Skip to content

Instantly share code, notes, and snippets.

@sebastian-marinescu
Last active April 23, 2021 12:09
Show Gist options
  • Select an option

  • Save sebastian-marinescu/4b2991bc40dbbc405257ba5846adb1ef to your computer and use it in GitHub Desktop.

Select an option

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;
@sebastian-marinescu
Copy link
Copy Markdown
Author

Used like this in the root-resource of your main context:

[[!LanguageMatch?
    &ctxDefault=`web-de`
    &ctxPrefix=`web-`
]]

@JANogueira
Copy link
Copy Markdown

Just posted to say thanks. Very interesting

@komatera
Copy link
Copy Markdown

Used like this in the root-resource of your main context:

[[!LanguageMatch?
    &ctxDefault=`web-de`
    &ctxPrefix=`web-`
]]

What is I use contexts named "web" and "ru"?
Is it right?

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

@sebastian-marinescu
Copy link
Copy Markdown
Author

Hi, yeah this looks about right. It should redirect to Russian site, if the browser has "ru" inside "HTTP_ACCEPT_LANGUAGE".
If not, then it should fall-back to your "web" context.

@komatera
Copy link
Copy Markdown

komatera commented Apr 23, 2021

Hi, yeah this looks about right. It should redirect to Russian site, if the browser has "ru" inside "HTTP_ACCEPT_LANGUAGE".
If not, then it should fall-back to your "web" context.

I have had some sort of erros with "too many redirects". Don't understand why...

There are 2 contexts, "web" and "ru". One of them is http://site.com/, another - http://site.com/ru. May be, context without "en" in address is not correct for this snippet?

@sebastian-marinescu
Copy link
Copy Markdown
Author

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"?

@komatera
Copy link
Copy Markdown

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
Copy Markdown
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
Copy Markdown

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
Copy Markdown
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
Copy Markdown

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

@komatera
Copy link
Copy Markdown

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
Copy Markdown
Author

It was intended to be used like this originally:

image

@komatera
Copy link
Copy Markdown

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
Copy Markdown

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