Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F585031
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
10 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/includes/Html/Html.php b/includes/Html/Html.php
index 8612456b8e0..ef32e655bfa 100644
--- a/includes/Html/Html.php
+++ b/includes/Html/Html.php
@@ -443,6 +443,54 @@ class Html {
return $attribs;
}
+ /**
+ * Convert a value for a 'class' attribute in a format accepted by Html::element() and similar
+ * methods to a single string.
+ *
+ * This method may also be used for any other space-separated attribute, such as 'rel'.
+ *
+ * @param array|string $classes
+ * @return string
+ */
+ public static function expandClassList( $classes ): string {
+ // Convert into correct array. Array can contain space-separated
+ // values. Implode/explode to get those into the main array as well.
+ if ( is_array( $classes ) ) {
+ // If input wasn't an array, we can skip this step
+ $arrayValue = [];
+ foreach ( $classes as $k => $v ) {
+ if ( is_string( $v ) ) {
+ // String values should be normal `[ 'foo' ]`
+ // Just append them
+ if ( !isset( $classes[$v] ) ) {
+ // As a special case don't set 'foo' if a
+ // separate 'foo' => true/false exists in the array
+ // keys should be authoritative
+ foreach ( explode( ' ', $v ) as $part ) {
+ // Normalize spacing by fixing up cases where people used
+ // more than 1 space and/or a trailing/leading space
+ if ( $part !== '' && $part !== ' ' ) {
+ $arrayValue[] = $part;
+ }
+ }
+ }
+ } elseif ( $v ) {
+ // If the value is truthy but not a string this is likely
+ // an [ 'foo' => true ], falsy values don't add strings
+ $arrayValue[] = $k;
+ }
+ }
+ } else {
+ $arrayValue = explode( ' ', $classes );
+ // Normalize spacing by fixing up cases where people used
+ // more than 1 space and/or a trailing/leading space
+ $arrayValue = array_diff( $arrayValue, [ '', ' ' ] );
+ }
+
+ // Remove duplicates and create the string
+ return implode( ' ', array_unique( $arrayValue ) );
+ }
+
/**
* Given an associative array of element attributes, generate a string
* to stick after the element name in HTML output. Like [ 'href' =>
@@ -513,43 +561,7 @@ class Html {
// Specific features for attributes that allow a list of space-separated values
if ( isset( $spaceSeparatedListAttributes[$key] ) ) {
// Apply some normalization and remove duplicates
-
- // Convert into correct array. Array can contain space-separated
- // values. Implode/explode to get those into the main array as well.
- if ( is_array( $value ) ) {
- // If input wasn't an array, we can skip this step
- $arrayValue = [];
- foreach ( $value as $k => $v ) {
- if ( is_string( $v ) ) {
- // String values should be normal `[ 'foo' ]`
- // Just append them
- if ( !isset( $value[$v] ) ) {
- // As a special case don't set 'foo' if a
- // separate 'foo' => true/false exists in the array
- // keys should be authoritative
- foreach ( explode( ' ', $v ) as $part ) {
- // Normalize spacing by fixing up cases where people used
- // more than 1 space and/or a trailing/leading space
- if ( $part !== '' && $part !== ' ' ) {
- $arrayValue[] = $part;
- }
- }
- }
- } elseif ( $v ) {
- // If the value is truthy but not a string this is likely
- // an [ 'foo' => true ], falsy values don't add strings
- $arrayValue[] = $k;
- }
- }
- } else {
- $arrayValue = explode( ' ', $value );
- // Normalize spacing by fixing up cases where people used
- // more than 1 space and/or a trailing/leading space
- $arrayValue = array_diff( $arrayValue, [ '', ' ' ] );
- }
-
- // Remove duplicates and create the string
- $value = implode( ' ', array_unique( $arrayValue ) );
+ $value = self::expandClassList( $value );
// Optimization: Skip below boolAttribs check and jump straight
// to its `else` block. The current $spaceSeparatedListAttributes
diff --git a/includes/Output/OutputPage.php b/includes/Output/OutputPage.php
index d0f99772fad..9c1ef3d615f 100644
--- a/includes/Output/OutputPage.php
+++ b/includes/Output/OutputPage.php
@@ -3958,9 +3958,9 @@ class OutputPage extends ContextSource {
}
$bodyAttrs = [];
- // While the implode() is not strictly needed, it's used for backwards compatibility
+ // While the expandClassList() is not strictly needed, it's used for backwards compatibility
// (this used to be built as a string and hooks likely still expect that).
- $bodyAttrs['class'] = implode( ' ', $bodyClasses );
+ $bodyAttrs['class'] = Html::expandClassList( $bodyClasses );
$this->getHookRunner()->onOutputPageBodyAttributes( $this, $sk, $bodyAttrs );
diff --git a/includes/linker/LinkRenderer.php b/includes/linker/LinkRenderer.php
index b61931f1ce5..cf32b779b51 100644
--- a/includes/linker/LinkRenderer.php
+++ b/includes/linker/LinkRenderer.php
@@ -388,14 +388,13 @@ class LinkRenderer {
public function makeExternalLink(
string $url, $text, $title, $linktype = '', $attribs = []
) {
- $class = 'external';
+ $attribs['class'] ??= [];
+ Html::addClass( $attribs['class'], 'external' );
if ( $linktype ) {
- $class .= " $linktype";
+ Html::addClass( $attribs['class'], $linktype );
}
- if ( isset( $attribs['class'] ) && $attribs['class'] ) {
- $class .= " {$attribs['class']}";
- }
- $attribs['class'] = $class;
+ // Stringify attributes for hook compatibility
+ $attribs['class'] = Html::expandClassList( $attribs['class'] );
if ( $text instanceof Message ) {
$text = $text->escaped();
@@ -406,14 +405,10 @@ class LinkRenderer {
}
$newRel = Parser::getExternalLinkRel( $url, $title );
- if ( !isset( $attribs['rel'] ) || $attribs['rel'] === '' ) {
- $attribs['rel'] = $newRel;
- } elseif ( $newRel !== null ) {
- // Merge the rel attributes.
- $newRels = explode( ' ', $newRel );
- $oldRels = explode( ' ', $attribs['rel'] );
- $combined = array_unique( array_merge( $newRels, $oldRels ) );
- $attribs['rel'] = implode( ' ', $combined );
+ if ( $newRel !== null ) {
+ $attribs['rel'] ??= [];
+ Html::addClass( $attribs['rel'], $newRel );
+ $attribs['rel'] = Html::expandClassList( $attribs['rel'] );
}
$link = '';
$success = $this->hookRunner->onLinkerMakeExternalLink(
diff --git a/includes/skins/components/SkinComponentLink.php b/includes/skins/components/SkinComponentLink.php
index 57d42651bfc..437ec001bff 100644
--- a/includes/skins/components/SkinComponentLink.php
+++ b/includes/skins/components/SkinComponentLink.php
@@ -188,7 +188,7 @@ class SkinComponentLink implements SkinComponent {
$class, $options['link-class']
);
}
- $attrs['class'] = is_array( $class ) ? implode( ' ', $class ) : $class;
+ $attrs['class'] = Html::expandClassList( $class );
foreach ( $attrs as $key => $value ) {
if ( $value === null ) {
continue;
@@ -214,7 +214,7 @@ class SkinComponentLink implements SkinComponent {
'text' => trim( $text ),
];
if ( $classAsProperty ) {
- $data['class'] = $attrs['class'] ?? '';
+ $data['class'] = $attrs['class'];
}
return $data;
}
diff --git a/includes/skins/components/SkinComponentListItem.php b/includes/skins/components/SkinComponentListItem.php
index 58ff49be704..cd127985ff7 100644
--- a/includes/skins/components/SkinComponentListItem.php
+++ b/includes/skins/components/SkinComponentListItem.php
@@ -192,9 +192,7 @@ class SkinComponentListItem implements SkinComponent {
$attrs['title'] = $item['itemtitle'];
}
// Making sure we always have strings as class values
- $classes = is_array( $attrs['class'] ) ?
- implode( ' ', $attrs['class'] ) :
- $attrs['class'] ?? null;
+ $classes = Html::expandClassList( $attrs['class'] );
return [
'tag' => $options['tag'] ?? 'li',
'attrs' => $attrs,
diff --git a/tests/phpunit/includes/Html/HtmlTest.php b/tests/phpunit/includes/Html/HtmlTest.php
index 74e3104d77e..78f11c83efe 100644
--- a/tests/phpunit/includes/Html/HtmlTest.php
+++ b/tests/phpunit/includes/Html/HtmlTest.php
@@ -247,6 +247,10 @@ class HtmlTest extends MediaWikiIntegrationTestCase {
' zero="0"',
[ 'zero' => 0 ]
];
+ yield 'Integration test for space-separated attribs' => [
+ ' class="a b"',
+ [ 'class' => [ 'a', 'b' ] ]
+ ];
}
/**
@@ -273,35 +277,35 @@ class HtmlTest extends MediaWikiIntegrationTestCase {
// $expect, $classes
// string values
yield 'Normalization should strip redundant spaces' => [
- ' class="redundant spaces here"',
+ 'redundant spaces here',
' redundant spaces here '
];
yield 'Normalization should remove duplicates in string-lists' => [
- ' class="foo bar"',
+ 'foo bar',
'foo bar foo bar bar'
];
// array values
yield 'Value with an empty array' => [
- ' class=""',
+ '',
[]
];
yield 'Array with null, empty string and spaces' => [
- ' class=""',
+ '',
[ null, '', ' ', ' ' ]
];
yield 'Normalization should remove duplicates in the array' => [
- ' class="foo bar"',
+ 'foo bar',
[ 'foo', 'bar', 'foo', 'bar', 'bar' ]
];
yield 'Normalization should remove duplicates in string-lists in the array' => [
- ' class="foo bar"',
- [ 'foo bar', 'bar foo', 'foo', 'bar bar' ]
+ 'foo bar baz',
+ [ 'foo bar', 'bar foo', 'foo', 'bar bar', 'baz' ]
];
// Feature added in r96188 - pass attributes values as a PHP array
// only applies to class, rel, and accesskey
yield 'Associative array' => [
- ' class="booltrue one"',
+ 'booltrue one',
[
'booltrue' => true,
'one' => 1,
@@ -319,7 +323,7 @@ class HtmlTest extends MediaWikiIntegrationTestCase {
// We could pass a "class" the values: 'GREEN' and [ 'GREEN' => false ]
// The latter will take precedence
yield 'Duplicate keys' => [
- ' class=""',
+ '',
[
'GREEN',
'GREEN' => false,
@@ -329,16 +333,12 @@ class HtmlTest extends MediaWikiIntegrationTestCase {
}
/**
- * Html::expandAttributes has special features for HTML
- * attributes that use space separated lists and also
- * allows arrays to be used as values.
- *
* @dataProvider provideExpandAttributesClass
*/
public function testExpandAttributesClass( string $expect, $classes ) {
$this->assertEquals(
$expect,
- Html::expandAttributes( [ 'class' => $classes ] )
+ Html::expandClassList( $classes )
);
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Jul 5, 5:31 AM (14 h, 53 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
227481
Default Alt Text
(10 KB)
Attached To
Mode
rMW mediawiki
Attached
Detach File
Event Timeline
Log In to Comment