Bug 11529: (RM follow-up) Add WHILE to directive list
[koha.git] / t / lib / QA / TemplateFilters.pm
1 package t::lib::QA::TemplateFilters;
2
3 use Modern::Perl;
4
5 our @tt_directives = (
6     qr{^\s*INCLUDE},
7     qr{^\s*USE},
8     qr{^\s*IF},
9     qr{^\s*UNLESS},
10     qr{^\s*ELSE},
11     qr{^\s*ELSIF},
12     qr{^\s*END},
13     qr{^\s*SET},
14     qr{^\s*WHILE},
15     qr{^\s*FOR},
16     qr{^\s*FOREACH},
17     qr{^\s*MACRO},
18     qr{^\s*SWITCH},
19     qr{^\s*CASE},
20     qr{^\s*PROCESS},
21     qr{^\s*DEFAULT},
22     qr{^\s*TRY},
23     qr{^\s*CATCH},
24     qr{^\s*BLOCK},
25     qr{^\s*FILTER},
26     qr{^\s*STOP},
27     qr{^\s*NEXT},
28     qr{^\s*LAST},
29 );
30
31 our @tt_methods = (
32     qr{\.push\(},
33 );
34
35 sub fix_filters {
36     return _process_tt_content( @_ )->{new_content};
37 }
38
39 sub missing_filters {
40     return @{_process_tt_content( @_ )->{errors}};
41
42 }
43
44 sub _process_tt_content {
45     my ($content) = @_;
46     my ( $use_raw, $has_use_raw );
47     my @errors;
48     my @new_lines;
49     my $line_number;
50     for my $line ( split "\n", $content ) {
51         my $new_line = $line;
52         $line_number++;
53         if ( $line =~ m{\[%[^%]+%\]} ) {
54
55             # handle exceptions first
56             if ( $line =~ m{\|\s*\$raw} ) {    # Is the file use the raw filter?
57                 $use_raw = 1;
58             }
59
60             # Do we have Asset without the raw filter?
61             if ( $line =~ m{^\s*\[% Asset} && $line !~ m{\|\s*\$raw} ) {
62                 push @errors,
63                   {
64                     error       => 'asset_must_be_raw',
65                     line        => $line,
66                     line_number => $line_number
67                   };
68                 $new_line =~ s/\)\s*%]/) | \$raw %]/;
69                 $use_raw = 1;
70                 push @new_lines, $new_line;
71                 next;
72             }
73
74             $has_use_raw++
75               if $line =~ m{\[%(\s|-|~)*USE raw(\s|-|~)*%\]};    # Does [% Use raw %] exist?
76
77             my $e;
78             if ( $line =~ qr{<a href="([^"]+)} ) {
79                 my $to_uri_escape = $1;
80                 while (
81                     $to_uri_escape =~ m{
82                         \[%
83                         (?<pre_chomp>(\s|\-|~)*)
84                         (?<tt_block>[^%\-~]+)
85                         (?<post_chomp>(\s|\-|~)*)
86                         %\]}gmxs
87                   )
88                 {
89                     ( $new_line, $e ) = process_tt_block($new_line, { %+, filter => 'uri' });
90                     push @errors, { line => $line, line_number => $line_number, error => $e } if $e;
91                 }
92             }
93
94             # Loop on TT blocks
95             while (
96                 $line =~ m{
97                     \[%
98                     (?<pre_chomp>(\s|\-|~)*)
99                     (?<tt_block>[^%\-~]+)
100                     (?<post_chomp>(\s|\-|~)*)
101                     %\]}gmxs
102               )
103             {
104                 ( $new_line, $e ) = process_tt_block($new_line, \%+);
105                 push @errors, { line => $line, line_number => $line_number, error => $e } if $e;
106             }
107
108             push @new_lines, $new_line;
109         }
110         else {
111             push @new_lines, $new_line;
112         }
113
114     }
115
116     # Adding [% USE raw %] on top if the filter is used
117     @new_lines = ( '[% USE raw %]', @new_lines )
118       if $use_raw and not $has_use_raw;
119
120     my $new_content = join "\n", @new_lines;
121     return { errors => \@errors, new_content => $new_content };
122 }
123
124 sub process_tt_block {
125     my ( $line, $params ) = @_;
126     my $tt_block   = $params->{tt_block};
127     my $pre_chomp  = $params->{pre_chomp};
128     my $post_chomp = $params->{post_chomp};
129     my $filter     = $params->{filter} || 'html';
130     my $error;
131
132     return ( $line, $error ) if
133         # It's a TT directive, no filters needed
134         grep { $tt_block =~ $_ } @tt_directives
135
136         # It's a TT method
137         or grep { $tt_block =~ $_ } @tt_methods
138
139         # It is a comment
140         or $tt_block =~ m{^\#}
141
142         # Already escaped with a special filter
143         # We could escape it but should be safe
144         or $tt_block =~ m{\s?\|\s?\$KohaDates[^\|]*$}
145         or $tt_block =~ m{\s?\|\s?\$Price[^\|]*$}
146         or $tt_block =~ m{\s?\|\s?\$HtmlTags[^\|]*$}
147
148         # Already escaped correctly with raw
149         or $tt_block =~ m{\|\s?\$raw}
150
151         # Assignment, maybe we should require to use SET (?)
152         or ( $tt_block =~ m{=} and not $tt_block =~ m{\s\|\s} )
153
154         # Already has url or uri filter
155         or $tt_block =~ m{\|\s?ur(l|i)}
156
157         # Specific for [% foo UNLESS bar %]
158         or $tt_block =~ m{^(?<before>\S+)\s+UNLESS\s+(?<after>\S+)}
159     ;
160
161     $pre_chomp =
162         $pre_chomp
163       ? $pre_chomp =~ m|-|
164           ? q|- |
165           : $pre_chomp =~ m|~|
166             ? q|~ |
167             : q| |
168       : q| |;
169     $post_chomp =
170         $post_chomp
171       ? $post_chomp =~ m|-|
172           ? q| -|
173           : $post_chomp =~ m|~|
174             ? q| ~|
175             : q| |
176       : q| |;
177
178     if (   $tt_block =~ m{\s?\|\s?\$KohaDates[^\|]*\|.*$}
179         or $tt_block =~ m{\s?\|\s?\$Price[^\|]*\|.*$}
180         or $tt_block =~ m{\s?\|\s?\$HtmlTags[^\|]*\|.*$}
181     ) {
182         $tt_block =~
183           s/\s*\|\s*(uri|url|html)\s*$//;    # Could be another filter...
184         $line =~ s{
185             \[%
186             \s*$pre_chomp\s*
187             \Q$tt_block\E\s*\|\s*(uri|url|html)
188             \s*$post_chomp\s*
189             %\]
190         }{[%$pre_chomp$tt_block$post_chomp%]}xms;
191
192         return ( $line, 'extra_filter_not_needed' );
193     }
194
195     if (
196         # Use the uri filter is needed
197         # If html filtered or not filtered
198         $filter ne 'html'
199             and (
200                     $tt_block !~ m{\|}
201                 or ($tt_block =~ m{\|\s?html} and not $tt_block =~ m{\|\s?html_entity})
202                 or $tt_block !~ m{\s*|\s*(uri|url)}
203       )
204     ) {
205         $tt_block =~ s/^\s*|\s*$//g;    # trim
206         $tt_block =~ s/\s*\|\s*html\s*//;
207         $line =~ s{
208                 \[%
209                 \s*$pre_chomp\s*
210                 \Q$tt_block\E(\s*\|\s*html)?
211                 \s*$post_chomp\s*
212                 %\]
213             }{[%$pre_chomp$tt_block | uri$post_chomp%]}xms;
214
215         $error = 'wrong_html_filter';
216     }
217     elsif (
218         $tt_block !~ m{\|\s?html} # already has html filter
219       )
220     {
221         $tt_block =~ s/^\s*|\s*$//g; # trim
222         $line =~ s{
223             \[%
224             \s*$pre_chomp\s*
225             \Q$tt_block\E
226             \s*$post_chomp\s*
227             %\]
228         }{[%$pre_chomp$tt_block | html$post_chomp%]}xms;
229
230         $error = 'missing_filter';
231     }
232     return ( $line, $error );
233 }
234
235 1;
236
237 =head1 NAME
238
239 t::lib::QA::TemplateFilters - Module used by tests and QA script to catch missing filters in template files
240
241 =head1 SYNOPSIS
242
243     my $content = read_file($filename);
244     my $new_content = t::lib::QA::TemplateFilters::fix_filters($content);
245     my $errors      = t::lib::QA::TemplateFilters::missing_filters($content);
246
247 =head1 DESCRIPTION
248
249 The goal of this module is to make the subroutine reusable from the QA scripts
250 and to not duplicate the code.
251
252 =head1 METHODS
253
254 =head2 fix_filters
255
256     Take a template content file in parameter and return the same content with
257     the correct (guessed) filters.
258     It will also add the [% USE raw %] statement if it is needed.
259
260 =head2 missing_filters
261
262     Take a template content file in parameter and return an arrayref of errors.
263
264     An error is a hashref with 3 keys, error and line, line_number.
265     * error can be:
266     asset_must_be_raw - When Asset is called without using raw
267     missing_filter    - When a TT variable is displayed without filter
268     wrong_html_filter - When a TT variable is using the html filter when uri (or url)
269                         should be used instead.
270
271     * line is the line where the error has been found.
272     * line_number is the line number where the error has been found.
273
274
275 =head1 AUTHORS
276
277 Jonathan Druart <jonathan.druart@bugs.koha-community.org>
278
279 =head1 COPYRIGHT
280
281 Copyright 2017 - Koha Development Team
282
283 =head1 LICENSE
284
285 This file is part of Koha.
286
287 Koha is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by
288 the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
289
290 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.
291
292 You should have received a copy of the GNU General Public License along with Koha; if not, see <http://www.gnu.org/licenses>.
293
294 =cut
295
296 1;