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

(-)a/Koha/Quote.pm (-1 / +17 lines)
Lines 33-38 Koha::Quote - Koha Quote object class Link Here
33
33
34
=cut
34
=cut
35
35
36
=head3 to_api_mapping
37
38
This method returns the mapping for representing a Koha::Quote object
39
on the API.
40
41
=cut
42
43
sub to_api_mapping {
44
    return {
45
        id        => 'quote_id',
46
        source    => 'source',
47
        text      => 'text',
48
        timestamp => 'displayed_on',
49
    };
50
}
51
36
=head3 _type
52
=head3 _type
37
53
38
=cut
54
=cut
Lines 41-44 sub _type { Link Here
41
    return 'Quote';
57
    return 'Quote';
42
}
58
}
43
59
44
1;
60
1;
(-)a/api/v1/swagger/definitions.json (+3 lines)
Lines 65-70 Link Here
65
  "patron_balance": {
65
  "patron_balance": {
66
    "$ref": "definitions/patron_balance.json"
66
    "$ref": "definitions/patron_balance.json"
67
  },
67
  },
68
  "quote": {
69
    "$ref": "definitions/quote.json"
70
  },
68
  "allows_renewal": {
71
  "allows_renewal": {
69
    "$ref": "definitions/allows_renewal.json"
72
    "$ref": "definitions/allows_renewal.json"
70
  },
73
  },
(-)a/api/v1/swagger/definitions/quote.json (+22 lines)
Line 0 Link Here
1
{
2
  "type": "object",
3
  "properties": {
4
    "quote_id": {
5
      "$ref": "../x-primitives.json#/quote_id"
6
    },
7
    "source": {
8
      "description": "source of the quote",
9
      "type": "string"
10
    },
11
    "text": {
12
      "description": "text",
13
      "type": ["string", "null"]
14
    },
15
    "displayed_on": {
16
      "description": "Last display date",
17
      "type": ["string", "null"]
18
    }
19
  },
20
  "additionalProperties": false,
21
  "required": ["quote_id", "source", "text"]
22
}
(-)a/api/v1/swagger/parameters.json (+3 lines)
Lines 32-37 Link Here
32
  "order_id_pp": {
32
  "order_id_pp": {
33
    "$ref": "parameters/order.json#/order_id_pp"
33
    "$ref": "parameters/order.json#/order_id_pp"
34
  },
34
  },
35
  "quote_id_pp": {
36
    "$ref": "parameters/quote.json#/quote_id_pp"
37
  },
35
  "smtp_server_id_pp": {
38
  "smtp_server_id_pp": {
36
    "$ref": "parameters/smtp_server.json#/smtp_server_id_pp"
39
    "$ref": "parameters/smtp_server.json#/smtp_server_id_pp"
37
  },
40
  },
(-)a/api/v1/swagger/parameters/quote.json (+9 lines)
Line 0 Link Here
1
{
2
    "quote_id_pp": {
3
      "name": "quote_id",
4
      "in": "path",
5
      "description": "Quote internal identifier",
6
      "required": true,
7
      "type": "integer"
8
    }
9
}
(-)a/api/v1/swagger/paths.json (+6 lines)
Lines 134-139 Link Here
134
  "/public/patrons/{patron_id}/guarantors/can_see_checkouts": {
134
  "/public/patrons/{patron_id}/guarantors/can_see_checkouts": {
135
    "$ref": "paths/public_patrons.json#/~1public~1patrons~1{patron_id}~1guarantors~1can_see_checkouts"
135
    "$ref": "paths/public_patrons.json#/~1public~1patrons~1{patron_id}~1guarantors~1can_see_checkouts"
136
  },
136
  },
137
  "/quotes": {
138
    "$ref": "paths/quotes.json#/~1quotes"
139
  },
140
  "/quotes/{quote_id}": {
141
    "$ref": "paths/quotes.json#/~1quotes~1{quote_id}"
142
  },
137
  "/return_claims": {
143
  "/return_claims": {
138
    "$ref": "paths/return_claims.json#/~1return_claims"
144
    "$ref": "paths/return_claims.json#/~1return_claims"
139
  },
145
  },
(-)a/api/v1/swagger/paths/quotes.json (+327 lines)
Line 0 Link Here
1
{
2
  "/quotes": {
3
    "get": {
4
      "x-mojo-to": "Quotes#list",
5
      "operationId": "listQuotes",
6
      "tags": [
7
        "quotes"
8
      ],
9
      "produces": [
10
        "application/json"
11
      ],
12
      "parameters": [
13
        {
14
          "name": "quote_id",
15
          "in": "query",
16
          "description": "Case insensitive search on quote id",
17
          "required": false,
18
          "type": "string"
19
        },
20
        {
21
          "name": "source",
22
          "in": "query",
23
          "description": "Case insensitive search on source",
24
          "required": false,
25
          "type": "string"
26
        },
27
        {
28
          "name": "text",
29
          "in": "query",
30
          "description": "Case insensitive search on text",
31
          "required": false,
32
          "type": "string"
33
        },
34
        {
35
          "name": "displayed_on",
36
          "in": "query",
37
          "description": "Case Insensative search on last displayed date",
38
          "required": false,
39
          "type": "string"
40
        },
41
        {
42
          "$ref": "../parameters.json#/match"
43
        },
44
        {
45
          "$ref": "../parameters.json#/order_by"
46
        },
47
        {
48
          "$ref": "../parameters.json#/page"
49
        },
50
        {
51
          "$ref": "../parameters.json#/per_page"
52
        },
53
        {
54
          "$ref": "../parameters.json#/q_param"
55
        },
56
        {
57
          "$ref": "../parameters.json#/q_body"
58
        },
59
        {
60
          "$ref": "../parameters.json#/q_header"
61
        }
62
      ],
63
      "responses": {
64
        "200": {
65
          "description": "A list of quotes",
66
          "schema": {
67
            "type": "array",
68
            "items": {
69
              "$ref": "../definitions.json#/quote"
70
            }
71
          }
72
        },
73
        "403": {
74
          "description": "Access forbidden",
75
          "schema": {
76
            "$ref": "../definitions.json#/error"
77
          }
78
        },
79
        "500": {
80
          "description": "Internal error",
81
          "schema": {
82
            "$ref": "../definitions.json#/error"
83
          }
84
        },
85
        "503": {
86
          "description": "Under maintenance",
87
          "schema": {
88
            "$ref": "../definitions.json#/error"
89
          }
90
        }
91
      },
92
      "x-koha-authorization": {
93
        "permissions": {
94
          "catalogue": "1"
95
        }
96
      }
97
    },
98
    "post": {
99
      "x-mojo-to": "Quotes#add",
100
      "operationId": "addQuote",
101
      "tags": [
102
        "quotes"
103
      ],
104
      "parameters": [
105
        {
106
          "name": "body",
107
          "in": "body",
108
          "description": "A JSON object containing informations about the new quote",
109
          "required": true,
110
          "schema": {
111
            "$ref": "../definitions.json#/quote"
112
          }
113
        }
114
      ],
115
      "produces": [
116
        "application/json"
117
      ],
118
      "responses": {
119
        "201": {
120
          "description": "Quote added",
121
          "schema": {
122
            "$ref": "../definitions.json#/quote"
123
          }
124
        },
125
        "401": {
126
          "description": "Authentication required",
127
          "schema": {
128
            "$ref": "../definitions.json#/error"
129
          }
130
        },
131
        "403": {
132
          "description": "Access forbidden",
133
          "schema": {
134
            "$ref": "../definitions.json#/error"
135
          }
136
        },
137
        "500": {
138
          "description": "Internal error",
139
          "schema": {
140
            "$ref": "../definitions.json#/error"
141
          }
142
        },
143
        "503": {
144
          "description": "Under maintenance",
145
          "schema": {
146
            "$ref": "../definitions.json#/error"
147
          }
148
        }
149
      },
150
      "x-koha-authorization": {
151
        "permissions": {
152
          "tools": "edit_quotes"
153
        }
154
      }
155
    }
156
  },
157
  "/quotes/{quote_id}": {
158
    "get": {
159
      "x-mojo-to": "Quotes#get",
160
      "operationId": "getQuote",
161
      "tags": [
162
        "quotes"
163
      ],
164
      "parameters": [
165
        {
166
          "$ref": "../parameters.json#/quote_id_pp"
167
        }
168
      ],
169
      "produces": [
170
        "application/json"
171
      ],
172
      "responses": {
173
        "200": {
174
          "description": "A Quote",
175
          "schema": {
176
            "$ref": "../definitions.json#/quote"
177
          }
178
        },
179
        "404": {
180
          "description": "Quote not found",
181
          "schema": {
182
            "$ref": "../definitions.json#/error"
183
          }
184
        },
185
        "500": {
186
          "description": "Internal error",
187
          "schema": {
188
            "$ref": "../definitions.json#/error"
189
          }
190
        },
191
        "503": {
192
          "description": "Under maintenance",
193
          "schema": {
194
            "$ref": "../definitions.json#/error"
195
          }
196
        }
197
      },
198
      "x-koha-authorization": {
199
        "permissions": {
200
          "catalogue": "1"
201
        }
202
      }
203
    },
204
    "put": {
205
      "x-mojo-to": "Quotes#update",
206
      "operationId": "updateQuote",
207
      "tags": [
208
        "quotes"
209
      ],
210
      "parameters": [
211
        {
212
          "$ref": "../parameters.json#/quote_id_pp"
213
        },
214
        {
215
          "name": "body",
216
          "in": "body",
217
          "description": "a quote object",
218
          "required": true,
219
          "schema": {
220
            "$ref": "../definitions.json#/quote"
221
          }
222
        }
223
      ],
224
      "produces": [
225
        "application/json"
226
      ],
227
      "responses": {
228
        "200": {
229
          "description": "A quote",
230
          "schema": {
231
            "$ref": "../definitions.json#/quote"
232
          }
233
        },
234
        "401": {
235
          "description": "Authentication required",
236
          "schema": {
237
            "$ref": "../definitions.json#/error"
238
          }
239
        },
240
        "403": {
241
          "description": "Access forbidden",
242
          "schema": {
243
            "$ref": "../definitions.json#/error"
244
          }
245
        },
246
        "404": {
247
          "description": "Quote not found",
248
          "schema": {
249
            "$ref": "../definitions.json#/error"
250
          }
251
        },
252
        "500": {
253
          "description": "Internal error",
254
          "schema": {
255
            "$ref": "../definitions.json#/error"
256
          }
257
        },
258
        "503": {
259
          "description": "Under maintenance",
260
          "schema": {
261
            "$ref": "../definitions.json#/error"
262
          }
263
        }
264
      },
265
      "x-koha-authorization": {
266
        "permissions": {
267
          "tools": "edit_quotes"
268
        }
269
      }
270
    },
271
    "delete": {
272
      "x-mojo-to": "Quotes#delete",
273
      "operationId": "deleteQuote",
274
      "tags": [
275
        "quotes"
276
      ],
277
      "parameters": [
278
        {
279
          "$ref": "../parameters.json#/quote_id_pp"
280
        }
281
      ],
282
      "produces": [
283
        "application/json"
284
      ],
285
      "responses": {
286
        "204": {
287
          "description": "Quote deleted"
288
        },
289
        "401": {
290
          "description": "Authentication required",
291
          "schema": {
292
            "$ref": "../definitions.json#/error"
293
          }
294
        },
295
        "403": {
296
          "description": "Access forbidden",
297
          "schema": {
298
            "$ref": "../definitions.json#/error"
299
          }
300
        },
301
        "404": {
302
          "description": "Quote not found",
303
          "schema": {
304
            "$ref": "../definitions.json#/error"
305
          }
306
        },
307
        "500": {
308
          "description": "Internal error",
309
          "schema": {
310
            "$ref": "../definitions.json#/error"
311
          }
312
        },
313
        "503": {
314
          "description": "Under maintenance",
315
          "schema": {
316
            "$ref": "../definitions.json#/error"
317
          }
318
        }
319
      },
320
      "x-koha-authorization": {
321
        "permissions": {
322
          "tools": "edit_quotes"
323
        }
324
      }
325
    }
326
  }
327
}
(-)a/api/v1/swagger/x-primitives.json (-1 / +5 lines)
Lines 52-57 Link Here
52
    "type": "integer",
52
    "type": "integer",
53
    "description": "internally assigned fund identifier",
53
    "description": "internally assigned fund identifier",
54
    "readOnly": true
54
    "readOnly": true
55
  },
56
  "quote_id": {
57
    "type": "integer",
58
    "description": "internally assigned quote identifier",
59
    "readOnly": true
55
  }
60
  }
56
57
}
61
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes-upload.tt (-379 lines)
Lines 1-379 Link Here
1
[% USE raw %]
2
[% USE Asset %]
3
[% SET footerjs = 1 %]
4
    [% INCLUDE 'doc-head-open.inc' %]
5
    <title>Koha &rsaquo; Tools &rsaquo; Quote uploader</title>
6
    [% INCLUDE 'doc-head-close.inc' %]
7
    [% Asset.css("css/uploader.css") | $raw %]
8
    [% Asset.css("css/quotes.css") | $raw %]
9
</head>
10
11
<body id="tools_quotes" class="tools">
12
[% INCLUDE 'header.inc' %]
13
[% INCLUDE 'cat-search.inc' %]
14
15
<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; <a href="/cgi-bin/koha/tools/quotes.pl">Quote editor</a> &rsaquo; Quote uploader</div>
16
17
<div class="main container-fluid">
18
    <div class="row">
19
        <div class="col-sm-10 col-sm-push-2">
20
            <main>
21
22
                [% INCLUDE 'quotes-upload-toolbar.inc' %]
23
                <h2>Quote uploader</h2>
24
                <div id="instructions">
25
                <fieldset id="file_uploader_help" class="rows">
26
                    <legend>Instructions</legend>
27
                    <div id="file_uploader_inst">
28
                        <ul>
29
                        <li>The quote uploader accepts standard csv files with two columns: "source","text"</li>
30
                        <li>Click the "Choose File" button and select the csv file to be uploaded.</li>
31
                        <li>The file will be imported into an editable table for review prior to saving.</li>
32
                        </ul>
33
                    </div>
34
                    <div id="file_editor_inst">
35
                        <ul>
36
                        <li>Click on any field to edit the contents; Press the &lt;Enter&gt; key to save edit.</li>
37
                        <li>Click on one or more quote numbers to select entire quotes for deletion; Click the 'Delete Quote(s)' button to delete selected quotes.</li>
38
                        <li>Click the 'Save Quotes' button in the toolbar to save the entire batch of quotes.</li>
39
                        </ul>
40
                    </div>
41
                </fieldset>
42
                </div>
43
                <fieldset id="file_uploader" class="rows">
44
                    <legend>Upload quotes</legend>
45
                    <div id="file_upload">
46
                        <input type="file" name="file" />
47
                        <button id="cancel_upload" style="display:none">Cancel upload</button>
48
                        <div id="progress_bar"><div class="percent">0%</div></div>
49
                    </div>
50
                </fieldset>
51
                <table id="quotes_editor" style="visibility: hidden;">
52
                <thead>
53
                    <tr>
54
                        <th>Source</th>
55
                        <th>Text</th>
56
                        <th>Actions</th>
57
                    </tr>
58
                </thead>
59
                <tbody>
60
                    <!-- tbody content is generated by DataTables -->
61
                    <tr>
62
                        <td></td>
63
                        <td>Loading data...</td>
64
                        <td></td>
65
                    </tr>
66
                </tbody>
67
                </table>
68
                <fieldset id="footer" class="action" style="visibility: hidden;">
69
                </fieldset>
70
71
            </main>
72
        </div> <!-- /.col-sm-10.col-sm-push-2 -->
73
74
        <div class="col-sm-2 col-sm-pull-10">
75
            <aside>
76
                [% INCLUDE 'tools-menu.inc' %]
77
            </aside>
78
        </div> <!-- /.col-sm-2.col-sm-pull-10 -->
79
     </div> <!-- /.row -->
80
81
[% MACRO jsinclude BLOCK %]
82
    [% Asset.js("js/tools-menu.js") | $raw %]
83
    [% INCLUDE 'datatables.inc' %]
84
    [% Asset.js("lib/jquery/plugins/jquery.jeditable.mini.js") | $raw %]
85
    <script>
86
        var oTable; //DataTable object
87
        $(document).ready(function() {
88
89
            $("#cancel_upload").on("click",function(e){
90
                e.preventDefault();
91
                fnAbortRead();
92
            });
93
            $("#cancel_quotes").on("click",function(){
94
                return confirm( _("Are you sure you want to cancel this import?") );
95
            });
96
97
        // Credits:
98
        // FileReader() code copied and hacked from:
99
        // http://www.html5rocks.com/en/tutorials/file/dndfiles/
100
        // fnCSVToArray() gratefully borrowed from:
101
        // http://www.bennadel.com/blog/1504-Ask-Ben-Parsing-CSV-Strings-With-Javascript-Exec-Regular-Expression-Command.htm
102
103
        var reader;
104
        var progress = document.querySelector('.percent');
105
        $("#server_response").hide();
106
107
        function yuiGetData() {
108
            fnGetData(document.getElementById('quotes_editor'));
109
        }
110
111
        function fnAbortRead() {
112
            reader.abort();
113
        }
114
115
        function fnErrorHandler(evt) {
116
            switch(evt.target.error.code) {
117
                case evt.target.error.NOT_FOUND_ERR:
118
                    alert(_("File Not Found!"));
119
                    break;
120
                case evt.target.error.NOT_READABLE_ERR:
121
                    alert(_("File is not readable"));
122
                    break;
123
                case evt.target.error.ABORT_ERR:
124
                    break; // noop
125
                default:
126
                    alert(_("An error occurred reading this file."));
127
            };
128
        }
129
130
        function fnUpdateProgress(evt) {
131
            // evt is an ProgressEvent.
132
            if (evt.lengthComputable) {
133
                var percentLoaded = Math.round((evt.loaded / evt.total) * 100);
134
                // Increase the progress bar length.
135
                if (percentLoaded < 100) {
136
                    progress.style.width = percentLoaded + '%';
137
                    progress.textContent = percentLoaded + '%';
138
                }
139
            }
140
        }
141
142
        function fnCSVToArray( strData, strDelimiter ){
143
            // This will parse a delimited string into an array of
144
            // arrays. The default delimiter is the comma, but this
145
            // can be overriden in the second argument.
146
147
            // Check to see if the delimiter is defined. If not,
148
            // then default to comma.
149
            strDelimiter = (strDelimiter || ",");
150
151
            // Create a regular expression to parse the CSV values.
152
            var objPattern = new RegExp(
153
            (
154
                // Delimiters.
155
                "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +
156
                // Quoted fields.
157
                "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +
158
                // Standard fields.
159
                "([^\"\\" + strDelimiter + "\\r\\n]*))"
160
            ),
161
                "gi"
162
            );
163
164
            // Create an array to hold our data. Give the array
165
            // a default empty first row.
166
            var arrData = [[]];
167
168
            // Create an array to hold our individual pattern
169
            // matching groups.
170
            var arrMatches = null;
171
172
            // Keep looping over the regular expression matches
173
            // until we can no longer find a match.
174
            while (arrMatches = objPattern.exec( strData )){
175
176
                // Get the delimiter that was found.
177
                var strMatchedDelimiter = arrMatches[ 1 ];
178
179
                // Check to see if the given delimiter has a length
180
                // (is not the start of string) and if it matches
181
                // field delimiter. If it does not, then we know
182
                // that this delimiter is a row delimiter.
183
                if ( strMatchedDelimiter.length && (strMatchedDelimiter != strDelimiter) ){
184
                    // Since we have reached a new row of data,
185
                    // add an empty row to our data array.
186
                    // Note: if there is not more data, we will have to remove this row later
187
                    arrData.push( [] );
188
                }
189
190
                // Now that we have our delimiter out of the way,
191
                // let's check to see which kind of value we
192
                // captured (quoted or unquoted).
193
                if (arrMatches[ 2 ]){
194
                    // We found a quoted value. When we capture
195
                    // this value, unescape any double quotes.
196
                    var strMatchedValue = arrMatches[ 2 ].replace(
197
                    new RegExp( "\"\"", "g" ),
198
                        "\""
199
                    );
200
                } else if (arrMatches[3]){
201
                    // We found a non-quoted value.
202
                    var strMatchedValue = arrMatches[ 3 ];
203
                } else {
204
                    // There is no more valid data so remove the row we added earlier
205
                    // Is there a better way? Perhaps a look-ahead regexp?
206
                    arrData.splice(arrData.length-1, 1);
207
                }
208
209
                // Now that we have our value string, let's add
210
                // it to the data array.
211
                arrData[ arrData.length - 1 ].push( strMatchedValue );
212
            }
213
214
            // Return the parsed data.
215
            return( arrData );
216
        }
217
218
        function fnDataTable(aaData) {
219
            for(var i=0; i<aaData.length; i++) {
220
                aaData[i].unshift(i+1); // Add a column w/quote number
221
            }
222
223
            /* Transition from the quote file uploader to the quote file editor interface */
224
            $('#toolbar').css("visibility","visible");
225
            $('#toolbar').css("position","");
226
            $('#file_editor_inst').css("visibility", "visible");
227
            $('#file_editor_inst').css("position", "");
228
            $('#file_uploader_inst').css("visibility", "hidden");
229
            $('#save_quotes').css("visibility","visible");
230
            $('#file_uploader').css("visibility","hidden");
231
            $('#file_uploader').css("position","absolute");
232
            $('#file_uploader').css("top","-150px");
233
            $('#quotes_editor').css("visibility","visible");
234
            $("#save_quotes").on("click", yuiGetData);
235
            $("#delete_quote").on("click", fnClickDeleteRow);
236
237
            oTable = $('#quotes_editor').dataTable( {
238
                "bAutoWidth"        : false,
239
                "bPaginate"         : true,
240
                "bSort"             : false,
241
                "sPaginationType"   : "full_numbers",
242
                "sDom": '<"top pager"iflp>rt<"bottom pager"flp><"clear">',
243
                "aaData"            : aaData,
244
                "aoColumns"         : [
245
                    {
246
                        "sTitle"  : _("Number"),
247
                        "sWidth"  : "2%",
248
                    },
249
                    {
250
                        "sTitle"  : _("Source"),
251
                        "sWidth"  : "15%",
252
                    },
253
                    {
254
                        "sTitle"  : _("Quote"),
255
                        "sWidth"  : "83%",
256
                    },
257
                ],
258
               "fnPreDrawCallback": function(oSettings) {
259
                    return true;
260
                },
261
                "fnRowCallback": function( nRow, aData, iDisplayIndex ) {
262
                    /* do foo on various cells in the current row */
263
                    var quoteNum = $('td', nRow)[0].innerHTML;
264
                    $(nRow).attr("id", quoteNum); /* set row ids to quote number */
265
                    $('td:eq(0)', nRow).click(function() {$(this.parentNode).toggleClass('selected',this.clicked);}); /* add row selectors */
266
                    $('td:eq(0)', nRow).attr("title", _("Click ID to select/deselect quote"));
267
                    /* apply no_edit id to noEditFields */
268
                    noEditFields = [0]; /* number */
269
                    for (i=0; i<noEditFields.length; i++) {
270
                        $('td', nRow)[noEditFields[i]].setAttribute("id","no_edit");
271
                    }
272
                    return nRow;
273
                },
274
               "fnDrawCallback": function(oSettings) {
275
                    /* Apply the jEditable handlers to the table on all fields w/o the no_edit id */
276
                    $('#quotes_editor tbody td[id!="no_edit"]').editable( function(value, settings) {
277
                            var cellPosition = oTable.fnGetPosition( this );
278
                            oTable.fnUpdate(value, cellPosition[0], cellPosition[1], false, false);
279
                            return(value);
280
                        },
281
                        {
282
                        "callback"      : function( sValue, y ) {
283
                                              oTable.fnDraw(false); /* no filter/sort or we lose our pagination */
284
                                          },
285
                        "height"        : "14px",
286
                    });
287
               },
288
            });
289
            $('#footer').css("visibility","visible");
290
        }
291
292
        function fnHandleFileSelect(evt) {
293
            // Reset progress indicator on new file selection.
294
            progress.style.width = '0%';
295
            progress.textContent = '0%';
296
297
            reader = new FileReader();
298
            reader.onerror = fnErrorHandler;
299
            reader.onprogress = fnUpdateProgress;
300
            reader.onabort = function(e) {
301
                alert(_("File read cancelled"));
302
                parent.location='quotes-upload.pl';
303
            };
304
            reader.onloadstart = function(e) {
305
                $('#cancel_upload').show();
306
                $('#progress_bar').addClass("loading");
307
            };
308
            reader.onload = function(e) {
309
                // Ensure that the progress bar displays 100% at the end.
310
                progress.style.width = '100%';
311
                progress.textContent = '100%';
312
                $('#cancel_upload').hide();
313
                quotes = fnCSVToArray(e.target.result, ',');
314
                fnDataTable(quotes);
315
            }
316
317
            // perform various sanity checks on the target file prior to uploading...
318
            var fileType = evt.target.files[0].type || 'unknown';
319
            var fileSizeInK = Math.round(evt.target.files[0].size/1024);
320
321
            if (!fileType.match(/comma-separated-values|csv|excel|calc/i)) {
322
                alert(_("Uploads limited to csv. Incorrect filetype: %s").format(fileType));
323
                parent.location='quotes-upload.pl';
324
                return;
325
            }
326
            if (fileSizeInK > 512) {
327
                if (!confirm(_("%s %s KB Do you really want to upload this file?").format(evt.target.files[0].name, fileSizeInK))) {
328
                    parent.location='quotes-upload.pl';
329
                    return;
330
                }
331
            }
332
            // Read in the image file as a text string.
333
            reader.readAsText(evt.target.files[0]);
334
        }
335
336
        $('#file_upload').one('change', fnHandleFileSelect);
337
338
        });
339
340
        function fnGetData(element) {
341
            var jqXHR = $.ajax({
342
                url         : "/cgi-bin/koha/tools/quotes/quotes-upload_ajax.pl",
343
                type        : "POST",
344
                contentType : "application/x-www-form-urlencoded", // we must claim this mimetype or CGI will not decode the URL encoding
345
                dataType    : "json",
346
                data        : {
347
                                "quote"     : encodeURI ( JSON.stringify(oTable.fnGetData()) ),
348
                                "action"    : "add",
349
                              },
350
                success     : function(){
351
                    var response = JSON.parse(jqXHR.responseText);
352
                    if (response.success) {
353
                        alert(_("%s quotes saved.").format(response.records));
354
                        window.location.reload(true);   // is this the best route?
355
                    } else {
356
                        alert(_("%s quotes saved, but an error has occurred. Please ask your administrator to check the server log for more details.").format(response.records));
357
                        window.location.reload(true);   // is this the best route?
358
                    }
359
                  },
360
            });
361
        }
362
363
        function fnClickDeleteRow() {
364
            var idsToDelete = oTable.$('.selected').map(function() {
365
                  return this.id;
366
            }).get().join(', ');
367
            if (!idsToDelete) {
368
                alert(_("Please select a quote(s) by clicking the quote id(s) you desire to delete."));
369
            }
370
            else if (confirm(_("Are you sure you wish to delete quote(s) %s?").format(idsToDelete))) {
371
                oTable.$('.selected').each(function(){
372
                    oTable.fnDeleteRow(this);
373
                });
374
            }
375
        }
376
    </script>
377
[% END %]
378
379
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes.tt (-188 / +189 lines)
Lines 1-10 Link Here
1
[% USE raw %]
1
[% USE raw %]
2
[% USE Asset %]
2
[% USE Asset %]
3
[% SET footerjs = 1 %]
3
[% SET footerjs = 1 %]
4
    [% INCLUDE 'doc-head-open.inc' %]
4
[% INCLUDE 'doc-head-open.inc' %]
5
    <title>Koha &rsaquo; Tools &rsaquo; Quote editor</title>
5
<title>Koha &rsaquo; Tools &rsaquo; Quote editor</title>
6
    [% INCLUDE 'doc-head-close.inc' %]
6
[% INCLUDE 'doc-head-close.inc' %]
7
    [% Asset.css("css/quotes.css") | $raw %]
7
[% Asset.css("css/quotes.css") | $raw %]
8
</head>
8
</head>
9
9
10
<body id="tools_quotes" class="tools">
10
<body id="tools_quotes" class="tools">
Lines 18-60 Link Here
18
        <div class="col-sm-10 col-sm-push-2">
18
        <div class="col-sm-10 col-sm-push-2">
19
            <main>
19
            <main>
20
20
21
                [% INCLUDE 'quotes-toolbar.inc' %]
21
[% FOREACH m IN messages %]
22
                <h2>Quote editor</h2>
22
    <div class="dialog [% m.type | html %]" id="quote_action_result_dialog">
23
                <div id="instructions">
23
        [% SWITCH m.code %]
24
                <fieldset id="quote_editor_help" class="rows">
24
        [% CASE 'error_on_update' %]
25
                    <legend>Instructions</legend>
25
            An error occurred when updating this quote. Perhaps it already exists.
26
                    <div id="quote_editor_inst">
26
        [% CASE 'error_on_insert' %]
27
                        <ul>
27
            An error occurred when adding this quote.
28
                        <li>Click on the 'Add quote' button to add a single quote; Press the &lt;Enter&gt; key to save the quote.<br />
28
        [% CASE 'success_on_update' %]
29
                            <strong>Note: </strong>Both the 'source' and 'text' fields must have content in order for the quote to be saved.</li>
29
            Quote updated successfully.
30
                        <li>Click on any field to edit the contents; Press the &lt;Enter&gt; key to save edit.</li>
30
        [% CASE 'success_on_insert' %]
31
                        <li>Click on one or more quote numbers to select entire quotes for deletion; Click the 'Delete quote(s)' button to delete selected quotes.</li>
31
            Quote added successfully.
32
                        <li>Click the 'Import quotes' button in the toolbar to import a CSV file of quotes.</li>
32
        [% CASE %]
33
                        </ul>
33
            [% m.code | html %]
34
                    </div>
34
        [% END %]
35
                </fieldset>
35
    </div>
36
[% END %]
37
38
    <div class="dialog message" id="quote_delete_success" style="display: none;"></div>
39
    <div class="dialog alert"   id="quote_delete_error"   style="display: none;"></div>
40
41
[% IF op == 'list' %]
42
    <div id="toolbar" class="btn-toolbar">
43
        <a class="btn btn-default" id="newquote" href="/cgi-bin/koha/tools/quotes.pl?op=add_form"><i class="fa fa-plus"></i> New Quote</a>
44
    </div>
45
[% END %]
46
47
[% IF op == 'add_form' %]
48
    <h3>[% IF quote %]Modify quote[% ELSE %]New quote[% END %]</h3>
49
    <form action="/cgi-bin/koha/tools/quotes.pl" id="Aform" name="Aform" class="validated" method="post">
50
        <fieldset class="rows">
51
            <input type="hidden" name="op" value="add_validate" />
52
            <ol>
53
                <li>
54
                    <label for="text" class="required">Source: </label>
55
                    <input type="text" name="source" id="source" value="[% quote.source | html %]" class="required" required="required" />
56
                    <span class="required">Required</span>
57
                </li>
58
                <li>
59
                    <label for="text" class="required">text: </label>
60
                    <textarea name="text" id="text" class="required" required="required" />[% quote.text | html %]</textarea>
61
                    <span class="required">Required</span>
62
                </li>
63
            </ol>
64
        </fieldset>
65
        <fieldset class="action">
66
            <input type="hidden" name="id" value="[% quote.id %]" />
67
            <input type="submit" value="Submit" />
68
            <a class="cancel" href="/cgi-bin/koha/tools/quotes.pl">Cancel</a>
69
        </fieldset>
70
    </form>
71
[% END %]
72
73
[% IF op == 'delete_confirm' %]
74
    <div class="dialog alert">
75
        <form action="/cgi-bin/koha/tools/quotes.pl" method="post">
76
            <h3>Are you sure you want to delete the following quote?</h3>
77
            [% quote.source | html %] - [% quote.text | html %]
78
            <input type="hidden" name="op" value="delete_confirmed" />
79
            <input type="hidden" name="id" value="[% quote.id | html %]" />
80
            <button type="submit" class="approve"><i class="fa fa-fw fa-check"></i> Yes, delete</button>
81
        </form>
82
        <form action="/cgi-bin/koha/tools/quotes.pl" method="get">
83
            <button type="submit" class="deny"><i class="fa fa-fw fa-remove"></i> No, do not delete</button>
84
        </form>
85
    </div>
86
[% END %]
87
88
[% IF op == 'list' %]
89
    <h3>Quotes</h3>
90
    [% IF quotes_count > 0 %]
91
        <table id="quotes">
92
            <thead>
93
                <tr>
94
                    <th>ID</th>
95
                    <th>Source</th>
96
                    <th>Text</th>
97
                    <th>Last display</th>
98
                    <th data-class-name="actions">Actions</th>
99
                </tr>
100
            </thead>
101
        </table>
102
    [% ELSE %]
103
        <div class="dialog message">There are no quotes defined. <a href="/cgi-bin/koha/tools/quotes.pl?op=add_form">Start defining quotes</a>.</div>
104
    [% END %]
105
106
    <div id="delete_confirm_modal" class="modal" tabindex="-1" role="dialog" aria-labelledby="delete_confirm_modal_label" aria-hidden="true">
107
        <div class="modal-dialog">
108
            <div class="modal-content">
109
                <div class="modal-header">
110
                    <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button>
111
                    <h3 id="delete_confirm_modal_label">Delete quote</h3>
36
                </div>
112
                </div>
37
                <table id="quotes_editor">
113
                <div class="modal-body">
38
                <thead>
114
                    <div id="delete_confirm_dialog"></div>
39
                    <tr>
115
                </div>
40
                        <th><span style="cursor: help" id="id_help">ID</span></th>
116
                <div class="modal-footer">
41
                        <th>Source</th>
117
                    <a href="#" class="btn btn-default" id="delete_confirm_modal_button" role="button" data-toggle="modal">Delete</a>
42
                        <th>Text</th>
118
                    <button class="btn btn-default" data-dismiss="modal" aria-hidden="true">Close</button>
43
                        <th>Last displayed</th>
119
                </div>
44
                    </tr>
120
            </div> <!-- /.modal-content -->
45
                </thead>
121
        </div> <!-- /.modal-dialog -->
46
                <tbody>
122
    </div> <!-- #delete_confirm_modal -->
47
                    <!-- tbody content is generated by DataTables -->
123
[% END %]
48
                    <tr>
49
                        <td></td>
50
                        <td></td>
51
                        <td>Loading data...</td>
52
                        <td></td>
53
                    </tr>
54
                </tbody>
55
                </table>
56
                <fieldset id="footer" class="action">
57
                </fieldset>
58
124
59
            </main>
125
            </main>
60
        </div> <!-- /.col-sm-10.col-sm-push-2 -->
126
        </div> <!-- /.col-sm-10.col-sm-push-2 -->
Lines 68-231 Link Here
68
134
69
[% MACRO jsinclude BLOCK %]
135
[% MACRO jsinclude BLOCK %]
70
    [% Asset.js("js/tools-menu.js") | $raw %]
136
    [% Asset.js("js/tools-menu.js") | $raw %]
137
    [% INCLUDE 'js-date-format.inc' %]
71
    [% INCLUDE 'datatables.inc' %]
138
    [% INCLUDE 'datatables.inc' %]
72
    [% Asset.js("lib/jquery/plugins/dataTables.fnReloadAjax.js") | $raw %]
139
73
    [% Asset.js("lib/jquery/plugins/jquery.jeditable.mini.js") | $raw %]
74
    <script>
140
    <script>
75
        var oTable; /* oTable needs to be global */
76
        var sEmptyTable = _("No quotes available. Please use the 'Add quote' button to add a quote."); /* override the default message in datatables.inc */
77
        $(document).ready(function() {
141
        $(document).ready(function() {
78
            /* NOTE: This is an ajax-source datatable and *not* a server-side sourced datatable. */
142
79
            /* See the datatable docs if you don't understand this difference. */
143
            var quotes_url = '/api/v1/quotes';
80
            oTable = $("#quotes_editor").dataTable({
144
            var quotes = $("#quotes").api({
81
                "bAutoWidth"        : false,
145
                "ajax": {
82
                "bProcessing"       : true,
146
                    "url": quotes_url
83
                "bPaginate"         : true,
84
                "sPaginationType"   : "full_numbers",
85
                "sDom": '<"top pager"iflp>rt<"bottom pager"flp><"clear">',
86
                "sAjaxSource"       : "/cgi-bin/koha/tools/quotes/quotes_ajax.pl",
87
                "aoColumns"         : [
88
                                        { "sWidth": "3%"  },
89
                                        { "sWidth": "11%" },
90
                                        { "sWidth": "75%" },
91
                                        { "sWidth": "11%" },
92
                                      ],
93
               "oLanguage": dataTablesDefaults.oLanguage,
94
               "fnPreDrawCallback": function(oSettings) {
95
                    return true;
96
                },
147
                },
97
                "fnRowCallback": function( nRow, aData, iDisplayIndex ) {
148
                'emptyTable': '<div class="dialog message">'+_("There are no quotes defined.")+' <a href="/cgi-bin/koha/tools/quotes.pl?op=add_form">'+_("Start defining quotes")+'</a>.</div>',
98
                    /* do foo on the current row and its child nodes */
149
                "columnDefs": [ {
99
                    var noEditFields = [];
150
                    "targets": [0,1,2,3],
100
                    var quoteID = $('td', nRow)[0].innerHTML;
151
                    "render": function (data, type, row, meta) {
101
                    $(nRow).attr("id", quoteID); /* set row ids to quote id */
152
                        if ( type == 'display' ) {
102
                    $('td:eq(0)', nRow).click(function() {$(this.parentNode).toggleClass('selected',this.clicked);}); /* add row selectors */
153
                            if ( data != null ) {
103
                    $('td:eq(0)', nRow).attr("title", _("Click ID to select/deselect quote"));
154
                                return data.escapeHtml();
104
                    $('td', nRow).attr("id",quoteID); /* FIXME: this is a bit of a hack */
155
                            }
105
                    if (isNaN(quoteID)) {
156
                            else {
106
                        noEditFields = [0,1,2,3]; /* all fields when adding a quote */
157
                                return "";
107
                    } else {
158
                            }
108
                        noEditFields = [0,3]; /* id, timestamp */
159
                        }
109
                    }
160
                        return data;
110
                    /* apply no_edit id to noEditFields */
111
                    for (i=0; i<noEditFields.length; i++) {
112
                        $('td', nRow)[noEditFields[i]].setAttribute("id","no_edit");
113
                    }
161
                    }
114
                    return nRow;
162
                } ],
115
                },
163
                "columns": [
116
               "fnDrawCallback": function(oSettings) {
164
                    {
117
                    /* Apply the jEditable handlers to the table on all fields w/o the no_edit id */
165
                        "data": "quote_id",
118
                    $('#quotes_editor tbody td[id!="no_edit"]').editable( "/cgi-bin/koha/tools/quotes/quotes_ajax.pl", {
166
                        "searchable": true,
119
                        "submitdata"    : function ( value, settings ) {
167
                        "orderable": true
120
                                              return {
168
                    },
121
                                                  "column"        : oTable.fnGetPosition( this )[2],
169
                    {
122
                                                  "action"        : "edit",
170
                        "data": "source",
123
                                              };
171
                        "searchable": true,
124
                                          },
172
                        "orderable": true
125
                        "placeholder"   : "Saving data...",
173
                    },
126
                    });
174
                    {
127
               },
175
                        "data": "text",
128
            });
176
                        "searchable": true,
129
            $("#add_quote").click(function(){
177
                        "orderable": true
130
                fnClickAddRow();
178
                    },
131
                return false;
179
                    {
132
            });
180
                        "data": function( row, type, val, meta ) {
133
            $("#delete_quote").click(function(){
181
                            return $datetime(row.displayed_on);
134
                fnClickDeleteRow();
182
                        },
135
                return false;
183
                        "searchable": true,
136
            });
184
                        "orderable": true
137
            $("#id_help").on("click",function(e){
185
                    },
138
                e.stopPropagation();
186
                    {
139
                alert( _("Click on the quote's id to select or deselect the quote. Multiple quotes may be selected.") );
187
                        "data": function( row, type, val, meta ) {
140
            });
141
        });
142
188
143
        function fnClickAddQuote(e, node) {
189
                            var result = '<a class="btn btn-default btn-xs" href="/cgi-bin/koha/tools/quotes.pl?op=add_form&amp;id='+encodeURIComponent(row.quote_id)+'" role="button"><i class="fa fa-pencil" aria-hidden="true"></i> '+_("Edit")+'</a>';
144
            if (e.charCode) {
190
                            result += '<form action="/cgi-bin/koha/tools/quotes.pl" method="post">';
145
                /* some browsers only give charCode, so this will need to be */
191
                            result += '<input type="hidden" name="id" value="'+row.quote_id.escapeHtml()+'" />'+"\n";
146
                /* fixed up to handle that */
192
147
                console.log('charCode: '+e.charCode);
193
                            result += '<a class="btn btn-default btn-xs delete_quote" role="button" href="#" data-toggle="modal" data-target="#delete_confirm_modal" data-quote-id="'+ encodeURIComponent(row.quote_id) +'"><i class="fa fa-trash" aria-hidden="true"></i>'+_("Delete")+'</a>';
148
            }
194
149
            if (e.keyCode == 13) {
195
                            return result;
150
                var quoteSource = $('#quoteSource').val();
151
                var quoteText = $('#quoteText').val()
152
                /* If passed a quote source, add the quote to the db */
153
                if (quoteSource && quoteText) {
154
                    $.ajax({
155
                        url: "/cgi-bin/koha/tools/quotes/quotes_ajax.pl",
156
                        type: "POST",
157
                        data: {
158
                            "source"    : quoteSource,
159
                            "text"      : quoteText,
160
                            "action"    : "add",
161
                        },
196
                        },
162
                        success: function(data){
197
                        "searchable": false,
163
                            var newQuote = data[0];
198
                        "orderable": false
164
                            var aRow = oTable.fnUpdate(
199
                    },
165
                                newQuote,
200
                ]
166
                                node,
201
            });
167
                                undefined,
202
168
                                false,
203
            $('#quotes').on( "click", '.delete_quote', function () {
169
                                false
204
                var quote_id   = decodeURIComponent($(this).data('quote-id'));
170
                            );
205
171
                            oTable.fnPageChange( 'last' );
206
                $("#delete_confirm_dialog").html(
172
                            $('.add_quote_button').attr('onclick', 'fnClickAddRow()'); // re-enable add button
207
                    _("You are about to delete the quote #%s.").format(quote_id)
173
                        }
208
                );
174
                    });
209
175
                } else {
210
                $("#delete_confirm_modal_button").unbind("click").on( "click", function () {
176
                    alert(_("Please supply both the text and source of the quote before saving."));
177
                }
178
            } else if (e.keyCode == 27) {
179
                if (confirm(_("Are you sure you want to cancel adding this quote?"))) {
180
                    oTable.fnDeleteRow(node);
181
                } else {
182
                    return;
183
                }
184
            }
185
        }
186
187
        function fnClickAddRow() {
188
            $('.add_quote_button').removeAttr('onclick'); // disable add button once it has been clicked
189
            var aRow = oTable.fnAddData(
190
                [
191
                    'NA', // this is hackish to fool the datatable sort routine into placing this row at the end of the list...
192
                    '<input id="quoteSource" type="text" style="width:99%" onkeydown="fnClickAddQuote(event,this.parentNode.parentNode)"/>',
193
                    '<input id="quoteText" type="text" style="width:99%" onkeydown="fnClickAddQuote(event,this.parentNode.parentNode)"/>',
194
                    '0000-00-00 00:00:00',
195
                ],
196
                false
197
            );
198
            oTable.fnPageChange( 'last' );
199
            $('#quoteSource').focus();
200
        }
201
202
        function fnClickDeleteRow() {
203
            var idsToDelete = oTable.$('.selected').map(function() {
204
                  return this.id;
205
            }).get().join(', ');
206
            if (!idsToDelete) {
207
                alert(_("Please select a quote(s) by clicking the quote id(s) you desire to delete."));
208
            } else if (confirm(_("Are you sure you wish to delete quote(s) %s?").format(idsToDelete))) {
209
                oTable.$('.selected').each(function(){
210
                    var quoteID = $(this).attr('id');
211
                    $.ajax({
211
                    $.ajax({
212
                        url: "/cgi-bin/koha/tools/quotes/quotes_ajax.pl",
212
                        method: "DELETE",
213
                        type: "POST",
213
                        url: "/api/v1/quotes/"+quote_id
214
                        data: {
214
                    }).success(function() {
215
                                "id"        : quoteID,
215
                        $("#delete_confirm_modal").modal('hide');
216
                                "action"    : "delete",
216
                        quotes.api().ajax.reload(function (data) {
217
                        },
217
                            if (data.recordsTotal == 0) {
218
                        /* Delete the row from the datatable */
218
                                $("#quotes").hide();
219
                        success: function(){
219
                            }
220
                            oTable.fnDeleteRow(this);
220
                            $("#quote_action_result_dialog").hide();
221
                            oTable.fnReloadAjax(null, null, true);
221
                            $("#quote_delete_success").html(_("Quote #%s deleted successfully.").format(quote_id)).show();
222
                        }
222
                        });
223
                    }).error(function () {
224
                        $("#quote_delete_error").html(_("Error deleting quote #%s. Check the logs.").format(quote_id)).show();
223
                    });
225
                    });
224
                });
226
                });
225
            } else {
227
            });
226
                return;
228
227
            }
229
        });
228
        }
229
    </script>
230
    </script>
230
[% END %]
231
[% END %]
231
232
(-)a/tools/quotes-upload.pl (-42 lines)
Lines 1-42 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
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
22
use CGI qw ( -utf8 );
23
use autouse 'Data::Dumper' => qw(Dumper);
24
25
use C4::Auth;
26
use C4::Koha;
27
use C4::Context;
28
use C4::Output;
29
30
my $cgi = CGI->new;
31
32
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
33
    {
34
        template_name   => "tools/quotes-upload.tt",
35
        query           => $cgi,
36
        type            => "intranet",
37
        flagsrequired   => { tools => 'edit_quotes' },
38
        debug           => 1,
39
    }
40
);
41
42
output_html_with_http_headers $cgi, $cookie, $template->output;
(-)a/tools/quotes.pl (-11 / +66 lines)
Lines 1-7 Link Here
1
#!/usr/bin/perl
1
#!/usr/bin/perl
2
2
3
# Copyright 2012 Foundations Bible College Inc.
4
#
5
# This file is part of Koha.
3
# This file is part of Koha.
6
#
4
#
7
# Koha is free software; you can redistribute it and/or modify it
5
# Koha is free software; you can redistribute it and/or modify it
Lines 20-42 Link Here
20
use Modern::Perl;
18
use Modern::Perl;
21
19
22
use CGI qw ( -utf8 );
20
use CGI qw ( -utf8 );
23
use autouse 'Data::Dumper' => qw(Dumper);
21
use Try::Tiny;
24
22
25
use C4::Auth;
23
use C4::Auth;
26
use C4::Koha;
27
use C4::Context;
24
use C4::Context;
28
use C4::Output;
25
use C4::Output;
26
use Koha::Quotes;
29
27
30
my $cgi = CGI->new;
28
my $input = CGI->new;
31
29
32
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
30
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
33
    {
31
    {
34
        template_name   => "tools/quotes.tt",
32
        template_name => "tools/quotes.tt",
35
        query           => $cgi,
33
        query         => $input,
36
        type            => "intranet",
34
        type          => "intranet",
37
        flagsrequired   => { tools => 'edit_quotes' },
35
        flagsrequired => { tools => 'edit_quotes' },
38
        debug           => 1,
36
        debug         => 1,
39
    }
37
    }
40
);
38
);
41
39
42
output_html_with_http_headers $cgi, $cookie, $template->output;
40
my $id = $input->param('id');
41
my $op = $input->param('op') || 'list';
42
my @messages;
43
44
if ( $op eq 'add_form' ) {
45
    $template->param( quote => Koha::Quotes->find($id), );
46
}
47
elsif ( $op eq 'add_validate' ) {
48
    my @fields = qw(
49
      source
50
      text
51
    );
52
53
    if ($id) {
54
        my $quote = Koha::Quotes->find($id);
55
        for my $field (@fields) {
56
            $quote->$field( scalar $input->param($field) );
57
        }
58
59
        try {
60
            $quote->store;
61
            push @messages, { type => 'message', code => 'success_on_update' };
62
        }
63
        catch {
64
            push @messages, { type => 'alert', code => 'error_on_update' };
65
        }
66
    }
67
    else {
68
        my $quote = Koha::Quote->new(
69
            {
70
                id => $id,
71
                ( map { $_ => scalar $input->param($_) || undef } @fields )
72
            }
73
        );
74
75
        try {
76
            $quote->store;
77
            push @messages, { type => 'message', code => 'success_on_insert' };
78
        }
79
        catch {
80
            push @messages, { type => 'alert', code => 'error_on_insert' };
81
        };
82
    }
83
    $op = 'list';
84
}
85
else {
86
    $op = 'list';
87
}
88
89
$template->param( quotes_count => Koha::Quotes->search->count )
90
  if $op eq 'list';
91
92
$template->param(
93
    messages => \@messages,
94
    op       => $op,
95
);
96
97
output_html_with_http_headers $input, $cookie, $template->output;
(-)a/tools/quotes/quotes-upload_ajax.pl (-65 lines)
Lines 1-65 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
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
22
use CGI qw ( -utf8 );
23
use JSON;
24
use URI::Escape;
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 = CGI->new;
33
my $dbh = C4::Context->dbh;
34
35
my ( $status, $cookie, $sessionID ) = C4::Auth::check_api_auth( $cgi, { tools => 'edit_quotes' } );
36
unless ($status eq "ok") {
37
    print $cgi->header(-type => 'application/json', -status => '403 Forbidden');
38
    print to_json({ auth_status => $status });
39
    exit 0;
40
}
41
42
my $success = 'true';
43
my $quotes_tmp = uri_unescape( $cgi->param('quote' ) );
44
my $quotes = decode_json( $quotes_tmp );
45
46
my $action = $cgi->param('action');
47
48
my $sth = $dbh->prepare('INSERT INTO quotes (source, text) VALUES (?, ?);');
49
50
my $insert_count = 0;
51
52
foreach my $quote (@$quotes) {
53
    $insert_count++ if $sth->execute($quote->[1], $quote->[2]);
54
    if ($sth->err) {
55
        warn sprintf('Database returned the following error: %s', $sth->errstr);
56
        $success = 'false';
57
    }
58
}
59
60
print $cgi->header('application/json');
61
62
print to_json({
63
                success => $success,
64
                records => $insert_count,
65
});
(-)a/tools/quotes/quotes_ajax.pl (-110 lines)
Lines 1-109 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
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
22
use CGI qw ( -utf8 );
23
use JSON;
24
use autouse 'Data::Dumper' => qw(Dumper);
25
26
use C4::Auth;
27
use C4::Context;
28
29
my $cgi = CGI->new;
30
my $dbh = C4::Context->dbh;
31
my $sort_columns = ["id", "source", "text", "timestamp"];
32
33
my ( $status, $cookie, $sessionID ) = C4::Auth::check_api_auth( $cgi, { tools => 'edit_quotes' } );
34
unless ($status eq "ok") {
35
    print $cgi->header(-type => 'application/json', -status => '403 Forbidden');
36
    print to_json({ auth_status => $status });
37
    exit 0;
38
}
39
40
# NOTE: This is a collection of ajax functions for use with tools/quotes.pl
41
42
my $params = $cgi->Vars; # NOTE: Multivalue parameters NOT allowed!!
43
44
print $cgi->header('application/json; charset=utf-8');
45
46
my $action = $params->{'action'} || 'get';
47
if ($action eq 'add') {
48
    my $sth = $dbh->prepare('INSERT INTO quotes (source, text) VALUES (?, ?);');
49
    $sth->execute($params->{'source'}, $params->{'text'});
50
    if ($sth->err) {
51
        warn sprintf('Database returned the following error: %s', $sth->errstr);
52
        exit 0;
53
    }
54
    my $new_quote_id = $dbh->{q{mysql_insertid}}; # ALERT: mysqlism here
55
    $sth = $dbh->prepare('SELECT * FROM quotes WHERE id = ?;');
56
    $sth->execute($new_quote_id);
57
    print to_json($sth->fetchall_arrayref, {utf8 =>1});
58
    exit 0;
59
}
60
elsif ($action eq 'edit') {
61
    my $aaData = [];
62
    my $editable_columns = [qw(source text)]; # pay attention to element order; these columns match the quotes table columns
63
    my $sth = $dbh->prepare("UPDATE quotes SET $editable_columns->[$params->{'column'}-1]  = ? WHERE id = ?;");
64
    $sth->execute($params->{'value'}, $params->{'id'});
65
    if ($sth->err) {
66
        warn sprintf('Database returned the following error: %s', $sth->errstr);
67
        exit 1;
68
    }
69
    $sth = $dbh->prepare("SELECT $editable_columns->[$params->{'column'}-1] FROM quotes WHERE id = ?;");
70
    $sth->execute($params->{'id'});
71
    $aaData = $sth->fetchrow_array();
72
    print Encode::encode('utf8', $aaData);
73
74
    exit 0;
75
}
76
elsif ($action eq 'delete') {
77
    my $sth = $dbh->prepare("DELETE FROM quotes WHERE id = ?;");
78
    $sth->execute($params->{'id'});
79
    if ($sth->err) {
80
        warn sprintf('Database returned the following error: %s', $sth->errstr);
81
        exit 1;
82
    }
83
    exit 0;
84
}
85
else {
86
    my $aaData = [];
87
    my $iTotalRecords = '';
88
    my $sth = '';
89
90
    $iTotalRecords = $dbh->selectrow_array('SELECT count(*) FROM quotes;');
91
    $sth = $dbh->prepare("SELECT * FROM quotes;");
92
93
    $sth->execute();
94
    if ($sth->err) {
95
        warn sprintf('Database returned the following error: %s', $sth->errstr);
96
        exit 1;
97
    }
98
99
    $aaData = $sth->fetchall_arrayref;
100
    my $iTotalDisplayRecords = $iTotalRecords; # no filtering happening here
101
102
103
    print to_json({
104
                    iTotalRecords       =>  $iTotalRecords,
105
                    iTotalDisplayRecords=>  $iTotalDisplayRecords,
106
                    sEcho               =>  $params->{'sEcho'},
107
                    aaData              =>  $aaData,
108
                  }, {utf8 =>1});
109
}
110
- 

Return to bug 27251