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