← Index
NYTProf Performance Profile   « line view »
For ./view
  Run on Fri Jul 31 19:05:14 2015
Reported on Fri Jul 31 19:08:10 2015

Filename/var/www/foswiki11/lib/Foswiki/Plugins/WysiwygPlugin/Handlers.pm
StatementsExecuted 62 statements in 5.87ms
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
1111.79ms7.31msFoswiki::Plugins::WysiwygPlugin::Handlers::::BEGIN@18Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@18
41147µs47µsFoswiki::Plugins::WysiwygPlugin::Handlers::::addXMLTagFoswiki::Plugins::WysiwygPlugin::Handlers::addXMLTag
11122µs54µsFoswiki::Plugins::WysiwygPlugin::Handlers::::BEGIN@7Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@7
11122µs1.14msFoswiki::Plugins::WysiwygPlugin::Handlers::::BEGIN@12Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@12
11122µs28µsFoswiki::Plugins::WysiwygPlugin::Handlers::::BEGIN@693Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@693
11119µs59µsFoswiki::Plugins::WysiwygPlugin::Handlers::::BEGIN@9Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@9
11118µs50µsFoswiki::Plugins::WysiwygPlugin::Handlers::::BEGIN@278Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@278
11114µs169µsFoswiki::Plugins::WysiwygPlugin::Handlers::::BEGIN@10Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@10
11112µs19µsFoswiki::Plugins::WysiwygPlugin::Handlers::::BEGIN@8Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@8
1117µs7µsFoswiki::Plugins::WysiwygPlugin::Handlers::::BEGIN@14Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@14
1116µs6µsFoswiki::Plugins::WysiwygPlugin::Handlers::::BEGIN@16Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@16
1115µs5µsFoswiki::Plugins::WysiwygPlugin::Handlers::::BEGIN@17Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@17
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::RESTParameter2SiteCharSetFoswiki::Plugins::WysiwygPlugin::Handlers::RESTParameter2SiteCharSet
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::TranslateHTML2TMLFoswiki::Plugins::WysiwygPlugin::Handlers::TranslateHTML2TML
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::TranslateTML2HTMLFoswiki::Plugins::WysiwygPlugin::Handlers::TranslateTML2HTML
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::_JAVASCRIPT_TEXTFoswiki::Plugins::WysiwygPlugin::Handlers::_JAVASCRIPT_TEXT
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::_OTOPICTAGFoswiki::Plugins::WysiwygPlugin::Handlers::_OTOPICTAG
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::_OWEBTAGFoswiki::Plugins::WysiwygPlugin::Handlers::_OWEBTAG
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::_SECRET_IDFoswiki::Plugins::WysiwygPlugin::Handlers::_SECRET_ID
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::_WYSIWYG_TEXTFoswiki::Plugins::WysiwygPlugin::Handlers::_WYSIWYG_TEXT
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::__ANON__[:481]Foswiki::Plugins::WysiwygPlugin::Handlers::__ANON__[:481]
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::__ANON__[:872]Foswiki::Plugins::WysiwygPlugin::Handlers::__ANON__[:872]
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::__ANON__[:875]Foswiki::Plugins::WysiwygPlugin::Handlers::__ANON__[:875]
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::__ANON__[:878]Foswiki::Plugins::WysiwygPlugin::Handlers::__ANON__[:878]
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::_convertImageFoswiki::Plugins::WysiwygPlugin::Handlers::_convertImage
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::_dropBackFoswiki::Plugins::WysiwygPlugin::Handlers::_dropBack
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::_liftOutFoswiki::Plugins::WysiwygPlugin::Handlers::_liftOut
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::_populateVarsFoswiki::Plugins::WysiwygPlugin::Handlers::_populateVars
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::_restAttachmentsFoswiki::Plugins::WysiwygPlugin::Handlers::_restAttachments
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::_restHTML2TMLFoswiki::Plugins::WysiwygPlugin::Handlers::_restHTML2TML
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::_restTML2HTMLFoswiki::Plugins::WysiwygPlugin::Handlers::_restTML2HTML
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::_restUploadFoswiki::Plugins::WysiwygPlugin::Handlers::_restUpload
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::_unquoteFoswiki::Plugins::WysiwygPlugin::Handlers::_unquote
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::afterEditHandlerFoswiki::Plugins::WysiwygPlugin::Handlers::afterEditHandler
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::beforeCommonTagsHandlerFoswiki::Plugins::WysiwygPlugin::Handlers::beforeCommonTagsHandler
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::beforeEditHandlerFoswiki::Plugins::WysiwygPlugin::Handlers::beforeEditHandler
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::beforeMergeHandlerFoswiki::Plugins::WysiwygPlugin::Handlers::beforeMergeHandler
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::beforeSaveHandlerFoswiki::Plugins::WysiwygPlugin::Handlers::beforeSaveHandler
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::expandVarsInURLFoswiki::Plugins::WysiwygPlugin::Handlers::expandVarsInURL
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::getViewUrlFoswiki::Plugins::WysiwygPlugin::Handlers::getViewUrl
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::modifyHeaderHandlerFoswiki::Plugins::WysiwygPlugin::Handlers::modifyHeaderHandler
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::postConvertURLFoswiki::Plugins::WysiwygPlugin::Handlers::postConvertURL
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::postRenderingHandlerFoswiki::Plugins::WysiwygPlugin::Handlers::postRenderingHandler
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::protectedByAttrFoswiki::Plugins::WysiwygPlugin::Handlers::protectedByAttr
0000s0sFoswiki::Plugins::WysiwygPlugin::Handlers::::returnRESTResultFoswiki::Plugins::WysiwygPlugin::Handlers::returnRESTResult
Call graph for these subroutines as a Graphviz dot language file.
Line State
ments
Time
on line
Calls Time
in subs
Code
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.
7251µs286µs
# spent 54µs (22+32) within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@7 which was called: # once (22µs+32µs) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 7
use strict;
# spent 54µs making 1 call to Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@7 # spent 32µs making 1 call to strict::import
8245µs226µs
# spent 19µs (12+7) within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@8 which was called: # once (12µs+7µs) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 8
use warnings;
# spent 19µs making 1 call to Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@8 # spent 7µs making 1 call to warnings::import
9250µs298µs
# spent 59µs (19+39) within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@9 which was called: # once (19µs+39µs) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 9
use Assert;
# spent 59µs making 1 call to Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@9 # spent 39µs making 1 call to Assert::import
10254µs2324µs
# spent 169µs (14+155) within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@10 which was called: # once (14µs+155µs) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 10
use Error (':try');
# spent 169µs making 1 call to Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@10 # spent 155µs making 1 call to Error::import
11
12261µs22.25ms
# spent 1.14ms (22µs+1.11) within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@12 which was called: # once (22µs+1.11ms) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 12
use CGI qw( :cgi -any );
# spent 1.14ms making 1 call to Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@12 # spent 1.11ms making 1 call to CGI::import
13
14240µs17µs
# spent 7µs within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@14 which was called: # once (7µs+0s) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 14
use Encode ();
15
16229µs16µs
# spent 6µs within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@16 which was called: # once (6µs+0s) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 16
use Foswiki::Func (); # The plugins API
17233µs15µs
# spent 5µs within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@17 which was called: # once (5µs+0s) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 17
use Foswiki::Plugins (); # For the API version
1821.80ms17.31ms
# spent 7.31ms (1.79+5.51) within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@18 which was called: # once (1.79ms+5.51ms) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 18
use Foswiki::Plugins::WysiwygPlugin::Constants ();
19
201200nsour $html2tml;
211100nsour $imgMap;
221300nsour @refs;
231200nsour %xmltagPlugin;
24
2511µsour $SECRET_ID =
26'WYSIWYG content - do not remove this comment, and never use this identical text in your topics';
27
28sub _SECRET_ID {
29 $SECRET_ID;
30}
31
32sub _OWEBTAG {
33 my ( $session, $params, $topic, $web ) = @_;
34
35 my $query = Foswiki::Func::getCgiQuery();
36
37 return $web unless $query;
38
39 if ( defined( $query->param('templatetopic') ) ) {
40 my @split = split( /\./, $query->param('templatetopic') );
41
42 if ( $#split == 0 ) {
43 return $web;
44 }
45 else {
46 return $split[0];
47 }
48 }
49
50 return $web;
51}
52
53sub _OTOPICTAG {
54 my ( $session, $params, $topic, $web ) = @_;
55
56 my $query = Foswiki::Func::getCgiQuery();
57
58 return $topic unless $query;
59
60 if ( defined( $query->param('templatetopic') ) ) {
61 my @split = split( /\./, $query->param('templatetopic') );
62
63 return $split[$#split];
64 }
65
66 return $topic;
67}
68
69# This handler is used to determine whether the topic is editable by
70# a WYSIWYG editor or not. The only thing it does is to redirect to a
71# normal edit url if the skin is set to WYSIWYGPLUGIN_WYSIWYGSKIN and
72# nasty content is found.
73sub beforeEditHandler {
74
75 #my( $text, $topic, $web, $meta ) = @_;
76
77 my $skin = Foswiki::Func::getPreferencesValue('WYSIWYGPLUGIN_WYSIWYGSKIN');
78
79 if ( $skin && Foswiki::Func::getSkin() =~ /\b$skin\b/o ) {
80 if ( notWysiwygEditable( $_[0] ) ) {
81
82 # redirect
83 my $query = Foswiki::Func::getCgiQuery();
84 foreach my $p (qw( skin cover )) {
85 my $arg = $query->param($p);
86 if ( $arg && $arg =~ s/\b$skin\b// ) {
87 if ( $arg =~ /^[\s,]*$/ ) {
88 $query->delete($p);
89 }
90 else {
91 $query->param( -name => $p, -value => $arg );
92 }
93 }
94 }
95 my $url = $query->url( -full => 1, -path => 1, -query => 1 );
96 Foswiki::Func::redirectCgiQuery( $query, $url );
97
98 # Bring this session to an untimely end
99 exit 0;
100 }
101 }
102}
103
104# This handler is only invoked *after* merging is complete
105sub beforeSaveHandler {
106
107 #my( $text, $topic, $web ) = @_;
108 my $query = Foswiki::Func::getCgiQuery();
109 return unless $query;
110
111 return unless defined( $query->param('wysiwyg_edit') );
112
113 $_[0] = TranslateHTML2TML( $_[0], $_[1], $_[2] );
114}
115
116# This handler is invoked before a merge. Merges are done before the
117# afterEditHandler is called, so we need to translate here.
118sub beforeMergeHandler {
119
120 #my( $text, $currRev, $currText, $origRev, $origText, $web, $topic ) = @_;
121 afterEditHandler( $_[0], $_[6], $_[5] );
122}
123
124# This handler is invoked *after* a merge, and only from the edit
125# script (so it's useless for a REST save)
126sub afterEditHandler {
127 my ( $text, $topic, $web ) = @_;
128 my $query = Foswiki::Func::getCgiQuery();
129 return unless $query;
130
131 return
132 unless defined( $query->param('wysiwyg_edit') )
133 || $text =~ s/<!--$SECRET_ID-->//go;
134
135 # Switch off wysiwyg_edit so it doesn't try to transform again in
136 # the beforeSaveHandler
137 $query->delete('wysiwyg_edit');
138
139 $text = TranslateHTML2TML( $text, $_[1], $_[2] );
140
141 $_[0] = $text;
142}
143
144# Invoked to convert HTML to TML (best efforts)
145sub TranslateHTML2TML {
146 my ( $text, $topic, $web ) = @_;
147
148 # $text must be in encoded in the site charset
149 ASSERT( $text !~ /[^\x00-\xff]/,
150 "only octets expected in input to TranslateHTML2TML" )
151 if DEBUG;
152
153 unless ($html2tml) {
154 require Foswiki::Plugins::WysiwygPlugin::HTML2TML;
155
156 $html2tml = new Foswiki::Plugins::WysiwygPlugin::HTML2TML();
157 }
158
159 # SMELL: really, really bad smell; bloody core should NOT pass text
160 # with embedded meta to plugins! It is VERY BAD DESIGN!!!
161 my $top = '';
162 if ( $text =~ s/^(%META:[A-Z]+{.*?}%\r?\n)//s ) {
163 $top = $1;
164 }
165 my $bottom = '';
166 $text =~ s/^(%META:[A-Z]+{.*?}%\r?\n)/$bottom = "$1$bottom";''/gem;
167
168 my $opts = {
169 web => $web,
170 topic => $topic,
171 convertImage => \&_convertImage,
172 rewriteURL => \&postConvertURL,
173 very_clean => 1, # aggressively polish saved HTML
174 };
175
176 $text = $html2tml->convert( $text, $opts );
177
178 $text =~ s/\s+$/\n/s;
179
180 ASSERT( $text !~ /[^\x00-\xff]/,
181 "only octets expected in topic text output from TranslateHTML2TML" )
182 if DEBUG;
183 return $top . $text . $bottom;
184}
185
186# Handler used to process text in a =view= URL to generate text/html
187# containing the HTML of the topic to be edited.
188#
189# Invoked when the selected skin is in use to convert the text to HTML
190# We can't use the beforeEditHandler, because the editor loads up and then
191# uses a URL to fetch the text to be edited. This handler is designed to
192# provide the text for that request. It's a real struggle, because the
193# commonTagsHandler is called so many times that getting the right
194# call is hard, and then preventing a repeat call is harder!
195sub beforeCommonTagsHandler {
196
197 #my ( $text, $topic, $web, $meta )
198 my $query = Foswiki::Func::getCgiQuery();
199
200 # stop it from processing the template without expanded
201 # %TEXT% (grr; we need a better way to tell where we
202 # are in the processing pipeline)
203 return if ( $_[0] =~ /^<!-- WysiwygPlugin Template/ );
204
205 # Have to re-read the topic because verbatim blocks have already been
206 # lifted out, and we need them.
207 my $topic = $_[1];
208 my $web = $_[2];
209 my ( $meta, $text );
210 my $altText = $query->param('templatetopic');
211 if ( $altText && Foswiki::Func::topicExists( $web, $altText ) ) {
212 ( $web, $topic ) =
213 Foswiki::Func::normalizeWebTopicName( $web, $altText );
214 }
215
216 $_[0] = _WYSIWYG_TEXT( $Foswiki::Plugins::SESSION, {}, $topic, $web );
217}
218
219# Handler used by editors that require pre-prepared HTML embedded in the
220# edit template.
221sub _WYSIWYG_TEXT {
222 my ( $session, $params, $topic, $web ) = @_;
223
224 # Have to re-read the topic because content has already been munged
225 # by other plugins, or by the extraction of verbatim blocks.
226 my ( $meta, $text ) = Foswiki::Func::readTopic( $web, $topic );
227
228 $text = TranslateTML2HTML( $text, $web, $topic );
229
230 # Lift out the text to protect it from further Foswiki rendering. It will be
231 # put back in the postRenderingHandler.
232 return _liftOut($text);
233}
234
235# Handler used to present the editable text in a javascript constant string
236sub _JAVASCRIPT_TEXT {
237 my ( $session, $params, $topic, $web ) = @_;
238
239 my $html = _dropBack( _WYSIWYG_TEXT(@_) );
240
241 $html =~ s/([\\'])/\\$1/sg;
242 $html =~ s/\r/\\r/sg;
243 $html =~ s/\n/\\n/sg;
244 $html =~ s/script/scri'+'pt/g;
245
246 return _liftOut("'$html'");
247}
248
249sub postRenderingHandler {
250
251 # Replace protected content.
252 $_[0] = _dropBack( $_[0] );
253}
254
255sub modifyHeaderHandler {
256 my ( $headers, $query ) = @_;
257
258 if ( $query->param('wysiwyg_edit') ) {
259 $headers->{Expires} = 0;
260 $headers->{'Cache-control'} = 'max-age=0, must-revalidate';
261 }
262}
263
264# callback passed to the TML2HTML convertor
265sub getViewUrl {
266 my ( $web, $topic ) = @_;
267
268 # the Cairo documentation says getViewUrl defaults the web. It doesn't.
269 unless ( defined $Foswiki::Plugins::SESSION ) {
270 $web ||= $Foswiki::webName;
271 }
272
273 return Foswiki::Func::getViewUrl( $web, $topic );
274}
275
276# The subset of vars for which bidirection transformation is supported
277# in URLs only
27822.04ms282µs
# spent 50µs (18+32) within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@278 which was called: # once (18µs+32µs) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 278
use vars qw( @VARS );
# spent 50µs making 1 call to Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@278 # spent 32µs making 1 call to vars::import
279
280# The set of macros that get "special treatment" in URLs
28113µs@VARS = (
282 '%ATTACHURL%',
283 '%ATTACHURLPATH%',
284 '%PUBURL%',
285 '%PUBURLPATH%',
286 '%SCRIPTURLPATH{"view"}%',
287 '%SCRIPTURLPATH%',
288 '%SCRIPTURL{"view"}%',
289 '%SCRIPTURL%',
290 '%SCRIPTSUFFIX%', # bit dodgy, this one
291);
292
293# Initialises the mapping from var to URL and back
294sub _populateVars {
295 my $opts = shift;
296
297 return if ( $opts->{exp} );
298
299 local $Foswiki::Plugins::WysiwygPlugin::recursionBlock =
300 1; # block calls to beforeCommonTagshandler
301
302 # Item9973: We call CGI::unescape() on the expanded value, because the
303 # content of the src attribute on an img tag is received from WYSIWYG that
304 # way. Without this expandVarsInURL can only match webs, topics, attachments
305 # named with ascii characters (international characters fail match).
306 my @exp = split(
307 /\0/,
308 CGI::unescape(
309 Foswiki::Func::expandCommonVariables(
310 join( "\0", @VARS ),
311 $opts->{topic}, $opts->{web}
312 )
313 )
314 );
315
316 for my $i ( 0 .. $#VARS ) {
317 my $nvar = $VARS[$i];
318 $opts->{match}[$i] = $nvar;
319 $exp[$i] ||= '';
320 }
321 $opts->{exp} = \@exp;
322}
323
324# callback passed to the TML2HTML convertor on each
325# variable in a URL used in a square bracketed link
326sub expandVarsInURL {
327 my ( $url, $opts ) = @_;
328
329 return '' unless $url;
330
331 _populateVars($opts);
332 for my $i ( 0 .. $#VARS ) {
333 $url =~ s/$opts->{match}[$i]/$opts->{exp}->[$i]/g;
334 }
335 return $url;
336}
337
338# callback passed to the HTML2TML convertor
339sub postConvertURL {
340 my ( $url, $opts ) = @_;
341
342 #my $orig = $url; #debug
343
344 local $Foswiki::Plugins::WysiwygPlugin::recursionBlock =
345 1; # block calls to beforeCommonTagshandler
346
347 my $anchor = '';
348 if ( $url =~ s/(#.*)$// ) {
349 $anchor = $1;
350 }
351 my $parameters = '';
352 if ( $url =~ s/(\?.*)$// ) {
353 $parameters = $1;
354 }
355
356 _populateVars($opts);
357
358 for my $i ( 0 .. $#VARS ) {
359 next unless $opts->{exp}->[$i];
360 $url =~ s/^$opts->{exp}->[$i]/$VARS[$i]/;
361 }
362
363 if ( $url =~ m#^%SCRIPTURL(?:PATH)?(?:{"view"}%|%/+view[^/]*)/+([/\w.]+)$#
364 && !$parameters )
365 {
366 my $orig = $1;
367 my ( $web, $topic ) =
368 Foswiki::Func::normalizeWebTopicName( $opts->{web}, $orig );
369
370 if ( $web && $web ne $opts->{web} ) {
371
372 #print STDERR "$orig -> $web+$topic$anchor\n"; #debug
373 return $web . '.' . $topic . $anchor;
374 }
375
376 #print STDERR "$orig -> $topic$anchor\n"; #debug
377 return $topic . $anchor;
378 }
379
380 #print STDERR "$orig -> $url$anchor$parameters\n"; #debug
381 return $url . $anchor . $parameters;
382}
383
384# Callback used to convert an image reference into a Foswiki variable.
385sub _convertImage {
386 my ( $src, $opts ) = @_;
387
388 return unless $src;
389
390 # block calls to beforeCommonTagshandler
391 local $Foswiki::Plugins::WysiwygPlugin::recursionBlock = 1;
392
393 unless ($imgMap) {
394 $imgMap = {};
395 my $imgs = Foswiki::Func::getPreferencesValue('WYSIWYGPLUGIN_ICONS');
396 if ($imgs) {
397 while ( $imgs =~ s/src="(.*?)" alt="(.*?)"// ) {
398 my ( $src, $alt ) = ( $1, $2 );
399 $src =
400 Foswiki::Func::expandCommonVariables( $src, $opts->{topic},
401 $opts->{web} );
402 $alt .= '%' if $alt =~ /^%/;
403 $imgMap->{$src} = $alt;
404 }
405 }
406 }
407
408 return $imgMap->{$src};
409}
410
411# Replace content with a marker to prevent it being munged by Foswiki
412sub _liftOut {
413 my ($text) = @_;
414 my $n = scalar(@refs);
415 push( @refs, $text );
416 return "\05$n\05";
417}
418
419# Substitute marker
420sub _dropBack {
421 my ($text) = @_;
422
423 # Restore everything that was lifted out
424 while ( $text =~ s/\05([0-9]+)\05/$refs[$1]/gi ) {
425 }
426 return $text;
427}
428
429=begin TML
430
431---++ StaticMethod addXMLTag($tag, \&fn)
432
433Instruct WysiwygPlugin to "lift out" the named tag
434and pass it to &fn for processing.
435&fn may modify the text of the tag.
436&fn should return 0 if the tag is to be re-embedded immediately,
437or 1 if it is to be re-embedded after all processing is complete.
438The text passed (by reference) to &fn includes the
439=<tag> ... </tag>= brackets.
440
441The simplest use of this function is something like this:
442=Foswiki::Plugins::WysiwygPlugin::addXMLTag( 'mytag', sub { 1 } );=
443
444A plugin may call this function more than once
445e.g. to change the processing function for a tag.
446However, only the *original plugin* may change the processing
447for a tag.
448
449Plugins should call this function from their =initPlugin=
450handlers so that WysiwygPlugin will protect the XML-like tags
451for all conversions, including REST conversions.
452Plugins that are intended to be used with older versions of Foswiki
453(e.g. 1.0.6) should check that this function is defined before calling it,
454so that they degrade gracefully if an older version of WysiwygPlugin
455(e.g. that shipped with 1.0.6) is installed.
456
457=cut
458
459
# spent 47µs within Foswiki::Plugins::WysiwygPlugin::Handlers::addXMLTag which was called 4 times, avg 12µs/call: # 4 times (47µs+0s) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 240 of /var/www/foswiki11/lib/Foswiki/Plugins/WysiwygPlugin.pm, avg 12µs/call
sub addXMLTag {
46043µs my ( $tag, $fn ) = @_;
461
462416µs my $plugin = caller;
46348µs $plugin =~ s/^Foswiki::Plugins:://;
464
46541µs return if not defined $tag;
466
467422µs if (
468 (
469 not exists $Foswiki::Plugins::WysiwygPlugin::xmltag{$tag}
470 and not exists $xmltagPlugin{$tag}
471 )
472 or ( $xmltagPlugin{$tag} eq $plugin )
473 )
474 {
475
476 # This is either a plugin adding a new tag
477 # or a plugin adding a tag it had previously added before.
478 # A plugin is allowed to add a tag that it had added before
479 # and the new function replaces the old.
480 #
481 $fn = sub { 1 }
48241µs unless $fn; # Default function
483
48444µs $Foswiki::Plugins::WysiwygPlugin::xmltag{$tag} = $fn;
48543µs $xmltagPlugin{$tag} = $plugin;
486 }
487 else {
488
489 # DON'T replace the existing processing for this tag
490 printf STDERR "WysiwygPlugin::addXMLTag: "
491 . "$plugin cannot add XML tag $tag, "
492 . "that tag was already registered by $xmltagPlugin{$tag}\n";
493 }
494}
495
496sub TranslateTML2HTML {
497 my ( $text, $web, $topic, @extraConvertOptions ) = @_;
498
499 ASSERT( $text !~ /[^\x00-\xff]/,
500 "only octets expected in input to TranslateTML2HTML" )
501 if DEBUG;
502
503 # Translate the topic text to pure HTML.
504 unless ($Foswiki::Plugins::WysiwygPlugin::tml2html) {
505 require Foswiki::Plugins::WysiwygPlugin::TML2HTML;
506 $Foswiki::Plugins::WysiwygPlugin::tml2html =
507 new Foswiki::Plugins::WysiwygPlugin::TML2HTML();
508 }
509 my $html = $Foswiki::Plugins::WysiwygPlugin::tml2html->convert(
510 $_[0],
511 {
512 web => $web,
513 topic => $topic,
514 getViewUrl => \&getViewUrl,
515 expandVarsInURL => \&expandVarsInURL,
516 xmltag => \%Foswiki::Plugins::WysiwygPlugin::xmltag,
517 @extraConvertOptions,
518 }
519 );
520 ASSERT( $html !~ /[^\x00-\xff]/,
521 "only octets expected in output from TranslateTML2HTML" )
522 if DEBUG;
523 return $html;
524}
525
526# PACKAGE PRIVATE
527# Determine if sticky attributes prevent a tag being converted to
528# TML when this attribute is present.
5291300nsmy @protectedByAttr;
530
531sub protectedByAttr {
532 my ( $tag, $attr ) = @_;
533
534 unless ( scalar(@protectedByAttr) ) {
535
536 # See the WysiwygPluginSettings for information on stickybits
537 my $protection =
538 Foswiki::Func::getPreferencesValue('WYSIWYGPLUGIN_STICKYBITS')
539 || <<'DEFAULT';
540(?!img).*=id,lang,title,dir,on.*;
541a=accesskey,coords,shape,target;
542bdo=dir;
543br=clear;
544col=char,charoff,span,valign,width;
545colgroup=align,char,charoff,span,valign,width;
546dir=compact;
547div=align,style;
548dl=compact;
549font=size,face;
550h\d=align;
551hr=align,noshade,size,width;
552legend=accesskey,align;
553li=value;
554ol=compact,start,type;
555p=align;
556param=name,type,value,valuetype;
557pre=width;
558q=cite;
559table=align,bgcolor,.*?background-color:.*,frame,rules,summary,width;
560tbody=align,char,charoff,valign;
561td=abbr,align,axis,bgcolor,.*?background-color:.*,.*?border-color:.*,char,charoff,headers,height,nowrap,rowspan,scope,valign,width;
562tfoot=align,char,charoff,valign;
563th=abbr,align,axis,bgcolor,.*?background-color:.*,char,charoff,height,nowrap,rowspan,scope,valign,width,headers;
564thead=align,char,charoff,valign;
565tr=bgcolor,.*?background-color:.*,char,charoff,valign;
566ul=compact,type;
567DEFAULT
568 foreach my $def ( split( /;\s*/s, $protection ) ) {
569 my ( $re, $ats ) = split( /\s*=\s*/s, $def, 2 );
570 push(
571 @protectedByAttr,
572 {
573 tag => qr/$re/i,
574 attrs => join( '|', split( /\s*,\s*/, $ats ) )
575 }
576 );
577 }
578 }
579 foreach my $row (@protectedByAttr) {
580 if ( $tag =~ /^$row->{tag}$/i ) {
581
582 #print STDERR
583 # "Matched $tag, looking for ^($row->{attrs})\$ in $attr\n";
584 if ( $attr =~ /^($row->{attrs})$/i ) {
585
586 # print STDERR
587 # "Protecting $tag with $attr matches $row->{attrs} \n"; #debug
588 return 1;
589 }
590 }
591 }
592 return 0;
593}
594
595# Text that is taken from a web page and added to the parameters of an XHR
596# by JavaScript is UTF-8 encoded. This is because UTF-8 is the default encoding
597# for XML, which XHR was designed to transport.
598
599# This function is used to decode such parameters to the currently selected
600# Foswiki site character set.
601
602# Note that this transform is not as simple as an Encode::from_to, as
603# a number of unicode code points must be remapped for certain encodings.
604sub RESTParameter2SiteCharSet {
605 my ($text) = @_;
606
607 #print STDERR "octets in [". WC::debugEncode($text). "]\n\n";
608 # $text is supposed to contain octets that are valid UTF-8.
609 # $text should certainly not have any codes above 255.
610 ASSERT( $text !~ /[^\x00-\xff]/,
611 "only octets expected in input to RESTParameter2SiteCharSet" )
612 if DEBUG;
613
614 # $text might contain octets that are not valid UTF-8
615 # because it came from the browser, and so it might be hostile content.
616 # Encode::FB_PERLQQ makes decode_utf8 convert invalid octet sequences
617 # into a perl escape sequence, octet for octet (e.g. \xFF\x80),
618 # instead of throwing an exception. This defuses the invalid sequence.
619 $text = Encode::decode_utf8( $text, Encode::FB_PERLQQ );
620
621 # $text now contains unicode characters
622 #print STDERR "as utf-8 [". WC::debugEncode($text). "]\n\n";
623
624 if ( WC::encoding() =~ /^utf-?8/ ) {
625 $text = Encode::encode_utf8($text);
626 }
627 else {
628
629 # The site charset is a non-UTF-8 8-bit charset
630
631 WC::convertNotRepresentabletoEntity($text);
632
633# All characters that cannot be represented in the site charset are now encoded as entities
634# Named entities are used if available, otherwise numeric entities,
635# because named entities produce more readable TML
636
637 # Encode $text in the site charset
638 # The Encode::FB_HTMLCREF should not be needed, as all characters in $text
639 # are supposed to be representable in the site charset.
640 $text = Encode::encode( WC::encoding(), $text, Encode::FB_HTMLCREF );
641 }
642
643# $text is now encoded as per the site charset.
644# For UTF-8 - that means octets.
645# For non-UTF8, Unicode characters that cannot be represented in the site charset
646# are converted to HTML entities (preferring named entities to numeric entities)
647
648 # The return value is supposed to be according to the currently selected
649 # Foswiki site character set, encoded as octets.
650 # Thus, there should not be any codes above 255.
651 ASSERT( $text !~ /[^\x00-\xff]/,
652 "only octets expected in return value for RESTParameter2SiteCharSet" )
653 if DEBUG;
654
655 #print STDERR "octets out [". WC::debugEncode($text). "]\n\n";
656 return $text;
657}
658
659# Text that is taken from a web page and added to the parameters of an XHR
660# by JavaScript is UTF-8 encoded. This is because UTF-8 is the default encoding
661# for XML, which XHR was designed to transport. For usefulness in Javascript
662# the response to an XHR should also be UTF-8 encoded.
663# This function generates such a response.
664sub returnRESTResult {
665 my ( $response, $status, $text ) = @_;
666 ASSERT( $text !~ /[^\x00-\xff]/,
667 "only octets expected in input to returnRESTResult" )
668 if DEBUG;
669
670 $text = Encode::decode( WC::encoding(), $text, Encode::FB_HTMLCREF );
671
672 #print STDERR "unicodechr[". WC::debugEncode($text). "]\n\n";
673
674 $text = Encode::encode_utf8($text);
675
676 # Foswiki 1.0 introduces the Foswiki::Response object, which handles all
677 # responses.
678 if ( UNIVERSAL::isa( $response, 'Foswiki::Response' ) ) {
679 $response->header(
680 -status => $status,
681 -type => 'text/plain',
682 -charset => 'UTF-8'
683 );
684 $response->print($text);
685 }
686 else { # Pre-Foswiki-1.0.
687 # Turn off AUTOFLUSH
688 # See http://perl.apache.org/docs/2.0/user/coding/coding.html
689 local $| = 0;
690 my $query = Foswiki::Func::getCgiQuery();
691 if ( defined($query) ) {
692 my $len;
69321.59ms234µs
# spent 28µs (22+6) within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@693 which was called: # once (22µs+6µs) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 693
{ use bytes; $len = length($text); };
# spent 28µs making 1 call to Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@693 # spent 6µs making 1 call to bytes::import
694 print $query->header(
695 -status => $status,
696 -type => 'text/plain',
697 -charset => 'UTF-8',
698 -Content_length => $len
699 );
700 print $text;
701 }
702 }
703 print STDERR $text if ( $status >= 400 );
704}
705
706# Rest handler for use from Javascript. The 'text' parameter is used to
707# pass the text for conversion. The text must be URI-encoded (this is
708# to support use of this handler from XMLHttpRequest, which gets it
709# wrong). Example:
710#
711# var req = new XMLHttpRequest();
712# req.open("POST", url, true);
713# req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
714# var params = "text=" + encodeURIComponent(escape(text));
715# request.req.setRequestHeader("Content-length", params.length);
716# request.req.setRequestHeader("Connection", "close");
717# request.req.onreadystatechange = ...;
718# req.send(params);
719#
720sub _restTML2HTML {
721 my ( $session, $plugin, $verb, $response ) = @_;
722 my $tml = Foswiki::Func::getCgiQuery()->param('text');
723
724 $tml = RESTParameter2SiteCharSet($tml);
725
726 # if the secret ID is present, don't convert again. We are probably
727 # going 'back' to this page (doesn't work on IE :-( )
728 if ( $tml =~ /<!--$SECRET_ID-->/ ) {
729 return $tml;
730 }
731
732 my $html =
733 TranslateTML2HTML( $tml, $session->{webName}, $session->{topicName} );
734
735 # Add the secret id to trigger reconversion. Doesn't work if the
736 # editor eats HTML comments, so the editor may need to put it back
737 # in during final cleanup.
738 $html = '<!--' . $SECRET_ID . '-->' . $html;
739
740 returnRESTResult( $response, 200, $html );
741
742 return; # to prevent further processing
743}
744
745# Rest handler for use from Javascript
746sub _restHTML2TML {
747 my ( $session, $plugin, $verb, $response ) = @_;
748 unless ($html2tml) {
749 require Foswiki::Plugins::WysiwygPlugin::HTML2TML;
750
751 $html2tml = new Foswiki::Plugins::WysiwygPlugin::HTML2TML();
752 }
753 my $html = Foswiki::Func::getCgiQuery()->param('text');
754
755#print STDERR "param [". Foswiki::Plugins::WysiwygPlugin::HTML2TML::debugEncode($html). "]\n\n";
756
757 $html = RESTParameter2SiteCharSet($html);
758
759#print STDERR "paraminSC [". Foswiki::Plugins::WysiwygPlugin::HTML2TML::debugEncode($html). "]\n\n";
760
761 $html =~ s/<!--$SECRET_ID-->//go;
762 my $tml = $html2tml->convert(
763 $html,
764 {
765 web => $session->{webName},
766 topic => $session->{topicName},
767 convertImage => \&_convertImage,
768 rewriteURL => \&postConvertURL,
769 very_clean => 1,
770 }
771 );
772
773#print STDERR "tml inSc [". Foswiki::Plugins::WysiwygPlugin::HTML2TML::debugEncode($tml). "]\n\n";
774
775 returnRESTResult( $response, 200, $tml );
776 return; # to prevent further processing
777}
778
779# SMELL: foswiki supports proper REST usage of the upload script,
780# so debatable if this is required any more
781sub _restUpload {
782 my ( $session, $plugin, $verb, $response ) = @_;
783 my $query = Foswiki::Func::getCgiQuery();
784
785 # Item1458 ignore uploads not using POST
786 if ( $query && $query->method() && uc( $query->method() ) ne 'POST' ) {
787 returnRESTResult( $response, 405, "Method not Allowed" );
788 return;
789 }
790 my ( $web, $topic ) = ( $session->{webName}, $session->{topicName} );
791 my $hideFile = $query->param('hidefile') || '';
792 my $fileComment = $query->param('filecomment') || '';
793 my $createLink = $query->param('createlink') || '';
794 my $doPropsOnly = $query->param('changeproperties');
795 my $filePath = $query->param('filepath') || '';
796 my $fileName = $query->param('filename') || '';
797 if ( $filePath && !$fileName ) {
798 $filePath =~ m|([^/\\]*$)|;
799 $fileName = $1;
800 }
801 $fileComment =~ s/\s+/ /go;
802 $fileComment =~ s/^\s*//o;
803 $fileComment =~ s/\s*$//o;
804 $fileName =~ s/\s*$//o;
805 $filePath =~ s/\s*$//o;
806
807 unless (
808 Foswiki::Func::checkAccessPermission(
809 'CHANGE', Foswiki::Func::getWikiName(),
810 undef, $topic, $web
811 )
812 )
813 {
814 returnRESTResult( $response, 401, "Access denied" );
815 return; # to prevent further processing
816 }
817
818 my ( $fileSize, $fileDate, $tmpFileName );
819
820 my $stream;
821 $stream = $query->upload('filepath') unless $doPropsOnly;
822 my $origName = $fileName;
823
824 unless ($doPropsOnly) {
825
826 # SMELL: call to unpublished function
827 ( $fileName, $origName ) =
828 Foswiki::Sandbox::sanitizeAttachmentName($fileName);
829
830 # check if upload has non zero size
831 if ($stream) {
832 my @stats = stat $stream;
833 $fileSize = $stats[7];
834 $fileDate = $stats[9];
835 }
836
837 unless ( $fileSize && $fileName ) {
838 returnRESTResult( $response, 500, "Zero-sized file upload" );
839 return; # to prevent further processing
840 }
841
842 my $maxSize = Foswiki::Func::getPreferencesValue('ATTACHFILESIZELIMIT')
843 || 0;
844 $maxSize =~ s/\s+$//;
845 $maxSize = 0 unless ( $maxSize =~ /([0-9]+)/o );
846
847 if ( $maxSize && $fileSize > $maxSize * 1024 ) {
848 returnRESTResult( $response, 500, "OVERSIZED UPLOAD" );
849 return; # to prevent further processing
850 }
851 }
852
853 # SMELL: use of undocumented CGI::tmpFileName
854 my $tfp = $query->tmpFileName( $query->param('filepath') );
855 my $error;
856 try {
857 Foswiki::Func::saveAttachment(
858 $web, $topic,
859 $fileName,
860 {
861 dontlog => !$Foswiki::cfg{Log}{upload},
862 comment => $fileComment,
863 hide => $hideFile,
864 createlink => $createLink,
865 stream => $stream,
866 filepath => $filePath,
867 filesize => $fileSize,
868 filedate => $fileDate,
869 tmpFilename => $tfp,
870 }
871 );
872 }
873 catch Error::Simple with {
874 $error = shift->{-text};
875 }
876 finally {
877 close($stream) if $stream;
878 };
879
880 if ($error) {
881 returnRESTResult( $response, 500, $error );
882 return; # to prevent further processing
883 }
884
885 # Otherwise allow the rest dispatcher to write a 200
886 return "$origName attached to $web.$topic"
887 . ( $origName ne $fileName ? " as $fileName" : '' );
888}
889
890sub _unquote {
891 my $text = shift;
892 $text =~ s/\\/\\\\/g;
893 $text =~ s/\n/\\n/g;
894 $text =~ s/\r/\\r/g;
895 $text =~ s/\t/\\t/g;
896 $text =~ s/"/\\"/g;
897 $text =~ s/'/\\'/g;
898 return $text;
899}
900
901# Get, and return, a list of attachments using JSON
902sub _restAttachments {
903 my ( $session, $plugin, $verb, $response ) = @_;
904 my ( $web, $topic ) = ( $session->{webName}, $session->{topicName} );
905 my ( $meta, $text ) = Foswiki::Func::readTopic( $web, $topic );
906
907 unless (
908 Foswiki::Func::checkAccessPermission(
909 'VIEW', Foswiki::Func::getWikiName(),
910 $text, $topic, $web, $meta
911 )
912 )
913 {
914 returnRESTResult( $response, 401, "Access denied" );
915 return; # to prevent further processing
916 }
917
918 # Create a JSON list of attachment data, sorted by name
919 my @atts;
920 foreach my $att ( sort { $a->{name} cmp $b->{name} }
921 $meta->find('FILEATTACHMENT') )
922 {
923 push(
924 @atts,
925 '{' . join(
926 ',',
927 map {
928 '"'
929 . _unquote($_) . '":"'
930 . _unquote( $att->{$_} ) . '"'
931 } keys %$att
932 )
933 . '}'
934 );
935
936 }
937 return '[' . join( ',', @atts ) . ']';
938}
939
94017µs1;
941__END__