Applies the approved QRForge brand kit throughout the app:
- New logo/icon SVGs, favicon set (ico/svg/png), apple-touch-icon, all
copied from the approved brand kit into src/dist/img/brand/. Old
Symbol_WhiteBlue.png/DynamicQRCode_Original.png removed (unused after
the swap).
- Sidebar brand image/text, login and change-password page logos, all
<title> tags, manifest.json name/theme-color, and the PWA icons
(dist/img/icon-192.png/icon-512.png, same filenames so no other
reference needed to change) updated to QRForge branding and the
#2563EB brand blue.
- New about.php page (+ sidebar link): credits the original upstream
fork (Giandonato Inverso) and chillerlan/php-qrcode, links to the free
qr.ensembia.com try-out, the commercial www.qrforge.eu product page,
and this GitHub repo for self-hosters.
- Footer now reads "QRForge" + "About / credits" + "Version 3.0"
(replaces the inherited "PHP Qrcode Generator by Giandonato Inverso" /
"Version 2.3.0" line - full credit moved to the About page instead).
- README.md rewritten: current feature set (all 16 static qr types,
presets, scanner, PWA, location search, roles), qr.ensembia.com as the
free try-out, www.qrforge.eu as the commercial product page, corrected
Docker Compose setup steps (.env is required now, the old README still
described the single-file demo setup from the original upstream fork).
IMPORTANT: not pushed to origin/gitea. Per user instruction, no push
until qrforge.eu domain registration is confirmed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fo3DiRRpmz2DXjD7Uzhc8u
- Sidebar submenu bullets (List all/Add new/Batch create) swapped from
far fa-circle to fas fa-angle-right - the outlined circle read as an
unchecked radio button, per feedback.
- List-page qr thumbnails now sit in a fixed 100x100 box with
object-fit:contain instead of width/height attrs, so taller images
(e.g. icon-above-qr) no longer get squashed into a square. Thumbnails
are now clickable, opening a shared Bootstrap modal with the full-size
image (same data-toggle/data-target pattern already used for the
delete-confirmation modal).
- Location QR form gets an address search box backed by OpenStreetMap
Nominatim (dist/js/location-search.js): free-text query, pick a result,
it fills in latitude/longitude. No API key needed; the browser talks to
nominatim.openstreetmap.org directly, called out in the field's help
text since queries leave the self-hosted server.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fo3DiRRpmz2DXjD7Uzhc8u
- Style preview now updates immediately when picking a color via the
colorpicker widget. It sets the input value through jQuery's synthetic
trigger(), which a native addEventListener('change', ...) never sees -
bound the listener through jQuery instead so both native and
colorpicker-driven changes refresh the preview.
- Icon-above-QR max height raised from 25% to 62.5% of QR height (~2.5x
bigger per feedback); the existing 60%-width cap now becomes the
practical limit for most icons. Verified generated QR still decodes.
- Dashboard's "Dynamic Qr codes" and "Static QR codes" info-boxes now link
to their list pages. "Total qr codes"/"Total Scans" left as-is - no
combined-list or scan-report page exists yet to link them to.
- Random-style button now has a "Randomize" label to match its row-mates
(Load preset/Save as preset/Style preview), instead of an empty spacer.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fo3DiRRpmz2DXjD7Uzhc8u
- Default the Owner select to the creator's own account instead of "All", so
superadmin-created codes no longer silently become visible to every admin
(the underlying NULL-fallback sharing behavior for an explicit "All" choice
is unchanged).
- Add a live color/precision/size preview swatch next to the preset picker.
- Let the frame text use a chosen DejaVu font + font size instead of a fixed
GD bitmap font.
- Add an optional self-uploaded icon rendered above the qr code (not embedded
in it, so scanability is unaffected).
- Make the Bitcoin qr amount optional; a standing wallet address is useful
without forcing a one-off amount per code.
- Add a WPA3 option to the WiFi qr encryption select.
- Fix a real bug surfaced while testing the preview/style JS: qrcode_options.php
was included once per static qr type (16 times on one page) and each
inclusion re-executed <script src="qrcode-style-tools.js">, so every button
click fired once per type - e.g. saving one preset wrote 16 duplicate rows,
and every tab except the first ("Text") had dead random-style/preset
buttons since only the first DOM match ever got a listener. Moved the
script include to load once per page and rewrote the JS to scope every
lookup to the triggering element's own tab-pane/form instead of relying on
getElementById's first-match behavior.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0196bLhZhuxwK3MkHuKLe5WB
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).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New static qr types:
- App Link: Android intent:// links (with package + optional browser
fallback) or a generic custom-scheme URI. iOS Universal Links need no
special encoding (they're just plain https:// URLs).
- Bluetooth: device name + MAC address. Purely informational, since unlike
WIFI:/vCard there's no OS-native "scan to pair" convention.
SVG export: already worked (format whitelist/dropdown existed since Fase 1),
verified rather than reimplemented.
Copy-to-clipboard button next to the download button on both qr list tables,
using the Clipboard API against a fetched blob.
Optional frame text label rendered below the qr code via GD after
generation (raster formats only, no-op for svg/eps).
Batch CSV upload (batch_qrcode.php): filename,link rows create dynamic qr
codes with sane defaults, downloadable as a zip. Required refactoring
Qrcode-intchil.php's generation path (previously always redirected/exited
via failure()/success(), which can't run in a loop) into a private
renderAndStore() that throws instead, shared by addQrcode() and the new
addQrcodeBatch(). Qrcode.php's addQrcodeBatch() is a separate, deliberately
duplicated implementation instead, since its generation logic is small
enough that duplication carries less risk than refactoring the working
external-API code path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes critical pre-existing issues found during review: bulk_action.php had no
auth check at all (unauthenticated download/delete of any qrcode) and built a
table name from unwhitelisted user input (SQL injection); the QR generator
classes wrote files from unvalidated filename/format, allowing path traversal
and arbitrary file writes. Also pins chillerlan/php-qrcode to 5.0.5 since
master now requires PHP 8.4, breaking the PHP 8.3 build.
- CSRF tokens on all POST forms and the bulk_action.php JSON endpoint
- Login rate limiting (5 attempts / 15 min) via new login_attempts table
- Hardened sessions: httponly/samesite cookies, 30 min idle timeout,
session regeneration on login
- Forced password change for the default superadmin/superadmin account
- Server-side validation in Users/DynamicQrcode/Qrcode classes
- Audit log table for auth, user, and qrcode actions
- Checked-in db schema (db/init.sql, migrations/) instead of relying on an
opaque prebuilt db image
- Production docker-compose with Nginx + php-fpm instead of the PHP dev server
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>