diff --git a/includes/composer/ComposerVendorHtaccessCreator.php b/includes/composer/ComposerVendorHtaccessCreator.php index 1e5efdf13a0..dafaab93cd0 100644 --- a/includes/composer/ComposerVendorHtaccessCreator.php +++ b/includes/composer/ComposerVendorHtaccessCreator.php @@ -1,43 +1,43 @@ + * Copyright (C) 2017 Kunal Mehta * * 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. * */ /** * Creates a .htaccess in the vendor/ directory * to prevent web access. * * This class runs *outside* of the normal MediaWiki * environment and cannot depend upon any MediaWiki * code. */ class ComposerVendorHtaccessCreator { /** * Handle post-install-cmd and post-update-cmd hooks */ public static function onEvent() { $fname = dirname( dirname( __DIR__ ) ) . "/vendor/.htaccess"; if ( file_exists( $fname ) ) { // Already exists return; } file_put_contents( $fname, "Deny from all\n" ); } } diff --git a/includes/editpage/TextConflictHelper.php b/includes/editpage/TextConflictHelper.php index 084a3c4bec8..405b3dc29fc 100644 --- a/includes/editpage/TextConflictHelper.php +++ b/includes/editpage/TextConflictHelper.php @@ -1,313 +1,313 @@ + * Copyright (C) 2017 Kunal Mehta * * 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 */ namespace MediaWiki\EditPage; use Content; use ContentHandler; use Html; use IBufferingStatsdDataFactory; use MediaWiki\Content\IContentHandlerFactory; use MediaWiki\MediaWikiServices; use MWUnknownContentModelException; use OutputPage; use Title; use User; /** * Helper for displaying edit conflicts in text content * models to users * * @since 1.31 */ class TextConflictHelper { /** * @var Title */ protected $title; /** * @var null|string */ public $contentModel; /** * @var null|string */ public $contentFormat; /** * @var OutputPage */ protected $out; /** * @var IBufferingStatsdDataFactory */ protected $stats; /** * @var string Message key for submit button's label */ protected $submitLabel; /** * @var string */ protected $yourtext = ''; /** * @var string */ protected $storedversion = ''; /** * @var IContentHandlerFactory */ private $contentHandlerFactory; /** * @param Title $title * @param OutputPage $out * @param IBufferingStatsdDataFactory $stats * @param string $submitLabel * @param IContentHandlerFactory|null $contentHandlerFactory Required param with legacy support * * @throws MWUnknownContentModelException */ public function __construct( Title $title, OutputPage $out, IBufferingStatsdDataFactory $stats, $submitLabel, ?IContentHandlerFactory $contentHandlerFactory = null ) { $this->title = $title; $this->out = $out; $this->stats = $stats; $this->submitLabel = $submitLabel; $this->contentModel = $title->getContentModel(); if ( !$contentHandlerFactory ) { wfDeprecated( __METHOD__ . ' without $contentHandlerFactory parameter', '1.35' ); $contentHandlerFactory = MediaWikiServices::getInstance()->getContentHandlerFactory(); } $this->contentHandlerFactory = $contentHandlerFactory; $this->contentFormat = $this->contentHandlerFactory ->getContentHandler( $this->contentModel ) ->getDefaultFormat(); } /** * @param string $yourtext * @param string $storedversion */ public function setTextboxes( $yourtext, $storedversion ) { $this->yourtext = $yourtext; $this->storedversion = $storedversion; } /** * @param string $contentModel */ public function setContentModel( $contentModel ) { $this->contentModel = $contentModel; } /** * @param string $contentFormat */ public function setContentFormat( $contentFormat ) { $this->contentFormat = $contentFormat; } /** * Record a user encountering an edit conflict * @param User|null $user */ public function incrementConflictStats( User $user = null ) { $this->stats->increment( 'edit.failures.conflict' ); // Only include 'standard' namespaces to avoid creating unknown numbers of statsd metrics if ( $this->title->getNamespace() >= NS_MAIN && $this->title->getNamespace() <= NS_CATEGORY_TALK ) { $this->stats->increment( 'edit.failures.conflict.byNamespaceId.' . $this->title->getNamespace() ); } if ( $user ) { $this->incrementStatsByUserEdits( $user->getEditCount(), 'edit.failures.conflict' ); } } /** * Record when a user has resolved an edit conflict * @param User|null $user */ public function incrementResolvedStats( User $user = null ) { $this->stats->increment( 'edit.failures.conflict.resolved' ); // Only include 'standard' namespaces to avoid creating unknown numbers of statsd metrics if ( $this->title->getNamespace() >= NS_MAIN && $this->title->getNamespace() <= NS_CATEGORY_TALK ) { $this->stats->increment( 'edit.failures.conflict.resolved.byNamespaceId.' . $this->title->getNamespace() ); } if ( $user ) { $this->incrementStatsByUserEdits( $user->getEditCount(), 'edit.failures.conflict.resolved' ); } } /** * @param int|null $userEdits * @param string $keyPrefixBase */ protected function incrementStatsByUserEdits( $userEdits, $keyPrefixBase ) { if ( $userEdits === null ) { $userBucket = 'anon'; } elseif ( $userEdits > 200 ) { $userBucket = 'over200'; } elseif ( $userEdits > 100 ) { $userBucket = 'over100'; } elseif ( $userEdits > 10 ) { $userBucket = 'over10'; } else { $userBucket = 'under11'; } $this->stats->increment( $keyPrefixBase . '.byUserEdits.' . $userBucket ); } /** * @return string HTML */ public function getExplainHeader() { return Html::rawElement( 'div', [ 'class' => 'mw-explainconflict' ], $this->out->msg( 'explainconflict', $this->out->msg( $this->submitLabel )->text() )->parse() ); } /** * HTML to build the textbox1 on edit conflicts * * @param array $customAttribs * @return string HTML */ public function getEditConflictMainTextBox( array $customAttribs = [] ) { $builder = new TextboxBuilder(); $classes = $builder->getTextboxProtectionCSSClasses( $this->title ); $attribs = [ 'aria-label' => $this->out->msg( 'edit-textarea-aria-label' )->text(), 'tabindex' => 1, ]; $attribs += $customAttribs; $attribs = $builder->mergeClassesIntoAttributes( $classes, $attribs ); $attribs = $builder->buildTextboxAttribs( 'wpTextbox1', $attribs, $this->out->getUser(), $this->title ); return Html::textarea( 'wpTextbox1', $builder->addNewLineAtEnd( $this->storedversion ), $attribs ); } /** * Content to go in the edit form before textbox1 * * @see EditPage::$editFormTextBeforeContent * @return string HTML */ public function getEditFormHtmlBeforeContent() { return ''; } /** * Content to go in the edit form after textbox1 * * @see EditPage::$editFormTextAfterContent * @return string HTML */ public function getEditFormHtmlAfterContent() { return ''; } /** * Content to go in the edit form after the footers * (templates on this page, hidden categories, limit report) */ public function showEditFormTextAfterFooters() { $this->out->wrapWikiMsg( '

$1

', "yourdiff" ); $yourContent = $this->toEditContent( $this->yourtext ); $storedContent = $this->toEditContent( $this->storedversion ); $handler = $this->contentHandlerFactory->getContentHandler( $this->contentModel ); $diffEngine = $handler->createDifferenceEngine( $this->out ); $diffEngine->setContent( $yourContent, $storedContent ); $diffEngine->showDiff( $this->out->msg( 'yourtext' )->parse(), $this->out->msg( 'storedversion' )->text() ); $this->out->wrapWikiMsg( '

$1

', "yourtext" ); $builder = new TextboxBuilder(); $attribs = $builder->buildTextboxAttribs( 'wpTextbox2', [ 'tabindex' => 6, 'readonly' ], $this->out->getUser(), $this->title ); $this->out->addHTML( Html::textarea( 'wpTextbox2', $builder->addNewLineAtEnd( $this->yourtext ), $attribs ) ); } /** * @param string $text * @return Content */ private function toEditContent( $text ) { return ContentHandler::makeContent( $text, $this->title, $this->contentModel, $this->contentFormat ); } } diff --git a/includes/editpage/TextboxBuilder.php b/includes/editpage/TextboxBuilder.php index 8161251d0b9..343e904e68a 100644 --- a/includes/editpage/TextboxBuilder.php +++ b/includes/editpage/TextboxBuilder.php @@ -1,138 +1,138 @@ + * (C) Copyright 2017 Kunal Mehta * * 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 */ namespace MediaWiki\EditPage; use MediaWiki\MediaWikiServices; use Sanitizer; use Title; use User; /** * Helps EditPage build textboxes * * @since 1.31 */ class TextboxBuilder { /** * @param string $wikitext * @return string */ public function addNewLineAtEnd( $wikitext ) { if ( strval( $wikitext ) !== '' ) { // Ensure there's a newline at the end, otherwise adding lines // is awkward. // But don't add a newline if the text is empty, or Firefox in XHTML // mode will show an extra newline. A bit annoying. $wikitext .= "\n"; return $wikitext; } return $wikitext; } /** * @param string[] $classes * @param mixed[] $attribs * @return mixed[] */ public function mergeClassesIntoAttributes( array $classes, array $attribs ) { if ( $classes === [] ) { return $attribs; } return Sanitizer::mergeAttributes( $attribs, [ 'class' => implode( ' ', $classes ) ] ); } /** * @param Title $title * @return string[] */ public function getTextboxProtectionCSSClasses( Title $title ) { $classes = []; // Textarea CSS if ( $title->isProtected( 'edit' ) && MediaWikiServices::getInstance()->getPermissionManager() ->getNamespaceRestrictionLevels( $title->getNamespace() ) !== [ '' ] ) { # Is the title semi-protected? if ( $title->isSemiProtected() ) { $classes[] = 'mw-textarea-sprotected'; } else { # Then it must be protected based on static groups (regular) $classes[] = 'mw-textarea-protected'; } # Is the title cascade-protected? if ( $title->isCascadeProtected() ) { $classes[] = 'mw-textarea-cprotected'; } } return $classes; } /** * @param string $name * @param mixed[] $customAttribs * @param User $user * @param Title $title * @return mixed[] */ public function buildTextboxAttribs( $name, array $customAttribs, User $user, Title $title ) { $attribs = $customAttribs + [ 'accesskey' => ',', 'id' => $name, 'cols' => 80, 'rows' => 25, // Avoid PHP notices when appending preferences // (appending allows customAttribs['style'] to still work). 'style' => '' ]; // The following classes can be used here: // * mw-editfont-monospace // * mw-editfont-sans-serif // * mw-editfont-serif $class = 'mw-editfont-' . $user->getOption( 'editfont' ); if ( isset( $attribs['class'] ) ) { if ( is_string( $attribs['class'] ) ) { $attribs['class'] .= ' ' . $class; } elseif ( is_array( $attribs['class'] ) ) { $attribs['class'][] = $class; } } else { $attribs['class'] = $class; } $pageLang = $title->getPageLanguage(); $attribs['lang'] = $pageLang->getHtmlCode(); $attribs['dir'] = $pageLang->getDir(); return $attribs; } } diff --git a/includes/interwiki/NullInterwikiLookup.php b/includes/interwiki/NullInterwikiLookup.php index 09fa1ecd229..671aa9a83b3 100644 --- a/includes/interwiki/NullInterwikiLookup.php +++ b/includes/interwiki/NullInterwikiLookup.php @@ -1,57 +1,57 @@ + * Copyright (C) 2018 Kunal Mehta * * 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. * */ namespace MediaWiki\Interwiki; /** * An interwiki lookup that has no data, intended * for use in the installer. * * @since 1.31 */ class NullInterwikiLookup implements InterwikiLookup { /** * @inheritDoc */ public function isValidInterwiki( $prefix ) { return false; } /** * @inheritDoc */ public function fetch( $prefix ) { return false; } /** * @inheritDoc */ public function getAllPrefixes( $local = null ) { return []; } /** * @inheritDoc */ public function invalidateCache( $prefix ) { } } diff --git a/includes/libs/HtmlArmor.php b/includes/libs/HtmlArmor.php index 0b35443a814..1f4eeaab793 100644 --- a/includes/libs/HtmlArmor.php +++ b/includes/libs/HtmlArmor.php @@ -1,61 +1,61 @@ + * @author Kunal Mehta */ /** * Marks HTML that shouldn't be escaped * * @newable * * @since 1.28 */ class HtmlArmor { /** * @var string|null */ private $value; /** * @stable to call * * @param string|null $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|null safe for usage in HTML, or null * if the HtmlArmor instance was wrapping null. */ public static function getHtml( $input ) { if ( $input instanceof HtmlArmor ) { return $input->value; } else { return htmlspecialchars( $input, ENT_QUOTES ); } } } diff --git a/includes/linker/LinkRenderer.php b/includes/linker/LinkRenderer.php index 631c41194de..9def917b133 100644 --- a/includes/linker/LinkRenderer.php +++ b/includes/linker/LinkRenderer.php @@ -1,426 +1,426 @@ + * @author Kunal Mehta */ namespace MediaWiki\Linker; use Html; use HtmlArmor; use LinkCache; use MediaWiki\HookContainer\HookContainer; use MediaWiki\HookContainer\HookRunner; use MediaWiki\SpecialPage\SpecialPageFactory; use NamespaceInfo; use Sanitizer; use Title; use TitleFormatter; use TitleValue; /** * Class that generates HTML links for pages. * * @see https://www.mediawiki.org/wiki/Manual:LinkRenderer * @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; /** * @var int */ private $stubThreshold = 0; /** * @var TitleFormatter */ private $titleFormatter; /** * @var LinkCache */ private $linkCache; /** * @var NamespaceInfo */ private $nsInfo; /** @var HookContainer */ private $hookContainer; /** @var HookRunner */ private $hookRunner; /** * @var SpecialPageFactory */ private $specialPageFactory; /** * @internal For use by LinkRendererFactory * @param TitleFormatter $titleFormatter * @param LinkCache $linkCache * @param NamespaceInfo $nsInfo * @param SpecialPageFactory $specialPageFactory * @param HookContainer $hookContainer */ public function __construct( TitleFormatter $titleFormatter, LinkCache $linkCache, NamespaceInfo $nsInfo, SpecialPageFactory $specialPageFactory, HookContainer $hookContainer ) { $this->titleFormatter = $titleFormatter; $this->linkCache = $linkCache; $this->nsInfo = $nsInfo; $this->specialPageFactory = $specialPageFactory; $this->hookContainer = $hookContainer; $this->hookRunner = new HookRunner( $hookContainer ); } /** * @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 int $threshold */ public function setStubThreshold( $threshold ) { $this->stubThreshold = $threshold; } /** * @return int */ public function getStubThreshold() { return $this->stubThreshold; } /** * @param LinkTarget $target * @param string|HtmlArmor|null $text * @param array $extraAttribs * @param array $query * @return string HTML */ 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 ); } } private function runBeginHook( LinkTarget $target, &$text, &$extraAttribs, &$query, $isKnown ) { $ret = null; if ( !$this->hookRunner->onHtmlPageLinkRendererBegin( $this, $target, $text, $extraAttribs, $query, $ret ) ) { return $ret; } } /** * If you have already looked up the proper CSS classes using LinkRenderer::getLinkClasses() * or some other method, use this to avoid looking it up again. * * @param LinkTarget $target * @param string|HtmlArmor|null $text * @param string $classes CSS classes to add * @param array $extraAttribs * @param array $query * @return string */ public function makePreloadedLink( LinkTarget $target, $text = null, $classes = '', 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 = [ 'class' => $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 HTML */ public function makeKnownLink( LinkTarget $target, $text = null, array $extraAttribs = [], array $query = [] ) { $classes = []; if ( $target->isExternal() ) { $classes[] = 'extiw'; } $colour = $this->getLinkClasses( $target ); if ( $colour !== '' ) { $classes[] = $colour; } return $this->makePreloadedLink( $target, $text, implode( ' ', $classes ), $extraAttribs, $query ); } /** * @param LinkTarget $target * @param-taint $target none * @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 = [ '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 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 ( !$this->hookRunner->onHtmlPageLinkRendererEnd( $this, $target, $isKnown, $text, $attribs, $ret ) ) { return $ret; } return Html::rawElement( 'a', $attribs, HtmlArmor::getHtml( $text ) ); } /** * @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 ); if ( $this->forceArticlePath ) { $realQuery = $query; $query = []; } else { $realQuery = []; } $url = $title->getLinkURL( $query, false, $this->expandUrls ); if ( $this->forceArticlePath && $realQuery ) { $url = wfAppendQuery( $url, $realQuery ); } return $url; } /** * Normalizes the provided target * * @internal For use by deprecated Linker & DummyLinker * ::normaliseSpecialPage() methods * @param LinkTarget $target * @return LinkTarget */ public function normalizeTarget( LinkTarget $target ) { if ( $target->getNamespace() === NS_SPECIAL && !$target->isExternal() ) { list( $name, $subpage ) = $this->specialPageFactory->resolveAlias( $target->getDBkey() ); if ( $name ) { return new TitleValue( NS_SPECIAL, $this->specialPageFactory->getLocalNameFor( $name, $subpage ), $target->getFragment() ); } } return $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; } /** * Return the CSS classes of a known link * * @param LinkTarget $target * @return string CSS class */ public function getLinkClasses( LinkTarget $target ) { // Make sure the target is in the cache $id = $this->linkCache->addLinkObj( $target ); if ( $id == 0 ) { // Doesn't exist return ''; } if ( $this->linkCache->getGoodLinkFieldObj( $target, 'redirect' ) ) { # Page is a redirect return 'mw-redirect'; } elseif ( $this->stubThreshold > 0 && $this->nsInfo->isContent( $target->getNamespace() ) && $this->linkCache->getGoodLinkFieldObj( $target, 'length' ) < $this->stubThreshold ) { # Page is a stub return 'stub'; } return ''; } } diff --git a/includes/linker/LinkRendererFactory.php b/includes/linker/LinkRendererFactory.php index 49640c5856a..aa09b72f066 100644 --- a/includes/linker/LinkRendererFactory.php +++ b/includes/linker/LinkRendererFactory.php @@ -1,129 +1,129 @@ + * @author Kunal Mehta */ namespace MediaWiki\Linker; use LinkCache; use MediaWiki\HookContainer\HookContainer; use MediaWiki\SpecialPage\SpecialPageFactory; use NamespaceInfo; use TitleFormatter; use User; /** * Factory to create LinkRender objects * @since 1.28 */ class LinkRendererFactory { /** * @var TitleFormatter */ private $titleFormatter; /** * @var LinkCache */ private $linkCache; /** * @var NamespaceInfo */ private $nsInfo; /** * @var HookContainer */ private $hookContainer; /** * @var SpecialPageFactory */ private $specialPageFactory; /** * @internal For use by core ServiceWiring * @param TitleFormatter $titleFormatter * @param LinkCache $linkCache * @param NamespaceInfo $nsInfo * @param SpecialPageFactory $specialPageFactory * @param HookContainer $hookContainer */ public function __construct( TitleFormatter $titleFormatter, LinkCache $linkCache, NamespaceInfo $nsInfo, SpecialPageFactory $specialPageFactory, HookContainer $hookContainer ) { $this->titleFormatter = $titleFormatter; $this->linkCache = $linkCache; $this->nsInfo = $nsInfo; $this->specialPageFactory = $specialPageFactory; $this->hookContainer = $hookContainer; } /** * @return LinkRenderer */ public function create() { return new LinkRenderer( $this->titleFormatter, $this->linkCache, $this->nsInfo, $this->specialPageFactory, $this->hookContainer ); } /** * @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( '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; } } diff --git a/includes/registration/ExtensionDependencyError.php b/includes/registration/ExtensionDependencyError.php index 3b4648cfd94..98c1f8e1e3c 100644 --- a/includes/registration/ExtensionDependencyError.php +++ b/includes/registration/ExtensionDependencyError.php @@ -1,106 +1,106 @@ + * Copyright (C) 2018 Kunal Mehta * * 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. * */ /** * @newable * @since 1.31 */ class ExtensionDependencyError extends Exception { /** * @var string[] */ public $missingExtensions = []; /** * @var string[] */ public $missingSkins = []; /** * @var string[] */ public $incompatibleExtensions = []; /** * @var string[] */ public $incompatibleSkins = []; /** * @var bool */ public $incompatibleCore = false; /** * @var bool */ public $incompatiblePhp = false; /** * @var string[] */ public $missingPhpExtensions = []; /** * @var string[] */ public $missingAbilities = []; /** * @param array[] $errors Each error has a 'msg' and 'type' key at minimum */ public function __construct( array $errors ) { $msg = ''; foreach ( $errors as $info ) { $msg .= $info['msg'] . "\n"; switch ( $info['type'] ) { case 'incompatible-core': $this->incompatibleCore = true; break; case 'incompatible-php': $this->incompatiblePhp = true; break; case 'missing-phpExtension': $this->missingPhpExtensions[] = $info['missing']; break; case 'missing-ability': $this->missingAbilities[] = $info['missing']; break; case 'missing-skins': $this->missingSkins[] = $info['missing']; break; case 'missing-extensions': $this->missingExtensions[] = $info['missing']; break; case 'incompatible-skins': $this->incompatibleSkins[] = $info['incompatible']; break; case 'incompatible-extensions': $this->incompatibleExtensions[] = $info['incompatible']; break; // default: continue } } parent::__construct( $msg ); } } diff --git a/includes/utils/ExecutableFinder.php b/includes/utils/ExecutableFinder.php index 78b3f8e29b3..44fb9ccd04b 100644 --- a/includes/utils/ExecutableFinder.php +++ b/includes/utils/ExecutableFinder.php @@ -1,115 +1,115 @@ + * Copyright (C) 2017 Kunal Mehta * * 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. * */ use MediaWiki\Shell\Shell; /** * Utility class to find executables in likely places * * @since 1.31 */ class ExecutableFinder { /** * Get an array of likely places we can find executables. Check a bunch * of known Unix-like defaults, as well as the PATH environment variable * (which should maybe make it work for Windows?) * * @return array */ protected static function getPossibleBinPaths() { return array_unique( array_merge( [ '/usr/bin', '/bin', '/usr/local/bin', '/opt/csw/bin', '/usr/gnu/bin', '/usr/sfw/bin', '/sw/bin', '/opt/local/bin' ], explode( PATH_SEPARATOR, getenv( 'PATH' ) ) ) ); } /** * Search a path for any of the given executable names. Returns the * executable name if found. Also checks the version string returned * by each executable. * * Used only by environment checks. * * @param string $path Path to search * @param string $name Executable name to look for * @param array|bool $versionInfo False or array with two members: * 0 => Parameter to pass to binary for version check (e.g. --version) * 1 => String to compare the output with * * If $versionInfo is not false, only executables with a version * matching $versionInfo[1] will be returned. * @return bool|string */ protected static function findExecutable( $path, $name, $versionInfo = false ) { $command = $path . DIRECTORY_SEPARATOR . $name; Wikimedia\suppressWarnings(); $file_exists = is_executable( $command ); Wikimedia\restoreWarnings(); if ( $file_exists ) { if ( !$versionInfo ) { return $command; } $output = Shell::command( $command, $versionInfo[0] ) ->includeStderr()->execute()->getStdout(); if ( strstr( $output, $versionInfo[1] ) !== false ) { return $command; } } return false; } /** * Same as locateExecutable(), but checks in getPossibleBinPaths() by default * @see locateExecutable() * @param string|string[] $names Array of possible names. * @param array|bool $versionInfo Default: false or array with two members: * 0 => Parameter to run for version check, e.g. '--version' * 1 => String to compare the output with * * If $versionInfo is not false, only executables with a version * matching $versionInfo[1] will be returned. * @return bool|string */ public static function findInDefaultPaths( $names, $versionInfo = false ) { if ( Shell::isDisabled() ) { // If we can't shell out, there's no point looking for executables return false; } $paths = self::getPossibleBinPaths(); foreach ( (array)$names as $name ) { foreach ( $paths as $path ) { $exe = self::findExecutable( $path, $name, $versionInfo ); if ( $exe !== false ) { return $exe; } } } return false; } } diff --git a/maintenance/benchmarks/benchmarkTitleValue.php b/maintenance/benchmarks/benchmarkTitleValue.php index f92c13adc87..de0722f13f9 100644 --- a/maintenance/benchmarks/benchmarkTitleValue.php +++ b/maintenance/benchmarks/benchmarkTitleValue.php @@ -1,144 +1,144 @@ + * Copyright (C) 2018 Kunal Mehta * * 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. * */ use MediaWiki\MediaWikiServices; require_once __DIR__ . '/../includes/Benchmarker.php'; /** * Maintenance script that benchmarks TitleValue vs Title. * * @ingroup Benchmark */ class BenchmarkTitleValue extends Benchmarker { /** * @var TitleFormatter */ private $titleFormatter; /** * @var TitleParser */ private $titleParser; /** * @var string */ private $dbKey = 'FooBar'; /** * @var TitleValue */ private $titleValue; /** * @var Title */ private $title; /** * @var string */ private $toParse; public function __construct() { parent::__construct(); $this->addDescription( 'Benchmark TitleValue vs Title.' ); } public function execute() { $this->titleFormatter = MediaWikiServices::getInstance()->getTitleFormatter(); $this->titleParser = MediaWikiServices::getInstance()->getTitleParser(); $this->titleValue = $this->constructTitleValue(); $this->title = $this->constructTitle(); $this->toParse = 'Category:FooBar'; $this->bench( [ [ 'function' => [ $this, 'constructTitleValue' ], ], [ 'function' => [ $this, 'constructTitle' ], ], [ 'function' => [ $this, 'constructTitleSafe' ], ], [ 'function' => [ $this, 'getPrefixedTextTitleValue' ], ], [ 'function' => [ $this, 'getPrefixedTextTitle' ], ], 'parseTitleValue cached' => [ 'function' => [ $this, 'parseTitleValue' ], 'setup' => [ $this, 'randomize' ], ], 'parseTitle cached' => [ 'function' => [ $this, 'parseTitle' ], 'setup' => [ $this, 'randomize' ], ], 'parseTitleValue no cache' => [ 'function' => [ $this, 'parseTitleValue' ], 'setupEach' => [ $this, 'randomize' ], ], 'parseTitle no cache' => [ 'function' => [ $this, 'parseTitle' ], 'setupEach' => [ $this, 'randomize' ], ], ] ); } /** * Use a different dbKey each time to avoid influence of Title caches */ protected function randomize() { $this->dbKey = ucfirst( wfRandomString( 10 ) ); } protected function constructTitleValue() { return new TitleValue( NS_CATEGORY, $this->dbKey ); } protected function constructTitle() { return Title::makeTitle( NS_CATEGORY, $this->dbKey ); } protected function constructTitleSafe() { return Title::makeTitleSafe( NS_CATEGORY, $this->dbKey ); } protected function getPrefixedTextTitleValue() { // This is really showing TitleFormatter aka MediaWikiTitleCodec perf return $this->titleFormatter->getPrefixedText( $this->titleValue ); } protected function getPrefixedTextTitle() { return $this->title->getPrefixedText(); } protected function parseTitleValue() { // This is really showing TitleParser aka MediaWikiTitleCodec perf $this->titleParser->parseTitle( 'Category:' . $this->dbKey, NS_MAIN ); } protected function parseTitle() { Title::newFromText( 'Category:' . $this->dbKey ); } } $maintClass = BenchmarkTitleValue::class; require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/checkDependencies.php b/maintenance/checkDependencies.php index e3780e12cf1..be52e10f547 100644 --- a/maintenance/checkDependencies.php +++ b/maintenance/checkDependencies.php @@ -1,203 +1,203 @@ + * (C) 2019 Kunal Mehta * * 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 */ require_once __DIR__ . '/Maintenance.php'; /** * Checks dependencies for extensions, mostly without loading them * * @since 1.34 */ class CheckDependencies extends Maintenance { private $checkDev; public function __construct() { parent::__construct(); $this->addDescription( 'Check dependencies for extensions' ); $this->addOption( 'extensions', 'Comma separated list of extensions to check', false, true ); $this->addOption( 'skins', 'Comma separated list of skins to check', false, true ); $this->addOption( 'json', 'Output in JSON' ); $this->addOption( 'dev', 'Check development dependencies too' ); } public function execute() { $this->checkDev = $this->hasOption( 'dev' ); $extensions = $this->hasOption( 'extensions' ) ? explode( ',', $this->getOption( 'extensions' ) ) : []; $skins = $this->hasOption( 'skins' ) ? explode( ',', $this->getOption( 'skins' ) ) : []; $dependencies = []; // Note that we can only use the main registry if we are // not checking development dependencies. $registry = ExtensionRegistry::getInstance(); foreach ( $extensions as $extension ) { if ( !$this->checkDev && $registry->isLoaded( $extension ) ) { // If it's already loaded, we know all the dependencies resolved. $this->addToDependencies( $dependencies, [ $extension ], [] ); continue; } $this->loadThing( $dependencies, $extension, [ $extension ], [] ); } foreach ( $skins as $skin ) { if ( !$this->checkDev && $registry->isLoaded( $skin ) ) { // If it's already loaded, we know all the dependencies resolved. $this->addToDependencies( $dependencies, [], [ $skin ] ); continue; } $this->loadThing( $dependencies, $skin, [], [ $skin ] ); } if ( $this->hasOption( 'json' ) ) { $this->output( json_encode( $dependencies ) . "\n" ); } else { $this->output( $this->formatForHumans( $dependencies ) . "\n" ); } } private function loadThing( &$dependencies, $name, $extensions, $skins ) { $extDir = $this->getConfig()->get( 'ExtensionDirectory' ); $styleDir = $this->getConfig()->get( 'StyleDirectory' ); $queue = []; $missing = false; foreach ( $extensions as $extension ) { $path = "$extDir/$extension/extension.json"; if ( file_exists( $path ) ) { // 1 is ignored $queue[$path] = 1; $this->addToDependencies( $dependencies, [ $extension ], [], $name ); } else { $missing = true; $this->addToDependencies( $dependencies, [ $extension ], [], $name, 'missing' ); } } foreach ( $skins as $skin ) { $path = "$styleDir/$skin/skin.json"; if ( file_exists( $path ) ) { $queue[$path] = 1; $this->addToDependencies( $dependencies, [], [ $skin ], $name ); } else { $missing = true; $this->addToDependencies( $dependencies, [], [ $skin ], $name, 'missing' ); } } if ( $missing ) { // Stuff is missing, give up return; } $registry = new ExtensionRegistry(); $registry->setCheckDevRequires( $this->checkDev ); try { $registry->readFromQueue( $queue ); } catch ( ExtensionDependencyError $e ) { $reason = false; if ( $e->incompatibleCore ) { $reason = 'incompatible-core'; } elseif ( $e->incompatibleSkins ) { $reason = 'incompatible-skins'; } elseif ( $e->incompatibleExtensions ) { $reason = 'incompatible-extensions'; } elseif ( $e->missingExtensions || $e->missingSkins ) { // There's an extension missing in the dependency tree, // so add those to the dependency list and try again return $this->loadThing( $dependencies, $name, array_merge( $extensions, $e->missingExtensions ), array_merge( $skins, $e->missingSkins ) ); } else { // missing-phpExtension // missing-ability // XXX: ??? $this->fatalError( $e->getMessage() ); } $this->addToDependencies( $dependencies, $extensions, $skins, $name, $reason, $e->getMessage() ); } $this->addToDependencies( $dependencies, $extensions, $skins, $name ); } private function addToDependencies( &$dependencies, $extensions, $skins, $why = null, $status = null, $message = null ) { $mainRegistry = ExtensionRegistry::getInstance(); $iter = [ 'extensions' => $extensions, 'skins' => $skins ]; foreach ( $iter as $type => $things ) { foreach ( $things as $thing ) { $preStatus = $dependencies[$type][$thing]['status'] ?? false; if ( $preStatus !== 'loaded' ) { // OK to overwrite if ( $status ) { $tStatus = $status; } else { $tStatus = $mainRegistry->isLoaded( $thing ) ? 'loaded' : 'present'; } $dependencies[$type][$thing]['status'] = $tStatus; } if ( $why !== null ) { $dependencies[$type][$thing]['why'][] = $why; // XXX: this is a bit messy $dependencies[$type][$thing]['why'] = array_unique( $dependencies[$type][$thing]['why'] ); } if ( $message !== null ) { $dependencies[$type][$thing]['message'] = trim( $message ); } } } } private function formatForHumans( $dependencies ) { $text = ''; foreach ( $dependencies as $type => $things ) { $text .= ucfirst( $type ) . "\n" . str_repeat( '=', strlen( $type ) ) . "\n"; foreach ( $things as $thing => $info ) { $why = $info['why'] ?? []; if ( $why ) { $whyText = '(because: ' . implode( ',', $why ) . ') '; } else { $whyText = ''; } $msg = isset( $info['message'] ) ? ", {$info['message']}" : ''; $text .= "$thing: {$info['status']}{$msg} $whyText\n"; } $text .= "\n"; } return trim( $text ); } } $maintClass = CheckDependencies::class; require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/tests/phpunit/HamcrestPHPUnitIntegration.php b/tests/phpunit/HamcrestPHPUnitIntegration.php index 80ecc7dd559..8613576e1b6 100644 --- a/tests/phpunit/HamcrestPHPUnitIntegration.php +++ b/tests/phpunit/HamcrestPHPUnitIntegration.php @@ -1,35 +1,35 @@ + * Copyright (C) 2018 Kunal Mehta * * 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. * */ /** * @since 1.31 */ trait HamcrestPHPUnitIntegration { /** * Wrapper around Hamcrest's assertThat, which marks the assertion * for PHPUnit so the test is not marked as risky * @param mixed ...$args */ public function assertThatHamcrest( ...$args ) { assertThat( ...$args ); $this->addToAssertionCount( 1 ); } } diff --git a/tests/phpunit/MediaWikiCoversValidator.php b/tests/phpunit/MediaWikiCoversValidator.php index 5eff91ea8f8..ae6a6f239aa 100644 --- a/tests/phpunit/MediaWikiCoversValidator.php +++ b/tests/phpunit/MediaWikiCoversValidator.php @@ -1,57 +1,57 @@ + * Copyright (C) 2017 Kunal Mehta * * 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. * */ use PHPUnit\Framework\CodeCoverageException; use PHPUnit\Util\Test; /** * Check that `@covers` tags are valid. PHPUnit only does this when generating * code coverage reports, which is slow and we generally don't do that when * running tests during development and pre-merge in CI. * * @since 1.31 */ trait MediaWikiCoversValidator { /** * Assert that all "test*" methods in the host class have valid `@covers` tags. * * Don't use a data provider here because this assertion will be run many * thousands of times in CI, and the implicit overhead from PHPUnit with * generating and executing a test case around each becomes rather significant * at that scale. Also, when using a data provider, the setUp() and tearDown() * of the host class would be re-run for every check, which becomes very * expensive for integration tests that involve databases. */ public function testValidCovers() { $class = static::class; foreach ( get_class_methods( $this ) as $method ) { if ( strncmp( $method, 'test', 4 ) === 0 ) { try { Test::getLinesToBeCovered( $class, $method ); } catch ( CodeCoverageException $ex ) { $this->fail( "$class::$method: " . $ex->getMessage() ); } } } $this->addToAssertionCount( 1 ); } } diff --git a/tests/phpunit/includes/editpage/TextboxBuilderTest.php b/tests/phpunit/includes/editpage/TextboxBuilderTest.php index d72a0d7dc1f..40ed1797f09 100644 --- a/tests/phpunit/includes/editpage/TextboxBuilderTest.php +++ b/tests/phpunit/includes/editpage/TextboxBuilderTest.php @@ -1,92 +1,92 @@ + * Copyright (C) 2017 Kunal Mehta * * 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. * */ namespace MediaWiki\Tests\EditPage; use MediaWiki\EditPage\TextboxBuilder; use MediaWikiIntegrationTestCase; use Title; /** * See also unit tests at \MediaWiki\Tests\Unit\EditPage\TextboxBuilderTest * * @covers \MediaWiki\EditPage\TextboxBuilder */ class TextboxBuilderTest extends MediaWikiIntegrationTestCase { public function provideGetTextboxProtectionCSSClasses() { return [ [ [ '' ], [ 'isProtected' ], [], ], [ true, [], [], ], [ true, [ 'isProtected' ], [ 'mw-textarea-protected' ] ], [ true, [ 'isProtected', 'isSemiProtected' ], [ 'mw-textarea-sprotected' ], ], [ true, [ 'isProtected', 'isCascadeProtected' ], [ 'mw-textarea-protected', 'mw-textarea-cprotected' ], ], [ true, [ 'isProtected', 'isCascadeProtected', 'isSemiProtected' ], [ 'mw-textarea-sprotected', 'mw-textarea-cprotected' ], ], ]; } /** * @dataProvider provideGetTextboxProtectionCSSClasses */ public function testGetTextboxProtectionCSSClasses( $restrictionLevels, $protectionModes, $expected ) { $this->setMwGlobals( [ // set to trick PermissionManager::getNamespaceRestrictionLevels 'wgRestrictionLevels' => $restrictionLevels ] ); $title = $this->createMock( Title::class ); $title->method( 'getNamespace' )->willReturn( 1 ); foreach ( $protectionModes as $method ) { $title->method( $method )->willReturn( true ); } $builder = new TextboxBuilder(); $this->assertSame( $expected, $builder->getTextboxProtectionCSSClasses( $title ) ); } } diff --git a/tests/phpunit/unit/includes/editpage/TextboxBuilderTest.php b/tests/phpunit/unit/includes/editpage/TextboxBuilderTest.php index f8cb6265acf..d450aeba0b8 100644 --- a/tests/phpunit/unit/includes/editpage/TextboxBuilderTest.php +++ b/tests/phpunit/unit/includes/editpage/TextboxBuilderTest.php @@ -1,140 +1,140 @@ + * Copyright (C) 2017 Kunal Mehta * * 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. * */ namespace MediaWiki\Tests\Unit\EditPage; use Language; use MediaWiki\EditPage\TextboxBuilder; use MediaWikiUnitTestCase; use Title; use User; /** * Split from \MediaWiki\Tests\EditPage\TextboxBuilderTest integration tests * * @covers \MediaWiki\EditPage\TextboxBuilder */ class TextboxBuilderTest extends MediaWikiUnitTestCase { public function provideAddNewLineAtEnd() { return [ [ '', '' ], [ 'foo', "foo\n" ], ]; } /** * @dataProvider provideAddNewLineAtEnd */ public function testAddNewLineAtEnd( $input, $expected ) { $builder = new TextboxBuilder(); $this->assertSame( $expected, $builder->addNewLineAtEnd( $input ) ); } public function testBuildTextboxAttribs() { $user = $this->createMock( User::class ); $user->method( 'getOption' ) ->with( 'editfont' ) ->willReturn( 'monospace' ); $enLanguage = $this->createMock( Language::class ); $enLanguage->method( 'getHtmlCode' )->willReturn( 'en' ); $enLanguage->method( 'getDir' )->willReturn( 'ltr' ); $title = $this->createMock( Title::class ); $title->method( 'getPageLanguage' )->willReturn( $enLanguage ); $builder = new TextboxBuilder(); $attribs = $builder->buildTextboxAttribs( 'mw-textbox1', [ 'class' => 'foo bar', 'data-foo' => '123', 'rows' => 30 ], $user, $title ); $this->assertIsArray( $attribs ); // custom attrib showed up $this->assertArrayHasKey( 'data-foo', $attribs ); // classes merged properly (string) $this->assertSame( 'foo bar mw-editfont-monospace', $attribs['class'] ); // overrides in custom attrib worked $this->assertSame( 30, $attribs['rows'] ); $this->assertSame( 'en', $attribs['lang'] ); $attribs2 = $builder->buildTextboxAttribs( 'mw-textbox2', [ 'class' => [ 'foo', 'bar' ] ], $user, $title ); // classes merged properly (array) $this->assertSame( [ 'foo', 'bar', 'mw-editfont-monospace' ], $attribs2['class'] ); $attribs3 = $builder->buildTextboxAttribs( 'mw-textbox3', [], $user, $title ); // classes ok when nothing to be merged $this->assertSame( 'mw-editfont-monospace', $attribs3['class'] ); } public function provideMergeClassesIntoAttributes() { return [ [ [], [], [], ], [ [ 'mw-new-classname' ], [], [ 'class' => 'mw-new-classname' ], ], [ [], [ 'title' => 'My Title' ], [ 'title' => 'My Title' ], ], [ [ 'mw-new-classname' ], [ 'title' => 'My Title' ], [ 'title' => 'My Title', 'class' => 'mw-new-classname' ], ], [ [ 'mw-new-classname' ], [ 'class' => 'mw-existing-classname' ], [ 'class' => 'mw-existing-classname mw-new-classname' ], ], [ [ 'mw-new-classname', 'mw-existing-classname' ], [ 'class' => 'mw-existing-classname' ], [ 'class' => 'mw-existing-classname mw-new-classname' ], ], ]; } /** * @dataProvider provideMergeClassesIntoAttributes */ public function testMergeClassesIntoAttributes( $inputClasses, $inputAttributes, $expected ) { $builder = new TextboxBuilder(); $this->assertSame( $expected, $builder->mergeClassesIntoAttributes( $inputClasses, $inputAttributes ) ); } } diff --git a/tests/phpunit/unit/includes/libs/DeflateTest.php b/tests/phpunit/unit/includes/libs/DeflateTest.php index 1e0f28e9673..b0305003fa4 100644 --- a/tests/phpunit/unit/includes/libs/DeflateTest.php +++ b/tests/phpunit/unit/includes/libs/DeflateTest.php @@ -1,64 +1,64 @@ + * Copyright (C) 2018 Kunal Mehta * * 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. * */ /** * @covers Deflate */ class DeflateTest extends PHPUnit\Framework\TestCase { public function provideIsDeflated() { return [ [ 'rawdeflate,S8vPT0osAgA=', true ], [ 'abcdefghijklmnopqrstuvwxyz', false ], ]; } /** * @dataProvider provideIsDeflated */ public function testIsDeflated( $data, $expected ) { $actual = Deflate::isDeflated( $data ); $this->assertSame( $expected, $actual ); } public function provideInflate() { return [ [ 'rawdeflate,S8vPT0osAgA=', true, 'foobar' ], // Fails base64_decode [ 'rawdeflate,🌻', false, 'deflate-invaliddeflate' ], // Fails gzinflate [ 'rawdeflate,S8vPT0dfdAgB=', false, 'deflate-invaliddeflate' ], ]; } /** * @dataProvider provideInflate */ public function testInflate( $data, $ok, $value ) { $actual = Deflate::inflate( $data ); if ( $ok ) { $this->assertTrue( $actual->isOK() ); $this->assertSame( $value, $actual->getValue() ); } else { $this->assertFalse( $actual->isOK() ); $this->assertTrue( $actual->hasMessage( $value ) ); } } } diff --git a/tests/phpunit/unit/includes/libs/StaticArrayWriterTest.php b/tests/phpunit/unit/includes/libs/StaticArrayWriterTest.php index 6fcf3b2478b..59953973fae 100644 --- a/tests/phpunit/unit/includes/libs/StaticArrayWriterTest.php +++ b/tests/phpunit/unit/includes/libs/StaticArrayWriterTest.php @@ -1,64 +1,64 @@ + * Copyright (C) 2018 Kunal Mehta * * 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. * */ use Wikimedia\StaticArrayWriter; /** * @covers \Wikimedia\StaticArrayWriter */ class StaticArrayWriterTest extends PHPUnit\Framework\TestCase { public function testCreate() { $data = [ 'foo' => 'bar', 'baz' => 'rawr', "they're" => '"quoted properly"', 'nested' => [ 'elements', 'work' ], 'intlike' => [ '050' => true, '101' => true, '221B' => true ], 'and' => [ 'these' => 'do too' ], ]; $writer = new StaticArrayWriter(); $actual = $writer->create( $data, "Header\nWith\nNewlines" ); $expected = << 'bar', 'baz' => 'rawr', 'they\'re' => '"quoted properly"', 'nested' => [ 0 => 'elements', 1 => 'work', ], 'intlike' => [ '050' => true, 101 => true, '221B' => true, ], 'and' => [ 'these' => 'do too', ], ]; PHP; $this->assertSame( $expected, $actual ); } } diff --git a/tests/phpunit/unit/includes/registration/ExtensionJsonValidatorTest.php b/tests/phpunit/unit/includes/registration/ExtensionJsonValidatorTest.php index f7557c426ec..d907b92b17c 100644 --- a/tests/phpunit/unit/includes/registration/ExtensionJsonValidatorTest.php +++ b/tests/phpunit/unit/includes/registration/ExtensionJsonValidatorTest.php @@ -1,99 +1,99 @@ + * Copyright (C) 2018 Kunal Mehta * * 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. * */ /** * @covers ExtensionJsonValidator */ class ExtensionJsonValidatorTest extends MediaWikiUnitTestCase { /** * @dataProvider provideValidate */ public function testValidate( $file, $expected ) { // If a dependency is missing, skip this test. $validator = new ExtensionJsonValidator( function ( $msg ) { $this->markTestSkipped( $msg ); } ); if ( is_string( $expected ) ) { $this->expectException( ExtensionJsonValidationError::class ); $this->expectExceptionMessage( $expected ); } $dir = __DIR__ . '/../../../data/registration/'; $this->assertSame( $expected, $validator->validate( $dir . $file ) ); } public function provideValidate() { return [ [ 'notjson.txt', 'notjson.txt is not valid JSON' ], [ 'duplicate_keys.json', 'Duplicate key: name' ], [ 'no_manifest_version.json', 'no_manifest_version.json does not have manifest_version set.' ], [ 'old_manifest_version.json', 'old_manifest_version.json is using a non-supported schema version' ], [ 'newer_manifest_version.json', 'newer_manifest_version.json is using a non-supported schema version' ], [ 'bad_spdx.json', "bad_spdx.json did not pass validation. [license-name] Invalid SPDX license identifier, see " ], [ 'invalid.json', "invalid.json did not pass validation. [license-name] Array value found, but a string is required" ], [ 'good.json', true ], [ 'good_with_license_expressions.json', true ], [ 'bad_url.json', 'bad_url.json did not pass validation. [url] Should use HTTPS for www.mediawiki.org URLs' ], [ 'bad_url2.json', 'bad_url2.json did not pass validation. [url] Should use www.mediawiki.org domain [url] Should use HTTPS for www.mediawiki.org URLs' ] ]; } }