/*  Prototype JavaScript framework, version 1.5.0
 *  (c) 2005-2007 Sam Stephenson
 *
 *  Prototype is freely distributable under the terms of an MIT-style license.
 *  For details, see the Prototype web site: http://prototype.conio.net/
 *
/*--------------------------------------------------------------------------*/

var Prototype = {
  Version: '1.5.0',
  BrowserFeatures: {
    XPath: !!document.evaluate
  },

  ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
  emptyFunction: function() {},
  K: function(x) { return x }
}

var Class = {
  create: function() {
    return function() {
      this.initialize.apply(this, arguments);
    }
  }
}

var Abstract = new Object();

Object.extend = function(destination, source) {
  for (var property in source) {
    destination[property] = source[property];
  }
  return destination;
}

Object.extend(Object, {
  inspect: function(object) {
    try {
      if (object === undefined) return 'undefined';
      if (object === null) return 'null';
      return object.inspect ? object.inspect() : object.toString();
    } catch (e) {
      if (e instanceof RangeError) return '...';
      throw e;
    }
  },

  keys: function(object) {
    var keys = [];
    for (var property in object)
      keys.push(property);
    return keys;
  },

  values: function(object) {
    var values = [];
    for (var property in object)
      values.push(object[property]);
    return values;
  },

  clone: function(object) {
    return Object.extend({}, object);
  }
});

Function.prototype.bind = function() {
  var __method = this, args = $A(arguments), object = args.shift();
  return function() {
    return __method.apply(object, args.concat($A(arguments)));
  }
}

Function.prototype.bindAsEventListener = function(object) {
  var __method = this, args = $A(arguments), object = args.shift();
  return function(event) {
    return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments)));
  }
}

Object.extend(Number.prototype, {
  toColorPart: function() {
    var digits = this.toString(16);
    if (this < 16) return '0' + digits;
    return digits;
  },

  succ: function() {
    return this + 1;
  },

  times: function(iterator) {
    $R(0, this, true).each(iterator);
    return this;
  }
});

var Try = {
  these: function() {
    var returnValue;

    for (var i = 0, length = arguments.length; i < length; i++) {
      var lambda = arguments[i];
      try {
        returnValue = lambda();
        break;
      } catch (e) {}
    }

    return returnValue;
  }
}

/*--------------------------------------------------------------------------*/

var PeriodicalExecuter = Class.create();
PeriodicalExecuter.prototype = {
  initialize: function(callback, frequency) {
    this.callback = callback;
    this.frequency = frequency;
    this.currentlyExecuting = false;

    this.registerCallback();
  },

  registerCallback: function() {
    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  },

  stop: function() {
    if (!this.timer) return;
    clearInterval(this.timer);
    this.timer = null;
  },

  onTimerEvent: function() {
    if (!this.currentlyExecuting) {
      try {
        this.currentlyExecuting = true;
        this.callback(this);
      } finally {
        this.currentlyExecuting = false;
      }
    }
  }
}
String.interpret = function(value){
  return value == null ? '' : String(value);
}

Object.extend(String.prototype, {
  gsub: function(pattern, replacement) {
    var result = '', source = this, match;
    replacement = arguments.callee.prepareReplacement(replacement);

    while (source.length > 0) {
      if (match = source.match(pattern)) {
        result += source.slice(0, match.index);
        result += String.interpret(replacement(match));
        source  = source.slice(match.index + match[0].length);
      } else {
        result += source, source = '';
      }
    }
    return result;
  },

  sub: function(pattern, replacement, count) {
    replacement = this.gsub.prepareReplacement(replacement);
    count = count === undefined ? 1 : count;

    return this.gsub(pattern, function(match) {
      if (--count < 0) return match[0];
      return replacement(match);
    });
  },

  scan: function(pattern, iterator) {
    this.gsub(pattern, iterator);
    return this;
  },

  truncate: function(length, truncation) {
    length = length || 30;
    truncation = truncation === undefined ? '...' : truncation;
    return this.length > length ?
      this.slice(0, length - truncation.length) + truncation : this;
  },

  strip: function() {
    return this.replace(/^\s+/, '').replace(/\s+$/, '');
  },

  stripTags: function() {
    return this.replace(/<\/?[^>]+>/gi, '');
  },

  stripScripts: function() {
    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
  },

  extractScripts: function() {
    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
    return (this.match(matchAll) || []).map(function(scriptTag) {
      return (scriptTag.match(matchOne) || ['', ''])[1];
    });
  },

  evalScripts: function() {
    return this.extractScripts().map(function(script) { return eval(script) });
  },

  escapeHTML: function() {
    var div = document.createElement('div');
    var text = document.createTextNode(this);
    div.appendChild(text);
    return div.innerHTML;
  },

  unescapeHTML: function() {
    var div = document.createElement('div');
    div.innerHTML = this.stripTags();
    return div.childNodes[0] ? (div.childNodes.length > 1 ?
      $A(div.childNodes).inject('',function(memo,node){ return memo+node.nodeValue }) :
      div.childNodes[0].nodeValue) : '';
  },

  toQueryParams: function(separator) {
    var match = this.strip().match(/([^?#]*)(#.*)?$/);
    if (!match) return {};

    return match[1].split(separator || '&').inject({}, function(hash, pair) {
      if ((pair = pair.split('='))[0]) {
        var name = decodeURIComponent(pair[0]);
        var value = pair[1] ? decodeURIComponent(pair[1]) : undefined;

        if (hash[name] !== undefined) {
          if (hash[name].constructor != Array)
            hash[name] = [hash[name]];
          if (value) hash[name].push(value);
        }
        else hash[name] = value;
      }
      return hash;
    });
  },

  toArray: function() {
    return this.split('');
  },

  succ: function() {
    return this.slice(0, this.length - 1) +
      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
  },

  camelize: function() {
    var parts = this.split('-'), len = parts.length;
    if (len == 1) return parts[0];

    var camelized = this.charAt(0) == '-'
      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
      : parts[0];

    for (var i = 1; i < len; i++)
      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);

    return camelized;
  },

  capitalize: function(){
    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
  },

  underscore: function() {
    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
  },

  dasherize: function() {
    return this.gsub(/_/,'-');
  },

  inspect: function(useDoubleQuotes) {
    var escapedString = this.replace(/\\/g, '\\\\');
    if (useDoubleQuotes)
      return '"' + escapedString.replace(/"/g, '\\"') + '"';
    else
      return "'" + escapedString.replace(/'/g, '\\\'') + "'";
  }
});

String.prototype.gsub.prepareReplacement = function(replacement) {
  if (typeof replacement == 'function') return replacement;
  var template = new Template(replacement);
  return function(match) { return template.evaluate(match) };
}

String.prototype.parseQuery = String.prototype.toQueryParams;

var Template = Class.create();
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
Template.prototype = {
  initialize: function(template, pattern) {
    this.template = template.toString();
    this.pattern  = pattern || Template.Pattern;
  },

  evaluate: function(object) {
    return this.template.gsub(this.pattern, function(match) {
      var before = match[1];
      if (before == '\\') return match[2];
      return before + String.interpret(object[match[3]]);
    });
  }
}

var $break    = new Object();
var $continue = new Object();

var Enumerable = {
  each: function(iterator) {
    var index = 0;
    try {
      this._each(function(value) {
        try {
          iterator(value, index++);
        } catch (e) {
          if (e != $continue) throw e;
        }
      });
    } catch (e) {
      if (e != $break) throw e;
    }
    return this;
  },

  eachSlice: function(number, iterator) {
    var index = -number, slices = [], array = this.toArray();
    while ((index += number) < array.length)
      slices.push(array.slice(index, index+number));
    return slices.map(iterator);
  },

  all: function(iterator) {
    var result = true;
    this.each(function(value, index) {
      result = result && !!(iterator || Prototype.K)(value, index);
      if (!result) throw $break;
    });
    return result;
  },

  any: function(iterator) {
    var result = false;
    this.each(function(value, index) {
      if (result = !!(iterator || Prototype.K)(value, index))
        throw $break;
    });
    return result;
  },

  collect: function(iterator) {
    var results = [];
    this.each(function(value, index) {
      results.push((iterator || Prototype.K)(value, index));
    });
    return results;
  },

  detect: function(iterator) {
    var result;
    this.each(function(value, index) {
      if (iterator(value, index)) {
        result = value;
        throw $break;
      }
    });
    return result;
  },

  findAll: function(iterator) {
    var results = [];
    this.each(function(value, index) {
      if (iterator(value, index))
        results.push(value);
    });
    return results;
  },

  grep: function(pattern, iterator) {
    var results = [];
    this.each(function(value, index) {
      var stringValue = value.toString();
      if (stringValue.match(pattern))
        results.push((iterator || Prototype.K)(value, index));
    })
    return results;
  },

  include: function(object) {
    var found = false;
    this.each(function(value) {
      if (value == object) {
        found = true;
        throw $break;
      }
    });
    return found;
  },

  inGroupsOf: function(number, fillWith) {
    fillWith = fillWith === undefined ? null : fillWith;
    return this.eachSlice(number, function(slice) {
      while(slice.length < number) slice.push(fillWith);
      return slice;
    });
  },

  inject: function(memo, iterator) {
    this.each(function(value, index) {
      memo = iterator(memo, value, index);
    });
    return memo;
  },

  invoke: function(method) {
    var args = $A(arguments).slice(1);
    return this.map(function(value) {
      return value[method].apply(value, args);
    });
  },

  max: function(iterator) {
    var result;
    this.each(function(value, index) {
      value = (iterator || Prototype.K)(value, index);
      if (result == undefined || value >= result)
        result = value;
    });
    return result;
  },

  min: function(iterator) {
    var result;
    this.each(function(value, index) {
      value = (iterator || Prototype.K)(value, index);
      if (result == undefined || value < result)
        result = value;
    });
    return result;
  },

  partition: function(iterator) {
    var trues = [], falses = [];
    this.each(function(value, index) {
      ((iterator || Prototype.K)(value, index) ?
        trues : falses).push(value);
    });
    return [trues, falses];
  },

  pluck: function(property) {
    var results = [];
    this.each(function(value, index) {
      results.push(value[property]);
    });
    return results;
  },

  reject: function(iterator) {
    var results = [];
    this.each(function(value, index) {
      if (!iterator(value, index))
        results.push(value);
    });
    return results;
  },

  sortBy: function(iterator) {
    return this.map(function(value, index) {
      return {value: value, criteria: iterator(value, index)};
    }).sort(function(left, right) {
      var a = left.criteria, b = right.criteria;
      return a < b ? -1 : a > b ? 1 : 0;
    }).pluck('value');
  },

  toArray: function() {
    return this.map();
  },

  zip: function() {
    var iterator = Prototype.K, args = $A(arguments);
    if (typeof args.last() == 'function')
      iterator = args.pop();

    var collections = [this].concat(args).map($A);
    return this.map(function(value, index) {
      return iterator(collections.pluck(index));
    });
  },

  size: function() {
    return this.toArray().length;
  },

  inspect: function() {
    return '#<Enumerable:' + this.toArray().inspect() + '>';
  }
}

Object.extend(Enumerable, {
  map:     Enumerable.collect,
  find:    Enumerable.detect,
  select:  Enumerable.findAll,
  member:  Enumerable.include,
  entries: Enumerable.toArray
});
var $A = Array.from = function(iterable) {
  if (!iterable) return [];
  if (iterable.toArray) {
    return iterable.toArray();
  } else {
    var results = [];
    for (var i = 0, length = iterable.length; i < length; i++)
      results.push(iterable[i]);
    return results;
  }
}

Object.extend(Array.prototype, Enumerable);

if (!Array.prototype._reverse)
  Array.prototype._reverse = Array.prototype.reverse;

Object.extend(Array.prototype, {
  _each: function(iterator) {
    for (var i = 0, length = this.length; i < length; i++)
      iterator(this[i]);
  },

  clear: function() {
    this.length = 0;
    return this;
  },

  first: function() {
    return this[0];
  },

  last: function() {
    return this[this.length - 1];
  },

  compact: function() {
    return this.select(function(value) {
      return value != null;
    });
  },

  flatten: function() {
    return this.inject([], function(array, value) {
      return array.concat(value && value.constructor == Array ?
        value.flatten() : [value]);
    });
  },

  without: function() {
    var values = $A(arguments);
    return this.select(function(value) {
      return !values.include(value);
    });
  },

  indexOf: function(object) {
    for (var i = 0, length = this.length; i < length; i++)
      if (this[i] == object) return i;
    return -1;
  },

  reverse: function(inline) {
    return (inline !== false ? this : this.toArray())._reverse();
  },

  reduce: function() {
    return this.length > 1 ? this : this[0];
  },

  uniq: function() {
    return this.inject([], function(array, value) {
      return array.include(value) ? array : array.concat([value]);
    });
  },

  clone: function() {
    return [].concat(this);
  },

  size: function() {
    return this.length;
  },

  inspect: function() {
    return '[' + this.map(Object.inspect).join(', ') + ']';
  }
});

Array.prototype.toArray = Array.prototype.clone;

function $w(string){
  string = string.strip();
  return string ? string.split(/\s+/) : [];
}

if(window.opera){
  Array.prototype.concat = function(){
    var array = [];
    for(var i = 0, length = this.length; i < length; i++) array.push(this[i]);
    for(var i = 0, length = arguments.length; i < length; i++) {
      if(arguments[i].constructor == Array) {
        for(var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
          array.push(arguments[i][j]);
      } else {
        array.push(arguments[i]);
      }
    }
    return array;
  }
}
var Hash = function(obj) {
  Object.extend(this, obj || {});
};

Object.extend(Hash, {
  toQueryString: function(obj) {
    var parts = [];

	  this.prototype._each.call(obj, function(pair) {
      if (!pair.key) return;

      if (pair.value && pair.value.constructor == Array) {
        var values = pair.value.compact();
        if (values.length < 2) pair.value = values.reduce();
        else {
        	key = encodeURIComponent(pair.key);
          values.each(function(value) {
            value = value != undefined ? encodeURIComponent(value) : '';
            parts.push(key + '=' + encodeURIComponent(value));
          });
          return;
        }
      }
      if (pair.value == undefined) pair[1] = '';
      parts.push(pair.map(encodeURIComponent).join('='));
	  });

    return parts.join('&');
  }
});

Object.extend(Hash.prototype, Enumerable);
Object.extend(Hash.prototype, {
  _each: function(iterator) {
    for (var key in this) {
      var value = this[key];
      if (value && value == Hash.prototype[key]) continue;

      var pair = [key, value];
      pair.key = key;
      pair.value = value;
      iterator(pair);
    }
  },

  keys: function() {
    return this.pluck('key');
  },

  values: function() {
    return this.pluck('value');
  },

  merge: function(hash) {
    return $H(hash).inject(this, function(mergedHash, pair) {
      mergedHash[pair.key] = pair.value;
      return mergedHash;
    });
  },

  remove: function() {
    var result;
    for(var i = 0, length = arguments.length; i < length; i++) {
      var value = this[arguments[i]];
      if (value !== undefined){
        if (result === undefined) result = value;
        else {
          if (result.constructor != Array) result = [result];
          result.push(value)
        }
      }
      delete this[arguments[i]];
    }
    return result;
  },

  toQueryString: function() {
    return Hash.toQueryString(this);
  },

  inspect: function() {
    return '#<Hash:{' + this.map(function(pair) {
      return pair.map(Object.inspect).join(': ');
    }).join(', ') + '}>';
  }
});

function $H(object) {
  if (object && object.constructor == Hash) return object;
  return new Hash(object);
};
ObjectRange = Class.create();
Object.extend(ObjectRange.prototype, Enumerable);
Object.extend(ObjectRange.prototype, {
  initialize: function(start, end, exclusive) {
    this.start = start;
    this.end = end;
    this.exclusive = exclusive;
  },

  _each: function(iterator) {
    var value = this.start;
    while (this.include(value)) {
      iterator(value);
      value = value.succ();
    }
  },

  include: function(value) {
    if (value < this.start)
      return false;
    if (this.exclusive)
      return value < this.end;
    return value <= this.end;
  }
});

var $R = function(start, end, exclusive) {
  return new ObjectRange(start, end, exclusive);
}

var Ajax = {
  getTransport: function() {
    return Try.these(
      function() {return new XMLHttpRequest()},
      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
    ) || false;
  },

  activeRequestCount: 0
}

Ajax.Responders = {
  responders: [],

  _each: function(iterator) {
    this.responders._each(iterator);
  },

  register: function(responder) {
    if (!this.include(responder))
      this.responders.push(responder);
  },

  unregister: function(responder) {
    this.responders = this.responders.without(responder);
  },

  dispatch: function(callback, request, transport, json) {
    this.each(function(responder) {
      if (typeof responder[callback] == 'function') {
        try {
          responder[callback].apply(responder, [request, transport, json]);
        } catch (e) {}
      }
    });
  }
};

Object.extend(Ajax.Responders, Enumerable);

Ajax.Responders.register({
  onCreate: function() {
    Ajax.activeRequestCount++;
  },
  onComplete: function() {
    Ajax.activeRequestCount--;
  }
});

Ajax.Base = function() {};
Ajax.Base.prototype = {
  setOptions: function(options) {
    this.options = {
      method:       'post',
      asynchronous: true,
      contentType:  'application/x-www-form-urlencoded',
      encoding:     'UTF-8',
      parameters:   ''
    }
    Object.extend(this.options, options || {});

    this.options.method = this.options.method.toLowerCase();
    if (typeof this.options.parameters == 'string')
      this.options.parameters = this.options.parameters.toQueryParams();
  }
}

Ajax.Request = Class.create();
Ajax.Request.Events =
  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
  _complete: false,

  initialize: function(url, options) {
    this.transport = Ajax.getTransport();
    this.setOptions(options);
    this.request(url);
  },

  request: function(url) {
    this.url = url;
    this.method = this.options.method;
    var params = this.options.parameters;

    if (!['get', 'post'].include(this.method)) {
      // simulate other verbs over post
      params['_method'] = this.method;
      this.method = 'post';
    }

    params = Hash.toQueryString(params);
    if (params && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_='

    // when GET, append parameters to URL
    if (this.method == 'get' && params)
      this.url += (this.url.indexOf('?') > -1 ? '&' : '?') + params;

    try {
      Ajax.Responders.dispatch('onCreate', this, this.transport);

      this.transport.open(this.method.toUpperCase(), this.url,
        this.options.asynchronous);

      if (this.options.asynchronous)
        setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10);

      this.transport.onreadystatechange = this.onStateChange.bind(this);
      this.setRequestHeaders();

      var body = this.method == 'post' ? (this.options.postBody || params) : null;

      this.transport.send(body);

      /* Force Firefox to handle ready state 4 for synchronous requests */
      if (!this.options.asynchronous && this.transport.overrideMimeType)
        this.onStateChange();

    }
    catch (e) {
      this.dispatchException(e);
    }
  },

  onStateChange: function() {
    var readyState = this.transport.readyState;
    if (readyState > 1 && !((readyState == 4) && this._complete))
      this.respondToReadyState(this.transport.readyState);
  },

  setRequestHeaders: function() {
    var headers = {
      'X-Requested-With': 'XMLHttpRequest',
      'X-Prototype-Version': Prototype.Version,
      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
    };

    if (this.method == 'post') {
      headers['Content-type'] = this.options.contentType +
        (this.options.encoding ? '; charset=' + this.options.encoding : '');

      /* Force "Connection: close" for older Mozilla browsers to work
       * around a bug where XMLHttpRequest sends an incorrect
       * Content-length header. See Mozilla Bugzilla #246651.
       */
      if (this.transport.overrideMimeType &&
          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
            headers['Connection'] = 'close';
    }

    // user-defined headers
    if (typeof this.options.requestHeaders == 'object') {
      var extras = this.options.requestHeaders;

      if (typeof extras.push == 'function')
        for (var i = 0, length = extras.length; i < length; i += 2)
          headers[extras[i]] = extras[i+1];
      else
        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
    }

    for (var name in headers)
     if (typeof headers[name] != 'function') //Added by Steve Kallestad for compatibility with json.js
      this.transport.setRequestHeader(name, headers[name]);
  },

  success: function() {
    return !this.transport.status
        || (this.transport.status >= 200 && this.transport.status < 300);
  },

  respondToReadyState: function(readyState) {
    var state = Ajax.Request.Events[readyState];
    var transport = this.transport, json = this.evalJSON();

    if (state == 'Complete') {
      try {
        this._complete = true;
        (this.options['on' + this.transport.status]
         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
         || Prototype.emptyFunction)(transport, json);
      } catch (e) {
        this.dispatchException(e);
      }

      if ((this.getHeader('Content-type') || 'text/javascript').strip().
        match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i))
          this.evalResponse();
    }

    try {
      (this.options['on' + state] || Prototype.emptyFunction)(transport, json);
      Ajax.Responders.dispatch('on' + state, this, transport, json);
    } catch (e) {
      this.dispatchException(e);
    }

    if (state == 'Complete') {
      // avoid memory leak in MSIE: clean up
      this.transport.onreadystatechange = Prototype.emptyFunction;
    }
  },

  getHeader: function(name) {
    try {
      return this.transport.getResponseHeader(name);
    } catch (e) { return null }
  },

  evalJSON: function() {
    try {
      var json = this.getHeader('X-JSON');
      return json ? eval('(' + json + ')') : null;
    } catch (e) { return null }
  },

  evalResponse: function() {
    try {
      return eval(this.transport.responseText);
    } catch (e) {
      this.dispatchException(e);
    }
  },

  dispatchException: function(exception) {
    (this.options.onException || Prototype.emptyFunction)(this, exception);
    Ajax.Responders.dispatch('onException', this, exception);
  }
});

Ajax.Updater = Class.create();

Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
  initialize: function(container, url, options) {
    this.container = {
      success: (container.success || container),
      failure: (container.failure || (container.success ? null : container))
    }

    this.transport = Ajax.getTransport();
    this.setOptions(options);

    var onComplete = this.options.onComplete || Prototype.emptyFunction;
    this.options.onComplete = (function(transport, param) {
      this.updateContent();
      onComplete(transport, param);
    }).bind(this);

    this.request(url);
  },

  updateContent: function() {
    var receiver = this.container[this.success() ? 'success' : 'failure'];
    var response = this.transport.responseText;

    if (!this.options.evalScripts) response = response.stripScripts();

    if (receiver = $(receiver)) {
      if (this.options.insertion)
        new this.options.insertion(receiver, response);
      else
        receiver.update(response);
    }

    if (this.success()) {
      if (this.onComplete)
        setTimeout(this.onComplete.bind(this), 10);
    }
  }
});

Ajax.PeriodicalUpdater = Class.create();
Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
  initialize: function(container, url, options) {
    this.setOptions(options);
    this.onComplete = this.options.onComplete;

    this.frequency = (this.options.frequency || 2);
    this.decay = (this.options.decay || 1);

    this.updater = {};
    this.container = container;
    this.url = url;

    this.start();
  },

  start: function() {
    this.options.onComplete = this.updateComplete.bind(this);
    this.onTimerEvent();
  },

  stop: function() {
    this.updater.options.onComplete = undefined;
    clearTimeout(this.timer);
    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
  },

  updateComplete: function(request) {
    if (this.options.decay) {
      this.decay = (request.responseText == this.lastText ?
        this.decay * this.options.decay : 1);

      this.lastText = request.responseText;
    }
    this.timer = setTimeout(this.onTimerEvent.bind(this),
      this.decay * this.frequency * 1000);
  },

  onTimerEvent: function() {
    this.updater = new Ajax.Updater(this.container, this.url, this.options);
  }
});
function $(element) {
  if (arguments.length > 1) {
    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
      elements.push($(arguments[i]));
    return elements;
  }
  if (typeof element == 'string')
    element = document.getElementById(element);
  return Element.extend(element);
}

if (Prototype.BrowserFeatures.XPath) {
  document._getElementsByXPath = function(expression, parentElement) {
    var results = [];
    var query = document.evaluate(expression, $(parentElement) || document,
      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    for (var i = 0, length = query.snapshotLength; i < length; i++)
      results.push(query.snapshotItem(i));
    return results;
  };
}

document.getElementsByClassName = function(className, parentElement) {
  if (Prototype.BrowserFeatures.XPath) {
    var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
    return document._getElementsByXPath(q, parentElement);
  } else {
    var children = ($(parentElement) || document.body).getElementsByTagName('*');
    var elements = [], child;
    for (var i = 0, length = children.length; i < length; i++) {
      child = children[i];
      if (Element.hasClassName(child, className))
        elements.push(Element.extend(child));
    }
    return elements;
  }
};

/*--------------------------------------------------------------------------*/

if (!window.Element)
  var Element = new Object();

Element.extend = function(element) {
  if (!element || _nativeExtensions || element.nodeType == 3) return element;

  if (!element._extended && element.tagName && element != window) {
    var methods = Object.clone(Element.Methods), cache = Element.extend.cache;

    if (element.tagName == 'FORM')
      Object.extend(methods, Form.Methods);
    if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName))
      Object.extend(methods, Form.Element.Methods);

    Object.extend(methods, Element.Methods.Simulated);

    for (var property in methods) {
      var value = methods[property];
      if (typeof value == 'function' && !(property in element))
        element[property] = cache.findOrStore(value);
    }
  }

  element._extended = true;
  return element;
};

Element.extend.cache = {
  findOrStore: function(value) {
    return this[value] = this[value] || function() {
      return value.apply(null, [this].concat($A(arguments)));
    }
  }
};

Element.Methods = {
  visible: function(element) {
    return $(element).style.display != 'none';
  },

  toggle: function(element) {
    element = $(element);
    Element[Element.visible(element) ? 'hide' : 'show'](element);
    return element;
  },

  hide: function(element) {
    $(element).style.display = 'none';
    return element;
  },

  show: function(element) {
    $(element).style.display = '';
    return element;
  },

  remove: function(element) {
    element = $(element);
    element.parentNode.removeChild(element);
    return element;
  },

  update: function(element, html) {
    html = typeof html == 'undefined' ? '' : html.toString();
    $(element).innerHTML = html.stripScripts();
    setTimeout(function() {html.evalScripts()}, 10);
    return element;
  },

  replace: function(element, html) {
    element = $(element);
    html = typeof html == 'undefined' ? '' : html.toString();
    if (element.outerHTML) {
      element.outerHTML = html.stripScripts();
    } else {
      var range = element.ownerDocument.createRange();
      range.selectNodeContents(element);
      element.parentNode.replaceChild(
        range.createContextualFragment(html.stripScripts()), element);
    }
    setTimeout(function() {html.evalScripts()}, 10);
    return element;
  },

  inspect: function(element) {
    element = $(element);
    var result = '<' + element.tagName.toLowerCase();
    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
      var property = pair.first(), attribute = pair.last();
      var value = (element[property] || '').toString();
      if (value) result += ' ' + attribute + '=' + value.inspect(true);
    });
    return result + '>';
  },

  recursivelyCollect: function(element, property) {
    element = $(element);
    var elements = [];
    while (element = element[property])
      if (element.nodeType == 1)
        elements.push(Element.extend(element));
    return elements;
  },

  ancestors: function(element) {
    return $(element).recursivelyCollect('parentNode');
  },

  descendants: function(element) {
    return $A($(element).getElementsByTagName('*'));
  },

  immediateDescendants: function(element) {
    if (!(element = $(element).firstChild)) return [];
    while (element && element.nodeType != 1) element = element.nextSibling;
    if (element) return [element].concat($(element).nextSiblings());
    return [];
  },

  previousSiblings: function(element) {
    return $(element).recursivelyCollect('previousSibling');
  },

  nextSiblings: function(element) {
    return $(element).recursivelyCollect('nextSibling');
  },

  siblings: function(element) {
    element = $(element);
    return element.previousSiblings().reverse().concat(element.nextSiblings());
  },

  match: function(element, selector) {
    if (typeof selector == 'string')
      selector = new Selector(selector);
    return selector.match($(element));
  },

  up: function(element, expression, index) {
    return Selector.findElement($(element).ancestors(), expression, index);
  },

  down: function(element, expression, index) {
    return Selector.findElement($(element).descendants(), expression, index);
  },

  previous: function(element, expression, index) {
    return Selector.findElement($(element).previousSiblings(), expression, index);
  },

  next: function(element, expression, index) {
    return Selector.findElement($(element).nextSiblings(), expression, index);
  },

  getElementsBySelector: function() {
    var args = $A(arguments), element = $(args.shift());
    return Selector.findChildElements(element, args);
  },

  getElementsByClassName: function(element, className) {
    return document.getElementsByClassName(className, element);
  },

  readAttribute: function(element, name) {
    element = $(element);
    if (document.all && !window.opera) {
      var t = Element._attributeTranslations;
      if (t.values[name]) return t.values[name](element, name);
      if (t.names[name])  name = t.names[name];
      var attribute = element.attributes[name];
      if(attribute) return attribute.nodeValue;
    }
    return element.getAttribute(name);
  },

  getHeight: function(element) {
    return $(element).getDimensions().height;
  },

  getWidth: function(element) {
    return $(element).getDimensions().width;
  },

  classNames: function(element) {
    return new Element.ClassNames(element);
  },

  hasClassName: function(element, className) {
    if (!(element = $(element))) return;
    var elementClassName = element.className;
    if (elementClassName.length == 0) return false;
    if (elementClassName == className ||
        elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
      return true;
    return false;
  },

  addClassName: function(element, className) {
    if (!(element = $(element))) return;
    Element.classNames(element).add(className);
    return element;
  },

  removeClassName: function(element, className) {
    if (!(element = $(element))) return;
    Element.classNames(element).remove(className);
    return element;
  },

  toggleClassName: function(element, className) {
    if (!(element = $(element))) return;
    Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className);
    return element;
  },

  observe: function() {
    Event.observe.apply(Event, arguments);
    return $A(arguments).first();
  },

  stopObserving: function() {
    Event.stopObserving.apply(Event, arguments);
    return $A(arguments).first();
  },

  // removes whitespace-only text node children
  cleanWhitespace: function(element) {
    element = $(element);
    var node = element.firstChild;
    while (node) {
      var nextNode = node.nextSibling;
      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
        element.removeChild(node);
      node = nextNode;
    }
    return element;
  },

  empty: function(element) {
    return $(element).innerHTML.match(/^\s*$/);
  },

  descendantOf: function(element, ancestor) {
    element = $(element), ancestor = $(ancestor);
    while (element = element.parentNode)
      if (element == ancestor) return true;
    return false;
  },

  scrollTo: function(element) {
    element = $(element);
    var pos = Position.cumulativeOffset(element);
    window.scrollTo(pos[0], pos[1]);
    return element;
  },

  getStyle: function(element, style) {
    element = $(element);
    if (['float','cssFloat'].include(style))
      style = (typeof element.style.styleFloat != 'undefined' ? 'styleFloat' : 'cssFloat');
    style = style.camelize();
    var value = element.style[style];
    if (!value) {
      if (document.defaultView && document.defaultView.getComputedStyle) {
        var css = document.defaultView.getComputedStyle(element, null);
        value = css ? css[style] : null;
      } else if (element.currentStyle) {
        value = element.currentStyle[style];
      }
    }

    if((value == 'auto') && ['width','height'].include(style) && (element.getStyle('display') != 'none'))
      value = element['offset'+style.capitalize()] + 'px';

    if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
      if (Element.getStyle(element, 'position') == 'static') value = 'auto';
    if(style == 'opacity') {
      if(value) return parseFloat(value);
      if(value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
        if(value[1]) return parseFloat(value[1]) / 100;
      return 1.0;
    }
    return value == 'auto' ? null : value;
  },

  setStyle: function(element, style) {
    element = $(element);
    for (var name in style) {
      var value = style[name];
      if(name == 'opacity') {
        if (value == 1) {
          value = (/Gecko/.test(navigator.userAgent) &&
            !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 0.999999 : 1.0;
          if(/MSIE/.test(navigator.userAgent) && !window.opera)
            element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'');
        } else if(value === '') {
          if(/MSIE/.test(navigator.userAgent) && !window.opera)
            element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'');
        } else {
          if(value < 0.00001) value = 0;
          if(/MSIE/.test(navigator.userAgent) && !window.opera)
            element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') +
              'alpha(opacity='+value*100+')';
        }
      } else if(['float','cssFloat'].include(name)) name = (typeof element.style.styleFloat != 'undefined') ? 'styleFloat' : 'cssFloat';
      element.style[name.camelize()] = value;
    }
    return element;
  },

  getDimensions: function(element) {
    element = $(element);
    var display = $(element).getStyle('display');
    if (display != 'none' && display != null) // Safari bug
      return {width: element.offsetWidth, height: element.offsetHeight};

    // All *Width and *Height properties give 0 on elements with display none,
    // so enable the element temporarily
    var els = element.style;
    var originalVisibility = els.visibility;
    var originalPosition = els.position;
    var originalDisplay = els.display;
    els.visibility = 'hidden';
    els.position = 'absolute';
    els.display = 'block';
    var originalWidth = element.clientWidth;
    var originalHeight = element.clientHeight;
    els.display = originalDisplay;
    els.position = originalPosition;
    els.visibility = originalVisibility;
    return {width: originalWidth, height: originalHeight};
  },

  makePositioned: function(element) {
    element = $(element);
    var pos = Element.getStyle(element, 'position');
    if (pos == 'static' || !pos) {
      element._madePositioned = true;
      element.style.position = 'relative';
      // Opera returns the offset relative to the positioning context, when an
      // element is position relative but top and left have not been defined
      if (window.opera) {
        element.style.top = 0;
        element.style.left = 0;
      }
    }
    return element;
  },

  undoPositioned: function(element) {
    element = $(element);
    if (element._madePositioned) {
      element._madePositioned = undefined;
      element.style.position =
        element.style.top =
        element.style.left =
        element.style.bottom =
        element.style.right = '';
    }
    return element;
  },

  makeClipping: function(element) {
    element = $(element);
    if (element._overflow) return element;
    element._overflow = element.style.overflow || 'auto';
    if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
      element.style.overflow = 'hidden';
    return element;
  },

  undoClipping: function(element) {
    element = $(element);
    if (!element._overflow) return element;
    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
    element._overflow = null;
    return element;
  }
};

Object.extend(Element.Methods, {childOf: Element.Methods.descendantOf});

Element._attributeTranslations = {};

Element._attributeTranslations.names = {
  colspan:   "colSpan",
  rowspan:   "rowSpan",
  valign:    "vAlign",
  datetime:  "dateTime",
  accesskey: "accessKey",
  tabindex:  "tabIndex",
  enctype:   "encType",
  maxlength: "maxLength",
  readonly:  "readOnly",
  longdesc:  "longDesc"
};

Element._attributeTranslations.values = {
  _getAttr: function(element, attribute) {
    return element.getAttribute(attribute, 2);
  },

  _flag: function(element, attribute) {
    return $(element).hasAttribute(attribute) ? attribute : null;
  },

  style: function(element) {
    return element.style.cssText.toLowerCase();
  },

  title: function(element) {
    var node = element.getAttributeNode('title');
    return node.specified ? node.nodeValue : null;
  }
};

Object.extend(Element._attributeTranslations.values, {
  href: Element._attributeTranslations.values._getAttr,
  src:  Element._attributeTranslations.values._getAttr,
  disabled: Element._attributeTranslations.values._flag,
  checked:  Element._attributeTranslations.values._flag,
  readonly: Element._attributeTranslations.values._flag,
  multiple: Element._attributeTranslations.values._flag
});

Element.Methods.Simulated = {
  hasAttribute: function(element, attribute) {
    var t = Element._attributeTranslations;
    attribute = t.names[attribute] || attribute;
    return $(element).getAttributeNode(attribute).specified;
  }
};

// IE is missing .innerHTML support for TABLE-related elements
if (document.all && !window.opera){
  Element.Methods.update = function(element, html) {
    element = $(element);
    html = typeof html == 'undefined' ? '' : html.toString();
    var tagName = element.tagName.toUpperCase();
    if (['THEAD','TBODY','TR','TD'].include(tagName)) {
      var div = document.createElement('div');
      switch (tagName) {
        case 'THEAD':
        case 'TBODY':
          div.innerHTML = '<table><tbody>' +  html.stripScripts() + '</tbody></table>';
          depth = 2;
          break;
        case 'TR':
          div.innerHTML = '<table><tbody><tr>' +  html.stripScripts() + '</tr></tbody></table>';
          depth = 3;
          break;
        case 'TD':
          div.innerHTML = '<table><tbody><tr><td>' +  html.stripScripts() + '</td></tr></tbody></table>';
          depth = 4;
      }
      $A(element.childNodes).each(function(node){
        element.removeChild(node)
      });
      depth.times(function(){ div = div.firstChild });

      $A(div.childNodes).each(
        function(node){ element.appendChild(node) });
    } else {
      element.innerHTML = html.stripScripts();
    }
    setTimeout(function() {html.evalScripts()}, 10);
    return element;
  }
};

Object.extend(Element, Element.Methods);

var _nativeExtensions = false;

if(/Konqueror|Safari|KHTML/.test(navigator.userAgent))
  ['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) {
    var className = 'HTML' + tag + 'Element';
    if(window[className]) return;
    var klass = window[className] = {};
    klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__proto__;
  });

Element.addMethods = function(methods) {
  Object.extend(Element.Methods, methods || {});

  function copy(methods, destination, onlyIfAbsent) {
    onlyIfAbsent = onlyIfAbsent || false;
    var cache = Element.extend.cache;
    for (var property in methods) {
      var value = methods[property];
      if (!onlyIfAbsent || !(property in destination))
        destination[property] = cache.findOrStore(value);
    }
  }

  if (typeof HTMLElement != 'undefined') {
    copy(Element.Methods, HTMLElement.prototype);
    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
    copy(Form.Methods, HTMLFormElement.prototype);
    [HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) {
      copy(Form.Element.Methods, klass.prototype);
    });
    _nativeExtensions = true;
  }
}

var Toggle = new Object();
Toggle.display = Element.toggle;

/*--------------------------------------------------------------------------*/

Abstract.Insertion = function(adjacency) {
  this.adjacency = adjacency;
}

Abstract.Insertion.prototype = {
  initialize: function(element, content) {
    this.element = $(element);
    this.content = content.stripScripts();

    if (this.adjacency && this.element.insertAdjacentHTML) {
      try {
        this.element.insertAdjacentHTML(this.adjacency, this.content);
      } catch (e) {
        var tagName = this.element.tagName.toUpperCase();
        if (['TBODY', 'TR'].include(tagName)) {
          this.insertContent(this.contentFromAnonymousTable());
        } else {
          throw e;
        }
      }
    } else {
      this.range = this.element.ownerDocument.createRange();
      if (this.initializeRange) this.initializeRange();
      this.insertContent([this.range.createContextualFragment(this.content)]);
    }

    setTimeout(function() {content.evalScripts()}, 10);
  },

  contentFromAnonymousTable: function() {
    var div = document.createElement('div');
    div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
    return $A(div.childNodes[0].childNodes[0].childNodes);
  }
}

var Insertion = new Object();

Insertion.Before = Class.create();
Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
  initializeRange: function() {
    this.range.setStartBefore(this.element);
  },

  insertContent: function(fragments) {
    fragments.each((function(fragment) {
      this.element.parentNode.insertBefore(fragment, this.element);
    }).bind(this));
  }
});

Insertion.Top = Class.create();
Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
  initializeRange: function() {
    this.range.selectNodeContents(this.element);
    this.range.collapse(true);
  },

  insertContent: function(fragments) {
    fragments.reverse(false).each((function(fragment) {
      this.element.insertBefore(fragment, this.element.firstChild);
    }).bind(this));
  }
});

Insertion.Bottom = Class.create();
Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
  initializeRange: function() {
    this.range.selectNodeContents(this.element);
    this.range.collapse(this.element);
  },

  insertContent: function(fragments) {
    fragments.each((function(fragment) {
      this.element.appendChild(fragment);
    }).bind(this));
  }
});

Insertion.After = Class.create();
Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
  initializeRange: function() {
    this.range.setStartAfter(this.element);
  },

  insertContent: function(fragments) {
    fragments.each((function(fragment) {
      this.element.parentNode.insertBefore(fragment,
        this.element.nextSibling);
    }).bind(this));
  }
});

/*--------------------------------------------------------------------------*/

Element.ClassNames = Class.create();
Element.ClassNames.prototype = {
  initialize: function(element) {
    this.element = $(element);
  },

  _each: function(iterator) {
    this.element.className.split(/\s+/).select(function(name) {
      return name.length > 0;
    })._each(iterator);
  },

  set: function(className) {
    this.element.className = className;
  },

  add: function(classNameToAdd) {
    if (this.include(classNameToAdd)) return;
    this.set($A(this).concat(classNameToAdd).join(' '));
  },

  remove: function(classNameToRemove) {
    if (!this.include(classNameToRemove)) return;
    this.set($A(this).without(classNameToRemove).join(' '));
  },

  toString: function() {
    return $A(this).join(' ');
  }
};

Object.extend(Element.ClassNames.prototype, Enumerable);
var Selector = Class.create();
Selector.prototype = {
  initialize: function(expression) {
    this.params = {classNames: []};
    this.expression = expression.toString().strip();
    this.parseExpression();
    this.compileMatcher();
  },

  parseExpression: function() {
    function abort(message) { throw 'Parse error in selector: ' + message; }

    if (this.expression == '')  abort('empty expression');

    var params = this.params, expr = this.expression, match, modifier, clause, rest;
    while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
      params.attributes = params.attributes || [];
      params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
      expr = match[1];
    }

    if (expr == '*') return this.params.wildcard = true;

    while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) {
      modifier = match[1], clause = match[2], rest = match[3];
      switch (modifier) {
        case '#':       params.id = clause; break;
        case '.':       params.classNames.push(clause); break;
        case '':
        case undefined: params.tagName = clause.toUpperCase(); break;
        default:        abort(expr.inspect());
      }
      expr = rest;
    }

    if (expr.length > 0) abort(expr.inspect());
  },

  buildMatchExpression: function() {
    var params = this.params, conditions = [], clause;

    if (params.wildcard)
      conditions.push('true');
    if (clause = params.id)
      conditions.push('element.readAttribute("id") == ' + clause.inspect());
    if (clause = params.tagName)
      conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
    if ((clause = params.classNames).length > 0)
      for (var i = 0, length = clause.length; i < length; i++)
        conditions.push('element.hasClassName(' + clause[i].inspect() + ')');
    if (clause = params.attributes) {
      clause.each(function(attribute) {
        var value = 'element.readAttribute(' + attribute.name.inspect() + ')';
        var splitValueBy = function(delimiter) {
          return value + ' && ' + value + '.split(' + delimiter.inspect() + ')';
        }

        switch (attribute.operator) {
          case '=':       conditions.push(value + ' == ' + attribute.value.inspect()); break;
          case '~=':      conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break;
          case '|=':      conditions.push(
                            splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()
                          ); break;
          case '!=':      conditions.push(value + ' != ' + attribute.value.inspect()); break;
          case '':
          case undefined: conditions.push('element.hasAttribute(' + attribute.name.inspect() + ')'); break;
          default:        throw 'Unknown operator ' + attribute.operator + ' in selector';
        }
      });
    }

    return conditions.join(' && ');
  },

  compileMatcher: function() {
    this.match = new Function('element', 'if (!element.tagName) return false; \
      element = $(element); \
      return ' + this.buildMatchExpression());
  },

  findElements: function(scope) {
    var element;

    if (element = $(this.params.id))
      if (this.match(element))
        if (!scope || Element.childOf(element, scope))
          return [element];

    scope = (scope || document).getElementsByTagName(this.params.tagName || '*');

    var results = [];
    for (var i = 0, length = scope.length; i < length; i++)
      if (this.match(element = scope[i]))
        results.push(Element.extend(element));

    return results;
  },

  toString: function() {
    return this.expression;
  }
}

Object.extend(Selector, {
  matchElements: function(elements, expression) {
    var selector = new Selector(expression);
    return elements.select(selector.match.bind(selector)).map(Element.extend);
  },

  findElement: function(elements, expression, index) {
    if (typeof expression == 'number') index = expression, expression = false;
    return Selector.matchElements(elements, expression || '*')[index || 0];
  },

  findChildElements: function(element, expressions) {
    return expressions.map(function(expression) {
      return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], function(results, expr) {
        var selector = new Selector(expr);
        return results.inject([], function(elements, result) {
          return elements.concat(selector.findElements(result || element));
        });
      });
    }).flatten();
  }
});

function $$() {
  return Selector.findChildElements(document, $A(arguments));
}
var Form = {
  reset: function(form) {
    $(form).reset();
    return form;
  },

  serializeElements: function(elements, getHash) {
    var data = elements.inject({}, function(result, element) {
      if (!element.disabled && element.name) {
        var key = element.name, value = $(element).getValue();
        if (value != undefined) {
          if (result[key]) {
            if (result[key].constructor != Array) result[key] = [result[key]];
            result[key].push(value);
          }
          else result[key] = value;
        }
      }
      return result;
    });

    return getHash ? data : Hash.toQueryString(data);
  }
};

Form.Methods = {
  serialize: function(form, getHash) {
    return Form.serializeElements(Form.getElements(form), getHash);
  },

  getElements: function(form) {
    return $A($(form).getElementsByTagName('*')).inject([],
      function(elements, child) {
        if (Form.Element.Serializers[child.tagName.toLowerCase()])
          elements.push(Element.extend(child));
        return elements;
      }
    );
  },

  getInputs: function(form, typeName, name) {
    form = $(form);
    var inputs = form.getElementsByTagName('input');

    if (!typeName && !name) return $A(inputs).map(Element.extend);

    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
      var input = inputs[i];
      if ((typeName && input.type != typeName) || (name && input.name != name))
        continue;
      matchingInputs.push(Element.extend(input));
    }

    return matchingInputs;
  },

  disable: function(form) {
    form = $(form);
    form.getElements().each(function(element) {
      element.blur();
      element.disabled = 'true';
    });
    return form;
  },

  enable: function(form) {
    form = $(form);
    form.getElements().each(function(element) {
      element.disabled = '';
    });
    return form;
  },

  findFirstElement: function(form) {
    return $(form).getElements().find(function(element) {
      return element.type != 'hidden' && !element.disabled &&
        ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
    });
  },

  focusFirstElement: function(form) {
    form = $(form);
    form.findFirstElement().activate();
    return form;
  }
}

Object.extend(Form, Form.Methods);

/*--------------------------------------------------------------------------*/

Form.Element = {
  focus: function(element) {
    $(element).focus();
    return element;
  },

  select: function(element) {
    $(element).select();
    return element;
  }
}

Form.Element.Methods = {
  serialize: function(element) {
    element = $(element);
    if (!element.disabled && element.name) {
      var value = element.getValue();
      if (value != undefined) {
        var pair = {};
        pair[element.name] = value;
        return Hash.toQueryString(pair);
      }
    }
    return '';
  },

  getValue: function(element) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    return Form.Element.Serializers[method](element);
  },

  clear: function(element) {
    $(element).value = '';
    return element;
  },

  present: function(element) {
    return $(element).value != '';
  },

  activate: function(element) {
    element = $(element);
    element.focus();
    if (element.select && ( element.tagName.toLowerCase() != 'input' ||
      !['button', 'reset', 'submit'].include(element.type) ) )
      element.select();
    return element;
  },

  disable: function(element) {
    element = $(element);
    element.disabled = true;
    return element;
  },

  enable: function(element) {
    element = $(element);
    element.blur();
    element.disabled = false;
    return element;
  }
}

Object.extend(Form.Element, Form.Element.Methods);
var Field = Form.Element;
var $F = Form.Element.getValue;

/*--------------------------------------------------------------------------*/

Form.Element.Serializers = {
  input: function(element) {
    switch (element.type.toLowerCase()) {
      case 'checkbox':
      case 'radio':
        return Form.Element.Serializers.inputSelector(element);
      default:
        return Form.Element.Serializers.textarea(element);
    }
  },

  inputSelector: function(element) {
    return element.checked ? element.value : null;
  },

  textarea: function(element) {
    return element.value;
  },

  select: function(element) {
    return this[element.type == 'select-one' ?
      'selectOne' : 'selectMany'](element);
  },

  selectOne: function(element) {
    var index = element.selectedIndex;
    return index >= 0 ? this.optionValue(element.options[index]) : null;
  },

  selectMany: function(element) {
    var values, length = element.length;
    if (!length) return null;

    for (var i = 0, values = []; i < length; i++) {
      var opt = element.options[i];
      if (opt.selected) values.push(this.optionValue(opt));
    }
    return values;
  },

  optionValue: function(opt) {
    // extend element because hasAttribute may not be native
    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
  }
}

/*--------------------------------------------------------------------------*/

Abstract.TimedObserver = function() {}
Abstract.TimedObserver.prototype = {
  initialize: function(element, frequency, callback) {
    this.frequency = frequency;
    this.element   = $(element);
    this.callback  = callback;

    this.lastValue = this.getValue();
    this.registerCallback();
  },

  registerCallback: function() {
    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  },

  onTimerEvent: function() {
    var value = this.getValue();
    var changed = ('string' == typeof this.lastValue && 'string' == typeof value
      ? this.lastValue != value : String(this.lastValue) != String(value));
    if (changed) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  }
}

Form.Element.Observer = Class.create();
Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.Observer = Class.create();
Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
  getValue: function() {
    return Form.serialize(this.element);
  }
});

/*--------------------------------------------------------------------------*/

Abstract.EventObserver = function() {}
Abstract.EventObserver.prototype = {
  initialize: function(element, callback) {
    this.element  = $(element);
    this.callback = callback;

    this.lastValue = this.getValue();
    if (this.element.tagName.toLowerCase() == 'form')
      this.registerFormCallbacks();
    else
      this.registerCallback(this.element);
  },

  onElementEvent: function() {
    var value = this.getValue();
    if (this.lastValue != value) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  },

  registerFormCallbacks: function() {
    Form.getElements(this.element).each(this.registerCallback.bind(this));
  },

  registerCallback: function(element) {
    if (element.type) {
      switch (element.type.toLowerCase()) {
        case 'checkbox':
        case 'radio':
          Event.observe(element, 'click', this.onElementEvent.bind(this));
          break;
        default:
          Event.observe(element, 'change', this.onElementEvent.bind(this));
          break;
      }
    }
  }
}

Form.Element.EventObserver = Class.create();
Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.EventObserver = Class.create();
Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
  getValue: function() {
    return Form.serialize(this.element);
  }
});
if (!window.Event) {
  var Event = new Object();
}

Object.extend(Event, {
  KEY_BACKSPACE: 8,
  KEY_TAB:       9,
  KEY_RETURN:   13,
  KEY_ESC:      27,
  KEY_LEFT:     37,
  KEY_UP:       38,
  KEY_RIGHT:    39,
  KEY_DOWN:     40,
  KEY_DELETE:   46,
  KEY_HOME:     36,
  KEY_END:      35,
  KEY_PAGEUP:   33,
  KEY_PAGEDOWN: 34,

  element: function(event) {
    return event.target || event.srcElement;
  },

  isLeftClick: function(event) {
    return (((event.which) && (event.which == 1)) ||
            ((event.button) && (event.button == 1)));
  },

  pointerX: function(event) {
    return event.pageX || (event.clientX +
      (document.documentElement.scrollLeft || document.body.scrollLeft));
  },

  pointerY: function(event) {
    return event.pageY || (event.clientY +
      (document.documentElement.scrollTop || document.body.scrollTop));
  },

  stop: function(event) {
    if (event.preventDefault) {
      event.preventDefault();
      event.stopPropagation();
    } else {
      event.returnValue = false;
      event.cancelBubble = true;
    }
  },

  // find the first node with the given tagName, starting from the
  // node the event was triggered on; traverses the DOM upwards
  findElement: function(event, tagName) {
    var element = Event.element(event);
    while (element.parentNode && (!element.tagName ||
        (element.tagName.toUpperCase() != tagName.toUpperCase())))
      element = element.parentNode;
    return element;
  },

  observers: false,

  _observeAndCache: function(element, name, observer, useCapture) {
    if (!this.observers) this.observers = [];
    if (element.addEventListener) {
      this.observers.push([element, name, observer, useCapture]);
      element.addEventListener(name, observer, useCapture);
    } else if (element.attachEvent) {
      this.observers.push([element, name, observer, useCapture]);
      element.attachEvent('on' + name, observer);
    }
  },

  unloadCache: function() {
    if (!Event.observers) return;
    for (var i = 0, length = Event.observers.length; i < length; i++) {
      Event.stopObserving.apply(this, Event.observers[i]);
      Event.observers[i][0] = null;
    }
    Event.observers = false;
  },

  observe: function(element, name, observer, useCapture) {
    element = $(element);
    useCapture = useCapture || false;

    if (name == 'keypress' &&
        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
        || element.attachEvent))
      name = 'keydown';

    Event._observeAndCache(element, name, observer, useCapture);
  },

  stopObserving: function(element, name, observer, useCapture) {
    element = $(element);
    useCapture = useCapture || false;

    if (name == 'keypress' &&
        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
        || element.detachEvent))
      name = 'keydown';

    if (element.removeEventListener) {
      element.removeEventListener(name, observer, useCapture);
    } else if (element.detachEvent) {
      try {
        element.detachEvent('on' + name, observer);
      } catch (e) {}
    }
  }
});

/* prevent memory leaks in IE */
if (navigator.appVersion.match(/\bMSIE\b/))
  Event.observe(window, 'unload', Event.unloadCache, false);
var Position = {
  // set to true if needed, warning: firefox performance problems
  // NOT neeeded for page scrolling, only if draggable contained in
  // scrollable elements
  includeScrollOffsets: false,

  // must be called before calling withinIncludingScrolloffset, every time the
  // page is scrolled
  prepare: function() {
    this.deltaX =  window.pageXOffset
                || document.documentElement.scrollLeft
                || document.body.scrollLeft
                || 0;
    this.deltaY =  window.pageYOffset
                || document.documentElement.scrollTop
                || document.body.scrollTop
                || 0;
  },

  realOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.scrollTop  || 0;
      valueL += element.scrollLeft || 0;
      element = element.parentNode;
    } while (element);
    return [valueL, valueT];
  },

  cumulativeOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
    } while (element);
    return [valueL, valueT];
  },

  positionedOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
      if (element) {
        if(element.tagName=='BODY') break;
        var p = Element.getStyle(element, 'position');
        if (p == 'relative' || p == 'absolute') break;
      }
    } while (element);
    return [valueL, valueT];
  },

  offsetParent: function(element) {
    if (element.offsetParent) return element.offsetParent;
    if (element == document.body) return element;

    while ((element = element.parentNode) && element != document.body)
      if (Element.getStyle(element, 'position') != 'static')
        return element;

    return document.body;
  },

  // caches x/y coordinate pair to use with overlap
  within: function(element, x, y) {
    if (this.includeScrollOffsets)
      return this.withinIncludingScrolloffsets(element, x, y);
    this.xcomp = x;
    this.ycomp = y;
    this.offset = this.cumulativeOffset(element);

    return (y >= this.offset[1] &&
            y <  this.offset[1] + element.offsetHeight &&
            x >= this.offset[0] &&
            x <  this.offset[0] + element.offsetWidth);
  },

  withinIncludingScrolloffsets: function(element, x, y) {
    var offsetcache = this.realOffset(element);

    this.xcomp = x + offsetcache[0] - this.deltaX;
    this.ycomp = y + offsetcache[1] - this.deltaY;
    this.offset = this.cumulativeOffset(element);

    return (this.ycomp >= this.offset[1] &&
            this.ycomp <  this.offset[1] + element.offsetHeight &&
            this.xcomp >= this.offset[0] &&
            this.xcomp <  this.offset[0] + element.offsetWidth);
  },

  // within must be called directly before
  overlap: function(mode, element) {
    if (!mode) return 0;
    if (mode == 'vertical')
      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
        element.offsetHeight;
    if (mode == 'horizontal')
      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
        element.offsetWidth;
  },

  page: function(forElement) {
    var valueT = 0, valueL = 0;

    var element = forElement;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;

      // Safari fix
      if (element.offsetParent==document.body)
        if (Element.getStyle(element,'position')=='absolute') break;

    } while (element = element.offsetParent);

    element = forElement;
    do {
      if (!window.opera || element.tagName=='BODY') {
        valueT -= element.scrollTop  || 0;
        valueL -= element.scrollLeft || 0;
      }
    } while (element = element.parentNode);

    return [valueL, valueT];
  },

  clone: function(source, target) {
    var options = Object.extend({
      setLeft:    true,
      setTop:     true,
      setWidth:   true,
      setHeight:  true,
      offsetTop:  0,
      offsetLeft: 0
    }, arguments[2] || {})

    // find page position of source
    source = $(source);
    var p = Position.page(source);

    // find coordinate system to use
    target = $(target);
    var delta = [0, 0];
    var parent = null;
    // delta [0,0] will do fine with position: fixed elements,
    // position:absolute needs offsetParent deltas
    if (Element.getStyle(target,'position') == 'absolute') {
      parent = Position.offsetParent(target);
      delta = Position.page(parent);
    }

    // correct by body offsets (fixes Safari)
    if (parent == document.body) {
      delta[0] -= document.body.offsetLeft;
      delta[1] -= document.body.offsetTop;
    }

    // set position
    if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
    if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
    if(options.setWidth)  target.style.width = source.offsetWidth + 'px';
    if(options.setHeight) target.style.height = source.offsetHeight + 'px';
  },

  absolutize: function(element) {
    element = $(element);
    if (element.style.position == 'absolute') return;
    Position.prepare();

    var offsets = Position.positionedOffset(element);
    var top     = offsets[1];
    var left    = offsets[0];
    var width   = element.clientWidth;
    var height  = element.clientHeight;

    element._originalLeft   = left - parseFloat(element.style.left  || 0);
    element._originalTop    = top  - parseFloat(element.style.top || 0);
    element._originalWidth  = element.style.width;
    element._originalHeight = element.style.height;

    element.style.position = 'absolute';
    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.width  = width + 'px';
    element.style.height = height + 'px';
  },

  relativize: function(element) {
    element = $(element);
    if (element.style.position == 'relative') return;
    Position.prepare();

    element.style.position = 'relative';
    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);

    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.height = element._originalHeight;
    element.style.width  = element._originalWidth;
  }
}

// Safari returns margins on body which is incorrect if the child is absolutely
// positioned.  For performance reasons, redefine Position.cumulativeOffset for
// KHTML/WebKit only.
if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
  Position.cumulativeOffset = function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      if (element.offsetParent == document.body)
        if (Element.getStyle(element, 'position') == 'absolute') break;

      element = element.offsetParent;
    } while (element);

    return [valueL, valueT];
  }
}

Element.addMethods();/*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.6.5
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2007 Jelsoft Enterprises Ltd. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

// #############################################################################
// Initial setup

// ensure SESSIONURL exists
if (typeof(SESSIONURL) == "undefined")
{
	var SESSIONURL = "";
}

// ensure vbphrase exists
if (typeof(vbphrase) == "undefined")
{
	var vbphrase = new Array();
}

// Array of message editor objects
var vB_Editor = new Array();

// Ignore characters within [quote] tags in messages for length check
var ignorequotechars = false;

// Number of pagenav items dealt with so far
var pagenavcounter = 0;

// #############################################################################
// Browser detection and limitation workarounds

// DefinevB_AJAX_XML_Builder the browser we have instead of multiple calls throughout the file
var userAgent = navigator.userAgent.toLowerCase();
var is_opera  = ((userAgent.indexOf('opera') != -1) || (typeof(window.opera) != 'undefined'));
var is_saf    = ((userAgent.indexOf('applewebkit') != -1) || (navigator.vendor == 'Apple Computer, Inc.'));
var is_webtv  = (userAgent.indexOf('webtv') != -1);
var is_ie     = ((userAgent.indexOf('msie') != -1) && (!is_opera) && (!is_saf) && (!is_webtv));
var is_ie4    = ((is_ie) && (userAgent.indexOf('msie 4.') != -1));
var is_ie7    = ((is_ie) && (userAgent.indexOf('msie 7.') != -1));
var is_moz    = ((navigator.product == 'Gecko') && (!is_saf));
var is_kon    = (userAgent.indexOf('konqueror') != -1);
var is_ns     = ((userAgent.indexOf('compatible') == -1) && (userAgent.indexOf('mozilla') != -1) && (!is_opera) && (!is_webtv) && (!is_saf));
var is_ns4    = ((is_ns) && (parseInt(navigator.appVersion) == 4));
var is_mac    = (userAgent.indexOf('mac') != -1);

// Catch possible bugs with WebTV and other older browsers
var is_regexp = (window.RegExp) ? true : false;

// Is the visiting browser compatible with AJAX?
var AJAX_Compatible = false;

// Help out old versions of IE that don't understand element.style.cursor = 'pointer'
var pointer_cursor = (is_ie ? 'hand' : 'pointer');

/**
* Workaround for heinous IE bug - add special vBlength property to all strings
* This method is applied to ALL string objects automatically
*
* @return	integer
*/
String.prototype.vBlength = function()
{
	return (is_ie && this.indexOf('\n') != -1) ? this.replace(/\r?\n/g, '_').length : this.length;
}

if ('1234'.substr(-2, 2) == '12') // (which would be incorrect)
{
	String.prototype.substr_orig = String.prototype.substr;

	/**
	* Overrides IE's original String.prototype.substr to accept negative values
	*
	* @param	integer	Substring start position
	* @param	integer	Substring length
	*
	* @return	string
	*/
	String.prototype.substr = function(start, length)
	{
		return this.substr_orig( (start < 0 ? this.length + start : start), length);
	};
}

/**
* Pop function for browsers that don't have it built in
*
* @param	array	Array from which to pop
*
* @return	mixed	null on empty, value on success
*/
function array_pop(a)
{
	if (typeof a != 'object' || !a.length)
	{
		return null;
	}
	else
	{
		var response = a[a.length - 1];
		a.length--;
		return response;
	}
}

if (typeof Array.prototype.shift === 'undefined')
{
	Array.prototype.shift = function()
	{
		for(var i = 0, b = this[0], l = this.length-1; i < l; i++)
		{
			this[i] = this[i + 1];
		}
		this.length--;
		return b;
	};
}

/**
* Push function for browsers that don't have it built in
*
* @param	array	Array onto which to push
* @param	mixed	Value(s) to push onto - you may use multiple arguments here, eg: array_push(myArray, 1, 2, 3, 4, ...)
*
* @return	integer	Length of array
*/
function array_push(a, values)
{
	for (var i = 1; i < arguments.length; i++)
	{
		a[a.length] = arguments[i];
	}
	return a.length;
}

/**
* Function to emulate document.getElementById
*
* @param	string	Object ID
*
* @return	mixed	null if not found, object if found
*/
function fetch_object(idname)
{
	if (document.getElementById)
	{
		return document.getElementById(idname);
	}
	else if (document.all)
	{
		return document.all[idname];
	}
	else if (document.layers)
	{
		return document.layers[idname];
	}
	else
	{
		return null;
	}
}

/**
* Function to emulate document.getElementsByTagName
*
* @param	object	Parent object (eg: document)
* @param	string	Tag type (eg: 'td')
*
* @return	array
*/
function fetch_tags(parentobj, tag)
{
	if (parentobj == null)
	{
		return new Array();
	}
	else if (typeof parentobj.getElementsByTagName != 'undefined')
	{
		return parentobj.getElementsByTagName(tag);
	}
	else if (parentobj.all && parentobj.all.tags)
	{
		return parentobj.all.tags(tag);
	}
	else
	{
		return new Array();
	}
}

/**
* Function to count the number of tags in an object
*
* @param	object	Parent object (eg: document)
* @param	string	Tag type (eg: 'td')
*
* @return	integer
*/
function fetch_tag_count(parentobj, tag)
{
	return fetch_tags(parentobj, tag).length;
}



// #############################################################################
// Event handlers

/**
* Handles the different event models of different browsers and prevents event bubbling
*
* @param	event	Event object
*
* @return	event
*/
function do_an_e(eventobj)
{
	if (!eventobj || is_ie)
	{
		window.event.returnValue = false;
		window.event.cancelBubble = true;
		return window.event;
	}
	else
	{
		eventobj.stopPropagation();
		eventobj.preventDefault();
		return eventobj;
	}
}

/**
* Handles the different event models of different browsers and prevents event bubbling in a lesser way than do_an_e()
*
* @param	event	Event object
*
* @return	event
*/
function e_by_gum(eventobj)
{
	if (!eventobj || is_ie)
	{
		window.event.cancelBubble = true;
		return window.event;
	}
	else
	{
		if (eventobj.target.type == 'submit')
		{
			// naughty safari
			eventobj.target.form.submit();
		}
		eventobj.stopPropagation();
		return eventobj;
	}
}

// #############################################################################
// Message manipulation and validation

/**
* Checks that a message is valid for submission to PHP
*
* @param	string	Message text
* @param	mixed	Either subject text (if you want to make sure it exists) or 0 if you don't care
* @param	integer	Minimum acceptable character limit for the message
*
* @return	boolean
*/
function validatemessage(messagetext, subjecttext, minchars)
{
	if (is_kon || is_saf || is_webtv)
	{
		// ignore less-than-capable browsers
		return true;
	}
	else if (subjecttext.length < 1)
	{
		// subject not specified
		alert(vbphrase['must_enter_subject']);
		return false;
	}
	else
	{
		var stripped = PHP.trim(stripcode(messagetext, false, ignorequotechars));

		if (stripped.length < minchars)
		{
			// minimum message length not met
			alert(construct_phrase(vbphrase['message_too_short'], minchars));
			return false;
		}
		else if (typeof(document.forms.vbform) != 'undefined' && typeof(document.forms.vbform.imagestamp) != 'undefined')
		{	// This form has image verification enabled
			if (document.forms.vbform.imagestamp.value.length != 6)
			{
				alert(vbphrase['complete_image_verification']);
				document.forms.vbform.imagestamp.focus();
				return false;
			}
			else
			{
				return true;
			}
		}
		else
		{
			// everything seems ok
			return true;
		}
	}
}

/**
* Strips quotes and bbcode tags from text
*
* @param	string	Text to manipulate
* @param	boolean	If true, strip <x> otherwise strip [x]
* @param	boolean	If true, strip all [quote]...contents...[/quote]
*
* @return	string
*/
function stripcode(str, ishtml, stripquotes)
{
	if (!is_regexp)
	{
		return str;
	}

	if (stripquotes)
	{
		var start_time = new Date().getTime();

		while ((startindex = PHP.stripos(str, '[quote')) !== false)
		{
			if (new Date().getTime() - start_time > 2000)
			{
				// while loop has been running for over 2 seconds and has probably gone infinite
				break;
			}

			if ((stopindex = PHP.stripos(str, '[/quote]')) !== false)
			{
				fragment = str.substr(startindex, stopindex - startindex + 8);
				str = str.replace(fragment, '');
			}
			else
			{
				break;
			}
			str = PHP.trim(str);
		}
	}

	if (ishtml)
	{
		// exempt image tags -- they need to count as characters in the string
		// as the do as BB codes
		str = str.replace(/<img[^>]+src="([^"]+)"[^>]*>/gi, '$1');

		var html1 = new RegExp("<(\\w+)[^>]*>", 'gi');
		var html2 = new RegExp("<\\/\\w+>", 'gi');

		str = str.replace(html1, '');
		str = str.replace(html2, '');

		var html3 = new RegExp('(&nbsp;)', 'gi');
		str = str.replace(html3, ' ');
	}
	else
	{
		var bbcode1 = new RegExp("\\[(\\w+)[^\\]]*\\]", 'gi');
		var bbcode2 = new RegExp("\\[\\/(\\w+)\\]", 'gi');

		str = str.replace(bbcode1, '');
		str = str.replace(bbcode2, '');
	}

	return str;
}

// #############################################################################
// vB_PHP_Emulator class
// #############################################################################

/**
* PHP Function Emulator Class
*/
function vB_PHP_Emulator()
{
}

// =============================================================================
// vB_PHP_Emulator Methods

/**
* Find a string within a string (case insensitive)
*
* @param	string	Haystack
* @param	string	Needle
* @param	integer	Offset
*
* @return	mixed	Not found: false / Found: integer position
*/
vB_PHP_Emulator.prototype.stripos = function(haystack, needle, offset)
{
	if (typeof offset == 'undefined')
	{
		offset = 0;
	}

	index = haystack.toLowerCase().indexOf(needle.toLowerCase(), offset);

	return (index == -1 ? false : index);
}

/**
* Trims leading whitespace
*
* @param	string	String to trim
*
* @return	string
*/
vB_PHP_Emulator.prototype.ltrim = function(str)
{
	return str.replace(/^\s+/g, '');
}

/**
* Trims trailing whitespace
*
* @param	string	String to trim
*
* @return	string
*/
vB_PHP_Emulator.prototype.rtrim = function(str)
{
	return str.replace(/(\s+)$/g, '');
}

/**
* Trims leading and trailing whitespace
*
* @param	string	String to trim
*
* @return	string
*/
vB_PHP_Emulator.prototype.trim = function(str)
{
	return this.ltrim(this.rtrim(str));
}

/**
* Emulation of PHP's preg_quote()
*
* @param	string	String to process
*
* @return	string
*/
vB_PHP_Emulator.prototype.preg_quote = function(str)
{
	// replace + { } ( ) [ ] | / ? ^ $ \ . = ! < > : * with backslash+character
	return str.replace(/(\+|\{|\}|\(|\)|\[|\]|\||\/|\?|\^|\$|\\|\.|\=|\!|\<|\>|\:|\*)/g, "\\$1");
}

/**
* Emulates unhtmlspecialchars in vBulletin
*
* @param	string	String to process
*
* @return	string
*/
vB_PHP_Emulator.prototype.unhtmlspecialchars = function(str)
{
	f = new Array(/&lt;/g, /&gt;/g, /&quot;/g, /&amp;/g);
	r = new Array('<', '>', '"', '&');

	for (var i in f)
	{
if (typeof f[i] == 'function') continue;
		str = str.replace(f[i], r[i]);
	}

	return str;
}

/**
* Unescape CDATA from vB_AJAX_XML_Builder PHP class
*
* @param	string	Escaped CDATA
*
* @return	string
*/
vB_PHP_Emulator.prototype.unescape_cdata = function(str)
{
	var r1 = /<\=\!\=\[\=C\=D\=A\=T\=A\=\[/g;
	var r2 = /\]\=\]\=>/g;

	return str.replace(r1, '<![CDATA[').replace(r2, ']]>');
}

/**
* Emulates PHP's htmlspecialchars()
*
* @param	string	String to process
*
* @return	string
*/
vB_PHP_Emulator.prototype.htmlspecialchars = function(str)
{
	//var f = new Array(/&(?!#[0-9]+;)/g, /</g, />/g, /"/g);
	var f = new Array(
		(is_mac && is_ie ? new RegExp('&', 'g') : new RegExp('&(?!#[0-9]+;)', 'g')),
		new RegExp('<', 'g'),
		new RegExp('>', 'g'),
		new RegExp('"', 'g')
	);
	var r = new Array(
		'&amp;',
		'&lt;',
		'&gt;',
		'&quot;'
	);

	for (var i = 0; i < f.length; i++)
	{
		str = str.replace(f[i], r[i]);
	}

	return str;
}

/**
* Searches an array for a value
*
* @param	string	Needle
* @param	array	Haystack
* @param	boolean	Case insensitive
*
* @return	integer	Not found: -1 / Found: integer index
*/
vB_PHP_Emulator.prototype.in_array = function(ineedle, haystack, caseinsensitive)
{
	var needle = new String(ineedle);

	if (caseinsensitive)
	{
		needle = needle.toLowerCase();
		for (var i in haystack)
		{
if (typeof haystack[i] == 'function') continue;
			if (haystack[i].toLowerCase() == needle)
			{
				return i;
			}
		}
	}
	else
	{
		for (var i in haystack)
		{
if (typeof haystack[i] == 'function') continue;
			if (haystack[i] == needle)
			{
				return i;
			}
		}
	}
	return -1;
}

/**
* Emulates PHP's strpad()
*
* @param	string	Text to pad
* @param	integer	Length to pad
* @param	string	String with which to pad
*
* @return	string
*/
vB_PHP_Emulator.prototype.str_pad = function(text, length, padstring)
{
	text = new String(text);
	padstring = new String(padstring);

	if (text.length < length)
	{
		padtext = new String(padstring);

		while (padtext.length < (length - text.length))
		{
			padtext += padstring;
		}

		text = padtext.substr(0, (length - text.length)) + text;
	}

	return text;
}

/**
* A sort of emulation of PHP's urlencode - not 100% the same, but accomplishes the same thing
*
* @param	string	String to encode
*
* @return	string
*/
vB_PHP_Emulator.prototype.urlencode = function(text)
{
	text = escape(text.toString()).replace(/\+/g, "%2B");

	// this escapes 128 - 255, as JS uses the unicode code points for them.
	// This causes problems with submitting text via AJAX with the UTF-8 charset.
	var matches = text.match(/(%([0-9A-F]{2}))/gi);
	if (matches)
	{
		for (var matchid = 0; matchid < matches.length; matchid++)
		{
			var code = matches[matchid].substring(1,3);
			if (parseInt(code, 16) >= 128)
			{
				text = text.replace(matches[matchid], '%u00' + code);
			}
		}
	}

	// %25 gets translated to % by PHP, so if you have %25u1234,
	// we see it as %u1234 and it gets translated. So make it %u0025u1234,
	// which will print as %u1234!
	text = text.replace('%25', '%u0025');

	return text;
}

/**
* Works a bit like ucfirst, but with some extra options
*
* @param	string	String with which to work
* @param	string	Cut off string before first occurence of this string
*
* @return	string
*/
vB_PHP_Emulator.prototype.ucfirst = function(str, cutoff)
{
	if (typeof cutoff != 'undefined')
	{
		var cutpos = str.indexOf(cutoff);
		if (cutpos > 0)
		{
			str = str.substr(0, cutpos);
		}
	}

	str = str.split(' ');
	for (var i = 0; i < str.length; i++)
	{
		str[i] = str[i].substr(0, 1).toUpperCase() + str[i].substr(1);
	}
	return str.join(' ');
}

// initialize the PHP emulator
var PHP = new vB_PHP_Emulator();

// #############################################################################
// vB_AJAX_Handler
// #############################################################################

/**
* XML Sender Class
*
* @param	boolean	Should connections be asyncronous?
*/
function vB_AJAX_Handler(async)
{
	/**
	* Should connections be asynchronous?
	*
	* @var	boolean
	*/
	this.async = async ? true : false;
}

// =============================================================================
// vB_AJAX_Handler methods

/**
* Initializes the XML handler
*
* @return	boolean	True if handler created OK
*/
vB_AJAX_Handler.prototype.init = function()
{
	if (typeof vb_disable_ajax != 'undefined' && vb_disable_ajax == 2)
	{
		// disable all ajax features
		return false;
	}

	try
	{
		this.handler = new XMLHttpRequest();
		return (this.handler.setRequestHeader ? true : false);
	}
	catch(e)
	{
		try
		{
			this.handler = eval("new A" + "ctiv" + "eX" + "Ob" + "ject('Micr" + "osoft.XM" + "LHTTP');");
			return true;
		}
		catch(e)
		{
			return false;
		}
	}
}

/**
* Detects if the browser is fully compatible
*
* @return	boolean
*/
vB_AJAX_Handler.prototype.is_compatible = function()
{
	if (typeof vb_disable_ajax != 'undefined' && vb_disable_ajax == 2)
	{
		// disable all ajax features
		return false;
	}

	if (is_ie && !is_ie4) { return true; }
	else if (typeof XMLHttpRequest != 'undefined')
	{
		try { return XMLHttpRequest.prototype.setRequestHeader ? true : false; }
		catch(e)
		{
			try { var tester = new XMLHttpRequest(); return tester.setRequestHeader ? true : false; }
			catch(e) { return false; }
		}
	}
	else { return false; }
}

/**
* Checks if the system is ready
*
* @return	boolean	False if ready
*/
vB_AJAX_Handler.prototype.not_ready = function()
{
	return (this.handler.readyState && (this.handler.readyState < 4));
}

/**
* OnReadyStateChange event handler
*
* @param	function
*/
vB_AJAX_Handler.prototype.onreadystatechange = function(event)
{
	if (!this.handler)
	{
		if  (!this.init())
		{
			return false;
		}
	}
	if (typeof event == 'function')
	{
		this.handler.onreadystatechange = event;
	}
	else
	{
		alert('XML Sender OnReadyState event is not a function');
	}

	return false;
}

/**
* Sends data
*
* @param	string	Destination URL
* @param	string	Request Data
*
* @return	mixed	Return message
*/
vB_AJAX_Handler.prototype.send = function(desturl, datastream)
{
	if (!this.handler)
	{
		if (!this.init())
		{
			return false;
		}
	}
	if (!this.not_ready())
	{
		this.handler.open('POST', desturl, this.async);
		this.handler.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
		this.handler.send(datastream + '&s=' + fetch_sessionhash());

		if (!this.async && this.handler.readyState == 4 && this.handler.status == 200)
		{
			return true;
		}
	}
	return false;
}

/**
* Fetches the contents of an XML node
*
* @param	object	XML node
*
* @return	string	XML node contents
*/
vB_AJAX_Handler.prototype.fetch_data = function(xml_node)
{
	if (xml_node && xml_node.firstChild && xml_node.firstChild.nodeValue)
	{
		return PHP.unescape_cdata(xml_node.firstChild.nodeValue);
	}
	else
	{
		return '';
	}
}

// we can check this variable to see if browser is AJAX compatible
var AJAX_Compatible = vB_AJAX_Handler.prototype.is_compatible();

// #############################################################################
// vB_Hidden_Form
// #############################################################################

/**
* Form Generator Class
*
* Builds a form filled with hidden fields for invisible submit via POST
*
* @param	string	Script (my_target_script.php)
*/
function vB_Hidden_Form(script)
{
	this.action = script;
	this.variables = new Array();
}

// =============================================================================
// vB_Hidden_Form methods

/**
* Adds a hidden input field to the form object
*
* @param	string	Name attribute
* @param	string	Value attribute
*/
vB_Hidden_Form.prototype.add_variable = function(name, value)
{
	this.variables[this.variables.length] = new Array(name, value);
};

/**
* Fetches all form elements inside an HTML element and performs 'add_input()' on them
*
* @param	object	HTML element to search
*/
vB_Hidden_Form.prototype.add_variables_from_object = function(obj)
{
	var inputs = fetch_tags(obj, 'input');
	for (var i = 0; i < inputs.length; i++)
	{
		switch (inputs[i].type)
		{
			case 'checkbox':
			case 'radio':
				if (inputs[i].checked)
				{
					this.add_variable(inputs[i].name, inputs[i].value);
				}
				break;
			case 'text':
			case 'hidden':
			case 'password':
				this.add_variable(inputs[i].name, inputs[i].value);
				break;
			default:
				continue;
		}
	}

	var textareas = fetch_tags(obj, 'textarea');
	for (var i = 0; i < textareas.length; i++)
	{
		this.add_variable(textareas[i].name, textareas[i].value);
	}

	var selects = fetch_tags(obj, 'select');
	for (var i = 0; i < selects.length; i++)
	{
		if (selects[i].multiple)
		{
			for (var j = 0; j < selects[i].options.length; j++)
			{
				if (selects[i].options[j].selected)
				{
					this.add_variable(selects[i].name, selects[i].options[j].value);
				}
			}
		}
		else
		{
			this.add_variable(selects[i].name, selects[i].options[selects[i].selectedIndex].value);
		}
	}
};

/**
* Fetches a variable value
*
* @param	string	Variable name
*
* @return	mixed	Variable value
*/
vB_Hidden_Form.prototype.fetch_variable = function(varname)
{
	for (var i = 0; i < this.variables.length; i++)
	{
		if (this.variables[i][0] == varname)
		{
			return this.variables[i][1];
		}
	}

	return null;
};

/**
* Submits the hidden form object
*/
vB_Hidden_Form.prototype.submit_form = function()
{
	this.form = document.createElement('form');
	this.form.method = 'post';
	this.form.action = this.action;

	for (var i = 0; i < this.variables.length; i++)
	{
		var inputobj = document.createElement('input');

		inputobj.type  = 'hidden';
		inputobj.name  = this.variables[i][0];
		inputobj.value = this.variables[i][1];

		this.form.appendChild(inputobj);
	}

	document.body.appendChild(this.form).submit();
};

/**
* Builds a URI query string from the given variables
*/
vB_Hidden_Form.prototype.build_query_string = function()
{
	var query_string = '';

	for (var i = 0; i < this.variables.length; i++)
	{
		query_string += this.variables[i][0] + '=' + PHP.urlencode(this.variables[i][1]) + '&';
	}

	return query_string;
}

/**
* Legacy functions for backward compatability
*/
vB_Hidden_Form.prototype.add_input = vB_Hidden_Form.prototype.add_variable;
vB_Hidden_Form.prototype.add_inputs_from_object = vB_Hidden_Form.prototype.add_variables_from_object;

// #############################################################################
// Window openers and instant messenger wrappers

/**
* Opens a generic browser window
*
* @param	string	URL
* @param	integer	Width
* @param	integer	Height
* @param	string	Optional Window ID
*/
function openWindow(url, width, height, windowid)
{
	return window.open(
		url,
		(typeof windowid == 'undefined' ? 'vBPopup' : windowid),
		'statusbar=no,menubar=no,toolbar=no,scrollbars=yes,resizable=yes'
		+ (typeof width != 'undefined' ? (',width=' + width) : '') + (typeof height != 'undefined' ? (',height=' + height) : '')
	);
}

/**
* Opens control panel help window
*
* @param	string	Script name
* @param	string	Action type
* @param	string	Option value
*
* @return	window
*/
function js_open_help(scriptname, actiontype, optionval)
{
	return openWindow(
		'help.php?s=' + SESSIONHASH + '&do=answer&page=' + scriptname + '&pageaction=' + actiontype + '&option=' + optionval,
		600, 450, 'helpwindow'
	);
}

/**
* Opens a window to show a list of attachments in a thread (misc.php?do=showattachments)
*
* @param	integer	Thread ID
*
* @return	window
*/
function attachments(threadid)
{
	return openWindow(
		'misc.php?' + SESSIONURL + 'do=showattachments&t=' + threadid,
		480, 300
	);
}

/**
* Opens a window to show a list of posters in a thread (misc.php?do=whoposted)
*
* @param	integer	Thread ID
*
* @return	window
*/
function who(threadid)
{
	return openWindow(
		'misc.php?' + SESSIONURL + 'do=whoposted&t=' + threadid,
		230, 300
	);
}

/**
* Opens an IM Window
*
* @param	string	IM type
* @param	integer	User ID
* @param	integer	Width of window
* @param	integer	Height of window
*
* @return	window
*/
function imwindow(imtype, userid, width, height)
{
	return openWindow(
		'sendmessage.php?' + SESSIONURL + 'do=im&type=' + imtype + '&u=' + userid,
		width, height
	);
}

/**
* Sends an MSN message
*
* @param	string	Target MSN handle
*
* @return	boolean	false
*/
function SendMSNMessage(name)
{
	if (!is_ie)
	{
		alert(vbphrase['msn_functions_only_work_in_ie']);
		return false;
	}
	else
	{
		MsgrObj.InstantMessage(name);
		return false;
	}
}

/**
* Adds an MSN Contact (requires MSN)
*
* @param	string	MSN handle
*
* @return	boolean	false
*/
function AddMSNContact(name)
{
	if (!is_ie)
	{
		alert(vbphrase['msn_functions_only_work_in_ie']);
		return false;
	}
	else
	{
		MsgrObj.AddContact(0, name);
		return false;
	}
}

/**
* Detects Caps-Lock when a key is pressed
*
* @param	event
*
* @return	boolean	True if Caps-Lock is on
*/
function detect_caps_lock(e)
{
	e = (e ? e : window.event);

	var keycode = (e.which ? e.which : (e.keyCode ? e.keyCode : (e.charCode ? e.charCode : 0)));
	var shifted = (e.shiftKey || (e.modifiers && (e.modifiers & 4)));
	var ctrled = (e.ctrlKey || (e.modifiers && (e.modifiers & 2)));

	// if characters are uppercase without shift, or lowercase with shift, caps-lock is on.
	return (keycode >= 65 && keycode <= 90 && !shifted && !ctrled) || (keycode >= 97 && keycode <= 122 && shifted);
}

// #############################################################################
// Cookie handlers

/**
* Sets a cookie
*
* @param	string	Cookie name
* @param	string	Cookie value
* @param	date	Cookie expiry date
*/
function set_cookie(name, value, expires)
{
	document.cookie = name + '=' + escape(value) + '; path=/' + (typeof expires != 'undefined' ? '; expires=' + expires.toGMTString() : '');
}

/**
* Deletes a cookie
*
* @param	string	Cookie name
*/
function delete_cookie(name)
{
	document.cookie = name + '=' + '; expires=Thu, 01-Jan-70 00:00:01 GMT' +  '; path=/';
}

/**
* Fetches the value of a cookie
*
* @param	string	Cookie name
*
* @return	string
*/
function fetch_cookie(name)
{
	cookie_name = name + '=';
	cookie_length = document.cookie.length;
	cookie_begin = 0;
	while (cookie_begin < cookie_length)
	{
		value_begin = cookie_begin + cookie_name.length;
		if (document.cookie.substring(cookie_begin, value_begin) == cookie_name)
		{
			var value_end = document.cookie.indexOf (';', value_begin);
			if (value_end == -1)
			{
				value_end = cookie_length;
			}
			return unescape(document.cookie.substring(value_begin, value_end));
		}
		cookie_begin = document.cookie.indexOf(' ', cookie_begin) + 1;
		if (cookie_begin == 0)
		{
			break;
		}
	}
	return null;
}

// #############################################################################
// Form element managers (used for 'check all' type systems

/**
* Sets all checkboxes, radio buttons or selects in a given form to a given state, with exceptions
*
* @param	object	Form object
* @param	string	Target element type (one of 'radio', 'select-one', 'checkbox')
* @param	string	Selected option in case of 'radio'
* @param	array	Array of element names to be excluded
* @param	mixed	Value to give to found elements
*/
function js_toggle_all(formobj, formtype, option, exclude, setto)
{
	for (var i =0; i < formobj.elements.length; i++)
	{
		var elm = formobj.elements[i];
		if (elm.type == formtype && PHP.in_array(elm.name, exclude, false) == -1)
		{
			switch (formtype)
			{
				case 'radio':
					if (elm.value == option) // option == '' evaluates true when option = 0
					{
						elm.checked = setto;
					}
				break;
				case 'select-one':
					elm.selectedIndex = setto;
				break;
				default:
					elm.checked = setto;
				break;
			}
		}
	}
}

/**
* Sets all <select> elements to the selectedIndex specified by the 'selectall' element
*
* @param	object	Form object
*/
function js_select_all(formobj)
{
	exclude = new Array();
	exclude[0] = 'selectall';
	js_toggle_all(formobj, 'select-one', '', exclude, formobj.selectall.selectedIndex);
}

/**
* Sets all <input type="checkbox" /> elements to have the same checked status as 'allbox'
*
* @param	object	Form object
*/
function js_check_all(formobj)
{
	exclude = new Array();
	exclude[0] = 'keepattachments';
	exclude[1] = 'allbox';
	exclude[2] = 'removeall';
	js_toggle_all(formobj, 'checkbox', '', exclude, formobj.allbox.checked);
}

/**
* Sets all <input type="radio" /> groups to have a particular option checked
*
* @param	object	Form object
* @param	mixed	Selected option
*/
function js_check_all_option(formobj, option)
{
	exclude = new Array();
	exclude[0] = 'useusergroup';
	js_toggle_all(formobj, 'radio', option, exclude, true);
}

/**
* Alias to js_check_all
*/
function checkall(formobj) { js_check_all(formobj); }

/**
* Alias to js_check_all_option
*/
function checkall_option(formobj, option) { js_check_all_option(formobj, option); }

/**
* Resize function for CP textareas
*
* @param	integer	If positive, size up, otherwise size down
* @param	string	ID of the textarea
*
* @return	boolean	false
*/
function resize_textarea(to, id)
{
	if (to < 0)
	{
		var rows = -5;
		var cols = -10;
	}
	else
	{
		var rows = 5;
		var cols = 10;
	}

	var textarea = fetch_object(id);
	if (typeof textarea.orig_rows == 'undefined')
	{
		textarea.orig_rows = textarea.rows;
		textarea.orig_cols = textarea.cols;
	}

	var newrows = textarea.rows + rows;
	var newcols = textarea.cols + cols;

	if (newrows >= textarea.orig_rows && newcols >= textarea.orig_cols)
	{
		textarea.rows = newrows;
		textarea.cols = newcols;
	}

	return false;
}

// #############################################################################
// Collapsible element handlers

/**
* Toggles the collapse state of an object, and saves state to 'vbulletin_collapse' cookie
*
* @param	string	Unique ID for the collapse group
*
* @return	boolean	false
*/
function toggle_collapse(objid)
{
	if (!is_regexp)
	{
		return false;
	}

	obj = fetch_object('collapseobj_' + objid);
	img = fetch_object('collapseimg_' + objid);
	cel = fetch_object('collapsecel_' + objid);

	if (!obj)
	{
		// nothing to collapse!
		if (img)
		{
			// hide the clicky image if there is one
			img.style.display = 'none';
		}
		return false;
	}

	if (obj.style.display == 'none')
	{
		obj.style.display = '';
		save_collapsed(objid, false);
		if (img)
		{
			img_re = new RegExp("_collapsed\\.gif$");
			img.src = img.src.replace(img_re, '.gif');
		}
		if (cel)
		{
			cel_re = new RegExp("^(thead|tcat)(_collapsed)$");
			cel.className = cel.className.replace(cel_re, '$1');
		}
	}
	else
	{
		obj.style.display = 'none';
		save_collapsed(objid, true);
		if (img)
		{
			img_re = new RegExp("\\.gif$");
			img.src = img.src.replace(img_re, '_collapsed.gif');
		}
		if (cel)
		{
			cel_re = new RegExp("^(thead|tcat)$");
			cel.className = cel.className.replace(cel_re, '$1_collapsed');
		}
	}
	return false;
}

/**
* Updates vbulletin_collapse cookie with collapse preferences
*
* @param	string	Unique ID for the collapse group
* @param	boolean	Add a cookie
*/
function save_collapsed(objid, addcollapsed)
{
	var collapsed = fetch_cookie('vbulletin_collapse');
	var tmp = new Array();

	if (collapsed != null)
	{
		collapsed = collapsed.split('\n');

		for (var i in collapsed)
		{
if (typeof collapsed[i] == 'function') continue;
			if (collapsed[i] != objid && collapsed[i] != '')
			{
				tmp[tmp.length] = collapsed[i];
			}
		}
	}

	if (addcollapsed)
	{
		tmp[tmp.length] = objid;
	}

	expires = new Date();
	expires.setTime(expires.getTime() + (1000 * 86400 * 365));
	set_cookie('vbulletin_collapse', tmp.join('\n'), expires);
}

// #############################################################################
// Event Handlers for PageNav menus

/**
* Class to handle pagenav events
*/
function vBpagenav()
{
}

/**
* Handles clicks on pagenav menu control objects
*/
vBpagenav.prototype.controlobj_onclick = function(e)
{
	this._onclick(e);
	var inputs = fetch_tags(this.menu.menuobj, 'input');
	for (var i = 0; i < inputs.length; i++)
	{
		if (inputs[i].type == 'text')
		{
			inputs[i].focus();
			break;
		}
	}
};

/**
* Submits the pagenav form... sort of
*/
vBpagenav.prototype.form_gotopage = function(e)
{
	if ((pagenum = parseInt(fetch_object('pagenav_itxt').value, 10)) > 0)
	{
		window.location = this.addr + '&page=' + pagenum;
	}
	return false;
};

/**
* Handles clicks on the 'Go' button in pagenav popups
*/
vBpagenav.prototype.ibtn_onclick = function(e)
{
	return this.form.gotopage();
};

/**
* Handles keypresses in the text input of pagenav popups
*/
vBpagenav.prototype.itxt_onkeypress = function(e)
{
	return ((e ? e : window.event).keyCode == 13 ? this.form.gotopage() : true);
};

// #############################################################################
// DHTML Popup Menu Handling (complements vbulletin_menu.js)

/**
* Wrapper for vBmenu.register
*
* @param	string	Control ID
* @param	boolean	No image (true)
* @param	boolean	Does nothing any more
*/
function vbmenu_register(controlid, noimage, datefield)
{
	if (typeof(vBmenu) == "object")
	{
		return vBmenu.register(controlid, noimage);
	}
	else
	{
		return false;
	}
}

// #############################################################################
// Stuff that really doesn't fit anywhere else

/**
* Sets an element and all its children to be 'unselectable'
*
* @param	object	Object to be made unselectable
*/
function set_unselectable(obj)
{
	if (!is_ie4 && typeof obj.tagName != 'undefined')
	{
		if (obj.hasChildNodes())
		{
			for (var i = 0; i < obj.childNodes.length; i++)
			{
				set_unselectable(obj.childNodes[i]);
			}
		}
		obj.unselectable = 'on';
	}
}

/**
* Fetches the sessionhash from the SESSIONURL variable
*
* @return	string
*/
function fetch_sessionhash()
{
	return (SESSIONURL == '' ? '' : SESSIONURL.substr(2, 32));
}

/**
* Emulates the PHP version of vBulletin's construct_phrase() sprintf wrapper
*
* @param	string	String containing %1$s type replacement markers
* @param	string	First replacement
* @param	string	Nth replacement
*
* @return	string
*/
function construct_phrase()
{
	if (!arguments || arguments.length < 1 || !is_regexp)
	{
		return false;
	}

	var args = arguments;
	var str = args[0];
	var re;

	for (var i = 1; i < args.length; i++)
	{
		re = new RegExp("%" + i + "\\$s", 'gi');
		str = str.replace(re, args[i]);
	}
	return str;
}

/**
* Handles the quick style/language options in the footer
*
* @param	object	Select object
* @param	string	Type (style or language)
*/
function switch_id(selectobj, type)
{
	var id = selectobj.options[selectobj.selectedIndex].value;

	if (id == '')
	{
		return;
	}

	var url = new String(window.location);
	var fragment = new String('');

	// get rid of fragment
	url = url.split('#');

	// deal with the fragment first
	if (url[1])
	{
		fragment = '#' + url[1];
	}

	// deal with the main url
	url = url[0];

	// remove id=x& from main bit
	if (url.indexOf(type + 'id=') != -1 && is_regexp)
	{
		re = new RegExp(type + "id=\\d+&?");
		url = url.replace(re, '');
	}

	// add the ? to the url if needed
	if (url.indexOf('?') == -1)
	{
		url += '?';
	}
	else
	{
		// make sure that we have a valid character to join our id bit
		lastchar = url.substr(url.length - 1);
		if (lastchar != '&' && lastchar != '?')
		{
			url += '&';
		}
	}

	window.location = url + type + 'id=' + id + fragment;
}

/**
* Takes the 'alt' attribute for an image and attaches it to the 'title' attribute
*
* @param	object	Image object
*/
function img_alt_2_title(img)
{
	if (!img.title && img.alt != '')
	{
		img.title = img.alt;
	}
}

// #############################################################################
// Initialize a PostBit

/**
* This function runs all the necessary Javascript code on a PostBit
* after it has been loaded via AJAX. Don't use this method before a
* complete page load or you'll have problems.
*
* @param	object	Object containing postbits
*/
function PostBit_Init(obj, postid)
{
	if (typeof vBmenu != 'undefined')
	{
		// init profile menu(s)
		var divs = fetch_tags(obj, 'div');
		for (var i = 0; i < divs.length; i++)
		{
			if (divs[i].id && divs[i].id.substr(0, 9) == 'postmenu_')
			{
				vBmenu.register(divs[i].id, true);
			}
		}
	}

	if (typeof vB_QuickEditor != 'undefined')
	{
		// init quick edit controls
		vB_AJAX_QuickEdit_Init(obj);
	}

	if (typeof vB_QuickReply != 'undefined')
	{
		// init quick reply button
		qr_init_buttons(obj);
	}

	if (typeof mq_init != 'undefined')
	{
		// init quick reply button
		mq_init(obj);
	}

	if (typeof vBrep != 'undefined')
	{
		if (typeof postid != 'undefined' && typeof postid != 'null')
		{
			vbrep_register(postid);
		}
	}

	if (typeof inlineMod != 'undefined')
	{
		im_init(obj);
	}
}

// #############################################################################
// Main vBulletin Javascript Initialization

/**
* This function runs (almost) at the end of script loading on most vBulletin pages
*
* It sets up things like image alt->title tags, turns on the popup menu system etc.
*
* @return	boolean
*/
function vBulletin_init()
{
	// don't bother doing any exciting stuff for WebTV
	if (is_webtv)
	{
		return false;
	}

	// set 'title' tags for image elements
	var imgs = fetch_tags(document, 'img');
	for (var i = 0; i < imgs.length; i++)
	{
		img_alt_2_title(imgs[i]);
	}

	// finalize popup menus
	if (typeof vBmenu == 'object')
	{
		// close all menus on document click
		if (window.attachEvent && !is_saf)
		{
			document.attachEvent('onclick', vbmenu_hide);
			window.attachEvent('onresize', vbmenu_hide);
		}
		else if (document.addEventListener && !is_saf)
		{
			document.addEventListener('click', vbmenu_hide, false);
			window.addEventListener('resize', vbmenu_hide, false);
		}
		else
		{
			window.onclick = vbmenu_hide;
			window.onresize = vbmenu_hide;
		}

		// add popups to pagenav elements
		var pagenavs = fetch_tags(document, 'td');
		for (var n = 0; n < pagenavs.length; n++)
		{
			if (pagenavs[n].hasChildNodes() && pagenavs[n].firstChild.name && pagenavs[n].firstChild.name.indexOf('PageNav') != -1)
			{
				var addr = pagenavs[n].title;
				pagenavs[n].title = '';
				pagenavs[n].innerHTML = '';
				pagenavs[n].id = 'pagenav.' + n;
				var pn = vBmenu.register(pagenavs[n].id);
				if (is_saf)
				{
					pn.controlobj._onclick = pn.controlobj.onclick;
					pn.controlobj.onclick = vBpagenav.prototype.controlobj_onclick;
				}
			}
		}

		// process the pagenavs popup form
		if (typeof addr != 'undefined')
		{
			fetch_object('pagenav_form').addr = addr;
			fetch_object('pagenav_form').gotopage = vBpagenav.prototype.form_gotopage;
			fetch_object('pagenav_ibtn').onclick = vBpagenav.prototype.ibtn_onclick;
			fetch_object('pagenav_itxt').onkeypress = vBpagenav.prototype.itxt_onkeypress;
		}

		// activate the menu system
		vBmenu.activate(true);
	}

	return true;
}

/*======================================================================*\
|| ####################################################################
|| # Downloaded: 17:39, Fri Mar 2nd 2007
|| # CVS: $RCSfile$ - $Revision: 16478 $
|| ####################################################################
\*======================================================================*/
/*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.6.5
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2007 Jelsoft Enterprises Ltd. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

/**
* vBulletin popup menu example usage:
*
* To create a new popup menu:
* 	<element id="x">Click me <script type="text/javascript"> vBmenu.register('x'); </script></element>
*
* To create a dynamic popup menu with a title and two options:
* 	<element id="x">Click me</element>
* 	<script type="text/javascript">
* 	menu = new vB_Menu_Builder('x');
* 	menu.set_title('My Menu');
* 	menu.add_option('Option 1', 'script.php?opt=1');
* 	menu.add_option('Option 2', 'script.php?opt=2');
* 	menu.build();
* 	</script>
*/

// #############################################################################
// vB_Popup_Handler
// #############################################################################

/**
* vBulletin popup menu registry
*/
function vB_Popup_Handler()
{
	/**
	* Options:
	*
	* @var	integer	Number of steps to use in sliding menus open
	* @var	boolean	Use opacity face in menu open?
	*/
	this.open_steps = 10;
	this.open_fade = false;

	this.active = false;

	this.menus = new Array();
	this.activemenu = null;

	this.hidden_selects = new Array();
};

// =============================================================================
// vB_Popup_Handler methods

/**
* Activate / Deactivate the menu system
*
* @param	boolean	Active state for menus
*/
vB_Popup_Handler.prototype.activate = function(active)
{
	this.active = active;
};

/**
* Register a control object as a menu control
*
* @param	string	ID of the control object
* @param	boolean	Do not add an image (true)
*
* @return	vB_Popup_Menu
*/
vB_Popup_Handler.prototype.register = function(controlkey, noimage)
{
	this.menus[controlkey] = new vB_Popup_Menu(controlkey, noimage);

	return this.menus[controlkey];
};

/**
* Hide active menu
*/
vB_Popup_Handler.prototype.hide = function()
{
	if (this.activemenu != null)
	{
		//this.activemenu.hide();
		this.menus[this.activemenu].hide();
	}
};


// #############################################################################
// initialize menu registry

var vBmenu = new vB_Popup_Handler();

/**
* Function to allow anything to hide all menus
*
* @param	event	Event object
*
* @return	mixed
*/
function vbmenu_hide(e)
{
	if (e && e.button && e.button != 1 && e.type == 'click')
	{
		return true;
	}
	else
	{
		vBmenu.hide();
	}
};

// #############################################################################
// vB_Popup_Menu
// #############################################################################

/**
* vBulletin popup menu class constructor
*
* Manages a single menu and control object
* Initializes control object
*
* @param	string	ID of the control object
*/
function vB_Popup_Menu(controlkey, noimage)
{
	this.controlkey = controlkey;
	this.menuname = this.controlkey.split('.')[0] + '_menu';

	this.init_control(noimage);

	if (fetch_object(this.menuname))
	{
		this.init_menu();
	}

	this.slide_open = (is_opera ? false : true);
	this.open_steps = vBmenu.open_steps;
};

// =============================================================================
// vB_Popup_Menu methods

/**
* Initialize the control object
*/
vB_Popup_Menu.prototype.init_control = function(noimage)
{
	this.controlobj = fetch_object(this.controlkey);
	this.controlobj.state = false;

	if (this.controlobj.firstChild && (this.controlobj.firstChild.tagName == 'TEXTAREA' || this.controlobj.firstChild.tagName == 'INPUT'))
	{
		// do nothing
	}
	else
	{
		if (!noimage && !(is_mac && is_ie))
		{
			var space = document.createTextNode(' ');
			this.controlobj.appendChild(space);

			var img = document.createElement('img');
			img.src = IMGDIR_MISC + '/menu_open.gif';
			img.border = 0;
			img.title = '';
			img.alt = '';
			this.controlobj.appendChild(img);
		}

		this.controlobj.unselectable = true;
		if (!noimage)
		{
			this.controlobj.style.cursor = pointer_cursor;
		}
		this.controlobj.onclick = vB_Popup_Events.prototype.controlobj_onclick;
		this.controlobj.onmouseover = vB_Popup_Events.prototype.controlobj_onmouseover;
	}
};

/**
* Init the popup menu object
*/
vB_Popup_Menu.prototype.init_menu = function()
{
	this.menuobj = fetch_object(this.menuname);

	if (this.menuobj && !this.menuobj.initialized)
	{
		this.menuobj.initialized = true;
		this.menuobj.onclick = e_by_gum;
		this.menuobj.style.position = 'absolute';
		this.menuobj.style.zIndex = 50;

		// init popup filters (ie only)
		if (is_ie && !is_mac)
		{
			this.menuobj.style.filter += "progid:DXImageTransform.Microsoft.alpha(enabled=1,opacity=100)";
			this.menuobj.style.filter += "progid:DXImageTransform.Microsoft.shadow(direction=135,color=#8E8E8E,strength=3)";
		}

		this.init_menu_contents();
	}
};

/**
* Init the popup menu contents
*/
vB_Popup_Menu.prototype.init_menu_contents = function()
{
	var tds = fetch_tags(this.menuobj, 'td');
	for (var i = 0; i < tds.length; i++)
	{
		if (tds[i].className == 'vbmenu_option')
		{
			if (tds[i].title && tds[i].title == 'nohilite')
			{
				// not an active cell
				tds[i].title = '';
			}
			else
			{
				// create a reference back to the menu class
				tds[i].controlkey = this.controlkey;

				// handle mouseover / mouseout highlighting events
				tds[i].onmouseover = vB_Popup_Events.prototype.menuoption_onmouseover;
				tds[i].onmouseout = vB_Popup_Events.prototype.menuoption_onmouseout;

				var links = fetch_tags(tds[i], 'a');
				if (links.length == 1)
				{
					/* Ok we have a link, we should use this if
					1. There is no onclick event in the link
					2. There is no onclick event on the cell
					3. The onclick event for the cell should equal the link if the above are true

					If we find a browser thats gets confused we may need to set remove_link to true for it.
					*/

					tds[i].className = tds[i].className + ' vbmenu_option_alink';
					tds[i].islink = true;

					var linkobj = links[0];
					var remove_link = false;

					tds[i].target = linkobj.getAttribute('target');

					if (typeof linkobj.onclick == 'function')
					{
						tds[i].ofunc = linkobj.onclick;
						tds[i].onclick = vB_Popup_Events.prototype.menuoption_onclick_function;
						remove_link = true;
					}
					else if (typeof tds[i].onclick == 'function')
					{
						tds[i].ofunc = tds[i].onclick;
						tds[i].onclick = vB_Popup_Events.prototype.menuoption_onclick_function;
						remove_link = true;
					}
					else
					{
						tds[i].href = linkobj.href;
						tds[i].onclick = vB_Popup_Events.prototype.menuoption_onclick_link;
					}

					if (remove_link)
					{
						var myspan = document.createElement('span');
						myspan.innerHTML = linkobj.innerHTML;
						tds[i].insertBefore(myspan, linkobj);
						tds[i].removeChild(linkobj);
					}
				}
				else if (typeof tds[i].onclick == 'function')
				{
					tds[i].ofunc = tds[i].onclick;
					tds[i].onclick = vB_Popup_Events.prototype.menuoption_onclick_function;
				}
			}
		}
	}
};

/**
* Show the menu
*
* @param	object	The control object calling the menu
* @param	boolean	Use slide (false) or open instantly? (true)
*/
vB_Popup_Menu.prototype.show = function(obj, instant)
{
	if (!vBmenu.active)
	{
		return false;
	}
	else if (!this.menuobj)
	{
		this.init_menu();
	}

	if (!this.menuobj)
	{
		return false;
	}

	if (vBmenu.activemenu != null)
	{
		vBmenu.menus[vBmenu.activemenu].hide();
	}

	vBmenu.activemenu = this.controlkey;

	this.menuobj.style.display = '';
	if (this.slide_open)
	{
		this.menuobj.style.clip = 'rect(auto, 0px, 0px, auto)';
	}
	this.pos = this.fetch_offset(obj);
	this.leftpx = this.pos['left'];
	this.toppx = this.pos['top'] + obj.offsetHeight;

	if ((this.leftpx + this.menuobj.offsetWidth) >= document.body.clientWidth && (this.leftpx + obj.offsetWidth - this.menuobj.offsetWidth) > 0)
	{
		this.leftpx = this.leftpx + obj.offsetWidth - this.menuobj.offsetWidth;
		this.direction = 'right';
	}
	else
	{
		this.direction = 'left'
	}

	this.menuobj.style.left = this.leftpx + 'px';
	this.menuobj.style.top  = this.toppx + 'px';

	if (!instant && this.slide_open)
	{
		this.intervalX = Math.ceil(this.menuobj.offsetWidth / this.open_steps);
		this.intervalY = Math.ceil(this.menuobj.offsetHeight / this.open_steps);
		this.slide((this.direction == 'left' ? 0 : this.menuobj.offsetWidth), 0, 0);
	}
	else if (this.menuobj.style.clip && this.slide_open)
	{
		this.menuobj.style.clip = 'rect(auto, auto, auto, auto)';
	}

	// deal with IE putting <select> elements on top of everything
	this.handle_overlaps(true);

	if (this.controlobj.editorid)
	{
		this.controlobj.state = true;
		//this.controlobj.editor.menu_context(this.controlobj, 'mousedown');
		vB_Editor[this.controlobj.editorid].menu_context(this.controlobj, 'mousedown');
	}
};

/**
* Hide the menu
*/
vB_Popup_Menu.prototype.hide = function(e)
{

	if (e && e.button && e.button != 1)
	{
		// get around some context menu issues etc.
		return true;
	}

	this.stop_slide();

	this.menuobj.style.display = 'none';

	this.handle_overlaps(false);

	if (this.controlobj.editorid)
	{
		this.controlobj.state = false;
		//this.controlobj.editor.menu_context(this.controlobj, 'mouseout');
		vB_Editor[this.controlobj.editorid].menu_context(this.controlobj, 'mouseout');
	}

	vBmenu.activemenu = null;
};

/**
* Hover behaviour for control object
*/
vB_Popup_Menu.prototype.hover = function(obj)
{
	if (vBmenu.activemenu != null)
	{
		if (vBmenu.menus[vBmenu.activemenu].controlkey != this.id)
		{
			this.show(obj, true);
		}
	}
};

/**
* Slides menu open
*
* @param	integer	Clip X
* @param	integer	Clip Y
* @param	integer	Opacity (0-100)
*/
vB_Popup_Menu.prototype.slide = function(clipX, clipY, opacity)
{
	if (this.direction == 'left' && (clipX < this.menuobj.offsetWidth || clipY < this.menuobj.offsetHeight))
	{
		if (vBmenu.open_fade && is_ie)
		{
			opacity += 10;
			this.menuobj.filters.item('DXImageTransform.Microsoft.alpha').opacity = opacity;
		}

		clipX += this.intervalX;
		clipY += this.intervalY;

		this.menuobj.style.clip = "rect(auto, " + clipX + "px, " + clipY + "px, auto)";
		this.slidetimer = setTimeout("vBmenu.menus[vBmenu.activemenu].slide(" + clipX + ", " + clipY + ", " + opacity + ");", 0);
	}
	else if (this.direction == 'right' && (clipX > 0 || clipY < this.menuobj.offsetHeight))
	{
		if (vBmenu.open_fade && is_ie)
		{
			opacity += 10;
			menuobj.filters.item('DXImageTransform.Microsoft.alpha').opacity = opacity;
		}

		clipX -= this.intervalX;
		clipY += this.intervalY;

		this.menuobj.style.clip = "rect(auto, " + this.menuobj.offsetWidth + "px, " + clipY + "px, " + clipX + "px)";
		this.slidetimer = setTimeout("vBmenu.menus[vBmenu.activemenu].slide(" + clipX + ", " + clipY + ", " + opacity + ");", 0);
	}
	else
	{
		this.stop_slide();
	}
};

/**
* Abort menu slider
*/
vB_Popup_Menu.prototype.stop_slide = function()
{
	clearTimeout(this.slidetimer);

	this.menuobj.style.clip = 'rect(auto, auto, auto, auto)';

	if (vBmenu.open_fade && is_ie)
	{
		this.menuobj.filters.item('DXImageTransform.Microsoft.alpha').opacity = 100;
	}
};

/**
* Fetch offset of an object
*
* @param	object	The object to be measured
*
* @return	array	The measured offsets left/top
*/
vB_Popup_Menu.prototype.fetch_offset = function(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 };
};

/**
* Detect an overlap of an object and a menu
*
* @param	object	Object to be tested for overlap
* @param	array	Array of dimensions for menu object
*
* @return	boolean	True if overlap
*/
vB_Popup_Menu.prototype.overlaps = function(obj, m)
{
	var s = new Array();
	var pos = this.fetch_offset(obj);
	s['L'] = pos['left'];
	s['T'] = pos['top'];
	s['R'] = s['L'] + obj.offsetWidth;
	s['B'] = s['T'] + obj.offsetHeight;


	if (s['L'] > m['R'] || s['R'] < m['L'] || s['T'] > m['B'] || s['B'] < m['T'])
	{
		return false;
	}
	return true;
};

/**
* Handle IE overlapping <select> elements
*
* @param	boolean	Hide (true) or show (false) overlapping <select> elements
*/
vB_Popup_Menu.prototype.handle_overlaps = function(dohide)
{
	if (is_ie && !is_ie7)
	{
		var selects = fetch_tags(document, 'select');

		if (dohide)
		{
			var menuarea = new Array(); menuarea = {
				'L' : this.leftpx,
				'R' : this.leftpx + this.menuobj.offsetWidth,
				'T' : this.toppx,
				'B' : this.toppx + this.menuobj.offsetHeight
			};

			for (var i = 0; i < selects.length; i++)
			{
				if (this.overlaps(selects[i], menuarea))
				{
					var hide = true;
					var s = selects[i];
					while (s = s.parentNode)
					{
						if (s.className == 'vbmenu_popup')
						{
							hide = false;
							break;
						}
					}

					if (hide)
					{
						selects[i].style.visibility = 'hidden';
						array_push(vBmenu.hidden_selects, i);
					}
				}
			}
		}
		else
		{
			while (true)
			{
				var i = array_pop(vBmenu.hidden_selects);
				if (typeof i == 'undefined' || i == null)
				{
					break;
				}
				else
				{
					selects[i].style.visibility = 'visible';
				}
			}
		}
	}
};

// #############################################################################
// Menu event handler functions

/**
* Class containing menu popup event handlers
*/
function vB_Popup_Events()
{
};

/**
* Handles control object click events
*/
vB_Popup_Events.prototype.controlobj_onclick = function(e)
{
	if (typeof do_an_e == 'function')
	{
		do_an_e(e);
		if (vBmenu.activemenu == null || vBmenu.menus[vBmenu.activemenu].controlkey != this.id)
		{
			vBmenu.menus[this.id].show(this);
		}
		else
		{
			vBmenu.menus[this.id].hide();
		}
	}
};

/**
* Handles control object mouseover events
*/
vB_Popup_Events.prototype.controlobj_onmouseover = function(e)
{
	if (typeof do_an_e == 'function')
	{
		do_an_e(e);
		vBmenu.menus[this.id].hover(this);
	}
};

/**
* Handles menu option click events for options with onclick events
*/
vB_Popup_Events.prototype.menuoption_onclick_function = function(e)
{
	this.ofunc(e);
	vBmenu.menus[this.controlkey].hide();
};

/**
* Handles menu option click events for options containing links
*/
vB_Popup_Events.prototype.menuoption_onclick_link = function(e)
{
	e = e ? e : window.event;

	if (e.shiftKey || (this.target != null && this.target != '' && this.target.toLowerCase() != '_self'))
	{
		if (this.target != null && this.target.charAt(0) != '_')
		{
			window.open(this.href, this.target);
		}
		else
		{
			window.open(this.href);
		}
	}
	else
	{
		window.location = this.href;
	}

	// Safari has "issues" with resetting what was clicked on, super minor and I dont care
	e.cancelBubble = true;
	if (e.stopPropagation) e.stopPropagation();
	if (e.preventDefault) e.preventDefault();

	vBmenu.menus[this.controlkey].hide();
	return false;
};

/**
* Handles menu option mouseover events
*/
vB_Popup_Events.prototype.menuoption_onmouseover = function(e)
{
	this.className = 'vbmenu_hilite' + (this.islink ? ' vbmenu_hilite_alink' : '');
	this.style.cursor = pointer_cursor;
};

/**
* Handles menu option mouseout events
*/
vB_Popup_Events.prototype.menuoption_onmouseout = function(e)
{
	this.className = 'vbmenu_option' + (this.islink ? ' vbmenu_option_alink' : '');
	this.style.cursor = 'default';
};

/*======================================================================*\
|| ####################################################################
|| # Downloaded: 17:39, Fri Mar 2nd 2007
|| # CVS: $RCSfile$ - $Revision: 16478 $
|| ####################################################################
\*======================================================================*//*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.6.5
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2007 Jelsoft Enterprises Ltd. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

// #############################################################################
// vB_Inline_Mod
// #############################################################################

/**
* Inline Moderation Class
*
* @param	string	Name of the instance of this class
* @param	string	Type of system (thread/post)
* @param	string	ID of the form containing all checkboxes
* @param	string	Phrase for use on Go button
* @param	string	Name of cookie
*/
function vB_Inline_Mod(varname, type, formobjid, go_phrase, cookieprefix)
{
	/**
	* Variables created from arguments
	*
	* @var	string
	* @var	string
	* @var	object
	* @var	string
	*/
	this.varname = varname;
	this.type = (type.toLowerCase() == 'thread' ? 'thread' : 'post');
	this.formobj = fetch_object(formobjid);
	this.go_phrase = go_phrase;
	if (typeof cookieprefix != 'undefined')
	{
		this.cookieprefix = cookieprefix;
	}
	else
	{
		this.cookieprefix = 'vbulletin_inline';
	}
	/**
	* Other variables
	*
	* @var	string	Prefix for all checkbox IDs
	* @var	integer	Number of items checked on this page
	* @var	array	Array of IDs fetched from the cookie
	* @var	array	Array of IDs ready to be saved into the cookie
	*/
	this.list = (this.type == 'thread' ? 'tlist_' : 'plist_');
	this.cookie_ids = null;
	this.cookie_array = new Array();

	// =============================================================================
	// vB_Inline_Mod methods

	/**
	* Initialization action to run on page load
	*/
	this.init = function(elements)
	{
		// attach clickfunc to all checkboxes
		for (i = 0; i < elements.length; i++)
		{
			if (this.is_in_list(elements[i]))
			{
				elements[i].inlineModID = this.varname;
				elements[i].onclick = inlinemod_checkbox_onclick;
			}
		}

		this.cookie_array = new Array();
		if (this.fetch_ids())
		{
			for (i in this.cookie_ids)
			{
                         if(typeof this.cookie_ids[i]=='function') continue;
 
				if (this.cookie_ids[i] != '')
				{
					if (checkbox = fetch_object(this.list + this.cookie_ids[i]))
					{
						checkbox.checked = true;

						if (this.type == 'thread')
						{
							this.highlight_thread(checkbox);
						}
						else
						{
							this.highlight_post(checkbox);
						}
					}
					this.cookie_array[this.cookie_array.length] = this.cookie_ids[i];
				}
			}
		}

		this.set_output_counters();
	}

	/**
	* Returns an array of IDs from the inlinemod cookie
	*
	* @return	boolean	True if array created
	*/
	this.fetch_ids = function()
	{
		this.cookie_ids = fetch_cookie(this.cookieprefix + this.type);

		if (this.cookie_ids != null && this.cookie_ids != '')
		{
			this.cookie_ids = this.cookie_ids.split('-');
			if (this.cookie_ids.length > 0)
			{
				return true;
			}
		}

		return false;
	}

	/**
	* Toggles the selected state of an inline moderation item, updates the cookie
	*
	* @param	string	ID of the checkbox
	*
	* @return	boolean
	*/
	this.toggle = function(checkbox)
	{
		if (this.type == 'thread')
		{
			this.highlight_thread(checkbox);
		}
		else
		{
			this.highlight_post(checkbox);
		}

		this.save(checkbox.id.substr(6), checkbox.checked);
	}

	/**
	* Saves the inline moderation cookie
	*
	* @param	string	Item ID
	* @param	boolean	Add id to array?
	*
	* @return	boolean
	*/
	this.save = function(checkboxid, checked)
	{
		this.cookie_array = new Array();

		if (this.fetch_ids())
		{
			for (i in this.cookie_ids)
			{
                         if(typeof this.cookie_ids[i]=='function') continue;
				if (this.cookie_ids[i] != checkboxid && this.cookie_ids[i] != '')
				{
					this.cookie_array[this.cookie_array.length] = this.cookie_ids[i];
				}
			}
		}

		if (checked)
		{
			this.cookie_array[this.cookie_array.length] = checkboxid;
		}

		this.set_output_counters();

		this.set_cookie();

		return true;
	}

	/**
	* Saves the inline moderation cookie
	*/
	this.set_cookie = function()
	{
		expires = new Date();
		expires.setTime(expires.getTime() + 3600000);
		set_cookie(this.cookieprefix + this.type, this.cookie_array.join('-'), expires);
	}

	/**
	* Check / Uncheck All Inline Moderation Checkboxes
	*/
	this.check_all = function(checked, itemtype, caller)
	{
		if (typeof checked == 'undefined')
		{
			checked = this.formobj.allbox.checked;
		}

		this.cookie_array = new Array();

		// Remove all items on this page from the cookie list
		if (this.fetch_ids())
		{
			for (i in this.cookie_ids)
			{
                         if(typeof this.cookie_ids[i]=='function') continue;
				if (!fetch_object(this.list + this.cookie_ids[i]))
				{
					// this item is not on this page so put back in the cookie
					this.cookie_array[this.cookie_array.length] = this.cookie_ids[i]
				}
			}
		}

		counter = 0;

		// check/uncheck all boxes
		for (var i = 0; i < this.formobj.elements.length; i++)
		{
			if (this.is_in_list(this.formobj.elements[i]))
			{
				elm = this.formobj.elements[i];

				if (typeof itemtype != 'undefined')
				{
					if (elm.value & itemtype)
					{
						elm.checked = checked;
					}
					else
					{
						elm.checked = !checked;
					}
				}
				else if (checked == 'invert')
				{
					elm.checked = !elm.checked;
				}
				else
				{
					elm.checked = checked;
				}

				if (this.type == 'thread')
				{
					this.highlight_thread(elm);
				}
				else
				{
					this.highlight_post(elm);
				}

				if (elm.checked)
				{
					// add item to cookie if we are 'checking' it
					this.cookie_array[this.cookie_array.length] = elm.id.substring(6);
				}
			}
		}

		this.set_output_counters();

		this.set_cookie();

		return true;
	}

	this.is_in_list = function(obj)
	{
		return (obj.type == 'checkbox' && obj.id.indexOf(this.list) == 0 && (obj.disabled == false || obj.disabled == 'undefined'));
	}

	/**
	* Sets the value of the inline go button and the menu feedback
	*/
	this.set_output_counters = function()
	{
		if (obj = fetch_object('inlinego'))
		{
			obj.value = construct_phrase(this.go_phrase, this.cookie_array.length);
		}
	}

	/**
	* Toggles an element's classname between original and 'inlinemod'
	*
	* @param	object	The element on which to work
	* @param	object	The checkbox corresponding to the cell element
	*/
	this.toggle_highlight = function(cell, checkbox)
	{
		if (cell.className == 'alt1' || cell.className == 'alt2' || cell.className == 'inlinemod')
		{
			if (checkbox.checked)
			{
				if (!cell.oclassName)
				{
					cell.oclassName = cell.className;
				}
				cell.className = 'inlinemod';
			}
			else if (cell.oclassName)
			{
				cell.className = cell.oclassName;
			}
		}
	}

	/**
	* Highlights a thread <tr> in a thread listing
	*
	* @param	object	The checkbox for the thread
	*/
	this.highlight_thread = function(checkbox)
	{
		tobj = checkbox;
		while (tobj.tagName != 'TR')
		{
			if (tobj.parentNode.tagName == 'HTML')
			{
				break;
			}
			else
			{
				tobj = tobj.parentNode;
			}
		}
		if (tobj.tagName == 'TR')
		{
			tds = tobj.childNodes;
			for (var i = 0; i < tds.length; i++)
			{
				this.toggle_highlight(tds[i], checkbox);
			}
		}
	}

	/**
	* Highlights a post <table> on showthread
	*
	* @param	object	The checkbox for the post
	*/
	this.highlight_post = function(checkbox)
	{
		if (table = fetch_object('post' + checkbox.id.substr(6)))
		{
			tds = fetch_tags(table, 'td');
			for (var i = 0; i < tds.length; i++)
			{
				this.toggle_highlight(tds[i], checkbox);
			}
		}
	}

	// get everything running
	this.init(this.formobj.elements);
}

/**
* Function to handle checkboxes being clicked
*
* @param	event	Event object
*/
function inlinemod_checkbox_onclick(e)
{
	var inlineModObj = eval(this.inlineModID);
	inlineModObj.toggle(this);
};

/**
* Function to init a single post
*
* @param	event	Event object
*/
function im_init(obj)
{
	var inputs = fetch_tags(obj, 'input');
	inlineMod.init(inputs);
}

/*======================================================================*\
|| ####################################################################
|| # Downloaded: 17:39, Fri Mar 2nd 2007
|| # CVS: $RCSfile$ - $Revision: 15243 $
|| ####################################################################
\*======================================================================*/
/*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.6.5
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2007 Jelsoft Enterprises Ltd. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

/**
* Adds onclick event to the save search prefs buttons
*
* @param	string	The ID of the button that fires the search prefs
*/
function vB_AJAX_ImageReg_Init()
{
	if (AJAX_Compatible && (typeof vb_disable_ajax == 'undefined' || vb_disable_ajax < 2) && fetch_object('refresh_imagereg'))
	{
		fetch_object('refresh_imagereg').onclick = vB_AJAX_ImageReg.prototype.image_click;
		fetch_object('refresh_imagereg').style.cursor = pointer_cursor;
		fetch_object('refresh_imagereg').style.display = '';

		if (fetch_object('imagereg'))
		{
			fetch_object('imagereg').style.cursor = pointer_cursor;
			fetch_object('imagereg').onclick = vB_AJAX_ImageReg.prototype.image_click;
		}
	}
};

/**
* Class to handle saveing search prefs
*
* @param	object	The form object containing the search options
*/
function vB_AJAX_ImageReg()
{
	// AJAX handler
	this.xml_sender = null;

	// Imagehach
	this.imagehash = '';

	// Closure
	var me = this;

	/**
	* OnReadyStateChange callback. Uses a closure to keep state.
	* Remember to use me instead of this inside this function!
	*/
	this.handle_ajax_response = function()
	{
		if (me.xml_sender.handler.readyState == 4 && me.xml_sender.handler.status == 200)
		{
			fetch_object('progress_imagereg').style.display = 'none';
			if (me.xml_sender.handler.responseXML)
			{
				var imagehash = me.xml_sender.fetch_data(fetch_tags(me.xml_sender.handler.responseXML, 'imagehash')[0]);
				if (imagehash)
				{
					fetch_object('imagehash').value = imagehash;
					fetch_object('imagereg').src = 'image.php?' + SESSIONURL + 'type=regcheck&imagehash=' + imagehash;
				}
			}

			if (is_ie)
			{
				me.xml_sender.handler.abort();
			}
		}
	}
};

/**
* Submits the form via Ajax
*/
vB_AJAX_ImageReg.prototype.fetch_image = function()
{
	fetch_object('progress_imagereg').style.display = '';
	this.xml_sender = new vB_AJAX_Handler(true);
	this.xml_sender.onreadystatechange(this.handle_ajax_response);
	this.xml_sender.send('ajax.php?do=imagereg&imagehash=' + this.imagehash, 'do=imagereg&imagehash=' + this.imagehash);
};

/**
* Handles the form 'submit' action
*/
vB_AJAX_ImageReg.prototype.image_click = function()
{
	var AJAX_ImageReg = new vB_AJAX_ImageReg();
	AJAX_ImageReg.imagehash = fetch_object('imagehash').value;
	AJAX_ImageReg.fetch_image();
	return false;
};

/*======================================================================*\
|| ####################################################################
|| # Downloaded: 17:39, Fri Mar 2nd 2007
|| # CVS: $RCSfile$ - $Revision: 15906 $
|| ####################################################################
\*======================================================================*//*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.6.5
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2007 Jelsoft Enterprises Ltd. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

// #############################################################################
// vB_AJAX_NameSuggest
// #############################################################################

/**
* Class to read input and suggest usernames from the typed fragment
*
* @param	string	Name of variable instantiating this class
* @param	string	ID of the text input element to monitor
* @param	string	Unique key of the popup menu in which to show suggestions
*/
function vB_AJAX_NameSuggest(varname, textobjid, menukey)
{
	var webkit_version = userAgent.match(/applewebkit\/([0-9]+)/);

	if (AJAX_Compatible && !(is_saf && !(webkit_version[1] >= 412)))
	{
		this.menuobj = fetch_object(menukey + '_menu');
		this.textobj = fetch_object(textobjid);
		this.textobj.onfocus = function(e) { this.obj.active = true; };
		this.textobj.onblur  = function(e) { this.obj.active = false; };
		this.textobj.obj = this;

		/**
		* Varaiables used by this class
		*
		* @var	string	The name given to the instance of this class
		* @var	string	The menu key for the vbmenu name suggestion popup
		* @var	string	The current name fragment text
		* @var	string	The current string of completed names (Foo ; Bar etc.)
		* @var	integer	The currently selected name index in the menu
		* @var	boolean	Is the suggestion menu open or not
		* @var	object	A javascript timeout marker
		* @var	array	The list of suggested names
		* @var	object	The XML sender object
		* @var	boolean	True when text box is focussed - only show menu when true
		*/
		this.varname = varname;
		this.menukey = menukey;
		this.fragment = '';
		this.donenames = '';
		this.selected = 0;
		this.menuopen = false;
		this.timeout = null;
		this.names = new Array();
		this.xml_sender = null;
		this.active = false;

		/**
		* Options used by this class
		*
		* @var	boolean	Allow multiple names (Foo ; Bar etc.) or just single (Foo)
		* @var	integer	The minimum length of the text fragment before requesting a search
		*/
		this.allow_multiple = false;
		this.min_chars = 3;

		// =============================================================================
		// vB_AJAX_NameSuggest methods

		/**
		* Reads the contents of the text input box
		*/
		this.get_text = function()
		{
			if (this.allow_multiple)
			{
				// search for a semi-colon (meaning we have more than one name in the box)
				var semicolon = this.textobj.value.lastIndexOf(';');

				if (semicolon == -1)
				{
					// the current name is the only one in the text box
					this.donenames = new String('');
					this.fragment = new String(this.textobj.value);
				}
				else
				{
					// also need to store completed names in the text box
					this.donenames = new String(this.textobj.value.substring(0, semicolon + 1));
					this.fragment = new String(this.textobj.value.substring(semicolon + 1));
				}
			}
			else
			{
				this.fragment = new String(this.textobj.value);
			}

			// trim away leading and trailing spaces from the fragment
			this.fragment = PHP.trim(this.fragment);
		}

		/**
		* Sets the contents of the text input box
		*
		* @param	integer	The index of the desired name in this.names to insert
		*/
		this.set_text = function(i)
		{
			if (this.allow_multiple)
			{
				this.textobj.value = PHP.ltrim(this.donenames + " " + PHP.unhtmlspecialchars(this.names[i]) + " ; ");
			}
			else
			{
				this.textobj.value = PHP.unhtmlspecialchars(this.names[i]);
			}

			this.textobj.focus();

			this.menu_hide();

			return false;
		}

		/**
		* Moves the 'selected' row in the menu
		*
		* @param	integer	Increment (1, -1 etc.)
		*/
		this.move_row_selection = function(increment)
		{
			var newval = parseInt(this.selected, 10) + parseInt(increment, 10);

			if (newval < 0)
			{
				newval = this.names.length - 1;
			}
			else if (newval >= this.names.length)
			{
				newval = 0;
			}

			this.set_row_selection(newval);

			return false;
		}

		/**
		* Sets the 'selected' row in the menu
		*
		* @param	integer	The index of the desired selection (0 - n)
		*/
		this.set_row_selection = function(i)
		{
			var tds = fetch_tags(this.menuobj, 'td');
			tds[this.selected].className = 'vbmenu_option';
			this.selected = i;
			tds[this.selected].className = 'vbmenu_hilite';
		}

		/**
		* Event handler for the text input box key-up event
		*
		* @param	event	The event object
		*/
		this.key_event_handler = function(evt)
		{
			evt = evt ? evt : window.event;

			if (this.menuopen)
			{
				// 38 = up
				// 40 = down
				// 13 = return
				// 27 = escape

				switch (evt.keyCode)
				{
					case 38: // arrow up
					{
						this.move_row_selection(-1);
						return false;
					}
					case 40: // arrow down
					{
						this.move_row_selection(1);
						return false;
					}
					case 27: // escape
					{
						this.menu_hide();
						return false;
					}
					case 13: // return / enter
					{
						this.set_text(this.selected);
						return false;
					}
				}
			}

			// create the fragment
			this.get_text();

			if (this.fragment.length >= this.min_chars)
			{
				clearTimeout(this.timeout);
				this.timeout = setTimeout(this.varname + '.name_search();', 500);
			}
			else
			{
				this.menu_hide();
			}
		}

		/**
		* Sends the fragment to search the database
		*/
		this.name_search = function()
		{
			if (this.active)
			{
				this.names = new Array();

				if (!this.xml_sender)
				{
					this.xml_sender = new vB_AJAX_Handler(true);
				}
				this.xml_sender.onreadystatechange(this.onreadystatechange);
				this.xml_sender.send('ajax.php?do=usersearch', 'do=usersearch&fragment=' + PHP.urlencode(this.fragment));
			}
		}

		var me = this;

		/**
		* OnReadyStateChange callback. Uses a closure to keep state.
		* Remember to use 'me' instead of 'this' inside this function!
		*/
		this.onreadystatechange = function()
		{
			if (me.xml_sender.handler.readyState == 4 && me.xml_sender.handler.status == 200 && me.xml_sender.handler.responseXML)
			{
				var users = fetch_tags(me.xml_sender.handler.responseXML, 'user');
				for (i = 0; i < users.length; i++)
				{
					me.names[i] = me.xml_sender.fetch_data(users[i]);
				}

				if (me.names.length > 0)
				{
					me.menu_build();
					me.menu_show();
				}
				else
				{
					me.menu_hide();
				}

				me.xml_sender.handler.abort();
			}
		}

		/**
		* Builds the menu html from the list of found names
		*/
		this.menu_build = function()
		{
			this.menu_empty();
			var re = new RegExp('^(' + PHP.preg_quote(this.fragment) + ')', "i");

			var table = document.createElement('table');
			table.cellPadding = 4;
			table.cellSpacing = 1;
			table.border = 0;
			for (i in this.names)
			{
                         if(typeof this.names[i]=='function') continue;
				var td = table.insertRow(-1).insertCell(-1);
				td.className = (i == this.selected ? 'vbmenu_hilite' : 'vbmenu_option');
				td.title = 'nohilite';
				td.innerHTML = '<a onclick="return ' + this.varname + '.set_text(' + i + ')">' + this.names[i].replace(re, '<strong>$1</strong>') + '</a>';
			}
			this.menuobj.appendChild(table);

			if (this.vbmenu == null)
			{
				if (typeof(vBmenu.menus[this.menukey]) != 'undefined')
				{
					this.vbmenu = vBmenu.menus[this.menukey];
				}
				else
				{
					this.vbmenu = vBmenu.register(this.menukey, true);
				}
			}
			else
			{
				this.vbmenu.init_menu_contents();
			}
		}

		/**
		* Empties the menu of all names
		*/
		this.menu_empty = function()
		{
			this.selected = 0;

			while (this.menuobj.firstChild)
			{
				this.menuobj.removeChild(this.menuobj.firstChild);
			}
		}

		/**
		* Shows the menu
		*/
		this.menu_show = function()
		{
			if (this.active)
			{
				this.vbmenu.show(fetch_object(this.menukey), this.menuopen);
				this.menuopen = true;
			}
		}

		/**
		* Hides the menu
		*/
		this.menu_hide = function()
		{
			try
			{
				this.vbmenu.hide();
			}
			catch(e) {}
			this.menuopen = false;
		}

		this.textobj.onkeyup = function(e) { return this.obj.key_event_handler(e); };
		this.textobj.onkeypress = function(e)
		{
			e = e ? e : window.event;
			if (e.keyCode == 13)
			{
				return (this.obj.menuopen ? false : true);
			}
		};
	}
}

/*======================================================================*\
|| ####################################################################
|| # Downloaded: 17:39, Fri Mar 2nd 2007
|| # CVS: $RCSfile$ - $Revision: 15546 $
|| ####################################################################
\*======================================================================*/
/*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.6.5
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2007 Jelsoft Enterprises Ltd. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

var quote_editorid;
var quote_xml;

/**
* Initializes the link to fetch/deselect the additional MQ'd posts not in this thread.
*
* @param	string	ID of the editor to add the text to
* @param	integer	The ID of the current thread
*/
function init_unquoted_posts(editorid, threadid)
{
	var fetch_link = fetch_object('multiquote_more');
	if (fetch_link)
	{
		fetch_link.onclick = function() { return handle_unquoted_posts(editorid, threadid, 'fetch'); };
	}

	var deselect_link = fetch_object('multiquote_deselect');
	if (deselect_link)
	{
		deselect_link.onclick = function() { return handle_unquoted_posts(editorid, threadid, 'deselect'); };
	}
}

/**
* Handles the unquoted posts for all other threads. Either fetches the contents of the posts, or deselects them
*
* @param	string	ID of the editor to insert the text into
* @param	integer	The thread ID of the current thread; posts from this thread will not be fetched
* @param	string	Type of data to retrieve: fetch (returns post text) or deselect (returns new value of cookie)
*/
function handle_unquoted_posts(editorid, threadid, type)
{
	quote_editorid = editorid;

	quote_xml = new vB_AJAX_Handler(true);
	quote_xml.onreadystatechange(handle_ajax_unquoted_response);
	quote_xml.send(
		'newreply.php?do=unquotedposts&threadid=' + threadid,
		'do=unquotedposts&threadid=' + threadid
			+ '&wysiwyg=' + (vB_Editor[quote_editorid].wysiwyg_mode ? 1 : 0)
			+ '&type=' + PHP.urlencode(type)
	);

	return false;
}

/**
* OnReadyStateChange handler for the AJAX object.
* If a fetch response, inserts the text at the cursor position.
*/
function handle_ajax_unquoted_response()
{
	if (quote_xml.handler.readyState == 4 && quote_xml.handler.status == 200)
	{
		if (quote_xml.handler.responseXML)
		{
			if (fetch_tags(quote_xml.handler.responseXML, 'quotes')[0])
			{
				// insert the text into the editor
				vB_Editor[quote_editorid].history.add_snapshot(vB_Editor[quote_editorid].get_editor_contents());
				vB_Editor[quote_editorid].insert_text(quote_xml.fetch_data(fetch_tags(quote_xml.handler.responseXML, 'quotes')[0]));
				vB_Editor[quote_editorid].collapse_selection_end();
				vB_Editor[quote_editorid].history.add_snapshot(vB_Editor[quote_editorid].get_editor_contents());

				// change the multiquote empty input to empty the cookie completely
				var multiquote_empty_input = fetch_object('multiquote_empty_input');
				if (multiquote_empty_input)
				{
					multiquote_empty_input.value = 'all';
				}
			}
			else if (fetch_tags(quote_xml.handler.responseXML, 'mqpostids')[0])
			{
				// this returns the new content of the cookie, so use it
				set_cookie('vbulletin_multiquote', quote_xml.fetch_data(fetch_tags(quote_xml.handler.responseXML, 'mqpostids')[0]));
			}

			// remove the link to insert unquoted posts
			var unquoted_posts = fetch_object('unquoted_posts');
			if (unquoted_posts)
			{
				unquoted_posts.style.display = 'none';
			}
		}

		if (is_ie)
		{
			quote_xml.handler.abort();
		}
	}
}

/*======================================================================*\
|| ####################################################################
|| # Downloaded: 17:39, Fri Mar 2nd 2007
|| # CVS: $RCSfile$ - $Revision: 14996 $
|| ####################################################################
\*======================================================================*//*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.6.5
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2007 Jelsoft Enterprises Ltd. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

/**
* Initializes the MQ images, so they are clickable. Additionally, it redoes
* the coloring of the image based on the current value of the cookie. This
* is helpful if a user uses the back button.
*
* @param	object	The object to search within for MQ images.
*/
function mq_init(obj)
{
	var cookie_ids = fetch_cookie('vbulletin_multiquote');
	if (cookie_ids != null && cookie_ids != '')
	{
		cookie_ids = cookie_ids.split(',');
	}
	else
	{
		cookie_ids = new Array();
	}

	var postid;

	var images = fetch_tags(obj, 'img');
	for (var i = 0; i < images.length; i++)
	{
		if (images[i].id && images[i].id.substr(0, 3) == 'mq_')
		{
			postid = images[i].id.substr(3);
			images[i].onclick = function(e) { return mq_click(this.id.substr(3)); };
			change_mq_image(postid, (PHP.in_array(postid, cookie_ids) > -1 ? true : false));
		}
	}
}

/**
* Callback function to when an MQ image is clicked. Modifies the cookie and
* updates the look of the image to suit.
*
* @param	integer	Post ID of the image clicked.
*
* @return	false	Always returns false to ensure any href event does not run
*/
function mq_click(postid)
{
	var cookie_ids = fetch_cookie('vbulletin_multiquote');

	var cookie_text = new Array();
	var is_selected = false;

	if (cookie_ids != null && cookie_ids != '')
	{
		cookie_ids = cookie_ids.split(',');

		for (i in cookie_ids)
		{
                   if(typeof this.cookie_ids[i]=='function') continue;
			if (cookie_ids[i] == postid)
			{
				is_selected = true;
			}
			else if (cookie_ids[i])
			{
				cookie_text.push(cookie_ids[i]);
			}
		}
	}

	// flip the image to the other option
	change_mq_image(postid, (is_selected ? false : true));

	// if we don't have the postid in the cookie, add it
	if (!is_selected)
	{
		cookie_text.push(postid);
		if (typeof mqlimit != 'undefined' && mqlimit > 0)
		{
			for (var i = 0; i < (cookie_text.length - mqlimit); i++)
			{
				var removal = cookie_text.shift();
				change_mq_image(removal, false);
			}
		}
	}

	set_cookie('vbulletin_multiquote', cookie_text.join(','));

	return false;
}

/**
* Changes the MQ image to show as being selected or unselected
*
* @param	integer	ID of the post whose MQ button is changing
* @param	boolean	Whether to make the image selected or not
*/
function change_mq_image(postid, to_selected)
{
	var mq_obj = fetch_object('mq_' + postid);
	if (mq_obj)
	{
		if (to_selected == true)
		{
			mq_obj.src = mq_obj.src.replace(/\/multiquote_off\.([a-zA-Z0-9]+)$/, '/multiquote_on.$1');
		}
		else
		{
			mq_obj.src = mq_obj.src.replace(/\/multiquote_on\.([a-zA-Z0-9]+)$/, '/multiquote_off.$1');
		}
	}
}

mq_init(fetch_object('posts'));

/*======================================================================*\
|| ####################################################################
|| # Downloaded: 17:39, Fri Mar 2nd 2007
|| # CVS: $RCSfile$ - $Revision: 15174 $
|| ####################################################################
\*======================================================================*/
/*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.6.5
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2007 Jelsoft Enterprises Ltd. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

/**
* Attempts to display a post via AJAX, falling back to opening a new window if AJAX not available
*
* @param	integer	Post ID
*
* @return	boolean	False
*/
function display_post(postid)
{
	if (AJAX_Compatible)
	{
		vB_PostLoader[postid] = new vB_AJAX_PostLoader(postid);
		vB_PostLoader[postid].init();
	}
	else
	{
		pc_obj = fetch_object('postcount' + this.postid);
		openWindow('showpost.php?' + (SESSIONURL ? 's=' + SESSIONURL : '') + (pc_obj != null ? '&postcount=' + PHP.urlencode(pc_obj.name) : '') + '&p=' + postid);
	}
	return false;
};

// #############################################################################
// vB_AJAX_PostLoader
// #############################################################################

var vB_PostLoader = new Array();

/**
* Class to load a postbit via AJAX
*
* @param	integer	Post ID
*/
function vB_AJAX_PostLoader(postid)
{
	this.postid = postid;
	this.container = fetch_object('edit' + this.postid);
};

/**
* Initiates the AJAX send to showpost.php
*/
vB_AJAX_PostLoader.prototype.init = function()
{
	if (this.container)
	{
		postid = this.postid;
		pc_obj = fetch_object('postcount' + this.postid);
		this.ajax = new vB_AJAX_Handler(true);
		this.ajax.onreadystatechange(vB_PostLoader[postid].ajax_check);
		this.ajax.send('showpost.php?p=' + this.postid,
			'ajax=1&postid=' + this.postid +
			(pc_obj != null ? '&postcount=' + PHP.urlencode(pc_obj.name) : '')
		);
	}
};

/**
* Onreadystate handler for AJAX post loader
*
* @return	boolean	False
*/
vB_AJAX_PostLoader.prototype.ajax_check = function()
{
	var AJAX = vB_PostLoader[postid].ajax.handler;

	if (AJAX.readyState == 4 && AJAX.status == 200)
	{
		vB_PostLoader[postid].display(AJAX.responseXML);

		if (is_ie)
		{
			AJAX.abort();
		}
	}

	return false;
};

/**
* Takes the AJAX HTML output and replaces the existing post placeholder with the new HTML
*
* @param	string	Postbit HTML
*/
vB_AJAX_PostLoader.prototype.display = function(postbit_xml)
{
	if (fetch_tag_count(postbit_xml, 'postbit'))
	{
		this.container.innerHTML = this.ajax.fetch_data(fetch_tags(postbit_xml, 'postbit')[0]);
		PostBit_Init(fetch_object('post' + this.postid), this.postid);
	}
	else
	{	// parsing of XML failed, probably IE
		openWindow('showpost.php?' + (SESSIONURL ? 's=' + SESSIONURL : '') + (pc_obj != null ? '&postcount=' + PHP.urlencode(pc_obj.name) : '') + '&p=' + this.postid);
	}
};

/*======================================================================*\
|| ####################################################################
|| # Downloaded: 17:39, Fri Mar 2nd 2007
|| # CVS: $RCSfile$ - $Revision: 15091 $
|| ####################################################################
\*======================================================================*//*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.6.5
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2007 Jelsoft Enterprises Ltd. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

/**
* Register a post for ajax reputation
*
* @param	string	Postid
*
* @return	vB_Reputation_Object
*/
function vbrep_register(postid)
{
	if (typeof vBrep == 'object' && typeof postid != 'undefined')
	{
		return vBrep.register(postid);
	}
}

// #############################################################################
// vB_Reputation_Handler
// #############################################################################

/**
* vBulletin reputation registry
*/
function vB_Reputation_Handler()
{
	this.reps = new Array();
	this.ajax = new Array();
};

// =============================================================================
// vB_Reputation_Handler methods

/**
* Register a control object as a reputation control
*
* @param	string	ID of the control object
*
* @return	vB_Reputation_Object
*/
vB_Reputation_Handler.prototype.register = function(postid)
{
	if (AJAX_Compatible && (typeof vb_disable_ajax == 'undefined' || vb_disable_ajax < 2))
	{
		this.reps[postid] = new vB_Reputation_Object(postid);
		if (obj = fetch_object('reputation_' + postid))
		{
			obj.onclick = vB_Reputation_Object.prototype.reputation_click;
			return this.reps[postid];
		}
	}
};

// #############################################################################
// initialize reputation registry

vBrep = new vB_Reputation_Handler();

// #############################################################################
// vB_Reputation_Object
// #############################################################################

/**
* vBulletin Reputation class constructor
*
* Manages a single reputation and control object
* Initializes control object
*
* @param	string	postid
*/
function vB_Reputation_Object(postid)
{
	this.postid = postid;
	this.divname = 'reputationmenu_' + postid + '_menu';
	this.divobj = null;
	this.postobj = fetch_object('post' + postid);

	this.vbmenuname = 'reputationmenu_' + postid;
	this.vbmenu = null;

	this.xml_sender_populate = null;
	this.xml_sender_submit = null;

	var me = this;

	/**
	* Populate OnReadyStateChange callback. Uses a closure to keep state.
	* Remember to use me instead of "this" inside this function!
	*/
	this.onreadystatechange_populate = function()
	{
		if (me.xml_sender_populate.handler.readyState == 4 && me.xml_sender_populate.handler.status == 200)
		{
			if (me.xml_sender_populate.handler.responseXML)
			{
				// check for error first
				var error = me.xml_sender_populate.fetch_data(fetch_tags(me.xml_sender_populate.handler.responseXML, 'error')[0]);
				if (error)
				{
					alert(error);
				}
				else
				{
					if (!me.divobj)
					{
						// Create new div to hold reputation menu html
						me.divobj = document.createElement('div');
						me.divobj.id = me.divname;
						me.divobj.style.display = 'none';
						me.divobj.onkeypress = vB_Reputation_Object.prototype.repinput_onkeypress;
						me.postobj.parentNode.appendChild(me.divobj);

						me.vbmenu = vbmenu_register(me.vbmenuname, true);
						// Remove menu's mouseover event
						fetch_object(me.vbmenu.controlkey).onmouseover = '';
						fetch_object(me.vbmenu.controlkey).onclick = '';
					}

					me.divobj.innerHTML = me.xml_sender_populate.fetch_data(fetch_tags(me.xml_sender_populate.handler.responseXML, 'reputationbit')[0]);

					var inputs = fetch_tags(me.divobj, 'input');
					for (var i = 0; i < inputs.length; i++)
					{
						if (inputs[i].type == 'submit')
						{
							var sbutton = inputs[i];
							var button = document.createElement('input');
							button.type = 'button';
							button.className = sbutton.className;
							button.value = sbutton.value;
							button.onclick = vB_Reputation_Object.prototype.submit_onclick;
							sbutton.parentNode.insertBefore(button, sbutton);
							sbutton.parentNode.removeChild(sbutton);
							button.name = sbutton.name;
							button.id = sbutton.name + '_' + me.postid
						}
					}

					me.vbmenu.show(fetch_object(me.vbmenuname));
				}
			}

			if (is_ie)
			{
				me.xml_sender_populate.handler.abort();
			}
		}
	}

	/**
	* Submit OnReadyStateChange callback. Uses a closure to keep state.
	* Remember to use me instead of "this" inside this function!
	*/
	this.onreadystatechange_submit = function()
	{
		if (me.xml_sender_submit.handler.readyState == 4 && me.xml_sender_submit.handler.status == 200)
		{
			if (me.xml_sender_submit.handler.responseXML)
			{
				// Register new menu item for this reputation icon
				if (!me.vbmenu)
				{
					me.vbmenu = vbmenu_register(me.vbmenuname, true);
					// Remove menu's mouseover event
					fetch_object(me.vbmenu.controlkey).onmouseover = '';
					fetch_object(me.vbmenu.controlkey).onclick = '';
				}

				// check for error first
				var error = me.xml_sender_submit.fetch_data(fetch_tags(me.xml_sender_submit.handler.responseXML, 'error')[0]);
				if (error)
				{
					me.vbmenu.hide(fetch_object(me.vbmenuname));
					alert(error);
				}
				else
				{
					me.vbmenu.hide(fetch_object(me.vbmenuname));
					var repinfo =  fetch_tags(me.xml_sender_submit.handler.responseXML, 'reputation')[0];
					var repdisplay = repinfo.getAttribute('repdisplay');
					var reppower = repinfo.getAttribute('reppower');
					var userid = repinfo.getAttribute('userid');

					var spans = fetch_tags(document, 'span');
					var postid = null;
					var match = null;

					for (var i = 0; i < spans.length; i++)
					{
						if (match = spans[i].id.match(/^reppower_(\d+)_(\d+)$/))
						{
							if (match[2] == userid)
							{
								spans[i].innerHTML = reppower;
							}
						}
						else if (match = spans[i].id.match(/^repdisplay_(\d+)_(\d+)$/))
						{
							if (match[2] == userid)
							{
								spans[i].innerHTML = repdisplay;
							}
						}
					}
					alert(me.xml_sender_submit.fetch_data(repinfo));
				}
			}

			if (is_ie)
			{
				me.xml_sender_submit.handler.abort();
			}
		}
	}
}

/**
* Handles click events on reputation icon
*/
vB_Reputation_Object.prototype.reputation_click = function (e)
{
	e = e ? e : window.event;

	do_an_e(e);
	var postid = this.id.substr(this.id.lastIndexOf('_') + 1);
	var repobj = vBrep.reps[postid];

	// fetch and return reputation html
	if (repobj.vbmenu == null)
	{
		repobj.populate();
	}
	else if (vBmenu.activemenu != repobj.vbmenuname)
	{
		repobj.vbmenu.show(fetch_object(repobj.vbmenuname));
	}
	else
	{
		repobj.vbmenu.hide();
	}

	return true;
}

/**
* Handles click events on reputation submit button
*/

vB_Reputation_Object.prototype.submit_onclick = function (e)
{
	e = e ? e : window.event;
	do_an_e(e);

	var postid = this.id.substr(this.id.lastIndexOf('_') + 1);
	var repobj = vBrep.reps[postid];
	repobj.submit();

	return false;
}

/**
*	Catches the keypress of the reputation controls to keep them from submitting to inlineMod
*/
vB_Reputation_Object.prototype.repinput_onkeypress = function (e)
{
	e = e ? e : window.event;

	switch (e.keyCode)
	{
		case 13:
		{
			vBrep.reps[this.id.split(/_/)[1]].submit();	
			return false;
		}
		default:
		{
			return true;
		}
	}
}

/**
* Queries for proper response to reputation, response varies
*
*/
vB_Reputation_Object.prototype.populate = function()
{
	this.xml_sender_populate = new vB_AJAX_Handler(true);
	this.xml_sender_populate.onreadystatechange(this.onreadystatechange_populate);
	this.xml_sender_populate.send('reputation.php?p=' + this.postid, 'p=' + this.postid + '&ajax=1');
}

/**
* Submits reputation
*
*/
vB_Reputation_Object.prototype.submit = function()
{
	this.psuedoform = new vB_Hidden_Form('reputation.php');
	this.psuedoform.add_variable('ajax', 1);
	this.psuedoform.add_variables_from_object(this.divobj);

	this.xml_sender_submit = new vB_AJAX_Handler(true);
	this.xml_sender_submit.onreadystatechange(this.onreadystatechange_submit)
	this.xml_sender_submit.send(
		'reputation.php?do=addreputation&p=' + this.psuedoform.fetch_variable('p') + '&reputation=' + this.psuedoform.fetch_variable('reputation') + '&reason=' + PHP.urlencode(this.psuedoform.fetch_variable('reason')),
		this.psuedoform.build_query_string()
	);
}

/*======================================================================*\
|| ####################################################################
|| # Downloaded: 17:39, Fri Mar 2nd 2007
|| # CVS: $RCSfile$ - $Revision: 15753 $
|| ####################################################################
\*======================================================================*//*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.6.5
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2007 Jelsoft Enterprises Ltd. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

/**
* Adds onclick event to the save search prefs buttons
*
* @param	string	The ID of the button that fires the search prefs
*/
function vB_AJAX_SearchPrefs_Init(buttonid)
{
	if (AJAX_Compatible && (typeof vb_disable_ajax == 'undefined' || vb_disable_ajax < 2) && fetch_object(buttonid))
	{
		// prevent the form from submitting when clicking the submit button
		var sbutton = fetch_object(buttonid);
		sbutton.onclick = vB_AJAX_SearchPrefs.prototype.form_click;
	}
};

/**
* Class to handle saveing search prefs
*
* @param	object	The form object containing the search options
*/
function vB_AJAX_SearchPrefs(formobj)
{
	// AJAX handler
	this.xml_sender = null;

	// vB_Hidden_Form object to handle form variables
	this.pseudoform = new vB_Hidden_Form('search.php');
	this.pseudoform.add_variable('ajax', 1);
	this.pseudoform.add_variable('doprefs', 1);
	this.pseudoform.add_variables_from_object(formobj);

	// Closure
	var me = this;

	/**
	* OnReadyStateChange callback. Uses a closure to keep state.
	* Remember to use me instead of this inside this function!
	*/
	this.handle_ajax_response = function()
	{
		if (me.xml_sender.handler.readyState == 4 && me.xml_sender.handler.status == 200)
		{
			if (me.xml_sender.handler.responseXML)
			{
				var obj = fetch_object(me.objid);

				// check for error first
				var error = me.xml_sender.fetch_data(fetch_tags(me.xml_sender.handler.responseXML, 'error')[0]);
				if (error)
				{
					alert(error);
				}
				else
				{
					var message = me.xml_sender.fetch_data(fetch_tags(me.xml_sender.handler.responseXML, 'message')[0]);
					if (message)
					{
						alert(message);
					}
				}
			}

			if (is_ie)
			{
				me.xml_sender.handler.abort();
			}
		}
	}
};

/**
* Submits the form via Ajax
*/
vB_AJAX_SearchPrefs.prototype.submit = function()
{
	this.xml_sender = new vB_AJAX_Handler(true);
	this.xml_sender.onreadystatechange(this.handle_ajax_response);
	this.xml_sender.send(
		'search.php?',
		this.pseudoform.build_query_string()
	);
};

/**
* Handles the form 'submit' action
*/
vB_AJAX_SearchPrefs.prototype.form_click = function()
{
	var AJAX_SearchPrefs = new vB_AJAX_SearchPrefs(this.form);
	AJAX_SearchPrefs.submit();
	return false;
};

/*======================================================================*\
|| ####################################################################
|| # Downloaded: 17:39, Fri Mar 2nd 2007
|| # CVS: $RCSfile$ - $Revision: 14850 $
|| ####################################################################
\*======================================================================*//*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.6.5
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2007 Jelsoft Enterprises Ltd. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

/**
* Adds onclick events to appropriate elements for thread rating
*
* @param	string	The ID of the form that contains the rating options
*/
function vB_AJAX_ThreadRate_Init(formid)
{
	var formobj = fetch_object(formid);

	if (AJAX_Compatible && (typeof vb_disable_ajax == 'undefined' || vb_disable_ajax < 2) && formobj)
	{
		for (var i = 0; i < formobj.elements.length; i++)
		{
			//alert(1);
			if (formobj.elements[i].type == 'submit')
			{
				// prevent the form from submitting when clicking the submit button
				var sbutton = formobj.elements[i];
				var button = document.createElement('input');
				button.type = 'button';
				button.className = sbutton.className;
				button.value     = sbutton.value;
				button.onclick   = vB_AJAX_ThreadRate.prototype.form_click;
				sbutton.parentNode.insertBefore(button, sbutton);
				sbutton.parentNode.removeChild(sbutton);
			}
		}
	}
};

/**
* Class to handle thread rating
*
* @param	object	The form object containing the vote options
*/
function vB_AJAX_ThreadRate(formobj)
{
	// AJAX handler
	this.xml_sender = null;

	// vB_Hidden_Form object to handle form variables
	this.pseudoform = new vB_Hidden_Form('threadrate.php');
	this.pseudoform.add_variable('ajax', 1);
	this.pseudoform.add_variables_from_object(formobj);

	// Output object
	this.output_element_id = 'threadrating_current';

	// Closure
	var me = this;

	/**
	* OnReadyStateChange callback. Uses a closure to keep state.
	* Remember to use me instead of this inside this function!
	*/
	this.handle_ajax_response = function()
	{
		if (me.xml_sender.handler.readyState == 4 && me.xml_sender.handler.status == 200)
		{
			if (me.xml_sender.handler.responseXML)
			{
				var obj = fetch_object(me.objid);
				// check for error first
				var error = me.xml_sender.fetch_data(fetch_tags(me.xml_sender.handler.responseXML, 'error')[0]);
				if (error)
				{
					// Hide thread rating popup menu now
					if (vBmenu.activemenu == 'threadrating')
					{
						vBmenu.hide();
					}
					alert(error);
				}
				else
				{
					var newrating = me.xml_sender.fetch_data(fetch_tags(me.xml_sender.handler.responseXML, 'voteavg')[0]);
					if (newrating != '')
					{
						fetch_object(me.output_element_id).innerHTML = newrating;
					}
					// Hide thread rating popup menu now
					if (vBmenu.activemenu == 'threadrating')
					{
						vBmenu.hide();
					}

					var message = me.xml_sender.fetch_data(fetch_tags(me.xml_sender.handler.responseXML, 'message')[0]);
					if (message)
					{
						alert(message);
					}
				}
			}

			if (is_ie)
			{
				me.xml_sender.handler.abort();
			}
		}
	}
};

/**
* Places the vote
*/
vB_AJAX_ThreadRate.prototype.rate = function()
{
	this.xml_sender = new vB_AJAX_Handler(true);
	this.xml_sender.onreadystatechange(this.handle_ajax_response);
	this.xml_sender.send(
		'threadrate.php?t=' + threadid + '&vote=' + PHP.urlencode(this.pseudoform.fetch_variable('vote')),
		this.pseudoform.build_query_string()
	);
};

/**
* Handles the form 'submit' action
*/
vB_AJAX_ThreadRate.prototype.form_click = function()
{
	var AJAX_ThreadRate = new vB_AJAX_ThreadRate(this.form);
	AJAX_ThreadRate.rate();
	return false;
};

/*======================================================================*\
|| ####################################################################
|| # Downloaded: 17:39, Fri Mar 2nd 2007
|| # CVS: $RCSfile$ - $Revision: 15518 $
|| ####################################################################
\*======================================================================*//*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.6.5
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2007 Jelsoft Enterprises Ltd. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

var vB_ThreadTitle_Editor = null;

/**
* Adds ondblclick events to appropriate elements for title editing
*
* @param	string	The ID of the thread list element (usually 'threadslist')
*/
function vB_AJAX_Threadlist_Init(threadlistid)
{
	if (AJAX_Compatible && (typeof vb_disable_ajax == 'undefined' || vb_disable_ajax < 2))
	{
		var tds = fetch_tags(fetch_object(threadlistid), 'td');
		for (var i = 0; i < tds.length; i++)
		{
			if (tds[i].hasChildNodes() && tds[i].id && tds[i].id.substr(0, 3) == 'td_')
			{
				var anchors = fetch_tags(tds[i], 'a');
				for (var j = 0; j < anchors.length; j++)
				{
					if (anchors[j].name && anchors[j].name.indexOf('vB::AJAX') != -1)
					{
						var details = tds[i].id.split('_');

						switch (details[1])
						{
							case 'threadtitle':
							{
								if (typeof vb_disable_ajax == 'undefined' || vb_disable_ajax == 0)
								{
									tds[i].style.cursor = 'default';
									tds[i].ondblclick = vB_AJAX_ThreadList_Events.prototype.threadtitle_doubleclick;
								}
							}
							break;

							case 'threadstatusicon':
							{
								tds[i].style.cursor = pointer_cursor;
								tds[i].ondblclick = vB_AJAX_ThreadList_Events.prototype.threadicon_doubleclick;
							}
							break;
						}

						break;
					}
				}
			}
		}
	}
}

// #############################################################################
// vB_AJAX_OpenClose
// #############################################################################

/**
* Class to handle opening and closing of threads from forumdisplay with XML-HTTP
*
* @param	object	The clickable status icon image for the thread
*/
function vB_AJAX_OpenClose(obj)
{
	this.obj = obj;
	this.threadid = this.obj.id.substr(this.obj.id.lastIndexOf('_') + 1);
	this.imgobj = fetch_object('thread_statusicon_' + this.threadid);
	this.xml_sender = null;

	// =============================================================================
	// vB_AJAX_OpenClose methods

	/**
	* Function to switch the open/closed state of a thread / thread status icon
	*/
	this.toggle = function()
	{
		this.xml_sender = new vB_AJAX_Handler(true);
		this.xml_sender.onreadystatechange(this.onreadystatechange);
		this.xml_sender.send('ajax.php?do=updatethreadopen&t=' + this.threadid, 'do=updatethreadopen&t=' + this.threadid + '&src=' + PHP.urlencode(this.imgobj.src));
	}

	var me = this;

	/**
	* OnReadyStateChange callback. Uses a closure to keep state.
	* Remember to use me instead of this inside this function!
	*/
	this.onreadystatechange = function()
	{
		if (me.xml_sender.handler.readyState == 4 && me.xml_sender.handler.status == 200)
		{
			if (me.xml_sender.handler.responseXML)
			{
				me.imgobj.src = me.xml_sender.fetch_data(fetch_tags(me.xml_sender.handler.responseXML, 'imagesrc')[0]);
				if (iobj = fetch_object("tlist_" + me.threadid))
				{
					if (me.imgobj.src.indexOf('_lock') != -1)
					{	// locked thread - add lock status to inline mod
						iobj.value = iobj.value | 1;
					}
					else
					{	// unlocked thread - remove lock status from inline mod
						iobj.value = iobj.value & ~1;
					}
				}
			}

			if (is_ie)
			{
				me.xml_sender.handler.abort();
			}
		}
	}

	// send the data
	this.toggle();
}

// #############################################################################
// vB_AJAX_TitleEdit
// #############################################################################

/**
* Class to handle thread title editing with XML-HTTP
*
* @param	object	The <td> containing the title element
*/
function vB_AJAX_TitleEdit(obj)
{
	this.obj = obj;
	this.threadid = this.obj.id.substr(this.obj.id.lastIndexOf('_') + 1);
	this.linkobj = fetch_object('thread_title_' + this.threadid);
	this.container = this.linkobj.parentNode;
	this.editobj = null;
	this.xml_sender = null;

	this.origtitle = '';
	this.editstate = false;

	// =============================================================================
	// vB_AJAX_TitleEdit methods

	/**
	* Function to initialize the editor for a thread title
	*/
	this.edit = function()
	{
		if (this.editstate == false)
		{
			// create the new editor input box properties...
			this.inputobj = document.createElement('input');
			this.inputobj.type = 'text';
			this.inputobj.size = 50;
			this.inputobj.maxLength = 85;
			this.inputobj.style.width = Math.max(this.linkobj.offsetWidth, 250) + 'px';
			this.inputobj.className = 'bginput';
			this.inputobj.value = PHP.unhtmlspecialchars(this.linkobj.innerHTML);
			this.inputobj.title = this.inputobj.value;

			// ... and event handlers
			this.inputobj.onblur = vB_AJAX_ThreadList_Events.prototype.titleinput_onblur;
			this.inputobj.onkeypress = vB_AJAX_ThreadList_Events.prototype.titleinput_onkeypress;

			// insert the editor box and select it
			this.editobj = this.container.insertBefore(this.inputobj, this.linkobj);
			this.editobj.select();

			// store the original text
			this.origtitle = this.linkobj.innerHTML;

			// hide the link object
			this.linkobj.style.display = 'none';

			// declare that we are in an editing state
			this.editstate = true;
		}
	}

	/**
	* Function to restore a thread title in the editing state
	*/
	this.restore = function()
	{
		if (this.editstate == true)
		{
			// do we actually need to save?
			if (this.editobj.value != this.origtitle)
			{
				this.linkobj.innerHTML = PHP.htmlspecialchars(this.editobj.value);
				this.save(this.editobj.value);
			}
			else
			{
				// set the new contents for the link
				this.linkobj.innerHTML = this.editobj.value;
			}

			// remove the editor box
			this.container.removeChild(this.editobj);

			// un-hide the link
			this.linkobj.style.display = '';

			// declare that we are in a normal state
			this.editstate = false;
			this.obj = null;
		}
	}

	/**
	* Function to save an edited thread title
	*
	* @param	string	Edited title text
	*
	* @return	string	Validated title text
	*/
	this.save = function(titletext)
	{
		this.xml_sender = new vB_AJAX_Handler(true);
		this.xml_sender.onreadystatechange(this.onreadystatechange);
		this.xml_sender.send('ajax.php?do=updatethreadtitle&t=' + this.threadid, 'do=updatethreadtitle&t=' + this.threadid + '&title=' + PHP.urlencode(titletext));
	}

	var me = this;

	/**
	* OnReadyStateChange callback. Uses a closure to keep state.
	* Remember to use me instead of this inside this function!
	*/
	this.onreadystatechange = function()
	{
		if (me.xml_sender.handler.readyState == 4 && me.xml_sender.handler.status == 200)
		{
			if (me.xml_sender.handler.responseXML)
			{
				me.linkobj.innerHTML = me.xml_sender.fetch_data(fetch_tags(me.xml_sender.handler.responseXML, 'linkhtml')[0]);
			}

			if (is_ie)
			{
				me.xml_sender.handler.abort();
			}

			vB_ThreadTitle_Editor.obj = null;
		}
	}

	// start the editor
	this.edit();
}

// #############################################################################
// Threadlist event handlers

/**
* Class to handle events in the threadlist
*/
function vB_AJAX_ThreadList_Events()
{
}

/**
* Handles double-clicking on thread title cells to initialize title edit
*/
vB_AJAX_ThreadList_Events.prototype.threadtitle_doubleclick = function(e)
{
	if (vB_ThreadTitle_Editor && vB_ThreadTitle_Editor.obj == this)
	{
		return false;
	}
	else
	{
		try
		{
			vB_ThreadTitle_Editor.restore();
		}
		catch(e) {}

		vB_ThreadTitle_Editor = new vB_AJAX_TitleEdit(this);
	}
};

/**
* Handles double-clicking on thread icon cells to toggle open/close state
*/
vB_AJAX_ThreadList_Events.prototype.threadicon_doubleclick = function(e)
{
	openclose = new vB_AJAX_OpenClose(this);
};

/**
* Handles blur events on thread title input boxes
*/
vB_AJAX_ThreadList_Events.prototype.titleinput_onblur = function(e)
{
	vB_ThreadTitle_Editor.restore();
};

/**
* Handles keypress events on thread title input boxes
*/
vB_AJAX_ThreadList_Events.prototype.titleinput_onkeypress = function (e)
{
	e = e ? e : window.event;
	switch (e.keyCode)
	{
		case 13: // return / enter
		{
			vB_ThreadTitle_Editor.inputobj.blur();
			return false;
		}
		case 27: // escape
		{
			vB_ThreadTitle_Editor.inputobj.value = vB_ThreadTitle_Editor.origtitle;
			vB_ThreadTitle_Editor.inputobj.blur();
			return true;
		}
	}
};

/*======================================================================*\
|| ####################################################################
|| # Downloaded: 17:39, Fri Mar 2nd 2007
|| # CVS: $RCSfile$ - $Revision: 15089 $
|| ####################################################################
\*======================================================================*//*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.6.5
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2007 Jelsoft Enterprises Ltd. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

/**
* Adds onclick events to appropriate elements for submitting the form
*
* @param	string	The ID of the form that contains the ignore list elements
* @param	string	The ID of the form that contains the buddy list elements
*/
function vB_AJAX_Userlist_Init(ignoreformid, buddyformid)
{
	// this can count as a "problematic" AJAX function, as usernames won't be found without iconv
	if (AJAX_Compatible && (typeof vb_disable_ajax == 'undefined' || vb_disable_ajax == 0))
	{
		if (typeof(document.forms.userlist_buddyform) != 'undefined')
		{
			document.forms.userlist_buddyform.onsubmit = vB_AJAX_Userlist.prototype.form_click;
		}
		if (typeof(document.forms.userlist_ignoreform) != 'undefined')
		{
			document.forms.userlist_ignoreform.onsubmit = vB_AJAX_Userlist.prototype.form_click;
		}
	}
};

/**
* Class to handle userlist modifications
*
* @param	object	The form object containing the list elements
*/
function vB_AJAX_Userlist(formobj)
{
	// AJAX handler
	this.xml_sender = null;

	// vB_Hidden_Form object to handle form variables
	this.pseudoform = new vB_Hidden_Form('profile.php');
	this.pseudoform.add_variable('ajax', 1);
	this.pseudoform.add_variables_from_object(formobj);

	this.list = this.pseudoform.fetch_variable('userlist');

	// Closure
	var me = this;

	/**
	* OnReadyStateChange callback. Uses a closure to keep state.
	* Remember to use me instead of this inside this function!
	*/
	this.handle_ajax_response = function()
	{
		if (me.xml_sender.handler.readyState == 4 && me.xml_sender.handler.status == 200)
		{
			if (me.xml_sender.handler.responseXML)
			{
				// check for error first
				var error = me.xml_sender.fetch_data(fetch_tags(me.xml_sender.handler.responseXML, 'error')[0]);
				if (error)
				{
					// show error
					fetch_object('userfield_' + me.list + '_errortext').innerHTML = error;
					fetch_object('userfield_' + me.list + '_error').style.display = '';
				}
				else
				{
					// hide error
					fetch_object('userfield_' + me.list + '_error').style.display = 'none';

					fetch_object('userfield_' + me.list + '_txt').value = '';
					fetch_object(me.list + 'list1').innerHTML = me.xml_sender.fetch_data(fetch_tags(me.xml_sender.handler.responseXML, 'listbit1')[0]);
					fetch_object(me.list + 'list2').innerHTML = me.xml_sender.fetch_data(fetch_tags(me.xml_sender.handler.responseXML, 'listbit2')[0]);
				}
			}

			if (is_ie)
			{
				me.xml_sender.handler.abort();
			}
		}
	}
};

/**
* Submit the form
*/
vB_AJAX_Userlist.prototype.submit = function()
{
	this.xml_sender = new vB_AJAX_Handler(true);
	this.xml_sender.onreadystatechange(this.handle_ajax_response);
	this.xml_sender.send(
		'profile.php?do=updatelist&userlist=' + PHP.urlencode(this.list),
		this.pseudoform.build_query_string()
	);
};

/**
* Handles the form 'submit' action
*/
vB_AJAX_Userlist.prototype.form_click = function()
{
	var AJAX_Userlist = new vB_AJAX_Userlist(this);
	AJAX_Userlist.submit();
	return false;
};

/*======================================================================*\
|| ####################################################################
|| # Downloaded: 17:39, Fri Mar 2nd 2007
|| # CVS: $RCSfile$ - $Revision: 15437 $
|| ####################################################################
\*======================================================================*//*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.6.5
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2007 Jelsoft Enterprises Ltd. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

/**
* Adds onclick events to appropriate elements for AJAX IP resolving
*
* @param	string	The ID of the table that contains WOL entries with IPs to resolve
*/
function vB_AJAX_WolResolve_Init(woltableid)
{
	if (AJAX_Compatible && (typeof vb_disable_ajax == 'undefined' || vb_disable_ajax < 2))
	{
		var link_list = fetch_tags(fetch_object(woltableid), 'a');
		for (var i = 0; i < link_list.length; i++)
		{
			if (link_list[i].id && link_list[i].id.substr(0, 10) == 'resolveip_' && link_list[i].innerHTML.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/))
			{
				// innerHTML is the ip address
				link_list[i].onclick = resolve_ip_click;
			}
		}
	}
}

/**
* Class to handle resolving IP addresses to host names with AJAX
*
* @param	string	The IP to resolve
* @param	string	The ID of the object that the resolved IP will replace
*/
function vB_AJAX_WolResolve(ip, objid)
{
	this.ip = ip;
	this.objid = objid;
	this.xml_sender = null;

	var me = this;

	/**
	* Resolves the IP using AJAX
	*/
	this.resolve = function()
	{
		this.xml_sender = new vB_AJAX_Handler(true);
		this.xml_sender.onreadystatechange(this.onreadystatechange);
		this.xml_sender.send('online.php?do=resolveip&ipaddress=' + PHP.urlencode(this.ip), 'do=resolveip&ajax=1&ipaddress=' + PHP.urlencode(this.ip));
	}

	/**
	* OnReadyStateChange callback. Uses a closure to keep state.
	* Remember to use me instead of this inside this function!
	*/
	this.onreadystatechange = function()
	{
		if (me.xml_sender.handler.readyState == 4 && me.xml_sender.handler.status == 200)
		{
			if (me.xml_sender.handler.responseXML)
			{
				var obj = fetch_object(me.objid);
				obj.parentNode.insertBefore(document.createTextNode(me.xml_sender.fetch_data(fetch_tags(me.xml_sender.handler.responseXML, 'ipaddress')[0])), obj);

				// might need to display the IP still instead of removing it... we'll wait and see.
				obj.parentNode.removeChild(obj);
			}

			if (is_ie)
			{
				me.xml_sender.handler.abort();
			}
		}
	}
}

/**
* Handles click events on resolve IP links
*/
function resolve_ip_click(e)
{
	var resolver = new vB_AJAX_WolResolve(this.innerHTML, this.id);
	resolver.resolve();
	return false;
}

/*======================================================================*\
|| ####################################################################
|| # Downloaded: 17:39, Fri Mar 2nd 2007
|| # CVS: $RCSfile$ - $Revision: 14538 $
|| ####################################################################
\*======================================================================*//*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.6.5
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2007 Jelsoft Enterprises Ltd. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/


// #############################################################################
// vB_Attachment
// #############################################################################

/**
* Class to deal with attachments
*
* @param	string	ID of the HTML element to contain the list of attachments
* @param	string	ID of the editor object
*/
function vB_Attachment(listobjid, editorid)
{
	this.attachments = new Array();
	this.menu_contents = new Array();
	this.windows = new Array();

	this.listobjid = listobjid;

	if (editorid == '')
	{
		for (var editorid in vB_Editor)
		{
			if (typeof vB_Editor[editorid] != 'function')
			{
				this.editorid = editorid;
				break;
			}
		}
	}
	else
	{
		this.editorid = (editorid ? editorid : null);
	}
};

// =============================================================================
// vB_Attachment methods

/**
* Does the editor popup exist in a built state?
*
* @return	boolean
*/
vB_Attachment.prototype.popup_exists = function()
{
	if (
		this.editorid &&
		((typeof vB_Editor[this.editorid].popups['attach'] != 'undefined' && vB_Editor[this.editorid].popups['attach'] != null)
		||
		(!vB_Editor[this.editorid].popupmode && typeof vB_Editor[this.editorid].buttons['attach'] != 'undefined' && vB_Editor[this.editorid].buttons['attach'] != null))
	)
	{
		return true;
	}
	else
	{
		return false;
	}
};

/**
* Add a new attachment
*
* @param	integer	Attachment ID
* @param	string	File name
* @param	string	File size
* @param	string	Path to item's image (images/attach/jpg.gif etc.)
*/
vB_Attachment.prototype.add = function(id, filename, filesize, imgpath)
{
	this.attachments[id] = new Array();
	this.attachments[id] = {
		'filename' : filename,
		'filesize' : filesize,
		'imgpath'  : imgpath
	};

	this.update_list();
};

/**
* Remove an attachment
*
* @param	integer	Attachment ID
*/
vB_Attachment.prototype.remove = function(id)
{
	if (typeof this.attachments[id] != 'undefined')
	{
		this.attachments[id] = null;

		this.update_list();
	}
};

/**
* Do we have any attachments?
*
* @return	boolean
*/
vB_Attachment.prototype.has_attachments = function()
{
	for (var id in this.attachments)
	{
if (typeof this.attachments[id] == 'function') continue;
		if (this.attachments[id] != null)
		{
			return true;
		}
	}
	return false;
};

/**
* Reset the attachments array
*/
vB_Attachment.prototype.reset = function()
{
	this.attachments = new Array();

	this.update_list();
};

/**
* Build Attachments List
*
* @param	string	ID of the HTML element to contain the list of attachments
*/
vB_Attachment.prototype.build_list = function(listobjid)
{
	var listobj = fetch_object(listobjid);

	if (listobjid != null)
	{
		while (listobj.hasChildNodes())
		{
			listobj.removeChild(listobj.firstChild);
		}

		for (var id in this.attachments)
		{
if (typeof this.attachments[id] == 'function') continue;
			var div = document.createElement('div');
			// try to use the template if it's been submitted to Javascript
			if (typeof newpost_attachmentbit != 'undefined')
			{
				div.innerHTML = construct_phrase(newpost_attachmentbit,
					this.attachments[id]['imgpath'],
					SESSIONURL,
					id,
					Math.ceil((new Date().getTime()) / 1000),
					this.attachments[id]['filename'],
					this.attachments[id]['filesize']
				);
			}
			else
			{
				div.innerHTML =
					'<div style="margin:2px"><img src="' + this.attachments[id]['imgpath'] + '" alt="" class="inlineimg" /> ' +
					'<a href="attachment.php?' + SESSIONURL + 'attachmentid=' + id + '&stc=1&d=' + Math.ceil((new Date().getTime()) / 1000) + '" target="_blank" />' + this.attachments[id]['filename'] + '</a> ' +
					'(' + this.attachments[id]['filesize'] + ')</div>';
			}
			listobj.appendChild(div);
		}
	}
};

/**
* Update the places we show a list of attachments
*/
vB_Attachment.prototype.update_list = function()
{
	this.build_list(this.listobjid);

	if (this.popup_exists())
	{
		vB_Editor[this.editorid].build_attachments_popup(
			vB_Editor[this.editorid].popupmode ? vB_Editor[this.editorid].popups['attach'] : vB_Editor[this.editorid].buttons['attach'],
			vB_Editor[this.editorid].buttons['attach']
		);
	}
};

/**
* Opens the attachment manager window
*
* @param	string	URL
* @param	integer	Width
* @param	integer	Height
* @param	string	Hash
*
* @return	window
*/
vB_Attachment.prototype.open_window = function(url, width, height, hash)
{
	if (typeof(this.windows[hash]) != 'undefined' && this.windows[hash].closed == false)
	{
		this.windows[hash].focus();
	}
	else
	{
		this.windows[hash] = openWindow(url, width, height, 'Attach' + hash);
	}

	return this.windows[hash];
};

/*======================================================================*\
|| ####################################################################
|| # Downloaded: 17:39, Fri Mar 2nd 2007
|| # CVS: $RCSfile$ - $Revision: 13452 $
|| ####################################################################
\*======================================================================*//*
 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
 * Digest Algorithm, as defined in RFC 1321.
 * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for more info.

 * vBulletin Usage: md5hash(input,output)
 * Recommend: input = password input field; output = hidden field

 */

/*
 * Configurable variables. You may need to tweak these to be compatible with
 * the server-side, but the defaults work in most cases.
 */
var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */

/*
 * These are the functions you'll usually want to call
 * They take string arguments and return either hex or base-64 encoded strings
 */
function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }

/*
 * Calculate the MD5 of an array of little-endian words, and a bit length
 */
function core_md5(x, len)
{
  /* append padding */
  x[len >> 5] |= 0x80 << ((len) % 32);
  x[(((len + 64) >>> 9) << 4) + 14] = len;

  var a =  1732584193;
  var b = -271733879;
  var c = -1732584194;
  var d =  271733878;

  for(var i = 0; i < x.length; i += 16)
  {
    var olda = a;
    var oldb = b;
    var oldc = c;
    var oldd = d;

    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);

    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);

    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);

    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);

    a = safe_add(a, olda);
    b = safe_add(b, oldb);
    c = safe_add(c, oldc);
    d = safe_add(d, oldd);
  }
  return Array(a, b, c, d);

}

/*
 * These functions implement the four basic operations the algorithm uses.
 */
function md5_cmn(q, a, b, x, s, t)
{
  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}

/*
 * Calculate the HMAC-MD5, of a key and some data
 */
function core_hmac_md5(key, data)
{
  var bkey = str2binl(key);
  if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);

  var ipad = Array(16), opad = Array(16);
  for(var i = 0; i < 16; i++)
  {
    ipad[i] = bkey[i] ^ 0x36363636;
    opad[i] = bkey[i] ^ 0x5C5C5C5C;
  }

  var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
  return core_md5(opad.concat(hash), 512 + 128);
}

/*
 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
 * to work around bugs in some JS interpreters.
 */
function safe_add(x, y)
{
  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  return (msw << 16) | (lsw & 0xFFFF);
}

/*
 * Bitwise rotate a 32-bit number to the left.
 */
function bit_rol(num, cnt)
{
  return (num << cnt) | (num >>> (32 - cnt));
}

/*
 * Convert a string to an array of little-endian words
 * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
 */
function str2binl(str)
{
  var bin = new Array();
  var mask = (1 << chrsz) - 1;
  for(var i = 0; i < str.length * chrsz; i += chrsz)
    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
  return bin;
}

/*
 * Convert an array of little-endian words to a string
 */
function binl2str(bin)
{
  var str = "";
  var mask = (1 << chrsz) - 1;
  for(var i = 0; i < bin.length * 32; i += chrsz)
    str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
  return str;
}

/*
 * Convert an array of little-endian words to a hex string.
 */
function binl2hex(binarray)
{
  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
  var str = "";
  for(var i = 0; i < binarray.length * 4; i++)
  {
    str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
           hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
  }
  return str;
}

/*
 * Convert an array of little-endian words to a base-64 string
 */
function binl2b64(binarray)
{
  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  var str = "";
  for(var i = 0; i < binarray.length * 4; i += 3)
  {
    var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
                | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
                |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
    for(var j = 0; j < 4; j++)
    {
      if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
      else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
    }
  }
  return str;
}

function str_to_ent(str)
{
	var result = '';
	var i;

	for (i = 0; i < str.length; i++)
	{
		var c = str.charCodeAt(i);
		var tmp = '';

		if (c > 255)
		{

			while (c >= 1)
			{
				tmp = "0123456789" . charAt(c % 10) + tmp;
				c = c / 10;
			}

			if (tmp == '')
			{
				tmp = "0";
			}
			tmp = "#" + tmp;
			tmp = "&" + tmp;
			tmp = tmp + ";";

			result += tmp;
		}
		else
		{
			result += str.charAt(i);
		}
	}
	return result;
}

function trim(s)
{
	while (s.substring(0, 1) == ' ')
	{
		s = s.substring(1, s.length);
	}
	while (s.substring(s.length-1, s.length) == ' ')
	{
		s = s.substring(0, s.length-1);
	}
	return s;
}

function md5hash(input, output_html, output_utf, skip_empty)
{

	if (navigator.userAgent.indexOf("Mozilla/") == 0 && parseInt(navigator.appVersion) >= 4)
	{
		var md5string = hex_md5(str_to_ent(trim(input.value)));
		output_html.value = md5string;
		if (output_utf)
		{
			md5string = hex_md5(trim(input.value));
			output_utf.value = md5string;
		}
		if (!skip_empty)
		{
			// implemented like this to make sure un-updated templates behave as before
			input.value = '';
		}
	}

	return true;
}/*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.6.5
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2007 Jelsoft Enterprises Ltd. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

// #############################################################################
// vB_Text_Editor

/**
* vBulletin Editor Class
*
* Activates any HTML controls for an editor
*
* @param	string	Unique key for this editor
* @param	boolean	Initialise to WYSIWYG mode?
* @param	string	Forumid / Calendar etc.
* @param	boolean	Parse smilies?
* @param	string	(Optional) Initial text for the editor
*/
function vB_Text_Editor(editorid, mode, parsetype, parsesmilies, initial_text)
{
	/**
	* Miscellaneous variables
	*
	* @var	string	Unique Editor ID
	* @var	boolean	WYSIWYG mode
	* @var	boolean	Have we initialized the editor?
	* @var	mixed	Passed parsetype (corresponds to bbcodeparse forumid)
	* @var	boolean	Passed parsesmilies option
	* @var	boolean	Can we use vBmenu popups?
	* @var	object	The element containing controls
	* @var	object	The textarea object containing the initial text
	* @var	array	Array containing all button objects
	* @var	array	Array containing all popup objects
	* @var	object	Current prompt() emulation popup
	* @var	string	State of the font context control
	* @var	string	State of the size context control
	* @var	string	State of the color context control
	* @var	string	String to contain the fake 'clipboard'
	* @var	boolean	Is the editor 'disabled'? (quick reply use)
	* @var	vB_History	History manager for undo/redo systems
	* @var  integer Is the editor mode trying to be changed?
	*/
	this.editorid = editorid;
	this.wysiwyg_mode = parseInt(mode, 10) ? 1 : 0;
	this.initialized = false;
	this.parsetype = (typeof parsetype == 'undefined' ? 'nonforum' : parsetype);
	this.parsesmilies = (typeof parsesmilies == 'undefined' ? 1 : parsesmilies);
	this.popupmode = (typeof vBmenu == 'undefined' ? false : true);
	this.controlbar = fetch_object(this.editorid + '_controls');
	this.textobj = fetch_object(this.editorid + '_textarea');
	this.buttons = new Array();
	this.popups = new Array();
	this.prompt_popup = null;
	this.fontstate = null;
	this.sizestate = null;
	this.colorstate = null;
	this.clipboard = '';
	this.disabled = false;
	this.history = new vB_History();
	this.influx = 0;

	// =============================================================================
	// vB_Text_Editor methods

	/**
	* Editor initialization wrapper
	*/
	this.init = function()
	{
		if (this.initialized)
		{
			return;
		}

		this.textobj.disabled = false;

		if (this.tempiframe)
		{
			this.tempiframe.parentNode.removeChild(this.tempiframe);
		}

		this.set_editor_contents(initial_text);

		this.set_editor_functions();

		this.init_controls();

		this.init_smilies(fetch_object(this.editorid + '_smiliebox'));

		if (typeof smilie_window != 'undefined' && !smilie_window.closed)
		{
			this.init_smilies(smilie_window.document.getElementById('smilietable'));
		}

		this.initialized = true;
	};

	/**
	* Check if we need to refocus the editor window
	*/
	this.check_focus = function()
	{
		if (!this.editwin.hasfocus)
		{
			this.editwin.focus();
			if (is_opera)
			{
				// see http://www.vbulletin.com/forum/bugs35.php?do=view&bugid=687
				this.editwin.focus();
			}
		}
	}

	/**
	* Init button controls for the editor
	*/
	this.init_controls = function()
	{
		var controls = new Array();

		if (this.controlbar == null)
		{
			return;
		}

		var buttons = fetch_tags(this.controlbar, 'div');
		for (var i = 0; i < buttons.length; i++)
		{
			if (buttons[i].className == 'imagebutton' && buttons[i].id)
			{
				controls[controls.length] = buttons[i].id;
			}
		}
		for (var i = 0; i < controls.length; i++)
		{
			var control = fetch_object(controls[i]);

			if (control.id.indexOf(this.editorid + '_cmd_') != -1)
			{
				this.init_command_button(control);
			}
			else if (control.id.indexOf(this.editorid + '_popup_') != -1)
			{
				this.init_popup_button(control);
			}
		}

		set_unselectable(this.controlbar);
	};

	/**
	* Init Smilies
	*/
	this.init_smilies = function(smilie_container)
	{
		if (smilie_container != null)
		{
			var smilies = fetch_tags(smilie_container, 'img');
			for (var i = 0; i < smilies.length; i++)
			{
				if (smilies[i].id && smilies[i].id.indexOf('_smilie_') != false)
				{
					smilies[i].style.cursor = pointer_cursor;
					smilies[i].editorid = this.editorid;
					smilies[i].onclick = vB_Text_Editor_Events.prototype.smilie_onclick;
					smilies[i].unselectable = 'on';
				}
			}
		}
	}

	/**
	* Init command button (b, i, u etc.)
	*
	* @param	object	Current HTML button node
	*/
	this.init_command_button = function(obj)
	{
		obj.cmd = obj.id.substr(obj.id.indexOf('_cmd_') + 5);
		obj.editorid = this.editorid;
		this.buttons[obj.cmd] = obj;

		if (obj.cmd == 'switchmode')
		{
			if (AJAX_Compatible)
			{
				obj.state = this.wysiwyg_mode ? true : false;
				this.set_control_style(obj, 'button', this.wysiwyg_mode ? 'selected' : 'normal');
			}
			else
			{
				obj.parentNode.removeChild(obj);
			}
		}
		else
		{
			obj.state = false;
			obj.mode = 'normal';
		}

		// event handlers
		obj.onclick = obj.onmousedown = obj.onmouseover = obj.onmouseout = vB_Text_Editor_Events.prototype.command_button_onmouseevent;
	}

	/**
	* Init popup button (forecolor, fontname etc.)
	*
	* @param	object	Current HTML button node
	*/
	this.init_popup_button = function(obj)
	{
		obj.cmd = obj.id.substr(obj.id.indexOf('_popup_') + 7);

		if (this.popupmode)
		{
			// register popup menu control
			vBmenu.register(obj.id, true);
			vBmenu.menus[obj.id].open_steps = 5;

			obj.editorid = this.editorid;
			obj.state = false;
			this.buttons[obj.cmd] = obj;

			if (obj.cmd == 'fontname')
			{
				this.fontout = fetch_object(this.editorid + '_font_out');
				this.fontout.innerHTML = obj.title;
				this.fontoptions = {'' : this.fontout};

				for (var option in fontoptions)
				{
if (typeof fontoptions[option] == 'function') continue;
					var div = document.createElement('div');
					div.id = this.editorid + '_fontoption_' + fontoptions[option];
					div.style.width = this.fontout.style.width;
					div.style.display = 'none';
					div.innerHTML = fontoptions[option];
					this.fontoptions[fontoptions[option]] = this.fontout.parentNode.appendChild(div);
				}
			}
			else if (obj.cmd == 'fontsize')
			{
				this.sizeout = fetch_object(this.editorid + '_size_out');
				this.sizeout.innerHTML = obj.title;
				this.sizeoptions = {'' : this.sizeout};

				for (var option in sizeoptions)
				{
if (typeof sizeoptions[option] == 'function') continue;
					var div = document.createElement('div');
					div.id = this.editorid + '_sizeoption_' + sizeoptions[option];
					div.style.width = this.sizeout.style.width;
					div.style.display = 'none';
					div.innerHTML = sizeoptions[option];
					this.sizeoptions[sizeoptions[option]] = this.sizeout.parentNode.appendChild(div);
				}
			}

			// extend onmouseover
			obj._onmouseover = obj.onmouseover;
			// extend onclick
			obj._onclick = obj.onclick;

			// event handlers
			obj.onmouseover = obj.onmouseout = obj.onclick = vB_Text_Editor_Events.prototype.popup_button_onmouseevent;

			// extend menu show
			vBmenu.menus[obj.id]._show = vBmenu.menus[obj.id].show;
			vBmenu.menus[obj.id].show = vB_Text_Editor_Events.prototype.popup_button_show;
		}
		else
		{
			this.build_select(obj);
		}
	}

	/**
	* Replace the popup controls with <select> menus for rubbish browsers
	*
	* @param	object	The popup control element
	*/
	this.build_select = function(obj)
	{
		var sel = document.createElement('select');
		sel.id = this.editorid + '_select_' + obj.cmd;
		sel.editorid = this.editorid;
		sel.cmd = obj.cmd;

		var opt = document.createElement('option');
		opt.value = '';
		opt.text = obj.title;
		sel.add(opt, is_ie ? sel.options.length : null);

		var opt = document.createElement('option');
		opt.value = '';
		opt.text = ' ';
		sel.add(opt, is_ie ? sel.options.length : null);

		switch (obj.cmd)
		{
			case 'fontname':
			{
				for (var i = 0; i < fontoptions.length; i++)
				{
					var opt = document.createElement('option');
					opt.value = fontoptions[i];
					opt.text = (fontoptions[i].length > 10 ? (fontoptions[i].substr(0, 10) + '...') : fontoptions[i]);
					sel.add(opt, is_ie ? sel.options.length : null);
				}

				sel.onchange = vB_Text_Editor_Events.prototype.formatting_select_onchange;
				break;
			}

			case 'fontsize':
			{
				for (var i = 0; i < sizeoptions.length; i++)
				{
					var opt = document.createElement('option');
					opt.value = sizeoptions[i];
					opt.text = sizeoptions[i];
					sel.add(opt, is_ie ? sel.options.length : null);
				}

				sel.onchange = vB_Text_Editor_Events.prototype.formatting_select_onchange;
				break;
			}

			case 'forecolor':
			{
				for (var i in coloroptions)
				{
if (typeof coloroptions[i] == 'function') continue;
					var opt = document.createElement('option');
					opt.value = coloroptions[i];
					opt.text = PHP.trim((coloroptions[i].length > 5 ? (coloroptions[i].substr(0, 5) + '...') : coloroptions[i]).replace(new RegExp('([A-Z])', 'g'), ' $1'));
					opt.style.backgroundColor = i;
					sel.add(opt, is_ie ? sel.options.length : null);
				}

				sel.onchange = vB_Text_Editor_Events.prototype.formatting_select_onchange;
				break;
			}

			case 'smilie':
			{
				for (var cat in smilieoptions)
				{
if (typeof smilieoptions[cat] == 'function') continue;
					for (var smilieid in smilieoptions[cat])
					{
						if (smilieid != 'more')
						{
							var opt = document.createElement('option');
							opt.value = smilieoptions[cat][smilieid][1];
							opt.text = smilieoptions[cat][smilieid][1];
							opt.smilieid = smilieid;
							opt.smiliepath = smilieoptions[cat][smilieid][0];
							opt.smilietitle = smilieoptions[cat][smilieid][2];
							sel.add(opt, is_ie ? sel.options.length : null);
						}
					}
				}

				sel.onchange = vB_Text_Editor_Events.prototype.smilieselect_onchange;
				break;
			}

			case 'attach':
			{
				sel.onmouseover = vB_Text_Editor_Events.prototype.attachselect_onmouseover;
				sel.onchange = vB_Text_Editor_Events.prototype.attachselect_onchange;
				break;
			}
		}

		while (obj.hasChildNodes())
		{
			obj.removeChild(obj.firstChild);
		}

		this.buttons[obj.cmd] = obj.appendChild(sel);
	}

	/**
	* Init menu controls for the editor
	*
	* @param	object	HTML menu node
	*/
	this.init_popup_menu = function(obj)
	{
		if (this.disabled)
		{
			return;
		}

		switch (obj.cmd)
		{
			case 'fontname':
			{
				var menu = this.init_menu_container('fontname', '200px', '250px', 'auto');
				this.build_fontname_popup(obj, menu);
				break;
			}
			case 'fontsize':
			{
				var menu = this.init_menu_container('fontsize', 'auto', 'auto', 'visible');
				this.build_fontsize_popup(obj, menu);
				break;
			}
			case 'forecolor':
			{
				var menu = this.init_menu_container('forecolor', 'auto', 'auto', 'visible');
				this.build_forecolor_popup(obj, menu);
				break;
			}
			case 'smilie':
			{
				var menu = this.init_menu_container('smilie', '175px', '250px', 'auto');
				this.build_smilie_popup(obj, menu);
				break;
			}
			case 'attach':
			{
				if (typeof vB_Attachments != 'undefined' && vB_Attachments.has_attachments())
				{
					var menu = this.init_menu_container('attach', 'auto', 'auto', 'visible');
					this.build_attachments_popup(menu, obj);
				}
				else
				{
					return fetch_object('manage_attachments_button').onclick();
				}
			}
		}

		this.popups[obj.cmd] = this.controlbar.appendChild(menu);

		set_unselectable(menu);
	};

	/**
	* Init Menu Container DIV
	*
	* @param	string	Command string (forecolor, fontname etc.)
	* @param	string	CSS width for the menu
	* @param	string	CSS height for the menu
	* @param	string	CSS overflow for the menu
	*
	* @return	object	Newly created menu element
	*/
	this.init_menu_container = function(cmd, width, height, overflow)
	{
		var menu = document.createElement('div');

		menu.id = this.editorid + '_popup_' + cmd + '_menu';
		menu.className = 'vbmenu_popup';
		menu.style.display = 'none';
		menu.style.cursor = 'default';
		menu.style.padding = '3px';
		menu.style.width = width;
		menu.style.height = height;
		menu.style.overflow = overflow;

		return menu;
	}

	/**
	* Build Font Name Popup Contents
	*
	* @param	object	The control object for the menu
	* @param	object	The menu container object
	*/
	this.build_fontname_popup = function(obj, menu)
	{
		for (var n in fontoptions)
		{
if (typeof fontoptions[n] == 'function') continue;
			var option = document.createElement('div');
			option.innerHTML = '<font face="' + fontoptions[n] + '">' + fontoptions[n] + '</font>';
			option.className = 'ofont';
			option.style.textAlign = 'left';
			option.title = fontoptions[n];
			option.cmd = obj.cmd;
			option.controlkey = obj.id;
			option.editorid = this.editorid;
			option.onmouseover = option.onmouseout = option.onmouseup = option.onmousedown = vB_Text_Editor_Events.prototype.menuoption_onmouseevent;
			option.onclick = vB_Text_Editor_Events.prototype.formatting_option_onclick;
			menu.appendChild(option);
		}
	}

	/**
	* Build Font Size Popup Contents
	*
	* @param	object	The control object for the menu
	* @param	object	The menu container object
	*/
	this.build_fontsize_popup = function(obj, menu)
	{
		for (var n in sizeoptions)
		{
if (typeof sizeoptions[n] == 'function') continue;
			var option = document.createElement('div');
			option.innerHTML = '<font size="' + sizeoptions[n] + '">' + sizeoptions[n] + '</font>';
			option.className = 'osize';
			option.style.textAlign = 'center';
			option.title = sizeoptions[n];
			option.cmd = obj.cmd;
			option.controlkey = obj.id;
			option.editorid = this.editorid;
			option.onmouseover = option.onmouseout = option.onmouseup = option.onmousedown = vB_Text_Editor_Events.prototype.menuoption_onmouseevent;
			option.onclick = vB_Text_Editor_Events.prototype.formatting_option_onclick;
			menu.appendChild(option);
		}
	}

	/**
	* Build ForeColor Popup Contents
	*
	* @param	object	The control object for the menu
	* @param	object	The menu container object
	*/
	this.build_forecolor_popup = function(obj, menu)
	{
		var colorout = fetch_object(this.editorid + '_color_out');
		colorout.editorid = this.editorid;
		colorout.onclick = vB_Text_Editor_Events.prototype.colorout_onclick;

		var table = document.createElement('table');
		table.cellPadding = 0;
		table.cellSpacing = 0;
		table.border = 0;

		var i = 0;
		for (var hex in coloroptions)
		{
if (typeof coloroptions[hex] == 'function') continue;
			if (i % 8 == 0)
			{
				var tr = table.insertRow(-1);
			}
			i++;

			var div = document.createElement('div');
			div.style.backgroundColor = coloroptions[hex];

			var option = tr.insertCell(-1);
			option.style.textAlign = 'center';
			option.className = 'ocolor';
			option.appendChild(div);
			option.cmd = obj.cmd;
			option.editorid = this.editorid;
			option.controlkey = obj.id;
			option.colorname = coloroptions[hex];
			option.id = this.editorid + '_color_' + coloroptions[hex];
			option.onmouseover = option.onmouseout = option.onmouseup = option.onmousedown = vB_Text_Editor_Events.prototype.menuoption_onmouseevent;
			option.onclick = vB_Text_Editor_Events.prototype.coloroption_onclick;
		}

		menu.appendChild(table);
	}

	/**
	* Build Smilie Popup Contents
	*
	* @param	object	The control object for the menu
	* @param	object	The menu container object
	*/
	this.build_smilie_popup = function(obj, menu)
	{
		for (var cat in smilieoptions)
		{
if (typeof smilieoptions[cat] == 'function') continue;
			var category = document.createElement('div');
			category.className = 'thead';
			category.innerHTML = cat;
			menu.appendChild(category);

			for (var smilieid in smilieoptions[cat])
			{
				if (smilieid == 'more')
				{
					var option = document.createElement('div');
					option.className = 'thead';
					option.innerHTML = smilieoptions[cat][smilieid];
					option.style.cursor = pointer_cursor;
					option.editorid = this.editorid;
					option.controlkey = obj.id;
					option.onclick = vB_Text_Editor_Events.prototype.smiliemore_onclick;
				}
				else
				{
					var option = document.createElement('div');
					option.editorid = this.editorid;
					option.controlkey = obj.id;
					option.smilieid = smilieid;
					option.smilietext = smilieoptions[cat][smilieid][1];
					option.smilietitle = smilieoptions[cat][smilieid][2];

					option.className = 'osmilie';
					option.innerHTML = '<img src="' + smilieoptions[cat][smilieid][0] + '" alt="' + smilieoptions[cat][smilieid][2] + '" /> ' + smilieoptions[cat][smilieid][2];

					option.onmouseover = option.onmouseout = option.onmousedown = option.onmouseup = vB_Text_Editor_Events.prototype.menuoption_onmouseevent;

					option.onclick = vB_Text_Editor_Events.prototype.smilieoption_onclick;
				}

				menu.appendChild(option);
			}
		}
	}

	/**
	* Build Attachments Popup
	*
	* @param	object	The control object for the menu
	* @param	object	The menu container object
	*/
	this.build_attachments_popup = function(menu, obj)
	{
		if (this.popupmode)
		{
			while (menu.hasChildNodes())
			{
				menu.removeChild(menu.firstChild);
			}

			var div = document.createElement('div');
			div.editorid = this.editorid;
			div.controlkey = obj.id;
			div.className = 'thead';
			div.style.cursor = pointer_cursor;
			div.innerHTML = fetch_object('manage_attachments_button').value;
			div.title = fetch_object('manage_attachments_button').title;
			div.onclick = vB_Text_Editor_Events.prototype.attachmanage_onclick;

			menu.appendChild(div);

			var attach_count = 0;
			for (var id in vB_Attachments.attachments)
			{
				var div = document.createElement('div');
				div.editorid = this.editorid;
				div.controlkey = obj.id;
				div.className = 'osmilie';
				div.attachmentid = id;
				div.innerHTML = '<img src="' + vB_Attachments.attachments[id]['imgpath'] + '" alt="" /> ' + vB_Attachments.attachments[id]['filename'];
				div.onmouseover = div.onmouseout = div.onmousedown = div.onmouseup = vB_Text_Editor_Events.prototype.menuoption_onmouseevent;
				div.onclick = vB_Text_Editor_Events.prototype.attachoption_onclick;

				menu.appendChild(div);
				attach_count++;
			}
			if (attach_count > 1)
			{
				var div = document.createElement('div');
				div.editorid = this.editorid
				div.controlkey = obj.id;
				div.className = 'osmilie';
				div.style.fontWeight = 'bold';
				div.style.paddingLeft = '25px';
				div.innerHTML = vbphrase['insert_all'];
				div.onmouseover = div.onmouseout = div.onmousedown = div.onmouseup = vB_Text_Editor_Events.prototype.menuoption_onmouseevent;
				div.onclick = vB_Text_Editor_Events.prototype.attachinsertall_onclick;

				menu.appendChild(div);
			}
		}
		else
		{
			while (menu.options.length > 2)
			{
				menu.remove(menu.options.length - 1);
			}

			for (var id in vB_Attachments.attachments)
			{
				var opt = document.createElement('option');
				opt.value = id;
				opt.text = vB_Attachments.attachments[id]['filename'];
				menu.add(opt, is_ie ? menu.options.length : null);
			}
		}

		set_unselectable(menu);
	}

	/**
	* Menu Context
	*
	* @param	object	The menu container object
	* @param	string	The state of the control
	*/
	this.menu_context = function(obj, state)
	{
		if (this.disabled)
		{
			return;
		}

		switch (obj.state)
		{
			case true: // selected menu is open
			{
				this.set_control_style(obj, 'button', 'down');
				break;
			}

			default:
			{
				switch (state)
				{
					case 'mouseout':
					{
						this.set_control_style(obj, 'button', 'normal');
						break;
					}
					case 'mousedown':
					{
						this.set_control_style(obj, 'popup', 'down');
						break;
					}
					case 'mouseup':
					case 'mouseover':
					{
						this.set_control_style(obj, 'button', 'hover');
						break;
					}
				}
			}
		}
	};

	/**
	* Button Context
	*
	* @param	object	The button object
	* @param	string	Incoming event type
	* @param	string	Control type - 'button' or 'menu'
	*/
	this.button_context = function(obj, state, controltype)
	{
		if (this.disabled)
		{
			return;
		}

		if (typeof controltype == 'undefined')
		{
			controltype = 'button';
		}

		switch (obj.state)
		{
			case true: // selected button
			{
				switch (state)
				{
					case 'mouseover':
					case 'mousedown':
					case 'mouseup':
					{
						this.set_control_style(obj, controltype, 'down');
						break;
					}
					case 'mouseout':
					{
						this.set_control_style(obj, 'button', 'selected');
						break;
					}
				}
				break;
			}

			default: // not selected
			{
				switch (state)
				{
					case 'mouseover':
					case 'mouseup':
					{
						this.set_control_style(obj, controltype, 'hover');
						break;
					}
					case 'mousedown':
					{
						this.set_control_style(obj, controltype, 'down');
						break;
					}
					case 'mouseout':
					{
						this.set_control_style(obj, controltype, 'normal');
						break;
					}
				}
				break;
			}
		}
	};

	/**
	* Set Control Style
	*
	* @param	object	The object to be styled
	* @param	string	Control type - 'button' or 'menu'
	* @param	string	The mode to use, corresponding to the istyles array
	*/
	this.set_control_style = function(obj, controltype, mode)
	{
		if (obj.mode != mode)
		{
			obj.mode = mode;

			// construct the name of the appropriate array key from the istyles array
			istyle = 'pi_' + controltype + '_' + obj.mode;

			// set element background, color, padding and border
			if (typeof istyles != 'undefined' && typeof istyles[istyle] != 'undefined')
			{
				obj.style.background = istyles[istyle][0];
				obj.style.color = istyles[istyle][1];
				if (controltype != 'menu')
				{
					obj.style.padding = istyles[istyle][2];
				}
				obj.style.border = istyles[istyle][3];

				var tds = fetch_tags(obj, 'td');
				for (var i = 0; i < tds.length; i++)
				{
					switch (tds[i].className)
					{
						// set the right-border for popup_feedback class elements
						case 'popup_feedback':
						{
							tds[i].style.borderRight = (mode == 'normal' ? istyles['pi_menu_normal'][3] : istyles[istyle][3]);
						}
						break;

						// set the border colour for popup_pickbutton class elements
						case 'popup_pickbutton':
						{
							tds[i].style.borderColor = (mode == 'normal' ? istyles['pi_menu_normal'][0] : istyles[istyle][0]);
						}
						break;

						// set the left-padding and left-border for alt_pickbutton elements
						case 'alt_pickbutton':
						{
							if (obj.state)
							{
								tds[i].style.paddingLeft = istyles['pi_button_normal'][2];
								tds[i].style.borderLeft = istyles['pi_button_normal'][3];
							}
							else
							{
								tds[i].style.paddingLeft = istyles[istyle][2];
								tds[i].style.borderLeft = istyles[istyle][3];
							}
						}
					}
				}
			}
		}
	};

	/**
	* Format text
	*
	* @param	event	Event object
	* @param	string	Formatting command
	* @param	string	Optional argument to the formatting command
	*
	* @return	boolean
	*/
	this.format = function(e, cmd, arg)
	{
		e = do_an_e(e);

		if (this.disabled)
		{
			return false;
		}

		if (cmd != 'redo')
		{
			this.history.add_snapshot(this.get_editor_contents());
		}

		if (cmd == 'switchmode')
		{
			switch_editor_mode(this.editorid);
			return;
		}
		else if (cmd.substr(0, 6) == 'resize')
		{
			this.resize_editor(parseInt(cmd.substr(9), 10) * (parseInt(cmd.substr(7, 1), 10) == '1' ? 1 : -1));
			return;
		}

		this.check_focus();

		if (cmd.substr(0, 4) == 'wrap')
		{
			var ret = this.wrap_tags(cmd.substr(6), (cmd.substr(4, 1) == '1' ? true : false));
		}
		else if (this[cmd])
		{
			var ret = this[cmd](e);
		}
		else
		{
			try
			{
				var ret = this.apply_format(cmd, false, (typeof arg == 'undefined' ? true : arg));
			}
			catch(e)
			{
				this.handle_error(cmd, e);
				var ret = false;
			}
		}

		if (cmd != 'undo')
		{
			this.history.add_snapshot(this.get_editor_contents());
		}

		this.set_context(cmd);

		this.check_focus();

		return ret;
	};

	/**
	* Insert Image
	*
	* @param	event	Event object
	* @param	string	(Optional) Image URL
	*
	* @return	boolean
	*/
	this.insertimage = function(e, img)
	{
		if (typeof img == 'undefined')
		{
			img = this.show_prompt(vbphrase['enter_image_url'], 'http://');
		}
		if (img = this.verify_prompt(img))
		{
			return this.apply_format('insertimage', false, img);
		}
		else
		{
			return false;
		}
	};

	/**
	* Wrap Tags
	*
	* @param	string	Tag to wrap
	* @param	boolean	Use option?
	* @param	string	(Optional) selected text
	*
	* @return	boolean
	*/
	this.wrap_tags = function(tagname, useoption, selection)
	{
		tagname = tagname.toUpperCase();

		switch (tagname)
		{
			case 'CODE':
			case 'HTML':
			case 'PHP':
			{
				this.apply_format('removeformat');
			}
			break;
		}

		if (typeof selection == 'undefined')
		{
			selection = this.get_selection();
			if (selection === false)
			{
				selection = '';
			}
			else
			{
				selection = new String(selection);
			}
		}

		if (useoption === true)
		{
			var option = this.show_prompt(construct_phrase(vbphrase['enter_tag_option'], ('[' + tagname + ']')), '');
			if (option = this.verify_prompt(option))
			{
				var opentag = '[' + tagname + '="' + option + '"' + ']';
			}
			else
			{
				return false;
			}
		}
		else if (useoption !== false)
		{
			var opentag = '[' + tagname + '="' + useoption + '"' + ']';
		}
		else
		{
			var opentag = '[' + tagname + ']';
		}

		var closetag = '[/' + tagname + ']';
		var text = opentag + selection + closetag;

		this.insert_text(text, opentag.vBlength(), closetag.vBlength());

		return false;
	};

	/**
	* Check Spelling (uses ieSpell from www.iespell.com)
	*
	* Eventually we hope to integrate SpellBound (http://spellbound.sourceforge.net) for Gecko.
	*/
	this.spelling = function()
	{
		if (is_ie)
		{
			try
			{
				// attempt to instantiate ieSpell
				eval("new A" + "ctiv" + "eX" + "Ob" + "ject('ieSpell." + "ieSpellExt" + "ension').CheckD" + "ocumentNode(this.spellobj);");
			}
			catch(e)
			{
				// ask if user wants to download ieSpell
				if (e.number == -2146827859 && confirm(vbphrase['iespell_not_installed']))
				{
					// ooh they do...
					window.open('http://www.iespell.com/download.ph' + 'p');
				}
			}
		}
		else if (is_moz)
		{
			// attempt to instantiate SpellBound... when it supports this behaviour
		}
	};

	/**
	* Handle Error
	*
	* @param	string	Command name
	* @param	event	Event object
	*/
	this.handle_error = function(cmd, e)
	{
	};

	/**
	* Show JS Prompt and filter result
	*
	* @param	string	Text for the dialog
	* @param	string	Default value for the dialog
	*
	* @return	string
	*/
	this.show_prompt = function(dialogtxt, defaultval)
	{
		if (is_ie7)
		{
			var returnvalue = window.showModalDialog("clientscript/ieprompt.html", { value: defaultval, label: dialogtxt, dir: document.dir, title: document.title }, "dialogWidth:320px; dialogHeight:150px; dialogTop:" + (parseInt(window.screenTop) + parseInt(window.event.clientY) + parseInt(document.body.scrollTop) - 100) + "px; dialogLeft:" + (parseInt(window.screenLeft) + parseInt(window.event.clientX) + parseInt(document.body.scrollLeft) - 160) + "px; resizable: No;");
		}
		else
		{
			var returnvalue = prompt(dialogtxt, defaultval);
		}
		
		return PHP.trim(new String(returnvalue));
	};
	
	/**
	* Closes an open prompt (emulator) window opened by show_prompt() under IE7+
	*/
	this.close_prompt = function()
	{
		if (this.prompt_popup)
		{
			document.getElementById("vB_Editor_001").removeChild(this.prompt_popup);
			this.prompt_popup = null;
		}
		
		this.check_focus();
	}

	/**
	* Verify the return value of a javascript prompt
	*
	* @param	string	String to be checked
	*
	* @return	mixed	False on fail, string on success
	*/
	this.verify_prompt = function(str)
	{
		switch(str)
		{
			case 'http://':
			case 'null':
			case 'undefined':
			case 'false':
			case '':
			case null:
			case false:
				return false;

			default:
				return str;
		}
	};

	/**
	* Open Smilie Window
	*
	* @param	integer	Window width
	* @param	integer	Window height
	*/
	this.open_smilie_window = function(width, height)
	{
		smilie_window = openWindow('misc.php?' + SESSIONURL + 'do=getsmilies&editorid=' + this.editorid, width, height, 'smilie_window');

		window.onunload = vB_Text_Editor_Events.prototype.smiliewindow_onunload;
	}

	/**
	* Resize Editor
	*
	* @param	integer	Number of pixels by which to resize the editor
	*/
	this.resize_editor = function(change)
	{
		var newheight = parseInt(this.editbox.style.height, 10) + change;

		if (newheight >= 100)
		{
			this.editbox.style.height = newheight + 'px';

			// remember the setting for next time
			if (change % 99 != 0)
			{
				set_cookie('editor_height', newheight);
			}
		}
	};

	/**
	* Destroy Popup
	*/
	this.destroy_popup = function(popupname)
	{
		this.popups[popupname].parentNode.removeChild(this.popups[popupname]);
		this.popups[popupname] = null;
	}

	/**
	* Destroy Editor
	*/
	this.destroy = function()
	{
		// reset all buttons to default state
		for (var i in this.buttons)
		{
if (typeof this.buttons[i] == 'function') continue;
			this.set_control_style(this.buttons[i], 'button', 'normal');
		}

		// destroy popups
		for (var menu in this.popups)
		{
if (typeof this.popups[menu] == 'function') continue;
			this.destroy_popup(menu);
		}

		if (this.fontoptions)
		{
			for (var i in this.fontoptions)
			{
if (typeof this.fontoptions[i] == 'function') continue;
				if (i != '')
				{
					this.fontoptions[i].parentNode.removeChild(this.fontoptions[i]);
				}
			}
			this.fontoptions[''].style.display = '';
		}

		if (this.sizeoptions)
		{
			for (var i in this.sizeoptions)
			{
if (typeof this.sizeoptions[i] == 'function') continue;
				if (i != '')
				{
					this.sizeoptions[i].parentNode.removeChild(this.sizeoptions[i]);
				}
			}
			this.sizeoptions[''].style.display = '';
		}
	};

	/**
	* Collapse the current selection and place the cursor where the end of the
	* selection was.
	*/
	this.collapse_selection_end = function()
	{
		if (this.editdoc.selection)
		{
			var range = this.editdoc.selection.createRange();
			// fix for horribly confusing IE bug where it randomly makes text white for funsies
			// see 3.5 bug #279
			eval("range." + "move" + "('character', -1);");
			range.collapse(false);
			range.select();
		}
		else if (document.selection && document.selection.createRange)
		{
			var range = document.selection.createRange();
			range.collapse(false);
			range.select();
		}
		else if (typeof(this.editdoc.selectionStart) != 'undefined')
		{
			var opn = this.editdoc.selectionStart + 0;
			var sel_text = this.editdoc.value.substr(this.editdoc.selectionStart, this.editdoc.selectionEnd - this.editdoc.selectionStart);

			this.editdoc.selectionStart = this.editdoc.selectionStart + sel_text.vBlength();
		}
		else if (window.getSelection)
		{
			// don't think I can do anything for this
		}
	}

	// =============================================================================
	// WYSIWYG editor
	// =============================================================================
	if (this.wysiwyg_mode)
	{
		/**
		* Disables the editor
		*
		* @param	string	Initial text for the editor
		*/
		this.disable_editor = function(text)
		{
			if (!this.disabled)
			{
				this.disabled = true;

				var hider = fetch_object(this.editorid + '_hider');
				if (hider)
				{
					hider.parentNode.removeChild(hider);
				}

				var div = document.createElement('div');
				div.id = this.editorid + '_hider';
				div.className = 'wysiwyg';
				div.style.border = '2px inset';
				div.style.width = this.editbox.style.width;
				div.style.height = this.editbox.style.height;
				var childdiv = document.createElement('div');
				childdiv.style.margin = '8px';
				childdiv.innerHTML = text;
				div.appendChild(childdiv);
				this.editbox.parentNode.appendChild(div);

				// and hide the real editor
				this.editbox.style.width = '0px';
				this.editbox.style.height = '0px';
				this.editbox.style.border = 'none';
			}
		};

		/**
		* Enables the editor
		*
		* @param	string	Initial text for the editor
		*/
		this.enable_editor = function(text)
		{
			if (typeof text != 'undefined')
			{
				this.set_editor_contents(text);
			}

			var hider = fetch_object(this.editorid + '_hider');
			if (hider)
			{
				hider.parentNode.removeChild(hider);
			}

			this.disabled = false;
		};

		/**
		* Puts the text into the editor
		*
		* @param	string	Text to write
		*/
		this.write_editor_contents = function(text, doinit)
		{
			if (text == '')
			{
				if (is_ie)
				{
					text = '<p></p>';
				}
				else if (is_moz)
				{
					text = '<br />';
				}
			}
			if (this.editdoc && this.editdoc.initialized)
			{
				this.editdoc.body.innerHTML = text;
			}
			else
			{
				if (doinit) { this.editdoc.designMode = 'on'; }
				this.editdoc = this.editwin.document; // See: http://msdn.microsoft.com/workshop/author/dhtml/overview/XpSp2Compat.asp#caching
				this.editdoc.open('text/html', 'replace');
				this.editdoc.write(text);
				this.editdoc.close();
				if (doinit) { this.editdoc.body.contentEditable = true; }
				this.editdoc.body.spellcheck = true;

				this.editdoc.initialized = true;

				this.set_editor_style();
			}

			this.set_direction();
		}

		/**
		* Put the text into the editor
		*/
		this.set_editor_contents = function(initial_text)
		{
			if (fetch_object(this.editorid + '_iframe'))
			{
				this.editbox = fetch_object(this.editorid + '_iframe');
			}
			else
			{
				var iframe = document.createElement('iframe');
				if (is_ie && window.location.protocol == 'https:')
				{
					// workaround for IE throwing insecure page warnings
					iframe.src = 'clientscript/index.html';
				}
				this.editbox = this.textobj.parentNode.appendChild(iframe);
				this.editbox.id = this.editorid + '_iframe';
				this.editbox.tabIndex = 1;
			}

			if (!is_ie)
			{
				this.editbox.style.border = '2px inset';
			}
			this.editbox.style.width = this.textobj.style.width;
			this.editbox.style.height = this.textobj.style.height;
			this.textobj.style.display = 'none';

			this.editwin = this.editbox.contentWindow;
			this.editdoc = this.editwin.document;

			this.write_editor_contents((typeof initial_text == 'undefined' ?  this.textobj.value : initial_text), true);

			if (this.editdoc.dir == 'rtl')
			{
			//	this.editdoc.execCommand('justifyright', false, null);
			}
			this.spellobj = this.editdoc.body;

			this.editdoc.editorid = this.editorid;
			this.editwin.editorid = this.editorid;
		};

		/**
		* Sets the text direction for the editor
		*/
		this.set_direction = function()
		{
			this.editdoc.dir = this.textobj.dir;
		};

		/**
		* Set the CSS style of the editor
		*/
		this.set_editor_style = function()
		{
			if (document.styleSheets['vbulletin_css'])
			{
				this.editdoc.createStyleSheet().cssText = document.styleSheets['vbulletin_css'].cssText + ' p { margin: 0px; }';
				this.editdoc.body.className = 'wysiwyg';
			}
		};

		/**
		* Init Editor Functions
		*/
		this.set_editor_functions = function()
		{
			this.editdoc.onmouseup = vB_Text_Editor_Events.prototype.editdoc_onmouseup;
			this.editdoc.onkeyup = vB_Text_Editor_Events.prototype.editdoc_onkeyup;

			if (this.editdoc.attachEvent)
			{
				this.editdoc.body.attachEvent("onresizestart", vB_Text_Editor_Events.prototype.editdoc_onresizestart);
			}

			this.editwin.onfocus = vB_Text_Editor_Events.prototype.editwin_onfocus;
			this.editwin.onblur = vB_Text_Editor_Events.prototype.editwin_onblur;
		};

		/**
		* Set Context
		*/
		this.set_context = function(cmd)
		{
			for (var i in contextcontrols)
			{
if (typeof contextcontrols[i] == 'function') continue;
				var obj = fetch_object(this.editorid + '_cmd_' + contextcontrols[i]);
				if (obj != null)
				{
					try{state = this.editdoc.queryCommandState(contextcontrols[i]);}catch(l){state=obj.state;}
					//alert (contextcontrols[i] + ' ' + state + ' ' + obj.state);
					if (obj.state != state)
					{
						obj.state = state;
						this.button_context(obj, (obj.cmd == cmd ? 'mouseover' : 'mouseout'));
					}
				}
			}

			this.set_font_context();

			this.set_size_context();

			this.set_color_context();
		};

		/**
		* Set Font Context
		*/
		this.set_font_context = function(fontstate)
		{
			if (this.buttons['fontname'])
			{
				if (typeof fontstate == 'undefined')
				{
					fontstate = this.editdoc.queryCommandValue('fontname');
				}
				switch (fontstate)
				{
					case '':
					{
						if (!is_ie && window.getComputedStyle)
						{
							fontstate = this.editdoc.body.style.fontFamily;
						}
					}
					break;

					case null:
					{
						fontstate = '';
					}
					break;
				}

				if (fontstate != this.fontstate)
				{
					this.fontstate = fontstate;
					var thingy = PHP.ucfirst(this.fontstate, ',');

					if (this.popupmode)
					{
						for (var i in this.fontoptions)
						{
if (typeof this.fontoptions[i] == 'function') continue;
							this.fontoptions[i].style.display = (i == thingy ? '' : 'none');
						}
					}
					else
					{
						for (var i = 0; i < this.buttons['fontname'].options.length; i++)
						{
							if (this.buttons['fontname'].options[i].value == thingy)
							{
								this.buttons['fontname'].selectedIndex = i;
								break;
							}
						}
					}
				}
			}
		};

		/**
		* Set Size Context
		*/
		this.set_size_context = function(sizestate)
		{
			if (this.buttons['fontsize'])
			{
				if (typeof sizestate == 'undefined')
				{
					sizestate = this.editdoc.queryCommandValue('fontsize');
				}
				switch (sizestate)
				{
					case null:
					case '':
					{
						if (is_moz)
						{
							sizestate = this.translate_fontsize(this.editdoc.body.style.fontSize);
						}
					}
					break;
				}
				if (sizestate != this.sizestate)
				{
					this.sizestate = sizestate;

					if (this.popupmode)
					{
						for (var i in this.sizeoptions)
						{
if (typeof this.sizeoptions[i] == 'function') continue;
							this.sizeoptions[i].style.display = (i == this.sizestate ? '' : 'none');
						}
					}
					else
					{
						for (var i = 0; i < this.buttons['fontsize'].options.length; i++)
						{
							if (this.buttons['fontsize'].options[i].value == this.sizestate)
							{
								this.buttons['fontsize'].selectedIndex = i;
								break;
							}
						}
					}
				}
			}
		};

		/**
		* Set Color Context
		*/
		this.set_color_context = function(colorstate)
		{
			if (this.buttons['forecolor'])
			{
				if (typeof colorstate == 'undefined')
				{
					colorstate = this.editdoc.queryCommandValue('forecolor');
				}
				if (colorstate != this.colorstate)
				{
					if (this.popupmode)
					{
						var obj = fetch_object(this.editorid + '_color_' + this.translate_color_commandvalue(this.colorstate));
						if (obj != null)
						{
							obj.state = false;
							this.button_context(obj, 'mouseout', 'menu');
						}

						this.colorstate = colorstate;

						elmid = this.editorid + '_color_' + this.translate_color_commandvalue(colorstate);
						var obj = fetch_object(elmid);
						if (obj != null)
						{
							obj.state = true;
							this.button_context(obj, 'mouseout', 'menu');
						}
					}
					else
					{
						this.colorstate = colorstate;

						colorstate = this.translate_color_commandvalue(this.colorstate);

						for (var i = 0; i < this.buttons['forecolor'].options.length; i++)
						{
							if (this.buttons['forecolor'].options[i].value == colorstate)
							{
								this.buttons['forecolor'].selectedIndex = i;
								break;
							}
						}
					}
				}
			}
		};

		/**
		* Function to translate the output from queryCommandState('forecolor') into something useful
		*
		* @param	string	Output from queryCommandState('forecolor')
		*
		* @return	string
		*/
		this.translate_color_commandvalue = function(forecolor)
		{
			return this.translate_silly_hex((forecolor & 0xFF).toString(16), ((forecolor >> 8) & 0xFF).toString(16), ((forecolor >> 16) & 0xFF).toString(16));
		};

		/**
		* Function to translate a hex like F AB 9 to #0FAB09 and then to coloroptions['#0FAB09']
		*
		* @param	string	Red value
		* @param	string	Green value
		* @param	string	Blue value
		*
		* @return	string	Option from coloroptions array
		*/
		this.translate_silly_hex = function(r, g, b)
		{
			return coloroptions['#' + (PHP.str_pad(r, 2, 0) + PHP.str_pad(g, 2, 0) + PHP.str_pad(b, 2, 0)).toUpperCase()];
		};

		/**
		* Apply Formatting
		*/
		this.apply_format = function(cmd, dialog, argument)
		{
			this.editdoc.execCommand(cmd, (typeof dialog == 'undefined' ? false : dialog), (typeof argument == 'undefined' ? true : argument));
			return false;
		};

		/**
		* Insert Link
		*/
		this.createlink = function(e, url)
		{
			return this.apply_format('createlink', is_ie, (typeof url == 'undefined' ? true : url));
		};

		/**
		* Insert Email Link
		*/
		this.email = function(e, email)
		{
			if (typeof email == 'undefined')
			{
				email = this.show_prompt(vbphrase['enter_email_link'], '');
			}

			email = this.verify_prompt(email);

			if (email === false)
			{
				return this.apply_format('unlink');
			}
			else
			{
				var selection = this.get_selection();
				return this.insert_text('<a href="mailto:' + email + '">' + (selection ? selection : email) + '</a>', (selection ? true : false));
			}
		};

		/**
		* Insert Smilie
		*/
		this.insert_smilie = function(e, smilietext, smiliepath, smilieid)
		{
			this.check_focus();

			return this.insert_text('<img src="' + smiliepath + '" border="0" alt="0" smilieid="' + smilieid + '" /> ', false);
		};

		/**
		* Get Editor Contents
		*/
		this.get_editor_contents = function()
		{
			return this.editdoc.body.innerHTML;
		};

		/**
		* Get Selected Text
		*/
		this.get_selection = function()
		{
			var range = this.editdoc.selection.createRange();
			if (range.htmlText && range.text)
			{
				return range.htmlText;
			}
			else
			{
				var do_not_steal_this_code_html = '';
				for (var i = 0; i < range.length; i++)
				{
					do_not_steal_this_code_html += range.item(i).outerHTML;
				}
				return do_not_steal_this_code_html;
			}
		};

		/**
		* Paste HTML
		*/
		this.insert_text = function(text, movestart, moveend)
		{
			this.check_focus();

			if (typeof(this.editdoc.selection) != 'undefined' && this.editdoc.selection.type != 'Text' && this.editdoc.selection.type != 'None')
			{
				movestart = false;
				this.editdoc.selection.clear();
			}

			var sel = this.editdoc.selection.createRange();
			sel.pasteHTML(text);

			if (text.indexOf('\n') == -1)
			{
				if (movestart === false)
				{
					// do nothing
				}
				else if (typeof movestart != 'undefined')
				{
					sel.moveStart('character', -text.vBlength() +movestart);
					sel.moveEnd('character', -moveend);
				}
				else
				{
					sel.moveStart('character', -text.vBlength());
				}
				sel.select();
			}
		};

		/**
		* Prepare Form For Submit
		*/
		this.prepare_submit = function(subjecttext, minchars)
		{
			this.textobj.value = this.get_editor_contents();

			var returnvalue = validatemessage(stripcode(this.textobj.value, true), subjecttext, minchars);

			if (returnvalue)
			{
				return returnvalue;
			}
			else
			{
				this.check_focus();
				return false;
			}
		}

		// =============================================================================
		// Mozilla WYSIWYG only
		// =============================================================================
		if (is_moz)
		{
			/**
			* Set editor contents
			*/
			this._set_editor_contents = this.set_editor_contents;
			this.set_editor_contents = function(initial_text)
			{
				this._set_editor_contents(initial_text);

				this.editdoc.addEventListener('keypress', vB_Text_Editor_Events.prototype.editdoc_onkeypress, true);
			};

			/**
			* Set editor style
			*/
			this.set_editor_style = function()
			{
				for (var ss = 0; ss < document.styleSheets.length; ss++)
				{
					try
					{
						if (document.styleSheets[ss].cssRules.length <= 0)
						{
							continue;
						}
					}
					catch(e)
					{ // trying to access a stylesheet outside the allowed domain
						continue;
					}
					for (var i = 0; i < document.styleSheets[ss].cssRules.length; i++)
					{
						if (document.styleSheets[ss].cssRules[i].selectorText == '.wysiwyg')
						{
							newss = this.editdoc.createElement('style');
							newss.type = 'text/css';
							newss.innerHTML = document.styleSheets[ss].cssRules[i].cssText + ' p { margin: 0px; }';
							this.editdoc.documentElement.childNodes[0].appendChild(newss);
							this.editdoc.body.className = 'wysiwyg';
							this.editdoc.body.style.fontFamily = document.styleSheets[ss].cssRules[i].style.fontFamily;
							this.editdoc.body.style.fontSize = document.styleSheets[ss].cssRules[i].style.fontSize;
							return false;
						}
					}
				}
			};

			/**
			* Translate Color Command Value
			*/
			this.translate_color_commandvalue = function(forecolor)
			{
				if (forecolor == '' || forecolor == null)
				{
					forecolor = window.getComputedStyle(this.editdoc.body, null).getPropertyValue('color');
				}

				if (forecolor.toLowerCase().indexOf('rgb') == 0)
				{
					var matches = forecolor.match(/^rgb\s*\(([0-9]+),\s*([0-9]+),\s*([0-9]+)\)$/);
					if (matches)
					{
						return this.translate_silly_hex((matches[1] & 0xFF).toString(16), (matches[2] & 0xFF).toString(16), (matches[3] & 0xFF).toString(16));
					}
					else
					{
						return this.translate_color_commandvalue(null);
					}
				}
				else
				{
					return forecolor;
				}
			};

			/**
			* Translate CSS fontSize to HTML Font Size
			*/
			this.translate_fontsize = function(csssize)
			{
				switch (csssize)
				{
					case '7.5pt':
					case '10px': return 1;
					case '10pt': return 2;
					case '12pt': return 3;
					case '14pt': return 4;
					case '18pt': return 5;
					case '24pt': return 6;
					case '36pt': return 7;
					default:     return '';
				}
			}

			/**
			* Apply Format
			*/
			this._apply_format = this.apply_format;
			this.apply_format = function(cmd, dialog, arg)
			{
				this.editdoc.execCommand('useCSS', false, true);
				return this._apply_format(cmd, dialog, arg);
			};

			/**
			* Insert Link
			*/
			this._createlink = this.createlink;
			this.createlink = function(e, url)
			{
				if (typeof url == 'undefined')
				{
					url = this.show_prompt(vbphrase['enter_link_url'], 'http://');
				}
				if ((url = this.verify_prompt(url)) !== false)
				{
					if (this.get_selection())
					{
						this.apply_format('unlink');
						this._createlink(e, url);
					}
					else
					{
						this.insert_text('<a href="' + url + '">' + url + '</a>');
					}
				}
				return true;
			};

			/**
			* Insert Smilie
			*/
			this.insert_smilie = function(e, smilietext, smiliepath, smilieid)
			{
				this.check_focus();

				try
				{
					this.apply_format('InsertImage', false, smiliepath);
					var smilies = fetch_tags(this.editdoc.body, 'img');
					for (var i = 0; i < smilies.length; i++)
					{
						if (smilies[i].src == smiliepath)
						{
							if (smilies[i].getAttribute('smilieid') < 1)
							{
								smilies[i].setAttribute('smilieid', smilieid);
								smilies[i].setAttribute('border', "0");
							}
						}
					}
				}
				catch(e)
				{
					// failed... probably due to inserting a smilie over a smilie in mozilla
				}
			};

			/**
			* Get Selection
			*/
			this.get_selection = function()
			{
				selection = this.editwin.getSelection();
				this.check_focus();
				range = selection ? selection.getRangeAt(0) : this.editdoc.createRange();
				return this.read_nodes(range.cloneContents(), false);
			};

			/**
			* Paste Text
			*/
			this.insert_text = function(str)
			{
				this.editdoc.execCommand('insertHTML', false, str);
				/*fragment = this.editdoc.createDocumentFragment();
				holder = this.editdoc.createElement('span');
				holder.innerHTML = str;

				while (holder.firstChild)
				{
					fragment.appendChild(holder.firstChild);
				}

				this.insert_node_at_selection(fragment);*/
			};

			/**
			* Set editor functions
			*/
			this.set_editor_functions = function()
			{
				this.editdoc.addEventListener('mouseup', vB_Text_Editor_Events.prototype.editdoc_onmouseup, true);
				this.editdoc.addEventListener('keyup', vB_Text_Editor_Events.prototype.editdoc_onkeyup, true);
				this.editwin.addEventListener('focus', vB_Text_Editor_Events.prototype.editwin_onfocus, true);
				this.editwin.addEventListener('blur', vB_Text_Editor_Events.prototype.editwin_onblur, true);
			};


			/**
			* Add Range (Mozilla)
			*/
			this.add_range = function(node)
			{
				this.check_focus();
				var sel = this.editwin.getSelection();
				var range = this.editdoc.createRange();
				range.selectNodeContents(node);
				sel.removeAllRanges();
				sel.addRange(range);
			};

			/**
			* Read Nodes (Mozilla)
			*/
			this.read_nodes = function(root, toptag)
			{
				var html = "";
				var moz_check = /_moz/i;

				switch (root.nodeType)
				{
					case Node.ELEMENT_NODE:
					case Node.DOCUMENT_FRAGMENT_NODE:
					{
						var closed;
						if (toptag)
						{
							closed = !root.hasChildNodes();
							html = '<' + root.tagName.toLowerCase();
							var attr = root.attributes;
							for (var i = 0; i < attr.length; ++i)
							{
								var a = attr.item(i);
								if (!a.specified || a.name.match(moz_check) || a.value.match(moz_check))
								{
									continue;
								}

								html += " " + a.name.toLowerCase() + '="' + a.value + '"';
							}
							html += closed ? " />" : ">";
						}
						for (var i = root.firstChild; i; i = i.nextSibling)
						{
							html += this.read_nodes(i, true);
						}
						if (toptag && !closed)
						{
							html += "</" + root.tagName.toLowerCase() + ">";
						}
					}
					break;

					case Node.TEXT_NODE:
					{
						//html = htmlspecialchars(root.data);
						html = PHP.htmlspecialchars(root.data);
					}
					break;
				}

				return html;
			};

			/**
			* Insert Node at Selection (Mozilla)
			*/
			this.insert_node_at_selection = function(text)
			{
				this.check_focus();

				var sel = this.editwin.getSelection();
				var range = sel ? sel.getRangeAt(0) : this.editdoc.createRange();
				sel.removeAllRanges();
				range.deleteContents();

				var node = range.startContainer;
				var pos = range.startOffset;

				switch (node.nodeType)
				{
					case Node.ELEMENT_NODE:
					{
						if (text.nodeType == Node.DOCUMENT_FRAGMENT_NODE)
						{
							selNode = text.firstChild;
						}
						else
						{
							selNode = text;
						}
						node.insertBefore(text, node.childNodes[pos]);
						this.add_range(selNode);
					}
					break;

					case Node.TEXT_NODE:
					{
						if (text.nodeType == Node.TEXT_NODE)
						{
							var text_length = pos + text.length;
							node.insertData(pos, text.data);
							range = this.editdoc.createRange();
							range.setEnd(node, text_length);
							range.setStart(node, text_length);
							sel.addRange(range);
						}
						else
						{
							node = node.splitText(pos);
							var selNode;
							if (text.nodeType == Node.DOCUMENT_FRAGMENT_NODE)
							{
								selNode = text.firstChild;
							}
							else
							{
								selNode = text;
							}
							node.parentNode.insertBefore(text, node);
							this.add_range(selNode);
						}
					}
					break;
				}
			};
		}
		// =============================================================================
		// Opera WYSIWYG only
		// =============================================================================
		else if (is_opera)
		{
			/**
				There are several issues with Opera which is why this code is disabled:
					1. Focus issues, when a button is pressed the editor loses focus
					2. Styling is lost when we make any adjustments to the editor
					3. Adding a URL is ever lasting even if you push enter
			*/
			/**
			* Set editor style
			*/
			this.set_editor_style = function()
			{
				for (var ss = 0; ss < document.styleSheets.length; ss++)
				{
					try
					{
						if (document.styleSheets[ss].cssRules.length <= 0)
						{
							continue;
						}
					}
					catch(e)
					{ // trying to access a stylesheet outside the allowed domain
						continue;
					}
					for (var i = 0; i < document.styleSheets[ss].cssRules.length; i++)
					{
						if (document.styleSheets[ss].cssRules[i].selectorText == '.wysiwyg')
						{
							newss = this.editdoc.createElement('style');
							newss.type = 'text/css';
							newss.innerHTML = document.styleSheets[ss].cssRules[i].cssText + ' p { margin: 0px; }';
							this.editdoc.documentElement.childNodes[0].appendChild(newss);
							this.editdoc.body.className = 'wysiwyg';
							this.editdoc.body.style.fontFamily = document.styleSheets[ss].cssRules[i].style.fontFamily;
							this.editdoc.body.style.fontSize = document.styleSheets[ss].cssRules[i].style.fontSize;
							return false;
						}
					}
				}
			};

			/**
			* Insert Link
			*/
			this._createlink = this.createlink;
			this.createlink = function(e, url)
			{
				if (typeof url == 'undefined')
				{
					url = this.show_prompt(vbphrase['enter_link_url'], 'http://');
				}
				if ((url = this.verify_prompt(url)) !== false)
				{
					if (this.get_selection())
					{
						this.apply_format('unlink');
						this._createlink(e, url);
					}
					else
					{
						this.insert_text('<a href="' + url + '">' + url + '</a>');
					}
				}
				return true;
			};

			/**
			* Insert Smilie
			*/
			this.insert_smilie = function(e, smilietext, smiliepath, smilieid)
			{
				this.check_focus();

				try
				{
					this.apply_format('InsertImage', false, smiliepath);
					var smilies = fetch_tags(this.editdoc.body, 'img');
					for (var i = 0; i < smilies.length; i++)
					{
						if (smilies[i].src == smiliepath)
						{
							if (smilies[i].getAttribute('smilieid') < 1)
							{
								smilies[i].setAttribute('smilieid', smilieid);
								smilies[i].setAttribute('border', "0");
							}
						}
					}
				}
				catch(e)
				{
					// failed... probably due to inserting a smilie over a smilie in mozilla
				}
			};

			/**
			* Get Selection
			*/
			this.get_selection = function()
			{
				selection = this.editwin.getSelection();
				this.check_focus();
				range = selection ? selection.getRangeAt(0) : this.editdoc.createRange();
				var lsserializer = document.implementation.createLSSerializer();
				return lsserializer.writeToString(range.cloneContents());
			};

			/**
			* Paste Text
			*/
			this.insert_text = function(str)
			{
				this.editdoc.execCommand('insertHTML', false, str);
			};
		}
	}
	// =============================================================================
	// Standard editor
	// =============================================================================
	else
	{
		this.disable_editor = function(text)
		{
			if (!this.disabled)
			{
				this.disabled = true;

				if (typeof text != 'undefined')
				{
					this.editbox.value = text;
				}
				this.editbox.disabled = true;
			}
		};

		this.enable_editor = function(text)
		{
			if (typeof text != 'undefined')
			{
				this.editbox.value = text;
			}
			this.editbox.disabled = false;

			this.disabled = false;
		};

		/**
		* Writes contents to the <textarea>
		*
		* @param	object	<textarea>
		* @param	string	Initial text
		*/
		this.write_editor_contents = function(text)
		{
			this.textobj.value = text;
		}

		/**
		* Put the text into the editor
		*/
		this.set_editor_contents = function(initial_text)
		{
			var iframe = this.textobj.parentNode.getElementsByTagName('iframe')[0];
			if (iframe)
			{
				this.textobj.style.display = '';
				this.textobj.style.width = iframe.style.width;
				this.textobj.style.height = iframe.style.height;

				iframe.style.width = '0px';
				iframe.style.height = '0px';
				iframe.style.border = 'none';
			}

			this.editwin = this.textobj;
			this.editdoc = this.textobj;
			this.editbox = this.textobj;
			this.spellobj = this.textobj;

			if (typeof initial_text != 'undefined')
			{
				this.write_editor_contents(initial_text);
			}

			this.editdoc.editorid = this.editorid;
			this.editwin.editorid = this.editorid;

			this.history.add_snapshot(this.get_editor_contents());
		};

		/**
		* Set the CSS style of the editor
		*/
		this.set_editor_style = function()
		{
		};

		/**
		* Init Editor Functions
		*/
		this.set_editor_functions = function()
		{
			if (this.editdoc.addEventListener)
			{
				this.editdoc.addEventListener('keypress', vB_Text_Editor_Events.prototype.editdoc_onkeypress, false);
			}
			else if (is_ie)
			{
				this.editdoc.onkeydown = vB_Text_Editor_Events.prototype.editdoc_onkeypress;
			}

			this.editwin.onfocus = vB_Text_Editor_Events.prototype.editwin_onfocus;
			this.editwin.onblur = vB_Text_Editor_Events.prototype.editwin_onblur;
		};

		/**
		* Set Context
		*/
		this.set_context = function()
		{
		};

		/**
		* Apply formatting
		*/
		this.apply_format = function(cmd, dialog, argument)
		{
			// undo & redo array pops

			switch (cmd)
			{
				case 'bold':
				case 'italic':
				case 'underline':
				{
					this.wrap_tags(cmd.substr(0, 1), false);
					return;
				}

				case 'justifyleft':
				case 'justifycenter':
				case 'justifyright':
				{
					this.wrap_tags(cmd.substr(7), false);
					return;
				}

				case 'indent':
				{
					this.wrap_tags(cmd, false);
					return;
				}

				case 'fontname':
				{
					this.wrap_tags('font', argument);
					return;
				}

				case 'fontsize':
				{
					this.wrap_tags('size', argument);
					return;
				}

				case 'forecolor':
				{
					this.wrap_tags('color', argument);
					return;
				}

				case 'createlink':
				{
					var sel = this.get_selection();
					if (sel)
					{
						this.wrap_tags('url', argument);
					}
					else
					{
						this.wrap_tags('url', argument, argument);
					}
					return;
				}

				case 'insertimage':
				{
					this.wrap_tags('img', false, argument);
					return;
				}

				case 'removeformat':
				return;
			}

			//alert('cmd :: ' + cmd + '\ndialog :: ' + dialog + '\nargument :: ' + argument);
		};

		this.undo = function()
		{
			this.history.add_snapshot(this.get_editor_contents());
			this.history.move_cursor(-1);
			if ((str = this.history.get_snapshot()) !== false)
			{
				this.editdoc.value = str;
			}
		};

		this.redo = function()
		{
			this.history.move_cursor(1);
			if ((str = this.history.get_snapshot()) !== false)
			{
				this.editdoc.value = str;
			}
		};

		/**
		* Strip a simple tag...
		*/
		this.strip_simple = function(tag, str, iterations)
		{
			var opentag = '[' + tag + ']';
			var closetag = '[/' + tag + ']';

			if (typeof iterations == 'undefined')
			{
				iterations = -1;
			}

			while ((startindex = PHP.stripos(str, opentag)) !== false && iterations != 0)
			{
				iterations --;
				if ((stopindex = PHP.stripos(str, closetag)) !== false)
				{
					var text = str.substr(startindex + opentag.length, stopindex - startindex - opentag.length);
					str = str.substr(0, startindex) + text + str.substr(stopindex + closetag.length);
				}
				else
				{
					break;
				}
			}

			return str;
		};

		/**
		* Strip a tag with an option
		*/
		this.strip_complex = function(tag, str, iterations)
		{
			var opentag = '[' + tag + '=';
			var closetag = '[/' + tag + ']';

			if (typeof iterations == 'undefined')
			{
				iterations = -1;
			}

			while ((startindex = PHP.stripos(str, opentag)) !== false && iterations != 0)
			{
				iterations --;
				if ((stopindex = PHP.stripos(str, closetag)) !== false)
				{
					var openend = PHP.stripos(str, ']', startindex);
					if (openend !== false && openend > startindex && openend < stopindex)
					{
						var text = str.substr(openend + 1, stopindex - openend - 1);
						str = str.substr(0, startindex) + text + str.substr(stopindex + closetag.length);
					}
					else
					{
						break;
					}
				}
				else
				{
					break;
				}
			}

			return str;
		};

		/**
		* Remove Formatting
		*/
		this.removeformat = function(e)
		{
			var simplestrip = new Array('b', 'i', 'u');
			var complexstrip = new Array('font', 'color', 'size');

			var str = this.get_selection();
			if (str === false)
			{
				return;
			}

			// simple stripper
			for (var tag in simplestrip)
			{
if (typeof simplestrip[tag] == 'function') continue;
				str = this.strip_simple(simplestrip[tag], str);
			}

			// complex stripper
			for (var tag in complexstrip)
			{
if (typeof complexstrip[tag] == 'function') continue;
				str = this.strip_complex(complexstrip[tag], str);
			}

			this.insert_text(str);
		};

		/**
		* Insert Link
		*/
		this.createlink = function(e, url)
		{
			this.prompt_link('url', url, vbphrase['enter_link_url'], 'http://');
		};

		/**
		* Remove Link
		*/
		this.unlink = function(e)
		{
			var sel = this.get_selection();
			sel = this.strip_simple('url', sel);
			sel = this.strip_complex('url', sel);
			this.insert_text(sel);
		};

		/**
		* Insert Email Link
		*/
		this.email = function(e, email)
		{
			this.prompt_link('email', email, vbphrase['enter_email_link'], '');
		};

		/**
		* Insert Smilie
		*/
		this.insert_smilie = function(e, smilietext)
		{
			this.check_focus();

			smilietext += ' ';

			return this.insert_text(smilietext, smilietext.length, 0);
		};

		/**
		* Wrapper for Link / Email Link insert
		*/
		this.prompt_link = function(tagname, value, phrase, iprompt)
		{
			if (typeof value == 'undefined')
			{
				value = this.show_prompt(phrase, iprompt);
			}
			if ((value = this.verify_prompt(value)) !== false)
			{
				if (this.get_selection())
				{
					this.apply_format('unlink');
					this.wrap_tags(tagname, value);
				}
				else
				{
					this.wrap_tags(tagname, value, value);
				}
			}
			return true;
		};

		/**
		* Insert Ordered List
		*/
		this.insertorderedlist = function(e)
		{
			this.insertlist(vbphrase['insert_ordered_list'], '1');
		};

		/**
		* Insert Unordered List
		*/
		this.insertunorderedlist = function(e)
		{
			this.insertlist(vbphrase['insert_unordered_list'], '');
		};

		/**
		* Insert List
		*/
		this.insertlist = function(phrase, listtype)
		{
			var opentag = '[LIST' + (listtype ? ('=' + listtype) : '') + ']\n';
			var closetag = '[/LIST]';

			if (txt = this.get_selection())
			{
				var regex = new RegExp('([\r\n]+|^[\r\n]*)(?!\\[\\*\\]|\\[\\/?list)(?=[^\r\n])', 'gi');
				txt = opentag + PHP.trim(txt).replace(regex, '$1[*]') + '\n' + closetag;
				this.insert_text(txt, txt.vBlength(), 0);
			}
			else
			{
				this.insert_text(opentag + closetag, opentag.length, closetag.length);

				while (listvalue = this.show_prompt(vbphrase['enter_list_item'], ''))
				{
					listvalue = '[*]' + listvalue + '\n';
					this.insert_text(listvalue, listvalue.vBlength(), 0);
				}
			}
		};

		/**
		* Outdent
		*/
		this.outdent = function(e)
		{
			var sel = this.get_selection();
			sel = this.strip_simple('indent', sel, 1);
			this.insert_text(sel);
		};

		/**
		* Get Editor Contents
		*/
		this.get_editor_contents = function()
		{
			return this.editdoc.value;
		};

		/**
		* Get Selected Text
		*/
		this.get_selection = function()
		{
			if (typeof(this.editdoc.selectionStart) != 'undefined')
			{
				return this.editdoc.value.substr(this.editdoc.selectionStart, this.editdoc.selectionEnd - this.editdoc.selectionStart);
			}
			else if (document.selection && document.selection.createRange)
			{
				return document.selection.createRange().text;
			}
			else if (window.getSelection)
			{
				return window.getSelection() + '';
			}
			else
			{
				return false;
			}
		};

		/**
		* Paste HTML
		*/
		this.insert_text = function(text, movestart, moveend)
		{
			this.check_focus();

			if (typeof(this.editdoc.selectionStart) != 'undefined')
			{
				var opn = this.editdoc.selectionStart + 0;
				var scrollpos = this.editdoc.scrollTop;

				this.editdoc.value = this.editdoc.value.substr(0, this.editdoc.selectionStart) + text + this.editdoc.value.substr(this.editdoc.selectionEnd);

				if (movestart === false)
				{
					// do nothing
				}
				else if (typeof movestart != 'undefined')
				{
					this.editdoc.selectionStart = opn + movestart;
					this.editdoc.selectionEnd = opn + text.vBlength() - moveend;
				}
				else
				{
					this.editdoc.selectionStart = opn;
					this.editdoc.selectionEnd = opn + text.vBlength();
				}
				this.editdoc.scrollTop = scrollpos;
			}
			else if (document.selection && document.selection.createRange)
			{
				var sel = document.selection.createRange();
				sel.text = text.replace(/\r?\n/g, '\r\n');

				if (movestart === false)
				{
					// do nothing
				}
				else if (typeof movestart != 'undefined')
				{
					sel.moveStart('character', -text.vBlength() +movestart);
					sel.moveEnd('character', -moveend);
				}
				else
				{
					sel.moveStart('character', -text.vBlength());
				}
				sel.select();
			}
			else
			{
				// failed - just stuff it at the end of the message
				this.editdoc.value += text;
			}
		};

		/**
		* Prepare Form For Submit
		*/
		this.prepare_submit = function(subjecttext, minchars)
		{
			var returnvalue = validatemessage(this.textobj.value, subjecttext, minchars);

			if (returnvalue)
			{
				return returnvalue;
			}
			else
			{
				this.check_focus();
				return false;
			}
		}

		// =============================================================================
		// Safari / Old Opera Standard Editor Only
		// =============================================================================
		if (is_saf || (is_opera && (!opera.version || opera.version() < 8)))
		{
			/**
			* Insert List for Safari and older Opera
			*/
			this.insertlist = function(phrase, listtype)
			{
				var opentag = '[LIST' + (listtype ? ('=' + listtype) : '') + ']\n';
				var closetag = '[/LIST]';
				if (txt = this.get_selection())
				{
					var regex = new RegExp('([\r\n]+|^[\r\n]*)(?!\\[\\*\\]|\\[\\/?list)(?=[^\r\n])', 'gi');
					txt = opentag + PHP.trim(txt).replace(regex, '$1[*]') + '\n' + closetag;
					this.insert_text(txt, txt.vBlength(), 0);
				}
				else
				{
					this.insert_text(opentag, opentag.length, 0);

					while (listvalue = prompt(vbphrase['enter_list_item'], ''))
					{
						listvalue = '[*]' + listvalue + '\n';
						this.insert_text(listvalue, listvalue.vBlength(), 0);
					}

					this.insert_text(closetag, closetag.length, 0);
				}
			};
		}
	}

	// gentlemen, start your engines...
	this.init();
}

// =============================================================================
// Editor event handler functions

/**
* Class containing editor event handlers
*/
function vB_Text_Editor_Events()
{
}

/**
* Handles a click on a smilie in the smiliebox
*/
vB_Text_Editor_Events.prototype.smilie_onclick = function(e)
{
	vB_Editor[this.editorid].insert_smilie(e,
		this.alt,
		this.src,
		this.id.substr(this.id.lastIndexOf('_') + 1)
	);

	if (typeof smilie_window != 'undefined' && !smilie_window.closed)
	{
		smilie_window.focus();
	}

	return false;
};

/**
* Handles a mouse event on a command button
*/
vB_Text_Editor_Events.prototype.command_button_onmouseevent = function(e)
{
	e = do_an_e(e);

	if (e.type == 'click')
	{
		vB_Editor[this.editorid].format(e, this.cmd, false, true);
	}

	vB_Editor[this.editorid].button_context(this, e.type);
};

/**
* Handles a mouse event on a popup controller button
*/
vB_Text_Editor_Events.prototype.popup_button_onmouseevent = function(e)
{
	e = do_an_e(e);

	if (e.type == 'click')
	{
		this._onclick(e);
		vB_Editor[this.editorid].menu_context(this, 'mouseover');
	}
	else
	{
		vB_Editor[this.editorid].menu_context(this, e.type);
	}
};

/**
* Overrides the show() function from the vBmenu system
*
* @param	object	Control object
* @param	boolean	Show instantly?
*/
vB_Text_Editor_Events.prototype.popup_button_show = function(obj, instant)
{
	if (typeof vB_Editor[obj.editorid].popups[obj.cmd] == 'undefined' || vB_Editor[obj.editorid].popups[obj.cmd] == null)
	{
		vB_Editor[obj.editorid].init_popup_menu(obj);
	}
	else if (obj.cmd == 'attach' && (typeof vB_Attachments == 'undefined' || !vB_Attachments.has_attachments()))
	{
		return fetch_object('manage_attachments_button').onclick();
	}
	this._show(obj, instant);
};

/**
* Handles a selection from a formatting <select> menu
*/
vB_Text_Editor_Events.prototype.formatting_select_onchange = function(e)
{
	var arg = this.options[this.selectedIndex].value;
	if (arg != '')
	{
		vB_Editor[this.editorid].format(e, this.cmd, arg);
	}
	this.selectedIndex = 0;
};

/**
* Handles a selection from the smilies <select> menu
*/
vB_Text_Editor_Events.prototype.smilieselect_onchange = function(e)
{
	if (this.options[this.selectedIndex].value != '')
	{
		vB_Editor[this.editorid].insert_smilie(e,
			this.options[this.selectedIndex].value,
			this.options[this.selectedIndex].smiliepath,
			this.options[this.selectedIndex].smilieid
		);
	}
	this.selectedIndex = 0;
};

/**
* Handles a selection from the attachment <select> menu
*/
vB_Text_Editor_Events.prototype.attachselect_onchange = function(e)
{
	var arg = this.options[this.selectedIndex].value;
	if (arg != '')
	{
		vB_Editor[this.editorid].wrap_tags('attach', false, arg);
	}
	this.selectedIndex = 0;
};

/**
* Handles a mouse over event for the attachment <select> menu
*/
vB_Text_Editor_Events.prototype.attachselect_onmouseover = function(e)
{
	if (this.options.length <= 2)
	{
		vB_Editor[this.editorid].build_attachments_popup(this);
		return true;
	}
};

/**
* Handles a mouse event on a menu option
*/
vB_Text_Editor_Events.prototype.menuoption_onmouseevent = function(e)
{
	e = do_an_e(e);
	vB_Editor[this.editorid].button_context(this, e.type, 'menu');
};

/**
* Handles a click on a formatting option in the font/size menus
*/
vB_Text_Editor_Events.prototype.formatting_option_onclick = function(e)
{
	vB_Editor[this.editorid].format(e, this.cmd, this.firstChild.innerHTML);
	vBmenu.hide();
};

/**
* Handles a click on a color option in the color menu
*/
vB_Text_Editor_Events.prototype.coloroption_onclick = function(e)
{
	fetch_object(this.editorid + '_color_bar').style.backgroundColor = this.colorname;
	vB_Editor[this.editorid].format(e, this.cmd, this.colorname);
	vBmenu.hide();
};

/**
* Handles a click on the color instant-select button
*/
vB_Text_Editor_Events.prototype.colorout_onclick = function(e)
{
	e = do_an_e(e);
	vB_Editor[this.editorid].format(e, 'forecolor', fetch_object(this.editorid + '_color_bar').style.backgroundColor);
	return false;
};

/**
* Handles a click on a smilie option in the smilie menu
*/
vB_Text_Editor_Events.prototype.smilieoption_onclick = function(e)
{
	vB_Editor[this.editorid].button_context(this, 'mouseout', 'menu');
	vB_Editor[this.editorid].insert_smilie(e, this.smilietext, fetch_tags(this, 'img')[0].src, this.smilieid);
	vBmenu.hide();
};

/**
* Handles a click on the 'More' link in the smilie menu
*/
vB_Text_Editor_Events.prototype.smiliemore_onclick = function(e)
{
	vB_Editor[this.editorid].open_smilie_window(smiliewindow_x, smiliewindow_y);
	vBmenu.hide();
};

/**
* Handles a click on the 'Manage' link in the attachments menu
*/
vB_Text_Editor_Events.prototype.attachmanage_onclick = function(e)
{
	vBmenu.hide();
	fetch_object('manage_attachments_button').onclick();
};

/**
* Handles a click on an attachment option in the attachments menu
*/
vB_Text_Editor_Events.prototype.attachoption_onclick = function(e)
{
	vB_Editor[this.editorid].button_context(this, 'mouseout', 'menu');
	vB_Editor[this.editorid].wrap_tags('attach', false, this.attachmentid);
	vBmenu.hide();
};

vB_Text_Editor_Events.prototype.attachinsertall_onclick = function(e)
{
	var insert = '';
	var breakchar = (vB_Editor[this.editorid].wysiwyg_mode ? '<br /><br />' : '\r\n\r\n');

	for (var id in vB_Attachments.attachments)
	{
		insert += insert != '' ? breakchar : '';
		insert += '[ATTACH]' + id + '[/ATTACH]';
	}
	vB_Editor[this.editorid].insert_text(insert);
	vBmenu.hide();
}

/**
* Closes the smilie window when the main page exits
*/
vB_Text_Editor_Events.prototype.smiliewindow_onunload = function(e)
{
	if (typeof smilie_window != 'undefined' && !smilie_window.closed)
	{
		smilie_window.close();
	}
};

/**
* Sets editwin.hasfocus = true on focus
*/
vB_Text_Editor_Events.prototype.editwin_onfocus = function(e)
{
	this.hasfocus = true;
};

/**
* Sets editwin.hasfocus = false on blur
*/
vB_Text_Editor_Events.prototype.editwin_onblur = function(e)
{
	this.hasfocus = false;
};

/**
* Sets context and hides menus on mouse clicks in the editor
*/
vB_Text_Editor_Events.prototype.editdoc_onmouseup = function(e)
{
	vB_Editor[this.editorid].set_context();
	if (vB_Editor[this.editorid].popupmode)
	{
		vBmenu.hide();
	}
};

/**
* Sets context on key presses in the editor
*/
vB_Text_Editor_Events.prototype.editdoc_onkeyup = function(e)
{
	vB_Editor[this.editorid].set_context();
};

/**
* Handle a keypress event in the editor window
*/
vB_Text_Editor_Events.prototype.editdoc_onkeypress = function(e)
{
	if (!e)
	{
		e = window.event;
	}

	if (e.ctrlKey)
	{
		var code = e.charCode ? e.charCode : e.keyCode;
		switch (String.fromCharCode(code).toLowerCase())
		{
			case 'b': cmd = 'bold'; break;
			case 'i': cmd = 'italic'; break;
			case 'u': cmd = 'underline'; break;
			default: return;
		}

		e = do_an_e(e);
		vB_Editor[this.editorid].apply_format(cmd, false, null);
		return false;
	}
	else if (e.keyCode == 9)
	{
		// first lets try post icon, then submit, then just let it proceed making the tab
		var firsticon = fetch_object('rb_iconid_0');
		if (firsticon != null)
		{
			firsticon.focus();
		}
		else if (fetch_object(this.editorid + '_save') != null)
		{
			fetch_object(this.editorid + '_save').focus();
		}
		else if (fetch_object('qr_submit') != null)
		{
			fetch_object('qr_submit').focus();
		}
		else
		{
			return;
		}
		e = do_an_e(e);
	}
};

/**
* Stop resizing of images in IE
*/
vB_Text_Editor_Events.prototype.editdoc_onresizestart = function(e)
{
	if (e.srcElement.tagName == 'IMG')
	{
		return false;
	}
};

/**
* Save editor contents to textarea so if we hit back / forward its not lost
* Only appears to work with Firefox at the moment
*/
function save_iframe_to_textarea()
{
	for (var editorid in vB_Editor)
	{
		if (vB_Editor[editorid].wysiwyg_mode && vB_Editor[editorid].initialized)
		{
			vB_Editor[editorid].textobj.value = vB_Editor[editorid].get_editor_contents();
		}
	}
}

if (window.attachEvent)
{
	window.attachEvent('onbeforeunload', save_iframe_to_textarea);
}
else if(window.addEventListener)
{
	window.addEventListener('unload', save_iframe_to_textarea, true);
}

// #############################################################################
// Editor mode switcher system

/**
* Switch editor between standard and wysiwyg modes
*
* @param	string	EditorID (vB_Editor[x])
*/
function switch_editor_mode(editorid)
{
	if (AJAX_Compatible)
	{
		var mode = (vB_Editor[editorid].wysiwyg_mode ? 0 : 1);

		if (vB_Editor[editorid].influx == 1)
		{
			// already clicked, go away!
			return;
		}
		else
		{
			vB_Editor[editorid].influx = 1;
		}

		if (typeof vBmenu != 'undefined')
		{
			vBmenu.hide();
		}

		var ajax = new vB_AJAX_Handler(true);
		ajax.onreadystatechange(function()
		{
			if (ajax.handler.readyState == 4 && ajax.handler.status == 200)
			{
				if (ajax.handler.responseXML)
				{
					// destroyer
					var parsetype = vB_Editor[editorid].parsetype;
					var parsesmilies = vB_Editor[editorid].parsesmilies;
					vB_Editor[editorid].destroy();

					var parsed_text = ajax.fetch_data(fetch_tags(ajax.handler.responseXML, 'message')[0]);
					var matches = parsed_text.match(/&#([0-9]+);/g);
					if (matches)
					{
						for (var i = 0; typeof matches[i] != 'undefined'; i++)
						{
							if (submatch = matches[i].match(/^&#([0-9]+);$/))
							{
								parsed_text = parsed_text.replace(submatch[0], String.fromCharCode(submatch[1]));
							}
						}
					}

					vB_Editor[editorid] = null; // collect the garbage

					vB_Editor[editorid] = new vB_Text_Editor(editorid, mode, parsetype, parsesmilies, parsed_text);
					vB_Editor[editorid].check_focus();
					fetch_object(editorid + '_mode').value = mode;
				}

				if (is_ie)
				{
					ajax.handler.abort();
				}
			}
		});

		// !!attention
		ajax.send('ajax.php?do=editorswitch', 'do=editorswitch'
			+ '&towysiwyg='	+ mode
			+ '&parsetype=' + vB_Editor[editorid].parsetype
			+ '&allowsmilie=' + vB_Editor[editorid].parsesmilies
			+ '&message=' + PHP.urlencode(vB_Editor[editorid].get_editor_contents())
		);
	}
}


// #############################################################################
// Generic global editor variables

/**
* Define which buttons are context-controlled
*
* @var	array	Context controls
*/
var contextcontrols = new Array(
	'bold',
	'italic',
	'underline',
	'justifyleft',
	'justifycenter',
	'justifyright',
	'insertorderedlist',
	'insertunorderedlist'
);

/**
* Define available color name options - keyed with hex value
*
* @var	array	Color options
*/
var coloroptions = new Array();
coloroptions = {
	'#000000' : 'Black',
	'#A0522D' : 'Sienna',
	'#556B2F' : 'DarkOliveGreen',
	'#006400' : 'DarkGreen',
	'#483D8B' : 'DarkSlateBlue',
	'#000080' : 'Navy',
	'#4B0082' : 'Indigo',
	'#2F4F4F' : 'DarkSlateGray',
	'#8B0000' : 'DarkRed',
	'#FF8C00' : 'DarkOrange',
	'#808000' : 'Olive',
	'#008000' : 'Green',
	'#008080' : 'Teal',
	'#0000FF' : 'Blue',
	'#708090' : 'SlateGray',
	'#696969' : 'DimGray',
	'#FF0000' : 'Red',
	'#F4A460' : 'SandyBrown',
	'#9ACD32' : 'YellowGreen',
	'#2E8B57' : 'SeaGreen',
	'#48D1CC' : 'MediumTurquoise',
	'#4169E1' : 'RoyalBlue',
	'#800080' : 'Purple',
	'#808080' : 'Gray',
	'#FF00FF' : 'Magenta',
	'#FFA500' : 'Orange',
	'#FFFF00' : 'Yellow',
	'#00FF00' : 'Lime',
	'#00FFFF' : 'Cyan',
	'#00BFFF' : 'DeepSkyBlue',
	'#9932CC' : 'DarkOrchid',
	'#C0C0C0' : 'Silver',
	'#FFC0CB' : 'Pink',
	'#F5DEB3' : 'Wheat',
	'#FFFACD' : 'LemonChiffon',
	'#98FB98' : 'PaleGreen',
	'#AFEEEE' : 'PaleTurquoise',
	'#ADD8E6' : 'LightBlue',
	'#DDA0DD' : 'Plum',
	'#FFFFFF' : 'White'
};

// #############################################################################
// vB_History
// #############################################################################

function vB_History()
{
	this.cursor = -1;
	this.stack = new Array();
}

// =============================================================================
// vB_History methods

vB_History.prototype.move_cursor = function(increment)
{
	var test = this.cursor + increment;
	if (test >= 0 && this.stack[test] != null && typeof this.stack[test] != 'undefined')
	{
		this.cursor += increment;
	}
};

vB_History.prototype.add_snapshot = function(str)
{
	if (this.stack[this.cursor] == str)
	{
		return;
	}
	else
	{
		this.cursor++;
		this.stack[this.cursor] = str;

		if (typeof this.stack[this.cursor + 1] != 'undefined')
		{
			this.stack[this.cursor + 1] = null;
		}
	}
};

vB_History.prototype.get_snapshot = function()
{
	if (typeof this.stack[this.cursor] != 'undefined' && this.stack[this.cursor] != null)
	{
		return this.stack[this.cursor];
	}
	else
	{
		return false;
	}
};

/*======================================================================*\
|| ####################################################################
|| # Downloaded: 17:39, Fri Mar 2nd 2007
|| # CVS: $RCSfile$ - $Revision: 16478 $
|| ####################################################################
\*======================================================================*/
/*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.6.5
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2007 Jelsoft Enterprises Ltd. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

/**
* Initialize AJAX post editing
*
* @param	mixed	ID of element (or actual element) containing postbits
*/
function vB_AJAX_QuickEdit_Init(postobj)
{
	if (AJAX_Compatible)
	{
		if (typeof postobj == 'string')
		{
			postobj = fetch_object(postobj);
		}
		var anchors = fetch_tags(postobj, 'a');
		var postid = 0;
		for (var i = 0; i < anchors.length; i++)
		{
			if (anchors[i].name && anchors[i].name.indexOf('vB::QuickEdit::') != -1)
			{
				anchors[i].onclick = vB_AJAX_QuickEditor_Events.prototype.editbutton_click;
			}
		}
	}
}

// #############################################################################
// vB_AJAX_QuickEditor
// #############################################################################

/**
* Class to allow quick editing of posts within postbit via AJAX
*/
function vB_AJAX_QuickEditor()
{
	this.postid = null;
	this.messageobj = null;
	this.container = null;
	this.originalhtml = null;
	this.ajax = null;
	this.editstate = false;
	this.editorcounter = 0;
	this.pending = false;
}

// =============================================================================
// vB_AJAX_QuickEditor methods

/**
* Check if the AJAX system is ready for us to proceed
*
* @return	boolean
*/
vB_AJAX_QuickEditor.prototype.ready = function()
{
	if (this.editstate || this.pending)
	{
		return false;
	}
	else
	{
		return true;
	}
};

/**
* Prepare to edit a single post
*
* @param	string	Name attribute of clicked link - takes the form of 'vB::QuickEdit::$postid'
*
* @return	boolean	false
*/
vB_AJAX_QuickEditor.prototype.edit = function(anchor_name)
{
	var test_ajax = new vB_AJAX_Handler(true);
	if (!test_ajax.init() || (typeof vb_disable_ajax != 'undefined' && vb_disable_ajax > 0))
	{
		// couldn't initialize, return true to allow click to go through
		return true;
	}

	var tmppostid = anchor_name.substr(anchor_name.lastIndexOf('::') + 2);

	if (this.pending)
	{
		// something is waiting to complete
		return false;
	}
	else if (!this.ready())
	{
		if (this.postid == tmppostid)
		{
			this.full_edit();
			return false;
		}
		this.abort();
	}

	this.editorcounter++;
	this.editorid = 'vB_Editor_QE_' + this.editorcounter;

	this.postid = tmppostid;

	this.messageobj = fetch_object('post_message_' + this.postid);
	this.originalhtml = this.messageobj.innerHTML;

	this.unchanged = null;
	this.unchanged_reason = null;

	this.fetch_editor();

	this.editstate = true;

	return false;
};

/**
* Send an AJAX request to fetch the editor HTML
*/
vB_AJAX_QuickEditor.prototype.fetch_editor = function()
{
	if (fetch_object('progress_' + this.postid))
	{
		fetch_object('progress_' + this.postid).style.display = '';
	}
	document.body.style.cursor = 'wait';
	this.ajax = new vB_AJAX_Handler(true);
	this.ajax.onreadystatechange(this.display_editor);
	this.ajax.send('ajax.php?do=quickedit&p=' + this.postid, 'do=quickedit&p=' + this.postid + '&editorid=' + PHP.urlencode(this.editorid));
	this.pending = true;
};

/**
* Display the editor HTML when AJAX says fetch_editor() is ready
*/
vB_AJAX_QuickEditor.prototype.display_editor = function()
{
	var AJAX = vB_QuickEditor.ajax.handler;

	if (AJAX.readyState == 4 && AJAX.status == 200)
	{
		if (fetch_object('progress_' + vB_QuickEditor.postid))
		{
			fetch_object('progress_' + vB_QuickEditor.postid).style.display = 'none';
		}
		document.body.style.cursor = 'auto';
		vB_QuickEditor.pending = false;

		if (fetch_tag_count(AJAX.responseXML, 'disabled'))
		{
			// this will fire if quick edit has been disabled after the showthread page is loaded
			window.location = 'editpost.php?' + SESSIONURL + 'do=editpost&postid=' + vB_QuickEditor.postid;
		}
		else if (fetch_tag_count(AJAX.responseXML, 'error'))
		{
			// do nothing
		}
		else
		{
			var editor = fetch_tags(AJAX.responseXML, 'editor')[0];
			var reason = editor.getAttribute('reason');

			// display the editor
			vB_QuickEditor.messageobj.innerHTML = vB_QuickEditor.ajax.fetch_data(editor);

			// display the reason
			if (fetch_object(vB_QuickEditor.editorid + '_edit_reason'))
			{
				vB_QuickEditor.unchanged_reason = PHP.unhtmlspecialchars(reason);
				fetch_object(vB_QuickEditor.editorid + '_edit_reason').value = vB_QuickEditor.unchanged_reason;
				fetch_object(vB_QuickEditor.editorid + '_edit_reason').onkeypress = vB_AJAX_QuickEditor_Events.prototype.reason_key_trap;
			}

			// initialize the editor
			vB_Editor[vB_QuickEditor.editorid] = new vB_Text_Editor(
				vB_QuickEditor.editorid,
				editor.getAttribute('mode'),
				editor.getAttribute('parsetype'),
				editor.getAttribute('parsesmilies')
			);

			if (fetch_object(vB_QuickEditor.editorid + '_editor') && fetch_object(vB_QuickEditor.editorid + '_editor').scrollIntoView)
			{
				fetch_object(vB_QuickEditor.editorid + '_editor').scrollIntoView(true);
			}

			vB_Editor[vB_QuickEditor.editorid].editbox.style.width = '100%';
			vB_Editor[vB_QuickEditor.editorid].check_focus();

			vB_QuickEditor.unchanged = vB_Editor[vB_QuickEditor.editorid].get_editor_contents();

			fetch_object(vB_QuickEditor.editorid + '_save').onclick = vB_QuickEditor.save;
			fetch_object(vB_QuickEditor.editorid + '_abort').onclick = vB_QuickEditor.abort;
			fetch_object(vB_QuickEditor.editorid + '_adv').onclick = vB_QuickEditor.full_edit;

			var delbutton = fetch_object(vB_QuickEditor.editorid + '_delete');
			if (delbutton)
			{
				delbutton.onclick = vB_QuickEditor.show_delete;
			}
		}

		if (is_ie)
		{
			AJAX.abort();
		}
	}
};

/**
* Destroy the editor, and use the specified text as the post contents
*
* @param	string	Text of post
*/
vB_AJAX_QuickEditor.prototype.restore = function(post_html, type)
{
	this.hide_errors(true);
	if (this.editorid && vB_Editor[this.editorid] && vB_Editor[this.editorid].initialized)
	{
		vB_Editor[this.editorid].destroy();
	}
	if (type == 'tableobj')
	{
		// usually called when message is saved
		fetch_object('edit' + this.postid).innerHTML = post_html;
	}
	else
	{
		// usually called when message edit is cancelled
		this.messageobj.innerHTML = post_html;
	}

	this.editstate = false;
};

/**
* Cancel the post edit and restore everything to how it started
*
* @param	event	Event object
*/
vB_AJAX_QuickEditor.prototype.abort = function(e)
{
	if (fetch_object('progress_' + vB_QuickEditor.postid))
	{
		fetch_object('progress_' + vB_QuickEditor.postid).style.display = 'none';
	}
	document.body.style.cursor = 'auto';
	vB_QuickEditor.restore(vB_QuickEditor.originalhtml, 'messageobj');
};

/**
* Pass the edits along to the full editpost.php interface
*
* @param	event	Event object
*/
vB_AJAX_QuickEditor.prototype.full_edit = function(e)
{
	var form = new vB_Hidden_Form('editpost.php?do=editpost&postid=' + vB_QuickEditor.postid);

	form.add_variable('do', 'updatepost');
	form.add_variable('s', fetch_sessionhash());
	form.add_variable('advanced', 1);
	// Don't preview - see editpost.php if you want to know why
	//form.add_variable('preview', 'Yes');
	form.add_variable('postid', vB_QuickEditor.postid);
	form.add_variable('wysiwyg', vB_Editor[vB_QuickEditor.editorid].wysiwyg_mode);
	form.add_variable('message', vB_Editor[vB_QuickEditor.editorid].get_editor_contents());
	form.add_variable('reason', fetch_object(vB_QuickEditor.editorid + '_edit_reason').value);

	form.submit_form();
}

/**
* Save the edited post via AJAX
*
* @param	event	Event object
*/
vB_AJAX_QuickEditor.prototype.save = function(e)
{
	var newtext = vB_Editor[vB_QuickEditor.editorid].get_editor_contents();
	var newreason = vB_Editor[vB_QuickEditor.editorid];

	if (newtext == vB_QuickEditor.unchanged && newreason == vB_QuickEditor.unchanged_reason)
	{
		vB_QuickEditor.abort(e);
	}
	else
	{
		fetch_object(vB_QuickEditor.editorid + '_posting_msg').style.display = '';
		document.body.style.cursor = 'wait';

		pc_obj = fetch_object('postcount' + vB_QuickEditor.postid);
		vB_QuickEditor.ajax = new vB_AJAX_Handler(true);
		vB_QuickEditor.ajax.onreadystatechange(vB_QuickEditor.update);
		vB_QuickEditor.ajax.send('editpost.php?do=updatepost&postid=' + vB_QuickEditor.postid,
			'do=updatepost&ajax=1&postid='
			+ vB_QuickEditor.postid
			+ '&wysiwyg=' + vB_Editor[vB_QuickEditor.editorid].wysiwyg_mode
			+ '&message=' + PHP.urlencode(newtext)
			+ '&reason=' + PHP.urlencode(fetch_object(vB_QuickEditor.editorid + '_edit_reason').value)
			+ (pc_obj != null ? '&postcount=' + PHP.urlencode(pc_obj.name) : '')
		);

		vB_QuickEditor.pending = true;
	}
};

/**
* Show the delete dialog
*/
vB_AJAX_QuickEditor.prototype.show_delete = function()
{
	vB_QuickEditor.deletedialog = fetch_object('quickedit_delete');
	if (vB_QuickEditor.deletedialog)
	{
		vB_QuickEditor.deletedialog.style.display = '';

		vB_QuickEditor.deletebutton = fetch_object('quickedit_dodelete');
		vB_QuickEditor.deletebutton.onclick = vB_QuickEditor.delete_post;

		// don't do this stuff for browsers that don't have any defined events
		// to detect changed radio buttons with keyboard navigation
		if (!is_opera && !is_saf)
		{
			vB_QuickEditor.deletebutton.disabled = true;
			vB_QuickEditor.deleteoptions = new Array();

			vB_QuickEditor.deleteoptions['leave'] = fetch_object('rb_del_leave');
			vB_QuickEditor.deleteoptions['soft'] = fetch_object('rb_del_soft');
			vB_QuickEditor.deleteoptions['hard'] = fetch_object('rb_del_hard');

			for (var i in vB_QuickEditor.deleteoptions)
			{
				if (vB_QuickEditor.deleteoptions[i])
				{
					vB_QuickEditor.deleteoptions[i].onclick = vB_QuickEditor.deleteoptions[i].onchange = vB_AJAX_QuickEditor_Events.prototype.delete_button_handler;
				}
			}
		}
	}
};

/**
* Run the delete system
*/
vB_AJAX_QuickEditor.prototype.delete_post = function()
{
	var dontdelete = fetch_object('rb_del_leave');
	if (dontdelete && dontdelete.checked)
	{
		vB_QuickEditor.abort();
		return;
	}

	var form = new vB_Hidden_Form('editpost.php');

	form.add_variable('do', 'deletepost');
	form.add_variable('s', fetch_sessionhash());
	form.add_variable('postid', vB_QuickEditor.postid);
	form.add_variables_from_object(vB_QuickEditor.deletedialog);

	form.submit_form();
};

/**
* Check for errors etc. and initialize restore when AJAX says save() is complete
*
* @return	boolean	false
*/
vB_AJAX_QuickEditor.prototype.update = function()
{
	var AJAX = vB_QuickEditor.ajax.handler;

	if (AJAX.readyState == 4 && AJAX.status == 200)
	{
		vB_QuickEditor.pending = false;
		document.body.style.cursor = 'auto';
		fetch_object(vB_QuickEditor.editorid + '_posting_msg').style.display = 'none';

		// this is the nice error handler, of which Safari makes a mess
		if (fetch_tag_count(AJAX.responseXML, 'error'))
		{
			var errors = fetch_tags(AJAX.responseXML, 'error');

			var error_html = '<ol>';
			for (var i = 0; i < errors.length; i++)
			{
				error_html += '<li>' + vB_QuickEditor.ajax.fetch_data(errors[i]) + '</li>';
			}
			error_html += '</ol>';

			vB_QuickEditor.show_errors('<ol>' + error_html + '</ol>');
		}
		else
		{
			vB_QuickEditor.restore(vB_QuickEditor.ajax.fetch_data(fetch_tags(AJAX.responseXML, 'postbit')[0]), 'tableobj');
			PostBit_Init(fetch_object('post' + vB_QuickEditor.postid), vB_QuickEditor.postid);
		}

		if (is_ie)
		{
			AJAX.abort();
		}
	}

	return false;
};

/**
* Pop up a window showing errors
*
* @param	string	Error HTML
*/
vB_AJAX_QuickEditor.prototype.show_errors = function(errortext)
{
	fetch_object('ajax_post_errors_message').innerHTML = errortext;
	var errortable = fetch_object('ajax_post_errors');
	errortable.style.width = '400px';
	errortable.style.zIndex = 500;
	var measurer = (is_saf ? 'body' : 'documentElement');
	errortable.style.left = (is_ie ? document.documentElement.clientWidth : self.innerWidth) / 2 - 200 + document[measurer].scrollLeft + 'px';
	errortable.style.top = (is_ie ? document.documentElement.clientHeight : self.innerHeight) / 2 - 150 + document[measurer].scrollTop + 'px';
	errortable.style.display = '';
};

/**
* Hide the error window
*/
vB_AJAX_QuickEditor.prototype.hide_errors = function(skip_focus_check)
{
	this.errors = false;
	fetch_object('ajax_post_errors').style.display = 'none';
	if (skip_focus_check != true)
	{
		vB_Editor[this.editorid].check_focus();
	}
};

// =============================================================================
// vB_AJAX_QuickEditor Event Handlers

/**
* Class to handle quick editor events
*/
function vB_AJAX_QuickEditor_Events()
{
}

/**
* Handles clicks on edit buttons of postbits
*/
vB_AJAX_QuickEditor_Events.prototype.editbutton_click = function(e)
{
	return vB_QuickEditor.edit(this.name);
};

/**
* Handles manipulation of form elements in the delete section
*/
vB_AJAX_QuickEditor_Events.prototype.delete_button_handler = function(e)
{
	if (this.id == 'rb_del_leave' && this.checked)
	{
		vB_QuickEditor.deletebutton.disabled = true;
	}
	else
	{
		vB_QuickEditor.deletebutton.disabled = false;
	}
}

/**
* Key trapper for reason box
*/
vB_AJAX_QuickEditor_Events.prototype.reason_key_trap = function(e)
{
	e = e ? e : window.event;

	switch (e.keyCode)
	{
		case 9: // tab
		{
			fetch_object(vB_QuickEditor.editorid + '_save').focus();
			return false;
		}
		break;

		case 13: // enter
		{
			vB_QuickEditor.save();
			return false;
		}
		break;

		default:
		{
			return true;
		}
	}
}

// #############################################################################
// initialize the editor class

var vB_QuickEditor = new vB_AJAX_QuickEditor();

/*======================================================================*\
|| ####################################################################
|| # Downloaded: 17:39, Fri Mar 2nd 2007
|| # CVS: $RCSfile$ - $Revision: 15047 $
|| ####################################################################
\*======================================================================*//*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.6.5
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2007 Jelsoft Enterprises Ltd. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

var qr_repost = false;
var qr_errors_shown = false;
var qr_active = false;
var qr_posting = 0;
var clickedelm = false;

/**
* Initializes the quick reply system
*/
function qr_init()
{
	qr_disable_controls();
	qr_init_buttons(fetch_object('posts'));
}

/**
* Steps through the given object activating all quick reply buttons it finds
*
* @param	object	HTML object to search
*/
function qr_init_buttons(obj)
{
	var anchors = fetch_tags(obj, 'a');
	for (var i = 0; i < anchors.length; i++)
	{
		if (anchors[i].id && anchors[i].id.substr(0, 3) == 'qr_')
		{
			anchors[i].onclick = function(e) { return qr_activate(this.id.substr(3)); };
		}
	}
}

/**
* Disables the controls in the quick reply system
*/
function qr_disable_controls()
{
	if (require_click)
	{
		fetch_object('qr_postid').value = 0;

		vB_Editor[QR_EditorID].disable_editor(vbphrase['click_quick_reply_icon']);

		var qr_sig = fetch_object('cb_signature');
		if (qr_sig != null)
		{
			qr_sig.disabled = true;
		}

		active = false;
	}
	else
	{
		vB_Editor[QR_EditorID].write_editor_contents('');
	}

	if (threaded_mode != 1)
	{
		fetch_object('qr_quickreply').disabled = true;
	}

	qr_active = false;
}

/**
* Activates the controls in the quick reply system
*
* @param	integer	Post ID of the post to which we are replying
*
* @return	boolean	false
*/
function qr_activate(postid)
{
	var qr_collapse = fetch_object('collapseobj_quickreply');
	if (qr_collapse && qr_collapse.style.display == "none")
	{
		toggle_collapse('quickreply');
	}

	fetch_object('qr_postid').value = postid;
	fetch_object('qr_preview').select();
	fetch_object('qr_quickreply').disabled = false;

	var qr_sig = fetch_object("cb_signature");
	if (qr_sig)
	{
		qr_sig.disabled = false;
		// Workaround for 3.5 Bug # 1618: Set checked as Firefox < 1.5 "forgets" that when checkbox is disabled via JS
		qr_sig.checked = true;
	}

	if (qr_active == false)
	{
		vB_Editor[QR_EditorID].enable_editor('');
	}

	if (!is_ie && vB_Editor[QR_EditorID].wysiwyg_mode)
	{
		fetch_object('qr_scroll').scrollIntoView(false);
	}

	vB_Editor[QR_EditorID].check_focus();

	qr_active = true;

	return false;
}

/**
* Checks the contents of the new reply and decides whether or not to allow it through
*
* @param	object	<form> object containing quick reply
* @param	integer	Minimum allowed characters in message
*
* @return	boolean
*/
function qr_prepare_submit(formobj, minchars)
{
	if (qr_repost == true)
	{
		return true;
	}

	if (!allow_ajax_qr || !AJAX_Compatible)
	{
		// not last page, or threaded mode - do not attempt to use AJAX
		return qr_check_data(formobj, minchars);
	}
	else if (qr_check_data(formobj, minchars))
	{
		var test_ajax = new vB_AJAX_Handler(true);
		if (!test_ajax.init() || (typeof vb_disable_ajax != 'undefined' && vb_disable_ajax > 0))
		{
			// couldn't initialize, return true to allow click to go through
			return true;
		}

		if (is_ie && userAgent.indexOf('msie 5.') != -1)
		{
			// IE 5 has problems with non-ASCII characters being returned by
			// AJAX. Don't universally disable it, but if we're going to be sending
			// non-ASCII, let's not use AJAX.
			if (PHP.urlencode(formobj.message.value).indexOf('%u') != -1)
			{
				return true;
			}
		}

		if (qr_posting == 1)
		{
			return false;
		}
		else
		{
			qr_posting = 1;
			setTimeout("qr_posting = 0", 1000);
		}

		if (clickedelm == formobj.preview.value)
		{
			return true;
		}
		else
		{
			var submitstring = 'ajax=1';
			if (typeof ajax_last_post != 'undefined')
			{
				submitstring += '&ajax_lastpost=' + PHP.urlencode(ajax_last_post);
			}

			for (i = 0; i < formobj.elements.length; i++)
			{
				var obj = formobj.elements[i];

				if (obj.name && !obj.disabled)
				{
					switch (obj.type)
					{
						case 'text':
						case 'textarea':
						case 'hidden':
							submitstring += '&' + obj.name + '=' + PHP.urlencode(obj.value);
							break;
						case 'checkbox':
						case 'radio':
							submitstring += obj.checked ? '&' + obj.name + '=' + PHP.urlencode(obj.value) : '';
							break;
						case 'select':
							submitstring += '&' + obj.name + '=' + PHP.urlencode(obj.options[obj.selectedIndex].value);
							break;
					}
				}
			}

			fetch_object('qr_posting_msg').style.display = '';
			document.body.style.cursor = 'wait';

			qr_ajax_post(formobj.action, submitstring);
			return false;
		}
	}
	else
	{
		return false;
	}
}

/**
* Works with form data to decide what to do
*
* @param	object	<form> object containing quick reply
* @param	integer	Minimum allowed characters in message
*
* @return	boolean
*/
function qr_check_data(formobj, minchars)
{
	switch (fetch_object('qr_postid').value)
	{
		case '0':
		{
			alert(vbphrase['click_quick_reply_icon']);
			return false;
		}

		case 'who cares':
		{
			if (typeof formobj.quickreply != 'undefined')
			{
				formobj.quickreply.checked = false;
			}
			break;
		}
	}

	if (clickedelm == formobj.preview.value)
	{
		minchars = 0;
	}

	return vB_Editor[QR_EditorID].prepare_submit(0, minchars);
}

/**
* Sends quick reply data to newreply.php via AJAX
*
* @param	string	GET string for action (newreply.php)
* @param	string	String representing form data ('x=1&y=2&z=3' etc.)
*/
function qr_ajax_post(submitaction, submitstring)
{
	qr_repost = false;
	xml = new vB_AJAX_Handler(true);
	xml.onreadystatechange(qr_do_ajax_post);
	xml.send(submitaction, submitstring);
}

/**
* Handles quick reply data when AJAX says qr_ajax_post() is complete
*/
function qr_do_ajax_post()
{
	if (xml.handler.readyState == 4 && xml.handler.status == 200)
	{
		if (xml.handler.responseXML)
		{
			document.body.style.cursor = 'auto';
			fetch_object('qr_posting_msg').style.display = 'none';
			qr_posting = 0;

			if (fetch_tag_count(xml.handler.responseXML, 'postbit'))
			{
				ajax_last_post = xml.fetch_data(fetch_tags(xml.handler.responseXML, 'time')[0]);
				qr_disable_controls();
				qr_hide_errors();

				var postbits = fetch_tags(xml.handler.responseXML, 'postbit');
				for (var i = 0; i < postbits.length; i++)
				{
					var newdiv = document.createElement('div');
					newdiv.innerHTML = xml.fetch_data(postbits[i]);
					var lp = fetch_object('lastpost');
					var lpparent = lp.parentNode;
					var postbit = lpparent.insertBefore(newdiv, lp);

					PostBit_Init(postbit, postbits[i].getAttribute('postid'));
				}

				// unfocus the qr_submit button to prevent a space from resubmitting
				if (fetch_object('qr_submit'))
				{
					fetch_object('qr_submit').blur();
				}
			}
			else
			{
				if (!is_saf)
				{
					// this is the nice error handler, of which Safari makes a mess
					var errors = fetch_tags(xml.handler.responseXML, 'error');

					var error_html = '<ol>';
					for (var i = 0; i < errors.length; i++)
					{
						error_html += '<li>' + xml.fetch_data(errors[i]) + '</li>';
					}
					error_html += '</ol>';

					qr_show_errors('<ol>' + error_html + '</ol>');

					if (is_ie)
					{
						xml.handler.abort();
					}

					return false;
				}

				// this is the not so nice error handler, which is a fallback in case the previous one doesn't work

				qr_repost = true;
				fetch_object('qrform').submit();
			}
		}
		else
		{
			qr_repost = true;
			fetch_object('qrform').submit();
		}

		if (is_ie)
		{
			// in my tests, FF freaks out if I do this abort call here...
			// however, it seems to be necessary (in local tests) for IE
			xml.handler.abort();
		}
	}
}

/**
* Un-hides the quick reply errors element
*
* @param	string	Error(s) to show
*
* @return	boolean	false
*/
function qr_show_errors(errortext)
{
	qr_errors_shown = true;
	fetch_object('qr_error_td').innerHTML = errortext;
	fetch_object('qr_error_tbody').style.display = '';
	vB_Editor[QR_EditorID].check_focus();
	return false;
}

/**
* Hides the quick reply errors element
*
* @return	boolean	false
*/
function qr_hide_errors()
{
	if (qr_errors_shown)
	{
		qr_errors_shown = true;
		fetch_object('qr_error_tbody').style.display = 'none';
		return false;
	}
}

var vB_QuickReply = true;

/*======================================================================*\
|| ####################################################################
|| # Downloaded: 17:39, Fri Mar 2nd 2007
|| # CVS: $RCSfile$ - $Revision: 15827 $
|| ####################################################################
\*======================================================================*//*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.6.5
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2007 Jelsoft Enterprises Ltd. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

/**
* Array to store initialized vB_AJAX_ReadMarker objects
*
* @var	array
*/
var vB_ReadMarker = new Array()
var vB_ReadMarker = {
	'forum_statusicon_prefix'  : 'forum_statusicon_',
	'thread_statusicon_prefix' : 'thread_statusicon_',
	'thread_gotonew_prefix'    : 'thread_gotonew_',
	'thread_title_prefix'      : 'thread_title_'
};

// #############################################################################
// vB_AJAX_ReadMarker
// #############################################################################

/**
* vBulletin AJAX forum read marker class
*
* Allows a forum, its child forums and all contained threads to be marked as read
*
* @param	integer	Forum ID to be marked as read
*/
function vB_AJAX_ReadMarker(forumid)
{
	this.forumid = forumid;
};

/**
* Initializes the AJAX request to mark the forum as read
*/
vB_AJAX_ReadMarker.prototype.mark_read = function()
{
	forumid = this.forumid;
	this.ajax = new vB_AJAX_Handler(true);
	this.ajax.onreadystatechange(vB_ReadMarker[forumid].ajax_check);
	this.ajax.send('ajax.php?do=markread&f=' + this.forumid, 'do=markread&forumid=' + this.forumid);
};

/**
* Receives the AJAX response and passes the XML to the handle_xml function
*
* @return	boolean	false
*/
vB_AJAX_ReadMarker.prototype.ajax_check = function()
{
	var AJAX = vB_ReadMarker[forumid].ajax.handler;

	if (AJAX.readyState == 4 && AJAX.status == 200)
	{
		if (AJAX.responseXML)
		{
			vB_ReadMarker[forumid].handle_forums_xml(AJAX.responseXML);
		}

		if (is_ie)
		{
			AJAX.abort();
		}
	}

	return false;
};

/**
* Handles the XML response from the AJAX response
*
* Passes forum IDs in XML to handler functions
*
* @param	string	XML containing <forum> nodes with forum ID contents
*/
vB_AJAX_ReadMarker.prototype.handle_forums_xml = function(forums_xml)
{
	var forum_nodes = fetch_tags(forums_xml, 'forum');
	for (var i = 0; i < forum_nodes.length; i++)
	{
		var forumid = this.ajax.fetch_data(forum_nodes[i]);
		this.update_forum_status(forumid);

		var threadbits_object = fetch_object('threadbits_forum_' + forumid);
		if (threadbits_object)
		{
			this.handle_threadbits(threadbits_object);
		}
	}
};

/**
* Updates the status of a 'forumbit*' template
*
* @param	integer	Forum ID
*/
vB_AJAX_ReadMarker.prototype.update_forum_status = function(forumid)
{
	var imageobj = fetch_object(vB_ReadMarker['forum_statusicon_prefix'] + forumid);
	if (imageobj)
	{
		imageobj.style.cursor = 'default';
		imageobj.title = imageobj.otitle;
		imageobj.src = this.fetch_old_src(imageobj.src, 'forum');
	}
};

/**
* Scans the provided object for gotonew links in threads
*
* @param	object	HTML object containing 'threadbit*' templates
*/
vB_AJAX_ReadMarker.prototype.handle_threadbits = function(threadbits_object)
{
	var links = fetch_tags(threadbits_object, 'a');
	for (var i = 0; i < links.length; i++)
	{
		if (links[i].id && links[i].id.substr(0, vB_ReadMarker['thread_gotonew_prefix'].length) == vB_ReadMarker['thread_gotonew_prefix'])
		{
			this.update_thread_status(links[i].id.substr(vB_ReadMarker['thread_gotonew_prefix'].length));
		}
	}
};

/**
* Updates the status of a 'threadbit*' template
*
* @param	integer	Thread ID
*/
vB_AJAX_ReadMarker.prototype.update_thread_status = function(threadid)
{
	var statusicon = fetch_object(vB_ReadMarker['thread_statusicon_prefix'] + threadid);
	if (statusicon)
	{
		statusicon.src = this.fetch_old_src(statusicon.src, 'thread');
	}

	var gotonew = fetch_object(vB_ReadMarker['thread_gotonew_prefix'] + threadid);
	if (gotonew)
	{
		gotonew.parentNode.removeChild(gotonew);
	}

	var threadtitle = fetch_object(vB_ReadMarker['thread_title_prefix'] + threadid);
	if (threadtitle)
	{
		threadtitle.style.fontWeight = 'normal';
	}
};

/**
* Converts an image source from x_new.y to the appropriate x_old.y format
*
* @param	string	Original image source
* @param	string	Type ('forum' or 'thread')
*
* @return	string	New image source
*/
vB_AJAX_ReadMarker.prototype.fetch_old_src = function(newsrc, type)
{
	var foo = newsrc.replace(/_(new)([\._])(.+)$/i, (type == 'thread' ? '$2$3' : '_old$2$3'));
	return foo;
};

// #############################################################################
// Ancilliary functions
// #############################################################################

/**
* Initializes a request to mark a forum and its children as read
*
* @param	integer	Forum ID to be marked as read
*
* @return	boolean	false
*/
function mark_forum_read(forumid)
{
	if (AJAX_Compatible)
	{
		vB_ReadMarker[forumid] = new vB_AJAX_ReadMarker(forumid);
		vB_ReadMarker[forumid].mark_read();
	}
	else
	{
		window.location = 'forumdisplay.php?' + SESSIONURL + 'do=markread&forumid=' + forumid;
	}

	return false;
};

/**
* Translates the ID of a scanned object into a forum ID and passes it to mark_forum_read()
*
* @param	event
*/
function init_forum_readmarker_icon(e)
{
	mark_forum_read(this.id.substr(vB_ReadMarker['forum_statusicon_prefix'].length));
};

/**
* Scans images on a page for forum status icons indicating that they contain new posts
* then initializes them to activate the read marking system on double-click
*/
function init_forum_readmarker_system()
{
	var images = fetch_tags(document, 'img');
	for (var i = 0; i < images.length; i++)
	{
		if (images[i].id && images[i].id.substr(0, vB_ReadMarker['forum_statusicon_prefix'].length) == vB_ReadMarker['forum_statusicon_prefix'])
		{
			if (images[i].src.search(/\/([^\/]+)(new)(_lock)?\.([a-z0-9]+)$/i) != -1)
			{
				img_alt_2_title(images[i]);
				images[i].otitle = images[i].title;
				images[i].title = vbphrase['doubleclick_forum_markread'];
				images[i].style.cursor = pointer_cursor;
				images[i].ondblclick = init_forum_readmarker_icon;
			}
		}
	}
};

/*======================================================================*\
|| ####################################################################
|| # Downloaded: 17:39, Fri Mar 2nd 2007
|| # CVS: $RCSfile$ - $Revision: 15485 $
|| ####################################################################
\*======================================================================*//************************************************************************************
* [Patent Pending] vBSEO 3.0.0 RC8 for vBulletin v3.x.x by Crawlability, Inc.       *
*-----------------------------------------------------------------------------------*
*                                                                                   *
* vBSEO AJAX Functions (LinkBacks Moderation)                                       *
*                                                                                   *
* Sales Email: sales@crawlability.com                                               *
*                                                                                   *
*----------------------------vBSEO IS NOT FREE SOFTWARE-----------------------------*
* http://www.crawlability.com/vbseo/license.html                                    *
************************************************************************************/

function vBSEO_linkback_mod_Init(linkback_id, action)
{
    if(AJAX_Compatible)
    {
		new vBSEO_linkback(linkback_id, action)
	}
	return false
}

function vBSEO_linkback(linkback_id, action)
{
	this.link_ajax = null
	this.linkback_id = linkback_id
	this.action = action
	var me = this;

    this.linkback_mod = function()
    {
       	this.link_ajax = new vB_AJAX_Handler(true);
       	this.link_ajax.onreadystatechange(this.linkback_mod_ready);
       	this.link_ajax.send('ajax.php?do=linkbackmod&id=' + this.linkback_id + '&action=' + this.action, 'do=linkbackmod&id=' + this.linkback_id + '&action=' + this.action);
  		if(this.action == 'mod')
  		{
			var linkback_img = fetch_object('linkbackimg_' + this.linkback_id);
  			var linkback_approved = ( linkback_img.title == vbphrase['vbseo_mod_unapprove'] );
			linkback_img.title = linkback_approved ? vbphrase['vbseo_mod_approve'] : vbphrase['vbseo_mod_unapprove'];

  			var linkback_row = fetch_object('linkback_' + this.linkback_id);
  	     	var linkback_cells = fetch_tags(linkback_row, 'td');
           	for (var i = 0; i < linkback_cells.length; i++)
           	{
       			linkback_cells[i].className = linkback_approved ? 'inlinemod' : ('alt'+( (i%2) ? '2' : '1')) ;
  	     	}

  		}else
  		if(this.action == 'del')
  		{
  			var linkback_row = fetch_object('linkback_' + this.linkback_id);
  			linkback_row.style.display = 'none'
  		}

        return false;
    }

    this.linkback_mod_ready = function()
    {
    	if (me.link_ajax.handler.readyState == 4 && me.link_ajax.handler.status == 200)
    	{
    		if (me.link_ajax.handler.responseText)
    		{
    		}

    		if (is_ie)
    		{
    			me.link_ajax.handler.abort();
    		}
    	}
    }

    return this.linkback_mod ()
}
/*======================================================================*\
|| #################################################################### ||
|| # vBulletin 3.6.5
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2000-2007 Jelsoft Enterprises Ltd. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

// #############################################################################
// set up some variables

// see if browser is compatible...
if (document.all && navigator.appVersion.charAt(navigator.appVersion.indexOf("MSIE")+5) >= 5 && navigator.userAgent.toLowerCase().indexOf('opera') == -1)
{
	ie5 = true;
}
else
{
	ie5 = false;
}

// #############################################################################
// initialize data arrays
var pd = new Array(); // posts data
var pn = new Array(); // next / prev info
var pu = new Array(); // user names

// #############################################################################
// initlialize image / image string caches
var imgStringCache = new Array();
var imgCache = new Array();
imgCache = {
	"I"  : '<img src="' + imgdir_misc + '/tree_i.gif" alt="" />',
	"L"  : '<img src="' + imgdir_misc + '/tree_l.gif" alt="" />',
	"T"  : '<img src="' + imgdir_misc + '/tree_t.gif" alt="" />'
};

// #############################################################################
// function to show the next or previous post
function showPrevNextPost(nextOrPrev)
{
	info = pn[curpostid].split(',');
	showPost(info[nextOrPrev]);
}

// #############################################################################
// function to set quick reply postid
function setQRpostid(postid)
{
	if (quickreply)
	{
		fetch_object("qr_postid").value = postid;
	}
}

// #############################################################################
// function to navigate to a new post
function navToPost(postid, noreload)
{
	if (postid != 0 && !noreload)
	{
		window.location = "showthread.php?" + SESSIONURL + "p=" + postid + "#poststop";
	}
}

// #############################################################################
// function to show a post, either via JS, or to click a link...
function showPost(postid, noreload)
{
	if (typeof pd[postid] != 'undefined')
	{
		try
		{
			if (quickreply)
			{
				fetch_object("qr_postid").value = postid;
			}

			// set the old selected link to normal font weight
			fetch_object("link" + curpostid).style.fontWeight = "normal";
			fetch_object("div" + curpostid).className = 'alt1';

			// set the new selected link to bold font weight
			fetch_object("link" + postid).style.fontWeight = "bold";
			fetch_object("div" + postid).className = 'alt2';

			try
			{
				// scroll the posts table back into view
				fetch_object("links").scrollIntoView(true);
			}
			catch(e)
			{
				// can't use scrollintoview
			}

			// set the innerHTML of the 'posts' element to the cached post data
			fetch_object("posts").innerHTML = pd[postid];

			// set the first and last postids of the page to the current post
			FIRSTPOSTID = postid;
			LASTPOSTID = postid;
		}
		catch(e)
		{
			navToPost(postid, noreload);
		}
	}
	else
	{
		navToPost(postid, noreload);
	}

	// set the current postid to the clicked link postid
	curpostid = postid;
	return false;
}

// #############################################################################
// function to write a span end
function writeLink(postid, isnew, attachment, userid, imgString, title, datestring, timestring, doshowpost)
{
	// get the bgclass for the row and then write it
	if (postid == curpostid || doshowpost)
	{
		bgclass = 'alt2';
	}
	else
	{
		bgclass = 'alt1';
	}
	document.write('<div class="' + bgclass + '" id="div' + postid + '">');

	// check to see if we have already cached the result of this image string
	if (!imgStringCache[imgString])
	{
		// not cached - we need to build the image string and stick it into the cache
		imgStringCache[imgString] = "";

		// split the imgString and write out the appropriate images...
		imgArray = imgString.split(',');

		for (i in imgArray)
		{
                        if(typeof imgArray[i]=='function') continue;
			curType = imgArray[i];
			if (isNaN(curType))
			{
				// pull the appropriate image from the imgCache
				imgStringCache[imgString] += imgCache[curType];
			}
			else
			{
				// write a blank image with the appropriate width
				imgStringCache[imgString] += '<img src="' + cleargifurl + '" width="' + (curType * 20) + '" height="20" alt="" />';
			}
		}
	}

	// now write out the cached image string
	document.write(imgStringCache[imgString]);

	// see if we should display the 'new' posticon and then write it
	if (isnew == 1)
	{
		statusicon = 'new';
	}
	else
	{
		statusicon = 'old';
	}
	document.write('<img src="' + imgdir_statusicon + '/post_' + statusicon + '.gif" alt="" /> ');

	if (datestring == 'more')
	{
		document.write('<a href="showthread.php?p=' + postid + highlightwords + '#post' + postid + '" id="link' + postid + '"><i>' + morephrase + '</i></a></div>\n');
	}
	else
	{
		// write the paperclip if the post has an attachment
		if (attachment == 1)
		{
			document.write('<img src="' + imgdir_misc + '/paperclip.gif" alt="PaperClip" title="Attachment" /> ');
		}

		// get username from pu[] array, else "Guest"
		if (typeof pu[userid] != "undefined")
		{
			document.write(pu[userid].bold() + ' ');
		}
		else
		{
			document.write(guestphrase + " ");
		}

		// set the title link
		if (postid == curpostid)
		{
			titlestyle = ' style="font-weight:bold;"';
		}
		else
		{
			titlestyle = '';
		}

		document.write('<a href="showthread.php?p=' + postid + highlightwords + '#post' + postid + '" onclick="return showPost(' + postid + ')" id="link' + postid + '"' + titlestyle + '>' + title + '</a> ');

		// set the time/date string
		if (ie5 && typeof pd[postid] != "undefined")
		{
			iscached = '.';
		}
		else
		{
			iscached = '';
		}
		document.write(datestring + ', <span class="time">' + timestring + iscached + '</span>');

		// complete the string
		document.write('</div>\n');

	}
}

/*======================================================================*\
|| ####################################################################
|| # Downloaded: 17:39, Fri Mar 2nd 2007
|| # CVS: $RCSfile$ - $Revision: 15872 $
|| ####################################################################
\*======================================================================*/
// script.aculo.us builder.js v1.7.0, Fri Jan 19 19:16:36 CET 2007

// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/

var Builder = {
  NODEMAP: {
    AREA: 'map',
    CAPTION: 'table',
    COL: 'table',
    COLGROUP: 'table',
    LEGEND: 'fieldset',
    OPTGROUP: 'select',
    OPTION: 'select',
    PARAM: 'object',
    TBODY: 'table',
    TD: 'table',
    TFOOT: 'table',
    TH: 'table',
    THEAD: 'table',
    TR: 'table'
  },
  // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
  //       due to a Firefox bug
  node: function(elementName) {
    elementName = elementName.toUpperCase();
    
    // try innerHTML approach
    var parentTag = this.NODEMAP[elementName] || 'div';
    var parentElement = document.createElement(parentTag);
    try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
    } catch(e) {}
    var element = parentElement.firstChild || null;
      
    // see if browser added wrapping tags
    if(element && (element.tagName.toUpperCase() != elementName))
      element = element.getElementsByTagName(elementName)[0];
    
    // fallback to createElement approach
    if(!element) element = document.createElement(elementName);
    
    // abort if nothing could be created
    if(!element) return;

    // attributes (or text)
    if(arguments[1])
      if(this._isStringOrNumber(arguments[1]) ||
        (arguments[1] instanceof Array)) {
          this._children(element, arguments[1]);
        } else {
          var attrs = this._attributes(arguments[1]);
          if(attrs.length) {
            try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
              parentElement.innerHTML = "<" +elementName + " " +
                attrs + "></" + elementName + ">";
            } catch(e) {}
            element = parentElement.firstChild || null;
            // workaround firefox 1.0.X bug
            if(!element) {
              element = document.createElement(elementName);
              for(attr in arguments[1]) 
                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
            }
            if(element.tagName.toUpperCase() != elementName)
              element = parentElement.getElementsByTagName(elementName)[0];
            }
        } 

    // text, or array of children
    if(arguments[2])
      this._children(element, arguments[2]);

     return element;
  },
  _text: function(text) {
     return document.createTextNode(text);
  },

  ATTR_MAP: {
    'className': 'class',
    'htmlFor': 'for'
  },

  _attributes: function(attributes) {
    var attrs = [];
    for(attribute in attributes)
      attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
          '="' + attributes[attribute].toString().escapeHTML() + '"');
    return attrs.join(" ");
  },
  _children: function(element, children) {
    if(typeof children=='object') { // array can hold nodes and text
      children.flatten().each( function(e) {
        if(typeof e=='object')
          element.appendChild(e)
        else
          if(Builder._isStringOrNumber(e))
            element.appendChild(Builder._text(e));
      });
    } else
      if(Builder._isStringOrNumber(children)) 
         element.appendChild(Builder._text(children));
  },
  _isStringOrNumber: function(param) {
    return(typeof param=='string' || typeof param=='number');
  },
  build: function(html) {
    var element = this.node('div');
    $(element).update(html.strip());
    return element.down();
  },
  dump: function(scope) { 
    if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope 
  
    var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
      "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
      "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
      "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
      "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
      "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
  
    tags.each( function(tag){ 
      scope[tag] = function() { 
        return Builder.node.apply(Builder, [tag].concat($A(arguments)));  
      } 
    });
  }
}
// script.aculo.us effects.js v1.7.0, Fri Jan 19 19:16:36 CET 2007

// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// Contributors:
//  Justin Palmer (http://encytemedia.com/)
//  Mark Pilgrim (http://diveintomark.org/)
//  Martin Bialasinki
// 
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/ 

// converts rgb() and #xxx to #xxxxxx format,  
// returns self (or first argument) if not convertable  
String.prototype.parseColor = function() {  
  var color = '#';
  if(this.slice(0,4) == 'rgb(') {  
    var cols = this.slice(4,this.length-1).split(',');  
    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
  } else {  
    if(this.slice(0,1) == '#') {  
      if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
      if(this.length==7) color = this.toLowerCase();  
    }  
  }  
  return(color.length==7 ? color : (arguments[0] || this));  
}

/*--------------------------------------------------------------------------*/

Element.collectTextNodes = function(element) {  
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue : 
      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
  }).flatten().join('');
}

Element.collectTextNodesIgnoreClass = function(element, className) {  
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue : 
      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 
        Element.collectTextNodesIgnoreClass(node, className) : ''));
  }).flatten().join('');
}

Element.setContentZoom = function(element, percent) {
  element = $(element);  
  element.setStyle({fontSize: (percent/100) + 'em'});   
  if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
  return element;
}

Element.getOpacity = function(element){
  return $(element).getStyle('opacity');
}

Element.setOpacity = function(element, value){
  return $(element).setStyle({opacity:value});
}

Element.getInlineOpacity = function(element){
  return $(element).style.opacity || '';
}

Element.forceRerendering = function(element) {
  try {
    element = $(element);
    var n = document.createTextNode(' ');
    element.appendChild(n);
    element.removeChild(n);
  } catch(e) { }
};

/*--------------------------------------------------------------------------*/

Array.prototype.call = function() {
  var args = arguments;
  this.each(function(f){ f.apply(this, args) });
}

/*--------------------------------------------------------------------------*/

var Effect = {
  _elementDoesNotExistError: {
    name: 'ElementDoesNotExistError',
    message: 'The specified DOM element does not exist, but is required for this effect to operate'
  },
  tagifyText: function(element) {
    if(typeof Builder == 'undefined')
      throw("Effect.tagifyText requires including script.aculo.us' builder.js library");
      
    var tagifyStyle = 'position:relative';
    if(/MSIE/.test(navigator.userAgent) && !window.opera) tagifyStyle += ';zoom:1';
    
    element = $(element);
    $A(element.childNodes).each( function(child) {
      if(child.nodeType==3) {
        child.nodeValue.toArray().each( function(character) {
          element.insertBefore(
            Builder.node('span',{style: tagifyStyle},
              character == ' ' ? String.fromCharCode(160) : character), 
              child);
        });
        Element.remove(child);
      }
    });
  },
  multiple: function(element, effect) {
    var elements;
    if(((typeof element == 'object') || 
        (typeof element == 'function')) && 
       (element.length))
      elements = element;
    else
      elements = $(element).childNodes;
      
    var options = Object.extend({
      speed: 0.1,
      delay: 0.0
    }, arguments[2] || {});
    var masterDelay = options.delay;

    $A(elements).each( function(element, index) {
      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
    });
  },
  PAIRS: {
    'slide':  ['SlideDown','SlideUp'],
    'blind':  ['BlindDown','BlindUp'],
    'appear': ['Appear','Fade']
  },
  toggle: function(element, effect) {
    element = $(element);
    effect = (effect || 'appear').toLowerCase();
    var options = Object.extend({
      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
    }, arguments[2] || {});
    Effect[element.visible() ? 
      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
  }
};

var Effect2 = Effect; // deprecated

/* ------------- transitions ------------- */

Effect.Transitions = {
  linear: Prototype.K,
  sinoidal: function(pos) {
    return (-Math.cos(pos*Math.PI)/2) + 0.5;
  },
  reverse: function(pos) {
    return 1-pos;
  },
  flicker: function(pos) {
    return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
  },
  wobble: function(pos) {
    return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
  },
  pulse: function(pos, pulses) { 
    pulses = pulses || 5; 
    return (
      Math.round((pos % (1/pulses)) * pulses) == 0 ? 
            ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) : 
        1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2))
      );
  },
  none: function(pos) {
    return 0;
  },
  full: function(pos) {
    return 1;
  }
};

/* ------------- core effects ------------- */

Effect.ScopedQueue = Class.create();
Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
  initialize: function() {
    this.effects  = [];
    this.interval = null;
  },
  _each: function(iterator) {
    this.effects._each(iterator);
  },
  add: function(effect) {
    var timestamp = new Date().getTime();
    
    var position = (typeof effect.options.queue == 'string') ? 
      effect.options.queue : effect.options.queue.position;
    
    switch(position) {
      case 'front':
        // move unstarted effects after this effect  
        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
            e.startOn  += effect.finishOn;
            e.finishOn += effect.finishOn;
          });
        break;
      case 'with-last':
        timestamp = this.effects.pluck('startOn').max() || timestamp;
        break;
      case 'end':
        // start effect after last queued effect has finished
        timestamp = this.effects.pluck('finishOn').max() || timestamp;
        break;
    }
    
    effect.startOn  += timestamp;
    effect.finishOn += timestamp;

    if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
      this.effects.push(effect);
    
    if(!this.interval) 
      this.interval = setInterval(this.loop.bind(this), 15);
  },
  remove: function(effect) {
    this.effects = this.effects.reject(function(e) { return e==effect });
    if(this.effects.length == 0) {
      clearInterval(this.interval);
      this.interval = null;
    }
  },
  loop: function() {
    var timePos = new Date().getTime();
    for(var i=0, len=this.effects.length;i<len;i++) 
      if(this.effects[i]) this.effects[i].loop(timePos);
  }
});

Effect.Queues = {
  instances: $H(),
  get: function(queueName) {
    if(typeof queueName != 'string') return queueName;
    
    if(!this.instances[queueName])
      this.instances[queueName] = new Effect.ScopedQueue();
      
    return this.instances[queueName];
  }
}
Effect.Queue = Effect.Queues.get('global');

Effect.DefaultOptions = {
  transition: Effect.Transitions.sinoidal,
  duration:   1.0,   // seconds
  fps:        60.0,  // max. 60fps due to Effect.Queue implementation
  sync:       false, // true for combining
  from:       0.0,
  to:         1.0,
  delay:      0.0,
  queue:      'parallel'
}

Effect.Base = function() {};
Effect.Base.prototype = {
  position: null,
  start: function(options) {
    this.options      = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
    this.currentFrame = 0;
    this.state        = 'idle';
    this.startOn      = this.options.delay*1000;
    this.finishOn     = this.startOn + (this.options.duration*1000);
    this.event('beforeStart');
    if(!this.options.sync)
      Effect.Queues.get(typeof this.options.queue == 'string' ? 
        'global' : this.options.queue.scope).add(this);
  },
  loop: function(timePos) {
    if(timePos >= this.startOn) {
      if(timePos >= this.finishOn) {
        this.render(1.0);
        this.cancel();
        this.event('beforeFinish');
        if(this.finish) this.finish(); 
        this.event('afterFinish');
        return;  
      }
      var pos   = (timePos - this.startOn) / (this.finishOn - this.startOn);
      var frame = Math.round(pos * this.options.fps * this.options.duration);
      if(frame > this.currentFrame) {
        this.render(pos);
        this.currentFrame = frame;
      }
    }
  },
  render: function(pos) {
    if(this.state == 'idle') {
      this.state = 'running';
      this.event('beforeSetup');
      if(this.setup) this.setup();
      this.event('afterSetup');
    }
    if(this.state == 'running') {
      if(this.options.transition) pos = this.options.transition(pos);
      pos *= (this.options.to-this.options.from);
      pos += this.options.from;
      this.position = pos;
      this.event('beforeUpdate');
      if(this.update) this.update(pos);
      this.event('afterUpdate');
    }
  },
  cancel: function() {
    if(!this.options.sync)
      Effect.Queues.get(typeof this.options.queue == 'string' ? 
        'global' : this.options.queue.scope).remove(this);
    this.state = 'finished';
  },
  event: function(eventName) {
    if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
    if(this.options[eventName]) this.options[eventName](this);
  },
  inspect: function() {
    var data = $H();
    for(property in this)
      if(typeof this[property] != 'function') data[property] = this[property];
    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
  }
}

Effect.Parallel = Class.create();
Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
  initialize: function(effects) {
    this.effects = effects || [];
    this.start(arguments[1]);
  },
  update: function(position) {
    this.effects.invoke('render', position);
  },
  finish: function(position) {
    this.effects.each( function(effect) {
      effect.render(1.0);
      effect.cancel();
      effect.event('beforeFinish');
      if(effect.finish) effect.finish(position);
      effect.event('afterFinish');
    });
  }
});

Effect.Event = Class.create();
Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), {
  initialize: function() {
    var options = Object.extend({
      duration: 0
    }, arguments[0] || {});
    this.start(options);
  },
  update: Prototype.emptyFunction
});

Effect.Opacity = Class.create();
Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    if(!this.element) throw(Effect._elementDoesNotExistError);
    // make this work on IE on elements without 'layout'
    if(/MSIE/.test(navigator.userAgent) && !window.opera && (!this.element.currentStyle.hasLayout))
      this.element.setStyle({zoom: 1});
    var options = Object.extend({
      from: this.element.getOpacity() || 0.0,
      to:   1.0
    }, arguments[1] || {});
    this.start(options);
  },
  update: function(position) {
    this.element.setOpacity(position);
  }
});

Effect.Move = Class.create();
Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    if(!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      x:    0,
      y:    0,
      mode: 'relative'
    }, arguments[1] || {});
    this.start(options);
  },
  setup: function() {
    // Bug in Opera: Opera returns the "real" position of a static element or
    // relative element that does not have top/left explicitly set.
    // ==> Always set top and left for position relative elements in your stylesheets 
    // (to 0 if you do not need them) 
    this.element.makePositioned();
    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
    if(this.options.mode == 'absolute') {
      // absolute movement, so we need to calc deltaX and deltaY
      this.options.x = this.options.x - this.originalLeft;
      this.options.y = this.options.y - this.originalTop;
    }
  },
  update: function(position) {
    this.element.setStyle({
      left: Math.round(this.options.x  * position + this.originalLeft) + 'px',
      top:  Math.round(this.options.y  * position + this.originalTop)  + 'px'
    });
  }
});

// for backwards compatibility
Effect.MoveBy = function(element, toTop, toLeft) {
  return new Effect.Move(element, 
    Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
};

Effect.Scale = Class.create();
Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
  initialize: function(element, percent) {
    this.element = $(element);
    if(!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      scaleX: true,
      scaleY: true,
      scaleContent: true,
      scaleFromCenter: false,
      scaleMode: 'box',        // 'box' or 'contents' or {} with provided values
      scaleFrom: 100.0,
      scaleTo:   percent
    }, arguments[2] || {});
    this.start(options);
  },
  setup: function() {
    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
    this.elementPositioning = this.element.getStyle('position');
    
    this.originalStyle = {};
    ['top','left','width','height','fontSize'].each( function(k) {
      this.originalStyle[k] = this.element.style[k];
    }.bind(this));
      
    this.originalTop  = this.element.offsetTop;
    this.originalLeft = this.element.offsetLeft;
    
    var fontSize = this.element.getStyle('font-size') || '100%';
    ['em','px','%','pt'].each( function(fontSizeType) {
      if(fontSize.indexOf(fontSizeType)>0) {
        this.fontSize     = parseFloat(fontSize);
        this.fontSizeType = fontSizeType;
      }
    }.bind(this));
    
    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
    
    this.dims = null;
    if(this.options.scaleMode=='box')
      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
    if(/^content/.test(this.options.scaleMode))
      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
    if(!this.dims)
      this.dims = [this.options.scaleMode.originalHeight,
                   this.options.scaleMode.originalWidth];
  },
  update: function(position) {
    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
    if(this.options.scaleContent && this.fontSize)
      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
  },
  finish: function(position) {
    if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
  },
  setDimensions: function(height, width) {
    var d = {};
    if(this.options.scaleX) d.width = Math.round(width) + 'px';
    if(this.options.scaleY) d.height = Math.round(height) + 'px';
    if(this.options.scaleFromCenter) {
      var topd  = (height - this.dims[0])/2;
      var leftd = (width  - this.dims[1])/2;
      if(this.elementPositioning == 'absolute') {
        if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
        if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
      } else {
        if(this.options.scaleY) d.top = -topd + 'px';
        if(this.options.scaleX) d.left = -leftd + 'px';
      }
    }
    this.element.setStyle(d);
  }
});

Effect.Highlight = Class.create();
Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    if(!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
    this.start(options);
  },
  setup: function() {
    // Prevent executing on elements not in the layout flow
    if(this.element.getStyle('display')=='none') { this.cancel(); return; }
    // Disable background image during the effect
    this.oldStyle = {};
    if (!this.options.keepBackgroundImage) {
      this.oldStyle.backgroundImage = this.element.getStyle('background-image');
      this.element.setStyle({backgroundImage: 'none'});
    }
    if(!this.options.endcolor)
      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
    if(!this.options.restorecolor)
      this.options.restorecolor = this.element.getStyle('background-color');
    // init color calculations
    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
  },
  update: function(position) {
    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
      return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
  },
  finish: function() {
    this.element.setStyle(Object.extend(this.oldStyle, {
      backgroundColor: this.options.restorecolor
    }));
  }
});

Effect.ScrollTo = Class.create();
Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    this.start(arguments[1] || {});
  },
  setup: function() {
    Position.prepare();
    var offsets = Position.cumulativeOffset(this.element);
    if(this.options.offset) offsets[1] += this.options.offset;
    var max = window.innerHeight ? 
      window.height - window.innerHeight :
      document.body.scrollHeight - 
        (document.documentElement.clientHeight ? 
          document.documentElement.clientHeight : document.body.clientHeight);
    this.scrollStart = Position.deltaY;
    this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
  },
  update: function(position) {
    Position.prepare();
    window.scrollTo(Position.deltaX, 
      this.scrollStart + (position*this.delta));
  }
});

/* ------------- combination effects ------------- */

Effect.Fade = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  var options = Object.extend({
  from: element.getOpacity() || 1.0,
  to:   0.0,
  afterFinishInternal: function(effect) { 
    if(effect.options.to!=0) return;
    effect.element.hide().setStyle({opacity: oldOpacity}); 
  }}, arguments[1] || {});
  return new Effect.Opacity(element,options);
}

Effect.Appear = function(element) {
  element = $(element);
  var options = Object.extend({
  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
  to:   1.0,
  // force Safari to render floated elements properly
  afterFinishInternal: function(effect) {
    effect.element.forceRerendering();
  },
  beforeSetup: function(effect) {
    effect.element.setOpacity(effect.options.from).show(); 
  }}, arguments[1] || {});
  return new Effect.Opacity(element,options);
}

Effect.Puff = function(element) {
  element = $(element);
  var oldStyle = { 
    opacity: element.getInlineOpacity(), 
    position: element.getStyle('position'),
    top:  element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height
  };
  return new Effect.Parallel(
   [ new Effect.Scale(element, 200, 
      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 
     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 
     Object.extend({ duration: 1.0, 
      beforeSetupInternal: function(effect) {
        Position.absolutize(effect.effects[0].element)
      },
      afterFinishInternal: function(effect) {
         effect.effects[0].element.hide().setStyle(oldStyle); }
     }, arguments[1] || {})
   );
}

Effect.BlindUp = function(element) {
  element = $(element);
  element.makeClipping();
  return new Effect.Scale(element, 0,
    Object.extend({ scaleContent: false, 
      scaleX: false, 
      restoreAfterFinish: true,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping();
      } 
    }, arguments[1] || {})
  );
}

Effect.BlindDown = function(element) {
  element = $(element);
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({ 
    scaleContent: false, 
    scaleX: false,
    scaleFrom: 0,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
    },  
    afterFinishInternal: function(effect) {
      effect.element.undoClipping();
    }
  }, arguments[1] || {}));
}

Effect.SwitchOff = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  return new Effect.Appear(element, Object.extend({
    duration: 0.4,
    from: 0,
    transition: Effect.Transitions.flicker,
    afterFinishInternal: function(effect) {
      new Effect.Scale(effect.element, 1, { 
        duration: 0.3, scaleFromCenter: true,
        scaleX: false, scaleContent: false, restoreAfterFinish: true,
        beforeSetup: function(effect) { 
          effect.element.makePositioned().makeClipping();
        },
        afterFinishInternal: function(effect) {
          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
        }
      })
    }
  }, arguments[1] || {}));
}

Effect.DropOut = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left'),
    opacity: element.getInlineOpacity() };
  return new Effect.Parallel(
    [ new Effect.Move(element, {x: 0, y: 100, sync: true }), 
      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
    Object.extend(
      { duration: 0.5,
        beforeSetup: function(effect) {
          effect.effects[0].element.makePositioned(); 
        },
        afterFinishInternal: function(effect) {
          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
        } 
      }, arguments[1] || {}));
}

Effect.Shake = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left') };
    return new Effect.Move(element, 
      { x:  20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
        effect.element.undoPositioned().setStyle(oldStyle);
  }}) }}) }}) }}) }}) }});
}

Effect.SlideDown = function(element) {
  element = $(element).cleanWhitespace();
  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
  var oldInnerBottom = element.down().getStyle('bottom');
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({ 
    scaleContent: false, 
    scaleX: false, 
    scaleFrom: window.opera ? 0 : 1,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makePositioned();
      effect.element.down().makePositioned();
      if(window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
    },
    afterUpdateInternal: function(effect) {
      effect.element.down().setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' }); 
    },
    afterFinishInternal: function(effect) {
      effect.element.undoClipping().undoPositioned();
      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
    }, arguments[1] || {})
  );
}

Effect.SlideUp = function(element) {
  element = $(element).cleanWhitespace();
  var oldInnerBottom = element.down().getStyle('bottom');
  return new Effect.Scale(element, window.opera ? 0 : 1,
   Object.extend({ scaleContent: false, 
    scaleX: false, 
    scaleMode: 'box',
    scaleFrom: 100,
    restoreAfterFinish: true,
    beforeStartInternal: function(effect) {
      effect.element.makePositioned();
      effect.element.down().makePositioned();
      if(window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping().show();
    },  
    afterUpdateInternal: function(effect) {
      effect.element.down().setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' });
    },
    afterFinishInternal: function(effect) {
      effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom});
      effect.element.down().undoPositioned();
    }
   }, arguments[1] || {})
  );
}

// Bug in opera makes the TD containing this element expand for a instance after finish 
Effect.Squish = function(element) {
  return new Effect.Scale(element, window.opera ? 1 : 0, { 
    restoreAfterFinish: true,
    beforeSetup: function(effect) {
      effect.element.makeClipping(); 
    },  
    afterFinishInternal: function(effect) {
      effect.element.hide().undoClipping(); 
    }
  });
}

Effect.Grow = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.full
  }, arguments[1] || {});
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();    
  var initialMoveX, initialMoveY;
  var moveX, moveY;
  
  switch (options.direction) {
    case 'top-left':
      initialMoveX = initialMoveY = moveX = moveY = 0; 
      break;
    case 'top-right':
      initialMoveX = dims.width;
      initialMoveY = moveY = 0;
      moveX = -dims.width;
      break;
    case 'bottom-left':
      initialMoveX = moveX = 0;
      initialMoveY = dims.height;
      moveY = -dims.height;
      break;
    case 'bottom-right':
      initialMoveX = dims.width;
      initialMoveY = dims.height;
      moveX = -dims.width;
      moveY = -dims.height;
      break;
    case 'center':
      initialMoveX = dims.width / 2;
      initialMoveY = dims.height / 2;
      moveX = -dims.width / 2;
      moveY = -dims.height / 2;
      break;
  }
  
  return new Effect.Move(element, {
    x: initialMoveX,
    y: initialMoveY,
    duration: 0.01, 
    beforeSetup: function(effect) {
      effect.element.hide().makeClipping().makePositioned();
    },
    afterFinishInternal: function(effect) {
      new Effect.Parallel(
        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
          new Effect.Scale(effect.element, 100, {
            scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 
            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
        ], Object.extend({
             beforeSetup: function(effect) {
               effect.effects[0].element.setStyle({height: '0px'}).show(); 
             },
             afterFinishInternal: function(effect) {
               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); 
             }
           }, options)
      )
    }
  });
}

Effect.Shrink = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.none
  }, arguments[1] || {});
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();
  var moveX, moveY;
  
  switch (options.direction) {
    case 'top-left':
      moveX = moveY = 0;
      break;
    case 'top-right':
      moveX = dims.width;
      moveY = 0;
      break;
    case 'bottom-left':
      moveX = 0;
      moveY = dims.height;
      break;
    case 'bottom-right':
      moveX = dims.width;
      moveY = dims.height;
      break;
    case 'center':  
      moveX = dims.width / 2;
      moveY = dims.height / 2;
      break;
  }
  
  return new Effect.Parallel(
    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
    ], Object.extend({            
         beforeStartInternal: function(effect) {
           effect.effects[0].element.makePositioned().makeClipping(); 
         },
         afterFinishInternal: function(effect) {
           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
       }, options)
  );
}

Effect.Pulsate = function(element) {
  element = $(element);
  var options    = arguments[1] || {};
  var oldOpacity = element.getInlineOpacity();
  var transition = options.transition || Effect.Transitions.sinoidal;
  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
  reverser.bind(transition);
  return new Effect.Opacity(element, 
    Object.extend(Object.extend({  duration: 2.0, from: 0,
      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
    }, options), {transition: reverser}));
}

Effect.Fold = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height };
  element.makeClipping();
  return new Effect.Scale(element, 5, Object.extend({   
    scaleContent: false,
    scaleX: false,
    afterFinishInternal: function(effect) {
    new Effect.Scale(element, 1, { 
      scaleContent: false, 
      scaleY: false,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping().setStyle(oldStyle);
      } });
  }}, arguments[1] || {}));
};

Effect.Morph = Class.create();
Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    if(!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      style: {}
    }, arguments[1] || {});
    if (typeof options.style == 'string') {
      if(options.style.indexOf(':') == -1) {
        var cssText = '', selector = '.' + options.style;
        $A(document.styleSheets).reverse().each(function(styleSheet) {
          if (styleSheet.cssRules) cssRules = styleSheet.cssRules;
          else if (styleSheet.rules) cssRules = styleSheet.rules;
          $A(cssRules).reverse().each(function(rule) {
            if (selector == rule.selectorText) {
              cssText = rule.style.cssText;
              throw $break;
            }
          });
          if (cssText) throw $break;
        });
        this.style = cssText.parseStyle();
        options.afterFinishInternal = function(effect){
          effect.element.addClassName(effect.options.style);
          effect.transforms.each(function(transform) {
            if(transform.style != 'opacity')
              effect.element.style[transform.style.camelize()] = '';
          });
        }
      } else this.style = options.style.parseStyle();
    } else this.style = $H(options.style)
    this.start(options);
  },
  setup: function(){
    function parseColor(color){
      if(!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
      color = color.parseColor();
      return $R(0,2).map(function(i){
        return parseInt( color.slice(i*2+1,i*2+3), 16 ) 
      });
    }
    this.transforms = this.style.map(function(pair){
      var property = pair[0].underscore().dasherize(), value = pair[1], unit = null;

      if(value.parseColor('#zzzzzz') != '#zzzzzz') {
        value = value.parseColor();
        unit  = 'color';
      } else if(property == 'opacity') {
        value = parseFloat(value);
        if(/MSIE/.test(navigator.userAgent) && !window.opera && (!this.element.currentStyle.hasLayout))
          this.element.setStyle({zoom: 1});
      } else if(Element.CSS_LENGTH.test(value)) 
        var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/),
          value = parseFloat(components[1]), unit = (components.length == 3) ? components[2] : null;

      var originalValue = this.element.getStyle(property);
      return $H({ 
        style: property, 
        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), 
        targetValue: unit=='color' ? parseColor(value) : value,
        unit: unit
      });
    }.bind(this)).reject(function(transform){
      return (
        (transform.originalValue == transform.targetValue) ||
        (
          transform.unit != 'color' &&
          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
        )
      )
    });
  },
  update: function(position) {
    var style = $H(), value = null;
    this.transforms.each(function(transform){
      value = transform.unit=='color' ?
        $R(0,2).inject('#',function(m,v,i){
          return m+(Math.round(transform.originalValue[i]+
            (transform.targetValue[i] - transform.originalValue[i])*position)).toColorPart() }) : 
        transform.originalValue + Math.round(
          ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit;
      style[transform.style] = value;
    });
    this.element.setStyle(style);
  }
});

Effect.Transform = Class.create();
Object.extend(Effect.Transform.prototype, {
  initialize: function(tracks){
    this.tracks  = [];
    this.options = arguments[1] || {};
    this.addTracks(tracks);
  },
  addTracks: function(tracks){
    tracks.each(function(track){
      var data = $H(track).values().first();
      this.tracks.push($H({
        ids:     $H(track).keys().first(),
        effect:  Effect.Morph,
        options: { style: data }
      }));
    }.bind(this));
    return this;
  },
  play: function(){
    return new Effect.Parallel(
      this.tracks.map(function(track){
        var elements = [$(track.ids) || $$(track.ids)].flatten();
        return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) });
      }).flatten(),
      this.options
    );
  }
});

Element.CSS_PROPERTIES = $w(
  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 
  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
  'fontSize fontWeight height left letterSpacing lineHeight ' +
  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
  'right textIndent top width wordSpacing zIndex');
  
Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;

String.prototype.parseStyle = function(){
  var element = Element.extend(document.createElement('div'));
  element.innerHTML = '<div style="' + this + '"></div>';
  var style = element.down().style, styleRules = $H();
  
  Element.CSS_PROPERTIES.each(function(property){
    if(style[property]) styleRules[property] = style[property]; 
  });
  if(/MSIE/.test(navigator.userAgent) && !window.opera && this.indexOf('opacity') > -1) {
    styleRules.opacity = this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1];
  }
  return styleRules;
};

Element.morph = function(element, style) {
  new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {}));
  return element;
};

['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom',
 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each( 
  function(f) { Element.Methods[f] = Element[f]; }
);

Element.Methods.visualEffect = function(element, effect, options) {
  s = effect.gsub(/_/, '-').camelize();
  effect_class = s.charAt(0).toUpperCase() + s.substring(1);
  new Effect[effect_class](element, options);
  return $(element);
};

Element.addMethods();// -----------------------------------------------------------------------------------
//
//	Lightbox v2.02
//	by Lokesh Dhakar - http://www.huddletogether.com
//	3/31/06
//
//	For more information on this script, visit:
//	http://huddletogether.com/projects/lightbox2/
//
//	Licensed under the Creative Commons Attribution 2.5 License - http://creativecommons.org/licenses/by/2.5/
//	
//	Credit also due to those who have helped, inspired, and made their code available to the public.
//	Including: Scott Upton(uptonic.com), Peter-Paul Koch(quirksmode.org), Thomas Fuchs(mir.aculo.us), and others.
//
//
// -----------------------------------------------------------------------------------
/*

	Table of Contents
	-----------------
	Configuration
	Global Variables

	Extending Built-in Objects	
	- Object.extend(Element)
	- Array.prototype.removeDuplicates()
	- Array.prototype.empty()

	Lightbox Class Declaration
	- initialize()
	- start()
	- changeImage()
	- resizeImageContainer()
	- showImage()
	- updateDetails()
	- updateNav()
	- enableKeyboardNav()
	- disableKeyboardNav()
	- keyboardAction()
	- preloadNeighborImages()
	- end()
	
	Miscellaneous Functions
	- getPageScroll()
	- getPageSize()
	- getKey()
	- listenKey()
	- showSelectBoxes()
	- hideSelectBoxes()
	- pause()
	- initLightbox()
	
	Function Calls
	- addLoadEvent(initLightbox)
	
*/
// -----------------------------------------------------------------------------------

//
//	Configuration
//
var fileLoadingImage = "http://static.stopmomstop.com/dev/images/loading.gif";		
var fileBottomNavCloseImage = "http://static.stopmomstop.com/dev/images/closelabel.gif";

var resizeSpeed = 7;	// controls the speed of the image resizing (1=slowest and 10=fastest)

var borderSize = 10;	//if you adjust the padding in the CSS, you will need to update this variable

// -----------------------------------------------------------------------------------

//
//	Global Variables
//
var imageArray = new Array;
var activeImage;
var objLightboxImage;
if(resizeSpeed > 10){ resizeSpeed = 10;}
if(resizeSpeed < 1){ resizeSpeed = 1;}
resizeDuration = (11 - resizeSpeed) * 0.15;

// -----------------------------------------------------------------------------------

//
//	Additional methods for Element added by SU, Couloir
//	- further additions by Lokesh Dhakar (huddletogether.com)
//
Object.extend(Element, {
	getWidth: function(element) {
	   	element = $(element);
	   	return element.offsetWidth; 
	},
	setWidth: function(element,w) {
	   	element = $(element);
    	element.style.width = w +"px";
	},
	setHeight: function(element,h) {
   		element = $(element);
    	element.style.height = h +"px";
	},
	setTop: function(element,t) {
	   	element = $(element);
    	element.style.top = t +"px";
	},
	setSrc: function(element,src) {
    	element = $(element);
    	element.src = src; 
	},
	setHref: function(element,href) {
    	element = $(element);
    	element.href = href; 
	},
	setInnerHTML: function(element,content) {
		element = $(element);
		element.innerHTML = content;
	}
});

// -----------------------------------------------------------------------------------

//
//	Extending built-in Array object
//	- array.removeDuplicates()
//	- array.empty()
//
Array.prototype.removeDuplicates = function () {
	for(i = 1; i < this.length; i++){
		if(this[i][0] == this[i-1][0]){
			this.splice(i,1);
		}
	}
}

// -----------------------------------------------------------------------------------

Array.prototype.empty = function () {
	for(i = 0; i <= this.length; i++){
		this.shift();
	}
}

// -----------------------------------------------------------------------------------

//
//	Lightbox Class Declaration
//	- initialize()
//	- start()
//	- changeImage()
//	- resizeImageContainer()
//	- showImage()
//	- updateDetails()
//	- updateNav()
//	- enableKeyboardNav()
//	- disableKeyboardNav()
//	- keyboardNavAction()
//	- preloadNeighborImages()
//	- end()
//
//	Structuring of code inspired by Scott Upton (http://www.uptonic.com/)
//
var Lightbox = Class.create();

Lightbox.prototype = {
	
	// initialize()
	// Constructor runs on completion of the DOM loading. Loops through anchor tags looking for 
	// 'lightbox' references and applies onclick events to appropriate links. The 2nd section of
	// the function inserts html at the bottom of the page which is used to display the shadow 
	// overlay and the image container.
	//
	initialize: function() {	
		if (!document.getElementsByTagName){ return; }
		var anchors = document.getElementsByTagName('a');

		// loop through all anchor tags
		for (var i=0; i<anchors.length; i++){
			var anchor = anchors[i];
			
			var relAttribute = String(anchor.getAttribute('rel'));
			
			// use the string.match() method to catch 'lightbox' references in the rel attribute
			if (anchor.getAttribute('href') && (relAttribute.toLowerCase().match('lightbox'))){
				anchor.onclick = function () {myLightbox.start(this); return false;}
			}
		}

		// The rest of this code inserts html at the bottom of the page that looks similar to this:
		//
		//	<div id="overlay"></div>
		//	<div id="lightbox">
		//		<div id="outerImageContainer">
		//			<div id="imageContainer">
		//				<img id="lightboxImage">
		//				<div style="" id="hoverNav">
		//					<a href="#" id="prevLink"></a>
		//					<a href="#" id="nextLink"></a>
		//				</div>
		//				<div id="loading">
		//					<a href="#" id="loadingLink">
		//						<img src="images/loading.gif">
		//					</a>
		//				</div>
		//			</div>
		//		</div>
		//		<div id="imageDataContainer">
		//			<div id="imageData">
		//				<div id="imageDetails">
		//					<span id="caption"></span>
		//					<span id="numberDisplay"></span>
		//				</div>
		//				<div id="bottomNav">
		//					<a href="#" id="bottomNavClose">
		//						<img src="images/close.gif">
		//					</a>
		//				</div>
		//			</div>
		//		</div>
		//	</div>


		var objBody = document.getElementsByTagName("body").item(0);
		
		var objOverlay = document.createElement("div");
		objOverlay.setAttribute('id','overlay');
		objOverlay.style.display = 'none';
		objOverlay.onclick = function() { myLightbox.end(); return false; }
		objBody.appendChild(objOverlay);
		
		var objLightbox = document.createElement("div");
		objLightbox.setAttribute('id','lightbox');
		objLightbox.style.display = 'none';
		objBody.appendChild(objLightbox);
	
		var objOuterImageContainer = document.createElement("div");
		objOuterImageContainer.setAttribute('id','outerImageContainer');
		objLightbox.appendChild(objOuterImageContainer);

		var objImageContainer = document.createElement("div");
		objImageContainer.setAttribute('id','imageContainer');
		objOuterImageContainer.appendChild(objImageContainer);
	
		objLightboxImage = document.createElement("img");
		objLightboxImage.setAttribute('id','lightboxImage');
		objLightboxImage.setAttribute('width',''); //needed for proper resizing
		objLightboxImage.setAttribute('height',''); //needed for proper resizing
		objImageContainer.appendChild(objLightboxImage);
	
		var objHoverNav = document.createElement("div");
		objHoverNav.setAttribute('id','hoverNav');
		objImageContainer.appendChild(objHoverNav);
	
		var objPrevLink = document.createElement("a");
		objPrevLink.setAttribute('id','prevLink');
		objPrevLink.setAttribute('href','#');
		objHoverNav.appendChild(objPrevLink);
		
		var objNextLink = document.createElement("a");
		objNextLink.setAttribute('id','nextLink');
		objNextLink.setAttribute('href','#');
		objHoverNav.appendChild(objNextLink);
	
		var objLoading = document.createElement("div");
		objLoading.setAttribute('id','loading');
		objImageContainer.appendChild(objLoading);
	
		var objLoadingLink = document.createElement("a");
		objLoadingLink.setAttribute('id','loadingLink');
		objLoadingLink.setAttribute('href','#');
		objLoadingLink.onclick = function() { myLightbox.end(); return false; }
		objLoading.appendChild(objLoadingLink);
	
		var objLoadingImage = document.createElement("img");
		objLoadingImage.setAttribute('src', fileLoadingImage);
		objLoadingLink.appendChild(objLoadingImage);

		var objImageDataContainer = document.createElement("div");
		objImageDataContainer.setAttribute('id','imageDataContainer');
		objImageDataContainer.className = 'clearfix';
		objLightbox.appendChild(objImageDataContainer);

		var objImageData = document.createElement("div");
		objImageData.setAttribute('id','imageData');
		objImageDataContainer.appendChild(objImageData);
	
		var objImageDetails = document.createElement("div");
		objImageDetails.setAttribute('id','imageDetails');
		objImageData.appendChild(objImageDetails);
	
		var objCaption = document.createElement("span");
		objCaption.setAttribute('id','caption');
		objImageDetails.appendChild(objCaption);
	
		var objNumberDisplay = document.createElement("span");
		objNumberDisplay.setAttribute('id','numberDisplay');
		objImageDetails.appendChild(objNumberDisplay);
		
		var objBottomNav = document.createElement("div");
		objBottomNav.setAttribute('id','bottomNav');
		objImageData.appendChild(objBottomNav);
	
		var objBottomNavCloseLink = document.createElement("a");
		objBottomNavCloseLink.setAttribute('id','bottomNavClose');
		objBottomNavCloseLink.setAttribute('href','#');
		objBottomNavCloseLink.onclick = function() { myLightbox.end(); return false; }
		objBottomNav.appendChild(objBottomNavCloseLink);
	
		var objBottomNavCloseImage = document.createElement("img");
		objBottomNavCloseImage.setAttribute('src', fileBottomNavCloseImage);
		objBottomNavCloseLink.appendChild(objBottomNavCloseImage);
	},
	
	//
	//	start()
	//	Display overlay and lightbox. If image is part of a set, add siblings to imageArray.
	//
	start: function(imageLink) {	

		hideSelectBoxes();

		// stretch overlay to fill page and fade in
		var arrayPageSize = getPageSize();
		Element.setHeight('overlay', arrayPageSize[1]);
		new Effect.Appear('overlay', { duration: 0.2, from: 0.0, to: 0.8 });

		imageArray = [];
		imageNum = 0;		

		if (!document.getElementsByTagName){ return; }
		var anchors = document.getElementsByTagName('a');

		// if image is NOT part of a set..
		if((imageLink.getAttribute('rel') == 'lightbox')){
			// add single image to imageArray
			imageArray.push(new Array(imageLink.getAttribute('href'), imageLink.getAttribute('title')));			
		} else {
		// if image is part of a set..

			// loop through anchors, find other images in set, and add them to imageArray
			for (var i=0; i<anchors.length; i++){
				var anchor = anchors[i];
				if (anchor.getAttribute('href') && (anchor.getAttribute('rel') == imageLink.getAttribute('rel'))){
					imageArray.push(new Array(anchor.getAttribute('href'), anchor.getAttribute('title')));
				}
			}
			imageArray.removeDuplicates();
			while(imageArray[imageNum][0] != imageLink.getAttribute('href')) { imageNum++;}
		}

		// calculate top offset for the lightbox and display 
		var arrayPageSize = getPageSize();
		var arrayPageScroll = getPageScroll();
		var lightboxTop = arrayPageScroll[1] + (arrayPageSize[3] / 15);

		Element.setTop('lightbox', lightboxTop);
		Element.show('lightbox');
		
		this.changeImage(imageNum);
	},

	//
	//	changeImage()
	//	Hide most elements and preload image in preparation for resizing image container.
	//
	changeImage: function(imageNum) {	
		
		activeImage = imageNum;	// update global var

		// hide elements during transition
		Element.show('loading');
		Element.hide('lightboxImage');
		Element.hide('hoverNav');
		Element.hide('prevLink');
		Element.hide('nextLink');
		Element.hide('imageDataContainer');
		Element.hide('numberDisplay');		
		
		imgPreloader = new Image();
		
		// once image is preloaded, resize image container
		imgPreloader.onload=function(){
			Element.setSrc('lightboxImage', imageArray[activeImage][0]);
			
			myLightbox.resizeImageAndContainer(imgPreloader.width, imgPreloader.height);
		}
		imgPreloader.src = imageArray[activeImage][0];
	},
	resizeImageAndContainer: function(imgWidth, imgHeight) {
			useableWidth = 0.9; // 90% of the window
			useableHeight = 0.8; // 80% of the window
			var arrayPageSize = getPageSize();
			

			windowWidth = arrayPageSize[2];
			windowHeight = arrayPageSize[3];
			scaleX = 1; scaleY = 1;
			if ( imgWidth > windowWidth * useableWidth ) scaleX = (windowWidth * useableWidth) / imgWidth;
			if ( imgHeight > windowHeight * useableHeight ) scaleY = (windowHeight * useableHeight) / imgHeight;
			scale = Math.min( scaleX, scaleY );
			imgWidth *= scale;
			imgHeight *= scale;
      
			 objLightboxImage.setAttribute('width', imgWidth);
			 objLightboxImage.setAttribute('height', imgHeight); 
		this.resizeImageContainer(imgWidth, imgHeight);
	},

	//
	//	resizeImageContainer()
	//
	resizeImageContainer: function( imgWidth, imgHeight) {

		// get current height and width
		this.wCur = Element.getWidth('outerImageContainer');
		this.hCur = Element.getHeight('outerImageContainer');

		// scalars based on change from old to new
		this.xScale = ((imgWidth  + (borderSize * 2)) / this.wCur) * 100;
		this.yScale = ((imgHeight  + (borderSize * 2)) / this.hCur) * 100;

		// calculate size difference between new and old image, and resize if necessary
		wDiff = (this.wCur - borderSize * 2) - imgWidth;
		hDiff = (this.hCur - borderSize * 2) - imgHeight;

		if(!( hDiff == 0)){ new Effect.Scale('outerImageContainer', this.yScale, {scaleX: false, duration: resizeDuration, queue: 'front'}); }
		if(!( wDiff == 0)){ new Effect.Scale('outerImageContainer', this.xScale, {scaleY: false, delay: resizeDuration, duration: resizeDuration}); }

		// if new and old image are same size and no scaling transition is necessary, 
		// do a quick pause to prevent image flicker.
		if((hDiff == 0) && (wDiff == 0)){
			if (navigator.appVersion.indexOf("MSIE")!=-1){ pause(250); } else { pause(100);} 
		}

		Element.setHeight('prevLink', imgHeight);
		Element.setHeight('nextLink', imgHeight);
		Element.setWidth( 'imageDataContainer', imgWidth + (borderSize * 2));

		this.showImage();
	},
	
	//
	//	showImage()
	//	Display image and begin preloading neighbors.
	//
	showImage: function(){
		Element.hide('loading');
		new Effect.Appear('lightboxImage', { duration: 0.5, queue: 'end', afterFinish: function(){	myLightbox.updateDetails(); } });
		this.preloadNeighborImages();
	},

	//
	//	updateDetails()
	//	Display caption, image number, and bottom nav.
	//
	updateDetails: function() {
	
		Element.show('caption');
		Element.setInnerHTML( 'caption', imageArray[activeImage][1]);
		
		// if image is part of set display 'Image x of x' 
		if(imageArray.length > 1){
			Element.show('numberDisplay');
			Element.setInnerHTML( 'numberDisplay', "Image " + eval(activeImage + 1) + " of " + imageArray.length);
		}

		new Effect.Parallel(
			[ new Effect.SlideDown( 'imageDataContainer', { sync: true, duration: resizeDuration + 0.25, from: 0.0, to: 1.0 }), 
			  new Effect.Appear('imageDataContainer', { sync: true, duration: 1.0 }) ], 
			{ duration: 0.65, afterFinish: function() { myLightbox.updateNav();} } 
		);
	},

	//
	//	updateNav()
	//	Display appropriate previous and next hover navigation.
	//
	updateNav: function() {

		Element.show('hoverNav');				

		// if not first image in set, display prev image button
		if(activeImage != 0){
			Element.show('prevLink');
			document.getElementById('prevLink').onclick = function() {
				myLightbox.changeImage(activeImage - 1); return false;
			}
		}

		// if not last image in set, display next image button
		if(activeImage != (imageArray.length - 1)){
			Element.show('nextLink');
			document.getElementById('nextLink').onclick = function() {
				myLightbox.changeImage(activeImage + 1); return false;
			}
		}
		
		this.enableKeyboardNav();
	},

	//
	//	enableKeyboardNav()
	//
	enableKeyboardNav: function() {
		document.onkeydown = this.keyboardAction; 
	},

	//
	//	disableKeyboardNav()
	//
	disableKeyboardNav: function() {
		document.onkeydown = '';
	},

	//
	//	keyboardAction()
	//
	keyboardAction: function(e) {
		if (e == null) { // ie
			keycode = event.keyCode;
		} else { // mozilla
			keycode = e.which;
		}

		key = String.fromCharCode(keycode).toLowerCase();
		
		if((key == 'x') || (key == 'o') || (key == 'c')){	// close lightbox
			myLightbox.end();
		} else if(key == 'p'){	// display previous image
			if(activeImage != 0){
				myLightbox.disableKeyboardNav();
				myLightbox.changeImage(activeImage - 1);
			}
		} else if(key == 'n'){	// display next image
			if(activeImage != (imageArray.length - 1)){
				myLightbox.disableKeyboardNav();
				myLightbox.changeImage(activeImage + 1);
			}
		}


	},

	//
	//	preloadNeighborImages()
	//	Preload previous and next images.
	//
	preloadNeighborImages: function(){

		if((imageArray.length - 1) > activeImage){
			preloadNextImage = new Image();
			preloadNextImage.src = imageArray[activeImage + 1][0];
		}
		if(activeImage > 0){
			preloadPrevImage = new Image();
			preloadPrevImage.src = imageArray[activeImage - 1][0];
		}
	
	},

	//
	//	end()
	//
	end: function() {
		this.disableKeyboardNav();
		Element.hide('lightbox');
		new Effect.Fade('overlay', { duration: 0.2});
		showSelectBoxes();
	}
}

// -----------------------------------------------------------------------------------

//
// getPageScroll()
// Returns array with x,y page scroll values.
// Core code from - quirksmode.org
//
function getPageScroll(){

	var yScroll;

	if (self.pageYOffset) {
		yScroll = self.pageYOffset;
	} else if (document.documentElement && document.documentElement.scrollTop){	 // Explorer 6 Strict
		yScroll = document.documentElement.scrollTop;
	} else if (document.body) {// all other Explorers
		yScroll = document.body.scrollTop;
	}

	arrayPageScroll = new Array('',yScroll) 
	return arrayPageScroll;
}

// -----------------------------------------------------------------------------------

//
// getPageSize()
// Returns array with page width, height and window width, height
// Core code from - quirksmode.org
// Edit for Firefox by pHaez
//
function getPageSize(){
	
	var xScroll, yScroll;
	
	if (window.innerHeight && window.scrollMaxY) {	
		xScroll = document.body.scrollWidth;
		yScroll = window.innerHeight + window.scrollMaxY;
	} else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
		xScroll = document.body.scrollWidth;
		yScroll = document.body.scrollHeight;
	} else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
		xScroll = document.body.offsetWidth;
		yScroll = document.body.offsetHeight;
	}
	
	var windowWidth, windowHeight;
	if (self.innerHeight) {	// all except Explorer
		windowWidth = self.innerWidth;
		windowHeight = self.innerHeight;
	} else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
		windowWidth = document.documentElement.clientWidth;
		windowHeight = document.documentElement.clientHeight;
	} else if (document.body) { // other Explorers
		windowWidth = document.body.clientWidth;
		windowHeight = document.body.clientHeight;
	}	
	
	// for small pages with total height less then height of the viewport
	if(yScroll < windowHeight){
		pageHeight = windowHeight;
	} else { 
		pageHeight = yScroll;
	}

	// for small pages with total width less then width of the viewport
	if(xScroll < windowWidth){	
		pageWidth = windowWidth;
	} else {
		pageWidth = xScroll;
	}


	arrayPageSize = new Array(pageWidth,pageHeight,windowWidth,windowHeight) 
	return arrayPageSize;
}

// -----------------------------------------------------------------------------------

//
// getKey(key)
// Gets keycode. If 'x' is pressed then it hides the lightbox.
//
function getKey(e){
	if (e == null) { // ie
		keycode = event.keyCode;
	} else { // mozilla
		keycode = e.which;
	}
	key = String.fromCharCode(keycode).toLowerCase();
	
	if(key == 'x'){
	}
}

// -----------------------------------------------------------------------------------

//
// listenKey()
//
function listenKey () {	document.onkeypress = getKey; }
	
// ---------------------------------------------------

function showSelectBoxes(){
	selects = document.getElementsByTagName("select");
	for (i = 0; i != selects.length; i++) {
		selects[i].style.visibility = "visible";
	}
}

// ---------------------------------------------------

function hideSelectBoxes(){
	selects = document.getElementsByTagName("select");
	for (i = 0; i != selects.length; i++) {
		selects[i].style.visibility = "hidden";
	}
}

// ---------------------------------------------------

//
// pause(numberMillis)
// Pauses code execution for specified time. Uses busy code, not good.
// Code from http://www.faqts.com/knowledge_base/view.phtml/aid/1602
//
function pause(numberMillis) {
	var now = new Date();
	var exitTime = now.getTime() + numberMillis;
	while (true) {
		now = new Date();
		if (now.getTime() > exitTime)
			return;
	}
}

// ---------------------------------------------------



function initLightbox() { myLightbox = new Lightbox(); }
Event.observe(window, 'load', initLightbox, false);/* 
  ------------------------------------------------
  PopMenu Magic menu scripts
  Copyright (c) 2004-2006 Project Seven Development
  www.projectseven.com
  Version: 1.0.3
  ------------------------------------------------
*/
var p7PMp,p7PMct;
function P7_setPM(){ //v1.0.3 by PVII-www.projectseven.com
 var i,d='',h="<sty"+"le type=\"text/css\">",tA=navigator.userAgent.toLowerCase();if(window.opera){
 if(tA.indexOf("opera 5")>-1||tA.indexOf("opera 6")>-1){return;}}if(document.getElementById){
 for(i=1;i<20;i++){d+='ul ';h+="\n#p7PMnav "+d+"{position:absolute;left:-9000px;}";}
 document.write(h+"\n<"+"/sty"+"le>");}}P7_setPM();
function P7_PMtrig(a){ //v1.0.3 by PVII-www.projectseven.com
 var b,t;if(document.p7PMt){clearTimeout(document.p7PMt);}document.p7PMa=1;b=(a.p7PMsub)?'P7_PMshow(':'P7_PMtg(';
 t='document.p7PMt=setTimeout("'+b+a.p7PMcl+')",160)';
 eval (t);
}
function P7_PMshow(a,bp){ //v1.0.3 by PVII-www.projectseven.com
 var u,lv,oft,ofr,uw,uh,pp,aw,ah,adj,mR,mT,wW=0,wH,w1,w2,w3,sct,pw,lc,pwv,xx=0,yy=0,wP=true;
 var iem=(navigator.appVersion.indexOf("MSIE 5")>-1)?true:false,dce=document.documentElement,dby=document.body;document.p7PMa=1;
 if(!bp){P7_PMtg(a);}u=p7PMct[a].p7PMsub;if(u.p7pmax&&u.p7pmax==1){return;}u.p7pmax=1;lv=(p7PMp[0]==1&&p7PMct[a].p7PMlv==1)?true:false;
 p7PMct[a].className=p7PMct[a].className.replace("p7PMtrg","p7PMon");oft=parseInt(p7PMp[3]);ofr=parseInt(p7PMp[4]);
 uw=u.offsetWidth;uh=u.offsetHeight;pp=p7PMct[a];aw=pp.offsetWidth;ah=pp.offsetHeight;while(pp){xx+=(pp.offsetLeft)?pp.offsetLeft:0;
 yy+=(pp.offsetTop)?pp.offsetTop:0;if(window.opera||navigator.userAgent.indexOf("Safari")>-1){
 if(p7PMct[a].p7PMlv!=1&&pp.nodeName=="BODY"){yy-=(pp.offsetTop)?pp.offsetTop:0;}}pp=pp.offsetParent;}
 if(iem&&navigator.userAgent.indexOf("Mac")>-1){yy+=parseInt(dby.currentStyle.marginTop);}adj=parseInt((aw*ofr)/100);mR=(lv)?0:aw-adj;
 adj=parseInt((ah*oft)/100);mT=(lv)?0:(ah-adj)*-1;w3=dby.parentNode.scrollLeft;if(!w3){w3=dby.scrollLeft;}w3=(w3)?w3:0;
 if(dce&&dce.clientWidth){wW=dce.clientWidth+w3;}else if(dby){wW=dby.clientWidth+w3;}if(!wW){wW=0;wP=false;}wH=window.innerHeight;
 if(!wH){wH=dce.clientHeight;if(!wH||wH<=0){wH=dby.clientHeight;}}sct=dby.parentNode.scrollTop;if(!sct){sct=dby.scrollTop;if(!sct){
 sct=window.scrollY?window.scrollY:0;}}pw=xx+mR+uw;if(pw>wW&&wP){mR=uw*-1;mR+=10;if(lv){mR=(wW-xx)-uw;}}lc=xx+mR;if(lc<0){mR=xx*-1;}
 pw=yy+uh+ah+mT-sct;pwv=wH-pw;if(pwv<0){mT+=pwv;if(uh>wH){mT=(yy+ah-sct)*-1;}}u.style.marginLeft=mR+'px';u.style.marginTop=mT+'px';
 if(p7PMp[2]==1){if(!iem){P7_PManim(a,20);}}u.className="p7PMshow";
}
function P7_PMhide(u){ //v1.0.3 by PVII-www.projectseven.com
 var i,tt,ua;u.p7pmax=0;u.className="p7PMhide";ua=u.parentNode.firstChild;ua.className=ua.className.replace("p7PMon","p7PMtrg");
}
function P7_PMtg(a,b){ //v1.0.3 alpha by PVII-www.projectseven.com
 var i,u,tA,tU,pp;tA=p7PMct[a];pp=tA.parentNode;while(pp){if(pp.tagName=="UL"){break;}pp=pp.parentNode;}if(pp){
 tU=pp.getElementsByTagName("UL");for(i=tU.length-1;i>-1;i--){if(b!=1&&tA.p7PMsub==tU[i]){continue;}else{P7_PMhide(tU[i]);}}}
}
function P7_PMclose(evt){ //v1.0.3 by PVII-www.projectseven.com
 var pp,st,tS,m=true;evt=(evt)?evt:((event)?event:null);st=document.p7PMa;if(st!=-1){if(evt){
 tS=(evt.relatedTarget)?evt.relatedTarget:evt.toElement;
 if(tS){ 	
 	try {pp=tS.parentNode;} catch (l){m=false;document.p7PMa=1;}
 	while(pp){if(pp&&pp.id&&pp.id=="p7PMnav"){m=false;
 document.p7PMa=1;break;}pp=pp.parentNode;}}if(m){document.p7PMa=-1;if(document.p7PMt){clearTimeout(document.p7PMt);}
 document.p7PMt=setTimeout("P7_PMclr()",360);}}}
}
function P7_PMclr(){ //v1.0.3 by PVII-www.projectseven.com
 var i,tU,tUU;document.p7PMa=-1;tU=document.getElementById('p7PMnav');if(tU){tUU=tU.getElementsByTagName("UL");if(tUU){
 for(i=tUU.length-1;i>-1;i--){P7_PMhide(tUU[i]);}}}
}
function P7_PManim(a,st){ //v1.0.3 by PVII-www.projectseven.com
 var g=p7PMct[a].p7PMsub,sp=30,inc=20;st=(st>=100)?100:st;g.style.fontSize=st+"%";if(st<100){st+=inc;setTimeout("P7_PManim("+a+","+st+")",sp);}
}
function P7_PMmark(){document.p7PMop=arguments;}
function P7_PMopen(){ //v1.0.3 by PVII-www.projectseven.com
 var i,x,tA,op,pp,wH,tA,aU,r1,k=-1,kk=-1,mt=new Array(1,'','');if(document.p7PMop){mt=document.p7PMop;}op=mt[0];if(op<1){return;}
 tA=document.getElementById('p7PMnav').getElementsByTagName("A");wH=window.location.href;r1=/index\.[\S]*/i;for(i=0;i<tA.length;i++){
 if(tA[i].href){aU=tA[i].href.replace(r1,'');if(op>0){if(tA[i].href==wH||aU==wH){k=i;kk=-1;break;}}if(op==2){if(tA[i].firstChild){
 if(tA[i].firstChild.nodeValue==mt[1]){kk=i;}}}if(op==3 && tA[i].href.indexOf(mt[1])>-1){kk=i;}if(op==4){for(x=1;x<mt.length;x+=2){
 if(wH.indexOf(mt[x])>-1){if(tA[i].firstChild&&tA[i].firstChild.data){if(tA[i].firstChild.data==mt[x+1]){kk=i;break;}}}}}}}k=(kk>k)?kk:k;
 if(k>-1){pp=tA[k].parentNode;while(pp){if(pp.nodeName=="LI"){pp.firstChild.className="p7PMmark"+" "+pp.firstChild.className;}
 pp=pp.parentNode;}}if(kk>-1){document.p7PMad=1;}P7_PMadma();P7_PMadmb();
}
function P7_PMadma(){ //v1.0.3 by PVII-www.projectseven.com
 var s,ss,i,j,a,g,b,c,d,t,h,tA,b,tP,r1,r2,tI,bA,aA,tB=new Array(),bC='',x=0,ur=1,mt=document.p7PMad;g=document.getElementById("p7PMnav");
 b=document.getElementById("pmmcrumb");if(g&&b){c=b.getElementsByTagName("A");if(c&&c[0]){tP=c[0].parentNode.childNodes;r1=/<a/i;r2=/\/a>/i;
 tI=c[0].parentNode.innerHTML;j=tI.search(r1);bA=tI.substring(0,j);j=tI.search(r2);aA=tI.substring(j+3);bC+=(bA)?bA:'';s=(aA)?aA:' &gt ';
 if(!c[0].id||c[0].id!="pmmcn"){if(c[0].href!=window.location.href){tB[0]=c[0];x++;ur=2;}}tA=g.getElementsByTagName("A");for(i=0;i<tA.length;i++){
 if(tA[i].className.indexOf("p7PMmark")>-1){tB[x]=tA[i];x++;}}for(i=0;i<tB.length;i++){ss=(i>0)?s:'';a=(i==tB.length-1)?0:1;
 d=(i==0&&c[0].id)?'id="'+c[0].id+'" ':' ';t=tB[i].firstChild.nodeValue;if(a==1||mt==1||x<ur){bC+=ss+'<a '+d+'hr'+'ef="'+tB[i].href+'">'+t+'</a>';
 }else{bC+=ss+t;}}if(mt==1||i<ur){ss=(i>0)?s:'';bC+=ss+document.title;}c[0].parentNode.innerHTML=bC;}}
}
function P7_PMadmb(){ //v1.0.3 by PVII-www.projectseven.com
 var h='',g,i,tA,b,m=false;g=document.getElementById("p7PMnav");b=document.getElementById("pmmnext");if(g&&b){tA=g.getElementsByTagName("A");
 for(i=tA.length-1;i>-1;i--){if(tA[i].className.indexOf("p7PMmark")>-1){m=true;break;}}if(m){if(i<tA.length-1){i++;}else{i=0;}
 while(tA[i].href==window.location.href+"#"||tA[i].href=="javascript:;"){i++;if(i>tA.length-1){
 i=0;break;}}b.href=tA[i].href;b.innerHTML=tA[i].firstChild.nodeValue;}}	
}
function P7_initPM(){ //v1.0 by PVII-www.projectseven.com
 var i,g,tD,tA,tU,pp,lvl,ev,tn=navigator.userAgent.toLowerCase();if(window.opera){
 if(tn.indexOf("opera 5")>-1||tn.indexOf("opera 6")>-1){return;}}else if(!document.getElementById){return;}
 p7PMp=arguments;p7PMct=new Array;tD=document.getElementById('p7PMnav');if(tD){tA=tD.getElementsByTagName('A');
 for(i=0;i<tA.length;i++){tA[i].p7PMcl=p7PMct.length;p7PMct[p7PMct.length]=tA[i];g=tA[i].parentNode.getElementsByTagName("UL");
 tA[i].p7PMsub=(g&&g[0])?g[0]:false;
 ev=tA[i].getAttribute("onmouseover");
 if(!ev||ev=='undefined'){
 	tA[i].onmouseover=function(){
    if(typeof P7_PMtrig == 'function'){P7_PMtrig(this);}};}
 ev=tA[i].getAttribute("onfocus");
 if(!ev||ev=='undefined'){
 	tA[i].onfocus=function(){
 		if(typeof P7_PMtrig == 'function'){P7_PMtrig(this);}};}
 if(tA[i].p7PMsub){pp=tA[i].parentNode;lvl=0;while(pp){if(pp.tagName&&pp.tagName=="UL"){lvl++;}pp=pp.parentNode;}
 tA[i].p7PMlv=lvl;}}tD.onmouseout=P7_PMclose;P7_PMopen();}
}//START EDITOR GLOBALS
var fontoptions = new Array("Arial", "Arial Black", "Arial Narrow", "Book Antiqua", "Century Gothic", "Comic Sans MS", "Courier New", "Fixedsys", "Franklin Gothic Medium", "Garamond", "Georgia", "Impact", "Lucida Console", "Lucida Sans Unicode", "Microsoft Sans Serif", "Palatino Linotype", "System", "Tahoma", "Times New Roman", "Trebuchet MS", "Verdana");
var sizeoptions = new Array(1, 2, 3, 4, 5, 6, 7);
var smilieoptions = new Array(); smilieoptions = {  };
var istyles = new Array(); istyles = { "pi_button_down" : [ "#98B5E2", "#000000", "0px", "1px solid #316AC5" ], "pi_button_hover" : [ "#C1D2EE", "#000000", "0px", "1px solid #316AC5" ], "pi_button_normal" : [ "#E1E1E2", "#000000", "1px", "none" ], "pi_button_selected" : [ "#F1F6F8", "#000000", "0px", "1px solid #316AC5" ], "pi_menu_down" : [ "#98B5E2", "#316AC5", "0px", "1px solid #316AC5" ], "pi_menu_hover" : [ "#C1D2EE", "#316AC5", "0px", "1px solid #316AC5" ], "pi_menu_normal" : [ "#FFFFFF", "#000000", "0px", "1px solid #FFFFFF" ], "pi_popup_down" : [ "#98B5E2", "#000000", "0px", "1px solid #316AC5" ] };
var smiliewindow_x = 240;
var smiliewindow_y = 280;
var ignorequotechars = 1;
//END EDITOR GLOBALS

function log_out()
{
  ht = document.getElementsByTagName("html");
  ht[0].style.filter = "progid:DXImageTransform.Microsoft.BasicImage(grayscale=1)";
  if (confirm('Are you sure you want to log out?'))
  {
   return true;
  }
  else
  {
   ht[0].style.filter = "";
   return false;
  }
}
function hash_passwords(currentpassword, currentpassword_md5, newpassword, newpassword_md5, newpasswordconfirm, newpasswordconfirm_md5)
{
  var junk_output;
  md5hash(currentpassword, currentpassword_md5, junk_output, 0);
  // do various checks
  if (newpassword.value != '')
  {
   md5hash(newpassword, newpassword_md5, junk_output, 0);
  }
  if (newpasswordconfirm.value != '')
  {
   md5hash(newpasswordconfirm, newpasswordconfirm_md5, junk_output, 0);
  }
}
function verify_passwords(password1, password2)                                                          
{                                                                                                        
	// do various checks, this will save people noticing mistakes on next page                             
	if (password1.value == '' || password2.value == '')                                                    
	{                                                                                                      
		alert('Please fill out both password fields.');                                                   
		return false;                                                                                        
	}                                                                                                      
	else if (password1.value != password2.value)                                                           
	{                                                                                                      
		alert('The entered passwords do not match.');                                                  
		return false;                                                                                        
	}                                                                                                      
	else                                                                                                   
	{                                                                                                      
		var junk_output;                                                                                     
		                                                                                                     
		md5hash(password1, document.forms.register.password_md5, junk_output, 0);       
		md5hash(password2, document.forms.register.passwordconfirm_md5, junk_output, 0);
		                                                                                                     		                                                                                                     
		return true;                                                                                         
	}                                                                                                      
	return false;                                                                                          
}
function pm(tform)
{
	var users = new Array();
	var arrCount = 0;
	for (i = 0; i < tform.elements.length; i++)
	{
		var element = tform.elements[i];
		if ((element.name != "allbox") && (element.type == "checkbox") && (element.checked == true))
		{
			users[arrCount] = element.value;
			arrCount++;
		}
	}
	if (arrCount == 0)
	{
		alert("No users selected!");
	}
	else
	{
		var querystring = "";
		for (i = 0; i < users.length; i++)
		{
			querystring += "&userid[]=" + users[i];
		}
		if (opener && !opener.closed)
		{ // parent window is still open
			opener.location="private.php?do=newpm" + querystring;
		}
		else
		{ // parent window has closed or went to a different URL.
			window.open("private.php?do=newpm" + querystring, "pm");
		}
	}
}
function checkform(formobj)
{
	if (formobj.month.selectedIndex == 0)
	{
		alert("Select a month");
		return false;
	}
	if (formobj.day.selectedIndex == 0)
	{
		alert("Select a day");
		return false;
	}
	if (! formobj.year.value.match(/^\d{4}$/))
	{
 		alert("Please enter a proper year");
		return false;
	}
	return true;
}
function CheckChildren(postid, value)
{
	// check this box
	fetch_object("checkpost" + postid).checked = value;

	// check check children
	for (i in parentassoc)
	{
		if (parentassoc[i] == postid)
		{
			CheckChildren(i, value);
		}
	}

	return false;
}
function CheckAllPosts(formobj)
{
	for (var i = 0; i < formobj.elements.length; i++)
	{
		element = formobj.elements[i];
		if (element.type == "checkbox" && element.name.indexOf("checkpost") == 0)
		{
			element.checked = formobj.allbox.checked;
		}
	}
}
function AddvbMenuControls(){
 var menulist = document.getElementsByClassName('vbmenu_control');
 var trues = ['pn_menu_control','userfield','referrerfield','postmenu_','pmrecips','bccpmrecips','userfield_buddy','userfield_buddy','userfield_ignore']
 menulist.each(function(menu){
  //console.log("menu id: "+menu.id);
  if(menu.id == ''){return;}
  if(trues.indexOf(menu.id)>=0 || menu.id.indexOf('postmenu_')>=0){
   //console.log('true: '+menu.id);
   vBmenu.register(menu.id,true);
  } else {
  	//console.log('false: '+menu.id);
  	vBmenu.register(menu.id);
  }
  
 });
}
function InitvbPhrases(){
//ensure that a global array named vbphrase exists!!!
// vB Phrases
vbphrase["wysiwyg_please_wait"]          = "Please wait for the WYSIWYG editor to finish loading...";
vbphrase["wysiwyg_initialized"]          = "WYSIWYG Editor initialized for %1$s in %2$s seconds.";
vbphrase["wysiwyg_command_invalid"]      = "This command is invalid or not implemented.";
vbphrase["moz_must_select_text"]         = "Mozilla requires that you must select some text for this function to work";
vbphrase["moz_edit_config_file"]         = "You need to edit your Mozilla config file to allow this action.";
vbphrase["enter_tag_option"]             = "Please enter the option for your %1$s tag:";
vbphrase["must_select_text_to_use"]      = "You must select some text to use this function.";
vbphrase["browser_is_safari_no_wysiwyg"] = "The Safari browser does not support WYSIWYG mode.";
vbphrase["enter_option_x_tag"]           = "Enter the option for the [%1$s] tag:";
vbphrase["enter_text_to_be_formatted"]   = "Enter the text to be formatted";
vbphrase["enter_link_text"]              = "Enter the text to be displayed for the link (optional):";
vbphrase["enter_list_type"]              = "What type of list do you want? Enter '1' for a numbered list, enter 'a' for an alphabetical list, or leave blank for a list with bullet points:";
vbphrase["enter_list_item"]              = "Enter a list item.\r\nLeave the box empty or press 'Cancel' to complete the list:";
vbphrase["must_enter_subject"]           = "You must enter a title / subject!";
vbphrase["message_too_short"]            = "The message you have entered is too short. Please lengthen your message to at least %1$s characters.";
vbphrase["enter_link_url"]               = "Please enter the URL of your link";
vbphrase["enter_image_url"]              = "Please enter the URL of your image:";
vbphrase["enter_email_link"]             = "Please enter the email address for the link:";
vbphrase["complete_image_verification"]  = "You did not complete the Image Verification";
vbphrase["iespell_not_installed"]        = "ieSpell is a spell-checking tool for Internet Explorer. If you would like to download ieSpell, click OK; otherwise click Cancel. ieSpell can be downloaded from http://www.iespell.com";
vbphrase["click_quick_reply_icon"]       = "Please click one of the Quick Reply icons in the posts above to activate Quick Reply.";
vbphrase["insert_all"]                   = "Insert All";
vbphrase['vbseo_mod_approve']            = "LinkBack awaiting moderation. Double-click this icon to approve.";
vbphrase['vbseo_mod_unapprove']          = "Double-click this icon to unapprove.";
vbphrase['vbseo_mod_delete']             = "Double-click this icon to delete linkback.";
vbphrase['doubleclick_forum_markread']   = "Double-click this icon to mark this forum and its contents as read"; 
vbphrase['you_must_be_logged_into_msn_before_doing_this'] = "You must be logged into MSN or Windows Messenger before doing this.";
vbphrase['msn_functions_only_work_in_ie']  = "The MSN functions only work when launched from Internet Explorer (MSIE).";
vbphrase['vbseo_mod_approve']            = "LinkBack awaiting moderation. Double-click this icon to approve.";
vbphrase['vbseo_mod_unapprove']          = "Double-click this icon to unapprove.";
vbphrase['vbseo_mod_delete']             = "Double-click this icon to delete linkback.";
}
function doTZOffset(){
	//user info in head maybe?  Check for dstform before submitting...
//	var tzOffset = $bbuserinfo[timezoneoffset] + $bbuserinfo[dstonoff];
	var utcOffset = new Date().getTimezoneOffset() / 60;
	if (Math.abs(tzOffset + utcOffset) == 1)
	{	// Dst offset is 1 so its changed
		document.forms.dstform.submit();
	}	
	tzOffset = null;
} 
function swap_posticon(imgid)
{
	var out = fetch_object("display_posticon");
	var img = fetch_object(imgid);
	if (img)
	{
		out.src = img.src;
		out.alt = img.alt;
	}
	else
	{
		out.src = "http://www.stevekallestad.com/discuss/clear.gif";
		out.alt = "";
	}
}
function checkbox_counter() {
  var checked = 0;
  for (counter = 0; counter < $idnum; counter++) {
    if (eval("document.smilieform.favsmilies_" + counter + ".checked") == true) {
      checked = checked + 1;
    }
  }


  if (checked > $checkedlimit) {
//    msg="<phrase 1="$checkedlimit">$vbphrase[your_favorite_smilies_list_only_has_room_for_x_selections]</phrase>\n"
//    msg=msg + "$vbphrase[if_you_would_like_to_add_this_one]\n"
    msg="you've reached the limit...  have steve update this error message";
    alert(msg)
    return (false);
  }
}
function radioCheck(formname, option){
	formlength = formname.elements.length;
	for (i=0; i<formlength; i++)
	{
		e = formname.elements[i];
		if (e.type=='radio')
		{
			e.checked = false;
			if (option == e.value)
			{
				e.checked = true;
			}
		}
	}
}
function check_yes(objid)
{
	yes = fetch_object(objid);
	if (yes)
	{
		yes.checked = true;
	}
}
function switch_avatar_category()
{
	selobj = fetch_object("avatar_category_select");
	window.location = "profile.php?do=editavatar&categoryid=" + selobj.options[selobj.selectedIndex].value;
}
function toggle_disabled(status, objid)
{
	obj = fetch_object(objid);
	if (obj)
	{
		obj.disabled = (status ? false : true);
	}
}
function verify_upload(formobj)
{
	var haveupload = false;
	for (var i=0; i < formobj.elements.length; i++)
	{
		var elm = formobj.elements[i];
		if (elm.type == 'file' || elm.type == 'text')
		{
			if (elm.value != "")
			{
				haveupload = true;
			}
		}
	}

	if (haveupload)
	{
		obj = fetch_object("uploading");
		obj.style.display = "";
		return true;
	}
	else
	{
		alert("Please select a file to attach.");
		return false;
	}
}
function clear_birthday()
{
	fetch_object('bd_month').selectedIndex = 0;
	fetch_object('bd_day').selectedIndex = 0;
	fetch_object('bd_year').value = '';
}
function check_all_group(checkobj, value)
{
	formobj = checkobj.form;
	for (var i = 0; i < formobj.elements.length; i++)
	{
		elm = formobj.elements[i];
		if (elm.type == "checkbox" && elm.value == value)
		{
			elm.checked = checkobj.checked;
		}
	}
}
function toggle_pattern(objtype, radioselection){                                                     
	var rec_optionsets  = {                               
	"daily" : fetch_object("daily_event"),              
	"weekly" : fetch_object("weekly_event"),            
	"monthly" : fetch_object("monthly_event"),          
	"yearly" : fetch_object("yearly_event")             
  };   
	
	if (radioselection)                                 
	{                                                   
		fetch_object(radioselection).checked = true;      
	}                                                   
	for (key in rec_optionsets)                         
	{                                                   
		rec_optionsets[key].style.display = "none";       
	}                                                   
	rec_optionsets[objtype].style.display = "";         
}                                                     
function swapbcc(obj){
	obj.style.display = 'none';
	fetch_object('bccpmrecips').style.display = '';
	fetch_object('bccspan2').style.display = '';
	return false;
}
function init_receipt(){
	if(!($('rcptinfo')==null)){
		doReceipt(confirm($('rcptinfo').innerText));
	}
}
function doReceipt(yesno)
{
	var pmid = $('rcptinfo').name;
	// do image method too to get around popup blockers
	var img_obj = new Image();
	img_obj.src = "private.php?$session[sessionurl]do=dopmreceipt&type=img&pmid="+pmid+"&confirm=" + (yesno ? 1 : 0);
	if (yesno)
	{
		fetch_object('receipt').style.display = 'none';
	}
}
function init_editor_attachments(){
	window.vB_Attachments = new vB_Attachment('attachlist', edid);
//  var writer = '<input type="button" id="manage_attachments_button" class="button" tabindex="1" style="font-weight:normal" ';
//  writer += 'value="Manage Attachments" title="Click here to add or edit files attached to this message" ';			
//  writer += 'onclick="';
//  writer += "vB_Attachments.open_window('newattachment.php?"+attachurl+"&amp;poststarttime="+poststarttime+"&amp;posthash="+posthash+"'";
//  writer += ', 480, 480, \'$postid\')" />';
  //document.write(writer);
	writer = null;	
}
function init_editors(){
	var nodes = $$('textarea');
	//var editor_id;
	//var mode = 1; //default to wysiwig for now, should be based on $editortype variable
	//var parsetype = 3; //forum id?  looks like nonforum, undefined, or int
	//var parsesmilies = 1; //parse smilies for everything for now - need to investigate when this shouldn't happen
	if(nodes.length){
	  nodes.each(function(node){
	  	//console.log('new vbte: '+edid+","+edtp+","+edfi+","+edps);
	  	editor_id = node.id.replace(/_textarea/,'');
	  	window.vB_Editor[edid] = new vB_Text_Editor(edid,edtp,edfi,edps);
	    if(node.className.indexOf('quicker')){
	    	QR_EditorID = edid;
	    }	  
	  });
   if(!($('fieldthreadid')==null)){
   	 if (AJAX_Compatible)
				{
					var unq = $('unquoted_posts');
					if(!(unq==null)){
						unq.style.display = '';
					}
					//fetch_object('unquoted_posts').style.display = '';
				}
		 init_unquoted_posts(editor_id,$F('fieldthreadid'));		
   }
   if(!($('attachlist')==null)){
   	init_editor_attachments();
   }
  }
}
function init_recur_calendar(){
  var selectdiv = fetch_object("select_event");         
  selectdiv.style.display = "";                         
  fetch_object("daily_legend").style.display = "none";  
  fetch_object("weekly_legend").style.display = "none"; 
  fetch_object("monthly_legend").style.display = "none";
  fetch_object("yearly_legend").style.display = "none"; 
  // show the correct recurrence pattern                
  // potentially update this to query the value of $F('eventtype') 1= daily, 2= "weekly", 3="monthly", 4="yearly"
  toggle_pattern("daily", false);                   
	
}
function init_rep_menu(){
	var nodes = document.getElementsByClassName('repmenu');
	var postid;
	if(nodes){
		nodes.each(function(node){
			postid = node.id.replace(/reputationmenu_/,'');
			vbrep_register(postid);
		});
	}
}
function init_name_suggest(){
	var nodes = document.getElementsByClassName('namesug');
	
	if(nodes){
	 nodes.each(function(node){
	  var ev = node.id + '_nsv';
	  window[ev] = new vB_AJAX_NameSuggest(ev,node.id+'_txt',node.id);
	  if(node.id.indexOf('recips')){
	  	//for pm name suggests
	  	window[ev].allow_multiple = true;
	  }
	  if(node.id == 'bccpmrecips'){
      fetch_object('bccpmrecips').style.display = 'none';
			fetch_object('bccspan2').style.display = 'none';
			fetch_object('bccspan1').style.display = '';	  	
	  }
	 });
	}
}
function init_inline_mod(){
	var postthread;
	if(!($('modthread')==null)) {
		postthread = "thread" 
	} else {
		postthread = "post"
	}
	//console.log('creating inlineMod');
	window.inlineMod = new vB_Inline_Mod('inlineMod', postthread, 'inlinemodform', 'Go (%1$s)');
}
//function get_location_info(){
	//extract information about your current location
	// so you can do local initialization as appropriate
//} 
function global_init(){
	vBulletin_init();
	AddvbMenuControls();
	InitvbPhrases();
	if (!($('dstform'))==null){
		doTZOffset();  
	}
	mqlimit = 20; 
	if (locdetect == 'FORUMHOME' || locdetect == 'FORUMDISPLAY'){
		//on forum home page and forumdisplay pages
		init_forum_readmarker_system();
	}
	if (locdetect == 'WOL'){
		//init whos online resolve js ???  where, I do not know...
		vB_AJAX_WolResolve_Init('woltable');
	}
	if(fetch_object('refresh_imagereg')){
		vB_AJAX_ImageReg_Init();
	}
	if($$('textarea')){
		init_editors();
	}
	if (!($('rb_event_1')==null)){
		init_recur_calendar();
	}
	//if (...){
		init_rep_menu();
	//}
	//if (...){
		init_name_suggest();
	//}
	if (!($('inlinemodform')==null)){
		init_inline_mod();
	}
	if(!($('threadslist')==null)){
		vB_AJAX_Threadlist_Init('threadslist');
	}
	if(!($('userlist_ignoreform')==null)){
		vB_AJAX_Userlist_Init('userlist_ignoreform', 'userlist_buddyform');
	}
	if(!($('save_searchprefs')==null)){
		vB_AJAX_SearchPrefs_Init('save_searchprefs');
	}
	//if(...){
		init_receipt();
	//}
	if(!($('newattachlistjs')==null)){
	  var new_attachlist_js = $('newattachlistjs').innerHTML;
	  if (typeof window.opener.vB_Attachments != 'undefined')
	  {
	  	window.opener.vB_Attachments.reset();
	  	if(!(new_attachlist_js =='')) {eval(new_attachlist_js);}
	  }
	}
	if(typeof att_arr == 'Array'){
		att_arr.each(function(a){vB_Attachments.add(a.id,a.filename,a.filesize,a.image.strip());});
	}
	if(!($('smilieinit')==null)){
	 var editorid = $('smilieinit').innerHTML;
	 window.opener.vB_Editor[editorid].init_smilies(fetch_object('smilietable'));
	}
	if(!($('showquickedit')==null)){
		vB_AJAX_QuickEdit_Init('posts');
	}
	if(!($('qr_error_tbody')==null)){
		//console.log('initting quick reply');
		qr_init();
	}
	if(!($('threadrating_submit')==null)){
		vB_AJAX_ThreadRate_Init('showthread_threadrate_form');
		threadid = $F('qr_threadid');
	}
	if(!($('nppreview')==null)){
		preview_focus = true;
	}
	if(!($('close_button')==null)){
	  if (self.opener){
	  		var close_button = fetch_object('close_button');
	  		close_button.style.display = '';
	  		close_button.onclick = function() { self.close(); };
	  	}		
	}
	P7_initPM(1,6,1,-20,10);
} 