Changeset 5816
- Timestamp:
- 12/18/09 10:38:11 (2 years ago)
- Location:
- trunk/WysiwygPlugin
- Files:
-
- 5 edited
-
data/System/WysiwygPlugin.txt (modified) (2 diffs)
-
lib/Foswiki/Plugins/WysiwygPlugin.pm (modified) (9 diffs)
-
lib/Foswiki/Plugins/WysiwygPlugin/HTML2TML/Node.pm (modified) (1 diff)
-
test/unit/WysiwygPlugin/ExtendedTranslatorTests.pm (modified) (11 diffs)
-
test/unit/WysiwygPlugin/WysiwygPluginTests.pm (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/WysiwygPlugin/data/System/WysiwygPlugin.txt
r5573 r5816 7 7 %SHORTDESCRIPTION% 8 8 9 Support for the integration of WYSIWYG (What-You-See-Is-What-You-Get) editors. On its own, the only thing this plugin gives you is a stand-alone HTML to TML (topic markup language) translator script. For WYSIWYG editing in Foswiki and Foswiki,you will also need to install a specific editor package such as Foswiki:Extensions.TinyMCEPlugin.9 Support for the integration of WYSIWYG (What-You-See-Is-What-You-Get) editors. On its own, the only thing this plugin gives you is a stand-alone HTML to TML (topic markup language) translator script. For WYSIWYG editing you will also need to install a specific editor package such as Foswiki:Extensions.TinyMCEPlugin. 10 10 11 11 This plugin provides a generic framework that supports editing of topics using any browser-based HTML editor. It works by transforming TML into HTML for the editor, and then transforming HTML back into TML on save. … … 176 176 | Release: | %$RELEASE% | 177 177 | Change History: | | 178 | 18 Dec 2009 | Foswikitask:Item2511: move code out of the plugin module to accelerate loading | 178 179 | 18 Nov 2009 | Foswikitask:Item2369: Convert tables with cells that span rows | 179 180 | 22 Oct 2009 | Foswikitask:Item2183: Protect div style= by default | -
trunk/WysiwygPlugin/lib/Foswiki/Plugins/WysiwygPlugin.pm
r5354 r5816 23 23 package Foswiki::Plugins::WysiwygPlugin; 24 24 25 use CGI qw( :cgi -any );26 25 27 26 use strict; 28 27 29 28 use Assert; 30 use Encode (); 31 32 use Foswiki::Func (); # The plugins API 33 use Foswiki::Plugins (); # For the API version 34 use Foswiki::Plugins::WysiwygPlugin::Constants (); 35 36 use vars qw( $html2tml $tml2html $recursionBlock $imgMap ); 37 use vars qw( %FoswikiCompatibility @refs %xmltag %xmltagPlugin); 38 39 our $SHORTDESCRIPTION = 'Translator framework for Wysiwyg editors'; 29 30 our $SHORTDESCRIPTION = 'Translator framework for WYSIWYG editors'; 40 31 our $NO_PREFS_IN_TOPIC = 1; 41 32 our $VERSION = '$Rev$'; 42 33 43 our $RELEASE = '18 Sep2009';44 45 our $SECRET_ID =46 'WYSIWYG content - do not remove this comment, and never use this identical text in your topics'; 47 48 sub WHY { 0 } 34 our $RELEASE = '18 Dec 2009'; 35 36 our %xmltag; 37 38 # Set to 1 for reasons for rejection 39 sub WHY { 0 }; 49 40 50 41 sub initPlugin { … … 53 44 # %OWEB%.%OTOPIC% is the topic where the initial content should be 54 45 # grabbed from, as defined in templates/edit.skin.tmpl 55 Foswiki::Func::registerTagHandler( 'OWEB', \&_OWEBTAG ); 56 Foswiki::Func::registerTagHandler( 'OTOPIC', \&_OTOPICTAG ); 57 Foswiki::Func::registerTagHandler( 'WYSIWYG_TEXT', \&_WYSIWYG_TEXT ); 58 Foswiki::Func::registerTagHandler( 'JAVASCRIPT_TEXT', \&_JAVASCRIPT_TEXT ); 59 Foswiki::Func::registerTagHandler( 'WYSIWYG_SECRET_ID', 60 sub { $SECRET_ID } ); 61 62 Foswiki::Func::registerRESTHandler( 'tml2html', \&_restTML2HTML ); 63 Foswiki::Func::registerRESTHandler( 'html2tml', \&_restHTML2TML ); 64 Foswiki::Func::registerRESTHandler( 'upload', \&_restUpload ); 65 Foswiki::Func::registerRESTHandler( 'attachments', \&_restAttachments ); 46 # Note; rather than declaring the handlers in this module, we use 47 # the _execute function to hand off execution to 48 # Foswiki::Plugins::WysiwygPlugin::Handlers. The goal is to keep this 49 # module small and light so it loads fast. 50 Foswiki::Func::registerTagHandler( 51 'OWEB', sub { _execute('_OWEBTAG', @_) } ); 52 Foswiki::Func::registerTagHandler( 53 'OTOPIC', sub { _execute('_OTOPICTAG', @_) } ); 54 Foswiki::Func::registerTagHandler( 55 'WYSIWYG_TEXT', sub { _execute('_WYSIWYG_TEXT', @_) } ); 56 Foswiki::Func::registerTagHandler( 57 'JAVASCRIPT_TEXT', sub { _execute('_JAVASCRIPT_TEXT', @_) } ); 58 Foswiki::Func::registerTagHandler( 59 'WYSIWYG_SECRET_ID', sub { _execute('_SECRET_ID', @_) } ); 60 61 Foswiki::Func::registerRESTHandler( 62 'tml2html', sub { _execute('_restTML2HTML', @_) } ); 63 Foswiki::Func::registerRESTHandler( 64 'html2tml', sub { _execute('_restHTML2TML', @_) } ); 65 Foswiki::Func::registerRESTHandler( 66 'upload', sub { _execute('_restUpload', @_) } ); 67 Foswiki::Func::registerRESTHandler( 68 'attachments', sub { _execute('_restAttachments', @_) } ); 66 69 67 70 # Plugin correctly initialized … … 69 72 } 70 73 71 sub _OWEBTAG { 72 my ( $session, $params, $topic, $web ) = @_; 73 74 my $query = Foswiki::Func::getCgiQuery(); 75 76 return $web unless $query; 77 78 if ( defined( $query->param('templatetopic') ) ) { 79 my @split = split( /\./, $query->param('templatetopic') ); 80 81 if ( $#split == 0 ) { 82 return $web; 83 } 84 else { 85 return $split[0]; 86 } 87 } 88 89 return $web; 90 } 91 92 sub _OTOPICTAG { 93 my ( $session, $params, $topic, $web ) = @_; 94 95 my $query = Foswiki::Func::getCgiQuery(); 96 97 return $topic unless $query; 98 99 if ( defined( $query->param('templatetopic') ) ) { 100 my @split = split( /\./, $query->param('templatetopic') ); 101 102 return $split[$#split]; 103 } 104 105 return $topic; 106 } 107 108 $FoswikiCompatibility{startRenderingHandler} = 2.1; 109 sub startRenderingHandler { 110 $_[0] =~ s#</?sticky>##g; 111 } 112 113 # This handler is used to determine whether the topic is editable by 114 # a WYSIWYG editor or not. The only thing it does is to redirect to a 115 # normal edit url if the skin is set to WYSIWYGPLUGIN_WYSIWYGSKIN and 116 # nasty content is found. 117 sub beforeEditHandler { 118 119 #my( $text, $topic, $web, $meta ) = @_; 120 121 my $skin = Foswiki::Func::getPreferencesValue('WYSIWYGPLUGIN_WYSIWYGSKIN'); 122 123 if ( $skin && Foswiki::Func::getSkin() =~ /\b$skin\b/o ) { 124 if ( notWysiwygEditable( $_[0] ) ) { 125 126 # redirect 127 my $query = Foswiki::Func::getCgiQuery(); 128 foreach my $p qw( skin cover ) { 129 my $arg = $query->param($p); 130 if ( $arg && $arg =~ s/\b$skin\b// ) { 131 if ( $arg =~ /^[\s,]*$/ ) { 132 $query->delete($p); 133 } 134 else { 135 $query->param( -name => $p, -value => $arg ); 136 } 137 } 138 } 139 my $url = $query->url( -full => 1, -path => 1, -query => 1 ); 140 Foswiki::Func::redirectCgiQuery( $query, $url ); 141 142 # Bring this session to an untimely end 143 exit 0; 144 } 145 } 146 } 147 148 # This handler is only invoked *after* merging is complete 149 sub beforeSaveHandler { 150 151 #my( $text, $topic, $web ) = @_; 152 my $query = Foswiki::Func::getCgiQuery(); 153 return unless $query; 154 155 return unless defined( $query->param('wysiwyg_edit') ); 156 157 $_[0] = TranslateHTML2TML( $_[0], $_[1], $_[2] ); 158 } 159 160 # This handler is invoked before a merge. Merges are done before the 161 # afterEditHandler is called, so we need to translate here. 162 sub beforeMergeHandler { 163 164 #my( $text, $currRev, $currText, $origRev, $origText, $web, $topic ) = @_; 165 afterEditHandler( $_[0], $_[6], $_[5] ); 166 } 167 168 # This handler is invoked *after* a merge, and only from the edit 169 # script (so it's useless for a REST save) 170 sub afterEditHandler { 171 my ( $text, $topic, $web ) = @_; 172 my $query = Foswiki::Func::getCgiQuery(); 173 return unless $query; 174 175 if ( $Foswiki::cfg{Site}{CharSet} 176 && $Foswiki::cfg{Site}{CharSet} =~ /^utf-?8$/i ) 177 { 178 179 # If the site charset is utf-8, then form POSTs (such as the one 180 # that got us here) are utf-8 encoded. we have to decode to prevent 181 # the HTML parser from going tits up when it sees utf-8 in the data. 182 $text = Encode::decode_utf8($text); 183 } 184 185 return 186 unless defined( $query->param('wysiwyg_edit') ) 187 || $text =~ s/<!--$SECRET_ID-->//go; 188 189 # Switch off wysiwyg_edit so it doesn't try to transform again in 190 # the beforeSaveHandler 191 $query->delete('wysiwyg_edit'); 192 193 $text = TranslateHTML2TML( $text, $_[1], $_[2] ); 194 195 $_[0] = $text; 196 } 197 198 # Invoked to convert HTML to TML (best efforts) 199 sub TranslateHTML2TML { 200 my ( $text, $topic, $web ) = @_; 201 202 unless ($html2tml) { 203 require Foswiki::Plugins::WysiwygPlugin::HTML2TML; 204 205 $html2tml = new Foswiki::Plugins::WysiwygPlugin::HTML2TML(); 206 } 207 208 # SMELL: really, really bad smell; bloody core should NOT pass text 209 # with embedded meta to plugins! It is VERY BAD DESIGN!!! 210 my $top = ''; 211 if ( $text =~ s/^(%META:[A-Z]+{.*?}%\r?\n)//s ) { 212 $top = $1; 213 } 214 my $bottom = ''; 215 $text =~ s/^(%META:[A-Z]+{.*?}%\r?\n)/$bottom = "$1$bottom";''/gem; 216 217 my $opts = { 218 web => $web, 219 topic => $topic, 220 convertImage => \&_convertImage, 221 rewriteURL => \&postConvertURL, 222 very_clean => 1, # aggressively polish saved HTML 223 }; 224 225 $text = $html2tml->convert( $text, $opts ); 226 227 $text =~ s/\s+$/\n/s; 228 229 return $top . $text . $bottom; 230 } 231 232 # Handler used to process text in a =view= URL to generate text/html 233 # containing the HTML of the topic to be edited. 234 # 235 # Invoked when the selected skin is in use to convert the text to HTML 236 # We can't use the beforeEditHandler, because the editor loads up and then 237 # uses a URL to fetch the text to be edited. This handler is designed to 238 # provide the text for that request. It's a real struggle, because the 239 # commonTagsHandler is called so many times that getting the right 240 # call is hard, and then preventing a repeat call is harder! 241 sub beforeCommonTagsHandler { 242 243 #my ( $text, $topic, $web, $meta ) 244 return if $recursionBlock; 245 return unless Foswiki::Func::getContext()->{body_text}; 246 247 my $query = Foswiki::Func::getCgiQuery(); 248 249 return unless $query; 250 251 return unless defined( $query->param('wysiwyg_edit') ); 252 253 # stop it from processing the template without expanded 254 # %TEXT% (grr; we need a better way to tell where we 255 # are in the processing pipeline) 256 return if ( $_[0] =~ /^<!-- WysiwygPlugin Template/ ); 257 258 # Have to re-read the topic because verbatim blocks have already been 259 # lifted out, and we need them. 260 my $topic = $_[1]; 261 my $web = $_[2]; 262 my ( $meta, $text ); 263 my $altText = $query->param('templatetopic'); 264 if ( $altText && Foswiki::Func::topicExists( $web, $altText ) ) { 265 ( $web, $topic ) = 266 Foswiki::Func::normalizeWebTopicName( $web, $altText ); 267 } 268 269 $_[0] = _WYSIWYG_TEXT( $Foswiki::Plugins::SESSION, {}, $topic, $web ); 270 } 271 272 # Handler used by editors that require pre-prepared HTML embedded in the 273 # edit template. 274 sub _WYSIWYG_TEXT { 275 my ( $session, $params, $topic, $web ) = @_; 276 277 # Have to re-read the topic because content has already been munged 278 # by other plugins, or by the extraction of verbatim blocks. 279 my ( $meta, $text ) = Foswiki::Func::readTopic( $web, $topic ); 280 281 $text = TranslateTML2HTML( $text, $web, $topic ); 282 283 # Lift out the text to protect it from further Foswiki rendering. It will be 284 # put back in the postRenderingHandler. 285 return _liftOut($text); 286 } 287 288 # Handler used to present the editable text in a javascript constant string 289 sub _JAVASCRIPT_TEXT { 290 my ( $session, $params, $topic, $web ) = @_; 291 292 my $html = _dropBack( _WYSIWYG_TEXT(@_) ); 293 294 $html =~ s/([\\'])/\\$1/sg; 295 $html =~ s/\r/\\r/sg; 296 $html =~ s/\n/\\n/sg; 297 $html =~ s/script/scri'+'pt/g; 298 299 return _liftOut("'$html'"); 300 } 301 302 sub postRenderingHandler { 303 return if ( $recursionBlock || !$tml2html ); 304 305 # Replace protected content. 306 $_[0] = _dropBack( $_[0] ); 307 } 308 309 sub modifyHeaderHandler { 310 my ( $headers, $query ) = @_; 311 312 if ( $query->param('wysiwyg_edit') ) { 313 $headers->{Expires} = 0; 314 $headers->{'Cache-control'} = 'max-age=0, must-revalidate'; 315 } 316 } 317 318 # callback passed to the TML2HTML convertor 319 sub getViewUrl { 320 my ( $web, $topic ) = @_; 321 322 # the Cairo documentation says getViewUrl defaults the web. It doesn't. 323 unless ( defined $Foswiki::Plugins::SESSION ) { 324 $web ||= $Foswiki::webName; 325 } 326 327 return Foswiki::Func::getViewUrl( $web, $topic ); 328 } 329 330 # The subset of vars for which bidirection transformation is supported 331 # in URLs only 332 use vars qw( @VARS ); 333 334 # The set of macros that get "special treatment" in URLs 335 @VARS = ( 336 '%ATTACHURL%', 337 '%ATTACHURLPATH%', 338 '%PUBURL%', 339 '%PUBURLPATH%', 340 '%SCRIPTURLPATH{"view"}%', 341 '%SCRIPTURLPATH%', 342 '%SCRIPTURL{"view"}%', 343 '%SCRIPTURL%', 344 '%SCRIPTSUFFIX%', # bit dodgy, this one 345 ); 346 347 # Initialises the mapping from var to URL and back 348 sub _populateVars { 349 my $opts = shift; 350 351 return if ( $opts->{exp} ); 352 353 local $recursionBlock = 1; # block calls to beforeCommonTagshandler 354 355 my @exp = split( 356 /\0/, 357 Foswiki::Func::expandCommonVariables( 358 join( "\0", @VARS ), 359 $opts->{topic}, $opts->{web} 360 ) 361 ); 362 363 for my $i ( 0 .. $#VARS ) { 364 my $nvar = $VARS[$i]; 365 $opts->{match}[$i] = $nvar; 366 $exp[$i] ||= ''; 367 } 368 $opts->{exp} = \@exp; 369 } 370 371 # callback passed to the TML2HTML convertor on each 372 # variable in a URL used in a square bracketed link 373 sub expandVarsInURL { 374 my ( $url, $opts ) = @_; 375 376 return '' unless $url; 377 378 _populateVars($opts); 379 for my $i ( 0 .. $#VARS ) { 380 $url =~ s/$opts->{match}[$i]/$opts->{exp}->[$i]/g; 381 } 382 return $url; 383 } 384 385 # callback passed to the HTML2TML convertor 386 sub postConvertURL { 387 my ( $url, $opts ) = @_; 388 389 #my $orig = $url; #debug 390 391 local $recursionBlock = 1; # block calls to beforeCommonTagshandler 392 393 my $anchor = ''; 394 if ( $url =~ s/(#.*)$// ) { 395 $anchor = $1; 396 } 397 my $parameters = ''; 398 if ( $url =~ s/(\?.*)$// ) { 399 $parameters = $1; 400 } 401 402 _populateVars($opts); 403 404 for my $i ( 0 .. $#VARS ) { 405 next unless $opts->{exp}->[$i]; 406 $url =~ s/^$opts->{exp}->[$i]/$VARS[$i]/; 407 } 408 409 if ( $url =~ m#^%SCRIPTURL(?:PATH)?(?:{"view"}%|%/+view[^/]*)/+([/\w.]+)$# 410 && !$parameters ) 411 { 412 my $orig = $1; 413 my ( $web, $topic ) = 414 Foswiki::Func::normalizeWebTopicName( $opts->{web}, $orig ); 415 416 if ( $web && $web ne $opts->{web} ) { 417 418 #print STDERR "$orig -> $web+$topic$anchor\n"; #debug 419 return $web . '.' . $topic . $anchor; 420 } 421 422 #print STDERR "$orig -> $topic$anchor\n"; #debug 423 return $topic . $anchor; 424 } 425 426 #print STDERR "$orig -> $url$anchor$parameters\n"; #debug 427 return $url . $anchor . $parameters; 428 } 429 430 # Callback used to convert an image reference into a Foswiki variable. 431 sub _convertImage { 432 my ( $src, $opts ) = @_; 433 434 return unless $src; 435 436 local $recursionBlock = 1; # block calls to beforeCommonTagshandler 437 438 unless ($imgMap) { 439 $imgMap = {}; 440 my $imgs = Foswiki::Func::getPreferencesValue('WYSIWYGPLUGIN_ICONS'); 441 if ($imgs) { 442 while ( $imgs =~ s/src="(.*?)" alt="(.*?)"// ) { 443 my ( $src, $alt ) = ( $1, $2 ); 444 $src = 445 Foswiki::Func::expandCommonVariables( $src, $opts->{topic}, 446 $opts->{web} ); 447 $alt .= '%' if $alt =~ /^%/; 448 $imgMap->{$src} = $alt; 449 } 450 } 451 } 452 453 return $imgMap->{$src}; 454 } 455 456 # Replace content with a marker to prevent it being munged by Foswiki 457 sub _liftOut { 458 my ($text) = @_; 459 my $n = scalar(@refs); 460 push( @refs, $text ); 461 return "\05$n\05"; 462 } 463 464 # Substitute marker 465 sub _dropBack { 466 my ($text) = @_; 467 468 # Restore everything that was lifted out 469 while ( $text =~ s/\05([0-9]+)\05/$refs[$1]/gi ) { 470 } 471 return $text; 74 sub _execute { 75 my $fn = shift; 76 77 require Foswiki::Plugins::WysiwygPlugin::Handlers; 78 $fn = 'Foswiki::Plugins::WysiwygPlugin::Handlers::'.$fn; 79 no strict 'refs'; 80 return &$fn(@_); 81 use strict 'refs'; 472 82 } 473 83 … … 495 105 # purely content-related reasons for exclusion 496 106 if ($exclusions) { 497 my $calls_ok = Foswiki::Func::getPreferencesValue('WYSIWYG_EDITABLE_CALLS') 107 my $calls_ok = Foswiki::Func::getPreferencesValue( 108 'WYSIWYG_EDITABLE_CALLS') 498 109 || '---'; 499 110 $calls_ok =~ s/\s//g; … … 532 143 533 144 # Copy the content. 534 # Then crunch verbatim blocks, because verbatim blocks may contain *anything*. 145 # Then crunch verbatim blocks, because verbatim blocks may 146 # contain *anything*. 535 147 my $text = $_[0]; 536 148 537 # Look for combinations of sticky and other markup that cause problems together 149 # Look for combinations of sticky and other markup that cause 150 # problems together 538 151 for my $tag ('literal', keys %xmltag) { 539 152 while ($text =~ /<$tag\b[^>]*>(.*?)<\/$tag>/gsi) { … … 550 163 while ($text =~ s/<verbatim\b[^>]*>(.*?)<\/verbatim>/$wasAVerbatimTag/i) { 551 164 #my $content = $1; 552 # If there is any content that breaks conversion if it is inside a verbatim block, 553 # check for it here: 554 } 555 556 # Look for combinations of verbatim and other markup that cause problems together 165 # If there is any content that breaks conversion if it is inside 166 # a verbatim block, check for it here: 167 } 168 169 # Look for combinations of verbatim and other markup that cause 170 # problems together 557 171 for my $tag ('literal', keys %xmltag) { 558 172 while ($text =~ /<$tag\b[^>]*>(.*?)<\/$tag>/gsi) { … … 566 180 } 567 181 568 # Look for combinations of literal and other markup that cause problems together 182 # Look for combinations of literal and other markup that cause 183 # problems together 569 184 for my $tag (keys %xmltag) { 570 185 while ($text =~ /<$tag\b[^>]*>(.*?)<\/$tag>/gsi) { … … 578 193 } 579 194 580 # Check that the topic text can be converted to HTML 195 # Check that the topic text can be converted to HTML. This is an 196 # *expensive* process, to be avoided if possible (hence all the 197 # earlier checks) 581 198 eval { 582 TranslateTML2HTML( $_[0], 'Fakewebname', 'FakeTopicName', dieOnError => 1 ); 199 require Foswiki::Plugins::WysiwygPlugin::Handlers; 200 Foswiki::Plugins::WysiwygPlugin::Handlers::TranslateTML2HTML( 201 $_[0], 'Fakewebname', 'FakeTopicName', dieOnError => 1 ); 583 202 }; 584 203 if ($@) { … … 591 210 } 592 211 593 =pod 594 595 ---++ StaticMethod addXMLTag($tag, \&fn) 596 597 Instruct WysiwygPlugin to "lift out" the named tag 598 and pass it to &fn for processing. 599 &fn may modify the text of the tag. 600 &fn should return 0 if the tag is to be re-embedded immediately, 601 or 1 if it is to be re-embedded after all processing is complete. 602 The text passed (by reference) to &fn includes the 603 =<tag> ... </tag>= brackets. 604 605 The simplest use of this function is something like this: 606 =Foswiki::Plugins::WysiwygPlugin::addXMLTag( 'mytag', sub { 1 } );= 607 608 A plugin may call this function more than once 609 e.g. to change the processing function for a tag. 610 However, only the *original plugin* may change the processing 611 for a tag. 612 613 Plugins should call this function from their =initPlugin= 614 handlers so that WysiwygPlugin will protect the XML-like tags 615 for all conversions, including REST conversions. 616 Plugins that are intended to be used with older versions of Foswiki 617 (e.g. 1.0.6) should check that this function is defined before calling it, 618 so that they degrade gracefully if an older version of WysiwygPlugin 619 (e.g. that shipped with 1.0.6) is installed. 620 621 =cut 622 623 sub addXMLTag { 624 my ( $tag, $fn ) = @_; 625 626 my $plugin = caller; 627 $plugin =~ s/^Foswiki::Plugins:://; 628 629 return if not defined $tag; 630 631 if ( ( not exists $xmltag{$tag} and not exists $xmltagPlugin{$tag} ) 632 or ( $xmltagPlugin{$tag} eq $plugin ) ) 633 { 634 635 # This is either a plugin adding a new tag 636 # or a plugin adding a tag it had previously added before. 637 # A plugin is allowed to add a tag that it had added before 638 # and the new function replaces the old. 639 # 640 $fn = sub { 1 } 641 unless $fn; # Default function 642 643 $xmltag{$tag} = $fn; 644 $xmltagPlugin{$tag} = $plugin; 645 } 646 else { 647 648 # DON'T replace the existing processing for this tag 649 printf STDERR "WysiwygPlugin::addXMLTag: " 650 . "$plugin cannot add XML tag $tag, " 651 . "that tag was already registered by $xmltagPlugin{$tag}\n"; 652 } 653 } 654 655 sub TranslateTML2HTML { 656 my ( $text, $web, $topic, @extraConvertOptions ) = @_; 657 658 # Translate the topic text to pure HTML. 659 unless ($tml2html) { 660 require Foswiki::Plugins::WysiwygPlugin::TML2HTML; 661 $tml2html = new Foswiki::Plugins::WysiwygPlugin::TML2HTML(); 662 } 663 return $tml2html->convert( 664 $_[0], 665 { 666 web => $web, 667 topic => $topic, 668 getViewUrl => \&getViewUrl, 669 expandVarsInURL => \&expandVarsInURL, 670 xmltag => \%xmltag, 671 @extraConvertOptions, 672 } 673 ); 674 } 675 676 # PACKAGE PRIVATE 677 # Determine if sticky attributes prevent a tag being converted to 678 # TML when this attribute is present. 679 my @protectedByAttr; 680 681 sub protectedByAttr { 682 my ( $tag, $attr ) = @_; 683 684 unless ( scalar(@protectedByAttr) ) { 685 686 # See the WysiwygPluginSettings for information on stickybits 687 my $protection = 688 Foswiki::Func::getPreferencesValue('WYSIWYGPLUGIN_STICKYBITS') 689 || <<'DEFAULT'; 690 .*=id,lang,title,dir,on.*; 691 a=accesskey,coords,shape,target; 692 bdo=dir; 693 br=clear; 694 col=char,charoff,span,valign,width; 695 colgroup=align,char,charoff,span,valign,width; 696 dir=compact; 697 div=align,style; 698 dl=compact; 699 font=size,face; 700 h\d=align; 701 hr=align,noshade,size,width; 702 legend=accesskey,align; 703 li=value; 704 ol=compact,start,type; 705 p=align; 706 param=name,type,value,valuetype; 707 pre=width; 708 q=cite; 709 table=align,bgcolor,frame,rules,summary,width; 710 tbody=align,char,charoff,valign; 711 td=abbr,align,axis,bgcolor,char,charoff,headers,height,nowrap,rowspan,scope,valign,width; 712 tfoot=align,char,charoff,valign; 713 th=abbr,align,axis,bgcolor,char,charoff,height,nowrap,rowspan,scope,valign,width,headers; 714 thead=align,char,charoff,valign; 715 tr=bgcolor,char,charoff,valign; 716 ul=compact,type; 717 DEFAULT 718 foreach my $def ( split( /;\s*/s, $protection ) ) { 719 my ( $re, $ats ) = split( /\s*=\s*/s, $def, 2 ); 720 push( 721 @protectedByAttr, 722 { 723 tag => qr/$re/i, 724 attrs => join( '|', split( /\s*,\s*/, $ats ) ) 725 } 726 ); 727 } 728 } 729 foreach my $row (@protectedByAttr) { 730 if ( $tag =~ /^$row->{tag}$/i ) { 731 if ( $attr =~ /^($row->{attrs})$/i ) { 732 #print STDERR "Protecting $tag with $attr matches $row->{attrs} \n"; #debug 733 return 1; 734 } 735 } 736 } 737 return 0; 738 } 739 740 # Text that is taken from a web page and added to the parameters of an XHR 741 # by JavaScript is UTF-8 encoded. This is because UTF-8 is the default encoding 742 # for XML, which XHR was designed to transport. 743 744 # This function is used to decode such parameters to the currently selected 745 # Foswiki site character set. 746 747 # Note that this transform is not as simple as an Encode::from_to, as 748 # a number of unicode code points must be remapped for certain encodings. 749 sub RESTParameter2SiteCharSet { 750 my ($text) = @_; 751 752 $text = Encode::decode_utf8( $text, Encode::FB_PERLQQ ); 753 754 WC::mapUnicode2HighBit($text); 755 756 if ( $Foswiki::cfg{Site}{CharSet} ) { 757 $text = Encode::encode( $Foswiki::cfg{Site}{CharSet}, 758 $text, Encode::FB_PERLQQ ); 759 } 760 761 return $text; 762 } 763 764 # Text that is taken from a web page and added to the parameters of an XHR 765 # by JavaScript is UTF-8 encoded. This is because UTF-8 is the default encoding 766 # for XML, which XHR was designed to transport. For usefulness in Javascript 767 # the response to an XHR should also be UTF-8 encoded. 768 # This function generates such a response. 769 sub returnRESTResult { 770 my ( $response, $status, $text ) = @_; 771 772 if ( $Foswiki::cfg{Site}{CharSet} ) { 773 $text = Encode::decode( $Foswiki::cfg{Site}{CharSet}, 774 $text, Encode::FB_PERLQQ ); 775 } 776 777 WC::mapHighBit2Unicode($text); 778 779 $text = Encode::encode_utf8($text); 780 781 # Foswiki 1.0 introduces the Foswiki::Response object, which handles all 782 # responses. 783 if ( UNIVERSAL::isa( $response, 'Foswiki::Response' ) ) { 784 $response->header( 785 -status => $status, 786 -type => 'text/plain', 787 -charset => 'UTF-8' 788 ); 789 $response->print($text); 790 } 791 else { # Pre-Foswiki-1.0. 792 # Turn off AUTOFLUSH 793 # See http://perl.apache.org/docs/2.0/user/coding/coding.html 794 local $| = 0; 795 my $query = Foswiki::Func::getCgiQuery(); 796 if ( defined($query) ) { 797 my $len; 798 { use bytes; $len = length($text); }; 799 print $query->header( 800 -status => $status, 801 -type => 'text/plain', 802 -charset => 'UTF-8', 803 -Content_length => $len 804 ); 805 print $text; 806 } 807 } 808 print STDERR $text if ( $status >= 400 ); 809 } 810 811 # Rest handler for use from Javascript. The 'text' parameter is used to 812 # pass the text for conversion. The text must be URI-encoded (this is 813 # to support use of this handler from XMLHttpRequest, which gets it 814 # wrong). Example: 815 # 816 # var req = new XMLHttpRequest(); 817 # req.open("POST", url, true); 818 # req.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 819 # var params = "text=" + encodeURIComponent(escape(text)); 820 # request.req.setRequestHeader("Content-length", params.length); 821 # request.req.setRequestHeader("Connection", "close"); 822 # request.req.onreadystatechange = ...; 823 # req.send(params); 824 # 825 sub _restTML2HTML { 826 my ( $session, $plugin, $verb, $response ) = @_; 827 my $tml = Foswiki::Func::getCgiQuery()->param('text'); 828 829 $tml = RESTParameter2SiteCharSet($tml); 830 831 # if the secret ID is present, don't convert again. We are probably 832 # going 'back' to this page (doesn't work on IE :-( ) 833 if ( $tml =~ /<!--$SECRET_ID-->/ ) { 834 return $tml; 835 } 836 837 my $html = 838 TranslateTML2HTML( $tml, $session->{webName}, $session->{topicName} ); 839 840 # Add the secret id to trigger reconversion. Doesn't work if the 841 # editor eats HTML comments, so the editor may need to put it back 842 # in during final cleanup. 843 $html = '<!--' . $SECRET_ID . '-->' . $html; 844 845 returnRESTResult( $response, 200, $html ); 846 847 return; # to prevent further processing 848 } 849 850 # Rest handler for use from Javascript 851 sub _restHTML2TML { 852 my ( $session, $plugin, $verb, $response ) = @_; 853 unless ($html2tml) { 854 require Foswiki::Plugins::WysiwygPlugin::HTML2TML; 855 856 $html2tml = new Foswiki::Plugins::WysiwygPlugin::HTML2TML(); 857 } 858 my $html = Foswiki::Func::getCgiQuery()->param('text'); 859 860 $html = RESTParameter2SiteCharSet($html); 861 862 $html =~ s/<!--$SECRET_ID-->//go; 863 my $tml = $html2tml->convert( 864 $html, 865 { 866 web => $session->{webName}, 867 topic => $session->{topicName}, 868 getViewUrl => \&getViewUrl, 869 expandVarsInURL => \&expandVarsInURL, 870 very_clean => 1, 871 } 872 ); 873 874 returnRESTResult( $response, 200, $tml ); 875 return; # to prevent further processing 876 } 877 878 # SMELL: foswiki supports proper REST usage of the upload script, 879 # so debatable if this is required any more 880 sub _restUpload { 881 my ( $session, $plugin, $verb, $response ) = @_; 882 my $query = Foswiki::Func::getCgiQuery(); 883 884 # Item1458 ignore uploads not using POST 885 if ( $query && $query->method() && uc( $query->method() ) ne 'POST' ) { 886 returnRESTResult( $response, 405, "Method not Allowed" ); 887 return; 888 } 889 my ( $web, $topic ) = 890 Foswiki::Func::normalizeWebTopicName( undef, $query->param('topic') ); 891 $web = 892 Foswiki::Sandbox::untaint( $web, \&Foswiki::Sandbox::validateWebName ); 893 $topic = Foswiki::Sandbox::untaint( $topic, 894 \&Foswiki::Sandbox::validateTopicName ); 895 unless ( defined $web && defined $topic ) { 896 returnRESTResult( $response, 401, "Access denied" ); 897 return; # to prevent further processing 898 } 899 my $hideFile = $query->param('hidefile') || ''; 900 my $fileComment = $query->param('filecomment') || ''; 901 my $createLink = $query->param('createlink') || ''; 902 my $doPropsOnly = $query->param('changeproperties'); 903 my $filePath = $query->param('filepath') || ''; 904 my $fileName = $query->param('filename') || ''; 905 if ( $filePath && !$fileName ) { 906 $filePath =~ m|([^/\\]*$)|; 907 $fileName = $1; 908 } 909 $fileComment =~ s/\s+/ /go; 910 $fileComment =~ s/^\s*//o; 911 $fileComment =~ s/\s*$//o; 912 $fileName =~ s/\s*$//o; 913 $filePath =~ s/\s*$//o; 914 915 unless ( 916 Foswiki::Func::checkAccessPermission( 917 'CHANGE', Foswiki::Func::getWikiName(), 918 undef, $topic, $web 919 ) 920 ) 921 { 922 returnRESTResult( $response, 401, "Access denied" ); 923 return; # to prevent further processing 924 } 925 926 my ( $fileSize, $fileDate, $tmpFileName ); 927 928 my $stream = $query->upload('filepath') unless $doPropsOnly; 929 my $origName = $fileName; 930 931 unless ($doPropsOnly) { 932 933 # SMELL: call to unpublished function 934 ( $fileName, $origName ) = 935 Foswiki::Sandbox::sanitizeAttachmentName($fileName); 936 937 # check if upload has non zero size 938 if ($stream) { 939 my @stats = stat $stream; 940 $fileSize = $stats[7]; 941 $fileDate = $stats[9]; 942 } 943 944 unless ( $fileSize && $fileName ) { 945 returnRESTResult( $response, 500, "Zero-sized file upload" ); 946 return; # to prevent further processing 947 } 948 949 my $maxSize = Foswiki::Func::getPreferencesValue('ATTACHFILESIZELIMIT'); 950 $maxSize = 0 unless ( $maxSize =~ /([0-9]+)/o ); 951 952 if ( $maxSize && $fileSize > $maxSize * 1024 ) { 953 returnRESTResult( $response, 500, "OVERSIZED UPLOAD" ); 954 return; # to prevent further processing 955 } 956 } 957 958 # SMELL: use of undocumented CGI::tmpFileName 959 my $tfp = $query->tmpFileName( $query->param('filepath') ); 960 my $error = Foswiki::Func::saveAttachment( 961 $web, $topic, 962 $fileName, 963 { 964 dontlog => !$Foswiki::cfg{Log}{upload}, 965 comment => $fileComment, 966 hide => $hideFile, 967 createlink => $createLink, 968 stream => $stream, 969 filepath => $filePath, 970 filesize => $fileSize, 971 filedate => $fileDate, 972 tmpFilename => $tfp, 973 } 974 ); 975 976 close($stream) if $stream; 977 978 if ($error) { 979 returnRESTResult( $response, 500, $error ); 980 return; # to prevent further processing 981 } 982 983 # Otherwise allow the rest dispatcher to write a 200 984 return "$origName attached to $web.$topic" 985 . ( $origName ne $fileName ? " as $fileName" : '' ); 986 } 987 988 sub _unquote { 989 my $text = shift; 990 $text =~ s/\\/\\\\/g; 991 $text =~ s/\n/\\n/g; 992 $text =~ s/\r/\\r/g; 993 $text =~ s/\t/\\t/g; 994 $text =~ s/"/\\"/g; 995 $text =~ s/'/\\'/g; 996 return $text; 997 } 998 999 # Get, and return, a list of attachments using JSON 1000 sub _restAttachments { 1001 my ( $session, $plugin, $verb, $response ) = @_; 1002 my ( $web, $topic ) = 1003 Foswiki::Func::normalizeWebTopicName( undef, 1004 Foswiki::Func::getCgiQuery()->param('topic') ); 1005 $web = 1006 Foswiki::Sandbox::untaint( $web, \&Foswiki::Sandbox::validateWebName ); 1007 $topic = Foswiki::Sandbox::untaint( $topic, 1008 \&Foswiki::Sandbox::validateTopicName ); 1009 my ( $meta, $text ) = Foswiki::Func::readTopic( $web, $topic ); 1010 unless ( 1011 Foswiki::Func::checkAccessPermission( 1012 'VIEW', Foswiki::Func::getWikiName(), 1013 $text, $topic, $web, $meta 1014 ) 1015 ) 1016 { 1017 returnRESTResult( $response, 401, "Access denied" ); 1018 return; # to prevent further processing 1019 } 1020 1021 # Create a JSON list of attachment data, sorted by name 1022 my @atts; 1023 foreach my $att ( sort { $a->{name} cmp $b->{name} } 1024 $meta->find('FILEATTACHMENT') ) 1025 { 1026 push( 1027 @atts, 1028 '{' . join( 1029 ',', 1030 map { 1031 '"' 1032 . _unquote($_) . '":"' 1033 . _unquote( $att->{$_} ) . '"' 1034 } keys %$att 1035 ) 1036 . '}' 1037 ); 1038 1039 } 1040 return '[' . join( ',', @atts ) . ']'; 212 sub postConvertURL { 213 require Foswiki::Plugins::WysiwygPlugin::Handlers; 214 Foswiki::Plugins::WysiwygPlugin::Handlers::postConvertURL(@_); 1041 215 } 1042 216 1043 217 1; 1044 218 __DATA__ 1045 #Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/1046 # 1047 # Copyright (C) 2008Foswiki Contributors. Foswiki Contributors1048 #are listed in the AUTHORS file in the root of this distribution.1049 #NOTE: Please extend that file, not this notice.1050 # 1051 #Additional copyrights apply to some or all of the code in this file:1052 # 1053 #Copyright (C) 2005 ILOG http://www.ilog.fr1054 #and TWiki Contributors. All Rights Reserved. TWiki Contributors1055 #are listed in the AUTHORS file in the root of your Foswiki (or TWiki)1056 #distribution.1057 # 1058 #This program is free software; you can redistribute it and/or1059 #modify it under the terms of the GNU General Public License1060 #as published by the Free Software Foundation; either version 21061 #of the License, or (at your option) any later version. For1062 #more details read LICENSE in the root of the TWiki distribution.1063 # 1064 #This program is distributed in the hope that it will be useful,1065 #but WITHOUT ANY WARRANTY; without even the implied warranty of1066 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.1067 # 1068 #As per the GPL, removal of this notice is prohibited.219 Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/ 220 221 Copyright (C) 2008-2009 Foswiki Contributors. Foswiki Contributors 222 are listed in the AUTHORS file in the root of this distribution. 223 NOTE: Please extend that file, not this notice. 224 225 Additional copyrights apply to some or all of the code in this file: 226 227 Copyright (C) 2005 ILOG http://www.ilog.fr 228 and TWiki Contributors. All Rights Reserved. TWiki Contributors 229 are listed in the AUTHORS file in the root of your Foswiki (or TWiki) 230 distribution. 231 232 This program is free software; you can redistribute it and/or 233 modify it under the terms of the GNU General Public License 234 as published by the Free Software Foundation; either version 2 235 of the License, or (at your option) any later version. For 236 more details read LICENSE in the root of the TWiki distribution. 237 238 This program is distributed in the hope that it will be useful, 239 but WITHOUT ANY WARRANTY; without even the implied warranty of 240 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 241 242 As per the GPL, removal of this notice is prohibited. -
trunk/WysiwygPlugin/lib/Foswiki/Plugins/WysiwygPlugin/HTML2TML/Node.pm
r5573 r5816 656 656 next unless length( $this->{attrs}->{$attr} ); # ignore nulls 657 657 return $attr 658 if Foswiki::Plugins::WysiwygPlugin:: protectedByAttr( $this->{tag},658 if Foswiki::Plugins::WysiwygPlugin::Handlers::protectedByAttr( $this->{tag}, 659 659 $attr ); 660 660 } -
trunk/WysiwygPlugin/test/unit/WysiwygPlugin/ExtendedTranslatorTests.pm
r5573 r5816 125 125 $extraTML2HTMLOptions{xmltag} = 126 126 \%Foswiki::Plugins::WysiwygPlugin::xmltag; 127 Foswiki::Plugins::WysiwygPlugin:: addXMLTag( 'customtag',127 Foswiki::Plugins::WysiwygPlugin::Handlers::addXMLTag( 'customtag', 128 128 sub { 0 } ); 129 129 }, … … 145 145 $extraTML2HTMLOptions{xmltag} = 146 146 \%Foswiki::Plugins::WysiwygPlugin::xmltag; 147 Foswiki::Plugins::WysiwygPlugin:: addXMLTag( 'customtag',147 Foswiki::Plugins::WysiwygPlugin::Handlers::addXMLTag( 'customtag', 148 148 sub { 1 } ); 149 149 }, … … 160 160 $extraTML2HTMLOptions{xmltag} = 161 161 \%Foswiki::Plugins::WysiwygPlugin::xmltag; 162 Foswiki::Plugins::WysiwygPlugin:: addXMLTag( 'customtag',162 Foswiki::Plugins::WysiwygPlugin::Handlers::addXMLTag( 'customtag', 163 163 sub { $_[0] =~ s/some/different/; return 1; } ); 164 164 }, … … 176 176 $extraTML2HTMLOptions{xmltag} = 177 177 \%Foswiki::Plugins::WysiwygPlugin::xmltag; 178 Foswiki::Plugins::WysiwygPlugin:: addXMLTag('customtag');178 Foswiki::Plugins::WysiwygPlugin::Handlers::addXMLTag('customtag'); 179 179 }, 180 180 html => '<p>' … … 190 190 $extraTML2HTMLOptions{xmltag} = 191 191 \%Foswiki::Plugins::WysiwygPlugin::xmltag; 192 Foswiki::Plugins::WysiwygPlugin:: addXMLTag( 'customtag',192 Foswiki::Plugins::WysiwygPlugin::Handlers::addXMLTag( 'customtag', 193 193 sub { 1 } ); 194 194 }, … … 209 209 $extraTML2HTMLOptions{xmltag} = 210 210 \%Foswiki::Plugins::WysiwygPlugin::xmltag; 211 Foswiki::Plugins::WysiwygPlugin:: addXMLTag( 'customtag',211 Foswiki::Plugins::WysiwygPlugin::Handlers::addXMLTag( 'customtag', 212 212 sub { 1 } ); 213 213 }, … … 243 243 $extraTML2HTMLOptions{xmltag} = 244 244 \%Foswiki::Plugins::WysiwygPlugin::xmltag; 245 Foswiki::Plugins::WysiwygPlugin:: addXMLTag( 'dot', sub { 1 } );245 Foswiki::Plugins::WysiwygPlugin::Handlers::addXMLTag( 'dot', sub { 1 } ); 246 246 }, 247 247 tml => <<'DOT', … … 262 262 $extraTML2HTMLOptions{xmltag} = 263 263 \%Foswiki::Plugins::WysiwygPlugin::xmltag; 264 Foswiki::Plugins::WysiwygPlugin:: addXMLTag( 'customtag',264 Foswiki::Plugins::WysiwygPlugin::Handlers::addXMLTag( 'customtag', 265 265 sub { 1 } ); 266 266 }, … … 280 280 $extraTML2HTMLOptions{xmltag} = 281 281 \%Foswiki::Plugins::WysiwygPlugin::xmltag; 282 Foswiki::Plugins::WysiwygPlugin:: addXMLTag( 'customtag',282 Foswiki::Plugins::WysiwygPlugin::Handlers::addXMLTag( 'customtag', 283 283 sub { 1 } ); 284 284 }, … … 332 332 $extraTML2HTMLOptions{xmltag} = 333 333 \%Foswiki::Plugins::WysiwygPlugin::xmltag; 334 Foswiki::Plugins::WysiwygPlugin:: addXMLTag( 'customtag',334 Foswiki::Plugins::WysiwygPlugin::Handlers::addXMLTag( 'customtag', 335 335 sub { 1 } ); 336 336 }, … … 362 362 $extraTML2HTMLOptions{xmltag} = 363 363 \%Foswiki::Plugins::WysiwygPlugin::xmltag; 364 Foswiki::Plugins::WysiwygPlugin:: addXMLTag( 'customtag',364 Foswiki::Plugins::WysiwygPlugin::Handlers::addXMLTag( 'customtag', 365 365 sub { 1 } ); 366 366 }, -
trunk/WysiwygPlugin/test/unit/WysiwygPlugin/WysiwygPluginTests.pm
r5392 r5816 25 25 use Foswiki; 26 26 use Foswiki::Plugins::WysiwygPlugin; 27 27 use Foswiki::Plugins::WysiwygPlugin::Handlers; 28 use Encode(); 28 29 use strict; 29 30 use Carp; … … 150 151 save => sub { 151 152 my $ok = 152 Foswiki::Plugins::WysiwygPlugin:: _restTML2HTML( $foswiki, undef,153 Foswiki::Plugins::WysiwygPlugin::Handlers::_restTML2HTML( $foswiki, undef, 153 154 undef, $foswiki->{response} ); 154 155 $Foswiki::engine->finalize( $foswiki->{response}, … … 167 168 $out = Encode::decode_utf8($out); 168 169 169 my $id = "<!--$Foswiki::Plugins::WysiwygPlugin:: SECRET_ID-->";170 my $id = "<!--$Foswiki::Plugins::WysiwygPlugin::Handlers::SECRET_ID-->"; 170 171 $this->assert( $out =~ s/^\s*$id<p>\s*//s, anal($out) ); 171 172 $out =~ s/\s*<\/p>\s*$//s; … … 205 206 save => sub { 206 207 my $ok = 207 Foswiki::Plugins::WysiwygPlugin:: _restHTML2TML( $foswiki, undef,208 Foswiki::Plugins::WysiwygPlugin::Handlers::_restHTML2TML( $foswiki, undef, 208 209 undef, $foswiki->{response} ); 209 210 $Foswiki::engine->finalize( $foswiki->{response},
Note: See TracChangeset
for help on using the changeset viewer.
