Merge "Add LinkRenderer (rewrite of Linker::link())"
This commit is contained in:
commit
8b9584646b
13 changed files with 945 additions and 170 deletions
|
|
@ -557,6 +557,7 @@ $wgAutoloadLocalClasses = [
|
|||
'HistoryPager' => __DIR__ . '/includes/actions/HistoryAction.php',
|
||||
'Hooks' => __DIR__ . '/includes/Hooks.php',
|
||||
'Html' => __DIR__ . '/includes/Html.php',
|
||||
'HtmlArmor' => __DIR__ . '/includes/libs/HtmlArmor.php',
|
||||
'HtmlFormatter' => __DIR__ . '/includes/HtmlFormatter.php',
|
||||
'Http' => __DIR__ . '/includes/HttpFunctions.php',
|
||||
'HttpError' => __DIR__ . '/includes/exception/HttpError.php',
|
||||
|
|
@ -834,6 +835,8 @@ $wgAutoloadLocalClasses = [
|
|||
'MediaWiki\\Interwiki\\InterwikiLookup' => __DIR__ . '/includes/interwiki/InterwikiLookup.php',
|
||||
'MediaWiki\\Languages\\Data\\Names' => __DIR__ . '/languages/data/Names.php',
|
||||
'MediaWiki\\Languages\\Data\\ZhConversion' => __DIR__ . '/languages/data/ZhConversion.php',
|
||||
'MediaWiki\\Linker\\LinkRenderer' => __DIR__ . '/includes/linker/LinkRenderer.php',
|
||||
'MediaWiki\\Linker\\LinkRendererFactory' => __DIR__ . '/includes/linker/LinkRendererFactory.php',
|
||||
'MediaWiki\\Linker\\LinkTarget' => __DIR__ . '/includes/linker/LinkTarget.php',
|
||||
'MediaWiki\\Logger\\LegacyLogger' => __DIR__ . '/includes/debug/logger/LegacyLogger.php',
|
||||
'MediaWiki\\Logger\\LegacySpi' => __DIR__ . '/includes/debug/logger/LegacySpi.php',
|
||||
|
|
|
|||
|
|
@ -1783,7 +1783,8 @@ $title: The page's Title.
|
|||
$out: The output page.
|
||||
$cssClassName: CSS class name of the language selector.
|
||||
|
||||
'LinkBegin': Used when generating internal and interwiki links in
|
||||
'LinkBegin': DEPRECATED! Use HtmlPageLinkRendererBegin instead.
|
||||
Used when generating internal and interwiki links in
|
||||
Linker::link(), before processing starts. Return false to skip default
|
||||
processing and return $ret. See documentation for Linker::link() for details on
|
||||
the expected meanings of parameters.
|
||||
|
|
@ -1800,7 +1801,8 @@ $target: the Title that the link is pointing to
|
|||
&$options: array of options. Can include 'known', 'broken', 'noclasses'.
|
||||
&$ret: the value to return if your hook returns false.
|
||||
|
||||
'LinkEnd': Used when generating internal and interwiki links in Linker::link(),
|
||||
'LinkEnd': DEPRECATED! Use HtmlPageLinkRendererEnd hook instead
|
||||
Used when generating internal and interwiki links in Linker::link(),
|
||||
just before the function returns a value. If you return true, an <a> element
|
||||
with HTML attributes $attribs and contents $html will be returned. If you
|
||||
return false, $ret will be returned.
|
||||
|
|
@ -1835,6 +1837,35 @@ $file: the File object or false if broken link
|
|||
&$attribs: the attributes to be applied
|
||||
&$ret: the value to return if your hook returns false
|
||||
|
||||
'LinkRendererBegin':
|
||||
Used when generating internal and interwiki links in
|
||||
LinkRenderer, before processing starts. Return false to skip default
|
||||
processing and return $ret.
|
||||
$linkRenderer: the LinkRenderer object
|
||||
$target: the LinkTarget that the link is pointing to
|
||||
&$html: the contents that the <a> tag should have (raw HTML); null means
|
||||
"default".
|
||||
&$customAttribs: the HTML attributes that the <a> tag should have, in
|
||||
associative array form, with keys and values unescaped. Should be merged
|
||||
with default values, with a value of false meaning to suppress the
|
||||
attribute.
|
||||
&$query: the query string to add to the generated URL (the bit after the "?"),
|
||||
in associative array form, with keys and values unescaped.
|
||||
&$ret: the value to return if your hook returns false.
|
||||
|
||||
'LinkRendererEnd':
|
||||
Used when generating internal and interwiki links in LinkRenderer,
|
||||
just before the function returns a value. If you return true, an <a> element
|
||||
with HTML attributes $attribs and contents $html will be returned. If you
|
||||
return false, $ret will be returned.
|
||||
$linkRenderer: the LinkRenderer object
|
||||
$target: the LinkTarget object that the link is pointing to
|
||||
$isKnown: boolean indicating whether the page is known or not
|
||||
&$html: the final (raw HTML) contents of the <a> tag, after processing.
|
||||
&$attribs: the final HTML attributes of the <a> tag, after processing, in
|
||||
associative array form.
|
||||
&$ret: the value to return if your hook returns false.
|
||||
|
||||
'LinksUpdate': At the beginning of LinksUpdate::doUpdate() just before the
|
||||
actual update.
|
||||
&$linksUpdate: the LinksUpdate object
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
* @file
|
||||
*/
|
||||
use MediaWiki\Linker\LinkTarget;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
|
||||
/**
|
||||
* Some internal bits split of from Skin.php. These functions are used
|
||||
|
|
@ -210,55 +211,33 @@ class Linker {
|
|||
wfDeprecated( __METHOD__ . ' with parameter $query as string (should be array)', '1.20' );
|
||||
$query = wfCgiToArray( $query );
|
||||
}
|
||||
|
||||
$services = MediaWikiServices::getInstance();
|
||||
$options = (array)$options;
|
||||
|
||||
$dummy = new DummyLinker; // dummy linker instance for bc on the hooks
|
||||
|
||||
$ret = null;
|
||||
if ( !Hooks::run( 'LinkBegin',
|
||||
[ $dummy, $target, &$html, &$customAttribs, &$query, &$options, &$ret ] )
|
||||
) {
|
||||
return $ret;
|
||||
}
|
||||
|
||||
# Normalize the Title if it's a special page
|
||||
$target = self::normaliseSpecialPage( $target );
|
||||
|
||||
# If we don't know whether the page exists, let's find out.
|
||||
if ( !in_array( 'known', $options, true ) && !in_array( 'broken', $options, true ) ) {
|
||||
if ( $target->isKnown() ) {
|
||||
$options[] = 'known';
|
||||
} else {
|
||||
$options[] = 'broken';
|
||||
if ( $options ) {
|
||||
// Custom options, create new LinkRenderer
|
||||
if ( !isset( $options['stubThreshold'] ) ) {
|
||||
global $wgUser;
|
||||
$options['stubThreshold'] = $wgUser->getStubThreshold();
|
||||
}
|
||||
$linkRenderer = $services->getLinkRendererFactory()
|
||||
->createFromLegacyOptions( $options );
|
||||
} else {
|
||||
$linkRenderer = $services->getLinkRenderer();
|
||||
}
|
||||
|
||||
$oldquery = [];
|
||||
if ( in_array( "forcearticlepath", $options, true ) && $query ) {
|
||||
$oldquery = $query;
|
||||
$query = [];
|
||||
if ( $html !== null ) {
|
||||
$text = new HtmlArmor( $html );
|
||||
} else {
|
||||
$text = $html; // null
|
||||
}
|
||||
|
||||
# Note: we want the href attribute first, for prettiness.
|
||||
$attribs = [ 'href' => self::linkUrl( $target, $query, $options ) ];
|
||||
if ( in_array( 'forcearticlepath', $options, true ) && $oldquery ) {
|
||||
$attribs['href'] = wfAppendQuery( $attribs['href'], $oldquery );
|
||||
if ( in_array( 'known', $options, true ) ) {
|
||||
return $linkRenderer->makeKnownLink( $target, $text, $customAttribs, $query );
|
||||
} elseif ( in_array( 'broken', $options, true ) ) {
|
||||
return $linkRenderer->makeBrokenLink( $target, $text, $customAttribs, $query );
|
||||
} else {
|
||||
return $linkRenderer->makeLink( $target, $text, $customAttribs, $query );
|
||||
}
|
||||
|
||||
$attribs = array_merge(
|
||||
$attribs,
|
||||
self::linkAttribs( $target, $customAttribs, $options )
|
||||
);
|
||||
if ( is_null( $html ) ) {
|
||||
$html = self::linkText( $target );
|
||||
}
|
||||
|
||||
$ret = null;
|
||||
if ( Hooks::run( 'LinkEnd', [ $dummy, $target, $options, &$html, &$attribs, &$ret ] ) ) {
|
||||
$ret = Html::rawElement( 'a', $attribs, $html );
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -274,130 +253,6 @@ class Linker {
|
|||
return self::link( $target, $html, $customAttribs, $query, $options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Url used to link to a Title
|
||||
*
|
||||
* @param LinkTarget $target
|
||||
* @param array $query Query parameters
|
||||
* @param array $options
|
||||
* @return string
|
||||
*/
|
||||
private static function linkUrl( LinkTarget $target, $query, $options ) {
|
||||
# We don't want to include fragments for broken links, because they
|
||||
# generally make no sense.
|
||||
if ( in_array( 'broken', $options, true ) && $target->hasFragment() ) {
|
||||
$target = $target->createFragmentTarget( '' );
|
||||
}
|
||||
|
||||
# If it's a broken link, add the appropriate query pieces, unless
|
||||
# there's already an action specified, or unless 'edit' makes no sense
|
||||
# (i.e., for a nonexistent special page).
|
||||
if ( in_array( 'broken', $options, true ) && empty( $query['action'] )
|
||||
&& $target->getNamespace() !== NS_SPECIAL ) {
|
||||
$query['action'] = 'edit';
|
||||
$query['redlink'] = '1';
|
||||
}
|
||||
|
||||
if ( in_array( 'http', $options, true ) ) {
|
||||
$proto = PROTO_HTTP;
|
||||
} elseif ( in_array( 'https', $options, true ) ) {
|
||||
$proto = PROTO_HTTPS;
|
||||
} else {
|
||||
$proto = PROTO_RELATIVE;
|
||||
}
|
||||
|
||||
$title = Title::newFromLinkTarget( $target );
|
||||
$ret = $title->getLinkURL( $query, false, $proto );
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array of attributes used when linking to the Title $target
|
||||
*
|
||||
* @param Title $target
|
||||
* @param array $attribs
|
||||
* @param array $options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function linkAttribs( $target, $attribs, $options ) {
|
||||
global $wgUser;
|
||||
$defaults = [];
|
||||
|
||||
if ( !in_array( 'noclasses', $options, true ) ) {
|
||||
# Now build the classes.
|
||||
$classes = [];
|
||||
|
||||
if ( in_array( 'broken', $options, true ) ) {
|
||||
$classes[] = 'new';
|
||||
}
|
||||
|
||||
if ( $target->isExternal() ) {
|
||||
$classes[] = 'extiw';
|
||||
}
|
||||
|
||||
if ( !in_array( 'broken', $options, true ) ) { # Avoid useless calls to LinkCache (see r50387)
|
||||
$colour = self::getLinkColour(
|
||||
$target,
|
||||
isset( $options['stubThreshold'] ) ? $options['stubThreshold'] : $wgUser->getStubThreshold()
|
||||
);
|
||||
if ( $colour !== '' ) {
|
||||
$classes[] = $colour; # mw-redirect or stub
|
||||
}
|
||||
}
|
||||
if ( $classes != [] ) {
|
||||
$defaults['class'] = implode( ' ', $classes );
|
||||
}
|
||||
}
|
||||
|
||||
# Get a default title attribute.
|
||||
if ( $target->getPrefixedText() == '' ) {
|
||||
# A link like [[#Foo]]. This used to mean an empty title
|
||||
# attribute, but that's silly. Just don't output a title.
|
||||
} elseif ( in_array( 'known', $options, true ) ) {
|
||||
$defaults['title'] = $target->getPrefixedText();
|
||||
} else {
|
||||
// This ends up in parser cache!
|
||||
$defaults['title'] = wfMessage( 'red-link-title', $target->getPrefixedText() )
|
||||
->inContentLanguage()
|
||||
->text();
|
||||
}
|
||||
|
||||
# Finally, merge the custom attribs with the default ones, and iterate
|
||||
# over that, deleting all "false" attributes.
|
||||
$ret = [];
|
||||
$merged = Sanitizer::mergeAttributes( $defaults, $attribs );
|
||||
foreach ( $merged as $key => $val ) {
|
||||
# A false value suppresses the attribute, and we don't want the
|
||||
# href attribute to be overridden.
|
||||
if ( $key != 'href' && $val !== false ) {
|
||||
$ret[$key] = $val;
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default text of the links to the Title $target
|
||||
*
|
||||
* @param Title $target
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function linkText( $target ) {
|
||||
if ( !$target instanceof Title ) {
|
||||
wfWarn( __METHOD__ . ': Requires $target to be a Title object.' );
|
||||
return '';
|
||||
}
|
||||
// If the target is just a fragment, with no title, we return the fragment
|
||||
// text. Otherwise, we return the title text itself.
|
||||
if ( $target->getPrefixedText() === '' && $target->hasFragment() ) {
|
||||
return htmlspecialchars( $target->getFragment() );
|
||||
}
|
||||
|
||||
return htmlspecialchars( $target->getPrefixedText() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Make appropriate markup for a link to the current article. This is
|
||||
* currently rendered as the bold link text. The calling sequence is the
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ use LBFactory;
|
|||
use LinkCache;
|
||||
use Liuggio\StatsdClient\Factory\StatsdDataFactory;
|
||||
use LoadBalancer;
|
||||
use MediaWiki\Linker\LinkRenderer;
|
||||
use MediaWiki\Linker\LinkRendererFactory;
|
||||
use MediaWiki\Services\SalvageableService;
|
||||
use MediaWiki\Services\ServiceContainer;
|
||||
use MWException;
|
||||
|
|
@ -516,6 +518,25 @@ class MediaWikiServices extends ServiceContainer {
|
|||
return $this->getService( 'LinkCache' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.28
|
||||
* @return LinkRendererFactory
|
||||
*/
|
||||
public function getLinkRendererFactory() {
|
||||
return $this->getService( 'LinkRendererFactory' );
|
||||
}
|
||||
|
||||
/**
|
||||
* LinkRenderer instance that can be used
|
||||
* if no custom options are needed
|
||||
*
|
||||
* @since 1.28
|
||||
* @return LinkRenderer
|
||||
*/
|
||||
public function getLinkRenderer() {
|
||||
return $this->getService( 'LinkRenderer' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.28
|
||||
* @return TitleFormatter
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@
|
|||
*/
|
||||
|
||||
use MediaWiki\Interwiki\ClassicInterwikiLookup;
|
||||
use MediaWiki\Linker\LinkRenderer;
|
||||
use MediaWiki\Linker\LinkRendererFactory;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
|
||||
return [
|
||||
|
|
@ -159,6 +161,18 @@ return [
|
|||
);
|
||||
},
|
||||
|
||||
'LinkRendererFactory' => function( MediaWikiServices $services ) {
|
||||
return new LinkRendererFactory(
|
||||
$services->getTitleFormatter()
|
||||
);
|
||||
},
|
||||
|
||||
'LinkRenderer' => function( MediaWikiServices $services ) {
|
||||
global $wgUser;
|
||||
|
||||
return $services->getLinkRendererFactory()->createForUser( $wgUser );
|
||||
},
|
||||
|
||||
'GenderCache' => function( MediaWikiServices $services ) {
|
||||
return new GenderCache();
|
||||
},
|
||||
|
|
|
|||
56
includes/libs/HtmlArmor.php
Normal file
56
includes/libs/HtmlArmor.php
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
/**
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*
|
||||
* @file
|
||||
* @license GPL-2.0+
|
||||
* @author Kunal Mehta <legoktm@member.fsf.org>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Marks HTML that shouldn't be escaped
|
||||
*
|
||||
* @since 1.28
|
||||
*/
|
||||
class HtmlArmor {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
*/
|
||||
public function __construct( $value ) {
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a string or HtmlArmor object
|
||||
* and get safe HTML back
|
||||
*
|
||||
* @param string|HtmlArmor $input
|
||||
* @return string safe for usage in HTML
|
||||
*/
|
||||
public static function getHtml( $input ) {
|
||||
if ( $input instanceof HtmlArmor ) {
|
||||
return $input->value;
|
||||
} else {
|
||||
return htmlspecialchars( $input );
|
||||
}
|
||||
}
|
||||
}
|
||||
451
includes/linker/LinkRenderer.php
Normal file
451
includes/linker/LinkRenderer.php
Normal file
|
|
@ -0,0 +1,451 @@
|
|||
<?php
|
||||
/**
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*
|
||||
* @file
|
||||
* @license GPL-2.0+
|
||||
* @author Kunal Mehta <legoktm@member.fsf.org>
|
||||
*/
|
||||
namespace MediaWiki\Linker;
|
||||
|
||||
use DummyLinker;
|
||||
use Hooks;
|
||||
use Html;
|
||||
use HtmlArmor;
|
||||
use Linker;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
use Sanitizer;
|
||||
use Title;
|
||||
use TitleFormatter;
|
||||
|
||||
/**
|
||||
* Class that generates HTML <a> links for pages.
|
||||
*
|
||||
* @since 1.28
|
||||
*/
|
||||
class LinkRenderer {
|
||||
|
||||
/**
|
||||
* Whether to force the pretty article path
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $forceArticlePath = false;
|
||||
|
||||
/**
|
||||
* A PROTO_* constant or false
|
||||
*
|
||||
* @var string|bool|int
|
||||
*/
|
||||
private $expandUrls = false;
|
||||
|
||||
/**
|
||||
* Whether extra classes should be added
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $noClasses = false;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $stubThreshold = 0;
|
||||
|
||||
/**
|
||||
* @var TitleFormatter
|
||||
*/
|
||||
private $titleFormatter;
|
||||
|
||||
/**
|
||||
* Whether to run the legacy Linker hooks
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $runLegacyBeginHook = true;
|
||||
|
||||
/**
|
||||
* @param TitleFormatter $titleFormatter
|
||||
*/
|
||||
public function __construct( TitleFormatter $titleFormatter ) {
|
||||
$this->titleFormatter = $titleFormatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $force
|
||||
*/
|
||||
public function setForceArticlePath( $force ) {
|
||||
$this->forceArticlePath = $force;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getForceArticlePath() {
|
||||
return $this->forceArticlePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|bool|int $expand A PROTO_* constant or false
|
||||
*/
|
||||
public function setExpandURLs( $expand ) {
|
||||
$this->expandUrls = $expand;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|bool|int a PROTO_* constant or false
|
||||
*/
|
||||
public function getExpandURLs() {
|
||||
return $this->expandUrls;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $no
|
||||
*/
|
||||
public function setNoClasses( $no ) {
|
||||
$this->noClasses = $no;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getNoClasses() {
|
||||
return $this->noClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $threshold
|
||||
*/
|
||||
public function setStubThreshold( $threshold ) {
|
||||
$this->stubThreshold = $threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getStubThreshold() {
|
||||
return $this->stubThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $run
|
||||
*/
|
||||
public function setRunLegacyBeginHook( $run ) {
|
||||
$this->runLegacyBeginHook = $run;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinkTarget $target
|
||||
* @param string|HtmlArmor|null $text
|
||||
* @param array $extraAttribs
|
||||
* @param array $query
|
||||
* @return string
|
||||
*/
|
||||
public function makeLink(
|
||||
LinkTarget $target, $text = null, array $extraAttribs = [], array $query = []
|
||||
) {
|
||||
$title = Title::newFromLinkTarget( $target );
|
||||
if ( $title->isKnown() ) {
|
||||
return $this->makeKnownLink( $target, $text, $extraAttribs, $query );
|
||||
} else {
|
||||
return $this->makeBrokenLink( $target, $text, $extraAttribs, $query );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the options in the legacy format
|
||||
*
|
||||
* @param bool $isKnown Whether the link is known or broken
|
||||
* @return array
|
||||
*/
|
||||
private function getLegacyOptions( $isKnown ) {
|
||||
$options = [ 'stubThreshold' => $this->stubThreshold ];
|
||||
if ( $this->noClasses ) {
|
||||
$options[] = 'noclasses';
|
||||
}
|
||||
if ( $this->forceArticlePath ) {
|
||||
$options[] = 'forcearticlepath';
|
||||
}
|
||||
if ( $this->expandUrls === PROTO_HTTP ) {
|
||||
$options[] = 'http';
|
||||
} elseif ( $this->expandUrls === PROTO_HTTPS ) {
|
||||
$options[] = 'https';
|
||||
}
|
||||
|
||||
$options[] = $isKnown ? 'known' : 'broken';
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
private function runBeginHook( LinkTarget $target, &$text, &$extraAttribs, &$query, $isKnown ) {
|
||||
$ret = null;
|
||||
if ( !Hooks::run( 'HtmlPageLinkRendererBegin',
|
||||
[ $this, $target, &$text, &$extraAttribs, &$query, &$ret ] )
|
||||
) {
|
||||
return $ret;
|
||||
}
|
||||
|
||||
// Now run the legacy hook
|
||||
return $this->runLegacyBeginHook( $target, $text, $extraAttribs, $query, $isKnown );
|
||||
}
|
||||
|
||||
private function runLegacyBeginHook( LinkTarget $target, &$text, &$extraAttribs, &$query,
|
||||
$isKnown
|
||||
) {
|
||||
if ( !$this->runLegacyBeginHook || !Hooks::isRegistered( 'LinkBegin' ) ) {
|
||||
// Disabled, or nothing registered
|
||||
return null;
|
||||
}
|
||||
|
||||
$realOptions = $options = $this->getLegacyOptions( $isKnown );
|
||||
$ret = null;
|
||||
$dummy = new DummyLinker();
|
||||
$title = Title::newFromLinkTarget( $target );
|
||||
$realHtml = $html = HtmlArmor::getHtml( $text );
|
||||
if ( !Hooks::run( 'LinkBegin',
|
||||
[ $dummy, $title, &$html, &$extraAttribs, &$query, &$options, &$ret ] )
|
||||
) {
|
||||
return $ret;
|
||||
}
|
||||
|
||||
if ( $html !== null && $html !== $realHtml ) {
|
||||
// &$html was modified, so re-armor it as $text
|
||||
$text = new HtmlArmor( $html );
|
||||
}
|
||||
|
||||
// Check if they changed any of the options, hopefully not!
|
||||
if ( $options !== $realOptions ) {
|
||||
$factory = MediaWikiServices::getInstance()->getLinkRendererFactory();
|
||||
// They did, so create a separate instance and have that take over the rest
|
||||
$newRenderer = $factory->createFromLegacyOptions( $options );
|
||||
// Don't recurse the hook...
|
||||
$newRenderer->setRunLegacyBeginHook( false );
|
||||
if ( in_array( 'known', $options, true ) ) {
|
||||
return $newRenderer->makeKnownLink( $title, $text, $extraAttribs, $query );
|
||||
} elseif ( in_array( 'broken', $options, true ) ) {
|
||||
return $newRenderer->makeBrokenLink( $title, $text, $extraAttribs, $query );
|
||||
} else {
|
||||
return $newRenderer->makeLink( $title, $text, $extraAttribs, $query );
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinkTarget $target
|
||||
* @param string|HtmlArmor|null $text
|
||||
* @param array $extraAttribs
|
||||
* @param array $query
|
||||
* @return string
|
||||
*/
|
||||
public function makeKnownLink(
|
||||
LinkTarget $target, $text = null, array $extraAttribs = [], array $query = []
|
||||
) {
|
||||
// Run begin hook
|
||||
$ret = $this->runBeginHook( $target, $text, $extraAttribs, $query, true );
|
||||
if ( $ret !== null ) {
|
||||
return $ret;
|
||||
}
|
||||
$target = $this->normalizeTarget( $target );
|
||||
$url = $this->getLinkURL( $target, $query );
|
||||
$attribs = [];
|
||||
if ( !$this->noClasses ) {
|
||||
$classes = [];
|
||||
if ( $target->isExternal() ) {
|
||||
$classes[] = 'extiw';
|
||||
}
|
||||
$title = Title::newFromLinkTarget( $target );
|
||||
$colour = Linker::getLinkColour( $title, $this->stubThreshold );
|
||||
if ( $colour !== '' ) {
|
||||
$classes[] = $colour;
|
||||
}
|
||||
if ( $classes ) {
|
||||
$attribs['class'] = implode( ' ', $classes );
|
||||
}
|
||||
}
|
||||
|
||||
$prefixedText = $this->titleFormatter->getPrefixedText( $target );
|
||||
if ( $prefixedText !== '' ) {
|
||||
$attribs['title'] = $prefixedText;
|
||||
}
|
||||
|
||||
$attribs = [
|
||||
'href' => $url,
|
||||
] + $this->mergeAttribs( $attribs, $extraAttribs );
|
||||
|
||||
if ( $text === null ) {
|
||||
$text = $this->getLinkText( $target );
|
||||
}
|
||||
|
||||
return $this->buildAElement( $target, $text, $attribs, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinkTarget $target
|
||||
* @param string|HtmlArmor|null $text
|
||||
* @param array $extraAttribs
|
||||
* @param array $query
|
||||
* @return string
|
||||
*/
|
||||
public function makeBrokenLink(
|
||||
LinkTarget $target, $text = null, array $extraAttribs = [], array $query = []
|
||||
) {
|
||||
// Run legacy hook
|
||||
$ret = $this->runBeginHook( $target, $text, $extraAttribs, $query, false );
|
||||
if ( $ret !== null ) {
|
||||
return $ret;
|
||||
}
|
||||
|
||||
# We don't want to include fragments for broken links, because they
|
||||
# generally make no sense.
|
||||
if ( $target->hasFragment() ) {
|
||||
$target = $target->createFragmentTarget( '' );
|
||||
}
|
||||
$target = $this->normalizeTarget( $target );
|
||||
|
||||
if ( !isset( $query['action'] ) && $target->getNamespace() !== NS_SPECIAL ) {
|
||||
$query['action'] = 'edit';
|
||||
$query['redlink'] = '1';
|
||||
}
|
||||
|
||||
$url = $this->getLinkURL( $target, $query );
|
||||
$attribs = $this->noClasses ? [] : [ 'class' => 'new' ];
|
||||
$prefixedText = $this->titleFormatter->getPrefixedText( $target );
|
||||
if ( $prefixedText !== '' ) {
|
||||
// This ends up in parser cache!
|
||||
$attribs['title'] = wfMessage( 'red-link-title', $prefixedText )
|
||||
->inContentLanguage()
|
||||
->text();
|
||||
}
|
||||
|
||||
$attribs = [
|
||||
'href' => $url,
|
||||
] + $this->mergeAttribs( $attribs, $extraAttribs );
|
||||
|
||||
if ( $text === null ) {
|
||||
$text = $this->getLinkText( $target );
|
||||
}
|
||||
|
||||
return $this->buildAElement( $target, $text, $attribs, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the final <a> element
|
||||
*
|
||||
* @param LinkTarget $target
|
||||
* @param string|HtmlArmor $text
|
||||
* @param array $attribs
|
||||
* @param bool $isKnown
|
||||
* @return null|string
|
||||
*/
|
||||
private function buildAElement( LinkTarget $target, $text, array $attribs, $isKnown ) {
|
||||
$ret = null;
|
||||
if ( !Hooks::run( 'HtmlPageLinkRendererEnd',
|
||||
[ $this, $target, $isKnown, &$text, &$attribs, &$ret ] )
|
||||
) {
|
||||
return $ret;
|
||||
}
|
||||
|
||||
$html = HtmlArmor::getHtml( $text );
|
||||
|
||||
// Run legacy hook
|
||||
if ( Hooks::isRegistered( 'LinkEnd' ) ) {
|
||||
$dummy = new DummyLinker();
|
||||
$title = Title::newFromLinkTarget( $target );
|
||||
$options = $this->getLegacyOptions( $isKnown );
|
||||
if ( !Hooks::run( 'LinkEnd',
|
||||
[ $dummy, $title, $options, &$html, &$attribs, &$ret ] )
|
||||
) {
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
|
||||
return Html::rawElement( 'a', $attribs, $html );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinkTarget $target
|
||||
* @return string non-escaped text
|
||||
*/
|
||||
private function getLinkText( LinkTarget $target ) {
|
||||
$prefixedText = $this->titleFormatter->getPrefixedText( $target );
|
||||
// If the target is just a fragment, with no title, we return the fragment
|
||||
// text. Otherwise, we return the title text itself.
|
||||
if ( $prefixedText === '' && $target->hasFragment() ) {
|
||||
return $target->getFragment();
|
||||
}
|
||||
|
||||
return $prefixedText;
|
||||
}
|
||||
|
||||
private function getLinkURL( LinkTarget $target, array $query = [] ) {
|
||||
// TODO: Use a LinkTargetResolver service instead of Title
|
||||
$title = Title::newFromLinkTarget( $target );
|
||||
$proto = $this->expandUrls !== false
|
||||
? $this->expandUrls
|
||||
: PROTO_RELATIVE;
|
||||
if ( $this->forceArticlePath ) {
|
||||
$realQuery = $query;
|
||||
$query = [];
|
||||
} else {
|
||||
$realQuery = [];
|
||||
}
|
||||
$url = $title->getLinkURL( $query, false, $proto );
|
||||
|
||||
if ( $this->forceArticlePath && $realQuery ) {
|
||||
$url = wfAppendQuery( $url, $realQuery );
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the provided target
|
||||
*
|
||||
* @todo move the code from Linker actually here
|
||||
* @param LinkTarget $target
|
||||
* @return LinkTarget
|
||||
*/
|
||||
private function normalizeTarget( LinkTarget $target ) {
|
||||
return Linker::normaliseSpecialPage( $target );
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges two sets of attributes
|
||||
*
|
||||
* @param array $defaults
|
||||
* @param array $attribs
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function mergeAttribs( $defaults, $attribs ) {
|
||||
if ( !$attribs ) {
|
||||
return $defaults;
|
||||
}
|
||||
# Merge the custom attribs with the default ones, and iterate
|
||||
# over that, deleting all "false" attributes.
|
||||
$ret = [];
|
||||
$merged = Sanitizer::mergeAttributes( $defaults, $attribs );
|
||||
foreach ( $merged as $key => $val ) {
|
||||
# A false value suppresses the attribute
|
||||
if ( $val !== false ) {
|
||||
$ret[$key] = $val;
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
}
|
||||
92
includes/linker/LinkRendererFactory.php
Normal file
92
includes/linker/LinkRendererFactory.php
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
/**
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*
|
||||
* @file
|
||||
* @license GPL-2.0+
|
||||
* @author Kunal Mehta <legoktm@member.fsf.org>
|
||||
*/
|
||||
namespace MediaWiki\Linker;
|
||||
|
||||
use TitleFormatter;
|
||||
use User;
|
||||
|
||||
/**
|
||||
* Factory to create LinkRender objects
|
||||
* @since 1.28
|
||||
*/
|
||||
class LinkRendererFactory {
|
||||
|
||||
/**
|
||||
* @var TitleFormatter
|
||||
*/
|
||||
private $titleFormatter;
|
||||
|
||||
/**
|
||||
* @param TitleFormatter $titleFormatter
|
||||
*/
|
||||
public function __construct( TitleFormatter $titleFormatter ) {
|
||||
$this->titleFormatter = $titleFormatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LinkRenderer
|
||||
*/
|
||||
public function create() {
|
||||
return new LinkRenderer( $this->titleFormatter );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @return LinkRenderer
|
||||
*/
|
||||
public function createForUser( User $user ) {
|
||||
$linkRenderer = $this->create();
|
||||
$linkRenderer->setStubThreshold( $user->getStubThreshold() );
|
||||
|
||||
return $linkRenderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @return LinkRenderer
|
||||
*/
|
||||
public function createFromLegacyOptions( array $options ) {
|
||||
$linkRenderer = $this->create();
|
||||
|
||||
if ( in_array( 'noclasses', $options, true ) ) {
|
||||
$linkRenderer->setNoClasses( true );
|
||||
}
|
||||
|
||||
if ( in_array( 'forcearticlepath', $options, true ) ) {
|
||||
$linkRenderer->setForceArticlePath( true );
|
||||
}
|
||||
|
||||
if ( in_array( 'http', $options, true ) ) {
|
||||
$linkRenderer->setExpandURLs( PROTO_HTTP );
|
||||
} elseif ( in_array( 'https', $options, true ) ) {
|
||||
$linkRenderer->setExpandURLs( PROTO_HTTPS );
|
||||
}
|
||||
|
||||
if ( isset( $options['stubThreshold'] ) ) {
|
||||
$linkRenderer->setStubThreshold(
|
||||
$options['stubThreshold']
|
||||
);
|
||||
}
|
||||
|
||||
return $linkRenderer;
|
||||
}
|
||||
}
|
||||
|
|
@ -342,7 +342,8 @@ class ParserTest {
|
|||
$services->resetServiceForTesting( 'TitleFormatter' );
|
||||
$services->resetServiceForTesting( 'TitleParser' );
|
||||
$services->resetServiceForTesting( '_MediaWikiTitleCodec' );
|
||||
|
||||
$services->resetServiceForTesting( 'LinkRenderer' );
|
||||
$services->resetServiceForTesting( 'LinkRendererFactory' );
|
||||
}
|
||||
|
||||
public function setupRecorder( $options ) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
use Liuggio\StatsdClient\Factory\StatsdDataFactory;
|
||||
use MediaWiki\Interwiki\InterwikiLookup;
|
||||
use MediaWiki\Linker\LinkRenderer;
|
||||
use MediaWiki\Linker\LinkRendererFactory;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
use MediaWiki\Services\DestructibleService;
|
||||
use MediaWiki\Services\SalvageableService;
|
||||
|
|
@ -315,6 +317,8 @@ class MediaWikiServicesTest extends MediaWikiTestCase {
|
|||
'WatchedItemStore' => [ 'WatchedItemStore', WatchedItemStore::class ],
|
||||
'GenderCache' => [ 'GenderCache', GenderCache::class ],
|
||||
'LinkCache' => [ 'LinkCache', LinkCache::class ],
|
||||
'LinkRenderer' => [ 'LinkRenderer', LinkRenderer::class ],
|
||||
'LinkRendererFactory' => [ 'LinkRendererFactory', LinkRendererFactory::class ],
|
||||
'_MediaWikiTitleCodec' => [ '_MediaWikiTitleCodec', MediaWikiTitleCodec::class ],
|
||||
'TitleFormatter' => [ 'TitleFormatter', TitleFormatter::class ],
|
||||
'TitleParser' => [ 'TitleParser', TitleParser::class ],
|
||||
|
|
|
|||
34
tests/phpunit/includes/libs/HtmlArmorTest.php
Normal file
34
tests/phpunit/includes/libs/HtmlArmorTest.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @covers HtmlArmor
|
||||
*/
|
||||
class HtmlArmorTest extends PHPUnit_Framework_TestCase {
|
||||
|
||||
public static function provideHtmlArmor() {
|
||||
return [
|
||||
[
|
||||
'foobar',
|
||||
'foobar',
|
||||
],
|
||||
[
|
||||
'<script>alert("evil!");</script>',
|
||||
'<script>alert("evil!");</script>',
|
||||
],
|
||||
[
|
||||
new HtmlArmor( '<script>alert("evil!");</script>' ),
|
||||
'<script>alert("evil!");</script>',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideHtmlArmor
|
||||
*/
|
||||
public function testHtmlArmor( $input, $expected ) {
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
HtmlArmor::getHtml( $input )
|
||||
);
|
||||
}
|
||||
}
|
||||
79
tests/phpunit/includes/linker/LinkRendererFactoryTest.php
Normal file
79
tests/phpunit/includes/linker/LinkRendererFactoryTest.php
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
use MediaWiki\Linker\LinkRenderer;
|
||||
use MediaWiki\Linker\LinkRendererFactory;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
|
||||
/**
|
||||
* @covers LinkRendererFactory
|
||||
*/
|
||||
class LinkRendererFactoryTest extends MediaWikiLangTestCase {
|
||||
|
||||
/**
|
||||
* @var TitleFormatter
|
||||
*/
|
||||
private $titleFormatter;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
$this->titleFormatter = MediaWikiServices::getInstance()->getTitleFormatter();
|
||||
}
|
||||
|
||||
public static function provideCreateFromLegacyOptions() {
|
||||
return [
|
||||
[
|
||||
[ 'noclasses' ],
|
||||
'getNoClasses',
|
||||
true
|
||||
],
|
||||
[
|
||||
[ 'forcearticlepath' ],
|
||||
'getForceArticlePath',
|
||||
true
|
||||
],
|
||||
[
|
||||
[ 'http' ],
|
||||
'getExpandURLs',
|
||||
PROTO_HTTP
|
||||
],
|
||||
[
|
||||
[ 'https' ],
|
||||
'getExpandURLs',
|
||||
PROTO_HTTPS
|
||||
],
|
||||
[
|
||||
[ 'stubThreshold' => 150 ],
|
||||
'getStubThreshold',
|
||||
150
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideCreateFromLegacyOptions
|
||||
*/
|
||||
public function testCreateFromLegacyOptions( $options, $func, $val ) {
|
||||
$factory = new LinkRendererFactory( $this->titleFormatter );
|
||||
$linkRenderer = $factory->createFromLegacyOptions(
|
||||
$options
|
||||
);
|
||||
$this->assertInstanceOf( LinkRenderer::class, $linkRenderer );
|
||||
$this->assertEquals( $val, $linkRenderer->$func(), $func );
|
||||
}
|
||||
|
||||
public function testCreate() {
|
||||
$factory = new LinkRendererFactory( $this->titleFormatter );
|
||||
$this->assertInstanceOf( LinkRenderer::class, $factory->create() );
|
||||
}
|
||||
|
||||
public function testCreateForUser() {
|
||||
$user = $this->getMock( User::class, [ 'getStubThreshold' ] );
|
||||
$user->expects( $this->once() )
|
||||
->method( 'getStubThreshold' )
|
||||
->willReturn( 15 );
|
||||
$factory = new LinkRendererFactory( $this->titleFormatter );
|
||||
$linkRenderer = $factory->createForUser( $user );
|
||||
$this->assertInstanceOf( LinkRenderer::class, $linkRenderer );
|
||||
$this->assertEquals( 15, $linkRenderer->getStubThreshold() );
|
||||
}
|
||||
}
|
||||
134
tests/phpunit/includes/linker/LinkRendererTest.php
Normal file
134
tests/phpunit/includes/linker/LinkRendererTest.php
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
use MediaWiki\Linker\LinkRenderer;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
|
||||
/**
|
||||
* @covers LinkRenderer
|
||||
*/
|
||||
class LinkRendererTest extends MediaWikiLangTestCase {
|
||||
|
||||
/**
|
||||
* @var TitleFormatter
|
||||
*/
|
||||
private $titleFormatter;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
$this->setMwGlobals( [
|
||||
'wgArticlePath' => '/wiki/$1',
|
||||
'wgServer' => '//example.org',
|
||||
'wgCanonicalServer' => 'http://example.org',
|
||||
'wgScriptPath' => '/w',
|
||||
'wgScript' => '/w/index.php',
|
||||
] );
|
||||
$this->titleFormatter = MediaWikiServices::getInstance()->getTitleFormatter();
|
||||
}
|
||||
|
||||
public function testMergeAttribs() {
|
||||
$target = new TitleValue( NS_SPECIAL, 'Blankpage' );
|
||||
$linkRenderer = new LinkRenderer( $this->titleFormatter );
|
||||
$link = $linkRenderer->makeBrokenLink( $target, null, [
|
||||
// Appended to class
|
||||
'class' => 'foobar',
|
||||
// Suppresses href attribute
|
||||
'href' => false,
|
||||
// Extra attribute
|
||||
'bar' => 'baz'
|
||||
] );
|
||||
$this->assertEquals(
|
||||
'<a href="/wiki/Special:BlankPage" class="new foobar" '
|
||||
. 'title="Special:BlankPage (page does not exist)" bar="baz">'
|
||||
. 'Special:BlankPage</a>',
|
||||
$link
|
||||
);
|
||||
}
|
||||
|
||||
public function testMakeKnownLink() {
|
||||
$target = new TitleValue( NS_MAIN, 'Foobar' );
|
||||
$linkRenderer = new LinkRenderer( $this->titleFormatter );
|
||||
|
||||
// Query added
|
||||
$this->assertEquals(
|
||||
'<a href="/w/index.php?title=Foobar&foo=bar" '. 'title="Foobar">Foobar</a>',
|
||||
$linkRenderer->makeKnownLink( $target, null, [], [ 'foo' => 'bar' ] )
|
||||
);
|
||||
|
||||
// forcearticlepath
|
||||
$linkRenderer->setForceArticlePath( true );
|
||||
$this->assertEquals(
|
||||
'<a href="/wiki/Foobar?foo=bar" title="Foobar">Foobar</a>',
|
||||
$linkRenderer->makeKnownLink( $target, null, [], [ 'foo' => 'bar' ] )
|
||||
);
|
||||
|
||||
// expand = HTTPS
|
||||
$linkRenderer->setForceArticlePath( false );
|
||||
$linkRenderer->setExpandURLs( PROTO_HTTPS );
|
||||
$this->assertEquals(
|
||||
'<a href="https://example.org/wiki/Foobar" title="Foobar">Foobar</a>',
|
||||
$linkRenderer->makeKnownLink( $target )
|
||||
);
|
||||
}
|
||||
|
||||
public function testMakeBrokenLink() {
|
||||
$target = new TitleValue( NS_MAIN, 'Foobar' );
|
||||
$special = new TitleValue( NS_SPECIAL, 'Foobar' );
|
||||
$linkRenderer = new LinkRenderer( $this->titleFormatter );
|
||||
|
||||
// action=edit&redlink=1 added
|
||||
$this->assertEquals(
|
||||
'<a href="/w/index.php?title=Foobar&action=edit&redlink=1" '
|
||||
. 'class="new" title="Foobar (page does not exist)">Foobar</a>',
|
||||
$linkRenderer->makeBrokenLink( $target )
|
||||
);
|
||||
|
||||
// action=edit&redlink=1 not added due to action query parameter
|
||||
$this->assertEquals(
|
||||
'<a href="/w/index.php?title=Foobar&action=foobar" class="new" '
|
||||
. 'title="Foobar (page does not exist)">Foobar</a>',
|
||||
$linkRenderer->makeBrokenLink( $target, null, [], [ 'action' => 'foobar' ] )
|
||||
);
|
||||
|
||||
// action=edit&redlink=1 not added due to NS_SPECIAL
|
||||
$this->assertEquals(
|
||||
'<a href="/wiki/Special:Foobar" class="new" title="Special:Foobar '
|
||||
. '(page does not exist)">Special:Foobar</a>',
|
||||
$linkRenderer->makeBrokenLink( $special )
|
||||
);
|
||||
|
||||
// fragment stripped
|
||||
$this->assertEquals(
|
||||
'<a href="/w/index.php?title=Foobar&action=edit&redlink=1" '
|
||||
. 'class="new" title="Foobar (page does not exist)">Foobar</a>',
|
||||
$linkRenderer->makeBrokenLink( $target->createFragmentTarget( 'foobar' ) )
|
||||
);
|
||||
}
|
||||
|
||||
public function testMakeLink() {
|
||||
$linkRenderer = new LinkRenderer( $this->titleFormatter );
|
||||
$foobar = new TitleValue( NS_SPECIAL, 'Foobar' );
|
||||
$blankpage = new TitleValue( NS_SPECIAL, 'Blankpage' );
|
||||
$this->assertEquals(
|
||||
'<a href="/wiki/Special:Foobar" class="new" title="Special:Foobar '
|
||||
. '(page does not exist)">foo</a>',
|
||||
$linkRenderer->makeLink( $foobar, 'foo' )
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'<a href="/wiki/Special:BlankPage" title="Special:BlankPage">blank</a>',
|
||||
$linkRenderer->makeLink( $blankpage, 'blank' )
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'<a href="/wiki/Special:Foobar" class="new" title="Special:Foobar '
|
||||
. '(page does not exist)"><script>evil()</script></a>',
|
||||
$linkRenderer->makeLink( $foobar, '<script>evil()</script>' )
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'<a href="/wiki/Special:Foobar" class="new" title="Special:Foobar '
|
||||
. '(page does not exist)"><script>evil()</script></a>',
|
||||
$linkRenderer->makeLink( $foobar, new HtmlArmor( '<script>evil()</script>' ) )
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue