1

Is there a specific format to Joomla's language ini-files? We would like to validate them, but it's not obvious which rules apply.

Should parse_ini_file work?

One example line that's valid? Or is it?

JOOMLA! DEFAULT IMAGES=Жуумла! үндсэн зурагнууд
1

2 Answers 2

3

I suppose if you are seeking line-by-line .ini file validation with actionable diagnostic detail, then I would avoid trying to re-invent the parser's intricacies/quirks via a regex approach.

Split your .ini file on newline characters, loop that array of strings, attempt to parse each line, handle each parsing attempt with some deliberate condition statements to offer insights about anomalies.

Code: (Demo)

$ini = <<<INI
TPL_CONTACT_MISC="Miscellaneous Info"
date.timezone = Pacific/Tahiti
TPL_CONTACT_MISC="Your german misc info translation"
COM_TODO_ALIGN="Image Float"
JOOMLA! DEFAULT IMAGES=Жуумла! үндсэн зурагнууд
COM_TODO_ALIGN_DESC="This will apply the classes 'pull-left', 'pull-center' or 'pull-right' to the '<figure>' or '<img>' element"
COM_TODO_BROWSE_FILES="Browse files"
COM_TODO_CAPTION="Caption"
COM_TODO_CAPTION_CLASS_LABEL="Caption Class"
COM_MEMBERSHIPS_FIELD_MEMBERSHIP_LABEL 911
COM_FRED_GREETING='Hello'
COM_FRED_GREETING_BARNEY='Yo, Buddy'
DR_SOLD="SOLD"
upload_tmp_dir = /var/www/html/tmp
INI;

$result = [];
foreach (explode(PHP_EOL, $ini) as $i => $line) {
    $parsed = @parse_ini_string($line);
    if (!is_array($parsed)) {
        $anomalies[] = "Invalid character(s) @ line index $i: $line";
        continue;
    }
    $key = key($parsed);
    if ($key === null) {
        $anomalies[] = "No valid declaration @ line index $i: $line";
    } elseif (key_exists($key, $result)) {
        $anomalies[] = "Redundant key declaration @ line index $i: $key => $parsed[$key]";
    } else {
        $result[$key] = $parsed[$key];
    }
}
var_export($anomalies);
echo "\n---\n";
var_export($result);

Output:

array (
  0 => 'Redundant key declaration @ line index 2: TPL_CONTACT_MISC => Your german misc info translation',
  1 => 'Invalid character(s) @ line index 4: JOOMLA! DEFAULT IMAGES=Жуумла! үндсэн зурагнууд',
  2 => 'No valid declaration @ line index 9: COM_MEMBERSHIPS_FIELD_MEMBERSHIP_LABEL 911',
)
---
array (
  'TPL_CONTACT_MISC' => 'Miscellaneous Info',
  'date.timezone' => 'Pacific/Tahiti',
  'COM_TODO_ALIGN' => 'Image Float',
  'COM_TODO_ALIGN_DESC' => 'This will apply the classes \'pull-left\', \'pull-center\' or \'pull-right\' to the \'<figure>\' or \'<img>\' element',
  'COM_TODO_BROWSE_FILES' => 'Browse files',
  'COM_TODO_CAPTION' => 'Caption',
  'COM_TODO_CAPTION_CLASS_LABEL' => 'Caption Class',
  'COM_FRED_GREETING' => 'Hello',
  'COM_FRED_GREETING_BARNEY' => 'Yo, Buddy',
  'DR_SOLD' => 'SOLD',
  'upload_tmp_dir' => '/var/www/html/tmp',
)

In your actual application, replace explode(PHP_EOL, $ini) with a file($myFilePath) call.


If we are talking more specifically about Joomla's language .ini files, then additional key and value validation conditions should be added to align with Joomla's version-specific parser requirements (e.g. support for dot in keys and missing double quotes around values). More reading at: Creating a language definition file

After a bit more effort to align with Joomla's parser restrictions: (Demo)

$anomalies = [];
$result = [];
foreach (explode(PHP_EOL, $ini) as $i => $line) {
    $parsed = @parse_ini_string($line);
    if (!is_array($parsed)) {
        $anomalies[] = "Invalid character(s) @ line index $i: $line";
        continue;
    }
    $key = key($parsed);
    if ($key === null) {
        $anomalies[] = "No valid declaration @ line index $i: $line";
    } elseif (!mb_check_encoding($key, 'ASCII')) {
        $anomalies[] = "Non-ASCII character found in key @ line index $i: $line";
    } elseif (!preg_match('/^[A-Z\d_-]+/', $key)) {
        $anomalies[] = "Key is expected to contain only uppercase letters, numbers, underscores, and hyphens @ line index $i: $line";
    } elseif (!preg_match('/^[A-Z\d_-]+="(.*)"$/', $line, $m)) {
        $anomalies[] = "Value not wrapped in double quotes @ line index $i: $line";
    } elseif (preg_match('/(?<!\\\\)(?:\\\\\\\\)*"/', $m[1])) {
        $anomalies[] = "Value contains unescaped double quote @ line index $i: $line";
    } elseif (key_exists($key, $result)) {
        $anomalies[] = "Redundant key declaration @ line index $i: $key => $result[$key] **overwritten by** $parsed[$key]";
        $result[$key] = $parsed[$key];  // mimic Joomla parser behaviour
    } else {
        $result[$key] = $parsed[$key];
    }
}
var_export($anomalies);
echo "\n---\n";
var_export($result);
0
1
<?php

if (!isset($argv[1])) {
    return;
}

$res = parse_ini_file($argv[1]);
if ($res === false) {
    return 1;
}
$content = file_get_contents($argv[1]);
$matches = [];
preg_match_all('/^[A-Z_0-9]*\s?=.*$/m', $content, $matches);
$file = file($argv[1]);
foreach ($res as $key => $val) {
    $found = false;
    foreach ($matches[0] as $i => $match) {
        if (strpos($match, $key) !== false) {
            $found = true;
            break;
        }
    }
    if (!$found) {
        printf("Did not find %s in regexp\n", $key);
    } else {
        unset($matches[0][$i]);
    }
}
if ($matches[0]) {
    printf("Some matches were not found in in parse_ini_file variable - are there duplicates?\n");
    var_dump($matches[0]);
}
exit(count($res) === count($matches[0]) ? 0 : 1);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.