View | Details | Raw Unified | Return to bug 6815
Collapse All | Expand All

(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/circ-menu.inc (-1 / +1 lines)
Lines 160-166 Link Here
160
                <img src="[% interface | html %]/[% theme | html %]/img/spinner-small.gif" alt="" />
160
                <img src="[% interface | html %]/[% theme | html %]/img/spinner-small.gif" alt="" />
161
            </div>
161
            </div>
162
            <div class="modal-footer">
162
            <div class="modal-footer">
163
                <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
163
                <button type="button" class="btn btn-default" onclick="turn_off();" data-dismiss="modal">Cancel</button>
164
            </div>
164
            </div>
165
        </div>
165
        </div>
166
    </div>
166
    </div>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember-patronimage.tt (+214 lines)
Line 0 Link Here
1
[% USE Koha %]
2
3
[% IF ( Koha.Preference('patronimages') ) %]
4
  [% IF ( CAN_user_tools_batch_upload_patron_images ) %]
5
  <div id="manage-patron-image" class="patroninfo-section" style="float:up">
6
    <form method="post" id="picture-upload" action="/cgi-bin/koha/tools/picture-upload.pl" enctype="multipart/form-data">
7
      [% IF ( patron.image ) %]
8
        <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>
9
      [% ELSE %]
10
        <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>
11
      [% END %]
12
      <p>Only PNG, GIF, JPEG, XPM formats are supported.</p>
13
      <label for="uploadfile">Select the file to upload: </label>
14
      <input type="file" id="uploadfile" name="uploadfile" required="required" />
15
      <div class="action">
16
        <input type="hidden" id="csrf_token" name="csrf_token" value="[% csrf_token | html %]" />
17
        <input type="hidden" id="image" name="filetype" value="image" />
18
        <input type="hidden" id="cardnumber" name="cardnumber" value="[% patron.cardnumber | html %]" />
19
        <input type="hidden" id="borrowernumber" name="borrowernumber" value="[% patron.borrowernumber | html %]" />
20
        <button type="submit" class="btn btn-default btn-xs"><i class="fa fa-upload"></i> Upload</button>
21
        <input name="op" type="hidden" value="Upload" />
22
        [% IF ( patron.image ) %]
23
          <a id="delpicture" href="/cgi-bin/koha/tools/picture-upload.pl?op=Delete&amp;borrowernumber=[% patron.borrowernumber | html %]&amp;csrf_token=[% csrf_token | html %]" class="btn btn-default btn-xs delete"><i class="fa fa-trash"></i> Delete</a>
24
        [% END %]
25
      </div>
26
    </form>
27
  </div>
28
  <div id="capture-patron-image" class="patroninfo-section">
29
    <form>
30
      <!-- 240 is the fixed width. See Javascript rheight calculations. -->
31
      <div id="liveFeed" style="float:left" width="260px">
32
        <video width="240px" height="180px" id="videoFeed" style="float:left; width:240px !important; height:180px !important"></video>
33
      </div>
34
      <div id="captured-image" style="float:right" width="260px">
35
        <canvas width="240px" id="canvas" style="display:none"></canvas>
36
        <img width="240px" height="180px" id="picture" src="">
37
      </div>
38
      <hr>
39
      <div id="capture-controls" style="display:block">
40
        <button id="webcam_off" type="button" onclick="turn_off();">Off</button>
41
        <button id="webcam_on" type="button" onclick="turn_on();">On</button>
42
        <button id="webcam_capture" type="button" onclick="take_picture();">
43
          Take
44
        </button>
45
        <button id="webcam_save" type="button" onclick="save_picture();">
46
          Keep
47
        </button>
48
      </div>
49
    </form>
50
  </div>
51
  [% END %]
52
[% END %]
53
54
  <script>
55
    function turn_on() {
56
      // Detect camera
57
      navigator.getMedia = ( navigator.getUserMedia ||
58
                             navigator.webkitGetUserMedia ||
59
                             navigator.mozGetUserMedia ||
60
                             navigator.msGetUserMedia );
61
      if (navigator.getMedia) {
62
        // keep it on, and set object sizes
63
        navigator.getMedia(
64
          {video: true},
65
          function(stream) {
66
            var takebutton = document.getElementById('webcam_capture');
67
            var savebutton = document.getElementById('webcam_save');
68
            var video = document.getElementById('videoFeed');
69
            var canvas = document.getElementById('canvas');
70
            var picture = document.getElementById('picture');
71
            video.srcObject = stream;
72
            video.autoplay = true;
73
            // use 200ms timeout to set because mobile switches height/width
74
            //   until used and the videoWidth and videoHeight are checked
75
            setTimeout(function(){
76
              width = video.videoWidth;
77
              height = video.videoHeight;
78
              var ratio = height.toFixed(6)/width.toFixed(6);
79
              var rheight = 240 * ratio;
80
              video.height = rheight;
81
              canvas.height = rheight;
82
              picture.height = rheight;
83
              takebutton.disabled = false;
84
            }, 200);
85
          },
86
          function() {
87
            alert('Webcam not detected or permission denied!');
88
          }
89
        );
90
      } else {
91
        alert('Webcam not detected or permission denied!');
92
      }
93
    }
94
95
    function turn_off() {
96
      var takebutton = document.getElementById('webcam_capture');
97
      var savebutton = document.getElementById('webcam_save');
98
      var picture = document.getElementById('picture');
99
      var video = document.getElementById('videoFeed');
100
      var stream = video.srcObject;
101
      if (stream) {
102
        stream.getTracks().forEach(function(track) {
103
          track.stop();
104
        });
105
      }
106
      video.srcObject = null;
107
      video.autoplay = false;
108
      takebutton.disabled = true;
109
    }
110
111
    function take_picture() {
112
      var savebutton = document.getElementById('webcam_save');
113
      var video = document.getElementById('videoFeed');
114
      var stream = video.srcObject;
115
      var canvas = document.getElementById('canvas');
116
      var context = canvas.getContext("2d");
117
      var picture = document.getElementById('picture');
118
      context.drawImage( video, 0, 0, picture.width, picture.height);
119
      picture.src = canvas.toDataURL('image/jpeg');
120
      savebutton.disabled = false;
121
    }
122
123
    function save_picture() {
124
      // gather form data for AJAX POST
125
      var data = new FormData();
126
      data.append("uploadfiletext", picture.src );
127
      data.append("csrf_token", $("#csrf_token").val() );
128
      data.append("filetype", "image" );
129
      data.append("uploadfilename", "patron-photo.jpg" );
130
      data.append("cardnumber", $("#cardnumber").val() );
131
      data.append("borrowernumber", $("#borrowernumber").val() );
132
      data.append("op", "Upload" );
133
      $.ajax({
134
        type: "POST",
135
        url: "/cgi-bin/koha/tools/picture-upload.pl",
136
        data: data,
137
        cache: false,
138
        contentType: false,
139
        processData: false
140
      }).done(function() {
141
        // turn off video nicely if it is still on.
142
        var video = document.getElementById('videoFeed');
143
        var stream = video.srcObject;
144
        if (stream) {
145
          stream.getTracks().forEach(function(track) {
146
            if (track.readyState == 'live' && track.kind === 'video') {
147
              track.stop();
148
            }
149
          });
150
        }
151
        location.reload();
152
      });
153
    }
154
  </script>
155
  <script>
156
     //if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
157
     //  navigator.mediaDevices.getUserMedia(mediaConfig).then(
158
     //    function(stream) {
159
     //      video.srcObject = stream;
160
     //      video.play();
161
     //    }
162
     //  );
163
     //}
164
165
    var takepicture = document.getElementById('webcam_capture');
166
    var savepicture = document.getElementById('webcam_save');
167
    savepicture.disabled = true;
168
169
    // Detect camera
170
    navigator.getMedia = ( navigator.getUserMedia ||
171
                           navigator.webkitGetUserMedia ||
172
                           navigator.mozGetUserMedia ||
173
                           navigator.msGetUserMedia );
174
    // If no camera...
175
    if (! navigator.getMedia) {
176
      // Disable the buttons.
177
      var webcamoff = document.getElementById('webcam_off');
178
      var webcamon = document.getElementById('webcam_on');
179
      webcamon.disabled = true;
180
      webcamoff.disabled = true;
181
      takepicture.disabled = true;
182
      document.getElementById('capture-patron-image').style.visibility = "hidden";
183
      document.getElementById('capture-patron-image').remove();
184
    } else {
185
      // If camera...
186
      //   keep it on and set objects height and width
187
      navigator.getMedia(
188
        {video: true},
189
        function(stream) {
190
          var video = document.getElementById('videoFeed');
191
          var canvas = document.getElementById('canvas');
192
          var picture = document.getElementById('picture');
193
          video.srcObject = stream;
194
          video.autoplay = true;
195
          // use 200ms timeout to set because mobile switches height/width
196
          //   until used and the videoWidth and videoHeight are checked
197
          setTimeout(function(){
198
            var width = video.videoWidth;
199
            var height = video.videoHeight;
200
            var ratio = height.toFixed(6)/width.toFixed(6);
201
            var rheight = 240 * ratio;
202
            video.height = rheight;
203
            canvas.height = rheight;
204
            picture.height = rheight;
205
            takepicture.disabled = false;
206
          }, 200);
207
        },
208
        function() {
209
          alert('Webcam not detected or permission denied!');
210
        }
211
      );
212
    }
213
  </script>
214
</html>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt (-40 lines)
Lines 296-341 Link Here
296
                                </div> [% # /div.rows %]
296
                                </div> [% # /div.rows %]
297
                            </div> [% # /div#patron-information %]
297
                            </div> [% # /div#patron-information %]
298
298
299
                            [% IF ( patronimages ) %]
300
                                [% IF ( CAN_user_tools_batch_upload_patron_images ) %]
301
                                    <div id="manage-patron-image" class="patroninfo-section">
302
                                        [% IF ( patron.image ) %]
303
                                            <div class="patroninfo-heading">
304
                                                <h3>Manage patron image</h3>
305
                                                <a class="btn btn-default btn-xs" id="show-picture-upload" href="#"><i class="fa fa-pencil"></i> Edit</a>
306
                                            </div>
307
                                        [% ELSE %]
308
                                            <div class="patroninfo-heading">
309
                                                <h3>Upload patron image</h3>
310
                                                <a class="btn btn-default btn-xs" id="show-picture-upload" href="#"><i class="fa fa-plus"></i> Add</a>
311
                                            </div>
312
                                        [% END %]
313
                                        <form method="post" id="picture-upload" style="display:none;" action="/cgi-bin/koha/tools/picture-upload.pl" enctype="multipart/form-data">
314
                                            [% IF ( patron.image ) %]
315
                                                <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>
316
                                            [% ELSE %]
317
                                                <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>
318
                                            [% END %]
319
                                            <p>Only PNG, GIF, JPEG, XPM formats are supported.</p>
320
                                            <label for="uploadfile">Select the file to upload: </label>
321
                                            <input type="file" id="uploadfile" name="uploadfile" required="required" />
322
                                            <div class="action">
323
                                                <input type="hidden" name="csrf_token" value="[% csrf_token | html %]" />
324
                                                <input type="hidden" id="image" name="filetype" value="image" />
325
                                                <input type="hidden" id="cardnumber" name="cardnumber" value="[% patron.cardnumber | html %]" />
326
                                                <input type="hidden" name="borrowernumber" value="[% patron.borrowernumber | html %]" />
327
                                                <button type="submit" class="btn btn-default btn-xs"><i class="fa fa-upload"></i> Upload</button>
328
                                                <input name="op" type="hidden" value="Upload" />
329
                                                [% IF ( patron.image ) %]
330
                                                    <a id="delpicture" href="/cgi-bin/koha/tools/picture-upload.pl?op=Delete&amp;borrowernumber=[% patron.borrowernumber | html %]&amp;csrf_token=[% csrf_token | html %]" class="btn btn-default btn-xs delete"><i class="fa fa-trash"></i> Delete</a>
331
                                                [% END %]
332
                                                <a href="#" id="cancel-picture-upload" class="cancel">Cancel</a>
333
                                            </div>
334
                                        </form>
335
                                    </div> [% # /div#manage-patron-image %]
336
                                [% END %]
337
                            [% END %]
338
339
                            [% IF Koha.Preference('HouseboundModule') %]
299
                            [% IF Koha.Preference('HouseboundModule') %]
340
                                <div id="houseboundroles" class="patroninfo-section">
300
                                <div id="houseboundroles" class="patroninfo-section">
341
                                    [% IF ( housebound_role.housebound_chooser == 1 OR housebound_role.housebound_deliverer == 1 ) %]
301
                                    [% IF ( housebound_role.housebound_chooser == 1 OR housebound_role.housebound_deliverer == 1 ) %]
(-)a/koha-tmpl/intranet-tmpl/prog/js/members-menu.js (-4 / +2 lines)
Lines 86-95 $(document).ready(function(){ Link Here
86
    $(".edit-patronimage").on("click", function(e){
86
    $(".edit-patronimage").on("click", function(e){
87
        e.preventDefault();
87
        e.preventDefault();
88
        var borrowernumber = $(this).data("borrowernumber");
88
        var borrowernumber = $(this).data("borrowernumber");
89
        $.get("/cgi-bin/koha/members/moremember.pl", { borrowernumber : borrowernumber }, function( data ){
89
        $.get("/cgi-bin/koha/members/moremember-patronimage.pl", { borrowernumber : borrowernumber }, function( data ){
90
            var image_form = $(data).find("#picture-upload");
90
            $("#patronImageEdit .modal-body").html( data );
91
            image_form.show().find(".cancel").remove();
92
            $("#patronImageEdit .modal-body").html( image_form );
93
        });
91
        });
94
        var modalTitle = $(this).attr("title");
92
        var modalTitle = $(this).attr("title");
95
        $("#patronImageEdit .modal-title").text(modalTitle);
93
        $("#patronImageEdit .modal-title").text(modalTitle);
(-)a/members/moremember-patronimage.pl (+59 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# Copyright 2020 Mark Tompsett
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
use CGI qw ( -utf8 );
22
use C4::Auth;
23
use C4::Output;
24
use Koha::Patrons;
25
use Koha::Token;
26
27
my $input = CGI->new;
28
29
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
30
    {
31
        template_name   => 'members/moremember-patronimage.tt',
32
        query           => $input,
33
        type            => 'intranet',
34
        authnotrequired => 0,
35
        flagsrequired   => { borrowers => 'edit_borrowers' },
36
    }
37
);
38
39
my $borrowernumber = $input->param('borrowernumber');
40
my $patron         = Koha::Patrons->find($borrowernumber);
41
my $logged_in_user = Koha::Patrons->find($loggedinuser);
42
43
$template->param(
44
    csrf_token => Koha::Token->new->generate_csrf(
45
        { session_id => $input->cookie('CGISESSID'), }
46
    ),
47
    patron         => $patron,
48
    logged_in_user => $logged_in_user,
49
);
50
51
output_html_with_http_headers $input, $cookie, $template->output;
52
53
__END__
54
55
=head1 moremember-patronimage.pl
56
57
 script to provide modal window for patron images
58
59
=cut
(-)a/tools/picture-upload.pl (-19 / +40 lines)
Lines 25-30 use File::Temp; Link Here
25
use File::Copy;
25
use File::Copy;
26
use CGI qw ( -utf8 );
26
use CGI qw ( -utf8 );
27
use GD;
27
use GD;
28
use MIME::Base64;
28
use C4::Context;
29
use C4::Context;
29
use C4::Auth;
30
use C4::Auth;
30
use C4::Output;
31
use C4::Output;
Lines 35-41 use Koha::Patrons; Link Here
35
use Koha::Patron::Images;
36
use Koha::Patron::Images;
36
use Koha::Token;
37
use Koha::Token;
37
38
38
my $input = new CGI;
39
my $input = CGI->new;
39
40
40
unless (C4::Context->preference('patronimages')) {
41
unless (C4::Context->preference('patronimages')) {
41
    # redirect to intranet home if patronimages is not enabled
42
    # redirect to intranet home if patronimages is not enabled
Lines 43-60 unless (C4::Context->preference('patronimages')) { Link Here
43
    exit;
44
    exit;
44
}
45
}
45
46
46
my ($template, $loggedinuser, $cookie)
47
my ($template, $loggedinuser, $cookie) = get_template_and_user(
47
    = get_template_and_user({template_name => "tools/picture-upload.tt",
48
    {
48
					query => $input,
49
        template_name => "tools/picture-upload.tt",
49
					type => "intranet",
50
        query => $input,
50
					authnotrequired => 0,
51
        type => "intranet",
51
					flagsrequired => { tools => 'batch_upload_patron_images'},
52
        authnotrequired => 0,
52
					debug => 0,
53
        flagsrequired => { tools => 'batch_upload_patron_images'},
53
					});
54
        debug => 0,
55
    }
56
);
54
57
55
our $filetype      = $input->param('filetype') || '';
58
our $filetype      = $input->param('filetype') || '';
56
my $cardnumber     = $input->param('cardnumber');
59
my $cardnumber     = $input->param('cardnumber');
57
our $uploadfilename = $input->param('uploadfile') || '';
60
our $uploadfilename = $input->param('uploadfile') || $input->param('uploadfilename') || '';
61
my $uploadfiletext = $input->param('uploadfiletext') || '';
58
my $uploadfile     = $input->upload('uploadfile');
62
my $uploadfile     = $input->upload('uploadfile');
59
my $borrowernumber = $input->param('borrowernumber');
63
my $borrowernumber = $input->param('borrowernumber');
60
my $op             = $input->param('op') || '';
64
my $op             = $input->param('op') || '';
Lines 87-93 our @counts = (); Link Here
87
our %errors = ();
91
our %errors = ();
88
92
89
# Case is important in these operational values as the template must use case to be visually pleasing!
93
# Case is important in these operational values as the template must use case to be visually pleasing!
90
if ( ( $op eq 'Upload' ) && $uploadfile ) {
94
if ( ( $op eq 'Upload' ) && ($uploadfile || $uploadfiletext) ) {
91
95
92
    output_and_exit( $input, $cookie, $template, 'wrong_csrf_token' )
96
    output_and_exit( $input, $cookie, $template, 'wrong_csrf_token' )
93
        unless Koha::Token->new->check_csrf({
97
        unless Koha::Token->new->check_csrf({
Lines 106-123 if ( ( $op eq 'Upload' ) && $uploadfile ) { Link Here
106
    $debug and warn "tempfile = $tempfile";
110
    $debug and warn "tempfile = $tempfile";
107
    my ( @directories, $results );
111
    my ( @directories, $results );
108
112
109
    $errors{'NOTZIP'} = 1
110
      if ( $uploadfilename !~ /\.zip$/i && $filetype =~ m/zip/i );
111
    $errors{'NOWRITETEMP'} = 1 unless ( -w $dirname );
113
    $errors{'NOWRITETEMP'} = 1 unless ( -w $dirname );
112
    $errors{'EMPTYUPLOAD'} = 1 unless ( length($uploadfile) > 0 );
114
    if ( length($uploadfiletext) == 0 ) {
115
        $errors{'NOTZIP'} = 1
116
          if ( $uploadfilename !~ /\.zip$/i && $filetype =~ m/zip/i );
117
        $errors{'EMPTYUPLOAD'} = 1 unless ( length($uploadfile) > 0 );
118
    }
113
119
114
    if (%errors) {
120
    if (%errors) {
115
        $template->param( ERRORS => [ \%errors ] );
121
        $template->param( ERRORS => [ \%errors ] );
116
        output_html_with_http_headers $input, $cookie, $template->output;
122
        output_html_with_http_headers $input, $cookie, $template->output;
117
        exit;
123
        exit;
118
    }
124
    }
119
    while (<$uploadfile>) {
125
120
        print $tfh $_;
126
    if ( length($uploadfiletext) == 0 ) {
127
        while (<$uploadfile>) {
128
            print $tfh $_;
129
        }
130
    } else {
131
        # data type controlled in toDataURL() in template
132
        if ( $uploadfiletext =~ /data:image\/jpeg;base64,(.*)/ ) {
133
            my $encoded_picture = $1;
134
            my $decoded_picture = decode_base64($encoded_picture);
135
            print $tfh $decoded_picture;
136
        } else {
137
            $errors{'BADPICTUREDATA'} = 1;
138
            $template->param( ERRORS => [ \%errors ] );
139
            output_html_with_http_headers $input, $cookie, $template->output;
140
            exit;
141
        }
121
    }
142
    }
122
    close $tfh;
143
    close $tfh;
123
    if ( $filetype eq 'zip' ) {
144
    if ( $filetype eq 'zip' ) {
Lines 228-234 sub handle_dir { Link Here
228
            return \%direrrors;
249
            return \%direrrors;
229
        }
250
        }
230
251
231
        while ( my $line = <$fh> ) {
252
        my @lines = <$fh>;
253
        close $fh;
254
        foreach my $line (@lines) {
232
            $debug and warn "Reading contents of $file";
255
            $debug and warn "Reading contents of $file";
233
            chomp $line;
256
            chomp $line;
234
            $debug and warn "Examining line: $line";
257
            $debug and warn "Examining line: $line";
Lines 248-254 sub handle_dir { Link Here
248
            $source = "$dir/$filename";
271
            $source = "$dir/$filename";
249
            %counts = handle_file( $cardnumber, $source, $template, %counts );
272
            %counts = handle_file( $cardnumber, $source, $template, %counts );
250
        }
273
        }
251
        close $fh;
252
        closedir DIR;
274
        closedir DIR;
253
    }
275
    }
254
    else {
276
    else {
255
- 

Return to bug 6815