Index: trunk/phase3/includes/Parser.php |
— | — | @@ -592,7 +592,8 @@ |
593 | 593 | $output = Xml::escapeTagsOnly( $content ); |
594 | 594 | break; |
595 | 595 | case 'math': |
596 | | - $output = $wgContLang->armourMath( MathRenderer::renderMath( $content ) ); |
| 596 | + $output = $wgContLang->armourMath( |
| 597 | + MathRenderer::renderMath( $content, $params ) ); |
597 | 598 | break; |
598 | 599 | case 'gallery': |
599 | 600 | $output = $this->renderImageGallery( $content, $params ); |
— | — | @@ -4381,6 +4382,7 @@ |
4382 | 4383 | $ig->setShowBytes( false ); |
4383 | 4384 | $ig->setShowFilename( false ); |
4384 | 4385 | $ig->setParsing(); |
| 4386 | + $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'table' ) ); |
4385 | 4387 | $ig->useSkin( $this->mOptions->getSkin() ); |
4386 | 4388 | $ig->mRevisionId = $this->mRevisionId; |
4387 | 4389 | |
Index: trunk/phase3/includes/Sanitizer.php |
— | — | @@ -566,6 +566,7 @@ |
567 | 567 | * |
568 | 568 | * - Discards attributes not on a whitelist for the given element |
569 | 569 | * - Unsafe style attributes are discarded |
| 570 | + * - Invalid id attributes are reencoded |
570 | 571 | * |
571 | 572 | * @param array $attribs |
572 | 573 | * @param string $element |
— | — | @@ -575,7 +576,27 @@ |
576 | 577 | * @todo Check for unique id attribute :P |
577 | 578 | */ |
578 | 579 | static function validateTagAttributes( $attribs, $element ) { |
579 | | - $whitelist = array_flip( Sanitizer::attributeWhitelist( $element ) ); |
| 580 | + return Sanitizer::validateAttributes( $attribs, |
| 581 | + Sanitizer::attributeWhitelist( $element ) ); |
| 582 | + } |
| 583 | + |
| 584 | + /** |
| 585 | + * Take an array of attribute names and values and normalize or discard |
| 586 | + * illegal values for the given whitelist. |
| 587 | + * |
| 588 | + * - Discards attributes not the given whitelist |
| 589 | + * - Unsafe style attributes are discarded |
| 590 | + * - Invalid id attributes are reencoded |
| 591 | + * |
| 592 | + * @param array $attribs |
| 593 | + * @param array $whitelist list of allowed attribute names |
| 594 | + * @return array |
| 595 | + * |
| 596 | + * @todo Check for legal values where the DTD limits things. |
| 597 | + * @todo Check for unique id attribute :P |
| 598 | + */ |
| 599 | + static function validateAttributes( $attribs, $whitelist ) { |
| 600 | + $whitelist = array_flip( $whitelist ); |
580 | 601 | $out = array(); |
581 | 602 | foreach( $attribs as $attribute => $value ) { |
582 | 603 | if( !isset( $whitelist[$attribute] ) ) { |
— | — | @@ -602,6 +623,33 @@ |
603 | 624 | } |
604 | 625 | |
605 | 626 | /** |
| 627 | + * Merge two sets of HTML attributes. |
| 628 | + * Conflicting items in the second set will override those |
| 629 | + * in the first, except for 'class' attributes which will be |
| 630 | + * combined. |
| 631 | + * |
| 632 | + * @todo implement merging for other attributes such as style |
| 633 | + * @param array $a |
| 634 | + * @param array $b |
| 635 | + * @return array |
| 636 | + */ |
| 637 | + static function mergeAttributes( $a, $b ) { |
| 638 | + $out = array_merge( $a, $b ); |
| 639 | + if( isset( $a['class'] ) |
| 640 | + && isset( $b['class'] ) |
| 641 | + && $a['class'] !== $b['class'] ) { |
| 642 | + |
| 643 | + $out['class'] = implode( ' ', |
| 644 | + array_unique( |
| 645 | + preg_split( '/\s+/', |
| 646 | + $a['class'] . ' ' . $b['class'], |
| 647 | + -1, |
| 648 | + PREG_SPLIT_NO_EMPTY ) ) ); |
| 649 | + } |
| 650 | + return $out; |
| 651 | + } |
| 652 | + |
| 653 | + /** |
606 | 654 | * Pick apart some CSS and check it for forbidden or unsafe structures. |
607 | 655 | * Returns a sanitized string, or false if it was just too evil. |
608 | 656 | * |
— | — | @@ -1159,6 +1207,11 @@ |
1160 | 1208 | # 11.2.6 |
1161 | 1209 | 'td' => array_merge( $common, $tablecell, $tablealign ), |
1162 | 1210 | 'th' => array_merge( $common, $tablecell, $tablealign ), |
| 1211 | + |
| 1212 | + # 13.2 |
| 1213 | + # Not usually allowed, but may be used for extension-style hooks |
| 1214 | + # such as <math> when it is rasterized |
| 1215 | + 'img' => array_merge( $common, array( 'alt' ) ), |
1163 | 1216 | |
1164 | 1217 | # 15.2.1 |
1165 | 1218 | 'tt' => $common, |
— | — | @@ -1185,6 +1238,11 @@ |
1186 | 1239 | 'rb' => $common, |
1187 | 1240 | 'rt' => $common, #array_merge( $common, array( 'rbspan' ) ), |
1188 | 1241 | 'rp' => $common, |
| 1242 | + |
| 1243 | + # MathML root element, where used for extensions |
| 1244 | + # 'title' may not be 100% valid here; it's XHTML |
| 1245 | + # http://www.w3.org/TR/REC-MathML/ |
| 1246 | + 'math' => array( 'class', 'style', 'id', 'title' ), |
1189 | 1247 | ); |
1190 | 1248 | return $whitelist; |
1191 | 1249 | } |
Index: trunk/phase3/includes/Math.php |
— | — | @@ -20,8 +20,9 @@ |
21 | 21 | var $mathml = ''; |
22 | 22 | var $conservativeness = 0; |
23 | 23 | |
24 | | - function __construct( $tex ) { |
| 24 | + function __construct( $tex, $params=array() ) { |
25 | 25 | $this->tex = $tex; |
| 26 | + $this->params = $params; |
26 | 27 | } |
27 | 28 | |
28 | 29 | function setOutputMode( $mode ) { |
— | — | @@ -233,24 +234,44 @@ |
234 | 235 | */ |
235 | 236 | function _doRender() { |
236 | 237 | if( $this->mode == MW_MATH_MATHML && $this->mathml != '' ) { |
237 | | - return "<math xmlns='http://www.w3.org/1998/Math/MathML'>{$this->mathml}</math>"; |
| 238 | + return Xml::tags( 'math', |
| 239 | + $this->_attribs( 'math', |
| 240 | + array( 'xmlns' => 'http://www.w3.org/1998/Math/MathML' ) ), |
| 241 | + $this->mathml ); |
238 | 242 | } |
239 | 243 | if (($this->mode == MW_MATH_PNG) || ($this->html == '') || |
240 | 244 | (($this->mode == MW_MATH_SIMPLE) && ($this->conservativeness != 2)) || |
241 | 245 | (($this->mode == MW_MATH_MODERN || $this->mode == MW_MATH_MATHML) && ($this->conservativeness == 0))) { |
242 | 246 | return $this->_linkToMathImage(); |
243 | 247 | } else { |
244 | | - return '<span class="texhtml">'.$this->html.'</span>'; |
| 248 | + return Xml::tags( 'span', |
| 249 | + $this->_attribs( 'span', |
| 250 | + array( 'class' => 'texhtml' ) ), |
| 251 | + $this->html ); |
245 | 252 | } |
246 | 253 | } |
| 254 | + |
| 255 | + function _attribs( $tag, $defaults=array(), $overrides=array() ) { |
| 256 | + $attribs = Sanitizer::validateTagAttributes( $this->params, $tag ); |
| 257 | + $attribs = Sanitizer::mergeAttributes( $defaults, $attribs ); |
| 258 | + $attribs = Sanitizer::mergeAttributes( $attribs, $overrides ); |
| 259 | + return $attribs; |
| 260 | + } |
247 | 261 | |
248 | 262 | function _linkToMathImage() { |
249 | 263 | global $wgMathPath; |
250 | | - $url = htmlspecialchars( "$wgMathPath/" . substr($this->hash, 0, 1) |
| 264 | + $url = "$wgMathPath/" . substr($this->hash, 0, 1) |
251 | 265 | .'/'. substr($this->hash, 1, 1) .'/'. substr($this->hash, 2, 1) |
252 | | - . "/{$this->hash}.png" ); |
253 | | - $alt = trim(str_replace("\n", ' ', htmlspecialchars( $this->tex ))); |
254 | | - return "<img class='tex' src=\"$url\" alt=\"$alt\" />"; |
| 266 | + . "/{$this->hash}.png"; |
| 267 | + |
| 268 | + return Xml::element( 'img', |
| 269 | + $this->_attribs( |
| 270 | + 'img', |
| 271 | + array( |
| 272 | + 'class' => 'tex', |
| 273 | + 'alt' => $this->tex ), |
| 274 | + array( |
| 275 | + 'src' => $url ) ) ); |
255 | 276 | } |
256 | 277 | |
257 | 278 | function _getHashPath() { |
— | — | @@ -262,9 +283,9 @@ |
263 | 284 | return $path; |
264 | 285 | } |
265 | 286 | |
266 | | - public static function renderMath( $tex ) { |
| 287 | + public static function renderMath( $tex, $params=array() ) { |
267 | 288 | global $wgUser; |
268 | | - $math = new MathRenderer( $tex ); |
| 289 | + $math = new MathRenderer( $tex, $params ); |
269 | 290 | $math->setOutputMode( $wgUser->getOption('math')); |
270 | 291 | return $math->render(); |
271 | 292 | } |
Index: trunk/phase3/includes/ImageGallery.php |
— | — | @@ -32,6 +32,8 @@ |
33 | 33 | |
34 | 34 | private $mPerRow = 4; // How many images wide should the gallery be? |
35 | 35 | private $mWidths = 120, $mHeights = 120; // How wide/tall each thumbnail should be |
| 36 | + |
| 37 | + private $mAttribs = array(); |
36 | 38 | |
37 | 39 | /** |
38 | 40 | * Create a new image gallery object. |
— | — | @@ -181,6 +183,19 @@ |
182 | 184 | function setShowFilename( $f ) { |
183 | 185 | $this->mShowFilename = ( $f == true); |
184 | 186 | } |
| 187 | + |
| 188 | + /** |
| 189 | + * Set arbitrary attributes to go on the HTML gallery output element. |
| 190 | + * Should be suitable for a <table> element. |
| 191 | + * |
| 192 | + * Note -- if taking from user input, you should probably run through |
| 193 | + * Sanitizer::validateAttributes() first. |
| 194 | + * |
| 195 | + * @param array of HTML attribute pairs |
| 196 | + */ |
| 197 | + function setAttributes( $attribs ) { |
| 198 | + $this->mAttribs = $attribs; |
| 199 | + } |
185 | 200 | |
186 | 201 | /** |
187 | 202 | * Return a HTML representation of the image gallery |
— | — | @@ -197,7 +212,13 @@ |
198 | 213 | |
199 | 214 | $sk = $this->getSkin(); |
200 | 215 | |
201 | | - $s = '<table class="gallery" cellspacing="0" cellpadding="0">'; |
| 216 | + $attribs = Sanitizer::mergeAttributes( |
| 217 | + array( |
| 218 | + 'class' => 'gallery', |
| 219 | + 'cellspacing' => '0', |
| 220 | + 'cellpadding' => '0' ), |
| 221 | + $this->mAttribs ); |
| 222 | + $s = Xml::openElement( 'table', $attribs ); |
202 | 223 | if( $this->mCaption ) |
203 | 224 | $s .= "\n\t<caption>{$this->mCaption}</caption>"; |
204 | 225 | |
Index: trunk/phase3/RELEASE-NOTES |
— | — | @@ -132,7 +132,10 @@ |
133 | 133 | * Allow showing a one-off preview on first edit with "preview=yes" |
134 | 134 | * (bug 9151) Remove timed redirects on "Return to X" pages for accessibility. |
135 | 135 | * Link to user logs in toolbox when viewing a user page |
| 136 | +* (bug 10508) Allow HTML attributes on <gallery> |
| 137 | +* (bug 1962) Allow HTML attributes on <math> |
136 | 138 | |
| 139 | + |
137 | 140 | == Bugfixes since 1.10 == |
138 | 141 | |
139 | 142 | * (bug 9712) Use Arabic comma in date/time formats for Arabic and Farsi |