Bug 36908: Sort branches based on branchcode
[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
12 # under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
15 #
16 # Koha is distributed in the hope that it will be useful, but
17 # WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
20 #
21 # You should have received a copy of the GNU General Public License
22 # along with Koha; if not, see <http://www.gnu.org/licenses>.
23
24
25 # NOTE: I'm pretty sure this module is deprecated in favor of
26 # templates.
27
28 use Modern::Perl;
29
30 use HTML::Entities;
31 use Scalar::Util qw( looks_like_number );
32 use URI::Escape;
33
34 use C4::Auth qw( get_template_and_user );
35 use C4::Context;
36 use C4::Templates;
37
38 our (@ISA, @EXPORT_OK);
39
40 BEGIN {
41     require Exporter;
42
43     @ISA    = qw(Exporter);
44     @EXPORT_OK = qw(
45         is_ajax
46         ajax_fail
47         setlanguagecookie getlanguagecookie pagination_bar parametrized_url
48         output_html_with_http_headers output_ajax_with_http_headers output_with_http_headers
49         output_and_exit_if_error output_and_exit output_error
50     );
51 }
52
53 =head1 NAME
54
55 C4::Output - Functions for managing output, is slowly being deprecated
56
57 =head1 FUNCTIONS
58
59 =over 2
60
61 =item pagination_bar
62
63    pagination_bar($base_url, $nb_pages, $current_page, $startfrom_name)
64
65 Build an HTML pagination bar based on the number of page to display, the
66 current page and the url to give to each page link.
67
68 C<$base_url> is the URL for each page link. The
69 C<$startfrom_name>=page_number is added at the end of the each URL.
70
71 C<$nb_pages> is the total number of pages available.
72
73 C<$current_page> is the current page number. This page number won't become a
74 link.
75
76 This function returns HTML, without any language dependency.
77
78 =cut
79
80 sub pagination_bar {
81     my $base_url = (@_ ? shift : return);
82     my $nb_pages       = (@_) ? shift : 1;
83     my $current_page   = (@_) ? shift : undef;  # delay default until later
84     my $startfrom_name = (@_) ? shift : 'page';
85     my $additional_parameters = shift || {};
86
87     $base_url = HTML::Entities::encode($base_url);
88
89     $current_page = looks_like_number($current_page) ? $current_page : undef;
90     $nb_pages     = looks_like_number($nb_pages)     ? $nb_pages     : undef;
91
92     # how many pages to show before and after the current page?
93     my $pages_around = 2;
94
95         my $delim = qr/\&(?:amp;)?|;/;          # "non memory" cluster: no backreference
96         $base_url =~ s/$delim*\b$startfrom_name=(\d+)//g; # remove previous pagination var
97     unless (defined $current_page and $current_page > 0 and $current_page <= $nb_pages) {
98         $current_page = ($1) ? $1 : 1;  # pull current page from param in URL, else default to 1
99     }
100         $base_url =~ s/($delim)+/$1/g;  # compress duplicate delims
101         $base_url =~ s/$delim;//g;              # remove empties
102         $base_url =~ s/$delim$//;               # remove trailing delim
103
104     my $url = $base_url . (($base_url =~ m/$delim/ or $base_url =~ m/\?/) ? '&amp;' : '?' ) . $startfrom_name . '=';
105     my $url_suffix = '';
106     while ( my ( $k, $v ) = each %$additional_parameters ) {
107         $url_suffix .= '&amp;' . URI::Escape::uri_escape_utf8($k) . '=' . URI::Escape::uri_escape_utf8($v);
108     }
109     my $pagination_bar = '';
110
111     # navigation bar useful only if more than one page to display !
112     if ( $nb_pages > 1 ) {
113
114         # link to first page?
115         if ( $current_page > 1 ) {
116             $pagination_bar .=
117                 "\n" . '&nbsp;'
118               . '<a href="'
119               . $url
120               . '1'
121               . $url_suffix
122               . '"rel="start">'
123               . '&lt;&lt;' . '</a>';
124         }
125         else {
126             $pagination_bar .=
127               "\n" . '&nbsp;<span class="inactive">&lt;&lt;</span>';
128         }
129
130         # link on previous page ?
131         if ( $current_page > 1 ) {
132             my $previous = $current_page - 1;
133
134             $pagination_bar .=
135                 "\n" . '&nbsp;'
136               . '<a href="'
137               . $url
138               . $previous
139               . $url_suffix
140               . '" rel="prev">' . '&lt;' . '</a>';
141         }
142         else {
143             $pagination_bar .=
144               "\n" . '&nbsp;<span class="inactive">&lt;</span>';
145         }
146
147         my $min_to_display      = $current_page - $pages_around;
148         my $max_to_display      = $current_page + $pages_around;
149         my $last_displayed_page = undef;
150
151         for my $page_number ( 1 .. $nb_pages ) {
152             if (
153                    $page_number == 1
154                 or $page_number == $nb_pages
155                 or (    $page_number >= $min_to_display
156                     and $page_number <= $max_to_display )
157               )
158             {
159                 if ( defined $last_displayed_page
160                     and $last_displayed_page != $page_number - 1 )
161                 {
162                     $pagination_bar .=
163                       "\n" . '&nbsp;<span class="inactive">...</span>';
164                 }
165
166                 if ( $page_number == $current_page ) {
167                     $pagination_bar .=
168                         "\n" . '&nbsp;'
169                       . '<span class="currentPage">'
170                       . $page_number
171                       . '</span>';
172                 }
173                 else {
174                     $pagination_bar .=
175                         "\n" . '&nbsp;'
176                       . '<a href="'
177                       . $url
178                       . $page_number
179                       . $url_suffix
180                       . '">'
181                       . $page_number . '</a>';
182                 }
183                 $last_displayed_page = $page_number;
184             }
185         }
186
187         # link on next page?
188         if ( $current_page < $nb_pages ) {
189             my $next = $current_page + 1;
190
191             $pagination_bar .= "\n"
192               . '&nbsp;<a href="'
193               . $url
194               . $next
195               . $url_suffix
196               . '" rel="next">' . '&gt;' . '</a>';
197         }
198         else {
199             $pagination_bar .=
200               "\n" . '&nbsp;<span class="inactive">&gt;</span>';
201         }
202
203         # link to last page?
204         if ( $current_page != $nb_pages ) {
205             $pagination_bar .= "\n"
206               . '&nbsp;<a href="'
207               . $url
208               . $nb_pages
209               . $url_suffix
210               . '" rel="last">'
211               . '&gt;&gt;' . '</a>';
212         }
213         else {
214             $pagination_bar .=
215               "\n" . '&nbsp;<span class="inactive">&gt;&gt;</span>';
216         }
217     }
218
219     return $pagination_bar;
220 }
221
222 =item output_with_http_headers
223
224    &output_with_http_headers($query, $cookie, $data, $content_type[, $status[, $extra_options]])
225
226 Outputs $data with the appropriate HTTP headers,
227 the authentication cookie $cookie and a Content-Type specified in
228 $content_type.
229
230 If applicable, $cookie can be undef, and it will not be sent.
231
232 $content_type is one of the following: 'html', 'js', 'json', 'opensearchdescription', 'xml', 'rss', or 'atom'.
233
234 $status is an HTTP status message, like '403 Authentication Required'. It defaults to '200 OK'.
235
236 $extra_options is hashref.  If the key 'force_no_caching' is present and has
237 a true value, the HTTP headers include directives to force there to be no
238 caching whatsoever.
239
240 =cut
241
242 sub output_with_http_headers {
243     my ( $query, $cookie, $data, $content_type, $status, $extra_options ) = @_;
244     $status ||= '200 OK';
245
246     $extra_options //= {};
247
248     my %content_type_map = (
249         'html' => 'text/html',
250         'js'   => 'text/javascript',
251         'json' => 'application/json',
252         'xml'  => 'text/xml',
253         # NOTE: not using application/atom+xml or application/rss+xml because of
254         # Internet Explorer 6; see bug 2078.
255         'rss'  => 'text/xml',
256         'atom' => 'text/xml',
257         'opensearchdescription' => 'application/opensearchdescription+xml',
258     );
259
260     die "Unknown content type '$content_type'" if ( !defined( $content_type_map{$content_type} ) );
261     my $cache_policy = 'no-cache';
262     $cache_policy .= ', no-store, max-age=0' if $extra_options->{force_no_caching};
263     my $options = {
264         type              => $content_type_map{$content_type},
265         status            => $status,
266         charset           => 'UTF-8',
267         Pragma            => 'no-cache',
268         'Cache-Control'   => $cache_policy,
269         'X-Frame-Options' => 'SAMEORIGIN',
270     };
271     $options->{expires} = 'now' if $extra_options->{force_no_caching};
272     $options->{'Access-Control-Allow-Origin'} = C4::Context->preference('AccessControlAllowOrigin')
273         if C4::Context->preference('AccessControlAllowOrigin');
274
275     $options->{cookie} = $cookie if $cookie;
276     if ($content_type eq 'html') {  # guaranteed to be one of the content_type_map keys, else we'd have died
277         $options->{'Content-Style-Type' } = 'text/css';
278         $options->{'Content-Script-Type'} = 'text/javascript';
279     }
280
281 # We can't encode here, that will double encode our templates, and xslt
282 # We need to fix the encoding as it comes out of the database, or when we pass the variables to templates
283
284     $data =~ s/\&amp\;amp\; /\&amp\; /g;
285     print $query->header($options), $data;
286 }
287
288 sub output_html_with_http_headers {
289     my ( $query, $cookie, $data, $status, $extra_options ) = @_;
290     output_with_http_headers( $query, $cookie, $data, 'html', $status, $extra_options );
291 }
292
293
294 sub output_ajax_with_http_headers {
295     my ( $query, $js ) = @_;
296     print $query->header(
297         -type            => 'text/javascript',
298         -charset         => 'UTF-8',
299         -Pragma          => 'no-cache',
300         -'Cache-Control' => 'no-cache',
301         -expires         => '-1d',
302     ), $js;
303 }
304
305 sub is_ajax {
306     my $x_req = $ENV{HTTP_X_REQUESTED_WITH};
307     return ( $x_req and $x_req =~ /XMLHttpRequest/i ) ? 1 : 0;
308 }
309
310 =item output_and_exit_if_error
311
312     output_and_exit_if_error( $query, $cookie, $template, $params );
313
314 To executed at the beginning of scripts to stop the script at this point if
315 some errors are found.
316
317 A series of tests can be run for a given module, or a specific check.
318 Params "module" and "check" are mutually exclusive.
319
320 Tests for modules:
321 * members:
322     - Patron is not defined (we are looking for a patron that does no longer exist/never existed)
323     - The logged in user cannot see patron's infos (feature 'cannot_see_patron_infos')
324
325 Tests for specific check:
326 * csrf_token
327     will test if the csrf_token CGI param is valid
328
329 Others will be added here depending on the needs (for instance biblio does not exist will be useful).
330
331 =cut
332
333 sub output_and_exit_if_error {
334     my ( $query, $cookie, $template, $params ) = @_;
335     my $error;
336     if ( $params and exists $params->{module} ) {
337         if ( $params->{module} eq 'members' ) {
338             my $logged_in_user = $params->{logged_in_user};
339             my $current_patron = $params->{current_patron};
340             if ( not $current_patron ) {
341                 $error = 'unknown_patron';
342             }
343             elsif ( not $logged_in_user->can_see_patron_infos($current_patron) )
344             {
345                 $error = 'cannot_see_patron_infos';
346             }
347         }
348         elsif ( $params->{module} eq 'cataloguing' ) {
349             # We are testing the record to avoid additem to fetch the Koha::Biblio
350             # But in the long term we will want to get a biblio in parameter
351             $error = 'unknown_biblio' unless $params->{record};
352         }
353     }
354     elsif ( $params and exists $params->{check} ) {
355         if ( $params->{check} eq 'csrf_token' ) {
356             $error = 'wrong_csrf_token'
357               unless Koha::Token->new->check_csrf(
358                 {
359                     session_id => scalar $query->cookie('CGISESSID'),
360                     token      => scalar $query->param('csrf_token'),
361                 }
362               );
363         }
364     }
365     output_and_exit( $query, $cookie, $template, $error ) if $error;
366     return;
367 }
368
369 =item output_and_exit
370
371     output_and_exit( $query, $cookie, $template, $error );
372
373     $error is a blocking error like biblionumber not found or so.
374     We should output the error and exit.
375
376 =cut
377
378 sub output_and_exit {
379     my ( $query, $cookie, $template, $error ) = @_;
380     $template->param( blocking_error => $error );
381     output_html_with_http_headers ( $query, $cookie, $template->output );
382     exit;
383 }
384
385 sub output_error {
386     my ( $query, $error ) = @_;
387     my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
388         {
389             template_name   => 'errors/errorpage.tt',
390             query           => $query,
391             type            => 'intranet',
392             authnotrequired => 1,
393         }
394     );
395     my $admin = C4::Context->preference('KohaAdminEmailAddress');
396     $template->param (
397         admin => $admin,
398         errno => $error,
399     );
400     output_with_http_headers $query, $cookie, $template->output, 'html', '404 Not Found';
401 }
402
403 sub parametrized_url {
404     my $url = shift || ''; # ie page.pl?ln={LANG}
405     my $vars = shift || {}; # ie { LANG => en }
406     my $ret = $url;
407     while ( my ($key,$val) = each %$vars) {
408         my $val_url = URI::Escape::uri_escape_utf8( $val // q{} );
409         $ret =~ s/\{$key\}/$val_url/g;
410     }
411     $ret =~ s/\{[^\{]*\}//g; # remove remaining vars
412     return $ret;
413 }
414
415 END { }    # module clean-up code here (global destructor)
416
417 1;
418 __END__
419
420 =back
421
422 =head1 AUTHOR
423
424 Koha Development Team <http://koha-community.org/>
425
426 =cut