1# See bottom of file for license and copyright information
2package Foswiki::Plugins::WysiwygPlugin::Handlers;
3
4# This package contains the handler functions used to implement the
5# WysiwygPlugin. They are implemented here so we can 'lazy-load' this
6# module only when it is actually required.
7use strict;
8use warnings;
9use Assert;
10use Error (':try');
11
12use CGI qw( :cgi );
13
14use Foswiki::Func                              ();    # The plugins API
15use Foswiki::Plugins                           ();    # For the API version
16use Foswiki::Plugins::WysiwygPlugin::Constants ();
17
18our $html2tml;
19our $imgMap;
20our @refs;
21our %xmltagPlugin;
22
23our $SECRET_ID =
24'WYSIWYG content - do not remove this comment, and never use this identical text in your topics';
25
26sub toSiteCharSet {
27    my $string = shift;
28
29    return $string unless defined $string;
30
31    return $string if $Foswiki::UNICODE;
32
33    return $string
34      if ( $Foswiki::cfg{Site}{CharSet} =~ /^utf-?8/i );
35
36    # If the site charset is not utf-8, need to convert it
37    # Leave this code using Encode:: - not used on UNICODE core.
38    require Encode;
39    return Encode::encode(
40        $Foswiki::cfg{Site}{CharSet},
41        Encode::decode_utf8($string),
42        Encode::FB_PERLQQ
43    );
44}
45
46sub _SECRET_ID {
47    $SECRET_ID;
48}
49
50sub _OWEBTAG {
51    my ( $session, $params, $topic, $web ) = @_;
52
53    my $query = Foswiki::Func::getCgiQuery();
54
55    return $web unless $query;
56
57    my $tt = $query->param('templatetopic');
58    if ( defined($tt) ) {
59        my @split =
60          split( /\./, toSiteCharSet($tt) );
61
62        if ( $#split == 0 ) {
63            return $web;
64        }
65        else {
66            return $split[0];
67        }
68    }
69
70    return $web;
71}
72
73sub _OTOPICTAG {
74    my ( $session, $params, $topic, $web ) = @_;
75
76    my $query = Foswiki::Func::getCgiQuery();
77
78    return $topic unless $query;
79
80    my $tt = $query->param('templatetopic');
81    if ( defined($tt) ) {
82        my @split =
83          split( /\./, toSiteCharSet($tt) );
84
85        return $split[$#split];
86    }
87
88    return $topic;
89}
90
91# This handler is used to determine whether the topic is editable by
92# a WYSIWYG editor or not. The only thing it does is to redirect to a
93# normal edit url if the skin is set to WYSIWYGPLUGIN_WYSIWYGSKIN and
94# nasty content is found.
95sub beforeEditHandler {
96
97    #my( $text, $topic, $web, $meta ) = @_;
98
99    my $skin = Foswiki::Func::getPreferencesValue('WYSIWYGPLUGIN_WYSIWYGSKIN');
100
101    if ( $skin && Foswiki::Func::getSkin() =~ /\b$skin\b/o ) {
102        if ( notWysiwygEditable( $_[0] ) ) {
103
104            # redirect
105            my $query = Foswiki::Func::getCgiQuery();
106            foreach my $p (qw( skin cover )) {
107                my $arg = toSiteCharSet( $query->param($p) );
108                if ( $arg && $arg =~ s/\b$skin\b// ) {
109                    if ( $arg =~ /^[\s,]*$/ ) {
110                        $query->delete($p);
111                    }
112                    else {
113                        $query->param( -name => $p, -value => $arg );
114                    }
115                }
116            }
117            my $url = $query->url( -full => 1, -path => 1, -query => 1 );
118            Foswiki::Func::redirectCgiQuery( $query, $url );
119
120            # Bring this session to an untimely end
121            exit 0;
122        }
123    }
124}
125
126# This handler is only invoked *after* merging is complete
127sub beforeSaveHandler {
128
129    #my( $text, $topic, $web ) = @_;
130    my $query = Foswiki::Func::getCgiQuery();
131    return unless $query;
132
133    return unless defined( $query->param('wysiwyg_edit') );
134
135    $_[0] = TranslateHTML2TML( $_[0], $_[1], $_[2] );
136}
137
138# This handler is invoked before a merge. Merges are done before the
139# afterEditHandler is called, so we need to translate here.
140sub beforeMergeHandler {
141
142    #my( $text, $currRev, $currText, $origRev, $origText, $web, $topic ) = @_;
143    afterEditHandler( $_[0], $_[6], $_[5] );
144}
145
146# This handler is invoked *after* a merge, and only from the edit
147# script (so it's useless for a REST save)
148sub afterEditHandler {
149    my ( $text, $topic, $web ) = @_;
150    my $query = Foswiki::Func::getCgiQuery();
151    return unless $query;
152
153    return
154      unless defined( $query->param('wysiwyg_edit') )
155      || $text =~ s/<!--$SECRET_ID-->//go;
156
157    # Switch off wysiwyg_edit so it doesn't try to transform again in
158    # the beforeSaveHandler
159    $query->delete('wysiwyg_edit');
160
161    $text = TranslateHTML2TML( $text, $_[1], $_[2] );
162
163    $_[0] = $text;
164}
165
166# Invoked to convert HTML to TML
167# $text is a foswiki string, i.e. octets encoded in utf8, and so is the result.
168sub TranslateHTML2TML {
169    my ( $text, %opts ) = @_;
170
171    unless ($html2tml) {
172        require Foswiki::Plugins::WysiwygPlugin::HTML2TML;
173
174        $html2tml = new Foswiki::Plugins::WysiwygPlugin::HTML2TML();
175    }
176
177    # SMELL: really, really bad smell; bloody core should NOT pass text
178    # with embedded meta to plugins! It is VERY BAD DESIGN!!!
179    my $top = '';
180    if ( $text =~ s/^(%META:[A-Z]+{.*?}%\r?\n)//s ) {
181        $top = $1;
182    }
183    my $bottom = '';
184    $text =~ s/^(%META:[A-Z]+{.*?}%\r?\n)/$bottom = "$1$bottom";''/gem;
185
186    # Apply defaults
187    $opts{convertImage} ||= \&_convertImage;
188    $opts{rewriteURL}   ||= \&postConvertURL;
189
190    # used by above callbacks
191    $opts{web}   ||= $Foswiki::Plugins::SESSION->{webName};
192    $opts{topic} ||= $Foswiki::Plugins::SESSION->{topicName};
193
194    $opts{very_clean} = 1;    # aggressively polish TML
195    $opts{stickybits} =
196      Foswiki::Func::getPreferencesValue('WYSIWYGPLUGIN_STICKYBITS');
197    $opts{ignoreattrs} =
198      Foswiki::Func::getPreferencesValue('WYSIWYGPLUGIN_IGNOREATTRS');
199
200    $text = $html2tml->convert( $text, \%opts );
201
202    return $top . $text . $bottom;
203}
204
205# Handler used to process text in a =view= URL to generate text/html
206# containing the HTML of the topic to be edited.
207#
208# Invoked when the selected skin is in use to convert the text to HTML
209# We can't use the beforeEditHandler, because the editor loads up and then
210# uses a URL to fetch the text to be edited. This handler is designed to
211# provide the text for that request. It's a real struggle, because the
212# commonTagsHandler is called so many times that getting the right
213# call is hard, and then preventing a repeat call is harder!
214sub beforeCommonTagsHandler {
215
216    #my ( $text, $topic, $web, $meta )
217    my $query = Foswiki::Func::getCgiQuery();
218
219    # stop it from processing the template without expanded
220    # %TEXT% (grr; we need a better way to tell where we
221    # are in the processing pipeline)
222    return if ( $_[0] =~ /^<!-- WysiwygPlugin Template/ );
223
224    # Have to re-read the topic because verbatim blocks have already been
225    # lifted out, and we need them.
226    my $topic = $_[1];
227    my $web   = $_[2];
228    my ( $meta, $text );
229    my $altText = $query->param('templatetopic');
230    if ($altText) {
231        $altText = toSiteCharSet($altText);
232        if ( Foswiki::Func::topicExists( $web, $altText ) ) {
233            ( $web, $topic ) =
234              Foswiki::Func::normalizeWebTopicName( $web, $altText );
235        }
236    }
237
238    $_[0] = _WYSIWYG_TEXT( $Foswiki::Plugins::SESSION, {}, $topic, $web );
239}
240
241# Handler used by editors that require pre-prepared HTML embedded in the
242# edit template.
243sub _WYSIWYG_TEXT {
244    my ( $session, $params, $topic, $web ) = @_;
245
246    # Have to re-read the topic because content has already been munged
247    # by other plugins, or by the extraction of verbatim blocks.
248    my ( $meta, $text ) = Foswiki::Func::readTopic( $web, $topic );
249
250    $text = TranslateTML2HTML( $text, web => $web, topic => $topic );
251
252    # Lift out the text to protect it from further Foswiki rendering. It will be
253    # put back in the postRenderingHandler.
254    return _liftOut($text);
255}
256
257# Handler used to present the editable text in a javascript constant string
258sub _JAVASCRIPT_TEXT {
259    my ( $session, $params, $topic, $web ) = @_;
260
261    my $html = _dropBack( _WYSIWYG_TEXT(@_) );
262
263    $html =~ s/([\\'])/\\$1/sg;
264    $html =~ s/\r/\\r/sg;
265    $html =~ s/\n/\\n/sg;
266    $html =~ s/script/scri'+'pt/g;
267
268    return _liftOut("'$html'");
269}
270
271sub postRenderingHandler {
272
273    # Replace protected content.
274    $_[0] = _dropBack( $_[0] );
275}
276
277sub modifyHeaderHandler {
278    my ( $headers, $query ) = @_;
279
280    if ( $query->param('wysiwyg_edit') ) {
281        $headers->{Expires} = 0;
282        $headers->{'Cache-control'} = 'max-age=0, must-revalidate';
283    }
284}
285
286# The subset of vars for which bidirection transformation is supported
287# in URLs only
288use vars qw( @VARS );
289
290# The set of macros that get "special treatment" in URLs,  They have to end up
291# sorted based on their expanded length.  To convert from URL to MACRO it has to
292# be based upon longest match.  So _populateVars replaces this with the appropriately
293# sorted array.
294@VARS = (
295    '%ATTACHURL%',
296    '%ATTACHURLPATH%',
297    '%PUBURL%',
298    '%PUBURLPATH%',
299    '%SCRIPTURLPATH{"view"}%',
300    '%SCRIPTURLPATH{"viewfile"}%',
301    '%SCRIPTURLPATH%',
302    '%SCRIPTURL{"view"}%',
303    '%SCRIPTURL{"viewfile"}%',
304    '%SCRIPTURL%',
305    '%SCRIPTSUFFIX%',    # bit dodgy, this one
306);
307
308# Initialises the mapping from var to URL and back
309sub _populateVars {
310    my $opts = shift;
311
312    return if ( $opts->{exp} );
313
314    local $Foswiki::Plugins::WysiwygPlugin::recursionBlock =
315      1;                 # block calls to beforeCommonTagshandler
316
317    my @exp = split(
318        /\0/,
319        Foswiki::Func::expandCommonVariables(
320            join( "\0", @VARS ),
321            $opts->{topic}, $opts->{web}
322        )
323    );
324
325    # Item13178: The mapping between URL and vars needs to be longest match
326    # so the list must be sorted by length of the value.  Also, null entries
327    # should be omitted from the mapping, as they cannot be reversed.
328    my %varh;
329    my @exph = @exp;
330    foreach my $k (@VARS) {
331        my $val = shift @exph;
332        $varh{$k} = $val if ( defined $val );
333    }
334
335    my @nvars;
336    my @nexp;
337
338    # Do the sort by length.
339    foreach
340      my $k ( sort { length( $varh{$b} ) <=> length( $varh{$a} ) } keys %varh )
341    {
342        next unless $varh{$k};    # Omit empty variables, can't be reversed.
343        push @nvars, $k;
344        push @nexp,  $varh{$k};
345    }
346
347    @VARS = @nvars;    # Replace the vars list with the length sorted list.
348
349    # and build the list of values in order of @nvars.
350    for my $i ( 0 .. $#VARS ) {
351        my $nvar = $VARS[$i];
352        $opts->{match}[$i] = "\Q$nvar\E";
353        $nexp[$i] ||= '';    # Avoid undefined issues.
354    }
355    $opts->{exp} = \@nexp;
356
357}
358
359# callback passed to the TML2HTML convertor on each
360# variable in a URL used in a square bracketed link
361sub expandVarsInURL {
362    my ( $url, $opts ) = @_;
363
364    return '' unless $url;
365
366    _populateVars($opts);
367    for my $i ( 0 .. $#VARS ) {
368        $url =~ s/$opts->{match}[$i]/$opts->{exp}->[$i]/g;
369    }
370    return $url;
371}
372
373# callback passed to the HTML2TML convertor
374# See also foswiki_tiny.js in TinyMCEPlugin,  which performs similar functions.
375sub postConvertURL {
376    my ( $url, $opts ) = @_;
377
378    #my $orig = $url; #debug
379
380    local $Foswiki::Plugins::WysiwygPlugin::recursionBlock =
381      1;    # block calls to beforeCommonTagshandler
382
383    my $anchor = '';
384    if ( $url =~ s/(#.*)$// ) {
385        $anchor = $1;
386    }
387    my $parameters = '';
388    if ( $url =~ s/(\?.*)$// ) {
389        $parameters = $1;
390    }
391
392    _populateVars($opts);
393
394    for my $i ( 0 .. $#VARS ) {
395        next unless $opts->{exp}->[$i];
396
397        # URLs passed here will be URL-encoded, so
398        # we have to url-encode the test expression.
399        my $test = quotemeta( Foswiki::urlEncode( $opts->{exp}->[$i] ) );
400        $url =~ s/^$test/$VARS[$i]/g;
401    }
402
403    if ( $url =~ m#^%SCRIPTURL(?:PATH)?(?:{"view"}%|%/+view[^/]*)/+([/\w.]+)$#
404        && !$parameters )
405    {
406        my $orig = $1;
407        my ( $web, $topic ) =
408          Foswiki::Func::normalizeWebTopicName( $opts->{web}, $orig );
409
410        if ( $web && $web ne $opts->{web} ) {
411
412            return $web . '.' . $topic . $anchor;
413        }
414
415        return $topic . $anchor;
416    }
417
418    return $url . $parameters . $anchor;
419}
420
421# Callback used to convert an IMG reference into a Foswiki variable,
422# given the src= URL
423sub _convertImage {
424    my ( $src, $opts ) = @_;
425
426    return unless $src;
427
428    # block calls to beforeCommonTagshandler
429    local $Foswiki::Plugins::WysiwygPlugin::recursionBlock = 1;
430
431    # SMELL: this is not documented anywhere; is it still useful?
432    unless ($imgMap) {
433        $imgMap = {};
434        my $imgs = Foswiki::Func::getPreferencesValue('WYSIWYGPLUGIN_ICONS');
435        if ($imgs) {
436            while ( $imgs =~ s/src="(.*?)" alt="(.*?)"// ) {
437                my ( $src, $alt ) = ( $1, $2 );
438                $src =
439                  Foswiki::Func::expandCommonVariables( $src, $opts->{topic},
440                    $opts->{web} );
441                $alt .= '%' if $alt =~ /^%/;
442                $imgMap->{$src} = $alt;
443            }
444        }
445    }
446
447    return $imgMap->{$src};
448}
449
450# Replace content with a marker to prevent it being munged by Foswiki
451sub _liftOut {
452    my ($text) = @_;
453    my $n = scalar(@refs);
454    push( @refs, $text );
455    return "\05$n\05";
456}
457
458# Substitute marker
459sub _dropBack {
460    my ($text) = @_;
461
462    # Restore everything that was lifted out
463    while ( $text =~ s/\05([0-9]+)\05/$refs[$1]/gi ) {
464    }
465    return $text;
466}
467
468=begin TML
469
470---++ StaticMethod addXMLTag($tag, \&fn)
471
472Instruct WysiwygPlugin to "lift out" the named tag
473and pass it to &fn for processing.
474&fn may modify the text of the tag.
475&fn should return 0 if the tag is to be re-embedded immediately,
476or 1 if it is to be re-embedded after all processing is complete.
477The text passed (by reference) to &fn includes the
478=<tag> ... </tag>= brackets.
479
480The simplest use of this function is something like this:
481=Foswiki::Plugins::WysiwygPlugin::addXMLTag( 'mytag', sub { 1 } );=
482
483A plugin may call this function more than once
484e.g. to change the processing function for a tag.
485However, only the *original plugin* may change the processing
486for a tag.
487
488Plugins should call this function from their =initPlugin=
489handlers so that WysiwygPlugin will protect the XML-like tags
490for all conversions, including REST conversions.
491Plugins that are intended to be used with older versions of Foswiki
492(e.g. 1.0.6) should check that this function is defined before calling it,
493so that they degrade gracefully if an older version of WysiwygPlugin
494(e.g. that shipped with 1.0.6) is installed.
495
496=cut
497
498sub addXMLTag {
499    my ( $tag, $fn ) = @_;
500
501    my $plugin = caller;
502    $plugin =~ s/^Foswiki::Plugins:://;
503
504    return if not defined $tag;
505
506    if (
507        (
508                not exists $Foswiki::Plugins::WysiwygPlugin::xmltag{$tag}
509            and not exists $xmltagPlugin{$tag}
510        )
511        or ( $xmltagPlugin{$tag} eq $plugin )
512      )
513    {
514
515        # This is either a plugin adding a new tag
516        # or a plugin adding a tag it had previously added before.
517        # A plugin is allowed to add a tag that it had added before
518        # and the new function replaces the old.
519        #
520        $fn = sub { 1 }
521          unless $fn;    # Default function
522
523        $Foswiki::Plugins::WysiwygPlugin::xmltag{$tag} = $fn;
524        $xmltagPlugin{$tag}                            = $plugin;
525    }
526    else {
527
528        # DON'T replace the existing processing for this tag
529        printf STDERR "WysiwygPlugin::addXMLTag: "
530          . "$plugin cannot add XML tag $tag, "
531          . "that tag was already registered by $xmltagPlugin{$tag}\n";
532    }
533}
534
535# Invoked to convert TML to HTML
536# $text is a foswiki string, i.e. octets encoded in utf8, and so is the result.
537sub TranslateTML2HTML {
538    my ( $text, %opts ) = @_;
539
540    unless ($Foswiki::Plugins::WysiwygPlugin::tml2html) {
541        require Foswiki::Plugins::WysiwygPlugin::TML2HTML;
542        $Foswiki::Plugins::WysiwygPlugin::tml2html =
543          new Foswiki::Plugins::WysiwygPlugin::TML2HTML();
544    }
545
546    # Apply defaults
547    $opts{web}             ||= $Foswiki::Plugins::SESSION->{webName};
548    $opts{topic}           ||= $Foswiki::Plugins::SESSION->{topicName};
549    $opts{expandVarsInURL} ||= \&expandVarsInURL;
550    $opts{xmltag}          ||= \%Foswiki::Plugins::WysiwygPlugin::xmltag;
551    my $keepblocks =
552      Foswiki::Func::getPreferencesValue('WYSIWYGPLUGIN_PROTECT_TAG_BLOCKS');
553    if ( defined $keepblocks && $keepblocks ne 'NONE' ) {
554        $opts{keepblocks} = [];
555        foreach my $tag ( split /[,\s]+/, $keepblocks ) {
556            push( @{ $opts{keepblocks} }, $tag );
557        }
558    }
559    my $keeptags =
560      Foswiki::Func::getPreferencesValue('WYSIWYGPLUGIN_PROTECT_EXISTING_TAGS');
561    if ( defined $keeptags && $keeptags ne 'NONE' ) {
562        $opts{keeptags} = [];
563        foreach ( split( /[,\s]+/, $keeptags ) ) {
564            push( @{ $opts{keeptags} }, $_ );
565        }
566    }
567    $opts{forcenoautolink} =
568      Foswiki::isTrue( Foswiki::Func::getPreferencesValue('NOAUTOLINK') );
569    $opts{isKnownColour} = \&_isKnownColour;
570
571    # SMELL: WTF is this? - CDot
572    $opts{supportsparaindent} =
573      Foswiki::Func::getContext()->{SUPPORTS_PARA_INDENT};
574    my $disabled =
575      Foswiki::Plugins::WysiwygPlugin::wysiwygEditingDisabledForThisContent(
576        $_[0] );
577    $opts{protectall} = $disabled ? 1 : 0;
578
579    my $html =
580      $Foswiki::Plugins::WysiwygPlugin::tml2html->convert( $_[0], \%opts );
581
582    if ( $opts{protectall} ) {
583        $html = CGI::div(
584            { class => 'WYSIWYG_WARNING foswikiBroadcastMessage' },
585            Foswiki::Func::renderText(
586                Foswiki::Func::expandCommonVariables( <<"WARNING" ) ) )
587*%MAKETEXT{"Conversion to HTML for WYSIWYG editing is disabled because of the topic content."}%*
588
589%MAKETEXT{"This is why the conversion is disabled:"}% $disabled
590
591%MAKETEXT{"(This message will be removed automatically)"}%
592WARNING
593          . CGI::div( { class => 'WYSIWYG_PROTECTED' }, $html );
594    }
595
596    return $html;
597}
598
599# Look in the Foswiki preferences to see if the named colour is
600# a preference mapped to an HTML colour
601sub _isKnownColour {
602    my $name = shift;
603
604    my $epr = Foswiki::Func::getPreferencesValue($name);
605
606    # Match <font color="x" and style="color:x"
607    if (
608        defined $epr
609        && (   $epr =~ /color=["'](#?\w+)['"]/
610            || $epr =~ /color\s*:\s*(#?\w+)/
611            || $epr =~ /class=["']foswiki(${name})FG['"]/i )
612      )
613    {
614        return $1;
615    }
616    return undef;
617}
618
619# Text that is taken from a web page and added to the parameters of an XHR
620# by JavaScript is UTF-8 encoded. This is because UTF-8 is the default encoding
621# for XML, which XHR was designed to transport. For usefulness in Javascript
622# the response to an XHR should also be UTF-8 encoded.
623# This function generates such a response.
624sub returnRESTResult {
625    my ( $response, $status, $text ) = @_;
626
627    # Foswiki 1.0 introduces the Foswiki::Response object, which handles all
628    # responses.
629    if ( UNIVERSAL::isa( $response, 'Foswiki::Response' ) ) {
630        $response->header(
631            -status  => $status,
632            -type    => 'text/plain',
633            -charset => 'UTF-8'
634        );
635        $response->print($text);
636    }
637    else {    # Pre-Foswiki-1.0.
638              # Turn off AUTOFLUSH
639              # See http://perl.apache.org/docs/2.0/user/coding/coding.html
640        local $| = 0;
641        my $query = Foswiki::Func::getCgiQuery();
642        if ( defined($query) ) {
643            my $len;
644            { use bytes; $len = length($text); };
645            print $query->header(
646                -status         => $status,
647                -type           => 'text/plain',
648                -charset        => 'UTF-8',
649                -Content_length => $len
650            );
651            print $text;
652        }
653    }
654    print STDERR $text if ( $status >= 400 );
655}
656
657# Rest handler for use from Javascript. The 'text' parameter is used to
658# pass the text for conversion. The text must be URI-encoded (this is
659# to support use of this handler from XMLHttpRequest, which gets it
660# wrong). Example:
661#
662# var req = new XMLHttpRequest();
663# req.open("POST", url, true);
664# req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
665# var params = "text=" + encodeURIComponent(escape(text));
666# request.req.setRequestHeader("Content-length", params.length);
667# request.req.setRequestHeader("Connection", "close");
668# request.req.onreadystatechange = ...;
669# req.send(params);
670#
671sub REST_TML2HTML {
672    my ( $session, $plugin, $verb, $response ) = @_;
673
674    my $tml = Foswiki::Func::getCgiQuery()->param('text');
675    $tml = toSiteCharSet($tml);
676
677    return '' unless $tml;
678
679    # if the secret ID is present, don't convert again. We are probably
680    # going 'back' to this page (doesn't work on IE :-( )
681    if ( $tml =~ /<!--$SECRET_ID-->/ ) {
682        return $tml;
683    }
684
685    my $html = TranslateTML2HTML( toSiteCharSet($tml) );
686
687    # Add the secret id to trigger reconversion. Doesn't work if the
688    # editor eats HTML comments, so the editor may need to put it back
689    # in during final cleanup.
690    $html = '<!--' . $SECRET_ID . '-->' . $html;
691
692    returnRESTResult( $response, 200, $html );
693
694    return;    # to prevent further processing
695}
696
697# Rest handler for use from Javascript
698sub REST_HTML2TML {
699    my ( $session, $plugin, $verb, $response ) = @_;
700
701    my $html = Foswiki::Func::getCgiQuery()->param('text');
702
703    return '' unless $html;
704
705    $html = toSiteCharSet($html);
706
707    $html =~ s/<!--$SECRET_ID-->//go;
708    unless ($html2tml) {
709        require Foswiki::Plugins::WysiwygPlugin::HTML2TML;
710
711        $html2tml = new Foswiki::Plugins::WysiwygPlugin::HTML2TML();
712    }
713
714    my $tml = $html2tml->convert(
715        $html,
716        {
717            very_clean => 1,
718            stickybits =>
719              Foswiki::Func::getPreferencesValue('WYSIWYGPLUGIN_STICKYBITS'),
720            ignoreattrs =>
721              Foswiki::Func::getPreferencesValue('WYSIWYGPLUGIN_IGNOREATTRS'),
722            convertImage => \&_convertImage,
723            rewriteURL   => \&postConvertURL,
724            web          => $session->{webName},      # used by callbacks
725            topic        => $session->{topicName},    # used by callbacks
726        }
727    );
728
729    returnRESTResult( $response, 200, $tml );
730
731    return;    # to prevent further processing
732}
733
734sub _unquote {
735    my $text = shift;
736    $text =~ s/\\/\\\\/g;
737    $text =~ s/\n/\\n/g;
738    $text =~ s/\r/\\r/g;
739    $text =~ s/\t/\\t/g;
740    $text =~ s/"/\\"/g;
741    $text =~ s/'/\\'/g;
742    return $text;
743}
744
745# Get, and return, a list of attachments using JSON
746sub REST_attachments {
747    my ( $session, $plugin, $verb, $response ) = @_;
748    my ( $web, $topic ) = ( $session->{webName}, $session->{topicName} );
749    my ( $meta, $text ) = Foswiki::Func::readTopic( $web, $topic );
750
751    unless (
752        Foswiki::Func::checkAccessPermission(
753            'VIEW', Foswiki::Func::getWikiName(),
754            $text, $topic, $web, $meta
755        )
756      )
757    {
758        returnRESTResult( $response, 401, "Access denied" );
759        return;    # to prevent further processing
760    }
761
762    # Create a JSON list of attachment data, sorted by name
763    my @atts;
764    foreach my $att ( sort { $a->{name} cmp $b->{name} }
765        $meta->find('FILEATTACHMENT') )
766    {
767        push(
768            @atts,
769            '{' . join(
770                ',',
771                map {
772                        '"'
773                      . _unquote($_) . '":"'
774                      . _unquote( $att->{$_} ) . '"'
775                } keys %$att
776              )
777              . '}'
778        );
779
780    }
781    return '[' . join( ',', @atts ) . ']';
782}
783
7841;
785__END__
786Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/
787
788Copyright (C) 2008-2015 Foswiki Contributors. Foswiki Contributors
789are listed in the AUTHORS file in the root of this distribution.
790NOTE: Please extend that file, not this notice.
791
792Additional copyrights apply to some or all of the code in this file:
793
794Copyright (C) 2005 ILOG http://www.ilog.fr
795and TWiki Contributors. All Rights Reserved. TWiki Contributors
796are listed in the AUTHORS file in the root of your Foswiki (or TWiki)
797distribution.
798
799This program is free software; you can redistribute it and/or
800modify it under the terms of the GNU General Public License
801as published by the Free Software Foundation; either version 2
802of the License, or (at your option) any later version. For
803more details read LICENSE in the root of the TWiki distribution.
804
805This program is distributed in the hope that it will be useful,
806but WITHOUT ANY WARRANTY; without even the implied warranty of
807MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
808
809As per the GPL, removal of this notice is prohibited.
810