<?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/>.

/**
 * Utility API
 *
 * Utility functions are *small* functions that are used often and therefore
 * have *no* prefix, to keep their names short.
 *
 * Utility functions have *no* dependencies on any other APIs, since they are
 * included first in order to make them available to all the APIs.
 * Miscellaneous functions that provide functionality on top of other APIS
 * are found in the helper_api.
 *
 * @package CoreAPI
 * @subpackage UtilityAPI
 * @copyright Copyright 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
 * @copyright Copyright 2002  MantisBT Team - mantisbt-dev@lists.sourceforge.net
 * @link http://www.mantisbt.org
 *
 * @uses config_api.php
 * @uses constant_inc.php
 * @uses error_api.php
 */

require_api( 'config_api.php' );
require_api( 'constant_inc.php' );
require_api( 'error_api.php' );

/**
 * converts a 1 value to X
 * converts a 0 value to a space
 * @param integer $p_num A numeric to translate as a boolean for display.
 * @return string X or space
 * @access public
 */
function trans_bool( $p_num ) {
	if( 0 == $p_num ) {
		return '&#160;';
	} else {
		return icon_get( 'fa-check', 'fa-lg' );
	}
}

/**
 * Add a trailing DIRECTORY_SEPARATOR to a string if it isn't present
 * @param string $p_path A string representing a file system path.
 * @return string
 * @access public
 */
function terminate_directory_path( $p_path ) {
	return rtrim( $p_path, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR;
}

/**
 * Return true if the parameter is an empty string or a string
 * containing only whitespace, false otherwise
 * @param string $p_var String to test whether it is blank.
 * @return boolean
 * @access public
 */
function is_blank( $p_var ) {
	$p_var = trim( (string)$p_var );
	$t_str_len = strlen( $p_var );
	if( 0 == $t_str_len ) {
		return true;
	}
	return false;
}

/**
 * Get the named php ini variable but return it as a boolean
 * @param string $p_name A php.ini variable to evaluate.
 * @return boolean
 * @access public
 */
function ini_get_bool( $p_name ) {
	$t_result = ini_get( $p_name );

	if( is_string( $t_result ) ) {
		switch( strtolower( $t_result ) ) {
			case 'off':
			case 'false':
			case 'no':
			case 'none':
			case '':
			case '0':
				return false;
			case 'on':
			case 'true':
			case 'yes':
			case '1':
				return true;
		}
	}
	return (bool)$t_result;
}

/**
 * Get the named php.ini variable but return it as a number after converting
 * the giga (g/G), mega (m/M) and kilo (k/K) postfixes. These postfixes do not
 * adhere to IEEE 1541 in that k=1024, not k=1000. For more information see
 * http://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes
 * @param string $p_name Name of the configuration option to read.
 * @return int Integer value of the configuration option.
 * @access public
 */
function ini_get_number( $p_name ) {
	$t_value = ini_get( $p_name );

	switch( substr( $t_value, -1 ) ) {
		case 'G':
		case 'g':
			$t_result = (int)$t_value * 1073741824;
			break;
		case 'M':
		case 'm':
			$t_result = (int)$t_value * 1048576;
			break;
		case 'K':
		case 'k':
			$t_result = (int)$t_value * 1024;
			break;
		default:
			$t_result = (int)$t_value;
			break;
	}
	return $t_result;
}

/**
 * Sort a multi-dimensional array by one of its keys
 * @param array   $p_array     Array to sort.
 * @param string  $p_key       Array key to sort array on.
 * @param integer $p_direction Sort direction.
 * @return array sorted array
 * @access public
 */
function multi_sort( array $p_array, $p_key, $p_direction = ASCENDING ) {
	if( DESCENDING == $p_direction ) {
		$t_factor = -1;
	} else {
		# might as well allow everything else to mean ASC rather than erroring
		$t_factor = 1;
	}

	if( empty( $p_array ) ) {
		return $p_array;
	}
	if( !is_array( current( $p_array ) ) ) {
		error_parameters( 'tried to multisort an invalid multi-dimensional array' );
		trigger_error( ERROR_GENERIC, ERROR );
	}

	# Security measure: see http://www.mantisbt.org/bugs/view.php?id=9704 for details
	if( array_key_exists( $p_key, current( $p_array ) ) ) {
		uasort(
			$p_array,
			function( $a, $b ) use( $t_factor, $p_key ) {
				return $t_factor * strnatcasecmp( $a[$p_key], $b[$p_key] );
			}
		);
	} else {
		trigger_error( ERROR_INVALID_SORT_FIELD, ERROR );
	}
	return $p_array;
}

/**
 * Return GD version
 * It doesn't use gd_info() so it works with PHP < 4.3.0 as well
 * @return int represents gd version
 * @access public
 */
function get_gd_version() {
	$t_GDfuncList = get_extension_funcs( 'gd' );
	if( !is_array( $t_GDfuncList ) ) {
		return 0;
	} else {
		if( in_array( 'imagegd2', $t_GDfuncList ) ) {
			return 2;
		} else {
			return 1;
		}
	}
}

/**
 * return true or false if string matches current page name
 * @param string $p_string To test against the php script name.
 * @return boolean
 * @access public
 */
function is_page_name( $p_string ) {
	return isset( $_SERVER['SCRIPT_NAME'] ) && ( 0 < strpos( $_SERVER['SCRIPT_NAME'], $p_string ) );
}

/**
 * Return true if the host operating system is Windows, false otherwise.
 *
 * @return bool
 * @access public
 */
function is_windows_server() {
	if( defined( 'PHP_WINDOWS_VERSION_MAJOR' ) ) {
		return PHP_WINDOWS_VERSION_MAJOR > 0;
	}
	return 'Windows' === PHP_OS_FAMILY;
}

/**
 * return array of class properties (via reflection api)
 * @param string  $p_classname      Class name.
 * @param string  $p_type           Property type - public/private/protected/static.
 * @param boolean $p_return_object  Whether to return array of property objects.
 * @param boolean $p_include_parent Whether to include properties of parent classes.
 * @return array
 * @access public
 */
function getClassProperties( $p_classname, $p_type = 'public', $p_return_object = false, $p_include_parent = false ) {
	$t_ref = new ReflectionClass( $p_classname );
	$t_props = $t_ref->getProperties();
	$t_props_arr = array();
	foreach( $t_props as $t_prop ){
		$t_name = $t_prop->getName();
		if( $t_prop->isPublic() and (stripos( $p_type, 'public' ) === false) ) {
			continue;
		}
		if( $t_prop->isPrivate() and (stripos( $p_type, 'private' ) === false) ) {
			continue;
		}
		if( $t_prop->isProtected() and (stripos( $p_type, 'protected' ) === false) ) {
			continue;
		}
		if( $t_prop->isStatic() and (stripos( $p_type, 'static' ) === false) ) {
			continue;
		}
		if( $p_return_object ) {
			$t_props_arr[$t_name] = $t_prop;
		} else {
			$t_props_arr[$t_name] = true;
		}
	}
	if( $p_include_parent ) {
		if( $t_parentclass = $t_ref->getParentClass() ) {
			$t_parent_props_arr = getClassProperties( $t_parentclass->getName() );
			if( count( $t_parent_props_arr ) > 0 ) {
				$t_props_arr = array_merge( $t_parent_props_arr, $t_props_arr );
			}
		}
	}
	return $t_props_arr;
}

/**
 * Return path to system fonts location.
 *
 * @return string
 *
 * @deprecated 2.28.1 See PHPDoc for {@see $g_system_font_folder}.
 * @access public
 */
function get_font_path() {
	$t_font_path = config_get_global( 'system_font_folder' );
	if( !$t_font_path ) {
		if( is_windows_server() ) {
			$t_system_root = $_SERVER['SystemRoot'] ?? '';
			if( $t_system_root ) {
				return $t_system_root . DIRECTORY_SEPARATOR . 'Fonts' . DIRECTORY_SEPARATOR;
			}
		} else {
			$t_font_dirs = [
				'/usr/share/fonts/corefonts/',
				'/usr/share/fonts/truetype/msttcorefonts/',
				'/usr/share/fonts/msttcorefonts/',
				'/usr/share/fonts/truetype/',
			];
			foreach( $t_font_dirs as $t_font_dir ) {
				if( file_exists( $t_font_dir ) ) {
					return $t_font_dir;
				}
			}
		}
	}
	return $t_font_path;
}

/**
 * unserialize() with Exception instead of PHP notice.
 *
 * When given invalid data, unserialize() throws a PHP notice; this function
 * relies on a custom error handler to throw an Exception instead.
 *
 * @param string $p_string The serialized string.
 * @return mixed The converted value
 *
 * @throws ErrorException
 */
function safe_unserialize( $p_string ) {
	set_error_handler( 'error_convert_to_exception' );
	try {
		$t_data = unserialize( $p_string );
	}
	catch( ErrorException $e ) {
		restore_error_handler();
		throw $e;
	}
	restore_error_handler();
	return $t_data;
}
