Bugzilla – Attachment 114477 Details for
Bug 27251
Rewrite the QOTD editor using the Koha REST API
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 27251: Rewrite QOTD with the Koha REST API
Bug-27251-Rewrite-QOTD-with-the-Koha-REST-API.patch (text/plain), 62.71 KB, created by
Jonathan Druart
on 2020-12-17 13:24:42 UTC
(
hide
)
Description:
Bug 27251: Rewrite QOTD with the Koha REST API
Filename:
MIME Type:
Creator:
Jonathan Druart
Created:
2020-12-17 13:24:42 UTC
Size:
62.71 KB
patch
obsolete
>From d33db4f2ef3f664b1063ec8d655d601a1b184bef Mon Sep 17 00:00:00 2001 >From: Jonathan Druart <jonathan.druart@bugs.koha-community.org> >Date: Wed, 16 Dec 2020 12:34:43 +0100 >Subject: [PATCH] Bug 27251: Rewrite QOTD with the Koha REST API > >This patch replace the QOTD editor with our new way to CRUD the >adminitration page (like libraries and STMP servers) > >Test plan: >Play with the QOTD by adding, removing, updating quotes >Try to find bugs :) >--- > Koha/Quote.pm | 18 +- > Koha/REST/V1/Quotes.pm | 139 +++++++ > api/v1/swagger/definitions.json | 3 + > api/v1/swagger/definitions/quote.json | 22 + > api/v1/swagger/parameters.json | 3 + > api/v1/swagger/parameters/quote.json | 9 + > api/v1/swagger/paths.json | 6 + > api/v1/swagger/paths/quotes.json | 327 +++++++++++++++ > api/v1/swagger/x-primitives.json | 6 +- > .../prog/en/modules/tools/quotes-upload.tt | 379 ------------------ > .../prog/en/modules/tools/quotes.tt | 377 ++++++++--------- > tools/quotes-upload.pl | 42 -- > tools/quotes.pl | 77 +++- > tools/quotes/quotes-upload_ajax.pl | 65 --- > tools/quotes/quotes_ajax.pl | 109 ----- > 15 files changed, 786 insertions(+), 796 deletions(-) > create mode 100644 Koha/REST/V1/Quotes.pm > create mode 100644 api/v1/swagger/definitions/quote.json > create mode 100644 api/v1/swagger/parameters/quote.json > create mode 100644 api/v1/swagger/paths/quotes.json > delete mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes-upload.tt > delete mode 100755 tools/quotes-upload.pl > delete mode 100755 tools/quotes/quotes-upload_ajax.pl > delete mode 100755 tools/quotes/quotes_ajax.pl > >diff --git a/Koha/Quote.pm b/Koha/Quote.pm >index acb844d3af..49868aa2a3 100644 >--- a/Koha/Quote.pm >+++ b/Koha/Quote.pm >@@ -33,6 +33,22 @@ Koha::Quote - Koha Quote object class > > =cut > >+=head3 to_api_mapping >+ >+This method returns the mapping for representing a Koha::Quote object >+on the API. >+ >+=cut >+ >+sub to_api_mapping { >+ return { >+ id => 'quote_id', >+ source => 'source', >+ text => 'text', >+ timestamp => 'displayed_on', >+ }; >+} >+ > =head3 _type > > =cut >@@ -41,4 +57,4 @@ sub _type { > return 'Quote'; > } > >-1; >\ No newline at end of file >+1; >diff --git a/Koha/REST/V1/Quotes.pm b/Koha/REST/V1/Quotes.pm >new file mode 100644 >index 0000000000..49c6e4cab6 >--- /dev/null >+++ b/Koha/REST/V1/Quotes.pm >@@ -0,0 +1,139 @@ >+package Koha::REST::V1::Quotes; >+ >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha is distributed in the hope that it will be useful, but >+# WITHOUT ANY WARRANTY; without even the implied warranty of >+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with Koha; if not, see <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use Mojo::Base 'Mojolicious::Controller'; >+ >+use Koha::Quotes; >+ >+use Try::Tiny; >+ >+=head1 API >+ >+=head2 Methods >+ >+=head3 list >+ >+=cut >+ >+sub list { >+ my $c = shift->openapi->valid_input or return; >+ >+ return try { >+ my $quotes_set = Koha::Quotes->new; >+ my $quotes = $c->objects->search( $quotes_set ); >+ return $c->render( status => 200, openapi => $quotes ); >+ } >+ catch { >+ $c->unhandled_exception($_); >+ }; >+ >+} >+ >+=head3 get >+ >+=cut >+ >+sub get { >+ my $c = shift->openapi->valid_input or return; >+ >+ return try { >+ my $quote = Koha::Quotes->find( $c->validation->param('quote_id') ); >+ unless ($quote) { >+ return $c->render( status => 404, >+ openapi => { error => "quote not found" } ); >+ } >+ >+ return $c->render( status => 200, openapi => $quote->to_api ); >+ } >+ catch { >+ $c->unhandled_exception($_); >+ } >+} >+ >+=head3 add >+ >+=cut >+ >+sub add { >+ my $c = shift->openapi->valid_input or return; >+ >+ return try { >+ my $quote = Koha::Quote->new_from_api( $c->validation->param('body') ); >+ $quote->store; >+ $c->res->headers->location( $c->req->url->to_string . '/' . $quote->id ); >+ return $c->render( >+ status => 201, >+ openapi => $quote->to_api >+ ); >+ } >+ catch { >+ $c->unhandled_exception($_); >+ }; >+} >+ >+=head3 update >+ >+=cut >+ >+sub update { >+ my $c = shift->openapi->valid_input or return; >+ >+ my $quote = Koha::Quotes->find( $c->validation->param('quote_id') ); >+ >+ if ( not defined $quote ) { >+ return $c->render( status => 404, >+ openapi => { error => "Object not found" } ); >+ } >+ >+ return try { >+ $quote->set_from_api( $c->validation->param('body') ); >+ $quote->store(); >+ return $c->render( status => 200, openapi => $quote->to_api ); >+ } >+ catch { >+ $c->unhandled_exception($_); >+ }; >+} >+ >+=head3 delete >+ >+=cut >+ >+sub delete { >+ my $c = shift->openapi->valid_input or return; >+ >+ my $quote = Koha::Quotes->find( $c->validation->param('quote_id') ); >+ if ( not defined $quote ) { >+ return $c->render( status => 404, >+ openapi => { error => "Object not found" } ); >+ } >+ >+ return try { >+ $quote->delete; >+ return $c->render( >+ status => 204, >+ openapi => q{} >+ ); >+ } >+ catch { >+ $c->unhandled_exception($_); >+ }; >+} >+ >+1; >diff --git a/api/v1/swagger/definitions.json b/api/v1/swagger/definitions.json >index e35054a456..bc844bdc8f 100644 >--- a/api/v1/swagger/definitions.json >+++ b/api/v1/swagger/definitions.json >@@ -65,6 +65,9 @@ > "patron_balance": { > "$ref": "definitions/patron_balance.json" > }, >+ "quote": { >+ "$ref": "definitions/quote.json" >+ }, > "allows_renewal": { > "$ref": "definitions/allows_renewal.json" > }, >diff --git a/api/v1/swagger/definitions/quote.json b/api/v1/swagger/definitions/quote.json >new file mode 100644 >index 0000000000..8033f2af66 >--- /dev/null >+++ b/api/v1/swagger/definitions/quote.json >@@ -0,0 +1,22 @@ >+{ >+ "type": "object", >+ "properties": { >+ "quote_id": { >+ "$ref": "../x-primitives.json#/quote_id" >+ }, >+ "source": { >+ "description": "source of the quote", >+ "type": "string" >+ }, >+ "text": { >+ "description": "text", >+ "type": ["string", "null"] >+ }, >+ "displayed_on": { >+ "description": "Last display date", >+ "type": ["string", "null"] >+ } >+ }, >+ "additionalProperties": false, >+ "required": ["quote_id", "source", "text"] >+} >diff --git a/api/v1/swagger/parameters.json b/api/v1/swagger/parameters.json >index ddced814d0..8fc892a6af 100644 >--- a/api/v1/swagger/parameters.json >+++ b/api/v1/swagger/parameters.json >@@ -32,6 +32,9 @@ > "order_id_pp": { > "$ref": "parameters/order.json#/order_id_pp" > }, >+ "quote_id_pp": { >+ "$ref": "parameters/quote.json#/quote_id_pp" >+ }, > "smtp_server_id_pp": { > "$ref": "parameters/smtp_server.json#/smtp_server_id_pp" > }, >diff --git a/api/v1/swagger/parameters/quote.json b/api/v1/swagger/parameters/quote.json >new file mode 100644 >index 0000000000..c7f36fcbc3 >--- /dev/null >+++ b/api/v1/swagger/parameters/quote.json >@@ -0,0 +1,9 @@ >+{ >+ "quote_id_pp": { >+ "name": "quote_id", >+ "in": "path", >+ "description": "Quote internal identifier", >+ "required": true, >+ "type": "integer" >+ } >+} >diff --git a/api/v1/swagger/paths.json b/api/v1/swagger/paths.json >index 3b5dc352d5..e046c57a22 100644 >--- a/api/v1/swagger/paths.json >+++ b/api/v1/swagger/paths.json >@@ -134,6 +134,12 @@ > "/public/patrons/{patron_id}/guarantors/can_see_checkouts": { > "$ref": "paths/public_patrons.json#/~1public~1patrons~1{patron_id}~1guarantors~1can_see_checkouts" > }, >+ "/quotes": { >+ "$ref": "paths/quotes.json#/~1quotes" >+ }, >+ "/quotes/{quote_id}": { >+ "$ref": "paths/quotes.json#/~1quotes~1{quote_id}" >+ }, > "/return_claims": { > "$ref": "paths/return_claims.json#/~1return_claims" > }, >diff --git a/api/v1/swagger/paths/quotes.json b/api/v1/swagger/paths/quotes.json >new file mode 100644 >index 0000000000..ac2a503fe0 >--- /dev/null >+++ b/api/v1/swagger/paths/quotes.json >@@ -0,0 +1,327 @@ >+{ >+ "/quotes": { >+ "get": { >+ "x-mojo-to": "Quotes#list", >+ "operationId": "listQuotes", >+ "tags": [ >+ "quotes" >+ ], >+ "produces": [ >+ "application/json" >+ ], >+ "parameters": [ >+ { >+ "name": "quote_id", >+ "in": "query", >+ "description": "Case insensitive search on quote id", >+ "required": false, >+ "type": "string" >+ }, >+ { >+ "name": "source", >+ "in": "query", >+ "description": "Case insensitive search on source", >+ "required": false, >+ "type": "string" >+ }, >+ { >+ "name": "text", >+ "in": "query", >+ "description": "Case insensitive search on text", >+ "required": false, >+ "type": "string" >+ }, >+ { >+ "name": "displayed_on", >+ "in": "query", >+ "description": "Case Insensative search on last displayed date", >+ "required": false, >+ "type": "string" >+ }, >+ { >+ "$ref": "../parameters.json#/match" >+ }, >+ { >+ "$ref": "../parameters.json#/order_by" >+ }, >+ { >+ "$ref": "../parameters.json#/page" >+ }, >+ { >+ "$ref": "../parameters.json#/per_page" >+ }, >+ { >+ "$ref": "../parameters.json#/q_param" >+ }, >+ { >+ "$ref": "../parameters.json#/q_body" >+ }, >+ { >+ "$ref": "../parameters.json#/q_header" >+ } >+ ], >+ "responses": { >+ "200": { >+ "description": "A list of quotes", >+ "schema": { >+ "type": "array", >+ "items": { >+ "$ref": "../definitions.json#/quote" >+ } >+ } >+ }, >+ "403": { >+ "description": "Access forbidden", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ }, >+ "500": { >+ "description": "Internal error", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ }, >+ "503": { >+ "description": "Under maintenance", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ } >+ }, >+ "x-koha-authorization": { >+ "permissions": { >+ "catalogue": "1" >+ } >+ } >+ }, >+ "post": { >+ "x-mojo-to": "Quotes#add", >+ "operationId": "addQuote", >+ "tags": [ >+ "quotes" >+ ], >+ "parameters": [ >+ { >+ "name": "body", >+ "in": "body", >+ "description": "A JSON object containing informations about the new quote", >+ "required": true, >+ "schema": { >+ "$ref": "../definitions.json#/quote" >+ } >+ } >+ ], >+ "produces": [ >+ "application/json" >+ ], >+ "responses": { >+ "201": { >+ "description": "Quote added", >+ "schema": { >+ "$ref": "../definitions.json#/quote" >+ } >+ }, >+ "401": { >+ "description": "Authentication required", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ }, >+ "403": { >+ "description": "Access forbidden", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ }, >+ "500": { >+ "description": "Internal error", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ }, >+ "503": { >+ "description": "Under maintenance", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ } >+ }, >+ "x-koha-authorization": { >+ "permissions": { >+ "tools": "edit_quotes" >+ } >+ } >+ } >+ }, >+ "/quotes/{quote_id}": { >+ "get": { >+ "x-mojo-to": "Quotes#get", >+ "operationId": "getQuote", >+ "tags": [ >+ "quotes" >+ ], >+ "parameters": [ >+ { >+ "$ref": "../parameters.json#/quote_id_pp" >+ } >+ ], >+ "produces": [ >+ "application/json" >+ ], >+ "responses": { >+ "200": { >+ "description": "A Quote", >+ "schema": { >+ "$ref": "../definitions.json#/quote" >+ } >+ }, >+ "404": { >+ "description": "Quote not found", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ }, >+ "500": { >+ "description": "Internal error", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ }, >+ "503": { >+ "description": "Under maintenance", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ } >+ }, >+ "x-koha-authorization": { >+ "permissions": { >+ "catalogue": "1" >+ } >+ } >+ }, >+ "put": { >+ "x-mojo-to": "Quotes#update", >+ "operationId": "updateQuote", >+ "tags": [ >+ "quotes" >+ ], >+ "parameters": [ >+ { >+ "$ref": "../parameters.json#/quote_id_pp" >+ }, >+ { >+ "name": "body", >+ "in": "body", >+ "description": "a quote object", >+ "required": true, >+ "schema": { >+ "$ref": "../definitions.json#/quote" >+ } >+ } >+ ], >+ "produces": [ >+ "application/json" >+ ], >+ "responses": { >+ "200": { >+ "description": "A quote", >+ "schema": { >+ "$ref": "../definitions.json#/quote" >+ } >+ }, >+ "401": { >+ "description": "Authentication required", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ }, >+ "403": { >+ "description": "Access forbidden", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ }, >+ "404": { >+ "description": "Quote not found", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ }, >+ "500": { >+ "description": "Internal error", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ }, >+ "503": { >+ "description": "Under maintenance", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ } >+ }, >+ "x-koha-authorization": { >+ "permissions": { >+ "tools": "edit_quotes" >+ } >+ } >+ }, >+ "delete": { >+ "x-mojo-to": "Quotes#delete", >+ "operationId": "deleteQuote", >+ "tags": [ >+ "quotes" >+ ], >+ "parameters": [ >+ { >+ "$ref": "../parameters.json#/quote_id_pp" >+ } >+ ], >+ "produces": [ >+ "application/json" >+ ], >+ "responses": { >+ "204": { >+ "description": "Quote deleted" >+ }, >+ "401": { >+ "description": "Authentication required", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ }, >+ "403": { >+ "description": "Access forbidden", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ }, >+ "404": { >+ "description": "Quote not found", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ }, >+ "500": { >+ "description": "Internal error", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ }, >+ "503": { >+ "description": "Under maintenance", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ } >+ }, >+ "x-koha-authorization": { >+ "permissions": { >+ "tools": "edit_quotes" >+ } >+ } >+ } >+ } >+} >diff --git a/api/v1/swagger/x-primitives.json b/api/v1/swagger/x-primitives.json >index 0594264be9..13cc1da4fc 100644 >--- a/api/v1/swagger/x-primitives.json >+++ b/api/v1/swagger/x-primitives.json >@@ -52,6 +52,10 @@ > "type": "integer", > "description": "internally assigned fund identifier", > "readOnly": true >+ }, >+ "quote_id": { >+ "type": "integer", >+ "description": "internally assigned quote identifier", >+ "readOnly": true > } >- > } >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes-upload.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes-upload.tt >deleted file mode 100644 >index dc5da4b0f2..0000000000 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes-upload.tt >+++ /dev/null >@@ -1,379 +0,0 @@ >-[% USE raw %] >-[% USE Asset %] >-[% SET footerjs = 1 %] >- [% INCLUDE 'doc-head-open.inc' %] >- <title>Koha › Tools › Quote uploader</title> >- [% INCLUDE 'doc-head-close.inc' %] >- [% Asset.css("css/uploader.css") | $raw %] >- [% Asset.css("css/quotes.css") | $raw %] >-</head> >- >-<body id="tools_quotes" class="tools"> >-[% INCLUDE 'header.inc' %] >-[% INCLUDE 'cat-search.inc' %] >- >-<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> › <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a> › <a href="/cgi-bin/koha/tools/quotes.pl">Quote editor</a> › Quote uploader</div> >- >-<div class="main container-fluid"> >- <div class="row"> >- <div class="col-sm-10 col-sm-push-2"> >- <main> >- >- [% INCLUDE 'quotes-upload-toolbar.inc' %] >- <h2>Quote uploader</h2> >- <div id="instructions"> >- <fieldset id="file_uploader_help" class="rows"> >- <legend>Instructions</legend> >- <div id="file_uploader_inst"> >- <ul> >- <li>The quote uploader accepts standard csv files with two columns: "source","text"</li> >- <li>Click the "Choose File" button and select the csv file to be uploaded.</li> >- <li>The file will be imported into an editable table for review prior to saving.</li> >- </ul> >- </div> >- <div id="file_editor_inst"> >- <ul> >- <li>Click on any field to edit the contents; Press the <Enter> key to save edit.</li> >- <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> >- <li>Click the 'Save Quotes' button in the toolbar to save the entire batch of quotes.</li> >- </ul> >- </div> >- </fieldset> >- </div> >- <fieldset id="file_uploader" class="rows"> >- <legend>Upload quotes</legend> >- <div id="file_upload"> >- <input type="file" name="file" /> >- <button id="cancel_upload" style="display:none">Cancel upload</button> >- <div id="progress_bar"><div class="percent">0%</div></div> >- </div> >- </fieldset> >- <table id="quotes_editor" style="visibility: hidden;"> >- <thead> >- <tr> >- <th>Source</th> >- <th>Text</th> >- <th>Actions</th> >- </tr> >- </thead> >- <tbody> >- <!-- tbody content is generated by DataTables --> >- <tr> >- <td></td> >- <td>Loading data...</td> >- <td></td> >- </tr> >- </tbody> >- </table> >- <fieldset id="footer" class="action" style="visibility: hidden;"> >- </fieldset> >- >- </main> >- </div> <!-- /.col-sm-10.col-sm-push-2 --> >- >- <div class="col-sm-2 col-sm-pull-10"> >- <aside> >- [% INCLUDE 'tools-menu.inc' %] >- </aside> >- </div> <!-- /.col-sm-2.col-sm-pull-10 --> >- </div> <!-- /.row --> >- >-[% MACRO jsinclude BLOCK %] >- [% Asset.js("js/tools-menu.js") | $raw %] >- [% INCLUDE 'datatables.inc' %] >- [% Asset.js("lib/jquery/plugins/jquery.jeditable.mini.js") | $raw %] >- <script> >- var oTable; //DataTable object >- $(document).ready(function() { >- >- $("#cancel_upload").on("click",function(e){ >- e.preventDefault(); >- fnAbortRead(); >- }); >- $("#cancel_quotes").on("click",function(){ >- return confirm( _("Are you sure you want to cancel this import?") ); >- }); >- >- // Credits: >- // FileReader() code copied and hacked from: >- // http://www.html5rocks.com/en/tutorials/file/dndfiles/ >- // fnCSVToArray() gratefully borrowed from: >- // http://www.bennadel.com/blog/1504-Ask-Ben-Parsing-CSV-Strings-With-Javascript-Exec-Regular-Expression-Command.htm >- >- var reader; >- var progress = document.querySelector('.percent'); >- $("#server_response").hide(); >- >- function yuiGetData() { >- fnGetData(document.getElementById('quotes_editor')); >- } >- >- function fnAbortRead() { >- reader.abort(); >- } >- >- function fnErrorHandler(evt) { >- switch(evt.target.error.code) { >- case evt.target.error.NOT_FOUND_ERR: >- alert(_("File Not Found!")); >- break; >- case evt.target.error.NOT_READABLE_ERR: >- alert(_("File is not readable")); >- break; >- case evt.target.error.ABORT_ERR: >- break; // noop >- default: >- alert(_("An error occurred reading this file.")); >- }; >- } >- >- function fnUpdateProgress(evt) { >- // evt is an ProgressEvent. >- if (evt.lengthComputable) { >- var percentLoaded = Math.round((evt.loaded / evt.total) * 100); >- // Increase the progress bar length. >- if (percentLoaded < 100) { >- progress.style.width = percentLoaded + '%'; >- progress.textContent = percentLoaded + '%'; >- } >- } >- } >- >- function fnCSVToArray( strData, strDelimiter ){ >- // This will parse a delimited string into an array of >- // arrays. The default delimiter is the comma, but this >- // can be overriden in the second argument. >- >- // Check to see if the delimiter is defined. If not, >- // then default to comma. >- strDelimiter = (strDelimiter || ","); >- >- // Create a regular expression to parse the CSV values. >- var objPattern = new RegExp( >- ( >- // Delimiters. >- "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" + >- // Quoted fields. >- "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" + >- // Standard fields. >- "([^\"\\" + strDelimiter + "\\r\\n]*))" >- ), >- "gi" >- ); >- >- // Create an array to hold our data. Give the array >- // a default empty first row. >- var arrData = [[]]; >- >- // Create an array to hold our individual pattern >- // matching groups. >- var arrMatches = null; >- >- // Keep looping over the regular expression matches >- // until we can no longer find a match. >- while (arrMatches = objPattern.exec( strData )){ >- >- // Get the delimiter that was found. >- var strMatchedDelimiter = arrMatches[ 1 ]; >- >- // Check to see if the given delimiter has a length >- // (is not the start of string) and if it matches >- // field delimiter. If it does not, then we know >- // that this delimiter is a row delimiter. >- if ( strMatchedDelimiter.length && (strMatchedDelimiter != strDelimiter) ){ >- // Since we have reached a new row of data, >- // add an empty row to our data array. >- // Note: if there is not more data, we will have to remove this row later >- arrData.push( [] ); >- } >- >- // Now that we have our delimiter out of the way, >- // let's check to see which kind of value we >- // captured (quoted or unquoted). >- if (arrMatches[ 2 ]){ >- // We found a quoted value. When we capture >- // this value, unescape any double quotes. >- var strMatchedValue = arrMatches[ 2 ].replace( >- new RegExp( "\"\"", "g" ), >- "\"" >- ); >- } else if (arrMatches[3]){ >- // We found a non-quoted value. >- var strMatchedValue = arrMatches[ 3 ]; >- } else { >- // There is no more valid data so remove the row we added earlier >- // Is there a better way? Perhaps a look-ahead regexp? >- arrData.splice(arrData.length-1, 1); >- } >- >- // Now that we have our value string, let's add >- // it to the data array. >- arrData[ arrData.length - 1 ].push( strMatchedValue ); >- } >- >- // Return the parsed data. >- return( arrData ); >- } >- >- function fnDataTable(aaData) { >- for(var i=0; i<aaData.length; i++) { >- aaData[i].unshift(i+1); // Add a column w/quote number >- } >- >- /* Transition from the quote file uploader to the quote file editor interface */ >- $('#toolbar').css("visibility","visible"); >- $('#toolbar').css("position",""); >- $('#file_editor_inst').css("visibility", "visible"); >- $('#file_editor_inst').css("position", ""); >- $('#file_uploader_inst').css("visibility", "hidden"); >- $('#save_quotes').css("visibility","visible"); >- $('#file_uploader').css("visibility","hidden"); >- $('#file_uploader').css("position","absolute"); >- $('#file_uploader').css("top","-150px"); >- $('#quotes_editor').css("visibility","visible"); >- $("#save_quotes").on("click", yuiGetData); >- $("#delete_quote").on("click", fnClickDeleteRow); >- >- oTable = $('#quotes_editor').dataTable( { >- "bAutoWidth" : false, >- "bPaginate" : true, >- "bSort" : false, >- "sPaginationType" : "full_numbers", >- "sDom": '<"top pager"iflp>rt<"bottom pager"flp><"clear">', >- "aaData" : aaData, >- "aoColumns" : [ >- { >- "sTitle" : _("Number"), >- "sWidth" : "2%", >- }, >- { >- "sTitle" : _("Source"), >- "sWidth" : "15%", >- }, >- { >- "sTitle" : _("Quote"), >- "sWidth" : "83%", >- }, >- ], >- "fnPreDrawCallback": function(oSettings) { >- return true; >- }, >- "fnRowCallback": function( nRow, aData, iDisplayIndex ) { >- /* do foo on various cells in the current row */ >- var quoteNum = $('td', nRow)[0].innerHTML; >- $(nRow).attr("id", quoteNum); /* set row ids to quote number */ >- $('td:eq(0)', nRow).click(function() {$(this.parentNode).toggleClass('selected',this.clicked);}); /* add row selectors */ >- $('td:eq(0)', nRow).attr("title", _("Click ID to select/deselect quote")); >- /* apply no_edit id to noEditFields */ >- noEditFields = [0]; /* number */ >- for (i=0; i<noEditFields.length; i++) { >- $('td', nRow)[noEditFields[i]].setAttribute("id","no_edit"); >- } >- return nRow; >- }, >- "fnDrawCallback": function(oSettings) { >- /* Apply the jEditable handlers to the table on all fields w/o the no_edit id */ >- $('#quotes_editor tbody td[id!="no_edit"]').editable( function(value, settings) { >- var cellPosition = oTable.fnGetPosition( this ); >- oTable.fnUpdate(value, cellPosition[0], cellPosition[1], false, false); >- return(value); >- }, >- { >- "callback" : function( sValue, y ) { >- oTable.fnDraw(false); /* no filter/sort or we lose our pagination */ >- }, >- "height" : "14px", >- }); >- }, >- }); >- $('#footer').css("visibility","visible"); >- } >- >- function fnHandleFileSelect(evt) { >- // Reset progress indicator on new file selection. >- progress.style.width = '0%'; >- progress.textContent = '0%'; >- >- reader = new FileReader(); >- reader.onerror = fnErrorHandler; >- reader.onprogress = fnUpdateProgress; >- reader.onabort = function(e) { >- alert(_("File read cancelled")); >- parent.location='quotes-upload.pl'; >- }; >- reader.onloadstart = function(e) { >- $('#cancel_upload').show(); >- $('#progress_bar').addClass("loading"); >- }; >- reader.onload = function(e) { >- // Ensure that the progress bar displays 100% at the end. >- progress.style.width = '100%'; >- progress.textContent = '100%'; >- $('#cancel_upload').hide(); >- quotes = fnCSVToArray(e.target.result, ','); >- fnDataTable(quotes); >- } >- >- // perform various sanity checks on the target file prior to uploading... >- var fileType = evt.target.files[0].type || 'unknown'; >- var fileSizeInK = Math.round(evt.target.files[0].size/1024); >- >- if (!fileType.match(/comma-separated-values|csv|excel|calc/i)) { >- alert(_("Uploads limited to csv. Incorrect filetype: %s").format(fileType)); >- parent.location='quotes-upload.pl'; >- return; >- } >- if (fileSizeInK > 512) { >- if (!confirm(_("%s %s KB Do you really want to upload this file?").format(evt.target.files[0].name, fileSizeInK))) { >- parent.location='quotes-upload.pl'; >- return; >- } >- } >- // Read in the image file as a text string. >- reader.readAsText(evt.target.files[0]); >- } >- >- $('#file_upload').one('change', fnHandleFileSelect); >- >- }); >- >- function fnGetData(element) { >- var jqXHR = $.ajax({ >- url : "/cgi-bin/koha/tools/quotes/quotes-upload_ajax.pl", >- type : "POST", >- contentType : "application/x-www-form-urlencoded", // we must claim this mimetype or CGI will not decode the URL encoding >- dataType : "json", >- data : { >- "quote" : encodeURI ( JSON.stringify(oTable.fnGetData()) ), >- "action" : "add", >- }, >- success : function(){ >- var response = JSON.parse(jqXHR.responseText); >- if (response.success) { >- alert(_("%s quotes saved.").format(response.records)); >- window.location.reload(true); // is this the best route? >- } else { >- alert(_("%s quotes saved, but an error has occurred. Please ask your administrator to check the server log for more details.").format(response.records)); >- window.location.reload(true); // is this the best route? >- } >- }, >- }); >- } >- >- function fnClickDeleteRow() { >- var idsToDelete = oTable.$('.selected').map(function() { >- return this.id; >- }).get().join(', '); >- if (!idsToDelete) { >- alert(_("Please select a quote(s) by clicking the quote id(s) you desire to delete.")); >- } >- else if (confirm(_("Are you sure you wish to delete quote(s) %s?").format(idsToDelete))) { >- oTable.$('.selected').each(function(){ >- oTable.fnDeleteRow(this); >- }); >- } >- } >- </script> >-[% END %] >- >-[% INCLUDE 'intranet-bottom.inc' %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes.tt >index 5339069a0f..ae6fa313f8 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes.tt >@@ -1,10 +1,10 @@ > [% USE raw %] > [% USE Asset %] > [% SET footerjs = 1 %] >- [% INCLUDE 'doc-head-open.inc' %] >- <title>Koha › Tools › Quote editor</title> >- [% INCLUDE 'doc-head-close.inc' %] >- [% Asset.css("css/quotes.css") | $raw %] >+[% INCLUDE 'doc-head-open.inc' %] >+<title>Koha › Tools › Quote editor</title> >+[% INCLUDE 'doc-head-close.inc' %] >+[% Asset.css("css/quotes.css") | $raw %] > </head> > > <body id="tools_quotes" class="tools"> >@@ -18,43 +18,109 @@ > <div class="col-sm-10 col-sm-push-2"> > <main> > >- [% INCLUDE 'quotes-toolbar.inc' %] >- <h2>Quote editor</h2> >- <div id="instructions"> >- <fieldset id="quote_editor_help" class="rows"> >- <legend>Instructions</legend> >- <div id="quote_editor_inst"> >- <ul> >- <li>Click on the 'Add quote' button to add a single quote; Press the <Enter> key to save the quote.<br /> >- <strong>Note: </strong>Both the 'source' and 'text' fields must have content in order for the quote to be saved.</li> >- <li>Click on any field to edit the contents; Press the <Enter> key to save edit.</li> >- <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> >- <li>Click the 'Import quotes' button in the toolbar to import a CSV file of quotes.</li> >- </ul> >- </div> >- </fieldset> >+[% FOREACH m IN messages %] >+ <div class="dialog [% m.type | html %]" id="quote_action_result_dialog"> >+ [% SWITCH m.code %] >+ [% CASE 'error_on_update' %] >+ An error occurred when updating this quote. Perhaps it already exists. >+ [% CASE 'error_on_insert' %] >+ An error occurred when adding this quote. >+ [% CASE 'success_on_update' %] >+ Quote updated successfully. >+ [% CASE 'success_on_insert' %] >+ Quote added successfully. >+ [% CASE %] >+ [% m.code | html %] >+ [% END %] >+ </div> >+[% END %] >+ >+ <div class="dialog message" id="quote_delete_success" style="display: none;"></div> >+ <div class="dialog alert" id="quote_delete_error" style="display: none;"></div> >+ >+[% IF op == 'list' %] >+ <div id="toolbar" class="btn-toolbar"> >+ <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> >+ </div> >+[% END %] >+ >+[% IF op == 'add_form' %] >+ <h3>[% IF quote %]Modify quote[% ELSE %]New quote[% END %]</h3> >+ <form action="/cgi-bin/koha/tools/quotes.pl" id="Aform" name="Aform" class="validated" method="post"> >+ <fieldset class="rows"> >+ <input type="hidden" name="op" value="add_validate" /> >+ <ol> >+ <li> >+ <label for="text" class="required">Source: </label> >+ <input type="text" name="source" id="source" value="[% quote.source | html %]" class="required" required="required" /> >+ <span class="required">Required</span> >+ </li> >+ <li> >+ <label for="text" class="required">text: </label> >+ <textarea name="text" id="text" class="required" required="required" />[% quote.text | html %]</textarea> >+ <span class="required">Required</span> >+ </li> >+ </ol> >+ </fieldset> >+ <fieldset class="action"> >+ <input type="hidden" name="id" value="[% quote.id %]" /> >+ <input type="submit" value="Submit" /> >+ <a class="cancel" href="/cgi-bin/koha/tools/quotes.pl">Cancel</a> >+ </fieldset> >+ </form> >+[% END %] >+ >+[% IF op == 'delete_confirm' %] >+ <div class="dialog alert"> >+ <form action="/cgi-bin/koha/tools/quotes.pl" method="post"> >+ <h3>Are you sure you want to delete the following quote?</h3> >+ [% quote.source | html %] - [% quote.text | html %] >+ <input type="hidden" name="op" value="delete_confirmed" /> >+ <input type="hidden" name="id" value="[% quote.id | html %]" /> >+ <button type="submit" class="approve"><i class="fa fa-fw fa-check"></i> Yes, delete</button> >+ </form> >+ <form action="/cgi-bin/koha/tools/quotes.pl" method="get"> >+ <button type="submit" class="deny"><i class="fa fa-fw fa-remove"></i> No, do not delete</button> >+ </form> >+ </div> >+[% END %] >+ >+[% IF op == 'list' %] >+ <h3>Quotes</h3> >+ [% IF quotes_count > 0 %] >+ <table id="quotes"> >+ <thead> >+ <tr> >+ <th>ID</th> >+ <th>Source</th> >+ <th>Text</th> >+ <th>Last display</th> >+ <th data-class-name="actions">Actions</th> >+ </tr> >+ </thead> >+ </table> >+ [% ELSE %] >+ <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> >+ [% END %] >+ >+ <div id="delete_confirm_modal" class="modal" tabindex="-1" role="dialog" aria-labelledby="delete_confirm_modal_label" aria-hidden="true"> >+ <div class="modal-dialog"> >+ <div class="modal-content"> >+ <div class="modal-header"> >+ <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">Ã</button> >+ <h3 id="delete_confirm_modal_label">Delete quote</h3> > </div> >- <table id="quotes_editor"> >- <thead> >- <tr> >- <th><span style="cursor: help" id="id_help">ID</span></th> >- <th>Source</th> >- <th>Text</th> >- <th>Last displayed</th> >- </tr> >- </thead> >- <tbody> >- <!-- tbody content is generated by DataTables --> >- <tr> >- <td></td> >- <td></td> >- <td>Loading data...</td> >- <td></td> >- </tr> >- </tbody> >- </table> >- <fieldset id="footer" class="action"> >- </fieldset> >+ <div class="modal-body"> >+ <div id="delete_confirm_dialog"></div> >+ </div> >+ <div class="modal-footer"> >+ <a href="#" class="btn btn-default" id="delete_confirm_modal_button" role="button" data-toggle="modal">Delete</a> >+ <button class="btn btn-default" data-dismiss="modal" aria-hidden="true">Close</button> >+ </div> >+ </div> <!-- /.modal-content --> >+ </div> <!-- /.modal-dialog --> >+ </div> <!-- #delete_confirm_modal --> >+[% END %] > > </main> > </div> <!-- /.col-sm-10.col-sm-push-2 --> >@@ -68,164 +134,99 @@ > > [% MACRO jsinclude BLOCK %] > [% Asset.js("js/tools-menu.js") | $raw %] >+ [% INCLUDE 'js-date-format.inc' %] > [% INCLUDE 'datatables.inc' %] >- [% Asset.js("lib/jquery/plugins/dataTables.fnReloadAjax.js") | $raw %] >- [% Asset.js("lib/jquery/plugins/jquery.jeditable.mini.js") | $raw %] >+ > <script> >- var oTable; /* oTable needs to be global */ >- var sEmptyTable = _("No quotes available. Please use the 'Add quote' button to add a quote."); /* override the default message in datatables.inc */ > $(document).ready(function() { >- /* NOTE: This is an ajax-source datatable and *not* a server-side sourced datatable. */ >- /* See the datatable docs if you don't understand this difference. */ >- oTable = $("#quotes_editor").dataTable({ >- "bAutoWidth" : false, >- "bProcessing" : true, >- "bPaginate" : true, >- "sPaginationType" : "full_numbers", >- "sDom": '<"top pager"iflp>rt<"bottom pager"flp><"clear">', >- "sAjaxSource" : "/cgi-bin/koha/tools/quotes/quotes_ajax.pl", >- "aoColumns" : [ >- { "sWidth": "3%" }, >- { "sWidth": "11%" }, >- { "sWidth": "75%" }, >- { "sWidth": "11%" }, >- ], >- "oLanguage": dataTablesDefaults.oLanguage, >- "fnPreDrawCallback": function(oSettings) { >- return true; >+ >+ var quotes_url = '/api/v1/quotes'; >+ var quotes = $("#quotes").api({ >+ "ajax": { >+ "url": quotes_url > }, >- "fnRowCallback": function( nRow, aData, iDisplayIndex ) { >- /* do foo on the current row and its child nodes */ >- var noEditFields = []; >- var quoteID = $('td', nRow)[0].innerHTML; >- $(nRow).attr("id", quoteID); /* set row ids to quote id */ >- $('td:eq(0)', nRow).click(function() {$(this.parentNode).toggleClass('selected',this.clicked);}); /* add row selectors */ >- $('td:eq(0)', nRow).attr("title", _("Click ID to select/deselect quote")); >- $('td', nRow).attr("id",quoteID); /* FIXME: this is a bit of a hack */ >- if (isNaN(quoteID)) { >- noEditFields = [0,1,2,3]; /* all fields when adding a quote */ >- } else { >- noEditFields = [0,3]; /* id, timestamp */ >- } >- /* apply no_edit id to noEditFields */ >- for (i=0; i<noEditFields.length; i++) { >- $('td', nRow)[noEditFields[i]].setAttribute("id","no_edit"); >+ '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>', >+ "columnDefs": [ { >+ "targets": [0,1,2,3], >+ "render": function (data, type, row, meta) { >+ if ( type == 'display' ) { >+ if ( data != null ) { >+ return data.escapeHtml(); >+ } >+ else { >+ return ""; >+ } >+ } >+ return data; > } >- return nRow; >- }, >- "fnDrawCallback": function(oSettings) { >- /* Apply the jEditable handlers to the table on all fields w/o the no_edit id */ >- $('#quotes_editor tbody td[id!="no_edit"]').editable( "/cgi-bin/koha/tools/quotes/quotes_ajax.pl", { >- "submitdata" : function ( value, settings ) { >- return { >- "column" : oTable.fnGetPosition( this )[2], >- "action" : "edit", >- }; >- }, >- "placeholder" : "Saving data...", >- }); >- }, >- }); >- $("#add_quote").click(function(){ >- fnClickAddRow(); >- return false; >- }); >- $("#delete_quote").click(function(){ >- fnClickDeleteRow(); >- return false; >- }); >- $("#id_help").on("click",function(e){ >- e.stopPropagation(); >- alert( _("Click on the quote's id to select or deselect the quote. Multiple quotes may be selected.") ); >- }); >- }); >+ } ], >+ "columns": [ >+ { >+ "data": "quote_id", >+ "searchable": true, >+ "orderable": true >+ }, >+ { >+ "data": "source", >+ "searchable": true, >+ "orderable": true >+ }, >+ { >+ "data": "text", >+ "searchable": true, >+ "orderable": true >+ }, >+ { >+ "data": function( row, type, val, meta ) { >+ return $datetime(row.displayed_on); >+ }, >+ "searchable": true, >+ "orderable": true >+ }, >+ { >+ "data": function( row, type, val, meta ) { > >- function fnClickAddQuote(e, node) { >- if (e.charCode) { >- /* some browsers only give charCode, so this will need to be */ >- /* fixed up to handle that */ >- console.log('charCode: '+e.charCode); >- } >- if (e.keyCode == 13) { >- var quoteSource = $('#quoteSource').val(); >- var quoteText = $('#quoteText').val() >- /* If passed a quote source, add the quote to the db */ >- if (quoteSource && quoteText) { >- $.ajax({ >- url: "/cgi-bin/koha/tools/quotes/quotes_ajax.pl", >- type: "POST", >- data: { >- "source" : quoteSource, >- "text" : quoteText, >- "action" : "add", >+ var result = '<a class="btn btn-default btn-xs" href="/cgi-bin/koha/tools/quotes.pl?op=add_form&id='+encodeURIComponent(row.quote_id)+'" role="button"><i class="fa fa-pencil" aria-hidden="true"></i> '+_("Edit")+'</a>'; >+ result += '<form action="/cgi-bin/koha/tools/quotes.pl" method="post">'; >+ result += '<input type="hidden" name="id" value="'+row.quote_id.escapeHtml()+'" />'+"\n"; >+ >+ 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>'; >+ >+ return result; > }, >- success: function(data){ >- var newQuote = data[0]; >- var aRow = oTable.fnUpdate( >- newQuote, >- node, >- undefined, >- false, >- false >- ); >- oTable.fnPageChange( 'last' ); >- $('.add_quote_button').attr('onclick', 'fnClickAddRow()'); // re-enable add button >- } >- }); >- } else { >- alert(_("Please supply both the text and source of the quote before saving.")); >- } >- } else if (e.keyCode == 27) { >- if (confirm(_("Are you sure you want to cancel adding this quote?"))) { >- oTable.fnDeleteRow(node); >- } else { >- return; >- } >- } >- } >- >- function fnClickAddRow() { >- $('.add_quote_button').removeAttr('onclick'); // disable add button once it has been clicked >- var aRow = oTable.fnAddData( >- [ >- 'NA', // this is hackish to fool the datatable sort routine into placing this row at the end of the list... >- '<input id="quoteSource" type="text" style="width:99%" onkeydown="fnClickAddQuote(event,this.parentNode.parentNode)"/>', >- '<input id="quoteText" type="text" style="width:99%" onkeydown="fnClickAddQuote(event,this.parentNode.parentNode)"/>', >- '0000-00-00 00:00:00', >- ], >- false >- ); >- oTable.fnPageChange( 'last' ); >- $('#quoteSource').focus(); >- } >- >- function fnClickDeleteRow() { >- var idsToDelete = oTable.$('.selected').map(function() { >- return this.id; >- }).get().join(', '); >- if (!idsToDelete) { >- alert(_("Please select a quote(s) by clicking the quote id(s) you desire to delete.")); >- } else if (confirm(_("Are you sure you wish to delete quote(s) %s?").format(idsToDelete))) { >- oTable.$('.selected').each(function(){ >- var quoteID = $(this).attr('id'); >+ "searchable": false, >+ "orderable": false >+ }, >+ ] >+ }); >+ >+ $('#quotes').on( "click", '.delete_quote', function () { >+ var quote_id = decodeURIComponent($(this).data('quote-id')); >+ >+ $("#delete_confirm_dialog").html( >+ _("You are about to delete the quote #%s.").format(quote_id) >+ ); >+ >+ $("#delete_confirm_modal_button").unbind("click").on( "click", function () { > $.ajax({ >- url: "/cgi-bin/koha/tools/quotes/quotes_ajax.pl", >- type: "POST", >- data: { >- "id" : quoteID, >- "action" : "delete", >- }, >- /* Delete the row from the datatable */ >- success: function(){ >- oTable.fnDeleteRow(this); >- oTable.fnReloadAjax(null, null, true); >- } >+ method: "DELETE", >+ url: "/api/v1/quotes/"+quote_id >+ }).success(function() { >+ $("#delete_confirm_modal").modal('hide'); >+ quotes.api().ajax.reload(function (data) { >+ if (data.recordsTotal == 0) { >+ $("#quotes").hide(); >+ } >+ $("#quote_action_result_dialog").hide(); >+ $("#quote_delete_success").html(_("Quote #%s deleted successfully.").format(quote_id)).show(); >+ }); >+ }).error(function () { >+ $("#quote_delete_error").html(_("Error deleting quote #%s. Check the logs.").format(quote_id)).show(); > }); > }); >- } else { >- return; >- } >- } >+ }); >+ >+ }); > </script> > [% END %] > >diff --git a/tools/quotes-upload.pl b/tools/quotes-upload.pl >deleted file mode 100755 >index 8ca4583471..0000000000 >--- a/tools/quotes-upload.pl >+++ /dev/null >@@ -1,42 +0,0 @@ >-#!/usr/bin/perl >- >-# Copyright 2012 Foundations Bible College Inc. >-# >-# This file is part of Koha. >-# >-# Koha is free software; you can redistribute it and/or modify it >-# under the terms of the GNU General Public License as published by >-# the Free Software Foundation; either version 3 of the License, or >-# (at your option) any later version. >-# >-# Koha is distributed in the hope that it will be useful, but >-# WITHOUT ANY WARRANTY; without even the implied warranty of >-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >-# GNU General Public License for more details. >-# >-# You should have received a copy of the GNU General Public License >-# along with Koha; if not, see <http://www.gnu.org/licenses>. >- >-use Modern::Perl; >- >-use CGI qw ( -utf8 ); >-use autouse 'Data::Dumper' => qw(Dumper); >- >-use C4::Auth; >-use C4::Koha; >-use C4::Context; >-use C4::Output; >- >-my $cgi = CGI->new; >- >-my ( $template, $borrowernumber, $cookie ) = get_template_and_user( >- { >- template_name => "tools/quotes-upload.tt", >- query => $cgi, >- type => "intranet", >- flagsrequired => { tools => 'edit_quotes' }, >- debug => 1, >- } >-); >- >-output_html_with_http_headers $cgi, $cookie, $template->output; >diff --git a/tools/quotes.pl b/tools/quotes.pl >index a10e5747f7..57859f14b2 100755 >--- a/tools/quotes.pl >+++ b/tools/quotes.pl >@@ -1,7 +1,5 @@ > #!/usr/bin/perl > >-# Copyright 2012 Foundations Bible College Inc. >-# > # This file is part of Koha. > # > # Koha is free software; you can redistribute it and/or modify it >@@ -20,23 +18,80 @@ > use Modern::Perl; > > use CGI qw ( -utf8 ); >-use autouse 'Data::Dumper' => qw(Dumper); >+use Try::Tiny; > > use C4::Auth; >-use C4::Koha; > use C4::Context; > use C4::Output; >+use Koha::Quotes; > >-my $cgi = CGI->new; >+my $input = CGI->new; > > my ( $template, $borrowernumber, $cookie ) = get_template_and_user( > { >- template_name => "tools/quotes.tt", >- query => $cgi, >- type => "intranet", >- flagsrequired => { tools => 'edit_quotes' }, >- debug => 1, >+ template_name => "tools/quotes.tt", >+ query => $input, >+ type => "intranet", >+ flagsrequired => { tools => 'edit_quotes' }, >+ debug => 1, > } > ); > >-output_html_with_http_headers $cgi, $cookie, $template->output; >+my $id = $input->param('id'); >+my $op = $input->param('op') || 'list'; >+my @messages; >+ >+if ( $op eq 'add_form' ) { >+ $template->param( quote => Koha::Quotes->find($id), ); >+} >+elsif ( $op eq 'add_validate' ) { >+ my @fields = qw( >+ source >+ text >+ ); >+ >+ if ($id) { >+ my $quote = Koha::Quotes->find($id); >+ for my $field (@fields) { >+ $quote->$field( scalar $input->param($field) ); >+ } >+ >+ try { >+ $quote->store; >+ push @messages, { type => 'message', code => 'success_on_update' }; >+ } >+ catch { >+ push @messages, { type => 'alert', code => 'error_on_update' }; >+ } >+ } >+ else { >+ my $quote = Koha::Quote->new( >+ { >+ id => $id, >+ ( map { $_ => scalar $input->param($_) || undef } @fields ) >+ } >+ ); >+ >+ try { >+ $quote->store; >+ push @messages, { type => 'message', code => 'success_on_insert' }; >+ } >+ catch { >+ push @messages, { type => 'alert', code => 'error_on_insert' }; >+ }; >+ } >+ $op = 'list'; >+} >+else { >+ $op = 'list'; >+} >+ >+$template->param( quotes_count => Koha::Quotes->search->count ) >+ if $op eq 'list'; >+ >+$template->param( >+ messages => \@messages, >+ op => $op, >+); >+ >+output_html_with_http_headers $input, $cookie, $template->output; >diff --git a/tools/quotes/quotes-upload_ajax.pl b/tools/quotes/quotes-upload_ajax.pl >deleted file mode 100755 >index c988e7c540..0000000000 >--- a/tools/quotes/quotes-upload_ajax.pl >+++ /dev/null >@@ -1,65 +0,0 @@ >-#!/usr/bin/perl >- >-# Copyright 2012 Foundations Bible College Inc. >-# >-# This file is part of Koha. >-# >-# Koha is free software; you can redistribute it and/or modify it >-# under the terms of the GNU General Public License as published by >-# the Free Software Foundation; either version 3 of the License, or >-# (at your option) any later version. >-# >-# Koha is distributed in the hope that it will be useful, but >-# WITHOUT ANY WARRANTY; without even the implied warranty of >-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >-# GNU General Public License for more details. >-# >-# You should have received a copy of the GNU General Public License >-# along with Koha; if not, see <http://www.gnu.org/licenses>. >- >-use Modern::Perl; >- >-use CGI qw ( -utf8 ); >-use JSON; >-use URI::Escape; >-use autouse 'Data::Dumper' => qw(Dumper); >- >-use C4::Auth; >-use C4::Koha; >-use C4::Context; >-use C4::Output; >- >-my $cgi = CGI->new; >-my $dbh = C4::Context->dbh; >- >-my ( $status, $cookie, $sessionID ) = C4::Auth::check_api_auth( $cgi, { tools => 'edit_quotes' } ); >-unless ($status eq "ok") { >- print $cgi->header(-type => 'application/json', -status => '403 Forbidden'); >- print to_json({ auth_status => $status }); >- exit 0; >-} >- >-my $success = 'true'; >-my $quotes_tmp = uri_unescape( $cgi->param('quote' ) ); >-my $quotes = decode_json( $quotes_tmp ); >- >-my $action = $cgi->param('action'); >- >-my $sth = $dbh->prepare('INSERT INTO quotes (source, text) VALUES (?, ?);'); >- >-my $insert_count = 0; >- >-foreach my $quote (@$quotes) { >- $insert_count++ if $sth->execute($quote->[1], $quote->[2]); >- if ($sth->err) { >- warn sprintf('Database returned the following error: %s', $sth->errstr); >- $success = 'false'; >- } >-} >- >-print $cgi->header('application/json'); >- >-print to_json({ >- success => $success, >- records => $insert_count, >-}); >diff --git a/tools/quotes/quotes_ajax.pl b/tools/quotes/quotes_ajax.pl >deleted file mode 100755 >index 79fbe6fb01..0000000000 >--- a/tools/quotes/quotes_ajax.pl >+++ /dev/null >@@ -1,109 +0,0 @@ >-#!/usr/bin/perl >- >-# Copyright 2012 Foundations Bible College Inc. >-# >-# This file is part of Koha. >-# >-# Koha is free software; you can redistribute it and/or modify it >-# under the terms of the GNU General Public License as published by >-# the Free Software Foundation; either version 3 of the License, or >-# (at your option) any later version. >-# >-# Koha is distributed in the hope that it will be useful, but >-# WITHOUT ANY WARRANTY; without even the implied warranty of >-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >-# GNU General Public License for more details. >-# >-# You should have received a copy of the GNU General Public License >-# along with Koha; if not, see <http://www.gnu.org/licenses>. >- >-use Modern::Perl; >- >-use CGI qw ( -utf8 ); >-use JSON; >-use autouse 'Data::Dumper' => qw(Dumper); >- >-use C4::Auth; >-use C4::Context; >- >-my $cgi = CGI->new; >-my $dbh = C4::Context->dbh; >-my $sort_columns = ["id", "source", "text", "timestamp"]; >- >-my ( $status, $cookie, $sessionID ) = C4::Auth::check_api_auth( $cgi, { tools => 'edit_quotes' } ); >-unless ($status eq "ok") { >- print $cgi->header(-type => 'application/json', -status => '403 Forbidden'); >- print to_json({ auth_status => $status }); >- exit 0; >-} >- >-# NOTE: This is a collection of ajax functions for use with tools/quotes.pl >- >-my $params = $cgi->Vars; # NOTE: Multivalue parameters NOT allowed!! >- >-print $cgi->header('application/json; charset=utf-8'); >- >-my $action = $params->{'action'} || 'get'; >-if ($action eq 'add') { >- my $sth = $dbh->prepare('INSERT INTO quotes (source, text) VALUES (?, ?);'); >- $sth->execute($params->{'source'}, $params->{'text'}); >- if ($sth->err) { >- warn sprintf('Database returned the following error: %s', $sth->errstr); >- exit 0; >- } >- my $new_quote_id = $dbh->{q{mysql_insertid}}; # ALERT: mysqlism here >- $sth = $dbh->prepare('SELECT * FROM quotes WHERE id = ?;'); >- $sth->execute($new_quote_id); >- print to_json($sth->fetchall_arrayref, {utf8 =>1}); >- exit 0; >-} >-elsif ($action eq 'edit') { >- my $aaData = []; >- my $editable_columns = [qw(source text)]; # pay attention to element order; these columns match the quotes table columns >- my $sth = $dbh->prepare("UPDATE quotes SET $editable_columns->[$params->{'column'}-1] = ? WHERE id = ?;"); >- $sth->execute($params->{'value'}, $params->{'id'}); >- if ($sth->err) { >- warn sprintf('Database returned the following error: %s', $sth->errstr); >- exit 1; >- } >- $sth = $dbh->prepare("SELECT $editable_columns->[$params->{'column'}-1] FROM quotes WHERE id = ?;"); >- $sth->execute($params->{'id'}); >- $aaData = $sth->fetchrow_array(); >- print Encode::encode('utf8', $aaData); >- >- exit 0; >-} >-elsif ($action eq 'delete') { >- my $sth = $dbh->prepare("DELETE FROM quotes WHERE id = ?;"); >- $sth->execute($params->{'id'}); >- if ($sth->err) { >- warn sprintf('Database returned the following error: %s', $sth->errstr); >- exit 1; >- } >- exit 0; >-} >-else { >- my $aaData = []; >- my $iTotalRecords = ''; >- my $sth = ''; >- >- $iTotalRecords = $dbh->selectrow_array('SELECT count(*) FROM quotes;'); >- $sth = $dbh->prepare("SELECT * FROM quotes;"); >- >- $sth->execute(); >- if ($sth->err) { >- warn sprintf('Database returned the following error: %s', $sth->errstr); >- exit 1; >- } >- >- $aaData = $sth->fetchall_arrayref; >- my $iTotalDisplayRecords = $iTotalRecords; # no filtering happening here >- >- >- print to_json({ >- iTotalRecords => $iTotalRecords, >- iTotalDisplayRecords=> $iTotalDisplayRecords, >- sEcho => $params->{'sEcho'}, >- aaData => $aaData, >- }, {utf8 =>1}); >-} >-- >2.20.1
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 27251
:
114435
|
114477
|
114556
|
114832
|
114885
|
114992
|
114993
|
115234
|
115235
|
115236
|
115915
|
115916
|
115917
|
115918
|
115919