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

(-)a/koha-tmpl/intranet-tmpl/prog/en/css/uploader.css (+38 lines)
Line 0 Link Here
1
#progress_bar {
2
  margin: 10px 0;
3
  padding: 3px;
4
  border: 1px solid #000;
5
  font-size: 14px;
6
  clear: both;
7
  opacity: 0;
8
  -moz-transition: opacity 1s linear;
9
  -o-transition: opacity 1s linear;
10
  -webkit-transition: opacity 1s linear;
11
}
12
#progress_bar.loading {
13
  opacity: 1.0;
14
}
15
#progress_bar .percent {
16
  background-color: #99ccff;
17
  height: auto;
18
  width: 0;
19
}
20
#server_response {
21
    background-color: white;
22
    background-image: url("../../img/x_alt_16x16.png");
23
    background-repeat: no-repeat;
24
    background-origin: padding-box;
25
    background-position: right top;
26
    border: 1px solid #DDDDDD;
27
    color: #999999;
28
    font-size: 14px;
29
    height: 30px;
30
    left: 50%;
31
    margin-left: -125px;
32
    margin-top: -15px;
33
    padding: 14px 0 2px;
34
    position: fixed;
35
    text-align: center;
36
    top: 50%;
37
    width: 250px;
38
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes-upload.tt (+317 lines)
Line 0 Link Here
1
    [% INCLUDE 'doc-head-open.inc' %]
2
    <title>Koha &rsaquo; Tools &rsaquo; Quote Uploader</title>
3
    [% INCLUDE 'doc-head-close.inc' %]
4
    <link rel="stylesheet" type="text/css" href="/intranet-tmpl/prog/en/css/uploader.css" />
5
    <link rel="stylesheet" type="text/css" href="/intranet-tmpl/prog/en/css/datatables.css" />
6
    <script type="text/javascript" src="/intranet-tmpl/prog/en/lib/jquery/plugins/jquery.dataTables.min.js"></script>
7
    [% INCLUDE 'datatables-strings.inc' %]
8
    </script>
9
    <script type="text/javascript" src="/intranet-tmpl/prog/en/js/datatables.js"></script>
10
    <script type="text/javascript" src="/intranet-tmpl/prog/en/js/jquery.jeditable.mini.js"></script>
11
    <script type="text/javascript">
12
    //<![CDATA[
13
    var oTable; //DataTable object
14
    $(document).ready(function() {
15
16
    // Credits:
17
    // FileReader() code copied and hacked from:
18
    // http://www.html5rocks.com/en/tutorials/file/dndfiles/
19
    // fnCSVToArray() gratefully borrowed from:
20
    // http://www.bennadel.com/blog/1504-Ask-Ben-Parsing-CSV-Strings-With-Javascript-Exec-Regular-Expression-Command.htm
21
22
    var reader;
23
    var progress = document.querySelector('.percent');
24
    $("#server_response").hide();
25
26
    function fnAbortRead() {
27
        reader.abort();
28
    }
29
30
    function fnErrorHandler(evt) {
31
        switch(evt.target.error.code) {
32
            case evt.target.error.NOT_FOUND_ERR:
33
                alert('File Not Found!');
34
                break;
35
            case evt.target.error.NOT_READABLE_ERR:
36
                alert('File is not readable');
37
                break;
38
            case evt.target.error.ABORT_ERR:
39
                break; // noop
40
            default:
41
                alert('An error occurred reading this file.');
42
        };
43
    }
44
45
    function fnUpdateProgress(evt) {
46
        // evt is an ProgressEvent.
47
        if (evt.lengthComputable) {
48
            var percentLoaded = Math.round((evt.loaded / evt.total) * 100);
49
            // Increase the progress bar length.
50
            if (percentLoaded < 100) {
51
                progress.style.width = percentLoaded + '%';
52
                progress.textContent = percentLoaded + '%';
53
            }
54
        }
55
    }
56
57
    function fnCSVToArray( strData, strDelimiter ){
58
        // This will parse a delimited string into an array of
59
        // arrays. The default delimiter is the comma, but this
60
        // can be overriden in the second argument.
61
62
        // Check to see if the delimiter is defined. If not,
63
        // then default to comma.
64
        strDelimiter = (strDelimiter || ",");
65
66
        // Create a regular expression to parse the CSV values.
67
        var objPattern = new RegExp(
68
        (
69
            // Delimiters.
70
            "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +
71
            // Quoted fields.
72
            "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +
73
            // Standard fields.
74
            "([^\"\\" + strDelimiter + "\\r\\n]*))"
75
        ),
76
            "gi"
77
        );
78
79
        // Create an array to hold our data. Give the array
80
        // a default empty first row.
81
        var arrData = [[]];
82
83
        // Create an array to hold our individual pattern
84
        // matching groups.
85
        var arrMatches = null;
86
87
        // Keep looping over the regular expression matches
88
        // until we can no longer find a match.
89
        while (arrMatches = objPattern.exec( strData )){
90
91
            // Get the delimiter that was found.
92
            var strMatchedDelimiter = arrMatches[ 1 ];
93
94
            // Check to see if the given delimiter has a length
95
            // (is not the start of string) and if it matches
96
            // field delimiter. If it does not, then we know
97
            // that this delimiter is a row delimiter.
98
            if ( strMatchedDelimiter.length && (strMatchedDelimiter != strDelimiter) ){
99
                // Since we have reached a new row of data,
100
                // add an empty row to our data array.
101
                // Note: if there is not more data, we will have to remove this row later
102
                arrData.push( [] );
103
            }
104
105
            // Now that we have our delimiter out of the way,
106
            // let's check to see which kind of value we
107
            // captured (quoted or unquoted).
108
            if (arrMatches[ 2 ]){
109
                // We found a quoted value. When we capture
110
                // this value, unescape any double quotes.
111
                var strMatchedValue = arrMatches[ 2 ].replace(
112
                new RegExp( "\"\"", "g" ),
113
                    "\""
114
                );
115
            } else if (arrMatches[3]){
116
                // We found a non-quoted value.
117
                var strMatchedValue = arrMatches[ 3 ];
118
            } else {
119
                // There is no more valid data so remove the row we added earlier
120
                // Is there a better way? Perhaps a look-ahead regexp?
121
                arrData.splice(arrData.length-1, 1);
122
            }
123
124
            // Now that we have our value string, let's add
125
            // it to the data array.
126
            arrData[ arrData.length - 1 ].push( strMatchedValue );
127
        }
128
129
        // Return the parsed data.
130
        return( arrData );
131
    }
132
133
    function fnDataTable(aaData) {
134
        for(var i=0; i<aaData.length; i++) {
135
            aaData[i].push('Delete'); //this is hackish FIXME
136
        }
137
        document.getElementById('quotes_editor').style.visibility="visible";
138
        document.getElementById('file_uploader').style.visibility="hidden";
139
        oTable = $('#quotes_editor').dataTable( {
140
            "bAutoWidth"        : false,
141
            "bPaginate"         : true,
142
            "bSort"             : false,
143
            "sPaginationType"   : "full_numbers",
144
            "sDom"              : '<"save_quotes">frtip',
145
            "aaData"            : aaData,
146
            "aoColumns"         : [
147
                {
148
                    "sTitle"  : "Source",
149
                    "sWidth"  : "15%",
150
                },
151
                {
152
                    "sTitle"  : "Quote",
153
                    "sWidth"  : "75%",
154
                },
155
                {
156
                    "sTitle"  : "Actions",
157
                    "sWidth"  : "10%",
158
                },
159
            ],
160
           "fnPreDrawCallback": function(oSettings) {
161
                return true;
162
            },
163
            "fnRowCallback": function( nRow, aData, iDisplayIndex ) {
164
                noEditFields = [2]; /* action */
165
                /* console.log('Quote ID: '+quoteID); */
166
                /* do foo on various cells in the current row */
167
                $('td:eq(2)', nRow).html('<input type="button" class="delete" value="Delete" onclick="fnClickDeleteRow(this.parentNode);" />');
168
                /* apply no_edit id to noEditFields */
169
                for (i=0; i<noEditFields.length; i++) {
170
                    $('td', nRow)[noEditFields[i]].setAttribute("id","no_edit");
171
                }
172
                return nRow;
173
            },
174
           "fnDrawCallback": function(oSettings) {
175
                /* Apply the jEditable handlers to the table on all fields w/o the no_edit id */
176
                $('#quotes_editor tbody td[id!="no_edit"]').editable( function(value, settings) {
177
                        var cellPosition = oTable.fnGetPosition( this );
178
                        oTable.fnUpdate(value, cellPosition[0], cellPosition[1], false, false);
179
                        return(value);
180
                    },
181
                    {
182
                    "callback"      : function( sValue, y ) {
183
                                          oTable.fnDraw(false); /* no filter/sort or we lose our pagination */
184
                                      },
185
                    "height"        : "14px",
186
                });
187
                $("div.save_quotes").html('<input type="button" class="add_quote_button" value="Save Quotes" style="float: right;" onclick="fnGetData(document.getElementById(\'quotes_editor\'));"/>');
188
           },
189
        });
190
    }
191
192
    function fnHandleFileSelect(evt) {
193
        // Reset progress indicator on new file selection.
194
        progress.style.width = '0%';
195
        progress.textContent = '0%';
196
197
        reader = new FileReader();
198
        reader.onerror = fnErrorHandler;
199
        reader.onprogress = fnUpdateProgress;
200
        reader.onabort = function(e) {
201
            alert('File read cancelled');
202
            parent.location='quotes-upload.pl';
203
        };
204
        reader.onloadstart = function(e) {
205
            document.getElementById('progress_bar').className = 'loading';
206
        };
207
        reader.onload = function(e) {
208
            // Ensure that the progress bar displays 100% at the end.
209
            progress.style.width = '100%';
210
            progress.textContent = '100%';
211
            setTimeout("document.getElementById('progress_bar').className='';", 2000);
212
            quotes = fnCSVToArray(e.target.result, ',');
213
            fnDataTable(quotes);
214
        }
215
216
        // perform various sanity checks on the target file prior to uploading...
217
        var fileType = evt.target.files[0].type || 'unknown';
218
        var fileSizeInK = Math.round(evt.target.files[0].size/1024);
219
220
        if (fileType != 'text/csv') {
221
            alert('Incorrect filetype: '+fileType+'. Uploads limited to text/csv.');
222
            parent.location='quotes-upload.pl';
223
            return;
224
        }
225
        if (fileSizeInK > 512) {
226
            if (!confirm(evt.target.files[0].name+' is '+fileSizeInK+' K in size. Do you really want to upload this file?')) {
227
                parent.location='quotes-upload.pl';
228
                return;
229
            }
230
        }
231
        // Read in the image file as a text string.
232
        reader.readAsText(evt.target.files[0]);
233
    }
234
235
    document.getElementById('files').addEventListener('change', fnHandleFileSelect, false);
236
237
    });
238
239
    function fnGetData(element) {
240
        var jqXHR = $.ajax({
241
            url         : "/cgi-bin/koha/tools/quotes/quotes-upload_ajax.pl",
242
            type        : "POST",
243
            contentType : "application/x-www-form-urlencoded", // we must claim this mimetype or CGI will not decode the URL encoding
244
            dataType    : "json",
245
            data        : {
246
                            "quote"     : JSON.stringify(oTable.fnGetData()),
247
                            "action"    : "add",
248
                          },
249
            success     : function(){
250
                            var response = JSON.parse(jqXHR.responseText);
251
                            if (response.success) {
252
                                $("#server_response").text(response.records+' quotes saved.');
253
                            }
254
                            else {
255
                                $("#server_response").text('An error has occurred. '+response.records+' quotes saved. Please ask your administrator to check the server log for more details.');
256
                            }
257
                            $("#server_response").fadeIn(200);
258
                          },
259
        });
260
    }
261
262
    function fnClickDeleteRow(td) {
263
        oTable.fnDeleteRow(oTable.fnGetPosition(td)[0]);
264
    }
265
266
    function fnResetUpload() {
267
        $('#server_response').fadeOut(200);
268
        window.location.reload(true);   // is this the best route?
269
    }
270
271
    //]]>
272
    </script>
273
</head>
274
<body id="tools_quotes" class="tools">
275
[% INCLUDE 'header.inc' %]
276
[% INCLUDE 'cat-search.inc' %]
277
278
<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a> &rsaquo; Quote Uploader</div>
279
280
<div id="doc3" class="yui-t2">
281
    <div id="bd">
282
        <div id="yui-main">
283
            <div class="yui-b">
284
285
                <div id="file_uploader" style="float: left; width: 100%; visibility:visible;">
286
                    <input type="file" id="files" name="file" />
287
                    <button onclick="fnAbortRead();">Cancel Upload</button>
288
                    <div id="progress_bar"><div class="percent">0%</div></div>
289
                </div>
290
                <div id="server_response" onclick='fnResetUpload()'>Server Response</div>
291
                <table id="quotes_editor" style="float: left; width: 100%; visibility:hidden;">
292
                <thead>
293
                    <tr>
294
                        <th>Source</th>
295
                        <th>Text</th>
296
                        <th>Actions</th>
297
                    </tr>
298
                </thead>
299
                <tbody>
300
                    <!-- tbody content is generated by DataTables -->
301
                    <tr>
302
                        <td></td>
303
                        <td>Loading data...</td>
304
                        <td></td>
305
                    </tr>
306
                </tbody>
307
                </table>
308
309
310
311
            </div>
312
        </div>
313
    <div class="yui-b noprint">
314
        [% INCLUDE 'tools-menu.inc' %]
315
    </div>
316
</div>
317
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes.tt (-5 / +5 lines)
Lines 1-12 Link Here
1
    [% INCLUDE 'doc-head-open.inc' %]
1
    [% INCLUDE 'doc-head-open.inc' %]
2
    <title>Koha &rsaquo; Tools &rsaquo; Quote Editor</title>
2
    <title>Koha &rsaquo; Tools &rsaquo; Quote Editor</title>
3
    [% INCLUDE 'doc-head-close.inc' %]
3
    [% INCLUDE 'doc-head-close.inc' %]
4
    <link rel="stylesheet" type="text/css" href="/intranet-tmpl/prog/en/css/datatables.css" />
4
    <link rel="stylesheet" type="text/css" href="[% themelang %]/css/datatables.css" />
5
    <script type="text/javascript" src="/intranet-tmpl/prog/en/lib/jquery/plugins/jquery.dataTables.min.js"></script>
5
    <script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/jquery.dataTables.min.js"></script>
6
    [% INCLUDE 'datatables-strings.inc' %]
6
    [% INCLUDE 'datatables-strings.inc' %]
7
    </script>
7
    </script>
8
    <script type="text/javascript" src="/intranet-tmpl/prog/en/js/datatables.js"></script>
8
    <script type="text/javascript" src="[% themelang %]/js/datatables.js"></script>
9
    <script type="text/javascript" src="/intranet-tmpl/prog/en/js/jquery.jeditable.mini.js"></script>
9
    <script type="text/javascript" src="[% themelang %]/js/jquery.jeditable.mini.js"></script>
10
    <script type="text/javascript">
10
    <script type="text/javascript">
11
    //<![CDATA[
11
    //<![CDATA[
12
    var oTable; /* oTable needs to be global */
12
    var oTable; /* oTable needs to be global */
Lines 61-67 Link Here
61
                        });
61
                        });
62
                   },
62
                   },
63
        });
63
        });
64
        $("div.add_quote").html('<input type="button" class="add_quote_button" value="Add Quote" style="float: right;" onclick="fnClickAddRow();"/>');
64
        $("div.add_quote").html('<input type="button" class="add_quote_button" value="Add Quote" style="float: right;" onclick="fnClickAddRow();"/><input type="button" class="import_quote_button" value="Import Quotes" style="float: right;" onclick="parent.location=\'quotes-upload.pl\'"/>');
65
    });
65
    });
66
66
67
        function fnClickAddQuote() {
67
        function fnClickAddQuote() {
(-)a/tools/quotes-upload.pl (+44 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# Copyright 2012 Foundations Bible College Inc.
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 2 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
use strict;
21
use warnings;
22
23
use CGI;
24
use autouse 'Data::Dumper' => qw(Dumper);
25
26
use C4::Auth;
27
use C4::Koha;
28
use C4::Context;
29
use C4::Output;
30
31
my $cgi = new CGI;
32
33
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
34
    {
35
        template_name   => "tools/quotes-upload.tt",
36
        query           => $cgi,
37
        type            => "intranet",
38
        authnotrequired => 0,
39
        flagsrequired   => { tools => 'edit_quotes' },
40
        debug           => 1,
41
    }
42
);
43
44
output_html_with_http_headers $cgi, $cookie, $template->output;
(-)a/tools/quotes/quotes-upload_ajax.pl (-1 / +68 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
# Copyright 2012 Foundations Bible College Inc.
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 2 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
use strict;
21
use warnings;
22
23
use CGI;
24
use JSON;
25
use autouse 'Data::Dumper' => qw(Dumper);
26
27
use C4::Auth;
28
use C4::Koha;
29
use C4::Context;
30
use C4::Output;
31
32
my $cgi = new CGI;
33
my $dbh = C4::Context->dbh;
34
35
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
36
    {
37
        template_name   => "",
38
        query           => $cgi,
39
        type            => "intranet",
40
        authnotrequired => 0,
41
        flagsrequired   => { tools => 'edit_quotes' },
42
        debug           => 1,
43
    }
44
);
45
46
my $success = 'true';
47
48
my $quotes = decode_json($cgi->param('quote'));
49
my $action = $cgi->param('action');
50
51
my $sth = $dbh->prepare('INSERT INTO quotes (source, text) VALUES (?, ?);');
52
53
my $insert_count = 0;
54
55
foreach my $quote (@$quotes) {
56
    $insert_count++ if $sth->execute($quote->[0], $quote->[1]);
57
    if ($sth->err) {
58
        warn sprintf('Database returned the following error: %s', $sth->errstr);
59
        $success = 'false';
60
    }
61
}
62
63
print $cgi->header('application/json');
64
65
print to_json({
66
                success => $success,
67
                records => $insert_count,
68
});

Return to bug 7977