Bugzilla – Attachment 106938 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: Take a patron's picture
Bug-6815-Take-a-patrons-picture.patch (text/plain), 23.12 KB, created by
Mark Tompsett
on 2020-07-15 20:02:49 UTC
(
hide
)
Description:
Bug 6815: Take a patron's picture
Filename:
MIME Type:
Creator:
Mark Tompsett
Created:
2020-07-15 20:02:49 UTC
Size:
23.12 KB
patch
obsolete
>From 2989e2483ebff5aa315ae0510227676a0f08556b 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: Take a patron's picture > >- Remove Manage Image Patron > (everything done via picture clicks) >- Break out the modal to its own pl/tt pair >- tweak member-menu.js >- tweak tools/picture-upload.pl > >Clicking Add/Edit button on the patron >picture should give the user the ability >to either upload a file, or take a patron's >picture. > >The camera can be turned on/off. >Turn it on, take until the picture on the right >looks good. >Click keep. >Profile picture should be refreshed >--- > .../intranet-tmpl/prog/en/includes/circ-menu.inc | 2 +- > .../en/modules/members/moremember-patronimage.tt | 214 +++++++++++++++++++++ > .../prog/en/modules/members/moremember.tt | 40 ---- > koha-tmpl/intranet-tmpl/prog/js/members-menu.js | 6 +- > members/moremember-patronimage.pl | 59 ++++++ > tools/picture-upload.pl | 58 ++++-- > 6 files changed, 316 insertions(+), 63 deletions(-) > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember-patronimage.tt > create mode 100755 members/moremember-patronimage.pl > >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 a12be5e07f..9570b84147 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/circ-menu.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/circ-menu.inc >@@ -160,7 +160,7 @@ > <img src="[% interface | html %]/[% theme | html %]/img/spinner-small.gif" alt="" /> > </div> > <div class="modal-footer"> >- <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> >+ <button type="button" class="btn btn-default" onclick="turn_off();" data-dismiss="modal">Cancel</button> > </div> > </div> > </div> >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 0000000000..3db86fab46 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember-patronimage.tt >@@ -0,0 +1,214 @@ >+[% USE Koha %] >+ >+[% IF ( Koha.Preference('patronimages') ) %] >+ [% IF ( CAN_user_tools_batch_upload_patron_images ) %] >+ <div id="manage-patron-image" class="patroninfo-section" style="float:up"> >+ <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</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"> >+ <form> >+ <!-- 240 is the fixed width. See Javascript rheight calculations. --> >+ <div id="liveFeed" style="float:left" width="260px"> >+ <video width="240px" height="180px" id="videoFeed" style="float:left; width:240px !important; height:180px !important"></video> >+ </div> >+ <div id="captured-image" style="float:right" width="260px"> >+ <canvas width="240px" id="canvas" style="display:none"></canvas> >+ <img width="240px" height="180px" id="picture" src=""> >+ </div> >+ <hr> >+ <div id="capture-controls" style="display:block"> >+ <button id="webcam_off" type="button" onclick="turn_off();">Off</button> >+ <button id="webcam_on" type="button" onclick="turn_on();">On</button> >+ <button id="webcam_capture" type="button" onclick="take_picture();"> >+ Take >+ </button> >+ <button id="webcam_save" type="button" onclick="save_picture();"> >+ Keep >+ </button> >+ </div> >+ </form> >+ </div> >+ [% END %] >+[% END %] >+ >+ <script> >+ function turn_on() { >+ // Detect camera >+ navigator.getMedia = ( navigator.getUserMedia || >+ navigator.webkitGetUserMedia || >+ navigator.mozGetUserMedia || >+ navigator.msGetUserMedia ); >+ if (navigator.getMedia) { >+ // keep it on, and set object sizes >+ navigator.getMedia( >+ {video: true}, >+ function(stream) { >+ var takebutton = document.getElementById('webcam_capture'); >+ var savebutton = document.getElementById('webcam_save'); >+ var video = document.getElementById('videoFeed'); >+ var canvas = document.getElementById('canvas'); >+ var picture = document.getElementById('picture'); >+ video.srcObject = stream; >+ video.autoplay = true; >+ // use 200ms timeout to set because mobile switches height/width >+ // until used and the videoWidth and videoHeight are checked >+ setTimeout(function(){ >+ width = video.videoWidth; >+ height = video.videoHeight; >+ var ratio = height.toFixed(6)/width.toFixed(6); >+ var rheight = 240 * ratio; >+ video.height = rheight; >+ canvas.height = rheight; >+ picture.height = rheight; >+ takebutton.disabled = false; >+ }, 200); >+ }, >+ function() { >+ alert('Webcam not detected or permission denied!'); >+ } >+ ); >+ } else { >+ alert('Webcam not detected or permission denied!'); >+ } >+ } >+ >+ function turn_off() { >+ var takebutton = document.getElementById('webcam_capture'); >+ var savebutton = document.getElementById('webcam_save'); >+ var picture = document.getElementById('picture'); >+ var video = document.getElementById('videoFeed'); >+ var stream = video.srcObject; >+ if (stream) { >+ stream.getTracks().forEach(function(track) { >+ track.stop(); >+ }); >+ } >+ video.srcObject = null; >+ video.autoplay = false; >+ takebutton.disabled = true; >+ } >+ >+ function take_picture() { >+ var savebutton = document.getElementById('webcam_save'); >+ var video = document.getElementById('videoFeed'); >+ var stream = video.srcObject; >+ var canvas = document.getElementById('canvas'); >+ var context = canvas.getContext("2d"); >+ var picture = document.getElementById('picture'); >+ context.drawImage( video, 0, 0, picture.width, picture.height); >+ picture.src = canvas.toDataURL('image/jpeg'); >+ savebutton.disabled = false; >+ } >+ >+ function save_picture() { >+ // gather form data for AJAX POST >+ var data = new FormData(); >+ data.append("uploadfiletext", picture.src ); >+ data.append("csrf_token", $("#csrf_token").val() ); >+ data.append("filetype", "image" ); >+ data.append("uploadfilename", "patron-photo.jpg" ); >+ data.append("cardnumber", $("#cardnumber").val() ); >+ data.append("borrowernumber", $("#borrowernumber").val() ); >+ data.append("op", "Upload" ); >+ $.ajax({ >+ type: "POST", >+ url: "/cgi-bin/koha/tools/picture-upload.pl", >+ data: data, >+ cache: false, >+ contentType: false, >+ processData: false >+ }).done(function() { >+ // turn off video nicely if it is still on. >+ var video = document.getElementById('videoFeed'); >+ var stream = video.srcObject; >+ if (stream) { >+ stream.getTracks().forEach(function(track) { >+ if (track.readyState == 'live' && track.kind === 'video') { >+ track.stop(); >+ } >+ }); >+ } >+ location.reload(); >+ }); >+ } >+ </script> >+ <script> >+ //if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { >+ // navigator.mediaDevices.getUserMedia(mediaConfig).then( >+ // function(stream) { >+ // video.srcObject = stream; >+ // video.play(); >+ // } >+ // ); >+ //} >+ >+ var takepicture = document.getElementById('webcam_capture'); >+ var savepicture = document.getElementById('webcam_save'); >+ savepicture.disabled = true; >+ >+ // Detect camera >+ navigator.getMedia = ( navigator.getUserMedia || >+ navigator.webkitGetUserMedia || >+ navigator.mozGetUserMedia || >+ navigator.msGetUserMedia ); >+ // If no camera... >+ if (! navigator.getMedia) { >+ // Disable the buttons. >+ var webcamoff = document.getElementById('webcam_off'); >+ var webcamon = document.getElementById('webcam_on'); >+ webcamon.disabled = true; >+ webcamoff.disabled = true; >+ takepicture.disabled = true; >+ document.getElementById('capture-patron-image').style.visibility = "hidden"; >+ document.getElementById('capture-patron-image').remove(); >+ } else { >+ // If camera... >+ // keep it on and set objects height and width >+ navigator.getMedia( >+ {video: true}, >+ function(stream) { >+ var video = document.getElementById('videoFeed'); >+ var canvas = document.getElementById('canvas'); >+ var picture = document.getElementById('picture'); >+ video.srcObject = stream; >+ video.autoplay = true; >+ // use 200ms timeout to set because mobile switches height/width >+ // until used and the videoWidth and videoHeight are checked >+ setTimeout(function(){ >+ var width = video.videoWidth; >+ var height = video.videoHeight; >+ var ratio = height.toFixed(6)/width.toFixed(6); >+ var rheight = 240 * ratio; >+ video.height = rheight; >+ canvas.height = rheight; >+ picture.height = rheight; >+ takepicture.disabled = false; >+ }, 200); >+ }, >+ function() { >+ alert('Webcam not detected or permission denied!'); >+ } >+ ); >+ } >+ </script> >+</html> >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 b4351f4afa..43bde949dc 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt >@@ -296,46 +296,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.</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 ) %] >diff --git a/koha-tmpl/intranet-tmpl/prog/js/members-menu.js b/koha-tmpl/intranet-tmpl/prog/js/members-menu.js >index f5458cfea4..5bd86490ba 100644 >--- a/koha-tmpl/intranet-tmpl/prog/js/members-menu.js >+++ b/koha-tmpl/intranet-tmpl/prog/js/members-menu.js >@@ -86,10 +86,8 @@ $(document).ready(function(){ > $(".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 ); >+ $.get("/cgi-bin/koha/members/moremember-patronimage.pl", { borrowernumber : borrowernumber }, function( data ){ >+ $("#patronImageEdit .modal-body").html( data ); > }); > var modalTitle = $(this).attr("title"); > $("#patronImageEdit .modal-title").text(modalTitle); >diff --git a/members/moremember-patronimage.pl b/members/moremember-patronimage.pl >new file mode 100755 >index 0000000000..5c56fb1012 >--- /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; >+use C4::Output; >+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', >+ authnotrequired => 0, >+ flagsrequired => { borrowers => 'edit_borrowers' }, >+ } >+); >+ >+my $borrowernumber = $input->param('borrowernumber'); >+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 c114a86a32..a4f0deac84 100755 >--- a/tools/picture-upload.pl >+++ b/tools/picture-upload.pl >@@ -25,6 +25,7 @@ use File::Temp; > use File::Copy; > use CGI qw ( -utf8 ); > use GD; >+use MIME::Base64; > use C4::Context; > use C4::Auth; > use C4::Output; >@@ -35,7 +36,7 @@ use Koha::Patrons; > use Koha::Patron::Images; > use Koha::Token; > >-my $input = new CGI; >+my $input = CGI->new; > > unless (C4::Context->preference('patronimages')) { > # redirect to intranet home if patronimages is not enabled >@@ -43,18 +44,21 @@ unless (C4::Context->preference('patronimages')) { > exit; > } > >-my ($template, $loggedinuser, $cookie) >- = get_template_and_user({template_name => "tools/picture-upload.tt", >- query => $input, >- type => "intranet", >- authnotrequired => 0, >- flagsrequired => { tools => 'batch_upload_patron_images'}, >- debug => 0, >- }); >+my ($template, $loggedinuser, $cookie) = get_template_and_user( >+ { >+ template_name => "tools/picture-upload.tt", >+ query => $input, >+ type => "intranet", >+ authnotrequired => 0, >+ flagsrequired => { tools => 'batch_upload_patron_images'}, >+ debug => 0, >+ } >+); > > 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') || ''; >@@ -87,7 +91,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({ >@@ -106,18 +110,35 @@ if ( ( $op eq 'Upload' ) && $uploadfile ) { > $debug and warn "tempfile = $tempfile"; > 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' ) { >@@ -228,7 +249,9 @@ sub handle_dir { > return \%direrrors; > } > >- while ( my $line = <$fh> ) { >+ my @lines = <$fh>; >+ close $fh; >+ foreach my $line (@lines) { > $debug and warn "Reading contents of $file"; > chomp $line; > $debug and warn "Examining line: $line"; >@@ -248,7 +271,6 @@ sub handle_dir { > $source = "$dir/$filename"; > %counts = handle_file( $cardnumber, $source, $template, %counts ); > } >- close $fh; > closedir DIR; > } > else { >-- >2.11.0
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