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

(-)a/koha-tmpl/opac-tmpl/bootstrap/en/includes/overdrive-checkout.inc (+19 lines)
Line 0 Link Here
1
<div id="overdrive-checkout" class="modal hide" tabindex="-1" role="dialog" aria-labelledby="overdrive-checkout-label" aria-hidden="true">
2
    <div class="modal-header">
3
        <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button>
4
        <h3 id="overdrive-checkout-label">Checkout</h3>
5
    </div>
6
    <form action="#" method="post" id="overdrive-checkout-form">
7
        <div class="modal-body">
8
                <input type="hidden" name="id" value="" />
9
                <fieldset class="brief">
10
                    <label for="format">Format:</label>
11
                    <ul class="overdrive-format-list">
12
                </fieldset>
13
        </div>
14
        <div class="modal-footer">
15
            <input type="submit" class="btn btn-primary overdrive-checkout-submit" value="Checkout" />
16
            <a href="#" data-dismiss="modal" aria-hidden="true" class="cancel">Cancel</a>
17
        </div>
18
    </form> <!-- /#overdrive-checkout-form -->
19
</div>  <!-- /#overdrive-checkout  -->
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-overdrive-search.tt (-2 / +15 lines)
Lines 55-60 Link Here
55
        </div> <!-- / .container-fluid -->
55
        </div> <!-- / .container-fluid -->
56
    </div> <!-- / .main -->
56
    </div> <!-- / .main -->
57
57
58
[% INCLUDE 'overdrive-checkout.inc' %]
59
58
[% INCLUDE 'opac-bottom.inc' %]
60
[% INCLUDE 'opac-bottom.inc' %]
59
[% BLOCK jsinclude %]
61
[% BLOCK jsinclude %]
60
<script type="text/javascript" src="[% interface %]/[% theme %]/js/overdrive.js"></script>
62
<script type="text/javascript" src="[% interface %]/[% theme %]/js/overdrive.js"></script>
Lines 80-86 function fetch_availability( prod, $tr ) { Link Here
80
                $availability_summary.find( '.available' ).append( ', ' + _("waiting holds:") + ' <strong>' + data.numberOfHolds + '</strong>' );
82
                $availability_summary.find( '.available' ).append( ', ' + _("waiting holds:") + ' <strong>' + data.numberOfHolds + '</strong>' );
81
            }
83
            }
82
84
83
            $tr.find( '.info' ).append( '<div class="actions-menu"><span class="actions"><a href="' + prod.contentDetails[0].href + '" ' + ( data.copiesAvailable ? ( ' class="addtocart">' + _("Check out") ) : ( ' class="hold">' + _("Place hold") ) ) + '</a></span></div>' );
85
            $tr.find( '.info' ).each(function() {
86
                KOHA.OverDriveCirculation.add_actions(this, data.id, data.copiesAvailable);
87
            });
84
        }
88
        }
85
    );
89
    );
86
}
90
}
Lines 179-185 $( document ).ready( function() { Link Here
179
        return false;
183
        return false;
180
    });
184
    });
181
185
182
    search( 0 );
186
    [% IF ( overdrive_error ) %]
187
    KOHA.OverDriveCirculation.display_error("#breadcrumbs", "[% overdrive_error.dquote %]");
188
    [% END %]
189
    [% IF ( loggedinusername ) %]
190
    KOHA.OverDriveCirculation.with_account_details("#breadcrumbs", function() {
191
        search( 0 );
192
    });
193
    [% ELSE %]
194
        search( 0 );
195
    [% END %]
183
} );
196
} );
184
</script>
197
</script>
185
[% END %]
198
[% END %]
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt (+23 lines)
Lines 147-154 Using this account is not recommended because some parts of Koha will not functi Link Here
147
                                [% IF ( BORROWER_INFO.amountlessthanzero ) %]<li><a href="#opac-user-fines">Credits ([% BORROWER_INFO.amountoutstanding | $Price %])</a></li>[% END %]
147
                                [% IF ( BORROWER_INFO.amountlessthanzero ) %]<li><a href="#opac-user-fines">Credits ([% BORROWER_INFO.amountoutstanding | $Price %])</a></li>[% END %]
148
                            [% END %]
148
                            [% END %]
149
                            [% IF ( RESERVES.count ) %]<li><a href="#opac-user-holds">Holds ([% RESERVES.count %])</a></li>[% END %]
149
                            [% IF ( RESERVES.count ) %]<li><a href="#opac-user-holds">Holds ([% RESERVES.count %])</a></li>[% END %]
150
                            [% IF ( OverDriveCirculation ) %]
151
                            <li><a href="#opac-user-overdrive">OverDrive Account</a></li>
152
                            [% END %]
150
                        </ul>
153
                        </ul>
151
154
155
                        <div id="opac-user-overdrive">
156
                        </div>
152
                        <div id="opac-user-checkouts">
157
                        <div id="opac-user-checkouts">
153
                            [% IF ( issues_count ) %]
158
                            [% IF ( issues_count ) %]
154
                                <form id="renewselected" action="/cgi-bin/koha/opac-renew.pl" method="post">
159
                                <form id="renewselected" action="/cgi-bin/koha/opac-renew.pl" method="post">
Lines 735-740 Using this account is not recommended because some parts of Koha will not functi Link Here
735
    </div> <!-- /.container-fluid -->
740
    </div> <!-- /.container-fluid -->
736
</div> <!-- /#main -->
741
</div> <!-- /#main -->
737
742
743
[% IF ( OverDriveCirculation ) %]
744
[% INCLUDE 'overdrive-checkout.inc' %]
745
[% END %]
746
738
[% INCLUDE 'opac-bottom.inc' %]
747
[% INCLUDE 'opac-bottom.inc' %]
739
748
740
749
Lines 798-801 Using this account is not recommended because some parts of Koha will not functi Link Here
798
        });
807
        });
799
        //]]>
808
        //]]>
800
    </script>
809
    </script>
810
    <script type="text/javascript" src="[% interface %]//[% theme %]/js/overdrive.js"></script>
811
    <script type="text/JavaScript">
812
    $(document).ready(function() {
813
    [% IF ( overdrive_error ) %]
814
        KOHA.OverDriveCirculation.display_error("#opac-user-overdrive", "[% overdrive_error.dquote %]");
815
    [% END %]
816
    [% IF ( overdrive_tab ) %]
817
        $("#opac-user-views").tabs("select", "#opac-user-overdrive");
818
    [% END %]
819
        $("#opac-user-overdrive").each( function() {
820
            KOHA.OverDriveCirculation.display_account_details(this);
821
        } );
822
    });
823
    </script>
801
[% END %]
824
[% END %]
(-)a/koha-tmpl/opac-tmpl/bootstrap/js/overdrive.js (+359 lines)
Lines 59-61 KOHA.OverDrive = ( function() { Link Here
59
        }
59
        }
60
    };
60
    };
61
} )();
61
} )();
62
63
KOHA.OverDriveCirculation = new function() {
64
    var svc_url = '/cgi-bin/koha/svc/overdrive';
65
66
    var error_div = $('<div class="overdrive-error">');
67
    function display_error ( error ) {
68
        error_div.text(error);
69
    }
70
71
    var login_link = $('<a href="#">')
72
        .click(function(e) {
73
            e.preventDefault();
74
            login();
75
        })
76
        .text(_("Login to OverDrive account"));
77
    var login_div = $('<div class="overdrive-login">').append(login_link);
78
79
    var details = null;
80
81
    function is_logged_in() {
82
        return details ? details.is_logged_in : false;
83
    }
84
85
    var checkout_popup = null;
86
    $( document ).ready(function() {
87
        checkout_popup = $("#overdrive-checkout");
88
    });
89
90
    function display_account (container, data) {
91
        if (!data.is_logged_in) {
92
            $(container).append(login_div);
93
            return;
94
        }
95
96
        var logout_link = $('<a href="#logout" class="overdrive-logout">')
97
            .click(function(e) {
98
                e.preventDefault();
99
                $(container).empty().append(error_div);
100
                logout(function(data) {
101
                    display_account(container, data);
102
                });
103
            }).text(_("Logout from OverDrive account"));
104
        $(container).append(logout_link);
105
106
        if (data.checkouts) {
107
            var checkouts_div = $('<div class="overdrive-div">').html('<h3>' + _("Checkouts") + '</h3>');
108
            var checkouts_list = $('<ul class="overdrive-list">');
109
            data.checkouts.items.forEach(function(item) {
110
                item_line(checkouts_list, item);
111
            });
112
            checkouts_div.append(checkouts_list);
113
            $(container).append(checkouts_div);
114
        }
115
116
        if (data.holds) {
117
            var holds_div = $('<div class="overdrive-div">').html('<h3>' + _("Holds") + '</h3>');
118
            var holds_list = $('<ul class="overdrive-list">');
119
            data.holds.items.forEach(function(item) {
120
                item_line(holds_list, item);
121
            });
122
            holds_div.append(holds_list);
123
            $(container).append(holds_div);
124
        }
125
    }
126
127
    function item_line(ul_el, item) {
128
        var line = $('<li class="overdrive-item">');
129
        if (item.images) {
130
            var thumb_url = item.images.thumbnail;
131
            if (thumb_url) {
132
                $('<img class="overdrive-item-thumbnail">')
133
                    .attr("src", thumb_url)
134
                    .appendTo(line);
135
            }
136
        }
137
        $('<div class="overdrive-item-title">')
138
            .text(item.title)
139
            .appendTo(line);
140
        $('<div class="overdrive-item-subtitle">')
141
            .text(item.subtitle)
142
            .appendTo(line);
143
        $('<div class="overdrive-item-author">')
144
            .text(item.author)
145
            .appendTo(line);
146
        var actions = $('<span class="actions">');
147
        display_actions(actions, item.id);
148
        $('<div id="action_'+item.id+'" class="actions-menu">')
149
            .append(actions)
150
            .appendTo(line);
151
152
        $(ul_el).append(line);
153
    }
154
155
    function svc_ajax ( method, params, success_callback ) {
156
        return $.ajax({
157
            method: method,
158
            dataType: "json",
159
            url: svc_url,
160
            data: params,
161
            success: function (data) {
162
                if (data.error) {
163
                    display_error(data.error);
164
                }
165
                success_callback(data);
166
            },
167
            error: function(jqXHR, textStatus, errorThrown) {
168
                display_error(errorThrown);
169
            }
170
        });
171
    }
172
173
    function load_account_details ( callback ) {
174
        svc_ajax('get', { action: "account" }, function(data) {
175
            details = data;
176
            callback(data);
177
        });
178
    }
179
180
    function login() {
181
        svc_ajax('get', { action: "login" }, function(data) {
182
            details = null;
183
            if (data.login_url) {
184
                window.location = data.login_url;
185
            }
186
        });
187
    }
188
189
    function logout (callback) {
190
        svc_ajax('post', { action: "logout" }, function(data) {
191
            details = null;
192
            callback(data);
193
        });
194
    }
195
196
    function item_action (params, el, copies_available) {
197
        var id = params.id;
198
        svc_ajax('post', params, function(data) {
199
            if (data.checkouts) {
200
                details.checkouts = data.checkouts;
201
            }
202
            if (data.holds) {
203
                details.holds = data.holds;
204
            }
205
            display_actions(el, id, copies_available);
206
        });
207
    }
208
209
    function item_is_checked_out (id) {
210
        if ( !(details && details.checkouts) ) {
211
            return null;
212
        }
213
        var id_uc = id.toUpperCase();
214
        var items = details.checkouts.items;
215
        for (var i = 0; i < items.length; i++) {
216
            if ( items[i].id.toUpperCase() == id_uc ) {
217
                return items[i];
218
            }
219
        }
220
        return null;
221
    }
222
223
    function item_is_on_hold (id) {
224
        if ( !(details && details.holds) ) {
225
            return false;
226
        }
227
        var id_uc = id.toUpperCase();
228
        var items = details.holds.items;
229
        for (var i = 0; i < items.length; i++) {
230
            if ( items[i].id.toUpperCase() == id_uc ) {
231
                return items[i];
232
            }
233
        }
234
        return null;
235
    }
236
237
    function display_actions(el, id, copies_available) {
238
        $(el).empty();
239
        if (is_logged_in()) {
240
241
            var item = item_is_checked_out(id);
242
            if (item) {
243
                var expires = new Date(item.expires);
244
                $('<span class="overdrive-item-status">')
245
                    .text(_("Checked out until") + " " + expires.toLocaleString())
246
                    .appendTo(el);
247
                $(el).append(" ");
248
249
                if (item.format) {
250
                    var download = $('<a href="#">').appendTo(el);
251
                    decorate_button(download, _("Download") + " " + item.format);
252
                    svc_ajax('get', {action: "download-url", id: id, format: item.format}, function(data) {
253
                        download.attr("href", data.action);
254
                    });
255
                    $(el).append(" ");
256
                }
257
258
                if (item.formats) {
259
                    var lockable_formats = [];
260
                    for (var f in item.formats) {
261
                        if (f == item.format) continue;
262
263
                        if (item.formats[f]) {
264
                            var access = $('<a target="_blank">').appendTo(el);
265
                            decorate_button(access, _("Access online") + " " + f);
266
                            svc_ajax('get', {action: "download-url", id: id, format: f}, function(data) {
267
                                access.attr("href", data.action);
268
                            });
269
                            $(el).append(" ");
270
                        }
271
                        else {
272
                            lockable_formats.push(f);
273
                        }
274
                    }
275
                    if (lockable_formats.length > 0 && checkout_popup) {
276
                        $(el).append( ajax_button(_("Download as"), function() {
277
                            checkout_format(el, id, lockable_formats, copies_available);
278
                        }) ).append(" ");
279
                    }
280
                }
281
282
                if (item.format) return item;
283
284
                $(el).append( ajax_button(_("Check in"), function() {
285
                    if( confirm(_("Are you sure you want to return this item?")) ) {
286
                        item_action({action: "return", id: id}, el, copies_available + 1);
287
                    }
288
                }) );
289
290
                return item;
291
            }
292
293
            item = item_is_on_hold(id);
294
            if (item) {
295
                $('<span class="overdrive-status">')
296
                    .text(_("On hold"))
297
                    .appendTo(el);
298
                $(el).append(" ");
299
            }
300
301
            if(copies_available && checkout_popup) {
302
                $(el).append( ajax_button(_("Check out"), function() {
303
                    if( confirm(_("Are you sure you want to checkout this item?")) ) {
304
                        svc_ajax('post', {action: "checkout", id: id}, function(data) {
305
                            if (data.checkouts) {
306
                                details.checkouts = data.checkouts;
307
                            }
308
                            if (data.holds) {
309
                                details.holds = data.holds;
310
                            }
311
                            item = display_actions(el, id, copies_available - 1);
312
                            if (item && item.formats && !item.format) {
313
                                var has_available_formats = false;
314
                                var lockable_formats = [];
315
                                for (var f in item.formats) {
316
                                    if (item.formats[f]) {
317
                                        has_available_formats = true;
318
                                        break;
319
                                    }
320
                                    lockable_formats.push(f);
321
                                }
322
323
                                if (!has_available_formats) {
324
                                    checkout_format(el, id, lockable_formats, copies_available - 1);
325
                                }
326
                            }
327
                        });
328
                    }
329
                }) );
330
            }
331
            else if (!item) {
332
                $(el).append( ajax_button(_("Place hold"), function() {
333
                    item_action({action: "place_hold", id: id}, el, copies_available);
334
                }) );
335
            }
336
337
            if (item) {
338
                $(el).append( ajax_button(_("Cancel"), function() {
339
                    if( confirm(_("Are you sure you want to cancel this hold?")) ) {
340
                        item_action({action: "return", id: id}, el, copies_available);
341
                    }
342
                }) );
343
            }
344
            return item;
345
        }
346
    }
347
348
    function ajax_button(label, on_click) {
349
        var button = $('<a href="#">')
350
            .click(function(e) {
351
                e.preventDefault();
352
                on_click();
353
            });
354
        decorate_button(button, label);
355
        return button;
356
    }
357
358
    function decorate_button(button, label) {
359
        $(button)
360
            .addClass("btn btn-primary btn-mini")
361
            .css("color","white")
362
            .text(label);
363
    }
364
365
    function checkout_format(el, id, formats, copies_available) {
366
        if (formats.length == 0) {
367
            alert(_("Item cannot be checked out - no available formats"));
368
            return false;
369
        }
370
371
        var checkout_format_list = checkout_popup.find("ul.overdrive-format-list").empty();
372
        formats.forEach(function (item) {
373
            var li = $('<li>').appendTo(checkout_format_list);
374
            $('<input name="checkout-format" type="radio">')
375
                .val(item)
376
                .appendTo(li);
377
            li.append(item);
378
        });
379
        checkout_popup.modal("show");
380
        checkout_popup.find(".overdrive-checkout-submit").click(function(e) {
381
            e.preventDefault();
382
            var format = checkout_format_list.find("input[type='radio'][name='checkout-format']:checked").val();
383
            item_action({action: "checkout-format", id: id, format: format}, el, copies_available);
384
            $(this).unbind( e );
385
            checkout_popup.modal("hide");
386
        });
387
    }
388
389
    this.with_account_details = function( el, callback ) {
390
        $(el).append(error_div);
391
        load_account_details(function(data) {
392
            if (!data.is_logged_in) {
393
                $(el).append(login_div);
394
            }
395
            callback(data);
396
        });
397
    }
398
399
    this.display_account_details = function( el ) {
400
        $(el).empty().append(error_div);
401
        load_account_details(function(data) {
402
            display_account(el, data);
403
        });
404
    };
405
406
    this.display_error = function( el, error ) {
407
        $(el).empty().append(error_div);
408
        display_error(error);
409
    };
410
411
    this.is_logged_in = is_logged_in;
412
413
    this.add_actions = function(el, id, copies_available) {
414
        var actions = $('<span class="actions">');
415
        display_actions(actions, id, copies_available);
416
        $('<div id="action_'+id+'" class="actions-menu">')
417
            .append(actions)
418
            .appendTo(el);
419
    };
420
}
(-)a/opac/opac-overdrive-search.pl (+1 lines)
Lines 41-45 $template->{'VARS'}->{'q'} = $cgi->param('q'); Link Here
41
$template->{'VARS'}->{'limit'} = C4::Context->preference('OPACnumSearchResults') || 20;
41
$template->{'VARS'}->{'limit'} = C4::Context->preference('OPACnumSearchResults') || 20;
42
$template->{'VARS'}->{'OPACnumSearchResults'} = C4::Context->preference('OPACnumSearchResults') || 20;
42
$template->{'VARS'}->{'OPACnumSearchResults'} = C4::Context->preference('OPACnumSearchResults') || 20;
43
$template->{'VARS'}->{'OverDriveLibraryID'} = C4::Context->preference('OverDriveLibraryID');
43
$template->{'VARS'}->{'OverDriveLibraryID'} = C4::Context->preference('OverDriveLibraryID');
44
$template->param(overdrive_error => $cgi->param('overdrive_error'));
44
45
45
output_html_with_http_headers $cgi, $cookie, $template->output;
46
output_html_with_http_headers $cgi, $cookie, $template->output;
(-)a/opac/opac-user.pl (+6 lines)
Lines 302-307 if (C4::Context->preference("OPACAmazonCoverImages") or Link Here
302
        $template->param(JacketImages=>1);
302
        $template->param(JacketImages=>1);
303
}
303
}
304
304
305
$template->param(
306
    OverDriveCirculation => C4::Context->preference('OverDriveCirculation') || 0,
307
    overdrive_error      => $query->param('overdrive_error') || undef,
308
    overdrive_tab        => $query->param('overdrive_tab') || 0,
309
);
310
305
my $patron_messages = Koha::Patron::Messages->search(
311
my $patron_messages = Koha::Patron::Messages->search(
306
    {
312
    {
307
        borrowernumber => $borrowernumber,
313
        borrowernumber => $borrowernumber,
(-)a/opac/svc/overdrive (-1 / +139 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
# script to log clicks on links to external urls
4
5
# Copyright 2015 Catalyst IT
6
# This file is part of Koha.
7
#
8
# Koha is free software; you can redistribute it and/or modify it
9
# under the terms of the GNU General Public License as published by
10
# the Free Software Foundation; either version 3 of the License, or
11
# (at your option) any later version.
12
#
13
# Koha is distributed in the hope that it will be useful, but
14
# WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
# GNU General Public License for more details.
17
#
18
# You should have received a copy of the GNU General Public License
19
# along with Koha; if not, see <http://www.gnu.org/licenses>.
20
21
use Modern::Perl;
22
use CGI qw ( -utf8 );
23
use JSON qw(encode_json);
24
use C4::Auth qw(checkauth);
25
use C4::Output;
26
use Koha::Logger;
27
use Koha::ExternalContent::OverDrive;
28
29
my $logger = Koha::Logger->get({ interface => 'opac' });
30
our $cgi = new CGI;
31
my $page_url = $cgi->referer();
32
33
my ( $user, $cookie, $sessionID, $flags ) = checkauth( $cgi, 1, {}, 'opac' );
34
$user && $sessionID or response_bad_request("User not logged in");
35
36
my $action = $cgi->param('action') or response_bad_request("No 'action' specified");
37
38
my $od = Koha::ExternalContent::OverDrive->new({ koha_session_id => $sessionID });
39
my %data = (
40
   is_logged_in => JSON::false,
41
);
42
local $@;
43
eval {
44
        {
45
            $action eq 'login' && do {
46
                $data{login_url} = $od->auth_url($page_url);
47
                last;
48
            };
49
50
            if ($od->is_logged_in) {
51
                $data{is_logged_in} = JSON::true;
52
53
                $action eq 'logout' && do {
54
                    $od->forget();
55
                    $data{login_url} = $od->auth_url($page_url);
56
                    $data{is_logged_in} = JSON::false;
57
                    last;
58
                };
59
60
                $action eq 'account' && do {
61
                    $data{account} = $od->patron;
62
                    $data{checkouts} = $od->checkouts;
63
                    $data{holds} = $od->holds;
64
                    last;
65
                };
66
67
                $action eq 'checkout' && do {
68
                    my $id = $cgi->param('id')
69
                      or response_bad_request("No 'id' specified");
70
                    my $format = $cgi->param('format');
71
                    $data{action} = $od->checkout($id, $format);
72
                    $data{checkouts} = $od->checkouts;
73
                    $data{holds} = $od->holds;
74
                    last;
75
                };
76
77
                $action eq 'checkout-format' && do {
78
                    my $id = $cgi->param('id')
79
                      or response_bad_request("No 'id' specified");
80
                    my $format = $cgi->param('format')
81
                      or response_bad_request("No 'format' specified");
82
                    $data{action} = $od->lock_format($id, $format);
83
                    $data{checkouts} = $od->checkouts;
84
                    last;
85
                };
86
87
                $action eq 'download-url' && do {
88
                    my $id = $cgi->param('id')
89
                      or response_bad_request("No 'id' specified");
90
                    my $format = $cgi->param('format')
91
                      or response_bad_request("No 'format' specified");
92
                    $data{action} = $od->checkout_download_url($id, $format, $page_url, $page_url);
93
                    last;
94
                };
95
96
                $action eq 'place_hold' && do {
97
                    my $id = $cgi->param('id')
98
                      or response_bad_request("No 'id' specified");
99
                    $data{action} = $od->place_hold($id);
100
                    $data{holds} = $od->holds;
101
                    last;
102
                };
103
104
                $action eq 'return' && do {
105
                    my $id = $cgi->param('id')
106
                      or response_bad_request("No 'id' specified");
107
                    $data{action} = $od->return($id);
108
                    $data{checkouts} = $od->checkouts;
109
                    $data{holds} = $od->holds;
110
                    last;
111
                };
112
113
                response_bad_request("Invalid 'action': $action");
114
            }
115
        }
116
};
117
if ($@) {
118
    if ($od->is_not_authenticated_error($@)) {
119
        $logger->debug("OverDrive session timeout");
120
        $data{is_logged_in} = JSON::false;
121
    } else {
122
        $logger->error($@);
123
        $data{error} = $od->error_message($@);
124
    }
125
}
126
127
response(\%data);
128
129
130
sub response_bad_request {
131
    my ($error) = @_;
132
    response({error => $error}, "400 $error");
133
}
134
sub response {
135
    my ($data, $status_line) = @_;
136
    $status_line ||= "200 OK";
137
    output_with_http_headers $cgi, undef, encode_json($data), 'json', $status_line;
138
    exit;
139
}

Return to bug 16034