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

/**
 * Language (Internationalization) API
 *
 * @package CoreAPI
 * @subpackage LanguageAPI
 * @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 authentication_api.php
 * @uses config_api.php
 * @uses constant_inc.php
 * @uses error_api.php
 * @uses plugin_api.php
 * @uses user_pref_api.php
 */

require_api( 'authentication_api.php' );
require_api( 'config_api.php' );
require_api( 'constant_inc.php' );
require_api( 'error_api.php' );
require_api( 'plugin_api.php' );
require_api( 'user_pref_api.php' );

# Cache of localization strings in the language specified by the last
# lang_load call
$g_lang_strings = array();

# stack for language overrides
$g_lang_overrides = array();

# To be used in custom_strings_inc.php :
$g_active_language = '';

/**
 * Loads the specified language and stores it in $g_lang_strings, to be used by lang_get
 * @param string $p_lang Name of Language to load.
 * @param string $p_dir  Directory Containing language file.
 * @return void
 */
function lang_load( $p_lang, $p_dir = null ) {
	global $g_lang_strings, $g_active_language;

	$g_active_language = $p_lang;
	if( isset( $g_lang_strings[$p_lang] ) && is_null( $p_dir ) ) {
		return;
	}

	if( !lang_language_exists( $p_lang ) ) {
		return;
	}

	if( $p_dir === null ) {
		include_once( config_get_global( 'language_path' ) . 'strings_' . $p_lang . '.txt' );
	} else {
		if( is_file( $p_dir . 'strings_' . $p_lang . '.txt' ) ) {
			include_once( $p_dir . 'strings_' . $p_lang . '.txt' );
		}
	}

	# Allow overriding strings declared in the language file.
	# custom_strings_inc.php can use $g_active_language.
	# Include file multiple times to allow for overrides per language.
	global $g_config_path;

	if( file_exists( $g_config_path . 'custom_strings_inc.php' ) ) {
		include( $g_config_path . 'custom_strings_inc.php' );
	}

	$t_vars = get_defined_vars();

	foreach( array_keys( $t_vars ) as $t_var ) {
		if( $t_var[0] === 's' && $t_var[1] === '_' ) {
			$g_lang_strings[$p_lang][substr( $t_var, 2 )] = $$t_var;
		} else if( 'MANTIS_ERROR' == $t_var ) {
			if( isset( $g_lang_strings[$p_lang][$t_var] ) ) {
				foreach( $$t_var as $t_key => $t_val ) {
					$g_lang_strings[$p_lang][$t_var][$t_key] = $t_val;
				}
			} else {
				$g_lang_strings[$p_lang][$t_var] = $$t_var;
			}
		}
	}
}

/**
 * Determine the preferred language
 * @return string
 */
function lang_get_default() {
	global $g_active_language;

	$t_lang = false;

	# Confirm that the user's language can be determined
	if( function_exists( 'auth_is_user_authenticated' ) && auth_is_user_authenticated() ) {
		$t_lang = user_pref_get_language( auth_get_current_user_id() );
	}

	# Otherwise fall back to default
	if( !$t_lang ) {
		$t_lang = config_get_global( 'default_language' );
	}

	if( $t_lang == 'auto' ) {
		$t_lang = lang_map_auto();
	}

	# Remember the language
	$g_active_language = $t_lang;

	return $t_lang;
}

/**
 * Auto Map Language from HTTP server data
 * @return string
 */
function lang_map_auto() {
	$t_lang = config_get_global( 'fallback_language' );

	if( isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) {
		$t_accept_langs = explode( ',', $_SERVER['HTTP_ACCEPT_LANGUAGE'] );
		$t_auto_map = config_get_global( 'language_auto_map' );

		# Expand language map
		$t_auto_map_exp = array();
		foreach( $t_auto_map as $t_encs => $t_enc_lang ) {
			$t_encs_arr = explode( ',', $t_encs );

			foreach( $t_encs_arr as $t_enc ) {
				$t_auto_map_exp[trim( $t_enc )] = $t_enc_lang;
			}
		}

		# Find encoding
		foreach( $t_accept_langs as $t_accept_lang ) {
			$t_tmp = explode( ';', mb_strtolower( $t_accept_lang ) );

			if( isset( $t_auto_map_exp[trim( $t_tmp[0] )] ) ) {
				$t_valid_langs = config_get( 'language_choices_arr' );
				$t_found_lang = $t_auto_map_exp[trim( $t_tmp[0] )];

				if( in_array( $t_found_lang, $t_valid_langs, true ) ) {
					$t_lang = $t_found_lang;
					break;
				}
			}
		}
	}

	return $t_lang;
}

/**
 * Ensures that a language file has been loaded.
 * Also load the currently active plugin's strings, if there is one.
 * @param string $p_lang The language name.
 * @return void
 */
function lang_ensure_loaded( $p_lang ) {
	global $g_lang_strings;

	if( !isset( $g_lang_strings[$p_lang] ) ) {
		lang_load( $p_lang );
	}

	# Load current plugin's strings
	$t_plugin_current = plugin_get_current();
	if( $t_plugin_current !== null ) {
		lang_load( $p_lang, config_get_global( 'plugin_path' ) . $t_plugin_current . '/lang/' );
	}
}

/**
* Check if the given language exists
*
* @param string $p_lang The language name.
* @return boolean
*/
function lang_language_exists( $p_lang ) {
	$t_valid_langs = config_get( 'language_choices_arr' );
	$t_valid = in_array( $p_lang, $t_valid_langs, true );
	return $t_valid;
}

/**
 * language stack implementation
 * push a language onto the stack
 * @param string $p_lang The language name.
 * @return void
 */
function lang_push( $p_lang = null ) {
	global $g_lang_overrides;

	# If no specific language is requested, we'll
	#  try to determine the language from the users
	#  preferences

	$t_lang = $p_lang;

	if( null === $t_lang ) {
		$t_lang = config_get_global( 'default_language' );
	}

	# don't allow 'auto' as a language to be pushed onto the stack
	#  The results from auto are always the local user, not what the
	#  override wants, unless this is the first language setting
	if( ( 'auto' == $t_lang ) && ( 0 < count( $g_lang_overrides ) ) ) {
		$t_lang = config_get_global( 'fallback_language' );
	}

	$g_lang_overrides[] = $t_lang;

	# Remember the language
	$g_active_language = $t_lang;

	# make sure it's loaded
	lang_ensure_loaded( $t_lang );
}

/**
 * pop a language from the stack and return it
 * @return string
 */
function lang_pop() {
	global $g_lang_overrides;

	return array_pop( $g_lang_overrides );
}

/**
 * return value on top of the language stack
 * return default if stack is empty
 * @return string
 */
function lang_get_current() {
	global $g_lang_overrides;

	$t_count_overrides = count( $g_lang_overrides );
	if( $t_count_overrides > 0 ) {
		$t_lang = $g_lang_overrides[$t_count_overrides - 1];
	} else {
		$t_lang = lang_get_default();
	}

	return $t_lang;
}

/**
 * Retrieves an internationalized string
 * This function will return one of (in order of preference):
 *  1. The string in the current user's preferred language (if defined)
 *  2. The string in English
 * Note: when $p_string is 'MANTIS_ERROR', the return value is an array of error messages.
 * @param string $p_string The language string to retrieve.
 * @param string $p_lang   The language name.
 * @return string|array
 */
function lang_get( $p_string, $p_lang = null ) {
	global $g_lang_strings;

	# If no specific language is requested, we'll try to
	# determine the language from the users preferences

	$t_lang = $p_lang;

	if( null === $t_lang ) {
		$t_lang = lang_get_current();
	}

	# Now we'll make sure that the requested language is loaded
	lang_ensure_loaded( $t_lang );

	# note in the current implementation we always return the same value
	#  because we don't have a concept of falling back on a language.  The
	#  language files actually *contain* English strings if none has been
	#  defined in the correct language
	# @todo thraxisp - not sure if this is still true. Strings from last language loaded
	#      may still be in memory if a new language is loaded.

	if( lang_exists( $p_string, $t_lang ) ) {
		return $g_lang_strings[$t_lang][$p_string];
	} elseif( $t_lang != 'english' ) {
		# If the string was not found in the foreign language, then retry with English.
		return lang_get( $p_string, 'english' );
	} else {
		error_parameters( $p_string );
		trigger_error( ERROR_LANG_STRING_NOT_FOUND, WARNING );
		return $p_string;
	}
}

/**
 * Check the language entry, if found return true, otherwise return false.
 * @param string $p_string The language string to retrieve.
 * @param string $p_lang   The language name.
 * @return boolean
 */
function lang_exists( $p_string, $p_lang ) {
	global $g_lang_strings;

	return( isset( $g_lang_strings[$p_lang] ) && isset( $g_lang_strings[$p_lang][$p_string] ) );
}

/**
 * Get language:
 * - If found, return the appropriate string (as lang_get()).
 * - If not found, no default supplied, return the supplied string as is.
 * - If not found, default supplied, return default.
 * @param string $p_string  The language string to retrieve.
 * @param string $p_default The default value to return.
 * @param string $p_lang    The language name.
 * @return string
 */
function lang_get_defaulted( $p_string, $p_default = null, $p_lang = null ) {
	$t_lang = $p_lang;

	if( null === $t_lang ) {
		$t_lang = lang_get_current();
	}

	# Now we'll make sure that the requested language is loaded
	lang_ensure_loaded( $t_lang );

	if( lang_exists( $p_string, $t_lang ) ) {
		return lang_get( $p_string );
	} elseif( $t_lang != 'english' ) {
		# If the string was not found in the foreign language, then retry with English.
		return lang_get_defaulted( $p_string, $p_default, 'english' );
	} else {
		# English string was not found either, return the default,
		# or the original string if no default was provided
		return $p_default !== null ? $p_default : $p_string;
	}
}

/**
 * Get current language code (ISO 639-1).
 * @return string Browser language code (e.g. 'en' for English)
 */
function lang_get_current_lang() {
	$t_key_arr = explode( ',', array_search( lang_get_current(),
		config_get_global( 'language_auto_map' ) ) );
	return trim( end( $t_key_arr ) );
}

/**
 * Maps current lang string to moment.js locale.
 * @see https://github.com/moment/moment/tree/develop/locale
 * @return string Two chars browser language code (e.g. 'de' for German)
 */
function lang_get_current_datetime_locale() {
	$t_lang = lang_get_current();

	# Lookup $g_language_auto_map by value and then return the first key
	$t_auto_map = config_get_global( 'language_auto_map' );
	$t_entry = array_search( $t_lang, $t_auto_map );
	$t_key_arr = explode( ',', $t_entry );

	return $t_key_arr[0];
}
