Files
QRForge-selfhosted/src/scan_qrcode.php
T
dillard c3c6f167e0 Fase 3 priority 2: presets, random style, qr scanner, PWA
Preset system: qr_presets table (migration 005) plus a presets.php AJAX
endpoint (list/save/delete, CSRF-protected, scoped to the logged-in user's
own id - presets are personal, never shared across accounts). UI/JS lives in
dist/js/qrcode-style-tools.js.

Random style button: client-side only, fills foreground/background with a
random hex color pair (playful randomize, no contrast/scannability
guarantee).

Qr scanner (scan_qrcode.php): camera or image upload, decoded entirely
client-side via html5-qrcode (CDN, pinned to 2.3.8).

PWA: manifest.json + service-worker.js, icons generated from the existing
DynamicQRCode_Original.png glyph. The service worker only caches static
assets (css/js/images) and deliberately never touches PHP pages, since those
carry CSRF tokens and session-specific content that must never be cached.

Fixes a gap found while testing: qrcode_options.php is only a shared partial
for the static qr forms - the dynamic qr form (form_dynamic_add.php) has its
own separate copy of the foreground/background/level/size/filename/format
fields (pre-existing structure, not something introduced here). That meant
frame_text and the new preset/random-style UI never showed up on the
dynamic qr page. Added the same fields there too, verified with a dynamic qr
plus frame text (150x180px, the expected +30px padding).
2026-07-09 00:49:45 +02:00

149 lines
5.4 KiB
PHP

<?php
require_once 'includes/bootstrap.php';
require_once BASE_PATH . '/includes/auth_validate.php';
?>
<!DOCTYPE html>
<html lang="en">
<title>Qrcode Generator</title>
<head>
<?php include './includes/head.php'; ?>
</head>
<body class="hold-transition sidebar-mini layout-fixed layout-navbar-fixed layout-footer-fixed">
<div class="wrapper">
<!-- Navbar -->
<?php include './includes/navbar.php'; ?>
<!-- /.navbar -->
<!-- Main Sidebar Container -->
<?php include './includes/sidebar.php'; ?>
<!-- /.Main Sidebar Container -->
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<div class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1 class="m-0 text-dark">Scan a qr code</h1>
</div><!-- /.col -->
</div><!-- /.row -->
</div><!-- /.container-fluid -->
</div>
<!-- /.content-header -->
<!-- Main content -->
<section class="content">
<div class="container-fluid">
<div class="card card-primary">
<div class="card-header">
<h3 class="card-title">Scan from camera or upload an image</h3>
</div>
<div class="card-body">
<p class="text-muted">Decoding happens entirely in your browser - no image is uploaded to the server.</p>
<button type="button" id="start_camera_btn" class="btn btn-primary mb-3">
<i class="fa fa-camera"></i> Start camera
</button>
<button type="button" id="stop_camera_btn" class="btn btn-secondary mb-3" style="display:none;">
<i class="fa fa-stop"></i> Stop camera
</button>
<div id="camera_reader" style="max-width: 500px;"></div>
<div class="form-group mt-3">
<label for="qr_image_input">...or upload an image</label>
<input type="file" id="qr_image_input" accept="image/*" class="form-control">
</div>
<div id="scan_result_wrapper" class="mt-3" style="display:none;">
<label>Decoded content</label>
<div class="input-group">
<input type="text" id="scan_result" class="form-control" readonly>
<div class="input-group-append">
<button type="button" id="copy_result_btn" class="btn btn-outline-secondary"><i class="fa fa-copy"></i></button>
</div>
</div>
</div>
</div>
</div>
</div><!--/. container-fluid -->
</section><!-- /.content -->
</div><!-- /.content-wrapper -->
<!-- Footer and scripts -->
<?php include './includes/footer.php'; ?>
<script src="https://unpkg.com/html5-qrcode@2.3.8/html5-qrcode.min.js"></script>
<script>
(function () {
var resultInput = document.getElementById('scan_result');
var resultWrapper = document.getElementById('scan_result_wrapper');
var html5QrCode = new Html5Qrcode('camera_reader');
var cameraRunning = false;
function showResult(text) {
resultInput.value = text;
resultWrapper.style.display = '';
}
document.getElementById('start_camera_btn').addEventListener('click', function () {
var startBtn = this;
var stopBtn = document.getElementById('stop_camera_btn');
html5QrCode.start(
{ facingMode: 'environment' },
{ fps: 10, qrbox: 250 },
function (decodedText) {
showResult(decodedText);
}
).then(function () {
cameraRunning = true;
startBtn.style.display = 'none';
stopBtn.style.display = '';
}).catch(function (err) {
alert('Could not start the camera: ' + err);
});
});
document.getElementById('stop_camera_btn').addEventListener('click', function () {
var startBtn = document.getElementById('start_camera_btn');
var stopBtn = this;
if (!cameraRunning) {
return;
}
html5QrCode.stop().then(function () {
cameraRunning = false;
startBtn.style.display = '';
stopBtn.style.display = 'none';
});
});
document.getElementById('qr_image_input').addEventListener('change', function (event) {
var file = event.target.files[0];
if (!file) {
return;
}
html5QrCode.scanFile(file, false)
.then(function (decodedText) {
showResult(decodedText);
})
.catch(function (err) {
alert('Could not find a qr code in this image: ' + err);
});
});
document.getElementById('copy_result_btn').addEventListener('click', function () {
navigator.clipboard.writeText(resultInput.value).catch(function () {
resultInput.select();
document.execCommand('copy');
});
});
})();
</script>
</body>
</html>