#!/usr/bin/perl # This file is part of Koha. # Copyright 2006-2010 BibLibre # # 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 . use Modern::Perl; require Exporter; use CGI qw ( -utf8 ); use C4::Auth qw( get_template_and_user ); use C4::Output qw( output_html_with_http_headers output_and_exit_if_error ); use C4::Suggestions; use C4::Koha qw( GetAuthorisedValues ); use C4::Budgets qw( GetBudget GetBudgets GetBudgetHierarchy CanUserUseBudget ); use C4::Search qw( FindDuplicate GetDistinctValues ); use C4::Members; use Koha::DateUtils qw( dt_from_string ); use Koha::AuthorisedValues; use Koha::Acquisition::Currencies; use Koha::Libraries; use Koha::Patrons; use Koha::Suggestion; use Koha::Suggestions; use Koha::Token; use URI::Escape qw( uri_escape ); sub GetCriteriumDesc { my ( $criteriumvalue, $displayby ) = @_; if ( $displayby =~ /status/i ) { unless ( grep { /$criteriumvalue/ } qw(ASKED ACCEPTED REJECTED CHECKED ORDERED AVAILABLE) ) { my $av = Koha::AuthorisedValues->search( { category => 'SUGGEST_STATUS', authorised_value => $criteriumvalue } ); return $av->count ? $av->next->lib : 'Unknown'; } return ( $criteriumvalue eq 'ASKED' ? "Pending" : ucfirst( lc($criteriumvalue) ) ) if ( $displayby =~ /status/i ); } if ( $displayby =~ /branchcode/ ) { return $criteriumvalue ? Koha::Libraries->find($criteriumvalue)->branchname : "__ANY__"; } if ( $displayby =~ /itemtype/ ) { my $av = Koha::AuthorisedValues->search( { category => 'SUGGEST_FORMAT', authorised_value => $criteriumvalue } ); return $av->count ? $av->next->lib : 'Unknown'; } if ( $displayby =~ /suggestedby/ || $displayby =~ /managedby/ || $displayby =~ /acceptedby/ ) { my $patron = Koha::Patrons->find($criteriumvalue); return "" unless $patron; return $patron->surname . ", " . $patron->firstname; } if ( $displayby =~ /budgetid/ ) { my $budget = GetBudget($criteriumvalue); return "" unless $budget; return $$budget{budget_name}; } } my $input = CGI->new; my $redirect = $input->param('redirect'); my $suggestedbyme = ( defined $input->param('suggestedbyme') ? $input->param('suggestedbyme') : 1 ); my $op = $input->param('op') || 'else'; my @editsuggestions = $input->multi_param('suggestionid'); my $suggestedby = $input->param('suggestedby'); my $returnsuggestedby = $input->param('returnsuggestedby'); my $returnsuggested = $input->param('returnsuggested'); my $managedby = $input->param('managedby'); my $displayby = $input->param('displayby') || ''; my $tabcode = $input->param('tabcode'); my $save_confirmed = $input->param('save_confirmed') || 0; my $notify = $input->param('notify'); my $filter_archived = $input->param('filter_archived') || 0; my $new_itemtype = $input->param('suggestion_itemtype'); my $sugg_managedby = $input->param('suggestion_managedby'); #NOTE: Validate displayby as it gets passed to sensitive functions my %valid_displayby = ( STATUS => 1, branchcode => 1, itemtype => 1, managedby => 1, acceptedby => 1, ); if ( !$valid_displayby{$displayby} ) { $displayby = ''; } my $reasonsloop = GetAuthorisedValues("SUGGEST"); my $suggestion_ref = { $input->Vars }; my $stored_suggestion; if ( $op eq "edit_form" || $op eq "show" ) { $stored_suggestion = Koha::Suggestions->find( $input->param('suggestionid') ) if $input->param('suggestionid'); } delete $suggestion_ref->{$_} for qw(csrf_token suggestion_itemtype suggestion_managedby table_1_length); # get only the columns of Suggestion my $schema = Koha::Database->new()->schema; my $columns = ' ' . join( ' ', $schema->source('Suggestion')->columns ) . ' '; my $suggestion_only = { map { $columns =~ / $_ / ? ( $_ => $suggestion_ref->{$_} ) : () } keys %$suggestion_ref }; $suggestion_only->{STATUS} = $suggestion_ref->{STATUS}; delete $$suggestion_ref{$_} foreach qw( suggestedbyme displayby tabcode notify filter_archived koha_login_context auth_forwarded_hash login_op login_password login_userid ); foreach my $key ( keys %$suggestion_ref ) { delete $suggestion_ref->{$key} if ( !$suggestion_ref->{$key} && ( $op eq 'else' ) ); delete $suggestion_ref->{$key} if $key =~ m{^DataTables_acqui_suggestions_suggestions}; } delete $suggestion_only->{branchcode} if $suggestion_only->{branchcode} eq '__ANY__'; delete $suggestion_only->{budgetid} if $suggestion_only->{budgetid} eq '__ANY__'; unless ( $op eq 'cud-save' ) { while ( my ( $k, $v ) = each %$suggestion_only ) { delete $suggestion_only->{$k} if $v eq ''; } } my ( $template, $borrowernumber, $cookie, $userflags ) = get_template_and_user( { template_name => "suggestion/suggestion.tt", query => $input, type => "intranet", flagsrequired => { suggestions => '*' }, } ); my $librarian = Koha::Patrons->find($borrowernumber); $borrowernumber = $input->param('borrowernumber') if ( $input->param('borrowernumber') ); $template->param( 'borrowernumber' => $borrowernumber ); my $branchfilter = $input->param('branchcode') || C4::Context->userenv->{'branch'}; ######################################### ## Operations ## my @messages; if ( $op =~ /cud-save/ ) { output_and_exit_if_error( $input, $cookie, $template, { check => 'csrf_token' } ); my @messages; my $biblio = MarcRecordFromNewSuggestion( { title => $suggestion_only->{title}, author => $suggestion_only->{author}, itemtype => $suggestion_only->{itemtype}, isbn => $suggestion_only->{isbn}, } ); my $manager = Koha::Patrons->find( $suggestion_only->{managedby} ); if ( $manager && not $manager->has_permission( { suggestions => 'suggestions_manage' } ) ) { push @messages, { type => 'error', code => 'manager_not_enough_permissions' }; $template->param( messages => \@messages, ); } elsif ( !$suggestion_only->{suggestionid} && ( my ( $duplicatebiblionumber, $duplicatetitle ) = FindDuplicate($biblio) ) && !$save_confirmed ) { push @messages, { type => 'error', code => 'biblio_exists', id => $duplicatebiblionumber, title => $duplicatetitle }; $stored_suggestion = Koha::Suggestion->new($suggestion_only); $template->param( messages => \@messages, need_confirm => 1 ); $op = 'save'; } else { for my $date_key (qw( suggesteddate manageddate accepteddate rejecteddate )) { # FIXME Do we need this? $suggestion_only->{$date_key} = dt_from_string( $suggestion_only->{$date_key} ) if $suggestion_only->{$date_key}; } if ( $suggestion_only->{"STATUS"} ) { if ( my $tmpstatus = lc( $suggestion_only->{"STATUS"} ) =~ /ACCEPTED|REJECTED/i ) { $suggestion_only->{ lc( $suggestion_only->{"STATUS"} ) . "date" } = dt_from_string; $suggestion_only->{ lc( $suggestion_only->{"STATUS"} ) . "by" } = C4::Context->userenv->{number}; } $suggestion_only->{manageddate} = dt_from_string; $suggestion_only->{"managedby"} ||= C4::Context->userenv->{number}; } my $otherreason = $input->param('other_reason'); if ( $suggestion_only->{reason} eq 'other' && $otherreason ) { $suggestion_only->{reason} = $otherreason; } if ( $suggestion_only->{'suggestionid'} > 0 ) { $suggestion_only->{lastmodificationdate} = dt_from_string; $suggestion_only->{lastmodificationby} = C4::Context->userenv->{number}; $suggestion_only->{branchcode} = undef if exists $suggestion_only->{branchcode} && $suggestion_only->{branchcode} eq ""; if ( $librarian->has_permission( { 'suggestions' => 'suggestions_manage' } ) ) { &ModSuggestion($suggestion_only); } else { push @messages, { type => 'error', code => 'no_manage_permission' }; $template->param( messages => \@messages, ); } if ($notify) { my $patron = Koha::Patrons->find( $suggestion_only->{managedby} ); my $email_address = $patron->notice_email_address; if ( $patron->notice_email_address ) { my $letter = C4::Letters::GetPreparedLetter( module => 'suggestions', letter_code => 'NOTIFY_MANAGER', branchcode => $patron->branchcode, lang => $patron->lang, tables => { suggestions => $suggestion_only->{suggestionid}, branches => $patron->branchcode, borrowers => $patron->borrowernumber, }, ); C4::Letters::EnqueueLetter( { letter => $letter, borrowernumber => $patron->borrowernumber, message_transport_type => 'email' } ); } } } else { ###FIXME:Search here if suggestion already exists. my $suggestions = Koha::Suggestions->search_limited($suggestion_only); if ( $suggestions->count ) { #some suggestion are answering the request, do not add my @messages; while ( my $suggestion = $suggestions->next ) { push @messages, { type => 'error', code => 'already_exists', id => $suggestion->suggestionid }; } $template->param( messages => \@messages ); } else { ## Adding some information related to suggestion if ( $librarian->has_permission( { 'suggestions' => 'suggestions_create' } ) ) { Koha::Suggestion->new($suggestion_only)->store(); } else { push @messages, { type => 'error', code => 'no_delete_permission' }; $template->param( messages => \@messages ); } } # empty fields, to avoid filter in "SearchSuggestion" } map { delete $$suggestion_ref{$_} unless $_ eq 'branchcode' } keys %$suggestion_ref; $op = 'else'; if ( $redirect eq 'purchase_suggestions' ) { print $input->redirect("/cgi-bin/koha/members/purchase-suggestions.pl?borrowernumber=$borrowernumber"); exit; } } } elsif ( $op eq 'add_form' ) { # get the patron that is suggesting or fall back to the logged-in-user my $default_suggester = $suggestedby ? Koha::Patrons->find($suggestedby) : $librarian; $template->param( default_suggester => $default_suggester, default_suggesteddate => dt_from_string, default_branch => C4::Context->userenv->{"branch"}, ); $op = 'save'; } elsif ( $op eq 'edit_form' ) { #Edit suggestion my $other_reason = 1; foreach my $reason ( @{$reasonsloop} ) { if ( $stored_suggestion->reason eq $reason->{lib} ) { $other_reason = 0; } } $other_reason = 0 unless $stored_suggestion->reason; $template->param( other_reason => $other_reason, default_manageddate => dt_from_string, ); $op = 'save'; } elsif ( $op eq "cud-update_status" ) { my $suggestion; # set accepted/rejected/managed information if applicable # ie= if the librarian has chosen some action on the suggestions my $STATUS = $input->param('STATUS'); my $accepted_by = $input->param('acceptedby'); if ( $STATUS eq "ACCEPTED" ) { $suggestion = { accepteddate => dt_from_string, acceptedby => C4::Context->userenv->{number}, }; } elsif ( $STATUS eq "REJECTED" ) { $suggestion = { rejecteddate => dt_from_string, rejectedby => C4::Context->userenv->{number}, }; } if ($STATUS) { $suggestion->{manageddate} = dt_from_string; $suggestion->{managedby} = C4::Context->userenv->{number}; $suggestion->{STATUS} = $STATUS; } if ( my $reason = $input->param("reason") ) { if ( $reason eq "other" ) { $reason = $input->param("other_reason"); } $suggestion->{reason} = $reason; } if ( $librarian->has_permission( { 'suggestions' => 'suggestions_manage' } ) ) { foreach my $suggestionid (@editsuggestions) { next unless $suggestionid; $suggestion->{suggestionid} = $suggestionid; &ModSuggestion($suggestion); } redirect_with_params($input); } else { push @messages, { type => 'error', code => 'no_manage_permission' }; $template->param( messages => \@messages, ); } redirect_with_params($input); } elsif ( $op eq "cud-delete" ) { if ( $librarian->has_permission( { 'suggestions' => 'suggestions_delete' } ) ) { foreach my $delete_field (@editsuggestions) { &DelSuggestion( $borrowernumber, $delete_field, 'intranet' ); } redirect_with_params($input); } else { push @messages, { type => 'error', code => 'no_delete_permission' }; $template->param( messages => \@messages, ); } } elsif ( $op eq "cud-archive" ) { Koha::Suggestions->find($_)->update( { archived => 1 } ) for @editsuggestions; redirect_with_params($input); } elsif ( $op eq "cud-unarchive" ) { Koha::Suggestions->find($_)->update( { archived => 0 } ) for @editsuggestions; redirect_with_params($input); } elsif ( $op eq 'cud-update_itemtype' ) { if ( $librarian->has_permission( { 'suggestions' => 'suggestions_manage' } ) ) { foreach my $suggestionid (@editsuggestions) { next unless $suggestionid; &ModSuggestion( { suggestionid => $suggestionid, itemtype => $new_itemtype } ); } redirect_with_params($input); } else { push @messages, { type => 'error', code => 'no_manage_permission' }; $template->param( messages => \@messages, ); } } elsif ( $op eq 'cud-update_manager' ) { if ( $librarian->has_permission( { 'suggestions' => 'suggestions_manage' } ) ) { foreach my $suggestionid (@editsuggestions) { next unless $suggestionid; &ModSuggestion( { suggestionid => $suggestionid, managedby => $sugg_managedby } ); } redirect_with_params($input); } else { push @messages, { type => 'error', code => 'no_manage_permission' }; $template->param( messages => \@messages, ); } } if ( $op eq 'else' ) { $displayby ||= "STATUS"; # distinct values of display by my $criteria_list = GetDistinctValues( "suggestions." . $displayby ); my ( @criteria_dv, $criteria_has_empty ); foreach (@$criteria_list) { if ( $_->{value} ) { push @criteria_dv, $_->{value}; } else { $criteria_has_empty = 1; } } # aggregate null and empty values under empty value push @criteria_dv, '' if $criteria_has_empty; # Hack to not modify GetDistinctValues for this specific case if ( $displayby eq 'branchcode' && C4::Context->preference('IndependentBranches') && not C4::Context->IsSuperLibrarian ) { @criteria_dv = ( C4::Context->userenv->{'branch'} ); } # Pending tab first if ( $displayby eq 'STATUS' ) { @criteria_dv = grep { $_ ne 'ASKED' } @criteria_dv; unshift @criteria_dv, 'ASKED'; } unless ( exists $suggestion_ref->{branchcode} ) { $suggestion_ref->{branchcode} = C4::Context->userenv->{'branch'}; } my $search_params = {%$suggestion_ref}; # filter on date fields foreach my $field (qw( suggesteddate manageddate accepteddate )) { my $from = delete $search_params->{"${field}_from"}; my $to = delete $search_params->{"${field}_to"}; my $from_dt = $from && eval { dt_from_string($from) }; my $to_dt = $to && eval { dt_from_string($to) }; if ( $from_dt || $to_dt ) { my $dtf = Koha::Database->new->schema->storage->datetime_parser; if ( $from_dt && $to_dt ) { $search_params->{$field} = { -between => [ $dtf->format_date($from_dt), $dtf->format_date($to_dt) ] }; } elsif ($from_dt) { $search_params->{$field} = { '>=' => $dtf->format_date($from_dt) }; } elsif ($to_dt) { $search_params->{$field} = { '<=' => $dtf->format_date($to_dt) }; } } } if ( $search_params->{budgetid} && $search_params->{budgetid} eq '__NONE__' ) { $search_params->{budgetid} = [ undef, '' ]; } for my $f (qw (branchcode budgetid)) { delete $search_params->{$f} if $search_params->{$f} eq '__ANY__' || $search_params->{$f} eq ''; } for my $bi (qw (title author isbn publishercode copyrightdate collectiontitle)) { if ( $search_params->{$bi} ) { $search_params->{"me.$bi"} = { 'like' => "%" . $search_params->{$bi} . "%" }; delete $search_params->{$bi}; } } $search_params->{archived} = 0 if !$filter_archived; foreach my $key ( keys %$search_params ) { if ( $key eq 'branchcode' ) { if ( $displayby ne 'branchcode' ) { my $branch_param = delete $search_params->{$key}; $search_params->{"me.branchcode"} = $branch_param; } else { delete $search_params->{$key}; } } } # Retrieve the count to display on each tab my $suggestion_tabs = Koha::Suggestions->search_limited( $search_params, { select => [ $displayby, { count => $displayby, -as => 'count' } ], as => [ $displayby, 'count' ], group_by => [$displayby], } ); my $tab_counts = { map { $_->get_column($displayby) => $_->get_column('count') } $suggestion_tabs->as_list }; my @allsuggestions; foreach my $criteriumvalue (@criteria_dv) { # By default, display suggestions from current working branch my $definedvalue = defined $$suggestion_ref{$displayby} && $$suggestion_ref{$displayby} ne ""; next if $search_params->{STATUS} && $displayby eq 'STATUS' && $criteriumvalue ne $search_params->{STATUS}; next if ( $definedvalue && $$suggestion_ref{$displayby} ne $criteriumvalue ) and ( $displayby ne 'branchcode' && $branchfilter ne '__ANY__' ); my $criterium_search_params = {%$search_params}; $criterium_search_params->{"me.$displayby"} = $criteriumvalue; push @allsuggestions, { "suggestiontype" => $criteriumvalue || "suggest", "suggestiontypelabel" => GetCriteriumDesc( $criteriumvalue, $displayby ) || "", 'search_params' => $criterium_search_params, 'tab_count' => $tab_counts->{$criteriumvalue}, } if $tab_counts->{$criteriumvalue} > 0; delete $$suggestion_ref{$displayby} unless $definedvalue; } $template->param( "displayby" => $displayby, "notabs" => $displayby eq "", suggestions => \@allsuggestions, ); } $template->param( suggestion => $stored_suggestion, filter_archived => $filter_archived, "op" => $op, reasonsloop => $reasonsloop, redirect => $redirect, ); if ( defined($returnsuggested) and $returnsuggested ne "no_one" ) { print $input->redirect( "/cgi-bin/koha/members/moremember.pl?borrowernumber=" . $returnsuggested . "#suggestions" ); } $template->param( branchfilter => $branchfilter, ); $template->param( returnsuggestedby => $returnsuggestedby ); my $patron_reason_loop = GetAuthorisedValues("OPAC_SUG"); $template->param( patron_reason_loop => $patron_reason_loop ); # Budgets for suggestion add or edition my $sugg_budget_loop = []; my $sugg_budgets = GetBudgetHierarchy(); foreach my $r ( @{$sugg_budgets} ) { next unless ( CanUserUseBudget( $librarian->unblessed, $r, $userflags ) ); my $selected = ( $$suggestion_ref{budgetid} && $r->{budget_id} eq $$suggestion_ref{budgetid} ) ? 1 : 0; push @{$sugg_budget_loop}, { b_id => $r->{budget_id}, b_txt => $r->{budget_name}, b_active => $r->{budget_period_active}, selected => $selected, }; } @{$sugg_budget_loop} = sort { uc( $a->{b_txt} ) cmp uc( $b->{b_txt} ) } @{$sugg_budget_loop}; $template->param( sugg_budgets => $sugg_budget_loop ); if ( $stored_suggestion && $stored_suggestion->STATUS ) { $template->param( "statusselected_" . $stored_suggestion->STATUS => 1, selected_status => $stored_suggestion->STATUS, # We need template var selected_status in the second part of the template where template var suggestion.STATUS is out of scope ); } my $currencies = Koha::Acquisition::Currencies->search; $template->param( currencies => $currencies, suggestion => $stored_suggestion, price => sprintf( "%.2f", $$suggestion_ref{'price'} || 0 ), total => sprintf( "%.2f", $$suggestion_ref{'total'} || 0 ), ); # lists of distinct values (without empty) for filters my %hashlists; foreach my $field (qw(managedby acceptedby suggestedby budgetid)) { my $values_list; $values_list = GetDistinctValues( "suggestions." . $field ); my @codes_list = map { { 'code' => $$_{'value'}, 'desc' => GetCriteriumDesc( $$_{'value'}, $field ) || $$_{'value'}, 'selected' => ( $$suggestion_ref{$field} ) ? $$_{'value'} eq $$suggestion_ref{$field} : 0, } } grep { $$_{'value'} } @$values_list; @codes_list = sort { $a->{desc} cmp $b->{desc} } @codes_list; $hashlists{ lc($field) . "_loop" } = \@codes_list; } $template->param( %hashlists, borrowernumber => ( $input->param('borrowernumber') // undef ), SuggestionStatuses => GetAuthorisedValues('SUGGEST_STATUS'), ); output_html_with_http_headers $input, $cookie, $template->output; sub redirect_with_params { my ($input) = @_; my $params = ''; foreach my $key ( qw( displayby branchcode title author isbn publishercode copyrightdate collectiontitle suggestedby suggesteddate_from suggesteddate_to manageddate_from manageddate_to accepteddate_from accepteddate_to budgetid filter_archived ) ) { $params .= $key . '=' . uri_escape( scalar $input->param($key) ) . '&' if defined( $input->param($key) ); } print $input->redirect("/cgi-bin/koha/suggestion/suggestion.pl?$params"); exit; }