Branch 'kolab-webadmin-3.0' - 143 commits - doc/kolab_hosting-3.0.sql hosted/index.php hosted/js hosted/skins lib/api lib/Auth lib/Auth.php lib/client lib/ext lib/functions.php lib/hosted lib/kolab_api_controller.php lib/kolab_api_service.php lib/kolab_client_api.php lib/kolab_client_output.php lib/kolab_client_task.php lib/kolab_form.php lib/kolab_html.php lib/kolab_json_output.php lib/kolab_utils.php lib/locale lib/recaptchalib.php lib/SQL.php public_html/index.php public_html/js public_html/skins

Jeroen van Meeuwen vanmeeuwen at kolabsys.com
Wed Nov 28 15:14:39 CET 2012


 dev/null                                                  |binary
 doc/kolab_hosting-3.0.sql                                 |    4 
 hosted/index.php                                          |   11 
 hosted/js                                                 |    1 
 hosted/js/jquery.min.js                                   |    1 
 hosted/js/kolab_admin.js                                  |    1 
 hosted/js/kolab_hosted.js                                 |  119 -
 hosted/skins                                              |    1 
 hosted/skins/default/hosted.css                           |   40 
 hosted/skins/default/images/error.png                     |    1 
 hosted/skins/default/images/favicon.png                   |    1 
 hosted/skins/default/images/info.png                      |    1 
 hosted/skins/default/images/loading.gif                   |    1 
 hosted/skins/default/style.css                            |    1 
 hosted/skins/default/templates/footer.html                |    1 
 hosted/skins/default/templates/signup.html                |   43 
 hosted/skins/default/ui.js                                |    1 
 hosted/skins/kolabsys/hosted.css                          |   26 
 hosted/skins/kolabsys/images/error.png                    |    1 
 hosted/skins/kolabsys/images/favicon.png                  |    1 
 hosted/skins/kolabsys/images/info.png                     |    1 
 hosted/skins/kolabsys/images/loading.gif                  |    1 
 hosted/skins/kolabsys/style.css                           |    1 
 hosted/skins/kolabsys/templates/signup.html               |   20 
 hosted/skins/kolabsys/ui.js                               |    1 
 hosted/skins/minimal/hosted.css                           |    1 
 hosted/skins/minimal/images/error.png                     |    1 
 hosted/skins/minimal/images/favicon.png                   |    1 
 hosted/skins/minimal/images/info.png                      |    1 
 hosted/skins/minimal/images/loading.gif                   |    1 
 hosted/skins/minimal/style.css                            |    1 
 hosted/skins/minimal/templates/footer.html                |    1 
 hosted/skins/minimal/templates/signup.html                |   26 
 hosted/skins/minimal/ui.js                                |    1 
 lib/Auth.php                                              |   27 
 lib/Auth/LDAP.php                                         |  792 +++-----
 lib/SQL.php                                               |   32 
 lib/api/kolab_api_service_domain.php                      |   40 
 lib/api/kolab_api_service_domain_types.php                |   86 
 lib/api/kolab_api_service_form_value.php                  |  376 ++-
 lib/api/kolab_api_service_group.php                       |    2 
 lib/api/kolab_api_service_role.php                        |   13 
 lib/api/kolab_api_service_type.php                        |  250 ++
 lib/api/kolab_api_service_user.php                        |    3 
 lib/client/kolab_client_task_about.php                    |    6 
 lib/client/kolab_client_task_domain.php                   |   56 
 lib/client/kolab_client_task_group.php                    |   39 
 lib/client/kolab_client_task_main.php                     |   15 
 lib/client/kolab_client_task_resource.php                 |   39 
 lib/client/kolab_client_task_role.php                     |   40 
 lib/client/kolab_client_task_settings.php                 |  911 +++++++++
 lib/client/kolab_client_task_signup.php                   |  414 ++++
 lib/client/kolab_client_task_user.php                     |   26 
 lib/ext/Mail.php                                          |  270 ++
 lib/ext/Mail/RFC822.php                                   |  951 +++++++++
 lib/ext/Mail/mail.php                                     |  168 +
 lib/ext/Mail/mock.php                                     |  143 +
 lib/ext/Mail/null.php                                     |   84 
 lib/ext/Mail/sendmail.php                                 |  171 +
 lib/ext/Mail/smtp.php                                     |  444 ++++
 lib/ext/Mail/smtpmx.php                                   |  502 +++++
 lib/ext/Net/LDAP3.php                                     |  267 ++
 lib/ext/Net/SMTP.php                                      | 1342 ++++++++++++++
 lib/functions.php                                         |    3 
 lib/hosted/kolab_client_task_signup.php                   |  331 ---
 lib/hosted/recaptchalib.php                               |  277 --
 lib/kolab_api_controller.php                              |   42 
 lib/kolab_api_service.php                                 |  200 +-
 lib/kolab_client_api.php                                  |   20 
 lib/kolab_client_output.php                               |   14 
 lib/kolab_client_task.php                                 |  277 +-
 lib/kolab_form.php                                        |   61 
 lib/kolab_html.php                                        |    9 
 lib/kolab_json_output.php                                 |   68 
 lib/kolab_utils.php                                       |    2 
 lib/locale/de.php                                         |  137 +
 lib/locale/de_DE.php                                      |  121 +
 lib/locale/en.php                                         |  133 +
 lib/locale/en_US.php                                      |  131 +
 lib/locale/es.php                                         |  115 +
 lib/locale/et_EE.php                                      |  131 +
 lib/locale/ja.php                                         |  133 +
 lib/locale/nl.php                                         |  121 +
 lib/locale/pl_PL.php                                      |  115 +
 lib/recaptchalib.php                                      |  277 ++
 public_html/index.php                                     |    2 
 public_html/js/jquery.min.js                              |    6 
 public_html/js/kolab_admin.js                             |  862 +++++++-
 public_html/js/kolab_hosted.js                            |  128 +
 public_html/skins/default/hosted.css                      |   45 
 public_html/skins/default/images/body.png                 |binary
 public_html/skins/default/images/buttons.png              |binary
 public_html/skins/default/images/linen_header.jpg         |binary
 public_html/skins/default/images/loading.gif              |binary
 public_html/skins/default/images/logo_kolab.png           |binary
 public_html/skins/default/images/settings.png             |binary
 public_html/skins/default/style.css                       |  203 +-
 public_html/skins/default/templates/about.html            |    5 
 public_html/skins/default/templates/about_kolab.html      |   56 
 public_html/skins/default/templates/about_kolabsys.html   |   14 
 public_html/skins/default/templates/about_technology.html |   15 
 public_html/skins/default/templates/domain.html           |    2 
 public_html/skins/default/templates/group.html            |    2 
 public_html/skins/default/templates/main.html             |    2 
 public_html/skins/default/templates/resource.html         |    2 
 public_html/skins/default/templates/role.html             |    2 
 public_html/skins/default/templates/signup.html           |   32 
 public_html/skins/default/templates/signup_footer.html    |    1 
 public_html/skins/default/templates/type.html             |   18 
 public_html/skins/default/templates/user.html             |    2 
 public_html/skins/default/ui.js                           |   12 
 public_html/skins/kolabsys/hosted.css                     |   26 
 public_html/skins/kolabsys/images                         |    1 
 public_html/skins/kolabsys/style.css                      |    1 
 public_html/skins/kolabsys/templates/signup.html          |   20 
 public_html/skins/kolabsys/ui.js                          |    1 
 public_html/skins/minimal/hosted.css                      |    1 
 public_html/skins/minimal/images                          |    1 
 public_html/skins/minimal/style.css                       |    1 
 public_html/skins/minimal/templates/signup.html           |   23 
 public_html/skins/minimal/templates/signup_footer.html    |    1 
 public_html/skins/minimal/ui.js                           |    1 
 122 files changed, 9689 insertions(+), 2340 deletions(-)

New commits:
commit 3445b193f9cd8008fa28a6fa5515821c44765b88
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Mon Nov 26 13:26:32 2012 +0000

    Update translations

diff --git a/lib/locale/de.php b/lib/locale/de.php
index 04720e0..02a2baf 100644
--- a/lib/locale/de.php
+++ b/lib/locale/de.php
@@ -7,25 +7,50 @@ $LANG['about.support'] = 'Professionelle Unterstützung gibt es u.A. von <a href
 $LANG['about.technology'] = 'Technologie';
 $LANG['about.warranty'] = 'Sie kommt mit <b>keinen Garantien</b> und wird normalerweise ohne professionelle Unterstützung eingesetzt. Hilfe und weitere Informationen finden Sie auf der <a href="http://kolab.org">Web-Seite</a> und im <a href="http://wiki.kolab.org">Wiki</a>.';
 
+$LANG['add'] = 'Add';
+
+$LANG['attribute.add'] = 'Add attribute';
+$LANG['attribute.static'] = 'Static value';
+$LANG['attribute.name'] = 'Attribute';
+$LANG['attribute.optional'] = 'Optional';
+$LANG['attribute.maxcount'] = 'Max. count';
+$LANG['attribute.readonly'] = 'Read-only';
+$LANG['attribute.type'] = 'Field type';
+$LANG['attribute.value'] = 'Value';
+$LANG['attribute.value.auto'] = 'Generated';
+$LANG['attribute.value.auto-readonly'] = 'Generated (read-only)';
+$LANG['attribute.value.normal'] = 'Normal';
+$LANG['attribute.value.static'] = 'Static';
+$LANG['attribute.options'] = 'Options';
+$LANG['attribute.key.invalid'] = 'Type key contains forbidden characters!';
+$LANG['attribute.required.error'] = 'Required attributes missing in attributes list ($1)!';
+
+$LANG['button.cancel'] = 'Cancel';
+$LANG['button.delete'] = 'Delete';
+$LANG['button.save'] = 'Save';
+$LANG['button.submit'] = 'Submit';
+
 $LANG['creatorsname'] = 'Erstellt von';
 $LANG['days'] = 'Tage';
 $LANG['debug'] = 'Debug Informationen';
-$LANG['delete.button'] = 'Löschen';
+$LANG['delete'] = 'Delete';
 $LANG['deleting'] = 'Lösche Daten...';
 
 $LANG['domain.add'] = 'Domain hinzufügen';
 $LANG['domain.add.success'] = 'Domain erfolgreich hinzugefügt';
 $LANG['domain.associateddomain'] = 'Domain-Name(n)';
+$LANG['domain.delete.success'] = 'Domain deleted successfully.';
 $LANG['domain.edit'] = 'Domain bearbeiten';
 $LANG['domain.edit.success'] = 'Domain aktualisiert';
 $LANG['domain.inetdomainbasedn'] = 'Benutzerdefinierte Root DN(s)';
 $LANG['domain.list'] = 'Domain Liste';
-$LANG['domain.list.records'] = '$1 zu $2 von $3';
+$LANG['domain.norecords'] = 'No domain records found!';
 $LANG['domain.o'] = 'Organisation';
 $LANG['domain.other'] = 'Andere';
 $LANG['domain.system'] = 'System';
 $LANG['domain.type_id'] = 'Standard Domain';
 
+$LANG['edit'] = 'Edit';
 $LANG['error'] = 'Fehler';
 
 $LANG['form.required.empty'] = 'Einige der benötigten Felder sind leer!';
@@ -38,9 +63,9 @@ $LANG['group.delete.success'] = 'Gruppe erfolgreich gelöscht.';
 $LANG['group.edit.success'] = 'Gruppe erfolgreich editiert.';
 $LANG['group.gidnumber'] = 'Primäre Gruppennummer';
 $LANG['group.list'] = 'Gruppenliste';
-$LANG['group.list.records'] = '$1 zu $2 von $3';
 $LANG['group.mail'] = 'Primäre Email-Adresse';
 $LANG['group.member'] = 'Mitglied(er)';
+$LANG['group.memberurl'] = 'Members URL';
 $LANG['group.norecords'] = 'Keine Gruppeneinträge gefunden!';
 $LANG['group.other'] = 'Andere';
 $LANG['group.system'] = 'System';
@@ -49,7 +74,24 @@ $LANG['group.uniquemember'] = 'Mitglieder';
 
 $LANG['info'] = 'Informationen';
 $LANG['internalerror'] = 'Interner Systemfehler!';
+
+$LANG['ldap.one'] = 'one: all entries one level under the base DN';
+$LANG['ldap.sub'] = 'sub: whole subtree starting with the base DN';
+$LANG['ldap.base'] = 'base: base DN only';
+$LANG['ldap.basedn'] = 'Base DN';
+$LANG['ldap.host'] = 'LDAP Server';
+$LANG['ldap.conditions'] = 'Conditions';
+$LANG['ldap.scope'] = 'Scope';
+$LANG['ldap.filter_any'] = 'is non-empty';
+$LANG['ldap.filter_both'] = 'contains';
+$LANG['ldap.filter_prefix'] = 'starts with';
+$LANG['ldap.filter_suffix'] = 'ends with';
+$LANG['ldap.filter_exact'] = 'is equal to';
+
+$LANG['list.records'] = '$1 to $2 of $3';
+
 $LANG['loading'] = 'Lade...';
+$LANG['logout'] = 'Logout';
 $LANG['login.username'] = 'Benutzername:';
 $LANG['login.password'] = 'Passwort:';
 $LANG['login.domain'] = 'Domain:';
@@ -78,7 +120,6 @@ $LANG['resource.edit'] = 'Ressource bearbeiten';
 $LANG['resource.edit.success'] = 'Ressource erfolgreich bearbeitet';
 $LANG['resource.kolabtargetfolder'] = 'Zielordner';
 $LANG['resource.list'] = 'Ressourcen-(Sammlung)-Liste';
-$LANG['resource.list.records'] = '$1 zu $2 von $3';
 $LANG['resource.mail'] = 'Email-Adresse';
 $LANG['resource.member'] = 'Mitglieder der Sammlung';
 $LANG['resource.norecords'] = 'Keine Ressourceneinträge gefunden!';
@@ -88,11 +129,12 @@ $LANG['resource.type_id'] = 'Ressourcentyp';
 $LANG['resource.uniquemember'] = 'Mitglieder der Sammlung';
 
 $LANG['role.add'] = 'Rolle hinzufügen';
+$LANG['role.add.success'] = 'Role created successfully.';
 $LANG['role.cn'] = 'Rollenname';
+$LANG['role.delete.success'] = 'Role deleted successfully.';
 $LANG['role.description'] = 'Rollenbeschreibung';
 $LANG['role.edit.success'] = 'Rolle erfolgreich bearbeitet';
 $LANG['role.list'] = 'Rollenliste';
-$LANG['role.list.records'] = '$1 zu $2 von $3';
 $LANG['role.norecords'] = 'Keine Rolleneinträge gefunden!';
 $LANG['role.system'] = 'Details';
 $LANG['role.type_id'] = 'Rollentyp';
@@ -100,15 +142,17 @@ $LANG['role.type_id'] = 'Rollentyp';
 $LANG['saving'] = 'Speichere...';
 
 $LANG['search'] = 'Suchen';
-$LANG['search.criteria'] = 'Suchkriterien';
 $LANG['search.reset'] = 'Zurücksetzen';
+$LANG['search.criteria'] = 'Suchkriterien';
 $LANG['search.field'] = 'Feld:';
 $LANG['search.method'] = 'Methode:';
 $LANG['search.contains'] = 'enthält';
 $LANG['search.is'] = 'ist';
+$LANG['search.key'] = 'key';
 $LANG['search.prefix'] = 'beginnt mit';
 $LANG['search.name'] = 'Name';
 $LANG['search.email'] = 'Email';
+$LANG['search.description'] = 'description';
 $LANG['search.uid'] = 'UID';
 $LANG['search.loading'] = 'Suche...';
 $LANG['search.acchars'] = 'Mindestens $min Zeichen werden für die Autovervollständigung benötigt';
@@ -133,7 +177,25 @@ $LANG['signup.wronguid'] = 'Ungültiger Benutzername!';
 $LANG['signup.wrongmailalternateaddress'] = 'Bitte geben Sie eine gültige E-Mail Adresse an!';
 $LANG['signup.footer'] = 'Dieser Dienst wird von <a href="http://kolabsys.com">Kolab Systems</a> angeboten.';
 
-$LANG['submit.button'] = 'Abschicken';
+$LANG['type.add'] = 'Add Object Type';
+$LANG['type.add.success'] = 'Object type created successfully.';
+$LANG['type.attributes'] = 'Attributes';
+$LANG['type.description'] = 'Description';
+$LANG['type.delete.success'] = 'Object type deleted successfully.';
+$LANG['type.domain'] = 'Domain';
+$LANG['type.edit.success'] = 'Object type updated successfully.';
+$LANG['type.group'] = 'Group';
+$LANG['type.list'] = 'Object Types List';
+$LANG['type.key'] = 'Key';
+$LANG['type.name'] = 'Name';
+$LANG['type.norecords'] = 'No object type records found!';
+$LANG['type.objectclass'] = 'Object class';
+$LANG['type.object_type'] = 'Object type';
+$LANG['type.properties'] = 'Properties';
+$LANG['type.resource'] = 'Resource';
+$LANG['type.role'] = 'Role';
+$LANG['type.used_for'] = 'Hosted';
+$LANG['type.user'] = 'User';
 
 $LANG['user.add'] = 'Benutzer hinzufügen';
 $LANG['user.add.success'] = 'Benutzer erfolgreich hinzugefügt';
@@ -141,17 +203,22 @@ $LANG['user.alias'] = 'Sekundäre Email-Adresse';
 $LANG['user.astaccountallowedcodec'] = 'Erlaubte(r) Codec(s)';
 $LANG['user.astaccountcallerid'] = 'Anrufer ID';
 $LANG['user.astaccountcontext'] = 'Konto-Kontext';
+$LANG['user.astaccountdefaultuser'] = 'Asterisk Account Default User';
 $LANG['user.astaccountdeny'] = 'Account verweigern';
 $LANG['user.astaccounthost'] = 'Asterisk Server';
+$LANG['user.astaccountmailbox'] = 'Mailbox';
 $LANG['user.astaccountnat'] = 'Account benutzt NAT';
 $LANG['user.astaccountname'] = 'Asterisk Kontoname';
 $LANG['user.astaccountqualify'] = 'Account Qualify';
 $LANG['user.astaccountrealmedpassword'] = 'Realmed Account Password';
+$LANG['user.astaccountregistrationexten'] = 'Extension';
+$LANG['user.astaccountregistrationcontext'] = 'Registration Context';
 $LANG['user.astaccountsecret'] = 'Klartext-Passwort';
 $LANG['user.astaccounttype'] = 'Kontotyp';
 $LANG['user.astcontext'] = 'Asterisk Kontext';
 $LANG['user.asterisk'] = 'Asterisk SIP';
 $LANG['user.astextension'] = 'Asterisk Erweiterung';
+$LANG['user.astvoicemailpassword'] = 'Voicemail PIN Code';
 $LANG['user.c'] = 'Land';
 $LANG['user.city'] = 'Stadt';
 $LANG['user.cn'] = 'Gewöhnlicher Name';
@@ -167,7 +234,7 @@ $LANG['user.fax'] = 'Faxnummer';
 $LANG['user.fbinterval'] = 'Frei-Beschäftigt Intervall';
 $LANG['user.fbinterval.desc'] = 'Leer lassen für Standardwert (60 Tage)';
 $LANG['user.gidnumber'] = 'Primäre Gruppennummer';
-$LANG['user.givenname'] = 'Nachname';
+$LANG['user.givenname'] = 'Vorname';
 $LANG['user.homedirectory'] = 'Heimverzeichnis';
 $LANG['user.homephone'] = 'Festnetznummer';
 $LANG['user.initials'] = 'Initialen';
@@ -179,10 +246,10 @@ $LANG['user.kolabhomeserver'] = 'Email-Server';
 $LANG['user.kolabinvitationpolicy'] = 'Richtlinie um Einladungen zu behandeln';
 $LANG['user.l'] = 'Stadt, Region';
 $LANG['user.list'] = 'Benutzerliste';
-$LANG['user.list.records'] = '$1 zu $2 von $3';
 $LANG['user.loginshell'] = 'Shell';
 $LANG['user.mail'] = 'Primäre Email-Adresse';
 $LANG['user.mailalternateaddress'] = 'Sekundäre Email-Adresse(n)';
+$LANG['user.mailforwardingaddress'] = 'Forward Mail To';
 $LANG['user.mailhost'] = 'Email-Server';
 $LANG['user.mailquota'] = 'Kontingent';
 $LANG['user.mailquota.desc'] = 'Leer lassen für unbegrenzt';
@@ -190,7 +257,7 @@ $LANG['user.mobile'] = 'Handynummer';
 $LANG['user.name'] = 'Name';
 $LANG['user.norecords'] = 'Keine Benutzereinträge gefunden!';
 $LANG['user.nsrole'] = 'Rolle(n)';
-$LANG['user.nsroledn'] = $LANG['user.nsrole'];
+$LANG['user.nsroledn'] = 'Role(s)';
 $LANG['user.other'] = 'Andere';
 $LANG['user.o'] = 'Organisation';
 $LANG['user.org'] = 'Organisation';
@@ -205,7 +272,7 @@ $LANG['user.postbox'] = 'Briefkasten';
 $LANG['user.postcode'] = 'Postleitzahl';
 $LANG['user.preferredlanguage'] = 'Muttersprache';
 $LANG['user.room'] = 'Zimmernummer';
-$LANG['user.sn'] = 'Vorname';
+$LANG['user.sn'] = 'Nachname';
 $LANG['user.street'] = 'Straße';
 $LANG['user.system'] = 'System';
 $LANG['user.telephonenumber'] = 'Telefonnummer';
@@ -217,3 +284,5 @@ $LANG['user.userpassword2'] = 'Passwort bestätigen';
 $LANG['user.uidnumber'] = 'Benutzer ID Nummer';
 
 $LANG['welcome'] = 'Willkommen bei der Kolab Groupware Server-Wartung';
+
+$LANG['yes'] = 'yes';
diff --git a/lib/locale/de_DE.php b/lib/locale/de_DE.php
index 54d4b5e..50d5d32 100644
--- a/lib/locale/de_DE.php
+++ b/lib/locale/de_DE.php
@@ -7,25 +7,50 @@ $LANG['about.support'] = 'Professioneller Support ist erhältlich bei <a href="h
 $LANG['about.technology'] = 'Technologie';
 $LANG['about.warranty'] = 'Sie beinhaltet <b>keinerlei Garantie</b> und wird normalerweise ausschließlich mit eigener Unterstützung betrieben. Weitere Hilfe und Informationen finden Sie auf Webseite <a href="http://kolab.org">web site</a> und im <a href="http://wiki.kolab.org">Wiki</a> der Community.';
 
+$LANG['add'] = 'Add';
+
+$LANG['attribute.add'] = 'Add attribute';
+$LANG['attribute.static'] = 'Static value';
+$LANG['attribute.name'] = 'Attribute';
+$LANG['attribute.optional'] = 'Optional';
+$LANG['attribute.maxcount'] = 'Max. count';
+$LANG['attribute.readonly'] = 'Read-only';
+$LANG['attribute.type'] = 'Field type';
+$LANG['attribute.value'] = 'Value';
+$LANG['attribute.value.auto'] = 'Generated';
+$LANG['attribute.value.auto-readonly'] = 'Generated (read-only)';
+$LANG['attribute.value.normal'] = 'Normal';
+$LANG['attribute.value.static'] = 'Static';
+$LANG['attribute.options'] = 'Options';
+$LANG['attribute.key.invalid'] = 'Type key contains forbidden characters!';
+$LANG['attribute.required.error'] = 'Required attributes missing in attributes list ($1)!';
+
+$LANG['button.cancel'] = 'Cancel';
+$LANG['button.delete'] = 'Delete';
+$LANG['button.save'] = 'Save';
+$LANG['button.submit'] = 'Submit';
+
 $LANG['creatorsname'] = 'Erzeugt von';
 $LANG['days'] = 'Tagen erneut senden';
 $LANG['debug'] = 'Debug info';
-$LANG['delete.button'] = 'Löschen';
+$LANG['delete'] = 'Delete';
 $LANG['deleting'] = 'Daten werden gelöscht...';
 
 $LANG['domain.add'] = 'Domäne hinzufügen';
 $LANG['domain.add.success'] = 'Domäne hinzugefügt';
 $LANG['domain.associateddomain'] = 'Domänenname(n)';
+$LANG['domain.delete.success'] = 'Domain deleted successfully.';
 $LANG['domain.edit'] = 'Domain bearbeiten';
 $LANG['domain.edit.success'] = 'Domain aktualisiert';
 $LANG['domain.inetdomainbasedn'] = 'Custom Root DN(s)';
 $LANG['domain.list'] = 'Liste der Domänen';
-$LANG['domain.list.records'] = '$1 bis $2 von $3';
+$LANG['domain.norecords'] = 'No domain records found!';
 $LANG['domain.o'] = 'Oganisation';
 $LANG['domain.other'] = 'Sonstiges';
 $LANG['domain.system'] = 'System';
 $LANG['domain.type_id'] = 'Standarddomäne';
 
+$LANG['edit'] = 'Edit';
 $LANG['error'] = 'Fehler';
 
 $LANG['form.required.empty'] = 'Einige erforderliche Felder sind leer!';
@@ -38,9 +63,9 @@ $LANG['group.delete.success'] = 'Gruppe erfolgreich gelöscht.';
 $LANG['group.edit.success'] = 'Gruppe erfolgreich bearbeitet.';
 $LANG['group.gidnumber'] = 'Primäre Gruppennummer';
 $LANG['group.list'] = 'Liste der Gruppen';
-$LANG['group.list.records'] = '$1 bis $2 von $3';
 $LANG['group.mail'] = 'Primäre E-Mail-Adresse';
 $LANG['group.member'] = 'Mitglied(er)';
+$LANG['group.memberurl'] = 'Members URL';
 $LANG['group.norecords'] = 'Keine Gruppeneinträge gefunden!';
 $LANG['group.other'] = 'Sonstiges';
 $LANG['group.system'] = 'System';
@@ -49,7 +74,24 @@ $LANG['group.uniquemember'] = ' Mitglieder';
 
 $LANG['info'] = 'Information';
 $LANG['internalerror'] = 'Interner Systemfehler';
+
+$LANG['ldap.one'] = 'one: all entries one level under the base DN';
+$LANG['ldap.sub'] = 'sub: whole subtree starting with the base DN';
+$LANG['ldap.base'] = 'base: base DN only';
+$LANG['ldap.basedn'] = 'Base DN';
+$LANG['ldap.host'] = 'LDAP Server';
+$LANG['ldap.conditions'] = 'Conditions';
+$LANG['ldap.scope'] = 'Scope';
+$LANG['ldap.filter_any'] = 'is non-empty';
+$LANG['ldap.filter_both'] = 'contains';
+$LANG['ldap.filter_prefix'] = 'starts with';
+$LANG['ldap.filter_suffix'] = 'ends with';
+$LANG['ldap.filter_exact'] = 'is equal to';
+
+$LANG['list.records'] = '$1 to $2 of $3';
+
 $LANG['loading'] = 'Laden...';
+$LANG['logout'] = 'Logout';
 $LANG['login.username'] = 'Benutzername:';
 $LANG['login.password'] = 'Passwort:';
 $LANG['login.domain'] = 'Domäne:';
@@ -78,8 +120,7 @@ $LANG['resource.edit'] = 'Ressource bearbeiten';
 $LANG['resource.edit.success'] = 'Ressource erfolgreich bearbeitet';
 $LANG['resource.kolabtargetfolder'] = 'Zielordner';
 $LANG['resource.list'] = 'Resource (Collection) List';
-$LANG['resource.list.records'] = '$1 bis $2 von $3';
-$LANG['resource.mail'] = 'Mail Address';
+$LANG['resource.mail'] = 'E-Mail Adresse';
 $LANG['resource.member'] = 'Collection Members';
 $LANG['resource.norecords'] = 'Keine Ressourceneinträge gefunden!';
 $LANG['resource.other'] = 'Sonstiges';
@@ -88,11 +129,12 @@ $LANG['resource.type_id'] = 'Ressourcentyp';
 $LANG['resource.uniquemember'] = 'Collection Members';
 
 $LANG['role.add'] = 'Rolle hinzufügen';
+$LANG['role.add.success'] = 'Role created successfully.';
 $LANG['role.cn'] = 'Role Name';
+$LANG['role.delete.success'] = 'Role deleted successfully.';
 $LANG['role.description'] = 'Role Description';
-$LANG['role.edit.success'] = 'Role edited successfully';
+$LANG['role.edit.success'] = 'Role updated successfully';
 $LANG['role.list'] = 'Liste der Rollen';
-$LANG['role.list.records'] = '$1 bis $2 von $3';
 $LANG['role.norecords'] = 'Keine Rolleneinträge gefunden!';
 $LANG['role.system'] = 'Details';
 $LANG['role.type_id'] = 'Role Type';
@@ -100,15 +142,17 @@ $LANG['role.type_id'] = 'Role Type';
 $LANG['saving'] = 'Daten werden gespeichert...';
 
 $LANG['search'] = 'Suchen';
-$LANG['search.criteria'] = 'Suchkritierien';
 $LANG['search.reset'] = 'Zurücksetzen';
+$LANG['search.criteria'] = 'Suchkritierien';
 $LANG['search.field'] = 'Feld:';
 $LANG['search.method'] = 'Methode:';
 $LANG['search.contains'] = 'enthält';
 $LANG['search.is'] = 'ist';
+$LANG['search.key'] = 'key';
 $LANG['search.prefix'] = 'beginnt mit';
 $LANG['search.name'] = 'Name';
 $LANG['search.email'] = 'E-Mail';
+$LANG['search.description'] = 'description';
 $LANG['search.uid'] = 'UID';
 $LANG['search.loading'] = 'Suchen...';
 $LANG['search.acchars'] = 'Für die Autovervollständigung sind mindestens $min Zeichen erforderlich';
@@ -121,37 +165,60 @@ $LANG['signup.headline'] = 'Sign Up for Hosted Kolab';
 $LANG['signup.intro1'] = 'Having an account on a Kolab server is way better than just simple Email. It also provides you with full groupware functionality including synchronization for shared addressbooks, calendars, tasks, journal and more.';
 $LANG['signup.intro2'] = 'You can sign up here now for an account.';
 $LANG['signup.formtitle'] = 'Sign Up';
-$LANG['signup.username'] = 'Username';
+$LANG['signup.username'] = 'Benutzername';
 $LANG['signup.domain'] = 'Domäne';
 $LANG['signup.mailalternateaddress'] = 'Current Email Address';
 $LANG['signup.futuremail'] = 'Future Email Address';
-$LANG['signup.company'] = 'Company';
+$LANG['signup.company'] = 'Firma';
 $LANG['signup.captcha'] = 'CAPTCHA';
-$LANG['signup.userexists'] = 'User already exists!';
+$LANG['signup.userexists'] = 'Benutzer besteht bereits';
 $LANG['signup.usercreated'] = '<h3>Your account has been successfully added!</h3>Congratulations, you now have your own Kolab account.';
-$LANG['signup.wronguid'] = 'Invalid Username!';
+$LANG['signup.wronguid'] = 'ungültiger Benutzername';
 $LANG['signup.wrongmailalternateaddress'] = 'Please provide a valid Email Address!';
 $LANG['signup.footer'] = 'This is a service offered by <a href="http://kolabsys.com">Kolab Systems</a>.';
 
-$LANG['submit.button'] = 'Absenden';
+$LANG['type.add'] = 'Add Object Type';
+$LANG['type.add.success'] = 'Object type created successfully.';
+$LANG['type.attributes'] = 'Attributes';
+$LANG['type.description'] = 'Description';
+$LANG['type.delete.success'] = 'Object type deleted successfully.';
+$LANG['type.domain'] = 'Domain';
+$LANG['type.edit.success'] = 'Object type updated successfully.';
+$LANG['type.group'] = 'Group';
+$LANG['type.list'] = 'Object Types List';
+$LANG['type.key'] = 'Key';
+$LANG['type.name'] = 'Name';
+$LANG['type.norecords'] = 'No object type records found!';
+$LANG['type.objectclass'] = 'Object class';
+$LANG['type.object_type'] = 'Object type';
+$LANG['type.properties'] = 'Properties';
+$LANG['type.resource'] = 'Resource';
+$LANG['type.role'] = 'Role';
+$LANG['type.used_for'] = 'Hosted';
+$LANG['type.user'] = 'User';
 
 $LANG['user.add'] = 'Benutzer hinzufügen';
 $LANG['user.add.success'] = 'Benutzer erfolgreich angelegt.';
 $LANG['user.alias'] = 'Sekundäre E-Mail-Adresse(n)';
 $LANG['user.astaccountallowedcodec'] = 'Allowed codec(s)';
-$LANG['user.astaccountcallerid'] = 'Caller ID';
+$LANG['user.astaccountcallerid'] = 'Anruferkennung';
 $LANG['user.astaccountcontext'] = 'Account Context';
+$LANG['user.astaccountdefaultuser'] = 'Asterisk Account Default User';
 $LANG['user.astaccountdeny'] = 'Account deny';
 $LANG['user.astaccounthost'] = 'Asterisk Host';
+$LANG['user.astaccountmailbox'] = 'Mailbox';
 $LANG['user.astaccountnat'] = 'Account uses NAT';
 $LANG['user.astaccountname'] = 'Asterisk Account Name';
 $LANG['user.astaccountqualify'] = 'Account Qualify';
 $LANG['user.astaccountrealmedpassword'] = 'Realmed Account Password';
+$LANG['user.astaccountregistrationexten'] = 'Extension';
+$LANG['user.astaccountregistrationcontext'] = 'Registration Context';
 $LANG['user.astaccountsecret'] = 'Klartextpasswort';
 $LANG['user.astaccounttype'] = 'Kontotyp';
 $LANG['user.astcontext'] = 'Asterisk Context';
 $LANG['user.asterisk'] = 'Asterisk SIP';
 $LANG['user.astextension'] = 'Asterisk Extension';
+$LANG['user.astvoicemailpassword'] = 'Voicemail PIN Code';
 $LANG['user.c'] = 'Land';
 $LANG['user.city'] = 'Stadt';
 $LANG['user.cn'] = 'Common name';
@@ -179,10 +246,10 @@ $LANG['user.kolabhomeserver'] = 'E-Mail-Server';
 $LANG['user.kolabinvitationpolicy'] = 'Invitation Handling Policy';
 $LANG['user.l'] = 'Ort, Land';
 $LANG['user.list'] = 'Benutzerliste';
-$LANG['user.list.records'] = '$1 bis $2 von $3';
 $LANG['user.loginshell'] = 'Shell';
 $LANG['user.mail'] = 'Primäre E-Mail-Adresse';
 $LANG['user.mailalternateaddress'] = 'Sekundäre E-Mail-Adresse(n)';
+$LANG['user.mailforwardingaddress'] = 'Forward Mail To';
 $LANG['user.mailhost'] = 'E-Mail-Server';
 $LANG['user.mailquota'] = 'Quota';
 $LANG['user.mailquota.desc'] = 'Freilassen für unbegrenzten Platz';
@@ -190,7 +257,7 @@ $LANG['user.mobile'] = 'Mobile Phone Number';
 $LANG['user.name'] = 'Name';
 $LANG['user.norecords'] = 'Keine Benutzereinträge gefunden!';
 $LANG['user.nsrole'] = 'Rolle(n)';
-$LANG['user.nsroledn'] = $LANG['user.nsrole'];
+$LANG['user.nsroledn'] = 'Role(s)';
 $LANG['user.other'] = 'Sonstiges';
 $LANG['user.o'] = 'Organisation';
 $LANG['user.org'] = 'Organisation';
@@ -217,3 +284,5 @@ $LANG['user.userpassword2'] = 'Passwort bestätigen';
 $LANG['user.uidnumber'] = 'User ID number';
 
 $LANG['welcome'] = 'Willkommen bei Verwaltung der Kolab Server Groupware';
+
+$LANG['yes'] = 'yes';
diff --git a/lib/locale/en.php b/lib/locale/en.php
index fb002ab..a19869c 100644
--- a/lib/locale/en.php
+++ b/lib/locale/en.php
@@ -7,25 +7,50 @@ $LANG['about.support'] = 'Professional support is available from <a href="http:/
 $LANG['about.technology'] = 'Technology';
 $LANG['about.warranty'] = 'It comes with absolutely <b>no warranties</b> and is typically run entirely self supported. You can find help & information on the community <a href="http://kolab.org">web site</a> & <a href="http://wiki.kolab.org">wiki</a>.';
 
+$LANG['add'] = 'Add';
+
+$LANG['attribute.add'] = 'Add attribute';
+$LANG['attribute.static'] = 'Static value';
+$LANG['attribute.name'] = 'Attribute';
+$LANG['attribute.optional'] = 'Optional';
+$LANG['attribute.maxcount'] = 'Max. count';
+$LANG['attribute.readonly'] = 'Read-only';
+$LANG['attribute.type'] = 'Field type';
+$LANG['attribute.value'] = 'Value';
+$LANG['attribute.value.auto'] = 'Generated';
+$LANG['attribute.value.auto-readonly'] = 'Generated (read-only)';
+$LANG['attribute.value.normal'] = 'Normal';
+$LANG['attribute.value.static'] = 'Static';
+$LANG['attribute.options'] = 'Options';
+$LANG['attribute.key.invalid'] = 'Type key contains forbidden characters!';
+$LANG['attribute.required.error'] = 'Required attributes missing in attributes list ($1)!';
+
+$LANG['button.cancel'] = 'Cancel';
+$LANG['button.delete'] = 'Delete';
+$LANG['button.save'] = 'Save';
+$LANG['button.submit'] = 'Submit';
+
 $LANG['creatorsname'] = 'Created by';
 $LANG['days'] = 'days';
 $LANG['debug'] = 'Debug info';
-$LANG['delete.button'] = 'Delete';
+$LANG['delete'] = 'Delete';
 $LANG['deleting'] = 'Deleting data...';
 
 $LANG['domain.add'] = 'Add Domain';
-$LANG['domain.add.success'] = 'Added domain';
+$LANG['domain.add.success'] = 'Domain created successfully.';
 $LANG['domain.associateddomain'] = 'Domain name(s)';
+$LANG['domain.delete.success'] = 'Domain deleted successfully.';
 $LANG['domain.edit'] = 'Edit domain';
-$LANG['domain.edit.success'] = 'Domain updated';
+$LANG['domain.edit.success'] = 'Domain updated successfully.';
 $LANG['domain.inetdomainbasedn'] = 'Custom Root DN(s)';
 $LANG['domain.list'] = 'Domains List';
-$LANG['domain.list.records'] = '$1 to $2 of $3';
+$LANG['domain.norecords'] = 'No domain records found!';
 $LANG['domain.o'] = 'Organization';
 $LANG['domain.other'] = 'Other';
 $LANG['domain.system'] = 'System';
 $LANG['domain.type_id'] = 'Standard Domain';
 
+$LANG['edit'] = 'Edit';
 $LANG['error'] = 'Error';
 
 $LANG['form.required.empty'] = 'Some of the required fields are empty!';
@@ -38,9 +63,9 @@ $LANG['group.delete.success'] = 'Group deleted successfully.';
 $LANG['group.edit.success'] = 'Group updated successfully.';
 $LANG['group.gidnumber'] = 'Primary group number';
 $LANG['group.list'] = 'Groups List';
-$LANG['group.list.records'] = '$1 to $2 of $3';
 $LANG['group.mail'] = 'Primary Email Address';
 $LANG['group.member'] = 'Member(s)';
+$LANG['group.memberurl'] = 'Members URL';
 $LANG['group.norecords'] = 'No group records found!';
 $LANG['group.other'] = 'Other';
 $LANG['group.system'] = 'System';
@@ -49,7 +74,24 @@ $LANG['group.uniquemember'] = 'Members';
 
 $LANG['info'] = 'Information';
 $LANG['internalerror'] = 'Internal system error!';
+
+$LANG['ldap.one'] = 'one: all entries one level under the base DN';
+$LANG['ldap.sub'] = 'sub: whole subtree starting with the base DN';
+$LANG['ldap.base'] = 'base: base DN only';
+$LANG['ldap.basedn'] = 'Base DN';
+$LANG['ldap.host'] = 'LDAP Server';
+$LANG['ldap.conditions'] = 'Conditions';
+$LANG['ldap.scope'] = 'Scope';
+$LANG['ldap.filter_any'] = 'is non-empty';
+$LANG['ldap.filter_both'] = 'contains';
+$LANG['ldap.filter_prefix'] = 'starts with';
+$LANG['ldap.filter_suffix'] = 'ends with';
+$LANG['ldap.filter_exact'] = 'is equal to';
+
+$LANG['list.records'] = '$1 to $2 of $3';
+
 $LANG['loading'] = 'Loading...';
+$LANG['logout'] = 'Logout';
 $LANG['login.username'] = 'Username:';
 $LANG['login.password'] = 'Password:';
 $LANG['login.domain'] = 'Domain:';
@@ -70,29 +112,29 @@ $LANG['password.generate'] = 'Generate password';
 $LANG['reqtime'] = 'Request time: $1 sec.';
 
 $LANG['resource.add'] = 'Add Resource';
-$LANG['resource.add.success'] = 'Added Resource';
+$LANG['resource.add.success'] = 'Resource created successfully.';
 $LANG['resource.cn'] = 'Name';
 $LANG['resource.delete'] = 'Delete Resource';
-$LANG['resource.delete.success'] = 'Successfully deleted Resource';
+$LANG['resource.delete.success'] = 'Resource deleted successfully.';
 $LANG['resource.edit'] = 'Edit Resource';
-$LANG['resource.edit.success'] = 'Successfully updated Resource';
+$LANG['resource.edit.success'] = 'Resource updated successfully.';
 $LANG['resource.kolabtargetfolder'] = 'Target Folder';
 $LANG['resource.list'] = 'Resource (Collection) List';
-$LANG['resource.list.records'] = '$1 to $2 of $3';
 $LANG['resource.mail'] = 'Mail Address';
 $LANG['resource.member'] = 'Collection Members';
-$LANG['resource.norecords'] = 'No resource record(s) found!';
+$LANG['resource.norecords'] = 'No resource records found!';
 $LANG['resource.other'] = 'Other';
 $LANG['resource.system'] = 'System';
 $LANG['resource.type_id'] = 'Resource Type';
 $LANG['resource.uniquemember'] = 'Collection Members';
 
 $LANG['role.add'] = 'Add Role';
+$LANG['role.add.success'] = 'Role created successfully.';
 $LANG['role.cn'] = 'Role Name';
+$LANG['role.delete.success'] = 'Role deleted successfully.';
 $LANG['role.description'] = 'Role Description';
 $LANG['role.edit.success'] = 'Role updated successfully';
 $LANG['role.list'] = 'Role List';
-$LANG['role.list.records'] = '$1 to $2 of $3';
 $LANG['role.norecords'] = 'No role records found!';
 $LANG['role.system'] = 'Details';
 $LANG['role.type_id'] = 'Role Type';
@@ -100,15 +142,17 @@ $LANG['role.type_id'] = 'Role Type';
 $LANG['saving'] = 'Saving data...';
 
 $LANG['search'] = 'Search';
-$LANG['search.criteria'] = 'Search criteria';
 $LANG['search.reset'] = 'Reset';
+$LANG['search.criteria'] = 'Search criteria';
 $LANG['search.field'] = 'Field:';
 $LANG['search.method'] = 'Method:';
 $LANG['search.contains'] = 'contains';
 $LANG['search.is'] = 'is';
+$LANG['search.key'] = 'key';
 $LANG['search.prefix'] = 'begins with';
 $LANG['search.name'] = 'name';
 $LANG['search.email'] = 'email';
+$LANG['search.description'] = 'description';
 $LANG['search.uid'] = 'UID';
 $LANG['search.loading'] = 'Searching...';
 $LANG['search.acchars'] = 'At least $min characters required for autocompletion';
@@ -133,7 +177,25 @@ $LANG['signup.wronguid'] = 'Invalid Username!';
 $LANG['signup.wrongmailalternateaddress'] = 'Please provide a valid Email Address!';
 $LANG['signup.footer'] = 'This is a service offered by <a href="http://kolabsys.com">Kolab Systems</a>.';
 
-$LANG['submit.button'] = 'Submit';
+$LANG['type.add'] = 'Add Object Type';
+$LANG['type.add.success'] = 'Object type created successfully.';
+$LANG['type.attributes'] = 'Attributes';
+$LANG['type.description'] = 'Description';
+$LANG['type.delete.success'] = 'Object type deleted successfully.';
+$LANG['type.domain'] = 'Domain';
+$LANG['type.edit.success'] = 'Object type updated successfully.';
+$LANG['type.group'] = 'Group';
+$LANG['type.list'] = 'Object Types List';
+$LANG['type.key'] = 'Key';
+$LANG['type.name'] = 'Name';
+$LANG['type.norecords'] = 'No object type records found!';
+$LANG['type.objectclass'] = 'Object class';
+$LANG['type.object_type'] = 'Object type';
+$LANG['type.properties'] = 'Properties';
+$LANG['type.resource'] = 'Resource';
+$LANG['type.role'] = 'Role';
+$LANG['type.used_for'] = 'Hosted';
+$LANG['type.user'] = 'User';
 
 $LANG['user.add'] = 'Add User';
 $LANG['user.add.success'] = 'User created successfully.';
@@ -141,17 +203,22 @@ $LANG['user.alias'] = 'Secondary Email Address(es)';
 $LANG['user.astaccountallowedcodec'] = 'Allowed codec(s)';
 $LANG['user.astaccountcallerid'] = 'Caller ID';
 $LANG['user.astaccountcontext'] = 'Account Context';
+$LANG['user.astaccountdefaultuser'] = 'Asterisk Account Default User';
 $LANG['user.astaccountdeny'] = 'Account deny';
 $LANG['user.astaccounthost'] = 'Asterisk Host';
+$LANG['user.astaccountmailbox'] = 'Mailbox';
 $LANG['user.astaccountnat'] = 'Account uses NAT';
 $LANG['user.astaccountname'] = 'Asterisk Account Name';
 $LANG['user.astaccountqualify'] = 'Account Qualify';
 $LANG['user.astaccountrealmedpassword'] = 'Realmed Account Password';
+$LANG['user.astaccountregistrationexten'] = 'Extension';
+$LANG['user.astaccountregistrationcontext'] = 'Registration Context';
 $LANG['user.astaccountsecret'] = 'Plaintext Password';
 $LANG['user.astaccounttype'] = 'Account Type';
 $LANG['user.astcontext'] = 'Asterisk Context';
 $LANG['user.asterisk'] = 'Asterisk SIP';
 $LANG['user.astextension'] = 'Asterisk Extension';
+$LANG['user.astvoicemailpassword'] = 'Voicemail PIN Code';
 $LANG['user.c'] = 'Country';
 $LANG['user.city'] = 'City';
 $LANG['user.cn'] = 'Common name';
@@ -179,10 +246,10 @@ $LANG['user.kolabhomeserver'] = 'Email Server';
 $LANG['user.kolabinvitationpolicy'] = 'Invitation Handling Policy';
 $LANG['user.l'] = 'City, Region';
 $LANG['user.list'] = 'Users List';
-$LANG['user.list.records'] = '$1 to $2 of $3';
 $LANG['user.loginshell'] = 'Shell';
 $LANG['user.mail'] = 'Primary Email Address';
 $LANG['user.mailalternateaddress'] = 'External Email Address(es)';
+$LANG['user.mailforwardingaddress'] = 'Forward Mail To';
 $LANG['user.mailhost'] = 'Email Server';
 $LANG['user.mailquota'] = 'Quota';
 $LANG['user.mailquota.desc'] = 'Leave blank for unlimited';
@@ -190,7 +257,7 @@ $LANG['user.mobile'] = 'Mobile Phone Number';
 $LANG['user.name'] = 'Name';
 $LANG['user.norecords'] = 'No user records found!';
 $LANG['user.nsrole'] = 'Role(s)';
-$LANG['user.nsroledn'] = $LANG['user.nsrole'];
+$LANG['user.nsroledn'] = 'Role(s)';
 $LANG['user.other'] = 'Other';
 $LANG['user.o'] = 'Organization';
 $LANG['user.org'] = 'Organization';
@@ -217,3 +284,5 @@ $LANG['user.userpassword2'] = 'Confirm password';
 $LANG['user.uidnumber'] = 'User ID number';
 
 $LANG['welcome'] = 'Welcome to the Kolab Groupware Server Maintenance';
+
+$LANG['yes'] = 'yes';
diff --git a/lib/locale/es.php b/lib/locale/es.php
index ad8f349..c57ffc3 100644
--- a/lib/locale/es.php
+++ b/lib/locale/es.php
@@ -7,25 +7,50 @@ $LANG['about.support'] = 'Professional support is available from <a href="http:/
 $LANG['about.technology'] = 'Tecnología';
 $LANG['about.warranty'] = 'Viene absolutamente <b>sin ninguna garantia</b> y normalmente se ejecuta sin ningun tipo de soporte. Puedes obetner ayuda y más información en el <a href="http://kolab.org">sitio web</a> de la comunidad y en el <a href="http://wiki.kolab.org">wiki</a>.';
 
+$LANG['add'] = 'Add';
+
+$LANG['attribute.add'] = 'Add attribute';
+$LANG['attribute.static'] = 'Static value';
+$LANG['attribute.name'] = 'Attribute';
+$LANG['attribute.optional'] = 'Optional';
+$LANG['attribute.maxcount'] = 'Max. count';
+$LANG['attribute.readonly'] = 'Read-only';
+$LANG['attribute.type'] = 'Field type';
+$LANG['attribute.value'] = 'Value';
+$LANG['attribute.value.auto'] = 'Generated';
+$LANG['attribute.value.auto-readonly'] = 'Generated (read-only)';
+$LANG['attribute.value.normal'] = 'Normal';
+$LANG['attribute.value.static'] = 'Static';
+$LANG['attribute.options'] = 'Options';
+$LANG['attribute.key.invalid'] = 'Type key contains forbidden characters!';
+$LANG['attribute.required.error'] = 'Required attributes missing in attributes list ($1)!';
+
+$LANG['button.cancel'] = 'Cancel';
+$LANG['button.delete'] = 'Delete';
+$LANG['button.save'] = 'Save';
+$LANG['button.submit'] = 'Submit';
+
 $LANG['creatorsname'] = 'Creado por';
 $LANG['days'] = 'días';
 $LANG['debug'] = 'Información de depuración';
-$LANG['delete.button'] = 'Borrar';
+$LANG['delete'] = 'Delete';
 $LANG['deleting'] = 'Borrando datos...';
 
 $LANG['domain.add'] = 'Añadir dominio';
 $LANG['domain.add.success'] = 'Dominio añadido';
 $LANG['domain.associateddomain'] = 'Nombre(s) de dominio(s)';
+$LANG['domain.delete.success'] = 'Domain deleted successfully.';
 $LANG['domain.edit'] = 'Editar dominio';
 $LANG['domain.edit.success'] = 'Dominio actualizado';
 $LANG['domain.inetdomainbasedn'] = 'Custom Root DN(s)';
 $LANG['domain.list'] = 'Lista de dominios';
-$LANG['domain.list.records'] = '$1 to $2 of $3';
+$LANG['domain.norecords'] = 'No domain records found!';
 $LANG['domain.o'] = 'Organización';
 $LANG['domain.other'] = 'Otro';
 $LANG['domain.system'] = 'Sistema';
 $LANG['domain.type_id'] = 'Dominio estandar';
 
+$LANG['edit'] = 'Edit';
 $LANG['error'] = 'Error';
 
 $LANG['form.required.empty'] = '¡Alguno de los campos obligatorios está vacio!';
@@ -38,9 +63,9 @@ $LANG['group.delete.success'] = 'Grupo borrado correctamente.';
 $LANG['group.edit.success'] = 'Grupo editado correctamente.';
 $LANG['group.gidnumber'] = 'Número de grupo primario';
 $LANG['group.list'] = 'Lista de grupos';
-$LANG['group.list.records'] = '$1 to $2 of $3';
 $LANG['group.mail'] = 'Dirección primario de correo electrónico';
 $LANG['group.member'] = 'Miembros(s)';
+$LANG['group.memberurl'] = 'Members URL';
 $LANG['group.norecords'] = '¡No se han encontrado registro(s) de recursos¡';
 $LANG['group.other'] = 'Otro';
 $LANG['group.system'] = 'Sistema';
@@ -49,7 +74,24 @@ $LANG['group.uniquemember'] = 'Miembros';
 
 $LANG['info'] = 'Información';
 $LANG['internalerror'] = '¡Error interno del sistema!';
+
+$LANG['ldap.one'] = 'one: all entries one level under the base DN';
+$LANG['ldap.sub'] = 'sub: whole subtree starting with the base DN';
+$LANG['ldap.base'] = 'base: base DN only';
+$LANG['ldap.basedn'] = 'Base DN';
+$LANG['ldap.host'] = 'LDAP Server';
+$LANG['ldap.conditions'] = 'Conditions';
+$LANG['ldap.scope'] = 'Scope';
+$LANG['ldap.filter_any'] = 'is non-empty';
+$LANG['ldap.filter_both'] = 'contains';
+$LANG['ldap.filter_prefix'] = 'starts with';
+$LANG['ldap.filter_suffix'] = 'ends with';
+$LANG['ldap.filter_exact'] = 'is equal to';
+
+$LANG['list.records'] = '$1 to $2 of $3';
+
 $LANG['loading'] = 'Cargando...';
+$LANG['logout'] = 'Logout';
 $LANG['login.username'] = 'Nombre de usuario:';
 $LANG['login.password'] = 'Contraseña:';
 $LANG['login.domain'] = 'Dominio:';
@@ -78,7 +120,6 @@ $LANG['resource.edit'] = 'Editar recurso';
 $LANG['resource.edit.success'] = 'Successfully updated Resource';
 $LANG['resource.kolabtargetfolder'] = 'Carpeta de destino';
 $LANG['resource.list'] = 'Lista de recursos';
-$LANG['resource.list.records'] = '$1 to $2 of $3';
 $LANG['resource.mail'] = 'Correo electrónico';
 $LANG['resource.member'] = 'Miembros de la colección';
 $LANG['resource.norecords'] = '¡No se ha encontrado registro(s) de recursos¡';
@@ -88,11 +129,12 @@ $LANG['resource.type_id'] = 'Tipo de recurso';
 $LANG['resource.uniquemember'] = 'Miembros de la colección';
 
 $LANG['role.add'] = 'Añadir role';
+$LANG['role.add.success'] = 'Role created successfully.';
 $LANG['role.cn'] = 'Role Name';
+$LANG['role.delete.success'] = 'Role deleted successfully.';
 $LANG['role.description'] = 'Role Description';
-$LANG['role.edit.success'] = 'Role edited successfully';
+$LANG['role.edit.success'] = 'Role updated successfully';
 $LANG['role.list'] = 'Lista de roles';
-$LANG['role.list.records'] = '$1 to $2 of $3';
 $LANG['role.norecords'] = '¡No se han encontrado registros del rol!';
 $LANG['role.system'] = 'Details';
 $LANG['role.type_id'] = 'Role Type';
@@ -100,15 +142,17 @@ $LANG['role.type_id'] = 'Role Type';
 $LANG['saving'] = 'Guardando datos...';
 
 $LANG['search'] = 'Buscar';
-$LANG['search.criteria'] = 'Criterio de búsqueda';
 $LANG['search.reset'] = 'Restablecer';
+$LANG['search.criteria'] = 'Criterio de búsqueda';
 $LANG['search.field'] = 'Campo:';
 $LANG['search.method'] = 'Método:';
 $LANG['search.contains'] = 'contiene';
 $LANG['search.is'] = 'es';
+$LANG['search.key'] = 'key';
 $LANG['search.prefix'] = 'comienza por';
 $LANG['search.name'] = 'nombre';
 $LANG['search.email'] = 'correo electrónico';
+$LANG['search.description'] = 'description';
 $LANG['search.uid'] = 'ID Usuario';
 $LANG['search.loading'] = 'Buscando...';
 $LANG['search.acchars'] = 'Son necesarios $min caracteres para el autocompletado';
@@ -129,11 +173,29 @@ $LANG['signup.company'] = 'Company';
 $LANG['signup.captcha'] = 'CAPTCHA';
 $LANG['signup.userexists'] = 'User already exists!';
 $LANG['signup.usercreated'] = '<h3>Your account has been successfully added!</h3>Congratulations, you now have your own Kolab account.';
-$LANG['signup.wronguid'] = 'Invalid Username!';
-$LANG['signup.wrongmailalternateaddress'] = 'Please provide a valid Email Address!';
+$LANG['signup.wronguid'] = 'Usuario Invalido';
+$LANG['signup.wrongmailalternateaddress'] = '¡Introduzca una dirección de email correcta, por favor!';
 $LANG['signup.footer'] = 'This is a service offered by <a href="http://kolabsys.com">Kolab Systems</a>.';
 
-$LANG['submit.button'] = 'Enviar';
+$LANG['type.add'] = 'Add Object Type';
+$LANG['type.add.success'] = 'Object type created successfully.';
+$LANG['type.attributes'] = 'Attributes';
+$LANG['type.description'] = 'Description';
+$LANG['type.delete.success'] = 'Object type deleted successfully.';
+$LANG['type.domain'] = 'Domain';
+$LANG['type.edit.success'] = 'Object type updated successfully.';
+$LANG['type.group'] = 'Group';
+$LANG['type.list'] = 'Object Types List';
+$LANG['type.key'] = 'Key';
+$LANG['type.name'] = 'Name';
+$LANG['type.norecords'] = 'No object type records found!';
+$LANG['type.objectclass'] = 'Object class';
+$LANG['type.object_type'] = 'Object type';
+$LANG['type.properties'] = 'Properties';
+$LANG['type.resource'] = 'Resource';
+$LANG['type.role'] = 'Role';
+$LANG['type.used_for'] = 'Hosted';
+$LANG['type.user'] = 'User';
 
 $LANG['user.add'] = 'Añadir usuario';
 $LANG['user.add.success'] = 'Usuario creado con exito.';
@@ -141,17 +203,22 @@ $LANG['user.alias'] = 'Dirección(es) de correo electrónico secundaria(s)';
 $LANG['user.astaccountallowedcodec'] = 'Codec(s) permitidos';
 $LANG['user.astaccountcallerid'] = 'Identificador de llamadas';
 $LANG['user.astaccountcontext'] = 'Contexto de la cuenta';
+$LANG['user.astaccountdefaultuser'] = 'Asterisk Account Default User';
 $LANG['user.astaccountdeny'] = 'Cuenta denegadaM';
 $LANG['user.astaccounthost'] = 'Servidor Asterisk';
+$LANG['user.astaccountmailbox'] = 'Mailbox';
 $LANG['user.astaccountnat'] = 'La cuenta utiliza NAT';
 $LANG['user.astaccountname'] = 'Nombre de la cuenta de Asterisk';
 $LANG['user.astaccountqualify'] = 'Calificación de la cuenta';
 $LANG['user.astaccountrealmedpassword'] = 'Realmed Account Password';
+$LANG['user.astaccountregistrationexten'] = 'Extension';
+$LANG['user.astaccountregistrationcontext'] = 'Registration Context';
 $LANG['user.astaccountsecret'] = 'Contraseña texto plano';
 $LANG['user.astaccounttype'] = 'Tipo de cuenta';
 $LANG['user.astcontext'] = 'Asterisk Context';
 $LANG['user.asterisk'] = 'Asterisk SIP';
 $LANG['user.astextension'] = 'Extensión de Asterisk ';
+$LANG['user.astvoicemailpassword'] = 'Voicemail PIN Code';
 $LANG['user.c'] = 'País';
 $LANG['user.city'] = 'Ciudad';
 $LANG['user.cn'] = 'Nombre común';
@@ -179,10 +246,10 @@ $LANG['user.kolabhomeserver'] = 'Servidor de correo electronico';
 $LANG['user.kolabinvitationpolicy'] = 'Política de administración de invitaciones';
 $LANG['user.l'] = 'Población, Provincia';
 $LANG['user.list'] = 'Listado de usuarios';
-$LANG['user.list.records'] = '$1 to $2 of $3';
 $LANG['user.loginshell'] = 'Shell';
 $LANG['user.mail'] = 'Dirección primaria de correo electrónico';
 $LANG['user.mailalternateaddress'] = 'Dirección secundaria de correo electrónico';
+$LANG['user.mailforwardingaddress'] = 'Forward Mail To';
 $LANG['user.mailhost'] = 'Servidor de correo electrónico';
 $LANG['user.mailquota'] = 'Cuota';
 $LANG['user.mailquota.desc'] = 'Dejar en blanco para ilimitado';
@@ -190,7 +257,7 @@ $LANG['user.mobile'] = 'Número de teléfono movil';
 $LANG['user.name'] = 'Nombre';
 $LANG['user.norecords'] = '¡No se han encontrado registros de usuario!';
 $LANG['user.nsrole'] = 'Rol(es)';
-$LANG['user.nsroledn'] = $LANG['user.nsrole'];
+$LANG['user.nsroledn'] = 'Role(s)';
 $LANG['user.other'] = 'Otros';
 $LANG['user.o'] = 'Organización';
 $LANG['user.org'] = 'Organización';
@@ -217,3 +284,5 @@ $LANG['user.userpassword2'] = 'Confirmar contraseña';
 $LANG['user.uidnumber'] = 'Número de ID de usuario';
 
 $LANG['welcome'] = 'Bienvenido a la interfaz de administración de Kolab';
+
+$LANG['yes'] = 'yes';
diff --git a/lib/locale/et_EE.php b/lib/locale/et_EE.php
index 31c1120..dde72b3 100644
--- a/lib/locale/et_EE.php
+++ b/lib/locale/et_EE.php
@@ -7,25 +7,50 @@ $LANG['about.support'] = 'Professional support is available from <a href="http:/
 $LANG['about.technology'] = 'Tehnoloogia';
 $LANG['about.warranty'] = 'It comes with absolutely <b>no warranties</b> and is typically run entirely self supported. You can find help & information on the community <a href="http://kolab.org">web site</a> & <a href="http://wiki.kolab.org">wiki</a>.';
 
+$LANG['add'] = 'Add';
+
+$LANG['attribute.add'] = 'Add attribute';
+$LANG['attribute.static'] = 'Static value';
+$LANG['attribute.name'] = 'Attribute';
+$LANG['attribute.optional'] = 'Optional';
+$LANG['attribute.maxcount'] = 'Max. count';
+$LANG['attribute.readonly'] = 'Read-only';
+$LANG['attribute.type'] = 'Field type';
+$LANG['attribute.value'] = 'Value';
+$LANG['attribute.value.auto'] = 'Generated';
+$LANG['attribute.value.auto-readonly'] = 'Generated (read-only)';
+$LANG['attribute.value.normal'] = 'Normal';
+$LANG['attribute.value.static'] = 'Static';
+$LANG['attribute.options'] = 'Options';
+$LANG['attribute.key.invalid'] = 'Type key contains forbidden characters!';
+$LANG['attribute.required.error'] = 'Required attributes missing in attributes list ($1)!';
+
+$LANG['button.cancel'] = 'Cancel';
+$LANG['button.delete'] = 'Delete';
+$LANG['button.save'] = 'Save';
+$LANG['button.submit'] = 'Submit';
+
 $LANG['creatorsname'] = 'Created by';
 $LANG['days'] = 'days';
 $LANG['debug'] = 'Debug info';
-$LANG['delete.button'] = 'Kustuta';
+$LANG['delete'] = 'Delete';
 $LANG['deleting'] = 'Deleting data...';
 
 $LANG['domain.add'] = 'Add Domain';
-$LANG['domain.add.success'] = 'Added domain';
+$LANG['domain.add.success'] = 'Domain created successfully.';
 $LANG['domain.associateddomain'] = 'Domain name(s)';
+$LANG['domain.delete.success'] = 'Domain deleted successfully.';
 $LANG['domain.edit'] = 'Edit domain';
-$LANG['domain.edit.success'] = 'Domain updated';
+$LANG['domain.edit.success'] = 'Domain updated successfully.';
 $LANG['domain.inetdomainbasedn'] = 'Custom Root DN(s)';
 $LANG['domain.list'] = 'Domains List';
-$LANG['domain.list.records'] = '$1 to $2 of $3';
+$LANG['domain.norecords'] = 'No domain records found!';
 $LANG['domain.o'] = 'Organization';
 $LANG['domain.other'] = 'Other';
 $LANG['domain.system'] = 'System';
 $LANG['domain.type_id'] = 'Standard Domain';
 
+$LANG['edit'] = 'Edit';
 $LANG['error'] = 'Error';
 
 $LANG['form.required.empty'] = 'Some of the required fields are empty!';
@@ -35,12 +60,12 @@ $LANG['group.add'] = 'Add Group';
 $LANG['group.add.success'] = 'Group created successfully.';
 $LANG['group.cn'] = 'Common name';
 $LANG['group.delete.success'] = 'Group deleted successfully.';
-$LANG['group.edit.success'] = 'Group edited successfully.';
+$LANG['group.edit.success'] = 'Group updated successfully.';
 $LANG['group.gidnumber'] = 'Primary group number';
 $LANG['group.list'] = 'Groups List';
-$LANG['group.list.records'] = '$1 to $2 of $3';
 $LANG['group.mail'] = 'Peamine e-posti aadress';
 $LANG['group.member'] = 'Member(s)';
+$LANG['group.memberurl'] = 'Members URL';
 $LANG['group.norecords'] = 'No group records found!';
 $LANG['group.other'] = 'Other';
 $LANG['group.system'] = 'System';
@@ -49,7 +74,24 @@ $LANG['group.uniquemember'] = 'Liikmed';
 
 $LANG['info'] = 'Information';
 $LANG['internalerror'] = 'Internal system error!';
+
+$LANG['ldap.one'] = 'one: all entries one level under the base DN';
+$LANG['ldap.sub'] = 'sub: whole subtree starting with the base DN';
+$LANG['ldap.base'] = 'base: base DN only';
+$LANG['ldap.basedn'] = 'Base DN';
+$LANG['ldap.host'] = 'LDAP Server';
+$LANG['ldap.conditions'] = 'Conditions';
+$LANG['ldap.scope'] = 'Scope';
+$LANG['ldap.filter_any'] = 'is non-empty';
+$LANG['ldap.filter_both'] = 'contains';
+$LANG['ldap.filter_prefix'] = 'starts with';
+$LANG['ldap.filter_suffix'] = 'ends with';
+$LANG['ldap.filter_exact'] = 'is equal to';
+
+$LANG['list.records'] = '$1 to $2 of $3';
+
 $LANG['loading'] = 'Loading...';
+$LANG['logout'] = 'Logout';
 $LANG['login.username'] = 'Username:';
 $LANG['login.password'] = 'Password:';
 $LANG['login.domain'] = 'Domain:';
@@ -70,29 +112,29 @@ $LANG['password.generate'] = 'Generate password';
 $LANG['reqtime'] = 'Request time: $1 sec.';
 
 $LANG['resource.add'] = 'Add Resource';
-$LANG['resource.add.success'] = 'Added Resource';
+$LANG['resource.add.success'] = 'Resource created successfully.';
 $LANG['resource.cn'] = 'Nimi';
 $LANG['resource.delete'] = 'Delete Resource';
-$LANG['resource.delete.success'] = 'Successfully deleted Resource';
+$LANG['resource.delete.success'] = 'Resource deleted successfully.';
 $LANG['resource.edit'] = 'Edit Resource';
-$LANG['resource.edit.success'] = 'Successfully updated Resource';
+$LANG['resource.edit.success'] = 'Resource updated successfully.';
 $LANG['resource.kolabtargetfolder'] = 'Target Folder';
 $LANG['resource.list'] = 'Resource (Collection) List';
-$LANG['resource.list.records'] = '$1 to $2 of $3';
 $LANG['resource.mail'] = 'Mail Address';
 $LANG['resource.member'] = 'Collection Members';
-$LANG['resource.norecords'] = 'No resource record(s) found!';
+$LANG['resource.norecords'] = 'No resource records found!';
 $LANG['resource.other'] = 'Other';
 $LANG['resource.system'] = 'System';
 $LANG['resource.type_id'] = 'Resource Type';
 $LANG['resource.uniquemember'] = 'Collection Members';
 
 $LANG['role.add'] = 'Add Role';
+$LANG['role.add.success'] = 'Role created successfully.';
 $LANG['role.cn'] = 'Role Name';
+$LANG['role.delete.success'] = 'Role deleted successfully.';
 $LANG['role.description'] = 'Role Description';
-$LANG['role.edit.success'] = 'Role edited successfully';
+$LANG['role.edit.success'] = 'Role updated successfully';
 $LANG['role.list'] = 'Role List';
-$LANG['role.list.records'] = '$1 to $2 of $3';
 $LANG['role.norecords'] = 'No role records found!';
 $LANG['role.system'] = 'Details';
 $LANG['role.type_id'] = 'Role Type';
@@ -100,15 +142,17 @@ $LANG['role.type_id'] = 'Role Type';
 $LANG['saving'] = 'Saving data...';
 
 $LANG['search'] = 'Search';
-$LANG['search.criteria'] = 'Search criteria';
 $LANG['search.reset'] = 'Reset';
+$LANG['search.criteria'] = 'Search criteria';
 $LANG['search.field'] = 'Field:';
 $LANG['search.method'] = 'Method:';
 $LANG['search.contains'] = 'contains';
 $LANG['search.is'] = 'is';
+$LANG['search.key'] = 'key';
 $LANG['search.prefix'] = 'begins with';
 $LANG['search.name'] = 'name';
 $LANG['search.email'] = 'email';
+$LANG['search.description'] = 'description';
 $LANG['search.uid'] = 'UID';
 $LANG['search.loading'] = 'Searching...';
 $LANG['search.acchars'] = 'At least $min characters required for autocompletion';
@@ -133,7 +177,25 @@ $LANG['signup.wronguid'] = 'Invalid Username!';
 $LANG['signup.wrongmailalternateaddress'] = 'Please provide a valid Email Address!';
 $LANG['signup.footer'] = 'This is a service offered by <a href="http://kolabsys.com">Kolab Systems</a>.';
 
-$LANG['submit.button'] = 'Submit';
+$LANG['type.add'] = 'Add Object Type';
+$LANG['type.add.success'] = 'Object type created successfully.';
+$LANG['type.attributes'] = 'Attributes';
+$LANG['type.description'] = 'Description';
+$LANG['type.delete.success'] = 'Object type deleted successfully.';
+$LANG['type.domain'] = 'Domain';
+$LANG['type.edit.success'] = 'Object type updated successfully.';
+$LANG['type.group'] = 'Group';
+$LANG['type.list'] = 'Object Types List';
+$LANG['type.key'] = 'Key';
+$LANG['type.name'] = 'Name';
+$LANG['type.norecords'] = 'No object type records found!';
+$LANG['type.objectclass'] = 'Object class';
+$LANG['type.object_type'] = 'Object type';
+$LANG['type.properties'] = 'Properties';
+$LANG['type.resource'] = 'Resource';
+$LANG['type.role'] = 'Role';
+$LANG['type.used_for'] = 'Hosted';
+$LANG['type.user'] = 'User';
 
 $LANG['user.add'] = 'Add User';
 $LANG['user.add.success'] = 'User created successfully.';
@@ -141,17 +203,22 @@ $LANG['user.alias'] = 'Secondary Email Address(es)';
 $LANG['user.astaccountallowedcodec'] = 'Allowed codec(s)';
 $LANG['user.astaccountcallerid'] = 'Caller ID';
 $LANG['user.astaccountcontext'] = 'Account Context';
+$LANG['user.astaccountdefaultuser'] = 'Asterisk Account Default User';
 $LANG['user.astaccountdeny'] = 'Account deny';
 $LANG['user.astaccounthost'] = 'Asterisk Host';
+$LANG['user.astaccountmailbox'] = 'Mailbox';
 $LANG['user.astaccountnat'] = 'Account uses NAT';
 $LANG['user.astaccountname'] = 'Asterisk Account Name';
 $LANG['user.astaccountqualify'] = 'Account Qualify';
 $LANG['user.astaccountrealmedpassword'] = 'Realmed Account Password';
+$LANG['user.astaccountregistrationexten'] = 'Extension';
+$LANG['user.astaccountregistrationcontext'] = 'Registration Context';
 $LANG['user.astaccountsecret'] = 'Plaintext Password';
 $LANG['user.astaccounttype'] = 'Account Type';
 $LANG['user.astcontext'] = 'Asterisk Context';
 $LANG['user.asterisk'] = 'Asterisk SIP';
 $LANG['user.astextension'] = 'Asterisk Extension';
+$LANG['user.astvoicemailpassword'] = 'Voicemail PIN Code';
 $LANG['user.c'] = 'Riik';
 $LANG['user.city'] = 'Linn';
 $LANG['user.cn'] = 'Common name';
@@ -162,7 +229,7 @@ $LANG['user.country'] = 'Riik';
 $LANG['user.country.desc'] = '2 letter code from ISO 3166-1';
 $LANG['user.delete.success'] = 'User deleted successfully.';
 $LANG['user.displayname'] = 'Display name';
-$LANG['user.edit.success'] = 'User edited successfully.';
+$LANG['user.edit.success'] = 'User updated successfully.';
 $LANG['user.fax'] = 'Fax number';
 $LANG['user.fbinterval'] = 'Free-Busy interval';
 $LANG['user.fbinterval.desc'] = 'Leave blank for default (60 days)';
@@ -179,10 +246,10 @@ $LANG['user.kolabhomeserver'] = 'Email Server';
 $LANG['user.kolabinvitationpolicy'] = 'Invitation Handling Policy';
 $LANG['user.l'] = 'City, Region';
 $LANG['user.list'] = 'Users List';
-$LANG['user.list.records'] = '$1 to $2 of $3';
 $LANG['user.loginshell'] = 'Shell';
 $LANG['user.mail'] = 'Peamine e-posti aadress';
 $LANG['user.mailalternateaddress'] = 'External Email Address(es)';
+$LANG['user.mailforwardingaddress'] = 'Forward Mail To';
 $LANG['user.mailhost'] = 'Email Server';
 $LANG['user.mailquota'] = 'Quota';
 $LANG['user.mailquota.desc'] = 'Leave blank for unlimited';
@@ -190,7 +257,7 @@ $LANG['user.mobile'] = 'Mobile Phone Number';
 $LANG['user.name'] = 'Nimi';
 $LANG['user.norecords'] = 'No user records found!';
 $LANG['user.nsrole'] = 'Role(s)';
-$LANG['user.nsroledn'] = $LANG['user.nsrole'];
+$LANG['user.nsroledn'] = 'Role(s)';
 $LANG['user.other'] = 'Other';
 $LANG['user.o'] = 'Organization';
 $LANG['user.org'] = 'Organization';
@@ -217,3 +284,5 @@ $LANG['user.userpassword2'] = 'Confirm password';
 $LANG['user.uidnumber'] = 'User ID number';
 
 $LANG['welcome'] = 'Welcome to the Kolab Groupware Server Maintenance';
+
+$LANG['yes'] = 'yes';
diff --git a/lib/locale/ja.php b/lib/locale/ja.php
index 1440161..963bb25 100644
--- a/lib/locale/ja.php
+++ b/lib/locale/ja.php
@@ -7,25 +7,50 @@ $LANG['about.support'] = 'Professional support is available from <a href="http:/
 $LANG['about.technology'] = 'Technology';
 $LANG['about.warranty'] = 'It comes with absolutely <b>no warranties</b> and is typically run entirely self supported. You can find help & information on the community <a href="http://kolab.org">web site</a> & <a href="http://wiki.kolab.org">wiki</a>.';
 
+$LANG['add'] = 'Add';
+
+$LANG['attribute.add'] = 'Add attribute';
+$LANG['attribute.static'] = 'Static value';
+$LANG['attribute.name'] = 'Attribute';
+$LANG['attribute.optional'] = 'Optional';
+$LANG['attribute.maxcount'] = 'Max. count';
+$LANG['attribute.readonly'] = 'Read-only';
+$LANG['attribute.type'] = 'Field type';
+$LANG['attribute.value'] = 'Value';
+$LANG['attribute.value.auto'] = 'Generated';
+$LANG['attribute.value.auto-readonly'] = 'Generated (read-only)';
+$LANG['attribute.value.normal'] = 'Normal';
+$LANG['attribute.value.static'] = 'Static';
+$LANG['attribute.options'] = 'Options';
+$LANG['attribute.key.invalid'] = 'Type key contains forbidden characters!';
+$LANG['attribute.required.error'] = 'Required attributes missing in attributes list ($1)!';
+
+$LANG['button.cancel'] = 'Cancel';
+$LANG['button.delete'] = 'Delete';
+$LANG['button.save'] = 'Save';
+$LANG['button.submit'] = 'Submit';
+
 $LANG['creatorsname'] = 'Created by';
 $LANG['days'] = 'days';
 $LANG['debug'] = 'Debug info';
-$LANG['delete.button'] = 'Delete';
+$LANG['delete'] = 'Delete';
 $LANG['deleting'] = 'Deleting data...';
 
 $LANG['domain.add'] = 'Add Domain';
-$LANG['domain.add.success'] = 'Added domain';
+$LANG['domain.add.success'] = 'Domain created successfully.';
 $LANG['domain.associateddomain'] = 'Domain name(s)';
+$LANG['domain.delete.success'] = 'Domain deleted successfully.';
 $LANG['domain.edit'] = 'Edit domain';
-$LANG['domain.edit.success'] = 'Domain updated';
+$LANG['domain.edit.success'] = 'Domain updated successfully.';
 $LANG['domain.inetdomainbasedn'] = 'Custom Root DN(s)';
 $LANG['domain.list'] = 'Domains List';
-$LANG['domain.list.records'] = '$1 to $2 of $3';
+$LANG['domain.norecords'] = 'No domain records found!';
 $LANG['domain.o'] = 'Organization';
 $LANG['domain.other'] = 'Other';
 $LANG['domain.system'] = 'System';
 $LANG['domain.type_id'] = 'Standard Domain';
 
+$LANG['edit'] = 'Edit';
 $LANG['error'] = 'Error';
 
 $LANG['form.required.empty'] = 'Some of the required fields are empty!';
@@ -35,12 +60,12 @@ $LANG['group.add'] = 'Add Group';
 $LANG['group.add.success'] = 'Group created successfully.';
 $LANG['group.cn'] = 'Common name';
 $LANG['group.delete.success'] = 'Group deleted successfully.';
-$LANG['group.edit.success'] = 'Group edited successfully.';
+$LANG['group.edit.success'] = 'Group updated successfully.';
 $LANG['group.gidnumber'] = 'Primary group number';
 $LANG['group.list'] = 'Groups List';
-$LANG['group.list.records'] = '$1 to $2 of $3';
 $LANG['group.mail'] = 'Primary Email Address';
 $LANG['group.member'] = 'Member(s)';
+$LANG['group.memberurl'] = 'Members URL';
 $LANG['group.norecords'] = 'No group records found!';
 $LANG['group.other'] = 'Other';
 $LANG['group.system'] = 'System';
@@ -49,7 +74,24 @@ $LANG['group.uniquemember'] = 'Members';
 
 $LANG['info'] = 'Information';
 $LANG['internalerror'] = 'Internal system error!';
+
+$LANG['ldap.one'] = 'one: all entries one level under the base DN';
+$LANG['ldap.sub'] = 'sub: whole subtree starting with the base DN';
+$LANG['ldap.base'] = 'base: base DN only';
+$LANG['ldap.basedn'] = 'Base DN';
+$LANG['ldap.host'] = 'LDAP Server';
+$LANG['ldap.conditions'] = 'Conditions';
+$LANG['ldap.scope'] = 'Scope';
+$LANG['ldap.filter_any'] = 'is non-empty';
+$LANG['ldap.filter_both'] = 'contains';
+$LANG['ldap.filter_prefix'] = 'starts with';
+$LANG['ldap.filter_suffix'] = 'ends with';
+$LANG['ldap.filter_exact'] = 'is equal to';
+
+$LANG['list.records'] = '$1 to $2 of $3';
+
 $LANG['loading'] = 'Loading...';
+$LANG['logout'] = 'Logout';
 $LANG['login.username'] = 'Username:';
 $LANG['login.password'] = 'Password:';
 $LANG['login.domain'] = 'Domain:';
@@ -70,29 +112,29 @@ $LANG['password.generate'] = 'Generate password';
 $LANG['reqtime'] = 'Request time: $1 sec.';
 
 $LANG['resource.add'] = 'Add Resource';
-$LANG['resource.add.success'] = 'Added Resource';
+$LANG['resource.add.success'] = 'Resource created successfully.';
 $LANG['resource.cn'] = 'Name';
 $LANG['resource.delete'] = 'Delete Resource';
-$LANG['resource.delete.success'] = 'Successfully deleted Resource';
+$LANG['resource.delete.success'] = 'Resource deleted successfully.';
 $LANG['resource.edit'] = 'Edit Resource';
-$LANG['resource.edit.success'] = 'Successfully updated Resource';
+$LANG['resource.edit.success'] = 'Resource updated successfully.';
 $LANG['resource.kolabtargetfolder'] = 'Target Folder';
 $LANG['resource.list'] = 'Resource (Collection) List';
-$LANG['resource.list.records'] = '$1 to $2 of $3';
 $LANG['resource.mail'] = 'Mail Address';
 $LANG['resource.member'] = 'Collection Members';
-$LANG['resource.norecords'] = 'No resource record(s) found!';
+$LANG['resource.norecords'] = 'No resource records found!';
 $LANG['resource.other'] = 'Other';
 $LANG['resource.system'] = 'System';
 $LANG['resource.type_id'] = 'Resource Type';
 $LANG['resource.uniquemember'] = 'Collection Members';
 
 $LANG['role.add'] = 'Add Role';
+$LANG['role.add.success'] = 'Role created successfully.';
 $LANG['role.cn'] = 'Role Name';
+$LANG['role.delete.success'] = 'Role deleted successfully.';
 $LANG['role.description'] = 'Role Description';
-$LANG['role.edit.success'] = 'Role edited successfully';
+$LANG['role.edit.success'] = 'Role updated successfully';
 $LANG['role.list'] = 'Role List';
-$LANG['role.list.records'] = '$1 to $2 of $3';
 $LANG['role.norecords'] = 'No role records found!';
 $LANG['role.system'] = 'Details';
 $LANG['role.type_id'] = 'Role Type';
@@ -100,15 +142,17 @@ $LANG['role.type_id'] = 'Role Type';
 $LANG['saving'] = 'Saving data...';
 
 $LANG['search'] = 'Search';
-$LANG['search.criteria'] = 'Search criteria';
 $LANG['search.reset'] = 'Reset';
+$LANG['search.criteria'] = 'Search criteria';
 $LANG['search.field'] = 'Field:';
 $LANG['search.method'] = 'Method:';
 $LANG['search.contains'] = 'contains';
 $LANG['search.is'] = 'is';
+$LANG['search.key'] = 'key';
 $LANG['search.prefix'] = 'begins with';
 $LANG['search.name'] = 'name';
 $LANG['search.email'] = 'email';
+$LANG['search.description'] = 'description';
 $LANG['search.uid'] = 'UID';
 $LANG['search.loading'] = 'Searching...';
 $LANG['search.acchars'] = 'At least $min characters required for autocompletion';
@@ -133,7 +177,25 @@ $LANG['signup.wronguid'] = 'Invalid Username!';
 $LANG['signup.wrongmailalternateaddress'] = 'Please provide a valid Email Address!';
 $LANG['signup.footer'] = 'This is a service offered by <a href="http://kolabsys.com">Kolab Systems</a>.';
 
-$LANG['submit.button'] = 'Submit';
+$LANG['type.add'] = 'Add Object Type';
+$LANG['type.add.success'] = 'Object type created successfully.';
+$LANG['type.attributes'] = 'Attributes';
+$LANG['type.description'] = 'Description';
+$LANG['type.delete.success'] = 'Object type deleted successfully.';
+$LANG['type.domain'] = 'Domain';
+$LANG['type.edit.success'] = 'Object type updated successfully.';
+$LANG['type.group'] = 'Group';
+$LANG['type.list'] = 'Object Types List';
+$LANG['type.key'] = 'Key';
+$LANG['type.name'] = 'Name';
+$LANG['type.norecords'] = 'No object type records found!';
+$LANG['type.objectclass'] = 'Object class';
+$LANG['type.object_type'] = 'Object type';
+$LANG['type.properties'] = 'Properties';
+$LANG['type.resource'] = 'Resource';
+$LANG['type.role'] = 'Role';
+$LANG['type.used_for'] = 'Hosted';
+$LANG['type.user'] = 'User';
 
 $LANG['user.add'] = 'Add User';
 $LANG['user.add.success'] = 'User created successfully.';
@@ -141,17 +203,22 @@ $LANG['user.alias'] = 'Secondary Email Address(es)';
 $LANG['user.astaccountallowedcodec'] = 'Allowed codec(s)';
 $LANG['user.astaccountcallerid'] = 'Caller ID';
 $LANG['user.astaccountcontext'] = 'Account Context';
+$LANG['user.astaccountdefaultuser'] = 'Asterisk Account Default User';
 $LANG['user.astaccountdeny'] = 'Account deny';
 $LANG['user.astaccounthost'] = 'Asterisk Host';
+$LANG['user.astaccountmailbox'] = 'Mailbox';
 $LANG['user.astaccountnat'] = 'Account uses NAT';
 $LANG['user.astaccountname'] = 'Asterisk Account Name';
 $LANG['user.astaccountqualify'] = 'Account Qualify';
 $LANG['user.astaccountrealmedpassword'] = 'Realmed Account Password';
+$LANG['user.astaccountregistrationexten'] = 'Extension';
+$LANG['user.astaccountregistrationcontext'] = 'Registration Context';
 $LANG['user.astaccountsecret'] = 'Plaintext Password';
 $LANG['user.astaccounttype'] = 'Account Type';
 $LANG['user.astcontext'] = 'Asterisk Context';
 $LANG['user.asterisk'] = 'Asterisk SIP';
 $LANG['user.astextension'] = 'Asterisk Extension';
+$LANG['user.astvoicemailpassword'] = 'Voicemail PIN Code';
 $LANG['user.c'] = 'Country';
 $LANG['user.city'] = 'City';
 $LANG['user.cn'] = 'Common name';
@@ -162,7 +229,7 @@ $LANG['user.country'] = 'Country';
 $LANG['user.country.desc'] = '2 letter code from ISO 3166-1';
 $LANG['user.delete.success'] = 'User deleted successfully.';
 $LANG['user.displayname'] = 'Display name';
-$LANG['user.edit.success'] = 'User edited successfully.';
+$LANG['user.edit.success'] = 'User updated successfully.';
 $LANG['user.fax'] = 'Fax number';
 $LANG['user.fbinterval'] = 'Free-Busy interval';
 $LANG['user.fbinterval.desc'] = 'Leave blank for default (60 days)';
@@ -179,10 +246,10 @@ $LANG['user.kolabhomeserver'] = 'Email Server';
 $LANG['user.kolabinvitationpolicy'] = 'Invitation Handling Policy';
 $LANG['user.l'] = 'City, Region';
 $LANG['user.list'] = 'Users List';
-$LANG['user.list.records'] = '$1 to $2 of $3';
 $LANG['user.loginshell'] = 'Shell';
 $LANG['user.mail'] = 'Primary Email Address';
 $LANG['user.mailalternateaddress'] = 'External Email Address(es)';
+$LANG['user.mailforwardingaddress'] = 'Forward Mail To';
 $LANG['user.mailhost'] = 'Email Server';
 $LANG['user.mailquota'] = 'Quota';
 $LANG['user.mailquota.desc'] = 'Leave blank for unlimited';
@@ -190,7 +257,7 @@ $LANG['user.mobile'] = 'Mobile Phone Number';
 $LANG['user.name'] = 'Name';
 $LANG['user.norecords'] = 'No user records found!';
 $LANG['user.nsrole'] = 'Role(s)';
-$LANG['user.nsroledn'] = $LANG['user.nsrole'];
+$LANG['user.nsroledn'] = 'Role(s)';
 $LANG['user.other'] = 'Other';
 $LANG['user.o'] = 'Organization';
 $LANG['user.org'] = 'Organization';
@@ -217,3 +284,5 @@ $LANG['user.userpassword2'] = 'Confirm password';
 $LANG['user.uidnumber'] = 'User ID number';
 
 $LANG['welcome'] = 'Welcome to the Kolab Groupware Server Maintenance';
+
+$LANG['yes'] = 'yes';
diff --git a/lib/locale/nl.php b/lib/locale/nl.php
index 6b5d896..678d0ba 100644
--- a/lib/locale/nl.php
+++ b/lib/locale/nl.php
@@ -7,25 +7,50 @@ $LANG['about.support'] = 'Professionele ondersteuning is beschikbaar bij <a href
 $LANG['about.technology'] = 'Technologie';
 $LANG['about.warranty'] = 'Het komt <b>zonder enige garanties</b> en wordt typisch geheel zelf ondersteund. U kunt help & informatie vinden in de community <a href="http://kolab.org">web site</a> & <a href="http://wiki.kolab.org">wiki</a>.';
 
+$LANG['add'] = 'Add';
+
+$LANG['attribute.add'] = 'Add attribute';
+$LANG['attribute.static'] = 'Static value';
+$LANG['attribute.name'] = 'Attribute';
+$LANG['attribute.optional'] = 'Optional';
+$LANG['attribute.maxcount'] = 'Max. count';
+$LANG['attribute.readonly'] = 'Read-only';
+$LANG['attribute.type'] = 'Field type';
+$LANG['attribute.value'] = 'Value';
+$LANG['attribute.value.auto'] = 'Generated';
+$LANG['attribute.value.auto-readonly'] = 'Generated (read-only)';
+$LANG['attribute.value.normal'] = 'Normal';
+$LANG['attribute.value.static'] = 'Static';
+$LANG['attribute.options'] = 'Options';
+$LANG['attribute.key.invalid'] = 'Type key contains forbidden characters!';
+$LANG['attribute.required.error'] = 'Required attributes missing in attributes list ($1)!';
+
+$LANG['button.cancel'] = 'Cancel';
+$LANG['button.delete'] = 'Delete';
+$LANG['button.save'] = 'Save';
+$LANG['button.submit'] = 'Submit';
+
 $LANG['creatorsname'] = 'Gemaakt door';
 $LANG['days'] = 'dagen';
 $LANG['debug'] = 'Debug informatie';
-$LANG['delete.button'] = 'Verwijder';
+$LANG['delete'] = 'Delete';
 $LANG['deleting'] = 'Bezig data te verwijderen...';
 
 $LANG['domain.add'] = 'Domein Toevoegen';
 $LANG['domain.add.success'] = 'Domein Toegevoegd';
 $LANG['domain.associateddomain'] = 'Domein naam/namen';
+$LANG['domain.delete.success'] = 'Domain deleted successfully.';
 $LANG['domain.edit'] = 'Domein wijzigen';
 $LANG['domain.edit.success'] = 'Domein bijgewerkt';
 $LANG['domain.inetdomainbasedn'] = 'Custom Root DN(s)';
 $LANG['domain.list'] = 'Lijst van Domeinen';
-$LANG['domain.list.records'] = '$1 tot $2 van $3';
+$LANG['domain.norecords'] = 'No domain records found!';
 $LANG['domain.o'] = 'Organisatie';
 $LANG['domain.other'] = 'Overig';
 $LANG['domain.system'] = 'Systeem';
 $LANG['domain.type_id'] = 'Standaard domein';
 
+$LANG['edit'] = 'Edit';
 $LANG['error'] = 'Fout';
 
 $LANG['form.required.empty'] = 'Sommige van de vereiste velden zijn leeg!';
@@ -38,9 +63,9 @@ $LANG['group.delete.success'] = 'Groep succesvol verwijderd.';
 $LANG['group.edit.success'] = 'Groep succesvol gewijzigd.';
 $LANG['group.gidnumber'] = 'Primair groep nummer';
 $LANG['group.list'] = 'Lijst van Groepen';
-$LANG['group.list.records'] = '$1 tot $2 van $3';
 $LANG['group.mail'] = 'Primair Email Adres';
 $LANG['group.member'] = 'Leden';
+$LANG['group.memberurl'] = 'Members URL';
 $LANG['group.norecords'] = 'Geen groepen gevonden!';
 $LANG['group.other'] = 'Overig';
 $LANG['group.system'] = 'Systeem';
@@ -49,7 +74,24 @@ $LANG['group.uniquemember'] = 'Leden';
 
 $LANG['info'] = 'Informatie';
 $LANG['internalerror'] = 'Interne systeem-fout!';
+
+$LANG['ldap.one'] = 'one: all entries one level under the base DN';
+$LANG['ldap.sub'] = 'sub: whole subtree starting with the base DN';
+$LANG['ldap.base'] = 'base: base DN only';
+$LANG['ldap.basedn'] = 'Base DN';
+$LANG['ldap.host'] = 'LDAP Server';
+$LANG['ldap.conditions'] = 'Conditions';
+$LANG['ldap.scope'] = 'Scope';
+$LANG['ldap.filter_any'] = 'is non-empty';
+$LANG['ldap.filter_both'] = 'contains';
+$LANG['ldap.filter_prefix'] = 'starts with';
+$LANG['ldap.filter_suffix'] = 'ends with';
+$LANG['ldap.filter_exact'] = 'is equal to';
+
+$LANG['list.records'] = '$1 to $2 of $3';
+
 $LANG['loading'] = 'Bezig met laden...';
+$LANG['logout'] = 'Logout';
 $LANG['login.username'] = 'Gebruikersnaam:';
 $LANG['login.password'] = 'Wachtwoord:';
 $LANG['login.domain'] = 'Domein:';
@@ -78,7 +120,6 @@ $LANG['resource.edit'] = 'Middel Wijzigen';
 $LANG['resource.edit.success'] = 'Middel succesvol bijgewerkt';
 $LANG['resource.kolabtargetfolder'] = 'Doel Map';
 $LANG['resource.list'] = '(Collecties van) Middellen Lijst';
-$LANG['resource.list.records'] = '$1 tot $2 van $3';
 $LANG['resource.mail'] = 'Email Adres';
 $LANG['resource.member'] = 'Middelen in Collectie';
 $LANG['resource.norecords'] = 'Geen middelen gevonden!';
@@ -88,11 +129,12 @@ $LANG['resource.type_id'] = 'Type Middel';
 $LANG['resource.uniquemember'] = 'Middellen in Collectie';
 
 $LANG['role.add'] = 'Rol Toevoegen';
+$LANG['role.add.success'] = 'Role created successfully.';
 $LANG['role.cn'] = 'Rol naam';
+$LANG['role.delete.success'] = 'Role deleted successfully.';
 $LANG['role.description'] = 'Rol uitleg';
 $LANG['role.edit.success'] = 'Rol succesvol gewijzigd';
 $LANG['role.list'] = 'Rollen Lijst';
-$LANG['role.list.records'] = '$1 tot $2 van $3';
 $LANG['role.norecords'] = 'Geen rollen gevonden!';
 $LANG['role.system'] = 'Details';
 $LANG['role.type_id'] = 'Rol Type';
@@ -100,15 +142,17 @@ $LANG['role.type_id'] = 'Rol Type';
 $LANG['saving'] = 'Bezig data op te slaan...';
 
 $LANG['search'] = 'Zoeken';
-$LANG['search.criteria'] = 'Zoek criteria';
 $LANG['search.reset'] = 'Reset';
+$LANG['search.criteria'] = 'Zoek criteria';
 $LANG['search.field'] = 'Veld:';
 $LANG['search.method'] = 'Methode:';
 $LANG['search.contains'] = 'bevat';
 $LANG['search.is'] = 'is';
+$LANG['search.key'] = 'key';
 $LANG['search.prefix'] = 'start met';
 $LANG['search.name'] = 'naam';
 $LANG['search.email'] = 'email';
+$LANG['search.description'] = 'description';
 $LANG['search.uid'] = 'uid';
 $LANG['search.loading'] = 'Bezig met zoeken...';
 $LANG['search.acchars'] = 'Tenminste $min karakters nodig voor autocompletion';
@@ -133,7 +177,25 @@ $LANG['signup.wronguid'] = 'Invalid Username!';
 $LANG['signup.wrongmailalternateaddress'] = 'Please provide a valid Email Address!';
 $LANG['signup.footer'] = 'This is a service offered by <a href="http://kolabsys.com">Kolab Systems</a>.';
 
-$LANG['submit.button'] = 'Opslaan';
+$LANG['type.add'] = 'Add Object Type';
+$LANG['type.add.success'] = 'Object type created successfully.';
+$LANG['type.attributes'] = 'Attributes';
+$LANG['type.description'] = 'Description';
+$LANG['type.delete.success'] = 'Object type deleted successfully.';
+$LANG['type.domain'] = 'Domain';
+$LANG['type.edit.success'] = 'Object type updated successfully.';
+$LANG['type.group'] = 'Group';
+$LANG['type.list'] = 'Object Types List';
+$LANG['type.key'] = 'Key';
+$LANG['type.name'] = 'Name';
+$LANG['type.norecords'] = 'No object type records found!';
+$LANG['type.objectclass'] = 'Object class';
+$LANG['type.object_type'] = 'Object type';
+$LANG['type.properties'] = 'Properties';
+$LANG['type.resource'] = 'Resource';
+$LANG['type.role'] = 'Role';
+$LANG['type.used_for'] = 'Hosted';
+$LANG['type.user'] = 'User';
 
 $LANG['user.add'] = 'Gebruiker Toevoegen';
 $LANG['user.add.success'] = 'Gebruiker succesvol toegevoegd.';
@@ -141,17 +203,22 @@ $LANG['user.alias'] = 'Secundaire Email Adressen';
 $LANG['user.astaccountallowedcodec'] = 'Toegestane codec(s)';
 $LANG['user.astaccountcallerid'] = 'Caller ID';
 $LANG['user.astaccountcontext'] = 'Account Context';
+$LANG['user.astaccountdefaultuser'] = 'Asterisk Account Default User';
 $LANG['user.astaccountdeny'] = 'Account deny';
 $LANG['user.astaccounthost'] = 'Asterisk Host';
+$LANG['user.astaccountmailbox'] = 'Mailbox';
 $LANG['user.astaccountnat'] = 'Account maakt gebruik van NAT';
 $LANG['user.astaccountname'] = 'Asterisk Account Naam';
 $LANG['user.astaccountqualify'] = 'Account Qualify';
 $LANG['user.astaccountrealmedpassword'] = 'Realmed Account Password';
+$LANG['user.astaccountregistrationexten'] = 'Extension';
+$LANG['user.astaccountregistrationcontext'] = 'Registration Context';
 $LANG['user.astaccountsecret'] = 'Wachtwoord in gewone text';
 $LANG['user.astaccounttype'] = 'Type Account';
 $LANG['user.astcontext'] = 'Asterisk Context';
 $LANG['user.asterisk'] = 'Asterisk SIP';
 $LANG['user.astextension'] = 'Asterisk Extensie';
+$LANG['user.astvoicemailpassword'] = 'Voicemail PIN Code';
 $LANG['user.c'] = 'Land';
 $LANG['user.city'] = 'Stad';
 $LANG['user.cn'] = 'Volledige naam';
@@ -179,10 +246,10 @@ $LANG['user.kolabhomeserver'] = 'Email Server';
 $LANG['user.kolabinvitationpolicy'] = 'Policy voor afhandelen uitnodigingen';
 $LANG['user.l'] = 'Stad, Regio';
 $LANG['user.list'] = 'Gebruikerslijst';
-$LANG['user.list.records'] = '$1 tot $2 van $3';
 $LANG['user.loginshell'] = 'Shell';
 $LANG['user.mail'] = 'Primair Email Adres';
 $LANG['user.mailalternateaddress'] = 'Secundaire Email Adressen';
+$LANG['user.mailforwardingaddress'] = 'Forward Mail To';
 $LANG['user.mailhost'] = 'Email Server';
 $LANG['user.mailquota'] = 'Quota';
 $LANG['user.mailquota.desc'] = 'Laat leeg voor ongelimiteerd';
@@ -190,7 +257,7 @@ $LANG['user.mobile'] = 'Telefoonnummer (Mobiel)';
 $LANG['user.name'] = 'Naam';
 $LANG['user.norecords'] = 'Geen gebruikers gevonden!';
 $LANG['user.nsrole'] = 'Rollen';
-$LANG['user.nsroledn'] = $LANG['user.nsrole'];
+$LANG['user.nsroledn'] = 'Role(s)';
 $LANG['user.other'] = 'Overig';
 $LANG['user.o'] = 'Organizatie';
 $LANG['user.org'] = 'Organizatie';
@@ -217,3 +284,5 @@ $LANG['user.userpassword2'] = 'Wachtwoord bevestigen';
 $LANG['user.uidnumber'] = 'User ID nummer';
 
 $LANG['welcome'] = 'Welkom bij Kolab Groupware Server';
+
+$LANG['yes'] = 'yes';
diff --git a/lib/locale/pl_PL.php b/lib/locale/pl_PL.php
index 5e5f86a..395a907 100644
--- a/lib/locale/pl_PL.php
+++ b/lib/locale/pl_PL.php
@@ -7,25 +7,50 @@ $LANG['about.support'] = 'Profesjonalne wsparcie techniczne jest dostępne na st
 $LANG['about.technology'] = 'Technologia';
 $LANG['about.warranty'] = 'Na funkcjonowanie oprogramowanie nieudzielana jest <b>żadna gwarancja</b>. Wsparcie techniczne organizowane jest we własnym zakresie. Pomoc i niezbędne informacje można znaleźć na stronie <a href=http://kolab.org">Kolab</a> oraz <a href="http://wiki.kolab.org">wiki</a>';
 
+$LANG['add'] = 'Add';
+
+$LANG['attribute.add'] = 'Add attribute';
+$LANG['attribute.static'] = 'Static value';
+$LANG['attribute.name'] = 'Attribute';
+$LANG['attribute.optional'] = 'Optional';
+$LANG['attribute.maxcount'] = 'Max. count';
+$LANG['attribute.readonly'] = 'Read-only';
+$LANG['attribute.type'] = 'Field type';
+$LANG['attribute.value'] = 'Value';
+$LANG['attribute.value.auto'] = 'Generated';
+$LANG['attribute.value.auto-readonly'] = 'Generated (read-only)';
+$LANG['attribute.value.normal'] = 'Normal';
+$LANG['attribute.value.static'] = 'Static';
+$LANG['attribute.options'] = 'Options';
+$LANG['attribute.key.invalid'] = 'Type key contains forbidden characters!';
+$LANG['attribute.required.error'] = 'Required attributes missing in attributes list ($1)!';
+
+$LANG['button.cancel'] = 'Cancel';
+$LANG['button.delete'] = 'Delete';
+$LANG['button.save'] = 'Save';
+$LANG['button.submit'] = 'Submit';
+
 $LANG['creatorsname'] = 'Stworzony przez';
 $LANG['days'] = 'dni';
 $LANG['debug'] = 'Debug info';
-$LANG['delete.button'] = 'Usuń';
+$LANG['delete'] = 'Delete';
 $LANG['deleting'] = 'Usuwam dane...';
 
 $LANG['domain.add'] = 'Dodaj DomenÄ™';
 $LANG['domain.add.success'] = 'Dodana domena';
 $LANG['domain.associateddomain'] = 'Nazwy domen';
+$LANG['domain.delete.success'] = 'Domain deleted successfully.';
 $LANG['domain.edit'] = 'Edytuj domenÄ™';
 $LANG['domain.edit.success'] = 'Domena uaktualniona';
 $LANG['domain.inetdomainbasedn'] = 'Niestandardowy Root DN';
 $LANG['domain.list'] = 'Lista Domen';
-$LANG['domain.list.records'] = '$1 do $2 z $3';
+$LANG['domain.norecords'] = 'No domain records found!';
 $LANG['domain.o'] = 'Organizacja';
 $LANG['domain.other'] = 'Inne';
 $LANG['domain.system'] = 'System';
 $LANG['domain.type_id'] = 'Domena Standardowa';
 
+$LANG['edit'] = 'Edit';
 $LANG['error'] = 'BÅ‚Ä…d';
 
 $LANG['form.required.empty'] = 'Niektóre z wymaganych pół są puste!';
@@ -38,9 +63,9 @@ $LANG['group.delete.success'] = 'Grupa usunięta pomyślnie.';
 $LANG['group.edit.success'] = 'Grupa zmieniona pomyślnie.';
 $LANG['group.gidnumber'] = 'Numer grupy głównej';
 $LANG['group.list'] = 'Lista grup';
-$LANG['group.list.records'] = '$1 do $2 z $3';
 $LANG['group.mail'] = 'Główny Adres Email';
 $LANG['group.member'] = 'Członkowie';
+$LANG['group.memberurl'] = 'Members URL';
 $LANG['group.norecords'] = 'Nie znaleziono wpisów dla grupy!';
 $LANG['group.other'] = 'Inne';
 $LANG['group.system'] = 'System';
@@ -49,7 +74,24 @@ $LANG['group.uniquemember'] = 'Członkowie';
 
 $LANG['info'] = 'Informacje';
 $LANG['internalerror'] = 'Wewnętrzny błąd systemu!';
+
+$LANG['ldap.one'] = 'one: all entries one level under the base DN';
+$LANG['ldap.sub'] = 'sub: whole subtree starting with the base DN';
+$LANG['ldap.base'] = 'base: base DN only';
+$LANG['ldap.basedn'] = 'Base DN';
+$LANG['ldap.host'] = 'LDAP Server';
+$LANG['ldap.conditions'] = 'Conditions';
+$LANG['ldap.scope'] = 'Scope';
+$LANG['ldap.filter_any'] = 'is non-empty';
+$LANG['ldap.filter_both'] = 'contains';
+$LANG['ldap.filter_prefix'] = 'starts with';
+$LANG['ldap.filter_suffix'] = 'ends with';
+$LANG['ldap.filter_exact'] = 'is equal to';
+
+$LANG['list.records'] = '$1 to $2 of $3';
+
 $LANG['loading'] = 'WczytujÄ™...';
+$LANG['logout'] = 'Logout';
 $LANG['login.username'] = 'Nazwa użytkownika:';
 $LANG['login.password'] = 'Hasło:';
 $LANG['login.domain'] = 'Domena:';
@@ -78,7 +120,6 @@ $LANG['resource.edit'] = 'Edytuj zasób';
 $LANG['resource.edit.success'] = 'Pomyślnie zmieniono zasób';
 $LANG['resource.kolabtargetfolder'] = 'Folder docelowy';
 $LANG['resource.list'] = 'Lista (kolekcja) zasobów';
-$LANG['resource.list.records'] = '$1 do $2 z $3';
 $LANG['resource.mail'] = 'Adres pocztowy';
 $LANG['resource.member'] = 'Członkowie kolekcji';
 $LANG['resource.norecords'] = 'Nie znaleziono żadnych zasobów!';
@@ -88,11 +129,12 @@ $LANG['resource.type_id'] = 'Rodzaj zasobu';
 $LANG['resource.uniquemember'] = 'Członkowie kolekcji';
 
 $LANG['role.add'] = 'Dodaj rolÄ™';
+$LANG['role.add.success'] = 'Role created successfully.';
 $LANG['role.cn'] = 'Role Name';
+$LANG['role.delete.success'] = 'Role deleted successfully.';
 $LANG['role.description'] = 'Role Description';
-$LANG['role.edit.success'] = 'Role edited successfully';
+$LANG['role.edit.success'] = 'Role updated successfully';
 $LANG['role.list'] = 'Lista ról';
-$LANG['role.list.records'] = '$1 do $2 z $3';
 $LANG['role.norecords'] = 'Nie znaleziono żadnej roli';
 $LANG['role.system'] = 'Details';
 $LANG['role.type_id'] = 'Role Type';
@@ -100,15 +142,17 @@ $LANG['role.type_id'] = 'Role Type';
 $LANG['saving'] = 'ZapisujÄ™ dane...';
 
 $LANG['search'] = 'Znajdź';
-$LANG['search.criteria'] = 'Kryteria wyszukiwania';
 $LANG['search.reset'] = 'Wyczyść';
+$LANG['search.criteria'] = 'Kryteria wyszukiwania';
 $LANG['search.field'] = 'Pole:';
 $LANG['search.method'] = 'Metoda:';
 $LANG['search.contains'] = 'zawiera';
 $LANG['search.is'] = 'jest';
+$LANG['search.key'] = 'key';
 $LANG['search.prefix'] = 'zaczyna siÄ™ od';
 $LANG['search.name'] = 'nazwa';
 $LANG['search.email'] = 'email';
+$LANG['search.description'] = 'description';
 $LANG['search.uid'] = 'UID';
 $LANG['search.loading'] = 'WyszukujÄ™...';
 $LANG['search.acchars'] = 'Przynajmniej $min znaków wymaganych jest dla funkcji autouzupełniania';
@@ -133,7 +177,25 @@ $LANG['signup.wronguid'] = 'Invalid Username!';
 $LANG['signup.wrongmailalternateaddress'] = 'Please provide a valid Email Address!';
 $LANG['signup.footer'] = 'This is a service offered by <a href="http://kolabsys.com">Kolab Systems</a>.';
 
-$LANG['submit.button'] = 'Zatwierdź';
+$LANG['type.add'] = 'Add Object Type';
+$LANG['type.add.success'] = 'Object type created successfully.';
+$LANG['type.attributes'] = 'Attributes';
+$LANG['type.description'] = 'Description';
+$LANG['type.delete.success'] = 'Object type deleted successfully.';
+$LANG['type.domain'] = 'Domain';
+$LANG['type.edit.success'] = 'Object type updated successfully.';
+$LANG['type.group'] = 'Group';
+$LANG['type.list'] = 'Object Types List';
+$LANG['type.key'] = 'Key';
+$LANG['type.name'] = 'Name';
+$LANG['type.norecords'] = 'No object type records found!';
+$LANG['type.objectclass'] = 'Object class';
+$LANG['type.object_type'] = 'Object type';
+$LANG['type.properties'] = 'Properties';
+$LANG['type.resource'] = 'Resource';
+$LANG['type.role'] = 'Role';
+$LANG['type.used_for'] = 'Hosted';
+$LANG['type.user'] = 'User';
 
 $LANG['user.add'] = 'Dodaj użytkownika';
 $LANG['user.add.success'] = 'Użytkownik został pomyślnie dodany.';
@@ -141,17 +203,22 @@ $LANG['user.alias'] = 'Dodatkowe adresy email';
 $LANG['user.astaccountallowedcodec'] = 'Dozwolone kodeki';
 $LANG['user.astaccountcallerid'] = 'ID dzwoniÄ…cego';
 $LANG['user.astaccountcontext'] = 'Kontekst konta';
+$LANG['user.astaccountdefaultuser'] = 'Asterisk Account Default User';
 $LANG['user.astaccountdeny'] = 'Konto zabronione';
 $LANG['user.astaccounthost'] = 'Host Asteriska';
+$LANG['user.astaccountmailbox'] = 'Mailbox';
 $LANG['user.astaccountnat'] = 'Konto używa NAT';
 $LANG['user.astaccountname'] = 'Nazwa konta Asterisk';
 $LANG['user.astaccountqualify'] = 'Kwalifikacja konta';
 $LANG['user.astaccountrealmedpassword'] = 'Domenowe hasło konta';
+$LANG['user.astaccountregistrationexten'] = 'Extension';
+$LANG['user.astaccountregistrationcontext'] = 'Registration Context';
 $LANG['user.astaccountsecret'] = 'Hasło w czystym tekście';
 $LANG['user.astaccounttype'] = 'Typ konta';
 $LANG['user.astcontext'] = 'Kontekst Asteriska';
 $LANG['user.asterisk'] = 'Asterisk SIP';
 $LANG['user.astextension'] = 'Rozszerzenie Asteriska';
+$LANG['user.astvoicemailpassword'] = 'Voicemail PIN Code';
 $LANG['user.c'] = 'Kraj';
 $LANG['user.city'] = 'Miasto';
 $LANG['user.cn'] = 'Nazwa zwyczajowa';
@@ -179,10 +246,10 @@ $LANG['user.kolabhomeserver'] = 'Serwer email';
 $LANG['user.kolabinvitationpolicy'] = 'Polityka zarzÄ…dzania zaproszeniami';
 $LANG['user.l'] = 'Miasto, województwo';
 $LANG['user.list'] = 'Lista użytkowników';
-$LANG['user.list.records'] = '$1 do $2 z $3';
 $LANG['user.loginshell'] = 'Powłoka';
 $LANG['user.mail'] = 'Główny adres email';
 $LANG['user.mailalternateaddress'] = 'Dodatkowe adresy email';
+$LANG['user.mailforwardingaddress'] = 'Forward Mail To';
 $LANG['user.mailhost'] = 'Serwer email';
 $LANG['user.mailquota'] = 'Quota';
 $LANG['user.mailquota.desc'] = 'Pozostaw puste dla braku ograniczeń';
@@ -190,7 +257,7 @@ $LANG['user.mobile'] = 'Numer telefonu komórkowego';
 $LANG['user.name'] = 'Nazwa';
 $LANG['user.norecords'] = 'Nie znaleziono użytkowników';
 $LANG['user.nsrole'] = 'Role';
-$LANG['user.nsroledn'] = $LANG['user.nsrole'];
+$LANG['user.nsroledn'] = 'Role(s)';
 $LANG['user.other'] = 'Inne';
 $LANG['user.o'] = 'Organizacja';
 $LANG['user.org'] = 'Organizacja';
@@ -217,3 +284,5 @@ $LANG['user.userpassword2'] = 'Potwierdź hasło';
 $LANG['user.uidnumber'] = 'Numer ID użytkownika';
 
 $LANG['welcome'] = 'Witamy w panelu konserwacyjnym Serwera Kolab Groupware';
+
+$LANG['yes'] = 'yes';


commit 0626eaed37004a1bd545be3fd691d8a7f3664683
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Nov 23 15:23:02 2012 +0100

    Improve buttons.png sprite-image so it list icons vertically

diff --git a/public_html/skins/default/images/buttons.png b/public_html/skins/default/images/buttons.png
index 71ed051..75e966f 100644
Binary files a/public_html/skins/default/images/buttons.png and b/public_html/skins/default/images/buttons.png differ
diff --git a/public_html/skins/default/style.css b/public_html/skins/default/style.css
index aeb234a..6b30d90 100644
--- a/public_html/skins/default/style.css
+++ b/public_html/skins/default/style.css
@@ -257,7 +257,7 @@ td.label {
 }
 
 #topmenu .logout {
-  background: url(images/buttons.png) -100px -1px no-repeat;
+  background: url(images/buttons.png) -1px -100px no-repeat;
   padding-left: 20px;
   margin-right: 10px;
   color: white;
@@ -400,7 +400,7 @@ td.label {
   float: left;
   width: 18px;
   height: 18px;
-  background: url(images/buttons.png) 0 0 no-repeat;
+  background: url(images/buttons.png) -1px 0 no-repeat;
 }
 
 #search-reset:hover,
@@ -413,7 +413,7 @@ td.label {
 }
 
 #search-details {
-  background-position: -21px 0;
+  background-position: -1px -20px;
 }
 
 .searchdetails {
@@ -568,15 +568,15 @@ a.button {
 }
 
 a.button.edit {
-  background-position: -81px 0;
+  background-position: -1px -81px;
 }
 
 a.button.add {
-  background-position: -41px 0;
+  background-position: -1px -41px;
 }
 
 a.button.delete {
-  background-position: -1px 0;
+  background-position: -1px -1px;
 }
 
 /********* Form smart inputs *********/
@@ -660,11 +660,11 @@ span.listelement span.actions span.reset {
 }
 
 span.listelement span.actions span.add {
-  background-position: -41px -2px;
+  background-position: -2px -41px;
 }
 
 span.listelement span.actions span.search {
-  background-position: -61px -1px;
+  background-position: -1px -61px;
   cursor: default;
 }
 
@@ -916,11 +916,12 @@ fieldset.tabbed
   line-height: 24px;
   width: 330px;
   text-align: left;
+  white-space: nowrap;
 }
 
 #login_form label {
   display: block;
-  width: 80px;
+  width: 100px;
   text-align: right;
   float: left;
   margin-right: 10px;


commit 109906a3816bc1d4c99c2103e74a52f5c20d4791
Author: Aleksander Machniak <alec at alec.pl>
Date:   Tue Nov 13 14:55:40 2012 +0100

    jQuery-1.8.3

diff --git a/public_html/js/jquery.min.js b/public_html/js/jquery.min.js
index bc3fbc8..83589da 100644
--- a/public_html/js/jquery.min.js
+++ b/public_html/js/jquery.min.js
@@ -1,2 +1,2 @@
-/*! jQuery v1.8.2 jquery.com | jquery.org/license */
-(function(a,b){function G(a){var b=F[a]={};return p.each(a.split(s),function(a,c){b[c]=!0}),b}function J(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(I,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:+d+""===d?+d:H.test(d)?p.parseJSON(d):d}catch(f){}p.data(a,c,d)}else d=b}return d}function K(a){var b;for(b in a){if(b==="data"&&p.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function ba(){return!1}function bb(){return!0}function bh(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function bi(a,b){do a=a[b];while(a&&a.nodeType!==1);return a}function bj(a,b,c){b=b||0;if(p.isFunction(b))return p.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return p.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=p.grep(a,function(a){return a.nodeType===1});if(be.test(b))return p.filter(b,d,!c);b=p.filter(b,d)}return p.grep(a,function(a,d){return p.
 inArray(a,b)>=0===c})}function bk(a){var b=bl.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function bC(a,b){return a.getElementsByTagName(b)[0]||a.appendChild(a.ownerDocument.createElement(b))}function bD(a,b){if(b.nodeType!==1||!p.hasData(a))return;var c,d,e,f=p._data(a),g=p._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;d<e;d++)p.event.add(b,c,h[c][d])}g.data&&(g.data=p.extend({},g.data))}function bE(a,b){var c;if(b.nodeType!==1)return;b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase(),c==="object"?(b.parentNode&&(b.outerHTML=a.outerHTML),p.support.html5Clone&&a.innerHTML&&!p.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):c==="input"&&bv.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):c==="option"?b.selected=a.defaultSelected:c==="input"||c==="textarea"?b.defaultValue=a.defaul
 tValue:c==="script"&&b.text!==a.text&&(b.text=a.text),b.removeAttribute(p.expando)}function bF(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bG(a){bv.test(a.type)&&(a.defaultChecked=a.checked)}function bY(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=bW.length;while(e--){b=bW[e]+c;if(b in a)return b}return d}function bZ(a,b){return a=b||a,p.css(a,"display")==="none"||!p.contains(a.ownerDocument,a)}function b$(a,b){var c,d,e=[],f=0,g=a.length;for(;f<g;f++){c=a[f];if(!c.style)continue;e[f]=p._data(c,"olddisplay"),b?(!e[f]&&c.style.display==="none"&&(c.style.display=""),c.style.display===""&&bZ(c)&&(e[f]=p._data(c,"olddisplay",cc(c.nodeName)))):(d=bH(c,"display"),!e[f]&&d!=="none"&&p._data(c,"olddisplay",d))}for(f=0;f<g;f++){c=a[f];if(!c.style)continue;if(!b||c.style.display==="none"||c.style.display==="")c.style.display=b?e[f]||"":"none"}retu
 rn a}function b_(a,b,c){var d=bP.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function ca(a,b,c,d){var e=c===(d?"border":"content")?4:b==="width"?1:0,f=0;for(;e<4;e+=2)c==="margin"&&(f+=p.css(a,c+bV[e],!0)),d?(c==="content"&&(f-=parseFloat(bH(a,"padding"+bV[e]))||0),c!=="margin"&&(f-=parseFloat(bH(a,"border"+bV[e]+"Width"))||0)):(f+=parseFloat(bH(a,"padding"+bV[e]))||0,c!=="padding"&&(f+=parseFloat(bH(a,"border"+bV[e]+"Width"))||0));return f}function cb(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=!0,f=p.support.boxSizing&&p.css(a,"boxSizing")==="border-box";if(d<=0||d==null){d=bH(a,b);if(d<0||d==null)d=a.style[b];if(bQ.test(d))return d;e=f&&(p.support.boxSizingReliable||d===a.style[b]),d=parseFloat(d)||0}return d+ca(a,b,c||(f?"border":"content"),e)+"px"}function cc(a){if(bS[a])return bS[a];var b=p("<"+a+">").appendTo(e.body),c=b.css("display");b.remove();if(c==="none"||c===""){bI=e.body.appendChild(bI||p.extend(e.createElement("iframe"),{frameBorder:0,
 width:0,height:0}));if(!bJ||!bI.createElement)bJ=(bI.contentWindow||bI.contentDocument).document,bJ.write("<!doctype html><html><body>"),bJ.close();b=bJ.body.appendChild(bJ.createElement(a)),c=bH(b,"display"),e.body.removeChild(bI)}return bS[a]=c,c}function ci(a,b,c,d){var e;if(p.isArray(b))p.each(b,function(b,e){c||ce.test(a)?d(a,e):ci(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&p.type(b)==="object")for(e in b)ci(a+"["+e+"]",b[e],c,d);else d(a,b)}function cz(a){return function(b,c){typeof b!="string"&&(c=b,b="*");var d,e,f,g=b.toLowerCase().split(s),h=0,i=g.length;if(p.isFunction(c))for(;h<i;h++)d=g[h],f=/^\+/.test(d),f&&(d=d.substr(1)||"*"),e=a[d]=a[d]||[],e[f?"unshift":"push"](c)}}function cA(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h,i=a[f],j=0,k=i?i.length:0,l=a===cv;for(;j<k&&(l||!h);j++)h=i[j](c,d,e),typeof h=="string"&&(!l||g[h]?h=b:(c.dataTypes.unshift(h),h=cA(a,c,d,e,h,g)));return(l||!h)&&!g["*"]&&(h=cA(a,c,d,e,"*",g)),h}function cB(a,c){va
 r d,e,f=p.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((f[d]?a:e||(e={}))[d]=c[d]);e&&p.extend(!0,a,e)}function cC(a,c,d){var e,f,g,h,i=a.contents,j=a.dataTypes,k=a.responseFields;for(f in k)f in d&&(c[k[f]]=d[f]);while(j[0]==="*")j.shift(),e===b&&(e=a.mimeType||c.getResponseHeader("content-type"));if(e)for(f in i)if(i[f]&&i[f].test(e)){j.unshift(f);break}if(j[0]in d)g=j[0];else{for(f in d){if(!j[0]||a.converters[f+" "+j[0]]){g=f;break}h||(h=f)}g=g||h}if(g)return g!==j[0]&&j.unshift(g),d[g]}function cD(a,b){var c,d,e,f,g=a.dataTypes.slice(),h=g[0],i={},j=0;a.dataFilter&&(b=a.dataFilter(b,a.dataType));if(g[1])for(c in a.converters)i[c.toLowerCase()]=a.converters[c];for(;e=g[++j];)if(e!=="*"){if(h!=="*"&&h!==e){c=i[h+" "+e]||i["* "+e];if(!c)for(d in i){f=d.split(" ");if(f[1]===e){c=i[h+" "+f[0]]||i["* "+f[0]];if(c){c===!0?c=i[d]:i[d]!==!0&&(e=f[0],g.splice(j--,0,e));break}}}if(c!==!0)if(c&&a["throws"])b=c(b);else try{b=c(b)}catch(k){return{state:"parsererror",error:c?k:"
 No conversion from "+h+" to "+e}}}h=e}return{state:"success",data:b}}function cL(){try{return new a.XMLHttpRequest}catch(b){}}function cM(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function cU(){return setTimeout(function(){cN=b},0),cN=p.now()}function cV(a,b){p.each(b,function(b,c){var d=(cT[b]||[]).concat(cT["*"]),e=0,f=d.length;for(;e<f;e++)if(d[e].call(a,b,c))return})}function cW(a,b,c){var d,e=0,f=0,g=cS.length,h=p.Deferred().always(function(){delete i.elem}),i=function(){var b=cN||cU(),c=Math.max(0,j.startTime+j.duration-b),d=1-(c/j.duration||0),e=0,f=j.tweens.length;for(;e<f;e++)j.tweens[e].run(d);return h.notifyWith(a,[j,d,c]),d<1&&f?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:p.extend({},b),opts:p.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:cN||cU(),duration:c.duration,tweens:[],createTween:function(b,c,d){var e=p.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(e),e
 },stop:function(b){var c=0,d=b?j.tweens.length:0;for(;c<d;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;cX(k,j.opts.specialEasing);for(;e<g;e++){d=cS[e].call(j,a,k,j.opts);if(d)return d}return cV(j,k),p.isFunction(j.opts.start)&&j.opts.start.call(a,j),p.fx.timer(p.extend(i,{anim:j,queue:j.opts.queue,elem:a})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}function cX(a,b){var c,d,e,f,g;for(c in a){d=p.camelCase(c),e=b[d],f=a[c],p.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=p.cssHooks[d];if(g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}}function cY(a,b,c){var d,e,f,g,h,i,j,k,l=this,m=a.style,n={},o=[],q=a.nodeType&&bZ(a);c.queue||(j=p._queueHooks(a,"fx"),j.unqueued==null&&(j.unqueued=0,k=j.empty.fire,j.empty.fire=function(){j.unqueued||k()}),j.unqueued++,l.always(function(){l.always(function(){j.unqueued--,p.
 queue(a,"fx").length||j.empty.fire()})})),a.nodeType===1&&("height"in b||"width"in b)&&(c.overflow=[m.overflow,m.overflowX,m.overflowY],p.css(a,"display")==="inline"&&p.css(a,"float")==="none"&&(!p.support.inlineBlockNeedsLayout||cc(a.nodeName)==="inline"?m.display="inline-block":m.zoom=1)),c.overflow&&(m.overflow="hidden",p.support.shrinkWrapBlocks||l.done(function(){m.overflow=c.overflow[0],m.overflowX=c.overflow[1],m.overflowY=c.overflow[2]}));for(d in b){f=b[d];if(cP.exec(f)){delete b[d];if(f===(q?"hide":"show"))continue;o.push(d)}}g=o.length;if(g){h=p._data(a,"fxshow")||p._data(a,"fxshow",{}),q?p(a).show():l.done(function(){p(a).hide()}),l.done(function(){var b;p.removeData(a,"fxshow",!0);for(b in n)p.style(a,b,n[b])});for(d=0;d<g;d++)e=o[d],i=l.createTween(e,q?h[e]:0),n[e]=h[e]||p.style(a,e),e in h||(h[e]=i.start,q&&(i.end=i.start,i.start=e==="width"||e==="height"?1:0))}}function cZ(a,b,c,d,e){return new cZ.prototype.init(a,b,c,d,e)}function c$(a,b){var c,d={height:a},
 e=0;b=b?1:0;for(;e<4;e+=2-b)c=bV[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function da(a){return p.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}var c,d,e=a.document,f=a.location,g=a.navigator,h=a.jQuery,i=a.$,j=Array.prototype.push,k=Array.prototype.slice,l=Array.prototype.indexOf,m=Object.prototype.toString,n=Object.prototype.hasOwnProperty,o=String.prototype.trim,p=function(a,b){return new p.fn.init(a,b,c)},q=/[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,r=/\S/,s=/\s+/,t=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,u=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,y=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,z=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,A=/^-ms-/,B=/-([\da-z])/gi,C=function(a,b){return(b+"").toUpperCase()},D=function(){e.addEventListener?(e.removeEventListener("DOMContentLoaded",D,!1),p.ready()):e.readyState==="complete"&&(e.detachEve
 nt("onreadystatechange",D),p.ready())},E={};p.fn=p.prototype={constructor:p,init:function(a,c,d){var f,g,h,i;if(!a)return this;if(a.nodeType)return this.context=this[0]=a,this.length=1,this;if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?f=[null,a,null]:f=u.exec(a);if(f&&(f[1]||!c)){if(f[1])return c=c instanceof p?c[0]:c,i=c&&c.nodeType?c.ownerDocument||c:e,a=p.parseHTML(f[1],i,!0),v.test(f[1])&&p.isPlainObject(c)&&this.attr.call(a,c,!0),p.merge(this,a);g=e.getElementById(f[2]);if(g&&g.parentNode){if(g.id!==f[2])return d.find(a);this.length=1,this[0]=g}return this.context=e,this.selector=a,this}return!c||c.jquery?(c||d).find(a):this.constructor(c).find(a)}return p.isFunction(a)?d.ready(a):(a.selector!==b&&(this.selector=a.selector,this.context=a.context),p.makeArray(a,this))},selector:"",jquery:"1.8.2",length:0,size:function(){return this.length},toArray:function(){return k.call(this)},get:function(a){return a==null?this.toArray():a<0?this[t
 his.length+a]:this[a]},pushStack:function(a,b,c){var d=p.merge(this.constructor(),a);return d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")"),d},each:function(a,b){return p.each(this,a,b)},ready:function(a){return p.ready.promise().done(a),this},eq:function(a){return a=+a,a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(k.apply(this,arguments),"slice",k.call(arguments).join(","))},map:function(a){return this.pushStack(p.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:j,sort:[].sort,splice:[].splice},p.fn.init.prototype=p.fn,p.extend=p.fn.extend=function(){var a,c,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;typeof h=="boolean"&&(k=h,h=arguments[1]||{},i=2),typeof h!="object"&&!p.isFunction(h)&&(h={}),j
 ===i&&(h=this,--i);for(;i<j;i++)if((a=arguments[i])!=null)for(c in a){d=h[c],e=a[c];if(h===e)continue;k&&e&&(p.isPlainObject(e)||(f=p.isArray(e)))?(f?(f=!1,g=d&&p.isArray(d)?d:[]):g=d&&p.isPlainObject(d)?d:{},h[c]=p.extend(k,g,e)):e!==b&&(h[c]=e)}return h},p.extend({noConflict:function(b){return a.$===p&&(a.$=i),b&&a.jQuery===p&&(a.jQuery=h),p},isReady:!1,readyWait:1,holdReady:function(a){a?p.readyWait++:p.ready(!0)},ready:function(a){if(a===!0?--p.readyWait:p.isReady)return;if(!e.body)return setTimeout(p.ready,1);p.isReady=!0;if(a!==!0&&--p.readyWait>0)return;d.resolveWith(e,[p]),p.fn.trigger&&p(e).trigger("ready").off("ready")},isFunction:function(a){return p.type(a)==="function"},isArray:Array.isArray||function(a){return p.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):E[m.call(a)]||"object"},isPlainObject:function(a){if(!a||p.type(a)!=="object"|
 |a.nodeType||p.isWindow(a))return!1;try{if(a.constructor&&!n.call(a,"constructor")&&!n.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||n.call(a,d)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},error:function(a){throw new Error(a)},parseHTML:function(a,b,c){var d;return!a||typeof a!="string"?null:(typeof b=="boolean"&&(c=b,b=0),b=b||e,(d=v.exec(a))?[b.createElement(d[1])]:(d=p.buildFragment([a],b,c?null:[]),p.merge([],(d.cacheable?p.clone(d.fragment):d.fragment).childNodes)))},parseJSON:function(b){if(!b||typeof b!="string")return null;b=p.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(w.test(b.replace(y,"@").replace(z,"]").replace(x,"")))return(new Function("return "+b))();p.error("Invalid JSON: "+b)},parseXML:function(c){var d,e;if(!c||typeof c!="string")return null;try{a.DOMParser?(e=new DOMParser,d=e.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d
 .loadXML(c))}catch(f){d=b}return(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&p.error("Invalid XML: "+c),d},noop:function(){},globalEval:function(b){b&&r.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(A,"ms-").replace(B,C)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,c,d){var e,f=0,g=a.length,h=g===b||p.isFunction(a);if(d){if(h){for(e in a)if(c.apply(a[e],d)===!1)break}else for(;f<g;)if(c.apply(a[f++],d)===!1)break}else if(h){for(e in a)if(c.call(a[e],e,a[e])===!1)break}else for(;f<g;)if(c.call(a[f],f,a[f++])===!1)break;return a},trim:o&&!o.call(" ")?function(a){return a==null?"":o.call(a)}:function(a){return a==null?"":(a+"").replace(t,"")},makeArray:function(a,b){var c,d=b||[];return a!=null&&(c=p.type(a),a.length==null||c==="string"||c==="function"||c==="regexp"||p.isWindow(a)?j.call(d,a):p.merge(d,a)),d},inArray:function(a,b,c){var d;
 if(b){if(l)return l.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=c.length,e=a.length,f=0;if(typeof d=="number")for(;f<d;f++)a[e++]=c[f];else while(c[f]!==b)a[e++]=c[f++];return a.length=e,a},grep:function(a,b,c){var d,e=[],f=0,g=a.length;c=!!c;for(;f<g;f++)d=!!b(a[f],f),c!==d&&e.push(a[f]);return e},map:function(a,c,d){var e,f,g=[],h=0,i=a.length,j=a instanceof p||i!==b&&typeof i=="number"&&(i>0&&a[0]&&a[i-1]||i===0||p.isArray(a));if(j)for(;h<i;h++)e=c(a[h],h,d),e!=null&&(g[g.length]=e);else for(f in a)e=c(a[f],f,d),e!=null&&(g[g.length]=e);return g.concat.apply([],g)},guid:1,proxy:function(a,c){var d,e,f;return typeof c=="string"&&(d=a[c],c=a,a=d),p.isFunction(a)?(e=k.call(arguments,2),f=function(){return a.apply(c,e.concat(k.call(arguments)))},f.guid=a.guid=a.guid||p.guid++,f):b},access:function(a,c,d,e,f,g,h){var i,j=d==null,k=0,l=a.length;if(d&&typeof d=="object"){for(k in d)p.access(a,c,
 k,d[k],1,g,e);f=1}else if(e!==b){i=h===b&&p.isFunction(e),j&&(i?(i=c,c=function(a,b,c){return i.call(p(a),c)}):(c.call(a,e),c=null));if(c)for(;k<l;k++)c(a[k],d,i?e.call(a[k],k,c(a[k],d)):e,h);f=1}return f?a:j?c.call(a):l?c(a[0],d):g},now:function(){return(new Date).getTime()}}),p.ready.promise=function(b){if(!d){d=p.Deferred();if(e.readyState==="complete")setTimeout(p.ready,1);else if(e.addEventListener)e.addEventListener("DOMContentLoaded",D,!1),a.addEventListener("load",p.ready,!1);else{e.attachEvent("onreadystatechange",D),a.attachEvent("onload",p.ready);var c=!1;try{c=a.frameElement==null&&e.documentElement}catch(f){}c&&c.doScroll&&function g(){if(!p.isReady){try{c.doScroll("left")}catch(a){return setTimeout(g,50)}p.ready()}}()}}return d.promise(b)},p.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){E["[object "+b+"]"]=b.toLowerCase()}),c=p(e);var F={};p.Callbacks=function(a){a=typeof a=="string"?F[a]||G(a):p.extend({},a);var c,d,e,
 f,g,h,i=[],j=!a.once&&[],k=function(b){c=a.memory&&b,d=!0,h=f||0,f=0,g=i.length,e=!0;for(;i&&h<g;h++)if(i[h].apply(b[0],b[1])===!1&&a.stopOnFalse){c=!1;break}e=!1,i&&(j?j.length&&k(j.shift()):c?i=[]:l.disable())},l={add:function(){if(i){var b=i.length;(function d(b){p.each(b,function(b,c){var e=p.type(c);e==="function"&&(!a.unique||!l.has(c))?i.push(c):c&&c.length&&e!=="string"&&d(c)})})(arguments),e?g=i.length:c&&(f=b,k(c))}return this},remove:function(){return i&&p.each(arguments,function(a,b){var c;while((c=p.inArray(b,i,c))>-1)i.splice(c,1),e&&(c<=g&&g--,c<=h&&h--)}),this},has:function(a){return p.inArray(a,i)>-1},empty:function(){return i=[],this},disable:function(){return i=j=c=b,this},disabled:function(){return!i},lock:function(){return j=b,c||l.disable(),this},locked:function(){return!j},fireWith:function(a,b){return b=b||[],b=[a,b.slice?b.slice():b],i&&(!d||j)&&(e?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!
 d}};return l},p.extend({Deferred:function(a){var b=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return p.Deferred(function(c){p.each(b,function(b,d){var f=d[0],g=a[b];e[d[1]](p.isFunction(g)?function(){var a=g.apply(this,arguments);a&&p.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===e?c:this,[a])}:c[f])}),a=null}).promise()},promise:function(a){return a!=null?p.extend(a,d):d}},e={};return d.pipe=d.then,p.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[a^1][2].disable,b[2][2].lock),e[f[0]]=g.fire,e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=k.call(arguments),d=c.length,e=d!==1||a&&p.isFunction(a.promi
 se)?d:0,f=e===1?a:p.Deferred(),g=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?k.call(arguments):d,c===h?f.notifyWith(b,c):--e||f.resolveWith(b,c)}},h,i,j;if(d>1){h=new Array(d),i=new Array(d),j=new Array(d);for(;b<d;b++)c[b]&&p.isFunction(c[b].promise)?c[b].promise().done(g(b,j,c)).fail(f.reject).progress(g(b,i,h)):--e}return e||f.resolveWith(j,c),f.promise()}}),p.support=function(){var b,c,d,f,g,h,i,j,k,l,m,n=e.createElement("div");n.setAttribute("className","t"),n.innerHTML="  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",c=n.getElementsByTagName("*"),d=n.getElementsByTagName("a")[0],d.style.cssText="top:1px;float:left;opacity:.5";if(!c||!c.length)return{};f=e.createElement("select"),g=f.appendChild(e.createElement("option")),h=n.getElementsByTagName("input")[0],b={leadingWhitespace:n.firstChild.nodeType===3,tbody:!n.getElementsByTagName("tbody").length,htmlSerialize:!!n.getElementsByTagName("link").length,style:/top/.test(d.getA
 ttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.5/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:n.className!=="t",enctype:!!e.createElement("form").enctype,html5Clone:e.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",boxModel:e.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},h.checked=!0,b.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,b.optDisabled=!g.disabled;try{delete n.test}catch(o){b.deleteExpando=!1}!n.addEventListener&&n.attachEvent&&n.fireEvent&&(n.attachEvent("onclick",m=function(){b.noCloneEvent=!1}),n.cloneNode(!0).fireEvent("onclick"),n.detachEvent("onclick",m)),h=e.createElement("input"),h.value="t",h.setAttribute("type","radio"),b.radioValue=h.value==="t",h.setAttribute("checked
 ","checked"),h.setAttribute("name","t"),n.appendChild(h),i=e.createDocumentFragment(),i.appendChild(n.lastChild),b.checkClone=i.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=h.checked,i.removeChild(h),i.appendChild(n);if(n.attachEvent)for(k in{submit:!0,change:!0,focusin:!0})j="on"+k,l=j in n,l||(n.setAttribute(j,"return;"),l=typeof n[j]=="function"),b[k+"Bubbles"]=l;return p(function(){var c,d,f,g,h="padding:0;margin:0;border:0;display:block;overflow:hidden;",i=e.getElementsByTagName("body")[0];if(!i)return;c=e.createElement("div"),c.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",i.insertBefore(c,i.firstChild),d=e.createElement("div"),c.appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",f=d.getElementsByTagName("td"),f[0].style.cssText="padding:0;margin:0;border:0;display:none",l=f[0].offsetHeight===0,f[0].style.display="",f[1].style.display="none",b.reliableHiddenOffsets=l&&f[0].offsetH
 eight===0,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",b.boxSizing=d.offsetWidth===4,b.doesNotIncludeMarginInBodyOffset=i.offsetTop!==1,a.getComputedStyle&&(b.pixelPosition=(a.getComputedStyle(d,null)||{}).top!=="1%",b.boxSizingReliable=(a.getComputedStyle(d,null)||{width:"4px"}).width==="4px",g=e.createElement("div"),g.style.cssText=d.style.cssText=h,g.style.marginRight=g.style.width="0",d.style.width="1px",d.appendChild(g),b.reliableMarginRight=!parseFloat((a.getComputedStyle(g,null)||{}).marginRight)),typeof d.style.zoom!="undefined"&&(d.innerHTML="",d.style.cssText=h+"width:1px;padding:1px;display:inline;zoom:1",b.inlineBlockNeedsLayout=d.offsetWidth===3,d.style.display="block",d.style.overflow="visible",d.innerHTML="<div></div>",d.firstChild.style.width="5px",b.shrinkWrapBlocks=d.offsetWidth!==3,c.style.zoom=1),i.remov
 eChild(c),c=d=f=g=null}),i.removeChild(n),c=d=f=g=h=i=n=null,b}();var H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,I=/([A-Z])/g;p.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(p.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){return a=a.nodeType?p.cache[a[p.expando]]:a[p.expando],!!a&&!K(a)},data:function(a,c,d,e){if(!p.acceptData(a))return;var f,g,h=p.expando,i=typeof c=="string",j=a.nodeType,k=j?p.cache:a,l=j?a[h]:a[h]&&h;if((!l||!k[l]||!e&&!k[l].data)&&i&&d===b)return;l||(j?a[h]=l=p.deletedIds.pop()||p.guid++:l=h),k[l]||(k[l]={},j||(k[l].toJSON=p.noop));if(typeof c=="object"||typeof c=="function")e?k[l]=p.extend(k[l],c):k[l].data=p.extend(k[l].data,c);return f=k[l],e||(f.data||(f.data={}),f=f.data),d!==b&&(f[p.camelCase(c)]=d),i?(g=f[c],g==null&&(g=f[p.camelCase(c)])):g=f,g},removeData:function(a,b,c){if(!p.acceptData(a))return;var d,e,f,g=a.nodeType,h=g?p.cache:a,i=g?a[p.expando]
 :p.expando;if(!h[i])return;if(b){d=c?h[i]:h[i].data;if(d){p.isArray(b)||(b in d?b=[b]:(b=p.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,f=b.length;e<f;e++)delete d[b[e]];if(!(c?K:p.isEmptyObject)(d))return}}if(!c){delete h[i].data;if(!K(h[i]))return}g?p.cleanData([a],!0):p.support.deleteExpando||h!=h.window?delete h[i]:h[i]=null},_data:function(a,b,c){return p.data(a,b,c,!0)},acceptData:function(a){var b=a.nodeName&&p.noData[a.nodeName.toLowerCase()];return!b||b!==!0&&a.getAttribute("classid")===b}}),p.fn.extend({data:function(a,c){var d,e,f,g,h,i=this[0],j=0,k=null;if(a===b){if(this.length){k=p.data(i);if(i.nodeType===1&&!p._data(i,"parsedAttrs")){f=i.attributes;for(h=f.length;j<h;j++)g=f[j].name,g.indexOf("data-")||(g=p.camelCase(g.substring(5)),J(i,g,k[g]));p._data(i,"parsedAttrs",!0)}}return k}return typeof a=="object"?this.each(function(){p.data(this,a)}):(d=a.split(".",2),d[1]=d[1]?"."+d[1]:"",e=d[1]+"!",p.access(this,function(c){if(c===b)return k=this.triggerHan
 dler("getData"+e,[d[0]]),k===b&&i&&(k=p.data(i,a),k=J(i,a,k)),k===b&&d[1]?this.data(d[0]):k;d[1]=c,this.each(function(){var b=p(this);b.triggerHandler("setData"+e,d),p.data(this,a,c),b.triggerHandler("changeData"+e,d)})},null,c,arguments.length>1,null,!1))},removeData:function(a){return this.each(function(){p.removeData(this,a)})}}),p.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=p._data(a,b),c&&(!d||p.isArray(c)?d=p._data(a,b,p.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=p.queue(a,b),d=c.length,e=c.shift(),f=p._queueHooks(a,b),g=function(){p.dequeue(a,b)};e==="inprogress"&&(e=c.shift(),d--),e&&(b==="fx"&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return p._data(a,c)||p._data(a,c,{empty:p.Callbacks("once memory").add(function(){p.removeData(a,b+"queue",!0),p.removeData(a,c,!0)})})}}),p.fn.extend({queue:function(a,c){var d=2;return typeof a!="strin
 g"&&(c=a,a="fx",d--),arguments.length<d?p.queue(this[0],a):c===b?this:this.each(function(){var b=p.queue(this,a,c);p._queueHooks(this,a),a==="fx"&&b[0]!=="inprogress"&&p.dequeue(this,a)})},dequeue:function(a){return this.each(function(){p.dequeue(this,a)})},delay:function(a,b){return a=p.fx?p.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){var d,e=1,f=p.Deferred(),g=this,h=this.length,i=function(){--e||f.resolveWith(g,[g])};typeof a!="string"&&(c=a,a=b),a=a||"fx";while(h--)d=p._data(g[h],a+"queueHooks"),d&&d.empty&&(e++,d.empty.add(i));return i(),f.promise(c)}});var L,M,N,O=/[\t\r\n]/g,P=/\r/g,Q=/^(?:button|input)$/i,R=/^(?:button|input|object|select|textarea)$/i,S=/^a(?:rea|)$/i,T=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,U=p.support.getSetAttribute;p
 .fn.extend({attr:function(a,b){return p.access(this,p.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){p.removeAttr(this,a)})},prop:function(a,b){return p.access(this,p.prop,a,b,arguments.length>1)},removeProp:function(a){return a=p.propFix[a]||a,this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,f,g,h;if(p.isFunction(a))return this.each(function(b){p(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(s);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{f=" "+e.className+" ";for(g=0,h=b.length;g<h;g++)f.indexOf(" "+b[g]+" ")<0&&(f+=b[g]+" ");e.className=p.trim(f)}}}return this},removeClass:function(a){var c,d,e,f,g,h,i;if(p.isFunction(a))return this.each(function(b){p(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(s);for(h=0,i=this.length;h<i;h++){e=this[h]
 ;if(e.nodeType===1&&e.className){d=(" "+e.className+" ").replace(O," ");for(f=0,g=c.length;f<g;f++)while(d.indexOf(" "+c[f]+" ")>=0)d=d.replace(" "+c[f]+" "," ");e.className=a?p.trim(d):""}}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";return p.isFunction(a)?this.each(function(c){p(this).toggleClass(a.call(this,c,this.className,b),b)}):this.each(function(){if(c==="string"){var e,f=0,g=p(this),h=b,i=a.split(s);while(e=i[f++])h=d?h:!g.hasClass(e),g[h?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||a===!1?"":p._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(O," ").indexOf(b)>=0)return!0;return!1},val:function(a){var c,d,e,f=this[0];if(!arguments.length){if(f)return c=p.valHooks[f.type]||p.valHooks[f.nodeName.toLowerCase()],
 c&&"get"in c&&(d=c.get(f,"value"))!==b?d:(d=f.value,typeof d=="string"?d.replace(P,""):d==null?"":d);return}return e=p.isFunction(a),this.each(function(d){var f,g=p(this);if(this.nodeType!==1)return;e?f=a.call(this,d,g.val()):f=a,f==null?f="":typeof f=="number"?f+="":p.isArray(f)&&(f=p.map(f,function(a){return a==null?"":a+""})),c=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,f,"value")===b)this.value=f})}}),p.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,f=a.selectedIndex,g=[],h=a.options,i=a.type==="select-one";if(f<0)return null;c=i?f:0,d=i?f+1:h.length;for(;c<d;c++){e=h[c];if(e.selected&&(p.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!p.nodeName(e.parentNode,"optgroup"))){b=p(e).val();if(i)return b;g.push(b)}}return i&&!g.length&&h.length?p(h[f]).val():g},set:function(a,b){var c=p
 .makeArray(b);return p(a).find("option").each(function(){this.selected=p.inArray(p(this).val(),c)>=0}),c.length||(a.selectedIndex=-1),c}}},attrFn:{},attr:function(a,c,d,e){var f,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return;if(e&&p.isFunction(p.fn[c]))return p(a)[c](d);if(typeof a.getAttribute=="undefined")return p.prop(a,c,d);h=i!==1||!p.isXMLDoc(a),h&&(c=c.toLowerCase(),g=p.attrHooks[c]||(T.test(c)?M:L));if(d!==b){if(d===null){p.removeAttr(a,c);return}return g&&"set"in g&&h&&(f=g.set(a,d,c))!==b?f:(a.setAttribute(c,d+""),d)}return g&&"get"in g&&h&&(f=g.get(a,c))!==null?f:(f=a.getAttribute(c),f===null?b:f)},removeAttr:function(a,b){var c,d,e,f,g=0;if(b&&a.nodeType===1){d=b.split(s);for(;g<d.length;g++)e=d[g],e&&(c=p.propFix[e]||e,f=T.test(e),f||p.attr(a,e,""),a.removeAttribute(U?e:c),f&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(Q.test(a.nodeName)&&a.parentNode)p.error("type property can't be changed");else if(!p.support.radioValue&&b==="radio"&&p.nodeN
 ame(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}},value:{get:function(a,b){return L&&p.nodeName(a,"button")?L.get(a,b):b in a?a.value:null},set:function(a,b,c){if(L&&p.nodeName(a,"button"))return L.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,f,g,h=a.nodeType;if(!a||h===3||h===8||h===2)return;return g=h!==1||!p.isXMLDoc(a),g&&(c=p.propFix[c]||c,f=p.propHooks[c]),d!==b?f&&"set"in f&&(e=f.set(a,d,c))!==b?e:a[c]=d:f&&"get"in f&&(e=f.get(a,c))!==null?e:a[c]},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):R.test(a.nodeName)||S.test(a.nodeName)&&a.href?0:b}}}}),M={get:function(a,c){var d,e=p.prop(a,c);return
  e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;return b===!1?p.removeAttr(a,c):(d=p.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase())),c}},U||(N={name:!0,id:!0,coords:!0},L=p.valHooks.button={get:function(a,c){var d;return d=a.getAttributeNode(c),d&&(N[c]?d.value!=="":d.specified)?d.value:b},set:function(a,b,c){var d=a.getAttributeNode(c);return d||(d=e.createAttribute(c),a.setAttributeNode(d)),d.value=b+""}},p.each(["width","height"],function(a,b){p.attrHooks[b]=p.extend(p.attrHooks[b],{set:function(a,c){if(c==="")return a.setAttribute(b,"auto"),c}})}),p.attrHooks.contenteditable={get:L.get,set:function(a,b,c){b===""&&(b="false"),L.set(a,b,c)}}),p.support.hrefNormalized||p.each(["href","src","width","height"],function(a,c){p.attrHooks[c]=p.extend(p.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),p.support.style||(p.attrHooks.style={get:function(a){return 
 a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=b+""}}),p.support.optSelected||(p.propHooks.selected=p.extend(p.propHooks.selected,{get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}})),p.support.enctype||(p.propFix.enctype="encoding"),p.support.checkOn||p.each(["radio","checkbox"],function(){p.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),p.each(["radio","checkbox"],function(){p.valHooks[this]=p.extend(p.valHooks[this],{set:function(a,b){if(p.isArray(b))return a.checked=p.inArray(p(a).val(),b)>=0}})});var V=/^(?:textarea|input|select)$/i,W=/^([^\.]*|)(?:\.(.+)|)$/,X=/(?:^|\s)hover(\.\S+|)\b/,Y=/^key/,Z=/^(?:mouse|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=function(a){return p.event.special.hover?a:a.replace(X,"mouseenter$1 mouseleave$1")};p.event={add:function(a,c,d,e,f){var g,h,i,j,k,l,m,n,o,q,r;if(a.nodeType===3||a.nodeType===8||!c|
 |!d||!(g=p._data(a)))return;d.handler&&(o=d,d=o.handler,f=o.selector),d.guid||(d.guid=p.guid++),i=g.events,i||(g.events=i={}),h=g.handle,h||(g.handle=h=function(a){return typeof p!="undefined"&&(!a||p.event.triggered!==a.type)?p.event.dispatch.apply(h.elem,arguments):b},h.elem=a),c=p.trim(_(c)).split(" ");for(j=0;j<c.length;j++){k=W.exec(c[j])||[],l=k[1],m=(k[2]||"").split(".").sort(),r=p.event.special[l]||{},l=(f?r.delegateType:r.bindType)||l,r=p.event.special[l]||{},n=p.extend({type:l,origType:k[1],data:e,handler:d,guid:d.guid,selector:f,needsContext:f&&p.expr.match.needsContext.test(f),namespace:m.join(".")},o),q=i[l];if(!q){q=i[l]=[],q.delegateCount=0;if(!r.setup||r.setup.call(a,e,m,h)===!1)a.addEventListener?a.addEventListener(l,h,!1):a.attachEvent&&a.attachEvent("on"+l,h)}r.add&&(r.add.call(a,n),n.handler.guid||(n.handler.guid=d.guid)),f?q.splice(q.delegateCount++,0,n):q.push(n),p.event.global[l]=!0}a=null},global:{},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,q
 ,r=p.hasData(a)&&p._data(a);if(!r||!(m=r.events))return;b=p.trim(_(b||"")).split(" ");for(f=0;f<b.length;f++){g=W.exec(b[f])||[],h=i=g[1],j=g[2];if(!h){for(h in m)p.event.remove(a,h+b[f],c,d,!0);continue}n=p.event.special[h]||{},h=(d?n.delegateType:n.bindType)||h,o=m[h]||[],k=o.length,j=j?new RegExp("(^|\\.)"+j.split(".").sort().join("\\.(?:.*\\.|)")+"(\\.|$)"):null;for(l=0;l<o.length;l++)q=o[l],(e||i===q.origType)&&(!c||c.guid===q.guid)&&(!j||j.test(q.namespace))&&(!d||d===q.selector||d==="**"&&q.selector)&&(o.splice(l--,1),q.selector&&o.delegateCount--,n.remove&&n.remove.call(a,q));o.length===0&&k!==o.length&&((!n.teardown||n.teardown.call(a,j,r.handle)===!1)&&p.removeEvent(a,h,r.handle),delete m[h])}p.isEmptyObject(m)&&(delete r.handle,p.removeData(a,"events",!0))},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,f,g){if(!f||f.nodeType!==3&&f.nodeType!==8){var h,i,j,k,l,m,n,o,q,r,s=c.type||c,t=[];if($.test(s+p.event.triggered))return;s.indexOf("!")>=
 0&&(s=s.slice(0,-1),i=!0),s.indexOf(".")>=0&&(t=s.split("."),s=t.shift(),t.sort());if((!f||p.event.customEvent[s])&&!p.event.global[s])return;c=typeof c=="object"?c[p.expando]?c:new p.Event(s,c):new p.Event(s),c.type=s,c.isTrigger=!0,c.exclusive=i,c.namespace=t.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,m=s.indexOf(":")<0?"on"+s:"";if(!f){h=p.cache;for(j in h)h[j].events&&h[j].events[s]&&p.event.trigger(c,d,h[j].handle.elem,!0);return}c.result=b,c.target||(c.target=f),d=d!=null?p.makeArray(d):[],d.unshift(c),n=p.event.special[s]||{};if(n.trigger&&n.trigger.apply(f,d)===!1)return;q=[[f,n.bindType||s]];if(!g&&!n.noBubble&&!p.isWindow(f)){r=n.delegateType||s,k=$.test(r+s)?f:f.parentNode;for(l=f;k;k=k.parentNode)q.push([k,r]),l=k;l===(f.ownerDocument||e)&&q.push([l.defaultView||l.parentWindow||a,r])}for(j=0;j<q.length&&!c.isPropagationStopped();j++)k=q[j][0],c.type=q[j][1],o=(p._data(k,"events")||{})[c.type]&&p._data(k,"hand
 le"),o&&o.apply(k,d),o=m&&k[m],o&&p.acceptData(k)&&o.apply&&o.apply(k,d)===!1&&c.preventDefault();return c.type=s,!g&&!c.isDefaultPrevented()&&(!n._default||n._default.apply(f.ownerDocument,d)===!1)&&(s!=="click"||!p.nodeName(f,"a"))&&p.acceptData(f)&&m&&f[s]&&(s!=="focus"&&s!=="blur"||c.target.offsetWidth!==0)&&!p.isWindow(f)&&(l=f[m],l&&(f[m]=null),p.event.triggered=s,f[s](),p.event.triggered=b,l&&(f[m]=l)),c.result}return},dispatch:function(c){c=p.event.fix(c||a.event);var d,e,f,g,h,i,j,l,m,n,o=(p._data(this,"events")||{})[c.type]||[],q=o.delegateCount,r=k.call(arguments),s=!c.exclusive&&!c.namespace,t=p.event.special[c.type]||{},u=[];r[0]=c,c.delegateTarget=this;if(t.preDispatch&&t.preDispatch.call(this,c)===!1)return;if(q&&(!c.button||c.type!=="click"))for(f=c.target;f!=this;f=f.parentNode||this)if(f.disabled!==!0||c.type!=="click"){h={},j=[];for(d=0;d<q;d++)l=o[d],m=l.selector,h[m]===b&&(h[m]=l.needsContext?p(m,this).index(f)>=0:p.find(m,this,null,[f]).length),h[m]&&j.
 push(l);j.length&&u.push({elem:f,matches:j})}o.length>q&&u.push({elem:this,matches:o.slice(q)});for(d=0;d<u.length&&!c.isPropagationStopped();d++){i=u[d],c.currentTarget=i.elem;for(e=0;e<i.matches.length&&!c.isImmediatePropagationStopped();e++){l=i.matches[e];if(s||!c.namespace&&!l.namespace||c.namespace_re&&c.namespace_re.test(l.namespace))c.data=l.data,c.handleObj=l,g=((p.event.special[l.origType]||{}).handle||l.handler).apply(i.elem,r),g!==b&&(c.result=g,g===!1&&(c.preventDefault(),c.stopPropagation()))}}return t.postDispatch&&t.postDispatch.call(this,c),c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX off
 setY pageX pageY screenX screenY toElement".split(" "),filter:function(a,c){var d,f,g,h=c.button,i=c.fromElement;return a.pageX==null&&c.clientX!=null&&(d=a.target.ownerDocument||e,f=d.documentElement,g=d.body,a.pageX=c.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=c.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?c.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0),a}},fix:function(a){if(a[p.expando])return a;var b,c,d=a,f=p.event.fixHooks[a.type]||{},g=f.props?this.props.concat(f.props):this.props;a=p.Event(d);for(b=g.length;b;)c=g[--b],a[c]=d[c];return a.target||(a.target=d.srcElement||e),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,f.filter?f.filter(a,d):a},special:{load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){p.isWindow(this)&&(this
 .onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=p.extend(new p.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?p.event.trigger(e,null,b):p.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},p.event.handle=p.event.dispatch,p.removeEvent=e.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]=="undefined"&&(a[d]=null),a.detachEvent(d,c))},p.Event=function(a,b){if(this instanceof p.Event)a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?bb:ba):this.type=a,b&&p.extend(this,b),this.timeStamp=a&&a.timeStamp||p.now(),this[p.expando]=!0;else return new p.Event(a,b)},p.Event.prototype={preventDefault:function(){this.isDefaultPrevented=bb;var a=this.originalEvent;if(!a)return;a.prevent
 Default?a.preventDefault():a.returnValue=!1},stopPropagation:function(){this.isPropagationStopped=bb;var a=this.originalEvent;if(!a)return;a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=bb,this.stopPropagation()},isDefaultPrevented:ba,isPropagationStopped:ba,isImmediatePropagationStopped:ba},p.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){p.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj,g=f.selector;if(!e||e!==d&&!p.contains(d,e))a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b;return c}}}),p.support.submitBubbles||(p.event.special.submit={setup:function(){if(p.nodeName(this,"form"))return!1;p.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=p.nodeName(c,"input")||p.nodeName(c,"button")?c.form:b;d&&!p._data(d,"_submit_attached")&&(p.event.add(d,"submit._submit",function(a){a.
 _submit_bubble=!0}),p._data(d,"_submit_attached",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&p.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){if(p.nodeName(this,"form"))return!1;p.event.remove(this,"._submit")}}),p.support.changeBubbles||(p.event.special.change={setup:function(){if(V.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")p.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),p.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),p.event.simulate("change",this,a,!0)});return!1}p.event.add(this,"beforeactivate._change",function(a){var b=a.target;V.test(b.nodeName)&&!p._data(b,"_change_attached")&&(p.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&p.event.simulate("change",this.parentNode,a,!0)}),p._data(b,"_chang
 e_attached",!0))})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){return p.event.remove(this,"._change"),!V.test(this.nodeName)}}),p.support.focusinBubbles||p.each({focus:"focusin",blur:"focusout"},function(a,b){var c=0,d=function(a){p.event.simulate(b,a.target,p.event.fix(a),!0)};p.event.special[b]={setup:function(){c++===0&&e.addEventListener(a,d,!0)},teardown:function(){--c===0&&e.removeEventListener(a,d,!0)}}}),p.fn.extend({on:function(a,c,d,e,f){var g,h;if(typeof a=="object"){typeof c!="string"&&(d=d||c,c=b);for(h in a)this.on(h,c,d,a[h],f);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=ba;else if(!e)return this;return f===1&&(g=e,e=function(a){return p().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=p.guid++)),this.each(function(){p.event.add(this,a,e,d,c)})},one:fun
 ction(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,c,d){var e,f;if(a&&a.preventDefault&&a.handleObj)return e=a.handleObj,p(a.delegateTarget).off(e.namespace?e.origType+"."+e.namespace:e.origType,e.selector,e.handler),this;if(typeof a=="object"){for(f in a)this.off(f,c,a[f]);return this}if(c===!1||typeof c=="function")d=c,c=b;return d===!1&&(d=ba),this.each(function(){p.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){return p(this.context).on(a,this.selector,b,c),this},die:function(a,b){return p(this.context).off(a,this.selector||"**",b),this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length===1?this.off(a,"**"):this.off(b,a||"**",c)},trigger:function(a,b){return this.each(function(){p.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return p.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=argum
 ents,c=a.guid||p.guid++,d=0,e=function(c){var e=(p._data(this,"lastToggle"+a.guid)||0)%d;return p._data(this,"lastToggle"+a.guid,e+1),c.preventDefault(),b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),p.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){p.fn[b]=function(a,c){return c==null&&(c=a,a=null),arguments.length>0?this.on(b,null,a,c):this.trigger(b)},Y.test(b)&&(p.event.fixHooks[b]=p.event.keyHooks),Z.test(b)&&(p.event.fixHooks[b]=p.event.mouseHooks)}),function(a,b){function bc(a,b,c,d){c=c||[],b=b||r;var e,f,i,j,k=b.nodeType;if(!a||typeof a!="string")return c;if(k!==1&&k!==9)return[];i=g(b);if(!i&&!d)if(e=P.exec(a))if(j=e[1]){if(k===9){f=b.getElementById(j);if(!f||!f.parentNode)
 return c;if(f.id===j)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(j))&&h(b,f)&&f.id===j)return c.push(f),c}else{if(e[2])return w.apply(c,x.call(b.getElementsByTagName(a),0)),c;if((j=e[3])&&_&&b.getElementsByClassName)return w.apply(c,x.call(b.getElementsByClassName(j),0)),c}return bp(a.replace(L,"$1"),b,c,d,i)}function bd(a){return function(b){var c=b.nodeName.toLowerCase();return c==="input"&&b.type===a}}function be(a){return function(b){var c=b.nodeName.toLowerCase();return(c==="input"||c==="button")&&b.type===a}}function bf(a){return z(function(b){return b=+b,z(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function bg(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}function bh(a,b){var c,d,f,g,h,i,j,k=C[o][a];if(k)return b?0:k.slice(0);h=a,i=[],j=e.preFilter;while(h){if(!c||(d=M.exec(h)))d&&(h=h.slice(d[0].length)),i.push(f=[]);c=!1;if(d=N.exe
 c(h))f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=d[0].replace(L," ");for(g in e.filter)(d=W[g].exec(h))&&(!j[g]||(d=j[g](d,r,!0)))&&(f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=g,c.matches=d);if(!c)break}return b?h.length:h?bc.error(a):C(a,i).slice(0)}function bi(a,b,d){var e=b.dir,f=d&&b.dir==="parentNode",g=u++;return b.first?function(b,c,d){while(b=b[e])if(f||b.nodeType===1)return a(b,c,d)}:function(b,d,h){if(!h){var i,j=t+" "+g+" ",k=j+c;while(b=b[e])if(f||b.nodeType===1){if((i=b[o])===k)return b.sizset;if(typeof i=="string"&&i.indexOf(j)===0){if(b.sizset)return b}else{b[o]=k;if(a(b,d,h))return b.sizset=!0,b;b.sizset=!1}}}else while(b=b[e])if(f||b.nodeType===1)if(a(b,d,h))return b}}function bj(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function bk(a,b,c,d,e){var f,g=[],h=0,i=a.length,j=b!=null;for(;h<i;h++)if(f=a[h])if(!c||c(f,d,e))g.push(f),j&&b.push(h);return g}function bl(a,b,c,d,e,f){retur
 n d&&!d[o]&&(d=bl(d)),e&&!e[o]&&(e=bl(e,f)),z(function(f,g,h,i){if(f&&e)return;var j,k,l,m=[],n=[],o=g.length,p=f||bo(b||"*",h.nodeType?[h]:h,[],f),q=a&&(f||!b)?bk(p,m,a,h,i):p,r=c?e||(f?a:o||d)?[]:g:q;c&&c(q,r,h,i);if(d){l=bk(r,n),d(l,[],h,i),j=l.length;while(j--)if(k=l[j])r[n[j]]=!(q[n[j]]=k)}if(f){j=a&&r.length;while(j--)if(k=r[j])f[m[j]]=!(g[m[j]]=k)}else r=bk(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):w.apply(g,r)})}function bm(a){var b,c,d,f=a.length,g=e.relative[a[0].type],h=g||e.relative[" "],i=g?1:0,j=bi(function(a){return a===b},h,!0),k=bi(function(a){return y.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==l)||((b=c).nodeType?j(a,c,d):k(a,c,d))}];for(;i<f;i++)if(c=e.relative[a[i].type])m=[bi(bj(m),c)];else{c=e.filter[a[i].type].apply(null,a[i].matches);if(c[o]){d=++i;for(;d<f;d++)if(e.relative[a[d].type])break;return bl(i>1&&bj(m),i>1&&a.slice(0,i-1).join("").replace(L,"$1"),c,i<d&&bm(a.slice(i,d)),d<f&&bm(a=a.slice(d)),d<f&&a.join(""))}m.push(c)}retur
 n bj(m)}function bn(a,b){var d=b.length>0,f=a.length>0,g=function(h,i,j,k,m){var n,o,p,q=[],s=0,u="0",x=h&&[],y=m!=null,z=l,A=h||f&&e.find.TAG("*",m&&i.parentNode||i),B=t+=z==null?1:Math.E;y&&(l=i!==r&&i,c=g.el);for(;(n=A[u])!=null;u++){if(f&&n){for(o=0;p=a[o];o++)if(p(n,i,j)){k.push(n);break}y&&(t=B,c=++g.el)}d&&((n=!p&&n)&&s--,h&&x.push(n))}s+=u;if(d&&u!==s){for(o=0;p=b[o];o++)p(x,q,i,j);if(h){if(s>0)while(u--)!x[u]&&!q[u]&&(q[u]=v.call(k));q=bk(q)}w.apply(k,q),y&&!h&&q.length>0&&s+b.length>1&&bc.uniqueSort(k)}return y&&(t=B,l=z),x};return g.el=0,d?z(g):g}function bo(a,b,c,d){var e=0,f=b.length;for(;e<f;e++)bc(a,b[e],c,d);return c}function bp(a,b,c,d,f){var g,h,j,k,l,m=bh(a),n=m.length;if(!d&&m.length===1){h=m[0]=m[0].slice(0);if(h.length>2&&(j=h[0]).type==="ID"&&b.nodeType===9&&!f&&e.relative[h[1].type]){b=e.find.ID(j.matches[0].replace(V,""),b,f)[0];if(!b)return c;a=a.slice(h.shift().length)}for(g=W.POS.test(a)?-1:h.length-1;g>=0;g--){j=h[g];if(e.relative[k=j.type])break
 ;if(l=e.find[k])if(d=l(j.matches[0].replace(V,""),R.test(h[0].type)&&b.parentNode||b,f)){h.splice(g,1),a=d.length&&h.join("");if(!a)return w.apply(c,x.call(d,0)),c;break}}}return i(a,m)(d,b,f,c,R.test(a)),c}function bq(){}var c,d,e,f,g,h,i,j,k,l,m=!0,n="undefined",o=("sizcache"+Math.random()).replace(".",""),q=String,r=a.document,s=r.documentElement,t=0,u=0,v=[].pop,w=[].push,x=[].slice,y=[].indexOf||function(a){var b=0,c=this.length;for(;b<c;b++)if(this[b]===a)return b;return-1},z=function(a,b){return a[o]=b==null||b,a},A=function(){var a={},b=[];return z(function(c,d){return b.push(c)>e.cacheLength&&delete a[b.shift()],a[c]=d},a)},B=A(),C=A(),D=A(),E="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",G=F.replace("w","w#"),H="([*^$|!~]?=)",I="\\["+E+"*("+F+")"+E+"*(?:"+H+E+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+G+")|)|)"+E+"*\\]",J=":("+F+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+I+")|[^:]|\\\\.)*|.*))\\)|)",K=":(even|odd|eq|gt|lt|nth|first|las
 t)(?:\\("+E+"*((?:-\\d)?\\d*)"+E+"*\\)|)(?=[^-]|$)",L=new RegExp("^"+E+"+|((?:^|[^\\\\])(?:\\\\.)*)"+E+"+$","g"),M=new RegExp("^"+E+"*,"+E+"*"),N=new RegExp("^"+E+"*([\\x20\\t\\r\\n\\f>+~])"+E+"*"),O=new RegExp(J),P=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,Q=/^:not/,R=/[\x20\t\r\n\f]*[+~]/,S=/:not\($/,T=/h\d/i,U=/input|select|textarea|button/i,V=/\\(?!\\)/g,W={ID:new RegExp("^#("+F+")"),CLASS:new RegExp("^\\.("+F+")"),NAME:new RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:new RegExp("^("+F.replace("w","w*")+")"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+J),POS:new RegExp(K,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+E+"*(even|odd|(([+-]|)(\\d*)n|)"+E+"*(?:([+-]|)"+E+"*(\\d+)|))"+E+"*\\)|)","i"),needsContext:new RegExp("^"+E+"*[>+~]|"+K,"i")},X=function(a){var b=r.createElement("div");try{return a(b)}catch(c){return!1}finally{b=null}},Y=X(function(a){return a.appendChild(r.createComment("")),!a.getElementsByTagName("*").length}),Z=X(function(a){return a.innerHT
 ML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!==n&&a.firstChild.getAttribute("href")==="#"}),$=X(function(a){a.innerHTML="<select></select>";var b=typeof a.lastChild.getAttribute("multiple");return b!=="boolean"&&b!=="string"}),_=X(function(a){return a.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",!a.getElementsByClassName||!a.getElementsByClassName("e").length?!1:(a.lastChild.className="e",a.getElementsByClassName("e").length===2)}),ba=X(function(a){a.id=o+0,a.innerHTML="<a name='"+o+"'></a><div name='"+o+"'></div>",s.insertBefore(a,s.firstChild);var b=r.getElementsByName&&r.getElementsByName(o).length===2+r.getElementsByName(o+0).length;return d=!r.getElementById(o),s.removeChild(a),b});try{x.call(s.childNodes,0)[0].nodeType}catch(bb){x=function(a){var b,c=[];for(;b=this[a];a++)c.push(b);return c}}bc.matches=function(a,b){return bc(a,null,null,b)},bc.matchesSelector=function(a,b){return bc(b,null,null,[a]).length>0},f=bc.getText=
 function(a){var b,c="",d=0,e=a.nodeType;if(e){if(e===1||e===9||e===11){if(typeof a.textContent=="string")return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=f(a)}else if(e===3||e===4)return a.nodeValue}else for(;b=a[d];d++)c+=f(b);return c},g=bc.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?b.nodeName!=="HTML":!1},h=bc.contains=s.contains?function(a,b){var c=a.nodeType===9?a.documentElement:a,d=b&&b.parentNode;return a===d||!!(d&&d.nodeType===1&&c.contains&&c.contains(d))}:s.compareDocumentPosition?function(a,b){return b&&!!(a.compareDocumentPosition(b)&16)}:function(a,b){while(b=b.parentNode)if(b===a)return!0;return!1},bc.attr=function(a,b){var c,d=g(a);return d||(b=b.toLowerCase()),(c=e.attrHandle[b])?c(a):d||$?a.getAttribute(b):(c=a.getAttributeNode(b),c?typeof a[b]=="boolean"?a[b]?b:null:c.specified?c.value:null:null)},e=bc.selectors={cacheLength:50,createPseudo:z,match:W,attrHandle:Z?{}:{href:function(a){return a.getAttribute("href",2
 )},type:function(a){return a.getAttribute("type")}},find:{ID:d?function(a,b,c){if(typeof b.getElementById!==n&&!c){var d=b.getElementById(a);return d&&d.parentNode?[d]:[]}}:function(a,c,d){if(typeof c.getElementById!==n&&!d){var e=c.getElementById(a);return e?e.id===a||typeof e.getAttributeNode!==n&&e.getAttributeNode("id").value===a?[e]:b:[]}},TAG:Y?function(a,b){if(typeof b.getElementsByTagName!==n)return b.getElementsByTagName(a)}:function(a,b){var c=b.getElementsByTagName(a);if(a==="*"){var d,e=[],f=0;for(;d=c[f];f++)d.nodeType===1&&e.push(d);return e}return c},NAME:ba&&function(a,b){if(typeof b.getElementsByName!==n)return b.getElementsByName(name)},CLASS:_&&function(a,b,c){if(typeof b.getElementsByClassName!==n&&!c)return b.getElementsByClassName(a)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(V,""),a[3]=(a[4]||a[5]||"").replace(V
 ,""),a[2]==="~="&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),a[1]==="nth"?(a[2]||bc.error(a[0]),a[3]=+(a[3]?a[4]+(a[5]||1):2*(a[2]==="even"||a[2]==="odd")),a[4]=+(a[6]+a[7]||a[2]==="odd")):a[2]&&bc.error(a[0]),a},PSEUDO:function(a){var b,c;if(W.CHILD.test(a[0]))return null;if(a[3])a[2]=a[3];else if(b=a[4])O.test(b)&&(c=bh(b,!0))&&(c=b.indexOf(")",b.length-c)-b.length)&&(b=b.slice(0,c),a[0]=a[0].slice(0,c)),a[2]=b;return a.slice(0,3)}},filter:{ID:d?function(a){return a=a.replace(V,""),function(b){return b.getAttribute("id")===a}}:function(a){return a=a.replace(V,""),function(b){var c=typeof b.getAttributeNode!==n&&b.getAttributeNode("id");return c&&c.value===a}},TAG:function(a){return a==="*"?function(){return!0}:(a=a.replace(V,"").toLowerCase(),function(b){return b.nodeName&&b.nodeName.toLowerCase()===a})},CLASS:function(a){var b=B[o][a];return b||(b=B(a,new RegExp("(^|"+E+")"+a+"("+E+"|$)"))),function(a){return b.test(a.className||typ
 eof a.getAttribute!==n&&a.getAttribute("class")||"")}},ATTR:function(a,b,c){return function(d,e){var f=bc.attr(d,a);return f==null?b==="!=":b?(f+="",b==="="?f===c:b==="!="?f!==c:b==="^="?c&&f.indexOf(c)===0:b==="*="?c&&f.indexOf(c)>-1:b==="$="?c&&f.substr(f.length-c.length)===c:b==="~="?(" "+f+" ").indexOf(c)>-1:b==="|="?f===c||f.substr(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d){return a==="nth"?function(a){var b,e,f=a.parentNode;if(c===1&&d===0)return!0;if(f){e=0;for(b=f.firstChild;b;b=b.nextSibling)if(b.nodeType===1){e++;if(a===b)break}}return e-=d,e===c||e%c===0&&e/c>=0}:function(b){var c=b;switch(a){case"only":case"first":while(c=c.previousSibling)if(c.nodeType===1)return!1;if(a==="first")return!0;c=b;case"last":while(c=c.nextSibling)if(c.nodeType===1)return!1;return!0}}},PSEUDO:function(a,b){var c,d=e.pseudos[a]||e.setFilters[a.toLowerCase()]||bc.error("unsupported pseudo: "+a);return d[o]?d(b):d.length>1?(c=[a,a,"",b],e.setFilters.hasOwnProperty(a.toLowerCa
 se())?z(function(a,c){var e,f=d(a,b),g=f.length;while(g--)e=y.call(a,f[g]),a[e]=!(c[e]=f[g])}):function(a){return d(a,0,c)}):d}},pseudos:{not:z(function(a){var b=[],c=[],d=i(a.replace(L,"$1"));return d[o]?z(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)if(f=g[h])a[h]=!(b[h]=f)}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:z(function(a){return function(b){return bc(a,b).length>0}}),contains:z(function(a){return function(b){return(b.textContent||b.innerText||f(b)).indexOf(a)>-1}}),enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&!!a.checked||b==="option"&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},parent:function(a){return!e.pseudos.empty(a)},empty:function(a){var b;a=a.firstChild;while(a){if(a.nodeName>"@"||(b=a.nodeType)===3||b===4)return!1;a=a.nextSibling}return!0},header:funct
 ion(a){return T.test(a.nodeName)},text:function(a){var b,c;return a.nodeName.toLowerCase()==="input"&&(b=a.type)==="text"&&((c=a.getAttribute("type"))==null||c.toLowerCase()===b)},radio:bd("radio"),checkbox:bd("checkbox"),file:bd("file"),password:bd("password"),image:bd("image"),submit:be("submit"),reset:be("reset"),button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&a.type==="button"||b==="button"},input:function(a){return U.test(a.nodeName)},focus:function(a){var b=a.ownerDocument;return a===b.activeElement&&(!b.hasFocus||b.hasFocus())&&(!!a.type||!!a.href)},active:function(a){return a===a.ownerDocument.activeElement},first:bf(function(a,b,c){return[0]}),last:bf(function(a,b,c){return[b-1]}),eq:bf(function(a,b,c){return[c<0?c+b:c]}),even:bf(function(a,b,c){for(var d=0;d<b;d+=2)a.push(d);return a}),odd:bf(function(a,b,c){for(var d=1;d<b;d+=2)a.push(d);return a}),lt:bf(function(a,b,c){for(var d=c<0?c+b:c;--d>=0;)a.push(d);return a}),gt:bf(function(a,b,c){fo
 r(var d=c<0?c+b:c;++d<b;)a.push(d);return a})}},j=s.compareDocumentPosition?function(a,b){return a===b?(k=!0,0):(!a.compareDocumentPosition||!b.compareDocumentPosition?a.compareDocumentPosition:a.compareDocumentPosition(b)&4)?-1:1}:function(a,b){if(a===b)return k=!0,0;if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,h=b.parentNode,i=g;if(g===h)return bg(a,b);if(!g)return-1;if(!h)return 1;while(i)e.unshift(i),i=i.parentNode;i=h;while(i)f.unshift(i),i=i.parentNode;c=e.length,d=f.length;for(var j=0;j<c&&j<d;j++)if(e[j]!==f[j])return bg(e[j],f[j]);return j===c?bg(a,f[j],-1):bg(e[j],b,1)},[0,0].sort(j),m=!k,bc.uniqueSort=function(a){var b,c=1;k=m,a.sort(j);if(k)for(;b=a[c];c++)b===a[c-1]&&a.splice(c--,1);return a},bc.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},i=bc.compile=function(a,b){var c,d=[],e=[],f=D[o][a];if(!f){b||(b=bh(a)),c=b.length;while(c--)f=bm(b[c]),f[o]?d.push(f):e.push(f);f=D(a,
 bn(e,d))}return f},r.querySelectorAll&&function(){var a,b=bp,c=/'|\\/g,d=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,e=[":focus"],f=[":active",":focus"],h=s.matchesSelector||s.mozMatchesSelector||s.webkitMatchesSelector||s.oMatchesSelector||s.msMatchesSelector;X(function(a){a.innerHTML="<select><option selected=''></option></select>",a.querySelectorAll("[selected]").length||e.push("\\["+E+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),a.querySelectorAll(":checked").length||e.push(":checked")}),X(function(a){a.innerHTML="<p test=''></p>",a.querySelectorAll("[test^='']").length&&e.push("[*^$]="+E+"*(?:\"\"|'')"),a.innerHTML="<input type='hidden'/>",a.querySelectorAll(":enabled").length||e.push(":enabled",":disabled")}),e=new RegExp(e.join("|")),bp=function(a,d,f,g,h){if(!g&&!h&&(!e||!e.test(a))){var i,j,k=!0,l=o,m=d,n=d.nodeType===9&&a;if(d.nodeType===1&&d.nodeName.toLowerCase()!=="object"){i=bh(a),(k=d.getAttribute("id"))?l=k.replace(c,"\\$&"):d.setAttrib
 ute("id",l),l="[id='"+l+"'] ",j=i.length;while(j--)i[j]=l+i[j].join("");m=R.test(a)&&d.parentNode||d,n=i.join(",")}if(n)try{return w.apply(f,x.call(m.querySelectorAll(n),0)),f}catch(p){}finally{k||d.removeAttribute("id")}}return b(a,d,f,g,h)},h&&(X(function(b){a=h.call(b,"div");try{h.call(b,"[test!='']:sizzle"),f.push("!=",J)}catch(c){}}),f=new RegExp(f.join("|")),bc.matchesSelector=function(b,c){c=c.replace(d,"='$1']");if(!g(b)&&!f.test(c)&&(!e||!e.test(c)))try{var i=h.call(b,c);if(i||a||b.document&&b.document.nodeType!==11)return i}catch(j){}return bc(c,null,null,[b]).length>0})}(),e.pseudos.nth=e.pseudos.eq,e.filters=bq.prototype=e.pseudos,e.setFilters=new bq,bc.attr=p.attr,p.find=bc,p.expr=bc.selectors,p.expr[":"]=p.expr.pseudos,p.unique=bc.uniqueSort,p.text=bc.getText,p.isXMLDoc=bc.isXML,p.contains=bc.contains}(a);var bc=/Until$/,bd=/^(?:parents|prev(?:Until|All))/,be=/^.[^:#\[\.,]*$/,bf=p.expr.match.needsContext,bg={children:!0,contents:!0,next:!0,prev:!0};p.fn.extend(
 {find:function(a){var b,c,d,e,f,g,h=this;if(typeof a!="string")return p(a).filter(function(){for(b=0,c=h.length;b<c;b++)if(p.contains(h[b],this))return!0});g=this.pushStack("","find",a);for(b=0,c=this.length;b<c;b++){d=g.length,p.find(a,this[b],g);if(b>0)for(e=d;e<g.length;e++)for(f=0;f<d;f++)if(g[f]===g[e]){g.splice(e--,1);break}}return g},has:function(a){var b,c=p(a,this),d=c.length;return this.filter(function(){for(b=0;b<d;b++)if(p.contains(this,c[b]))return!0})},not:function(a){return this.pushStack(bj(this,a,!1),"not",a)},filter:function(a){return this.pushStack(bj(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?bf.test(a)?p(a,this.context).index(this[0])>=0:p.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c,d=0,e=this.length,f=[],g=bf.test(a)||typeof a!="string"?p(a,b||this.context):0;for(;d<e;d++){c=this[d];while(c&&c.ownerDocument&&c!==b&&c.nodeType!==11){if(g?g.index(c)>-1:p.find.matchesSelector(c,a)){f.push(c);brea
 k}c=c.parentNode}}return f=f.length>1?p.unique(f):f,this.pushStack(f,"closest",a)},index:function(a){return a?typeof a=="string"?p.inArray(this[0],p(a)):p.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?d:p.unique(d))},addBack:function(a){return this.add(a==null?this.prevObject:this.prevObject.filter(a))}}),p.fn.andSelf=p.fn.addBack,p.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return p.dir(a,"parentNode")},parentsUntil:function(a,b,c){return p.dir(a,"parentNode",c)},next:function(a){return bi(a,"nextSibling")},prev:function(a){return bi(a,"previousSibling")},nextAll:function(a){return p.dir(a,"nextSibling")},prevAll:function(a){return p.dir(a,"previousSibling")},nextUntil:function(a,b,c){return p.dir(a,"nextSibling",c)},prevUntil:function(a
 ,b,c){return p.dir(a,"previousSibling",c)},siblings:function(a){return p.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return p.sibling(a.firstChild)},contents:function(a){return p.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:p.merge([],a.childNodes)}},function(a,b){p.fn[a]=function(c,d){var e=p.map(this,b,c);return bc.test(a)||(d=c),d&&typeof d=="string"&&(e=p.filter(d,e)),e=this.length>1&&!bg[a]?p.unique(e):e,this.length>1&&bd.test(a)&&(e=e.reverse()),this.pushStack(e,a,k.call(arguments).join(","))}}),p.extend({filter:function(a,b,c){return c&&(a=":not("+a+")"),b.length===1?p.find.matchesSelector(b[0],a)?[b[0]]:[]:p.find.matches(a,b)},dir:function(a,c,d){var e=[],f=a[c];while(f&&f.nodeType!==9&&(d===b||f.nodeType!==1||!p(f).is(d)))f.nodeType===1&&e.push(f),f=f[c];return e},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var bl="abbr|article|aside|audio|bdi|canvas|data|datalist|details|f
 igcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",bm=/ jQuery\d+="(?:null|\d+)"/g,bn=/^\s+/,bo=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bp=/<([\w:]+)/,bq=/<tbody/i,br=/<|&#?\w+;/,bs=/<(?:script|style|link)/i,bt=/<(?:script|object|embed|option|style)/i,bu=new RegExp("<(?:"+bl+")[\\s/>]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,bz={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.ht
 mlSerialize||(bz._default=[1,"X<div>","</div>"]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).
 end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){
 var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1></$2>");try{for(;d<e;d++)c=this[d]||{},c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),c.innerHTML=a);c=0}catch(f){}}c&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(a){return bh(this[0])?this.length?this.pushStack(p(p.isFunction(a)?a():a),"replaceWith",a):this:p.isFunction(a)?this.each(function(b){var c=p(this),d=c.html();c.replaceWith(a.call(this,b,d))}):(type
 of a!="string"&&(a=p(a).detach()),this.each(function(){var b=this.nextSibling,c=this.parentNode;p(this).remove(),b?p(b).before(a):p(c).append(a)}))},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){a=[].concat.apply([],a);var e,f,g,h,i=0,j=a[0],k=[],l=this.length;if(!p.support.checkClone&&l>1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i<l;i++)d.call(c&&p.nodeName(this[i],"table")?bC(this[i],"tbody"):this[i],i===h?g:p.clone(g,!0,!0))}g=f=null,k.length&&p.each(k,function(a,b){b.src?p.ajax?p.ajax({url:b.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):p.error("no ajax"):p.globalEval((b.text||b.textContent||b.innerHTML||"").replace(by,""
 )),b.parentNode&&b.parentNode.removeChild(b)})}return this}}),p.buildFragment=function(a,c,d){var f,g,h,i=a[0];return c=c||e,c=!c.nodeType&&c[0]||c,c=c.ownerDocument||c,a.length===1&&typeof i=="string"&&i.length<512&&c===e&&i.charAt(0)==="<"&&!bt.test(i)&&(p.support.checkClone||!bw.test(i))&&(p.support.html5Clone||!bu.test(i))&&(g=!0,f=p.fragments[i],h=f!==b),f||(f=c.createDocumentFragment(),p.clean(a,c,f,d),g&&(p.fragments[i]=h&&f)),{fragment:f,cacheable:g}},p.fragments={},p.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){p.fn[a]=function(c){var d,e=0,f=[],g=p(c),h=g.length,i=this.length===1&&this[0].parentNode;if((i==null||i&&i.nodeType===11&&i.childNodes.length===1)&&h===1)return g[b](this[0]),this;for(;e<h;e++)d=(e>0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu
 .test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=b===e&&bA,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(f=0;(h=a[f])!=null;f++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{s=s||bk(b),l=b.createElement("div"),s.appendChild(l),h=h.replace(bo,"<$1></$2>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]==="<table>"&&!m?l.childNodes:[];for(g=n.length-1;g>=0;--g)p.nodeName(n[g],"tb
 ody")&&!n[g].childNodes.length&&n[g].parentNode.removeChild(n[g])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l.parentNode.removeChild(l)}h.nodeType?t.push(h):p.merge(t,h)}l&&(h=l=s=null);if(!p.support.appendChecked)for(f=0;(h=t[f])!=null;f++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(f=0;(h=t[f])!=null;f++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[f+1,0].concat(r)),f+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(
 f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.chrome?b.webkit=!0:b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=
 ([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^(none|table(?!-c[ea]).+)/,bO=/^margin/,bP=new RegExp("^("+q+")(.*)$","i"),bQ=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bR=new RegExp("^([-+])=("+q+")","i"),bS={},bT={position:"absolute",visibility:"hidden",display:"block"},bU={letterSpacing:0,fontWeight:400},bV=["Top","Right","Bottom","Left"],bW=["Webkit","O","Moz","ms"],bX=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return b$(this,!0)},hide:function(){return b$(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bX.apply(this,arguments):this.each(function(){(c?a:bZ(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":
 p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bY(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bR.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bY(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bU&&(f=bU[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(b,c){var d,e,f,g,h=a.getC
 omputedStyle(b,null),i=b.style;return h&&(d=h[c],d===""&&!p.contains(b.ownerDocument,b)&&(d=p.style(b,c)),bQ.test(d)&&bO.test(c)&&(e=i.width,f=i.minWidth,g=i.maxWidth,i.minWidth=i.maxWidth=i.width=d,d=h.width,i.width=e,i.minWidth=f,i.maxWidth=g)),d}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bQ.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth===0&&bN.test(bH(a,"display"))?p.swap(a,bT,function(){return cb(a,b,d)}):cb(a,b,d)},set:function(a,c,d){return b_(a,c,d?ca(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.curren
 tStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bQ.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hid
 den(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bV[d]+b]=e[d]||e[d-2]||e[0];return f}},bO.test(a)||(p.cssHooks[a+b].set=b_)});var cd=/%20/g,ce=/\[\]$/,cf=/\r?\n/g,cg=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ch=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ch.test(this.nodeName)||cg.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(cf,"\r\n")}}):{name:b.name,value:c.replace(cf,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b
 ():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ci(d,a[d],c,f);return e.join("&").replace(cd,"+")};var cj,ck,cl=/#.*$/,cm=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,co=/^(?:GET|HEAD)$/,cp=/^\/\//,cq=/\?/,cr=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,cs=/([?&])_=[^&]*/,ct=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,cu=p.fn.load,cv={},cw={},cx=["*/"]+["*"];try{ck=f.href}catch(cy){ck=e.createElement("a"),ck.href="",ck=ck.href}cj=ct.exec(ck.toLowerCase())||[],p.fn.load=function(a,c,d){if(typeof a!="string"&&cu)return cu.apply(this,arguments);if(!this.length)return this;var e,f,g,h=this,i=a.indexOf(" ");return i>=0&&(e=a.slice(i,a.length),a=a.slice(0,i)),p.isFunction(c)?(d=c,c=b):c&&typeof c=="object"&&(f="POST"),p.ajax({u
 rl:a,type:f,dataType:"html",data:c,complete:function(a,b){d&&h.each(d,g||[a.responseText,b,a])}}).done(function(a){g=arguments,h.html(e?p("<div>").append(a.replace(cr,"")).find(e):a)}),this},p.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){p.fn[b]=function(a){return this.on(b,a)}}),p.each(["get","post"],function(a,c){p[c]=function(a,d,e,f){return p.isFunction(d)&&(f=f||e,e=d,d=b),p.ajax({type:c,url:a,data:d,success:e,dataType:f})}}),p.extend({getScript:function(a,c){return p.get(a,b,c,"script")},getJSON:function(a,b,c){return p.get(a,b,c,"json")},ajaxSetup:function(a,b){return b?cB(a,p.ajaxSettings):(b=a,a=p.ajaxSettings),cB(a,b),a},ajaxSettings:{url:ck,isLocal:cn.test(cj[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":cx},contents:{xml:/xml/,htm
 l:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:cz(cv),ajaxTransport:cz(cw),ajax:function(a,c){function y(a,c,f,i){var k,s,t,u,w,y=c;if(v===2)return;v=2,h&&clearTimeout(h),g=b,e=i||"",x.readyState=a>0?4:0,f&&(u=cC(l,x,f));if(a>=200&&a<300||a===304)l.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(p.lastModified[d]=w),w=x.getResponseHeader("Etag"),w&&(p.etag[d]=w)),a===304?(y="notmodified",k=!0):(k=cD(l,u),y=k.state,s=k.data,t=k.error,k=!t);else{t=y;if(!y||a)y="error",a<0&&(a=0)}x.status=a,x.statusText=(c||y)+"",k?o.resolveWith(m,[s,y,x]):o.rejectWith(m,[x,y,t]),x.statusCode(r),r=b,j&&n.trigger("ajax"+(k?"Success":"Error"),[x,l,k?s:t]),q.fireWith(m,[x,y]),j&&(n.trigger("ajaxComplete",[x,l]),--p.active||p.event.trigger("ajaxStop"))}typeof a=="object"&&(c=a,a=b),c=c||{};var d,e,f,g,h,i,j,k,l=p.ajaxSetup({
 },c),m=l.context||l,n=m!==l&&(m.nodeType||m instanceof p)?p(m):p.event,o=p.Deferred(),q=p.Callbacks("once memory"),r=l.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,setRequestHeader:function(a,b){if(!v){var c=a.toLowerCase();a=u[c]=u[c]||a,t[a]=b}return this},getAllResponseHeaders:function(){return v===2?e:null},getResponseHeader:function(a){var c;if(v===2){if(!f){f={};while(c=cm.exec(e))f[c[1].toLowerCase()]=c[2]}c=f[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){return v||(l.mimeType=a),this},abort:function(a){return a=a||w,g&&g.abort(a),y(0,a),this}};o.promise(x),x.success=x.done,x.error=x.fail,x.complete=q.add,x.statusCode=function(a){if(a){var b;if(v<2)for(b in a)r[b]=[r[b],a[b]];else b=a[x.status],x.always(b)}return this},l.url=((a||l.url)+"").replace(cl,"").replace(cp,cj[1]+"//"),l.dataTypes=p.trim(l.dataType||"*").toLowerCase().split(s),l.crossDomain==null&&(i=ct.exec(l.url.toLowerCase())||!1,l.crossDomain=i&&i.join(":")+(i[3]?"":i[1]=
 =="http:"?80:443)!==cj.join(":")+(cj[3]?"":cj[1]==="http:"?80:443)),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),cA(cv,l,c,x);if(v===2)return x;j=l.global,l.type=l.type.toUpperCase(),l.hasContent=!co.test(l.type),j&&p.active++===0&&p.event.trigger("ajaxStart");if(!l.hasContent){l.data&&(l.url+=(cq.test(l.url)?"&":"?")+l.data,delete l.data),d=l.url;if(l.cache===!1){var z=p.now(),A=l.url.replace(cs,"$1_="+z);l.url=A+(A===l.url?(cq.test(l.url)?"&":"?")+"_="+z:"")}}(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",l.contentType),l.ifModified&&(d=d||l.url,p.lastModified[d]&&x.setRequestHeader("If-Modified-Since",p.lastModified[d]),p.etag[d]&&x.setRequestHeader("If-None-Match",p.etag[d])),x.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+cx+"; q=0.01":""):l.accepts["*"]);for(k in l.headers)x.setRequestHeader(k,l.headers[k]);i
 f(!l.beforeSend||l.beforeSend.call(m,x,l)!==!1&&v!==2){w="abort";for(k in{success:1,error:1,complete:1})x[k](l[k]);g=cA(cw,l,c,x);if(!g)y(-1,"No Transport");else{x.readyState=1,j&&n.trigger("ajaxSend",[x,l]),l.async&&l.timeout>0&&(h=setTimeout(function(){x.abort("timeout")},l.timeout));try{v=1,g.send(t,y)}catch(B){if(v<2)y(-1,B);else throw B}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var cE=[],cF=/\?/,cG=/(=)\?(?=&|$)|\?\?/,cH=p.now();p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=cE.pop()||p.expando+"_"+cH++;return this[a]=!0,a}}),p.ajaxPrefilter("json jsonp",function(c,d,e){var f,g,h,i=c.data,j=c.url,k=c.jsonp!==!1,l=k&&cG.test(j),m=k&&!l&&typeof i=="string"&&!(c.contentType||"").indexOf("application/x-www-form-urlencoded")&&cG.test(i);if(c.dataTypes[0]==="jsonp"||l||m)return f=c.jsonpCallback=p.isFunction(c.jsonpCallback)?c.jsonpCallback():c.jsonpCallback,g=a[f],l?c.url=j.replace(cG,"$1"+f):m?c.data=i.replace(cG,"$1"+f):k&&(c.url+=(cF.tes
 t(j)?"&":"?")+c.jsonp+"="+f),c.converters["script json"]=function(){return h||p.error(f+" was not called"),h[0]},c.dataTypes[0]="json",a[f]=function(){h=arguments},e.always(function(){a[f]=g,c[f]&&(c.jsonpCallback=d.jsonpCallback,cE.push(f)),h&&p.isFunction(g)&&g(h[0]),h=g=b}),"script"}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){return p.globalEval(a),a}}}),p.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),p.ajaxTransport("script",function(a){if(a.crossDomain){var c,d=e.head||e.getElementsByTagName("head")[0]||e.documentElement;return{send:function(f,g){c=e.createElement("script"),c.async="async",a.scriptCharset&&(c.charset=a.scriptCharset),c.src=a.url,c.onload=c.onreadystatechange=function(a,e){if(e||!c.readyState||/loaded|complete/.test(c.readyState))c.onload=
 c.onreadystatechange=null,d&&c.parentNode&&d.removeChild(c),c=b,e||g(200,"success")},d.insertBefore(c,d.firstChild)},abort:function(){c&&c.onload(0,1)}}}});var cI,cJ=a.ActiveXObject?function(){for(var a in cI)cI[a](0,1)}:!1,cK=0;p.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cL()||cM()}:cL,function(a){p.extend(p.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(p.ajaxSettings.xhr()),p.support.ajax&&p.ajaxTransport(function(c){if(!c.crossDomain||p.support.cors){var d;return{send:function(e,f){var g,h,i=c.xhr();c.username?i.open(c.type,c.url,c.async,c.username,c.password):i.open(c.type,c.url,c.async);if(c.xhrFields)for(h in c.xhrFields)i[h]=c.xhrFields[h];c.mimeType&&i.overrideMimeType&&i.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(h in e)i.setRequestHeader(h,e[h])}catch(j){}i.send(c.hasContent&&c.data||null),d=function(a,e){var h,j,k,l,m;try{if(d&&(e||i.readyState===4)){d=b,g&&(i.
 onreadystatechange=p.noop,cJ&&delete cI[g]);if(e)i.readyState!==4&&i.abort();else{h=i.status,k=i.getAllResponseHeaders(),l={},m=i.responseXML,m&&m.documentElement&&(l.xml=m);try{l.text=i.responseText}catch(a){}try{j=i.statusText}catch(n){j=""}!h&&c.isLocal&&!c.crossDomain?h=l.text?200:404:h===1223&&(h=204)}}}catch(o){e||f(-1,o)}l&&f(h,j,l,k)},c.async?i.readyState===4?setTimeout(d,0):(g=++cK,cJ&&(cI||(cI={},p(a).unload(cJ)),cI[g]=d),i.onreadystatechange=d):d()},abort:function(){d&&d(0,1)}}}});var cN,cO,cP=/^(?:toggle|show|hide)$/,cQ=new RegExp("^(?:([-+])=|)("+q+")([a-z%]*)$","i"),cR=/queueHooks$/,cS=[cY],cT={"*":[function(a,b){var c,d,e=this.createTween(a,b),f=cQ.exec(b),g=e.cur(),h=+g||0,i=1,j=20;if(f){c=+f[2],d=f[3]||(p.cssNumber[a]?"":"px");if(d!=="px"&&h){h=p.css(e.elem,a,!0)||c||1;do i=i||".5",h=h/i,p.style(e.elem,a,h+d);while(i!==(i=e.cur()/g)&&i!==1&&--j)}e.unit=d,e.start=h,e.end=f[1]?h+(f[1]+1)*c:c}return e}]};p.Animation=p.extend(cW,{tweener:function(a,b){p.isFuncti
 on(a)?(b=a,a=["*"]):a=a.split(" ");var c,d=0,e=a.length;for(;d<e;d++)c=a[d],cT[c]=cT[c]||[],cT[c].unshift(b)},prefilter:function(a,b){b?cS.unshift(a):cS.push(a)}}),p.Tween=cZ,cZ.prototype={constructor:cZ,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(p.cssNumber[c]?"":"px")},cur:function(){var a=cZ.propHooks[this.prop];return a&&a.get?a.get(this):cZ.propHooks._default.get(this)},run:function(a){var b,c=cZ.propHooks[this.prop];return this.options.duration?this.pos=b=p.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):cZ.propHooks._default.set(this),this}},cZ.prototype.init.prototype=cZ.prototype,cZ.propHooks={_default:{get:function(a){var b;return a.elem[a.prop]==null||!!a.elem.style&&a.elem.style[a.prop]!=null?(b=
 p.css(a.elem,a.prop,!1,""),!b||b==="auto"?0:b):a.elem[a.prop]},set:function(a){p.fx.step[a.prop]?p.fx.step[a.prop](a):a.elem.style&&(a.elem.style[p.cssProps[a.prop]]!=null||p.cssHooks[a.prop])?p.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},cZ.propHooks.scrollTop=cZ.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},p.each(["toggle","show","hide"],function(a,b){var c=p.fn[b];p.fn[b]=function(d,e,f){return d==null||typeof d=="boolean"||!a&&p.isFunction(d)&&p.isFunction(e)?c.apply(this,arguments):this.animate(c$(b,!0),d,e,f)}}),p.fn.extend({fadeTo:function(a,b,c,d){return this.filter(bZ).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=p.isEmptyObject(a),f=p.speed(b,c,d),g=function(){var b=cW(this,p.extend({},a),f);e&&b.stop(!0)};return e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,c,d){var e=function(a){var b=a.stop;delete a.stop,b(d)};return typeof a!="s
 tring"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,c=a!=null&&a+"queueHooks",f=p.timers,g=p._data(this);if(c)g[c]&&g[c].stop&&e(g[c]);else for(c in g)g[c]&&g[c].stop&&cR.test(c)&&e(g[c]);for(c=f.length;c--;)f[c].elem===this&&(a==null||f[c].queue===a)&&(f[c].anim.stop(d),b=!1,f.splice(c,1));(b||!d)&&p.dequeue(this,a)})}}),p.each({slideDown:c$("show"),slideUp:c$("hide"),slideToggle:c$("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){p.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),p.speed=function(a,b,c){var d=a&&typeof a=="object"?p.extend({},a):{complete:c||!c&&b||p.isFunction(a)&&a,duration:a,easing:c&&b||b&&!p.isFunction(b)&&b};d.duration=p.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in p.fx.speeds?p.fx.speeds[d.duration]:p.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";return d.old=d.complete,d.complete=function(){p.isFunction(d.old)&&d.old.call(
 this),d.queue&&p.dequeue(this,d.queue)},d},p.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},p.timers=[],p.fx=cZ.prototype.init,p.fx.tick=function(){var a,b=p.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||p.fx.stop()},p.fx.timer=function(a){a()&&p.timers.push(a)&&!cO&&(cO=setInterval(p.fx.tick,p.fx.interval))},p.fx.interval=13,p.fx.stop=function(){clearInterval(cO),cO=null},p.fx.speeds={slow:600,fast:200,_default:400},p.fx.step={},p.expr&&p.expr.filters&&(p.expr.filters.animated=function(a){return p.grep(p.timers,function(b){return a===b.elem}).length});var c_=/^(?:body|html)$/i;p.fn.offset=function(a){if(arguments.length)return a===b?this:this.each(function(b){p.offset.setOffset(this,a,b)});var c,d,e,f,g,h,i,j={top:0,left:0},k=this[0],l=k&&k.ownerDocument;if(!l)return;return(d=l.body)===k?p.offset.bodyOffset(k):(c=l.documentElement,p.contains(c,k)?(typeof k.getBoundingClientRect!="undefined"&&(j=k.get
 BoundingClientRect()),e=da(l),f=c.clientTop||d.clientTop||0,g=c.clientLeft||d.clientLeft||0,h=e.pageYOffset||c.scrollTop,i=e.pageXOffset||c.scrollLeft,{top:j.top+h-f,left:j.left+i-g}):j)},p.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;return p.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(p.css(a,"marginTop"))||0,c+=parseFloat(p.css(a,"marginLeft"))||0),{top:b,left:c}},setOffset:function(a,b,c){var d=p.css(a,"position");d==="static"&&(a.style.position="relative");var e=p(a),f=e.offset(),g=p.css(a,"top"),h=p.css(a,"left"),i=(d==="absolute"||d==="fixed")&&p.inArray("auto",[g,h])>-1,j={},k={},l,m;i?(k=e.position(),l=k.top,m=k.left):(l=parseFloat(g)||0,m=parseFloat(h)||0),p.isFunction(b)&&(b=b.call(a,c,f)),b.top!=null&&(j.top=b.top-f.top+l),b.left!=null&&(j.left=b.left-f.left+m),"using"in b?b.using.call(a,j):e.css(j)}},p.fn.extend({position:function(){if(!this[0])return;var a=this[0],b=this.offsetParent(),c=this.offset(),d=c_.test(b[0].nodeName)?{to
 p:0,left:0}:b.offset();return c.top-=parseFloat(p.css(a,"marginTop"))||0,c.left-=parseFloat(p.css(a,"marginLeft"))||0,d.top+=parseFloat(p.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(p.css(b[0],"borderLeftWidth"))||0,{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||e.body;while(a&&!c_.test(a.nodeName)&&p.css(a,"position")==="static")a=a.offsetParent;return a||e.body})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);p.fn[a]=function(e){return p.access(this,function(a,e,f){var g=da(a);if(f===b)return g?c in g?g[c]:g.document.documentElement[e]:a[e];g?g.scrollTo(d?p(g).scrollLeft():f,d?f:p(g).scrollTop()):a[e]=f},a,e,arguments.length,null)}}),p.each({Height:"height",Width:"width"},function(a,c){p.each({padding:"inner"+a,content:c,"":"outer"+a},function(d,e){p.fn[e]=function(e,f){var g=arguments.length&&(d||typeof e!="boolean"),h=d||(e===!0||f===!0?"margin":"border"
 );return p.access(this,function(c,d,e){var f;return p.isWindow(c)?c.document.documentElement["client"+a]:c.nodeType===9?(f=c.documentElement,Math.max(c.body["scroll"+a],f["scroll"+a],c.body["offset"+a],f["offset"+a],f["client"+a])):e===b?p.css(c,d,e,h):p.style(c,d,e,h)},c,g?e:b,g,null)}})}),a.jQuery=a.$=p,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return p})})(window);
\ No newline at end of file
+/*! jQuery v1.8.3 jquery.com | jquery.org/license */
+(function(e,t){function _(e){var t=M[e]={};return v.each(e.split(y),function(e,n){t[n]=!0}),t}function H(e,n,r){if(r===t&&e.nodeType===1){var i="data-"+n.replace(P,"-$1").toLowerCase();r=e.getAttribute(i);if(typeof r=="string"){try{r=r==="true"?!0:r==="false"?!1:r==="null"?null:+r+""===r?+r:D.test(r)?v.parseJSON(r):r}catch(s){}v.data(e,n,r)}else r=t}return r}function B(e){var t;for(t in e){if(t==="data"&&v.isEmptyObject(e[t]))continue;if(t!=="toJSON")return!1}return!0}function et(){return!1}function tt(){return!0}function ut(e){return!e||!e.parentNode||e.parentNode.nodeType===11}function at(e,t){do e=e[t];while(e&&e.nodeType!==1);return e}function ft(e,t,n){t=t||0;if(v.isFunction(t))return v.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return v.grep(e,function(e,r){return e===t===n});if(typeof t=="string"){var r=v.grep(e,function(e){return e.nodeType===1});if(it.test(t))return v.filter(t,r,!n);t=v.filter(t,r)}return v.grep(e,function(e,r){return v.
 inArray(e,t)>=0===n})}function lt(e){var t=ct.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function At(e,t){if(t.nodeType!==1||!v.hasData(e))return;var n,r,i,s=v._data(e),o=v._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r<i;r++)v.event.add(t,n,u[n][r])}o.data&&(o.data=v.extend({},o.data))}function Ot(e,t){var n;if(t.nodeType!==1)return;t.clearAttributes&&t.clearAttributes(),t.mergeAttributes&&t.mergeAttributes(e),n=t.nodeName.toLowerCase(),n==="object"?(t.parentNode&&(t.outerHTML=e.outerHTML),v.support.html5Clone&&e.innerHTML&&!v.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):n==="input"&&Et.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):n==="option"?t.selected=e.defaultSelected:n==="input"||n==="textarea"?t.defaultValue=e.defaul
 tValue:n==="script"&&t.text!==e.text&&(t.text=e.text),t.removeAttribute(v.expando)}function Mt(e){return typeof e.getElementsByTagName!="undefined"?e.getElementsByTagName("*"):typeof e.querySelectorAll!="undefined"?e.querySelectorAll("*"):[]}function _t(e){Et.test(e.type)&&(e.defaultChecked=e.checked)}function Qt(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Jt.length;while(i--){t=Jt[i]+n;if(t in e)return t}return r}function Gt(e,t){return e=t||e,v.css(e,"display")==="none"||!v.contains(e.ownerDocument,e)}function Yt(e,t){var n,r,i=[],s=0,o=e.length;for(;s<o;s++){n=e[s];if(!n.style)continue;i[s]=v._data(n,"olddisplay"),t?(!i[s]&&n.style.display==="none"&&(n.style.display=""),n.style.display===""&&Gt(n)&&(i[s]=v._data(n,"olddisplay",nn(n.nodeName)))):(r=Dt(n,"display"),!i[s]&&r!=="none"&&v._data(n,"olddisplay",r))}for(s=0;s<o;s++){n=e[s];if(!n.style)continue;if(!t||n.style.display==="none"||n.style.display==="")n.style.display=t?i[s]||"":"none"}retu
 rn e}function Zt(e,t,n){var r=Rt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function en(e,t,n,r){var i=n===(r?"border":"content")?4:t==="width"?1:0,s=0;for(;i<4;i+=2)n==="margin"&&(s+=v.css(e,n+$t[i],!0)),r?(n==="content"&&(s-=parseFloat(Dt(e,"padding"+$t[i]))||0),n!=="margin"&&(s-=parseFloat(Dt(e,"border"+$t[i]+"Width"))||0)):(s+=parseFloat(Dt(e,"padding"+$t[i]))||0,n!=="padding"&&(s+=parseFloat(Dt(e,"border"+$t[i]+"Width"))||0));return s}function tn(e,t,n){var r=t==="width"?e.offsetWidth:e.offsetHeight,i=!0,s=v.support.boxSizing&&v.css(e,"boxSizing")==="border-box";if(r<=0||r==null){r=Dt(e,t);if(r<0||r==null)r=e.style[t];if(Ut.test(r))return r;i=s&&(v.support.boxSizingReliable||r===e.style[t]),r=parseFloat(r)||0}return r+en(e,t,n||(s?"border":"content"),i)+"px"}function nn(e){if(Wt[e])return Wt[e];var t=v("<"+e+">").appendTo(i.body),n=t.css("display");t.remove();if(n==="none"||n===""){Pt=i.body.appendChild(Pt||v.extend(i.createElement("iframe"),{frameBorder:0,
 width:0,height:0}));if(!Ht||!Pt.createElement)Ht=(Pt.contentWindow||Pt.contentDocument).document,Ht.write("<!doctype html><html><body>"),Ht.close();t=Ht.body.appendChild(Ht.createElement(e)),n=Dt(t,"display"),i.body.removeChild(Pt)}return Wt[e]=n,n}function fn(e,t,n,r){var i;if(v.isArray(t))v.each(t,function(t,i){n||sn.test(e)?r(e,i):fn(e+"["+(typeof i=="object"?t:"")+"]",i,n,r)});else if(!n&&v.type(t)==="object")for(i in t)fn(e+"["+i+"]",t[i],n,r);else r(e,t)}function Cn(e){return function(t,n){typeof t!="string"&&(n=t,t="*");var r,i,s,o=t.toLowerCase().split(y),u=0,a=o.length;if(v.isFunction(n))for(;u<a;u++)r=o[u],s=/^\+/.test(r),s&&(r=r.substr(1)||"*"),i=e[r]=e[r]||[],i[s?"unshift":"push"](n)}}function kn(e,n,r,i,s,o){s=s||n.dataTypes[0],o=o||{},o[s]=!0;var u,a=e[s],f=0,l=a?a.length:0,c=e===Sn;for(;f<l&&(c||!u);f++)u=a[f](n,r,i),typeof u=="string"&&(!c||o[u]?u=t:(n.dataTypes.unshift(u),u=kn(e,n,r,i,u,o)));return(c||!u)&&!o["*"]&&(u=kn(e,n,r,i,"*",o)),u}function Ln(e,n){va
 r r,i,s=v.ajaxSettings.flatOptions||{};for(r in n)n[r]!==t&&((s[r]?e:i||(i={}))[r]=n[r]);i&&v.extend(!0,e,i)}function An(e,n,r){var i,s,o,u,a=e.contents,f=e.dataTypes,l=e.responseFields;for(s in l)s in r&&(n[l[s]]=r[s]);while(f[0]==="*")f.shift(),i===t&&(i=e.mimeType||n.getResponseHeader("content-type"));if(i)for(s in a)if(a[s]&&a[s].test(i)){f.unshift(s);break}if(f[0]in r)o=f[0];else{for(s in r){if(!f[0]||e.converters[s+" "+f[0]]){o=s;break}u||(u=s)}o=o||u}if(o)return o!==f[0]&&f.unshift(o),r[o]}function On(e,t){var n,r,i,s,o=e.dataTypes.slice(),u=o[0],a={},f=0;e.dataFilter&&(t=e.dataFilter(t,e.dataType));if(o[1])for(n in e.converters)a[n.toLowerCase()]=e.converters[n];for(;i=o[++f];)if(i!=="*"){if(u!=="*"&&u!==i){n=a[u+" "+i]||a["* "+i];if(!n)for(r in a){s=r.split(" ");if(s[1]===i){n=a[u+" "+s[0]]||a["* "+s[0]];if(n){n===!0?n=a[r]:a[r]!==!0&&(i=s[0],o.splice(f--,0,i));break}}}if(n!==!0)if(n&&e["throws"])t=n(t);else try{t=n(t)}catch(l){return{state:"parsererror",error:n?l:"
 No conversion from "+u+" to "+i}}}u=i}return{state:"success",data:t}}function Fn(){try{return new e.XMLHttpRequest}catch(t){}}function In(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}function $n(){return setTimeout(function(){qn=t},0),qn=v.now()}function Jn(e,t){v.each(t,function(t,n){var r=(Vn[t]||[]).concat(Vn["*"]),i=0,s=r.length;for(;i<s;i++)if(r[i].call(e,t,n))return})}function Kn(e,t,n){var r,i=0,s=0,o=Xn.length,u=v.Deferred().always(function(){delete a.elem}),a=function(){var t=qn||$n(),n=Math.max(0,f.startTime+f.duration-t),r=n/f.duration||0,i=1-r,s=0,o=f.tweens.length;for(;s<o;s++)f.tweens[s].run(i);return u.notifyWith(e,[f,i,n]),i<1&&o?n:(u.resolveWith(e,[f]),!1)},f=u.promise({elem:e,props:v.extend({},t),opts:v.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:qn||$n(),duration:n.duration,tweens:[],createTween:function(t,n,r){var i=v.Tween(e,f.opts,t,n,f.opts.specialEasing[t]||f.opts.easing);return f.tweens.push(i)
 ,i},stop:function(t){var n=0,r=t?f.tweens.length:0;for(;n<r;n++)f.tweens[n].run(1);return t?u.resolveWith(e,[f,t]):u.rejectWith(e,[f,t]),this}}),l=f.props;Qn(l,f.opts.specialEasing);for(;i<o;i++){r=Xn[i].call(f,e,l,f.opts);if(r)return r}return Jn(f,l),v.isFunction(f.opts.start)&&f.opts.start.call(e,f),v.fx.timer(v.extend(a,{anim:f,queue:f.opts.queue,elem:e})),f.progress(f.opts.progress).done(f.opts.done,f.opts.complete).fail(f.opts.fail).always(f.opts.always)}function Qn(e,t){var n,r,i,s,o;for(n in e){r=v.camelCase(n),i=t[r],s=e[n],v.isArray(s)&&(i=s[1],s=e[n]=s[0]),n!==r&&(e[r]=s,delete e[n]),o=v.cssHooks[r];if(o&&"expand"in o){s=o.expand(s),delete e[r];for(n in s)n in e||(e[n]=s[n],t[n]=i)}else t[r]=i}}function Gn(e,t,n){var r,i,s,o,u,a,f,l,c,h=this,p=e.style,d={},m=[],g=e.nodeType&&Gt(e);n.queue||(l=v._queueHooks(e,"fx"),l.unqueued==null&&(l.unqueued=0,c=l.empty.fire,l.empty.fire=function(){l.unqueued||c()}),l.unqueued++,h.always(function(){h.always(function(){l.unqueued-
 -,v.queue(e,"fx").length||l.empty.fire()})})),e.nodeType===1&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],v.css(e,"display")==="inline"&&v.css(e,"float")==="none"&&(!v.support.inlineBlockNeedsLayout||nn(e.nodeName)==="inline"?p.display="inline-block":p.zoom=1)),n.overflow&&(p.overflow="hidden",v.support.shrinkWrapBlocks||h.done(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t){s=t[r];if(Un.exec(s)){delete t[r],a=a||s==="toggle";if(s===(g?"hide":"show"))continue;m.push(r)}}o=m.length;if(o){u=v._data(e,"fxshow")||v._data(e,"fxshow",{}),"hidden"in u&&(g=u.hidden),a&&(u.hidden=!g),g?v(e).show():h.done(function(){v(e).hide()}),h.done(function(){var t;v.removeData(e,"fxshow",!0);for(t in d)v.style(e,t,d[t])});for(r=0;r<o;r++)i=m[r],f=h.createTween(i,g?u[i]:0),d[i]=u[i]||v.style(e,i),i in u||(u[i]=f.start,g&&(f.end=f.start,f.start=i==="width"||i==="height"?1:0))}}function Yn(e,t,n,r,i){return new
  Yn.prototype.init(e,t,n,r,i)}function Zn(e,t){var n,r={height:e},i=0;t=t?1:0;for(;i<4;i+=2-t)n=$t[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}function tr(e){return v.isWindow(e)?e:e.nodeType===9?e.defaultView||e.parentWindow:!1}var n,r,i=e.document,s=e.location,o=e.navigator,u=e.jQuery,a=e.$,f=Array.prototype.push,l=Array.prototype.slice,c=Array.prototype.indexOf,h=Object.prototype.toString,p=Object.prototype.hasOwnProperty,d=String.prototype.trim,v=function(e,t){return new v.fn.init(e,t,n)},m=/[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,g=/\S/,y=/\s+/,b=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,w=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,E=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,S=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,T=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,N=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,C=/^-ms-/,k=/-([\da-z])/gi,L=function(e,t){return(t+"").toUpperCase()},A=function(){i.addEventListener?(i.removeEventListener("DOMConte
 ntLoaded",A,!1),v.ready()):i.readyState==="complete"&&(i.detachEvent("onreadystatechange",A),v.ready())},O={};v.fn=v.prototype={constructor:v,init:function(e,n,r){var s,o,u,a;if(!e)return this;if(e.nodeType)return this.context=this[0]=e,this.length=1,this;if(typeof e=="string"){e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3?s=[null,e,null]:s=w.exec(e);if(s&&(s[1]||!n)){if(s[1])return n=n instanceof v?n[0]:n,a=n&&n.nodeType?n.ownerDocument||n:i,e=v.parseHTML(s[1],a,!0),E.test(s[1])&&v.isPlainObject(n)&&this.attr.call(e,n,!0),v.merge(this,e);o=i.getElementById(s[2]);if(o&&o.parentNode){if(o.id!==s[2])return r.find(e);this.length=1,this[0]=o}return this.context=i,this.selector=e,this}return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e)}return v.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),v.makeArray(e,this))},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return l.ca
 ll(this)},get:function(e){return e==null?this.toArray():e<0?this[this.length+e]:this[e]},pushStack:function(e,t,n){var r=v.merge(this.constructor(),e);return r.prevObject=this,r.context=this.context,t==="find"?r.selector=this.selector+(this.selector?" ":"")+n:t&&(r.selector=this.selector+"."+t+"("+n+")"),r},each:function(e,t){return v.each(this,e,t)},ready:function(e){return v.ready.promise().done(e),this},eq:function(e){return e=+e,e===-1?this.slice(e):this.slice(e,e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(l.apply(this,arguments),"slice",l.call(arguments).join(","))},map:function(e){return this.pushStack(v.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:[].sort,splice:[].splice},v.fn.init.prototype=v.fn,v.extend=v.fn.extend=function(){var e,n,r,i,s,o,u=arguments[0]||{},a=1,f=arguments.length,l=!1;typeof u=="boolean"&&(l=u,u=ar
 guments[1]||{},a=2),typeof u!="object"&&!v.isFunction(u)&&(u={}),f===a&&(u=this,--a);for(;a<f;a++)if((e=arguments[a])!=null)for(n in e){r=u[n],i=e[n];if(u===i)continue;l&&i&&(v.isPlainObject(i)||(s=v.isArray(i)))?(s?(s=!1,o=r&&v.isArray(r)?r:[]):o=r&&v.isPlainObject(r)?r:{},u[n]=v.extend(l,o,i)):i!==t&&(u[n]=i)}return u},v.extend({noConflict:function(t){return e.$===v&&(e.$=a),t&&e.jQuery===v&&(e.jQuery=u),v},isReady:!1,readyWait:1,holdReady:function(e){e?v.readyWait++:v.ready(!0)},ready:function(e){if(e===!0?--v.readyWait:v.isReady)return;if(!i.body)return setTimeout(v.ready,1);v.isReady=!0;if(e!==!0&&--v.readyWait>0)return;r.resolveWith(i,[v]),v.fn.trigger&&v(i).trigger("ready").off("ready")},isFunction:function(e){return v.type(e)==="function"},isArray:Array.isArray||function(e){return v.type(e)==="array"},isWindow:function(e){return e!=null&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return e==null?String(e):O[h.call(e)]
 ||"object"},isPlainObject:function(e){if(!e||v.type(e)!=="object"||e.nodeType||v.isWindow(e))return!1;try{if(e.constructor&&!p.call(e,"constructor")&&!p.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||p.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw new Error(e)},parseHTML:function(e,t,n){var r;return!e||typeof e!="string"?null:(typeof t=="boolean"&&(n=t,t=0),t=t||i,(r=E.exec(e))?[t.createElement(r[1])]:(r=v.buildFragment([e],t,n?null:[]),v.merge([],(r.cacheable?v.clone(r.fragment):r.fragment).childNodes)))},parseJSON:function(t){if(!t||typeof t!="string")return null;t=v.trim(t);if(e.JSON&&e.JSON.parse)return e.JSON.parse(t);if(S.test(t.replace(T,"@").replace(N,"]").replace(x,"")))return(new Function("return "+t))();v.error("Invalid JSON: "+t)},parseXML:function(n){var r,i;if(!n||typeof n!="string")return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text
 /xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(s){r=t}return(!r||!r.documentElement||r.getElementsByTagName("parsererror").length)&&v.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&g.test(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(C,"ms-").replace(k,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,n,r){var i,s=0,o=e.length,u=o===t||v.isFunction(e);if(r){if(u){for(i in e)if(n.apply(e[i],r)===!1)break}else for(;s<o;)if(n.apply(e[s++],r)===!1)break}else if(u){for(i in e)if(n.call(e[i],i,e[i])===!1)break}else for(;s<o;)if(n.call(e[s],s,e[s++])===!1)break;return e},trim:d&&!d.call("\ufeff\u00a0")?function(e){return e==null?"":d.call(e)}:function(e){return e==null?"":(e+"").replace(b,"")},makeArray:function(e,t){var n,r=t||[];return e!=null&&(n=v.type(e),e.length==null||n==="string"||n==="function"||n==="regexp"||
 v.isWindow(e)?f.call(r,e):v.merge(r,e)),r},inArray:function(e,t,n){var r;if(t){if(c)return c.call(t,e,n);r=t.length,n=n?n<0?Math.max(0,r+n):n:0;for(;n<r;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,s=0;if(typeof r=="number")for(;s<r;s++)e[i++]=n[s];else while(n[s]!==t)e[i++]=n[s++];return e.length=i,e},grep:function(e,t,n){var r,i=[],s=0,o=e.length;n=!!n;for(;s<o;s++)r=!!t(e[s],s),n!==r&&i.push(e[s]);return i},map:function(e,n,r){var i,s,o=[],u=0,a=e.length,f=e instanceof v||a!==t&&typeof a=="number"&&(a>0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u<a;u++)i=n(e[u],u,r),i!=null&&(o[o.length]=i);else for(s in e)i=n(e[s],s,r),i!=null&&(o[o.length]=i);return o.concat.apply([],o)},guid:1,proxy:function(e,n){var r,i,s;return typeof n=="string"&&(r=e[n],n=e,e=r),v.isFunction(e)?(i=l.call(arguments,2),s=function(){return e.apply(n,i.concat(l.call(arguments)))},s.guid=e.guid=e.guid||v.guid++,s):t},access:function(e,n,r,i,s,o,u){var a,f=
 r==null,l=0,c=e.length;if(r&&typeof r=="object"){for(l in r)v.access(e,n,l,r[l],1,o,i);s=1}else if(i!==t){a=u===t&&v.isFunction(i),f&&(a?(a=n,n=function(e,t,n){return a.call(v(e),n)}):(n.call(e,i),n=null));if(n)for(;l<c;l++)n(e[l],r,a?i.call(e[l],l,n(e[l],r)):i,u);s=1}return s?e:f?n.call(e):c?n(e[0],r):o},now:function(){return(new Date).getTime()}}),v.ready.promise=function(t){if(!r){r=v.Deferred();if(i.readyState==="complete")setTimeout(v.ready,1);else if(i.addEventListener)i.addEventListener("DOMContentLoaded",A,!1),e.addEventListener("load",v.ready,!1);else{i.attachEvent("onreadystatechange",A),e.attachEvent("onload",v.ready);var n=!1;try{n=e.frameElement==null&&i.documentElement}catch(s){}n&&n.doScroll&&function o(){if(!v.isReady){try{n.doScroll("left")}catch(e){return setTimeout(o,50)}v.ready()}}()}}return r.promise(t)},v.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(e,t){O["[object "+t+"]"]=t.toLowerCase()}),n=v(i);var M={};v.Callba
 cks=function(e){e=typeof e=="string"?M[e]||_(e):v.extend({},e);var n,r,i,s,o,u,a=[],f=!e.once&&[],l=function(t){n=e.memory&&t,r=!0,u=s||0,s=0,o=a.length,i=!0;for(;a&&u<o;u++)if(a[u].apply(t[0],t[1])===!1&&e.stopOnFalse){n=!1;break}i=!1,a&&(f?f.length&&l(f.shift()):n?a=[]:c.disable())},c={add:function(){if(a){var t=a.length;(function r(t){v.each(t,function(t,n){var i=v.type(n);i==="function"?(!e.unique||!c.has(n))&&a.push(n):n&&n.length&&i!=="string"&&r(n)})})(arguments),i?o=a.length:n&&(s=t,l(n))}return this},remove:function(){return a&&v.each(arguments,function(e,t){var n;while((n=v.inArray(t,a,n))>-1)a.splice(n,1),i&&(n<=o&&o--,n<=u&&u--)}),this},has:function(e){return v.inArray(e,a)>-1},empty:function(){return a=[],this},disable:function(){return a=f=n=t,this},disabled:function(){return!a},lock:function(){return f=t,n||c.disable(),this},locked:function(){return!f},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],a&&(!r||f)&&(i?f.push(t):l(t)),this},fire:fun
 ction(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},v.extend({Deferred:function(e){var t=[["resolve","done",v.Callbacks("once memory"),"resolved"],["reject","fail",v.Callbacks("once memory"),"rejected"],["notify","progress",v.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return v.Deferred(function(n){v.each(t,function(t,r){var s=r[0],o=e[t];i[r[1]](v.isFunction(o)?function(){var e=o.apply(this,arguments);e&&v.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===i?n:this,[e])}:n[s])}),e=null}).promise()},promise:function(e){return e!=null?v.extend(e,r):r}},i={};return r.pipe=r.then,v.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=o.fire,i[s[0]+"With"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e
 ){var t=0,n=l.call(arguments),r=n.length,i=r!==1||e&&v.isFunction(e.promise)?r:0,s=i===1?e:v.Deferred(),o=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?l.call(arguments):r,n===u?s.notifyWith(t,n):--i||s.resolveWith(t,n)}},u,a,f;if(r>1){u=new Array(r),a=new Array(r),f=new Array(r);for(;t<r;t++)n[t]&&v.isFunction(n[t].promise)?n[t].promise().done(o(t,f,n)).fail(s.reject).progress(o(t,a,u)):--i}return i||s.resolveWith(f,n),s.promise()}}),v.support=function(){var t,n,r,s,o,u,a,f,l,c,h,p=i.createElement("div");p.setAttribute("className","t"),p.innerHTML="  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",n=p.getElementsByTagName("*"),r=p.getElementsByTagName("a")[0];if(!n||!r||!n.length)return{};s=i.createElement("select"),o=s.appendChild(i.createElement("option")),u=p.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,
 htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:r.getAttribute("href")==="/a",opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:u.value==="on",optSelected:o.selected,getSetAttribute:p.className!=="t",enctype:!!i.createElement("form").enctype,html5Clone:i.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",boxModel:i.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},u.checked=!0,t.noCloneChecked=u.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!o.disabled;try{delete p.test}catch(d){t.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",h=function(){t.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick"),p.detachEvent("onclick",h)),u=i.createElement("input"),u.value="t",u.se
 tAttribute("type","radio"),t.radioValue=u.value==="t",u.setAttribute("checked","checked"),u.setAttribute("name","t"),p.appendChild(u),a=i.createDocumentFragment(),a.appendChild(p.lastChild),t.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,t.appendChecked=u.checked,a.removeChild(u),a.appendChild(p);if(p.attachEvent)for(l in{submit:!0,change:!0,focusin:!0})f="on"+l,c=f in p,c||(p.setAttribute(f,"return;"),c=typeof p[f]=="function"),t[l+"Bubbles"]=c;return v(function(){var n,r,s,o,u="padding:0;margin:0;border:0;display:block;overflow:hidden;",a=i.getElementsByTagName("body")[0];if(!a)return;n=i.createElement("div"),n.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",a.insertBefore(n,a.firstChild),r=i.createElement("div"),n.appendChild(r),r.innerHTML="<table><tr><td></td><td>t</td></tr></table>",s=r.getElementsByTagName("td"),s[0].style.cssText="padding:0;margin:0;border:0;display:none",c=s[0].offsetHeight===0,s[0].style
 .display="",s[1].style.display="none",t.reliableHiddenOffsets=c&&s[0].offsetHeight===0,r.innerHTML="",r.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=r.offsetWidth===4,t.doesNotIncludeMarginInBodyOffset=a.offsetTop!==1,e.getComputedStyle&&(t.pixelPosition=(e.getComputedStyle(r,null)||{}).top!=="1%",t.boxSizingReliable=(e.getComputedStyle(r,null)||{width:"4px"}).width==="4px",o=i.createElement("div"),o.style.cssText=r.style.cssText=u,o.style.marginRight=o.style.width="0",r.style.width="1px",r.appendChild(o),t.reliableMarginRight=!parseFloat((e.getComputedStyle(o,null)||{}).marginRight)),typeof r.style.zoom!="undefined"&&(r.innerHTML="",r.style.cssText=u+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=r.offsetWidth===3,r.style.display="block",r.style.overflow="visible",r.innerHTML="<div></div>",r.firstChild.s
 tyle.width="5px",t.shrinkWrapBlocks=r.offsetWidth!==3,n.style.zoom=1),a.removeChild(n),n=r=s=o=null}),a.removeChild(p),n=r=s=o=u=a=p=null,t}();var D=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;v.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(v.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?v.cache[e[v.expando]]:e[v.expando],!!e&&!B(e)},data:function(e,n,r,i){if(!v.acceptData(e))return;var s,o,u=v.expando,a=typeof n=="string",f=e.nodeType,l=f?v.cache:e,c=f?e[u]:e[u]&&u;if((!c||!l[c]||!i&&!l[c].data)&&a&&r===t)return;c||(f?e[u]=c=v.deletedIds.pop()||v.guid++:c=u),l[c]||(l[c]={},f||(l[c].toJSON=v.noop));if(typeof n=="object"||typeof n=="function")i?l[c]=v.extend(l[c],n):l[c].data=v.extend(l[c].data,n);return s=l[c],i||(s.data||(s.data={}),s=s.data),r!==t&&(s[v.camelCase(n)]=r),a?(o=s[n],o==null&&(o=s[v.camelCase(n)])):o=s,o},removeData:function(e,t,n){if(
 !v.acceptData(e))return;var r,i,s,o=e.nodeType,u=o?v.cache:e,a=o?e[v.expando]:v.expando;if(!u[a])return;if(t){r=n?u[a]:u[a].data;if(r){v.isArray(t)||(t in r?t=[t]:(t=v.camelCase(t),t in r?t=[t]:t=t.split(" ")));for(i=0,s=t.length;i<s;i++)delete r[t[i]];if(!(n?B:v.isEmptyObject)(r))return}}if(!n){delete u[a].data;if(!B(u[a]))return}o?v.cleanData([e],!0):v.support.deleteExpando||u!=u.window?delete u[a]:u[a]=null},_data:function(e,t,n){return v.data(e,t,n,!0)},acceptData:function(e){var t=e.nodeName&&v.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),v.fn.extend({data:function(e,n){var r,i,s,o,u,a=this[0],f=0,l=null;if(e===t){if(this.length){l=v.data(a);if(a.nodeType===1&&!v._data(a,"parsedAttrs")){s=a.attributes;for(u=s.length;f<u;f++)o=s[f].name,o.indexOf("data-")||(o=v.camelCase(o.substring(5)),H(a,o,l[o]));v._data(a,"parsedAttrs",!0)}}return l}return typeof e=="object"?this.each(function(){v.data(this,e)}):(r=e.split(".",2),r[1]=r[1]?"."+r
 [1]:"",i=r[1]+"!",v.access(this,function(n){if(n===t)return l=this.triggerHandler("getData"+i,[r[0]]),l===t&&a&&(l=v.data(a,e),l=H(a,e,l)),l===t&&r[1]?this.data(r[0]):l;r[1]=n,this.each(function(){var t=v(this);t.triggerHandler("setData"+i,r),v.data(this,e,n),t.triggerHandler("changeData"+i,r)})},null,n,arguments.length>1,null,!1))},removeData:function(e){return this.each(function(){v.removeData(this,e)})}}),v.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=v._data(e,t),n&&(!r||v.isArray(n)?r=v._data(e,t,v.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=v.queue(e,t),r=n.length,i=n.shift(),s=v._queueHooks(e,t),o=function(){v.dequeue(e,t)};i==="inprogress"&&(i=n.shift(),r--),i&&(t==="fx"&&n.unshift("inprogress"),delete s.stop,i.call(e,o,s)),!r&&s&&s.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return v._data(e,n)||v._data(e,n,{empty:v.Callbacks("once memory").add(function(){v.removeData(e,t+"queue",!0),v.removeData(e,
 n,!0)})})}}),v.fn.extend({queue:function(e,n){var r=2;return typeof e!="string"&&(n=e,e="fx",r--),arguments.length<r?v.queue(this[0],e):n===t?this:this.each(function(){var t=v.queue(this,e,n);v._queueHooks(this,e),e==="fx"&&t[0]!=="inprogress"&&v.dequeue(this,e)})},dequeue:function(e){return this.each(function(){v.dequeue(this,e)})},delay:function(e,t){return e=v.fx?v.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,s=v.Deferred(),o=this,u=this.length,a=function(){--i||s.resolveWith(o,[o])};typeof e!="string"&&(n=e,e=t),e=e||"fx";while(u--)r=v._data(o[u],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(a));return a(),s.promise(n)}});var j,F,I,q=/[\t\r\n]/g,R=/\r/g,U=/^(?:button|input)$/i,z=/^(?:button|input|object|select|textarea)$/i,W=/^a(?:rea|)$/i,X=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|mult
 iple|open|readonly|required|scoped|selected)$/i,V=v.support.getSetAttribute;v.fn.extend({attr:function(e,t){return v.access(this,v.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){v.removeAttr(this,e)})},prop:function(e,t){return v.access(this,v.prop,e,t,arguments.length>1)},removeProp:function(e){return e=v.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,s,o,u;if(v.isFunction(e))return this.each(function(t){v(this).addClass(e.call(this,t,this.className))});if(e&&typeof e=="string"){t=e.split(y);for(n=0,r=this.length;n<r;n++){i=this[n];if(i.nodeType===1)if(!i.className&&t.length===1)i.className=e;else{s=" "+i.className+" ";for(o=0,u=t.length;o<u;o++)s.indexOf(" "+t[o]+" ")<0&&(s+=t[o]+" ");i.className=v.trim(s)}}}return this},removeClass:function(e){var n,r,i,s,o,u,a;if(v.isFunction(e))return this.each(function(t){v(this).removeClass(e.call(this,t,this.className))});if(e&&typeof e=
 ="string"||e===t){n=(e||"").split(y);for(u=0,a=this.length;u<a;u++){i=this[u];if(i.nodeType===1&&i.className){r=(" "+i.className+" ").replace(q," ");for(s=0,o=n.length;s<o;s++)while(r.indexOf(" "+n[s]+" ")>=0)r=r.replace(" "+n[s]+" "," ");i.className=e?v.trim(r):""}}}return this},toggleClass:function(e,t){var n=typeof e,r=typeof t=="boolean";return v.isFunction(e)?this.each(function(n){v(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if(n==="string"){var i,s=0,o=v(this),u=t,a=e.split(y);while(i=a[s++])u=r?u:!o.hasClass(i),o[u?"addClass":"removeClass"](i)}else if(n==="undefined"||n==="boolean")this.className&&v._data(this,"__className__",this.className),this.className=this.className||e===!1?"":v._data(this,"__className__")||""})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;n<r;n++)if(this[n].nodeType===1&&(" "+this[n].className+" ").replace(q," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,s=this[0];if(!arguments.le
 ngth){if(s)return n=v.valHooks[s.type]||v.valHooks[s.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(s,"value"))!==t?r:(r=s.value,typeof r=="string"?r.replace(R,""):r==null?"":r);return}return i=v.isFunction(e),this.each(function(r){var s,o=v(this);if(this.nodeType!==1)return;i?s=e.call(this,r,o.val()):s=e,s==null?s="":typeof s=="number"?s+="":v.isArray(s)&&(s=v.map(s,function(e){return e==null?"":e+""})),n=v.valHooks[this.type]||v.valHooks[this.nodeName.toLowerCase()];if(!n||!("set"in n)||n.set(this,s,"value")===t)this.value=s})}}),v.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,s=e.type==="select-one"||i<0,o=s?null:[],u=s?i+1:r.length,a=i<0?u:s?i:0;for(;a<u;a++){n=r[a];if((n.selected||a===i)&&(v.support.optDisabled?!n.disabled:n.getAttribute("disabled")===null)&&(!n.parentNode.disabled||!v.nodeName(n.parentNode,"optgroup"))){t=v(n).val();if(s)return t;o.
 push(t)}}return o},set:function(e,t){var n=v.makeArray(t);return v(e).find("option").each(function(){this.selected=v.inArray(v(this).val(),n)>=0}),n.length||(e.selectedIndex=-1),n}}},attrFn:{},attr:function(e,n,r,i){var s,o,u,a=e.nodeType;if(!e||a===3||a===8||a===2)return;if(i&&v.isFunction(v.fn[n]))return v(e)[n](r);if(typeof e.getAttribute=="undefined")return v.prop(e,n,r);u=a!==1||!v.isXMLDoc(e),u&&(n=n.toLowerCase(),o=v.attrHooks[n]||(X.test(n)?F:j));if(r!==t){if(r===null){v.removeAttr(e,n);return}return o&&"set"in o&&u&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r)}return o&&"get"in o&&u&&(s=o.get(e,n))!==null?s:(s=e.getAttribute(n),s===null?t:s)},removeAttr:function(e,t){var n,r,i,s,o=0;if(t&&e.nodeType===1){r=t.split(y);for(;o<r.length;o++)i=r[o],i&&(n=v.propFix[i]||i,s=X.test(i),s||v.attr(e,i,""),e.removeAttribute(V?i:n),s&&n in e&&(e[n]=!1))}},attrHooks:{type:{set:function(e,t){if(U.test(e.nodeName)&&e.parentNode)v.error("type property can't be changed");else if
 (!v.support.radioValue&&t==="radio"&&v.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}},value:{get:function(e,t){return j&&v.nodeName(e,"button")?j.get(e,t):t in e?e.value:null},set:function(e,t,n){if(j&&v.nodeName(e,"button"))return j.set(e,t,n);e.value=t}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(e,n,r){var i,s,o,u=e.nodeType;if(!e||u===3||u===8||u===2)return;return o=u!==1||!v.isXMLDoc(e),o&&(n=v.propFix[n]||n,s=v.propHooks[n]),r!==t?s&&"set"in s&&(i=s.set(e,r,n))!==t?i:e[n]=r:s&&"get"in s&&(i=s.get(e,n))!==null?i:e[n]},propHooks:{tabIndex:{get:function(e){var n=e.getAttributeNode("tabindex");return n&&n.specified?parseInt(n.value,10):z.test(e.nodeName)||W.test(e.nodeName)&&e.href?0:t}}}}),F={
 get:function(e,n){var r,i=v.prop(e,n);return i===!0||typeof i!="boolean"&&(r=e.getAttributeNode(n))&&r.nodeValue!==!1?n.toLowerCase():t},set:function(e,t,n){var r;return t===!1?v.removeAttr(e,n):(r=v.propFix[n]||n,r in e&&(e[r]=!0),e.setAttribute(n,n.toLowerCase())),n}},V||(I={name:!0,id:!0,coords:!0},j=v.valHooks.button={get:function(e,n){var r;return r=e.getAttributeNode(n),r&&(I[n]?r.value!=="":r.specified)?r.value:t},set:function(e,t,n){var r=e.getAttributeNode(n);return r||(r=i.createAttribute(n),e.setAttributeNode(r)),r.value=t+""}},v.each(["width","height"],function(e,t){v.attrHooks[t]=v.extend(v.attrHooks[t],{set:function(e,n){if(n==="")return e.setAttribute(t,"auto"),n}})}),v.attrHooks.contenteditable={get:j.get,set:function(e,t,n){t===""&&(t="false"),j.set(e,t,n)}}),v.support.hrefNormalized||v.each(["href","src","width","height"],function(e,n){v.attrHooks[n]=v.extend(v.attrHooks[n],{get:function(e){var r=e.getAttribute(n,2);return r===null?t:r}})}),v.support.style|
 |(v.attrHooks.style={get:function(e){return e.style.cssText.toLowerCase()||t},set:function(e,t){return e.style.cssText=t+""}}),v.support.optSelected||(v.propHooks.selected=v.extend(v.propHooks.selected,{get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}})),v.support.enctype||(v.propFix.enctype="encoding"),v.support.checkOn||v.each(["radio","checkbox"],function(){v.valHooks[this]={get:function(e){return e.getAttribute("value")===null?"on":e.value}}}),v.each(["radio","checkbox"],function(){v.valHooks[this]=v.extend(v.valHooks[this],{set:function(e,t){if(v.isArray(t))return e.checked=v.inArray(v(e).val(),t)>=0}})});var $=/^(?:textarea|input|select)$/i,J=/^([^\.]*|)(?:\.(.+)|)$/,K=/(?:^|\s)hover(\.\S+|)\b/,Q=/^key/,G=/^(?:mouse|contextmenu)|click/,Y=/^(?:focusinfocus|focusoutblur)$/,Z=function(e){return v.event.special.hover?e:e.replace(K,"mouseenter$1 mouseleave$1")};v.event={add:function(e,n,r,i,s){var o,u,a,f,l,c,h,p,
 d,m,g;if(e.nodeType===3||e.nodeType===8||!n||!r||!(o=v._data(e)))return;r.handler&&(d=r,r=d.handler,s=d.selector),r.guid||(r.guid=v.guid++),a=o.events,a||(o.events=a={}),u=o.handle,u||(o.handle=u=function(e){return typeof v=="undefined"||!!e&&v.event.triggered===e.type?t:v.event.dispatch.apply(u.elem,arguments)},u.elem=e),n=v.trim(Z(n)).split(" ");for(f=0;f<n.length;f++){l=J.exec(n[f])||[],c=l[1],h=(l[2]||"").split(".").sort(),g=v.event.special[c]||{},c=(s?g.delegateType:g.bindType)||c,g=v.event.special[c]||{},p=v.extend({type:c,origType:l[1],data:i,handler:r,guid:r.guid,selector:s,needsContext:s&&v.expr.match.needsContext.test(s),namespace:h.join(".")},d),m=a[c];if(!m){m=a[c]=[],m.delegateCount=0;if(!g.setup||g.setup.call(e,i,h,u)===!1)e.addEventListener?e.addEventListener(c,u,!1):e.attachEvent&&e.attachEvent("on"+c,u)}g.add&&(g.add.call(e,p),p.handler.guid||(p.handler.guid=r.guid)),s?m.splice(m.delegateCount++,0,p):m.push(p),v.event.global[c]=!0}e=null},global:{},remove:fu
 nction(e,t,n,r,i){var s,o,u,a,f,l,c,h,p,d,m,g=v.hasData(e)&&v._data(e);if(!g||!(h=g.events))return;t=v.trim(Z(t||"")).split(" ");for(s=0;s<t.length;s++){o=J.exec(t[s])||[],u=a=o[1],f=o[2];if(!u){for(u in h)v.event.remove(e,u+t[s],n,r,!0);continue}p=v.event.special[u]||{},u=(r?p.delegateType:p.bindType)||u,d=h[u]||[],l=d.length,f=f?new RegExp("(^|\\.)"+f.split(".").sort().join("\\.(?:.*\\.|)")+"(\\.|$)"):null;for(c=0;c<d.length;c++)m=d[c],(i||a===m.origType)&&(!n||n.guid===m.guid)&&(!f||f.test(m.namespace))&&(!r||r===m.selector||r==="**"&&m.selector)&&(d.splice(c--,1),m.selector&&d.delegateCount--,p.remove&&p.remove.call(e,m));d.length===0&&l!==d.length&&((!p.teardown||p.teardown.call(e,f,g.handle)===!1)&&v.removeEvent(e,u,g.handle),delete h[u])}v.isEmptyObject(h)&&(delete g.handle,v.removeData(e,"events",!0))},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(n,r,s,o){if(!s||s.nodeType!==3&&s.nodeType!==8){var u,a,f,l,c,h,p,d,m,g,y=n.type||n,b=[];if(Y.test(y
 +v.event.triggered))return;y.indexOf("!")>=0&&(y=y.slice(0,-1),a=!0),y.indexOf(".")>=0&&(b=y.split("."),y=b.shift(),b.sort());if((!s||v.event.customEvent[y])&&!v.event.global[y])return;n=typeof n=="object"?n[v.expando]?n:new v.Event(y,n):new v.Event(y),n.type=y,n.isTrigger=!0,n.exclusive=a,n.namespace=b.join("."),n.namespace_re=n.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,h=y.indexOf(":")<0?"on"+y:"";if(!s){u=v.cache;for(f in u)u[f].events&&u[f].events[y]&&v.event.trigger(n,r,u[f].handle.elem,!0);return}n.result=t,n.target||(n.target=s),r=r!=null?v.makeArray(r):[],r.unshift(n),p=v.event.special[y]||{};if(p.trigger&&p.trigger.apply(s,r)===!1)return;m=[[s,p.bindType||y]];if(!o&&!p.noBubble&&!v.isWindow(s)){g=p.delegateType||y,l=Y.test(g+y)?s:s.parentNode;for(c=s;l;l=l.parentNode)m.push([l,g]),c=l;c===(s.ownerDocument||i)&&m.push([c.defaultView||c.parentWindow||e,g])}for(f=0;f<m.length&&!n.isPropagationStopped();f++)l=m[f][0],n.type=m[f][1],d=(v._dat
 a(l,"events")||{})[n.type]&&v._data(l,"handle"),d&&d.apply(l,r),d=h&&l[h],d&&v.acceptData(l)&&d.apply&&d.apply(l,r)===!1&&n.preventDefault();return n.type=y,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(s.ownerDocument,r)===!1)&&(y!=="click"||!v.nodeName(s,"a"))&&v.acceptData(s)&&h&&s[y]&&(y!=="focus"&&y!=="blur"||n.target.offsetWidth!==0)&&!v.isWindow(s)&&(c=s[h],c&&(s[h]=null),v.event.triggered=y,s[y](),v.event.triggered=t,c&&(s[h]=c)),n.result}return},dispatch:function(n){n=v.event.fix(n||e.event);var r,i,s,o,u,a,f,c,h,p,d=(v._data(this,"events")||{})[n.type]||[],m=d.delegateCount,g=l.call(arguments),y=!n.exclusive&&!n.namespace,b=v.event.special[n.type]||{},w=[];g[0]=n,n.delegateTarget=this;if(b.preDispatch&&b.preDispatch.call(this,n)===!1)return;if(m&&(!n.button||n.type!=="click"))for(s=n.target;s!=this;s=s.parentNode||this)if(s.disabled!==!0||n.type!=="click"){u={},f=[];for(r=0;r<m;r++)c=d[r],h=c.selector,u[h]===t&&(u[h]=c.needsContext?v(h,this).index(s)>
 =0:v.find(h,this,null,[s]).length),u[h]&&f.push(c);f.length&&w.push({elem:s,matches:f})}d.length>m&&w.push({elem:this,matches:d.slice(m)});for(r=0;r<w.length&&!n.isPropagationStopped();r++){a=w[r],n.currentTarget=a.elem;for(i=0;i<a.matches.length&&!n.isImmediatePropagationStopped();i++){c=a.matches[i];if(y||!n.namespace&&!c.namespace||n.namespace_re&&n.namespace_re.test(c.namespace))n.data=c.data,n.handleObj=c,o=((v.event.special[c.origType]||{}).handle||c.handler).apply(a.elem,g),o!==t&&(n.result=o,o===!1&&(n.preventDefault(),n.stopPropagation()))}}return b.postDispatch&&b.postDispatch.call(this,n),n.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return e.which==null&&(e.which=t.charCode!=null?t.charCode:t.keyCode),e}},mouseHooks:{props:"button butt
 ons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,s,o,u=n.button,a=n.fromElement;return e.pageX==null&&n.clientX!=null&&(r=e.target.ownerDocument||i,s=r.documentElement,o=r.body,e.pageX=n.clientX+(s&&s.scrollLeft||o&&o.scrollLeft||0)-(s&&s.clientLeft||o&&o.clientLeft||0),e.pageY=n.clientY+(s&&s.scrollTop||o&&o.scrollTop||0)-(s&&s.clientTop||o&&o.clientTop||0)),!e.relatedTarget&&a&&(e.relatedTarget=a===e.target?n.toElement:a),!e.which&&u!==t&&(e.which=u&1?1:u&2?3:u&4?2:0),e}},fix:function(e){if(e[v.expando])return e;var t,n,r=e,s=v.event.fixHooks[e.type]||{},o=s.props?this.props.concat(s.props):this.props;e=v.Event(r);for(t=o.length;t;)n=o[--t],e[n]=r[n];return e.target||(e.target=r.srcElement||i),e.target.nodeType===3&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,r):e},special:{load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{se
 tup:function(e,t,n){v.isWindow(this)&&(this.onbeforeunload=n)},teardown:function(e,t){this.onbeforeunload===t&&(this.onbeforeunload=null)}}},simulate:function(e,t,n,r){var i=v.extend(new v.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?v.event.trigger(i,null,t):v.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},v.event.handle=v.event.dispatch,v.removeEvent=i.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]=="undefined"&&(e[r]=null),e.detachEvent(r,n))},v.Event=function(e,t){if(!(this instanceof v.Event))return new v.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?tt:et):this.type=e,t&&v.extend(this,t),this.timeStamp=e&&e.timeStamp||v.now(),this[v.expando]=!0},v.Event.prototype={preventDefault:function(){this.isDefaultPrevented=tt;var e=
 this.originalEvent;if(!e)return;e.preventDefault?e.preventDefault():e.returnValue=!1},stopPropagation:function(){this.isPropagationStopped=tt;var e=this.originalEvent;if(!e)return;e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=tt,this.stopPropagation()},isDefaultPrevented:et,isPropagationStopped:et,isImmediatePropagationStopped:et},v.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){v.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,s=e.handleObj,o=s.selector;if(!i||i!==r&&!v.contains(r,i))e.type=s.origType,n=s.handler.apply(this,arguments),e.type=t;return n}}}),v.support.submitBubbles||(v.event.special.submit={setup:function(){if(v.nodeName(this,"form"))return!1;v.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=v.nodeName(n,"input")||v.nodeName(n,"button")?n.form:t;r&&!v._data(r,"_submit_attached")&&(v.ev
 ent.add(r,"submit._submit",function(e){e._submit_bubble=!0}),v._data(r,"_submit_attached",!0))})},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&v.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){if(v.nodeName(this,"form"))return!1;v.event.remove(this,"._submit")}}),v.support.changeBubbles||(v.event.special.change={setup:function(){if($.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")v.event.add(this,"propertychange._change",function(e){e.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),v.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),v.event.simulate("change",this,e,!0)});return!1}v.event.add(this,"beforeactivate._change",function(e){var t=e.target;$.test(t.nodeName)&&!v._data(t,"_change_attached")&&(v.event.add(t,"change._change",function(e){this.parentNode&&!e.isSimulated&&!e.isTrigger&&v.event.simulate("change",
 this.parentNode,e,!0)}),v._data(t,"_change_attached",!0))})},handle:function(e){var t=e.target;if(this!==t||e.isSimulated||e.isTrigger||t.type!=="radio"&&t.type!=="checkbox")return e.handleObj.handler.apply(this,arguments)},teardown:function(){return v.event.remove(this,"._change"),!$.test(this.nodeName)}}),v.support.focusinBubbles||v.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){v.event.simulate(t,e.target,v.event.fix(e),!0)};v.event.special[t]={setup:function(){n++===0&&i.addEventListener(e,r,!0)},teardown:function(){--n===0&&i.removeEventListener(e,r,!0)}}}),v.fn.extend({on:function(e,n,r,i,s){var o,u;if(typeof e=="object"){typeof n!="string"&&(r=r||n,n=t);for(u in e)this.on(u,n,r,e[u],s);return this}r==null&&i==null?(i=n,r=n=t):i==null&&(typeof n=="string"?(i=r,r=t):(i=r,r=n,n=t));if(i===!1)i=et;else if(!i)return this;return s===1&&(o=i,i=function(e){return v().off(e),o.apply(this,arguments)},i.guid=o.guid||(o.guid=v.guid++)),this.each(functi
 on(){v.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,s;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,v(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if(typeof e=="object"){for(s in e)this.off(s,n,e[s]);return this}if(n===!1||typeof n=="function")r=n,n=t;return r===!1&&(r=et),this.each(function(){v.event.remove(this,e,r,n)})},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},live:function(e,t,n){return v(this.context).on(e,this.selector,t,n),this},die:function(e,t){return v(this.context).off(e,this.selector||"**",t),this},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return arguments.length===1?this.off(e,"**"):this.off(t,e||"**",n)},trigger:function(e,t){return this.each(function(){v.event.trigger(e,t,this)})},triggerHandler:function(e,t){if(this[0])return v.event.trigger(e,t,th
 is[0],!0)},toggle:function(e){var t=arguments,n=e.guid||v.guid++,r=0,i=function(n){var i=(v._data(this,"lastToggle"+e.guid)||0)%r;return v._data(this,"lastToggle"+e.guid,i+1),n.preventDefault(),t[i].apply(this,arguments)||!1};i.guid=n;while(r<t.length)t[r++].guid=n;return this.click(i)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),v.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){v.fn[t]=function(e,n){return n==null&&(n=e,e=null),arguments.length>0?this.on(t,null,e,n):this.trigger(t)},Q.test(t)&&(v.event.fixHooks[t]=v.event.keyHooks),G.test(t)&&(v.event.fixHooks[t]=v.event.mouseHooks)}),function(e,t){function nt(e,t,n,r){n=n||[],t=t||g;var i,s,a,f,l=t.nodeType;if(!e||typeof e!="string")return n;if(l!==1&&l!==9)return[];a=o(t);if(!a&&!r)if(i=R.exec(e))if(f=i[1]){if(l===9){s=
 t.getElementById(f);if(!s||!s.parentNode)return n;if(s.id===f)return n.push(s),n}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(f))&&u(t,s)&&s.id===f)return n.push(s),n}else{if(i[2])return S.apply(n,x.call(t.getElementsByTagName(e),0)),n;if((f=i[3])&&Z&&t.getElementsByClassName)return S.apply(n,x.call(t.getElementsByClassName(f),0)),n}return vt(e.replace(j,"$1"),t,n,r,a)}function rt(e){return function(t){var n=t.nodeName.toLowerCase();return n==="input"&&t.type===e}}function it(e){return function(t){var n=t.nodeName.toLowerCase();return(n==="input"||n==="button")&&t.type===e}}function st(e){return N(function(t){return t=+t,N(function(n,r){var i,s=e([],n.length,t),o=s.length;while(o--)n[i=s[o]]&&(n[i]=!(r[i]=n[i]))})})}function ot(e,t,n){if(e===t)return n;var r=e.nextSibling;while(r){if(r===t)return-1;r=r.nextSibling}return 1}function ut(e,t){var n,r,s,o,u,a,f,l=L[d][e+" "];if(l)return t?0:l.slice(0);u=e,a=[],f=i.preFilter;while(u){if(!n||(r=F.exec(u)))r&&(u=u.sli
 ce(r[0].length)||u),a.push(s=[]);n=!1;if(r=I.exec(u))s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=r[0].replace(j," ");for(o in i.filter)(r=J[o].exec(u))&&(!f[o]||(r=f[o](r)))&&(s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=o,n.matches=r);if(!n)break}return t?u.length:u?nt.error(e):L(e,a).slice(0)}function at(e,t,r){var i=t.dir,s=r&&t.dir==="parentNode",o=w++;return t.first?function(t,n,r){while(t=t[i])if(s||t.nodeType===1)return e(t,n,r)}:function(t,r,u){if(!u){var a,f=b+" "+o+" ",l=f+n;while(t=t[i])if(s||t.nodeType===1){if((a=t[d])===l)return t.sizset;if(typeof a=="string"&&a.indexOf(f)===0){if(t.sizset)return t}else{t[d]=l;if(e(t,r,u))return t.sizset=!0,t;t.sizset=!1}}}else while(t=t[i])if(s||t.nodeType===1)if(e(t,r,u))return t}}function ft(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function lt(e,t,n,r,i){var s,o=[],u=0,a=e.length,f=t!=null;for(;u<a;u++)if(s=e[u])if(!n||n(s,r,i))o.push(s),f&&t.push
 (u);return o}function ct(e,t,n,r,i,s){return r&&!r[d]&&(r=ct(r)),i&&!i[d]&&(i=ct(i,s)),N(function(s,o,u,a){var f,l,c,h=[],p=[],d=o.length,v=s||dt(t||"*",u.nodeType?[u]:u,[]),m=e&&(s||!t)?lt(v,h,e,u,a):v,g=n?i||(s?e:d||r)?[]:o:m;n&&n(m,g,u,a);if(r){f=lt(g,p),r(f,[],u,a),l=f.length;while(l--)if(c=f[l])g[p[l]]=!(m[p[l]]=c)}if(s){if(i||e){if(i){f=[],l=g.length;while(l--)(c=g[l])&&f.push(m[l]=c);i(null,g=[],f,a)}l=g.length;while(l--)(c=g[l])&&(f=i?T.call(s,c):h[l])>-1&&(s[f]=!(o[f]=c))}}else g=lt(g===o?g.splice(d,g.length):g),i?i(null,o,g,a):S.apply(o,g)})}function ht(e){var t,n,r,s=e.length,o=i.relative[e[0].type],u=o||i.relative[" "],a=o?1:0,f=at(function(e){return e===t},u,!0),l=at(function(e){return T.call(t,e)>-1},u,!0),h=[function(e,n,r){return!o&&(r||n!==c)||((t=n).nodeType?f(e,n,r):l(e,n,r))}];for(;a<s;a++)if(n=i.relative[e[a].type])h=[at(ft(h),n)];else{n=i.filter[e[a].type].apply(null,e[a].matches);if(n[d]){r=++a;for(;r<s;r++)if(i.relative[e[r].type])break;return ct(a>1&
 &ft(h),a>1&&e.slice(0,a-1).join("").replace(j,"$1"),n,a<r&&ht(e.slice(a,r)),r<s&&ht(e=e.slice(r)),r<s&&e.join(""))}h.push(n)}return ft(h)}function pt(e,t){var r=t.length>0,s=e.length>0,o=function(u,a,f,l,h){var p,d,v,m=[],y=0,w="0",x=u&&[],T=h!=null,N=c,C=u||s&&i.find.TAG("*",h&&a.parentNode||a),k=b+=N==null?1:Math.E;T&&(c=a!==g&&a,n=o.el);for(;(p=C[w])!=null;w++){if(s&&p){for(d=0;v=e[d];d++)if(v(p,a,f)){l.push(p);break}T&&(b=k,n=++o.el)}r&&((p=!v&&p)&&y--,u&&x.push(p))}y+=w;if(r&&w!==y){for(d=0;v=t[d];d++)v(x,m,a,f);if(u){if(y>0)while(w--)!x[w]&&!m[w]&&(m[w]=E.call(l));m=lt(m)}S.apply(l,m),T&&!u&&m.length>0&&y+t.length>1&&nt.uniqueSort(l)}return T&&(b=k,c=N),x};return o.el=0,r?N(o):o}function dt(e,t,n){var r=0,i=t.length;for(;r<i;r++)nt(e,t[r],n);return n}function vt(e,t,n,r,s){var o,u,f,l,c,h=ut(e),p=h.length;if(!r&&h.length===1){u=h[0]=h[0].slice(0);if(u.length>2&&(f=u[0]).type==="ID"&&t.nodeType===9&&!s&&i.relative[u[1].type]){t=i.find.ID(f.matches[0].replace($,""),t,s)[
 0];if(!t)return n;e=e.slice(u.shift().length)}for(o=J.POS.test(e)?-1:u.length-1;o>=0;o--){f=u[o];if(i.relative[l=f.type])break;if(c=i.find[l])if(r=c(f.matches[0].replace($,""),z.test(u[0].type)&&t.parentNode||t,s)){u.splice(o,1),e=r.length&&u.join("");if(!e)return S.apply(n,x.call(r,0)),n;break}}}return a(e,h)(r,t,s,n,z.test(e)),n}function mt(){}var n,r,i,s,o,u,a,f,l,c,h=!0,p="undefined",d=("sizcache"+Math.random()).replace(".",""),m=String,g=e.document,y=g.documentElement,b=0,w=0,E=[].pop,S=[].push,x=[].slice,T=[].indexOf||function(e){var t=0,n=this.length;for(;t<n;t++)if(this[t]===e)return t;return-1},N=function(e,t){return e[d]=t==null||t,e},C=function(){var e={},t=[];return N(function(n,r){return t.push(n)>i.cacheLength&&delete e[t.shift()],e[n+" "]=r},e)},k=C(),L=C(),A=C(),O="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",_=M.replace("w","w#"),D="([*^$|!~]?=)",P="\\["+O+"*("+M+")"+O+"*(?:"+D+O+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+_+")|)|)"+O+"*\\]",H=":("+
 M+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+P+")|[^:]|\\\\.)*|.*))\\)|)",B=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+O+"*((?:-\\d)?\\d*)"+O+"*\\)|)(?=[^-]|$)",j=new RegExp("^"+O+"+|((?:^|[^\\\\])(?:\\\\.)*)"+O+"+$","g"),F=new RegExp("^"+O+"*,"+O+"*"),I=new RegExp("^"+O+"*([\\x20\\t\\r\\n\\f>+~])"+O+"*"),q=new RegExp(H),R=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,U=/^:not/,z=/[\x20\t\r\n\f]*[+~]/,W=/:not\($/,X=/h\d/i,V=/input|select|textarea|button/i,$=/\\(?!\\)/g,J={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),NAME:new RegExp("^\\[name=['\"]?("+M+")['\"]?\\]"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+H),POS:new RegExp(B,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+O+"*(even|odd|(([+-]|)(\\d*)n|)"+O+"*(?:([+-]|)"+O+"*(\\d+)|))"+O+"*\\)|)","i"),needsContext:new RegExp("^"+O+"*[>+~]|"+B,"i")},K=function(e){var t=g.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}
 },Q=K(function(e){return e.appendChild(g.createComment("")),!e.getElementsByTagName("*").length}),G=K(function(e){return e.innerHTML="<a href='#'></a>",e.firstChild&&typeof e.firstChild.getAttribute!==p&&e.firstChild.getAttribute("href")==="#"}),Y=K(function(e){e.innerHTML="<select></select>";var t=typeof e.lastChild.getAttribute("multiple");return t!=="boolean"&&t!=="string"}),Z=K(function(e){return e.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",!e.getElementsByClassName||!e.getElementsByClassName("e").length?!1:(e.lastChild.className="e",e.getElementsByClassName("e").length===2)}),et=K(function(e){e.id=d+0,e.innerHTML="<a name='"+d+"'></a><div name='"+d+"'></div>",y.insertBefore(e,y.firstChild);var t=g.getElementsByName&&g.getElementsByName(d).length===2+g.getElementsByName(d+0).length;return r=!g.getElementById(d),y.removeChild(e),t});try{x.call(y.childNodes,0)[0].nodeType}catch(tt){x=function(e){var t,n=[];for(;t=this[e];e++)n.push(t);return n}}nt.m
 atches=function(e,t){return nt(e,null,null,t)},nt.matchesSelector=function(e,t){return nt(t,null,null,[e]).length>0},s=nt.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(i===1||i===9||i===11){if(typeof e.textContent=="string")return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=s(e)}else if(i===3||i===4)return e.nodeValue}else for(;t=e[r];r++)n+=s(t);return n},o=nt.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?t.nodeName!=="HTML":!1},u=nt.contains=y.contains?function(e,t){var n=e.nodeType===9?e.documentElement:e,r=t&&t.parentNode;return e===r||!!(r&&r.nodeType===1&&n.contains&&n.contains(r))}:y.compareDocumentPosition?function(e,t){return t&&!!(e.compareDocumentPosition(t)&16)}:function(e,t){while(t=t.parentNode)if(t===e)return!0;return!1},nt.attr=function(e,t){var n,r=o(e);return r||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):r||Y?e.getAttribute(t):(n=e.getAttributeNode(t),n?typeof e[t]=="boolean"?e[t]?t:null:n.specified?n.value:
 null:null)},i=nt.selectors={cacheLength:50,createPseudo:N,match:J,attrHandle:G?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},find:{ID:r?function(e,t,n){if(typeof t.getElementById!==p&&!n){var r=t.getElementById(e);return r&&r.parentNode?[r]:[]}}:function(e,n,r){if(typeof n.getElementById!==p&&!r){var i=n.getElementById(e);return i?i.id===e||typeof i.getAttributeNode!==p&&i.getAttributeNode("id").value===e?[i]:t:[]}},TAG:Q?function(e,t){if(typeof t.getElementsByTagName!==p)return t.getElementsByTagName(e)}:function(e,t){var n=t.getElementsByTagName(e);if(e==="*"){var r,i=[],s=0;for(;r=n[s];s++)r.nodeType===1&&i.push(r);return i}return n},NAME:et&&function(e,t){if(typeof t.getElementsByName!==p)return t.getElementsByName(name)},CLASS:Z&&function(e,t,n){if(typeof t.getElementsByClassName!==p&&!n)return t.getElementsByClassName(e)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling"
 ,first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace($,""),e[3]=(e[4]||e[5]||"").replace($,""),e[2]==="~="&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),e[1]==="nth"?(e[2]||nt.error(e[0]),e[3]=+(e[3]?e[4]+(e[5]||1):2*(e[2]==="even"||e[2]==="odd")),e[4]=+(e[6]+e[7]||e[2]==="odd")):e[2]&&nt.error(e[0]),e},PSEUDO:function(e){var t,n;if(J.CHILD.test(e[0]))return null;if(e[3])e[2]=e[3];else if(t=e[4])q.test(t)&&(n=ut(t,!0))&&(n=t.indexOf(")",t.length-n)-t.length)&&(t=t.slice(0,n),e[0]=e[0].slice(0,n)),e[2]=t;return e.slice(0,3)}},filter:{ID:r?function(e){return e=e.replace($,""),function(t){return t.getAttribute("id")===e}}:function(e){return e=e.replace($,""),function(t){var n=typeof t.getAttributeNode!==p&&t.getAttributeNode("id");return n&&n.value===e}},TAG:function(e){return e==="*"?function(){return!0}:(e=e.replace($,"").toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},C
 LASS:function(e){var t=k[d][e+" "];return t||(t=new RegExp("(^|"+O+")"+e+"("+O+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==p&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r,i){var s=nt.attr(r,e);return s==null?t==="!=":t?(s+="",t==="="?s===n:t==="!="?s!==n:t==="^="?n&&s.indexOf(n)===0:t==="*="?n&&s.indexOf(n)>-1:t==="$="?n&&s.substr(s.length-n.length)===n:t==="~="?(" "+s+" ").indexOf(n)>-1:t==="|="?s===n||s.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r){return e==="nth"?function(e){var t,i,s=e.parentNode;if(n===1&&r===0)return!0;if(s){i=0;for(t=s.firstChild;t;t=t.nextSibling)if(t.nodeType===1){i++;if(e===t)break}}return i-=r,i===n||i%n===0&&i/n>=0}:function(t){var n=t;switch(e){case"only":case"first":while(n=n.previousSibling)if(n.nodeType===1)return!1;if(e==="first")return!0;n=t;case"last":while(n=n.nextSibling)if(n.nodeType===1)return!1;return!0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters
 [e.toLowerCase()]||nt.error("unsupported pseudo: "+e);return r[d]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?N(function(e,n){var i,s=r(e,t),o=s.length;while(o--)i=T.call(e,s[o]),e[i]=!(n[i]=s[o])}):function(e){return r(e,0,n)}):r}},pseudos:{not:N(function(e){var t=[],n=[],r=a(e.replace(j,"$1"));return r[d]?N(function(e,t,n,i){var s,o=r(e,null,i,[]),u=e.length;while(u--)if(s=o[u])e[u]=!(t[u]=s)}):function(e,i,s){return t[0]=e,r(t,null,s,n),!n.pop()}}),has:N(function(e){return function(t){return nt(e,t).length>0}}),contains:N(function(e){return function(t){return(t.textContent||t.innerText||s(t)).indexOf(e)>-1}}),enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&!!e.checked||t==="option"&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},parent:function(e){return!i.pseudos.empty(e)},empty
 :function(e){var t;e=e.firstChild;while(e){if(e.nodeName>"@"||(t=e.nodeType)===3||t===4)return!1;e=e.nextSibling}return!0},header:function(e){return X.test(e.nodeName)},text:function(e){var t,n;return e.nodeName.toLowerCase()==="input"&&(t=e.type)==="text"&&((n=e.getAttribute("type"))==null||n.toLowerCase()===t)},radio:rt("radio"),checkbox:rt("checkbox"),file:rt("file"),password:rt("password"),image:rt("image"),submit:it("submit"),reset:it("reset"),button:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&e.type==="button"||t==="button"},input:function(e){return V.test(e.nodeName)},focus:function(e){var t=e.ownerDocument;return e===t.activeElement&&(!t.hasFocus||t.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},active:function(e){return e===e.ownerDocument.activeElement},first:st(function(){return[0]}),last:st(function(e,t){return[t-1]}),eq:st(function(e,t,n){return[n<0?n+t:n]}),even:st(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:st(function(e,t){fo
 r(var n=1;n<t;n+=2)e.push(n);return e}),lt:st(function(e,t,n){for(var r=n<0?n+t:n;--r>=0;)e.push(r);return e}),gt:st(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}},f=y.compareDocumentPosition?function(e,t){return e===t?(l=!0,0):(!e.compareDocumentPosition||!t.compareDocumentPosition?e.compareDocumentPosition:e.compareDocumentPosition(t)&4)?-1:1}:function(e,t){if(e===t)return l=!0,0;if(e.sourceIndex&&t.sourceIndex)return e.sourceIndex-t.sourceIndex;var n,r,i=[],s=[],o=e.parentNode,u=t.parentNode,a=o;if(o===u)return ot(e,t);if(!o)return-1;if(!u)return 1;while(a)i.unshift(a),a=a.parentNode;a=u;while(a)s.unshift(a),a=a.parentNode;n=i.length,r=s.length;for(var f=0;f<n&&f<r;f++)if(i[f]!==s[f])return ot(i[f],s[f]);return f===n?ot(e,s[f],-1):ot(i[f],t,1)},[0,0].sort(f),h=!l,nt.uniqueSort=function(e){var t,n=[],r=1,i=0;l=h,e.sort(f);if(l){for(;t=e[r];r++)t===e[r-1]&&(i=n.push(r));while(i--)e.splice(n[i],1)}return e},nt.error=function(e){throw new Error("Syntax erro
 r, unrecognized expression: "+e)},a=nt.compile=function(e,t){var n,r=[],i=[],s=A[d][e+" "];if(!s){t||(t=ut(e)),n=t.length;while(n--)s=ht(t[n]),s[d]?r.push(s):i.push(s);s=A(e,pt(i,r))}return s},g.querySelectorAll&&function(){var e,t=vt,n=/'|\\/g,r=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,i=[":focus"],s=[":active"],u=y.matchesSelector||y.mozMatchesSelector||y.webkitMatchesSelector||y.oMatchesSelector||y.msMatchesSelector;K(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||i.push("\\["+O+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||i.push(":checked")}),K(function(e){e.innerHTML="<p test=''></p>",e.querySelectorAll("[test^='']").length&&i.push("[*^$]="+O+"*(?:\"\"|'')"),e.innerHTML="<input type='hidden'/>",e.querySelectorAll(":enabled").length||i.push(":enabled",":disabled")}),i=new RegExp(i.join("|")),vt=function(e,r,s,o,u){if(!o&&!u&&!i.test(e)){var 
 a,f,l=!0,c=d,h=r,p=r.nodeType===9&&e;if(r.nodeType===1&&r.nodeName.toLowerCase()!=="object"){a=ut(e),(l=r.getAttribute("id"))?c=l.replace(n,"\\$&"):r.setAttribute("id",c),c="[id='"+c+"'] ",f=a.length;while(f--)a[f]=c+a[f].join("");h=z.test(e)&&r.parentNode||r,p=a.join(",")}if(p)try{return S.apply(s,x.call(h.querySelectorAll(p),0)),s}catch(v){}finally{l||r.removeAttribute("id")}}return t(e,r,s,o,u)},u&&(K(function(t){e=u.call(t,"div");try{u.call(t,"[test!='']:sizzle"),s.push("!=",H)}catch(n){}}),s=new RegExp(s.join("|")),nt.matchesSelector=function(t,n){n=n.replace(r,"='$1']");if(!o(t)&&!s.test(n)&&!i.test(n))try{var a=u.call(t,n);if(a||e||t.document&&t.document.nodeType!==11)return a}catch(f){}return nt(n,null,null,[t]).length>0})}(),i.pseudos.nth=i.pseudos.eq,i.filters=mt.prototype=i.pseudos,i.setFilters=new mt,nt.attr=v.attr,v.find=nt,v.expr=nt.selectors,v.expr[":"]=v.expr.pseudos,v.unique=nt.uniqueSort,v.text=nt.getText,v.isXMLDoc=nt.isXML,v.contains=nt.contains}(e);var n
 t=/Until$/,rt=/^(?:parents|prev(?:Until|All))/,it=/^.[^:#\[\.,]*$/,st=v.expr.match.needsContext,ot={children:!0,contents:!0,next:!0,prev:!0};v.fn.extend({find:function(e){var t,n,r,i,s,o,u=this;if(typeof e!="string")return v(e).filter(function(){for(t=0,n=u.length;t<n;t++)if(v.contains(u[t],this))return!0});o=this.pushStack("","find",e);for(t=0,n=this.length;t<n;t++){r=o.length,v.find(e,this[t],o);if(t>0)for(i=r;i<o.length;i++)for(s=0;s<r;s++)if(o[s]===o[i]){o.splice(i--,1);break}}return o},has:function(e){var t,n=v(e,this),r=n.length;return this.filter(function(){for(t=0;t<r;t++)if(v.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e,!1),"not",e)},filter:function(e){return this.pushStack(ft(this,e,!0),"filter",e)},is:function(e){return!!e&&(typeof e=="string"?st.test(e)?v(e,this.context).index(this[0])>=0:v.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,s=[],o=st.test(e)||typeof e!="string"?v(e,t|
 |this.context):0;for(;r<i;r++){n=this[r];while(n&&n.ownerDocument&&n!==t&&n.nodeType!==11){if(o?o.index(n)>-1:v.find.matchesSelector(n,e)){s.push(n);break}n=n.parentNode}}return s=s.length>1?v.unique(s):s,this.pushStack(s,"closest",e)},index:function(e){return e?typeof e=="string"?v.inArray(this[0],v(e)):v.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(e,t){var n=typeof e=="string"?v(e,t):v.makeArray(e&&e.nodeType?[e]:e),r=v.merge(this.get(),n);return this.pushStack(ut(n[0])||ut(r[0])?r:v.unique(r))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),v.fn.andSelf=v.fn.addBack,v.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return v.dir(e,"parentNode")},parentsUntil:function(e,t,n){return v.dir(e,"parentNode",n)},next:function(e){return at(e,"nextSibling")},prev:function(e){return at(e,"previousSibling")},nextAll:function(e){return v.dir
 (e,"nextSibling")},prevAll:function(e){return v.dir(e,"previousSibling")},nextUntil:function(e,t,n){return v.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return v.dir(e,"previousSibling",n)},siblings:function(e){return v.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return v.sibling(e.firstChild)},contents:function(e){return v.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:v.merge([],e.childNodes)}},function(e,t){v.fn[e]=function(n,r){var i=v.map(this,t,n);return nt.test(e)||(r=n),r&&typeof r=="string"&&(i=v.filter(r,i)),i=this.length>1&&!ot[e]?v.unique(i):i,this.length>1&&rt.test(e)&&(i=i.reverse()),this.pushStack(i,e,l.call(arguments).join(","))}}),v.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),t.length===1?v.find.matchesSelector(t[0],e)?[t[0]]:[]:v.find.matches(e,t)},dir:function(e,n,r){var i=[],s=e[n];while(s&&s.nodeType!==9&&(r===t||s.nodeType!==1||!v(s).is(r)))s.nodeType===1&&i.push(s),s=s[n];return i},sibling:funct
 ion(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}});var ct="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ht=/ jQuery\d+="(?:null|\d+)"/g,pt=/^\s+/,dt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,vt=/<([\w:]+)/,mt=/<tbody/i,gt=/<|&#?\w+;/,yt=/<(?:script|style|link)/i,bt=/<(?:script|object|embed|option|style)/i,wt=new RegExp("<(?:"+ct+")[\\s/>]","i"),Et=/^(?:checkbox|radio)$/,St=/checked\s*(?:[^=]|=\s*.checked.)/i,xt=/\/(java|ecma)script/i,Tt=/^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,Nt={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,""
 ,""]},Ct=lt(i),kt=Ct.appendChild(i.createElement("div"));Nt.optgroup=Nt.option,Nt.tbody=Nt.tfoot=Nt.colgroup=Nt.caption=Nt.thead,Nt.th=Nt.td,v.support.htmlSerialize||(Nt._default=[1,"X<div>","</div>"]),v.fn.extend({text:function(e){return v.access(this,function(e){return e===t?v.text(this):this.empty().append((this[0]&&this[0].ownerDocument||i).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(v.isFunction(e))return this.each(function(t){v(this).wrapAll(e.call(this,t))});if(this[0]){var t=v(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return v.isFunction(e)?this.each(function(t){v(this).wrapInner(e.call(this,t))}):this.each(function(){var t=v(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v.isFunction(e);return this.each(function(n){v(thi
 s).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){v.nodeName(this,"body")||v(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(e,this.firstChild)})},before:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(e,this),"before",this.selector)}},after:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this.nextSibling)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(this,e),"after",this.selector)}},remove:function(e,t){var n,r=0;for(;(n=this[r])!=null;r++)if(!e||v.filter(e,[n]).length
 )!t&&n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),v.cleanData([n])),n.parentNode&&n.parentNode.removeChild(n);return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&v.cleanData(e.getElementsByTagName("*"));while(e.firstChild)e.removeChild(e.firstChild)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return v.clone(this,e,t)})},html:function(e){return v.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return n.nodeType===1?n.innerHTML.replace(ht,""):t;if(typeof e=="string"&&!yt.test(e)&&(v.support.htmlSerialize||!wt.test(e))&&(v.support.leadingWhitespace||!pt.test(e))&&!Nt[(vt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(dt,"<$1></$2>");try{for(;r<i;r++)n=this[r]||{},n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),n.innerHTML=e);n=0}catch(s){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(e){return ut(this[0])?this.length?this.pushSt
 ack(v(v.isFunction(e)?e():e),"replaceWith",e):this:v.isFunction(e)?this.each(function(t){var n=v(this),r=n.html();n.replaceWith(e.call(this,t,r))}):(typeof e!="string"&&(e=v(e).detach()),this.each(function(){var t=this.nextSibling,n=this.parentNode;v(this).remove(),t?v(t).before(e):v(n).append(e)}))},detach:function(e){return this.remove(e,!0)},domManip:function(e,n,r){e=[].concat.apply([],e);var i,s,o,u,a=0,f=e[0],l=[],c=this.length;if(!v.support.checkClone&&c>1&&typeof f=="string"&&St.test(f))return this.each(function(){v(this).domManip(e,n,r)});if(v.isFunction(f))return this.each(function(i){var s=v(this);e[0]=f.call(this,i,n?s.html():t),s.domManip(e,n,r)});if(this[0]){i=v.buildFragment(e,this,l),o=i.fragment,s=o.firstChild,o.childNodes.length===1&&(o=s);if(s){n=n&&v.nodeName(s,"tr");for(u=i.cacheable||c-1;a<c;a++)r.call(n&&v.nodeName(this[a],"table")?Lt(this[a],"tbody"):this[a],a===u?o:v.clone(o,!0,!0))}o=s=null,l.length&&v.each(l,function(e,t){t.src?v.ajax?v.ajax({url:t
 .src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):v.error("no ajax"):v.globalEval((t.text||t.textContent||t.innerHTML||"").replace(Tt,"")),t.parentNode&&t.parentNode.removeChild(t)})}return this}}),v.buildFragment=function(e,n,r){var s,o,u,a=e[0];return n=n||i,n=!n.nodeType&&n[0]||n,n=n.ownerDocument||n,e.length===1&&typeof a=="string"&&a.length<512&&n===i&&a.charAt(0)==="<"&&!bt.test(a)&&(v.support.checkClone||!St.test(a))&&(v.support.html5Clone||!wt.test(a))&&(o=!0,s=v.fragments[a],u=s!==t),s||(s=n.createDocumentFragment(),v.clean(e,n,s,r),o&&(v.fragments[a]=u&&s)),{fragment:s,cacheable:o}},v.fragments={},v.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){v.fn[e]=function(n){var r,i=0,s=[],o=v(n),u=o.length,a=this.length===1&&this[0].parentNode;if((a==null||a&&a.nodeType===11&&a.childNodes.length===1)&&u===1)return o[t](this[0]),this;for(;i<u;i++)r=(i>0?this.clone(!0):this).get(),
 v(o[i])[t](r),s=s.concat(r);return this.pushStack(s,e,o.selector)}}),v.extend({clone:function(e,t,n){var r,i,s,o;v.support.html5Clone||v.isXMLDoc(e)||!wt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(kt.innerHTML=e.outerHTML,kt.removeChild(o=kt.firstChild));if((!v.support.noCloneEvent||!v.support.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!v.isXMLDoc(e)){Ot(e,o),r=Mt(e),i=Mt(o);for(s=0;r[s];++s)i[s]&&Ot(r[s],i[s])}if(t){At(e,o);if(n){r=Mt(e),i=Mt(o);for(s=0;r[s];++s)At(r[s],i[s])}}return r=i=null,o},clean:function(e,t,n,r){var s,o,u,a,f,l,c,h,p,d,m,g,y=t===i&&Ct,b=[];if(!t||typeof t.createDocumentFragment=="undefined")t=i;for(s=0;(u=e[s])!=null;s++){typeof u=="number"&&(u+="");if(!u)continue;if(typeof u=="string")if(!gt.test(u))u=t.createTextNode(u);else{y=y||lt(t),c=t.createElement("div"),y.appendChild(c),u=u.replace(dt,"<$1></$2>"),a=(vt.exec(u)||["",""])[1].toLowerCase(),f=Nt[a]||Nt._default,l=f[0],c.innerHTML=f[1]+u+f[2];while(l--)c=c.lastChild;if(!v.support.tbody
 ){h=mt.test(u),p=a==="table"&&!h?c.firstChild&&c.firstChild.childNodes:f[1]==="<table>"&&!h?c.childNodes:[];for(o=p.length-1;o>=0;--o)v.nodeName(p[o],"tbody")&&!p[o].childNodes.length&&p[o].parentNode.removeChild(p[o])}!v.support.leadingWhitespace&&pt.test(u)&&c.insertBefore(t.createTextNode(pt.exec(u)[0]),c.firstChild),u=c.childNodes,c.parentNode.removeChild(c)}u.nodeType?b.push(u):v.merge(b,u)}c&&(u=c=y=null);if(!v.support.appendChecked)for(s=0;(u=b[s])!=null;s++)v.nodeName(u,"input")?_t(u):typeof u.getElementsByTagName!="undefined"&&v.grep(u.getElementsByTagName("input"),_t);if(n){m=function(e){if(!e.type||xt.test(e.type))return r?r.push(e.parentNode?e.parentNode.removeChild(e):e):n.appendChild(e)};for(s=0;(u=b[s])!=null;s++)if(!v.nodeName(u,"script")||!m(u))n.appendChild(u),typeof u.getElementsByTagName!="undefined"&&(g=v.grep(v.merge([],u.getElementsByTagName("script")),m),b.splice.apply(b,[s+1,0].concat(g)),s+=g.length)}return b},cleanData:function(e,t){var n,r,i,s,o=0
 ,u=v.expando,a=v.cache,f=v.support.deleteExpando,l=v.event.special;for(;(i=e[o])!=null;o++)if(t||v.acceptData(i)){r=i[u],n=r&&a[r];if(n){if(n.events)for(s in n.events)l[s]?v.event.remove(i,s):v.removeEvent(i,s,n.handle);a[r]&&(delete a[r],f?delete i[u]:i.removeAttribute?i.removeAttribute(u):i[u]=null,v.deletedIds.push(r))}}}}),function(){var e,t;v.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e=v.uaMatch(o.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),v.browser=t,v.sub=function(){function e(t,n){return new e.fn.init(t,n)}v.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(r,i){return i&&i instanceof v&&
 !(i instanceof e)&&(i=e(i)),v.fn.init.call(this,r,i,t)},e.fn.init.prototype=e.fn;var t=e(i);return e}}();var Dt,Pt,Ht,Bt=/alpha\([^)]*\)/i,jt=/opacity=([^)]*)/,Ft=/^(top|right|bottom|left)$/,It=/^(none|table(?!-c[ea]).+)/,qt=/^margin/,Rt=new RegExp("^("+m+")(.*)$","i"),Ut=new RegExp("^("+m+")(?!px)[a-z%]+$","i"),zt=new RegExp("^([-+])=("+m+")","i"),Wt={BODY:"block"},Xt={position:"absolute",visibility:"hidden",display:"block"},Vt={letterSpacing:0,fontWeight:400},$t=["Top","Right","Bottom","Left"],Jt=["Webkit","O","Moz","ms"],Kt=v.fn.toggle;v.fn.extend({css:function(e,n){return v.access(this,function(e,n,r){return r!==t?v.style(e,n,r):v.css(e,n)},e,n,arguments.length>1)},show:function(){return Yt(this,!0)},hide:function(){return Yt(this)},toggle:function(e,t){var n=typeof e=="boolean";return v.isFunction(e)&&v.isFunction(t)?Kt.apply(this,arguments):this.each(function(){(n?e:Gt(this))?v(this).show():v(this).hide()})}}),v.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=
 Dt(e,"opacity");return n===""?"1":n}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":v.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var s,o,u,a=v.camelCase(n),f=e.style;n=v.cssProps[a]||(v.cssProps[a]=Qt(f,a)),u=v.cssHooks[n]||v.cssHooks[a];if(r===t)return u&&"get"in u&&(s=u.get(e,!1,i))!==t?s:f[n];o=typeof r,o==="string"&&(s=zt.exec(r))&&(r=(s[1]+1)*s[2]+parseFloat(v.css(e,n)),o="number");if(r==null||o==="number"&&isNaN(r))return;o==="number"&&!v.cssNumber[a]&&(r+="px");if(!u||!("set"in u)||(r=u.set(e,r,i))!==t)try{f[n]=r}catch(l){}},css:function(e,n,r,i){var s,o,u,a=v.camelCase(n);return n=v.cssProps[a]||(v.cssProps[a]=Qt(e.style,a)),u=v.cssHooks[n]||v.cssHooks[a],u&&"get"in u&&(s=u.get(e,!0,i)),s===t&&(s=Dt(e,n)),s==="normal"&&n in Vt&&(s=Vt[n]),r||i!==t?(o=parseFloat(s),r||v.isNumeric(o)?o||0:s):s},swap:function(e,t,n){
 var r,i,s={};for(i in t)s[i]=e.style[i],e.style[i]=t[i];r=n.call(e);for(i in t)e.style[i]=s[i];return r}}),e.getComputedStyle?Dt=function(t,n){var r,i,s,o,u=e.getComputedStyle(t,null),a=t.style;return u&&(r=u.getPropertyValue(n)||u[n],r===""&&!v.contains(t.ownerDocument,t)&&(r=v.style(t,n)),Ut.test(r)&&qt.test(n)&&(i=a.width,s=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=r,r=u.width,a.width=i,a.minWidth=s,a.maxWidth=o)),r}:i.documentElement.currentStyle&&(Dt=function(e,t){var n,r,i=e.currentStyle&&e.currentStyle[t],s=e.style;return i==null&&s&&s[t]&&(i=s[t]),Ut.test(i)&&!Ft.test(t)&&(n=s.left,r=e.runtimeStyle&&e.runtimeStyle.left,r&&(e.runtimeStyle.left=e.currentStyle.left),s.left=t==="fontSize"?"1em":i,i=s.pixelLeft+"px",s.left=n,r&&(e.runtimeStyle.left=r)),i===""?"auto":i}),v.each(["height","width"],function(e,t){v.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&It.test(Dt(e,"display"))?v.swap(e,Xt,function(){return tn(e,t,r)}):tn(e,t,r)},set:funct
 ion(e,n,r){return Zt(e,n,r?en(e,t,r,v.support.boxSizing&&v.css(e,"boxSizing")==="border-box"):0)}}}),v.support.opacity||(v.cssHooks.opacity={get:function(e,t){return jt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=v.isNumeric(t)?"alpha(opacity="+t*100+")":"",s=r&&r.filter||n.filter||"";n.zoom=1;if(t>=1&&v.trim(s.replace(Bt,""))===""&&n.removeAttribute){n.removeAttribute("filter");if(r&&!r.filter)return}n.filter=Bt.test(s)?s.replace(Bt,i):s+" "+i}}),v(function(){v.support.reliableMarginRight||(v.cssHooks.marginRight={get:function(e,t){return v.swap(e,{display:"inline-block"},function(){if(t)return Dt(e,"marginRight")})}}),!v.support.pixelPosition&&v.fn.position&&v.each(["top","left"],function(e,t){v.cssHooks[t]={get:function(e,n){if(n){var r=Dt(e,t);return Ut.test(r)?v(e).position()[t]+"px":r}}}})}),v.expr&&v.expr.filters&&(v.expr.filters.hidden=function(e){return e
 .offsetWidth===0&&e.offsetHeight===0||!v.support.reliableHiddenOffsets&&(e.style&&e.style.display||Dt(e,"display"))==="none"},v.expr.filters.visible=function(e){return!v.expr.filters.hidden(e)}),v.each({margin:"",padding:"",border:"Width"},function(e,t){v.cssHooks[e+t]={expand:function(n){var r,i=typeof n=="string"?n.split(" "):[n],s={};for(r=0;r<4;r++)s[e+$t[r]+t]=i[r]||i[r-2]||i[0];return s}},qt.test(e)||(v.cssHooks[e+t].set=Zt)});var rn=/%20/g,sn=/\[\]$/,on=/\r?\n/g,un=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,an=/^(?:select|textarea)/i;v.fn.extend({serialize:function(){return v.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?v.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||an.test(this.nodeName)||un.test(this.type))}).map(function(e,t){var n=v(this).val();return n==null?null:v.isArray(n)?v.map
 (n,function(e,n){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),v.param=function(e,n){var r,i=[],s=function(e,t){t=v.isFunction(t)?t():t==null?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};n===t&&(n=v.ajaxSettings&&v.ajaxSettings.traditional);if(v.isArray(e)||e.jquery&&!v.isPlainObject(e))v.each(e,function(){s(this.name,this.value)});else for(r in e)fn(r,e[r],n,s);return i.join("&").replace(rn,"+")};var ln,cn,hn=/#.*$/,pn=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,dn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,vn=/^(?:GET|HEAD)$/,mn=/^\/\//,gn=/\?/,yn=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bn=/([?&])_=[^&]*/,wn=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,En=v.fn.load,Sn={},xn={},Tn=["*/"]+["*"];try{cn=s.href}catch(Nn){cn=i.createElement("a"),cn.href="",cn=cn.href}ln=wn.exec(cn.toLowerCase())||[],v.fn.load=function(e,n,r){if(typeof e!="string"&&En)return En.apply(this,argument
 s);if(!this.length)return this;var i,s,o,u=this,a=e.indexOf(" ");return a>=0&&(i=e.slice(a,e.length),e=e.slice(0,a)),v.isFunction(n)?(r=n,n=t):n&&typeof n=="object"&&(s="POST"),v.ajax({url:e,type:s,dataType:"html",data:n,complete:function(e,t){r&&u.each(r,o||[e.responseText,t,e])}}).done(function(e){o=arguments,u.html(i?v("<div>").append(e.replace(yn,"")).find(i):e)}),this},v.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,t){v.fn[t]=function(e){return this.on(t,e)}}),v.each(["get","post"],function(e,n){v[n]=function(e,r,i,s){return v.isFunction(r)&&(s=s||i,i=r,r=t),v.ajax({type:n,url:e,data:r,success:i,dataType:s})}}),v.extend({getScript:function(e,n){return v.get(e,t,n,"script")},getJSON:function(e,t,n){return v.get(e,t,n,"json")},ajaxSetup:function(e,t){return t?Ln(e,v.ajaxSettings):(t=e,e=v.ajaxSettings),Ln(e,t),e},ajaxSettings:{url:cn,isLocal:dn.test(ln[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; c
 harset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":Tn},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":v.parseJSON,"text xml":v.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:Cn(Sn),ajaxTransport:Cn(xn),ajax:function(e,n){function T(e,n,s,a){var l,y,b,w,S,T=n;if(E===2)return;E=2,u&&clearTimeout(u),o=t,i=a||"",x.readyState=e>0?4:0,s&&(w=An(c,x,s));if(e>=200&&e<300||e===304)c.ifModified&&(S=x.getResponseHeader("Last-Modified"),S&&(v.lastModified[r]=S),S=x.getResponseHeader("Etag"),S&&(v.etag[r]=S)),e===304?(T="notmodified",l=!0):(l=On(c,w),T=l.state,y=l.data,b=l.error,l=!b);else{b=T;if(!T||e)T="error",e<0&&(e=0)}x.status=e,x.statusText=(n||T)+"",l?d.resolveWith(h,[y,T,x]):d.rejectWith(h,[x,T,b]),x.statusCode(g),g=t,f&&p.trigger("ajax"+(l?"Success":"Error
 "),[x,c,l?y:b]),m.fireWith(h,[x,T]),f&&(p.trigger("ajaxComplete",[x,c]),--v.active||v.event.trigger("ajaxStop"))}typeof e=="object"&&(n=e,e=t),n=n||{};var r,i,s,o,u,a,f,l,c=v.ajaxSetup({},n),h=c.context||c,p=h!==c&&(h.nodeType||h instanceof v)?v(h):v.event,d=v.Deferred(),m=v.Callbacks("once memory"),g=c.statusCode||{},b={},w={},E=0,S="canceled",x={readyState:0,setRequestHeader:function(e,t){if(!E){var n=e.toLowerCase();e=w[n]=w[n]||e,b[e]=t}return this},getAllResponseHeaders:function(){return E===2?i:null},getResponseHeader:function(e){var n;if(E===2){if(!s){s={};while(n=pn.exec(i))s[n[1].toLowerCase()]=n[2]}n=s[e.toLowerCase()]}return n===t?null:n},overrideMimeType:function(e){return E||(c.mimeType=e),this},abort:function(e){return e=e||S,o&&o.abort(e),T(0,e),this}};d.promise(x),x.success=x.done,x.error=x.fail,x.complete=m.add,x.statusCode=function(e){if(e){var t;if(E<2)for(t in e)g[t]=[g[t],e[t]];else t=e[x.status],x.always(t)}return this},c.url=((e||c.url)+"").replace(hn,
 "").replace(mn,ln[1]+"//"),c.dataTypes=v.trim(c.dataType||"*").toLowerCase().split(y),c.crossDomain==null&&(a=wn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===ln[1]&&a[2]===ln[2]&&(a[3]||(a[1]==="http:"?80:443))==(ln[3]||(ln[1]==="http:"?80:443)))),c.data&&c.processData&&typeof c.data!="string"&&(c.data=v.param(c.data,c.traditional)),kn(Sn,c,n,x);if(E===2)return x;f=c.global,c.type=c.type.toUpperCase(),c.hasContent=!vn.test(c.type),f&&v.active++===0&&v.event.trigger("ajaxStart");if(!c.hasContent){c.data&&(c.url+=(gn.test(c.url)?"&":"?")+c.data,delete c.data),r=c.url;if(c.cache===!1){var N=v.now(),C=c.url.replace(bn,"$1_="+N);c.url=C+(C===c.url?(gn.test(c.url)?"&":"?")+"_="+N:"")}}(c.data&&c.hasContent&&c.contentType!==!1||n.contentType)&&x.setRequestHeader("Content-Type",c.contentType),c.ifModified&&(r=r||c.url,v.lastModified[r]&&x.setRequestHeader("If-Modified-Since",v.lastModified[r]),v.etag[r]&&x.setRequestHeader("If-None-Match",v.etag[r])),x.setRequestHeader("Acce
 pt",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+(c.dataTypes[0]!=="*"?", "+Tn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)x.setRequestHeader(l,c.headers[l]);if(!c.beforeSend||c.beforeSend.call(h,x,c)!==!1&&E!==2){S="abort";for(l in{success:1,error:1,complete:1})x[l](c[l]);o=kn(xn,c,n,x);if(!o)T(-1,"No Transport");else{x.readyState=1,f&&p.trigger("ajaxSend",[x,c]),c.async&&c.timeout>0&&(u=setTimeout(function(){x.abort("timeout")},c.timeout));try{E=1,o.send(b,T)}catch(k){if(!(E<2))throw k;T(-1,k)}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var Mn=[],_n=/\?/,Dn=/(=)\?(?=&|$)|\?\?/,Pn=v.now();v.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Mn.pop()||v.expando+"_"+Pn++;return this[e]=!0,e}}),v.ajaxPrefilter("json jsonp",function(n,r,i){var s,o,u,a=n.data,f=n.url,l=n.jsonp!==!1,c=l&&Dn.test(f),h=l&&!c&&typeof a=="string"&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Dn.test(a);if(n.dataTypes[0]==="
 jsonp"||c||h)return s=n.jsonpCallback=v.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,o=e[s],c?n.url=f.replace(Dn,"$1"+s):h?n.data=a.replace(Dn,"$1"+s):l&&(n.url+=(_n.test(f)?"&":"?")+n.jsonp+"="+s),n.converters["script json"]=function(){return u||v.error(s+" was not called"),u[0]},n.dataTypes[0]="json",e[s]=function(){u=arguments},i.always(function(){e[s]=o,n[s]&&(n.jsonpCallback=r.jsonpCallback,Mn.push(s)),u&&v.isFunction(o)&&o(u[0]),u=o=t}),"script"}),v.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(e){return v.globalEval(e),e}}}),v.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),v.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=i.head||i.getElementsByTagName("head")[0]||i.documentElement;return{send:function(s,o){n=i.createElement("script"
 ),n.async="async",e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,i){if(i||!n.readyState||/loaded|complete/.test(n.readyState))n.onload=n.onreadystatechange=null,r&&n.parentNode&&r.removeChild(n),n=t,i||o(200,"success")},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(0,1)}}}});var Hn,Bn=e.ActiveXObject?function(){for(var e in Hn)Hn[e](0,1)}:!1,jn=0;v.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&Fn()||In()}:Fn,function(e){v.extend(v.support,{ajax:!!e,cors:!!e&&"withCredentials"in e})}(v.ajaxSettings.xhr()),v.support.ajax&&v.ajaxTransport(function(n){if(!n.crossDomain||v.support.cors){var r;return{send:function(i,s){var o,u,a=n.xhr();n.username?a.open(n.type,n.url,n.async,n.username,n.password):a.open(n.type,n.url,n.async);if(n.xhrFields)for(u in n.xhrFields)a[u]=n.xhrFields[u];n.mimeType&&a.overrideMimeType&&a.overrideMimeType(n.mimeType),!n.crossDomain&&!i["X-Requested-With"]&&(i["X-Requested
 -With"]="XMLHttpRequest");try{for(u in i)a.setRequestHeader(u,i[u])}catch(f){}a.send(n.hasContent&&n.data||null),r=function(e,i){var u,f,l,c,h;try{if(r&&(i||a.readyState===4)){r=t,o&&(a.onreadystatechange=v.noop,Bn&&delete Hn[o]);if(i)a.readyState!==4&&a.abort();else{u=a.status,l=a.getAllResponseHeaders(),c={},h=a.responseXML,h&&h.documentElement&&(c.xml=h);try{c.text=a.responseText}catch(p){}try{f=a.statusText}catch(p){f=""}!u&&n.isLocal&&!n.crossDomain?u=c.text?200:404:u===1223&&(u=204)}}}catch(d){i||s(-1,d)}c&&s(u,f,c,l)},n.async?a.readyState===4?setTimeout(r,0):(o=++jn,Bn&&(Hn||(Hn={},v(e).unload(Bn)),Hn[o]=r),a.onreadystatechange=r):r()},abort:function(){r&&r(0,1)}}}});var qn,Rn,Un=/^(?:toggle|show|hide)$/,zn=new RegExp("^(?:([-+])=|)("+m+")([a-z%]*)$","i"),Wn=/queueHooks$/,Xn=[Gn],Vn={"*":[function(e,t){var n,r,i=this.createTween(e,t),s=zn.exec(t),o=i.cur(),u=+o||0,a=1,f=20;if(s){n=+s[2],r=s[3]||(v.cssNumber[e]?"":"px");if(r!=="px"&&u){u=v.css(i.elem,e,!0)||n||1;do a=a
 ||".5",u/=a,v.style(i.elem,e,u+r);while(a!==(a=i.cur()/o)&&a!==1&&--f)}i.unit=r,i.start=u,i.end=s[1]?u+(s[1]+1)*n:n}return i}]};v.Animation=v.extend(Kn,{tweener:function(e,t){v.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;r<i;r++)n=e[r],Vn[n]=Vn[n]||[],Vn[n].unshift(t)},prefilter:function(e,t){t?Xn.unshift(e):Xn.push(e)}}),v.Tween=Yn,Yn.prototype={constructor:Yn,init:function(e,t,n,r,i,s){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=s||(v.cssNumber[n]?"":"px")},cur:function(){var e=Yn.propHooks[this.prop];return e&&e.get?e.get(this):Yn.propHooks._default.get(this)},run:function(e){var t,n=Yn.propHooks[this.prop];return this.options.duration?this.pos=t=v.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):Yn.propHooks._de
 fault.set(this),this}},Yn.prototype.init.prototype=Yn.prototype,Yn.propHooks={_default:{get:function(e){var t;return e.elem[e.prop]==null||!!e.elem.style&&e.elem.style[e.prop]!=null?(t=v.css(e.elem,e.prop,!1,""),!t||t==="auto"?0:t):e.elem[e.prop]},set:function(e){v.fx.step[e.prop]?v.fx.step[e.prop](e):e.elem.style&&(e.elem.style[v.cssProps[e.prop]]!=null||v.cssHooks[e.prop])?v.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},Yn.propHooks.scrollTop=Yn.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},v.each(["toggle","show","hide"],function(e,t){var n=v.fn[t];v.fn[t]=function(r,i,s){return r==null||typeof r=="boolean"||!e&&v.isFunction(r)&&v.isFunction(i)?n.apply(this,arguments):this.animate(Zn(t,!0),r,i,s)}}),v.fn.extend({fadeTo:function(e,t,n,r){return this.filter(Gt).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=v.isEmptyObject(e),s=v.speed(t,n,r),o=function(){var t=Kn(thi
 s,v.extend({},e),s);i&&t.stop(!0)};return i||s.queue===!1?this.each(o):this.queue(s.queue,o)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return typeof e!="string"&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=e!=null&&e+"queueHooks",s=v.timers,o=v._data(this);if(n)o[n]&&o[n].stop&&i(o[n]);else for(n in o)o[n]&&o[n].stop&&Wn.test(n)&&i(o[n]);for(n=s.length;n--;)s[n].elem===this&&(e==null||s[n].queue===e)&&(s[n].anim.stop(r),t=!1,s.splice(n,1));(t||!r)&&v.dequeue(this,e)})}}),v.each({slideDown:Zn("show"),slideUp:Zn("hide"),slideToggle:Zn("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){v.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),v.speed=function(e,t,n){var r=e&&typeof e=="object"?v.extend({},e):{complete:n||!n&&t||v.isFunction(e)&&e,duration:e,easing:n&&t||t&&!v.isFunction(t)&&t};r.duration=v.fx.off?0:typeof r.duration=="number"?r.duration:r.durati
 on in v.fx.speeds?v.fx.speeds[r.duration]:v.fx.speeds._default;if(r.queue==null||r.queue===!0)r.queue="fx";return r.old=r.complete,r.complete=function(){v.isFunction(r.old)&&r.old.call(this),r.queue&&v.dequeue(this,r.queue)},r},v.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},v.timers=[],v.fx=Yn.prototype.init,v.fx.tick=function(){var e,n=v.timers,r=0;qn=v.now();for(;r<n.length;r++)e=n[r],!e()&&n[r]===e&&n.splice(r--,1);n.length||v.fx.stop(),qn=t},v.fx.timer=function(e){e()&&v.timers.push(e)&&!Rn&&(Rn=setInterval(v.fx.tick,v.fx.interval))},v.fx.interval=13,v.fx.stop=function(){clearInterval(Rn),Rn=null},v.fx.speeds={slow:600,fast:200,_default:400},v.fx.step={},v.expr&&v.expr.filters&&(v.expr.filters.animated=function(e){return v.grep(v.timers,function(t){return e===t.elem}).length});var er=/^(?:body|html)$/i;v.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){v.offset.setOffset(this,e,t)});var n,r,i,s,o
 ,u,a,f={top:0,left:0},l=this[0],c=l&&l.ownerDocument;if(!c)return;return(r=c.body)===l?v.offset.bodyOffset(l):(n=c.documentElement,v.contains(n,l)?(typeof l.getBoundingClientRect!="undefined"&&(f=l.getBoundingClientRect()),i=tr(c),s=n.clientTop||r.clientTop||0,o=n.clientLeft||r.clientLeft||0,u=i.pageYOffset||n.scrollTop,a=i.pageXOffset||n.scrollLeft,{top:f.top+u-s,left:f.left+a-o}):f)},v.offset={bodyOffset:function(e){var t=e.offsetTop,n=e.offsetLeft;return v.support.doesNotIncludeMarginInBodyOffset&&(t+=parseFloat(v.css(e,"marginTop"))||0,n+=parseFloat(v.css(e,"marginLeft"))||0),{top:t,left:n}},setOffset:function(e,t,n){var r=v.css(e,"position");r==="static"&&(e.style.position="relative");var i=v(e),s=i.offset(),o=v.css(e,"top"),u=v.css(e,"left"),a=(r==="absolute"||r==="fixed")&&v.inArray("auto",[o,u])>-1,f={},l={},c,h;a?(l=i.position(),c=l.top,h=l.left):(c=parseFloat(o)||0,h=parseFloat(u)||0),v.isFunction(t)&&(t=t.call(e,n,s)),t.top!=null&&(f.top=t.top-s.top+c),t.left!=nul
 l&&(f.left=t.left-s.left+h),"using"in t?t.using.call(e,f):i.css(f)}},v.fn.extend({position:function(){if(!this[0])return;var e=this[0],t=this.offsetParent(),n=this.offset(),r=er.test(t[0].nodeName)?{top:0,left:0}:t.offset();return n.top-=parseFloat(v.css(e,"marginTop"))||0,n.left-=parseFloat(v.css(e,"marginLeft"))||0,r.top+=parseFloat(v.css(t[0],"borderTopWidth"))||0,r.left+=parseFloat(v.css(t[0],"borderLeftWidth"))||0,{top:n.top-r.top,left:n.left-r.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||i.body;while(e&&!er.test(e.nodeName)&&v.css(e,"position")==="static")e=e.offsetParent;return e||i.body})}}),v.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);v.fn[e]=function(i){return v.access(this,function(e,i,s){var o=tr(e);if(s===t)return o?n in o?o[n]:o.document.documentElement[i]:e[i];o?o.scrollTo(r?v(o).scrollLeft():s,r?s:v(o).scrollTop()):e[i]=s},e,i,arguments.length,null)}}),v.each({Height:"height",W
 idth:"width"},function(e,n){v.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){v.fn[i]=function(i,s){var o=arguments.length&&(r||typeof i!="boolean"),u=r||(i===!0||s===!0?"margin":"border");return v.access(this,function(n,r,i){var s;return v.isWindow(n)?n.document.documentElement["client"+e]:n.nodeType===9?(s=n.documentElement,Math.max(n.body["scroll"+e],s["scroll"+e],n.body["offset"+e],s["offset"+e],s["client"+e])):i===t?v.css(n,r,i,u):v.style(n,r,i,u)},n,o?i:t,o,null)}})}),e.jQuery=e.$=v,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return v})})(window);
\ No newline at end of file


commit 103b5451ef349040c5f44b710675dc87d8c7220c
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Thu Nov 1 16:49:05 2012 +0100

    Add to object type id detection, the position in the tree that a particular object type id may have defined.

diff --git a/lib/kolab_api_service.php b/lib/kolab_api_service.php
index fc04314..4c99d81 100644
--- a/lib/kolab_api_service.php
+++ b/lib/kolab_api_service.php
@@ -138,7 +138,7 @@ abstract class kolab_api_service
         $keys_score   = 0;
         $type_id      = null;
 
-        //console("Data objectClasses: " . implode(", ", $object_class));
+        Log::trace("kolab_api_service::object_type_id objectClasses: " . implode(", ", $object_class));
 
         foreach ($object_types as $idx => $elem) {
             $ref_class = $elem['attributes']['fields']['objectclass'];
@@ -147,7 +147,7 @@ abstract class kolab_api_service
                 continue;
             }
 
-            //console("Reference objectclasses for " . $elem['key'] . ": " . implode(", ", $ref_class));
+            Log::trace("Reference objectclasses for " . $elem['key'] . ": " . implode(", ", $ref_class));
 
             // Eliminate the duplicates between the $data_ocs and $ref_ocs
             $_object_class = array_diff($object_class, $ref_class);
@@ -165,12 +165,24 @@ abstract class kolab_api_service
                     array_keys((array) $elem['attributes']['form_fields']),
                     array_keys($elem['attributes']['fields'])
                 ));
+
                 $elem_keys_score = $keys_count - count(array_diff($object_keys, $ref_keys));
             }
 
-            //console("\$object_class not in \$ref_class (" . $elem['key'] . "): " . implode(", ", $_object_class));
-            //console("\$ref_class not in \$object_class (" . $elem['key'] . "): " . implode(", ", $_ref_class));
-            //console("Score for $object_name type " . $elem['name'] . ": " . $elem_score . "(" . $commonalities . "/" . $differences . ") " . $elem_keys_score);
+            // Position in tree score
+            if (!empty($elem['attributes']['fields']['ou'])) {
+                if (!empty($attributes['ou'])) {
+                    if (strtolower($elem['attributes']['fields']['ou']) == strtolower($attributes['ou'])) {
+                        Log::trace("object_type " . $elem['key'] . " fields ou setting matches entry, bumping scores.");
+                        $elem_score += 2;
+                        $elem_keys_score += 10;
+                    }
+                }
+            }
+
+            Log::trace("\$object_class not in \$ref_class (" . $elem['key'] . "): " . implode(", ", $_object_class));
+            Log::trace("\$ref_class not in \$object_class (" . $elem['key'] . "): " . implode(", ", $_ref_class));
+            Log::trace("Score for $object_name type " . $elem['name'] . ": " . $elem_score . "(" . $commonalities . "/" . $differences . ") " . $elem_keys_score);
 
             // Compare last and current element score
             if ($elem_score > $type_score || ($elem_score == $type_score && $elem_keys_score > $keys_score)) {


commit 4f374764df34068dc57bf66c54fbbfdd6f710f0f
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Thu Nov 1 16:21:01 2012 +0100

    Preserve additional aliases that may have been configured
    Allow an additional parameter "validate" to be set to false for object type attributes, to allow objects with external attributes to be configured
    Do not (int) mailquota as it is not needed any longer
    Set the astaccountrealmedpassword from the userpassword if available

diff --git a/lib/api/kolab_api_service_form_value.php b/lib/api/kolab_api_service_form_value.php
index 25c44b5..2beee83 100644
--- a/lib/api/kolab_api_service_form_value.php
+++ b/lib/api/kolab_api_service_form_value.php
@@ -223,6 +223,8 @@ class kolab_api_service_form_value extends kolab_api_service
 
             if (array_key_exists($attr_name, $attribs['form_fields']) && !empty($attribs['form_fields'][$attr_name]['optional']) && !$attribs['form_fields'][$attr_name]['optional']) {
                 $result[$attr_name] = $this->{$method_name}($attr_value, $postdata);
+            } elseif (array_key_exists($attr_name, $attribs['form_fields']) && !empty($attribs['form_fields'][$attr_name]['validate']) && !$attribs['form_fields'][$attr_name]['validate']) {
+                $result[$attr_name] = $attr_value;
             } else {
                 try {
                     $result[$attr_name] = $this->{$method_name}($attr_value, $postdata);
@@ -240,7 +242,29 @@ class kolab_api_service_form_value extends kolab_api_service
 
     private function generate_alias($postdata, $attribs = array())
     {
-        return $this->generate_secondary_mail($postdata, $attribs);
+        $rcpt_pol_aliases = $this->generate_secondary_mail($postdata, $attribs);
+
+        $service = $this->controller->get_service('user');
+        $user_attrs  = $service->user_info(Array( "user" => $postdata['id']), null);
+
+        if (!empty($user_attrs['alias'])) {
+            $cur_aliases = $user_attrs['alias'];
+        } else {
+            $cur_aliases = Array();
+        }
+
+        if (!is_array($cur_aliases)) {
+            $cur_aliases = (array)($cur_aliases);
+        }
+
+        sort($rcpt_pol_aliases);
+        sort($cur_aliases);
+
+        $form_aliases = array_unique(array_merge($rcpt_pol_aliases, $cur_aliases));
+
+        Log::trace("kolab_api_service_form_value::generate_alias() \$form_aliases: " . var_export($form_aliases, TRUE));
+
+        return array_values($form_aliases);
     }
 
     private function generate_astaccountcallerid($postdata, $attribs = array())
@@ -1006,6 +1030,10 @@ class kolab_api_service_form_value extends kolab_api_service
 
     private function validate_astaccountrealmedpassword($value, $postdata)
     {
+        if (!array_key_exists('userpassword', $postdata) || empty($postdata['userpassword'])) {
+            return $value;
+        }
+
         if (!array_key_exists('uid', $postdata) || empty($postdata['uid'])) {
             $postdata['uid'] = $this->generate_uid($postdata);
         }
@@ -1042,7 +1070,8 @@ class kolab_api_service_form_value extends kolab_api_service
 
     private function validate_mailquota($value)
     {
-        return (int)($value);
+        //return (int)($value);
+        return $value;
     }
 
     private function validate_mailalternateaddress($value)


commit 292f1ce63e18674a34b15c9e86273b074a08f0d8
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Thu Nov 1 16:19:20 2012 +0100

    Ensure attributes that are multi-valued but only contain one item are compared correctly

diff --git a/lib/ext/Net/LDAP3.php b/lib/ext/Net/LDAP3.php
index c857ee9..76c6702 100644
--- a/lib/ext/Net/LDAP3.php
+++ b/lib/ext/Net/LDAP3.php
@@ -989,7 +989,21 @@ class Net_LDAP3
         // in the new attrs, if any.
         foreach ($old_attrs as $attr => $old_attr_value) {
 
+            if (is_array($old_attr_value)) {
+                if (count($old_attr_value) == 1) {
+                    $old_attrs[$attr] = $old_attr_value[0];
+                    $old_attr_value = $old_attrs[$attr];
+                }
+            }
+
             if (array_key_exists($attr, $new_attrs)) {
+
+                if (is_array($new_attrs[$attr])) {
+                    if (count($new_attrs[$attr]) == 1) {
+                        $new_attrs[$attr] = $new_attrs[$attr][0];
+                    }
+                }
+
                 if (is_array($old_attrs[$attr]) && is_array($new_attrs[$attr])) {
                     $_sort1 = $new_attrs[$attr];
                     sort($_sort1);
@@ -1086,7 +1100,21 @@ class Net_LDAP3
         }
 
         foreach ($new_attrs as $attr => $value) {
+            if (is_array($value)) {
+                if (count($value) == 1) {
+                    $new_attrs[$attr] = $value[0];
+                    $value = $new_attrs[$attr];
+                }
+            }
+
             if (array_key_exists($attr, $old_attrs)) {
+
+                if (is_array($old_attrs[$attr])) {
+                    if (count($old_attrs[$attr]) == 1) {
+                        $old_attrs[$attr] = $old_attrs[$attr][0];
+                    }
+                }
+
                 if (is_array($new_attrs[$attr]) && is_array($old_attrs[$attr])) {
                     $_sort1 = $old_attrs[$attr];
                     sort($_sort1);


commit 47bf4d4819024fdc7f5b4fffc0cea7df2ba003fe
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Thu Nov 1 14:38:28 2012 +0100

    Fix additional attributes requested not being returned, due to array position offsets

diff --git a/lib/kolab_api_service.php b/lib/kolab_api_service.php
index ea62318..fc04314 100644
--- a/lib/kolab_api_service.php
+++ b/lib/kolab_api_service.php
@@ -457,7 +457,7 @@ abstract class kolab_api_service
 
         // Get extra attributes
         if (!empty($extra_attrs)) {
-            $extra_attrs = $auth->get_entry_attributes($dn, $extra_attrs);
+            $extra_attrs = $auth->get_entry_attributes($dn, array_values($extra_attrs));
 
             if (!empty($extra_attrs)) {
                 $attrs = array_merge($attrs, $extra_attrs);


commit 279f3c2635c14dd6a87767b38fc458dc9abb50b9
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed Oct 31 14:54:58 2012 +0100

    Add mailforwardingaddress to Configuration tab

diff --git a/lib/client/kolab_client_task_user.php b/lib/client/kolab_client_task_user.php
index 166f8a5..85b58e7 100644
--- a/lib/client/kolab_client_task_user.php
+++ b/lib/client/kolab_client_task_user.php
@@ -270,6 +270,7 @@ class kolab_client_task_user extends kolab_client_task
             'kolabdelegate'             => 'config',
             'kolaballowsmtprecipient'   => 'config',
             'kolaballowsmtpsender'      => 'config',
+            'mailforwardingaddress'     => 'config',
 
             /* Asterisk Settings */
             'astaccountallowedcodec'    => 'asterisk',


commit 1178018d972dd1e863b609d86dac9ba520dc116b
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed Oct 31 14:54:25 2012 +0100

    Add text for mailforwardingaddress label

diff --git a/lib/locale/en_US.php b/lib/locale/en_US.php
index ec9b3ca..a19869c 100644
--- a/lib/locale/en_US.php
+++ b/lib/locale/en_US.php
@@ -249,6 +249,7 @@ $LANG['user.list'] = 'Users List';
 $LANG['user.loginshell'] = 'Shell';
 $LANG['user.mail'] = 'Primary Email Address';
 $LANG['user.mailalternateaddress'] = 'External Email Address(es)';
+$LANG['user.mailforwardingaddress'] = 'Forward Mail To';
 $LANG['user.mailhost'] = 'Email Server';
 $LANG['user.mailquota'] = 'Quota';
 $LANG['user.mailquota.desc'] = 'Leave blank for unlimited';


commit e40589afda1b9e0baa3119386c71f40cd9de0b61
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed Oct 31 14:53:53 2012 +0100

    Ensure attributes are not marked as needing replacement if in fact they did not change

diff --git a/lib/ext/Net/LDAP3.php b/lib/ext/Net/LDAP3.php
index 466b351..c857ee9 100644
--- a/lib/ext/Net/LDAP3.php
+++ b/lib/ext/Net/LDAP3.php
@@ -1001,7 +1001,7 @@ class Net_LDAP3
                 }
 
                 if (!($new_attrs[$attr] === $old_attr_value) && !($_sort1 === $_sort2)) {
-                    $this->_debug("Attribute $attr changed from", $old_attr_value, "to", $new_attrs[$attr]);
+                    $this->_debug("Attribute $attr changed from " . var_export($old_attr_value, TRUE) . " to " . var_export($new_attrs[$attr], TRUE));
                     if ($attr === $rdn_attr) {
                         $this->_debug("This attribute is the RDN attribute. Let's see if it is multi-valued, and if the original still exists in the new value.");
                         if (is_array($old_attrs[$attr])) {
@@ -1087,6 +1087,16 @@ class Net_LDAP3
 
         foreach ($new_attrs as $attr => $value) {
             if (array_key_exists($attr, $old_attrs)) {
+                if (is_array($new_attrs[$attr]) && is_array($old_attrs[$attr])) {
+                    $_sort1 = $old_attrs[$attr];
+                    sort($_sort1);
+                    $_sort2 = $value;
+                    sort($_sort2);
+                } else {
+                    $_sort1 = TRUE;
+                    $_sort2 = FALSE;
+                }
+
                 if (empty($value)) {
                     if (!array_key_exists($attr, $mod_array['del'])) {
                         switch ($attr) {
@@ -1099,7 +1109,7 @@ class Net_LDAP3
                         }
                     }
                 } else {
-                    if (!($old_attrs[$attr] === $value) && !($attr === $rdn_attr)) {
+                    if (!($old_attrs[$attr] === $value) && !($attr === $rdn_attr) && !($_sort1 === $_sort2)) {
                         if (!array_key_exists($attr, $mod_array['replace'])) {
                             $this->_debug("Adding to replace(2): $attr");
                             $mod_array['replace'][$attr] = $value;


commit 82846cfebabdc479c43c02057764d6b6c4f971c6
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Tue Oct 30 17:07:12 2012 +0100

    Add functions to generate and validate Asterisk account attributes

diff --git a/lib/api/kolab_api_service_form_value.php b/lib/api/kolab_api_service_form_value.php
index fbd55fc..25c44b5 100644
--- a/lib/api/kolab_api_service_form_value.php
+++ b/lib/api/kolab_api_service_form_value.php
@@ -198,11 +198,11 @@ class kolab_api_service_form_value extends kolab_api_service
      */
     public function validate($getdata, $postdata)
     {
-        //console("Executing validate() for \$getdata, \$postdata", $getdata, $postdata);
-
         $attribs = $this->object_type_attributes($postdata['object_type'], $postdata['type_id']);
         $result  = array();
 
+        Log::trace("kolab_api_form_value::validate() \$postdata: " . var_export($postdata, TRUE));
+
         foreach ((array)$postdata as $attr_name => $attr_value) {
             if (empty($attr_name) || $attr_name == 'type_id' || $attr_name == 'object_type') {
                 continue;
@@ -222,16 +222,19 @@ class kolab_api_service_form_value extends kolab_api_service
             }
 
             if (array_key_exists($attr_name, $attribs['form_fields']) && !empty($attribs['form_fields'][$attr_name]['optional']) && !$attribs['form_fields'][$attr_name]['optional']) {
-                $result[$attr_name] = $this->{$method_name}($attr_value);
+                $result[$attr_name] = $this->{$method_name}($attr_value, $postdata);
             } else {
                 try {
-                    $result[$attr_name] = $this->{$method_name}($attr_value);
+                    $result[$attr_name] = $this->{$method_name}($attr_value, $postdata);
                 } catch (Exception $e) {
                     Log::debug("Attribute $attr_name did not validate, but it is not a required attribute. Not saving. (Error was: $e)");
                 }
             }
+
         }
 
+        Log::trace("kolab_api_form_value::validate() \$result: " . var_export($result, TRUE));
+
         return $result;
     }
 
@@ -240,6 +243,115 @@ class kolab_api_service_form_value extends kolab_api_service
         return $this->generate_secondary_mail($postdata, $attribs);
     }
 
+    private function generate_astaccountcallerid($postdata, $attribs = array())
+    {
+        if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['astaccountcallerid'])) {
+            // Use Data Please
+            foreach ($attribs['auto_form_fields']['astaccountcallerid']['data'] as $key) {
+                if (!isset($postdata[$key])) {
+                    throw new Exception("Key not set: " . $key, 12356);
+                }
+            }
+
+            // TODO: Correct this with what is in 'data'...
+            return $this->generate_cn($postdata, $attribs);
+        }
+    }
+
+    private function generate_astaccountdefaultuser($postdata, $attribs = array())
+    {
+        if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['astaccountdefaultuser'])) {
+            // Use Data Please
+            foreach ($attribs['auto_form_fields']['astaccountdefaultuser']['data'] as $key) {
+                if (!isset($postdata[$key])) {
+                    throw new Exception("Key not set: " . $key, 12356);
+                }
+            }
+
+            return $this->generate_uid($postdata, $attribs);
+        }
+    }
+
+    private function generate_astaccountmailbox($postdata, $attribs = array())
+    {
+        if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['astaccountmailbox'])) {
+            // Use Data Please
+            foreach ($attribs['auto_form_fields']['astaccountmailbox']['data'] as $key) {
+                if (!isset($postdata[$key])) {
+                    throw new Exception("Key not set: " . $key, 12356);
+                }
+            }
+
+            return $this->generate_uid($postdata, $attribs);
+        }
+    }
+
+    private function generate_astaccountregistrationcontext($postdata, $attribs = array())
+    {
+        if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['astaccountregistrationcontext'])) {
+            // Use Data Please
+            foreach ($attribs['auto_form_fields']['astaccountregistrationcontext']['data'] as $key) {
+                if (!isset($postdata[$key])) {
+                    throw new Exception("Key not set: " . $key, 12356);
+                }
+            }
+
+            return $this->generate_uid($postdata, $attribs);
+        }
+    }
+
+    private function generate_astaccountregistrationexten($postdata, $attribs = array())
+    {
+        if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['astaccountregistrationexten'])) {
+            $search = array(
+                'params' => array(
+                    'objectclass' => array(
+                        'type'  => 'exact',
+                        'value' => 'asterisksipuser',
+                    ),
+                ),
+            );
+
+            $auth  = Auth::get_instance($_SESSION['user']->get_domain());
+            $conf  = Conf::get_instance();
+            $users = $auth->list_users(NULL, Array('astaccountregistrationexten'), $search);
+
+            $lower_astaccountregistrationexten = $conf->get('astaccountregistrationexten_lower_barrier');
+            if (!$lower_astaccountregistrationexten) {
+                $lower_astaccountregistrationexten = 200;
+            }
+
+            // Start at the lower barrier + 1
+            $lower_astaccountregistrationexten = ($lower_astaccountregistrationexten + 1);
+
+            $higher_astaccountregistrationexten = $conf->get('astaccountregistrationexten_higher_barrier');
+            if (!$higher_astaccountregistrationexten) {
+                $higher_astaccountregistrationexten = 300;
+            }
+
+            $astaccountregistrationextens = Array();
+
+            foreach ($users['list'] as $dn => $attributes) {
+                if (!array_key_exists('astaccountregistrationexten', $attributes)) {
+                    continue;
+                }
+
+                if ($attributes['astaccountregistrationexten'] > $highest_astaccountregistrationexten) {
+                    $astaccountregistrationextens[] = $attributes['astaccountregistrationexten'];
+                }
+            }
+
+            for ($i = $lower_astaccountregistrationexten; $i < $higher_astaccountregistrationexten; $i++) {
+                if (!in_array($i, $astaccountregistrationextens)) {
+                    $astaccountregistrationexten = $i;
+                    break;
+                }
+            }
+
+            return $astaccountregistrationexten;
+        }
+    }
+
     private function generate_cn($postdata, $attribs = array())
     {
         if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['cn'])) {
@@ -892,6 +1004,19 @@ class kolab_api_service_form_value extends kolab_api_service
 
     }
 
+    private function validate_astaccountrealmedpassword($value, $postdata)
+    {
+        if (!array_key_exists('uid', $postdata) || empty($postdata['uid'])) {
+            $postdata['uid'] = $this->generate_uid($postdata);
+        }
+
+        $str = $postdata['uid'] . ":" . $_SESSION['user']->get_domain() . ":" . $postdata['userpassword'];
+
+        Log::trace("Inserting astaccountrealmedpassword with value md5('" . $str . "');");
+
+        return md5($str);
+    }
+
     private function validate_mail($value)
     {
         $auth = Auth::get_instance();
diff --git a/lib/kolab_api_service.php b/lib/kolab_api_service.php
index a2eda00..ea62318 100644
--- a/lib/kolab_api_service.php
+++ b/lib/kolab_api_service.php
@@ -264,7 +264,17 @@ abstract class kolab_api_service
         $form_service = $this->controller->get_service('form_value');
 
         // With the result, start validating the input
-        $form_service->validate(null, $attribs);
+        $validate_result = $form_service->validate(null, $attribs);
+
+        $special_attr_validate = Array();
+
+        foreach ($validate_result as $attr_name => $value) {
+            if (!empty($value) && $value !== "OK" && $value !== 0) {
+                $special_attr_validate[$attr_name] = $value;
+            }
+        }
+
+        Log::trace("kolab_api_service::parse_input_attributes() \$special_attr_validate: " . var_export($special_attr_validate, TRUE));
 
         $result       = array();
 
@@ -316,7 +326,9 @@ abstract class kolab_api_service
             }
         }
 
-        Log::trace("parse_input_attributes result", $result);
+        $result = array_merge($result, $special_attr_validate);
+
+        Log::trace("parse_input_attributes result (merge of \$result and \$special_attr_validate)", $result);
 
         return $result;
     }


commit e40d3efc8672ff8258412044147cf1fd12ad60d9
Author: Torsten Grote <grote at kolabsys.com>
Date:   Tue Oct 30 14:53:39 2012 +0100

    added error translations to signup page

diff --git a/lib/client/kolab_client_task_signup.php b/lib/client/kolab_client_task_signup.php
index a80f67c..d53602d 100644
--- a/lib/client/kolab_client_task_signup.php
+++ b/lib/client/kolab_client_task_signup.php
@@ -265,7 +265,7 @@ class kolab_client_task_signup extends kolab_client_task
 
         $form->set_title($this->translate('signup.formtitle'));
 
-        $this->output->add_translation('user.password.mismatch', 'signup.wronguid', 'signup.userexists', 'signup.wrongmailalternateaddress');
+        $this->output->add_translation('user.password.mismatch', 'signup.wronguid', 'signup.userexists', 'signup.wrongmailalternateaddress', 'internalerror', 'servererror');
 
         return $form->output();
     }


commit e3a67306286aadf231151e588a7ca4b3900e0ac4
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Mon Oct 29 19:24:01 2012 +0100

    Add label text for Asterisk attributes

diff --git a/lib/locale/en_US.php b/lib/locale/en_US.php
index 798deb0..ec9b3ca 100644
--- a/lib/locale/en_US.php
+++ b/lib/locale/en_US.php
@@ -203,17 +203,22 @@ $LANG['user.alias'] = 'Secondary Email Address(es)';
 $LANG['user.astaccountallowedcodec'] = 'Allowed codec(s)';
 $LANG['user.astaccountcallerid'] = 'Caller ID';
 $LANG['user.astaccountcontext'] = 'Account Context';
+$LANG['user.astaccountdefaultuser'] = 'Asterisk Account Default User';
 $LANG['user.astaccountdeny'] = 'Account deny';
 $LANG['user.astaccounthost'] = 'Asterisk Host';
+$LANG['user.astaccountmailbox'] = 'Mailbox';
 $LANG['user.astaccountnat'] = 'Account uses NAT';
 $LANG['user.astaccountname'] = 'Asterisk Account Name';
 $LANG['user.astaccountqualify'] = 'Account Qualify';
 $LANG['user.astaccountrealmedpassword'] = 'Realmed Account Password';
+$LANG['user.astaccountregistrationexten'] = 'Extension';
+$LANG['user.astaccountregistrationcontext'] = 'Registration Context';
 $LANG['user.astaccountsecret'] = 'Plaintext Password';
 $LANG['user.astaccounttype'] = 'Account Type';
 $LANG['user.astcontext'] = 'Asterisk Context';
 $LANG['user.asterisk'] = 'Asterisk SIP';
 $LANG['user.astextension'] = 'Asterisk Extension';
+$LANG['user.astvoicemailpassword'] = 'Voicemail PIN Code';
 $LANG['user.c'] = 'Country';
 $LANG['user.city'] = 'City';
 $LANG['user.cn'] = 'Common name';


commit d811ce2496eeaa54d08ef27c442f468c3e46e3d3
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Mon Oct 29 19:23:34 2012 +0100

    Add Asterisk attributes used to the correct pane

diff --git a/lib/client/kolab_client_task_user.php b/lib/client/kolab_client_task_user.php
index d67e7e3..166f8a5 100644
--- a/lib/client/kolab_client_task_user.php
+++ b/lib/client/kolab_client_task_user.php
@@ -275,16 +275,21 @@ class kolab_client_task_user extends kolab_client_task
             'astaccountallowedcodec'    => 'asterisk',
             'astaccountcallerid'        => 'asterisk',
             'astaccountcontext'         => 'asterisk',
+            'astaccountdefaultuser'     => 'asterisk',
             'astaccountdeny'            => 'asterisk',
             'astaccounthost'            => 'asterisk',
+            'astaccountmailbox'         => 'asterisk',
             'astaccountnat'             => 'asterisk',
             'astaccountname'            => 'asterisk',
             'astaccountqualify'         => 'asterisk',
             'astaccountrealmedpassword' => 'asterisk',
+            'astaccountregistrationexten'   => 'asterisk',
+            'astaccountregistrationcontext' => 'asterisk',
             'astaccountsecret'          => 'asterisk',
             'astaccounttype'            => 'asterisk',
             'astcontext'                => 'asterisk',
             'astextension'              => 'asterisk',
+            'astvoicemailpassword'      => 'asterisk',
         );
 
         // Prepare fields


commit 32bd233cc46bcf153bc52b50b9b15d6abe06817b
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Oct 25 13:29:07 2012 +0200

    Cleanup more data entries for API user.add call

diff --git a/lib/client/kolab_client_task_signup.php b/lib/client/kolab_client_task_signup.php
index 3565066..a80f67c 100644
--- a/lib/client/kolab_client_task_signup.php
+++ b/lib/client/kolab_client_task_signup.php
@@ -159,16 +159,18 @@ class kolab_client_task_signup extends kolab_client_task
 
         $this->api->get('system.select_domain', array('domain', $data['domain']));
 
-        // Remove domain from $data before adding user
+        // Remove some useless data from $data before adding user
         unset($data['domain']);
+        unset($data['recaptcha_challenge_field']);
+        unset($data['recaptcha_response_field']);
 
         // Add user
         $result = $this->api->post('user.add', null, $data);
 
-        if (array_key_exists('error_code', $result)) {
+        if ($code = $result->get_error_code()) {
             $this->output->command('display_message', 'internalerror', 'error');
-            return;
-        } else {
+        }
+        else {
             $this->output->set_object('taskcontent', $this->translate('signup.usercreated'));
             // TODO catch errors
             $this->send_mail($data);


commit 0257a4636c7c15043b26e8cf1bdf434bb11fa906
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Oct 25 13:16:34 2012 +0200

    Fix ouput->command() arguments

diff --git a/lib/client/kolab_client_task_signup.php b/lib/client/kolab_client_task_signup.php
index 8863c2b..3565066 100644
--- a/lib/client/kolab_client_task_signup.php
+++ b/lib/client/kolab_client_task_signup.php
@@ -101,7 +101,7 @@ class kolab_client_task_signup extends kolab_client_task
         $this->output->assign('form', $form);
         $this->output->set_env('token', $this->token);
         $this->output->set_object('taskcontent', $form);
-        $this->output->command('check_user_availability()');
+        $this->output->command('check_user_availability');
     }
 
     // check if user already exists
@@ -117,11 +117,11 @@ class kolab_client_task_signup extends kolab_client_task
         $result = $this->api->post('users.list', null, $post);
 
         if($result->get('count') > 0) {
-            $this->output->command('update_user_info("signup.userexists", "uid")');
+            $this->output->command('update_user_info', 'signup.userexists', 'uid');
             return false;
         }
 
-        $this->output->command('update_user_info("", "uid")');
+        $this->output->command('update_user_info', '', 'uid');
         return true;
     }
 


commit 8424bcdccef5f81886df8f30701e05f756a28c21
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Oct 25 13:05:34 2012 +0200

    Fix PHP Fatal error: Call to undefined method kolab_client_task_signup::api_login()

diff --git a/lib/client/kolab_client_task_signup.php b/lib/client/kolab_client_task_signup.php
index 7c40b36..8863c2b 100644
--- a/lib/client/kolab_client_task_signup.php
+++ b/lib/client/kolab_client_task_signup.php
@@ -69,7 +69,7 @@ class kolab_client_task_signup extends kolab_client_task
         }
 
         // Login ($result is a kolab_client_api_result instance)
-        $result = $this->api_login($this->config_get('bind_dn'), $this->config_get('bind_pw'), $this->domain);
+        $result = $this->api->login($this->config_get('bind_dn'), $this->config_get('bind_pw'), $this->domain);
 
         // Set the session token we got in the API client instance, so subsequent
         // API calls are made in the same session.


commit f180ec629f3edcb2d05eb284dac0c897a1010296
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Oct 25 12:45:04 2012 +0200

    Fix an issues where get_entry_attributes() was called without bind()

diff --git a/lib/Auth.php b/lib/Auth.php
index bd5c908..d7a0467 100644
--- a/lib/Auth.php
+++ b/lib/Auth.php
@@ -241,12 +241,13 @@ class Auth {
 
     public function get_entry_attribute($subject, $attribute)
     {
-        return $this->auth_instance()->get_entry_attribute($subject, $attribute);
+        $entry = $this->auth_instance()->get_attributes($subject, (array)$attribute);
+        return $entry[$attribute];
     }
 
     public function get_entry_attributes($subject, $attributes)
     {
-        return $this->auth_instance()->get_entry_attributes($subject, $attributes);
+        return $this->auth_instance()->get_attributes($subject, $attributes);
     }
 
     public function group_add($attributes, $typeid = null)
diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php
index e2f5d27..9bbcce0 100644
--- a/lib/Auth/LDAP.php
+++ b/lib/Auth/LDAP.php
@@ -242,6 +242,14 @@ class LDAP extends Net_LDAP3 {
 
     }
 
+    public function get_attributes($subject_dn, $attributes)
+    {
+        $this->_log(LOG_DEBUG, "Auth::LDAP::get_attributes() for $subject_dn");
+        $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
+
+        return $this->get_entry_attributes($subject_dn, $attributes);
+    }
+
     public function group_add($attrs, $typeid = null)
     {
         $base_dn = $this->entry_base_dn('group', $typeid);
diff --git a/lib/kolab_api_service.php b/lib/kolab_api_service.php
index ac590d1..a2eda00 100644
--- a/lib/kolab_api_service.php
+++ b/lib/kolab_api_service.php
@@ -446,11 +446,11 @@ abstract class kolab_api_service
         // Get extra attributes
         if (!empty($extra_attrs)) {
             $extra_attrs = $auth->get_entry_attributes($dn, $extra_attrs);
+
             if (!empty($extra_attrs)) {
                 $attrs = array_merge($attrs, $extra_attrs);
             }
         }
-
         // Replace unique attribute with 'id' key
         $attrs['id'] = $attrs[$unique_attr];
         unset($attrs[$unique_attr]);


commit 9d7ffbc91efa26c2548015f812fa6b2bd055d7bf
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Oct 25 12:32:48 2012 +0200

    Exclude attributes not listed in object type definition

diff --git a/lib/kolab_api_service.php b/lib/kolab_api_service.php
index 7d9da8b..ac590d1 100644
--- a/lib/kolab_api_service.php
+++ b/lib/kolab_api_service.php
@@ -410,33 +410,35 @@ abstract class kolab_api_service
         $dn          = key($attrs);
         $attrs       = $attrs[$dn];
         $extra_attrs = array();
-
-        // add group type id to the result
-        $attrs['type_id'] = $this->object_type_id($object_name, $attrs);
-
-        if (empty($attrs['type_id'])) {
-            if ($object_name == 'domain') {
-                $attrs['type_id'] = 1;
-            }
-        }
+        $type_id     = $this->object_type_id($object_name, $attrs);
+        $unique_attr = $this->unique_attribute();
 
         // Search for attributes associated with the type_id that are not part
-        // of the results returned earlier. Example: nsrole / nsroledn / aci, etc.
+        // of the result returned earlier. Example: nsrole / nsroledn / aci, etc.
         // @TODO: this should go to LDAP class
-        if ($attrs['type_id']) {
-            $uta = $this->object_type_attributes($object_name, $attrs['type_id']);
-
-            foreach ((array)$uta as $field_type => $attributes) {
-                foreach ($attributes as $attribute => $data) {
-                    if (!array_key_exists($attribute, $attrs)) {
-                        $extra_attrs[] = $attribute;
-                    }
-                }
-            }
+        if ($type_id) {
+            $uta = $this->object_type_attributes($object_name, $type_id);
+
+            $attributes = array_merge(
+                array_keys((array) $uta['auto_form_fields']),
+                array_keys((array) $uta['form_fields']),
+                array_keys((array) $uta['fields'])
+            );
+            $attributes = array_filter($attributes);
+            $attributes = array_unique($attributes);
+
+            $object_attributes = array_keys($attrs);
+
+            // extra attributes
+            $extra_attrs = array_diff($attributes, $object_attributes);
+
+            // remove attributes not listed in object type definition
+            // @TODO: make this optional?
+            $attributes = array_flip(array_merge($attributes, array($unique_attr)));
+            $attrs = array_intersect_key($attrs, $attributes);
         }
 
         // Insert the persistent, unique attribute
-        $unique_attr = $this->unique_attribute();
         if (!array_key_exists($unique_attr, $attrs)) {
             $extra_attrs[] = $unique_attr;
         }
@@ -453,6 +455,9 @@ abstract class kolab_api_service
         $attrs['id'] = $attrs[$unique_attr];
         unset($attrs[$unique_attr]);
 
+        // add object type id to the result
+        $attrs['type_id'] = $type_id;
+
         return $attrs;
     }
 


commit aac84b95b7a8c8aaeb8fe23ad7df69e65666db90
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Oct 25 12:24:01 2012 +0200

    Fix possible error in get_entry_attributes(s) where search() result
    wasn't checked for error

diff --git a/lib/ext/Net/LDAP3.php b/lib/ext/Net/LDAP3.php
index 2980076..466b351 100644
--- a/lib/ext/Net/LDAP3.php
+++ b/lib/ext/Net/LDAP3.php
@@ -714,10 +714,7 @@ class Net_LDAP3
 
     public function get_entry_attribute($subject_dn, $attribute)
     {
-        $this->config_set('return_attributes', $attribute);
-        $entries = $this->search($subject_dn, '(objectclass=*)', 'base')->entries(TRUE);
-        $entry_dn = key($entries);
-        $entry = $entries[$entry_dn];
+        $entry = $this->get_entry_attributes($subject_dn, (array)$attribute);
 
         return $entry[$attribute];
     }
@@ -725,9 +722,15 @@ class Net_LDAP3
     public function get_entry_attributes($subject_dn, $attributes)
     {
         $this->config_set('return_attributes', $attributes);
-        $entries = $this->search($subject_dn, '(objectclass=*)', 'base')->entries(TRUE);
+        $result = $this->search($subject_dn, '(objectclass=*)', 'base');
+
+        if (!$result) {
+            return array();
+        }
+
+        $entries  = $result->entries(true);
         $entry_dn = key($entries);
-        $entry = $entries[$entry_dn];
+        $entry    = $entries[$entry_dn];
 
         return $entry;
     }


commit 3904a4c512c0fd1ab408910e8bdcfe9743e39f42
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 24 20:13:57 2012 +0200

    Implement base64 encoding of binary content in JSON data (Bug #1047)

diff --git a/lib/functions.php b/lib/functions.php
index 9761358..4183454 100644
--- a/lib/functions.php
+++ b/lib/functions.php
@@ -127,7 +127,7 @@ function timer($time = null, $label = '')
 {
     $now = microtime(true);
     if ($time) {
-        //console(($label ? $label.' ' : '') . sprintf('%.4f', $now - $time));
+        console(($label ? $label.' ' : '') . sprintf('%.4f', $now - $time));
     }
     return $now;
 }
diff --git a/lib/kolab_api_controller.php b/lib/kolab_api_controller.php
index 0e45a30..a4a4b78 100644
--- a/lib/kolab_api_controller.php
+++ b/lib/kolab_api_controller.php
@@ -138,10 +138,13 @@ class kolab_api_controller
 
         $service = $this->request['service'];
         $method  = $this->request['method'];
-        $postdata = @json_decode($postdata, true);
 
         Log::debug("Calling $service.$method");
 
+        // Decode request data
+        $postdata = @json_decode($postdata, true);
+        kolab_json_output::decode($postdata);
+
         // validate user session
         if (!in_array($method, array('quit', 'authenticate'))) {
             if (!$this->session_validate($postdata)) {
diff --git a/lib/kolab_json_output.php b/lib/kolab_json_output.php
index fe0d062..473e579 100644
--- a/lib/kolab_json_output.php
+++ b/lib/kolab_json_output.php
@@ -19,6 +19,7 @@
  | along with this program. If not, see <http://www.gnu.org/licenses/>      |
  +--------------------------------------------------------------------------+
  | Author: Jeroen van Meeuwen <vanmeeuwen at kolabsys.com>                     |
+ | Author: Aleksander Machniak <machniak at kolabsys.com>                      |
  +--------------------------------------------------------------------------+
 */
 
@@ -29,7 +30,9 @@ class kolab_json_output
 {
 
     /**
+     * Send success response
      *
+     * @param mixed $data Data
      */
     public function success($data)
     {
@@ -42,7 +45,9 @@ class kolab_json_output
 
 
     /**
+     * Send error response
      *
+     * @param mixed $data Data
      */
     public function error($errdata, $code = 400)
     {
@@ -55,13 +60,74 @@ class kolab_json_output
 
 
     /**
+     * Send response
      *
+     * @param mixed $data Data
      */
-    public function send($data)
+    protected function send($data)
     {
+        // Encode response
+        self::encode($data);
+
+        // Send response
         header("Content-Type: application/json");
         echo json_encode($data);
         exit;
     }
 
+
+    /**
+     * Parse response and base64-encode non-UTF8/binary data
+     *
+     * @param mixed $data Data
+     *
+     * @return bool True if data was encoded
+     */
+    public static function encode(&$data)
+    {
+        if (is_array($data)) {
+            $encoded = array();
+            foreach (array_keys($data) as $key) {
+                if (self::encode($data[$key])) {
+                    $encoded[] = $key;
+                }
+            }
+            if (!empty($encoded)) {
+                $data['__encoded'] = $encoded;
+            }
+        }
+        else if (is_string($data) && $data !== '') {
+            $result = @json_encode($data);
+            // In case of invalid characters json_encode returns "null"
+            if (($result === 'null' && $data != 'null') || $result === false) {
+                $data = base64_encode($data);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Parse response and base64-decode encoded data
+     *
+     * @param mixed $data Data
+     */
+    public static function decode(&$data)
+    {
+        if (is_array($data)) {
+            $encoded = $data['__encoded'];
+            foreach ($data as $key => $value) {
+                if (is_array($value)) {
+                    self::decode($data[$key]);
+                }
+                else if (is_string($value) && $encoded && in_array($key, $encoded)) {
+                    $data[$key] = base64_decode($value);
+                }
+            }
+            unset($data['__encoded']);
+        }
+    }
+
 }
diff --git a/lib/kolab_utils.php b/lib/kolab_utils.php
index 9c6c35e..2c4c2a0 100644
--- a/lib/kolab_utils.php
+++ b/lib/kolab_utils.php
@@ -149,7 +149,7 @@ class kolab_utils
     /**
      * Finds wether an array is associative or not.
      */
-    public static function is_assoc ($arr)
+    public static function is_assoc($arr)
     {
         return is_array($arr) && count(array_filter(array_keys($arr), 'is_string')) == count($arr);
     }


commit f944da77efb7de0a38c4d616d272916306a923f0
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 24 13:13:59 2012 +0200

    Limit OUs list by base_dn according to specified object type (#699)

diff --git a/lib/api/kolab_api_service_form_value.php b/lib/api/kolab_api_service_form_value.php
index 70aa8b9..fbd55fc 100644
--- a/lib/api/kolab_api_service_form_value.php
+++ b/lib/api/kolab_api_service_form_value.php
@@ -105,7 +105,7 @@ class kolab_api_service_form_value extends kolab_api_service
     {
         //console($postdata);
 
-        $attribs   = $this->object_type_attributes($postdata['object_type'], $postdata['type_id']);
+        $attribs   = $this->object_type_attributes($postdata['object_type'], $postdata['type_id'], true, $key_name);
         $attr_name = $postdata['attribute'];
         $result    = array(
             // return search value, so client can match response to request
@@ -117,6 +117,9 @@ class kolab_api_service_form_value extends kolab_api_service
             return $result;
         }
 
+        if ($key_name) {
+            $postdata['type_key'] = $key_name;
+        }
 
         $method_name = 'list_options_' . strtolower($attr_name) . '_' . strtolower($postdata['object_type']);
 
@@ -151,10 +154,14 @@ class kolab_api_service_form_value extends kolab_api_service
     public function select_options($getdata, $postdata)
     {
         //console("form_value.select_options postdata", $postdata);
-        $attribs    = $this->object_type_attributes($postdata['object_type'], $postdata['type_id']);
+        $attribs    = $this->object_type_attributes($postdata['object_type'], $postdata['type_id'], true, $key_name);
         $attributes = (array) $postdata['attributes'];
         $result     = array();
 
+        if ($key_name) {
+            $postdata['type_key'] = $key_name;
+        }
+
         foreach ($attributes as $attr_name) {
             if (empty($attr_name)) {
                 continue;
@@ -407,11 +414,6 @@ class kolab_api_service_form_value extends kolab_api_service
 
     private function generate_mail_resource($postdata, $attribs = array())
     {
-        $db = SQL::get_instance();
-        $result = $db->fetch_assoc($db->query("SELECT `key` FROM `resource_types` WHERE id = ?", $postdata['type_id']));
-
-        $object_type_key = $result['key'];
-
         if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['mail'])) {
             // Use Data Please
             foreach ($attribs['auto_form_fields']['mail']['data'] as $key) {
@@ -424,7 +426,7 @@ class kolab_api_service_form_value extends kolab_api_service
             //console("normalized resource data", $resourcedata);
 
             // TODO: Normalize $postdata
-            $mail_local  = 'resource-' . $object_type_key . '-' . strtolower($resourcedata['cn']);
+            $mail_local  = 'resource-' . $postdata['type_key'] . '-' . strtolower($resourcedata['cn']);
             $mail_domain = $_SESSION['user']->get_domain();
             $mail        = $mail_local . '@' . $mail_domain;
             $auth        = Auth::get_instance($_SESSION['user']->get_domain());
@@ -782,8 +784,15 @@ class kolab_api_service_form_value extends kolab_api_service
         $conf = Conf::get_instance();
 
         $unique_attr = $this->unique_attribute();
+        $object_type = $postdata['object_type'];
+        $object_key  = $postdata['type_key'];
 
-        $base_dn = $conf->get('user_base_dn');
+        if ($object_key && $object_type) {
+            $base_dn = $conf->get($object_key . '_' . $object_type . '_base_dn');
+        }
+        if (!$base_dn && $object_type) {
+            $base_dn = $conf->get($object_type . '_base_dn');
+        }
         if (!$base_dn) {
             $base_dn = $conf->get('base_dn');
         }
diff --git a/lib/kolab_api_service.php b/lib/kolab_api_service.php
index 3293bb5..7d9da8b 100644
--- a/lib/kolab_api_service.php
+++ b/lib/kolab_api_service.php
@@ -58,10 +58,11 @@ abstract class kolab_api_service
      * @param string $object_name  Name of the object (user, group, etc.)
      * @param int    $type_id      User type identifier
      * @param bool   $required     Throws exception on empty ID
+     * @param string $key_name     Reference to a variable which will be set to type key
      *
      * @return array User type attributes
      */
-    protected function object_type_attributes($object_name, $type_id, $required = true)
+    protected function object_type_attributes($object_name, $type_id, $required = true, &$key_name = null)
     {
         if (!$object_name || !in_array($object_name, $this->supported_types)) {
             return array();
@@ -101,6 +102,8 @@ abstract class kolab_api_service
             }
         }
 
+        $key_name = $object_types[$type_id]['key'];
+
         return $object_types[$type_id]['attributes'];
     }
 


commit d41fd23fdc80f48c27e70caa7b083c5b7e8396aa
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 24 12:53:17 2012 +0200

    Code improvements

diff --git a/lib/api/kolab_api_service_domain.php b/lib/api/kolab_api_service_domain.php
index 636c230..2d91358 100644
--- a/lib/api/kolab_api_service_domain.php
+++ b/lib/api/kolab_api_service_domain.php
@@ -145,14 +145,11 @@ class kolab_api_service_domain extends kolab_api_service
     public function domain_effective_rights($getdata, $postdata)
     {
         $auth = Auth::get_instance();
-        $conf = Conf::get_instance();
 
         if (!empty($getdata['domain'])) {
-            $entry_dn = $getdata['domain'];
-
-            $unique_attr = $conf->get('ldap', 'unique_attribute');
-
-            $domain = $auth->domain_find_by_attribute(array($unique_attr => $entry_dn));
+            $entry_dn    = $getdata['domain'];
+            $unique_attr = $this->unique_attribute();
+            $domain      = $auth->domain_find_by_attribute(array($unique_attr => $entry_dn));
 
             if (!empty($domain)) {
                 $entry_dn = key($domain);
diff --git a/lib/api/kolab_api_service_form_value.php b/lib/api/kolab_api_service_form_value.php
index 1443a1b..70aa8b9 100644
--- a/lib/api/kolab_api_service_form_value.php
+++ b/lib/api/kolab_api_service_form_value.php
@@ -260,15 +260,9 @@ class kolab_api_service_form_value extends kolab_api_service
                 }
             }
 
-            $auth = Auth::get_instance($_SESSION['user']->get_domain());
-            $conf = Conf::get_instance();
-
-            $unique_attr = $conf->get('unique_attribute');
-            if (!$unique_attr) {
-                $unique_attr = 'nsuniqueid';
-            }
-
-            $cn = $postdata['cn'];
+            $auth        = Auth::get_instance($_SESSION['user']->get_domain());
+            $unique_attr = $this->unique_attribute();
+            $cn          = $postdata['cn'];
 
             $x = 2;
             while (($resource_found = $auth->resource_find_by_attribute(array('cn' => $cn)))) {
@@ -430,19 +424,11 @@ class kolab_api_service_form_value extends kolab_api_service
             //console("normalized resource data", $resourcedata);
 
             // TODO: Normalize $postdata
-            $mail_local = 'resource-' . $object_type_key . '-' . strtolower($resourcedata['cn']);
+            $mail_local  = 'resource-' . $object_type_key . '-' . strtolower($resourcedata['cn']);
             $mail_domain = $_SESSION['user']->get_domain();
-            $mail = $mail_local . '@' . $mail_domain;
-
-            $orig_mail = $mail;
-
-            $auth = Auth::get_instance($_SESSION['user']->get_domain());
-            $conf = Conf::get_instance();
-
-            $unique_attr = $conf->get('unique_attribute');
-            if (!$unique_attr) {
-                $unique_attr = 'nsuniqueid';
-            }
+            $mail        = $mail_local . '@' . $mail_domain;
+            $auth        = Auth::get_instance($_SESSION['user']->get_domain());
+            $unique_attr = $this->unique_attribute();
 
             $x = 2;
             while (($resource_found = $auth->resource_find_by_attribute(array('mail' => $mail)))) {
@@ -461,8 +447,6 @@ class kolab_api_service_form_value extends kolab_api_service
             }
 
             return $mail;
-
-
         }
     }
 
@@ -594,13 +578,8 @@ class kolab_api_service_form_value extends kolab_api_service
 
             $orig_uid = $uid;
 
-            $auth = Auth::get_instance($_SESSION['user']->get_domain());
-            $conf = Conf::get_instance();
-
-            $unique_attr = $conf->get('unique_attribute');
-            if (!$unique_attr) {
-                $unique_attr = 'nsuniqueid';
-            }
+            $auth        = Auth::get_instance($_SESSION['user']->get_domain());
+            $unique_attr = $this->unique_attribute();
 
             $x = 2;
             while (($user_found = $auth->user_find_by_attribute(array('uid' => $uid)))) {
@@ -625,18 +604,17 @@ class kolab_api_service_form_value extends kolab_api_service
     private function generate_uidnumber($postdata, $attribs = array())
     {
         if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['uidnumber'])) {
-            $auth = Auth::get_instance($_SESSION['user']->get_domain());
-            $conf = Conf::get_instance();
-
-            $search = Array(
-                    'params' => Array(
-                            'objectclass' => Array(
-                                    'type' => 'exact',
-                                    'value' => 'posixaccount',
-                                ),
-                        ),
-                );
+            $search = array(
+                'params' => array(
+                    'objectclass' => array(
+                        'type'  => 'exact',
+                        'value' => 'posixaccount',
+                    ),
+                ),
+            );
 
+            $auth  = Auth::get_instance($_SESSION['user']->get_domain());
+            $conf  = Conf::get_instance();
             $users = $auth->list_users(NULL, Array('uidnumber'), $search);
 
             $highest_uidnumber = $conf->get('uidnumber_lower_barrier');
@@ -803,10 +781,7 @@ class kolab_api_service_form_value extends kolab_api_service
         $auth = Auth::get_instance();
         $conf = Conf::get_instance();
 
-        $unique_attr = $conf->get('unique_attribute');
-        if (!$unique_attr) {
-            $unique_attr = 'nsuniqueid';
-        }
+        $unique_attr = $this->unique_attribute();
 
         $base_dn = $conf->get('user_base_dn');
         if (!$base_dn) {
diff --git a/lib/api/kolab_api_service_role.php b/lib/api/kolab_api_service_role.php
index 18ebf19..cf1e339 100644
--- a/lib/api/kolab_api_service_role.php
+++ b/lib/api/kolab_api_service_role.php
@@ -137,16 +137,11 @@ class kolab_api_service_role extends kolab_api_service
     public function role_effective_rights($getdata, $postdata)
     {
         $auth = Auth::get_instance();
-        $conf = Conf::get_instance();
 
         // Roles are special in that they are ldapsubentries.
         if (!empty($getdata['role'])) {
-            $unique_attr = $conf->get('unique_attribute');
-            if (empty($unique_attr)) {
-                $unique_attr = 'nsuniqueid';
-            }
-
-            $role = $auth->role_find_by_attribute(Array($unique_attr => $getdata['role']));
+            $unique_attr = $this->unique_attribute();
+            $role        = $auth->role_find_by_attribute(Array($unique_attr => $getdata['role']));
 
             if (is_array($role) && count($role) == 1) {
                 $role_dn = key($role);
diff --git a/lib/kolab_api_service.php b/lib/kolab_api_service.php
index e90aa87..3293bb5 100644
--- a/lib/kolab_api_service.php
+++ b/lib/kolab_api_service.php
@@ -403,7 +403,6 @@ abstract class kolab_api_service
             return $attrs;
         }
 
-        $conf        = Conf::get_instance();
         $auth        = Auth::get_instance();
         $dn          = key($attrs);
         $attrs       = $attrs[$dn];
@@ -434,11 +433,7 @@ abstract class kolab_api_service
         }
 
         // Insert the persistent, unique attribute
-        $unique_attr = $conf->get('unique_attribute');
-        if (!$unique_attr) {
-            $unique_attr = 'nsuniqueid';
-        }
-
+        $unique_attr = $this->unique_attribute();
         if (!array_key_exists($unique_attr, $attrs)) {
             $extra_attrs[] = $unique_attr;
         }
@@ -458,4 +453,20 @@ abstract class kolab_api_service
         return $attrs;
     }
 
+    /**
+     * Returns name of unique attribute
+     *
+     * @return string Unique attribute name
+     */
+    protected function unique_attribute()
+    {
+        $conf        = Conf::get_instance();
+        $unique_attr = $conf->get('unique_attribute');
+
+        if (!$unique_attr) {
+            $unique_attr = 'nsuniqueid';
+        }
+
+        return $unique_attr;
+    }
 }


commit 2a405d0483013bceac13e49c9f8a32dd0b7d0f2c
Author: Aleksander Machniak <machniak at kolabsys.com>
Date:   Tue Oct 23 20:36:37 2012 +0200

    Small style improvement

diff --git a/public_html/skins/default/style.css b/public_html/skins/default/style.css
index 4b9c6ec..aeb234a 100644
--- a/public_html/skins/default/style.css
+++ b/public_html/skins/default/style.css
@@ -383,9 +383,9 @@ td.label {
   margin-left: -1px;
   height: 18px;
   width: 37px;
-  border: 1px solid #d0d0d0;
   background-color: #f0f0f0;
   cursor: pointer;
+  border-right: 1px solid #e0e0e0;
   border-top-left-radius: 3px;
   border-bottom-left-radius: 3px;
   -moz-border-radius-topleft: 3px;


commit 222d7d21892885eb8aa16ce7b96bd69589e05f26
Author: Aleksander Machniak <alec at alec.pl>
Date:   Tue Oct 23 19:59:45 2012 +0200

    Remove Log class usage (#1134)

diff --git a/lib/ext/Net/LDAP3.php b/lib/ext/Net/LDAP3.php
index 1f0321f..2980076 100644
--- a/lib/ext/Net/LDAP3.php
+++ b/lib/ext/Net/LDAP3.php
@@ -127,7 +127,6 @@ class Net_LDAP3
      */
     public function __construct($config = Array())
     {
-        Log::trace("Net_LDAP3 being constructed");
         if (!empty($config) && is_array($config)) {
             foreach ($config as $key => $value) {
                 if (!isset($this->config[$key]) || empty($this->config[$key])) {
@@ -258,7 +257,7 @@ class Net_LDAP3
                     }
                 }
             } else {
-                Log::warning("LDAP: No schema details exist for attribute $attribute (which is strange)");
+                $this->_warning("LDAP: No schema details exist for attribute $attribute (which is strange)");
             }
 
             // The relevant parts only, please
@@ -434,8 +433,6 @@ class Net_LDAP3
      */
     public function connect()
     {
-        Log::trace("Net_LDAP3 connecting");
-
         if (!function_exists('ldap_connect')) {
             new PEAR_Error("No ldap support in this PHP installation", 100);
             return FALSE;
@@ -1874,7 +1871,7 @@ class Net_LDAP3
             }
             else {
                 $this->_debug("LDAP: S: " . ldap_error($this->conn));
-                Log::warning("LDAP: Failed to rename $olddn to $newrdn,$new_parent");
+                $this->_warning("LDAP: Failed to rename $olddn to $newrdn,$new_parent");
                 return FALSE;
             }
         }
@@ -1889,7 +1886,7 @@ class Net_LDAP3
             }
             else {
                 $this->_debug("LDAP: S: " . ldap_error($this->conn));
-                Log::warning("LDAP: Failed to replace attributes on $subject_dn: " . json_encode($attributes['replace']));
+                $this->_warning("LDAP: Failed to replace attributes on $subject_dn: " . json_encode($attributes['replace']));
                 return FALSE;
             }
         }
@@ -1904,7 +1901,7 @@ class Net_LDAP3
             }
             else {
                 $this->_debug("LDAP: S: " . ldap_error($this->conn));
-                Log::warning("LDAP: Failed to delete attributes on $subject_dn: " . json_encode($attributes['del']));
+                $this->_warning("LDAP: Failed to delete attributes on $subject_dn: " . json_encode($attributes['del']));
                 return FALSE;
             }
         }
@@ -1920,7 +1917,7 @@ class Net_LDAP3
             }
             else {
                 $this->_debug("LDAP: S: " . ldap_error($this->conn));
-                Log::warning("LDAP: Failed to add attributes on $subject_dn: " . json_encode($attributes['add']));
+                $this->_warning("LDAP: Failed to add attributes on $subject_dn: " . json_encode($attributes['add']));
                 return FALSE;
             }
         }


commit 7a98138b282256c96a24b12e5c40bc96484c51bd
Author: Aleksander Machniak <alec at alec.pl>
Date:   Tue Oct 23 19:37:49 2012 +0200

    Check user domain before getting all domains in _validate_email_address_in_any_of_my_domains() (#1138)

diff --git a/lib/api/kolab_api_service_form_value.php b/lib/api/kolab_api_service_form_value.php
index 9e213f6..1443a1b 100644
--- a/lib/api/kolab_api_service_form_value.php
+++ b/lib/api/kolab_api_service_form_value.php
@@ -959,7 +959,8 @@ class kolab_api_service_form_value extends kolab_api_service
         }
     }
 
-    private function _highest_of_two($one, $two) {
+    private function _highest_of_two($one, $two)
+    {
         if ($one > $two) {
             return $one;
         } elseif ($one == $two) {
@@ -1091,7 +1092,8 @@ class kolab_api_service_form_value extends kolab_api_service
         return array('list' => $result);
     }
 
-    private function _validate_email_address($mail_address) {
+    private function _validate_email_address($mail_address)
+    {
         $valid = true;
 
         $at_index = strrpos($mail_address, "@");
@@ -1150,20 +1152,8 @@ class kolab_api_service_form_value extends kolab_api_service
         return $valid;
     }
 
-    private function _validate_email_address_in_any_of_my_domains($mail_address) {
-        $valid = false;
-
-        $auth = Auth::get_instance();
-        $conf = Conf::get_instance();
-
-        $my_primary_domain = $_SESSION['user']->get_domain();
-        $all_domains = $auth->list_domains();
-        $all_domains = $all_domains['list'];
-
-        $valid_domains = array();
-
-        $dna = $conf->get('domain_name_attribute');
-
+    private function _validate_email_address_in_any_of_my_domains($mail_address)
+    {
         $at_index = strrpos($mail_address, "@");
         if (is_bool($at_index) && !$at_index) {
             throw new Exception("Invalid email address: No domain name space", 235);
@@ -1171,6 +1161,20 @@ class kolab_api_service_form_value extends kolab_api_service
             $email_domain = substr($mail_address, $at_index+1);
         }
 
+        $my_primary_domain = $_SESSION['user']->get_domain();
+
+        if ($email_domain == $my_primary_domain) {
+            return true;
+        }
+
+        $auth          = Auth::get_instance();
+        $conf          = Conf::get_instance();
+        $all_domains   = $auth->list_domains();
+        $all_domains   = $all_domains['list'];
+        $valid_domains = array();
+        $dna           = $conf->get('domain_name_attribute');
+        $valid         = false;
+
         Log::trace("_validate_email_address_in_any_of_mydomains(\$mail_address = " . var_export($mail_address, TRUE) . ")");
         Log::trace("\$all_domains includes: " . var_export($all_domains, TRUE) . " (must include domain for \$mail_address)");
 


commit e9eaf6114dadcc862e8dc18cf9e49908e9fbed89
Author: Aleksander Machniak <alec at alec.pl>
Date:   Tue Oct 23 15:40:49 2012 +0200

    Fix handling of empty select values list

diff --git a/public_html/js/kolab_admin.js b/public_html/js/kolab_admin.js
index 99fc6b9..3290bf1 100644
--- a/public_html/js/kolab_admin.js
+++ b/public_html/js/kolab_admin.js
@@ -1737,7 +1737,7 @@ function kolab_admin()
       else if (attr.type != 'text')
         data.type = attr.type;
 
-      if ((attr.type == 'select' || attr.type == 'multiselect') && attr.values)
+      if ((attr.type == 'select' || attr.type == 'multiselect') && attr.values && attr.values.length)
         data.values = attr.values;
 
       if (attr.optional)


commit 53141e75f644ae0c5500ff15d509d587e7638aa4
Author: Aleksander Machniak <alec at alec.pl>
Date:   Tue Oct 23 15:19:01 2012 +0200

    Fix char-case of attribute names and checking for required attributes

diff --git a/lib/client/kolab_client_task_settings.php b/lib/client/kolab_client_task_settings.php
index a6254f0..849bc90 100644
--- a/lib/client/kolab_client_task_settings.php
+++ b/lib/client/kolab_client_task_settings.php
@@ -837,9 +837,8 @@ class kolab_client_task_settings extends kolab_client_task
         if (!empty($response['list'])) {
             // remove objectClass
             $attributes = array_diff($response['list'], array('objectClass'));
-            $attributes = array_map('strtolower', $attributes);
             if (count($attributes)) {
-                $attributes = array_combine($attributes, $attributes);
+                $attributes = array_combine(array_map('strtolower', $attributes), $attributes);
             }
         }
 


commit dee1ca2accce325f947efb1e377c102d8374918f
Author: Aleksander Machniak <alec at alec.pl>
Date:   Tue Oct 23 15:11:26 2012 +0200

    Small improvements

diff --git a/lib/locale/en.php b/lib/locale/en.php
index 4527251..fb002ab 100644
--- a/lib/locale/en.php
+++ b/lib/locale/en.php
@@ -35,7 +35,7 @@ $LANG['group.add'] = 'Add Group';
 $LANG['group.add.success'] = 'Group created successfully.';
 $LANG['group.cn'] = 'Common name';
 $LANG['group.delete.success'] = 'Group deleted successfully.';
-$LANG['group.edit.success'] = 'Group edited successfully.';
+$LANG['group.edit.success'] = 'Group updated successfully.';
 $LANG['group.gidnumber'] = 'Primary group number';
 $LANG['group.list'] = 'Groups List';
 $LANG['group.list.records'] = '$1 to $2 of $3';
@@ -90,7 +90,7 @@ $LANG['resource.uniquemember'] = 'Collection Members';
 $LANG['role.add'] = 'Add Role';
 $LANG['role.cn'] = 'Role Name';
 $LANG['role.description'] = 'Role Description';
-$LANG['role.edit.success'] = 'Role edited successfully';
+$LANG['role.edit.success'] = 'Role updated successfully';
 $LANG['role.list'] = 'Role List';
 $LANG['role.list.records'] = '$1 to $2 of $3';
 $LANG['role.norecords'] = 'No role records found!';
@@ -162,7 +162,7 @@ $LANG['user.country'] = 'Country';
 $LANG['user.country.desc'] = '2 letter code from ISO 3166-1';
 $LANG['user.delete.success'] = 'User deleted successfully.';
 $LANG['user.displayname'] = 'Display name';
-$LANG['user.edit.success'] = 'User edited successfully.';
+$LANG['user.edit.success'] = 'User updated successfully.';
 $LANG['user.fax'] = 'Fax number';
 $LANG['user.fbinterval'] = 'Free-Busy interval';
 $LANG['user.fbinterval.desc'] = 'Leave blank for default (60 days)';
diff --git a/lib/locale/en_US.php b/lib/locale/en_US.php
index d8911ab..798deb0 100644
--- a/lib/locale/en_US.php
+++ b/lib/locale/en_US.php
@@ -60,7 +60,7 @@ $LANG['group.add'] = 'Add Group';
 $LANG['group.add.success'] = 'Group created successfully.';
 $LANG['group.cn'] = 'Common name';
 $LANG['group.delete.success'] = 'Group deleted successfully.';
-$LANG['group.edit.success'] = 'Group edited successfully.';
+$LANG['group.edit.success'] = 'Group updated successfully.';
 $LANG['group.gidnumber'] = 'Primary group number';
 $LANG['group.list'] = 'Groups List';
 $LANG['group.mail'] = 'Primary Email Address';
@@ -224,7 +224,7 @@ $LANG['user.country'] = 'Country';
 $LANG['user.country.desc'] = '2 letter code from ISO 3166-1';
 $LANG['user.delete.success'] = 'User deleted successfully.';
 $LANG['user.displayname'] = 'Display name';
-$LANG['user.edit.success'] = 'User edited successfully.';
+$LANG['user.edit.success'] = 'User updated successfully.';
 $LANG['user.fax'] = 'Fax number';
 $LANG['user.fbinterval'] = 'Free-Busy interval';
 $LANG['user.fbinterval.desc'] = 'Leave blank for default (60 days)';


commit ce9f741c3161f5bbbc7c7a2430e4458a6c40f800
Author: Aleksander Machniak <alec at alec.pl>
Date:   Tue Oct 23 12:36:49 2012 +0200

    Update the session password on own's password change via user.edit (Bug #964)

diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php
index 2891857..e2f5d27 100644
--- a/lib/Auth/LDAP.php
+++ b/lib/Auth/LDAP.php
@@ -530,7 +530,18 @@ class LDAP extends Net_LDAP3 {
         $user_dn = key($user);
 
         // We should start throwing stuff over the fence here.
-        return $this->modify_entry($user_dn, $user[$user_dn], $attributes);
+        $result = $this->modify_entry($user_dn, $user[$user_dn], $attributes);
+
+        // Handle modification of current user data
+        if (!empty($result) && $user_dn == $_SESSION['user']->user_bind_dn) {
+            // update session password
+            if (!empty($result['replace']) && !empty($result['replace']['userpassword'])) {
+                $pass = $result['replace']['userpassword'];
+                $_SESSION['user']->user_bind_pw = is_array($pass) ? implode($pass) : $pass;
+            }
+        }
+
+        return $result;
     }
 
     public function user_delete($user)


commit 5d73614d72655eaddc5f53c50931d123fd695be8
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Oct 22 15:37:44 2012 +0200

    Fix domain edit/delete by unique attribute

diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php
index 7178559..2891857 100644
--- a/lib/Auth/LDAP.php
+++ b/lib/Auth/LDAP.php
@@ -166,7 +166,9 @@ class LDAP extends Net_LDAP3 {
 
     public function domain_delete($domain)
     {
-        return $this->entry_delete($domain);
+        $base_dn = $this->conf->get('ldap', 'domain_base_dn');
+
+        return $this->entry_delete($domain, array(), $base_dn);
     }
 
     public function domain_find_by_attribute($attribute)
@@ -181,10 +183,10 @@ class LDAP extends Net_LDAP3 {
         $this->_log(LOG_DEBUG, "Auth::LDAP::domain_info() for domain " . var_export($domain, true));
         $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
 
-        $domain_dn = $this->entry_dn($domain);
+        $domain_base_dn = $this->conf->get('ldap', 'domain_base_dn');
+        $domain_dn      = $this->entry_dn($domain, array(), $domain_base_dn);
 
         if (!$domain_dn) {
-            $domain_base_dn        = $this->conf->get('ldap', 'domain_base_dn');
             $domain_filter         = $this->conf->get('ldap', 'domain_filter');
             $domain_name_attribute = $this->conf->get('ldap', 'domain_name_attribute');
             $domain_filter         = "(&" . $domain_filter . "(" . $domain_name_attribute . "=" . $domain . "))";
@@ -624,11 +626,11 @@ class LDAP extends Net_LDAP3 {
     /**
      * delete_entry() wrapper with binding and DN resolving
      */
-    protected function entry_delete($entry, $attributes = array())
+    protected function entry_delete($entry, $attributes = array(), $base_dn = null)
     {
         $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
 
-        $entry_dn = $this->entry_dn($entry, $attributes);
+        $entry_dn = $this->entry_dn($entry, $attributes, $base_dn);
 
         if (!$entry_dn) {
             return false;
diff --git a/lib/ext/Net/LDAP3.php b/lib/ext/Net/LDAP3.php
index 318f7f4..1f0321f 100644
--- a/lib/ext/Net/LDAP3.php
+++ b/lib/ext/Net/LDAP3.php
@@ -636,10 +636,11 @@ class Net_LDAP3
      *
      * @param string $subject    Entry string (e.g. entry DN or unique attribute value)
      * @param array  $attributes Additional attributes
+     * @param string $base_dn    Optional base DN
      *
      * @return string Entry DN string
      */
-    public function entry_dn($subject, $attributes = array())
+    public function entry_dn($subject, $attributes = array(), $base_dn = NULL)
     {
         $this->_debug("entry_dn on subject $subject");
         $is_dn = ldap_explode_dn($subject, 1);
@@ -651,7 +652,7 @@ class Net_LDAP3
 
         $unique_attr = $this->config_get('unique_attribute', 'nsuniqueid');
         $attributes  = array_merge(array($unique_attr => $subject), (array)$attributes);
-        $subject     = $this->entry_find_by_attribute($attributes);
+        $subject     = $this->entry_find_by_attribute($attributes, $base_dn);
 
         if (!empty($subject)) {
             return key($subject);


commit 376f798d83f568610f771edc6e42f7d48ccb7863
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Oct 22 15:25:24 2012 +0200

    Fix PHP fatal error on wrong _search() result

diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php
index 1428cfc..7178559 100644
--- a/lib/Auth/LDAP.php
+++ b/lib/Auth/LDAP.php
@@ -191,6 +191,7 @@ class LDAP extends Net_LDAP3 {
 
             $this->_log(LOG_DEBUG, "Auth::LDAP::domain_info() uses _search()");
             $result = $this->_search($domain_base_dn, $domain_filter, $attributes);
+            $result = $result->entries(true);
         } else {
             $this->_log(LOG_DEBUG, "Auth::LDAP::domain_info() uses _read()");
             $result = $this->_read($domain_dn, $attributes);
@@ -794,6 +795,7 @@ class LDAP extends Net_LDAP3 {
         );
 
         $subject    = $this->_search($subject_dn);
+        $subject    = $subject->entries(true);
         $attributes = $this->allowed_attributes($subject[$subject_dn]['objectclass']);
         $attributes = array_merge($attributes['may'], $attributes['must']);
 


commit 14f390ae2c43ecbd3be0c0a1fe8578e5c305ec3d
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Oct 22 14:43:31 2012 +0200

    Remove Auth::LDAP references in debug messages

diff --git a/lib/ext/Net/LDAP3.php b/lib/ext/Net/LDAP3.php
index 1f37f3e..318f7f4 100644
--- a/lib/ext/Net/LDAP3.php
+++ b/lib/ext/Net/LDAP3.php
@@ -660,7 +660,7 @@ class Net_LDAP3
 
     public function entry_find_by_attribute($attributes, $base_dn = NULL)
     {
-        $this->_debug("Auth::LDAP::entry_find_by_attribute(\$attributes, \$base_dn) called with base_dn", $base_dn, "and attributes", $attributes);
+        $this->_debug("Net_LDAP3::entry_find_by_attribute(\$attributes, \$base_dn) called with base_dn", $base_dn, "and attributes", $attributes);
 
         if (empty($attributes) || !is_array($attributes)) {
             return FALSE;
@@ -964,7 +964,7 @@ class Net_LDAP3
 
         $rdn_attr = $rdn_components[0];
 
-        $this->_debug("Auth::LDAP::modify_entry() using rdn attribute: " . $rdn_attr);
+        $this->_debug("Net_LDAP3::modify_entry() using rdn attribute: " . $rdn_attr);
 
         $mod_array = array(
             'add'       => array(), // For use with ldap_mod_add()


commit 21d2dbd3b1cae736f20aaabff3d88b3b6232098c
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Oct 22 14:33:39 2012 +0200

    CS fixes

diff --git a/lib/ext/Net/LDAP3.php b/lib/ext/Net/LDAP3.php
index 7c51b8a..1f37f3e 100644
--- a/lib/ext/Net/LDAP3.php
+++ b/lib/ext/Net/LDAP3.php
@@ -125,7 +125,8 @@ class Net_LDAP3
      *                          that have in fact been set, use the config_set()
      *                          method after initialization.
      */
-    public function __construct($config = Array()) {
+    public function __construct($config = Array())
+    {
         Log::trace("Net_LDAP3 being constructed");
         if (!empty($config) && is_array($config)) {
             foreach ($config as $key => $value) {
@@ -139,7 +140,8 @@ class Net_LDAP3
     /**
      *  Add multiple entries to the directory information tree in one go.
      */
-    public function add_entries($entries, $attributes = Array()) {
+    public function add_entries($entries, $attributes = Array())
+    {
         // If $entries is an associative array, it's keys are DNs and it's
         // values are the attributes for that DN.
         //
@@ -385,7 +387,8 @@ class Net_LDAP3
      *  @param  string  $key        Configuration key
      *  @param  mixed   $default    Default value to return
      */
-    public function config_get($key, $default = NULL) {
+    public function config_get($key, $default = NULL)
+    {
         if (!empty($this->_config_get_hook)) {
             return call_user_func_array($this->_config_get_hook, Array($key, $value));
         } else if (method_exists($this, "config_get_{$key}")) {
@@ -403,7 +406,8 @@ class Net_LDAP3
      *  @param string  $key        Configuration key
      *  @param mixed   $value      Configuration value
      */
-    public function config_set($key, $value) {
+    public function config_set($key, $value)
+    {
         if (!empty($this->_config_set_hook)) {
             return call_user_func(
                     $this->_config_set_hook,
@@ -785,7 +789,8 @@ class Net_LDAP3
         return $this->result;
     }
 
-    public function login($username, $password, $domain = null) {
+    public function login($username, $password, $domain = null)
+    {
         $this->_debug("Net_LDAP3::login(\$username = '" . $username . "', \$password = '****', \$domain = '" . $domain . "')");
         $_bind_dn = $this->config_get('service_bind_dn');
         $_bind_pw = $this->config_get('service_bind_pw');
@@ -1429,7 +1434,8 @@ class Net_LDAP3
         return $result;
     }
 
-    public static function scopeint2str($scope) {
+    public static function scopeint2str($scope)
+    {
         switch ($scope) {
             case 2:
                 return 'sub';
@@ -1473,18 +1479,21 @@ class Net_LDAP3
         return $function;
     }
 
-    private function config_set_config_get_hook($callback) {
+    private function config_set_config_get_hook($callback)
+    {
         $this->_config_get_hook = $callback;
     }
 
-    private function config_set_config_set_hook($callback) {
+    private function config_set_config_set_hook($callback)
+    {
         $this->_config_set_hook = $callback;
     }
 
     /**
      * Sets the debug level both for this class and the ldap connection.
      */
-    private function config_set_debug($value) {
+    private function config_set_debug($value)
+    {
         if ($value === FALSE) {
             $this->config['debug'] = FALSE;
         } else {
@@ -1499,11 +1508,13 @@ class Net_LDAP3
     /**
      *  Sets a log hook that is called with every log message in this module.
      */
-    private function config_set_log_hook($callback) {
+    private function config_set_log_hook($callback)
+    {
         $this->_log_hook = $callback;
     }
 
-    private function config_set_return_attributes($attribute_names = Array('entrydn')) {
+    private function config_set_return_attributes($attribute_names = Array('entrydn'))
+    {
         $this->_debug("setting return attributes: " . var_export($attribute_names, TRUE));
         $this->return_attributes = (Array)($attribute_names);
     }
@@ -1511,7 +1522,8 @@ class Net_LDAP3
     /**
      * Find a matching VLV
      */
-    private function find_vlv($base_dn, $filter, $scope, $sort_attrs = NULL) {
+    private function find_vlv($base_dn, $filter, $scope, $sort_attrs = NULL)
+    {
         if (array_key_exists('vlv', $this->config) && $this->config['vlv'] === FALSE) {
             return FALSE;
         }
@@ -1567,7 +1579,8 @@ class Net_LDAP3
         Return VLV indexes and searches including necessary configuration
         details.
     */
-    private function find_vlv_indexes_and_searches($refresh = FALSE) {
+    private function find_vlv_indexes_and_searches($refresh = FALSE)
+    {
         if (!empty($this->config['vlv'])) {
             if ($this->config['vlv'] === FALSE) {
                 return Array();
@@ -1969,39 +1982,48 @@ class Net_LDAP3
         return $result['']['supportedcontrol'];
     }
 
-    private function _alert() {
+    private function _alert()
+    {
         $this->__log(LOG_ALERT, func_get_args());
     }
 
-    private function _critical() {
+    private function _critical()
+    {
         $this->__log(LOG_CRIT, func_get_args());
     }
 
-    private function _debug() {
+    private function _debug()
+    {
         $this->__log(LOG_DEBUG, func_get_args());
     }
 
-    private function _emergency() {
+    private function _emergency()
+    {
         $this->__log(LOG_EMERG, func_get_args());
     }
 
-    private function _error() {
+    private function _error()
+    {
         $this->__log(LOG_ERR, func_get_args());
     }
 
-    private function _info() {
+    private function _info()
+    {
         $this->__log(LOG_INFO, func_get_args());
     }
 
-    private function _notice() {
+    private function _notice()
+    {
         $this->__log(LOG_NOTICE, func_get_args());
     }
 
-    private function _warning() {
+    private function _warning()
+    {
         $this->__log(LOG_WARNING, func_get_args());
     }
 
-    private function _fuzzy_search_prefix() {
+    private function _fuzzy_search_prefix()
+    {
         switch ($this->config_get("fuzzy_search", 2)) {
             case 2:
                 return "*";
@@ -2014,7 +2036,8 @@ class Net_LDAP3
         }
     }
 
-    private function _fuzzy_search_suffix() {
+    private function _fuzzy_search_suffix()
+    {
         switch ($this->config_get("fuzzy_search", 2)) {
             case 2:
                 return "*";
@@ -2028,7 +2051,8 @@ class Net_LDAP3
         }
     }
 
-    private function _vlv_search($sort, $search) {
+    private function _vlv_search($sort, $search)
+    {
         if (!empty($this->additional_filter)) {
             $this->_debug("Not setting a VLV search filter because we already have a filter");
             return NULL;
@@ -2112,7 +2136,8 @@ class Net_LDAP3
      *  @param  string  $filter     Filter
      *  @param  string  $scope      Scope
      */
-    private function __result_current($base_dn, $filter, $scope) {
+    private function __result_current($base_dn, $filter, $scope)
+    {
         if (empty($this->result)) {
             return FALSE;
         }


commit db825aa3c83341c571a8e4b9c23bf0e28495fd23
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Oct 22 14:30:29 2012 +0200

    Make search filter strings simpler if possible

diff --git a/lib/ext/Net/LDAP3.php b/lib/ext/Net/LDAP3.php
index 432d68d..7c51b8a 100644
--- a/lib/ext/Net/LDAP3.php
+++ b/lib/ext/Net/LDAP3.php
@@ -666,13 +666,13 @@ class Net_LDAP3
             return FALSE;
         }
 
-        $filter = "(&";
+        $filter = count($attributes) ? "(&" : "";
 
         foreach ($attributes as $key => $value) {
             $filter .= "(" . $key . "=" . $value . ")";
         }
 
-        $filter .= ")";
+        $filter .= count($attributes) ? ")" : "";
 
         if (empty($base_dn)) {
             $base_dn = $this->config_get('root_dn');
@@ -1201,14 +1201,16 @@ class Net_LDAP3
         $this->_debug("Using function $function on scope $scope (\$ns_function is $ns_function)");
 
         if ($this->vlv_active) {
-            if (isset($this->additional_filter) && !empty($this->additional_filter)) {
+            if (!empty($this->additional_filter)) {
                 $filter = "(&" . $filter . $this->additional_filter . ")";
                 $this->_debug("C: (With VLV) Setting a filter (with additional filter) of " . $filter);
             } else {
                 $this->_debug("C: (With VLV) Setting a filter (without additional filter) of " . $filter);
             }
         } else {
-            $filter = "(&" . $filter . $this->additional_filter . ")";
+            if (!empty($this->additional_filter)) {
+                $filter = "(&" . $filter . $this->additional_filter . ")";
+            }
             $this->_debug("C: (Without VLV) Setting a filter of " . $filter);
         }
 


commit 1a8df4711b944e06971e230d017f657721bce14d
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Oct 22 14:19:22 2012 +0200

    Add default filters for lists

diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php
index a468a10..1428cfc 100644
--- a/lib/Auth/LDAP.php
+++ b/lib/Auth/LDAP.php
@@ -314,6 +314,10 @@ class LDAP extends Net_LDAP3 {
             $filter = $kolab_filter;
         }
 
+        if (!$filter) {
+            $filter = "(associateddomain=*)";
+        }
+
         return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
     }
 
@@ -324,6 +328,10 @@ class LDAP extends Net_LDAP3 {
         $base_dn = $this->_subject_base_dn('group');
         $filter  = $this->conf->get('group_filter');
 
+        if (!$filter) {
+            $filter = "(|(objectclass=groupofuniquenames)(objectclass=groupofurls))";
+        }
+
         return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
     }
 
@@ -335,7 +343,7 @@ class LDAP extends Net_LDAP3 {
         $filter  = $this->conf->get('resource_filter');
 
         if (!$filter) {
-            $filter = '(&(objectclass=*)(!(objectclass=organizationalunit)))';
+            $filter = "(&(objectclass=*)(!(objectclass=organizationalunit)))";
         }
 
         return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
@@ -362,6 +370,10 @@ class LDAP extends Net_LDAP3 {
         $base_dn = $this->_subject_base_dn('user');
         $filter  = $this->conf->get('user_filter');
 
+        if (empty($filter)) {
+            $filter  = "(objectclass=kolabinetorgperson)";
+        }
+
         return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
     }
 


commit aa6c7f595885d1a02970b0e32430060ef4ffcaa3
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Oct 22 14:09:50 2012 +0200

    Small style improvement

diff --git a/lib/client/kolab_client_task_user.php b/lib/client/kolab_client_task_user.php
index 2ab1647..d67e7e3 100644
--- a/lib/client/kolab_client_task_user.php
+++ b/lib/client/kolab_client_task_user.php
@@ -316,6 +316,7 @@ class kolab_client_task_user extends kolab_client_task
                     'content' => $this->translate('password.generate'),
                     'href'    => '#',
                     'onclick' => "kadm.generate_password('userpassword')",
+                    'class'   => 'nowrap',
                 ));
             }
         }
diff --git a/public_html/skins/default/style.css b/public_html/skins/default/style.css
index 87a0a2e..4b9c6ec 100644
--- a/public_html/skins/default/style.css
+++ b/public_html/skins/default/style.css
@@ -444,6 +444,10 @@ div.vsplitter {
 
 /**** Common classes ****/
 
+.nowrap {
+  white-space: nowrap;
+}
+
 .clear {
   clear: both;
 }


commit 4f9a2f2ad1ac5a4cb7138700bfb01c9726fe4961
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Oct 22 14:02:30 2012 +0200

    Fix return value of select_options_preferredlanguage()

diff --git a/lib/api/kolab_api_service_form_value.php b/lib/api/kolab_api_service_form_value.php
index b6c68b2..9e213f6 100644
--- a/lib/api/kolab_api_service_form_value.php
+++ b/lib/api/kolab_api_service_form_value.php
@@ -862,10 +862,9 @@ class kolab_api_service_form_value extends kolab_api_service
             $default = $postdata['preferredlanguage'];
         }
 
-        return array(
-            'list'    => $options,
-            'default' => $default,
-        );
+        $options['default'] = $default;
+
+        return $options;
     }
 
     private function validate_alias($value)


commit 00411510c616107d595f1be52863183e04c30b51
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Oct 22 13:56:27 2012 +0200

    Fix PHP warning when calling session_destroy() and there's no session

diff --git a/lib/kolab_api_controller.php b/lib/kolab_api_controller.php
index de1c346..0e45a30 100644
--- a/lib/kolab_api_controller.php
+++ b/lib/kolab_api_controller.php
@@ -398,7 +398,7 @@ class kolab_api_controller
      */
     private function quit()
     {
-        session_destroy();
+        @session_destroy();
         return true;
     }
 


commit 3519d532612f838bc94a8e7978ca2b487e19d390
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Oct 22 13:55:41 2012 +0200

    Improve object type id detection by scoring higher type with more attributes (Bug #1089)

diff --git a/lib/kolab_api_service.php b/lib/kolab_api_service.php
index e2ba761..e90aa87 100644
--- a/lib/kolab_api_service.php
+++ b/lib/kolab_api_service.php
@@ -122,9 +122,17 @@ abstract class kolab_api_service
             return null;
         }
 
-        $object_class = array_map('strtolower', $object_class);
         $object_types = $this->object_types($object_name);
+
+        if (count($object_types) == 1) {
+            return key($object_types);
+        }
+
+        $object_class = array_map('strtolower', $object_class);
+        $object_keys  = array_keys($attributes);
+        $keys_count   = count($object_keys);
         $type_score   = -1;
+        $keys_score   = 0;
         $type_id      = null;
 
         //console("Data objectClasses: " . implode(", ", $object_class));
@@ -142,17 +150,30 @@ abstract class kolab_api_service
             $_object_class = array_diff($object_class, $ref_class);
             $_ref_class    = array_diff($ref_class, $object_class);
 
+            // Object classes score
             $differences   = count($_object_class) + count($_ref_class);
             $commonalities = count($object_class) - $differences;
             $elem_score    = $differences > 0 ? ($commonalities / $differences) : $commonalities;
 
+            // Attributes score
+            if ($keys_count) {
+                $ref_keys = array_unique(array_merge(
+                    array_keys((array) $elem['attributes']['auto_form_fields']),
+                    array_keys((array) $elem['attributes']['form_fields']),
+                    array_keys($elem['attributes']['fields'])
+                ));
+                $elem_keys_score = $keys_count - count(array_diff($object_keys, $ref_keys));
+            }
+
             //console("\$object_class not in \$ref_class (" . $elem['key'] . "): " . implode(", ", $_object_class));
             //console("\$ref_class not in \$object_class (" . $elem['key'] . "): " . implode(", ", $_ref_class));
-            //console("Score for $object_name type " . $elem['name'] . ": " . $elem_score . "(" . $commonalities . "/" . $differences . ")");
+            //console("Score for $object_name type " . $elem['name'] . ": " . $elem_score . "(" . $commonalities . "/" . $differences . ") " . $elem_keys_score);
 
-            if ($elem_score > $type_score) {
+            // Compare last and current element score
+            if ($elem_score > $type_score || ($elem_score == $type_score && $elem_keys_score > $keys_score)) {
                 $type_id    = $idx;
                 $type_score = $elem_score;
+                $keys_score = $elem_keys_score;
             }
 
             // On the likely chance that the object is a resource (types of which likely have the same
@@ -297,7 +318,8 @@ abstract class kolab_api_service
         return $result;
     }
 
-    protected function parse_list_attributes($post) {
+    protected function parse_list_attributes($post)
+    {
         $attributes = Array();
         // Attributes to return
         if (!empty($post['attributes']) && is_array($post['attributes'])) {
@@ -314,7 +336,8 @@ abstract class kolab_api_service
         return $attributes;
     }
 
-    protected function parse_list_params($post) {
+    protected function parse_list_params($post)
+    {
         $params = Array();
         if (!empty($post['sort_by'])) {
             if (is_array($post['sort_by'])) {
@@ -347,7 +370,8 @@ abstract class kolab_api_service
         return $params;
     }
 
-    protected function parse_list_search($post) {
+    protected function parse_list_search($post)
+    {
         $search = Array();
         // Search parameters
         if (!empty($post['search']) && is_array($post['search'])) {


commit 1a774764c88ab5e2ef7d5797fe9d12eac2d7b247
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Oct 22 11:38:00 2012 +0200

    Fix warning on role.effective_rights

diff --git a/lib/api/kolab_api_service_domain.php b/lib/api/kolab_api_service_domain.php
index 89064e0..636c230 100644
--- a/lib/api/kolab_api_service_domain.php
+++ b/lib/api/kolab_api_service_domain.php
@@ -152,15 +152,13 @@ class kolab_api_service_domain extends kolab_api_service
 
             $unique_attr = $conf->get('ldap', 'unique_attribute');
 
-            $domain = $auth->domain_find_by_attribute(
-                    array($unique_attr => $entry_dn)
-                );
+            $domain = $auth->domain_find_by_attribute(array($unique_attr => $entry_dn));
 
             if (!empty($domain)) {
                 $entry_dn = key($domain);
             }
-
-        } else {
+        }
+        else {
             $conf = Conf::get_instance();
             $entry_dn = $conf->get('ldap', 'domain_base_dn');
         }
diff --git a/lib/api/kolab_api_service_role.php b/lib/api/kolab_api_service_role.php
index 0deecf9..18ebf19 100644
--- a/lib/api/kolab_api_service_role.php
+++ b/lib/api/kolab_api_service_role.php
@@ -146,7 +146,11 @@ class kolab_api_service_role extends kolab_api_service
                 $unique_attr = 'nsuniqueid';
             }
 
-            $role_dn = $auth->role_find_by_attribute(Array($unique_attr => $getdata['role']));
+            $role = $auth->role_find_by_attribute(Array($unique_attr => $getdata['role']));
+
+            if (is_array($role) && count($role) == 1) {
+                $role_dn = key($role);
+            }
         }
 
         $effective_rights = $auth->list_rights(empty($role_dn) ? 'role' : $role_dn);


commit 3aab8233d5cdbd30ec27525a7d0ebf34642f327e
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Oct 22 11:28:36 2012 +0200

    Remove redundant code

diff --git a/lib/kolab_api_service.php b/lib/kolab_api_service.php
index c45f811..e2ba761 100644
--- a/lib/kolab_api_service.php
+++ b/lib/kolab_api_service.php
@@ -194,11 +194,6 @@ abstract class kolab_api_service
             }
         }
 
-        $unique_attr = $conf->get('unique_attribute');
-        if (!$unique_attr) {
-            $unique_attr = 'nsuniqueid';
-        }
-
         $sql_result   = $this->db->query("SELECT * FROM {$object_name}_types ORDER BY name");
         $object_types = array();
 


commit 029c1c512d25be056c194b621b497d3705a23e74
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Oct 22 11:12:22 2012 +0200

    More code unification and cleanup

diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php
index 4ced2d8..a468a10 100644
--- a/lib/Auth/LDAP.php
+++ b/lib/Auth/LDAP.php
@@ -34,18 +34,19 @@ class LDAP extends Net_LDAP3 {
     /**
      * Class constructor
      */
-    public function __construct($domain = null) {
+    public function __construct($domain = null)
+    {
         parent::__construct();
 
         $this->conf = Conf::get_instance();
 
         // Causes nesting levels to be too deep...?
-        //$this->config_set('config_get_hook', Array($this, "_config_get"));
+        //$this->config_set('config_get_hook', array($this, "_config_get"));
 
-        $this->config_set("debug", TRUE);
-        $this->config_set("log_hook", Array($this, "_log"));
+        $this->config_set("debug", true);
+        $this->config_set("log_hook", array($this, "_log"));
 
-        //$this->config_set("vlv", FALSE);
+        //$this->config_set("vlv", false);
         $this->config_set("config_root_dn", "cn=config");
 
         $this->config_set("service_bind_dn", $this->conf->get("service_bind_dn"));
@@ -110,7 +111,8 @@ class LDAP extends Net_LDAP3 {
      *
      * @return bool|string User ID or False on failure
      */
-    public function authenticate($username, $password, $domain = NULL) {
+    public function authenticate($username, $password, $domain = NULL)
+    {
         Log::debug("Auth::LDAP: authentication request for $username against domain $domain");
 
         if (!$this->connect()) {
@@ -124,7 +126,7 @@ class LDAP extends Net_LDAP3 {
         $result = $this->login($username, $password, $domain);
 
         if (!$result) {
-            return FALSE;
+            return false;
         }
 
         $_SESSION['user']->user_bind_dn = $result;
@@ -133,7 +135,8 @@ class LDAP extends Net_LDAP3 {
         return $result;
     }
 
-    public function domain_add($domain, $parent_domain = false, $prepopulate = true) {
+    public function domain_add($domain, $parent_domain = false, $prepopulate = true)
+    {
         // Apply some routines for access control to this function here.
         if (!empty($parent_domain)) {
             if ($this->domain_info($parent_domain)->count() < 1) {
@@ -147,7 +150,8 @@ class LDAP extends Net_LDAP3 {
         }
     }
 
-    public function domain_edit($domain, $attributes, $typeid = null) {
+    public function domain_edit($domain, $attributes, $typeid = null)
+    {
         $domain = $this->domain_info($domain, array_keys($attributes));
 
         if (empty($domain)) {
@@ -160,20 +164,24 @@ class LDAP extends Net_LDAP3 {
         return $this->modify_entry($domain_dn, $domain[$domain_dn], $attributes);
     }
 
-    public function domain_delete($domain) {
+    public function domain_delete($domain)
+    {
         return $this->entry_delete($domain);
     }
 
-    public function domain_find_by_attribute($attribute) {
+    public function domain_find_by_attribute($attribute)
+    {
         $base_dn = $this->conf->get('ldap', 'domain_base_dn');
 
         return $this->entry_find_by_attribute($attribute, $base_dn);
     }
 
-    public function domain_info($domain, $attributes = array('*')) {
-        $domain_dn = $this->entry_dn($domain);
+    public function domain_info($domain, $attributes = array('*'))
+    {
+        $this->_log(LOG_DEBUG, "Auth::LDAP::domain_info() for domain " . var_export($domain, true));
+        $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
 
-        $this->_log(LOG_DEBUG, "Auth::LDAP::domain_info() \$domain_dn: " . $domain_dn . " and attributes: " . var_export($attributes, TRUE));
+        $domain_dn = $this->entry_dn($domain);
 
         if (!$domain_dn) {
             $domain_base_dn        = $this->conf->get('ldap', 'domain_base_dn');
@@ -192,7 +200,7 @@ class LDAP extends Net_LDAP3 {
             return false;
         }
 
-        $this->_log(LOG_DEBUG, "Auth::LDAP::domain_info() result: " . var_export($result, TRUE));
+        $this->_log(LOG_DEBUG, "Auth::LDAP::domain_info() result: " . var_export($result, true));
 
         return $result;
     }
@@ -201,7 +209,8 @@ class LDAP extends Net_LDAP3 {
      * Proxy to parent function in order to enable us to insert our
      * configuration.
      */
-    public function effective_rights($subject) {
+    public function effective_rights($subject)
+    {
         // Ensure we are bound with the user's credentials
         $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
 
@@ -230,7 +239,8 @@ class LDAP extends Net_LDAP3 {
 
     }
 
-    public function group_add($attrs, $typeid = null) {
+    public function group_add($attrs, $typeid = null)
+    {
         $base_dn = $this->entry_base_dn('group', $typeid);
 
         // TODO: The rdn is configurable as well.
@@ -240,11 +250,13 @@ class LDAP extends Net_LDAP3 {
         return $this->entry_add($dn, $attrs);
     }
 
-    public function group_delete($group) {
+    public function group_delete($group)
+    {
         return $this->entry_delete($group);
     }
 
-    public function group_edit($group, $attributes, $typeid = null) {
+    public function group_edit($group, $attributes, $typeid = null)
+    {
         $group = $this->group_info($group, array_keys($attributes));
 
         if (empty($group)) {
@@ -257,35 +269,29 @@ class LDAP extends Net_LDAP3 {
         return $this->modify_entry($group_dn, $group[$group_dn], $attributes);
     }
 
-    public function group_find_by_attribute($attribute) {
+    public function group_find_by_attribute($attribute)
+    {
         return $this->entry_find_by_attribute($attribute);
     }
 
-    public function group_info($group, $attributes = array('*')) {
-        $this->_log(LOG_DEBUG, "Auth::LDAP::group_info() for group " . var_export($group, TRUE));
+    public function group_info($group, $attributes = array('*'))
+    {
+        $this->_log(LOG_DEBUG, "Auth::LDAP::group_info() for group " . var_export($group, true));
         $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
 
-        $unique_attr = $this->unique_attribute();
-        if (!in_array($unique_attr, $attributes)) {
-            $attributes[] = $unique_attr;
-        }
-
-        $this->config_set('return_attributes', $attributes);
-
         $group_dn = $this->entry_dn($group);
 
-        $this->_log(LOG_DEBUG, "group_info() group_dn " . var_export($group_dn, TRUE));
-
         if (!$group_dn) {
             return false;
         }
 
-        $group_info = $this->_read($group_dn, $attributes);
-        $this->_log(LOG_DEBUG, "Auth::LDAP::group_info() result: " . var_export($group_info, TRUE));
-        return $group_info;
+        $this->read_prepare($attributes);
+
+        return $this->_read($group_dn, $attributes);
     }
 
-    public function group_members_list($group, $recurse = true) {
+    public function group_members_list($group, $recurse = true)
+    {
         $group_dn = $this->entry_dn($group);
 
         if (!$group_dn) {
@@ -295,8 +301,9 @@ class LDAP extends Net_LDAP3 {
         return $this->_list_group_members($group_dn, null, $recurse);
     }
 
-    public function list_domains($attributes = array(), $search = array(), $params = array()) {
-        $this->list_prepare($params, $attributes);
+    public function list_domains($attributes = array(), $search = array(), $params = array())
+    {
+        $this->_log(LOG_DEBUG, "Auth::LDAP::list_domains(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true));
 
         $section = $this->conf->get('kolab', 'auth_mechanism');
         $base_dn = $this->conf->get($section, 'domain_base_dn');
@@ -307,31 +314,22 @@ class LDAP extends Net_LDAP3 {
             $filter = $kolab_filter;
         }
 
-        $result  = $this->search_entries($base_dn, $filter, 'sub', NULL, $search);
-        $entries = $this->sort_and_slice($result, $params);
-
-        return Array(
-                'list' => $entries,
-                'count' => $result->count()
-            );
+        return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
     }
 
-    public function list_groups($attributes = array(), $search = array(), $params = array()) {
-        $this->list_prepare($params, $attributes);
+    public function list_groups($attributes = array(), $search = array(), $params = array())
+    {
+        $this->_log(LOG_DEBUG, "Auth::LDAP::list_groups(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true));
 
         $base_dn = $this->_subject_base_dn('group');
         $filter  = $this->conf->get('group_filter');
-        $result  = $this->search_entries($base_dn, $filter, 'sub', NULL, $search);
-        $entries = $this->sort_and_slice($result, $params);
 
-        return Array(
-                'list' => $entries,
-                'count' => $result->count()
-            );
+        return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
     }
 
-    public function list_resources($attributes = array(), $search = array(), $params = array()) {
-        $this->list_prepare($params, $attributes);
+    public function list_resources($attributes = array(), $search = array(), $params = array())
+    {
+        $this->_log(LOG_DEBUG, "Auth::LDAP::list_resources(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true));
 
         $base_dn = $this->_subject_base_dn('resource');
         $filter  = $this->conf->get('resource_filter');
@@ -340,17 +338,12 @@ class LDAP extends Net_LDAP3 {
             $filter = '(&(objectclass=*)(!(objectclass=organizationalunit)))';
         }
 
-        $result  = $this->search_entries($base_dn, $filter, 'sub', NULL, $search);
-        $entries = $this->sort_and_slice($result, $params);
-
-        return Array(
-                'list' => $entries,
-                'count' => $result->count()
-            );
+        return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
     }
 
-    public function list_roles($attributes = array(), $search = array(), $params = array()) {
-        $this->list_prepare($params, $attributes);
+    public function list_roles($attributes = array(), $search = array(), $params = array())
+    {
+        $this->_log(LOG_DEBUG, "Auth::LDAP::list_roles(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true));
 
         $base_dn = $this->_subject_base_dn('role');
         $filter  = $this->conf->get('role_filter');
@@ -359,72 +352,21 @@ class LDAP extends Net_LDAP3 {
             $filter  = "(&(objectclass=ldapsubentry)(objectclass=nsroledefinition))";
         }
 
-        $result  = $this->search_entries($base_dn, $filter, 'sub', NULL, $search);
-        $entries = $this->sort_and_slice($result, $params);
-
-        return Array(
-                'list' => $entries,
-                'count' => $result->count()
-            );
+        return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
     }
 
-    public function list_users($attributes = array(), $search = array(), $params = array()) {
-        $this->_log(LOG_DEBUG, "Auth::LDAP::list_users(" . var_export($attributes, TRUE) . ", " . var_export($search, TRUE) . ", " . var_export($params, TRUE));
-
-        $this->list_prepare($params, $attributes);
+    public function list_users($attributes = array(), $search = array(), $params = array())
+    {
+        $this->_log(LOG_DEBUG, "Auth::LDAP::list_users(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true));
 
-        $base_dn = $this->_subject_base_dn("user");
+        $base_dn = $this->_subject_base_dn('user');
         $filter  = $this->conf->get('user_filter');
-        $result  = $this->search_entries($base_dn, $filter, 'sub', NULL, $search);
-        $entries = $this->sort_and_slice($result, $params);
 
-        return Array(
-                'list' => $entries,
-                'count' => $result->count()
-            );
+        return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
     }
 
-    /**
-     * Prepare environment before search_entries() call
-     */
-    protected function list_prepare($params, $attributes)
+    public function resource_add($attrs, $typeid = null)
     {
-        $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
-
-        if (!empty($params['sort_by'])) {
-            if (is_array($params['sort_by'])) {
-                foreach ($params['sort_by'] as $attrib) {
-                    if (!in_array($attrib, $attributes)) {
-                        $attributes[] = $attrib;
-                    }
-                }
-            } else {
-                if (!in_array($params['sort_by'], $attributes)) {
-                    $attributes[] = $params['sort_by'];
-                }
-            }
-        }
-
-        if (!empty($params['page_size'])) {
-            $this->config_set('page_size', $params['page_size']);
-        } else {
-            $this->config_get('page_size', 15);
-        }
-
-        if (!empty($params['page'])) {
-            $this->config_set('list_page', $params['page']);
-        } else {
-            $this->config_set('list_page', 1);
-        }
-
-        if (empty($attributes) || !is_array($attributes)) {
-            $attributes = array('*');
-        }
-
-        $this->config_set('return_attributes', $attributes);
-    }
-
-    public function resource_add($attrs, $typeid = null) {
         $base_dn = $this->entry_base_dn('resource', $typeid);
 
         // TODO: The rdn is configurable as well.
@@ -434,11 +376,13 @@ class LDAP extends Net_LDAP3 {
         return $this->entry_add($dn, $attrs);
     }
 
-    public function resource_delete($resource) {
+    public function resource_delete($resource)
+    {
         return $this->entry_delete($resource);
     }
 
-    public function resource_edit($resource, $attributes, $typeid = null) {
+    public function resource_edit($resource, $attributes, $typeid = null)
+    {
         $resource = $this->resource_info($resource, array_keys($attributes));
 
         if (empty($resource)) {
@@ -451,21 +395,29 @@ class LDAP extends Net_LDAP3 {
         return $this->modify_entry($resource_dn, $resource[$resource_dn], $attributes);
     }
 
-    public function resource_find_by_attribute($attribute) {
+    public function resource_find_by_attribute($attribute)
+    {
         return $this->entry_find_by_attribute($attribute);
     }
 
-    public function resource_info($resource, $attributes = array('*')) {
+    public function resource_info($resource, $attributes = array('*'))
+    {
+        $this->_log(LOG_DEBUG, "Auth::LDAP::resource_info() for resource " . var_export($resource, true));
+        $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
+
         $resource_dn = $this->entry_dn($resource);
 
         if (!$resource_dn) {
             return false;
         }
 
+        $this->read_prepare($attributes);
+
         return $this->_read($resource_dn, $attributes);
     }
 
-    public function resource_members_list($resource, $recurse = true) {
+    public function resource_members_list($resource, $recurse = true)
+    {
         $resource_dn = $this->entry_dn($resource);
 
         if (!$resource_dn) {
@@ -475,7 +427,8 @@ class LDAP extends Net_LDAP3 {
         return $this->_list_resource_members($resource_dn, null, $recurse);
     }
 
-    public function role_add($attrs) {
+    public function role_add($attrs)
+    {
         $base_dn = $this->entry_base_dn('role', $typeid);
 
         // TODO: The rdn is configurable as well.
@@ -485,7 +438,8 @@ class LDAP extends Net_LDAP3 {
         return $this->entry_add($dn, $attrs);
     }
 
-    public function role_edit($role, $attributes, $typeid = null) {
+    public function role_edit($role, $attributes, $typeid = null)
+    {
         $role = $this->role_info($role, array_keys($attributes));
 
         if (empty($role)) {
@@ -498,50 +452,45 @@ class LDAP extends Net_LDAP3 {
         return $this->modify_entry($role_dn, $role[$role_dn], $attributes);
     }
 
-    public function role_delete($role) {
+    public function role_delete($role)
+    {
         return $this->entry_delete($role, array('objectclass' => 'ldapsubentry'));
     }
 
-    public function role_find_by_attribute($attribute) {
-        $this->_log(LOG_DEBUG, "Finding role by attribute: " . var_export($attribute, TRUE));
-
+    public function role_find_by_attribute($attribute)
+    {
         $attribute['objectclass'] = 'ldapsubentry';
-        $result = $this->entry_find_by_attribute($attribute);
-
-        if (is_array($result) && count($result) == 0) {
-            return key($result);
-        }
-
-        return false;
+        return $this->entry_find_by_attribute($attribute);
     }
 
-    public function role_info($role, $attributes = array('*')) {
+    public function role_info($role, $attributes = array('*'))
+    {
+        $this->_log(LOG_DEBUG, "Auth::LDAP::role_info() for role " . var_export($role, true));
+        $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
+
         $role_dn = $this->entry_dn($role, array('objectclass' => 'ldapsubentry'));
 
         if (!$role_dn) {
             return false;
         }
 
-        $unique_attr = $this->unique_attribute();
-        if (!in_array($unique_attr, $attributes)) {
-            $attributes[] = $unique_attr;
-        }
+        $this->read_prepare($attributes);
 
-        $result = $this->_search($role_dn, '(objectclass=ldapsubentry)', $attributes);
-        $this->_log(LOG_DEBUG, "Auth::LDAP::role_info() result: " . var_export($result, TRUE));
-        return $result->entries(TRUE);
+        return $this->_read($role_dn, $attributes);
     }
 
-    public function search($base_dn, $filter = '(objectclass=*)', $scope = 'sub', $sort = NULL, $search = Array()) {
+    public function search($base_dn, $filter = '(objectclass=*)', $scope = 'sub', $sort = NULL, $search = array())
+    {
         if (isset($_SESSION['user']->user_bind_dn) && !empty($_SESSION['user']->user_bind_dn)) {
             $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
         }
 
-        $this->_log(LOG_DEBUG, "Relaying search to parent:" . var_export(func_get_args(), TRUE));
+        $this->_log(LOG_DEBUG, "Relaying search to parent:" . var_export(func_get_args(), true));
         return parent::search($base_dn, $filter, $scope, $sort, $search);
     }
 
-    public function user_add($attrs, $typeid = null) {
+    public function user_add($attrs, $typeid = null)
+    {
         $base_dn = $this->entry_base_dn('user', $typeid);
 
         if (!empty($attrs['ou'])) {
@@ -555,7 +504,8 @@ class LDAP extends Net_LDAP3 {
         return $this->entry_add($dn, $attrs);
     }
 
-    public function user_edit($user, $attributes, $typeid = null) {
+    public function user_edit($user, $attributes, $typeid = null)
+    {
         $user = $this->user_info($user, array_keys($attributes));
 
         if (empty($user)) {
@@ -568,39 +518,101 @@ class LDAP extends Net_LDAP3 {
         return $this->modify_entry($user_dn, $user[$user_dn], $attributes);
     }
 
-    public function user_delete($user) {
+    public function user_delete($user)
+    {
         return $this->entry_delete($user);
     }
 
-    public function user_info($user, $attributes = array('*')) {
-        $this->_log(LOG_DEBUG, "Auth::LDAP::user_info() for user " . var_export($user, TRUE));
+    public function user_info($user, $attributes = array('*'))
+    {
+        $this->_log(LOG_DEBUG, "Auth::LDAP::user_info() for user " . var_export($user, true));
         $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
 
-        $unique_attr = $this->unique_attribute();
-        if (!in_array($unique_attr, $attributes)) {
-            $attributes[] = $unique_attr;
-        }
-
-        $this->config_set('return_attributes', $attributes);
-
         $user_dn = $this->entry_dn($user);
 
-        $this->_log(LOG_DEBUG, "user_info() user_dn " . var_export($user_dn, TRUE));
         if (!$user_dn) {
             return false;
         }
 
+        $this->read_prepare($attributes);
+
         return $this->_read($user_dn, $attributes);
     }
 
-    public function user_find_by_attribute($attribute) {
+    public function user_find_by_attribute($attribute)
+    {
         return $this->entry_find_by_attribute($attribute);
     }
 
     /**
+     * Wrapper for search_entries()
+     */
+    protected function _list($base_dn, $filter, $scope, $attributes, $search, $params)
+    {
+        $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
+
+        if (!empty($params['sort_by'])) {
+            if (is_array($params['sort_by'])) {
+                foreach ($params['sort_by'] as $attrib) {
+                    if (!in_array($attrib, $attributes)) {
+                        $attributes[] = $attrib;
+                    }
+                }
+            } else {
+                if (!in_array($params['sort_by'], $attributes)) {
+                    $attributes[] = $params['sort_by'];
+                }
+            }
+        }
+
+        if (!empty($params['page_size'])) {
+            $this->config_set('page_size', $params['page_size']);
+        } else {
+            $this->config_get('page_size', 15);
+        }
+
+        if (!empty($params['page'])) {
+            $this->config_set('list_page', $params['page']);
+        } else {
+            $this->config_set('list_page', 1);
+        }
+
+        if (empty($attributes) || !is_array($attributes)) {
+            $attributes = array('*');
+        }
+
+        $this->config_set('return_attributes', $attributes);
+
+        $result  = $this->search_entries($base_dn, $filter, $scope, null, $search);
+        $entries = $this->sort_and_slice($result, $params);
+
+        return array(
+            'list' => $entries,
+            'count' => $result->count()
+        );
+    }
+
+    /**
+     * Prepare environment before _read() call
+     */
+    protected function read_prepare(&$attributes)
+    {
+        // always return unique attribute
+        $unique_attr = $this->conf->get('unique_attribute');
+        if (empty($unique_attr)) {
+            $unique_attr = 'nsuniqueid';
+        }
+
+        if (!in_array($unique_attr, $attributes)) {
+            $attributes[] = $unique_attr;
+        }
+    }
+
+    /**
      * delete_entry() wrapper with binding and DN resolving
      */
-    protected function entry_delete($entry, $attributes = array()) {
+    protected function entry_delete($entry, $attributes = array())
+    {
         $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
 
         $entry_dn = $this->entry_dn($entry, $attributes);
@@ -615,7 +627,8 @@ class LDAP extends Net_LDAP3 {
     /**
      * add_entry() wrapper with binding
      */
-    protected function entry_add($entry_dn, $attrs) {
+    protected function entry_add($entry_dn, $attrs)
+    {
         $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
 
         return $this->add_entry($entry_dn, $attrs);
@@ -641,7 +654,8 @@ class LDAP extends Net_LDAP3 {
         return $base_dn;
     }
 
-    public function _config_get($key, $default = NULL) {
+    public function _config_get($key, $default = NULL)
+    {
         $key_parts = explode("_", $key);
         $this->_log(LOG_DEBUG, var_export($key_parts));
 
@@ -662,7 +676,8 @@ class LDAP extends Net_LDAP3 {
 
     }
 
-    public function _log($level, $msg) {
+    public function _log($level, $msg)
+    {
         if (strstr($_SERVER["REQUEST_URI"], "/api/")) {
             $str = "(api) ";
         } else {
@@ -696,7 +711,8 @@ class LDAP extends Net_LDAP3 {
         }
     }
 
-    private function _subject_base_dn($subject) {
+    private function _subject_base_dn($subject)
+    {
         // Attempt to get a configured base_dn
         $base_dn = $this->conf->get($this->domain, "base_dn");
 
@@ -723,7 +739,8 @@ class LDAP extends Net_LDAP3 {
         return $base_dn;
     }
 
-    private function legacy_rights($subject) {
+    private function legacy_rights($subject)
+    {
         $subject_dn    = $this->entry_dn($subject);
         $user_is_admin = false;
         $user_is_self  = false;
@@ -775,8 +792,9 @@ class LDAP extends Net_LDAP3 {
         return $rights;
     }
 
-    private function sort_and_slice(&$result, &$params) {
-        $entries = $result->entries(TRUE);
+    private function sort_and_slice(&$result, &$params)
+    {
+        $entries = $result->entries(true);
 
         if ($this->vlv_active) {
             return $entries;
@@ -790,14 +808,14 @@ class LDAP extends Net_LDAP3 {
 
             if (array_key_exists('page_size', $params) && array_key_exists('page', $params)) {
                 if ($result->count() > $params['page_size']) {
-                    $entries = array_slice($entries, (($params['page'] - 1) * $params['page_size']), $params['page_size'], TRUE);
+                    $entries = array_slice($entries, (($params['page'] - 1) * $params['page_size']), $params['page_size'], true);
                 }
 
             }
 
             if (array_key_exists('sort_order', $params) && !empty($params['sort_order'])) {
                 if ($params['sort_order'] == "DESC") {
-                    $entries = array_reverse($entries, TRUE);
+                    $entries = array_reverse($entries, true);
                 }
             }
         }
@@ -827,11 +845,6 @@ class LDAP extends Net_LDAP3 {
         return strcmp(mb_strtoupper($str1), mb_strtoupper($str2));
     }
 
-    private function unique_attribute() {
-        $unique_attr = $this->conf->get("unique_attribute");
-        return empty($unique_attr) ? 'nsuniqueid' : $unique_attr;
-    }
-
     /**
      * Qualify a username.
      *
@@ -840,7 +853,8 @@ class LDAP extends Net_LDAP3 {
      * username is 'kanarip', the domain name is to be assumed the
      * management domain name.
      */
-    private function _qualify_id($username) {
+    private function _qualify_id($username)
+    {
         $username_parts = explode('@', $username);
         if (count($username_parts) == 1) {
             $domain_name = $this->conf->get('primary_domain');
@@ -856,7 +870,8 @@ class LDAP extends Net_LDAP3 {
      ************      Shortcut functions       ****************
      ***********************************************************/
 
-    private function _domain_add_alias($domain, $parent) {
+    private function _domain_add_alias($domain, $parent)
+    {
         $domain_base_dn = $this->conf->get('ldap', 'domain_base_dn');
         $domain_filter  = $this->conf->get('ldap', 'domain_filter');
 
@@ -875,7 +890,7 @@ class LDAP extends Net_LDAP3 {
             return false;
         }
 
-        $entries = $result->entries(TRUE);
+        $entries = $result->entries(true);
 
         $domain_dn    = key($entries);
         $domain_entry = $entries[$domain_dn];
@@ -891,7 +906,8 @@ class LDAP extends Net_LDAP3 {
         return $this->modify_entry($domain_dn, $_old_attr, $_new_attr);
     }
 
-    private function _domain_add_new($domain) {
+    private function _domain_add_new($domain)
+    {
         console("Auth::LDAP::_domain_add_new()", $domain);
 
         $auth = Auth::get_instance();
@@ -941,7 +957,7 @@ class LDAP extends Net_LDAP3 {
         $domain_filter = $this->conf->get('ldap', 'domain_filter');
         $domain_filter = '(&(' . $domain_name_attribute . '=' . $this->conf->get('kolab', 'primary_domain') . ')' . $domain_filter . ')';
         $results  = $this->_search($domain_base_dn, $domain_filter);
-        $entries = $results->entries(TRUE);
+        $entries = $results->entries(true);
         $domain_entry = array_shift($entries);
 
         // The root_dn for the parent domain is needed to find the ldbm
@@ -961,7 +977,7 @@ class LDAP extends Net_LDAP3 {
             $result = $this->_read("cn=userRoot,cn=ldbm database,cn=plugins,cn=config", array('nsslapd-directory'));
         }
 
-        $this->_log(LOG_DEBUG, "Primary domain ldbm database configuration entry: " . var_export($result, TRUE));
+        $this->_log(LOG_DEBUG, "Primary domain ldbm database configuration entry: " . var_export($result, true));
 
         $result = $result[key($result)];
 
@@ -1000,7 +1016,7 @@ class LDAP extends Net_LDAP3 {
         $domain_filter = $this->conf->get('ldap', 'domain_filter');
         $domain_filter = '(&(' . $domain_name_attribute . '=' . $this->conf->get('kolab', 'primary_domain') . ')' . $domain_filter . ')';
         $results  = $this->_search($domain_base_dn, $domain_filter);
-        $entries = $results->entries(TRUE);
+        $entries = $results->entries(true);
         $domain_entry = array_shift($entries);
 
         if (in_array('inetdomainbasedn', $domain_entry)) {
@@ -1014,7 +1030,7 @@ class LDAP extends Net_LDAP3 {
         $acis   = $result['aci'];
 
         foreach ($acis as $aci) {
-            if (stristr($aci, "SIE Group") === FALSE) {
+            if (stristr($aci, "SIE Group") === false) {
                 continue;
             }
             $_aci = $aci;
@@ -1169,7 +1185,7 @@ class LDAP extends Net_LDAP3 {
 
         $result = $this->_search($domain_base_dn, $domain_filter);
 
-        $entries = $result->entries(TRUE);
+        $entries = $result->entries(true);
         $entry_dn = key($entries);
         $entry_attrs = $entries[$entry_dn];
 
@@ -1203,7 +1219,8 @@ class LDAP extends Net_LDAP3 {
      * any results. If we don't, maybe this user is not authorized for the
      * domain at all?
      */
-    private function _probe_root_dn($entry_root_dn) {
+    private function _probe_root_dn($entry_root_dn)
+    {
         //console("Running for entry root dn: " . $entry_root_dn);
         if (($tmpconn = ldapconnect($this->_ldap_server)) == false) {
             //message("LDAP Error: " . $this->_errstr());
@@ -1225,23 +1242,25 @@ class LDAP extends Net_LDAP3 {
         return true;
     }
 
-    private function _read($entry_dn, $attributes = Array('*')) {
+    private function _read($entry_dn, $attributes = array('*'))
+    {
         $this->config_set('return_attributes', $attributes);
 
         $result = $this->search($entry_dn, '(objectclass=*)', 'base');
 
         if ($result) {
-            $this->_log(LOG_DEBUG, "Auth::LDAP::_read() result: " . var_export($result->entries(TRUE), TRUE));
-            return $result->entries(TRUE);
+            $this->_log(LOG_DEBUG, "Auth::LDAP::_read() result: " . var_export($result->entries(true), true));
+            return $result->entries(true);
         } else {
-            return FALSE;
+            return false;
         }
     }
 
-    private function _search($base_dn, $filter = '(objectclass=*)', $attributes = Array('*')) {
+    private function _search($base_dn, $filter = '(objectclass=*)', $attributes = array('*'))
+    {
         $this->config_set('return_attributes', $attributes);
         $result = $this->search($base_dn, $filter);
-        $this->_log(LOG_DEBUG, "Auth::LDAP::_search on $base_dn with $filter for attributes: " . var_export($attributes, TRUE) . " with result: " . var_export($result, TRUE));
+        $this->_log(LOG_DEBUG, "Auth::LDAP::_search on $base_dn with $filter for attributes: " . var_export($attributes, true) . " with result: " . var_export($result, true));
         return $result;
     }
 
@@ -1257,7 +1276,8 @@ class LDAP extends Net_LDAP3 {
      *
      * @return string
      */
-    private function _standard_root_dn($associatedDomains) {
+    private function _standard_root_dn($associatedDomains)
+    {
         if (is_array($associatedDomains)) {
             // Usually, the associatedDomain in position 0 is the naming attribute associatedDomain
             if ($associatedDomains['count'] > 1) {


commit a0c4c4f1bf028a276ff43887caf4a6b24405ec01
Author: Aleksander Machniak <alec at alec.pl>
Date:   Sun Oct 21 10:42:05 2012 +0200

    Simplified/unified code of list_* methods

diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php
index 2609e74..4ced2d8 100644
--- a/lib/Auth/LDAP.php
+++ b/lib/Auth/LDAP.php
@@ -296,35 +296,7 @@ class LDAP extends Net_LDAP3 {
     }
 
     public function list_domains($attributes = array(), $search = array(), $params = array()) {
-        $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
-
-        if (!empty($params['sort_by'])) {
-            if (is_array($params['sort_by'])) {
-                foreach ($params['sort_by'] as $attrib) {
-                    if (!in_array($attrib, $attributes)) {
-                        $attributes[] = $attrib;
-                    }
-                }
-            } else {
-                if (!in_array($params['sort_by'], $attributes)) {
-                    $attributes[] = $params['sort_by'];
-                }
-            }
-        }
-
-        if (!empty($params['page_size'])) {
-            $this->config_set('page_size', $params['page_size']);
-        }
-
-        if (!empty($params['page'])) {
-            $this->config_set('list_page', $params['page']);
-        }
-
-        if (empty($attributes) || !is_array($attributes)) {
-            $attributes = array('*');
-        }
-
-        $this->config_set('return_attributes', $attributes);
+        $this->list_prepare($params, $attributes);
 
         $section = $this->conf->get('kolab', 'auth_mechanism');
         $base_dn = $this->conf->get($section, 'domain_base_dn');
@@ -335,8 +307,7 @@ class LDAP extends Net_LDAP3 {
             $filter = $kolab_filter;
         }
 
-        $result = $this->search_entries($base_dn, $filter, 'sub', NULL, $search);
-
+        $result  = $this->search_entries($base_dn, $filter, 'sub', NULL, $search);
         $entries = $this->sort_and_slice($result, $params);
 
         return Array(
@@ -346,41 +317,11 @@ class LDAP extends Net_LDAP3 {
     }
 
     public function list_groups($attributes = array(), $search = array(), $params = array()) {
-        $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
-
-        if (!empty($params['sort_by'])) {
-            if (is_array($params['sort_by'])) {
-                foreach ($params['sort_by'] as $attrib) {
-                    if (!in_array($attrib, $attributes)) {
-                        $attributes[] = $attrib;
-                    }
-                }
-            } else {
-                if (!in_array($params['sort_by'], $attributes)) {
-                    $attributes[] = $params['sort_by'];
-                }
-            }
-        }
-
-        if (!empty($params['page_size'])) {
-            $this->config_set('page_size', $params['page_size']);
-        }
-
-        if (!empty($params['page'])) {
-            $this->config_set('list_page', $params['page']);
-        }
-
-        if (empty($attributes) || !is_array($attributes)) {
-            $attributes = array('*');
-        }
-
-        $this->config_set('return_attributes', $attributes);
-
-        $base_dn = $this->_subject_base_dn("group");
-        $filter = $this->conf->get('group_filter');
-
-        $result = $this->search_entries($base_dn, $filter, 'sub', NULL, $search);
+        $this->list_prepare($params, $attributes);
 
+        $base_dn = $this->_subject_base_dn('group');
+        $filter  = $this->conf->get('group_filter');
+        $result  = $this->search_entries($base_dn, $filter, 'sub', NULL, $search);
         $entries = $this->sort_and_slice($result, $params);
 
         return Array(
@@ -390,58 +331,16 @@ class LDAP extends Net_LDAP3 {
     }
 
     public function list_resources($attributes = array(), $search = array(), $params = array()) {
-        $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
-
-        if (!empty($params['sort_by'])) {
-            if (is_array($params['sort_by'])) {
-                foreach ($params['sort_by'] as $attrib) {
-                    if (!in_array($attrib, $attributes)) {
-                        $attributes[] = $attrib;
-                    }
-                }
-            } else {
-                if (!in_array($params['sort_by'], $attributes)) {
-                    $attributes[] = $params['sort_by'];
-                }
-            }
-        }
-
-        if (!empty($params['page_size'])) {
-            $this->config_set('page_size', $params['page_size']);
-        } else {
-            $this->config_get('page_size', 15);
-        }
+        $this->list_prepare($params, $attributes);
 
-        if (!empty($params['page'])) {
-            $this->config_set('list_page', $params['page']);
-        } else {
-            $this->config_set('list_page', 1);
-        }
-
-        if (empty($attributes) || !is_array($attributes)) {
-            $attributes = array('*');
-        }
-
-        $this->config_set("return_attributes", $attributes);
-
-        $base_dn = $this->_subject_base_dn("resource");
+        $base_dn = $this->_subject_base_dn('resource');
         $filter  = $this->conf->get('resource_filter');
 
         if (!$filter) {
             $filter = '(&(objectclass=*)(!(objectclass=organizationalunit)))';
         }
 
-        if (empty($attributes) || !is_array($attributes)) {
-            $attributes = array('*');
-        }
-
-        if ($s_filter = $this->search_filter($search)) {
-            // join search filter with objectClass filter
-            $filter = '(&' . $filter . $s_filter . ')';
-        }
-
-        $result = $this->search_entries($base_dn, $filter, 'sub', NULL, $search);
-
+        $result  = $this->search_entries($base_dn, $filter, 'sub', NULL, $search);
         $entries = $this->sort_and_slice($result, $params);
 
         return Array(
@@ -451,33 +350,16 @@ class LDAP extends Net_LDAP3 {
     }
 
     public function list_roles($attributes = array(), $search = array(), $params = array()) {
-        if (!empty($params['sort_by'])) {
-            if (!in_array($params['sort_by'], $attributes)) {
-                $attributes[] = $params['sort_by'];
-            }
-        }
+        $this->list_prepare($params, $attributes);
 
-        $base_dn = $this->_subject_base_dn("role");
-        Log::trace("Auth::LDAP::list_roles() using \$base_dn: " . var_export($base_dn, TRUE));
-        // TODO: From config
-        $filter  = "(&(objectclass=ldapsubentry)(objectclass=nsroledefinition))";
+        $base_dn = $this->_subject_base_dn('role');
+        $filter  = $this->conf->get('role_filter');
 
-        if (empty($attributes) || !is_array($attributes)) {
-            $attributes = array('*');
+        if (empty($filter)) {
+            $filter  = "(&(objectclass=ldapsubentry)(objectclass=nsroledefinition))";
         }
 
-        $unique_attr = $this->unique_attribute();
-        if (!in_array($unique_attr, $attributes)) {
-            $attributes[] = $unique_attr;
-        }
-
-        if ($s_filter = $this->search_filter($search)) {
-            // join search filter with objectClass filter
-            $filter = '(&' . $filter . $s_filter . ')';
-        }
-
-        $result = $this->_search($base_dn, $filter, $attributes);
-
+        $result  = $this->search_entries($base_dn, $filter, 'sub', NULL, $search);
         $entries = $this->sort_and_slice($result, $params);
 
         return Array(
@@ -489,6 +371,24 @@ class LDAP extends Net_LDAP3 {
     public function list_users($attributes = array(), $search = array(), $params = array()) {
         $this->_log(LOG_DEBUG, "Auth::LDAP::list_users(" . var_export($attributes, TRUE) . ", " . var_export($search, TRUE) . ", " . var_export($params, TRUE));
 
+        $this->list_prepare($params, $attributes);
+
+        $base_dn = $this->_subject_base_dn("user");
+        $filter  = $this->conf->get('user_filter');
+        $result  = $this->search_entries($base_dn, $filter, 'sub', NULL, $search);
+        $entries = $this->sort_and_slice($result, $params);
+
+        return Array(
+                'list' => $entries,
+                'count' => $result->count()
+            );
+    }
+
+    /**
+     * Prepare environment before search_entries() call
+     */
+    protected function list_prepare($params, $attributes)
+    {
         $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
 
         if (!empty($params['sort_by'])) {
@@ -521,21 +421,7 @@ class LDAP extends Net_LDAP3 {
             $attributes = array('*');
         }
 
-        $this->config_set("return_attributes", $attributes);
-
-        $base_dn = $this->_subject_base_dn("user");
-        $filter = $this->conf->get('user_filter');
-
-        $this->_log(LOG_DEBUG, "Auth::LDAP::list_users() searching entries in $base_dn with $filter, 'sub', NULL, " . var_export($search, TRUE));
-
-        $result = $this->search_entries($base_dn, $filter, 'sub', NULL, $search);
-
-        $entries = $this->sort_and_slice($result, $params);
-
-        return Array(
-                'list' => $entries,
-                'count' => $result->count()
-            );
+        $this->config_set('return_attributes', $attributes);
     }
 
     public function resource_add($attrs, $typeid = null) {


commit 3ed66ab2cb4092fd24d1cdb15fd84f2c61c684f1
Author: Aleksander Machniak <alec at alec.pl>
Date:   Sat Oct 20 13:54:22 2012 +0200

    Code unification/simplification

diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php
index f4fd89c..2609e74 100644
--- a/lib/Auth/LDAP.php
+++ b/lib/Auth/LDAP.php
@@ -148,35 +148,16 @@ class LDAP extends Net_LDAP3 {
     }
 
     public function domain_edit($domain, $attributes, $typeid = null) {
-        // Domain identifier
-        $unique_attr = $this->unique_attribute();
-
-        // Now that values have been re-generated where necessary, compare
-        // the new domain attributes to the original domain attributes.
-        $_domain = $this->domain_find_by_attribute(array($unique_attr => $attributes[$unique_attr]));
-
-        if (empty($_domain)) {
-            $_domain = $this->entry_dn($domain);
-
-            if (empty($_domain)) {
-                return false;
-            }
-
-            $_domain_dn = $domain;
-        }
-        else {
-            $_domain_dn = key($_domain);
-        }
+        $domain = $this->domain_info($domain, array_keys($attributes));
 
-        if (!$_domain) {
-            console("Could not find domain");
+        if (empty($domain)) {
             return false;
         }
 
-        $_domain = $this->domain_info($_domain_dn, array_keys($attributes));
+        $domain_dn = key($domain);
 
         // We should start throwing stuff over the fence here.
-        return $this->modify_entry($_domain_dn, $_domain[$_domain_dn], $attributes);
+        return $this->modify_entry($domain_dn, $domain[$domain_dn], $attributes);
     }
 
     public function domain_delete($domain) {
@@ -250,21 +231,7 @@ class LDAP extends Net_LDAP3 {
     }
 
     public function group_add($attrs, $typeid = null) {
-        if ($typeid == null) {
-            $type_str = 'group';
-        }
-        else {
-            $db   = SQL::get_instance();
-            $_key = $db->fetch_assoc($db->query("SELECT `key` FROM group_types WHERE id = ?", $typeid));
-            $type_str = $_key['key'];
-        }
-
-        // Check if the group_type has a specific base DN specified.
-        $base_dn = $this->conf->get($type_str . "_group_base_dn");
-        // If not, take the regular user_base_dn
-        if (!$base_dn) {
-            $base_dn = $this->conf->get("group_base_dn");
-        }
+        $base_dn = $this->entry_base_dn('group', $typeid);
 
         // TODO: The rdn is configurable as well.
         // Use [$type_str . "_"]user_rdn_attr
@@ -278,24 +245,16 @@ class LDAP extends Net_LDAP3 {
     }
 
     public function group_edit($group, $attributes, $typeid = null) {
-        // Group identifier
-        $unique_attr = $this->unique_attribute();
-        $attributes[$unique_attr] = $group;
+        $group = $this->group_info($group, array_keys($attributes));
 
-        // Now that values have been re-generated where necessary, compare
-        // the new group attributes to the original group attributes.
-        $_group = $this->entry_find_by_attribute(array($unique_attr => $attributes[$unique_attr]));
-
-        if (!$_group) {
-            console("Could not find group");
+        if (empty($group)) {
             return false;
         }
 
-        $_group_dn = key($_group);
-        $_group = $this->group_info($_group_dn, array_keys($attributes));
+        $group_dn = key($group);
 
         // We should start throwing stuff over the fence here.
-        return $this->modify_entry($_group_dn, $_group[$_group_dn], $attributes);
+        return $this->modify_entry($group_dn, $group[$group_dn], $attributes);
     }
 
     public function group_find_by_attribute($attribute) {
@@ -306,7 +265,7 @@ class LDAP extends Net_LDAP3 {
         $this->_log(LOG_DEBUG, "Auth::LDAP::group_info() for group " . var_export($group, TRUE));
         $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
 
-        $unique_attr = $this->config_get('unique_attribute', 'nsuniqueid');
+        $unique_attr = $this->unique_attribute();
         if (!in_array($unique_attr, $attributes)) {
             $attributes[] = $unique_attr;
         }
@@ -580,21 +539,7 @@ class LDAP extends Net_LDAP3 {
     }
 
     public function resource_add($attrs, $typeid = null) {
-        if ($typeid == null) {
-            $type_str = 'resource';
-        }
-        else {
-            $db   = SQL::get_instance();
-            $_key = $db->fetch_assoc($db->query("SELECT `key` FROM resource_types WHERE id = ?", $typeid));
-            $type_str = $_key['key'];
-        }
-
-        // Check if the resource_type has a specific base DN specified.
-        $base_dn = $this->conf->get($type_str . "_resource_base_dn");
-        // If not, take the regular user_base_dn
-        if (!$base_dn) {
-            $base_dn = $this->conf->get("resource_base_dn");
-        }
+        $base_dn = $this->entry_base_dn('resource', $typeid);
 
         // TODO: The rdn is configurable as well.
         // Use [$type_str . "_"]user_rdn_attr
@@ -608,25 +553,16 @@ class LDAP extends Net_LDAP3 {
     }
 
     public function resource_edit($resource, $attributes, $typeid = null) {
-        // Resource identifier
-        $unique_attr = $this->unique_attribute();
-        $attributes[$unique_attr] = $resource;
+        $resource = $this->resource_info($resource, array_keys($attributes));
 
-        console("\$this->domain: " . $this->domain);
-        // Now that values have been re-generated where necessary, compare
-        // the new resource attributes to the original resource attributes.
-        $_resource = $this->entry_find_by_attribute(array($unique_attr => $attributes[$unique_attr]));
-
-        if (!$_resource) {
-            console("Could not find resource");
+        if (empty($resource)) {
             return false;
         }
 
-        $_resource_dn = key($_resource);
-        $_resource = $this->resource_info($_resource_dn, array_keys($attributes));
+        $resource_dn = key($resource);
 
         // We should start throwing stuff over the fence here.
-        return $this->modify_entry($_resource_dn, $_resource[$_resource_dn], $attributes);
+        return $this->modify_entry($resource_dn, $resource[$resource_dn], $attributes);
     }
 
     public function resource_find_by_attribute($attribute) {
@@ -654,16 +590,7 @@ class LDAP extends Net_LDAP3 {
     }
 
     public function role_add($attrs) {
-        if ($typeid == null) {
-            $type_str = 'role';
-        }
-        else {
-            $db   = SQL::get_instance();
-            $_key = $db->fetch_assoc($db->query("SELECT `key` FROM role_types WHERE id = ?", $typeid));
-            $type_str = $_key['key'];
-        }
-
-        $base_dn = $this->_subject_base_dn('role');
+        $base_dn = $this->entry_base_dn('role', $typeid);
 
         // TODO: The rdn is configurable as well.
         // Use [$type_str . "_"]user_rdn_attr
@@ -673,24 +600,16 @@ class LDAP extends Net_LDAP3 {
     }
 
     public function role_edit($role, $attributes, $typeid = null) {
-        // Resource identifier
-        $unique_attr = $this->unique_attribute();
-        $attributes[$unique_attr] = $role;
-
-        // Now that values have been re-generated where necessary, compare
-        // the new role attributes to the original role attributes.
-        $_role = $this->entry_find_by_attribute(array($unique_attr => $attributes[$unique_attr], 'objectclass' => 'ldapsubentry'));
+        $role = $this->role_info($role, array_keys($attributes));
 
-        if (!$_role) {
-            Log::error("Could not find role identified with $role.");
+        if (empty($role)) {
             return false;
         }
 
-        $_role_dn = key($_role);
-        $_role = $this->role_info($_role_dn, array_keys($attributes));
+        $role_dn = key($role);
 
         // We should start throwing stuff over the fence here.
-        return $this->modify_entry($_role_dn, $_role[$_role_dn], $attributes);
+        return $this->modify_entry($role_dn, $role[$role_dn], $attributes);
     }
 
     public function role_delete($role) {
@@ -711,7 +630,7 @@ class LDAP extends Net_LDAP3 {
     }
 
     public function role_info($role, $attributes = array('*')) {
-        $role_dn = $this->entry_dn($role);
+        $role_dn = $this->entry_dn($role, array('objectclass' => 'ldapsubentry'));
 
         if (!$role_dn) {
             return false;
@@ -737,27 +656,12 @@ class LDAP extends Net_LDAP3 {
     }
 
     public function user_add($attrs, $typeid = null) {
-        if ($typeid == null) {
-            $type_str = 'user';
-        }
-        else {
-            $db   = SQL::get_instance();
-            $_key = $db->fetch_assoc($db->query("SELECT `key` FROM user_types WHERE id = ?", $typeid));
-            $type_str = $_key['key'];
-        }
-
-        // Check if the user_type has a specific base DN specified.
-        $base_dn = $this->_subject_base_dn($type_str . "_user");
-        if (empty($base_dn)) {
-            $base_dn = $this->_subject_base_dn("user");
-        }
+        $base_dn = $this->entry_base_dn('user', $typeid);
 
         if (!empty($attrs['ou'])) {
             $base_dn = $attrs['ou'];
         }
 
-        //console("Base DN now: $base_dn");
-
         // TODO: The rdn is configurable as well.
         // Use [$type_str . "_"]user_rdn_attr
         $dn = "uid=" . $attrs['uid'] . "," . $base_dn;
@@ -766,27 +670,16 @@ class LDAP extends Net_LDAP3 {
     }
 
     public function user_edit($user, $attributes, $typeid = null) {
-        $this->_log(LOG_DEBUG, "user.edit() called for $user, attributes", $attributes);
-
-        $unique_attr = $this->config_get('unique_attribute', 'nsuniqueid');
-
-        $attributes[$unique_attr] = $user;
-
-        // Now that values have been re-generated where necessary, compare
-        // the new group attributes to the original group attributes.
-        $_user = $this->entry_find_by_attribute(array($unique_attr => $attributes[$unique_attr]));
+        $user = $this->user_info($user, array_keys($attributes));
 
-        if (!$_user) {
-            console("Could not find user");
+        if (empty($user)) {
             return false;
         }
-        $_user_dn = key($_user);
-        $_user = $this->user_info($_user_dn, array_keys($attributes));
 
-        console("Auth::LDAP::user_edit() existing \$_user info", $_user);
+        $user_dn = key($user);
 
         // We should start throwing stuff over the fence here.
-        return $this->modify_entry($_user_dn, $_user[$_user_dn], $attributes);
+        return $this->modify_entry($user_dn, $user[$user_dn], $attributes);
     }
 
     public function user_delete($user) {
@@ -797,7 +690,7 @@ class LDAP extends Net_LDAP3 {
         $this->_log(LOG_DEBUG, "Auth::LDAP::user_info() for user " . var_export($user, TRUE));
         $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
 
-        $unique_attr = $this->config_get('unique_attribute', 'nsuniqueid');
+        $unique_attr = $this->unique_attribute();
         if (!in_array($unique_attr, $attributes)) {
             $attributes[] = $unique_attr;
         }
@@ -842,6 +735,26 @@ class LDAP extends Net_LDAP3 {
         return $this->add_entry($entry_dn, $attrs);
     }
 
+    /**
+     * Return base DN for specified object type
+     */
+    protected function entry_base_dn($type, $typeid = null)
+    {
+        if ($typeid) {
+            $db  = SQL::get_instance();
+            $sql = $db->fetch_assoc($db->query("SELECT `key` FROM {$type}_types WHERE id = ?", $typeid));
+
+            // Check if the type has a specific base DN specified.
+            $base_dn = $this->_subject_base_dn($sql['key'] . '_' . $type . '_base_dn');
+        }
+
+        if (empty($base_dn)) {
+            $base_dn = $this->_subject_base_dn($type . '_base_dn');
+        }
+
+        return $base_dn;
+    }
+
     public function _config_get($key, $default = NULL) {
         $key_parts = explode("_", $key);
         $this->_log(LOG_DEBUG, var_export($key_parts));


commit b15bd9b89c55820e185399efa76b41f7d2c186ad
Author: Aleksander Machniak <alec at alec.pl>
Date:   Sat Oct 20 12:56:45 2012 +0200

    Fix role deletion (#696)

diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php
index 4bfe7c5..f4fd89c 100644
--- a/lib/Auth/LDAP.php
+++ b/lib/Auth/LDAP.php
@@ -694,7 +694,7 @@ class LDAP extends Net_LDAP3 {
     }
 
     public function role_delete($role) {
-        return $this->entry_delete($role);
+        return $this->entry_delete($role, array('objectclass' => 'ldapsubentry'));
     }
 
     public function role_find_by_attribute($attribute) {
@@ -821,10 +821,10 @@ class LDAP extends Net_LDAP3 {
     /**
      * delete_entry() wrapper with binding and DN resolving
      */
-    protected function entry_delete($entry) {
+    protected function entry_delete($entry, $attributes = array()) {
         $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
 
-        $entry_dn = $this->entry_dn($entry);
+        $entry_dn = $this->entry_dn($entry, $attributes);
 
         if (!$entry_dn) {
             return false;
diff --git a/lib/ext/Net/LDAP3.php b/lib/ext/Net/LDAP3.php
index 75c01a5..432d68d 100644
--- a/lib/ext/Net/LDAP3.php
+++ b/lib/ext/Net/LDAP3.php
@@ -627,7 +627,15 @@ class Net_LDAP3
         return $attributes;
     }
 
-    public function entry_dn($subject)
+    /**
+     * Resolve entry data to entry DN
+     *
+     * @param string $subject    Entry string (e.g. entry DN or unique attribute value)
+     * @param array  $attributes Additional attributes
+     *
+     * @return string Entry DN string
+     */
+    public function entry_dn($subject, $attributes = array())
     {
         $this->_debug("entry_dn on subject $subject");
         $is_dn = ldap_explode_dn($subject, 1);
@@ -638,7 +646,8 @@ class Net_LDAP3
         }
 
         $unique_attr = $this->config_get('unique_attribute', 'nsuniqueid');
-        $subject     = $this->entry_find_by_attribute(array($unique_attr => $subject));
+        $attributes  = array_merge(array($unique_attr => $subject), (array)$attributes);
+        $subject     = $this->entry_find_by_attribute($attributes);
 
         if (!empty($subject)) {
             return key($subject);


commit c985b1959b7781d0d343a94dffd0b166abf3939b
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Oct 19 15:43:24 2012 +0200

    Bind for entries deletion, simplified code

diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php
index 41b1bfa..4bfe7c5 100644
--- a/lib/Auth/LDAP.php
+++ b/lib/Auth/LDAP.php
@@ -270,9 +270,7 @@ class LDAP extends Net_LDAP3 {
         // Use [$type_str . "_"]user_rdn_attr
         $dn = "cn=" . $attrs['cn'] . "," . $base_dn;
 
-        $this->bind($_SESSION['user']->user_bind_dn,$_SESSION['user']->user_bind_pw);
-
-        return $this->add_entry($dn, $attrs);
+        return $this->entry_add($dn, $attrs);
     }
 
     public function group_delete($group) {
@@ -326,7 +324,6 @@ class LDAP extends Net_LDAP3 {
         $group_info = $this->_read($group_dn, $attributes);
         $this->_log(LOG_DEBUG, "Auth::LDAP::group_info() result: " . var_export($group_info, TRUE));
         return $group_info;
-
     }
 
     public function group_members_list($group, $recurse = true) {
@@ -603,7 +600,7 @@ class LDAP extends Net_LDAP3 {
         // Use [$type_str . "_"]user_rdn_attr
         $dn = "cn=" . $attrs['cn'] . "," . $base_dn;
 
-        return $this->add_entry($dn, $attrs);
+        return $this->entry_add($dn, $attrs);
     }
 
     public function resource_delete($resource) {
@@ -666,15 +663,13 @@ class LDAP extends Net_LDAP3 {
             $type_str = $_key['key'];
         }
 
-        $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
-
         $base_dn = $this->_subject_base_dn('role');
 
         // TODO: The rdn is configurable as well.
         // Use [$type_str . "_"]user_rdn_attr
         $dn = "cn=" . $attrs['cn'] . "," . $base_dn;
 
-        return $this->add_entry($dn, $attrs);
+        return $this->entry_add($dn, $attrs);
     }
 
     public function role_edit($role, $attributes, $typeid = null) {
@@ -742,7 +737,6 @@ class LDAP extends Net_LDAP3 {
     }
 
     public function user_add($attrs, $typeid = null) {
-        $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
         if ($typeid == null) {
             $type_str = 'user';
         }
@@ -762,13 +756,13 @@ class LDAP extends Net_LDAP3 {
             $base_dn = $attrs['ou'];
         }
 
-        console("Base DN now: $base_dn");
+        //console("Base DN now: $base_dn");
 
         // TODO: The rdn is configurable as well.
         // Use [$type_str . "_"]user_rdn_attr
         $dn = "uid=" . $attrs['uid'] . "," . $base_dn;
 
-        return $this->add_entry($dn, $attrs);
+        return $this->entry_add($dn, $attrs);
     }
 
     public function user_edit($user, $attributes, $typeid = null) {
@@ -824,7 +818,12 @@ class LDAP extends Net_LDAP3 {
         return $this->entry_find_by_attribute($attribute);
     }
 
+    /**
+     * delete_entry() wrapper with binding and DN resolving
+     */
     protected function entry_delete($entry) {
+        $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
+
         $entry_dn = $this->entry_dn($entry);
 
         if (!$entry_dn) {
@@ -834,6 +833,15 @@ class LDAP extends Net_LDAP3 {
         return $this->delete_entry($entry_dn);
     }
 
+    /**
+     * add_entry() wrapper with binding
+     */
+    protected function entry_add($entry_dn, $attrs) {
+        $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
+
+        return $this->add_entry($entry_dn, $attrs);
+    }
+
     public function _config_get($key, $default = NULL) {
         $key_parts = explode("_", $key);
         $this->_log(LOG_DEBUG, var_export($key_parts));
@@ -1397,13 +1405,13 @@ class LDAP extends Net_LDAP3 {
      * domain at all?
      */
     private function _probe_root_dn($entry_root_dn) {
-        console("Running for entry root dn: " . $entry_root_dn);
+        //console("Running for entry root dn: " . $entry_root_dn);
         if (($tmpconn = ldapconnect($this->_ldap_server)) == false) {
             //message("LDAP Error: " . $this->_errstr());
             return false;
         }
 
-        console("User DN: " . $_SESSION['user']->user_bind_dn);
+        //console("User DN: " . $_SESSION['user']->user_bind_dn);
 
         if (ldap_bind($tmpconn, $_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw) === false) {
             //message("LDAP Error: " . $this->_errstr());


commit cd110196281e957144d167410e11d76ebef5e3e1
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Oct 19 14:33:28 2012 +0200

    Fix api_call() where get and post requests were replaced

diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php
index 95d45a7..5fe2473 100644
--- a/lib/kolab_client_task.php
+++ b/lib/kolab_client_task.php
@@ -565,10 +565,10 @@ class kolab_client_task
     protected function api_call($type, $action, $get = array(), $post = array())
     {
         if ($type == 'post') {
-            $result = $this->api->get($action, $get);
+            $result = $this->api->post($action, $get, $post);
         }
         else {
-            $result = $this->api->post($action, $get, $post);
+            $result = $this->api->get($action, $get);
         }
 
         // error handling


commit 5c1842a9216ef364ff508a63087f6a23bb0a4b3a
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Oct 19 12:17:30 2012 +0200

    Implemented session timeout (#695)

diff --git a/lib/kolab_api_controller.php b/lib/kolab_api_controller.php
index bf7058b..de1c346 100644
--- a/lib/kolab_api_controller.php
+++ b/lib/kolab_api_controller.php
@@ -30,6 +30,7 @@ class kolab_api_controller
 {
     public $output;
 
+    private $config;
     private $uid;
     private $request  = array();
     private $services = array();
@@ -39,6 +40,7 @@ class kolab_api_controller
     public function __construct()
     {
         $this->output = new kolab_json_output();
+        $this->config = Conf::get_instance();
 
         if (!empty($_GET['service'])) {
             if (!empty($_GET['method'])) {
@@ -128,10 +130,8 @@ class kolab_api_controller
      */
     public function dispatch($postdata)
     {
-        $config = Conf::get_instance();
-
         // Use proxy
-        if (empty($_GET['proxy']) && ($url = $config->get('kolab_wap', 'api_url'))) {
+        if (empty($_GET['proxy']) && ($url = $this->config->get('kolab_wap', 'api_url'))) {
             $this->proxy($postdata, $url);
             return;
         }
@@ -143,7 +143,7 @@ class kolab_api_controller
         Log::debug("Calling $service.$method");
 
         // validate user session
-        if ($service != 'system' || $method != 'authenticate') {
+        if (!in_array($method, array('quit', 'authenticate'))) {
             if (!$this->session_validate($postdata)) {
                 throw new Exception("Invalid session", 403);
             }
@@ -253,11 +253,20 @@ class kolab_api_controller
         session_id($sess_id);
         session_start();
 
-        if (isset($_SESSION['user']) && $_SESSION['user']->authenticated()) {
-            return true;
+        if (empty($_SESSION['user']) || !$_SESSION['user']->authenticated()) {
+            return false;
         }
 
-        return false;
+        // Session timeout
+        $timeout = $this->config->get('kolab_wap', 'session_timeout');
+        if ($timeout && $_SESSION['time'] && $_SESSION['time'] < time() - $timeout) {
+            return false;
+        }
+
+        // update session time
+        $_SESSION['time'] = time();
+
+        return true;
     }
 
 
@@ -296,8 +305,7 @@ class kolab_api_controller
             }
             else {
                 Log::debug("No domain name space in the username, using the primary domain");
-                $conf = Conf::get_instance();
-                $domain = $conf->get('kolab', 'primary_domain');
+                $domain = $this->config->get('kolab', 'primary_domain');
             }
         }
         else {
@@ -312,7 +320,7 @@ class kolab_api_controller
 
         // start new (PHP) session
         if ($valid) {
-            $_SESSION['start'] = time();
+            $_SESSION['time'] = time();
             return array(
                 'user'          => $_SESSION['user']->get_username(),
                 'userid'        => $_SESSION['user']->get_userid(),
@@ -335,8 +343,7 @@ class kolab_api_controller
         $auth = Auth::get_instance();
 
         // Get the domain name attribute
-        $conf = Conf::get_instance();
-        $dna = $conf->get('ldap', 'domain_name_attribute');
+        $dna = $this->config->get('ldap', 'domain_name_attribute');
         if (empty($dna)) {
             $dna = 'associateddomain';
         }


commit dca7666f46a1ef81dd2ca2fca7f665521b9163a3
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Oct 19 12:07:35 2012 +0200

    Improved API errors handling added error logging and logout on invalid session

diff --git a/lib/client/kolab_client_task_domain.php b/lib/client/kolab_client_task_domain.php
index 71a138a..25dc711 100644
--- a/lib/client/kolab_client_task_domain.php
+++ b/lib/client/kolab_client_task_domain.php
@@ -94,7 +94,7 @@ class kolab_client_task_domain extends kolab_client_task
         }
 
         // get domains list
-        $result = $this->api->post('domains.list', null, $post);
+        $result = $this->api_post('domains.list', null, $post);
         $count  = (int) $result->get('count');
         $result = (array) $result->get('list');
 
@@ -189,7 +189,7 @@ class kolab_client_task_domain extends kolab_client_task
         $id     = $this->get_input('id', 'POST');
         //console("action_info() on", $id);
 
-        $result = $this->api->get('domain.info', array('domain' => $id));
+        $result = $this->api_get('domain.info', array('domain' => $id));
         //console("action_info() \$result", $result);
 
         $domain  = $result->get();
diff --git a/lib/client/kolab_client_task_group.php b/lib/client/kolab_client_task_group.php
index 3349129..81c1fe4 100644
--- a/lib/client/kolab_client_task_group.php
+++ b/lib/client/kolab_client_task_group.php
@@ -94,7 +94,7 @@ class kolab_client_task_group extends kolab_client_task
         }
 
         // get groups list
-        $result = $this->api->post('groups.list', null, $post);
+        $result = $this->api_post('groups.list', null, $post);
         $count  = (int) $result->get('count');
         $result = (array) $result->get('list');
 
@@ -179,7 +179,7 @@ class kolab_client_task_group extends kolab_client_task
     public function action_info()
     {
         $id     = $this->get_input('id', 'POST');
-        $result = $this->api->get('group.info', array('group' => $id));
+        $result = $this->api_get('group.info', array('group' => $id));
         $group  = $result->get();
         $output = $this->group_form(null, $group);
 
diff --git a/lib/client/kolab_client_task_main.php b/lib/client/kolab_client_task_main.php
index bab614f..b88fa40 100644
--- a/lib/client/kolab_client_task_main.php
+++ b/lib/client/kolab_client_task_main.php
@@ -39,7 +39,7 @@ class kolab_client_task_main extends kolab_client_task
     {
         // handle domain change
         if ($domain = $this->get_input('domain', 'GET')) {
-            $result = $this->api->get('system.select_domain', array('domain' => $domain));
+            $result = $this->api_get('system.select_domain', array('domain' => $domain));
 
             if (!$result->get_error_code()) {
                 $_SESSION['user']['domain'] = $domain;
diff --git a/lib/client/kolab_client_task_resource.php b/lib/client/kolab_client_task_resource.php
index a5669be..15caf6f 100644
--- a/lib/client/kolab_client_task_resource.php
+++ b/lib/client/kolab_client_task_resource.php
@@ -94,7 +94,7 @@ class kolab_client_task_resource extends kolab_client_task
         }
 
         // get resources list
-        $result = $this->api->post('resources.list', null, $post);
+        $result = $this->api_post('resources.list', null, $post);
         $count  = $result->get('count');
         $result = (array) $result->get('list');
 
@@ -192,7 +192,7 @@ class kolab_client_task_resource extends kolab_client_task
     public function action_info()
     {
         $id         = $this->get_input('id', 'POST');
-        $result     = $this->api->get('resource.info', array('resource' => $id));
+        $result     = $this->api_get('resource.info', array('resource' => $id));
         $resource   = $result->get();
 
         //console("action_info()", $resource);
diff --git a/lib/client/kolab_client_task_role.php b/lib/client/kolab_client_task_role.php
index 5539ceb..bae4585 100644
--- a/lib/client/kolab_client_task_role.php
+++ b/lib/client/kolab_client_task_role.php
@@ -94,7 +94,7 @@ class kolab_client_task_role extends kolab_client_task
         }
 
         // get roles list
-        $result = $this->api->post('roles.list', null, $post);
+        $result = $this->api_post('roles.list', null, $post);
         $count  = (int) $result->get('count');
         $result = (array) $result->get('list');
 
@@ -179,7 +179,7 @@ class kolab_client_task_role extends kolab_client_task
     public function action_info()
     {
         $id     = $this->get_input('id', 'POST');
-        $result = $this->api->get('role.info', array('role' => $id));
+        $result = $this->api_get('role.info', array('role' => $id));
         $role  = $result->get();
         $output = $this->role_form(null, $role);
 
diff --git a/lib/client/kolab_client_task_settings.php b/lib/client/kolab_client_task_settings.php
index 376097e..a6254f0 100644
--- a/lib/client/kolab_client_task_settings.php
+++ b/lib/client/kolab_client_task_settings.php
@@ -829,7 +829,7 @@ class kolab_client_task_settings extends kolab_client_task
         );
 
         // get all available attributes
-        $response   = $this->api->post('form_value.select_options', null, $post_data);
+        $response   = $this->api_post('form_value.select_options', null, $post_data);
         $response   = $response->get('attribute');
         $attributes = array();
 
diff --git a/lib/client/kolab_client_task_signup.php b/lib/client/kolab_client_task_signup.php
index 8863c2b..7c40b36 100644
--- a/lib/client/kolab_client_task_signup.php
+++ b/lib/client/kolab_client_task_signup.php
@@ -69,7 +69,7 @@ class kolab_client_task_signup extends kolab_client_task
         }
 
         // Login ($result is a kolab_client_api_result instance)
-        $result = $this->api->login($this->config_get('bind_dn'), $this->config_get('bind_pw'), $this->domain);
+        $result = $this->api_login($this->config_get('bind_dn'), $this->config_get('bind_pw'), $this->domain);
 
         // Set the session token we got in the API client instance, so subsequent
         // API calls are made in the same session.
diff --git a/lib/client/kolab_client_task_user.php b/lib/client/kolab_client_task_user.php
index 3edf551..2ab1647 100644
--- a/lib/client/kolab_client_task_user.php
+++ b/lib/client/kolab_client_task_user.php
@@ -94,7 +94,7 @@ class kolab_client_task_user extends kolab_client_task
         }
 
         // get users list
-        $result = $this->api->post('users.list', null, $post);
+        $result = $this->api_post('users.list', null, $post);
         $count  = $result->get('count');
         $result = (array) $result->get('list');
 
@@ -179,7 +179,7 @@ class kolab_client_task_user extends kolab_client_task
     public function action_info()
     {
         $id     = $this->get_input('id', 'POST');
-        $result = $this->api->get('user.info', array('user' => $id));
+        $result = $this->api_get('user.info', array('user' => $id));
         $user   = $result->get();
         $output = $this->user_form(null, $user);
 
diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php
index 59ed1fb..95d45a7 100644
--- a/lib/kolab_client_task.php
+++ b/lib/kolab_client_task.php
@@ -316,9 +316,9 @@ class kolab_client_task
     /**
      * Logout action.
      */
-    private function action_logout($sess_expired = false)
+    private function action_logout($sess_expired = false, $stop_sess = true)
     {
-        if (!empty($_SESSION['user']) && !empty($_SESSION['user']['token'])) {
+        if (!empty($_SESSION['user']) && !empty($_SESSION['user']['token']) && $stop_sess) {
             $this->api->logout();
         }
         $_SESSION = array();
@@ -328,6 +328,11 @@ class kolab_client_task
                 $args = array('error' => 'session.expired');
             }
             $this->output->command('main_logout', $args);
+
+            if ($sess_expired) {
+                $this->output->send();
+                exit;
+            }
         }
         else {
             $this->output->add_translation('loginerror', 'internalerror', 'session.expired');
@@ -539,6 +544,50 @@ class kolab_client_task
     }
 
     /**
+     * API GET request wrapper
+     */
+    protected function api_get($action, $get = array())
+    {
+        return $this->api_call('get', $action, $get);
+    }
+
+    /**
+     * API POST request wrapper
+     */
+    protected function api_post($action, $get = array(), $post = array())
+    {
+        return $this->api_call('post', $action, $get, $post);
+    }
+
+    /**
+     * API request wrapper with error handling
+     */
+    protected function api_call($type, $action, $get = array(), $post = array())
+    {
+        if ($type == 'post') {
+            $result = $this->api->get($action, $get);
+        }
+        else {
+            $result = $this->api->post($action, $get, $post);
+        }
+
+        // error handling
+        if ($code = $result->get_error_code()) {
+            // Invalid session, do logout
+            if ($code == 403) {
+                $this->action_logout(true, false);
+            }
+
+            // Log communication errors, other should be logged on API side
+            if ($code < 400) {
+                $this->raise_error($code, 'API Error: ' . $result->get_error_str());
+            }
+        }
+
+        return $result;
+    }
+
+    /**
      * Returns list of object types.
      *
      * @para string  $type     Object type name
@@ -555,7 +604,7 @@ class kolab_client_task
         $cache_idx = $type . '_types' . ($used_for ? ":$used_for" : '');
 
         if (!array_key_exists($cache_idx, $this->cache)) {
-            $result = $this->api->post($type . '_types.list');
+            $result = $this->api_post($type . '_types.list');
             $list   = $result->get('list');
 
             if (!empty($used_for) && is_array($list)) {
@@ -589,7 +638,7 @@ class kolab_client_task
             }
         }
 
-        $result   = $this->api->get('user.info', array('user' => $dn));
+        $result   = $this->api_get('user.info', array('user' => $dn));
         $username = $result->get('displayname');
 
         if (empty($username)) {
@@ -622,7 +671,7 @@ class kolab_client_task
             $list = $_SESSION['capabilities'];
         }
         else {
-            $result = $this->api->post('system.capabilities');
+            $result = $this->api_post('system.capabilities');
             $list   = $result->get('list');
 
             if (is_array($list) && !$this->devel_mode) {
@@ -681,7 +730,7 @@ class kolab_client_task
         }
 
         // Get the rights on the entry and attribute level
-        $result = $this->api->get($type . '.effective_rights', array($type => $id));
+        $result = $this->api_get($type . '.effective_rights', array($type => $id));
 
         $result = array(
             'attribute' => $result->get('attributeLevelRights'),
@@ -838,7 +887,7 @@ class kolab_client_task
 
         if (!isset($field['values'])) {
             $data['attributes'] = array($field['name']);
-            $resp = $this->api->post('form_value.select_options', null, $data);
+            $resp = $this->api_post('form_value.select_options', null, $data);
             $resp = $resp->get($field['name']);
             unset($data['attributes']);
 
@@ -1012,7 +1061,7 @@ class kolab_client_task
         if ($add_mode) {
             if (!empty($auto_attribs)) {
                 $data['attributes'] = $auto_attribs;
-                $resp = $this->api->post('form_value.generate', null, $data);
+                $resp = $this->api_post('form_value.generate', null, $data);
                 $data = array_merge((array)$data, (array)$resp->get());
                 unset($data['attributes']);
             }
@@ -1078,7 +1127,7 @@ class kolab_client_task
                 );
 
                 // get options list
-                $result = $this->api->post('form_value.list_options', null, $post);
+                $result = $this->api_post('form_value.list_options', null, $post);
                 $result = $result->get('list');
 
                 $data[$fname] = $result;
diff --git a/public_html/js/kolab_admin.js b/public_html/js/kolab_admin.js
index 4f79b5f..99fc6b9 100644
--- a/public_html/js/kolab_admin.js
+++ b/public_html/js/kolab_admin.js
@@ -355,12 +355,11 @@ function kolab_admin()
     this.set_busy(false);
 
     if (!response || response.status != 'OK') {
-      var msg = response && response.reason ? response.reason : this.t('servererror');
-      this.display_message(msg, 'error');
-
       // Logout on invalid-session error
       if (response && response.code == 403)
         this.main_logout();
+      else
+        this.display_message(response && response.reason ? response.reason : this.t('servererror'), 'error');
 
       return false;
     }


commit ed595bad4134dc44e57dfd0f50781b62df7f5f78
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Oct 19 12:07:23 2012 +0200

    Added API calls tracking

diff --git a/lib/kolab_client_api.php b/lib/kolab_client_api.php
index 7405489..af9eeca 100644
--- a/lib/kolab_client_api.php
+++ b/lib/kolab_client_api.php
@@ -37,9 +37,6 @@ class kolab_client_api
      */
     private $base_url;
 
-    const STATUS_OK    = 0;
-    const STATUS_ERROR = 1;
-
     const ERROR_INTERNAL   = 100;
     const ERROR_CONNECTION = 200;
 
@@ -158,9 +155,9 @@ class kolab_client_api
     {
         $url = $this->build_url($action, $args);
 
-        $this->request->setMethod(HTTP_Request2::METHOD_GET);
+        Log::trace("Calling API GET: $url");
 
-        //console("GET", $url);
+        $this->request->setMethod(HTTP_Request2::METHOD_GET);
 
         return $this->get_response($url);
     }
@@ -178,6 +175,8 @@ class kolab_client_api
     {
         $url = $this->build_url($action, $url_args);
 
+        Log::trace("Calling API POST: $url");
+
         $this->request->setMethod(HTTP_Request2::METHOD_POST);
         $this->request->setBody(@json_encode($post));
 


commit 3b0ca89d3aac590882e91169d50df090a92534f0
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Oct 19 11:02:20 2012 +0200

    Set timeout of session-expired message to 60 sec.

diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php
index 189ad3e..59ed1fb 100644
--- a/lib/kolab_client_task.php
+++ b/lib/kolab_client_task.php
@@ -341,7 +341,7 @@ class kolab_client_task
         }
 
         if ($error) {
-            $this->output->command('display_message', $error, 'error');
+            $this->output->command('display_message', $error, 'error', 60000);
         }
 
         $this->output->send('login');


commit ae1885c33c1785c4eca09f284a0b154b68c86e49
Author: Aleksander Machniak <alec at alec.pl>
Date:   Tue Oct 16 15:29:43 2012 +0200

    Implemented form field of type 'ldap_url'

diff --git a/lib/client/kolab_client_task_settings.php b/lib/client/kolab_client_task_settings.php
index d5517e7..376097e 100644
--- a/lib/client/kolab_client_task_settings.php
+++ b/lib/client/kolab_client_task_settings.php
@@ -32,7 +32,7 @@ class kolab_client_task_settings extends kolab_client_task
     );
 
     protected $form_element_types = array(
-        'text', 'select', 'multiselect', 'list', 'list-autocomplete', 'checkbox', 'password'
+        'text', 'select', 'multiselect', 'list', 'list-autocomplete', 'checkbox', 'password', 'ldap_url',
     );
 
 
diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php
index d60f1ea..189ad3e 100644
--- a/lib/kolab_client_task.php
+++ b/lib/kolab_client_task.php
@@ -805,6 +805,16 @@ class kolab_client_task
             if (isset($field['maxlength'])) {
                 $result['maxlength'] = $field['maxlength'];
             }
+            if ($field['type'] && $field['type'] != 'text') {
+                $result['data-type'] = $field['type'];
+                if ($field['type'] == 'ldap_url') {
+                    $this->output->add_translation('ldap.one', 'ldap.sub', 'ldap.base',
+                        'ldap.host', 'ldap.basedn','ldap.scope', 'ldap.conditions',
+                        'ldap.filter_any', 'ldap.filter_both', 'ldap.filter_prefix', 'ldap.filter_suffix',
+                        'ldap.filter_exact'
+                    );
+                }
+            }
         }
 
         $result['required'] = empty($field['optional']);
@@ -1260,7 +1270,8 @@ class kolab_client_task
         $this->output->set_env('assoc_fields', $assoc_fields);
         $this->output->set_env('required_fields', $req_fields);
         $this->output->add_translation('form.required.empty', 'form.maxcount.exceeded',
-            $name . '.add.success', $name . '.edit.success', $name . '.delete.success');
+            $name . '.add.success', $name . '.edit.success', $name . '.delete.success',
+            'add', 'edit', 'delete');
 
         return $form;
     }
diff --git a/lib/locale/en_US.php b/lib/locale/en_US.php
index 7a4b58f..d8911ab 100644
--- a/lib/locale/en_US.php
+++ b/lib/locale/en_US.php
@@ -7,6 +7,8 @@ $LANG['about.support'] = 'Professional support is available from <a href="http:/
 $LANG['about.technology'] = 'Technology';
 $LANG['about.warranty'] = 'It comes with absolutely <b>no warranties</b> and is typically run entirely self supported. You can find help & information on the community <a href="http://kolab.org">web site</a> & <a href="http://wiki.kolab.org">wiki</a>.';
 
+$LANG['add'] = 'Add';
+
 $LANG['attribute.add'] = 'Add attribute';
 $LANG['attribute.static'] = 'Static value';
 $LANG['attribute.name'] = 'Attribute';
@@ -31,6 +33,7 @@ $LANG['button.submit'] = 'Submit';
 $LANG['creatorsname'] = 'Created by';
 $LANG['days'] = 'days';
 $LANG['debug'] = 'Debug info';
+$LANG['delete'] = 'Delete';
 $LANG['deleting'] = 'Deleting data...';
 
 $LANG['domain.add'] = 'Add Domain';
@@ -47,6 +50,7 @@ $LANG['domain.other'] = 'Other';
 $LANG['domain.system'] = 'System';
 $LANG['domain.type_id'] = 'Standard Domain';
 
+$LANG['edit'] = 'Edit';
 $LANG['error'] = 'Error';
 
 $LANG['form.required.empty'] = 'Some of the required fields are empty!';
@@ -61,6 +65,7 @@ $LANG['group.gidnumber'] = 'Primary group number';
 $LANG['group.list'] = 'Groups List';
 $LANG['group.mail'] = 'Primary Email Address';
 $LANG['group.member'] = 'Member(s)';
+$LANG['group.memberurl'] = 'Members URL';
 $LANG['group.norecords'] = 'No group records found!';
 $LANG['group.other'] = 'Other';
 $LANG['group.system'] = 'System';
@@ -70,6 +75,19 @@ $LANG['group.uniquemember'] = 'Members';
 $LANG['info'] = 'Information';
 $LANG['internalerror'] = 'Internal system error!';
 
+$LANG['ldap.one'] = 'one: all entries one level under the base DN';
+$LANG['ldap.sub'] = 'sub: whole subtree starting with the base DN';
+$LANG['ldap.base'] = 'base: base DN only';
+$LANG['ldap.basedn'] = 'Base DN';
+$LANG['ldap.host'] = 'LDAP Server';
+$LANG['ldap.conditions'] = 'Conditions';
+$LANG['ldap.scope'] = 'Scope';
+$LANG['ldap.filter_any'] = 'is non-empty';
+$LANG['ldap.filter_both'] = 'contains';
+$LANG['ldap.filter_prefix'] = 'starts with';
+$LANG['ldap.filter_suffix'] = 'ends with';
+$LANG['ldap.filter_exact'] = 'is equal to';
+
 $LANG['list.records'] = '$1 to $2 of $3';
 
 $LANG['loading'] = 'Loading...';
diff --git a/public_html/js/kolab_admin.js b/public_html/js/kolab_admin.js
index b7fff8a..4f79b5f 100644
--- a/public_html/js/kolab_admin.js
+++ b/public_html/js/kolab_admin.js
@@ -284,7 +284,7 @@ function kolab_admin()
 
     return $.ajax({
       type: 'POST', url: url, data: postdata, dataType: 'json',
-      success: function(data) { kadm.http_response(data); },
+      success: function(response) { kadm.http_response(response); },
       error: function(o, status, err) { kadm.http_error(o, status, err); }
     });
   };
@@ -300,7 +300,8 @@ function kolab_admin()
 
     return $.ajax({
       type: 'POST', url: url, data: JSON.stringify(postdata), dataType: 'json',
-      success: function(data) { kadm[func](data); },
+      contentType: 'application/json; charset=utf-8',
+      success: function(response) { kadm[func](response); },
       error: function(o, status, err) { kadm.http_error(o, status, err); }
     });
   };
@@ -317,15 +318,15 @@ function kolab_admin()
     if (response.env)
       this.set_env(response.env);
 
+    // we have translation labels to add
+    if (typeof response.labels === 'object')
+      this.tdef(response.labels);
+
     // HTML page elements
     if (response.objects)
       for (i in response.objects)
         $('#'+i).html(response.objects[i]);
 
-    // we have translation labels to add
-    if (typeof response.labels === 'object')
-      this.tdef(response.labels);
-
     this.update_request_time();
     this.set_busy(false);
 
@@ -727,6 +728,9 @@ function kolab_admin()
     // create smart select fields
     $('input[data-type="select"]', form)
       .each(function() { kadm.form_select_element_wrapper(this); });
+    // create LDAP URL fields
+    $('input[data-type="ldap_url"]:not(:disabled):not([readonly])', form)
+      .each(function() { kadm.form_url_element_wrapper(this); });
   };
 
   // Form serialization
@@ -735,7 +739,7 @@ function kolab_admin()
     var form = $(data.id);
 
     // smart list fields
-    $('textarea[data-type="list"]', form).not('disabled').each(function() {
+    $('textarea[data-type="list"]:not(:disabled)', form).each(function() {
       var i, v, value = [],
         re = RegExp('^' + RegExp.escape(this.name) + '\[[0-9-]+\]$');
 
@@ -762,6 +766,11 @@ function kolab_admin()
       delete data.json[this.name];
     });
 
+    // LDAP URL fields
+    $('input[data-type="ldap_url"]:not(:disabled):not([readonly])', form).each(function() {
+      data.json = kadm.form_url_element_submit(this.name, data.json, form);
+    });
+
     return data;
   };
 
@@ -984,7 +993,7 @@ function kolab_admin()
       form = form_element.form,
       elem = $('<span class="link"></span>'),
       area = $('<span class="listarea autocomplete select popup"></span>'),
-      content = $('<span class="listcontent"></span>');
+      content = $('<span class="listcontent"></span>'),
       list = this.env.assoc_fields ? this.env.assoc_fields[form_element.name] : [];
 
     elem.text(e.val()).css({cursor: 'pointer'})
@@ -1118,6 +1127,125 @@ function kolab_admin()
     return elem;
   };
 
+  // Replaces form element with LDAP URL element
+  this.form_url_element_wrapper = function(form_element)
+  {
+    var i, e = $(form_element),
+      form = form_element.form,
+      name = form_element.name,
+      ldap = this.parse_ldap_url(e.val()) || {},
+      options = ['sub', 'one', 'base'],
+      div = $('<div class="ldap_url"><table></table></div>'),
+      host = $('<input type="text">').attr({name: 'ldap_host_'+name, size: 30, value: ldap.host, 'class': 'ldap_host'}),
+      port = $('<input type="text">').attr({name: 'ldap_port_'+name, size: 5, value: ldap.port || '389'}),
+      base = $('<input type="text">').attr({name: 'ldap_base_'+name, value: ldap.base}),
+      scope = $('<select>').attr({name: 'ldap_scope_'+name}),
+      filter = $('<ul>'),
+      row_host = $('<tr class="ldap_host"><td></td><td></td></tr>'),
+      row_base = $('<tr class="ldap_base"><td></td><td></td></tr>'),
+      row_scope = $('<tr class="ldap_scope"><td></td><td></td></tr>'),
+      row_filter = $('<tr class="ldap_filter"><td></td><td></td></tr>');
+
+    for (i in options)
+      $('<option>').val(options[i]).text(this.t('ldap.'+options[i])).appendTo(scope);
+    scope.val(ldap.scope);
+
+    for (i in ldap.filter)
+      filter.append(this.form_url_filter_element(name, i, ldap.filter[i]));
+    if (!$('li', filter).length)
+      filter.append(this.form_url_filter_element(name, 0, {}));
+
+    e.hide();
+
+    $('td:first', row_host).text(this.t('ldap.host'));
+    $('td:last', row_host).append(host).append($('<span>').text(':')).append(port);
+    $('td:first', row_base).text(this.t('ldap.basedn'));
+    $('td:last', row_base).append(base);
+    $('td:first', row_scope).text(this.t('ldap.scope'));
+    $('td:last', row_scope).append(scope);
+    $('td:first', row_filter).text(this.t('ldap.conditions'));
+    $('td:last', row_filter).append(filter);
+    $('table', div).append(row_host).append(row_base).append(row_scope).append(row_filter);
+    $(form_element).parent().append(div);
+  };
+
+  this.form_url_filter_element = function(name, idx, filter)
+  {
+    var options = ['any', 'both', 'prefix', 'suffix', 'exact'],
+      filter_type = $('<select>').attr({name: 'ldap_filter_type_'+name+'['+idx+']'}),
+      filter_name = $('<input type="text">').attr({name: 'ldap_filter_name_'+name+'['+idx+']'}),
+      filter_value = $('<input type="text">').attr({name: 'ldap_filter_value_'+name+'['+idx+']'}),
+      a_add = $('<a class="button add" href="#add"></a>').click(function() {
+        var dt = new Date().getTime();
+        $(this.parentNode.parentNode).append(kadm.form_url_filter_element(name, dt, {}));
+      }).attr({title: this.t('add')}),
+      a_del = $('<a class="button delete" href="#delete"></a>').click(function() {
+        if ($('li', this.parentNode.parentNode).length > 1)
+          $(this.parentNode).remove();
+        else {
+          $('input', this.parentNode).val('');
+          $('select', this.parentNode).val('any').change();
+        }
+      }).attr({title: this.t('delete')}),
+      li = $('<li>');
+
+    for (i in options)
+      $('<option>').val(options[i]).text(this.t('ldap.filter_'+options[i])).appendTo(filter_type);
+
+    if (filter.type)
+      filter_type.val(filter.type);
+    if (filter.name)
+      filter_name.val(filter.name);
+    if (filter.value)
+      filter_value.val(filter.value);
+
+    filter_type.change(function() {
+      filter_value.css({display: $(this).val() == 'any' ? 'none' : 'inline'});
+    }).change();
+
+    return li.append(filter_name).append(filter_type).append(filter_value)
+      .append(a_del).append(a_add);
+  };
+
+  // updates form data with LDAP URL (on form submit)
+  this.form_url_element_submit = function(name, data, form)
+  {
+    var i, rx = new RegExp('^ldap_(host|port|base|scope|filter_name|filter_type|filter_value)_'+name+'(\\[|$)');
+
+    for (i in data)
+      if (rx.test(i))
+        delete data[i];
+
+    data[name] = this.form_url_element_save(name, form);
+
+    return data;
+  };
+
+  // updates LDAP URL field
+  this.form_url_element_save = function(name, form)
+  {
+    var url, form = $(form), params = {
+      host: $('input[name="ldap_host_'+name+'"]', form).val(),
+      port: $('input[name="ldap_port_'+name+'"]', form).val(),
+      base: $('input[name="ldap_base_'+name+'"]', form).val(),
+      scope: $('select[name="ldap_scope_'+name+'"]', form).val(),
+      filter: []};
+
+    $('input[name^="ldap_filter_name_'+name+'"]', form).each(function() {
+      if (this.value && /\[([^\]]+)\]/.test(this.name)) {
+        var suffix = name + '[' + RegExp.$1 + ']',
+          type = $('select[name="ldap_filter_type_'+suffix+'"]', form).val(),
+          value = type == 'any' ? '' : $('input[name="ldap_filter_value_'+suffix+'"]', form).val();
+
+        params.filter.push({name: this.value, type: type, value: value, join: 'AND', level: 0});
+      }
+    });
+
+    url = this.build_ldap_url(params);
+    $('input[name="'+name+'"]').val(url);
+
+    return url;
+  };
 
   /*********************************************************/
   /*********                 Forms                 *********/
@@ -1916,6 +2044,150 @@ function kolab_admin()
     $('input[name="' + f + '2"]').val(pass);
   };
 
+  // LDAP URL parser
+  this.parse_ldap_url = function(url)
+  {
+    var result = {},
+      url_parser = /^([^:]+):\/\/([^\/]*)\/([^?]*)\?([^?]*)\?([^?]*)\?(.*)$/;
+      matches = url_parser.exec(url);
+
+    if (matches && matches[1])
+      return {
+        scheme: matches[1],
+        host: matches[2],
+        base: matches[3],
+        attrs: matches[4],
+        scope: matches[5],
+        filter: this.parse_ldap_filter(matches[6])
+      };
+  };
+
+  // LDAP filter parser
+  this.parse_ldap_filter = function(filter)
+  {
+    var chr, next, elem, pos = 0, join, level = -1, res = [],
+      len = filter ? filter.length : 0;
+
+    if (!filter.match(/^\(.*\)$/))
+      filter = '(' + filter + ')';
+
+    while (len > pos) {
+      chr = filter.charAt(pos);
+      if (chr == '&') {
+          join = 'AND';
+          level++;
+      }
+      else if (chr == '|') {
+          join = 'OR';
+          level++;
+      }
+      else if (chr == '(') {
+        next = filter.charAt(pos+1);
+        if (next != '&' && next != '|') {
+          next = filter.indexOf(')', pos);
+          if (next > 0) {
+            if (elem = this.parse_ldap_filter_entry(filter.substr(pos + 1, next - pos - 1))) {
+              elem.join = join;
+              elem.level = level;
+              res.push(elem);
+            }
+            pos = next;
+          }
+        }
+      }
+      else if (chr == ')')
+        level--;
+
+      pos++;
+    }
+
+    return res;
+  };
+
+  // LDAP filter-entry parser
+  this.parse_ldap_filter_entry = function(entry)
+  {
+    var type = 'exact', name, value;
+
+    if (entry.match(/^([a-zA-Z0-9_-]+)=(.*)$/)) {
+      name = RegExp.$1;
+      value = RegExp.$2;
+
+      if (value == '*') {
+        value = ''
+        type = 'any';
+      }
+      else if (value.match(/^\*(.+)\*$/)) {
+        value = RegExp.$1;
+        type = 'both';
+      }
+      else if (value.match(/^\*(.+)$/)) {
+        value = RegExp.$1;
+        type = 'suffix';
+      }
+      else if (value.match(/^(.+)\*$/)) {
+        value = RegExp.$1;
+        type = 'prefix';
+      }
+
+      return {name: name, type: type, value: value};
+    }
+  };
+
+  // Builds LDAP filter string from the defined structure
+  this.build_ldap_filter = function(filter)
+  {
+    var i, elem, str = '', last = -1, join = {'AND': '&', 'OR': '|'};
+
+    for (i=0; i<filter.length; i++) {
+      elem = filter[i];
+      if (elem.level > last)
+        str += (elem.join && filter.length > 1 ? join[elem.join] : '');
+      else if (elem.level < last)
+        str += ')';
+
+      str += '(' + elem.name + '=';
+
+      if (elem.type == 'any')
+        str += '*';
+      else if (elem.type == 'both')
+        str += '*' + elem.value + '*';
+      else if (elem.type == 'prefix')
+        str += elem.value + '*';
+      else if (elem.type == 'suffix')
+        str += '*' + elem.value;
+      else
+        str += elem.value;
+
+      str += ')';
+      last = elem.level;
+    }
+
+    if (filter.length > 1)
+      str = '(' + str + ')';
+
+    return str;
+  };
+
+  // Builds LDAP URL string from the defined structure
+  this.build_ldap_url = function(params)
+  {
+    var url = '';
+
+    if (!params.filter.length && !params.base)
+      return url;
+
+    url += (params.scheme ? params.scheme : 'ldap') + '://';
+    url += (params.host ? params.host + (params.port && params.port != 389 ? ':'+params.port : '') : '') + '/';
+    url += (params.base ? params.base : '') + '?';
+    url += (params.attrs ? params.attrs : '') + '?';
+    url += (params.scope ? params.scope : '') + '?';
+    if (params.filter)
+      url += this.build_ldap_filter(params.filter);
+
+    return url;
+  };
+
 };
 
 // Add escape() method to RegExp object
diff --git a/public_html/skins/default/style.css b/public_html/skins/default/style.css
index b4fa8e4..87a0a2e 100644
--- a/public_html/skins/default/style.css
+++ b/public_html/skins/default/style.css
@@ -150,6 +150,9 @@ td.label {
 #topmenu > span {
   color: #aaa;
   font-size: 11px;
+  padding-top: 2px;
+  display: inline-block;
+  height: 18px;
 }
 
 #navigation {
@@ -255,21 +258,21 @@ td.label {
 
 #topmenu .logout {
   background: url(images/buttons.png) -100px -1px no-repeat;
-  padding: 0 0 0 20px;
-  margin: 0 10px;
+  padding-left: 20px;
+  margin-right: 10px;
   color: white;
 }
 
 #topmenu .login {
   padding-left: 20px;
   margin-right: 20px;
-  background: url(images/user_ico.png) 0 1px no-repeat;
+  background: url(images/user_ico.png) 0 2px no-repeat;
 }
 
 #topmenu .domain {
   padding-left: 20px;
   margin-right: 10px;
-  background: url(images/domain_ico.png) 0 2px no-repeat;
+  background: url(images/domain_ico.png) 0 3px no-repeat;
 }
 
 #navigation ul {
@@ -564,6 +567,10 @@ a.button.edit {
   background-position: -81px 0;
 }
 
+a.button.add {
+  background-position: -41px 0;
+}
+
 a.button.delete {
   background-position: -1px 0;
 }
@@ -734,6 +741,59 @@ span.form_error {
   padding-left: 5px;
 }
 
+.ldap_url {
+  background-color: #F5F5F5;
+  border: 1px solid #D0D0D0;
+  border-radius: 3px 3px 3px 3px;
+}
+
+.ldap_url td, li {
+  white-space: nowrap;
+}
+
+.ldap_url ul {
+  list-style-type: none;
+  padding: 0;
+  margin: 0;
+}
+
+.ldap_url ul li {
+  padding: 0;
+  margin: 0;
+}
+
+.ldap_url ul li input {
+  width: 120px;
+  margin-right: 5px;
+}
+
+.ldap_url tr span {
+  padding: 0 5px;
+}
+
+.ldap_url tr.ldap_base td input,
+.ldap_url tr.ldap_scope td select {
+  width: 368px;
+}
+
+.ldap_url tr.ldap_host td input.ldap_host {
+  width: 290px;
+}
+
+table.form tr.required .ldap_url input,
+table.form tr.required .ldap_url select {
+  background-color: white;
+}
+
+table.form tr.required .ldap_url {
+  background-color: #F0F9FF;
+}
+
+.ldap_url a.button {
+  vertical-align: middle;
+  height: 22px;
+}
+
 /*****   autocomplete list   *****/
 
 #autocompletepane


commit 6d78ad95518d24b21292450d36726cb89dba14aa
Author: Aleksander Machniak <alec at alec.pl>
Date:   Tue Oct 16 12:01:38 2012 +0200

    jQuery 1.8.2

diff --git a/public_html/js/jquery.min.js b/public_html/js/jquery.min.js
index 16ad06c..bc3fbc8 100644
--- a/public_html/js/jquery.min.js
+++ b/public_html/js/jquery.min.js
@@ -1,4 +1,2 @@
-/*! jQuery v1.7.2 jquery.com | jquery.org/license */
-(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"<!doctype html>":"")+"<html><body>"),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;
 for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function ca(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function b_(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bD.test(a)?d(a,e):b_(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&f.type(b)==="object")for(var e in b)b_(a+"["+e
 +"]",b[e],c,d);else d(a,b)}function b$(a,c){var d,e,g=f.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((g[d]?a:e||(e={}))[d]=c[d]);e&&f.extend(!0,a,e)}function bZ(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bS,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=bZ(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=bZ(a,c,d,e,"*",g));return l}function bY(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bO),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bB(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?1:0,g=4;if(d>0){if(c!=="border")for(;e<g;e+=2)c||(d-=parseFloat(f.css(a,"padding"+bx[e]))||0),c==="margin"?d+=parseFloat(f.css(a,c+bx[e]))||0:d-=parseFloat(f.css(a,"border"+bx[e]+"Width"))||0;return d+"px"}d=by(a,b);if(d<0||d==null)d=a.style[b];
 if(bt.test(d))return d;d=parseFloat(d)||0;if(c)for(;e<g;e+=2)d+=parseFloat(f.css(a,"padding"+bx[e]))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+bx[e]+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+bx[e]))||0);return d+"px"}function bo(a){var b=c.createElement("div");bh.appendChild(b),b.innerHTML=a.outerHTML;return b.firstChild}function bn(a){var b=(a.nodeName||"").toLowerCase();b==="input"?bm(a):b!=="script"&&typeof a.getElementsByTagName!="undefined"&&f.grep(a.getElementsByTagName("input"),bm)}function bm(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bl(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bk(a,b){var c;b.nodeType===1&&(b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase(),c==="object"?b.outerHTML=a.outerHTML:c!=="input"||a.type!=="checkbox"&&a.type!=="radio"?
 c==="option"?b.selected=a.defaultSelected:c==="input"||c==="textarea"?b.defaultValue=a.defaultValue:c==="script"&&b.text!==a.text&&(b.text=a.text):(a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value)),b.removeAttribute(f.expando),b.removeAttribute("_submit_attached"),b.removeAttribute("_change_attached"))}function bj(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c,d,e,g=f._data(a),h=f._data(b,g),i=g.events;if(i){delete h.handle,h.events={};for(c in i)for(d=0,e=i[c].length;d<e;d++)f.event.add(b,c,i[c][d])}h.data&&(h.data=f.extend({},h.data))}}function bi(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function U(a){var b=V.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function T(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a
 ,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(O.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(
 c=0,d=a.length;c<d;c++)b[a[c]]=!0;return b}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return th
 is;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArra
 y:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=a
 rguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.a
 ttachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(
 q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:func
 tion(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b,c){var d;if(b){if(H)return H.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){v
 ar d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h,i){var j,k=d==null,l=0,m=a.length;if(d&&typeof d=="object"){for(l in d)e.access(a,c,l,d[l],1,h,f);g=1}else if(f!==b){j=i===b&&e.isFunction(f),k&&(j?(j=c,c=function(a,b,c){return j.call(e(a),c)}):(c.call(a,f),c=null));if(c)for(;l<m;l++)c(a[l],d,j?f.call(a[l],l,c(a[l],d)):f,i);g=1}return g?a:k?c.call(a):m?c(a[0],d):h},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b
 )},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g={};f.Callbacks=function(a){a=a?g[a]||h(a):{};var c=[],d=[],e,i,j,k,l,m,n=function(b){var d,e,g,h,i;for(d=0,e=b.length;d<e;d++)g=b[d],h=f.type(g),h==="array"?n(g):h==="function"&&(!a.unique||!p.has(g))&&c.push(g)},o=function(b,f){f=f||[],e=!a.memory||[b,f],i=!0,j=!0,m=k||0,k=0,l=c.length;for(;c&&m<l;m++)if(c[m].apply(b,f)===!1&&a.stopOnFalse){e=!0;break}j=!1,c&&(a.once?e===!0?p.disable():c=[]:d&&d.length&&(e=d.s
 hift(),p.fireWith(e[0],e[1])))},p={add:function(){if(c){var a=c.length;n(arguments),j?l=c.length:e&&e!==!0&&(k=a,o(e[0],e[1]))}return this},remove:function(){if(c){var b=arguments,d=0,e=b.length;for(;d<e;d++)for(var f=0;f<c.length;f++)if(b[d]===c[f]){j&&f<=l&&(l--,f<=m&&m--),c.splice(f--,1);if(a.unique)break}}return this},has:function(a){if(c){var b=0,d=c.length;for(;b<d;b++)if(a===c[b])return!0}return!1},empty:function(){c=[];return this},disable:function(){c=d=e=b;return this},disabled:function(){return!c},lock:function(){d=b,(!e||e===!0)&&p.disable();return this},locked:function(){return!d},fireWith:function(b,c){d&&(j?a.once||d.push([b,c]):(!a.once||!e)&&o(b,c));return this},fire:function(){p.fireWith(this,arguments);return this},fired:function(){return!!i}};return p};var i=[].slice;f.extend({Deferred:function(a){var b=f.Callbacks("once memory"),c=f.Callbacks("once memory"),d=f.Callbacks("memory"),e="pending",g={resolve:b,reject:c,notify:d},h={done:b.add,fail:c.add,progr
 ess:d.add,state:function(){return e},isResolved:b.fired,isRejected:c.fired,then:function(a,b,c){i.done(a).fail(b).progress(c);return this},always:function(){i.done.apply(i,arguments).fail.apply(i,arguments);return this},pipe:function(a,b,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[b,"reject"],progress:[c,"notify"]},function(a,b){var c=b[0],e=b[1],g;f.isFunction(c)?i[a](function(){g=c.apply(this,arguments),g&&f.isFunction(g.promise)?g.promise().then(d.resolve,d.reject,d.notify):d[e+"With"](this===i?d:this,[g])}):i[a](d[e])})}).promise()},promise:function(a){if(a==null)a=h;else for(var b in h)a[b]=h[b];return a}},i=h.promise({}),j;for(j in g)i[j]=g[j].fire,i[j+"With"]=g[j].fireWith;i.done(function(){e="resolved"},c.disable,d.lock).fail(function(){e="rejected"},b.disable,d.lock),a&&a.call(i,i);return i},when:function(a){function m(a){return function(b){e[a]=arguments.length>1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=ar
 guments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c<d;c++)b[c]&&b[c].promise&&f.isFunction(b[c].promise)?b[c].promise().then(l(c),j.reject,m(c)):--g;g||j.resolveWith(j,b)}else j!==a&&j.resolveWith(j,d?[a]:[]);return k}}),f.support=function(){var b,d,e,g,h,i,j,k,l,m,n,o,p=c.createElement("div"),q=c.documentElement;p.setAttribute("className","t"),p.innerHTML="   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.ge
 tAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","
 t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="<div "+n+"display:block;'><div style='"+t+"0;display:block;overflow:hidden;'></div></div>"+"<table "+n+"' cellpadding='0' cellspacing='0'>"+"<tr><td></td></tr></table>",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="<table><tr><
 td style='"+t+"0;display:none'></td><td>t</td></tr></table>",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="<div style='width:5px;'></div>",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.
 style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].dat
 a)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e<g;e++)delete d[b[e]];if(!(c?m:f.isEmptyObject)(d))return}}if(!c){delete j[k].data;if(!m(j[k]))return}f.support.deleteExpando||!j.setInterval?delete j[k]:j[k]=null,i&&(f.support.deleteExpando?delete a[h]:a.removeAttribute?a.removeAttribute(h):a[h]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return
  b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d,e,g,h,i,j=this[0],k=0,m=null;if(a===b){if(this.length){m=f.data(j);if(j.nodeType===1&&!f._data(j,"parsedAttrs")){g=j.attributes;for(i=g.length;k<i;k++)h=g[k].name,h.indexOf("data-")===0&&(h=f.camelCase(h.substring(5)),l(j,h,m[h]));f._data(j,"parsedAttrs",!0)}}return m}if(typeof a=="object")return this.each(function(){f.data(this,a)});d=a.split(".",2),d[1]=d[1]?"."+d[1]:"",e=d[1]+"!";return f.access(this,function(c){if(c===b){m=this.triggerHandler("getData"+e,[d[0]]),m===b&&j&&(m=f.data(j,a),m=l(j,a,m));return m===b&&d[1]?this.data(d[0]):m}d[1]=c,this.each(function(){var b=f(this);b.triggerHandler("setData"+e,d),f.data(this,a,c),b.triggerHandler("changeData"+e,d)})},null,c,arguments.length>1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a
 !==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length<d)return f.queue(this[0],a);return c===b?this:this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(
 ){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f.Callbacks("once memory"),!0))h++,l.add(m);m();return d.promise(c)}});var o=/[\n\t\r]/g,p=/\s+/,q=/\r/g,r=/^(?:button|input)$/i,s=/^(?:button|input|object|select|textarea)$/i,t=/^a(?:rea)?$/i,u=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,v=f.support.getSetAttribute,w,x,y;f.fn.extend({attr:function(a,b){return f.access(this,f.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.
 each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(p);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(o," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.e
 ach(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(p);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(o," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.val
 Hooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c<d;c++){e=i[c];if(e.selected&&(f.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!f.nodeName(e.parentNode,"optgroup"))){b=f(e).val();if(j)return b;h.push(b)}}if(j&&!h.length&&i.length)return f(i[g]).val();return h},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!
 ==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i<g;i++)e=d[i],e&&(c=f.propFix[e]||e,h=u.test(e),h||f.attr(a,e,""),a.removeAttribute(v?e:c),h&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(r.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(w&&f.nodeName(a,"button"))return w.get(a,b);return b in a?a.value:null},set:fu
 nction(a,b,c){if(w&&f.nodeName(a,"button"))return w.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,g,h,i=a.nodeType;if(!!a&&i!==3&&i!==8&&i!==2){h=i!==1||!f.isXMLDoc(a),h&&(c=f.propFix[c]||c,g=f.propHooks[c]);return d!==b?g&&"set"in g&&(e=g.set(a,d,c))!==b?e:a[c]=d:g&&"get"in g&&(e=g.get(a,c))!==null?e:a[c]}},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):s.test(a.nodeName)||t.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabindex=f.propHooks.tabIndex,x={get:function(a,c){var d,e=f.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a
 ,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},v||(y={name:!0,id:!0,coords:!0},w=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&(y[c]?d.nodeValue!=="":d.specified)?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.attrHooks.tabindex.set=w.set,f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})}),f.attrHooks.contenteditable={get:w.get,set:function(a,b,c){b===""&&(b="false"),w.set(a,b,c)}}),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.op
 tSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.enctype||(f.propFix.enctype="encoding"),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(
-a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]||{},m=(g?s.delegateType:s.bindType)||m,s=f.event.special[m]||{},o=f.extend({type:m,origType:l[1],data:e,handler:d,gu
 id:d.guid,selector:g,quick:g&&G(g),namespace:n.join(".")},p),r=j[m];if(!r){r=j[m]=[],r.delegateCount=0;if(!s.setup||s.setup.call(a,e,n,i)===!1)a.addEventListener?a.addEventListener(m,i,!1):a.attachEvent&&a.attachEvent("on"+m,i)}s.add&&(s.add.call(a,o),o.handler.guid||(o.handler.guid=d.guid)),g?r.splice(r.delegateCount++,0,o):r.push(o),f.event.global[m]=!0}a=null}},global:{},remove:function(a,b,c,d,e){var g=f.hasData(a)&&f._data(a),h,i,j,k,l,m,n,o,p,q,r,s;if(!!g&&!!(o=g.events)){b=f.trim(I(b||"")).split(" ");for(h=0;h<b.length;h++){i=A.exec(b[h])||[],j=k=i[1],l=i[2];if(!j){for(j in o)f.event.remove(a,j+b[h],c,d,!0);continue}p=f.event.special[j]||{},j=(d?p.delegateType:p.bindType)||j,r=o[j]||[],m=r.length,l=l?new RegExp("(^|\\.)"+l.split(".").sort().join("\\.(?:.*\\.)?")+"(\\.|$)"):null;for(n=0;n<r.length;n++)s=r[n],(e||k===s.origType)&&(!c||c.guid===s.guid)&&(!l||l.test(s.namespace))&&(!d||d===s.selector||d==="**"&&s.selector)&&(r.splice(n--,1),s.selector&&r.delegateCount--,p
 .remove&&p.remove.call(a,s));r.length===0&&m!==r.length&&((!p.teardown||p.teardown.call(a,l)===!1)&&f.removeEvent(a,j,g.handle),delete o[j])}f.isEmptyObject(o)&&(q=g.handle,q&&(q.elem=null),f.removeData(a,["events","handle"],!0))}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){if(!e||e.nodeType!==3&&e.nodeType!==8){var h=c.type||c,i=[],j,k,l,m,n,o,p,q,r,s;if(E.test(h+f.event.triggered))return;h.indexOf("!")>=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArr
 ay(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;l<r.length&&!c.isPropagationStopped();l++)m=r[l][0],c.type=r[l][1],q=(f._data(m,"events")||{})[c.type]&&f._data(m,"handle"),q&&q.apply(m,d),q=o&&m[o],q&&f.acceptData(m)&&q.apply(m,d)===!1&&c.preventDefault();c.type=h,!g&&!c.isDefaultPrevented()&&(!p._default||p._default.apply(e.ownerDocument,d)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)&&o&&e[h]&&(h!=="focus"&&h!=="blur"||c.target.offsetWidth!==0)&&!f.isWindow(e)&&(n=e[o],n&&(e[o]=null),f.event.triggered=h,e[h](),f.event.triggered=b,n&&(e[o]=n));return c.result}},dispatch:function(c){c=f.event.fix(c||a.event);var d=(f._data(this,"events")||{})[c.type]||[],e=d.delegateCount,g=[].slice.call(ar
 guments,0),h=!c.exclusive&&!c.namespace,i=f.event.special[c.type]||{},j=[],k,l,m,n,o,p,q,r,s,t,u;g[0]=c,c.delegateTarget=this;if(!i.preDispatch||i.preDispatch.call(this,c)!==!1){if(e&&(!c.button||c.type!=="click")){n=f(this),n.context=this.ownerDocument||this;for(m=c.target;m!=this;m=m.parentNode||this)if(m.disabled!==!0){p={},r=[],n[0]=m;for(k=0;k<e;k++)s=d[k],t=s.selector,p[t]===b&&(p[t]=s.quick?H(m,s.quick):n.is(t)),p[t]&&r.push(s);r.length&&j.push({elem:m,matches:r})}}d.length>e&&j.push({elem:this,matches:d.slice(e)});for(k=0;k<j.length&&!c.isPropagationStopped();k++){q=j[k],c.currentTarget=q.elem;for(l=0;l<q.matches.length&&!c.isImmediatePropagationStopped();l++){s=q.matches[l];if(h||!c.namespace&&!s.namespace||c.namespace_re&&c.namespace_re.test(s.namespace))c.data=s.data,c.handleObj=s,o=((f.event.special[s.origType]||{}).handle||s.handler).apply(q.elem,g),o!==b&&(c.result=o,o===!1&&(c.preventDefault(),c.stopPropagation()))}}i.postDispatch&&i.postDispatch.call(this,c);
 return c.result}},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode);return a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,d){var e,f,g,h=d.button,i=d.fromElement;a.pageX==null&&d.clientX!=null&&(e=a.target.ownerDocument||c,f=e.documentElement,g=e.body,a.pageX=d.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=d.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?d.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0);return a}},fix:function(a){if(a[f.expando])return a;var
  d,e,g=a,h=f.event.fixHooks[a.type]||{},i=h.props?this.props.concat(h.props):this.props;a=f.Event(g);for(d=i.length;d;)e=i[--d],a[e]=g[e];a.target||(a.target=g.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey===b&&(a.metaKey=a.ctrlKey);return h.filter?h.filter(a,g):a},special:{ready:{setup:f.bindReady},load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=f.extend(new f.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?f.event.trigger(e,null,b):f.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},f.event.handle=f.event.dispatch,f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(
 !(this instanceof f.Event))return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?K:J):this.type=a,b&&f.extend(this,b),this.timeStamp=a&&a.timeStamp||f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=K;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=K;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=K,this.stopPropagation()},isDefaultPrevented:J,isPropagationStopped:J,isImmediatePropagationStopped:J},f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c=this,d=a.relatedTarget,e=a.handleObj,g=e.selector,h;if(!d||d!==c&&!f.cont
 ains(c,d))a.type=e.origType,h=e.handler.apply(this,arguments),a.type=b;return h}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(){if(f.nodeName(this,"form"))return!1;f.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=f.nodeName(c,"input")||f.nodeName(c,"button")?c.form:b;d&&!d._submit_attached&&(f.event.add(d,"submit._submit",function(a){a._submit_bubble=!0}),d._submit_attached=!0)})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&f.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){if(f.nodeName(this,"form"))return!1;f.event.remove(this,"._submit")}}),f.support.changeBubbles||(f.event.special.change={setup:function(){if(z.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")f.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),f.event.add(this,"click._change",function(a){this.
 _just_changed&&!a.isTrigger&&(this._just_changed=!1,f.event.simulate("change",this,a,!0))});return!1}f.event.add(this,"beforeactivate._change",function(a){var b=a.target;z.test(b.nodeName)&&!b._change_attached&&(f.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&f.event.simulate("change",this.parentNode,a,!0)}),b._change_attached=!0)})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){f.event.remove(this,"._change");return z.test(this.nodeName)}}),f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){var d=0,e=function(a){f.event.simulate(b,a.target,f.event.fix(a),!0)};f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.fn.extend({on:function(a,c,d,e,g){var h,i;if(typeof a=="object"){typeof c!
 ="string"&&(d=d||c,c=b);for(i in a)this.on(i,c,d,a[i],g);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=J;else if(!e)return this;g===1&&(h=e,e=function(a){f().off(a);return h.apply(this,arguments)},e.guid=h.guid||(h.guid=f.guid++));return this.each(function(){f.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,c,d){if(a&&a.preventDefault&&a.handleObj){var e=a.handleObj;f(a.delegateTarget).off(e.namespace?e.origType+"."+e.namespace:e.origType,e.selector,e.handler);return this}if(typeof a=="object"){for(var g in a)this.off(g,c,a[g]);return this}if(c===!1||typeof c=="function")d=c,c=b;d===!1&&(d=J);return this.each(function(){f.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){f(this.context).on(a,this.selector,b,c);return this},die:function(a,b){f(this.context).off(a,this.selector||"
 **",b);return this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a,c)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f._data(this,"lastToggle"+a.guid)||0)%d;f._data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.on(b,null,a,c):thi
 s.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}if(j.nodeType===1){g||(j[d]=c,j.sizset=h);if(typeof b!="string"){if(j===b){k=!0;break}}else if(m.filter(b,[j]).length>0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}j.nodeType===1&&!g&&(j[d]=c,j.sizset=h);if(j.nodeName.toLowerCase()===b){k=j;break}j=j[a]}e[h]=k}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[]
 ,d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]
 !=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},m.matches=function(a,b){return m(a,null,null,b)},m.matchesSelector=function(a,b){return m(b,null,null,[a]).length>0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e<f;e++){h=o.order[e];if(g=o.leftMatch[h].exec(a)){i=g[1],g.splice(1,1);if(i.substr(i.length-1)!=="\\"){g[1]=(g[1]||"").replace(j,""),d=o.find[h](g,b,c);if(d!=null){a=a.replace(o.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},m.filter=function(a,c,d,e){var f,g,h,i,j,k,l,n,p,q=a,r=[],s=c,t=c&&c[0]&&m.isXML(c[0]);while(a&&c.length){for(h in o.filter)if((f=o.leftMatch[h].exec(a))!=null&&f[2]){k=
 o.filter[h],l=f[1],g=!1,f.splice(1,1);if(l.substr(l.length-1)==="\\")continue;s===r&&(r=[]);if(o.preFilter[h]){f=o.preFilter[h](f,s,d,r,e,t);if(!f)g=i=!0;else if(f===!0)continue}if(f)for(n=0;(j=s[n])!=null;n++)j&&(i=k(j,f,n,s),p=e^i,d&&i!=null?p?g=!0:s[n]=!1:p&&(r.push(j),g=!0));if(i!==b){d||(s=r),a=a.replace(o.match[h],"");if(!g)return[];break}}if(a===q)if(g==null)m.error(a);else break;q=a}return s},m.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)};var n=m.getText=function(a){var b,c,d=a.nodeType,e="";if(d){if(d===1||d===9||d===11){if(typeof a.textContent=="string")return a.textContent;if(typeof a.innerText=="string")return a.innerText.replace(k,"");for(a=a.firstChild;a;a=a.nextSibling)e+=n(a)}else if(d===3||d===4)return a.nodeValue}else for(b=0;c=a[b];b++)c.nodeType!==8&&(e+=n(c));return e},o=m.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u
 00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!l.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&m.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e<f;e++)
 {c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&m.filter(b,a,!0)}},"":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("parentNode",b,f,a,d,c)},"~":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("previousSibling",b,f,a,d,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(j,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&
 (" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){re
 turn a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toL
 owerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}m.error(e)},CHILD:function(a,b){var c,e,f,g,h,i,
 j,k=b[1],l=a;switch(k){case"only":case"first":while(l=l.previousSibling)if(l.nodeType===1)return!1;if(k==="first")return!0;l=a;case"last":while(l=l.nextSibling)if(l.nodeType===1)return!1;return!0;case"nth":c=b[2],e=b[3];if(c===1&&e===0)return!0;f=b[0],g=a.parentNode;if(g&&(g[d]!==f||!a.nodeIndex)){i=0;for(l=g.firstChild;l;l=l.nextSibling)l.nodeType===1&&(l.nodeIndex=++i);g[d]=f}j=a.nodeIndex-e;return c===0?j===0:j%c===0&&j/c>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f===
 "$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c<e;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var u,v;c.documentElement.compareDocumentPosition?u=function(a,b){if(a===b){h=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compa
 reDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(u=function(a,b){if(a===b){h=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,i=b.parentNode,j=g;if(g===i)return v(a,b);if(!g)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return v(e[k],f[k]);return k===c?v(a,f[k],-1):v(e[k],b,1)},v=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.
 ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagNam
 e(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\
 ]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){va
 r d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h<i;h++)m(a,g[h],e,c);return m.filter(f,e)};m.attr=f.attr,m.selectors.attrMap={},f.find=m,f.expr=m.selectors,f.expr[":"]=f.expr.filters,f.unique=m.uniqueSort,f.text=m.getText,f.isXMLDoc=m.isXML,f.contains=m.contains}();var L=/Until$/,M=/^(?:parents|prevUntil|prevAll)/,N=/,/,O=/^.[^:#\[\.,]*$/,P=Array.prototype.slice,Q=f.expr.match.globalPOS,R={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.co
 ntains(this,b[a]))return!0})},not:function(a){return this.pushStack(T(this,a,!1),"not",a)},filter:function(a){return this.pushStack(T(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?Q.test(a)?f(a,this.context).index(this[0])>=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d<a.length;d++)f(g).is(a[d])&&c.push({selector:a[d],elem:g,level:h});g=g.parentNode,h++}return c}var i=Q.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(i?i.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,t
 his)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNode
 s)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|me
 ta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/<tbody/i,_=/<|&#?\w+;/,ba=/<(?:script|style)/i,bb=/<(?:script|object|embed|option|style)/i,bc=new RegExp("<(?:"+V+")[\\s/>]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a
 ))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&
 this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f
-.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,"
 "):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1></$2>");try{for(;d<e;d++)c=this[d]||{},c.nodeType===1&&(f.cleanData(c.getElementsByTagName("*")),c.innerHTML=a);c=0}catch(g){}}c&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return 
 this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bi(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,function(a,b){b.src?f.ajax({type:"GET",global:!1,url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)})}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i,j=a[0];b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof j=="string"&&j.length<512&&i===c&&j.charAt(0)==="<"&&!bb.test(j)&&(f.support.checkClone||!bd.test(j))&&(f.support.html5Clone||!bc.test(j))&&(g=!0,h=f.fragm
 ents[j],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[j]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e)
 {var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1></$2>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]==="<table>"&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]
 &&typeof (u=l.length)=="number")for(i=0;i<u;i++)bn(l[i]);else bn(l);l.nodeType?j.push(l):j=f.merge(j,l)}if(d){g=function(a){return!a.type||be.test(a.type)};for(k=0;j[k];k++){h=j[k];if(e&&f.nodeName(h,"script")&&(!h.type||be.test(h.type)))e.push(h.parentNode?h.parentNode.removeChild(h):h);else{if(h.nodeType===1){var v=f.grep(h.getElementsByTagName("script"),g);j.splice.apply(j,[k+1,0].concat(v))}d.appendChild(h)}}}return j},cleanData:function(a){var b,c,d=f.cache,e=f.event.special,g=f.support.deleteExpando;for(var h=0,i;(i=a[h])!=null;h++){if(i.nodeName&&f.noData[i.nodeName.toLowerCase()])continue;c=i[f.expando];if(c){b=d[c];if(b&&b.events){for(var j in b.events)e[j]?f.event.remove(i,j):f.removeEvent(i,j,b.handle);b.handle&&(b.handle.elem=null)}g?delete i[f.expando]:i.removeAttribute&&i.removeAttribute(f.expando),delete d[c]}}}});var bp=/alpha\([^)]*\)/i,bq=/opacity=([^)]*)/,br=/([A-Z]|^ms)/g,bs=/^[\-+]?(?:\d*\.)?\d+$/i,bt=/^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i,bu=/^([\-+])=([\-+
 .\de]+)/,bv=/^margin/,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Top","Right","Bottom","Left"],by,bz,bA;f.fn.css=function(a,c){return f.access(this,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)},a,c,arguments.length>1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch
 (l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,
 f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=fun
 ction(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement
 ("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.na
 me,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String
 ,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once mem
 ory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d
 .data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error
 :1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var 
 g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.
 async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-Wit
 h"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3)
 ,a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),(e===""&&f.css(d,"display")==="none"||!f.contains(d.ownerDocument.documentElement,d))&&f._data(d,"olddisplay",cu(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(ct("hide",3),a,b,c);var d,e,g=0,h=this.length;for(;g<h;g++)d=this[g],d.style&&(e=f.css(d,"display"),e!=="none"&&!f._data(d,"olddisplay")&&f._data(d,"olddisplay",e));for(g=0;g<h;g++)this[g].style&&(this[g].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(ct("toggle",3),a,b,c);return this},fadeTo:functi
 on(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){function g(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o,p,q;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]);if((k=f.cssHooks[g])&&"expand"in k){l=k.expand(a[g]),delete a[g];for(i in l)i in a||(a[i]=l[i])}}for(g in a){h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(!f.support.inlineBlockNeedsLayout||cu(this.nodeName)==="inline"?this.style.display="inline-block":this.style.zoom=1))}b.overflow!=null&&(this.style.overflow="hidd
 en");for(i in a)j=new f.fx(this,b,i),h=a[i],cm.test(h)?(q=f._data(this,"toggle"+i)||(h==="toggle"?d?"show":"hide":0),q?(f._data(this,"toggle"+i,q==="show"?"hide":"show"),j[q]()):j[h]()):(m=cn.exec(h),n=j.cur(),m?(o=parseFloat(m[2]),p=m[3]||(f.cssNumber[i]?"":"px"),p!=="px"&&(f.style(this,i,(o||1)+p),n=(o||1)/j.cur()*n,f.style(this,i,n+p)),m[1]&&(o=(m[1]==="-="?-1:1)*o+n),j.custom(n,o,p)):j.custom(n,h,""));return!0}var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return e.queue===!1?this.each(g):this.queue(e.queue,g)},stop:function(a,c,d){typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]);return this.each(function(){function h(a,b,c){var e=b[c];f.removeData(a,c,!0),e.stop(d)}var b,c=!1,e=f.timers,g=f._data(this);d||f._unmark(!0,this);if(a==null)for(b in g)g[b]&&g[b].stop&&b.indexOf(".run")===b.length-4&&h(this,g,b);else g[b=a+".run"]&&g[b].stop&&h(this,g,b);for(b=e.length;b--;)e[b].elem===this&&(a==null||e[b].queue
 ===a)&&(d?e[b](!0):e[b].saveState(),c=!0,e.splice(b,1));(!d||!c)&&f.dequeue(this,a)})}}),f.each({slideDown:ct("show",1),slideUp:ct("hide",1),slideToggle:ct("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue?f.dequeue(this,d.queue):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a){return a},swing:function(a){return-Math.cos(a*Math.PI)/2+.5}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.proto
 type={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,c,d){function h(a){return e.step(a)}var e=this,g=f.fx;this.startTime=cq||cr(),this.end=c,this.now=this.start=a,this.pos=this.state=0,this.unit=d||this.unit||(f.cssNumber[this.prop]?"":"px"),h.queue=this.options.queue,h.elem=this.elem,h.saveState=function(){f._data(e.elem,"fxshow"+e.prop)===b&&(e.options.hide?f._data(e.elem,"fxshow"+e.prop,e.start):e.options.show&&f._data(e.elem,"fxshow"+e.prop,e.end))},h()&&f.timers.push(h)&&!co&&(co=setInterval(g.tick,g.interval))},show:function(){var a=f._data(this.elem,"fxshow"+this.prop);this.options.orig[this.prop]=a||f.style(this.elem,this.prop),this.options.show=!0,a!=
 =b?this.custom(this.cur(),a):this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f._data(this.elem,"fxshow"+this.prop)||f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b,c,d,e=cq||cr(),g=!0,h=this.elem,i=this.options;if(a||e>=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this
 .prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||f.fx.stop()},interval:13,stop:function(){clearInterval(co),co=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=a.now+a.unit:a.elem[a.prop]=a.now}}}),f.each(cp.concat.apply([],cp),function(a,b){b.indexOf("margin")&&(f.fx.step[b]=function(a){f.style(a.elem,b,Math.max(0,a.now)+a.unit)})}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cv,cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?cv=function(a,b,c,d){try{d=a.getBoundingClientRect()}catch(e){}if(!d||!f.contains(c,a))return d?{top:d.top,l
 eft:d.left}:{top:0,left:0};var g=b.body,h=cy(b),i=c.clientTop||g.clientTop||0,j=c.clientLeft||g.clientLeft||0,k=h.pageYOffset||f.support.boxModel&&c.scrollTop||g.scrollTop,l=h.pageXOffset||f.support.boxModel&&c.scrollLeft||g.scrollLeft,m=d.top+k-i,n=d.left+l-j;return{top:m,left:n}}:cv=function(a,b,c){var d,e=a.offsetParent,g=a,h=b.body,i=b.defaultView,j=i?i.getComputedStyle(a,null):a.currentStyle,k=a.offsetTop,l=a.offsetLeft;while((a=a.parentNode)&&a!==h&&a!==c){if(f.support.fixedPosition&&j.position==="fixed")break;d=i?i.getComputedStyle(a,null):a.currentStyle,k-=a.scrollTop,l-=a.scrollLeft,a===e&&(k+=a.offsetTop,l+=a.offsetLeft,f.support.doesNotAddBorder&&(!f.support.doesAddBorderForTableAndCells||!cw.test(a.nodeName))&&(k+=parseFloat(d.borderTopWidth)||0,l+=parseFloat(d.borderLeftWidth)||0),g=e,e=a.offsetParent),f.support.subtractsBorderForOverflowNotVisible&&d.overflow!=="visible"&&(k+=parseFloat(d.borderTopWidth)||0,l+=parseFloat(d.borderLeftWidth)||0),j=d}if(j.position
 ==="relative"||j.position==="static")k+=h.offsetTop,l+=h.offsetLeft;f.support.fixedPosition&&j.position==="fixed"&&(k+=Math.max(c.scrollTop,h.scrollTop),l+=Math.max(c.scrollLeft,h.scrollLeft));return{top:k,left:l}},f.fn.offset=function(a){if(arguments.length)return a===b?this:this.each(function(b){f.offset.setOffset(this,a,b)});var c=this[0],d=c&&c.ownerDocument;if(!d)return null;if(c===d.body)return f.offset.bodyOffset(c);return cv(c,d,d.documentElement)},f.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFuncti
 on(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(
 d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window);
\ No newline at end of file
+/*! jQuery v1.8.2 jquery.com | jquery.org/license */
+(function(a,b){function G(a){var b=F[a]={};return p.each(a.split(s),function(a,c){b[c]=!0}),b}function J(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(I,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:+d+""===d?+d:H.test(d)?p.parseJSON(d):d}catch(f){}p.data(a,c,d)}else d=b}return d}function K(a){var b;for(b in a){if(b==="data"&&p.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function ba(){return!1}function bb(){return!0}function bh(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function bi(a,b){do a=a[b];while(a&&a.nodeType!==1);return a}function bj(a,b,c){b=b||0;if(p.isFunction(b))return p.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return p.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=p.grep(a,function(a){return a.nodeType===1});if(be.test(b))return p.filter(b,d,!c);b=p.filter(b,d)}return p.grep(a,function(a,d){return p.
 inArray(a,b)>=0===c})}function bk(a){var b=bl.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function bC(a,b){return a.getElementsByTagName(b)[0]||a.appendChild(a.ownerDocument.createElement(b))}function bD(a,b){if(b.nodeType!==1||!p.hasData(a))return;var c,d,e,f=p._data(a),g=p._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;d<e;d++)p.event.add(b,c,h[c][d])}g.data&&(g.data=p.extend({},g.data))}function bE(a,b){var c;if(b.nodeType!==1)return;b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase(),c==="object"?(b.parentNode&&(b.outerHTML=a.outerHTML),p.support.html5Clone&&a.innerHTML&&!p.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):c==="input"&&bv.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):c==="option"?b.selected=a.defaultSelected:c==="input"||c==="textarea"?b.defaultValue=a.defaul
 tValue:c==="script"&&b.text!==a.text&&(b.text=a.text),b.removeAttribute(p.expando)}function bF(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bG(a){bv.test(a.type)&&(a.defaultChecked=a.checked)}function bY(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=bW.length;while(e--){b=bW[e]+c;if(b in a)return b}return d}function bZ(a,b){return a=b||a,p.css(a,"display")==="none"||!p.contains(a.ownerDocument,a)}function b$(a,b){var c,d,e=[],f=0,g=a.length;for(;f<g;f++){c=a[f];if(!c.style)continue;e[f]=p._data(c,"olddisplay"),b?(!e[f]&&c.style.display==="none"&&(c.style.display=""),c.style.display===""&&bZ(c)&&(e[f]=p._data(c,"olddisplay",cc(c.nodeName)))):(d=bH(c,"display"),!e[f]&&d!=="none"&&p._data(c,"olddisplay",d))}for(f=0;f<g;f++){c=a[f];if(!c.style)continue;if(!b||c.style.display==="none"||c.style.display==="")c.style.display=b?e[f]||"":"none"}retu
 rn a}function b_(a,b,c){var d=bP.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function ca(a,b,c,d){var e=c===(d?"border":"content")?4:b==="width"?1:0,f=0;for(;e<4;e+=2)c==="margin"&&(f+=p.css(a,c+bV[e],!0)),d?(c==="content"&&(f-=parseFloat(bH(a,"padding"+bV[e]))||0),c!=="margin"&&(f-=parseFloat(bH(a,"border"+bV[e]+"Width"))||0)):(f+=parseFloat(bH(a,"padding"+bV[e]))||0,c!=="padding"&&(f+=parseFloat(bH(a,"border"+bV[e]+"Width"))||0));return f}function cb(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=!0,f=p.support.boxSizing&&p.css(a,"boxSizing")==="border-box";if(d<=0||d==null){d=bH(a,b);if(d<0||d==null)d=a.style[b];if(bQ.test(d))return d;e=f&&(p.support.boxSizingReliable||d===a.style[b]),d=parseFloat(d)||0}return d+ca(a,b,c||(f?"border":"content"),e)+"px"}function cc(a){if(bS[a])return bS[a];var b=p("<"+a+">").appendTo(e.body),c=b.css("display");b.remove();if(c==="none"||c===""){bI=e.body.appendChild(bI||p.extend(e.createElement("iframe"),{frameBorder:0,
 width:0,height:0}));if(!bJ||!bI.createElement)bJ=(bI.contentWindow||bI.contentDocument).document,bJ.write("<!doctype html><html><body>"),bJ.close();b=bJ.body.appendChild(bJ.createElement(a)),c=bH(b,"display"),e.body.removeChild(bI)}return bS[a]=c,c}function ci(a,b,c,d){var e;if(p.isArray(b))p.each(b,function(b,e){c||ce.test(a)?d(a,e):ci(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&p.type(b)==="object")for(e in b)ci(a+"["+e+"]",b[e],c,d);else d(a,b)}function cz(a){return function(b,c){typeof b!="string"&&(c=b,b="*");var d,e,f,g=b.toLowerCase().split(s),h=0,i=g.length;if(p.isFunction(c))for(;h<i;h++)d=g[h],f=/^\+/.test(d),f&&(d=d.substr(1)||"*"),e=a[d]=a[d]||[],e[f?"unshift":"push"](c)}}function cA(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h,i=a[f],j=0,k=i?i.length:0,l=a===cv;for(;j<k&&(l||!h);j++)h=i[j](c,d,e),typeof h=="string"&&(!l||g[h]?h=b:(c.dataTypes.unshift(h),h=cA(a,c,d,e,h,g)));return(l||!h)&&!g["*"]&&(h=cA(a,c,d,e,"*",g)),h}function cB(a,c){va
 r d,e,f=p.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((f[d]?a:e||(e={}))[d]=c[d]);e&&p.extend(!0,a,e)}function cC(a,c,d){var e,f,g,h,i=a.contents,j=a.dataTypes,k=a.responseFields;for(f in k)f in d&&(c[k[f]]=d[f]);while(j[0]==="*")j.shift(),e===b&&(e=a.mimeType||c.getResponseHeader("content-type"));if(e)for(f in i)if(i[f]&&i[f].test(e)){j.unshift(f);break}if(j[0]in d)g=j[0];else{for(f in d){if(!j[0]||a.converters[f+" "+j[0]]){g=f;break}h||(h=f)}g=g||h}if(g)return g!==j[0]&&j.unshift(g),d[g]}function cD(a,b){var c,d,e,f,g=a.dataTypes.slice(),h=g[0],i={},j=0;a.dataFilter&&(b=a.dataFilter(b,a.dataType));if(g[1])for(c in a.converters)i[c.toLowerCase()]=a.converters[c];for(;e=g[++j];)if(e!=="*"){if(h!=="*"&&h!==e){c=i[h+" "+e]||i["* "+e];if(!c)for(d in i){f=d.split(" ");if(f[1]===e){c=i[h+" "+f[0]]||i["* "+f[0]];if(c){c===!0?c=i[d]:i[d]!==!0&&(e=f[0],g.splice(j--,0,e));break}}}if(c!==!0)if(c&&a["throws"])b=c(b);else try{b=c(b)}catch(k){return{state:"parsererror",error:c?k:"
 No conversion from "+h+" to "+e}}}h=e}return{state:"success",data:b}}function cL(){try{return new a.XMLHttpRequest}catch(b){}}function cM(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function cU(){return setTimeout(function(){cN=b},0),cN=p.now()}function cV(a,b){p.each(b,function(b,c){var d=(cT[b]||[]).concat(cT["*"]),e=0,f=d.length;for(;e<f;e++)if(d[e].call(a,b,c))return})}function cW(a,b,c){var d,e=0,f=0,g=cS.length,h=p.Deferred().always(function(){delete i.elem}),i=function(){var b=cN||cU(),c=Math.max(0,j.startTime+j.duration-b),d=1-(c/j.duration||0),e=0,f=j.tweens.length;for(;e<f;e++)j.tweens[e].run(d);return h.notifyWith(a,[j,d,c]),d<1&&f?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:p.extend({},b),opts:p.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:cN||cU(),duration:c.duration,tweens:[],createTween:function(b,c,d){var e=p.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(e),e
 },stop:function(b){var c=0,d=b?j.tweens.length:0;for(;c<d;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;cX(k,j.opts.specialEasing);for(;e<g;e++){d=cS[e].call(j,a,k,j.opts);if(d)return d}return cV(j,k),p.isFunction(j.opts.start)&&j.opts.start.call(a,j),p.fx.timer(p.extend(i,{anim:j,queue:j.opts.queue,elem:a})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}function cX(a,b){var c,d,e,f,g;for(c in a){d=p.camelCase(c),e=b[d],f=a[c],p.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=p.cssHooks[d];if(g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}}function cY(a,b,c){var d,e,f,g,h,i,j,k,l=this,m=a.style,n={},o=[],q=a.nodeType&&bZ(a);c.queue||(j=p._queueHooks(a,"fx"),j.unqueued==null&&(j.unqueued=0,k=j.empty.fire,j.empty.fire=function(){j.unqueued||k()}),j.unqueued++,l.always(function(){l.always(function(){j.unqueued--,p.
 queue(a,"fx").length||j.empty.fire()})})),a.nodeType===1&&("height"in b||"width"in b)&&(c.overflow=[m.overflow,m.overflowX,m.overflowY],p.css(a,"display")==="inline"&&p.css(a,"float")==="none"&&(!p.support.inlineBlockNeedsLayout||cc(a.nodeName)==="inline"?m.display="inline-block":m.zoom=1)),c.overflow&&(m.overflow="hidden",p.support.shrinkWrapBlocks||l.done(function(){m.overflow=c.overflow[0],m.overflowX=c.overflow[1],m.overflowY=c.overflow[2]}));for(d in b){f=b[d];if(cP.exec(f)){delete b[d];if(f===(q?"hide":"show"))continue;o.push(d)}}g=o.length;if(g){h=p._data(a,"fxshow")||p._data(a,"fxshow",{}),q?p(a).show():l.done(function(){p(a).hide()}),l.done(function(){var b;p.removeData(a,"fxshow",!0);for(b in n)p.style(a,b,n[b])});for(d=0;d<g;d++)e=o[d],i=l.createTween(e,q?h[e]:0),n[e]=h[e]||p.style(a,e),e in h||(h[e]=i.start,q&&(i.end=i.start,i.start=e==="width"||e==="height"?1:0))}}function cZ(a,b,c,d,e){return new cZ.prototype.init(a,b,c,d,e)}function c$(a,b){var c,d={height:a},
 e=0;b=b?1:0;for(;e<4;e+=2-b)c=bV[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function da(a){return p.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}var c,d,e=a.document,f=a.location,g=a.navigator,h=a.jQuery,i=a.$,j=Array.prototype.push,k=Array.prototype.slice,l=Array.prototype.indexOf,m=Object.prototype.toString,n=Object.prototype.hasOwnProperty,o=String.prototype.trim,p=function(a,b){return new p.fn.init(a,b,c)},q=/[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,r=/\S/,s=/\s+/,t=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,u=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,y=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,z=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,A=/^-ms-/,B=/-([\da-z])/gi,C=function(a,b){return(b+"").toUpperCase()},D=function(){e.addEventListener?(e.removeEventListener("DOMContentLoaded",D,!1),p.ready()):e.readyState==="complete"&&(e.detachEve
 nt("onreadystatechange",D),p.ready())},E={};p.fn=p.prototype={constructor:p,init:function(a,c,d){var f,g,h,i;if(!a)return this;if(a.nodeType)return this.context=this[0]=a,this.length=1,this;if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?f=[null,a,null]:f=u.exec(a);if(f&&(f[1]||!c)){if(f[1])return c=c instanceof p?c[0]:c,i=c&&c.nodeType?c.ownerDocument||c:e,a=p.parseHTML(f[1],i,!0),v.test(f[1])&&p.isPlainObject(c)&&this.attr.call(a,c,!0),p.merge(this,a);g=e.getElementById(f[2]);if(g&&g.parentNode){if(g.id!==f[2])return d.find(a);this.length=1,this[0]=g}return this.context=e,this.selector=a,this}return!c||c.jquery?(c||d).find(a):this.constructor(c).find(a)}return p.isFunction(a)?d.ready(a):(a.selector!==b&&(this.selector=a.selector,this.context=a.context),p.makeArray(a,this))},selector:"",jquery:"1.8.2",length:0,size:function(){return this.length},toArray:function(){return k.call(this)},get:function(a){return a==null?this.toArray():a<0?this[t
 his.length+a]:this[a]},pushStack:function(a,b,c){var d=p.merge(this.constructor(),a);return d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")"),d},each:function(a,b){return p.each(this,a,b)},ready:function(a){return p.ready.promise().done(a),this},eq:function(a){return a=+a,a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(k.apply(this,arguments),"slice",k.call(arguments).join(","))},map:function(a){return this.pushStack(p.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:j,sort:[].sort,splice:[].splice},p.fn.init.prototype=p.fn,p.extend=p.fn.extend=function(){var a,c,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;typeof h=="boolean"&&(k=h,h=arguments[1]||{},i=2),typeof h!="object"&&!p.isFunction(h)&&(h={}),j
 ===i&&(h=this,--i);for(;i<j;i++)if((a=arguments[i])!=null)for(c in a){d=h[c],e=a[c];if(h===e)continue;k&&e&&(p.isPlainObject(e)||(f=p.isArray(e)))?(f?(f=!1,g=d&&p.isArray(d)?d:[]):g=d&&p.isPlainObject(d)?d:{},h[c]=p.extend(k,g,e)):e!==b&&(h[c]=e)}return h},p.extend({noConflict:function(b){return a.$===p&&(a.$=i),b&&a.jQuery===p&&(a.jQuery=h),p},isReady:!1,readyWait:1,holdReady:function(a){a?p.readyWait++:p.ready(!0)},ready:function(a){if(a===!0?--p.readyWait:p.isReady)return;if(!e.body)return setTimeout(p.ready,1);p.isReady=!0;if(a!==!0&&--p.readyWait>0)return;d.resolveWith(e,[p]),p.fn.trigger&&p(e).trigger("ready").off("ready")},isFunction:function(a){return p.type(a)==="function"},isArray:Array.isArray||function(a){return p.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):E[m.call(a)]||"object"},isPlainObject:function(a){if(!a||p.type(a)!=="object"|
 |a.nodeType||p.isWindow(a))return!1;try{if(a.constructor&&!n.call(a,"constructor")&&!n.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||n.call(a,d)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},error:function(a){throw new Error(a)},parseHTML:function(a,b,c){var d;return!a||typeof a!="string"?null:(typeof b=="boolean"&&(c=b,b=0),b=b||e,(d=v.exec(a))?[b.createElement(d[1])]:(d=p.buildFragment([a],b,c?null:[]),p.merge([],(d.cacheable?p.clone(d.fragment):d.fragment).childNodes)))},parseJSON:function(b){if(!b||typeof b!="string")return null;b=p.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(w.test(b.replace(y,"@").replace(z,"]").replace(x,"")))return(new Function("return "+b))();p.error("Invalid JSON: "+b)},parseXML:function(c){var d,e;if(!c||typeof c!="string")return null;try{a.DOMParser?(e=new DOMParser,d=e.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d
 .loadXML(c))}catch(f){d=b}return(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&p.error("Invalid XML: "+c),d},noop:function(){},globalEval:function(b){b&&r.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(A,"ms-").replace(B,C)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,c,d){var e,f=0,g=a.length,h=g===b||p.isFunction(a);if(d){if(h){for(e in a)if(c.apply(a[e],d)===!1)break}else for(;f<g;)if(c.apply(a[f++],d)===!1)break}else if(h){for(e in a)if(c.call(a[e],e,a[e])===!1)break}else for(;f<g;)if(c.call(a[f],f,a[f++])===!1)break;return a},trim:o&&!o.call(" ")?function(a){return a==null?"":o.call(a)}:function(a){return a==null?"":(a+"").replace(t,"")},makeArray:function(a,b){var c,d=b||[];return a!=null&&(c=p.type(a),a.length==null||c==="string"||c==="function"||c==="regexp"||p.isWindow(a)?j.call(d,a):p.merge(d,a)),d},inArray:function(a,b,c){var d;
 if(b){if(l)return l.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=c.length,e=a.length,f=0;if(typeof d=="number")for(;f<d;f++)a[e++]=c[f];else while(c[f]!==b)a[e++]=c[f++];return a.length=e,a},grep:function(a,b,c){var d,e=[],f=0,g=a.length;c=!!c;for(;f<g;f++)d=!!b(a[f],f),c!==d&&e.push(a[f]);return e},map:function(a,c,d){var e,f,g=[],h=0,i=a.length,j=a instanceof p||i!==b&&typeof i=="number"&&(i>0&&a[0]&&a[i-1]||i===0||p.isArray(a));if(j)for(;h<i;h++)e=c(a[h],h,d),e!=null&&(g[g.length]=e);else for(f in a)e=c(a[f],f,d),e!=null&&(g[g.length]=e);return g.concat.apply([],g)},guid:1,proxy:function(a,c){var d,e,f;return typeof c=="string"&&(d=a[c],c=a,a=d),p.isFunction(a)?(e=k.call(arguments,2),f=function(){return a.apply(c,e.concat(k.call(arguments)))},f.guid=a.guid=a.guid||p.guid++,f):b},access:function(a,c,d,e,f,g,h){var i,j=d==null,k=0,l=a.length;if(d&&typeof d=="object"){for(k in d)p.access(a,c,
 k,d[k],1,g,e);f=1}else if(e!==b){i=h===b&&p.isFunction(e),j&&(i?(i=c,c=function(a,b,c){return i.call(p(a),c)}):(c.call(a,e),c=null));if(c)for(;k<l;k++)c(a[k],d,i?e.call(a[k],k,c(a[k],d)):e,h);f=1}return f?a:j?c.call(a):l?c(a[0],d):g},now:function(){return(new Date).getTime()}}),p.ready.promise=function(b){if(!d){d=p.Deferred();if(e.readyState==="complete")setTimeout(p.ready,1);else if(e.addEventListener)e.addEventListener("DOMContentLoaded",D,!1),a.addEventListener("load",p.ready,!1);else{e.attachEvent("onreadystatechange",D),a.attachEvent("onload",p.ready);var c=!1;try{c=a.frameElement==null&&e.documentElement}catch(f){}c&&c.doScroll&&function g(){if(!p.isReady){try{c.doScroll("left")}catch(a){return setTimeout(g,50)}p.ready()}}()}}return d.promise(b)},p.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){E["[object "+b+"]"]=b.toLowerCase()}),c=p(e);var F={};p.Callbacks=function(a){a=typeof a=="string"?F[a]||G(a):p.extend({},a);var c,d,e,
 f,g,h,i=[],j=!a.once&&[],k=function(b){c=a.memory&&b,d=!0,h=f||0,f=0,g=i.length,e=!0;for(;i&&h<g;h++)if(i[h].apply(b[0],b[1])===!1&&a.stopOnFalse){c=!1;break}e=!1,i&&(j?j.length&&k(j.shift()):c?i=[]:l.disable())},l={add:function(){if(i){var b=i.length;(function d(b){p.each(b,function(b,c){var e=p.type(c);e==="function"&&(!a.unique||!l.has(c))?i.push(c):c&&c.length&&e!=="string"&&d(c)})})(arguments),e?g=i.length:c&&(f=b,k(c))}return this},remove:function(){return i&&p.each(arguments,function(a,b){var c;while((c=p.inArray(b,i,c))>-1)i.splice(c,1),e&&(c<=g&&g--,c<=h&&h--)}),this},has:function(a){return p.inArray(a,i)>-1},empty:function(){return i=[],this},disable:function(){return i=j=c=b,this},disabled:function(){return!i},lock:function(){return j=b,c||l.disable(),this},locked:function(){return!j},fireWith:function(a,b){return b=b||[],b=[a,b.slice?b.slice():b],i&&(!d||j)&&(e?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!
 d}};return l},p.extend({Deferred:function(a){var b=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return p.Deferred(function(c){p.each(b,function(b,d){var f=d[0],g=a[b];e[d[1]](p.isFunction(g)?function(){var a=g.apply(this,arguments);a&&p.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===e?c:this,[a])}:c[f])}),a=null}).promise()},promise:function(a){return a!=null?p.extend(a,d):d}},e={};return d.pipe=d.then,p.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[a^1][2].disable,b[2][2].lock),e[f[0]]=g.fire,e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=k.call(arguments),d=c.length,e=d!==1||a&&p.isFunction(a.promi
 se)?d:0,f=e===1?a:p.Deferred(),g=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?k.call(arguments):d,c===h?f.notifyWith(b,c):--e||f.resolveWith(b,c)}},h,i,j;if(d>1){h=new Array(d),i=new Array(d),j=new Array(d);for(;b<d;b++)c[b]&&p.isFunction(c[b].promise)?c[b].promise().done(g(b,j,c)).fail(f.reject).progress(g(b,i,h)):--e}return e||f.resolveWith(j,c),f.promise()}}),p.support=function(){var b,c,d,f,g,h,i,j,k,l,m,n=e.createElement("div");n.setAttribute("className","t"),n.innerHTML="  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",c=n.getElementsByTagName("*"),d=n.getElementsByTagName("a")[0],d.style.cssText="top:1px;float:left;opacity:.5";if(!c||!c.length)return{};f=e.createElement("select"),g=f.appendChild(e.createElement("option")),h=n.getElementsByTagName("input")[0],b={leadingWhitespace:n.firstChild.nodeType===3,tbody:!n.getElementsByTagName("tbody").length,htmlSerialize:!!n.getElementsByTagName("link").length,style:/top/.test(d.getA
 ttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.5/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:n.className!=="t",enctype:!!e.createElement("form").enctype,html5Clone:e.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",boxModel:e.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},h.checked=!0,b.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,b.optDisabled=!g.disabled;try{delete n.test}catch(o){b.deleteExpando=!1}!n.addEventListener&&n.attachEvent&&n.fireEvent&&(n.attachEvent("onclick",m=function(){b.noCloneEvent=!1}),n.cloneNode(!0).fireEvent("onclick"),n.detachEvent("onclick",m)),h=e.createElement("input"),h.value="t",h.setAttribute("type","radio"),b.radioValue=h.value==="t",h.setAttribute("checked
 ","checked"),h.setAttribute("name","t"),n.appendChild(h),i=e.createDocumentFragment(),i.appendChild(n.lastChild),b.checkClone=i.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=h.checked,i.removeChild(h),i.appendChild(n);if(n.attachEvent)for(k in{submit:!0,change:!0,focusin:!0})j="on"+k,l=j in n,l||(n.setAttribute(j,"return;"),l=typeof n[j]=="function"),b[k+"Bubbles"]=l;return p(function(){var c,d,f,g,h="padding:0;margin:0;border:0;display:block;overflow:hidden;",i=e.getElementsByTagName("body")[0];if(!i)return;c=e.createElement("div"),c.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",i.insertBefore(c,i.firstChild),d=e.createElement("div"),c.appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",f=d.getElementsByTagName("td"),f[0].style.cssText="padding:0;margin:0;border:0;display:none",l=f[0].offsetHeight===0,f[0].style.display="",f[1].style.display="none",b.reliableHiddenOffsets=l&&f[0].offsetH
 eight===0,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",b.boxSizing=d.offsetWidth===4,b.doesNotIncludeMarginInBodyOffset=i.offsetTop!==1,a.getComputedStyle&&(b.pixelPosition=(a.getComputedStyle(d,null)||{}).top!=="1%",b.boxSizingReliable=(a.getComputedStyle(d,null)||{width:"4px"}).width==="4px",g=e.createElement("div"),g.style.cssText=d.style.cssText=h,g.style.marginRight=g.style.width="0",d.style.width="1px",d.appendChild(g),b.reliableMarginRight=!parseFloat((a.getComputedStyle(g,null)||{}).marginRight)),typeof d.style.zoom!="undefined"&&(d.innerHTML="",d.style.cssText=h+"width:1px;padding:1px;display:inline;zoom:1",b.inlineBlockNeedsLayout=d.offsetWidth===3,d.style.display="block",d.style.overflow="visible",d.innerHTML="<div></div>",d.firstChild.style.width="5px",b.shrinkWrapBlocks=d.offsetWidth!==3,c.style.zoom=1),i.remov
 eChild(c),c=d=f=g=null}),i.removeChild(n),c=d=f=g=h=i=n=null,b}();var H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,I=/([A-Z])/g;p.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(p.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){return a=a.nodeType?p.cache[a[p.expando]]:a[p.expando],!!a&&!K(a)},data:function(a,c,d,e){if(!p.acceptData(a))return;var f,g,h=p.expando,i=typeof c=="string",j=a.nodeType,k=j?p.cache:a,l=j?a[h]:a[h]&&h;if((!l||!k[l]||!e&&!k[l].data)&&i&&d===b)return;l||(j?a[h]=l=p.deletedIds.pop()||p.guid++:l=h),k[l]||(k[l]={},j||(k[l].toJSON=p.noop));if(typeof c=="object"||typeof c=="function")e?k[l]=p.extend(k[l],c):k[l].data=p.extend(k[l].data,c);return f=k[l],e||(f.data||(f.data={}),f=f.data),d!==b&&(f[p.camelCase(c)]=d),i?(g=f[c],g==null&&(g=f[p.camelCase(c)])):g=f,g},removeData:function(a,b,c){if(!p.acceptData(a))return;var d,e,f,g=a.nodeType,h=g?p.cache:a,i=g?a[p.expando]
 :p.expando;if(!h[i])return;if(b){d=c?h[i]:h[i].data;if(d){p.isArray(b)||(b in d?b=[b]:(b=p.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,f=b.length;e<f;e++)delete d[b[e]];if(!(c?K:p.isEmptyObject)(d))return}}if(!c){delete h[i].data;if(!K(h[i]))return}g?p.cleanData([a],!0):p.support.deleteExpando||h!=h.window?delete h[i]:h[i]=null},_data:function(a,b,c){return p.data(a,b,c,!0)},acceptData:function(a){var b=a.nodeName&&p.noData[a.nodeName.toLowerCase()];return!b||b!==!0&&a.getAttribute("classid")===b}}),p.fn.extend({data:function(a,c){var d,e,f,g,h,i=this[0],j=0,k=null;if(a===b){if(this.length){k=p.data(i);if(i.nodeType===1&&!p._data(i,"parsedAttrs")){f=i.attributes;for(h=f.length;j<h;j++)g=f[j].name,g.indexOf("data-")||(g=p.camelCase(g.substring(5)),J(i,g,k[g]));p._data(i,"parsedAttrs",!0)}}return k}return typeof a=="object"?this.each(function(){p.data(this,a)}):(d=a.split(".",2),d[1]=d[1]?"."+d[1]:"",e=d[1]+"!",p.access(this,function(c){if(c===b)return k=this.triggerHan
 dler("getData"+e,[d[0]]),k===b&&i&&(k=p.data(i,a),k=J(i,a,k)),k===b&&d[1]?this.data(d[0]):k;d[1]=c,this.each(function(){var b=p(this);b.triggerHandler("setData"+e,d),p.data(this,a,c),b.triggerHandler("changeData"+e,d)})},null,c,arguments.length>1,null,!1))},removeData:function(a){return this.each(function(){p.removeData(this,a)})}}),p.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=p._data(a,b),c&&(!d||p.isArray(c)?d=p._data(a,b,p.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=p.queue(a,b),d=c.length,e=c.shift(),f=p._queueHooks(a,b),g=function(){p.dequeue(a,b)};e==="inprogress"&&(e=c.shift(),d--),e&&(b==="fx"&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return p._data(a,c)||p._data(a,c,{empty:p.Callbacks("once memory").add(function(){p.removeData(a,b+"queue",!0),p.removeData(a,c,!0)})})}}),p.fn.extend({queue:function(a,c){var d=2;return typeof a!="strin
 g"&&(c=a,a="fx",d--),arguments.length<d?p.queue(this[0],a):c===b?this:this.each(function(){var b=p.queue(this,a,c);p._queueHooks(this,a),a==="fx"&&b[0]!=="inprogress"&&p.dequeue(this,a)})},dequeue:function(a){return this.each(function(){p.dequeue(this,a)})},delay:function(a,b){return a=p.fx?p.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){var d,e=1,f=p.Deferred(),g=this,h=this.length,i=function(){--e||f.resolveWith(g,[g])};typeof a!="string"&&(c=a,a=b),a=a||"fx";while(h--)d=p._data(g[h],a+"queueHooks"),d&&d.empty&&(e++,d.empty.add(i));return i(),f.promise(c)}});var L,M,N,O=/[\t\r\n]/g,P=/\r/g,Q=/^(?:button|input)$/i,R=/^(?:button|input|object|select|textarea)$/i,S=/^a(?:rea|)$/i,T=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,U=p.support.getSetAttribute;p
 .fn.extend({attr:function(a,b){return p.access(this,p.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){p.removeAttr(this,a)})},prop:function(a,b){return p.access(this,p.prop,a,b,arguments.length>1)},removeProp:function(a){return a=p.propFix[a]||a,this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,f,g,h;if(p.isFunction(a))return this.each(function(b){p(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(s);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{f=" "+e.className+" ";for(g=0,h=b.length;g<h;g++)f.indexOf(" "+b[g]+" ")<0&&(f+=b[g]+" ");e.className=p.trim(f)}}}return this},removeClass:function(a){var c,d,e,f,g,h,i;if(p.isFunction(a))return this.each(function(b){p(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(s);for(h=0,i=this.length;h<i;h++){e=this[h]
 ;if(e.nodeType===1&&e.className){d=(" "+e.className+" ").replace(O," ");for(f=0,g=c.length;f<g;f++)while(d.indexOf(" "+c[f]+" ")>=0)d=d.replace(" "+c[f]+" "," ");e.className=a?p.trim(d):""}}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";return p.isFunction(a)?this.each(function(c){p(this).toggleClass(a.call(this,c,this.className,b),b)}):this.each(function(){if(c==="string"){var e,f=0,g=p(this),h=b,i=a.split(s);while(e=i[f++])h=d?h:!g.hasClass(e),g[h?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||a===!1?"":p._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(O," ").indexOf(b)>=0)return!0;return!1},val:function(a){var c,d,e,f=this[0];if(!arguments.length){if(f)return c=p.valHooks[f.type]||p.valHooks[f.nodeName.toLowerCase()],
 c&&"get"in c&&(d=c.get(f,"value"))!==b?d:(d=f.value,typeof d=="string"?d.replace(P,""):d==null?"":d);return}return e=p.isFunction(a),this.each(function(d){var f,g=p(this);if(this.nodeType!==1)return;e?f=a.call(this,d,g.val()):f=a,f==null?f="":typeof f=="number"?f+="":p.isArray(f)&&(f=p.map(f,function(a){return a==null?"":a+""})),c=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,f,"value")===b)this.value=f})}}),p.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,f=a.selectedIndex,g=[],h=a.options,i=a.type==="select-one";if(f<0)return null;c=i?f:0,d=i?f+1:h.length;for(;c<d;c++){e=h[c];if(e.selected&&(p.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!p.nodeName(e.parentNode,"optgroup"))){b=p(e).val();if(i)return b;g.push(b)}}return i&&!g.length&&h.length?p(h[f]).val():g},set:function(a,b){var c=p
 .makeArray(b);return p(a).find("option").each(function(){this.selected=p.inArray(p(this).val(),c)>=0}),c.length||(a.selectedIndex=-1),c}}},attrFn:{},attr:function(a,c,d,e){var f,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return;if(e&&p.isFunction(p.fn[c]))return p(a)[c](d);if(typeof a.getAttribute=="undefined")return p.prop(a,c,d);h=i!==1||!p.isXMLDoc(a),h&&(c=c.toLowerCase(),g=p.attrHooks[c]||(T.test(c)?M:L));if(d!==b){if(d===null){p.removeAttr(a,c);return}return g&&"set"in g&&h&&(f=g.set(a,d,c))!==b?f:(a.setAttribute(c,d+""),d)}return g&&"get"in g&&h&&(f=g.get(a,c))!==null?f:(f=a.getAttribute(c),f===null?b:f)},removeAttr:function(a,b){var c,d,e,f,g=0;if(b&&a.nodeType===1){d=b.split(s);for(;g<d.length;g++)e=d[g],e&&(c=p.propFix[e]||e,f=T.test(e),f||p.attr(a,e,""),a.removeAttribute(U?e:c),f&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(Q.test(a.nodeName)&&a.parentNode)p.error("type property can't be changed");else if(!p.support.radioValue&&b==="radio"&&p.nodeN
 ame(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}},value:{get:function(a,b){return L&&p.nodeName(a,"button")?L.get(a,b):b in a?a.value:null},set:function(a,b,c){if(L&&p.nodeName(a,"button"))return L.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,f,g,h=a.nodeType;if(!a||h===3||h===8||h===2)return;return g=h!==1||!p.isXMLDoc(a),g&&(c=p.propFix[c]||c,f=p.propHooks[c]),d!==b?f&&"set"in f&&(e=f.set(a,d,c))!==b?e:a[c]=d:f&&"get"in f&&(e=f.get(a,c))!==null?e:a[c]},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):R.test(a.nodeName)||S.test(a.nodeName)&&a.href?0:b}}}}),M={get:function(a,c){var d,e=p.prop(a,c);return
  e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;return b===!1?p.removeAttr(a,c):(d=p.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase())),c}},U||(N={name:!0,id:!0,coords:!0},L=p.valHooks.button={get:function(a,c){var d;return d=a.getAttributeNode(c),d&&(N[c]?d.value!=="":d.specified)?d.value:b},set:function(a,b,c){var d=a.getAttributeNode(c);return d||(d=e.createAttribute(c),a.setAttributeNode(d)),d.value=b+""}},p.each(["width","height"],function(a,b){p.attrHooks[b]=p.extend(p.attrHooks[b],{set:function(a,c){if(c==="")return a.setAttribute(b,"auto"),c}})}),p.attrHooks.contenteditable={get:L.get,set:function(a,b,c){b===""&&(b="false"),L.set(a,b,c)}}),p.support.hrefNormalized||p.each(["href","src","width","height"],function(a,c){p.attrHooks[c]=p.extend(p.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),p.support.style||(p.attrHooks.style={get:function(a){return 
 a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=b+""}}),p.support.optSelected||(p.propHooks.selected=p.extend(p.propHooks.selected,{get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}})),p.support.enctype||(p.propFix.enctype="encoding"),p.support.checkOn||p.each(["radio","checkbox"],function(){p.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),p.each(["radio","checkbox"],function(){p.valHooks[this]=p.extend(p.valHooks[this],{set:function(a,b){if(p.isArray(b))return a.checked=p.inArray(p(a).val(),b)>=0}})});var V=/^(?:textarea|input|select)$/i,W=/^([^\.]*|)(?:\.(.+)|)$/,X=/(?:^|\s)hover(\.\S+|)\b/,Y=/^key/,Z=/^(?:mouse|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=function(a){return p.event.special.hover?a:a.replace(X,"mouseenter$1 mouseleave$1")};p.event={add:function(a,c,d,e,f){var g,h,i,j,k,l,m,n,o,q,r;if(a.nodeType===3||a.nodeType===8||!c|
 |!d||!(g=p._data(a)))return;d.handler&&(o=d,d=o.handler,f=o.selector),d.guid||(d.guid=p.guid++),i=g.events,i||(g.events=i={}),h=g.handle,h||(g.handle=h=function(a){return typeof p!="undefined"&&(!a||p.event.triggered!==a.type)?p.event.dispatch.apply(h.elem,arguments):b},h.elem=a),c=p.trim(_(c)).split(" ");for(j=0;j<c.length;j++){k=W.exec(c[j])||[],l=k[1],m=(k[2]||"").split(".").sort(),r=p.event.special[l]||{},l=(f?r.delegateType:r.bindType)||l,r=p.event.special[l]||{},n=p.extend({type:l,origType:k[1],data:e,handler:d,guid:d.guid,selector:f,needsContext:f&&p.expr.match.needsContext.test(f),namespace:m.join(".")},o),q=i[l];if(!q){q=i[l]=[],q.delegateCount=0;if(!r.setup||r.setup.call(a,e,m,h)===!1)a.addEventListener?a.addEventListener(l,h,!1):a.attachEvent&&a.attachEvent("on"+l,h)}r.add&&(r.add.call(a,n),n.handler.guid||(n.handler.guid=d.guid)),f?q.splice(q.delegateCount++,0,n):q.push(n),p.event.global[l]=!0}a=null},global:{},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,q
 ,r=p.hasData(a)&&p._data(a);if(!r||!(m=r.events))return;b=p.trim(_(b||"")).split(" ");for(f=0;f<b.length;f++){g=W.exec(b[f])||[],h=i=g[1],j=g[2];if(!h){for(h in m)p.event.remove(a,h+b[f],c,d,!0);continue}n=p.event.special[h]||{},h=(d?n.delegateType:n.bindType)||h,o=m[h]||[],k=o.length,j=j?new RegExp("(^|\\.)"+j.split(".").sort().join("\\.(?:.*\\.|)")+"(\\.|$)"):null;for(l=0;l<o.length;l++)q=o[l],(e||i===q.origType)&&(!c||c.guid===q.guid)&&(!j||j.test(q.namespace))&&(!d||d===q.selector||d==="**"&&q.selector)&&(o.splice(l--,1),q.selector&&o.delegateCount--,n.remove&&n.remove.call(a,q));o.length===0&&k!==o.length&&((!n.teardown||n.teardown.call(a,j,r.handle)===!1)&&p.removeEvent(a,h,r.handle),delete m[h])}p.isEmptyObject(m)&&(delete r.handle,p.removeData(a,"events",!0))},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,f,g){if(!f||f.nodeType!==3&&f.nodeType!==8){var h,i,j,k,l,m,n,o,q,r,s=c.type||c,t=[];if($.test(s+p.event.triggered))return;s.indexOf("!")>=
 0&&(s=s.slice(0,-1),i=!0),s.indexOf(".")>=0&&(t=s.split("."),s=t.shift(),t.sort());if((!f||p.event.customEvent[s])&&!p.event.global[s])return;c=typeof c=="object"?c[p.expando]?c:new p.Event(s,c):new p.Event(s),c.type=s,c.isTrigger=!0,c.exclusive=i,c.namespace=t.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,m=s.indexOf(":")<0?"on"+s:"";if(!f){h=p.cache;for(j in h)h[j].events&&h[j].events[s]&&p.event.trigger(c,d,h[j].handle.elem,!0);return}c.result=b,c.target||(c.target=f),d=d!=null?p.makeArray(d):[],d.unshift(c),n=p.event.special[s]||{};if(n.trigger&&n.trigger.apply(f,d)===!1)return;q=[[f,n.bindType||s]];if(!g&&!n.noBubble&&!p.isWindow(f)){r=n.delegateType||s,k=$.test(r+s)?f:f.parentNode;for(l=f;k;k=k.parentNode)q.push([k,r]),l=k;l===(f.ownerDocument||e)&&q.push([l.defaultView||l.parentWindow||a,r])}for(j=0;j<q.length&&!c.isPropagationStopped();j++)k=q[j][0],c.type=q[j][1],o=(p._data(k,"events")||{})[c.type]&&p._data(k,"hand
 le"),o&&o.apply(k,d),o=m&&k[m],o&&p.acceptData(k)&&o.apply&&o.apply(k,d)===!1&&c.preventDefault();return c.type=s,!g&&!c.isDefaultPrevented()&&(!n._default||n._default.apply(f.ownerDocument,d)===!1)&&(s!=="click"||!p.nodeName(f,"a"))&&p.acceptData(f)&&m&&f[s]&&(s!=="focus"&&s!=="blur"||c.target.offsetWidth!==0)&&!p.isWindow(f)&&(l=f[m],l&&(f[m]=null),p.event.triggered=s,f[s](),p.event.triggered=b,l&&(f[m]=l)),c.result}return},dispatch:function(c){c=p.event.fix(c||a.event);var d,e,f,g,h,i,j,l,m,n,o=(p._data(this,"events")||{})[c.type]||[],q=o.delegateCount,r=k.call(arguments),s=!c.exclusive&&!c.namespace,t=p.event.special[c.type]||{},u=[];r[0]=c,c.delegateTarget=this;if(t.preDispatch&&t.preDispatch.call(this,c)===!1)return;if(q&&(!c.button||c.type!=="click"))for(f=c.target;f!=this;f=f.parentNode||this)if(f.disabled!==!0||c.type!=="click"){h={},j=[];for(d=0;d<q;d++)l=o[d],m=l.selector,h[m]===b&&(h[m]=l.needsContext?p(m,this).index(f)>=0:p.find(m,this,null,[f]).length),h[m]&&j.
 push(l);j.length&&u.push({elem:f,matches:j})}o.length>q&&u.push({elem:this,matches:o.slice(q)});for(d=0;d<u.length&&!c.isPropagationStopped();d++){i=u[d],c.currentTarget=i.elem;for(e=0;e<i.matches.length&&!c.isImmediatePropagationStopped();e++){l=i.matches[e];if(s||!c.namespace&&!l.namespace||c.namespace_re&&c.namespace_re.test(l.namespace))c.data=l.data,c.handleObj=l,g=((p.event.special[l.origType]||{}).handle||l.handler).apply(i.elem,r),g!==b&&(c.result=g,g===!1&&(c.preventDefault(),c.stopPropagation()))}}return t.postDispatch&&t.postDispatch.call(this,c),c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX off
 setY pageX pageY screenX screenY toElement".split(" "),filter:function(a,c){var d,f,g,h=c.button,i=c.fromElement;return a.pageX==null&&c.clientX!=null&&(d=a.target.ownerDocument||e,f=d.documentElement,g=d.body,a.pageX=c.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=c.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?c.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0),a}},fix:function(a){if(a[p.expando])return a;var b,c,d=a,f=p.event.fixHooks[a.type]||{},g=f.props?this.props.concat(f.props):this.props;a=p.Event(d);for(b=g.length;b;)c=g[--b],a[c]=d[c];return a.target||(a.target=d.srcElement||e),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,f.filter?f.filter(a,d):a},special:{load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){p.isWindow(this)&&(this
 .onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=p.extend(new p.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?p.event.trigger(e,null,b):p.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},p.event.handle=p.event.dispatch,p.removeEvent=e.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]=="undefined"&&(a[d]=null),a.detachEvent(d,c))},p.Event=function(a,b){if(this instanceof p.Event)a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?bb:ba):this.type=a,b&&p.extend(this,b),this.timeStamp=a&&a.timeStamp||p.now(),this[p.expando]=!0;else return new p.Event(a,b)},p.Event.prototype={preventDefault:function(){this.isDefaultPrevented=bb;var a=this.originalEvent;if(!a)return;a.prevent
 Default?a.preventDefault():a.returnValue=!1},stopPropagation:function(){this.isPropagationStopped=bb;var a=this.originalEvent;if(!a)return;a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=bb,this.stopPropagation()},isDefaultPrevented:ba,isPropagationStopped:ba,isImmediatePropagationStopped:ba},p.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){p.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj,g=f.selector;if(!e||e!==d&&!p.contains(d,e))a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b;return c}}}),p.support.submitBubbles||(p.event.special.submit={setup:function(){if(p.nodeName(this,"form"))return!1;p.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=p.nodeName(c,"input")||p.nodeName(c,"button")?c.form:b;d&&!p._data(d,"_submit_attached")&&(p.event.add(d,"submit._submit",function(a){a.
 _submit_bubble=!0}),p._data(d,"_submit_attached",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&p.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){if(p.nodeName(this,"form"))return!1;p.event.remove(this,"._submit")}}),p.support.changeBubbles||(p.event.special.change={setup:function(){if(V.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")p.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),p.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),p.event.simulate("change",this,a,!0)});return!1}p.event.add(this,"beforeactivate._change",function(a){var b=a.target;V.test(b.nodeName)&&!p._data(b,"_change_attached")&&(p.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&p.event.simulate("change",this.parentNode,a,!0)}),p._data(b,"_chang
 e_attached",!0))})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){return p.event.remove(this,"._change"),!V.test(this.nodeName)}}),p.support.focusinBubbles||p.each({focus:"focusin",blur:"focusout"},function(a,b){var c=0,d=function(a){p.event.simulate(b,a.target,p.event.fix(a),!0)};p.event.special[b]={setup:function(){c++===0&&e.addEventListener(a,d,!0)},teardown:function(){--c===0&&e.removeEventListener(a,d,!0)}}}),p.fn.extend({on:function(a,c,d,e,f){var g,h;if(typeof a=="object"){typeof c!="string"&&(d=d||c,c=b);for(h in a)this.on(h,c,d,a[h],f);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=ba;else if(!e)return this;return f===1&&(g=e,e=function(a){return p().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=p.guid++)),this.each(function(){p.event.add(this,a,e,d,c)})},one:fun
 ction(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,c,d){var e,f;if(a&&a.preventDefault&&a.handleObj)return e=a.handleObj,p(a.delegateTarget).off(e.namespace?e.origType+"."+e.namespace:e.origType,e.selector,e.handler),this;if(typeof a=="object"){for(f in a)this.off(f,c,a[f]);return this}if(c===!1||typeof c=="function")d=c,c=b;return d===!1&&(d=ba),this.each(function(){p.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){return p(this.context).on(a,this.selector,b,c),this},die:function(a,b){return p(this.context).off(a,this.selector||"**",b),this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length===1?this.off(a,"**"):this.off(b,a||"**",c)},trigger:function(a,b){return this.each(function(){p.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return p.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=argum
 ents,c=a.guid||p.guid++,d=0,e=function(c){var e=(p._data(this,"lastToggle"+a.guid)||0)%d;return p._data(this,"lastToggle"+a.guid,e+1),c.preventDefault(),b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),p.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){p.fn[b]=function(a,c){return c==null&&(c=a,a=null),arguments.length>0?this.on(b,null,a,c):this.trigger(b)},Y.test(b)&&(p.event.fixHooks[b]=p.event.keyHooks),Z.test(b)&&(p.event.fixHooks[b]=p.event.mouseHooks)}),function(a,b){function bc(a,b,c,d){c=c||[],b=b||r;var e,f,i,j,k=b.nodeType;if(!a||typeof a!="string")return c;if(k!==1&&k!==9)return[];i=g(b);if(!i&&!d)if(e=P.exec(a))if(j=e[1]){if(k===9){f=b.getElementById(j);if(!f||!f.parentNode)
 return c;if(f.id===j)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(j))&&h(b,f)&&f.id===j)return c.push(f),c}else{if(e[2])return w.apply(c,x.call(b.getElementsByTagName(a),0)),c;if((j=e[3])&&_&&b.getElementsByClassName)return w.apply(c,x.call(b.getElementsByClassName(j),0)),c}return bp(a.replace(L,"$1"),b,c,d,i)}function bd(a){return function(b){var c=b.nodeName.toLowerCase();return c==="input"&&b.type===a}}function be(a){return function(b){var c=b.nodeName.toLowerCase();return(c==="input"||c==="button")&&b.type===a}}function bf(a){return z(function(b){return b=+b,z(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function bg(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}function bh(a,b){var c,d,f,g,h,i,j,k=C[o][a];if(k)return b?0:k.slice(0);h=a,i=[],j=e.preFilter;while(h){if(!c||(d=M.exec(h)))d&&(h=h.slice(d[0].length)),i.push(f=[]);c=!1;if(d=N.exe
 c(h))f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=d[0].replace(L," ");for(g in e.filter)(d=W[g].exec(h))&&(!j[g]||(d=j[g](d,r,!0)))&&(f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=g,c.matches=d);if(!c)break}return b?h.length:h?bc.error(a):C(a,i).slice(0)}function bi(a,b,d){var e=b.dir,f=d&&b.dir==="parentNode",g=u++;return b.first?function(b,c,d){while(b=b[e])if(f||b.nodeType===1)return a(b,c,d)}:function(b,d,h){if(!h){var i,j=t+" "+g+" ",k=j+c;while(b=b[e])if(f||b.nodeType===1){if((i=b[o])===k)return b.sizset;if(typeof i=="string"&&i.indexOf(j)===0){if(b.sizset)return b}else{b[o]=k;if(a(b,d,h))return b.sizset=!0,b;b.sizset=!1}}}else while(b=b[e])if(f||b.nodeType===1)if(a(b,d,h))return b}}function bj(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function bk(a,b,c,d,e){var f,g=[],h=0,i=a.length,j=b!=null;for(;h<i;h++)if(f=a[h])if(!c||c(f,d,e))g.push(f),j&&b.push(h);return g}function bl(a,b,c,d,e,f){retur
 n d&&!d[o]&&(d=bl(d)),e&&!e[o]&&(e=bl(e,f)),z(function(f,g,h,i){if(f&&e)return;var j,k,l,m=[],n=[],o=g.length,p=f||bo(b||"*",h.nodeType?[h]:h,[],f),q=a&&(f||!b)?bk(p,m,a,h,i):p,r=c?e||(f?a:o||d)?[]:g:q;c&&c(q,r,h,i);if(d){l=bk(r,n),d(l,[],h,i),j=l.length;while(j--)if(k=l[j])r[n[j]]=!(q[n[j]]=k)}if(f){j=a&&r.length;while(j--)if(k=r[j])f[m[j]]=!(g[m[j]]=k)}else r=bk(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):w.apply(g,r)})}function bm(a){var b,c,d,f=a.length,g=e.relative[a[0].type],h=g||e.relative[" "],i=g?1:0,j=bi(function(a){return a===b},h,!0),k=bi(function(a){return y.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==l)||((b=c).nodeType?j(a,c,d):k(a,c,d))}];for(;i<f;i++)if(c=e.relative[a[i].type])m=[bi(bj(m),c)];else{c=e.filter[a[i].type].apply(null,a[i].matches);if(c[o]){d=++i;for(;d<f;d++)if(e.relative[a[d].type])break;return bl(i>1&&bj(m),i>1&&a.slice(0,i-1).join("").replace(L,"$1"),c,i<d&&bm(a.slice(i,d)),d<f&&bm(a=a.slice(d)),d<f&&a.join(""))}m.push(c)}retur
 n bj(m)}function bn(a,b){var d=b.length>0,f=a.length>0,g=function(h,i,j,k,m){var n,o,p,q=[],s=0,u="0",x=h&&[],y=m!=null,z=l,A=h||f&&e.find.TAG("*",m&&i.parentNode||i),B=t+=z==null?1:Math.E;y&&(l=i!==r&&i,c=g.el);for(;(n=A[u])!=null;u++){if(f&&n){for(o=0;p=a[o];o++)if(p(n,i,j)){k.push(n);break}y&&(t=B,c=++g.el)}d&&((n=!p&&n)&&s--,h&&x.push(n))}s+=u;if(d&&u!==s){for(o=0;p=b[o];o++)p(x,q,i,j);if(h){if(s>0)while(u--)!x[u]&&!q[u]&&(q[u]=v.call(k));q=bk(q)}w.apply(k,q),y&&!h&&q.length>0&&s+b.length>1&&bc.uniqueSort(k)}return y&&(t=B,l=z),x};return g.el=0,d?z(g):g}function bo(a,b,c,d){var e=0,f=b.length;for(;e<f;e++)bc(a,b[e],c,d);return c}function bp(a,b,c,d,f){var g,h,j,k,l,m=bh(a),n=m.length;if(!d&&m.length===1){h=m[0]=m[0].slice(0);if(h.length>2&&(j=h[0]).type==="ID"&&b.nodeType===9&&!f&&e.relative[h[1].type]){b=e.find.ID(j.matches[0].replace(V,""),b,f)[0];if(!b)return c;a=a.slice(h.shift().length)}for(g=W.POS.test(a)?-1:h.length-1;g>=0;g--){j=h[g];if(e.relative[k=j.type])break
 ;if(l=e.find[k])if(d=l(j.matches[0].replace(V,""),R.test(h[0].type)&&b.parentNode||b,f)){h.splice(g,1),a=d.length&&h.join("");if(!a)return w.apply(c,x.call(d,0)),c;break}}}return i(a,m)(d,b,f,c,R.test(a)),c}function bq(){}var c,d,e,f,g,h,i,j,k,l,m=!0,n="undefined",o=("sizcache"+Math.random()).replace(".",""),q=String,r=a.document,s=r.documentElement,t=0,u=0,v=[].pop,w=[].push,x=[].slice,y=[].indexOf||function(a){var b=0,c=this.length;for(;b<c;b++)if(this[b]===a)return b;return-1},z=function(a,b){return a[o]=b==null||b,a},A=function(){var a={},b=[];return z(function(c,d){return b.push(c)>e.cacheLength&&delete a[b.shift()],a[c]=d},a)},B=A(),C=A(),D=A(),E="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",G=F.replace("w","w#"),H="([*^$|!~]?=)",I="\\["+E+"*("+F+")"+E+"*(?:"+H+E+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+G+")|)|)"+E+"*\\]",J=":("+F+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+I+")|[^:]|\\\\.)*|.*))\\)|)",K=":(even|odd|eq|gt|lt|nth|first|las
 t)(?:\\("+E+"*((?:-\\d)?\\d*)"+E+"*\\)|)(?=[^-]|$)",L=new RegExp("^"+E+"+|((?:^|[^\\\\])(?:\\\\.)*)"+E+"+$","g"),M=new RegExp("^"+E+"*,"+E+"*"),N=new RegExp("^"+E+"*([\\x20\\t\\r\\n\\f>+~])"+E+"*"),O=new RegExp(J),P=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,Q=/^:not/,R=/[\x20\t\r\n\f]*[+~]/,S=/:not\($/,T=/h\d/i,U=/input|select|textarea|button/i,V=/\\(?!\\)/g,W={ID:new RegExp("^#("+F+")"),CLASS:new RegExp("^\\.("+F+")"),NAME:new RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:new RegExp("^("+F.replace("w","w*")+")"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+J),POS:new RegExp(K,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+E+"*(even|odd|(([+-]|)(\\d*)n|)"+E+"*(?:([+-]|)"+E+"*(\\d+)|))"+E+"*\\)|)","i"),needsContext:new RegExp("^"+E+"*[>+~]|"+K,"i")},X=function(a){var b=r.createElement("div");try{return a(b)}catch(c){return!1}finally{b=null}},Y=X(function(a){return a.appendChild(r.createComment("")),!a.getElementsByTagName("*").length}),Z=X(function(a){return a.innerHT
 ML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!==n&&a.firstChild.getAttribute("href")==="#"}),$=X(function(a){a.innerHTML="<select></select>";var b=typeof a.lastChild.getAttribute("multiple");return b!=="boolean"&&b!=="string"}),_=X(function(a){return a.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",!a.getElementsByClassName||!a.getElementsByClassName("e").length?!1:(a.lastChild.className="e",a.getElementsByClassName("e").length===2)}),ba=X(function(a){a.id=o+0,a.innerHTML="<a name='"+o+"'></a><div name='"+o+"'></div>",s.insertBefore(a,s.firstChild);var b=r.getElementsByName&&r.getElementsByName(o).length===2+r.getElementsByName(o+0).length;return d=!r.getElementById(o),s.removeChild(a),b});try{x.call(s.childNodes,0)[0].nodeType}catch(bb){x=function(a){var b,c=[];for(;b=this[a];a++)c.push(b);return c}}bc.matches=function(a,b){return bc(a,null,null,b)},bc.matchesSelector=function(a,b){return bc(b,null,null,[a]).length>0},f=bc.getText=
 function(a){var b,c="",d=0,e=a.nodeType;if(e){if(e===1||e===9||e===11){if(typeof a.textContent=="string")return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=f(a)}else if(e===3||e===4)return a.nodeValue}else for(;b=a[d];d++)c+=f(b);return c},g=bc.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?b.nodeName!=="HTML":!1},h=bc.contains=s.contains?function(a,b){var c=a.nodeType===9?a.documentElement:a,d=b&&b.parentNode;return a===d||!!(d&&d.nodeType===1&&c.contains&&c.contains(d))}:s.compareDocumentPosition?function(a,b){return b&&!!(a.compareDocumentPosition(b)&16)}:function(a,b){while(b=b.parentNode)if(b===a)return!0;return!1},bc.attr=function(a,b){var c,d=g(a);return d||(b=b.toLowerCase()),(c=e.attrHandle[b])?c(a):d||$?a.getAttribute(b):(c=a.getAttributeNode(b),c?typeof a[b]=="boolean"?a[b]?b:null:c.specified?c.value:null:null)},e=bc.selectors={cacheLength:50,createPseudo:z,match:W,attrHandle:Z?{}:{href:function(a){return a.getAttribute("href",2
 )},type:function(a){return a.getAttribute("type")}},find:{ID:d?function(a,b,c){if(typeof b.getElementById!==n&&!c){var d=b.getElementById(a);return d&&d.parentNode?[d]:[]}}:function(a,c,d){if(typeof c.getElementById!==n&&!d){var e=c.getElementById(a);return e?e.id===a||typeof e.getAttributeNode!==n&&e.getAttributeNode("id").value===a?[e]:b:[]}},TAG:Y?function(a,b){if(typeof b.getElementsByTagName!==n)return b.getElementsByTagName(a)}:function(a,b){var c=b.getElementsByTagName(a);if(a==="*"){var d,e=[],f=0;for(;d=c[f];f++)d.nodeType===1&&e.push(d);return e}return c},NAME:ba&&function(a,b){if(typeof b.getElementsByName!==n)return b.getElementsByName(name)},CLASS:_&&function(a,b,c){if(typeof b.getElementsByClassName!==n&&!c)return b.getElementsByClassName(a)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(V,""),a[3]=(a[4]||a[5]||"").replace(V
 ,""),a[2]==="~="&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),a[1]==="nth"?(a[2]||bc.error(a[0]),a[3]=+(a[3]?a[4]+(a[5]||1):2*(a[2]==="even"||a[2]==="odd")),a[4]=+(a[6]+a[7]||a[2]==="odd")):a[2]&&bc.error(a[0]),a},PSEUDO:function(a){var b,c;if(W.CHILD.test(a[0]))return null;if(a[3])a[2]=a[3];else if(b=a[4])O.test(b)&&(c=bh(b,!0))&&(c=b.indexOf(")",b.length-c)-b.length)&&(b=b.slice(0,c),a[0]=a[0].slice(0,c)),a[2]=b;return a.slice(0,3)}},filter:{ID:d?function(a){return a=a.replace(V,""),function(b){return b.getAttribute("id")===a}}:function(a){return a=a.replace(V,""),function(b){var c=typeof b.getAttributeNode!==n&&b.getAttributeNode("id");return c&&c.value===a}},TAG:function(a){return a==="*"?function(){return!0}:(a=a.replace(V,"").toLowerCase(),function(b){return b.nodeName&&b.nodeName.toLowerCase()===a})},CLASS:function(a){var b=B[o][a];return b||(b=B(a,new RegExp("(^|"+E+")"+a+"("+E+"|$)"))),function(a){return b.test(a.className||typ
 eof a.getAttribute!==n&&a.getAttribute("class")||"")}},ATTR:function(a,b,c){return function(d,e){var f=bc.attr(d,a);return f==null?b==="!=":b?(f+="",b==="="?f===c:b==="!="?f!==c:b==="^="?c&&f.indexOf(c)===0:b==="*="?c&&f.indexOf(c)>-1:b==="$="?c&&f.substr(f.length-c.length)===c:b==="~="?(" "+f+" ").indexOf(c)>-1:b==="|="?f===c||f.substr(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d){return a==="nth"?function(a){var b,e,f=a.parentNode;if(c===1&&d===0)return!0;if(f){e=0;for(b=f.firstChild;b;b=b.nextSibling)if(b.nodeType===1){e++;if(a===b)break}}return e-=d,e===c||e%c===0&&e/c>=0}:function(b){var c=b;switch(a){case"only":case"first":while(c=c.previousSibling)if(c.nodeType===1)return!1;if(a==="first")return!0;c=b;case"last":while(c=c.nextSibling)if(c.nodeType===1)return!1;return!0}}},PSEUDO:function(a,b){var c,d=e.pseudos[a]||e.setFilters[a.toLowerCase()]||bc.error("unsupported pseudo: "+a);return d[o]?d(b):d.length>1?(c=[a,a,"",b],e.setFilters.hasOwnProperty(a.toLowerCa
 se())?z(function(a,c){var e,f=d(a,b),g=f.length;while(g--)e=y.call(a,f[g]),a[e]=!(c[e]=f[g])}):function(a){return d(a,0,c)}):d}},pseudos:{not:z(function(a){var b=[],c=[],d=i(a.replace(L,"$1"));return d[o]?z(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)if(f=g[h])a[h]=!(b[h]=f)}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:z(function(a){return function(b){return bc(a,b).length>0}}),contains:z(function(a){return function(b){return(b.textContent||b.innerText||f(b)).indexOf(a)>-1}}),enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&!!a.checked||b==="option"&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},parent:function(a){return!e.pseudos.empty(a)},empty:function(a){var b;a=a.firstChild;while(a){if(a.nodeName>"@"||(b=a.nodeType)===3||b===4)return!1;a=a.nextSibling}return!0},header:funct
 ion(a){return T.test(a.nodeName)},text:function(a){var b,c;return a.nodeName.toLowerCase()==="input"&&(b=a.type)==="text"&&((c=a.getAttribute("type"))==null||c.toLowerCase()===b)},radio:bd("radio"),checkbox:bd("checkbox"),file:bd("file"),password:bd("password"),image:bd("image"),submit:be("submit"),reset:be("reset"),button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&a.type==="button"||b==="button"},input:function(a){return U.test(a.nodeName)},focus:function(a){var b=a.ownerDocument;return a===b.activeElement&&(!b.hasFocus||b.hasFocus())&&(!!a.type||!!a.href)},active:function(a){return a===a.ownerDocument.activeElement},first:bf(function(a,b,c){return[0]}),last:bf(function(a,b,c){return[b-1]}),eq:bf(function(a,b,c){return[c<0?c+b:c]}),even:bf(function(a,b,c){for(var d=0;d<b;d+=2)a.push(d);return a}),odd:bf(function(a,b,c){for(var d=1;d<b;d+=2)a.push(d);return a}),lt:bf(function(a,b,c){for(var d=c<0?c+b:c;--d>=0;)a.push(d);return a}),gt:bf(function(a,b,c){fo
 r(var d=c<0?c+b:c;++d<b;)a.push(d);return a})}},j=s.compareDocumentPosition?function(a,b){return a===b?(k=!0,0):(!a.compareDocumentPosition||!b.compareDocumentPosition?a.compareDocumentPosition:a.compareDocumentPosition(b)&4)?-1:1}:function(a,b){if(a===b)return k=!0,0;if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,h=b.parentNode,i=g;if(g===h)return bg(a,b);if(!g)return-1;if(!h)return 1;while(i)e.unshift(i),i=i.parentNode;i=h;while(i)f.unshift(i),i=i.parentNode;c=e.length,d=f.length;for(var j=0;j<c&&j<d;j++)if(e[j]!==f[j])return bg(e[j],f[j]);return j===c?bg(a,f[j],-1):bg(e[j],b,1)},[0,0].sort(j),m=!k,bc.uniqueSort=function(a){var b,c=1;k=m,a.sort(j);if(k)for(;b=a[c];c++)b===a[c-1]&&a.splice(c--,1);return a},bc.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},i=bc.compile=function(a,b){var c,d=[],e=[],f=D[o][a];if(!f){b||(b=bh(a)),c=b.length;while(c--)f=bm(b[c]),f[o]?d.push(f):e.push(f);f=D(a,
 bn(e,d))}return f},r.querySelectorAll&&function(){var a,b=bp,c=/'|\\/g,d=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,e=[":focus"],f=[":active",":focus"],h=s.matchesSelector||s.mozMatchesSelector||s.webkitMatchesSelector||s.oMatchesSelector||s.msMatchesSelector;X(function(a){a.innerHTML="<select><option selected=''></option></select>",a.querySelectorAll("[selected]").length||e.push("\\["+E+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),a.querySelectorAll(":checked").length||e.push(":checked")}),X(function(a){a.innerHTML="<p test=''></p>",a.querySelectorAll("[test^='']").length&&e.push("[*^$]="+E+"*(?:\"\"|'')"),a.innerHTML="<input type='hidden'/>",a.querySelectorAll(":enabled").length||e.push(":enabled",":disabled")}),e=new RegExp(e.join("|")),bp=function(a,d,f,g,h){if(!g&&!h&&(!e||!e.test(a))){var i,j,k=!0,l=o,m=d,n=d.nodeType===9&&a;if(d.nodeType===1&&d.nodeName.toLowerCase()!=="object"){i=bh(a),(k=d.getAttribute("id"))?l=k.replace(c,"\\$&"):d.setAttrib
 ute("id",l),l="[id='"+l+"'] ",j=i.length;while(j--)i[j]=l+i[j].join("");m=R.test(a)&&d.parentNode||d,n=i.join(",")}if(n)try{return w.apply(f,x.call(m.querySelectorAll(n),0)),f}catch(p){}finally{k||d.removeAttribute("id")}}return b(a,d,f,g,h)},h&&(X(function(b){a=h.call(b,"div");try{h.call(b,"[test!='']:sizzle"),f.push("!=",J)}catch(c){}}),f=new RegExp(f.join("|")),bc.matchesSelector=function(b,c){c=c.replace(d,"='$1']");if(!g(b)&&!f.test(c)&&(!e||!e.test(c)))try{var i=h.call(b,c);if(i||a||b.document&&b.document.nodeType!==11)return i}catch(j){}return bc(c,null,null,[b]).length>0})}(),e.pseudos.nth=e.pseudos.eq,e.filters=bq.prototype=e.pseudos,e.setFilters=new bq,bc.attr=p.attr,p.find=bc,p.expr=bc.selectors,p.expr[":"]=p.expr.pseudos,p.unique=bc.uniqueSort,p.text=bc.getText,p.isXMLDoc=bc.isXML,p.contains=bc.contains}(a);var bc=/Until$/,bd=/^(?:parents|prev(?:Until|All))/,be=/^.[^:#\[\.,]*$/,bf=p.expr.match.needsContext,bg={children:!0,contents:!0,next:!0,prev:!0};p.fn.extend(
 {find:function(a){var b,c,d,e,f,g,h=this;if(typeof a!="string")return p(a).filter(function(){for(b=0,c=h.length;b<c;b++)if(p.contains(h[b],this))return!0});g=this.pushStack("","find",a);for(b=0,c=this.length;b<c;b++){d=g.length,p.find(a,this[b],g);if(b>0)for(e=d;e<g.length;e++)for(f=0;f<d;f++)if(g[f]===g[e]){g.splice(e--,1);break}}return g},has:function(a){var b,c=p(a,this),d=c.length;return this.filter(function(){for(b=0;b<d;b++)if(p.contains(this,c[b]))return!0})},not:function(a){return this.pushStack(bj(this,a,!1),"not",a)},filter:function(a){return this.pushStack(bj(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?bf.test(a)?p(a,this.context).index(this[0])>=0:p.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c,d=0,e=this.length,f=[],g=bf.test(a)||typeof a!="string"?p(a,b||this.context):0;for(;d<e;d++){c=this[d];while(c&&c.ownerDocument&&c!==b&&c.nodeType!==11){if(g?g.index(c)>-1:p.find.matchesSelector(c,a)){f.push(c);brea
 k}c=c.parentNode}}return f=f.length>1?p.unique(f):f,this.pushStack(f,"closest",a)},index:function(a){return a?typeof a=="string"?p.inArray(this[0],p(a)):p.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?d:p.unique(d))},addBack:function(a){return this.add(a==null?this.prevObject:this.prevObject.filter(a))}}),p.fn.andSelf=p.fn.addBack,p.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return p.dir(a,"parentNode")},parentsUntil:function(a,b,c){return p.dir(a,"parentNode",c)},next:function(a){return bi(a,"nextSibling")},prev:function(a){return bi(a,"previousSibling")},nextAll:function(a){return p.dir(a,"nextSibling")},prevAll:function(a){return p.dir(a,"previousSibling")},nextUntil:function(a,b,c){return p.dir(a,"nextSibling",c)},prevUntil:function(a
 ,b,c){return p.dir(a,"previousSibling",c)},siblings:function(a){return p.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return p.sibling(a.firstChild)},contents:function(a){return p.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:p.merge([],a.childNodes)}},function(a,b){p.fn[a]=function(c,d){var e=p.map(this,b,c);return bc.test(a)||(d=c),d&&typeof d=="string"&&(e=p.filter(d,e)),e=this.length>1&&!bg[a]?p.unique(e):e,this.length>1&&bd.test(a)&&(e=e.reverse()),this.pushStack(e,a,k.call(arguments).join(","))}}),p.extend({filter:function(a,b,c){return c&&(a=":not("+a+")"),b.length===1?p.find.matchesSelector(b[0],a)?[b[0]]:[]:p.find.matches(a,b)},dir:function(a,c,d){var e=[],f=a[c];while(f&&f.nodeType!==9&&(d===b||f.nodeType!==1||!p(f).is(d)))f.nodeType===1&&e.push(f),f=f[c];return e},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var bl="abbr|article|aside|audio|bdi|canvas|data|datalist|details|f
 igcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",bm=/ jQuery\d+="(?:null|\d+)"/g,bn=/^\s+/,bo=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bp=/<([\w:]+)/,bq=/<tbody/i,br=/<|&#?\w+;/,bs=/<(?:script|style|link)/i,bt=/<(?:script|object|embed|option|style)/i,bu=new RegExp("<(?:"+bl+")[\\s/>]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,bz={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.ht
 mlSerialize||(bz._default=[1,"X<div>","</div>"]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).
 end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){
 var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1></$2>");try{for(;d<e;d++)c=this[d]||{},c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),c.innerHTML=a);c=0}catch(f){}}c&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(a){return bh(this[0])?this.length?this.pushStack(p(p.isFunction(a)?a():a),"replaceWith",a):this:p.isFunction(a)?this.each(function(b){var c=p(this),d=c.html();c.replaceWith(a.call(this,b,d))}):(type
 of a!="string"&&(a=p(a).detach()),this.each(function(){var b=this.nextSibling,c=this.parentNode;p(this).remove(),b?p(b).before(a):p(c).append(a)}))},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){a=[].concat.apply([],a);var e,f,g,h,i=0,j=a[0],k=[],l=this.length;if(!p.support.checkClone&&l>1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i<l;i++)d.call(c&&p.nodeName(this[i],"table")?bC(this[i],"tbody"):this[i],i===h?g:p.clone(g,!0,!0))}g=f=null,k.length&&p.each(k,function(a,b){b.src?p.ajax?p.ajax({url:b.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):p.error("no ajax"):p.globalEval((b.text||b.textContent||b.innerHTML||"").replace(by,""
 )),b.parentNode&&b.parentNode.removeChild(b)})}return this}}),p.buildFragment=function(a,c,d){var f,g,h,i=a[0];return c=c||e,c=!c.nodeType&&c[0]||c,c=c.ownerDocument||c,a.length===1&&typeof i=="string"&&i.length<512&&c===e&&i.charAt(0)==="<"&&!bt.test(i)&&(p.support.checkClone||!bw.test(i))&&(p.support.html5Clone||!bu.test(i))&&(g=!0,f=p.fragments[i],h=f!==b),f||(f=c.createDocumentFragment(),p.clean(a,c,f,d),g&&(p.fragments[i]=h&&f)),{fragment:f,cacheable:g}},p.fragments={},p.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){p.fn[a]=function(c){var d,e=0,f=[],g=p(c),h=g.length,i=this.length===1&&this[0].parentNode;if((i==null||i&&i.nodeType===11&&i.childNodes.length===1)&&h===1)return g[b](this[0]),this;for(;e<h;e++)d=(e>0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu
 .test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=b===e&&bA,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(f=0;(h=a[f])!=null;f++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{s=s||bk(b),l=b.createElement("div"),s.appendChild(l),h=h.replace(bo,"<$1></$2>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]==="<table>"&&!m?l.childNodes:[];for(g=n.length-1;g>=0;--g)p.nodeName(n[g],"tb
 ody")&&!n[g].childNodes.length&&n[g].parentNode.removeChild(n[g])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l.parentNode.removeChild(l)}h.nodeType?t.push(h):p.merge(t,h)}l&&(h=l=s=null);if(!p.support.appendChecked)for(f=0;(h=t[f])!=null;f++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(f=0;(h=t[f])!=null;f++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[f+1,0].concat(r)),f+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(
 f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.chrome?b.webkit=!0:b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=
 ([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^(none|table(?!-c[ea]).+)/,bO=/^margin/,bP=new RegExp("^("+q+")(.*)$","i"),bQ=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bR=new RegExp("^([-+])=("+q+")","i"),bS={},bT={position:"absolute",visibility:"hidden",display:"block"},bU={letterSpacing:0,fontWeight:400},bV=["Top","Right","Bottom","Left"],bW=["Webkit","O","Moz","ms"],bX=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return b$(this,!0)},hide:function(){return b$(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bX.apply(this,arguments):this.each(function(){(c?a:bZ(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":
 p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bY(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bR.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bY(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bU&&(f=bU[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(b,c){var d,e,f,g,h=a.getC
 omputedStyle(b,null),i=b.style;return h&&(d=h[c],d===""&&!p.contains(b.ownerDocument,b)&&(d=p.style(b,c)),bQ.test(d)&&bO.test(c)&&(e=i.width,f=i.minWidth,g=i.maxWidth,i.minWidth=i.maxWidth=i.width=d,d=h.width,i.width=e,i.minWidth=f,i.maxWidth=g)),d}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bQ.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth===0&&bN.test(bH(a,"display"))?p.swap(a,bT,function(){return cb(a,b,d)}):cb(a,b,d)},set:function(a,c,d){return b_(a,c,d?ca(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.curren
 tStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bQ.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hid
 den(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bV[d]+b]=e[d]||e[d-2]||e[0];return f}},bO.test(a)||(p.cssHooks[a+b].set=b_)});var cd=/%20/g,ce=/\[\]$/,cf=/\r?\n/g,cg=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ch=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ch.test(this.nodeName)||cg.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(cf,"\r\n")}}):{name:b.name,value:c.replace(cf,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b
 ():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ci(d,a[d],c,f);return e.join("&").replace(cd,"+")};var cj,ck,cl=/#.*$/,cm=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,co=/^(?:GET|HEAD)$/,cp=/^\/\//,cq=/\?/,cr=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,cs=/([?&])_=[^&]*/,ct=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,cu=p.fn.load,cv={},cw={},cx=["*/"]+["*"];try{ck=f.href}catch(cy){ck=e.createElement("a"),ck.href="",ck=ck.href}cj=ct.exec(ck.toLowerCase())||[],p.fn.load=function(a,c,d){if(typeof a!="string"&&cu)return cu.apply(this,arguments);if(!this.length)return this;var e,f,g,h=this,i=a.indexOf(" ");return i>=0&&(e=a.slice(i,a.length),a=a.slice(0,i)),p.isFunction(c)?(d=c,c=b):c&&typeof c=="object"&&(f="POST"),p.ajax({u
 rl:a,type:f,dataType:"html",data:c,complete:function(a,b){d&&h.each(d,g||[a.responseText,b,a])}}).done(function(a){g=arguments,h.html(e?p("<div>").append(a.replace(cr,"")).find(e):a)}),this},p.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){p.fn[b]=function(a){return this.on(b,a)}}),p.each(["get","post"],function(a,c){p[c]=function(a,d,e,f){return p.isFunction(d)&&(f=f||e,e=d,d=b),p.ajax({type:c,url:a,data:d,success:e,dataType:f})}}),p.extend({getScript:function(a,c){return p.get(a,b,c,"script")},getJSON:function(a,b,c){return p.get(a,b,c,"json")},ajaxSetup:function(a,b){return b?cB(a,p.ajaxSettings):(b=a,a=p.ajaxSettings),cB(a,b),a},ajaxSettings:{url:ck,isLocal:cn.test(cj[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":cx},contents:{xml:/xml/,htm
 l:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:cz(cv),ajaxTransport:cz(cw),ajax:function(a,c){function y(a,c,f,i){var k,s,t,u,w,y=c;if(v===2)return;v=2,h&&clearTimeout(h),g=b,e=i||"",x.readyState=a>0?4:0,f&&(u=cC(l,x,f));if(a>=200&&a<300||a===304)l.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(p.lastModified[d]=w),w=x.getResponseHeader("Etag"),w&&(p.etag[d]=w)),a===304?(y="notmodified",k=!0):(k=cD(l,u),y=k.state,s=k.data,t=k.error,k=!t);else{t=y;if(!y||a)y="error",a<0&&(a=0)}x.status=a,x.statusText=(c||y)+"",k?o.resolveWith(m,[s,y,x]):o.rejectWith(m,[x,y,t]),x.statusCode(r),r=b,j&&n.trigger("ajax"+(k?"Success":"Error"),[x,l,k?s:t]),q.fireWith(m,[x,y]),j&&(n.trigger("ajaxComplete",[x,l]),--p.active||p.event.trigger("ajaxStop"))}typeof a=="object"&&(c=a,a=b),c=c||{};var d,e,f,g,h,i,j,k,l=p.ajaxSetup({
 },c),m=l.context||l,n=m!==l&&(m.nodeType||m instanceof p)?p(m):p.event,o=p.Deferred(),q=p.Callbacks("once memory"),r=l.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,setRequestHeader:function(a,b){if(!v){var c=a.toLowerCase();a=u[c]=u[c]||a,t[a]=b}return this},getAllResponseHeaders:function(){return v===2?e:null},getResponseHeader:function(a){var c;if(v===2){if(!f){f={};while(c=cm.exec(e))f[c[1].toLowerCase()]=c[2]}c=f[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){return v||(l.mimeType=a),this},abort:function(a){return a=a||w,g&&g.abort(a),y(0,a),this}};o.promise(x),x.success=x.done,x.error=x.fail,x.complete=q.add,x.statusCode=function(a){if(a){var b;if(v<2)for(b in a)r[b]=[r[b],a[b]];else b=a[x.status],x.always(b)}return this},l.url=((a||l.url)+"").replace(cl,"").replace(cp,cj[1]+"//"),l.dataTypes=p.trim(l.dataType||"*").toLowerCase().split(s),l.crossDomain==null&&(i=ct.exec(l.url.toLowerCase())||!1,l.crossDomain=i&&i.join(":")+(i[3]?"":i[1]=
 =="http:"?80:443)!==cj.join(":")+(cj[3]?"":cj[1]==="http:"?80:443)),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),cA(cv,l,c,x);if(v===2)return x;j=l.global,l.type=l.type.toUpperCase(),l.hasContent=!co.test(l.type),j&&p.active++===0&&p.event.trigger("ajaxStart");if(!l.hasContent){l.data&&(l.url+=(cq.test(l.url)?"&":"?")+l.data,delete l.data),d=l.url;if(l.cache===!1){var z=p.now(),A=l.url.replace(cs,"$1_="+z);l.url=A+(A===l.url?(cq.test(l.url)?"&":"?")+"_="+z:"")}}(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",l.contentType),l.ifModified&&(d=d||l.url,p.lastModified[d]&&x.setRequestHeader("If-Modified-Since",p.lastModified[d]),p.etag[d]&&x.setRequestHeader("If-None-Match",p.etag[d])),x.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+cx+"; q=0.01":""):l.accepts["*"]);for(k in l.headers)x.setRequestHeader(k,l.headers[k]);i
 f(!l.beforeSend||l.beforeSend.call(m,x,l)!==!1&&v!==2){w="abort";for(k in{success:1,error:1,complete:1})x[k](l[k]);g=cA(cw,l,c,x);if(!g)y(-1,"No Transport");else{x.readyState=1,j&&n.trigger("ajaxSend",[x,l]),l.async&&l.timeout>0&&(h=setTimeout(function(){x.abort("timeout")},l.timeout));try{v=1,g.send(t,y)}catch(B){if(v<2)y(-1,B);else throw B}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var cE=[],cF=/\?/,cG=/(=)\?(?=&|$)|\?\?/,cH=p.now();p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=cE.pop()||p.expando+"_"+cH++;return this[a]=!0,a}}),p.ajaxPrefilter("json jsonp",function(c,d,e){var f,g,h,i=c.data,j=c.url,k=c.jsonp!==!1,l=k&&cG.test(j),m=k&&!l&&typeof i=="string"&&!(c.contentType||"").indexOf("application/x-www-form-urlencoded")&&cG.test(i);if(c.dataTypes[0]==="jsonp"||l||m)return f=c.jsonpCallback=p.isFunction(c.jsonpCallback)?c.jsonpCallback():c.jsonpCallback,g=a[f],l?c.url=j.replace(cG,"$1"+f):m?c.data=i.replace(cG,"$1"+f):k&&(c.url+=(cF.tes
 t(j)?"&":"?")+c.jsonp+"="+f),c.converters["script json"]=function(){return h||p.error(f+" was not called"),h[0]},c.dataTypes[0]="json",a[f]=function(){h=arguments},e.always(function(){a[f]=g,c[f]&&(c.jsonpCallback=d.jsonpCallback,cE.push(f)),h&&p.isFunction(g)&&g(h[0]),h=g=b}),"script"}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){return p.globalEval(a),a}}}),p.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),p.ajaxTransport("script",function(a){if(a.crossDomain){var c,d=e.head||e.getElementsByTagName("head")[0]||e.documentElement;return{send:function(f,g){c=e.createElement("script"),c.async="async",a.scriptCharset&&(c.charset=a.scriptCharset),c.src=a.url,c.onload=c.onreadystatechange=function(a,e){if(e||!c.readyState||/loaded|complete/.test(c.readyState))c.onload=
 c.onreadystatechange=null,d&&c.parentNode&&d.removeChild(c),c=b,e||g(200,"success")},d.insertBefore(c,d.firstChild)},abort:function(){c&&c.onload(0,1)}}}});var cI,cJ=a.ActiveXObject?function(){for(var a in cI)cI[a](0,1)}:!1,cK=0;p.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cL()||cM()}:cL,function(a){p.extend(p.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(p.ajaxSettings.xhr()),p.support.ajax&&p.ajaxTransport(function(c){if(!c.crossDomain||p.support.cors){var d;return{send:function(e,f){var g,h,i=c.xhr();c.username?i.open(c.type,c.url,c.async,c.username,c.password):i.open(c.type,c.url,c.async);if(c.xhrFields)for(h in c.xhrFields)i[h]=c.xhrFields[h];c.mimeType&&i.overrideMimeType&&i.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(h in e)i.setRequestHeader(h,e[h])}catch(j){}i.send(c.hasContent&&c.data||null),d=function(a,e){var h,j,k,l,m;try{if(d&&(e||i.readyState===4)){d=b,g&&(i.
 onreadystatechange=p.noop,cJ&&delete cI[g]);if(e)i.readyState!==4&&i.abort();else{h=i.status,k=i.getAllResponseHeaders(),l={},m=i.responseXML,m&&m.documentElement&&(l.xml=m);try{l.text=i.responseText}catch(a){}try{j=i.statusText}catch(n){j=""}!h&&c.isLocal&&!c.crossDomain?h=l.text?200:404:h===1223&&(h=204)}}}catch(o){e||f(-1,o)}l&&f(h,j,l,k)},c.async?i.readyState===4?setTimeout(d,0):(g=++cK,cJ&&(cI||(cI={},p(a).unload(cJ)),cI[g]=d),i.onreadystatechange=d):d()},abort:function(){d&&d(0,1)}}}});var cN,cO,cP=/^(?:toggle|show|hide)$/,cQ=new RegExp("^(?:([-+])=|)("+q+")([a-z%]*)$","i"),cR=/queueHooks$/,cS=[cY],cT={"*":[function(a,b){var c,d,e=this.createTween(a,b),f=cQ.exec(b),g=e.cur(),h=+g||0,i=1,j=20;if(f){c=+f[2],d=f[3]||(p.cssNumber[a]?"":"px");if(d!=="px"&&h){h=p.css(e.elem,a,!0)||c||1;do i=i||".5",h=h/i,p.style(e.elem,a,h+d);while(i!==(i=e.cur()/g)&&i!==1&&--j)}e.unit=d,e.start=h,e.end=f[1]?h+(f[1]+1)*c:c}return e}]};p.Animation=p.extend(cW,{tweener:function(a,b){p.isFuncti
 on(a)?(b=a,a=["*"]):a=a.split(" ");var c,d=0,e=a.length;for(;d<e;d++)c=a[d],cT[c]=cT[c]||[],cT[c].unshift(b)},prefilter:function(a,b){b?cS.unshift(a):cS.push(a)}}),p.Tween=cZ,cZ.prototype={constructor:cZ,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(p.cssNumber[c]?"":"px")},cur:function(){var a=cZ.propHooks[this.prop];return a&&a.get?a.get(this):cZ.propHooks._default.get(this)},run:function(a){var b,c=cZ.propHooks[this.prop];return this.options.duration?this.pos=b=p.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):cZ.propHooks._default.set(this),this}},cZ.prototype.init.prototype=cZ.prototype,cZ.propHooks={_default:{get:function(a){var b;return a.elem[a.prop]==null||!!a.elem.style&&a.elem.style[a.prop]!=null?(b=
 p.css(a.elem,a.prop,!1,""),!b||b==="auto"?0:b):a.elem[a.prop]},set:function(a){p.fx.step[a.prop]?p.fx.step[a.prop](a):a.elem.style&&(a.elem.style[p.cssProps[a.prop]]!=null||p.cssHooks[a.prop])?p.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},cZ.propHooks.scrollTop=cZ.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},p.each(["toggle","show","hide"],function(a,b){var c=p.fn[b];p.fn[b]=function(d,e,f){return d==null||typeof d=="boolean"||!a&&p.isFunction(d)&&p.isFunction(e)?c.apply(this,arguments):this.animate(c$(b,!0),d,e,f)}}),p.fn.extend({fadeTo:function(a,b,c,d){return this.filter(bZ).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=p.isEmptyObject(a),f=p.speed(b,c,d),g=function(){var b=cW(this,p.extend({},a),f);e&&b.stop(!0)};return e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,c,d){var e=function(a){var b=a.stop;delete a.stop,b(d)};return typeof a!="s
 tring"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,c=a!=null&&a+"queueHooks",f=p.timers,g=p._data(this);if(c)g[c]&&g[c].stop&&e(g[c]);else for(c in g)g[c]&&g[c].stop&&cR.test(c)&&e(g[c]);for(c=f.length;c--;)f[c].elem===this&&(a==null||f[c].queue===a)&&(f[c].anim.stop(d),b=!1,f.splice(c,1));(b||!d)&&p.dequeue(this,a)})}}),p.each({slideDown:c$("show"),slideUp:c$("hide"),slideToggle:c$("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){p.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),p.speed=function(a,b,c){var d=a&&typeof a=="object"?p.extend({},a):{complete:c||!c&&b||p.isFunction(a)&&a,duration:a,easing:c&&b||b&&!p.isFunction(b)&&b};d.duration=p.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in p.fx.speeds?p.fx.speeds[d.duration]:p.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";return d.old=d.complete,d.complete=function(){p.isFunction(d.old)&&d.old.call(
 this),d.queue&&p.dequeue(this,d.queue)},d},p.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},p.timers=[],p.fx=cZ.prototype.init,p.fx.tick=function(){var a,b=p.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||p.fx.stop()},p.fx.timer=function(a){a()&&p.timers.push(a)&&!cO&&(cO=setInterval(p.fx.tick,p.fx.interval))},p.fx.interval=13,p.fx.stop=function(){clearInterval(cO),cO=null},p.fx.speeds={slow:600,fast:200,_default:400},p.fx.step={},p.expr&&p.expr.filters&&(p.expr.filters.animated=function(a){return p.grep(p.timers,function(b){return a===b.elem}).length});var c_=/^(?:body|html)$/i;p.fn.offset=function(a){if(arguments.length)return a===b?this:this.each(function(b){p.offset.setOffset(this,a,b)});var c,d,e,f,g,h,i,j={top:0,left:0},k=this[0],l=k&&k.ownerDocument;if(!l)return;return(d=l.body)===k?p.offset.bodyOffset(k):(c=l.documentElement,p.contains(c,k)?(typeof k.getBoundingClientRect!="undefined"&&(j=k.get
 BoundingClientRect()),e=da(l),f=c.clientTop||d.clientTop||0,g=c.clientLeft||d.clientLeft||0,h=e.pageYOffset||c.scrollTop,i=e.pageXOffset||c.scrollLeft,{top:j.top+h-f,left:j.left+i-g}):j)},p.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;return p.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(p.css(a,"marginTop"))||0,c+=parseFloat(p.css(a,"marginLeft"))||0),{top:b,left:c}},setOffset:function(a,b,c){var d=p.css(a,"position");d==="static"&&(a.style.position="relative");var e=p(a),f=e.offset(),g=p.css(a,"top"),h=p.css(a,"left"),i=(d==="absolute"||d==="fixed")&&p.inArray("auto",[g,h])>-1,j={},k={},l,m;i?(k=e.position(),l=k.top,m=k.left):(l=parseFloat(g)||0,m=parseFloat(h)||0),p.isFunction(b)&&(b=b.call(a,c,f)),b.top!=null&&(j.top=b.top-f.top+l),b.left!=null&&(j.left=b.left-f.left+m),"using"in b?b.using.call(a,j):e.css(j)}},p.fn.extend({position:function(){if(!this[0])return;var a=this[0],b=this.offsetParent(),c=this.offset(),d=c_.test(b[0].nodeName)?{to
 p:0,left:0}:b.offset();return c.top-=parseFloat(p.css(a,"marginTop"))||0,c.left-=parseFloat(p.css(a,"marginLeft"))||0,d.top+=parseFloat(p.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(p.css(b[0],"borderLeftWidth"))||0,{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||e.body;while(a&&!c_.test(a.nodeName)&&p.css(a,"position")==="static")a=a.offsetParent;return a||e.body})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);p.fn[a]=function(e){return p.access(this,function(a,e,f){var g=da(a);if(f===b)return g?c in g?g[c]:g.document.documentElement[e]:a[e];g?g.scrollTo(d?p(g).scrollLeft():f,d?f:p(g).scrollTop()):a[e]=f},a,e,arguments.length,null)}}),p.each({Height:"height",Width:"width"},function(a,c){p.each({padding:"inner"+a,content:c,"":"outer"+a},function(d,e){p.fn[e]=function(e,f){var g=arguments.length&&(d||typeof e!="boolean"),h=d||(e===!0||f===!0?"margin":"border"
 );return p.access(this,function(c,d,e){var f;return p.isWindow(c)?c.document.documentElement["client"+a]:c.nodeType===9?(f=c.documentElement,Math.max(c.body["scroll"+a],f["scroll"+a],c.body["offset"+a],f["offset"+a],f["client"+a])):e===b?p.css(c,d,e,h):p.style(c,d,e,h)},c,g?e:b,g,null)}})}),a.jQuery=a.$=p,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return p})})(window);
\ No newline at end of file


commit 41209e464c224f41157ca077334fcf281ec5178a
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Oct 12 13:35:49 2012 +0200

    Fix attribute form initialization on attribute edit

diff --git a/public_html/js/kolab_admin.js b/public_html/js/kolab_admin.js
index 09da5f8..b7fff8a 100644
--- a/public_html/js/kolab_admin.js
+++ b/public_html/js/kolab_admin.js
@@ -1785,7 +1785,7 @@ function kolab_admin()
   {
     var name_select = $('select[name="attr_name"]'),
       data = attr ? this.env.attr_table[attr] : {},
-      type = data.type ? data.type : 'text';
+      type = data.type || 'text';
 
     $('select[name="attr_type"]').val(type);
     $('select[name="attr_value"]').val(attr ? data.valtype : 'normal');
@@ -1799,7 +1799,6 @@ function kolab_admin()
       name_select.hide().val(attr);
       $('<span></span>').text(this.env.attributes[attr] ? this.env.attributes[attr] : attr)
         .appendTo(name_select.parent());
-      return;
     }
     else {
       this.type_attr_select_init();


commit dea2159a546097b627e4006830798dfd9776a378
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Oct 12 13:18:13 2012 +0200

    Fix interface/list refresh after type add/edit/delete action

diff --git a/public_html/js/kolab_admin.js b/public_html/js/kolab_admin.js
index b7b16c9..09da5f8 100644
--- a/public_html/js/kolab_admin.js
+++ b/public_html/js/kolab_admin.js
@@ -1664,7 +1664,7 @@ function kolab_admin()
     this.display_message(action + '.success');
 
     var page = this.env.list_page,
-      list_id = list.replace(/[^a-z]/, '');
+      list_id = list.replace(/[^a-z]/g, '');
 
     // if objects list exists
     if ($('#'+list_id).length) {


commit cfe3c326009b0e952fb4738af926a3eca2b61970
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Oct 12 13:05:16 2012 +0200

    Fix setting current value of types list filter

diff --git a/lib/client/kolab_client_task_settings.php b/lib/client/kolab_client_task_settings.php
index e0cb7fe..d5517e7 100644
--- a/lib/client/kolab_client_task_settings.php
+++ b/lib/client/kolab_client_task_settings.php
@@ -903,7 +903,7 @@ class kolab_client_task_settings extends kolab_client_task
             'name'     => 'type',
             'id'       => 'type_list_filter',
             'options'  => $options,
-            'value'    => $this->type_selected ? $this->type_selected : 'user',
+            'value'    => !empty($_POST['type']) ? $_POST['type'] : 'user',
             'onchange' => "kadm.command('settings.type_list')",
         );
 


commit 3c58cc3828c9b0f23aaa4542b4ab874ac8621f63
Author: Torsten Grote <grote at kolabsys.com>
Date:   Tue Oct 9 18:11:13 2012 +0200

    hide signup dropdown fields if there's one value

diff --git a/lib/client/kolab_client_task_signup.php b/lib/client/kolab_client_task_signup.php
index b0a58c9..8863c2b 100644
--- a/lib/client/kolab_client_task_signup.php
+++ b/lib/client/kolab_client_task_signup.php
@@ -204,13 +204,18 @@ class kolab_client_task_signup extends kolab_client_task
                 $accttypes[$idx] = array('value' => $idx, 'content' => $elem['name']);
             }
         }
-
         $fields['type_id'] = array(
             'section'  => 'personal',
             'type'     => kolab_form::INPUT_SELECT,
             'options'  => $accttypes,
             'onchange' => "kadm.change_user_type()",
         );
+        // Hide user types selector if only one is available
+        if(count($accttypes) <= 1) {
+            $accctype = array_shift(array_values($accttypes));
+            $fields['type_id']['type'] = kolab_form::INPUT_HIDDEN;
+            $fields['type_id']['value'] = $accttype['value'];
+        }
         
         // Add object type field
         $fields['object_type'] = array(
@@ -219,11 +224,17 @@ class kolab_client_task_signup extends kolab_client_task
         );
  
         // Add available domains
+        $domains = $this->get_domains();
         $fields['domain'] = array(
             'type'     => kolab_form::INPUT_SELECT,
-            'options'  => $this->get_domains(),
+            'options'  => $domains,
             'onchange' => 'kadm.check_user_availability()',
         );
+        // Hide domains if only one is available
+        if(count($domains) <= 1) {
+            $fields['domain']['type'] = kolab_form::INPUT_HIDDEN;
+            $fields['domain']['value'] = array_shift(array_values($domains));
+        }
 
         // Check for user availability
         $fields['uid']['onchange'] = 'kadm.check_user_availability()';


commit 3dce2c4d2e020ebcdd531fb049f5187155dff4c0
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Oct 8 12:47:02 2012 +0200

    Add links to default/images directory

diff --git a/public_html/skins/kolabsys/images b/public_html/skins/kolabsys/images
new file mode 120000
index 0000000..670e2a1
--- /dev/null
+++ b/public_html/skins/kolabsys/images
@@ -0,0 +1 @@
+../default/images
\ No newline at end of file
diff --git a/public_html/skins/minimal/images b/public_html/skins/minimal/images
new file mode 120000
index 0000000..670e2a1
--- /dev/null
+++ b/public_html/skins/minimal/images
@@ -0,0 +1 @@
+../default/images
\ No newline at end of file


commit 0ee3c679a536e5cc1fc0d8aa7584210c6daf5754
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Oct 8 12:34:41 2012 +0200

    Small fix in loading image

diff --git a/public_html/skins/default/images/loading.gif b/public_html/skins/default/images/loading.gif
index d838429..48623dc 100644
Binary files a/public_html/skins/default/images/loading.gif and b/public_html/skins/default/images/loading.gif differ


commit edd1ba93e19c2979134f027e064339bbc10cc925
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Oct 8 12:24:41 2012 +0200

    Small style fixes

diff --git a/public_html/skins/default/style.css b/public_html/skins/default/style.css
index de3e7ec..b4fa8e4 100644
--- a/public_html/skins/default/style.css
+++ b/public_html/skins/default/style.css
@@ -378,7 +378,6 @@ td.label {
   float: left;
   padding: 1px;
   margin-left: -1px;
-  margin-top: -1px;
   height: 18px;
   width: 37px;
   border: 1px solid #d0d0d0;
@@ -407,7 +406,6 @@ td.label {
 }
 
 #search-reset {
-  background-position: -1px 0;
   border-left: 1px solid #e0e0e0;
 }
 


commit d37d04e68dd5fb19c3daa6058548f08243970335
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Fri Oct 5 14:57:43 2012 +0100

    Add a bind() call to group_add() (#1078)

diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php
index b73c2a2..41b1bfa 100644
--- a/lib/Auth/LDAP.php
+++ b/lib/Auth/LDAP.php
@@ -270,6 +270,8 @@ class LDAP extends Net_LDAP3 {
         // Use [$type_str . "_"]user_rdn_attr
         $dn = "cn=" . $attrs['cn'] . "," . $base_dn;
 
+        $this->bind($_SESSION['user']->user_bind_dn,$_SESSION['user']->user_bind_pw);
+
         return $this->add_entry($dn, $attrs);
     }
 


commit 75c62a15864fd56e2338510a3637fccd6ce819ee
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Oct 5 13:18:11 2012 +0200

    Fix small code issues

diff --git a/lib/Auth.php b/lib/Auth.php
index ec705a9..bd5c908 100644
--- a/lib/Auth.php
+++ b/lib/Auth.php
@@ -388,7 +388,7 @@ class Auth {
 
     public function search()
     {
-        return call_user_func_array(Array($this->auth_instance(), search), func_get_args());
+        return call_user_func_array(Array($this->auth_instance(), 'search'), func_get_args());
     }
 
     public function user_add($attributes, $typeid = null)
diff --git a/lib/api/kolab_api_service_user.php b/lib/api/kolab_api_service_user.php
index 37c83af..578e102 100644
--- a/lib/api/kolab_api_service_user.php
+++ b/lib/api/kolab_api_service_user.php
@@ -129,11 +129,10 @@ class kolab_api_service_user extends kolab_api_service
         //console("\$postdata to user_edit()", $postdata);
 
         $user_attributes = $this->parse_input_attributes('user', $postdata);
+        $user            = $postdata['id'];
 
         //console("\$user_attributes as result from parse_input_attributes", $user_attributes);
 
-        $user            = $postdata['id'];
-
         $auth   = Auth::get_instance();
         $result = $auth->user_edit($user, $user_attributes, $postdata['type_id']);
 


commit cb8040b34c3438a55f7fd3791165803924075c44
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Oct 5 12:45:18 2012 +0200

    Fixes in Settings for read-only access to object types

diff --git a/lib/client/kolab_client_task_settings.php b/lib/client/kolab_client_task_settings.php
index 7ebfcee..e0cb7fe 100644
--- a/lib/client/kolab_client_task_settings.php
+++ b/lib/client/kolab_client_task_settings.php
@@ -49,7 +49,6 @@ class kolab_client_task_settings extends kolab_client_task
         }
         // otherwise display object types list
         else if (self::can_edit_types($caps_actions)) {
-            $this->output->set_object('content', 'type', true);
             $this->action_type_list();
             unset($this->menu['type_list']);
 
@@ -203,6 +202,9 @@ class kolab_client_task_settings extends kolab_client_task
             $post['search']          = $search_request;
             $post['search_operator'] = 'OR';
         }
+        else {
+            $this->output->set_object('content', 'type', true);
+        }
 
         // object type
         $type = self::get_input('type', 'POST');
@@ -444,7 +446,11 @@ class kolab_client_task_settings extends kolab_client_task
             $data['objectclass'] = array('top');
         }
 
-        $name     = 'type';
+        // Get the rights on the entry and attribute level
+        $data['effective_rights'] = $this->effective_rights('type', $data['id']);
+        $attribute_rights         = (array) $data['effective_rights']['attribute'];
+        $entry_rights             = (array) $data['effective_rights']['entry'];
+
         $add_mode = empty($data['id']);
         $fields   = array(
             'key' => array(
@@ -484,12 +490,6 @@ class kolab_client_task_settings extends kolab_client_task
             unset($form_fields['used_for']);
         }
 
-
-        // Get the rights on the entry and attribute level
-        $data['effective_rights'] = $this->effective_rights($name, $data['id']);
-        $attribute_rights         = $data['effective_rights']['attribute'];
-        $entry_rights             = $data['effective_rights']['entry'];
-
         // See if "administrators" (those who can delete and add back on the entry
         // level) may override the automatically generated contents of auto_form_fields.
         //$admin_auto_fields_rw = $this->config_get('admin_auto_fields_rw', false, Conf::BOOL);
@@ -586,10 +586,12 @@ class kolab_client_task_settings extends kolab_client_task
             'value' => array(
                 'body'  => $this->translate('attribute.value'),
             ),
-            'actions' => array(
-            ),
         );
 
+        if (!empty($data['effective_rights']['entry'])) {
+            $cells['actions'] = array();
+        }
+
         foreach ($cells as $idx => $cell) {
             $cells[$idx]['class'] = $idx;
         }
@@ -611,8 +613,10 @@ class kolab_client_task_settings extends kolab_client_task
         // table header
         $table['head'] = array(array('cells' => $cells));
 
-        $yes = $this->translate('yes');
-        $no  = '';
+        $rights = (array)$data['effective_rights']['attribute']['attributes'];
+        $yes    = $this->translate('yes');
+        $no     = '';
+
         // defined attributes
         foreach ($attributes as $attr) {
             $row          = $cells;
@@ -652,11 +656,22 @@ class kolab_client_task_settings extends kolab_client_task
             $row['value']['body']    = $value;
             $row['readonly']['body'] = $valtype == 'auto-readonly' ? $yes : $no;
             $row['optional']['body'] = $optional ? $yes : $no;
-            $row['actions']['body']  = 
-                kolab_html::a(array('href' => '#delete', 'onclick' => "kadm.type_attr_delete('$attr')",
-                    'class' => 'button delete', 'title' => $this->translate('delete')))
-                . kolab_html::a(array('href' => '#edit', 'onclick' => "kadm.type_attr_edit('$attr')",
-                    'class' => 'button edit', 'title' => $this->translate('edit')));
+
+            if (!empty($row['actions'])) {
+                $row['actions']['body']  = '';
+
+                if (in_array('delete', $rights)) {
+                    $row['actions']['body'] .= kolab_html::a(array(
+                        'href' => '#delete', 'onclick' => "kadm.type_attr_delete('$attr')",
+                        'class' => 'button delete', 'title' => $this->translate('delete')));
+                }
+
+                if (in_array('write', $rights)) {
+                    $row['actions']['body'] .= kolab_html::a(array(
+                        'href' => '#edit', 'onclick' => "kadm.type_attr_edit('$attr')",
+                        'class' => 'button edit', 'title' => $this->translate('edit')));
+                }
+            }
 
             $rows[] = array(
                 'id'    => 'attr_table_row_' . $attr,
@@ -698,10 +713,12 @@ class kolab_client_task_settings extends kolab_client_task
             'attribute.key.invalid', 'attribute.required.error');
 
         // Add attribute link
-        $link = kolab_html::a(array(
-            'href' => '#add_attr', 'class' => 'add_attr',
-            'onclick' => "kadm.type_attr_add()",
-            'content' =>  $this->translate('attribute.add')), true);
+        if (in_array('write', $rights)) {
+            $link = kolab_html::a(array(
+                'href' => '#add_attr', 'class' => 'add_attr',
+                'onclick' => "kadm.type_attr_add()",
+                'content' =>  $this->translate('attribute.add')), true);
+        }
 
         return kolab_html::table($table) . $link;
     }
diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php
index fec0404..d60f1ea 100644
--- a/lib/kolab_client_task.php
+++ b/lib/kolab_client_task.php
@@ -1248,7 +1248,7 @@ class kolab_client_task
             ));
         }
 
-        if (!empty($data['id']) && in_array('delete', $data['effective_rights']['entry'])) {
+        if (!empty($data['id']) && in_array('delete', (array) $data['effective_rights']['entry'])) {
             $id = $data['id'];
             $form->add_button(array(
                 'value'   => kolab_html::escape($this->translate('button.delete')),
diff --git a/public_html/skins/default/style.css b/public_html/skins/default/style.css
index 287aa33..de3e7ec 100644
--- a/public_html/skins/default/style.css
+++ b/public_html/skins/default/style.css
@@ -512,12 +512,15 @@ input.inactive {
 }
 
 .disabled,
-.readonly {
+.readonly,
+.select.readonly option {
   color: #a0a0a0;
 }
 
 input.disabled,
 input.readonly,
+select.disabled,
+select.readonly,
 textarea.disabled,
 textarea.readonly {
   background-color: #f5f5f5;


commit c547543b2264387e515159743cb6750c0fb4a628
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Oct 5 12:07:31 2012 +0200

    Fix PHP warning when using array_combine() with an empty array

diff --git a/lib/client/kolab_client_task_settings.php b/lib/client/kolab_client_task_settings.php
index bd3ea99..7ebfcee 100644
--- a/lib/client/kolab_client_task_settings.php
+++ b/lib/client/kolab_client_task_settings.php
@@ -820,7 +820,10 @@ class kolab_client_task_settings extends kolab_client_task
         if (!empty($response['list'])) {
             // remove objectClass
             $attributes = array_diff($response['list'], array('objectClass'));
-            $attributes = array_combine(array_map('strtolower', $attributes), $attributes);
+            $attributes = array_map('strtolower', $attributes);
+            if (count($attributes)) {
+                $attributes = array_combine($attributes, $attributes);
+            }
         }
 
         $this->output->set_env('attributes', $attributes);


commit e4ca2a8edd1896ae7ab5ab9e51eeae806273eaa7
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Oct 5 11:52:02 2012 +0200

    Fix domain searching - replace 'cn' with 'associateddomain' (Bug #1083)
    Remove email field from domain search form

diff --git a/lib/client/kolab_client_task_domain.php b/lib/client/kolab_client_task_domain.php
index edb716e..71a138a 100644
--- a/lib/client/kolab_client_task_domain.php
+++ b/lib/client/kolab_client_task_domain.php
@@ -312,8 +312,7 @@ class kolab_client_task_domain extends kolab_client_task
             'name'    => 'field',
             'type'    => kolab_form::INPUT_SELECT,
             'options' => array(
-                'cn'   => kolab_html::escape($this->translate('search.name')),
-                'mail' => kolab_html::escape($this->translate('search.email')),
+                'associateddomain' => kolab_html::escape($this->translate('search.name')),
             ),
         ));
         $form->add_element(array(


commit bf75f57de8354fdbf44a2f41af036e51d0675123
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Oct 5 11:29:55 2012 +0200

    Disable optional flag for required attributes

diff --git a/lib/client/kolab_client_task_settings.php b/lib/client/kolab_client_task_settings.php
index 87a03ab..bd3ea99 100644
--- a/lib/client/kolab_client_task_settings.php
+++ b/lib/client/kolab_client_task_settings.php
@@ -717,6 +717,7 @@ class kolab_client_task_settings extends kolab_client_task
             'name' => array(
                 'type' => kolab_form::INPUT_SELECT,
                 'options' => $attributes,
+                'onchange' => 'kadm.type_attr_name_change(this)',
             ),
             'type' => array(
                 'type' => kolab_form::INPUT_SELECT,
diff --git a/public_html/js/kolab_admin.js b/public_html/js/kolab_admin.js
index b6c9a72..b7b16c9 100644
--- a/public_html/js/kolab_admin.js
+++ b/public_html/js/kolab_admin.js
@@ -1793,20 +1793,22 @@ function kolab_admin()
     $('input[name="attr_data"]').val(attr ? data.data : '');
     $('input[name="attr_maxcount"]').val(data.maxcount ? data.maxcount : '');
     $('textarea[name="attr_options"]').val(data.values ? data.values.join("\n") : '');
-    this.form_element_update({name: 'attr_options'});
-
     $('span', name_select.parent()).remove();
-    this.type_attr_type_change('select[name="attr_type"]');
-    this.type_attr_value_change('select[name="attr_value"]');
 
     if (attr) {
       name_select.hide().val(attr);
-      $('<span></span>').text(this.env.attributes[attr] ? this.env.attributes[attr] : attr).appendTo(name_select.parent());
+      $('<span></span>').text(this.env.attributes[attr] ? this.env.attributes[attr] : attr)
+        .appendTo(name_select.parent());
       return;
     }
+    else {
+      this.type_attr_select_init();
+      name_select.show();
+    }
 
-    this.type_attr_select_init();
-    name_select.show();
+    this.form_element_update({name: 'attr_options'});
+    this.type_attr_type_change('select[name="attr_type"]');
+    this.type_attr_value_change('select[name="attr_value"]');
   };
 
   // Initialize attribute name selector
@@ -1822,13 +1824,28 @@ function kolab_admin()
     options.not(':disabled').first().attr('selected', true);
   };
 
+  // Update attribute form on attribute name change
+  this.type_attr_name_change = function(elem)
+  {
+    this.type_attr_value_change('select[name="attr_value"]');
+  };
+
   // Update attribute form on value type change
   this.type_attr_value_change = function(elem)
   {
-    var type = $(elem).val();
+    var type = $(elem).val(),
+      optional = $('#attr_form_row_optional'),
+      select = $('select[name="attr_name"]').val(),
+      attr_name = this.env.attributes[select],
+      // only non-static and non-required attributes can be marked as optional
+      opt = type != 'static' && $.inArray(attr_name, this.env.attributes_required) == -1;
+
     $('input[name="attr_data"]')[type != 'normal' ? 'show' : 'hide']();
-    $('#attr_form_row_optional')[type != 'static' ? 'show' : 'hide']();
     $('#attr_form_row_readonly')[type != 'static' ? 'show' : 'hide']();
+    optional[opt ? 'show' : 'hide']();
+
+    if (!opt)
+      $('input', optional).attr('checked', false);
   };
 
   // Update attribute form on type change


commit b9471887769bb65c38e969e969251a6cd1b0f891
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Oct 5 10:51:45 2012 +0200

    Add/fix some translation

diff --git a/lib/locale/en_US.php b/lib/locale/en_US.php
index 1810fbc..7a4b58f 100644
--- a/lib/locale/en_US.php
+++ b/lib/locale/en_US.php
@@ -41,6 +41,7 @@ $LANG['domain.edit'] = 'Edit domain';
 $LANG['domain.edit.success'] = 'Domain updated successfully.';
 $LANG['domain.inetdomainbasedn'] = 'Custom Root DN(s)';
 $LANG['domain.list'] = 'Domains List';
+$LANG['domain.norecords'] = 'No domain records found!';
 $LANG['domain.o'] = 'Organization';
 $LANG['domain.other'] = 'Other';
 $LANG['domain.system'] = 'System';
@@ -103,7 +104,7 @@ $LANG['resource.kolabtargetfolder'] = 'Target Folder';
 $LANG['resource.list'] = 'Resource (Collection) List';
 $LANG['resource.mail'] = 'Mail Address';
 $LANG['resource.member'] = 'Collection Members';
-$LANG['resource.norecords'] = 'No resource record(s) found!';
+$LANG['resource.norecords'] = 'No resource records found!';
 $LANG['resource.other'] = 'Other';
 $LANG['resource.system'] = 'System';
 $LANG['resource.type_id'] = 'Resource Type';
@@ -232,7 +233,7 @@ $LANG['user.mobile'] = 'Mobile Phone Number';
 $LANG['user.name'] = 'Name';
 $LANG['user.norecords'] = 'No user records found!';
 $LANG['user.nsrole'] = 'Role(s)';
-$LANG['user.nsroledn'] = $LANG['user.nsrole'];
+$LANG['user.nsroledn'] = 'Role(s)';
 $LANG['user.other'] = 'Other';
 $LANG['user.o'] = 'Organization';
 $LANG['user.org'] = 'Organization';


commit 7454f900765c913a14a2127c1d446545a4639b64
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Oct 5 09:29:07 2012 +0200

    Add required attributes check in object types management

diff --git a/lib/client/kolab_client_task_settings.php b/lib/client/kolab_client_task_settings.php
index f149ba0..87a03ab 100644
--- a/lib/client/kolab_client_task_settings.php
+++ b/lib/client/kolab_client_task_settings.php
@@ -695,7 +695,7 @@ class kolab_client_task_settings extends kolab_client_task
         $this->output->set_env('yes_label', $yes);
         $this->output->set_env('no_label', $no);
         $this->output->add_translation('attribute.value.auto', 'attribute.value.static',
-            'attribute.key.invalid');
+            'attribute.key.invalid', 'attribute.required.error');
 
         // Add attribute link
         $link = kolab_html::a(array(
@@ -817,12 +817,13 @@ class kolab_client_task_settings extends kolab_client_task
 
         // convert to hash array
         if (!empty($response['list'])) {
-            $attributes = array_combine(array_map('strtolower', $response['list']), $response['list']);
+            // remove objectClass
+            $attributes = array_diff($response['list'], array('objectClass'));
+            $attributes = array_combine(array_map('strtolower', $attributes), $attributes);
         }
 
         $this->output->set_env('attributes', $attributes);
-        // @TODO: check if all required attributes are used
-//        $this->output->set_env('attributes_required', $attributes['required']);
+        $this->output->set_env('attributes_required', $response['required']);
 
         return $attributes;
     }
diff --git a/lib/locale/en_US.php b/lib/locale/en_US.php
index 6b7606d..1810fbc 100644
--- a/lib/locale/en_US.php
+++ b/lib/locale/en_US.php
@@ -21,6 +21,7 @@ $LANG['attribute.value.normal'] = 'Normal';
 $LANG['attribute.value.static'] = 'Static';
 $LANG['attribute.options'] = 'Options';
 $LANG['attribute.key.invalid'] = 'Type key contains forbidden characters!';
+$LANG['attribute.required.error'] = 'Required attributes missing in attributes list ($1)!';
 
 $LANG['button.cancel'] = 'Cancel';
 $LANG['button.delete'] = 'Delete';
diff --git a/public_html/js/kolab_admin.js b/public_html/js/kolab_admin.js
index fd4d3fd..b6c9a72 100644
--- a/public_html/js/kolab_admin.js
+++ b/public_html/js/kolab_admin.js
@@ -1549,9 +1549,10 @@ function kolab_admin()
 
   this.type_save = function(reload, section)
   {
-    var i, attr, request = {},
+    var i, n, attr, request = {},
       data = this.serialize_form('#'+this.env.form_id),
-      action = data.id ? 'edit' : 'add';
+      action = data.id ? 'edit' : 'add',
+      required = this.env.attributes_required || [];
 
     if (reload) {
       data.section = section;
@@ -1571,6 +1572,9 @@ function kolab_admin()
       return;
     }
 
+    // remove objectClass from required attributes list
+    required = $.map(required, function(a) { return a == 'objectClass' ? null : a; });
+
     request.id = data.id;
     request.key = data.key;
     request.name = data.name;
@@ -1583,6 +1587,14 @@ function kolab_admin()
     // Build attributes array compatible with the API format
     // @TODO: use attr_table format
     for (i in this.env.attr_table) {
+      // attribute doesn't exist in specified object classes set
+      if (!(n = this.env.attributes[i]))
+        continue;
+
+      // check required attributes
+      if (required.length)
+        required = $.map(required, function(a) { return a != n ? a : null; });
+
       attr = this.env.attr_table[i];
       data = {};
 
@@ -1615,6 +1627,11 @@ function kolab_admin()
       }
     }
 
+    if (required.length) {
+      this.display_message(this.t('attribute.required.error').replace(/\$1/, required.join(',')), 'error');
+      return;
+    }
+
     this.set_busy(true, 'saving');
     this.api_post('type.' + action, request, 'type_' + action + '_response');
   };
@@ -1836,16 +1853,18 @@ function kolab_admin()
     if (!this.api_response(response))
       return;
 
-    var i, lc, list = response.result.attribute.list,
-      required = response.result.attribute.required,
+    var i, lc, list = response.result.attribute.list || [],
       select = $('select[name="attr_name"]');
 
     this.env.attributes = {};
+    this.env.attributes_required = response.result.attribute.required || [];
     select.empty();
 
     for (i in list) {
-      lc = list[i].toLowerCase()
-      this.env.attributes[list[i].toLowerCase()] = list[i];
+      if (i == 'objectClass')
+        continue;
+      lc = list[i].toLowerCase();
+      this.env.attributes[lc] = list[i];
       $('<option>').text(list[i]).val(lc).appendTo(select);
     }
   };


commit e8c98e43e017f43baf4471b50a3d04a0baaf4469
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Oct 5 09:21:43 2012 +0200

    Join optional and required attributes in select_options_attribute response

diff --git a/lib/api/kolab_api_service_form_value.php b/lib/api/kolab_api_service_form_value.php
index fd57060..b6c68b2 100644
--- a/lib/api/kolab_api_service_form_value.php
+++ b/lib/api/kolab_api_service_form_value.php
@@ -785,6 +785,10 @@ class kolab_api_service_form_value extends kolab_api_service
         $list = $auth->schema_attributes($postdata['classes']);
 
         if (is_array($list['may'])) {
+            // return required + optional
+            if (is_array($list['must']) && !empty($list['must'])) {
+                $list['may'] = array_unique(array_merge($list['may'], $list['must']));
+            }
             sort($list['may']);
         }
 


commit 96385fa7846622ddd986c82792ee2c969fca8574
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Oct 4 14:20:53 2012 +0200

    Fix jumping to previous page when deleting last record on current page

diff --git a/lib/client/kolab_client_task_domain.php b/lib/client/kolab_client_task_domain.php
index 8a538fc..edb716e 100644
--- a/lib/client/kolab_client_task_domain.php
+++ b/lib/client/kolab_client_task_domain.php
@@ -177,6 +177,7 @@ class kolab_client_task_domain extends kolab_client_task
         $this->output->set_env('search_request', $search_request ? base64_encode(serialize($search_request)) : null);
         $this->output->set_env('list_page', $page);
         $this->output->set_env('list_count', $count);
+        $this->output->set_env('list_size', $i);
         $this->output->set_object('domainlist', $table);
     }
 
diff --git a/lib/client/kolab_client_task_group.php b/lib/client/kolab_client_task_group.php
index eb5e6b7..3349129 100644
--- a/lib/client/kolab_client_task_group.php
+++ b/lib/client/kolab_client_task_group.php
@@ -169,6 +169,7 @@ class kolab_client_task_group extends kolab_client_task
         $this->output->set_env('search_request', $search_request ? base64_encode(serialize($search_request)) : null);
         $this->output->set_env('list_page', $page);
         $this->output->set_env('list_count', $count);
+        $this->output->set_env('list_size', count($result));
         $this->output->set_object('grouplist', $table);
     }
 
diff --git a/lib/client/kolab_client_task_resource.php b/lib/client/kolab_client_task_resource.php
index f0ce4b0..a5669be 100644
--- a/lib/client/kolab_client_task_resource.php
+++ b/lib/client/kolab_client_task_resource.php
@@ -171,6 +171,7 @@ class kolab_client_task_resource extends kolab_client_task
         $this->output->set_env('search_request', $search_request ? base64_encode(serialize($search_request)) : null);
         $this->output->set_env('list_page', $page);
         $this->output->set_env('list_count', $count);
+        $this->output->set_env('list_size', count($result));
         $this->output->set_object('resourcelist', $table);
     }
 
diff --git a/lib/client/kolab_client_task_role.php b/lib/client/kolab_client_task_role.php
index beac689..5539ceb 100644
--- a/lib/client/kolab_client_task_role.php
+++ b/lib/client/kolab_client_task_role.php
@@ -169,6 +169,7 @@ class kolab_client_task_role extends kolab_client_task
         $this->output->set_env('search_request', $search_request ? base64_encode(serialize($search_request)) : null);
         $this->output->set_env('list_page', $page);
         $this->output->set_env('list_count', $count);
+        $this->output->set_env('list_size', count($result));
         $this->output->set_object('rolelist', $table);
     }
 
diff --git a/lib/client/kolab_client_task_settings.php b/lib/client/kolab_client_task_settings.php
index ca29f50..f149ba0 100644
--- a/lib/client/kolab_client_task_settings.php
+++ b/lib/client/kolab_client_task_settings.php
@@ -330,6 +330,7 @@ class kolab_client_task_settings extends kolab_client_task
         $this->output->set_env('search_request', $search_request ? base64_encode(serialize($search_request)) : null);
         $this->output->set_env('list_page', $page);
         $this->output->set_env('list_count', $count);
+        $this->output->set_env('list_size', count($result));
         $this->output->set_object('typelist', $table);
     }
 
diff --git a/lib/client/kolab_client_task_user.php b/lib/client/kolab_client_task_user.php
index cf4d9b9..3edf551 100644
--- a/lib/client/kolab_client_task_user.php
+++ b/lib/client/kolab_client_task_user.php
@@ -169,6 +169,7 @@ class kolab_client_task_user extends kolab_client_task
         $this->output->set_env('search_request', $search_request ? base64_encode(serialize($search_request)) : null);
         $this->output->set_env('list_page', $page);
         $this->output->set_env('list_count', $count);
+        $this->output->set_env('list_size', count($result));
         $this->output->set_object('userlist', $table);
     }
 
diff --git a/public_html/js/kolab_admin.js b/public_html/js/kolab_admin.js
index 928ef30..fd4d3fd 100644
--- a/public_html/js/kolab_admin.js
+++ b/public_html/js/kolab_admin.js
@@ -1652,7 +1652,7 @@ function kolab_admin()
     // if objects list exists
     if ($('#'+list_id).length) {
       // goto previous page if last record on the current page has been deleted
-      if (action.match(/\.delete/) && this.env.list_count)
+      if (this.env.list_page > 1 && this.env.list_size == 1 && action.match(/\.delete/))
         page -= 1;
 
       this.command(list, {page: page});


commit 255cb1a9fb8a1aecfa2f6850591b27eb061fd4f5
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Oct 4 13:56:01 2012 +0200

    Remove useless title attribute

diff --git a/public_html/skins/default/templates/main.html b/public_html/skins/default/templates/main.html
index 829c407..0de2e4f 100644
--- a/public_html/skins/default/templates/main.html
+++ b/public_html/skins/default/templates/main.html
@@ -22,7 +22,7 @@
             {$user.domain}
         {/if}
         </span>
-        <span class="logout link" title="{$engine->translate('logout')}" onclick="kadm.main_logout()">{$engine->translate('logout')}</span>
+        <span class="logout link" onclick="kadm.main_logout()">{$engine->translate('logout')}</span>
     </div>
     <div id="navigation">{$main_menu}</div>
     <div id="task_navigation">{$task_menu}</div>


commit 2114483027aa167eaa09ae342c908a184829233c
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Oct 4 13:55:33 2012 +0200

    Implement searching in object types

diff --git a/lib/client/kolab_client_task_settings.php b/lib/client/kolab_client_task_settings.php
index 4dffdb6..ca29f50 100644
--- a/lib/client/kolab_client_task_settings.php
+++ b/lib/client/kolab_client_task_settings.php
@@ -213,6 +213,38 @@ class kolab_client_task_settings extends kolab_client_task
         // get object types list
         $result = $this->object_types($type);
 
+        // Implement searching here, not in the API, we don't expect too many records
+        if (!empty($search_request)) {
+            foreach ($result as $idx => $record) {
+                $found = false;
+                foreach ($search_request as $key => $value) {
+                    if (!empty($record[$key])) {
+                        $s = mb_strtoupper($value['value']);
+                        $v = mb_strtoupper($record[$key]);
+                        switch ($value['type']) {
+                        case 'both':
+                            $res = $v === $s || strpos($v, $s) !== false;
+                            break;
+                        case 'exact':
+                            $res = $v === $s;
+                            break;
+                        case 'prefix':
+                            $res = strpos($v, $s) === 0;
+                            break;
+                        }
+
+                        if ($res) {
+                            $found = true;
+                            break;
+                        }
+                    }
+                }
+                if (!$found) {
+                    unset($result[$idx]);
+                }
+            }
+        }
+
         // assign ID
         foreach (array_keys($result) as $idx) {
             $result[$idx]['id'] = $idx;


commit 3ec0c4c39961f4d5f94064e7e428b0d86ba85ed1
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Oct 4 13:23:07 2012 +0200

    Improved page header style

diff --git a/lib/locale/en_US.php b/lib/locale/en_US.php
index 9e3301b..6b7606d 100644
--- a/lib/locale/en_US.php
+++ b/lib/locale/en_US.php
@@ -71,6 +71,7 @@ $LANG['internalerror'] = 'Internal system error!';
 $LANG['list.records'] = '$1 to $2 of $3';
 
 $LANG['loading'] = 'Loading...';
+$LANG['logout'] = 'Logout';
 $LANG['login.username'] = 'Username:';
 $LANG['login.password'] = 'Password:';
 $LANG['login.domain'] = 'Domain:';
diff --git a/public_html/skins/default/images/body.png b/public_html/skins/default/images/body.png
index cffc992..79c0fd8 100644
Binary files a/public_html/skins/default/images/body.png and b/public_html/skins/default/images/body.png differ
diff --git a/public_html/skins/default/images/buttons.png b/public_html/skins/default/images/buttons.png
index d206367..71ed051 100644
Binary files a/public_html/skins/default/images/buttons.png and b/public_html/skins/default/images/buttons.png differ
diff --git a/public_html/skins/default/images/linen_header.jpg b/public_html/skins/default/images/linen_header.jpg
new file mode 100644
index 0000000..040a4e2
Binary files /dev/null and b/public_html/skins/default/images/linen_header.jpg differ
diff --git a/public_html/skins/default/images/loading.gif b/public_html/skins/default/images/loading.gif
index ee7cfcf..d838429 100644
Binary files a/public_html/skins/default/images/loading.gif and b/public_html/skins/default/images/loading.gif differ
diff --git a/public_html/skins/default/images/logout.png b/public_html/skins/default/images/logout.png
deleted file mode 100644
index a07e5cb..0000000
Binary files a/public_html/skins/default/images/logout.png and /dev/null differ
diff --git a/public_html/skins/default/style.css b/public_html/skins/default/style.css
index 16dd1a2..287aa33 100644
--- a/public_html/skins/default/style.css
+++ b/public_html/skins/default/style.css
@@ -141,15 +141,15 @@ td.label {
 #topmenu {
   text-align: right;
   height: 20px;
-  padding: 0;
-  margin: 0 10px;
+  padding: 0 10px;
+  margin: 0;
   white-space: nowrap;
+  background: url(images/linen_header.jpg) 0 0 repeat-x;
 }
 
 #topmenu > span {
-  color: #f8fcff;
+  color: #aaa;
   font-size: 11px;
-  text-shadow: black 1px 1px;
 }
 
 #navigation {
@@ -246,18 +246,18 @@ td.label {
   width: 150px;
   height: 18px;
   padding-left: 86px;
-  color: #f8fcff;
+  color: #aaa;
   font-size: 11px;
-  text-shadow: black 1px 1px;
   z-index: 1;
   background: url(images/loading.gif) 0 3px no-repeat;
   white-space: nowrap;
 }
 
 #topmenu .logout {
-  background: url(images/logout.png) center 10px no-repeat;
-  padding: 10px 16px;
-  margin: 0px 5px 0px 10px;
+  background: url(images/buttons.png) -100px -1px no-repeat;
+  padding: 0 0 0 20px;
+  margin: 0 10px;
+  color: white;
 }
 
 #topmenu .login {
@@ -938,7 +938,7 @@ fieldset.tabbed
 }
 
 #domain-selector span.link {
-  color: #1E90FF;
+  color: white;
   text-decoration: none;
 }
 
diff --git a/public_html/skins/default/templates/main.html b/public_html/skins/default/templates/main.html
index 7dbdf55..829c407 100644
--- a/public_html/skins/default/templates/main.html
+++ b/public_html/skins/default/templates/main.html
@@ -22,7 +22,7 @@
             {$user.domain}
         {/if}
         </span>
-        <span class="logout link" title="{$engine->translate('Logout')}" onclick="kadm.main_logout()"></span>
+        <span class="logout link" title="{$engine->translate('logout')}" onclick="kadm.main_logout()">{$engine->translate('logout')}</span>
     </div>
     <div id="navigation">{$main_menu}</div>
     <div id="task_navigation">{$task_menu}</div>


commit a4bd3d112b5e4d51a0ebd642166374f7069f42e4
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Oct 4 09:18:10 2012 +0200

    Renamed Auth methods - removed ldap_ prefix

diff --git a/lib/Auth.php b/lib/Auth.php
index db61490..ec705a9 100644
--- a/lib/Auth.php
+++ b/lib/Auth.php
@@ -416,12 +416,12 @@ class Auth {
         return $this->auth_instance()->user_info($userdata);
     }
 
-    public function ldap_schema_attributes($object_classes)
+    public function schema_attributes($object_classes)
     {
         return $this->auth_instance()->attributes_allowed($object_classes);
     }
 
-    public function ldap_schema_classes()
+    public function schema_classes()
     {
         return $this->auth_instance()->classes_allowed();
     }
diff --git a/lib/api/kolab_api_service_form_value.php b/lib/api/kolab_api_service_form_value.php
index a4eac28..fd57060 100644
--- a/lib/api/kolab_api_service_form_value.php
+++ b/lib/api/kolab_api_service_form_value.php
@@ -770,7 +770,7 @@ class kolab_api_service_form_value extends kolab_api_service
     private function select_options_objectclass($postdata, $attribs = array())
     {
         $auth = Auth::get_instance();
-        $list = $auth->ldap_schema_classes();
+        $list = $auth->schema_classes();
 
         if (is_array($list)) {
             sort($list);
@@ -782,7 +782,7 @@ class kolab_api_service_form_value extends kolab_api_service
     private function select_options_attribute($postdata, $attribs = array())
     {
         $auth = Auth::get_instance();
-        $list = $auth->ldap_schema_attributes($postdata['classes']);
+        $list = $auth->schema_attributes($postdata['classes']);
 
         if (is_array($list['may'])) {
             sort($list['may']);


commit 70e80e209d3367300fcb1a7aa148d85773e5118a
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Oct 4 09:17:20 2012 +0200

    Fix Net_LDAP2 connection error handling (Bug #1053)

diff --git a/lib/api/kolab_api_service_form_value.php b/lib/api/kolab_api_service_form_value.php
index d854d3e..a4eac28 100644
--- a/lib/api/kolab_api_service_form_value.php
+++ b/lib/api/kolab_api_service_form_value.php
@@ -772,7 +772,9 @@ class kolab_api_service_form_value extends kolab_api_service
         $auth = Auth::get_instance();
         $list = $auth->ldap_schema_classes();
 
-        sort($list);
+        if (is_array($list)) {
+            sort($list);
+        }
 
         return array('list' => $list);
     }
diff --git a/lib/ext/Net/LDAP3.php b/lib/ext/Net/LDAP3.php
index c722fce..75c01a5 100644
--- a/lib/ext/Net/LDAP3.php
+++ b/lib/ext/Net/LDAP3.php
@@ -222,9 +222,13 @@ class Net_LDAP3
 
     public function attribute_details($attributes = array())
     {
-        $_schema = $this->init_schema();
+        $schema = $this->init_schema();
 
-        $attribs = $_schema->getAll('attributes');
+        if (!$schema) {
+            return array();
+        }
+
+        $attribs = $schema->getAll('attributes');
 
         $attributes_details = array();
 
@@ -272,14 +276,18 @@ class Net_LDAP3
         $this->_debug("Listing allowed_attributes for objectclasses", $objectclasses);
 
         if (!is_array($objectclasses)) {
-            return FALSE;
+            return false;
         }
 
         if (empty($objectclasses)) {
-            return FALSE;
+            return false;
+        }
+
+        $schema = $this->init_schema();
+        if (!$schema) {
+            return false;
         }
 
-        $schema       = $this->init_schema();
         $may          = array();
         $must         = array();
         $superclasses = array();
@@ -312,7 +320,11 @@ class Net_LDAP3
 
     public function classes_allowed()
     {
-        $schema  = $this->init_schema();
+        $schema = $this->init_schema();
+        if (!$schema) {
+            return false;
+        }
+
         $list    = $schema->getAll('objectclasses');
         $classes = array();
 
@@ -1655,17 +1667,24 @@ class Net_LDAP3
                 'max_age' => 86400,
             );
 
-            $_ldap_schema_cache = new Net_LDAP2_SimpleFileSchemaCache($_ldap_schema_cache_cfg);
+            $_ldap = Net_LDAP2::connect($_ldap_cfg);
 
-            if ($_ldap = Net_LDAP2::connect($_ldap_cfg)) {
+            if (!is_a($_ldap, 'Net_LDAP2_Error')) {
                 $this->_debug("S: OK");
                 break;
             }
 
             $this->_debug("S: NOT OK");
+            new PEAR_Error($_ldap->getMessage());
         }
 
-        $result = $_ldap->registerSchemaCache($_ldap_schema_cache);
+        if (is_a($_ldap, 'Net_LDAP2_Error')) {
+            return null;
+        }
+
+        $_ldap_schema_cache = new Net_LDAP2_SimpleFileSchemaCache($_ldap_schema_cache_cfg);
+
+        $_ldap->registerSchemaCache($_ldap_schema_cache);
 
         // TODO: We should learn what LDAP tech. we're running against.
         // Perhaps with a scope base objectclass recognize rootdse entry


commit 2a7d3a1ae12d41fa146e36c532b7aecfcd61ee9a
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Oct 4 08:33:47 2012 +0200

    Implement domain.delete

diff --git a/lib/Auth.php b/lib/Auth.php
index 3e0b9bd..db61490 100644
--- a/lib/Auth.php
+++ b/lib/Auth.php
@@ -219,6 +219,11 @@ class Auth {
         return $this->auth_instance()->domain_edit($domain, $attributes, $typeid);
     }
 
+    public function domain_delete($domain)
+    {
+        return $this->auth_instance()->domain_delete($domain);
+    }
+
     public function domain_find_by_attribute($attribute)
     {
         return $this->auth_instance()->domain_find_by_attribute($attribute);
diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php
index 7e0ecd4..b73c2a2 100644
--- a/lib/Auth/LDAP.php
+++ b/lib/Auth/LDAP.php
@@ -179,6 +179,10 @@ class LDAP extends Net_LDAP3 {
         return $this->modify_entry($_domain_dn, $_domain[$_domain_dn], $attributes);
     }
 
+    public function domain_delete($domain) {
+        return $this->entry_delete($domain);
+    }
+
     public function domain_find_by_attribute($attribute) {
         $base_dn = $this->conf->get('ldap', 'domain_base_dn');
 
diff --git a/lib/api/kolab_api_service_domain.php b/lib/api/kolab_api_service_domain.php
index 23da894..89064e0 100644
--- a/lib/api/kolab_api_service_domain.php
+++ b/lib/api/kolab_api_service_domain.php
@@ -117,6 +117,31 @@ class kolab_api_service_domain extends kolab_api_service
         return false;
     }
 
+    /**
+     * Domain delete.
+     *
+     * @param array $get  GET parameters
+     * @param array $post POST parameters
+     *
+     * @return bool True on success, False on failure
+     */
+    public function domain_delete($getdata, $postdata)
+    {
+        if (empty($postdata['domain'])) {
+            return false;
+        }
+
+        // TODO: Input validation
+        $auth   = Auth::get_instance();
+        $result = $auth->domain_delete($postdata['domain']);
+
+        if ($result) {
+            return $result;
+        }
+
+        return false;
+    }
+
     public function domain_effective_rights($getdata, $postdata)
     {
         $auth = Auth::get_instance();


commit 994fe06a2a7bb09ef62fd0cc83d743532f4c2805
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 3 20:11:41 2012 +0200

    Added lost domain_list and domain_delete methods

diff --git a/public_html/js/kolab_admin.js b/public_html/js/kolab_admin.js
index 57bc970..928ef30 100644
--- a/public_html/js/kolab_admin.js
+++ b/public_html/js/kolab_admin.js
@@ -1250,6 +1250,17 @@ function kolab_admin()
     this.http_post('domain.info', {id: id});
   };
 
+  this.domain_list = function(props)
+  {
+    this.list_handler('domain', props);
+  };
+
+  this.domain_delete = function(domainid)
+  {
+    this.set_busy(true, 'deleting');
+    this.api_post('domain.delete', {domain: domainid}, 'domain_delete_response');
+  };
+
   this.domain_save = function(reload, section)
   {
     var data = this.serialize_form('#'+this.env.form_id),
@@ -1272,6 +1283,11 @@ function kolab_admin()
     this.api_post('domain.' + action, data, 'domain_' + action + '_response');
   };
 
+  this.domain_delete_response = function(response)
+  {
+    this.response_handler(response, 'domain.delete', 'domain.list');
+  };
+
   this.domain_add_response = function(response)
   {
     this.response_handler(response, 'domain.add', 'domain.list');


commit 54b3c7789c956a539546f1f30022709ececf5802
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 3 20:06:52 2012 +0200

    Unified list/edit/add methods for all object types

diff --git a/lib/client/kolab_client_task_settings.php b/lib/client/kolab_client_task_settings.php
index 1e87de5..4dffdb6 100644
--- a/lib/client/kolab_client_task_settings.php
+++ b/lib/client/kolab_client_task_settings.php
@@ -284,7 +284,7 @@ class kolab_client_task_settings extends kolab_client_task
         }
 
         $table = kolab_html::table(array(
-            'id'    => 'typelist',
+            'id'    => 'settingstypelist',
             'class' => 'list',
             'head'  => $head,
             'body'  => $rows,
diff --git a/public_html/js/kolab_admin.js b/public_html/js/kolab_admin.js
index e4bf81e..57bc970 100644
--- a/public_html/js/kolab_admin.js
+++ b/public_html/js/kolab_admin.js
@@ -1274,22 +1274,12 @@ function kolab_admin()
 
   this.domain_add_response = function(response)
   {
-    if (!this.api_response(response))
-      return;
-
-    this.display_message('domain.add.success');
-    this.command('domain.list', {page: this.env.list_page});
-    this.set_watermark('taskcontent');
+    this.response_handler(response, 'domain.add', 'domain.list');
   };
 
   this.domain_edit_response = function(response)
   {
-    if (!this.api_response(response))
-      return;
-
-    this.display_message('domain.edit.success');
-    this.command('domain.list', {page: this.env.list_page});
-    this.set_watermark('taskcontent');
+    this.response_handler(response, 'domain.edit', 'domain.list');
   };
 
   this.user_info = function(id)
@@ -1299,13 +1289,7 @@ function kolab_admin()
 
   this.user_list = function(props)
   {
-    if (!props)
-      props = {};
-
-    if (props.search === undefined && this.env.search_request)
-      props.search_request = this.env.search_request;
-
-    this.http_post('user.list', props);
+    this.list_handler('user', props);
   };
 
   this.user_delete = function(userid)
@@ -1314,22 +1298,6 @@ function kolab_admin()
     this.api_post('user.delete', {user: userid}, 'user_delete_response');
   };
 
-  this.user_delete_response = function(response)
-  {
-    if (!this.api_response(response))
-      return;
-
-    var page = this.env.list_page;
-
-    // goto previous page if last user on the current page has been deleted
-    if (this.env.list_count)
-      page -= 1;
-
-    this.display_message('user.delete.success');
-    this.command('user.list', {page: page});
-    this.set_watermark('taskcontent');
-  };
-
   this.user_save = function(reload, section)
   {
     var data = this.serialize_form('#'+this.env.form_id),
@@ -1360,27 +1328,19 @@ function kolab_admin()
     this.api_post('user.' + action, data, 'user_' + action + '_response');
   };
 
-  this.user_add_response = function(response)
+  this.user_delete_response = function(response)
   {
-    if (!this.api_response(response))
-      return;
+    this.response_handler(response, 'user.delete', 'user.list');
+  };
 
-    this.display_message('user.add.success');
-    this.command('user.list', {page: this.env.list_page});
-    this.set_watermark('taskcontent');
+  this.user_add_response = function(response)
+  {
+    this.response_handler(response, 'user.add', 'user.list');
   };
 
   this.user_edit_response = function(response)
   {
-    if (!this.api_response(response))
-      return;
-
-    this.display_message('user.edit.success');
-
-    if ($('#userlist').length) {
-      this.command('user.list', {page: this.env.list_page});
-      this.set_watermark('taskcontent');
-    }
+    this.response_handler(response, 'user.edit', 'user.list');
   };
 
   this.group_info = function(id)
@@ -1390,13 +1350,7 @@ function kolab_admin()
 
   this.group_list = function(props)
   {
-    if (!props)
-      props = {};
-
-    if (props.search === undefined && this.env.search_request)
-      props.search_request = this.env.search_request;
-
-    this.http_post('group.list', props);
+    this.list_handler('group', props);
   };
 
   this.group_delete = function(groupid)
@@ -1405,22 +1359,6 @@ function kolab_admin()
     this.api_post('group.delete', {group: groupid}, 'group_delete_response');
   };
 
-  this.group_delete_response = function(response)
-  {
-    if (!this.api_response(response))
-      return;
-
-    var page = this.env.list_page;
-
-    // goto previous page if last record on the current page has been deleted
-    if (this.env.list_count)
-      page -= 1;
-
-    this.display_message('group.delete.success');
-    this.command('group.list', {page: page});
-    this.set_watermark('taskcontent');
-  };
-
   this.group_save = function(reload, section)
   {
     var data = this.serialize_form('#'+this.env.form_id),
@@ -1443,24 +1381,19 @@ function kolab_admin()
     this.api_post('group.' + action, data, 'group_' + action + '_response');
   };
 
-  this.group_add_response = function(response)
+  this.group_delete_response = function(response)
   {
-    if (!this.api_response(response))
-      return;
+    this.response_handler(response, 'group.delete', 'group.list');
+  };
 
-    this.display_message('group.add.success');
-    this.command('group.list', {page: this.env.list_page});
-    this.set_watermark('taskcontent');
+  this.group_add_response = function(response)
+  {
+    this.response_handler(response, 'group.add', 'group.list');
   };
 
   this.group_edit_response = function(response)
   {
-    if (!this.api_response(response))
-      return;
-
-    this.display_message('group.edit.success');
-    this.command('group.list', {page: this.env.list_page});
-    this.set_watermark('taskcontent');
+    this.response_handler(response, 'group.edit', 'group.list');
   };
 
   this.resource_info = function(id)
@@ -1470,13 +1403,7 @@ function kolab_admin()
 
   this.resource_list = function(props)
   {
-    if (!props)
-      props = {};
-
-    if (props.search === undefined && this.env.search_request)
-      props.search_request = this.env.search_request;
-
-    this.http_post('resource.list', props);
+    this.list_handler('resource', props);
   };
 
   this.resource_delete = function(resourceid)
@@ -1485,22 +1412,6 @@ function kolab_admin()
     this.api_post('resource.delete', {resource: resourceid}, 'resource_delete_response');
   };
 
-  this.resource_delete_response = function(response)
-  {
-    if (!this.api_response(response))
-      return;
-
-    var page = this.env.list_page;
-
-    // goto previous page if last record on the current page has been deleted
-    if (this.env.list_count)
-      page -= 1;
-
-    this.display_message('resource.delete.success');
-    this.command('resource.list', {page: page});
-    this.set_watermark('taskcontent');
-  };
-
   this.resource_save = function(reload, section)
   {
     var data = this.serialize_form('#'+this.env.form_id),
@@ -1523,24 +1434,19 @@ function kolab_admin()
     this.api_post('resource.' + action, data, 'resource_' + action + '_response');
   };
 
-  this.resource_add_response = function(response)
+  this.resource_delete_response = function(response)
   {
-    if (!this.api_response(response))
-      return;
+    this.response_handler(response, 'resource.delete', 'resource.list');
+  };
 
-    this.display_message('resource.add.success');
-    this.command('resource.list', {page: this.env.list_page});
-    this.set_watermark('taskcontent');
+  this.resource_add_response = function(response)
+  {
+    this.response_handler(response, 'resource.add', 'resource.list');
   };
 
   this.resource_edit_response = function(response)
   {
-    if (!this.api_response(response))
-      return;
-
-    this.display_message('resource.edit.success');
-    this.command('resource.list', {page: this.env.list_page});
-    this.set_watermark('taskcontent');
+    this.response_handler(response, 'resource.edit', 'resource.list');
   };
 
   this.role_info = function(id)
@@ -1550,13 +1456,7 @@ function kolab_admin()
 
   this.role_list = function(props)
   {
-    if (!props)
-      props = {};
-
-    if (props.search === undefined && this.env.search_request)
-      props.search_request = this.env.search_request;
-
-    this.http_post('role.list', props);
+    this.list_handler('role', props);
   };
 
   this.role_delete = function(roleid)
@@ -1565,22 +1465,6 @@ function kolab_admin()
     this.api_post('role.delete', {role: roleid}, 'role_delete_response');
   };
 
-  this.role_delete_response = function(response)
-  {
-    if (!this.api_response(response))
-      return;
-
-    var page = this.env.list_page;
-
-    // goto previous page if last record on the current page has been deleted
-    if (this.env.list_count)
-      page -= 1;
-
-    this.display_message('role.delete.success');
-    this.command('role.list', {page: page});
-    this.set_watermark('taskcontent');
-  };
-
   this.role_save = function(reload, section)
   {
     var data = this.serialize_form('#'+this.env.form_id),
@@ -1603,24 +1487,19 @@ function kolab_admin()
     this.api_post('role.' + action, data, 'role_' + action + '_response');
   };
 
-  this.role_add_response = function(response)
+  this.role_delete_response = function(response)
   {
-    if (!this.api_response(response))
-      return;
+    this.response_handler(response, 'role.delete', 'role.list');
+  };
 
-    this.display_message('role.add.success');
-    this.command('role.list', {page: this.env.list_page});
-    this.set_watermark('taskcontent');
+  this.role_add_response = function(response)
+  {
+    this.response_handler(response, 'role.add', 'role.list');
   };
 
   this.role_edit_response = function(response)
   {
-    if (!this.api_response(response))
-      return;
-
-    this.display_message('role.edit.success');
-    this.command('role.list', {page: this.env.list_page});
-    this.set_watermark('taskcontent');
+    this.response_handler(response, 'role.edit', 'role.list');
   };
 
   this.settings_type_info = function(id)
@@ -1652,22 +1531,6 @@ function kolab_admin()
     this.api_post('type.delete', this.type_id_parse(id), 'type_delete_response');
   };
 
-  this.type_delete_response = function(response)
-  {
-    if (!this.api_response(response))
-      return;
-
-    var page = this.env.list_page;
-
-    // goto previous page if last record on the current page has been deleted
-    if (this.env.list_count)
-      page -= 1;
-
-    this.display_message('type.delete.success');
-    this.command('settings.type_list', {page: page});
-    this.set_watermark('taskcontent');
-  };
-
   this.type_save = function(reload, section)
   {
     var i, attr, request = {},
@@ -1740,30 +1603,59 @@ function kolab_admin()
     this.api_post('type.' + action, request, 'type_' + action + '_response');
   };
 
-  this.type_add_response = function(response)
+  this.type_delete_response = function(response)
   {
-    if (!this.api_response(response))
-      return;
+    this.response_handler(response, 'type.delete', 'settings.type_list');
+  };
 
-    this.display_message('type.add.success');
-    this.command('settings.type_list', {page: this.env.list_page});
-    this.set_watermark('taskcontent');
+  this.type_add_response = function(response)
+  {
+    this.response_handler(response, 'type.add', 'settings.type_list');
   };
 
   this.type_edit_response = function(response)
   {
-    if (!this.api_response(response))
-      return;
-
-    this.display_message('type.edit.success');
-    this.command('settings.type_list', {page: this.env.list_page});
-    this.set_watermark('taskcontent');
+    this.response_handler(response, 'type.edit', 'settings.type_list');
   };
 
   /*********************************************************/
   /*********       Various helper methods          *********/
   /*********************************************************/
 
+  // universal API response handler
+  this.response_handler = function(response, action, list)
+  {
+    if (!this.api_response(response))
+      return;
+
+    this.display_message(action + '.success');
+
+    var page = this.env.list_page,
+      list_id = list.replace(/[^a-z]/, '');
+
+    // if objects list exists
+    if ($('#'+list_id).length) {
+      // goto previous page if last record on the current page has been deleted
+      if (action.match(/\.delete/) && this.env.list_count)
+        page -= 1;
+
+      this.command(list, {page: page});
+      this.set_watermark('taskcontent');
+    }
+  };
+
+  // universal list request handler
+  this.list_handler = function(type, props)
+  {
+    if (!props)
+      props = {};
+
+    if (props.search === undefined && this.env.search_request)
+      props.search_request = this.env.search_request;
+
+    this.http_post(type + '.list', props);
+  };
+
   // Parses object type identifier
   this.type_id_parse = function(id)
   {


commit 31bb39e853a99278eee3649c4aa307d326e3a545
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 3 19:27:44 2012 +0200

    Fix text wrapping in #loading element

diff --git a/public_html/skins/default/style.css b/public_html/skins/default/style.css
index cf597ee..16dd1a2 100644
--- a/public_html/skins/default/style.css
+++ b/public_html/skins/default/style.css
@@ -243,7 +243,7 @@ td.label {
   display: none;
   top: 2px;
   left: 15px;
-  width: 75px;
+  width: 150px;
   height: 18px;
   padding-left: 86px;
   color: #f8fcff;
@@ -251,6 +251,7 @@ td.label {
   text-shadow: black 1px 1px;
   z-index: 1;
   background: url(images/loading.gif) 0 3px no-repeat;
+  white-space: nowrap;
 }
 
 #topmenu .logout {


commit 170bba3a5f6a1e7102faa2a74a20e11505397b62
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 3 18:59:02 2012 +0200

    Fix bug where ssl_* configuration wasn't used when proxying API requests (Bug #1016)

diff --git a/lib/kolab_api_controller.php b/lib/kolab_api_controller.php
index 8e9fc30..bf7058b 100644
--- a/lib/kolab_api_controller.php
+++ b/lib/kolab_api_controller.php
@@ -198,6 +198,8 @@ class kolab_api_controller
         $request->setMethod($method == 'GET' ? HTTP_Request2::METHOD_GET : HTTP_Request2::METHOD_POST);
         $request->setHeader('X-Session-Token', kolab_utils::get_request_header('X-Session-Token'));
 
+        kolab_client_api::configure($request);
+
         if ($method == 'GET') {
             parse_str($_SERVER['QUERY_STRING'], $query);
             unset($query['service']);
diff --git a/lib/kolab_client_api.php b/lib/kolab_client_api.php
index aeb37a5..7405489 100644
--- a/lib/kolab_client_api.php
+++ b/lib/kolab_client_api.php
@@ -60,7 +60,16 @@ class kolab_client_api
     public function init()
     {
         $this->request = new HTTP_Request2();
+        self::configure($this->request);
+    }
 
+    /**
+     * Configure HTTP_Request2 object
+     *
+     * @param HTTP_Request2 $request Request object
+     */
+    public static function configure($request)
+    {
         // Configure connection options
         $config  = Conf::get_instance();
         $options = array(
@@ -75,7 +84,7 @@ class kolab_client_api
         foreach ($options as $optname => $opttype) {
             if (($optvalue = $config->get('kolab_wap', $optname, $opttype)) !== null) {
                 try {
-                    $this->request->setConfig($optname, $optvalue);
+                    $request->setConfig($optname, $optvalue);
                 }
                 catch (Exception $e) {
                     Log::error("HTTP: " . $e->getMessage());


commit 285c6c1a8fbd4e4a8fed215071f5489c7479ca20
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 3 14:29:05 2012 +0200

    Implement role.delete

diff --git a/lib/Auth.php b/lib/Auth.php
index 19b3857..3e0b9bd 100644
--- a/lib/Auth.php
+++ b/lib/Auth.php
@@ -366,6 +366,11 @@ class Auth {
         return $this->auth_instance()->role_edit($role, $attributes, $typeid);
     }
 
+    public function role_delete($role)
+    {
+        return $this->auth_instance()->role_delete($role);
+    }
+
     public function role_find_by_attribute($attribute)
     {
         return $this->auth_instance()->role_find_by_attribute($attribute);
diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php
index e7a2656..7e0ecd4 100644
--- a/lib/Auth/LDAP.php
+++ b/lib/Auth/LDAP.php
@@ -270,13 +270,7 @@ class LDAP extends Net_LDAP3 {
     }
 
     public function group_delete($group) {
-        $group_dn = $this->entry_dn($group);
-
-        if (!$group_dn) {
-            return false;
-        }
-
-        return $this->delete_entry($group_dn);
+        return $this->entry_delete($group);
     }
 
     public function group_edit($group, $attributes, $typeid = null) {
@@ -607,13 +601,7 @@ class LDAP extends Net_LDAP3 {
     }
 
     public function resource_delete($resource) {
-        $resource_dn = $this->entry_dn($resource);
-
-        if (!$resource_dn) {
-            return false;
-        }
-
-        return $this->delete_entry($resource_dn);
+        return $this->entry_delete($resource);
     }
 
     public function resource_edit($resource, $attributes, $typeid = null) {
@@ -704,6 +692,10 @@ class LDAP extends Net_LDAP3 {
         return $this->modify_entry($_role_dn, $_role[$_role_dn], $attributes);
     }
 
+    public function role_delete($role) {
+        return $this->entry_delete($role);
+    }
+
     public function role_find_by_attribute($attribute) {
         $this->_log(LOG_DEBUG, "Finding role by attribute: " . var_export($attribute, TRUE));
 
@@ -798,13 +790,7 @@ class LDAP extends Net_LDAP3 {
     }
 
     public function user_delete($user) {
-        $user_dn = $this->entry_dn($user);
-
-        if (!$user_dn) {
-            return false;
-        }
-
-        return $this->delete_entry($user_dn);
+        return $this->entry_delete($user);
     }
 
     public function user_info($user, $attributes = array('*')) {
@@ -832,6 +818,16 @@ class LDAP extends Net_LDAP3 {
         return $this->entry_find_by_attribute($attribute);
     }
 
+    protected function entry_delete($entry) {
+        $entry_dn = $this->entry_dn($entry);
+
+        if (!$entry_dn) {
+            return false;
+        }
+
+        return $this->delete_entry($entry_dn);
+    }
+
     public function _config_get($key, $default = NULL) {
         $key_parts = explode("_", $key);
         $this->_log(LOG_DEBUG, var_export($key_parts));


commit 791ed63f4b3a691c215b455f191ae6f6faf390eb
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 3 14:11:11 2012 +0200

    Fix/add some translation

diff --git a/lib/locale/en_US.php b/lib/locale/en_US.php
index 23d81d4..9e3301b 100644
--- a/lib/locale/en_US.php
+++ b/lib/locale/en_US.php
@@ -33,10 +33,11 @@ $LANG['debug'] = 'Debug info';
 $LANG['deleting'] = 'Deleting data...';
 
 $LANG['domain.add'] = 'Add Domain';
-$LANG['domain.add.success'] = 'Added domain';
+$LANG['domain.add.success'] = 'Domain created successfully.';
 $LANG['domain.associateddomain'] = 'Domain name(s)';
+$LANG['domain.delete.success'] = 'Domain deleted successfully.';
 $LANG['domain.edit'] = 'Edit domain';
-$LANG['domain.edit.success'] = 'Domain updated';
+$LANG['domain.edit.success'] = 'Domain updated successfully.';
 $LANG['domain.inetdomainbasedn'] = 'Custom Root DN(s)';
 $LANG['domain.list'] = 'Domains List';
 $LANG['domain.o'] = 'Organization';
@@ -90,12 +91,12 @@ $LANG['password.generate'] = 'Generate password';
 $LANG['reqtime'] = 'Request time: $1 sec.';
 
 $LANG['resource.add'] = 'Add Resource';
-$LANG['resource.add.success'] = 'Added Resource';
+$LANG['resource.add.success'] = 'Resource created successfully.';
 $LANG['resource.cn'] = 'Name';
 $LANG['resource.delete'] = 'Delete Resource';
-$LANG['resource.delete.success'] = 'Successfully deleted Resource';
+$LANG['resource.delete.success'] = 'Resource deleted successfully.';
 $LANG['resource.edit'] = 'Edit Resource';
-$LANG['resource.edit.success'] = 'Successfully updated Resource';
+$LANG['resource.edit.success'] = 'Resource updated successfully.';
 $LANG['resource.kolabtargetfolder'] = 'Target Folder';
 $LANG['resource.list'] = 'Resource (Collection) List';
 $LANG['resource.mail'] = 'Mail Address';
@@ -107,9 +108,11 @@ $LANG['resource.type_id'] = 'Resource Type';
 $LANG['resource.uniquemember'] = 'Collection Members';
 
 $LANG['role.add'] = 'Add Role';
+$LANG['role.add.success'] = 'Role created successfully.';
 $LANG['role.cn'] = 'Role Name';
+$LANG['role.delete.success'] = 'Role deleted successfully.';
 $LANG['role.description'] = 'Role Description';
-$LANG['role.edit.success'] = 'Role edited successfully';
+$LANG['role.edit.success'] = 'Role updated successfully';
 $LANG['role.list'] = 'Role List';
 $LANG['role.norecords'] = 'No role records found!';
 $LANG['role.system'] = 'Details';


commit eb8cafa784fad91cf9c10cda8eeb41c97011c369
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 3 13:57:41 2012 +0200

    Better effective rights checking, also do checks before executing any SQL queries

diff --git a/lib/api/kolab_api_service_type.php b/lib/api/kolab_api_service_type.php
index e6c56a1..0c47c89 100644
--- a/lib/api/kolab_api_service_type.php
+++ b/lib/api/kolab_api_service_type.php
@@ -37,16 +37,16 @@ class kolab_api_service_type extends kolab_api_service
      */
     public function capabilities($domain)
     {
-        $auth = Auth::get_instance();
+        $effective_rights = $this->type_effective_rights();
+        $rights           = array();
 
-        //$effective_rights = $auth->list_rights('user');
-
-        $rights = array();
-
-        // @TODO: set rights according to user group or sth
-        if ($_SESSION['user']->get_userid() == 'cn=Directory Manager') {
+        if (in_array('add', (array)$effective_rights['entryLevelRights'])) {
             $rights['add'] = "w";
+        }
+        if (in_array('delete', (array)$effective_rights['entryLevelRights'])) {
             $rights['delete'] = "w";
+        }
+        if (in_array('modrdn', (array)$effective_rights['entryLevelRights'])) {
             $rights['edit'] = "w";
         }
 
@@ -78,7 +78,10 @@ class kolab_api_service_type extends kolab_api_service
             return false;
         }
 
-        // @TODO: check privileges
+        $effective_rights = $this->type_effective_rights();
+        if (!in_array('add', (array)$effective_rights['entryLevelRights'])) {
+            return false;
+        }
 
         $type  = $postdata['type'];
         $query = array(
@@ -128,10 +131,13 @@ class kolab_api_service_type extends kolab_api_service
             return false;
         }
 
-        $object_name = $postdata['type'];
-        $object_id   = $postdata['id'];
+        $object_name      = $postdata['type'];
+        $object_id        = $postdata['id'];
+        $effective_rights = $this->type_effective_rights();
 
-        // @TODO: check privileges
+        if (!in_array('delete', (array)$effective_rights['entryLevelRights'])) {
+            return false;
+        }
 
         $this->db->query("DELETE FROM {$object_name}_types WHERE id = " . intval($object_id));
 
@@ -160,7 +166,11 @@ class kolab_api_service_type extends kolab_api_service
             return false;
         }
 
-        // @TODO: check privileges
+        $effective_rights = $this->type_effective_rights();
+        if (!in_array('modrdn', (array)$effective_rights['entryLevelRights'])) {
+            return false;
+        }
+
         $type  = $postdata['type'];
         $query = array(
             'key'         => $postdata['key'],
@@ -190,7 +200,7 @@ class kolab_api_service_type extends kolab_api_service
         return $postdata;
     }
 
-    public function type_effective_rights($getdata, $postdata)
+    public function type_effective_rights($getdata = null, $postdata = null)
     {
         $effective_rights = array();
         // @TODO: set rights according to user group or sth
@@ -198,7 +208,7 @@ class kolab_api_service_type extends kolab_api_service
             $attr_acl = array('read', 'write', 'delete');
             $effective_rights = array(
                 'entryLevelRights' => array(
-                    'read', 'add', 'delete', 'write',
+                    'read', 'add', 'delete', 'modrdn',
                 ),
                 'attributeLevelRights' => array(
                     'key'         => $attr_acl,


commit d4eed4870482dc6ffc159f8db0ecbca0fc6d6f06
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 3 13:27:36 2012 +0200

    Object types management - editable

diff --git a/lib/client/kolab_client_task_settings.php b/lib/client/kolab_client_task_settings.php
index 09d7697..1e87de5 100644
--- a/lib/client/kolab_client_task_settings.php
+++ b/lib/client/kolab_client_task_settings.php
@@ -32,9 +32,10 @@ class kolab_client_task_settings extends kolab_client_task
     );
 
     protected $form_element_types = array(
-        'text', 'select', 'multiselect', 'list', 'checkbox', 'password'
+        'text', 'select', 'multiselect', 'list', 'list-autocomplete', 'checkbox', 'password'
     );
 
+
     /**
      * Default action.
      */
@@ -382,9 +383,11 @@ class kolab_client_task_settings extends kolab_client_task
         $title    = $add_mode ? $this->translate('type.add') : $data['name'];
 
         // unset $data for correct form_create() run, we've got already data specified
+        $effective_rights = $data['effective_rights'];
+        $id = $data['id'] ? $data['type'].':'.$data['id'] : null;
         $data = array();
-        // enable delete button
-        $data['effective_rights']['entry'] = array('delete');
+        $data['effective_rights'] = $effective_rights;
+        $data['id'] = $id;
 
         // Create form object and populate with fields
         $form = $this->form_create('type', $attribs, $sections, $fields, $fields_map, $data, $add_mode);
@@ -431,6 +434,7 @@ class kolab_client_task_settings extends kolab_client_task
                 'multiple' => true,
                 'required' => true,
                 'value'    => $data['objectclass'],
+                'onchange' => "kadm.type_attr_class_change(this)",
             ),
             'used_for' => array(
                 'value'   => 'hosted',
@@ -447,7 +451,7 @@ class kolab_client_task_settings extends kolab_client_task
             unset($form_fields['used_for']);
         }
 
-/*
+
         // Get the rights on the entry and attribute level
         $data['effective_rights'] = $this->effective_rights($name, $data['id']);
         $attribute_rights         = $data['effective_rights']['attribute'];
@@ -455,7 +459,7 @@ class kolab_client_task_settings extends kolab_client_task
 
         // See if "administrators" (those who can delete and add back on the entry
         // level) may override the automatically generated contents of auto_form_fields.
-        $admin_auto_fields_rw = $this->config_get('admin_auto_fields_rw', false, Conf::BOOL);
+        //$admin_auto_fields_rw = $this->config_get('admin_auto_fields_rw', false, Conf::BOOL);
 
         foreach ($fields as $idx => $field) {
             if (!array_key_exists($idx, $attribute_rights)) {
@@ -481,7 +485,7 @@ class kolab_client_task_settings extends kolab_client_task
                 }
             }
         }
-*/
+
         // (Re-|Pre-)populate auto_form_fields
         if (!$add_mode) {
             // Add debug information
@@ -500,7 +504,7 @@ class kolab_client_task_settings extends kolab_client_task
         }
 
         // Get object classes
-        $sd = $this->form_element_select_data($fields['objectclass']);
+        $sd = $this->form_element_select_data($fields['objectclass'], null, true);
         $fields['objectclass'] = array_merge($fields['objectclass'], $sd);
 
         // Add entry identifier
@@ -521,39 +525,42 @@ class kolab_client_task_settings extends kolab_client_task
         return $fields;
     }
 
+    /**
+     * Type attributes table
+     */
     private function type_form_attributes($data)
     {
         $attributes = array();
         $rows       = array();
+        $attr_table = array();
         $table      = array(
+            'id'    => 'type_attr_table',
             'class' => 'list',
         );
         $cells      = array(
             'name' => array(
-                'class' => 'name',
                 'body'  => $this->translate('attribute.name'),
             ),
             'type' => array(
-                'class' => 'type',
                 'body'  => $this->translate('attribute.type'),
             ),
+            'readonly' => array(
+                'body'  => $this->translate('attribute.readonly'),
+            ),
             'optional' => array(
-                'class' => 'optional',
                 'body'  => $this->translate('attribute.optional'),
             ),
-            'auto' => array(
-                'class' => 'auto',
-                'body'  => $this->translate('attribute.auto'),
+            'value' => array(
+                'body'  => $this->translate('attribute.value'),
             ),
-            'static' => array(
-                'class' => 'default',
-                'body'  => $this->translate('attribute.static'),
+            'actions' => array(
             ),
-//            'actions' => array(
-//                'class' => 'actions',
-//            ),
         );
 
+        foreach ($cells as $idx => $cell) {
+            $cells[$idx]['class'] = $idx;
+        }
+
         // get attributes list from $data
         if (!empty($data) && count($data) > 1) {
             $attributes = array_merge(
@@ -565,43 +572,229 @@ class kolab_client_task_settings extends kolab_client_task
             $attributes = array_unique($attributes);
         }
 
+        // get all available attributes
+        $available = $this->type_attributes($data['objectclass']);
+
         // table header
         $table['head'] = array(array('cells' => $cells));
-/*
-        // attribute row elements
-        $cells['type']['element'] = array(
-            'type'    => kolab_form::INPUT_SELECT,
-            'options' => $this->form_element_types,
-        );
-        $cells['optional']['element'] = array(
-            'type'  => kolab_form::INPUT_CHECKBOX,
-            'value' => 1,
-        );
-*/
+
         $yes = $this->translate('yes');
+        $no  = '';
         // defined attributes
         foreach ($attributes as $attr) {
-            $row = $cells;
+            $row          = $cells;
+            $type         = $data['attributes']['form_fields'][$attr]['type'];
+            $optional     = $data['attributes']['form_fields'][$attr]['optional'];
+            $autocomplete = $data['attributes']['form_fields'][$attr]['autocomplete'];
+            $valtype      = 'normal';
+            $value        = '';
+
+            if ($type == 'list' && $autocomplete) {
+                $type = 'list-autocomplete';
+            }
+
+            if ($data['attributes']['fields'][$attr]) {
+                $valtype = 'static';
+                $_data   = $data['attributes']['fields'][$attr];
+                $value   = $this->translate('attribute.value.static') . ': ' . kolab_html::escape($_data);
+            }
+            else if (isset($data['attributes']['auto_form_fields'][$attr])) {
+                $valtype = 'auto';
+                if (is_array($data['attributes']['auto_form_fields'][$attr]['data'])) {
+                    $_data = implode(',', $data['attributes']['auto_form_fields'][$attr]['data']);
+                }
+                else {
+                    $_data = '';
+                }
+                $value = $this->translate('attribute.value.auto') . ': ' . kolab_html::escape($_data);
 
-            $type     = $data['attributes']['form_fields'][$attr]['type'];
-            $optional = $data['attributes']['form_fields'][$attr]['optional'];
+                if (empty($data['attributes']['form_fields'][$attr])) {
+                    $valtype = 'auto-readonly';
+                }
+            }
 
             // set cell content
-            $row['name']['body']     = $attr;
-            $row['static']['body']   = kolab_html::escape($data['attributes']['fields'][$attr]);
-            $row['auto']['body']     = isset($data['attributes']['fields'][$attr]) ? $yes : '';
+            $row['name']['body']     = !empty($available[$attr]) ? $available[$attr] : $attr;
             $row['type']['body']     = !empty($type) ? $type : 'text';
-            $row['optional']['body'] = $optional ? $yes : '';
+            $row['value']['body']    = $value;
+            $row['readonly']['body'] = $valtype == 'auto-readonly' ? $yes : $no;
+            $row['optional']['body'] = $optional ? $yes : $no;
+            $row['actions']['body']  = 
+                kolab_html::a(array('href' => '#delete', 'onclick' => "kadm.type_attr_delete('$attr')",
+                    'class' => 'button delete', 'title' => $this->translate('delete')))
+                . kolab_html::a(array('href' => '#edit', 'onclick' => "kadm.type_attr_edit('$attr')",
+                    'class' => 'button edit', 'title' => $this->translate('edit')));
+
+            $rows[] = array(
+                'id'    => 'attr_table_row_' . $attr,
+                'cells' => $row,
+            );
 
-            $rows[] = array('cells' => $row);
+            // data array for the UI
+            $attr_table[$attr] = array(
+                'type'     => !empty($type) ? $type : 'text',
+                'valtype'  => $valtype,
+                'optional' => $optional,
+                'maxcount' => $data['attributes']['form_fields'][$attr]['maxcount'],
+                'data'     => $_data,
+                'values'   => $data['attributes']['form_fields'][$attr]['values'],
+            );
         }
 
+        // edit form
+        $rows[] = array(
+            'cells' => array(
+                array(
+                    'body'    => $this->type_form_attributes_form($available),
+                    'colspan' => count($cells),
+                ),
+            ),
+            'id' => 'type_attr_form',
+        );
+
         $table['body'] = $rows;
 
+        // sort attr_table by attribute name
+        ksort($attr_table);
+
+        // set environment variables
+        $this->output->set_env('attr_table', $attr_table);
+        $this->output->set_env('yes_label', $yes);
+        $this->output->set_env('no_label', $no);
+        $this->output->add_translation('attribute.value.auto', 'attribute.value.static',
+            'attribute.key.invalid');
+
+        // Add attribute link
+        $link = kolab_html::a(array(
+            'href' => '#add_attr', 'class' => 'add_attr',
+            'onclick' => "kadm.type_attr_add()",
+            'content' =>  $this->translate('attribute.add')), true);
+
+        return kolab_html::table($table) . $link;
+    }
+
+    /**
+     * Attributes edit form
+     */
+    private function type_form_attributes_form($attributes)
+    {
+        // build form
+        $rows = array();
+        $form = array(
+            'name' => array(
+                'type' => kolab_form::INPUT_SELECT,
+                'options' => $attributes,
+            ),
+            'type' => array(
+                'type' => kolab_form::INPUT_SELECT,
+                'options' => array_combine($this->form_element_types, $this->form_element_types),
+                'onchange' => 'kadm.type_attr_type_change(this)',
+            ),
+            'options' => array(
+                'type'      => kolab_form::INPUT_TEXTAREA,
+                'data-type' => kolab_form::TYPE_LIST,
+            ),
+            'maxcount' => array(
+                'type' => kolab_form::INPUT_TEXT,
+                'size' => 5,
+            ),
+            'value' => array(
+                'type'  => kolab_form::INPUT_SELECT,
+                'options' => array(
+                    'normal'        => $this->translate('attribute.value.normal'),
+                    'auto'          => $this->translate('attribute.value.auto'),
+                    'auto-readonly' => $this->translate('attribute.value.auto-readonly'),
+                    'static'        => $this->translate('attribute.value.static'),
+                ),
+                'onchange' => 'kadm.type_attr_value_change(this)',
+            ),
+            'optional' => array(
+                'type'  => kolab_form::INPUT_CHECKBOX,
+                'value' => 1,
+            ),
+        );
+
+        foreach ($form as $idx => $element) {
+            $element['name'] = 'attr_' . $idx;
+            $body = kolab_form::get_element($element);
+
+            if ($idx == 'value') {
+                $body .= kolab_form::get_element(array(
+                    'name' => 'attr_data',
+                    'type' => kolab_form::INPUT_TEXT,
+                ));
+            }
+
+            $rows[] = array(
+                'id' => 'attr_form_row_' . $idx,
+                'cells' => array(
+                    array(
+                        'class' => 'label',
+                        'body'  => $this->translate('attribute.' . $idx),
+                    ),
+                    array(
+                        'class' => 'value',
+                        'body'  => $body,
+                    ),
+                ),
+            );
+        }
+
+        $rows[] = array(
+            'cells' => array(
+                array(
+                    'colspan' => 2,
+                    'class' => 'formbuttons',
+                    'body' => kolab_html::input(array(
+                        'type'    => 'button',
+                        'value'   => $this->translate('button.save'),
+                        'onclick' => "kadm.type_attr_save()",
+                    ))
+                    . kolab_html::input(array(
+                        'type'    => 'button',
+                        'value'   => $this->translate('button.cancel'),
+                        'onclick' => "kadm.type_attr_cancel()",
+                    )),
+                ),
+            ),
+        );
+
+        $table = array(
+            'class' => 'form',
+            'body'  => $rows,
+        );
+
         return kolab_html::table($table);
     }
 
     /**
+     * Returns list of LDAP attributes for specified opject classes.
+     */
+    public function type_attributes($object_class = null)
+    {
+        $post_data = array(
+            'attributes' => array('attribute'),
+            'classes'    => $object_class,
+        );
+
+        // get all available attributes
+        $response   = $this->api->post('form_value.select_options', null, $post_data);
+        $response   = $response->get('attribute');
+        $attributes = array();
+
+        // convert to hash array
+        if (!empty($response['list'])) {
+            $attributes = array_combine(array_map('strtolower', $response['list']), $response['list']);
+        }
+
+        $this->output->set_env('attributes', $attributes);
+        // @TODO: check if all required attributes are used
+//        $this->output->set_env('attributes_required', $attributes['required']);
+
+        return $attributes;
+    }
+
+    /**
      * Users search form.
      *
      * @return string HTML output of the form
diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php
index 13a90f1..fec0404 100644
--- a/lib/kolab_client_task.php
+++ b/lib/kolab_client_task.php
@@ -279,10 +279,10 @@ class kolab_client_task
         // Run security checks
         $this->input_checks();
 
-        $action = $this->get_input('action', 'GET');
+        $this->action = $this->get_input('action', 'GET');
 
-        if ($action) {
-            $method = 'action_' . $action;
+        if ($this->action) {
+            $method = 'action_' . $this->action;
             if (method_exists($this, $method)) {
                 $this->$method();
             }
@@ -817,10 +817,11 @@ class kolab_client_task
      *
      * @param array $field Field attributes
      * @param array $data  Attribute values
+     * @param bool  $lc    Convert option values to lower-case
      *
      * @return array Options/Default definition
      */
-    protected function form_element_select_data($field, $data = array())
+    protected function form_element_select_data($field, $data = array(), $lc = false)
     {
         $options = array();
         $default = null;
@@ -836,7 +837,12 @@ class kolab_client_task
         }
 
         if (!empty($field['values'])) {
-            $options = array_combine($field['values'], $field['values']);
+            if ($lc) {
+                $options = array_combine(array_map('strtolower', $field['values']), $field['values']);
+            }
+            else {
+                $options = array_combine($field['values'], $field['values']);
+            }
 
             // Exceptions
             if ($field['name'] == 'ou') {
@@ -1237,7 +1243,7 @@ class kolab_client_task
 
         if ($writeable) {
             $form->add_button(array(
-                'value'   => kolab_html::escape($this->translate('submit.button')),
+                'value'   => kolab_html::escape($this->translate('button.submit')),
                 'onclick' => "kadm.{$name}_save()",
             ));
         }
@@ -1245,7 +1251,7 @@ class kolab_client_task
         if (!empty($data['id']) && in_array('delete', $data['effective_rights']['entry'])) {
             $id = $data['id'];
             $form->add_button(array(
-                'value'   => kolab_html::escape($this->translate('delete.button')),
+                'value'   => kolab_html::escape($this->translate('button.delete')),
                 'onclick' => "kadm.{$name}_delete('{$id}')",
             ));
         }
diff --git a/lib/locale/en_US.php b/lib/locale/en_US.php
index 9f5561e..23d81d4 100644
--- a/lib/locale/en_US.php
+++ b/lib/locale/en_US.php
@@ -7,16 +7,29 @@ $LANG['about.support'] = 'Professional support is available from <a href="http:/
 $LANG['about.technology'] = 'Technology';
 $LANG['about.warranty'] = 'It comes with absolutely <b>no warranties</b> and is typically run entirely self supported. You can find help & information on the community <a href="http://kolab.org">web site</a> & <a href="http://wiki.kolab.org">wiki</a>.';
 
-$LANG['attribute.auto'] = 'Auto-generated';
+$LANG['attribute.add'] = 'Add attribute';
 $LANG['attribute.static'] = 'Static value';
 $LANG['attribute.name'] = 'Attribute';
 $LANG['attribute.optional'] = 'Optional';
+$LANG['attribute.maxcount'] = 'Max. count';
+$LANG['attribute.readonly'] = 'Read-only';
 $LANG['attribute.type'] = 'Field type';
+$LANG['attribute.value'] = 'Value';
+$LANG['attribute.value.auto'] = 'Generated';
+$LANG['attribute.value.auto-readonly'] = 'Generated (read-only)';
+$LANG['attribute.value.normal'] = 'Normal';
+$LANG['attribute.value.static'] = 'Static';
+$LANG['attribute.options'] = 'Options';
+$LANG['attribute.key.invalid'] = 'Type key contains forbidden characters!';
+
+$LANG['button.cancel'] = 'Cancel';
+$LANG['button.delete'] = 'Delete';
+$LANG['button.save'] = 'Save';
+$LANG['button.submit'] = 'Submit';
 
 $LANG['creatorsname'] = 'Created by';
 $LANG['days'] = 'days';
 $LANG['debug'] = 'Debug info';
-$LANG['delete.button'] = 'Delete';
 $LANG['deleting'] = 'Deleting data...';
 
 $LANG['domain.add'] = 'Add Domain';
@@ -140,12 +153,13 @@ $LANG['signup.wronguid'] = 'Invalid Username!';
 $LANG['signup.wrongmailalternateaddress'] = 'Please provide a valid Email Address!';
 $LANG['signup.footer'] = 'This is a service offered by <a href="http://kolabsys.com">Kolab Systems</a>.';
 
-$LANG['submit.button'] = 'Submit';
-
 $LANG['type.add'] = 'Add Object Type';
+$LANG['type.add.success'] = 'Object type created successfully.';
 $LANG['type.attributes'] = 'Attributes';
 $LANG['type.description'] = 'Description';
+$LANG['type.delete.success'] = 'Object type deleted successfully.';
 $LANG['type.domain'] = 'Domain';
+$LANG['type.edit.success'] = 'Object type updated successfully.';
 $LANG['type.group'] = 'Group';
 $LANG['type.list'] = 'Object Types List';
 $LANG['type.key'] = 'Key';
@@ -240,3 +254,5 @@ $LANG['user.userpassword2'] = 'Confirm password';
 $LANG['user.uidnumber'] = 'User ID number';
 
 $LANG['welcome'] = 'Welcome to the Kolab Groupware Server Maintenance';
+
+$LANG['yes'] = 'yes';
diff --git a/public_html/js/kolab_admin.js b/public_html/js/kolab_admin.js
index c58a592..e4bf81e 100644
--- a/public_html/js/kolab_admin.js
+++ b/public_html/js/kolab_admin.js
@@ -1648,9 +1648,6 @@ function kolab_admin()
 
   this.type_delete = function(id)
   {
-    // @TODO:
-    return alert('Not implemented');
-
     this.set_busy(true, 'deleting');
     this.api_post('type.delete', this.type_id_parse(id), 'type_delete_response');
   };
@@ -1667,16 +1664,14 @@ function kolab_admin()
       page -= 1;
 
     this.display_message('type.delete.success');
-    this.command('type.list', {page: page});
+    this.command('settings.type_list', {page: page});
     this.set_watermark('taskcontent');
   };
 
   this.type_save = function(reload, section)
   {
-    // @TODO:
-    return alert('Not implemented');
-
-    var data = this.serialize_form('#'+this.env.form_id),
+    var i, attr, request = {},
+      data = this.serialize_form('#'+this.env.form_id),
       action = data.id ? 'edit' : 'add';
 
     if (reload) {
@@ -1692,8 +1687,57 @@ function kolab_admin()
       return;
     }
 
+    if (data.key.match(/[^a-z_-]/)) {
+      this.display_message('attribute.key.invalid', 'error');
+      return;
+    }
+
+    request.id = data.id;
+    request.key = data.key;
+    request.name = data.name;
+    request.type = data.type;
+    request.description = data.description;
+    request.used_for = data.used_for;
+    request.attributes = {fields: {}, form_fields: {}, auto_form_fields: {}};
+    request.attributes.fields.objectclass = data.objectclass;
+
+    // Build attributes array compatible with the API format
+    // @TODO: use attr_table format
+    for (i in this.env.attr_table) {
+      attr = this.env.attr_table[i];
+      data = {};
+
+      if (attr.valtype == 'static') {
+        request.attributes.fields[i] = attr.data;
+        continue;
+      }
+
+      if (attr.type == 'list-autocomplete') {
+        data.type = 'list';
+        data.autocomplete = true;
+      }
+      else if (attr.type != 'text')
+        data.type = attr.type;
+
+      if ((attr.type == 'select' || attr.type == 'multiselect') && attr.values)
+        data.values = attr.values;
+
+      if (attr.optional)
+        data.optional = true;
+      if (attr.maxcount)
+        data.maxcount = attr.maxcount;
+
+      if (attr.valtype == 'normal' || attr.valtype == 'auto')
+        request.attributes.form_fields[i] = data;
+      if (attr.valtype == 'auto' || attr.valtype == 'auto-readonly') {
+        if (attr.data)
+          data.data = attr.data.split(/,/);
+        request.attributes.auto_form_fields[i] = data;
+      }
+    }
+
     this.set_busy(true, 'saving');
-    this.api_post('type.' + action, data, 'type_' + action + '_response');
+    this.api_post('type.' + action, request, 'type_' + action + '_response');
   };
 
   this.type_add_response = function(response)
@@ -1716,12 +1760,199 @@ function kolab_admin()
     this.set_watermark('taskcontent');
   };
 
+  /*********************************************************/
+  /*********       Various helper methods          *********/
+  /*********************************************************/
+
+  // Parses object type identifier
   this.type_id_parse = function(id)
   {
     var id = String(id).split(':');
     return {type: id[0], id: id[1]};
   };
 
+  // Removes attribute row
+  this.type_attr_delete = function(attr)
+  {
+    $('#attr_table_row_' + attr).remove();
+    $('select[name="attr_name"] > option[value="'+attr+'"]').show();
+
+    delete this.env.attr_table[attr];
+    this.type_attr_cancel();
+  };
+
+  // Displays attribute edition form
+  this.type_attr_edit = function(attr)
+  {
+    var form = $('#type_attr_form');
+
+    form.detach();
+    $('#attr_table_row_'+attr).after(form);
+    this.type_attr_form_init(attr);
+    form.slideDown(400);
+  };
+
+  // Displays attribute addition form
+  this.type_attr_add = function()
+  {
+    var form = $('#type_attr_form');
+
+    form.detach();
+    $('#type_attr_table > tbody').append(form);
+    this.type_attr_form_init();
+    form.slideDown(400);
+  };
+
+  // Saves attribute form, create/update attribute row
+  this.type_attr_save = function()
+  {
+    var attr, row, value = '', data = {},
+      form_data = this.serialize_form('#'+this.env.form_id),
+      name_select = $('select[name="attr_name"]');
+
+    // read attribute form data
+    data.type = form_data.attr_type;
+    data.valtype = form_data.attr_value;
+    data.optional = form_data.attr_optional;
+    data.data = data.valtype != 'normal' ? form_data.attr_data : null;
+    data.maxcount = data.type == 'list' || data.type == 'list-autocomplete' ? form_data.attr_maxcount : 0;
+    data.values = data.type == 'select' || data.type == 'multiselect' ? form_data.attr_options : [];
+
+    if (name_select.is(':visible')) {
+      // new attribute
+      attr = name_select.val();
+      row = $('<tr><td class="name"></td><td class="type"></td><td class="readonly"></td>'
+        +'<td class="optional"></td><td class="value"></td><td class="actions">'
+        +'<a class="button delete" title="delete" onclick="kadm.type_attr_delete(\''+attr+'\')" href="#delete"></a>'
+        +'<a class="button edit" title="edit" onclick="kadm.type_attr_edit(\''+attr+'\')" href="#edit"></a></td></tr>')
+        .attr('id', 'attr_table_row_' + attr).appendTo('#type_attr_table > tbody');
+    }
+    else {
+      // edited attribute
+      attr = $('span', name_select.parent()).text().toLowerCase();
+      row = $('#attr_table_row_' + attr);
+    }
+
+    if (data.valtype != 'normal') {
+      value = this.t('attribute.value.' + (data.valtype == 'static' ? 'static' : 'auto')) + ': ' + data.data;
+    }
+
+    // Update table row
+    $('td.name', row).text(this.env.attributes[attr]);
+    $('td.type', row).text(data.type);
+    $('td.readonly', row).text(data.valtype == 'auto-readonly' ? this.env.yes_label : this.env.no_label);
+    $('td.optional', row).text(data.optional ? this.env.yes_label : this.env.no_label);
+    $('td.value', row).text(value);
+
+    // Update env data
+    this.env.attr_table[attr] = data;
+
+    this.type_attr_cancel();
+  };
+
+  // Hide attribute form
+  this.type_attr_cancel = function()
+  {
+    $('#type_attr_form').hide();
+  };
+
+  this.type_attr_form_init = function(attr)
+  {
+    var name_select = $('select[name="attr_name"]'),
+      data = attr ? this.env.attr_table[attr] : {},
+      type = data.type ? data.type : 'text';
+
+    $('select[name="attr_type"]').val(type);
+    $('select[name="attr_value"]').val(attr ? data.valtype : 'normal');
+    $('input[name="attr_optional"]').attr('checked', attr ? data.optional : false);
+    $('input[name="attr_data"]').val(attr ? data.data : '');
+    $('input[name="attr_maxcount"]').val(data.maxcount ? data.maxcount : '');
+    $('textarea[name="attr_options"]').val(data.values ? data.values.join("\n") : '');
+    this.form_element_update({name: 'attr_options'});
+
+    $('span', name_select.parent()).remove();
+    this.type_attr_type_change('select[name="attr_type"]');
+    this.type_attr_value_change('select[name="attr_value"]');
+
+    if (attr) {
+      name_select.hide().val(attr);
+      $('<span></span>').text(this.env.attributes[attr] ? this.env.attributes[attr] : attr).appendTo(name_select.parent());
+      return;
+    }
+
+    this.type_attr_select_init();
+    name_select.show();
+  };
+
+  // Initialize attribute name selector
+  this.type_attr_select_init = function()
+  {
+    var select = $('select[name="attr_name"]'),
+      options = $('option', select);
+
+    options.each(function() {
+      if (kadm.env.attr_table[this.value])
+        $(this).attr('disabled', true);
+    });
+    options.not(':disabled').first().attr('selected', true);
+  };
+
+  // Update attribute form on value type change
+  this.type_attr_value_change = function(elem)
+  {
+    var type = $(elem).val();
+    $('input[name="attr_data"]')[type != 'normal' ? 'show' : 'hide']();
+    $('#attr_form_row_optional')[type != 'static' ? 'show' : 'hide']();
+    $('#attr_form_row_readonly')[type != 'static' ? 'show' : 'hide']();
+  };
+
+  // Update attribute form on type change
+  this.type_attr_type_change = function(elem)
+  {
+    var type = $(elem).val();
+    $('#attr_form_row_maxcount')[type == 'list' || type == 'list-autocomplete' ? 'show' : 'hide']();
+    $('#attr_form_row_options')[type == 'select' || type == 'multiselect' ? 'show' : 'hide']();
+  };
+
+  // Update attributes list on object classes change
+  this.type_attr_class_change = function(field)
+  {
+    var data = {attributes: 'attribute', classes: this.type_object_classes(field)};
+    this.api_post('form_value.select_options', data, 'type_attr_class_change_response');
+    this.type_attr_cancel();
+  };
+
+  // Update attributes list on object classes change - API response handler
+  this.type_attr_class_change_response = function(response)
+  {
+    if (!this.api_response(response))
+      return;
+
+    var i, lc, list = response.result.attribute.list,
+      required = response.result.attribute.required,
+      select = $('select[name="attr_name"]');
+
+    this.env.attributes = {};
+    select.empty();
+
+    for (i in list) {
+      lc = list[i].toLowerCase()
+      this.env.attributes[list[i].toLowerCase()] = list[i];
+      $('<option>').text(list[i]).val(lc).appendTo(select);
+    }
+  };
+
+  // Return selected objectclasses array
+  this.type_object_classes = function(field)
+  {
+    var classes = [];
+    $('option:selected', $(field)).each(function() {
+      classes.push(this.value);
+    });
+    return classes;
+  };
+
+  // Password generation - request
   this.generate_password = function(fieldname)
   {
     this.env.password_field = fieldname;
@@ -1730,6 +1961,7 @@ function kolab_admin()
     this.api_post('form_value.generate', {attributes: [fieldname]}, 'generate_password_response');
   };
 
+  // Password generation - response handler
   this.generate_password_response = function(response)
   {
     if (!this.api_response(response))
diff --git a/public_html/skins/default/style.css b/public_html/skins/default/style.css
index 201e7f6..cf597ee 100644
--- a/public_html/skins/default/style.css
+++ b/public_html/skins/default/style.css
@@ -117,11 +117,13 @@ table.form td {
 }
 
 table.form tr.required input,
+table.form tr.required select,
 table.form tr.required textarea {
   background-color: #f0f9ff;
 }
 
 table.form tr input.error,
+table.form tr select.error,
 table.form tr textarea.error {
   background-color: #f5e3e3;
 }
@@ -472,6 +474,7 @@ input.inactive {
 
 .formbuttons {
   text-align: center;
+  white-space: nowrap;
 }
 
 .formbuttons input {
@@ -548,6 +551,21 @@ pre.debug {
     -o-box-shadow: 0 2px 6px 0 #333;
 }
 
+a.button {
+  display: inline-block;
+  width: 18px;
+  height: 18px;
+  background: url(images/buttons.png) 0 0 no-repeat;
+}
+
+a.button.edit {
+  background-position: -81px 0;
+}
+
+a.button.delete {
+  background-position: -1px 0;
+}
+
 /********* Form smart inputs *********/
 
 span.listarea {
@@ -944,3 +962,40 @@ td.no {
   font-weight: bold;
   text-align: center;
 }
+
+/**** Settings ****/
+
+table.form table.list td {
+  padding: 2px 4px;
+}
+
+#type_attr_table td.actions {
+  width: 40px;
+  padding: 0;
+  white-space: nowrap;
+}
+
+#type_attr_table thead td {
+  white-space: nowrap;
+}
+
+#type_attr_table tfoot span {
+  cursor: pointer;
+}
+
+#type_attr_table td.readonly {
+  color: #514949;
+}
+
+#type_attr_form {
+  display: none;
+}
+
+#type_attr_form table.form td {
+  border: none;
+}
+
+a.add_attr {
+  padding-left: 2px;
+  padding-top: 2px;
+}


commit e208cf24370b7ef02256482b802f1494a7cd1729
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Sep 28 08:14:53 2012 +0200

    Add edit button for future use

diff --git a/public_html/skins/default/images/buttons.png b/public_html/skins/default/images/buttons.png
index 586851e..d206367 100644
Binary files a/public_html/skins/default/images/buttons.png and b/public_html/skins/default/images/buttons.png differ
diff --git a/public_html/skins/default/style.css b/public_html/skins/default/style.css
index db3e596..201e7f6 100644
--- a/public_html/skins/default/style.css
+++ b/public_html/skins/default/style.css
@@ -629,11 +629,11 @@ span.listelement span.actions span.reset {
 }
 
 span.listelement span.actions span.add {
-  background-position: -43px -2px;
+  background-position: -41px -2px;
 }
 
 span.listelement span.actions span.search {
-  background-position: -65px -1px;
+  background-position: -61px -1px;
   cursor: default;
 }
 


commit a78c8e27074e2f3465bed0db3cecef620ab5b7f9
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Sep 27 15:03:57 2012 +0200

    Fix PHP warning when capability response is empty/malformed

diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php
index 4326b9a..13a90f1 100644
--- a/lib/kolab_client_task.php
+++ b/lib/kolab_client_task.php
@@ -504,11 +504,11 @@ class kolab_client_task
 
         $menu = array();
         $task = $this->get_task();
-        $caps = $this->capabilities();
+        $caps = (array) $this->get_capability('actions');
 
         foreach ($this->menu as $idx => $label) {
             if (in_array($task, array('domain', 'group', 'resource', 'role', 'user'))) {
-                if (!array_key_exists($task . "." . $idx, $caps['actions'])) {
+                if (!array_key_exists($task . "." . $idx, $caps)) {
                     continue;
                 }
             }


commit 162cd8a5981c533fb377855d55aebc5674498ca9
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 3 13:09:55 2012 +0200

    Fix query in type_delete()

diff --git a/lib/api/kolab_api_service_type.php b/lib/api/kolab_api_service_type.php
index 9255d1c..e6c56a1 100644
--- a/lib/api/kolab_api_service_type.php
+++ b/lib/api/kolab_api_service_type.php
@@ -133,7 +133,7 @@ class kolab_api_service_type extends kolab_api_service
 
         // @TODO: check privileges
 
-        $this->db->query("DELETE FROM {$object_name}_types WHERE id = ?", array($object_id));
+        $this->db->query("DELETE FROM {$object_name}_types WHERE id = " . intval($object_id));
 
         return (bool) $this->db->affected_rows();
     }


commit d8baa63fe4744fe763053ea75d0530ecffbc9aea
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 3 12:58:26 2012 +0200

    Implemented type.effective_rights

diff --git a/lib/api/kolab_api_service_type.php b/lib/api/kolab_api_service_type.php
index 4524556..9255d1c 100644
--- a/lib/api/kolab_api_service_type.php
+++ b/lib/api/kolab_api_service_type.php
@@ -192,10 +192,25 @@ class kolab_api_service_type extends kolab_api_service
 
     public function type_effective_rights($getdata, $postdata)
     {
-//        $auth = Auth::get_instance();
-//        $effective_rights = $auth->list_rights(empty($getdata['user']) ? 'user' : $getdata['user']);
-//        return $effective_rights;
-        return array();
+        $effective_rights = array();
+        // @TODO: set rights according to user group or sth
+        if ($_SESSION['user']->get_userid() == 'cn=Directory Manager') {
+            $attr_acl = array('read', 'write', 'delete');
+            $effective_rights = array(
+                'entryLevelRights' => array(
+                    'read', 'add', 'delete', 'write',
+                ),
+                'attributeLevelRights' => array(
+                    'key'         => $attr_acl,
+                    'name'        => $attr_acl,
+                    'description' => $attr_acl,
+                    'used_for'    => $attr_acl,
+                    'attributes'  => $attr_acl,
+                ),
+            );
+        }
+
+        return $effective_rights;
     }
 
     /**


commit 30d0b7bbb45a0ba338f59ea06925eb5f7ac01548
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 3 12:27:33 2012 +0200

    Fix query in type_edit()

diff --git a/lib/api/kolab_api_service_type.php b/lib/api/kolab_api_service_type.php
index 794de16..4524556 100644
--- a/lib/api/kolab_api_service_type.php
+++ b/lib/api/kolab_api_service_type.php
@@ -161,7 +161,6 @@ class kolab_api_service_type extends kolab_api_service
         }
 
         // @TODO: check privileges
-
         $type  = $postdata['type'];
         $query = array(
             'key'         => $postdata['key'],
@@ -179,7 +178,7 @@ class kolab_api_service_type extends kolab_api_service
         }
 
         $result = $this->db->query("UPDATE {$type}_types SET "
-            . implode(', ', $query) . " WHERE id = ?", array($postdata['id']));
+            . implode(', ', $query) . " WHERE id = " . intval($postdata['id']));
 
         if (!$result) {
             return false;


commit 032452bb3fe7d077610baa08ba7eafaa847dcd58
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 3 12:17:30 2012 +0200

    Fix DB column names escaping in SQL queries

diff --git a/lib/api/kolab_api_service_type.php b/lib/api/kolab_api_service_type.php
index 11d9387..794de16 100644
--- a/lib/api/kolab_api_service_type.php
+++ b/lib/api/kolab_api_service_type.php
@@ -92,11 +92,11 @@ class kolab_api_service_type extends kolab_api_service
             $query['used_for'] = $postdata['used_for'] == 'hosted' ? 'hosted' : null;
         }
 
-        $query = array_map(array($this->db, 'escape'), $query);
+        $query   = array_map(array($this->db, 'escape'), $query);
+        $columns = array_map(array($this->db, 'escape_identifier'), array_keys($query));
 
         $this->db->query("INSERT INTO {$type}_types"
-            . " (" . implode(',', array_keys($query)) . ")"
-            . " VALUES (" . implode(',', $query) . ")");
+            . " (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $query) . ")");
 
         if (!($id = $this->db->last_insert_id())) {
             return false;
@@ -175,7 +175,7 @@ class kolab_api_service_type extends kolab_api_service
         }
 
         foreach ($query as $idx => $value) {
-            $query[$idx] = $idx . " = " . $this->db->escape($value);
+            $query[$idx] = $this->db->escape_identifier($idx) . " = " . $this->db->escape($value);
         }
 
         $result = $this->db->query("UPDATE {$type}_types SET "


commit 12bd34693397eb521475c26a6995d3647ddc4ae6
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 3 12:13:55 2012 +0200

    Added escape_identifier() method

diff --git a/lib/SQL.php b/lib/SQL.php
index 2b885e7..3c41d8e 100644
--- a/lib/SQL.php
+++ b/lib/SQL.php
@@ -122,6 +122,18 @@ class SQL
         return "'" . mysql_real_escape_string($str, $this->conn) . "'";
     }
 
+    public function escape_identifier($str)
+    {
+        $name = array();
+
+        foreach (explode('.', $str) as $elem) {
+            $elem = str_replace('`', '', $elem);
+            $name[] = '`' . $elem . '`';
+        }
+
+        return  implode($name, '.');
+    }
+
     private function _connect()
     {
         if (!$this->conn && !$this->conn_tried) {


commit 1701d6a005a80adaadecd0154c6d4d0f6f14458c
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 3 12:11:47 2012 +0200

    Fix escape(), so it connects to DB if $this->conn isn't set

diff --git a/lib/SQL.php b/lib/SQL.php
index 129a090..2b885e7 100644
--- a/lib/SQL.php
+++ b/lib/SQL.php
@@ -30,8 +30,9 @@ class SQL
     private $sql_uri = "mysql://username:password@hostname/database";
 
     /* Placeholder for the existing MySQL connection */
-    private $conn = FALSE;
+    private $conn = false;
 
+    private $conn_tried = false;
     private $sql_stats = array(
         'queries' => 0,
         'query_time' => 0,
@@ -114,12 +115,16 @@ class SQL
             return 'NULL';
         }
 
+        if (!$this->conn) {
+            $this->_connect();
+        }
+
         return "'" . mysql_real_escape_string($str, $this->conn) . "'";
     }
 
     private function _connect()
     {
-        if (!$this->conn) {
+        if (!$this->conn && !$this->conn_tried) {
             Log::debug("SQL: Connecting to " . $this->sql_uri);
 
             $_uri = parse_url($this->sql_uri);
@@ -127,6 +132,7 @@ class SQL
             $this->_password = $_uri['pass'];
             $this->_hostname = $_uri['host'];
             $this->_database = str_replace('/','',$_uri['path']);
+            $this->conn_tried = true;
 
             $this->conn = mysql_connect($this->_hostname, $this->_username, $this->_password);
             mysql_select_db($this->_database, $this->conn);


commit 7fe2acb416ed4524acf0d64cd1e92c50e2bdbb00
Author: Torsten Grote <grote at kolabsys.com>
Date:   Tue Oct 2 19:31:47 2012 +0200

    put new signups into openERP as leads

diff --git a/lib/client/kolab_client_task_signup.php b/lib/client/kolab_client_task_signup.php
index f2e9136..b0a58c9 100644
--- a/lib/client/kolab_client_task_signup.php
+++ b/lib/client/kolab_client_task_signup.php
@@ -170,7 +170,9 @@ class kolab_client_task_signup extends kolab_client_task
             return;
         } else {
             $this->output->set_object('taskcontent', $this->translate('signup.usercreated'));
+            // TODO catch errors
             $this->send_mail($data);
+            $this->add_to_openerp($data);
         }
     }
 
@@ -310,6 +312,7 @@ class kolab_client_task_signup extends kolab_client_task
             $headers['To']      = $this->config_get('mail_address');
             $headers['Subject'] = 'New Kolab Signup from ' . $data['mailalternateaddress'];
 
+            // TODO localize this
             $body = "{$data['cn']} <{$data['mailalternateaddress']}> from {$data['org']} just signed up for a Kolab account ({$data['mail']}).";
 
             Log::Debug("Send Mail to $recipients ...");
@@ -324,6 +327,59 @@ class kolab_client_task_signup extends kolab_client_task
         }
     }
 
+    private function add_to_openerp($data)
+    {
+        $HOST = $this->config_get('openerp_host');
+        $PORT = $this->config_get('openerp_port');
+        $DB   = $this->config_get('openerp_db');
+        $USER = $this->config_get('openerp_user');
+        $PASS = $this->config_get('openerp_pass');
+
+        if($HOST && $PORT && $DB && $USER && $PASS) {
+            include_once('xmlrpc/xmlrpc.inc');
+
+            $sock = new xmlrpc_client("http://$HOST:$PORT/xmlrpc/common");
+            $msg = new xmlrpcmsg('login');
+            $msg->addParam(new xmlrpcval($DB,   "string"));
+            $msg->addParam(new xmlrpcval($USER, "string"));
+            $msg->addParam(new xmlrpcval($PASS, "string"));
+            $resp =  $sock->send($msg);
+            $val = $resp->value();
+            $uid = $val->scalarval();
+
+            Log::Trace("Logged in to OpenERP as $USER (uid:$uid)");
+
+            // Create a new lead
+            $arrayVal = array(
+                // TODO localize name string
+                'name' => new xmlrpcval("New Evaluation Signup by {$data['mailalternateaddress']}", "string"),
+                'contact_name' => new xmlrpcval($data['cn'], "string"),
+                'email_from' => new xmlrpcval($data['mailalternateaddress'], "string"),
+                'partner_name' => new xmlrpcval($data['org'], "string"),
+                'inventor_id' => new xmlrpcval($uid, "int"),
+            );
+
+            $client = new xmlrpc_client("http://$HOST:$PORT/xmlrpc/object");
+
+            $msg = new xmlrpcmsg('execute');
+            $msg->addParam(new xmlrpcval($DB, "string"));
+            $msg->addParam(new xmlrpcval($uid, "int"));
+            $msg->addParam(new xmlrpcval($PASS, "string"));
+            $msg->addParam(new xmlrpcval("crm.lead", "string"));
+            $msg->addParam(new xmlrpcval("create", "string"));
+            $msg->addParam(new xmlrpcval($arrayVal, "struct"));
+            $resp = $client->send($msg);
+
+            if ($resp->faultCode()) {
+                Log::Error($resp->faultString());
+            } else {
+                Log::Debug('Lead '.$resp->value()->scalarval().' created in OpenERP!');
+            }
+        } else {
+            Log::Debug('OpenERP settings in kolab.conf are incomplete.');
+        }
+    }
+
     /**
      * Overrides config_get() from kolab_client_task
      * Returns configuration option value for hosting.


commit d4f5a9ade7efc70142d73ac6f33906a5d2207c79
Author: Torsten Grote <grote at kolabsys.com>
Date:   Tue Oct 2 15:36:05 2012 +0200

    send mail after signup

diff --git a/lib/client/kolab_client_task_signup.php b/lib/client/kolab_client_task_signup.php
index 86789b0..f2e9136 100644
--- a/lib/client/kolab_client_task_signup.php
+++ b/lib/client/kolab_client_task_signup.php
@@ -170,6 +170,7 @@ class kolab_client_task_signup extends kolab_client_task
             return;
         } else {
             $this->output->set_object('taskcontent', $this->translate('signup.usercreated'));
+            $this->send_mail($data);
         }
     }
 
@@ -300,6 +301,29 @@ class kolab_client_task_signup extends kolab_client_task
         return $domain_form_names;
     }
 
+    private function send_mail($data)
+    {
+        if($this->config_get('send_signup_mail', 'false', Conf::BOOL) && $this->config_get('mail_address')) {
+            $recipients = $this->config_get('mail_address');
+
+            $headers['From']    = 'Kolab Web Admin Signup <noreply@' . $this->config_get('primary_domain') . '>';
+            $headers['To']      = $this->config_get('mail_address');
+            $headers['Subject'] = 'New Kolab Signup from ' . $data['mailalternateaddress'];
+
+            $body = "{$data['cn']} <{$data['mailalternateaddress']}> from {$data['org']} just signed up for a Kolab account ({$data['mail']}).";
+
+            Log::Debug("Send Mail to $recipients ...");
+            Log::Trace($body);
+
+            $mail =& Mail::factory('sendmail');
+            $result = $mail->send($recipients, $headers, $body);
+
+            if(PEAR::isError($result)) {
+                Log::Error($result->toString());
+            }
+        }
+    }
+
     /**
      * Overrides config_get() from kolab_client_task
      * Returns configuration option value for hosting.
diff --git a/lib/ext/Mail.php b/lib/ext/Mail.php
new file mode 100755
index 0000000..75132ac
--- /dev/null
+++ b/lib/ext/Mail.php
@@ -0,0 +1,270 @@
+<?php
+/**
+ *  PEAR's Mail:: interface.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2002-2007, Richard Heyes
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * o Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * o The names of the authors may not be used to endorse or promote
+ *   products derived from this software without specific prior written
+ *   permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category    Mail
+ * @package     Mail
+ * @author      Chuck Hagenbuch <chuck at horde.org>
+ * @copyright   1997-2010 Chuck Hagenbuch
+ * @license     http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version     CVS: $Id: Mail.php 294747 2010-02-08 08:18:33Z clockwerx $
+ * @link        http://pear.php.net/package/Mail/
+ */
+
+require_once 'PEAR.php';
+
+/**
+ * PEAR's Mail:: interface. Defines the interface for implementing
+ * mailers under the PEAR hierarchy, and provides supporting functions
+ * useful in multiple mailer backends.
+ *
+ * @access public
+ * @version $Revision: 294747 $
+ * @package Mail
+ */
+class Mail
+{
+    /**
+     * Line terminator used for separating header lines.
+     * @var string
+     */
+    var $sep = "\r\n";
+
+    /**
+     * Provides an interface for generating Mail:: objects of various
+     * types
+     *
+     * @param string $driver The kind of Mail:: object to instantiate.
+     * @param array  $params The parameters to pass to the Mail:: object.
+     * @return object Mail a instance of the driver class or if fails a PEAR Error
+     * @access public
+     */
+    function &factory($driver, $params = array())
+    {
+        $driver = strtolower($driver);
+        @include_once 'Mail/' . $driver . '.php';
+        $class = 'Mail_' . $driver;
+        if (class_exists($class)) {
+            $mailer = new $class($params);
+            return $mailer;
+        } else {
+            return PEAR::raiseError('Unable to find class for driver ' . $driver);
+        }
+    }
+
+    /**
+     * Implements Mail::send() function using php's built-in mail()
+     * command.
+     *
+     * @param mixed $recipients Either a comma-seperated list of recipients
+     *              (RFC822 compliant), or an array of recipients,
+     *              each RFC822 valid. This may contain recipients not
+     *              specified in the headers, for Bcc:, resending
+     *              messages, etc.
+     *
+     * @param array $headers The array of headers to send with the mail, in an
+     *              associative array, where the array key is the
+     *              header name (ie, 'Subject'), and the array value
+     *              is the header value (ie, 'test'). The header
+     *              produced from those values would be 'Subject:
+     *              test'.
+     *
+     * @param string $body The full text of the message body, including any
+     *               Mime parts, etc.
+     *
+     * @return mixed Returns true on success, or a PEAR_Error
+     *               containing a descriptive error message on
+     *               failure.
+     *
+     * @access public
+     * @deprecated use Mail_mail::send instead
+     */
+    function send($recipients, $headers, $body)
+    {
+        if (!is_array($headers)) {
+            return PEAR::raiseError('$headers must be an array');
+        }
+
+        $result = $this->_sanitizeHeaders($headers);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        // if we're passed an array of recipients, implode it.
+        if (is_array($recipients)) {
+            $recipients = implode(', ', $recipients);
+        }
+
+        // get the Subject out of the headers array so that we can
+        // pass it as a seperate argument to mail().
+        $subject = '';
+        if (isset($headers['Subject'])) {
+            $subject = $headers['Subject'];
+            unset($headers['Subject']);
+        }
+
+        // flatten the headers out.
+        list(, $text_headers) = Mail::prepareHeaders($headers);
+
+        return mail($recipients, $subject, $body, $text_headers);
+    }
+
+    /**
+     * Sanitize an array of mail headers by removing any additional header
+     * strings present in a legitimate header's value.  The goal of this
+     * filter is to prevent mail injection attacks.
+     *
+     * @param array $headers The associative array of headers to sanitize.
+     *
+     * @access private
+     */
+    function _sanitizeHeaders(&$headers)
+    {
+        foreach ($headers as $key => $value) {
+            $headers[$key] =
+                preg_replace('=((<CR>|<LF>|0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i',
+                             null, $value);
+        }
+    }
+
+    /**
+     * Take an array of mail headers and return a string containing
+     * text usable in sending a message.
+     *
+     * @param array $headers The array of headers to prepare, in an associative
+     *              array, where the array key is the header name (ie,
+     *              'Subject'), and the array value is the header
+     *              value (ie, 'test'). The header produced from those
+     *              values would be 'Subject: test'.
+     *
+     * @return mixed Returns false if it encounters a bad address,
+     *               otherwise returns an array containing two
+     *               elements: Any From: address found in the headers,
+     *               and the plain text version of the headers.
+     * @access private
+     */
+    function prepareHeaders($headers)
+    {
+        $lines = array();
+        $from = null;
+
+        foreach ($headers as $key => $value) {
+            if (strcasecmp($key, 'From') === 0) {
+                include_once 'Mail/RFC822.php';
+                $parser = new Mail_RFC822();
+                $addresses = $parser->parseAddressList($value, 'localhost', false);
+                if (is_a($addresses, 'PEAR_Error')) {
+                    return $addresses;
+                }
+
+                $from = $addresses[0]->mailbox . '@' . $addresses[0]->host;
+
+                // Reject envelope From: addresses with spaces.
+                if (strstr($from, ' ')) {
+                    return false;
+                }
+
+                $lines[] = $key . ': ' . $value;
+            } elseif (strcasecmp($key, 'Received') === 0) {
+                $received = array();
+                if (is_array($value)) {
+                    foreach ($value as $line) {
+                        $received[] = $key . ': ' . $line;
+                    }
+                }
+                else {
+                    $received[] = $key . ': ' . $value;
+                }
+                // Put Received: headers at the top.  Spam detectors often
+                // flag messages with Received: headers after the Subject:
+                // as spam.
+                $lines = array_merge($received, $lines);
+            } else {
+                // If $value is an array (i.e., a list of addresses), convert
+                // it to a comma-delimited string of its elements (addresses).
+                if (is_array($value)) {
+                    $value = implode(', ', $value);
+                }
+                $lines[] = $key . ': ' . $value;
+            }
+        }
+
+        return array($from, join($this->sep, $lines));
+    }
+
+    /**
+     * Take a set of recipients and parse them, returning an array of
+     * bare addresses (forward paths) that can be passed to sendmail
+     * or an smtp server with the rcpt to: command.
+     *
+     * @param mixed Either a comma-seperated list of recipients
+     *              (RFC822 compliant), or an array of recipients,
+     *              each RFC822 valid.
+     *
+     * @return mixed An array of forward paths (bare addresses) or a PEAR_Error
+     *               object if the address list could not be parsed.
+     * @access private
+     */
+    function parseRecipients($recipients)
+    {
+        include_once 'Mail/RFC822.php';
+
+        // if we're passed an array, assume addresses are valid and
+        // implode them before parsing.
+        if (is_array($recipients)) {
+            $recipients = implode(', ', $recipients);
+        }
+
+        // Parse recipients, leaving out all personal info. This is
+        // for smtp recipients, etc. All relevant personal information
+        // should already be in the headers.
+        $addresses = Mail_RFC822::parseAddressList($recipients, 'localhost', false);
+
+        // If parseAddressList() returned a PEAR_Error object, just return it.
+        if (is_a($addresses, 'PEAR_Error')) {
+            return $addresses;
+        }
+
+        $recipients = array();
+        if (is_array($addresses)) {
+            foreach ($addresses as $ob) {
+                $recipients[] = $ob->mailbox . '@' . $ob->host;
+            }
+        }
+
+        return $recipients;
+    }
+
+}
diff --git a/lib/ext/Mail/RFC822.php b/lib/ext/Mail/RFC822.php
new file mode 100755
index 0000000..58d3646
--- /dev/null
+++ b/lib/ext/Mail/RFC822.php
@@ -0,0 +1,951 @@
+<?php
+/**
+ * RFC 822 Email address list validation Utility
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2001-2010, Richard Heyes
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * o Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * o The names of the authors may not be used to endorse or promote
+ *   products derived from this software without specific prior written
+ *   permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category    Mail
+ * @package     Mail
+ * @author      Richard Heyes <richard at phpguru.org>
+ * @author      Chuck Hagenbuch <chuck at horde.org
+ * @copyright   2001-2010 Richard Heyes
+ * @license     http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version     CVS: $Id: RFC822.php 294749 2010-02-08 08:22:25Z clockwerx $
+ * @link        http://pear.php.net/package/Mail/
+ */
+
+/**
+ * RFC 822 Email address list validation Utility
+ *
+ * What is it?
+ *
+ * This class will take an address string, and parse it into it's consituent
+ * parts, be that either addresses, groups, or combinations. Nested groups
+ * are not supported. The structure it returns is pretty straight forward,
+ * and is similar to that provided by the imap_rfc822_parse_adrlist(). Use
+ * print_r() to view the structure.
+ *
+ * How do I use it?
+ *
+ * $address_string = 'My Group: "Richard" <richard at localhost> (A comment), ted at example.com (Ted Bloggs), Barney;';
+ * $structure = Mail_RFC822::parseAddressList($address_string, 'example.com', true)
+ * print_r($structure);
+ *
+ * @author  Richard Heyes <richard at phpguru.org>
+ * @author  Chuck Hagenbuch <chuck at horde.org>
+ * @version $Revision: 294749 $
+ * @license BSD
+ * @package Mail
+ */
+class Mail_RFC822 {
+
+    /**
+     * The address being parsed by the RFC822 object.
+     * @var string $address
+     */
+    var $address = '';
+
+    /**
+     * The default domain to use for unqualified addresses.
+     * @var string $default_domain
+     */
+    var $default_domain = 'localhost';
+
+    /**
+     * Should we return a nested array showing groups, or flatten everything?
+     * @var boolean $nestGroups
+     */
+    var $nestGroups = true;
+
+    /**
+     * Whether or not to validate atoms for non-ascii characters.
+     * @var boolean $validate
+     */
+    var $validate = true;
+
+    /**
+     * The array of raw addresses built up as we parse.
+     * @var array $addresses
+     */
+    var $addresses = array();
+
+    /**
+     * The final array of parsed address information that we build up.
+     * @var array $structure
+     */
+    var $structure = array();
+
+    /**
+     * The current error message, if any.
+     * @var string $error
+     */
+    var $error = null;
+
+    /**
+     * An internal counter/pointer.
+     * @var integer $index
+     */
+    var $index = null;
+
+    /**
+     * The number of groups that have been found in the address list.
+     * @var integer $num_groups
+     * @access public
+     */
+    var $num_groups = 0;
+
+    /**
+     * A variable so that we can tell whether or not we're inside a
+     * Mail_RFC822 object.
+     * @var boolean $mailRFC822
+     */
+    var $mailRFC822 = true;
+
+    /**
+    * A limit after which processing stops
+    * @var int $limit
+    */
+    var $limit = null;
+
+    /**
+     * Sets up the object. The address must either be set here or when
+     * calling parseAddressList(). One or the other.
+     *
+     * @access public
+     * @param string  $address         The address(es) to validate.
+     * @param string  $default_domain  Default domain/host etc. If not supplied, will be set to localhost.
+     * @param boolean $nest_groups     Whether to return the structure with groups nested for easier viewing.
+     * @param boolean $validate        Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance.
+     *
+     * @return object Mail_RFC822 A new Mail_RFC822 object.
+     */
+    function Mail_RFC822($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null)
+    {
+        if (isset($address))        $this->address        = $address;
+        if (isset($default_domain)) $this->default_domain = $default_domain;
+        if (isset($nest_groups))    $this->nestGroups     = $nest_groups;
+        if (isset($validate))       $this->validate       = $validate;
+        if (isset($limit))          $this->limit          = $limit;
+    }
+
+    /**
+     * Starts the whole process. The address must either be set here
+     * or when creating the object. One or the other.
+     *
+     * @access public
+     * @param string  $address         The address(es) to validate.
+     * @param string  $default_domain  Default domain/host etc.
+     * @param boolean $nest_groups     Whether to return the structure with groups nested for easier viewing.
+     * @param boolean $validate        Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance.
+     *
+     * @return array A structured array of addresses.
+     */
+    function parseAddressList($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null)
+    {
+        if (!isset($this) || !isset($this->mailRFC822)) {
+            $obj = new Mail_RFC822($address, $default_domain, $nest_groups, $validate, $limit);
+            return $obj->parseAddressList();
+        }
+
+        if (isset($address))        $this->address        = $address;
+        if (isset($default_domain)) $this->default_domain = $default_domain;
+        if (isset($nest_groups))    $this->nestGroups     = $nest_groups;
+        if (isset($validate))       $this->validate       = $validate;
+        if (isset($limit))          $this->limit          = $limit;
+
+        $this->structure  = array();
+        $this->addresses  = array();
+        $this->error      = null;
+        $this->index      = null;
+
+        // Unfold any long lines in $this->address.
+        $this->address = preg_replace('/\r?\n/', "\r\n", $this->address);
+        $this->address = preg_replace('/\r\n(\t| )+/', ' ', $this->address);
+
+        while ($this->address = $this->_splitAddresses($this->address));
+
+        if ($this->address === false || isset($this->error)) {
+            require_once 'PEAR.php';
+            return PEAR::raiseError($this->error);
+        }
+
+        // Validate each address individually.  If we encounter an invalid
+        // address, stop iterating and return an error immediately.
+        foreach ($this->addresses as $address) {
+            $valid = $this->_validateAddress($address);
+
+            if ($valid === false || isset($this->error)) {
+                require_once 'PEAR.php';
+                return PEAR::raiseError($this->error);
+            }
+
+            if (!$this->nestGroups) {
+                $this->structure = array_merge($this->structure, $valid);
+            } else {
+                $this->structure[] = $valid;
+            }
+        }
+
+        return $this->structure;
+    }
+
+    /**
+     * Splits an address into separate addresses.
+     *
+     * @access private
+     * @param string $address The addresses to split.
+     * @return boolean Success or failure.
+     */
+    function _splitAddresses($address)
+    {
+        if (!empty($this->limit) && count($this->addresses) == $this->limit) {
+            return '';
+        }
+
+        if ($this->_isGroup($address) && !isset($this->error)) {
+            $split_char = ';';
+            $is_group   = true;
+        } elseif (!isset($this->error)) {
+            $split_char = ',';
+            $is_group   = false;
+        } elseif (isset($this->error)) {
+            return false;
+        }
+
+        // Split the string based on the above ten or so lines.
+        $parts  = explode($split_char, $address);
+        $string = $this->_splitCheck($parts, $split_char);
+
+        // If a group...
+        if ($is_group) {
+            // If $string does not contain a colon outside of
+            // brackets/quotes etc then something's fubar.
+
+            // First check there's a colon at all:
+            if (strpos($string, ':') === false) {
+                $this->error = 'Invalid address: ' . $string;
+                return false;
+            }
+
+            // Now check it's outside of brackets/quotes:
+            if (!$this->_splitCheck(explode(':', $string), ':')) {
+                return false;
+            }
+
+            // We must have a group at this point, so increase the counter:
+            $this->num_groups++;
+        }
+
+        // $string now contains the first full address/group.
+        // Add to the addresses array.
+        $this->addresses[] = array(
+                                   'address' => trim($string),
+                                   'group'   => $is_group
+                                   );
+
+        // Remove the now stored address from the initial line, the +1
+        // is to account for the explode character.
+        $address = trim(substr($address, strlen($string) + 1));
+
+        // If the next char is a comma and this was a group, then
+        // there are more addresses, otherwise, if there are any more
+        // chars, then there is another address.
+        if ($is_group && substr($address, 0, 1) == ','){
+            $address = trim(substr($address, 1));
+            return $address;
+
+        } elseif (strlen($address) > 0) {
+            return $address;
+
+        } else {
+            return '';
+        }
+
+        // If you got here then something's off
+        return false;
+    }
+
+    /**
+     * Checks for a group at the start of the string.
+     *
+     * @access private
+     * @param string $address The address to check.
+     * @return boolean Whether or not there is a group at the start of the string.
+     */
+    function _isGroup($address)
+    {
+        // First comma not in quotes, angles or escaped:
+        $parts  = explode(',', $address);
+        $string = $this->_splitCheck($parts, ',');
+
+        // Now we have the first address, we can reliably check for a
+        // group by searching for a colon that's not escaped or in
+        // quotes or angle brackets.
+        if (count($parts = explode(':', $string)) > 1) {
+            $string2 = $this->_splitCheck($parts, ':');
+            return ($string2 !== $string);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * A common function that will check an exploded string.
+     *
+     * @access private
+     * @param array $parts The exloded string.
+     * @param string $char  The char that was exploded on.
+     * @return mixed False if the string contains unclosed quotes/brackets, or the string on success.
+     */
+    function _splitCheck($parts, $char)
+    {
+        $string = $parts[0];
+
+        for ($i = 0; $i < count($parts); $i++) {
+            if ($this->_hasUnclosedQuotes($string)
+                || $this->_hasUnclosedBrackets($string, '<>')
+                || $this->_hasUnclosedBrackets($string, '[]')
+                || $this->_hasUnclosedBrackets($string, '()')
+                || substr($string, -1) == '\\') {
+                if (isset($parts[$i + 1])) {
+                    $string = $string . $char . $parts[$i + 1];
+                } else {
+                    $this->error = 'Invalid address spec. Unclosed bracket or quotes';
+                    return false;
+                }
+            } else {
+                $this->index = $i;
+                break;
+            }
+        }
+
+        return $string;
+    }
+
+    /**
+     * Checks if a string has unclosed quotes or not.
+     *
+     * @access private
+     * @param string $string  The string to check.
+     * @return boolean  True if there are unclosed quotes inside the string,
+     *                  false otherwise.
+     */
+    function _hasUnclosedQuotes($string)
+    {
+        $string = trim($string);
+        $iMax = strlen($string);
+        $in_quote = false;
+        $i = $slashes = 0;
+
+        for (; $i < $iMax; ++$i) {
+            switch ($string[$i]) {
+            case '\\':
+                ++$slashes;
+                break;
+
+            case '"':
+                if ($slashes % 2 == 0) {
+                    $in_quote = !$in_quote;
+                }
+                // Fall through to default action below.
+
+            default:
+                $slashes = 0;
+                break;
+            }
+        }
+
+        return $in_quote;
+    }
+
+    /**
+     * Checks if a string has an unclosed brackets or not. IMPORTANT:
+     * This function handles both angle brackets and square brackets;
+     *
+     * @access private
+     * @param string $string The string to check.
+     * @param string $chars  The characters to check for.
+     * @return boolean True if there are unclosed brackets inside the string, false otherwise.
+     */
+    function _hasUnclosedBrackets($string, $chars)
+    {
+        $num_angle_start = substr_count($string, $chars[0]);
+        $num_angle_end   = substr_count($string, $chars[1]);
+
+        $this->_hasUnclosedBracketsSub($string, $num_angle_start, $chars[0]);
+        $this->_hasUnclosedBracketsSub($string, $num_angle_end, $chars[1]);
+
+        if ($num_angle_start < $num_angle_end) {
+            $this->error = 'Invalid address spec. Unmatched quote or bracket (' . $chars . ')';
+            return false;
+        } else {
+            return ($num_angle_start > $num_angle_end);
+        }
+    }
+
+    /**
+     * Sub function that is used only by hasUnclosedBrackets().
+     *
+     * @access private
+     * @param string $string The string to check.
+     * @param integer &$num    The number of occurences.
+     * @param string $char   The character to count.
+     * @return integer The number of occurences of $char in $string, adjusted for backslashes.
+     */
+    function _hasUnclosedBracketsSub($string, &$num, $char)
+    {
+        $parts = explode($char, $string);
+        for ($i = 0; $i < count($parts); $i++){
+            if (substr($parts[$i], -1) == '\\' || $this->_hasUnclosedQuotes($parts[$i]))
+                $num--;
+            if (isset($parts[$i + 1]))
+                $parts[$i + 1] = $parts[$i] . $char . $parts[$i + 1];
+        }
+
+        return $num;
+    }
+
+    /**
+     * Function to begin checking the address.
+     *
+     * @access private
+     * @param string $address The address to validate.
+     * @return mixed False on failure, or a structured array of address information on success.
+     */
+    function _validateAddress($address)
+    {
+        $is_group = false;
+        $addresses = array();
+
+        if ($address['group']) {
+            $is_group = true;
+
+            // Get the group part of the name
+            $parts     = explode(':', $address['address']);
+            $groupname = $this->_splitCheck($parts, ':');
+            $structure = array();
+
+            // And validate the group part of the name.
+            if (!$this->_validatePhrase($groupname)){
+                $this->error = 'Group name did not validate.';
+                return false;
+            } else {
+                // Don't include groups if we are not nesting
+                // them. This avoids returning invalid addresses.
+                if ($this->nestGroups) {
+                    $structure = new stdClass;
+                    $structure->groupname = $groupname;
+                }
+            }
+
+            $address['address'] = ltrim(substr($address['address'], strlen($groupname . ':')));
+        }
+
+        // If a group then split on comma and put into an array.
+        // Otherwise, Just put the whole address in an array.
+        if ($is_group) {
+            while (strlen($address['address']) > 0) {
+                $parts       = explode(',', $address['address']);
+                $addresses[] = $this->_splitCheck($parts, ',');
+                $address['address'] = trim(substr($address['address'], strlen(end($addresses) . ',')));
+            }
+        } else {
+            $addresses[] = $address['address'];
+        }
+
+        // Check that $addresses is set, if address like this:
+        // Groupname:;
+        // Then errors were appearing.
+        if (!count($addresses)){
+            $this->error = 'Empty group.';
+            return false;
+        }
+
+        // Trim the whitespace from all of the address strings.
+        array_map('trim', $addresses);
+
+        // Validate each mailbox.
+        // Format could be one of: name <geezer at domain.com>
+        //                         geezer at domain.com
+        //                         geezer
+        // ... or any other format valid by RFC 822.
+        for ($i = 0; $i < count($addresses); $i++) {
+            if (!$this->validateMailbox($addresses[$i])) {
+                if (empty($this->error)) {
+                    $this->error = 'Validation failed for: ' . $addresses[$i];
+                }
+                return false;
+            }
+        }
+
+        // Nested format
+        if ($this->nestGroups) {
+            if ($is_group) {
+                $structure->addresses = $addresses;
+            } else {
+                $structure = $addresses[0];
+            }
+
+        // Flat format
+        } else {
+            if ($is_group) {
+                $structure = array_merge($structure, $addresses);
+            } else {
+                $structure = $addresses;
+            }
+        }
+
+        return $structure;
+    }
+
+    /**
+     * Function to validate a phrase.
+     *
+     * @access private
+     * @param string $phrase The phrase to check.
+     * @return boolean Success or failure.
+     */
+    function _validatePhrase($phrase)
+    {
+        // Splits on one or more Tab or space.
+        $parts = preg_split('/[ \\x09]+/', $phrase, -1, PREG_SPLIT_NO_EMPTY);
+
+        $phrase_parts = array();
+        while (count($parts) > 0){
+            $phrase_parts[] = $this->_splitCheck($parts, ' ');
+            for ($i = 0; $i < $this->index + 1; $i++)
+                array_shift($parts);
+        }
+
+        foreach ($phrase_parts as $part) {
+            // If quoted string:
+            if (substr($part, 0, 1) == '"') {
+                if (!$this->_validateQuotedString($part)) {
+                    return false;
+                }
+                continue;
+            }
+
+            // Otherwise it's an atom:
+            if (!$this->_validateAtom($part)) return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Function to validate an atom which from rfc822 is:
+     * atom = 1*<any CHAR except specials, SPACE and CTLs>
+     *
+     * If validation ($this->validate) has been turned off, then
+     * validateAtom() doesn't actually check anything. This is so that you
+     * can split a list of addresses up before encoding personal names
+     * (umlauts, etc.), for example.
+     *
+     * @access private
+     * @param string $atom The string to check.
+     * @return boolean Success or failure.
+     */
+    function _validateAtom($atom)
+    {
+        if (!$this->validate) {
+            // Validation has been turned off; assume the atom is okay.
+            return true;
+        }
+
+        // Check for any char from ASCII 0 - ASCII 127
+        if (!preg_match('/^[\\x00-\\x7E]+$/i', $atom, $matches)) {
+            return false;
+        }
+
+        // Check for specials:
+        if (preg_match('/[][()<>@,;\\:". ]/', $atom)) {
+            return false;
+        }
+
+        // Check for control characters (ASCII 0-31):
+        if (preg_match('/[\\x00-\\x1F]+/', $atom)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Function to validate quoted string, which is:
+     * quoted-string = <"> *(qtext/quoted-pair) <">
+     *
+     * @access private
+     * @param string $qstring The string to check
+     * @return boolean Success or failure.
+     */
+    function _validateQuotedString($qstring)
+    {
+        // Leading and trailing "
+        $qstring = substr($qstring, 1, -1);
+
+        // Perform check, removing quoted characters first.
+        return !preg_match('/[\x0D\\\\"]/', preg_replace('/\\\\./', '', $qstring));
+    }
+
+    /**
+     * Function to validate a mailbox, which is:
+     * mailbox =   addr-spec         ; simple address
+     *           / phrase route-addr ; name and route-addr
+     *
+     * @access public
+     * @param string &$mailbox The string to check.
+     * @return boolean Success or failure.
+     */
+    function validateMailbox(&$mailbox)
+    {
+        // A couple of defaults.
+        $phrase  = '';
+        $comment = '';
+        $comments = array();
+
+        // Catch any RFC822 comments and store them separately.
+        $_mailbox = $mailbox;
+        while (strlen(trim($_mailbox)) > 0) {
+            $parts = explode('(', $_mailbox);
+            $before_comment = $this->_splitCheck($parts, '(');
+            if ($before_comment != $_mailbox) {
+                // First char should be a (.
+                $comment    = substr(str_replace($before_comment, '', $_mailbox), 1);
+                $parts      = explode(')', $comment);
+                $comment    = $this->_splitCheck($parts, ')');
+                $comments[] = $comment;
+
+                // +2 is for the brackets
+                $_mailbox = substr($_mailbox, strpos($_mailbox, '('.$comment)+strlen($comment)+2);
+            } else {
+                break;
+            }
+        }
+
+        foreach ($comments as $comment) {
+            $mailbox = str_replace("($comment)", '', $mailbox);
+        }
+
+        $mailbox = trim($mailbox);
+
+        // Check for name + route-addr
+        if (substr($mailbox, -1) == '>' && substr($mailbox, 0, 1) != '<') {
+            $parts  = explode('<', $mailbox);
+            $name   = $this->_splitCheck($parts, '<');
+
+            $phrase     = trim($name);
+            $route_addr = trim(substr($mailbox, strlen($name.'<'), -1));
+
+            if ($this->_validatePhrase($phrase) === false || ($route_addr = $this->_validateRouteAddr($route_addr)) === false) {
+                return false;
+            }
+
+        // Only got addr-spec
+        } else {
+            // First snip angle brackets if present.
+            if (substr($mailbox, 0, 1) == '<' && substr($mailbox, -1) == '>') {
+                $addr_spec = substr($mailbox, 1, -1);
+            } else {
+                $addr_spec = $mailbox;
+            }
+
+            if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
+                return false;
+            }
+        }
+
+        // Construct the object that will be returned.
+        $mbox = new stdClass();
+
+        // Add the phrase (even if empty) and comments
+        $mbox->personal = $phrase;
+        $mbox->comment  = isset($comments) ? $comments : array();
+
+        if (isset($route_addr)) {
+            $mbox->mailbox = $route_addr['local_part'];
+            $mbox->host    = $route_addr['domain'];
+            $route_addr['adl'] !== '' ? $mbox->adl = $route_addr['adl'] : '';
+        } else {
+            $mbox->mailbox = $addr_spec['local_part'];
+            $mbox->host    = $addr_spec['domain'];
+        }
+
+        $mailbox = $mbox;
+        return true;
+    }
+
+    /**
+     * This function validates a route-addr which is:
+     * route-addr = "<" [route] addr-spec ">"
+     *
+     * Angle brackets have already been removed at the point of
+     * getting to this function.
+     *
+     * @access private
+     * @param string $route_addr The string to check.
+     * @return mixed False on failure, or an array containing validated address/route information on success.
+     */
+    function _validateRouteAddr($route_addr)
+    {
+        // Check for colon.
+        if (strpos($route_addr, ':') !== false) {
+            $parts = explode(':', $route_addr);
+            $route = $this->_splitCheck($parts, ':');
+        } else {
+            $route = $route_addr;
+        }
+
+        // If $route is same as $route_addr then the colon was in
+        // quotes or brackets or, of course, non existent.
+        if ($route === $route_addr){
+            unset($route);
+            $addr_spec = $route_addr;
+            if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
+                return false;
+            }
+        } else {
+            // Validate route part.
+            if (($route = $this->_validateRoute($route)) === false) {
+                return false;
+            }
+
+            $addr_spec = substr($route_addr, strlen($route . ':'));
+
+            // Validate addr-spec part.
+            if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
+                return false;
+            }
+        }
+
+        if (isset($route)) {
+            $return['adl'] = $route;
+        } else {
+            $return['adl'] = '';
+        }
+
+        $return = array_merge($return, $addr_spec);
+        return $return;
+    }
+
+    /**
+     * Function to validate a route, which is:
+     * route = 1#("@" domain) ":"
+     *
+     * @access private
+     * @param string $route The string to check.
+     * @return mixed False on failure, or the validated $route on success.
+     */
+    function _validateRoute($route)
+    {
+        // Split on comma.
+        $domains = explode(',', trim($route));
+
+        foreach ($domains as $domain) {
+            $domain = str_replace('@', '', trim($domain));
+            if (!$this->_validateDomain($domain)) return false;
+        }
+
+        return $route;
+    }
+
+    /**
+     * Function to validate a domain, though this is not quite what
+     * you expect of a strict internet domain.
+     *
+     * domain = sub-domain *("." sub-domain)
+     *
+     * @access private
+     * @param string $domain The string to check.
+     * @return mixed False on failure, or the validated domain on success.
+     */
+    function _validateDomain($domain)
+    {
+        // Note the different use of $subdomains and $sub_domains
+        $subdomains = explode('.', $domain);
+
+        while (count($subdomains) > 0) {
+            $sub_domains[] = $this->_splitCheck($subdomains, '.');
+            for ($i = 0; $i < $this->index + 1; $i++)
+                array_shift($subdomains);
+        }
+
+        foreach ($sub_domains as $sub_domain) {
+            if (!$this->_validateSubdomain(trim($sub_domain)))
+                return false;
+        }
+
+        // Managed to get here, so return input.
+        return $domain;
+    }
+
+    /**
+     * Function to validate a subdomain:
+     *   subdomain = domain-ref / domain-literal
+     *
+     * @access private
+     * @param string $subdomain The string to check.
+     * @return boolean Success or failure.
+     */
+    function _validateSubdomain($subdomain)
+    {
+        if (preg_match('|^\[(.*)]$|', $subdomain, $arr)){
+            if (!$this->_validateDliteral($arr[1])) return false;
+        } else {
+            if (!$this->_validateAtom($subdomain)) return false;
+        }
+
+        // Got here, so return successful.
+        return true;
+    }
+
+    /**
+     * Function to validate a domain literal:
+     *   domain-literal =  "[" *(dtext / quoted-pair) "]"
+     *
+     * @access private
+     * @param string $dliteral The string to check.
+     * @return boolean Success or failure.
+     */
+    function _validateDliteral($dliteral)
+    {
+        return !preg_match('/(.)[][\x0D\\\\]/', $dliteral, $matches) && $matches[1] != '\\';
+    }
+
+    /**
+     * Function to validate an addr-spec.
+     *
+     * addr-spec = local-part "@" domain
+     *
+     * @access private
+     * @param string $addr_spec The string to check.
+     * @return mixed False on failure, or the validated addr-spec on success.
+     */
+    function _validateAddrSpec($addr_spec)
+    {
+        $addr_spec = trim($addr_spec);
+
+        // Split on @ sign if there is one.
+        if (strpos($addr_spec, '@') !== false) {
+            $parts      = explode('@', $addr_spec);
+            $local_part = $this->_splitCheck($parts, '@');
+            $domain     = substr($addr_spec, strlen($local_part . '@'));
+
+        // No @ sign so assume the default domain.
+        } else {
+            $local_part = $addr_spec;
+            $domain     = $this->default_domain;
+        }
+
+        if (($local_part = $this->_validateLocalPart($local_part)) === false) return false;
+        if (($domain     = $this->_validateDomain($domain)) === false) return false;
+
+        // Got here so return successful.
+        return array('local_part' => $local_part, 'domain' => $domain);
+    }
+
+    /**
+     * Function to validate the local part of an address:
+     *   local-part = word *("." word)
+     *
+     * @access private
+     * @param string $local_part
+     * @return mixed False on failure, or the validated local part on success.
+     */
+    function _validateLocalPart($local_part)
+    {
+        $parts = explode('.', $local_part);
+        $words = array();
+
+        // Split the local_part into words.
+        while (count($parts) > 0){
+            $words[] = $this->_splitCheck($parts, '.');
+            for ($i = 0; $i < $this->index + 1; $i++) {
+                array_shift($parts);
+            }
+        }
+
+        // Validate each word.
+        foreach ($words as $word) {
+            // If this word contains an unquoted space, it is invalid. (6.2.4)
+            if (strpos($word, ' ') && $word[0] !== '"')
+            {
+                return false;
+            }
+
+            if ($this->_validatePhrase(trim($word)) === false) return false;
+        }
+
+        // Managed to get here, so return the input.
+        return $local_part;
+    }
+
+    /**
+     * Returns an approximate count of how many addresses are in the
+     * given string. This is APPROXIMATE as it only splits based on a
+     * comma which has no preceding backslash. Could be useful as
+     * large amounts of addresses will end up producing *large*
+     * structures when used with parseAddressList().
+     *
+     * @param  string $data Addresses to count
+     * @return int          Approximate count
+     */
+    function approximateCount($data)
+    {
+        return count(preg_split('/(?<!\\\\),/', $data));
+    }
+
+    /**
+     * This is a email validating function separate to the rest of the
+     * class. It simply validates whether an email is of the common
+     * internet form: <user>@<domain>. This can be sufficient for most
+     * people. Optional stricter mode can be utilised which restricts
+     * mailbox characters allowed to alphanumeric, full stop, hyphen
+     * and underscore.
+     *
+     * @param  string  $data   Address to check
+     * @param  boolean $strict Optional stricter mode
+     * @return mixed           False if it fails, an indexed array
+     *                         username/domain if it matches
+     */
+    function isValidInetAddress($data, $strict = false)
+    {
+        $regex = $strict ? '/^([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i' : '/^([*+!.&#$|\'\\%\/0-9a-z^_`{}=?~:-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i';
+        if (preg_match($regex, trim($data), $matches)) {
+            return array($matches[1], $matches[2]);
+        } else {
+            return false;
+        }
+    }
+
+}
diff --git a/lib/ext/Mail/mail.php b/lib/ext/Mail/mail.php
new file mode 100755
index 0000000..a8b4b5d
--- /dev/null
+++ b/lib/ext/Mail/mail.php
@@ -0,0 +1,168 @@
+<?php
+/**
+ * internal PHP-mail() implementation of the PEAR Mail:: interface.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2010 Chuck Hagenbuch
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * o Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * o The names of the authors may not be used to endorse or promote
+ *   products derived from this software without specific prior written
+ *   permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category    Mail
+ * @package     Mail
+ * @author      Chuck Hagenbuch <chuck at horde.org> 
+ * @copyright   2010 Chuck Hagenbuch
+ * @license     http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version     CVS: $Id: mail.php 294747 2010-02-08 08:18:33Z clockwerx $
+ * @link        http://pear.php.net/package/Mail/
+ */
+
+/**
+ * internal PHP-mail() implementation of the PEAR Mail:: interface.
+ * @package Mail
+ * @version $Revision: 294747 $
+ */
+class Mail_mail extends Mail {
+
+    /**
+     * Any arguments to pass to the mail() function.
+     * @var string
+     */
+    var $_params = '';
+
+    /**
+     * Constructor.
+     *
+     * Instantiates a new Mail_mail:: object based on the parameters
+     * passed in.
+     *
+     * @param array $params Extra arguments for the mail() function.
+     */
+    function Mail_mail($params = null)
+    {
+        // The other mail implementations accept parameters as arrays.
+        // In the interest of being consistent, explode an array into
+        // a string of parameter arguments.
+        if (is_array($params)) {
+            $this->_params = join(' ', $params);
+        } else {
+            $this->_params = $params;
+        }
+
+        /* Because the mail() function may pass headers as command
+         * line arguments, we can't guarantee the use of the standard
+         * "\r\n" separator.  Instead, we use the system's native line
+         * separator. */
+        if (defined('PHP_EOL')) {
+            $this->sep = PHP_EOL;
+        } else {
+            $this->sep = (strpos(PHP_OS, 'WIN') === false) ? "\n" : "\r\n";
+        }
+    }
+
+    /**
+     * Implements Mail_mail::send() function using php's built-in mail()
+     * command.
+     *
+     * @param mixed $recipients Either a comma-seperated list of recipients
+     *              (RFC822 compliant), or an array of recipients,
+     *              each RFC822 valid. This may contain recipients not
+     *              specified in the headers, for Bcc:, resending
+     *              messages, etc.
+     *
+     * @param array $headers The array of headers to send with the mail, in an
+     *              associative array, where the array key is the
+     *              header name (ie, 'Subject'), and the array value
+     *              is the header value (ie, 'test'). The header
+     *              produced from those values would be 'Subject:
+     *              test'.
+     *
+     * @param string $body The full text of the message body, including any
+     *               Mime parts, etc.
+     *
+     * @return mixed Returns true on success, or a PEAR_Error
+     *               containing a descriptive error message on
+     *               failure.
+     *
+     * @access public
+     */
+    function send($recipients, $headers, $body)
+    {
+        if (!is_array($headers)) {
+            return PEAR::raiseError('$headers must be an array');
+        }
+
+        $result = $this->_sanitizeHeaders($headers);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        // If we're passed an array of recipients, implode it.
+        if (is_array($recipients)) {
+            $recipients = implode(', ', $recipients);
+        }
+
+        // Get the Subject out of the headers array so that we can
+        // pass it as a seperate argument to mail().
+        $subject = '';
+        if (isset($headers['Subject'])) {
+            $subject = $headers['Subject'];
+            unset($headers['Subject']);
+        }
+
+        // Also remove the To: header.  The mail() function will add its own
+        // To: header based on the contents of $recipients.
+        unset($headers['To']);
+
+        // Flatten the headers out.
+        $headerElements = $this->prepareHeaders($headers);
+        if (is_a($headerElements, 'PEAR_Error')) {
+            return $headerElements;
+        }
+        list(, $text_headers) = $headerElements;
+
+        // We only use mail()'s optional fifth parameter if the additional
+        // parameters have been provided and we're not running in safe mode.
+        if (empty($this->_params) || ini_get('safe_mode')) {
+            $result = mail($recipients, $subject, $body, $text_headers);
+        } else {
+            $result = mail($recipients, $subject, $body, $text_headers,
+                           $this->_params);
+        }
+
+        // If the mail() function returned failure, we need to create a
+        // PEAR_Error object and return it instead of the boolean result.
+        if ($result === false) {
+            $result = PEAR::raiseError('mail() returned failure');
+        }
+
+        return $result;
+    }
+
+}
diff --git a/lib/ext/Mail/mock.php b/lib/ext/Mail/mock.php
new file mode 100755
index 0000000..61570ba
--- /dev/null
+++ b/lib/ext/Mail/mock.php
@@ -0,0 +1,143 @@
+<?php
+/**
+ * Mock implementation
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2010 Chuck Hagenbuch
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * o Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * o The names of the authors may not be used to endorse or promote
+ *   products derived from this software without specific prior written
+ *   permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category    Mail
+ * @package     Mail
+ * @author      Chuck Hagenbuch <chuck at horde.org> 
+ * @copyright   2010 Chuck Hagenbuch
+ * @license     http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version     CVS: $Id: mock.php 294747 2010-02-08 08:18:33Z clockwerx $
+ * @link        http://pear.php.net/package/Mail/
+ */
+
+/**
+ * Mock implementation of the PEAR Mail:: interface for testing.
+ * @access public
+ * @package Mail
+ * @version $Revision: 294747 $
+ */
+class Mail_mock extends Mail {
+
+    /**
+     * Array of messages that have been sent with the mock.
+     *
+     * @var array
+     * @access public
+     */
+    var $sentMessages = array();
+
+    /**
+     * Callback before sending mail.
+     *
+     * @var callback
+     */
+    var $_preSendCallback;
+
+    /**
+     * Callback after sending mai.
+     *
+     * @var callback
+     */
+    var $_postSendCallback;
+
+    /**
+     * Constructor.
+     *
+     * Instantiates a new Mail_mock:: object based on the parameters
+     * passed in. It looks for the following parameters, both optional:
+     *     preSendCallback   Called before an email would be sent.
+     *     postSendCallback  Called after an email would have been sent.
+     *
+     * @param array Hash containing any parameters.
+     * @access public
+     */
+    function Mail_mock($params)
+    {
+        if (isset($params['preSendCallback']) &&
+            is_callable($params['preSendCallback'])) {
+            $this->_preSendCallback = $params['preSendCallback'];
+        }
+
+        if (isset($params['postSendCallback']) &&
+            is_callable($params['postSendCallback'])) {
+            $this->_postSendCallback = $params['postSendCallback'];
+        }
+    }
+
+    /**
+     * Implements Mail_mock::send() function. Silently discards all
+     * mail.
+     *
+     * @param mixed $recipients Either a comma-seperated list of recipients
+     *              (RFC822 compliant), or an array of recipients,
+     *              each RFC822 valid. This may contain recipients not
+     *              specified in the headers, for Bcc:, resending
+     *              messages, etc.
+     *
+     * @param array $headers The array of headers to send with the mail, in an
+     *              associative array, where the array key is the
+     *              header name (ie, 'Subject'), and the array value
+     *              is the header value (ie, 'test'). The header
+     *              produced from those values would be 'Subject:
+     *              test'.
+     *
+     * @param string $body The full text of the message body, including any
+     *               Mime parts, etc.
+     *
+     * @return mixed Returns true on success, or a PEAR_Error
+     *               containing a descriptive error message on
+     *               failure.
+     * @access public
+     */
+    function send($recipients, $headers, $body)
+    {
+        if ($this->_preSendCallback) {
+            call_user_func_array($this->_preSendCallback,
+                                 array(&$this, $recipients, $headers, $body));
+        }
+
+        $entry = array('recipients' => $recipients, 'headers' => $headers, 'body' => $body);
+        $this->sentMessages[] = $entry;
+
+        if ($this->_postSendCallback) {
+            call_user_func_array($this->_postSendCallback,
+                                 array(&$this, $recipients, $headers, $body));
+        }
+
+        return true;
+    }
+
+}
diff --git a/lib/ext/Mail/null.php b/lib/ext/Mail/null.php
new file mode 100755
index 0000000..f8d5827
--- /dev/null
+++ b/lib/ext/Mail/null.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * Null implementation of the PEAR Mail interface
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2010 Phil Kernick
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * o Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * o The names of the authors may not be used to endorse or promote
+ *   products derived from this software without specific prior written
+ *   permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category    Mail
+ * @package     Mail
+ * @author      Phil Kernick <philk at rotfl.com.au>
+ * @copyright   2010 Phil Kernick
+ * @license     http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version     CVS: $Id: null.php 294747 2010-02-08 08:18:33Z clockwerx $
+ * @link        http://pear.php.net/package/Mail/
+ */
+
+/**
+ * Null implementation of the PEAR Mail:: interface.
+ * @access public
+ * @package Mail
+ * @version $Revision: 294747 $
+ */
+class Mail_null extends Mail {
+
+    /**
+     * Implements Mail_null::send() function. Silently discards all
+     * mail.
+     *
+     * @param mixed $recipients Either a comma-seperated list of recipients
+     *              (RFC822 compliant), or an array of recipients,
+     *              each RFC822 valid. This may contain recipients not
+     *              specified in the headers, for Bcc:, resending
+     *              messages, etc.
+     *
+     * @param array $headers The array of headers to send with the mail, in an
+     *              associative array, where the array key is the
+     *              header name (ie, 'Subject'), and the array value
+     *              is the header value (ie, 'test'). The header
+     *              produced from those values would be 'Subject:
+     *              test'.
+     *
+     * @param string $body The full text of the message body, including any
+     *               Mime parts, etc.
+     *
+     * @return mixed Returns true on success, or a PEAR_Error
+     *               containing a descriptive error message on
+     *               failure.
+     * @access public
+     */
+    function send($recipients, $headers, $body)
+    {
+        return true;
+    }
+
+}
diff --git a/lib/ext/Mail/sendmail.php b/lib/ext/Mail/sendmail.php
new file mode 100755
index 0000000..b056575
--- /dev/null
+++ b/lib/ext/Mail/sendmail.php
@@ -0,0 +1,171 @@
+<?php
+//
+// +----------------------------------------------------------------------+
+// | PHP Version 4                                                        |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2003 The PHP Group                                |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.02 of the PHP license,      |
+// | that is bundled with this package in the file LICENSE, and is        |
+// | available at through the world-wide-web at                           |
+// | http://www.php.net/license/2_02.txt.                                 |
+// | If you did not receive a copy of the PHP license and are unable to   |
+// | obtain it through the world-wide-web, please send a note to          |
+// | license at php.net so we can mail you a copy immediately.               |
+// +----------------------------------------------------------------------+
+// | Author: Chuck Hagenbuch <chuck at horde.org>                            |
+// +----------------------------------------------------------------------+
+
+/**
+ * Sendmail implementation of the PEAR Mail:: interface.
+ * @access public
+ * @package Mail
+ * @version $Revision: 294744 $
+ */
+class Mail_sendmail extends Mail {
+
+    /**
+     * The location of the sendmail or sendmail wrapper binary on the
+     * filesystem.
+     * @var string
+     */
+    var $sendmail_path = '/usr/sbin/sendmail';
+
+    /**
+     * Any extra command-line parameters to pass to the sendmail or
+     * sendmail wrapper binary.
+     * @var string
+     */
+    var $sendmail_args = '-i';
+
+    /**
+     * Constructor.
+     *
+     * Instantiates a new Mail_sendmail:: object based on the parameters
+     * passed in. It looks for the following parameters:
+     *     sendmail_path    The location of the sendmail binary on the
+     *                      filesystem. Defaults to '/usr/sbin/sendmail'.
+     *
+     *     sendmail_args    Any extra parameters to pass to the sendmail
+     *                      or sendmail wrapper binary.
+     *
+     * If a parameter is present in the $params array, it replaces the
+     * default.
+     *
+     * @param array $params Hash containing any parameters different from the
+     *              defaults.
+     * @access public
+     */
+    function Mail_sendmail($params)
+    {
+        if (isset($params['sendmail_path'])) {
+            $this->sendmail_path = $params['sendmail_path'];
+        }
+        if (isset($params['sendmail_args'])) {
+            $this->sendmail_args = $params['sendmail_args'];
+        }
+
+        /*
+         * Because we need to pass message headers to the sendmail program on
+         * the commandline, we can't guarantee the use of the standard "\r\n"
+         * separator.  Instead, we use the system's native line separator.
+         */
+        if (defined('PHP_EOL')) {
+            $this->sep = PHP_EOL;
+        } else {
+            $this->sep = (strpos(PHP_OS, 'WIN') === false) ? "\n" : "\r\n";
+        }
+    }
+
+    /**
+     * Implements Mail::send() function using the sendmail
+     * command-line binary.
+     *
+     * @param mixed $recipients Either a comma-seperated list of recipients
+     *              (RFC822 compliant), or an array of recipients,
+     *              each RFC822 valid. This may contain recipients not
+     *              specified in the headers, for Bcc:, resending
+     *              messages, etc.
+     *
+     * @param array $headers The array of headers to send with the mail, in an
+     *              associative array, where the array key is the
+     *              header name (ie, 'Subject'), and the array value
+     *              is the header value (ie, 'test'). The header
+     *              produced from those values would be 'Subject:
+     *              test'.
+     *
+     * @param string $body The full text of the message body, including any
+     *               Mime parts, etc.
+     *
+     * @return mixed Returns true on success, or a PEAR_Error
+     *               containing a descriptive error message on
+     *               failure.
+     * @access public
+     */
+    function send($recipients, $headers, $body)
+    {
+        if (!is_array($headers)) {
+            return PEAR::raiseError('$headers must be an array');
+        }
+
+        $result = $this->_sanitizeHeaders($headers);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        $recipients = $this->parseRecipients($recipients);
+        if (is_a($recipients, 'PEAR_Error')) {
+            return $recipients;
+        }
+        $recipients = implode(' ', array_map('escapeshellarg', $recipients));
+
+        $headerElements = $this->prepareHeaders($headers);
+        if (is_a($headerElements, 'PEAR_Error')) {
+            return $headerElements;
+        }
+        list($from, $text_headers) = $headerElements;
+
+        /* Since few MTAs are going to allow this header to be forged
+         * unless it's in the MAIL FROM: exchange, we'll use
+         * Return-Path instead of From: if it's set. */
+        if (!empty($headers['Return-Path'])) {
+            $from = $headers['Return-Path'];
+        }
+
+        if (!isset($from)) {
+            return PEAR::raiseError('No from address given.');
+        } elseif (strpos($from, ' ') !== false ||
+                  strpos($from, ';') !== false ||
+                  strpos($from, '&') !== false ||
+                  strpos($from, '`') !== false) {
+            return PEAR::raiseError('From address specified with dangerous characters.');
+        }
+
+        $from = escapeshellarg($from); // Security bug #16200
+
+        $mail = @popen($this->sendmail_path . (!empty($this->sendmail_args) ? ' ' . $this->sendmail_args : '') . " -f$from -- $recipients", 'w');
+        if (!$mail) {
+            return PEAR::raiseError('Failed to open sendmail [' . $this->sendmail_path . '] for execution.');
+        }
+
+        // Write the headers following by two newlines: one to end the headers
+        // section and a second to separate the headers block from the body.
+        fputs($mail, $text_headers . $this->sep . $this->sep);
+
+        fputs($mail, $body);
+        $result = pclose($mail);
+        if (version_compare(phpversion(), '4.2.3') == -1) {
+            // With older php versions, we need to shift the pclose
+            // result to get the exit code.
+            $result = $result >> 8 & 0xFF;
+        }
+
+        if ($result != 0) {
+            return PEAR::raiseError('sendmail returned error code ' . $result,
+                                    $result);
+        }
+
+        return true;
+    }
+
+}
diff --git a/lib/ext/Mail/smtp.php b/lib/ext/Mail/smtp.php
new file mode 100755
index 0000000..52ea602
--- /dev/null
+++ b/lib/ext/Mail/smtp.php
@@ -0,0 +1,444 @@
+<?php
+/**
+ * SMTP implementation of the PEAR Mail interface. Requires the Net_SMTP class.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2010, Chuck Hagenbuch
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * o Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * o The names of the authors may not be used to endorse or promote
+ *   products derived from this software without specific prior written
+ *   permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category    HTTP
+ * @package     HTTP_Request
+ * @author      Jon Parise <jon at php.net> 
+ * @author      Chuck Hagenbuch <chuck at horde.org>
+ * @copyright   2010 Chuck Hagenbuch
+ * @license     http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version     CVS: $Id: smtp.php 294747 2010-02-08 08:18:33Z clockwerx $
+ * @link        http://pear.php.net/package/Mail/
+ */
+
+/** Error: Failed to create a Net_SMTP object */
+define('PEAR_MAIL_SMTP_ERROR_CREATE', 10000);
+
+/** Error: Failed to connect to SMTP server */
+define('PEAR_MAIL_SMTP_ERROR_CONNECT', 10001);
+
+/** Error: SMTP authentication failure */
+define('PEAR_MAIL_SMTP_ERROR_AUTH', 10002);
+
+/** Error: No From: address has been provided */
+define('PEAR_MAIL_SMTP_ERROR_FROM', 10003);
+
+/** Error: Failed to set sender */
+define('PEAR_MAIL_SMTP_ERROR_SENDER', 10004);
+
+/** Error: Failed to add recipient */
+define('PEAR_MAIL_SMTP_ERROR_RECIPIENT', 10005);
+
+/** Error: Failed to send data */
+define('PEAR_MAIL_SMTP_ERROR_DATA', 10006);
+
+/**
+ * SMTP implementation of the PEAR Mail interface. Requires the Net_SMTP class.
+ * @access public
+ * @package Mail
+ * @version $Revision: 294747 $
+ */
+class Mail_smtp extends Mail {
+
+    /**
+     * SMTP connection object.
+     *
+     * @var object
+     * @access private
+     */
+    var $_smtp = null;
+
+    /**
+     * The list of service extension parameters to pass to the Net_SMTP
+     * mailFrom() command.
+     * @var array
+     */
+    var $_extparams = array();
+
+    /**
+     * The SMTP host to connect to.
+     * @var string
+     */
+    var $host = 'localhost';
+
+    /**
+     * The port the SMTP server is on.
+     * @var integer
+     */
+    var $port = 25;
+
+    /**
+     * Should SMTP authentication be used?
+     *
+     * This value may be set to true, false or the name of a specific
+     * authentication method.
+     *
+     * If the value is set to true, the Net_SMTP package will attempt to use
+     * the best authentication method advertised by the remote SMTP server.
+     *
+     * @var mixed
+     */
+    var $auth = false;
+
+    /**
+     * The username to use if the SMTP server requires authentication.
+     * @var string
+     */
+    var $username = '';
+
+    /**
+     * The password to use if the SMTP server requires authentication.
+     * @var string
+     */
+    var $password = '';
+
+    /**
+     * Hostname or domain that will be sent to the remote SMTP server in the
+     * HELO / EHLO message.
+     *
+     * @var string
+     */
+    var $localhost = 'localhost';
+
+    /**
+     * SMTP connection timeout value.  NULL indicates no timeout.
+     *
+     * @var integer
+     */
+    var $timeout = null;
+
+    /**
+     * Turn on Net_SMTP debugging?
+     *
+     * @var boolean $debug
+     */
+    var $debug = false;
+
+    /**
+     * Indicates whether or not the SMTP connection should persist over
+     * multiple calls to the send() method.
+     *
+     * @var boolean
+     */
+    var $persist = false;
+
+    /**
+     * Use SMTP command pipelining (specified in RFC 2920) if the SMTP server
+     * supports it. This speeds up delivery over high-latency connections. By
+     * default, use the default value supplied by Net_SMTP.
+     * @var bool
+     */
+    var $pipelining;
+
+    /**
+     * Constructor.
+     *
+     * Instantiates a new Mail_smtp:: object based on the parameters
+     * passed in. It looks for the following parameters:
+     *     host        The server to connect to. Defaults to localhost.
+     *     port        The port to connect to. Defaults to 25.
+     *     auth        SMTP authentication.  Defaults to none.
+     *     username    The username to use for SMTP auth. No default.
+     *     password    The password to use for SMTP auth. No default.
+     *     localhost   The local hostname / domain. Defaults to localhost.
+     *     timeout     The SMTP connection timeout. Defaults to none.
+     *     verp        Whether to use VERP or not. Defaults to false.
+     *                 DEPRECATED as of 1.2.0 (use setMailParams()).
+     *     debug       Activate SMTP debug mode? Defaults to false.
+     *     persist     Should the SMTP connection persist?
+     *     pipelining  Use SMTP command pipelining
+     *
+     * If a parameter is present in the $params array, it replaces the
+     * default.
+     *
+     * @param array Hash containing any parameters different from the
+     *              defaults.
+     * @access public
+     */
+    function Mail_smtp($params)
+    {
+        if (isset($params['host'])) $this->host = $params['host'];
+        if (isset($params['port'])) $this->port = $params['port'];
+        if (isset($params['auth'])) $this->auth = $params['auth'];
+        if (isset($params['username'])) $this->username = $params['username'];
+        if (isset($params['password'])) $this->password = $params['password'];
+        if (isset($params['localhost'])) $this->localhost = $params['localhost'];
+        if (isset($params['timeout'])) $this->timeout = $params['timeout'];
+        if (isset($params['debug'])) $this->debug = (bool)$params['debug'];
+        if (isset($params['persist'])) $this->persist = (bool)$params['persist'];
+        if (isset($params['pipelining'])) $this->pipelining = (bool)$params['pipelining'];
+
+        // Deprecated options
+        if (isset($params['verp'])) {
+            $this->addServiceExtensionParameter('XVERP', is_bool($params['verp']) ? null : $params['verp']);
+        }
+
+        register_shutdown_function(array(&$this, '_Mail_smtp'));
+    }
+
+    /**
+     * Destructor implementation to ensure that we disconnect from any
+     * potentially-alive persistent SMTP connections.
+     */
+    function _Mail_smtp()
+    {
+        $this->disconnect();
+    }
+
+    /**
+     * Implements Mail::send() function using SMTP.
+     *
+     * @param mixed $recipients Either a comma-seperated list of recipients
+     *              (RFC822 compliant), or an array of recipients,
+     *              each RFC822 valid. This may contain recipients not
+     *              specified in the headers, for Bcc:, resending
+     *              messages, etc.
+     *
+     * @param array $headers The array of headers to send with the mail, in an
+     *              associative array, where the array key is the
+     *              header name (e.g., 'Subject'), and the array value
+     *              is the header value (e.g., 'test'). The header
+     *              produced from those values would be 'Subject:
+     *              test'.
+     *
+     * @param string $body The full text of the message body, including any
+     *               MIME parts, etc.
+     *
+     * @return mixed Returns true on success, or a PEAR_Error
+     *               containing a descriptive error message on
+     *               failure.
+     * @access public
+     */
+    function send($recipients, $headers, $body)
+    {
+        /* If we don't already have an SMTP object, create one. */
+        $result = &$this->getSMTPObject();
+        if (PEAR::isError($result)) {
+            return $result;
+        }
+
+        if (!is_array($headers)) {
+            return PEAR::raiseError('$headers must be an array');
+        }
+
+        $this->_sanitizeHeaders($headers);
+
+        $headerElements = $this->prepareHeaders($headers);
+        if (is_a($headerElements, 'PEAR_Error')) {
+            $this->_smtp->rset();
+            return $headerElements;
+        }
+        list($from, $textHeaders) = $headerElements;
+
+        /* Since few MTAs are going to allow this header to be forged
+         * unless it's in the MAIL FROM: exchange, we'll use
+         * Return-Path instead of From: if it's set. */
+        if (!empty($headers['Return-Path'])) {
+            $from = $headers['Return-Path'];
+        }
+
+        if (!isset($from)) {
+            $this->_smtp->rset();
+            return PEAR::raiseError('No From: address has been provided',
+                                    PEAR_MAIL_SMTP_ERROR_FROM);
+        }
+
+        $params = null;
+        if (!empty($this->_extparams)) {
+            foreach ($this->_extparams as $key => $val) {
+                $params .= ' ' . $key . (is_null($val) ? '' : '=' . $val);
+            }
+        }
+        if (PEAR::isError($res = $this->_smtp->mailFrom($from, ltrim($params)))) {
+            $error = $this->_error("Failed to set sender: $from", $res);
+            $this->_smtp->rset();
+            return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_SENDER);
+        }
+
+        $recipients = $this->parseRecipients($recipients);
+        if (is_a($recipients, 'PEAR_Error')) {
+            $this->_smtp->rset();
+            return $recipients;
+        }
+
+        foreach ($recipients as $recipient) {
+            $res = $this->_smtp->rcptTo($recipient);
+            if (is_a($res, 'PEAR_Error')) {
+                $error = $this->_error("Failed to add recipient: $recipient", $res);
+                $this->_smtp->rset();
+                return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_RECIPIENT);
+            }
+        }
+
+        /* Send the message's headers and the body as SMTP data. */
+        $res = $this->_smtp->data($textHeaders . "\r\n\r\n" . $body);
+		list(,$args) = $this->_smtp->getResponse();
+
+		if (preg_match("/Ok: queued as (.*)/", $args, $queued)) {
+			$this->queued_as = $queued[1];
+		}
+
+		/* we need the greeting; from it we can extract the authorative name of the mail server we've really connected to.
+		 * ideal if we're connecting to a round-robin of relay servers and need to track which exact one took the email */
+		$this->greeting = $this->_smtp->getGreeting();
+
+        if (is_a($res, 'PEAR_Error')) {
+            $error = $this->_error('Failed to send data', $res);
+            $this->_smtp->rset();
+            return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_DATA);
+        }
+
+        /* If persistent connections are disabled, destroy our SMTP object. */
+        if ($this->persist === false) {
+            $this->disconnect();
+        }
+
+        return true;
+    }
+
+    /**
+     * Connect to the SMTP server by instantiating a Net_SMTP object.
+     *
+     * @return mixed Returns a reference to the Net_SMTP object on success, or
+     *               a PEAR_Error containing a descriptive error message on
+     *               failure.
+     *
+     * @since  1.2.0
+     * @access public
+     */
+    function &getSMTPObject()
+    {
+        if (is_object($this->_smtp) !== false) {
+            return $this->_smtp;
+        }
+
+        include_once 'Net/SMTP.php';
+        $this->_smtp = &new Net_SMTP($this->host,
+                                     $this->port,
+                                     $this->localhost);
+
+        /* If we still don't have an SMTP object at this point, fail. */
+        if (is_object($this->_smtp) === false) {
+            return PEAR::raiseError('Failed to create a Net_SMTP object',
+                                    PEAR_MAIL_SMTP_ERROR_CREATE);
+        }
+
+        /* Configure the SMTP connection. */
+        if ($this->debug) {
+            $this->_smtp->setDebug(true);
+        }
+
+        /* Attempt to connect to the configured SMTP server. */
+        if (PEAR::isError($res = $this->_smtp->connect($this->timeout))) {
+            $error = $this->_error('Failed to connect to ' .
+                                   $this->host . ':' . $this->port,
+                                   $res);
+            return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_CONNECT);
+        }
+
+        /* Attempt to authenticate if authentication has been enabled. */
+        if ($this->auth) {
+            $method = is_string($this->auth) ? $this->auth : '';
+
+            if (PEAR::isError($res = $this->_smtp->auth($this->username,
+                                                        $this->password,
+                                                        $method))) {
+                $error = $this->_error("$method authentication failure",
+                                       $res);
+                $this->_smtp->rset();
+                return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_AUTH);
+            }
+        }
+
+        return $this->_smtp;
+    }
+
+    /**
+     * Add parameter associated with a SMTP service extension.
+     *
+     * @param string Extension keyword.
+     * @param string Any value the keyword needs.
+     *
+     * @since 1.2.0
+     * @access public
+     */
+    function addServiceExtensionParameter($keyword, $value = null)
+    {
+        $this->_extparams[$keyword] = $value;
+    }
+
+    /**
+     * Disconnect and destroy the current SMTP connection.
+     *
+     * @return boolean True if the SMTP connection no longer exists.
+     *
+     * @since  1.1.9
+     * @access public
+     */
+    function disconnect()
+    {
+        /* If we have an SMTP object, disconnect and destroy it. */
+        if (is_object($this->_smtp) && $this->_smtp->disconnect()) {
+            $this->_smtp = null;
+        }
+
+        /* We are disconnected if we no longer have an SMTP object. */
+        return ($this->_smtp === null);
+    }
+
+    /**
+     * Build a standardized string describing the current SMTP error.
+     *
+     * @param string $text  Custom string describing the error context.
+     * @param object $error Reference to the current PEAR_Error object.
+     *
+     * @return string       A string describing the current SMTP error.
+     *
+     * @since  1.1.7
+     * @access private
+     */
+    function _error($text, &$error)
+    {
+        /* Split the SMTP response into a code and a response string. */
+        list($code, $response) = $this->_smtp->getResponse();
+
+        /* Build our standardized error string. */
+        return $text
+            . ' [SMTP: ' . $error->getMessage()
+            . " (code: $code, response: $response)]";
+    }
+
+}
diff --git a/lib/ext/Mail/smtpmx.php b/lib/ext/Mail/smtpmx.php
new file mode 100755
index 0000000..f0b6940
--- /dev/null
+++ b/lib/ext/Mail/smtpmx.php
@@ -0,0 +1,502 @@
+<?PHP
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * SMTP MX
+ *
+ * SMTP MX implementation of the PEAR Mail interface. Requires the Net_SMTP class.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2010, gERD Schaufelberger
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * o Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * o The names of the authors may not be used to endorse or promote
+ *   products derived from this software without specific prior written
+ *   permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category   Mail
+ * @package    Mail_smtpmx
+ * @author     gERD Schaufelberger <gerd at php-tools.net>
+ * @copyright  2010 gERD Schaufelberger
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: smtpmx.php 294747 2010-02-08 08:18:33Z clockwerx $
+ * @link       http://pear.php.net/package/Mail/
+ */
+
+require_once 'Net/SMTP.php';
+
+/**
+ * SMTP MX implementation of the PEAR Mail interface. Requires the Net_SMTP class.
+ *
+ *
+ * @access public
+ * @author  gERD Schaufelberger <gerd at php-tools.net>
+ * @package Mail
+ * @version $Revision: 294747 $
+ */
+class Mail_smtpmx extends Mail {
+
+    /**
+     * SMTP connection object.
+     *
+     * @var object
+     * @access private
+     */
+    var $_smtp = null;
+
+    /**
+     * The port the SMTP server is on.
+     * @var integer
+     * @see getservicebyname()
+     */
+    var $port = 25;
+
+    /**
+     * Hostname or domain that will be sent to the remote SMTP server in the
+     * HELO / EHLO message.
+     *
+     * @var string
+     * @see posix_uname()
+     */
+    var $mailname = 'localhost';
+
+    /**
+     * SMTP connection timeout value.  NULL indicates no timeout.
+     *
+     * @var integer
+     */
+    var $timeout = 10;
+
+    /**
+     * use either PEAR:Net_DNS or getmxrr
+     *
+     * @var boolean
+     */
+    var $withNetDns = true;
+
+    /**
+     * PEAR:Net_DNS_Resolver
+     *
+     * @var object
+     */
+    var $resolver;
+
+    /**
+     * Whether to use VERP or not. If not a boolean, the string value
+     * will be used as the VERP separators.
+     *
+     * @var mixed boolean or string
+     */
+    var $verp = false;
+
+    /**
+     * Whether to use VRFY or not.
+     *
+     * @var boolean $vrfy
+     */
+    var $vrfy = false;
+
+    /**
+     * Switch to test mode - don't send emails for real
+     *
+     * @var boolean $debug
+     */
+    var $test = false;
+
+    /**
+     * Turn on Net_SMTP debugging?
+     *
+     * @var boolean $peardebug
+     */
+    var $debug = false;
+
+    /**
+     * internal error codes
+     *
+     * translate internal error identifier to PEAR-Error codes and human
+     * readable messages.
+     *
+     * @var boolean $debug
+     * @todo as I need unique error-codes to identify what exactly went wrond
+     *       I did not use intergers as it should be. Instead I added a "namespace"
+     *       for each code. This avoids conflicts with error codes from different
+     *       classes. How can I use unique error codes and stay conform with PEAR?
+     */
+    var $errorCode = array(
+        'not_connected' => array(
+            'code'  => 1,
+            'msg'   => 'Could not connect to any mail server ({HOST}) at port {PORT} to send mail to {RCPT}.'
+        ),
+        'failed_vrfy_rcpt' => array(
+            'code'  => 2,
+            'msg'   => 'Recipient "{RCPT}" could not be veryfied.'
+        ),
+        'failed_set_from' => array(
+            'code'  => 3,
+            'msg'   => 'Failed to set sender: {FROM}.'
+        ),
+        'failed_set_rcpt' => array(
+            'code'  => 4,
+            'msg'   => 'Failed to set recipient: {RCPT}.'
+        ),
+        'failed_send_data' => array(
+            'code'  => 5,
+            'msg'   => 'Failed to send mail to: {RCPT}.'
+        ),
+        'no_from' => array(
+            'code'  => 5,
+            'msg'   => 'No from address has be provided.'
+        ),
+        'send_data' => array(
+            'code'  => 7,
+            'msg'   => 'Failed to create Net_SMTP object.'
+        ),
+        'no_mx' => array(
+            'code'  => 8,
+            'msg'   => 'No MX-record for {RCPT} found.'
+        ),
+        'no_resolver' => array(
+            'code'  => 9,
+            'msg'   => 'Could not start resolver! Install PEAR:Net_DNS or switch off "netdns"'
+        ),
+        'failed_rset' => array(
+            'code'  => 10,
+            'msg'   => 'RSET command failed, SMTP-connection corrupt.'
+        ),
+    );
+
+    /**
+     * Constructor.
+     *
+     * Instantiates a new Mail_smtp:: object based on the parameters
+     * passed in. It looks for the following parameters:
+     *     mailname    The name of the local mail system (a valid hostname which matches the reverse lookup)
+     *     port        smtp-port - the default comes from getservicebyname() and should work fine
+     *     timeout     The SMTP connection timeout. Defaults to 30 seconds.
+     *     vrfy        Whether to use VRFY or not. Defaults to false.
+     *     verp        Whether to use VERP or not. Defaults to false.
+     *     test        Activate test mode? Defaults to false.
+     *     debug       Activate SMTP and Net_DNS debug mode? Defaults to false.
+     *     netdns      whether to use PEAR:Net_DNS or the PHP build in function getmxrr, default is true
+     *
+     * If a parameter is present in the $params array, it replaces the
+     * default.
+     *
+     * @access public
+     * @param array Hash containing any parameters different from the
+     *              defaults.
+     * @see _Mail_smtpmx()
+     */
+    function __construct($params)
+    {
+        if (isset($params['mailname'])) {
+            $this->mailname = $params['mailname'];
+        } else {
+            // try to find a valid mailname
+            if (function_exists('posix_uname')) {
+                $uname = posix_uname();
+                $this->mailname = $uname['nodename'];
+            }
+        }
+
+        // port number
+        if (isset($params['port'])) {
+            $this->_port = $params['port'];
+        } else {
+            $this->_port = getservbyname('smtp', 'tcp');
+        }
+
+        if (isset($params['timeout'])) $this->timeout = $params['timeout'];
+        if (isset($params['verp'])) $this->verp = $params['verp'];
+        if (isset($params['test'])) $this->test = $params['test'];
+        if (isset($params['peardebug'])) $this->test = $params['peardebug'];
+        if (isset($params['netdns'])) $this->withNetDns = $params['netdns'];
+    }
+
+    /**
+     * Constructor wrapper for PHP4
+     *
+     * @access public
+     * @param array Hash containing any parameters different from the defaults
+     * @see __construct()
+     */
+    function Mail_smtpmx($params)
+    {
+        $this->__construct($params);
+        register_shutdown_function(array(&$this, '__destruct'));
+    }
+
+    /**
+     * Destructor implementation to ensure that we disconnect from any
+     * potentially-alive persistent SMTP connections.
+     */
+    function __destruct()
+    {
+        if (is_object($this->_smtp)) {
+            $this->_smtp->disconnect();
+            $this->_smtp = null;
+        }
+    }
+
+    /**
+     * Implements Mail::send() function using SMTP direct delivery
+     *
+     * @access public
+     * @param mixed $recipients in RFC822 style or array
+     * @param array $headers The array of headers to send with the mail.
+     * @param string $body The full text of the message body,
+     * @return mixed Returns true on success, or a PEAR_Error
+     */
+    function send($recipients, $headers, $body)
+    {
+        if (!is_array($headers)) {
+            return PEAR::raiseError('$headers must be an array');
+        }
+
+        $result = $this->_sanitizeHeaders($headers);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        // Prepare headers
+        $headerElements = $this->prepareHeaders($headers);
+        if (is_a($headerElements, 'PEAR_Error')) {
+            return $headerElements;
+        }
+        list($from, $textHeaders) = $headerElements;
+
+        // use 'Return-Path' if possible
+        if (!empty($headers['Return-Path'])) {
+            $from = $headers['Return-Path'];
+        }
+        if (!isset($from)) {
+            return $this->_raiseError('no_from');
+        }
+
+        // Prepare recipients
+        $recipients = $this->parseRecipients($recipients);
+        if (is_a($recipients, 'PEAR_Error')) {
+            return $recipients;
+        }
+
+        foreach ($recipients as $rcpt) {
+            list($user, $host) = explode('@', $rcpt);
+
+            $mx = $this->_getMx($host);
+            if (is_a($mx, 'PEAR_Error')) {
+                return $mx;
+            }
+
+            if (empty($mx)) {
+                $info = array('rcpt' => $rcpt);
+                return $this->_raiseError('no_mx', $info);
+            }
+
+            $connected = false;
+            foreach ($mx as $mserver => $mpriority) {
+                $this->_smtp = new Net_SMTP($mserver, $this->port, $this->mailname);
+
+                // configure the SMTP connection.
+                if ($this->debug) {
+                    $this->_smtp->setDebug(true);
+                }
+
+                // attempt to connect to the configured SMTP server.
+                $res = $this->_smtp->connect($this->timeout);
+                if (is_a($res, 'PEAR_Error')) {
+                    $this->_smtp = null;
+                    continue;
+                }
+
+                // connection established
+                if ($res) {
+                    $connected = true;
+                    break;
+                }
+            }
+
+            if (!$connected) {
+                $info = array(
+                    'host' => implode(', ', array_keys($mx)),
+                    'port' => $this->port,
+                    'rcpt' => $rcpt,
+                );
+                return $this->_raiseError('not_connected', $info);
+            }
+
+            // Verify recipient
+            if ($this->vrfy) {
+                $res = $this->_smtp->vrfy($rcpt);
+                if (is_a($res, 'PEAR_Error')) {
+                    $info = array('rcpt' => $rcpt);
+                    return $this->_raiseError('failed_vrfy_rcpt', $info);
+                }
+            }
+
+            // mail from:
+            $args['verp'] = $this->verp;
+            $res = $this->_smtp->mailFrom($from, $args);
+            if (is_a($res, 'PEAR_Error')) {
+                $info = array('from' => $from);
+                return $this->_raiseError('failed_set_from', $info);
+            }
+
+            // rcpt to:
+            $res = $this->_smtp->rcptTo($rcpt);
+            if (is_a($res, 'PEAR_Error')) {
+                $info = array('rcpt' => $rcpt);
+                return $this->_raiseError('failed_set_rcpt', $info);
+            }
+
+            // Don't send anything in test mode
+            if ($this->test) {
+                $result = $this->_smtp->rset();
+                $res = $this->_smtp->rset();
+                if (is_a($res, 'PEAR_Error')) {
+                    return $this->_raiseError('failed_rset');
+                }
+
+                $this->_smtp->disconnect();
+                $this->_smtp = null;
+                return true;
+            }
+
+            // Send data
+            $res = $this->_smtp->data("$textHeaders\r\n$body");
+            if (is_a($res, 'PEAR_Error')) {
+                $info = array('rcpt' => $rcpt);
+                return $this->_raiseError('failed_send_data', $info);
+            }
+
+            $this->_smtp->disconnect();
+            $this->_smtp = null;
+        }
+
+        return true;
+    }
+
+    /**
+     * Recieve mx rexords for a spciefied host
+     *
+     * The MX records
+     *
+     * @access private
+     * @param string $host mail host
+     * @return mixed sorted
+     */
+    function _getMx($host)
+    {
+        $mx = array();
+
+        if ($this->withNetDns) {
+            $res = $this->_loadNetDns();
+            if (is_a($res, 'PEAR_Error')) {
+                return $res;
+            }
+
+            $response = $this->resolver->query($host, 'MX');
+            if (!$response) {
+                return false;
+            }
+
+            foreach ($response->answer as $rr) {
+                if ($rr->type == 'MX') {
+                    $mx[$rr->exchange] = $rr->preference;
+                }
+            }
+        } else {
+            $mxHost = array();
+            $mxWeight = array();
+
+            if (!getmxrr($host, $mxHost, $mxWeight)) {
+                return false;
+            }
+            for ($i = 0; $i < count($mxHost); ++$i) {
+                $mx[$mxHost[$i]] = $mxWeight[$i];
+            }
+        }
+
+        asort($mx);
+        return $mx;
+    }
+
+    /**
+     * initialize PEAR:Net_DNS_Resolver
+     *
+     * @access private
+     * @return boolean true on success
+     */
+    function _loadNetDns()
+    {
+        if (is_object($this->resolver)) {
+            return true;
+        }
+
+        if (!include_once 'Net/DNS.php') {
+            return $this->_raiseError('no_resolver');
+        }
+
+        $this->resolver = new Net_DNS_Resolver();
+        if ($this->debug) {
+            $this->resolver->test = 1;
+        }
+
+        return true;
+    }
+
+    /**
+     * raise standardized error
+     *
+     * include additional information in error message
+     *
+     * @access private
+     * @param string $id maps error ids to codes and message
+     * @param array $info optional information in associative array
+     * @see _errorCode
+     */
+    function _raiseError($id, $info = array())
+    {
+        $code = $this->errorCode[$id]['code'];
+        $msg = $this->errorCode[$id]['msg'];
+
+        // include info to messages
+        if (!empty($info)) {
+            $search = array();
+            $replace = array();
+
+            foreach ($info as $key => $value) {
+                array_push($search, '{' . strtoupper($key) . '}');
+                array_push($replace, $value);
+            }
+
+            $msg = str_replace($search, $replace, $msg);
+        }
+
+        return PEAR::raiseError($msg, $code);
+    }
+
+}
diff --git a/lib/ext/Net/SMTP.php b/lib/ext/Net/SMTP.php
new file mode 100644
index 0000000..8d0682f
--- /dev/null
+++ b/lib/ext/Net/SMTP.php
@@ -0,0 +1,1342 @@
+<?php
+/* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */
+// +----------------------------------------------------------------------+
+// | PHP Version 4                                                        |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2003 The PHP Group                                |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.02 of the PHP license,      |
+// | that is bundled with this package in the file LICENSE, and is        |
+// | available at through the world-wide-web at                           |
+// | http://www.php.net/license/2_02.txt.                                 |
+// | If you did not receive a copy of the PHP license and are unable to   |
+// | obtain it through the world-wide-web, please send a note to          |
+// | license at php.net so we can mail you a copy immediately.               |
+// +----------------------------------------------------------------------+
+// | Authors: Chuck Hagenbuch <chuck at horde.org>                           |
+// |          Jon Parise <jon at php.net>                                    |
+// |          Damian Alejandro Fernandez Sosa <damlists at cnba.uba.ar>      |
+// +----------------------------------------------------------------------+
+//
+// $Id: SMTP.php 314875 2011-08-13 17:03:30Z jon $
+
+require_once 'PEAR.php';
+require_once 'Net/Socket.php';
+
+/**
+ * Provides an implementation of the SMTP protocol using PEAR's
+ * Net_Socket:: class.
+ *
+ * @package Net_SMTP
+ * @author  Chuck Hagenbuch <chuck at horde.org>
+ * @author  Jon Parise <jon at php.net>
+ * @author  Damian Alejandro Fernandez Sosa <damlists at cnba.uba.ar>
+ *
+ * @example basic.php   A basic implementation of the Net_SMTP package.
+ */
+class Net_SMTP
+{
+    /**
+     * The server to connect to.
+     * @var string
+     * @access public
+     */
+    var $host = 'localhost';
+
+    /**
+     * The port to connect to.
+     * @var int
+     * @access public
+     */
+    var $port = 25;
+
+    /**
+     * The value to give when sending EHLO or HELO.
+     * @var string
+     * @access public
+     */
+    var $localhost = 'localhost';
+
+    /**
+     * List of supported authentication methods, in preferential order.
+     * @var array
+     * @access public
+     */
+    var $auth_methods = array();
+
+    /**
+     * Use SMTP command pipelining (specified in RFC 2920) if the SMTP
+     * server supports it.
+     *
+     * When pipeling is enabled, rcptTo(), mailFrom(), sendFrom(),
+     * somlFrom() and samlFrom() do not wait for a response from the
+     * SMTP server but return immediately.
+     *
+     * @var bool
+     * @access public
+     */
+    var $pipelining = false;
+
+    /**
+     * Number of pipelined commands.
+     * @var int
+     * @access private
+     */
+    var $_pipelined_commands = 0;
+
+    /**
+     * Should debugging output be enabled?
+     * @var boolean
+     * @access private
+     */
+    var $_debug = false;
+
+    /**
+     * Debug output handler.
+     * @var callback
+     * @access private
+     */
+    var $_debug_handler = null;
+
+    /**
+     * The socket resource being used to connect to the SMTP server.
+     * @var resource
+     * @access private
+     */
+    var $_socket = null;
+
+    /**
+     * Array of socket options that will be passed to Net_Socket::connect().
+     * @see stream_context_create()
+     * @var array
+     * @access private
+     */
+    var $_socket_options = null;
+
+    /**
+     * The socket I/O timeout value in seconds.
+     * @var int
+     * @access private
+     */
+    var $_timeout = 0;
+
+    /**
+     * The most recent server response code.
+     * @var int
+     * @access private
+     */
+    var $_code = -1;
+
+    /**
+     * The most recent server response arguments.
+     * @var array
+     * @access private
+     */
+    var $_arguments = array();
+
+    /**
+     * Stores the SMTP server's greeting string.
+     * @var string
+     * @access private
+     */
+    var $_greeting = null;
+
+    /**
+     * Stores detected features of the SMTP server.
+     * @var array
+     * @access private
+     */
+    var $_esmtp = array();
+
+    /**
+     * Instantiates a new Net_SMTP object, overriding any defaults
+     * with parameters that are passed in.
+     *
+     * If you have SSL support in PHP, you can connect to a server
+     * over SSL using an 'ssl://' prefix:
+     *
+     *   // 465 is a common smtps port.
+     *   $smtp = new Net_SMTP('ssl://mail.host.com', 465);
+     *   $smtp->connect();
+     *
+     * @param string  $host       The server to connect to.
+     * @param integer $port       The port to connect to.
+     * @param string  $localhost  The value to give when sending EHLO or HELO.
+     * @param boolean $pipeling   Use SMTP command pipelining
+     * @param integer $timeout    Socket I/O timeout in seconds.
+     * @param array   $socket_options Socket stream_context_create() options.
+     *
+     * @access  public
+     * @since   1.0
+     */
+    function Net_SMTP($host = null, $port = null, $localhost = null,
+        $pipelining = false, $timeout = 0, $socket_options = null)
+    {
+        if (isset($host)) {
+            $this->host = $host;
+        }
+        if (isset($port)) {
+            $this->port = $port;
+        }
+        if (isset($localhost)) {
+            $this->localhost = $localhost;
+        }
+        $this->pipelining = $pipelining;
+
+        $this->_socket = new Net_Socket();
+        $this->_socket_options = $socket_options;
+        $this->_timeout = $timeout;
+
+        /* Include the Auth_SASL package.  If the package is available, we 
+         * enable the authentication methods that depend upon it. */
+        if (@include_once 'Auth/SASL.php') {
+            $this->setAuthMethod('CRAM-MD5', array($this, '_authCram_MD5'));
+            $this->setAuthMethod('DIGEST-MD5', array($this, '_authDigest_MD5'));
+        }
+
+        /* These standard authentication methods are always available. */
+        $this->setAuthMethod('LOGIN', array($this, '_authLogin'), false);
+        $this->setAuthMethod('PLAIN', array($this, '_authPlain'), false);
+    }
+
+    /**
+     * Set the socket I/O timeout value in seconds plus microseconds.
+     *
+     * @param   integer $seconds        Timeout value in seconds.
+     * @param   integer $microseconds   Additional value in microseconds.
+     *
+     * @access  public
+     * @since   1.5.0
+     */
+    function setTimeout($seconds, $microseconds = 0) {
+        return $this->_socket->setTimeout($seconds, $microseconds);
+    }
+
+    /**
+     * Set the value of the debugging flag.
+     *
+     * @param   boolean $debug      New value for the debugging flag.
+     *
+     * @access  public
+     * @since   1.1.0
+     */
+    function setDebug($debug, $handler = null)
+    {
+        $this->_debug = $debug;
+        $this->_debug_handler = $handler;
+    }
+
+    /**
+     * Write the given debug text to the current debug output handler.
+     *
+     * @param   string  $message    Debug mesage text.
+     *
+     * @access  private
+     * @since   1.3.3
+     */
+    function _debug($message)
+    {
+        if ($this->_debug) {
+            if ($this->_debug_handler) {
+                call_user_func_array($this->_debug_handler,
+                                     array(&$this, $message));
+            } else {
+                echo "DEBUG: $message\n";
+            }
+        }
+    }
+
+    /**
+     * Send the given string of data to the server.
+     *
+     * @param   string  $data       The string of data to send.
+     *
+     * @return  mixed   The number of bytes that were actually written,
+     *                  or a PEAR_Error object on failure.
+     *
+     * @access  private
+     * @since   1.1.0
+     */
+    function _send($data)
+    {
+        $this->_debug("Send: $data");
+
+        $result = $this->_socket->write($data);
+        if (!$result || PEAR::isError($result)) {
+            $msg = ($result) ? $result->getMessage() : "unknown error";
+            return PEAR::raiseError("Failed to write to socket: $msg",
+                                    null, PEAR_ERROR_RETURN);
+        }
+
+        return $result;
+    }
+
+    /**
+     * Send a command to the server with an optional string of
+     * arguments.  A carriage return / linefeed (CRLF) sequence will
+     * be appended to each command string before it is sent to the
+     * SMTP server - an error will be thrown if the command string
+     * already contains any newline characters. Use _send() for
+     * commands that must contain newlines.
+     *
+     * @param   string  $command    The SMTP command to send to the server.
+     * @param   string  $args       A string of optional arguments to append
+     *                              to the command.
+     *
+     * @return  mixed   The result of the _send() call.
+     *
+     * @access  private
+     * @since   1.1.0
+     */
+    function _put($command, $args = '')
+    {
+        if (!empty($args)) {
+            $command .= ' ' . $args;
+        }
+
+        if (strcspn($command, "\r\n") !== strlen($command)) {
+            return PEAR::raiseError('Commands cannot contain newlines',
+                                    null, PEAR_ERROR_RETURN);
+        }
+
+        return $this->_send($command . "\r\n");
+    }
+
+    /**
+     * Read a reply from the SMTP server.  The reply consists of a response
+     * code and a response message.
+     *
+     * @param   mixed   $valid      The set of valid response codes.  These
+     *                              may be specified as an array of integer
+     *                              values or as a single integer value.
+     * @param   bool    $later      Do not parse the response now, but wait
+     *                              until the last command in the pipelined
+     *                              command group
+     *
+     * @return  mixed   True if the server returned a valid response code or
+     *                  a PEAR_Error object is an error condition is reached.
+     *
+     * @access  private
+     * @since   1.1.0
+     *
+     * @see     getResponse
+     */
+    function _parseResponse($valid, $later = false)
+    {
+        $this->_code = -1;
+        $this->_arguments = array();
+
+        if ($later) {
+            $this->_pipelined_commands++;
+            return true;
+        }
+
+        for ($i = 0; $i <= $this->_pipelined_commands; $i++) {
+            while ($line = $this->_socket->readLine()) {
+                $this->_debug("Recv: $line");
+
+                /* If we receive an empty line, the connection was closed. */
+                if (empty($line)) {
+                    $this->disconnect();
+                    return PEAR::raiseError('Connection was closed',
+                                            null, PEAR_ERROR_RETURN);
+                }
+
+                /* Read the code and store the rest in the arguments array. */
+                $code = substr($line, 0, 3);
+                $this->_arguments[] = trim(substr($line, 4));
+
+                /* Check the syntax of the response code. */
+                if (is_numeric($code)) {
+                    $this->_code = (int)$code;
+                } else {
+                    $this->_code = -1;
+                    break;
+                }
+
+                /* If this is not a multiline response, we're done. */
+                if (substr($line, 3, 1) != '-') {
+                    break;
+                }
+            }
+        }
+
+        $this->_pipelined_commands = 0;
+
+        /* Compare the server's response code with the valid code/codes. */
+        if (is_int($valid) && ($this->_code === $valid)) {
+            return true;
+        } elseif (is_array($valid) && in_array($this->_code, $valid, true)) {
+            return true;
+        }
+
+        return PEAR::raiseError('Invalid response code received from server',
+                                $this->_code, PEAR_ERROR_RETURN);
+    }
+
+    /**
+     * Issue an SMTP command and verify its response.
+     *
+     * @param   string  $command    The SMTP command string or data.
+     * @param   mixed   $valid      The set of valid response codes.  These
+     *                              may be specified as an array of integer
+     *                              values or as a single integer value.
+     *
+     * @return  mixed   True on success or a PEAR_Error object on failure.
+     *
+     * @access  public
+     * @since   1.6.0
+     */
+    function command($command, $valid)
+    {
+        if (PEAR::isError($error = $this->_put($command))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse($valid))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Return a 2-tuple containing the last response from the SMTP server.
+     *
+     * @return  array   A two-element array: the first element contains the
+     *                  response code as an integer and the second element
+     *                  contains the response's arguments as a string.
+     *
+     * @access  public
+     * @since   1.1.0
+     */
+    function getResponse()
+    {
+        return array($this->_code, join("\n", $this->_arguments));
+    }
+
+    /**
+     * Return the SMTP server's greeting string.
+     *
+     * @return  string  A string containing the greeting string, or null if a 
+     *                  greeting has not been received.
+     *
+     * @access  public
+     * @since   1.3.3
+     */
+    function getGreeting()
+    {
+        return $this->_greeting;
+    }
+
+    /**
+     * Attempt to connect to the SMTP server.
+     *
+     * @param   int     $timeout    The timeout value (in seconds) for the
+     *                              socket connection attempt.
+     * @param   bool    $persistent Should a persistent socket connection
+     *                              be used?
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function connect($timeout = null, $persistent = false)
+    {
+        $this->_greeting = null;
+        $result = $this->_socket->connect($this->host, $this->port,
+                                          $persistent, $timeout,
+                                          $this->_socket_options);
+        if (PEAR::isError($result)) {
+            return PEAR::raiseError('Failed to connect socket: ' .
+                                    $result->getMessage());
+        }
+
+        /*
+         * Now that we're connected, reset the socket's timeout value for 
+         * future I/O operations.  This allows us to have different socket 
+         * timeout values for the initial connection (our $timeout parameter) 
+         * and all other socket operations.
+         */
+        if ($this->_timeout > 0) {
+            if (PEAR::isError($error = $this->setTimeout($this->_timeout))) {
+                return $error;
+            }
+        }
+
+        if (PEAR::isError($error = $this->_parseResponse(220))) {
+            return $error;
+        }
+
+        /* Extract and store a copy of the server's greeting string. */
+        list(, $this->_greeting) = $this->getResponse();
+
+        if (PEAR::isError($error = $this->_negotiate())) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Attempt to disconnect from the SMTP server.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function disconnect()
+    {
+        if (PEAR::isError($error = $this->_put('QUIT'))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(221))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_socket->disconnect())) {
+            return PEAR::raiseError('Failed to disconnect socket: ' .
+                                    $error->getMessage());
+        }
+
+        return true;
+    }
+
+    /**
+     * Attempt to send the EHLO command and obtain a list of ESMTP
+     * extensions available, and failing that just send HELO.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     *
+     * @access private
+     * @since  1.1.0
+     */
+    function _negotiate()
+    {
+        if (PEAR::isError($error = $this->_put('EHLO', $this->localhost))) {
+            return $error;
+        }
+
+        if (PEAR::isError($this->_parseResponse(250))) {
+            /* If we receive a 503 response, we're already authenticated. */
+            if ($this->_code === 503) {
+                return true;
+            }
+
+            /* If the EHLO failed, try the simpler HELO command. */
+            if (PEAR::isError($error = $this->_put('HELO', $this->localhost))) {
+                return $error;
+            }
+            if (PEAR::isError($this->_parseResponse(250))) {
+                return PEAR::raiseError('HELO was not accepted: ', $this->_code,
+                                        PEAR_ERROR_RETURN);
+            }
+
+            return true;
+        }
+
+        foreach ($this->_arguments as $argument) {
+            $verb = strtok($argument, ' ');
+            $arguments = substr($argument, strlen($verb) + 1,
+                                strlen($argument) - strlen($verb) - 1);
+            $this->_esmtp[$verb] = $arguments;
+        }
+
+        if (!isset($this->_esmtp['PIPELINING'])) {
+            $this->pipelining = false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns the name of the best authentication method that the server
+     * has advertised.
+     *
+     * @return mixed    Returns a string containing the name of the best
+     *                  supported authentication method or a PEAR_Error object
+     *                  if a failure condition is encountered.
+     * @access private
+     * @since  1.1.0
+     */
+    function _getBestAuthMethod()
+    {
+        $available_methods = explode(' ', $this->_esmtp['AUTH']);
+
+        foreach ($this->auth_methods as $method => $callback) {
+            if (in_array($method, $available_methods)) {
+                return $method;
+            }
+        }
+
+        return PEAR::raiseError('No supported authentication methods',
+                                null, PEAR_ERROR_RETURN);
+    }
+
+    /**
+     * Attempt to do SMTP authentication.
+     *
+     * @param string The userid to authenticate as.
+     * @param string The password to authenticate with.
+     * @param string The requested authentication method.  If none is
+     *               specified, the best supported method will be used.
+     * @param bool   Flag indicating whether or not TLS should be attempted.
+     * @param string An optional authorization identifier.  If specified, this
+     *               identifier will be used as the authorization proxy.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function auth($uid, $pwd , $method = '', $tls = true, $authz = '')
+    {
+        /* We can only attempt a TLS connection if one has been requested,
+         * we're running PHP 5.1.0 or later, have access to the OpenSSL 
+         * extension, are connected to an SMTP server which supports the 
+         * STARTTLS extension, and aren't already connected over a secure 
+         * (SSL) socket connection. */
+        if ($tls && version_compare(PHP_VERSION, '5.1.0', '>=') &&
+            extension_loaded('openssl') && isset($this->_esmtp['STARTTLS']) &&
+            strncasecmp($this->host, 'ssl://', 6) !== 0) {
+            /* Start the TLS connection attempt. */
+            if (PEAR::isError($result = $this->_put('STARTTLS'))) {
+                return $result;
+            }
+            if (PEAR::isError($result = $this->_parseResponse(220))) {
+                return $result;
+            }
+            if (PEAR::isError($result = $this->_socket->enableCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT))) {
+                return $result;
+            } elseif ($result !== true) {
+                return PEAR::raiseError('STARTTLS failed');
+            }
+
+            /* Send EHLO again to recieve the AUTH string from the
+             * SMTP server. */
+            $this->_negotiate();
+        }
+
+        if (empty($this->_esmtp['AUTH'])) {
+            return PEAR::raiseError('SMTP server does not support authentication');
+        }
+
+        /* If no method has been specified, get the name of the best
+         * supported method advertised by the SMTP server. */
+        if (empty($method)) {
+            if (PEAR::isError($method = $this->_getBestAuthMethod())) {
+                /* Return the PEAR_Error object from _getBestAuthMethod(). */
+                return $method;
+            }
+        } else {
+            $method = strtoupper($method);
+            if (!array_key_exists($method, $this->auth_methods)) {
+                return PEAR::raiseError("$method is not a supported authentication method");
+            }
+        }
+
+        if (!isset($this->auth_methods[$method])) {
+            return PEAR::raiseError("$method is not a supported authentication method");
+        }
+
+        if (!is_callable($this->auth_methods[$method], false)) {
+            return PEAR::raiseError("$method authentication method cannot be called");
+        }
+
+        if (is_array($this->auth_methods[$method])) {
+            list($object, $method) = $this->auth_methods[$method];
+            $result = $object->{$method}($uid, $pwd, $authz, $this);
+        } else {
+            $func =  $this->auth_methods[$method];
+            $result = $func($uid, $pwd, $authz, $this);
+         }
+
+        /* If an error was encountered, return the PEAR_Error object. */
+        if (PEAR::isError($result)) {
+            return $result;
+        }
+
+        return true;
+    }
+
+    /**
+     * Add a new authentication method.
+     *
+     * @param string    The authentication method name (e.g. 'PLAIN')
+     * @param mixed     The authentication callback (given as the name of a 
+     *                  function or as an (object, method name) array).
+     * @param bool      Should the new method be prepended to the list of 
+     *                  available methods?  This is the default behavior, 
+     *                  giving the new method the highest priority.
+     *
+     * @return  mixed   True on success or a PEAR_Error object on failure.
+     *
+     * @access public
+     * @since  1.6.0
+     */
+    function setAuthMethod($name, $callback, $prepend = true)
+    {
+        if (!is_string($name)) {
+            return PEAR::raiseError('Method name is not a string');
+        }
+
+        if (!is_string($callback) && !is_array($callback)) {
+            return PEAR::raiseError('Method callback must be string or array');
+        }
+
+        if (is_array($callback)) {
+            if (!is_object($callback[0]) || !is_string($callback[1]))
+                return PEAR::raiseError('Bad mMethod callback array');
+        }
+
+        if ($prepend) {
+            $this->auth_methods = array_merge(array($name => $callback),
+                                              $this->auth_methods);
+        } else {
+            $this->auth_methods[$name] = $callback;
+        }
+
+        return true;
+    }
+
+    /**
+     * Authenticates the user using the DIGEST-MD5 method.
+     *
+     * @param string The userid to authenticate as.
+     * @param string The password to authenticate with.
+     * @param string The optional authorization proxy identifier.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access private
+     * @since  1.1.0
+     */
+    function _authDigest_MD5($uid, $pwd, $authz = '')
+    {
+        if (PEAR::isError($error = $this->_put('AUTH', 'DIGEST-MD5'))) {
+            return $error;
+        }
+        /* 334: Continue authentication request */
+        if (PEAR::isError($error = $this->_parseResponse(334))) {
+            /* 503: Error: already authenticated */
+            if ($this->_code === 503) {
+                return true;
+            }
+            return $error;
+        }
+
+        $challenge = base64_decode($this->_arguments[0]);
+        $digest = &Auth_SASL::factory('digestmd5');
+        $auth_str = base64_encode($digest->getResponse($uid, $pwd, $challenge,
+                                                       $this->host, "smtp",
+                                                       $authz));
+
+        if (PEAR::isError($error = $this->_put($auth_str))) {
+            return $error;
+        }
+        /* 334: Continue authentication request */
+        if (PEAR::isError($error = $this->_parseResponse(334))) {
+            return $error;
+        }
+
+        /* We don't use the protocol's third step because SMTP doesn't
+         * allow subsequent authentication, so we just silently ignore
+         * it. */
+        if (PEAR::isError($error = $this->_put(''))) {
+            return $error;
+        }
+        /* 235: Authentication successful */
+        if (PEAR::isError($error = $this->_parseResponse(235))) {
+            return $error;
+        }
+    }
+
+    /**
+     * Authenticates the user using the CRAM-MD5 method.
+     *
+     * @param string The userid to authenticate as.
+     * @param string The password to authenticate with.
+     * @param string The optional authorization proxy identifier.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access private
+     * @since  1.1.0
+     */
+    function _authCRAM_MD5($uid, $pwd, $authz = '')
+    {
+        if (PEAR::isError($error = $this->_put('AUTH', 'CRAM-MD5'))) {
+            return $error;
+        }
+        /* 334: Continue authentication request */
+        if (PEAR::isError($error = $this->_parseResponse(334))) {
+            /* 503: Error: already authenticated */
+            if ($this->_code === 503) {
+                return true;
+            }
+            return $error;
+        }
+
+        $challenge = base64_decode($this->_arguments[0]);
+        $cram = &Auth_SASL::factory('crammd5');
+        $auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge));
+
+        if (PEAR::isError($error = $this->_put($auth_str))) {
+            return $error;
+        }
+
+        /* 235: Authentication successful */
+        if (PEAR::isError($error = $this->_parseResponse(235))) {
+            return $error;
+        }
+    }
+
+    /**
+     * Authenticates the user using the LOGIN method.
+     *
+     * @param string The userid to authenticate as.
+     * @param string The password to authenticate with.
+     * @param string The optional authorization proxy identifier.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access private
+     * @since  1.1.0
+     */
+    function _authLogin($uid, $pwd, $authz = '')
+    {
+        if (PEAR::isError($error = $this->_put('AUTH', 'LOGIN'))) {
+            return $error;
+        }
+        /* 334: Continue authentication request */
+        if (PEAR::isError($error = $this->_parseResponse(334))) {
+            /* 503: Error: already authenticated */
+            if ($this->_code === 503) {
+                return true;
+            }
+            return $error;
+        }
+
+        if (PEAR::isError($error = $this->_put(base64_encode($uid)))) {
+            return $error;
+        }
+        /* 334: Continue authentication request */
+        if (PEAR::isError($error = $this->_parseResponse(334))) {
+            return $error;
+        }
+
+        if (PEAR::isError($error = $this->_put(base64_encode($pwd)))) {
+            return $error;
+        }
+
+        /* 235: Authentication successful */
+        if (PEAR::isError($error = $this->_parseResponse(235))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Authenticates the user using the PLAIN method.
+     *
+     * @param string The userid to authenticate as.
+     * @param string The password to authenticate with.
+     * @param string The optional authorization proxy identifier.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access private
+     * @since  1.1.0
+     */
+    function _authPlain($uid, $pwd, $authz = '')
+    {
+        if (PEAR::isError($error = $this->_put('AUTH', 'PLAIN'))) {
+            return $error;
+        }
+        /* 334: Continue authentication request */
+        if (PEAR::isError($error = $this->_parseResponse(334))) {
+            /* 503: Error: already authenticated */
+            if ($this->_code === 503) {
+                return true;
+            }
+            return $error;
+        }
+
+        $auth_str = base64_encode($authz . chr(0) . $uid . chr(0) . $pwd);
+
+        if (PEAR::isError($error = $this->_put($auth_str))) {
+            return $error;
+        }
+
+        /* 235: Authentication successful */
+        if (PEAR::isError($error = $this->_parseResponse(235))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Send the HELO command.
+     *
+     * @param string The domain name to say we are.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function helo($domain)
+    {
+        if (PEAR::isError($error = $this->_put('HELO', $domain))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Return the list of SMTP service extensions advertised by the server.
+     *
+     * @return array The list of SMTP service extensions.
+     * @access public
+     * @since 1.3
+     */
+    function getServiceExtensions()
+    {
+        return $this->_esmtp;
+    }
+
+    /**
+     * Send the MAIL FROM: command.
+     *
+     * @param string $sender    The sender (reverse path) to set.
+     * @param string $params    String containing additional MAIL parameters,
+     *                          such as the NOTIFY flags defined by RFC 1891
+     *                          or the VERP protocol.
+     *
+     *                          If $params is an array, only the 'verp' option
+     *                          is supported.  If 'verp' is true, the XVERP
+     *                          parameter is appended to the MAIL command.  If
+     *                          the 'verp' value is a string, the full
+     *                          XVERP=value parameter is appended.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function mailFrom($sender, $params = null)
+    {
+        $args = "FROM:<$sender>";
+
+        /* Support the deprecated array form of $params. */
+        if (is_array($params) && isset($params['verp'])) {
+            /* XVERP */
+            if ($params['verp'] === true) {
+                $args .= ' XVERP';
+
+            /* XVERP=something */
+            } elseif (trim($params['verp'])) {
+                $args .= ' XVERP=' . $params['verp'];
+            }
+        } elseif (is_string($params) && !empty($params)) {
+            $args .= ' ' . $params;
+        }
+
+        if (PEAR::isError($error = $this->_put('MAIL', $args))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Send the RCPT TO: command.
+     *
+     * @param string $recipient The recipient (forward path) to add.
+     * @param string $params    String containing additional RCPT parameters,
+     *                          such as the NOTIFY flags defined by RFC 1891.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     *
+     * @access public
+     * @since  1.0
+     */
+    function rcptTo($recipient, $params = null)
+    {
+        $args = "TO:<$recipient>";
+        if (is_string($params)) {
+            $args .= ' ' . $params;
+        }
+
+        if (PEAR::isError($error = $this->_put('RCPT', $args))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(array(250, 251), $this->pipelining))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Quote the data so that it meets SMTP standards.
+     *
+     * This is provided as a separate public function to facilitate
+     * easier overloading for the cases where it is desirable to
+     * customize the quoting behavior.
+     *
+     * @param string $data  The message text to quote. The string must be passed
+     *                      by reference, and the text will be modified in place.
+     *
+     * @access public
+     * @since  1.2
+     */
+    function quotedata(&$data)
+    {
+        /* Change Unix (\n) and Mac (\r) linefeeds into
+         * Internet-standard CRLF (\r\n) linefeeds. */
+        $data = preg_replace(array('/(?<!\r)\n/','/\r(?!\n)/'), "\r\n", $data);
+
+        /* Because a single leading period (.) signifies an end to the
+         * data, legitimate leading periods need to be "doubled"
+         * (e.g. '..'). */
+        $data = str_replace("\n.", "\n..", $data);
+    }
+
+    /**
+     * Send the DATA command.
+     *
+     * @param mixed $data     The message data, either as a string or an open
+     *                        file resource.
+     * @param string $headers The message headers.  If $headers is provided,
+     *                        $data is assumed to contain only body data.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function data($data, $headers = null)
+    {
+        /* Verify that $data is a supported type. */
+        if (!is_string($data) && !is_resource($data)) {
+            return PEAR::raiseError('Expected a string or file resource');
+        }
+
+        /* Start by considering the size of the optional headers string.  We
+         * also account for the addition 4 character "\r\n\r\n" separator
+         * sequence. */
+        $size = (is_null($headers)) ? 0 : strlen($headers) + 4;
+
+        if (is_resource($data)) {
+            $stat = fstat($data);
+            if ($stat === false) {
+                return PEAR::raiseError('Failed to get file size');
+            }
+            $size += $stat['size'];
+        } else {
+            $size += strlen($data);
+        }
+
+        /* RFC 1870, section 3, subsection 3 states "a value of zero indicates
+         * that no fixed maximum message size is in force".  Furthermore, it
+         * says that if "the parameter is omitted no information is conveyed
+         * about the server's fixed maximum message size". */
+        $limit = (isset($this->_esmtp['SIZE'])) ? $this->_esmtp['SIZE'] : 0;
+        if ($limit > 0 && $size >= $limit) {
+            $this->disconnect();
+            return PEAR::raiseError('Message size exceeds server limit');
+        }
+
+        /* Initiate the DATA command. */
+        if (PEAR::isError($error = $this->_put('DATA'))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(354))) {
+            return $error;
+        }
+
+        /* If we have a separate headers string, send it first. */
+        if (!is_null($headers)) {
+            $this->quotedata($headers);
+            if (PEAR::isError($result = $this->_send($headers . "\r\n\r\n"))) {
+                return $result;
+            }
+        }
+
+        /* Now we can send the message body data. */
+        if (is_resource($data)) {
+            /* Stream the contents of the file resource out over our socket 
+             * connection, line by line.  Each line must be run through the 
+             * quoting routine. */
+            while (strlen($line = fread($data, 8192)) > 0) {
+                /* If the last character is an newline, we need to grab the
+                 * next character to check to see if it is a period. */
+                while (!feof($data)) {
+                    $char = fread($data, 1);
+                    $line .= $char;
+                    if ($char != "\n") {
+                        break;
+                    }
+                }
+                $this->quotedata($line);
+                if (PEAR::isError($result = $this->_send($line))) {
+                    return $result;
+                }
+            }
+        } else {
+            /*
+             * Break up the data by sending one chunk (up to 512k) at a time.  
+             * This approach reduces our peak memory usage.
+             */
+            for ($offset = 0; $offset < $size;) {
+                $end = $offset + 512000;
+
+                /*
+                 * Ensure we don't read beyond our data size or span multiple 
+                 * lines.  quotedata() can't properly handle character data 
+                 * that's split across two line break boundaries.
+                 */
+                if ($end >= $size) {
+                    $end = $size;
+                } else {
+                    for (; $end < $size; $end++) {
+                        if ($data[$end] != "\n") {
+                            break;
+                        }
+                    }
+                }
+
+                /* Extract our chunk and run it through the quoting routine. */
+                $chunk = substr($data, $offset, $end - $offset);
+                $this->quotedata($chunk);
+
+                /* If we run into a problem along the way, abort. */
+                if (PEAR::isError($result = $this->_send($chunk))) {
+                    return $result;
+                }
+
+                /* Advance the offset to the end of this chunk. */
+                $offset = $end;
+            }
+        }
+
+        /* Finally, send the DATA terminator sequence. */
+        if (PEAR::isError($result = $this->_send("\r\n.\r\n"))) {
+            return $result;
+        }
+
+        /* Verify that the data was successfully received by the server. */
+        if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Send the SEND FROM: command.
+     *
+     * @param string The reverse path to send.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.2.6
+     */
+    function sendFrom($path)
+    {
+        if (PEAR::isError($error = $this->_put('SEND', "FROM:<$path>"))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Backwards-compatibility wrapper for sendFrom().
+     *
+     * @param string The reverse path to send.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     *
+     * @access      public
+     * @since       1.0
+     * @deprecated  1.2.6
+     */
+    function send_from($path)
+    {
+        return sendFrom($path);
+    }
+
+    /**
+     * Send the SOML FROM: command.
+     *
+     * @param string The reverse path to send.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.2.6
+     */
+    function somlFrom($path)
+    {
+        if (PEAR::isError($error = $this->_put('SOML', "FROM:<$path>"))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Backwards-compatibility wrapper for somlFrom().
+     *
+     * @param string The reverse path to send.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     *
+     * @access      public
+     * @since       1.0
+     * @deprecated  1.2.6
+     */
+    function soml_from($path)
+    {
+        return somlFrom($path);
+    }
+
+    /**
+     * Send the SAML FROM: command.
+     *
+     * @param string The reverse path to send.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.2.6
+     */
+    function samlFrom($path)
+    {
+        if (PEAR::isError($error = $this->_put('SAML', "FROM:<$path>"))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Backwards-compatibility wrapper for samlFrom().
+     *
+     * @param string The reverse path to send.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     *
+     * @access      public
+     * @since       1.0
+     * @deprecated  1.2.6
+     */
+    function saml_from($path)
+    {
+        return samlFrom($path);
+    }
+
+    /**
+     * Send the RSET command.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function rset()
+    {
+        if (PEAR::isError($error = $this->_put('RSET'))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Send the VRFY command.
+     *
+     * @param string The string to verify
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function vrfy($string)
+    {
+        /* Note: 251 is also a valid response code */
+        if (PEAR::isError($error = $this->_put('VRFY', $string))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(array(250, 252)))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Send the NOOP command.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function noop()
+    {
+        if (PEAR::isError($error = $this->_put('NOOP'))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Backwards-compatibility method.  identifySender()'s functionality is
+     * now handled internally.
+     *
+     * @return  boolean     This method always return true.
+     *
+     * @access  public
+     * @since   1.0
+     */
+    function identifySender()
+    {
+        return true;
+    }
+
+}


commit 3cea9289ef04ce54e1079b8171c560705f65fa14
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Oct 1 15:44:25 2012 +0200

    Implement select_options_objectclasses() with LDAP schema read

diff --git a/lib/Auth.php b/lib/Auth.php
index e1f754b..19b3857 100644
--- a/lib/Auth.php
+++ b/lib/Auth.php
@@ -410,4 +410,9 @@ class Auth {
     {
         return $this->auth_instance()->attributes_allowed($object_classes);
     }
+
+    public function ldap_schema_classes()
+    {
+        return $this->auth_instance()->classes_allowed();
+    }
 }
diff --git a/lib/api/kolab_api_service_form_value.php b/lib/api/kolab_api_service_form_value.php
index c803919..d854d3e 100644
--- a/lib/api/kolab_api_service_form_value.php
+++ b/lib/api/kolab_api_service_form_value.php
@@ -769,24 +769,12 @@ class kolab_api_service_form_value extends kolab_api_service
 
     private function select_options_objectclass($postdata, $attribs = array())
     {
-        // @TODO: get list from LDAP
-        // @TODO: filter by object type?
-        $classes = array(
-            'groupofuniquenames',
-            'inetorgperson',
-            'kolabgroupofuniquenames',
-            'kolabinetorgperson',
-            'kolabsharedfolder',
-            'mailrecipient',
-            'organizationalperson',
-            'organizationalunit',
-            'person',
-            'posixaccount',
-            'posixgroup',
-            'top',
-        );
+        $auth = Auth::get_instance();
+        $list = $auth->ldap_schema_classes();
+
+        sort($list);
 
-        return array('list' => $classes);
+        return array('list' => $list);
     }
 
     private function select_options_attribute($postdata, $attribs = array())
diff --git a/lib/ext/Net/LDAP3.php b/lib/ext/Net/LDAP3.php
index 3d4f897..c722fce 100644
--- a/lib/ext/Net/LDAP3.php
+++ b/lib/ext/Net/LDAP3.php
@@ -310,6 +310,19 @@ class Net_LDAP3
         return array('may' => $may, 'must' => $must, 'super' => $superclasses);
     }
 
+    public function classes_allowed()
+    {
+        $schema  = $this->init_schema();
+        $list    = $schema->getAll('objectclasses');
+        $classes = array();
+
+        foreach ($list as $class) {
+            $classes[] = $class['name'];
+        }
+
+        return $classes;
+    }
+
     /**
      * Bind connection with DN and password
      *


commit cbcd7742920721896e048eeb3a545977426047f6
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Oct 1 15:21:37 2012 +0200

    Return list of unique attributes from attributes_allowed()

diff --git a/lib/ext/Net/LDAP3.php b/lib/ext/Net/LDAP3.php
index 9fc7763..3d4f897 100644
--- a/lib/ext/Net/LDAP3.php
+++ b/lib/ext/Net/LDAP3.php
@@ -303,6 +303,10 @@ class Net_LDAP3
             } */
         }
 
+        $may          = array_unique($may);
+        $must         = array_unique($must);
+        $superclasses = array_unique($superclasses);
+
         return array('may' => $may, 'must' => $must, 'super' => $superclasses);
     }
 


commit 393b459a95be466178994ecb40665f42802cab8c
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Oct 1 11:44:46 2012 +0200

    Sort select_attribute() result

diff --git a/lib/api/kolab_api_service_form_value.php b/lib/api/kolab_api_service_form_value.php
index 0ea4c0e..c803919 100644
--- a/lib/api/kolab_api_service_form_value.php
+++ b/lib/api/kolab_api_service_form_value.php
@@ -794,6 +794,10 @@ class kolab_api_service_form_value extends kolab_api_service
         $auth = Auth::get_instance();
         $list = $auth->ldap_schema_attributes($postdata['classes']);
 
+        if (is_array($list['may'])) {
+            sort($list['may']);
+        }
+
         return array(
             'list'     => $list['may'],
             'required' => $list['must']


commit f736b8d5d8d06a7106859002c7d8d822cc90cd4d
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Fri Sep 28 13:55:31 2012 +0200

    Sort and slice the array returned as part of form_value.list_options_member(), as it includes users and groups. (#1010)

diff --git a/lib/api/kolab_api_service_form_value.php b/lib/api/kolab_api_service_form_value.php
index bff2aa8..0ea4c0e 100644
--- a/lib/api/kolab_api_service_form_value.php
+++ b/lib/api/kolab_api_service_form_value.php
@@ -1003,7 +1003,6 @@ class kolab_api_service_form_value extends kolab_api_service
         $data['attributes'] = array('displayname', 'cn', 'mail');
 
         $service = $this->controller->get_service('users');
-
         $result  = $service->users_list(null, $data);
 
         $list    = $result['list'];
@@ -1029,6 +1028,13 @@ class kolab_api_service_form_value extends kolab_api_service
             }
         }
 
+        // Sort and slice
+        asort($list);
+
+        if (!empty($data['page_size'])) {
+            $list = array_slice($list, 0, $data['page_size']);
+        }
+
         return $list;
     }
 


commit b6e9399574ca352bc8938952af7da53ebd45ec08
Author: Torsten Grote <grote at kolabsys.com>
Date:   Thu Sep 27 16:26:54 2012 +0200

    automatically reload captcha when captcha fails

diff --git a/lib/client/kolab_client_task_signup.php b/lib/client/kolab_client_task_signup.php
index 856862c..86789b0 100644
--- a/lib/client/kolab_client_task_signup.php
+++ b/lib/client/kolab_client_task_signup.php
@@ -141,7 +141,9 @@ class kolab_client_task_signup extends kolab_client_task
 
             if (!$resp->is_valid) {
                 // What happens when the CAPTCHA was entered incorrectly
-                $this->output->command('display_message', "The reCAPTCHA wasn't entered correctly. Please reload and try it again.", 'error');
+                $this->output->command('reload_captcha');
+                // TODO localise this error message
+                $this->output->command('display_message', "The reCAPTCHA wasn't entered correctly. Please try again.", 'error');
                 return;
             }
 
diff --git a/public_html/js/kolab_hosted.js b/public_html/js/kolab_hosted.js
index af3e1c2..aa4ab81 100644
--- a/public_html/js/kolab_hosted.js
+++ b/public_html/js/kolab_hosted.js
@@ -49,6 +49,11 @@ kadm.user_save = function(reload, section)
     kadm.http_post('signup.add_user', {data: data});
 };
 
+kadm.reload_captcha = function()
+{
+    Recaptcha.reload();
+};
+
 kadm.change_user_type = function()
 {
     var data = kadm.serialize_form('#'+this.env.form_id);


commit 4fece9d4c7d584b8afcf822ab1d598ba0764de08
Author: Torsten Grote <grote at kolabsys.com>
Date:   Thu Sep 27 16:11:41 2012 +0200

    proper change of account type for hosted signup

diff --git a/lib/client/kolab_client_task_signup.php b/lib/client/kolab_client_task_signup.php
index 86a123e..856862c 100644
--- a/lib/client/kolab_client_task_signup.php
+++ b/lib/client/kolab_client_task_signup.php
@@ -101,6 +101,7 @@ class kolab_client_task_signup extends kolab_client_task
         $this->output->assign('form', $form);
         $this->output->set_env('token', $this->token);
         $this->output->set_object('taskcontent', $form);
+        $this->output->command('check_user_availability()');
     }
 
     // check if user already exists
diff --git a/public_html/js/kolab_hosted.js b/public_html/js/kolab_hosted.js
index 9f717ae..af3e1c2 100644
--- a/public_html/js/kolab_hosted.js
+++ b/public_html/js/kolab_hosted.js
@@ -53,6 +53,7 @@ kadm.change_user_type = function()
 {
     var data = kadm.serialize_form('#'+this.env.form_id);
 
+    this.set_busy(true, 'loading');
     kadm.http_post('signup.default', {data: data});
 };
 
@@ -62,14 +63,16 @@ kadm.check_user_availability = function()
     var data = kadm.serialize_form('#signup-form');
     var mail = data['uid'] + '@' + data['domain'];
 
-    if(isValidEmailAddress(mail)) {
-        // update future mail form field
-        $('input[name="mail"]').val(mail);
+    if(data['uid'] != '') {
+        if(isValidEmailAddress(mail)) {
+            // update future mail form field
+            $('input[name="mail"]').val(mail);
 
-        // check if user with that email address already exists
-        kadm.http_post('signup.check_user', {data: data});
-    } else {
-        kadm.update_user_info('signup.wronguid', 'uid');
+            // check if user with that email address already exists
+            kadm.http_post('signup.check_user', {data: data});
+        } else {
+            kadm.update_user_info('signup.wronguid', 'uid');
+        }
     }
 };
 


commit 7d038c0504f37455c6f58cccdab2aba971701d08
Author: Torsten Grote <grote at kolabsys.com>
Date:   Thu Sep 27 15:46:44 2012 +0200

    add organization and mail to signup form, show optional fields

diff --git a/doc/kolab_hosting-3.0.sql b/doc/kolab_hosting-3.0.sql
index cc64e88..fb7c2a7 100644
--- a/doc/kolab_hosting-3.0.sql
+++ b/doc/kolab_hosting-3.0.sql
@@ -3,6 +3,6 @@
 --
 
 INSERT INTO `user_types` (`key`, `name`, `description`, `attributes`, `used_for`) VALUES
-('personal', 'Hosted Personal', 'A user with a personal hosted plan', '{"auto_form_fields":{"cn":{"data":["givenname","sn"]},"mail":{"data":["uid"]}},"form_fields":{"givenname":[],"mailalternateaddress":{"optional":true},"sn":[],"uid":[],"userpassword":{"type":"password"}},"fields":{"mailquota":"131072","nsroledn":"cn=personal-user,%(base_dn)s","objectclass":["top","inetorgperson","kolabinetorgperson","mailrecipient","organizationalperson","person"]}}', 'hosted'),
-('professional', 'Hosted Professional', 'A user with a professional hosted plan', '{"auto_form_fields":{"cn":{"data":["givenname","sn"]},"mail":{"data":["uid"]}},"form_fields":{"alias":{"type":"list","optional":true,"maxcount":2},"givenname":[],"mailalternateaddress":{"optional":true},"sn":[],"uid":[],"userpassword":{"type":"password"}},"fields":{"mailquota":"1048576","nsroledn":"cn=professional-user,%(base_dn)s","objectclass":["top","inetorgperson","kolabinetorgperson","mailrecipient","organizationalperson","person"]}}', 'hosted');
+('personal', 'Hosted Personal', 'A user with a personal hosted plan', '{"auto_form_fields":{"cn":{"data":["givenname","sn"]},"mail":{"data":["uid"]}},"form_fields":{"givenname":[],"mailalternateaddress":[],"org":{"optional":true},"sn":[],"uid":[],"userpassword":{"type":"password"}},"fields":{"mailquota":"131072","nsroledn":"cn=personal-user,%(base_dn)s","objectclass":["top","inetorgperson","kolabinetorgperson","mailrecipient","organizationalperson","person"]}}', 'hosted'),
+('professional', 'Hosted Professional', 'A user with a professional hosted plan', '{"auto_form_fields":{"cn":{"data":["givenname","sn"]},"mail":{"data":["uid"]}},"form_fields":{"alias":{"type":"list","optional":true,"maxcount":2},"givenname":[],"mailalternateaddress":[],"org":{"optional":true},"sn":[],"uid":[],"userpassword":{"type":"password"}},"fields":{"mailquota":"1048576","nsroledn":"cn=professional-user,%(base_dn)s","objectclass":["top","inetorgperson","kolabinetorgperson","mailrecipient","organizationalperson","person"]}}', 'hosted');
 
diff --git a/lib/client/kolab_client_task_signup.php b/lib/client/kolab_client_task_signup.php
index de43c45..86a123e 100644
--- a/lib/client/kolab_client_task_signup.php
+++ b/lib/client/kolab_client_task_signup.php
@@ -99,6 +99,7 @@ class kolab_client_task_signup extends kolab_client_task
         }
 
         $this->output->assign('form', $form);
+        $this->output->set_env('token', $this->token);
         $this->output->set_object('taskcontent', $form);
     }
 
@@ -158,8 +159,6 @@ class kolab_client_task_signup extends kolab_client_task
         // Remove domain from $data before adding user
         unset($data['domain']);
 
-        $data['cn'] = $this->api->post('form_value.generate', $data);
-
         // Add user
         $result = $this->api->post('user.add', null, $data);
 
@@ -179,6 +178,7 @@ class kolab_client_task_signup extends kolab_client_task
             'givenname'                 => 'other',
             'sn'                        => 'other',
             'cn'                        => 'other',
+            'org'                       => 'other',
             'mailalternateaddress'      => 'other',
             'uid'                       => 'other',
             'domain'                    => 'other',
@@ -190,13 +190,6 @@ class kolab_client_task_signup extends kolab_client_task
 
         // Prepare fields
         list($fields, $types, $type) = $this->form_prepare('user', $data, array('userpassword2'), 'hosted');
-        
-        // Show only required fields
-        foreach ($fields as $field_name => $field_attrs) {
-            if(!array_key_exists('required', $field_attrs) or $field_attrs['required'] != 'true') {
-                unset($fields[$field_name]);
-            }
-        }
 
         // Add user type id selector
         $accttypes = array();
@@ -231,11 +224,8 @@ class kolab_client_task_signup extends kolab_client_task
 
         // Hide cn field
         if (isset($fields['cn'])) {
-            // TODO use type info from user types table
+            // TODO add "hidden":true to user_types attributes and use it
             $fields['cn']['type'] = kolab_form::INPUT_HIDDEN;
-
-            $fields['sn']['onchange'] = '';
-            $fields['givenname']['onchange'] = '';
         }
 
         // Add password confirmation
@@ -248,6 +238,7 @@ class kolab_client_task_signup extends kolab_client_task
         $fields['uid']['label'] = 'signup.username';
         $fields['mail']['label'] = 'signup.futuremail';
         if(isset($fields['mailalternateaddress'])) $fields['mailalternateaddress']['label'] = 'signup.mailalternateaddress';
+//        if(isset($fields['org'])) $fields['org']['label'] = 'signup.company';
         $fields['domain']['label'] = 'signup.domain';
 
         // Create form object and populate with fields
@@ -255,7 +246,7 @@ class kolab_client_task_signup extends kolab_client_task
 
         $form->set_title($this->translate('signup.formtitle'));
 
-        $this->output->add_translation('user.password.mismatch', 'signup.wronguid', 'signup.userexists');
+        $this->output->add_translation('user.password.mismatch', 'signup.wronguid', 'signup.userexists', 'signup.wrongmailalternateaddress');
 
         return $form->output();
     }


commit 6a938921eafcb67eb50db6f5560acfe4b7e318e7
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Sep 27 14:26:26 2012 +0200

    Use service_bind_dn/service_bind_pw for LDAP connection in init_schema()

diff --git a/lib/ext/Net/LDAP3.php b/lib/ext/Net/LDAP3.php
index 62eba7d..9fc7763 100644
--- a/lib/ext/Net/LDAP3.php
+++ b/lib/ext/Net/LDAP3.php
@@ -1629,8 +1629,8 @@ class Net_LDAP3
                 'port'   => $port,
                 'tls'    => FALSE,
                 'version' => 3,
-                'binddn' => $this->config_get('bind_dn'),
-                'bindpw' => $this->config_get('bind_pw')
+                'binddn' => $this->config_get('service_bind_dn'),
+                'bindpw' => $this->config_get('service_bind_pw')
             );
 
             $_ldap_schema_cache_cfg = array(


commit 907dfe8ccb10205a193b3fbf720e6d21ca84cd8e
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Sep 27 14:23:08 2012 +0200

    Improved response format of form_value.select_options action.
    Now each attribute response contains 'list' element and other
    optional elements e.g. 'default', 'required', etc.

diff --git a/lib/api/kolab_api_service_form_value.php b/lib/api/kolab_api_service_form_value.php
index 4b3430f..bff2aa8 100644
--- a/lib/api/kolab_api_service_form_value.php
+++ b/lib/api/kolab_api_service_form_value.php
@@ -60,9 +60,7 @@ class kolab_api_service_form_value extends kolab_api_service
     public function generate($getdata, $postdata)
     {
         $attribs    = $this->object_type_attributes($postdata['object_type'], $postdata['type_id']);
-
         $attributes = (array) $postdata['attributes'];
-
         $result     = array();
 
         foreach ($attributes as $attr_name) {
@@ -164,12 +162,18 @@ class kolab_api_service_form_value extends kolab_api_service
 
             $method_name = 'select_options_' . strtolower($attr_name);
 
-            if (!method_exists($this, $method_name)) {
-                $result[$attr_name] = array();
-                continue;
+            if (method_exists($this, $method_name)) {
+                $res = $this->{$method_name}($postdata, $attribs);
+            }
+            else {
+                $res = array();
             }
 
-            $result[$attr_name] = $this->{$method_name}($postdata, $attribs);
+            if (!is_array($res['list'])) {
+                $res['list'] = array();
+            }
+
+            $result[$attr_name] = $res;
         }
 
         return $result;
@@ -782,7 +786,7 @@ class kolab_api_service_form_value extends kolab_api_service
             'top',
         );
 
-        return $classes;
+        return array('list' => $classes);
     }
 
     private function select_options_attribute($postdata, $attribs = array())
@@ -790,7 +794,10 @@ class kolab_api_service_form_value extends kolab_api_service
         $auth = Auth::get_instance();
         $list = $auth->ldap_schema_attributes($postdata['classes']);
 
-        return $list;
+        return array(
+            'list'     => $list['may'],
+            'required' => $list['must']
+        );
     }
 
     private function select_options_ou($postdata, $attribs = array())
@@ -809,7 +816,7 @@ class kolab_api_service_form_value extends kolab_api_service
         }
 
         if (!empty($postdata['id'])) {
-            $subjects = $auth->search($base_dn, '(' . $unique_attr . '=' . $postdata['id'] . ')')->entries(TRUE);
+            $subjects = $auth->search($base_dn, '(' . $unique_attr . '=' . $postdata['id'] . ')')->entries(true);
 
             if ($subjects) {
                 $subject = array_shift($subjects);
@@ -828,27 +835,27 @@ class kolab_api_service_form_value extends kolab_api_service
             $default = $base_dn;
         }
 
-        $ous = $auth->search($base_dn, '(objectclass=organizationalunit)');
-
+        $ous  = $auth->search($base_dn, '(objectclass=organizationalunit)');
         $_ous = array();
 
-        foreach ($ous->entries(TRUE) as $ou_dn => $ou_attrs) {
+        foreach ($ous->entries(true) as $ou_dn => $ou_attrs) {
             $_ous[] = strtolower($ou_dn);
         }
 
         sort($_ous);
 
-        $_ous['default'] = strtolower($default);
-
-        return $_ous;
+        return array(
+            'list'    => $_ous,
+            'default' => strtolower($default),
+        );
     }
 
     private function select_options_preferredlanguage($postdata, $attribs = array())
     {
         $options = $this->_select_options_from_db('preferredlanguage');
-
-        $conf = Conf::get_instance();
+        $conf    = Conf::get_instance();
         $default = $conf->get('default_locale');
+
         if (!$default) {
             $default = 'en_US';
         }
@@ -857,9 +864,10 @@ class kolab_api_service_form_value extends kolab_api_service
             $default = $postdata['preferredlanguage'];
         }
 
-        $options['default'] = $default;
-
-        return $options;
+        return array(
+            'list'    => $options,
+            'default' => $default,
+        );
     }
 
     private function validate_alias($value)
@@ -1069,21 +1077,15 @@ class kolab_api_service_form_value extends kolab_api_service
 
     private function _select_options_from_db($attribute)
     {
-
         if (empty($attribute)) {
             return false;
         }
 
-        $db = SQL::get_instance();
+        $db     = SQL::get_instance();
         $result = $db->fetch_assoc($db->query("SELECT option_values FROM options WHERE attribute = ?", $attribute));
-
         $result = json_decode($result['option_values']);
 
-        if (empty($result)) {
-            return false;
-        } else {
-            return $result;
-        }
+        return array('list' => $result);
     }
 
     private function _validate_email_address($mail_address) {
diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php
index 02ca4a5..4326b9a 100644
--- a/lib/kolab_client_task.php
+++ b/lib/kolab_client_task.php
@@ -828,14 +828,11 @@ class kolab_client_task
         if (!isset($field['values'])) {
             $data['attributes'] = array($field['name']);
             $resp = $this->api->post('form_value.select_options', null, $data);
-
+            $resp = $resp->get($field['name']);
             unset($data['attributes']);
-            $field['values'] = $resp->get($field['name']);
-        }
 
-        if (!empty($field['values']['default'])) {
-            $default = $field['values']['default'];
-            unset($field['values']['default']);
+            $default         = $resp['default'];
+            $field['values'] = $resp['list'];
         }
 
         if (!empty($field['values'])) {


commit 2f1200e5f464d64f54bf5848e8858ffa70e30389
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Thu Sep 27 12:43:16 2012 +0200

    Re-use the existing local configuration for init_schema()

diff --git a/lib/ext/Net/LDAP3.php b/lib/ext/Net/LDAP3.php
index 54ebc12..62eba7d 100644
--- a/lib/ext/Net/LDAP3.php
+++ b/lib/ext/Net/LDAP3.php
@@ -1617,36 +1617,43 @@ class Net_LDAP3
 
     private function init_schema()
     {
-        $this->_ldap_uri    = $this->config_get('ldap_uri');
-        $this->_ldap_server = parse_url($this->_ldap_uri, PHP_URL_HOST);
-        $this->_ldap_port   = parse_url($this->_ldap_uri, PHP_URL_PORT);
-        $this->_ldap_scheme = parse_url($this->_ldap_uri, PHP_URL_SCHEME);
-
         require_once("Net/LDAP2.php");
 
-        $_ldap_cfg = array(
-            'host'   => $this->_ldap_server,
-            'port'   => $this->_ldap_port,
-            'tls'    => FALSE,
-            'version' => 3,
-            'binddn' => $this->config_get('bind_dn'),
-            'bindpw' => $this->config_get('bind_pw')
-        );
+        $port = $this->config_get('port', 389);
 
-        $_ldap_schema_cache_cfg = array(
-            'path' => "/tmp/" . $this->_ldap_server . ":" . ($this->_ldap_port ? $this->_ldap_port : '389') . "-Net_LDAP2_Schema.cache",
-            'max_age' => 86400,
-        );
+        foreach ($this->config_get('hosts') as $host) {
+            $this->_debug("C: Connect [$host:$port]");
+
+            $_ldap_cfg = array(
+                'host'   => $host,
+                'port'   => $port,
+                'tls'    => FALSE,
+                'version' => 3,
+                'binddn' => $this->config_get('bind_dn'),
+                'bindpw' => $this->config_get('bind_pw')
+            );
 
-        $_ldap_schema_cache = new Net_LDAP2_SimpleFileSchemaCache($_ldap_schema_cache_cfg);
+            $_ldap_schema_cache_cfg = array(
+                'path' => "/tmp/" . $host . ":" . ($port ? $port : '389') . "-Net_LDAP2_Schema.cache",
+                'max_age' => 86400,
+            );
 
-        $_ldap = Net_LDAP2::connect($_ldap_cfg);
+            $_ldap_schema_cache = new Net_LDAP2_SimpleFileSchemaCache($_ldap_schema_cache_cfg);
+
+            if ($_ldap = Net_LDAP2::connect($_ldap_cfg)) {
+                $this->_debug("S: OK");
+                break;
+            }
+
+            $this->_debug("S: NOT OK");
+        }
 
         $result = $_ldap->registerSchemaCache($_ldap_schema_cache);
 
         // TODO: We should learn what LDAP tech. we're running against.
         // Perhaps with a scope base objectclass recognize rootdse entry
         $schema_root_dn = $this->config_get('schema_root_dn');
+
         if (!$schema_root_dn) {
             $_schema = $_ldap->schema();
         }


commit 7d8dd754e28c5035aad3565c75ca83cc87659f79
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Sep 27 12:00:51 2012 +0200

    Add select_options_attribute()

diff --git a/lib/Auth.php b/lib/Auth.php
index 94ba753..e1f754b 100644
--- a/lib/Auth.php
+++ b/lib/Auth.php
@@ -405,4 +405,9 @@ class Auth {
     {
         return $this->auth_instance()->user_info($userdata);
     }
+
+    public function ldap_schema_attributes($object_classes)
+    {
+        return $this->auth_instance()->attributes_allowed($object_classes);
+    }
 }
diff --git a/lib/api/kolab_api_service_form_value.php b/lib/api/kolab_api_service_form_value.php
index 22cc294..4b3430f 100644
--- a/lib/api/kolab_api_service_form_value.php
+++ b/lib/api/kolab_api_service_form_value.php
@@ -785,6 +785,14 @@ class kolab_api_service_form_value extends kolab_api_service
         return $classes;
     }
 
+    private function select_options_attribute($postdata, $attribs = array())
+    {
+        $auth = Auth::get_instance();
+        $list = $auth->ldap_schema_attributes($postdata['classes']);
+
+        return $list;
+    }
+
     private function select_options_ou($postdata, $attribs = array())
     {
         $auth = Auth::get_instance();


commit e14340ee8b78ec4b128a10021379aa54c35f1f80
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Sep 27 11:52:38 2012 +0200

    Replace lost $this->conf->get() with $this->config_get()

diff --git a/lib/ext/Net/LDAP3.php b/lib/ext/Net/LDAP3.php
index 31247a6..54ebc12 100644
--- a/lib/ext/Net/LDAP3.php
+++ b/lib/ext/Net/LDAP3.php
@@ -304,7 +304,6 @@ class Net_LDAP3
         }
 
         return array('may' => $may, 'must' => $must, 'super' => $superclasses);
-
     }
 
     /**
@@ -1618,7 +1617,7 @@ class Net_LDAP3
 
     private function init_schema()
     {
-        $this->_ldap_uri    = $this->conf->get('ldap_uri');
+        $this->_ldap_uri    = $this->config_get('ldap_uri');
         $this->_ldap_server = parse_url($this->_ldap_uri, PHP_URL_HOST);
         $this->_ldap_port   = parse_url($this->_ldap_uri, PHP_URL_PORT);
         $this->_ldap_scheme = parse_url($this->_ldap_uri, PHP_URL_SCHEME);
@@ -1630,8 +1629,8 @@ class Net_LDAP3
             'port'   => $this->_ldap_port,
             'tls'    => FALSE,
             'version' => 3,
-            'binddn' => $this->conf->get('bind_dn'),
-            'bindpw' => $this->conf->get('bind_pw')
+            'binddn' => $this->config_get('bind_dn'),
+            'bindpw' => $this->config_get('bind_pw')
         );
 
         $_ldap_schema_cache_cfg = array(
@@ -1647,7 +1646,7 @@ class Net_LDAP3
 
         // TODO: We should learn what LDAP tech. we're running against.
         // Perhaps with a scope base objectclass recognize rootdse entry
-        $schema_root_dn = $this->conf->get('schema_root_dn');
+        $schema_root_dn = $this->config_get('schema_root_dn');
         if (!$schema_root_dn) {
             $_schema = $_ldap->schema();
         }


commit c13ca974905cfe959365374464b9b8336c577c98
Author: Torsten Grote <grote at kolabsys.com>
Date:   Thu Sep 27 11:57:30 2012 +0200

    fixed translation issues with signup

diff --git a/lib/client/kolab_client_task_signup.php b/lib/client/kolab_client_task_signup.php
index b3b8615..de43c45 100644
--- a/lib/client/kolab_client_task_signup.php
+++ b/lib/client/kolab_client_task_signup.php
@@ -167,7 +167,7 @@ class kolab_client_task_signup extends kolab_client_task
             $this->output->command('display_message', 'internalerror', 'error');
             return;
         } else {
-            $this->output->set_object('taskcontent', 'signup.usercreated');
+            $this->output->set_object('taskcontent', $this->translate('signup.usercreated'));
         }
     }
 
@@ -255,7 +255,7 @@ class kolab_client_task_signup extends kolab_client_task
 
         $form->set_title($this->translate('signup.formtitle'));
 
-        $this->output->add_translation('user.password.mismatch', 'signup.wronguid');
+        $this->output->add_translation('user.password.mismatch', 'signup.wronguid', 'signup.userexists');
 
         return $form->output();
     }


commit f527aad8464c55142d1d3a86ccb7ab4e329e7953
Author: Torsten Grote <grote at kolabsys.com>
Date:   Thu Sep 27 11:50:57 2012 +0200

    fixed signup bug introduced in f70608cc

diff --git a/lib/client/kolab_client_task_signup.php b/lib/client/kolab_client_task_signup.php
index e39a158..b3b8615 100644
--- a/lib/client/kolab_client_task_signup.php
+++ b/lib/client/kolab_client_task_signup.php
@@ -64,6 +64,8 @@ class kolab_client_task_signup extends kolab_client_task
     {
         if(is_null($domain)) {
             $this->domain = $this->config_get('primary_domain');
+        } else {
+            $this->domain = $domain;
         }
 
         // Login ($result is a kolab_client_api_result instance)


commit 9ab310cb03344647a1b85238fd6bd27257fcb045
Author: Torsten Grote <grote at kolabsys.com>
Date:   Thu Sep 27 11:50:30 2012 +0200

    removed old code from hosted signup

diff --git a/lib/client/kolab_client_task_signup.php b/lib/client/kolab_client_task_signup.php
index a320216..e39a158 100644
--- a/lib/client/kolab_client_task_signup.php
+++ b/lib/client/kolab_client_task_signup.php
@@ -232,8 +232,6 @@ class kolab_client_task_signup extends kolab_client_task
             // TODO use type info from user types table
             $fields['cn']['type'] = kolab_form::INPUT_HIDDEN;
 
-            // TODO auto generate value again with indirect API call
-            $fields['cn']['value'] = 'temporary fake cn';
             $fields['sn']['onchange'] = '';
             $fields['givenname']['onchange'] = '';
         }
@@ -255,7 +253,7 @@ class kolab_client_task_signup extends kolab_client_task
 
         $form->set_title($this->translate('signup.formtitle'));
 
-        $this->output->add_translation('user.password.mismatch', 'user.add.success', 'signup.wronguid');
+        $this->output->add_translation('user.password.mismatch', 'signup.wronguid');
 
         return $form->output();
     }


commit aa3bb34e4498388750543209298e0c7a997113f9
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Sep 27 10:12:17 2012 +0200

    Fix so object creation form is displayed by default on default task action

diff --git a/lib/client/kolab_client_task_domain.php b/lib/client/kolab_client_task_domain.php
index 3a9f6a9..8a538fc 100644
--- a/lib/client/kolab_client_task_domain.php
+++ b/lib/client/kolab_client_task_domain.php
@@ -170,7 +170,10 @@ class kolab_client_task_domain extends kolab_client_task
             'foot'  => $foot,
         ));
 
-        $this->output->command('set_watermark', 'taskcontent');
+        if ($this->action == 'list') {
+            $this->output->command('set_watermark', 'taskcontent');
+        }
+
         $this->output->set_env('search_request', $search_request ? base64_encode(serialize($search_request)) : null);
         $this->output->set_env('list_page', $page);
         $this->output->set_env('list_count', $count);
diff --git a/lib/client/kolab_client_task_group.php b/lib/client/kolab_client_task_group.php
index fada00c..eb5e6b7 100644
--- a/lib/client/kolab_client_task_group.php
+++ b/lib/client/kolab_client_task_group.php
@@ -162,7 +162,10 @@ class kolab_client_task_group extends kolab_client_task
             'foot'  => $foot,
         ));
 
-        $this->output->command('set_watermark', 'taskcontent');
+        if ($this->action == 'list') {
+            $this->output->command('set_watermark', 'taskcontent');
+        }
+
         $this->output->set_env('search_request', $search_request ? base64_encode(serialize($search_request)) : null);
         $this->output->set_env('list_page', $page);
         $this->output->set_env('list_count', $count);
diff --git a/lib/client/kolab_client_task_resource.php b/lib/client/kolab_client_task_resource.php
index 8dde136..f0ce4b0 100644
--- a/lib/client/kolab_client_task_resource.php
+++ b/lib/client/kolab_client_task_resource.php
@@ -164,7 +164,10 @@ class kolab_client_task_resource extends kolab_client_task
             'foot'  => $foot,
         ));
 
-        $this->output->command('set_watermark', 'taskcontent');
+        if ($this->action == 'list') {
+            $this->output->command('set_watermark', 'taskcontent');
+        }
+
         $this->output->set_env('search_request', $search_request ? base64_encode(serialize($search_request)) : null);
         $this->output->set_env('list_page', $page);
         $this->output->set_env('list_count', $count);
diff --git a/lib/client/kolab_client_task_role.php b/lib/client/kolab_client_task_role.php
index 4640716..beac689 100644
--- a/lib/client/kolab_client_task_role.php
+++ b/lib/client/kolab_client_task_role.php
@@ -162,7 +162,10 @@ class kolab_client_task_role extends kolab_client_task
             'foot'  => $foot,
         ));
 
-        $this->output->command('set_watermark', 'taskcontent');
+        if ($this->action == 'list') {
+            $this->output->command('set_watermark', 'taskcontent');
+        }
+
         $this->output->set_env('search_request', $search_request ? base64_encode(serialize($search_request)) : null);
         $this->output->set_env('list_page', $page);
         $this->output->set_env('list_count', $count);
diff --git a/lib/client/kolab_client_task_settings.php b/lib/client/kolab_client_task_settings.php
index 061c65f..09d7697 100644
--- a/lib/client/kolab_client_task_settings.php
+++ b/lib/client/kolab_client_task_settings.php
@@ -290,7 +290,10 @@ class kolab_client_task_settings extends kolab_client_task
             'foot'  => $foot,
         ));
 
-        $this->output->command('set_watermark', 'taskcontent');
+        if ($this->action == 'type_list') {
+            $this->output->command('set_watermark', 'taskcontent');
+        }
+
         $this->output->set_env('search_request', $search_request ? base64_encode(serialize($search_request)) : null);
         $this->output->set_env('list_page', $page);
         $this->output->set_env('list_count', $count);
diff --git a/lib/client/kolab_client_task_user.php b/lib/client/kolab_client_task_user.php
index 859f29a..cf4d9b9 100644
--- a/lib/client/kolab_client_task_user.php
+++ b/lib/client/kolab_client_task_user.php
@@ -162,7 +162,10 @@ class kolab_client_task_user extends kolab_client_task
             'foot'  => $foot,
         ));
 
-        $this->output->command('set_watermark', 'taskcontent');
+        if ($this->action == 'list') {
+            $this->output->command('set_watermark', 'taskcontent');
+        }
+
         $this->output->set_env('search_request', $search_request ? base64_encode(serialize($search_request)) : null);
         $this->output->set_env('list_page', $page);
         $this->output->set_env('list_count', $count);


commit 35cfc507460898e0e0027a0ce269989435017dd9
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Sep 27 09:25:14 2012 +0200

    Integrate hosted/signup with core, fix Smarty cache issue (#1044)

diff --git a/hosted/index.php b/hosted/index.php
index 867a6e1..6414cbe 100644
--- a/hosted/index.php
+++ b/hosted/index.php
@@ -26,16 +26,7 @@
 // environment initialization
 require_once '../lib/functions.php';
 
-// starting task
-$task = kolab_utils::get_input('task', kolab_utils::REQUEST_GET);
-
-if (!$task) {
-    $task = 'signup';
-}
-
-$class = "kolab_client_task_$task";
-
-$KADM = new $class;
+$KADM = new kolab_client_task_signup;
 
 // run actions and send output
 $KADM->run();
diff --git a/hosted/js b/hosted/js
new file mode 120000
index 0000000..050426c
--- /dev/null
+++ b/hosted/js
@@ -0,0 +1 @@
+../public_html/js
\ No newline at end of file
diff --git a/hosted/js/jquery.min.js b/hosted/js/jquery.min.js
deleted file mode 120000
index c3b1873..0000000
--- a/hosted/js/jquery.min.js
+++ /dev/null
@@ -1 +0,0 @@
-../../public_html/js/jquery.min.js
\ No newline at end of file
diff --git a/hosted/js/kolab_admin.js b/hosted/js/kolab_admin.js
deleted file mode 120000
index ea11949..0000000
--- a/hosted/js/kolab_admin.js
+++ /dev/null
@@ -1 +0,0 @@
-../../public_html/js/kolab_admin.js
\ No newline at end of file
diff --git a/hosted/js/kolab_hosted.js b/hosted/js/kolab_hosted.js
deleted file mode 100644
index 9f717ae..0000000
--- a/hosted/js/kolab_hosted.js
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- +--------------------------------------------------------------------------+
- | This file is part of the Kolab Web Admin Panel                           |
- |                                                                          |
- | Copyright (C) 2011-2012, Kolab Systems AG                                |
- |                                                                          |
- | This program is free software: you can redistribute it and/or modify     |
- | it under the terms of the GNU Affero General Public License as published |
- | by the Free Software Foundation, either version 3 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 Affero General Public License for more details.                      |
- |                                                                          |
- | You should have received a copy of the GNU Affero General Public License |
- | along with kadm program. If not, see <http://www.gnu.org/licenses/>      |
- +--------------------------------------------------------------------------+
- | Author: Torsten Grote <grote at kolabsys.com>                               |
- +--------------------------------------------------------------------------+
-*/
-
-// overwrite user_save() function
-kadm.user_save = function(reload, section)
-{
-    var data = kadm.serialize_form('#'+this.env.form_id);
-
-    // check email address
-    if(typeof data.mailalternateaddress != 'undefined' && !isValidEmailAddress(data.mailalternateaddress)) {
-        kadm.display_message('signup.wrongmailalternateaddress', 'error');
-        kadm.form_value_error('mailalternateaddress');
-        return;
-    }
-
-    if (!kadm.check_required_fields(data)) {
-      kadm.display_message('form.required.empty', 'error');
-      return;
-    }
-
-    // check password
-    if (data.userpassword != data.userpassword2) {
-      kadm.display_message('user.password.mismatch', 'error');
-      kadm.form_value_error('userpassword2');
-      return;
-    }
-    delete data['userpassword2'];
-
-    kadm.http_post('signup.add_user', {data: data});
-};
-
-kadm.change_user_type = function()
-{
-    var data = kadm.serialize_form('#'+this.env.form_id);
-
-    kadm.http_post('signup.default', {data: data});
-};
-
-kadm.check_user_availability = function()
-{
-    // get form data and build new email address
-    var data = kadm.serialize_form('#signup-form');
-    var mail = data['uid'] + '@' + data['domain'];
-
-    if(isValidEmailAddress(mail)) {
-        // update future mail form field
-        $('input[name="mail"]').val(mail);
-
-        // check if user with that email address already exists
-        kadm.http_post('signup.check_user', {data: data});
-    } else {
-        kadm.update_user_info('signup.wronguid', 'uid');
-    }
-};
-
-kadm.update_user_info = function(msg, part)
-{
-    var span_id = 'availability';
-    if(!part.localeCompare('userpassword')) {
-        span_id = 'pass_match';
-    }
-
-    if (msg) {
-        msg = kadm.t(msg);
-    }
-
-    // display message next to form field
-    if($('span[id="'+span_id+'"]').length) {
-        // update existing span area
-        $('span[id="'+span_id+'"]').html(msg);
-    }
-    else {
-        // add span area and add message
-        $('input[name="'+part+'"]').after(' <span id="'+span_id+'" class="form_error">' + msg + '</span>');
-    }
-
-    // enable/disable button
-    if(msg == '') {
-        $('input[type="button"]').removeAttr("disabled");
-    } else {
-        $('input[type="button"]').attr("disabled", "disabled");
-    }
-};
-
-
-function password_match()
-{
-    if($('input[name="userpassword"]').val().localeCompare($('input[name="userpassword2"]').val())) {
-        kadm.update_user_info('user.password.mismatch', 'userpassword');
-    }
-    else {
-        kadm.update_user_info('', 'userpassword');
-    }
-}
-
-// TODO use form.validate api call for that
-function isValidEmailAddress(emailAddress) {
-    var pattern = new RegExp(/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i);
-    return pattern.test(emailAddress);
-};
diff --git a/hosted/skins b/hosted/skins
new file mode 120000
index 0000000..5327ffe
--- /dev/null
+++ b/hosted/skins
@@ -0,0 +1 @@
+../public_html/skins
\ No newline at end of file
diff --git a/hosted/skins/default/hosted.css b/hosted/skins/default/hosted.css
deleted file mode 100644
index bba4df6..0000000
--- a/hosted/skins/default/hosted.css
+++ /dev/null
@@ -1,44 +0,0 @@
-body {
-  background: #f0f0f0;
-  text-align: center;
-}
-
-#bodybox {
-  display: inline-block;
-  width: 900px;
-  text-align: left;
-}
-
-#logo {
-  width: 462px;
-  height: 100px;
-  position: relative;
-  margin: 10px;
-  cursor: default;
-}
-
-#content {
-  padding: 15px;
-}
-
-#taskcontent {
-  display: inline-block;
-  margin-top: 10px;
-  text-align: center;
-}
-
-form#signup-form {
-  padding-top: 10px;
-}
-
-td.label {
-  vertical-align: top;
-}
-
-table.form td {
-    text-align: left;
-}
-
-#footer {
-  text-align: center;
-}
diff --git a/hosted/skins/default/images/error.png b/hosted/skins/default/images/error.png
deleted file mode 120000
index fe901a7..0000000
--- a/hosted/skins/default/images/error.png
+++ /dev/null
@@ -1 +0,0 @@
-../../../../public_html/skins/default/images/error.png
\ No newline at end of file
diff --git a/hosted/skins/default/images/favicon.png b/hosted/skins/default/images/favicon.png
deleted file mode 120000
index 7ee1ea8..0000000
--- a/hosted/skins/default/images/favicon.png
+++ /dev/null
@@ -1 +0,0 @@
-../../../../public_html/skins/default/images/favicon.png
\ No newline at end of file
diff --git a/hosted/skins/default/images/info.png b/hosted/skins/default/images/info.png
deleted file mode 120000
index 8e1fec7..0000000
--- a/hosted/skins/default/images/info.png
+++ /dev/null
@@ -1 +0,0 @@
-../../../../public_html/skins/default/images/info.png
\ No newline at end of file
diff --git a/hosted/skins/default/images/loading.gif b/hosted/skins/default/images/loading.gif
deleted file mode 120000
index aaca76f..0000000
--- a/hosted/skins/default/images/loading.gif
+++ /dev/null
@@ -1 +0,0 @@
-../../../../public_html/skins/default/images/loading.gif
\ No newline at end of file
diff --git a/hosted/skins/default/images/logo.png b/hosted/skins/default/images/logo.png
deleted file mode 100644
index 0c06ff1..0000000
Binary files a/hosted/skins/default/images/logo.png and /dev/null differ
diff --git a/hosted/skins/default/style.css b/hosted/skins/default/style.css
deleted file mode 120000
index 6f5490d..0000000
--- a/hosted/skins/default/style.css
+++ /dev/null
@@ -1 +0,0 @@
-../../../public_html/skins/default/style.css
\ No newline at end of file
diff --git a/hosted/skins/default/templates/footer.html b/hosted/skins/default/templates/footer.html
deleted file mode 100644
index 24d972d..0000000
--- a/hosted/skins/default/templates/footer.html
+++ /dev/null
@@ -1 +0,0 @@
-{$engine->translate('signup.footer')}
diff --git a/hosted/skins/default/templates/signup.html b/hosted/skins/default/templates/signup.html
deleted file mode 100644
index 8043859..0000000
--- a/hosted/skins/default/templates/signup.html
+++ /dev/null
@@ -1,41 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta charset="utf-8" />
-    <title>{$engine->translate('signup.headline')}</title>
-    <link rel="stylesheet" href="{$skin_path}style.css" />
-    <link rel="stylesheet" href="{$skin_path}hosted.css" />
-    <link rel="shortcut icon" type="image/png" href="{$skin_path}images/favicon.png" />
-    <script src="js/jquery.min.js"></script>
-    <script src="js/kolab_admin.js"></script>
-    <script type="text/javascript" src="https://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script>
-    <script src="js/kolab_hosted.js"></script>
-    <script src="{$skin_path}ui.js"></script>
-</head>
-<body>
-    <div id="bodybox">
-        <div id="logo"></div>
-        <div id="content">
-
-
-        <div id="task_content" class="signup">
-    
-        <h1>{$engine->translate('signup.headline')}</h1>
-
-        <p>{$engine->translate('signup.intro1')}</p>
-
-        <p>{$engine->translate('signup.intro2')}</p>
-    
-        <div id="taskcontent" class="signup">{$form}</div>
-
-
-        </div>
-    
-        </div>
-        <div id="footer">
-            {include file="footer.html"}
-        </div>
-    </div>
-{$script}
-</body>
-</html>
diff --git a/hosted/skins/default/ui.js b/hosted/skins/default/ui.js
deleted file mode 120000
index 625253b..0000000
--- a/hosted/skins/default/ui.js
+++ /dev/null
@@ -1 +0,0 @@
-../../../public_html/skins/default/ui.js
\ No newline at end of file
diff --git a/hosted/skins/kolabsys/hosted.css b/hosted/skins/kolabsys/hosted.css
deleted file mode 100644
index 427d57d..0000000
--- a/hosted/skins/kolabsys/hosted.css
+++ /dev/null
@@ -1,26 +0,0 @@
-body {
-  background: #F3F3F3;
-  text-align: center;
-}
-
-#taskcontent {
-  width: 680px;
-  min-height: 100px;
-  max-height: 460px;
-}
-
-form#signup-form {
-  padding-top: 10px;
-}
-
-td.value input {
-    width: 250px;
-}
-
-td.label {
-  vertical-align: top;
-}
-
-table.form td {
-    text-align: left;
-}
diff --git a/hosted/skins/kolabsys/images/error.png b/hosted/skins/kolabsys/images/error.png
deleted file mode 120000
index fe901a7..0000000
--- a/hosted/skins/kolabsys/images/error.png
+++ /dev/null
@@ -1 +0,0 @@
-../../../../public_html/skins/default/images/error.png
\ No newline at end of file
diff --git a/hosted/skins/kolabsys/images/favicon.png b/hosted/skins/kolabsys/images/favicon.png
deleted file mode 120000
index 7ee1ea8..0000000
--- a/hosted/skins/kolabsys/images/favicon.png
+++ /dev/null
@@ -1 +0,0 @@
-../../../../public_html/skins/default/images/favicon.png
\ No newline at end of file
diff --git a/hosted/skins/kolabsys/images/info.png b/hosted/skins/kolabsys/images/info.png
deleted file mode 120000
index 8e1fec7..0000000
--- a/hosted/skins/kolabsys/images/info.png
+++ /dev/null
@@ -1 +0,0 @@
-../../../../public_html/skins/default/images/info.png
\ No newline at end of file
diff --git a/hosted/skins/kolabsys/images/loading.gif b/hosted/skins/kolabsys/images/loading.gif
deleted file mode 120000
index aaca76f..0000000
--- a/hosted/skins/kolabsys/images/loading.gif
+++ /dev/null
@@ -1 +0,0 @@
-../../../../public_html/skins/default/images/loading.gif
\ No newline at end of file
diff --git a/hosted/skins/kolabsys/style.css b/hosted/skins/kolabsys/style.css
deleted file mode 120000
index 6f5490d..0000000
--- a/hosted/skins/kolabsys/style.css
+++ /dev/null
@@ -1 +0,0 @@
-../../../public_html/skins/default/style.css
\ No newline at end of file
diff --git a/hosted/skins/kolabsys/templates/signup.html b/hosted/skins/kolabsys/templates/signup.html
deleted file mode 100644
index 7c756ae..0000000
--- a/hosted/skins/kolabsys/templates/signup.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta charset="utf-8" />
-    <title>Sign Up For Hosted Kolab</title>
-    <link rel="stylesheet" href="{$skin_path}style.css" />
-    <link rel="stylesheet" href="{$skin_path}hosted.css" />
-    <link rel="shortcut icon" type="image/png" href="{$skin_path}images/favicon.png" />
-    <script src="js/jquery.min.js"></script>
-    <script src="js/kolab_admin.js"></script>
-    <script type="text/javascript" src="https://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script>
-    <script src="js/kolab_hosted.js"></script>
-    <script src="{$skin_path}ui.js"></script>
-</head>
-<body>
-    <div id="taskcontent" class="signup">{$form}</div>
-
-    {$script}
-</body>
-</html>
diff --git a/hosted/skins/kolabsys/ui.js b/hosted/skins/kolabsys/ui.js
deleted file mode 120000
index 625253b..0000000
--- a/hosted/skins/kolabsys/ui.js
+++ /dev/null
@@ -1 +0,0 @@
-../../../public_html/skins/default/ui.js
\ No newline at end of file
diff --git a/hosted/skins/minimal/hosted.css b/hosted/skins/minimal/hosted.css
deleted file mode 120000
index a66df34..0000000
--- a/hosted/skins/minimal/hosted.css
+++ /dev/null
@@ -1 +0,0 @@
-../default/hosted.css
\ No newline at end of file
diff --git a/hosted/skins/minimal/images/error.png b/hosted/skins/minimal/images/error.png
deleted file mode 120000
index fe901a7..0000000
--- a/hosted/skins/minimal/images/error.png
+++ /dev/null
@@ -1 +0,0 @@
-../../../../public_html/skins/default/images/error.png
\ No newline at end of file
diff --git a/hosted/skins/minimal/images/favicon.png b/hosted/skins/minimal/images/favicon.png
deleted file mode 120000
index 7ee1ea8..0000000
--- a/hosted/skins/minimal/images/favicon.png
+++ /dev/null
@@ -1 +0,0 @@
-../../../../public_html/skins/default/images/favicon.png
\ No newline at end of file
diff --git a/hosted/skins/minimal/images/info.png b/hosted/skins/minimal/images/info.png
deleted file mode 120000
index 8e1fec7..0000000
--- a/hosted/skins/minimal/images/info.png
+++ /dev/null
@@ -1 +0,0 @@
-../../../../public_html/skins/default/images/info.png
\ No newline at end of file
diff --git a/hosted/skins/minimal/images/loading.gif b/hosted/skins/minimal/images/loading.gif
deleted file mode 120000
index aaca76f..0000000
--- a/hosted/skins/minimal/images/loading.gif
+++ /dev/null
@@ -1 +0,0 @@
-../../../../public_html/skins/default/images/loading.gif
\ No newline at end of file
diff --git a/hosted/skins/minimal/style.css b/hosted/skins/minimal/style.css
deleted file mode 120000
index 6f5490d..0000000
--- a/hosted/skins/minimal/style.css
+++ /dev/null
@@ -1 +0,0 @@
-../../../public_html/skins/default/style.css
\ No newline at end of file
diff --git a/hosted/skins/minimal/templates/footer.html b/hosted/skins/minimal/templates/footer.html
deleted file mode 100644
index 4b6bac5..0000000
--- a/hosted/skins/minimal/templates/footer.html
+++ /dev/null
@@ -1 +0,0 @@
-This is a service offered by <a href="http://kolabsys.com">Kolab Systems</a>.
diff --git a/hosted/skins/minimal/templates/signup.html b/hosted/skins/minimal/templates/signup.html
deleted file mode 100644
index 961dbf8..0000000
--- a/hosted/skins/minimal/templates/signup.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta charset="utf-8" />
-    <title>Sign Up For Hosted Kolab</title>
-    <link rel="stylesheet" href="{$skin_path}style.css" />
-    <link rel="stylesheet" href="{$skin_path}hosted.css" />
-    <link rel="shortcut icon" type="image/png" href="{$skin_path}images/favicon.png" />
-    <script src="js/jquery.min.js"></script>
-    <script src="js/kolab_admin.js"></script>
-    <script type="text/javascript" src="https://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script>
-    <script src="js/kolab_hosted.js"></script>
-    <script src="{$skin_path}ui.js"></script>
-</head>
-<body>
-    <div id="taskcontent" class="signup">{$form}</div>
-
-    <div id="footer">
-        {include file="footer.html"}
-    </div>
-{$script}
-</body>
-</html>
diff --git a/hosted/skins/minimal/ui.js b/hosted/skins/minimal/ui.js
deleted file mode 120000
index 625253b..0000000
--- a/hosted/skins/minimal/ui.js
+++ /dev/null
@@ -1 +0,0 @@
-../../../public_html/skins/default/ui.js
\ No newline at end of file
diff --git a/lib/client/kolab_client_task_signup.php b/lib/client/kolab_client_task_signup.php
new file mode 100644
index 0000000..a320216
--- /dev/null
+++ b/lib/client/kolab_client_task_signup.php
@@ -0,0 +1,327 @@
+<?php
+/*
+ +--------------------------------------------------------------------------+
+ | This file is part of the Kolab Web Admin Panel                           |
+ |                                                                          |
+ | Copyright (C) 2011-2012, Kolab Systems AG                                |
+ |                                                                          |
+ | This program is free software: you can redistribute it and/or modify     |
+ | it under the terms of the GNU Affero General Public License as published |
+ | by the Free Software Foundation, either version 3 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 Affero General Public License for more details.                      |
+ |                                                                          |
+ | You should have received a copy of the GNU Affero General Public License |
+ | along with this program. If not, see <http://www.gnu.org/licenses/>      |
+ +--------------------------------------------------------------------------+
+ | Author: Aleksander Machniak <machniak at kolabsys.com>                      |
+ | Author: Torsten Grote <grote at kolabsys.com>                               |
+ +--------------------------------------------------------------------------+
+*/
+
+require_once('recaptchalib.php');
+
+class kolab_client_task_signup extends kolab_client_task
+{
+    protected $ajax_only = true;
+
+    /**
+     * Overwrite Main execution.
+     */
+    public function run()
+    {
+        // don't set any cookies
+        ini_set('session.use_cookies', '0');
+
+        // Initialize locales
+        $this->locale_init();
+
+        // Assign self to template variable
+        $this->output->assign('engine', $this);
+
+        // Run security checks
+        // TODO figure out to reenable this
+//        $this->input_checks();
+
+        $action = $this->get_input('action', 'GET');
+
+        if ($action) {
+            $method = 'action_' . $action;
+            if (method_exists($this, $method)) {
+                $this->$method();
+            }
+        }
+        else if (method_exists($this, 'action_default')) {
+            $this->action_default();
+        }
+    }
+
+    private function login($domain=NULL)
+    {
+        if(is_null($domain)) {
+            $this->domain = $this->config_get('primary_domain');
+        }
+
+        // Login ($result is a kolab_client_api_result instance)
+        $result = $this->api->login($this->config_get('bind_dn'), $this->config_get('bind_pw'), $this->domain);
+
+        // Set the session token we got in the API client instance, so subsequent
+        // API calls are made in the same session.
+        $this->token = $result->get('session_token');
+        $this->api->set_session_token($this->token);
+    }
+
+    public function action_default()
+    {
+        $this->login();
+
+        $data = $this->get_input('data', 'POST');
+        $form = $this->user_form($data);
+
+        // add captcha
+        $publickey = $this->config_get('recaptcha_public_key');
+
+        if (!empty($publickey)) {
+            // TODO find a less dirty way to add captcha into form
+            $form = preg_replace('/<\/tbody>/', '<tr><td class="label">'.$this->translate('signup.captcha').'</td><td class="value"><div id="recaptcha_div"></div></td></tr></tbody>', $form);
+
+            // load captcha
+            $form .= '
+                <script type="text/javascript">
+                    Recaptcha.create("'.$publickey.'", "recaptcha_div", {theme: "red"});
+                </script>';
+        }
+
+        $this->output->assign('form', $form);
+        $this->output->set_object('taskcontent', $form);
+    }
+
+    // check if user already exists
+    public function action_check_user($data = array()) {
+        if(count($data) == 0) $data = $this->get_input('data', 'POST');
+
+        $this->login($data['domain']);
+
+        // Assemble mail attribute
+        $mail = $data['uid'].'@'.$data['domain'];
+
+        $post = array('search' => array('mail' => array('value' => $mail) ) );
+        $result = $this->api->post('users.list', null, $post);
+
+        if($result->get('count') > 0) {
+            $this->output->command('update_user_info("signup.userexists", "uid")');
+            return false;
+        }
+
+        $this->output->command('update_user_info("", "uid")');
+        return true;
+    }
+
+    public function action_add_user() {
+        $data = $this->get_input('data', 'POST');
+
+        $private_key = $this->config_get('recaptcha_private_key');
+
+        if (!empty($private_key)) {
+            // Check for valid CAPTCHA
+            $resp = recaptcha_check_answer(
+                        $private_key,
+                        $_SERVER['REMOTE_ADDR'],
+                        $data['recaptcha_challenge_field'],
+                        $data['recaptcha_response_field']
+            );
+
+            if (!$resp->is_valid) {
+                // What happens when the CAPTCHA was entered incorrectly
+                $this->output->command('display_message', "The reCAPTCHA wasn't entered correctly. Please reload and try it again.", 'error');
+                return;
+            }
+
+        }
+
+        // Check again for user availability before adding user
+        // this also logs into the API
+        // TODO perform security check on value of $data['uid'] and $data['domain']
+        if(!$this->action_check_user($data)) {
+            $this->output->command('form_value_error', 'uid');
+            return;
+        }
+
+        $this->api->get('system.select_domain', array('domain', $data['domain']));
+
+        // Remove domain from $data before adding user
+        unset($data['domain']);
+
+        $data['cn'] = $this->api->post('form_value.generate', $data);
+
+        // Add user
+        $result = $this->api->post('user.add', null, $data);
+
+        if (array_key_exists('error_code', $result)) {
+            $this->output->command('display_message', 'internalerror', 'error');
+            return;
+        } else {
+            $this->output->set_object('taskcontent', 'signup.usercreated');
+        }
+    }
+
+    private function user_form($data = array()) {
+        $attribs['id'] = 'signup-form';
+
+        $fields_map = array(
+            'type_id'                   => 'other',
+            'givenname'                 => 'other',
+            'sn'                        => 'other',
+            'cn'                        => 'other',
+            'mailalternateaddress'      => 'other',
+            'uid'                       => 'other',
+            'domain'                    => 'other',
+            'userpassword'              => 'other',
+            'userpassword2'             => 'other',
+            'mail'                      => 'other',
+            'alias'                     => 'other',
+        );
+
+        // Prepare fields
+        list($fields, $types, $type) = $this->form_prepare('user', $data, array('userpassword2'), 'hosted');
+        
+        // Show only required fields
+        foreach ($fields as $field_name => $field_attrs) {
+            if(!array_key_exists('required', $field_attrs) or $field_attrs['required'] != 'true') {
+                unset($fields[$field_name]);
+            }
+        }
+
+        // Add user type id selector
+        $accttypes = array();
+        foreach ($types as $idx => $elem) {
+            if($elem['used_for'] == 'hosted') {
+                $accttypes[$idx] = array('value' => $idx, 'content' => $elem['name']);
+            }
+        }
+
+        $fields['type_id'] = array(
+            'section'  => 'personal',
+            'type'     => kolab_form::INPUT_SELECT,
+            'options'  => $accttypes,
+            'onchange' => "kadm.change_user_type()",
+        );
+        
+        // Add object type field
+        $fields['object_type'] = array(
+            'type'     => kolab_form::INPUT_HIDDEN,
+            'value'    => 'user',
+        );
+ 
+        // Add available domains
+        $fields['domain'] = array(
+            'type'     => kolab_form::INPUT_SELECT,
+            'options'  => $this->get_domains(),
+            'onchange' => 'kadm.check_user_availability()',
+        );
+
+        // Check for user availability
+        $fields['uid']['onchange'] = 'kadm.check_user_availability()';
+
+        // Hide cn field
+        if (isset($fields['cn'])) {
+            // TODO use type info from user types table
+            $fields['cn']['type'] = kolab_form::INPUT_HIDDEN;
+
+            // TODO auto generate value again with indirect API call
+            $fields['cn']['value'] = 'temporary fake cn';
+            $fields['sn']['onchange'] = '';
+            $fields['givenname']['onchange'] = '';
+        }
+
+        // Add password confirmation
+        if (isset($fields['userpassword'])) {
+            $fields['userpassword2'] = $fields['userpassword'];
+            $fields['userpassword2']['onchange'] = 'password_match()';
+        }
+        
+        // Change field labels for hosted case
+        $fields['uid']['label'] = 'signup.username';
+        $fields['mail']['label'] = 'signup.futuremail';
+        if(isset($fields['mailalternateaddress'])) $fields['mailalternateaddress']['label'] = 'signup.mailalternateaddress';
+        $fields['domain']['label'] = 'signup.domain';
+
+        // Create form object and populate with fields
+        $form = $this->form_create('user', $attribs, array('other'), $fields, $fields_map, $data, true);
+
+        $form->set_title($this->translate('signup.formtitle'));
+
+        $this->output->add_translation('user.password.mismatch', 'user.add.success', 'signup.wronguid');
+
+        return $form->output();
+    }
+
+    protected function get_domains() {
+        // Get a list of domains ($domains again is a kolab_client_api_result instance)
+        $domains_list = $this->api->get('domains.list')->get('list');
+
+        if (empty($domains_list)) {
+            return array();
+        }
+
+        // The domain name attribute (the name of the LDAP attribute that holds the actual domain name space)
+        // is configurable as well. Provide a fallback.
+        $domain_name_attribute = $this->config->get('ldap','domain_name_attribute');
+        if (empty($domain_name_attribute)) {
+            $domain_name_attribute = 'associateddomain';
+        }
+
+        // Placeholder for the domain names in this deployment
+        $domain_names = array();
+
+        foreach ($domains_list as $domain_dn => $domain_attrs) {
+            // If $domain_attrs[$domain_name_attribute] is an array, the primary domain name space
+            // is the first value in the array.
+            if (is_array($domain_attrs[$domain_name_attribute])) {
+                $_domain_names = $domain_attrs[$domain_name_attribute];
+                $domain_name = array_shift($domain_attrs[$domain_name_attribute]);
+            } else {
+                $_domain_names = (array)($domain_attrs[$domain_name_attribute]);
+                $domain_name = $domain_attrs[$domain_name_attribute];
+            }
+
+            $parent_domain_only = $this->config->get($domain_name, 'hosted_parent_domain_only');
+
+            if (!empty($parent_domain_only) && in_array(strtolower($parent_domain_only), array('1', 'yes', 'true'))) {
+                $domain_names = array_merge($domain_names, array($domain_name));
+            } else {
+                $domain_names = array_merge($domain_names, $_domain_names);
+            }
+        }
+
+        // prepare array with proper key ids for form building
+        foreach ($domain_names as $domain) {
+            $domain_form_names[$domain] = $domain;
+        }
+
+        return $domain_form_names;
+    }
+
+    /**
+     * Overrides config_get() from kolab_client_task
+     * Returns configuration option value for hosting.
+     *
+     * @param string $name      Option name
+     * @param mixed  $fallback  Default value
+     * @param int    $type      Value type (one of Conf class constants)
+     *
+     * @return mixed Option value
+     */
+    public function config_get($name, $fallback = null, $type = null)
+    {
+        $value = $this->config->get('kolab_hosting', $name, $type);
+        if($value === null) {
+            $value = parent::config_get($name, $fallback, $type);
+        }
+        return $value !== null ? $value : $fallback;
+    }
+}
diff --git a/lib/functions.php b/lib/functions.php
index 8c74cf7..9761358 100644
--- a/lib/functions.php
+++ b/lib/functions.php
@@ -47,7 +47,6 @@ $include_path = INSTALL_PATH . PATH_SEPARATOR;
 $include_path .= INSTALL_PATH . '/client' . PATH_SEPARATOR;
 $include_path .= INSTALL_PATH . '/api' . PATH_SEPARATOR;
 $include_path .= INSTALL_PATH . '/ext' . PATH_SEPARATOR;
-$include_path .= INSTALL_PATH . '/hosted' . PATH_SEPARATOR;
 $include_path .= ini_get('include_path');
 
 if (set_include_path($include_path) === false) {
diff --git a/lib/hosted/kolab_client_task_signup.php b/lib/hosted/kolab_client_task_signup.php
deleted file mode 100644
index 6ebcdd9..0000000
--- a/lib/hosted/kolab_client_task_signup.php
+++ /dev/null
@@ -1,327 +0,0 @@
-<?php
-/*
- +--------------------------------------------------------------------------+
- | This file is part of the Kolab Web Admin Panel                           |
- |                                                                          |
- | Copyright (C) 2011-2012, Kolab Systems AG                                |
- |                                                                          |
- | This program is free software: you can redistribute it and/or modify     |
- | it under the terms of the GNU Affero General Public License as published |
- | by the Free Software Foundation, either version 3 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 Affero General Public License for more details.                      |
- |                                                                          |
- | You should have received a copy of the GNU Affero General Public License |
- | along with this program. If not, see <http://www.gnu.org/licenses/>      |
- +--------------------------------------------------------------------------+
- | Author: Aleksander Machniak <machniak at kolabsys.com>                      |
- | Author: Torsten Grote <grote at kolabsys.com>                               |
- +--------------------------------------------------------------------------+
-*/
-
-require_once('hosted/recaptchalib.php');
-
-class kolab_client_task_signup extends kolab_client_task
-{
-    protected $ajax_only = true;
-
-    /**
-     * Overwrite Main execution.
-     */
-    public function run()
-    {
-        // don't set any cookies
-        ini_set('session.use_cookies', '0');
-
-        // Initialize locales
-        $this->locale_init();
-
-        // Assign self to template variable
-        $this->output->assign('engine', $this);
-
-        // Run security checks
-        // TODO figure out to reenable this
-//        $this->input_checks();
-
-        $action = $this->get_input('action', 'GET');
-
-        if ($action) {
-            $method = 'action_' . $action;
-            if (method_exists($this, $method)) {
-                $this->$method();
-            }
-        }
-        else if (method_exists($this, 'action_default')) {
-            $this->action_default();
-        }
-    }
-
-    private function login($domain=NULL)
-    {
-        if(is_null($domain)) {
-            $this->domain = $this->config_get('primary_domain');
-        }
-
-        // Login ($result is a kolab_client_api_result instance)
-        $result = $this->api->login($this->config_get('bind_dn'), $this->config_get('bind_pw'), $this->domain);
-
-        // Set the session token we got in the API client instance, so subsequent
-        // API calls are made in the same session.
-        $this->token = $result->get('session_token');
-        $this->api->set_session_token($this->token);
-    }
-
-    public function action_default()
-    {
-        $this->login();
-
-        $data = $this->get_input('data', 'POST');
-        $form = $this->user_form($data);
-
-        // add captcha
-        $publickey = $this->config_get('recaptcha_public_key');
-
-        if (!empty($publickey)) {
-            // TODO find a less dirty way to add captcha into form
-            $form = preg_replace('/<\/tbody>/', '<tr><td class="label">'.$this->translate('signup.captcha').'</td><td class="value"><div id="recaptcha_div"></div></td></tr></tbody>', $form);
-
-            // load captcha
-            $form .= '
-                <script type="text/javascript">
-                    Recaptcha.create("'.$publickey.'", "recaptcha_div", {theme: "red"});
-                </script>';
-        }
-
-        $this->output->assign('form', $form);
-        $this->output->set_object('taskcontent', $form);
-    }
-
-    // check if user already exists
-    public function action_check_user($data = array()) {
-        if(count($data) == 0) $data = $this->get_input('data', 'POST');
-
-        $this->login($data['domain']);
-
-        // Assemble mail attribute
-        $mail = $data['uid'].'@'.$data['domain'];
-
-        $post = array('search' => array('mail' => array('value' => $mail) ) );
-        $result = $this->api->post('users.list', null, $post);
-
-        if($result->get('count') > 0) {
-            $this->output->command('update_user_info("signup.userexists", "uid")');
-            return false;
-        }
-
-        $this->output->command('update_user_info("", "uid")');
-        return true;
-    }
-
-    public function action_add_user() {
-        $data = $this->get_input('data', 'POST');
-
-        $private_key = $this->config_get('recaptcha_private_key');
-
-        if (!empty($private_key)) {
-            // Check for valid CAPTCHA
-            $resp = recaptcha_check_answer(
-                        $private_key,
-                        $_SERVER['REMOTE_ADDR'],
-                        $data['recaptcha_challenge_field'],
-                        $data['recaptcha_response_field']
-            );
-
-            if (!$resp->is_valid) {
-                // What happens when the CAPTCHA was entered incorrectly
-                $this->output->command('display_message', "The reCAPTCHA wasn't entered correctly. Please reload and try it again.", 'error');
-                return;
-            }
-
-        }
-
-        // Check again for user availability before adding user
-        // this also logs into the API
-        // TODO perform security check on value of $data['uid'] and $data['domain']
-        if(!$this->action_check_user($data)) {
-            $this->output->command('form_value_error', 'uid');
-            return;
-        }
-
-        $this->api->get('system.select_domain', array('domain', $data['domain']));
-
-        // Remove domain from $data before adding user
-        unset($data['domain']);
-
-        $data['cn'] = $this->api->post('form_value.generate', $data);
-
-        // Add user
-        $result = $this->api->post('user.add', null, $data);
-
-        if (array_key_exists('error_code', $result)) {
-            $this->output->command('display_message', 'internalerror', 'error');
-            return;
-        } else {
-            $this->output->set_object('taskcontent', 'signup.usercreated');
-        }
-    }
-
-    private function user_form($data = array()) {
-        $attribs['id'] = 'signup-form';
-
-        $fields_map = array(
-            'type_id'                   => 'other',
-            'givenname'                 => 'other',
-            'sn'                        => 'other',
-            'cn'                        => 'other',
-            'mailalternateaddress'      => 'other',
-            'uid'                       => 'other',
-            'domain'                    => 'other',
-            'userpassword'              => 'other',
-            'userpassword2'             => 'other',
-            'mail'                      => 'other',
-            'alias'                     => 'other',
-        );
-
-        // Prepare fields
-        list($fields, $types, $type) = $this->form_prepare('user', $data, array('userpassword2'), 'hosted');
-        
-        // Show only required fields
-        foreach ($fields as $field_name => $field_attrs) {
-            if(!array_key_exists('required', $field_attrs) or $field_attrs['required'] != 'true') {
-                unset($fields[$field_name]);
-            }
-        }
-
-        // Add user type id selector
-        $accttypes = array();
-        foreach ($types as $idx => $elem) {
-            if($elem['used_for'] == 'hosted') {
-                $accttypes[$idx] = array('value' => $idx, 'content' => $elem['name']);
-            }
-        }
-
-        $fields['type_id'] = array(
-            'section'  => 'personal',
-            'type'     => kolab_form::INPUT_SELECT,
-            'options'  => $accttypes,
-            'onchange' => "kadm.change_user_type()",
-        );
-        
-        // Add object type field
-        $fields['object_type'] = array(
-            'type'     => kolab_form::INPUT_HIDDEN,
-            'value'    => 'user',
-        );
- 
-        // Add available domains
-        $fields['domain'] = array(
-            'type'     => kolab_form::INPUT_SELECT,
-            'options'  => $this->get_domains(),
-            'onchange' => 'kadm.check_user_availability()',
-        );
-
-        // Check for user availability
-        $fields['uid']['onchange'] = 'kadm.check_user_availability()';
-
-        // Hide cn field
-        if (isset($fields['cn'])) {
-            // TODO use type info from user types table
-            $fields['cn']['type'] = kolab_form::INPUT_HIDDEN;
-
-            // TODO auto generate value again with indirect API call
-            $fields['cn']['value'] = 'temporary fake cn';
-            $fields['sn']['onchange'] = '';
-            $fields['givenname']['onchange'] = '';
-        }
-
-        // Add password confirmation
-        if (isset($fields['userpassword'])) {
-            $fields['userpassword2'] = $fields['userpassword'];
-            $fields['userpassword2']['onchange'] = 'password_match()';
-        }
-        
-        // Change field labels for hosted case
-        $fields['uid']['label'] = 'signup.username';
-        $fields['mail']['label'] = 'signup.futuremail';
-        if(isset($fields['mailalternateaddress'])) $fields['mailalternateaddress']['label'] = 'signup.mailalternateaddress';
-        $fields['domain']['label'] = 'signup.domain';
-
-        // Create form object and populate with fields
-        $form = $this->form_create('user', $attribs, array('other'), $fields, $fields_map, $data, true);
-
-        $form->set_title($this->translate('signup.formtitle'));
-
-        $this->output->add_translation('user.password.mismatch', 'user.add.success', 'signup.wronguid');
-
-        return $form->output();
-    }
-
-    protected function get_domains() {
-        // Get a list of domains ($domains again is a kolab_client_api_result instance)
-        $domains_list = $this->api->get('domains.list')->get('list');
-
-        if (empty($domains_list)) {
-            return array();
-        }
-
-        // The domain name attribute (the name of the LDAP attribute that holds the actual domain name space)
-        // is configurable as well. Provide a fallback.
-        $domain_name_attribute = $this->config->get('ldap','domain_name_attribute');
-        if (empty($domain_name_attribute)) {
-            $domain_name_attribute = 'associateddomain';
-        }
-
-        // Placeholder for the domain names in this deployment
-        $domain_names = array();
-
-        foreach ($domains_list as $domain_dn => $domain_attrs) {
-            // If $domain_attrs[$domain_name_attribute] is an array, the primary domain name space
-            // is the first value in the array.
-            if (is_array($domain_attrs[$domain_name_attribute])) {
-                $_domain_names = $domain_attrs[$domain_name_attribute];
-                $domain_name = array_shift($domain_attrs[$domain_name_attribute]);
-            } else {
-                $_domain_names = (array)($domain_attrs[$domain_name_attribute]);
-                $domain_name = $domain_attrs[$domain_name_attribute];
-            }
-
-            $parent_domain_only = $this->config->get($domain_name, 'hosted_parent_domain_only');
-
-            if (!empty($parent_domain_only) && in_array(strtolower($parent_domain_only), array('1', 'yes', 'true'))) {
-                $domain_names = array_merge($domain_names, array($domain_name));
-            } else {
-                $domain_names = array_merge($domain_names, $_domain_names);
-            }
-        }
-
-        // prepare array with proper key ids for form building
-        foreach ($domain_names as $domain) {
-            $domain_form_names[$domain] = $domain;
-        }
-
-        return $domain_form_names;
-    }
-
-    /**
-     * Overrides config_get() from kolab_client_task
-     * Returns configuration option value for hosting.
-     *
-     * @param string $name      Option name
-     * @param mixed  $fallback  Default value
-     * @param int    $type      Value type (one of Conf class constants)
-     *
-     * @return mixed Option value
-     */
-    public function config_get($name, $fallback = null, $type = null)
-    {
-        $value = $this->config->get('kolab_hosting', $name, $type);
-        if($value === null) {
-            $value = parent::config_get($name, $fallback, $type);
-        }
-        return $value !== null ? $value : $fallback;
-    }
-}
diff --git a/lib/hosted/recaptchalib.php b/lib/hosted/recaptchalib.php
deleted file mode 100644
index 32c4f4d..0000000
--- a/lib/hosted/recaptchalib.php
+++ /dev/null
@@ -1,277 +0,0 @@
-<?php
-/*
- * This is a PHP library that handles calling reCAPTCHA.
- *    - Documentation and latest version
- *          http://recaptcha.net/plugins/php/
- *    - Get a reCAPTCHA API Key
- *          https://www.google.com/recaptcha/admin/create
- *    - Discussion group
- *          http://groups.google.com/group/recaptcha
- *
- * Copyright (c) 2007 reCAPTCHA -- http://recaptcha.net
- * AUTHORS:
- *   Mike Crawford
- *   Ben Maurer
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-/**
- * The reCAPTCHA server URL's
- */
-define("RECAPTCHA_API_SERVER", "http://www.google.com/recaptcha/api");
-define("RECAPTCHA_API_SECURE_SERVER", "https://www.google.com/recaptcha/api");
-define("RECAPTCHA_VERIFY_SERVER", "www.google.com");
-
-/**
- * Encodes the given data into a query string format
- * @param $data - array of string elements to be encoded
- * @return string - encoded request
- */
-function _recaptcha_qsencode ($data) {
-        $req = "";
-        foreach ( $data as $key => $value )
-                $req .= $key . '=' . urlencode( stripslashes($value) ) . '&';
-
-        // Cut the last '&'
-        $req=substr($req,0,strlen($req)-1);
-        return $req;
-}
-
-
-
-/**
- * Submits an HTTP POST to a reCAPTCHA server
- * @param string $host
- * @param string $path
- * @param array $data
- * @param int port
- * @return array response
- */
-function _recaptcha_http_post($host, $path, $data, $port = 80) {
-
-        $req = _recaptcha_qsencode ($data);
-
-        $http_request  = "POST $path HTTP/1.0\r\n";
-        $http_request .= "Host: $host\r\n";
-        $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n";
-        $http_request .= "Content-Length: " . strlen($req) . "\r\n";
-        $http_request .= "User-Agent: reCAPTCHA/PHP\r\n";
-        $http_request .= "\r\n";
-        $http_request .= $req;
-
-        $response = '';
-        if( false == ( $fs = @fsockopen($host, $port, $errno, $errstr, 10) ) ) {
-                die ('Could not open socket');
-        }
-
-        fwrite($fs, $http_request);
-
-        while ( !feof($fs) )
-                $response .= fgets($fs, 1160); // One TCP-IP packet
-        fclose($fs);
-        $response = explode("\r\n\r\n", $response, 2);
-
-        return $response;
-}
-
-
-
-/**
- * Gets the challenge HTML (javascript and non-javascript version).
- * This is called from the browser, and the resulting reCAPTCHA HTML widget
- * is embedded within the HTML form it was called from.
- * @param string $pubkey A public key for reCAPTCHA
- * @param string $error The error given by reCAPTCHA (optional, default is null)
- * @param boolean $use_ssl Should the request be made over ssl? (optional, default is false)
-
- * @return string - The HTML to be embedded in the user's form.
- */
-function recaptcha_get_html ($pubkey, $error = null, $use_ssl = false)
-{
-	if ($pubkey == null || $pubkey == '') {
-		die ("To use reCAPTCHA you must get an API key from <a href='https://www.google.com/recaptcha/admin/create'>https://www.google.com/recaptcha/admin/create</a>");
-	}
-	
-	if ($use_ssl) {
-                $server = RECAPTCHA_API_SECURE_SERVER;
-        } else {
-                $server = RECAPTCHA_API_SERVER;
-        }
-
-        $errorpart = "";
-        if ($error) {
-           $errorpart = "&error=" . $error;
-        }
-        return '<script type="text/javascript" src="'. $server . '/challenge?k=' . $pubkey . $errorpart . '"></script>
-
-	<noscript>
-  		<iframe src="'. $server . '/noscript?k=' . $pubkey . $errorpart . '" height="300" width="500" frameborder="0"></iframe><br/>
-  		<textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
-  		<input type="hidden" name="recaptcha_response_field" value="manual_challenge"/>
-	</noscript>';
-}
-
-
-
-
-/**
- * A ReCaptchaResponse is returned from recaptcha_check_answer()
- */
-class ReCaptchaResponse {
-        var $is_valid;
-        var $error;
-}
-
-
-/**
-  * Calls an HTTP POST function to verify if the user's guess was correct
-  * @param string $privkey
-  * @param string $remoteip
-  * @param string $challenge
-  * @param string $response
-  * @param array $extra_params an array of extra variables to post to the server
-  * @return ReCaptchaResponse
-  */
-function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $extra_params = array())
-{
-	if ($privkey == null || $privkey == '') {
-		die ("To use reCAPTCHA you must get an API key from <a href='https://www.google.com/recaptcha/admin/create'>https://www.google.com/recaptcha/admin/create</a>");
-	}
-
-	if ($remoteip == null || $remoteip == '') {
-		die ("For security reasons, you must pass the remote ip to reCAPTCHA");
-	}
-
-	
-	
-        //discard spam submissions
-        if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) {
-                $recaptcha_response = new ReCaptchaResponse();
-                $recaptcha_response->is_valid = false;
-                $recaptcha_response->error = 'incorrect-captcha-sol';
-                return $recaptcha_response;
-        }
-
-        $response = _recaptcha_http_post (RECAPTCHA_VERIFY_SERVER, "/recaptcha/api/verify",
-                                          array (
-                                                 'privatekey' => $privkey,
-                                                 'remoteip' => $remoteip,
-                                                 'challenge' => $challenge,
-                                                 'response' => $response
-                                                 ) + $extra_params
-                                          );
-
-        $answers = explode ("\n", $response [1]);
-        $recaptcha_response = new ReCaptchaResponse();
-
-        if (trim ($answers [0]) == 'true') {
-                $recaptcha_response->is_valid = true;
-        }
-        else {
-                $recaptcha_response->is_valid = false;
-                $recaptcha_response->error = $answers [1];
-        }
-        return $recaptcha_response;
-
-}
-
-/**
- * gets a URL where the user can sign up for reCAPTCHA. If your application
- * has a configuration page where you enter a key, you should provide a link
- * using this function.
- * @param string $domain The domain where the page is hosted
- * @param string $appname The name of your application
- */
-function recaptcha_get_signup_url ($domain = null, $appname = null) {
-	return "https://www.google.com/recaptcha/admin/create?" .  _recaptcha_qsencode (array ('domains' => $domain, 'app' => $appname));
-}
-
-function _recaptcha_aes_pad($val) {
-	$block_size = 16;
-	$numpad = $block_size - (strlen ($val) % $block_size);
-	return str_pad($val, strlen ($val) + $numpad, chr($numpad));
-}
-
-/* Mailhide related code */
-
-function _recaptcha_aes_encrypt($val,$ky) {
-	if (! function_exists ("mcrypt_encrypt")) {
-		die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed.");
-	}
-	$mode=MCRYPT_MODE_CBC;   
-	$enc=MCRYPT_RIJNDAEL_128;
-	$val=_recaptcha_aes_pad($val);
-	return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
-}
-
-
-function _recaptcha_mailhide_urlbase64 ($x) {
-	return strtr(base64_encode ($x), '+/', '-_');
-}
-
-/* gets the reCAPTCHA Mailhide url for a given email, public key and private key */
-function recaptcha_mailhide_url($pubkey, $privkey, $email) {
-	if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) {
-		die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " .
-		     "you can do so at <a href='http://www.google.com/recaptcha/mailhide/apikey'>http://www.google.com/recaptcha/mailhide/apikey</a>");
-	}
-	
-
-	$ky = pack('H*', $privkey);
-	$cryptmail = _recaptcha_aes_encrypt ($email, $ky);
-	
-	return "http://www.google.com/recaptcha/mailhide/d?k=" . $pubkey . "&c=" . _recaptcha_mailhide_urlbase64 ($cryptmail);
-}
-
-/**
- * gets the parts of the email to expose to the user.
- * eg, given johndoe at example,com return ["john", "example.com"].
- * the email is then displayed as john... at example.com
- */
-function _recaptcha_mailhide_email_parts ($email) {
-	$arr = preg_split("/@/", $email );
-
-	if (strlen ($arr[0]) <= 4) {
-		$arr[0] = substr ($arr[0], 0, 1);
-	} else if (strlen ($arr[0]) <= 6) {
-		$arr[0] = substr ($arr[0], 0, 3);
-	} else {
-		$arr[0] = substr ($arr[0], 0, 4);
-	}
-	return $arr;
-}
-
-/**
- * Gets html to display an email address given a public an private key.
- * to get a key, go to:
- *
- * http://www.google.com/recaptcha/mailhide/apikey
- */
-function recaptcha_mailhide_html($pubkey, $privkey, $email) {
-	$emailparts = _recaptcha_mailhide_email_parts ($email);
-	$url = recaptcha_mailhide_url ($pubkey, $privkey, $email);
-	
-	return htmlentities($emailparts[0]) . "<a href='" . htmlentities ($url) .
-		"' onclick=\"window.open('" . htmlentities ($url) . "', '', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=300'); return false;\" title=\"Reveal this e-mail address\">...</a>@" . htmlentities ($emailparts [1]);
-
-}
-
-
-?>
diff --git a/lib/recaptchalib.php b/lib/recaptchalib.php
new file mode 100644
index 0000000..32c4f4d
--- /dev/null
+++ b/lib/recaptchalib.php
@@ -0,0 +1,277 @@
+<?php
+/*
+ * This is a PHP library that handles calling reCAPTCHA.
+ *    - Documentation and latest version
+ *          http://recaptcha.net/plugins/php/
+ *    - Get a reCAPTCHA API Key
+ *          https://www.google.com/recaptcha/admin/create
+ *    - Discussion group
+ *          http://groups.google.com/group/recaptcha
+ *
+ * Copyright (c) 2007 reCAPTCHA -- http://recaptcha.net
+ * AUTHORS:
+ *   Mike Crawford
+ *   Ben Maurer
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * The reCAPTCHA server URL's
+ */
+define("RECAPTCHA_API_SERVER", "http://www.google.com/recaptcha/api");
+define("RECAPTCHA_API_SECURE_SERVER", "https://www.google.com/recaptcha/api");
+define("RECAPTCHA_VERIFY_SERVER", "www.google.com");
+
+/**
+ * Encodes the given data into a query string format
+ * @param $data - array of string elements to be encoded
+ * @return string - encoded request
+ */
+function _recaptcha_qsencode ($data) {
+        $req = "";
+        foreach ( $data as $key => $value )
+                $req .= $key . '=' . urlencode( stripslashes($value) ) . '&';
+
+        // Cut the last '&'
+        $req=substr($req,0,strlen($req)-1);
+        return $req;
+}
+
+
+
+/**
+ * Submits an HTTP POST to a reCAPTCHA server
+ * @param string $host
+ * @param string $path
+ * @param array $data
+ * @param int port
+ * @return array response
+ */
+function _recaptcha_http_post($host, $path, $data, $port = 80) {
+
+        $req = _recaptcha_qsencode ($data);
+
+        $http_request  = "POST $path HTTP/1.0\r\n";
+        $http_request .= "Host: $host\r\n";
+        $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n";
+        $http_request .= "Content-Length: " . strlen($req) . "\r\n";
+        $http_request .= "User-Agent: reCAPTCHA/PHP\r\n";
+        $http_request .= "\r\n";
+        $http_request .= $req;
+
+        $response = '';
+        if( false == ( $fs = @fsockopen($host, $port, $errno, $errstr, 10) ) ) {
+                die ('Could not open socket');
+        }
+
+        fwrite($fs, $http_request);
+
+        while ( !feof($fs) )
+                $response .= fgets($fs, 1160); // One TCP-IP packet
+        fclose($fs);
+        $response = explode("\r\n\r\n", $response, 2);
+
+        return $response;
+}
+
+
+
+/**
+ * Gets the challenge HTML (javascript and non-javascript version).
+ * This is called from the browser, and the resulting reCAPTCHA HTML widget
+ * is embedded within the HTML form it was called from.
+ * @param string $pubkey A public key for reCAPTCHA
+ * @param string $error The error given by reCAPTCHA (optional, default is null)
+ * @param boolean $use_ssl Should the request be made over ssl? (optional, default is false)
+
+ * @return string - The HTML to be embedded in the user's form.
+ */
+function recaptcha_get_html ($pubkey, $error = null, $use_ssl = false)
+{
+	if ($pubkey == null || $pubkey == '') {
+		die ("To use reCAPTCHA you must get an API key from <a href='https://www.google.com/recaptcha/admin/create'>https://www.google.com/recaptcha/admin/create</a>");
+	}
+	
+	if ($use_ssl) {
+                $server = RECAPTCHA_API_SECURE_SERVER;
+        } else {
+                $server = RECAPTCHA_API_SERVER;
+        }
+
+        $errorpart = "";
+        if ($error) {
+           $errorpart = "&error=" . $error;
+        }
+        return '<script type="text/javascript" src="'. $server . '/challenge?k=' . $pubkey . $errorpart . '"></script>
+
+	<noscript>
+  		<iframe src="'. $server . '/noscript?k=' . $pubkey . $errorpart . '" height="300" width="500" frameborder="0"></iframe><br/>
+  		<textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
+  		<input type="hidden" name="recaptcha_response_field" value="manual_challenge"/>
+	</noscript>';
+}
+
+
+
+
+/**
+ * A ReCaptchaResponse is returned from recaptcha_check_answer()
+ */
+class ReCaptchaResponse {
+        var $is_valid;
+        var $error;
+}
+
+
+/**
+  * Calls an HTTP POST function to verify if the user's guess was correct
+  * @param string $privkey
+  * @param string $remoteip
+  * @param string $challenge
+  * @param string $response
+  * @param array $extra_params an array of extra variables to post to the server
+  * @return ReCaptchaResponse
+  */
+function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $extra_params = array())
+{
+	if ($privkey == null || $privkey == '') {
+		die ("To use reCAPTCHA you must get an API key from <a href='https://www.google.com/recaptcha/admin/create'>https://www.google.com/recaptcha/admin/create</a>");
+	}
+
+	if ($remoteip == null || $remoteip == '') {
+		die ("For security reasons, you must pass the remote ip to reCAPTCHA");
+	}
+
+	
+	
+        //discard spam submissions
+        if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) {
+                $recaptcha_response = new ReCaptchaResponse();
+                $recaptcha_response->is_valid = false;
+                $recaptcha_response->error = 'incorrect-captcha-sol';
+                return $recaptcha_response;
+        }
+
+        $response = _recaptcha_http_post (RECAPTCHA_VERIFY_SERVER, "/recaptcha/api/verify",
+                                          array (
+                                                 'privatekey' => $privkey,
+                                                 'remoteip' => $remoteip,
+                                                 'challenge' => $challenge,
+                                                 'response' => $response
+                                                 ) + $extra_params
+                                          );
+
+        $answers = explode ("\n", $response [1]);
+        $recaptcha_response = new ReCaptchaResponse();
+
+        if (trim ($answers [0]) == 'true') {
+                $recaptcha_response->is_valid = true;
+        }
+        else {
+                $recaptcha_response->is_valid = false;
+                $recaptcha_response->error = $answers [1];
+        }
+        return $recaptcha_response;
+
+}
+
+/**
+ * gets a URL where the user can sign up for reCAPTCHA. If your application
+ * has a configuration page where you enter a key, you should provide a link
+ * using this function.
+ * @param string $domain The domain where the page is hosted
+ * @param string $appname The name of your application
+ */
+function recaptcha_get_signup_url ($domain = null, $appname = null) {
+	return "https://www.google.com/recaptcha/admin/create?" .  _recaptcha_qsencode (array ('domains' => $domain, 'app' => $appname));
+}
+
+function _recaptcha_aes_pad($val) {
+	$block_size = 16;
+	$numpad = $block_size - (strlen ($val) % $block_size);
+	return str_pad($val, strlen ($val) + $numpad, chr($numpad));
+}
+
+/* Mailhide related code */
+
+function _recaptcha_aes_encrypt($val,$ky) {
+	if (! function_exists ("mcrypt_encrypt")) {
+		die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed.");
+	}
+	$mode=MCRYPT_MODE_CBC;   
+	$enc=MCRYPT_RIJNDAEL_128;
+	$val=_recaptcha_aes_pad($val);
+	return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
+}
+
+
+function _recaptcha_mailhide_urlbase64 ($x) {
+	return strtr(base64_encode ($x), '+/', '-_');
+}
+
+/* gets the reCAPTCHA Mailhide url for a given email, public key and private key */
+function recaptcha_mailhide_url($pubkey, $privkey, $email) {
+	if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) {
+		die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " .
+		     "you can do so at <a href='http://www.google.com/recaptcha/mailhide/apikey'>http://www.google.com/recaptcha/mailhide/apikey</a>");
+	}
+	
+
+	$ky = pack('H*', $privkey);
+	$cryptmail = _recaptcha_aes_encrypt ($email, $ky);
+	
+	return "http://www.google.com/recaptcha/mailhide/d?k=" . $pubkey . "&c=" . _recaptcha_mailhide_urlbase64 ($cryptmail);
+}
+
+/**
+ * gets the parts of the email to expose to the user.
+ * eg, given johndoe at example,com return ["john", "example.com"].
+ * the email is then displayed as john... at example.com
+ */
+function _recaptcha_mailhide_email_parts ($email) {
+	$arr = preg_split("/@/", $email );
+
+	if (strlen ($arr[0]) <= 4) {
+		$arr[0] = substr ($arr[0], 0, 1);
+	} else if (strlen ($arr[0]) <= 6) {
+		$arr[0] = substr ($arr[0], 0, 3);
+	} else {
+		$arr[0] = substr ($arr[0], 0, 4);
+	}
+	return $arr;
+}
+
+/**
+ * Gets html to display an email address given a public an private key.
+ * to get a key, go to:
+ *
+ * http://www.google.com/recaptcha/mailhide/apikey
+ */
+function recaptcha_mailhide_html($pubkey, $privkey, $email) {
+	$emailparts = _recaptcha_mailhide_email_parts ($email);
+	$url = recaptcha_mailhide_url ($pubkey, $privkey, $email);
+	
+	return htmlentities($emailparts[0]) . "<a href='" . htmlentities ($url) .
+		"' onclick=\"window.open('" . htmlentities ($url) . "', '', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=300'); return false;\" title=\"Reveal this e-mail address\">...</a>@" . htmlentities ($emailparts [1]);
+
+}
+
+
+?>
diff --git a/public_html/index.php b/public_html/index.php
index 3e78361..1f9f1ed 100644
--- a/public_html/index.php
+++ b/public_html/index.php
@@ -29,7 +29,7 @@ require_once '../lib/functions.php';
 // starting task
 $task = kolab_utils::get_input('task', kolab_utils::REQUEST_GET);
 
-if (!$task) {
+if (!$task || $task == 'signup') {
     $task = 'main';
 }
 
diff --git a/public_html/js/kolab_hosted.js b/public_html/js/kolab_hosted.js
new file mode 100644
index 0000000..9f717ae
--- /dev/null
+++ b/public_html/js/kolab_hosted.js
@@ -0,0 +1,120 @@
+/*
+ +--------------------------------------------------------------------------+
+ | This file is part of the Kolab Web Admin Panel                           |
+ |                                                                          |
+ | Copyright (C) 2011-2012, Kolab Systems AG                                |
+ |                                                                          |
+ | This program is free software: you can redistribute it and/or modify     |
+ | it under the terms of the GNU Affero General Public License as published |
+ | by the Free Software Foundation, either version 3 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 Affero General Public License for more details.                      |
+ |                                                                          |
+ | You should have received a copy of the GNU Affero General Public License |
+ | along with kadm program. If not, see <http://www.gnu.org/licenses/>      |
+ +--------------------------------------------------------------------------+
+ | Author: Torsten Grote <grote at kolabsys.com>                               |
+ +--------------------------------------------------------------------------+
+*/
+
+// overwrite user_save() function
+kadm.user_save = function(reload, section)
+{
+    var data = kadm.serialize_form('#'+this.env.form_id);
+
+    // check email address
+    if(typeof data.mailalternateaddress != 'undefined' && !isValidEmailAddress(data.mailalternateaddress)) {
+        kadm.display_message('signup.wrongmailalternateaddress', 'error');
+        kadm.form_value_error('mailalternateaddress');
+        return;
+    }
+
+    if (!kadm.check_required_fields(data)) {
+      kadm.display_message('form.required.empty', 'error');
+      return;
+    }
+
+    // check password
+    if (data.userpassword != data.userpassword2) {
+      kadm.display_message('user.password.mismatch', 'error');
+      kadm.form_value_error('userpassword2');
+      return;
+    }
+    delete data['userpassword2'];
+
+    kadm.http_post('signup.add_user', {data: data});
+};
+
+kadm.change_user_type = function()
+{
+    var data = kadm.serialize_form('#'+this.env.form_id);
+
+    kadm.http_post('signup.default', {data: data});
+};
+
+kadm.check_user_availability = function()
+{
+    // get form data and build new email address
+    var data = kadm.serialize_form('#signup-form');
+    var mail = data['uid'] + '@' + data['domain'];
+
+    if(isValidEmailAddress(mail)) {
+        // update future mail form field
+        $('input[name="mail"]').val(mail);
+
+        // check if user with that email address already exists
+        kadm.http_post('signup.check_user', {data: data});
+    } else {
+        kadm.update_user_info('signup.wronguid', 'uid');
+    }
+};
+
+kadm.update_user_info = function(msg, part)
+{
+    var span_id = 'availability';
+    if(!part.localeCompare('userpassword')) {
+        span_id = 'pass_match';
+    }
+
+    if (msg) {
+        msg = kadm.t(msg);
+    }
+
+    // display message next to form field
+    if($('span[id="'+span_id+'"]').length) {
+        // update existing span area
+        $('span[id="'+span_id+'"]').html(msg);
+    }
+    else {
+        // add span area and add message
+        $('input[name="'+part+'"]').after(' <span id="'+span_id+'" class="form_error">' + msg + '</span>');
+    }
+
+    // enable/disable button
+    if(msg == '') {
+        $('input[type="button"]').removeAttr("disabled");
+    } else {
+        $('input[type="button"]').attr("disabled", "disabled");
+    }
+};
+
+
+function password_match()
+{
+    if($('input[name="userpassword"]').val().localeCompare($('input[name="userpassword2"]').val())) {
+        kadm.update_user_info('user.password.mismatch', 'userpassword');
+    }
+    else {
+        kadm.update_user_info('', 'userpassword');
+    }
+}
+
+// TODO use form.validate api call for that
+function isValidEmailAddress(emailAddress) {
+    var pattern = new RegExp(/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i);
+    return pattern.test(emailAddress);
+};
diff --git a/public_html/skins/default/hosted.css b/public_html/skins/default/hosted.css
new file mode 100644
index 0000000..694d19d
--- /dev/null
+++ b/public_html/skins/default/hosted.css
@@ -0,0 +1,45 @@
+body {
+  background: #f0f0f0;
+  text-align: center;
+}
+
+#bodybox {
+  display: inline-block;
+  width: 900px;
+  text-align: left;
+}
+
+#logo {
+  width: 462px;
+  height: 100px;
+  position: relative;
+  margin: 10px;
+  cursor: default;
+  background: url(images/logo_kolab.png) 0 0 no-repeat;
+}
+
+#content {
+  padding: 15px;
+}
+
+#taskcontent {
+  display: inline-block;
+  margin-top: 10px;
+  text-align: center;
+}
+
+form#signup-form {
+  padding-top: 10px;
+}
+
+td.label {
+  vertical-align: top;
+}
+
+table.form td {
+    text-align: left;
+}
+
+#footer {
+  text-align: center;
+}
diff --git a/public_html/skins/default/images/logo_kolab.png b/public_html/skins/default/images/logo_kolab.png
new file mode 100644
index 0000000..0c06ff1
Binary files /dev/null and b/public_html/skins/default/images/logo_kolab.png differ
diff --git a/public_html/skins/default/templates/signup.html b/public_html/skins/default/templates/signup.html
new file mode 100644
index 0000000..66a649a
--- /dev/null
+++ b/public_html/skins/default/templates/signup.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8" />
+    <title>{$engine->translate('signup.headline')}</title>
+    <link rel="stylesheet" href="{$skin_path}style.css" />
+    <link rel="stylesheet" href="{$skin_path}hosted.css" />
+    <link rel="shortcut icon" type="image/png" href="{$skin_path}images/favicon.png" />
+    <script src="js/jquery.min.js"></script>
+    <script src="js/kolab_admin.js"></script>
+    <script type="text/javascript" src="https://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script>
+    <script src="js/kolab_hosted.js"></script>
+    <script src="{$skin_path}ui.js"></script>
+</head>
+<body>
+    <div id="bodybox">
+        <div id="logo"></div>
+        <div id="content">
+            <div id="task_content" class="signup">
+                <h1>{$engine->translate('signup.headline')}</h1>
+                <p>{$engine->translate('signup.intro1')}</p>
+                <p>{$engine->translate('signup.intro2')}</p>
+                <div id="taskcontent" class="signup">{$form}</div>
+            </div>
+        </div>
+        <div id="footer">
+            {include file="signup_footer.html"}
+        </div>
+    </div>
+{$script}
+</body>
+</html>
diff --git a/public_html/skins/default/templates/signup_footer.html b/public_html/skins/default/templates/signup_footer.html
new file mode 100644
index 0000000..24d972d
--- /dev/null
+++ b/public_html/skins/default/templates/signup_footer.html
@@ -0,0 +1 @@
+{$engine->translate('signup.footer')}
diff --git a/public_html/skins/default/ui.js b/public_html/skins/default/ui.js
index c0dc901..45bfd51 100644
--- a/public_html/skins/default/ui.js
+++ b/public_html/skins/default/ui.js
@@ -153,7 +153,7 @@ function ui_resize()
   var h = $('#content').height();
   // resize UI elements
   if (h - 22 > 380) {
-    $('#taskcontent').css({'min-height': h - 22});
+    $('#taskcontent:not(.signup)').css({'min-height': h - 22});
   }
 };
 
diff --git a/public_html/skins/kolabsys/hosted.css b/public_html/skins/kolabsys/hosted.css
new file mode 100644
index 0000000..427d57d
--- /dev/null
+++ b/public_html/skins/kolabsys/hosted.css
@@ -0,0 +1,26 @@
+body {
+  background: #F3F3F3;
+  text-align: center;
+}
+
+#taskcontent {
+  width: 680px;
+  min-height: 100px;
+  max-height: 460px;
+}
+
+form#signup-form {
+  padding-top: 10px;
+}
+
+td.value input {
+    width: 250px;
+}
+
+td.label {
+  vertical-align: top;
+}
+
+table.form td {
+    text-align: left;
+}
diff --git a/public_html/skins/kolabsys/style.css b/public_html/skins/kolabsys/style.css
new file mode 120000
index 0000000..2bc3133
--- /dev/null
+++ b/public_html/skins/kolabsys/style.css
@@ -0,0 +1 @@
+../default/style.css
\ No newline at end of file
diff --git a/public_html/skins/kolabsys/templates/signup.html b/public_html/skins/kolabsys/templates/signup.html
new file mode 100644
index 0000000..7c756ae
--- /dev/null
+++ b/public_html/skins/kolabsys/templates/signup.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8" />
+    <title>Sign Up For Hosted Kolab</title>
+    <link rel="stylesheet" href="{$skin_path}style.css" />
+    <link rel="stylesheet" href="{$skin_path}hosted.css" />
+    <link rel="shortcut icon" type="image/png" href="{$skin_path}images/favicon.png" />
+    <script src="js/jquery.min.js"></script>
+    <script src="js/kolab_admin.js"></script>
+    <script type="text/javascript" src="https://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script>
+    <script src="js/kolab_hosted.js"></script>
+    <script src="{$skin_path}ui.js"></script>
+</head>
+<body>
+    <div id="taskcontent" class="signup">{$form}</div>
+
+    {$script}
+</body>
+</html>
diff --git a/public_html/skins/kolabsys/ui.js b/public_html/skins/kolabsys/ui.js
new file mode 120000
index 0000000..b845f3f
--- /dev/null
+++ b/public_html/skins/kolabsys/ui.js
@@ -0,0 +1 @@
+../default/ui.js
\ No newline at end of file
diff --git a/public_html/skins/minimal/hosted.css b/public_html/skins/minimal/hosted.css
new file mode 120000
index 0000000..a66df34
--- /dev/null
+++ b/public_html/skins/minimal/hosted.css
@@ -0,0 +1 @@
+../default/hosted.css
\ No newline at end of file
diff --git a/public_html/skins/minimal/style.css b/public_html/skins/minimal/style.css
new file mode 120000
index 0000000..2bc3133
--- /dev/null
+++ b/public_html/skins/minimal/style.css
@@ -0,0 +1 @@
+../default/style.css
\ No newline at end of file
diff --git a/public_html/skins/minimal/templates/signup.html b/public_html/skins/minimal/templates/signup.html
new file mode 100644
index 0000000..ccf16b8
--- /dev/null
+++ b/public_html/skins/minimal/templates/signup.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8" />
+    <title>Sign Up For Hosted Kolab</title>
+    <link rel="stylesheet" href="{$skin_path}style.css" />
+    <link rel="stylesheet" href="{$skin_path}hosted.css" />
+    <link rel="shortcut icon" type="image/png" href="{$skin_path}images/favicon.png" />
+    <script src="js/jquery.min.js"></script>
+    <script src="js/kolab_admin.js"></script>
+    <script type="text/javascript" src="https://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script>
+    <script src="js/kolab_hosted.js"></script>
+    <script src="{$skin_path}ui.js"></script>
+</head>
+<body>
+    <div id="taskcontent" class="signup">{$form}</div>
+
+    <div id="footer">
+        {include file="signup_footer.html"}
+    </div>
+{$script}
+</body>
+</html>
diff --git a/public_html/skins/minimal/templates/signup_footer.html b/public_html/skins/minimal/templates/signup_footer.html
new file mode 100644
index 0000000..4b6bac5
--- /dev/null
+++ b/public_html/skins/minimal/templates/signup_footer.html
@@ -0,0 +1 @@
+This is a service offered by <a href="http://kolabsys.com">Kolab Systems</a>.
diff --git a/public_html/skins/minimal/ui.js b/public_html/skins/minimal/ui.js
new file mode 120000
index 0000000..b845f3f
--- /dev/null
+++ b/public_html/skins/minimal/ui.js
@@ -0,0 +1 @@
+../default/ui.js
\ No newline at end of file


commit f779682278bce833d204a6d3ab98151d08c6ca72
Author: Torsten Grote <grote at kolabsys.com>
Date:   Wed Sep 26 14:44:10 2012 +0200

    updated/fixed about pages

diff --git a/public_html/skins/default/style.css b/public_html/skins/default/style.css
index 9b68183..db3e596 100644
--- a/public_html/skins/default/style.css
+++ b/public_html/skins/default/style.css
@@ -929,3 +929,18 @@ fieldset.tabbed
 #grouplist table {
   width: 100%;
 }
+
+/**** About pages ****/
+
+td.yes {
+  background-color: #B5FFAB;
+  font-style: italic;
+  text-align: center;
+}
+
+td.no {
+  background-color: #FF0000;
+  color: #FFFFFF;
+  font-weight: bold;
+  text-align: center;
+}
diff --git a/public_html/skins/default/templates/about.html b/public_html/skins/default/templates/about.html
index 48dc9b3..2db6af3 100644
--- a/public_html/skins/default/templates/about.html
+++ b/public_html/skins/default/templates/about.html
@@ -41,7 +41,10 @@
                 <a href="http://lists.kolab.org/mailman/listinfo/kolab-devel">Development mailing list</a>, for enhancement requests, compatibility issues and general inquiries. (<a href="mailto:kolab-devel-subscribe at kolab.org">subscribe</a>, <a href="http://lists.kolab.org/pipermail/kolab-devel/">archives</a>)
             </li>
             <li>
-                Our <a href="http://issues.kolab.org/">issue tracker</a>.
+                Our <a href="https://issues.kolab.org/">issue tracker</a>,
+            </li>
+            <li>
+                The <a href="http://freenode.net/using_the_network.shtml">IRC Channel</a> on freenode: <a href="irc://irc.freenode.net/kolab">#kolab</a>.
             </li>
         </ul>
     </p>
diff --git a/public_html/skins/default/templates/about_kolab.html b/public_html/skins/default/templates/about_kolab.html
index ab0845d..baa81b5 100644
--- a/public_html/skins/default/templates/about_kolab.html
+++ b/public_html/skins/default/templates/about_kolab.html
@@ -7,9 +7,10 @@ a <a href="http://fsfe.org/about/basics/freesoftware">Free Software</a>
 Personal Information Management (PIM) solution developed by the
 <a href="http://www.kolab.org/">Kolab Community</a>. Any installation of
 the Kolab Groupware Solution would normally include one or more of these
-servers, in combination with a variety of clients for its users.
+servers, in combination with <a href="http://kolab.org/clients">a
+variety of clients</a> for its users.
 If you are interested in the technology, you can read more about
-it <a href="/admin/kolab/technology.php">here</a>.
+it on the dedicated <a href="/admin/kolab/technology.php">technology page</a>.
 </p>
 
 <h2>Why Kolab?</h2>
@@ -32,7 +33,10 @@ To summarize just some points, Kolab is your solution, if
     <li>strategic issues are important to you, e.g. you need sovereignty of the software you deploy;</li>
     <li>your users should be fully productive whether they are on- or offline;</li>
     <li>you want the flexibility to create solutions for your specialised needs;</li>
-    <li>you want Free Software/Open Source, but also want the professional support without having to resort to proprietary lock-in.</li>
+    <li>you want <a href="http://fsfe.org/about/basics/freesoftware">Free Software</a>/Open Source,
+        but also want the <a href="http://kolabsys.com/">professional support</a> without having
+        to resort to proprietary lock-in.
+    </li>
 </ul>
 
 <h2>More Information</h2>
@@ -40,7 +44,7 @@ To summarize just some points, Kolab is your solution, if
 <ul>
     <li><a href="http://www.kolab.org">Kolab Community Web Site</a></li>
     <li><a href="http://wiki.kolab.org">Kolab Community Wiki</a></li>
-    <li><a href="http://blogs.fsfe.org/greve/?p=431">Article:  'The Kolab Story' by Georg Greve</a></li>
+    <li><a href="http://blogs.fsfe.org/greve/?p=431">Article: 'The Kolab Story' by Georg Greve</a></li>
     <li><a href="http://files.kolabsys.com/Public/Handouts/">Handouts for the Kolab Groupware Solution by Kolab Systems</a></li>
 </ul>
 
@@ -75,22 +79,46 @@ self-supported On Demand support models.</b>
 </p>
 
 <p>
-    <table cellspacing="2" cellpadding="2" border="1" align="center">
-        <tr><td></td><td><b>This edition</b></td><td><em>With SLA by Kolab Systems</em></b></td></tr>
-        <tr><td>Support channels</td><td><b>Wiki & public mailing lists only</b></td><td><em>Kolab Systems Ticketing</em></td></tr>
-        <tr><td>Guaranteed response times</td><td><b>NO</b></td><td><em>YES</em></td></tr>
-        <tr><td>Access to Certified Kolab</td><td><b>NO</b></td><td><em>YES</em></td></tr>
-        <tr><td>Warranties</td><td><b>NO</b></td><td><em>YES</em></td></tr>
-        <tr><td>Access to custom versions</td><td><b>NO</b></td><td><em>YES</em></td></tr>
+    <table id="support" cellspacing="0" cellpadding="5" border="1" align="center">
+        <tr>
+            <td></td>
+            <td><b>This edition</b></td>
+            <td><em>With SLA by Kolab Systems</em></b></td>
+        </tr>
+        <tr>
+            <td>Support channels</td>
+            <td><b>Wiki & public mailing lists only</b></td>
+            <td><em>Kolab Systems Ticketing</em></td>
+        </tr>
+        <tr>
+            <td>Guaranteed response times</td>
+            <td class="no">NO</td>
+            <td class="yes">YES</td>
+        </tr>
+        <tr>
+            <td>Access to Certified Kolab</td>
+            <td class="no">NO</td>
+            <td class="yes">YES</td>
+        </tr>
+        <tr>
+            <td>Warranties</td>
+            <td class="no">NO</td>
+            <td class="yes">YES</td>
+        </tr>
+        <tr>
+            <td>Access to custom versions</td>
+            <td class="no">NO</td>
+            <td class="yes">YES</td>
+        </tr>
     </table>
 </p>
 
 <p>
 Kolab Systems provides an overview of all available
-<a href="http://kolabsys.com/index.php/solutions/101">Service Level Agreements (SLA)</a>.
+<a href="http://kolabsys.com/pricing">Service Level Agreements (SLA)</a>.
 To upgrade your installation to Certified Kolab, please contact
-<a href="getkolab at kolabsys.com">getkolab at kolabsys.com</a> or visit our
-<a href="http://kolabsys.com/index.php/component/form/Get%20Kolab?form_id=2">web page</a>.
+<a href="mailto:getkolab at kolabsys.com">getkolab at kolabsys.com</a> or visit our
+<a href="http://kolabsys.com/contact">web page</a>.
 </p>
 
 </div>
diff --git a/public_html/skins/default/templates/about_kolabsys.html b/public_html/skins/default/templates/about_kolabsys.html
index 0716ee2..c9d7cc4 100644
--- a/public_html/skins/default/templates/about_kolabsys.html
+++ b/public_html/skins/default/templates/about_kolabsys.html
@@ -20,18 +20,18 @@ support and warranties to enable productivity and collaboration.
 <h2>Looking for professional support for your Kolab Groupware Solution?</h2>
 
 <p>
-Kolab Systems provides a wide range of <a href="http://kolabsys.com/index.php/solutions/101">Service Level Agreements (SLA)</a>
+Kolab Systems provides a wide range of <a href="http://kolabsys.com/pricing">Service Level Agreements (SLA)</a>
 which range from fully self-supported installations to maintained installations with
 reaction times as low as 1 hour. These are provided exclusively on Kolab Systems' Certified Kolab
 edition. To upgrade your installation to Certified Kolab, please contact
-<a href="getkolab at kolabsys.com">getkolab at kolabsys.com</a> or visit our
-<a href="http://kolabsys.com/index.php/component/form/Get%20Kolab?form_id=2">web page</a>.
+<a href="mailto:getkolab at kolabsys.com">getkolab at kolabsys.com</a> or visit our
+<a href="http://kolabsys.com/contact">web page</a>.
 </p>
 
 <h2>Our Development Partners</h2>
 
 <p>
-In continuously improvingall parts of the Kolab Groupware Solution,
+In continuously improving all parts of the Kolab Groupware Solution,
 Kolab Systems is working closely with development partners, such as (in alphabetical order):
 </p>
 
@@ -40,15 +40,15 @@ Kolab Systems is working closely with development partners, such as (in alphabet
     <li><a href="/admin/kolab/intevation.php">Intevation</a></li>
     <li><a href="/admin/kolab/kdab.php">KDAB</a></li>
     <li><a href="http://www.libertech.fr/default.htm">Libertech</a></li>
-    <li>...and all the community contributors.
+    <li>...and all the community contributors.</li>
 </ul>
 
 <p>
-<b>Thank you for being part of this awesome community!</b>
+    <b>Thank you for being part of this awesome community!</b>
 </p>
 
 <p>If <b>you</b> want to join the community, please go to
-<a href="http://kolab.org">kolab.org</a> and <a href="http://wiki.kolab.org">wiki.kolab.org</a>.
+    <a href="http://kolab.org">kolab.org</a> and <a href="http://wiki.kolab.org">wiki.kolab.org</a>.
 </p>
 
 </div>
diff --git a/public_html/skins/default/templates/about_technology.html b/public_html/skins/default/templates/about_technology.html
index bd95c28..4f082e6 100644
--- a/public_html/skins/default/templates/about_technology.html
+++ b/public_html/skins/default/templates/about_technology.html
@@ -20,16 +20,16 @@ For this server, Free Software communities and projects that the Kolab Community
     <li><a href="http://www.port389.org/">389 Directory Server</a> (LDAP Server)</li>
     <li><a href="http://www.postfix.org/">Postfix</a> (MTA)</li>
     <li><a href="http://spamassassin.apache.org/">SpamAssassin</a> (SPAM Filter)</li>
-    <li><a href="">Z-Push</a> (ActiveSync Support)</li>
+    <li><a href="http://www.syncroton.org/">Syncroton</a> (ActiveSync Support)</li>
     <li><a href="http://www.roundcube.net/">Roundcube</a> (Web client)</li>
-    <li><a href="http://www.python.org/">Python</a> (Server side scripting & glue)</li>
-    <li><a href="http://www.php.net/">PHP</a> (Server side scripting & glue)</li>
+    <li><a href="http://www.python.org/">Python</a> (Server side scripting & glue)</li>
+    <li><a href="http://www.php.net/">PHP</a> (Server side scripting & glue)</li>
     <li><a href="http://smarty.php.net/">Smarty Template Engine</a> (This interface)</li>
 </ul>
 </p>
 
 <p>
-For the clients, Free Software communities and projects that the Kolab Community is a part of and builds upon include:
+For the <a href="http://kolab.org/clients">clients</a>, Free Software communities and projects that the Kolab Community is a part of and builds upon include:
 <ul>
     <li><a href="http://www.kde.org/">K Desktop Environment (KDE)</a></li>
     <li><a href="http://www.mozilla.org/">Mozilla (Thunderbird & Lightning)</a></li>
@@ -64,7 +64,7 @@ and <a href="http://kolab.org">http://kolab.org</a>.
 
 <p>
 The Kolab project owes a great deal of thanks to the people of the
-<a href="http://www.openpkg.org/">OpenPKG</a> project. OpenPKG allows Kolab to run on many
+<a href="http://www.openpkg.org/">OpenPKG</a> project. OpenPKG allowed Kolab to run on many
 diverse platforms in a reliable and predictable manner, by providing a common, easy-to-install,
 cross-platform base on which to build the server.
 </p>
@@ -86,11 +86,11 @@ Radley has worked closely with the Kolab community to help develop the Kolab2 st
 
 <p>
 A list of additional Outlook plug-ins that provide interoperability with the Kolab server
-is <a href="http://www.kolab.org/kolab-plugins.html">maintained on the Kolab website</a>.
+is <a href="http://kolab.org/clients#outlook">maintained on the Kolab website</a>.
 </p>
 
 <p>
-There is also a <a href="http://www.kolab.org/webclient.html">web-based client</a>
+There was also a <a href="http://kolab.org/clients#roundcube">web-based client</a>
 in development which provides full groupware functionality to mobile users through a web interface.
 It allows users to access their email, calendars, tasks, etc. from anywhere in the world,
 by simply connecting through a standard web browser. The web client would not have been
@@ -131,6 +131,7 @@ Principal contributors of the Kolab Groupware Solution are (in alphabetical orde
     <li>Achim Frank</li>
     <li>Michel Boyer de la Giroday</li>
     <li>Georg Greve</li>
+    <li>Torsten Grote</li>
     <li>Steffen Hansen</li>
     <li>Bernhard Herzog</li>
     <li>Martin Konold</li>


commit 5157a2520ec855602ec17fd51f256e03134c5bb4
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Sep 26 13:45:33 2012 +0200

    Fix UI resize - don't display scrollbar in #taskcontent

diff --git a/public_html/skins/default/ui.js b/public_html/skins/default/ui.js
index 9332051..c0dc901 100644
--- a/public_html/skins/default/ui.js
+++ b/public_html/skins/default/ui.js
@@ -152,8 +152,8 @@ function ui_resize()
 {
   var h = $('#content').height();
   // resize UI elements
-  if (h > 100) {
-    $('#taskcontent').height(h - 22);
+  if (h - 22 > 380) {
+    $('#taskcontent').css({'min-height': h - 22});
   }
 };
 


commit 0771e7815392143a81967ae3a8bac84db68bee57
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Sep 26 13:34:40 2012 +0200

    Fix empty capabilities on hosted (#1043)

diff --git a/lib/hosted/kolab_client_task_signup.php b/lib/hosted/kolab_client_task_signup.php
index b1dbe6d..6ebcdd9 100644
--- a/lib/hosted/kolab_client_task_signup.php
+++ b/lib/hosted/kolab_client_task_signup.php
@@ -63,11 +63,11 @@ class kolab_client_task_signup extends kolab_client_task
     private function login($domain=NULL)
     {
         if(is_null($domain)) {
-            $domain = $this->config_get('primary_domain');
+            $this->domain = $this->config_get('primary_domain');
         }
 
         // Login ($result is a kolab_client_api_result instance)
-        $result = $this->api->login($this->config_get('bind_dn'), $this->config_get('bind_pw'), $domain);
+        $result = $this->api->login($this->config_get('bind_dn'), $this->config_get('bind_pw'), $this->domain);
 
         // Set the session token we got in the API client instance, so subsequent
         // API calls are made in the same session.
diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php
index 47dce4a..02ca4a5 100644
--- a/lib/kolab_client_task.php
+++ b/lib/kolab_client_task.php
@@ -630,7 +630,7 @@ class kolab_client_task
             }
         }
 
-        $domain = $_SESSION['user']['domain'];
+        $domain = $this->domain ? $this->domain : $_SESSION['user']['domain'];
 
         return !$all ? $list[$domain] : $list;
     }


commit 64511c13fca4d884ec9a6812adfd3ec1cf33377f
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Sep 26 12:39:44 2012 +0200

    Fix typo in last commit

diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php
index 1ab8e9d..47dce4a 100644
--- a/lib/kolab_client_task.php
+++ b/lib/kolab_client_task.php
@@ -566,7 +566,7 @@ class kolab_client_task
                 }
             }
 
-            $this->cache[$cache_idx] = empty($list) ? $list : array();
+            $this->cache[$cache_idx] = !empty($list) ? $list : array();
 
             Log::trace("kolab_client_task::${type}_types() returns: " . var_export($list, true));
         }


commit 2628b041a5dc6fce6c4c829facfdb356bfd15bdd
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Sep 26 12:16:07 2012 +0200

    Check result type to prevent from PHP warnings

diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php
index f3f0aa0..1ab8e9d 100644
--- a/lib/kolab_client_task.php
+++ b/lib/kolab_client_task.php
@@ -558,7 +558,7 @@ class kolab_client_task
             $result = $this->api->post($type . '_types.list');
             $list   = $result->get('list');
 
-            if (!empty($used_for)) {
+            if (!empty($used_for) && is_array($list)) {
                 foreach ($list as $type_id => $type_attrs) {
                     if ($type_attrs['used_for'] != $used_for) {
                         unset($list[$type_id]);
@@ -566,7 +566,7 @@ class kolab_client_task
                 }
             }
 
-            $this->cache[$cache_idx] = $list;
+            $this->cache[$cache_idx] = empty($list) ? $list : array();
 
             Log::trace("kolab_client_task::${type}_types() returns: " . var_export($list, true));
         }


commit 75dc13451ca14d927ee2580cd371160e1b725660
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Sep 26 11:27:54 2012 +0200

    Types management (read-only), code improvements here and there

diff --git a/lib/client/kolab_client_task_domain.php b/lib/client/kolab_client_task_domain.php
index 8c8228c..3a9f6a9 100644
--- a/lib/client/kolab_client_task_domain.php
+++ b/lib/client/kolab_client_task_domain.php
@@ -118,7 +118,7 @@ class kolab_client_task_domain extends kolab_client_task
             $next  = $page < $pages ? $page + 1 : 0;
 
             $count_str = kolab_html::span(array(
-                'content' => $this->translate('domain.list.records', $start, $end, $count)), true);
+                'content' => $this->translate('list.records', $start, $end, $count)), true);
             $prev = kolab_html::a(array(
                 'class' => 'prev' . ($prev ? '' : ' disabled'),
                 'href'  => '#',
@@ -170,6 +170,7 @@ class kolab_client_task_domain extends kolab_client_task
             'foot'  => $foot,
         ));
 
+        $this->output->command('set_watermark', 'taskcontent');
         $this->output->set_env('search_request', $search_request ? base64_encode(serialize($search_request)) : null);
         $this->output->set_env('list_page', $page);
         $this->output->set_env('list_count', $count);
@@ -288,47 +289,10 @@ class kolab_client_task_domain extends kolab_client_task
 
         $form->set_title(kolab_html::escape($title));
 
-        $this->output->add_translation('domain.add.success', 'domain.edit.success', 'domain.delete.success');
-
         return $form->output();
     }
 
     /**
-     * Returns list of domain types.
-     *
-     * @return array List of domain types
-     */
-    public function domain_types()
-    {
-        $result = array(
-                1 => array(
-                        'key' => 'standard',
-                        'name' => 'Standard domain',
-                        'description' => 'A standard domain name space',
-                        'attributes' => array(
-                                'auto_form_fields' => array(),
-                                'form_fields' => array(
-                                        'associateddomain' => array(
-                                                'type' => 'list',
-                                            ),
-                                        'inetdomainbasedn' => array(
-                                                'optional' => 'true',
-                                            ),
-                                    ),
-                                'fields' => array(
-                                        'objectclass' => array(
-                                                'top',
-                                                'domainrelatedobject',
-                                            ),
-                                    ),
-                            ),
-                    ),
-            );
-        //console("domain_types() \$result", $result);
-        return $result;
-    }
-
-    /**
      * Users search form.
      *
      * @return string HTML output of the form
diff --git a/lib/client/kolab_client_task_group.php b/lib/client/kolab_client_task_group.php
index a628d50..fada00c 100644
--- a/lib/client/kolab_client_task_group.php
+++ b/lib/client/kolab_client_task_group.php
@@ -118,7 +118,7 @@ class kolab_client_task_group extends kolab_client_task
             $next  = $page < $pages ? $page + 1 : 0;
 
             $count_str = kolab_html::span(array(
-                'content' => $this->translate('group.list.records', $start, $end, $count)), true);
+                'content' => $this->translate('list.records', $start, $end, $count)), true);
             $prev = kolab_html::a(array(
                 'class' => 'prev' . ($prev ? '' : ' disabled'),
                 'href'  => '#',
@@ -162,6 +162,7 @@ class kolab_client_task_group extends kolab_client_task
             'foot'  => $foot,
         ));
 
+        $this->output->command('set_watermark', 'taskcontent');
         $this->output->set_env('search_request', $search_request ? base64_encode(serialize($search_request)) : null);
         $this->output->set_env('list_page', $page);
         $this->output->set_env('list_count', $count);
@@ -264,8 +265,6 @@ class kolab_client_task_group extends kolab_client_task
 
         $form->set_title(kolab_html::escape($title));
 
-        $this->output->add_translation('group.add.success', 'group.edit.success', 'group.delete.success');
-
         return $form->output();
     }
 
@@ -290,27 +289,6 @@ class kolab_client_task_group extends kolab_client_task
     }
 
     /**
-     * Returns list of group types.
-     *
-     * @return array List of group types
-     */
-    public function group_types()
-    {
-        if (!isset($_SESSION['group_types'])) {
-            $result = $this->api->post('group_types.list');
-            $list   = $result->get('list');
-
-            if (is_array($list)) {
-                $_SESSION['group_types'] = $list;
-            }
-        }
-
-        //console($_SESSION['group_types']);
-
-        return $_SESSION['group_types'];
-    }
-
-    /**
      * Users search form.
      *
      * @return string HTML output of the form
diff --git a/lib/client/kolab_client_task_main.php b/lib/client/kolab_client_task_main.php
index 8c50b10..bab614f 100644
--- a/lib/client/kolab_client_task_main.php
+++ b/lib/client/kolab_client_task_main.php
@@ -64,7 +64,7 @@ class kolab_client_task_main extends kolab_client_task
         $this->menu   = array();
 
         foreach ($this->_menu as $task => $api_task) {
-            if ($task != 'about' && !array_key_exists($api_task . '.list', $capabilities['actions'])) {
+            if ($task != 'about' && !array_key_exists($api_task . '.list', (array)$capabilities['actions'])) {
                 $task_class = 'kolab_client_task_' . $task;
                 if (!method_exists($task_class, 'is_enabled') || !$task_class::is_enabled($capabilities['actions'])) {
                     continue;
diff --git a/lib/client/kolab_client_task_resource.php b/lib/client/kolab_client_task_resource.php
index 9a1626d..8dde136 100644
--- a/lib/client/kolab_client_task_resource.php
+++ b/lib/client/kolab_client_task_resource.php
@@ -120,7 +120,7 @@ class kolab_client_task_resource extends kolab_client_task
             $next  = $page < $pages ? $page + 1 : 0;
 
             $count_str = kolab_html::span(array(
-                'content' => $this->translate('resource.list.records', $start, $end, $count)), true);
+                'content' => $this->translate('list.records', $start, $end, $count)), true);
             $prev = kolab_html::a(array(
                 'class' => 'prev' . ($prev ? '' : ' disabled'),
                 'href'  => '#',
@@ -164,6 +164,7 @@ class kolab_client_task_resource extends kolab_client_task
             'foot'  => $foot,
         ));
 
+        $this->output->command('set_watermark', 'taskcontent');
         $this->output->set_env('search_request', $search_request ? base64_encode(serialize($search_request)) : null);
         $this->output->set_env('list_page', $page);
         $this->output->set_env('list_count', $count);
@@ -292,8 +293,6 @@ class kolab_client_task_resource extends kolab_client_task
 
         $form->set_title(kolab_html::escape($title));
 
-        $this->output->add_translation('resource.add.success', 'resource.edit.success', 'resource.delete.success');
-
         return $form->output();
     }
 
@@ -332,25 +331,4 @@ class kolab_client_task_resource extends kolab_client_task
 
         return $form->output();
     }
-
-    /**
-     * Returns list of resource types.
-     *
-     * @return array List of resource types
-     */
-    public function resource_types()
-    {
-        if (isset($_SESSION['resource_types']) && !$this->devel_mode) {
-            return $_SESSION['resource_types'];
-        }
-
-        $result = $this->api->post('resource_types.list');
-        $list   = $result->get('list');
-
-        if (is_array($list) && !$this->devel_mode) {
-            $_SESSION['resource_types'] = $list;
-        }
-
-        return $list;
-    }
 }
diff --git a/lib/client/kolab_client_task_role.php b/lib/client/kolab_client_task_role.php
index 0363435..4640716 100644
--- a/lib/client/kolab_client_task_role.php
+++ b/lib/client/kolab_client_task_role.php
@@ -118,7 +118,7 @@ class kolab_client_task_role extends kolab_client_task
             $next  = $page < $pages ? $page + 1 : 0;
 
             $count_str = kolab_html::span(array(
-                'content' => $this->translate('role.list.records', $start, $end, $count)), true);
+                'content' => $this->translate('list.records', $start, $end, $count)), true);
             $prev = kolab_html::a(array(
                 'class' => 'prev' . ($prev ? '' : ' disabled'),
                 'href'  => '#',
@@ -162,6 +162,7 @@ class kolab_client_task_role extends kolab_client_task
             'foot'  => $foot,
         ));
 
+        $this->output->command('set_watermark', 'taskcontent');
         $this->output->set_env('search_request', $search_request ? base64_encode(serialize($search_request)) : null);
         $this->output->set_env('list_page', $page);
         $this->output->set_env('list_count', $count);
@@ -264,8 +265,6 @@ class kolab_client_task_role extends kolab_client_task
 
         $form->set_title(kolab_html::escape($title));
 
-        $this->output->add_translation('role.add.success', 'role.edit.success', 'role.delete.success');
-
         return $form->output();
     }
 
@@ -290,27 +289,6 @@ class kolab_client_task_role extends kolab_client_task
     }
 
     /**
-     * Returns list of role types.
-     *
-     * @return array List of role types
-     */
-    public function role_types()
-    {
-        if (isset($_SESSION['role_types']) && !$this->devel_mode) {
-            return $_SESSION['role_types'];
-        }
-
-        $result = $this->api->post('role_types.list');
-        $list   = $result->get('list');
-
-        if (is_array($list) && !$this->devel_mode) {
-            $_SESSION['role_types'] = $list;
-        }
-
-        return $list;
-    }
-
-    /**
      * Users search form.
      *
      * @return string HTML output of the form
diff --git a/lib/client/kolab_client_task_settings.php b/lib/client/kolab_client_task_settings.php
index 8eb5caa..061c65f 100644
--- a/lib/client/kolab_client_task_settings.php
+++ b/lib/client/kolab_client_task_settings.php
@@ -27,8 +27,12 @@ class kolab_client_task_settings extends kolab_client_task
     protected $ajax_only = true;
 
     protected $menu = array(
-        'type_list'  => 'types.list',
-        'type_add'   => 'type.add',
+        'type_list'  => 'type.list',
+        'type.add'   => 'type.add',
+    );
+
+    protected $form_element_types = array(
+        'text', 'select', 'multiselect', 'list', 'checkbox', 'password'
     );
 
     /**
@@ -36,22 +40,38 @@ class kolab_client_task_settings extends kolab_client_task
      */
     public function action_default()
     {
-        $this->output->set_object('task_navigation', $this->menu());
-//        $this->output->set_object('content', 'settings', true);
-
         $caps_actions = $this->get_capability('actions');
+
+        // Display user info by default
         if (self::can_edit_self($caps_actions)) {
             $this->action_info();
         }
+        // otherwise display object types list
+        else if (self::can_edit_types($caps_actions)) {
+            $this->output->set_object('content', 'type', true);
+            $this->action_type_list();
+            unset($this->menu['type_list']);
+
+            // ... and type add form
+            if (!empty($caps_actions['type.add'])) {
+                $this->action_type_add();
+            }
+            else {
+                $this->output->command('set_watermark', 'taskcontent');
+            }
+        }
+        // fallback
         else {
             $this->output->command('set_watermark', 'content');
         }
+
+        $this->output->set_object('task_navigation', $this->menu());
     }
 
     /**
      * Checks if it's possible to edit data of current user
      */
-    public static function can_edit_self($caps_actions)
+    private static function can_edit_self($caps_actions)
     {
         // Disable user form for directory manager (see #1025)
         if (preg_match('/^cn=([a-z ]+)/i', $_SESSION['user']['id'])) {
@@ -69,6 +89,20 @@ class kolab_client_task_settings extends kolab_client_task
     }
 
     /**
+     * Checks if it's possible to edit object types
+     */
+    private static function can_edit_types($caps_actions)
+    {
+        // I think type management interface shouldn't be displayed at all
+        // if user has no write rights to 'type' service
+        if (!empty($caps_actions['type.edit']) || !empty($caps_actions['type.add']) || !empty($caps_actions['type.delete'])) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
      * Check if any of task actions is accessible for current user
      *
      * @return bool
@@ -80,7 +114,7 @@ class kolab_client_task_settings extends kolab_client_task
             return true;
         }
 
-        if (!empty($caps_actions['types.list'])) {
+        if (self::can_edit_types($caps_actions)) {
             return true;
         }
 
@@ -98,18 +132,13 @@ class kolab_client_task_settings extends kolab_client_task
         $menu = array();
 
         foreach ($this->menu as $idx => $label) {
-            if (!array_key_exists($idx, (array)$caps['actions'])) {
+            if (strpos($idx, '.') && !array_key_exists($idx, (array)$caps['actions'])) {
                 continue;
             }
 
-            if (strpos($idx, '.')) {
-                $action = $idx;
-                $class  = preg_replace('/\.[a-z_-]+$/', '', $idx);
-            }
-            else {
-                $action = $task . '.' . $idx;
-                $class  = $idx;
-            }
+            $idx    = str_replace('.', '_', $idx);
+            $action = 'settings.' . $idx;
+            $class  = $idx;
 
             $menu[$idx] = sprintf('<li class="%s">'
                 .'<a href="#%s" onclick="return kadm.command(\'%s\', \'\', this)">%s</a></li>',
@@ -125,7 +154,7 @@ class kolab_client_task_settings extends kolab_client_task
     public function action_info()
     {
         $_POST['id'] = $_SESSION['user']['id'];
-        $user_task    = new kolab_client_task_user($this->output);
+        $user_task   = new kolab_client_task_user($this->output);
         $user_task->action_info();
 
         $this->output->set_object('content', $this->output->get_object('taskcontent'));
@@ -134,7 +163,7 @@ class kolab_client_task_settings extends kolab_client_task
     /**
      * Groups list action.
      */
-    public function action_types_list()
+    public function action_type_list()
     {
         $page_size = 20;
         $page      = (int) self::get_input('page', 'POST');
@@ -144,9 +173,9 @@ class kolab_client_task_settings extends kolab_client_task
 
         // request parameters
         $post = array(
-            'attributes' => array('cn'),
+            'attributes' => array('name', 'key'),
 //            'sort_order' => 'ASC',
-            'sort_by'    => 'cn',
+            'sort_by'    => array('name', 'key'),
             'page_size'  => $page_size,
             'page'       => $page,
         );
@@ -174,15 +203,33 @@ class kolab_client_task_settings extends kolab_client_task
             $post['search_operator'] = 'OR';
         }
 
-        // get groups list
-        $result = $this->api->post('types.list', null, $post);
-        $count  = (int) $result->get('count');
-        $result = (array) $result->get('list');
+        // object type
+        $type = self::get_input('type', 'POST');
+        if (empty($type) || !in_array($type, $this->object_types)) {
+            $type = 'user';
+        }
+
+        // get object types list
+        $result = $this->object_types($type);
+
+        // assign ID
+        foreach (array_keys($result) as $idx) {
+            $result[$idx]['id'] = $idx;
+        }
+
+        $result = array_values($result);
+        $count  = count($result);
 
         // calculate records
         if ($count) {
             $start = 1 + max(0, $page - 1) * $page_size;
             $end   = min($start + $page_size - 1, $count);
+
+            // sort and slice the result array
+
+            if ($count > $page_size) {
+                $result = array_slice($result, $start - 1, $page_size);
+            }
         }
 
         $rows = $head = $foot = array();
@@ -199,16 +246,16 @@ class kolab_client_task_settings extends kolab_client_task
             $next  = $page < $pages ? $page + 1 : 0;
 
             $count_str = kolab_html::span(array(
-                'content' => $this->translate('type.list.records', $start, $end, $count)), true);
+                'content' => $this->translate('list.records', $start, $end, $count)), true);
             $prev = kolab_html::a(array(
                 'class' => 'prev' . ($prev ? '' : ' disabled'),
                 'href'  => '#',
-                'onclick' => $prev ? "kadm.command('type.list', {page: $prev})" : "return false",
+                'onclick' => $prev ? "kadm.command('settings.type_list', {page: $prev})" : "return false",
             ));
             $next = kolab_html::a(array(
                 'class' => 'next' . ($next ? '' : ' disabled'),
                 'href'  => '#',
-                'onclick' => $next ? "kadm.command('type.list', {page: $next})" : "return false",
+                'onclick' => $next ? "kadm.command('settings.type_list', {page: $next})" : "return false",
             ));
 
             $foot_body = kolab_html::span(array('content' => $prev . $count_str . $next));
@@ -218,14 +265,14 @@ class kolab_client_task_settings extends kolab_client_task
         // table body
         if (!empty($result)) {
             foreach ($result as $idx => $item) {
-                if (!is_array($item) || empty($item['cn'])) {
+                if (!is_array($item) || empty($item['name'])) {
                     continue;
                 }
 
                 $i++;
                 $cells = array();
-                $cells[] = array('class' => 'name', 'body' => kolab_html::escape($item['cn']),
-                    'onclick' => "kadm.command('type.info', '$idx')");
+                $cells[] = array('class' => 'name', 'body' => kolab_html::escape($item['name']),
+                    'onclick' => "kadm.command('settings.type_info', '$type:" . $item['id'] . "')");
                 $rows[] = array('id' => $i, 'class' => 'selectable', 'cells' => $cells);
             }
         }
@@ -243,6 +290,7 @@ class kolab_client_task_settings extends kolab_client_task
             'foot'  => $foot,
         ));
 
+        $this->output->command('set_watermark', 'taskcontent');
         $this->output->set_env('search_request', $search_request ? base64_encode(serialize($search_request)) : null);
         $this->output->set_env('list_page', $page);
         $this->output->set_env('list_count', $count);
@@ -254,10 +302,27 @@ class kolab_client_task_settings extends kolab_client_task
      */
     public function action_type_info()
     {
-        $id     = $this->get_input('id', 'POST');
-        $result = $this->api->get('type.info', array('type' => $id));
-        $type   = $result->get();
-        $output = $this->group_form(null, $type);
+        $id   = $this->get_input('id', 'POST');
+        $data = array();
+
+        list($type, $idx) = explode(':', $id);
+
+        if ($idx && $type && ($result = $this->object_types($type))) {
+            if (!empty($result[$idx])) {
+                $data = $result[$idx];
+            }
+        }
+
+        // prepare data for form
+        if (!empty($data)) {
+            $data['id']   = $idx;
+            $data['type'] = $type;
+
+            $data['objectclass'] = $data['attributes']['fields']['objectclass'];
+            unset($data['attributes']['fields']['objectclass']);
+        }
+
+        $output = $this->type_form(null, $data);
 
         $this->output->set_object('taskcontent', $output);
     }
@@ -267,7 +332,15 @@ class kolab_client_task_settings extends kolab_client_task
      */
     public function action_type_add()
     {
-        $data   = $this->get_input('data', 'POST');
+        $data = $this->get_input('data', 'POST');
+
+        if (empty($data['type'])) {
+            $data['type'] = self::get_input('type', 'POST');
+            if (empty($data['type']) || !in_array($data['type'], $this->object_types)) {
+                $data['type'] = 'user';
+            }
+        }
+
         $output = $this->type_form(null, $data, true);
 
         $this->output->set_object('taskcontent', $output);
@@ -290,40 +363,239 @@ class kolab_client_task_settings extends kolab_client_task
 
         // field-to-section map and fields order
         $fields_map = array(
-            'type_id'       => 'props',
-            'objectclasses' => 'props',
-            'type_id_name'  => 'attribs',
+            'id'            => 'props',
+            'type'          => 'props',
+            'key'           => 'props',
+            'name'          => 'props',
+            'description'   => 'props',
+            'objectclass'   => 'props',
+            'used_for'      => 'props',
+            'attributes'    => 'attribs',
         );
 
         // Prepare fields
-        list($fields, $types, $type) = $this->form_prepare('type', $data);
-
+        $fields   = $this->type_form_prepare($data);
         $add_mode = empty($data['id']);
+        $title    = $add_mode ? $this->translate('type.add') : $data['name'];
+
+        // unset $data for correct form_create() run, we've got already data specified
+        $data = array();
+        // enable delete button
+        $data['effective_rights']['entry'] = array('delete');
+
+        // Create form object and populate with fields
+        $form = $this->form_create('type', $attribs, $sections, $fields, $fields_map, $data, $add_mode);
 
-        // Add type id selector
-        $fields['type_id'] = array(
-            'section'  => 'props',
-            'type'     => kolab_form::INPUT_HIDDEN,
+        $form->set_title(kolab_html::escape($title));
+
+        return $form->output();
+    }
+
+    /**
+     * HTML Form elements preparation.
+     *
+     * @param array $data Object data
+     *
+     * @return array Fields list
+     */
+    protected function type_form_prepare(&$data)
+    {
+        // select top class by default for new type
+        if (empty($data['objectclass'])) {
+            $data['objectclass'] = array('top');
+        }
+
+        $name     = 'type';
+        $add_mode = empty($data['id']);
+        $fields   = array(
+            'key' => array(
+                'type' => kolab_form::INPUT_TEXT,
+                'required' => true,
+                'value' => $data['key'],
+            ),
+            'name' => array(
+                'type' => kolab_form::INPUT_TEXT,
+                'required' => true,
+                'value' => $data['name'],
+            ),
+            'description' => array(
+                'type'  => kolab_form::INPUT_TEXTAREA,
+                'value' => $data['description'],
+            ),
+            'objectclass' => array(
+                'type'     => kolab_form::INPUT_SELECT,
+                'name'     => 'objectclass', // needed for form_element_select_data() below
+                'multiple' => true,
+                'required' => true,
+                'value'    => $data['objectclass'],
+            ),
+            'used_for' => array(
+                'value'   => 'hosted',
+                'type'    => kolab_form::INPUT_CHECKBOX,
+                'checked' => !empty($data['used_for']) && $data['used_for'] == 'hosted',
+            ),
+            'attributes' => array(
+                'type'    => kolab_form::INPUT_CONTENT,
+                'content' => $this->type_form_attributes($data),
+            ),
         );
 
-        // Create mode
-        if ($add_mode) {
-            // Page title
-            $title = $this->translate('type.add');
+        if ($data['type'] != 'user') {
+            unset($form_fields['used_for']);
         }
-        // Edit mode
-        else {
-            $title = $data['cn'];
+
+/*
+        // Get the rights on the entry and attribute level
+        $data['effective_rights'] = $this->effective_rights($name, $data['id']);
+        $attribute_rights         = $data['effective_rights']['attribute'];
+        $entry_rights             = $data['effective_rights']['entry'];
+
+        // See if "administrators" (those who can delete and add back on the entry
+        // level) may override the automatically generated contents of auto_form_fields.
+        $admin_auto_fields_rw = $this->config_get('admin_auto_fields_rw', false, Conf::BOOL);
+
+        foreach ($fields as $idx => $field) {
+            if (!array_key_exists($idx, $attribute_rights)) {
+                // If the entry level rights contain 'add' and 'delete', well, you're an admin
+                if (in_array('add', $entry_rights) && in_array('delete', $entry_rights)) {
+                    if ($admin_auto_fields_rw) {
+                        $fields[$idx]['readonly'] = false;
+                    }
+                }
+                else {
+                    $fields[$idx]['readonly'] = true;
+                }
+            }
+            else {
+                if (in_array('add', $entry_rights) && in_array('delete', $entry_rights)) {
+                    if ($admin_auto_fields_rw) {
+                        $fields[$idx]['readonly'] = false;
+                    }
+                }
+                // Explicit attribute level rights, check for 'write'
+                elseif (!in_array('write', $attribute_rights[$idx])) {
+                    $fields[$idx]['readonly'] = true;
+                }
+            }
+        }
+*/
+        // (Re-|Pre-)populate auto_form_fields
+        if (!$add_mode) {
+            // Add debug information
+            if ($this->devel_mode) {
+                ksort($data);
+                $debug = kolab_html::escape(print_r($data, true));
+                $debug = preg_replace('/(^Array\n\(|\n*\)$|\t)/', '', $debug);
+                $debug = str_replace("\n    ", "\n", $debug);
+                $debug = '<pre class="debug">' . $debug . '</pre>';
+                $fields['debug'] = array(
+                    'label'   => 'debug',
+                    'section' => 'props',
+                    'value'   => $debug,
+                );
+            }
         }
 
-        // Create form object and populate with fields
-        $form = $this->form_create('type', $attribs, $sections, $fields, $fields_map, $data, $add_mode);
+        // Get object classes
+        $sd = $this->form_element_select_data($fields['objectclass']);
+        $fields['objectclass'] = array_merge($fields['objectclass'], $sd);
 
-        $form->set_title(kolab_html::escape($title));
+        // Add entry identifier
+        if (!$add_mode) {
+            $fields['id'] = array(
+                'section'   => 'props',
+                'type'      => kolab_form::INPUT_HIDDEN,
+                'value'     => $data['id'],
+            );
+        }
 
-        $this->output->add_translation('type.add.success', 'type.edit.success', 'type.delete.success');
+        $fields['type'] = array(
+            'section'   => 'props',
+            'type'      => kolab_form::INPUT_HIDDEN,
+            'value'     => $data['type'] ?: 'user',
+        );
 
-        return $form->output();
+        return $fields;
+    }
+
+    private function type_form_attributes($data)
+    {
+        $attributes = array();
+        $rows       = array();
+        $table      = array(
+            'class' => 'list',
+        );
+        $cells      = array(
+            'name' => array(
+                'class' => 'name',
+                'body'  => $this->translate('attribute.name'),
+            ),
+            'type' => array(
+                'class' => 'type',
+                'body'  => $this->translate('attribute.type'),
+            ),
+            'optional' => array(
+                'class' => 'optional',
+                'body'  => $this->translate('attribute.optional'),
+            ),
+            'auto' => array(
+                'class' => 'auto',
+                'body'  => $this->translate('attribute.auto'),
+            ),
+            'static' => array(
+                'class' => 'default',
+                'body'  => $this->translate('attribute.static'),
+            ),
+//            'actions' => array(
+//                'class' => 'actions',
+//            ),
+        );
+
+        // get attributes list from $data
+        if (!empty($data) && count($data) > 1) {
+            $attributes = array_merge(
+                array_keys((array) $data['attributes']['auto_form_fields']),
+                array_keys((array) $data['attributes']['form_fields']),
+                array_keys((array) $data['attributes']['fields'])
+            );
+            $attributes = array_filter($attributes);
+            $attributes = array_unique($attributes);
+        }
+
+        // table header
+        $table['head'] = array(array('cells' => $cells));
+/*
+        // attribute row elements
+        $cells['type']['element'] = array(
+            'type'    => kolab_form::INPUT_SELECT,
+            'options' => $this->form_element_types,
+        );
+        $cells['optional']['element'] = array(
+            'type'  => kolab_form::INPUT_CHECKBOX,
+            'value' => 1,
+        );
+*/
+        $yes = $this->translate('yes');
+        // defined attributes
+        foreach ($attributes as $attr) {
+            $row = $cells;
+
+            $type     = $data['attributes']['form_fields'][$attr]['type'];
+            $optional = $data['attributes']['form_fields'][$attr]['optional'];
+
+            // set cell content
+            $row['name']['body']     = $attr;
+            $row['static']['body']   = kolab_html::escape($data['attributes']['fields'][$attr]);
+            $row['auto']['body']     = isset($data['attributes']['fields'][$attr]) ? $yes : '';
+            $row['type']['body']     = !empty($type) ? $type : 'text';
+            $row['optional']['body'] = $optional ? $yes : '';
+
+            $rows[] = array('cells' => $row);
+        }
+
+        $table['body'] = $rows;
+
+        return kolab_html::table($table);
     }
 
     /**
@@ -331,10 +603,10 @@ class kolab_client_task_settings extends kolab_client_task
      *
      * @return string HTML output of the form
      */
-    public function search_form()
+    public function type_search_form()
     {
         $form = new kolab_form(array('id' => 'search-form'));
-/*
+
         $form->add_section('criteria', kolab_html::escape($this->translate('search.criteria')));
         $form->add_element(array(
             'section' => 'criteria',
@@ -342,8 +614,9 @@ class kolab_client_task_settings extends kolab_client_task
             'name'    => 'field',
             'type'    => kolab_form::INPUT_SELECT,
             'options' => array(
-                'cn'   => kolab_html::escape($this->translate('search.name')),
-                'mail' => kolab_html::escape($this->translate('search.email')),
+                'name'        => kolab_html::escape($this->translate('search.name')),
+                'key'         => kolab_html::escape($this->translate('search.key')),
+                'description' => kolab_html::escape($this->translate('search.description')),
             ),
         ));
         $form->add_element(array(
@@ -357,8 +630,32 @@ class kolab_client_task_settings extends kolab_client_task
                 'prefix' => kolab_html::escape($this->translate('search.prefix')),
             ),
         ));
-*/
+
         return $form->output();
     }
 
+    /**
+     * Users search form.
+     *
+     * @return string HTML output of the form
+     */
+    public function type_filter()
+    {
+        $options = array();
+
+        foreach ($this->object_types as $type) {
+            $options[$type] = $this->translate('type.' . $type);
+        }
+
+        $filter = array(
+            'type'     => kolab_form::INPUT_SELECT,
+            'name'     => 'type',
+            'id'       => 'type_list_filter',
+            'options'  => $options,
+            'value'    => $this->type_selected ? $this->type_selected : 'user',
+            'onchange' => "kadm.command('settings.type_list')",
+        );
+
+        return kolab_form::get_element($filter);
+    }
 }
diff --git a/lib/client/kolab_client_task_user.php b/lib/client/kolab_client_task_user.php
index 73dbcc3..859f29a 100644
--- a/lib/client/kolab_client_task_user.php
+++ b/lib/client/kolab_client_task_user.php
@@ -118,7 +118,7 @@ class kolab_client_task_user extends kolab_client_task
             $next  = $page < $pages ? $page + 1 : 0;
 
             $count_str = kolab_html::span(array(
-                'content' => $this->translate('user.list.records', $start, $end, $count)), true);
+                'content' => $this->translate('list.records', $start, $end, $count)), true);
             $prev = kolab_html::a(array(
                 'class' => 'prev' . ($prev ? '' : ' disabled'),
                 'href'  => '#',
@@ -162,6 +162,7 @@ class kolab_client_task_user extends kolab_client_task
             'foot'  => $foot,
         ));
 
+        $this->output->command('set_watermark', 'taskcontent');
         $this->output->set_env('search_request', $search_request ? base64_encode(serialize($search_request)) : null);
         $this->output->set_env('list_page', $page);
         $this->output->set_env('list_count', $count);
@@ -351,8 +352,7 @@ class kolab_client_task_user extends kolab_client_task
 
         $form->set_title(kolab_html::escape($title));
 
-        $this->output->add_translation('user.password.mismatch',
-            'user.add.success', 'user.edit.success', 'user.delete.success');
+        $this->output->add_translation('user.password.mismatch');
 
         return $form->output();
     }
diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php
index b2edadf..f3f0aa0 100644
--- a/lib/kolab_client_task.php
+++ b/lib/kolab_client_task.php
@@ -46,9 +46,11 @@ class kolab_client_task
     protected $menu = array();
     protected $cache = array();
     protected $devel_mode = false;
+    protected $object_types = array('user', 'group', 'role', 'resource', 'domain');
 
     protected static $translation = array();
 
+
     /**
      * Class constructor.
      *
@@ -537,37 +539,39 @@ class kolab_client_task
     }
 
     /**
-     * Returns list of user types.
+     * Returns list of object types.
+     *
+     * @para string  $type     Object type name
+     * @param string $used_for Used_for attribute of object type
      *
      * @return array List of user types
      */
-    protected function user_types($used_for = null)
+    protected function object_types($type, $used_for = null)
     {
-        if (!empty($_SESSION['user_types']) && !$this->devel_mode) {
-            return $_SESSION['user_types'];
+        if (empty($type) || !in_array($type, $this->object_types)) {
+            return array();
         }
 
-        $list   = array();
-        $result = $this->api->post('user_types.list');
-        $_list  = $result->get('list');
+        $cache_idx = $type . '_types' . ($used_for ? ":$used_for" : '');
 
-        if (!empty($used_for)) {
-            foreach ($_list as $user_type_id => $user_type_attrs) {
-                if (array_key_exists('used_for', $user_type_attrs) && $user_type_attrs['used_for'] == $used_for) {
-                    $list[$user_type_id] = $user_type_attrs;
+        if (!array_key_exists($cache_idx, $this->cache)) {
+            $result = $this->api->post($type . '_types.list');
+            $list   = $result->get('list');
+
+            if (!empty($used_for)) {
+                foreach ($list as $type_id => $type_attrs) {
+                    if ($type_attrs['used_for'] != $used_for) {
+                        unset($list[$type_id]);
+                    }
                 }
             }
-        } else {
-            $list = $_list;
-        }
 
-        if (is_array($list) && !$this->devel_mode) {
-            $_SESSION['user_types'] = $list;
-        }
+            $this->cache[$cache_idx] = $list;
 
-        Log::trace("kolab_client_task::user_types() returns: " . var_export($list, true));
+            Log::trace("kolab_client_task::${type}_types() returns: " . var_export($list, true));
+        }
 
-        return $list;
+        return $this->cache[$cache_idx];
     }
 
     /**
@@ -784,6 +788,10 @@ class kolab_client_task
             }
             break;
 
+        case 'checkbox':
+            $result['type'] = kolab_form::INPUT_CHECKBOX;
+            break;
+
         case 'password':
             $result['type'] = kolab_form::INPUT_PASSWORD;
 
@@ -820,6 +828,7 @@ class kolab_client_task
         if (!isset($field['values'])) {
             $data['attributes'] = array($field['name']);
             $resp = $this->api->post('form_value.select_options', null, $data);
+
             unset($data['attributes']);
             $field['values'] = $resp->get($field['name']);
         }
@@ -857,17 +866,13 @@ class kolab_client_task
      */
     protected function form_prepare($name, &$data, $extra_fields = array(), $used_for = null)
     {
-        $types        = (array) $this->{$name . '_types'}($used_for);
-
-        $form_id      = $attribs['id'];
+        $types        = (array) $this->object_types($name, $used_for);
         $add_mode     = empty($data['id']);
-
         $event_fields = array();
         $auto_fields  = array();
         $form_fields  = array();
         $fields       = array();
         $auto_attribs = array();
-
         $extra_fields = array_flip($extra_fields);
 
         // Object type
@@ -1103,11 +1108,6 @@ class kolab_client_task
             if (!$field['section']) {
                 $fields[$idx]['section'] = isset($fields_map[$idx]) ? $fields_map[$idx] : 'other';
                 //console("Assigned field $idx to section " . $fields[$idx]['section']);
-/*
-            } else {
-                $fields[$idx]['section'] = 'other';
-                //console("Assigned field $idx to section " . $fields[$idx]['section']);
-*/
             }
         }
 
@@ -1256,7 +1256,8 @@ class kolab_client_task
         $this->output->set_env('form_id', $attribs['id']);
         $this->output->set_env('assoc_fields', $assoc_fields);
         $this->output->set_env('required_fields', $req_fields);
-        $this->output->add_translation('form.required.empty', 'form.maxcount.exceeded');
+        $this->output->add_translation('form.required.empty', 'form.maxcount.exceeded',
+            $name . '.add.success', $name . '.edit.success', $name . '.delete.success');
 
         return $form;
     }
diff --git a/lib/kolab_html.php b/lib/kolab_html.php
index ca40b85..33bc705 100644
--- a/lib/kolab_html.php
+++ b/lib/kolab_html.php
@@ -198,8 +198,13 @@ class kolab_html
                 if (empty($option['value'])) {
                     $option['value'] = $idx;
                 }
-                if (!empty($attribs['value']) && $attribs['value'] == $option['value']) {
-                    $option['selected'] = true;
+                if (!empty($attribs['value'])) {
+                    if (is_array($attribs['value'])) {
+                        $option['selected'] = in_array($option['value'], $attribs['value']);
+                    }
+                    else if ($attribs['value'] == $option['value']) {
+                        $option['selected'] = true;
+                    }
                 }
                 // make a select really readonly by disabling options
                 else if (!empty($attribs['disabled']) || !empty($attribs['readonly'])) {
diff --git a/lib/locale/en_US.php b/lib/locale/en_US.php
index 4527251..9f5561e 100644
--- a/lib/locale/en_US.php
+++ b/lib/locale/en_US.php
@@ -7,6 +7,12 @@ $LANG['about.support'] = 'Professional support is available from <a href="http:/
 $LANG['about.technology'] = 'Technology';
 $LANG['about.warranty'] = 'It comes with absolutely <b>no warranties</b> and is typically run entirely self supported. You can find help & information on the community <a href="http://kolab.org">web site</a> & <a href="http://wiki.kolab.org">wiki</a>.';
 
+$LANG['attribute.auto'] = 'Auto-generated';
+$LANG['attribute.static'] = 'Static value';
+$LANG['attribute.name'] = 'Attribute';
+$LANG['attribute.optional'] = 'Optional';
+$LANG['attribute.type'] = 'Field type';
+
 $LANG['creatorsname'] = 'Created by';
 $LANG['days'] = 'days';
 $LANG['debug'] = 'Debug info';
@@ -20,7 +26,6 @@ $LANG['domain.edit'] = 'Edit domain';
 $LANG['domain.edit.success'] = 'Domain updated';
 $LANG['domain.inetdomainbasedn'] = 'Custom Root DN(s)';
 $LANG['domain.list'] = 'Domains List';
-$LANG['domain.list.records'] = '$1 to $2 of $3';
 $LANG['domain.o'] = 'Organization';
 $LANG['domain.other'] = 'Other';
 $LANG['domain.system'] = 'System';
@@ -38,7 +43,6 @@ $LANG['group.delete.success'] = 'Group deleted successfully.';
 $LANG['group.edit.success'] = 'Group edited successfully.';
 $LANG['group.gidnumber'] = 'Primary group number';
 $LANG['group.list'] = 'Groups List';
-$LANG['group.list.records'] = '$1 to $2 of $3';
 $LANG['group.mail'] = 'Primary Email Address';
 $LANG['group.member'] = 'Member(s)';
 $LANG['group.norecords'] = 'No group records found!';
@@ -49,6 +53,9 @@ $LANG['group.uniquemember'] = 'Members';
 
 $LANG['info'] = 'Information';
 $LANG['internalerror'] = 'Internal system error!';
+
+$LANG['list.records'] = '$1 to $2 of $3';
+
 $LANG['loading'] = 'Loading...';
 $LANG['login.username'] = 'Username:';
 $LANG['login.password'] = 'Password:';
@@ -78,7 +85,6 @@ $LANG['resource.edit'] = 'Edit Resource';
 $LANG['resource.edit.success'] = 'Successfully updated Resource';
 $LANG['resource.kolabtargetfolder'] = 'Target Folder';
 $LANG['resource.list'] = 'Resource (Collection) List';
-$LANG['resource.list.records'] = '$1 to $2 of $3';
 $LANG['resource.mail'] = 'Mail Address';
 $LANG['resource.member'] = 'Collection Members';
 $LANG['resource.norecords'] = 'No resource record(s) found!';
@@ -92,7 +98,6 @@ $LANG['role.cn'] = 'Role Name';
 $LANG['role.description'] = 'Role Description';
 $LANG['role.edit.success'] = 'Role edited successfully';
 $LANG['role.list'] = 'Role List';
-$LANG['role.list.records'] = '$1 to $2 of $3';
 $LANG['role.norecords'] = 'No role records found!';
 $LANG['role.system'] = 'Details';
 $LANG['role.type_id'] = 'Role Type';
@@ -100,15 +105,17 @@ $LANG['role.type_id'] = 'Role Type';
 $LANG['saving'] = 'Saving data...';
 
 $LANG['search'] = 'Search';
-$LANG['search.criteria'] = 'Search criteria';
 $LANG['search.reset'] = 'Reset';
+$LANG['search.criteria'] = 'Search criteria';
 $LANG['search.field'] = 'Field:';
 $LANG['search.method'] = 'Method:';
 $LANG['search.contains'] = 'contains';
 $LANG['search.is'] = 'is';
+$LANG['search.key'] = 'key';
 $LANG['search.prefix'] = 'begins with';
 $LANG['search.name'] = 'name';
 $LANG['search.email'] = 'email';
+$LANG['search.description'] = 'description';
 $LANG['search.uid'] = 'UID';
 $LANG['search.loading'] = 'Searching...';
 $LANG['search.acchars'] = 'At least $min characters required for autocompletion';
@@ -135,6 +142,23 @@ $LANG['signup.footer'] = 'This is a service offered by <a href="http://kolabsys.
 
 $LANG['submit.button'] = 'Submit';
 
+$LANG['type.add'] = 'Add Object Type';
+$LANG['type.attributes'] = 'Attributes';
+$LANG['type.description'] = 'Description';
+$LANG['type.domain'] = 'Domain';
+$LANG['type.group'] = 'Group';
+$LANG['type.list'] = 'Object Types List';
+$LANG['type.key'] = 'Key';
+$LANG['type.name'] = 'Name';
+$LANG['type.norecords'] = 'No object type records found!';
+$LANG['type.objectclass'] = 'Object class';
+$LANG['type.object_type'] = 'Object type';
+$LANG['type.properties'] = 'Properties';
+$LANG['type.resource'] = 'Resource';
+$LANG['type.role'] = 'Role';
+$LANG['type.used_for'] = 'Hosted';
+$LANG['type.user'] = 'User';
+
 $LANG['user.add'] = 'Add User';
 $LANG['user.add.success'] = 'User created successfully.';
 $LANG['user.alias'] = 'Secondary Email Address(es)';
@@ -179,7 +203,6 @@ $LANG['user.kolabhomeserver'] = 'Email Server';
 $LANG['user.kolabinvitationpolicy'] = 'Invitation Handling Policy';
 $LANG['user.l'] = 'City, Region';
 $LANG['user.list'] = 'Users List';
-$LANG['user.list.records'] = '$1 to $2 of $3';
 $LANG['user.loginshell'] = 'Shell';
 $LANG['user.mail'] = 'Primary Email Address';
 $LANG['user.mailalternateaddress'] = 'External Email Address(es)';
diff --git a/public_html/js/kolab_admin.js b/public_html/js/kolab_admin.js
index 2fd8899..c58a592 100644
--- a/public_html/js/kolab_admin.js
+++ b/public_html/js/kolab_admin.js
@@ -1412,7 +1412,7 @@ function kolab_admin()
 
     var page = this.env.list_page;
 
-    // goto previous page if last user on the current page has been deleted
+    // goto previous page if last record on the current page has been deleted
     if (this.env.list_count)
       page -= 1;
 
@@ -1492,7 +1492,7 @@ function kolab_admin()
 
     var page = this.env.list_page;
 
-    // goto previous page if last user on the current page has been deleted
+    // goto previous page if last record on the current page has been deleted
     if (this.env.list_count)
       page -= 1;
 
@@ -1572,7 +1572,7 @@ function kolab_admin()
 
     var page = this.env.list_page;
 
-    // goto previous page if last user on the current page has been deleted
+    // goto previous page if last record on the current page has been deleted
     if (this.env.list_count)
       page -= 1;
 
@@ -1623,6 +1623,105 @@ function kolab_admin()
     this.set_watermark('taskcontent');
   };
 
+  this.settings_type_info = function(id)
+  {
+    this.http_post('settings.type_info', {id: id});
+  };
+
+  this.settings_type_add = function()
+  {
+    this.http_post('settings.type_add', {type: $('#type_list_filter').val()});
+  };
+
+  this.settings_type_list = function(props)
+  {
+    if (!props)
+      props = {};
+
+    if (props.search === undefined && this.env.search_request)
+      props.search_request = this.env.search_request;
+
+    props.type = $('#type_list_filter').val();
+
+    this.http_post('settings.type_list', props);
+  };
+
+  this.type_delete = function(id)
+  {
+    // @TODO:
+    return alert('Not implemented');
+
+    this.set_busy(true, 'deleting');
+    this.api_post('type.delete', this.type_id_parse(id), 'type_delete_response');
+  };
+
+  this.type_delete_response = function(response)
+  {
+    if (!this.api_response(response))
+      return;
+
+    var page = this.env.list_page;
+
+    // goto previous page if last record on the current page has been deleted
+    if (this.env.list_count)
+      page -= 1;
+
+    this.display_message('type.delete.success');
+    this.command('type.list', {page: page});
+    this.set_watermark('taskcontent');
+  };
+
+  this.type_save = function(reload, section)
+  {
+    // @TODO:
+    return alert('Not implemented');
+
+    var data = this.serialize_form('#'+this.env.form_id),
+      action = data.id ? 'edit' : 'add';
+
+    if (reload) {
+      data.section = section;
+      this.http_post('type.' + action, {data: data});
+      return;
+    }
+
+    this.form_error_clear();
+
+    if (!this.check_required_fields(data)) {
+      this.display_message('form.required.empty', 'error');
+      return;
+    }
+
+    this.set_busy(true, 'saving');
+    this.api_post('type.' + action, data, 'type_' + action + '_response');
+  };
+
+  this.type_add_response = function(response)
+  {
+    if (!this.api_response(response))
+      return;
+
+    this.display_message('type.add.success');
+    this.command('settings.type_list', {page: this.env.list_page});
+    this.set_watermark('taskcontent');
+  };
+
+  this.type_edit_response = function(response)
+  {
+    if (!this.api_response(response))
+      return;
+
+    this.display_message('type.edit.success');
+    this.command('settings.type_list', {page: this.env.list_page});
+    this.set_watermark('taskcontent');
+  };
+
+  this.type_id_parse = function(id)
+  {
+    var id = String(id).split(':');
+    return {type: id[0], id: id[1]};
+  };
+
   this.generate_password = function(fieldname)
   {
     this.env.password_field = fieldname;
diff --git a/public_html/skins/default/style.css b/public_html/skins/default/style.css
index 3968c93..9b68183 100644
--- a/public_html/skins/default/style.css
+++ b/public_html/skins/default/style.css
@@ -29,6 +29,10 @@ textarea {
   color: black;
 }
 
+select[multiple="multiple"] {
+  padding-left: 0;
+}
+
 table.list {
   border: 1px solid #d0d0d0;
   border-spacing: 0;
@@ -412,6 +416,12 @@ td.label {
   display: none;
 }
 
+.searchfilter {
+  color: #909090;
+  font-weight: bold;
+  margin-top: 5px;
+}
+
 #search fieldset {
   margin: 0;
   color: #909090;
diff --git a/public_html/skins/default/templates/domain.html b/public_html/skins/default/templates/domain.html
index e658427..1ad4332 100644
--- a/public_html/skins/default/templates/domain.html
+++ b/public_html/skins/default/templates/domain.html
@@ -2,7 +2,7 @@
     <div id="search">
         <div class="searchinput">
             <input type="text" id="searchinput" name="search" value="{$engine->translate('search')}" />
-            <script type="text/javascript">search_init('domain')</script>
+            <script type="text/javascript">search_init('domain.list')</script>
             <span class="searchactions">
                 <span id="search-details" title="{$engine->translate('search.criteria')}" onclick="search_details()"></span>
                 <span id="search-reset" title="{$engine->translate('search.reset')}" onclick="search_reset()"></span>
diff --git a/public_html/skins/default/templates/group.html b/public_html/skins/default/templates/group.html
index 0360bec..e1a4c82 100644
--- a/public_html/skins/default/templates/group.html
+++ b/public_html/skins/default/templates/group.html
@@ -2,7 +2,7 @@
     <div id="search">
         <div class="searchinput">
             <input type="text" id="searchinput" name="search" value="{$engine->translate('search')}" />
-            <script type="text/javascript">search_init('group')</script>
+            <script type="text/javascript">search_init('group.list')</script>
             <span class="searchactions">
                 <span id="search-details" title="{$engine->translate('search.criteria')}" onclick="search_details()"></span>
                 <span id="search-reset" title="{$engine->translate('search.reset')}" onclick="search_reset()"></span>
diff --git a/public_html/skins/default/templates/resource.html b/public_html/skins/default/templates/resource.html
index b2adf2f..0f09c04 100644
--- a/public_html/skins/default/templates/resource.html
+++ b/public_html/skins/default/templates/resource.html
@@ -2,7 +2,7 @@
     <div id="search">
         <div class="searchinput">
             <input type="text" id="searchinput" name="search" value="{$engine->translate('search')}" />
-            <script type="text/javascript">search_init('resource')</script>
+            <script type="text/javascript">search_init('resource.list')</script>
             <span class="searchactions">
                 <span id="search-details" title="{$engine->translate('search.criteria')}" onclick="search_details()"></span>
                 <span id="search-reset" title="{$engine->translate('search.reset')}" onclick="search_reset()"></span>
diff --git a/public_html/skins/default/templates/role.html b/public_html/skins/default/templates/role.html
index ba8d87e..1a37852 100644
--- a/public_html/skins/default/templates/role.html
+++ b/public_html/skins/default/templates/role.html
@@ -2,7 +2,7 @@
     <div id="search">
         <div class="searchinput">
             <input type="text" id="searchinput" name="search" value="{$engine->translate('search')}" />
-            <script type="text/javascript">search_init('role')</script>
+            <script type="text/javascript">search_init('role.list')</script>
             <span class="searchactions">
                 <span id="search-details" title="{$engine->translate('search.criteria')}" onclick="search_details()"></span>
                 <span id="search-reset" title="{$engine->translate('search.reset')}" onclick="search_reset()"></span>
diff --git a/public_html/skins/default/templates/type.html b/public_html/skins/default/templates/type.html
new file mode 100644
index 0000000..8e15792
--- /dev/null
+++ b/public_html/skins/default/templates/type.html
@@ -0,0 +1,18 @@
+<div id="toc" class="type">
+    <div id="search">
+        <div class="searchinput">
+            <input type="text" id="searchinput" name="search" value="{$engine->translate('search')}" />
+            <script type="text/javascript">search_init('settings.type_list')</script>
+            <span class="searchactions">
+                <span id="search-details" title="{$engine->translate('search.criteria')}" onclick="search_details()"></span>
+                <span id="search-reset" title="{$engine->translate('search.reset')}" onclick="search_reset()"></span>
+            </span>
+        </div>
+        <div class="searchdetails">{$engine->type_search_form()}</div>
+        <div class="searchfilter">{$engine->translate('type.object_type')}: {$engine->type_filter()}</div>
+    </div>
+    <div id="typelist"></div>
+</div>
+<div class="vsplitter"> </div>
+<div id="taskcontent" class="type"></div>
+<div class="clear"></div>
diff --git a/public_html/skins/default/templates/user.html b/public_html/skins/default/templates/user.html
index 1c5e732..8244305 100644
--- a/public_html/skins/default/templates/user.html
+++ b/public_html/skins/default/templates/user.html
@@ -2,7 +2,7 @@
     <div id="search">
         <div class="searchinput">
             <input type="text" id="searchinput" name="search" value="{$engine->translate('search')}" />
-            <script type="text/javascript">search_init('user')</script>
+            <script type="text/javascript">search_init('user.list')</script>
             <span class="searchactions">
                 <span id="search-details" title="{$engine->translate('search.criteria')}" onclick="search_details()"></span>
                 <span id="search-reset" title="{$engine->translate('search.reset')}" onclick="search_reset()"></span>
diff --git a/public_html/skins/default/ui.js b/public_html/skins/default/ui.js
index 94891c6..9332051 100644
--- a/public_html/skins/default/ui.js
+++ b/public_html/skins/default/ui.js
@@ -24,9 +24,9 @@
 /**
  * Search form events
  */
-function search_init(task)
+function search_init(command)
 {
-  kadm.env.search_task = task;
+  kadm.env.search_command = command;
 
   $('#searchinput').addClass('inactive')
     .blur(function() {
@@ -38,7 +38,7 @@ function search_init(task)
         var props = kadm.serialize_form('#search-form');
         props.search = this.value;
 
-        kadm.command(kadm.env.search_task + '.list', props);
+        kadm.command(kadm.env.search_command, props);
       }
     })
     .focus(function() {
@@ -53,7 +53,7 @@ function search_reset()
 
   input.val(kadm.t('search')).addClass('inactive');
 
-  kadm.command(kadm.env.search_task + '.list', {search: ''});
+  kadm.command(kadm.env.search_command, {search: ''});
 };
 
 function search_details()


commit 86487317c41327705d6ee942f255da51d292954c
Author: Aleksander Machniak <alec at alec.pl>
Date:   Tue Sep 25 14:58:27 2012 +0200

    Make get_element() static and public

diff --git a/lib/kolab_form.php b/lib/kolab_form.php
index ff19098..353d993 100644
--- a/lib/kolab_form.php
+++ b/lib/kolab_form.php
@@ -136,7 +136,7 @@ class kolab_form
                     }
 
                     if ($element['type'] == self::INPUT_HIDDEN) {
-                        $hidden[] = $this->get_element($element);
+                        $hidden[] = self::get_element($element);
                         continue;
                     }
 
@@ -161,7 +161,7 @@ class kolab_form
             }
 
             if ($element['type'] == self::INPUT_HIDDEN) {
-                $hidden[] = $this->get_element($element);
+                $hidden[] = self::get_element($element);
                 continue;
             }
 
@@ -238,7 +238,7 @@ class kolab_form
                 ),
                 1 => array(
                     'class' => 'value',
-                    'body'  => $this->get_element($element),
+                    'body'  => self::get_element($element),
                 ),
             );
         }
@@ -249,7 +249,7 @@ class kolab_form
     /**
      * Builds an element of the form.
      */
-    private function get_element($attribs)
+    public static function get_element($attribs)
     {
         $type = isset($attribs['type']) ? $attribs['type'] : 0;
 


commit 3523da816294e306cbb9de84dfd606d4d4d44a15
Author: Aleksander Machniak <alec at alec.pl>
Date:   Tue Sep 25 09:33:11 2012 +0200

    Implemented INPUT_CHECKBOX and INPUT_CONTENT

diff --git a/lib/kolab_form.php b/lib/kolab_form.php
index de39bc3..ff19098 100644
--- a/lib/kolab_form.php
+++ b/lib/kolab_form.php
@@ -27,16 +27,17 @@
  */
 class kolab_form
 {
-    const INPUT_TEXT = 1;
+    const INPUT_TEXT     = 1;
     const INPUT_PASSWORD = 2;
     const INPUT_TEXTAREA = 3;
     const INPUT_CHECKBOX = 4;
-    const INPUT_RADIO = 5;
-    const INPUT_BUTTON = 6;
-    const INPUT_SUBMIT = 7;
-    const INPUT_SELECT = 8;
-    const INPUT_HIDDEN = 9;
-    const INPUT_CUSTOM = 10;
+    const INPUT_RADIO    = 5;
+    const INPUT_BUTTON   = 6;
+    const INPUT_SUBMIT   = 7;
+    const INPUT_SELECT   = 8;
+    const INPUT_HIDDEN   = 9;
+    const INPUT_CUSTOM   = 10;
+    const INPUT_CONTENT  = 20;
 
     const TYPE_LIST = 1;
 
@@ -214,23 +215,34 @@ class kolab_form
      */
     private function form_row($element)
     {
-        $cells = array(
-            0 => array(
-                'class' => 'label',
-                'body' => $element['label'],
-            ),
-            1 => array(
-                'class' => 'value',
-                'body' => $this->get_element($element),
-            ),
-        );
-
-        $attrib = array('cells' => $cells);
+        $attrib = array();
 
         if (!empty($element['required']) && empty($element['readonly']) && empty($element['disabled'])) {
             $attrib['class'] = 'required';
         }
 
+        if ($element['type'] == self::INPUT_CONTENT) {
+            $attrib['cells'] = array(
+                0 => array(
+                    'class'   => $element['class'],
+                    'colspan' => 2,
+                    'body'    => $element['content'],
+                ),
+            );
+        }
+        else {
+            $attrib['cells'] = array(
+                0 => array(
+                    'class' => 'label',
+                    'body'  => $element['label'],
+                ),
+                1 => array(
+                    'class' => 'value',
+                    'body'  => $this->get_element($element),
+                ),
+            );
+        }
+
         return $attrib;
     }
 
@@ -265,6 +277,11 @@ class kolab_form
             $content = kolab_html::input($attribs);
             break;
 
+        case self::INPUT_CHECKBOX:
+            $attribs['type'] = 'checkbox';
+            $content = kolab_html::input($attribs);
+            break;
+
         case self::INPUT_HIDDEN:
             $attribs['type'] = 'hidden';
             $content = kolab_html::input($attribs);


commit 58e593fa1b4db1aae39b1e422fa8171d9154ee60
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Sep 26 09:08:17 2012 +0200

    Fix error handling in type.edit

diff --git a/lib/api/kolab_api_service_type.php b/lib/api/kolab_api_service_type.php
index 429df7b..11d9387 100644
--- a/lib/api/kolab_api_service_type.php
+++ b/lib/api/kolab_api_service_type.php
@@ -178,18 +178,16 @@ class kolab_api_service_type extends kolab_api_service
             $query[$idx] = $idx . " = " . $this->db->escape($value);
         }
 
-        $this->db->query("UPDATE {$type}_types SET "
+        $result = $this->db->query("UPDATE {$type}_types SET "
             . implode(', ', $query) . " WHERE id = ?", array($postdata['id']));
 
-        if (!($id = $this->db->last_insert_id())) {
+        if (!$result) {
             return false;
         }
 
         // update cache
         $this->cache['object_types'][$type][$id] = $postdata;
 
-        $postdata['id'] = $id;
-
         return $postdata;
     }
 


commit 20fa92e2a56cd52d8e8bf5fd9f12ba25439e1b72
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Sep 26 08:55:39 2012 +0200

    Implemented type.add and type.edit

diff --git a/lib/api/kolab_api_service_type.php b/lib/api/kolab_api_service_type.php
index 4b71080..429df7b 100644
--- a/lib/api/kolab_api_service_type.php
+++ b/lib/api/kolab_api_service_type.php
@@ -24,7 +24,7 @@
 */
 
 /**
- * Service providing user data management
+ * Service providing object types management
  */
 class kolab_api_service_type extends kolab_api_service
 {
@@ -50,35 +50,64 @@ class kolab_api_service_type extends kolab_api_service
             $rights['edit'] = "w";
         }
 
+        $rights['info'] = "r";
         $rights['effective_rights'] = "r";
 
         return $rights;
     }
 
     /**
-     * Create user.
+     * Create type.
      *
-     * @param array $get   GET parameters
-     * @param array $post  POST parameters
+     * @param array $get  GET parameters
+     * @param array $post POST parameters
      *
-     * @return array|bool User attributes or False on error.
+     * @return array|bool Type attributes or False on error.
      */
     public function type_add($getdata, $postdata)
     {
-        //console("type_add()", $postdata);
+        if (!in_array($postdata['type'], $this->supported_types_db)) {
+            return false;
+        }
+
+        if (empty($postdata['name']) || empty($postdata['key'])) {
+            return false;
+        }
+
+        if (empty($postdata['attributes']) || !is_array($postdata['attributes'])) {
+            return false;
+        }
+
+        // @TODO: check privileges
 
-        $type_attributes = $this->parse_input_attributes('type', $postdata);
+        $type  = $postdata['type'];
+        $query = array(
+            'key'         => $postdata['key'],
+            'name'        => $postdata['name'],
+            'description' => $postdata['description'] ? $postdata['description'] : '',
+            'attributes'  => json_encode($postdata['attributes']),
+        );
 
-        //console("type_add()", $type_attributes);
+        if ($postdata['type'] == 'user') {
+            $query['used_for'] = $postdata['used_for'] == 'hosted' ? 'hosted' : null;
+        }
 
-//        $auth   = Auth::get_instance();
-//        $result = $auth->type_add($type_attributes, $postdata['type_id']);
+        $query = array_map(array($this->db, 'escape'), $query);
 
-        if ($result) {
-            return $type_attributes;
+        $this->db->query("INSERT INTO {$type}_types"
+            . " (" . implode(',', array_keys($query)) . ")"
+            . " VALUES (" . implode(',', $query) . ")");
+
+        if (!($id = $this->db->last_insert_id())) {
+            return false;
         }
 
-        return false;
+        // update cache
+        $this->cache['object_types'][$type][$id] = $postdata;
+
+        $postdata['id'] = $id;
+
+        return $postdata;
     }
 
     /**
@@ -102,6 +131,8 @@ class kolab_api_service_type extends kolab_api_service
         $object_name = $postdata['type'];
         $object_id   = $postdata['id'];
 
+        // @TODO: check privileges
+
         $this->db->query("DELETE FROM {$object_name}_types WHERE id = ?", array($object_id));
 
         return (bool) $this->db->affected_rows();
@@ -117,21 +148,49 @@ class kolab_api_service_type extends kolab_api_service
      */
     public function type_edit($getdata, $postdata)
     {
-        //console("\$postdata to type_edit()", $postdata);
+        if (empty($postdata['type']) || empty($postdata['id'])) {
+            return false;
+        }
+
+        if (empty($postdata['name']) || empty($postdata['key'])) {
+            return false;
+        }
+
+        if (empty($postdata['attributes']) || !is_array($postdata['attributes'])) {
+            return false;
+        }
 
-        $type_attributes = $this->parse_input_attributes('type', $postdata);
-        $type            = $postdata['id'];
+        // @TODO: check privileges
 
-//        $auth   = Auth::get_instance();
-//        $result = $auth->type_edit($type, $type_attributes, $postdata['type_id']);
+        $type  = $postdata['type'];
+        $query = array(
+            'key'         => $postdata['key'],
+            'name'        => $postdata['name'],
+            'description' => $postdata['description'] ? $postdata['description'] : '',
+            'attributes'  => json_encode($postdata['attributes']),
+        );
 
-        // Return the $mod_array
-        if ($result) {
-            return $result;
+        if ($postdata['type'] == 'user') {
+            $query['used_for'] = $postdata['used_for'] == 'hosted' ? 'hosted' : null;
         }
 
-        return false;
+        foreach ($query as $idx => $value) {
+            $query[$idx] = $idx . " = " . $this->db->escape($value);
+        }
 
+        $this->db->query("UPDATE {$type}_types SET "
+            . implode(', ', $query) . " WHERE id = ?", array($postdata['id']));
+
+        if (!($id = $this->db->last_insert_id())) {
+            return false;
+        }
+
+        // update cache
+        $this->cache['object_types'][$type][$id] = $postdata;
+
+        $postdata['id'] = $id;
+
+        return $postdata;
     }
 
     public function type_effective_rights($getdata, $postdata)
@@ -143,32 +202,27 @@ class kolab_api_service_type extends kolab_api_service
     }
 
     /**
-     * User information.
+     * Type information.
      *
      * @param array $get  GET parameters
      * @param array $post POST parameters
      *
-     * @return array|bool User attributes, False on error
+     * @return array|bool Type data, False on error
      */
     public function type_info($getdata, $postdata)
     {
-        if (!isset($getdata['type'])) {
+        if (empty($getdata['type']) || empty($getdata['id'])) {
             return false;
         }
 
-//        $auth   = Auth::get_instance();
-//        $result = $auth->type_info($getdata['type']);
-
-//        Log::trace("type.info on " . $getdata['type'] . " result: " . var_export($result, TRUE));
-        // normalize result
-//        $result = $this->parse_result_attributes('type', $result);
-
-//        Log::trace("type.info on " . $getdata['type'] . " parsed result: " . var_export($result, TRUE));
-
-        if ($result) {
-            return $result;
+        if (!in_array($getdata['type'], $this->supported_types_db)) {
+            return false;
         }
 
-        return false;
+        $object_name = $getdata['type'];
+        $object_id   = $getdata['id'];
+        $types       = $this->object_types($object_name);
+
+        return !empty($types[$object_id]) ? $types[$object_id] : false;
     }
 }


commit 9cae24f77fb69fa1f20243c21fbbf11040bf306e
Author: Aleksander Machniak <alec at alec.pl>
Date:   Tue Sep 25 13:58:41 2012 +0200

    Added select_options_objectclass() method, @TODO: get objectclasses from LDAP

diff --git a/lib/api/kolab_api_service_form_value.php b/lib/api/kolab_api_service_form_value.php
index 4d90d68..22cc294 100644
--- a/lib/api/kolab_api_service_form_value.php
+++ b/lib/api/kolab_api_service_form_value.php
@@ -153,7 +153,6 @@ class kolab_api_service_form_value extends kolab_api_service
     public function select_options($getdata, $postdata)
     {
         //console("form_value.select_options postdata", $postdata);
-
         $attribs    = $this->object_type_attributes($postdata['object_type'], $postdata['type_id']);
         $attributes = (array) $postdata['attributes'];
         $result     = array();
@@ -764,6 +763,28 @@ class kolab_api_service_form_value extends kolab_api_service
         return $this->_select_options_from_db('c');
     }
 
+    private function select_options_objectclass($postdata, $attribs = array())
+    {
+        // @TODO: get list from LDAP
+        // @TODO: filter by object type?
+        $classes = array(
+            'groupofuniquenames',
+            'inetorgperson',
+            'kolabgroupofuniquenames',
+            'kolabinetorgperson',
+            'kolabsharedfolder',
+            'mailrecipient',
+            'organizationalperson',
+            'organizationalunit',
+            'person',
+            'posixaccount',
+            'posixgroup',
+            'top',
+        );
+
+        return $classes;
+    }
+
     private function select_options_ou($postdata, $attribs = array())
     {
         $auth = Auth::get_instance();


commit 7f5e0537e8366667058cebfd904664c8951b0eb5
Author: Aleksander Machniak <alec at alec.pl>
Date:   Tue Sep 25 10:42:23 2012 +0200

    Added last_insert_id() method

diff --git a/lib/SQL.php b/lib/SQL.php
index d5b6737..129a090 100644
--- a/lib/SQL.php
+++ b/lib/SQL.php
@@ -103,6 +103,11 @@ class SQL
         return mysql_affected_rows($this->conn);
     }
 
+    public function last_insert_id()
+    {
+        return mysql_insert_id($this->conn);
+    }
+
     public function escape($str)
     {
         if ($str === null || is_array($str)) {


commit 8e17609aef394f81d3b67bb88b0d5eaf48a8477a
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Sep 24 14:32:43 2012 +0200

    Implemented type.delete

diff --git a/lib/api/kolab_api_service_type.php b/lib/api/kolab_api_service_type.php
index 54d1464..4b71080 100644
--- a/lib/api/kolab_api_service_type.php
+++ b/lib/api/kolab_api_service_type.php
@@ -91,20 +91,20 @@ class kolab_api_service_type extends kolab_api_service
      */
     public function type_delete($getdata, $postdata)
     {
-        //console("type_delete()", $getdata, $postdata);
-        if (!isset($postdata['type'])) {
+        if (empty($postdata['type']) || empty($postdata['id'])) {
             return false;
         }
 
-        // TODO: Input validation
-//        $auth   = Auth::get_instance();
-//        $result = $auth->type_delete($postdata['type']);
-
-        if ($result) {
-            return $result;
+        if (!in_array($postdata['type'], $this->supported_types_db)) {
+            return false;
         }
 
-        return false;
+        $object_name = $postdata['type'];
+        $object_id   = $postdata['id'];
+
+        $this->db->query("DELETE FROM {$object_name}_types WHERE id = ?", array($object_id));
+
+        return (bool) $this->db->affected_rows();
     }
 
     /**
diff --git a/lib/kolab_api_service.php b/lib/kolab_api_service.php
index 3fcefda..c45f811 100644
--- a/lib/kolab_api_service.php
+++ b/lib/kolab_api_service.php
@@ -32,6 +32,8 @@ abstract class kolab_api_service
     protected $conf;
     protected $controller;
     protected $db;
+    protected $supported_types_db = array('group', 'resource', 'role', 'user');
+    protected $supported_types    = array('domain', 'group', 'resource', 'role', 'user');
 
     /**
      * Class constructor.
@@ -40,10 +42,10 @@ abstract class kolab_api_service
      */
     public function __construct($ctrl)
     {
-        $this->conf       = Conf::get_instance();
         $this->controller = $ctrl;
+        $this->conf       = Conf::get_instance();
         $this->db         = SQL::get_instance();
-   }
+    }
 
     /**
      * Advertise this service's capabilities
@@ -61,8 +63,7 @@ abstract class kolab_api_service
      */
     protected function object_type_attributes($object_name, $type_id, $required = true)
     {
-        $supported = array('domain', 'group', 'resource', 'role', 'user');
-        if (!$object_name || !in_array($object_name, $supported)) {
+        if (!$object_name || !in_array($object_name, $this->supported_types)) {
             return array();
         }
 
@@ -79,25 +80,23 @@ abstract class kolab_api_service
         if (empty($object_types[$type_id])) {
             if ($object_name == 'domain') {
                 $result = array(
-                        'auto_form_fields' => array(),
-                        'form_fields' => array(
-                                'associateddomain' => array(
-                                        'type' => 'list'
-                                    ),
-                            ),
-                        'fields' => array(
-                                'objectclass' => array(
-                                        'top',
-                                        'domainrelatedobject',
-                                    ),
-                            ),
-                    );
-
-                //console("object_type_attributes('domain', $type_id);", $result);
+                    'auto_form_fields' => array(),
+                    'form_fields' => array(
+                        'associateddomain' => array(
+                            'type' => 'list'
+                        ),
+                    ),
+                    'fields' => array(
+                        'objectclass' => array(
+                            'top',
+                            'domainrelatedobject',
+                        ),
+                    ),
+                );
 
                 return $result;
-
-            } else {
+            }
+            else {
                 throw new Exception($this->controller->translate($object_name . '.invalidtypeid'), 35);
             }
         }
@@ -181,8 +180,7 @@ abstract class kolab_api_service
      */
     protected function object_types($object_name)
     {
-        $supported = array('group', 'resource', 'role', 'user');
-        if (!$object_name || !in_array($object_name, $supported)) {
+        if (!$object_name || !in_array($object_name, $this->supported_types_db)) {
             return array();
         }
 


commit 7119549421ef1e6d4c7ad00a417a780aceb662f6
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Sep 24 14:30:00 2012 +0200

    Added affected_rows() method

diff --git a/lib/SQL.php b/lib/SQL.php
index b3d3823..d5b6737 100644
--- a/lib/SQL.php
+++ b/lib/SQL.php
@@ -98,6 +98,11 @@ class SQL
         return mysql_fetch_assoc($result);
     }
 
+    public function affected_rows()
+    {
+        return mysql_affected_rows($this->conn);
+    }
+
     public function escape($str)
     {
         if ($str === null || is_array($str)) {


commit f0d3e1c8e578b0e34cc8b0398c573b78ec790ddf
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Sep 21 15:23:29 2012 +0200

    Add 'type' service skeleton

diff --git a/lib/api/kolab_api_service_type.php b/lib/api/kolab_api_service_type.php
new file mode 100644
index 0000000..54d1464
--- /dev/null
+++ b/lib/api/kolab_api_service_type.php
@@ -0,0 +1,174 @@
+<?php
+/*
+ +--------------------------------------------------------------------------+
+ | This file is part of the Kolab Web Admin Panel                           |
+ |                                                                          |
+ | Copyright (C) 2011-2012, Kolab Systems AG                                |
+ |                                                                          |
+ | This program is free software: you can redistribute it and/or modify     |
+ | it under the terms of the GNU Affero General Public License as published |
+ | by the Free Software Foundation, either version 3 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 Affero General Public License for more details.                      |
+ |                                                                          |
+ | You should have received a copy of the GNU Affero General Public License |
+ | along with this program. If not, see <http://www.gnu.org/licenses/>      |
+ +--------------------------------------------------------------------------+
+ | Author: Aleksander Machniak <machniak at kolabsys.com>                      |
+ | Author: Jeroen van Meeuwen <vanmeeuwen at kolabsys.com>                     |
+ +--------------------------------------------------------------------------+
+*/
+
+/**
+ * Service providing user data management
+ */
+class kolab_api_service_type extends kolab_api_service
+{
+    /**
+     * Returns service capabilities.
+     *
+     * @param string $domain Domain name
+     *
+     * @return array Capabilities list
+     */
+    public function capabilities($domain)
+    {
+        $auth = Auth::get_instance();
+
+        //$effective_rights = $auth->list_rights('user');
+
+        $rights = array();
+
+        // @TODO: set rights according to user group or sth
+        if ($_SESSION['user']->get_userid() == 'cn=Directory Manager') {
+            $rights['add'] = "w";
+            $rights['delete'] = "w";
+            $rights['edit'] = "w";
+        }
+
+        $rights['effective_rights'] = "r";
+
+        return $rights;
+    }
+
+    /**
+     * Create user.
+     *
+     * @param array $get   GET parameters
+     * @param array $post  POST parameters
+     *
+     * @return array|bool User attributes or False on error.
+     */
+    public function type_add($getdata, $postdata)
+    {
+        //console("type_add()", $postdata);
+
+        $type_attributes = $this->parse_input_attributes('type', $postdata);
+
+        //console("type_add()", $type_attributes);
+
+//        $auth   = Auth::get_instance();
+//        $result = $auth->type_add($type_attributes, $postdata['type_id']);
+
+        if ($result) {
+            return $type_attributes;
+        }
+
+        return false;
+    }
+
+    /**
+     * Detete type.
+     *
+     * @param array $get  GET parameters
+     * @param array $post POST parameters
+     *
+     * @return bool True on success, False on failure
+     */
+    public function type_delete($getdata, $postdata)
+    {
+        //console("type_delete()", $getdata, $postdata);
+        if (!isset($postdata['type'])) {
+            return false;
+        }
+
+        // TODO: Input validation
+//        $auth   = Auth::get_instance();
+//        $result = $auth->type_delete($postdata['type']);
+
+        if ($result) {
+            return $result;
+        }
+
+        return false;
+    }
+
+    /**
+     * Update type.
+     *
+     * @param array $get  GET parameters
+     * @param array $post POST parameters
+     *
+     * @return bool True on success, False on failure
+     */
+    public function type_edit($getdata, $postdata)
+    {
+        //console("\$postdata to type_edit()", $postdata);
+
+        $type_attributes = $this->parse_input_attributes('type', $postdata);
+        $type            = $postdata['id'];
+
+//        $auth   = Auth::get_instance();
+//        $result = $auth->type_edit($type, $type_attributes, $postdata['type_id']);
+
+        // Return the $mod_array
+        if ($result) {
+            return $result;
+        }
+
+        return false;
+
+    }
+
+    public function type_effective_rights($getdata, $postdata)
+    {
+//        $auth = Auth::get_instance();
+//        $effective_rights = $auth->list_rights(empty($getdata['user']) ? 'user' : $getdata['user']);
+//        return $effective_rights;
+        return array();
+    }
+
+    /**
+     * User information.
+     *
+     * @param array $get  GET parameters
+     * @param array $post POST parameters
+     *
+     * @return array|bool User attributes, False on error
+     */
+    public function type_info($getdata, $postdata)
+    {
+        if (!isset($getdata['type'])) {
+            return false;
+        }
+
+//        $auth   = Auth::get_instance();
+//        $result = $auth->type_info($getdata['type']);
+
+//        Log::trace("type.info on " . $getdata['type'] . " result: " . var_export($result, TRUE));
+        // normalize result
+//        $result = $this->parse_result_attributes('type', $result);
+
+//        Log::trace("type.info on " . $getdata['type'] . " parsed result: " . var_export($result, TRUE));
+
+        if ($result) {
+            return $result;
+        }
+
+        return false;
+    }
+}
diff --git a/lib/kolab_api_controller.php b/lib/kolab_api_controller.php
index 3cfa54f..8e9fc30 100644
--- a/lib/kolab_api_controller.php
+++ b/lib/kolab_api_controller.php
@@ -57,6 +57,7 @@ class kolab_api_controller
 
         // TODO: register services based on config or whatsoever
         $this->add_service('domain',            'kolab_api_service_domain');
+        $this->add_service('domain_types',      'kolab_api_service_domain_types');
         $this->add_service('domains',           'kolab_api_service_domains');
         $this->add_service('form_value',        'kolab_api_service_form_value');
         $this->add_service('group_types',       'kolab_api_service_group_types');
@@ -68,6 +69,7 @@ class kolab_api_controller
         $this->add_service('roles',             'kolab_api_service_roles');
         $this->add_service('role',              'kolab_api_service_role');
         $this->add_service('role_types',        'kolab_api_service_role_types');
+        $this->add_service('type',              'kolab_api_service_type');
         $this->add_service('user_types',        'kolab_api_service_user_types');
         $this->add_service('user',              'kolab_api_service_user');
         $this->add_service('users',             'kolab_api_service_users');


commit 2cffc441819e351313f693ccaa4329563d2e9e84
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Sep 21 15:01:55 2012 +0200

    Sort object types list by name

diff --git a/lib/kolab_api_service.php b/lib/kolab_api_service.php
index ad5c051..3fcefda 100644
--- a/lib/kolab_api_service.php
+++ b/lib/kolab_api_service.php
@@ -201,7 +201,7 @@ abstract class kolab_api_service
             $unique_attr = 'nsuniqueid';
         }
 
-        $sql_result   = $this->db->query("SELECT * FROM {$object_name}_types");
+        $sql_result   = $this->db->query("SELECT * FROM {$object_name}_types ORDER BY name");
         $object_types = array();
 
         while ($row = $this->db->fetch_assoc($sql_result)) {


commit fbf00358086c7ab637b518e2d92a0b7d1f54978a
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Sep 21 14:10:57 2012 +0200

    Create domain_types service, @TODO: move types array into database

diff --git a/lib/api/kolab_api_service_domain_types.php b/lib/api/kolab_api_service_domain_types.php
new file mode 100644
index 0000000..a84cf82
--- /dev/null
+++ b/lib/api/kolab_api_service_domain_types.php
@@ -0,0 +1,86 @@
+<?php
+/*
+ +--------------------------------------------------------------------------+
+ | This file is part of the Kolab Web Admin Panel                           |
+ |                                                                          |
+ | Copyright (C) 2011-2012, Kolab Systems AG                                |
+ |                                                                          |
+ | This program is free software: you can redistribute it and/or modify     |
+ | it under the terms of the GNU Affero General Public License as published |
+ | by the Free Software Foundation, either version 3 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 Affero General Public License for more details.                      |
+ |                                                                          |
+ | You should have received a copy of the GNU Affero General Public License |
+ | along with this program. If not, see <http://www.gnu.org/licenses/>      |
+ +--------------------------------------------------------------------------+
+ | Author: Aleksander Machniak <machniak at kolabsys.com>                      |
+ | Author: Jeroen van Meeuwen <vanmeeuwen at kolabsys.com>                     |
+ +--------------------------------------------------------------------------+
+*/
+
+/**
+ *
+ */
+class kolab_api_service_domain_types extends kolab_api_service
+{
+    /**
+     * Returns service capabilities.
+     *
+     * @param string $domain Domain name
+     *
+     * @return array Capabilities list
+     */
+    public function capabilities($domain)
+    {
+        return array(
+            'list' => 'r',
+        );
+    }
+
+    /**
+     * Domain types listing.
+     *
+     * @param array $get  GET parameters
+     * @param array $post POST parameters
+     *
+     * @return array List result with 'list' and 'count' items
+     */
+    public function domain_types_list($get, $post)
+    {
+        // @TODO: move to database
+        $types = array(
+            1 => array(
+                'key' => 'standard',
+                'name' => 'Standard domain',
+                'description' => 'A standard domain name space',
+                'attributes' => array(
+                    'auto_form_fields' => array(),
+                    'form_fields' => array(
+                        'associateddomain' => array(
+                            'type' => 'list',
+                        ),
+                        'inetdomainbasedn' => array(
+                            'optional' => 'true',
+                        ),
+                    ),
+                    'fields' => array(
+                        'objectclass' => array(
+                            'top',
+                            'domainrelatedobject',
+                        ),
+                    ),
+                ),
+            ),
+        );
+
+        return array(
+            'list'  => $types,
+            'count' => count($types),
+        );
+    }
+}


commit 8622c0a5be40f9b3a134592e6cbd1a18b2b7a0d6
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Sep 21 12:15:52 2012 +0200

    Enable effective_rights API command for group and role

diff --git a/lib/api/kolab_api_service_group.php b/lib/api/kolab_api_service_group.php
index c41dd3c..9357e74 100644
--- a/lib/api/kolab_api_service_group.php
+++ b/lib/api/kolab_api_service_group.php
@@ -64,6 +64,8 @@ class kolab_api_service_group extends kolab_api_service
             $rights['members_list'] = "r";
         }
 
+        $rights['effective_rights'] = "r";
+
         return $rights;
     }
 


commit af103d9e992762743690f365ea0813fac8137cf0
Author: Torsten Grote <t at grobox.de>
Date:   Wed Sep 19 18:35:28 2012 +0200

    updated translations from transifex

diff --git a/lib/locale/de.php b/lib/locale/de.php
index 1429a4b..04720e0 100644
--- a/lib/locale/de.php
+++ b/lib/locale/de.php
@@ -1,8 +1,12 @@
 <?php
 
 $LANG['about.community'] = 'Dies ist die Community-Edition des <b>Kolab Servers</b>.';
-$LANG['about.warranty'] = 'Sie kommt mit <b>keinen Garantien</b> und wird normalerweise ohne professionelle Unterstützung eingesetzt. Hilfe und weitere Informationen finden Sie auf der <a href="http://kolab.org">Web-Seite</a> und im <a href="http://wiki.kolab.org">Wiki</a>.';
+$LANG['about.kolab'] = 'Kolab';
+$LANG['about.kolabsys'] = 'Kolab Systems';
 $LANG['about.support'] = 'Professionelle Unterstützung gibt es u.A. von <a href="http://kolabsys.com">Kolab Systems</a>.';
+$LANG['about.technology'] = 'Technologie';
+$LANG['about.warranty'] = 'Sie kommt mit <b>keinen Garantien</b> und wird normalerweise ohne professionelle Unterstützung eingesetzt. Hilfe und weitere Informationen finden Sie auf der <a href="http://kolab.org">Web-Seite</a> und im <a href="http://wiki.kolab.org">Wiki</a>.';
+
 $LANG['creatorsname'] = 'Erstellt von';
 $LANG['days'] = 'Tage';
 $LANG['debug'] = 'Debug Informationen';
@@ -56,11 +60,9 @@ $LANG['MB'] = 'MB';
 $LANG['menu.about'] = 'Ãœber';
 $LANG['menu.domains'] = 'Domains';
 $LANG['menu.groups'] = 'Gruppen';
-$LANG['menu.kolab'] = 'Kolab';
-$LANG['menu.kolabsys'] = 'Kolab Systems';
 $LANG['menu.resources'] = 'Ressourcen';
 $LANG['menu.roles'] = 'Rollen';
-$LANG['menu.technology'] = 'Technologie';
+$LANG['menu.settings'] = 'Einstellungen';
 $LANG['menu.users'] = 'Benutzer';
 
 $LANG['modifiersname'] = 'Geändert von';
@@ -86,14 +88,14 @@ $LANG['resource.type_id'] = 'Ressourcentyp';
 $LANG['resource.uniquemember'] = 'Mitglieder der Sammlung';
 
 $LANG['role.add'] = 'Rolle hinzufügen';
-$LANG['role.cn'] = 'Role Name';
-$LANG['role.description'] = 'Role Description';
-$LANG['role.edit.success'] = 'Role edited successfully';
+$LANG['role.cn'] = 'Rollenname';
+$LANG['role.description'] = 'Rollenbeschreibung';
+$LANG['role.edit.success'] = 'Rolle erfolgreich bearbeitet';
 $LANG['role.list'] = 'Rollenliste';
 $LANG['role.list.records'] = '$1 zu $2 von $3';
 $LANG['role.norecords'] = 'Keine Rolleneinträge gefunden!';
 $LANG['role.system'] = 'Details';
-$LANG['role.type_id'] = 'Role Type';
+$LANG['role.type_id'] = 'Rollentyp';
 
 $LANG['saving'] = 'Speichere...';
 
@@ -116,7 +118,7 @@ $LANG['servererror'] = 'Server Fehler!';
 $LANG['session.expired'] = 'Die Sitzung ist ausgelaufen. Bitte wieder Anmelden';
 
 $LANG['signup.headline'] = 'Anmeldung für ein Kolab Konto';
-$LANG['signup.intro1'] = 'Ein Kolab E-Mail Adresse ist viel besser als eine normale E-Mail. Denn mit ihr kommen Möglichkeiten zur Syncronisation und der gemeinsamen Nutzung von Adressbüchern, Kalendern, Aufgaben, Notizen und vielem mehr.';
+$LANG['signup.intro1'] = 'Ein Kolab E-Mail Adresse ist viel besser als eine normale E-Mail. Denn mit ihr kommen Möglichkeiten zur Synchronisation und der gemeinsamen Nutzung von Adressbüchern, Kalendern, Aufgaben, Notizen und vielem mehr.';
 $LANG['signup.intro2'] = 'Sie können sich hier eine E-Mail Adresse registrieren.';
 $LANG['signup.formtitle'] = 'Anmelden';
 $LANG['signup.username'] = 'Benutzername';
@@ -139,7 +141,7 @@ $LANG['user.alias'] = 'Sekundäre Email-Adresse';
 $LANG['user.astaccountallowedcodec'] = 'Erlaubte(r) Codec(s)';
 $LANG['user.astaccountcallerid'] = 'Anrufer ID';
 $LANG['user.astaccountcontext'] = 'Konto-Kontext';
-$LANG['user.astaccountdeny'] = 'Account deny';
+$LANG['user.astaccountdeny'] = 'Account verweigern';
 $LANG['user.astaccounthost'] = 'Asterisk Server';
 $LANG['user.astaccountnat'] = 'Account benutzt NAT';
 $LANG['user.astaccountname'] = 'Asterisk Kontoname';
@@ -162,7 +164,7 @@ $LANG['user.delete.success'] = 'Benutzer erfolgreich gelöscht.';
 $LANG['user.displayname'] = 'Anzeigename';
 $LANG['user.edit.success'] = 'Benutzer erfolgreich bearbeitet';
 $LANG['user.fax'] = 'Faxnummer';
-$LANG['user.fbinterval'] = 'Free-Busy interval';
+$LANG['user.fbinterval'] = 'Frei-Beschäftigt Intervall';
 $LANG['user.fbinterval.desc'] = 'Leer lassen für Standardwert (60 Tage)';
 $LANG['user.gidnumber'] = 'Primäre Gruppennummer';
 $LANG['user.givenname'] = 'Nachname';
@@ -196,10 +198,10 @@ $LANG['user.orgunit'] = 'Organisationseinheit';
 $LANG['user.ou'] = 'Organisationseinheit';
 $LANG['user.pager'] = 'Pager Nummer';
 $LANG['user.password.mismatch'] = 'Passwörter stimmen nicht überein!';
-$LANG['user.personal'] = 'Personal';
+$LANG['user.personal'] = 'Persönlich';
 $LANG['user.phone'] = 'Telefonnummer';
 $LANG['user.postalcode'] = 'Postleitzahl';
-$LANG['user.postbox'] = 'Postal box';
+$LANG['user.postbox'] = 'Briefkasten';
 $LANG['user.postcode'] = 'Postleitzahl';
 $LANG['user.preferredlanguage'] = 'Muttersprache';
 $LANG['user.room'] = 'Zimmernummer';
@@ -207,9 +209,9 @@ $LANG['user.sn'] = 'Vorname';
 $LANG['user.street'] = 'Straße';
 $LANG['user.system'] = 'System';
 $LANG['user.telephonenumber'] = 'Telefonnummer';
-$LANG['user.title'] = 'Job Title';
+$LANG['user.title'] = 'Jobbezeichnung';
 $LANG['user.type_id'] = 'Kontotyp';
-$LANG['user.uid'] = 'Unique identity (UID)';
+$LANG['user.uid'] = 'Eindeutige Identifizierung (UID)';
 $LANG['user.userpassword'] = 'Passwort';
 $LANG['user.userpassword2'] = 'Passwort bestätigen';
 $LANG['user.uidnumber'] = 'Benutzer ID Nummer';
diff --git a/lib/locale/de_DE.php b/lib/locale/de_DE.php
index b238243..54d4b5e 100644
--- a/lib/locale/de_DE.php
+++ b/lib/locale/de_DE.php
@@ -1,8 +1,12 @@
 <?php
 
 $LANG['about.community'] = 'Dies ist die Community-Edition des <b>Kolab Servers</b>';
-$LANG['about.warranty'] = 'Sie beinhaltet <b>keinerlei Garantie</b> und wird normalerweise ausschließlich mit eigener Unterstützung betrieben. Weitere Hilfe und Informationen finden Sie auf Webseite <a href="http://kolab.org">web site</a> und im <a href="http://wiki.kolab.org">Wiki</a> der Community.';
+$LANG['about.kolab'] = 'Kolab';
+$LANG['about.kolabsys'] = 'Kolab Systems';
 $LANG['about.support'] = 'Professioneller Support ist erhältlich bei <a href="http://kolabsys.com">Kolab Systems</a>.';
+$LANG['about.technology'] = 'Technologie';
+$LANG['about.warranty'] = 'Sie beinhaltet <b>keinerlei Garantie</b> und wird normalerweise ausschließlich mit eigener Unterstützung betrieben. Weitere Hilfe und Informationen finden Sie auf Webseite <a href="http://kolab.org">web site</a> und im <a href="http://wiki.kolab.org">Wiki</a> der Community.';
+
 $LANG['creatorsname'] = 'Erzeugt von';
 $LANG['days'] = 'Tagen erneut senden';
 $LANG['debug'] = 'Debug info';
@@ -56,11 +60,9 @@ $LANG['MB'] = 'MB';
 $LANG['menu.about'] = 'Info';
 $LANG['menu.domains'] = 'Domänen';
 $LANG['menu.groups'] = 'Gruppen';
-$LANG['menu.kolab'] = 'Kolab';
-$LANG['menu.kolabsys'] = 'Kolab Systems';
 $LANG['menu.resources'] = 'Ressourcen';
 $LANG['menu.roles'] = 'Rollen';
-$LANG['menu.technology'] = 'Technologie';
+$LANG['menu.settings'] = 'Einstellungen';
 $LANG['menu.users'] = 'Benutzer';
 
 $LANG['modifiersname'] = 'Geändert von';
@@ -115,6 +117,22 @@ $LANG['servererror'] = 'Serverfehler!';
 
 $LANG['session.expired'] = 'Sitzung abgelaufen. Bitte melden Sie sich erneut an.';
 
+$LANG['signup.headline'] = 'Sign Up for Hosted Kolab';
+$LANG['signup.intro1'] = 'Having an account on a Kolab server is way better than just simple Email. It also provides you with full groupware functionality including synchronization for shared addressbooks, calendars, tasks, journal and more.';
+$LANG['signup.intro2'] = 'You can sign up here now for an account.';
+$LANG['signup.formtitle'] = 'Sign Up';
+$LANG['signup.username'] = 'Username';
+$LANG['signup.domain'] = 'Domäne';
+$LANG['signup.mailalternateaddress'] = 'Current Email Address';
+$LANG['signup.futuremail'] = 'Future Email Address';
+$LANG['signup.company'] = 'Company';
+$LANG['signup.captcha'] = 'CAPTCHA';
+$LANG['signup.userexists'] = 'User already exists!';
+$LANG['signup.usercreated'] = '<h3>Your account has been successfully added!</h3>Congratulations, you now have your own Kolab account.';
+$LANG['signup.wronguid'] = 'Invalid Username!';
+$LANG['signup.wrongmailalternateaddress'] = 'Please provide a valid Email Address!';
+$LANG['signup.footer'] = 'This is a service offered by <a href="http://kolabsys.com">Kolab Systems</a>.';
+
 $LANG['submit.button'] = 'Absenden';
 
 $LANG['user.add'] = 'Benutzer hinzufügen';
@@ -148,7 +166,7 @@ $LANG['user.edit.success'] = 'Benutzer erfolgreich bearbeitet.';
 $LANG['user.fax'] = 'Faxnummer';
 $LANG['user.fbinterval'] = 'Frei/Belegt-Intervall';
 $LANG['user.fbinterval.desc'] = 'Freilassen für die Voreinstellung (60 Tage)';
-$LANG['user.gidnumber'] = 'Primary group number';
+$LANG['user.gidnumber'] = 'Primäre Gruppennummer';
 $LANG['user.givenname'] = 'Vorname';
 $LANG['user.homedirectory'] = 'Heimatverzeichnis';
 $LANG['user.homephone'] = 'Home Phone Number';
diff --git a/lib/locale/en.php b/lib/locale/en.php
index 75d1f6d..4527251 100644
--- a/lib/locale/en.php
+++ b/lib/locale/en.php
@@ -27,7 +27,6 @@ $LANG['domain.system'] = 'System';
 $LANG['domain.type_id'] = 'Standard Domain';
 
 $LANG['error'] = 'Error';
-$LANG['error.domainselect'] = 'Unable to change domain!';
 
 $LANG['form.required.empty'] = 'Some of the required fields are empty!';
 $LANG['form.maxcount.exceeded'] = 'Maximum count of items exceeded!';
@@ -51,7 +50,6 @@ $LANG['group.uniquemember'] = 'Members';
 $LANG['info'] = 'Information';
 $LANG['internalerror'] = 'Internal system error!';
 $LANG['loading'] = 'Loading...';
-
 $LANG['login.username'] = 'Username:';
 $LANG['login.password'] = 'Password:';
 $LANG['login.domain'] = 'Domain:';
diff --git a/lib/locale/es.php b/lib/locale/es.php
index 37d1f2c..ad8f349 100644
--- a/lib/locale/es.php
+++ b/lib/locale/es.php
@@ -1,8 +1,12 @@
 <?php
 
 $LANG['about.community'] = 'Esta es la edición Community de <b>Kolab Server</b>.';
-$LANG['about.warranty'] = 'Viene absolutamente <b>sin ninguna garantia</b> y normalmente se ejecuta sin ningun tipo de soporte. Puedes obetner ayuda y más información en el <a href="http://kolab.org">sitio web</a> de la comunidad y en el <a href="http://wiki.kolab.org">wiki</a>.';
+$LANG['about.kolab'] = 'Kolab';
+$LANG['about.kolabsys'] = 'Kolab Systems';
 $LANG['about.support'] = 'Professional support is available from <a href="http://kolabsys.com">Kolab Systems</a>.';
+$LANG['about.technology'] = 'Tecnología';
+$LANG['about.warranty'] = 'Viene absolutamente <b>sin ninguna garantia</b> y normalmente se ejecuta sin ningun tipo de soporte. Puedes obetner ayuda y más información en el <a href="http://kolab.org">sitio web</a> de la comunidad y en el <a href="http://wiki.kolab.org">wiki</a>.';
+
 $LANG['creatorsname'] = 'Creado por';
 $LANG['days'] = 'días';
 $LANG['debug'] = 'Información de depuración';
@@ -56,11 +60,9 @@ $LANG['MB'] = 'MB';
 $LANG['menu.about'] = 'Sobre Kolab';
 $LANG['menu.domains'] = 'Dominios';
 $LANG['menu.groups'] = 'Grupos';
-$LANG['menu.kolab'] = 'Kolab';
-$LANG['menu.kolabsys'] = 'Kolab Systems';
 $LANG['menu.resources'] = 'Recursos';
 $LANG['menu.roles'] = 'Roles';
-$LANG['menu.technology'] = 'Tecnología';
+$LANG['menu.settings'] = 'Servicios';
 $LANG['menu.users'] = 'Usuarios';
 
 $LANG['modifiersname'] = 'Modificado por';
@@ -115,6 +117,22 @@ $LANG['servererror'] = '¡Error del servidor!';
 
 $LANG['session.expired'] = 'La sesión ha finalizado. Por faovr, inicie la sesión de nuevo';
 
+$LANG['signup.headline'] = 'Sign Up for Hosted Kolab';
+$LANG['signup.intro1'] = 'Having an account on a Kolab server is way better than just simple Email. It also provides you with full groupware functionality including synchronization for shared addressbooks, calendars, tasks, journal and more.';
+$LANG['signup.intro2'] = 'You can sign up here now for an account.';
+$LANG['signup.formtitle'] = 'Sign Up';
+$LANG['signup.username'] = 'Username';
+$LANG['signup.domain'] = 'Dominio';
+$LANG['signup.mailalternateaddress'] = 'Current Email Address';
+$LANG['signup.futuremail'] = 'Future Email Address';
+$LANG['signup.company'] = 'Company';
+$LANG['signup.captcha'] = 'CAPTCHA';
+$LANG['signup.userexists'] = 'User already exists!';
+$LANG['signup.usercreated'] = '<h3>Your account has been successfully added!</h3>Congratulations, you now have your own Kolab account.';
+$LANG['signup.wronguid'] = 'Invalid Username!';
+$LANG['signup.wrongmailalternateaddress'] = 'Please provide a valid Email Address!';
+$LANG['signup.footer'] = 'This is a service offered by <a href="http://kolabsys.com">Kolab Systems</a>.';
+
 $LANG['submit.button'] = 'Enviar';
 
 $LANG['user.add'] = 'Añadir usuario';
diff --git a/lib/locale/et_EE.php b/lib/locale/et_EE.php
index 791aeaa..31c1120 100644
--- a/lib/locale/et_EE.php
+++ b/lib/locale/et_EE.php
@@ -1,8 +1,12 @@
 <?php
 
 $LANG['about.community'] = 'This is the Community Edition of the <b>Kolab Server</b>.';
-$LANG['about.warranty'] = 'It comes with absolutely <b>no warranties</b> and is typically run entirely self supported. You can find help & information on the community <a href="http://kolab.org">web site</a> & <a href="http://wiki.kolab.org">wiki</a>.';
+$LANG['about.kolab'] = 'Kolab';
+$LANG['about.kolabsys'] = 'Kolab Systems';
 $LANG['about.support'] = 'Professional support is available from <a href="http://kolabsys.com">Kolab Systems</a>.';
+$LANG['about.technology'] = 'Tehnoloogia';
+$LANG['about.warranty'] = 'It comes with absolutely <b>no warranties</b> and is typically run entirely self supported. You can find help & information on the community <a href="http://kolab.org">web site</a> & <a href="http://wiki.kolab.org">wiki</a>.';
+
 $LANG['creatorsname'] = 'Created by';
 $LANG['days'] = 'days';
 $LANG['debug'] = 'Debug info';
@@ -56,11 +60,9 @@ $LANG['MB'] = 'MB';
 $LANG['menu.about'] = 'About';
 $LANG['menu.domains'] = 'Domeenid';
 $LANG['menu.groups'] = 'Groups';
-$LANG['menu.kolab'] = 'Kolab';
-$LANG['menu.kolabsys'] = 'Kolab Systems';
 $LANG['menu.resources'] = 'Resources';
 $LANG['menu.roles'] = 'Roles';
-$LANG['menu.technology'] = 'Tehnoloogia';
+$LANG['menu.settings'] = 'Settings';
 $LANG['menu.users'] = 'Kasutajad';
 
 $LANG['modifiersname'] = 'Modified by';
@@ -115,6 +117,22 @@ $LANG['servererror'] = 'Server Error!';
 
 $LANG['session.expired'] = 'Session has expired. Login again, please';
 
+$LANG['signup.headline'] = 'Sign Up for Hosted Kolab';
+$LANG['signup.intro1'] = 'Having an account on a Kolab server is way better than just simple Email. It also provides you with full groupware functionality including synchronization for shared addressbooks, calendars, tasks, journal and more.';
+$LANG['signup.intro2'] = 'You can sign up here now for an account.';
+$LANG['signup.formtitle'] = 'Sign Up';
+$LANG['signup.username'] = 'Username';
+$LANG['signup.domain'] = 'Domeen';
+$LANG['signup.mailalternateaddress'] = 'Current Email Address';
+$LANG['signup.futuremail'] = 'Future Email Address';
+$LANG['signup.company'] = 'Company';
+$LANG['signup.captcha'] = 'CAPTCHA';
+$LANG['signup.userexists'] = 'User already exists!';
+$LANG['signup.usercreated'] = '<h3>Your account has been successfully added!</h3>Congratulations, you now have your own Kolab account.';
+$LANG['signup.wronguid'] = 'Invalid Username!';
+$LANG['signup.wrongmailalternateaddress'] = 'Please provide a valid Email Address!';
+$LANG['signup.footer'] = 'This is a service offered by <a href="http://kolabsys.com">Kolab Systems</a>.';
+
 $LANG['submit.button'] = 'Submit';
 
 $LANG['user.add'] = 'Add User';
diff --git a/lib/locale/ja.php b/lib/locale/ja.php
index a9fde09..1440161 100644
--- a/lib/locale/ja.php
+++ b/lib/locale/ja.php
@@ -1,8 +1,12 @@
 <?php
 
 $LANG['about.community'] = 'This is the Community Edition of the <b>Kolab Server</b>.';
-$LANG['about.warranty'] = 'It comes with absolutely <b>no warranties</b> and is typically run entirely self supported. You can find help & information on the community <a href="http://kolab.org">web site</a> & <a href="http://wiki.kolab.org">wiki</a>.';
+$LANG['about.kolab'] = 'Kolab';
+$LANG['about.kolabsys'] = 'Kolab Systems';
 $LANG['about.support'] = 'Professional support is available from <a href="http://kolabsys.com">Kolab Systems</a>.';
+$LANG['about.technology'] = 'Technology';
+$LANG['about.warranty'] = 'It comes with absolutely <b>no warranties</b> and is typically run entirely self supported. You can find help & information on the community <a href="http://kolab.org">web site</a> & <a href="http://wiki.kolab.org">wiki</a>.';
+
 $LANG['creatorsname'] = 'Created by';
 $LANG['days'] = 'days';
 $LANG['debug'] = 'Debug info';
@@ -56,11 +60,9 @@ $LANG['MB'] = 'MB';
 $LANG['menu.about'] = 'About';
 $LANG['menu.domains'] = 'Domains';
 $LANG['menu.groups'] = 'Groups';
-$LANG['menu.kolab'] = 'Kolab';
-$LANG['menu.kolabsys'] = 'Kolab Systems';
 $LANG['menu.resources'] = 'Resources';
 $LANG['menu.roles'] = 'Roles';
-$LANG['menu.technology'] = 'Technology';
+$LANG['menu.settings'] = 'Settings';
 $LANG['menu.users'] = 'Users';
 
 $LANG['modifiersname'] = 'Modified by';
@@ -115,6 +117,22 @@ $LANG['servererror'] = 'Server Error!';
 
 $LANG['session.expired'] = 'Session has expired. Login again, please';
 
+$LANG['signup.headline'] = 'Sign Up for Hosted Kolab';
+$LANG['signup.intro1'] = 'Having an account on a Kolab server is way better than just simple Email. It also provides you with full groupware functionality including synchronization for shared addressbooks, calendars, tasks, journal and more.';
+$LANG['signup.intro2'] = 'You can sign up here now for an account.';
+$LANG['signup.formtitle'] = 'Sign Up';
+$LANG['signup.username'] = 'Username';
+$LANG['signup.domain'] = 'Domain';
+$LANG['signup.mailalternateaddress'] = 'Current Email Address';
+$LANG['signup.futuremail'] = 'Future Email Address';
+$LANG['signup.company'] = 'Company';
+$LANG['signup.captcha'] = 'CAPTCHA';
+$LANG['signup.userexists'] = 'User already exists!';
+$LANG['signup.usercreated'] = '<h3>Your account has been successfully added!</h3>Congratulations, you now have your own Kolab account.';
+$LANG['signup.wronguid'] = 'Invalid Username!';
+$LANG['signup.wrongmailalternateaddress'] = 'Please provide a valid Email Address!';
+$LANG['signup.footer'] = 'This is a service offered by <a href="http://kolabsys.com">Kolab Systems</a>.';
+
 $LANG['submit.button'] = 'Submit';
 
 $LANG['user.add'] = 'Add User';
@@ -137,7 +155,7 @@ $LANG['user.astextension'] = 'Asterisk Extension';
 $LANG['user.c'] = 'Country';
 $LANG['user.city'] = 'City';
 $LANG['user.cn'] = 'Common name';
-$LANG['user.config'] = 'Configuration';
+$LANG['user.config'] = '設定';
 $LANG['user.contact'] = 'Contact';
 $LANG['user.contact_info'] = 'Contact Information';
 $LANG['user.country'] = 'Country';
diff --git a/lib/locale/nl.php b/lib/locale/nl.php
index f43181e..6b5d896 100644
--- a/lib/locale/nl.php
+++ b/lib/locale/nl.php
@@ -1,8 +1,12 @@
 <?php
 
 $LANG['about.community'] = 'Dit is de community editie van de <b>Kolab Server</b>';
-$LANG['about.warranty'] = 'Het komt <b>zonder enige garanties</b> en wordt typisch geheel zelf ondersteund. U kunt help & informatie vinden in de community <a href="http://kolab.org">web site</a> & <a href="http://wiki.kolab.org">wiki</a>.';
+$LANG['about.kolab'] = 'Kolab';
+$LANG['about.kolabsys'] = 'Kolab Systems';
 $LANG['about.support'] = 'Professionele ondersteuning is beschikbaar bij <a href="http://kolabsys.com">Kolab Systems</a>';
+$LANG['about.technology'] = 'Technologie';
+$LANG['about.warranty'] = 'Het komt <b>zonder enige garanties</b> en wordt typisch geheel zelf ondersteund. U kunt help & informatie vinden in de community <a href="http://kolab.org">web site</a> & <a href="http://wiki.kolab.org">wiki</a>.';
+
 $LANG['creatorsname'] = 'Gemaakt door';
 $LANG['days'] = 'dagen';
 $LANG['debug'] = 'Debug informatie';
@@ -56,11 +60,9 @@ $LANG['MB'] = 'MB';
 $LANG['menu.about'] = 'Over';
 $LANG['menu.domains'] = 'Domeinen';
 $LANG['menu.groups'] = 'Groepen';
-$LANG['menu.kolab'] = 'Kolab';
-$LANG['menu.kolabsys'] = 'Kolab Systems';
 $LANG['menu.resources'] = 'Middelen';
 $LANG['menu.roles'] = 'Rollen';
-$LANG['menu.technology'] = 'Technologie';
+$LANG['menu.settings'] = 'Settings';
 $LANG['menu.users'] = 'Gebruikers';
 
 $LANG['modifiersname'] = 'Laatst gewijzigd door';
@@ -86,14 +88,14 @@ $LANG['resource.type_id'] = 'Type Middel';
 $LANG['resource.uniquemember'] = 'Middellen in Collectie';
 
 $LANG['role.add'] = 'Rol Toevoegen';
-$LANG['role.cn'] = 'Role Name';
-$LANG['role.description'] = 'Role Description';
-$LANG['role.edit.success'] = 'Role edited successfully';
+$LANG['role.cn'] = 'Rol naam';
+$LANG['role.description'] = 'Rol uitleg';
+$LANG['role.edit.success'] = 'Rol succesvol gewijzigd';
 $LANG['role.list'] = 'Rollen Lijst';
 $LANG['role.list.records'] = '$1 tot $2 van $3';
 $LANG['role.norecords'] = 'Geen rollen gevonden!';
 $LANG['role.system'] = 'Details';
-$LANG['role.type_id'] = 'Role Type';
+$LANG['role.type_id'] = 'Rol Type';
 
 $LANG['saving'] = 'Bezig data op te slaan...';
 
@@ -115,6 +117,22 @@ $LANG['servererror'] = 'Server Fout!';
 
 $LANG['session.expired'] = 'Sessie verlopen. Log opnieuw in alstublieft.';
 
+$LANG['signup.headline'] = 'Sign Up for Hosted Kolab';
+$LANG['signup.intro1'] = 'Having an account on a Kolab server is way better than just simple Email. It also provides you with full groupware functionality including synchronization for shared addressbooks, calendars, tasks, journal and more.';
+$LANG['signup.intro2'] = 'You can sign up here now for an account.';
+$LANG['signup.formtitle'] = 'Sign Up';
+$LANG['signup.username'] = 'Username';
+$LANG['signup.domain'] = 'Domain';
+$LANG['signup.mailalternateaddress'] = 'Current Email Address';
+$LANG['signup.futuremail'] = 'Future Email Address';
+$LANG['signup.company'] = 'Company';
+$LANG['signup.captcha'] = 'CAPTCHA';
+$LANG['signup.userexists'] = 'User already exists!';
+$LANG['signup.usercreated'] = '<h3>Your account has been successfully added!</h3>Congratulations, you now have your own Kolab account.';
+$LANG['signup.wronguid'] = 'Invalid Username!';
+$LANG['signup.wrongmailalternateaddress'] = 'Please provide a valid Email Address!';
+$LANG['signup.footer'] = 'This is a service offered by <a href="http://kolabsys.com">Kolab Systems</a>.';
+
 $LANG['submit.button'] = 'Opslaan';
 
 $LANG['user.add'] = 'Gebruiker Toevoegen';
diff --git a/lib/locale/pl_PL.php b/lib/locale/pl_PL.php
index d48425e..5e5f86a 100644
--- a/lib/locale/pl_PL.php
+++ b/lib/locale/pl_PL.php
@@ -1,8 +1,12 @@
 <?php
 
 $LANG['about.community'] = 'To jest wersja społecznościowa <b>Serwera Kolab</b>';
-$LANG['about.warranty'] = 'Na funkcjonowanie oprogramowanie nieudzielana jest <b>żadna gwarancja</b>. Wsparcie techniczne organizowane jest we własnym zakresie. Pomoc i niezbędne informacje można znaleźć na stronie <a href=http://kolab.org">Kolab</a> oraz <a href="http://wiki.kolab.org">wiki</a>';
+$LANG['about.kolab'] = 'Kolab';
+$LANG['about.kolabsys'] = 'Kolab Systems';
 $LANG['about.support'] = 'Profesjonalne wsparcie techniczne jest dostępne na stronie <a href="http://kolabsys.com">Kolab Systems</a>.';
+$LANG['about.technology'] = 'Technologia';
+$LANG['about.warranty'] = 'Na funkcjonowanie oprogramowanie nieudzielana jest <b>żadna gwarancja</b>. Wsparcie techniczne organizowane jest we własnym zakresie. Pomoc i niezbędne informacje można znaleźć na stronie <a href=http://kolab.org">Kolab</a> oraz <a href="http://wiki.kolab.org">wiki</a>';
+
 $LANG['creatorsname'] = 'Stworzony przez';
 $LANG['days'] = 'dni';
 $LANG['debug'] = 'Debug info';
@@ -56,11 +60,9 @@ $LANG['MB'] = 'MB';
 $LANG['menu.about'] = 'O nas';
 $LANG['menu.domains'] = 'Domeny';
 $LANG['menu.groups'] = 'Grupy';
-$LANG['menu.kolab'] = 'Kolab';
-$LANG['menu.kolabsys'] = 'Kolab Systems';
 $LANG['menu.resources'] = 'Zasoby';
 $LANG['menu.roles'] = 'Role';
-$LANG['menu.technology'] = 'Technologia';
+$LANG['menu.settings'] = 'Settings';
 $LANG['menu.users'] = 'Użytkownicy';
 
 $LANG['modifiersname'] = 'Zmodyfikowane przez';
@@ -115,6 +117,22 @@ $LANG['servererror'] = 'BÅ‚Ä…d serwera!';
 
 $LANG['session.expired'] = 'Sesja wygasła. Proszę zalogować się ponownie';
 
+$LANG['signup.headline'] = 'Sign Up for Hosted Kolab';
+$LANG['signup.intro1'] = 'Having an account on a Kolab server is way better than just simple Email. It also provides you with full groupware functionality including synchronization for shared addressbooks, calendars, tasks, journal and more.';
+$LANG['signup.intro2'] = 'You can sign up here now for an account.';
+$LANG['signup.formtitle'] = 'Sign Up';
+$LANG['signup.username'] = 'Username';
+$LANG['signup.domain'] = 'Domain';
+$LANG['signup.mailalternateaddress'] = 'Current Email Address';
+$LANG['signup.futuremail'] = 'Future Email Address';
+$LANG['signup.company'] = 'Company';
+$LANG['signup.captcha'] = 'CAPTCHA';
+$LANG['signup.userexists'] = 'User already exists!';
+$LANG['signup.usercreated'] = '<h3>Your account has been successfully added!</h3>Congratulations, you now have your own Kolab account.';
+$LANG['signup.wronguid'] = 'Invalid Username!';
+$LANG['signup.wrongmailalternateaddress'] = 'Please provide a valid Email Address!';
+$LANG['signup.footer'] = 'This is a service offered by <a href="http://kolabsys.com">Kolab Systems</a>.';
+
 $LANG['submit.button'] = 'Zatwierdź';
 
 $LANG['user.add'] = 'Dodaj użytkownika';


commit 6a7d679f35c4ad9f5edcd49a63c954389cbfbba3
Author: Torsten Grote <grote at kolabsys.com>
Date:   Wed Sep 19 18:07:40 2012 +0200

    added signup translations to en_US locale

diff --git a/lib/locale/en_US.php b/lib/locale/en_US.php
index 7b56ba1..4527251 100644
--- a/lib/locale/en_US.php
+++ b/lib/locale/en_US.php
@@ -117,6 +117,22 @@ $LANG['servererror'] = 'Server Error!';
 
 $LANG['session.expired'] = 'Session has expired. Login again, please';
 
+$LANG['signup.headline'] = 'Sign Up for Hosted Kolab';
+$LANG['signup.intro1'] = 'Having an account on a Kolab server is way better than just simple Email. It also provides you with full groupware functionality including synchronization for shared addressbooks, calendars, tasks, journal and more.';
+$LANG['signup.intro2'] = 'You can sign up here now for an account.';
+$LANG['signup.formtitle'] = 'Sign Up';
+$LANG['signup.username'] = 'Username';
+$LANG['signup.domain'] = 'Domain';
+$LANG['signup.mailalternateaddress'] = 'Current Email Address';
+$LANG['signup.futuremail'] = 'Future Email Address';
+$LANG['signup.company'] = 'Company';
+$LANG['signup.captcha'] = 'CAPTCHA';
+$LANG['signup.userexists'] = 'User already exists!';
+$LANG['signup.usercreated'] = '<h3>Your account has been successfully added!</h3>Congratulations, you now have your own Kolab account.';
+$LANG['signup.wronguid'] = 'Invalid Username!';
+$LANG['signup.wrongmailalternateaddress'] = 'Please provide a valid Email Address!';
+$LANG['signup.footer'] = 'This is a service offered by <a href="http://kolabsys.com">Kolab Systems</a>.';
+
 $LANG['submit.button'] = 'Submit';
 
 $LANG['user.add'] = 'Add User';


commit c62bec8765d84d48e37f7d53bba818dba29925a5
Author: Torsten Grote <grote at kolabsys.com>
Date:   Wed Sep 19 17:47:50 2012 +0200

    localized hosted signup

diff --git a/hosted/js/kolab_hosted.js b/hosted/js/kolab_hosted.js
index 7baad54..9f717ae 100644
--- a/hosted/js/kolab_hosted.js
+++ b/hosted/js/kolab_hosted.js
@@ -28,8 +28,7 @@ kadm.user_save = function(reload, section)
 
     // check email address
     if(typeof data.mailalternateaddress != 'undefined' && !isValidEmailAddress(data.mailalternateaddress)) {
-        // TODO use translatable error message
-        kadm.display_message('Please provide a valid email adress as this is where your password will be sent to.', 'error');
+        kadm.display_message('signup.wrongmailalternateaddress', 'error');
         kadm.form_value_error('mailalternateaddress');
         return;
     }
@@ -70,8 +69,7 @@ kadm.check_user_availability = function()
         // check if user with that email address already exists
         kadm.http_post('signup.check_user', {data: data});
     } else {
-        // TODO use translatable string
-        kadm.update_user_info('This will not produce a valid email address!', 'uid');
+        kadm.update_user_info('signup.wronguid', 'uid');
     }
 };
 
@@ -82,6 +80,10 @@ kadm.update_user_info = function(msg, part)
         span_id = 'pass_match';
     }
 
+    if (msg) {
+        msg = kadm.t(msg);
+    }
+
     // display message next to form field
     if($('span[id="'+span_id+'"]').length) {
         // update existing span area
@@ -104,11 +106,10 @@ kadm.update_user_info = function(msg, part)
 function password_match()
 {
     if($('input[name="userpassword"]').val().localeCompare($('input[name="userpassword2"]').val())) {
-        // TODO make message translatable
-        kadm.update_user_info("The passwords don't match!", 'userpassword');
+        kadm.update_user_info('user.password.mismatch', 'userpassword');
     }
     else {
-        kadm.update_user_info("", 'userpassword');
+        kadm.update_user_info('', 'userpassword');
     }
 }
 
diff --git a/hosted/skins/default/hosted.css b/hosted/skins/default/hosted.css
index 4c891d4..bba4df6 100644
--- a/hosted/skins/default/hosted.css
+++ b/hosted/skins/default/hosted.css
@@ -38,3 +38,7 @@ td.label {
 table.form td {
     text-align: left;
 }
+
+#footer {
+  text-align: center;
+}
diff --git a/hosted/skins/default/templates/footer.html b/hosted/skins/default/templates/footer.html
index 4b6bac5..24d972d 100644
--- a/hosted/skins/default/templates/footer.html
+++ b/hosted/skins/default/templates/footer.html
@@ -1 +1 @@
-This is a service offered by <a href="http://kolabsys.com">Kolab Systems</a>.
+{$engine->translate('signup.footer')}
diff --git a/hosted/skins/default/templates/signup.html b/hosted/skins/default/templates/signup.html
index 463f832..8043859 100644
--- a/hosted/skins/default/templates/signup.html
+++ b/hosted/skins/default/templates/signup.html
@@ -2,7 +2,7 @@
 <html>
 <head>
     <meta charset="utf-8" />
-    <title>Sign Up For Hosted Kolab</title>
+    <title>{$engine->translate('signup.headline')}</title>
     <link rel="stylesheet" href="{$skin_path}style.css" />
     <link rel="stylesheet" href="{$skin_path}hosted.css" />
     <link rel="shortcut icon" type="image/png" href="{$skin_path}images/favicon.png" />
@@ -20,13 +20,11 @@
 
         <div id="task_content" class="signup">
     
-        <h1>Sign Up For Hosted Kolab</h1>
-        <p>
-            Having an account on a Kolab server is way better than just simple Email. It also provides you with full groupware functionality including shared adressbooks, calendars, tasks and journal.
-        </p>
-        <p>
-            You can sign up here now for a personal account.
-        </p>
+        <h1>{$engine->translate('signup.headline')}</h1>
+
+        <p>{$engine->translate('signup.intro1')}</p>
+
+        <p>{$engine->translate('signup.intro2')}</p>
     
         <div id="taskcontent" class="signup">{$form}</div>
 
diff --git a/hosted/skins/minimal/templates/signup.html b/hosted/skins/minimal/templates/signup.html
index 10ff483..961dbf8 100644
--- a/hosted/skins/minimal/templates/signup.html
+++ b/hosted/skins/minimal/templates/signup.html
@@ -15,9 +15,6 @@
 <body>
     <div id="taskcontent" class="signup">{$form}</div>
 
-
-    </div>
-
     <div id="footer">
         {include file="footer.html"}
     </div>
diff --git a/lib/hosted/kolab_client_task_signup.php b/lib/hosted/kolab_client_task_signup.php
index 4bcc194..b1dbe6d 100644
--- a/lib/hosted/kolab_client_task_signup.php
+++ b/lib/hosted/kolab_client_task_signup.php
@@ -87,7 +87,7 @@ class kolab_client_task_signup extends kolab_client_task
 
         if (!empty($publickey)) {
             // TODO find a less dirty way to add captcha into form
-            $form = preg_replace('/<\/tbody>/', '<tr><td class="label">Captcha</td><td class="value"><div id="recaptcha_div"></div></td></tr></tbody>', $form);
+            $form = preg_replace('/<\/tbody>/', '<tr><td class="label">'.$this->translate('signup.captcha').'</td><td class="value"><div id="recaptcha_div"></div></td></tr></tbody>', $form);
 
             // load captcha
             $form .= '
@@ -113,8 +113,7 @@ class kolab_client_task_signup extends kolab_client_task
         $result = $this->api->post('users.list', null, $post);
 
         if($result->get('count') > 0) {
-            // TODO make this message translatable
-            $this->output->command('update_user_info("User already exists!", "uid")');
+            $this->output->command('update_user_info("signup.userexists", "uid")');
             return false;
         }
 
@@ -163,12 +162,10 @@ class kolab_client_task_signup extends kolab_client_task
         $result = $this->api->post('user.add', null, $data);
 
         if (array_key_exists('error_code', $result)) {
-            // TODO make this message translatable
-            $this->output->command('display_message', 'An Error occured. You could not be signed up. Please try again.', 'error');
+            $this->output->command('display_message', 'internalerror', 'error');
             return;
         } else {
-            // TODO make this message translatable
-            $this->output->set_object('taskcontent', '<h3>Your account has been successfully added!</h3>Congratulations, you now have your own Kolab account.');
+            $this->output->set_object('taskcontent', 'signup.usercreated');
         }
     }
 
@@ -248,18 +245,17 @@ class kolab_client_task_signup extends kolab_client_task
         }
         
         // Change field labels for hosted case
-        // TODO make translatable
-        $fields['uid']['label'] = "Username";
-        $fields['mail']['label'] = "Your Future Email Address";
-        if(isset($fields['mailalternateaddress'])) $fields['mailalternateaddress']['label'] = "Your Current Email Address";
-        $fields['domain']['label'] = "Domain";
+        $fields['uid']['label'] = 'signup.username';
+        $fields['mail']['label'] = 'signup.futuremail';
+        if(isset($fields['mailalternateaddress'])) $fields['mailalternateaddress']['label'] = 'signup.mailalternateaddress';
+        $fields['domain']['label'] = 'signup.domain';
 
         // Create form object and populate with fields
         $form = $this->form_create('user', $attribs, array('other'), $fields, $fields_map, $data, true);
 
-        $form->set_title(kolab_html::escape('Sign up'));
+        $form->set_title($this->translate('signup.formtitle'));
 
-        $this->output->add_translation('user.password.mismatch', 'user.add.success');
+        $this->output->add_translation('user.password.mismatch', 'user.add.success', 'signup.wronguid');
 
         return $form->output();
     }
diff --git a/lib/locale/de.php b/lib/locale/de.php
index de118f5..1429a4b 100644
--- a/lib/locale/de.php
+++ b/lib/locale/de.php
@@ -115,6 +115,22 @@ $LANG['servererror'] = 'Server Fehler!';
 
 $LANG['session.expired'] = 'Die Sitzung ist ausgelaufen. Bitte wieder Anmelden';
 
+$LANG['signup.headline'] = 'Anmeldung für ein Kolab Konto';
+$LANG['signup.intro1'] = 'Ein Kolab E-Mail Adresse ist viel besser als eine normale E-Mail. Denn mit ihr kommen Möglichkeiten zur Syncronisation und der gemeinsamen Nutzung von Adressbüchern, Kalendern, Aufgaben, Notizen und vielem mehr.';
+$LANG['signup.intro2'] = 'Sie können sich hier eine E-Mail Adresse registrieren.';
+$LANG['signup.formtitle'] = 'Anmelden';
+$LANG['signup.username'] = 'Benutzername';
+$LANG['signup.domain'] = 'Domain';
+$LANG['signup.mailalternateaddress'] = 'Aktuelle E-Mail Adresse';
+$LANG['signup.futuremail'] = 'Zukünftige E-Mail Adresse';
+$LANG['signup.company'] = 'Firma';
+$LANG['signup.captcha'] = 'CAPTCHA';
+$LANG['signup.userexists'] = 'Benutzer existiert bereits!';
+$LANG['signup.usercreated'] = '<h3>Ihre E-Mail Adresse wurde erfolgreich angelegt!</h3>Sie haben jetzt ein eigenes Kolab-Konto.';
+$LANG['signup.wronguid'] = 'Ungültiger Benutzername!';
+$LANG['signup.wrongmailalternateaddress'] = 'Bitte geben Sie eine gültige E-Mail Adresse an!';
+$LANG['signup.footer'] = 'Dieser Dienst wird von <a href="http://kolabsys.com">Kolab Systems</a> angeboten.';
+
 $LANG['submit.button'] = 'Abschicken';
 
 $LANG['user.add'] = 'Benutzer hinzufügen';
diff --git a/lib/locale/en.php b/lib/locale/en.php
index 0da1aa0..75d1f6d 100644
--- a/lib/locale/en.php
+++ b/lib/locale/en.php
@@ -119,6 +119,22 @@ $LANG['servererror'] = 'Server Error!';
 
 $LANG['session.expired'] = 'Session has expired. Login again, please';
 
+$LANG['signup.headline'] = 'Sign Up for Hosted Kolab';
+$LANG['signup.intro1'] = 'Having an account on a Kolab server is way better than just simple Email. It also provides you with full groupware functionality including synchronization for shared addressbooks, calendars, tasks, journal and more.';
+$LANG['signup.intro2'] = 'You can sign up here now for an account.';
+$LANG['signup.formtitle'] = 'Sign Up';
+$LANG['signup.username'] = 'Username';
+$LANG['signup.domain'] = 'Domain';
+$LANG['signup.mailalternateaddress'] = 'Current Email Address';
+$LANG['signup.futuremail'] = 'Future Email Address';
+$LANG['signup.company'] = 'Company';
+$LANG['signup.captcha'] = 'CAPTCHA';
+$LANG['signup.userexists'] = 'User already exists!';
+$LANG['signup.usercreated'] = '<h3>Your account has been successfully added!</h3>Congratulations, you now have your own Kolab account.';
+$LANG['signup.wronguid'] = 'Invalid Username!';
+$LANG['signup.wrongmailalternateaddress'] = 'Please provide a valid Email Address!';
+$LANG['signup.footer'] = 'This is a service offered by <a href="http://kolabsys.com">Kolab Systems</a>.';
+
 $LANG['submit.button'] = 'Submit';
 
 $LANG['user.add'] = 'Add User';


commit 2758e4b9bdde9c9701e88c9677df97c64bd319f9
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Sep 19 14:28:38 2012 +0200

    Fix input length in smart elements

diff --git a/public_html/skins/default/style.css b/public_html/skins/default/style.css
index b24f209..3968c93 100644
--- a/public_html/skins/default/style.css
+++ b/public_html/skins/default/style.css
@@ -565,6 +565,7 @@ span.listelement {
   height: 18px;
   overflow: hidden;
   border-top: 1px solid #d0d0d0;
+  white-space: nowrap;
 }
 
 span.listelement:first-child {
@@ -575,8 +576,7 @@ span.listelement input {
   border: none;
   background-color: transparent;
   padding-left: 2px;
-  /* FIXME: it should be 330px, but when listarea has scroller, input jumps below action buttons */
-  width: 314px;
+  width: 328px;
   height: 16px;
 }
 
@@ -649,8 +649,7 @@ span.listarea.autocomplete span.listelement input.autocomplete {
 }
 
 .autocomplete > span.listelement input {
-  /* FIXME: it should be 348px, but when listarea has scroller, input jumps below action buttons */
-  width: 332px;
+  width: 346px;
 }
 
 .autocomplete > span.listelement span.actions {


commit 3348e88c0d7c6d0a9dbdb98231c75ce8fe220a48
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Sep 19 14:17:28 2012 +0200

    Improved detection of task availability according to capabilities
    Some code improvements

diff --git a/lib/client/kolab_client_task_main.php b/lib/client/kolab_client_task_main.php
index 7a7a1f0..8c50b10 100644
--- a/lib/client/kolab_client_task_main.php
+++ b/lib/client/kolab_client_task_main.php
@@ -30,8 +30,8 @@ class kolab_client_task_main extends kolab_client_task
         'domain'    => 'domains',
         'role'      => 'roles',
         'resource'  => 'resources',
-        'settings'  => '',
-        'about'     => '',
+        'settings'  => 'settings',
+        'about'     => 'about',
     );
 
 
@@ -60,15 +60,15 @@ class kolab_client_task_main extends kolab_client_task
             'search', 'search.loading', 'search.acchars');
 
         // Create list of tasks for dashboard
-        // @TODO: check capabilities
         $capabilities = $this->capabilities();
-
-        $this->menu = array();
+        $this->menu   = array();
 
         foreach ($this->_menu as $task => $api_task) {
-            if ($api_task && !array_key_exists($api_task . '.list', $capabilities['actions'])) {
-                //console("Skipping menu item $task for $api_task");
-                continue;
+            if ($task != 'about' && !array_key_exists($api_task . '.list', $capabilities['actions'])) {
+                $task_class = 'kolab_client_task_' . $task;
+                if (!method_exists($task_class, 'is_enabled') || !$task_class::is_enabled($capabilities['actions'])) {
+                    continue;
+                }
             }
 
             $this->menu[$task . '.default'] = 'menu.' . ($api_task ? $api_task : $task);
diff --git a/lib/client/kolab_client_task_settings.php b/lib/client/kolab_client_task_settings.php
index 03e0a34..8eb5caa 100644
--- a/lib/client/kolab_client_task_settings.php
+++ b/lib/client/kolab_client_task_settings.php
@@ -27,8 +27,8 @@ class kolab_client_task_settings extends kolab_client_task
     protected $ajax_only = true;
 
     protected $menu = array(
-//        'user.info'  => 'settings.info',
-        'type_list'  => 'type.list',
+        'type_list'  => 'types.list',
+        'type_add'   => 'type.add',
     );
 
     /**
@@ -39,8 +39,8 @@ class kolab_client_task_settings extends kolab_client_task
         $this->output->set_object('task_navigation', $this->menu());
 //        $this->output->set_object('content', 'settings', true);
 
-        $caps = $this->get_capability('actions');
-        if (!empty($caps['user.edit']) || !empty($caps['user.info'])) {
+        $caps_actions = $this->get_capability('actions');
+        if (self::can_edit_self($caps_actions)) {
             $this->action_info();
         }
         else {
@@ -49,6 +49,45 @@ class kolab_client_task_settings extends kolab_client_task
     }
 
     /**
+     * Checks if it's possible to edit data of current user
+     */
+    public static function can_edit_self($caps_actions)
+    {
+        // Disable user form for directory manager (see #1025)
+        if (preg_match('/^cn=([a-z ]+)/i', $_SESSION['user']['id'])) {
+            return false;
+        }
+
+        if (empty($caps_actions['user.info'])) {
+            return false;
+        }
+
+        // If we can do user.info, we can at least display
+        // the form, effective rights will be checked later
+        // there's a very small chance that user cannot view his data
+        return true;
+    }
+
+    /**
+     * Check if any of task actions is accessible for current user
+     *
+     * @return bool
+     */
+    public static function is_enabled($caps_actions)
+    {
+        // User form
+        if (self::can_edit_self($caps_actions)) {
+            return true;
+        }
+
+        if (!empty($caps_actions['types.list'])) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
      * Returns task menu output (overrides parent's menu method).
      *
      * @return string HTML output
@@ -85,12 +124,6 @@ class kolab_client_task_settings extends kolab_client_task
      */
     public function action_info()
     {
-        // Disable for directory manager (see #1025)
-        if (preg_match('/^cn=([a-z ]+)/i', $_SESSION['user']['id'])) {
-            $this->output->command('set_watermark', 'content');
-            return;
-        }
-
         $_POST['id'] = $_SESSION['user']['id'];
         $user_task    = new kolab_client_task_user($this->output);
         $user_task->action_info();
diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php
index 27f7c53..b2edadf 100644
--- a/lib/kolab_client_task.php
+++ b/lib/kolab_client_task.php
@@ -658,6 +658,36 @@ class kolab_client_task
     }
 
     /**
+     * Returns effective rights for the specified object
+     *
+     * @param string $type Object type
+     * @param string $id   Object identifier
+     *
+     * @return array Two element array with 'attribute' and 'entry' elements
+     */
+    protected function effective_rights($type, $id = null)
+    {
+        $caps = $this->get_capability('actions');
+
+        if (empty($caps[$type . '.effective_rights'])) {
+            return array(
+                'attribute' => array(),
+                'entry'     => array(),
+            );
+        }
+
+        // Get the rights on the entry and attribute level
+        $result = $this->api->get($type . '.effective_rights', array($type => $id));
+
+        $result = array(
+            'attribute' => $result->get('attributeLevelRights'),
+            'entry'     => $result->get('entryLevelRights'),
+        );
+
+        return $result;
+    }
+
+    /**
      * Returns execution time in seconds
      *
      * @param string Execution time
@@ -922,14 +952,9 @@ class kolab_client_task
         }
 
         // Get the rights on the entry and attribute level
-        $result = $this->api->get($name . ".effective_rights", array($name => $data['id']));
-        $attribute_rights = $result->get('attributeLevelRights');
-        $entry_rights     = $result->get('entryLevelRights');
-
-        $data['effective_rights'] = array(
-            'attribute' => $attribute_rights,
-            'entry'     => $entry_rights,
-        );
+        $data['effective_rights'] = $this->effective_rights($name, $data['id']);
+        $attribute_rights         = $data['effective_rights']['attribute'];
+        $entry_rights             = $data['effective_rights']['entry'];
 
         // See if "administrators" (those who can delete and add back on the entry
         // level) may override the automatically generated contents of auto_form_fields.


commit 23858fe854a62b6d8a189fc9f3bd372b1f028cf1
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Sep 19 14:15:04 2012 +0200

    Define role.effective_rights capability

diff --git a/lib/api/kolab_api_service_role.php b/lib/api/kolab_api_service_role.php
index 1158f03..0deecf9 100644
--- a/lib/api/kolab_api_service_role.php
+++ b/lib/api/kolab_api_service_role.php
@@ -64,6 +64,8 @@ class kolab_api_service_role extends kolab_api_service
             $rights['members_list'] = "r";
         }
 
+        $rights['effective_rights'] = "r";
+
         return $rights;
     }
 


commit 361c54294e879d8304c31105240efe9411a34aea
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Sep 19 11:33:06 2012 +0200

    Fix some labels

diff --git a/lib/client/kolab_client_task_about.php b/lib/client/kolab_client_task_about.php
index 5380581..0a2ce41 100644
--- a/lib/client/kolab_client_task_about.php
+++ b/lib/client/kolab_client_task_about.php
@@ -27,9 +27,9 @@ class kolab_client_task_about extends kolab_client_task
     protected $ajax_only = true;
 
     protected $menu = array(
-        'kolab'      => 'menu.kolab',
-        'kolabsys'   => 'menu.kolabsys',
-        'technology' => 'menu.technology',
+        'kolab'      => 'about.kolab',
+        'kolabsys'   => 'about.kolabsys',
+        'technology' => 'about.technology',
     );
 
     public function action_default()
diff --git a/lib/locale/en.php b/lib/locale/en.php
index a9d4fac..0da1aa0 100644
--- a/lib/locale/en.php
+++ b/lib/locale/en.php
@@ -1,8 +1,12 @@
 <?php
 
 $LANG['about.community'] = 'This is the Community Edition of the <b>Kolab Server</b>.';
-$LANG['about.warranty'] = 'It comes with absolutely <b>no warranties</b> and is typically run entirely self supported. You can find help & information on the community <a href="http://kolab.org">web site</a> & <a href="http://wiki.kolab.org">wiki</a>.';
+$LANG['about.kolab'] = 'Kolab';
+$LANG['about.kolabsys'] = 'Kolab Systems';
 $LANG['about.support'] = 'Professional support is available from <a href="http://kolabsys.com">Kolab Systems</a>.';
+$LANG['about.technology'] = 'Technology';
+$LANG['about.warranty'] = 'It comes with absolutely <b>no warranties</b> and is typically run entirely self supported. You can find help & information on the community <a href="http://kolab.org">web site</a> & <a href="http://wiki.kolab.org">wiki</a>.';
+
 $LANG['creatorsname'] = 'Created by';
 $LANG['days'] = 'days';
 $LANG['debug'] = 'Debug info';
@@ -58,12 +62,9 @@ $LANG['MB'] = 'MB';
 $LANG['menu.about'] = 'About';
 $LANG['menu.domains'] = 'Domains';
 $LANG['menu.groups'] = 'Groups';
-$LANG['menu.kolab'] = 'Kolab';
-$LANG['menu.kolabsys'] = 'Kolab Systems';
 $LANG['menu.resources'] = 'Resources';
 $LANG['menu.roles'] = 'Roles';
 $LANG['menu.settings'] = 'Settings';
-$LANG['menu.technology'] = 'Technology';
 $LANG['menu.users'] = 'Users';
 
 $LANG['modifiersname'] = 'Modified by';
diff --git a/lib/locale/en_US.php b/lib/locale/en_US.php
index 760f513..7b56ba1 100644
--- a/lib/locale/en_US.php
+++ b/lib/locale/en_US.php
@@ -1,8 +1,12 @@
 <?php
 
 $LANG['about.community'] = 'This is the Community Edition of the <b>Kolab Server</b>.';
-$LANG['about.warranty'] = 'It comes with absolutely <b>no warranties</b> and is typically run entirely self supported. You can find help & information on the community <a href="http://kolab.org">web site</a> & <a href="http://wiki.kolab.org">wiki</a>.';
+$LANG['about.kolab'] = 'Kolab';
+$LANG['about.kolabsys'] = 'Kolab Systems';
 $LANG['about.support'] = 'Professional support is available from <a href="http://kolabsys.com">Kolab Systems</a>.';
+$LANG['about.technology'] = 'Technology';
+$LANG['about.warranty'] = 'It comes with absolutely <b>no warranties</b> and is typically run entirely self supported. You can find help & information on the community <a href="http://kolab.org">web site</a> & <a href="http://wiki.kolab.org">wiki</a>.';
+
 $LANG['creatorsname'] = 'Created by';
 $LANG['days'] = 'days';
 $LANG['debug'] = 'Debug info';
@@ -56,12 +60,9 @@ $LANG['MB'] = 'MB';
 $LANG['menu.about'] = 'About';
 $LANG['menu.domains'] = 'Domains';
 $LANG['menu.groups'] = 'Groups';
-$LANG['menu.kolab'] = 'Kolab';
-$LANG['menu.kolabsys'] = 'Kolab Systems';
 $LANG['menu.resources'] = 'Resources';
 $LANG['menu.roles'] = 'Roles';
 $LANG['menu.settings'] = 'Settings';
-$LANG['menu.technology'] = 'Technology';
 $LANG['menu.users'] = 'Users';
 
 $LANG['modifiersname'] = 'Modified by';


commit c71148cb63bad4205efb7bddef9cf2b50fcd47b5
Author: Aleksander Machniak <alec at alec.pl>
Date:   Tue Sep 18 20:17:41 2012 +0200

    Fix caching + devel_mode issues (#1026), small code improvements

diff --git a/lib/client/kolab_client_task_role.php b/lib/client/kolab_client_task_role.php
index 4037098..0363435 100644
--- a/lib/client/kolab_client_task_role.php
+++ b/lib/client/kolab_client_task_role.php
@@ -307,7 +307,7 @@ class kolab_client_task_role extends kolab_client_task
             $_SESSION['role_types'] = $list;
         }
 
-        return $_SESSION['role_types'];
+        return $list;
     }
 
     /**
diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php
index 519fe3e..27f7c53 100644
--- a/lib/kolab_client_task.php
+++ b/lib/kolab_client_task.php
@@ -541,7 +541,7 @@ class kolab_client_task
      *
      * @return array List of user types
      */
-    protected function user_types($used_for = NULL)
+    protected function user_types($used_for = null)
     {
         if (!empty($_SESSION['user_types']) && !$this->devel_mode) {
             return $_SESSION['user_types'];
@@ -565,7 +565,7 @@ class kolab_client_task
             $_SESSION['user_types'] = $list;
         }
 
-        Log::trace("kolab_client_task::user_types() returns: " . var_export($list, TRUE));
+        Log::trace("kolab_client_task::user_types() returns: " . var_export($list, true));
 
         return $list;
     }
@@ -587,6 +587,7 @@ class kolab_client_task
 
         $result   = $this->api->get('user.info', array('user' => $dn));
         $username = $result->get('displayname');
+
         if (empty($username)) {
             $username = $result->get('cn');
         }
@@ -598,10 +599,10 @@ class kolab_client_task
         }
 
         if (!$this->devel_mode) {
-            return $this->cache['user_names'][$dn] = $username;
-        } else {
-            return $username;
+            $this->cache['user_names'][$dn] = $username;
         }
+
+        return $username;
     }
 
     /**
@@ -613,20 +614,21 @@ class kolab_client_task
      */
     protected function capabilities($all = false)
     {
-        if (!isset($_SESSION['capabilities']) || $this->devel_mode) {
+        if (isset($_SESSION['capabilities']) && !$this->devel_mode) {
+            $list = $_SESSION['capabilities'];
+        }
+        else {
             $result = $this->api->post('system.capabilities');
             $list   = $result->get('list');
 
-            //console("Capabilities obtained from the API", $list);
-
-            if (is_array($list)) {
+            if (is_array($list) && !$this->devel_mode) {
                 $_SESSION['capabilities'] = $list;
             }
         }
 
         $domain = $_SESSION['user']['domain'];
 
-        return !$all ? $_SESSION['capabilities'][$domain] : $_SESSION['capabilities'];
+        return !$all ? $list[$domain] : $list;
     }
 
     /**


commit 035b9e1b11199c1af201ffd0e48623616387f9c8
Author: Torsten Grote <grote at kolabsys.com>
Date:   Tue Sep 18 14:17:15 2012 +0200

    added image/icon for new Settings option

diff --git a/public_html/skins/default/images/settings.png b/public_html/skins/default/images/settings.png
new file mode 100644
index 0000000..da2ee94
Binary files /dev/null and b/public_html/skins/default/images/settings.png differ
diff --git a/public_html/skins/default/style.css b/public_html/skins/default/style.css
index ece56b6..b24f209 100644
--- a/public_html/skins/default/style.css
+++ b/public_html/skins/default/style.css
@@ -888,7 +888,7 @@ fieldset.tabbed
 }
 
 #main div.settings span.image {
-  background: url(images/resources.png) center center no-repeat;
+  background: url(images/settings.png) center center no-repeat;
 }
 
 #main div.domain span.image {


commit 373538abda0c008bec8ef94bdbd8638dcf8774dc
Author: Aleksander Machniak <alec at alec.pl>
Date:   Tue Sep 18 14:13:18 2012 +0200

    Small code improvement

diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php
index 9205b43..519fe3e 100644
--- a/lib/kolab_client_task.php
+++ b/lib/kolab_client_task.php
@@ -543,7 +543,7 @@ class kolab_client_task
      */
     protected function user_types($used_for = NULL)
     {
-        if (isset($_SESSION['user_types']) && !empty($_SESSION['user_types']) && !$this->devel_mode) {
+        if (!empty($_SESSION['user_types']) && !$this->devel_mode) {
             return $_SESSION['user_types'];
         }
 


commit 63002fdf8e119c1f374e1717e1684d6946a020a4
Author: Aleksander Machniak <alec at alec.pl>
Date:   Tue Sep 18 14:00:24 2012 +0200

    Add lost file

diff --git a/lib/client/kolab_client_task_settings.php b/lib/client/kolab_client_task_settings.php
new file mode 100644
index 0000000..03e0a34
--- /dev/null
+++ b/lib/client/kolab_client_task_settings.php
@@ -0,0 +1,331 @@
+<?php
+/*
+ +--------------------------------------------------------------------------+
+ | This file is part of the Kolab Web Admin Panel                           |
+ |                                                                          |
+ | Copyright (C) 2011-2012, Kolab Systems AG                                |
+ |                                                                          |
+ | This program is free software: you can redistribute it and/or modify     |
+ | it under the terms of the GNU Affero General Public License as published |
+ | by the Free Software Foundation, either version 3 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 Affero General Public License for more details.                      |
+ |                                                                          |
+ | You should have received a copy of the GNU Affero General Public License |
+ | along with this program. If not, see <http://www.gnu.org/licenses/>      |
+ +--------------------------------------------------------------------------+
+ | Author: Aleksander Machniak <machniak at kolabsys.com>                      |
+ +--------------------------------------------------------------------------+
+*/
+
+class kolab_client_task_settings extends kolab_client_task
+{
+    protected $ajax_only = true;
+
+    protected $menu = array(
+//        'user.info'  => 'settings.info',
+        'type_list'  => 'type.list',
+    );
+
+    /**
+     * Default action.
+     */
+    public function action_default()
+    {
+        $this->output->set_object('task_navigation', $this->menu());
+//        $this->output->set_object('content', 'settings', true);
+
+        $caps = $this->get_capability('actions');
+        if (!empty($caps['user.edit']) || !empty($caps['user.info'])) {
+            $this->action_info();
+        }
+        else {
+            $this->output->command('set_watermark', 'content');
+        }
+    }
+
+    /**
+     * Returns task menu output (overrides parent's menu method).
+     *
+     * @return string HTML output
+     */
+    protected function menu()
+    {
+        $caps = $this->capabilities();
+        $menu = array();
+
+        foreach ($this->menu as $idx => $label) {
+            if (!array_key_exists($idx, (array)$caps['actions'])) {
+                continue;
+            }
+
+            if (strpos($idx, '.')) {
+                $action = $idx;
+                $class  = preg_replace('/\.[a-z_-]+$/', '', $idx);
+            }
+            else {
+                $action = $task . '.' . $idx;
+                $class  = $idx;
+            }
+
+            $menu[$idx] = sprintf('<li class="%s">'
+                .'<a href="#%s" onclick="return kadm.command(\'%s\', \'\', this)">%s</a></li>',
+                $class, $idx, $action, $this->translate($label));
+        }
+
+        return '<ul>' . implode("\n", $menu) . '</ul>';
+    }
+
+    /**
+     * User info action.
+     */
+    public function action_info()
+    {
+        // Disable for directory manager (see #1025)
+        if (preg_match('/^cn=([a-z ]+)/i', $_SESSION['user']['id'])) {
+            $this->output->command('set_watermark', 'content');
+            return;
+        }
+
+        $_POST['id'] = $_SESSION['user']['id'];
+        $user_task    = new kolab_client_task_user($this->output);
+        $user_task->action_info();
+
+        $this->output->set_object('content', $this->output->get_object('taskcontent'));
+    }
+
+    /**
+     * Groups list action.
+     */
+    public function action_types_list()
+    {
+        $page_size = 20;
+        $page      = (int) self::get_input('page', 'POST');
+        if (!$page || $page < 1) {
+            $page = 1;
+        }
+
+        // request parameters
+        $post = array(
+            'attributes' => array('cn'),
+//            'sort_order' => 'ASC',
+            'sort_by'    => 'cn',
+            'page_size'  => $page_size,
+            'page'       => $page,
+        );
+
+        // search parameters
+        if (!empty($_POST['search'])) {
+            $search = self::get_input('search', 'POST', true);
+            $field  = self::get_input('field',  'POST');
+            $method = self::get_input('method', 'POST');
+
+            $search_request = array(
+                $field => array(
+                    'value' => $search,
+                    'type'  => $method,
+                ),
+            );
+        }
+        else if (!empty($_POST['search_request'])) {
+            $search_request = self::get_input('search_request', 'POST');
+            $search_request = @unserialize(base64_decode($search_request));
+        }
+
+        if (!empty($search_request)) {
+            $post['search']          = $search_request;
+            $post['search_operator'] = 'OR';
+        }
+
+        // get groups list
+        $result = $this->api->post('types.list', null, $post);
+        $count  = (int) $result->get('count');
+        $result = (array) $result->get('list');
+
+        // calculate records
+        if ($count) {
+            $start = 1 + max(0, $page - 1) * $page_size;
+            $end   = min($start + $page_size - 1, $count);
+        }
+
+        $rows = $head = $foot = array();
+        $cols = array('name');
+        $i    = 0;
+
+        // table header
+        $head[0]['cells'][] = array('class' => 'name', 'body' => $this->translate('type.list'));
+
+        // table footer (navigation)
+        if ($count) {
+            $pages = ceil($count / $page_size);
+            $prev  = max(0, $page - 1);
+            $next  = $page < $pages ? $page + 1 : 0;
+
+            $count_str = kolab_html::span(array(
+                'content' => $this->translate('type.list.records', $start, $end, $count)), true);
+            $prev = kolab_html::a(array(
+                'class' => 'prev' . ($prev ? '' : ' disabled'),
+                'href'  => '#',
+                'onclick' => $prev ? "kadm.command('type.list', {page: $prev})" : "return false",
+            ));
+            $next = kolab_html::a(array(
+                'class' => 'next' . ($next ? '' : ' disabled'),
+                'href'  => '#',
+                'onclick' => $next ? "kadm.command('type.list', {page: $next})" : "return false",
+            ));
+
+            $foot_body = kolab_html::span(array('content' => $prev . $count_str . $next));
+        }
+        $foot[0]['cells'][] = array('class' => 'listnav', 'body' => $foot_body);
+
+        // table body
+        if (!empty($result)) {
+            foreach ($result as $idx => $item) {
+                if (!is_array($item) || empty($item['cn'])) {
+                    continue;
+                }
+
+                $i++;
+                $cells = array();
+                $cells[] = array('class' => 'name', 'body' => kolab_html::escape($item['cn']),
+                    'onclick' => "kadm.command('type.info', '$idx')");
+                $rows[] = array('id' => $i, 'class' => 'selectable', 'cells' => $cells);
+            }
+        }
+        else {
+            $rows[] = array('cells' => array(
+                0 => array('class' => 'empty-body', 'body' => $this->translate('type.norecords')
+            )));
+        }
+
+        $table = kolab_html::table(array(
+            'id'    => 'typelist',
+            'class' => 'list',
+            'head'  => $head,
+            'body'  => $rows,
+            'foot'  => $foot,
+        ));
+
+        $this->output->set_env('search_request', $search_request ? base64_encode(serialize($search_request)) : null);
+        $this->output->set_env('list_page', $page);
+        $this->output->set_env('list_count', $count);
+        $this->output->set_object('typelist', $table);
+    }
+
+    /**
+     * Group information (form) action.
+     */
+    public function action_type_info()
+    {
+        $id     = $this->get_input('id', 'POST');
+        $result = $this->api->get('type.info', array('type' => $id));
+        $type   = $result->get();
+        $output = $this->group_form(null, $type);
+
+        $this->output->set_object('taskcontent', $output);
+    }
+
+    /**
+     * Groups adding (form) action.
+     */
+    public function action_type_add()
+    {
+        $data   = $this->get_input('data', 'POST');
+        $output = $this->type_form(null, $data, true);
+
+        $this->output->set_object('taskcontent', $output);
+    }
+
+    /**
+     * Group edit/add form.
+     */
+    private function type_form($attribs, $data = array())
+    {
+        if (empty($attribs['id'])) {
+            $attribs['id'] = 'type-form';
+        }
+
+        // Form sections
+        $sections = array(
+            'props'   => 'type.properties',
+            'attribs' => 'type.attributes',
+        );
+
+        // field-to-section map and fields order
+        $fields_map = array(
+            'type_id'       => 'props',
+            'objectclasses' => 'props',
+            'type_id_name'  => 'attribs',
+        );
+
+        // Prepare fields
+        list($fields, $types, $type) = $this->form_prepare('type', $data);
+
+        $add_mode = empty($data['id']);
+
+        // Add type id selector
+        $fields['type_id'] = array(
+            'section'  => 'props',
+            'type'     => kolab_form::INPUT_HIDDEN,
+        );
+
+        // Create mode
+        if ($add_mode) {
+            // Page title
+            $title = $this->translate('type.add');
+        }
+        // Edit mode
+        else {
+            $title = $data['cn'];
+        }
+
+        // Create form object and populate with fields
+        $form = $this->form_create('type', $attribs, $sections, $fields, $fields_map, $data, $add_mode);
+
+        $form->set_title(kolab_html::escape($title));
+
+        $this->output->add_translation('type.add.success', 'type.edit.success', 'type.delete.success');
+
+        return $form->output();
+    }
+
+    /**
+     * Users search form.
+     *
+     * @return string HTML output of the form
+     */
+    public function search_form()
+    {
+        $form = new kolab_form(array('id' => 'search-form'));
+/*
+        $form->add_section('criteria', kolab_html::escape($this->translate('search.criteria')));
+        $form->add_element(array(
+            'section' => 'criteria',
+            'label'   => $this->translate('search.field'),
+            'name'    => 'field',
+            'type'    => kolab_form::INPUT_SELECT,
+            'options' => array(
+                'cn'   => kolab_html::escape($this->translate('search.name')),
+                'mail' => kolab_html::escape($this->translate('search.email')),
+            ),
+        ));
+        $form->add_element(array(
+            'section' => 'criteria',
+            'label'   => $this->translate('search.method'),
+            'name'    => 'method',
+            'type'    => kolab_form::INPUT_SELECT,
+            'options' => array(
+                'both'   => kolab_html::escape($this->translate('search.contains')),
+                'exact'  => kolab_html::escape($this->translate('search.is')),
+                'prefix' => kolab_html::escape($this->translate('search.prefix')),
+            ),
+        ));
+*/
+        return $form->output();
+    }
+
+}


commit 51bacb413b9cab2e0e54e165cc55746f0c579ee1
Author: Aleksander Machniak <alec at alec.pl>
Date:   Tue Sep 18 13:53:40 2012 +0200

    Don't call user.info for directory manager

diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php
index 30302b1..9205b43 100644
--- a/lib/kolab_client_task.php
+++ b/lib/kolab_client_task.php
@@ -197,18 +197,20 @@ class kolab_client_task
 
                     $this->api->set_session_token($user['token']);
 
-                    // find user settings
-                    $res = $this->api->get('user.info', array('user' => $user['id']));
-                    $res = $res->get();
-
-                    if (is_array($res) && !empty($res)) {
-                        $user['language'] = $res['preferredlanguage'];
-                        $user['fullname'] = $res['cn'];
-                    }
-                    // @TODO: why user.info returns empty result for 'cn=Directory Manager' login?
-                    else if (preg_match('/^cn=([a-zA-Z ]+)/', $login['username'], $m)) {
+                    // Find user settings
+                    // Don't call API user.info for non-existing users (#1025)
+                    if (preg_match('/^cn=([a-z ]+)/i', $login['username'], $m)) {
                         $user['fullname'] = ucwords($m[1]);
                     }
+                    else {
+                        $res = $this->api->get('user.info', array('user' => $user['id']));
+                        $res = $res->get();
+
+                        if (is_array($res) && !empty($res)) {
+                            $user['language'] = $res['preferredlanguage'];
+                            $user['fullname'] = $res['cn'];
+                        }
+                    }
 
                     // Save user data
                     $_SESSION['user'] = $user;


commit e653835eae425c5fa0612680cefe7c3b2932e6ec
Author: Aleksander Machniak <alec at alec.pl>
Date:   Tue Sep 18 13:29:10 2012 +0200

    - Add Settings menu with interface for current user information management.
      Settings menu will be used for object types management.
    - Fix some bugs and code cleanup

diff --git a/lib/client/kolab_client_task_domain.php b/lib/client/kolab_client_task_domain.php
index 97d5624..8c8228c 100644
--- a/lib/client/kolab_client_task_domain.php
+++ b/lib/client/kolab_client_task_domain.php
@@ -42,9 +42,12 @@ class kolab_client_task_domain extends kolab_client_task
 
         // display form to add domain if logged-in user has right to do so
         $caps = $this->get_capability('actions');
-        if($caps['domain.add']['type'] == 'w') {
+        if (!empty($caps['domain.add'])) {
             $this->action_add();
         }
+        else {
+            $this->output->command('set_watermark', 'taskcontent');
+        }
     }
 
     /**
diff --git a/lib/client/kolab_client_task_group.php b/lib/client/kolab_client_task_group.php
index a4b7c24..a628d50 100644
--- a/lib/client/kolab_client_task_group.php
+++ b/lib/client/kolab_client_task_group.php
@@ -42,9 +42,12 @@ class kolab_client_task_group extends kolab_client_task
 
         // display form to add group if logged-in user has right to do so
         $caps = $this->get_capability('actions');
-        if($caps['group.add']['type'] == 'w') {
+        if (!empty($caps['group.add'])) {
             $this->action_add();
         }
+        else {
+            $this->output->command('set_watermark', 'taskcontent');
+        }
     }
 
     /**
diff --git a/lib/client/kolab_client_task_main.php b/lib/client/kolab_client_task_main.php
index 6921377..7a7a1f0 100644
--- a/lib/client/kolab_client_task_main.php
+++ b/lib/client/kolab_client_task_main.php
@@ -30,7 +30,8 @@ class kolab_client_task_main extends kolab_client_task
         'domain'    => 'domains',
         'role'      => 'roles',
         'resource'  => 'resources',
-        'about'     => 'about',
+        'settings'  => '',
+        'about'     => '',
     );
 
 
@@ -65,14 +66,12 @@ class kolab_client_task_main extends kolab_client_task
         $this->menu = array();
 
         foreach ($this->_menu as $task => $api_task) {
-            if ($task !== "about") {
-                if (!array_key_exists($api_task . '.list', $capabilities['actions'])) {
-                    //console("Skipping menu item $task for $api_task");
-                    continue;
-                }
+            if ($api_task && !array_key_exists($api_task . '.list', $capabilities['actions'])) {
+                //console("Skipping menu item $task for $api_task");
+                continue;
             }
 
-            $this->menu[$task . '.default'] = 'menu.' . $api_task;
+            $this->menu[$task . '.default'] = 'menu.' . ($api_task ? $api_task : $task);
         }
 
         $this->output->assign('tasks', $this->menu);
diff --git a/lib/client/kolab_client_task_resource.php b/lib/client/kolab_client_task_resource.php
index ace2929..9a1626d 100644
--- a/lib/client/kolab_client_task_resource.php
+++ b/lib/client/kolab_client_task_resource.php
@@ -42,9 +42,12 @@ class kolab_client_task_resource extends kolab_client_task
 
         // display form to add resource if logged-in user has right to do so
         $caps = $this->get_capability('actions');
-        if($caps['resource.add']['type'] == 'w') {
+        if (!empty($caps['resource.add'])) {
             $this->action_add();
         }
+        else {
+            $this->output->command('set_watermark', 'taskcontent');
+        }
     }
 
     /**
diff --git a/lib/client/kolab_client_task_role.php b/lib/client/kolab_client_task_role.php
index b625f73..4037098 100644
--- a/lib/client/kolab_client_task_role.php
+++ b/lib/client/kolab_client_task_role.php
@@ -42,10 +42,12 @@ class kolab_client_task_role extends kolab_client_task
 
         // display form to add role if logged-in user has right to do so
         $caps = $this->get_capability('actions');
-        if($caps['role.add']['type'] == 'w') {
+        if (!empty($caps['role.add'])) {
             $this->action_add();
         }
-
+        else {
+            $this->output->command('set_watermark', 'taskcontent');
+        }
     }
 
     /**
diff --git a/lib/client/kolab_client_task_user.php b/lib/client/kolab_client_task_user.php
index 09bbca3..73dbcc3 100644
--- a/lib/client/kolab_client_task_user.php
+++ b/lib/client/kolab_client_task_user.php
@@ -42,9 +42,12 @@ class kolab_client_task_user extends kolab_client_task
 
         // display form to add user if logged-in user has right to do so
         $caps = $this->get_capability('actions');
-        if($caps['user.add']['type'] == 'w') {
+        if (!empty($caps['user.add'])) {
             $this->action_add();
         }
+        else {
+            $this->output->command('set_watermark', 'taskcontent');
+        }
     }
 
     /**
diff --git a/lib/kolab_client_output.php b/lib/kolab_client_output.php
index d40c323..f7bb471 100644
--- a/lib/kolab_client_output.php
+++ b/lib/kolab_client_output.php
@@ -227,7 +227,7 @@ class kolab_client_output
     }
 
     /**
-     * Sets conntent of a HTML object.
+     * Sets content of a HTML object.
      *
      * @param string $name        Object's identifier (HTML ID attribute)
      * @param string $content     Object's content
@@ -243,6 +243,18 @@ class kolab_client_output
     }
 
     /**
+     * Returns content of a HTML object (set with set_object())
+     *
+     * @param string $name Object's identifier (HTML ID attribute)
+     *
+     * @return string Object content
+     */
+    public function get_object($name)
+    {
+        return $this->objects[$name];
+    }
+
+    /**
      * Returns HTML template output.
      *
      * @param string $name Template name
diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php
index 7e21852..30302b1 100644
--- a/lib/kolab_client_task.php
+++ b/lib/kolab_client_task.php
@@ -51,14 +51,16 @@ class kolab_client_task
 
     /**
      * Class constructor.
+     *
+     * @param kolab_client_output $output Optional output object
      */
-    public function __construct()
+    public function __construct($output = null)
     {
         $this->config_init();
 
         $this->devel_mode = $this->config_get('devel_mode', false, Conf::BOOL);
 
-        $this->output_init();
+        $this->output_init($output);
         $this->api_init();
 
         ini_set('session.use_cookies', 'On');
@@ -101,8 +103,13 @@ class kolab_client_task
     /**
      * Output initialization.
      */
-    private function output_init()
+    private function output_init($output = null)
     {
+        if ($output) {
+            $this->output = $output;
+            return;
+        }
+
         $skin = $this->config_get('skin', 'default');
         $this->output = new kolab_client_output($skin);
     }
@@ -491,16 +498,13 @@ class kolab_client_task
             return '';
         }
 
+        $menu = array();
         $task = $this->get_task();
-
-        $capabilities = $this->capabilities();
+        $caps = $this->capabilities();
 
         foreach ($this->menu as $idx => $label) {
-            //console("$task: $task, idx: $idx, label: $label");
-
             if (in_array($task, array('domain', 'group', 'resource', 'role', 'user'))) {
-                if (!array_key_exists($task . "." . $idx, $capabilities['actions'])) {
-                    //console("$task.$idx not in \$capabilities['actions'], skipping", $capabilities['actions']);
+                if (!array_key_exists($task . "." . $idx, $caps['actions'])) {
                     continue;
                 }
             }
@@ -519,10 +523,7 @@ class kolab_client_task
                 $class, $idx, $action, $this->translate($label));
         }
 
-        if (is_array($menu))
-            return '<ul>' . implode("\n", $menu) . '</ul>';
-        else
-            return '<ul>' . $menu . '</ul>';
+        return '<ul>' . implode("\n", $menu) . '</ul>';
     }
 
     /**
@@ -1231,5 +1232,4 @@ class kolab_client_task
         return $form;
     }
 
-
 }
diff --git a/lib/locale/en.php b/lib/locale/en.php
index cbf9d62..a9d4fac 100644
--- a/lib/locale/en.php
+++ b/lib/locale/en.php
@@ -62,6 +62,7 @@ $LANG['menu.kolab'] = 'Kolab';
 $LANG['menu.kolabsys'] = 'Kolab Systems';
 $LANG['menu.resources'] = 'Resources';
 $LANG['menu.roles'] = 'Roles';
+$LANG['menu.settings'] = 'Settings';
 $LANG['menu.technology'] = 'Technology';
 $LANG['menu.users'] = 'Users';
 
diff --git a/lib/locale/en_US.php b/lib/locale/en_US.php
index a9fde09..760f513 100644
--- a/lib/locale/en_US.php
+++ b/lib/locale/en_US.php
@@ -60,6 +60,7 @@ $LANG['menu.kolab'] = 'Kolab';
 $LANG['menu.kolabsys'] = 'Kolab Systems';
 $LANG['menu.resources'] = 'Resources';
 $LANG['menu.roles'] = 'Roles';
+$LANG['menu.settings'] = 'Settings';
 $LANG['menu.technology'] = 'Technology';
 $LANG['menu.users'] = 'Users';
 
diff --git a/public_html/js/kolab_admin.js b/public_html/js/kolab_admin.js
index 701f818..2fd8899 100644
--- a/public_html/js/kolab_admin.js
+++ b/public_html/js/kolab_admin.js
@@ -1376,8 +1376,11 @@ function kolab_admin()
       return;
 
     this.display_message('user.edit.success');
-    this.command('user.list', {page: this.env.list_page});
-    this.set_watermark('taskcontent');
+
+    if ($('#userlist').length) {
+      this.command('user.list', {page: this.env.list_page});
+      this.set_watermark('taskcontent');
+    }
   };
 
   this.group_info = function(id)
diff --git a/public_html/skins/default/style.css b/public_html/skins/default/style.css
index d8e39ad..ece56b6 100644
--- a/public_html/skins/default/style.css
+++ b/public_html/skins/default/style.css
@@ -747,7 +747,6 @@ div.tabsbar
   border-bottom: 1px solid #d0d0d0;
   white-space: nowrap;
   margin: 10px 5px 0 5px;
-  background-color: #f0f0f0;
 }
 
 span.tablink,
@@ -888,6 +887,10 @@ fieldset.tabbed
   background: url(images/resources.png) center center no-repeat;
 }
 
+#main div.settings span.image {
+  background: url(images/resources.png) center center no-repeat;
+}
+
 #main div.domain span.image {
   background: url(images/domains.png) center center no-repeat;
 }





More information about the commits mailing list