Skip to content
This repository has been archived by the owner on Jun 7, 2024. It is now read-only.

Commit

Permalink
Allow templates to override language files
Browse files Browse the repository at this point in the history
Since people can embed HTML into language files, this can cause
templates to break.  So allow templates to override language files.

Also updates language handling to better handle Accept headers that
are ordered differently than by quality.  Related to that, updates
language initialization.

Starts to update things that include language files.  Will continue
over the next few releases.

Note that in the near term, this change should not actually cause
any behavior to change.  That won't happen until templates adjust
their mapping functions.  And of course it won't work until the
code loading the language file is changed.
  • Loading branch information
ecartz committed Dec 4, 2020
1 parent da2f654 commit 33b6226
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 41 deletions.
2 changes: 1 addition & 1 deletion contact_us.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

require 'includes/application_top.php';

require "includes/languages/$language/contact_us.php";
require language::map_to_translation('contact_us.php');

if (tep_validate_form_action_is('send')) {
$error = false;
Expand Down
12 changes: 1 addition & 11 deletions includes/classes/application.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,8 @@ public function fix_numeric_locale() {

public function set_session_language() {
if (!isset($_SESSION['language']) || isset($_GET['language'])) {
global $lng;
$GLOBALS['lng'] = language::build();

$lng = new language();

if (isset($_GET['language']) && tep_not_null($_GET['language'])) {
$lng->set_language($_GET['language']);
} else {
$lng->get_browser_language();
}

$_SESSION['language'] = $lng->language['directory'];
$_SESSION['languages_id'] = $lng->language['id'];
$GLOBALS['languages_id'] =& $_SESSION['languages_id'];
$GLOBALS['language'] =& $_SESSION['language'];
}
Expand Down
2 changes: 1 addition & 1 deletion includes/functions/autoloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ function tep_autoload_catalog($original_class) {

if (isset($class_files[$class])) {
if (isset($_SESSION['language']) && DIR_FS_CATALOG . 'includes/modules' === substr($class_files[$class], 0, $modules_directory_length)) {
$language_file = DIR_FS_CATALOG . 'includes/languages/' . $_SESSION['language'] . '/modules' . substr($class_files[$class], $modules_directory_length);
$language_file = language::map_to_translation('modules' . substr($class_files[$class], $modules_directory_length));
if (file_exists($language_file)) {
include $language_file;
}
Expand Down
119 changes: 97 additions & 22 deletions includes/system/versioned/1.0.7.other/1.0.7.12/language.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,39 +75,114 @@ class language {
'zu' => 'zu|zulu',
];

public $catalog_languages = [];
public $language;
public static function parse_browser_languages() {
$acceptable_locales = [];
foreach (explode(',', str_replace(' ', '', getenv('HTTP_ACCEPT_LANGUAGE'))) as $entry) {
$locale_qualities = explode(';q=', $entry);
$acceptable_locales[] = [
'locale' => $locale_qualities[0],
'quality' => $locale_qualities[1] ?? 1,
'codes' => explode('-', $locale_qualities[0]),
];
}

usort($acceptable_locales, function ($a, $b) {
$result = $b['quality'] <=> $a['quality'];
if ((0 === $result) && ($b['codes'][0] === $a['codes'][0])) {
return count($b['codes']) <=> count($a['codes']);
}

return $result;
});

return array_filter(
array_map('strtolower', array_column($acceptable_locales, 'locale')),
function ($v) {
foreach (static::LANGUAGES as $language) {
if (preg_match("{\A(?:$v)\z}", $language)) {
return true;
}
}

return false;
});
}

public static function load_languages() {
$languages = [];

public function __construct($language = '') {
$languages_query = tep_db_query("SELECT languages_id, name, code, image, directory FROM languages ORDER BY sort_order");
while ($languages = tep_db_fetch_array($languages_query)) {
$this->catalog_languages[$languages['code']] = [
'id' => $languages['languages_id'],
'name' => $languages['name'],
'image' => $languages['image'],
'directory' => $languages['directory'],
while ($language = tep_db_fetch_array($languages_query)) {
$languages[$language['code']] = [
'id' => $language['languages_id'],
'name' => $language['name'],
'image' => $language['image'],
'directory' => $language['directory'],
];
}

$this->set_language($language);
return $languages;
}

public function set_language($language) {
if ( (tep_not_null($language)) && (isset($this->catalog_languages[$language])) ) {
$this->language = $this->catalog_languages[$language];
public static function negotiate($languages) {
$fallback = null;
foreach (static::parse_browser_languages() as $locale) {
if (isset($languages[$locale])) {
return $locale;
}

if (is_null($fallback) && isset($languages[$locale = substr($locale, 0, 2)])) {
// if we do not yet have a fallback in case no locale matches, create one
$fallback = $locale;
}
}

return $fallback ?? DEFAULT_LANGUAGE;
}

public static function build() {
$languages = static::load_languages();

if (empty($_GET['language'])) {
$locale = static::negotiate($languages);
} else {
$this->language = $this->catalog_languages[DEFAULT_LANGUAGE];
$locale = $_GET['language'];
}

$language = new static($locale, $languages);

$_SESSION['language'] = $language->language['directory'];
$_SESSION['languages_id'] = $language->language['id'];

return $language;
}

public static function map_to_translation($page) {
$page = "includes/languages/{$_SESSION['language']}/$page";
$template =& Guarantor::ensure_global('oscTemplate');
$translation = $template->map_to_template($page, 'translation')
?? DIR_FS_CATALOG . $page;

return file_exists($translation) ? $translation : DIR_FS_CATALOG . $page;
}

public $catalog_languages;
public $language;

public function __construct($selection = null, $languages = null) {
$this->catalog_languages = $languages ?? static::load_languages();

$this->set_language($selection);
}

public function set_language($language) {
$this->language = $this->catalog_languages[$language ?? DEFAULT_LANGUAGE]
?? $this->catalog_languages[DEFAULT_LANGUAGE];
}

public function get_browser_language() {
foreach (explode(',', getenv('HTTP_ACCEPT_LANGUAGE')) as $language) {
foreach (static::LANGUAGES as $key => $value) {
if (preg_match('/^(' . $value . ')(;q=[0-9]\\.[0-9])?$/i', $language) && isset($this->catalog_languages[$key])) {
$this->language = $this->catalog_languages[$key];
return;
}
}
}
trigger_error('The get_browser_language function has been deprecated.', E_USER_DEPRECATED);
$this->language = $this->catalog_languages[static::negotiate($this->catalog_languages)];
}

}
7 changes: 3 additions & 4 deletions install/phoenix.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1126,14 +1126,13 @@ INSERT INTO hooks (hooks_site, hooks_group, hooks_action, hooks_code, hooks_clas
INSERT INTO hooks (hooks_site, hooks_group, hooks_action, hooks_code, hooks_class, hooks_method) VALUES ('shop', 'system', 'startApplication', '_18_set_currency', 'currencies', 'set_currency');
INSERT INTO hooks (hooks_site, hooks_group, hooks_action, hooks_code, hooks_class, hooks_method) VALUES ('shop', 'system', 'startApplication', '_19_ensure_navigation_history', 'Application', 'ensure_navigation_history');
INSERT INTO hooks (hooks_site, hooks_group, hooks_action, hooks_code, hooks_class, hooks_method) VALUES ('shop', 'system', 'startApplication', '_20_messageStack', 'Loader', 'messageStack');
INSERT INTO hooks (hooks_site, hooks_group, hooks_action, hooks_code, hooks_class, hooks_method) VALUES ('shop', 'system', 'startApplication', '_21_customer_data', 'Loader', 'customer_data');
INSERT INTO hooks (hooks_site, hooks_group, hooks_action, hooks_code, hooks_class, hooks_method) VALUES ('shop', 'system', 'startApplication', '_22_customer', 'Application', 'set_customer_if_identified');
INSERT INTO hooks (hooks_site, hooks_group, hooks_action, hooks_code, hooks_class, hooks_method) VALUES ('shop', 'system', 'startApplication', '_23_parse_actions', 'application_surface', 'parse_actions');
INSERT INTO hooks (hooks_site, hooks_group, hooks_action, hooks_code, hooks_class, hooks_method) VALUES ('shop', 'system', 'startApplication', '_23_template', 'Loader', 'oscTemplate');
INSERT INTO hooks (hooks_site, hooks_group, hooks_action, hooks_code, hooks_class, hooks_method) VALUES ('shop', 'system', 'startApplication', '_23a_customer_data', 'Loader', 'customer_data');
INSERT INTO hooks (hooks_site, hooks_group, hooks_action, hooks_code, hooks_class, hooks_method) VALUES ('shop', 'system', 'startApplication', '_23b_customer', 'Application', 'set_customer_if_identified');
INSERT INTO hooks (hooks_site, hooks_group, hooks_action, hooks_code, hooks_class, hooks_method) VALUES ('shop', 'system', 'startApplication', '_24_whos_online', '', 'whos_online::update');
INSERT INTO hooks (hooks_site, hooks_group, hooks_action, hooks_code, hooks_class, hooks_method) VALUES ('shop', 'system', 'startApplication', '_25_password_funcs', 'function_surface', 'password_funcs');
INSERT INTO hooks (hooks_site, hooks_group, hooks_action, hooks_code, hooks_class, hooks_method) VALUES ('shop', 'system', 'startApplication', '_26_validations', 'function_surface', 'validations');
INSERT INTO hooks (hooks_site, hooks_group, hooks_action, hooks_code, hooks_class, hooks_method) VALUES ('shop', 'system', 'startApplication', '_27_expire_specials', '', 'specials::expire');
INSERT INTO hooks (hooks_site, hooks_group, hooks_action, hooks_code, hooks_class, hooks_method) VALUES ('shop', 'system', 'startApplication', '_28_template', 'Loader', 'oscTemplate');
INSERT INTO hooks (hooks_site, hooks_group, hooks_action, hooks_code, hooks_class, hooks_method) VALUES ('shop', 'system', 'startApplication', '_29_category_path', 'application_surface', 'category_path');
INSERT INTO hooks (hooks_site, hooks_group, hooks_action, hooks_code, hooks_class, hooks_method) VALUES ('shop', 'system', 'startApplication', '_30_register_page_hook', 'hooks', 'register_page');

Expand Down
2 changes: 1 addition & 1 deletion password_reset.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
tep_redirect(tep_href_link('index.php'));
}

require "includes/languages/$language/password_reset.php";
require language::map_to_translation('password_reset.php');

$page_fields = [ 'password', 'password_confirmation' ];

Expand Down
5 changes: 4 additions & 1 deletion templates/default/includes/template.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ class default_template {
];

public function __construct() {
$hooks =& Guarantor::ensure_global('hooks', 'shop');
foreach ($this->_base_hook_directories as $directory) {
$GLOBALS['hooks']->add_directory($directory);
$hooks->add_directory($directory);
}

spl_autoload_register([$this, 'autoload_hooks'], true, true);
Expand All @@ -49,6 +50,8 @@ public static function _get_template_mapping_for($file, $type) {
case 'ext':
$file = static::extract_relative_path($file);
return DIR_FS_CATALOG . "templates/default/includes/$file";
case 'translation':
return DIR_FS_CATALOG . $file;
case 'literal':
default:
return DIR_FS_CATALOG . "templates/default/$file";
Expand Down

0 comments on commit 33b6226

Please sign in to comment.