• WPML String Translation: fallback translation loading uses inconsistent domain casing and builds unsafe relative paths when registry returns false

    Environment

    • WordPress: 6.9.4
    • WPML String Translation: 3.5.2
    • WPML Multilingual CMS: 4.9.3
    • WooCommerce Multilingual: 5.5.5
    • PHP: 8.4
    • Web server: LiteSpeed
    • open_basedir: active for web requests

    Problem

    WPML String Translation triggers repeated PHP warnings during translation loading:

    PHP Warning: file_exists(): open_basedir restriction in effect. File(WordPress-en_US.mo) is not within the allowed path(s)
    PHP Warning: file_exists(): open_basedir restriction in effect. File(WordPress-en_US.l10n.php) is not within the allowed path(s)
    PHP Warning: file_exists(): open_basedir restriction in effect. File(WordPress-de_DE.mo) is not within the allowed path(s)
    PHP Warning: file_exists(): open_basedir restriction in effect. File(WordPress-de_DE.l10n.php) is not within the allowed path(s)
    PHP Warning: realpath(): open_basedir restriction in effect. File(/usr/local/lsws/fcgi-bin) is not within the allowed path(s)

    This is not limited to the WordPress domain. The same casing/filename issue can affect other WPML String Translation domains such as WP Endpoints.

    Where It Happens

    wpml-string-translation/classes/MO/Hooks/LoadTranslationFile.php

    In LoadTranslationFile::getDefaultWordPressTranslationPath():

    $defaultPathDirectory = $wp_textdomain_registry->get( $domain, $locale );
    $defaultPathFile = "{$defaultPathDirectory}{$domain}-{$locale}.mo";

    When $wp_textdomain_registry->get( $domain, $locale ) returns false, WPML still concatenates it into a filename. PHP converts false to an empty string, producing relative paths like:

    WordPress-en_US.mo
    WordPress-en_US.l10n.php
    WP Endpoints-en_US.mo
    WP Endpoints-en_US.l10n.php

    With LiteSpeed, the current working directory can be /usr/local/lsws/fcgi-bin, so file_exists() / realpath() on these relative paths triggers open_basedir warnings.

    Diagnostics

    The WordPress textdomain registry returns:

    WordPress + pl_PL => /home/.../public/app/languages/wpml/
    WordPress + de_DE => false
    WordPress + en_US => false

    WPML-generated String Translation files exist, but filenames are normalized to lowercase:

    app/languages/wpml/wordpress-pl_PL.l10n.php
    app/languages/wpml/wordpress-de_DE.l10n.php
    app/languages/wpml/wordpress-en_US.l10n.php
    app/languages/wpml/wp endpoints-pl_PL.l10n.php

    Query Monitor confirms WPML loads the String Translation files using these lowercase filenames:

    WordPress    php    WPML\S\M\J\MO::loadTextDomain()    app/languages/wpml/wordpress-pl_PL.l10n.php
    WordPress    php    WPML\S\M\J\MO::loadTextDomain()    app/languages/wpml/wordpress-en_US.l10n.php

    So the warning is not caused by missing WPML String Translation files. It is caused by the additional fallback lookup for default translation files using the original domain casing.

    Root Cause

    WPML stores/uses domains such as:

    WordPress
    WP Endpoints

    For example, the scanner fallback uses:

    $domain = $domain ? $domain : 'WordPress';

    But WPML writes generated translation files with lowercased domain filenames:

    strtolower( $domain ) . '-' . $locale

    So:

    WordPress    -> wordpress-en_US.l10n.php
    WP Endpoints -> wp endpoints-pl_PL.l10n.php

    However, LoadTranslationFile::getDefaultWordPressTranslationPath() searches using the original domain casing:

    WordPress-en_US.l10n.php
    WP Endpoints-pl_PL.l10n.php

    This creates a mismatch between the domain name and the generated filename. When the registry cannot resolve a path, the missing guard also causes unsafe relative path checks.

    Expected Behavior

    WPML should:

    1. Never concatenate a false registry result into a filesystem path.
    2. Never call file_exists() / realpath() on relative fallback paths produced from unresolved registry data.
    3. Use the same filename normalization when reading fallback/generated translation files as it uses when writing them.
    4. Handle domains with uppercase letters and spaces consistently, including WordPress and WP Endpoints.

    Required Fix

    LoadTranslationFile::getDefaultWordPressTranslationPath() should first validate the registry result:

    $defaultPathDirectory = $wp_textdomain_registry->get( $domain, $locale );
    
    if ( ! is_string( $defaultPathDirectory ) || '' === $defaultPathDirectory ) {
        return null;
    }

    Then fallback lookup must use the same normalized filename convention WPML uses when generating files:

    $normalizedDomain = strtolower( str_replace( '/', '-', $domain ) );

    and check normalized filenames as well as any standard WordPress filename conventions relevant to core/plugin/theme translation files:

    {$normalizedDomain}-{$locale}.mo
    {$normalizedDomain}-{$locale}.l10n.php

    This needs to cover domains like:

    WordPress
    WP Endpoints

    Impact

    • Repeated PHP warnings in production logs.
    • Issue appears in web requests where open_basedir is active.
    • The actual WPML String Translation files can still load correctly, but the fallback/default translation lookup is unsafe and inconsistent.
    • Affects domains with uppercase letters and spaces, not only WordPress.
Viewing 3 replies - 1 through 3 (of 3 total)
  • Plugin Support Nicolas V.

    (@nicolasviallet)

    Hi @igyhere

    Thanks for reporting this issue and for sharing a detailed analysis.

    Please note that WordPress.org support is for our free plugin, WPML Multilingual and Multicurrency for WooCommerce. For our premium plugins, the support forum is available here.

    That said, I’ll forward your report to our developers internally. I’m not a developer myself, but I quickly reviewed the report and the files involved, and here is my initial feedback:

    1. Paths returning false

    Yes, we could add a safety guard to avoid this. Something like:

    $defaultPathDirectory = $wp_textdomain_registry->get( $domain, $locale );
    if ( ! is_string( $defaultPathDirectory ) || '' === $defaultPathDirectory ) {
    return null;
    }

    1. Normalization / casing

    For this one, I’m not entirely sure that we should blindly lowercase all domain names. It looks like we are handling two different things here: the default native WordPress translations (WordPress), and the WPML-generated files (wordpress lowercase).

    The purpose of getDefaultWordPressTranslationPath() seems to be finding those native translation files. I’ll leave this part for the developers to review, but perhaps checking again using the lowercase domain name could be an extra fallback.

    Anyway, thanks again for reporting this and helping us improve our products.

    Plugin Support Nicolas V.

    (@nicolasviallet)

    FYI,

    Those PHP warnings have been reported already. I have added your feedback to the existing ticket 😉

    Thread Starter igyhere

    (@igyhere)

    Great thanks for taking a look into this. It’s been for a while. I hope this gets resolved soon.

Viewing 3 replies - 1 through 3 (of 3 total)

You must be logged in to reply to this topic.