Bug 5834: Holds link missing when 942$c is not for loan
[koha.git] / C4 / Output.pm
1 package C4::Output;
2
3 #package to deal with marking up output
4 #You will need to edit parts of this pm
5 #set the value of path to be where your html lives
6
7 # Copyright 2000-2002 Katipo Communications
8 #
9 # This file is part of Koha.
10 #
11 # Koha is free software; you can redistribute it and/or modify it under the
12 # terms of the GNU General Public License as published by the Free Software
13 # Foundation; either version 2 of the License, or (at your option) any later
14 # version.
15 #
16 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
17 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
18 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
19 #
20 # You should have received a copy of the GNU General Public License along
21 # with Koha; if not, write to the Free Software Foundation, Inc.,
22 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
24
25 # NOTE: I'm pretty sure this module is deprecated in favor of
26 # templates.
27
28 use strict;
29 #use warnings; FIXME - Bug 2505
30
31 use C4::Context;
32 use C4::Languages qw(getTranslatedLanguages get_bidi regex_lang_subtags language_get_description accept_language );
33 use C4::Dates qw(format_date);
34 use C4::Budgets qw(GetCurrency);
35
36 use HTML::Template::Pro;
37 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
38
39 BEGIN {
40     # set the version for version checking
41     $VERSION = 3.03;
42     require Exporter;
43     @ISA    = qw(Exporter);
44         @EXPORT_OK = qw(&is_ajax ajax_fail); # More stuff should go here instead
45         %EXPORT_TAGS = ( all =>[qw(&themelanguage &gettemplate setlanguagecookie pagination_bar
46                                                                 &output_with_http_headers &output_html_with_http_headers)],
47                                         ajax =>[qw(&output_with_http_headers is_ajax)],
48                                         html =>[qw(&output_with_http_headers &output_html_with_http_headers)]
49                                 );
50     push @EXPORT, qw(
51         &themelanguage &gettemplate setlanguagecookie getlanguagecookie pagination_bar
52     );
53     push @EXPORT, qw(
54         &output_html_with_http_headers &output_with_http_headers FormatData FormatNumber
55     );
56 }
57
58 =head1 NAME
59
60 C4::Output - Functions for managing templates
61
62 =head1 FUNCTIONS
63
64 =over 2
65
66 =cut
67
68 #FIXME: this is a quick fix to stop rc1 installing broken
69 #Still trying to figure out the correct fix.
70 my $path = C4::Context->config('intrahtdocs') . "/prog/en/includes/";
71
72 #---------------------------------------------------------------------------------------------------------
73 # FIXME - POD
74
75 sub _get_template_file {
76     my ( $tmplbase, $interface, $query ) = @_;
77     my $htdocs = C4::Context->config( $interface ne 'intranet' ? 'opachtdocs' : 'intrahtdocs' );
78     my ( $theme, $lang ) = themelanguage( $htdocs, $tmplbase, $interface, $query );
79     my $opacstylesheet = C4::Context->preference('opacstylesheet');
80
81     # if the template doesn't exist, load the English one as a last resort
82     my $filename = "$htdocs/$theme/$lang/modules/$tmplbase";
83     unless (-f $filename) {
84         $lang = 'en';
85         $filename = "$htdocs/$theme/$lang/modules/$tmplbase";
86     }
87
88     return ( $htdocs, $theme, $lang, $filename );
89 }
90
91 sub gettemplate {
92     my ( $tmplbase, $interface, $query ) = @_;
93     ($query) or warn "no query in gettemplate";
94     my $path = C4::Context->preference('intranet_includes') || 'includes';
95     my $opacstylesheet = C4::Context->preference('opacstylesheet');
96     my ( $htdocs, $theme, $lang, $filename ) = _get_template_file( $tmplbase, $interface, $query );
97
98     my $template       = HTML::Template::Pro->new(
99         filename          => $filename,
100         die_on_bad_params => 1,
101         global_vars       => 1,
102         case_sensitive    => 1,
103         loop_context_vars => 1, # enable: __first__, __last__, __inner__, __odd__, __counter__ 
104         path              => ["$htdocs/$theme/$lang/$path"]
105     );
106     my $themelang=( $interface ne 'intranet' ? '/opac-tmpl' : '/intranet-tmpl' )
107           . "/$theme/$lang";
108     $template->param(
109         themelang => $themelang,
110         yuipath   => (C4::Context->preference("yuipath") eq "local"?"$themelang/lib/yui":C4::Context->preference("yuipath")),
111         interface => ( $interface ne 'intranet' ? '/opac-tmpl' : '/intranet-tmpl' ),
112         theme     => $theme,
113         lang      => $lang
114     );
115
116     # Bidirectionality
117     my $current_lang = regex_lang_subtags($lang);
118     my $bidi;
119     $bidi = get_bidi($current_lang->{script}) if $current_lang->{script};
120     # Languages
121     my $languages_loop = getTranslatedLanguages($interface,$theme,$lang);
122     my $num_languages_enabled = 0;
123     foreach my $lang (@$languages_loop) {
124         foreach my $sublang (@{ $lang->{'sublanguages_loop'} }) {
125             $num_languages_enabled++ if $sublang->{enabled};
126          }
127     }
128     $template->param(
129             languages_loop       => $languages_loop,
130             bidi                 => $bidi,
131             one_language_enabled => ($num_languages_enabled <= 1) ? 1 : 0, # deal with zero enabled langs as well
132     ) unless @$languages_loop<2;
133
134     return $template;
135 }
136
137 # FIXME - this is a horrible hack to cache
138 # the current known-good language, temporarily
139 # put in place to resolve bug 4403.  It is
140 # used only by C4::XSLT::XSLTParse4Display;
141 # the language is set via the usual call
142 # to themelanguage.
143 my $_current_language = 'en';
144 sub _current_language {
145     return $_current_language;
146 }
147
148 #---------------------------------------------------------------------------------------------------------
149 # FIXME - POD
150 sub themelanguage {
151     my ( $htdocs, $tmpl, $interface, $query ) = @_;
152     ($query) or warn "no query in themelanguage";
153
154     # Set some defaults for language and theme
155     # First, check the user's preferences
156     my $lang;
157     my $http_accept_language = $ENV{ HTTP_ACCEPT_LANGUAGE };
158     $lang = accept_language( $http_accept_language, 
159               getTranslatedLanguages($interface,'prog') )
160       if $http_accept_language;
161     # But, if there's a cookie set, obey it
162     $lang = $query->cookie('KohaOpacLanguage') if (defined $query and $query->cookie('KohaOpacLanguage'));
163     # Fall back to English
164     my @languages;
165     if ($interface eq 'intranet') {
166         @languages = split ",", C4::Context->preference("language");
167     } else {
168         @languages = split ",", C4::Context->preference("opaclanguages");
169     }
170     if ($lang){  
171         @languages=($lang,@languages);
172     } else {
173         $lang = $languages[0];
174     }      
175     my $theme = 'prog'; # in the event of theme failure default to 'prog' -fbcit
176     my $dbh = C4::Context->dbh;
177     my @themes;
178     if ( $interface eq "intranet" ) {
179         @themes    = split " ", C4::Context->preference("template");
180     }
181     else {
182       # we are in the opac here, what im trying to do is let the individual user
183       # set the theme they want to use.
184       # and perhaps the them as well.
185         #my $lang = $query->cookie('KohaOpacLanguage');
186         @themes = split " ", C4::Context->preference("opacthemes");
187     }
188
189  # searches through the themes and languages. First template it find it returns.
190  # Priority is for getting the theme right.
191     THEME:
192     foreach my $th (@themes) {
193         foreach my $la (@languages) {
194             #for ( my $pass = 1 ; $pass <= 2 ; $pass += 1 ) {
195                 # warn "$htdocs/$th/$la/modules/$interface-"."tmpl";
196                 #$la =~ s/([-_])/ $1 eq '-'? '_': '-' /eg if $pass == 2;
197                                 if ( -e "$htdocs/$th/$la/modules/$tmpl") {
198                 #".($interface eq 'intranet'?"modules":"")."/$tmpl" ) {
199                     $theme = $th;
200                     $lang  = $la;
201                     last THEME;
202                 }
203                 last unless $la =~ /[-_]/;
204             #}
205         }
206     }
207
208     $_current_language = $lang; # FIXME part of bad hack to paper over bug 4403
209     return ( $theme, $lang );
210 }
211
212 sub setlanguagecookie {
213     my ( $query, $language, $uri ) = @_;
214     my $cookie = $query->cookie(
215         -name    => 'KohaOpacLanguage',
216         -value   => $language,
217         -expires => ''
218     );
219     print $query->redirect(
220         -uri    => $uri,
221         -cookie => $cookie
222     );
223 }
224
225 sub getlanguagecookie {
226     my ($query) = @_;
227     my $lang;
228     if ($query->cookie('KohaOpacLanguage')){
229         $lang = $query->cookie('KohaOpacLanguage') ;
230     }else{
231         $lang = $ENV{HTTP_ACCEPT_LANGUAGE};
232         
233     }
234     $lang = substr($lang, 0, 2);
235
236     return $lang;
237 }
238
239 =item FormatNumber
240 =cut
241 sub FormatNumber{
242 my $cur  =  GetCurrency;
243 my $cur_format = C4::Context->preference("CurrencyFormat");
244 my $num;
245
246 if ( $cur_format eq 'FR' ) {
247     $num = new Number::Format(
248         'decimal_fill'      => '2',
249         'decimal_point'     => ',',
250         'int_curr_symbol'   => $cur->{symbol},
251         'mon_thousands_sep' => ' ',
252         'thousands_sep'     => ' ',
253         'mon_decimal_point' => ','
254     );
255 } else {  # US by default..
256     $num = new Number::Format(
257         'int_curr_symbol'   => '',
258         'mon_thousands_sep' => ',',
259         'mon_decimal_point' => '.'
260     );
261 }
262 return $num;
263 }
264
265 =item FormatData
266
267 FormatData($data_hashref)
268 C<$data_hashref> is a ref to data to format
269
270 Format dates of data those dates are assumed to contain date in their noun
271 Could be used in order to centralize all the formatting for HTML output
272 =cut
273
274 sub FormatData{
275                 my $data_hashref=shift;
276         $$data_hashref{$_} = format_date( $$data_hashref{$_} ) for grep{/date/} keys (%$data_hashref);
277 }
278
279 =item pagination_bar
280
281    pagination_bar($base_url, $nb_pages, $current_page, $startfrom_name)
282
283 Build an HTML pagination bar based on the number of page to display, the
284 current page and the url to give to each page link.
285
286 C<$base_url> is the URL for each page link. The
287 C<$startfrom_name>=page_number is added at the end of the each URL.
288
289 C<$nb_pages> is the total number of pages available.
290
291 C<$current_page> is the current page number. This page number won't become a
292 link.
293
294 This function returns HTML, without any language dependency.
295
296 =cut
297
298 sub pagination_bar {
299         my $base_url = (@_ ? shift : $ENV{SCRIPT_NAME} . $ENV{QUERY_STRING}) or return undef;
300     my $nb_pages       = (@_) ? shift : 1;
301     my $current_page   = (@_) ? shift : undef;  # delay default until later
302     my $startfrom_name = (@_) ? shift : 'page';
303
304     # how many pages to show before and after the current page?
305     my $pages_around = 2;
306
307         my $delim = qr/\&(?:amp;)?|;/;          # "non memory" cluster: no backreference
308         $base_url =~ s/$delim*\b$startfrom_name=(\d+)//g; # remove previous pagination var
309     unless (defined $current_page and $current_page > 0 and $current_page <= $nb_pages) {
310         $current_page = ($1) ? $1 : 1;  # pull current page from param in URL, else default to 1
311                 # $debug and    # FIXME: use C4::Debug;
312                 # warn "with QUERY_STRING:" .$ENV{QUERY_STRING}. "\ncurrent_page:$current_page\n1:$1  2:$2  3:$3";
313     }
314         $base_url =~ s/($delim)+/$1/g;  # compress duplicate delims
315         $base_url =~ s/$delim;//g;              # remove empties
316         $base_url =~ s/$delim$//;               # remove trailing delim
317
318     my $url = $base_url . (($base_url =~ m/$delim/ or $base_url =~ m/\?/) ? '&amp;' : '?' ) . $startfrom_name . '=';
319     my $pagination_bar = '';
320
321     # navigation bar useful only if more than one page to display !
322     if ( $nb_pages > 1 ) {
323
324         # link to first page?
325         if ( $current_page > 1 ) {
326             $pagination_bar .=
327                 "\n" . '&nbsp;'
328               . '<a href="'
329               . $url
330               . '1" rel="start">'
331               . '&lt;&lt;' . '</a>';
332         }
333         else {
334             $pagination_bar .=
335               "\n" . '&nbsp;<span class="inactive">&lt;&lt;</span>';
336         }
337
338         # link on previous page ?
339         if ( $current_page > 1 ) {
340             my $previous = $current_page - 1;
341
342             $pagination_bar .=
343                 "\n" . '&nbsp;'
344               . '<a href="'
345               . $url
346               . $previous
347               . '" rel="prev">' . '&lt;' . '</a>';
348         }
349         else {
350             $pagination_bar .=
351               "\n" . '&nbsp;<span class="inactive">&lt;</span>';
352         }
353
354         my $min_to_display      = $current_page - $pages_around;
355         my $max_to_display      = $current_page + $pages_around;
356         my $last_displayed_page = undef;
357
358         for my $page_number ( 1 .. $nb_pages ) {
359             if (
360                    $page_number == 1
361                 or $page_number == $nb_pages
362                 or (    $page_number >= $min_to_display
363                     and $page_number <= $max_to_display )
364               )
365             {
366                 if ( defined $last_displayed_page
367                     and $last_displayed_page != $page_number - 1 )
368                 {
369                     $pagination_bar .=
370                       "\n" . '&nbsp;<span class="inactive">...</span>';
371                 }
372
373                 if ( $page_number == $current_page ) {
374                     $pagination_bar .=
375                         "\n" . '&nbsp;'
376                       . '<span class="currentPage">'
377                       . $page_number
378                       . '</span>';
379                 }
380                 else {
381                     $pagination_bar .=
382                         "\n" . '&nbsp;'
383                       . '<a href="'
384                       . $url
385                       . $page_number . '">'
386                       . $page_number . '</a>';
387                 }
388                 $last_displayed_page = $page_number;
389             }
390         }
391
392         # link on next page?
393         if ( $current_page < $nb_pages ) {
394             my $next = $current_page + 1;
395
396             $pagination_bar .= "\n"
397               . '&nbsp;<a href="'
398               . $url
399               . $next
400               . '" rel="next">' . '&gt;' . '</a>';
401         }
402         else {
403             $pagination_bar .=
404               "\n" . '&nbsp;<span class="inactive">&gt;</span>';
405         }
406
407         # link to last page?
408         if ( $current_page != $nb_pages ) {
409             $pagination_bar .= "\n"
410               . '&nbsp;<a href="'
411               . $url
412               . $nb_pages
413               . '" rel="last">'
414               . '&gt;&gt;' . '</a>';
415         }
416         else {
417             $pagination_bar .=
418               "\n" . '&nbsp;<span class="inactive">&gt;&gt;</span>';
419         }
420     }
421
422     return $pagination_bar;
423 }
424
425 =item output_with_http_headers
426
427    &output_with_http_headers($query, $cookie, $data, $content_type[, $status])
428
429 Outputs $data with the appropriate HTTP headers,
430 the authentication cookie $cookie and a Content-Type specified in
431 $content_type.
432
433 If applicable, $cookie can be undef, and it will not be sent.
434
435 $content_type is one of the following: 'html', 'js', 'json', 'xml', 'rss', or 'atom'.
436
437 $status is an HTTP status message, like '403 Authentication Required'. It defaults to '200 OK'.
438
439 =cut
440
441 sub output_with_http_headers($$$$;$) {
442     my ( $query, $cookie, $data, $content_type, $status ) = @_;
443     $status ||= '200 OK';
444
445     my %content_type_map = (
446         'html' => 'text/html',
447         'js'   => 'text/javascript',
448         'json' => 'application/json',
449         'xml'  => 'text/xml',
450         # NOTE: not using application/atom+xml or application/rss+xml because of
451         # Internet Explorer 6; see bug 2078.
452         'rss'  => 'text/xml',
453         'atom' => 'text/xml'
454     );
455
456     die "Unknown content type '$content_type'" if ( !defined( $content_type_map{$content_type} ) );
457     my $options = {
458         type    => $content_type_map{$content_type},
459         status  => $status,
460         charset => 'UTF-8',
461         Pragma          => 'no-cache',
462         'Cache-Control' => 'no-cache',
463     };
464     $options->{cookie} = $cookie if $cookie;
465     if ($content_type eq 'html') {  # guaranteed to be one of the content_type_map keys, else we'd have died
466         $options->{'Content-Style-Type' } = 'text/css';
467         $options->{'Content-Script-Type'} = 'text/javascript';
468     }
469     # remove SUDOC specific NSB NSE
470     $data =~ s/\x{C2}\x{98}|\x{C2}\x{9C}/ /g;
471     $data =~ s/\x{C2}\x{88}|\x{C2}\x{89}/ /g;
472     print $query->header($options), $data;
473 }
474
475 sub output_html_with_http_headers ($$$;$) {
476     my ( $query, $cookie, $data, $status ) = @_;
477     $data =~ s/\&amp\;amp\; /\&amp\; /;
478     output_with_http_headers( $query, $cookie, $data, 'html', $status );
479 }
480
481 sub is_ajax () {
482     my $x_req = $ENV{HTTP_X_REQUESTED_WITH};
483     return ( $x_req and $x_req =~ /XMLHttpRequest/i ) ? 1 : 0;
484 }
485
486 END { }    # module clean-up code here (global destructor)
487
488 1;
489 __END__
490
491 =back
492
493 =head1 AUTHOR
494
495 Koha Development Team <http://koha-community.org/>
496
497 =cut