Bugzilla – Attachment 131999 Details for
Bug 6815
Capture member photo via webcam
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 6815: Capture member photo via webcam
Bug-6815-Capture-member-photo-via-webcam.patch (text/plain), 32.79 KB, created by
Jonathan Druart
on 2022-03-22 10:13:43 UTC
(
hide
)
Description:
Bug 6815: Capture member photo via webcam
Filename:
MIME Type:
Creator:
Jonathan Druart
Created:
2022-03-22 10:13:43 UTC
Size:
32.79 KB
patch
obsolete
>From 2484c1fcc771707d03950b7dd7393511a47f5872 Mon Sep 17 00:00:00 2001 >From: Mark Tompsett <mtompset@hotmail.com> >Date: Wed, 15 Jul 2020 04:55:06 +0000 >Subject: [PATCH] Bug 6815: Capture member photo via webcam > >This patch builds on a patch by Mark Tompsett, adding the option to take >a patron's picture using the computer's webcam. The photo can then be >saved to the patron's account. > >To test, apply the patch and rebuild the staff interface CSS >(https://wiki.koha-community.org/wiki/Working_with_SCSS_in_the_OPAC_and_staff_client). > >- Go to Administration -> System preferences and enable the > 'patronimages' preference. >- View a patron record. In the sidebar, hover your mouse over the blank > patron image. Click the "Edit" button which appears. >- A modal window should appear with two sections, "Upload patron photo" > and "Take patron photo." > - If your computer has a webcam, your browser should ask permission to > access it. Grant access. > - You should see the view of your webcam shown under the "Take photo" > button. > - Click the "Take photo" button. The captured photo should be shown in > place of the live video from the webcam. > - You should now see three buttons: "Retake photo," "Download photo," > and "Upload photo." > - Clicking "Retake photo" should hide those buttons and return you > to a live video view. > - Clicking "Download" should make your browser download the image. > - Clicking "Upload" should cause the page to redirect back to the > patron detail page where you should see the new patron image > displayed in the sidebar. > - Trigger the modal again and click the "cancel" button. The > modal should disappear and camera access should stop. >- If your computer has no webcam the modal should appear correctly but > there should be a banner at the bottom indicating that a camera is not > available. >- Try the test again but this time deny your browser access to the > webcam. You may need to reset the camera permissions in your browser's > settings. When the modal appears you should see a message saying > access to the camera is denied. >- The patron image edit modal should be available on all pages which > show the patron image in the sidebar: Check out, Batch check out, > Details, Accounting, Routing lists, Circulation history, Holds > history, Modification log, Notices, Statistics, Files, Purchase > suggestions, Discharges, Housebound, and ILL requests history. >- Test adding an image to a patron record using the "Upload photo" > option. It should still work correctly. >- If the patron has an image attached, the "Upload photo" section should > have a "Delete" button. Test that it works correctly. > >Signed-off-by: Nicolas Legrand <nicolas.legrand@bulac.fr> > >Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org> >--- > .../prog/css/src/staff-global.scss | 20 ++ > .../prog/en/includes/circ-menu.inc | 6 +- > .../prog/en/includes/str/members-menu.inc | 6 +- > .../prog/en/modules/members/housebound.tt | 1 - > .../modules/members/moremember-patronimage.tt | 83 ++++++++ > .../prog/en/modules/members/moremember.tt | 52 ----- > .../intranet-tmpl/prog/js/members-menu.js | 39 +++- > .../intranet-tmpl/prog/js/patron-webcam.js | 184 ++++++++++++++++++ > members/moremember-patronimage.pl | 59 ++++++ > tools/picture-upload.pl | 38 +++- > 10 files changed, 412 insertions(+), 76 deletions(-) > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember-patronimage.tt > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/patron-webcam.js > create mode 100755 members/moremember-patronimage.pl > >diff --git a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss >index fbf5200bfeb..cd1c0ba2a43 100644 >--- a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss >+++ b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss >@@ -4618,6 +4618,26 @@ div .suggestion_note { > } > } > >+#camera, #output { >+ border: 8px solid #EDF4F6; >+ padding: 1em; >+} >+ >+#photo { >+ display: block; >+ margin: auto; >+} >+ >+#camera-error { >+ display: none; >+ flex-direction: row; >+ flex-wrap: nowrap; >+ >+ div { >+ padding: 0 .5em; >+ } >+} >+ > @media (min-width: 200px) { > > } >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/circ-menu.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/circ-menu.inc >index 9b4940b2f3e..5bc5fb10827 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/circ-menu.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/circ-menu.inc >@@ -38,12 +38,12 @@ > [% IF ( patron.image ) %] > <img src="/cgi-bin/koha/members/patronimage.pl?borrowernumber=[% patron.borrowernumber | uri %]" class="patronimage" alt="[% patron.firstname | html %] [% patron.surname | html %] ([% patron.cardnumber | html %])" /> > <div class="patronimage-controls"> >- <div class="patronimage-control"><a data-borrowernumber="[% patron.borrowernumber | uri %]" class="btn btn-default edit-patronimage" title="Edit patron image" href="#"><i class="fa fa-pencil"></i> Edit</a></div> >+ <div class="patronimage-control"><a data-borrowernumber="[% patron.borrowernumber | uri %]" data-cardnumber="[% patron.cardnumber | html %]" class="btn btn-default edit-patronimage" title="Patron photo" href="#"><i class="fa fa-pencil"></i> Edit</a></div> > </div> > [% ELSE %] > <div class="patronimage empty"></div> > <div class="patronimage-controls"> >- <div class="patronimage-control"><a data-borrowernumber="[% patron.borrowernumber | uri %]" class="btn btn-default edit-patronimage" title="Add patron image" href="#"><i class="fa fa-plus"></i> Add</a></div> >+ <div class="patronimage-control"><a data-borrowernumber="[% patron.borrowernumber | uri %]" data-cardnumber="[% patron.cardnumber | html %]" class="btn btn-default edit-patronimage" title="Patron photo" href="#"><i class="fa fa-plus"></i> Add</a></div> > </div> > [% END %] > </div> >@@ -186,7 +186,7 @@ > <div class="modal-content"> > <div class="modal-header"> > <button type="button" class="closebtn" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> >- <h4 class="modal-title" id="patronImageEditLabel">Modal title</h4> >+ <h4 class="modal-title" id="patronImageEditLabel">Patron photo</h4> > </div> > <div class="modal-body"> > <img src="[% interface | html %]/[% theme | html %]/img/spinner-small.gif" alt="" /> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/str/members-menu.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/str/members-menu.inc >index 777f814f114..530e9bfb89a 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/str/members-menu.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/str/members-menu.inc >@@ -1,7 +1,11 @@ >+[% USE raw %] > [% USE scalar %] > [% USE Koha %] > [% USE Categories %] > <!-- str/members-menu.inc --> >+[% IF ( Koha.Preference('patronimages') ) %] >+ [% Asset.js("js/patron-webcam.js") | $raw %] >+[% END %] > <script> > var advsearch = "[% advsearch | html %]"; > var destination = "[% destination | html %]"; >@@ -26,7 +30,7 @@ > $("body").on("click", "#delpicture", function(){ > return confirm(_("Are you sure you want to delete this patron image? This cannot be undone.")); > }); >- $('#manage-patron-image').find("input[value*=Upload]").click(function(){ >+ $('#upload-patron-image').find("input[value*=Upload]").click(function(){ > if($("#uploadfile").val() == ""){ > alert(_("Please choose a file to upload")); > return false; >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/housebound.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/housebound.tt >index 330a55c2b27..ae21d734476 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/housebound.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/housebound.tt >@@ -436,7 +436,6 @@ > > [% MACRO jsinclude BLOCK %] > [% INCLUDE 'calendar.inc' %] >- [% Asset.js("js/members-menu.js") | $raw %] > <script> > $(document).ready(function() { > $("a.delete").click(function(){ >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember-patronimage.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember-patronimage.tt >new file mode 100644 >index 00000000000..bbacd022d3c >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember-patronimage.tt >@@ -0,0 +1,83 @@ >+[% USE Koha %] >+ >+[% IF ( Koha.Preference('patronimages') ) %] >+ [% IF ( CAN_user_tools_batch_upload_patron_images ) %] >+ <div id="upload-patron-image" class="patroninfo-section"> >+ <h5>Upload patron photo</h5> >+ <form method="post" id="picture-upload" action="/cgi-bin/koha/tools/picture-upload.pl" >+ enctype="multipart/form-data"> >+ [% IF ( patron.image ) %] >+ <div class="hint"> >+ To update the image for [% patron.title | html %] [% patron.firstname | html %] [% patron.surname | html %], select a new image file and click 'Upload.' <br />Click the 'Delete' button to >+ remove the current image. >+ </div> >+ [% ELSE %] >+ <div class="hint"> >+ [% patron.title | html %] [% patron.firstname | html %] [% patron.surname | html %] does not currently have an image available. To import an image for [% patron.title | html %] [% patron.firstname | html %] [% patron.surname | html %], enter the name of an image file to upload. >+ </div> >+ [% END %] >+ <p>Only PNG, GIF, JPEG, XPM formats are supported.</p> >+ <label for="uploadfile">Select the file to upload: </label> >+ <input type="file" id="uploadfile" name="uploadfile" required="required" /> >+ <div class="action"> >+ <input type="hidden" id="csrf_token" name="csrf_token" value="[% csrf_token | html %]" /> >+ <input type="hidden" id="image" name="filetype" value="image" /> >+ <input type="hidden" id="cardnumber" name="cardnumber" value="[% patron.cardnumber | html %]" /> >+ <input type="hidden" id="borrowernumber" name="borrowernumber" value="[% patron.borrowernumber | html %]" /> >+ <button type="submit" class="btn btn-default btn-xs"><i class="fa fa-upload"></i> Upload photo</button> >+ <input name="op" type="hidden" value="Upload" /> >+ [% IF ( patron.image ) %] >+ <a id="delpicture" >+ href="/cgi-bin/koha/tools/picture-upload.pl?op=Delete&borrowernumber=[% patron.borrowernumber | html %]&csrf_token=[% csrf_token | html %]" >+ class="btn btn-default btn-xs delete"><i class="fa fa-trash"></i> Delete</a> >+ [% END %] >+ </div> >+ </form> >+ </div> >+ <div id="capture-patron-image" class="patroninfo-section"> >+ <h5>Take patron photo</h5> >+ <form method="post" id="camera-upload" action="/cgi-bin/koha/tools/picture-upload.pl"> >+ <div class="btn-toolbar"> >+ <div class="btn-group"> >+ <button class="btn btn-default" id="takebutton"><i class="fa fa-camera"></i> Take photo</button> >+ </div> >+ <div class="btn-group"> >+ <button class="btn btn-default" id="retakebutton" style="display:none;"><i class="fa fa-refresh"></i> Retake photo</button> >+ </div> >+ <div class="btn-group"> >+ <a id="downloadbutton" href="#" class="btn btn-default" style="display:none;"><i class="fa fa-download"></i> Download photo</a> >+ </div> >+ <div class="btn-group"> >+ <button id="savebutton" type="submit" class="btn btn-default" style="display:none;"><i class="fa fa-hdd-o"></i> Upload</button> >+ </div> >+ </div> >+ <div id="camera"> >+ <video id="viewfinder">Video stream not available.</video> >+ </div> >+ <canvas id="canvas" style="display:none"></canvas> >+ <div id="output" style="display:none"> >+ <img style="width:100%;height:auto;" id="photo" alt="The screen capture will appear in this box."> >+ </div> >+ <input type="hidden" name="uploadfilename" value="patron-photo.jpg" /> >+ <textarea id="uploadfiletext" name="uploadfiletext" style="display:none;"></textarea> >+ <input type="hidden" name="csrf_token" value="[% csrf_token | html %]" /> >+ <input type="hidden" name="filetype" value="image" /> >+ <input type="hidden" name="cardnumber" value="[% patron.cardnumber | html %]" /> >+ <input type="hidden" name="borrowernumber" value="[% patron.borrowernumber | html %]" /> >+ <input name="op" type="hidden" value="Upload" /> >+ </form> >+ </div> >+ <div class="dialog message" style="display:none" id="camera-error"> >+ <div> >+ <span class="fa-stack fa-lg"> >+ <i class="fa fa-camera fa-stack-1x"></i> >+ <i class="fa fa-ban fa-stack-2x text-danger"></i> >+ </span> >+ </div> >+ <div> >+ <strong>Cannot take patron photo.</strong> >+ <span id="camera-error-message"></span> >+ </div> >+ </div> >+ [% END %] >+[% END %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt >index 335bbc2243b..eb68600d6d7 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt >@@ -256,46 +256,6 @@ > </div> [% # /div.rows %] > </div> [% # /div#patron-information %] > >- [% IF ( patronimages ) %] >- [% IF ( CAN_user_tools_batch_upload_patron_images ) %] >- <div id="manage-patron-image" class="patroninfo-section"> >- [% IF ( patron.image ) %] >- <div class="patroninfo-heading"> >- <h3>Manage patron image</h3> >- <a class="btn btn-default btn-xs" id="show-picture-upload" href="#"><i class="fa fa-pencil"></i> Edit</a> >- </div> >- [% ELSE %] >- <div class="patroninfo-heading"> >- <h3>Upload patron image</h3> >- <a class="btn btn-default btn-xs" id="show-picture-upload" href="#"><i class="fa fa-plus"></i> Add</a> >- </div> >- [% END %] >- <form method="post" id="picture-upload" style="display:none;" action="/cgi-bin/koha/tools/picture-upload.pl" enctype="multipart/form-data"> >- [% IF ( patron.image ) %] >- <div class="hint">To update the image for [% patron.title | html %] [% patron.firstname | html %] [% patron.surname | html %], select a new image file and click 'Upload.' <br />Click the 'Delete' button to remove the current image.</div> >- [% ELSE %] >- <div class="hint">[% patron.title | html %] [% patron.firstname | html %] [% patron.surname | html %] does not currently have an image available. To import an image for [% patron.title | html %] [% patron.firstname | html %] [% patron.surname | html %], enter the name of an image file to upload.</div> >- [% END %] >- <p>Only PNG, GIF, JPEG, XPM formats are supported. Maximum image size is 2MB.</p> >- <label for="uploadfile">Select the file to upload: </label> >- <input type="file" id="uploadfile" name="uploadfile" required="required" /> >- <div class="action"> >- <input type="hidden" name="csrf_token" value="[% csrf_token | html %]" /> >- <input type="hidden" id="image" name="filetype" value="image" /> >- <input type="hidden" id="cardnumber" name="cardnumber" value="[% patron.cardnumber | html %]" /> >- <input type="hidden" name="borrowernumber" value="[% patron.borrowernumber | html %]" /> >- <button type="submit" class="btn btn-default btn-xs"><i class="fa fa-upload"></i> Upload</button> >- <input name="op" type="hidden" value="Upload" /> >- [% IF ( patron.image ) %] >- <a id="delpicture" href="/cgi-bin/koha/tools/picture-upload.pl?op=Delete&borrowernumber=[% patron.borrowernumber | html %]&csrf_token=[% csrf_token | html %]" class="btn btn-default btn-xs delete"><i class="fa fa-trash"></i> Delete</a> >- [% END %] >- <a href="#" id="cancel-picture-upload" class="cancel">Cancel</a> >- </div> >- </form> >- </div> [% # /div#manage-patron-image %] >- [% END %] >- [% END %] >- > [% IF Koha.Preference('HouseboundModule') %] > <div id="houseboundroles" class="patroninfo-section"> > [% IF ( housebound_role.housebound_chooser == 1 OR housebound_role.housebound_deliverer == 1 ) %] >@@ -943,18 +903,6 @@ > $("#view_restrictions").on("click",function(){ > $('#debarments-tab-link').click(); > }); >- >- $("#show-picture-upload").on("click", function(e){ >- e.preventDefault(); >- $(this).toggle(); >- $("#picture-upload").toggle(); >- }); >- >- $("#cancel-picture-upload").on("click", function(e){ >- e.preventDefault(); >- $("#picture-upload, #show-picture-upload").toggle(); >- }); >- > }); > function uncheck_sibling(me){ > nodename=me.getAttribute("name"); >diff --git a/koha-tmpl/intranet-tmpl/prog/js/members-menu.js b/koha-tmpl/intranet-tmpl/prog/js/members-menu.js >index def803d2748..fb198bd1d79 100644 >--- a/koha-tmpl/intranet-tmpl/prog/js/members-menu.js >+++ b/koha-tmpl/intranet-tmpl/prog/js/members-menu.js >@@ -90,23 +90,42 @@ $(document).ready(function(){ > $("#borrower_message").val( $(this).val() ); > }); > >+ $("#patronImageEdit").on("shown.bs.modal", function(){ >+ startup(); >+ }); >+ > $(".edit-patronimage").on("click", function(e){ > e.preventDefault(); > var borrowernumber = $(this).data("borrowernumber"); >- $.get("/cgi-bin/koha/members/moremember.pl", { borrowernumber : borrowernumber }, function( data ){ >- var image_form = $(data).find("#picture-upload"); >- image_form.show().find(".cancel").remove(); >- $("#patronImageEdit .modal-body").html( image_form ); >- }); >+ var cardnumber = $(this).data("cardnumber"); > var modalTitle = $(this).attr("title"); >- $("#patronImageEdit .modal-title").text(modalTitle); >- $("#patronImageEdit").modal("show"); >+ $.ajax({ >+ url: "/cgi-bin/koha/members/moremember-patronimage.pl", >+ type: "GET", >+ data: { borrowernumber: borrowernumber, cardnumber: cardnumber }, >+ success: function ( data ) { >+ $("#patronImageEdit .modal-body").html( data ); >+ $("#patronImageEdit .modal-title").text(modalTitle); >+ $("#patronImageEdit").modal("show"); >+ }, >+ error: function () { >+ location.href="/cgi-bin/koha/members/moremember-patronimage.pl?borrowernumber=" + borrowernumber; >+ } >+ }); >+ $("#patronImageEdit").on("hidden.bs.modal", function(){ >+ /* Stop using the user's camera when modal is closed */ >+ let viewfinder = document.getElementById("viewfinder"); >+ if( viewfinder.srcObject ){ >+ viewfinder.srcObject.getTracks().forEach( track => { >+ if( track.readyState == 'live' && track.kind === 'video'){ >+ track.stop(); >+ } >+ }); >+ } >+ }); > }); >- > }); > >- >- > function searchfield_date_tooltip(filter) { > var field = "#searchmember" + filter; > var type = "#searchfieldstype" + filter; >diff --git a/koha-tmpl/intranet-tmpl/prog/js/patron-webcam.js b/koha-tmpl/intranet-tmpl/prog/js/patron-webcam.js >new file mode 100644 >index 00000000000..0736bcb4350 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/patron-webcam.js >@@ -0,0 +1,184 @@ >+/* global __ */ >+/* exported startup */ >+ >+/* Adapted from Mozilla's article "Taking still photos with WebRTC" >+* https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Taking_still_photos >+*/ >+ >+var width = 480; // We will scale the photo width to this >+var height = 0; // This will be computed based on the input stream >+ >+// |streaming| indicates whether or not we're currently streaming >+// video from the camera. Obviously, we start at false. >+ >+var streaming = false; >+ >+// The various HTML elements we need to configure or control. These >+// will be set by the startup() function. >+ >+var video = null; >+var canvas = null; >+var photo = null; >+var takebutton = null; >+var retakebutton = null; >+var downloadbutton = null; >+var savebutton = null; >+var output = null; >+var camera = null; >+var uploadfiletext = null; >+ >+/** >+ * Initiate the camera and add some click handlers >+ */ >+ >+function startup() { >+ video = document.getElementById('viewfinder'); >+ canvas = document.getElementById('canvas'); >+ photo = document.getElementById('photo'); >+ takebutton = document.getElementById('takebutton'); >+ retakebutton = document.getElementById('retakebutton'); >+ downloadbutton = document.getElementById('downloadbutton'); >+ savebutton = document.getElementById('savebutton'); >+ output = document.getElementById("output"); >+ camera = document.getElementById("camera"); >+ uploadfiletext = document.getElementById("uploadfiletext"); >+ >+ navigator.mediaDevices.getUserMedia({ >+ video: true, >+ audio: false >+ }) >+ .then(function (stream) { >+ video.srcObject = stream; >+ video.play(); >+ }) >+ .catch(function (err) { >+ $("#capture-patron-image").hide(); >+ $("#camera-error").css("display", "flex"); >+ $("#camera-error-message").text( showMediaErrors( err ) ); >+ }); >+ >+ video.addEventListener('canplay', function () { >+ if (!streaming) { >+ height = video.videoHeight / (video.videoWidth / width); >+ >+ // Firefox currently has a bug where the height can't be read from >+ // the video, so we will make assumptions if this happens. >+ >+ if (isNaN(height)) { >+ height = width / (4 / 3); >+ } >+ >+ video.setAttribute('width', width); >+ video.setAttribute('height', height); >+ canvas.setAttribute('width', width); >+ canvas.setAttribute('height', height); >+ photo.setAttribute('width', width); >+ photo.setAttribute('height', height); >+ streaming = true; >+ } >+ }, false); >+ >+ takebutton.addEventListener('click', function (ev) { >+ takepicture(); >+ ev.preventDefault(); >+ }, false); >+ >+ retakebutton.addEventListener('click', function (ev) { >+ ev.preventDefault(); >+ retakephoto(); >+ }, false); >+ >+ clearphoto(); >+} >+ >+function showMediaErrors( err ){ >+ // Example error: "NotAllowedError: Permission denied" >+ var errorcode = err.toString().split(":"); >+ var output; >+ switch ( errorcode[0] ) { >+ case "NotFoundError": >+ case "DevicesNotFoundError": >+ output = __("No camera detected."); >+ break; >+ case "NotReadableError": >+ case "TrackStartError": >+ output = __("Could not access camera."); >+ break; >+ case "NotAllowedError": >+ case "PermissionDeniedError": >+ output = __("Access to camera denied."); >+ break; >+ default: >+ output = __("An unknown error occurred: ") + err; >+ break; >+ } >+ return output; >+} >+ >+/** >+ * Clear anything passed to the canvas element and the corresponding image. >+ */ >+ >+function clearphoto() { >+ var context = canvas.getContext('2d'); >+ context.fillStyle = "#AAA"; >+ context.fillRect(0, 0, canvas.width, canvas.height); >+ >+ var data = canvas.toDataURL('image/jpeg', 1.0); >+ photo.setAttribute('src', data); >+} >+ >+/** >+ * Reset the interface to hide download and save buttons. >+ * Redisplay camera "shutter" button. >+ */ >+ >+function retakephoto(){ >+ downloadbutton.href= ""; >+ downloadbutton.style.display = "none"; >+ takebutton.style.display = "inline-block"; >+ retakebutton.style.display = "none"; >+ savebutton.style.display = "none"; >+ output.style.display = "none"; >+ photo.src = ""; >+ camera.style.display = "block"; >+ uploadfiletext.value = ""; >+} >+ >+/** >+ * Capture the data from the user's camera and write it to the canvas element. >+ * The canvas data is converted to a data-url, and that URL set as the src >+ * attribute of an image. >+ * Display two controls for the captured photo: Download (to save to the >+ * user's computer) and Upload (save to the patron's record in Koha). >+ */ >+ >+function takepicture() { >+ var context = canvas.getContext('2d'); >+ var cardnumber = document.getElementById("cardnumber").value; >+ camera.style.display = "none"; >+ downloadbutton.style.display = ''; >+ output.style.display = "block"; >+ takebutton.style.display = "none"; >+ retakebutton.style.display = "inline-block"; >+ savebutton.style.display = "inline-block"; >+ if (width && height) { >+ canvas.width = width; >+ canvas.height = height; >+ context.drawImage(video, 0, 0, width, height); >+ >+ var data = canvas.toDataURL('image/jpeg', 1.0); >+ photo.setAttribute('src', data); >+ if( cardnumber !== '' ){ >+ // Download a file which the patrons card number as its name >+ downloadbutton.download = cardnumber + ".jpg"; >+ } else { >+ downloadbutton.download = "patron-photo.jpg"; >+ } >+ downloadbutton.href = data; >+ uploadfiletext.value = data; >+ >+ } else { >+ clearphoto(); >+ } >+} >diff --git a/members/moremember-patronimage.pl b/members/moremember-patronimage.pl >new file mode 100755 >index 00000000000..93708981f39 >--- /dev/null >+++ b/members/moremember-patronimage.pl >@@ -0,0 +1,59 @@ >+#!/usr/bin/perl >+ >+# Copyright 2020 Mark Tompsett >+# >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha is distributed in the hope that it will be useful, but >+# WITHOUT ANY WARRANTY; without even the implied warranty of >+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with Koha; if not, see <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+use CGI qw ( -utf8 ); >+use C4::Auth qw( get_template_and_user ); >+use C4::Output qw( output_and_exit_if_error output_and_exit output_html_with_http_headers ); >+use Koha::Patrons; >+use Koha::Token; >+ >+my $input = CGI->new; >+ >+my ( $template, $loggedinuser, $cookie ) = get_template_and_user( >+ { >+ template_name => 'members/moremember-patronimage.tt', >+ query => $input, >+ type => 'intranet', >+ flagsrequired => { borrowers => 'edit_borrowers' }, >+ } >+); >+ >+my $borrowernumber = $input->param('borrowernumber'); >+my $cardnumber = $input->param('cardnumber'); >+my $patron = Koha::Patrons->find($borrowernumber); >+my $logged_in_user = Koha::Patrons->find($loggedinuser); >+ >+$template->param( >+ csrf_token => Koha::Token->new->generate_csrf( >+ { session_id => $input->cookie('CGISESSID'), } >+ ), >+ patron => $patron, >+ logged_in_user => $logged_in_user, >+); >+ >+output_html_with_http_headers $input, $cookie, $template->output; >+ >+__END__ >+ >+=head1 moremember-patronimage.pl >+ >+ script to provide modal window for patron images >+ >+=cut >diff --git a/tools/picture-upload.pl b/tools/picture-upload.pl >index 57c55b9a120..839976890ce 100755 >--- a/tools/picture-upload.pl >+++ b/tools/picture-upload.pl >@@ -24,6 +24,7 @@ use Modern::Perl; > use File::Temp; > use CGI qw ( -utf8 ); > use GD; >+use MIME::Base64; > use C4::Context; > use C4::Auth qw( get_template_and_user ); > use C4::Output qw( output_and_exit output_html_with_http_headers ); >@@ -51,7 +52,8 @@ my ($template, $loggedinuser, $cookie) > > our $filetype = $input->param('filetype') || ''; > my $cardnumber = $input->param('cardnumber'); >-our $uploadfilename = $input->param('uploadfile') || ''; >+our $uploadfilename = $input->param('uploadfile') || $input->param('uploadfilename') || ''; >+my $uploadfiletext = $input->param('uploadfiletext') || ''; > my $uploadfile = $input->upload('uploadfile'); > my $borrowernumber = $input->param('borrowernumber'); > my $op = $input->param('op') || ''; >@@ -83,7 +85,7 @@ our @counts = (); > our %errors = (); > > # Case is important in these operational values as the template must use case to be visually pleasing! >-if ( ( $op eq 'Upload' ) && $uploadfile ) { >+if ( ( $op eq 'Upload' ) && ($uploadfile || $uploadfiletext) ) { > > output_and_exit( $input, $cookie, $template, 'wrong_csrf_token' ) > unless Koha::Token->new->check_csrf({ >@@ -100,18 +102,35 @@ if ( ( $op eq 'Upload' ) && $uploadfile ) { > File::Temp::tempfile( SUFFIX => $filesuffix, UNLINK => 1 ); > my ( @directories, $results ); > >- $errors{'NOTZIP'} = 1 >- if ( $uploadfilename !~ /\.zip$/i && $filetype =~ m/zip/i ); > $errors{'NOWRITETEMP'} = 1 unless ( -w $dirname ); >- $errors{'EMPTYUPLOAD'} = 1 unless ( length($uploadfile) > 0 ); >+ if ( length($uploadfiletext) == 0 ) { >+ $errors{'NOTZIP'} = 1 >+ if ( $uploadfilename !~ /\.zip$/i && $filetype =~ m/zip/i ); >+ $errors{'EMPTYUPLOAD'} = 1 unless ( length($uploadfile) > 0 ); >+ } > > if (%errors) { > $template->param( ERRORS => [ \%errors ] ); > output_html_with_http_headers $input, $cookie, $template->output; > exit; > } >- while (<$uploadfile>) { >- print $tfh $_; >+ >+ if ( length($uploadfiletext) == 0 ) { >+ while (<$uploadfile>) { >+ print $tfh $_; >+ } >+ } else { >+ # data type controlled in toDataURL() in template >+ if ( $uploadfiletext =~ /data:image\/jpeg;base64,(.*)/ ) { >+ my $encoded_picture = $1; >+ my $decoded_picture = decode_base64($encoded_picture); >+ print $tfh $decoded_picture; >+ } else { >+ $errors{'BADPICTUREDATA'} = 1; >+ $template->param( ERRORS => [ \%errors ] ); >+ output_html_with_http_headers $input, $cookie, $template->output; >+ exit; >+ } > } > close $tfh; > if ( $filetype eq 'zip' ) { >@@ -221,7 +240,9 @@ sub handle_dir { > return \%direrrors; > } > >- while ( my $line = <$fh> ) { >+ my @lines = <$fh>; >+ close $fh; >+ foreach my $line (@lines) { > $logger->debug("Reading contents of $file"); > chomp $line; > $logger->debug("Examining line: $line"); >@@ -241,7 +262,6 @@ sub handle_dir { > $source = "$dir/$filename"; > %counts = handle_file( $cardnumber, $source, $template, %counts ); > } >- close $fh; > closedir DIR; > } > else { >-- >2.25.1
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 6815
:
22623
|
29613
|
38518
|
91302
|
91303
|
91335
|
99015
|
99016
|
99757
|
106774
|
106775
|
106776
|
106777
|
106782
|
106783
|
106787
|
106788
|
106789
|
106790
|
106791
|
106792
|
106793
|
106794
|
106795
|
106796
|
106797
|
106798
|
106799
|
106800
|
106801
|
106802
|
106803
|
106804
|
106938
|
106939
|
106940
|
131174
|
131554
|
131985
|
131987
|
131988
| 131999 |
132000
|
132001