From be9164850a56d7c34d7f2b226aedddfffb6d0ca1 Mon Sep 17 00:00:00 2001 From: Dillard Blom Date: Thu, 9 Jul 2026 00:49:45 +0200 Subject: [PATCH] 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). Co-Authored-By: Claude Sonnet 5 --- db/init.sql | 14 +++ db/migrations/005_qr_presets.sql | 14 +++ src/dist/img/icon-192.png | Bin 0 -> 6822 bytes src/dist/img/icon-512.png | Bin 0 -> 26495 bytes src/dist/js/qrcode-style-tools.js | 169 ++++++++++++++++++++++++++++++ src/forms/form_dynamic_add.php | 46 +++++++- src/forms/qrcode_options.php | 40 ++++++- src/includes/footer.php | 8 +- src/includes/head.php | 3 + src/includes/sidebar.php | 8 ++ src/manifest.json | 14 +++ src/presets.php | 89 ++++++++++++++++ src/scan_qrcode.php | 148 ++++++++++++++++++++++++++ src/service-worker.js | 33 ++++++ 14 files changed, 580 insertions(+), 6 deletions(-) create mode 100644 db/migrations/005_qr_presets.sql create mode 100644 src/dist/img/icon-192.png create mode 100644 src/dist/img/icon-512.png create mode 100644 src/dist/js/qrcode-style-tools.js create mode 100644 src/manifest.json create mode 100644 src/presets.php create mode 100644 src/scan_qrcode.php create mode 100644 src/service-worker.js diff --git a/db/init.sql b/db/init.sql index 9ff0e87..0969941 100644 --- a/db/init.sql +++ b/db/init.sql @@ -89,6 +89,20 @@ CREATE TABLE IF NOT EXISTS `audit_log` ( KEY `user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- Fase 3 (priority 2): saved color/style presets per user +CREATE TABLE IF NOT EXISTS `qr_presets` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `user_id` int(25) NOT NULL, + `name` varchar(50) NOT NULL, + `foreground` varchar(10) NOT NULL, + `background` varchar(10) NOT NULL, + `level` varchar(1) NOT NULL DEFAULT 'L', + `size` int(10) unsigned NOT NULL DEFAULT 200, + `created_at` datetime NOT NULL, + PRIMARY KEY (`id`), + KEY `user_id` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/db/migrations/005_qr_presets.sql b/db/migrations/005_qr_presets.sql new file mode 100644 index 0000000..9c0ce49 --- /dev/null +++ b/db/migrations/005_qr_presets.sql @@ -0,0 +1,14 @@ +-- Fase 3 (priority 2): saved color/style presets per user. + +CREATE TABLE IF NOT EXISTS `qr_presets` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `user_id` int(25) NOT NULL, + `name` varchar(50) NOT NULL, + `foreground` varchar(10) NOT NULL, + `background` varchar(10) NOT NULL, + `level` varchar(1) NOT NULL DEFAULT 'L', + `size` int(10) unsigned NOT NULL DEFAULT 200, + `created_at` datetime NOT NULL, + PRIMARY KEY (`id`), + KEY `user_id` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/src/dist/img/icon-192.png b/src/dist/img/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..ae8e3582ff4f6cb24c2260a419b31fa375b84f21 GIT binary patch literal 6822 zcma)>cT^KmxAq65gdzk%2ofnu?_IhOL8>6Vi1glj6%Yv0K|q=aNR$?u)X)(S5F=Fq zDFGn@2BkNV_Kp92>)!jWdy|=*l{4ogGqd)d{XD;u_)u5vR&el{_F$$U6EIjdNnTdG66dwNF3QnR4?bOS-#g~Y3FwYSh-n%|uPcQFU zURXB#cJuAsMZsby zRmDiIC)h#$>i`?t{eB2X63Rh5Kjc=v4tL{hHK!)b9sxWZh@ywa%+JlG9ULMcfsvNJ z3~J_fp}hk;vr-Q5|#CHMLz#&t-pnZXFWAks`*s%0=?J zl5c!*ahkzp!^Qj8uU|J|KZC#Bgm64lS)FX(kfDxtFsJeM#C6;iQ;PI<4duF)$elH; zDxNX2Mj1yu*m`iPmr%_+YgwD3Plwn$c_N;lGJuESSIAuG*3^96c1D;)uotv^{PqLp{76* zjTXC52mSC5=i-QqMRK;qw(vXoI&=uwvkChYiV3@EPHvYDN%+`>uSr10QU_Rn8 zcrTu>pM)qxuHy9&%MZn~!%k1VDPtZI#%W^3sW_8X78cINqIQ>}&d%=36;A(MmwM=& zO@c@eV^s6?+bh8mJSa(Yi*FpBbY`dhde7IWtc?Xv?8({Cn0Iweox9_oESq#PL;^H( z*nyi>r_FM^4cGTE5w9>d6GZdk0ej7S;P%CbaJDC~)B$u;l_M0wfo+(dpEs?0?CtKp zAEuzFs7P&yJ$Wb{t*YM{>i&D5#k#t#PCN5}WbRFgRH9$o0y&!%qh)Pvt?skRRc@Qv z$(#|V%ReWnySt|-GeFN^cFjy_Y3XfTf!X87 zQ*|`6RLZA(B6CYPLsvs7YCj_X@? z;e3f*Zw~#EnV}46oc;Z2^dli4 zL~*fpG%X_|qonuE;bCOVZfy@K!2Y(oyE|oxXw1J{FZJl~NTWdq5_+~22LMn#PE!+; za`45fD^geOAa1UvpVY%M7sZ#9sa0B5#ugMDNJD*K7|nWRP=+zt)60YN8iktv!4JAY zGbl2He#knM5I2#o>moW2 z9$23q{8cwqT1;~>+~fLW0Vxp_@b}*bv+ev`{S&M+RP=8)FS@;@rJI>@xS?9ek9Y4~ z4&DoM`fX=?e(7>Czg$;JTH1tv6^cm1%cOld>C6p*2gT#O?4j*J=ZyoUrJ0S7p@@{J z)RB>z&6l3)EW*)7ff5XhX{yb;=_|DE#DT$qN>RIg1Ig>8xmAj&_Sj7U+Ph zyvT8<#>2xS_~!PyOl_;OMX^ziqj`qF%|9x(M(n6;<=K>?;0d?pCJaVO@~Z^3;nd^I zoVB&BE#aKicWHi1hy-I}h5;@02=*7Y>~vdah71VE*nafj{qn|_FO||YMUXV{_n?*# z6~$OQv@d|Wv5h&CvvRUNw|Fl_^_QRy?3u4zAtRMF!H9)aY>rsFE3SfbIA7RyGfYQ~t8q=)BmM5Uu9+C zLNu#AQ{SC1g^_PVOg^&PtsLMj2EiYKBuiF=o%`Yr? zU}gmh-QM`Xt*y7&Sa0Z*8F)+_M*~ldo2#n65GqFYcV73RjP+mmga~A4Xf#km_{8N^ zA>U<3@rQpptvb4rZvNf)`9@Wofkq&y+MfBQPeIm#ZG)Z9VH?=|kDtTbqKL&H(^NT)=Z9Bs zoA5vPMeDGqXxtYki4VTIr-f+LR^_RYBNwN9t{B+V*E+DvP_Q* ziRt>pc?I}0t4)ljJgval0DuHk5}wxzy;~xt2k4N{7mF&03H7T%f_(85|C12-UzNhY z0s0ZYqILR|rZZ=L+<3b|51~idp(^sA!17|Z8Ui3O8*u!#US5c>=GB1VADiC4_7v(T z1D7sH#iK>44s9fj(P%IATa`u@8LQW<0Z>E?b~UKuv2gj6TeF#|nee|~e|~W;a`X0{cW$M2t8BbGWjgb_answB3^DGx z-f=}ZR$d%6@pF4A>T(Mgo~TNEt5sW&#Kql(<40(BeLV*h($peJOMO5~OVfhExRy_u z96q~;=X&1I-Xbj~Mn@ShCLFY$imHfKwr3xgQBY{Q^kM#L;e9M~@?@x+amxJ;Ty)$K zPrXIZH!J!7Zx=>yQPJKaD z)Wk`gT2Zl!i%VW%!Th%c1>ZBX@bCgGp?WZ)%F$(31wyCFm28J>D*wJnm1beH&MhVM z2EqY8Zi+3t!rVPPTzq!aqs~o-(%JUI6pe$;ee*lWrn3LsTPw)F2hnFx%UOM5)#)Fs zkTu--r>``bX*4*fY5&P8*7W?V)6(SG@2_FV=c>1S@z{cs&WewFSdpHu({pz9X8bo_ zTU97uzh5;0dzSy|<)>AD!J)_PAtfCh+Ik(IsEvHd8=S^f^^F5PJ{gj(-5+l)RuOjS>Jvfpj}LXh;dV!q3BV`^sR^vU80>BGH`qH6l6iEk?_lb<%+ zM8AFTwJgo*iQ0J5ZE6)sSdegbRO5?OiW1L%Sy0d+5$WyZnLupqPkAJt-&j%~Do=v2Rbd!{FUk!G$GORrKdJym^+U{?j;_n@a_) zv{aiStL%+|QZgaQepUTfg$0-CmuDyC#gRcjG>VtDF&2UYv|fX-V>VgwFu%8xvQ7y{ zEz|pBg&m>pg4YtA8fP=dv>V{*JRcAxlhNB+%wE5U021QVh={I}T}$z7{%%gUa;_KH zRYpc8ObaI0#DpX_8Q;1h^%{*{irAdWy3K=bBAK*-4Q?MFA07z`3keAdGDg7=yxIl| z8N6TLDc|U|+4)Q`#A;&`9sJ6P%oN<4hCoSsb{12V{@gJ%#HFI$>nKD}VJJ8_Sn7e~ z2Azif3lC}cwUXCh4ac-d7R#Ro*x$SICOo#cNcGQL0UARNyl>F4zZW~SNpw0_=py~; z_VWQdS5Hq_sj!AOZ|<#E2%NRUCn?4w(QL@)SVGgBo2?;?7w<#793%DX;e-f{q0`%{w_0S+|{wmA*{P+SZ%7z4j^lS>i#=K_bM6{W+Y0 z>ArOG7lm-leL0gT)#BSv`jD1(WNH=W>+7n~Cn%2m4{SwR8d3YAL zPt);u-c2JJb))Re9VNurjVWIrxnmV9a}#tBot>SxGz~_YnE94aXT%&xkD&RFiS$av z>8q^;cf5s!h1A}NsTohqklz^Bs&SI3;HsgD(+z1C>lweWUBw=`B58=R?)> z-z5nYK3*l_7<6g`O;kfN8l8mO65ZboiKk;mjxYPXl9Q5Ckj&F9cV1@%D+W_;?wbi0 z&TCiW`erS4jkE2Bx}moOw9M1s<8nxTZnc?fIXc&D*d6lEJ*?Fsv(?jUHa;OP595{5 zBW{Nw3WM~K%J3k6BovhC_{0$YY(_PGh5<`q9;_Cj@0HD_hDdDo%|XtW!VCH75l23c zOm?i&`ECQs0FK7%-ADw%?7i3QK1+hw==y#^$@uhiOVMvTSXoC^RrjJd=M?#nZEh+Q zfqCT9A<)vtM4w5Q9bYxPgtNqI^Eax;LWm$S_v^0G@BN-2J6*)8ZZ1q0ZEb;@mLq~$ z%zHJ=M;S09+l9wceCtpqL40gXkO~>Jt5Hr5F8R+014GC7;(Y}d$&9eB#iIDJRjD=oG3i|aE!)@!}| zj3mq8QvqhWs_TqM;JgkdC8z}u8oT%NP`otpx3g+-SsU1xq^T!LY{>&zl?;zII61E4 z1GsMsl-_B4$q1ptHZW4@LHMkwB&hA{&7k&+FXtGDRd%L`Zia>3(U3pDOo|yw>Tp2+ z404bUkfIk2%1y+)XEiD*|2u0kB4Q_zl7QM$Mnk^2Z+9luMczUt*!6x6{)9{wUOk@7pXg{i9~y?ue2dP58hGf$1BiE=n!i{Iw!1ESr}XN^Mn}>7 z!pu*np9IOk6%}!;m{S|SF6ulrcFZFf>}O9z@DoSJ>l{W6;W1*PtA3b4uR3oJ%dViV zI!rm3*UilXcFSUm&D8eL28)`=sKOB^37C&uVWf=I&BXhZq^@_O*^y=g1f4KP$3XC^ zHa09I84Zo84{*9~(P%6|L2i6||Aa>7^f=v|WUm$19~*li#|9{OJgnAWbf<+P>~=x& zXgU8audvYFl|oulatDri#H9hLpYssv{SNx=d5cF^$zExdb+?B4uT6N!6)p-HK}xv8 zLfi!yD67zaM_6i;UUMz|I&5if0XI4LQU96n5z?FlJm12{g+e1jG3+igqfAj&r!=r< z(NL$!e09R?Y+Kt(PmI`6D{d$qRQIe>!Vjf$3ySiYSvu&GhmvR12TvK#$qJseM#Xr|Cmz>1K%?EF}iMS72pRBFMbIp9SPq}VvY+N1wzJV8M zqhpj3xWICJsX0*@vo<>seF4^XnbEKEdODmSKfKM8R##i|U`#=aJ4?=by3qMRq55%K z+xDZri-TAqBydjXN*iK(M5@cX5%`GvsqqUMUMLU<3P#!v*gB(D2s7;Jjx1 zZ#$%xl;%QLc0)!L0>gHGJ{{V0(&^;7lBWgwz<$vy>S<>?o0vHZP_xwp$Yc{CzD>{V z^<$Pc?oOH#%(U!LEBaNWXBX`f&blIk!bx_vuA$8M$$Q+*(I%l{)cz}f>Omsu`T8;; ze~30%G(-6Ye26r*m&v;I1=D~7f4&CiA@!Vzl)Mz^$KAa>&$ry`y8jlMqWf5F5YYet z$Fkm)el@a+C>gJ*m#;UgezrMF#PM}v@hJH2$?j~UGx*PLe8D5U(0^F5_sx~4-B`?d zSsM8 zqj0h0hFOM&$2_bao}R-EG0?ANkDI;wUO7(cYTo-egEgcWgw33ugTfEQ!RP8AMn7aF z{E|}Yc|@lp^qg#LzqhQ+#_-F|_wTsfaF7-6Q&H`z-YOVg+mqF2pd!^YHdZubMx&kA zRG5%JN@5ZiSi$Z)Zm|Rq@i{V+iFJZ$8Co@Ehmmh@m(wejJNYd!CV7XvYB#U?CR+LR zP4KSnuNOybR2oib1h|HtEFXOSy>RyFlNK+-sDrp)rK2MfN;T#WH_f4;iFs~sUbGIX zGOsu`4y5Dk;2@a_6dn`~f)h%~mX;=JYB+@?8C*Ah#7*i%M@^GI>i&w-b4S^%;rplz zGr=MqZi#$w-GED*ofzavf!*!PCA}cY>xHfCemEzm1)VchT%T752?bbd}8{96{{ z3Q#i(aIg(8n&SpL5Wp56|w YPNDyK0;JYX2AlwBs_ClMB5Y#*9~f1<0RR91 literal 0 HcmV?d00001 diff --git a/src/dist/img/icon-512.png b/src/dist/img/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..7f11c06afae93888b7e4924b853b950051e7d04a GIT binary patch literal 26495 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 EUkppeHvj+t literal 0 HcmV?d00001 diff --git a/src/dist/js/qrcode-style-tools.js b/src/dist/js/qrcode-style-tools.js new file mode 100644 index 0000000..a488750 --- /dev/null +++ b/src/dist/js/qrcode-style-tools.js @@ -0,0 +1,169 @@ +// Preset system + random style button for the qr code generation forms (Fase 3, priority 2). +(function () { + function csrfToken() { + var meta = document.querySelector('meta[name="csrf-token"]'); + return meta ? meta.getAttribute('content') : ''; + } + + function setColor(id, hex) { + var input = document.getElementById(id); + if (!input) { + return; + } + input.value = hex; + input.dispatchEvent(new Event('change')); + try { + // Sync the bootstrap-colorpicker widget/swatch if it was initialized on this input. + if (window.jQuery) { + jQuery(input).colorpicker('setValue', hex); + } + } catch (e) { + // Colorpicker not initialized on this page - the raw value above is still correct. + } + } + + function randomHexColor() { + var value = Math.floor(Math.random() * 0xFFFFFF).toString(16); + return '#' + ('000000' + value).slice(-6); + } + + function loadPresets() { + var select = document.getElementById('preset_select'); + if (!select) { + return; + } + + fetch('presets.php?action=list') + .then(function (response) { return response.json(); }) + .then(function (json) { + if (json.status !== 200) { + return; + } + json.data.forEach(function (preset) { + var option = document.createElement('option'); + option.value = preset.id; + option.textContent = preset.name; + option.dataset.foreground = preset.foreground; + option.dataset.background = preset.background; + option.dataset.level = preset.level; + option.dataset.size = preset.size; + select.appendChild(option); + }); + }) + .catch(function () { /* presets are a nice-to-have, fail silently */ }); + } + + document.addEventListener('DOMContentLoaded', function () { + loadPresets(); + + var randomBtn = document.getElementById('random_style_btn'); + if (randomBtn) { + randomBtn.addEventListener('click', function () { + setColor('foreground', randomHexColor()); + setColor('background', randomHexColor()); + }); + } + + var presetSelect = document.getElementById('preset_select'); + if (presetSelect) { + presetSelect.addEventListener('change', function () { + var option = presetSelect.options[presetSelect.selectedIndex]; + if (!option.value) { + return; + } + + setColor('foreground', option.dataset.foreground); + setColor('background', option.dataset.background); + + var levelSelect = document.querySelector('select[name="level"]'); + if (levelSelect) { + levelSelect.value = option.dataset.level; + } + + var sizeSelect = document.getElementById('size'); + if (sizeSelect) { + sizeSelect.value = option.dataset.size; + } + }); + } + + var saveBtn = document.getElementById('preset_save_btn'); + if (saveBtn) { + saveBtn.addEventListener('click', function () { + var nameInput = document.getElementById('preset_name'); + var name = nameInput.value.trim(); + + if (!name) { + alert('Enter a name for this preset first.'); + return; + } + + var body = new URLSearchParams(); + body.set('action', 'save'); + body.set('name', name); + body.set('foreground', document.getElementById('foreground').value); + body.set('background', document.getElementById('background').value); + body.set('level', document.querySelector('select[name="level"]').value); + body.set('size', document.getElementById('size').value); + + fetch('presets.php', { + method: 'POST', + headers: { 'X-CSRF-Token': csrfToken() }, + body: body + }) + .then(function (response) { return response.json(); }) + .then(function (json) { + if (json.status !== 200) { + alert('Could not save preset: ' + json.data); + return; + } + + var option = document.createElement('option'); + option.value = json.data.id; + option.textContent = json.data.name; + option.dataset.foreground = document.getElementById('foreground').value; + option.dataset.background = document.getElementById('background').value; + option.dataset.level = document.querySelector('select[name="level"]').value; + option.dataset.size = document.getElementById('size').value; + presetSelect.appendChild(option); + presetSelect.value = option.value; + nameInput.value = ''; + }) + .catch(function () { alert('Could not save preset (network error).'); }); + }); + } + + var deleteBtn = document.getElementById('preset_delete_btn'); + if (deleteBtn) { + deleteBtn.addEventListener('click', function () { + var option = presetSelect.options[presetSelect.selectedIndex]; + if (!option.value) { + return; + } + + if (!confirm('Delete preset "' + option.textContent + '"?')) { + return; + } + + var body = new URLSearchParams(); + body.set('action', 'delete'); + body.set('id', option.value); + + fetch('presets.php', { + method: 'POST', + headers: { 'X-CSRF-Token': csrfToken() }, + body: body + }) + .then(function (response) { return response.json(); }) + .then(function (json) { + if (json.status === 200) { + option.remove(); + } else { + alert('Could not delete preset: ' + json.data); + } + }) + .catch(function () { alert('Could not delete preset (network error).'); }); + }); + } + }); +})(); diff --git a/src/forms/form_dynamic_add.php b/src/forms/form_dynamic_add.php index 6a40776..b6e424e 100644 --- a/src/forms/form_dynamic_add.php +++ b/src/forms/form_dynamic_add.php @@ -35,7 +35,7 @@
- @@ -51,13 +51,47 @@
+
+
+
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+
+
+
+ + + - +
@@ -94,6 +128,14 @@
+ +
+
+ + + Optional label rendered below the code. Only applies to PNG/JPEG/GIF, not SVG/EPS. +
+
diff --git a/src/forms/qrcode_options.php b/src/forms/qrcode_options.php index 9f8da2d..a9fbbd2 100644 --- a/src/forms/qrcode_options.php +++ b/src/forms/qrcode_options.php @@ -34,7 +34,7 @@
- @@ -54,14 +54,48 @@ if (QRCODE_GENERATOR === "internal-chillerlan.qrcode") {
- + +
+
+
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+
+
+
+ + + - +
diff --git a/src/includes/footer.php b/src/includes/footer.php index 8dbb10f..d008efa 100644 --- a/src/includes/footer.php +++ b/src/includes/footer.php @@ -28,4 +28,10 @@ - \ No newline at end of file + + + \ No newline at end of file diff --git a/src/includes/head.php b/src/includes/head.php index 4948d85..054fbd9 100644 --- a/src/includes/head.php +++ b/src/includes/head.php @@ -2,6 +2,9 @@ + + + diff --git a/src/includes/sidebar.php b/src/includes/sidebar.php index 2eec17d..4b8775f 100644 --- a/src/includes/sidebar.php +++ b/src/includes/sidebar.php @@ -32,6 +32,14 @@ Dashboard

+ +