diff --git a/info.textgrid.middleware.tgauth.tgaccount/LICENSE.txt b/info.textgrid.middleware.tgauth.tgaccount/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..65c5ca88a67c30becee01c5a8816d964b03862f9
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/LICENSE.txt
@@ -0,0 +1,165 @@
+                   GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/info.textgrid.middleware.tgauth.tgaccount/README.txt b/info.textgrid.middleware.tgauth.tgaccount/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..53bc1239eb1bb8b2114fdbc68754d852352b9f04
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/README.txt
@@ -0,0 +1,85 @@
+This is the textgrid account creation tool
+
+Installation:
+-------------
+
+1) Install required packages
+
+  Required debian/ubuntu packages:
+    smarty and sqlite3 
+
+    $ sudo apt-get install sqlite3 php5-sqlite
+    $ sudo apt-get install smarty
+
+  Required pear (http://pear.php.net) packages:
+  
+    Auth
+
+    $ sudo apt-get install php-pear
+    $ sudo pear install Auth
+  
+2) Put source into your Webserver path (possibly /var/www)
+
+3) Configure
+   ---------
+   The ini file template and the sqlite sql files are located in subdir install.
+   
+   a) ini file
+      Copy the ini file to a location outside webroot and edit the parameters.
+      The default is /var/textgrid/tgaccount/tgaccount.ini . This really needs 
+      to be outside webroot, as the http server will handout .ini files plaintext
+      to a requester. So your LDAP password would be world readable.
+      
+      $ cp install/tgaccount.ini.tmpl /var/textgrid/tgaccount/tgaccount.ini
+     
+      If put to a different location than /var/textgrid/tgaccount/tgaccount.ini
+      you need to edit include/config.inc.php
+   
+   b) database
+      setup the sqlite database in a place outside webroot. Same problem here,
+      a database inside webroot may be served to a requester by apache, so your
+      user data would be world readable.
+      
+      create table structure:
+      $ cat install/tgaccount.sqlite.sql | sqlite3 /var/textgrid/tgaccount/tgaccount.sqlite
+      
+      insert mailtemplates
+      $ cat install/mailtemplates.sqlite.sql | sqlite3 /var/textgrid/tgaccount/tgaccount.sqlite
+      
+      change user rights, so www-data is allowed to write the database
+      $ chown www-data:www-data /var/textgrid/tgaccount/tgaccount.sqlite
+      
+   
+4) LDAP TLS
+   --------
+    The textgrid LDAP now only allows TLS with a certificate signed by the DAASI CA. If you want
+    to connect to textgrid LDAP you need to install the ca certificate and edit /etc/hosts
+    
+    add to /etc/hosts
+    134.76.20.91    textgrid-users
+    
+    edit /etc/ldap/ldap.conf and add/change the line
+    TLS_CACERT      /etc/ssl/certs/daasi-ca.pem
+
+    Download the DAASI ca certificate from [TODO].
+    Copy the certificate to /etc/ssl/certs/daasi-ca.pem
+    
+5) Typo3 frontend
+   --------------
+   to access the tgaccount tool from the typo3 tgaccount extension you need to set an
+   htaccess password:
+   
+   $ htpasswd -c /var/textgrid/tgaccount/.htpass textgrid
+   
+   set password and user in the typo3 extension
+   
+
+Further notes:
+--------------
+
+The Homepage of included pwgen class is: http://code.google.com/p/pwgen-php/ the license of
+this class is GPL2.
+
+The tgaccount tool is under the LGPL3 license. http://www.gnu.org/licenses/lgpl-3.0.txt
+
+
diff --git a/info.textgrid.middleware.tgauth.tgaccount/ajax/ajaxinit.inc.php b/info.textgrid.middleware.tgauth.tgaccount/ajax/ajaxinit.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..e968e139a41440f0f67dcc26ab864d16f55110a2
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/ajax/ajaxinit.inc.php
@@ -0,0 +1,10 @@
+<?php
+include '../conf/config.inc.php';
+include '../include/auth.inc.php';
+include '../include/tgSqliteDB.class.php';
+include '../include/tgLdap.class.php';
+
+$db = new tgSqliteDB($conf);
+$ldap = new tgLdap($conf);
+
+?>
diff --git a/info.textgrid.middleware.tgauth.tgaccount/ajax/mail_template.php b/info.textgrid.middleware.tgauth.tgaccount/ajax/mail_template.php
new file mode 100644
index 0000000000000000000000000000000000000000..09587cc98a0e0250fc8978ef75fbf3f26d19da19
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/ajax/mail_template.php
@@ -0,0 +1,26 @@
+<?php
+include 'ajaxinit.inc.php';
+
+$template = $db->getMailTemplate($_GET['id']);
+$user = $db->getUserRequest($_GET['userid']);
+$sender = $ldap->getUsername($conf['login']['user']);
+
+$name = $user['name'] . ' ' . $user['surname'];
+
+$res['body'] = str_replace("{name}", $name, $template['body']);
+$res['body'] = str_replace("{sender}", $sender, $res['body']);
+
+if($_GET['uid']) {
+  $res['body'] = str_replace("{uid}", $_GET['uid'], $res['body']);
+}
+
+if($_GET['password']) {
+  $res['body'] = str_replace("{password}", $_GET['password'], $res['body']);  
+}
+
+$res['subject'] = $template['subject'];
+
+header('Content-type: application/json');
+echo json_encode($res);
+
+?>
diff --git a/info.textgrid.middleware.tgauth.tgaccount/ajax/pwgen.php b/info.textgrid.middleware.tgauth.tgaccount/ajax/pwgen.php
new file mode 100644
index 0000000000000000000000000000000000000000..c6c34faab36184f9a328069c7f4e94287eae9b90
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/ajax/pwgen.php
@@ -0,0 +1,9 @@
+<?php
+
+include '../include/pwgen.class.php';
+
+$pwgen = new PWGen();
+$password = $pwgen->generate();
+echo $password;
+
+?>
diff --git a/info.textgrid.middleware.tgauth.tgaccount/css/style.css b/info.textgrid.middleware.tgauth.tgaccount/css/style.css
new file mode 100644
index 0000000000000000000000000000000000000000..e08e74249f329acc3caa3ff34b774c28f13d050d
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/css/style.css
@@ -0,0 +1,86 @@
+
+.requestlist td { background-color: #f0f0f0; cursor: pointer; padding: 0.3em;}
+.requestlist td:hover { background-color: #a3b8cc; cursor: pointer;}
+
+td, th { background-color: #f0f0f0; padding: 0.3em; text-align: left;}
+
+label {
+  float: left;
+  text-align: right;
+  width: 9em;
+  margin: 0 .5em 0 0;
+}
+
+input[type=text], textarea {
+  border: 1px solid #999;
+  background-color: #fff;
+  margin: .3em;
+  padding: .1em;
+  width: 24em;
+  font-family: monospace;
+  font-size: 1.1em;
+}
+
+.mail input[type=text] {
+  width: 40em;
+}
+
+.mail textarea  {
+  width: 40em;
+  
+}
+
+#topMenu {
+ background-color: #8ac;
+ border: 1px solid #8ac; 
+ height: 2.2em;
+}
+
+ul.menu {
+  margin: 0;
+  padding: 0;
+}
+
+.menu li {
+  list-style-type: none;
+  float:left;
+  padding: 0.5em;
+}
+
+.menu a {
+  text-decoration: none;
+  color: #000;
+  /*font-family: monospace;*/
+}
+
+.mainMenu li {
+  background-color: #8ac;
+}
+
+.mainMenu li:hover{
+  background-color: #A3B8CC
+}
+
+.subMenu li {
+ border: 1px solid #8ac;
+ border-top: none;
+ cursor: pointer;
+}
+
+.subMenu li.active a {
+ color: #f00; 
+}
+
+.subMenu li:hover{
+  background-color: #A3B8CC
+}
+
+.spacer{
+  margin: 1em;
+}
+
+.status {
+  border: 1px solid #8ac;
+  color: #2D6CAB;
+  padding: 0.4em;
+}
diff --git a/info.textgrid.middleware.tgauth.tgaccount/edit_request.php b/info.textgrid.middleware.tgauth.tgaccount/edit_request.php
new file mode 100644
index 0000000000000000000000000000000000000000..8773f8c55ddebf196f5da7f8ccabc1975d9baf10
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/edit_request.php
@@ -0,0 +1,104 @@
+<?php
+
+include 'webinit.inc.php';
+
+if($_POST['mail']){
+
+  $mail = $_POST['mail'];
+  $imap->mail($mail['reciever'], $mail['subject'], $mail['body']);
+  $status='Mail an '.$mail['reciever'].' wurde versendet';
+  
+}
+
+if($_POST['create']) {
+
+  print_r($_POST);
+  /*
+   * TODO: nicer postdata-check, visual feedback
+   * keep form entries
+   */
+  if(!$_POST['ldap']['password']) {
+    $status='User nicht angelegt: Kein Passwort angegeben';
+    $smarty->assign('create', 'true');
+  } else if($ldap->uidExists($_POST['ldap']['uid'])) { 
+    $status='User nicht angelegt: UID schon vergeben';
+    $smarty->assign('create', 'true');
+  } else if($ldap->eppnExists($_POST['ldap']['eduPersonPrincipalName'])) {
+    $status='User nicht angelegt: eppn schon vergeben';
+    $smarty->assign('create', 'true');
+  } else {
+   
+    $success = $ldap->addUser($_POST['ldap']);
+    if($success) {
+      
+      if($_POST['sandbox']) {
+        foreach ($conf['sandbox'] as $val) {
+          $success = $ldap->addUserToProject($_POST['ldap']['eduPersonPrincipalName'], $val);
+          if($success) {
+            $status .= "Sandbox: Nutzer zu Projekt " . $val . " hinzugefügt <br/>";
+          } else {
+            $status .= "Sandbox: Nutzer zu Projekt " . $val . " hinzufügen schlug fehl<br/>";
+          }
+        }
+        
+      }
+      
+      $db->closeRequest($_GET[id], $_POST['ldap']['uid']);
+      $status .= 'Account angelegt';
+      $mailIndex =  $db->getMailTemplates();
+      $smarty->assign('mail',  $mailIndex);    
+      $smarty->assign('created', $_POST['ldap'] );
+    } else {
+      $status .= 'Fehler bei Anlegen des Accounts';
+    }
+    
+  }
+}
+
+if($_GET['action']=='mail' ) {
+  //$smarty->assign('mail', true );
+  $mailIndex =  $db->getMailTemplates();
+  $smarty->assign('mail',  $mailIndex);
+}
+
+if($_GET['action']=='create') {
+   $smarty->assign('create', 'true');
+}
+
+if($_GET['action']=='reject') {
+   $db->rejectRequest($_GET['id']);
+}
+
+if($_GET['action']=='reassign') {
+   $db->assignRequest($_GET['id']);
+}
+
+/**
+ * Load data, after all possible Operations on Data finished
+ */
+
+if($_GET['id']) {
+
+  $request = $db->getUserRequest($_GET['id']);
+  // construct eppn
+  $request['eppn'] = $request['name'].'.'.$request['surname'].'@textgrid.de';
+  // replace spaces
+  $request['eppn'] = strtr($request['eppn'], ' ', '.');
+
+  // if request not yet assigned to user, assign now
+  if(!$request['at_assignee']) {
+    $db->assignRequest($_GET['id']);
+  }
+  
+}
+
+/**
+ * Which Mailtemplate to load
+ */
+
+$smarty->assign('login_user', $conf['login']['user'] );
+$smarty->assign('status', $status);
+$smarty->assign('request', $request );
+$smarty->display('edit_requests.tpl');
+
+?>
diff --git a/info.textgrid.middleware.tgauth.tgaccount/include/auth.inc.php b/info.textgrid.middleware.tgauth.tgaccount/include/auth.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..b7ccfcad6f2c12b60d4c31cef46992ecb4235026
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/include/auth.inc.php
@@ -0,0 +1,36 @@
+<?php
+require_once "Auth.php";
+
+$auth = doAuth($conf['ldap']);
+if (!$auth->checkAuth()) die();
+
+if (isset($_GET['action']) && $_GET['action'] == "logout" ) {
+    $auth->logout();
+    $auth->start();
+    die();
+}
+
+$conf['login']['user'] = $auth->getUsername();
+
+function doAuth($c){
+  
+  $options = array(
+    'host' => $c['host'],
+    'port' => $c['port'],
+    'version' => 3,
+    'start_tls' => $c['tls'],
+    'binddn' => $c['managerdn'],
+    'bindpw' => $c['managerpassword'],
+    'basedn' => 'ou=users,dc=textgrid,dc=de',
+    'userattr' => 'uid',
+    'userfilter' => '(EduPersonEntitlement=TextGrid-Admintool)',
+    'debug' => $c['debug']
+  );
+
+  $a = new Auth("LDAP", $options, "loginFunction");
+  $a->start();
+
+  return $a;
+}
+
+?>
diff --git a/info.textgrid.middleware.tgauth.tgaccount/include/config.inc.php b/info.textgrid.middleware.tgauth.tgaccount/include/config.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..627bb6a98f22e1b5f2c827daf0f9fec2f4f3c148
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/include/config.inc.php
@@ -0,0 +1,34 @@
+<?php
+// conf variables from ini file
+// WARNING: should be outside webroot
+$ini = parse_ini_file('/var/textgrid/tgaccount/tgaccount.ini');
+
+// path
+$conf['path'] = $_SERVER['DOCUMENT_ROOT'].'/tgaccount/';
+$conf['url'] = $ini['host'];
+
+// sqlite db settings 
+// WARNING: database should be outside webroot and needs to be writable by www user
+$conf['sqlite']['path'] = $ini['sqlitePath'];
+
+// ldap-settings
+$conf['ldap']['host'] = $ini['ldapHost'];
+$conf['ldap']['port'] = $ini['ldapPort'];
+$conf['ldap']['tls'] = $ini['ldapTLS'];
+$conf['ldap']['managerdn'] = $ini['managerdn'];
+$conf['ldap']['managerpassword'] = $ini['managerpassword'];
+$conf['ldap']['debug'] = false;
+
+// imap
+$conf['imap']['sender'] = $ini['mailSender'];
+$conf['imap']['cc'] = $ini['mailCC'];
+ 
+// textgrid-sandbox
+$conf['sandbox'][0] = 'TGPR30';
+$conf['sandbox'][1] = 'TGPR31';
+
+// smarty
+$conf['smarty']['incPath'] = $ini['smartyLib'];
+$conf['smarty']['path'] = $conf['path'].'smarty/';
+
+?>
diff --git a/info.textgrid.middleware.tgauth.tgaccount/include/menu.inc.php b/info.textgrid.middleware.tgauth.tgaccount/include/menu.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..562d2b378576fb3db01d4ba87a534a8da3d76bd5
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/include/menu.inc.php
@@ -0,0 +1,12 @@
+<?php
+
+$menu = '<div id="topMenu">';
+$menu .= '<ul class="menu mainMenu">';
+$menu .= '<li><a href="index.php">Übersicht</a></li>';
+$menu .= '<li  style="float:right"><a href="index.php?action=logout">Logout '.$conf['login']['user'].'</a></li>';
+$menu .= '</ul>
+          </div>
+          <div style="clear:both;"></div>';
+
+
+?>
diff --git a/info.textgrid.middleware.tgauth.tgaccount/include/pwgen.class.php b/info.textgrid.middleware.tgauth.tgaccount/include/pwgen.class.php
new file mode 100644
index 0000000000000000000000000000000000000000..37d080f93e2f86886741662b033543aeed6291c7
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/include/pwgen.class.php
@@ -0,0 +1,413 @@
+<?php
+	/**
+	 * Port of the famous GNU/Linux Password Generator ("pwgen") to PHP.
+	 * This file may be distributed under the terms of the GNU Public License.
+	 * Copyright (C) 2001, 2002 by Theodore Ts'o <tytso@alum.mit.edu>
+	 * Copyright (C) 2009 by Superwayne <superwayne@superwayne.org>
+	 */
+	class PWGen {
+		// Flags for the pwgen function
+		const PW_DIGITS = 0x0001;
+		const PW_UPPERS = 0x0002; // At least one upper letter
+		const PW_SYMBOLS = 0x0004;
+		const PW_AMBIGUOUS = 0x0008;
+
+		// Flags for the pwgen element
+		const CONSONANT = 0x0001;
+		const VOWEL = 0x0002;
+		const DIPHTHONG = 0x0004;
+		const NOT_FIRST = 0x0008;
+		const NO_VOWELS = 0x0010;
+
+		private $pwgen;
+		private $pwgen_flags;
+		private $password;
+		private $pw_length;
+
+		private static $initialized = false; // static block alread called?
+		private static $elements;
+		private static $pw_ambiguous;
+		private static $pw_symbols;
+		private static $pw_digits;
+		private static $pw_uppers;
+		private static $pw_lowers;
+		private static $pw_vowels;
+
+		/**
+		 * @param length	Length of the generated password. Default: 8
+		 * @param secure	Generate completely random, hard-to-memorize passwords. These should only
+		 * 			be used for machine passwords, since otherwise it's almost guaranteed that
+		 * 			users will simply write the password on a piece of paper taped to the monitor...
+		 * @param numerals	Include at least one number in the password. This is the default.
+		 * @param capitalize	Include at least one capital letter in the password. This is the default.
+		 * @param ambiguous	Don't use characters that could be confused by the user when printed,
+		 * 			such as 'l' and '1', or '0' or 'O'. This reduces the number of possible
+		 *			passwords significantly, and as such reduces the quality of the passwords.
+		 *			It may be useful for users who have bad vision, but in general use of this
+		 *			option is not recommended.
+		 * @param no-vowels	Generate random passwords that do not contain vowels or numbers that might be
+		 * 			mistaken for vowels. It provides less secure passwords to allow system
+		 * 			administrators to not have to worry with random passwords accidentally contain
+		 * 			offensive substrings.
+		 * @param symbols	Include at least one special character in the password.
+		 */
+		public function __construct($length=8, $secure=false, $numerals=true, $capitalize=true,
+			$ambiguous=false, $no_vovels=false, $symbols=false) {
+			self::__static();
+
+			$this->pwgen = 'pw_phonemes';
+
+			$this->setLength($length);
+			$this->setSecure($secure);
+			$this->setNumerals($numerals);
+			$this->setCapitalize($capitalize);
+			$this->setAmbiguous($ambiguous);
+			$this->setNoVovels($no_vovels);
+			$this->setSymbols($symbols);
+		}
+
+		/**
+		 * Length of the generated password. Default: 8
+		 */
+		public function setLength($length) {
+			if (is_numeric($length) && $length > 0) {
+				$this->pw_length = $length;
+				if ($this->pw_length < 5) {
+					$this->pwgen = 'pw_rand';
+				}
+				if ($this->pw_length <= 2) {
+					$this->setCapitalize(false);
+				}
+				if ($this->pw_length <= 1) {
+					$this->setNumerals(false);
+				}
+			} else {
+				$this->pw_length = 8;
+			}
+			return $this;
+		}
+
+		/**
+		 * Generate completely random, hard-to-memorize passwords. These should only used for machine passwords,
+		 * since otherwise it's almost guaranteed that users will simply write the password on a piece of paper
+		 * taped to the monitor...
+		 * Please note that this function implies that you want passwords which include symbols, numerals and
+		 * capital letters.
+		 */
+		public function setSecure($secure) {
+			if($secure) {
+				$this->pwgen = 'pw_rand';
+				$this->setNumerals(true);
+				$this->setCapitalize(true);
+			} else {
+				$this->pwgen = 'pw_phonemes';
+			}
+			return $this;
+		}
+
+		/**
+		 * Include at least one number in the password. This is the default.
+		 */
+		public function setNumerals($numerals) {
+			if($numerals) {
+				$this->pwgen_flags |= self::PW_DIGITS;
+			} else {
+				$this->pwgen_flags &= ~self::PW_DIGITS;
+			}
+			return $this;
+		}
+
+		/**
+		 * Include at least one capital letter in the password. This is the default.
+		 */
+		public function setCapitalize($capitalize) {
+			if($capitalize) {
+				$this->pwgen_flags |= self::PW_UPPERS;
+			} else {
+				$this->pwgen_flags &= ~self::PW_UPPERS;
+			}
+			return $this;
+		}
+
+		/**
+		 * Don't use characters that could be confused by the user when printed, such as 'l' and '1', or '0' or
+		 * 'O'. This reduces the number of possible passwords significantly, and as such reduces the quality of
+		 * the passwords. It may be useful for users who have bad vision, but in general use of this option is
+		 * not recommended.
+		 */
+		public function setAmbiguous($ambiguous) {
+			if($ambiguous) {
+				$this->pwgen_flags |= self::PW_AMBIGUOUS;
+			} else {
+				$this->pwgen_flags &= ~self::PW_AMBIGUOUS;
+			}
+			return $this;
+		}
+
+		/**
+		 * Generate random passwords that do not contain vowels or numbers that might be mistaken for vowels. It
+		 * provides less secure passwords to allow system administrators to not have to worry with random
+		 * passwords accidentally contain offensive substrings.
+		 */
+		public function setNoVovels($no_vovels) {
+			if($no_vovels) {
+				$this->pwgen = 'pw_rand';
+				$this->pwgen_flags |= self::NO_VOWELS | self::PW_DIGITS | self::PW_UPPERS;
+			} else {
+				$this->pwgen = 'pw_phonemes';
+				$this->pwgen_flags &= ~self::NO_VOWELS;
+			}
+			return $this;
+		}
+
+		public function setSymbols($symbols) {
+			if($symbols) {
+				$this->pwgen_flags |= self::PW_SYMBOLS;
+			} else {
+				$this->pwgen_flags &= ~self::PW_SYMBOLS;
+			}
+			return $this;
+		}
+
+		public function generate() {
+			if($this->pwgen == 'pw_phonemes') {
+				$this->pw_phonemes();
+			} else { // $this->pwgen == 'pw_rand'
+				$this->pw_rand();
+			}
+			return $this->password;
+		}
+
+		private function pw_phonemes() {
+			$this->password = array();
+
+			do {
+				$feature_flags = $this->pwgen_flags;
+				$c = 0;
+				$prev = 0;
+				$should_be = self::my_rand(0, 1) ? self::VOWEL : self::CONSONANT;
+				$first = 1;
+
+				while ($c < $this->pw_length) {
+					$i = self::my_rand(0, count(self::$elements)-1);
+					$str = self::$elements[$i]->str;
+					$len = strlen($str);
+					$flags = self::$elements[$i]->flags;
+	
+					// Filter on the basic type of the next element
+					if (($flags & $should_be) == 0)
+						continue;
+					// Handle the NOT_FIRST flag
+					if ($first && ($flags & self::NOT_FIRST))
+						continue;
+					// Don't allow VOWEL followed a Vowel/Dipthong pair
+					if (($prev & self::VOWEL) && ($flags & self::VOWEL) &&
+						($flags & self::DIPHTHONG))
+						continue;
+					// Don't allow us to overflow the buffer
+					if ($len > $this->pw_length-$c)
+						continue;
+	
+					// Handle the AMBIGUOUS flag
+					if ($this->pwgen_flags & self::PW_AMBIGUOUS) {
+						if (strpbrk($str, self::$pw_ambiguous) !== false)
+							continue;
+					}
+	
+					/*
+					 * OK, we found an element which matches our criteria,
+					 * let's do it!
+					 */
+					for($j=0; $j < $len; $j++)
+						$this->password[$c+$j] = $str[$j];
+
+					// Handle PW_UPPERS
+					if ($this->pwgen_flags & self::PW_UPPERS) {
+						if (($first || $flags & self::CONSONANT) && (self::my_rand(0, 9) < 2)) {
+							$this->password[$c] = strtoupper($this->password[$c]);
+							$feature_flags &= ~self::PW_UPPERS;
+						}
+					}
+			
+					$c += $len;
+			
+					// Time to stop?
+					if ($c >= $this->pw_length)
+						break;
+			
+					// Handle PW_DIGITS
+					if ($this->pwgen_flags & self::PW_DIGITS) {
+						if (!$first && (self::my_rand(0, 9) < 3)) {
+							do {
+								$ch = strval(self::my_rand(0, 9));
+							} while (($this->pwgen_flags & self::PW_AMBIGUOUS) &&
+								strpos(self::$pw_ambiguous, $ch) !== false);
+							$this->password[$c++] = $ch;
+							$feature_flags &= ~self::PW_DIGITS;
+					
+							$first = 1;
+							$prev = 0;
+							$should_be = self::my_rand(0, 1) ? self::VOWEL : self::CONSONANT;
+							continue;
+						}
+					}
+					
+					// Handle PW_SYMBOLS
+					if ($this->pwgen_flags & self::PW_SYMBOLS) {
+						if (!$first && (self::my_rand(0, 9) < 2)) {
+							do {
+								$ch = self::$pw_symbols[self::my_rand(0,
+									strlen(self::$pw_symbols)-1)];
+							} while (($this->pwgen_flags & self::PW_AMBIGUOUS) &&
+								strpos(self::$pw_ambiguous, $ch) !== false);
+							$this->password[$c++] = $ch;
+							$feature_flags &= ~self::PW_SYMBOLS;
+						}
+					}
+	
+					// OK, figure out what the next element should be
+					if ($should_be == self::CONSONANT) {
+						$should_be = self::VOWEL;
+					} else { // should_be == VOWEL
+						if (($prev & self::VOWEL) || ($flags & self::DIPHTHONG) ||
+							(self::my_rand(0, 9) > 3))
+							$should_be = self::CONSONANT;
+						else
+							$should_be = self::VOWEL;
+					}
+					$prev = $flags;
+					$first = 0;
+				}
+			} while ($feature_flags & (self::PW_UPPERS | self::PW_DIGITS | self::PW_SYMBOLS));
+
+			$this->password = implode('', $this->password);
+		}
+
+		private function pw_rand() {
+			$this->password = array();
+
+        		$chars = '';
+			if ($this->pwgen_flags & self::PW_DIGITS) {
+				$chars .= self::$pw_digits;
+			}
+			if ($this->pwgen_flags & self::PW_UPPERS) {
+				$chars .= self::$pw_uppers;
+			}
+			$chars .= self::$pw_lowers;
+			if ($this->pwgen_flags & self::PW_SYMBOLS) {
+				$chars .= self::$pw_symbols;
+			}
+
+			do {
+				$len = strlen($chars);
+				$feature_flags = $this->pwgen_flags;
+				$i = 0;
+
+				while ($i < $this->pw_length) {
+					$ch = $chars[self::my_rand(0, $len-1)];
+					if (($this->pwgen_flags & self::PW_AMBIGUOUS) &&
+						strpos(self::$pw_ambiguous, $ch) !== false)
+						continue;
+					if (($this->pwgen_flags & self::NO_VOWELS) &&
+						strpos(self::$pw_vowels, $ch) !== false)
+						continue;
+					$this->password[$i++] = $ch;
+					if (strpos(self::$pw_digits, $ch) !== false)
+						$feature_flags &= ~self::PW_DIGITS;
+					if (strpos(self::$pw_uppers, $ch) !== false)
+						$feature_flags &= ~self::PW_UPPERS;
+					if (strchr(self::$pw_symbols, $ch) !== false)
+						$feature_flags &= ~self::PW_SYMBOLS;
+				}
+			} while ($feature_flags & (self::PW_UPPERS | self::PW_DIGITS | self::PW_SYMBOLS));
+
+			$this->password = implode('', $this->password);
+		}
+
+		/**
+		 * Generate a random number n, where $min <= n < $max
+		 * Mersenne Twister is used as an algorithm
+		 */
+		public static function my_rand($min=0, $max=0) {
+			return mt_rand($min, $max);
+		}
+
+		/**
+		 * This method initializes all static vars which contain complex datatypes.
+		 * It acts somewhat like a static block in Java. Since PHP does not support this principle, the method
+		 * is called from the constructor. Because of that you can not access the static vars unless there
+		 * exists at least one object of the class.
+		 */
+		private static function __static() {
+			if(!self::$initialized) {
+				self::$initialized = true;
+				self::$elements = array(
+					new PWElement('a', self::VOWEL),
+					new PWElement('ae', self::VOWEL | self::DIPHTHONG),
+					new PWElement('ah', self::VOWEL | self::DIPHTHONG),
+					new PWElement('ai', self::VOWEL | self::DIPHTHONG),
+					new PWElement('b', self::CONSONANT),
+					new PWElement('c', self::CONSONANT),
+					new PWElement('ch', self::CONSONANT | self::DIPHTHONG),
+					new PWElement('d', self::CONSONANT),
+					new PWElement('e', self::VOWEL),
+					new PWElement('ee', self::VOWEL | self::DIPHTHONG),
+					new PWElement('ei', self::VOWEL | self::DIPHTHONG),
+					new PWElement('f', self::CONSONANT),
+					new PWElement('g', self::CONSONANT),
+					new PWElement('gh', self::CONSONANT | self::DIPHTHONG | self::NOT_FIRST),
+					new PWElement('h', self::CONSONANT),
+					new PWElement('i', self::VOWEL),
+					new PWElement('ie', self::VOWEL | self::DIPHTHONG),
+					new PWElement('j', self::CONSONANT),
+					new PWElement('k', self::CONSONANT),
+					new PWElement('l', self::CONSONANT),
+					new PWElement('m', self::CONSONANT),
+					new PWElement('n', self::CONSONANT),
+					new PWElement('ng', self::CONSONANT | self::DIPHTHONG | self::NOT_FIRST),
+					new PWElement('o', self::VOWEL),
+					new PWElement('oh', self::VOWEL | self::DIPHTHONG),
+					new PWElement('oo', self::VOWEL | self::DIPHTHONG),
+					new PWElement('p', self::CONSONANT),
+					new PWElement('ph', self::CONSONANT | self::DIPHTHONG),
+					new PWElement('qu', self::CONSONANT | self::DIPHTHONG),
+					new PWElement('r', self::CONSONANT),
+					new PWElement('s', self::CONSONANT),
+					new PWElement('sh', self::CONSONANT | self::DIPHTHONG),
+					new PWElement('t', self::CONSONANT),
+					new PWElement('th', self::CONSONANT | self::DIPHTHONG),
+					new PWElement('u', self::VOWEL),
+					new PWElement('v', self::CONSONANT),
+					new PWElement('w', self::CONSONANT),
+					new PWElement('x', self::CONSONANT),
+					new PWElement('y', self::CONSONANT),
+					new PWElement('z', self::CONSONANT)
+				);
+				self::$pw_ambiguous = 'B8G6I1l0OQDS5Z2';
+				self::$pw_symbols = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
+				self::$pw_digits = '0123456789';
+				self::$pw_uppers = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+				self::$pw_lowers = 'abcdefghijklmnopqrstuvwxyz';
+				self::$pw_vowels = '01aeiouyAEIOUY';
+			}
+		}
+
+		/**
+		 * Returns the last generated password. If there is none, a new one will be generated.
+		 */
+		public function __toString() {
+			return (empty($this->password) ? $this->generate() : $this->password);
+		}
+
+	}
+
+	class PWElement {
+		public $str;
+		public $flags;	
+		
+		public function __construct($str, $flags) {
+			$this->str = $str;
+			$this->flags = $flags;
+		}
+	}
+?>
diff --git a/info.textgrid.middleware.tgauth.tgaccount/include/pwgen.class.php.LICENSE b/info.textgrid.middleware.tgauth.tgaccount/include/pwgen.class.php.LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..d511905c1647a1e311e8b20d5930a37a9c2531cd
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/include/pwgen.class.php.LICENSE
@@ -0,0 +1,339 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/info.textgrid.middleware.tgauth.tgaccount/include/tgImap.class.php b/info.textgrid.middleware.tgauth.tgaccount/include/tgImap.class.php
new file mode 100644
index 0000000000000000000000000000000000000000..f6fd920fbfc3dc87b066da83e9e48ac77404dbd8
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/include/tgImap.class.php
@@ -0,0 +1,38 @@
+<?php
+
+class tgImap {
+
+  var $conf;
+  var $sender;
+  var $cc;
+
+  function __construct( $conf) {
+    $this->conf = $conf;
+    $this->sender = $conf['imap']['sender'];
+    $this->cc = $conf['imap']['cc'];
+  }
+  
+  function mail($to, $subject, $body) {
+
+    // Falls eine Zeile der Nachricht mehr als 70 Zeichen enthälten könnte,
+    // sollte wordwrap() benutzt werden (http://de.php.net/manual/de/function.mail.php)
+    $body = wordwrap($body, 70);
+    
+    $header = 'From: '. $this->sender . "\n" .
+              'Reply-To: '. $this->sender . "\n" .
+              'Cc: ' . $this->cc . "\n" .
+              'X-Mailer: PHP/' . phpversion() . "\n" .
+              'MIME-Version: 1.0' . "\n" .
+              'Content-type: text/plain; charset=UTF-8' . "\n".
+              'Content-Transfer-Encoding: 8bit' . "\n";
+
+    // replace linebreaks on www.textgrid.de
+    $body = str_replace("\r\n", "\n", $body);
+    
+    mail($to, $subject, $body, $header);
+    
+  }
+
+}
+
+?>
diff --git a/info.textgrid.middleware.tgauth.tgaccount/include/tgLdap.class.php b/info.textgrid.middleware.tgauth.tgaccount/include/tgLdap.class.php
new file mode 100644
index 0000000000000000000000000000000000000000..a1de2fa24b642ce75015f6de3f52604b80cff1cd
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/include/tgLdap.class.php
@@ -0,0 +1,130 @@
+<?php
+
+class TGLdap {
+  
+  var $conn;
+  var $conf;
+
+  function __construct( &$conf ) {
+    $this->conf =& $conf;
+  
+    $ldap = $conf['ldap'];
+    $this->conn = ldap_connect($ldap['host'], $ldap['port'])
+          or die("Keine Verbindung zum LDAP Server.");      
+          
+    ldap_set_option($this->conn,LDAP_OPT_PROTOCOL_VERSION,3);
+    
+    if($ldap['tls']) {
+      ldap_start_tls( $this->conn ) or die('TLS not possible');
+    }
+    
+    $bound = ldap_bind($this->conn, $ldap['managerdn'], $ldap['managerpassword']);
+  }
+  
+  // return full-name of logged in user (e.g. for mail)
+  function getUsername($login) {
+     
+     $basedn = 'ou=users,dc=textgrid,dc=de';
+     $filter = '(uid='.$login.')';
+     $justthese = array("cn");
+     
+     $result = ldap_search( $this->conn, $basedn, $filter, $justthese);
+     $entry = ldap_get_entries($this->conn, $result);
+     
+     return $entry[0]['cn'][0];
+  }
+  
+  function uidExists($uid) {
+    // retrieving entry via complete dn and ldap_read() is no good idea, couse there may be more than one uid,
+    // but only one uid is in the dn
+    // $dn = 'uid='.$uid.',ou=users,dc=textgrid,dc=de';
+    
+    $basedn = 'ou=users,dc=textgrid,dc=de';
+    $filter = '(uid='.$uid.')';
+    $justthese = array("");
+
+    $result = ldap_search( $this->conn, $basedn, $filter, $justthese);
+    
+    // return true if result found
+    return (ldap_count_entries($this->conn, $result) > 0);
+    
+  }
+  
+  function eppnExists($eppn) {
+
+    $basedn = 'ou=users,dc=textgrid,dc=de';
+    $filter = '(eduPersonPrincipalName='.$eppn.')';
+    $justthese = array("");
+
+    $result = ldap_search( $this->conn, $basedn, $filter, $justthese);
+    
+    // return true if result found
+    return (ldap_count_entries($this->conn, $result) > 0);
+    
+  } 
+  
+  function emailExists($email) {
+    
+    $basedn = 'ou=users,dc=textgrid,dc=de';
+    $filter = '(mail='.$email.')';
+    $justthese = array("");
+
+    $result = ldap_search( $this->conn, $basedn, $filter, $justthese);
+    
+    // return true if result found
+    return (ldap_count_entries($this->conn, $result) > 0);
+  }
+  
+  /**
+   * TODO: userObject would be REALLY nice
+   */
+  function addUser($data) {
+    
+    $uid = $data['uid'];
+    
+    //objectclass presets
+    $in['objectclass'][0] = 'top';
+    $in['objectclass'][1] = 'inetOrgPerson';
+    $in['objectclass'][2] = 'eduPerson';
+    $in['objectclass'][3] = 'TextGridUser';
+    
+    // Organisation, optional
+    if($data['o'])
+      $in['o'] = $data['o'];
+    
+    // Vorname
+    $in['givenName'] = $data['givenName'];
+    // Nachname
+    $in['sn'] = $data['sn'];
+
+    // http://www.openldap.org/faq/data/cache/347.html
+    $in['userPassword'] = "{SHA}" . base64_encode(sha1( $data['password'], TRUE )); 
+    
+    $in['mail'] = $data['mail'];
+    
+    $in['eduPersonPrincipalName'] = $data['eduPersonPrincipalName'];
+    
+    $in['uid'] = $uid;
+    $in['cn'] =  $data['cn'];
+    
+    $in['TGWantsNewsletter'] = $data['TGWantsNewsletter'];
+    
+    $dn = 'uid='.$uid.',ou=users,dc=textgrid,dc=de';
+    $r = ldap_add($this->conn, $dn, $in);
+    
+    return $r;
+  } 
+  
+  // add user (eppn) to textgridproject with given id
+  function addUserToProject($eppn, $projectid) {
+    
+    $in["rbacPerformer"] = $eppn;
+    
+    $dn = 'rbacName=Bearbeiter,rbacName='.$projectid.',rbacName=Projekt-Teilnehmer,ou=roles,ou=rbac,dc=textgrid,dc=de';
+    $r = ldap_mod_add($this->conn, $dn, $in);
+    
+    return $r;
+  }
+  
+}
+?>
diff --git a/info.textgrid.middleware.tgauth.tgaccount/include/tgMysqlDB.class.php b/info.textgrid.middleware.tgauth.tgaccount/include/tgMysqlDB.class.php
new file mode 100644
index 0000000000000000000000000000000000000000..602e9f38cdb8f43ca288351451096ae201acb3ca
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/include/tgMysqlDB.class.php
@@ -0,0 +1,107 @@
+<?php
+
+class tgMysqlDB {
+
+  var $mysqli;
+  var $debug;
+  var $conf;
+  
+  function __construct ( &$conf ) {
+    $this->conf =& $conf;
+    $this->mysqli=new mysqli($conf['db']['host'], $conf['db']['user'], $conf['db']['pw'], $conf['db']['db'])
+      or die("Keine Verbindung zum MySQL Server.");
+    $this->mysqli->query("SET NAMES 'utf8'"); 
+  }
+  
+  function query ($db_query){
+    if ($this->debug) echo $db_query."<br/>"; 
+    $res = $this->mysqli->query($db_query);
+    if ($res->num_rows > 0 ) return $res;
+    else return false;
+  }
+  
+  /**
+   * Methods dealing with UserRequests
+   */
+  function getUserRequests($filter="open", $order = "timestamp DESC") {
+    
+    switch($filter) {
+      case "open":
+        $where .= "WHERE at_status=0";
+        break;
+      case "my":
+        $where .= "WHERE at_assignee='".$this->conf[login][user]."'";
+        break;
+      case "closed":
+        $where .= "WHERE at_status=1 OR at_status = 2";
+        break;    
+      case "assigned":
+        $where .= "WHERE at_status=3";
+        break;              
+      case "all":
+        break;
+    }
+    
+    $result = $this->query("SELECT * FROM user_request " . $where . " ORDER BY " . $order);
+    
+    if ($result) while($row = $result->fetch_array(MYSQLI_ASSOC)){
+        $requests[]= $row;
+      }
+    return $requests;
+  }
+  
+  function getUserRequest($id) {
+    $result = $this->query("SELECT * FROM user_request WHERE id=".$id);
+    
+    if ($result) return $result->fetch_assoc();
+    else return false;
+  }
+  
+  function insertUserRequest ($data) {
+
+     foreach ($data as $key => $val) {
+        $data[$key] = $this->mysqli->real_escape_string($val);
+     }
+     
+     $query = "INSERT INTO user_request (name, surname, pref_uid, email, institution, newsletter, lang) 
+               VALUES ('".$data[name]."','".$data[surname]."','".$data[userid]."','"
+               .$data[email]."','".$data[institution]."','".$data[newsletter]."', '".$data[lang]."' )";
+     $this->query($query);
+  }
+  
+  function assignRequest($id) {
+     $query = "UPDATE user_request SET at_assignee = '" . $this->conf[login][user] . "', at_status = 3 WHERE id=".$id;
+     $this->query($query);
+  }
+  
+  function closeRequest($id, $ldap_uid) {
+    $query = "UPDATE user_request SET at_status = 1, ldap_uid = '".$ldap_uid."' WHERE id = ". $id;
+    $this->query($query);
+  }
+  
+  function rejectRequest($id) {
+    $query = "UPDATE user_request SET at_status = 2 WHERE id = ". $id;
+    $this->query($query);
+  }
+  
+  /**
+   * Methods dealing with Mailtemplates
+   */  
+  function getMailTemplates() {
+    $result = $this->query("SELECT id,title,lang FROM mail_template");
+    
+    if ($result) while($row = $result->fetch_array(MYSQLI_ASSOC)){
+        $mails[]= $row;
+      }
+    return $mails;
+  }
+  
+  function getMailTemplate($id) {
+    $result = $this->query("SELECT * FROM mail_template WHERE id=".$id);
+    
+    if ($result) return $result->fetch_assoc();
+    else return false;
+  }
+}
+  
+?>
diff --git a/info.textgrid.middleware.tgauth.tgaccount/include/tgSqliteDB.class.php b/info.textgrid.middleware.tgauth.tgaccount/include/tgSqliteDB.class.php
new file mode 100644
index 0000000000000000000000000000000000000000..9e25ca1774d6dd8d0603611664c5abfb5f04c258
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/include/tgSqliteDB.class.php
@@ -0,0 +1,106 @@
+<?php
+
+class tgSqliteDB {
+
+  var $db;
+  var $debug;
+  var $conf;
+
+  function __construct ( &$conf ) {
+    $this->conf =& $conf;
+    $this->db = new SQLite3($conf['sqlite']['path']) 
+      or die('SQLite DB not found');;
+  }
+  
+  function query ($db_query){
+    if ($this->debug) echo $db_query."<br/>"; 
+    $res = $this->db->query($db_query);
+    return $res; 
+  }
+  
+  /**
+   * Methods dealing with UserRequests
+   */
+  function getUserRequests($filter="open", $order = "timestamp DESC") {
+    
+    switch($filter) {
+      case "open":
+        $where .= "WHERE at_status=0";
+        break;
+      case "my":
+        $where .= "WHERE at_assignee='".$this->conf[login][user]."'";
+        break;
+      case "closed":
+        $where .= "WHERE at_status=1 OR at_status = 2";
+        break;    
+      case "assigned":
+        $where .= "WHERE at_status=3";
+        break;              
+      case "all":
+        break;
+    }
+    
+    $result = $this->query("SELECT * FROM user_request " . $where . " ORDER BY " . $order);
+    
+    if ($result) while($row = $result->fetchArray(SQLITE3_ASSOC)){
+        $requests[]= $row;
+      }
+    return $requests;
+  }
+  
+  function getUserRequest($id) {
+    $result = $this->query("SELECT * FROM user_request WHERE id=".$id);
+    
+    if ($result) return $result->fetchArray(SQLITE3_ASSOC);
+    else return false;
+  }
+  
+  function insertUserRequest ($data) {
+
+     foreach ($data as $key => $val) {
+        $data[$key] = $this->db->escapeString($val);
+     }
+     
+     $query = "INSERT INTO user_request (name, surname, pref_uid, email, institution, newsletter, lang) 
+               VALUES ('".$data[name]."','".$data[surname]."','".$data[userid]."','"
+               .$data[email]."','".$data[institution]."','".$data[newsletter]."', '".$data[lang]."' )";
+     $this->query($query);
+  }
+  
+  function assignRequest($id) {
+     $query = "UPDATE user_request SET at_assignee = '" . $this->conf[login][user] . "', at_status = 3 WHERE id=".$id;
+     $this->query($query);
+  }
+  
+  function closeRequest($id, $ldap_uid) {
+    $query = "UPDATE user_request SET at_status = 1, ldap_uid = '".$ldap_uid."' WHERE id = ". $id;
+    $this->query($query);
+  }
+  
+  function rejectRequest($id) {
+    $query = "UPDATE user_request SET at_status = 2 WHERE id = ". $id;
+    $this->query($query);
+  }
+  
+  /**
+   * Methods dealing with Mailtemplates
+   */  
+  function getMailTemplates() {
+    $result = $this->query("SELECT id,title,lang FROM mail_template");
+    
+    if ($result) while($row = $result->fetchArray(SQLITE3_ASSOC)){
+        $mails[]= $row;
+      }
+    return $mails;
+  }
+  
+  function getMailTemplate($id) {
+    $result = $this->query("SELECT * FROM mail_template WHERE id=".$id);
+    
+    if ($result) return $result->fetchArray(SQLITE3_ASSOC);
+    else return false;
+  }  
+
+}
+
+?>
diff --git a/info.textgrid.middleware.tgauth.tgaccount/index.php b/info.textgrid.middleware.tgauth.tgaccount/index.php
new file mode 100644
index 0000000000000000000000000000000000000000..7dcd42542f752b4bf2cf8d24a3828f76ed1cbae9
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/index.php
@@ -0,0 +1,16 @@
+<?php
+include 'webinit.inc.php';
+
+
+if(isset($_GET['show'])){
+  $requests = $db->getUserRequests($_GET['show']);
+  $smarty->assign('show', $_GET['show'] );
+} else {
+  $requests = $db->getUserRequests('open');
+}
+
+$smarty->assign('login_user', $conf['login']['user'] );
+$smarty->assign('requests', $requests );
+$smarty->display('list_requests.tpl');
+
+?>
diff --git a/info.textgrid.middleware.tgauth.tgaccount/install/mailtemplates.sqlite.sql b/info.textgrid.middleware.tgauth.tgaccount/install/mailtemplates.sqlite.sql
new file mode 100644
index 0000000000000000000000000000000000000000..1c9f7802399398e524cecc5e5df11f35705c477a
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/install/mailtemplates.sqlite.sql
@@ -0,0 +1,81 @@
+PRAGMA synchronous = OFF;
+PRAGMA journal_mode = MEMORY;
+BEGIN TRANSACTION;
+INSERT INTO `mail_template` VALUES (1,'en','Nachhaken','Request on your account','Dear {name},
+
+We are glad about your interest in TextGrid but would like to verify
+its authenticity before we can provide you with a TextGrid account. Do
+you intent to use TextGrid for a private edition project or are you
+affiliated with an organisation that might want to employ a service
+like TextGrid?
+
+Please excuse our curiosity. TextGrid is a virtual research
+environment in which scholars collaborate closely, exchange their
+research data and trust one another. It must be made sure that new
+users are registered with their real names and have authentic
+scholarly interests. Therefore we would like to register you with an email-address affiliated to your institution.
+If there are further questions from your side, please do not hesitate to contact us.
+
+Sincerely yours,
+{sender}');
+INSERT INTO `mail_template` VALUES (2,'de','Nachhaken','Nachfrage zu Ihrem TextGrid Account','Sehr geehrte/r {name},
+
+vielen Dank für Ihr Interesse an TextGrid.
+
+Dürfen wir fragen, auf welche Weise Sie TextGrid nutzen wollen?
+Planen Sie privat an Editionsprojekten zu arbeiten, oder ist Ihre Institution an einem Dienst wie TextGrid interessiert? 
+
+Um auch für andere Forscher und Wissenschaftler zu gewährleisten, dass Ihr Interesse an TextGrid authentisch ist und mit Forschungsdaten korrekt umgegangen wird, würden wir Sie daher bitten, dass wir Ihren Account mit einer institutionellen Email-Adresse einrichten können.
+
+Verzeihen Sie bitte die Nachfragen. 
+Für weitere Informationen oder Fragen Ihrerseits stehen wir gern zur Verfügung!
+
+Viele Grüße,
+{sender}
+');
+INSERT INTO `mail_template` VALUES (3,'de','Welcome','TextGrid Nutzerdaten','Sehr geehrte/r {name}
+
+wir haben Ihnen einen Zugang für TextGrid erstellt.
+
+Ihre Nutzerdaten sind:
+
+Nutzername: {uid}
+Passwort: {password}
+
+Sie können Ihr Passwort im TextGridLab unter Help->Authentication ändern.
+
+Willkommen in der TextGrid Community!
+');
+INSERT INTO `mail_template` VALUES (4,'en','Welcome','TextGrid Account','Dear {name},
+
+we have established a textgrid.de account for you. You can now
+login to the TextGridLab with the following credentials:
+
+    username: {uid}
+    password: {password}
+
+You can change your textgrid.de password in the TextGridLab via
+Help->Authentication.
+
+Welcome to the TextGrid Community.
+
+Sincerely,
+the TextGrid team
+');
+INSERT INTO `mail_template` VALUES (5,'en','reg pending','TextGrid Account – Request Received','Thank you for your interest in TextGrid. Your request is being processed.
+
+Since researchers in TextGrid work closely with one another and exchange research data, each request is manually approved. We appreciate your patience – accounts are usually activated within 1-2 working days. 
+
+As soon as your account has been created, you will receive an email with your username and password.
+
+Sincerely,
+The TextGrid Team');
+INSERT INTO `mail_template` VALUES (6,'de','reg pending','TextGrid Account - Anfrage eingegangen','Vielen Dank für Ihr Interesse an TextGrid. Ihre Anfrage wurde registriert.
+
+Da im TextGridLab Wissenschaftler eng zusammenarbeiten und Forschungsdaten austauschen, wird jede Anfrage manuell geprüft. Wir bitten Sie daher um ein wenig Geduld – in der Regel wird der Account innerhalb von 1-2 Werktagen freigeschaltet.
+
+Sobald Ihr Zugang erstellt ist, erhalten Sie eine E-Mail mit Ihrem Benutzernamen und Ihrem Passwort.
+
+Mit freundlichen Grüßen,
+das TextGrid Team');
+END TRANSACTION;
diff --git a/info.textgrid.middleware.tgauth.tgaccount/install/tgaccount.ini.tmpl b/info.textgrid.middleware.tgauth.tgaccount/install/tgaccount.ini.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..1ca799271d3b0494823f9ef38b4fd4b024275069
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/install/tgaccount.ini.tmpl
@@ -0,0 +1,21 @@
+[host]
+host = http://localhost/tgaccount
+
+[sqlite]
+# WARNING: database should be outside webroot and needs to be writable by www user
+sqlitePath = /var/textgrid/tgaccount/tgaccount.sqlite
+
+[ldap]
+ldapHost = textgrid-users
+ldapPort = 389
+ldapTLS = true
+managerdn = "cn=Manager,dc=textgrid,dc=de"
+managerpassword = 
+
+[mail]
+mailSender = register@textgrid.de
+mailCC = TEXTGRID-REGISTER@D-GRID.DE
+
+[smarty]
+smartyLib = /usr/share/php/smarty/libs/Smarty.class.php
+
diff --git a/info.textgrid.middleware.tgauth.tgaccount/install/tgaccount.sqlite.sql b/info.textgrid.middleware.tgauth.tgaccount/install/tgaccount.sqlite.sql
new file mode 100644
index 0000000000000000000000000000000000000000..0b0e4004805296ae6aed79e5b54c1059fad70d3d
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/install/tgaccount.sqlite.sql
@@ -0,0 +1,34 @@
+PRAGMA synchronous = OFF;
+PRAGMA journal_mode = MEMORY;
+BEGIN TRANSACTION;
+CREATE TABLE `mail_template` (
+  `id` integer NOT NULL PRIMARY KEY AUTOINCREMENT
+,  `lang` varchar(255) NOT NULL
+,  `title` varchar(255) NOT NULL
+,  `subject` varchar(255) NOT NULL
+,  `body` text NOT NULL
+);
+CREATE TABLE `mails` (
+  `id` integer NOT NULL PRIMARY KEY AUTOINCREMENT
+,  `imap_uid` integer NOT NULL
+,  `user_request_id` integer NOT NULL
+,  `email` varchar(255) NOT NULL
+,  `date` timestamp NOT NULL 
+,  `seen` integer NOT NULL DEFAULT '0'
+,  UNIQUE (`imap_uid`)
+);
+CREATE TABLE `user_request` (
+  `id` integer NOT NULL PRIMARY KEY AUTOINCREMENT
+,  `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+,  `name` varchar(255) DEFAULT NULL
+,  `surname` varchar(255) NOT NULL
+,  `pref_uid` varchar(255) NOT NULL
+,  `ldap_uid` varchar(255) NOT NULL
+,  `email` varchar(255) DEFAULT NULL
+,  `institution` varchar(255) DEFAULT NULL
+,  `newsletter` integer NOT NULL
+,  `lang` varchar(255) NOT NULL
+,  `at_status` integer NOT NULL
+,  `at_assignee` varchar(255) NOT NULL
+);
+END TRANSACTION;
diff --git a/info.textgrid.middleware.tgauth.tgaccount/js/prototype.js b/info.textgrid.middleware.tgauth.tgaccount/js/prototype.js
new file mode 100644
index 0000000000000000000000000000000000000000..9fe6e1243bc0a7cf652985c3edd0d4dc9d8bdfb8
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/js/prototype.js
@@ -0,0 +1,4874 @@
+/*  Prototype JavaScript framework, version 1.6.1
+ *  (c) 2005-2009 Sam Stephenson
+ *
+ *  Prototype is freely distributable under the terms of an MIT-style license.
+ *  For details, see the Prototype web site: http://www.prototypejs.org/
+ *
+ *--------------------------------------------------------------------------*/
+
+var Prototype = {
+  Version: '1.6.1',
+
+  Browser: (function(){
+    var ua = navigator.userAgent;
+    var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
+    return {
+      IE:             !!window.attachEvent && !isOpera,
+      Opera:          isOpera,
+      WebKit:         ua.indexOf('AppleWebKit/') > -1,
+      Gecko:          ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1,
+      MobileSafari:   /Apple.*Mobile.*Safari/.test(ua)
+    }
+  })(),
+
+  BrowserFeatures: {
+    XPath: !!document.evaluate,
+    SelectorsAPI: !!document.querySelector,
+    ElementExtensions: (function() {
+      var constructor = window.Element || window.HTMLElement;
+      return !!(constructor && constructor.prototype);
+    })(),
+    SpecificElementExtensions: (function() {
+      if (typeof window.HTMLDivElement !== 'undefined')
+        return true;
+
+      var div = document.createElement('div');
+      var form = document.createElement('form');
+      var isSupported = false;
+
+      if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) {
+        isSupported = true;
+      }
+
+      div = form = null;
+
+      return isSupported;
+    })()
+  },
+
+  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
+  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
+
+  emptyFunction: function() { },
+  K: function(x) { return x }
+};
+
+if (Prototype.Browser.MobileSafari)
+  Prototype.BrowserFeatures.SpecificElementExtensions = false;
+
+
+var Abstract = { };
+
+
+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;
+  }
+};
+
+/* Based on Alex Arnell's inheritance implementation. */
+
+var Class = (function() {
+  function subclass() {};
+  function create() {
+    var parent = null, properties = $A(arguments);
+    if (Object.isFunction(properties[0]))
+      parent = properties.shift();
+
+    function klass() {
+      this.initialize.apply(this, arguments);
+    }
+
+    Object.extend(klass, Class.Methods);
+    klass.superclass = parent;
+    klass.subclasses = [];
+
+    if (parent) {
+      subclass.prototype = parent.prototype;
+      klass.prototype = new subclass;
+      parent.subclasses.push(klass);
+    }
+
+    for (var i = 0; i < properties.length; i++)
+      klass.addMethods(properties[i]);
+
+    if (!klass.prototype.initialize)
+      klass.prototype.initialize = Prototype.emptyFunction;
+
+    klass.prototype.constructor = klass;
+    return klass;
+  }
+
+  function addMethods(source) {
+    var ancestor   = this.superclass && this.superclass.prototype;
+    var properties = Object.keys(source);
+
+    if (!Object.keys({ toString: true }).length) {
+      if (source.toString != Object.prototype.toString)
+        properties.push("toString");
+      if (source.valueOf != Object.prototype.valueOf)
+        properties.push("valueOf");
+    }
+
+    for (var i = 0, length = properties.length; i < length; i++) {
+      var property = properties[i], value = source[property];
+      if (ancestor && Object.isFunction(value) &&
+          value.argumentNames().first() == "$super") {
+        var method = value;
+        value = (function(m) {
+          return function() { return ancestor[m].apply(this, arguments); };
+        })(property).wrap(method);
+
+        value.valueOf = method.valueOf.bind(method);
+        value.toString = method.toString.bind(method);
+      }
+      this.prototype[property] = value;
+    }
+
+    return this;
+  }
+
+  return {
+    create: create,
+    Methods: {
+      addMethods: addMethods
+    }
+  };
+})();
+(function() {
+
+  var _toString = Object.prototype.toString;
+
+  function extend(destination, source) {
+    for (var property in source)
+      destination[property] = source[property];
+    return destination;
+  }
+
+  function inspect(object) {
+    try {
+      if (isUndefined(object)) return 'undefined';
+      if (object === null) return 'null';
+      return object.inspect ? object.inspect() : String(object);
+    } catch (e) {
+      if (e instanceof RangeError) return '...';
+      throw e;
+    }
+  }
+
+  function toJSON(object) {
+    var type = typeof object;
+    switch (type) {
+      case 'undefined':
+      case 'function':
+      case 'unknown': return;
+      case 'boolean': return object.toString();
+    }
+
+    if (object === null) return 'null';
+    if (object.toJSON) return object.toJSON();
+    if (isElement(object)) return;
+
+    var results = [];
+    for (var property in object) {
+      var value = toJSON(object[property]);
+      if (!isUndefined(value))
+        results.push(property.toJSON() + ': ' + value);
+    }
+
+    return '{' + results.join(', ') + '}';
+  }
+
+  function toQueryString(object) {
+    return $H(object).toQueryString();
+  }
+
+  function toHTML(object) {
+    return object && object.toHTML ? object.toHTML() : String.interpret(object);
+  }
+
+  function keys(object) {
+    var results = [];
+    for (var property in object)
+      results.push(property);
+    return results;
+  }
+
+  function values(object) {
+    var results = [];
+    for (var property in object)
+      results.push(object[property]);
+    return results;
+  }
+
+  function clone(object) {
+    return extend({ }, object);
+  }
+
+  function isElement(object) {
+    return !!(object && object.nodeType == 1);
+  }
+
+  function isArray(object) {
+    return _toString.call(object) == "[object Array]";
+  }
+
+
+  function isHash(object) {
+    return object instanceof Hash;
+  }
+
+  function isFunction(object) {
+    return typeof object === "function";
+  }
+
+  function isString(object) {
+    return _toString.call(object) == "[object String]";
+  }
+
+  function isNumber(object) {
+    return _toString.call(object) == "[object Number]";
+  }
+
+  function isUndefined(object) {
+    return typeof object === "undefined";
+  }
+
+  extend(Object, {
+    extend:        extend,
+    inspect:       inspect,
+    toJSON:        toJSON,
+    toQueryString: toQueryString,
+    toHTML:        toHTML,
+    keys:          keys,
+    values:        values,
+    clone:         clone,
+    isElement:     isElement,
+    isArray:       isArray,
+    isHash:        isHash,
+    isFunction:    isFunction,
+    isString:      isString,
+    isNumber:      isNumber,
+    isUndefined:   isUndefined
+  });
+})();
+Object.extend(Function.prototype, (function() {
+  var slice = Array.prototype.slice;
+
+  function update(array, args) {
+    var arrayLength = array.length, length = args.length;
+    while (length--) array[arrayLength + length] = args[length];
+    return array;
+  }
+
+  function merge(array, args) {
+    array = slice.call(array, 0);
+    return update(array, args);
+  }
+
+  function argumentNames() {
+    var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1]
+      .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
+      .replace(/\s+/g, '').split(',');
+    return names.length == 1 && !names[0] ? [] : names;
+  }
+
+  function bind(context) {
+    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
+    var __method = this, args = slice.call(arguments, 1);
+    return function() {
+      var a = merge(args, arguments);
+      return __method.apply(context, a);
+    }
+  }
+
+  function bindAsEventListener(context) {
+    var __method = this, args = slice.call(arguments, 1);
+    return function(event) {
+      var a = update([event || window.event], args);
+      return __method.apply(context, a);
+    }
+  }
+
+  function curry() {
+    if (!arguments.length) return this;
+    var __method = this, args = slice.call(arguments, 0);
+    return function() {
+      var a = merge(args, arguments);
+      return __method.apply(this, a);
+    }
+  }
+
+  function delay(timeout) {
+    var __method = this, args = slice.call(arguments, 1);
+    timeout = timeout * 1000
+    return window.setTimeout(function() {
+      return __method.apply(__method, args);
+    }, timeout);
+  }
+
+  function defer() {
+    var args = update([0.01], arguments);
+    return this.delay.apply(this, args);
+  }
+
+  function wrap(wrapper) {
+    var __method = this;
+    return function() {
+      var a = update([__method.bind(this)], arguments);
+      return wrapper.apply(this, a);
+    }
+  }
+
+  function methodize() {
+    if (this._methodized) return this._methodized;
+    var __method = this;
+    return this._methodized = function() {
+      var a = update([this], arguments);
+      return __method.apply(null, a);
+    };
+  }
+
+  return {
+    argumentNames:       argumentNames,
+    bind:                bind,
+    bindAsEventListener: bindAsEventListener,
+    curry:               curry,
+    delay:               delay,
+    defer:               defer,
+    wrap:                wrap,
+    methodize:           methodize
+  }
+})());
+
+
+Date.prototype.toJSON = function() {
+  return '"' + this.getUTCFullYear() + '-' +
+    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
+    this.getUTCDate().toPaddedString(2) + 'T' +
+    this.getUTCHours().toPaddedString(2) + ':' +
+    this.getUTCMinutes().toPaddedString(2) + ':' +
+    this.getUTCSeconds().toPaddedString(2) + 'Z"';
+};
+
+
+RegExp.prototype.match = RegExp.prototype.test;
+
+RegExp.escape = function(str) {
+  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
+};
+var PeriodicalExecuter = Class.create({
+  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);
+  },
+
+  execute: function() {
+    this.callback(this);
+  },
+
+  stop: function() {
+    if (!this.timer) return;
+    clearInterval(this.timer);
+    this.timer = null;
+  },
+
+  onTimerEvent: function() {
+    if (!this.currentlyExecuting) {
+      try {
+        this.currentlyExecuting = true;
+        this.execute();
+        this.currentlyExecuting = false;
+      } catch(e) {
+        this.currentlyExecuting = false;
+        throw e;
+      }
+    }
+  }
+});
+Object.extend(String, {
+  interpret: function(value) {
+    return value == null ? '' : String(value);
+  },
+  specialChar: {
+    '\b': '\\b',
+    '\t': '\\t',
+    '\n': '\\n',
+    '\f': '\\f',
+    '\r': '\\r',
+    '\\': '\\\\'
+  }
+});
+
+Object.extend(String.prototype, (function() {
+
+  function prepareReplacement(replacement) {
+    if (Object.isFunction(replacement)) return replacement;
+    var template = new Template(replacement);
+    return function(match) { return template.evaluate(match) };
+  }
+
+  function gsub(pattern, replacement) {
+    var result = '', source = this, match;
+    replacement = prepareReplacement(replacement);
+
+    if (Object.isString(pattern))
+      pattern = RegExp.escape(pattern);
+
+    if (!(pattern.length || pattern.source)) {
+      replacement = replacement('');
+      return replacement + source.split('').join(replacement) + 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;
+  }
+
+  function sub(pattern, replacement, count) {
+    replacement = prepareReplacement(replacement);
+    count = Object.isUndefined(count) ? 1 : count;
+
+    return this.gsub(pattern, function(match) {
+      if (--count < 0) return match[0];
+      return replacement(match);
+    });
+  }
+
+  function scan(pattern, iterator) {
+    this.gsub(pattern, iterator);
+    return String(this);
+  }
+
+  function truncate(length, truncation) {
+    length = length || 30;
+    truncation = Object.isUndefined(truncation) ? '...' : truncation;
+    return this.length > length ?
+      this.slice(0, length - truncation.length) + truncation : String(this);
+  }
+
+  function strip() {
+    return this.replace(/^\s+/, '').replace(/\s+$/, '');
+  }
+
+  function stripTags() {
+    return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, '');
+  }
+
+  function stripScripts() {
+    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
+  }
+
+  function extractScripts() {
+    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];
+    });
+  }
+
+  function evalScripts() {
+    return this.extractScripts().map(function(script) { return eval(script) });
+  }
+
+  function escapeHTML() {
+    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
+  }
+
+  function unescapeHTML() {
+    return this.stripTags().replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&');
+  }
+
+
+  function toQueryParams(separator) {
+    var match = this.strip().match(/([^?#]*)(#.*)?$/);
+    if (!match) return { };
+
+    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
+      if ((pair = pair.split('='))[0]) {
+        var key = decodeURIComponent(pair.shift());
+        var value = pair.length > 1 ? pair.join('=') : pair[0];
+        if (value != undefined) value = decodeURIComponent(value);
+
+        if (key in hash) {
+          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
+          hash[key].push(value);
+        }
+        else hash[key] = value;
+      }
+      return hash;
+    });
+  }
+
+  function toArray() {
+    return this.split('');
+  }
+
+  function succ() {
+    return this.slice(0, this.length - 1) +
+      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
+  }
+
+  function times(count) {
+    return count < 1 ? '' : new Array(count + 1).join(this);
+  }
+
+  function camelize() {
+    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;
+  }
+
+  function capitalize() {
+    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
+  }
+
+  function underscore() {
+    return this.replace(/::/g, '/')
+               .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
+               .replace(/([a-z\d])([A-Z])/g, '$1_$2')
+               .replace(/-/g, '_')
+               .toLowerCase();
+  }
+
+  function dasherize() {
+    return this.replace(/_/g, '-');
+  }
+
+  function inspect(useDoubleQuotes) {
+    var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) {
+      if (character in String.specialChar) {
+        return String.specialChar[character];
+      }
+      return '\\u00' + character.charCodeAt().toPaddedString(2, 16);
+    });
+    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
+    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
+  }
+
+  function toJSON() {
+    return this.inspect(true);
+  }
+
+  function unfilterJSON(filter) {
+    return this.replace(filter || Prototype.JSONFilter, '$1');
+  }
+
+  function isJSON() {
+    var str = this;
+    if (str.blank()) return false;
+    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
+    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
+  }
+
+  function evalJSON(sanitize) {
+    var json = this.unfilterJSON();
+    try {
+      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
+    } catch (e) { }
+    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
+  }
+
+  function include(pattern) {
+    return this.indexOf(pattern) > -1;
+  }
+
+  function startsWith(pattern) {
+    return this.indexOf(pattern) === 0;
+  }
+
+  function endsWith(pattern) {
+    var d = this.length - pattern.length;
+    return d >= 0 && this.lastIndexOf(pattern) === d;
+  }
+
+  function empty() {
+    return this == '';
+  }
+
+  function blank() {
+    return /^\s*$/.test(this);
+  }
+
+  function interpolate(object, pattern) {
+    return new Template(this, pattern).evaluate(object);
+  }
+
+  return {
+    gsub:           gsub,
+    sub:            sub,
+    scan:           scan,
+    truncate:       truncate,
+    strip:          String.prototype.trim ? String.prototype.trim : strip,
+    stripTags:      stripTags,
+    stripScripts:   stripScripts,
+    extractScripts: extractScripts,
+    evalScripts:    evalScripts,
+    escapeHTML:     escapeHTML,
+    unescapeHTML:   unescapeHTML,
+    toQueryParams:  toQueryParams,
+    parseQuery:     toQueryParams,
+    toArray:        toArray,
+    succ:           succ,
+    times:          times,
+    camelize:       camelize,
+    capitalize:     capitalize,
+    underscore:     underscore,
+    dasherize:      dasherize,
+    inspect:        inspect,
+    toJSON:         toJSON,
+    unfilterJSON:   unfilterJSON,
+    isJSON:         isJSON,
+    evalJSON:       evalJSON,
+    include:        include,
+    startsWith:     startsWith,
+    endsWith:       endsWith,
+    empty:          empty,
+    blank:          blank,
+    interpolate:    interpolate
+  };
+})());
+
+var Template = Class.create({
+  initialize: function(template, pattern) {
+    this.template = template.toString();
+    this.pattern = pattern || Template.Pattern;
+  },
+
+  evaluate: function(object) {
+    if (object && Object.isFunction(object.toTemplateReplacements))
+      object = object.toTemplateReplacements();
+
+    return this.template.gsub(this.pattern, function(match) {
+      if (object == null) return (match[1] + '');
+
+      var before = match[1] || '';
+      if (before == '\\') return match[2];
+
+      var ctx = object, expr = match[3];
+      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
+      match = pattern.exec(expr);
+      if (match == null) return before;
+
+      while (match != null) {
+        var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1];
+        ctx = ctx[comp];
+        if (null == ctx || '' == match[3]) break;
+        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
+        match = pattern.exec(expr);
+      }
+
+      return before + String.interpret(ctx);
+    });
+  }
+});
+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
+
+var $break = { };
+
+var Enumerable = (function() {
+  function each(iterator, context) {
+    var index = 0;
+    try {
+      this._each(function(value) {
+        iterator.call(context, value, index++);
+      });
+    } catch (e) {
+      if (e != $break) throw e;
+    }
+    return this;
+  }
+
+  function eachSlice(number, iterator, context) {
+    var index = -number, slices = [], array = this.toArray();
+    if (number < 1) return array;
+    while ((index += number) < array.length)
+      slices.push(array.slice(index, index+number));
+    return slices.collect(iterator, context);
+  }
+
+  function all(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var result = true;
+    this.each(function(value, index) {
+      result = result && !!iterator.call(context, value, index);
+      if (!result) throw $break;
+    });
+    return result;
+  }
+
+  function any(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var result = false;
+    this.each(function(value, index) {
+      if (result = !!iterator.call(context, value, index))
+        throw $break;
+    });
+    return result;
+  }
+
+  function collect(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var results = [];
+    this.each(function(value, index) {
+      results.push(iterator.call(context, value, index));
+    });
+    return results;
+  }
+
+  function detect(iterator, context) {
+    var result;
+    this.each(function(value, index) {
+      if (iterator.call(context, value, index)) {
+        result = value;
+        throw $break;
+      }
+    });
+    return result;
+  }
+
+  function findAll(iterator, context) {
+    var results = [];
+    this.each(function(value, index) {
+      if (iterator.call(context, value, index))
+        results.push(value);
+    });
+    return results;
+  }
+
+  function grep(filter, iterator, context) {
+    iterator = iterator || Prototype.K;
+    var results = [];
+
+    if (Object.isString(filter))
+      filter = new RegExp(RegExp.escape(filter));
+
+    this.each(function(value, index) {
+      if (filter.match(value))
+        results.push(iterator.call(context, value, index));
+    });
+    return results;
+  }
+
+  function include(object) {
+    if (Object.isFunction(this.indexOf))
+      if (this.indexOf(object) != -1) return true;
+
+    var found = false;
+    this.each(function(value) {
+      if (value == object) {
+        found = true;
+        throw $break;
+      }
+    });
+    return found;
+  }
+
+  function inGroupsOf(number, fillWith) {
+    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
+    return this.eachSlice(number, function(slice) {
+      while(slice.length < number) slice.push(fillWith);
+      return slice;
+    });
+  }
+
+  function inject(memo, iterator, context) {
+    this.each(function(value, index) {
+      memo = iterator.call(context, memo, value, index);
+    });
+    return memo;
+  }
+
+  function invoke(method) {
+    var args = $A(arguments).slice(1);
+    return this.map(function(value) {
+      return value[method].apply(value, args);
+    });
+  }
+
+  function max(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator.call(context, value, index);
+      if (result == null || value >= result)
+        result = value;
+    });
+    return result;
+  }
+
+  function min(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator.call(context, value, index);
+      if (result == null || value < result)
+        result = value;
+    });
+    return result;
+  }
+
+  function partition(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var trues = [], falses = [];
+    this.each(function(value, index) {
+      (iterator.call(context, value, index) ?
+        trues : falses).push(value);
+    });
+    return [trues, falses];
+  }
+
+  function pluck(property) {
+    var results = [];
+    this.each(function(value) {
+      results.push(value[property]);
+    });
+    return results;
+  }
+
+  function reject(iterator, context) {
+    var results = [];
+    this.each(function(value, index) {
+      if (!iterator.call(context, value, index))
+        results.push(value);
+    });
+    return results;
+  }
+
+  function sortBy(iterator, context) {
+    return this.map(function(value, index) {
+      return {
+        value: value,
+        criteria: iterator.call(context, value, index)
+      };
+    }).sort(function(left, right) {
+      var a = left.criteria, b = right.criteria;
+      return a < b ? -1 : a > b ? 1 : 0;
+    }).pluck('value');
+  }
+
+  function toArray() {
+    return this.map();
+  }
+
+  function zip() {
+    var iterator = Prototype.K, args = $A(arguments);
+    if (Object.isFunction(args.last()))
+      iterator = args.pop();
+
+    var collections = [this].concat(args).map($A);
+    return this.map(function(value, index) {
+      return iterator(collections.pluck(index));
+    });
+  }
+
+  function size() {
+    return this.toArray().length;
+  }
+
+  function inspect() {
+    return '#<Enumerable:' + this.toArray().inspect() + '>';
+  }
+
+
+
+
+
+
+
+
+
+  return {
+    each:       each,
+    eachSlice:  eachSlice,
+    all:        all,
+    every:      all,
+    any:        any,
+    some:       any,
+    collect:    collect,
+    map:        collect,
+    detect:     detect,
+    findAll:    findAll,
+    select:     findAll,
+    filter:     findAll,
+    grep:       grep,
+    include:    include,
+    member:     include,
+    inGroupsOf: inGroupsOf,
+    inject:     inject,
+    invoke:     invoke,
+    max:        max,
+    min:        min,
+    partition:  partition,
+    pluck:      pluck,
+    reject:     reject,
+    sortBy:     sortBy,
+    toArray:    toArray,
+    entries:    toArray,
+    zip:        zip,
+    size:       size,
+    inspect:    inspect,
+    find:       detect
+  };
+})();
+function $A(iterable) {
+  if (!iterable) return [];
+  if ('toArray' in Object(iterable)) return iterable.toArray();
+  var length = iterable.length || 0, results = new Array(length);
+  while (length--) results[length] = iterable[length];
+  return results;
+}
+
+function $w(string) {
+  if (!Object.isString(string)) return [];
+  string = string.strip();
+  return string ? string.split(/\s+/) : [];
+}
+
+Array.from = $A;
+
+
+(function() {
+  var arrayProto = Array.prototype,
+      slice = arrayProto.slice,
+      _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available
+
+  function each(iterator) {
+    for (var i = 0, length = this.length; i < length; i++)
+      iterator(this[i]);
+  }
+  if (!_each) _each = each;
+
+  function clear() {
+    this.length = 0;
+    return this;
+  }
+
+  function first() {
+    return this[0];
+  }
+
+  function last() {
+    return this[this.length - 1];
+  }
+
+  function compact() {
+    return this.select(function(value) {
+      return value != null;
+    });
+  }
+
+  function flatten() {
+    return this.inject([], function(array, value) {
+      if (Object.isArray(value))
+        return array.concat(value.flatten());
+      array.push(value);
+      return array;
+    });
+  }
+
+  function without() {
+    var values = slice.call(arguments, 0);
+    return this.select(function(value) {
+      return !values.include(value);
+    });
+  }
+
+  function reverse(inline) {
+    return (inline !== false ? this : this.toArray())._reverse();
+  }
+
+  function uniq(sorted) {
+    return this.inject([], function(array, value, index) {
+      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
+        array.push(value);
+      return array;
+    });
+  }
+
+  function intersect(array) {
+    return this.uniq().findAll(function(item) {
+      return array.detect(function(value) { return item === value });
+    });
+  }
+
+
+  function clone() {
+    return slice.call(this, 0);
+  }
+
+  function size() {
+    return this.length;
+  }
+
+  function inspect() {
+    return '[' + this.map(Object.inspect).join(', ') + ']';
+  }
+
+  function toJSON() {
+    var results = [];
+    this.each(function(object) {
+      var value = Object.toJSON(object);
+      if (!Object.isUndefined(value)) results.push(value);
+    });
+    return '[' + results.join(', ') + ']';
+  }
+
+  function indexOf(item, i) {
+    i || (i = 0);
+    var length = this.length;
+    if (i < 0) i = length + i;
+    for (; i < length; i++)
+      if (this[i] === item) return i;
+    return -1;
+  }
+
+  function lastIndexOf(item, i) {
+    i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
+    var n = this.slice(0, i).reverse().indexOf(item);
+    return (n < 0) ? n : i - n - 1;
+  }
+
+  function concat() {
+    var array = slice.call(this, 0), item;
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      item = arguments[i];
+      if (Object.isArray(item) && !('callee' in item)) {
+        for (var j = 0, arrayLength = item.length; j < arrayLength; j++)
+          array.push(item[j]);
+      } else {
+        array.push(item);
+      }
+    }
+    return array;
+  }
+
+  Object.extend(arrayProto, Enumerable);
+
+  if (!arrayProto._reverse)
+    arrayProto._reverse = arrayProto.reverse;
+
+  Object.extend(arrayProto, {
+    _each:     _each,
+    clear:     clear,
+    first:     first,
+    last:      last,
+    compact:   compact,
+    flatten:   flatten,
+    without:   without,
+    reverse:   reverse,
+    uniq:      uniq,
+    intersect: intersect,
+    clone:     clone,
+    toArray:   clone,
+    size:      size,
+    inspect:   inspect,
+    toJSON:    toJSON
+  });
+
+  var CONCAT_ARGUMENTS_BUGGY = (function() {
+    return [].concat(arguments)[0][0] !== 1;
+  })(1,2)
+
+  if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat;
+
+  if (!arrayProto.indexOf) arrayProto.indexOf = indexOf;
+  if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf;
+})();
+function $H(object) {
+  return new Hash(object);
+};
+
+var Hash = Class.create(Enumerable, (function() {
+  function initialize(object) {
+    this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
+  }
+
+  function _each(iterator) {
+    for (var key in this._object) {
+      var value = this._object[key], pair = [key, value];
+      pair.key = key;
+      pair.value = value;
+      iterator(pair);
+    }
+  }
+
+  function set(key, value) {
+    return this._object[key] = value;
+  }
+
+  function get(key) {
+    if (this._object[key] !== Object.prototype[key])
+      return this._object[key];
+  }
+
+  function unset(key) {
+    var value = this._object[key];
+    delete this._object[key];
+    return value;
+  }
+
+  function toObject() {
+    return Object.clone(this._object);
+  }
+
+  function keys() {
+    return this.pluck('key');
+  }
+
+  function values() {
+    return this.pluck('value');
+  }
+
+  function index(value) {
+    var match = this.detect(function(pair) {
+      return pair.value === value;
+    });
+    return match && match.key;
+  }
+
+  function merge(object) {
+    return this.clone().update(object);
+  }
+
+  function update(object) {
+    return new Hash(object).inject(this, function(result, pair) {
+      result.set(pair.key, pair.value);
+      return result;
+    });
+  }
+
+  function toQueryPair(key, value) {
+    if (Object.isUndefined(value)) return key;
+    return key + '=' + encodeURIComponent(String.interpret(value));
+  }
+
+  function toQueryString() {
+    return this.inject([], function(results, pair) {
+      var key = encodeURIComponent(pair.key), values = pair.value;
+
+      if (values && typeof values == 'object') {
+        if (Object.isArray(values))
+          return results.concat(values.map(toQueryPair.curry(key)));
+      } else results.push(toQueryPair(key, values));
+      return results;
+    }).join('&');
+  }
+
+  function inspect() {
+    return '#<Hash:{' + this.map(function(pair) {
+      return pair.map(Object.inspect).join(': ');
+    }).join(', ') + '}>';
+  }
+
+  function toJSON() {
+    return Object.toJSON(this.toObject());
+  }
+
+  function clone() {
+    return new Hash(this);
+  }
+
+  return {
+    initialize:             initialize,
+    _each:                  _each,
+    set:                    set,
+    get:                    get,
+    unset:                  unset,
+    toObject:               toObject,
+    toTemplateReplacements: toObject,
+    keys:                   keys,
+    values:                 values,
+    index:                  index,
+    merge:                  merge,
+    update:                 update,
+    toQueryString:          toQueryString,
+    inspect:                inspect,
+    toJSON:                 toJSON,
+    clone:                  clone
+  };
+})());
+
+Hash.from = $H;
+Object.extend(Number.prototype, (function() {
+  function toColorPart() {
+    return this.toPaddedString(2, 16);
+  }
+
+  function succ() {
+    return this + 1;
+  }
+
+  function times(iterator, context) {
+    $R(0, this, true).each(iterator, context);
+    return this;
+  }
+
+  function toPaddedString(length, radix) {
+    var string = this.toString(radix || 10);
+    return '0'.times(length - string.length) + string;
+  }
+
+  function toJSON() {
+    return isFinite(this) ? this.toString() : 'null';
+  }
+
+  function abs() {
+    return Math.abs(this);
+  }
+
+  function round() {
+    return Math.round(this);
+  }
+
+  function ceil() {
+    return Math.ceil(this);
+  }
+
+  function floor() {
+    return Math.floor(this);
+  }
+
+  return {
+    toColorPart:    toColorPart,
+    succ:           succ,
+    times:          times,
+    toPaddedString: toPaddedString,
+    toJSON:         toJSON,
+    abs:            abs,
+    round:          round,
+    ceil:           ceil,
+    floor:          floor
+  };
+})());
+
+function $R(start, end, exclusive) {
+  return new ObjectRange(start, end, exclusive);
+}
+
+var ObjectRange = Class.create(Enumerable, (function() {
+  function initialize(start, end, exclusive) {
+    this.start = start;
+    this.end = end;
+    this.exclusive = exclusive;
+  }
+
+  function _each(iterator) {
+    var value = this.start;
+    while (this.include(value)) {
+      iterator(value);
+      value = value.succ();
+    }
+  }
+
+  function include(value) {
+    if (value < this.start)
+      return false;
+    if (this.exclusive)
+      return value < this.end;
+    return value <= this.end;
+  }
+
+  return {
+    initialize: initialize,
+    _each:      _each,
+    include:    include
+  };
+})());
+
+
+
+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 (Object.isFunction(responder[callback])) {
+        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 = Class.create({
+  initialize: function(options) {
+    this.options = {
+      method:       'post',
+      asynchronous: true,
+      contentType:  'application/x-www-form-urlencoded',
+      encoding:     'UTF-8',
+      parameters:   '',
+      evalJSON:     true,
+      evalJS:       true
+    };
+    Object.extend(this.options, options || { });
+
+    this.options.method = this.options.method.toLowerCase();
+
+    if (Object.isString(this.options.parameters))
+      this.options.parameters = this.options.parameters.toQueryParams();
+    else if (Object.isHash(this.options.parameters))
+      this.options.parameters = this.options.parameters.toObject();
+  }
+});
+Ajax.Request = Class.create(Ajax.Base, {
+  _complete: false,
+
+  initialize: function($super, url, options) {
+    $super(options);
+    this.transport = Ajax.getTransport();
+    this.request(url);
+  },
+
+  request: function(url) {
+    this.url = url;
+    this.method = this.options.method;
+    var params = Object.clone(this.options.parameters);
+
+    if (!['get', 'post'].include(this.method)) {
+      params['_method'] = this.method;
+      this.method = 'post';
+    }
+
+    this.parameters = params;
+
+    if (params = Object.toQueryString(params)) {
+      if (this.method == 'get')
+        this.url += (this.url.include('?') ? '&' : '?') + params;
+      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
+        params += '&_=';
+    }
+
+    try {
+      var response = new Ajax.Response(this);
+      if (this.options.onCreate) this.options.onCreate(response);
+      Ajax.Responders.dispatch('onCreate', this, response);
+
+      this.transport.open(this.method.toUpperCase(), this.url,
+        this.options.asynchronous);
+
+      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
+
+      this.transport.onreadystatechange = this.onStateChange.bind(this);
+      this.setRequestHeaders();
+
+      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
+      this.transport.send(this.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';
+    }
+
+    if (typeof this.options.requestHeaders == 'object') {
+      var extras = this.options.requestHeaders;
+
+      if (Object.isFunction(extras.push))
+        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)
+      this.transport.setRequestHeader(name, headers[name]);
+  },
+
+  success: function() {
+    var status = this.getStatus();
+    return !status || (status >= 200 && status < 300);
+  },
+
+  getStatus: function() {
+    try {
+      return this.transport.status || 0;
+    } catch (e) { return 0 }
+  },
+
+  respondToReadyState: function(readyState) {
+    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
+
+    if (state == 'Complete') {
+      try {
+        this._complete = true;
+        (this.options['on' + response.status]
+         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
+         || Prototype.emptyFunction)(response, response.headerJSON);
+      } catch (e) {
+        this.dispatchException(e);
+      }
+
+      var contentType = response.getHeader('Content-type');
+      if (this.options.evalJS == 'force'
+          || (this.options.evalJS && this.isSameOrigin() && contentType
+          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
+        this.evalResponse();
+    }
+
+    try {
+      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
+      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
+    } catch (e) {
+      this.dispatchException(e);
+    }
+
+    if (state == 'Complete') {
+      this.transport.onreadystatechange = Prototype.emptyFunction;
+    }
+  },
+
+  isSameOrigin: function() {
+    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
+    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
+      protocol: location.protocol,
+      domain: document.domain,
+      port: location.port ? ':' + location.port : ''
+    }));
+  },
+
+  getHeader: function(name) {
+    try {
+      return this.transport.getResponseHeader(name) || null;
+    } catch (e) { return null; }
+  },
+
+  evalResponse: function() {
+    try {
+      return eval((this.transport.responseText || '').unfilterJSON());
+    } catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  dispatchException: function(exception) {
+    (this.options.onException || Prototype.emptyFunction)(this, exception);
+    Ajax.Responders.dispatch('onException', this, exception);
+  }
+});
+
+Ajax.Request.Events =
+  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+
+
+
+
+
+
+
+Ajax.Response = Class.create({
+  initialize: function(request){
+    this.request = request;
+    var transport  = this.transport  = request.transport,
+        readyState = this.readyState = transport.readyState;
+
+    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
+      this.status       = this.getStatus();
+      this.statusText   = this.getStatusText();
+      this.responseText = String.interpret(transport.responseText);
+      this.headerJSON   = this._getHeaderJSON();
+    }
+
+    if(readyState == 4) {
+      var xml = transport.responseXML;
+      this.responseXML  = Object.isUndefined(xml) ? null : xml;
+      this.responseJSON = this._getResponseJSON();
+    }
+  },
+
+  status:      0,
+
+  statusText: '',
+
+  getStatus: Ajax.Request.prototype.getStatus,
+
+  getStatusText: function() {
+    try {
+      return this.transport.statusText || '';
+    } catch (e) { return '' }
+  },
+
+  getHeader: Ajax.Request.prototype.getHeader,
+
+  getAllHeaders: function() {
+    try {
+      return this.getAllResponseHeaders();
+    } catch (e) { return null }
+  },
+
+  getResponseHeader: function(name) {
+    return this.transport.getResponseHeader(name);
+  },
+
+  getAllResponseHeaders: function() {
+    return this.transport.getAllResponseHeaders();
+  },
+
+  _getHeaderJSON: function() {
+    var json = this.getHeader('X-JSON');
+    if (!json) return null;
+    json = decodeURIComponent(escape(json));
+    try {
+      return json.evalJSON(this.request.options.sanitizeJSON ||
+        !this.request.isSameOrigin());
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  },
+
+  _getResponseJSON: function() {
+    var options = this.request.options;
+    if (!options.evalJSON || (options.evalJSON != 'force' &&
+      !(this.getHeader('Content-type') || '').include('application/json')) ||
+        this.responseText.blank())
+          return null;
+    try {
+      return this.responseText.evalJSON(options.sanitizeJSON ||
+        !this.request.isSameOrigin());
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  }
+});
+
+Ajax.Updater = Class.create(Ajax.Request, {
+  initialize: function($super, container, url, options) {
+    this.container = {
+      success: (container.success || container),
+      failure: (container.failure || (container.success ? null : container))
+    };
+
+    options = Object.clone(options);
+    var onComplete = options.onComplete;
+    options.onComplete = (function(response, json) {
+      this.updateContent(response.responseText);
+      if (Object.isFunction(onComplete)) onComplete(response, json);
+    }).bind(this);
+
+    $super(url, options);
+  },
+
+  updateContent: function(responseText) {
+    var receiver = this.container[this.success() ? 'success' : 'failure'],
+        options = this.options;
+
+    if (!options.evalScripts) responseText = responseText.stripScripts();
+
+    if (receiver = $(receiver)) {
+      if (options.insertion) {
+        if (Object.isString(options.insertion)) {
+          var insertion = { }; insertion[options.insertion] = responseText;
+          receiver.insert(insertion);
+        }
+        else options.insertion(receiver, responseText);
+      }
+      else receiver.update(responseText);
+    }
+  }
+});
+
+Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
+  initialize: function($super, container, url, options) {
+    $super(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(response) {
+    if (this.options.decay) {
+      this.decay = (response.responseText == this.lastText ?
+        this.decay * this.options.decay : 1);
+
+      this.lastText = response.responseText;
+    }
+    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
+  },
+
+  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 (Object.isString(element))
+    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(Element.extend(query.snapshotItem(i)));
+    return results;
+  };
+}
+
+/*--------------------------------------------------------------------------*/
+
+if (!window.Node) var Node = { };
+
+if (!Node.ELEMENT_NODE) {
+  Object.extend(Node, {
+    ELEMENT_NODE: 1,
+    ATTRIBUTE_NODE: 2,
+    TEXT_NODE: 3,
+    CDATA_SECTION_NODE: 4,
+    ENTITY_REFERENCE_NODE: 5,
+    ENTITY_NODE: 6,
+    PROCESSING_INSTRUCTION_NODE: 7,
+    COMMENT_NODE: 8,
+    DOCUMENT_NODE: 9,
+    DOCUMENT_TYPE_NODE: 10,
+    DOCUMENT_FRAGMENT_NODE: 11,
+    NOTATION_NODE: 12
+  });
+}
+
+
+(function(global) {
+
+  var SETATTRIBUTE_IGNORES_NAME = (function(){
+    var elForm = document.createElement("form");
+    var elInput = document.createElement("input");
+    var root = document.documentElement;
+    elInput.setAttribute("name", "test");
+    elForm.appendChild(elInput);
+    root.appendChild(elForm);
+    var isBuggy = elForm.elements
+      ? (typeof elForm.elements.test == "undefined")
+      : null;
+    root.removeChild(elForm);
+    elForm = elInput = null;
+    return isBuggy;
+  })();
+
+  var element = global.Element;
+  global.Element = function(tagName, attributes) {
+    attributes = attributes || { };
+    tagName = tagName.toLowerCase();
+    var cache = Element.cache;
+    if (SETATTRIBUTE_IGNORES_NAME && attributes.name) {
+      tagName = '<' + tagName + ' name="' + attributes.name + '">';
+      delete attributes.name;
+      return Element.writeAttribute(document.createElement(tagName), attributes);
+    }
+    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
+    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
+  };
+  Object.extend(global.Element, element || { });
+  if (element) global.Element.prototype = element.prototype;
+})(this);
+
+Element.cache = { };
+Element.idCounter = 1;
+
+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 = $(element);
+    element.style.display = 'none';
+    return element;
+  },
+
+  show: function(element) {
+    element = $(element);
+    element.style.display = '';
+    return element;
+  },
+
+  remove: function(element) {
+    element = $(element);
+    element.parentNode.removeChild(element);
+    return element;
+  },
+
+  update: (function(){
+
+    var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){
+      var el = document.createElement("select"),
+          isBuggy = true;
+      el.innerHTML = "<option value=\"test\">test</option>";
+      if (el.options && el.options[0]) {
+        isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION";
+      }
+      el = null;
+      return isBuggy;
+    })();
+
+    var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){
+      try {
+        var el = document.createElement("table");
+        if (el && el.tBodies) {
+          el.innerHTML = "<tbody><tr><td>test</td></tr></tbody>";
+          var isBuggy = typeof el.tBodies[0] == "undefined";
+          el = null;
+          return isBuggy;
+        }
+      } catch (e) {
+        return true;
+      }
+    })();
+
+    var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () {
+      var s = document.createElement("script"),
+          isBuggy = false;
+      try {
+        s.appendChild(document.createTextNode(""));
+        isBuggy = !s.firstChild ||
+          s.firstChild && s.firstChild.nodeType !== 3;
+      } catch (e) {
+        isBuggy = true;
+      }
+      s = null;
+      return isBuggy;
+    })();
+
+    function update(element, content) {
+      element = $(element);
+
+      if (content && content.toElement)
+        content = content.toElement();
+
+      if (Object.isElement(content))
+        return element.update().insert(content);
+
+      content = Object.toHTML(content);
+
+      var tagName = element.tagName.toUpperCase();
+
+      if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) {
+        element.text = content;
+        return element;
+      }
+
+      if (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY) {
+        if (tagName in Element._insertionTranslations.tags) {
+          while (element.firstChild) {
+            element.removeChild(element.firstChild);
+          }
+          Element._getContentFromAnonymousElement(tagName, content.stripScripts())
+            .each(function(node) {
+              element.appendChild(node)
+            });
+        }
+        else {
+          element.innerHTML = content.stripScripts();
+        }
+      }
+      else {
+        element.innerHTML = content.stripScripts();
+      }
+
+      content.evalScripts.bind(content).defer();
+      return element;
+    }
+
+    return update;
+  })(),
+
+  replace: function(element, content) {
+    element = $(element);
+    if (content && content.toElement) content = content.toElement();
+    else if (!Object.isElement(content)) {
+      content = Object.toHTML(content);
+      var range = element.ownerDocument.createRange();
+      range.selectNode(element);
+      content.evalScripts.bind(content).defer();
+      content = range.createContextualFragment(content.stripScripts());
+    }
+    element.parentNode.replaceChild(content, element);
+    return element;
+  },
+
+  insert: function(element, insertions) {
+    element = $(element);
+
+    if (Object.isString(insertions) || Object.isNumber(insertions) ||
+        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
+          insertions = {bottom:insertions};
+
+    var content, insert, tagName, childNodes;
+
+    for (var position in insertions) {
+      content  = insertions[position];
+      position = position.toLowerCase();
+      insert = Element._insertionTranslations[position];
+
+      if (content && content.toElement) content = content.toElement();
+      if (Object.isElement(content)) {
+        insert(element, content);
+        continue;
+      }
+
+      content = Object.toHTML(content);
+
+      tagName = ((position == 'before' || position == 'after')
+        ? element.parentNode : element).tagName.toUpperCase();
+
+      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+
+      if (position == 'top' || position == 'after') childNodes.reverse();
+      childNodes.each(insert.curry(element));
+
+      content.evalScripts.bind(content).defer();
+    }
+
+    return element;
+  },
+
+  wrap: function(element, wrapper, attributes) {
+    element = $(element);
+    if (Object.isElement(wrapper))
+      $(wrapper).writeAttribute(attributes || { });
+    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
+    else wrapper = new Element('div', wrapper);
+    if (element.parentNode)
+      element.parentNode.replaceChild(wrapper, element);
+    wrapper.appendChild(element);
+    return wrapper;
+  },
+
+  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(element, 'parentNode');
+  },
+
+  descendants: function(element) {
+    return Element.select(element, "*");
+  },
+
+  firstDescendant: function(element) {
+    element = $(element).firstChild;
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    return $(element);
+  },
+
+  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(element, 'previousSibling');
+  },
+
+  nextSiblings: function(element) {
+    return Element.recursivelyCollect(element, 'nextSibling');
+  },
+
+  siblings: function(element) {
+    element = $(element);
+    return Element.previousSiblings(element).reverse()
+      .concat(Element.nextSiblings(element));
+  },
+
+  match: function(element, selector) {
+    if (Object.isString(selector))
+      selector = new Selector(selector);
+    return selector.match($(element));
+  },
+
+  up: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(element.parentNode);
+    var ancestors = Element.ancestors(element);
+    return Object.isNumber(expression) ? ancestors[expression] :
+      Selector.findElement(ancestors, expression, index);
+  },
+
+  down: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return Element.firstDescendant(element);
+    return Object.isNumber(expression) ? Element.descendants(element)[expression] :
+      Element.select(element, expression)[index || 0];
+  },
+
+  previous: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
+    var previousSiblings = Element.previousSiblings(element);
+    return Object.isNumber(expression) ? previousSiblings[expression] :
+      Selector.findElement(previousSiblings, expression, index);
+  },
+
+  next: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
+    var nextSiblings = Element.nextSiblings(element);
+    return Object.isNumber(expression) ? nextSiblings[expression] :
+      Selector.findElement(nextSiblings, expression, index);
+  },
+
+
+  select: function(element) {
+    var args = Array.prototype.slice.call(arguments, 1);
+    return Selector.findChildElements(element, args);
+  },
+
+  adjacent: function(element) {
+    var args = Array.prototype.slice.call(arguments, 1);
+    return Selector.findChildElements(element.parentNode, args).without(element);
+  },
+
+  identify: function(element) {
+    element = $(element);
+    var id = Element.readAttribute(element, 'id');
+    if (id) return id;
+    do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id));
+    Element.writeAttribute(element, 'id', id);
+    return id;
+  },
+
+  readAttribute: function(element, name) {
+    element = $(element);
+    if (Prototype.Browser.IE) {
+      var t = Element._attributeTranslations.read;
+      if (t.values[name]) return t.values[name](element, name);
+      if (t.names[name]) name = t.names[name];
+      if (name.include(':')) {
+        return (!element.attributes || !element.attributes[name]) ? null :
+         element.attributes[name].value;
+      }
+    }
+    return element.getAttribute(name);
+  },
+
+  writeAttribute: function(element, name, value) {
+    element = $(element);
+    var attributes = { }, t = Element._attributeTranslations.write;
+
+    if (typeof name == 'object') attributes = name;
+    else attributes[name] = Object.isUndefined(value) ? true : value;
+
+    for (var attr in attributes) {
+      name = t.names[attr] || attr;
+      value = attributes[attr];
+      if (t.values[attr]) name = t.values[attr](element, value);
+      if (value === false || value === null)
+        element.removeAttribute(name);
+      else if (value === true)
+        element.setAttribute(name, name);
+      else element.setAttribute(name, value);
+    }
+    return element;
+  },
+
+  getHeight: function(element) {
+    return Element.getDimensions(element).height;
+  },
+
+  getWidth: function(element) {
+    return Element.getDimensions(element).width;
+  },
+
+  classNames: function(element) {
+    return new Element.ClassNames(element);
+  },
+
+  hasClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    var elementClassName = element.className;
+    return (elementClassName.length > 0 && (elementClassName == className ||
+      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
+  },
+
+  addClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    if (!Element.hasClassName(element, className))
+      element.className += (element.className ? ' ' : '') + className;
+    return element;
+  },
+
+  removeClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    element.className = element.className.replace(
+      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
+    return element;
+  },
+
+  toggleClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    return Element[Element.hasClassName(element, className) ?
+      'removeClassName' : 'addClassName'](element, className);
+  },
+
+  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.blank();
+  },
+
+  descendantOf: function(element, ancestor) {
+    element = $(element), ancestor = $(ancestor);
+
+    if (element.compareDocumentPosition)
+      return (element.compareDocumentPosition(ancestor) & 8) === 8;
+
+    if (ancestor.contains)
+      return ancestor.contains(element) && ancestor !== element;
+
+    while (element = element.parentNode)
+      if (element == ancestor) return true;
+
+    return false;
+  },
+
+  scrollTo: function(element) {
+    element = $(element);
+    var pos = Element.cumulativeOffset(element);
+    window.scrollTo(pos[0], pos[1]);
+    return element;
+  },
+
+  getStyle: function(element, style) {
+    element = $(element);
+    style = style == 'float' ? 'cssFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value || value == 'auto') {
+      var css = document.defaultView.getComputedStyle(element, null);
+      value = css ? css[style] : null;
+    }
+    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
+    return value == 'auto' ? null : value;
+  },
+
+  getOpacity: function(element) {
+    return $(element).getStyle('opacity');
+  },
+
+  setStyle: function(element, styles) {
+    element = $(element);
+    var elementStyle = element.style, match;
+    if (Object.isString(styles)) {
+      element.style.cssText += ';' + styles;
+      return styles.include('opacity') ?
+        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
+    }
+    for (var property in styles)
+      if (property == 'opacity') element.setOpacity(styles[property]);
+      else
+        elementStyle[(property == 'float' || property == 'cssFloat') ?
+          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
+            property] = styles[property];
+
+    return element;
+  },
+
+  setOpacity: function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+    return element;
+  },
+
+  getDimensions: function(element) {
+    element = $(element);
+    var display = Element.getStyle(element, 'display');
+    if (display != 'none' && display != null) // Safari bug
+      return {width: element.offsetWidth, height: element.offsetHeight};
+
+    var els = element.style;
+    var originalVisibility = els.visibility;
+    var originalPosition = els.position;
+    var originalDisplay = els.display;
+    els.visibility = 'hidden';
+    if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari
+      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';
+      if (Prototype.Browser.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.getStyle(element, 'overflow') || 'auto';
+    if (element._overflow !== '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;
+  },
+
+  cumulativeOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+    } while (element);
+    return Element._returnOffset(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.toUpperCase() == 'BODY') break;
+        var p = Element.getStyle(element, 'position');
+        if (p !== 'static') break;
+      }
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  absolutize: function(element) {
+    element = $(element);
+    if (Element.getStyle(element, 'position') == 'absolute') return element;
+
+    var offsets = Element.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';
+    return element;
+  },
+
+  relativize: function(element) {
+    element = $(element);
+    if (Element.getStyle(element, 'position') == 'relative') return element;
+
+    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;
+    return element;
+  },
+
+  cumulativeScrollOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.scrollTop  || 0;
+      valueL += element.scrollLeft || 0;
+      element = element.parentNode;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  getOffsetParent: 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);
+  },
+
+  viewportOffset: function(forElement) {
+    var valueT = 0, valueL = 0;
+
+    var element = forElement;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+
+      if (element.offsetParent == document.body &&
+        Element.getStyle(element, 'position') == 'absolute') break;
+
+    } while (element = element.offsetParent);
+
+    element = forElement;
+    do {
+      if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) {
+        valueT -= element.scrollTop  || 0;
+        valueL -= element.scrollLeft || 0;
+      }
+    } while (element = element.parentNode);
+
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  clonePosition: function(element, source) {
+    var options = Object.extend({
+      setLeft:    true,
+      setTop:     true,
+      setWidth:   true,
+      setHeight:  true,
+      offsetTop:  0,
+      offsetLeft: 0
+    }, arguments[2] || { });
+
+    source = $(source);
+    var p = Element.viewportOffset(source);
+
+    element = $(element);
+    var delta = [0, 0];
+    var parent = null;
+    if (Element.getStyle(element, 'position') == 'absolute') {
+      parent = Element.getOffsetParent(element);
+      delta = Element.viewportOffset(parent);
+    }
+
+    if (parent == document.body) {
+      delta[0] -= document.body.offsetLeft;
+      delta[1] -= document.body.offsetTop;
+    }
+
+    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
+    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
+    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
+    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
+    return element;
+  }
+};
+
+Object.extend(Element.Methods, {
+  getElementsBySelector: Element.Methods.select,
+
+  childElements: Element.Methods.immediateDescendants
+});
+
+Element._attributeTranslations = {
+  write: {
+    names: {
+      className: 'class',
+      htmlFor:   'for'
+    },
+    values: { }
+  }
+};
+
+if (Prototype.Browser.Opera) {
+  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
+    function(proceed, element, style) {
+      switch (style) {
+        case 'left': case 'top': case 'right': case 'bottom':
+          if (proceed(element, 'position') === 'static') return null;
+        case 'height': case 'width':
+          if (!Element.visible(element)) return null;
+
+          var dim = parseInt(proceed(element, style), 10);
+
+          if (dim !== element['offset' + style.capitalize()])
+            return dim + 'px';
+
+          var properties;
+          if (style === 'height') {
+            properties = ['border-top-width', 'padding-top',
+             'padding-bottom', 'border-bottom-width'];
+          }
+          else {
+            properties = ['border-left-width', 'padding-left',
+             'padding-right', 'border-right-width'];
+          }
+          return properties.inject(dim, function(memo, property) {
+            var val = proceed(element, property);
+            return val === null ? memo : memo - parseInt(val, 10);
+          }) + 'px';
+        default: return proceed(element, style);
+      }
+    }
+  );
+
+  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
+    function(proceed, element, attribute) {
+      if (attribute === 'title') return element.title;
+      return proceed(element, attribute);
+    }
+  );
+}
+
+else if (Prototype.Browser.IE) {
+  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
+    function(proceed, element) {
+      element = $(element);
+      try { element.offsetParent }
+      catch(e) { return $(document.body) }
+      var position = element.getStyle('position');
+      if (position !== 'static') return proceed(element);
+      element.setStyle({ position: 'relative' });
+      var value = proceed(element);
+      element.setStyle({ position: position });
+      return value;
+    }
+  );
+
+  $w('positionedOffset viewportOffset').each(function(method) {
+    Element.Methods[method] = Element.Methods[method].wrap(
+      function(proceed, element) {
+        element = $(element);
+        try { element.offsetParent }
+        catch(e) { return Element._returnOffset(0,0) }
+        var position = element.getStyle('position');
+        if (position !== 'static') return proceed(element);
+        var offsetParent = element.getOffsetParent();
+        if (offsetParent && offsetParent.getStyle('position') === 'fixed')
+          offsetParent.setStyle({ zoom: 1 });
+        element.setStyle({ position: 'relative' });
+        var value = proceed(element);
+        element.setStyle({ position: position });
+        return value;
+      }
+    );
+  });
+
+  Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(
+    function(proceed, element) {
+      try { element.offsetParent }
+      catch(e) { return Element._returnOffset(0,0) }
+      return proceed(element);
+    }
+  );
+
+  Element.Methods.getStyle = function(element, style) {
+    element = $(element);
+    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value && element.currentStyle) value = element.currentStyle[style];
+
+    if (style == 'opacity') {
+      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
+        if (value[1]) return parseFloat(value[1]) / 100;
+      return 1.0;
+    }
+
+    if (value == 'auto') {
+      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
+        return element['offset' + style.capitalize()] + 'px';
+      return null;
+    }
+    return value;
+  };
+
+  Element.Methods.setOpacity = function(element, value) {
+    function stripAlpha(filter){
+      return filter.replace(/alpha\([^\)]*\)/gi,'');
+    }
+    element = $(element);
+    var currentStyle = element.currentStyle;
+    if ((currentStyle && !currentStyle.hasLayout) ||
+      (!currentStyle && element.style.zoom == 'normal'))
+        element.style.zoom = 1;
+
+    var filter = element.getStyle('filter'), style = element.style;
+    if (value == 1 || value === '') {
+      (filter = stripAlpha(filter)) ?
+        style.filter = filter : style.removeAttribute('filter');
+      return element;
+    } else if (value < 0.00001) value = 0;
+    style.filter = stripAlpha(filter) +
+      'alpha(opacity=' + (value * 100) + ')';
+    return element;
+  };
+
+  Element._attributeTranslations = (function(){
+
+    var classProp = 'className';
+    var forProp = 'for';
+
+    var el = document.createElement('div');
+
+    el.setAttribute(classProp, 'x');
+
+    if (el.className !== 'x') {
+      el.setAttribute('class', 'x');
+      if (el.className === 'x') {
+        classProp = 'class';
+      }
+    }
+    el = null;
+
+    el = document.createElement('label');
+    el.setAttribute(forProp, 'x');
+    if (el.htmlFor !== 'x') {
+      el.setAttribute('htmlFor', 'x');
+      if (el.htmlFor === 'x') {
+        forProp = 'htmlFor';
+      }
+    }
+    el = null;
+
+    return {
+      read: {
+        names: {
+          'class':      classProp,
+          'className':  classProp,
+          'for':        forProp,
+          'htmlFor':    forProp
+        },
+        values: {
+          _getAttr: function(element, attribute) {
+            return element.getAttribute(attribute);
+          },
+          _getAttr2: function(element, attribute) {
+            return element.getAttribute(attribute, 2);
+          },
+          _getAttrNode: function(element, attribute) {
+            var node = element.getAttributeNode(attribute);
+            return node ? node.value : "";
+          },
+          _getEv: (function(){
+
+            var el = document.createElement('div');
+            el.onclick = Prototype.emptyFunction;
+            var value = el.getAttribute('onclick');
+            var f;
+
+            if (String(value).indexOf('{') > -1) {
+              f = function(element, attribute) {
+                attribute = element.getAttribute(attribute);
+                if (!attribute) return null;
+                attribute = attribute.toString();
+                attribute = attribute.split('{')[1];
+                attribute = attribute.split('}')[0];
+                return attribute.strip();
+              };
+            }
+            else if (value === '') {
+              f = function(element, attribute) {
+                attribute = element.getAttribute(attribute);
+                if (!attribute) return null;
+                return attribute.strip();
+              };
+            }
+            el = null;
+            return f;
+          })(),
+          _flag: function(element, attribute) {
+            return $(element).hasAttribute(attribute) ? attribute : null;
+          },
+          style: function(element) {
+            return element.style.cssText.toLowerCase();
+          },
+          title: function(element) {
+            return element.title;
+          }
+        }
+      }
+    }
+  })();
+
+  Element._attributeTranslations.write = {
+    names: Object.extend({
+      cellpadding: 'cellPadding',
+      cellspacing: 'cellSpacing'
+    }, Element._attributeTranslations.read.names),
+    values: {
+      checked: function(element, value) {
+        element.checked = !!value;
+      },
+
+      style: function(element, value) {
+        element.style.cssText = value ? value : '';
+      }
+    }
+  };
+
+  Element._attributeTranslations.has = {};
+
+  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
+      'encType maxLength readOnly longDesc frameBorder').each(function(attr) {
+    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
+    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
+  });
+
+  (function(v) {
+    Object.extend(v, {
+      href:        v._getAttr2,
+      src:         v._getAttr2,
+      type:        v._getAttr,
+      action:      v._getAttrNode,
+      disabled:    v._flag,
+      checked:     v._flag,
+      readonly:    v._flag,
+      multiple:    v._flag,
+      onload:      v._getEv,
+      onunload:    v._getEv,
+      onclick:     v._getEv,
+      ondblclick:  v._getEv,
+      onmousedown: v._getEv,
+      onmouseup:   v._getEv,
+      onmouseover: v._getEv,
+      onmousemove: v._getEv,
+      onmouseout:  v._getEv,
+      onfocus:     v._getEv,
+      onblur:      v._getEv,
+      onkeypress:  v._getEv,
+      onkeydown:   v._getEv,
+      onkeyup:     v._getEv,
+      onsubmit:    v._getEv,
+      onreset:     v._getEv,
+      onselect:    v._getEv,
+      onchange:    v._getEv
+    });
+  })(Element._attributeTranslations.read.values);
+
+  if (Prototype.BrowserFeatures.ElementExtensions) {
+    (function() {
+      function _descendants(element) {
+        var nodes = element.getElementsByTagName('*'), results = [];
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.tagName !== "!") // Filter out comment nodes.
+            results.push(node);
+        return results;
+      }
+
+      Element.Methods.down = function(element, expression, index) {
+        element = $(element);
+        if (arguments.length == 1) return element.firstDescendant();
+        return Object.isNumber(expression) ? _descendants(element)[expression] :
+          Element.select(element, expression)[index || 0];
+      }
+    })();
+  }
+
+}
+
+else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1) ? 0.999999 :
+      (value === '') ? '' : (value < 0.00001) ? 0 : value;
+    return element;
+  };
+}
+
+else if (Prototype.Browser.WebKit) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+
+    if (value == 1)
+      if(element.tagName.toUpperCase() == 'IMG' && element.width) {
+        element.width++; element.width--;
+      } else try {
+        var n = document.createTextNode(' ');
+        element.appendChild(n);
+        element.removeChild(n);
+      } catch (e) { }
+
+    return element;
+  };
+
+  Element.Methods.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 Element._returnOffset(valueL, valueT);
+  };
+}
+
+if ('outerHTML' in document.documentElement) {
+  Element.Methods.replace = function(element, content) {
+    element = $(element);
+
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) {
+      element.parentNode.replaceChild(content, element);
+      return element;
+    }
+
+    content = Object.toHTML(content);
+    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
+
+    if (Element._insertionTranslations.tags[tagName]) {
+      var nextSibling = element.next();
+      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+      parent.removeChild(element);
+      if (nextSibling)
+        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
+      else
+        fragments.each(function(node) { parent.appendChild(node) });
+    }
+    else element.outerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+Element._returnOffset = function(l, t) {
+  var result = [l, t];
+  result.left = l;
+  result.top = t;
+  return result;
+};
+
+Element._getContentFromAnonymousElement = function(tagName, html) {
+  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
+  if (t) {
+    div.innerHTML = t[0] + html + t[1];
+    t[2].times(function() { div = div.firstChild });
+  } else div.innerHTML = html;
+  return $A(div.childNodes);
+};
+
+Element._insertionTranslations = {
+  before: function(element, node) {
+    element.parentNode.insertBefore(node, element);
+  },
+  top: function(element, node) {
+    element.insertBefore(node, element.firstChild);
+  },
+  bottom: function(element, node) {
+    element.appendChild(node);
+  },
+  after: function(element, node) {
+    element.parentNode.insertBefore(node, element.nextSibling);
+  },
+  tags: {
+    TABLE:  ['<table>',                '</table>',                   1],
+    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
+    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
+    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
+    SELECT: ['<select>',               '</select>',                  1]
+  }
+};
+
+(function() {
+  var tags = Element._insertionTranslations.tags;
+  Object.extend(tags, {
+    THEAD: tags.TBODY,
+    TFOOT: tags.TBODY,
+    TH:    tags.TD
+  });
+})();
+
+Element.Methods.Simulated = {
+  hasAttribute: function(element, attribute) {
+    attribute = Element._attributeTranslations.has[attribute] || attribute;
+    var node = $(element).getAttributeNode(attribute);
+    return !!(node && node.specified);
+  }
+};
+
+Element.Methods.ByTag = { };
+
+Object.extend(Element, Element.Methods);
+
+(function(div) {
+
+  if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) {
+    window.HTMLElement = { };
+    window.HTMLElement.prototype = div['__proto__'];
+    Prototype.BrowserFeatures.ElementExtensions = true;
+  }
+
+  div = null;
+
+})(document.createElement('div'))
+
+Element.extend = (function() {
+
+  function checkDeficiency(tagName) {
+    if (typeof window.Element != 'undefined') {
+      var proto = window.Element.prototype;
+      if (proto) {
+        var id = '_' + (Math.random()+'').slice(2);
+        var el = document.createElement(tagName);
+        proto[id] = 'x';
+        var isBuggy = (el[id] !== 'x');
+        delete proto[id];
+        el = null;
+        return isBuggy;
+      }
+    }
+    return false;
+  }
+
+  function extendElementWith(element, methods) {
+    for (var property in methods) {
+      var value = methods[property];
+      if (Object.isFunction(value) && !(property in element))
+        element[property] = value.methodize();
+    }
+  }
+
+  var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object');
+
+  if (Prototype.BrowserFeatures.SpecificElementExtensions) {
+    if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) {
+      return function(element) {
+        if (element && typeof element._extendedByPrototype == 'undefined') {
+          var t = element.tagName;
+          if (t && (/^(?:object|applet|embed)$/i.test(t))) {
+            extendElementWith(element, Element.Methods);
+            extendElementWith(element, Element.Methods.Simulated);
+            extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]);
+          }
+        }
+        return element;
+      }
+    }
+    return Prototype.K;
+  }
+
+  var Methods = { }, ByTag = Element.Methods.ByTag;
+
+  var extend = Object.extend(function(element) {
+    if (!element || typeof element._extendedByPrototype != 'undefined' ||
+        element.nodeType != 1 || element == window) return element;
+
+    var methods = Object.clone(Methods),
+        tagName = element.tagName.toUpperCase();
+
+    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
+
+    extendElementWith(element, methods);
+
+    element._extendedByPrototype = Prototype.emptyFunction;
+    return element;
+
+  }, {
+    refresh: function() {
+      if (!Prototype.BrowserFeatures.ElementExtensions) {
+        Object.extend(Methods, Element.Methods);
+        Object.extend(Methods, Element.Methods.Simulated);
+      }
+    }
+  });
+
+  extend.refresh();
+  return extend;
+})();
+
+Element.hasAttribute = function(element, attribute) {
+  if (element.hasAttribute) return element.hasAttribute(attribute);
+  return Element.Methods.Simulated.hasAttribute(element, attribute);
+};
+
+Element.addMethods = function(methods) {
+  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
+
+  if (!methods) {
+    Object.extend(Form, Form.Methods);
+    Object.extend(Form.Element, Form.Element.Methods);
+    Object.extend(Element.Methods.ByTag, {
+      "FORM":     Object.clone(Form.Methods),
+      "INPUT":    Object.clone(Form.Element.Methods),
+      "SELECT":   Object.clone(Form.Element.Methods),
+      "TEXTAREA": Object.clone(Form.Element.Methods)
+    });
+  }
+
+  if (arguments.length == 2) {
+    var tagName = methods;
+    methods = arguments[1];
+  }
+
+  if (!tagName) Object.extend(Element.Methods, methods || { });
+  else {
+    if (Object.isArray(tagName)) tagName.each(extend);
+    else extend(tagName);
+  }
+
+  function extend(tagName) {
+    tagName = tagName.toUpperCase();
+    if (!Element.Methods.ByTag[tagName])
+      Element.Methods.ByTag[tagName] = { };
+    Object.extend(Element.Methods.ByTag[tagName], methods);
+  }
+
+  function copy(methods, destination, onlyIfAbsent) {
+    onlyIfAbsent = onlyIfAbsent || false;
+    for (var property in methods) {
+      var value = methods[property];
+      if (!Object.isFunction(value)) continue;
+      if (!onlyIfAbsent || !(property in destination))
+        destination[property] = value.methodize();
+    }
+  }
+
+  function findDOMClass(tagName) {
+    var klass;
+    var trans = {
+      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
+      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
+      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
+      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
+      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
+      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
+      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
+      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
+      "FrameSet", "IFRAME": "IFrame"
+    };
+    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName.capitalize() + 'Element';
+    if (window[klass]) return window[klass];
+
+    var element = document.createElement(tagName);
+    var proto = element['__proto__'] || element.constructor.prototype;
+    element = null;
+    return proto;
+  }
+
+  var elementPrototype = window.HTMLElement ? HTMLElement.prototype :
+   Element.prototype;
+
+  if (F.ElementExtensions) {
+    copy(Element.Methods, elementPrototype);
+    copy(Element.Methods.Simulated, elementPrototype, true);
+  }
+
+  if (F.SpecificElementExtensions) {
+    for (var tag in Element.Methods.ByTag) {
+      var klass = findDOMClass(tag);
+      if (Object.isUndefined(klass)) continue;
+      copy(T[tag], klass.prototype);
+    }
+  }
+
+  Object.extend(Element, Element.Methods);
+  delete Element.ByTag;
+
+  if (Element.extend.refresh) Element.extend.refresh();
+  Element.cache = { };
+};
+
+
+document.viewport = {
+
+  getDimensions: function() {
+    return { width: this.getWidth(), height: this.getHeight() };
+  },
+
+  getScrollOffsets: function() {
+    return Element._returnOffset(
+      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
+      window.pageYOffset || document.documentElement.scrollTop  || document.body.scrollTop);
+  }
+};
+
+(function(viewport) {
+  var B = Prototype.Browser, doc = document, element, property = {};
+
+  function getRootElement() {
+    if (B.WebKit && !doc.evaluate)
+      return document;
+
+    if (B.Opera && window.parseFloat(window.opera.version()) < 9.5)
+      return document.body;
+
+    return document.documentElement;
+  }
+
+  function define(D) {
+    if (!element) element = getRootElement();
+
+    property[D] = 'client' + D;
+
+    viewport['get' + D] = function() { return element[property[D]] };
+    return viewport['get' + D]();
+  }
+
+  viewport.getWidth  = define.curry('Width');
+
+  viewport.getHeight = define.curry('Height');
+})(document.viewport);
+
+
+Element.Storage = {
+  UID: 1
+};
+
+Element.addMethods({
+  getStorage: function(element) {
+    if (!(element = $(element))) return;
+
+    var uid;
+    if (element === window) {
+      uid = 0;
+    } else {
+      if (typeof element._prototypeUID === "undefined")
+        element._prototypeUID = [Element.Storage.UID++];
+      uid = element._prototypeUID[0];
+    }
+
+    if (!Element.Storage[uid])
+      Element.Storage[uid] = $H();
+
+    return Element.Storage[uid];
+  },
+
+  store: function(element, key, value) {
+    if (!(element = $(element))) return;
+
+    if (arguments.length === 2) {
+      Element.getStorage(element).update(key);
+    } else {
+      Element.getStorage(element).set(key, value);
+    }
+
+    return element;
+  },
+
+  retrieve: function(element, key, defaultValue) {
+    if (!(element = $(element))) return;
+    var hash = Element.getStorage(element), value = hash.get(key);
+
+    if (Object.isUndefined(value)) {
+      hash.set(key, defaultValue);
+      value = defaultValue;
+    }
+
+    return value;
+  },
+
+  clone: function(element, deep) {
+    if (!(element = $(element))) return;
+    var clone = element.cloneNode(deep);
+    clone._prototypeUID = void 0;
+    if (deep) {
+      var descendants = Element.select(clone, '*'),
+          i = descendants.length;
+      while (i--) {
+        descendants[i]._prototypeUID = void 0;
+      }
+    }
+    return Element.extend(clone);
+  }
+});
+/* Portions of the Selector class are derived from Jack Slocum's DomQuery,
+ * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
+ * license.  Please see http://www.yui-ext.com/ for more information. */
+
+var Selector = Class.create({
+  initialize: function(expression) {
+    this.expression = expression.strip();
+
+    if (this.shouldUseSelectorsAPI()) {
+      this.mode = 'selectorsAPI';
+    } else if (this.shouldUseXPath()) {
+      this.mode = 'xpath';
+      this.compileXPathMatcher();
+    } else {
+      this.mode = "normal";
+      this.compileMatcher();
+    }
+
+  },
+
+  shouldUseXPath: (function() {
+
+    var IS_DESCENDANT_SELECTOR_BUGGY = (function(){
+      var isBuggy = false;
+      if (document.evaluate && window.XPathResult) {
+        var el = document.createElement('div');
+        el.innerHTML = '<ul><li></li></ul><div><ul><li></li></ul></div>';
+
+        var xpath = ".//*[local-name()='ul' or local-name()='UL']" +
+          "//*[local-name()='li' or local-name()='LI']";
+
+        var result = document.evaluate(xpath, el, null,
+          XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+
+        isBuggy = (result.snapshotLength !== 2);
+        el = null;
+      }
+      return isBuggy;
+    })();
+
+    return function() {
+      if (!Prototype.BrowserFeatures.XPath) return false;
+
+      var e = this.expression;
+
+      if (Prototype.Browser.WebKit &&
+       (e.include("-of-type") || e.include(":empty")))
+        return false;
+
+      if ((/(\[[\w-]*?:|:checked)/).test(e))
+        return false;
+
+      if (IS_DESCENDANT_SELECTOR_BUGGY) return false;
+
+      return true;
+    }
+
+  })(),
+
+  shouldUseSelectorsAPI: function() {
+    if (!Prototype.BrowserFeatures.SelectorsAPI) return false;
+
+    if (Selector.CASE_INSENSITIVE_CLASS_NAMES) return false;
+
+    if (!Selector._div) Selector._div = new Element('div');
+
+    try {
+      Selector._div.querySelector(this.expression);
+    } catch(e) {
+      return false;
+    }
+
+    return true;
+  },
+
+  compileMatcher: function() {
+    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
+        c = Selector.criteria, le, p, m, len = ps.length, name;
+
+    if (Selector._cache[e]) {
+      this.matcher = Selector._cache[e];
+      return;
+    }
+
+    this.matcher = ["this.matcher = function(root) {",
+                    "var r = root, h = Selector.handlers, c = false, n;"];
+
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i = 0; i<len; i++) {
+        p = ps[i].re;
+        name = ps[i].name;
+        if (m = e.match(p)) {
+          this.matcher.push(Object.isFunction(c[name]) ? c[name](m) :
+            new Template(c[name]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.matcher.push("return h.unique(n);\n}");
+    eval(this.matcher.join('\n'));
+    Selector._cache[this.expression] = this.matcher;
+  },
+
+  compileXPathMatcher: function() {
+    var e = this.expression, ps = Selector.patterns,
+        x = Selector.xpath, le, m, len = ps.length, name;
+
+    if (Selector._cache[e]) {
+      this.xpath = Selector._cache[e]; return;
+    }
+
+    this.matcher = ['.//*'];
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i = 0; i<len; i++) {
+        name = ps[i].name;
+        if (m = e.match(ps[i].re)) {
+          this.matcher.push(Object.isFunction(x[name]) ? x[name](m) :
+            new Template(x[name]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.xpath = this.matcher.join('');
+    Selector._cache[this.expression] = this.xpath;
+  },
+
+  findElements: function(root) {
+    root = root || document;
+    var e = this.expression, results;
+
+    switch (this.mode) {
+      case 'selectorsAPI':
+        if (root !== document) {
+          var oldId = root.id, id = $(root).identify();
+          id = id.replace(/([\.:])/g, "\\$1");
+          e = "#" + id + " " + e;
+        }
+
+        results = $A(root.querySelectorAll(e)).map(Element.extend);
+        root.id = oldId;
+
+        return results;
+      case 'xpath':
+        return document._getElementsByXPath(this.xpath, root);
+      default:
+       return this.matcher(root);
+    }
+  },
+
+  match: function(element) {
+    this.tokens = [];
+
+    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
+    var le, p, m, len = ps.length, name;
+
+    while (e && le !== e && (/\S/).test(e)) {
+      le = e;
+      for (var i = 0; i<len; i++) {
+        p = ps[i].re;
+        name = ps[i].name;
+        if (m = e.match(p)) {
+          if (as[name]) {
+            this.tokens.push([name, Object.clone(m)]);
+            e = e.replace(m[0], '');
+          } else {
+            return this.findElements(document).include(element);
+          }
+        }
+      }
+    }
+
+    var match = true, name, matches;
+    for (var i = 0, token; token = this.tokens[i]; i++) {
+      name = token[0], matches = token[1];
+      if (!Selector.assertions[name](element, matches)) {
+        match = false; break;
+      }
+    }
+
+    return match;
+  },
+
+  toString: function() {
+    return this.expression;
+  },
+
+  inspect: function() {
+    return "#<Selector:" + this.expression.inspect() + ">";
+  }
+});
+
+if (Prototype.BrowserFeatures.SelectorsAPI &&
+ document.compatMode === 'BackCompat') {
+  Selector.CASE_INSENSITIVE_CLASS_NAMES = (function(){
+    var div = document.createElement('div'),
+     span = document.createElement('span');
+
+    div.id = "prototype_test_id";
+    span.className = 'Test';
+    div.appendChild(span);
+    var isIgnored = (div.querySelector('#prototype_test_id .test') !== null);
+    div = span = null;
+    return isIgnored;
+  })();
+}
+
+Object.extend(Selector, {
+  _cache: { },
+
+  xpath: {
+    descendant:   "//*",
+    child:        "/*",
+    adjacent:     "/following-sibling::*[1]",
+    laterSibling: '/following-sibling::*',
+    tagName:      function(m) {
+      if (m[1] == '*') return '';
+      return "[local-name()='" + m[1].toLowerCase() +
+             "' or local-name()='" + m[1].toUpperCase() + "']";
+    },
+    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
+    id:           "[@id='#{1}']",
+    attrPresence: function(m) {
+      m[1] = m[1].toLowerCase();
+      return new Template("[@#{1}]").evaluate(m);
+    },
+    attr: function(m) {
+      m[1] = m[1].toLowerCase();
+      m[3] = m[5] || m[6];
+      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
+    },
+    pseudo: function(m) {
+      var h = Selector.xpath.pseudos[m[1]];
+      if (!h) return '';
+      if (Object.isFunction(h)) return h(m);
+      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
+    },
+    operators: {
+      '=':  "[@#{1}='#{3}']",
+      '!=': "[@#{1}!='#{3}']",
+      '^=': "[starts-with(@#{1}, '#{3}')]",
+      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
+      '*=': "[contains(@#{1}, '#{3}')]",
+      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
+      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
+    },
+    pseudos: {
+      'first-child': '[not(preceding-sibling::*)]',
+      'last-child':  '[not(following-sibling::*)]',
+      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
+      'empty':       "[count(*) = 0 and (count(text()) = 0)]",
+      'checked':     "[@checked]",
+      'disabled':    "[(@disabled) and (@type!='hidden')]",
+      'enabled':     "[not(@disabled) and (@type!='hidden')]",
+      'not': function(m) {
+        var e = m[6], p = Selector.patterns,
+            x = Selector.xpath, le, v, len = p.length, name;
+
+        var exclusion = [];
+        while (e && le != e && (/\S/).test(e)) {
+          le = e;
+          for (var i = 0; i<len; i++) {
+            name = p[i].name
+            if (m = e.match(p[i].re)) {
+              v = Object.isFunction(x[name]) ? x[name](m) : new Template(x[name]).evaluate(m);
+              exclusion.push("(" + v.substring(1, v.length - 1) + ")");
+              e = e.replace(m[0], '');
+              break;
+            }
+          }
+        }
+        return "[not(" + exclusion.join(" and ") + ")]";
+      },
+      'nth-child':      function(m) {
+        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
+      },
+      'nth-last-child': function(m) {
+        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
+      },
+      'nth-of-type':    function(m) {
+        return Selector.xpath.pseudos.nth("position() ", m);
+      },
+      'nth-last-of-type': function(m) {
+        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
+      },
+      'first-of-type':  function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
+      },
+      'last-of-type':   function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
+      },
+      'only-of-type':   function(m) {
+        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
+      },
+      nth: function(fragment, m) {
+        var mm, formula = m[6], predicate;
+        if (formula == 'even') formula = '2n+0';
+        if (formula == 'odd')  formula = '2n+1';
+        if (mm = formula.match(/^(\d+)$/)) // digit only
+          return '[' + fragment + "= " + mm[1] + ']';
+        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+          if (mm[1] == "-") mm[1] = -1;
+          var a = mm[1] ? Number(mm[1]) : 1;
+          var b = mm[2] ? Number(mm[2]) : 0;
+          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
+          "((#{fragment} - #{b}) div #{a} >= 0)]";
+          return new Template(predicate).evaluate({
+            fragment: fragment, a: a, b: b });
+        }
+      }
+    }
+  },
+
+  criteria: {
+    tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;',
+    className:    'n = h.className(n, r, "#{1}", c);    c = false;',
+    id:           'n = h.id(n, r, "#{1}", c);           c = false;',
+    attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
+    attr: function(m) {
+      m[3] = (m[5] || m[6]);
+      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
+    },
+    pseudo: function(m) {
+      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
+      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
+    },
+    descendant:   'c = "descendant";',
+    child:        'c = "child";',
+    adjacent:     'c = "adjacent";',
+    laterSibling: 'c = "laterSibling";'
+  },
+
+  patterns: [
+    { name: 'laterSibling', re: /^\s*~\s*/ },
+    { name: 'child',        re: /^\s*>\s*/ },
+    { name: 'adjacent',     re: /^\s*\+\s*/ },
+    { name: 'descendant',   re: /^\s/ },
+
+    { name: 'tagName',      re: /^\s*(\*|[\w\-]+)(\b|$)?/ },
+    { name: 'id',           re: /^#([\w\-\*]+)(\b|$)/ },
+    { name: 'className',    re: /^\.([\w\-\*]+)(\b|$)/ },
+    { name: 'pseudo',       re: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/ },
+    { name: 'attrPresence', re: /^\[((?:[\w-]+:)?[\w-]+)\]/ },
+    { name: 'attr',         re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ }
+  ],
+
+  assertions: {
+    tagName: function(element, matches) {
+      return matches[1].toUpperCase() == element.tagName.toUpperCase();
+    },
+
+    className: function(element, matches) {
+      return Element.hasClassName(element, matches[1]);
+    },
+
+    id: function(element, matches) {
+      return element.id === matches[1];
+    },
+
+    attrPresence: function(element, matches) {
+      return Element.hasAttribute(element, matches[1]);
+    },
+
+    attr: function(element, matches) {
+      var nodeValue = Element.readAttribute(element, matches[1]);
+      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
+    }
+  },
+
+  handlers: {
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        a.push(node);
+      return a;
+    },
+
+    mark: function(nodes) {
+      var _true = Prototype.emptyFunction;
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._countedByPrototype = _true;
+      return nodes;
+    },
+
+    unmark: (function(){
+
+      var PROPERTIES_ATTRIBUTES_MAP = (function(){
+        var el = document.createElement('div'),
+            isBuggy = false,
+            propName = '_countedByPrototype',
+            value = 'x'
+        el[propName] = value;
+        isBuggy = (el.getAttribute(propName) === value);
+        el = null;
+        return isBuggy;
+      })();
+
+      return PROPERTIES_ATTRIBUTES_MAP ?
+        function(nodes) {
+          for (var i = 0, node; node = nodes[i]; i++)
+            node.removeAttribute('_countedByPrototype');
+          return nodes;
+        } :
+        function(nodes) {
+          for (var i = 0, node; node = nodes[i]; i++)
+            node._countedByPrototype = void 0;
+          return nodes;
+        }
+    })(),
+
+    index: function(parentNode, reverse, ofType) {
+      parentNode._countedByPrototype = Prototype.emptyFunction;
+      if (reverse) {
+        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
+          var node = nodes[i];
+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
+        }
+      } else {
+        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
+      }
+    },
+
+    unique: function(nodes) {
+      if (nodes.length == 0) return nodes;
+      var results = [], n;
+      for (var i = 0, l = nodes.length; i < l; i++)
+        if (typeof (n = nodes[i])._countedByPrototype == 'undefined') {
+          n._countedByPrototype = Prototype.emptyFunction;
+          results.push(Element.extend(n));
+        }
+      return Selector.handlers.unmark(results);
+    },
+
+    descendant: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, node.getElementsByTagName('*'));
+      return results;
+    },
+
+    child: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        for (var j = 0, child; child = node.childNodes[j]; j++)
+          if (child.nodeType == 1 && child.tagName != '!') results.push(child);
+      }
+      return results;
+    },
+
+    adjacent: function(nodes) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        var next = this.nextElementSibling(node);
+        if (next) results.push(next);
+      }
+      return results;
+    },
+
+    laterSibling: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, Element.nextSiblings(node));
+      return results;
+    },
+
+    nextElementSibling: function(node) {
+      while (node = node.nextSibling)
+        if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    previousElementSibling: function(node) {
+      while (node = node.previousSibling)
+        if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    tagName: function(nodes, root, tagName, combinator) {
+      var uTagName = tagName.toUpperCase();
+      var results = [], h = Selector.handlers;
+      if (nodes) {
+        if (combinator) {
+          if (combinator == "descendant") {
+            for (var i = 0, node; node = nodes[i]; i++)
+              h.concat(results, node.getElementsByTagName(tagName));
+            return results;
+          } else nodes = this[combinator](nodes);
+          if (tagName == "*") return nodes;
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.tagName.toUpperCase() === uTagName) results.push(node);
+        return results;
+      } else return root.getElementsByTagName(tagName);
+    },
+
+    id: function(nodes, root, id, combinator) {
+      var targetNode = $(id), h = Selector.handlers;
+
+      if (root == document) {
+        if (!targetNode) return [];
+        if (!nodes) return [targetNode];
+      } else {
+        if (!root.sourceIndex || root.sourceIndex < 1) {
+          var nodes = root.getElementsByTagName('*');
+          for (var j = 0, node; node = nodes[j]; j++) {
+            if (node.id === id) return [node];
+          }
+        }
+      }
+
+      if (nodes) {
+        if (combinator) {
+          if (combinator == 'child') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (targetNode.parentNode == node) return [targetNode];
+          } else if (combinator == 'descendant') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Element.descendantOf(targetNode, node)) return [targetNode];
+          } else if (combinator == 'adjacent') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Selector.handlers.previousElementSibling(targetNode) == node)
+                return [targetNode];
+          } else nodes = h[combinator](nodes);
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node == targetNode) return [targetNode];
+        return [];
+      }
+      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
+    },
+
+    className: function(nodes, root, className, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      return Selector.handlers.byClassName(nodes, root, className);
+    },
+
+    byClassName: function(nodes, root, className) {
+      if (!nodes) nodes = Selector.handlers.descendant([root]);
+      var needle = ' ' + className + ' ';
+      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
+        nodeClassName = node.className;
+        if (nodeClassName.length == 0) continue;
+        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
+          results.push(node);
+      }
+      return results;
+    },
+
+    attrPresence: function(nodes, root, attr, combinator) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      var results = [];
+      for (var i = 0, node; node = nodes[i]; i++)
+        if (Element.hasAttribute(node, attr)) results.push(node);
+      return results;
+    },
+
+    attr: function(nodes, root, attr, value, operator, combinator) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      var handler = Selector.operators[operator], results = [];
+      for (var i = 0, node; node = nodes[i]; i++) {
+        var nodeValue = Element.readAttribute(node, attr);
+        if (nodeValue === null) continue;
+        if (handler(nodeValue, value)) results.push(node);
+      }
+      return results;
+    },
+
+    pseudo: function(nodes, name, value, root, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      return Selector.pseudos[name](nodes, value, root);
+    }
+  },
+
+  pseudos: {
+    'first-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.previousElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'last-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.nextElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'only-child': function(nodes, value, root) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
+          results.push(node);
+      return results;
+    },
+    'nth-child':        function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root);
+    },
+    'nth-last-child':   function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true);
+    },
+    'nth-of-type':      function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, false, true);
+    },
+    'nth-last-of-type': function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true, true);
+    },
+    'first-of-type':    function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, false, true);
+    },
+    'last-of-type':     function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, true, true);
+    },
+    'only-of-type':     function(nodes, formula, root) {
+      var p = Selector.pseudos;
+      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
+    },
+
+    getIndices: function(a, b, total) {
+      if (a == 0) return b > 0 ? [b] : [];
+      return $R(1, total).inject([], function(memo, i) {
+        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
+        return memo;
+      });
+    },
+
+    nth: function(nodes, formula, root, reverse, ofType) {
+      if (nodes.length == 0) return [];
+      if (formula == 'even') formula = '2n+0';
+      if (formula == 'odd')  formula = '2n+1';
+      var h = Selector.handlers, results = [], indexed = [], m;
+      h.mark(nodes);
+      for (var i = 0, node; node = nodes[i]; i++) {
+        if (!node.parentNode._countedByPrototype) {
+          h.index(node.parentNode, reverse, ofType);
+          indexed.push(node.parentNode);
+        }
+      }
+      if (formula.match(/^\d+$/)) { // just a number
+        formula = Number(formula);
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.nodeIndex == formula) results.push(node);
+      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+        if (m[1] == "-") m[1] = -1;
+        var a = m[1] ? Number(m[1]) : 1;
+        var b = m[2] ? Number(m[2]) : 0;
+        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
+        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
+          for (var j = 0; j < l; j++)
+            if (node.nodeIndex == indices[j]) results.push(node);
+        }
+      }
+      h.unmark(nodes);
+      h.unmark(indexed);
+      return results;
+    },
+
+    'empty': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (node.tagName == '!' || node.firstChild) continue;
+        results.push(node);
+      }
+      return results;
+    },
+
+    'not': function(nodes, selector, root) {
+      var h = Selector.handlers, selectorType, m;
+      var exclusions = new Selector(selector).findElements(root);
+      h.mark(exclusions);
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node._countedByPrototype) results.push(node);
+      h.unmark(exclusions);
+      return results;
+    },
+
+    'enabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node.disabled && (!node.type || node.type !== 'hidden'))
+          results.push(node);
+      return results;
+    },
+
+    'disabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.disabled) results.push(node);
+      return results;
+    },
+
+    'checked': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.checked) results.push(node);
+      return results;
+    }
+  },
+
+  operators: {
+    '=':  function(nv, v) { return nv == v; },
+    '!=': function(nv, v) { return nv != v; },
+    '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); },
+    '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },
+    '*=': function(nv, v) { return nv == v || nv && nv.include(v); },
+    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
+    '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() +
+     '-').include('-' + (v || "").toUpperCase() + '-'); }
+  },
+
+  split: function(expression) {
+    var expressions = [];
+    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
+      expressions.push(m[1].strip());
+    });
+    return expressions;
+  },
+
+  matchElements: function(elements, expression) {
+    var matches = $$(expression), h = Selector.handlers;
+    h.mark(matches);
+    for (var i = 0, results = [], element; element = elements[i]; i++)
+      if (element._countedByPrototype) results.push(element);
+    h.unmark(matches);
+    return results;
+  },
+
+  findElement: function(elements, expression, index) {
+    if (Object.isNumber(expression)) {
+      index = expression; expression = false;
+    }
+    return Selector.matchElements(elements, expression || '*')[index || 0];
+  },
+
+  findChildElements: function(element, expressions) {
+    expressions = Selector.split(expressions.join(','));
+    var results = [], h = Selector.handlers;
+    for (var i = 0, l = expressions.length, selector; i < l; i++) {
+      selector = new Selector(expressions[i].strip());
+      h.concat(results, selector.findElements(element));
+    }
+    return (l > 1) ? h.unique(results) : results;
+  }
+});
+
+if (Prototype.Browser.IE) {
+  Object.extend(Selector.handlers, {
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        if (node.tagName !== "!") a.push(node);
+      return a;
+    }
+  });
+}
+
+function $$() {
+  return Selector.findChildElements(document, $A(arguments));
+}
+
+var Form = {
+  reset: function(form) {
+    form = $(form);
+    form.reset();
+    return form;
+  },
+
+  serializeElements: function(elements, options) {
+    if (typeof options != 'object') options = { hash: !!options };
+    else if (Object.isUndefined(options.hash)) options.hash = true;
+    var key, value, submitted = false, submit = options.submit;
+
+    var data = elements.inject({ }, function(result, element) {
+      if (!element.disabled && element.name) {
+        key = element.name; value = $(element).getValue();
+        if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&
+            submit !== false && (!submit || key == submit) && (submitted = true)))) {
+          if (key in result) {
+            if (!Object.isArray(result[key])) result[key] = [result[key]];
+            result[key].push(value);
+          }
+          else result[key] = value;
+        }
+      }
+      return result;
+    });
+
+    return options.hash ? data : Object.toQueryString(data);
+  }
+};
+
+Form.Methods = {
+  serialize: function(form, options) {
+    return Form.serializeElements(Form.getElements(form), options);
+  },
+
+  getElements: function(form) {
+    var elements = $(form).getElementsByTagName('*'),
+        element,
+        arr = [ ],
+        serializers = Form.Element.Serializers;
+    for (var i = 0; element = elements[i]; i++) {
+      arr.push(element);
+    }
+    return arr.inject([], function(elements, child) {
+      if (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(form).invoke('disable');
+    return form;
+  },
+
+  enable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('enable');
+    return form;
+  },
+
+  findFirstElement: function(form) {
+    var elements = $(form).getElements().findAll(function(element) {
+      return 'hidden' != element.type && !element.disabled;
+    });
+    var firstByIndex = elements.findAll(function(element) {
+      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
+    }).sortBy(function(element) { return element.tabIndex }).first();
+
+    return firstByIndex ? firstByIndex : elements.find(function(element) {
+      return /^(?:input|select|textarea)$/i.test(element.tagName);
+    });
+  },
+
+  focusFirstElement: function(form) {
+    form = $(form);
+    form.findFirstElement().activate();
+    return form;
+  },
+
+  request: function(form, options) {
+    form = $(form), options = Object.clone(options || { });
+
+    var params = options.parameters, action = form.readAttribute('action') || '';
+    if (action.blank()) action = window.location.href;
+    options.parameters = form.serialize(true);
+
+    if (params) {
+      if (Object.isString(params)) params = params.toQueryParams();
+      Object.extend(options.parameters, params);
+    }
+
+    if (form.hasAttribute('method') && !options.method)
+      options.method = form.method;
+
+    return new Ajax.Request(action, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+
+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 Object.toQueryString(pair);
+      }
+    }
+    return '';
+  },
+
+  getValue: function(element) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    return Form.Element.Serializers[method](element);
+  },
+
+  setValue: function(element, value) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    Form.Element.Serializers[method](element, value);
+    return element;
+  },
+
+  clear: function(element) {
+    $(element).value = '';
+    return element;
+  },
+
+  present: function(element) {
+    return $(element).value != '';
+  },
+
+  activate: function(element) {
+    element = $(element);
+    try {
+      element.focus();
+      if (element.select && (element.tagName.toLowerCase() != 'input' ||
+          !(/^(?:button|reset|submit)$/i.test(element.type))))
+        element.select();
+    } catch (e) { }
+    return element;
+  },
+
+  disable: function(element) {
+    element = $(element);
+    element.disabled = true;
+    return element;
+  },
+
+  enable: function(element) {
+    element = $(element);
+    element.disabled = false;
+    return element;
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Field = Form.Element;
+
+var $F = Form.Element.Methods.getValue;
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element.Serializers = {
+  input: function(element, value) {
+    switch (element.type.toLowerCase()) {
+      case 'checkbox':
+      case 'radio':
+        return Form.Element.Serializers.inputSelector(element, value);
+      default:
+        return Form.Element.Serializers.textarea(element, value);
+    }
+  },
+
+  inputSelector: function(element, value) {
+    if (Object.isUndefined(value)) return element.checked ? element.value : null;
+    else element.checked = !!value;
+  },
+
+  textarea: function(element, value) {
+    if (Object.isUndefined(value)) return element.value;
+    else element.value = value;
+  },
+
+  select: function(element, value) {
+    if (Object.isUndefined(value))
+      return this[element.type == 'select-one' ?
+        'selectOne' : 'selectMany'](element);
+    else {
+      var opt, currentValue, single = !Object.isArray(value);
+      for (var i = 0, length = element.length; i < length; i++) {
+        opt = element.options[i];
+        currentValue = this.optionValue(opt);
+        if (single) {
+          if (currentValue == value) {
+            opt.selected = true;
+            return;
+          }
+        }
+        else opt.selected = value.include(currentValue);
+      }
+    }
+  },
+
+  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) {
+    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+
+Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
+  initialize: function($super, element, frequency, callback) {
+    $super(callback, frequency);
+    this.element   = $(element);
+    this.lastValue = this.getValue();
+  },
+
+  execute: function() {
+    var value = this.getValue();
+    if (Object.isString(this.lastValue) && Object.isString(value) ?
+        this.lastValue != value : String(this.lastValue) != String(value)) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  }
+});
+
+Form.Element.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.EventObserver = Class.create({
+  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, 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(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.EventObserver = Class.create(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+(function() {
+
+  var 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,
+    KEY_INSERT:   45,
+
+    cache: {}
+  };
+
+  var docEl = document.documentElement;
+  var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl
+    && 'onmouseleave' in docEl;
+
+  var _isButton;
+  if (Prototype.Browser.IE) {
+    var buttonMap = { 0: 1, 1: 4, 2: 2 };
+    _isButton = function(event, code) {
+      return event.button === buttonMap[code];
+    };
+  } else if (Prototype.Browser.WebKit) {
+    _isButton = function(event, code) {
+      switch (code) {
+        case 0: return event.which == 1 && !event.metaKey;
+        case 1: return event.which == 1 && event.metaKey;
+        default: return false;
+      }
+    };
+  } else {
+    _isButton = function(event, code) {
+      return event.which ? (event.which === code + 1) : (event.button === code);
+    };
+  }
+
+  function isLeftClick(event)   { return _isButton(event, 0) }
+
+  function isMiddleClick(event) { return _isButton(event, 1) }
+
+  function isRightClick(event)  { return _isButton(event, 2) }
+
+  function element(event) {
+    event = Event.extend(event);
+
+    var node = event.target, type = event.type,
+     currentTarget = event.currentTarget;
+
+    if (currentTarget && currentTarget.tagName) {
+      if (type === 'load' || type === 'error' ||
+        (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
+          && currentTarget.type === 'radio'))
+            node = currentTarget;
+    }
+
+    if (node.nodeType == Node.TEXT_NODE)
+      node = node.parentNode;
+
+    return Element.extend(node);
+  }
+
+  function findElement(event, expression) {
+    var element = Event.element(event);
+    if (!expression) return element;
+    var elements = [element].concat(element.ancestors());
+    return Selector.findElement(elements, expression, 0);
+  }
+
+  function pointer(event) {
+    return { x: pointerX(event), y: pointerY(event) };
+  }
+
+  function pointerX(event) {
+    var docElement = document.documentElement,
+     body = document.body || { scrollLeft: 0 };
+
+    return event.pageX || (event.clientX +
+      (docElement.scrollLeft || body.scrollLeft) -
+      (docElement.clientLeft || 0));
+  }
+
+  function pointerY(event) {
+    var docElement = document.documentElement,
+     body = document.body || { scrollTop: 0 };
+
+    return  event.pageY || (event.clientY +
+       (docElement.scrollTop || body.scrollTop) -
+       (docElement.clientTop || 0));
+  }
+
+
+  function stop(event) {
+    Event.extend(event);
+    event.preventDefault();
+    event.stopPropagation();
+
+    event.stopped = true;
+  }
+
+  Event.Methods = {
+    isLeftClick: isLeftClick,
+    isMiddleClick: isMiddleClick,
+    isRightClick: isRightClick,
+
+    element: element,
+    findElement: findElement,
+
+    pointer: pointer,
+    pointerX: pointerX,
+    pointerY: pointerY,
+
+    stop: stop
+  };
+
+
+  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
+    m[name] = Event.Methods[name].methodize();
+    return m;
+  });
+
+  if (Prototype.Browser.IE) {
+    function _relatedTarget(event) {
+      var element;
+      switch (event.type) {
+        case 'mouseover': element = event.fromElement; break;
+        case 'mouseout':  element = event.toElement;   break;
+        default: return null;
+      }
+      return Element.extend(element);
+    }
+
+    Object.extend(methods, {
+      stopPropagation: function() { this.cancelBubble = true },
+      preventDefault:  function() { this.returnValue = false },
+      inspect: function() { return '[object Event]' }
+    });
+
+    Event.extend = function(event, element) {
+      if (!event) return false;
+      if (event._extendedByPrototype) return event;
+
+      event._extendedByPrototype = Prototype.emptyFunction;
+      var pointer = Event.pointer(event);
+
+      Object.extend(event, {
+        target: event.srcElement || element,
+        relatedTarget: _relatedTarget(event),
+        pageX:  pointer.x,
+        pageY:  pointer.y
+      });
+
+      return Object.extend(event, methods);
+    };
+  } else {
+    Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__;
+    Object.extend(Event.prototype, methods);
+    Event.extend = Prototype.K;
+  }
+
+  function _createResponder(element, eventName, handler) {
+    var registry = Element.retrieve(element, 'prototype_event_registry');
+
+    if (Object.isUndefined(registry)) {
+      CACHE.push(element);
+      registry = Element.retrieve(element, 'prototype_event_registry', $H());
+    }
+
+    var respondersForEvent = registry.get(eventName);
+    if (Object.isUndefined(respondersForEvent)) {
+      respondersForEvent = [];
+      registry.set(eventName, respondersForEvent);
+    }
+
+    if (respondersForEvent.pluck('handler').include(handler)) return false;
+
+    var responder;
+    if (eventName.include(":")) {
+      responder = function(event) {
+        if (Object.isUndefined(event.eventName))
+          return false;
+
+        if (event.eventName !== eventName)
+          return false;
+
+        Event.extend(event, element);
+        handler.call(element, event);
+      };
+    } else {
+      if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED &&
+       (eventName === "mouseenter" || eventName === "mouseleave")) {
+        if (eventName === "mouseenter" || eventName === "mouseleave") {
+          responder = function(event) {
+            Event.extend(event, element);
+
+            var parent = event.relatedTarget;
+            while (parent && parent !== element) {
+              try { parent = parent.parentNode; }
+              catch(e) { parent = element; }
+            }
+
+            if (parent === element) return;
+
+            handler.call(element, event);
+          };
+        }
+      } else {
+        responder = function(event) {
+          Event.extend(event, element);
+          handler.call(element, event);
+        };
+      }
+    }
+
+    responder.handler = handler;
+    respondersForEvent.push(responder);
+    return responder;
+  }
+
+  function _destroyCache() {
+    for (var i = 0, length = CACHE.length; i < length; i++) {
+      Event.stopObserving(CACHE[i]);
+      CACHE[i] = null;
+    }
+  }
+
+  var CACHE = [];
+
+  if (Prototype.Browser.IE)
+    window.attachEvent('onunload', _destroyCache);
+
+  if (Prototype.Browser.WebKit)
+    window.addEventListener('unload', Prototype.emptyFunction, false);
+
+
+  var _getDOMEventName = Prototype.K;
+
+  if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) {
+    _getDOMEventName = function(eventName) {
+      var translations = { mouseenter: "mouseover", mouseleave: "mouseout" };
+      return eventName in translations ? translations[eventName] : eventName;
+    };
+  }
+
+  function observe(element, eventName, handler) {
+    element = $(element);
+
+    var responder = _createResponder(element, eventName, handler);
+
+    if (!responder) return element;
+
+    if (eventName.include(':')) {
+      if (element.addEventListener)
+        element.addEventListener("dataavailable", responder, false);
+      else {
+        element.attachEvent("ondataavailable", responder);
+        element.attachEvent("onfilterchange", responder);
+      }
+    } else {
+      var actualEventName = _getDOMEventName(eventName);
+
+      if (element.addEventListener)
+        element.addEventListener(actualEventName, responder, false);
+      else
+        element.attachEvent("on" + actualEventName, responder);
+    }
+
+    return element;
+  }
+
+  function stopObserving(element, eventName, handler) {
+    element = $(element);
+
+    var registry = Element.retrieve(element, 'prototype_event_registry');
+
+    if (Object.isUndefined(registry)) return element;
+
+    if (eventName && !handler) {
+      var responders = registry.get(eventName);
+
+      if (Object.isUndefined(responders)) return element;
+
+      responders.each( function(r) {
+        Element.stopObserving(element, eventName, r.handler);
+      });
+      return element;
+    } else if (!eventName) {
+      registry.each( function(pair) {
+        var eventName = pair.key, responders = pair.value;
+
+        responders.each( function(r) {
+          Element.stopObserving(element, eventName, r.handler);
+        });
+      });
+      return element;
+    }
+
+    var responders = registry.get(eventName);
+
+    if (!responders) return;
+
+    var responder = responders.find( function(r) { return r.handler === handler; });
+    if (!responder) return element;
+
+    var actualEventName = _getDOMEventName(eventName);
+
+    if (eventName.include(':')) {
+      if (element.removeEventListener)
+        element.removeEventListener("dataavailable", responder, false);
+      else {
+        element.detachEvent("ondataavailable", responder);
+        element.detachEvent("onfilterchange",  responder);
+      }
+    } else {
+      if (element.removeEventListener)
+        element.removeEventListener(actualEventName, responder, false);
+      else
+        element.detachEvent('on' + actualEventName, responder);
+    }
+
+    registry.set(eventName, responders.without(responder));
+
+    return element;
+  }
+
+  function fire(element, eventName, memo, bubble) {
+    element = $(element);
+
+    if (Object.isUndefined(bubble))
+      bubble = true;
+
+    if (element == document && document.createEvent && !element.dispatchEvent)
+      element = document.documentElement;
+
+    var event;
+    if (document.createEvent) {
+      event = document.createEvent('HTMLEvents');
+      event.initEvent('dataavailable', true, true);
+    } else {
+      event = document.createEventObject();
+      event.eventType = bubble ? 'ondataavailable' : 'onfilterchange';
+    }
+
+    event.eventName = eventName;
+    event.memo = memo || { };
+
+    if (document.createEvent)
+      element.dispatchEvent(event);
+    else
+      element.fireEvent(event.eventType, event);
+
+    return Event.extend(event);
+  }
+
+
+  Object.extend(Event, Event.Methods);
+
+  Object.extend(Event, {
+    fire:          fire,
+    observe:       observe,
+    stopObserving: stopObserving
+  });
+
+  Element.addMethods({
+    fire:          fire,
+
+    observe:       observe,
+
+    stopObserving: stopObserving
+  });
+
+  Object.extend(document, {
+    fire:          fire.methodize(),
+
+    observe:       observe.methodize(),
+
+    stopObserving: stopObserving.methodize(),
+
+    loaded:        false
+  });
+
+  if (window.Event) Object.extend(window.Event, Event);
+  else window.Event = Event;
+})();
+
+(function() {
+  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
+     Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */
+
+  var timer;
+
+  function fireContentLoadedEvent() {
+    if (document.loaded) return;
+    if (timer) window.clearTimeout(timer);
+    document.loaded = true;
+    document.fire('dom:loaded');
+  }
+
+  function checkReadyState() {
+    if (document.readyState === 'complete') {
+      document.stopObserving('readystatechange', checkReadyState);
+      fireContentLoadedEvent();
+    }
+  }
+
+  function pollDoScroll() {
+    try { document.documentElement.doScroll('left'); }
+    catch(e) {
+      timer = pollDoScroll.defer();
+      return;
+    }
+    fireContentLoadedEvent();
+  }
+
+  if (document.addEventListener) {
+    document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false);
+  } else {
+    document.observe('readystatechange', checkReadyState);
+    if (window == top)
+      timer = pollDoScroll.defer();
+  }
+
+  Event.observe(window, 'load', fireContentLoadedEvent);
+})();
+
+Element.addMethods();
+
+/*------------------------------- DEPRECATED -------------------------------*/
+
+Hash.toQueryString = Object.toQueryString;
+
+var Toggle = { display: Element.toggle };
+
+Element.Methods.childOf = Element.Methods.descendantOf;
+
+var Insertion = {
+  Before: function(element, content) {
+    return Element.insert(element, {before:content});
+  },
+
+  Top: function(element, content) {
+    return Element.insert(element, {top:content});
+  },
+
+  Bottom: function(element, content) {
+    return Element.insert(element, {bottom:content});
+  },
+
+  After: function(element, content) {
+    return Element.insert(element, {after:content});
+  }
+};
+
+var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
+
+var Position = {
+  includeScrollOffsets: false,
+
+  prepare: function() {
+    this.deltaX =  window.pageXOffset
+                || document.documentElement.scrollLeft
+                || document.body.scrollLeft
+                || 0;
+    this.deltaY =  window.pageYOffset
+                || document.documentElement.scrollTop
+                || document.body.scrollTop
+                || 0;
+  },
+
+  within: function(element, x, y) {
+    if (this.includeScrollOffsets)
+      return this.withinIncludingScrolloffsets(element, x, y);
+    this.xcomp = x;
+    this.ycomp = y;
+    this.offset = Element.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 = Element.cumulativeScrollOffset(element);
+
+    this.xcomp = x + offsetcache[0] - this.deltaX;
+    this.ycomp = y + offsetcache[1] - this.deltaY;
+    this.offset = Element.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);
+  },
+
+  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;
+  },
+
+
+  cumulativeOffset: Element.Methods.cumulativeOffset,
+
+  positionedOffset: Element.Methods.positionedOffset,
+
+  absolutize: function(element) {
+    Position.prepare();
+    return Element.absolutize(element);
+  },
+
+  relativize: function(element) {
+    Position.prepare();
+    return Element.relativize(element);
+  },
+
+  realOffset: Element.Methods.cumulativeScrollOffset,
+
+  offsetParent: Element.Methods.getOffsetParent,
+
+  page: Element.Methods.viewportOffset,
+
+  clone: function(source, target, options) {
+    options = options || { };
+    return Element.clonePosition(target, source, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
+  function iter(name) {
+    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
+  }
+
+  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
+  function(element, className) {
+    className = className.toString().strip();
+    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
+    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
+  } : function(element, className) {
+    className = className.toString().strip();
+    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
+    if (!classNames && !className) return elements;
+
+    var nodes = $(element).getElementsByTagName('*');
+    className = ' ' + className + ' ';
+
+    for (var i = 0, child, cn; child = nodes[i]; i++) {
+      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
+          (classNames && classNames.all(function(name) {
+            return !name.toString().blank() && cn.include(' ' + name + ' ');
+          }))))
+        elements.push(Element.extend(child));
+    }
+    return elements;
+  };
+
+  return function(className, parentElement) {
+    return $(parentElement || document.body).getElementsByClassName(className);
+  };
+}(Element.Methods);
+
+/*--------------------------------------------------------------------------*/
+
+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);
+
+/*--------------------------------------------------------------------------*/
diff --git a/info.textgrid.middleware.tgauth.tgaccount/service/.htaccess b/info.textgrid.middleware.tgauth.tgaccount/service/.htaccess
new file mode 100644
index 0000000000000000000000000000000000000000..1bf86189d4ab660b903f06942c0bb5fbac2a2b62
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/service/.htaccess
@@ -0,0 +1,6 @@
+
+AuthName "Admintool"
+AuthType Basic
+require valid-user
+
+AuthUserFile /etc/textgrid/tgaccount/.htpass
diff --git a/info.textgrid.middleware.tgauth.tgaccount/service/insertRequest.php b/info.textgrid.middleware.tgauth.tgaccount/service/insertRequest.php
new file mode 100644
index 0000000000000000000000000000000000000000..328d2f6de1f4cf7ce5b37cfb4ce74924fb071f97
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/service/insertRequest.php
@@ -0,0 +1,83 @@
+<?php
+// tell client, he hit correct endpoint ... 
+header("X-ATEndpoint: YES");
+
+include '../conf/config.inc.php';
+include '../include/tgSqliteDB.class.php';
+include '../include/tgLdap.class.php';
+include '../include/tgImap.class.php';
+
+$db = new tgSqliteDB($conf);
+$ldap = new tgLdap($conf);
+$imap = new tgImap($conf);
+
+// work around magic_quotes_gpc
+if (get_magic_quotes_gpc()) {
+  $data = stripslashes($_POST[data]);
+} else {
+  $data = $_POST[data];
+}
+
+/**
+ * Validation 
+ */  
+$in = json_decode($data, TRUE);
+$out['status'] = 'validating';
+// email already used? -> query ldap
+if($ldap->emailExists($in['email'])) {
+  $out['error']['email'] = "registered";
+} 
+
+// userid already used? -> query ldap
+if($ldap->uidExists($in['userid'])) {
+  $out['error']['userid'] = "userid_used";
+}
+
+
+if($out['error']) {
+  echo json_encode($out);
+  return;
+}
+
+/**
+ *  validation done, request -> db, mail to user and ...
+ */
+
+$db->insertUserRequest($in);
+
+// send email
+sendMails($conf, $imap, $db, $in);
+
+
+/**
+ * Here the data is returned
+ */
+echo json_encode(array("status" => "done"));
+
+
+
+function sendMails($conf, $imap, $db, $data) {
+  
+  /**
+   * notify user
+   */
+  $templateID = ($data['lang'] == 'de') ? 6 : 5;
+  $mail = $db->getMailTemplate($templateID);
+  
+  $subject = 'Request for textgrid account';
+  $imap->mail($data['email'], $mail['subject'], $mail['body']);
+  
+  /**
+   * notify textgrid-team
+   */
+  $body = "";
+  foreach($data as $key => $value) {
+    $body .= $key . ': ' . $value . "\n";
+  }
+  $body .= "\nLink zum Backend : " . $conf['url'] . "\n";
+
+  $imap->mail($conf['imap']['cc'], 'New Request', $body);
+  
+}  
+
+?>
diff --git a/info.textgrid.middleware.tgauth.tgaccount/smarty/templates/edit_requests.tpl b/info.textgrid.middleware.tgauth.tgaccount/smarty/templates/edit_requests.tpl
new file mode 100644
index 0000000000000000000000000000000000000000..5c701ba9752f804d55bc5ae9650998b74f53f8e1
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/smarty/templates/edit_requests.tpl
@@ -0,0 +1,198 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html
+     PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+  <script src="js/prototype.js" type="text/javascript"></script>
+  <title>User Info</title>
+  <link rel="stylesheet" type="text/css" href="css/style.css" />
+  
+  {if $created}
+  <script type="text/javascript">
+    var uid = '{$created.uid}';
+    var password = '{$created.password}';
+    var tempId = {if $request.lang == 'en' }4{else}3{/if};
+  </script>
+  {else}
+  <script type="text/javascript">
+    var uid;
+    var password;
+    var tempId = {if $request.lang == 'en' }1{else}2{/if};
+  </script>
+  {/if}
+  
+  {if $mail or $created} 
+  {literal}
+  <script type="text/javascript">
+  
+   document.observe("dom:loaded", function() {
+    loadTemplate(tempId);
+    $('templateChooser').options[tempId-1].selected = true;
+   });
+  
+   function loadTemplate(id) {
+
+      new Ajax.Request('ajax/mail_template.php', {
+                          method:'get',
+                          parameters: { id: id, 
+                                        userid: $('userID').value,
+                                        uid: uid,
+                                        password: password,
+                                      },
+                          onSuccess: function(transport){
+                            var data = transport.responseText.evalJSON();
+                            $('mail[body]').value=data.body;
+                            $('mail[subject]').value=data.subject;
+                          }
+                       });
+    
+   }
+  </script>
+  {/literal}  
+  {/if}
+  
+  
+  
+  {literal}
+  <script type="text/javascript">
+  
+  function generatePW() {
+    
+    new Ajax.Request('ajax/pwgen.php', { method:'get',
+      onSuccess: function(transport){
+        $('ldap[password]').value=transport.responseText;
+      }
+    });
+
+  
+  }
+  
+  </script>
+  {/literal}
+  
+</head>
+  
+<body>
+
+  <div id="topMenu">
+  <ul class="menu mainMenu">
+    <li><a href="index.php">Anfragen</a></li>
+    <li> &gt; &nbsp; bearbeiten</li>
+    <li  style="float:right"><a href="index.php?action=logout">Logout {$login_user}</a>
+    </li>
+  </ul>
+  </div>
+
+  {if $request}
+  
+  <ul class="subMenu menu">
+    <li onclick="window.location.href='{$selflink}?id={$request.id}&action=mail'"><a href="{$selflink}?id={$request.id}&action=mail">Mail</a></li>
+    {if $request.at_status != 1 && $request.at_status != 2}
+    <li onclick="window.location.href='{$selflink}?id={$request.id}&action=create'"><a href="{$selflink}?id={$request.id}&action=create">Einrichten</a></li>
+    {/if}
+    {if $request.at_status == 2}
+    <li onclick="window.location.href='{$selflink}?id={$request.id}&action=reassign'"><a href="{$selflink}?id={$request.id}&action=reassign">Wiederaufnehmen</a></li>
+    {elseif $request.at_status != 1 }
+    <li onclick="window.location.href='{$selflink}?id={$request.id}&action=reject'" ><a href="{$selflink}?id={$request.id}&action=reject">Ablehnen</a></li>
+    {/if}
+  </ul>
+  
+  <div style="clear:both;"></div>
+  <div class="spacer" ></div>
+  
+  {if $status}
+  <div class="status">{$status}</div>
+  {/if}
+  
+  <table>
+    <tr><th>Datum</th><td>{$request.timestamp}</td></tr>
+    <tr><th>Vorname</th><td>{$request.name}</td></tr>
+    <tr><th>Nachname</th><td>{$request.surname}</td></tr>
+    <tr><th>email</th><td>{$request.email}</td></tr>
+    <tr><th>Institution</th><td>{$request.institution}</td></tr>
+    <tr><th>Newsletter</th><td>
+      {if $request.newsletter == 0 }
+        Nein
+      {elseif $request.newsletter == 1}
+        Ja
+      {/if}
+    </td></tr>
+    <tr><th>Pref_uid</th><td>{$request.pref_uid}</td></tr>
+    <tr><th>Sprache</th><td>{$request.lang}</td></tr>
+    <tr><th>Status</th>
+        <td>
+          {if $request.at_status == 0 }
+            Nicht Zugeordnet
+          {elseif $request.at_status == 1 } 
+            Erstellt: {$request.ldap_uid}
+            
+          {elseif $request.at_status == 2 }
+            Abgelehnt
+          {elseif $request.at_status == 3 }
+            in Arbeit
+          {/if}
+        </td>
+    </tr>
+    <input type="hidden" value="{$request.id}" id="userID">
+  </table>
+  
+  {else}
+   <div class="status">kein Request ausgewählt</div>
+  {/if}
+
+  <div class="spacer"></div>
+  
+  {if $create}
+    <form action="{$selflink}?id={$request.id}" method="POST">
+      <fieldset>
+        <legend>Account Anlegen (LDAP)</legend>
+        <label>UserID</label><input name="ldap[uid]" type="text" value="{$request.pref_uid}" /><br/>
+        <label>Vorname</label><input name="ldap[givenName]" type="text" value="{$request.name}" /><br/>
+        <label>Nachname</label><input name="ldap[sn]" type="text" value="{$request.surname}" /><br/>
+        <label>Institution</label><input name="ldap[o]" type="text" value="{$request.institution}" /><br/>
+        <label>EMail</label><input name="ldap[mail]" type="text" value="{$request.email}" /><br/>
+        <label>EPPN</label><input name="ldap[eduPersonPrincipalName]" type="text" value="{$request.eppn}" /><br/>
+        <label>CN</label><input name="ldap[cn]" type="text" value="{$request.name} {$request.surname}" /><br/>
+        <label>Wants Newsletter</label>
+        <input type="radio" name="ldap[TGWantsNewsletter]" value="TRUE" {if $request.newsletter == 1 }checked="checked"{/if}>TRUE
+        <input type="radio" name="ldap[TGWantsNewsletter]" value="FALSE" {if $request.newsletter == 0 }checked="checked"{/if}>FALSE
+        <br/>
+        <label>Passwort</label><input id="ldap[password]" name="ldap[password]" type="text" value="" /><button onClick="generatePW();return false;">Vorschlagen</button><br/>
+        <label for="sandbox">Sandbox</label><input type="checkbox" name="sandbox" id="sandbox" checked="checked" /><br/>
+        <label>&nbsp;</label><input type="submit" name="create[submit]" value="Erstellen">
+      </fieldset>
+    </form>
+  {/if}
+  
+  
+  {if $mail or $created}
+    
+    <fieldset class="mail">
+    <legend>Mail</legend>
+
+    <label>Mailtemplate</label><select onChange="loadTemplate(this.options[this.selectedIndex].value);" id="templateChooser">
+      {foreach item=temp from=$mail}
+        <option value="{$temp.id}">{$temp.title} - {$temp.lang}</option>
+      {/foreach}  
+    </select>
+    
+    <form action="{$selflink}?id={$request.id}" method="POST">    
+      <label>An</label>    
+      <input type="text" name="mail[reciever]" value="{$request.email}" /><br/>
+      
+       <label>Betreff</label>    
+      <input  id="mail[subject]" type="text" name="mail[subject]" value="" /><br/>     
+      
+      <label>Text</label>
+      <textarea id="mail[body]" cols="80" rows="20" name="mail[body]"></textarea><br/>
+      <input type="submit" name="mail[submit]" value="senden">
+      </form>
+      
+    </fieldset>
+    
+  {/if}
+
+</body>
+</html>
diff --git a/info.textgrid.middleware.tgauth.tgaccount/smarty/templates/list_requests.tpl b/info.textgrid.middleware.tgauth.tgaccount/smarty/templates/list_requests.tpl
new file mode 100644
index 0000000000000000000000000000000000000000..f58b29951a93eb5f0c4f77d5dfd5671a8d021cbf
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/smarty/templates/list_requests.tpl
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html
+     PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+  <script src="js/prototype.js" type="text/javascript"></script>
+  <title>User Info</title>
+  <link rel="stylesheet" type="text/css" href="css/style.css" />
+
+  
+</head>
+  
+<body>
+
+
+  <div id="topMenu">
+  <ul class="menu mainMenu">
+    <li><a href="index.php">Anfragen</a></li>
+    <li  style="float:right"><a href="index.php?action=logout">Logout {$login_user}</a>
+    </li>
+  </ul>
+  </div>
+
+
+  <ul class="subMenu menu">
+  <li {if $show == open || $show == ''} class="active" {/if} onclick="window.location.href='{$selflink}?show=open'"><a href="{$selflink}?show=open">Offen</a></li>
+  <li {if $show == assigned} class="active" {/if} onclick="window.location.href='{$selflink}?show=assigned'"><a href="{$selflink}?show=assigned">in Arbeit</a></li>
+  <li {if $show == closed} class="active" {/if} onclick="window.location.href='{$selflink}?show=closed'"><a href="{$selflink}?show=closed">Geschlossen</a></li>
+  <li {if $show == all} class="active" {/if} onclick="window.location.href='{$selflink}?show=all'"><a href="{$selflink}?show=all">Alle</a></li>
+  </ul>
+  
+  <div style="clear:both;"></div>
+  <div class="spacer" ></div>
+  
+  {if $status}
+  <div class="status">{$status}</div>
+  {else}
+  <div class="status" style="visibility:hidden;">&nbsp;</div>
+  {/if}
+
+  {if $requests}
+  <table class="requestlist">
+    <tr>
+      <th>Name</th>
+      <th>Email</th>
+      <th>Datum</th>
+      <th>Status</th>
+      <!--<th>Bearbeitung</th>-->
+    </tr>
+    {foreach item=request from=$requests}
+    <tr onclick="window.location.href='edit_request.php?id={$request.id}'" >
+      <td>
+        {$request.name} {$request.surname}
+      </td>
+      <td>
+        {$request.email}
+      </td>
+      <td>{$request.timestamp}</td>
+      
+      <td>
+      
+          {if $request.at_status == 0 }
+            Offen
+          {elseif $request.at_status == 1 } 
+            Erstellt: {$request.ldap_uid}
+            
+          {elseif $request.at_status == 2 }
+            Abgelehnt
+          {elseif $request.at_status == 3 }
+            in Arbeit
+          {/if}
+      </td>
+      
+      <!--<td>{$request.at_assignee}</td>-->
+    </tr>
+    {/foreach}
+  </table>
+  {else}
+  <table class="requestlist">
+    <tr><th>keine offenen Anfragen</th></tr></table>
+  {/if}
+  
+  
+
+</body>
+</html>
diff --git a/info.textgrid.middleware.tgauth.tgaccount/smarty/templates_c/%%13^138^1388B721%%edit_requests.tpl.php b/info.textgrid.middleware.tgauth.tgaccount/smarty/templates_c/%%13^138^1388B721%%edit_requests.tpl.php
new file mode 100644
index 0000000000000000000000000000000000000000..fb8e171bd41b17f272dd58724ed1bc8b24833e94
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/smarty/templates_c/%%13^138^1388B721%%edit_requests.tpl.php
@@ -0,0 +1,251 @@
+<?php /* Smarty version 2.6.22, created on 2010-02-17 13:24:45
+         compiled from edit_requests.tpl */ ?>
+<?php echo '<?xml'; ?>
+ version="1.0" encoding="utf-8"<?php echo '?>'; ?>
+
+<!DOCTYPE html
+     PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+  <script src="js/prototype.js" type="text/javascript"></script>
+  <title>User Info</title>
+  <link rel="stylesheet" type="text/css" href="css/style.css" />
+  
+  <?php if ($this->_tpl_vars['created']): ?>
+  <script type="text/javascript">
+    var uid = '<?php echo $this->_tpl_vars['created']['uid']; ?>
+';
+    var password = '<?php echo $this->_tpl_vars['created']['password']; ?>
+';
+    var tempId = <?php if ($this->_tpl_vars['request']['lang'] == 'en'): ?>4<?php else: ?>3<?php endif; ?>;
+  </script>
+  <?php else: ?>
+  <script type="text/javascript">
+    var uid;
+    var password;
+    var tempId = <?php if ($this->_tpl_vars['request']['lang'] == 'en'): ?>1<?php else: ?>2<?php endif; ?>;
+  </script>
+  <?php endif; ?>
+  
+  <?php if ($this->_tpl_vars['mail'] || $this->_tpl_vars['created']): ?> 
+  <?php echo '
+  <script type="text/javascript">
+  
+   document.observe("dom:loaded", function() {
+    loadTemplate(tempId);
+    $(\'templateChooser\').options[tempId-1].selected = true;
+   });
+  
+   function loadTemplate(id) {
+
+      new Ajax.Request(\'ajax/mail_template.php\', {
+                          method:\'get\',
+                          parameters: { id: id, 
+                                        userid: $(\'userID\').value,
+                                        uid: uid,
+                                        password: password,
+                                      },
+                          onSuccess: function(transport){
+                            var data = transport.responseText.evalJSON();
+                            $(\'mail[body]\').value=data.body;
+                            $(\'mail[subject]\').value=data.subject;
+                          }
+                       });
+    
+   }
+  </script>
+  '; ?>
+  
+  <?php endif; ?>
+  
+  
+  
+  <?php echo '
+  <script type="text/javascript">
+  
+  function generatePW() {
+    
+    new Ajax.Request(\'ajax/pwgen.php\', { method:\'get\',
+      onSuccess: function(transport){
+        $(\'ldap[password]\').value=transport.responseText;
+      }
+    });
+
+  
+  }
+  
+  </script>
+  '; ?>
+
+  
+</head>
+  
+<body>
+
+  <div id="topMenu">
+  <ul class="menu mainMenu">
+    <li><a href="index.php">Anfragen</a></li>
+    <li> &gt; &nbsp; bearbeiten</li>
+    <li  style="float:right"><a href="index.php?action=logout">Logout <?php echo $this->_tpl_vars['login_user']; ?>
+</a>
+    </li>
+  </ul>
+  </div>
+
+  <?php if ($this->_tpl_vars['request']): ?>
+  
+  <ul class="subMenu menu">
+    <li onclick="window.location.href='<?php echo $this->_tpl_vars['selflink']; ?>
+?id=<?php echo $this->_tpl_vars['request']['id']; ?>
+&action=mail'"><a href="<?php echo $this->_tpl_vars['selflink']; ?>
+?id=<?php echo $this->_tpl_vars['request']['id']; ?>
+&action=mail">Mail</a></li>
+    <?php if ($this->_tpl_vars['request']['at_status'] != 1 && $this->_tpl_vars['request']['at_status'] != 2): ?>
+    <li onclick="window.location.href='<?php echo $this->_tpl_vars['selflink']; ?>
+?id=<?php echo $this->_tpl_vars['request']['id']; ?>
+&action=create'"><a href="<?php echo $this->_tpl_vars['selflink']; ?>
+?id=<?php echo $this->_tpl_vars['request']['id']; ?>
+&action=create">Einrichten</a></li>
+    <?php endif; ?>
+    <?php if ($this->_tpl_vars['request']['at_status'] == 2): ?>
+    <li onclick="window.location.href='<?php echo $this->_tpl_vars['selflink']; ?>
+?id=<?php echo $this->_tpl_vars['request']['id']; ?>
+&action=reassign'"><a href="<?php echo $this->_tpl_vars['selflink']; ?>
+?id=<?php echo $this->_tpl_vars['request']['id']; ?>
+&action=reassign">Wiederaufnehmen</a></li>
+    <?php elseif ($this->_tpl_vars['request']['at_status'] != 1): ?>
+    <li onclick="window.location.href='<?php echo $this->_tpl_vars['selflink']; ?>
+?id=<?php echo $this->_tpl_vars['request']['id']; ?>
+&action=reject'" ><a href="<?php echo $this->_tpl_vars['selflink']; ?>
+?id=<?php echo $this->_tpl_vars['request']['id']; ?>
+&action=reject">Ablehnen</a></li>
+    <?php endif; ?>
+  </ul>
+  
+  <div style="clear:both;"></div>
+  <div class="spacer" ></div>
+  
+  <?php if ($this->_tpl_vars['status']): ?>
+  <div class="status"><?php echo $this->_tpl_vars['status']; ?>
+</div>
+  <?php endif; ?>
+  
+  <table>
+    <tr><th>Datum</th><td><?php echo $this->_tpl_vars['request']['timestamp']; ?>
+</td></tr>
+    <tr><th>Vorname</th><td><?php echo $this->_tpl_vars['request']['name']; ?>
+</td></tr>
+    <tr><th>Nachname</th><td><?php echo $this->_tpl_vars['request']['surname']; ?>
+</td></tr>
+    <tr><th>email</th><td><?php echo $this->_tpl_vars['request']['email']; ?>
+</td></tr>
+    <tr><th>Institution</th><td><?php echo $this->_tpl_vars['request']['institution']; ?>
+</td></tr>
+    <tr><th>Newsletter</th><td>
+      <?php if ($this->_tpl_vars['request']['newsletter'] == 0): ?>
+        Nein
+      <?php elseif ($this->_tpl_vars['request']['newsletter'] == 1): ?>
+        Ja
+      <?php endif; ?>
+    </td></tr>
+    <tr><th>Pref_uid</th><td><?php echo $this->_tpl_vars['request']['pref_uid']; ?>
+</td></tr>
+    <tr><th>Sprache</th><td><?php echo $this->_tpl_vars['request']['lang']; ?>
+</td></tr>
+    <tr><th>Status</th>
+        <td>
+          <?php if ($this->_tpl_vars['request']['at_status'] == 0): ?>
+            Nicht Zugeordnet
+          <?php elseif ($this->_tpl_vars['request']['at_status'] == 1): ?> 
+            Erstellt: <?php echo $this->_tpl_vars['request']['ldap_uid']; ?>
+
+            
+          <?php elseif ($this->_tpl_vars['request']['at_status'] == 2): ?>
+            Abgelehnt
+          <?php elseif ($this->_tpl_vars['request']['at_status'] == 3): ?>
+            in Arbeit
+          <?php endif; ?>
+        </td>
+    </tr>
+    <input type="hidden" value="<?php echo $this->_tpl_vars['request']['id']; ?>
+" id="userID">
+  </table>
+  
+  <?php else: ?>
+   <div class="status">kein Request ausgewählt</div>
+  <?php endif; ?>
+
+  <div class="spacer"></div>
+  
+  <?php if ($this->_tpl_vars['create']): ?>
+    <form action="<?php echo $this->_tpl_vars['selflink']; ?>
+?id=<?php echo $this->_tpl_vars['request']['id']; ?>
+" method="POST">
+      <fieldset>
+        <legend>Account Anlegen (LDAP)</legend>
+        <label>UserID</label><input name="ldap[uid]" type="text" value="<?php echo $this->_tpl_vars['request']['pref_uid']; ?>
+" /><br/>
+        <label>Vorname</label><input name="ldap[givenName]" type="text" value="<?php echo $this->_tpl_vars['request']['name']; ?>
+" /><br/>
+        <label>Nachname</label><input name="ldap[sn]" type="text" value="<?php echo $this->_tpl_vars['request']['surname']; ?>
+" /><br/>
+        <label>Institution</label><input name="ldap[o]" type="text" value="<?php echo $this->_tpl_vars['request']['institution']; ?>
+" /><br/>
+        <label>EMail</label><input name="ldap[mail]" type="text" value="<?php echo $this->_tpl_vars['request']['email']; ?>
+" /><br/>
+        <label>EPPN</label><input name="ldap[eduPersonPrincipalName]" type="text" value="<?php echo $this->_tpl_vars['request']['eppn']; ?>
+" /><br/>
+        <label>CN</label><input name="ldap[cn]" type="text" value="<?php echo $this->_tpl_vars['request']['name']; ?>
+ <?php echo $this->_tpl_vars['request']['surname']; ?>
+" /><br/>
+        <label>Wants Newsletter</label>
+        <input type="radio" name="ldap[TGWantsNewsletter]" value="TRUE" <?php if ($this->_tpl_vars['request']['newsletter'] == 1): ?>checked="checked"<?php endif; ?>>TRUE
+        <input type="radio" name="ldap[TGWantsNewsletter]" value="FALSE" <?php if ($this->_tpl_vars['request']['newsletter'] == 0): ?>checked="checked"<?php endif; ?>>FALSE
+        <br/>
+        <label>Passwort</label><input id="ldap[password]" name="ldap[password]" type="text" value="" /><button onClick="generatePW();return false;">Vorschlagen</button><br/>
+        <label for="sandbox">Sandbox</label><input type="checkbox" name="sandbox" id="sandbox" checked="checked" /><br/>
+        <label>&nbsp;</label><input type="submit" name="create[submit]" value="Erstellen">
+      </fieldset>
+    </form>
+  <?php endif; ?>
+  
+  
+  <?php if ($this->_tpl_vars['mail'] || $this->_tpl_vars['created']): ?>
+    
+    <fieldset class="mail">
+    <legend>Mail</legend>
+
+    <label>Mailtemplate</label><select onChange="loadTemplate(this.options[this.selectedIndex].value);" id="templateChooser">
+      <?php $_from = $this->_tpl_vars['mail']; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array'); }if (count($_from)):
+    foreach ($_from as $this->_tpl_vars['temp']):
+?>
+        <option value="<?php echo $this->_tpl_vars['temp']['id']; ?>
+"><?php echo $this->_tpl_vars['temp']['title']; ?>
+ - <?php echo $this->_tpl_vars['temp']['lang']; ?>
+</option>
+      <?php endforeach; endif; unset($_from); ?>  
+    </select>
+    
+    <form action="<?php echo $this->_tpl_vars['selflink']; ?>
+?id=<?php echo $this->_tpl_vars['request']['id']; ?>
+" method="POST">    
+      <label>An</label>    
+      <input type="text" name="mail[reciever]" value="<?php echo $this->_tpl_vars['request']['email']; ?>
+" /><br/>
+      
+       <label>Betreff</label>    
+      <input  id="mail[subject]" type="text" name="mail[subject]" value="" /><br/>     
+      
+      <label>Text</label>
+      <textarea id="mail[body]" cols="80" rows="20" name="mail[body]"></textarea><br/>
+      <input type="submit" name="mail[submit]" value="senden">
+      </form>
+      
+    </fieldset>
+    
+  <?php endif; ?>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/info.textgrid.middleware.tgauth.tgaccount/smarty/templates_c/%%45^45E^45E480CD%%index.tpl.php b/info.textgrid.middleware.tgauth.tgaccount/smarty/templates_c/%%45^45E^45E480CD%%index.tpl.php
new file mode 100644
index 0000000000000000000000000000000000000000..d866c9004f4e0e029e7033ff64011b654cc9fcc2
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/smarty/templates_c/%%45^45E^45E480CD%%index.tpl.php
@@ -0,0 +1,191 @@
+<?php /* Smarty version 2.6.22, created on 2009-12-21 13:06:51
+         compiled from index.tpl */ ?>
+<?php echo '<?xml'; ?>
+ version="1.0" encoding="utf-8"<?php echo '?>'; ?>
+
+<!DOCTYPE html
+     PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+  <script src="js/prototype.js" type="text/javascript"></script>
+  <title>User Info</title>
+  <link rel="stylesheet" type="text/css" href="css/style.css" />
+  <?php echo '
+  <script>
+  
+   document.observe("dom:loaded", function() {
+    loadTemplate(1);
+   });
+  
+   function loadTemplate(id) {
+    
+    //alert("incomes " + id);
+    
+    /*new Ajax.Request(\'/some_url\', {
+      method: \'get\',
+      parameters: { id: \'id\' }
+    });*/
+    new Ajax.Updater( \'mail[body]\', 
+                      \'ajax_resp.php\', 
+                      { method: \'get\', 
+                        parameters: { id: id, 
+                                      userid: $(\'userID\').value }
+                      }
+    );
+   }
+   
+   
+  </script>
+  '; ?>
+  
+</head>
+<body>
+	<?php echo $this->_tpl_vars['menu']; ?>
+
+  
+<!--  <?php if ($this->_tpl_vars['requests']): ?> 
+  <ul class="requestList">
+  <?php unset($this->_sections['mysec']);
+$this->_sections['mysec']['name'] = 'mysec';
+$this->_sections['mysec']['loop'] = is_array($_loop=$this->_tpl_vars['requests']) ? count($_loop) : max(0, (int)$_loop); unset($_loop);
+$this->_sections['mysec']['show'] = true;
+$this->_sections['mysec']['max'] = $this->_sections['mysec']['loop'];
+$this->_sections['mysec']['step'] = 1;
+$this->_sections['mysec']['start'] = $this->_sections['mysec']['step'] > 0 ? 0 : $this->_sections['mysec']['loop']-1;
+if ($this->_sections['mysec']['show']) {
+    $this->_sections['mysec']['total'] = $this->_sections['mysec']['loop'];
+    if ($this->_sections['mysec']['total'] == 0)
+        $this->_sections['mysec']['show'] = false;
+} else
+    $this->_sections['mysec']['total'] = 0;
+if ($this->_sections['mysec']['show']):
+
+            for ($this->_sections['mysec']['index'] = $this->_sections['mysec']['start'], $this->_sections['mysec']['iteration'] = 1;
+                 $this->_sections['mysec']['iteration'] <= $this->_sections['mysec']['total'];
+                 $this->_sections['mysec']['index'] += $this->_sections['mysec']['step'], $this->_sections['mysec']['iteration']++):
+$this->_sections['mysec']['rownum'] = $this->_sections['mysec']['iteration'];
+$this->_sections['mysec']['index_prev'] = $this->_sections['mysec']['index'] - $this->_sections['mysec']['step'];
+$this->_sections['mysec']['index_next'] = $this->_sections['mysec']['index'] + $this->_sections['mysec']['step'];
+$this->_sections['mysec']['first']      = ($this->_sections['mysec']['iteration'] == 1);
+$this->_sections['mysec']['last']       = ($this->_sections['mysec']['iteration'] == $this->_sections['mysec']['total']);
+?>
+    <li onclick="window.location.href='<?php echo $this->_tpl_vars['selflink']; ?>
+?breaches_id=<?php echo $this->_tpl_vars['breaches'][$this->_sections['mysec']['index']]['id']; ?>
+&show=<?php echo $this->_tpl_vars['show']; ?>
+'">
+      <a href="<?php echo $this->_tpl_vars['selflink']; ?>
+?breaches_id=<?php echo $this->_tpl_vars['breaches'][$this->_sections['mysec']['index']]['id']; ?>
+&show=<?php echo $this->_tpl_vars['show']; ?>
+">
+        email: <?php echo $this->_tpl_vars['requests'][$this->_sections['mysec']['index']]['email']; ?>
+
+      </a>
+    </li>
+  <?php endfor; endif; ?>
+  </ul>  
+  <?php endif; ?> -->
+  
+  <?php if ($this->_tpl_vars['status']): ?>
+  <div class="status"><?php echo $this->_tpl_vars['status']; ?>
+</div>
+  <?php else: ?>
+  <div class="status" style="visibility:hidden;">&nbsp;</div>
+  <?php endif; ?>
+  
+  
+  <?php if ($this->_tpl_vars['request']): ?>
+  <table style="background-color:#eee">  
+    <tr><td>name </td><td><?php echo $this->_tpl_vars['request']['name']; ?>
+</td></tr>
+    <tr><td>email</td><td><?php echo $this->_tpl_vars['request']['email']; ?>
+</td></tr>
+    <tr><td>uid</td><td><?php echo $this->_tpl_vars['request']['pref_uid']; ?>
+</td></tr>
+    <input type="hidden" value="<?php echo $this->_tpl_vars['request']['id']; ?>
+" id="userID">
+  </table>
+  <a href="<?php echo $this->_tpl_vars['selflink']; ?>
+?id=<?php echo $this->_tpl_vars['request']['id']; ?>
+&action=mail">Mail</a>
+  <a href="<?php echo $this->_tpl_vars['selflink']; ?>
+?id=<?php echo $this->_tpl_vars['request']['id']; ?>
+&action=create">Einrichten</a>
+  
+  
+  <br/>TO
+  <ul>
+  <?php $_from = $this->_tpl_vars['mheaders']['to']; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array'); }if (count($_from)):
+    foreach ($_from as $this->_tpl_vars['mheader']):
+?>
+    <li><a href="#" onClick="showMail()"><?php echo $this->_tpl_vars['mheader']->subject; ?>
+</a></li>
+  <?php endforeach; endif; unset($_from); ?>
+  </ul>
+  
+  <br/>FROM
+   <ul>
+  <?php $_from = $this->_tpl_vars['mheaders']['from']; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array'); }if (count($_from)):
+    foreach ($_from as $this->_tpl_vars['mheader']):
+?>
+    <li><a href="#" onClick="showMail()"><?php echo $this->_tpl_vars['mheader']->subject; ?>
+</li>
+  <?php endforeach; endif; unset($_from); ?>
+  </ul> 
+  
+  <?php else: ?>
+  
+  <ul class="requestList">
+  <?php $_from = $this->_tpl_vars['requests']; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array'); }if (count($_from)):
+    foreach ($_from as $this->_tpl_vars['request']):
+?>
+    <li onclick="window.location.href='<?php echo $this->_tpl_vars['selflink']; ?>
+?id=<?php echo $this->_tpl_vars['request']['id']; ?>
+">
+      <a href="<?php echo $this->_tpl_vars['selflink']; ?>
+?id=<?php echo $this->_tpl_vars['request']['id']; ?>
+">
+        email: <?php echo $this->_tpl_vars['request']['email']; ?>
+
+      </a>
+    </li>
+  <?php endforeach; endif; unset($_from); ?>
+  </ul>  
+  <?php endif; ?>
+  
+  <?php if ($this->_tpl_vars['mail']): ?>
+  
+    <div>
+    <select onChange="loadTemplate(this.options[this.selectedIndex].value);">
+      <?php $_from = $this->_tpl_vars['mail']; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array'); }if (count($_from)):
+    foreach ($_from as $this->_tpl_vars['temp']):
+?>
+        <option value="<?php echo $this->_tpl_vars['temp']['id']; ?>
+"><?php echo $this->_tpl_vars['temp']['title']; ?>
+ - <?php echo $this->_tpl_vars['temp']['lang']; ?>
+</option>
+      <?php endforeach; endif; unset($_from); ?>  
+    </select>
+    </div>
+      
+    <form action="<?php echo $this->_tpl_vars['selflink']; ?>
+?id=<?php echo $this->_tpl_vars['request']['id']; ?>
+" method="POST">
+    
+    <fieldset>
+    <legend>Mail</legend>
+      <label>To:</label>    
+      <input type="text" name="mail[reciever]" value="<?php echo $this->_tpl_vars['request']['email']; ?>
+" /><br/>
+      
+      <label>Text:</label>
+      <textarea id="mail[body]" cols="80" rows="20" name="mail[body]"></textarea><br/>
+      <input type="submit" name="mail[submit]" value="senden">
+      
+    </fieldset>
+    </form>
+  <?php endif; ?>
+   
+</body>
+</html>
\ No newline at end of file
diff --git a/info.textgrid.middleware.tgauth.tgaccount/smarty/templates_c/%%58^581^581993FC%%list_requests.tpl.php b/info.textgrid.middleware.tgauth.tgaccount/smarty/templates_c/%%58^581^581993FC%%list_requests.tpl.php
new file mode 100644
index 0000000000000000000000000000000000000000..fda896b85111f32b9b4662c81939bc8a72c95396
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/smarty/templates_c/%%58^581^581993FC%%list_requests.tpl.php
@@ -0,0 +1,111 @@
+<?php /* Smarty version 2.6.22, created on 2010-01-21 16:32:05
+         compiled from list_requests.tpl */ ?>
+<?php echo '<?xml'; ?>
+ version="1.0" encoding="utf-8"<?php echo '?>'; ?>
+
+<!DOCTYPE html
+     PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+  <script src="js/prototype.js" type="text/javascript"></script>
+  <title>User Info</title>
+  <link rel="stylesheet" type="text/css" href="css/style.css" />
+
+  
+</head>
+  
+<body>
+
+
+  <div id="topMenu">
+  <ul class="menu mainMenu">
+    <li><a href="index.php">Anfragen</a></li>
+    <li  style="float:right"><a href="index.php?action=logout">Logout <?php echo $this->_tpl_vars['login_user']; ?>
+</a>
+    </li>
+  </ul>
+  </div>
+
+
+  <ul class="subMenu menu">
+  <li <?php if ($this->_tpl_vars['show'] == open || $this->_tpl_vars['show'] == ''): ?> class="active" <?php endif; ?> onclick="window.location.href='<?php echo $this->_tpl_vars['selflink']; ?>
+?show=open'"><a href="<?php echo $this->_tpl_vars['selflink']; ?>
+?show=open">Offen</a></li>
+  <li <?php if ($this->_tpl_vars['show'] == assigned): ?> class="active" <?php endif; ?> onclick="window.location.href='<?php echo $this->_tpl_vars['selflink']; ?>
+?show=assigned'"><a href="<?php echo $this->_tpl_vars['selflink']; ?>
+?show=assigned">in Arbeit</a></li>
+  <li <?php if ($this->_tpl_vars['show'] == closed): ?> class="active" <?php endif; ?> onclick="window.location.href='<?php echo $this->_tpl_vars['selflink']; ?>
+?show=closed'"><a href="<?php echo $this->_tpl_vars['selflink']; ?>
+?show=closed">Geschlossen</a></li>
+  <li <?php if ($this->_tpl_vars['show'] == all): ?> class="active" <?php endif; ?> onclick="window.location.href='<?php echo $this->_tpl_vars['selflink']; ?>
+?show=all'"><a href="<?php echo $this->_tpl_vars['selflink']; ?>
+?show=all">Alle</a></li>
+  </ul>
+  
+  <div style="clear:both;"></div>
+  <div class="spacer" ></div>
+  
+  <?php if ($this->_tpl_vars['status']): ?>
+  <div class="status"><?php echo $this->_tpl_vars['status']; ?>
+</div>
+  <?php else: ?>
+  <div class="status" style="visibility:hidden;">&nbsp;</div>
+  <?php endif; ?>
+
+  <?php if ($this->_tpl_vars['requests']): ?>
+  <table class="requestlist">
+    <tr>
+      <th>Name</th>
+      <th>Email</th>
+      <th>Datum</th>
+      <th>Status</th>
+      <!--<th>Bearbeitung</th>-->
+    </tr>
+    <?php $_from = $this->_tpl_vars['requests']; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array'); }if (count($_from)):
+    foreach ($_from as $this->_tpl_vars['request']):
+?>
+    <tr onclick="window.location.href='edit_request.php?id=<?php echo $this->_tpl_vars['request']['id']; ?>
+'" >
+      <td>
+        <?php echo $this->_tpl_vars['request']['name']; ?>
+ <?php echo $this->_tpl_vars['request']['surname']; ?>
+
+      </td>
+      <td>
+        <?php echo $this->_tpl_vars['request']['email']; ?>
+
+      </td>
+      <td><?php echo $this->_tpl_vars['request']['timestamp']; ?>
+</td>
+      
+      <td>
+      
+          <?php if ($this->_tpl_vars['request']['at_status'] == 0): ?>
+            Offen
+          <?php elseif ($this->_tpl_vars['request']['at_status'] == 1): ?> 
+            Erstellt: <?php echo $this->_tpl_vars['request']['ldap_uid']; ?>
+
+            
+          <?php elseif ($this->_tpl_vars['request']['at_status'] == 2): ?>
+            Abgelehnt
+          <?php elseif ($this->_tpl_vars['request']['at_status'] == 3): ?>
+            in Arbeit
+          <?php endif; ?>
+      </td>
+      
+      <!--<td><?php echo $this->_tpl_vars['request']['at_assignee']; ?>
+</td>-->
+    </tr>
+    <?php endforeach; endif; unset($_from); ?>
+  </table>
+  <?php else: ?>
+  <table class="requestlist">
+    <tr><th>keine offenen Anfragen</th></tr></table>
+  <?php endif; ?>
+  
+  
+
+</body>
+</html>
\ No newline at end of file
diff --git a/info.textgrid.middleware.tgauth.tgaccount/webinit.inc.php b/info.textgrid.middleware.tgauth.tgaccount/webinit.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..76898564999e53539c7b7384024ef5a37e6a8148
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.tgaccount/webinit.inc.php
@@ -0,0 +1,20 @@
+<?php
+include 'include/config.inc.php';
+include 'include/auth.inc.php';
+include 'include/tgSqliteDB.class.php';
+include 'include/tgLdap.class.php';
+include 'include/tgImap.class.php';
+
+$db = new tgSqliteDB($conf);
+$ldap = new tgLdap($conf);
+$imap = new tgImap($conf);
+
+require($conf['smarty']['incPath']);
+$smarty = new Smarty;
+
+$smarty->template_dir = $conf['smarty']['path'].'templates';
+$smarty->compile_dir = $conf['smarty']['path'].'templates_c';
+$smarty->cache_dir = $conf['smarty']['path'].'cache';
+$smarty->config_dir = $conf['smarty']['path'].'configs';
+
+?>