Skip to content

Instantly share code, notes, and snippets.

@sebastian-marinescu
Last active April 23, 2021 12:09
Show Gist options
  • Save sebastian-marinescu/4b2991bc40dbbc405257ba5846adb1ef to your computer and use it in GitHub Desktop.
Save sebastian-marinescu/4b2991bc40dbbc405257ba5846adb1ef to your computer and use it in GitHub Desktop.
A PHP snippet for MODX to redirect to accepted client-languages (adapted so it trims prefix/suffix before matching)
<?php
/**
* LanguageMatch
*
* Based on https://gist.github.com/christianseel/504302ce8ddfcde009c0
* Original code by http://stackoverflow.com/a/3771447
*
* Updated by Indigo74 & x-vance for prefix/suffix and default context
*/
$snippetName = 'LanguageMatch';
// Verify required parameters
if (!isset($scriptProperties['ctxDefault'])) {
$modx->log(modX::LOG_LEVEL_ERROR, '['.$snippetName.'] is missing required property &ctxDefault!');
return;
}
// Store parameters
define('CONTEXT_PREFIX', $modx->getOption('ctxPrefix', $scriptProperties, ''));
define('CONTEXT_SUFFIX', $modx->getOption('ctxSuffix', $scriptProperties, ''));
define('DEFAULT_CONTEXT', $modx->getOption('ctxDefault', $scriptProperties, 'web'));
// parse list of comma separated language tags and sort it by the quality value
function parseLanguageList($languageList) {
if (is_null($languageList)) {
if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
return array();
}
$languageList = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
}
$languages = array();
$languageRanges = explode(',', trim($languageList));
foreach ($languageRanges as $languageRange) {
if (preg_match('/(\*|[a-zA-Z0-9]{1,8}(?:-[a-zA-Z0-9]{1,8})*)(?:\s*;\s*q\s*=\s*(0(?:\.\d{0,3})|1(?:\.0{0,3})))?/', trim($languageRange), $match)) {
if (!isset($match[2])) {
$match[2] = '1.0';
} else {
$match[2] = (string) floatval($match[2]);
}
if (!isset($languages[$match[2]])) {
$languages[$match[2]] = array();
}
$languages[$match[2]][] = strtolower($match[1]);
}
}
krsort($languages);
return $languages;
}
// compare two parsed arrays of language tags and find the matches
function findMatches($accepted, $available) {
$matches = array();
$any = false;
foreach ($accepted as $acceptedQuality => $acceptedValues) {
$acceptedQuality = floatval($acceptedQuality);
if ($acceptedQuality === 0.0) continue;
foreach ($available as $availableQuality => $availableValues) {
$availableQuality = floatval($availableQuality);
if ($availableQuality === 0.0) continue;
foreach ($acceptedValues as $acceptedValue) {
if ($acceptedValue === '*') {
$any = true;
}
foreach ($availableValues as $availableValue) {
$matchingGrade = matchLanguage($acceptedValue, $availableValue);
if ($matchingGrade > 0) {
$q = (string) ($acceptedQuality * $availableQuality * $matchingGrade);
if (!isset($matches[$q])) {
$matches[$q] = array();
}
if (!in_array($availableValue, $matches[$q])) {
$matches[$q][] = $availableValue;
}
}
}
}
}
}
if (count($matches) === 0 && $any) {
$matches = $available;
}
krsort($matches);
return $matches;
}
// compare two language tags and distinguish the degree of matching
function matchLanguage($a, $b) {
$a = explode('-', $a);
$b = explode('-', $b);
for ($i=0, $n=min(count($a), count($b)); $i<$n; $i++) {
if ($a[$i] !== $b[$i]) break;
}
return $i === 0 ? 0 : (float) $i / count($a);
}
//echo '<pre>';
$accepted = parseLanguageList($_SERVER['HTTP_ACCEPT_LANGUAGE']);
//print_r($accepted);
$babelKeys = $modx->getOption('babel.contextKeys',null,'en');
$patternCtx = '/' . CONTEXT_PREFIX . '(\w+)' . CONTEXT_SUFFIX . '/i';
$languageKeys = preg_replace($patternCtx, '$1', $babelKeys);
$available = parseLanguageList($languageKeys);
//print_r($available);
$matches = findMatches($accepted, $available);
print_r($matches);
if (!empty($matches)) {
$matched_lang = array_shift(array_values($matches));
$matched_lang = $matched_lang[0];
} else {
$matched_lang = 'en';
}
$context = $modx->getContext(CONTEXT_PREFIX . $matched_lang . CONTEXT_SUFFIX);
if (!$context) {
$context = $modx->getContext(DEFAULT_CONTEXT);
if (!$context) return 'CONTEXT NOT FOUND. (ERROR IN LANGUAGEMATCH SNIPPET)';
}
$site_start = $context->getOption('site_start',null,$modx->getOption('site_start',null,1));
$url = $modx->makeUrl($site_start);
$modx->sendRedirect($url, array('responseCode' => 'HTTP/1.1 302 Found'));
return;
@komatera
Copy link

Maybe it doesn't work without any language-code in it, you might be right.

But reverse-thought: can't you rename the contexts to "web-en" and "web-ru"?

There is the problem :) MODx doesn't let you change key of contexts, that already exists. I don't know, how to change "web" context key :(

@sebastian-marinescu
Copy link
Author

Ahhhh, I remember :)
But I also remember, that you can change the key in the database.
Can you login to phpMyAdmin and change it there?

(if for some reason you can't, another idea I have is: duplicate the contexts with all it's contents and give it a new key)

@komatera
Copy link

Ahhhh, I remember :)
But I also remember, that you can change the key in the database.
Can you login to phpMyAdmin and change it there?

(if for some reason you can't, another idea I have is: duplicate the contexts with all it's contents and give it a new key)

No resoursces after renaming)) Not good variant. It's doesn't work.
Copying context seems to be hard, bacause I have to chain every resource in babel :(

May be there is a variant to add some sort of parameter, where I can match "web" context to "en" cultureKey? Every next context will have the Key similar to it's cultureKey, but "web" is the problem...

@sebastian-marinescu
Copy link
Author

In that case, you will have to adapt the script for your needs.

You could simplify the script drastically and just check manually if user/browser wants Russian, then send to "ru", if not, send to "web"; done.

@komatera
Copy link

Oh, hard work, I am not a developer)) Will try, thank you a lot.

@komatera
Copy link

Ahhhh, I remember :)
But I also remember, that you can change the key in the database.
Can you login to phpMyAdmin and change it there?

(if for some reason you can't, another idea I have is: duplicate the contexts with all it's contents and give it a new key)

Is it right, that the snippet doesn't allow you to change language by babellinks? It will switch context every time, as I understand

@sebastian-marinescu
Copy link
Author

It was intended to be used like this originally:

image

@komatera
Copy link

komatera commented Apr 23, 2021

It was intended to be used like this originally:

image

I have copied web context to "en" and now I have three of them, web (/), en (/en/) and ru (/ru/). This code on the mainpage of "Web":

[[!LanguageMatch?
    &ctxDefault=`en`
    &ctxPrefix=``
]]

Ru is opening good, but my english Firefox send this:

Array ( [0.5] => Array ( [0] => en ) ) CONTEXT NOT FOUND. (ERROR IN LANGUAGEMATCH SNIPPET) 

Help me pls, I don't understand, why there is impossible to find "en" context. (babel.contextKeys is "en,ru", default_context is "en").

Error log

[2021-04-23 14:21:42] (ERROR @ /home/k/komatera/polustrade.com/public_html/core/model/modx/modx.class.php : 1031) `0` is not a valid integer and may not be passed to makeUrl()
[2021-04-23 14:21:42] (ERROR in resource 3 @ /home/k/komatera/polustrade.com/public_html/core/model/modx/modparser.class.php : 1373) Bad link tag `[[~0]]` encountered

@komatera
Copy link

komatera commented Apr 23, 2021

I uncommented "print" commands and that, what I have got:

Array
(
    [1.0] => Array
        (
            [0] => en-us
        )

    [0.5] => Array
        (
            [0] => en
        )

)
Array
(
    [1.0] => Array
        (
            [0] => en
            [1] => ru
        )

)
Array
(
    [0.5] => Array
        (
            [0] => en
        )

)
CONTEXT NOT FOUND. (ERROR IN LANGUAGEMATCH SNIPPET)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment