source: trunk/CommentPlugin/lib/Foswiki/Plugins/CommentPlugin/Comment.pm @ 13648

Revision 13648, 13.3 KB checked in by GeorgeClark, 16 months ago (diff)

Item11441: redirectto overrides endPoint

The old CommentPlugin uses the query parameter "redirectto" to return to
a different topic after save. The REST script doesn't implement this,
and instead redirects to query parameter "endPoint".

Change the comment rest handler to replace endPoint with the value
recovered from redirectto and print a warning. Add unit tests to
verify that the redirect can handle anchors and query parameters, and
that a old UserCommentTemplate with redirectto correctly redirects.

  • Property svn:keywords set to Revision Date
Line 
1# See bottom of file for license and copyright information
2#
3# This version is specific to Foswiki::Plugins::VERSION > 1.026
4
5use strict;
6use warnings;
7use Assert;
8use Error ':try';
9
10use Foswiki;
11use Foswiki::Plugins;
12use Foswiki::Store;
13
14use CGI qw( -any );
15
16package Foswiki::Plugins::CommentPlugin::Comment;
17
18# PUBLIC STATIC convert COMMENT statements to form prompts
19sub prompt {
20    my ( $attrs, $web, $topic, $disabled ) = @_;
21
22    my $type =
23         $attrs->{type}
24      || $attrs->{mode}
25      || Foswiki::Func::getPreferencesValue('COMMENTPLUGIN_DEFAULT_TYPE')
26      || 'above';
27
28    my $templatetopic;
29    if ( $attrs->{templatetopic} ) {
30        my ( $templocweb, $temploctopic ) =
31          Foswiki::Func::normalizeWebTopicName( $web, $attrs->{templatetopic} );
32        $templatetopic = "$templocweb.$temploctopic";
33    }
34
35    # Get the templates.
36    my $templateFile =
37         $templatetopic
38      || Foswiki::Func::getPreferencesValue('COMMENTPLUGIN_TEMPLATES')
39      || 'comments';
40
41    unless ( Foswiki::Func::loadTemplate($templateFile) ) {
42        Foswiki::Func::writeWarning(
43            "Could not read template file '$templateFile'");
44        return _alert("Could not read templates from '$templateFile'");
45    }
46
47    my $message = $attrs->{default} || '';
48    $message = $disabled if $disabled;
49
50    # clean off whitespace
51    $type =~ s/\s+//;
52
53    # Expand the template in the context of the web where the comment
54    # box is (not the target of the comment!)
55    my $input = Foswiki::Func::expandTemplate("PROMPT:$type");
56    return _alert("No such template def 'PROMPT:$type'")
57      unless ( defined($input) && $input ne '' );
58
59    # Expand special attributes as required
60    $input =~ s/%([a-z]\w+)\|(.*?)%/_expandPromptParams($1, $2, $attrs)/ieg;
61
62    # see if this comment is targeted at a different topic, and
63    # change the url if it is.
64    my $anchor = undef;
65    my $target = $attrs->{target};
66    if ($target) {
67
68        # extract web and anchor
69        if ( $target =~ s/^(\w+)\.// ) {
70            $web = $1;
71        }
72        if ( $target =~ s/(#\w+)$// ) {
73            $anchor = $1;
74        }
75        if ( $target ne '' ) {
76            $topic = $target;
77        }
78    }
79
80    # see if an alternate return is specified.  Sanitize and set the endpoint
81    # if set.
82    my $endPointReq = $attrs->{redirectto} || '';
83    my $endPoint = "$web.$topic";
84
85    if ($endPointReq) {
86        my $epParam = '';
87
88        # extract ur
89        if ( $endPointReq =~ s/([\?\#].*)$// ) {
90            $epParam = $1;
91        }
92        my ( $epWeb, $epTopic ) =
93          Foswiki::Func::normalizeWebTopicName( $web, $endPointReq );
94        if ( Foswiki::Func::topicExists( $web, $endPointReq ) ) {
95            $endPoint = $epWeb . '/' . $epTopic . $epParam;
96        }
97    }
98
99    # See if a save url has been defined in the template
100    my $url = Foswiki::Func::expandTemplate('save_url');
101
102    # Default it to a rest url if not
103    $url ||= Foswiki::Func::getScriptUrl( 'CommentPlugin', 'comment', 'rest' );
104
105    $url = '' if $disabled;
106
107    my $noform = $attrs->{noform} || '';
108
109    # Note: Item10050: If CommentPlugin prompt adds newlines then it prevents
110    # COMMENT inside TML tables so avoid cosmetic \n
111    if ( $input !~ m/^%RED%/ ) {
112        $input =~ s/%DISABLED%/$disabled ? 'disabled' : '' /ge;
113        $input =~ s/%MESSAGE%/$message/g;
114        my $idx = $attrs->{comment_index};
115
116        unless ($disabled) {
117            my $hiddenFields = "";
118            $hiddenFields .=
119              CGI::hidden( -name => 'topic', -value => "$web.$topic" );
120
121            $hiddenFields .=
122              CGI::hidden( -name => 'comment_action', -value => 'save' );
123
124            $hiddenFields .=
125              CGI::hidden( -name => 'endPoint', -value => $endPoint );
126
127            $hiddenFields .=
128              CGI::hidden( -name => 'comment_type', -value => $type );
129
130            if ( defined( $attrs->{nonotify} ) ) {
131                $hiddenFields .=
132                  CGI::hidden( -name => 'comment_nonotify', value => 1 );
133            }
134            if ($templatetopic) {
135                $hiddenFields .= CGI::hidden(
136                    -name  => 'comment_templatetopic',
137                    -value => $templatetopic
138                );
139            }
140            if ( $attrs->{location} ) {
141                $hiddenFields .= CGI::hidden(
142                    -name  => 'comment_location',
143                    -value => $attrs->{location}
144                );
145            }
146            elsif ($anchor) {
147                $hiddenFields .=
148                  CGI::hidden( -name => 'comment_anchor', -value => $anchor );
149            }
150            else {
151                $hiddenFields .=
152                  CGI::hidden( -name => 'comment_index', -value => $idx );
153            }
154            if ( $attrs->{nopost} ) {
155                $hiddenFields .= CGI::hidden(
156                    -name  => 'comment_nopost',
157                    -value => $attrs->{nopost}
158                );
159            }
160            if ( $attrs->{remove} ) {
161                $hiddenFields .=
162                  CGI::hidden( -name => 'comment_remove', -value => $idx );
163            }
164            $input .= $hiddenFields;
165        }
166
167        # SMELL: would have been more elegant to split this into
168        # FORM:head:type and FORM:tail:type. Too late now :-(
169        my $form = Foswiki::Func::expandTemplate("FORM:$type");
170
171        if ( $noform || $form ) {
172            if ($form) {
173                $form =~ s/%COMMENTPROMPT%/$input/;
174                $input = $form;
175            }
176            else {
177                $input = "$form $input";
178            }
179        }
180        else {
181            my $startform = CGI::start_form(
182                -name   => $type . $idx,
183                -id     => $type . $idx,
184                -action => $url,
185                -method => 'post'
186            );
187
188            # Item10050: CGI may add a trailing new line.
189            # This prevents using COMMENT inside TML tables
190            $startform =~ s/\n$//;
191
192            $input = $startform . $input . CGI::end_form();
193        }
194    }
195    return $input;
196}
197
198sub _alert {
199    my $mess = shift;
200    return "<span class='foswikiAlert'> $mess </span>";
201}
202
203# PRIVATE expand special %param|default% parameters in PROMPT template
204sub _expandPromptParams {
205    my ( $name, $default, $attrs ) = @_;
206
207    my $val = $attrs->{$name};
208    return $val if defined($val);
209    return $default;
210}
211
212# PUBLIC build new topic text
213sub save {
214
215    my ( $text, $web, $topic ) = @_;
216
217    my $wikiName = Foswiki::Func::getWikiName();
218    my $mode     = $Foswiki::cfg{Plugins}{CommentPlugin}{RequiredForSave}
219      || 'change';
220    my $access =
221      Foswiki::Func::checkAccessPermission( $mode, $wikiName, $text, $topic,
222        $web );
223    unless ($access) {
224
225        # user has no permission to change the topic
226        throw Foswiki::AccessControlException(
227            $mode,
228            $wikiName,
229            web   => $web,
230            topic => $topic,
231            ''
232        );
233    }
234
235    my $query = Foswiki::Func::getCgiQuery();
236    return unless $query;
237
238    # The type of the comment dictates where in the target topic it
239    # will be saved.
240    my $type =
241         $query->param('comment_type')
242      || Foswiki::Func::getPreferencesValue('COMMENTPLUGIN_DEFAULT_TYPE')
243      || 'above';
244
245    # Indexing comment instances depends on macro expansion
246    # inside-out-left-right order and INCLUDE and SECTION expansion
247    # being correctly handled. Only relevant if the comment is being
248    # inserted relative to the instance, of course.
249    my $index = $query->param('comment_index') || 0;
250
251    my $anchor        = $query->param('comment_anchor');
252    my $location      = $query->param('comment_location');
253    my $remove        = $query->param('comment_remove');
254    my $nopost        = $query->param('comment_nopost');
255    my $templatetopic = $query->param('comment_templatetopic');
256
257    if ($templatetopic) {
258        my ( $templocweb, $temploctopic ) =
259          Foswiki::Func::normalizeWebTopicName( $web, $templatetopic );
260        $templatetopic = "$templocweb.$temploctopic";
261    }
262
263    # Get the templates.
264    my $templateFile =
265         $templatetopic
266      || Foswiki::Func::getPreferencesValue('COMMENTPLUGIN_TEMPLATES')
267      || 'comments';
268
269    Foswiki::Func::loadTemplate($templateFile);
270
271    my $output = Foswiki::Func::expandTemplate("OUTPUT:$type");
272    die _alert("No such template def 'OUTPUT:$type'") unless $output;
273
274    # Expand the template
275    my $position = 'AFTER';
276    if ( $output =~ s/%POS:(.*?)%//g ) {
277        $position = $1;
278    }
279
280    # Expand common variables in the template, but don't expand other
281    # tags.
282    $output = Foswiki::Func::expandVariablesOnTopicCreation($output);
283
284    $output = '' unless defined($output);
285
286    # SMELL: Reverse the process that inserts meta-data just performed
287    # by the Foswiki core, but this time without the support of the
288    # methods in the core. Fortunately this will work even if there is
289    # no embedded meta-data.
290    my $premeta   = '';
291    my $postmeta  = '';
292    my $inpost    = 0;
293    my $innerText = '';
294    foreach my $line ( split( /\r?\n/, $text ) ) {
295        if ( $line =~ /^%META:[A-Z]+{[^}]*}%$/ ) {
296            if ($inpost) {
297                $postmeta .= $line . "\n";
298            }
299            else {
300                $premeta .= $line . "\n";
301            }
302        }
303        else {
304            $innerText .= $line . "\n";
305            $inpost = 1;
306        }
307    }
308    $text = $innerText;
309
310    #make sure the anchor or location exits
311    if ( defined($location) and not( $text =~ /(?<!location\=\")($location)/ ) )
312    {
313        undef $location;
314    }
315    if ( defined($anchor) and $text !~ /^$anchor\s*$/m ) {
316        undef $anchor;
317    }
318
319    unless ($nopost) {
320        if ( $position eq 'TOP' ) {
321            $text = $output . $text;
322        }
323        elsif ( $position eq 'BOTTOM' ) {
324
325            # Awkward newlines here, to avoid running into meta-data.
326            # This should _not_ be a problem.
327            $text =~ s/[\r\n]+$//;
328            $text .= "\n" unless $output =~ m/^\n/s;
329            $text .= $output;
330            $text .= "\n" unless $text   =~ m/\n$/s;
331        }
332        else {
333            if ($location) {
334                if ( $position eq 'BEFORE' ) {
335                    $text .= $output
336                      unless (
337                        $text =~ s/(?<!location\=\")($location)/$output$1/m );
338                }
339                else {    # AFTER
340                    $text .= $output
341                      unless (
342                        $text =~ s/(?<!location\=\")($location)/$1$output/m );
343
344                }
345                $text .= "\n" unless $text =~ m/\n$/s;
346            }
347            elsif ($anchor) {
348
349                # position relative to anchor
350                if ( $position eq 'BEFORE' ) {
351                    $text .= $output
352                      unless ( $text =~ s/^($anchor\s)/$output$1/m );
353                }
354                else {    # AFTER
355                    $text .= $output
356                      unless ( $text =~ s/^($anchor\s)/$1$output/m );
357                }
358                $text .= "\n" unless $text =~ m/\n$/s;
359            }
360            else {
361
362                # Position relative to index'th comment
363                my $idx = 0;
364                unless (
365                    $text =~ s((%COMMENT({.*?})?%.*\n))
366                          (&_nth($1,\$idx,$position,$index,$output))eg
367                  )
368                {
369
370                    # If there was a problem adding relative to the comment,
371                    # add to the end of the topic
372                    $text .= $output;
373                }
374                $text .= "\n" unless $text =~ m/\n$/s;
375            }
376        }
377    }
378
379    if ( defined $remove ) {
380
381        # remove the index'th comment box
382        my $idx = 0;
383        $text =~ s/(%COMMENT({.*?})?%)/_remove_nth($1,\$idx,$remove)/eg;
384    }
385
386    return $premeta . $text . $postmeta;
387}
388
389# PRIVATE embed output if this comment is the interesting one
390sub _nth {
391    my ( $tag, $pidx, $position, $index, $output ) = @_;
392
393    if ( $$pidx == $index ) {
394        if ( $position eq 'BEFORE' ) {
395            $tag = $output . $tag;
396        }
397        else {    # AFTER
398            $tag .= $output;
399        }
400    }
401    $$pidx++;
402    return $tag;
403}
404
405# PRIVATE remove the nth comment box
406sub _remove_nth {
407    my ( $tag, $pidx, $index ) = @_;
408    $tag = '' if ( $$pidx == $index );
409    $$pidx++;
410    return $tag;
411}
412
4131;
414__END__
415Foswiki - The Free and Open Source Wiki, http://foswiki.org/
416
417Copyright (C) 2008-2010 Foswiki Contributors. Foswiki Contributors
418are listed in the AUTHORS file in the root of this distribution.
419NOTE: Please extend that file, not this notice.
420
421Additional copyrights apply to some or all of the code in this
422file as follows:
423
424Copyright (C) 2004 Crawford Currie
425Copyright (C) 2001-2006 TWiki Contributors.
426
427Original author David Weller, reimplemented by Peter Masiar
428and again by Crawford Currie
429
430This program is free software; you can redistribute it and/or
431modify it under the terms of the GNU General Public License
432as published by the Free Software Foundation; either version 2
433of the License, or (at your option) any later version. For
434more details read LICENSE in the root of this distribution.
435
436This program is distributed in the hope that it will be useful,
437but WITHOUT ANY WARRANTY; without even the implied warranty of
438MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
439
440As per the GPL, removal of this notice is prohibited.
Note: See TracBrowser for help on using the repository browser.