diff --git a/apps/user_ldap/ajax/getNewServerConfigPrefix.php b/apps/user_ldap/ajax/getNewServerConfigPrefix.php
index 7d1434a8fb4b10f66ac5061d71314d9710ec7b04..d0135917886cfedf64274a5dd81bc27e3ede2d11 100644
--- a/apps/user_ldap/ajax/getNewServerConfigPrefix.php
+++ b/apps/user_ldap/ajax/getNewServerConfigPrefix.php
@@ -32,4 +32,17 @@ sort($serverConnections);
 $lk = array_pop($serverConnections);
 $ln = intval(str_replace('s', '', $lk));
 $nk = 's'.str_pad($ln+1, 2, '0', STR_PAD_LEFT);
-OCP\JSON::success(array('configPrefix' => $nk));
+
+$resultData = array('configPrefix' => $nk);
+
+if(isset($_POST['copyConfig'])) {
+	$originalConfig = new \OCA\user_ldap\lib\Configuration($_POST['copyConfig']);
+	$newConfig = new \OCA\user_ldap\lib\Configuration($nk, false);
+	$newConfig->setConfiguration($originalConfig->getConfiguration());
+	$newConfig->saveConfiguration();
+} else {
+	$configuration = new \OCA\user_ldap\lib\Configuration($nk, false);
+	$resultData['defaults'] = $configuration->getDefaults();
+}
+
+OCP\JSON::success($resultData);
diff --git a/apps/user_ldap/ajax/wizard.php b/apps/user_ldap/ajax/wizard.php
index ab521b5bf0e3baed0f8f4db71616c06cb8bd99c8..267b9568a282869a73cf12ff8c2804c580fbe060 100644
--- a/apps/user_ldap/ajax/wizard.php
+++ b/apps/user_ldap/ajax/wizard.php
@@ -72,13 +72,11 @@ switch($action) {
 	case 'determineGroupsForGroups':
 	case 'determineAttributes':
 	case 'getUserListFilter':
-	case 'getLoginFilterMode':
 	case 'getUserLoginFilter':
-	case 'getUserFilterMode':
 	case 'getGroupFilter':
-	case 'getGroupFilterMode':
 	case 'countUsers':
 	case 'countGroups':
+	case 'countInBaseDN':
 		try {
 			$result = $wizard->$action();
 			if($result !== false) {
@@ -93,6 +91,23 @@ switch($action) {
 		exit;
 		break;
 
+	case 'testLoginName': {
+		try {
+			$loginName = $_POST['ldap_test_loginname'];
+			$result = $wizard->$action($loginName);
+			if($result !== false) {
+				OCP\JSON::success($result->getResultArray());
+				exit;
+			}
+		} catch (\Exception $e) {
+			\OCP\JSON::error(array('message' => $e->getMessage()));
+			exit;
+		}
+		\OCP\JSON::error();
+		exit;
+		break;
+	}
+
 	case 'save':
 		$key = isset($_POST['cfgkey']) ? $_POST['cfgkey'] : false;
 		$val = isset($_POST['cfgval']) ? $_POST['cfgval'] : null;
@@ -115,6 +130,6 @@ switch($action) {
 		OCP\JSON::success();
 		break;
 	default:
-		//TODO: return 4xx error
+		\OCP\JSON::error(array('message' => $l->t('Action does not exist')));
 		break;
 }
diff --git a/apps/user_ldap/appinfo/version b/apps/user_ldap/appinfo/version
index 8f0916f768f0487bcf8d33827ce2c8dcecb645c1..a918a2aa18d5bec6a8bb93891a7a63c243111796 100644
--- a/apps/user_ldap/appinfo/version
+++ b/apps/user_ldap/appinfo/version
@@ -1 +1 @@
-0.5.0
+0.6.0
diff --git a/apps/user_ldap/css/settings.css b/apps/user_ldap/css/settings.css
index 8f339451c6451a48eadbb460302e2cde08da138f..b351f9ae2af7c31f80d05bea1fc5a3d8a0459243 100644
--- a/apps/user_ldap/css/settings.css
+++ b/apps/user_ldap/css/settings.css
@@ -1,6 +1,6 @@
 .table {
 	display: table;
-	width: 60%;
+	width: 85%;
 }
 
 .tablerow {
@@ -21,10 +21,18 @@
 	margin-left: 3px;
 }
 
+.ldapIconCopy {
+	background-image: url('../img/copy.svg');
+}
+
 .invisible {
 	visibility: hidden;
 }
 
+.forceHidden {
+	display: none !important;
+}
+
 .ldapSettingsTabs {
 	float: right !important;
 }
@@ -49,13 +57,16 @@
 }
 
 #ldapWizard1 .hostPortCombinator div span {
-	width: 7%;
-	display: table-cell;
+	width: 14.5%;
+	display: inline-block;
 	text-align: right;
 }
 
 #ldapWizard1 .host {
-	width: 96.5% !important;
+	width: 100%;
+	margin-left: 0;
+	margin-right: 0;
+	border: 0;
 }
 
 .tableCellInput {
@@ -77,7 +88,7 @@
 	color: #FF3B3B;
 }
 
-.wizSpinner {
+.ldapSpinner {
 	height: 15px;
 	margin: 5px;
 }
@@ -104,10 +115,51 @@
 	width: auto;
 }
 
+.ldapManyGroupsSupport span {
+	display: inline-block;
+	vertical-align: top;
+	height: 150px;
+}
+
+.ldapManyGroupsSupport span button {
+	margin-top: 35px;
+}
+
+.ldapManyGroupsSearch {
+	width: 425px !important;
+}
+
+.ldapGroupList {
+	height: 150px;
+	width: 200px;
+}
+
 #ldap fieldset input, #ldap fieldset textarea {
  	width: 60%;
 }
 
+#ldap fieldset textarea ~ button {
+	vertical-align: text-bottom;
+}
+
+input.ldapVerifyInput {
+	width: 150px !important;
+}
+
+.ldapInputColElement {
+	width: 35%;
+	display: inline-block;
+	padding-left: 10px;
+}
+
+.ldapToggle {
+	text-decoration: underline;
+}
+
+span.ldapInputColElement {
+	margin-top: 9px;
+}
+
 #ldap fieldset p input[type=checkbox] {
 	vertical-align: bottom;
 }
diff --git a/apps/user_ldap/img/copy.png b/apps/user_ldap/img/copy.png
new file mode 100644
index 0000000000000000000000000000000000000000..283d627a5a7679f217f75d60de2721972cadaa87
Binary files /dev/null and b/apps/user_ldap/img/copy.png differ
diff --git a/apps/user_ldap/img/copy.svg b/apps/user_ldap/img/copy.svg
new file mode 100644
index 0000000000000000000000000000000000000000..2e19d8066e32c13b0d515679ad33531c2d037c4a
--- /dev/null
+++ b/apps/user_ldap/img/copy.svg
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:dc="http://purl.org/dc/elements/1.1/"
+    xmlns:ns1="http://sozi.baierouge.fr"
+    xmlns:cc="http://web.resource.org/cc/"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+    id="svg1"
+    sodipodi:docname="copy.svg"
+    viewBox="0 0 60 60"
+    sodipodi:version="0.32"
+    _SVGFile__filename="oldscale/actions/copy.svg"
+    version="1.0"
+    y="0"
+    x="0"
+    inkscape:version="0.40"
+    sodipodi:docbase="/home/danny/work/flat/SVG/mono/scalable/actions"
+  >
+  <sodipodi:namedview
+      id="base"
+      bordercolor="#666666"
+      inkscape:pageshadow="2"
+      inkscape:window-y="0"
+      pagecolor="#ffffff"
+      inkscape:window-height="699"
+      inkscape:zoom="4.9119411"
+      inkscape:window-x="0"
+      borderopacity="1.0"
+      inkscape:current-layer="svg1"
+      inkscape:cx="60.290892"
+      inkscape:cy="24.030855"
+      inkscape:window-width="1024"
+      inkscape:pageopacity="0.0"
+  />
+  <path
+      id="path1716"
+      style="stroke-linejoin:round;stroke:#ffffff;stroke-width:8.3605;fill:none"
+      transform="matrix(.97183 0 0 .97183 .87037 .23733)"
+      d="m7.513 4.5767c-1.3709 0-2.4745 1.1036-2.4745 2.4745v31.564c0 1.371 1.1036 2.474 2.4745 2.474h29.783c1.371 0 2.474-1.103 2.474-2.474v-31.564c0-1.3707-1.103-2.4743-2.474-2.4743h-29.783z"
+  />
+  <path
+      id="rect1101"
+      style="stroke-linejoin:round;fill-rule:evenodd;stroke:#000000;stroke-width:3.2156;fill:#ffffff"
+      transform="matrix(.97183 0 0 .97183 .87037 .23733)"
+      d="m7.513 4.5767c-1.3709 0-2.4745 1.1036-2.4745 2.4745v31.564c0 1.371 1.1036 2.474 2.4745 2.474h29.783c1.371 0 2.474-1.103 2.474-2.474v-31.564c0-1.3707-1.103-2.4743-2.474-2.4743h-29.783z"
+  />
+  <path
+      id="path1094"
+      style="stroke-linejoin:round;stroke:#ffffff;stroke-width:8.3605;fill:none"
+      transform="matrix(.97183 0 0 .97183 .87037 .23733)"
+      d="m22.652 20.161c-1.37 0-2.474 1.104-2.474 2.475v31.564c0 1.371 1.104 2.474 2.474 2.474h29.783c1.371 0 2.475-1.103 2.475-2.474v-31.564c0-1.371-1.104-2.475-2.475-2.475h-29.783z"
+  />
+  <path
+      id="rect1111"
+      style="stroke-linejoin:round;fill-rule:evenodd;stroke:#000000;stroke-width:3.2156;fill:#ffffff"
+      transform="matrix(.97183 0 0 .97183 .87037 .23733)"
+      d="m22.652 20.161c-1.37 0-2.474 1.104-2.474 2.475v31.564c0 1.371 1.104 2.474 2.474 2.474h29.783c1.371 0 2.475-1.103 2.475-2.474v-31.564c0-1.371-1.104-2.475-2.475-2.475h-29.783z"
+  />
+  <path
+      id="path1718"
+      style="stroke-linejoin:round;stroke:#ffffff;stroke-linecap:round;stroke-width:8.125;fill:none"
+      d="m12.457 21.599c2.325 20.529 20.15 19.15 21.296 19.043v6.139l8.981-8.889-8.981-8.87v6.066c-1.348 0.159-13.941 1.422-21.296-13.489z"
+  />
+  <path
+      id="path1574"
+      style="stroke-linejoin:round;fill-rule:evenodd;stroke:#000000;stroke-linecap:round;stroke-width:3.125;fill:#000000"
+      d="m12.457 21.599c2.325 20.529 20.15 19.15 21.296 19.043v6.139l8.981-8.889-8.981-8.87v6.066c-1.348 0.159-13.941 1.422-21.296-13.489z"
+  />
+  <metadata
+    >
+    <rdf:RDF
+      >
+      <cc:Work
+        >
+        <dc:format
+          >image/svg+xml</dc:format
+        >
+        <dc:type
+            rdf:resource="http://purl.org/dc/dcmitype/StillImage"
+        />
+        <cc:license
+            rdf:resource="http://creativecommons.org/licenses/publicdomain/"
+        />
+        <dc:publisher
+          >
+          <cc:Agent
+              rdf:about="http://openclipart.org/"
+            >
+            <dc:title
+              >Openclipart</dc:title
+            >
+          </cc:Agent
+          >
+        </dc:publisher
+        >
+      </cc:Work
+      >
+      <cc:License
+          rdf:about="http://creativecommons.org/licenses/publicdomain/"
+        >
+        <cc:permits
+            rdf:resource="http://creativecommons.org/ns#Reproduction"
+        />
+        <cc:permits
+            rdf:resource="http://creativecommons.org/ns#Distribution"
+        />
+        <cc:permits
+            rdf:resource="http://creativecommons.org/ns#DerivativeWorks"
+        />
+      </cc:License
+      >
+    </rdf:RDF
+    >
+  </metadata
+  >
+</svg
+>
diff --git a/apps/user_ldap/js/experiencedAdmin.js b/apps/user_ldap/js/experiencedAdmin.js
deleted file mode 100644
index 7dc5a4e503d17ae3ae8575e2326f41e630567b54..0000000000000000000000000000000000000000
--- a/apps/user_ldap/js/experiencedAdmin.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/**
- * Copyright (c) 2014, Arthur Schiwon <blizzz@owncloud.com>
- * This file is licensed under the Affero General Public License version 3 or later.
- * See the COPYING-README file.
- */
-
-/* global LdapWizard */
-
-/**
- * controls behaviour depend on whether the admin is experienced in LDAP or not.
- *
- * @class
- * @param {object} wizard the LDAP Wizard object
- * @param {boolean} initialState whether the admin is experienced or not
- */
-function ExperiencedAdmin(wizard, initialState) {
-	this.wizard = wizard;
-	this._isExperienced = initialState;
-	if(this._isExperienced) {
-		this.hideEntryCounters();
-	}
-}
-
-
-/**
- * toggles whether the admin is an experienced one or not
- *
- * @param {boolean} isExperienced whether the admin is experienced or not
- */
-ExperiencedAdmin.prototype.setExperienced = function(isExperienced) {
-	this._isExperienced = isExperienced;
-	if(this._isExperienced) {
-		this.enableRawMode();
-		this.hideEntryCounters();
-	} else {
-		this.showEntryCounters();
-	}
-};
-
-/**
-* answers whether the admin is an experienced one or not
-*
-* @return {boolean} whether the admin is experienced or not
-*/
-ExperiencedAdmin.prototype.isExperienced = function() {
-	return this._isExperienced;
-};
-
-/**
- * switches all LDAP filters from Assisted to Raw mode.
- */
-ExperiencedAdmin.prototype.enableRawMode = function() {
-	LdapWizard._save({id: 'ldapGroupFilterMode'}, LdapWizard.filterModeRaw);
-	LdapWizard._save({id: 'ldapUserFilterMode' }, LdapWizard.filterModeRaw);
-	LdapWizard._save({id: 'ldapLoginFilterMode'}, LdapWizard.filterModeRaw);
-};
-
-ExperiencedAdmin.prototype.updateUserTab = function(mode) {
-	this._updateTab(mode, $('#ldap_user_count'));
-};
-
-ExperiencedAdmin.prototype.updateGroupTab = function(mode) {
-	this._updateTab(mode, $('#ldap_group_count'));
-};
-
-ExperiencedAdmin.prototype._updateTab = function(mode, $countEl) {
-	if(mode === LdapWizard.filterModeAssisted) {
-		$countEl.removeClass('hidden');
-	} else if(!this._isExperienced) {
-		$countEl.removeClass('hidden');
-	} else {
-		$countEl.addClass('hidden');
-	}
-};
-
-/**
- * hide user and group counters, they will be displayed on demand only
- */
-ExperiencedAdmin.prototype.hideEntryCounters = function() {
-	$('#ldap_user_count').addClass('hidden');
-	$('#ldap_group_count').addClass('hidden');
-	$('.ldapGetEntryCount').removeClass('hidden');
-};
-
-/**
-* shows user and group counters, they will be displayed on demand only
-*/
-ExperiencedAdmin.prototype.showEntryCounters = function() {
-	$('#ldap_user_count').removeClass('hidden');
-	$('#ldap_group_count').removeClass('hidden');
-	$('.ldapGetEntryCount').addClass('hidden');
-};
diff --git a/apps/user_ldap/js/ldapFilter.js b/apps/user_ldap/js/ldapFilter.js
deleted file mode 100644
index dc65858217dc0f8832266e470955f5fe867689c4..0000000000000000000000000000000000000000
--- a/apps/user_ldap/js/ldapFilter.js
+++ /dev/null
@@ -1,193 +0,0 @@
-/* global LdapWizard */
-
-function LdapFilter(target, determineModeCallback) {
-	this.locked = true;
-	this.target = false;
-	this.mode = LdapWizard.filterModeAssisted;
-	this.lazyRunCompose = false;
-	this.determineModeCallback = determineModeCallback;
-	this.foundFeatures = false;
-	this.activated = false;
-	this.countPending = false;
-
-	if( target === 'User' ||
-		target === 'Login' ||
-		target === 'Group') {
-		this.target = target;
-	}
-}
-
-LdapFilter.prototype.activate = function() {
-	if(this.activated) {
-		// might be necessary, if configuration changes happened.
-		this.findFeatures();
-		return;
-	}
-	this.activated = true;
-
-	this.determineMode();
-};
-
-LdapFilter.prototype.compose = function(updateCount) {
-	var action;
-
-	if(updateCount === true) {
-		this.countPending = updateCount;
-	}
-
-	if(this.locked) {
-		this.lazyRunCompose = true;
-		return false;
-	}
-
-	if(this.mode === LdapWizard.filterModeRaw) {
-		//Raw filter editing, i.e. user defined filter, don't compose
-		return;
-	}
-
-	if(this.target === 'User') {
-		action = 'getUserListFilter';
-	} else if(this.target === 'Login') {
-		action = 'getUserLoginFilter';
-	} else if(this.target === 'Group') {
-		action = 'getGroupFilter';
-	}
-
-	var param = 'action='+action+
-		'&ldap_serverconfig_chooser='+
-		encodeURIComponent($('#ldap_serverconfig_chooser').val());
-
-	var filter = this;
-
-	LdapWizard.ajax(param,
-		function(result) {
-			filter.afterComposeSuccess(result);
-		},
-		function () {
-			filter.countPending = false;
-			console.log('LDAP Wizard: could not compose filter. '+
-				'Please check owncloud.log');
-		}
-	);
-};
-
-/**
- * this function is triggered after LDAP filters have been composed successfully
- * @param {object} result returned by the ajax call
- */
-LdapFilter.prototype.afterComposeSuccess = function(result) {
-	LdapWizard.applyChanges(result);
-	if(this.countPending) {
-		this.countPending = false;
-		this.updateCount();
-	}
-};
-
-LdapFilter.prototype.determineMode = function() {
-	var param = 'action=get'+encodeURIComponent(this.target)+'FilterMode'+
-		'&ldap_serverconfig_chooser='+
-		encodeURIComponent($('#ldap_serverconfig_chooser').val());
-
-	var filter = this;
-	LdapWizard.ajax(param,
-		function(result) {
-			var property = 'ldap' + filter.target + 'FilterMode';
-			filter.mode = parseInt(result.changes[property], 10);
-			var rawContainerIsInvisible =
-				$('#raw'+filter.target+'FilterContainer').hasClass('invisible');
-			if (   filter.mode === LdapWizard.filterModeRaw
-				&& rawContainerIsInvisible
-			) {
-				LdapWizard['toggleRaw'+filter.target+'Filter']();
-			} else if (    filter.mode === LdapWizard.filterModeAssisted
-						&& !rawContainerIsInvisible
-			) {
-				LdapWizard['toggleRaw'+filter.target+'Filter']();
-			} else {
-				console.log('LDAP Wizard determineMode: returned mode was »' +
-					filter.mode + '« of type ' + typeof filter.mode);
-			}
-			filter.unlock();
-			filter.determineModeCallback(filter.mode);
-		},
-		function () {
-			//on error case get back to default i.e. Assisted
-			if(!$('#raw'+filter.target+'FilterContainer').hasClass('invisible')) {
-				LdapWizard['toggleRaw'+filter.target+'Filter']();
-				filter.mode = LdapWizard.filterModeAssisted;
-			}
-			filter.unlock();
-			filter.determineModeCallback(filter.mode);
-		}
-	);
-};
-
-LdapFilter.prototype.setMode = function(mode) {
-	if(mode === LdapWizard.filterModeAssisted || mode === LdapWizard.filterModeRaw) {
-		this.mode = mode;
-	}
-};
-
-LdapFilter.prototype.getMode = function() {
-	return this.mode;
-};
-
-LdapFilter.prototype.unlock = function() {
-	this.locked = false;
-	if(this.lazyRunCompose) {
-		this.lazyRunCompose = false;
-		this.compose();
-	}
-};
-
-/**
- * resets this.foundFeatures so that LDAP queries can be fired again to retrieve
- * objectClasses, groups, etc.
- */
-LdapFilter.prototype.reAllowFeatureLookup = function () {
-	this.foundFeatures = false;
-};
-
-LdapFilter.prototype.findFeatures = function() {
-	if(!this.foundFeatures && !this.locked && this.mode === LdapWizard.filterModeAssisted) {
-		this.foundFeatures = true;
-		var objcEl, avgrEl;
-		if(this.target === 'User') {
-			objcEl = 'ldap_userfilter_objectclass';
-			avgrEl = 'ldap_userfilter_groups';
-		} else if (this.target === 'Group') {
-			objcEl = 'ldap_groupfilter_objectclass';
-			avgrEl = 'ldap_groupfilter_groups';
-		} else if (this.target === 'Login') {
-			LdapWizard.findAttributes();
-			return;
-		} else {
-			return false;
-		}
-		LdapWizard.findObjectClasses(objcEl, this.target);
-		LdapWizard.findAvailableGroups(avgrEl, this.target + "s");
-	}
-};
-
-/**
- * this function is triggered before user and group counts are executed
- * resolving the passed status variable will fire up counting
- */
-LdapFilter.prototype.beforeUpdateCount = function() {
-	var status = $.Deferred();
-	LdapWizard.runDetectors(this.target, function() {
-		status.resolve();
-	});
-	return status;
-};
-
-LdapFilter.prototype.updateCount = function(doneCallback) {
-	var filter = this;
-	$.when(this.beforeUpdateCount()).done(function() {
-		if(filter.target === 'User') {
-			LdapWizard.countUsers(doneCallback);
-		} else if (filter.target === 'Group') {
-			LdapWizard.countGroups(doneCallback);
-		}
-	});
-};
diff --git a/apps/user_ldap/js/settings.js b/apps/user_ldap/js/settings.js
deleted file mode 100644
index 768d62a18d1d85b3f8179fd8d7b2851e00fc148e..0000000000000000000000000000000000000000
--- a/apps/user_ldap/js/settings.js
+++ /dev/null
@@ -1,1205 +0,0 @@
-var LdapConfiguration = {
-	refreshConfig: function() {
-		if($('#ldap_serverconfig_chooser option').length < 2) {
-			LdapConfiguration.addConfiguration(true);
-			return;
-		}
-		$.post(
-			OC.filePath('user_ldap','ajax','getConfiguration.php'),
-			$('#ldap_serverconfig_chooser').serialize(),
-			function (result) {
-				if(result.status === 'success') {
-					$.each(result.configuration, function(configkey, configvalue) {
-						elementID = '#'+configkey;
-
-						//deal with Checkboxes
-						if($(elementID).is('input[type=checkbox]')) {
-							if(parseInt(configvalue, 10) === 1) {
-								$(elementID).attr('checked', 'checked');
-							} else {
-								$(elementID).removeAttr('checked');
-							}
-							return;
-						}
-
-						//On Textareas, Multi-Line Settings come as array
-						if($(elementID).is('textarea') && $.isArray(configvalue)) {
-							configvalue = configvalue.join("\n");
-						}
-
-						// assign the value
-						$('#'+configkey).val(configvalue);
-					});
-					LdapWizard.init();
-				}
-			}
-		);
-	},
-
-	resetDefaults: function() {
-		$('#ldap').find('input[type=text], input[type=number], input[type=password], textarea, select').each(function() {
-			if($(this).attr('id') === 'ldap_serverconfig_chooser') {
-				return;
-			}
-			$(this).val($(this).attr('data-default'));
-		});
-		$('#ldap').find('input[type=checkbox]').each(function() {
-			if($(this).attr('data-default') === 1) {
-				$(this).attr('checked', 'checked');
-			} else {
-				$(this).removeAttr('checked');
-			}
-		});
-	},
-
-	deleteConfiguration: function() {
-		$.post(
-			OC.filePath('user_ldap','ajax','deleteConfiguration.php'),
-			$('#ldap_serverconfig_chooser').serialize(),
-			function (result) {
-				if(result.status === 'success') {
-					$('#ldap_serverconfig_chooser option:selected').remove();
-					$('#ldap_serverconfig_chooser option:first').select();
-					LdapConfiguration.refreshConfig();
-				} else {
-					OC.dialogs.alert(
-						result.message,
-						t('user_ldap', 'Deletion failed')
-					);
-				}
-			}
-		);
-	},
-
-	addConfiguration: function(doNotAsk) {
-		$.post(
-			OC.filePath('user_ldap','ajax','getNewServerConfigPrefix.php'),
-			function (result) {
-				if(result.status === 'success') {
-					if(doNotAsk) {
-						LdapConfiguration.resetDefaults();
-					} else {
-						OC.dialogs.confirm(
-							t('user_ldap', 'Take over settings from recent server configuration?'),
-							t('user_ldap', 'Keep settings?'),
-							function(keep) {
-								if(!keep) {
-									LdapConfiguration.resetDefaults();
-								}
-							}
-						);
-					}
-					$('#ldap_serverconfig_chooser option:selected').removeAttr('selected');
-					var html = '<option value="'+result.configPrefix+'" selected="selected">'+t('user_ldap','{nthServer}. Server', {nthServer: $('#ldap_serverconfig_chooser option').length})+'</option>';
-					$('#ldap_serverconfig_chooser option:last').before(html);
-					LdapWizard.init();
-				} else {
-					OC.dialogs.alert(
-						result.message,
-						t('user_ldap', 'Cannot add server configuration')
-					);
-				}
-			}
-		);
-	},
-
-	testConfiguration: function(onSuccess, onError) {
-		$.post(
-			OC.filePath('user_ldap','ajax','testConfiguration.php'),
-			$('#ldap').serialize(),
-			function (result) {
-				if (result.status === 'success') {
-					onSuccess(result);
-				} else {
-					onError(result);
-				}
-			}
-		);
-	},
-
-	clearMappings: function(mappingSubject) {
-		$.post(
-			OC.filePath('user_ldap','ajax','clearMappings.php'),
-			'ldap_clear_mapping='+encodeURIComponent(mappingSubject),
-			function(result) {
-				if(result.status === 'success') {
-					OC.dialogs.info(
-						t('user_ldap', 'mappings cleared'),
-						t('user_ldap', 'Success')
-					);
-				} else {
-					OC.dialogs.alert(
-						result.message,
-						t('user_ldap', 'Error')
-					);
-				}
-			}
-		);
-	}
-};
-
-var LdapWizard = {
-	checkPortInfoShown: false,
-	saveBlacklist: {},
-	userFilterGroupSelectState: 'enable',
-	spinner: '<img class="wizSpinner" src="'+ OC.imagePath('core', 'loading.gif') +'">',
-	filterModeAssisted: 0,
-	filterModeRaw: 1,
-	userFilter: false,
-	loginFilter: false,
-	groupFilter: false,
-	ajaxRequests: {},
-	lastTestSuccessful: true,
-
-	ajax: function(param, fnOnSuccess, fnOnError, reqID) {
-		if(!_.isUndefined(reqID)) {
-			if(LdapWizard.ajaxRequests.hasOwnProperty(reqID)) {
-				console.log('aborting ' + reqID);
-				console.log(param);
-				LdapWizard.ajaxRequests[reqID].abort();
-			}
-		}
-		var request = $.post(
-			OC.filePath('user_ldap','ajax','wizard.php'),
-			param,
-			function(result) {
-				if(result.status === 'success') {
-					fnOnSuccess(result);
-				} else {
-					fnOnError(result);
-				}
-			}
-		);
-		if(!_.isUndefined(reqID)) {
-			LdapWizard.ajaxRequests[reqID] = request;
-		}
-		return request;
-	},
-
-	applyChanges: function (result) {
-		for (var id in result.changes) {
-			LdapWizard.blacklistAdd(id);
-			if(id.indexOf('count') > 0) {
-				$('#'+id).text(result.changes[id]);
-			} else {
-				$('#'+id).val(result.changes[id]);
-			}
-		}
-		LdapWizard.functionalityCheck();
-
-		if($('#ldapSettings').tabs('option', 'active') == 0) {
-			LdapWizard.basicStatusCheck();
-		}
-	},
-
-	enableTabs: function() {
-		//do not use this function directly, use basicStatusCheck instead.
-		if(LdapWizard.saveProcesses === 0) {
-			$('.ldap_action_continue').removeAttr('disabled');
-			$('.ldap_action_back').removeAttr('disabled');
-			$('#ldapSettings').tabs('option', 'disabled', []);
-		}
-	},
-
-	disableTabs: function() {
-		$('.ldap_action_continue').attr('disabled', 'disabled');
-		$('.ldap_action_back').attr('disabled', 'disabled');
-		$('#ldapSettings').tabs('option', 'disabled', [1, 2, 3, 4, 5]);
-	},
-
-	basicStatusCheck: function() {
-		//criteria to continue from the first tab
-		// - host, port, user filter, agent dn, password, base dn
-		var host  = $('#ldap_host').val();
-		var port  = $('#ldap_port').val();
-		var agent = $('#ldap_dn').val();
-		var pwd   = $('#ldap_agent_password').val();
-		var base  = $('#ldap_base').val();
-
-		if((host && port  && base) && ((!agent && !pwd) || (agent && pwd))) {
-			LdapWizard.enableTabs();
-		} else {
-			LdapWizard.disableTabs();
-		}
-	},
-
-
-	blacklistAdd: function(id) {
-		var obj = $('#' + id);
-		if(!(obj[0].hasOwnProperty('multiple') && obj[0]['multiple'] === true)) {
-			//no need to blacklist multiselect
-			LdapWizard.saveBlacklist[id] = true;
-			return true;
-		}
-		return false;
-	},
-
-	blacklistRemove: function(id) {
-		if(LdapWizard.saveBlacklist.hasOwnProperty(id)) {
-			delete LdapWizard.saveBlacklist[id];
-			return true;
-		}
-		return false;
-	},
-
-	checkBaseDN: function() {
-		var host = $('#ldap_host').val();
-		var port = $('#ldap_port').val();
-		var user = $('#ldap_dn').val();
-		var pass = $('#ldap_agent_password').val();
-
-		//FIXME: determine base dn with anonymous access
-		if(host && port && user && pass) {
-			var param = 'action=guessBaseDN'+
-					'&ldap_serverconfig_chooser='+
-					encodeURIComponent($('#ldap_serverconfig_chooser').val());
-
-			LdapWizard.showSpinner('#ldap_base');
-			$('#ldap_base').prop('disabled', 'disabled');
-			LdapWizard.ajax(param,
-				function(result) {
-					LdapWizard.applyChanges(result);
-					LdapWizard.hideSpinner('#ldap_base');
-					if($('#ldap_base').val()) {
-						LdapWizard.hideInfoBox();
-					}
-					$('#ldap_base').prop('disabled', false);
-				},
-				function (result) {
-					LdapWizard.hideSpinner('#ldap_base');
-					LdapWizard.showInfoBox(t('user_ldap', 'Please specify a Base DN'));
-					LdapWizard.showInfoBox(t('user_ldap', 'Could not determine Base DN'));
-					$('#ldap_base').prop('disabled', false);
-				},
-				'guessBaseDN'
-			);
-		}
-	},
-
-	checkPort: function() {
-		var host = $('#ldap_host').val();
-		var port = $('#ldap_port').val();
-
-		if(host && !port) {
-			var param = 'action=guessPortAndTLS'+
-					'&ldap_serverconfig_chooser='+
-					encodeURIComponent($('#ldap_serverconfig_chooser').val());
-
-			LdapWizard.showSpinner('#ldap_port');
-			$('#ldap_port').prop('disabled', 'disabled');
-			LdapWizard.ajax(param,
-				function(result) {
-					LdapWizard.applyChanges(result);
-					LdapWizard.hideSpinner('#ldap_port');
-					if($('#ldap_port').val()) {
-						LdapWizard.checkBaseDN();
-						$('#ldap_port').prop('disabled', false);
-						LdapWizard.hideInfoBox();
-					}
-				},
-				function (result) {
-					LdapWizard.hideSpinner('#ldap_port');
-					$('#ldap_port').prop('disabled', false);
-					LdapWizard.showInfoBox(t('user_ldap', 'Please specify the port'));
-				},
-				'guessPortAndTLS'
-			);
-		}
-	},
-
-	controlBack: function() {
-		var curTabIndex = $('#ldapSettings').tabs('option', 'active');
-		if(curTabIndex == 0) {
-			return;
-		}
-		$('#ldapSettings').tabs('option', 'active', curTabIndex - 1);
-		LdapWizard.controlUpdate(curTabIndex - 1);
-	},
-
-	controlContinue: function() {
-		var curTabIndex = $('#ldapSettings').tabs('option', 'active');
-		if(curTabIndex == 3) {
-			return;
-		}
-		$('#ldapSettings').tabs('option', 'active', 1 + curTabIndex);
-		LdapWizard.controlUpdate(curTabIndex + 1);
-	},
-
-	controlUpdate: function(nextTabIndex) {
-		if(nextTabIndex == 0) {
-			$('.ldap_action_back').addClass('invisible');
-			$('.ldap_action_continue').removeClass('invisible');
-		} else
-		if(nextTabIndex == 1) {
-			$('.ldap_action_back').removeClass('invisible');
-			$('.ldap_action_continue').removeClass('invisible');
-		} else
-		if(nextTabIndex == 2) {
-			$('.ldap_action_continue').removeClass('invisible');
-			$('.ldap_action_back').removeClass('invisible');
-		} else
-		if(nextTabIndex == 3) {
-			//now last tab
-			$('.ldap_action_back').removeClass('invisible');
-			$('.ldap_action_continue').addClass('invisible');
-		}
-	},
-
-	_countThings: function(method, spinnerID, doneCallback) {
-		var param = 'action='+method+
-				'&ldap_serverconfig_chooser='+
-				encodeURIComponent($('#ldap_serverconfig_chooser').val());
-
-		LdapWizard.showSpinner(spinnerID);
-		LdapWizard.ajax(param,
-			function(result) {
-				LdapWizard.applyChanges(result);
-				LdapWizard.hideSpinner(spinnerID);
-				if(!_.isUndefined(doneCallback)) {
-					doneCallback(method);
-				}
-			},
-			function (result) {
-				OC.Notification.showTemporary('Counting the entries failed with: ' + result.message);
-				LdapWizard.hideSpinner(spinnerID);
-				if(!_.isUndefined(doneCallback)) {
-					doneCallback(method);
-				}
-			},
-			method
-		);
-	},
-
-	countGroups: function(doneCallback) {
-		var groupFilter  = $('#ldap_group_filter').val();
-		if(!_.isEmpty(groupFilter)) {
-			LdapWizard._countThings('countGroups', '#ldap_group_count', doneCallback);
-		}
-	},
-
-	countUsers: function(doneCallback) {
-		var userFilter  = $('#ldap_userlist_filter').val();
-		if(!_.isEmpty(userFilter)) {
-			LdapWizard._countThings('countUsers', '#ldap_user_count', doneCallback);
-		}
-	},
-
-	/**
-	 * called after detectors have run
-	 * @callback runDetectorsCallback
-	 */
-
-	/**
-	 * runs detectors to determine appropriate attributes, e.g. displayName
-	 * @param {string} type either "User" or "Group"
-	 * @param {runDetectorsCallback} triggered after all detectors have completed
-	 */
-	runDetectors: function(type, callback) {
-		if(type === 'Group') {
-			$.when(LdapWizard.detectGroupMemberAssoc())
-				.then(callback, callback);
-			if(   LdapWizard.admin.isExperienced
-			   && !(LdapWizard.detectorsRunInXPMode & LdapWizard.groupDetectors)) {
-				LdapWizard.detectorsRunInXPMode += LdapWizard.groupDetectors;
-			}
-		} else if(type === 'User') {
-			var req1 = LdapWizard.detectUserDisplayNameAttribute();
-			var req2 = LdapWizard.detectEmailAttribute();
-			$.when(req1, req2)
-				.then(callback, callback);
-			if(   LdapWizard.admin.isExperienced
-			   && !(LdapWizard.detectorsRunInXPMode & LdapWizard.userDetectors)) {
-				LdapWizard.detectorsRunInXPMode += LdapWizard.userDetectors;
-			}
-		}
-	},
-
-	/**
-	 * runs detector to find out a fitting user display name attribute
-	 */
-	detectUserDisplayNameAttribute: function() {
-		var param = 'action=detectUserDisplayNameAttribute' +
-			'&ldap_serverconfig_chooser='+
-			encodeURIComponent($('#ldap_serverconfig_chooser').val());
-
-		//runs in the background, no callbacks necessary
-		return LdapWizard.ajax(param, LdapWizard.applyChanges, function(){}, 'detectUserDisplayNameAttribute');
-	},
-
-	detectEmailAttribute: function() {
-		var param = 'action=detectEmailAttribute'+
-				'&ldap_serverconfig_chooser='+
-				encodeURIComponent($('#ldap_serverconfig_chooser').val());
-		//runs in the background, no callbacks necessary
-		return LdapWizard.ajax(param, LdapWizard.applyChanges, function(){}, 'detectEmailAttribute');
-	},
-
-	detectGroupMemberAssoc: function() {
-		param = 'action=determineGroupMemberAssoc'+
-				'&ldap_serverconfig_chooser='+
-				encodeURIComponent($('#ldap_serverconfig_chooser').val());
-
-		return LdapWizard.ajax(param,
-			function(result) {
-				//pure background story
-			},
-			function (result) {
-				// error handling
-			},
-			'determineGroupMemberAssoc'
-		);
-	},
-
-	findAttributes: function() {
-		param = 'action=determineAttributes'+
-				'&ldap_serverconfig_chooser='+
-				encodeURIComponent($('#ldap_serverconfig_chooser').val());
-
-		LdapWizard.showSpinner('#ldap_loginfilter_attributes');
-		LdapWizard.ajax(param,
-			function(result) {
-				$('#ldap_loginfilter_attributes').find('option').remove();
-				for (var i in result.options['ldap_loginfilter_attributes']) {
-					//FIXME: move HTML into template
-					var attr = result.options['ldap_loginfilter_attributes'][i];
-					$('#ldap_loginfilter_attributes').append(
-								"<option value='"+attr+"'>"+attr+"</option>");
-				}
-				LdapWizard.hideSpinner('#ldap_loginfilter_attributes');
-				LdapWizard.applyChanges(result);
-				$('#ldap_loginfilter_attributes').multiselect('refresh');
-				if($('#rawLoginFilterContainer').hasClass('invisible')) {
-					$('#ldap_loginfilter_attributes').multiselect('enable');
-				}
-				LdapWizard.postInitLoginFilter();
-			},
-			function (result) {
-				//deactivate if no attributes found
-				$('#ldap_loginfilter_attributes').multiselect(
-									{noneSelectedText : 'No attributes found'});
-				$('#ldap_loginfilter_attributes').multiselect('disable');
-				LdapWizard.hideSpinner('#ldap_loginfilter_attributes');
-			},
-			'determineAttributes'
-		);
-	},
-
-	findAvailableGroups: function(multisel, type) {
-		if(type !== 'Users' && type !== 'Groups') {
-			return false;
-		}
-		param = 'action=determineGroupsFor'+encodeURIComponent(type)+
-				'&ldap_serverconfig_chooser='+
-				encodeURIComponent($('#ldap_serverconfig_chooser').val());
-
-		LdapWizard.showSpinner('#'+multisel);
-		LdapWizard.ajax(param,
-			function(result) {
-				$('#'+multisel).find('option').remove();
-				for (var i in result.options[multisel]) {
-					//FIXME: move HTML into template
-					objc = result.options[multisel][i];
-					$('#'+multisel).append("<option value='"+objc+"'>"+objc+"</option>");
-				}
-				LdapWizard.hideSpinner('#'+multisel);
-				LdapWizard.applyChanges(result);
-				$('#'+multisel).multiselect('refresh');
-				part = type.slice(0, -1);
-				if($('#raw' + part + 'FilterContainer').hasClass('invisible')) {
-					//enable only when raw filter editing is not turned on
-					$('#'+multisel).multiselect('enable');
-				}
-				if(type === 'Users') {
-					//required for initial save
-					filter = $('#ldap_userlist_filter').val();
-					if(!filter) {
-						LdapWizard.saveMultiSelect(multisel,
-									$('#'+multisel).multiselect("getChecked"));
-					}
-					LdapWizard.userFilterAvailableGroupsHasRun = true;
-					LdapWizard.postInitUserFilter();
-				}
-			},
-			function (result) {
-				LdapWizard.hideSpinner('#'+multisel);
-				$('#'+multisel).multiselect('disable');
-				if(type === 'Users') {
-					LdapWizard.userFilterAvailableGroupsHasRun = true;
-					LdapWizard.postInitUserFilter();
-				}
-			},
-			'findAvailableGroupsFor' + type
-		);
-	},
-
-	findObjectClasses: function(multisel, type) {
-		if(type !== 'User' && type !== 'Group') {
-			return false;
-		}
-		var param = 'action=determine'+encodeURIComponent(type)+'ObjectClasses'+
-				'&ldap_serverconfig_chooser='+
-				encodeURIComponent($('#ldap_serverconfig_chooser').val());
-
-		LdapWizard.showSpinner('#'+multisel);
-		LdapWizard.ajax(param,
-			function(result) {
-				$('#'+multisel).find('option').remove();
-				for (var i in result.options[multisel]) {
-					//FIXME: move HTML into template
-					objc = result.options[multisel][i];
-					$('#'+multisel).append("<option value='"+objc+"'>"+objc+"</option>");
-				}
-				LdapWizard.hideSpinner('#'+multisel);
-				LdapWizard.applyChanges(result);
-				$('#'+multisel).multiselect('refresh');
-				if(type === 'User') {
-					//required for initial save
-					filter = $('#ldap_userlist_filter').val();
-					if(!filter) {
-						LdapWizard.saveMultiSelect(multisel,
-										$('#'+multisel).multiselect("getChecked"));
-					}
-					LdapWizard.userFilterObjectClassesHasRun = true;
-					LdapWizard.postInitUserFilter();
-				}
-			},
-			function (result) {
-				LdapWizard.hideSpinner('#'+multisel);
-				if(type === 'User') {
-					LdapWizard.userFilterObjectClassesHasRun = true;
-					LdapWizard.postInitUserFilter();
-				}
-				//TODO: error handling
-			},
-			'determine' + type + 'ObjectClasses'
-		);
-	},
-
-	functionalityCheck: function() {
-		//criteria to enable the connection:
-		// - host, port, basedn, user filter, login filter
-		var host        = $('#ldap_host').val();
-		var port        = $('#ldap_port').val();
-		var base        = $('#ldap_base').val();
-		var userfilter  = $('#ldap_userlist_filter').val();
-		var loginfilter = $('#ldap_login_filter').val();
-
-		//FIXME: activates a manually deactivated configuration.
-		if(host && port && base && userfilter && loginfilter) {
-			LdapWizard.updateStatusIndicator(true);
-			if($('#ldap_configuration_active').is(':checked')) {
-				return;
-			}
-			if(!LdapWizard.isConfigurationActiveControlLocked) {
-				//avoids a manually deactivated connection will be activated
-				//upon opening the admin page
-				$('#ldap_configuration_active').prop('checked', true);
-				LdapWizard.save($('#ldap_configuration_active')[0]);
-			}
-		} else {
-			if($('#ldap_configuration_active').is(':checked')) {
-				$('#ldap_configuration_active').prop('checked', false);
-				LdapWizard.save($('#ldap_configuration_active')[0]);
-			}
-			LdapWizard.updateStatusIndicator(false);
-		}
-	},
-
-	hideInfoBox: function() {
-		if(LdapWizard.checkInfoShown) {
-			$('#ldapWizard1 .ldapWizardInfo').addClass('invisible');
-			LdapWizard.checkInfoShown = false;
-		}
-	},
-
-	hideSpinner: function(id) {
-		$(id+' + .wizSpinner').remove();
-		$(id + " + button").css('display', 'inline');
-	},
-
-	isConfigurationActiveControlLocked: true,
-	detectorsRunInXPMode: 0,
-	userDetectors: 1,
-	groupDetectors: 2,
-
-	init: function() {
-		LdapWizard.detectorsRunInXPMode = 0;
-		LdapWizard.instantiateFilters();
-		LdapWizard.admin.setExperienced($('#ldap_experienced_admin').is(':checked'));
-		LdapWizard.lastTestSuccessful = true;
-		LdapWizard.basicStatusCheck();
-		LdapWizard.functionalityCheck();
-		LdapWizard.isConfigurationActiveControlLocked = false;
-	},
-
-	initGroupFilter: function() {
-		LdapWizard.groupFilter.activate();
-	},
-
-	/** init login filter tab section **/
-
-	initLoginFilter: function() {
-		LdapWizard.loginFilter.activate();
-	},
-
-	postInitLoginFilter: function() {
-		if($('#rawLoginFilterContainer').hasClass('invisible')) {
-			LdapWizard.loginFilter.compose();
-		}
-	},
-
-	/** end of init user filter tab section **/
-
-	initMultiSelect: function(object, id, caption) {
-		object.multiselect({
-			header: false,
-			selectedList: 9,
-			noneSelectedText: caption,
-			click: function(event, ui) {
-				LdapWizard.saveMultiSelect(id,
-										$('#'+id).multiselect("getChecked"));
-			}
-		});
-	},
-
-	hideTestSpinner:function (countMethod) {
-		var selector;
-		if(countMethod === 'countUsers') {
-			selector = '#rawUserFilterContainer .ldapGetEntryCount';
-		} else {
-			selector = '#rawGroupFilterContainer .ldapGetEntryCount';
-		}
-		LdapWizard.hideSpinner(selector);
-	},
-
-	/** init user filter tab section **/
-
-	instantiateFilters: function() {
-		delete LdapWizard.userFilter;
-		LdapWizard.userFilter = new LdapFilter('User', function(mode) {
-			if( !LdapWizard.admin.isExperienced()
-			   || mode === LdapWizard.filterModeAssisted) {
-				LdapWizard.userFilter.updateCount();
-			}
-			LdapWizard.userFilter.findFeatures();
-		});
-		$('#rawUserFilterContainer .ldapGetEntryCount').click(function(event) {
-			event.preventDefault();
-			$('#ldap_user_count').text('');
-			LdapWizard.showSpinner('#rawUserFilterContainer .ldapGetEntryCount');
-			LdapWizard.userFilter.updateCount(LdapWizard.hideTestSpinner);
-			$('#ldap_user_count').removeClass('hidden');
-		});
-
-		delete LdapWizard.loginFilter;
-		LdapWizard.loginFilter = new LdapFilter('Login', function(mode) {
-			LdapWizard.loginFilter.findFeatures();
-		});
-
-		delete LdapWizard.groupFilter;
-		LdapWizard.groupFilter = new LdapFilter('Group', function(mode) {
-			if( !LdapWizard.admin.isExperienced()
-			   || mode === LdapWizard.filterModeAssisted) {
-				LdapWizard.groupFilter.updateCount();
-			}
-			LdapWizard.groupFilter.findFeatures();
-		});
-		$('#rawGroupFilterContainer .ldapGetEntryCount').click(function(event) {
-			event.preventDefault();
-			$('#ldap_group_count').text('');
-			LdapWizard.showSpinner('#rawGroupFilterContainer .ldapGetEntryCount');
-			LdapWizard.groupFilter.updateCount(LdapWizard.hideTestSpinner);
-			$('#ldap_group_count').removeClass('hidden');
-		});
-	},
-
-	userFilterObjectClassesHasRun: false,
-	userFilterAvailableGroupsHasRun: false,
-
-	initUserFilter: function() {
-		LdapWizard.userFilterObjectClassesHasRun = false;
-		LdapWizard.userFilterAvailableGroupsHasRun = false;
-		LdapWizard.userFilter.activate();
-	},
-
-	postInitUserFilter: function() {
-		if(LdapWizard.userFilterObjectClassesHasRun &&
-			LdapWizard.userFilterAvailableGroupsHasRun) {
-			LdapWizard.userFilter.compose();
-		}
-	},
-
-	/** end of init user filter tab section **/
-
-	onTabChange: function(event, ui) {
-		if(LdapWizard.saveProcesses  > 0) {
-			//do not allow to switch tabs as long as a save process is active
-			return false;
-		}
-		var newTabIndex = 0;
-		if(ui.newTab[0].id === '#ldapWizard2') {
-			LdapWizard.initUserFilter();
-			newTabIndex = 1;
-		} else if(ui.newTab[0].id === '#ldapWizard3') {
-			LdapWizard.initLoginFilter();
-			newTabIndex = 2;
-		} else if(ui.newTab[0].id === '#ldapWizard4') {
-			LdapWizard.initGroupFilter();
-			newTabIndex = 3;
-		}
-
-		var curTabIndex = $('#ldapSettings').tabs('option', 'active');
-		if(curTabIndex >= 0 && curTabIndex <= 3) {
-			LdapWizard.controlUpdate(newTabIndex);
-			//run detectors in XP mode, when "Test Filter" button has not been
-			//clicked in order to make sure that email, displayname, member-
-			//group association attributes are properly set.
-			if(   curTabIndex === 1
-			   && LdapWizard.admin.isExperienced
-			   && !(LdapWizard.detecorsRunInXPMode & LdapWizard.userDetectors)
-			) {
-				LdapWizard.runDetectors('User', function(){});
-			} else if(   curTabIndex === 3
-			          && LdapWizard.admin.isExperienced
-			          && !(LdapWizard.detecorsRunInXPMode & LdapWizard.groupDetectors)
-			) {
-				LdapWizard.runDetectors('Group', function(){});
-			}
-		}
-	},
-
-	/**
-	 * allows UserFilter, LoginFilter and GroupFilter to lookup objectClasses
-	 * and similar again. This should be called after essential changes, e.g.
-	 * Host or BaseDN changes, or positive functionality check
-	 *
-	 */
-	allowFilterFeatureSearch: function () {
-		LdapWizard.userFilter.reAllowFeatureLookup();
-		LdapWizard.loginFilter.reAllowFeatureLookup();
-		LdapWizard.groupFilter.reAllowFeatureLookup();
-	},
-
-	processChanges: function (triggerObj) {
-		LdapWizard.hideInfoBox();
-
-		if(triggerObj.id === 'ldap_host'
-		   || triggerObj.id === 'ldap_port'
-		   || triggerObj.id === 'ldap_dn'
-		   || triggerObj.id === 'ldap_agent_password') {
-			LdapWizard.checkPort();
-			if($('#ldap_port').val()) {
-				//if Port is already set, check BaseDN
-				LdapWizard.checkBaseDN();
-				LdapWizard.allowFilterFeatureSearch();
-			}
-		}
-
-		if(triggerObj.id === 'ldap_loginfilter_username'
-		   || triggerObj.id === 'ldap_loginfilter_email') {
-			LdapWizard.loginFilter.compose();
-		} else if (!LdapWizard.admin.isExperienced()) {
-			if(triggerObj.id === 'ldap_userlist_filter') {
-				LdapWizard.userFilter.updateCount();
-			} else if (triggerObj.id === 'ldap_group_filter') {
-				LdapWizard.groupFilter.updateCount();
-			}
-		}
-
-		if($('#ldapSettings').tabs('option', 'active') == 0) {
-			LdapWizard.basicStatusCheck();
-			LdapWizard.functionalityCheck();
-		}
-	},
-
-	save: function(inputObj) {
-		if(LdapWizard.blacklistRemove(inputObj.id)) {
-			return;
-		}
-		if($(inputObj).is('input[type=checkbox]')
-		   && !$(inputObj).is(':checked')) {
-			val = 0;
-		} else {
-			val = $(inputObj).val();
-		}
-		LdapWizard._save(inputObj, val);
-	},
-
-	/**
-	 * updates user or group count on multiSelect close. Resets the event
-	 * function subsequently.
-	 *
-	 * @param {LdapFilter} filter
-	 * @param {Object} $multiSelectObj
-	 */
-	onMultiSelectClose: function(filter, $multiSelectObj) {
-		filter.updateCount();
-		$multiSelectObj.multiselect({close: function(){}});
-	},
-
-	saveMultiSelect: function(originalObj, resultObj) {
-		var values = '';
-		for(var i = 0; i < resultObj.length; i++) {
-			values = values + "\n" + resultObj[i].value;
-		}
-		LdapWizard._save($('#'+originalObj)[0], $.trim(values));
-		var $multiSelectObj = $('#'+originalObj);
-		var updateCount = !$multiSelectObj.multiselect("isOpen");
-		var applyUpdateOnCloseToFilter;
-		if(originalObj === 'ldap_userfilter_objectclass'
-		   || originalObj === 'ldap_userfilter_groups') {
-			LdapWizard.userFilter.compose(updateCount);
-			if(!updateCount) {
-				applyUpdateOnCloseToFilter = LdapWizard.userFilter;
-			}
-			//when user filter is changed afterwards, login filter needs to
-			//be adjusted, too
-			if(!LdapWizard.loginFilter) {
-				LdapWizard.initLoginFilter();
-			}
-			LdapWizard.loginFilter.compose();
-		} else if(originalObj === 'ldap_loginfilter_attributes') {
-			LdapWizard.loginFilter.compose();
-		} else if(originalObj === 'ldap_groupfilter_objectclass'
-		   || originalObj === 'ldap_groupfilter_groups') {
-			LdapWizard.groupFilter.compose(updateCount);
-			if(!updateCount) {
-				applyUpdateOnCloseToFilter = LdapWizard.groupFilter;
-			}
-		}
-
-		if(applyUpdateOnCloseToFilter instanceof LdapFilter) {
-			$multiSelectObj.multiselect({
-				close: function () {
-					LdapWizard.onMultiSelectClose(
-						applyUpdateOnCloseToFilter, $multiSelectObj);
-				}
-			});
-		}
-	},
-
-	saveProcesses: 0,
-	_save: function(object, value) {
-		$('#ldap .ldap_saving').removeClass('hidden');
-		LdapWizard.saveProcesses += 1;
-		$('#ldap *').addClass('save-cursor');
-		param = 'cfgkey='+encodeURIComponent(object.id)+
-				'&cfgval='+encodeURIComponent(value)+
-				'&action=save'+
-				'&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val();
-
-		$.post(
-			OC.filePath('user_ldap','ajax','wizard.php'),
-			param,
-			function(result) {
-				LdapWizard.saveProcesses -= 1;
-				if(LdapWizard.saveProcesses === 0) {
-					$('#ldap .ldap_saving').addClass('hidden');
-					$('#ldap *').removeClass('save-cursor');
-				}
-				if(result.status === 'success') {
-					LdapWizard.processChanges(object);
-				} else {
-					console.log('Could not save value for ' + object.id);
-				}
-			}
-		);
-	},
-
-	showInfoBox: function(text) {
-		$('#ldapWizard1 .ldapWizardInfo').text(text);
-		$('#ldapWizard1 .ldapWizardInfo').removeClass('invisible');
-		LdapWizard.checkInfoShown = true;
-	},
-
-	showSpinner: function(id) {
-		if($(id + ' + .wizSpinner').length == 0) {
-			$(LdapWizard.spinner).insertAfter($(id));
-			$(id + " + img + button").css('display', 'none');
-		}
-	},
-
-	toggleRawFilter: function(container, moc, mg, stateVar, modeKey) {
-		var isUser = moc.indexOf('user') >= 0;
-		var filter = isUser ? LdapWizard.userFilter : LdapWizard.groupFilter;
-		//moc = multiselect objectclass
-		//mg = mutliselect groups
-		if($(container).hasClass('invisible')) {
-			filter.setMode(LdapWizard.filterModeRaw);
-			$(container).removeClass('invisible');
-			$(moc).multiselect('disable');
-			if($(mg).multiselect().attr('disabled') === 'disabled') {
-				LdapWizard[stateVar] = 'disable';
-			} else {
-				LdapWizard[stateVar] = 'enable';
-			}
-			$(mg).multiselect('disable');
-			LdapWizard._save({ id: modeKey }, LdapWizard.filterModeRaw);
-		} else {
-			filter.setMode(LdapWizard.filterModeAssisted);
-			filter.findFeatures();
-			$(container).addClass('invisible');
-			$(mg).multiselect(LdapWizard[stateVar]);
-			$(moc).multiselect('enable');
-			LdapWizard._save({ id: modeKey }, LdapWizard.filterModeAssisted);
-			if(isUser) {
-				LdapWizard.blacklistRemove('ldap_userlist_filter');
-				LdapWizard.userFilter.compose(true);
-			} else {
-				LdapWizard.blacklistRemove('ldap_group_filter');
-				LdapWizard.groupFilter.compose(true);
-			}
-		}
-	},
-
-	onToggleRawFilterConfirmation: function(currentMode, isRawVisible, callback) {
-		if(   !LdapWizard.admin.isExperienced()
-		   || currentMode === LdapWizard.filterModeAssisted
-		   || (LdapWizard.admin.isExperienced() && !isRawVisible)
-		) {
-			return callback(true);
-		}
-
-		var confirmed = OCdialogs.confirm(
-			'Switching the mode will enable automatic LDAP queries. Depending on your LDAP size they may take a while. Do you still want to switch the mode?',
-			'Mode switch',
-			callback
-		);
-	},
-
-	toggleRawGroupFilter: function() {
-		LdapWizard.onToggleRawFilterConfirmation(
-			LdapWizard.groupFilter.getMode(),
-			!$('#rawGroupFilterContainer').hasClass('invisible'),
-			function(confirmed) {
-				if(confirmed !== true) {
-					return;
-				}
-
-				LdapWizard.blacklistRemove('ldap_group_filter');
-				LdapWizard.toggleRawFilter('#rawGroupFilterContainer',
-										   '#ldap_groupfilter_objectclass',
-										   '#ldap_groupfilter_groups',
-										   'groupFilterGroupSelectState',
-										   'ldapGroupFilterMode'
-		  								);
-				LdapWizard.admin.updateGroupTab(LdapWizard.groupFilter.getMode());
-			}
-		);
-	},
-
-	toggleRawLoginFilter: function() {
-		LdapWizard.onToggleRawFilterConfirmation(
-			LdapWizard.loginFilter.getMode(),
-			!$('#rawLoginFilterContainer').hasClass('invisible'),
-			function(confirmed) {
-				if(confirmed !== true) {
-					return;
-				}
-
-				LdapWizard.blacklistRemove('ldap_login_filter');
-				container = '#rawLoginFilterContainer';
-				if($(container).hasClass('invisible')) {
-					$(container).removeClass('invisible');
-					action = 'disable';
-					property = 'disabled';
-					mode = LdapWizard.filterModeRaw;
-				} else {
-					$(container).addClass('invisible');
-					action = 'enable';
-					property = false;
-					mode = LdapWizard.filterModeAssisted;
-				}
-				LdapWizard.loginFilter.setMode(mode);
-				LdapWizard.loginFilter.findFeatures();
-				$('#ldap_loginfilter_attributes').multiselect(action);
-				$('#ldap_loginfilter_email').prop('disabled', property);
-				$('#ldap_loginfilter_username').prop('disabled', property);
-				LdapWizard._save({ id: 'ldapLoginFilterMode' }, mode);
-				if(action === 'enable') {
-					LdapWizard.loginFilter.compose();
-				}
-			}
-		);
-	},
-
-	toggleRawUserFilter: function() {
-		LdapWizard.onToggleRawFilterConfirmation(
-			LdapWizard.userFilter.getMode(),
-			!$('#rawUserFilterContainer').hasClass('invisible'),
-			function(confirmed) {
-				if(confirmed === true) {
-					LdapWizard.blacklistRemove('ldap_userlist_filter');
-					LdapWizard.toggleRawFilter('#rawUserFilterContainer',
-											   '#ldap_userfilter_objectclass',
-											   '#ldap_userfilter_groups',
-											   'userFilterGroupSelectState',
-											   'ldapUserFilterMode'
-			  								);
-					LdapWizard.admin.updateUserTab(LdapWizard.userFilter.getMode());
-				}
-			}
-		);
-	},
-
-	updateStatusIndicator: function(isComplete) {
-		if(isComplete) {
-			LdapConfiguration.testConfiguration(
-				//onSuccess
-				function(result) {
-					$('.ldap_config_state_indicator').text(t('user_ldap',
-						'Configuration OK'
-					));
-					$('.ldap_config_state_indicator').addClass('ldap_grey');
-					$('.ldap_config_state_indicator_sign').removeClass('error');
-					$('.ldap_config_state_indicator_sign').addClass('success');
-					if(!LdapWizard.lastTestSuccessful) {
-						LdapWizard.lastTestSuccessful = true;
-						LdapWizard.allowFilterFeatureSearch();
-					}
-				},
-				//onError
-				function(result) {
-					$('.ldap_config_state_indicator').text(t('user_ldap',
-						'Configuration incorrect'
-					));
-					$('.ldap_config_state_indicator').removeClass('ldap_grey');
-					$('.ldap_config_state_indicator_sign').addClass('error');
-					$('.ldap_config_state_indicator_sign').removeClass('success');
-					LdapWizard.lastTestSuccessful = false;
-				}
-			);
-		} else {
-			$('.ldap_config_state_indicator').text(t('user_ldap',
-				'Configuration incomplete'
-			));
-			$('.ldap_config_state_indicator').removeClass('ldap_grey');
-			$('.ldap_config_state_indicator_sign').removeClass('error');
-			$('.ldap_config_state_indicator_sign').removeClass('success');
-		}
-	}
-};
-
-$(document).ready(function() {
-	$('#ldapAdvancedAccordion').accordion({ heightStyle: 'content', animate: 'easeInOutCirc'});
-	$('#ldapSettings').tabs({ beforeActivate: LdapWizard.onTabChange });
-	$('.ldap_submit').button();
-	$('.ldap_action_test_connection').button();
-	$('#ldap_action_delete_configuration').button();
-	LdapWizard.initMultiSelect($('#ldap_userfilter_groups'),
-							   'ldap_userfilter_groups',
-							   t('user_ldap', 'Select groups'));
-	LdapWizard.initMultiSelect($('#ldap_userfilter_objectclass'),
-							   'ldap_userfilter_objectclass',
-							   t('user_ldap', 'Select object classes'));
-	LdapWizard.initMultiSelect($('#ldap_loginfilter_attributes'),
-							   'ldap_loginfilter_attributes',
-							   t('user_ldap', 'Select attributes'));
-	LdapWizard.initMultiSelect($('#ldap_groupfilter_groups'),
-							   'ldap_groupfilter_groups',
-							   t('user_ldap', 'Select groups'));
-	LdapWizard.initMultiSelect($('#ldap_groupfilter_objectclass'),
-							   'ldap_groupfilter_objectclass',
-							   t('user_ldap', 'Select object classes'));
-
-	$('.lwautosave').change(function() { LdapWizard.save(this); });
-	$('#toggleRawUserFilter').click(LdapWizard.toggleRawUserFilter);
-	$('#toggleRawGroupFilter').click(LdapWizard.toggleRawGroupFilter);
-	$('#toggleRawLoginFilter').click(LdapWizard.toggleRawLoginFilter);
-	LdapConfiguration.refreshConfig();
-	$('.ldap_action_continue').click(function(event) {
-		event.preventDefault();
-		LdapWizard.controlContinue();
-	});
-	$('.ldap_action_back').click(function(event) {
-		event.preventDefault();
-		LdapWizard.controlBack();
-	});
-	$('.ldap_action_test_connection').click(function(event){
-		event.preventDefault();
-		LdapConfiguration.testConfiguration(
-			//onSuccess
-			function(result) {
-				OC.dialogs.alert(
-					result.message,
-					t('user_ldap', 'Connection test succeeded')
-				);
-			},
-			//onError
-			function(result) {
-				OC.dialogs.alert(
-					result.message,
-					t('user_ldap', 'Connection test failed')
-				);
-			}
-		);
-	});
-
-	$('#ldap_action_delete_configuration').click(function(event) {
-		event.preventDefault();
-		OC.dialogs.confirm(
-			t('user_ldap', 'Do you really want to delete the current Server Configuration?'),
-			t('user_ldap', 'Confirm Deletion'),
-			function(deleteConfiguration) {
-				if(deleteConfiguration) {
-					LdapConfiguration.deleteConfiguration();
-				}
-			}
-		);
-	});
-
-	$('.ldap_submit').click(function(event) {
-		event.preventDefault();
-		$.post(
-			OC.filePath('user_ldap','ajax','setConfiguration.php'),
-			$('#ldap').serialize(),
-			function (result) {
-				bgcolor = $('.ldap_submit').css('background');
-				if (result.status === 'success') {
-					//the dealing with colors is a but ugly, but the jQuery version in use has issues with rgba colors
-					$('.ldap_submit').css('background', '#fff');
-					$('.ldap_submit').effect('highlight', {'color':'#A8FA87'}, 5000, function() {
-						$('.ldap_submit').css('background', bgcolor);
-					});
-					//update the Label in the config chooser
-					caption = $('#ldap_serverconfig_chooser option:selected:first').text();
-					pretext = '. Server: ';
-					caption = caption.slice(0, caption.indexOf(pretext) + pretext.length);
-					caption = caption + $('#ldap_host').val();
-					$('#ldap_serverconfig_chooser option:selected:first').text(caption);
-
-				} else {
-					$('.ldap_submit').css('background', '#fff');
-					$('.ldap_submit').effect('highlight', {'color':'#E97'}, 5000, function() {
-						$('.ldap_submit').css('background', bgcolor);
-					});
-				}
-			}
-		);
-	});
-
-	$('#ldap_action_clear_user_mappings').click(function(event) {
-		event.preventDefault();
-		LdapConfiguration.clearMappings('user');
-	});
-
-	$('#ldap_action_clear_group_mappings').click(function(event) {
-		event.preventDefault();
-		LdapConfiguration.clearMappings('group');
-	});
-
-	$('#ldap_serverconfig_chooser').change(function(event) {
-		value = $('#ldap_serverconfig_chooser option:selected:first').attr('value');
-		if(value === 'NEW') {
-			LdapConfiguration.addConfiguration(false);
-		} else {
-			LdapConfiguration.refreshConfig();
-		}
-	});
-
-	expAdminCB = $('#ldap_experienced_admin');
-	LdapWizard.admin = new ExperiencedAdmin(LdapWizard, expAdminCB.is(':checked'));
-	expAdminCB.change(function() {
-		LdapWizard.admin.setExperienced($(this).is(':checked'));
-	});
-});
diff --git a/apps/user_ldap/js/wizard/configModel.js b/apps/user_ldap/js/wizard/configModel.js
new file mode 100644
index 0000000000000000000000000000000000000000..c3f1e85b592e20e8fd6db6f6dd50c867080c50c4
--- /dev/null
+++ b/apps/user_ldap/js/wizard/configModel.js
@@ -0,0 +1,606 @@
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc this class represents a server configuration. It communicates
+	 * with the ownCloud server to ensure to always have the up to date LDAP
+	 * configuration. It sends various events that views can listen to and
+	 * provides methods so they can modify the configuration based upon user
+	 * input. This model is also extended by so-called "detectors" who let the
+	 * ownCloud server try to auto-detect settings and manipulate the
+	 * configuration as well.
+	 *
+	 * @constructor
+	 */
+	var ConfigModel = function() {};
+
+	ConfigModel.prototype = {
+		/** @constant {number} */
+		FILTER_MODE_ASSISTED: 0,
+		/** @constant {number} */
+		FILTER_MODE_RAW: 1,
+
+		/**
+		 * initializes the instance. Always call it after creating the instance.
+		 *
+		 * @param {OCA.LDAP.Wizard.WizardDetectorQueue} detectorQueue
+		 */
+		init: function (detectorQueue) {
+			/** @type {object} holds the configuration in key-value-pairs */
+			this.configuration     = {};
+			/** @type {object} holds the subscribers that listen to the events */
+			this.subscribers       = {};
+			/** @type {Array} holds registered detectors */
+			this.detectors         = [];
+			/** @type {boolean} whether a configuration is currently loading */
+			this.loadingConfig = false;
+
+			if(detectorQueue instanceof OCA.LDAP.Wizard.WizardDetectorQueue) {
+				/** @type {OCA.LDAP.Wizard.WizardDetectorQueue} */
+				this.detectorQueue = detectorQueue;
+			}
+		},
+
+		/**
+		 * loads a specified configuration
+		 *
+		 * @param {string} [configID] - the configuration id (or prefix)
+		 */
+		load: function (configID) {
+			if(this.loadingConfig) {
+				return;
+			}
+			this._resetDetectorQueue();
+
+			this.configID = configID;
+			var url = OC.generateUrl('apps/user_ldap/ajax/getConfiguration.php');
+			var params = OC.buildQueryString({ldap_serverconfig_chooser: configID});
+			this.loadingConfig = true;
+			var model = this;
+			$.post(url, params, function (result) { model._processLoadConfig(model, result) });
+		},
+
+		/**
+		 * creates a new LDAP configuration
+		 *
+		 * @param {boolean} [copyCurrent] - if true, the current configuration
+		 * is copied, otherwise a blank one is created.
+		 */
+		newConfig: function(copyCurrent) {
+			this._resetDetectorQueue();
+
+			var url = OC.generateUrl('apps/user_ldap/ajax/getNewServerConfigPrefix.php');
+			var params = {};
+			if(copyCurrent === true) {
+				params['copyConfig'] = this.configID;
+			}
+			params = OC.buildQueryString(params);
+			var model = this;
+			copyCurrent = _.isUndefined(copyCurrent) ? false : copyCurrent;
+			$.post(url, params, function (result) { model._processNewConfigPrefix(model, result, copyCurrent) });
+		},
+
+		/**
+		 * deletes the current configuration. This method will not ask for
+		 * confirmation, if desired it needs to be ensured by the caller.
+		 *
+		 * @param {string} [configID] - the configuration id (or prefix)
+		 */
+		deleteConfig: function(configID) {
+			var url = OC.generateUrl('apps/user_ldap/ajax/deleteConfiguration.php');
+			var params = OC.buildQueryString({ldap_serverconfig_chooser: configID});
+			var model = this;
+			$.post(url, params, function (result) { model._processDeleteConfig(model, result, configID) });
+		},
+
+		/**
+		 * @callback wizardCallBack
+		 * @param {ConfigModel} [model]
+		 * @param {OCA.LDAP.Wizard.WizardDetectorGeneric} [detector]
+		 * @param {object} [result] - response from the ajax request
+		 */
+
+		/**
+		 * calls an AJAX endpoint at ownCloud. This method should be called by
+		 * detectors only!
+		 *
+		 * @param {string} [params] - as return by OC.buildQueryString
+		 * @param {wizardCallBack} [callback]
+		 * @param {OCA.LDAP.Wizard.WizardDetectorGeneric} [detector]
+		 * @returns {jqXHR}
+		 */
+		callWizard: function(params, callback, detector) {
+			return this.callAjax('wizard.php', params, callback, detector);
+		},
+
+		/**
+		 * calls an AJAX endpoint at ownCloud. This method should be called by
+		 * detectors only!
+		 *
+		 * @param {string} destination - the desired end point
+		 * @param {string} [params] - as return by OC.buildQueryString
+		 * @param {wizardCallBack} [callback]
+		 * @param {OCA.LDAP.Wizard.WizardDetectorGeneric} [detector]
+		 * @returns {jqXHR}
+		 */
+		callAjax: function(destination, params, callback, detector) {
+			var url = OC.generateUrl('apps/user_ldap/ajax/' + destination);
+			var model = this;
+			return $.post(url, params, function (result) {
+				callback(model, detector,result);
+			});
+		},
+
+		/**
+		 * setRequested Event
+		 *
+		 * @event ConfigModel#setRequested
+		 * @type{object} - empty
+		 */
+
+		/**
+		 * modifies a configuration key. If a provided configuration key does
+		 * not exist or the provided value equals the current setting, false is
+		 * returned. Otherwise ownCloud server will be called to save the new
+		 * value, an event will notify when this is done. True is returned when
+		 * the request is sent, however it does not mean whether saving was
+		 * successful or not.
+		 *
+		 * This method is supposed to be called by views, after the user did a
+		 * change which needs to be saved.
+		 *
+		 * @param {string} [key]
+		 * @param {string|number} [value]
+		 * @returns {boolean}
+		 * @fires {ConfigModel#setRequested}
+		 */
+		set: function(key, value) {
+			if(_.isUndefined(this.configuration[key])) {
+				console.warn('will not save undefined key: ' + key);
+				return false;
+			}
+			if(this.configuration[key] === value) {
+				return false;
+			}
+			this._broadcast('setRequested', {});
+			var url = OC.generateUrl('apps/user_ldap/ajax/wizard.php');
+			var objParams = {
+				ldap_serverconfig_chooser: this.configID,
+				action: 'save',
+				cfgkey: key,
+				cfgval: value
+			};
+			var strParams = OC.buildQueryString(objParams);
+			var model = this;
+			$.post(url, strParams, function(result) { model._processSetResult(model, result, objParams) });
+			return true;
+		},
+
+		/**
+		 * configUpdated Event
+		 *
+		 * object property is a key-value-pair of the configuration key as index
+		 * and its value.
+		 *
+		 * @event ConfigModel#configUpdated
+		 * @type{object}
+		 */
+
+		/**
+		 * updates the model's configuration data. This should be called only,
+		 * when a new configuration value was received from the ownCloud server.
+		 * This is typically done by detectors, but never by views.
+		 *
+		 * Cancels with false if old and new values already match.
+		 *
+		 * @param {string} [key]
+		 * @param {string} [value]
+		 * @returns {boolean}
+		 * @fires ConfigModel#configUpdated
+		 */
+		update: function(key, value) {
+			if(this.configuration[key] === value) {
+				return false;
+			}
+			if(!_.isUndefined(this.configuration[key])) {
+				// don't write e.g. count values to the configuration
+				// they don't go as feature, yet
+				this.configuration[key] = value;
+			}
+			var configPart = {};
+			configPart[key] = value;
+			this._broadcast('configUpdated', configPart);
+		},
+
+		/**
+		 * @typedef {object} FeaturePayload
+		 * @property {string} feature
+		 * @property {Array} data
+		 */
+
+		/**
+		 * informs about a detected LDAP "feature" (wider sense). For examples,
+		 * the detected object classes for users or groups
+		 *
+		 * @param {FeaturePayload} payload
+		 */
+		inform: function(payload) {
+			this._broadcast('receivedLdapFeature', payload);
+		},
+
+		/**
+		 * @typedef {object} ErrorPayload
+		 * @property {string} message
+		 * @property {string} relatedKey
+		 */
+
+		/**
+		 * broadcasts an error message, if a wizard reply ended up in an error.
+		 * To be called by detectors.
+		 *
+		 * @param {ErrorPayload} payload
+		 */
+		gotServerError: function(payload) {
+			this._broadcast('serverError', payload);
+		},
+
+		/**
+		 * detectionStarted Event
+		 *
+		 * @event ConfigModel#detectionStarted
+		 * @type{string} - the target configuration key that is being
+		 * auto-detected
+		 */
+
+		/**
+		 * lets the model broadcast the info that a detector starts to run
+		 *
+		 * supposed to be called by detectors only
+		 *
+		 * @param {string} [key]
+		 * @fires ConfigModel#detectionStarted
+		 */
+		notifyAboutDetectionStart: function(key) {
+			this._broadcast('detectionStarted', key);
+		},
+
+		/**
+		 * detectionCompleted Event
+		 *
+		 * @event ConfigModel#detectionCompleted
+		 * @type{string} - the target configuration key that was
+		 * auto-detected
+		 */
+
+		/**
+		 * lets the model broadcast the info that a detector run was completed
+		 *
+		 * supposed to be called by detectors only
+		 *
+		 * @param {string} [key]
+		 * @fires ConfigModel#detectionCompleted
+		 */
+		notifyAboutDetectionCompletion: function(key) {
+			this._broadcast('detectionCompleted', key);
+		},
+
+		/**
+		 * @callback listenerCallback
+		 * @param {OCA.LDAP.Wizard.WizardTabGeneric|OCA.LDAP.Wizard.WizardView} [view]
+		 * @param  {object} [params]
+		 */
+
+		/**
+		 * registers a listener to an event
+		 *
+		 * the idea is that only views listen.
+		 *
+		 * @param {string} [name] - the event name
+		 * @param {listenerCallback} [fn]
+		 * @param {OCA.LDAP.Wizard.WizardTabGeneric|OCA.LDAP.Wizard.WizardView} [context]
+		 */
+		on: function(name, fn, context) {
+			if(_.isUndefined(this.subscribers[name])) {
+				this.subscribers[name] = [];
+			}
+			this.subscribers[name].push({fn: fn, context: context});
+		},
+
+		/**
+		 * starts a configuration test on the ownCloud server
+		 */
+		requestConfigurationTest: function() {
+			var url = OC.generateUrl('apps/user_ldap/ajax/testConfiguration.php');
+			var params = OC.buildQueryString(this.configuration);
+			var model = this;
+			$.post(url, params, function(result) { model._processTestResult(model, result) });
+			//TODO: make sure only one test is running at a time
+		},
+
+		/**
+		 * the view may request a call to the wizard, for instance to fetch
+		 * object classes or groups
+		 *
+		 * @param {string} featureKey
+		 * @param {Object} [additionalParams]
+		 */
+		requestWizard: function(featureKey, additionalParams) {
+			var model = this;
+			var detectorCount = this.detectors.length;
+			var found = false;
+			for(var i = 0; i < detectorCount; i++) {
+				if(this.detectors[i].runsOnFeatureRequest(featureKey)) {
+					found = true;
+					(function (detector) {
+						model.detectorQueue.add(function() {
+							return detector.run(model, model.configID, additionalParams);
+						});
+					})(model.detectors[i]);
+				}
+			}
+			if(!found) {
+				console.warn('No detector found for feature ' + featureKey);
+			}
+		},
+
+		/**
+		 * resets the detector queue
+		 *
+		 * @private
+		 */
+		_resetDetectorQueue: function() {
+			if(!_.isUndefined(this.detectorQueue)) {
+				this.detectorQueue.reset();
+			}
+		},
+
+		/**
+		 * detectors can be registered herewith
+		 *
+		 * @param {OCA.LDAP.Wizard.WizardDetectorGeneric} [detector]
+		 */
+		registerDetector: function(detector) {
+			if(detector instanceof OCA.LDAP.Wizard.WizardDetectorGeneric) {
+				this.detectors.push(detector);
+			}
+		},
+
+		/**
+		 * emits an event
+		 *
+		 * @param {string} [name] - the event name
+		 * @param {*} [params]
+		 * @private
+		 */
+		_broadcast: function(name, params) {
+			if(_.isUndefined(this.subscribers[name])) {
+				return;
+			}
+			var subscribers = this.subscribers[name];
+			var subscriberCount = subscribers.length;
+			for(var i = 0; i < subscriberCount; i++) {
+				if(_.isUndefined(subscribers[i]['fn'])) {
+					console.warn('callback method is not defined. Event ' + name);
+					continue;
+				}
+				subscribers[i]['fn'](subscribers[i]['context'], params);
+			}
+		},
+
+		/**
+		 * ConfigModel#configLoaded Event
+		 *
+		 * @event ConfigModel#configLoaded
+		 * @type {object} - LDAP configuration as key-value-pairs
+		 */
+
+		/**
+		 * @typedef {object} ConfigLoadResponse
+		 * @property {string} [status]
+		 * @property {object} [configuration] - only present if status equals 'success'
+		 */
+
+		/**
+		 * processes the ajax response of a configuration load request
+		 *
+		 * @param {ConfigModel} [model]
+		 * @param {ConfigLoadResponse} [result]
+		 * @fires ConfigModel#configLoaded
+		 * @private
+		 */
+		_processLoadConfig: function(model, result) {
+			model.configuration = {};
+			if(result['status'] === 'success') {
+				$.each(result['configuration'], function(key, value) {
+					model.configuration[key] = value;
+				});
+			}
+			model.loadingConfig = false;
+			model._broadcast('configLoaded', model.configuration);
+		},
+
+		/**
+		 * @typedef {object} ConfigSetPayload
+		 * @property {boolean} [isSuccess]
+		 * @property {string} [key]
+		 * @property {string} [value]
+		 * @property {string} [errorMessage]
+		 */
+
+		/**
+		 * ConfigModel#setCompleted Event
+		 *
+		 * @event ConfigModel#setCompleted
+		 * @type {ConfigSetPayload}
+		 */
+
+		/**
+		 * @typedef {object} ConfigSetResponse
+		 * @property {string} [status]
+		 * @property {object} [message] - might be present only in error cases
+		 */
+
+		/**
+		 * processes the ajax response of a configuration key set request
+		 *
+		 * @param {ConfigModel} [model]
+		 * @param {ConfigSetResponse} [result]
+		 * @param {object} [params] - the original changeSet
+		 * @fires ConfigModel#configLoaded
+		 * @private
+		 */
+		_processSetResult: function(model, result, params) {
+			var isSuccess = (result['status'] === 'success');
+			if(isSuccess) {
+				model.configuration[params.cfgkey] = params.cfgval;
+			}
+			var payload = {
+				isSuccess: isSuccess,
+				key: params.cfgkey,
+				value: model.configuration[params.cfgkey],
+				errorMessage: _.isUndefined(result['message']) ? '' : result['message']
+			};
+			model._broadcast('setCompleted', payload);
+
+			// let detectors run
+			// NOTE: detector's changes will not result in new _processSetResult
+			// calls, … in case they interfere it is because of this ;)
+			if(_.isUndefined(model.detectorQueue)) {
+				console.warn("DetectorQueue was not set, detectors will not be fired");
+				return;
+			}
+			var detectorCount = model.detectors.length;
+			for(var i = 0; i < detectorCount; i++) {
+				if(model.detectors[i].triggersOn(params.cfgkey)) {
+					(function (detector) {
+						model.detectorQueue.add(function() {
+							return detector.run(model, model.configID);
+						});
+					})(model.detectors[i]);
+				}
+			}
+		},
+
+		/**
+		 * @typedef {object} ConfigTestPayload
+		 * @property {boolean} [isSuccess]
+		 */
+
+		/**
+		 * ConfigModel#configurationTested Event
+		 *
+		 * @event ConfigModel#configurationTested
+		 * @type {ConfigTestPayload}
+		 */
+
+		/**
+		 * @typedef {object} StatusResponse
+		 * @property {string} [status]
+		 */
+
+		/**
+		 * processes the ajax response of a configuration test request
+		 *
+		 * @param {ConfigModel} [model]
+		 * @param {StatusResponse} [result]
+		 * @fires ConfigModel#configurationTested
+		 * @private
+		 */
+		_processTestResult: function(model, result) {
+			var payload = {
+				isSuccess: (result['status'] === 'success')
+			};
+			model._broadcast('configurationTested', payload);
+		},
+
+		/**
+		 * @typedef {object} BasicConfigPayload
+		 * @property {boolean} [isSuccess]
+		 * @property {string} [configPrefix] - the new config ID
+		 * @property {string} [errorMessage]
+		 */
+
+		/**
+		 * ConfigModel#newConfiguration Event
+		 *
+		 * @event ConfigModel#newConfiguration
+		 * @type {BasicConfigPayload}
+		 */
+
+		/**
+		 * @typedef {object} NewConfigResponse
+		 * @property {string} [status]
+		 * @property {string} [configPrefix]
+		 * @property {object} [defaults] - default configuration values
+		 * @property {string} [message] - might only appear with status being
+		 * not 'success'
+		 */
+
+		/**
+		 * processes the ajax response of a new configuration request
+		 *
+		 * @param {ConfigModel} [model]
+		 * @param {NewConfigResponse} [result]
+		 * @param {boolean} [copyCurrent]
+		 * @fires ConfigModel#newConfiguration
+		 * @fires ConfigModel#configLoaded
+		 * @private
+		 */
+		_processNewConfigPrefix: function(model, result, copyCurrent) {
+			var isSuccess = (result['status'] === 'success');
+			var payload = {
+				isSuccess: isSuccess,
+				configPrefix: result['configPrefix'],
+				errorMessage: _.isUndefined(result['message']) ? '' : result['message']
+			};
+			model._broadcast('newConfiguration', payload);
+
+			if(isSuccess) {
+				this.configID = result['configPrefix'];
+				if(!copyCurrent) {
+					model.configuration = {};
+					$.each(result['defaults'], function(key, value) {
+						model.configuration[key] = value;
+					});
+					// view / tabs need to update with new blank config
+					model._broadcast('configLoaded', model.configuration);
+				}
+			}
+		},
+
+		/**
+		 * ConfigModel#deleteConfiguration Event
+		 *
+		 * @event ConfigModel#deleteConfiguration
+		 * @type {BasicConfigPayload}
+		 */
+
+		/**
+		 * processes the ajax response of a delete configuration request
+		 *
+		 * @param {ConfigModel} [model]
+		 * @param {StatusResponse} [result]
+		 * @param {string} [configID]
+		 * @fires ConfigModel#deleteConfiguration
+		 * @private
+		 */
+		_processDeleteConfig: function(model, result, configID) {
+			var isSuccess = (result['status'] === 'success');
+			var payload = {
+				isSuccess: isSuccess,
+				configPrefix: configID,
+				errorMessage: _.isUndefined(result['message']) ? '' : result['message']
+			};
+			model._broadcast('deleteConfiguration', payload);
+		}
+	};
+
+	OCA.LDAP.Wizard.ConfigModel = ConfigModel;
+})();
diff --git a/apps/user_ldap/js/wizard/controller.js b/apps/user_ldap/js/wizard/controller.js
new file mode 100644
index 0000000000000000000000000000000000000000..7c1f0d5d818d03f9fb01f90be928cbe6dedb1f4a
--- /dev/null
+++ b/apps/user_ldap/js/wizard/controller.js
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+OCA.LDAP = {};
+OCA.LDAP.Wizard = {};
+
+(function(){
+
+	/**
+	 * @classdesc minimalistic controller that basically makes the view render
+	 *
+	 * @constructor
+	 */
+	var WizardController = function() {};
+
+	WizardController.prototype = {
+		/**
+		 * initializes the instance. Always call it after creating the instance.
+		 */
+		init: function() {
+			this.view = false;
+			this.configModel = false;
+		},
+
+		/**
+		 * sets the model instance
+		 *
+		 * @param {OCA.LDAP.Wizard.ConfigModel} [model]
+		 */
+		setModel: function(model) {
+			this.configModel = model;
+		},
+
+		/**
+		 * sets the view instance
+		 *
+		 * @param {OCA.LDAP.Wizard.WizardView} [view]
+		 */
+		setView: function(view) {
+			this.view = view;
+		},
+
+		/**
+		 * makes the view render i.e. ready to be used
+		 */
+		run: function() {
+			this.view.render();
+		}
+	};
+
+	OCA.LDAP.Wizard.Controller = WizardController;
+})();
diff --git a/apps/user_ldap/js/wizard/view.js b/apps/user_ldap/js/wizard/view.js
new file mode 100644
index 0000000000000000000000000000000000000000..7743c277d61bab72668a43a4e35602f2c3ebe90b
--- /dev/null
+++ b/apps/user_ldap/js/wizard/view.js
@@ -0,0 +1,437 @@
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc main view class. It takes care of tab-unrelated control
+	 * elements (status bar, control buttons) and does or requests configuration
+	 * checks. It also manages the separate tab views.
+	 *
+	 * @constructor
+	 */
+	var WizardView = function() {};
+
+	WizardView.prototype = {
+		/** @constant {number} */
+		STATUS_ERROR: 0,
+		/** @constant {number} */
+		STATUS_INCOMPLETE: 1,
+		/** @constant {number} */
+		STATUS_SUCCESS: 2,
+
+		/**
+		 * initializes the instance. Always call it after creating the instance.
+		 */
+		init: function () {
+			this.tabs = {};
+			this.tabs.server = new OCA.LDAP.Wizard.WizardTabElementary();
+			this.$settings = $('#ldapSettings');
+			this.$saveSpinners = $('.ldap_saving');
+			this.saveProcesses = 0;
+			_.bindAll(this, 'onTabChange', 'onTestButtonClick');
+		},
+
+		/**
+		 * applies click events to the forward and backword buttons
+		 */
+		initControls: function() {
+			var view = this;
+			$('.ldap_action_continue').click(function(event) {
+				event.preventDefault();
+				view._controlContinue(view);
+			});
+
+			$('.ldap_action_back').click(function(event) {
+				event.preventDefault();
+				view._controlBack(view);
+			});
+
+			$('.ldap_action_test_connection').click(this.onTestButtonClick);
+		},
+
+		/**
+		 * registers a tab
+		 *
+		 * @param {OCA.LDAP.Wizard.WizardTabGeneric} tabView
+		 * @param {string} index
+		 * @returns {boolean}
+		 */
+		registerTab: function(tabView, index) {
+			if( _.isUndefined(this.tabs[index])
+				&& tabView instanceof OCA.LDAP.Wizard.WizardTabGeneric
+			) {
+				this.tabs[index] = tabView;
+				this.tabs[index].setModel(this.configModel);
+				return true;
+			}
+			return false;
+		},
+
+		/**
+		 * checks certain config values for completeness and depending on them
+		 * enables or disables non-elementary tabs.
+		 */
+		basicStatusCheck: function(view) {
+			var host  = view.configModel.configuration.ldap_host;
+			var port  = view.configModel.configuration.ldap_port;
+			var base  = view.configModel.configuration.ldap_base;
+			var agent = view.configModel.configuration.ldap_dn;
+			var pwd   = view.configModel.configuration.ldap_agent_password;
+
+			if((host && port  && base) && ((!agent && !pwd) || (agent && pwd))) {
+				view.enableTabs();
+			} else {
+				view.disableTabs();
+			}
+		},
+
+		/**
+		 * if the configuration is sufficient the model is being request to
+		 * perform a configuration test. Otherwise, the status indicator is
+		 * being updated with the status "incomplete"
+		 */
+		functionalityCheck: function() {
+			// this method should be called only if necessary, because it may
+			// cause an LDAP request!
+			var host        = this.configModel.configuration.ldap_host;
+			var port        = this.configModel.configuration.ldap_port;
+			var base        = this.configModel.configuration.ldap_base;
+			var userFilter  = this.configModel.configuration.ldap_userlist_filter;
+			var loginFilter = this.configModel.configuration.ldap_login_filter;
+
+			if(host && port && base && userFilter && loginFilter) {
+				this.configModel.requestConfigurationTest();
+			} else {
+				this._updateStatusIndicator(this.STATUS_INCOMPLETE);
+			}
+		},
+
+		/**
+		 * will request a functionality check if one of the related configuration
+		 * settings was changed.
+		 *
+		 * @param {ConfigSetPayload|Object} [changeSet]
+		 */
+		considerFunctionalityCheck: function(changeSet) {
+			var testTriggers = [
+				'ldap_host', 'ldap_port', 'ldap_dn', 'ldap_agent_password',
+				'ldap_base', 'ldap_userlist_filter', 'ldap_login_filter'
+			];
+			for(var key in changeSet) {
+				if($.inArray(key, testTriggers) >= 0) {
+					this.functionalityCheck();
+					return;
+				}
+			}
+		},
+
+		/**
+		 * keeps number of running save processes and shows a spinner if
+		 * necessary
+		 *
+		 * @param {WizardView} [view]
+		 * @listens ConfigModel#setRequested
+		 */
+		onSetRequested: function(view) {
+			view.saveProcesses += 1;
+			if(view.saveProcesses === 1) {
+				view.showSaveSpinner();
+			}
+		},
+
+		/**
+		 * keeps number of running save processes and hides the spinner if
+		 * necessary. Also triggers checks, to adjust tabs state and status bar.
+		 *
+		 * @param {WizardView} [view]
+		 * @param {ConfigSetPayload} [result]
+		 * @listens ConfigModel#setCompleted
+		 */
+		onSetRequestDone: function(view, result) {
+			if(view.saveProcesses > 0) {
+				view.saveProcesses -= 1;
+				if(view.saveProcesses === 0) {
+					view.hideSaveSpinner();
+				}
+			}
+
+			view.basicStatusCheck(view);
+			var param = {};
+			param[result.key] = 1;
+			view.considerFunctionalityCheck(param);
+		},
+
+		/**
+		 * updates the status indicator based on the configuration test result
+		 *
+		 * @param {WizardView} [view]
+		 * @param {ConfigTestPayload} [result]
+		 * @listens ConfigModel#configurationTested
+		 */
+		onTestCompleted: function(view, result) {
+			if(result.isSuccess) {
+				view._updateStatusIndicator(view.STATUS_SUCCESS);
+			} else {
+				view._updateStatusIndicator(view.STATUS_ERROR);
+			}
+		},
+
+		/**
+		 * triggers initial checks upon configuration loading to update status
+		 * controls
+		 *
+		 * @param {WizardView} [view]
+		 * @listens ConfigModel#configLoaded
+		 */
+		onConfigLoaded: function(view) {
+			view.basicStatusCheck(view);
+			view.functionalityCheck();
+		},
+
+		/**
+		 * reacts on attempts to switch to a different tab
+		 *
+		 * @param {object} event
+		 * @param {object} ui
+		 * @returns {boolean}
+		 */
+		onTabChange: function(event, ui) {
+			if(this.saveProcesses > 0) {
+				return false;
+			}
+
+			var newTabID = ui.newTab[0].id;
+			if(newTabID === '#ldapWizard1') {
+				newTabID = 'server';
+			}
+			var oldTabID = ui.oldTab[0].id;
+			if(oldTabID === '#ldapWizard1') {
+				oldTabID = 'server';
+			}
+			if(!_.isUndefined(this.tabs[newTabID])) {
+				this.tabs[newTabID].isActive = true;
+				this.tabs[newTabID].onActivate();
+			} else {
+				console.warn('Unreferenced activated tab ' + newTabID);
+			}
+			if(!_.isUndefined(this.tabs[oldTabID])) {
+				this.tabs[oldTabID].isActive = false;
+			} else {
+				console.warn('Unreferenced left tab ' + oldTabID);
+			}
+
+			if(!_.isUndefined(this.tabs[newTabID])) {
+				this._controlUpdate(this.tabs[newTabID].tabIndex);
+			}
+		},
+
+		/**
+		 * triggers checks upon configuration updates to keep status controls
+		 * up to date
+		 *
+		 * @param {WizardView} [view]
+		 * @param {object} [changeSet]
+		 * @listens ConfigModel#configUpdated
+		 */
+		onConfigUpdated: function(view, changeSet) {
+			view.basicStatusCheck(view);
+			view.considerFunctionalityCheck(changeSet);
+		},
+
+		/**
+		 * requests a configuration test
+		 */
+		onTestButtonClick: function() {
+			this.configModel.requestWizard('ldap_action_test_connection', this.configModel.configuration);
+		},
+
+		/**
+		 * sets the model instance and registers event listeners
+		 *
+		 * @param {OCA.LDAP.Wizard.ConfigModel} [configModel]
+		 */
+		setModel: function(configModel) {
+			/** @type {OCA.LDAP.Wizard.ConfigModel} */
+			this.configModel = configModel;
+			for(var i in this.tabs) {
+				this.tabs[i].setModel(configModel);
+			}
+
+			// make sure this is definitely run after tabs did their work, order is important here
+			// for now this works, because tabs are supposed to register their listeners in their
+			// setModel() method.
+			// alternative: make Elementary Tab a Publisher as well.
+			this.configModel.on('configLoaded', this.onConfigLoaded, this);
+			this.configModel.on('configUpdated', this.onConfigUpdated, this);
+			this.configModel.on('setRequested', this.onSetRequested, this);
+			this.configModel.on('setCompleted', this.onSetRequestDone, this);
+			this.configModel.on('configurationTested', this.onTestCompleted, this);
+		},
+
+		/**
+		 * enables tab and navigation buttons
+		 */
+		enableTabs: function() {
+			//do not use this function directly, use basicStatusCheck instead.
+			if(this.saveProcesses === 0) {
+				$('.ldap_action_continue').removeAttr('disabled');
+				$('.ldap_action_back').removeAttr('disabled');
+				this.$settings.tabs('option', 'disabled', []);
+			}
+		},
+
+		/**
+		 * disables tab and navigation buttons
+		 */
+		disableTabs: function() {
+			$('.ldap_action_continue').attr('disabled', 'disabled');
+			$('.ldap_action_back').attr('disabled', 'disabled');
+			this.$settings.tabs('option', 'disabled', [1, 2, 3, 4, 5]);
+		},
+
+		/**
+		 * shows a save spinner
+		 */
+		showSaveSpinner: function() {
+			this.$saveSpinners.removeClass('hidden');
+			$('#ldap *').addClass('save-cursor');
+		},
+
+		/**
+		 * hides the save spinner
+		 */
+		hideSaveSpinner: function() {
+			this.$saveSpinners.addClass('hidden');
+			$('#ldap *').removeClass('save-cursor');
+		},
+
+		/**
+		 * performs a config load request to the model
+		 *
+		 * @param {string} [configID]
+		 * @private
+		 */
+		_requestConfig: function(configID) {
+			this.configModel.load(configID);
+		},
+
+		/**
+		 * bootstraps the visual appearance and event listeners, as well as the
+		 * first config
+		 */
+		render: function () {
+			$('#ldapAdvancedAccordion').accordion({ heightStyle: 'content', animate: 'easeInOutCirc'});
+			this.$settings.tabs({});
+			$('.ldap_submit').button();
+			$('.ldap_action_test_connection').button();
+			$('#ldapSettings').tabs({ beforeActivate: this.onTabChange });
+
+			this.initControls();
+			this.disableTabs();
+
+			this._requestConfig(this.tabs.server.getConfigID());
+		},
+
+		/**
+		 * updates the status indicator / bar
+		 *
+		 * @param {number} [state]
+		 * @private
+		 */
+		_updateStatusIndicator: function(state) {
+			var $indicator = $('.ldap_config_state_indicator');
+			var $indicatorLight = $('.ldap_config_state_indicator_sign');
+
+			switch(state) {
+				case this.STATUS_ERROR:
+					$indicator.text(t('user_ldap',
+						'Configuration incorrect'
+					));
+					$indicator.removeClass('ldap_grey');
+					$indicatorLight.addClass('error');
+					$indicatorLight.removeClass('success');
+					break;
+				case this.STATUS_INCOMPLETE:
+					$indicator.text(t('user_ldap',
+						'Configuration incomplete'
+					));
+					$indicator.removeClass('ldap_grey');
+					$indicatorLight.removeClass('error');
+					$indicatorLight.removeClass('success');
+					break;
+				case this.STATUS_SUCCESS:
+					$indicator.text(t('user_ldap', 'Configuration OK'));
+					$indicator.addClass('ldap_grey');
+					$indicatorLight.removeClass('error');
+					$indicatorLight.addClass('success');
+					if(!this.tabs.server.isActive) {
+						this.configModel.set('ldap_configuration_active', 1);
+					}
+					break;
+			}
+		},
+
+		/**
+		 * handles a click on the Back button
+		 *
+		 * @param {WizardView} [view]
+		 * @private
+		 */
+		_controlBack: function(view) {
+			var curTabIndex = view.$settings.tabs('option', 'active');
+			if(curTabIndex == 0) {
+				return;
+			}
+			view.$settings.tabs('option', 'active', curTabIndex - 1);
+			view._controlUpdate(curTabIndex - 1);
+		},
+
+		/**
+		 * handles a click on the Continue button
+		 *
+		 * @param {WizardView} [view]
+		 * @private
+		 */
+		_controlContinue: function(view) {
+			var curTabIndex = view.$settings.tabs('option', 'active');
+			if(curTabIndex == 3) {
+				return;
+			}
+			view.$settings.tabs('option', 'active', 1 + curTabIndex);
+			view._controlUpdate(curTabIndex + 1);
+		},
+
+		/**
+		 * updates the controls (navigation buttons)
+		 *
+		 * @param {number} [nextTabIndex] - index of the tab being switched to
+		 * @private
+		 */
+		_controlUpdate: function(nextTabIndex) {
+			if(nextTabIndex == 0) {
+				$('.ldap_action_back').addClass('invisible');
+				$('.ldap_action_continue').removeClass('invisible');
+			} else
+			if(nextTabIndex == 1) {
+				$('.ldap_action_back').removeClass('invisible');
+				$('.ldap_action_continue').removeClass('invisible');
+			} else
+			if(nextTabIndex == 2) {
+				$('.ldap_action_continue').removeClass('invisible');
+				$('.ldap_action_back').removeClass('invisible');
+			} else
+			if(nextTabIndex == 3) {
+				$('.ldap_action_back').removeClass('invisible');
+				$('.ldap_action_continue').addClass('invisible');
+			}
+		}
+	};
+
+	OCA.LDAP.Wizard.WizardView = WizardView;
+})();
diff --git a/apps/user_ldap/js/wizard/wizard.js b/apps/user_ldap/js/wizard/wizard.js
new file mode 100644
index 0000000000000000000000000000000000000000..e8450d1c78fc17992487023f507ed43f7db04524
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizard.js
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+
+
+/**
+ * initializes the wizard and related components and kicks it off.
+ */
+
+(function() {
+	var Wizard = function() {
+		var detectorQueue = new OCA.LDAP.Wizard.WizardDetectorQueue();
+		detectorQueue.init();
+
+		var detectors = [];
+		detectors.push(new OCA.LDAP.Wizard.WizardDetectorPort());
+		detectors.push(new OCA.LDAP.Wizard.WizardDetectorBaseDN());
+		detectors.push(new OCA.LDAP.Wizard.WizardDetectorEmailAttribute());
+		detectors.push(new OCA.LDAP.Wizard.WizardDetectorUserDisplayNameAttribute());
+		detectors.push(new OCA.LDAP.Wizard.WizardDetectorUserGroupAssociation());
+		detectors.push(new OCA.LDAP.Wizard.WizardDetectorUserObjectClasses());
+		detectors.push(new OCA.LDAP.Wizard.WizardDetectorGroupObjectClasses());
+		detectors.push(new OCA.LDAP.Wizard.WizardDetectorGroupsForUsers());
+		detectors.push(new OCA.LDAP.Wizard.WizardDetectorGroupsForGroups());
+		detectors.push(new OCA.LDAP.Wizard.WizardDetectorFilterUser());
+		detectors.push(new OCA.LDAP.Wizard.WizardDetectorFilterLogin());
+		detectors.push(new OCA.LDAP.Wizard.WizardDetectorFilterGroup());
+		detectors.push(new OCA.LDAP.Wizard.WizardDetectorUserCount());
+		detectors.push(new OCA.LDAP.Wizard.WizardDetectorGroupCount());
+		detectors.push(new OCA.LDAP.Wizard.WizardDetectorAvailableAttributes());
+		detectors.push(new OCA.LDAP.Wizard.WizardDetectorTestLoginName());
+		detectors.push(new OCA.LDAP.Wizard.WizardDetectorTestBaseDN());
+		detectors.push(new OCA.LDAP.Wizard.WizardDetectorTestConfiguration());
+		detectors.push(new OCA.LDAP.Wizard.WizardDetectorClearUserMappings());
+		detectors.push(new OCA.LDAP.Wizard.WizardDetectorClearGroupMappings());
+
+		var model = new OCA.LDAP.Wizard.ConfigModel();
+		model.init(detectorQueue);
+		// NOTE: order of detectors may play a role
+		// for example, BaseDN detector needs the port. The port is typically found
+		// by the Port Detector. If BaseDN detector was run first, it will not have
+		// all necessary information. Only after Port Detector was executed…
+		for (var i = 0; i <= detectors.length; i++) {
+			model.registerDetector(detectors[i]);
+		}
+
+		var filterOnTypeFactory = new OCA.LDAP.Wizard.FilterOnTypeFactory();
+
+		var tabs = [];
+		tabs.push(new OCA.LDAP.Wizard.WizardTabUserFilter(filterOnTypeFactory, 1));
+		tabs.push(new OCA.LDAP.Wizard.WizardTabLoginFilter(2));
+		tabs.push(new OCA.LDAP.Wizard.WizardTabGroupFilter(filterOnTypeFactory, 3));
+		tabs.push(new OCA.LDAP.Wizard.WizardTabAdvanced());
+		tabs.push(new OCA.LDAP.Wizard.WizardTabExpert());
+
+		var view = new OCA.LDAP.Wizard.WizardView(model);
+		view.init();
+		view.setModel(model);
+		for (var j = 0; j <= tabs.length; j++) {
+			view.registerTab(tabs[j], '#ldapWizard' + (j + 2));
+		}
+
+		var controller = new OCA.LDAP.Wizard.Controller();
+		controller.init();
+		controller.setView(view);
+		controller.setModel(model);
+		controller.run();
+	}
+
+	OCA.LDAP.Wizard.Wizard = Wizard;
+})();
+
+$(document).ready(function() {
+	new OCA.LDAP.Wizard.Wizard();
+});
diff --git a/apps/user_ldap/js/wizard/wizardDetectorAvailableAttributes.js b/apps/user_ldap/js/wizard/wizardDetectorAvailableAttributes.js
new file mode 100644
index 0000000000000000000000000000000000000000..f0272351749a5682f5afe23ae3febc508f1923b9
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorAvailableAttributes.js
@@ -0,0 +1,59 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc an Attributes Detector. It executes the auto-detection of
+	 * available attributes by the ownCloud server, if requirements are met.
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorAvailableAttributes = OCA.LDAP.Wizard.WizardDetectorGeneric.subClass({
+		/** @inheritdoc */
+		init: function() {
+			// given, it is not a configuration key
+			this.setTargetKey('ldap_loginfilter_attributes');
+			this.runsOnRequest = true;
+		},
+
+		/**
+		 * runs the detector, if port is not set.
+		 *
+		 * @param {OCA.LDAP.Wizard.ConfigModel} model
+		 * @param {string} configID - the configuration prefix
+		 * @returns {boolean|jqXHR}
+		 * @abstract
+		 */
+		run: function(model, configID) {
+			model.notifyAboutDetectionStart(this.getTargetKey());
+			var params = OC.buildQueryString({
+				action: 'determineAttributes',
+				ldap_serverconfig_chooser: configID
+			});
+			return model.callWizard(params, this.processResult, this);
+		},
+
+		/**
+		 * @inheritdoc
+		 */
+		processResult: function(model, detector, result) {
+			if(result.status === 'success') {
+				var payload = {
+					feature: 'AvailableAttributes',
+					data: result.options[detector.getTargetKey()]
+				};
+				model.inform(payload);
+			}
+			this._super(model, detector, result);
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorAvailableAttributes = WizardDetectorAvailableAttributes;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorBaseDN.js b/apps/user_ldap/js/wizard/wizardDetectorBaseDN.js
new file mode 100644
index 0000000000000000000000000000000000000000..70b9923e58dbea2f58e1da6ce56c68a204b269ae
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorBaseDN.js
@@ -0,0 +1,52 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc a Base DN Detector. It executes the auto-detection of the base
+	 * DN by the ownCloud server, if requirements are met.
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorBaseDN = OCA.LDAP.Wizard.WizardDetectorGeneric.subClass({
+		/** @inheritdoc */
+		init: function() {
+			this.setTargetKey('ldap_base');
+			this.runsOnRequest = true;
+		},
+
+		/**
+		 * runs the detector, if specified configuration settings are set and
+		 * base DN is not set.
+		 *
+		 * @param {OCA.LDAP.Wizard.ConfigModel} model
+		 * @param {string} configID - the configuration prefix
+		 * @returns {boolean|jqXHR}
+		 * @abstract
+		 */
+		run: function(model, configID) {
+			if(    !model.configuration['ldap_host']
+				|| !model.configuration['ldap_port']
+
+				)
+			{
+				return false;
+			}
+			model.notifyAboutDetectionStart(this.getTargetKey());
+			var params = OC.buildQueryString({
+				action: 'guessBaseDN',
+				ldap_serverconfig_chooser: configID
+			});
+			return model.callWizard(params, this.processResult, this);
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorBaseDN = WizardDetectorBaseDN;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorClearGroupMappings.js b/apps/user_ldap/js/wizard/wizardDetectorClearGroupMappings.js
new file mode 100644
index 0000000000000000000000000000000000000000..c6ef0a9cab18aa0b4fcf7114979986bc5f3a3870
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorClearGroupMappings.js
@@ -0,0 +1,30 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc requests clearing of user mappings
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorClearGroupMappings = OCA.LDAP.Wizard.WizardDetectorTestAbstract.subClass({
+		/** @inheritdoc */
+		init: function() {
+			// given, it is not a configuration key
+			this.setTargetKey('ldap_action_clear_group_mappings');
+			this.testName = 'ClearMappings';
+			this.isLegacy = true;
+			this.legacyDestination = 'clearMappings.php';
+			this.runsOnRequest = true;
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorClearGroupMappings = WizardDetectorClearGroupMappings;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorClearUserMappings.js b/apps/user_ldap/js/wizard/wizardDetectorClearUserMappings.js
new file mode 100644
index 0000000000000000000000000000000000000000..0e4811b39eaa6ea80d70e7efda187753ebbf91d1
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorClearUserMappings.js
@@ -0,0 +1,30 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc requests clearing of user mappings
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorClearUserMappings = OCA.LDAP.Wizard.WizardDetectorTestAbstract.subClass({
+		/** @inheritdoc */
+		init: function() {
+			// given, it is not a configuration key
+			this.setTargetKey('ldap_action_clear_user_mappings');
+			this.testName = 'ClearMappings';
+			this.isLegacy = true;
+			this.legacyDestination = 'clearMappings.php';
+			this.runsOnRequest = true;
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorClearUserMappings = WizardDetectorClearUserMappings;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorEmailAttribute.js b/apps/user_ldap/js/wizard/wizardDetectorEmailAttribute.js
new file mode 100644
index 0000000000000000000000000000000000000000..5f177734681e445578117a6964f8e3f1b8a5343b
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorEmailAttribute.js
@@ -0,0 +1,38 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc let's the wizard backend count the available users
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorEmailAttribute = OCA.LDAP.Wizard.WizardDetectorFilterSimpleRequestAbstract.subClass({
+		init: function() {
+			this.setTargetKey('ldap_user_count');
+			this.wizardMethod = 'detectEmailAttribute';
+			this.runsOnRequest = true;
+		},
+
+		/**
+		 * @inheritdoc
+		 */
+		run: function(model, configID) {
+			if(model.configuration.ldap_email_attr) {
+				// a value is already set. Don't overwrite and don't ask LDAP
+				// without reason.
+				return false;
+			}
+			this._super(model, configID);
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorEmailAttribute = WizardDetectorEmailAttribute;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorFeatureAbstract.js b/apps/user_ldap/js/wizard/wizardDetectorFeatureAbstract.js
new file mode 100644
index 0000000000000000000000000000000000000000..e025d8d624226081d6b2444b23a8e390147f45ca
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorFeatureAbstract.js
@@ -0,0 +1,52 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc abstract detector for detecting groups and object classes
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorFeatureAbstract = OCA.LDAP.Wizard.WizardDetectorGeneric.subClass({
+		/**
+		 * runs the detector, if port is not set.
+		 *
+		 * @param {OCA.LDAP.Wizard.ConfigModel} model
+		 * @param {string} configID - the configuration prefix
+		 * @returns {boolean|jqXHR}
+		 * @abstract
+		 */
+		run: function(model, configID) {
+			model.notifyAboutDetectionStart(this.getTargetKey());
+			var params = OC.buildQueryString({
+				action: this.wizardMethod,
+				ldap_serverconfig_chooser: configID
+			});
+			return model.callWizard(params, this.processResult, this);
+		},
+
+		/**
+		 * @inheritdoc
+		 */
+		processResult: function(model, detector, result) {
+			if(result.status === 'success') {
+				var payload = {
+					feature: detector.featureName,
+					data: result.options[detector.getTargetKey()]
+				};
+				model.inform(payload);
+			}
+
+			this._super(model, detector, result);
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorFeatureAbstract = WizardDetectorFeatureAbstract;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorFilterGroup.js b/apps/user_ldap/js/wizard/wizardDetectorFilterGroup.js
new file mode 100644
index 0000000000000000000000000000000000000000..cca889839e4eaa7db2a13a279d128befa8194ae0
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorFilterGroup.js
@@ -0,0 +1,31 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc a Port Detector. It executes the auto-detection of the port
+	 * by the ownCloud server, if requirements are met.
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorFilterGroup = OCA.LDAP.Wizard.WizardDetectorFilterSimpleRequestAbstract.subClass({
+		init: function() {
+			this.setTrigger([
+				'ldap_groupfilter_groups',
+				'ldap_groupfilter_objectclass'
+			]);
+			this.setTargetKey('ldap_group_filter');
+
+			this.wizardMethod = 'getGroupFilter';
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorFilterGroup = WizardDetectorFilterGroup;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorFilterLogin.js b/apps/user_ldap/js/wizard/wizardDetectorFilterLogin.js
new file mode 100644
index 0000000000000000000000000000000000000000..e796b81e0eb2b8c5f09500c121e5ff89b176dc8d
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorFilterLogin.js
@@ -0,0 +1,33 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc a Port Detector. It executes the auto-detection of the port
+	 * by the ownCloud server, if requirements are met.
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorFilterLogin = OCA.LDAP.Wizard.WizardDetectorFilterSimpleRequestAbstract.subClass({
+		init: function() {
+			this.setTrigger([
+				'ldap_loginfilter_username',
+				'ldap_loginfilter_email',
+				'ldap_loginfilter_attributes'
+			]);
+			this.setTargetKey('ldap_login_filter');
+			this.runsOnRequest = true;
+
+			this.wizardMethod = 'getUserLoginFilter';
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorFilterLogin = WizardDetectorFilterLogin;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorFilterUser.js b/apps/user_ldap/js/wizard/wizardDetectorFilterUser.js
new file mode 100644
index 0000000000000000000000000000000000000000..d34e244a1f58adf63b580c78931e73a27a94b803
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorFilterUser.js
@@ -0,0 +1,32 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc a Port Detector. It executes the auto-detection of the port
+	 * by the ownCloud server, if requirements are met.
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorFilterUser = OCA.LDAP.Wizard.WizardDetectorFilterSimpleRequestAbstract.subClass({
+		init: function() {
+			this.setTrigger([
+				'ldap_userfilter_groups',
+				'ldap_userfilter_objectclass'
+			]);
+			this.setTargetKey('ldap_userlist_filter');
+			this.runsOnRequest = true;
+
+			this.wizardMethod = 'getUserListFilter';
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorFilterUser = WizardDetectorFilterUser;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorGeneric.js b/apps/user_ldap/js/wizard/wizardDetectorGeneric.js
new file mode 100644
index 0000000000000000000000000000000000000000..fd80018943e60f6889df33474dcb9ebb0fe8b96c
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorGeneric.js
@@ -0,0 +1,117 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+	/**
+	 * @classdesc a generic (abstract) Detector template. A Detector's task is
+	 * to kick off server side detection of certain LDAP features. It is invoked
+	 * when changes to specified configuration keys happen.
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorGeneric = OCA.LDAP.Wizard.WizardObject.subClass({
+		/**
+		 * initializes the instance. Always call it after creating the instance.
+		 */
+		init: function() {
+			this.setTrigger([]);
+			this.targetKey = '';
+			this.runsOnRequest = false;
+		},
+
+		/**
+		 * sets the configuration keys the detector is listening on
+		 *
+		 * @param {string[]} triggers
+		 */
+		setTrigger: function(triggers) {
+			this.triggers = triggers;
+		},
+
+		/**
+		 * tests whether the detector is triggered by the provided key
+		 *
+		 * @param {string} key
+		 * @returns {boolean}
+		 */
+		triggersOn: function(key) {
+			return ($.inArray(key, this.triggers) >= 0);
+		},
+
+		/**
+		 * whether the detector runs on explicit request
+		 *
+		 * @param {string} key
+		 * @returns {boolean}
+		 */
+		runsOnFeatureRequest: function(key) {
+			return !!(this.runsOnRequest && this.targetKey === key);
+		},
+
+		/**
+		 * sets the configuration key the detector is attempting to auto-detect
+		 *
+		 * @param {string} key
+		 */
+		setTargetKey: function(key) {
+			this.targetKey = key;
+		},
+
+		/**
+		 * returns the configuration key the detector is attempting to
+		 * auto-detect
+		 */
+		getTargetKey: function() {
+			return this.targetKey;
+		},
+
+		/**
+		 * runs the detector. This method is supposed to be implemented by the
+		 * concrete detector.
+		 *
+		 * Must return false if the detector decides not to run.
+		 * Must return a jqXHR object otherwise, which is provided by the
+		 * model's callWizard()
+		 *
+		 * @param {OCA.LDAP.Wizard.ConfigModel} model
+		 * @param {string} configID - the configuration prefix
+		 * @returns {boolean|jqXHR}
+		 * @abstract
+		 */
+		run: function(model, configID) {
+			// to be implemented by subClass
+			return false;
+		},
+
+		/**
+		 * processes the result of the ownCloud server
+		 *
+		 * @param {OCA.LDAP.Wizard.ConfigModel} model
+		 * @param {WizardDetectorGeneric} detector
+		 * @param {object} result
+		 */
+		processResult: function(model, detector, result) {
+			model['notifyAboutDetectionCompletion'](detector.getTargetKey());
+			if(result.status === 'success') {
+				for (var id in result.changes) {
+					// update and not set method, as values are already stored
+					model['update'](id, result.changes[id]);
+				}
+			} else {
+				var payload = { relatedKey: detector.targetKey };
+				if(!_.isUndefined(result.message)) {
+					payload.message = result.message;
+				}
+				model.gotServerError(payload);
+			}
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorGeneric = WizardDetectorGeneric;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorGroupCount.js b/apps/user_ldap/js/wizard/wizardDetectorGroupCount.js
new file mode 100644
index 0000000000000000000000000000000000000000..12d7df7514bf74c62d53658c290557082114769e
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorGroupCount.js
@@ -0,0 +1,27 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc a Port Detector. It executes the auto-detection of the port
+	 * by the ownCloud server, if requirements are met.
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorGroupCount = OCA.LDAP.Wizard.WizardDetectorFilterSimpleRequestAbstract.subClass({
+		init: function() {
+			this.setTargetKey('ldap_group_count');
+			this.wizardMethod = 'countGroups';
+			this.runsOnRequest = true;
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorGroupCount = WizardDetectorGroupCount;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorGroupObjectClasses.js b/apps/user_ldap/js/wizard/wizardDetectorGroupObjectClasses.js
new file mode 100644
index 0000000000000000000000000000000000000000..6d6048b7986bcd7c4762635826ceb0b49339313a
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorGroupObjectClasses.js
@@ -0,0 +1,29 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc discovers object classes for the groups tab
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorGroupObjectClasses = OCA.LDAP.Wizard.WizardDetectorFeatureAbstract.subClass({
+		/** @inheritdoc */
+		init: function() {
+			// given, it is not a configuration key
+			this.setTargetKey('ldap_groupfilter_objectclass');
+			this.wizardMethod = 'determineGroupObjectClasses';
+			this.featureName = 'GroupObjectClasses';
+			this.runsOnRequest = true;
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorGroupObjectClasses = WizardDetectorGroupObjectClasses;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorGroupsForGroups.js b/apps/user_ldap/js/wizard/wizardDetectorGroupsForGroups.js
new file mode 100644
index 0000000000000000000000000000000000000000..fbb3f02e10a3861211af2344d8ddd0d015e3f85a
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorGroupsForGroups.js
@@ -0,0 +1,29 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc detects groups for the groups tab
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorGroupsForGroups = OCA.LDAP.Wizard.WizardDetectorFeatureAbstract.subClass({
+		/** @inheritdoc */
+		init: function() {
+			// given, it is not a configuration key
+			this.setTargetKey('ldap_groupfilter_groups');
+			this.wizardMethod = 'determineGroupsForGroups';
+			this.featureName = 'GroupsForGroups';
+			this.runsOnRequest = true;
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorGroupsForGroups = WizardDetectorGroupsForGroups;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorGroupsForUsers.js b/apps/user_ldap/js/wizard/wizardDetectorGroupsForUsers.js
new file mode 100644
index 0000000000000000000000000000000000000000..fe67854c7946e648f63651b7c83e1403353849d3
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorGroupsForUsers.js
@@ -0,0 +1,29 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc detects groups for the users tab
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorGroupsForUsers = OCA.LDAP.Wizard.WizardDetectorFeatureAbstract.subClass({
+		/** @inheritdoc */
+		init: function() {
+			// given, it is not a configuration key
+			this.setTargetKey('ldap_userfilter_groups');
+			this.wizardMethod = 'determineGroupsForUsers';
+			this.featureName = 'GroupsForUsers';
+			this.runsOnRequest = true;
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorGroupsForUsers = WizardDetectorGroupsForUsers;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorPort.js b/apps/user_ldap/js/wizard/wizardDetectorPort.js
new file mode 100644
index 0000000000000000000000000000000000000000..ba0751896674798048a1194d3493cfa598d6a126
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorPort.js
@@ -0,0 +1,44 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc a Port Detector. It executes the auto-detection of the port
+	 * by the ownCloud server, if requirements are met.
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorPort = OCA.LDAP.Wizard.WizardDetectorGeneric.subClass({
+		/** @inheritdoc */
+		init: function() {
+			this.setTargetKey('ldap_port');
+			this.runsOnRequest = true;
+		},
+
+		/**
+		 * runs the detector, if port is not set.
+		 *
+		 * @param {OCA.LDAP.Wizard.ConfigModel} model
+		 * @param {string} configID - the configuration prefix
+		 * @returns {boolean|jqXHR}
+		 * @abstract
+		 */
+		run: function(model, configID) {
+			model.notifyAboutDetectionStart('ldap_port');
+			var params = OC.buildQueryString({
+				action: 'guessPortAndTLS',
+				ldap_serverconfig_chooser: configID
+			});
+			return model.callWizard(params, this.processResult, this);
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorPort = WizardDetectorPort;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorQueue.js b/apps/user_ldap/js/wizard/wizardDetectorQueue.js
new file mode 100644
index 0000000000000000000000000000000000000000..b6fa644558e612149d6fc9b97b5406316e01b698
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorQueue.js
@@ -0,0 +1,89 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+	/**
+	 * @classdesc only run detector is allowed to run at a time. Basically
+	 * because we cannot have parallel LDAP connections per session. This
+	 * queue is takes care of running all the detectors one after the other.
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorQueue = OCA.LDAP.Wizard.WizardObject.subClass({
+		/**
+		 * initializes the instance. Always call it after creating the instance.
+		 */
+		init: function() {
+			this.queue = [];
+			this.isRunning = false;
+		},
+
+		/**
+		 * empties the queue and cancels a possibly running request
+		 */
+		reset: function() {
+			this.queue = [];
+			if(!_.isUndefined(this.runningRequest)) {
+				this.runningRequest.abort();
+				delete this.runningRequest;
+			}
+			this.isRunning = false;
+		},
+
+		/**
+		 * a parameter-free callback that eventually executes the run method of
+		 * the detector.
+		 *
+		 * @callback detectorCallBack
+		 * @see OCA.LDAP.Wizard.ConfigModel._processSetResult
+		 */
+
+		/**
+		 * adds a detector to the queue and attempts to trigger to run the
+		 * next job, because it might be the first.
+		 *
+		 * @param {detectorCallBack} callback
+		 */
+		add: function(callback) {
+			this.queue.push(callback);
+			this.next();
+		},
+
+		/**
+		 * Executes the next detector if none is running. This method is also
+		 * automatically invoked after a detector finished.
+		 */
+		next: function() {
+			if(this.isRunning === true || this.queue.length === 0) {
+				return;
+			}
+
+			this.isRunning = true;
+			var callback = this.queue.shift();
+			var request = callback();
+
+			// we receive either false or a jqXHR object
+			// false in case the detector decided against executing
+			if(request === false) {
+				this.isRunning = false;
+				this.next();
+				return;
+			}
+			this.runningRequest = request;
+
+			var detectorQueue = this;
+			$.when(request).then(function() {
+				detectorQueue.isRunning = false;
+				detectorQueue.next();
+			});
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorQueue = WizardDetectorQueue;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorSimpleRequestAbstract.js b/apps/user_ldap/js/wizard/wizardDetectorSimpleRequestAbstract.js
new file mode 100644
index 0000000000000000000000000000000000000000..37e41f42a6417eb41535cb4e579a347e257186e9
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorSimpleRequestAbstract.js
@@ -0,0 +1,44 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc a Port Detector. It executes the auto-detection of the port
+	 * by the ownCloud server, if requirements are met.
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorFilterSimpleRequestAbstract = OCA.LDAP.Wizard.WizardDetectorGeneric.subClass({
+		runsOnRequest: true,
+
+		/**
+		 * runs the detector, if port is not set.
+		 *
+		 * @param {OCA.LDAP.Wizard.ConfigModel} model
+		 * @param {string} configID - the configuration prefix
+		 * @returns {boolean|jqXHR}
+		 * @abstract
+		 */
+		run: function(model, configID) {
+			if(_.isUndefined(this.wizardMethod)) {
+				console.warn('wizardMethod not set! ' + this.constructor);
+				return false;
+			}
+			model.notifyAboutDetectionStart(this.targetKey);
+			var params = OC.buildQueryString({
+				action: this.wizardMethod,
+				ldap_serverconfig_chooser: configID
+			});
+			return model.callWizard(params, this.processResult, this);
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorFilterSimpleRequestAbstract = WizardDetectorFilterSimpleRequestAbstract;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorTestAbstract.js b/apps/user_ldap/js/wizard/wizardDetectorTestAbstract.js
new file mode 100644
index 0000000000000000000000000000000000000000..df0b0a2200a2edb4aee51c94e62e7d27156173c9
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorTestAbstract.js
@@ -0,0 +1,63 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc a Port Detector. It executes the auto-detection of the port
+	 * by the ownCloud server, if requirements are met.
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorTestAbstract = OCA.LDAP.Wizard.WizardDetectorGeneric.subClass({
+		isLegacy: false,
+
+		/**
+		 * runs the test
+		 *
+		 * @param {OCA.LDAP.Wizard.ConfigModel} model
+		 * @param {string} configID - the configuration prefix
+		 * @param {Object} params - additional parameters needed to send to the
+		 * wizard
+		 * @returns {boolean|jqXHR}
+		 * @abstract
+		 */
+		run: function(model, configID, params) {
+			if(_.isUndefined(this.wizardMethod) && !this.isLegacy) {
+				console.warn('wizardMethod not set! ' + this.constructor);
+				return false;
+			}
+			model.notifyAboutDetectionStart(this.getTargetKey());
+			params = params || {};
+			params = OC.buildQueryString($.extend({
+				action: this.wizardMethod,
+				ldap_serverconfig_chooser: configID
+			}, params));
+			if(!this.isLegacy) {
+				return model.callWizard(params, this.processResult, this);
+			} else {
+				return model.callAjax(this.legacyDestination, params, this.processResult, this);
+			}
+		},
+
+		/**
+		 * @inheritdoc
+		 */
+		processResult: function(model, detector, result) {
+			model['notifyAboutDetectionCompletion'](detector.getTargetKey());
+			var payload = {
+				feature: detector.testName,
+				data: result
+			};
+			model.inform(payload);
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorTestAbstract = WizardDetectorTestAbstract;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorTestBaseDN.js b/apps/user_ldap/js/wizard/wizardDetectorTestBaseDN.js
new file mode 100644
index 0000000000000000000000000000000000000000..52848819bd88bb659df0da05d3a2bbff078c534f
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorTestBaseDN.js
@@ -0,0 +1,29 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc Tests, how many objects reside in the given base DN(s)
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorTestBaseDN = OCA.LDAP.Wizard.WizardDetectorTestAbstract.subClass({
+		/** @inheritdoc */
+		init: function() {
+			// given, it is not a configuration key
+			this.setTargetKey('ldap_test_base');
+			this.testName = 'TestBaseDN';
+			this.wizardMethod = 'countInBaseDN';
+			this.runsOnRequest = true;
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorTestBaseDN = WizardDetectorTestBaseDN;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorTestConfiguration.js b/apps/user_ldap/js/wizard/wizardDetectorTestConfiguration.js
new file mode 100644
index 0000000000000000000000000000000000000000..1308c182909b246f351afff34faa39fe8b1910b9
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorTestConfiguration.js
@@ -0,0 +1,31 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc a Port Detector. It executes the auto-detection of the port
+	 * by the ownCloud server, if requirements are met.
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorTestConfiguration = OCA.LDAP.Wizard.WizardDetectorTestAbstract.subClass({
+		/** @inheritdoc */
+		init: function() {
+			// given, it is not a configuration key
+			this.setTargetKey('ldap_action_test_connection');
+			this.testName = 'TestConfiguration';
+			this.isLegacy = true;
+			this.legacyDestination = 'testConfiguration.php';
+			this.runsOnRequest = true;
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorTestConfiguration = WizardDetectorTestConfiguration;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorTestLoginName.js b/apps/user_ldap/js/wizard/wizardDetectorTestLoginName.js
new file mode 100644
index 0000000000000000000000000000000000000000..260df5a0fe0c05341921320c907df62cc3016b49
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorTestLoginName.js
@@ -0,0 +1,30 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc checks whether the provided log in name can be resolved into
+	 * a DN using the current login filter
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorTestLoginName = OCA.LDAP.Wizard.WizardDetectorTestAbstract.subClass({
+		/** @inheritdoc */
+		init: function() {
+			// given, it is not a configuration key
+			this.setTargetKey('ldap_test_loginname');
+			this.testName = 'TestLoginName';
+			this.wizardMethod = 'testLoginName';
+			this.runsOnRequest = true;
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorTestLoginName = WizardDetectorTestLoginName;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorUserCount.js b/apps/user_ldap/js/wizard/wizardDetectorUserCount.js
new file mode 100644
index 0000000000000000000000000000000000000000..bcff2cf3b1068875434b25c1c2b6a205fa6942d5
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorUserCount.js
@@ -0,0 +1,26 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc let's the wizard backend count the available users
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorUserCount = OCA.LDAP.Wizard.WizardDetectorFilterSimpleRequestAbstract.subClass({
+		init: function() {
+			this.setTargetKey('ldap_user_count');
+			this.wizardMethod = 'countUsers';
+			this.runsOnRequest = true;
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorUserCount = WizardDetectorUserCount;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorUserDisplayNameAttribute.js b/apps/user_ldap/js/wizard/wizardDetectorUserDisplayNameAttribute.js
new file mode 100644
index 0000000000000000000000000000000000000000..ae734480c1c11612e46b330318a0c71122bd2141
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorUserDisplayNameAttribute.js
@@ -0,0 +1,39 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc let's the wizard backend count the available users
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorUserDisplayNameAttribute = OCA.LDAP.Wizard.WizardDetectorFilterSimpleRequestAbstract.subClass({
+		init: function() {
+			this.setTargetKey('ldap_user_count');
+			this.wizardMethod = 'detectUserDisplayNameAttribute';
+			this.runsOnRequest = true;
+		},
+
+		/**
+		 * @inheritdoc
+		 */
+		run: function(model, configID) {
+			// default value has capital N. Detected values are always lowercase
+			if(model.configuration.ldap_display_name && model.configuration.ldap_display_name !== 'displayName') {
+				// a value is already set. Don't overwrite and don't ask LDAP
+				// without reason.
+				return false;
+			}
+			this._super(model, configID);
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorUserDisplayNameAttribute = WizardDetectorUserDisplayNameAttribute;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorUserGroupAssociation.js b/apps/user_ldap/js/wizard/wizardDetectorUserGroupAssociation.js
new file mode 100644
index 0000000000000000000000000000000000000000..953a0b909a626593c016e3a7a277ea33442d327d
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorUserGroupAssociation.js
@@ -0,0 +1,40 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc let's the wizard backend count the available users
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorUserGroupAssociation = OCA.LDAP.Wizard.WizardDetectorFilterSimpleRequestAbstract.subClass({
+		init: function() {
+			this.setTargetKey('ldap_group_count');
+			this.wizardMethod = 'determineGroupMemberAssoc';
+			this.runsOnRequest = true;
+		},
+
+		/**
+		 * @inheritdoc
+		 */
+		run: function(model, configID) {
+			// TODO: might be better with configuration marker as uniqueMember
+			// is a valid value (although probably less common then member and memberUid).
+			if(model.configuration.ldap_group_member_assoc_attribute && model.configuration.ldap_group_member_assoc_attribute !== 'uniqueMember') {
+				// a value is already set. Don't overwrite and don't ask LDAP
+				// without reason.
+				return false;
+			}
+			this._super(model, configID);
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorUserGroupAssociation = WizardDetectorUserGroupAssociation;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardDetectorUserObjectClasses.js b/apps/user_ldap/js/wizard/wizardDetectorUserObjectClasses.js
new file mode 100644
index 0000000000000000000000000000000000000000..0fa324a0809419e7573a7fd353ae94a851724812
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardDetectorUserObjectClasses.js
@@ -0,0 +1,29 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc discovers object classes for the users tab
+	 *
+	 * @constructor
+	 */
+	var WizardDetectorUserObjectClasses = OCA.LDAP.Wizard.WizardDetectorFeatureAbstract.subClass({
+		/** @inheritdoc */
+		init: function() {
+			// given, it is not a configuration key
+			this.setTargetKey('ldap_userfilter_objectclass');
+			this.wizardMethod = 'determineUserObjectClasses';
+			this.featureName = 'UserObjectClasses';
+			this.runsOnRequest = true;
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardDetectorUserObjectClasses = WizardDetectorUserObjectClasses;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardFilterOnType.js b/apps/user_ldap/js/wizard/wizardFilterOnType.js
new file mode 100644
index 0000000000000000000000000000000000000000..bb1871023a298c9f03370b39bbe25c73986f1f02
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardFilterOnType.js
@@ -0,0 +1,76 @@
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc filters a select box when a text element is typed in
+	 */
+	var FilterOnType = OCA.LDAP.Wizard.WizardObject.subClass({
+		/**
+		 * initializes a type filter on a text input for a select element
+		 *
+		 * @param {jQuery} $select
+		 * @param {jQuery} $textInput
+		 */
+		init: function($select, $textInput) {
+			this.$select = $select;
+			this.$textInput = $textInput;
+			this.updateOptions();
+			this.lastSearch = '';
+
+			var fity = this;
+			$textInput.bind('change keyup', function () {
+				if(fity.runID) {
+					window.clearTimeout(fity.runID);
+				}
+				fity.runID = window.setTimeout(function() {
+					fity.filter(fity);
+				}, 250);
+			});
+		},
+
+		/**
+		 * the options will be read in again. Should be called after a
+		 * configuration switch.
+		 */
+		updateOptions: function() {
+			var options = [];
+			this.$select.find('option').each(function() {
+				options.push({
+						value: $(this).val(),
+						normalized: $(this).val().toLowerCase()
+					}
+				);
+			});
+			this._options = options;
+		},
+
+		/**
+		 * the actual search or filter method
+		 *
+		 * @param {FilterOnType} fity
+		 */
+		filter: function(fity) {
+			var filterVal = fity.$textInput.val().toLowerCase();
+			if(filterVal === fity.lastSearch) {
+				return;
+			}
+			fity.lastSearch = filterVal;
+			fity.$select.empty();
+			$.each(fity._options, function() {
+				if(!filterVal || this.normalized.indexOf(filterVal) > -1) {
+					fity.$select.append($('<option>').val(this.value).text(this.value));
+				}
+			});
+			delete(fity.runID);
+		}
+	});
+
+	OCA.LDAP.Wizard.FilterOnType = FilterOnType;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardFilterOnTypeFactory.js b/apps/user_ldap/js/wizard/wizardFilterOnTypeFactory.js
new file mode 100644
index 0000000000000000000000000000000000000000..bd6511dc8b0f17e770473845413536d39d5cf14e
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardFilterOnTypeFactory.js
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc creates instances of OCA.LDAP.Wizard.FilterOnType upon request
+	 */
+	var FilterOnTypeFactory = OCA.LDAP.Wizard.WizardObject.subClass({
+		/**
+		 * initializes a type filter on a text input for a select element
+		 *
+		 * @param {jQuery} $select
+		 * @param {jQuery} $textInput
+		 */
+		get: function($select, $textInput) {
+			return new OCA.LDAP.Wizard.FilterOnType($select, $textInput);
+		}
+	});
+
+	OCA.LDAP.Wizard.FilterOnTypeFactory = FilterOnTypeFactory;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardObject.js b/apps/user_ldap/js/wizard/wizardObject.js
new file mode 100644
index 0000000000000000000000000000000000000000..a90f1533a26116118a1eead846bb0f72e5fc71ff
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardObject.js
@@ -0,0 +1,60 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+	var initializing = false;
+	var superPattern = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/;
+
+	/**
+	 * @classdesc a base class that allows inheritance
+	 *
+	 * @abstrcact
+	 * @constructor
+	 */
+	var WizardObject = function(){};
+	WizardObject.subClass = function(properties) {
+		var _super = this.prototype;
+
+		initializing = true;
+		var proto = new this();
+		initializing = false;
+
+		for (var name in properties) {
+			proto[name] =
+				typeof properties[name] === "function" &&
+				typeof _super[name] === 'function' &&
+				superPattern.test(properties[name]) ?
+					(function (name, fn) {
+						return function () {
+							var tmp = this._super;
+							this._super = _super[name];
+							var ret = fn.apply(this, arguments);
+							this._super = tmp;
+							return ret;
+						};
+					})(name, properties[name]) :
+					properties[name];
+		};
+
+		function Class() {
+			if(!initializing && this.init) {
+				this.init.apply(this, arguments);
+			}
+		}
+
+		Class.prototype = proto;
+		Class.constructor = Class;
+		Class.subClass = arguments.callee;
+		return Class;
+	};
+
+	WizardObject.constructor = WizardObject;
+
+	OCA.LDAP.Wizard.WizardObject = WizardObject;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardTabAbstractFilter.js b/apps/user_ldap/js/wizard/wizardTabAbstractFilter.js
new file mode 100644
index 0000000000000000000000000000000000000000..024b6af65d0302275ecf71a3feafb06c5e8fc128
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardTabAbstractFilter.js
@@ -0,0 +1,378 @@
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc This class represents the view belonging to the server tab
+	 * in the LDAP wizard.
+	 */
+	var WizardTabAbstractFilter = OCA.LDAP.Wizard.WizardTabGeneric.subClass({
+		/**
+		 * @property {number} number that needs to exceeded to use complex group
+		 * selection element
+		 */
+		_groupElementSwitchThreshold: 40,
+
+		/**
+		 * @property {boolean} - tells whether multiselect or complex element is
+		 * used for selecting groups
+		 */
+		isComplexGroupChooser: false,
+
+		/** @property {string} */
+		tabID: '',
+
+		/**
+		 * initializes the instance. Always call it after initialization.
+		 * concrete view must set managed items first, and then call the parent
+		 * init.
+		 *
+		 * @param {OCA.LDAP.Wizard.FilterOnTypeFactory} fotf
+		 * @param {number} [tabIndex]
+		 * @param {string} [tabID]
+		 */
+		init: function (fotf, tabIndex, tabID) {
+			this._super(tabIndex, tabID);
+
+			/** @type {OCA.LDAP.Wizard.FilterOnTypeFactory} */
+			this.foTFactory = fotf;
+			this._initMultiSelect(
+				this.getGroupsItem().$element,
+				t('user_ldap', 'Select groups')
+			);
+			this._initMultiSelect(
+				this.getObjectClassItem().$element,
+				t('user_ldap', 'Select object classes')
+			);
+			this.filterName = this.getFilterItem().keyName;
+			this._initFilterModeSwitcher(
+				this.getToggleItem().$element,
+				this.getRawFilterContainerItem().$element,
+				[ this.getObjectClassItem().$element ],
+				this.getFilterModeKey(),
+				{
+					status: 'disabled',
+					$element: this.getGroupsItem().$element
+				}
+			);
+			_.bindAll(this, 'onCountButtonClick',  'onSelectGroup', 'onDeselectGroup');
+			this.getCountItem().$relatedElements.click(this.onCountButtonClick);
+			if(this.manyGroupsSupport) {
+				var $selectBtn = $(this.tabID).find('.ldapGroupListSelect');
+				$selectBtn.click(this.onSelectGroup);
+				var $deselectBtn = $(this.tabID).find('.ldapGroupListDeselect');
+				$deselectBtn.click(this.onDeselectGroup);
+			}
+		},
+
+		/**
+		 * returns managed item for the object class chooser. must be
+		 * implemented by concrete view
+		 */
+		getObjectClassItem: function () {},
+
+		/**
+		 * returns managed item for the group chooser. must be
+		 * implemented by concrete view
+		 */
+		getGroupsItem: function () {},
+
+		/**
+		 * returns managed item for the effective filter. must be
+		 * implemented by concrete view
+		 */
+		getFilterItem: function () {},
+
+		/**
+		 * returns managed item for the toggle element. must be
+		 * implemented by concrete view
+		 */
+		getToggleItem: function () {},
+
+		/**
+		 * returns managed item for the raw filter container. must be
+		 * implemented by concrete view
+		 */
+		getRawFilterContainerItem: function () {},
+
+		/**
+		 * returns managed item for the count control. must be
+		 * implemented by concrete view
+		 */
+		getCountItem: function () {},
+
+		/**
+		 * returns name of the filter mode key. must be implemented by concrete
+		 * view
+		 */
+		getFilterModeKey: function () {},
+
+		/**
+		 * Sets the config model for this view and subscribes to some events.
+		 * Also binds the config chooser to the model
+		 *
+		 * @param {OCA.LDAP.Wizard.ConfigModel} configModel
+		 */
+		setModel: function(configModel) {
+			this._super(configModel);
+			this.configModel.on('configLoaded', this.onConfigSwitch, this);
+			this.configModel.on('receivedLdapFeature', this.onFeatureReceived, this);
+		},
+
+		/**
+		 * @inheritdoc
+		 */
+		_setFilterModeAssisted: function () {
+			this._super();
+			if(this.isComplexGroupChooser) {
+				this.enableElement(this.getGroupsItem().$relatedElements);
+			}
+		},
+
+		/**
+		 * @inheritdoc
+		 */
+		_setFilterModeRaw: function () {
+			this._super();
+			if(this.manyGroupsSupport) {
+				this.disableElement(this.getGroupsItem().$relatedElements);
+			}
+		},
+
+		/**
+		 * sets the selected user object classes
+		 *
+		 * @param {Array} classes
+		 */
+		setObjectClass: function(classes) {
+			this.setElementValue(this.getObjectClassItem().$element, classes);
+			this.getObjectClassItem().$element.multiselect('refresh');
+		},
+
+		/**
+		 * sets the selected groups
+		 *
+		 * @param {Array} groups
+		 */
+		setGroups: function(groups) {
+			if(!this.isComplexGroupChooser) {
+				this.setElementValue(this.getGroupsItem().$element, groups);
+				this.getGroupsItem().$element.multiselect('refresh');
+			} else {
+				var $element = $(this.tabID).find('.ldapGroupListSelected');
+				this.equipMultiSelect($element, groups);
+			}
+		},
+
+		/**
+		 * sets the filter
+		 *
+		 * @param {string} filter
+		 */
+		setFilter: function(filter) {
+			this.setElementValue(this.getFilterItem().$element, filter);
+			this.$filterModeRawContainer.siblings('.ldapReadOnlyFilterContainer').find('.ldapFilterReadOnlyElement').text(filter);
+		},
+
+		/**
+		 * sets the user count string
+		 *
+		 * @param {string} countInfo
+		 */
+		setCount: function(countInfo) {
+			this.setElementValue(this.getCountItem().$element, countInfo);
+		},
+
+		/**
+		 * @inheritdoc
+		 */
+		considerFeatureRequests: function() {
+			if(!this.isActive) {
+				return;
+			}
+			if(this.getObjectClassItem().$element.find('option').length === 0) {
+				this.disableElement(this.getObjectClassItem().$element);
+				this.disableElement(this.getGroupsItem().$element);
+				if(this.parsedFilterMode === this.configModel.FILTER_MODE_ASSISTED) {
+					this.configModel.requestWizard(this.getObjectClassItem().keyName);
+					this.configModel.requestWizard(this.getGroupsItem().keyName);
+				}
+			}
+		},
+
+		/**
+		 * updates (creates, if necessary) filterOnType instances
+		 *
+		 * @param {string} [only] - if only one search index should be updated
+		 */
+		updateFilterOnType: function(only) {
+			if(_.isUndefined(this.filterOnType)) {
+				this.filterOnType = [];
+
+				var $availableGroups = $(this.tabID).find('.ldapGroupListAvailable');
+				this.filterOnType.push(this.foTFactory.get(
+					$availableGroups, $(this.tabID).find('.ldapManyGroupsSearch')
+				));
+				var $selectedGroups  = $(this.tabID).find('.ldapGroupListSelected');
+				this.filterOnType.push(this.foTFactory.get(
+					$selectedGroups, $(this.tabID).find('.ldapManyGroupsSearch')
+				));
+			} else {
+				if(_.isUndefined || only.toLowerCase() === 'available')  {
+					this.filterOnType[0].updateOptions();
+				}
+				if(_.isUndefined || only.toLowerCase() === 'selected')  {
+					this.filterOnType[1].updateOptions();
+				}
+			}
+		},
+
+		/**
+		 * @inheritdoc
+		 */
+		onActivate: function() {
+			this.considerFeatureRequests();
+		},
+
+		/**
+		 * resets the view when a configuration switch happened.
+		 *
+		 * @param {WizardTabAbstractFilter} view
+		 * @param {Object} configuration
+		 */
+		onConfigSwitch: function(view, configuration) {
+			view.getObjectClassItem().$element.find('option').remove();
+			view.getGroupsItem().$element.find('option').remove();
+			view.getCountItem().$element.text('');
+			$(view.tabID).find('.ldapGroupListAvailable').empty();
+			$(view.tabID).find('.ldapGroupListSelected').empty();
+			view.updateFilterOnType();
+			$(view.tabID).find('.ldapManyGroupsSearch').val('');
+
+			if(view.isComplexGroupChooser) {
+				view.isComplexGroupChooser = false;
+				view.getGroupsItem().$element.multiselect({classes: view.multiSelectPluginClass});
+				$(view.tabID).find(".ldapManyGroupsSupport").addClass('hidden');
+			}
+
+			view.onConfigLoaded(view, configuration);
+		},
+
+		/**
+		 * @inheritdoc
+		 */
+		onConfigLoaded: function(view, configuration) {
+			for(var key in view.managedItems){
+				if(!_.isUndefined(configuration[key])) {
+					var value = configuration[key];
+					var methodName = view.managedItems[key].setMethod;
+					if(!_.isUndefined(view[methodName])) {
+						view[methodName](value);
+						// we reimplement it here to update the filter index
+						// for groups. Maybe we can isolate it?
+						if(methodName === 'setGroups') {
+							view.updateFilterOnType('selected');
+						}
+					}
+				}
+			}
+		},
+
+		/**
+		 * if UserObjectClasses are found, the corresponding element will be
+		 * updated
+		 *
+		 * @param {WizardTabAbstractFilter} view
+		 * @param {FeaturePayload} payload
+		 */
+		onFeatureReceived: function(view, payload) {
+			if(payload.feature === view.getObjectClassItem().featureName) {
+				view.equipMultiSelect(view.getObjectClassItem().$element, payload.data);
+				if(    !view.getFilterItem().$element.val()
+					&& view.parsedFilterMode === view.configModel.FILTER_MODE_ASSISTED
+				) {
+					view.configModel.requestWizard(view.getFilterItem().keyName)
+				}
+			} else if (payload.feature === view.getGroupsItem().featureName) {
+				if(view.manyGroupsSupport && payload.data.length > view._groupElementSwitchThreshold) {
+					// we need to fill the left list box, excluding the values
+					// that are already selected
+					var $element = $(view.tabID).find('.ldapGroupListAvailable');
+					var selected = view.configModel.configuration[view.getGroupsItem().keyName];
+					var available = $(payload.data).not(selected).get();
+					view.equipMultiSelect($element, available);
+					view.updateFilterOnType('available');
+					$(view.tabID).find(".ldapManyGroupsSupport").removeClass('hidden');
+					view.getGroupsItem().$element.multiselect({classes: view.multiSelectPluginClass + ' forceHidden'});
+					view.isComplexGroupChooser = true;
+				} else {
+					view.isComplexGroupChooser = false;
+					view.equipMultiSelect(view.getGroupsItem().$element, payload.data);
+					view.getGroupsItem().$element.multiselect({classes: view.multiSelectPluginClass});
+					$(view.tabID).find(".ldapManyGroupsSupport").addClass('hidden');
+
+				}
+			}
+		},
+
+		/**
+		 * request to count the users with the current filter
+		 *
+		 * @param {Event} event
+		 */
+		onCountButtonClick: function(event) {
+			event.preventDefault();
+			// let's clear the field
+			this.getCountItem().$element.text('');
+			this.configModel.requestWizard(this.getCountItem().keyName);
+		},
+
+		/**
+		 * saves groups when using the complex UI
+		 *
+		 * @param {Array} groups
+		 * @returns {boolean}
+		 * @private
+		 */
+		_saveGroups: function(groups) {
+			var toSave = '';
+			$(groups).each(function() { toSave = toSave + "\n" + this; } );
+			this.configModel.set(this.getGroupsItem().keyName, $.trim(toSave));
+		},
+
+		/**
+		 * acts on adding groups to the filter
+		 */
+		onSelectGroup: function() {
+			var $available = $(this.tabID).find('.ldapGroupListAvailable');
+			var $selected = $(this.tabID).find('.ldapGroupListSelected');
+			var selected = $.map($selected.find('option'), function(e) { return e.value; });
+
+			this._saveGroups(selected.concat($available.val()));
+			$available.find('option:selected').prependTo($selected);
+			this.updateFilterOnType();
+		},
+
+		/**
+		 * acts on removing groups to the filter
+		 */
+		onDeselectGroup: function() {
+			var $available = $(this.tabID).find('.ldapGroupListAvailable');
+			var $selected = $(this.tabID).find('.ldapGroupListSelected');
+			var selected = $.map($selected.find('option:not(:selected)'), function(e) { return e.value; });
+
+			this._saveGroups(selected);
+			$selected.find('option:selected').appendTo($available);
+			this.updateFilterOnType();
+		}
+
+	});
+
+	OCA.LDAP.Wizard.WizardTabAbstractFilter = WizardTabAbstractFilter;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardTabAdvanced.js b/apps/user_ldap/js/wizard/wizardTabAdvanced.js
new file mode 100644
index 0000000000000000000000000000000000000000..a27ec87b7c46fbc7a0b155c192c2fcbb70886245
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardTabAdvanced.js
@@ -0,0 +1,330 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc This class represents the view belonging to the advanced tab
+	 * in the LDAP wizard.
+	 */
+	var WizardTabAdvanced = OCA.LDAP.Wizard.WizardTabGeneric.subClass({
+		/**
+		 * initializes the instance. Always call it after initialization.
+		 *
+		 * @param tabIndex
+		 * @param tabID
+		 */
+		init: function (tabIndex, tabID) {
+			this._super(tabIndex, tabID);
+
+			var items = {
+				// Connection settings
+				ldap_configuration_active: {
+					$element: $('#ldap_configuration_active'),
+					setMethod: 'setConfigurationState'
+				},
+				ldap_backup_host: {
+					$element: $('#ldap_backup_host'),
+					setMethod: 'setBackupHost'
+				},
+				ldap_backup_port: {
+					$element: $('#ldap_backup_port'),
+					setMethod: 'setBackupPort'
+				},
+				ldap_override_main_server: {
+					$element: $('#ldap_override_main_server'),
+					setMethod: 'setOverrideMainServerState'
+				},
+				ldap_nocase: {
+					$element: $('#ldap_nocase'),
+					setMethod: 'setNoCase'
+				},
+				ldap_turn_off_cert_check: {
+					$element: $('#ldap_turn_off_cert_check'),
+					setMethod: 'setCertCheckDisabled'
+				},
+				ldap_cache_ttl: {
+					$element: $('#ldap_cache_ttl'),
+					setMethod: 'setCacheTTL'
+				},
+
+				//Directory Settings
+				ldap_display_name: {
+					$element: $('#ldap_display_name'),
+					setMethod: 'setUserDisplayName'
+				},
+				ldap_base_users: {
+					$element: $('#ldap_base_users'),
+					setMethod: 'setBaseDNUsers'
+				},
+				ldap_attributes_for_user_search: {
+					$element: $('#ldap_attributes_for_user_search'),
+					setMethod: 'setSearchAttributesUsers'
+				},
+				ldap_group_display_name: {
+					$element: $('#ldap_group_display_name'),
+					setMethod: 'setGroupDisplayName'
+				},
+				ldap_base_groups: {
+					$element: $('#ldap_base_groups'),
+					setMethod: 'setBaseDNGroups'
+				},
+				ldap_attributes_for_group_search: {
+					$element: $('#ldap_attributes_for_group_search'),
+					setMethod: 'setSearchAttributesGroups'
+				},
+				ldap_group_member_assoc_attribute: {
+					$element: $('#ldap_group_member_assoc_attribute'),
+					setMethod: 'setGroupMemberAssociationAttribute'
+				},
+				ldap_nested_groups: {
+					$element: $('#ldap_nested_groups'),
+					setMethod: 'setUseNestedGroups'
+				},
+				ldap_paging_size: {
+					$element: $('#ldap_paging_size'),
+					setMethod: 'setPagingSize'
+				},
+
+				//Special Attributes
+				ldap_quota_attr: {
+					$element: $('#ldap_quota_attr'),
+					setMethod: 'setQuotaAttribute'
+				},
+				ldap_quota_def: {
+					$element: $('#ldap_quota_def'),
+					setMethod: 'setQuotaDefault'
+				},
+				ldap_email_attr: {
+					$element: $('#ldap_email_attr'),
+					setMethod: 'setEmailAttribute'
+				},
+				home_folder_naming_rule: {
+					$element: $('#home_folder_naming_rule'),
+					setMethod: 'setHomeFolderAttribute'
+				}
+			};
+			this.setManagedItems(items);
+		},
+
+		/**
+		 * Sets the config model for this view and subscribes to some events.
+		 * Also binds the config chooser to the model
+		 *
+		 * @param {OCA.LDAP.Wizard.ConfigModel} configModel
+		 */
+		setModel: function(configModel) {
+			this._super(configModel);
+			this.configModel.on('configLoaded', this.onConfigLoaded, this);
+			this.configModel.on('receivedLdapFeature', this.onResultReceived, this);
+		},
+
+		/**
+		 * updates the experienced admin check box
+		 *
+		 * @param {string} isConfigActive contains an int
+		 */
+		setConfigurationState: function(isConfigActive) {
+			this.setElementValue(
+				this.managedItems.ldap_configuration_active.$element, isConfigActive
+			);
+		},
+
+		/**
+		 * updates the backup host configuration text field
+		 *
+		 * @param {string} host
+		 */
+		setBackupHost: function(host) {
+			this.setElementValue(this.managedItems.ldap_backup_host.$element, host);
+		},
+
+		/**
+		 * updates the backup port configuration text field
+		 *
+		 * @param {string} port
+		 */
+		setBackupPort: function(port) {
+			this.setElementValue(this.managedItems.ldap_backup_port.$element, port);
+		},
+
+		/**
+		 * sets whether the main server should be overridden or not
+		 *
+		 * @param {string} doOverride contains an int
+		 */
+		setOverrideMainServerState: function(doOverride) {
+			this.setElementValue(
+				this.managedItems.ldap_override_main_server.$element, doOverride
+			);
+		},
+
+		/**
+		 * whether the server is case insensitive. This setting does not play
+		 * a role anymore (probably never had).
+		 *
+		 * @param {string} noCase contains an int
+		 */
+		setNoCase: function(noCase) {
+			this.setElementValue(this.managedItems.ldap_nocase.$element, noCase);
+		},
+
+		/**
+		 * sets whether the SSL/TLS certification check shout be disabled
+		 *
+		 * @param {string} doCertCheck contains an int
+		 */
+		setCertCheckDisabled: function(doCertCheck) {
+			this.setElementValue(
+				this.managedItems.ldap_turn_off_cert_check.$element, doCertCheck
+			);
+		},
+
+		/**
+		 * sets the time-to-live of the LDAP cache (in seconds)
+		 *
+		 * @param {string} cacheTTL contains an int
+		 */
+		setCacheTTL: function(cacheTTL) {
+			this.setElementValue(this.managedItems.ldap_cache_ttl.$element, cacheTTL);
+		},
+
+		/**
+		 * sets the user display name attribute
+		 *
+		 * @param {string} attribute
+		 */
+		setUserDisplayName: function(attribute) {
+			this.setElementValue(this.managedItems.ldap_display_name.$element, attribute);
+		},
+
+		/**
+		 * sets the Base DN for users
+		 *
+		 * @param {string} base
+		 */
+		setBaseDNUsers: function(base) {
+			this.setElementValue(this.managedItems.ldap_base_users.$element, base);
+		},
+
+		/**
+		 * sets the attributes for user searches
+		 *
+		 * @param {string} attributes
+		 */
+		setSearchAttributesUsers: function(attributes) {
+			this.setElementValue(this.managedItems.ldap_attributes_for_user_search.$element, attributes);
+		},
+
+		/**
+		 * sets the display name attribute for groups
+		 *
+		 * @param {string} attribute
+		 */
+		setGroupDisplayName: function(attribute) {
+			this.setElementValue(this.managedItems.ldap_group_display_name.$element, attribute);
+		},
+
+		/**
+		 * sets the Base DN for groups
+		 *
+		 * @param {string} base
+		 */
+		setBaseDNGroups: function(base) {
+			this.setElementValue(this.managedItems.ldap_base_groups.$element, base);
+		},
+
+		/**
+		 * sets the attributes for group search
+		 *
+		 * @param {string} attributes
+		 */
+		setSearchAttributesGroups: function(attributes) {
+			this.setElementValue(this.managedItems.ldap_attributes_for_group_search.$element, attributes);
+		},
+
+		/**
+		 * sets the attribute for the association of users and groups
+		 *
+		 * @param {string} attribute
+		 */
+		setGroupMemberAssociationAttribute: function(attribute) {
+			this.setElementValue(this.managedItems.ldap_group_member_assoc_attribute.$element, attribute);
+		},
+
+		/**
+		 * enabled or disables the use of nested groups (groups in groups in
+		 * groups…)
+		 *
+		 * @param {string} useNestedGroups contains an int
+		 */
+		setUseNestedGroups: function(useNestedGroups) {
+			this.setElementValue(this.managedItems.ldap_nested_groups.$element, useNestedGroups);
+		},
+
+		/**
+		 * sets the size of pages for paged search
+		 *
+		 * @param {string} size contains an int
+		 */
+		setPagingSize: function(size) {
+			this.setElementValue(this.managedItems.ldap_paging_size.$element, size);
+		},
+
+		/**
+		 * sets the email attribute
+		 *
+		 * @param {string} attribute
+		 */
+		setEmailAttribute: function(attribute) {
+			this.setElementValue(this.managedItems.ldap_email_attr.$element, attribute);
+		},
+
+		/**
+		 * sets the quota attribute
+		 *
+		 * @param {string} attribute
+		 */
+		setQuotaAttribute: function(attribute) {
+			this.setElementValue(this.managedItems.ldap_quota_attr.$element, attribute);
+		},
+
+		/**
+		 * sets the default quota for LDAP users
+		 *
+		 * @param {string} quota contains an int
+		 */
+		setQuotaDefault: function(quota) {
+			this.setElementValue(this.managedItems.ldap_quota_def.$element, quota);
+		},
+
+		/**
+		 * sets the attribute for the ownCloud user specific home folder location
+		 *
+		 * @param {string} attribute
+		 */
+		setHomeFolderAttribute: function(attribute) {
+			this.setElementValue(this.managedItems.home_folder_naming_rule.$element, attribute);
+		},
+
+		/**
+		 * deals with the result of the Test Connection test
+		 *
+		 * @param {WizardTabAdvanced} view
+		 * @param {FeaturePayload} payload
+		 */
+		onResultReceived: function(view, payload) {
+			if(payload.feature === 'TestConfiguration') {
+				OC.Notification.showTemporary(payload.data.message);
+			}
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardTabAdvanced = WizardTabAdvanced;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardTabElementary.js b/apps/user_ldap/js/wizard/wizardTabElementary.js
new file mode 100644
index 0000000000000000000000000000000000000000..c7767b9cf669fada9a3c640d63bbc75a1f715e75
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardTabElementary.js
@@ -0,0 +1,347 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc This class represents the view belonging to the server tab
+	 * in the LDAP wizard.
+	 */
+	var WizardTabElementary = OCA.LDAP.Wizard.WizardTabGeneric.subClass({
+		/**
+		 * initializes the instance. Always call it after initialization.
+		 *
+		 * @param tabIndex
+		 * @param tabID
+		 */
+		init: function (tabIndex, tabID) {
+			tabIndex = 0;
+			this._super(tabIndex, tabID);
+			this.isActive = true;
+			this.configChooserID = '#ldap_serverconfig_chooser';
+
+			var items = {
+				ldap_host: {
+					$element: $('#ldap_host'),
+					setMethod: 'setHost'
+				},
+				ldap_port: {
+					$element: $('#ldap_port'),
+					setMethod: 'setPort',
+					$relatedElements: $('.ldapDetectPort')
+				},
+				ldap_dn: {
+					$element: $('#ldap_dn'),
+					setMethod: 'setAgentDN'
+				},
+				ldap_agent_password: {
+					$element: $('#ldap_agent_password'),
+					setMethod: 'setAgentPwd'
+				},
+				ldap_base: {
+					$element: $('#ldap_base'),
+					setMethod: 'setBase',
+					$relatedElements: $('.ldapDetectBase, .ldapTestBase'),
+					$detectButton: $('.ldapDetectBase'),
+					$testButton: $('.ldapTestBase')
+				},
+				ldap_base_test: {
+					$element: $('#ldap_base')
+				},
+				ldap_experienced_admin: {
+					$element: $('#ldap_experienced_admin'),
+					setMethod: 'setExperiencedAdmin'
+				}
+			};
+			this.setManagedItems(items);
+			_.bindAll(this, 'onPortButtonClick', 'onBaseDNButtonClick', 'onBaseDNTestButtonClick');
+			this.managedItems.ldap_port.$relatedElements.click(this.onPortButtonClick);
+			this.managedItems.ldap_base.$detectButton.click(this.onBaseDNButtonClick);
+			this.managedItems.ldap_base.$testButton.click(this.onBaseDNTestButtonClick);
+		},
+
+		/**
+		 * Sets the config model for this view and subscribes to some events.
+		 * Also binds the config chooser to the model
+		 *
+		 * @param {OCA.LDAP.Wizard.ConfigModel} configModel
+		 */
+		setModel: function(configModel) {
+			this._super(configModel);
+			this.configModel.on('configLoaded', this.onConfigSwitch, this);
+			this.configModel.on('newConfiguration', this.onNewConfiguration, this);
+			this.configModel.on('deleteConfiguration', this.onDeleteConfiguration, this);
+			this.configModel.on('receivedLdapFeature', this.onTestResultReceived, this);
+			this._enableConfigChooser();
+			this._enableConfigButtons();
+		},
+
+		/**
+		 * returns the currently selected configuration ID
+		 *
+		 * @returns {string}
+		 */
+		getConfigID: function() {
+			return $(this.configChooserID).val();
+		},
+
+		/**
+		 * updates the host configuration text field
+		 *
+		 * @param {string} host
+		 */
+		setHost: function(host) {
+			this.setElementValue(this.managedItems.ldap_host.$element, host);
+			if(host) {
+				this.enableElement(this.managedItems.ldap_port.$relatedElements);
+			} else {
+				this.disableElement(this.managedItems.ldap_port.$relatedElements);
+			}
+		},
+
+		/**
+		 * updates the port configuration text field
+		 *
+		 * @param {string} port
+		 */
+		setPort: function(port) {
+			this.setElementValue(this.managedItems.ldap_port.$element, port);
+		},
+
+		/**
+		 * updates the user (agent) DN text field
+		 *
+		 * @param {string} agentDN
+		 */
+		setAgentDN: function(agentDN) {
+			this.setElementValue(this.managedItems.ldap_dn.$element, agentDN);
+		},
+
+		/**
+		 * updates the user (agent) password field
+		 *
+		 * @param {string} agentPwd
+		 */
+		setAgentPwd: function(agentPwd) {
+			this.setElementValue(
+				this.managedItems.ldap_agent_password.$element, agentPwd
+			);
+		},
+		/**
+		 * updates the base DN text area
+		 *
+		 * @param {string} bases
+		 */
+		setBase: function(bases) {
+			this.setElementValue(this.managedItems.ldap_base.$element, bases);
+			if(!bases) {
+				this.disableElement(this.managedItems.ldap_base.$testButton);
+			} else {
+				this.enableElement(this.managedItems.ldap_base.$testButton);
+			}
+		},
+
+		/**
+		 * updates the experienced admin check box
+		 *
+		 * @param {string} xpAdminMode contains an int
+		 */
+		setExperiencedAdmin: function(xpAdminMode) {
+			this.setElementValue(
+				this.managedItems.ldap_experienced_admin.$element, xpAdminMode
+			);
+		},
+
+		/**
+		 * @inheritdoc
+		 */
+		overrideErrorMessage: function(message, key) {
+			switch(key) {
+				case 'ldap_port':
+					if (message === 'Invalid credentials') {
+						return t('user_ldap', 'Please check the credentials, they seem to be wrong.');
+					} else {
+						return t('user_ldap', 'Please specify the port, it could not be auto-detected.');
+					}
+					break;
+				case 'ldap_base':
+					if(   message === 'Server is unwilling to perform'
+						|| message === 'Could not connect to LDAP'
+					) {
+						return t('user_ldap', 'Base DN could not be auto-detected, please revise credentials, host and port.');
+					}
+					return t('user_ldap', 'Could not detect Base DN, please enter it manually.');
+					break;
+			}
+			return message;
+		},
+
+		/**
+		 * resets the view when a configuration switch happened.
+		 *
+		 * @param {WizardTabElementary} view
+		 * @param {Object} configuration
+		 */
+		onConfigSwitch: function(view, configuration) {
+			view.disableElement(view.managedItems.ldap_port.$relatedElements);
+
+			view.onConfigLoaded(view, configuration);
+		},
+
+		/**
+		 * updates the configuration chooser when a new configuration was added
+		 * which also means it is being switched to. The configuration fields
+		 * are updated on a different step.
+		 *
+		 * @param {WizardTabElementary} view
+		 * @param {Object} result
+		 */
+		onNewConfiguration: function(view, result) {
+			if(result.isSuccess === true) {
+				$(view.configChooserID + ' option:selected').removeAttr('selected');
+				var html = '<option value="'+result.configPrefix+'" selected="selected">'+t('user_ldap','{nthServer}. Server', {nthServer: $(view.configChooserID + ' option').length + 1})+'</option>';
+				$(view.configChooserID + ' option:last').after(html);
+			}
+		},
+
+		/**
+		 * updates the configuration chooser upon the deletion of a
+		 * configuration and, if necessary, loads an existing one.
+		 *
+		 * @param view
+		 * @param result
+		 */
+		onDeleteConfiguration: function(view, result) {
+			if(result.isSuccess === true) {
+				if(view.getConfigID() === result.configPrefix) {
+					// if the deleted value is still the selected one (99% of
+					// the cases), remove it from the list and load the topmost
+					$(view.configChooserID + ' option:selected').remove();
+					$(view.configChooserID + ' option:first').select();
+					if($(view.configChooserID +  ' option').length < 2) {
+						view.configModel.newConfig(false);
+					} else {
+						view.configModel.load(view.getConfigID());
+					}
+				} else {
+					// otherwise just remove the entry
+					$(view.configChooserID + ' option[value=' + result.configPrefix + ']').remove();
+				}
+			} else {
+				OC.Notification.showTemporary(result.errorMessage);
+			}
+		},
+
+		/**
+		 * Base DN test results will arrive here
+		 *
+		 * @param {WizardTabElementary} view
+		 * @param {FeaturePayload} payload
+		 */
+		onTestResultReceived: function(view, payload) {
+			if(payload.feature === 'TestBaseDN') {
+				var message;
+				if(payload.data.status === 'success') {
+					var objectsFound = parseInt(payload.data.changes.ldap_test_base, 10);
+					if(objectsFound < 1) {
+						message = t('user_ldap', 'No object found in the given Base DN. Please revise.');
+					} else if(objectsFound > 1000) {
+						message = t('user_ldap', 'More then 1.000 directory entries available.');
+					} else {
+						message = t('user_ldap', objectsFound + ' entries available within the provided Base DN');
+					}
+				} else {
+					message = t('user_ldap', 'An error occurred. Please check the Base DN, as well as connection settings and credentials.');
+					if(payload.data.message) {
+						console.warn(payload.data.message);
+					}
+				}
+				OC.Notification.showTemporary(message);
+			}
+		},
+
+		/**
+		 * request to count the users with the current filter
+		 *
+		 * @param {Event} event
+		 */
+		onPortButtonClick: function(event) {
+			event.preventDefault();
+			this.configModel.requestWizard('ldap_port');
+		},
+
+		/**
+		 * request to count the users with the current filter
+		 *
+		 * @param {Event} event
+		 */
+		onBaseDNButtonClick: function(event) {
+			event.preventDefault();
+			this.configModel.requestWizard('ldap_base');
+		},
+
+		/**
+		 * request to count the users with the current filter
+		 *
+		 * @param {Event} event
+		 */
+		onBaseDNTestButtonClick: function(event) {
+			event.preventDefault();
+			this.configModel.requestWizard('ldap_test_base');
+		},
+
+		/**
+		 * registers the change event on the configuration chooser and makes
+		 * the model load a newly selected configuration
+		 *
+		 * @private
+		 */
+		_enableConfigChooser: function() {
+			var view = this;
+			$(this.configChooserID).change(function(){
+				var value = $(view.configChooserID + ' option:selected:first').attr('value');
+				view.configModel.load(value);
+			});
+		},
+
+		/**
+		 * adds actions to the action buttons for configuration management
+		 *
+		 * @private
+		 */
+		_enableConfigButtons: function() {
+			var view = this;
+			$('#ldap_action_delete_configuration').click(function(event) {
+				event.preventDefault();
+				OC.dialogs.confirm(
+					t('user_ldap', 'Do you really want to delete the current Server Configuration?'),
+					t('user_ldap', 'Confirm Deletion'),
+					function(doDelete) {
+						if(doDelete) {
+							view.configModel.deleteConfig(view.getConfigID());
+						}
+					},
+					false
+				);
+			});
+
+			$('#ldap_action_add_configuration').click(function(event) {
+				event.preventDefault();
+				view.configModel.newConfig(false);
+			});
+
+			$('#ldap_action_copy_configuration').click(function(event) {
+				event.preventDefault();
+				view.configModel.newConfig(true);
+			});
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardTabElementary = WizardTabElementary;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardTabExpert.js b/apps/user_ldap/js/wizard/wizardTabExpert.js
new file mode 100644
index 0000000000000000000000000000000000000000..7cfd49ba0f6f19649e941b58236b2bc2ffb69357
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardTabExpert.js
@@ -0,0 +1,130 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc This class represents the view belonging to the expert tab
+	 * in the LDAP wizard.
+	 */
+	var WizardTabExpert = OCA.LDAP.Wizard.WizardTabGeneric.subClass({
+		/**
+		 * initializes the instance. Always call it after initialization.
+		 *
+		 * @param tabIndex
+		 * @param tabID
+		 */
+		init: function (tabIndex, tabID) {
+			this._super(tabIndex, tabID);
+
+			var items = {
+				ldap_expert_username_attr: {
+					$element: $('#ldap_expert_username_attr'),
+					setMethod: 'setUsernameAttribute'
+				},
+				ldap_expert_uuid_user_attr: {
+					$element: $('#ldap_expert_uuid_user_attr'),
+					setMethod: 'setUserUUIDAttribute'
+				},
+				ldap_expert_uuid_group_attr: {
+					$element: $('#ldap_expert_uuid_group_attr'),
+					setMethod: 'setGroupUUIDAttribute'
+				},
+
+				//Buttons
+				ldap_action_clear_user_mappings: {
+					$element: $('#ldap_action_clear_user_mappings')
+				},
+				ldap_action_clear_group_mappings: {
+					$element: $('#ldap_action_clear_group_mappings')
+				}
+
+			};
+			this.setManagedItems(items);
+			_.bindAll(this, 'onClearUserMappingsClick', 'onClearGroupMappingsClick');
+			this.managedItems.ldap_action_clear_user_mappings.$element.click(this.onClearUserMappingsClick);
+			this.managedItems.ldap_action_clear_group_mappings.$element.click(this.onClearGroupMappingsClick);
+		},
+
+		/**
+		 * Sets the config model for this view and subscribes to some events.
+		 * Also binds the config chooser to the model
+		 *
+		 * @param {OCA.LDAP.Wizard.ConfigModel} configModel
+		 */
+		setModel: function(configModel) {
+			this._super(configModel);
+			this.configModel.on('configLoaded', this.onConfigLoaded, this);
+			this.configModel.on('receivedLdapFeature', this.onResultReceived, this);
+		},
+
+		/**
+		 * sets the attribute to be used to create an ownCloud ID (username)
+		 *
+		 * @param {string} attribute
+		 */
+		setUsernameAttribute: function(attribute) {
+			this.setElementValue(this.managedItems.ldap_expert_username_attr.$element, attribute);
+		},
+
+		/**
+		 * sets the attribute that provides an unique identifier per LDAP user
+		 * entry
+		 *
+		 * @param {string} attribute
+		 */
+		setUserUUIDAttribute: function(attribute) {
+			this.setElementValue(this.managedItems.ldap_expert_uuid_user_attr.$element, attribute);
+		},
+
+		/**
+		 * sets the attribute that provides an unique identifier per LDAP group
+		 * entry
+		 *
+		 * @param {string} attribute
+		 */
+		setGroupUUIDAttribute: function(attribute) {
+			this.setElementValue(this.managedItems.ldap_expert_uuid_group_attr.$element, attribute);
+		},
+
+		/**
+		 * requests clearing of all user mappings
+		 */
+		onClearUserMappingsClick: function() {
+			this.configModel.requestWizard('ldap_action_clear_user_mappings', {ldap_clear_mapping: 'user'});
+		},
+
+		/**
+		 * requests clearing of all group mappings
+		 */
+		onClearGroupMappingsClick: function() {
+			this.configModel.requestWizard('ldap_action_clear_group_mappings', {ldap_clear_mapping: 'group'});
+		},
+
+		/**
+		 * deals with the result of the Test Connection test
+		 *
+		 * @param {WizardTabAdvanced} view
+		 * @param {FeaturePayload} payload
+		 */
+		onResultReceived: function(view, payload) {
+			if(payload.feature === 'ClearMappings') {
+				var message;
+				if(payload.data.status === 'success') {
+					message = t('user_ldap', 'Mappings cleared successfully!');
+				} else {
+					message = t('user_ldap', 'Error while clearing the mappings.');
+				}
+				OC.Notification.showTemporary(message);
+			}
+		}
+	});
+
+	OCA.LDAP.Wizard.WizardTabExpert = WizardTabExpert;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardTabGeneric.js b/apps/user_ldap/js/wizard/wizardTabGeneric.js
new file mode 100644
index 0000000000000000000000000000000000000000..524d2a048a18bd2d9408867d362e850f2ffdc3e0
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardTabGeneric.js
@@ -0,0 +1,547 @@
+
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc An abstract tab view
+	 * @abstract
+	 */
+	var WizardTabGeneric = OCA.LDAP.Wizard.WizardObject.subClass({
+		isActive: false,
+
+		/**
+		 * @property {string} - class that identifies a multiselect-plugin
+		 * control.
+		 */
+		multiSelectPluginClass: 'multiSelectPlugin',
+
+		/** @inheritdoc */
+		init: function(tabIndex, tabID) {
+			this.tabIndex = tabIndex;
+			this.tabID = tabID;
+			this.spinner = $('.ldapSpinner').first().clone().removeClass('hidden');
+			_.bindAll(this, '_toggleRawFilterMode', '_toggleRawFilterModeConfirmation');
+		},
+
+		/**
+		 * sets the configuration items that are managed by that view.
+		 *
+		 * The parameter contains key-value pairs the key being the
+		 * configuration keys and the value being its setter method.
+		 *
+		 * @param {object} managedItems
+		 */
+		setManagedItems: function(managedItems) {
+			this.managedItems = managedItems;
+			this._enableAutoSave();
+		},
+
+		/**
+		 * Sets the config model. The concrete view likely wants to subscribe
+		 * to events as well.
+		 *
+		 * @param {OCA.LDAP.Wizard.ConfigModel} configModel
+		 */
+		setModel: function(configModel) {
+			this.configModel = configModel;
+			this.parsedFilterMode = this.configModel.FILTER_MODE_ASSISTED;
+			this.configModel.on('detectionStarted', this.onDetectionStarted, this);
+			this.configModel.on('detectionCompleted', this.onDetectionCompleted, this);
+			this.configModel.on('serverError', this.onServerError, this);
+			this.configModel.on('setCompleted', this.onItemSaved, this);
+			this.configModel.on('configUpdated', this.onConfigLoaded, this);
+		},
+
+		/**
+		 * the method can be used to display a different error/information
+		 * message than provided by the ownCloud server response. The concrete
+		 * Tab View may optionally implement it. Returning an empty string will
+		 * avoid any notification.
+		 *
+		 * @param {string} message
+		 * @param {string} key
+		 * @returns {string}
+		 */
+		overrideErrorMessage: function(message, key) {
+			return message;
+		},
+
+		/**
+		 * this is called by the main view, if the tab is being switched to.
+		 * The concrete tab view can implement this if necessary.
+		 */
+		onActivate: function() { },
+
+		/**
+		 * updates the tab when the model loaded a configuration and notified
+		 * this view.
+		 *
+		 * @param {WizardTabGeneric} view - this instance
+		 * @param {Object} configuration
+		 */
+		onConfigLoaded: function(view, configuration) {
+			for(var key in view.managedItems){
+				if(!_.isUndefined(configuration[key])) {
+					var value = configuration[key];
+					var methodName = view.managedItems[key].setMethod;
+					if(!_.isUndefined(view[methodName])) {
+						view[methodName](value);
+					}
+				}
+			}
+		},
+
+		/**
+		 * reacts on a set action on the model and updates the tab with the
+		 * valid value.
+		 *
+		 * @param {WizardTabGeneric} view
+		 * @param {Object} result
+		 */
+		onItemSaved: function(view, result) {
+			if(!_.isUndefined(view.managedItems[result.key])) {
+				var methodName = view.managedItems[result.key].setMethod;
+				view[methodName](result.value);
+				if(!result.isSuccess) {
+					OC.Notification.showTemporary(t('user_ldap', 'Saving failed. Please make sure the database is in Operation. Reload before continuing.'));
+					console.warn(result.errorMessage);
+				}
+			}
+		},
+
+		/**
+		 * displays server error messages.
+		 *
+		 * @param view
+		 * @param payload
+		 */
+		onServerError: function(view, payload) {
+			if (   !_.isUndefined(view.managedItems[payload.relatedKey])) {
+				var message = view.overrideErrorMessage(payload.message, payload.relatedKey);
+				if(message) {
+					OC.Notification.showTemporary(message);
+				}
+			}
+		},
+
+		/**
+		 * disables affected, managed fields if a detector is running against them
+		 *
+		 * @param {WizardTabGeneric} view
+		 * @param {string} key
+		 */
+		onDetectionStarted: function(view, key) {
+			if(!_.isUndefined(view.managedItems[key])) {
+				view.disableElement(view.managedItems[key].$element);
+				if(!_.isUndefined(view.managedItems[key].$relatedElements)){
+					view.disableElement(view.managedItems[key].$relatedElements);
+				}
+				view.attachSpinner(view.managedItems[key].$element.attr('id'));
+			}
+		},
+
+		/**
+		 * enables affected, managed fields after a detector was run against them
+		 *
+		 * @param {WizardTabGeneric} view
+		 * @param {string} key
+		 */
+		onDetectionCompleted: function(view, key) {
+			if(!_.isUndefined(view.managedItems[key])) {
+				view.enableElement(view.managedItems[key].$element);
+				if(!_.isUndefined(view.managedItems[key].$relatedElements)){
+					view.enableElement(view.managedItems[key].$relatedElements);
+				}
+				view.removeSpinner(view.managedItems[key].$element.attr('id'));
+			}
+		},
+
+		/**
+		 * sets the value to an HTML element. Checkboxes, text areas and (text)
+		 * input fields are supported.
+		 *
+		 * @param {jQuery} $element - the target element
+		 * @param {string|number|Array} value
+		 */
+		setElementValue: function($element, value) {
+			// deal with check box
+			if ($element.is('input[type=checkbox]')) {
+				this._setCheckBox($element, value);
+				return;
+			}
+
+			// deal with text area
+			if ($element.is('textarea') && $.isArray(value)) {
+				value = value.join("\n");
+			}
+
+			if ($element.is('span')) {
+				$element.text(value);
+			} else {
+				$element.val(value);
+			}
+		},
+
+		/**
+		 * replaces options on a multiselect element
+		 *
+		 * @param {jQuery} $element - the multiselect element
+		 * @param {Array} options
+		 */
+		equipMultiSelect: function($element, options) {
+			$element.empty();
+			for (var i in options) {
+				var name = options[i];
+				$element.append($('<option>').val(name).text(name).attr('title', name));
+			}
+			if(!$element.hasClass('ldapGroupList')) {
+				$element.multiselect('refresh');
+				this.enableElement($element);
+			}
+		},
+
+		/**
+		 * enables the specified HTML element
+		 *
+		 * @param {jQuery} $element
+		 */
+		enableElement: function($element) {
+			var isMS = $element.is('select[multiple]');
+			var hasOptions = isMS ? ($element.find('option').length > 0) : false;
+
+			if($element.hasClass(this.multiSelectPluginClass) && hasOptions) {
+				$element.multiselect("enable");
+			} else if(!isMS || (isMS && hasOptions)) {
+				$element.prop('disabled', false);
+			}
+		},
+
+		/**
+		 * disables the specified HTML element
+		 *
+		 * @param {jQuery} $element
+		 */
+		disableElement: function($element) {
+			if($element.hasClass(this.multiSelectPluginClass)) {
+				$element.multiselect("disable");
+			} else {
+				$element.prop('disabled', 'disabled');
+			}
+		},
+
+		/**
+		 * attaches a spinner icon to the HTML element specified by ID
+		 *
+		 * @param {string} elementID
+		 */
+		attachSpinner: function(elementID) {
+			if($('#' + elementID + ' + .ldapSpinner').length == 0) {
+				var spinner = this.spinner.clone();
+				var $element = $('#' + elementID);
+				$(spinner).insertAfter($element);
+				// and special treatment for multiselects:
+				if ($element.is('select[multiple]')) {
+					$('#' + elementID + " + img + button").css('display', 'none');
+				}
+			}
+		},
+
+		/**
+		 * removes the spinner icon from the HTML element specified by ID
+		 *
+		 * @param {string} elementID
+		 */
+		removeSpinner: function(elementID) {
+			$('#' + elementID+' + .ldapSpinner').remove();
+			// and special treatment for multiselects:
+			$('#' + elementID + " + button").css('display', 'inline');
+		},
+
+		/**
+		 * whether the wizard works in experienced admin mode
+		 *
+		 * @returns {boolean}
+		 */
+		isExperiencedMode: function() {
+			return parseInt(this.configModel.configuration.ldap_experienced_admin, 10) === 1;
+		},
+
+		/**
+		 * sets up auto-save functionality to the managed items
+		 *
+		 * @private
+		 */
+		_enableAutoSave: function() {
+			var view = this;
+
+			for(var id in this.managedItems) {
+				if(_.isUndefined(this.managedItems[id].$element)
+				   || _.isUndefined(this.managedItems[id].setMethod)) {
+					continue;
+				}
+				var $element = this.managedItems[id].$element;
+				if (!$element.is('select[multiple]')) {
+					$element.change(function() {
+						view._requestSave($(this));
+					});
+				}
+			}
+		},
+
+		/**
+		 * initializes a multiSelect element
+		 *
+		 * @param {jQuery} $element
+		 * @param {string} caption
+		 * @private
+		 */
+		_initMultiSelect: function($element, caption) {
+			var view = this;
+			$element.multiselect({
+				header: false,
+				selectedList: 9,
+				noneSelectedText: caption,
+				classes: this.multiSelectPluginClass,
+				close: function() {
+					view._requestSave($element);
+				}
+			});
+		},
+
+		/**
+		 * @typedef {object} viewSaveInfo
+		 * @property {function} val
+		 * @property {function} attr
+		 * @property {function} is
+		 */
+
+		/**
+		 * requests a save operation from the model for a given value
+		 * represented by a HTML element and its ID.
+		 *
+		 * @param {jQuery|viewSaveInfo} $element
+		 * @private
+		 */
+		_requestSave: function($element) {
+			var value = '';
+			if($element.is('input[type=checkbox]')
+				&& !$element.is(':checked')) {
+				value = 0;
+			} else if ($element.is('select[multiple]')) {
+				var entries = $element.multiselect("getChecked");
+				for(var i = 0; i < entries.length; i++) {
+					value = value + "\n" + entries[i].value;
+				}
+				value = $.trim(value);
+			} else {
+				value = $element.val();
+			}
+			this.configModel.set($element.attr('id'), value);
+		},
+
+		/**
+		 * updates a checkbox element according to the provided value
+		 *
+		 * @param {jQuery} $element
+		 * @param {string|number} value
+		 * @private
+		 */
+		_setCheckBox: function($element, value) {
+			if(parseInt(value, 10) === 1) {
+				$element.attr('checked', 'checked');
+			} else {
+				$element.removeAttr('checked');
+			}
+		},
+
+		/**
+		 * this is called when the filter mode is switched to assisted. The
+		 * concrete tab view should implement this, to load LDAP features
+		 * (e.g. object classes, groups, attributes…), if necessary.
+		 */
+		considerFeatureRequests: function() {},
+
+		/**
+		 * this is called when the filter mode is switched to Assisted. The
+		 * concrete tab view should request the compilation of the respective
+		 * filter.
+		 */
+		requestCompileFilter: function() {
+			this.configModel.requestWizard(this.filterName);
+		},
+
+		/**
+		 * sets the filter mode according to the provided configuration value
+		 *
+		 * @param {string} mode
+		 */
+		setFilterMode: function(mode) {
+			if(parseInt(mode, 10) === this.configModel.FILTER_MODE_ASSISTED) {
+				this.parsedFilterMode = this.configModel.FILTER_MODE_ASSISTED;
+				this.considerFeatureRequests();
+				this._setFilterModeAssisted();
+				if(this.isActive) {
+					// filter compilation should happen only, if the mode was
+					// switched manually, but not when initiating the view
+					this.requestCompileFilter();
+				}
+			} else {
+				this._setFilterModeRaw();
+				this.parsedFilterMode = this.configModel.FILTER_MODE_RAW;
+			}
+		},
+
+		/**
+		 * updates the UI so that it represents the assisted mode setting
+		 *
+		 * @private
+		 */
+		_setFilterModeAssisted: function() {
+			var view = this;
+			this.$filterModeRawContainer.addClass('invisible');
+			var filter = this.$filterModeRawContainer.find('.ldapFilterInputElement').val();
+			this.$filterModeRawContainer.siblings('.ldapReadOnlyFilterContainer').find('.ldapFilterReadOnlyElement').text(filter);
+			this.$filterModeRawContainer.siblings('.ldapReadOnlyFilterContainer').removeClass('hidden');
+			$.each(this.filterModeDisableableElements, function(i, $element) {
+				view.enableElement($element);
+			});
+			if(!_.isUndefined(this.filterModeStateElement)) {
+				if (this.filterModeStateElement.status === 'enabled') {
+					this.enableElement(this.filterModeStateElement.$element);
+				} else {
+					this.filterModeStateElement.status = 'disabled';
+				}
+			}
+		},
+
+		/**
+		 * updates the UI so that it represents the raw mode setting
+		 *
+		 * @private
+		 */
+		_setFilterModeRaw: function() {
+			var view = this;
+			this.$filterModeRawContainer.removeClass('invisible');
+			this.$filterModeRawContainer.siblings('.ldapReadOnlyFilterContainer').addClass('hidden');
+			$.each(this.filterModeDisableableElements, function (i, $element) {
+				view.disableElement($element);
+			});
+
+			if(!_.isUndefined(this.filterModeStateElement)) {
+				if(this.filterModeStateElement.$element.multiselect().attr('disabled') === 'disabled') {
+					this.filterModeStateElement.status = 'disabled';
+				} else {
+					this.filterModeStateElement.status = 'enabled';
+				}
+			}
+			if(!_.isUndefined(this.filterModeStateElement)) {
+				this.disableElement(this.filterModeStateElement.$element);
+			}
+		},
+
+		/**
+		 * @callback toggleConfirmCallback
+		 * @param {boolean} isConfirmed
+		 */
+
+		/**
+		 * shows a confirmation dialogue before switching from raw to assisted
+		 * mode if experienced mode is enabled.
+		 *
+		 * @param {toggleConfirmCallback} toggleFnc
+		 * @private
+		 */
+		_toggleRawFilterModeConfirmation: function(toggleFnc) {
+			if( !this.isExperiencedMode()
+				|| this.parsedFilterMode === this.configModel.FILTER_MODE_ASSISTED
+			) {
+				toggleFnc(true);
+			} else {
+				OCdialogs.confirm(
+					t('user_ldap', 'Switching the mode will enable automatic LDAP queries. Depending on your LDAP size they may take a while. Do you still want to switch the mode?'),
+					t('user_ldap', 'Mode switch'),
+					toggleFnc
+				);
+			}
+		},
+
+		/**
+		 * toggles the visibility of a raw filter container and so also the
+		 * state of the multi-select controls. The model is requested to save
+		 * the state.
+		 */
+		_toggleRawFilterMode: function() {
+			var view = this;
+			this._toggleRawFilterModeConfirmation(function(isConfirmed) {
+				if(!isConfirmed) {
+					return;
+				}
+				/** var {number} */
+				var mode;
+				if (view.parsedFilterMode === view.configModel.FILTER_MODE_ASSISTED) {
+					mode = view.configModel.FILTER_MODE_RAW;
+				} else {
+					mode = view.configModel.FILTER_MODE_ASSISTED;
+				}
+				view.setFilterMode(mode);
+				/** @var {viewSaveInfo} */
+				var saveInfo = {
+					val: function () {
+						return mode;
+					},
+					attr: function () {
+						return view.filterModeKey;
+					},
+					is: function () {
+						return false;
+					}
+				};
+				view._requestSave(saveInfo);
+			});
+		},
+
+		/**
+		 * @typedef {object} filterModeStateElementObj
+		 * @property {string} status - either "enabled" or "disabled"
+		 * @property {jQuery} $element
+		 */
+
+		/**
+		 * initializes a raw filter mode switcher
+		 *
+		 * @param {jQuery} $switcher - the element receiving the click
+		 * @param {jQuery} $filterModeRawContainer - contains the raw filter
+		 * input elements
+		 * @param {jQuery[]} filterModeDisableableElements - an array of elements
+		 * not belonging to the raw filter part that shall be en/disabled.
+		 * @param {string} filterModeKey - the setting key that save the state
+		 * of the mode
+		 * @param {filterModeStateElementObj} [filterModeStateElement] - one element
+		 * which status (enabled or not) is tracked by a setting
+		 * @private
+		 */
+		_initFilterModeSwitcher: function(
+			$switcher,
+			$filterModeRawContainer,
+			filterModeDisableableElements,
+			filterModeKey,
+			filterModeStateElement
+		) {
+			this.$filterModeRawContainer = $filterModeRawContainer;
+			this.filterModeDisableableElements = filterModeDisableableElements;
+			this.filterModeStateElement = filterModeStateElement;
+			this.filterModeKey = filterModeKey;
+			$switcher.click(this._toggleRawFilterMode);
+		}
+
+	});
+
+	OCA.LDAP.Wizard.WizardTabGeneric = WizardTabGeneric;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardTabGroupFilter.js b/apps/user_ldap/js/wizard/wizardTabGroupFilter.js
new file mode 100644
index 0000000000000000000000000000000000000000..528b5d8367067a2a34462ee474c2206e4d0d75a3
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardTabGroupFilter.js
@@ -0,0 +1,124 @@
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc This class represents the view belonging to the server tab
+	 * in the LDAP wizard.
+	 */
+	var WizardTabGroupFilter = OCA.LDAP.Wizard.WizardTabAbstractFilter.subClass({
+		/**
+		 * @inheritdoc
+		 */
+		init: function (fotf, tabIndex, tabID) {
+			tabID = '#ldapWizard4';
+			var items = {
+				ldap_groupfilter_objectclass: {
+					$element: $('#ldap_groupfilter_objectclass'),
+					setMethod: 'setObjectClass',
+					keyName: 'ldap_groupfilter_objectclass',
+					featureName: 'GroupObjectClasses'
+				},
+				ldap_group_filter_mode: {
+					setMethod: 'setFilterMode'
+				},
+				ldap_groupfilter_groups: {
+					$element: $('#ldap_groupfilter_groups'),
+					setMethod: 'setGroups',
+					keyName: 'ldap_groupfilter_groups',
+					featureName: 'GroupsForGroups',
+					$relatedElements: $(
+						tabID + ' .ldapGroupListAvailable,' +
+						tabID + ' .ldapGroupListSelected,' +
+						tabID + ' .ldapManyGroupsSearch'
+					)
+				},
+				ldap_group_filter: {
+					$element: $('#ldap_group_filter'),
+					setMethod: 'setFilter',
+					keyName: 'ldap_group_filter'
+				},
+				groupFilterRawToggle: {
+					$element: $('#toggleRawGroupFilter')
+				},
+				groupFilterRawContainer: {
+					$element: $('#rawGroupFilterContainer')
+				},
+				ldap_group_count: {
+					$element: $('#ldap_group_count'),
+					$relatedElements: $('.ldapGetGroupCount'),
+					setMethod: 'setCount',
+					keyName: 'ldap_group_count'
+				}
+			};
+			this.setManagedItems(items);
+			this.manyGroupsSupport = true;
+			this._super(fotf, tabIndex, tabID);
+		},
+
+		/**
+		 * @inheritdoc
+		 * @returns {Object}
+		 */
+		getObjectClassItem: function () {
+			return this.managedItems.ldap_groupfilter_objectclass;
+		},
+
+		/**
+		 * @inheritdoc
+		 * @returns {Object}
+		 */
+		getGroupsItem: function () {
+			return this.managedItems.ldap_groupfilter_groups;
+		},
+
+		/**
+		 * @inheritdoc
+		 * @returns {Object}
+		 */
+		getFilterItem: function () {
+			return this.managedItems.ldap_group_filter;
+		},
+
+		/**
+		 * @inheritdoc
+		 * @returns {Object}
+		 */
+		getToggleItem: function () {
+			return this.managedItems.groupFilterRawToggle;
+		},
+
+		/**
+		 * @inheritdoc
+		 * @returns {Object}
+		 */
+		getRawFilterContainerItem: function () {
+			return this.managedItems.groupFilterRawContainer;
+		},
+
+		/**
+		 * @inheritdoc
+		 * @returns {Object}
+		 */
+		getCountItem: function () {
+			return this.managedItems.ldap_group_count;
+		},
+
+		/**
+		 * @inheritdoc
+		 * @returns {string}
+		 */
+		getFilterModeKey: function () {
+			return 'ldap_group_filter_mode';
+		}
+
+	});
+
+	OCA.LDAP.Wizard.WizardTabGroupFilter = WizardTabGroupFilter;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardTabLoginFilter.js b/apps/user_ldap/js/wizard/wizardTabLoginFilter.js
new file mode 100644
index 0000000000000000000000000000000000000000..9438fd73346470366e4d56141701fb918cfe7123
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardTabLoginFilter.js
@@ -0,0 +1,238 @@
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc This class represents the view belonging to the login filter
+	 * tab in the LDAP wizard.
+	 */
+	var WizardTabLoginFilter = OCA.LDAP.Wizard.WizardTabGeneric.subClass({
+		/**
+		 * initializes the instance. Always call it after initialization.
+		 *
+		 * @param tabIndex
+		 * @param tabID
+		 */
+		init: function (tabIndex, tabID) {
+			this._super(tabIndex, tabID);
+
+			var items = {
+				ldap_loginfilter_username: {
+					$element: $('#ldap_loginfilter_username'),
+					setMethod: 'setLoginAttributeUsername'
+				},
+				ldap_loginfilter_email: {
+					$element: $('#ldap_loginfilter_email'),
+					setMethod: 'setLoginAttributeEmail'
+				},
+				ldap_login_filter_mode: {
+					setMethod: 'setFilterMode'
+				},
+				ldap_loginfilter_attributes: {
+					$element: $('#ldap_loginfilter_attributes'),
+					setMethod: 'setLoginAttributesOther'
+				},
+				ldap_login_filter: {
+					$element: $('#ldap_login_filter'),
+					setMethod: 'setLoginFilter'
+				},
+				loginFilterRawToggle: {
+					$element: $('#toggleRawLoginFilter')
+				},
+				loginFilterRawContainer: {
+					$element: $('#rawLoginFilterContainer')
+				},
+				ldap_test_loginname: {
+					$element: $('#ldap_test_loginname'),
+					$relatedElements: $('.ldapVerifyLoginName')
+				}
+			};
+			this.setManagedItems(items);
+
+			this.filterModeKey = 'ldapLoginFilterMode';
+			this._initMultiSelect(
+				this.managedItems.ldap_loginfilter_attributes.$element,
+				t('user_ldap', 'Select attributes')
+			);
+			this.filterName = 'ldap_login_filter';
+			this._initFilterModeSwitcher(
+				this.managedItems.loginFilterRawToggle.$element,
+				this.managedItems.loginFilterRawContainer.$element,
+				[
+					this.managedItems.ldap_loginfilter_username.$element,
+					this.managedItems.ldap_loginfilter_email.$element,
+					this.managedItems.ldap_loginfilter_attributes.$element
+				],
+				'ldap_login_filter_mode'
+			);
+			_.bindAll(this, 'onVerifyClick');
+			this.managedItems.ldap_test_loginname.$relatedElements.click(this.onVerifyClick);
+		},
+
+		/**
+		 * Sets the config model for this view and subscribes to some events.
+		 * Also binds the config chooser to the model
+		 *
+		 * @param {OCA.LDAP.Wizard.ConfigModel} configModel
+		 */
+		setModel: function(configModel) {
+			this._super(configModel);
+			this.configModel.on('configLoaded', this.onConfigSwitch, this);
+			this.configModel.on('receivedLdapFeature', this.onFeatureReceived, this);
+		},
+
+		/**
+		 * sets the selected attributes
+		 *
+		 * @param {Array} attributes
+		 */
+		setLoginAttributesOther: function(attributes) {
+			this.setElementValue(this.managedItems.ldap_loginfilter_attributes.$element, attributes);
+			this.managedItems.ldap_loginfilter_attributes.$element.multiselect('refresh');
+		},
+
+		/**
+		 * sets the login list filter
+		 *
+		 * @param {string} filter
+		 */
+		setLoginFilter: function(filter) {
+			this.setElementValue(this.managedItems.ldap_login_filter.$element, filter);
+			this.$filterModeRawContainer.siblings('.ldapReadOnlyFilterContainer').find('.ldapFilterReadOnlyElement').text(filter);
+		},
+
+		/**
+		 * updates the username attribute check box
+		 *
+		 * @param {string} useUsername contains an int
+		 */
+		setLoginAttributeUsername: function(useUsername) {
+			this.setElementValue(
+				this.managedItems.ldap_loginfilter_username.$element, useUsername
+			);
+		},
+
+		/**
+		 * updates the email attribute check box
+		 *
+		 * @param {string} useEmail contains an int
+		 */
+		setLoginAttributeEmail: function(useEmail) {
+			this.setElementValue(
+				this.managedItems.ldap_loginfilter_email.$element, useEmail
+			);
+		},
+
+		/**
+		 * presents the result of the login name test
+		 *
+		 * @param result
+		 */
+		handleLoginTestResult: function(result) {
+			var message;
+			var isHtml = false;
+			if(result.status === 'success') {
+				var usersFound = parseInt(result.changes.ldap_test_loginname, 10);
+				if(usersFound < 1) {
+					var filter = $('<p>').text(result.changes.ldap_test_effective_filter).html();
+					message = t('user_ldap', 'User not found. Please check your login attributes and username. Effective filter (to copy-and-paste for command line validation): <br/>' + filter);
+					console.warn(filter);
+					isHtml = true;
+				} else if(usersFound === 1) {
+					message = t('user_ldap', 'User found and settings verified.');
+				} else if(usersFound > 1) {
+					message = t('user_ldap', 'Settings verified, but one user found. Only the first will be able to login. Consider a more narrow filter.');
+				}
+			} else {
+				message = t('user_ldap', 'An unspecified error occurred. Please check the settings and the log.');
+				if(!_.isUndefined(result.message) && result.message) {
+					message = result.message;
+				}
+				if(message === 'Bad search filter') {
+					message = t('user_ldap', 'The search filter is invalid, probably due to syntax issues like uneven number of opened and closed brackets. Please revise.');
+				} else if(message === 'connection error') {
+					message = t('user_ldap', 'A connection error to LDAP / AD occurred, please check host, port and credentials.');
+				} else if(message === 'missing placeholder') {
+					message = t('user_ldap', 'The %uid placeholder is missing. It will be replaced with the login name when querying LDAP / AD.');
+				}
+			}
+			OC.Notification.showTemporary(message, {isHTML: isHtml});
+		},
+
+		/**
+		 * @inheritdoc
+		 */
+		considerFeatureRequests: function() {
+			if(!this.isActive) {
+				return;
+			}
+			if(this.managedItems.ldap_loginfilter_attributes.$element.find('option').length === 0) {
+				this.disableElement(this.managedItems.ldap_loginfilter_attributes.$element);
+				if(this.parsedFilterMode === this.configModel.FILTER_MODE_ASSISTED) {
+					this.configModel.requestWizard('ldap_loginfilter_attributes');
+				}
+			}
+		},
+
+		/**
+		 * @inheritdoc
+		 */
+		onActivate: function() {
+			this.considerFeatureRequests();
+			if(!this.managedItems.ldap_login_filter.$element.val()) {
+				this.configModel.requestWizard('ldap_login_filter');
+			}
+		},
+
+		/**
+		 * resets the view when a configuration switch happened.
+		 *
+		 * @param {WizardTabLoginFilter} view
+		 * @param {Object} configuration
+		 */
+		onConfigSwitch: function(view, configuration) {
+			view.managedItems.ldap_loginfilter_attributes.$element.find('option').remove();
+
+			view.onConfigLoaded(view, configuration);
+		},
+
+		/**
+		 * if UserObjectClasses are found, the corresponding element will be
+		 * updated
+		 *
+		 * @param {WizardTabLoginFilter} view
+		 * @param {FeaturePayload} payload
+		 */
+		onFeatureReceived: function(view, payload) {
+			if(payload.feature === 'AvailableAttributes') {
+				view.equipMultiSelect(view.managedItems.ldap_loginfilter_attributes.$element, payload.data);
+			} else if(payload.feature === 'TestLoginName') {
+				view.handleLoginTestResult(payload.data);
+			}
+		},
+
+		/**
+		 * request to test the  provided login name
+		 *
+		 * @param {Event} event
+		 */
+		onVerifyClick: function(event) {
+			event.preventDefault();
+			var testLogin = this.managedItems.ldap_test_loginname.$element.val();
+			if(!testLogin) {
+				OC.Notification.showTemporary(t('user_ldap', 'Please provide a login name to test against'), 3);
+			} else {
+				this.configModel.requestWizard('ldap_test_loginname', {ldap_test_loginname: testLogin});
+			}
+		}
+
+	});
+
+	OCA.LDAP.Wizard.WizardTabLoginFilter = WizardTabLoginFilter;
+})();
diff --git a/apps/user_ldap/js/wizard/wizardTabUserFilter.js b/apps/user_ldap/js/wizard/wizardTabUserFilter.js
new file mode 100644
index 0000000000000000000000000000000000000000..992c1ccf37992b32b10963c4d3fb86975b88238d
--- /dev/null
+++ b/apps/user_ldap/js/wizard/wizardTabUserFilter.js
@@ -0,0 +1,136 @@
+/**
+ * Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+OCA = OCA || {};
+
+(function() {
+
+	/**
+	 * @classdesc This class represents the view belonging to the server tab
+	 * in the LDAP wizard.
+	 */
+	var WizardTabUserFilter = OCA.LDAP.Wizard.WizardTabAbstractFilter.subClass({
+		/**
+		 * @inheritdoc
+		 */
+		init: function (fotf, tabIndex, tabID) {
+			tabID = '#ldapWizard2';
+			var items = {
+				ldap_userfilter_objectclass: {
+					$element: $('#ldap_userfilter_objectclass'),
+					setMethod: 'setObjectClass',
+					keyName: 'ldap_userfilter_objectclass',
+					featureName: 'UserObjectClasses'
+				},
+				ldap_user_filter_mode: {
+					setMethod: 'setFilterMode'
+				},
+				ldap_userfilter_groups: {
+					$element: $('#ldap_userfilter_groups'),
+					setMethod: 'setGroups',
+					keyName: 'ldap_userfilter_groups',
+					featureName: 'GroupsForUsers',
+					$relatedElements: $(
+						tabID + ' .ldapGroupListAvailable,' +
+						tabID + ' .ldapGroupListSelected,' +
+						tabID + ' .ldapManyGroupsSearch'
+					)
+				},
+				ldap_userlist_filter: {
+					$element: $('#ldap_userlist_filter'),
+					setMethod: 'setFilter',
+					keyName: 'ldap_userlist_filter'
+				},
+				userFilterRawToggle: {
+					$element: $('#toggleRawUserFilter')
+				},
+				userFilterRawContainer: {
+					$element: $('#rawUserFilterContainer')
+				},
+				ldap_user_count: {
+					$element: $('#ldap_user_count'),
+					$relatedElements: $('.ldapGetUserCount'),
+					setMethod: 'setCount',
+					keyName: 'ldap_user_count'
+				}
+			};
+			this.setManagedItems(items);
+			this.manyGroupsSupport = true;
+			this._super(fotf, tabIndex, tabID);
+		},
+
+		/**
+		 * @inheritdoc
+		 * @returns {Object}
+		 */
+		getObjectClassItem: function () {
+			return this.managedItems.ldap_userfilter_objectclass;
+		},
+
+		/**
+		 * @inheritdoc
+		 * @returns {Object}
+		 */
+		getGroupsItem: function () {
+			return this.managedItems.ldap_userfilter_groups;
+		},
+
+		/**
+		 * @inheritdoc
+		 * @returns {Object}
+		 */
+		getFilterItem: function () {
+			return this.managedItems.ldap_userlist_filter;
+		},
+
+		/**
+		 * @inheritdoc
+		 * @returns {Object}
+		 */
+		getToggleItem: function () {
+			return this.managedItems.userFilterRawToggle;
+		},
+
+		/**
+		 * @inheritdoc
+		 * @returns {Object}
+		 */
+		getRawFilterContainerItem: function () {
+			return this.managedItems.userFilterRawContainer;
+		},
+
+		/**
+		 * @inheritdoc
+		 * @returns {Object}
+		 */
+		getCountItem: function () {
+			return this.managedItems.ldap_user_count;
+		},
+
+		/**
+		 * @inheritdoc
+		 * @returns {string}
+		 */
+		getFilterModeKey: function () {
+			return 'ldap_user_filter_mode';
+		},
+
+		/**
+		 * @inheritdoc
+		 */
+		overrideErrorMessage: function(message, key) {
+			if(   key === 'ldap_userfilter_groups'
+			   && message === 'memberOf is not supported by the server'
+			) {
+				message = t('user_ldap', 'The group box was disabled, because the LDAP / AD server does not support memberOf.');
+			}
+			return message;
+		}
+
+	});
+
+	OCA.LDAP.Wizard.WizardTabUserFilter = WizardTabUserFilter;
+})();
diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php
index e7fb4165c36709d342b6904a3184a41203e39272..f38d11d4be39482a9f50de9f575d3268e5271cea 100644
--- a/apps/user_ldap/lib/access.php
+++ b/apps/user_ldap/lib/access.php
@@ -595,6 +595,22 @@ class Access extends LDAPUtility implements user\IUserTools {
 		return $altName;
 	}
 
+	/**
+	 * fetches a list of users according to a provided loginName and utilizing
+	 * the login filter.
+	 *
+	 * @param string $loginName
+	 * @param array $attributes optional, list of attributes to read
+	 * @return array
+	 */
+	public function fetchUsersByLoginName($loginName, $attributes = array('dn')) {
+		$loginName = $this->escapeFilterPart($loginName);
+		$filter = \OCP\Util::mb_str_replace(
+			'%uid', $loginName, $this->connection->ldapLoginFilter, 'UTF-8');
+		$users = $this->fetchListOfUsers($filter, $attributes);
+		return $users;
+	}
+
 	/**
 	 * @param string $filter
 	 * @param string|string[] $attr
@@ -686,6 +702,17 @@ class Access extends LDAPUtility implements user\IUserTools {
 		return $this->count($filter, $this->connection->ldapBaseGroups, $attr, $limit, $offset);
 	}
 
+	/**
+	 * returns the number of available objects on the base DN
+	 *
+	 * @param int|null $limit
+	 * @param int|null $offset
+	 * @return int|bool
+	 */
+	public function countObjects($limit = null, $offset = null) {
+		return $this->count('objectclass=*', $this->connection->ldapBase, array('dn'), $limit, $offset);
+	}
+
 	/**
 	 * retrieved. Results will according to the order in the array.
 	 * @param int $limit optional, maximum results to be counted
diff --git a/apps/user_ldap/lib/configuration.php b/apps/user_ldap/lib/configuration.php
index 9b01fd2e559f370632b4a6ad003f4d399b8ffa90..373c5b48417a2df643b3944ca87a937a7eb4808f 100644
--- a/apps/user_ldap/lib/configuration.php
+++ b/apps/user_ldap/lib/configuration.php
@@ -201,11 +201,14 @@ class Configuration {
 					case 'ldapAgentPassword':
 						$readMethod = 'getPwd';
 						break;
-					case 'ldapUserDisplayName':
 					case 'ldapGroupDisplayName':
 						$readMethod = 'getLcValue';
 						break;
+					case 'ldapUserDisplayName':
 					default:
+						// user display name does not lower case because
+						// we rely on an upper case N as indicator whether to
+						// auto-detect it or not. FIXME
 						$readMethod = 'getValue';
 						break;
 				}
@@ -374,7 +377,7 @@ class Configuration {
 			'ldap_groupfilter_groups'           => '',
 			'ldap_display_name'                 => 'displayName',
 			'ldap_group_display_name'           => 'cn',
-			'ldap_tls'                          => 1,
+			'ldap_tls'                          => 0,
 			'ldap_nocase'                       => 0,
 			'ldap_quota_def'                    => '',
 			'ldap_quota_attr'                   => '',
diff --git a/apps/user_ldap/lib/wizard.php b/apps/user_ldap/lib/wizard.php
index 97f3002ccafd2d9409979bdb86ce43b12ebf4979..7bb5752352fbd72599df004f499be743957e406f 100644
--- a/apps/user_ldap/lib/wizard.php
+++ b/apps/user_ldap/lib/wizard.php
@@ -75,9 +75,11 @@ class Wizard extends LDAPUtility {
 
 	/**
 	 * counts entries in the LDAP directory
+	 *
 	 * @param string $filter the LDAP search filter
 	 * @param string $type a string being either 'users' or 'groups';
-	 * @return int|bool
+	 * @return bool|int
+	 * @throws \Exception
 	 */
 	public function countEntries($filter, $type) {
 		$reqs = array('ldapHost', 'ldapPort', 'ldapBase');
@@ -88,17 +90,36 @@ class Wizard extends LDAPUtility {
 			throw new \Exception('Requirements not met', 400);
 		}
 
+		$attr = array('dn'); // default
+		$limit = 1001;
 		if($type === 'groups') {
-			$result =  $this->access->countGroups($filter);
+			$result =  $this->access->countGroups($filter, $attr, $limit);
 		} else if($type === 'users') {
-			$result = $this->access->countUsers($filter);
+			$result = $this->access->countUsers($filter, $attr, $limit);
+		} else if ($type === 'objects') {
+			$result = $this->access->countObjects($limit);
 		} else {
-			throw new \Exception('internal error: invald object type', 500);
+			throw new \Exception('internal error: invalid object type', 500);
 		}
 
 		return $result;
 	}
 
+	/**
+	 * formats the return value of a count operation to the string to be
+	 * inserted.
+	 *
+	 * @param bool|int $count
+	 * @return int|string
+	 */
+	private function formatCountResult($count) {
+		$formatted = ($count !== false) ? $count : 0;
+		if($formatted > 1000) {
+			$formatted = '> 1000';
+		}
+		return $formatted;
+	}
+
 	public function countGroups() {
 		$filter = $this->configuration->ldapGroupFilter;
 
@@ -109,7 +130,7 @@ class Wizard extends LDAPUtility {
 		}
 
 		try {
-			$groupsTotal = $this->countEntries($filter, 'groups');
+			$groupsTotal = $this->formatCountResult($this->countEntries($filter, 'groups'));
 		} catch (\Exception $e) {
 			//400 can be ignored, 500 is forwarded
 			if($e->getCode() === 500) {
@@ -117,7 +138,6 @@ class Wizard extends LDAPUtility {
 			}
 			return false;
 		}
-		$groupsTotal = ($groupsTotal !== false) ? $groupsTotal : 0;
 		$output = self::$l->n('%s group found', '%s groups found', $groupsTotal, array($groupsTotal));
 		$this->result->addChange('ldap_group_count', $output);
 		return $this->result;
@@ -130,13 +150,28 @@ class Wizard extends LDAPUtility {
 	public function countUsers() {
 		$filter = $this->access->getFilterForUserCount();
 
-		$usersTotal = $this->countEntries($filter, 'users');
-		$usersTotal = ($usersTotal !== false) ? $usersTotal : 0;
+		$usersTotal = $this->formatCountResult($this->countEntries($filter, 'users'));
 		$output = self::$l->n('%s user found', '%s users found', $usersTotal, array($usersTotal));
 		$this->result->addChange('ldap_user_count', $output);
 		return $this->result;
 	}
 
+	/**
+	 * counts any objects in the currently set base dn
+	 *
+	 * @return WizardResult
+	 * @throws \Exception
+	 */
+	public function countInBaseDN() {
+		// we don't need to provide a filter in this case
+		$total = $this->countEntries(null, 'objects');
+		if($total === false) {
+			throw new \Exception('invalid results received');
+		}
+		$this->result->addChange('ldap_test_base', $total);
+		return $this->result;
+	}
+
 	/**
 	 * counts users with a specified attribute
 	 * @param string $attr
@@ -281,45 +316,6 @@ class Wizard extends LDAPUtility {
 		return $this->result;
 	}
 
-	/**
-	 * return the state of the Group Filter Mode
-	 * @return WizardResult
-	 */
-	public function getGroupFilterMode() {
-		$this->getFilterMode('ldapGroupFilterMode');
-		return $this->result;
-	}
-
-	/**
-	 * return the state of the Login Filter Mode
-	 * @return WizardResult
-	 */
-	public function getLoginFilterMode() {
-		$this->getFilterMode('ldapLoginFilterMode');
-		return $this->result;
-	}
-
-	/**
-	 * return the state of the User Filter Mode
-	 * @return WizardResult
-	 */
-	public function getUserFilterMode() {
-		$this->getFilterMode('ldapUserFilterMode');
-		return $this->result;
-	}
-
-	/**
-	 * return the state of the mode of the specified filter
-	 * @param string $confKey contains the access key of the Configuration
-	 */
-	private function getFilterMode($confKey) {
-		$mode = $this->configuration->$confKey;
-		if(is_null($mode)) {
-			$mode = $this->LFILTER_MODE_ASSISTED;
-		}
-		$this->result->addChange($confKey, $mode);
-	}
-
 	/**
 	 * detects the available LDAP attributes
 	 * @return array|false The instance's WizardResult instance
@@ -467,8 +463,7 @@ class Wizard extends LDAPUtility {
 			return false;
 		}
 		$this->configuration->setConfiguration(array('ldapGroupMemberAssocAttr' => $attribute));
-		//so it will be saved on destruct
-		$this->result->markChange();
+		$this->result->addChange('ldap_group_member_assoc_attribute', $attribute);
 
 		return $this->result;
 	}
@@ -603,6 +598,41 @@ class Wizard extends LDAPUtility {
 		return $this->result;
 	}
 
+	/**
+	 * @return bool|WizardResult
+	 * @param string $loginName
+	 * @throws \Exception
+	 */
+	public function testLoginName($loginName) {
+		if(!$this->checkRequirements(array('ldapHost',
+			'ldapPort',
+			'ldapBase',
+			'ldapLoginFilter',
+		))) {
+			return false;
+		}
+
+		$cr = $this->access->connection->getConnectionResource();
+		if(!$this->ldap->isResource($cr)) {
+			throw new \Exception('connection error');
+		}
+
+		if(mb_strpos($this->access->connection->ldapLoginFilter, '%uid', 0, 'UTF-8')
+			=== false) {
+			throw new \Exception('missing placeholder');
+		}
+
+		$users = $this->access->fetchUsersByLoginName($loginName);
+		if($this->ldap->errno($cr) !== 0) {
+			throw new \Exception($this->ldap->error($cr));
+		}
+		$filter = \OCP\Util::mb_str_replace(
+			'%uid', $loginName, $this->access->connection->ldapLoginFilter, 'UTF-8');
+		$this->result->addChange('ldap_test_loginname', count($users));
+		$this->result->addChange('ldap_test_effective_filter', $filter);
+		return $this->result;
+	}
+
 	/**
 	 * Tries to determine the port, requires given Host, User DN and Password
 	 * @return WizardResult|false WizardResult on success, false otherwise
@@ -674,10 +704,13 @@ class Wizard extends LDAPUtility {
 		}
 
 		$dparts = explode('.', $domain);
-		$base2 = implode('dc=', $dparts);
-		if($base !== $base2 && $this->testBaseDN($base2)) {
-			$this->applyFind('ldap_base', $base2);
-			return $this->result;
+		while(count($dparts) > 0) {
+			$base2 = 'dc=' . implode(',dc=', $dparts);
+			if ($base !== $base2 && $this->testBaseDN($base2)) {
+				$this->applyFind('ldap_base', $base2);
+				return $this->result;
+			}
+			array_shift($dparts);
 		}
 
 		return false;
@@ -720,7 +753,7 @@ class Wizard extends LDAPUtility {
 	 * @throws \Exception
 	 */
 	private function detectGroupMemberAssoc() {
-		$possibleAttrs = array('uniqueMember', 'memberUid', 'member', 'unfugasdfasdfdfa');
+		$possibleAttrs = array('uniqueMember', 'memberUid', 'member');
 		$filter = $this->configuration->ldapGroupFilter;
 		if(empty($filter)) {
 			return false;
@@ -730,7 +763,7 @@ class Wizard extends LDAPUtility {
 			throw new \Exception('Could not connect to LDAP');
 		}
 		$base = $this->configuration->ldapBase[0];
-		$rr = $this->ldap->search($cr, $base, $filter, $possibleAttrs);
+		$rr = $this->ldap->search($cr, $base, $filter, $possibleAttrs, 0, 1000);
 		if(!$this->ldap->isResource($rr)) {
 			return false;
 		}
@@ -1114,7 +1147,8 @@ class Wizard extends LDAPUtility {
 				//skip when the filter is a wildcard and results were found
 				continue;
 			}
-			$rr = $this->ldap->search($cr, $base, $filter, array($attr));
+			// 20k limit for performance and reason
+			$rr = $this->ldap->search($cr, $base, $filter, array($attr), 0, 20000);
 			if(!$this->ldap->isResource($rr)) {
 				continue;
 			}
diff --git a/apps/user_ldap/settings.php b/apps/user_ldap/settings.php
index 85bfbaa076ff11353f396134577d6ee064c2bb68..d17361cdfd5f33f0565b84fdf8494a5cae589638 100644
--- a/apps/user_ldap/settings.php
+++ b/apps/user_ldap/settings.php
@@ -29,13 +29,6 @@
 
 OC_Util::checkAdminUser();
 
-OCP\Util::addScript('user_ldap', 'ldapFilter');
-OCP\Util::addScript('user_ldap', 'experiencedAdmin');
-OCP\Util::addScript('user_ldap', 'settings');
-\OC_Util::addVendorScript('user_ldap', 'ui-multiselect/src/jquery.multiselect');
-OCP\Util::addStyle('user_ldap', 'settings');
-\OC_Util::addVendorStyle('user_ldap', 'ui-multiselect/jquery.multiselect');
-
 // fill template
 $tmpl = new OCP\Template('user_ldap', 'settings');
 
@@ -55,9 +48,9 @@ $l = \OC::$server->getL10N('user_ldap');
 
 $wizTabs = array();
 $wizTabs[] = array('tpl' => 'part.wizard-server',      'cap' => $l->t('Server'));
-$wizTabs[] = array('tpl' => 'part.wizard-userfilter',  'cap' => $l->t('User Filter'));
-$wizTabs[] = array('tpl' => 'part.wizard-loginfilter', 'cap' => $l->t('Login Filter'));
-$wizTabs[] = array('tpl' => 'part.wizard-groupfilter', 'cap' => $l->t('Group Filter'));
+$wizTabs[] = array('tpl' => 'part.wizard-userfilter',  'cap' => $l->t('Users'));
+$wizTabs[] = array('tpl' => 'part.wizard-loginfilter', 'cap' => $l->t('Login Attributes'));
+$wizTabs[] = array('tpl' => 'part.wizard-groupfilter', 'cap' => $l->t('Groups'));
 $wizTabsCount = count($wizTabs);
 for($i = 0; $i < $wizTabsCount; $i++) {
 	$tab = new OCP\Template('user_ldap', $wizTabs[$i]['tpl']);
diff --git a/apps/user_ldap/templates/part.settingcontrols.php b/apps/user_ldap/templates/part.settingcontrols.php
index e67cea41d9cc92234bd338decf959fdcff3f6317..bac00daa39f136c21272c4ad6704c5ff58f98181 100644
--- a/apps/user_ldap/templates/part.settingcontrols.php
+++ b/apps/user_ldap/templates/part.settingcontrols.php
@@ -1,5 +1,4 @@
 <div class="ldapSettingControls">
-	<input class="ldap_submit" value="<?php p($l->t('Save'));?>" type="submit">
 	<button type="button" class="ldap_action_test_connection" name="ldap_action_test_connection">
 		<?php p($l->t('Test Configuration'));?>
 	</button>
diff --git a/apps/user_ldap/templates/part.wizard-groupfilter.php b/apps/user_ldap/templates/part.wizard-groupfilter.php
index 1953d2eaa6e0836084a8b83a1407f44b89e7c136..bfcfd11521823238770b11205a72aa5496b1346f 100644
--- a/apps/user_ldap/templates/part.wizard-groupfilter.php
+++ b/apps/user_ldap/templates/part.wizard-groupfilter.php
@@ -5,31 +5,48 @@
 		</p>
 		<p>
 			<label for="ldap_groupfilter_objectclass">
-				<?php p($l->t('only those object classes:'));?>
+				<?php p($l->t('Only these object classes:'));?>
 			</label>
 
 			<select id="ldap_groupfilter_objectclass" multiple="multiple"
-			 name="ldap_groupfilter_objectclass">
+			 name="ldap_groupfilter_objectclass" class="multiSelectPlugin">
 			</select>
 		</p>
 		<p>
 			<label for="ldap_groupfilter_groups">
-				<?php p($l->t('only from those groups:'));?>
+				<?php p($l->t('Only from these groups:'));?>
 			</label>
 
+			<input type="text" class="ldapManyGroupsSupport ldapManyGroupsSearch hidden" placeholder="<?php p($l->t('Search groups'));?>" />
+
 			<select id="ldap_groupfilter_groups" multiple="multiple"
-			 name="ldap_groupfilter_groups">
+			 name="ldap_groupfilter_groups" class="multiSelectPlugin">
 			</select>
+
+		</p>
+		<p class="ldapManyGroupsSupport hidden">
+			<label></label>
+			<select class="ldapGroupList ldapGroupListAvailable" multiple="multiple"
+					title="<?php p($l->t('Available groups'));?>"></select>
+			<span>
+				<button class="ldapGroupListSelect" type="button">&gt;</button><br/>
+				<button class="ldapGroupListDeselect" type="button">&lt;</button>
+			</span>
+			<select class="ldapGroupList ldapGroupListSelected" multiple="multiple"
+					title="<?php p($l->t('Selected groups'));?>"></select>
 		</p>
 		<p>
-			<label><a id='toggleRawGroupFilter'>↓ <?php p($l->t('Edit raw filter instead'));?></a></label>
+			<label><a id='toggleRawGroupFilter' class='ldapToggle'>↓ <?php p($l->t('Edit LDAP Query'));?></a></label>
+		</p>
+		<p id="ldapReadOnlyGroupFilterContainer" class="hidden ldapReadOnlyFilterContainer">
+			<label><?php p($l->t('LDAP Filter:'));?></label>
+			<span class="ldapFilterReadOnlyElement ldapInputColElement"></span>
 		</p>
 		<p id="rawGroupFilterContainer" class="invisible">
-			<input type="text" id="ldap_group_filter" name="ldap_group_filter"
-			class="lwautosave"
-			placeholder="<?php p($l->t('Raw LDAP filter'));?>"
-			title="<?php p($l->t('The filter specifies which LDAP groups shall have access to the %s instance.', $theme->getName()));?>"
-			/>
+			<textarea type="text" id="ldap_group_filter" name="ldap_group_filter"
+					  placeholder="<?php p($l->t('Edit LDAP Query'));?>"
+					  title="<?php p($l->t('The filter specifies which LDAP groups shall have access to the %s instance.', $theme->getName()));?>">
+			</textarea>
 			<button class="ldapGetEntryCount hidden" name="ldapGetEntryCount" type="button">
 				<?php p($l->t('Test Filter'));?>
 			</button>
@@ -38,7 +55,10 @@
 			<div class="ldapWizardInfo invisible">&nbsp;</div>
 		</p>
 		<p class="ldap_count">
-			<span id="ldap_group_count">0 <?php p($l->t('groups found'));?></span>
+			<button class="ldapGetEntryCount ldapGetGroupCount" name="ldapGetEntryCount" type="button">
+				<?php p($l->t('Verify settings and count groups'));?>
+			</button>
+			<span id="ldap_group_count"></span>
 		</p>
 		<?php print_unescaped($_['wizardControls']); ?>
 	</div>
diff --git a/apps/user_ldap/templates/part.wizard-loginfilter.php b/apps/user_ldap/templates/part.wizard-loginfilter.php
index 3dde46fa97945542e1e2aa6885f939ad7ac7eb84..fa17a9b430b88c19f370e15590dc30061c798bbe 100644
--- a/apps/user_ldap/templates/part.wizard-loginfilter.php
+++ b/apps/user_ldap/templates/part.wizard-loginfilter.php
@@ -1,23 +1,25 @@
 <fieldset id="ldapWizard3">
 	<div>
 		<p>
-			<?php p($l->t('Users login with this attribute:'));?>
+			<?php p($l->t('When logging in, %s will find the user based on the following attributes:', $theme->getName()));?>
 		</p>
 		<p>
 			<label for="ldap_loginfilter_username">
-				<?php p($l->t('LDAP Username:'));?>
+				<?php p($l->t('LDAP / AD Username:'));?>
 			</label>
 
 			<input type="checkbox" id="ldap_loginfilter_username"
-			 name="ldap_loginfilter_username" value="1" class="lwautosave" />
+				   title="<?php p($l->t('Allows login against the LDAP / AD username, which is either uid or samaccountname and will be detected.'));?>"
+				   name="ldap_loginfilter_username" value="1" />
 		</p>
 		<p>
 			<label for="ldap_loginfilter_email">
-				<?php p($l->t('LDAP Email Address:'));?>
+				<?php p($l->t('LDAP / AD Email Address:'));?>
 			</label>
 
 			<input type="checkbox" id="ldap_loginfilter_email"
-			 name="ldap_loginfilter_email" value="1" class="lwautosave" />
+				   title="<?php p($l->t('Allows login against an email attribute. Mail and mailPrimaryAddress will be allowed.'));?>"
+				   name="ldap_loginfilter_email" value="1" />
 		</p>
 		<p>
 			<label for="ldap_loginfilter_attributes">
@@ -25,23 +27,35 @@
 			</label>
 
 			<select id="ldap_loginfilter_attributes" multiple="multiple"
-			 name="ldap_loginfilter_attributes">
+			 name="ldap_loginfilter_attributes" class="multiSelectPlugin">
 			</select>
 		</p>
 		<p>
-			<label><a id='toggleRawLoginFilter'>↓ <?php p($l->t('Edit raw filter instead'));?></a></label>
+			<label><a id='toggleRawLoginFilter' class='ldapToggle'>↓ <?php p($l->t('Edit LDAP Query'));?></a></label>
+		</p>
+		<p id="ldapReadOnlyLoginFilterContainer" class="hidden ldapReadOnlyFilterContainer">
+			<label><?php p($l->t('LDAP Filter:'));?></label>
+			<span class="ldapFilterReadOnlyElement ldapInputColElement"></span>
 		</p>
 		<p id="rawLoginFilterContainer" class="invisible">
-			<input type="text" id="ldap_login_filter" name="ldap_login_filter"
-				class="lwautosave"
-				placeholder="<?php p($l->t('Raw LDAP filter'));?>"
-				title="<?php p($l->t('Defines the filter to apply, when login is attempted. %%uid replaces the username in the login action. Example: "uid=%%uid"'));?>"
-			/>
+			<textarea type="text" id="ldap_login_filter" name="ldap_login_filter"
+				class="ldapFilterInputElement"
+				placeholder="<?php p($l->t('Edit LDAP Query'));?>"
+				title="<?php p($l->t('Defines the filter to apply, when login is attempted. %%uid replaces the username in the login action. Example: "uid=%%uid"'));?>">
+			</textarea>
 		</p>
 		<p>
 			<div class="ldapWizardInfo invisible">&nbsp;</div>
 		</p>
-
+		<p class="ldap_verify">
+			<input type="text" id="ldap_test_loginname" name="ldap_test_loginname"
+				   placeholder="<?php p($l->t('Test Loginname'));?>"
+				   class="ldapVerifyInput"
+				   title="Attempts to receive a DN for the given loginname and the current login filter"/>
+			<button class="ldapVerifyLoginName" name="ldapTestLoginSettings" type="button">
+				<?php p($l->t('Verify settings'));?>
+			</button>
+		</p>
 		<?php print_unescaped($_['wizardControls']); ?>
 	</div>
 </fieldset>
\ No newline at end of file
diff --git a/apps/user_ldap/templates/part.wizard-server.php b/apps/user_ldap/templates/part.wizard-server.php
index c1744143f98b0a006a6abb4b0293f34cebbdb131..3ce912fac4a2b3c889edf96a0f966acfcc65a737 100644
--- a/apps/user_ldap/templates/part.wizard-server.php
+++ b/apps/user_ldap/templates/part.wizard-server.php
@@ -22,32 +22,41 @@
 			}
 		}
 		?>
-		<option value="NEW"><?php p($l->t('Add Server Configuration'));?></option>
 		</select>
+		<button type="button" id="ldap_action_add_configuration"
+			name="ldap_action_add_configuration" class="icon-add"
+			title="Adds a new and blank configuration">&nbsp;</button>
+		<button type="button" id="ldap_action_copy_configuration"
+			name="ldap_action_copy_configuration"
+			class="ldapIconCopy icon-default-style"
+			title="Copy current configuration into new directory binding">&nbsp;</button>
 		<button type="button" id="ldap_action_delete_configuration"
-			name="ldap_action_delete_configuration"><?php p($l->t('Delete Configuration'));?></button>
+			name="ldap_action_delete_configuration" class="icon-delete"
+			title="Delete the current configuration">&nbsp;</button>
 		</p>
 
 		<div class="hostPortCombinator">
 			<div class="tablerow">
 				<div class="tablecell">
 					<div class="table">
-						<input type="text" class="host tablecell lwautosave" id="ldap_host"
+						<input type="text" class="host" id="ldap_host"
 							name="ldap_host"
 							placeholder="<?php p($l->t('Host'));?>"
 							title="<?php p($l->t('You can omit the protocol, except you require SSL. Then start with ldaps://'));?>"
 							/>
 						<span>
 							<input type="number" id="ldap_port" name="ldap_port"
-								class="lwautosave"
 								placeholder="<?php p($l->t('Port'));?>" />
+							<button class="ldapDetectPort" name="ldapDetectPort" type="button">
+								<?php p($l->t('Detect Port'));?>
+							</button>
 						</span>
 					</div>
 				</div>
 			</div>
 			<div class="tablerow">
 				<input type="text" id="ldap_dn" name="ldap_dn"
-				class="tablecell lwautosave"
+				class="tablecell"
 				placeholder="<?php p($l->t('User DN'));?>" autocomplete="off"
 				title="<?php p($l->t('The DN of the client user with which the bind shall be done, e.g. uid=agent,dc=example,dc=com. For anonymous access, leave DN and Password empty.'));?>"
 				/>
@@ -55,7 +64,7 @@
 
 			<div class="tablerow">
 				<input type="password" id="ldap_agent_password"
-				class="tablecell lwautosave" name="ldap_agent_password"
+				class="tablecell" name="ldap_agent_password"
 				placeholder="<?php p($l->t('Password'));?>" autocomplete="off"
 				title="<?php p($l->t('For anonymous access, leave DN and Password empty.'));?>"
 				/>
@@ -63,15 +72,21 @@
 
 			<div class="tablerow">
 				<textarea id="ldap_base" name="ldap_base"
-					class="tablecell lwautosave"
+					class="tablecell"
 					placeholder="<?php p($l->t('One Base DN per line'));?>"
 					title="<?php p($l->t('You can specify Base DN for users and groups in the Advanced tab'));?>">
 				</textarea>
+				<button class="ldapDetectBase" name="ldapDetectBase" type="button">
+					<?php p($l->t('Detect Base DN'));?>
+				</button>
+				<button class="ldapTestBase" name="ldapTestBase" type="button">
+					<?php p($l->t('Test Base DN'));?>
+				</button>
 			</div>
 
 			<div class="tablerow left">
 				<input type="checkbox" id="ldap_experienced_admin" value="1"
-					name="ldap_experienced_admin" class="tablecell lwautosave"
+					name="ldap_experienced_admin" class="tablecell"
 					title="<?php p($l->t('Avoids automatic LDAP requests. Better for bigger setups, but requires some LDAP knowledge.'));?>"
 					/>
 				<label for="ldap_experienced_admin" class="tablecell">
diff --git a/apps/user_ldap/templates/part.wizard-userfilter.php b/apps/user_ldap/templates/part.wizard-userfilter.php
index 99a6e75370b4a4443d9bedf0cb3315ca5e945b92..691c41a66a6d7252482d04c7628f06dabb0af64d 100644
--- a/apps/user_ldap/templates/part.wizard-userfilter.php
+++ b/apps/user_ldap/templates/part.wizard-userfilter.php
@@ -5,40 +5,61 @@
 		</p>
 		<p>
 			<label for="ldap_userfilter_objectclass">
-				<?php p($l->t('only those object classes:'));?>
+				<?php p($l->t('Only these object classes:'));?>
 			</label>
 
 			<select id="ldap_userfilter_objectclass" multiple="multiple"
-			 name="ldap_userfilter_objectclass">
+			 name="ldap_userfilter_objectclass" class="multiSelectPlugin">
 			</select>
 		</p>
+		<p>
+			<label></label>
+			<span class="ldapInputColElement"><?php p($l->t('The most common object classes for users are organizationalPerson, person, user, and inetOrgPerson. If you are not sure which object class to select, please consult your directory admin.'));?></span>
+		</p>
 		<p>
 			<label for="ldap_userfilter_groups">
-				<?php p($l->t('only from those groups:'));?>
+				<?php p($l->t('Only from these groups:'));?>
 			</label>
 
+			<input type="text" class="ldapManyGroupsSupport ldapManyGroupsSearch hidden" placeholder="<?php p($l->t('Search groups'));?>" />
+
 			<select id="ldap_userfilter_groups" multiple="multiple"
-			 name="ldap_userfilter_groups">
+			 name="ldap_userfilter_groups" class="multiSelectPlugin">
 			</select>
 		</p>
+		<p class="ldapManyGroupsSupport hidden">
+			<label></label>
+			<select class="ldapGroupList ldapGroupListAvailable" multiple="multiple"
+					title="<?php p($l->t('Available groups'));?>"></select>
+			<span>
+				<button class="ldapGroupListSelect" type="button">&gt;</button><br/>
+				<button class="ldapGroupListDeselect" type="button">&lt;</button>
+			</span>
+			<select class="ldapGroupList ldapGroupListSelected" multiple="multiple"
+					title="<?php p($l->t('Selected groups'));?>"></select>
+		</p>
 		<p>
-			<label><a id='toggleRawUserFilter'>↓ <?php p($l->t('Edit raw filter instead'));?></a></label>
-		</p>
-		<p id="rawUserFilterContainer" class="invisible">
-			<input type="text" id="ldap_userlist_filter" name="ldap_userlist_filter"
-			class="lwautosave"
-			placeholder="<?php p($l->t('Raw LDAP filter'));?>"
-			title="<?php p($l->t('The filter specifies which LDAP users shall have access to the %s instance.', $theme->getName()));?>"
-			/>
-			<button class="ldapGetEntryCount hidden" name="ldapGetEntryCount" type="button">
-				<?php p($l->t('Test Filter'));?>
-			</button>
+			<label><a id='toggleRawUserFilter' class='ldapToggle'>↓ <?php p($l->t('Edit LDAP Query'));?></a></label>
+		</p>
+		<p id="ldapReadOnlyUserFilterContainer" class="hidden ldapReadOnlyFilterContainer">
+			<label><?php p($l->t('LDAP Filter:'));?></label>
+			<span class="ldapFilterReadOnlyElement ldapInputColElement"></span>
+		</p>
+		<p id="rawUserFilterContainer">
+			<textarea type="text" id="ldap_userlist_filter" name="ldap_userlist_filter"
+				class="ldapFilterInputElement"
+				placeholder="<?php p($l->t('Edit LDAP Query'));?>"
+				title="<?php p($l->t('The filter specifies which LDAP users shall have access to the %s instance.', $theme->getName()));?>">
+			</textarea>
 		</p>
 		<p>
 			<div class="ldapWizardInfo invisible">&nbsp;</div>
 		</p>
 		<p class="ldap_count">
-			<span id="ldap_user_count">0 <?php p($l->t('users found'));?></span>
+			<button class="ldapGetEntryCount ldapGetUserCount" name="ldapGetEntryCount" type="button">
+				<?php p($l->t('Verify settings and count users'));?>
+			</button>
+			<span id="ldap_user_count"></span>
 		</p>
 		<?php print_unescaped($_['wizardControls']); ?>
 	</div>
diff --git a/apps/user_ldap/templates/settings.php b/apps/user_ldap/templates/settings.php
index 6aa2183726b6c08ff9b84f1a1dd4eedf4ae7611b..f40eba005d814a7afc51e065795524bc65963e80 100644
--- a/apps/user_ldap/templates/settings.php
+++ b/apps/user_ldap/templates/settings.php
@@ -1,3 +1,56 @@
+<?php
+
+vendor_script('user_ldap', 'ui-multiselect/src/jquery.multiselect');
+
+vendor_style('user_ldap', 'ui-multiselect/jquery.multiselect');
+
+script('user_ldap', [
+	'wizard/controller',
+	'wizard/configModel',
+	'wizard/view',
+	'wizard/wizardObject',
+	'wizard/wizardTabGeneric',
+	'wizard/wizardTabElementary',
+	'wizard/wizardTabAbstractFilter',
+	'wizard/wizardTabUserFilter',
+	'wizard/wizardTabLoginFilter',
+	'wizard/wizardTabGroupFilter',
+	'wizard/wizardTabAdvanced',
+	'wizard/wizardTabExpert',
+	'wizard/wizardDetectorQueue',
+	'wizard/wizardDetectorGeneric',
+	'wizard/wizardDetectorPort',
+	'wizard/wizardDetectorBaseDN',
+	'wizard/wizardDetectorFeatureAbstract',
+	'wizard/wizardDetectorUserObjectClasses',
+	'wizard/wizardDetectorGroupObjectClasses',
+	'wizard/wizardDetectorGroupsForUsers',
+	'wizard/wizardDetectorGroupsForGroups',
+	'wizard/wizardDetectorSimpleRequestAbstract',
+	'wizard/wizardDetectorFilterUser',
+	'wizard/wizardDetectorFilterLogin',
+	'wizard/wizardDetectorFilterGroup',
+	'wizard/wizardDetectorUserCount',
+	'wizard/wizardDetectorGroupCount',
+	'wizard/wizardDetectorEmailAttribute',
+	'wizard/wizardDetectorUserDisplayNameAttribute',
+	'wizard/wizardDetectorUserGroupAssociation',
+	'wizard/wizardDetectorAvailableAttributes',
+	'wizard/wizardDetectorTestAbstract',
+	'wizard/wizardDetectorTestLoginName',
+	'wizard/wizardDetectorTestBaseDN',
+	'wizard/wizardDetectorTestConfiguration',
+	'wizard/wizardDetectorClearUserMappings',
+	'wizard/wizardDetectorClearGroupMappings',
+	'wizard/wizardFilterOnType',
+	'wizard/wizardFilterOnTypeFactory',
+	'wizard/wizard'
+]);
+
+style('user_ldap', 'settings');
+
+?>
+
 <form id="ldap" class="section" action="#" method="post">
 	<h2><?php p($l->t('LDAP')); ?></h2>
 
@@ -65,5 +118,6 @@
 		<?php print_unescaped($_['settingControls']); ?>
 	</fieldset>
 	</div>
-
+	<!-- Spinner Template -->
+	<img class="ldapSpinner hidden" src="<?php p(\OCP\Util::imagePath('core', 'loading.gif')); ?>">
 </form>
diff --git a/apps/user_ldap/tests/user_ldap.php b/apps/user_ldap/tests/user_ldap.php
index fa3afe9c511fbe76b6658d81dc738bd6afeca0a7..b9beed1d35a9848ff913ecd3cdca1aac3c0dfe69 100644
--- a/apps/user_ldap/tests/user_ldap.php
+++ b/apps/user_ldap/tests/user_ldap.php
@@ -108,12 +108,6 @@ class Test_User_Ldap_Direct extends \Test\TestCase {
 	 * @return void
 	 */
 	private function prepareAccessForCheckPassword(&$access, $noDisplayName = false) {
-		$access->expects($this->once())
-			   ->method('escapeFilterPart')
-			   ->will($this->returnCallback(function($uid) {
-				   return $uid;
-			   }));
-
 		$access->connection->expects($this->any())
 			   ->method('__get')
 			   ->will($this->returnCallback(function($name) {
@@ -132,6 +126,15 @@ class Test_User_Ldap_Direct extends \Test\TestCase {
 					return array();
 			   }));
 
+		$access->expects($this->any())
+			->method('fetchUsersByLoginName')
+			->will($this->returnCallback(function($uid) {
+				if($uid === 'roland') {
+					return array(array('dn' => 'dnOfRoland,dc=test'));
+				}
+				return array();
+			}));
+
 		$retVal = 'gunslinger';
 		if($noDisplayName === true) {
 			$retVal = false;
diff --git a/apps/user_ldap/user_ldap.php b/apps/user_ldap/user_ldap.php
index ea1371c14d3623acb69e8cb66b23aa1fbeb97269..54e14c093f393322638795d4c64327ae3586118f 100644
--- a/apps/user_ldap/user_ldap.php
+++ b/apps/user_ldap/user_ldap.php
@@ -79,14 +79,10 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn
 	 * Check if the password is correct without logging in the user
 	 */
 	public function checkPassword($uid, $password) {
-		$uid = $this->access->escapeFilterPart($uid);
-
 		//find out dn of the user name
 		$attrs = array($this->access->connection->ldapUserDisplayName, 'dn',
 			'uid', 'samaccountname');
-		$filter = \OCP\Util::mb_str_replace(
-			'%uid', $uid, $this->access->connection->ldapLoginFilter, 'UTF-8');
-		$users = $this->access->fetchListOfUsers($filter, $attrs);
+		$users = $this->access->fetchUsersByLoginName($uid, $attrs);
 		if(count($users) < 1) {
 			return false;
 		}