/**
 * File:	include/ldap/ui.ycp
 * Package:	Configuration of LDAP
 * Summary:	User interface functions.
 * Authors:	Thorsten Kukuk <kukuk@suse.de>
 *		Anas Nashif <nashif@suse.de>
 *
 * $Id: ui.ycp 22200 2005-03-03 11:50:15Z jsuchome $
 *
 * All user interface functions.
 */

{
    textdomain "ldap-client";

    import "Autologin";
    import "Label";
    import "Ldap";
    import "LdapPopup";
    import "Message";
    import "Mode";
    import "Package";
    import "Popup";
    import "Report";
    import "Service";
    import "SLP";
    import "Stage";
    import "Wizard";

    include "ldap/routines.ycp";

    define boolean Modified () ``{

	return (Ldap::modified || Ldap::ldap_modified);
    }

    /**
     * The dialog that appears when the [Abort] button is pressed.
     * @return `abort if user really wants to abort, `back otherwise
     */
    define symbol ReallyAbort () ``{

	boolean ret = true;
	if (!Stage::cont () || contains (WFM::Args (), "from_users"))
	{
	    ret = Popup::ReallyAbort (Modified());;
	}
	else
	{
	    ret = Popup::ConfirmAbort (`incomplete);
	}

	if ( ret )	return `abort;
	else		return `back;
    }

    /**
     * Read settings dialog
     * @return `abort if aborted and `next otherwise
     */
    define symbol ReadDialog () ``{
	boolean ret = Ldap::Read();
        return ret ? `next : `abort;
    }

    /**
     * Write settings dialog
     * @return `next
     */
    define symbol WriteDialog() ``{

	// popup text
	block<boolean> abort = ``{
            if (UI::PollInput () == `abort &&
		// popup text
		Popup::YesNo (_("Really abort the writing process?")))
		return true;
	    return false;
	};

	if (Modified())
	{
	    // help text
	    Wizard::RestoreHelp(_("Writing LDAP Client Settings"));
	    return Ldap::Write (abort);
	}
	return `next;
    }


    /**
     * Select from LDAP servers provided by SLP
     */
    define string BrowseServers () {

	string servers	= "";

	UI::OpenDialog (`VBox(
	    `HSpacing (36),
	    `ReplacePoint (`id (`rp),
		`MultiSelectionBox (`id(`sel),
		// multiselection box label
		_("LDAP &Servers Provided by SLP:"), [])
	    ),
	    `HBox (
		`PushButton (`id(`ok), Label::OKButton()),
		`PushButton (`id(`cancel), Label::CancelButton())
	    )
	));
	UI::BusyCursor ();

	list items	= [];
	foreach (map service, SLP::FindSrvs ("service:ldap", ""), {
	    string s	= service["pcHost"]:service["srvurl"]:"";
	    items	= add (items, s);
	});
	UI::ReplaceWidget (`id(`rp),`MultiSelectionBox (`id(`sel),
	    // selection box label
	     _("LDAP &Servers Provided by SLP:"),items)
	);

	UI::NormalCursor ();
	any ret = "";
	do
	{
	    ret		= UI::UserInput ();
	    servers	= mergestring (
		(list<string>)UI::QueryWidget (`id (`sel),`SelectedItems), " ");

	} while (ret != `ok && ret != `cancel);

	UI::CloseDialog ();
	return ret == `ok ? servers : "";
    }

    /**
     * The main dialog for ldap-client configuration
     * @return	`back, `next or `abort
     */
    define symbol LdapDialog () {

	// help text 1/9
	string help_text = _("<p>Here, your machine can be set up as an
<b>LDAP client</b>.</p>") +

	// help text 2/9
	_("<p>To authenticate your users with an OpenLDAP server, select <b>Use LDAP</b>. NSS and PAM will be configured accordingly.</p>") +

	// help text 3/9
	_("<p>To deactivate LDAP services, click <b>Do Not Use LDAP</b>.
If you deactivate LDAP, the current LDAP entry for passwd in /etc/nsswitch.conf
will be removed. The PAM configuration will be modified and the LDAP entry
removed.</p>") +


	// help text 4/9
	_("<p>Enter the LDAP server's <b>address</b> (e.g., ldap.example.com or 10.20.0.2) in the first entry and <b>Distinguished Name</b> of the search base (\"base DN\", such as dc=example,dc=com) in the second. Specify multiple servers
by separating their addresses with spaces. It must be possible to resolve the
addresses without using LDAP. You can also specify the port on which the server is running using the syntax \"server:port\", for example, ldap.example.com:379.
</p>
") +

	// help text 5/9
	_("<p>With <b>Find</b>, select the LDAP server from the list provided by the service location protocol (SLP). Using <b>Fetch DN</b>, read the base DN from server.</p>") +

	// help text 6/9
	_("<p>Some LDAP servers support StartTLS [RFC2830].
If your server supports it and it is configured, activate <b>LDAP TLS/SSL</b>
to encrypt your communication with the LDAP server.</p>") +

	// help text 7/9
	_("<p>Normally, the LDAP version 3 protocol will be used. If you have
an LDAP server using protocol 2 (for example, OpenLDAP v1), activate
<b>LDAP Version 2</b>.</p>
") +

	// help text 8/9
	_("<p>For configuration of advanced LDAP settings, click
<b>Advanced Configuration</b>.</p>
");
	// help text 9/9 (additional)
	string autofs_help_text = _("<p><b>Automounter</b> is a daemon that automatically mounts directories,
such as users' home directories.
It is assumed that its configuration files (auto.*) already exist
locally or over LDAP.
If it is not installed and you want to use it, it is installed
automatically.</p>
");


	// during installation, starting ldap is default
	boolean start		= Ldap::start;
	if (Stage::cont () && !contains (WFM::Args (), "from_users"))
	    start		= true;

	string domain		= Ldap::GetDomain ();
	string server		= Ldap::server;
	boolean ldap_tls	= Ldap::ldap_tls;
	boolean ldap_v2		= Ldap::ldap_v2;

	boolean autofs		= Ldap::_start_autofs;
	term autofs_con		= `Empty ();
	if (Ldap::_autofs_allowed)
	{
	    autofs_con		= `VBox (
		    `VSpacing (0.5),
		    // check box label
		    `CheckBox (`id(`autofs), _("Start Auto&mounter"), autofs)
	    );
	    help_text	= help_text + autofs_help_text;
	}

	term con = `HBox (`HSpacing (3), `VBox (
	    `VSpacing (0.5),
	    `Frame (_("User Authentication"), `HBox (`HSpacing (0.5), `VBox (
		`VSpacing (0.5),
		`RadioButtonGroup (`id(`rd),
		    `Left(`HVSquash(`VBox (
			`Left (`RadioButton(`id(`ldapno), `opt (`notify),
			    // radio button label
			    _("Do N&ot Use LDAP"), !start)),
			`Left(`RadioButton(`id(`ldapyes), `opt (`notify),
			    // radio button label
			    _("&Use LDAP"), start)))))
		),
		`VSpacing (0.5)
	    ))),
	    `VSpacing (0.5),
	    // frame label
	    `Frame (_("LDAP client"), `HBox (`HSpacing (0.5), `VBox(
		`VSpacing (0.5),
		`HBox (
		    // text entry label
		    `TextEntry (`id (`ldapa), _("Addresses of LDAP &Servers"),
			server),
		    `VBox (
			`Label (""),
			// push button label
			`PushButton (`id(`slp), _("F&ind"))
		    )
		),
		`HBox (
		    // text entry label
		    `TextEntry (`id (`ldapd), _("LDAP base &DN"), domain),
		    `VBox (
			`Label (""),
			// push button label
			`PushButton (`id(`fetch), _("F&etch DN"))
		    )
		),
		`VSpacing (0.2),
		// check box label
		`Left (`CheckBox (`id(`ldaps), _("LDAP &TLS/SSL"), ldap_tls)),
		`VSpacing (0.2),
		// check box label
		`Left (`CheckBox (`id(`ldapv), _("LDAP &Version 2"), ldap_v2)),
		`VSpacing (0.5)
	    ), `HSpacing (0.5))),
	    autofs_con,
	    `VSpacing(0.5),
	    // pushbutton label
	    `PushButton (`id(`advanced), _("&Advanced Configuration..."))
        ), `HSpacing (3));

	Wizard::SetContentsButtons (
	    // dialog title
	    _("LDAP Client Configuration"), con, help_text,
	    Label::BackButton(),
	    Stage::cont () ? Label::NextButton(): Label::FinishButton());

	Wizard::RestoreAbortButton ();

	// during installation, only enabling makes sence
	if (Stage::cont () && !contains (WFM::Args (), "from_users"))
	{
	    UI::ChangeWidget (`id (`ldapno), `Enabled, false);
	}

	symbol result = `not_next;
	do {
	    result = (symbol) UI::UserInput ();

	    start = (UI::QueryWidget(`id(`rd), `CurrentButton) == `ldapyes);

	    server	= (string) UI::QueryWidget(`id(`ldapa), `Value);
	    ldap_v2	= (boolean) UI::QueryWidget(`id(`ldapv), `Value);
	    ldap_tls	= (boolean) UI::QueryWidget(`id(`ldaps), `Value);

	    if (result == `slp)
	    {
		string srv = BrowseServers ();
		if (srv != "")
		    UI::ChangeWidget (`id(`ldapa), `Value, srv);
	    }
	    if (result == `fetch)
	    {
		string dn = LdapPopup::InitAndBrowseTree ("", $[
		    "hostname"	: Ldap::GetFirstServer (server),
		    "port"	: Ldap::GetFirstPort (server),
		    "version"	: ldap_v2 ? 2 : 3,
		    "use_tls"	: ldap_tls ? "yes" : "no"
		]);
		if (dn != "")
		    UI::ChangeWidget (`id(`ldapd), `Value, dn);
	    }

	    if (result == `next || result == `advanced) {
		domain	= (string) UI::QueryWidget(`id(`ldapd), `Value);
		autofs	= Ldap::_autofs_allowed &&
		    (boolean) UI::QueryWidget (`id (`autofs), `Value);

		if (domain == "")
		{
		    // error popup label
                    Report::Error(_("Enter an LDAP base DN."));
                    result = `not_next;
                    continue;
                }

		if (server == "")
		{
		    // error popup label
		    Report::Error(_("Enter at least one address of an LDAP server."));
		    result = `not_next;
		    continue;
		}
	    }
	    if ((result == `abort || result == `cancel) &&
		ReallyAbort () != `abort)
	    {
		result = `not_next;
	    }
	    if (result == `next && (start || autofs))
	    {
		if (start && !Ldap::start && Ldap::nis_available)
		{
		    // popup question: user enabled LDAP now, but probably has
		    // enabled NIS client before
		    if (!Popup::YesNo (_("When you configure your machine as an LDAP client,
you cannot retrieve data with NIS. Are you sure?
")))
		    {
			result = `not_next;
			continue;
		    }
		}

		if (start && Ldap::krb_first && Ldap::krb_authentication == nil)
		{
		    any ret = Popup::AnyQuestion3 (
			Popup::NoHeadline(),
			// popup question
_("An active configuration of the Kerberos client is present.
Which should be used as the primary authentication service?
"),
			_("&LDAP"),
			_("&Kerberos"),
			Label::CancelButton (),
			`focus_yes
		    );
		    if (ret == `retry)
		    {
			result = `not_next;
			continue;
		    }
		    else
		    {
			Ldap::modified	= true;
			Ldap::krb_authentication = (ret == `no);
		    }
		}

		list<string> needed_packages =
		    Ldap::UpdatedArchPackages (["pam_ldap", "nss_ldap"]);

		if (start && !Package::InstalledAll (needed_packages))
		{
		    Ldap::required_packages = (list<string>) union (
			Ldap::required_packages, needed_packages);
		}

		if (autofs && !Package::InstalledAny(["autofs", "autofs4"]))
		{
		    Ldap::required_packages = (list<string>) union (
			Ldap::required_packages, ["autofs4"]);
		}
		if (!Package::InstallAll(Ldap::required_packages))
		{
		    if (start && !Package::InstalledAll (needed_packages))
		    {
			Popup::Error (Message::FailedToInstallPackages ());
			start = false;
			result = `not_next;
			UI::ChangeWidget(`id(`rd), `CurrentButton, `ldapno);
			continue;
		    }
		}
	    }
	} while (!contains ([`back, `next, `cancel, `abort, `advanced], result));

	if (result == `next || result == `advanced)
	{
	    if (Ldap::start != start || Ldap::GetDomain() != domain ||
		Ldap::server != server || Ldap::ldap_v2 != ldap_v2 ||
		Ldap::ldap_tls != ldap_tls || Ldap::_start_autofs != autofs)
	    {
		if (result == `next)
		{
		    if (start && !Ldap::start && !Mode::config())
		    {
			Autologin::AskForDisabling (
			    // popup text
			    _("Now you have enabled LDAP."));

			string message	=
			// message popup, part 1/2
_("This change only affects newly created processes and not already
running services. Restart your services manually or reboot 
the machine to enable it for all services.
");

			if (Service::Status ("sshd") == 0)
			{
			    Ldap::RestartSSHD (true);
			    // message popup, part 1/2
			    message = message + _("
To enable remote login for LDAP users, 'sshd' will be
restarted automatically by YaST.
");
			}
			Popup::Message (message);
		    }
		}
		Ldap::SetDomain (domain);
		Ldap::start	= start;
		Ldap::server	= server;
		Ldap::ldap_v2	= ldap_v2;
		Ldap::ldap_tls	= ldap_tls;
		Ldap::_start_autofs = autofs;
		Ldap::modified = true;
	    }
	}
	return result;
    }

/**
 * Configuration of advanced settings (how to get to config data on server)
 */
define symbol AdvancedConfigurationDialog () {

    string help_text =

    // help text caption 1
     _("<p><b>Users and Groups</b></p>") +

    // help text 1/3
    _("<p><b>File Server</b> indicates that home directories of users are
stored on this machine. Changing this value does not cause any direct action.
It is only information for the YaST Users module, which can manage user home
directories.</p>
")	+

    // help text 2/3
    _("<p>Set whether LDAP users are allowed to log in to this machine.
with <b>Enable LDAP Users to Log In</b>.</p>") +

    // help text 3/3, %1 is attribute name
    sformat (_("<p>Set the type of LDAP groups you are using.
The default value for <b>Group Member Attribute</b> is <i>%1</i>.</p>"),
    "member") +

    // help text caption 2
     _("<p><b>Access to Server</b></p>") +

    // help text 1/4
    _("<p>First, set <b>Configuration Base DN</b>.
It is the base for storing your configuration data, which is saved on the LDAP
server.</p>
") +

    // help text 2/4
    _("<p>To access the data stored on the server, enter the
<b>Administrator DN</b> (e.g., cn=Administrator,dc=mydomain,dc=com).</p>
") +

    // help text 3/4
    _("<p>To create the default configuration objects for LDAP users and groups,
check <b>Create Default Configuration Objects</b>. The objects are only created when they do not already exist.</p>
") +

    // help text 4/4
    _("<p>Press <b>Configure</b> to configure settings stored on the
LDAP server. You will be asked for the password if you are not connected yet or
have changed your configuration.</p>
");



    string bind_dn		= Ldap::bind_dn;
    boolean file_server		= Ldap::file_server;
    boolean login_enabled	= Ldap::login_enabled;
    string member_attribute	= Ldap::member_attribute;
    string base_config_dn	= Ldap::GetMainConfigDN();
    boolean create_ldap		= Ldap::create_ldap;

    list<term>member_attributes	= [
	`item (`id("member"), "member", member_attribute == "member"),
	`item (`id("uniquemember"), "uniquemember", member_attribute == "uniquemember")
    ];
    if (member_attribute != "member" && member_attribute != "uniquemember")
    {
	member_attributes = add (member_attributes,
	    `item (`id(member_attribute), member_attribute, true));
    }

    // propose some good default
    if (base_config_dn == "")
	base_config_dn = sformat ("ou=ldapconfig,%1", Ldap::GetDomain ());

    term contents = `HBox(`HSpacing (5), `VBox(
	// frame label
	`Frame (_("User and Group Settings"), `HBox(
	    `HSpacing (1), `VBox(
		`VSpacing(0.5),
		`Left(`CheckBox (`id(`file_server),
		    // checkbox label
		    _("F&ile Server (home directories are stored here)"),
			file_server)),
		`VSpacing(0.5),
		`Left(`CheckBox (`id(`login_enabled),
		    // checkbox label
		    _("&Enable LDAP Users to Log In"), login_enabled)),
		`VSpacing(0.5),
		`ComboBox (`id (`group_style), `opt (`notify, `hstretch),
		    // combobox label
		    _("&Group Member Attribute"), member_attributes),
		`VSpacing(0.5)
	    ),
	    `HSpacing (1)
	)),
        `VSpacing(),
	// frame label
	`Frame (_("Access to LDAP Server"), `HBox(
	    `HSpacing (1), `VBox(
		`VSpacing(0.5),
		`HBox (
		    `TextEntry (`id (`base_config_dn),
			// textentry label
			_("Configuration &Base DN"), base_config_dn),
		    `VBox (
			`Label (""),
			// button label
			`PushButton (`id(`br), `opt(`key_F6), _("Bro&wse"))
		    )
		),
		`VSpacing(0.5),
		`TextEntry (`id (`bind_dn),
		    // textentry label
		    _("Administrator &DN"), bind_dn),
		`VSpacing(0.5),
		`Left (`CheckBox (`id(`create_ldap),
		    // checkbox label
		    _("Crea&te Default Configuration Objects"), create_ldap)),
		`VSpacing(0.5),
		`Right (`PushButton (`id(`configure),
		    // pushbutton label
		    _("Configure User Management &Settings..."))),
		`VSpacing(0.5)
	    ),
	    `HSpacing (1)
	))
	),
	`HSpacing (5));

    // dialog label
    Wizard::SetContentsButtons(_("Advanced Configuration"),
            contents, help_text, Label::CancelButton(), Label::AcceptButton());
    Wizard::HideAbortButton ();

    symbol result = `notnext;

    // in autoyast-config mode, don't attach to server...
    if (Mode::config ())
	UI::ChangeWidget (`id(`configure), `Enabled, false);

    if (!Ldap::start)
	UI::ChangeWidget (`id(`login_enabled), `Enabled, false);

    while (true)
    {
	result = (symbol) UI::UserInput ();
	if (result == `cancel && ReallyAbort () != `abort)
	    result = `not_next;
	if (result == `back || result == `cancel)
	    break;

	if (result == `br)
	{
	    string dn = LdapPopup::InitAndBrowseTree (Ldap::GetDomain (), $[
		"hostname"	: Ldap::GetFirstServer (Ldap::server),
		"port"		: Ldap::GetFirstPort (Ldap::server),
		"version"	: Ldap::ldap_v2 ? 2 : 3,
		"use_tls"	: Ldap::ldap_tls ? "yes" : "no"
	    ]);
	    if (dn != "")
	    {
		UI::ChangeWidget (`id(`base_config_dn), `Value, dn);
	    }
	}
	if (result == `next || result == `configure)
	{
	    bind_dn = (string) UI::QueryWidget(`id(`bind_dn), `Value);
	    base_config_dn=(string)UI::QueryWidget(`id(`base_config_dn),`Value);
	    file_server = (boolean)UI::QueryWidget(`id(`file_server), `Value);
	    login_enabled =(boolean)UI::QueryWidget(`id(`login_enabled),`Value);
	    member_attribute =(string)UI::QueryWidget(`id(`group_style),`Value);
	    create_ldap = (boolean) UI::QueryWidget(`id(`create_ldap),`Value);

	    if (result == `configure && bind_dn == "")
	    {
		// error popup label
                Report::Error(_("Enter the DN used for binding to the LDAP server."));
		UI::SetFocus (`id (`bind_dn));
		continue;
	    }

	    if (result == `configure && base_config_dn == "")
	    {
		// error popup label
                Report::Error(_("Enter the configuration base DN"));
		UI::SetFocus (`id (`base_config_dn));
		continue;
	    }
	    if (Ldap::GetMainConfigDN() != base_config_dn ||
		Ldap::bind_dn != bind_dn || Ldap::file_server != file_server ||
		Ldap::login_enabled != login_enabled ||
		Ldap::member_attribute != member_attribute ||
		Ldap::create_ldap != create_ldap)
	    {
		Ldap::bind_dn		= bind_dn;
		Ldap::base_config_dn	= base_config_dn;
		Ldap::file_server	= file_server;
		Ldap::login_enabled	= login_enabled;
		Ldap::member_attribute	= member_attribute;
		Ldap::create_ldap	= create_ldap;
		Ldap::modified		= true;
	    }
	    break;
	}
    }
    return result;
}

/**
 * Initialize connection to LDAP server, bind and read the settings.
 * Everything is done before entering the Module Configuration Dialog.
 */
define symbol LDAPReadDialog () ``{

    string msg		= "";
    boolean read_now	= false;

    if (!Ldap::bound || Modified())
    {
	if (!Ldap::bound || Ldap::modified)
	{
	    // re-init/re-bind only when server information was changed (#39908)
	    if (!Ldap::bound || Ldap::old_server != Ldap::server ||
		 Ldap::DomainChanged ())
	    {
		msg = Ldap::LDAPInit ();
		if (msg != "")
		{
		    Ldap::LDAPErrorMessage ("init", msg);
		    return `back;
		}
	    }

	    if (!Ldap::bound || Ldap::old_server != Ldap::server)
	    {
		// Ldap::bind_pass might exist from server proposal...
		if (Stage::cont () && Ldap::bind_pass != nil)
		{
		    msg = Ldap::LDAPBind (Ldap::bind_pass);
		    if (msg != "")
		    {
			Ldap::LDAPErrorMessage ("bind", msg);
			Ldap::bind_pass = Ldap::LDAPAskAndBind (true);
		    }
		}
		else
		{
		    Ldap::bind_pass = Ldap::LDAPAskAndBind (true);
		}
		if (Ldap::bind_pass == nil)
		    return `back;

		read_now	= true;

		msg = Ldap::InitSchema ();
		if (msg != "") Ldap::LDAPErrorMessage ("schema", msg);
	    }
	}
	if (!Ldap::CheckBaseConfig (Ldap::base_config_dn))
	    return `back;
	if (read_now || (Ldap::modified && !Ldap::ldap_modified) ||
	    (Ldap::ldap_modified && Popup::AnyQuestion( Popup::NoHeadline(),
// yes/no popup
_("Reread settings from server?
All changes will be lost.
"), Label::YesButton(), Label::NoButton(), `focus_no))
	)
	{
	    msg = Ldap::ReadConfigModules ();
	    if (msg != "") Ldap::LDAPErrorMessage ("read", msg);

	    msg = Ldap::ReadTemplates ();
	    if (msg != "") Ldap::LDAPErrorMessage ("read", msg);

	    Ldap::ldap_modified = false;
	}
	Ldap::bound = true;
    }
    return `next;
}

/**
 * Dialog for configuration one object template
 */
define map<string,any> TemplateConfigurationDialog (map templ) {

    // help text 1/3
    string help_text = _("<p>Here, configure the template used for
creating new objects (like users or groups).</p>
") +

    // help text 2/3
    _("<p>Edit the template attribute values with <b>Edit</b>.
Changing the <b>cn</b> value renames the template.</p>
") +

    // help text 3/3
    _("<p>The second table contains a list of <b>default values</b>, used
for new objects. Modify the list by adding new values and editing or
removing current ones.</p>
");

    string template_dn = Ldap::current_template_dn;

    list table_items = [];
    map<string,any> template = (map<string,any>) eval (templ);

    // helper function converting list value to string
    define string to_table (string attr, list<string> val) ``{

	if (Ldap::SingleValued (attr) || attr == "cn")
	    return val[0]:"";
	else if (contains (["susesecondarygroup", "susedefaulttemplate"], attr))
	    return mergestring (val, " ");
	else
	    return mergestring (val, ",");
    }

    foreach (string attr, any value, template, ``{
	any val = value;
	// do not show internal attributes
	if (contains (["susedefaultvalue", "default_values", "objectclass",
	"modified", "old_dn"], tolower (attr)))
	    return;
	if (is (value, list))
	    val = to_table (attr, (list<string>) val);
	table_items = add (table_items, `item (`id(attr), attr, val));
    });

    list<term> default_items = [];
    map<string,string> default_values = template["default_values"]:$[];
    foreach (string attr, string value, default_values, ``{
	default_items = add (default_items, `item (`id(attr), attr, value));
    });

    term contents = `HBox(`HSpacing (1.5), `VBox(
        `VSpacing(0.5),
	`Table(`id(`table), `opt(`notify), `header(
	    // table header 1/2
	    _("Attribute"),
	    // table header 2/2
	    _("Value")),
	    table_items),
        `HBox (
            `PushButton(`id(`edit), Label::EditButton()),
	    `HStretch()
	),
	// label (table folows)
	`Left(`Label (_("Default Values for New Objects"))),
	`Table(`id(`defaults), `opt(`notify), `header(
	    // table header 1/2
	    _("Attribute of Object"),
	    // table header 2/2
	    _("Default Value")),
	    default_items),
        `HBox (
	    // button label (with non-default shortcut)
            `PushButton (`id(`add_dfl), `opt(`key_F3), _("A&dd")),
	    // button label
            `PushButton (`id(`edit_dfl), `opt(`key_F4), _("&Edit")),
            `PushButton(`id(`delete_dfl), `opt(`key_F5), Label::DeleteButton()),
            `HStretch()
	),
	`VSpacing (0.5)
	),
	`HSpacing (1.5));

    Wizard::OpenNextBackDialog ();
    // dialog label
    Wizard::SetContentsButtons(_("Object Template Configuration"),
            contents, help_text, Label::CancelButton(), Label::AcceptButton());
    Wizard::HideAbortButton();

    if (size (table_items) > 0)
    {
	UI::SetFocus (`id(`table));
    }
    UI::ChangeWidget (`id(`edit_dfl), `Enabled, default_items != []);
    UI::ChangeWidget (`id(`delete_dfl), `Enabled, default_items != []);

    any result = nil;
    while (true)
    {
	result		= UI::UserInput ();
	string attr	= (string)UI::QueryWidget (`id(`table), `CurrentItem);

	// edit attribute
	if (result == `edit || result == `table)
	{
	    if (attr == nil)
		continue;
	    list<string> value	= template [attr]:[];
	    list offer		= [];
	    list conflicts	= [];
	    if (attr == "susesecondarygroup")
		offer		= Ldap::GetGroupsDN (Ldap::GetDomain());
	    if (attr == "susenamingattribute")
	    {
		list classes	= Ldap::GetDefaultObjectClasses (template);
		offer		= Ldap::GetObjectAttributes (classes);
	    }
	    if (attr == "cn")
	    {
		string base = substring (template_dn, find (template_dn,",")+1);
		foreach (string dn, Ldap::ReadDN (base, ""), ``{
		    if (substring (dn,0,3) == "cn=")
			conflicts = add (conflicts, get_cn (dn));
		});
	    }
	    value = LdapPopup::EditAttribute ($[
		"attr"		: attr,
		"value"		: value,
		"conflicts"	: conflicts,
		"single"	: Ldap::SingleValued (attr) || attr == "cn",
		"offer"		: offer,
		"browse"	: attr == "susesecondarygroup"
	    ]);

	    if (value == template[attr]:[])
		continue;
	    UI::ChangeWidget (`id(`table),`Item(attr,1), to_table(attr, value));
	    template [attr] = value;
	}
	// add default value
	if (result == `add_dfl)
	{
	    list conflicts =
		maplist (string attr, string val, default_values,``(attr));
	    list classes = Ldap::GetDefaultObjectClasses (template);
	    list available = Ldap::GetObjectAttributes (classes);
	    // filter out objectclass
	    map dfl = LdapPopup::AddDefaultValue (sort (available),
		add (conflicts, "objectclass"));
	    if (dfl["value"]:"" == "")
		continue;
	    string attr = dfl["attr"]:"";
	    default_values [attr] = dfl["value"]:"";
	    default_items = add (default_items,
		`item (`id(attr), attr, dfl["value"]:""));
	    UI::ChangeWidget (`id(`defaults), `Items, default_items);
	    UI::ChangeWidget (`id(`edit_dfl), `Enabled, default_items != []);
	    UI::ChangeWidget (`id(`delete_dfl), `Enabled, default_items != []);
	}
	// edit default value
	if (result == `edit_dfl || result == `defaults)
	{
	    attr	= (string)UI::QueryWidget (`id(`defaults),`CurrentItem);
	    if (attr == nil)
		continue;
	    string value	= default_values [attr]:"";
	    list l_value	= LdapPopup::EditAttribute ($[
		"attr"		: attr,
		"value"		: [value],
		"single"	: true,
	    ]);
	    if (l_value [0]:"" == value)
		continue;
	    value = l_value[0]:"";
	    UI::ChangeWidget (`id(`defaults), `Item (attr, 1), value);
	    default_values [attr] = value;
	}
	// delete default value
	if (result == `delete_dfl)
	{
	    attr	= (string)UI::QueryWidget (`id(`defaults),`CurrentItem);
	    if (attr == nil)
		continue;
	    // yes/no popup, %1 is name
	    if (!Popup::YesNo (sformat (_("Really delete default attribute \"%1\"?"), attr)))
		continue;
	    default_values = remove (default_values, attr);
	    default_items = filter (term it, default_items, ``(
		it[1]:"" != attr));
	    UI::ChangeWidget (`id(`defaults), `Items, default_items);
	    UI::ChangeWidget (`id(`edit_dfl), `Enabled, default_items != []);
	    UI::ChangeWidget (`id(`delete_dfl), `Enabled, default_items != []);
	}
	if (is(result,symbol) && contains ([`back, `cancel, `abort], (symbol)result))
	    break;
	if (result == `next)
	{
	    boolean cont = false;

	    // check the template required attributes...
	    foreach (string oc, template["objectclass"]:[], ``{
		if (cont) return;
		foreach (string attr, Ldap::GetRequiredAttributes (oc), ``{
		    any val = template[attr]:nil;
		    if (!cont && val == nil || val == [] || val == "") {
			//error popup, %1 is attribute name
			Popup::Error (sformat (_("The \"%1\" attribute is mandatory.
Enter a value."), attr));
			UI::SetFocus (`id(`table));
			cont = true;
		    }
		});
	    });
	    if (cont) continue;
	    template ["default_values"] = default_values;
	    break;
	}
    }
    Wizard::CloseDialog ();
    return template;
}

/**
 * Dialog for configuration of one "configuration module"
 */
define symbol ModuleConfigurationDialog () ``{

    // helptext 1/4
    string help_text = _("<p>Here, manage the configuration stored in LDAP directory.</p>") +

    // helptext 2/4
    _("<p>Each configuration set is called a <b>configuration module</b>. If there
is no configuration module in the provided location (Base Configuration),
create one with <b>New</b>. Delete the current module
using <b>Delete</b>.</p>
") +

    // helptext 3/4
    _("<p>Edit the values of attributes in the table with <b>Edit</b>.
Some values have special meanings, for example, changing the <b>cn</b> value renames the
current module.</p>
") +

    // helptext 4/4
    _("<p>To configure the default template of the current module,
click <b>Configure Template</b>.
</p>
");

    string current_dn		= Ldap::current_module_dn;
    map  modules_attrs_items	= $[]; // map of list (table items), index is cn
    map<string,map<string,any> > modules	=
	(map<string,map<string,any> >) Ldap::GetConfigModules ();
    map<string,map<string,any> > templates	=
	(map<string,map<string,any> >) Ldap::GetTemplates ();
    list<string> names		= [];
    list<string> templates_dns	= maplist (string dn, map t, templates, ``(dn));

    /**
     * Helper for creating table items in ModuleConfiguration Dialog
     */
    define list create_attrs_items (string cn) ``{

	list attrs_items = [];
	string dn	= get_dn (cn);
	if (!haskey (modules, dn))
	    dn		= tolower (dn);
	foreach (string attr, any value, modules[dn]:$[], ``{
	    any val = value;
	    if (contains (["objectclass", "modified", "old_dn"], attr))
		return;
	    if (is (value, list))
	    {
		list lvalue	= (list) value;
		if (Ldap::SingleValued (attr) || attr == "cn")
		    val = lvalue[0]:"";
		else
		    val = mergestring ((list<string>)value, ",");
	    }
	    attrs_items = add (attrs_items, `item(`id(attr), attr, val));
	});

	return attrs_items;
    }

    foreach (string dn, map mod, modules, ``{
	string cn = get_string (mod, "cn");
	if (cn == "")
	    return;
	names = add (names, cn);
	// attributes for table
	modules_attrs_items [cn] = create_attrs_items (cn);
	if (current_dn == "")
	    current_dn = dn;
    });
    string current_cn = modules [current_dn, "cn",0]:get_cn (current_dn);

    /**
     * Helper for updating widgets in ModuleConfiguration Dialog
     */
    define void replace_module_names () ``{

	list modules_items = []; // list of module names
	foreach (string cn, names, ``{
	    if (tolower (cn) == tolower (current_cn))
		modules_items = add (modules_items, `item(`id(cn), cn, true));
	    else
		modules_items = add (modules_items, `item(`id(cn), cn));
	});
	UI::ReplaceWidget (`id(`rp_modnames),
	    `Left(`ComboBox (`id (`modules), `opt (`notify),
		// combobox label
		_("Configuration &Module"), modules_items)));
	boolean ena = names != [];
	UI::ChangeWidget (`id(`delete), `Enabled, ena);
	UI::ChangeWidget (`id(`edit), `Enabled, ena);
	UI::ChangeWidget (`id(`modules), `Enabled, ena);
    }

    /**
     * Helper for updating widgets in ModuleConfiguration Dialog
     */
    define void replace_templates_items () ``{

	list items = maplist (string dn,
	    modules[current_dn,"susedefaulttemplate"]:[], ``(`item (`id(dn), dn)));
	UI::ReplaceWidget (`id(`rp_templs),
	    `PushButton (`id(`templ_pb), `opt(`key_F7),
		// button label
		_("C&onfigure Template")));
	UI::ChangeWidget (`id(`templ_pb), `Enabled, items != []);
    }

    term contents = `HBox(`HSpacing (1.5), `VBox(
        `VSpacing(0.5),
	`HBox (
	    `ReplacePoint (`id(`rp_modnames), `Empty()),
	    `VBox (
		`Label (""), `PushButton (`id (`new), Label::NewButton())),
	    `VBox (
		`Label (""), `PushButton (`id (`delete), Label::DeleteButton()))
	),
        `VSpacing(0.5),
	`Table(`id(`table), `opt(`notify), `header(
	    // table header 1/2
	    _("Attribute"),
	    // table header 2/2
	    _("Value")),
	    modules_attrs_items[current_cn]:[]),
        `HBox (
            `PushButton(`id(`edit), `opt(`key_F4), Label::EditButton()),
            `HStretch(),
	    `ReplacePoint (`id(`rp_templs), `Empty ())
	),
	`VSpacing (0.5)
	),
	`HSpacing (1.5));

    // dialog label
    Wizard::SetContentsButtons(_("Module Configuration"),
            contents, help_text, Label::CancelButton(), Label::AcceptButton());
    Wizard::HideAbortButton ();

    if (size (modules_attrs_items[current_cn]:[]) > 0)
    {
	UI::SetFocus (`id(`table));
    }
    replace_templates_items ();
    replace_module_names ();

    // result could be symbol or string
    any result = nil;
    while (true)
    {
	result		= UI::UserInput ();
	string attr	= (string)UI::QueryWidget (`id(`table), `CurrentItem);

	// check the correctness of entry
	if (contains (modules [current_dn, "susedefaulttemplate"]:[], result) ||
	    result == `next || result == `modules || result == `new)
	{
	    foreach (string oc, modules [current_dn, "objectclass"]:[], ``{
		foreach (string attr, Ldap::GetRequiredAttributes (oc), ``{
		    any val = modules [current_dn, attr]:nil;
		    if (val == nil || val == [] || val == "") {
			//error popup, %1 is attribute name
			Popup::Error (sformat (_("The \"%1\" attribute is mandatory.
Enter a value."), attr));
			UI::SetFocus (`id(`table));
			result = `notnext;
			continue;
		    }
		});
	    });
	}
	// change the focus to new module
	if (result == `modules)
	{
	    current_cn = (string)UI::QueryWidget (`id(`modules), `Value);
	    current_dn = get_dn (current_cn);
	    if (!haskey (modules, current_dn))
		current_dn	= tolower (current_dn);
	    UI::ChangeWidget (`id(`table), `Items,
		modules_attrs_items[current_cn]:[]);
	    replace_templates_items ();
	}
	// delete the module
	if (result == `delete)
	{
	    // yes/no popup, %1 is name
	    if (!Popup::YesNo (sformat (_("Really delete module \"%1\"?"), current_cn)))
		continue;
	    modules_attrs_items = remove (modules_attrs_items, current_cn);
	    if (modules [current_dn, "modified"]:"" != "added")
	    {
		modules [current_dn, "modified"] = "deleted";
	    }
	    names = filter (string n, names, ``(n != current_cn));
	    current_cn = names [0]:"";
	    current_dn = get_dn (current_cn);
	    if (!haskey (modules, current_dn))
		current_dn	= tolower (current_dn);
	    replace_module_names ();
	    replace_templates_items ();
	    UI::ChangeWidget (`id(`table), `Items,
		modules_attrs_items[current_cn]:[]);
	}
	// new module
	if (result == `new)
	{
	    list<string> available = Ldap::available_config_modules;
	    foreach (string dn, map mod, modules, ``{
		if (mod["modified"]:"" == "deleted")
		    return;
		foreach (string cl, mod["objectclass"]:[], ``{
		    available = filter (string c, available, ``(
			tolower (c) != tolower (cl)));
		});
	    });
	    if (available == [])
	    {
		// message
		Popup::Message (_("You currently have configuration modules
of each type, so you cannot add a new one."));
		continue;
	    }
	    // get new name and class
	    map new = LdapPopup::NewModule (available, names);
	    string cn = new ["cn"]:"";
	    if (cn == "")
		continue;
	    current_cn = cn;
	    current_dn = get_dn (current_cn);
	    if (!haskey (modules, current_dn))
		current_dn	= tolower (current_dn);
	    modules [current_dn] = Ldap::CreateModule (cn, new ["class"]:"");
	    names = add (names, cn);
	    modules_attrs_items [cn] = create_attrs_items (cn);
	    replace_module_names ();
	    replace_templates_items ();
	    UI::ChangeWidget (`id(`table), `Items,
		modules_attrs_items[current_cn]:[]);
	}
	// module attribute modification
	if (result == `edit || result == `table)
	{
	    if (attr == nil)
		continue;
	    list<string> value		= modules [current_dn, attr]:[];
	    list offer		= [];
	    list conflicts	= [];
	    if (attr == "cn")
		conflicts = names;
	    if (attr == "susedefaulttemplate")
		offer		= templates_dns;
	    else if (attr == "susepasswordhash")
		offer		= Ldap::hash_schemas;

	    value = LdapPopup::EditAttribute ($[
		"attr"		: attr,
		"value"		: value,
		"conflicts"	: conflicts,
		"single"	: Ldap::SingleValued (attr) || attr == "cn",
		"offer"		: offer,
		"browse"	:
		    // TODO function, that checks if value should be DN
		    (attr == "susedefaultbase" || attr == "susedefaulttemplate")
	    ]);

	    if (value == modules [current_dn, attr]:[]) //nothing was changed
		continue;
	    modules [current_dn, attr] = value;
	    modules_attrs_items [current_cn] = create_attrs_items (current_cn);
	    UI::ChangeWidget (`id(`table), `Items,
		modules_attrs_items[current_cn]:[]);
	    UI::ChangeWidget (`id(`table), `CurrentItem, attr);
	    if (attr == "cn" && value != [])
	    {
		string cn = value [0]:current_cn;
		modules_attrs_items[cn] = modules_attrs_items[current_cn]:[];
		modules_attrs_items = remove (modules_attrs_items, current_cn);
		if (modules [current_dn, "modified"]:"" != "added" &&
		    modules [current_dn, "modified"]:"" != "renamed")
		{
		    modules [current_dn, "modified"] = "renamed";
		    modules [current_dn, "old_dn"] = current_dn;
		}
		modules [ get_dn (cn) ] = modules [current_dn]:$[];
		if (tolower (get_dn (cn)) != tolower (current_dn))
		    modules = remove (modules, current_dn);
		names = filter (string n, names, ``(n != current_cn));
		names = add (names, cn);
		current_cn = cn;
		current_dn = get_dn (cn);
		replace_module_names ();
	    }
	    if (attr == "susedefaulttemplate")
		replace_templates_items ();
	}
	// configure template
	if (result == `templ_pb)
	{
	    string template_dn = modules [current_dn, "susedefaulttemplate", 0]:"";
	    Ldap::current_template_dn = template_dn;
	    map template = (map) eval (templates [template_dn]:$[]);
	    // template not loaded, check DN:
	    if (template == $[])
	    {
		template = Ldap::CheckTemplateDN (template_dn);
		if (template == nil)
		    continue;
		else if (template == $[])
		{
		    if (!Ldap::ParentExists (template_dn))
			continue;
		    template = Ldap::CreateTemplate (get_cn (template_dn),
			modules [current_dn, "objectclass"]:[]);
		}
		templates_dns = add (templates_dns, template_dn);
	    }
	    templates [template_dn] = TemplateConfigurationDialog (template);
	    // check for template renaming
	    if (templates [template_dn, "cn"]:[] != template ["cn"]:[])
	    {
		string cn = get_string (templates [template_dn]:$[], "cn");
		string new_dn = get_new_dn (cn, template_dn);

		templates [new_dn] = templates [template_dn]:$[];
		if (new_dn != template_dn)
		    templates = remove (templates, template_dn);
		if (templates [new_dn, "modified"]:"" != "added")
		{
		    templates [new_dn, "modified"] = "renamed";
		    templates [new_dn, "old_dn"] = template_dn;
		}
		templates_dns = filter (string dn, templates_dns, ``(
		    dn != template_dn));
		templates_dns = add (templates_dns, new_dn);
		// update list of templates
		modules [current_dn, "susedefaulttemplate"] = maplist (
		    string dn, modules [current_dn, "susedefaulttemplate"]:[],``{
			if (dn == template_dn) return new_dn;
			return dn;
		});
		modules_attrs_items [current_cn] =
		    create_attrs_items (current_cn);
		UI::ChangeWidget (`id(`table), `Items,
		    modules_attrs_items[current_cn]:[]);
		replace_templates_items ();
	    }
	    UI::SetFocus (`id(`table));
	}
	if (result == `next)
	{
	    Ldap::current_module_dn = current_dn;
	    // save the edited values to global map...
	    Ldap::CommitConfigModules (modules);
	    // commit templates here!
	    Ldap::CommitTemplates (templates);
	    break;
	}
	if (result == `cancel && ReallyAbort () != `abort)
	    result = `not_next;
	if (result == `back || result == `cancel)
	    break;
    }

    return (symbol) result;
}

}//EOF
