~ubuntu-branches/debian/sid/mediawiki/sid

« back to all changes in this revision

Viewing changes to .pc/security_1.19.24.patch/includes/Html.php

  • Committer: Package Import Robot
  • Author(s): Thijs Kinkhorst
  • Date: 2015-04-06 16:53:54 UTC
  • Revision ID: package-import@ubuntu.com-20150406165354-sme6dft73f81n9r9
Tags: 1:1.19.20+dfsg-2.3
* Non-maintainer upload.
* Add patch fixing several security issues:
  - (bug T85848, bug T71210) SECURITY: Don't parse XMP blocks that
     contain XML entities, to prevent various DoS attacks.
  - (bug T88310) SECURITY: Always expand xml entities when checking
    SVG's.
  - (bug T73394) SECURITY: Escape > in Html::expandAttributes to
    prevent XSS.
  - (bug T85855) SECURITY: Don't execute another user's CSS or JS
    on preview.
  - (bug T85349, bug T85850, bug T86711) SECURITY: Multiple issues
    fixed in SVG filtering to prevent XSS and protect viewer's
    privacy.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
/**
 
3
 * Collection of methods to generate HTML content
 
4
 *
 
5
 * Copyright © 2009 Aryeh Gregor
 
6
 * http://www.mediawiki.org/
 
7
 *
 
8
 * This program is free software; you can redistribute it and/or modify
 
9
 * it under the terms of the GNU General Public License as published by
 
10
 * the Free Software Foundation; either version 2 of the License, or
 
11
 * (at your option) any later version.
 
12
 *
 
13
 * This program is distributed in the hope that it will be useful,
 
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 
16
 * GNU General Public License for more details.
 
17
 *
 
18
 * You should have received a copy of the GNU General Public License along
 
19
 * with this program; if not, write to the Free Software Foundation, Inc.,
 
20
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 
21
 * http://www.gnu.org/copyleft/gpl.html
 
22
 *
 
23
 * @file
 
24
 */
 
25
 
 
26
/**
 
27
 * This class is a collection of static functions that serve two purposes:
 
28
 *
 
29
 * 1) Implement any algorithms specified by HTML5, or other HTML
 
30
 * specifications, in a convenient and self-contained way.
 
31
 *
 
32
 * 2) Allow HTML elements to be conveniently and safely generated, like the
 
33
 * current Xml class but a) less confused (Xml supports HTML-specific things,
 
34
 * but only sometimes!) and b) not necessarily confined to XML-compatible
 
35
 * output.
 
36
 *
 
37
 * There are two important configuration options this class uses:
 
38
 *
 
39
 * $wgHtml5: If this is set to false, then all output should be valid XHTML 1.0
 
40
 *     Transitional.
 
41
 * $wgWellFormedXml: If this is set to true, then all output should be
 
42
 *     well-formed XML (quotes on attributes, self-closing tags, etc.).
 
43
 *
 
44
 * This class is meant to be confined to utility functions that are called from
 
45
 * trusted code paths.  It does not do enforcement of policy like not allowing
 
46
 * <a> elements.
 
47
 *
 
48
 * @since 1.16
 
49
 */
 
50
class Html {
 
51
        # List of void elements from HTML5, section 8.1.2 as of 2011-08-12
 
52
        private static $voidElements = array(
 
53
                'area',
 
54
                'base',
 
55
                'br',
 
56
                'col',
 
57
                'command',
 
58
                'embed',
 
59
                'hr',
 
60
                'img',
 
61
                'input',
 
62
                'keygen',
 
63
                'link',
 
64
                'meta',
 
65
                'param',
 
66
                'source',
 
67
                'track',
 
68
                'wbr',
 
69
        );
 
70
 
 
71
        # Boolean attributes, which may have the value omitted entirely.  Manually
 
72
        # collected from the HTML5 spec as of 2011-08-12.
 
73
        private static $boolAttribs = array(
 
74
                'async',
 
75
                'autofocus',
 
76
                'autoplay',
 
77
                'checked',
 
78
                'controls',
 
79
                'default',
 
80
                'defer',
 
81
                'disabled',
 
82
                'formnovalidate',
 
83
                'hidden',
 
84
                'ismap',
 
85
                'itemscope',
 
86
                'loop',
 
87
                'multiple',
 
88
                'muted',
 
89
                'novalidate',
 
90
                'open',
 
91
                'pubdate',
 
92
                'readonly',
 
93
                'required',
 
94
                'reversed',
 
95
                'scoped',
 
96
                'seamless',
 
97
                'selected',
 
98
                'truespeed',
 
99
                'typemustmatch',
 
100
                # HTML5 Microdata
 
101
                'itemscope',
 
102
        );
 
103
 
 
104
        private static $HTMLFiveOnlyAttribs = array(
 
105
                'autocomplete',
 
106
                'autofocus',
 
107
                'max',
 
108
                'min',
 
109
                'multiple',
 
110
                'pattern',
 
111
                'placeholder',
 
112
                'required',
 
113
                'step',
 
114
                'spellcheck',
 
115
        );
 
116
 
 
117
        /**
 
118
         * Returns an HTML element in a string.  The major advantage here over
 
119
         * manually typing out the HTML is that it will escape all attribute
 
120
         * values.  If you're hardcoding all the attributes, or there are none, you
 
121
         * should probably just type out the html element yourself.
 
122
         *
 
123
         * This is quite similar to Xml::tags(), but it implements some useful
 
124
         * HTML-specific logic.  For instance, there is no $allowShortTag
 
125
         * parameter: the closing tag is magically omitted if $element has an empty
 
126
         * content model.  If $wgWellFormedXml is false, then a few bytes will be
 
127
         * shaved off the HTML output as well.
 
128
         *
 
129
         * @param $element string The element's name, e.g., 'a'
 
130
         * @param $attribs array  Associative array of attributes, e.g., array(
 
131
         *   'href' => 'http://www.mediawiki.org/' ). See expandAttributes() for
 
132
         *   further documentation.
 
133
         * @param $contents string The raw HTML contents of the element: *not*
 
134
         *   escaped!
 
135
         * @return string Raw HTML
 
136
         */
 
137
        public static function rawElement( $element, $attribs = array(), $contents = '' ) {
 
138
                global $wgWellFormedXml;
 
139
                $start = self::openElement( $element, $attribs );
 
140
                if ( in_array( $element, self::$voidElements ) ) {
 
141
                        if ( $wgWellFormedXml ) {
 
142
                                # Silly XML.
 
143
                                return substr( $start, 0, -1 ) . ' />';
 
144
                        }
 
145
                        return $start;
 
146
                } else {
 
147
                        return "$start$contents" . self::closeElement( $element );
 
148
                }
 
149
        }
 
150
 
 
151
        /**
 
152
         * Identical to rawElement(), but HTML-escapes $contents (like
 
153
         * Xml::element()).
 
154
         *
 
155
         * @param $element string
 
156
         * @param $attribs array
 
157
         * @param $contents string
 
158
         *
 
159
         * @return string
 
160
         */
 
161
        public static function element( $element, $attribs = array(), $contents = '' ) {
 
162
                return self::rawElement( $element, $attribs, strtr( $contents, array(
 
163
                        # There's no point in escaping quotes, >, etc. in the contents of
 
164
                        # elements.
 
165
                        '&' => '&amp;',
 
166
                        '<' => '&lt;'
 
167
                ) ) );
 
168
        }
 
169
 
 
170
        /**
 
171
         * Identical to rawElement(), but has no third parameter and omits the end
 
172
         * tag (and the self-closing '/' in XML mode for empty elements).
 
173
         *
 
174
         * @param $element string
 
175
         * @param $attribs array
 
176
         *
 
177
         * @return string
 
178
         */
 
179
        public static function openElement( $element, $attribs = array() ) {
 
180
                global $wgHtml5, $wgWellFormedXml;
 
181
                $attribs = (array)$attribs;
 
182
                # This is not required in HTML5, but let's do it anyway, for
 
183
                # consistency and better compression.
 
184
                $element = strtolower( $element );
 
185
 
 
186
                # In text/html, initial <html> and <head> tags can be omitted under
 
187
                # pretty much any sane circumstances, if they have no attributes.  See:
 
188
                # <http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#optional-tags>
 
189
                if ( !$wgWellFormedXml && !$attribs
 
190
                && in_array( $element, array( 'html', 'head' ) ) ) {
 
191
                        return '';
 
192
                }
 
193
 
 
194
                # Remove HTML5-only attributes if we aren't doing HTML5, and disable
 
195
                # form validation regardless (see bug 23769 and the more detailed
 
196
                # comment in expandAttributes())
 
197
                if ( $element == 'input' ) {
 
198
                        # Whitelist of types that don't cause validation.  All except
 
199
                        # 'search' are valid in XHTML1.
 
200
                        $validTypes = array(
 
201
                                'hidden',
 
202
                                'text',
 
203
                                'password',
 
204
                                'checkbox',
 
205
                                'radio',
 
206
                                'file',
 
207
                                'submit',
 
208
                                'image',
 
209
                                'reset',
 
210
                                'button',
 
211
                                'search',
 
212
                        );
 
213
 
 
214
                        if ( isset( $attribs['type'] )
 
215
                        && !in_array( $attribs['type'], $validTypes ) ) {
 
216
                                unset( $attribs['type'] );
 
217
                        }
 
218
 
 
219
                        if ( isset( $attribs['type'] ) && $attribs['type'] == 'search'
 
220
                        && !$wgHtml5 ) {
 
221
                                unset( $attribs['type'] );
 
222
                        }
 
223
                }
 
224
 
 
225
                if ( !$wgHtml5 && $element == 'textarea' && isset( $attribs['maxlength'] ) ) {
 
226
                        unset( $attribs['maxlength'] );
 
227
                }
 
228
 
 
229
                return "<$element" . self::expandAttributes(
 
230
                        self::dropDefaults( $element, $attribs ) ) . '>';
 
231
        }
 
232
 
 
233
        /**
 
234
         * Returns "</$element>", except if $wgWellFormedXml is off, in which case
 
235
         * it returns the empty string when that's guaranteed to be safe.
 
236
         *
 
237
         * @since 1.17
 
238
         * @param $element string Name of the element, e.g., 'a'
 
239
         * @return string A closing tag, if required
 
240
         */
 
241
        public static function closeElement( $element ) {
 
242
                global $wgWellFormedXml;
 
243
 
 
244
                $element = strtolower( $element );
 
245
 
 
246
                # Reference:
 
247
                # http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#optional-tags
 
248
                if ( !$wgWellFormedXml && in_array( $element, array(
 
249
                        'html',
 
250
                        'head',
 
251
                        'body',
 
252
                        'li',
 
253
                        'dt',
 
254
                        'dd',
 
255
                        'tr',
 
256
                        'td',
 
257
                        'th',
 
258
                ) ) ) {
 
259
                        return '';
 
260
                }
 
261
                return "</$element>";
 
262
        }
 
263
 
 
264
        /**
 
265
         * Given an element name and an associative array of element attributes,
 
266
         * return an array that is functionally identical to the input array, but
 
267
         * possibly smaller.  In particular, attributes might be stripped if they
 
268
         * are given their default values.
 
269
         *
 
270
         * This method is not guaranteed to remove all redundant attributes, only
 
271
         * some common ones and some others selected arbitrarily at random.  It
 
272
         * only guarantees that the output array should be functionally identical
 
273
         * to the input array (currently per the HTML 5 draft as of 2009-09-06).
 
274
         *
 
275
         * @param $element string Name of the element, e.g., 'a'
 
276
         * @param $attribs array  Associative array of attributes, e.g., array(
 
277
         *   'href' => 'http://www.mediawiki.org/' ).  See expandAttributes() for
 
278
         *   further documentation.
 
279
         * @return array An array of attributes functionally identical to $attribs
 
280
         */
 
281
        private static function dropDefaults( $element, $attribs ) {
 
282
                # Don't bother doing anything if we aren't outputting HTML5; it's too
 
283
                # much of a pain to maintain two sets of defaults.
 
284
                global $wgHtml5;
 
285
                if ( !$wgHtml5 ) {
 
286
                        return $attribs;
 
287
                }
 
288
 
 
289
                static $attribDefaults = array(
 
290
                        'area' => array( 'shape' => 'rect' ),
 
291
                        'button' => array(
 
292
                                'formaction' => 'GET',
 
293
                                'formenctype' => 'application/x-www-form-urlencoded',
 
294
                                'type' => 'submit',
 
295
                        ),
 
296
                        'canvas' => array(
 
297
                                'height' => '150',
 
298
                                'width' => '300',
 
299
                        ),
 
300
                        'command' => array( 'type' => 'command' ),
 
301
                        'form' => array(
 
302
                                'action' => 'GET',
 
303
                                'autocomplete' => 'on',
 
304
                                'enctype' => 'application/x-www-form-urlencoded',
 
305
                        ),
 
306
                        'input' => array(
 
307
                                'formaction' => 'GET',
 
308
                                'type' => 'text',
 
309
                                'value' => '',
 
310
                        ),
 
311
                        'keygen' => array( 'keytype' => 'rsa' ),
 
312
                        'link' => array( 'media' => 'all' ),
 
313
                        'menu' => array( 'type' => 'list' ),
 
314
                        # Note: the use of text/javascript here instead of other JavaScript
 
315
                        # MIME types follows the HTML5 spec.
 
316
                        'script' => array( 'type' => 'text/javascript' ),
 
317
                        'style' => array(
 
318
                                'media' => 'all',
 
319
                                'type' => 'text/css',
 
320
                        ),
 
321
                        'textarea' => array( 'wrap' => 'soft' ),
 
322
                );
 
323
 
 
324
                $element = strtolower( $element );
 
325
 
 
326
                foreach ( $attribs as $attrib => $value ) {
 
327
                        $lcattrib = strtolower( $attrib );
 
328
                        $value = strval( $value );
 
329
 
 
330
                        # Simple checks using $attribDefaults
 
331
                        if ( isset( $attribDefaults[$element][$lcattrib] ) &&
 
332
                        $attribDefaults[$element][$lcattrib] == $value ) {
 
333
                                unset( $attribs[$attrib] );
 
334
                        }
 
335
 
 
336
                        if ( $lcattrib == 'class' && $value == '' ) {
 
337
                                unset( $attribs[$attrib] );
 
338
                        }
 
339
                }
 
340
 
 
341
                # More subtle checks
 
342
                if ( $element === 'link' && isset( $attribs['type'] )
 
343
                && strval( $attribs['type'] ) == 'text/css' ) {
 
344
                        unset( $attribs['type'] );
 
345
                }
 
346
                if ( $element === 'select' && isset( $attribs['size'] ) ) {
 
347
                        if ( in_array( 'multiple', $attribs )
 
348
                                || ( isset( $attribs['multiple'] ) && $attribs['multiple'] !== false )
 
349
                        ) {
 
350
                                # A multi-select
 
351
                                if ( strval( $attribs['size'] ) == '4' ) {
 
352
                                        unset( $attribs['size'] );
 
353
                                }
 
354
                        } else {
 
355
                                # Single select
 
356
                                if ( strval( $attribs['size'] ) == '1' ) {
 
357
                                        unset( $attribs['size'] );
 
358
                                }
 
359
                        }
 
360
                }
 
361
 
 
362
                return $attribs;
 
363
        }
 
364
 
 
365
        /**
 
366
         * Given an associative array of element attributes, generate a string
 
367
         * to stick after the element name in HTML output.  Like array( 'href' =>
 
368
         * 'http://www.mediawiki.org/' ) becomes something like
 
369
         * ' href="http://www.mediawiki.org"'.  Again, this is like
 
370
         * Xml::expandAttributes(), but it implements some HTML-specific logic.
 
371
         * For instance, it will omit quotation marks if $wgWellFormedXml is false,
 
372
         * and will treat boolean attributes specially.
 
373
         *
 
374
         * Attributes that should contain space-separated lists (such as 'class') array
 
375
         * values are allowed as well, which will automagically be normalized
 
376
         * and converted to a space-separated string. In addition to a numerical
 
377
         * array, the attribute value may also be an associative array. See the
 
378
         * example below for how that works.
 
379
         *
 
380
         * @par Numerical array
 
381
         * @code
 
382
         *     Html::element( 'em', array(
 
383
         *         'class' => array( 'foo', 'bar' )
 
384
         *     ) );
 
385
         *     // gives '<em class="foo bar"></em>'
 
386
         * @endcode
 
387
         *
 
388
         * @par Associative array
 
389
         * @code
 
390
         *     Html::element( 'em', array(
 
391
         *         'class' => array( 'foo', 'bar', 'foo' => false, 'quux' => true )
 
392
         *     ) );
 
393
         *     // gives '<em class="bar quux"></em>'
 
394
         * @endcode
 
395
         *
 
396
         * @param $attribs array Associative array of attributes, e.g., array(
 
397
         *   'href' => 'http://www.mediawiki.org/' ).  Values will be HTML-escaped.
 
398
         *   A value of false means to omit the attribute.  For boolean attributes,
 
399
         *   you can omit the key, e.g., array( 'checked' ) instead of
 
400
         *   array( 'checked' => 'checked' ) or such.
 
401
         * @return string HTML fragment that goes between element name and '>'
 
402
         *   (starting with a space if at least one attribute is output)
 
403
         */
 
404
        public static function expandAttributes( $attribs ) {
 
405
                global $wgHtml5, $wgWellFormedXml;
 
406
 
 
407
                $ret = '';
 
408
                $attribs = (array)$attribs;
 
409
                foreach ( $attribs as $key => $value ) {
 
410
                        if ( $value === false || is_null( $value ) ) {
 
411
                                continue;
 
412
                        }
 
413
 
 
414
                        # For boolean attributes, support array( 'foo' ) instead of
 
415
                        # requiring array( 'foo' => 'meaningless' ).
 
416
                        if ( is_int( $key )
 
417
                        && in_array( strtolower( $value ), self::$boolAttribs ) ) {
 
418
                                $key = $value;
 
419
                        }
 
420
 
 
421
                        # Not technically required in HTML5, but required in XHTML 1.0,
 
422
                        # and we'd like consistency and better compression anyway.
 
423
                        $key = strtolower( $key );
 
424
 
 
425
                        # Here we're blacklisting some HTML5-only attributes...
 
426
                        if ( !$wgHtml5 && in_array( $key, self::$HTMLFiveOnlyAttribs )
 
427
                         ) {
 
428
                                continue;
 
429
                        }
 
430
 
 
431
                        # Bug 23769: Blacklist all form validation attributes for now.  Current
 
432
                        # (June 2010) WebKit has no UI, so the form just refuses to submit
 
433
                        # without telling the user why, which is much worse than failing
 
434
                        # server-side validation.  Opera is the only other implementation at
 
435
                        # this time, and has ugly UI, so just kill the feature entirely until
 
436
                        # we have at least one good implementation.
 
437
 
 
438
                        # As the default value of "1" for "step" rejects decimal
 
439
                        # numbers to be entered in 'type="number"' fields, allow
 
440
                        # the special case 'step="any"'.
 
441
 
 
442
                        if ( in_array( $key, array( 'max', 'min', 'pattern', 'required' ) ) ||
 
443
                                 $key === 'step' && $value !== 'any' ) {
 
444
                                continue;
 
445
                        }
 
446
 
 
447
                        // http://www.w3.org/TR/html401/index/attributes.html ("space-separated")
 
448
                        // http://www.w3.org/TR/html5/index.html#attributes-1 ("space-separated")
 
449
                        $spaceSeparatedListAttributes = array(
 
450
                                'class', // html4, html5
 
451
                                'accesskey', // as of html5, multiple space-separated values allowed
 
452
                                // html4-spec doesn't document rel= as space-separated
 
453
                                // but has been used like that and is now documented as such 
 
454
                                // in the html5-spec.
 
455
                                'rel',
 
456
                        );
 
457
 
 
458
                        # Specific features for attributes that allow a list of space-separated values
 
459
                        if ( in_array( $key, $spaceSeparatedListAttributes ) ) {
 
460
                                // Apply some normalization and remove duplicates
 
461
 
 
462
                                // Convert into correct array. Array can contain space-seperated
 
463
                                // values. Implode/explode to get those into the main array as well.
 
464
                                if ( is_array( $value ) ) {
 
465
                                        // If input wasn't an array, we can skip this step
 
466
                                        
 
467
                                        $newValue = array();
 
468
                                        foreach ( $value as $k => $v ) {
 
469
                                                if ( is_string( $v ) ) {
 
470
                                                        // String values should be normal `array( 'foo' )`
 
471
                                                        // Just append them
 
472
                                                        if ( !isset( $value[$v] ) ) {
 
473
                                                                // As a special case don't set 'foo' if a
 
474
                                                                // separate 'foo' => true/false exists in the array
 
475
                                                                // keys should be authoritive
 
476
                                                                $newValue[] = $v;
 
477
                                                        }
 
478
                                                } elseif ( $v ) {
 
479
                                                        // If the value is truthy but not a string this is likely
 
480
                                                        // an array( 'foo' => true ), falsy values don't add strings
 
481
                                                        $newValue[] = $k;
 
482
                                                }
 
483
                                        }
 
484
                                        $value = implode( ' ', $newValue );
 
485
                                }
 
486
                                $value = explode( ' ', $value );
 
487
 
 
488
                                // Normalize spacing by fixing up cases where people used
 
489
                                // more than 1 space and/or a trailing/leading space
 
490
                                $value = array_diff( $value, array( '', ' ' ) );
 
491
 
 
492
                                // Remove duplicates and create the string
 
493
                                $value = implode( ' ', array_unique( $value ) );
 
494
                        }
 
495
 
 
496
                        # See the "Attributes" section in the HTML syntax part of HTML5,
 
497
                        # 9.1.2.3 as of 2009-08-10.  Most attributes can have quotation
 
498
                        # marks omitted, but not all.  (Although a literal " is not
 
499
                        # permitted, we don't check for that, since it will be escaped
 
500
                        # anyway.)
 
501
                        #
 
502
                        # See also research done on further characters that need to be
 
503
                        # escaped: http://code.google.com/p/html5lib/issues/detail?id=93
 
504
                        $badChars = "\\x00- '=<>`/\x{00a0}\x{1680}\x{180e}\x{180F}\x{2000}\x{2001}"
 
505
                                . "\x{2002}\x{2003}\x{2004}\x{2005}\x{2006}\x{2007}\x{2008}\x{2009}"
 
506
                                . "\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}";
 
507
                        if ( $wgWellFormedXml || $value === ''
 
508
                        || preg_match( "![$badChars]!u", $value ) ) {
 
509
                                $quote = '"';
 
510
                        } else {
 
511
                                $quote = '';
 
512
                        }
 
513
 
 
514
                        if ( in_array( $key, self::$boolAttribs ) ) {
 
515
                                # In XHTML 1.0 Transitional, the value needs to be equal to the
 
516
                                # key.  In HTML5, we can leave the value empty instead.  If we
 
517
                                # don't need well-formed XML, we can omit the = entirely.
 
518
                                if ( !$wgWellFormedXml ) {
 
519
                                        $ret .= " $key";
 
520
                                } elseif ( $wgHtml5 ) {
 
521
                                        $ret .= " $key=\"\"";
 
522
                                } else {
 
523
                                        $ret .= " $key=\"$key\"";
 
524
                                }
 
525
                        } else {
 
526
                                # Apparently we need to entity-encode \n, \r, \t, although the
 
527
                                # spec doesn't mention that.  Since we're doing strtr() anyway,
 
528
                                # and we don't need <> escaped here, we may as well not call
 
529
                                # htmlspecialchars().
 
530
                                # @todo FIXME: Verify that we actually need to
 
531
                                # escape \n\r\t here, and explain why, exactly.
 
532
                                #
 
533
                                # We could call Sanitizer::encodeAttribute() for this, but we
 
534
                                # don't because we're stubborn and like our marginal savings on
 
535
                                # byte size from not having to encode unnecessary quotes.
 
536
                                $map = array(
 
537
                                        '&' => '&amp;',
 
538
                                        '"' => '&quot;',
 
539
                                        "\n" => '&#10;',
 
540
                                        "\r" => '&#13;',
 
541
                                        "\t" => '&#9;'
 
542
                                );
 
543
                                if ( $wgWellFormedXml ) {
 
544
                                        # This is allowed per spec: <http://www.w3.org/TR/xml/#NT-AttValue>
 
545
                                        # But reportedly it breaks some XML tools?
 
546
                                        # @todo FIXME: Is this really true?
 
547
                                        $map['<'] = '&lt;';
 
548
                                }
 
549
                                
 
550
                                $ret .= " $key=$quote" . strtr( $value, $map ) . $quote;
 
551
                        }
 
552
                }
 
553
                return $ret;
 
554
        }
 
555
 
 
556
        /**
 
557
         * Output a <script> tag with the given contents.  TODO: do some useful
 
558
         * escaping as well, like if $contents contains literal '</script>' or (for
 
559
         * XML) literal "]]>".
 
560
         *
 
561
         * @param $contents string JavaScript
 
562
         * @return string Raw HTML
 
563
         */
 
564
        public static function inlineScript( $contents ) {
 
565
                global $wgHtml5, $wgJsMimeType, $wgWellFormedXml;
 
566
 
 
567
                $attrs = array();
 
568
 
 
569
                if ( !$wgHtml5 ) {
 
570
                        $attrs['type'] = $wgJsMimeType;
 
571
                }
 
572
 
 
573
                if ( $wgWellFormedXml && preg_match( '/[<&]/', $contents ) ) {
 
574
                        $contents = "/*<![CDATA[*/$contents/*]]>*/";
 
575
                }
 
576
 
 
577
                return self::rawElement( 'script', $attrs, $contents );
 
578
        }
 
579
 
 
580
        /**
 
581
         * Output a <script> tag linking to the given URL, e.g.,
 
582
         * <script src=foo.js></script>.
 
583
         *
 
584
         * @param $url string
 
585
         * @return string Raw HTML
 
586
         */
 
587
        public static function linkedScript( $url ) {
 
588
                global $wgHtml5, $wgJsMimeType;
 
589
 
 
590
                $attrs = array( 'src' => $url );
 
591
 
 
592
                if ( !$wgHtml5 ) {
 
593
                        $attrs['type'] = $wgJsMimeType;
 
594
                }
 
595
 
 
596
                return self::element( 'script', $attrs );
 
597
        }
 
598
 
 
599
        /**
 
600
         * Output a <style> tag with the given contents for the given media type
 
601
         * (if any).  TODO: do some useful escaping as well, like if $contents
 
602
         * contains literal '</style>' (admittedly unlikely).
 
603
         *
 
604
         * @param $contents string CSS
 
605
         * @param $media mixed A media type string, like 'screen'
 
606
         * @return string Raw HTML
 
607
         */
 
608
        public static function inlineStyle( $contents, $media = 'all' ) {
 
609
                global $wgWellFormedXml;
 
610
 
 
611
                if ( $wgWellFormedXml && preg_match( '/[<&]/', $contents ) ) {
 
612
                        $contents = "/*<![CDATA[*/$contents/*]]>*/";
 
613
                }
 
614
 
 
615
                return self::rawElement( 'style', array(
 
616
                        'type' => 'text/css',
 
617
                        'media' => $media,
 
618
                ), $contents );
 
619
        }
 
620
 
 
621
        /**
 
622
         * Output a <link rel=stylesheet> linking to the given URL for the given
 
623
         * media type (if any).
 
624
         *
 
625
         * @param $url string
 
626
         * @param $media mixed A media type string, like 'screen'
 
627
         * @return string Raw HTML
 
628
         */
 
629
        public static function linkedStyle( $url, $media = 'all' ) {
 
630
                return self::element( 'link', array(
 
631
                        'rel' => 'stylesheet',
 
632
                        'href' => $url,
 
633
                        'type' => 'text/css',
 
634
                        'media' => $media,
 
635
                ) );
 
636
        }
 
637
 
 
638
        /**
 
639
         * Convenience function to produce an <input> element.  This supports the
 
640
         * new HTML5 input types and attributes, and will silently strip them if
 
641
         * $wgHtml5 is false.
 
642
         *
 
643
         * @param $name    string name attribute
 
644
         * @param $value   mixed  value attribute
 
645
         * @param $type    string type attribute
 
646
         * @param $attribs array  Associative array of miscellaneous extra
 
647
         *   attributes, passed to Html::element()
 
648
         * @return string Raw HTML
 
649
         */
 
650
        public static function input( $name, $value = '', $type = 'text', $attribs = array() ) {
 
651
                $attribs['type'] = $type;
 
652
                $attribs['value'] = $value;
 
653
                $attribs['name'] = $name;
 
654
 
 
655
                return self::element( 'input', $attribs );
 
656
        }
 
657
 
 
658
        /**
 
659
         * Convenience function to produce an input element with type=hidden
 
660
         *
 
661
         * @param $name    string name attribute
 
662
         * @param $value   string value attribute
 
663
         * @param $attribs array  Associative array of miscellaneous extra
 
664
         *   attributes, passed to Html::element()
 
665
         * @return string Raw HTML
 
666
         */
 
667
        public static function hidden( $name, $value, $attribs = array() ) {
 
668
                return self::input( $name, $value, 'hidden', $attribs );
 
669
        }
 
670
 
 
671
        /**
 
672
         * Convenience function to produce an <input> element.  This supports leaving
 
673
         * out the cols= and rows= which Xml requires and are required by HTML4/XHTML
 
674
         * but not required by HTML5 and will silently set cols="" and rows="" if
 
675
         * $wgHtml5 is false and cols and rows are omitted (HTML4 validates present
 
676
         * but empty cols="" and rows="" as valid).
 
677
         *
 
678
         * @param $name    string name attribute
 
679
         * @param $value   string value attribute
 
680
         * @param $attribs array  Associative array of miscellaneous extra
 
681
         *   attributes, passed to Html::element()
 
682
         * @return string Raw HTML
 
683
         */
 
684
        public static function textarea( $name, $value = '', $attribs = array() ) {
 
685
                global $wgHtml5;
 
686
 
 
687
                $attribs['name'] = $name;
 
688
 
 
689
                if ( !$wgHtml5 ) {
 
690
                        if ( !isset( $attribs['cols'] ) ) {
 
691
                                $attribs['cols'] = "";
 
692
                        }
 
693
 
 
694
                        if ( !isset( $attribs['rows'] ) ) {
 
695
                                $attribs['rows'] = "";
 
696
                        }
 
697
                }
 
698
 
 
699
                if (substr($value, 0, 1) == "\n") {
 
700
                        // Workaround for bug 12130: browsers eat the initial newline
 
701
                        // assuming that it's just for show, but they do keep the later
 
702
                        // newlines, which we may want to preserve during editing.
 
703
                        // Prepending a single newline
 
704
                        $spacedValue = "\n" . $value;
 
705
                } else {
 
706
                        $spacedValue = $value;
 
707
                }
 
708
                return self::element( 'textarea', $attribs, $spacedValue );
 
709
        }
 
710
        /**
 
711
         * Build a drop-down box for selecting a namespace
 
712
         *
 
713
         * @param $params array:
 
714
         * - selected: [optional] Id of namespace which should be pre-selected
 
715
         * - all: [optional] Value of item for "all namespaces". If null or unset, no <option> is generated to select all namespaces
 
716
         * - label: text for label to add before the field
 
717
         * @param $selectAttribs array HTML attributes for the generated select element.
 
718
         * - id:   [optional], default: 'namespace'
 
719
         * - name: [optional], default: 'namespace'
 
720
         * @return string HTML code to select a namespace.
 
721
         */
 
722
        public static function namespaceSelector( Array $params = array(), Array $selectAttribs = array() ) {
 
723
                global $wgContLang;
 
724
 
 
725
                // Default 'id' & 'name' <select> attributes
 
726
                $selectAttribs = $selectAttribs + array(
 
727
                        'id'   => 'namespace',
 
728
                        'name' => 'namespace',
 
729
                );
 
730
                ksort( $selectAttribs );
 
731
 
 
732
                // Is a namespace selected?
 
733
                if ( isset( $params['selected'] ) ) {
 
734
                        // If string only contains digits, convert to clean int. Selected could also
 
735
                        // be "all" or "" etc. which needs to be left untouched.
 
736
                        // PHP is_numeric() has issues with large strings, PHP ctype_digit has other issues
 
737
                        // and returns false for already clean ints. Use regex instead..
 
738
                        if ( preg_match( '/^\d+$/', $params['selected'] ) ) {
 
739
                                $params['selected'] = intval( $params['selected'] );
 
740
                        }
 
741
                        // else: leaves it untouched for later processing
 
742
                } else {
 
743
                        $params['selected'] = '';
 
744
                }
 
745
 
 
746
                // Array holding the <option> elements
 
747
                $options = array();
 
748
 
 
749
                if ( isset( $params['all'] ) ) {
 
750
                        // add an <option> that would let the user select all namespaces.
 
751
                        // Value is provided by user, the name shown is localized.
 
752
                        $options[$params['all']] = wfMsg( 'namespacesall' );
 
753
                }
 
754
                // Add defaults <option> according to content language
 
755
                $options += $wgContLang->getFormattedNamespaces();
 
756
 
 
757
                // Convert $options to HTML
 
758
                $optionsHtml = array();
 
759
                foreach ( $options as $nsId => $nsName ) {
 
760
                        if ( $nsId < NS_MAIN ) {
 
761
                                continue;
 
762
                        }
 
763
                        if ( $nsId === 0 ) {
 
764
                                $nsName = wfMsg( 'blanknamespace' );
 
765
                        }
 
766
                        $optionsHtml[] = Xml::option( $nsName, $nsId, $nsId === $params['selected'] );
 
767
                }
 
768
 
 
769
                // Forge a <select> element and returns it
 
770
                $ret = '';
 
771
                if ( isset( $params['label'] ) ) {
 
772
                        $ret .= Xml::label( $params['label'], $selectAttribs['id'] ) . '&#160;';
 
773
                }
 
774
                $ret .= Html::openElement( 'select', $selectAttribs )
 
775
                        . "\n"
 
776
                        . implode( "\n", $optionsHtml )
 
777
                        . "\n"
 
778
                        . Html::closeElement( 'select' );
 
779
                return $ret;
 
780
        }
 
781
 
 
782
        /**
 
783
         * Constructs the opening html-tag with necessary doctypes depending on
 
784
         * global variables.
 
785
         *
 
786
         * @param $attribs array  Associative array of miscellaneous extra
 
787
         *   attributes, passed to Html::element() of html tag.
 
788
         * @return string  Raw HTML
 
789
         */
 
790
        public static function htmlHeader( $attribs = array() ) {
 
791
                $ret = '';
 
792
 
 
793
                global $wgMimeType;
 
794
 
 
795
                if ( self::isXmlMimeType( $wgMimeType ) ) {
 
796
                        $ret .= "<?xml version=\"1.0\" encoding=\"UTF-8\" ?" . ">\n";
 
797
                }
 
798
 
 
799
                global $wgHtml5, $wgHtml5Version, $wgDocType, $wgDTD;
 
800
                global $wgXhtmlNamespaces, $wgXhtmlDefaultNamespace;
 
801
 
 
802
                if ( $wgHtml5 ) {
 
803
                        $ret .= "<!DOCTYPE html>\n";
 
804
 
 
805
                        if ( $wgHtml5Version ) {
 
806
                                $attribs['version'] = $wgHtml5Version;
 
807
                        }
 
808
                } else {
 
809
                        $ret .= "<!DOCTYPE html PUBLIC \"$wgDocType\" \"$wgDTD\">\n";
 
810
                        $attribs['xmlns'] = $wgXhtmlDefaultNamespace;
 
811
 
 
812
                        foreach ( $wgXhtmlNamespaces as $tag => $ns ) {
 
813
                                $attribs["xmlns:$tag"] = $ns;
 
814
                        }
 
815
                }
 
816
 
 
817
                $html = Html::openElement( 'html', $attribs );
 
818
 
 
819
                if ( $html ) {
 
820
                        $html .= "\n";
 
821
                }
 
822
 
 
823
                $ret .= $html;
 
824
 
 
825
                return $ret;
 
826
        }
 
827
 
 
828
        /**
 
829
         * Determines if the given mime type is xml.
 
830
         *
 
831
         * @param $mimetype    string MimeType
 
832
         * @return Boolean
 
833
         */
 
834
        public static function isXmlMimeType( $mimetype ) {
 
835
                switch ( $mimetype ) {
 
836
                        case 'text/xml':
 
837
                        case 'application/xhtml+xml':
 
838
                        case 'application/xml':
 
839
                                return true;
 
840
                        default:
 
841
                                return false;
 
842
                }
 
843
        }
 
844
 
 
845
        /**
 
846
         * Get HTML for an info box with an icon.
 
847
         *
 
848
         * @param $text String: wikitext, get this with wfMsgNoTrans()
 
849
         * @param $icon String: icon name, file in skins/common/images
 
850
         * @param $alt String: alternate text for the icon
 
851
         * @param $class String: additional class name to add to the wrapper div
 
852
         * @param $useStylePath
 
853
         *
 
854
         * @return string
 
855
         */
 
856
        static function infoBox( $text, $icon, $alt, $class = false, $useStylePath = true ) {
 
857
                global $wgStylePath;
 
858
 
 
859
                if ( $useStylePath ) {
 
860
                        $icon = $wgStylePath.'/common/images/'.$icon;
 
861
                }
 
862
 
 
863
                $s  = Html::openElement( 'div', array( 'class' => "mw-infobox $class") );
 
864
 
 
865
                $s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-left' ) ).
 
866
                                Html::element( 'img',
 
867
                                        array(
 
868
                                                'src' => $icon,
 
869
                                                'alt' => $alt,
 
870
                                        )
 
871
                                ).
 
872
                                Html::closeElement( 'div' );
 
873
 
 
874
                $s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-right' ) ).
 
875
                                $text.
 
876
                                Html::closeElement( 'div' );
 
877
                $s .= Html::element( 'div', array( 'style' => 'clear: left;' ), ' ' );
 
878
 
 
879
                $s .= Html::closeElement( 'div' );
 
880
 
 
881
                $s .= Html::element( 'div', array( 'style' => 'clear: left;' ), ' ' );
 
882
 
 
883
                return $s;
 
884
        }
 
885
}