mirror of
https://git.bakhai.co.in/FbIN/libreqr.git
synced 2026-04-17 08:25:52 +05:30
Drop Less support: The style.less and themes/libreqr/theme.php files are converted to plain CSS. The main Less features used in LibreQR were variables and selector nesting, but since CSS custom properties and nesting are now widely supported across browsers, the less.php dependency can be ditched. To read the background color for contrast improvement, a regex is used on the theme CSS file. (5468a43a2f) Format locales according to Weblate (f3e2249a84) Estonian Translation (eb0ea03857) doc: Add link to Weblate + enhance markup (49394ebd9b)
358 lines
No EOL
12 KiB
PHP
Executable file
358 lines
No EOL
12 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";
|
|
|
|
const CONTRAST_THRESHOLD = 64;
|
|
const LIBREQR_VERSION = '2.0.1+dev';
|
|
|
|
// 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>";
|
|
}
|
|
|
|
preg_match('#.*/(?<page>.*)$#', $_SERVER['REQUEST_URI'], $matches);
|
|
define('PAGE', match ($matches['page']) {
|
|
'wifi' => 'wifi',
|
|
'' => 'home',
|
|
default => 'unknown',
|
|
});
|
|
|
|
define('ICONS_DIR', 'themes/' . THEME . '/icons');
|
|
$icon_files = array_diff(scandir(ICONS_DIR), ['.', '..']);
|
|
if (PAGE === 'unknown') {
|
|
$allowed_filenames['style.css'] = 'text/css';
|
|
$allowed_filenames['themes/' . THEME . '/theme.css'] = 'text/css';
|
|
$allowed_filenames['themes/' . THEME . '/logo-dark.png'] = 'image/png';
|
|
$allowed_filenames['themes/' . THEME . '/logo-light.png'] = 'image/png';
|
|
foreach ($icon_files as $icon_file)
|
|
$allowed_filenames[ICONS_DIR . '/' . $icon_file] = 'image/png';
|
|
foreach ($allowed_filenames as $filename => $type) {
|
|
if (str_ends_with($_SERVER['REQUEST_URI'], $filename)) {
|
|
header('Content-Type: ' . $type);
|
|
echo file_get_contents($filename);
|
|
exit();
|
|
}
|
|
}
|
|
http_response_code(404);
|
|
}
|
|
|
|
$_POST = [
|
|
"form" => $_POST['form'] ?? NULL,
|
|
"wifi" => [
|
|
"ssid" => $_POST['wifi']['ssid'] ?? "",
|
|
"password" => $_POST['wifi']['password'] ?? "",
|
|
],
|
|
"main" => [
|
|
"txt" => $_POST['main']['txt'] ?? "",
|
|
"redundancy" => $_POST['main']['redundancy'] ?? DEFAULT_REDUNDANCY,
|
|
"margin" => $_POST['main']['margin'] ?? DEFAULT_MARGIN,
|
|
"size" => $_POST['main']['size'] ?? DEFAULT_SIZE,
|
|
"bgColor" => $_POST['main']['bgColor'] ?? "#" . DEFAULT_BGCOLOR,
|
|
"fgColor" => $_POST['main']['fgColor'] ?? "#" . DEFAULT_FGCOLOR,
|
|
],
|
|
];
|
|
|
|
if ($_POST['wifi']['ssid'] !== "") {
|
|
if (!(strlen($_POST['wifi']['ssid']) >= 1 AND strlen($_POST['wifi']['ssid']) <= 4096)) {
|
|
http_response_code(400);
|
|
exit("Wrong value for ssid");
|
|
}
|
|
$escaped_ssid = preg_replace("/([\\\;\,\"\:])/", "\\\\$1", $_POST['wifi']['ssid']);
|
|
|
|
if ($_POST['wifi']['password'] === "")
|
|
$_POST['main']['txt'] = "WIFI:T:nopass;S:{$escaped_ssid};;";
|
|
else {
|
|
if (strlen($_POST['wifi']['password']) > 4096) {
|
|
http_response_code(400);
|
|
exit("Wrong value for password");
|
|
}
|
|
|
|
$escaped_password = preg_replace("/([\\\;\,\"\:])/", "\\\\$1", $_POST['wifi']['password']);
|
|
|
|
$_POST['main']['txt'] = "WIFI:T:WPA;S:{$escaped_ssid};P:{$escaped_password};;";
|
|
}
|
|
}
|
|
|
|
$qrCodeAvailable = NULL;
|
|
|
|
if ($_POST['main']['txt'] !== "") {
|
|
$qrCodeAvailable = true;
|
|
|
|
if (!(strlen($_POST['main']['txt']) >= 1 AND strlen($_POST['main']['txt']) <= 4096)) {
|
|
http_response_code(400);
|
|
exit("Wrong value for txt");
|
|
}
|
|
|
|
if (!in_array($_POST['main']['redundancy'], ["low", "medium", "quartile", "high"], strict: true)) {
|
|
http_response_code(400);
|
|
exit("Wrong value for redundancy");
|
|
}
|
|
|
|
if (!(is_numeric($_POST['main']['margin']) AND $_POST['main']['margin'] >= 0 AND $_POST['main']['margin'] <= 1024)) {
|
|
http_response_code(400);
|
|
exit("Wrong value for margin");
|
|
}
|
|
|
|
if (!(is_numeric($_POST['main']['size']) AND $_POST['main']['size'] >= 21 AND $_POST['main']['size'] <= 4096)) {
|
|
http_response_code(400);
|
|
exit("Wrong value for size");
|
|
}
|
|
|
|
if (preg_match("/^#[abcdefABCDEF0-9]{6}$/", $_POST['main']['bgColor']) === false) {
|
|
http_response_code(400);
|
|
exit("Wrong value for bgColor");
|
|
}
|
|
|
|
if (preg_match("/^#[abcdefABCDEF0-9]{6}$/", $_POST['main']['fgColor']) === false) {
|
|
http_response_code(400);
|
|
exit("Wrong value for fgColor");
|
|
}
|
|
|
|
$rgbBgColor = [
|
|
'r' => hexdec(substr($_POST['main']['bgColor'],1,2)),
|
|
'g' => hexdec(substr($_POST['main']['bgColor'],3,2)),
|
|
'b' => hexdec(substr($_POST['main']['bgColor'],5,2)),
|
|
];
|
|
|
|
$qrCode = Builder::create()
|
|
->data($_POST['main']['txt'])
|
|
->margin($_POST['main']['margin'])
|
|
->size($_POST['main']['size'])
|
|
->errorCorrectionLevel(match ($_POST['main']['redundancy']) {
|
|
"low" => new ErrorCorrectionLevelLow(),
|
|
"medium" => new ErrorCorrectionLevelMedium(),
|
|
"quartile" => new ErrorCorrectionLevelQuartile(),
|
|
"high" => new ErrorCorrectionLevelHigh(),
|
|
})
|
|
->backgroundColor(new Color(
|
|
$rgbBgColor['r'],
|
|
$rgbBgColor['g'],
|
|
$rgbBgColor['b'],
|
|
))
|
|
->foregroundColor(new Color(
|
|
hexdec(substr($_POST['main']['fgColor'],1,2)),
|
|
hexdec(substr($_POST['main']['fgColor'],3,2)),
|
|
hexdec(substr($_POST['main']['fgColor'],5,2)),
|
|
));
|
|
|
|
try {
|
|
$result = $qrCode->build();
|
|
} catch (Exception $ex) {
|
|
http_response_code(500);
|
|
$qrCodeAvailable = false;
|
|
error_log("FbIN QR encountered an error while generating a QR code: " . $ex);
|
|
}
|
|
}
|
|
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="<?= $locale ?>">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title><?=
|
|
match (PAGE) {
|
|
'home' => 'FbIN QR' . getIntlString('subtitle'),
|
|
'wifi' => 'FbIN QR' .getIntlString('tab_wifi_title'),
|
|
'unknown' => 'FbIN QR' .getIntlString('error_404'),
|
|
}
|
|
?></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="FbIN QR">
|
|
<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';">
|
|
|
|
<!-- Avoids leaking the "prefers-color-scheme" media query to the server -->
|
|
<link rel="preload" as="image" href="themes/<?= THEME ?>/logo-dark.png">
|
|
<link rel="preload" as="image" href="themes/<?= THEME ?>/logo-light.png">
|
|
|
|
<link rel="stylesheet" media="screen" href="style.css">
|
|
<link rel="stylesheet" media="screen" href="themes/<?= THEME ?>/theme.css">
|
|
<?php
|
|
natsort($icon_files);
|
|
foreach($icon_files as $icon_file) {
|
|
if (ctype_digit($icon_size = substr($icon_file, 0, -4)))
|
|
echo ' <link rel="icon" type="image/png" href="' . ICONS_DIR . '/' . $icon_size . '.png" sizes="' . $icon_size . 'x' . $icon_size . '">' . "\n";
|
|
}
|
|
?>
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<header>
|
|
<a id="linkTitles" href="./">
|
|
<hgroup id="titles">
|
|
<h1>FbIN QR</h1>
|
|
<p><?= getIntlString('subtitle') ?></p>
|
|
</hgroup>
|
|
</a>
|
|
</header>
|
|
|
|
<nav>
|
|
<h2 class="sr-only">Type de code QR</h2>
|
|
<ul>
|
|
<li<?php if (PAGE == 'home') echo ' class="tab-selected"' ?>><a href="./"><div><?= getIntlString('tab_text') ?></div></a></li>
|
|
<li<?php if (PAGE == 'wifi') echo ' class="tab-selected"' ?>><a href="./wifi"><div><?= getIntlString('tab_wifi') ?></div></a></li>
|
|
</ul>
|
|
</nav>
|
|
|
|
<?php if (PAGE === 'wifi') { ?>
|
|
<form method="post" action="./wifi#output">
|
|
<div class="param textboxParam">
|
|
<label for="ssid"><?= getIntlString('label_wifi_ssid') ?></label>
|
|
<input type="text" id="ssid" placeholder="<?= getIntlString('placeholder_wifi_ssid', raw: true) ?>" name="wifi[ssid]" required maxlength="4096" value="<?= htmlspecialchars($_POST['wifi']['ssid']) ?>">
|
|
</div>
|
|
<div class="param textboxParam">
|
|
<details>
|
|
<summary><label for="password"><?= getIntlString('label_wifi_password') ?></label></summary>
|
|
<div class="helpText">
|
|
<?= getIntlString('help_wifi_password') ?>
|
|
</div>
|
|
</details>
|
|
<input type="text" id="password" placeholder="<?= getIntlString('placeholder_wifi_password', raw: true) ?>" name="wifi[password]" maxlength="4096" value="<?= htmlspecialchars($_POST['wifi']['password']) ?>">
|
|
</div>
|
|
<?php require 'common.php' ?>
|
|
</form>
|
|
<?php } else if (PAGE === 'home') { ?>
|
|
<form method="post" action="./#output">
|
|
<div class="param textboxParam" id="txtParam">
|
|
<details>
|
|
<summary><label for="txt"><?= getIntlString('label_content') ?></label></summary>
|
|
<div class="helpText">
|
|
<?= getIntlString('help_content') ?>
|
|
</div>
|
|
</details>
|
|
<textarea rows="3" id="txt" placeholder="<?= getIntlString('placeholder', raw: true) ?>" name="main[txt]"><?= htmlspecialchars($_POST['main']['txt']) ?></textarea>
|
|
</div>
|
|
<?php require 'common.php' ?>
|
|
</form>
|
|
<?php } else { ?>
|
|
<p>
|
|
<?= getIntlString('error_404') ?>
|
|
</p>
|
|
<?php } ?>
|
|
|
|
<?php
|
|
|
|
if ($qrCodeAvailable) {
|
|
$dataUri = $result->getDataUri();
|
|
|
|
$qrSize = $_POST['main']['size'] + 2 * $_POST['main']['margin'];
|
|
|
|
?>
|
|
|
|
<section id="output">
|
|
<div class="centered" id="downloadQR">
|
|
<a href="<?= $dataUri ?>" class="button" download="<?= htmlspecialchars($_POST['main']['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($_POST['main']['txt']); ?><?= getIntlString('alt_QR_after', raw: true) ?>' id="qrCode"<?php
|
|
|
|
// Compute the difference between the QR code and theme background colors to determine whether a CSS corner is needed to let the user see the margin of the QR code
|
|
preg_match(
|
|
'/prefers-color-scheme: light.+--bg: (?<bg_light>#[A-Fa-f0-9]{6});.+prefers-color-scheme: dark.+--bg: (?<bg_dark>#[A-Fa-f0-9]{6});.+/s',
|
|
file_get_contents('themes/' . THEME . '/theme.css'),
|
|
$css,
|
|
);
|
|
if (
|
|
abs($rgbBgColor['r'] - hexdec(substr($css['bg_light'], -6, 2)))
|
|
+ abs($rgbBgColor['g'] - hexdec(substr($css['bg_light'], -4, 2)))
|
|
+ abs($rgbBgColor['b'] - hexdec(substr($css['bg_light'], -2, 2)))
|
|
< CONTRAST_THRESHOLD
|
|
)
|
|
echo " class='needLightContrast'";
|
|
if (
|
|
abs($rgbBgColor['r'] - hexdec(substr($css['bg_dark'], -6, 2)))
|
|
+ abs($rgbBgColor['g'] - hexdec(substr($css['bg_dark'], -4, 2)))
|
|
+ abs($rgbBgColor['b'] - hexdec(substr($css['bg_dark'], -2, 2)))
|
|
< CONTRAST_THRESHOLD
|
|
)
|
|
echo " class='needDarkContrast'";
|
|
|
|
?> src="<?= $dataUri ?>"></a>
|
|
</div>
|
|
<?php if (PAGE === "wifi") { ?>
|
|
<p>
|
|
<?= getIntlString('wifi_raw_content_before') ?><code><?= htmlspecialchars($_POST['main']['txt']) ?></code><?= getIntlString('wifi_raw_content_after') ?>
|
|
</p>
|
|
<form method="POST" action="./">
|
|
<?php foreach ($_POST['main'] as $name => $value) { ?>
|
|
<input type="hidden" name="main[<?= htmlspecialchars($name) ?>]" value="<?= htmlspecialchars($value) ?>" />
|
|
<?php } ?>
|
|
<input type="submit" value="<?= getIntlString('button_edit') ?>" />
|
|
</form>
|
|
<?php } ?>
|
|
</section>
|
|
|
|
<?php
|
|
} else if ($qrCodeAvailable === false) {
|
|
echo " <p><strong>" . getIntlString('error_generation') . "</strong></p></body></html>";
|
|
}
|
|
?>
|
|
|
|
<footer>
|
|
|
|
<?php if (CUSTOM_TEXT_ENABLED) { ?>
|
|
<section class="metaText">
|
|
<?= CUSTOM_TEXT ?>
|
|
</section>
|
|
<?php } ?>
|
|
|
|
</footer>
|
|
|
|
</body>
|
|
</html>
|