Browse Source

Bug 11425: Add item search form in staff interface

Item search is available at catalogue/itemsearch.pl (link is in
catalogue/search.pl)
It only uses SQL (not Zebra)
* Use DataTables and server-side processing to be able to filter on
  individual columns after the first search is done.
* Allow to export results in CSV
* With Javascript disabled, search form still works (and CSV export too)

There is the possibility to define "Custom search fields" in a new admin
page admin/items_search_fields.pl (link is in admin/admin-home.pl)
A custom item search field is defined by:
* a name: its unique identifier
* a label: the text displayed to the user
* a MARC field/subfield: the field/subfield to query (it uses
  ExtractValue)
* an authorised values list (optional): if defined the list is displayed
  in the search form

New Perl dependency: Template::Plugin::JSON::Escape

Test plan:
1/ Apply the patch and run updatedatabase.pl
2/ Go to advanced search (staff interface), then click on "Go to item
search"
3/ Play with the search form! :)
In the 3rd fieldset you can add as many fields as you want and combine them with
boolean operators (AND, OR). You can use SQL jokers characters (%, _)
You can output to screen (in a DataTables table) or to a CSV file.
4/ In the DataTables table, play with filters and try sorting columns.
5/ Disable Javascript (with Firefox: extensions NoScript or YesScript,
or in about:config 'javascript.enabled' = false
6/ Reload the search page and do some searches on screen output. (there
is no sorting or filtering features, but there is still pagination)
7/ Try again CSV output.
8/ You can re-enable Javascript.
9/ Go to Administration > Items search fields
10/ Add a new field. Example for title (in UNIMARC):
  Name: title
  Label: Title
  MARC field: 200
  MARC subfield: a
  Authorised values category: None
(add another field with an authorised values category to see the
difference).
11/ As you are there try to update and delete some fields.
12/ Go back to items search form. You can see in the 3rd fieldset that
your fields have appeared in the selects.
13/ Try searching on them.
14/ I think you're done :)

Signed-off-by: Bernardo Gonzalez Kriegel <bgkriegel@gmail.com>
Work as described. Good new option.
No koha-qa errors

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
3.18.x
Julian Maurice 11 years ago
committed by Tomas Cohen Arazi
parent
commit
7c2ff7940e
  1. 5
      C4/Installer/PerlDependencies.pm
  2. 197
      C4/Items.pm
  3. 33
      C4/SQLHelper.pm
  4. 107
      Koha/Item/Search/Field.pm
  5. 2
      acqui/check_uniqueness.pl
  6. 63
      admin/items_search_field.pl
  7. 81
      admin/items_search_fields.pl
  8. 298
      catalogue/itemsearch.pl
  9. 17
      installer/data/mysql/kohastructure.sql
  10. 19
      installer/data/mysql/updatedatabase.pl
  11. 44
      koha-tmpl/intranet-tmpl/prog/en/css/itemsearchform.css
  12. 58
      koha-tmpl/intranet-tmpl/prog/en/includes/admin-items-search-field-form.inc
  13. 1
      koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc
  14. 4
      koha-tmpl/intranet-tmpl/prog/en/includes/catalogue/itemsearch_item.csv.inc
  15. 23
      koha-tmpl/intranet-tmpl/prog/en/includes/catalogue/itemsearch_item.inc
  16. 18
      koha-tmpl/intranet-tmpl/prog/en/includes/catalogue/itemsearch_item.json.inc
  17. 49
      koha-tmpl/intranet-tmpl/prog/en/includes/catalogue/itemsearch_items.inc
  18. 2
      koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt
  19. 41
      koha-tmpl/intranet-tmpl/prog/en/modules/admin/items_search_field.tt
  20. 95
      koha-tmpl/intranet-tmpl/prog/en/modules/admin/items_search_fields.tt
  21. 1
      koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/advsearch.tt
  22. 4
      koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/itemsearch.csv.tt
  23. 12
      koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/itemsearch.json.tt
  24. 425
      koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/itemsearch.tt

5
C4/Installer/PerlDependencies.pm

@ -552,6 +552,11 @@ our $PERL_DEPS = {
'required' => '1',
'min_ver' => '0.03',
},
'Template::Plugin::JSON::Escape' => {
'usage' => 'Core',
'required' => '1',
'min_ver' => '0.02',
},
'DBD::Mock' => {
'usage' => 'Core',
'required' => '1',

197
C4/Items.pm

@ -35,6 +35,7 @@ use DateTime::Format::MySQL;
use Data::Dumper; # used as part of logging item record changes, not just for
# debugging; so please don't remove this
use Koha::DateUtils qw/dt_from_string/;
use C4::SQLHelper qw(GetColumns);
use vars qw($VERSION @ISA @EXPORT);
@ -85,6 +86,7 @@ BEGIN {
GetAnalyticsCount
GetItemHolds
SearchItemsByField
SearchItems
PrepareItemrecordDisplay
@ -2590,39 +2592,194 @@ sub GetItemHolds {
return $holds;
}
# Return the list of the column names of items table
sub _get_items_columns {
my $dbh = C4::Context->dbh;
my $sth = $dbh->column_info(undef, undef, 'items', '%');
$sth->execute;
my $results = $sth->fetchall_hashref('COLUMN_NAME');
return keys %$results;
=head2 SearchItemsByField
my $items = SearchItemsByField($field, $value);
SearchItemsByField will search for items on a specific given field.
For instance you can search all items with a specific stocknumber like this:
my $items = SearchItemsByField('stocknumber', $stocknumber);
=cut
sub SearchItemsByField {
my ($field, $value) = @_;
my $filters = [ {
field => $field,
query => $value,
} ];
my ($results) = SearchItems($filters);
return $results;
}
sub _SearchItems_build_where_fragment {
my ($filter) = @_;
my $where_fragment;
if (exists($filter->{conjunction})) {
my (@where_strs, @where_args);
foreach my $f (@{ $filter->{filters} }) {
my $fragment = _SearchItems_build_where_fragment($f);
if ($fragment) {
push @where_strs, $fragment->{str};
push @where_args, @{ $fragment->{args} };
}
}
my $where_str = '';
if (@where_strs) {
$where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
$where_fragment = {
str => $where_str,
args => \@where_args,
};
}
} else {
my @columns = GetColumns('items');
push @columns, GetColumns('biblio');
push @columns, GetColumns('biblioitems');
my @operators = qw(= != > < >= <= like);
my $field = $filter->{field};
if ( (0 < grep /^$field$/, @columns) or (substr($field, 0, 5) eq 'marc:') ) {
my $op = $filter->{operator};
my $query = $filter->{query};
if (!$op or (0 == grep /^$op$/, @operators)) {
$op = '='; # default operator
}
my $column;
if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
my $marcfield = $1;
my $marcsubfield = $2;
my $xpath;
if ($marcfield < 10) {
$xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
} else {
$xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
}
$column = "ExtractValue(marcxml, '$xpath')";
} else {
$column = $field;
}
if (ref $query eq 'ARRAY') {
if ($op eq '=') {
$op = 'IN';
} elsif ($op eq '!=') {
$op = 'NOT IN';
}
$where_fragment = {
str => "$column $op (" . join (',', ('?') x @$query) . ")",
args => $query,
};
} else {
$where_fragment = {
str => "$column $op ?",
args => [ $query ],
};
}
}
}
return $where_fragment;
}
=head2 SearchItems
my $items = SearchItems($field, $value);
my ($items, $total) = SearchItemsByField($filters, $params);
SearchItems will search for items on a specific given field.
For instance you can search all items with a specific stocknumber like this:
Perform a search among items
my $items = SearchItems('stocknumber', $stocknumber);
$filters is a reference to an array of filters, where each filter is a hash with
the following keys:
=over 2
=item * field: the name of a SQL column in table items
=item * query: the value to search in this column
=item * operator: comparison operator. Can be one of = != > < >= <= like
=back
A logical AND is used to combine filters.
$params is a reference to a hash that can contain the following parameters:
=over 2
=item * rows: Number of items to return. 0 returns everything (default: 0)
=item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
(default: 1)
=item * sortby: A SQL column name in items table to sort on
=item * sortorder: 'ASC' or 'DESC'
=back
=cut
sub SearchItems {
my ($field, $value) = @_;
my ($filter, $params) = @_;
$filter //= {};
$params //= {};
return unless ref $filter eq 'HASH';
return unless ref $params eq 'HASH';
# Default parameters
$params->{rows} ||= 0;
$params->{page} ||= 1;
$params->{sortby} ||= 'itemnumber';
$params->{sortorder} ||= 'ASC';
my ($where_str, @where_args);
my $where_fragment = _SearchItems_build_where_fragment($filter);
if ($where_fragment) {
$where_str = $where_fragment->{str};
@where_args = @{ $where_fragment->{args} };
}
my $dbh = C4::Context->dbh;
my @columns = _get_items_columns;
my $results = [];
if(0 < grep /^$field$/, @columns) {
my $query = "SELECT $field FROM items WHERE $field = ?";
my $sth = $dbh->prepare( $query );
$sth->execute( $value );
$results = $sth->fetchall_arrayref({});
my $query = q{
SELECT SQL_CALC_FOUND_ROWS items.*
FROM items
LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
};
if (defined $where_str and $where_str ne '') {
$query .= qq{ WHERE $where_str };
}
return $results;
my @columns = GetColumns('items');
push @columns, GetColumns('biblio');
push @columns, GetColumns('biblioitems');
my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
? $params->{sortby} : 'itemnumber';
my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
$query .= qq{ ORDER BY $sortby $sortorder };
my $rows = $params->{rows};
my @limit_args;
if ($rows > 0) {
my $offset = $rows * ($params->{page}-1);
$query .= qq { LIMIT ?, ? };
push @limit_args, $offset, $rows;
}
my $sth = $dbh->prepare($query);
my $rv = $sth->execute(@where_args, @limit_args);
return unless ($rv);
my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
return ($sth->fetchall_arrayref({}), $total_rows);
}

33
C4/SQLHelper.pm

@ -56,6 +56,7 @@ BEGIN {
UpdateInTable
GetPrimaryKeys
clear_columns_cache
GetColumns
);
%EXPORT_TAGS = ( all =>[qw( InsertInTable DeleteInTable SearchInTable UpdateInTable GetPrimaryKeys)]
);
@ -296,6 +297,38 @@ sub _get_columns {
return $hashref->{$tablename};
}
=head2 GetColumns
my @columns = GetColumns($tablename);
Given a tablename, returns an array of columns names.
=cut
sub GetColumns {
my ($tablename) = @_;
return unless $tablename;
return unless $tablename =~ /^[a-zA-Z0-9_]+$/;
# Get the database handle.
my $dbh = C4::Context->dbh;
# Pure ANSI SQL goodness.
my $sql = "SELECT * FROM $tablename WHERE 1=0;";
# Run the SQL statement to load STH's readonly properties.
my $sth = $dbh->prepare($sql);
my $rv = $sth->execute();
my @data;
if ($rv) {
@data = @{$sth->{NAME}};
}
return @data;
}
=head2 _filter_columns
=over 4

107
Koha/Item/Search/Field.pm

@ -0,0 +1,107 @@
package Koha::Item::Search::Field;
use Modern::Perl;
use base qw( Exporter );
our @EXPORT_OK = qw(
AddItemSearchField
ModItemSearchField
DelItemSearchField
GetItemSearchField
GetItemSearchFields
);
use C4::Context;
sub AddItemSearchField {
my ($field) = @_;
my ( $name, $label, $tagfield, $tagsubfield, $av_category ) =
@$field{qw(name label tagfield tagsubfield authorised_values_category)};
my $dbh = C4::Context->dbh;
my $query = q{
INSERT INTO items_search_fields (name, label, tagfield, tagsubfield, authorised_values_category)
VALUES (?, ?, ?, ?, ?)
};
my $sth = $dbh->prepare($query);
my $rv = $sth->execute($name, $label, $tagfield, $tagsubfield, $av_category);
return ($rv) ? $field : undef;
}
sub ModItemSearchField {
my ($field) = @_;
my ( $name, $label, $tagfield, $tagsubfield, $av_category ) =
@$field{qw(name label tagfield tagsubfield authorised_values_category)};
my $dbh = C4::Context->dbh;
my $query = q{
UPDATE items_search_fields
SET label = ?,
tagfield = ?,
tagsubfield = ?,
authorised_values_category = ?
WHERE name = ?
};
my $sth = $dbh->prepare($query);
my $rv = $sth->execute($label, $tagfield, $tagsubfield, $av_category, $name);
return ($rv) ? $field : undef;
}
sub DelItemSearchField {
my ($name) = @_;
my $dbh = C4::Context->dbh;
my $query = q{
DELETE FROM items_search_fields
WHERE name = ?
};
my $sth = $dbh->prepare($query);
my $rv = $sth->execute($name);
my $is_deleted = $rv ? int($rv) : 0;
if (!$is_deleted) {
warn "DelItemSearchField: Field '$name' doesn't exist";
}
return $is_deleted;
}
sub GetItemSearchField {
my ($name) = @_;
my $dbh = C4::Context->dbh;
my $query = q{
SELECT * FROM items_search_fields
WHERE name = ?
};
my $sth = $dbh->prepare($query);
my $rv = $sth->execute($name);
my $field;
if ($rv) {
$field = $sth->fetchrow_hashref;
}
return $field;
}
sub GetItemSearchFields {
my $dbh = C4::Context->dbh;
my $query = q{
SELECT * FROM items_search_fields
};
my $sth = $dbh->prepare($query);
my $rv = $sth->execute();
my @fields;
if ($rv) {
my $fields = $sth->fetchall_arrayref( {} );
@fields = @$fields;
}
return @fields;
}

2
acqui/check_uniqueness.pl

@ -43,7 +43,7 @@ my @value = $input->param('value[]');
my $r = {};
my $i = 0;
for ( my $i=0; $i<@field; $i++ ) {
my $items = C4::Items::SearchItems($field[$i], $value[$i]);
my $items = C4::Items::SearchItemsByField($field[$i], $value[$i]);
if ( @$items ) {
push @{ $r->{$field[$i]} }, $value[$i];

63
admin/items_search_field.pl

@ -0,0 +1,63 @@
#!/usr/bin/perl
# Copyright 2013 BibLibre
#
# 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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
use Modern::Perl;
use CGI;
use C4::Auth;
use C4::Output;
use C4::Koha;
use Koha::Item::Search::Field qw(GetItemSearchField ModItemSearchField);
my $cgi = new CGI;
my ($template, $borrowernumber, $cookie) = get_template_and_user({
template_name => 'admin/items_search_field.tt',
query => $cgi,
type => 'intranet',
authnotrequired => 0,
flagsrequired => { catalogue => 1 },
});
my $op = $cgi->param('op') || '';
my $name = $cgi->param('name');
if ($op eq 'mod') {
my %vars = $cgi->Vars;
my $field = { name => $name };
my @params = qw(label tagfield tagsubfield authorised_values_category);
@$field{@params} = @vars{@params};
if ( $field->{authorised_values_category} eq '' ) {
$field->{authorised_values_category} = undef;
}
$field = ModItemSearchField($field);
my $updated = ($field) ? 1 : 0;
print $cgi->redirect('/cgi-bin/koha/admin/items_search_fields.pl?updated=' . $updated);
exit;
}
my $field = GetItemSearchField($name);
my $authorised_values_categories = C4::Koha::GetAuthorisedValueCategories();
$template->param(
field => $field,
authorised_values_categories => $authorised_values_categories,
);
output_html_with_http_headers $cgi, $cookie, $template->output;

81
admin/items_search_fields.pl

@ -0,0 +1,81 @@
#!/usr/bin/perl
# Copyright 2013 BibLibre
#
# 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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
use Modern::Perl;
use CGI;
use C4::Auth;
use C4::Output;
use C4::Koha;
use Koha::Item::Search::Field qw(AddItemSearchField GetItemSearchFields DelItemSearchField);
my $cgi = new CGI;
my ($template, $borrowernumber, $cookie) = get_template_and_user({
template_name => 'admin/items_search_fields.tt',
query => $cgi,
type => 'intranet',
authnotrequired => 0,
flagsrequired => { catalogue => 1 },
});
my $op = $cgi->param('op') || '';
if ($op eq 'add') {
my %vars = $cgi->Vars;
my $field;
my @params = qw(name label tagfield tagsubfield authorised_values_category);
@$field{@params} = @vars{@params};
if ( $field->{authorised_values_category} eq '' ) {
$field->{authorised_values_category} = undef;
}
$field = AddItemSearchField($field);
if ($field) {
$template->param(field_added => $field);
} else {
$template->param(field_not_added => 1);
}
} elsif ($op eq 'del') {
my $name = $cgi->param('name');
my $rv = DelItemSearchField($name);
if ($rv) {
$template->param(field_deleted => 1);
} else {
$template->param(field_not_deleted => 1);
}
} else {
my $updated = $cgi->param('updated');
if (defined $updated) {
if ($updated) {
$template->param(field_updated => 1);
} else {
$template->param(field_not_updated => 1);
}
}
}
my @fields = GetItemSearchFields();
my $authorised_values_categories = C4::Koha::GetAuthorisedValueCategories();
$template->param(
fields => \@fields,
authorised_values_categories => $authorised_values_categories,
);
output_html_with_http_headers $cgi, $cookie, $template->output;

298
catalogue/itemsearch.pl

@ -0,0 +1,298 @@
#!/usr/bin/perl
# Copyright 2013 BibLibre
#
# 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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
use Modern::Perl;
use CGI;
use JSON;
use C4::Auth;
use C4::Output;
use C4::Items;
use C4::Biblio;
use C4::Branch;
use C4::Koha;
use C4::ItemType;
use Koha::Item::Search::Field qw(GetItemSearchFields);
my $cgi = new CGI;
my %params = $cgi->Vars;
my $format = $cgi->param('format');
my ($template_name, $content_type);
if (defined $format and $format eq 'json') {
$template_name = 'catalogue/itemsearch.json.tt';
$content_type = 'json';
# Map DataTables parameters with 'regular' parameters
$cgi->param('rows', $cgi->param('iDisplayLength'));
$cgi->param('page', ($cgi->param('iDisplayStart') / $cgi->param('iDisplayLength')) + 1);
my @columns = split /,/, $cgi->param('sColumns');
$cgi->param('sortby', $columns[ $cgi->param('iSortCol_0') ]);
$cgi->param('sortorder', $cgi->param('sSortDir_0'));
my @f = $cgi->param('f');
my @q = $cgi->param('q');
push @q, '' if @q == 0;
my @op = $cgi->param('op');
my @c = $cgi->param('c');
foreach my $i (0 .. ($cgi->param('iColumns') - 1)) {
my $sSearch = $cgi->param("sSearch_$i");
if ($sSearch) {
my @words = split /\s+/, $sSearch;
foreach my $word (@words) {
push @f, $columns[$i];
push @q, "%$word%";
push @op, 'like';
push @c, 'and';
}
}
}
$cgi->param('f', @f);
$cgi->param('q', @q);
$cgi->param('op', @op);
$cgi->param('c', @c);
} elsif (defined $format and $format eq 'csv') {
$template_name = 'catalogue/itemsearch.csv.tt';
# Retrieve all results
$cgi->param('rows', 0);
} else {
$format = 'html';
$template_name = 'catalogue/itemsearch.tt';
$content_type = 'html';
}
my ($template, $borrowernumber, $cookie) = get_template_and_user({
template_name => $template_name,
query => $cgi,
type => 'intranet',
authnotrequired => 0,
flagsrequired => { catalogue => 1 },
});
my $notforloan_avcode = GetAuthValCode('items.notforloan');
my $notforloan_values = GetAuthorisedValues($notforloan_avcode);
if (scalar keys %params > 0) {
# Parameters given, it's a search
my $filter = {
conjunction => 'AND',
filters => [],
};
foreach my $p (qw(homebranch location itype ccode issues datelastborrowed)) {
if (my @q = $cgi->param($p)) {
if ($q[0] ne '') {
my $f = {
field => $p,
query => \@q,
};
if (my $op = $cgi->param($p . '_op')) {
$f->{operator} = $op;
}
push @{ $filter->{filters} }, $f;
}
}
}
my @c = $cgi->param('c');
my @fields = $cgi->param('f');
my @q = $cgi->param('q');
my @op = $cgi->param('op');
my $f;
for (my $i = 0; $i < @fields; $i++) {
my $field = $fields[$i];
my $q = shift @q;
my $op = shift @op;
if (defined $q and $q ne '') {
if ($i == 0) {
$f = {
field => $field,
query => $q,
operator => $op,
};
} else {
my $c = shift @c;
$f = {
conjunction => $c,
filters => [
$f, {
field => $field,
query => $q,
operator => $op,
}
],
};
}
}
}
push @{ $filter->{filters} }, $f;
# Yes/No parameters
foreach my $p (qw(damaged itemlost)) {
my $v = $cgi->param($p) // '';
my $f = {
field => $p,
query => 0,
};
if ($v eq 'yes') {
$f->{operator} = '!=';
push @{ $filter->{filters} }, $f;
} elsif ($v eq 'no') {
$f->{operator} = '=';
push @{ $filter->{filters} }, $f;
}
}
if (my $itemcallnumber_from = $cgi->param('itemcallnumber_from')) {
push @{ $filter->{filters} }, {
field => 'itemcallnumber',
query => $itemcallnumber_from,
operator => '>=',
};
}
if (my $itemcallnumber_to = $cgi->param('itemcallnumber_to')) {
push @{ $filter->{filters} }, {
field => 'itemcallnumber',
query => $itemcallnumber_to,
operator => '<=',
};
}
my $search_params = {
rows => $cgi->param('rows') // 20,
page => $cgi->param('page') || 1,
sortby => $cgi->param('sortby') || 'itemnumber',
sortorder => $cgi->param('sortorder') || 'asc',
};
my ($results, $total_rows) = SearchItems($filter, $search_params);
if ($results) {
# Get notforloan labels
my $notforloan_map = {};
foreach my $nfl_value (@$notforloan_values) {
$notforloan_map->{$nfl_value->{authorised_value}} = $nfl_value->{lib};
}
foreach my $item (@$results) {
$item->{biblio} = GetBiblio($item->{biblionumber});
($item->{biblioitem}) = GetBiblioItemByBiblioNumber($item->{biblionumber});
$item->{status} = $notforloan_map->{$item->{notforloan}};
}
}
$template->param(
filter => $filter,
search_params => $search_params,
results => $results,
total_rows => $total_rows,
search_done => 1,
);
if ($format eq 'html') {
# Build pagination bar
my $url = $cgi->url(-absolute => 1);
my @params;
foreach my $p (keys %params) {
my @v = $cgi->param($p);
push @params, map { "$p=" . $_ } @v;
}
$url .= '?' . join ('&', @params);
my $nb_pages = 1 + int($total_rows / $search_params->{rows});
my $current_page = $search_params->{page};
my $pagination_bar = pagination_bar($url, $nb_pages, $current_page, 'page');
$template->param(pagination_bar => $pagination_bar);
}
}
if ($format eq 'html') {
# Retrieve data required for the form.
my $branches = GetBranches();
my @branches;
foreach my $branchcode (keys %$branches) {
push @branches, {
value => $branchcode,
label => $branches->{$branchcode}->{branchname},
};
}
my $locations = GetAuthorisedValues('LOC');
my @locations;
foreach my $location (@$locations) {
push @locations, {
value => $location->{authorised_value},
label => $location->{lib},
};
}
my @itemtypes = C4::ItemType->all();
foreach my $itemtype (@itemtypes) {
$itemtype->{value} = $itemtype->{itemtype};
$itemtype->{label} = $itemtype->{description};
}
my $ccode_avcode = GetAuthValCode('items.ccode') || 'CCODE';
my $ccodes = GetAuthorisedValues($ccode_avcode);
my @ccodes;
foreach my $ccode (@$ccodes) {
push @ccodes, {
value => $ccode->{authorised_value},
label => $ccode->{lib},
};
}
my @notforloans;
foreach my $value (@$notforloan_values) {
push @notforloans, {
value => $value->{authorised_value},
label => $value->{lib},
};
}
my @items_search_fields = GetItemSearchFields();
my $authorised_values = {};
foreach my $field (@items_search_fields) {
if (my $category = ($field->{authorised_values_category})) {
$authorised_values->{$category} = GetAuthorisedValues($category);
}
}
$template->param(
branches => \@branches,
locations => \@locations,
itemtypes => \@itemtypes,
ccodes => \@ccodes,
notforloans => \@notforloans,
items_search_fields => \@items_search_fields,
authorised_values_json => to_json($authorised_values),
);
}
if ($format eq 'csv') {
print $cgi->header({
type => 'text/csv',
attachment => 'items.csv',
});
print $template->output;
} else {
output_with_http_headers $cgi, $cookie, $template->output, $content_type;
}

17
installer/data/mysql/kohastructure.sql

@ -3461,6 +3461,23 @@ CREATE TABLE IF NOT EXISTS columns_settings (
PRIMARY KEY(module, page, tablename, columnname)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Table structure for table 'items_search_fields'
--
DROP TABLE IF EXISTS items_search_fields;
CREATE TABLE items_search_fields (
name VARCHAR(255) NOT NULL,
label VARCHAR(255) NOT NULL,
tagfield CHAR(3) NOT NULL,
tagsubfield CHAR(1) NULL DEFAULT NULL,
authorised_values_category VARCHAR(16) NULL DEFAULT NULL,
PRIMARY KEY(name),
CONSTRAINT items_search_fields_authorised_values_category
FOREIGN KEY (authorised_values_category) REFERENCES authorised_values (category)
ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;

19
installer/data/mysql/updatedatabase.pl

@ -8986,6 +8986,25 @@ if ( CheckVersion($DBversion) ) {
SetVersion($DBversion);
}
$DBversion = "3.17.00.XXX";
if ( CheckVersion($DBversion) ) {
$dbh->do(q{
CREATE TABLE IF NOT EXISTS items_search_fields (
name VARCHAR(255) NOT NULL,
label VARCHAR(255) NOT NULL,
tagfield CHAR(3) NOT NULL,
tagsubfield CHAR(1) NULL DEFAULT NULL,
authorised_values_category VARCHAR(16) NULL DEFAULT NULL,
PRIMARY KEY(name),
CONSTRAINT items_search_fields_authorised_values_category
FOREIGN KEY (authorised_values_category) REFERENCES authorised_values (category)
ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
});
print "Upgrade to $DBversion done (Bug 11425: Add items_search_fields table)\n";
SetVersion($DBversion);
}
=head1 FUNCTIONS
=head2 TableExists($table)

44
koha-tmpl/intranet-tmpl/prog/en/css/itemsearchform.css

@ -0,0 +1,44 @@
.form-field {
margin: 5px 0;
}
.form-field > * {
vertical-align: middle;
}
.form-field-label {
display: inline-block;
text-align: right;
width: 10em;
}
.form-field-conjunction[disabled] {
visibility: hidden;
}
.form-field-radio > * {
vertical-align: middle;
}
.form-actions {
margin-top: 20px;
}
th.active {
padding-right: 21px;
background-repeat: no-repeat;
background-position: 100% 50%;
}
th.sort-asc {
background-image: url('../../img/asc.gif');
}
th.sort-desc {
background-image: url('../../img/desc.gif');
}
th select {
width: 100%;
font-weight: normal;
}

58
koha-tmpl/intranet-tmpl/prog/en/includes/admin-items-search-field-form.inc

@ -0,0 +1,58 @@
<ul>
<li>
<label for="name">Name</label>
[% IF field %]
<input type="text" name="name" value="[% field.name %]" disabled="disabled">
<input type="hidden" name="name" value="[% field.name %]">
[% ELSE %]
<input type="text" name="name" />
[% END %]
</li>
<li>
<label for="label">Label</label>
[% IF field %]
<input type="text" name="label" value="[% field.label %]" />
[% ELSE %]
<input type="text" name="label" />
[% END %]
</li>
<li>
<label for="tagfield">MARC field</label>
<select name="tagfield">
[% FOREACH tagfield IN ['001'..'999'] %]
[% IF field && field.tagfield == tagfield %]
<option value="[% tagfield %]" selected="selected">[% tagfield %]</option>
[% ELSE %]
<option value="[% tagfield %]">[% tagfield %]</option>
[% END %]
[% END %]
</select>
</li>
<li>
<label for="tagsubfield">MARC subfield</label>
<select name="tagsubfield">
[% codes = [''] %]
[% codes = codes.merge([0..9], ['a'..'z']) %]
[% FOREACH tagsubfield IN codes %]
[% IF field && field.tagsubfield == tagsubfield %]
<option value="[% tagsubfield %]" selected="selected">[% tagsubfield %]</option>
[% ELSE %]
<option value="[% tagsubfield %]">[% tagsubfield %]</option>
[% END %]
[% END %]
</select>
</li>
<li>
<label for="authorised_values_category">Authorised values category</label>
<select name="authorised_values_category">
<option value="">- None -</option>
[% FOREACH category IN authorised_values_categories %]
[% IF field && field.authorised_values_category == category %]
<option value="[% category %]" selected="selected">[% category %]</option>
[% ELSE %]
<option value="[% category %]">[% category %]</option>
[% END %]
[% END %]
</select>
</li>
</ul>

1
koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc

@ -45,6 +45,7 @@
<li><a href="/cgi-bin/koha/admin/classsources.pl">Classification sources</a></li>
<li><a href="/cgi-bin/koha/admin/matching-rules.pl">Record matching rules</a></li>
<li><a href="/cgi-bin/koha/admin/oai_sets.pl">OAI sets configuration</a></li>
<li><a href="/cgi-bin/koha/admin/items_search_fields.pl">Items search fields</a></li>
</ul>
<h5>Acquisition parameters</h5>

4
koha-tmpl/intranet-tmpl/prog/en/includes/catalogue/itemsearch_item.csv.inc

@ -0,0 +1,4 @@
[%- USE Branches -%]
[%- biblio = item.biblio -%]
[%- biblioitem = item.biblioitem -%]
"[% biblio.title |html %] by [% biblio.author |html %]", "[% biblioitem.publicationyear |html %]", "[% biblioitem.publishercode |html %]", "[% biblioitem.collectiontitle |html %]", "[% item.barcode |html %]", "[% item.itemcallnumber |html %]", "[% Branches.GetName(item.homebranch) |html %]", "[% Branches.GetName(item.holdingbranch) |html %]", "[% item.location |html %]", "[% item.stocknumber |html %]", "[% item.status |html %]", "[% (item.issues || 0) |html %]"

23
koha-tmpl/intranet-tmpl/prog/en/includes/catalogue/itemsearch_item.inc

@ -0,0 +1,23 @@
[%- USE Branches -%]
[% biblio = item.biblio %]
[% biblioitem = item.biblioitem %]
<tr>
<td>
<a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% biblio.biblionumber %]" title="Go to record detail page">[% biblio.title %]</a>
by [% biblio.author %]
</td>
<td>[% biblioitem.publicationyear %]</td>
<td>[% biblioitem.publishercode %]</td>
<td>[% biblioitem.collectiontitle %]</td>
<td>
<a href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=[% biblio.biblionumber %]#item[% item.itemnumber %]" title="Go to item details">[% item.barcode %]</a>
</td>
<td>[% item.itemcallnumber %]</td>
<td>[% Branches.GetName(item.homebranch) %]</td>
<td>[% Branches.GetName(item.holdingbranch) %]</td>
<td>[% item.location %]</td>
<td>[% item.stocknumber %]</td>
<td>[% item.status %]</td>
<td>[% item.issues || 0 %]</td>
<td><a href="/cgi-bin/koha/cataloguing/additem.pl?op=edititem&biblionumber=[% item.biblionumber %]&itemnumber=[% item.itemnumber %]">Modify</a></td>
</tr>

18
koha-tmpl/intranet-tmpl/prog/en/includes/catalogue/itemsearch_item.json.inc

@ -0,0 +1,18 @@
[%- USE Branches -%]
[%- biblio = item.biblio -%]
[%- biblioitem = item.biblioitem -%]
[
"<a href='/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% biblio.biblionumber %]' title='Go to record detail page'>[% biblio.title |html %]</a> by [% biblio.author |html %]",
"[% biblioitem.publicationyear |html %]",
"[% biblioitem.publishercode |html %]",
"[% biblioitem.collectiontitle |html %]",
"<a href='/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=[% biblio.biblionumber %]#item[% item.itemnumber %]' title='Go to item details'>[% item.barcode |html %]</a>",
"[% item.itemcallnumber |html %]",
"[% Branches.GetName(item.homebranch) |html %]",
"[% Branches.GetName(item.holdingbranch) |html %]",
"[% item.location |html %]",
"[% item.stocknumber |html %]",
"[% item.status |html %]",
"[% (item.issues || 0) |html %]",
"<a href='/cgi-bin/koha/cataloguing/additem.pl?op=edititem&biblionumber=[% item.biblionumber %]&itemnumber=[% item.itemnumber %]'>Modify</a>"
]

49
koha-tmpl/intranet-tmpl/prog/en/includes/catalogue/itemsearch_items.inc

@ -0,0 +1,49 @@
[% names = CGI.param() %]
[% params = [] %]
[% FOREACH name IN names %]
[% IF name != 'sortby' AND name != 'sortorder' %]
[% params.push(name _ "=" _ CGI.param(name)) %]
[% END %]
[% END %]
[% base_url = "/cgi-bin/koha/catalogue/itemsearch.pl?" _ params.join('&') %]
[% BLOCK itemsearch_header %]
[% sortorder = 'asc' %]
[% classes = [] %]
[% IF CGI.param('sortby') == name %]
[% classes.push('active') %]
[% classes.push('sort-' _ CGI.param('sortorder')) %]
[% IF CGI.param('sortorder') == 'asc' %]
[% sortorder = 'desc' %]
[% END %]
[% END %]
[% url = base_url _ '&sortby=' _ name _ '&sortorder=' _ sortorder %]
<th class="[% classes.join(' ') %]">
<a href="[% url %]" title="Sort on [% label %] ([% sortorder %])">[% text %]</a>
</th>
[% END %]
<table>
<thead>
<tr>
[% INCLUDE itemsearch_header name='title' label='title' text='Bibliographic reference' %]
[% INCLUDE itemsearch_header name='publicationyear' label='publication date' text='Publication date' %]
[% INCLUDE itemsearch_header name='publishercode' label='publisher' text='Publisher' %]
[% INCLUDE itemsearch_header name='collectiontitle' label='collection' text='Collection' %]
[% INCLUDE itemsearch_header name='barcode' label='barcode' text='Barcode' %]
[% INCLUDE itemsearch_header name='itemcallnumber' label='callnumber' text='Callnumber' %]
[% INCLUDE itemsearch_header name='homebranch' label='home branch' text='Home branch' %]
[% INCLUDE itemsearch_header name='holdingbranch' label='holding branch' text='Holding branch' %]
[% INCLUDE itemsearch_header name='location' label='location' text='Location' %]
[% INCLUDE itemsearch_header name='stocknumber' label='stock number' text='Stock number' %]
[% INCLUDE itemsearch_header name='notforloan' label='status' text='Status' %]
[% INCLUDE itemsearch_header name='issues' label='issues' text='Issues' %]
<th></th>
</tr>
</thead>
<tbody>
[% FOREACH item IN items %]
[% INCLUDE 'catalogue/itemsearch_item.inc' item = item %]
[% END %]
</tbody>
</table>

2
koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt

@ -77,6 +77,8 @@
<dd>Manage rules for automatically matching MARC records during record imports.</dd>
<dt><a href="/cgi-bin/koha/admin/oai_sets.pl">OAI sets configuration</a></dt>
<dd>Manage OAI Sets</dd>
<dt><a href="/cgi-bin/koha/admin/items_search_fields.pl">Items search fields</a></dt>
<dd>Manage custom fields for items search</dd>
</dl>
<h3>Acquisition parameters</h3>

41
koha-tmpl/intranet-tmpl/prog/en/modules/admin/items_search_field.tt

@ -0,0 +1,41 @@
[% INCLUDE 'doc-head-open.inc' %]
<title>Koha &rsaquo; Administration &rsaquo; Items search fields</title>
[% INCLUDE 'doc-head-close.inc' %]
</head>
<body id="admin_itemssearchfields" class="admin">
[% INCLUDE 'header.inc' %]
[% INCLUDE 'cat-search.inc' %]
<div id="breadcrumbs">
<a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo;
<a href="/cgi-bin/koha/admin/admin-home.pl">Administration</a> &rsaquo;
<a href="/cgi-bin/koha/admin/items_search_fields.pl">Items search fields</a> &rsaquo;
[% field.name %]
</div>
<div id="doc3" class="yui-t2">
<div id="bd">
<div id="yui-main">
<div class="yui-b">
<h1>Items search field: [% field.label %]</h1>
<form action="" method="POST">
<fieldset class="rows">
<legend>Edit field</legend>
[% INCLUDE 'admin-items-search-field-form.inc' field=field %]
<div>
<input type="hidden" name="op" value="mod" />
</div>
<fieldset class="action">
<input type="submit" value="Update" />
</fieldset>
</fieldset>
</form>
<a href="/cgi-bin/koha/admin/items_search_fields.pl">Return to items search fields overview page</a>
</div>
</div>
<div class="yui-b">
[% INCLUDE 'admin-menu.inc' %]
</div>
</div>
[% INCLUDE 'intranet-bottom.inc' %]

95
koha-tmpl/intranet-tmpl/prog/en/modules/admin/items_search_fields.tt

@ -0,0 +1,95 @@
[% INCLUDE 'doc-head-open.inc' %]
<title>Koha &rsaquo; Administration &rsaquo; Items search fields</title>
[% INCLUDE 'doc-head-close.inc' %]
</head>
<body id="admin_itemssearchfields" class="admin">
[% INCLUDE 'header.inc' %]
[% INCLUDE 'cat-search.inc' %]
<div id="breadcrumbs">
<a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo;
<a href="/cgi-bin/koha/admin/admin-home.pl">Administration</a> &rsaquo;
Items search fields
</div>
<div id="doc3" class="yui-t2">
<div id="bd">
<div id="yui-main">
<div class="yui-b">
[% IF field_added %]
<div class="dialog">
Field successfully added: [% field_added.label %]
</div>
[% ELSIF field_not_added %]
<div class="alert">
<p>Failed to add field. Please check if the field name doesn't already exist.</p>
<p>Check logs for more details.</p>
</div>
[% ELSIF field_deleted %]
<div class="dialog">
Field successfully deleted.
</div>
[% ELSIF field_not_deleted %]
<div class="alert">
<p>Failed to delete field.</p>
<p>Check logs for more details.</p>
</div>
[% ELSIF field_updated %]
<div class="dialog">
Field successfully updated: [% field_updated.label %]
</div>
[% ELSIF field_not_updated %]
<div class="alert">
<p>Failed to update field.</p>
<p>Check logs for more details.</p>
</div>
[% END %]
<h1>Items search fields</h1>
[% IF fields.size %]
<table>
<thead>
<tr>
<th>Name</th>
<th>Label</th>
<th>MARC field</th>
<th>MARC subfield</th>
<th>Authorised values category</th>
<th>Operations</th>
</tr>
</thead>
<tbody>
[% FOREACH field IN fields %]
<tr>
<td>[% field.name %]</td>
<td>[% field.label %]</td>
<td>[% field.tagfield %]</td>
<td>[% field.tagsubfield %]</td>
<td>[% field.authorised_values_category %]</td>
<td>
<a href="/cgi-bin/koha/admin/items_search_field.pl?name=[% field.name %]" title="Edit [% field.name %] field">Edit</a>
<a href="/cgi-bin/koha/admin/items_search_fields.pl?op=del&name=[% field.name %]" title="Delete [% field.name %] field">Delete</a>
</td>
</tr>
[% END %]
</tbody>
</table>
[% END %]
<form action="" method="POST">
<fieldset class="rows">
<legend>Add a new field</legend>
[% INCLUDE 'admin-items-search-field-form.inc' field=undef %]
<div>
<input type="hidden" name="op" value="add" />
</div>
<fieldset class="action">
<input type="submit" value="Add this field" />
</fieldset>
</fieldset>
</form>
</div>
</div>
<div class="yui-b">
[% INCLUDE 'admin-menu.inc' %]
</div>
</div>
[% INCLUDE 'intranet-bottom.inc' %]

1
koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/advsearch.tt

@ -30,6 +30,7 @@
<form action="search.pl" method="get">
<div id="advanced-search">
<h1>Advanced search</h1>
<a href="/cgi-bin/koha/catalogue/itemsearch.pl">Go to item search</a>
[% IF ( outer_servers_loop ) %]
<!-- DATABASES -->

4
koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/itemsearch.csv.tt

@ -0,0 +1,4 @@
Bibliographic reference, Publication Date, Publisher, Collection, Barcode, Callnumber, Home branch, Holding branch, Location, Stock number, Status, Issues
[% FOREACH item IN results -%]
[%- INCLUDE 'catalogue/itemsearch_item.csv.inc' item = item -%]
[%- END -%]

12
koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/itemsearch.json.tt

@ -0,0 +1,12 @@
[%- USE CGI -%]
{
"sEcho": [% CGI.param('sEcho') %],
"iTotalRecords": [% total_rows %],
"iTotalDisplayRecords": [% total_rows %],
"aaData": [
[%- FOREACH item IN results -%]
[%- INCLUDE 'catalogue/itemsearch_item.json.inc' item = item -%]
[%- UNLESS loop.last %],[% END -%]
[%- END -%]
]
}

425
koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/itemsearch.tt

@ -0,0 +1,425 @@
[% USE CGI %]
[% USE JSON.Escape %]
[% BLOCK form_field_select %]
<div class="form-field form-field-select">
<label class="form-field-label" for="[% name %]">[% label %]</label>
<select id="[% name %]_op" name="[% name %]_op">
<option value="=">is</option>
[% IF CGI.param(name _ '_op') == '!=' %]
<option value="!=" selected="selected">is not</option>
[% ELSE %]
<option value="!=" >is not</option>
[% END %]
</select>
[% values = CGI.param(name) %]
<select id="[% name %]" name="[% name %]" multiple="multiple" size="[% options.size < 4 ? options.size + 1 : 4 %]">
[% IF (values == '') %]
<option value="" selected="selected">
[% ELSE %]
<option value="">
[% END %]
[% empty_option || "All" %]
</option>
[% FOREACH option IN options %]
[% IF values.grep(option.value).size %]
<option value="[% option.value %]" selected="selected">[% option.label %]</option>
[% ELSE %]
<option value="[% option.value %]">[% option.label %]</option>
[% END %]
[% END %]
</select>
</div>
[% END %]
[% BLOCK form_field_select_option %]
[% IF params.f == value %]
<option value="[% value %]" selected="selected">[% label %]</option>
[% ELSE %]
<option value="[% value %]">[% label %]</option>
[% END %]
[% END %]
[% BLOCK form_field_select_text %]
<div class="form-field form-field-select-text">
[% IF params.exists('c') %]
<select name="c" class="form-field-conjunction">
<option value="and">AND</option>
[% IF params.c == 'or' %]
<option value="or" selected="selected">OR</option>
[% ELSE %]
<option value="or">OR</option>
[% END %]
</select>
[% ELSE %]
<select name="c" class="form-field-conjunction" disabled="disabled">
<option value="and">AND</option>
<option value="or">OR</option>
</select>
[% END %]
<select name="f" class="form-field-column">
[% INCLUDE form_field_select_option value='barcode' label='Barcode' %]
[% INCLUDE form_field_select_option value='itemcallnumber' label='Callnumber' %]
[% INCLUDE form_field_select_option value='stocknumber' label='Stock number' %]
[% INCLUDE form_field_select_option value='title' label='Title' %]
[% INCLUDE form_field_select_option value='author' label='Author' %]
[% INCLUDE form_field_select_option value='publishercode' label='Publisher' %]
[% INCLUDE form_field_select_option value='publicationdate' label='Publication date' %]
[% INCLUDE form_field_select_option value='collectiontitle' label='Collection' %]
[% INCLUDE form_field_select_option value='isbn' label='ISBN' %]
[% INCLUDE form_field_select_option value='issn' label='ISSN' %]
[% IF items_search_fields.size %]
<optgroup label="Custom search fields">
[% FOREACH field IN items_search_fields %]
[% marcfield = field.tagfield %]
[% IF field.tagsubfield %]
[% marcfield = marcfield _ '$' _ field.tagsubfield %]
[% END %]
[% IF params.f == "marc:$marcfield" %]
<option value="marc:[% marcfield %]" data-authorised-values-category="[% field.authorised_values_category %]" selected="selected">[% field.label %] ([% marcfield %])</option>
[% ELSE %]
<option value="marc:[% marcfield %]" data-authorised-values-category="[% field.authorised_values_category %]">[% field.label %] ([% marcfield %])</option>
[% END %]
[% END %]
</optgroup>
[% END %]
</select>
<input type="text" name="q" class="form-field-value" value="[% params.q %]" />
<input type="hidden" name="op" value="like" />
</div>
[% END %]
[% BLOCK form_field_select_text_block %]
[% c = CGI.param('c').list %]
[% f = CGI.param('f').list %]
[% q = CGI.param('q').list %]
[% op = CGI.param('op').list %]
[% IF q.size %]
[% size = q.size - 1 %]
[% FOREACH i IN [0 .. size] %]
[%
params = {
f => f.$i
q = q.$i
op = op.$i
}
%]
[% IF i > 0 %]
[% j = i - 1 %]
[% params.c = c.$j %]
[% END %]
[% INCLUDE form_field_select_text params=params %]
[% END %]
[% ELSE %]
[% INCLUDE form_field_select_text %]
[% END %]
[% END %]
[% BLOCK form_field_radio_yes_no %]
<div class="form-field">
<label class="form-field-label" for="[% name %]">[% label %]:</label>
<input type="radio" name="[% name %]" id="[% name %]_indifferent" value="" checked="checked"/>
<label for="[% name %]_indifferent">Indifferent</label>
<input type="radio" name="[% name %]" id="[% name %]_yes" value="yes" />
<label for="[% name %]_yes">Yes</label>
<input type="radio" name="[% name %]" id="[% name %]_no" value="no" />
<label for="[% name %]_no">No</label>
</div>
[% END %]
[%# Page starts here %]
[% INCLUDE 'doc-head-open.inc' %]
<title>Koha &rsaquo; Catalog &rsaquo; Advanced search</title>
[% INCLUDE 'doc-head-close.inc' %]
[% INCLUDE 'datatables.inc' %]
<script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/jquery.dataTables.columnFilter.js"></script>
<link rel="stylesheet" type="text/css" href="[% themelang %]/css/datatables.css" />
<link rel="stylesheet" type="text/css" href="[% themelang %]/css/itemsearchform.css" />
<script type="text/javascript">
//<![CDATA[
var authorised_values = [% authorised_values_json %];
function loadAuthorisedValuesSelect(select) {
var selected = select.find('option:selected');
var category = selected.data('authorised-values-category');
var form_field_value = select.siblings('.form-field-value');
if (category && category in authorised_values) {
var values = authorised_values[category];
var html = '<select name="q" class="form-field-value">\n';
for (i in values) {
var value = values[i];
html += '<option value="' + value.authorised_value + '">' + value.lib + '</option>\n';
}
html += '</select>\n';
var new_form_field_value = $(html);
new_form_field_value.val(form_field_value.val());
form_field_value.replaceWith(new_form_field_value);
} else {
if (form_field_value.prop('tagName').toLowerCase() == 'select') {
html = '<input name="q" type="text" class="form-field-value" />';
var new_form_field_value = $(html);
form_field_value.replaceWith(new_form_field_value);
}
}
}
function addNewField() {
var form_field = $('div.form-field-select-text').last();
var copy = form_field.clone(true);
copy.find('input,select').not('[type="hidden"]').each(function() {
$(this).val('');
});
copy.find('.form-field-conjunction').removeAttr('disabled');
form_field.after(copy);
copy.find('select.form-field-column').change();
}
function submitForm($form) {
var tr = ''
+ ' <tr>'
+ ' <th>' + _("Bibliographic reference") + '</th>'
+ ' <th>' + _("Publication date") + '</th>'
+ ' <th>' + _("Publisher") + '</th>'
+ ' <th>' + _("Collection") + '</th>'
+ ' <th>' + _("Barcode") + '</th>'
+ ' <th>' + _("Callnumber") + '</th>'
+ ' <th>' + _("Home branch") + '</th>'
+ ' <th>' + _("Holding branch") + '</th>'
+ ' <th>' + _("Location") + '</th>'
+ ' <th>' + _("Stock number") + '</th>'
+ ' <th>' + _("Status") + '</th>'
+ ' <th>' + _("Issues") + '</th>'
+ ' <th></th>'
+ ' </tr>'
var table = ''
+ '<table id="results">'
+ ' <thead>' + tr + tr + '</thead>'
+ ' <tbody></tbody>'
+ '</table>';
$('#results-wrapper').empty().html(table);
var params = [];
$form.find('select,input[type="text"],input[type="hidden"]').not('[disabled]').each(function () {
params.push({ 'name': $(this).attr('name'), 'value': $(this).val() });
});
$form.find('input[type="radio"]:checked').each(function() {
params.push({ 'name': $(this).attr('name'), 'value': $(this).val() });
});
$('#results').dataTable($.extend(true, {}, dataTablesDefaults, {
'bDestroy': true,
'bServerSide': true,
'sAjaxSource': '/cgi-bin/koha/catalogue/itemsearch.pl',
'fnServerParams': function(aoData) {
aoData.push( { 'name': 'format', 'value': 'json' } );
for (i in params) {
aoData.push(params[i]);
}
},
'sDom': '<"top pager"ilp>t<"bottom pager"ip>',
'aoColumns': [
{ 'sName': 'title' },
{ 'sName': 'publicationyear' },
{ 'sName': 'publishercode' },
{ 'sName': 'collectiontitle' },
{ 'sName': 'barcode' },
{ 'sName': 'itemcallnumber' },
{ 'sName': 'homebranch' },
{ 'sName': 'holdingbranch' },
{ 'sName': 'location' },
{ 'sName': 'stocknumber' },
{ 'sName': 'notforloan' },
{ 'sName': 'issues' },
{ 'sName': 'checkbox', 'bSortable': false }
]
})).columnFilter({
'sPlaceHolder': 'head:after',
'aoColumns': [
{ 'type': 'text' },
{ 'type': 'text' },
{ 'type': 'text' },
{ 'type': 'text' },
{ 'type': 'text' },
{ 'type': 'text' },
{ 'type': 'select', 'values': [% branches.json %] },
{ 'type': 'select', 'values': [% branches.json %] },
{ 'type': 'select', 'values': [% locations.json %] },
{ 'type': 'text' },
{ 'type': 'select', 'values': [% notforloans.json %] },
{ 'type': 'text' },
null
]
});
}
function hideForm($form) {
$form.hide();
$('#editsearchlink').show();
}
$(document).ready(function () {
// Add the "New field" link.
var form_field = $('div.form-field-select-text').last()
var button_field_new = $('<a href="#" class="button-field-new" title="Add a new field">New field</a>');
button_field_new.click(function() {
addNewField();
return false;
});
form_field.after(button_field_new);
// If a field is linked to an authorised values list, display the list.
$('div.form-field-select-text select').change(function() {
loadAuthorisedValuesSelect($(this));
}).change();
// Prevent user to select the 'All ...' option with other options.
$('div.form-field-select').each(function() {
$(this).find('select').filter(':last').change(function() {
values = $(this).val();
if (values.length > 1) {
var idx = $.inArray('', values);
if (idx != -1) {
values.splice(idx, 1);
$(this).val(values);
}
}
});
});
$('#itemsearchform').submit(function() {
var format = $(this).find('input[name="format"]:checked').val();
if (format == 'html') {
submitForm($(this));
hideForm($(this));
return false;
}
});
$('#editsearchlink').click(function() {
$('#itemsearchform').show();
$(this).hide();
return false;
});
});
//]]>
</script>
</head>
<body id="catalog_itemsearch" class="catalog">
[% INCLUDE 'header.inc' %]
<div id="breadcrumbs">
<a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; Item search
</div>
<div id="doc" class="yui-t7">
<div id="bd">
<h1>Item search</h1>
<a href="/cgi-bin/koha/catalogue/search.pl">Go to advanced search</a>
<form action="" method="get" id="itemsearchform">
<fieldset>
<legend>Item search</legend>
<fieldset>
[% INCLUDE form_field_select
name="homebranch"
label="Home branch"
options = branches
empty_option = "All branches"
%]
[% INCLUDE form_field_select
name="location"
label="Location"
options = locations
empty_option = "All locations"
%]
</fieldset>
<fieldset>
[% INCLUDE form_field_select
name="itype"
label="Item type"
options = itemtypes
empty_option = "All item types"
%]
[% INCLUDE form_field_select
name="ccode"
label="Collection code"
options = ccodes
empty_option = "All collection codes"
%]
</fieldset>
<fieldset>
[% INCLUDE form_field_select_text_block %]
<p class="hint">You can use the following joker characters: % _</p>
</fieldset>
<fieldset>
<div class="form-field">
<label class="form-field-label" for="itemcallnumber_from">From call number:</label>
[% value = CGI.param('itemcallnumber_from') %]
<input type="text" id="itemcallnumber_from" name="itemcallnumber_from" value="[% value %]" />
<span class="hint">(inclusive)</span>
</div>
<div class="form-field">
[% value = CGI.param('itemcallnumber_to') %]
<label class="form-field-label" for="itemcallnumber_to">To call number:</label>
<input type="text" id="itemcallnumber_to" name="itemcallnumber_to" value="[% value %]" />
<span class="hint">(inclusive)</span>
</div>
[% INCLUDE form_field_radio_yes_no name="damaged" label="Damaged" %]
[% INCLUDE form_field_radio_yes_no name="itemlost" label="Lost" %]
<div class="form-field">
<label class="form-field-label" for="issues">Issues count:</label>
<select id="issues_op" name="issues_op">
<option value=">">&gt;</option>
<option value="<">&lt;</option>
<option value="=">=</option>
<option value="!=">!=</option>
</select>
<input type="text" name="issues" />
</div>
<div class="form-field">
<label class="form-field-label" for="datelastborrowed">Last issue date:</label>
<select id="datelastborrowed_op" name="datelastborrowed_op">
<option value=">">After</option>
<option value="<">Before</option>
<option value="=">On</option>
</select>
<input type="text" name="datelastborrowed" />
<span class="hint">ISO Format (AAAA-MM-DD)</span>
</div>
</fieldset>
<fieldset>
<div class="form-field-radio">
<label>Output:</label>
<input type="radio" id="format-html" name="format" value="html" checked="checked" /> <label for="format-html">Screen</label>
<input type="radio" id="format-csv" name="format" value="csv" /> <label for="format-csv">CSV</label>
</div>
<div class="form-actions">
<input type="submit" value="Search" />
</div>
</fieldset>
</fieldset>
</form>
<p><a id="editsearchlink" href="#" style="display:none">Edit search</a></p>
<div id="results-wrapper">
[% IF search_done %]
[% IF total_rows > 0 %]
<p>Found [% total_rows %] results.</p>
[% ELSE %]
<p>No results found.</p>
[% END %]
[% IF results %]
[% INCLUDE 'catalogue/itemsearch_items.inc' items = results %]
[% END %]
<div id="pagination-bar">
[% pagination_bar %]
</div>
[% END %]
</div>
</div>
[% INCLUDE 'intranet-bottom.inc' %]
Loading…
Cancel
Save