From cd6718b8cb35e305c4bd781b2c83e3ab94eb0e6d Mon Sep 17 00:00:00 2001
From: simith <simith@inlibro.com>
Date: Mon, 15 Dec 2014 08:24:44 -0500
Subject: [PATCH] Bug 10947 - Grouped ItemTypes - Patch should apply properly
 on latest master.

Other patches are now obsolete, this is a squashed patch.
Merge conflicts were resolved.

Sponsored-by: Vanier college

http://bugs.koha-community.org/show_bug.cgi?id=10937
---
 C4/Koha.pm                                         | 66 ++++++++++++++++++++++
 admin/authorised_values.pl                         |  2 +-
 admin/itemtypes.pl                                 | 14 ++++-
 installer/data/mysql/kohastructure.sql             |  2 +
 installer/data/mysql/updatedatabase.pl             | 13 +++++
 .../prog/en/modules/admin/itemtypes.tt             | 32 +++++++++++
 .../bootstrap/en/modules/opac-advsearch.tt         | 20 ++++++-
 opac/opac-search.pl                                | 24 +++++++-
 t/db_dependent/Koha.t                              | 44 ++++++++++++++-
 9 files changed, 208 insertions(+), 9 deletions(-)

diff --git a/C4/Koha.pm b/C4/Koha.pm
index 1b6160a..ebb0e50 100644
--- a/C4/Koha.pm
+++ b/C4/Koha.pm
@@ -43,6 +43,7 @@ BEGIN {
 		&subfield_is_koha_internal_p
 		&GetPrinters &GetPrinter
 		&GetItemTypes &getitemtypeinfo
+                &GetItemTypesCategorized &GetItemTypesByCategory
 		&GetSupportName &GetSupportList
 		&get_itemtypeinfos_of
 		&getframeworks &getframeworkinfo
@@ -273,6 +274,71 @@ sub GetItemTypes {
     }
 }
 
+=head2 GetItemTypesCategorized
+
+    $categories = GetItemTypesCategorized();
+
+Returns a hashref containing search categories.
+A search category will be put in the hash if at least one of its itemtypes is visible in OPAC.
+The categories must be part of Authorized Values (DOCTYPECAT)
+
+=cut
+
+sub GetItemTypesCategorized {
+    my $dbh   = C4::Context->dbh;
+    # Order is important, so that partially hidden (some items are not visible in OPAC) search
+    # categories will be visible. hideinopac=0 must be last.
+    my $query = qq|
+        SELECT itemtype, description, imageurl, hideinopac, 0 as 'iscat' FROM itemtypes WHERE ISNULL(searchcategory) or length(searchcategory) = 0
+        UNION
+        SELECT DISTINCT searchcategory AS `itemtype`,
+                        authorised_values.lib_opac AS description,
+                        authorised_values.imageurl AS imageurl,
+                        hideinopac, 1 as 'iscat'
+        FROM itemtypes
+        LEFT JOIN authorised_values ON searchcategory = authorised_value
+        WHERE searchcategory > '' and hideinopac=1
+        UNION
+        SELECT DISTINCT searchcategory AS `itemtype`,
+                        authorised_values.lib_opac AS description,
+                        authorised_values.imageurl AS imageurl,
+                        hideinopac, 1 as 'iscat'
+        FROM itemtypes
+        LEFT JOIN authorised_values ON searchcategory = authorised_value
+        WHERE searchcategory > '' and hideinopac=0
+        |;
+    my $sth = $dbh->prepare($query);
+    $sth->execute;
+
+    my %itemtypes;
+    while ( my $IT = $sth->fetchrow_hashref ) {
+        $itemtypes{ $IT->{'itemtype'} } = $IT;
+    }
+    return ( \%itemtypes );
+}
+
+=head2 GetItemTypesByCategory
+
+    @results = GetItemTypesByCategory( $searchcategory );
+
+Returns the itemtype code of all itemtypes included in a searchcategory.
+
+=cut
+
+sub GetItemTypesByCategory {
+    my ($category) = @_;
+    my $count = 0;
+    my @results;
+    my $dbh = C4::Context->dbh;
+    my $sth = $dbh->prepare("SELECT itemtype FROM itemtypes WHERE searchcategory=?");
+    $sth->execute($category);
+
+    while ( my $data = $sth->fetchrow ) {
+        push ( @results, $data );
+    }
+    return @results;
+}
+
 sub get_itemtypeinfos_of {
     my @itemtypes = @_;
 
diff --git a/admin/authorised_values.pl b/admin/authorised_values.pl
index 35afaca..0193920 100755
--- a/admin/authorised_values.pl
+++ b/admin/authorised_values.pl
@@ -245,7 +245,7 @@ sub default_form {
     }
 
     # push koha system categories
-    foreach (qw(Asort1 Asort2 Bsort1 Bsort2 SUGGEST DAMAGED LOST REPORT_GROUP REPORT_SUBGROUP DEPARTMENT TERM SUGGEST_STATUS)) {
+    foreach (qw(Asort1 Asort2 Bsort1 Bsort2 SUGGEST DAMAGED LOST REPORT_GROUP REPORT_SUBGROUP DEPARTMENT TERM SUGGEST_STATUS DOCTYPECAT)) {
         push @category_list, $_ unless $categories{$_};
     }
 
diff --git a/admin/itemtypes.pl b/admin/itemtypes.pl
index bfaa44c..e0d8a16 100755
--- a/admin/itemtypes.pl
+++ b/admin/itemtypes.pl
@@ -112,6 +112,8 @@ if ( $op eq 'add_form' ) {
         $remote_image = $data->{imageurl};
     }
 
+    my $searchcategory = GetAuthorisedValues("DOCTYPECAT", $data->{'searchcategory'});
+
     $template->param(
         itemtype        => $itemtype,
         description     => $data->{'description'},
@@ -125,6 +127,8 @@ if ( $op eq 'add_form' ) {
         imagesets       => $imagesets,
         remote_image    => $remote_image,
         sip_media_type  => $data->{sip_media_type},
+        hideinopac      => $data->{'hideinopac'},
+        searchcategory  => $searchcategory,
     );
 
     # END $OP eq ADD_FORM
@@ -150,6 +154,8 @@ elsif ( $op eq 'add_validate' ) {
                  , checkinmsg = ?
                  , checkinmsgtype = ?
                  , sip_media_type = ?
+                 , hideinopac = ?
+                 , searchcategory = ?
             WHERE itemtype = ?
         ';
         $sth = $dbh->prepare($query2);
@@ -168,15 +174,17 @@ elsif ( $op eq 'add_validate' ) {
             $input->param('checkinmsg'),
             $input->param('checkinmsgtype'),
             $sip_media_type,
+            $input->param('hideinopac') ? 1 : 0,
+            $input->param('searchcategory'),
             $input->param('itemtype')
         );
     }
     else {    # add a new itemtype & not modif an old
         my $query = "
             INSERT INTO itemtypes
-                (itemtype,description,rentalcharge, notforloan, imageurl, summary, checkinmsg, checkinmsgtype, sip_media_type)
+                (itemtype,description,rentalcharge, notforloan, imageurl, summary, checkinmsg, checkinmsgtype, sip_media_type, hideinopac, searchcategory)
             VALUES
-                (?,?,?,?,?,?,?,?,?);
+                (?,?,?,?,?,?,?,?,?,?,?);
             ";
         my $sth = $dbh->prepare($query);
 		my $image = $input->param('image');
@@ -192,6 +200,8 @@ elsif ( $op eq 'add_validate' ) {
             $input->param('checkinmsg'),
             $input->param('checkinmsgtype'),
             $sip_media_type,
+            $input->param('hideinopac') ? 1 : 0,
+            $input->param('searchcategory'),
         );
     }
 
diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql
index 4ffbbf2..80b50e4 100644
--- a/installer/data/mysql/kohastructure.sql
+++ b/installer/data/mysql/kohastructure.sql
@@ -1276,6 +1276,8 @@ CREATE TABLE `itemtypes` ( -- defines the item types
   checkinmsg VARCHAR(255), -- message that is displayed when an item with the given item type is checked in
   checkinmsgtype CHAR(16) DEFAULT 'message' NOT NULL, -- type (CSS class) for the checkinmsg, can be "alert" or "message"
   sip_media_type VARCHAR(3) DEFAULT NULL, -- SIP2 protocol media type for this itemtype
+  hideinopac tinyint(1) NOT NULL DEFAULT 0, -- Hide the item type from the search options in OPAC
+  searchcategory varchar(20) default NULL, -- Group this item type with others with the same value on OPAC search options
   PRIMARY KEY  (`itemtype`),
   UNIQUE KEY `itemtype` (`itemtype`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl
index cf17e0b..0d2b3fe 100755
--- a/installer/data/mysql/updatedatabase.pl
+++ b/installer/data/mysql/updatedatabase.pl
@@ -9585,6 +9585,19 @@ if ( CheckVersion($DBversion) ) {
     SetVersion ($DBversion);
 }
 
+$DBversion = "3.19.00.XXX";
+if ( CheckVersion($DBversion) ) {
+    $dbh->do(q{
+        ALTER TABLE itemtypes
+            ADD hideinopac TINYINT(1) NOT NULL DEFAULT 0
+              AFTER sip_media_type,
+            ADD searchcategory VARCHAR(20) DEFAULT NULL
+              AFTER hideinopac;
+    });
+    print "Upgrade to $DBversion done (Bug 10937 - Option to hide and group itemtypes from advanced search)\n";
+    SetVersion($DBversion);
+}
+
 =head1 FUNCTIONS
 
 =head2 TableExists($table)
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/itemtypes.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/itemtypes.tt
index 9a691fb..8d3e7e5 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/itemtypes.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/itemtypes.tt
@@ -109,6 +109,25 @@ Item types administration
   [% END %]
       <li>
           <label for="description" class="required">Description: </label><input type="text" id="description" name="description" size="48" value="[% description |html %]" required="required" /> <span class="required">Required</span></li>
+      <li>
+          <span class="label">Search category</span>
+          <select id="searchcategory" name="searchcategory">
+          <option value="">None</option>
+                [% FOREACH cat IN searchcategory %]
+                    [% IF ( cat.selected ) %]
+                        <option value="[% cat.authorised_value %]" selected="selected">
+                            [% cat.lib %]
+                        </option>
+                    [% ELSE %]
+                        <option value="[% cat.authorised_value %]" >
+                            [% cat.lib %]
+                        </option>
+                    [% END %]
+                [% END %]
+          </select>
+          (Options are defined as the authorized values for the DOCTYPECAT category)
+      </li>
+
      [% IF ( noItemTypeImages ) %]
 	 <li><span class="label">Image: </span>Item type images are disabled. To enable them, turn off the <a href="/cgi-bin/koha/admin/preferences.pl?op=search&amp;searchfield=noItemTypeImages">noItemTypeImages system preference</a></li></ol>
 	 [% ELSE %]</ol>
@@ -164,6 +183,15 @@ Item types administration
 [% END %]
 <ol>
       <li>
+          <label for="hideinopac">Hide in OPAC: </label>
+          [% IF ( hideinopac ) %]
+              <input type="checkbox" id="hideinopac" name="hideinopac" checked="checked" value="1" />
+          [% ELSE %]
+              <input type="checkbox" id="hideinopac" name="hideinopac" value="1" />
+          [% END %]
+          (if checked, items of this type will be hidden as filters in OPAC's advanced search)
+      </li>
+      <li>
           <label for="notforloan">Not for loan: </label>   [% IF ( notforloan ) %]
                 <input type="checkbox" id="notforloan" name="notforloan" checked="checked" value="1" />
             [% ELSE %]
@@ -260,7 +288,9 @@ Item types administration
     [% UNLESS ( noItemTypeImages ) %]<th>Image</th>[% END %]
     <th>Code</th>
     <th>Description</th>
+    <th>Search category</th>
     <th>Not for loan</th>
+    <th>Hide in OPAC</th>
     <th>Charge</th>
     <th>Checkin message</th>
     <th>Actions</th>
@@ -274,7 +304,9 @@ Item types administration
       </a>
     </td>
     <td>[% loo.description %]</td>
+    <td>[% loo.searchcategory %]</td>
     <td>[% IF ( loo.notforloan ) %]Yes[% ELSE %]&nbsp;[% END %]</td>
+    <td>[% IF ( loo.hideinopac ) %]Yes[% ELSE %]&nbsp;[% END %]</td>
     <td>
     [% UNLESS ( loo.notforloan ) %]
       [% loo.rentalcharge %]
diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-advsearch.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-advsearch.tt
index 12dd94f..c3568e2 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-advsearch.tt
+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-advsearch.tt
@@ -133,9 +133,25 @@
                                     <legend>Limit to any of the following:</legend>
                                     <div class="row-fluid">
                                         [% FOREACH itemtypeloo IN advsearchloo.code_loop %]
-                                            <div class="span3"><input type="checkbox" id="[% itemtypeloo.ccl FILTER remove(',') %]-[% itemtypeloo.number %]" name="limit" value="mc-[% itemtypeloo.ccl %]:[% itemtypeloo.code %]"/><label for="[% itemtypeloo.ccl FILTER remove(',') %]-[% itemtypeloo.number %]">[% UNLESS ( noItemTypeImages ) %][% IF ( itemtypeloo.imageurl ) %]<img src="[% itemtypeloo.imageurl %]" alt="[% itemtypeloo.description %]" />[% END %]&nbsp;[% END %]
-                                            [% itemtypeloo.description %]</label></div>
+                                            [% IF ((!itemtypeloo.searchcategory) AND (itemtypeloo.cat == 0)) OR (itemtypeloo.cat == 1) %]
+                                                <div class="span3">
+                                                    <input type="checkbox"
+                                                        id="[% itemtypeloo.ccl FILTER remove(',') %]-[% itemtypeloo.number %]"
+                                                        name="[% IF ( itemtypeloo.cat == 1 ) %]searchcat[% ELSE %]limit[% END %]"
+                                                        value="[% IF ( itemtypeloo.cat == 1 ) %][% itemtypeloo.code %][% ELSE %]mc-[% itemtypeloo.ccl %]:[% itemtypeloo.code %][% END %]"
+                                                    />
+                                                    <label for="[% itemtypeloo.ccl FILTER remove(',') %]-[% itemtypeloo.number %]">
+                                                        [% UNLESS ( noItemTypeImages ) %]
+                                                            [% IF ( itemtypeloo.imageurl ) %]
+                                                                <img border="0" src="[% itemtypeloo.imageurl %]" alt="[% itemtypeloo.description %]" />
+                                                            [% END %]
+                                                            &nbsp;
+                                                        [% END %]
+                                                        [% itemtypeloo.description %]
+                                                    </label>
+                                                </div>
                                             [% IF ( loop.last ) %]</div>[% ELSE %][% UNLESS ( loop.count % 4 ) %]</div><div class="row-fluid">[% END %][% END %]
+                                            [% END %]
                                         [% END %]
                                 </fieldset>
                             </div> <!-- / #advsearch-[% advsearchloo.advanced_search_type %] -->
diff --git a/opac/opac-search.pl b/opac/opac-search.pl
index d7eaf9e..8d39616 100755
--- a/opac/opac-search.pl
+++ b/opac/opac-search.pl
@@ -84,7 +84,7 @@ my ($template,$borrowernumber,$cookie);
 my $template_name;
 my $template_type = 'basic';
 my @params = $cgi->param("limit");
-
+my @searchCategories = $cgi->param('searchcat');
 
 my $format = $cgi->param("format") || '';
 my $build_grouped_results = C4::Context->preference('OPACGroupResults');
@@ -194,7 +194,7 @@ my $languages_limit_loop = getLanguages($lang, 1);
 $template->param(search_languages_loop => $languages_limit_loop,);
 
 # load the Type stuff
-my $itemtypes = GetItemTypes;
+my $itemtypes = GetItemTypesCategorized;
 # the index parameter is different for item-level itemtypes
 my $itype_or_itemtype = (C4::Context->preference("item-level_itypes"))?'itype':'itemtype';
 my @advancedsearchesloop;
@@ -228,8 +228,13 @@ foreach my $advanced_srch_type (@advanced_search_types) {
                 code => $thisitemtype,
                 description => $itemtypes->{$thisitemtype}->{'description'},
                 imageurl=> getitemtypeimagelocation( 'opac', $itemtypes->{$thisitemtype}->{'imageurl'} ),
+                cat => $itemtypes->{$thisitemtype}->{'iscat'},
+                hideinopac => $itemtypes->{$thisitemtype}->{'hideinopac'},
+                searchcategory => $itemtypes->{$thisitemtype}->{'searchcategory'},
             );
-	    push @itypesloop, \%row;
+            if ( !$itemtypes->{$thisitemtype}->{'hideinopac'} ) {
+                push @itypesloop, \%row;
+            }
 	}
         my %search_code = (  advanced_search_type => $advanced_srch_type,
                              code_loop => \@itypesloop );
@@ -247,6 +252,7 @@ foreach my $advanced_srch_type (@advanced_search_types) {
 				ccl => $advanced_srch_type,
                 code => $thisitemtype->{authorised_value},
                 description => $thisitemtype->{'lib_opac'} || $thisitemtype->{'lib'},
+                searchcategory => $itemtypes->{$thisitemtype}->{'searchcategory'},
                 imageurl => getitemtypeimagelocation( 'opac', $thisitemtype->{'imageurl'} ),
                 );
 		push @authvalueloop, \%row;
@@ -423,6 +429,18 @@ my @limits = $cgi->param('limit');
 my @nolimits = $cgi->param('nolimit');
 my %is_nolimit = map { $_ => 1 } @nolimits;
 @limits = grep { not $is_nolimit{$_} } @limits;
+
+if (@searchCategories > 0) {
+    my @tabcat;
+    foreach my $typecategory (@searchCategories) {
+        push (@tabcat, GetItemTypesByCategory($typecategory));
+    }
+
+    foreach my $itemtypeInCategory (@tabcat) {
+        push (@limits, "mc-$itype_or_itemtype,phr:".$itemtypeInCategory);
+    }
+}
+
 @limits = map { uri_unescape($_) } @limits;
 
 if($params->{'multibranchlimit'}) {
diff --git a/t/db_dependent/Koha.t b/t/db_dependent/Koha.t
index e40b4c2..fe22272 100644
--- a/t/db_dependent/Koha.t
+++ b/t/db_dependent/Koha.t
@@ -7,12 +7,13 @@ use strict;
 use warnings;
 use C4::Context;
 use Koha::DateUtils qw(dt_from_string);
+use Data::Dumper;
 
 use Test::More tests => 8;
 use DateTime::Format::MySQL;
 
 BEGIN {
-    use_ok('C4::Koha', qw( :DEFAULT GetDailyQuote ));
+    use_ok('C4::Koha', qw( :DEFAULT GetDailyQuote GetItemTypesByCategory GetItemTypesCategorized));
     use_ok('C4::Members');
 }
 
@@ -305,4 +306,45 @@ subtest 'GetFrameworksLoop() tests' => sub {
     );
 };
 
+subtest 'GetItemTypesByCategory GetItemTypesCategorized test' => sub{
+    plan tests => 7;
+
+    my $insertGroup = AddAuthorisedValue('DOCTYPECAT', 'Qwertyware');
+    ok($insertGroup, "Create group Qwertyware");
+
+    my $query = "INSERT into itemtypes (itemtype, description, searchcategory, hideinopac) values (?,?,?,?)";
+    my $insertSth = C4::Context->dbh->prepare($query);
+    $insertSth->execute('BKghjklo1', 'One type of book', '', 0);
+    $insertSth->execute('BKghjklo2', 'Another type of book', 'Qwertyware', 0);
+    $insertSth->execute('BKghjklo3', 'Yet another type of book', 'Qwertyware', 0);
+
+    # Azertyware should not exist.
+    my @results = GetItemTypesByCategory('Azertyware');
+    is(scalar @results, 0, 'GetItemTypesByCategory: Invalid category returns nothing');
+
+    @results = GetItemTypesByCategory('Qwertyware');
+    my @expected = ( 'BKghjklo2', 'BKghjklo3' );
+    is_deeply(\@results,\@expected,'GetItemTypesByCategory: valid category returns itemtypes');
+
+    # add more data since GetItemTypesCategorized's search is more subtle
+    $insertGroup = AddAuthorisedValue('DOCTYPECAT', 'Veryveryheavybook');
+    $insertSth->execute('BKghjklo4', 'Another hidden book', 'Veryveryheavybook', 1);
+
+    my $hrCat = GetItemTypesCategorized();
+    ok(exists $hrCat->{Qwertyware}, 'GetItemTypesCategorized: fully visible category exists');
+    ok($hrCat->{Veryveryheavybook} &&
+       $hrCat->{Veryveryheavybook}->{hideinopac}==1, 'GetItemTypesCategorized: non-visible category hidden' );
+
+    $insertSth->execute('BKghjklo5', 'An hidden book', 'Qwertyware', 1);
+    $hrCat = GetItemTypesCategorized();
+    ok(exists $hrCat->{Qwertyware}, 'GetItemTypesCategorized: partially visible category exists');
+
+    my @only = ( 'BKghjklo1', 'BKghjklo2', 'BKghjklo3', 'BKghjklo4', 'BKghjklo5', 'Qwertyware', 'Veryveryheavybook' );
+    @results = ();
+    foreach my $key (@only) {
+        push @results, $key if exists $hrCat->{$key};
+    }
+    @expected = ( 'BKghjklo1', 'Qwertyware', 'Veryveryheavybook' );
+    is_deeply(\@results,\@expected, 'GetItemTypesCategorized: grouped and ungrouped items returned as expected.');
+}
 $dbh->rollback();
-- 
1.9.1