Dependency Graph

Dependency Graph
related to related to child of child of duplicate of duplicate of

View Issue Details

IDProjectCategoryView StatusLast Update
0034783mantisbtinstallationpublic2025-03-07 19:10
Reportervboctor Assigned Todregad  
PrioritynormalSeverityminorReproducibilityalways
Status closedResolutionfixed 
Product Version2.26.1 
Target Version2.27.1Fixed in Version2.27.1 
Summary0034783: Checking URL to installation is failing
Description

Starting with an empty db and no config_inc.php file. MantisBT is hosted locally on Mac OS under https://mantisbt/. The server is MAMP PRO running Apache and PHP 8.1.13

The content returned is null for the URL.

The curl extension is installed.

It doesn't seem that the curl extension or the curl command are set up to follow redirects. But even if a follow redirect is added (e.g. curl -L and the appropriate option in the curl extension options), it doesn't successfully follow the URL.

Overriding hard fail to false, installs successfully.

TagsNo tags attached.

Relationships

has duplicate 0035261 closeddregad Checking URL to installation: BAD Web page at 'https://www.foo.net/bar/mantisbt/' does not appear to be a MantisBT site. 
has duplicate 0035282 closeddregad After updating from 2.26.4 to 2.27.0 unable to select Category 
related to 0034782 closeddregad Can't enable category General since 2.27.0 
related to 0035041 closeddregad BAD Can't retrieve web page at 
related to 0035540 resolveddregad A clean installation ends with Internal Server Error with no message/detail given 
related to 0035550 confirmed Use GuzzleHttp request in url_get() 

Activities

dregad

dregad

2024-09-30 04:22

developer   ~0069295

The URL check in the installer was introduced in 2.26.1, as part of fixing security issue 0019381 (see commit MantisBT master-2.26 7055731d), so this should not be something new with 2.27.0.

The content returned is null for the URL.

Can you clarify whether that is an actual NULL, a boolean FALSE or an empty string ? I tested this under various scenarios, but never faced an issue like you describe. As far as I know, only shell_exec() can return NULL, this means that somehow the libcurl call failed (returned false), it would be interesting to know why.

Would you be able to trace what is happening in url_get() and provide details ?

As a side note, the code of url_get() is really dated, we should probably replace that by a GuzzleHttp Request.

Seppeltronics

Seppeltronics

2025-01-27 17:20

reporter   ~0069764

Last edited: 2025-01-27 17:21

@dregad please look at the code,

if( !$f_path ) {
...Other Code that does not effect t_hard_fail
$t_hard_fail = false;
}
else {
...Other Code that does not effect t_hard_fail
$t_hard_fail = true;
}


1) url_get() does not effect t_hard_fail
2) url_get() when entered a valid URL does not seem to return a URL, but a newline, linefeed,... .


It may have been introduced in 2.26.1, but please give us some facts, "Checking URL to installation" fails with valid URL's, that is a valid statement I can confirm. Being old does not mean there could not be a bug?

dregad

dregad

2025-01-29 12:08

developer   ~0069771

My comment about this not being new, was because this was originally reported as a problem with 2.27.0, when in fact it was introduced in 2.26.1. Product version has been changed after that.

For the record, I never said there was no bug, just that I was unable to reproduce it.

url_get() when entered a valid URL does not seem to return a URL, but a newline, linefeed

Can you tell me the actual return value from url_get() on your system ? You can add var_dump( $t_page_contents ); before theif( !$t_page_contents ) {` at line 498 of install.php, and post the output here ? I need to know if it's NULL, FALSE, an empty string or something else.

Seppeltronics

Seppeltronics

2025-01-29 13:58

reporter   ~0069772

Where do I find the sting that it dumps?

What I found was something on the page, you have to tell me where I can dump something with PHP, I never implemented anything in PHP so far
(I'll attach the screenshot as well as the code used):

string(1271) "
"

install_2270_ID34783_test1.php (48,926 bytes)   
<?php
# MantisBT - A PHP based bugtracking system

# MantisBT is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# MantisBT 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with MantisBT.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Mantis Database installation process
 *
 * @package MantisBT
 * @copyright Copyright 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
 * @copyright Copyright 2002  MantisBT Team - mantisbt-dev@lists.sourceforge.net
 * @link http://www.mantisbt.org
 *
 * @noinspection PhpMethodParametersCountMismatchInspection For some reason,
 *  PHPStorm incorrectly matches the ADOConnection object to the ADODB_text
 *  subclass, which has a different signature for Connect() method; this
 *  inconsistency should be fixed in a future version of ADOdb, but for now we
 *  can just suppress the inspection to avoid the false positive warnings.
 */

error_reporting( E_ALL );
@set_time_limit( 0 );

# Load the MantisDB core in maintenance mode. This mode will assume that
# config/config_inc.php hasn't been specified. Thus the database will not be opened
# and plugins will not be loaded.
const MANTIS_MAINTENANCE_MODE = true;

require_once( dirname( __DIR__ ) . '/core.php' );
require_api( 'install_helper_functions_api.php' );
require_api( 'crypto_api.php' );
$g_error_send_page_header = false; # bypass page headers in error handler

$g_failed = false;
$g_database_upgrade = false;

/**
 * Print Test result
 *
 * @param integer $p_result    Result - BAD|GOOD.
 * @param boolean $p_hard_fail Fail installation or soft warning.
 * @param string  $p_message   Message to display to user.
 * @return void
 */
function print_test_result( $p_result, $p_hard_fail = true, $p_message = '' ) {
	global $g_failed;
	echo '<td ';
	if( BAD == $p_result ) {
		if( $p_hard_fail ) {
			$g_failed = true;
			echo 'class="danger">BAD';
		} else {
			echo 'class="warning">POSSIBLE PROBLEM';
		}
		if( '' != $p_message ) {
			echo '<br />' . $p_message;
		}
	}

	if( GOOD == $p_result ) {
		echo 'class="success">GOOD';
	}
	echo '</td>';
}

/**
 * Print Test result
 *
 * @param string  $p_test_description Test Description.
 * @param integer $p_result           Result - BAD|GOOD.
 * @param boolean $p_hard_fail        Fail installation or soft warning.
 * @param string  $p_message          Message to display to user.
 * @return void
 */
function print_test( $p_test_description, $p_result, $p_hard_fail = true, $p_message = '' ) {
	echo '<tr><td>' . $p_test_description . '</td>';
	print_test_result( $p_result, $p_hard_fail, $p_message );
	echo '</tr>' . "\n";
}

/**
 * @param $p_db_type
 * @return void
 */
function print_db_support_tests( $p_db_type ): void {
	print_test( 'Checking PHP support for database type',
		db_check_database_support( $p_db_type ),
		true,
		'Database is not supported by PHP. Check that it has been compiled into your server.'
	);

	switch( $p_db_type ) {
		case 'mssql':
			print_test( 'Checking PHP support for Microsoft SQL Server driver',
				BAD,
				true,
				'mssql driver is no longer supported in PHP >= 5.3, please use mssqlnative instead'
			);
			break;
		case 'mysql':
			# Legacy mysql driver
			print_test( 'Checking PHP support for MySQL driver',
				BAD,
				true,
				'mysql driver is deprecated as of PHP 5.5.0, and has been removed as of PHP 7.0.0. The driver is no longer supported by MantisBT, please use mysqli instead'
			);
			break;
		case 'mysqli':
			print_test( 'Checking PHP support for MySQL Native Driver',
				function_exists( 'mysqli_stmt_get_result' ),
				true,
				'Check that the MySQL Native Driver (mysqlnd) has been compiled into your server.'
			);
			break;
	}
}

# install_state
#   0 = no checks done
#   1 = server ok, get database information
#   2 = check the database information
#   3 = install the database
#   4 = get additional config file information
#   5 = write the config file
#	6 = post install checks
#	7 = done, link to login or db updater
$t_install_state = gpc_get_int( 'install', 0 );

layout_page_header_begin( 'Administration - Installation' );
# Javascript is only needed to support input of installation options
if( $t_install_state <= 2 ) {
	html_javascript_link( 'install.js' );
}
layout_page_header_end();

layout_admin_page_begin();
?>
<div class="col-md-12 col-xs-12">
	<div class="space-10"></div>

	<div class="page-header">
	<h1>
		<?php
switch( $t_install_state ) {
	case 7:
		echo 'Installation Complete';
		break;
	case 6:
		echo 'Post Installation Checks';
		break;
	case 5:
		echo 'Install Configuration File';
		break;
	case 4:
		echo 'Additional Configuration Information';
		break;
	case 3:
		echo 'Install Database';
		break;
	case 2:
		echo 'Check and Install Database';
		break;
	case 1:
		echo 'Database Parameters';
		break;
	case 0:
	default:
		$t_install_state = 0;
		echo 'Pre-Installation Check';
		break;
}
?>
		<div class="btn-group pull-right">
			<a class="btn btn-sm btn-primary btn-white btn-round" href="index.php">Back to Administration</a>
		</div>
	</h1>
	</div>
</div>
<?php
# installation checks table header is valid both for pre-install and
# database installation steps
if( 0 == $t_install_state || 2 == $t_install_state ) {
	?>
<div class="col-md-12 col-xs-12">
<div class="space-10"></div>

<div class="widget-box widget-color-blue2">
<div class="widget-header widget-header-small">
	<h4 class="widget-title lighter">
		Checking Installation
	</h4>
</div>

<div class="widget-body">
<div class="widget-main no-padding">
<div class="table-responsive">
<table class="table table-bordered table-condensed">
<?php
}

global $g_config_path;
$t_config_filename = $g_config_path . 'config_inc.php';
$t_config_exists = file_exists( $t_config_filename );

# Initialize Oracle-specific values for prefix and suffix, and set
# values for other db's as per config defaults
$t_prefix_defaults = array(
	'oci8' => array(
		'db_table_prefix'        => 'm',
		'db_table_plugin_prefix' => 'plg',
		'db_table_suffix'        => '',
	) ,
);
foreach( $t_prefix_defaults['oci8'] as $t_key => $t_value ) {
	$t_prefix_defaults['other'][$t_key] = config_get( $t_key, '' );
}

/**
 * @var string $f_db_table_prefix'
 * @var string $f_db_table_plugin_prefix
 * @var string $f_db_table_suffix
 */
if( $t_config_exists && $t_install_state <= 1 ) {
	# config already exists - probably an upgrade
	$f_dsn                    = config_get( 'dsn', '' );
	$f_hostname               = config_get_global( 'hostname', '' );
	$f_db_type                = config_get_global( 'db_type', '' );
	$f_database_name          = config_get_global( 'database_name', '' );
	$f_db_username            = config_get_global( 'db_username', '' );
	$f_db_password            = config_get_global( 'db_password', '' );
	$f_timezone               = config_get( 'default_timezone', '' );
	$f_path                   = config_get_global( 'path', '' );

	# Set default prefix/suffix form variables ($f_db_table_XXX)
	$t_prefix_type = 'other';
	foreach( $t_prefix_defaults[$t_prefix_type] as $t_key => $t_value ) {
		${'f_' . $t_key} = $t_value;
	}
} else {
	# read control variables with defaults
	$f_dsn                = gpc_get( 'dsn', config_get( 'dsn', '' ) );
	$f_hostname           = gpc_get( 'hostname', config_get_global( 'hostname', 'localhost' ) );
	$f_db_type            = gpc_get( 'db_type', config_get_global( 'db_type', '' ) );
	$f_database_name      = gpc_get( 'database_name', config_get_global( 'database_name', 'bugtracker' ) );
	$f_db_username        = gpc_get( 'db_username', config_get_global( 'db_username', '' ) );
	$f_db_password        = gpc_get( 'db_password', config_get_global( 'db_password', '' ) );
	if( CONFIGURED_PASSWORD == $f_db_password ) {
		$f_db_password = config_get_global( 'db_password' );
	}
	$f_timezone           = gpc_get( 'timezone', config_get( 'default_timezone' ) );
	$f_path               = gpc_get( 'path', config_get_global( 'path', '' ) );

	# Set default prefix/suffix form variables ($f_db_table_XXX)
	$t_prefix_type = $f_db_type == 'oci8' ? $f_db_type : 'other';
	foreach( $t_prefix_defaults[$t_prefix_type] as $t_key => $t_value ) {
		${'f_' . $t_key} = gpc_get( $t_key, $t_value );
	}
}
$f_admin_username = gpc_get( 'admin_username', '' );
$f_admin_password = gpc_get( 'admin_password', '' );
if( CONFIGURED_PASSWORD == $f_admin_password ) {
	$f_admin_password = '';
}
$f_log_queries    = gpc_get_bool( 'log_queries', false );
$f_db_exists      = gpc_get_bool( 'db_exists', false );

if( $t_config_exists ) {
	if( 0 == $t_install_state ) {
		print_test( 'Config File Exists - Upgrade', true );

		print_test( 'Setting Database Type', '' !== $f_db_type, true, 'database type is blank?' );

		# @TODO: dsn config seems to be undefined, remove ?
		$t_db_conn_exists = ( $f_dsn !== '' || ( $f_database_name !== '' && $f_db_username !== '' && $f_hostname !== '' ) );
		# Oracle supports binding in two ways:
		#  - hostname, username/password and database name
		#  - tns name (insert into hostname field) and username/password, database name is still empty
		if( $f_db_type == 'oci8' ) {
			$t_db_conn_exists = $t_db_conn_exists || ( $f_database_name == '' && $f_db_username !== '' && $f_hostname !== '' );
		}
		print_test( 'Checking Database connection settings exist',
			$t_db_conn_exists,
			true,
			'database connection settings do not exist?' );

		print_db_support_tests( $f_db_type );
	}

	/** @var ADOConnection $g_db */
	$g_db = ADONewConnection( $f_db_type );
	$t_result = @$g_db->Connect( $f_hostname, $f_db_username, $f_db_password, $f_database_name );
	if( $g_db->IsConnected() ) {
		$g_db_connected = true;
	}

	$t_cur_version = config_get( 'database_version', -1, ALL_USERS, ALL_PROJECTS);

	if( $t_cur_version > 1 ) {
		$g_database_upgrade = true;
		$f_db_exists = true;
	} else {
		if( 0 == $t_install_state ) {
			print_test( 'Config File Exists but Database does not', false, false, 'Bad config_inc.php?' );
		}
	}
}

if( 0 == $t_install_state ) {
	?>
<!-- Check UTF-8 support -->
<?php
	# We need the 'mbstring' extension
	print_test(
		'Checking UTF-8 support',
		extension_loaded( 'mbstring' ),
		true,
		'Please install or enable the PHP mbstring extension'
	);
?>
<!-- Check Safe Mode -->
<?php
print_test( 'Checking if safe mode is enabled for install script',
	!ini_get( 'SAFE_MODE' ),
	true,
	'Disable safe_mode in php.ini before proceeding' ) ?>

<?php
	# Check for custom config files in obsolete locations
	$t_config_files = array(
		'config_inc.php' => 'move',
		'custom_constants_inc.php' => 'move',
		'custom_strings_inc.php' => 'move',
		'custom_functions_inc.php' => 'move',
		'custom_relationships_inc.php' => 'move',
		'mc_config_defaults_inc.php' => 'delete',
		'mc_config_inc.php' => 'contents',
	);

	foreach( $t_config_files as $t_file => $t_action ) {
		$t_dir = dirname( __DIR__ ) . '/';
		if( substr( $t_file, 0, 3 ) == 'mc_' ) {
			$t_dir .= 'api/soap/';
		}

		switch( $t_action ) {
			case 'move':
				$t_message = "Move $t_file to config/$t_file.";
				break;
			case 'delete':
				$t_message = 'Delete this file.';
				break;
			case 'contents':
				$t_message = 'Move contents to config_inc.php file.';
				break;
			default:
				$t_message = '';
		}

		print_test(
			"Checking there is no '$t_file' file in 1.2.x location.",
			!file_exists( $t_dir . $t_file ),
			true,
			$t_message
		);
	}
?>

<?php
	if( !$g_failed ) {
		$t_install_state++;
	}
} # end install_state == 0

# got database information, check and install
if( 2 == $t_install_state ) {
	# By now user has picked a timezone, ensure it is set
	date_default_timezone_set( $f_timezone );
?>

<!-- Checking DB support-->
<?php
	print_test( 'Setting Database Type', '' !== $f_db_type, true, 'database type is blank?' );

	print_db_support_tests( $f_db_type );

	# ADOdb library version check
	global $ADODB_vers;
	$t_adodb_version = substr( $ADODB_vers, 1, strpos( $ADODB_vers, ' ' ) - 1 );
	print_test( 'Checking ADOdb Library version is at least ' . DB_MIN_VERSION_ADODB,
		version_compare( $t_adodb_version, DB_MIN_VERSION_ADODB, '>=' ),
		true,
		'Current version: ' . $ADODB_vers
	);

	print_test( 'Setting Database Hostname', '' !== $f_hostname, true, 'host name is blank' );
	print_test( 'Setting Database Username', '' !== $f_db_username, true, 'database username is blank' );
	print_test( 'Setting Database Password', '' !== $f_db_password, false, 'database password is blank' );
	print_test( 'Setting Database Name', '' !== $f_database_name || $f_db_type == 'oci8', true, 'database name is blank' );

?>
<tr>
	<td>
		Setting Admin Username
	</td>
	<?php
		if( '' !== $f_admin_username ) {
		print_test_result( GOOD );
	} else {
		print_test_result( BAD, false, 'admin user name is blank, using database user instead' );
		$f_admin_username = $f_db_username;
	}
	?>
</tr>
<tr>
	<td>
		Setting Admin Password
	</td>
	<?php
		if( '' !== $f_admin_password ) {
			print_test_result( GOOD );
		} else {
			print_test_result( BAD, false, 'admin user password is blank, using database user password instead' );
			$f_admin_password = $f_db_password;
		}
	?>
</tr>

<?php
	# Don't try to connect to the DB if we have failed checks at this stage,
	# as this probably means the DB extension is missing.
	if( !$g_failed ) { ?>
<!-- connect to db -->
<tr>
	<td>
		Attempting to connect to database as admin
	</td>
	<?php
		$t_db_open = false;
	$g_db = ADONewConnection( $f_db_type );
	$t_result = @$g_db->Connect( $f_hostname, $f_admin_username, $f_admin_password );

	if( $t_result ) {
		# due to a bug in ADODB, this call prompts warnings, hence the @
		# the check only works on mysql if the database is open
		$t_version_info = @$g_db->ServerInfo();

		# check if db exists for the admin
		$t_result = @$g_db->Connect( $f_hostname, $f_admin_username, $f_admin_password, $f_database_name );
		if( $t_result ) {
			$t_db_open = true;
			$f_db_exists = true;
		}

		print_test_result( GOOD );
	} else {
		print_test_result(
			BAD,
			true,
			'Does administrative user have access to the database? ( ' . string_attribute( db_error_msg() ) . ' )'
		);
		$t_version_info = null;
	}
	?>
</tr>
<tr>
	<td>
		Checking URL to installation
	</td>
	<?php
		$t_url_check = '';
		if( !$f_path ) {
			# Empty URL - warn admin about security risk
			$t_url_check = "Using an empty path is a security risk, as MantisBT "
				. "will dynamically set it based on headers from the HTTP request, "
				. "exposing your system to Host Header Injection attacks.";
			$t_hard_fail = false;
		} else {
			# Make sure we have a trailing '/'
			$f_path = rtrim( $f_path, '/' ) . '/';

			# Check that the URL is valid
			if( !filter_var( $f_path, FILTER_VALIDATE_URL ) ) {
				$t_url_check = "'$f_path' is not a valid URL.";
			} else {
				require_api( 'url_api.php' );
				$t_page_contents = url_get( $f_path );
				#Can you tell me the actual return value from url_get() on your system ? You can add var_dump( $t_page_contents );
				#before theif( !$t_page_contents ) {` at line 498 of install.php, and post the output here ? 
				#I need to know if it's NULL, FALSE, an empty string or something else.
				var_dump( $t_page_contents );
				if( !$t_page_contents ) {
					$t_url_check = "Can't retrieve web page at '$f_path'.";
				} elseif( false === strpos( $t_page_contents, 'MantisBT') ) {
					$t_url_check = "Web page at '$f_path' does not appear to be a MantisBT site.";
				}
			}
			$t_hard_fail = true;
		}

		print_test_result( $t_url_check ? BAD : GOOD, $t_hard_fail, $t_url_check );
	?>
</tr>
<?php
	if( $f_db_exists ) {
		?>
<tr>
	<td>
		Attempting to connect to database as user
	</td>
	<?php
		$g_db = ADONewConnection( $f_db_type );
		$t_result = @$g_db->Connect( $f_hostname, $f_db_username, $f_db_password, $f_database_name );

		if( $t_result ) {
			$t_db_open = true;
			print_test_result( GOOD );
		} else {
			print_test_result(
				BAD,
				false,
				'Database user doesn\'t have access to the database ( ' . string_attribute( db_error_msg() ) . ' )'
			);
		}
		?>
</tr>

<?php
	}
	if( $t_db_open ) {
		?>
<!-- display database version -->
<tr>
	<td>
		Checking Database Server Version
<?php
		if( isset( $t_version_info['description'] ) ) {
			echo '<br /> Running ' . string_attribute( $f_db_type )
				. ' version ' . nl2br( $t_version_info['description'] );
		}
?>
	</td>
<?php
		$t_warning = '';
		$t_error = '';
		switch( $f_db_type ) {
			case 'mysqli':
				if( version_compare( $t_version_info['version'], DB_MIN_VERSION_MYSQL, '<' ) ) {
					$t_error = 'MySQL ' . DB_MIN_VERSION_MYSQL . ' or later is required for installation';
				}
				break;
			case 'mssqlnative':
				if( version_compare( $t_version_info['version'], DB_MIN_VERSION_MSSQL, '<' ) ) {
					$t_error = 'SQL Server (' . DB_MIN_VERSION_MSSQL . ') or later is required for installation';
				}
				break;
			case 'pgsql':
			default:
				break;
		}

		if( is_null( $t_version_info ) ) {
			$t_warning = "Unable to determine '$f_db_type' version. ($t_error).";
			$t_error = '';
		}
		print_test_result(
			( '' == $t_error ) && ( '' == $t_warning ),
			( '' != $t_error ),
			$t_error . ' ' . $t_warning
		);
?>
</tr>

<?php
	} # end if db open
	} # end if failed DB checks
	
	if( !$g_failed ) {
		$t_install_state++;
	} else {
		$t_install_state--; # a check failed, redisplay the questions
	}
} # end 2 == $t_install_state
?>

</table>
</div>
</div>
</div>
</div>
</div>

<?php
# system checks have passed, get the database information
if( 1 == $t_install_state ) {
	?>

<form method='POST'>

<input name="install" type="hidden" value="2">

<div class="col-md-12 col-xs-12">
<div class="space-10"></div>

<div class="widget-box widget-color-blue2">
<div class="widget-header widget-header-small">
		<h4 class="widget-title lighter">
			<?php echo
				( $g_database_upgrade ? 'Upgrade Options' : 'Installation Options' ),
				( $g_failed ? ': Checks Failed ' : '' )
			?>
		</h4>
</div>

<div class="widget-body">
<div class="widget-main no-padding">
<div class="table-responsive">
<table class="table table-bordered table-condensed">

<?php
# install-only fields: when upgrading, only display admin username and password
if( !$g_database_upgrade ) {
?>

<!-- Database type selection list -->
<tr>
	<td>
		<label for="db_type">Type of Database</label>
	</td>
	<td>
		<!-- Default values for table prefix/suffix -->
		<div>
<?php
	# These elements are referenced by the db selection list's on change event
	# to populate the corresponding fields as appropriate
	foreach( $t_prefix_defaults as $t_db_type => $t_defaults ) {
		echo '<div id="default_' . $t_db_type . '" class="hidden">';
		foreach( $t_defaults as $t_key => $t_value ) {
			echo "\n\t" . '<span class="' . $t_key . '">' . $t_value . '</span>';
		}
		echo "\n" . '</div>' . "\n";
	}
?>
		</div>

		<select id="db_type" name="db_type" class="input-sm">
<?php
			# Build selection list of available DB types
			$t_db_list = array(
				'mysqli'      => 'MySQL Improved',
				'mssqlnative' => 'Microsoft SQL Server Native Driver',
				'pgsql'       => 'PostgreSQL',
				'oci8'        => 'Oracle',
			);

			foreach( $t_db_list as $t_db => $t_db_descr ) {
				echo '<option value="' . $t_db . '" ' .
					( $t_db == $f_db_type ? ' selected="selected"' : '' ) . '>' .
					$t_db_descr . "</option>\n";
			}
?>
		</select>
	</td>
</tr>

<!-- Database server hostname -->
<tr>
	<td>
		<label for="hostname">Hostname (for Database Server)</label>
	</td>
	<td>
		<input id="hostname" name="hostname" type="text" value="<?php echo string_attribute( $f_hostname ) ?>">
	</td>
</tr>

<!-- Database username and password -->
<tr>
	<td>
		<label for="db_username">Username (for Database)</label>
	</td>
	<td>
		<input id="db_username" name="db_username" type="text" value="<?php echo string_attribute( $f_db_username ) ?>">
	</td>
</tr>

<tr>
	<td>
		<label for="db_password">Password (for Database)</label>
	</td>
	<td>
		<input id="db_password" name="db_password" type="password" value="<?php
			echo !is_blank( $f_db_password ) && $t_config_exists
				? CONFIGURED_PASSWORD
				: $f_db_password;
		?>">
	</td>
</tr>

<!-- Database name -->
<tr>
	<td>
		<label for="database_name">Database name (for Database)</label>
	</td>
	<td>
		<input id="database_name" name="database_name" type="text" value="<?php echo string_attribute( $f_database_name ) ?>">
	</td>
</tr>
<?php
} # end install-only fields
?>

<!-- Admin user and password -->
<tr>
	<td>
		<label for="admin_username">
			Admin Username (to
			<?php echo( !$g_database_upgrade ) ? 'create Database' : 'update Database'?>
			if required)
		</label>
	</td>
	<td>
		<input id="admin_username" name="admin_username" type="text" value="<?php echo string_attribute( $f_admin_username ) ?>">
	</td>
</tr>

<tr>
	<td>
		<label for="admin_password">
			Admin Password (to
			<?php echo( !$g_database_upgrade ) ? 'create Database' : 'update Database'?>
			if required)
		</label>
	</td>
	<td>
		<input id="admin_password" name="admin_password" type="password" value="<?php
			echo !is_blank( $f_admin_password ) && $f_admin_password == $f_db_password
				? CONFIGURED_PASSWORD
				: string_attribute( $f_admin_password );
		?>">
	</td>
</tr>

<?php
# install-only fields: when upgrading, only display admin username and password
if( !$g_database_upgrade ) {
	$t_prefix_labels = array(
		'db_table_prefix'        => 'Database Table Prefix',
		'db_table_plugin_prefix' => 'Database Plugin Table Prefix',
		'db_table_suffix'        => 'Database Table Suffix',
	);
	foreach( $t_prefix_defaults[$t_prefix_type] as $t_key => $t_value ) {
		echo "<tr>\n\t<td>\n";
		echo "\t\t" . $t_prefix_labels[$t_key] . "\n";
		echo "\t</td>\n\t<td>\n\t\t";
		$t_required = $t_key == 'db_table_plugin_prefix' ? 'required' : '';
		/** @noinspection HtmlUnknownAttribute */
		printf( '<input id="%1$s" name="%1$s" type="text" class="table-prefix reset" %2$s value="%3$s">',
			$t_key,
			$t_key == 'db_table_plugin_prefix' ? 'required' : '',
			${'f_' . $t_key} // The actual value of the corresponding form variable
		);
		echo "\n&nbsp;";
		printf( '<button id="%s" type="button" class="btn btn-sm btn-primary btn-white btn-round reset">%s</button>',
			"btn_$t_key",
			lang_get( 'reset' )
		);
		echo "\n&nbsp;";
		if( $t_key != 'db_table_suffix' ) {
			$t_id_sample = $t_key. '_sample';
			echo '<label for="' . $t_id_sample . '">Sample table name:</label>';
			echo "\n", '<input id="' . $t_id_sample . '" type="text" size="40" disabled>';
		} else {
			echo '<span id="oracle_size_warning" >';
			echo "On Oracle < 12cR2, max length for identifiers is 30 chars. "
				. "Keep pre/suffixes as short as possible to avoid problems.";
			echo '<span>';
		}
		echo "\n\t</td>\n</tr>\n\n";
	}

	# Default timezone, get PHP setting if not defined in Mantis
	$t_tz = config_get_global( 'default_timezone' );
	if( is_blank( $t_tz ) ) {
		$t_tz = @date_default_timezone_get();
	}
?>
<!-- Timezone -->
<tr>
	<td>
		<label for="timezone">Default Time Zone</label>
	</td>
	<td>
		<select id="timezone" name="timezone">
			<?php print_timezone_option_list( $t_tz ) ?>
		</select>
	</td>
</tr>

<!-- URL to installation ($g_path) -->
<tr>
	<td>
		<label for="path">URL to your installation (<em>$g_path</em>, see
			<a href="https://mantisbt.org/docs/master/en-US/Admin_Guide/html-desktop/#admin.config.path"
			   target="_blank">documentation</a>)
		</label>
	</td>
	<td>
		<input id="path" name="path" type="text" size="50" class="reset"
			   value="<?php echo string_attribute( $f_path ) ?>"
			   data-defval="<?php global $g_path; echo string_attribute( $g_path ); ?>"
		/>

		<button id="btn_path" type="button" class="btn btn-sm btn-primary btn-white btn-round reset">
			<?php echo lang_get( 'reset' ); ?>
		</button>
	</td>
</tr>

<?php
} # end install-only fields
?>

<!-- Printing SQL queries -->
<tr>
	<td>
		<label for="log_queries">Print SQL Queries instead of Writing to the Database</label>
	</td>
	<td>
		<input id="log_queries" name="log_queries" type="checkbox" class="ace" value="1" <?php echo( $f_log_queries ? 'checked="checked"' : '' )?>>
		<span class="lbl"></span>
	</td>
</tr>

<!-- Submit button -->
<tr>
	<td>
		<?php echo ( $g_failed
			? 'Please correct failed checks and try again'
			: 'Attempt Installation' );
		?>
	</td>
	<td>
		<input name="go" type="submit" class="btn btn-primary btn-white btn-round" value="Install/Upgrade Database">
	</td>
</tr>

</table>
</div>
</div>
</div>
</div>
</div>
</form>

<?php
}  # end install_state == 1

# all checks have passed, install the database
if( 3 == $t_install_state ) {
	?>
<div class="col-md-12 col-xs-12">
<div class="space-10"></div>
<div class="widget-box widget-color-blue2">
<div class="widget-header widget-header-small">
	<h4 class="widget-title lighter">
		Installing Database
	</h4>
</div>
<div class="widget-body">
<div class="widget-main no-padding">
<div class="table-responsive">
<table class="table table-bordered table-condensed" style="table-layout:fixed">
<?php if( !$f_log_queries ) {?>
<tr>
	<td>
		Create database if it does not exist
	</td>
	<?php
		global $g_db;
		$t_result = @$g_db->Connect( $f_hostname, $f_admin_username, $f_admin_password, $f_database_name );

		$t_db_open = false;

		if( $t_result ) {
			print_test_result( GOOD );
			$t_db_open = true;
		} else {
			# create db
			$g_db = ADONewConnection( $f_db_type );
			$t_result = $g_db->Connect( $f_hostname, $f_admin_username, $f_admin_password );

			/** @var ADODB_DataDict $t_dict */
			$t_dict = NewDataDictionary( $g_db );

			$t_sqlarray = $t_dict->CreateDatabase( $f_database_name, array(
				'mysql' => 'DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci',
			) );
			$t_ret = $t_dict->ExecuteSQLArray( $t_sqlarray, false );
			if( $t_ret == 2 ) {
				print_test_result( GOOD );
				$t_db_open = true;
			} else {
				$t_error = db_error_msg();
				if( $f_db_type == 'oci8' ) {
					$t_db_exists = preg_match( '/ORA-01920/', $t_error );
				} else {
					$t_db_exists = strstr( $t_error, 'atabase exists' );
				}

				if( $t_db_exists ) {
					print_test_result(
						BAD,
						false,
						'Database already exists? ( ' . string_attribute( db_error_msg() ) . ' )'
					);
				} else {
					print_test_result(
						BAD,
						true,
						'Does administrative user have access to create the database? ( ' . string_attribute( db_error_msg() ) . ' )'
					);
					$t_install_state--; # db creation failed, allow user to re-enter user/password info
				}
			}
		}
		?>
</tr>
<?php
	# Close the connection and clear the ADOdb object to free memory
	$g_db->Close();
	$g_db = null;
?>
<tr>
	<td>
		Attempting to connect to database as user
	</td>
	<?php
		$g_db = ADONewConnection( $f_db_type );
		$t_result = @$g_db->Connect( $f_hostname, $f_db_username, $f_db_password, $f_database_name );
		if( $t_result ) {
			print_test_result( GOOD );
		} else {
			print_test_result(
				BAD,
				false,
				'Database user doesn\'t have access to the database ( ' . string_attribute( db_error_msg() ) . ' )'
			);
		}
		$g_db->Close();
	?>
</tr>
<?php
	}

	# install the tables
	if( !$g_failed ) {
		$g_db_connected = false;

		# fake out database access routines used by config_get
		config_set_global( 'db_type', $f_db_type );

		# Initialize table prefixes as specified by user
		config_set_global( 'db_table_prefix', $f_db_table_prefix );
		config_set_global( 'db_table_plugin_prefix', $f_db_table_plugin_prefix );
		config_set_global( 'db_table_suffix', $f_db_table_suffix );
		# database_api references this
		require_once( __DIR__ . '/schema.php' );
		$g_db = ADONewConnection( $f_db_type );
		$t_result = @$g_db->Connect( $f_hostname, $f_admin_username, $f_admin_password, $f_database_name );
		if( !$f_log_queries ) {
			$g_db_connected = true;

			# fake out database access routines used by config_get
		}
		$t_last_update = config_get( 'database_version', -1, ALL_USERS, ALL_PROJECTS );
		/** @var array $g_upgrade Upgrade steps defined in schema.php */
		$t_last_id = count( $g_upgrade ) - 1;
		$i = $t_last_update + 1;
		if( $f_log_queries ) {
			echo '<tr><td><span class="bigger-120">Database Creation Suppressed, SQL Queries follow</span>';

			echo '<div class="space-6"></div>';
			echo '<div class="alert alert-warning">';
			echo "Please note that executing the generated script below <strong>may not result in a fully functional "
				. "database</strong>, particularly in upgrade scenarios. This is due to the fact that some upgrade "
				. "steps require the execution of PHP code; these <em>Upgrade Functions</em> are defined in "
				. '<a href="https://github.com/mantisbt/mantisbt/blob/master/core/install_helper_functions_api.php">install_helper_functions_api.php</a>'
				. " and cannot be translated to SQL statements. Use at your own risk.";
			echo '</div>';

			echo '<pre>';
			echo "-- MantisBT " . MANTIS_VERSION . " Database creation script". PHP_EOL;
			echo "-- " . date("c") . PHP_EOL . PHP_EOL;
		}

		# Make sure we do the upgrades using UTF-8 if needed
		if( $f_db_type === 'mysqli' ) {
			$g_db->execute( 'SET NAMES UTF8' );
		}

		/** @var ADODB_DataDict $t_dict */
		$t_dict = NewDataDictionary( $g_db );

		# Special processing for specific schema versions
		# This allows execution of additional install steps, which are
		# not a Mantis schema upgrade but nevertheless required due to
		# changes in the code

		if( $f_db_type == 'pgsql' && $t_last_update > 51 && $t_last_update < 189 ) {
			# Since MantisBT 1.1.0 / ADOdb 4.96 (corresponding to schema 51)
			# 'L' columns are BOOLEAN instead of SMALLINT
			# Check for any DB discrepancies and update columns if needed
			$t_bool_columns = check_pgsql_bool_columns();
			if( $t_bool_columns !== true ) {
				# Some columns need converting
				$t_msg = "PostgreSQL: check Boolean columns' actual type";
				if( is_array( $t_bool_columns ) ) {
					$t_count = count( $t_bool_columns );
					print_test(
						$t_msg,
						$t_count == 0,
						false,
						"$t_count columns must be converted to BOOLEAN"
					);

					# Convert the columns
					foreach( $t_bool_columns as $t_row ) {
						/**
						 * @var string $v_table_name
						 * @var string $v_column_name
						 * @var boolean $v_is_nullable
						 * @var boolean $v_column_default
						 */
						extract( $t_row, EXTR_PREFIX_ALL, 'v' );

						$t_null = $v_is_nullable ? 'NULL' : 'NOT NULL';
						$t_default = is_null( $v_column_default ) ? 'NULL' : $v_column_default;
						$t_sqlarray = $t_dict->AlterColumnSQL(
							$v_table_name,
							$v_column_name . ' L ' . $t_null . ' DEFAULT ' . $t_default
						);
						print_test(
							'Converting column ' . $v_table_name . '.' . $v_column_name . ' to BOOLEAN',
							2 == $t_dict->ExecuteSQLArray( $t_sqlarray, false ),
							true,
							print_r( $t_sqlarray, true )
						);
						/** @noinspection PhpExpressionAlwaysConstantInspection Set by print_test() */
						if( $g_failed ) {
							# Error occurred, bail out
							break;
						}
					}
				} else {
					# We did not get an array => error occurred
					print_test( $t_msg, false, true, $t_bool_columns );
				}
			}
		}
		# Follow-up fix for user_pref.redirect_delay, which was incorrectly
		# set to boolean in check_pgsql_bool_columns() before MantisBT 2.23.0,
		# so we need to check its type and convert it back to integer if needed.
		# See issue #26109.
		elseif( $f_db_type == 'pgsql' && $t_last_update > 43
			&& version_compare( MANTIS_VERSION, '2.23.0', '<=' )
		) {
			$t_table = db_get_table( 'user_pref' );
			$t_column = 'redirect_delay';

			try {
				$t_is_integer = pgsql_get_column_type( $t_table, $t_column ) == 'integer';
				$t_msg = "Column must be converted to INTEGER";
				$t_exception_occured = false;
			}
			catch( Exception $e ) {
				$t_exception_occured = true;
				$t_msg = $e->getMessage();
			}

			/** @noinspection PhpUndefinedVariableInspection */
			print_test(
				"PostgreSQL: check column '$t_table.$t_column' data type",
				!$t_exception_occured && $t_is_integer,
				/* hard fail */ $t_exception_occured,
				$t_msg
			);
			if( !$t_exception_occured && !$t_is_integer ) {
				$t_sqlarray = $t_dict->AlterColumnSQL( $t_table,
					'redirect_delay  I  NOTNULL  DEFAULT 0'
				);
				print_test(
					"Converting column '$t_table.$t_column'' to INTEGER",
					2 == $t_dict->ExecuteSQLArray( $t_sqlarray, false ),
					true,
					print_r( $t_sqlarray, true )
				);
			}
		}

		# End of special processing for specific schema versions

		while( ( $i <= $t_last_id ) && !$g_failed ) {
			if( !$f_log_queries ) {
				echo '<tr><td>';
			}

			# No-op upgrade step - required for oci8
			if( $g_upgrade[$i] === null ) {
				$t_sql = false;
				$t_sqlarray = array();
				$t_operation = 'No operation';
				$t_target = null;
			} else {
				$t_sql = true;
				$t_operation = $g_upgrade[$i][0];
				$t_target = $g_upgrade[$i][1][0];

				switch( $t_operation ) {
					case 'InsertData':
						$t_sqlarray = call_user_func_array( $t_operation, $g_upgrade[$i][1] );
						break;

					case 'UpdateSQL':
						$t_sqlarray = array(
							$g_upgrade[$i][1],
						);
						$t_target = $g_upgrade[$i][1];
						break;

					case 'UpdateFunction':
						$t_sqlarray = array(
							$g_upgrade[$i][1],
						);
						if( isset( $g_upgrade[$i][2] ) ) {
							$t_sqlarray[] = $g_upgrade[$i][2];
						}
						$t_sql = false;
						$t_target = $g_upgrade[$i][1];
						break;

					default:
						$t_sqlarray = call_user_func_array( array( $t_dict, $t_operation ), $g_upgrade[$i][1] );

						# 0: function to call, 1: function params, 2: function to evaluate before calling upgrade, if false, skip upgrade.
						if( isset( $g_upgrade[$i][2] ) ) {
							if( call_user_func_array( $g_upgrade[$i][2][0], $g_upgrade[$i][2][1] ) ) {
								$t_sqlarray = call_user_func_array( array( $t_dict, $t_operation ), $g_upgrade[$i][1] );
							} else {
								$t_sql = false;
								$t_sqlarray = array();
								$t_operation = "No operation";
							}
						} else {
							$t_sqlarray = call_user_func_array( array( $t_dict, $t_operation ), $g_upgrade[$i][1] );
						}
						break;
				}
			}
			if( $f_log_queries ) {
				echo "-- Schema step $i" . PHP_EOL;
				if( $t_sql ) {
					foreach( $t_sqlarray as $t_statement ) {
						# "CREATE OR REPLACE TRIGGER" statements must end with "END;\n/" for Oracle sqlplus
						if( $f_db_type == 'oci8' && stripos( $t_statement, 'CREATE OR REPLACE TRIGGER' ) === 0 ) {
							$t_sql_end = PHP_EOL . '/';
						} else {
							$t_sql_end = ';';
						}
						echo htmlentities( $t_statement ) . $t_sql_end;
					}
				} elseif( $t_sqlarray ) {
					echo "-- Execute PHP Update Function: install_" . htmlentities( $t_sqlarray[0] ) . "(";
					# Convert the parameters array to a printable string
					if( isset( $t_sqlarray[1] ) ) {
						$t_params = array();
						foreach( $t_sqlarray[1] as $t_param ) {
							$t_value = var_export( $t_param, true );
							if( is_array( $t_param ) ) {
								# Remove unnecessary array keys, newlines and the trailing comma
								$t_value = preg_replace( '/\s*[0-9]+ => /', ' ', $t_value );
								$t_value = str_replace( ",\n", ' ', $t_value );
							}
							$t_params[] = $t_value;
						}
						echo htmlentities( implode( ', ', $t_params ) );
					}
					echo ")";
				} else {
					echo "-- $t_operation";
				}
				echo PHP_EOL . PHP_EOL;
			} else {
				echo 'Schema step ' . $i . ': ' . $t_operation;
				if( $t_target === null ) {
					$t_ret = 2;
				} else {
					echo ' ( ' . $t_target . ' )';
					if( $t_sql ) {
						$t_ret = $t_dict->ExecuteSQLArray( $t_sqlarray, false );
					} else {
						if( isset( $t_sqlarray[1] ) ) {
							$t_ret = call_user_func( 'install_' . $t_sqlarray[0], $t_sqlarray[1] );
						} else {
							$t_ret = call_user_func( 'install_' . $t_sqlarray[0] );
						}
					}
				}
				echo '</td>';
				if( $t_ret == 2 ) {
					print_test_result( GOOD );
					config_set( 'database_version', $i, ALL_USERS );
				} else {
					$t_all_sql = '';
					if( $t_sql ) {
						foreach( $t_sqlarray as $t_single_sql ) {
							if( !empty( $t_single_sql ) ) {
								$t_all_sql .= $t_single_sql . '<br />';
							}
						}
					}
					print_test_result( BAD, true, $t_all_sql  . $g_db->ErrorMsg() );
				}
				echo '</tr>';
			}
			$i++;
		}
		if( $f_log_queries ) {
			# add a query to set the database version
			echo "-- Set database version" . PHP_EOL;
			if( $t_last_update == -1 ) {
				echo "INSERT INTO " . db_get_table( 'config' )
					. " ( value, type, access_reqd, config_id, project_id, user_id )"
					. " VALUES ($t_last_id, 1, 90, 'database_version', 0, 0 );"
					. PHP_EOL;
			} else {
				echo "UPDATE " . db_get_table( 'config' )
					. " SET value = $t_last_id"
					. " WHERE config_id = 'database_version' AND project_id = 0 AND user_id = 0;"
					. PHP_EOL;
			}
			echo '</pre>';

			echo '<div class="space-6"></div>';
			echo '<div class="alert alert-danger">';
			echo "<strong>Your database is not ready yet !</strong> "
				. "Please create it, then install the tables and data using the above script before proceeding.";
			echo '</div>';
			echo '</td></tr>';
		}
	}
	if( !$g_failed ) {
		$t_install_state++;
	} else {
		$t_install_state--;
	}

	?>
</table>
</div>
</div>
</div>
</div>
</div>

<?php
}  # end install_state == 3

# database installed, get any additional information
if( 4 == $t_install_state ) {

/*
	# 20141227 dregad Disabling this step for now, because it does not seem to
	# be doing anything useful and can be used to retrieve system information
	# when the admin directory has not been deleted (see #17939).

	# @todo to be written
	# must post data gathered to preserve it
	?>
		<input name="hostname" type="hidden" value="<?php echo string_attribute( $f_hostname ) ?>">
		<input name="db_type" type="hidden" value="<?php echo string_attribute( $f_db_type ) ?>">
		<input name="database_name" type="hidden" value="<?php echo string_attribute( $f_database_name ) ?>">
		<input name="db_username" type="hidden" value="<?php echo string_attribute( $f_db_username ) ?>">
		<input name="db_password" type="hidden" value="<?php echo string_attribute( f_db_password ) ?>">
		<input name="admin_username" type="hidden" value="<?php echo string_attribute( $f_admin_username ) ?>">
		<input name="admin_password" type="hidden" value="<?php echo string_attribute( $f_admin_password ) ?>">
		<input name="log_queries" type="hidden" value="<?php echo( $f_log_queries ? 1 : 0 )?>">
		<input name="db_exists" type="hidden" value="<?php echo( $f_db_exists ? 1 : 0 )?>">
<?php
	# must post <input name="install" type="hidden" value="5">
	# rather than the following line
*/
	$t_install_state++;
}  # end install_state == 4

# all checks have passed, install the database
if( 5 == $t_install_state ) {
	$t_config_exists = file_exists( $t_config_filename );
	?>

<div class="col-md-12 col-xs-12">
<div class="space-10"></div>
<div class="widget-box widget-color-blue2">
<div class="widget-header widget-header-small">
	<h4 class="widget-title lighter">
		Write Configuration File(s)
	</h4>
</div>
<div class="widget-body">
<div class="widget-main no-padding">
<div class="table-responsive">
<table class="table table-bordered table-condensed">

<?php
	$t_crypto_master_salt = '';
	if( !$t_config_exists ) {
?>
	<tr>
		<td>
			Generating Crypto Master Salt
		</td>
<?php
		# Automatically generate a strong master salt/nonce for MantisBT
		# cryptographic purposes.
		try {
			$t_crypto_master_salt = base64_encode( random_bytes( 32 ) );
			print_test_result( GOOD );
		}
		# With PHP 8.2 and later this should catch Random\RandomException instead
		catch( Exception $e ) {
			$t_crypto_failed_msg = "No appropriate source of randomness found. "
				. "You will need to generate the Master Salt yourself "
				. "and add it to the configuration file manually.";
			print_test_result( BAD, false, $t_crypto_failed_msg );

		}
?>
	</tr>
<?php
	} # End crypto master salt generation
?>

<tr>
    <td>
        <?php echo ( $t_config_exists ? 'Updating' : 'Creating' ); ?>
        Configuration File (config/config_inc.php)<br />
    </td>
<?php
	# Generating the config_inc.php file

	$t_config = '<?php' . PHP_EOL
		. '$g_hostname               = \'' . addslashes( $f_hostname ) . '\';' . PHP_EOL
		. '$g_db_type                = \'' . addslashes( $f_db_type ) . '\';' . PHP_EOL
		. '$g_database_name          = \'' . addslashes( $f_database_name ) . '\';' . PHP_EOL
		. '$g_db_username            = \'' . addslashes( $f_db_username ) . '\';' . PHP_EOL
		. '$g_db_password            = \'' . addslashes( $f_db_password ) . '\';' . PHP_EOL;

	$t_config .= PHP_EOL;

	# Add lines for table prefix/suffix if different from default
	$t_insert_line = false;
	foreach( $t_prefix_defaults['other'] as $t_key => $t_value ) {
		$t_new_value = ${'f_' . $t_key};
		if( $t_new_value != $t_value ) {
			$t_config .= '$g_' . str_pad( $t_key, 25 ) . '= \'' . addslashes( ${'f_' . $t_key} ) . '\';' . PHP_EOL;
			$t_insert_line = true;
		}
	}
	if( $t_insert_line ) {
		$t_config .= PHP_EOL;
	}

	$t_config .=
		  '$g_default_timezone       = \'' . addslashes( $f_timezone ) . '\';' . PHP_EOL
		. PHP_EOL
		. (!$t_crypto_master_salt ? "# The installer could not generate the Master Salt; please set it manually.\n" : '')
		. "\$g_crypto_master_salt     = '" . addslashes( $t_crypto_master_salt ) . "';" . PHP_EOL;

	if( $f_path ) {
		$t_config .= PHP_EOL
			. "\$g_path                   = '" . addslashes( $f_path ) . "';" . PHP_EOL;
	}

	$t_write_failed = true;

	if( !$t_config_exists ) {
		# Try to create the config file
		if( is_writable( $g_config_path )
			&& $t_fd = fopen( $t_config_filename, 'w' )
		) {
			fwrite( $t_fd, $t_config );
			fclose( $t_fd );
		}

		if( file_exists( $t_config_filename ) ) {
			print_test_result( GOOD );
			$t_write_failed = false;
		} else {
			print_test_result( BAD, false, 'cannot write ' . $t_config_filename );
		}
	} else {
		# already exists, see if the information is the same
		if( ( $f_hostname != config_get_global( 'hostname', '' ) ) ||
			( $f_db_type != config_get_global( 'db_type', '' ) ) ||
			( $f_database_name != config_get_global( 'database_name', '' ) ) ||
			( $f_db_username != config_get_global( 'db_username', '' ) ) ||
			( $f_db_password != config_get_global( 'db_password', '' ) ) ) {
			print_test_result( BAD, false, 'file ' . $t_config_filename . ' already exists and has different settings' );
		} else {
			print_test_result( GOOD, false );
			$t_write_failed = false;
		}
	}
	?>
</tr>
<?php
	if( $t_write_failed ) {
?>
<tr>
	<td colspan="2">
		<table>
			<tr>
				<td>
					Please add the following lines to
					<em>'<?php echo $t_config_filename; ?>'</em>
					before continuing:
				</td>
			</tr>
			<tr>
				<td>
					<pre><?php echo htmlentities( $t_config ); ?></pre>
				</td>
			</tr>
		</table>
	</td>
</tr>
<?php
	}
?>

</table>
</div>
</div>
</div>
</div>
</div>

<?php
	if( !$g_failed ) {
		$t_install_state++;
	}
}

# end install_state == 5

if( 6 == $t_install_state ) {

# post install checks
?>
<div class="col-md-12 col-xs-12">
<div class="space-10"></div>
<div class="widget-box widget-color-blue2">
<div class="widget-header widget-header-small">
	<h4 class="widget-title lighter">
		Checking Installation
	</h4>
</div>
<div class="widget-body">
<div class="widget-main no-padding">
<div class="table-responsive">
<table class="table table-bordered table-condensed">

<tr>
	<td>
		Attempting to connect to database as user
	</td>
	<?php
		$g_db = ADONewConnection( $f_db_type );
	$t_result = @$g_db->Connect( $f_hostname, $f_db_username, $f_db_password, $f_database_name );

	if( $t_result ) {
		print_test_result( GOOD );
	} else {
		print_test_result(
			BAD,
			false,
			'Database user does not have access to the database ( ' . string_attribute( db_error_msg() ) . ' )'
		);
	}
	?>
</tr>
<tr>
	<td>
		checking ability to SELECT records
	</td>
	<?php
	$t_query = 'SELECT COUNT(*) FROM ' . db_get_table( 'config' );
	$t_result = @$g_db->Execute( $t_query );

	if( $t_result ) {
		print_test_result( GOOD );
	} else {
		print_test_result(
			BAD,
			true,
			'Database user does not have SELECT access to the database ( ' . string_attribute( db_error_msg() ) . ' )'
		);
	}
	?>
</tr>
<tr>
	<td>
		checking ability to INSERT records
	</td>
	<?php
		$t_query = 'INSERT INTO ' . db_get_table( 'config' ) . ' ( value, type, access_reqd, config_id, project_id, user_id ) VALUES (\'test\', 1, 90, \'database_test\', 20, 0 )';
	$t_result = @$g_db->Execute( $t_query );

	if( $t_result ) {
		print_test_result( GOOD );
	} else {
		print_test_result(
			BAD,
			true,
			'Database user does not have INSERT access to the database ( ' . string_attribute( db_error_msg() ) . ' )'
		);
	}
	?>
</tr>
<tr>
	<td>
		checking ability to UPDATE records
	</td>
	<?php
		$t_query = 'UPDATE ' . db_get_table( 'config' ) . ' SET value=\'test_update\' WHERE config_id=\'database_test\'';
	$t_result = @$g_db->Execute( $t_query );

	if( $t_result ) {
		print_test_result( GOOD );
	} else {
		print_test_result(
			BAD,
			true,
			'Database user does not have UPDATE access to the database ( ' . string_attribute( db_error_msg() ) . ' )'
		);
	}
	?>
</tr>
<tr>
	<td>
		checking ability to DELETE records
	</td>
	<?php
		$t_query = 'DELETE FROM ' . db_get_table( 'config' ) . ' WHERE config_id=\'database_test\'';
	$t_result = @$g_db->Execute( $t_query );

	if( $t_result ) {
		print_test_result( GOOD );
	} else {
		print_test_result(
			BAD,
			true,
			'Database user does not have DELETE access to the database ( ' . string_attribute( db_error_msg() ) . ' )'
		);
	}
	?>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>

<?php
	if( !$g_failed ) {
		$t_install_state++;
	}
}

# end install_state == 6

if( 7 == $t_install_state ) {
	# cleanup and launch upgrade
	?>
<div class="col-md-12 col-xs-12">
<div class="space-10"></div>
<div class="widget-box widget-color-blue2">
<div class="widget-header widget-header-small">
	<h4 class="widget-title lighter">
		Installation Complete
	</h4>
</div>
<div class="widget-body">
<div class="widget-main no-padding">
<div class="table-responsive">
<table class="table table-bordered table-condensed">
<tr>
	<td>
		<span class="bigger-130">
<?php if( $f_log_queries ) { ?>
		SQL script generated successfully.
		Use it to manually create or upgrade your database.
<?php } else { ?>
		MantisBT was installed successfully.
<?php if( $f_db_exists ) { ?>
		<a href="../login_page.php">Continue</a> to log in.
<?php } else { ?>
		Please log in as the administrator and <a href="../login_page.php">create</a> your first project.
		</span>
<?php } ?>
<?php } ?>
	</td>
	<?php print_test_result( GOOD ); ?>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>
<?php
}

# end install_state == 7

if( $g_failed && $t_install_state != 1 ) {
	?>
<div class="col-md-12 col-xs-12">
<div class="space-10"></div>
<div class="widget-box widget-color-blue2">
<div class="widget-header widget-header-small">
	<h4 class="widget-title lighter">
		Installation Failed
	</h4>
</div>
<div class="widget-body">
<div class="widget-main no-padding">
<div class="table-responsive">
<table class="table table-bordered table-condensed">
<tr>
	<td>Please correct failed checks</td>
	<td>
<form method='POST'>
		<input name="install" type="hidden" value="<?php echo $t_install_state?>">
		<input name="hostname" type="hidden" value="<?php echo string_attribute( $f_hostname ) ?>">
		<input name="db_type" type="hidden" value="<?php echo string_attribute( $f_db_type ) ?>">
		<input name="database_name" type="hidden" value="<?php echo string_attribute( $f_database_name ) ?>">
		<input name="db_username" type="hidden" value="<?php echo string_attribute( $f_db_username ) ?>">
		<input name="db_password" type="hidden" value="<?php
			echo !is_blank( $f_db_password ) && $t_config_exists
				? CONFIGURED_PASSWORD
				: string_attribute( $f_db_password );
		?>">
		<input name="admin_username" type="hidden" value="<?php echo string_attribute( $f_admin_username )?>">
		<input name="admin_password" type="hidden" value="<?php
			echo !is_blank( $f_admin_password ) && $f_admin_password == $f_db_password
				? CONFIGURED_PASSWORD
				: string_attribute( $f_admin_password );
		?>">
		<input name="log_queries" type="hidden" value="<?php echo( $f_log_queries ? 1 : 0 )?>">
		<input name="db_exists" type="hidden" value="<?php echo( $f_db_exists ? 1 : 0 )?>">
		<input name="retry" type="submit" class="btn btn-primary btn-white btn-round" value="Retry">
</form>
	</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>

<div class="space-10"></div>
<?php
}
layout_admin_page_end();
install_2270_ID34783_test1.php (48,926 bytes)   
Seppeltronics

Seppeltronics

2025-01-29 14:22

reporter   ~0069774

Please also check the code, it if effectively:

1, line 482 and following: if( !$f_path ) {$t_hard_fail = false;}

2, line 495 and following: if( $f_path ) {$t_hard_fail = true;}

As far as I understand the Code, whenever if( $f_path ) , the sting is not empty, then $t_hard_fail = true ? Is that the intended behavior or do I get it wrong?

raspopov

raspopov

2025-01-29 22:55

reporter   ~0069775

@dregad, By the way, many web hosts and web servers do not allow PHP to download files by URI by default (url_get), so this blocking check makes it unreasonably difficult to install and update MantisBT. I wonder why this check is done at all. Can't the administrator open MantisBT in a browser?

Seppeltronics

Seppeltronics

2025-01-30 06:17

reporter   ~0069776

Last edited: 2025-01-30 06:18

@raspopov, it is a webhosting, so it may be the case that url_get could be blocked, but I did not find anything about it in the webhosters documentation, so I’m not sure if that causes the issue. These webhostings are shared servers with ssh and sftp with limited options of what you can do for sure, but unless someone tells me how I can test that, I’m sorry that I can not provide more details about url_get.

However, the finding of the {$t_hard_fail = true;} if ( $f_path ) indicated there could be more issues than the return-value of url_get.

In general I open the tried to install/upgrade MantisBt, both failed, via opening the https://www.foo.net/bar/mantisbt_old/admin/install.php via the Firefox webbrowser, as the GUI should be the way to to install a standard installation, or isn’t it?

dregad

dregad

2025-01-30 07:14

developer   ~0069779

@Seppeltronics thanks for the feedback.

You did perfectly with the var_dump(), but the output is a bit confusing, string(1271) tells us that the variable contains a string of 1271 characters, but then the actual output (between quotes) looks like it only contains a single newline character (or maybe there is a lot of whitespace ?). Weird. Maybe you can check in the page source.

Anyway I'll have a closer look at the code as indeed something seems fishy here in the logic flow, as you pointed out.

I wonder why this check is done at all. Can't the administrator open MantisBT in a browser?

@raspopov The idea is to ensure that whatever the administrator entered is actually a valid MantisBT instance, otherwise the generated config_inc.php file will result in a non-working installation (which IMO is a blocking issue). This value is pre-defaulted by the installer

Seppeltronics

Seppeltronics

2025-01-30 08:15

reporter   ~0069780

@dregad Please see if the screenshot of the browser displays the source you mean, if not, please let me know how I can find it, I try my best to support your work.

dregad

dregad

2025-01-30 08:34

developer   ~0069782

OK so just for the record, the logic is correct.

  • hard fail, meaning we can't continue with the installation, when the URL is not a valid Mantis site (i.e all cases except empty $g_path)
  • what actually makes the test fail is when the $t_url_check variable is not empty
  • if url_get() returns a string containing MantisBT, the test will succeed (even if $t_hard_fail = true)

So we need to understand what the actual value is, and why.

Can you try the same as before, but with var_dump( htmlspecialchars( $t_page_contents ) );

Then we can discuss and decide what to do, e.g. whether this check needs to be relaxed (no hard fail, provide a more descriptive error message instead warning that the installation may not be functional due to invalid URL), fix url_get() or something else.

dregad

dregad

2025-01-30 08:48

developer   ~0069783

Sorry, we cross-posted.

We're making some progress. By the look of things, url_get() appears to be working, as some page content was retrieved. The output is definitely NOT from MantisBT, but I have no idea where it's coming from, do you ?

What did you type in the URL to your installation field ? Is it the correct path to your MantisBT ? What happens if you type this exact URL in your browser's address bar ? Specifically, are there any redirections (301, 302) ?

Seppeltronics

Seppeltronics

2025-01-30 09:53

reporter   ~0069784

Sorry, because of confidentiality I can not tell you the servers domain and pathes, but it is a valid one that is working, I executed the php via the internet on a firefox webbrowser. The webbrowser had a valid field.

What is very strange, there is "sedoparking.com", a address I have never seen before and it is unrelated to the hoster, IONOS, as far as I'm aware.

I replaced it with "var_dump(htmlspecialchars( $t_page_contents ) ); ", if that is what your intention was, this result in the page:

string(1547) "<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <style type="text/css"> html, body, #partner, iframe { height:100%; width:100%; margin:0; padding:0; border:0; outline:0; font-size:100%; vertical-align:baseline; background:transparent; } body { overflow:hidden; } </style> <meta content="NOW" name="expires"> <meta content="index, follow, all" name="GOOGLEBOT"> <meta content="index, follow, all" name="robots"> <!-- Following Meta-Tag fixes scaling-issues on mobile devices --> <meta content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;" name="viewport"> </head> <body> <div id="partner"> </div> <script type="text/javascript"> document.write( '<script type="text/javascript" language="JavaScript"' + 'src="//sedoparking.com/frmpark/' + window.location.host + '/' + 'IONOSParkingDE' + '/park.js">' + '<\/script>' ); </script> </body> </html>"

dregad

dregad

2025-01-30 11:24

developer   ~0069785

Well it would seem your web host is doing something funky in terms of redirection, executing javascript https://sedoparking.com/frmpark/{{host}}/IONOSParkingDE/park.js... No idea what that script is doing or why, maybe you should ask your provider what is going on here...

Obviously that web page is not MantisBT, hence the check fails.

As a short-term workaround for you, I suggest to set $t_hard_fail = false; at line 308, so it marks the check as "POSSIBLE PROBLEM" instead of "BAD" and does not prevent the installation to continue (as shown in attached screenshot).

It would be interesting to confirm if, using a more advanced mechanism than file_get_contents() to retrieve the page, we are able to get the actual target page behind this kind of redirection. To make this easier for me to test, maybe you can post the hostname as a private message so only MantisBT devs can see it, or send it to me by e-mail. If not, hopefully you will agree to run some tests for me later...

image.png (31,171 bytes)   
image.png (31,171 bytes)   
raspopov

raspopov

2025-01-30 12:20

reporter   ~0069786

otherwise the generated config_inc.php file will result in a non-working installation (which IMO is a blocking issue)

@dregad, I remembered why this option seemed so familiar in a bad way...

Three months ago, when I upgraded my local MantisBT, I also stumbled across this check. I'm using external authentication. And, of course, access to MantisBT from outside was denied to the user running the web server (access is only allowed from inside, to the file system). I had to temporarily allow full external access as it was harder to search the wilds of PHP. Then, of course, I found this weird check.

I think it would be better to make this a warning and leave it to the administrator's judgement.

Seppeltronics

Seppeltronics

2025-01-30 12:22

reporter   ~0069787

Last edited: 2025-01-30 12:27

This page is called if an invalid address is used, no idea why Ionos does that. However, the address that was used to install MantisBT is valid and by altering the Code to always {$t_hard_fail = false;} it could be installed.

@raspopov , my MantisBt page is password-protected, so it can not have access from outside, I log in for executing the /admin/install.php from the webbrowser, but maybe that is not enough,... .

Thanks a lot, good work from your side, awesome! Maybe other webhosters do such shady things as well, it would be possible to have an output that is helpful and additional documentation? From the other reported bugs I assume the problems had similar root-causes? Figuring it out was definitely worth it, I could have just set the $t_hard_fail to false and move on, but I liked investing the time to have the entire picture.

Have you checked and maybe tested the Code-Branch (the else) line 495 and following: if( $f_path ) {$t_hard_fail = true;} the behaviour is correct that $t_hard_fail is always set true?


I will call the support, from northdata I analyzed the ownership,... not happy about someone adding content that is not under my contol.

Sedo GmbH, Köln <--- InterNetX Holding GmbH, Regensburg <--- Schlund Technologies GmbH, Regensburg <--- IONOS Group SE, Montabaur

dregad

dregad

2025-01-30 12:40

developer   ~0069788

@Seppeltronics I appreciate your willingness and support to get through this, which allowed us to gain a better understanding of the problem. Based on this analysis as well as @raspopov's input, I now believe that the best approach moving forwards is to relax this check. I'll make necessary changes.

Have you checked and maybe tested the Code-Branch (the else) line 495 and following: if( $f_path ) {$t_hard_fail = true;} the behaviour is correct that $t_hard_fail is always set true?

Yes, my answer is here: 0034783:0069782

dregad

dregad

2025-01-30 13:16

developer   ~0069790

See PR https://github.com/mantisbt/mantisbt/pull/2086

Seppeltronics

Seppeltronics

2025-01-30 15:15

reporter   ~0069791

There is a way to avoid the rerouting of the website by changing the .htaccess, it is documented here (sorry German, not sure if there is an English version): https://www.ionos.de/hilfe/domains/allgemeine-informationen-zur-domain-verwendung/aendern-der-verwendungsart-einer-domain/#c191325

@dregad what happens is, I access the https://www.foo.net/bar/MantisBT/admin/install.php , the popup comes up to enter the user and password, then the Mantis Installer is shown. Then I click on the Button Install/Upgrade and the string: string(23) "Authentication required" is shown.

Not only is a problem on a standard installation of the webhoster, it is also an issue that the installer.php can not retrieve data, because there is a login required to access the website.

dregad

dregad

2025-01-31 03:29

developer   ~0069793

All that seems very specific to your provider and setup.

Can you please test the proposed patch, and confirm if it fixes the problem for you ?

Seppeltronics

Seppeltronics

2025-01-31 08:45

reporter   ~0069794

Last edited: 2025-01-31 09:53

I copied the install.php to the 2.27.0 :

POSSIBLE PROBLEM
Web page at 'https://www.foo.net/bar/MantisBT/' does not appear to be a MantisBT site.
The system will not function properly if this URL is not accessible.


When I clone the git repo "git clone https://github.com/mantisbt/mantisbt.git
" and try to execute the install.php it shows only a white page, it also has a different file-size,... not sure why that is,...

Sorry, I think I should use : https://github.com/dregad/mantisbt.git ?

Related Changesets

MantisBT: master-2.27 518f3b9d

2025-01-30 13:15

dregad


Details Diff
Install: relax severity of failed $g_path test

In some cases (e.g. redirections, authenticated pages), url_get() fails
to retrieve the MantisBT home page. This blocks the installer, because
the check is marked as "hard fail".

This commit relaxes the check to a simple warning, making it the Admin's
responsibility fix the system if it ends up to be non-functional due
to an invalid $g_path.

Fixes 0034783
Affected Issues
0034783
mod - admin/install.php Diff File