source: trunk/core/lib/Foswiki/Macros/INCLUDE.pm @ 14686

Revision 14686, 14.2 KB checked in by OlivierRaginel, 13 days ago (diff)

Item11808: perltidy them all, so people are not accidentally impacted by the new enforced rule -- Sorry Micha, could not find a way to define specific values within the file

Line 
1# See bottom of file for license and copyright information
2package Foswiki;
3
4use strict;
5use warnings;
6
7# applyPatternToIncludedText( $text, $pattern ) -> $text
8# Apply a pattern on included text to extract a subset
9# Package-private; used by IncludeHandlers.
10sub applyPatternToIncludedText {
11    my ( $text, $pattern ) = @_;
12
13    $pattern = Foswiki::Sandbox::untaint( $pattern, \&validatePattern );
14
15    my $ok = 0;
16    eval {
17
18        # The eval acts as a try block in case there is anything evil in
19        # the pattern.
20
21        # The () ensures that $1 is defined if $pattern matches
22        # but does not capture anything
23        if ( $text =~ m/$pattern()/is ) {
24            $text = $1;
25        }
26        else {
27
28            # The pattern did not match, so return nothing
29            $text = '';
30        }
31        $ok = 1;
32    };
33    $text = '' unless $ok;
34
35    return $text;
36}
37
38# Replace web references in a topic. Called from forEachLine, applying to
39# each non-verbatim and non-literal line.
40sub _fixupIncludedTopic {
41    my ( $text, $options ) = @_;
42
43    my $fromWeb = $options->{web};
44
45    unless ( $options->{in_noautolink} ) {
46
47        # 'TopicName' to 'Web.TopicName'
48        $text =~
49s#(?:^|(?<=[\s(]))($Foswiki::regex{wikiWordRegex})(?=\s|\)|$)#$fromWeb.$1#go;
50    }
51
52    # Handle explicit [[]] everywhere
53    # '[[TopicName][...]]' to '[[Web.TopicName][...]]'
54    $text =~ s/\[\[([^]]+)\](?:\[([^]]+)\])?\]/
55      _fixIncludeLink( $fromWeb, $1, $2 )/geo;
56
57    return $text;
58}
59
60# Add a web reference to a [[...][...]] link in an included topic
61sub _fixIncludeLink {
62    my ( $web, $link, $label ) = @_;
63
64    # Detect absolute and relative URLs and web-qualified wikinames
65    if ( $link =~
66m#^($Foswiki::regex{webNameRegex}\.|$Foswiki::regex{defaultWebNameRegex}\.|$Foswiki::regex{linkProtocolPattern}:|/)#o
67      )
68    {
69        if ($label) {
70            return "[[$link][$label]]";
71        }
72        else {
73            return "[[$link]]";
74        }
75    }
76    elsif ( !$label ) {
77
78        # Must be wikiword or spaced-out wikiword (or illegal link :-/)
79        $label = $link;
80    }
81
82    # If link is only an anchor, leave it as is (Foswikitask:Item771)
83    return "[[$link][$label]]" if $link =~ /^#/;
84    return "[[$web.$link][$label]]";
85}
86
87# generate an include warning
88# SMELL: varying number of parameters idiotic to handle for customized $warn
89sub _includeWarning {
90    my $this    = shift;
91    my $warn    = shift;
92    my $message = shift;
93
94    if ( $warn eq 'on' ) {
95        return $this->inlineAlert( 'alerts', $message, @_ );
96    }
97    elsif ( isTrue($warn) ) {
98
99        # different inlineAlerts need different argument counts
100        my $argument = '';
101        if ( $message eq 'topic_not_found' ) {
102            my ( $web, $topic ) = @_;
103            $argument = "$web.$topic";
104        }
105        else {
106            $argument = shift;
107        }
108        $warn =~ s/\$topic/$argument/go if $argument;
109        return $warn;
110    }    # else fail silently
111    return '';
112}
113
114sub _includeProtocol {
115    my ( $this, $handler, $control, $params ) = @_;
116
117    eval 'use Foswiki::IncludeHandlers::' . $handler . ' ()';
118    if ($@) {
119        return $this->_includeWarning( $control->{warn}, 'bad_include_path',
120            $control->{_DEFAULT} );
121    }
122    else {
123        $handler = 'Foswiki::IncludeHandlers::' . $handler;
124        return $handler->INCLUDE( $this, $control, $params );
125    }
126}
127
128sub _includeTopic {
129    my ( $this, $includingTopicObject, $control, $params ) = @_;
130
131    my $includedWeb;
132    my $includedTopic = $control->{_DEFAULT};
133    $includedTopic =~ s/\.txt$//;    # strip optional (undocumented) .txt
134
135    ( $includedWeb, $includedTopic ) =
136      $this->normalizeWebTopicName( $includingTopicObject->web,
137        $includedTopic );
138
139    if ( !Foswiki::isValidTopicName( $includedTopic, 1 ) ) {
140        return $this->_includeWarning(
141            $control->{warn}, 'bad_include_path', $control->{_DEFAULT}
142          ),
143          'bad_include_path';
144    }
145
146    # See Codev.FailedIncludeWarning for the history.
147    unless ( $this->{store}->topicExists( $includedWeb, $includedTopic ) ) {
148        return _includeWarning( $this, $control->{warn}, 'topic_not_found',
149            $includedWeb, $includedTopic ),
150          'topic_not_found';
151    }
152
153    # prevent recursive includes. Note that the inclusion of a topic into
154    # itself is not blocked; however subsequent attempts to include the
155    # topic will fail. There is a hard block of 99 on any recursive include.
156    my $key = $includingTopicObject->web . '.' . $includingTopicObject->topic;
157    my $count = grep( $key, keys %{ $this->{_INCLUDES} } );
158    $key .= $control->{_sArgs};
159    if ( $this->{_INCLUDES}->{$key} || $count > 99 ) {
160        return _includeWarning( $this, $control->{warn}, 'already_included',
161            "$includedWeb.$includedTopic", '' ),
162          'already_included';
163    }
164
165    # Push the topic context to the included topic, so we can create
166    # local (SESSION) macro definitions without polluting the including
167    # topic namespace.
168    $this->{prefs}->pushTopicContext( $this->{webName}, $this->{topicName} );
169
170    $this->{_INCLUDES}->{$key} = 1;
171
172    my $includedTopicObject =
173      Foswiki::Meta->load( $this, $includedWeb, $includedTopic,
174        $control->{rev} );
175    unless ( $includedTopicObject->haveAccess('VIEW') ) {
176        if ( isTrue( $control->{warn} ) ) {
177            return $this->inlineAlert( 'alerts', 'access_denied',
178                "[[$includedWeb.$includedTopic]]" ),
179              'access_denied';
180        }    # else fail silently
181        return '', 'access_denied';
182    }
183    my $memWeb   = $this->{prefs}->getPreference('INCLUDINGWEB');
184    my $memTopic = $this->{prefs}->getPreference('INCLUDINGTOPIC');
185
186    my $text       = '';
187    my $error      = '';
188    my $verbatim   = {};
189    my $dirtyAreas = {};
190    try {
191
192        # Copy params into session level preferences. That way finalisation
193        # will apply to them. These preferences will be popped when the topic
194        # context is restored after the include.
195        $this->{prefs}->setSessionPreferences(%$params);
196
197        # Set preferences that finalisation does *not* apply to
198        $this->{prefs}->setInternalPreferences(
199            INCLUDINGWEB   => $includingTopicObject->web,
200            INCLUDINGTOPIC => $includingTopicObject->topic
201        );
202
203        $text = $includedTopicObject->text;
204
205        # Simplify leading, and remove trailing, newlines. If we don't remove
206        # trailing, it becomes impossible to %INCLUDE a topic into a table.
207        $text =~ s/^[\r\n]+/\n/;
208        $text =~ s/[\r\n]+$//;
209
210        # remove everything before and after the default include block unless
211        # a section is explicitly defined
212        if ( !$control->{section} ) {
213            $text =~ s/.*?%STARTINCLUDE%//s;
214            $text =~ s/%STOPINCLUDE%.*//s;
215        }
216
217        # prevent dirty areas in included topics from being parsed
218        $text = takeOutBlocks( $text, 'dirtyarea', $dirtyAreas )
219          if $Foswiki::cfg{Cache}{Enabled};
220
221        # handle sections
222        my ( $ntext, $sections ) = parseSections($text);
223
224        my $interesting = ( defined $control->{section} );
225        if ( $interesting || scalar(@$sections) ) {
226
227            # Rebuild the text from the interesting sections
228            $text = '';
229            foreach my $s (@$sections) {
230                if (   $control->{section}
231                    && $s->{type} eq 'section'
232                    && $s->{name} eq $control->{section} )
233                {
234                    $text .=
235                      substr( $ntext, $s->{start}, $s->{end} - $s->{start} );
236                    $interesting = 1;
237                    last;
238                }
239                elsif ( $s->{type} eq 'include' && !$control->{section} ) {
240                    $text .=
241                      substr( $ntext, $s->{start}, $s->{end} - $s->{start} );
242                    $interesting = 1;
243                }
244            }
245        }
246
247        if ( $interesting and ( length($text) eq 0 ) ) {
248            $text =
249              _includeWarning( $this, $control->{warn},
250                'topic_section_not_found', $includedWeb, $includedTopic,
251                $control->{section} );
252            $error = 'topic_section_not_found';
253        }
254        else {
255
256            # If there were no interesting sections, restore the whole text
257            $text = $ntext unless $interesting;
258
259            $text = applyPatternToIncludedText( $text, $control->{pattern} )
260              if ( $control->{pattern} );
261
262            # Do not show TOC in included topic if TOC_HIDE_IF_INCLUDED
263            # preference has been set
264            if ( isTrue( $this->{prefs}->getPreference('TOC_HIDE_IF_INCLUDED') )
265              )
266            {
267                $text =~ s/%TOC(?:{(.*?)})?%//g;
268            }
269
270            $this->innerExpandMacros( \$text, $includedTopicObject );
271
272        # Item9569: remove verbatim blocks from text passed to commonTagsHandler
273            $text = takeOutBlocks( $text, 'verbatim', $verbatim );
274
275            # 4th parameter tells plugin that its called for an included file
276            $this->{plugins}
277              ->dispatch( 'commonTagsHandler', $text, $includedTopic,
278                $includedWeb, 1, $includedTopicObject );
279            putBackBlocks( \$text, $verbatim, 'verbatim' );
280
281            # We have to expand tags again, because a plugin may have inserted
282            # additional tags.
283            $this->innerExpandMacros( \$text, $includedTopicObject );
284
285            # If needed, fix all 'TopicNames' to 'Web.TopicNames' to get the
286            # right context so that links continue to work properly
287            if ( $includedWeb ne $includingTopicObject->web ) {
288                my $removed = {};
289
290                $text = $this->renderer->forEachLine(
291                    $text,
292                    \&_fixupIncludedTopic,
293                    {
294                        web        => $includedWeb,
295                        pre        => 1,
296                        noautolink => 1
297                    }
298                );
299
300                # handle tags again because of plugin hook
301                innerExpandMacros( $this, \$text, $includedTopicObject );
302            }
303        }
304    }
305    finally {
306
307        # always restore the context, even in the event of an error
308        delete $this->{_INCLUDES}->{$key};
309
310        $this->{prefs}->setInternalPreferences(
311            INCLUDINGWEB   => $memWeb,
312            INCLUDINGTOPIC => $memTopic
313        );
314
315        # restoring dirty areas
316        putBackBlocks( \$text, $dirtyAreas, 'dirtyarea' )
317          if $Foswiki::cfg{Cache}{Enabled};
318
319        ( $this->{webName}, $this->{topicName} ) =
320          $this->{prefs}->popTopicContext();
321    };
322
323    return ( $text, $error );
324}
325
326# Processes a specific instance %<nop>INCLUDE{...}% syntax.
327# Returns the text to be inserted in place of the INCLUDE command.
328# $includingTopicObject should be for the immediate parent topic in the
329# include hierarchy. Works for both URLs and absolute server paths.
330sub INCLUDE {
331    my ( $this, $params, $includingTopicObject ) = @_;
332
333    # remember args for the key before mangling the params
334    my $args = $params->stringify();
335
336    # Remove params, so they don't get expanded in the included page
337    my %control;
338    for my $p (qw(_DEFAULT pattern rev section raw warn)) {
339        $control{$p} = $params->remove($p);
340    }
341    $control{_sArgs} = $args;
342
343    $control{warn} ||= $this->{prefs}->getPreference('INCLUDEWARNING');
344
345    my $text;
346
347    # make sure we have something to include. If we don't do this, then
348    # normalizeWebTopicName will default to WebHome. TWikibug:Item2209.
349    unless ( $control{_DEFAULT} ) {
350        $text =
351          $this->_includeWarning( $control{warn}, 'bad_include_path', '' );
352    }
353
354    # Filter out '..' from path to prevent includes of '../../file'
355    elsif ( $Foswiki::cfg{DenyDotDotInclude} && $control{_DEFAULT} =~ /\.\./ ) {
356        $text =
357          $this->_includeWarning( $control{warn}, 'bad_include_path',
358            $control{_DEFAULT} );
359    }
360
361    else {
362
363        # no sense in considering an empty string as an unfindable section
364        delete $control{section}
365          if ( defined( $control{section} ) && $control{section} eq '' );
366        $control{raw} ||= '';
367        $control{inWeb}   = $includingTopicObject->web;
368        $control{inTopic} = $includingTopicObject->topic;
369
370        # Protocol links e.g. http:, https:, doc:
371        if ( $control{_DEFAULT} =~ /^([a-z]+):/ ) {
372            $text = $this->_includeProtocol( $1, \%control, $params );
373        }
374        else {
375            my @topics = split( /,\s*/, $control{_DEFAULT} );
376            my $error;
377            foreach my $t (@topics) {
378                $control{_DEFAULT} = $t;
379                ( $text, $error ) =
380                  $this->_includeTopic( $includingTopicObject, \%control,
381                    $params );
382                last if ( $error eq '' );
383            }
384        }
385    }
386
387    # Apply any heading offset
388    my $hoff = $params->{headingoffset};
389    if ( $hoff && $hoff =~ /^([-+]?\d+)$/ && $1 != 0 ) {
390        my ( $off, $noff ) = ( 0 + $1, 0 - $1 );
391        $text = "<ho off=\"$off\"/>\n$text\n<ho off=\"$noff\">";
392    }
393
394    return $text;
395}
396
3971;
398__END__
399Foswiki - The Free and Open Source Wiki, http://foswiki.org/
400
401Copyright (C) 2008-2009 Foswiki Contributors. Foswiki Contributors
402are listed in the AUTHORS file in the root of this distribution.
403NOTE: Please extend that file, not this notice.
404
405Additional copyrights apply to some or all of the code in this
406file as follows:
407
408Copyright (C) 1999-2007 Peter Thoeny, peter@thoeny.org
409and TWiki Contributors. All Rights Reserved. TWiki Contributors
410are listed in the AUTHORS file in the root of this distribution.
411Based on parts of Ward Cunninghams original Wiki and JosWiki.
412Copyright (C) 1998 Markus Peter - SPiN GmbH (warpi@spin.de)
413Some changes by Dave Harris (drh@bhresearch.co.uk) incorporated
414
415This program is free software; you can redistribute it and/or
416modify it under the terms of the GNU General Public License
417as published by the Free Software Foundation; either version 2
418of the License, or (at your option) any later version. For
419more details read LICENSE in the root of this distribution.
420
421This program is distributed in the hope that it will be useful,
422but WITHOUT ANY WARRANTY; without even the implied warranty of
423MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
424
425As per the GPL, removal of this notice is prohibited.
Note: See TracBrowser for help on using the repository browser.