ACC SHELL

Path : /usr/share/YaST2/modules/
File Upload :
Current File : //usr/share/YaST2/modules/ProductLicense.ycp

/**
 * File:	ProductLicense.ycp
 *
 * Module:	ProductLicense
 *
 * Summary:	Provide access / dialog for product license
 *
 * Author:	Jiri Srain <jsrain@suse.cz>
 *		Lukas Ocilka <locilka@suse.cz>
 *
 */
{

module "ProductLicense";

import "Directory";
import "InstShowInfo";
import "Language";
import "Popup";
import "Report";
import "Stage";
import "Wizard";
import "Mode";
import "FileUtils";
import "ProductFeatures";
import "String";
import "WorkflowManager";
import "Progress";

// IMPORTANT: maintainer of yast2-installation is responsible for this module

textdomain "packager";

// list of already accepted licenses
list <string> already_accepted_licenses = [];

list <string> license_patterns = [
    "license\\.html", "license\\.%1\\.html",
    "license\\.htm",  "license\\.%1\\.htm",
    "license\\.txt",  "license\\.%1\\.txt"
];
// no more wildcard patterns here, UI can display only html and txt anyway

// All licenses have their own unique ID
list <string> license_ids = [];

/**
 * License files by their eula_ID
 * @struct $["ID":$[licenses]]
 */
map <string, map <string, string> > all_licenses = $[];

/**
 * Checks the string that might contain ID of a license and
 * eventually returns that id.
 * See also GetIdPlease for a better ratio of successful stories.
 */
string GetId (string id_text) {
    string id = nil;

    if (regexpmatch (id_text, "^license_language_.+")) {
	id = regexpsub (id_text, "^license_language_(.+)", "\\1");
    } else {
	y2error ("Cannot get ID from %1", id_text);
    }

    return id;
}

// Helper func. Cuts encoding suffix off the LANG
// env. variable i.e. foo_BAR.UTF-8 => foo_BAR
string EnvLangToLangCode ( string env_lang )
{
    list <string> tmp = [];
    if (env_lang != nil)
	tmp = splitstring(env_lang, ".@");

    return tmp[0]:"";
}

/**
 * Creates a unique identification from filename
 * (MD5sum + file size)
 *
 * @param string filename
 * @return string unique ID
 */
string GetLicenseIdentString (string filename) {
    if (! FileUtils::Exists (filename)) {
	y2error ("License '%1' doesn't exist", filename);
	return nil;
    }

    string filemd5 = FileUtils::MD5sum (filename);
    if (filemd5 == nil) {
	return nil;
    }

    string ret = sformat("%1-%2", filemd5, FileUtils::GetSize (filename));

    y2milestone ("License ident for '%1' is '%2'", filename, ret);

    return ret;
}

/**
 * Checks whether the license (file) has been already accepted
 *
 * @param string filename
 * @return boolean whether the license has been accepted before
 */
boolean IsLicenseAlreadyAccepted (string license_ident) {
    if (license_ident == nil || license_ident == "") {
	y2error ("Wrong license ID '%1'", license_ident);
	return false;
    }

    return contains (already_accepted_licenses, license_ident);
}

/**
 * Sets that the license (file) has been already accepted
 *
 * @param string filename
 */
void LicenseHasBeenAccepted (string license_ident) {
    if (license_ident == nil || license_ident == "") {
	y2error ("Wrong license ID '%1'", license_ident);
	return;
    }

    y2milestone ("Adding License ID '%1' as already accepted", license_ident);
    already_accepted_licenses = add (already_accepted_licenses, license_ident);
}

string WhichLicenceFile (string license_language, map <string, string> & licenses) {
    string license_file = licenses[license_language]:"";

    if (license_file == nil || license_file == "") {
	y2error ("No license file defined for language '%1' in %2", license_language, licenses);
    } else {
	y2milestone ("Using license file: %1", license_file);
    }

    return license_file;
}

global term GetLicenseContent (string lic_lang, map <string, string> & licenses, string id) {
	    string license_file = WhichLicenceFile (lic_lang, licenses);

	    string license_text = (string) SCR::Read(.target.string, license_file);
	    if (license_text == nil)
	    {
		Report::Error (sformat(_("Cannot read license file %1"), license_file));
		license_text = "";
	    }
	    term rt = `Empty();

	    // License is HTML (or RichText)
	    if (regexpmatch(license_text, "</.*>"))
		rt = `MinWidth (80, `RichText(`id (sformat("welcome_text_%1", id)),
		    license_text
		));
	    // License is plain text
	    // details in BNC #449188
	    else
		rt = `MinWidth (80, `RichText(`id (sformat("welcome_text_%1", id)),
		    "<pre>" + String::EscapeTags (license_text) + "</pre>"
		));

	    return rt;
}

// filename printed in the license dialog
string license_file_print = nil;


term GetLicenseDialogTerm (list<string> languages, string license_language, map <string, string> & licenses, string id) {
    string license_text = "";
    term rt = GetLicenseContent (license_language, licenses, id);

    // bug #204791, no more "languages.ycp" client
    map<string,list> lang_names_orig = Language::GetLanguagesMap (false);
    if (lang_names_orig == nil) {
	y2error("Wrong definition of languages");
	lang_names_orig = $[];
    }

    map<string,string> lang_names = $[];

    // $[ "en" : "English (US)", "de" : "Deutsch" ]
    lang_names = mapmap (string code, list descr,
	lang_names_orig,
    {
	return $[ code : descr[4]:"" ];
    });

    /* for the default fallback */
    if (lang_names[""]:nil == nil) {
	// language name
	lang_names[""] = lang_names_orig["en_US", 4]:"";
    }

    if (lang_names["en"]:nil == nil) {
	// language name
	lang_names["en"] = lang_names_orig["en_US", 4]:"";
    }

    list<list<string> > lang_pairs = maplist (string l, languages, {
	string name_print = lang_names[l]:"";

	if (name_print == "") {
	    string l_short = substring (l, 0, 2);

	    foreach (string k, string v, lang_names, {
		if (substring (k, 0, 2) == l_short) {
		    name_print = v;
		    return true;
		}
		return false;
	    });
	}

	return [ l, name_print ];
    });

    // filter-out languages that don't have any name
    lang_pairs = filter (list<string> lang_pair, lang_pairs, {
	if (lang_pair[1]:"" == "") {
	    y2warning("Unknown license language '%1', filtering out...", lang_pair);
	    return false;
	} else {
	    return true;
	}
    });

    lang_pairs = sort (list<string> a, list<string> b, lang_pairs, {
	// bnc#385172: must use < instead of <=, the following means:
	// strcoll(x) <= strcoll(y) && strcoll(x) != strcoll(y)
	list lsorted = lsort ([a[1]:"", b[1]:""]);
	list lsorted_r = lsort ([b[1]:"", a[1]:""]);
	return (lsorted[0]:"" == a[1]:"" && lsorted == lsorted_r);
    });
    list langs = maplist (list<string> descr, lang_pairs, {
	return `item (`id (descr[0]:""), descr[1]:"", (descr[0]:"" == license_language));
    });

    term lang_selector_options = `opt (`notify);
    // Disable in case there is no language to select
    // bugzilla #203543
    if (size (langs) <= 1) {
	lang_selector_options = add (lang_selector_options, `disabled);
    }

    license_ids = toset (add (license_ids, id));

    return `VBox (
	// combo box
	`Left (`ComboBox (`id (sformat("license_language_%1", id)), lang_selector_options, _("&Language"), langs)),
	`ReplacePoint (`id (sformat("license_contents_rp_%1", id)), rt)
    );
}

// BNC #448598
// no-acceptance-needed file in license.tar.gz means the license
// doesn't have to be accepted by user, just displayed
map <string, boolean> license_acceptance_needed = $[];

/**
 * Returns whether accepting the license manually is requied.
 *
 * @see BNC #448598
 * @return boolean if required
 */
global boolean AcceptanceNeeded (string id) {
    return license_acceptance_needed[id]:true;
}

void SetAcceptanceNeeded (string id, boolean new_value) {
    if (new_value == nil) {
	y2error ("Undefined behavior (License ID %1), AcceptanceNeeded: %2", id, new_value);
	return;
    }

    license_acceptance_needed[id] = new_value;

    if (new_value == true) {
	y2milestone ("License agreement (ID %1) WILL be required", id);
    } else {
	y2milestone ("License agreement (ID %1) will NOT be required", id);
    }
}

term GetLicenseDialog (list<string> languages, string license_language, map <string, string> & licenses, string id, boolean spare_space) {
    map display = UI::GetDisplayInfo();
    integer space = display["TextMode"]:true ? 1 : 3;

    term license_buttons = `VBox (
	`VSpacing ((spare_space ? 0:2)),
	`RadioButtonGroup(`id (sformat ("eula_%1", id)),
	    `HBox(
		`HSpacing (2*space),
		`VBox(
		    `Left(`RadioButton(`id (sformat("yes_%1", id)),
			`opt(`notify),
			// radio button
			_("&Yes, I Agree to the License Agreement")
		    )),
		    `Left(`RadioButton(`id (sformat("no_%1", id)),
			`opt(`notify),
			// radio button
			_("N&o, I Do Not Agree")
		    ))
		),
		`HSpacing (2*space)
	    )
	)
    );

    return `VBox (
	`VSpacing ((spare_space ? 0:1)),
	`HBox (
	    `HSpacing (2*space),
	    GetLicenseDialogTerm (languages, license_language, licenses, id),
	    `HSpacing (2*space)
	),
	// BNC #448598
	// yes/no buttons exist only if needed
	// if they don't exist, user is not asked to accept the license later
	(AcceptanceNeeded (id) ?
	    license_buttons
	    :
	    `Empty()
	),
	`VSpacing((spare_space ? 0.5 : 1)),
	`HBox (
	    `HSpacing (2*space),
	    (license_file_print != nil ?
		`Left (
		    // FATE #302018
		    `Label (
			// TRANSLATORS: addition license information
			// %1 is replaced with the filename
			sformat(_("If you want to print this EULA, you can find it
on the first media in the file %1"), license_file_print)
		    )
		)
		:
		`Empty ()
	    ),
	    `HSpacing (2*space)
	),
	`VSpacing ((spare_space ? 0:1))
    );
}

string GetLicenseDialogHelp () {
    // help text
    return _("<p>Read the license agreement carefully and select
one of the available options. If you do not agree to the license agreement,
the configuration will be aborted.</p>
");
}

/**
 * Displays License with Help and ( ) Yes / ( ) No radio buttons
 * @param string file with the license
 */
void DisplayLicenseDialog (list<string> languages, boolean back, string license_language, map <string, string> & licenses, string id) {
    // dialog caption
    string caption = _("License Agreement");

    term contents = GetLicenseDialog (languages, license_language, licenses, id, false);

    // If acceptance is not needed, there's no need to disable the button
    // by default
    boolean default_next_button_state = (AcceptanceNeeded (id) ? false : true);

    Wizard::SetContents(caption, contents, GetLicenseDialogHelp(),
	back, default_next_button_state);

    Wizard::SetTitleIcon ("yast-license");
    Wizard::SetFocusToNextButton();
}



/**
 * Removes the temporary directory for licenses
 * @param string temporary directory path
 */
void CleanUpLicense (string tmpdir) {
    if (tmpdir != nil && tmpdir != "/")
	SCR::Execute (.target.bash_output,
	    sformat ("rm -rf '%1'", String::Quote(tmpdir))
	);
}

/**
 * Get all files with license existing in specified directory
 * @param dir string directory to look into
 * @param patterns a list of patterns for the files, regular expressions
 *   with %1 for the language
 * @return a map $[ lang_code : filename ]
 */
map<string,string> LicenseFiles (string dir, list<string> patterns) {
    map<string,string> ret = $[];

    if (dir == nil) return ret;

    list<string> files = (list<string>)SCR::Read (.target.dir, dir);
    y2milestone ("All files in license directory: %1", files);

    // no license
    if (files == nil) return $[];

    foreach (string p, patterns, {
	if (! issubstring (p, "%"))
	{
	    foreach (string file, files, {
		//Possible license file names are regexp patterns
		//(see list <string> license_patterns)
		//so we should treat them as such (bnc#533026)
		if ( regexpmatch(file,p) )
		{
		    ret[""] = dir + "/" + file;
		}
	    });
	}
	else
	{
	    string regpat = sformat (p, "(.+)");
	    foreach (string file, files, {
		if (regexpmatch (file, regpat))
		{
		    string key = regexpsub (file, regpat, "\\1");
		    ret[key] = dir + "/" + file;
		}
	    });
	}
    });
    y2milestone ("Files containing license: %1", ret);
    return ret;
}

string tmpdir = nil;
string license_dir = nil;
string info_file = nil;

// Functions for handling different locations of licenses -->


boolean UnpackLicenseTgzFileToDirectory (string unpack_file, string to_directory) {
    // License file exists
    if (FileUtils::Exists (unpack_file)) {
	map out = (map) SCR::Execute (.target.bash_output,
	    sformat ("
rm -rf '%1' && \
mkdir -p '%1' && \
cd '%1' && \
tar -xzf '%2'
",
		String::Quote (to_directory),
		String::Quote (unpack_file)
	    )
	);

	// Extracting license failed, cannot accept the license
	if (out["exit"]:0 != 0) {
	    y2error("Cannot untar license -> %1", out);
	    // popup error
	    Report::Error (_("An error occurred while preparing the installation system."));
	    CleanUpLicense (to_directory);
	    return false;
	}

	// Success
	return true;

    // Nothing to unpack
    } else {
	y2error ("No such file: %1", unpack_file);
	return false;
    }
}

void SearchForLicense_FirstStageBaseProduct (integer src_id, string fallback_dir) {
    y2milestone ("Getting license from installation product");

    string license_file = "/license.tar.gz";

    if (FileUtils::Exists (license_file)) {
	y2milestone("Installation Product has a license");

	tmpdir = sformat ("%1/product-license/base-product/", (string) SCR::Read (.target.tmpdir));

	if (UnpackLicenseTgzFileToDirectory (license_file, tmpdir)) {
	    license_dir = tmpdir;
	    license_file_print = "license.tar.gz";
	} else {
		license_file = nil;
	}
    } else {
	y2milestone ("Installation Product doesn't have a license");

	license_file = nil;
    }

    if (FileUtils::Exists ("/info.txt"))
        info_file = "/info.txt";
}

void SearchForLicense_LiveCDInstallation (integer src_id, string fallback_dir) {
    y2milestone ("LiveCD License");

    // BNC #594042: Multiple license locations
    list <string> license_locations = ["/usr/share/doc/licenses/", "/"];

    license_dir = nil;
    info_file = nil;

    foreach (string license_location, license_locations, {
	license_location = sformat ("%1/license.tar.gz", license_location);

	if (FileUtils::Exists (license_location)) {
	    y2milestone ("Using license: %1", license_location);
	    tmpdir = sformat ("%1/product-license/LiveCD/", (string) SCR::Read (.target.tmpdir));

	    if (UnpackLicenseTgzFileToDirectory (license_location, tmpdir)) {
		license_dir = tmpdir;
		license_file_print = "license.tar.gz";
	    } else {
		CleanUpLicense (tmpdir);
	    }
	    break;
	}
    });

    if (license_dir == nil) {
	y2milestone ("No license found in: %1", license_locations);
    }

    foreach (string info_location, license_locations, {
	info_location = sformat ("%1/README.BETA", info_location);

	if (FileUtils::Exists (info_location)) {
	    y2milestone ("Using info file: %1", info_location);
	    info_file = info_location;
	    break;
	}
    });

    if (info_file == nil) {
	y2milestone ("No info file found in: %1", license_locations);
    }
}

void SearchForLicense_NormalRunBaseProduct (integer src_id, string fallback_dir) {
    y2milestone ("Using default license directory %1", fallback_dir);

    if (FileUtils::Exists (fallback_dir)) {
	license_dir = fallback_dir;
    } else {
	y2warning ("Fallback dir doesn't exist %1", fallback_dir);
	license_dir = nil;
    }

    if (FileUtils::Exists ("/info.txt"))
        info_file = "/info.txt";
}

void SearchForLicense_AddOnProduct (integer src_id, string fallback_dir) {
	y2milestone ("Getting license info from repository %1", src_id);

	info_file = Pkg::SourceProvideDigestedFile (src_id, 1, "/media.1/info.txt", true /* optional */);

	// using a separate license directory for all products
	tmpdir = sformat ("%1/product-license/%2/", (string) SCR::Read(.target.tmpdir), src_id);

	// FATE #302018 comment #54
	string license_file_location = "/license.tar.gz";
	string license_file = Pkg::SourceProvideDigestedFile (src_id, 1, license_file_location, true /* optional */);

	if (license_file != nil) {
	    y2milestone ("Using file %1 with licenses", license_file);

	    if (UnpackLicenseTgzFileToDirectory (license_file, tmpdir)) {
		license_dir = tmpdir;
		license_file_print = "license.tar.gz";
	    } else {
		license_dir = nil;
	    }
	    
	    return;
	}

	y2milestone ("Licenses in %1... not supported", license_file_location);

	// New format didn't work, try the old one 1stMedia:/media.1/license.zip
	license_dir = tmpdir;
	license_file = Pkg::SourceProvideDigestedFile (src_id, 1, "/media.1/license.zip", true /* optional */);

	// no license present
	if (license_file == nil) {
	    y2milestone ("No license present");
	    license_dir = nil;
	    tmpdir = nil;
	    // return from the function
	    return;
	}

	y2milestone("Product has a license");
	map out = (map)SCR::Execute (.target.bash_output,
	    sformat (
		"
rm -rf '%1' && \
mkdir -p '%1' && \
cd '%1' && \
unzip -qqo '%2'
",
		String::Quote (tmpdir),
		String::Quote (license_file)
	    )
	);

	// Extracting license failed, cannot accept the license
	if (out["exit"]:0 != 0) {
	    y2error("Cannot unzip license -> %1", out);
	    // popup error
	    Report::Error (_("An error occurred while preparing the installation system."));
	    CleanUpLicense (tmpdir);
	    license_dir = nil;
	} else {
	    license_dir = tmpdir;
	    license_file_print = "/media.1/license.zip";
	}
}

// Functions for handling different locations of licenses <--

void GetSourceLicenseDirectory (integer src_id, string fallback_dir) {
    y2milestone ("Searching for licenses... (src_id: %1, fallback_dir: %2, mode: %3, stage: %4)",
	src_id, fallback_dir, Mode::mode(), Stage::stage());

    license_file_print = nil;

    // Bugzilla #299732
    // Base Product - LiveCD installation
    if (Mode::live_installation()) {

	SearchForLicense_LiveCDInstallation (src_id, fallback_dir);

    // Base-product - license not in installation
    //   * Stage is not initial
    //   * source ID is not defined
    } else if (! Stage::initial() && src_id == nil) {

	SearchForLicense_NormalRunBaseProduct (src_id, fallback_dir);

    // Base-product - first-stage installation
    //   * Stage is initial
    //   * Source ID is not set
    // bugzilla #298342
    } else if (Stage::initial() && src_id == nil) {

	SearchForLicense_FirstStageBaseProduct ((src_id == nil ? Pkg::SourceGetCurrent(true)[0]:0 : src_id), fallback_dir);

    // Add-on-product license
    //   * Source ID is set
    } else if (src_id != nil && src_id > -1) {

	SearchForLicense_AddOnProduct (src_id, fallback_dir);

    // Fallback
    } else {
	y2warning ("Source ID not defined, using fallback dir '%1'", fallback_dir);
	license_dir = fallback_dir;
    }

    y2milestone ("ProductLicense settings: license_dir: %1, tmpdir: %2, info_file: %3", license_dir, tmpdir, info_file);
}

string lic_lang = "";

symbol InitLicenseData (integer src_id, string dir, map <string, string> & licenses,
    list <string> & available_langs, boolean require_agreement, string & license_ident, string id) {
    GetSourceLicenseDirectory (src_id, dir);

    // License does not need to be accepted. Well, I mean, manually selected "Yes, of course, I agree..."
    if (FileUtils::Exists (sformat ("%1/no-acceptance-needed", license_dir))) {
	if (id == nil) {
	    y2error ("Parameter id not set");
	} else {
	    SetAcceptanceNeeded (id, false);
	}
    }

    licenses = LicenseFiles (license_dir,
	license_patterns);

    // all other 'licenses' could be replaced by this one
    all_licenses[id] = licenses;

    if (info_file == nil && size (licenses) == 0)
	return `auto;

    // Let's do getenv here. Language::language may not be initialized
    // by now (see bnc#504803, c#28). Language::Language does only
    // sysconfig reading, which is not too useful in cases like
    // 'LANG=foo_BAR yast repositories'
    string language = EnvLangToLangCode( getenv("LANG") );

    // Preferencies how the client selects from available languages
    list<string> langs = [
	language,
	substring (language, 0, 2), // "it_IT" -> "it"
	"en_US",
	"en_GB",
	"en",
	"" // license.txt fallback
    ];
    available_langs = maplist (string lang, string fn,
	licenses,
    {
	return lang;
    });

    // "en" is the same as "", we don't need to have them both
    if (contains(available_langs, "en") && contains(available_langs, "")) {
	y2milestone("Removing license fallback '' as we already have 'en'...");
	available_langs = filter (string one_lang, available_langs, {
	    return one_lang != "en";
	});
    }

    y2milestone ("Preffered lang: %1", language);
    if (size (available_langs) == 0)
	return `auto; // no license available
    lic_lang = find (string l, langs, {
	return haskey (licenses, l);
    });
    if (lic_lang == nil)
	lic_lang = available_langs[0]:"";

    y2milestone ("Preselected language: '%1'", lic_lang);

    if (lic_lang == nil)
    {
	if (tmpdir != nil) CleanUpLicense (tmpdir);
	return `auto;
    }

    // Check whether such license hasn't been already accepted
    // Bugzilla #305503
    string license_ident_lang = nil;

    // We need to store the original -- not localized license ID (if possible)
    foreach (string check_this, ["", "en", lic_lang], {
	if (contains (available_langs, check_this)) {
	    license_ident_lang = check_this;
	    y2milestone ("Using localization '%1' (for license ID)", license_ident_lang);
	    break;
	}
    });

    // fallback
    if (license_ident_lang == nil) license_ident_lang = lic_lang;

    string base_license = WhichLicenceFile (license_ident_lang, licenses);
    license_ident = GetLicenseIdentString (base_license);

    // agreement might be required even if license has been already accepted
    // defined, properly ($md5sum(32)-(1)$size(1..n))
    //
    // see also BNC #448598
    // Even if it it shown it sometimes doesn't need to be even accepted by
    // selecting "yes, I agree"
    if (require_agreement != true && tostring (license_ident) != nil && size (license_ident) > 33 && IsLicenseAlreadyAccepted (license_ident)) {
	y2milestone ("License has been already accepted/shown");

	CleanUpLicense (tmpdir);
	return `accepted;
    } else {
	y2milestone ("License needs to be shown");
    }

    // bugzilla #303922
    // src_id == nil (the initial product license)
    if (src_id != nil) {
	// use wizard with steps
        if (Stage::initial()) {
	    // Wizard::OpenNextBackStepsDialog();
	    // WorkflowManager::RedrawWizardSteps();
	    y2milestone ("Initial stage, not opening any window...");
	// use normal wizard
	} else {
	    Wizard::OpenNextBackDialog();
	}
    }

    return `cont;
}

// Should have been named 'UpdateLicenseContentBasedOnSelectedLanguage' :->
void UpdateLicenseContent (map <string, string> & licenses, string id) {
    // read the selected language
    lic_lang = (string) UI::QueryWidget (`id (sformat ("license_language_%1", id)), `Value);
    term rp_id = `id (sformat ("license_contents_rp_%1", id));

    if (licenses == $[]) {
	licenses = all_licenses[id]:$[];
    }

    if (UI::WidgetExists (rp_id)) {
	UI::ReplaceWidget (rp_id, GetLicenseContent (lic_lang, licenses, id));
    } else {
	y2error ("No such widget: %1", rp_id);
    }
}

boolean AllLicensesAccepted () {
    // BNC #448598
    // If buttons don't exist, eula is automatically accepted
    boolean accepted = true;
    string eula_id = nil;

    foreach (string one_license_id, license_ids, {
	if (AcceptanceNeeded (one_license_id) != true) {
	    y2milestone ("License %1 does not need to be accepted", one_license_id);
	    return;
	}

	eula_id = sformat ("eula_%1", one_license_id);
	if ((boolean) UI::WidgetExists (`id (eula_id)) != true) {
	    y2error ("Widget %1 does not exist", eula_id);
	    return;
	}

	// All licenses have to be accepted
	string license_accepted = (string) UI::QueryWidget (`id (eula_id), `CurrentButton);
	y2milestone ("License %1 accepted: %2", eula_id, license_accepted);

	if (! regexpmatch (license_accepted, "^yes_")) {
	    accepted = false;
	    break;
	}
    });

    return accepted;
}

boolean AllLicensesAcceptedOrDeclined () {
    boolean ret = true;

    string eula_id = nil;
    foreach (string one_license_id, license_ids, {
	if (AcceptanceNeeded (one_license_id) != true)
	    return;

	eula_id = sformat ("eula_%1", one_license_id);
	if ((boolean) UI::WidgetExists (`id (eula_id)) != true) {
	    y2error ("Widget %1 does not exist", eula_id);
	}

	string current_button = (string) UI::QueryWidget(`id (eula_id), `CurrentButton);
	// license have to be accepted or declined
	if (current_button == nil) {
	    y2warning ("License %1 hasn't been accepted or declined", eula_id);
	    ret = false;
	    break;
	}
    });

    return ret;
}

symbol HandleLicenseDialogRet (map <string, string> & licenses, boolean base_product, string action) {
    any ret = nil;

    while (true) {
	ret = UI::UserInput();

	if (is (ret, string) && regexpmatch (tostring (ret), "^license_language_")) {
	    UpdateLicenseContent (licenses, GetId (tostring (ret)));
	    ret = `language;
	// bugzilla #303828
	// disabled next button unless yes/no is selected
	} else if (is (ret, string) && (regexpmatch (tostring(ret), "^yes_") || regexpmatch (tostring(ret), "^no_"))) {
	    if (AllLicensesAcceptedOrDeclined())
		Wizard::EnableNextButton();
	// Aborting the license dialog
	} else if (ret == `abort) {
	    // bugzilla #218677
	    if (base_product) {
		if (Popup::ConfirmAbort (`painless)) {
		    y2milestone("Aborting...");
		    ret = `abort;
		    break;
		}
	    } else {
		// popup question
		if (Popup::ContinueCancel(_("Really abort the add-on product installation?"))) {
		    y2milestone("Aborting...");
		    ret = `abort;
		    break;
		}
	    }
	} else if (ret == `next) {
	    // License declined
	    if (AllLicensesAccepted() != true)
	    {
		// message is void in case not accepting license doesn't stop the installation
		if (action == "continue")
		{
		    y2milestone ("action in case of license refusal is continue, not asking user");
		    ret = `accepted;
		    break;
		}
		// text changed due to bug #162499
		string refuse_popup_text = base_product
		    // text asking whether to refuse a license (Yes-No popup)
		    ? _("Refusing the license agreement cancels the installation.
Really refuse the agreement?")
		    // text asking whether to refuse a license (Yes-No popup)
		    : _("Refusing the license agreement cancels the add-on
product installation. Really refuse the agreement?");
		if (! Popup::YesNo(refuse_popup_text))
		{
		    continue;
		}
		else
		{
		    y2milestone("License has been declined.");
		    if (action == "abort")
		    {
			ret = `abort;
			break;
		    }
		    else if (action == "continue")
		    {
			ret = `accepted;
			break;
		    }
		    else if (action == "halt")
		    {
			ret = `halt;
			break;
			// timed ok/cancel popup
			if (!Popup::TimedOKCancel(_("The system is shutting down..."), 10))
			{
			    continue;
			}
			else
			{
			    ret = `halt;
			    break;
			}
		    }
		    else
		    {
			y2error ("Unknown action %1", action);
			ret = `abort;
			break;
		    }
		}
	    }
	    // License accepted
	    else
	    {
		y2milestone("All licenses have been accepted.");
		ret = `accepted;
		break;
	    }
        } else if (ret == `back) {
	    ret = `back;
	    break;
	} else {
	    y2error ("Unhandled input: %1", ret);
	}
    }

    return (symbol) ret;
}

/**
 * Generic cleanup
 */
void CleanUp() {
    // BNC #581933: All license IDs are cached while the module is in memory.
    // Removing them when leaving the license dialog.
    license_ids = [];
}

/**
 * Ask user to confirm license agreement
 * @param src_id integer repository to get the license from.
 *   If set to 'nil', the license is considered to belong to a base product
 * @param dir string directory to look for the license in if src_id is nil
 *   and not 1st stage installation
 * @param patterns a list of patterns for the files, regular expressions
 *   with %1 for the language
 * @param boolean enable_back sets the back_button status
 * @param boolean base_product defines whether it is a base or add-on product
 *   true means base product, false add-on product
 * @param require_agreement means that even if the license (or the very same license)
 *   has been already accepetd, ask user to accept it again (because of 'going back'
 *   in the installation proposal).
 * @param string id, usually source id but it can be any unique id in UI. Well, of course
 *   it must be string.
 */
global symbol AskLicenseAgreement (integer src_id, string dir,
    list<string> patterns, string action, boolean enable_back,
    boolean base_product, boolean require_agreement, string id)
{
    lic_lang = "";
    map <string, string> licenses = $[];
    list <string> available_langs = [];
    string license_ident = "";

    symbol init_ret = InitLicenseData (src_id, dir, licenses, available_langs, require_agreement, license_ident, id);

    if (init_ret == `auto || init_ret == `accepted) {
	y2milestone ("Returning %1", init_ret);
	return init_ret;
    }

    boolean created_new_dialog = false;

    // #459391
    // If a progress is running open another dialog
    if (Progress::IsRunning()) {
	y2milestone ("Some progress is running, opening new dialog for license...");
	Wizard::OpenNextBackDialog();
	created_new_dialog = true;
    }

    DisplayLicenseDialog (available_langs, enable_back, lic_lang, licenses, id /* license id */);

    // Display info as a popup if exists
    if (info_file != nil)
	InstShowInfo::show_info_txt (info_file);

    // initial loop
    symbol ret = nil;

    // set timeout for autoinstallation
    // bugzilla #206706
    if (Mode::autoinst()) {
	y2milestone("AutoYaST: License has been accepted automatically");
	ret = `accepted;
    } else {
	ret = HandleLicenseDialogRet (licenses, base_product, action);
    }

    if (ret == `accepted && license_ident != nil) {
	// store already accepted license ID
	LicenseHasBeenAccepted (license_ident);
    }

    CleanUpLicense (tmpdir);

    // bugzilla #303922
    if (created_new_dialog || (!Stage::initial() && src_id != nil)) {
	Wizard::CloseDialog();
    }

    CleanUp();

    return ret;
}

/**
 * Ask user to confirm license agreement
 * @param src_id integer repository to get the license from.
 *   If set to 'nil', the license is considered to belong to a base product
 * @param list <string> dirs - directories to look for the licenses
 * @param patterns a list of patterns for the files, regular expressions
 *   with %1 for the language
 * @param boolean enable_back sets the back_button status
 * @param boolean base_product defines whether it is a base or add-on product
 *   true means base product, false add-on product
 * @param require_agreement means that even if the license (or the very same license)
 *   has been already accepetd, ask user to accept it again (because of 'going back'
 *   in the installation proposal).
 */
global symbol AskLicensesAgreement (list <string> dirs,
    list<string> patterns, string action, boolean enable_back,
    boolean base_product, boolean require_agreement)
{
    if (dirs == nil || dirs == []) {
	y2error ("No directories: %1", dirs);
	// error message
	Report::Error ("Internal Error: No license to show");
	return `auto;
    }

    symbol init_ret = nil;

    if (init_ret == `auto || init_ret == `accepted) {
	y2milestone ("Returning %1", init_ret);
	return init_ret;
    }

    boolean created_new_dialog = false;

    // #459391
    // If a progress is running open another dialog
    if (Progress::IsRunning()) {
	y2milestone ("Some progress is running, opening new dialog for license...");
	Wizard::OpenNextBackDialog();
	created_new_dialog = true;
    }

    // dialog caption
    string caption = _("License Agreement");

    list <string> license_idents = [];

    // initial loop
    symbol ret = nil;

    list <map <string, string> > licenses = [];
    integer counter = -1;
    term contents = `VBox();
    // If acceptance is not needed, there's no need to disable the button
    // by default
    boolean default_next_button_state = true;

    foreach (string dir, dirs, {
	counter = counter + 1;
	licenses[counter] = $[];

	lic_lang = "";
	list <string> available_langs = [];
	string license_ident = "";
	map <string, string> tmp_licenses = $[];

	symbol init_ret = InitLicenseData (nil, dir, tmp_licenses, available_langs, require_agreement, license_ident, dir);

	if (license_ident != nil)
	    license_idents = add (license_idents, license_ident);

	term license_term = GetLicenseDialog (available_langs, lic_lang, tmp_licenses, dir, true);
	if (license_term == nil) {
	    y2error ("Oops, license term is: %1", license_term);
	} else {
	    contents = add (contents, license_term);
	}

	// Display info as a popup if exists
	if (info_file != nil)
	    InstShowInfo::show_info_txt (info_file);

	licenses[counter] = tmp_licenses;

	if (AcceptanceNeeded (dir))
	    default_next_button_state = false;
    });

    SCR::Write (.target.ycp, "/tmp/a", contents);
    Wizard::SetContents(caption, contents, GetLicenseDialogHelp(),
	enable_back, default_next_button_state);

    Wizard::SetTitleIcon ("yast-license");
    Wizard::SetFocusToNextButton();

    // set timeout for autoinstallation
    // bugzilla #206706
    if (Mode::autoinst()) {
	y2milestone("AutoYaST: License has been accepted automatically");
	ret = `accepted;
    } else {
	map <string, string> tmp_licenses = $[];
	ret = HandleLicenseDialogRet (tmp_licenses, base_product, action);
	y2milestone ("Dialog ret: %1", ret);
    }

    // store already accepted license IDs
    if (ret == `accepted) {
	foreach (string license_ident, license_idents, {
	    LicenseHasBeenAccepted (license_ident);
	});
    }

    CleanUpLicense (tmpdir);

    // bugzilla #303922
    if (created_new_dialog || !Stage::initial()) {
	Wizard::CloseDialog();
    }

    CleanUp();

    return ret;
}

global symbol AskAddOnLicenseAgreement (integer src_id) {
    return AskLicenseAgreement (src_id, "",
	license_patterns,
	"abort",
	// back button is disabled
	false, false, false,
	tostring(src_id));
}

global symbol AskFirstStageLicenseAgreement (integer src_id, string action) {
    // bug #223258
    // disabling back button when the select-language dialog is skipped
    //
    boolean enable_back = true;
    if (Language::selection_skipped)
	enable_back = false;

    return AskLicenseAgreement (nil /* base product */, "",
	license_patterns,
	action,
	// back button is enabled
	enable_back, true, true,
	// unique id
	tostring (src_id));
}

// FIXME: map <string, boolean> ...
map <string, boolean> info_file_already_seen = $[];

/**
 * Called from the first stage Welcome dialog by clicking on a button
 */
global boolean ShowFullScreenLicenseInInstallation (any replace_point_ID, integer src_id) {
    lic_lang = "";
    map <string, string> licenses = $[];
    list <string> available_langs = [];
    string license_ident = "";

    symbol init_ret = InitLicenseData (nil /* base product */, "", licenses, available_langs, true, license_ident, tostring (src_id));

    // Replaces the dialog content with Languages combo-box
    // and the current license text (richtext)
    UI::ReplaceWidget (
	`id (replace_point_ID),
	GetLicenseDialogTerm (available_langs, lic_lang, licenses, tostring(src_id))
    );

    any ret = nil;

    while (true)
    {
	ret = UI::UserInput();

	if (is (ret, string) && regexpmatch (tostring (ret), "^license_language_[[:digit:]]+")) {
	    UpdateLicenseContent (licenses, GetId (tostring (ret)));
	} else {
	    break;
	}
    }

    CleanUp();

    return true;
}

/**
 * Used in the first-stage Welcome dialog
 */
global boolean ShowLicenseInInstallation (any replace_point_ID, integer src_id) {
    lic_lang = "";
    map <string, string> licenses = $[];
    list <string> available_langs = [];
    string license_ident = "";

    symbol init_ret = InitLicenseData (nil /* base product */, "", licenses, available_langs, true, license_ident, tostring (src_id));

    term rt = GetLicenseContent (lic_lang, licenses, tostring (src_id));
    UI::ReplaceWidget (`id (replace_point_ID), rt);

    string id = tostring (src_id);

    // Display info as a popup if exists
    if (info_file != nil && info_file_already_seen[id]:false != true) {
	if (Mode::autoinst()) {
	    y2milestone ("Autoinstallation: Skipping info file...");
	} else {
	    InstShowInfo::show_info_txt (info_file);
	    info_file_already_seen[id] = true;
	}
    }

    CleanUp();

    return true;
}

global symbol AskInstalledLicenseAgreement (string directory, string action) {
    // patterns are hard-coded
    return AskLicenseAgreement (nil, directory, [], action, false, true, false, directory);
}

// FATE #306295: More licenses in one dialog
global symbol AskInstalledLicensesAgreement (list <string> directories, string action) {
    // patterns are hard-coded
    return AskLicensesAgreement (directories, [], action, false, true, false);
}

} // EOF

ACC SHELL 2018