| 1 | # See bottom of file for license and copyright information |
|---|
| 2 | |
|---|
| 3 | =begin TML |
|---|
| 4 | |
|---|
| 5 | ---+ package WysiwygPlugin |
|---|
| 6 | |
|---|
| 7 | This plugin is responsible for translating TML to HTML before an edit starts |
|---|
| 8 | and translating the resultant HTML back into TML. |
|---|
| 9 | |
|---|
| 10 | Note: In the case of a new topic, you might expect to see the "create topic" |
|---|
| 11 | screen in the editor when it goes back to Foswiki for the topic content. This |
|---|
| 12 | doesn't happen because the earliest possible handler is called on the topic |
|---|
| 13 | content and not the template. The template is effectively ignored and a blank |
|---|
| 14 | document is sent to the editor. |
|---|
| 15 | |
|---|
| 16 | Attachment uploads can be handled by URL requests from the editor to the rest |
|---|
| 17 | handler in this plugin. This avoids the need to add any scripts to the bin dir. |
|---|
| 18 | You will have to use a form, though, as XmlHttpRequest does not support file |
|---|
| 19 | uploads. |
|---|
| 20 | |
|---|
| 21 | =cut |
|---|
| 22 | |
|---|
| 23 | package Foswiki::Plugins::WysiwygPlugin; |
|---|
| 24 | |
|---|
| 25 | use strict; |
|---|
| 26 | use warnings; |
|---|
| 27 | |
|---|
| 28 | use Assert; |
|---|
| 29 | |
|---|
| 30 | our $SHORTDESCRIPTION = 'Translator framework for WYSIWYG editors'; |
|---|
| 31 | our $NO_PREFS_IN_TOPIC = 1; |
|---|
| 32 | our $VERSION = '$Rev$'; |
|---|
| 33 | |
|---|
| 34 | our $RELEASE = '1.1.5'; |
|---|
| 35 | |
|---|
| 36 | our %xmltag; |
|---|
| 37 | |
|---|
| 38 | # The following are all used in Handlers, but declared here so we can |
|---|
| 39 | # check them without loading the handlers module |
|---|
| 40 | our $tml2html; |
|---|
| 41 | our $recursionBlock; |
|---|
| 42 | our %FoswikiCompatibility; |
|---|
| 43 | |
|---|
| 44 | # Set to 1 for reasons for rejection |
|---|
| 45 | sub WHY { 0 } |
|---|
| 46 | |
|---|
| 47 | sub initPlugin { |
|---|
| 48 | my ( $topic, $web, $user, $installWeb ) = @_; |
|---|
| 49 | |
|---|
| 50 | # %OWEB%.%OTOPIC% is the topic where the initial content should be |
|---|
| 51 | # grabbed from, as defined in templates/edit.skin.tmpl |
|---|
| 52 | # Note; rather than declaring the handlers in this module, we use |
|---|
| 53 | # the _execute function to hand off execution to |
|---|
| 54 | # Foswiki::Plugins::WysiwygPlugin::Handlers. The goal is to keep this |
|---|
| 55 | # module small and light so it loads fast. |
|---|
| 56 | Foswiki::Func::registerTagHandler( 'OWEB', |
|---|
| 57 | sub { _execute( '_OWEBTAG', @_ ) } ); |
|---|
| 58 | Foswiki::Func::registerTagHandler( 'OTOPIC', |
|---|
| 59 | sub { _execute( '_OTOPICTAG', @_ ) } ); |
|---|
| 60 | Foswiki::Func::registerTagHandler( 'WYSIWYG_TEXT', |
|---|
| 61 | sub { _execute( '_WYSIWYG_TEXT', @_ ) } ); |
|---|
| 62 | Foswiki::Func::registerTagHandler( 'JAVASCRIPT_TEXT', |
|---|
| 63 | sub { _execute( '_JAVASCRIPT_TEXT', @_ ) } ); |
|---|
| 64 | Foswiki::Func::registerTagHandler( 'WYSIWYG_SECRET_ID', |
|---|
| 65 | sub { _execute( '_SECRET_ID', @_ ) } ); |
|---|
| 66 | |
|---|
| 67 | Foswiki::Func::registerRESTHandler( 'tml2html', |
|---|
| 68 | sub { _execute( '_restTML2HTML', @_ ) } ); |
|---|
| 69 | Foswiki::Func::registerRESTHandler( 'html2tml', |
|---|
| 70 | sub { _execute( '_restHTML2TML', @_ ) } ); |
|---|
| 71 | Foswiki::Func::registerRESTHandler( 'upload', |
|---|
| 72 | sub { _execute( '_restUpload', @_ ) } ); |
|---|
| 73 | Foswiki::Func::registerRESTHandler( 'attachments', |
|---|
| 74 | sub { _execute( '_restAttachments', @_ ) } ); |
|---|
| 75 | |
|---|
| 76 | # Plugin correctly initialized |
|---|
| 77 | return 1; |
|---|
| 78 | } |
|---|
| 79 | |
|---|
| 80 | sub _execute { |
|---|
| 81 | my $fn = shift; |
|---|
| 82 | |
|---|
| 83 | require Foswiki::Plugins::WysiwygPlugin::Handlers; |
|---|
| 84 | $fn = 'Foswiki::Plugins::WysiwygPlugin::Handlers::' . $fn; |
|---|
| 85 | no strict 'refs'; |
|---|
| 86 | return &$fn(@_); |
|---|
| 87 | use strict 'refs'; |
|---|
| 88 | } |
|---|
| 89 | |
|---|
| 90 | =begin TML |
|---|
| 91 | |
|---|
| 92 | ---++ StaticMethod notWysiwygEditable($text) -> $boolean |
|---|
| 93 | Determine if the given =$text= is WYSIWYG editable, based on the topic content |
|---|
| 94 | and the value of the Foswiki preferences WYSIWYG_EXCLUDE and |
|---|
| 95 | WYSIWYG_EDITABLE_CALLS. Returns a descriptive string if the text is not |
|---|
| 96 | editable, 0 otherwise. |
|---|
| 97 | |
|---|
| 98 | =cut |
|---|
| 99 | |
|---|
| 100 | sub notWysiwygEditable { |
|---|
| 101 | |
|---|
| 102 | #my ($text, $exclusions) = @_; |
|---|
| 103 | my $disabled = wysiwygEditingDisabledForThisContent( $_[0], $_[1] ); |
|---|
| 104 | return $disabled if $disabled; |
|---|
| 105 | |
|---|
| 106 | # Check that the topic text can be converted to HTML. This is an |
|---|
| 107 | # *expensive* process, to be avoided if possible (hence all the |
|---|
| 108 | # earlier checks) |
|---|
| 109 | my $impossible = wysiwygEditingNotPossibleForThisContent( $_[0] ); |
|---|
| 110 | return $impossible if $impossible; |
|---|
| 111 | |
|---|
| 112 | return 0; |
|---|
| 113 | } |
|---|
| 114 | |
|---|
| 115 | sub wysiwygEditingDisabledForThisContent { |
|---|
| 116 | |
|---|
| 117 | #my ($text, $exclusions) = @_; |
|---|
| 118 | |
|---|
| 119 | my $exclusions = $_[1]; |
|---|
| 120 | unless ( defined($exclusions) ) { |
|---|
| 121 | $exclusions = Foswiki::Func::getPreferencesValue('WYSIWYG_EXCLUDE') |
|---|
| 122 | || 'script,style,table'; |
|---|
| 123 | } |
|---|
| 124 | |
|---|
| 125 | # Check for explicit exclusions before generic, non-configurable |
|---|
| 126 | # purely content-related reasons for exclusion |
|---|
| 127 | if ($exclusions) { |
|---|
| 128 | my $calls_ok = |
|---|
| 129 | Foswiki::Func::getPreferencesValue('WYSIWYG_EDITABLE_CALLS') |
|---|
| 130 | || '---'; |
|---|
| 131 | $calls_ok =~ s/\s//g; |
|---|
| 132 | |
|---|
| 133 | my $ok = 1; |
|---|
| 134 | if ( $exclusions =~ /calls/ |
|---|
| 135 | && $_[0] =~ /%((?!($calls_ok){)[A-Z_]+{.*?})%/s ) |
|---|
| 136 | { |
|---|
| 137 | print STDERR "WYSIWYG_DEBUG: has calls $1 (not in $calls_ok)\n" |
|---|
| 138 | if (WHY); |
|---|
| 139 | return "Text contains calls"; |
|---|
| 140 | } |
|---|
| 141 | if ( $exclusions =~ /(macros|variables)/ && $_[0] =~ /%([A-Z_]+)%/s ) { |
|---|
| 142 | print STDERR "$exclusions WYSIWYG_DEBUG: has macros $1\n" |
|---|
| 143 | if (WHY); |
|---|
| 144 | return "Text contains macros"; |
|---|
| 145 | } |
|---|
| 146 | if ( $exclusions =~ /html/ |
|---|
| 147 | && $_[0] =~ /<\/?((?!literal|verbatim|noautolink|nop|br)\w+)/i ) |
|---|
| 148 | { |
|---|
| 149 | print STDERR "WYSIWYG_DEBUG: has html: $1\n" |
|---|
| 150 | if (WHY); |
|---|
| 151 | return "Text contains HTML"; |
|---|
| 152 | } |
|---|
| 153 | if ( $exclusions =~ /comments/ && $_[0] =~ /<[!]--/ ) { |
|---|
| 154 | print STDERR "WYSIWYG_DEBUG: has comments\n" |
|---|
| 155 | if (WHY); |
|---|
| 156 | return "Text contains comments"; |
|---|
| 157 | } |
|---|
| 158 | if ( $exclusions =~ /pre/ && $_[0] =~ /<pre\w/i ) { |
|---|
| 159 | print STDERR "WYSIWYG_DEBUG: has pre\n" |
|---|
| 160 | if (WHY); |
|---|
| 161 | return "Text contains PRE"; |
|---|
| 162 | } |
|---|
| 163 | if ( $exclusions =~ /script/ && $_[0] =~ /<script\W/i ) { |
|---|
| 164 | print STDERR "WYSIWYG_DEBUG: has script\n" |
|---|
| 165 | if (WHY); |
|---|
| 166 | return "Text contains script"; |
|---|
| 167 | } |
|---|
| 168 | if ( $exclusions =~ /style/ && $_[0] =~ /<style\W/i ) { |
|---|
| 169 | print STDERR "WYSIWYG_DEBUG: has style\n" |
|---|
| 170 | if (WHY); |
|---|
| 171 | return "Text contains style"; |
|---|
| 172 | } |
|---|
| 173 | if ( $exclusions =~ /table/ && $_[0] =~ /<table\W/i ) { |
|---|
| 174 | print STDERR "WYSIWYG_DEBUG: has table\n" |
|---|
| 175 | if (WHY); |
|---|
| 176 | return "Text contains table"; |
|---|
| 177 | } |
|---|
| 178 | } |
|---|
| 179 | |
|---|
| 180 | # Copy the content. |
|---|
| 181 | # Then crunch verbatim blocks, because verbatim blocks may |
|---|
| 182 | # contain *anything*. |
|---|
| 183 | my $text = $_[0]; |
|---|
| 184 | |
|---|
| 185 | # Look for combinations of sticky and other markup that cause |
|---|
| 186 | # problems together |
|---|
| 187 | for my $tag ('literal') { |
|---|
| 188 | while ( $text =~ /<$tag\b[^>]*>(.*?)<\/$tag>/gsi ) { |
|---|
| 189 | my $inner = $1; |
|---|
| 190 | if ( $inner =~ /<sticky\b[^>]*>/i ) { |
|---|
| 191 | print STDERR "WYSIWYG_DEBUG: <sticky> inside <$tag>\n" |
|---|
| 192 | if (WHY); |
|---|
| 193 | return "<sticky> inside <$tag>"; |
|---|
| 194 | } |
|---|
| 195 | } |
|---|
| 196 | } |
|---|
| 197 | |
|---|
| 198 | my $wasAVerbatimTag = "\000verbatim\001"; |
|---|
| 199 | while ( $text =~ s/<verbatim\b[^>]*>(.*?)<\/verbatim>/$wasAVerbatimTag/i ) { |
|---|
| 200 | |
|---|
| 201 | #my $content = $1; |
|---|
| 202 | # If there is any content that breaks conversion if it is inside |
|---|
| 203 | # a verbatim block, check for it here: |
|---|
| 204 | } |
|---|
| 205 | |
|---|
| 206 | # Look for combinations of verbatim and other markup that cause |
|---|
| 207 | # problems together |
|---|
| 208 | for my $tag ('literal') { |
|---|
| 209 | while ( $text =~ /<$tag\b[^>]*>(.*?)<\/$tag>/gsi ) { |
|---|
| 210 | my $inner = $1; |
|---|
| 211 | if ( $inner =~ /$wasAVerbatimTag/i ) { |
|---|
| 212 | print STDERR "WYSIWYG_DEBUG: <verbatim> inside <$tag>\n" |
|---|
| 213 | if (WHY); |
|---|
| 214 | return "<verbatim> inside <$tag>"; |
|---|
| 215 | } |
|---|
| 216 | } |
|---|
| 217 | } |
|---|
| 218 | |
|---|
| 219 | return 0; |
|---|
| 220 | } |
|---|
| 221 | |
|---|
| 222 | sub wysiwygEditingNotPossibleForThisContent { |
|---|
| 223 | eval { |
|---|
| 224 | require Foswiki::Plugins::WysiwygPlugin::Handlers; |
|---|
| 225 | Foswiki::Plugins::WysiwygPlugin::Handlers::TranslateTML2HTML( $_[0], |
|---|
| 226 | 'Fakewebname', 'FakeTopicName', dieOnError => 1 ); |
|---|
| 227 | }; |
|---|
| 228 | if ($@) { |
|---|
| 229 | print STDERR |
|---|
| 230 | "WYSIWYG_DEBUG: TML2HTML conversion threw an exception: $@\n" |
|---|
| 231 | if (WHY); |
|---|
| 232 | return "TML2HTML conversion fails"; |
|---|
| 233 | } |
|---|
| 234 | |
|---|
| 235 | return 0; |
|---|
| 236 | } |
|---|
| 237 | |
|---|
| 238 | sub addXMLTag { |
|---|
| 239 | require Foswiki::Plugins::WysiwygPlugin::Handlers; |
|---|
| 240 | Foswiki::Plugins::WysiwygPlugin::Handlers::addXMLTag(@_); |
|---|
| 241 | } |
|---|
| 242 | |
|---|
| 243 | sub postConvertURL { |
|---|
| 244 | require Foswiki::Plugins::WysiwygPlugin::Handlers; |
|---|
| 245 | Foswiki::Plugins::WysiwygPlugin::Handlers::postConvertURL(@_); |
|---|
| 246 | } |
|---|
| 247 | |
|---|
| 248 | sub beforeEditHandler { |
|---|
| 249 | _execute( 'beforeEditHandler', @_ ); |
|---|
| 250 | } |
|---|
| 251 | |
|---|
| 252 | sub beforeSaveHandler { |
|---|
| 253 | _execute( 'beforeSaveHandler', @_ ); |
|---|
| 254 | } |
|---|
| 255 | |
|---|
| 256 | sub beforeMergeHandler { |
|---|
| 257 | _execute( 'beforeMergeHandler', @_ ); |
|---|
| 258 | } |
|---|
| 259 | |
|---|
| 260 | sub afterEditHandler { |
|---|
| 261 | _execute( 'afterEditHandler', @_ ); |
|---|
| 262 | } |
|---|
| 263 | |
|---|
| 264 | # The next few handlers have to be executed on topic views, so have to |
|---|
| 265 | # avoid lazy-loading the handlers unless absolutely necessary. |
|---|
| 266 | |
|---|
| 267 | $FoswikiCompatibility{startRenderingHandler} = 2.1; |
|---|
| 268 | |
|---|
| 269 | sub startRenderingHandler { |
|---|
| 270 | $_[0] =~ s#</?sticky>##g; |
|---|
| 271 | } |
|---|
| 272 | |
|---|
| 273 | sub beforeCommonTagsHandler { |
|---|
| 274 | return if $recursionBlock; |
|---|
| 275 | return unless Foswiki::Func::getContext()->{body_text}; |
|---|
| 276 | |
|---|
| 277 | my $query = Foswiki::Func::getCgiQuery(); |
|---|
| 278 | |
|---|
| 279 | return unless $query; |
|---|
| 280 | |
|---|
| 281 | return unless defined( $query->param('wysiwyg_edit') ); |
|---|
| 282 | _execute( 'beforeCommonTagsHandler', @_ ); |
|---|
| 283 | } |
|---|
| 284 | |
|---|
| 285 | sub postRenderingHandler { |
|---|
| 286 | return if ( $recursionBlock || !$tml2html ); |
|---|
| 287 | _execute( 'postRenderingHandler', @_ ); |
|---|
| 288 | } |
|---|
| 289 | |
|---|
| 290 | sub modifyHeaderHandler { |
|---|
| 291 | my ( $headers, $query ) = @_; |
|---|
| 292 | |
|---|
| 293 | if ( $query->param('wysiwyg_edit') ) { |
|---|
| 294 | _execute( 'modifyHeaderHandler', @_ ); |
|---|
| 295 | } |
|---|
| 296 | } |
|---|
| 297 | |
|---|
| 298 | 1; |
|---|
| 299 | __END__ |
|---|
| 300 | Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/ |
|---|
| 301 | |
|---|
| 302 | Copyright (C) 2008-2012 Foswiki Contributors. Foswiki Contributors |
|---|
| 303 | are listed in the AUTHORS file in the root of this distribution. |
|---|
| 304 | NOTE: Please extend that file, not this notice. |
|---|
| 305 | |
|---|
| 306 | Additional copyrights apply to some or all of the code in this file: |
|---|
| 307 | |
|---|
| 308 | Copyright (C) 2005 ILOG http://www.ilog.fr |
|---|
| 309 | and TWiki Contributors. All Rights Reserved. TWiki Contributors |
|---|
| 310 | are listed in the AUTHORS file in the root of your Foswiki (or TWiki) |
|---|
| 311 | distribution. |
|---|
| 312 | |
|---|
| 313 | This program is free software; you can redistribute it and/or |
|---|
| 314 | modify it under the terms of the GNU General Public License |
|---|
| 315 | as published by the Free Software Foundation; either version 2 |
|---|
| 316 | of the License, or (at your option) any later version. For |
|---|
| 317 | more details read LICENSE in the root of the TWiki distribution. |
|---|
| 318 | |
|---|
| 319 | This program is distributed in the hope that it will be useful, |
|---|
| 320 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 321 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
|---|
| 322 | |
|---|
| 323 | As per the GPL, removal of this notice is prohibited. |
|---|