* (T112474) Generalized the ResourceLoader mechanism for overriding modules
using a particular page during edit previews.
* Added 'ApiParseMakeOutputPage' hook.
+* (T174313) Added checkbox on Special:ListUsers to display only users in temporary
+ user groups.
=== External library changes in 1.32 ===
* …
* CollationFa has been removed completely as it's not needed anymore
=== Other changes in 1.32 ===
+* Soft hyphens (U+00AD) are now automatically removed from titles; these
+ characters can accidentally end up in copy-and-pasted titles.
+* Strip Unicode 6.3.0 directional formatting characters (U+061C, U+2066,
+ U+2067, U+2068, U+2069) from the title.
* …
== Compatibility ==
$rlClient = new ResourceLoaderClientHtml( $context, [
'target' => $this->getTarget(),
+ 'nonce' => $this->getCSPNonce(),
] );
$rlClient->setConfig( $this->getJSVars() );
$rlClient->setModules( $this->getModules( /*filter*/ true ) );
}
$pieces[] = Html::element( 'title', null, $this->getHTMLTitle() );
- $pieces[] = $this->getRlClient()->getHeadHtml( $this->getCSPNonce() );
+ $pieces[] = $this->getRlClient()->getHeadHtml();
$pieces[] = $this->buildExemptModules();
$pieces = array_merge( $pieces, array_values( $this->getHeadLinksArray() ) );
$pieces = array_merge( $pieces, array_values( $this->mHeadItems ) );
}
/**
- * Returns page information in an easily-manipulated format. Array keys are used so extensions
- * may add additional information in arbitrary positions. Array values are arrays with one
- * element to be rendered as a header, arrays with two elements to be rendered as a table row.
+ * Returns an array of info groups (will be rendered as tables), keyed by group ID.
+ * Group IDs are arbitrary and used so that extensions may add additional information in
+ * arbitrary positions (and as message keys for section headers for the tables, prefixed
+ * with 'pageinfo-').
+ * Each info group is a non-associative array of info items (rendered as table rows).
+ * Each info item is an array with two elements: the first describes the type of
+ * information, the second the value for the current page. Both can be strings (will be
+ * interpreted as raw HTML) or messages (will be interpreted as plain text and escaped).
*
* @return array
*/
* startup module if the client has adequate support for MediaWiki JavaScript code.
*
* @param string $script JavaScript code
- * @param string $nonce Content-security-policy nonce, from OutputPage::getCSPNonce()
+ * @param string $nonce [optional] Content-Security-Policy nonce (from OutputPage::getCSPNonce)
* @return WrappedString HTML
*/
public static function makeInlineScript( $script, $nonce = null ) {
* @param ResourceLoaderContext $context
* @param array $options [optional] Array of options
* - 'target': Custom parameter passed to StartupModule.
+ * - 'nonce': From OutputPage::getCSPNonce().
*/
public function __construct( ResourceLoaderContext $context, array $options = [] ) {
$this->context = $context;
$this->resourceLoader = $context->getResourceLoader();
- $this->options = $options;
+ $this->options = $options + [
+ 'target' => null,
+ 'nonce' => null,
+ ];
}
/**
* - Inline scripts can't be asynchronous.
* - For styles, earlier is better.
*
- * @param string $nonce From OutputPage::getCSPNonce()
* @return string|WrappedStringList HTML
*/
- public function getHeadHtml( $nonce ) {
+ public function getHeadHtml() {
+ $nonce = $this->options['nonce'];
$data = $this->getData();
$chunks = [];
// Async scripts. Once the startup is loaded, inline RLQ scripts will run.
// Pass-through a custom 'target' from OutputPage (T143066).
- $startupQuery = isset( $this->options['target'] )
+ $startupQuery = $this->options['target'] !== null
? [ 'target' => (string)$this->options['target'] ]
: [];
$chunks[] = $this->getLoad(
* @param ResourceLoaderContext $mainContext
* @param array $modules One or more module names
* @param string $only ResourceLoaderModule TYPE_ class constant
- * @param array $extraQuery Array with extra query parameters for the request
- * @param string $nonce See OutputPage::getCSPNonce() [Since 1.32]
+ * @param array $extraQuery [optional] Array with extra query parameters for the request
+ * @param string $nonce [optional] Content-Security-Policy nonce (from OutputPage::getCSPNonce)
* @return string|WrappedStringList HTML
*/
public static function makeLoad( ResourceLoaderContext $mainContext, array $modules, $only,
- array $extraQuery, $nonce
+ array $extraQuery = [], $nonce = null
) {
$rl = $mainContext->getResourceLoader();
$chunks = [];
$this->outputHeader();
$this->getOutput()->allowClickjacking();
$this->getOutput()->addHTML(
- Html::openElement( 'table', [ 'class' => 'mw-datatable',
+ Html::openElement( 'table', [ 'class' => 'mw-datatable sortable',
'id' => 'mw-trackingcategories-table' ] ) . "\n" .
"<thead><tr>
<th>" .
if ( $hidelinks || $hidetrans || $hideredirs || $hideimages ) {
$out->addHTML( $this->getFilterPanel() );
}
- $errMsg = is_int( $namespace ) ? 'nolinkshere-ns' : 'nolinkshere';
- $out->addWikiMsg( $errMsg, $this->target->getPrefixedText() );
+ $msgKey = is_int( $namespace ) ? 'nolinkshere-ns-2' : 'nolinkshere-2';
+ $link = $this->getLinkRenderer()->makeKnownLink(
+ $this->target,
+ null,
+ [],
+ $this->target->isRedirect() ? [ 'redirect' => 'no' ] : []
+ );
+
+ $errMsg = $this->msg( $msgKey )->rawParams( $link )->parseAsBlock();
+ $out->addHTML( $errMsg );
$out->setStatusCode( 404 );
}
}
if ( !$this->including() ) {
$out->addHTML( $this->whatlinkshereForm() );
$out->addHTML( $this->getFilterPanel() );
- $out->addWikiMsg( 'linkshere', $this->target->getPrefixedText() );
+
+ $link = $this->getLinkRenderer()->makeKnownLink(
+ $this->target,
+ null,
+ [],
+ $this->target->isRedirect() ? [ 'redirect' => 'no' ] : []
+ );
+
+ $msg = $this->msg( 'linkshere-2' )->rawParams( $link )->parseAsBlock();
+ $out->addHTML( $msg );
$prevnext = $this->getPrevNext( $prevId, $nextId );
$out->addHTML( $prevnext );
$this->requestedGroup = '';
}
$this->editsOnly = $request->getBool( 'editsOnly' );
+ $this->temporaryGroupsOnly = $request->getBool( 'temporaryGroupsOnly' );
$this->creationSort = $request->getBool( 'creationSort' );
$this->including = $including;
$this->mDefaultDirection = $request->getBool( 'desc' )
$options = [];
+ if ( $this->requestedGroup != '' || $this->temporaryGroupsOnly ) {
+ $conds[] = 'ug_expiry >= ' . $dbr->addQuotes( $dbr->timestamp() ) .
+ ( !$this->temporaryGroupsOnly ? ' OR ug_expiry IS NULL' : '' );
+ }
+
if ( $this->requestedGroup != '' ) {
$conds['ug_group'] = $this->requestedGroup;
- $conds[] = 'ug_expiry IS NULL OR ug_expiry >= ' . $dbr->addQuotes( $dbr->timestamp() );
}
if ( $this->requestedUser != '' ) {
'id' => 'editsOnly',
'default' => $this->editsOnly
],
+ 'temporaryGroupsOnly' => [
+ 'type' => 'check',
+ 'label' => $this->msg( 'listusers-temporarygroupsonly' )->text(),
+ 'name' => 'temporaryGroupsOnly',
+ 'id' => 'temporaryGroupsOnly',
+ 'default' => $this->temporaryGroupsOnly
+ ],
'creationSort' => [
'type' => 'check',
'label' => $this->msg( 'listusers-creationsort' )->text(),
'user_case_dbkey' => $dbkey,
];
- # Strip Unicode bidi override characters.
+ # Strip soft hyphens (U+00AD) and Unicode directional formatting characters (U+061C, U+200E,
+ # U+200F, U+202A. U+202B, U+202C, U+202D, U+202E, U+2066, U+2067, U+2068, U+2069).
# Sometimes they slip into cut-n-pasted page titles, where the
- # override chars get included in list displays.
- $dbkey = preg_replace( '/\xE2\x80[\x8E\x8F\xAA-\xAE]/S', '', $dbkey );
+ # soft hyphens or override chars get included in list displays.
+ $dbkey = preg_replace(
+ '/\xC2\xAD|\xD8\x9C|\xE2\x80[\x8E\x8F\xAA-\xAE]|\xE2\x81[\xA6-\xA9]/S',
+ '',
+ $dbkey
+ );
# Clean up whitespace
# Note: use of the /u option on preg_replace here will cause
'li' => 'Limburgs', # Limburgian
'lij' => 'Ligure', # Ligurian
'liv' => 'Līvõ kēļ', # Livonian
- 'lki' => 'لەکی', # Laki
+ 'lki' => 'لەکی', # Laki
'lmo' => 'lumbaart', # Lombard
'ln' => 'lingála', # Lingala
'lo' => 'ລາວ', # Laotian
"listusers": "User list",
"listusers-summary": "",
"listusers-editsonly": "Show only users with edits",
+ "listusers-temporarygroupsonly": "Show only users in temporary user groups",
"listusers-creationsort": "Sort by creation date",
"listusers-desc": "Sort in descending order",
"usereditcount": "$1 {{PLURAL:$1|edit|edits}}",
"whatlinkshere-title": "Pages that link to \"$1\"",
"whatlinkshere-summary": "",
"whatlinkshere-page": "Page:",
- "linkshere": "The following pages link to <strong>[[:$1]]</strong>:",
- "nolinkshere": "No pages link to <strong>[[:$1]]</strong>.",
- "nolinkshere-ns": "No pages link to <strong>[[:$1]]</strong> in the chosen namespace.",
+ "linkshere-2": "The following pages link to <strong>$1</strong>:",
+ "nolinkshere-2": "No pages link to <strong>$1</strong>.",
+ "nolinkshere-ns-2": "No pages link to <strong>$1</strong> in the chosen namespace.",
"isredirect": "redirect page",
"istemplate": "transclusion",
"isimage": "file link",
"listusers": "{{doc-special|ListUsers}}",
"listusers-summary": "{{notranslate}}\nThe summary displayed at the top of [[Special:Listusers]]. [[mw:Manual:Interface/Special pages summary|mw manual]].",
"listusers-editsonly": "Option in [[Special:ListUsers]].",
+ "listusers-temporarygroupsonly": "Option in [[Special:ListUsers]].",
"listusers-creationsort": "Option in [[Special:ListUsers]].",
"listusers-desc": "Used as label for the checkbox on [[Special:ListUsers]].",
"usereditcount": "Shown behind every username on [[Special:ListUsers]]. Parameters:\n* $1 - number of edits",
"whatlinkshere-title": "Title of the special page [[Special:WhatLinksHere]]. This page appears when you click on the 'What links here' button in the toolbox. $1 is the name of the page concerned.",
"whatlinkshere-summary": "{{doc-specialpagesummary|whatlinkshere}}",
"whatlinkshere-page": "{{Identical|Page}}",
- "linkshere": "This message is the header line of the [[Special:WhatLinksHere/$1]] page generated by clicking \"What links here\" in the sidebar toolbox.\n\nIt is followed by a navigation bar built using {{msg-mw|Viewprevnext}}.\n\nParameters:\n* $1 - page title",
- "nolinkshere": "Used in [[Special:WhatLinksHere]] if empty. Parameters:\n* $1 - page title\nSee also:\n* {{msg-mw|Nolinkshere-ns}}",
- "nolinkshere-ns": "Used in [[Special:WhatLinksHere]] if empty. Parameters:\n* $1 - page title\nSee also:\n* {{msg-mw|Nolinkshere}}",
+ "linkshere-2": "This message is the header line of the [[Special:WhatLinksHere/$1]] page generated by clicking \"What links here\" in the sidebar toolbox.\n\nIt is followed by a navigation bar built using {{msg-mw|Viewprevnext}}.\n\nParameters:\n* $1 - HTML link to the page.",
+ "nolinkshere-2": "Used in [[Special:WhatLinksHere]] if empty. Parameters:\n* $1 - HTML link to the page\nSee also:\n* {{msg-mw|Nolinkshere-ns-html}}",
+ "nolinkshere-ns-2": "Used in [[Special:WhatLinksHere]] if empty. Parameters:\n* $1 - HTML link to the page\nSee also:\n* {{msg-mw|Nolinkshere-html}}",
"isredirect": "Displayed in [[Special:WhatLinksHere]] (see [{{fullurl:Special:WhatLinksHere/Project:Translator|hidelinks=1}} Special:WhatLinksHere/Project:Translator] for example).\n\n{{Identical|Redirect page}}",
"istemplate": "Means that a page (a template, specifically) is used as <code><nowiki>{{Page name}}</nowiki></code>.\nDisplayed in [[Special:WhatLinksHere]] (see [[Special:WhatLinksHere/Template:New portal]] for example).\nIf you are not sure how to translate this term, think of something like \"inclusion\", \"embedding\", or \"insertion\".\n{{Identical|Transclusion}}",
"isimage": "This message is displayed on [[Special:WhatLinksHere]] for images. It means that the image is used on the page (as opposed to just being linked to like an non-image page).\n{{Identical|File link}}",
rWhitespace = /[ _\u00A0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]+/g,
// From MediaWikiTitleCodec::splitTitleString() in PHP
- rUnicodeBidi = /[\u200E\u200F\u202A-\u202E]/g,
+ rStripCharacters = /[\u00AD\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/g,
/**
* Slightly modified from Flinfo. Credit goes to Lupo and Flominator.
namespace = defaultNamespace === undefined ? NS_MAIN : defaultNamespace;
title = title
- // Strip Unicode bidi override characters
- .replace( rUnicodeBidi, '' )
+ // Strip soft hyphens and Unicode directional formatting characters
+ .replace( rStripCharacters, '' )
// Normalise whitespace to underscores and remove duplicates
.replace( rWhitespace, '_' )
// Trim underscores
# of the categories in wikitext
# Do not remove these characters in edits.
#
-# As part of the serialization, these bidi characters will get stripped.
+# As part of the serialization, these Unicode directional formatting characters will get stripped.
!! test
RTL (\u200f) and LTR (\u200e) markers around category tags should be stripped
!! options
*foo
footer
!! end
+
+!! test
+Check soft hyphens as entities (­) in displaytitle (T66528)
+!! options
+showtitle
+title=[[Lopadotemachoselachogaleokranioleipsanodrimhypotrimmatosilphioparaomelitokatakechymenokichlepikossyphophattoperisteralektryonoptekephalliokigklopeleiolagoiosiraiobaphetraganopterygon]]
+!! wikitext
+{{DISPLAYTITLE:Lopado­temacho­selacho­galeo­kranio­leipsano­drim­hypo­trimmato­silphio­parao­melito­katakechy­meno­kichl­epi­kossypho­phatto­perister­alektryon­opte­kephallio­kigklo­peleio­lagoio­siraio­baphe­tragano­pterygon}}
+!! html/php
+Lopado­temacho­selacho­galeo­kranio­leipsano­drim­hypo­trimmato­silphio­parao­melito­katakechy­meno­kichl­epi­kossypho­phatto­perister­alektryon­opte­kephallio­kigklo­peleio­lagoio­siraio­baphe­tragano­pterygon
+
+!! end
+
+!! test
+Check soft hyphens as Unicode characters (U+00AD) in displaytitle (T66528)
+!! options
+showtitle
+title=[[Lopadotemachoselachogaleokranioleipsanodrimhypotrimmatosilphioparaomelitokatakechymenokichlepikossyphophattoperisteralektryonoptekephalliokigklopeleiolagoiosiraiobaphetraganopterygon]]
+!! wikitext
+{{DISPLAYTITLE:Lopadotemachoselachogaleokranioleipsanodrimhypotrimmatosilphioparaomelitokatakechymenokichlepikossyphophattoperisteralektryonoptekephalliokigklopeleiolagoiosiraiobaphetraganopterygon}}
+!! html/php
+Lopadotemachoselachogaleokranioleipsanodrimhypotrimmatosilphioparaomelitokatakechymenokichlepikossyphophattoperisteralektryonoptekephalliokigklopeleiolagoiosiraiobaphetraganopterygon
+
+!! end
$context = self::makeContext();
$context->getResourceLoader()->register( self::makeSampleModules() );
- $client = new ResourceLoaderClientHtml( $context );
+ $client = new ResourceLoaderClientHtml( $context, [
+ 'nonce' => false,
+ ] );
$client->setConfig( [ 'key' => 'value' ] );
$client->setModules( [
'test',
// phpcs:enable
$expected = self::expandVariables( $expected );
- $this->assertEquals( $expected, $client->getHeadHtml( false ) );
+ $this->assertEquals( $expected, $client->getHeadHtml() );
}
/**
. '<script async="" src="/w/load.php?debug=false&lang=nl&modules=startup&only=scripts&skin=fallback&target=example"></script>';
// phpcs:enable
- $this->assertEquals( $expected, $client->getHeadHtml( false ) );
+ $this->assertEquals( $expected, $client->getHeadHtml() );
}
/**
. '<script async="" src="/w/load.php?debug=false&lang=nl&modules=startup&only=scripts&skin=fallback"></script>';
// phpcs:enable
- $this->assertEquals( $expected, $client->getHeadHtml( false ) );
+ $this->assertEquals( $expected, $client->getHeadHtml() );
}
/**
// names ending in "a" to be female.
[ NS_USER, 'Lisa_Müller', '', '', 'de', 'Benutzerin:Lisa Müller' ],
[ NS_MAIN, 'FooBar', '', 'remotetestiw', 'en', 'remotetestiw:FooBar' ],
+ // Strip soft hyphen and Unicode directional formatting characters
+ [ NS_MAIN, "Foo\xC2\xAD\xD8\x9C\xE2\x80\x8E\xE2\x80\x8F\xE2\x80\xAA\xE2\x80\xAB" .
+ "\xE2\x80\xAC\xE2\x80\xAD\xE2\x80\xAE\xE2\x81\xA6\xE2\x81\xA7" .
+ "\xE2\x81\xA8\xE2\x81\xA9bar", '', '', 'en',
+ "Foo\xC2\xAD\xD8\x9C\xE2\x80\x8E\xE2\x80\x8F\xE2\x80\xAA\xE2\x80\xAB" .
+ "\xE2\x80\xAC\xE2\x80\xAD\xE2\x80\xAE\xE2\x81\xA6\xE2\x81\xA7" .
+ "\xE2\x81\xA8\xE2\x81\xA9bar", 'Foobar' ],
];
}
title = new mw.Title( 'Foo \u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000 bar' );
assert.equal( title.getMain(), 'Foo_bar', 'Merge multiple types of whitespace/underscores into a single underscore' );
- title = new mw.Title( 'Foo\u200E\u200F\u202A\u202B\u202C\u202D\u202Ebar' );
- assert.equal( title.getMain(), 'Foobar', 'Strip Unicode bidi override characters' );
+ title = new mw.Title( 'Foo\u00AD\u061C\u200E\u200F\u202A\u202B\u202C\u202D\u202E\u2066\u2067\u2068\u2069bar' );
+ assert.equal( title.getMain(), 'Foobar', 'Strip soft hyphen and Unicode directional formatting characters' );
// Regression test: Previously it would only detect an extension if there is no space after it
title = new mw.Title( 'Example.js ' );
},
{
fileName: 'BI\u200EDI.jpg',
- typeOfName: 'Name containing BIDI overrides',
+ typeOfName: 'Name containing Unicode directional formatting characters',
nameText: 'BIDI',
prefixedText: 'File:BIDI.jpg'
},