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