<?php

// Copyright (C) 2017-2024 Combodo SAS
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>

/**
 * iTop Hub Launch Page
 * Collect the information to be posted to iTopHub
 *
 * @copyright Copyright (c) 2017-2024 Combodo SAS
 * @license http://opensource.org/licenses/AGPL-3.0
 */

/**
 * Collect the configuration information to be posted to the hub
 *
 * @return string[][]
 */

/*
 *
 * json schema available on itop hub, under this url : /bundles/combodoremoteitop/json_schema/itop-configuration.schema.json
 *
 * syntactically valid fictional file sample, :
 *
 *
 * {
 * "itop_hub_target_route": "view_dashboard|browse_extensions|deploy_extensions|read_documentation"
 * ,
 * "itop_stack":{
 * 		"uuidBdd" : "11a90082-b8a6-11e6-90f5-56524dec1a20",
 * 		"uuidFile" : "11a90082-b8a6-11e6-90f5-56352dec71a2",
 * 		"instance_friendly_name" : "example friendly name",
 * 		"instance_host" : "http://example.com",
 * 		"application_name" : "iTop",
 * 		"application_version" : "2.4.0",
 * 		"itop_user_id" : "42",
 * 		"itop_user_lang" : "fr",
 * 		"itop_modules" : {
 * 			"foo-bar": "1.0.1",
 * 			"barBaz": "1.3-dev"
 * 		},
 * 		"itop_extensions" : {
 * 			"itop-extra-extension": {
 * 				"label": "Super Nice Addon for iTop",
 * 				"value": "1.2.0"
 * 			},
 * 			"itop-hyper-extension": {
 * 				"label": "Hyper Fabulous Extension",
 * 				"value": "2.5.1"
 * 			},
 * 		},
 * 		"itop_installation_options" : {
 * 			"itop-service-mgmt": {
 * 				"label": "Service Management for Enterprises",
 * 				"value": "2.4.0"
 * 			},
 * 			"itop-simple-tickets": {
 * 				"label": "Simple Ticket Managment",
 * 				"value": "2.4.0"
 * 			},
 * 		}
 * },
 *
 * "server_stack":{
 * 		"os_name": "Linux",
 * 		"web_server_name": "apache",
 * 		"web_server_version": "2.4.12",
 * 		"database_name": "MySQL",
 * 		"database_version": "5.7-ubuntu",
 * 		"database_settings":{
 * 			"max_allowed_packet": "314116"
 * 		},
 * 		"php_version": "7.1",
 * 		"php_settings":{
 * 			"timezone": "Europe/Paris",
 * 			"memory_limit": "128M"
 * 		},
 * 		"php_extensions":{
 * 			"php-mysql": "1.2",
 * 			"php-mcrypt": "3.1.6"
 * 		}
 * 	}
 * }
 *
 *
 */

use Combodo\iTop\Application\WebPage\ErrorPage;
use Combodo\iTop\Application\WebPage\NiceWebPage;

/**
 * Return a cleaned (i.e.
 * properly truncated) versin number from
 * a very long version number like "7.0.18-0unbuntu0-16.04.1"
 *
 * @param string $sString
 *
 * @return string
 */
function CleanVersionNumber($sString)
{
	$aMatches = [];
	if (preg_match("|^([0-9\\.]+)-|", $sString, $aMatches)) {
		return $aMatches[1];
	}

	return $sString;
}

function collect_configuration()
{
	$aConfiguration = [
		'php' => [],
		'mysql' => [],
		'apache' => [],
	];

	// Database information
	$m_oMysqli = CMDBSource::GetMysqli();
	$aConfiguration['database_settings']['server'] = (string)$m_oMysqli->server_version;
	$aConfiguration['database_settings']['client'] = (string)$m_oMysqli->client_version;

	/** @var mysqli_result $resultSet */
	$result = CMDBSource::Query('SHOW VARIABLES LIKE "%max_allowed_packet%"');
	if ($result) {
		$row = $result->fetch_object();
		$aConfiguration['database_settings']['max_allowed_packet'] = (string)$row->Value;
	}

	/** @var mysqli_result $resultSet */
	$result = CMDBSource::Query('SHOW VARIABLES LIKE "%version_comment%"');
	if ($result) {
		$row = $result->fetch_object();
		if (preg_match('/mariadb/i', $row->Value)) {
			$aConfiguration['database_name'] = 'MariaDB';
		}
	}

	// Web server information
	if (function_exists('apache_get_version')) {
		$aConfiguration['web_server_name'] = 'apache';
		$aConfiguration['web_server_version'] = apache_get_version();
	} else {
		// The format of the variable $_SERVER["SERVER_SOFTWARE"] seems to be the following:
		// PHP 7 FPM with Apache on Ubuntu: "Apache/2.4.18 (Ubuntu)"
		// IIS 7.5 on Windows 7:            "Microsoft-IIS/7.5"
		// Nginx with PHP FPM on Ubuntu:    "nginx/1.10.0"
		$aConfiguration['web_server_name'] = substr($_SERVER["SERVER_SOFTWARE"], 0, strpos($_SERVER["SERVER_SOFTWARE"], '/'));
		$sWebServerVersion = trim(substr($_SERVER["SERVER_SOFTWARE"], 1 + strpos($_SERVER["SERVER_SOFTWARE"], '/')));
		if ($sWebServerVersion == '') {
			$sWebServerVersion = 'Unknown';
		}
		$aConfiguration['web_server_version'] = $sWebServerVersion;
	}

	// PHP extensions
	if (!MetaModel::GetConfig()->GetModuleSetting('itop-hub-connector', 'php_extensions_enable', true)) {
		$aConfiguration['php_extensions'] = [];
	} else {
		foreach (get_loaded_extensions() as $extension) {
			$aConfiguration['php_extensions'][$extension] = $extension;
		}
	}

	// Collect some PHP settings having a known impact on iTop
	$aIniGet = MetaModel::GetConfig()->GetModuleSetting('itop-hub-connector', 'php_settings_array', []); // by default, on the time of the writting, it values are : array('post_max_size', 'upload_max_filesize', 'apc.enabled', 'timezone', 'memory_limit', 'max_execution_time');
	$aConfiguration['php_settings'] = [];
	foreach ($aIniGet as $iniGet) {
		$aConfiguration['php_settings'][$iniGet] = (string)ini_get($iniGet);
	}

	// iTop modules
	$oConfig = MetaModel::GetConfig();
	$aInstalledModules = ModuleInstallationRepository::GetInstance()->ReadFromDB($oConfig);

	foreach ($aInstalledModules as $aDBInfo) {
		$aConfiguration['itop_modules'][$aDBInfo['name']] = $aDBInfo['version'];
	}

	// iTop Installation Options, i.e. "Extensions"
	$oExtensionMap = iTopExtensionsMap::GetExtensionsMap();
	$oExtensionMap->LoadChoicesFromDatabase($oConfig);
	$aConfiguration['itop_extensions'] = [];
	foreach ($oExtensionMap->GetChoices() as $oExtension) {
		switch ($oExtension->sSource) {
			case iTopExtension::SOURCE_MANUAL:
			case iTopExtension::SOURCE_REMOTE:
				$aConfiguration['itop_extensions'][$oExtension->sCode] = [
					'label' => $oExtension->sLabel,
					'value' => $oExtension->sInstalledVersion,
				];
				break;

			default:
				$aConfiguration['itop_installation_options'][$oExtension->sCode] = [
					'label' => $oExtension->sLabel,
					'value' => true,
				];
		}
	}

	return $aConfiguration;
}

function MakeDataToPost($sTargetRoute)
{
	if (MetaModel::GetConfig()->Get('demo_mode')) {
		// Don't expose such information in demo mode
		$aDataToPost = ['disabled' => true, 'reason' => 'demo_mode'];
	} else {
		$aConfiguration = collect_configuration();

		$aDataToPost = [
			'itop_hub_target_route' => $sTargetRoute,
			'itop_stack' => [
				"uuidBdd" => (string)trim(DBProperty::GetProperty('database_uuid', ''), '{}'), // TODO check if empty and... regenerate a new UUID ??
				"uuidFile" => (string)trim(@file_get_contents(utils::GetDataPath()."instance.txt"), "{} \n"), // TODO check if empty and... regenerate a new UUID ??
				'instance_friendly_name' => (string)$_SERVER['SERVER_NAME'],
				'instance_host' => (string)utils::GetAbsoluteUrlAppRoot(),
				'application_name' => (string)ITOP_APPLICATION,
				'application_version' => (string)ITOP_VERSION,
				'application_version_full' => (string)Dict::Format('UI:iTopVersion:Long', ITOP_APPLICATION, ITOP_VERSION, ITOP_REVISION, ITOP_BUILD_DATE),
				'itop_user_id' => (string)(UserRights::GetUserId() === null) ? "1" : UserRights::GetUserId(),
				'itop_user_lang' => (string)UserRights::GetUserLanguage(),
				'itop_modules' => (object)$aConfiguration['itop_modules'],
				'itop_extensions' => (object)$aConfiguration['itop_extensions'],
				'itop_installation_options' => (object)$aConfiguration['itop_installation_options'],
			],
			'server_stack' => [
				'os_name' => (string)PHP_OS,
				'web_server_name' => (string)$aConfiguration['web_server_name'],
				'web_server_version' => (string)$aConfiguration['web_server_version'],
				'database_name' => (string)isset($aConfiguration['database_name']) ? $aConfiguration['database_name'] : 'MySQL', // if we do not detect MariaDB, we assume this is mysql
				'database_version' => (string)CMDBSource::GetDBVersion(),
				'database_settings' => (object)$aConfiguration['database_settings'],
				'php_version' => (string)CleanVersionNumber(phpversion()),
				'php_settings' => (object)$aConfiguration['php_settings'],
				'php_extensions' => (object)$aConfiguration['php_extensions'],
			],
		];
	}

	return $aDataToPost;
}

try {
	require_once(APPROOT.'/application/application.inc.php');
	require_once(APPROOT.'/setup/extensionsmap.class.inc.php');
	require_once('hubconnectorpage.class.inc.php');

	require_once(APPROOT.'/application/startup.inc.php');
	require_once('TokenValidation.php');

	$sTargetRoute = utils::ReadParam('target', ''); // ||browse_extensions|deploy_extensions|

	if ($sTargetRoute != 'inform_after_setup') {
		require_once(APPROOT.'/application/loginwebpage.class.inc.php');
		LoginWebPage::DoLoginEx(null, true /* $bMustBeAdmin */); // Check user rights and prompt if needed
	}

	$sHubUrlStateless = MetaModel::GetModuleSetting('itop-hub-connector', 'url').MetaModel::GetModuleSetting('itop-hub-connector', 'route_landing_stateless');
	$sHubUrl = MetaModel::GetModuleSetting('itop-hub-connector', 'url').MetaModel::GetModuleSetting('itop-hub-connector', 'route_landing');

	// Display... or not... the page

	switch ($sTargetRoute) {
		case 'inform_after_setup':
			// Hidden IFRAME at the end of the setup
			$sParamToken = utils::ReadParam('setup_token');
			$oTokenValidation = new TokenValidation();
			$bIsTokenValid = $oTokenValidation->isSetupTokenValid($sParamToken);
			if (UserRights::IsAdministrator() || $bIsTokenValid) {
				$oPage = new NiceWebPage('');
				$aDataToPost = MakeDataToPost($sTargetRoute);
				$oPage->add('<form id="hub_launch_form" action="'.$sHubUrlStateless.'" method="post">');
				$oPage->add('<input type="hidden" name="json" value="'.utils::EscapeHtml(json_encode($aDataToPost)).'">');
				$oPage->add_ready_script('$("#hub_launch_form").trigger(\'submit\');');
			} else {
				IssueLog::Error('TokenValidation failed on inform_after_setup page');
				throw new Exception("Not allowed");
			}
			break;

		default:
			// All other cases, special "Hub like" web page
			if ($sTargetRoute == 'view_dashboard') {
				$sTitle = Dict::S('Menu:iTopHub:Register');
				$sLabel = Dict::S('Menu:iTopHub:Register+');
				$sText = Dict::S('Menu:iTopHub:Register:Description');
			} else {
				$sTitle = Dict::S('Menu:iTopHub:BrowseExtensions');
				$sLabel = Dict::S('Menu:iTopHub:BrowseExtensions+');
				$sText = Dict::S('Menu:iTopHub:BrowseExtensions:Description');
			}
			$sLogoUrl = utils::GetAbsoluteUrlModulesRoot().'/itop-hub-connector/images/itophub-logo.svg';
			$sArrowUrl = utils::GetAbsoluteUrlModulesRoot().'/itop-hub-connector/images/white-arrow-right.svg';
			$sCloseUrl = utils::GetAbsoluteUrlModulesRoot().'/itop-hub-connector/images/black-close.svg';

			$oPage = new HubConnectorPage(Dict::S('iTopHub:Connect'));
			$oPage->LinkScriptFromModule('itop-hub-connector/js/hub.js');
			$oPage->LinkStylesheetFromAppRoot('css/font-combodo/font-combodo.css');
			$oPage->LinkStylesheetFromModule('itop-hub-connector/css/hub.css');

			$aDataToPost = MakeDataToPost($sTargetRoute);

			if (MetaModel::GetConfig()->Get('demo_mode')) {
				$oPage->add("<div class=\"header_message message_info\">Sorry, iTop is in <b>demonstration mode</b>: the connection to iTop Hub is disabled.</div>");
			}

			$oPage->add('<div id="hub_top_banner"></div>');
			$oPage->add('<div id="hub_launch_content">');
			$oPage->add('<div id="hub_launch_container">');
			$oPage->add('<div id="hub_launch_image">');
			$oPage->add(file_get_contents(__DIR__.'/images/rocket.svg'));
			$oPage->add('</div>');
			$oPage->add('<h1 class="ibo-title--text title is-size-3"><img src="'.$sLogoUrl.'"><span>'.$sTitle.'</span></h1>');
			$oPage->add($sText);
			$sButtonLabelClose = Dict::S('iTopHub:CloseBtn');
			$sButtonLabelGo = Dict::S('iTopHub:GoBtn');
			$sButtonLabelTooltip = Dict::S('iTopHub:GoBtn:Tooltip');
			$oPage->add(
				<<<HTML
<p>
<button type="button" class="ibo-button" id="CancelBtn" title="Go back to iTop"><img src="$sCloseUrl"><span class="ibo-button--label">$sButtonLabelClose</span></button>
<span class="horiz-spacer"></span>
<button type="button" class="ibo-button positive" id="GoToHubBtn" title="$sButtonLabelTooltip"><span class="ibo-button--label">$sButtonLabelGo</span><img src="$sArrowUrl"></button>
</p>
HTML
			);
			$sFormTarget = appUserPreferences::GetPref('itophub_open_in_new_window', 1) ? 'target="_blank"' : '';
			$oPage->add('<form '.$sFormTarget.' id="hub_launch_form" action="'.$sHubUrl.'" method="post">');
			$oPage->add('<input type="hidden" name="json" value="'.utils::EscapeHtml(json_encode($aDataToPost)).'">');

			// $sNewWindowChecked = appUserPreferences::GetPref('itophub_open_in_new_window', 1) == 1 ? 'checked' : '';
			// $oPage->add('<p><input type="checkbox" class="userpref" id="itophub_open_in_new_window" '.$sNewWindowChecked.'><label for="itophub_open_in_new_window">'.Dict::S('iTopHub:OpenInNewWindow').'</label><br/>');

			// Beware the combination auto-submit and open in new window (cf above) is blocked by (some) browsers (namely Chrome)
			$sAutoSubmitChecked = appUserPreferences::GetPref('itophub_auto_submit', 0) == 1 ? 'checked' : '';
			$oPage->add('<label><input type="checkbox" class="userpref" id="itophub_auto_submit" '.$sAutoSubmitChecked.'>&nbsp;'.Dict::S('iTopHub:AutoSubmit').'</label></p>');
			$oPage->add('</form>');
			$oPage->add('<div style="clear:both"></div>');
			$oPage->add('</div>');
			$oPage->add('</div>');

			$oPage->add_ready_script('$(".userpref").on("click", function() { var value = $(this).prop("checked") ? 1 : 0; var code = $(this).attr("id"); SetUserPreference(code, value, true); });');
			if (MetaModel::GetConfig()->Get('demo_mode')) {
				$oPage->add_ready_script(
					<<<JS
			$("#GoToHubBtn").prop('disabled', true);
			$("#itophub_auto_submit").prop('disabled', true).prop('checked', false);
			$("#CancelBtn").on("click", function() {
			    window.history.back();
			});
JS
				);
			} else {
				$oPage->add_ready_script(
					<<<JS
$("#GoToHubBtn").on("click", function() {
	$(this).prop('disabled', true);
	$("#hub_launch_image").addClass("animate");
	window.setTimeout(function () {
		var bNewWindow = $('#itophub_open_in_new_window').prop("checked");
		if(bNewWindow) { $("#hub_launch_form").attr("target", "_blank"); } else { $("#hub_launch_form").removeAttr("target"); }
		$('#hub_launch_form').trigger('submit');
		window.setTimeout(function () {
			$("#GoToHubBtn").prop('disabled', false);
			$("#hub_launch_image").removeClass("animate");
		}, 5000);
	}, 1000);
});
$("#CancelBtn").on("click", function() {
    window.history.back();
});
JS
				);
			}

			if (appUserPreferences::GetPref('itophub_auto_submit', 0) == 1) {
				$oPage->add_ready_script('$("#GoToHubBtn").trigger("click");');
			}

			if (utils::ReadParam('show-json', false)) {
				$oPage->add('<h1>DEBUG : json</h1>');
				$oPage->add('<pre class="wizContainer">'.json_encode($aDataToPost, JSON_PRETTY_PRINT).'</pre>');
			}
	}

	$oPage->output();
} catch (CoreException $e) {
	require_once(APPROOT.'/setup/setuppage.class.inc.php');
	$oP = new ErrorPage(Dict::S('UI:PageTitle:FatalError'));
	$oP->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n");
	$oP->error(Dict::Format('UI:Error_Details', $e->getHtmlDesc()));
	$oP->output();

	if (MetaModel::IsLogEnabledIssue()) {
		if (MetaModel::IsValidClass('EventIssue')) {
			$oLog = new EventIssue();

			$oLog->Set('message', $e->getMessage());
			$oLog->Set('userinfo', '');
			$oLog->Set('issue', $e->GetIssue());
			$oLog->Set('impact', 'Page could not be displayed');
			$oLog->Set('callstack', $e->getTrace());
			$oLog->Set('data', $e->getContextData());
			$oLog->DBInsertNoReload();
		}

		IssueLog::Error($e->getMessage());
	}

	// For debugging only
	// throw $e;
} catch (Exception $e) {
	require_once(APPROOT.'/setup/setuppage.class.inc.php');
	$oP = new ErrorPage(Dict::S('UI:PageTitle:FatalError'));
	$oP->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n");
	$oP->error(Dict::Format('UI:Error_Details', $e->getMessage()));
	$oP->output();

	if (MetaModel::IsLogEnabledIssue()) {
		if (MetaModel::IsValidClass('EventIssue')) {
			$oLog = new EventIssue();

			$oLog->Set('message', $e->getMessage());
			$oLog->Set('userinfo', '');
			$oLog->Set('issue', 'PHP Exception');
			$oLog->Set('impact', 'Page could not be displayed');
			$oLog->Set('callstack', $e->getTrace());
			$oLog->Set('data', []);
			$oLog->DBInsertNoReload();
		}

		IssueLog::Error($e->getMessage());
	}
}
