libreqr/index.php
vdbhb59 601def06c0 Add fallback i18n if strings are missing from locale (#26)
On page load, at most three locales are loaded: user locale
from Accept-Language, the default locale (if they aren't the
same), and the template locale. Using function getIntlString(),
if the user locale doesn't have a string, the string is fetched
from the default locale (if they aren't the same), and then,
finally, if neither the user or the default locale have the string,
it is taken from the template (which only contains string labels).
The lang attribute is set when possible.

Reviewed-on: #26 (https://code.antopie.org/miraty/libreqr/pulls/26)
Co-authored-by: Denise <denisebitca@42l.fr>
Co-committed-by: Denise <denisebitca@42l.fr>

[80ee070168]
2025-04-12 17:49:07 +05:30

338 lines
11 KiB
PHP
Executable file

<?php // This file is part of LibreQR, which is distributed under the GNU AGPLv3+ license
use Endroid\QrCode\Builder\Builder;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelLow;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelMedium;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelQuartile;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelHigh;
use Endroid\QrCode\Color\Color;
require "config.inc.php";
require "vendor/autoload.php";
define("LIBREQR_VERSION", "2.0.1");
// Defines the locale to be used
$locale = DEFAULT_LOCALE;
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$clientLocales = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
$clientLocales = preg_replace("#[A-Z0-9]|q=|;|-|\.#", "", $clientLocales);
$clientLocales = explode(',', $clientLocales);
foreach (array_diff(scandir("locales"), array('..', '.')) as $key => $localeFile)
$availableLocales[$key] = basename($localeFile, ".php");
foreach ($clientLocales as $clientLocale) {
if (in_array($clientLocale, $availableLocales)) {
$locale = $clientLocale;
break;
}
}
}
require "locales/" . $locale . ".php";
$chosen_loc = $loc;
if ($locale != DEFAULT_LOCALE) {
require "locales/" . DEFAULT_LOCALE . ".php";
$default_loc = $loc;
} else
$default_loc = $chosen_loc;
require "locales/template.php";
$template_loc = $loc;
// Function to get a specific string from the locale file, fall back on default then template if missing
function getIntlString(
$string_label,
$raw = false
) {
global $chosen_loc, $default_loc, $template_loc, $locale;
if (array_key_exists($string_label, $chosen_loc))
return $chosen_loc[$string_label];
if ($locale != DEFAULT_LOCALE AND array_key_exists($string_label, $default_loc)) {
if ($raw)
return $default_loc[$string_label];
return "<span lang=\"" . DEFAULT_LOCALE . "\">" . $default_loc[$string_label] . "</span>";
}
if ($raw)
return $template_loc[$string_label];
return "<span lang=\"en\">" . $template_loc[$string_label] . "</span>";
}
$params = array(
"txt" => "",
"redundancy" => DEFAULT_REDUNDANCY,
"margin" => DEFAULT_MARGIN,
"size" => DEFAULT_SIZE,
"bgColor" => DEFAULT_BGCOLOR,
"fgColor" => DEFAULT_FGCOLOR,
);
$qrCodeAvailable = NULL;
if (
isset($_POST['txt'])
AND isset($_POST['redundancy'])
AND isset($_POST['margin'])
AND isset($_POST['size'])
AND isset($_POST['bgColor'])
AND isset($_POST['fgColor'])
) {
$qrCodeAvailable = true;
if (strlen($_POST['txt']) >= 1 AND strlen($_POST['txt']) <= 4096) {
$params['txt'] = $_POST['txt'];
} else {
http_response_code(400);
exit("Wrong value for txt");
}
if ($_POST['redundancy'] === "low" OR $_POST['redundancy'] === "medium" OR $_POST['redundancy'] === "quartile" OR $_POST['redundancy'] === "high") {
$params['redundancy'] = $_POST['redundancy'];
} else {
http_response_code(400);
exit("Wrong value for redundancy");
}
if (is_numeric($_POST['margin']) AND $_POST['margin'] >= 0 AND $_POST['margin'] <= 1024) {
$params['margin'] = $_POST['margin'];
} else if (empty($_POST['margin'])) {
$params['margin'] = NULL;
} else {
http_response_code(400);
exit("Wrong value for margin");
}
if (is_numeric($_POST['size']) AND $_POST['size'] >= 21 AND $_POST['size'] <= 4096) {
$params['size'] = $_POST['size'];
} else if (empty($_POST['size'])) {
$params['size'] = NULL;
} else {
http_response_code(400);
exit("Wrong value for size");
}
if (preg_match("/^#[abcdefABCDEF0-9]{6}$/", $_POST['bgColor'])) {
$params['bgColor'] = substr($_POST['bgColor'], -6);
} else {
http_response_code(400);
exit("Wrong value for bgColor");
}
if (preg_match("/^#[abcdefABCDEF0-9]{6}$/", $_POST['fgColor'])) {
$params['fgColor'] = substr($_POST['fgColor'], -6);
} else {
http_response_code(400);
exit("Wrong value for fgColor");
}
$validFormSubmitted = true;
$rgbBgColor = array(
'r' => hexdec(substr($params['bgColor'],0,2)),
'g' => hexdec(substr($params['bgColor'],2,2)),
'b' => hexdec(substr($params['bgColor'],4,2)),
);
$qrCode = Builder::create()
->data($params['txt']);
if (!is_null($params['margin']))
$qrCode->margin($params['margin']);
if (!is_null($params['size']))
$qrCode->size($params['size']);
if ($params['redundancy'] === "high")
$qrCode->errorCorrectionLevel(new ErrorCorrectionLevelHigh());
else if ($params['redundancy'] === "quartile")
$qrCode->errorCorrectionLevel(new ErrorCorrectionLevelQuartile());
else if ($params['redundancy'] === "medium")
$qrCode->errorCorrectionLevel(new ErrorCorrectionLevelMedium());
else if ($params['redundancy'] === "low")
$qrCode->errorCorrectionLevel(new ErrorCorrectionLevelLow());
$qrCode
->backgroundColor(new Color(
$rgbBgColor['r'],
$rgbBgColor['g'],
$rgbBgColor['b']
))
->foregroundColor(new Color(
hexdec(substr($params['fgColor'],0,2)),
hexdec(substr($params['fgColor'],2,2)),
hexdec(substr($params['fgColor'],4,2))
));
try {
$result = $qrCode->build();
} catch (Exception $ex) {
http_response_code(500);
$qrCodeAvailable = false;
error_log("LibreQR encountered an error while generating a QR code: " . $ex);
}
}
?>
<!DOCTYPE html>
<html lang="<?= $locale ?>">
<head>
<meta charset="utf-8">
<title>LibreQR · <?= getIntlString('subtitle') ?></title>
<meta name="description" content="<?= getIntlString('description', raw: true) ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark light">
<meta name="application-name" content="LibreQR">
<meta name="referrer" content="no-referrer">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' data:; style-src 'self'; form-action 'self';">
<?php
require "themes/" . THEME . "/theme.php";
$colorScheme['theme'] = THEME;
$options = array('cache_dir' => 'css/', 'compress' => true);
$cssFileName = Less_Cache::Get(array("style.less" => ""), $options, $colorScheme);
?>
<link rel="stylesheet" media="screen" href="css/<?= $cssFileName ?>">
<?php
foreach($themeDimensionsIcons as $dimFav) // Set all icons dimensions
echo ' <link rel="icon" type="image/png" href="themes/' . THEME . '/icons/' . $dimFav . '.png" sizes="' . $dimFav . 'x' . $dimFav . '">' . "\n";
?>
</head>
<body>
<header>
<a id="linkTitles" href="./">
<div id="titles">
<h1>LibreQR</h1>
<h2><?= getIntlString('subtitle') ?></h2>
</div>
</a>
</header>
<form method="post" action="./#output">
<div class="param" id="txtParam">
<details>
<summary><label for="txt"><?= getIntlString('label_content') ?></label></summary>
<div class="helpText">
<?= getIntlString('help_content') ?>
</div>
</details>
<textarea rows="3" required="" id="txt" placeholder="<?= getIntlString('placeholder', raw: true) ?>" name="txt"><?= htmlspecialchars($params['txt']) ?></textarea>
</div>
<div id="sideParams">
<div class="param">
<details>
<summary><label for="redundancy"><?= getIntlString('label_redundancy') ?></label></summary>
<p class="helpText">
<?= getIntlString('help_redundancy') ?>
</p>
</details>
<select id="redundancy" name="redundancy">
<option <?php if ($params['redundancy'] === "low") echo 'selected="" '; ?>value="low">L - 7%</option>
<option <?php if ($params['redundancy'] === "medium") echo 'selected="" '; ?>value="medium">M - 15%</option>
<option <?php if ($params['redundancy'] === "quartile") echo 'selected="" '; ?>value="quartile">Q - 25%</option>
<option <?php if ($params['redundancy'] === "high") echo 'selected="" '; ?>value="high">H - 30%</option>
</select>
</div>
<div class="param">
<details>
<summary><label for="margin"><?= getIntlString('label_margin') ?></label></summary>
<p class="helpText">
<?= getIntlString('help_margin') ?>
</p>
</details>
<input type="number" id="margin" placeholder="<?= DEFAULT_MARGIN ?>" name="margin" required="" min="0" max="1024" value="<?= htmlspecialchars($params['margin']) ?>">
</div>
<div class="param">
<details>
<summary><label for="size"><?= getIntlString('label_size') ?></label></summary>
<p class="helpText">
<?= getIntlString('help_size') ?>
</p>
</details>
<input type="number" id="size" placeholder="<?= DEFAULT_SIZE ?>" name="size" required="" min="21" max="4096" value="<?= htmlspecialchars($params['size']) ?>">
</div>
</div>
<div id="colors">
<div class="param">
<label for="bgColor"><?= getIntlString('label_bgColor') ?></label>
<input type="color" name="bgColor" id="bgColor" value="#<?= htmlspecialchars($params['bgColor']) ?>">
</div>
<div class="param">
<label for="fgColor"><?= getIntlString('label_fgColor') ?></label>
<input type="color" name="fgColor" id="fgColor" value="#<?= htmlspecialchars($params['fgColor']) ?>">
</div>
</div>
<div class="centered">
<input class="button" type="submit" value="<?= getIntlString('button_create', raw: true) ?>" />
</div>
</form>
<?php
if ($qrCodeAvailable) {
$dataUri = $result->getDataUri();
$qrSize = $params['size'] + 2 * $params['margin'];
?>
<section id="output">
<div class="centered" id="downloadQR">
<a href="<?= $dataUri ?>" class="button" download="<?= htmlspecialchars($params['txt']); ?>.png"><?= getIntlString('button_download') ?></a>
</div>
<div class="centered" id="showOnlyQR">
<a title="<?= getIntlString('title_showOnlyQR', raw: true) ?>" href="<?= $dataUri ?>"><img width="<?= $qrSize ?>" height="<?= $qrSize ?>" alt='<?= getIntlString('alt_QR_before', raw: true) ?><?= htmlspecialchars($params['txt']); ?><?= getIntlString('alt_QR_after', raw: true) ?>' id="qrCode"<?php
// Compute the difference between the QR code and theme background colors
$diffLight = abs($rgbBgColor['r']-hexdec(substr($colorScheme['bg-light'],-6,2))) + abs($rgbBgColor['g']-hexdec(substr($colorScheme['bg-light'],-4,2))) + abs($rgbBgColor['b']-hexdec(substr($colorScheme['bg-light'],-2,2)));
$diffDark = abs($rgbBgColor['r']-hexdec(substr($colorScheme['bg-dark'],-6,2))) + abs($rgbBgColor['g']-hexdec(substr($colorScheme['bg-dark'],-4,2))) + abs($rgbBgColor['b']-hexdec(substr($colorScheme['bg-dark'],-2,2)));
// Determine whether a CSS corner is needed to let the user see the margin of the QR code
$contrastThreshold = 64;
if ($diffLight < $contrastThreshold)
echo " class='needLightContrast'";
if ($diffDark < $contrastThreshold)
echo " class='needDarkContrast'";
?> src="<?= $dataUri ?>"></a>
</div>
</section>
<?php
} else if ($qrCodeAvailable === false) {
echo " <p><strong>" . getIntlString('error_generation') . "</strong></p></body></html>";
}
?>
<footer>
<section id="info" class="metaText">
<?= getIntlString('metaText_qr') ?>
</section>
<?php if (CUSTOM_TEXT_ENABLED) { ?>
<section class="metaText">
<?= CUSTOM_TEXT ?>
</section>
<?php } ?>
<section class="metaText">
<small><?= getIntlString('metaText_legal') ?></small>
</section>
</footer>
</body>
</html>