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