John Du Hart <john@compwhizii.net> <johnduhart@users.mediawiki.org>
John Erling Blad <john.blad@wikimedia.de>
Jon Harald Søby <jhsoby@gmail.com> <jhsoby@users.mediawiki.org>
+Jon Harald Søby <jhsoby@gmail.com>
Jon Robson <jrobson@wikimedia.org>
Jon Robson <jrobson@wikimedia.org> <jdlrobson@gmail.com>
Juliusz Gonera <jgonera@gmail.com>
Marko Obrovac <mobrovac@wikimedia.org>
Markus Glaser <glaser@hallowelt.biz>
Markus Glaser <glaser@hallowelt.biz> <mglaser@users.mediawiki.org>
+Martin Urbanec <martin.urbanec@wikimedia.cz>
Matt Johnston <mattj@emazestudios.com> <mattj@users.mediawiki.org>
Matthew Britton <hugglegurch@gmail.com> <gurch@users.mediawiki.org>
Matthew Bowker <matthewrbowker.bugs@gmail.com>
"PhanUndeclaredConstant",
// approximate error count: 60
"PhanTypeMismatchArgument",
- // approximate error count: 219
- "PhanUndeclaredMethod",
// approximate error count: 752
"PhanUndeclaredProperty",
] );
--- /dev/null
+<?php
+// These stubs were generated by the phan stub generator.
+// @phan-stub-for-extension dom@20031129
+
+namespace {
+class DOMAttr extends \DOMNode {
+
+ // properties
+ public $name;
+ public $ownerElement;
+ public $schemaTypeInfo;
+ public $specified;
+ public $value;
+
+ // methods
+ public function isId() {}
+ public function __construct($name, $value = null) {}
+}
+
+class DOMCdataSection extends \DOMText {
+
+ // methods
+ public function __construct($value) {}
+}
+
+class DOMCharacterData extends \DOMNode {
+
+ // properties
+ public $data;
+ public $length;
+
+ // methods
+ public function substringData($offset, $count) {}
+ public function appendData($arg) {}
+ public function insertData($offset, $arg) {}
+ public function deleteData($offset, $count) {}
+ public function replaceData($offset, $count, $arg) {}
+}
+
+class DOMComment extends \DOMCharacterData {
+
+ // methods
+ public function __construct($value = null) {}
+}
+
+class DOMConfiguration {
+
+ // methods
+ public function setParameter($name, $value) {}
+ public function getParameter($name = null) {}
+ public function canSetParameter($name = null, $value = null) {}
+}
+
+class DOMDocument extends \DOMNode {
+
+ // properties
+ public $actualEncoding;
+ public $config;
+ public $doctype;
+ public $documentElement;
+ public $documentURI;
+ public $encoding;
+ public $formatOutput;
+ public $implementation;
+ public $preserveWhiteSpace;
+ public $recover;
+ public $resolveExternals;
+ public $standalone;
+ public $strictErrorChecking;
+ public $substituteEntities;
+ public $validateOnParse;
+ public $version;
+ public $xmlEncoding;
+ public $xmlStandalone;
+ public $xmlVersion;
+
+ // methods
+ public function createElement($tagName, $value = null) {}
+ public function createDocumentFragment() {}
+ public function createTextNode($data) {}
+ public function createComment($data) {}
+ public function createCDATASection($data) {}
+ public function createProcessingInstruction($target, $data) {}
+ public function createAttribute($name) {}
+ public function createEntityReference($name) {}
+ public function getElementsByTagName($tagName) {}
+ public function importNode(\DOMNode $importedNode, $deep) {}
+ public function createElementNS($namespaceURI, $qualifiedName, $value = null) {}
+ public function createAttributeNS($namespaceURI, $qualifiedName) {}
+ public function getElementsByTagNameNS($namespaceURI, $localName) {}
+ public function getElementById($elementId) {}
+ public function adoptNode(\DOMNode $source) {}
+ public function normalizeDocument() {}
+ public function renameNode(\DOMNode $node, $namespaceURI, $qualifiedName) {}
+ public function load($source, $options = null) {}
+ public function save($file) {}
+ public function loadXML($source, $options = null) {}
+ public function saveXML(\DOMNode $node = null, $options = null) {}
+ public function __construct($version = null, $encoding = null) {}
+ public function validate() {}
+ public function xinclude($options = null) {}
+ public function loadHTML($source, $options = null) {}
+ public function loadHTMLFile($source, $options = null) {}
+ public function saveHTML() {}
+ public function saveHTMLFile($file) {}
+ public function schemaValidate($filename) {}
+ public function schemaValidateSource($source) {}
+ public function relaxNGValidate($filename) {}
+ public function relaxNGValidateSource($source) {}
+ public function registerNodeClass($baseClass, $extendedClass) {}
+}
+
+class DOMDocumentFragment extends \DOMNode {
+
+ // properties
+ public $name;
+
+ // methods
+ public function __construct() {}
+ public function appendXML($data) {}
+}
+
+class DOMDocumentType extends \DOMNode {
+
+ // properties
+ public $entities;
+ public $internalSubset;
+ public $name;
+ public $notations;
+ public $publicId;
+ public $systemId;
+}
+
+class DOMDomError {
+}
+
+class DOMElement extends \DOMNode {
+
+ // properties
+ public $schemaTypeInfo;
+ public $tagName;
+
+ // methods
+ public function getAttribute($name) {}
+ public function setAttribute($name, $value) {}
+ public function removeAttribute($name) {}
+ public function getAttributeNode($name) {}
+ public function setAttributeNode(\DOMAttr $newAttr) {}
+ public function removeAttributeNode(\DOMAttr $oldAttr) {}
+ public function getElementsByTagName($name) {}
+ public function getAttributeNS($namespaceURI, $localName) {}
+ public function setAttributeNS($namespaceURI, $qualifiedName, $value) {}
+ public function removeAttributeNS($namespaceURI, $localName) {}
+ public function getAttributeNodeNS($namespaceURI, $localName) {}
+ public function setAttributeNodeNS(\DOMAttr $newAttr) {}
+ public function getElementsByTagNameNS($namespaceURI, $localName) {}
+ public function hasAttribute($name) {}
+ public function hasAttributeNS($namespaceURI, $localName) {}
+ public function setIdAttribute($name, $isId) {}
+ public function setIdAttributeNS($namespaceURI, $localName, $isId) {}
+ public function setIdAttributeNode(\DOMAttr $attr, $isId) {}
+ public function __construct($name, $value = null, $uri = null) {}
+}
+
+class DOMEntity extends \DOMNode {
+
+ // properties
+ public $actualEncoding;
+ public $encoding;
+ public $notationName;
+ public $publicId;
+ public $systemId;
+ public $version;
+}
+
+class DOMEntityReference extends \DOMNode {
+
+ // properties
+ public $name;
+
+ // methods
+ public function __construct($name) {}
+}
+
+class DOMErrorHandler {
+
+ // methods
+ public function handleError(\DOMDomError $error) {}
+}
+
+final class DOMException extends \Exception {
+
+ // properties
+ public $code;
+ protected $message;
+ protected $file;
+ protected $line;
+}
+
+class DOMImplementation {
+
+ // properties
+ public $name;
+
+ // methods
+ public function getFeature($feature, $version) {}
+ public function hasFeature() {}
+ public function createDocumentType($qualifiedName, $publicId, $systemId) {}
+ public function createDocument($namespaceURI, $qualifiedName, \DOMDocumentType $docType) {}
+}
+
+class DOMImplementationList {
+
+ // methods
+ public function item($index) {}
+}
+
+class DOMImplementationSource {
+
+ // methods
+ public function getDomimplementation($features) {}
+ public function getDomimplementations($features) {}
+}
+
+class DOMLocator {
+}
+
+class DOMNameList {
+
+ // methods
+ public function getName($index) {}
+ public function getNamespaceURI($index) {}
+}
+
+class DOMNameSpaceNode {
+}
+
+class DOMNamedNodeMap implements \Traversable, \Countable {
+
+ // properties
+ public $length;
+
+ // methods
+ public function getNamedItem($name) {}
+ public function setNamedItem(\DOMNode $arg) {}
+ public function removeNamedItem($name = null) {}
+ public function item($index = null) {}
+ public function getNamedItemNS($namespaceURI = null, $localName = null) {}
+ public function setNamedItemNS(\DOMNode $arg = null) {}
+ public function removeNamedItemNS($namespaceURI = null, $localName = null) {}
+ public function count() {}
+}
+
+class DOMNode {
+
+ // properties
+ public $attributes;
+ public $baseURI;
+ public $childNodes;
+ public $firstChild;
+ public $lastChild;
+ public $localName;
+ public $namespaceURI;
+ public $nextSibling;
+ public $nodeName;
+ public $nodeType;
+ public $nodeValue;
+ public $ownerDocument;
+ public $parentNode;
+ public $prefix;
+ public $previousSibling;
+ public $textContent;
+
+ // methods
+ public function insertBefore(\DOMNode $newChild, \DOMNode $refChild = null) {}
+ public function replaceChild(\DOMNode $newChild, \DOMNode $oldChild) {}
+ public function removeChild(\DOMNode $oldChild) {}
+ public function appendChild(\DOMNode $newChild) {}
+ public function hasChildNodes() {}
+ public function cloneNode($deep = null) {}
+ public function normalize() {}
+ public function isSupported($feature, $version) {}
+ public function hasAttributes() {}
+ public function compareDocumentPosition(\DOMNode $other) {}
+ public function isSameNode(\DOMNode $other) {}
+ public function lookupPrefix($namespaceURI) {}
+ public function isDefaultNamespace($namespaceURI) {}
+ public function lookupNamespaceUri($prefix) {}
+ public function isEqualNode(\DOMNode $arg) {}
+ public function getFeature($feature, $version) {}
+ public function setUserData($key, $data, $handler) {}
+ public function getUserData($key) {}
+ public function getNodePath() {}
+ public function getLineNo() {}
+ public function C14N($exclusive = null, $with_comments = null, array $xpath = null, array $ns_prefixes = null) {}
+ public function C14NFile($uri, $exclusive = null, $with_comments = null, array $xpath = null, array $ns_prefixes = null) {}
+}
+
+class DOMNodeList implements \Traversable, \Countable {
+
+ // properties
+ public $length;
+
+ // methods
+ public function item($index) {}
+ public function count() {}
+}
+
+class DOMNotation extends \DOMNode {
+
+ // properties
+ public $publicId;
+ public $systemId;
+}
+
+class DOMProcessingInstruction extends \DOMNode {
+
+ // properties
+ public $data;
+ public $target;
+
+ // methods
+ public function __construct($name, $value = null) {}
+}
+
+class DOMStringExtend {
+
+ // methods
+ public function findOffset16($offset32) {}
+ public function findOffset32($offset16) {}
+}
+
+class DOMStringList {
+
+ // methods
+ public function item($index) {}
+}
+
+class DOMText extends \DOMCharacterData {
+
+ // properties
+ public $wholeText;
+
+ // methods
+ public function splitText($offset) {}
+ public function isWhitespaceInElementContent() {}
+ public function isElementContentWhitespace() {}
+ public function replaceWholeText($content) {}
+ public function __construct($value = null) {}
+}
+
+class DOMTypeinfo {
+}
+
+class DOMUserDataHandler {
+
+ // methods
+ public function handle() {}
+}
+
+class DOMXPath {
+
+ // properties
+ public $document;
+
+ // methods
+ public function __construct(\DOMDocument $doc) {}
+ public function registerNamespace($prefix, $uri) {}
+ public function query($expr, \DOMNode $context = null, $registerNodeNS = null) {}
+ public function evaluate($expr, \DOMNode $context = null, $registerNodeNS = null) {}
+ public function registerPhpFunctions() {}
+}
+
+function dom_import_simplexml($node) {}
+const DOMSTRING_SIZE_ERR = 2;
+const DOM_HIERARCHY_REQUEST_ERR = 3;
+const DOM_INDEX_SIZE_ERR = 1;
+const DOM_INUSE_ATTRIBUTE_ERR = 10;
+const DOM_INVALID_ACCESS_ERR = 15;
+const DOM_INVALID_CHARACTER_ERR = 5;
+const DOM_INVALID_MODIFICATION_ERR = 13;
+const DOM_INVALID_STATE_ERR = 11;
+const DOM_NAMESPACE_ERR = 14;
+const DOM_NOT_FOUND_ERR = 8;
+const DOM_NOT_SUPPORTED_ERR = 9;
+const DOM_NO_DATA_ALLOWED_ERR = 6;
+const DOM_NO_MODIFICATION_ALLOWED_ERR = 7;
+const DOM_PHP_ERR = 0;
+const DOM_SYNTAX_ERR = 12;
+const DOM_VALIDATION_ERR = 16;
+const DOM_WRONG_DOCUMENT_ERR = 4;
+const XML_ATTRIBUTE_CDATA = 1;
+const XML_ATTRIBUTE_DECL_NODE = 16;
+const XML_ATTRIBUTE_ENTITY = 6;
+const XML_ATTRIBUTE_ENUMERATION = 9;
+const XML_ATTRIBUTE_ID = 2;
+const XML_ATTRIBUTE_IDREF = 3;
+const XML_ATTRIBUTE_IDREFS = 4;
+const XML_ATTRIBUTE_NMTOKEN = 7;
+const XML_ATTRIBUTE_NMTOKENS = 8;
+const XML_ATTRIBUTE_NODE = 2;
+const XML_ATTRIBUTE_NOTATION = 10;
+const XML_CDATA_SECTION_NODE = 4;
+const XML_COMMENT_NODE = 8;
+const XML_DOCUMENT_FRAG_NODE = 11;
+const XML_DOCUMENT_NODE = 9;
+const XML_DOCUMENT_TYPE_NODE = 10;
+const XML_DTD_NODE = 14;
+const XML_ELEMENT_DECL_NODE = 15;
+const XML_ELEMENT_NODE = 1;
+const XML_ENTITY_DECL_NODE = 17;
+const XML_ENTITY_NODE = 6;
+const XML_ENTITY_REF_NODE = 5;
+const XML_HTML_DOCUMENT_NODE = 13;
+const XML_LOCAL_NAMESPACE = 18;
+const XML_NAMESPACE_DECL_NODE = 18;
+const XML_NOTATION_NODE = 12;
+const XML_PI_NODE = 7;
+const XML_TEXT_NODE = 3;
+}
*/
public function send( $recipients, array $headers, $body ) {
}
+ /**
+ * @return string
+ */
+ public function getMessage() {
+ }
}
class Mail_smtp extends Mail {
* Jaska Zedlik
* Jason Richey
* Jayprakash12345
-* jeblad
* Jeff Hobson
* Jeff Janes
* jeff303
* Jerome Jamnicky
* Jesús Martínez Novo
* jhobs
-* jhsoby
* Jiabao
* Jidanni
* Jimmy Collins
Use OutputPage::getRevisionId() and OutputPage::isRevisionCurrent() instead.
* LoadBalancer::haveIndex() and LoadBalancer::isNonZeroLoad() have
been deprecated.
-* MediaWikiIntegrationTest::setContentLang() has been deprecated. Use
- setMwGlobals( 'wgLanguageCode', 'xxx' ) to set a different site language
- code, or setService( 'ContentLanguage', $myObj ) to set a specific Language
- object. Service resets and $wgContLang will be handled automatically.
* FileBackend::getWikiId() has been deprecated.
Use FileBackend::getDomainId() instead.
* User::getRights() and User::$mRights have been deprecated. Use
* Really delete the file
*
* @param Title &$title
- * @param File &$file
+ * @param LocalFile &$file
* @param string &$oldimage Archive name
* @param string $reason Reason of the deletion
* @param bool $suppress Whether to mark all deleted versions as restricted
if ( $oldimage ) {
$page = null;
$status = $file->deleteOld( $oldimage, $reason, $suppress, $user );
- if ( $status->ok ) {
+ if ( $status->isOK() ) {
// Need to do a log item
$logComment = wfMessage( 'deletedrevision', $oldimage )->inContentLanguage()->text();
if ( trim( $reason ) != '' ) {
namespace MediaWiki\Navigation;
-use MediaWiki\Linker\LinkTarget;
-use MessageLocalizer;
use Html;
+use MessageLocalizer;
+use Title;
/**
* Helper class for generating prev/next links for paging.
+ * @todo Use LinkTarget instead of Title
*
* @since 1.34
*/
*/
private $messageLocalizer;
+ /**
+ * @param MessageLocalizer $messageLocalizer
+ */
public function __construct( MessageLocalizer $messageLocalizer ) {
$this->messageLocalizer = $messageLocalizer;
}
/**
* Generate (prev x| next x) (20|50|100...) type links for paging
*
- * @param LinkTarget $title LinkTarget object to link
+ * @param Title $title Title object to link
* @param int $offset
* @param int $limit
* @param array $query Optional URL query parameter string
* @param bool $atend Optional param for specified if this is the last page
* @return string
*/
- public function buildPrevNextNavigation( LinkTarget $title, $offset, $limit,
- array $query = [], $atend = false
+ public function buildPrevNextNavigation(
+ Title $title,
+ $offset,
+ $limit,
+ array $query = [],
+ $atend = false
) {
# Make 'previous' link
$prev = $this->messageLocalizer->msg( 'prevn' )->title( $title )
# Make links to set number of items per page
$numLinks = [];
+ // @phan-suppress-next-next-line PhanUndeclaredMethod
+ // @fixme MessageLocalizer doesn't have a getLanguage() method!
$lang = $this->messageLocalizer->getLanguage();
foreach ( [ 20, 50, 100, 250, 500 ] as $num ) {
$numLinks[] = $this->numLink( $title, $offset, $num, $query,
/**
* Helper function for buildPrevNextNavigation() that generates links
*
- * @param LinkTarget $title LinkTarget object to link
+ * @param Title $title Title object to link
* @param int $offset
* @param int $limit
* @param array $query Extra query parameters
* @param string $class Value of the "class" attribute of the link
* @return string HTML fragment
*/
- private function numLink( LinkTarget $title, $offset, $limit, array $query, $link,
+ private function numLink( Title $title, $offset, $limit, array $query, $link,
$tooltipMsg, $class
) {
$query = [ 'limit' => $limit, 'offset' => $offset ] + $query;
* @param Title $t
*/
public function setTitle( Title $t ) {
+ // @phan-suppress-next-next-line PhanUndeclaredMethod
+ // @fixme Not all implementations of IContextSource have this method!
$this->getContext()->setTitle( $t );
}
*/
public function isConfiguredProxy( $ip ) {
// Quick check of known singular proxy servers
- if ( in_array( $ip, $this->proxyServers ) ) {
+ if ( in_array( $ip, $this->proxyServers, true ) ) {
return true;
}
*
* run() must be declared in the subclass. It cannot be declared as abstract
* here because it has a variable parameter list.
+ * @todo Declare it as abstract after dropping HHVM
*
* @package MediaWiki\Rest
*/
class SimpleHandler extends Handler {
public function execute() {
$params = array_values( $this->getRequest()->getPathParams() );
+ // @phan-suppress-next-line PhanUndeclaredMethod
return $this->run( ...$params );
}
}
*
* @since 1.31
* @since 1.32 Renamed from MediaWiki\Storage\MutableRevisionRecord
+ * @property MutableRevisionSlots $mSlots
*/
class MutableRevisionRecord extends RevisionRecord {
$slots = new MutableRevisionSlots();
parent::__construct( $title, $slots, $dbDomain );
-
- $this->mSlots = $slots; // redundant, but nice for static analysis
}
/**
// TODO: introduce something like an UnsavedRevisionFactory service instead!
/** @var MutableRevisionRecord $rev */
$rev = $this->derivedDataUpdater->getRevision();
+ '@phan-var MutableRevisionRecord $rev';
$rev->setPageId( $title->getArticleID() );
// splitTitleString method, but the only implementation (MediaWikiTitleCodec) does
/** @var MediaWikiTitleCodec $titleCodec */
$titleCodec = MediaWikiServices::getInstance()->getTitleParser();
+ '@phan-var MediaWikiTitleCodec $titleCodec';
// MalformedTitleException can be thrown here
$parts = $titleCodec->splitTitleString( $this->mDbkeyform, $this->mDefaultNamespace );
/**
* The TitleArray class only exists to provide the newFromResult method at pre-
* sent.
+ *
+ * @method int count()
*/
abstract class TitleArray implements Iterator {
/**
$this->useTransactionalTimeLimit();
$old = $this->getRequest()->getText( 'oldimage' );
+ /** @var LocalFile $localFile */
$localFile = $this->page->getFile();
+ '@phan-var LocalFile $localFile';
$oldFile = OldLocalFile::newFromArchiveName( $this->getTitle(), $localFile->getRepo(), $old );
$source = $localFile->getArchiveVirtualUrl( $old );
$pageObj = $this->getTitleOrPageId( $params, 'fromdbmaster' );
$titleObj = $pageObj->getTitle();
if ( !$pageObj->exists() &&
+ // @phan-suppress-next-line PhanUndeclaredMethod
!( $titleObj->getNamespace() == NS_FILE && self::canDeleteFile( $pageObj->getFile() ) )
) {
$this->dieWithError( 'apierror-missingtitle' );
) {
$title = $page->getTitle();
+ // @phan-suppress-next-line PhanUndeclaredMethod There's no right typehint for it
$file = $page->getFile();
if ( !self::canDeleteFile( $file ) ) {
return self::delete( $page, $user, $reason, $tags );
* ApiResult.
* @since 1.25
* @ingroup API
+ * @phan-file-suppress PhanUndeclaredMethod Undeclared methods in IApiMessage
*/
class ApiErrorFormatter {
/** @var Title Dummy title to silence warnings from MessageCache::parse() */
$parser->startExternalParse( $titleObj, $options, Parser::OT_PREPROCESS );
$dom = $parser->preprocessToDom( $params['text'] );
if ( is_callable( [ $dom, 'saveXML' ] ) ) {
+ // @phan-suppress-next-line PhanUndeclaredMethod
$xml = $dom->saveXML();
} else {
+ // @phan-suppress-next-line PhanUndeclaredMethod
$xml = $dom->__toString();
}
if ( isset( $prop['parsetree'] ) ) {
if ( $e instanceof ApiUsageException ) {
foreach ( $e->getStatusValue()->getErrors() as $error ) {
+ // @phan-suppress-next-line PhanUndeclaredMethod
$msg = ApiMessage::create( $error )
->inLanguage( $this->getLanguage() );
$errorTitle = $this->msg( 'api-feed-error-title', $msg->getApiCode() );
$tmpFile = MediaWikiServices::getInstance()->getTempFSFileFactory()
->newTempFSFile( 'rotate_', $ext );
$dstPath = $tmpFile->getPath();
+ // @phan-suppress-next-line PhanUndeclaredMethod
$err = $handler->rotate( $file, [
'srcPath' => $srcPath,
'dstPath' => $dstPath,
$comment = wfMessage(
'rotate-comment'
)->numParams( $rotation )->inContentLanguage()->text();
+ // @phan-suppress-next-line PhanUndeclaredMethod
$status = $file->upload(
$dstPath,
$comment,
* @param IContextSource|WebRequest|null $context If this is an instance of
* FauxRequest, errors are thrown and no printing occurs
* @param bool $enableWrite Should be set to true if the api may modify data
+ * @suppress PhanUndeclaredMethod
*/
public function __construct( $context = null, $enableWrite = false ) {
if ( $context === null ) {
* @since 1.27
* @ingroup API
* @phan-file-suppress PhanTraitParentReference
+ * @phan-file-suppress PhanUndeclaredMethod
*/
trait ApiMessageTrait {
case 'xml':
$printer = $this->getMain()->createPrinterByName( 'xml' . $this->fm );
+ '@phan-var ApiFormatXML $printer';
$printer->setRootElement( 'SearchSuggestion' );
return $printer;
* @param string $search the search query
* @param array $params api request params
* @return array search results. Keys are integers.
- * @phan-return array<array{title:Title,extract:false,image:false,url:string}>
+ * @phan-return array<array{title:Title,redirect_from:?Title,extract:false,extract_trimmed:false,image:false,url:string}>
* Note that phan annotations don't support keys containing a space.
*/
private function search( $search, array $params ) {
$parser = MediaWikiServices::getInstance()->getParser();
$parser->startExternalParse( $titleObj, $popts, Parser::OT_PREPROCESS );
+ // @phan-suppress-next-line PhanUndeclaredMethod
$xml = $parser->preprocessToDom( $this->content->getText() )->__toString();
$result_array['parsetree'] = $xml;
$result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsetree';
// Filter modules based on continue parameter
$continuationManager = new ApiContinuationManager( $this, $allModules, $propModules );
$this->setContinuationManager( $continuationManager );
+ /** @var ApiQueryBase[] $modules */
$modules = $continuationManager->getRunModules();
+ '@phan-var ApiQueryBase[] $modules';
if ( !$continuationManager->isGeneratorDone() ) {
// Query modules may optimize data requests through the $this->getPageSet()
$cacheMode = $this->mPageSet->getCacheMode();
// Execute all unfinished modules
- /** @var ApiQueryBase $module */
foreach ( $modules as $module ) {
$params = $module->extractRequestParams();
$cacheMode = $this->mergeCacheMode(
$id = $restriction->getBlockId();
switch ( $restriction->getType() ) {
case 'page':
+ /** @var \MediaWiki\Block\Restriction\PageRestriction $restriction */
+ '@phan-var \MediaWiki\Block\Restriction\PageRestriction $restriction';
$value = [ 'id' => $restriction->getValue() ];
if ( $restriction->getTitle() ) {
self::addTitleInfo( $value, $restriction->getTitle() );
'cl_to' . $sort
] );
}
+ $this->addOption( 'LIMIT', $params['limit'] + 1 );
$res = $this->select( __METHOD__ );
$vals['thumbmime'] = $mime;
}
} elseif ( $mto && $mto->isError() ) {
+ /** @var MediaTransformError $mto */
+ '@phan-var MediaTransformError $mto';
$vals['thumberror'] = $mto->toText();
}
}
// Thus there should be no issue with format=xml.
$format = new FormatMetadata;
$format->setSingleLanguage( !$opts['multilang'] );
+ // @phan-suppress-next-line PhanUndeclaredMethod
$format->getContext()->setLanguage( $opts['language'] );
$extmetaArray = $format->fetchExtendedMetadata( $file );
if ( $opts['extmetadatafilter'] ) {
}
if ( $archive && $file->isOld() ) {
+ /** @var OldLocalFile $file */
+ '@phan-var OldLocalFile $file';
$vals['archivename'] = $file->getArchiveName();
}
if ( $this->fld_parsetree || ( $this->fld_content && $this->generateXML ) ) {
if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
+ /** @var WikitextContent $content */
+ '@phan-var WikitextContent $content';
$t = $content->getText(); # note: don't set $text
$parser = MediaWikiServices::getInstance()->getParser();
);
$dom = $parser->preprocessToDom( $t );
if ( is_callable( [ $dom, 'saveXML' ] ) ) {
+ // @phan-suppress-next-line PhanUndeclaredMethod
$xml = $dom->saveXML();
} else {
+ // @phan-suppress-next-line PhanUndeclaredMethod
$xml = $dom->__toString();
}
$vals['parsetree'] = $xml;
if ( $this->expandTemplates && !$this->parseContent ) {
if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
+ /** @var WikitextContent $content */
+ '@phan-var WikitextContent $content';
$text = $content->getText();
$text = MediaWikiServices::getInstance()->getParser()->preprocess(
public function __construct() {
/** @var SessionProvider $provider */
$provider = SessionManager::getGlobalSession()->getProvider();
+ '@phan-var SessionProvider $provider';
$this->expiration = $provider->getRememberUserDuration();
}
if ( $block instanceof SystemBlock ) {
$systemBlocks[] = $block;
} elseif ( $block->getType() === DatabaseBlock::TYPE_AUTO ) {
+ /** @var DatabaseBlock $block */
+ '@phan-var DatabaseBlock $block';
if ( !isset( $databaseBlocks[$block->getParentBlockId()] ) ) {
$databaseBlocks[$block->getParentBlockId()] = $block;
}
* @inheritDoc
*/
public static function newFromRow( \stdClass $row ) {
+ /** @var self $restriction */
$restriction = parent::newFromRow( $row );
+ '@phan-var self $restriction';
// If the page_namespace and the page_title were provided, add the title to
// the restriction.
*
* @since 1.33
* @param \stdClass $row
- * @return self
+ * @return static
*/
public static function newFromRow( \stdClass $row );
* but 'Bot' is unchecked, hidebots=1 will be sent.
*
* @since 1.29
+ * @method ChangesListBooleanFilter[] getFilters()
*/
class ChangesListBooleanFilterGroup extends ChangesListFilterGroup {
/**
* Registers a filter in this group
*
* @param ChangesListBooleanFilter $filter
+ * @suppress PhanParamSignaturePHPDocMismatchHasParamType,PhanParamSignatureMismatch
*/
public function registerFilter( ChangesListBooleanFilter $filter ) {
$this->filters[$filter->getName()] = $filter;
* Represents a filter group (used on ChangesListSpecialPage and descendants)
*
* @since 1.29
+ * @method registerFilter($filter)
*/
abstract class ChangesListFilterGroup {
/**
* Registers a filter in this group
*
* @param ChangesListStringOptionsFilter $filter
+ * @suppress PhanParamSignaturePHPDocMismatchHasParamType,PhanParamSignatureMismatch
*/
public function registerFilter( ChangesListStringOptionsFilter $filter ) {
$this->filters[$filter->getName()] = $filter;
/**
* Generic list for change tagging.
+ *
+ * @property ChangeTagsLogItem $current
+ * @method ChangeTagsLogItem next()
+ * @method ChangeTagsLogItem reset()
+ * @method ChangeTagsLogItem current()
+ * @phan-file-suppress PhanParamSignatureMismatch
*/
abstract class ChangeTagsList extends RevisionListBase {
function __construct( IContextSource $context, Title $title, array $ids ) {
* @return string|bool The raw text, or false if the conversion failed.
*/
public function getWikitextForTransclusion() {
+ /** @var WikitextContent $wikitext */
$wikitext = $this->convert( CONTENT_MODEL_WIKITEXT, 'lossy' );
+ '@phan-var WikitextContent $wikitext';
if ( $wikitext ) {
return $wikitext->getText();
*/
public function diff( Content $that, Language $lang = null ) {
$this->checkModelID( $that->getModel() );
-
+ /** @var self $that */
+ '@phan-var self $that';
// @todo could implement this in DifferenceEngine and just delegate here?
if ( !$lang ) {
public function serializeContent( Content $content, $format = null ) {
$this->checkFormat( $format );
+ // @phan-suppress-next-line PhanUndeclaredMethod
return $content->getText();
}
*/
public function serializeContent( Content $content, $format = null ) {
/** @var UnknownContent $content */
+ '@phan-var UnknownContent $content';
return $content->getData();
}
"document uses $myModelId but " .
"section uses $sectionModelId." );
}
+ /** @var self $with $oldtext */
+ '@phan-var self $with';
$oldtext = $this->getText();
$text = $with->getText();
if ( isset( $queue[$class] ) ) {
/** @var MergeableUpdate $existingUpdate */
$existingUpdate = $queue[$class];
+ '@phan-var MergeableUpdate $existingUpdate';
$existingUpdate->merge( $update );
// Move the update to the end to handle things like mergeable purge
// updates that might depend on the prior updates in the queue running
/** @var TextSlotDiffRenderer $slotDiffRenderer */
$slotDiffRenderer = ContentHandler::getForModelID( CONTENT_MODEL_TEXT )
->getSlotDiffRenderer( RequestContext::getMain() );
+ '@phan-var TextSlotDiffRenderer $slotDiffRenderer';
return $slotDiffRenderer->getTextDiff( $oldText, $newText );
}
use MediaWiki\MediaWikiServices as MediaWikiServicesAlias;
use MediaWiki\Storage\RevisionRecord;
use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
/**
* @ingroup SpecialPage Dump
/** @var XmlDumpWriter */
private $writer;
- /** @var IDatabase */
+ /** @var Database */
protected $db;
/** @var array|int */
}
/**
- * @param IDatabase $db
+ * @param Database $db
* @param int|array $history One of WikiExporter::FULL, WikiExporter::CURRENT,
* WikiExporter::RANGE or WikiExporter::STABLE, or an associative array:
* - offset: non-inclusive offset at which to start the query
*/
function writeUpload( $file, $dumpContents = false ) {
if ( $file->isOld() ) {
+ /** @var OldLocalFile $file */
+ '@phan-var OldLocalFile $file';
$archiveName = " " .
Xml::element( 'archivename', null, $file->getArchiveName() ) . "\n";
} else {
use MediaWiki\MediaWikiServices;
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\DBError;
+use Wikimedia\Timestamp\ConvertibleTimestamp;
/**
* Version of FileJournal that logs to a DB table
protected $domain;
/**
- * Construct a new instance from configuration.
+ * Construct a new instance from configuration. Do not call directly, use FileJournal::factory.
*
* @param array $config Includes:
* domain: database domain ID of the wiki
*/
- protected function __construct( array $config ) {
+ public function __construct( array $config ) {
parent::__construct( $config );
$this->domain = $config['domain'] ?? $config['wiki']; // b/c
return $status;
}
- $now = wfTimestamp( TS_UNIX );
+ $now = ConvertibleTimestamp::time();
$data = [];
foreach ( $entries as $entry ) {
try {
$dbw->insert( 'filejournal', $data, __METHOD__ );
+ // XXX Should we do this deterministically so it's testable? Maybe look at the last two
+ // digits of a hash of a bunch of the data?
if ( mt_rand( 0, 99 ) == 0 ) {
- $this->purgeOldLogs(); // occasionally delete old logs
+ // occasionally delete old logs
+ $this->purgeOldLogs(); // @codeCoverageIgnore
}
} catch ( DBError $e ) {
$status->fatal( 'filejournal-fail-dbquery', $this->backend );
}
$dbw = $this->getMasterDB();
- $dbCutoff = $dbw->timestamp( time() - 86400 * $this->ttlDays );
+ $dbCutoff = $dbw->timestamp( ConvertibleTimestamp::time() - 86400 * $this->ttlDays );
$dbw->delete( 'filejournal',
[ 'fj_timestamp < ' . $dbw->addQuotes( $dbCutoff ) ],
* @return string
*/
public static function getHashFromKey( $key ) {
- return strtok( $key, '.' );
+ $sha1 = strtok( $key, '.' );
+ if ( is_string( $sha1 ) && strlen( $sha1 ) === 32 && $sha1[0] === '0' ) {
+ $sha1 = substr( $sha1, 1 );
+ }
+ return $sha1;
}
/**
$thumb = false;
} elseif ( $thumb->isError() ) { // transform error
/** @var MediaTransformError $thumb */
+ '@phan-var MediaTransformError $thumb';
$this->lastError = $thumb->toText();
// Ignore errors if requested
if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
: FSFile::getSha1Base36FromPath( $srcPath );
/** @var FileBackendDBRepoWrapper $wrapperBackend */
$wrapperBackend = $repo->getBackend();
+ '@phan-var FileBackendDBRepoWrapper $wrapperBackend';
$dst = $wrapperBackend->getPathForSHA1( $sha1 );
$status = $repo->quickImport( $src, $dst );
if ( $flags & File::DELETE_SOURCE ) {
$oldTitleFile->purgeEverything();
foreach ( $archiveNames as $archiveName ) {
/** @var OldLocalFile $oldTitleFile */
+ '@phan-var OldLocalFile $oldTitleFile';
$oldTitleFile->purgeOldThumbnails( $archiveName );
}
$newTitleFile->purgeEverything();
public function execute() {
$repo = $this->file->repo;
$status = $repo->newGood();
+ /** @var LocalFile $destFile */
$destFile = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()
->newFile( $this->target );
+ '@phan-var LocalFile $destFile';
$this->file->lock();
$destFile->lock(); // quickly fail if destination is not available
* (defined in htmlform.Element.js) picks up the extra config when constructed using OO.ui.infuse().
*
* Currently only supports passing 'hide-if' data.
+ * @phan-file-suppress PhanUndeclaredMethod
*/
trait HTMLFormElement {
/**
* Take care of whatever is necessary to perform the URI request.
*
- * @return StatusValue
+ * @return Status
* @note currently returns Status for B/C
*/
public function execute() {
$user
);
} else {
+ '@phan-var LocalFile $file';
$flags = 0;
$status = $file->upload(
$source,
* This will return a cached connection if one is available.
*
* @return Status
+ * @suppress PhanUndeclaredMethod
*/
public function getConnection() {
if ( $this->db ) {
public function setupSchemaVars() {
$status = $this->getConnection();
if ( $status->isOK() ) {
+ // @phan-suppress-next-line PhanUndeclaredMethod
$status->value->setSchemaVars( $this->getSchemaVars() );
} else {
$msg = __METHOD__ . ': unexpected error while establishing'
$cl = $this->maintenance->runChild(
RebuildLocalisationCache::class, 'rebuildLocalisationCache.php'
);
+ '@phan-var RebuildLocalisationCache $cl';
$this->output( "Rebuilding localisation cache...\n" );
$cl->setForce();
$cl->execute();
$task = $this->maintenance->runChild(
MigrateImageCommentTemp::class, 'migrateImageCommentTemp.php'
);
+ // @phan-suppress-next-line PhanUndeclaredMethod
$task->setForce();
$ok = $task->execute();
$this->output( $ok ? "done.\n" : "errors were encountered.\n" );
if ( $this->db->fieldExists( 'archive', 'ar_text', __METHOD__ ) ) {
$this->output( "Migrating archive ar_text to modern storage.\n" );
$task = $this->maintenance->runChild( MigrateArchiveText::class, 'migrateArchiveText.php' );
+ // @phan-suppress-next-line PhanUndeclaredMethod
$task->setForce();
if ( $task->execute() ) {
$this->applyPatch( 'patch-drop-ar_text.sql', false,
if ( !$status->isOK() ) {
return $status;
}
+ // @phan-suppress-next-line PhanUndeclaredMethod
$status->value->insert(
'site_stats',
[
* @var Database $conn
*/
$conn = $status->value;
+ '@phan-var Database $conn';
// Check version
return static::meetsMinimumRequirement( $conn->getServerVersion() );
*
* @ingroup Deployment
* @since 1.17
+ * @property DatabaseMysqlBase $db
*/
class MysqlUpdater extends DatabaseUpdater {
protected function getCoreUpdateList() {
if ( !$status->isOK() ) {
return $status;
}
+ // @phan-suppress-next-line PhanUndeclaredMethod
$exists = $status->value->roleExists( $this->getVar( 'wgDBuser' ) );
}
}
/** @var DatabasePostgres $conn */
$conn = $status->value;
+ '@phan-var DatabasePostgres $conn';
// Create the schema if necessary
$schema = $this->getVar( 'wgDBmwschema' );
if ( !$status->isOK() ) {
return $status;
}
+ /** @var DatabasePostgres $conn */
$conn = $status->value;
+ '@phan-var DatabasePostgres $conn';
$safeuser = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
$safepass = $conn->addQuotes( $this->getVar( 'wgDBpassword' ) );
/** @var DatabasePostgres $conn */
$conn = $status->value;
+ '@phan-var DatabasePostgres $conn';
if ( $conn->tableExists( 'archive' ) ) {
$status->warning( 'config-install-tables-exist' );
# Special case for Creative Commons partner chooser box.
if ( $this->request->getVal( 'SubmitCC' ) ) {
+ /** @var WebInstallerOptions $page */
$page = $this->getPageByName( 'Options' );
+ '@phan-var WebInstallerOptions $page';
$this->output->useShortHeader();
$this->output->allowFrames();
$page->submitCC();
}
if ( $this->request->getVal( 'ShowCC' ) ) {
+ /** @var WebInstallerOptions $page */
$page = $this->getPageByName( 'Options' );
+ '@phan-var WebInstallerOptions $page';
$this->output->useShortHeader();
$this->output->allowFrames();
$this->output->addHTML( $page->getCCDoneBox() );
foreach ( $moduleNames as $moduleName ) {
/** @var ResourceLoaderFileModule $module */
$module = $resourceLoader->getModule( $moduleName );
+ '@phan-var ResourceLoaderFileModule $module';
if ( !$module ) {
// T98043: Don't fatal, but it won't look as pretty.
continue;
abstract class FileJournal {
/** @var string */
protected $backend;
- /** @var int */
+ /** @var int|false */
protected $ttlDays;
/**
* A starting change ID and/or limit can be specified.
*
* @param int|null $start Starting change ID or null
- * @param int $limit Maximum number of items to return
+ * @param int $limit Maximum number of items to return (0 = unlimited)
* @param string|null &$next Updated to the ID of the next entry.
* @return array List of associative arrays, each having:
* id : unique, monotonic, ID for this change
// custom prefixes used by thing like WANObjectCache, limit to 205.
$charsLeft = 205 - strlen( $keyspace ) - count( $args );
- $args = array_map(
- function ( $arg ) use ( &$charsLeft ) {
- $arg = strtr( $arg, ' ', '_' );
+ foreach ( $args as &$arg ) {
+ $arg = strtr( $arg, ' ', '_' );
- // Make sure %, #, and non-ASCII chars are escaped
- $arg = preg_replace_callback(
- '/[^\x21-\x22\x24\x26-\x39\x3b-\x7e]+/',
- function ( $m ) {
- return rawurlencode( $m[0] );
- },
- $arg
- );
+ // Make sure %, #, and non-ASCII chars are escaped
+ $arg = preg_replace_callback(
+ '/[^\x21-\x22\x24\x26-\x39\x3b-\x7e]+/',
+ function ( $m ) {
+ return rawurlencode( $m[0] );
+ },
+ $arg
+ );
- // 33 = 32 characters for the MD5 + 1 for the '#' prefix.
- if ( $charsLeft > 33 && strlen( $arg ) > $charsLeft ) {
- $arg = '#' . md5( $arg );
- }
+ // 33 = 32 characters for the MD5 + 1 for the '#' prefix.
+ if ( $charsLeft > 33 && strlen( $arg ) > $charsLeft ) {
+ $arg = '#' . md5( $arg );
+ }
- $charsLeft -= strlen( $arg );
- return $arg;
- },
- $args
- );
+ $charsLeft -= strlen( $arg );
+ }
if ( $charsLeft < 0 ) {
return $keyspace . ':BagOStuff-long-key:##' . md5( implode( ':', $args ) );
$lang = $this->getLanguage();
$pm = MediaWikiServices::getInstance()->getPermissionManager();
$timestamp = wfTimestamp( TS_MW, $file->getTimestamp() );
+ // @phan-suppress-next-line PhanUndeclaredMethod
$img = $iscur ? $file->getName() : $file->getArchiveName();
$userId = $file->getUser( 'id' );
$userText = $file->getUser( 'text' );
* @ingroup Media
*
* @property WikiFilePage $mPage Set by overwritten newPage() in this class
+ * @method WikiFilePage getPage()
*/
class ImagePage extends Article {
/** @var File|false */
/**
* Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
+ *
+ * @method array getActionOverrides()
+ * @method string getUserText($audience=1,User $user=null)
+ * @method string getTimestamp()
+ * @method Title getTitle()
*/
interface Page {
}
/**
* @ingroup Parser
+ * @property string[] $out
*/
// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
class PPDPart_Hash extends PPDPart {
* @ingroup Parser
*/
class PPDStack {
- public $stack, $rootAccum;
+ /** @var PPDStackElement[] */
+ public $stack;
+ public $rootAccum;
/**
- * @var PPDStack|false
+ * @var PPDStackElement|false
*/
public $top;
public $out;
/**
* @ingroup Parser
+ * @property PPDPart_Hash[] $parts
*/
// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
class PPDStackElement_Hash extends PPDStackElement {
*/
public function breakSyntax( $openingCount = false ) {
if ( $this->open == "\n" ) {
- // @phan-suppress-next-line PhanTypeMismatchArgumentInternal
$accum = array_merge( [ $this->savedPrefix ], $this->parts[0]->out );
} else {
if ( $openingCount === false ) {
} else {
$accum[++$lastIndex] = '|';
}
- // @phan-suppress-next-line PhanTypeMismatchForeach
+
foreach ( $part->out as $node ) {
if ( is_string( $node ) && is_string( $accum[$lastIndex] ) ) {
$accum[$lastIndex] .= $node;
* An expansion frame, used as a context to expand the result of preprocessToObj()
* @deprecated since 1.34, use PPFrame_Hash
* @ingroup Parser
+ * @phan-file-suppress PhanUndeclaredMethod
*/
// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
class PPFrame_DOM implements PPFrame {
/**
* @deprecated since 1.34, use PPNode_Hash_{Tree,Text,Array,Attr}
* @ingroup Parser
+ * @phan-file-suppress PhanUndeclaredMethod
*/
// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
class PPNode_DOM implements PPNode {
*/
private static function normalizeSectionName( $text ) {
# T90902: ensure the same normalization is applied for IDs as to links
+ /** @var MediaWikiTitleCodec $titleParser */
$titleParser = MediaWikiServices::getInstance()->getTitleParser();
+ '@phan-var MediaWikiTitleCodec $titleParser';
try {
$parts = $titleParser->splitTitleString( "#$text" );
}
$i += $count;
} elseif ( $found == 'close' ) {
+ /** @var PPDStackElement_Hash $piece */
$piece = $stack->top;
+ '@phan-var PPDStackElement_Hash $piece';
# lets check if there are enough characters for closing brace
$maxCount = $piece->count;
if ( $piece->close === '}-' && $curChar === '}' ) {
// Construct pseudo-hash based on params and arguments
/** @var ParameterizedPassword $passObj */
$passObj = $this->factory->newFromType( $type );
+ '@phan-var ParameterizedPassword $passObj';
$params = '';
$args = '';
// Hash the last hash with the next type in the layer
$passObj = $this->factory->newFromCiphertext( $existingHash );
+ '@phan-var ParameterizedPassword $passObj';
$passObj->crypt( $lastHash );
// Move over the params and args
// Construct pseudo-hash based on params and arguments
/** @var ParameterizedPassword $passObj */
$passObj = $this->factory->newFromType( $type );
+ '@phan-var ParameterizedPassword $passObj';
$params = '';
$args = '';
// Hash the last hash with the next type in the layer
$passObj = $this->factory->newFromCiphertext( $existingHash );
+ '@phan-var ParameterizedPassword $passObj';
$passObj->crypt( $lastHash );
// Move over the params and args
if ( !$status->isOK() ) {
return $status;
}
+ /** @var RedisConnRef $conn */
$conn = $status->value;
+ '@phan-var RedisConnRef $conn';
// phpcs:disable Generic.Files.LineLength
static $script =
if ( !$status->isOK() ) {
return $status;
}
+ /** @var RedisConnRef $conn */
$conn = $status->value;
+ '@phan-var RedisConnRef $conn';
$now = microtime( true );
try {
* Handle the form submission if everything validated properly
*
* @param array $formData
- * @param HTMLForm $form
+ * @param PreferencesFormOOUI $form
* @param array[] $formDescriptor
* @return bool|Status|string
*/
- protected function saveFormData( $formData, HTMLForm $form, array $formDescriptor ) {
- /** @var \User $user */
+ protected function saveFormData( $formData, PreferencesFormOOUI $form, array $formDescriptor ) {
$user = $form->getModifiedUser();
$hiddenPrefs = $this->options->get( 'HiddenPrefs' );
$result = true;
* Save the form data and reload the page
*
* @param array $formData
- * @param HTMLForm $form
+ * @param PreferencesFormOOUI $form
* @param array $formDescriptor
* @return Status
*/
- protected function submitForm( array $formData, HTMLForm $form, array $formDescriptor ) {
+ protected function submitForm(
+ array $formData,
+ PreferencesFormOOUI $form,
+ array $formDescriptor
+ ) {
$res = $this->saveFormData( $formData, $form, $formDescriptor );
if ( $res === true ) {
* @ingroup Profiler
*
* @since 1.25
+ * @property ProfilerXhprof $collector
*/
class ProfilerOutputDump extends ProfilerOutput {
/**
* Item class for a filearchive table row
+ *
+ * @property ArchivedFile $file
+ * @property RevDelArchivedFileList $list
*/
class RevDelArchivedFileItem extends RevDelFileItem {
- /** @var RevDelArchivedFileList $list */
- /** @var ArchivedFile $file */
/** @var LocalFile */
protected $lockFile;
}
public function doPostCommitUpdates( array $visibilityChangeMap ) {
+ /** @var LocalFile $file */
$file = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()
->newFile( $this->title );
+ '@phan-var LocalFile $file';
$file->purgeCache();
$file->purgeDescription();
* needs to be able to make a query from a set of identifiers to pull
* relevant rows, to return RevDelItem subclasses wrapping them, and
* to wrap bulk update operations.
+ *
+ * @property RevDelItem $current
+ * @method RevDelItem next()
+ * @method RevDelItem reset()
+ * @method RevDelItem current()
+ * @phan-file-suppress PhanParamSignatureMismatch
*/
abstract class RevDelList extends RevisionListBase {
function __construct( IContextSource $context, Title $title, array $ids ) {
/**
* Item class for a live revision table row
+ *
+ * @property RevDelRevisionList $list
*/
class RevDelRevisionItem extends RevDelItem {
/** @var Revision */
* This trait can be used directly by extensions providing a SearchEngine.
*
* @ingroup Search
+ * @phan-file-suppress PhanUndeclaredMethod
*/
trait SearchResultSetTrait {
/**
/** @var array Track original session fields for later modification check */
protected $sessionFieldCache = [];
- protected function __construct( SessionManagerInterface $manager ) {
+ protected function __construct( SessionManager $manager ) {
$this->setEnableFlags(
\RequestContext::getMain()->getConfig()->get( 'PHPSessionHandling' )
);
/**
* Install a session handler for the current web request
- * @param SessionManagerInterface $manager
+ * @param SessionManager $manager
*/
- public static function install( SessionManagerInterface $manager ) {
+ public static function install( SessionManager $manager ) {
if ( self::$instance ) {
$manager->setupPHPSessionHandler( self::$instance );
return;
$this->idIsSafe = $data['idIsSafe'];
$this->forceUse = $data['forceUse'] && $this->provider;
} else {
+ // @phan-suppress-next-line PhanUndeclaredMethod
$this->id = $this->provider->getManager()->generateSessionId();
$this->idIsSafe = true;
$this->forceUse = false;
/**
* Get the global SessionManager
- * @return SessionManagerInterface
- * (really a SessionManager, but this is to make IDEs less confused)
+ * @return self
*/
public static function singleton() {
if ( self::$instance === null ) {
/** @var CreditsAction $action */
$action = Action::factory(
'credits', $this->getWikiPage(), $this->getContext() );
+ '@phan-var CreditsAction $action';
$tpl->set( 'credits',
$action->getCredits( $wgMaxCredits, $wgShowCreditsIfMax ) );
} else {
$isLoggedIn = $this->getUser()->isLoggedIn();
$continuePart = $this->isContinued() ? 'continue-' : '';
$anotherPart = $isLoggedIn ? 'another-' : '';
+ // @phan-suppress-next-line PhanUndeclaredMethod
$expiration = $this->getRequest()->getSession()->getProvider()->getRememberUserDuration();
$expirationDays = ceil( $expiration / ( 3600 * 24 ) );
$secureLoginLink = '';
return $title;
}
+ // @phan-suppress-next-line PhanUndeclaredMethod
$context->setTitle( $page->getPageTitle( $par ) );
} elseif ( !$page->isIncludable() ) {
return false;
$opts->fetchValuesFromRequest( $this->getRequest() );
$opts->validateIntBounds( 'limit', 0, 5000 );
- $pager = new AllMessagesTablePager( $this->getContext(), $opts );
+ $pager = new AllMessagesTablePager( $this->getContext(), $opts, $this->getLinkRenderer() );
$formDescriptor = [
'prefix' => [
foreach ( $block->getRestrictions() as $restriction ) {
switch ( $restriction->getType() ) {
case PageRestriction::TYPE:
+ /** @var PageRestriction $restriction */
+ '@phan-var PageRestriction $restriction';
if ( $restriction->getTitle() ) {
$pageRestrictions[] = $restriction->getTitle()->getPrefixedText();
}
'hideMinor' => $this->opts['hideMinor'],
'nsInvert' => $this->opts['nsInvert'],
'associated' => $this->opts['associated'],
- ] );
+ ], $this->getLinkRenderer() );
if ( IP::isValidRange( $target ) && !$pager->isQueryableRange( $target ) ) {
// Valid range, but outside CIDR limit.
$this->getForm();
- $pager = new DeletedContribsPager( $this->getContext(), $target, $opts->getValue( 'namespace' ) );
+ $pager = new DeletedContribsPager( $this->getContext(), $target, $opts->getValue( 'namespace' ),
+ $this->getLinkRenderer() );
if ( !$pager->getNumRows() ) {
$out->addWikiMsg( 'nocontribs' );
$dom = $parser->preprocessToDom( $input );
if ( method_exists( $dom, 'saveXML' ) ) {
+ // @phan-suppress-next-line PhanUndeclaredMethod
$xml = $dom->saveXML();
} else {
+ // @phan-suppress-next-line PhanUndeclaredMethod
$xml = $dom->__toString();
}
}
$userName,
$search,
$this->including(),
- $showAll
+ $showAll,
+ $this->getLinkRenderer()
);
$out = $this->getOutput();
$dbr->addQuotes( '/' ),
'img_minor_mime',
$dbr->addQuotes( ';' ),
- 'COUNT(*)',
+ $dbr->buildStringCast( 'COUNT(*)' ),
$dbr->addQuotes( ';' ),
- 'SUM( img_size )'
+ $dbr->buildStringCast( 'SUM( img_size )' )
] );
return [
'tables' => [ 'image' ],
$this->buildForm( $context );
}
- $pager = new NewFilesPager( $context, $opts );
+ $pager = new NewFilesPager( $context, $opts, $this->getLinkRenderer() );
$out->addHTML( $pager->getBody() );
if ( !$this->including() ) {
$user = $this->getUser();
$significance = $this->getFilterGroup( 'significance' );
+ /** @var ChangesListBooleanFilter $hideMinor */
$hideMinor = $significance->getFilter( 'hideminor' );
+ '@phan-var ChangesListBooleanFilter $hideMinor';
$hideMinor->setDefault( $user->getBoolOption( 'hideminor' ) );
$automated = $this->getFilterGroup( 'automated' );
+ /** @var ChangesListBooleanFilter $hideBots */
$hideBots = $automated->getFilter( 'hidebots' );
+ '@phan-var ChangesListBooleanFilter $hideBots';
$hideBots->setDefault( true );
+ /** @var ChangesListStringOptionsFilterGroup|null $reviewStatus */
$reviewStatus = $this->getFilterGroup( 'reviewStatus' );
+ '@phan-var ChangesListStringOptionsFilterGroup|null $reviewStatus';
if ( $reviewStatus !== null ) {
// Conditional on feature being available and rights
if ( $user->getBoolOption( 'hidepatrolled' ) ) {
$reviewStatus->setDefault( 'unpatrolled' );
$legacyReviewStatus = $this->getFilterGroup( 'legacyReviewStatus' );
+ /** @var ChangesListBooleanFilter $legacyHidePatrolled */
$legacyHidePatrolled = $legacyReviewStatus->getFilter( 'hidepatrolled' );
+ '@phan-var ChangesListBooleanFilter $legacyHidePatrolled';
$legacyHidePatrolled->setDefault( true );
}
}
$changeType = $this->getFilterGroup( 'changeType' );
+ /** @var ChangesListBooleanFilter $hideCategorization */
$hideCategorization = $changeType->getFilter( 'hidecategorization' );
+ '@phan-var ChangesListBooleanFilter $hideCategorization';
if ( $hideCategorization !== null ) {
// Conditional on feature being available
$hideCategorization->setDefault( $user->getBoolOption( 'hidecategorization' ) );
$buttonFields = [];
if ( $isText ) {
+ '@phan-var TextContent $content';
// TODO: MCR: make this work for multiple slots
// source view for textual content
$sourceView = Xml::element( 'textarea', [
*
* @param string|null $par String if any subpage provided, else null
* @throws UserBlockedError|PermissionsError
+ * @suppress PhanUndeclaredMethod
*/
public function execute( $par ) {
$user = $this->getUser();
$this->getOutput()->addWikiTextAsInterface( $status->getWikiText() );
return;
- } else {
- $user = $status->value;
}
+ /** @var User $user */
+ $user = $status->value;
+ '@phan-var User $user';
+
$groups = $user->getGroups();
$groupMemberships = $user->getGroupMemberships();
$this->showEditUserGroupsForm( $user, $groups, $groupMemberships );
/**
* @inheritDoc
+ * @suppress PhanUndeclaredMethod
*/
protected function registerFilters() {
parent::registerFilters();
*/
use MediaWiki\MediaWikiServices;
+use MediaWiki\Linker\LinkRenderer;
use Wikimedia\Rdbms\FakeResultWrapper;
/**
/**
* @param IContextSource|null $context
* @param FormOptions $opts
+ * @param LinkRenderer $linkRenderer
*/
- public function __construct( IContextSource $context = null, FormOptions $opts ) {
- parent::__construct( $context );
+ public function __construct( IContextSource $context = null, FormOptions $opts,
+ LinkRenderer $linkRenderer
+ ) {
+ parent::__construct( $context, $linkRenderer );
$this->mIndexField = 'am_title';
// FIXME: Why does this need to be set to DIR_DESCENDING to produce ascending ordering?
switch ( $restriction->getType() ) {
case PageRestriction::TYPE:
+ '@phan-var PageRestriction $restriction';
if ( $restriction->getTitle() ) {
$items[$restriction->getType()][] = Html::rawElement(
'li',
* @ingroup Pager
*/
use MediaWiki\MediaWikiServices;
+use MediaWiki\Linker\LinkRenderer;
use MediaWiki\Storage\RevisionRecord;
use Wikimedia\Rdbms\IResultWrapper;
use Wikimedia\Rdbms\FakeResultWrapper;
*/
private $templateParser;
- public function __construct( IContextSource $context, array $options ) {
+ public function __construct( IContextSource $context, array $options,
+ LinkRenderer $linkRenderer = null
+ ) {
// Set ->target before calling parent::__construct() so
// parent can call $this->getIndexField() and get the right result. Set
// the rest too just to keep things simple.
$this->newOnly = !empty( $options['newOnly'] );
$this->hideMinor = !empty( $options['hideMinor'] );
- parent::__construct( $context );
+ parent::__construct( $context, $linkRenderer );
$msgs = [
'diff',
/**
* @ingroup Pager
*/
+use MediaWiki\Linker\LinkRenderer;
use MediaWiki\MediaWikiServices;
use MediaWiki\Storage\RevisionRecord;
use Wikimedia\Rdbms\IDatabase;
*/
protected $mNavigationBar;
- public function __construct( IContextSource $context, $target, $namespace = false ) {
- parent::__construct( $context );
+ public function __construct( IContextSource $context, $target, $namespace = false,
+ LinkRenderer $linkRenderer
+ ) {
+ parent::__construct( $context, $linkRenderer );
$msgs = [ 'deletionlog', 'undeleteviewlink', 'diff' ];
foreach ( $msgs as $msg ) {
$this->messages[$msg] = $this->msg( $msg )->text();
/**
* @ingroup Pager
*/
+use MediaWiki\Linker\LinkRenderer;
use MediaWiki\MediaWikiServices;
use Wikimedia\Rdbms\IResultWrapper;
use Wikimedia\Rdbms\FakeResultWrapper;
protected $mTableName = 'image';
public function __construct( IContextSource $context, $userName = null, $search = '',
- $including = false, $showAll = false
+ $including = false, $showAll = false, LinkRenderer $linkRenderer
) {
$this->setContext( $context );
$this->mDefaultDirection = IndexPager::DIR_DESCENDING;
}
- parent::__construct();
+ parent::__construct( $context, $linkRenderer );
}
/**
/**
* @ingroup Pager
*/
+use MediaWiki\Linker\LinkRenderer;
use MediaWiki\MediaWikiServices;
class NewFilesPager extends RangeChronologicalPager {
/**
* @param IContextSource $context
* @param FormOptions $opts
+ * @param LinkRenderer $linkRenderer
*/
- public function __construct( IContextSource $context, FormOptions $opts ) {
- parent::__construct( $context );
+ public function __construct( IContextSource $context, FormOptions $opts,
+ LinkRenderer $linkRenderer
+ ) {
+ parent::__construct( $context, $linkRenderer );
$this->opts = $opts;
$this->setLimit( $opts->getValue( 'limit' ) );
$throttle->clear( $user->getName(), $request->getIP() );
}
return self::loginHook( $user, $bp,
+ // @phan-suppress-next-line PhanUndeclaredMethod
Status::newGood( $provider->newSessionForRequest( $user, $bp, $request ) ) );
}
* @return string|string[] An error or list of errors in the
* provided $datum. When no errors exist the empty array is
* returned.
+ * @suppress PhanUndeclaredMethod
*/
public static function getErrors( AvroSchema $schema, $datum ) {
switch ( $schema->type ) {
const SUPPORTED = 'mwfile';
/**
- * @var LanguageConverter
+ * @var LanguageConverter|FakeConverter
*/
public $mConverter;
}
function __construct() {
- // @phan-suppress-next-line PhanTypeMismatchProperty
$this->mConverter = new FakeConverter( $this );
// Set the code to the name of the descendant
if ( static::class === 'Language' ) {
$revision = Revision::newFromTitle( $title );
if ( $revision ) {
if ( $revision->getContentModel() == CONTENT_MODEL_WIKITEXT ) {
+ // @phan-suppress-next-line PhanUndeclaredMethod
$txt = $revision->getContent( RevisionRecord::RAW )->getText();
}
"backend-fail-batchsize": "The storage backend was given a batch of $1 file {{PLURAL:$1|operation|operations}}; the limit is $2 {{PLURAL:$2|operation|operations}}.",
"backend-fail-usable": "Could not read or write file \"$1\" due to insufficient permissions or missing directories/containers.",
"backend-fail-stat": "Could not read the status of file \"$1\".",
- "backend-fail-hash": "Could determine the cryptographic hash of file \"$1\".",
+ "backend-fail-hash": "Could not determine the cryptographic hash of file \"$1\".",
"filejournal-fail-dbconnect": "Could not connect to the journal database for storage backend \"$1\".",
"filejournal-fail-dbquery": "Could not update the journal database for storage backend \"$1\".",
"lockmanager-notlocked": "Could not unlock \"$1\"; it is not locked.",
* @author Ibrahim
* @author Kaganer
* @author Soroush
+ * @author ToJack
* @author Urhixidur
* @author לערי ריינהארט
*/
NS_CATEGORY_TALK => 'Баҳси_гурӯҳ',
];
+$specialPageAliases = [
+ 'Activeusers' => [ 'Корбарони_фаъол' ],
+ 'Allmessages' => [ 'Паёмҳои_системавӣ' ],
+ 'AllMyUploads' => [ 'Тамоми_парвандаҳои_ман' ],
+ 'Allpages' => [ 'Тамоми_саҳифаҳо' ],
+ 'Badtitle' => [ 'Номи_номусоид' ],
+ 'Blankpage' => [ 'Саҳифаи_холӣ' ],
+ 'Block' => [ 'Бастан' ],
+ 'Booksources' => [ 'Манобеи_китобҳо' ],
+ 'BrokenRedirects' => [ 'Саҳифаҳои_равонакунии_кандашуда' ],
+ 'Categories' => [ 'Гурӯҳҳо' ],
+ 'ChangeEmail' => [ 'Тағйири_почтаи_электронӣ' ],
+ 'ChangePassword' => [ 'Тағйири_гузарвожа' ],
+ 'ComparePages' => [ 'Муқоисаи_саҳафот' ],
+ 'Confirmemail' => [ 'Тасдиқи_почтаи_электронӣ' ],
+ 'Contributions' => [ 'Ҳиссагузориҳо' ],
+ 'CreateAccount' => [ 'Сохтани_ҳисоби_корбарӣ' ],
+ 'Deadendpages' => [ 'Саҳифаҳои_бемаъно' ],
+ 'DeletedContributions' => [ 'Саҳми_ҳазфшуда' ],
+ 'Diff' => [ 'Тағйирот' ],
+ 'DoubleRedirects' => [ 'Саҳифаҳои_равонакунии_дукарата' ],
+ 'EditWatchlist' => [ 'Таҳрири_феҳристи_пайгириҳо' ],
+ 'Emailuser' => [ 'Навиштани_мактуб_ба_корбар' ],
+ 'ExpandTemplates' => [ 'Густариши_шаблонҳо' ],
+ 'Export' => [ 'Экспорт' ],
+ 'Fewestrevisions' => [ 'Камтарин_нусха' ],
+ 'FileDuplicateSearch' => [ 'Ҷустани_парвандаҳои_такрорӣ' ],
+ 'Filepath' => [ 'Масири_парванда' ],
+ 'Import' => [ 'Импорт' ],
+ 'Invalidateemail' => [ 'Қатъ_намудани_тасдиқоти_нишонаи_почтаи_электронӣ' ],
+ 'JavaScriptTest' => [ 'Тести_JavaScript' ],
+ 'BlockList' => [ 'Феҳристи_басташудаҳо' ],
+ 'LinkSearch' => [ 'Ҷустани_пайвандҳо' ],
+ 'Listadmins' => [ 'Феҳристи_мудирон' ],
+ 'Listbots' => [ 'Феҳристи_ботҳо' ],
+ 'Listfiles' => [ 'Феҳристи_аксҳо' ],
+ 'Listgrouprights' => [ 'Феҳристи_гурӯҳҳои_корбарӣ' ],
+ 'Listredirects' => [ 'Феҳкристи_саҳифаҳои_равонакунӣ' ],
+ 'ListDuplicatedFiles' => [ 'Феҳристи_парвандаҳои_такрорӣ' ],
+ 'Listusers' => [ 'Феҳристи_корбарон' ],
+ 'Lockdb' => [ 'Қуфл_намудани_пойгоҳи_додаҳо' ],
+ 'Log' => [ 'Гузоришҳо' ],
+ 'Lonelypages' => [ 'Саҳифаҳои_ятим' ],
+ 'Longpages' => [ 'Саҳифаҳои_калон' ],
+ 'MergeHistory' => [ 'Идғоми_таърихча' ],
+ 'MIMEsearch' => [ 'Ҷустуҷӯи_MIME' ],
+ 'Mostcategories' => [ 'Сергурӯҳтарин_саҳафот' ],
+ 'Mostimages' => [ 'Серистифодашавандатарин_парвандаҳо' ],
+ 'Mostinterwikis' => [ 'Бештарин_миёнавики' ],
+ 'Mostlinked' => [ 'Истифодашавандатарин_саҳифаҳо' ],
+ 'Mostlinkedcategories' => [ 'Истифодашавандатарин_гурӯҳҳо' ],
+ 'Mostlinkedtemplates' => [ 'Истифодашавандатарин_шаблонҳо' ],
+ 'Mostrevisions' => [ 'Саҳифаҳо_бо_бештарин_нусха' ],
+ 'Movepage' => [ 'Интиқоли_саҳифа' ],
+ 'Mycontributions' => [ 'Саҳми_ман' ],
+ 'MyLanguage' => [ 'Забони_ман' ],
+ 'Mypage' => [ 'Саҳифаи_ман' ],
+ 'Mytalk' => [ 'Баҳси_ман' ],
+ 'Myuploads' => [ 'Парвандаҳои_фиристодаи_ман' ],
+ 'Newimages' => [ 'Парвандаҳои_нав' ],
+ 'Newpages' => [ 'Саҳифаҳои_нав' ],
+ 'PasswordReset' => [ 'Партофтани_гузарвожа' ],
+ 'PermanentLink' => [ 'Пайванди_доимӣ' ],
+ 'Preferences' => [ 'Танзимот' ],
+ 'Prefixindex' => [ 'Намои_пешвандӣ' ],
+ 'Protectedpages' => [ 'Саҳифаҳои_муфозатшуда' ],
+ 'Protectedtitles' => [ 'Номҳои_муҳофизатшуда' ],
+ 'Randompage' => [ 'Саҳифаи_тасодуфӣ' ],
+ 'Randomredirect' => [ 'Саҳифаи_равонакунии_тасодуфӣ' ],
+ 'Recentchanges' => [ 'Тағйироти_охирин' ],
+ 'Recentchangeslinked' => [ 'Вироишоти_вобаста' ],
+ 'Revisiondelete' => [ 'Вироишоти_ҳазфшуда' ],
+ 'Search' => [ 'Ҷустуҷӯ' ],
+ 'Shortpages' => [ 'Саҳифаҳои_хурд' ],
+ 'Specialpages' => [ 'Саҳифаҳои_вижа' ],
+ 'Statistics' => [ 'Омор' ],
+ 'Tags' => [ 'Барчасбҳо' ],
+ 'Unblock' => [ 'Боз_кардан' ],
+ 'Uncategorizedcategories' => [ 'Гурӯҳҳои_бе_гурӯҳ' ],
+ 'Uncategorizedimages' => [ 'Парвандаҳои_бе_гурӯҳ' ],
+ 'Uncategorizedpages' => [ 'Саҳифаҳои_бе_гурӯҳ' ],
+ 'Uncategorizedtemplates' => [ 'Шаблонҳои_бе_гурӯҳ' ],
+ 'Undelete' => [ 'Эҳёи_саҳифаи_ҳазфшуда' ],
+ 'Unlockdb' => [ 'Боз_кардани_пойгоҳи_додаҳо' ],
+ 'Unusedcategories' => [ 'Гурӯҳҳои_истифоданашуда' ],
+ 'Unusedimages' => [ 'Парвандаҳои_истифоданашуда' ],
+ 'Unusedtemplates' => [ 'Шаблонҳои_истифоданашуда' ],
+ 'Upload' => [ 'Боргузории_парванда' ],
+ 'UploadStash' => [ 'Боркунии_пинҳонӣ' ],
+ 'Userlogin' => [ 'Вуруд' ],
+ 'Userlogout' => [ 'Хуруҷ' ],
+ 'Userrights' => [ 'Идораи_гурӯҳҳои_корбарӣ' ],
+ 'Version' => [ 'Нусха' ],
+ 'Wantedcategories' => [ 'Гурӯҳҳҳои_дархостӣ' ],
+ 'Wantedfiles' => [ 'Парвандаҳои_дархостӣ' ],
+ 'Wantedpages' => [ 'Саҳифаҳои_дархостӣ' ],
+ 'Wantedtemplates' => [ 'Шаблонҳои_дархости' ],
+ 'Watchlist' => [ 'Феҳристи_пайгириҳо' ],
+ 'Whatlinkshere' => [ 'Пайвандҳо_ба_инҷо' ],
+ 'Withoutinterwiki' => [ 'Бе_интервики' ],
+];
+
+$magicWords = [
+ 'redirect' => [ '0', '#равона', '#REDIRECT' ],
+ 'notoc' => [ '0', '__БЕ_ФЕҲРИСТ__', '__NOTOC__' ],
+ 'nogallery' => [ '0', '__БЕ_НИГОРХОНА__', '__NOGALLERY__' ],
+ 'forcetoc' => [ '0', '__БО_ФЕҲРИСТ__', '__FORCETOC__' ],
+ 'toc' => [ '0', '__ФЕҲРИСТ__', '__TOC__' ],
+ 'noeditsection' => [ '0', '__БЕ_ВИРОИШИ_ҶУЗЪӢ__', '__NOEDITSECTION__' ],
+ 'currentmonth' => [ '1', 'МОҲИ_КУНУНӢ', 'МОҲИ_КУНУНӢ_2', 'CURRENTMONTH', 'CURRENTMONTH2' ],
+ 'currentmonth1' => [ '1', 'МОҲИ_КУНУНӢ_1', 'CURRENTMONTH1' ],
+ 'currentmonthname' => [ '1', 'НОМИ_МОҲИ_КУНУНӢ', 'CURRENTMONTHNAME' ],
+ 'currentmonthnamegen' => [ '1', 'НОМИ_МОҲИ_КУНУНӢ_ТАСРИФ', 'CURRENTMONTHNAMEGEN' ],
+ 'currentmonthabbrev' => [ '1', 'НОМИ_МОҲИ_КУНУНӢ_ИХТИСОР', 'CURRENTMONTHABBREV' ],
+ 'currentday' => [ '1', 'РӮЗИ_КУНУНӢ', 'CURRENTDAY' ],
+ 'currentday2' => [ '1', 'РӮЗИ_КУНУНИ_2', 'CURRENTDAY2' ],
+ 'currentdayname' => [ '1', 'НОМИ_РӮЗИ_КУНУНӢ', 'CURRENTDAYNAME' ],
+ 'currentyear' => [ '1', 'СОЛИ_КУНУНӢ', 'CURRENTYEAR' ],
+ 'currenttime' => [ '1', 'ЗАМОНИ_КУНУНӢ', 'CURRENTTIME' ],
+ 'currenthour' => [ '1', 'СОАТИ_КУНУНӢ', 'CURRENTHOUR' ],
+ 'localmonth' => [ '1', 'МОҲИ_МАҲАЛЛӢ', 'МОҲИ_МАҲАЛЛӢ_2', 'LOCALMONTH', 'LOCALMONTH2' ],
+ 'localmonth1' => [ '1', 'МОҲИ_МАҲАЛЛӢ_1', 'LOCALMONTH1' ],
+ 'localmonthname' => [ '1', 'НОМИ_МОҲИ_МАҲАЛЛӢ', 'LOCALMONTHNAME' ],
+ 'localmonthnamegen' => [ '1', 'НОМИ_МОҲИ_МАҲАЛЛӢ_ТАСРИФ', 'LOCALMONTHNAMEGEN' ],
+ 'localmonthabbrev' => [ '1', 'НОМИ_МОҲИ_МАҲАЛЛӢ_ИХТИСОР', 'LOCALMONTHABBREV' ],
+ 'localday' => [ '1', 'РӮЗИ_МАҲАЛЛӢ', 'LOCALDAY' ],
+ 'localday2' => [ '1', 'РӮЗИ_МАҲАЛЛӢ_2', 'LOCALDAY2' ],
+ 'localdayname' => [ '1', 'НОМИ_РӮЗИ_МАҲАЛЛӢ', 'LOCALDAYNAME' ],
+ 'localyear' => [ '1', 'СОЛИ_МАҲАЛЛӢ', 'LOCALYEAR' ],
+ 'localtime' => [ '1', 'ЗАМОНИ_МАҲАЛЛӢ', 'LOCALTIME' ],
+ 'localhour' => [ '1', 'СОАТИ_МАҲАЛЛӢ', 'LOCALHOUR' ],
+ 'numberofpages' => [ '1', 'ШУМОРАИ_САҲИФАҲО', 'NUMBEROFPAGES' ],
+ 'numberofarticles' => [ '1', 'ШУМОРАИ_МАҚОЛАҲО', 'NUMBEROFARTICLES' ],
+ 'numberoffiles' => [ '1', 'ШУМОРАИ_ПАРВАНДАҲО', 'NUMBEROFFILES' ],
+ 'numberofusers' => [ '1', 'ШУМОРАИ_КОРБАРОН', 'NUMBEROFUSERS' ],
+ 'numberofactiveusers' => [ '1', 'ШУМОРАИ_КОРБАРОНИ_ФАЪОЛ', 'NUMBEROFACTIVEUSERS' ],
+ 'numberofedits' => [ '1', 'ШУМОРАИ_ВИРОИШОТ', 'NUMBEROFEDITS' ],
+ 'pagename' => [ '1', 'НОМИ_САҲИФА', 'PAGENAME' ],
+ 'pagenamee' => [ '1', 'НОМИ_САҲИФА_2', 'PAGENAMEE' ],
+ 'namespace' => [ '1', 'ФАЗОИ_НОМ', 'NAMESPACE' ],
+ 'namespacee' => [ '1', 'ФАЗОИ_НОМ_2', 'NAMESPACEE' ],
+ 'namespacenumber' => [ '1', 'РАҚАМИ_ФАЗОИ_НОМ', 'NAMESPACENUMBER' ],
+ 'talkspace' => [ '1', 'ФАЗОИ_БАҲСҲО', 'TALKSPACE' ],
+ 'talkspacee' => [ '1', 'ФАЗОИ_БАҲСҲО_2', 'TALKSPACEE' ],
+ 'subjectspace' => [ '1', 'ФАЗОИ_МАҚОЛАҲО', 'SUBJECTSPACE', 'ARTICLESPACE' ],
+ 'subjectspacee' => [ '1', 'ФАЗОИ_МАҚОЛАҲО_2', 'SUBJECTSPACEE', 'ARTICLESPACEE' ],
+ 'fullpagename' => [ '1', 'НОМИ_ПУРРАИ_САҲИФА', 'FULLPAGENAME' ],
+ 'fullpagenamee' => [ '1', 'НОМИ_ПУРРАИ_САҲИФА_2', 'FULLPAGENAMEE' ],
+ 'subpagename' => [ '1', 'НОМИ_ЗЕРГУРӮҲ', 'SUBPAGENAME' ],
+ 'subpagenamee' => [ '1', 'НОМИ_ЗЕРГУРӮҲ_2', 'SUBPAGENAMEE' ],
+ 'basepagename' => [ '1', 'АСОСИИ_НОМИ_САҲИФА', 'BASEPAGENAME' ],
+ 'basepagenamee' => [ '1', 'АСОСИИ_НОМИ_САҲИФА_2', 'BASEPAGENAMEE' ],
+ 'talkpagename' => [ '1', 'НОМИ_САҲИФАИ_БАҲС', 'TALKPAGENAME' ],
+ 'talkpagenamee' => [ '1', 'НОМИ_САҲИФАИ_БАҲС_2', 'TALKPAGENAMEE' ],
+ 'subjectpagename' => [ '1', 'НОМИ_САҲИФА_МАҚОЛА', 'SUBJECTPAGENAME', 'ARTICLEPAGENAME' ],
+ 'subjectpagenamee' => [ '1', 'НОМИ_САҲИФА_МАҚОЛА_2', 'SUBJECTPAGENAMEE', 'ARTICLEPAGENAMEE' ],
+ 'msg' => [ '0', 'ПАЁМ:', 'MSG:' ],
+ 'subst' => [ '0', 'МОНДАН:', 'SUBST:' ],
+ 'safesubst' => [ '0', 'МОНДАНИҲИФЗ:', 'SAFESUBST:' ],
+ 'msgnw' => [ '0', 'ПАЁМ_БЕ_ВИКИ:', 'MSGNW:' ],
+ 'img_thumbnail' => [ '1', 'мини', 'миниатюра', 'thumb', 'thumbnail' ],
+ 'img_manualthumb' => [ '1', 'мини=$1', 'миниатюра=$1', 'thumbnail=$1', 'thumb=$1' ],
+ 'img_right' => [ '1', 'рост', 'right' ],
+ 'img_left' => [ '1', 'чап', 'left' ],
+ 'img_none' => [ '1', 'бе', 'none' ],
+ 'img_width' => [ '1', '$1пкс', '$1px' ],
+ 'img_center' => [ '1', 'марказ', 'center', 'centre' ],
+ 'img_framed' => [ '1', 'чаҳорчӯба', 'рамка', 'frame', 'framed', 'enframed' ],
+ 'img_frameless' => [ '1', 'бе_чаҳорчӯба', 'бе_рамка', 'frameless' ],
+ 'img_page' => [ '1', 'саҳифа=$1', 'саҳифа $1', 'page=$1', 'page $1' ],
+ 'img_upright' => [ '1', 'болорост', 'боло_рост=$1', 'болорост $1', 'upright', 'upright=$1', 'upright $1' ],
+ 'img_border' => [ '1', 'сарҳад', 'border' ],
+ 'img_baseline' => [ '1', 'асос', 'baseline' ],
+ 'img_sub' => [ '1', 'поён', 'sub' ],
+ 'img_super' => [ '1', 'боло', 'super', 'sup' ],
+ 'img_top' => [ '1', 'аз_боло', 'top' ],
+ 'img_text_top' => [ '1', 'матнболо', 'text-top' ],
+ 'img_middle' => [ '1', 'дарбайн', 'middle' ],
+ 'img_bottom' => [ '1', 'дарпоён', 'bottom' ],
+ 'img_text_bottom' => [ '1', 'матнпоён', 'text-bottom' ],
+ 'img_link' => [ '1', 'пайванд=$1', 'link=$1' ],
+ 'img_alt' => [ '1', 'алт=$1', 'alt=$1' ],
+ 'int' => [ '0', 'ДАРУН:', 'INT:' ],
+ 'sitename' => [ '1', 'НОМИ_СОМОНА', 'НОМИ_САЙТ', 'SITENAME' ],
+ 'ns' => [ '0', 'ПИ:', 'NS:' ],
+ 'nse' => [ '0', 'ПИК:', 'NSE:' ],
+ 'localurl' => [ '0', 'СУРОҒАИ_ЛОКАЛӢ:', 'LOCALURL:' ],
+ 'localurle' => [ '0', 'СУРОҒАИ_ЛОКАЛӢ_2:', 'LOCALURLE:' ],
+ 'articlepath' => [ '0', 'МАСИРИ_САҲИФА', 'ARTICLEPATH' ],
+ 'pageid' => [ '0', 'ИДЕНТИФИКАТОРИ_САҲИФА', 'PAGEID' ],
+ 'server' => [ '0', 'СЕРВЕР', 'SERVER' ],
+ 'servername' => [ '0', 'НОМИ_СЕРВЕР', 'SERVERNAME' ],
+ 'scriptpath' => [ '0', 'МАСИРИ_СКРИПТ', 'SCRIPTPATH' ],
+ 'stylepath' => [ '0', 'МАСИРИ_УСЛУБ', 'STYLEPATH' ],
+ 'grammar' => [ '0', 'ТАСРИФ:', 'GRAMMAR:' ],
+ 'gender' => [ '0', 'ҶИНС:', 'GENDER:' ],
+ 'notitleconvert' => [ '0', '__БЕ_ТАҒЙИРИ_САРЛАВҲА__', '__NOTITLECONVERT__', '__NOTC__' ],
+ 'nocontentconvert' => [ '0', '__БЕ_ТАҒЙИРИ_МАТН__', '__NOCONTENTCONVERT__', '__NOCC__' ],
+ 'currentweek' => [ '1', 'ХАФТАИ_КУНУНӢ', 'CURRENTWEEK' ],
+ 'currentdow' => [ '1', 'РӮЗИ_КУНУНИИ_ҲАФТА', 'CURRENTDOW' ],
+ 'localweek' => [ '1', 'ҲАФТАИ_МАҲАЛЛӢ', 'LOCALWEEK' ],
+ 'localdow' => [ '1', 'РУЗИ_ҲАФТАИ_МАҲАЛЛӢ', 'LOCALDOW' ],
+ 'revisionid' => [ '1', 'ИД_НУСХА', 'REVISIONID' ],
+ 'revisionday' => [ '1', 'РӮЗИ_НУСХА', 'REVISIONDAY' ],
+ 'revisionday2' => [ '1', 'РӮЗИ_НУСХА_2', 'REVISIONDAY2' ],
+ 'revisionmonth' => [ '1', 'МОҲИ_НУСХА', 'REVISIONMONTH' ],
+ 'revisionmonth1' => [ '1', 'МОҲИ_НУСХА_1', 'REVISIONMONTH1' ],
+ 'revisionyear' => [ '1', 'СОЛИ_НУСХА', 'REVISIONYEAR' ],
+ 'revisiontimestamp' => [ '1', 'НИШОНИ_ЗАМОНИ_НУСХА', 'REVISIONTIMESTAMP' ],
+ 'revisionuser' => [ '1', 'НУСХАИ_КОРБАР', 'REVISIONUSER' ],
+ 'plural' => [ '0', 'ШАКЛИ_ҶАМЪ:', 'PLURAL:' ],
+ 'fullurl' => [ '0', 'СУРОҒАИ_ПУРРА:', 'FULLURL:' ],
+ 'fullurle' => [ '0', 'СУРОҒАИ_ПУРРА_2:', 'FULLURLE:' ],
+ 'lcfirst' => [ '0', 'ҲАРФИ_АВВАЛ_ХУРД:', 'LCFIRST:' ],
+ 'ucfirst' => [ '0', 'ҲАРФИ_АВВАЛ_КАЛОН:', 'UCFIRST:' ],
+ 'lc' => [ '0', 'БО_ҲАРФҲОИ_ХУРД:', 'LC:' ],
+ 'uc' => [ '0', 'БО_ҲАРФҲОИ_КАЛОН:', 'UC:' ],
+ 'raw' => [ '0', 'ХОМ:', 'RAW:' ],
+ 'displaytitle' => [ '1', 'НАМОИШИ_САРЛАВҲА', 'DISPLAYTITLE' ],
+ 'rawsuffix' => [ '1', 'Н', 'R' ],
+ 'newsectionlink' => [ '1', '__ПАЙВАНД_БА_ҚИСМАТИ_НАВ__', '__NEWSECTIONLINK__' ],
+ 'nonewsectionlink' => [ '1', '__БЕ_ПАЙВАНД_БА_ҚИСМАТИ_НАВ__', '__NONEWSECTIONLINK__' ],
+ 'currentversion' => [ '1', 'НУСХАИ_КУНУНӢ', 'CURRENTVERSION' ],
+ 'urlencode' => [ '0', 'СУРОҒАИ_РАМЗ:', 'URLENCODE:' ],
+ 'anchorencode' => [ '0', 'РАМЗКУНИИ_БАРЧАСБ', 'ANCHORENCODE' ],
+ 'currenttimestamp' => [ '1', 'БАРЧАСБИ_ЗАМОНИ_КУНУНӢ', 'CURRENTTIMESTAMP' ],
+ 'localtimestamp' => [ '1', 'БАРЧАСБИ_ЗАМОНИ_МАҲАЛЛӢ', 'LOCALTIMESTAMP' ],
+ 'directionmark' => [ '1', 'МАСИРИ_ПАЁМ', 'DIRECTIONMARK', 'DIRMARK' ],
+ 'language' => [ '0', '#ЗАБОН:', '#LANGUAGE:' ],
+ 'contentlanguage' => [ '1', 'ЗАБОНИ_МӮҲТАВО', 'CONTENTLANGUAGE', 'CONTENTLANG' ],
+ 'pagesinnamespace' => [ '1', 'САҲИФАҲО_ДАР_ФАЗОҲОИ_НОМ:', 'PAGESINNAMESPACE:', 'PAGESINNS:' ],
+ 'numberofadmins' => [ '1', 'ШУМОРАИ_МУДИРОН', 'NUMBEROFADMINS' ],
+ 'formatnum' => [ '0', 'ФОРМАТИ_РАҚАМ', 'FORMATNUM' ],
+ 'padleft' => [ '0', 'АЗ_ТАРАФИ_ЧАП', 'PADLEFT' ],
+ 'padright' => [ '0', 'АЗ_ТАРАФИ_РОСТ', 'PADRIGHT' ],
+ 'special' => [ '0', 'ВИЖА', 'special' ],
+ 'defaultsort' => [ '1', 'ТАРТИБ_БА_ТАВРИ_ПЕШФАРЗ:', 'КАЛИДИ_ТАРТИБ:', 'DEFAULTSORT:', 'DEFAULTSORTKEY:', 'DEFAULTCATEGORYSORT:' ],
+ 'filepath' => [ '0', 'МАСИРИ_ПАРВАНДА:', 'FILEPATH:' ],
+ 'tag' => [ '0', 'барчасб', 'тег', 'тэг', 'tag' ],
+ 'hiddencat' => [ '1', '__ГУРӮҲИ_ПИНҲОН__', '__HIDDENCAT__' ],
+ 'pagesincategory' => [ '1', 'САҲИФА_ДАР_ГУРӮҲ', 'PAGESINCATEGORY', 'PAGESINCAT' ],
+ 'pagesize' => [ '1', 'АНДОЗАИ_САҲИФА', 'PAGESIZE' ],
+ 'index' => [ '1', '__ИНДЕКС__', '__INDEX__' ],
+ 'noindex' => [ '1', '__БЕ_ИНДЕКС__', '__NOINDEX__' ],
+ 'numberingroup' => [ '1', 'РАҚАМ_ДАР_ГУРӮҲ', 'NUMBERINGROUP', 'NUMINGROUP' ],
+ 'staticredirect' => [ '1', '__РАВОНАИ_СТАТИСТИКӢ__', '__STATICREDIRECT__' ],
+ 'protectionlevel' => [ '1', 'ДАРАҶАИ_МУҲОФИЗАТ', 'PROTECTIONLEVEL' ],
+ 'formatdate' => [ '0', 'форматисана', 'formatdate', 'dateformat' ],
+ 'url_path' => [ '0', 'МАСИР', 'PATH' ],
+ 'url_wiki' => [ '0', 'ВИКИ', 'WIKI' ],
+ 'url_query' => [ '0', 'ДАРХОСТ', 'QUERY' ],
+ 'pagesincategory_all' => [ '0', 'ҳама', 'all' ],
+ 'pagesincategory_pages' => [ '0', 'саҳифаҳо', 'pages' ],
+ 'pagesincategory_subcats' => [ '0', 'зергурӯҳҳо', 'subcats' ],
+ 'pagesincategory_files' => [ '0', 'аксҳо', 'files' ],
+];
+
$datePreferences = [
'default',
'dmy',
$res = $dbw->select( 'content', 'content_address', [], __METHOD__, [ 'DISTINCT' ] );
$blobStore = MediaWikiServices::getInstance()->getBlobStore();
foreach ( $res as $row ) {
+ // @phan-suppress-next-line PhanUndeclaredMethod
$textId = $blobStore->getTextIdFromAddress( $row->content_address );
if ( $textId ) {
$cur[] = $textId;
*/
public function execute() {
$siteStore = MediaWikiServices::getInstance()->getSiteStore();
- $siteStore->reset();
+ if ( method_exists( $siteStore, 'reset' ) ) {
+ // @phan-suppress-next-line PhanUndeclaredMethod
+ $siteStore->reset();
+ }
$globalId = $this->getArg( 0 );
$group = $this->getArg( 1 );
$siteStore->saveSites( [ $site ] );
if ( method_exists( $siteStore, 'reset' ) ) {
+ // @phan-suppress-next-line PhanUndeclaredMethod
$siteStore->reset();
}
require __DIR__ . '/../commandLine.inc';
-use Wikimedia\Rdbms\IMaintainableDatabase;
-
/**
* Maintenance script that upgrade for log_id/log_deleted fields in a
* replication-safe way.
class UpdateLogging {
/**
- * @var IMaintainableDatabase
+ * @var Database
*/
public $dbw;
public $batchSize = 1000;
public $minTs = false;
function execute() {
- $this->dbw = $this->getDB( DB_MASTER );
+ $this->dbw = wfGetDB( DB_MASTER );
$logging = $this->dbw->tableName( 'logging' );
$logging_1_10 = $this->dbw->tableName( 'logging_1_10' );
$logging_pre_1_10 = $this->dbw->tableName( 'logging_pre_1_10' );
}
public function execute() {
- global $wgRCMaxAge;
-
$this->initialize();
$startTS = new MWTimestamp( $this->getOption( "start" ) );
$endTS = new MWTimestamp( $this->getOption( "end" ) );
$now = new MWTimestamp();
+ $rcMaxAge = $this->getConfig()->get( 'RCMaxAge' );
- if ( $now->getTimestamp() - $startTS->getTimestamp() > $wgRCMaxAge ) {
- $this->error( "Start timestamp too old, maximum RC age is $wgRCMaxAge!" );
+ if ( $now->getTimestamp() - $startTS->getTimestamp() > $rcMaxAge ) {
+ $this->error( "Start timestamp too old, maximum RC age is $rcMaxAge!" );
}
- if ( $now->getTimestamp() - $endTS->getTimestamp() > $wgRCMaxAge ) {
- $this->error( "End timestamp too old, maximum RC age is $wgRCMaxAge!" );
+ if ( $now->getTimestamp() - $endTS->getTimestamp() > $rcMaxAge ) {
+ $this->error( "End timestamp too old, maximum RC age is $rcMaxAge!" );
}
$this->startTS = $startTS->getTimestamp();
}
private function loadThing( &$dependencies, $name, $extensions, $skins ) {
- global $wgExtensionDirectory, $wgStyleDirectory;
+ $extDir = $this->getConfig()->get( 'ExtensionDirectory' );
+ $styleDir = $this->getConfig()->get( 'StyleDirectory' );
$queue = [];
$missing = false;
foreach ( $extensions as $extension ) {
- $path = "$wgExtensionDirectory/$extension/extension.json";
+ $path = "$extDir/$extension/extension.json";
if ( file_exists( $path ) ) {
// 1 is ignored
$queue[$path] = 1;
}
foreach ( $skins as $skin ) {
- $path = "$wgStyleDirectory/$skin/skin.json";
+ $path = "$styleDir/$skin/skin.json";
if ( file_exists( $path ) ) {
$queue[$path] = 1;
$this->addToDependencies( $dependencies, [], [ $skin ], $name );
"$IP/tests/phpunit/phpunit.php",
"$IP/tests/phpunit/suites/LessTestSuite.php"
];
+ // @phan-suppress-next-line PhanUndeclaredMethod
$textUICommand->run( $argv );
}
}
* all values are in that range. Drop ones that aren't.
*/
public function execute() {
- global $wgHiddenPrefs, $wgDefaultUserOptions;
-
$dbw = $this->getDB( DB_MASTER );
$hidden = $this->hasOption( 'hidden' );
$unknown = $this->hasOption( 'unknown' );
// Remove hidden prefs. Iterate over them to avoid the IN on a large table
if ( $hidden ) {
- if ( !$wgHiddenPrefs ) {
+ $hiddenPrefs = $this->getConfig()->get( 'HiddenPrefs' );
+ if ( !$hiddenPrefs ) {
$this->output( "No hidden preferences, skipping\n" );
}
- foreach ( $wgHiddenPrefs as $hiddenPref ) {
+ foreach ( $hiddenPrefs as $hiddenPref ) {
$this->deleteByWhere(
$dbw,
'Dropping hidden preferences',
// Remove unknown preferences. Special-case 'userjs-' as we can't control those names.
if ( $unknown ) {
+ $defaultUserOptions = $this->getConfig()->get( 'DefaultUserOptions' );
$where = [
'up_property NOT' . $dbw->buildLike( 'userjs-', $dbw->anyString() ),
- 'up_property NOT IN (' . $dbw->makeList( array_keys( $wgDefaultUserOptions ) ) . ')',
+ 'up_property NOT IN (' . $dbw->makeList( array_keys( $defaultUserOptions ) ) . ')',
];
// Allow extensions to add to the where clause to prevent deletion of their own prefs.
Hooks::run( 'DeleteUnknownPreferences', [ &$where, $dbw ] );
protected function doOperations( FileRepo $tempRepo, array $ops ) {
$status = $tempRepo->getBackend()->doQuickOperations( $ops );
if ( !$status->isOK() ) {
+ // @phan-suppress-next-line PhanUndeclaredMethod
$this->error( print_r( $status->getErrorsArray(), true ) );
}
}
return;
}
+ /** @var WikitextContent $content */
+ '@phan-var WikitextContent $content';
$text = strval( $content->getText() );
$output1 = $parser1->parse( $text, $title, $this->options );
}
$res = $dbw->query( "SELECT l_from FROM $links LIMIT 1" );
+ // @phan-suppress-next-line PhanUndeclaredMethod
if ( $dbw->fieldType( $res, 0 ) == "int" ) {
$this->output( "Schema already converted\n" );
/** @var LocalFile $file */
$file = $repo->newFile( $row->fa_name );
+ '@phan-var LocalFile $file';
try {
$file->lock();
} catch ( LocalFileLockError $e ) {
$this->output( "Deleted version '$key' ($ts) of file '$name'\n" );
} else {
$this->output( "Failed to delete version '$key' ($ts) of file '$name'\n" );
+ // @phan-suppress-next-line PhanUndeclaredMethod
$this->output( print_r( $status->getErrorsArray(), true ) );
}
} else {
}
public function execute() {
- global $wgAllDBsAreLocalhost;
- if ( $wgAllDBsAreLocalhost ) {
+ if ( $this->getConfig()->get( 'AllDBsAreLocalhost' ) ) {
$host = 'localhost';
} elseif ( $this->hasOption( 'group' ) ) {
$db = $this->getDB( DB_REPLICA, $this->getOption( 'group' ) );
}
$this->uploadCount++;
// $this->report();
+ // @phan-suppress-next-line PhanUndeclaredMethod
$this->progress( "upload: " . $revision->getFilename() );
if ( !$this->dryRun ) {
if ( $this->hasOption( 'dry' ) ) {
$this->output( "done.\n" );
+ // @phan-suppress-next-line PhanUndeclaredMethod
} elseif ( $image->recordUpload2(
$archive->value,
$summary,
}
protected function doDBUpdates() {
- global $wgActorTableSchemaMigrationStage;
+ $actorTableSchemaMigrationStage = $this->getConfig()->get( 'ActorTableSchemaMigrationStage' );
- if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) ) {
+ if ( !( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) ) {
$this->output(
"...cannot update while \$wgActorTableSchemaMigrationStage lacks SCHEMA_COMPAT_WRITE_NEW\n"
);
}
protected function doDBUpdates() {
- global $wgDefaultExternalStore;
-
$replaceMissing = $this->hasOption( 'replace-missing' );
+ $defaultExternalStore = $this->getConfig()->get( 'DefaultExternalStore' );
$batchSize = $this->getBatchSize();
$dbr = $this->getDB( DB_REPLICA, [ 'vslow' ] );
if ( $data !== false ) {
$flags = Revision::compressRevisionText( $data );
- if ( $wgDefaultExternalStore ) {
+ if ( $defaultExternalStore ) {
$data = ExternalStore::insertToDefault( $data );
if ( $flags ) {
$flags .= ',';
* @return bool
*/
private function checkAll( $options ) {
- global $wgNamespaceAliases, $wgCapitalLinks;
-
$contLang = MediaWikiServices::getInstance()->getContentLanguage();
$spaces = [];
$spaces[$name] = $ns;
}
}
- foreach ( $wgNamespaceAliases as $name => $ns ) {
+ foreach ( $this->getConfig()->get( 'NamespaceAliases' ) as $name => $ns ) {
$spaces[$name] = $ns;
}
foreach ( $contLang->getNamespaceAliases() as $name => $ns ) {
// We'll need to check for lowercase keys as well,
// since we're doing case-sensitive searches in the db.
+ $capitalLinks = $this->getConfig()->get( 'CapitalLinks' );
foreach ( $spaces as $name => $ns ) {
$moreNames = [];
$moreNames[] = $contLang->uc( $name );
$moreNames[] = $contLang->ucwords( $contLang->lc( $name ) );
$moreNames[] = $contLang->ucwordbreaks( $name );
$moreNames[] = $contLang->ucwordbreaks( $contLang->lc( $name ) );
- if ( !$wgCapitalLinks ) {
+ if ( !$capitalLinks ) {
foreach ( $moreNames as $altName ) {
$moreNames[] = $contLang->lcfirst( $altName );
}
$dbw->query( "DELETE FROM $tbl_pag WHERE page_id = $id" );
$this->commitTransaction( $dbw, __METHOD__ );
// Delete revisions as appropriate
+ /** @var NukePage $child */
$child = $this->runChild( NukePage::class, 'nukePage.php' );
+ '@phan-var NukePage $child';
$child->deleteRevisions( $revs );
$this->purgeRedundantText( true );
$n_deleted++;
}
public function execute() {
- global $wgMultiContentRevisionSchemaMigrationStage;
+ $multiContentRevisionSchemaMigrationStage =
+ $this->getConfig()->get( 'MultiContentRevisionSchemaMigrationStage' );
$t0 = microtime( true );
- if ( ( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) === 0 ) {
+ if ( ( $multiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) === 0 ) {
$this->writeln(
'...cannot update while \$wgMultiContentRevisionSchemaMigrationStage '
. 'does not have the SCHEMA_COMPAT_WRITE_NEW bit set.'
// with the database write operation, because the writes are queued
// in the pipe buffer. This can improve performance by up to a
// factor of 2.
- global $wgDBuser, $wgDBserver, $wgDBpassword, $wgDBname;
- $cmd = 'mysql -u' . Shell::escape( $wgDBuser ) .
- ' -h' . Shell::escape( $wgDBserver ) .
- ' -p' . Shell::escape( $wgDBpassword, $wgDBname );
+ $config = $this->getConfig();
+ $cmd = 'mysql -u' . Shell::escape( $config->get( 'DBuser' ) ) .
+ ' -h' . Shell::escape( $config->get( 'DBserver' ) ) .
+ ' -p' . Shell::escape( $config->get( 'DBpassword' ), $config->get( 'DBname' ) );
$this->output( "Using pipe method\n" );
$pipe = popen( $cmd, 'w' );
}
}
// Upgrade the old file versions...
foreach ( $file->getHistory() as $oldFile ) {
+ /** @var OldLocalFile $oldFile */
+ '@phan-var OldLocalFile $oldFile';
$sha1 = $oldFile->getRepo()->getFileSha1( $oldFile->getPath() );
if ( strval( $sha1 ) !== '' ) { // file on disk and hashed properly
if ( $isRegen && $oldFile->getSha1() !== $sha1 ) {
if ( $content->getModel() !== CONTENT_MODEL_WIKITEXT ) {
return;
}
+ /** @var WikitextContent $content */
+ '@phan-var WikitextContent $content';
try {
$this->mPreprocessor->preprocessToObj( strval( $content->getText() ), 0 );
$passed = 'passed';
} catch ( Exception $e ) {
$testReport = self::$currentTest->getReport();
- $exceptionReport = $e->getText();
+ $exceptionReport = $e instanceof MWException ? $e->getText() : (string)$e;
$hash = md5( $testReport );
file_put_contents( "results/ppft-$hash.in", serialize( self::$currentTest ) );
file_put_contents( "results/ppft-$hash.fail",
* @return int Number of entries changed, or that would be changed
*/
private function doReassignEdits( &$from, &$to, $rc = false, $report = false ) {
- global $wgActorTableSchemaMigrationStage;
+ $actorTableSchemaMigrationStage = $this->getConfig()->get( 'ActorTableSchemaMigrationStage' );
$dbw = $this->getDB( DB_MASTER );
$this->beginTransaction( $dbw, __METHOD__ );
if ( $total ) {
# Reassign edits
$this->output( "\nReassigning current edits..." );
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
+ if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
$dbw->update(
'revision',
[
__METHOD__
);
}
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
+ if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
$dbw->update(
'revision_actor_temp',
[ 'revactor_actor' => $to->getActorId( $dbw ) ],
* @return array
*/
private function userSpecification( IDatabase $dbw, &$user, $idfield, $utfield, $acfield ) {
- global $wgActorTableSchemaMigrationStage;
+ $actorTableSchemaMigrationStage = $this->getConfig()->get( 'ActorTableSchemaMigrationStage' );
$ret = [];
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
+ if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
$ret += [
$idfield => $user->getId(),
$utfield => $user->getName(),
];
}
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
+ if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
$ret += [ $acfield => $user->getActorId( $dbw ) ];
}
return $ret;
}
/**
- * @return FileRepo
+ * @return LocalRepo
*/
function getRepo() {
if ( !isset( $this->repo ) ) {
$filename = $altname;
$this->output( "Estimating transcoding... $altname\n" );
} else {
- # @todo FIXME: create renameFile()
+ // @fixme create renameFile()
+ // @phan-suppress-next-line PhanUndeclaredMethod See comment above...
$filename = $this->renameFile( $filename );
}
}
}
public function execute() {
- global $wgActorTableSchemaMigrationStage;
+ $actorTableSchemaMigrationStage = $this->getConfig()->get( 'ActorTableSchemaMigrationStage' );
$this->output( "Remove unused accounts\n\n" );
$delUser = [];
$delActor = [];
$dbr = $this->getDB( DB_REPLICA );
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
+ if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
$res = $dbr->select(
[ 'user', 'actor' ],
[ 'user_id', 'user_name', 'user_touched', 'actor_id' ],
$this->output( "\nDeleting unused accounts..." );
$dbw = $this->getDB( DB_MASTER );
$dbw->delete( 'user', [ 'user_id' => $delUser ], __METHOD__ );
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
+ if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
# Keep actor rows referenced from ipblocks
$keep = $dbw->selectFieldValues(
'ipblocks', 'ipb_by_actor', [ 'ipb_by_actor' => $delActor ], __METHOD__
$dbw->delete( 'user_groups', [ 'ug_user' => $delUser ], __METHOD__ );
$dbw->delete( 'user_former_groups', [ 'ufg_user' => $delUser ], __METHOD__ );
$dbw->delete( 'user_properties', [ 'up_user' => $delUser ], __METHOD__ );
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
+ if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
$dbw->delete( 'logging', [ 'log_actor' => $delActor ], __METHOD__ );
$dbw->delete( 'recentchanges', [ 'rc_actor' => $delActor ], __METHOD__ );
}
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
+ if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
$dbw->delete( 'logging', [ 'log_user' => $delUser ], __METHOD__ );
$dbw->delete( 'recentchanges', [ 'rc_user' => $delUser ], __METHOD__ );
}
[],
[ 'content' => [ 'INNER JOIN', [ 'content_id = slot_content_id' ] ] ]
);
+ /** @var \MediaWiki\Storage\SqlBlobStore $blobStore */
$blobStore = MediaWikiServices::getInstance()->getBlobStore();
+ '@phan-var \MediaWiki\Storage\SqlBlobStore $blobStore';
foreach ( $res as $row ) {
$textId = $blobStore->getTextIdFromAddress( $row->content_address );
if ( $textId ) {
require_once __DIR__ . '/Maintenance.php';
-use Wikimedia\Rdbms\IMaintainableDatabase;
use Wikimedia\Rdbms\DatabaseSqlite;
/**
$dbDomain = WikiMap::getCurrentWikiDbDomain()->getId();
$this->output( "Going to run database updates for $dbDomain\n" );
if ( $db->getType() === 'sqlite' ) {
- /** @var IMaintainableDatabase|DatabaseSqlite $db */
+ /** @var DatabaseSqlite $db */
+ '@phan-var DatabaseSqlite $db';
$this->output( "Using SQLite file: '{$db->getDbFilePath()}'\n" );
}
$this->output( "Depending on the size of your database this may take a while!\n" );
public function __construct() {
parent::__construct();
- global $wgCategoryCollation;
+ $categoryCollation = $this->getConfig()->get( 'CategoryCollation' );
$this->addDescription( <<<TEXT
This script will find all rows in the categorylinks table whose collation is
-out-of-date (cl_collation != '$wgCategoryCollation') and repopulate cl_sortkey
+out-of-date (cl_collation != '$categoryCollation') and repopulate cl_sortkey
using the page title and cl_sortkey_prefix. If all collations are
up-to-date, it will do nothing.
TEXT
}
public function execute() {
- global $wgCategoryCollation;
-
$dbw = $this->getDB( DB_MASTER );
$dbr = $this->getDB( DB_REPLICA );
$force = $this->getOption( 'force' );
$collationName = $this->getOption( 'target-collation' );
$collation = Collation::factory( $collationName );
} else {
- $collationName = $wgCategoryCollation;
+ $collationName = $this->getConfig()->get( 'CategoryCollation' );
$collation = Collation::singleton();
}
while ( $json['manifest_version'] !== ExtensionRegistry::MANIFEST_VERSION ) {
$json['manifest_version'] += 1;
$func = "updateTo{$json['manifest_version']}";
+ // @phan-suppress-next-line PhanUndeclaredMethod
$this->$func( $json );
}
*/
class UserDupes {
/**
- * @var IMaintainableDatabase
+ * @var Database
*/
private $db;
private $reassigned;
$user = User::newFromId( $row->user_id );
/** @var ParameterizedPassword $password */
$password = $passwordFactory->newFromCiphertext( $row->user_password );
+ '@phan-var ParameterizedPassword $password';
/** @var LayeredParameterizedPassword $layeredPassword */
$layeredPassword = $passwordFactory->newFromType( $layeredType );
+ '@phan-var LayeredParameterizedPassword $layeredPassword';
$layeredPassword->partialCrypt( $password );
$updateUsers[] = $user;
} );
};
+ // Skeleton user object, extended by the 'mediawiki.user' module.
+ /**
+ * @class mw.user
+ * @singleton
+ */
+ mw.user = {
+ /**
+ * @property {mw.Map}
+ */
+ options: new mw.Map(),
+ /**
+ * @property {mw.Map}
+ */
+ tokens: new mw.Map()
+ };
+
// Alias $j to jQuery for backwards compatibility
// @deprecated since 1.23 Use $ or jQuery instead
mw.log.deprecate( window, '$j', $, 'Use $ or jQuery instead.' );
}() )
}
};
- }() ),
-
- // Skeleton user object, extended by the 'mediawiki.user' module.
- /**
- * @class mw.user
- * @singleton
- */
- user: {
- /**
- * @property {mw.Map}
- */
- options: new Map(),
- /**
- * @property {mw.Map}
- */
- tokens: new Map()
- }
-
+ }() )
};
// Attach to window and globally alias
* Common code for test environment initialisation and teardown
*/
class TestSetup {
+ public static $bootstrapGlobals;
+
+ /**
+ * For use in MediaWikiUnitTestCase.
+ *
+ * This should be called before DefaultSettings.php or Setup.php loads.
+ */
+ public static function snapshotGlobals() {
+ self::$bootstrapGlobals = [];
+ foreach ( $GLOBALS as $key => $_ ) {
+ // Support: HHVM (avoid self-ref)
+ if ( $key !== 'GLOBALS' ) {
+ self::$bootstrapGlobals[ $key ] =& $GLOBALS[$key];
+ }
+ }
+ }
+
/**
* This should be called before Setup.php, e.g. from the finalSetup() method
* of a Maintenance subclass
'ApiQueryTestBase' => "$testDir/phpunit/includes/api/query/ApiQueryTestBase.php",
'ApiQueryContinueTestBase' => "$testDir/phpunit/includes/api/query/ApiQueryContinueTestBase.php",
'ApiTestCase' => "$testDir/phpunit/includes/api/ApiTestCase.php",
- 'ApiTestCaseUpload' => "$testDir/phpunit/includes/api/ApiTestCaseUpload.php",
'ApiTestContext' => "$testDir/phpunit/includes/api/ApiTestContext.php",
'ApiUploadTestCase' => "$testDir/phpunit/includes/api/ApiUploadTestCase.php",
'MockApi' => "$testDir/phpunit/includes/api/MockApi.php",
use Wikimedia\Rdbms\IMaintainableDatabase;
use Wikimedia\Rdbms\Database;
use Wikimedia\TestingAccessWrapper;
+use Wikimedia\Timestamp\ConvertibleTimestamp;
/**
* @since 1.18
$this->tmpFiles = array_merge( $this->tmpFiles, (array)$files );
}
+ private static function formatErrorLevel( $errorLevel ) {
+ switch ( gettype( $errorLevel ) ) {
+ case 'integer':
+ return '0x' . strtoupper( dechex( $errorLevel ) );
+ case 'NULL':
+ return 'null';
+ default:
+ throw new MWException( 'Unexpected error level type ' . gettype( $errorLevel ) );
+ }
+ }
+
protected function tearDown() {
global $wgRequest, $wgSQLMode;
if ( $phpErrorLevel !== $this->phpErrorLevel ) {
ini_set( 'error_reporting', $this->phpErrorLevel );
- $oldHex = strtoupper( dechex( $this->phpErrorLevel ) );
- $newHex = strtoupper( dechex( $phpErrorLevel ) );
+ $oldVal = self::formatErrorLevel( $this->phpErrorLevel );
+ $newVal = self::formatErrorLevel( $phpErrorLevel );
$message = "PHP error_reporting setting was left dirty: "
- . "was 0x$oldHex before test, 0x$newHex after test!";
+ . "was $oldVal before test, $newVal after test!";
$this->fail( $message );
}
+ // If anything faked the time, reset it
+ ConvertibleTimestamp::setFakeTime( false );
+
parent::tearDown();
}
$instantiator
);
- self::resetLegacyGlobals();
+ if ( $name === 'ContentLanguage' ) {
+ $this->setMwGlobals( [ 'wgContLang' => $this->localServices->getContentLanguage() ] );
+ }
}
/**
$this->localServices->resetServiceForTesting( $name, true );
}
- self::resetLegacyGlobals();
+ self::resetGlobalParser();
Language::clearCaches();
}
$newInstance->redefineService( $name, $callback );
}
- self::resetLegacyGlobals();
+ self::resetGlobalParser();
return $newInstance;
}
MediaWikiServices::forceGlobalInstance( $newServices );
- self::resetLegacyGlobals();
+ self::resetGlobalParser();
return $newServices;
}
MediaWikiServices::forceGlobalInstance( self::$originalServices );
$currentServices->destroy();
- self::resetLegacyGlobals();
+ self::resetGlobalParser();
return true;
}
/**
- * If legacy globals such as $wgParser or $wgContLang have been unstubbed, replace them with
- * fresh ones so they pick up any config changes. They're deprecated, but we still support them
- * for now.
+ * If $wgParser has been unstubbed, replace it with a fresh one so it picks up any config
+ * changes. $wgParser is deprecated, but we still support it for now.
*/
- private static function resetLegacyGlobals() {
+ private static function resetGlobalParser() {
// phpcs:ignore MediaWiki.Usage.DeprecatedGlobalVariables.Deprecated$wgParser
- global $wgParser, $wgContLang;
- if ( !( $wgParser instanceof StubObject ) ) {
- $wgParser = new StubObject( 'wgParser', function () {
- return MediaWikiServices::getInstance()->getParser();
- } );
- }
- if ( !( $wgContLang instanceof StubObject ) ) {
- $wgContlang = new StubObject( 'wgContLang', function () {
- return MediaWikiServices::getInstance()->getContLang();
- } );
+ global $wgParser;
+ if ( $wgParser instanceof StubObject ) {
+ return;
}
+ $wgParser = new StubObject( 'wgParser', function () {
+ return MediaWikiServices::getInstance()->getParser();
+ } );
}
/**
}
/**
- * @deprecated since 1.34, use setMwGlobals( 'wgLanguageCode' ) to set the code or
- * setService( 'ContentLanguage' ) to set an object
* @since 1.27
* @param string|Language $lang
*/
$this->setService( 'ContentLanguage', $lang );
$this->setMwGlobals( 'wgLanguageCode', $lang->getCode() );
} else {
- $this->setMwGlobals( 'wgLanguageCode', $lang );
+ $this->setMwGlobals( [
+ 'wgLanguageCode' => $lang,
+ 'wgContLang' => Language::factory( $lang ),
+ ] );
}
}
*/
protected function createNoOpMock( $type ) {
$mock = $this->createMock( $type );
- $mock->expects( $this->never() )->method( $this->anything() );
+ $mock->expects( $this->never() )->method( $this->anythingBut( '__destruct' ) );
return $mock;
}
}
use MediaWikiCoversValidator;
use MediaWikiTestCaseTrait;
- private $unitGlobals = [];
+ private static $originalGlobals;
+ private static $unitGlobals;
- protected function setUp() {
- parent::setUp();
- $reflection = new ReflectionClass( $this );
+ public static function setUpBeforeClass() {
+ parent::setUpBeforeClass();
+
+ $reflection = new ReflectionClass( static::class );
$dirSeparator = DIRECTORY_SEPARATOR;
- if ( strpos( $reflection->getFilename(), "${dirSeparator}unit${dirSeparator}" ) === false ) {
- $this->fail( 'This unit test needs to be in "tests/phpunit/unit"!' );
+ if ( stripos( $reflection->getFilename(), "${dirSeparator}unit${dirSeparator}" ) === false ) {
+ self::fail( 'This unit test needs to be in "tests/phpunit/unit"!' );
+ }
+
+ if ( defined( 'HHVM_VERSION' ) ) {
+ // There are a number of issues we encountered in trying to make this
+ // work on HHVM. Specifically, once an MediaWikiIntegrationTestCase executes
+ // before us, the original globals go missing. This might have to do with
+ // one of the non-unit tests passing GLOBALS somewhere and causing HHVM
+ // to get confused somehow.
+ return;
+ }
+
+ self::$unitGlobals =& TestSetup::$bootstrapGlobals;
+ // The autoloader may change between bootstrap and the first test,
+ // so (lazily) capture these here instead.
+ self::$unitGlobals['wgAutoloadClasses'] =& $GLOBALS['wgAutoloadClasses'];
+ self::$unitGlobals['wgAutoloadLocalClasses'] =& $GLOBALS['wgAutoloadLocalClasses'];
+ // This value should always be true.
+ self::$unitGlobals['wgAutoloadAttemptLowercase'] = true;
+
+ // Would be nice if we coud simply replace $GLOBALS as a whole,
+ // but unsetting or re-assigning that breaks the reference of this magic
+ // variable. Thus we have to modify it in place.
+ self::$originalGlobals = [];
+ foreach ( $GLOBALS as $key => $_ ) {
+ // Stash current values
+ self::$originalGlobals[$key] =& $GLOBALS[$key];
+
+ // Remove globals not part of the snapshot (see bootstrap.php, phpunit.php).
+ // Support: HHVM (avoid self-ref)
+ if ( $key !== 'GLOBALS' && !array_key_exists( $key, self::$unitGlobals ) ) {
+ unset( $GLOBALS[$key] );
+ }
}
- $this->unitGlobals = $GLOBALS;
- unset( $GLOBALS );
- $GLOBALS = [];
- // Add back the minimal set of globals needed for unit tests to run for core +
- // extensions/skins.
- foreach ( $this->unitGlobals['wgPhpUnitBootstrapGlobals'] ?? [] as $key => $value ) {
- $GLOBALS[ $key ] = $this->unitGlobals[ $key ];
+ // Restore values from the early snapshot
+ // Not by ref because tests must not be able to modify the snapshot.
+ foreach ( self::$unitGlobals as $key => $value ) {
+ $GLOBALS[ $key ] = $value;
}
}
protected function tearDown() {
- $GLOBALS = $this->unitGlobals;
+ if ( !defined( 'HHVM_VERSION' ) ) {
+ // Quick reset between tests
+ foreach ( $GLOBALS as $key => $_ ) {
+ if ( $key !== 'GLOBALS' && !array_key_exists( $key, self::$unitGlobals ) ) {
+ unset( $GLOBALS[$key] );
+ }
+ }
+ foreach ( self::$unitGlobals as $key => $value ) {
+ $GLOBALS[ $key ] = $value;
+ }
+ }
+
parent::tearDown();
}
+ public static function tearDownAfterClass() {
+ if ( !defined( 'HHVM_VERSION' ) ) {
+ // Remove globals created by the test
+ foreach ( $GLOBALS as $key => $_ ) {
+ if ( $key !== 'GLOBALS' && !array_key_exists( $key, self::$originalGlobals ) ) {
+ unset( $GLOBALS[$key] );
+ }
+ }
+ // Restore values (including reference!)
+ foreach ( self::$originalGlobals as $key => &$value ) {
+ $GLOBALS[ $key ] =& $value;
+ }
+ }
+
+ parent::tearDownAfterClass();
+ }
+
/**
* Create a temporary hook handler which will be reset by tearDown.
* This replaces other handlers for the same hook.
// these variables must be defined before setup runs
$GLOBALS['IP'] = $IP;
-// Set bootstrap globals to reuse in MediaWikiUnitTestCase
-$bootstrapGlobals = [];
-foreach ( $GLOBALS as $key => $value ) {
- $bootstrapGlobals[ $key ] = $value;
-}
-$GLOBALS['wgPhpUnitBootstrapGlobals'] = $bootstrapGlobals;
-// Faking for Setup.php
+
+require_once "$IP/tests/common/TestSetup.php";
+TestSetup::snapshotGlobals();
+
+// Faking in lieu of Setup.php
$GLOBALS['wgScopeTest'] = 'MediaWiki Setup.php scope test';
$GLOBALS['wgCommandLineMode'] = true;
$GLOBALS['wgAutoloadClasses'] = [];
-require_once "$IP/tests/common/TestSetup.php";
-
wfRequireOnceInGlobalScope( "$IP/includes/AutoLoader.php" );
wfRequireOnceInGlobalScope( "$IP/tests/common/TestsAutoLoader.php" );
wfRequireOnceInGlobalScope( "$IP/includes/Defines.php" );
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use MediaWiki\Revision\MainSlotRoleHandler;
+use PHPUnit\Framework\MockObject\MockObject;
+use Title;
+
+/**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler
+ */
+class MainSlotRoleHandlerTest extends \MediaWikiIntegrationTestCase {
+
+ private function makeTitleObject( $ns ) {
+ /** @var Title|MockObject $title */
+ $title = $this->getMockBuilder( Title::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $title->method( 'getNamespace' )
+ ->willReturn( $ns );
+
+ return $title;
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::__construct
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::getRole()
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::getNameMessageKey()
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::getOutputLayoutHints()
+ */
+ public function testConstruction() {
+ $handler = new MainSlotRoleHandler( [] );
+ $this->assertSame( 'main', $handler->getRole() );
+ $this->assertSame( 'slot-name-main', $handler->getNameMessageKey() );
+
+ $hints = $handler->getOutputLayoutHints();
+ $this->assertArrayHasKey( 'display', $hints );
+ $this->assertArrayHasKey( 'region', $hints );
+ $this->assertArrayHasKey( 'placement', $hints );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::getDefaultModel()
+ */
+ public function testFetDefaultModel() {
+ $handler = new MainSlotRoleHandler( [ 100 => CONTENT_MODEL_TEXT ] );
+
+ // For the main handler, the namespace determins the default model
+ $titleMain = $this->makeTitleObject( NS_MAIN );
+ $this->assertSame( CONTENT_MODEL_WIKITEXT, $handler->getDefaultModel( $titleMain ) );
+
+ $title100 = $this->makeTitleObject( 100 );
+ $this->assertSame( CONTENT_MODEL_TEXT, $handler->getDefaultModel( $title100 ) );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::isAllowedModel()
+ */
+ public function testIsAllowedModel() {
+ $handler = new MainSlotRoleHandler( [] );
+
+ // For the main handler, (nearly) all models are allowed
+ $title = $this->makeTitleObject( NS_MAIN );
+ $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_WIKITEXT, $title ) );
+ $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_TEXT, $title ) );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::supportsArticleCount()
+ */
+ public function testSupportsArticleCount() {
+ $handler = new MainSlotRoleHandler( [] );
+
+ $this->assertTrue( $handler->supportsArticleCount() );
+ }
+
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use InvalidArgumentException;
+use LogicException;
+use MediaWiki\Revision\IncompleteRevisionException;
+use MediaWiki\Revision\SlotRecord;
+use MediaWiki\Revision\SuppressedDataException;
+use WikitextContent;
+
+/**
+ * @covers \MediaWiki\Revision\SlotRecord
+ */
+class SlotRecordTest extends \MediaWikiIntegrationTestCase {
+
+ private function makeRow( $data = [] ) {
+ $data = $data + [
+ 'slot_id' => 1234,
+ 'slot_content_id' => 33,
+ 'content_size' => '5',
+ 'content_sha1' => 'someHash',
+ 'content_address' => 'tt:456',
+ 'model_name' => CONTENT_MODEL_WIKITEXT,
+ 'format_name' => CONTENT_FORMAT_WIKITEXT,
+ 'slot_revision_id' => '2',
+ 'slot_origin' => '1',
+ 'role_name' => 'myRole',
+ ];
+ return (object)$data;
+ }
+
+ public function testCompleteConstruction() {
+ $row = $this->makeRow();
+ $record = new SlotRecord( $row, new WikitextContent( 'A' ) );
+
+ $this->assertTrue( $record->hasAddress() );
+ $this->assertTrue( $record->hasContentId() );
+ $this->assertTrue( $record->hasRevision() );
+ $this->assertTrue( $record->isInherited() );
+ $this->assertSame( 'A', $record->getContent()->getText() );
+ $this->assertSame( 5, $record->getSize() );
+ $this->assertSame( 'someHash', $record->getSha1() );
+ $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
+ $this->assertSame( 2, $record->getRevision() );
+ $this->assertSame( 1, $record->getOrigin() );
+ $this->assertSame( 'tt:456', $record->getAddress() );
+ $this->assertSame( 33, $record->getContentId() );
+ $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
+ $this->assertSame( 'myRole', $record->getRole() );
+ }
+
+ public function testConstructionDeferred() {
+ $row = $this->makeRow( [
+ 'content_size' => null, // to be computed
+ 'content_sha1' => null, // to be computed
+ 'format_name' => function () {
+ return CONTENT_FORMAT_WIKITEXT;
+ },
+ 'slot_revision_id' => '2',
+ 'slot_origin' => '2',
+ 'slot_content_id' => function () {
+ return null;
+ },
+ ] );
+
+ $content = function () {
+ return new WikitextContent( 'A' );
+ };
+
+ $record = new SlotRecord( $row, $content );
+
+ $this->assertTrue( $record->hasAddress() );
+ $this->assertTrue( $record->hasRevision() );
+ $this->assertFalse( $record->hasContentId() );
+ $this->assertFalse( $record->isInherited() );
+ $this->assertSame( 'A', $record->getContent()->getText() );
+ $this->assertSame( 1, $record->getSize() );
+ $this->assertNotEmpty( $record->getSha1() );
+ $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
+ $this->assertSame( 2, $record->getRevision() );
+ $this->assertSame( 2, $record->getRevision() );
+ $this->assertSame( 'tt:456', $record->getAddress() );
+ $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
+ $this->assertSame( 'myRole', $record->getRole() );
+ }
+
+ public function testNewUnsaved() {
+ $record = SlotRecord::newUnsaved( 'myRole', new WikitextContent( 'A' ) );
+
+ $this->assertFalse( $record->hasAddress() );
+ $this->assertFalse( $record->hasContentId() );
+ $this->assertFalse( $record->hasRevision() );
+ $this->assertFalse( $record->isInherited() );
+ $this->assertFalse( $record->hasOrigin() );
+ $this->assertSame( 'A', $record->getContent()->getText() );
+ $this->assertSame( 1, $record->getSize() );
+ $this->assertNotEmpty( $record->getSha1() );
+ $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
+ $this->assertSame( 'myRole', $record->getRole() );
+ }
+
+ public function provideInvalidConstruction() {
+ yield 'both null' => [ null, null ];
+ yield 'null row' => [ null, new WikitextContent( 'A' ) ];
+ yield 'array row' => [ [], new WikitextContent( 'A' ) ];
+ yield 'empty row' => [ (object)[], new WikitextContent( 'A' ) ];
+ yield 'null content' => [ (object)[], null ];
+ }
+
+ /**
+ * @dataProvider provideInvalidConstruction
+ */
+ public function testInvalidConstruction( $row, $content ) {
+ $this->setExpectedException( InvalidArgumentException::class );
+ new SlotRecord( $row, $content );
+ }
+
+ public function testGetContentId_fails() {
+ $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+ $this->setExpectedException( IncompleteRevisionException::class );
+
+ $record->getContentId();
+ }
+
+ public function testGetAddress_fails() {
+ $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+ $this->setExpectedException( IncompleteRevisionException::class );
+
+ $record->getAddress();
+ }
+
+ public function provideIncomplete() {
+ $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+ yield 'unsaved' => [ $unsaved ];
+
+ $parent = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
+ $inherited = SlotRecord::newInherited( $parent );
+ yield 'inherited' => [ $inherited ];
+ }
+
+ /**
+ * @dataProvider provideIncomplete
+ */
+ public function testGetRevision_fails( SlotRecord $record ) {
+ $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+ $this->setExpectedException( IncompleteRevisionException::class );
+
+ $record->getRevision();
+ }
+
+ /**
+ * @dataProvider provideIncomplete
+ */
+ public function testGetOrigin_fails( SlotRecord $record ) {
+ $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+ $this->setExpectedException( IncompleteRevisionException::class );
+
+ $record->getOrigin();
+ }
+
+ public function provideHashStability() {
+ yield [ '', 'phoiac9h4m842xq45sp7s6u21eteeq1' ];
+ yield [ 'Lorem ipsum', 'hcr5u40uxr81d3nx89nvwzclfz6r9c5' ];
+ }
+
+ /**
+ * @dataProvider provideHashStability
+ */
+ public function testHashStability( $text, $hash ) {
+ // Changing the output of the hash function will break things horribly!
+
+ $this->assertSame( $hash, SlotRecord::base36Sha1( $text ) );
+
+ $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( $text ) );
+ $this->assertSame( $hash, $record->getSha1() );
+ }
+
+ public function testHashComputed() {
+ $row = $this->makeRow();
+ $row->content_sha1 = '';
+
+ $rec = new SlotRecord( $row, new WikitextContent( 'A' ) );
+ $this->assertNotEmpty( $rec->getSha1() );
+ }
+
+ public function testNewWithSuppressedContent() {
+ $input = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
+ $output = SlotRecord::newWithSuppressedContent( $input );
+
+ $this->setExpectedException( SuppressedDataException::class );
+ $output->getContent();
+ }
+
+ public function testNewInherited() {
+ $row = $this->makeRow( [ 'slot_revision_id' => 7, 'slot_origin' => 7 ] );
+ $parent = new SlotRecord( $row, new WikitextContent( 'A' ) );
+
+ // This would happen while doing an edit, before saving revision meta-data.
+ $inherited = SlotRecord::newInherited( $parent );
+
+ $this->assertSame( $parent->getContentId(), $inherited->getContentId() );
+ $this->assertSame( $parent->getAddress(), $inherited->getAddress() );
+ $this->assertSame( $parent->getContent(), $inherited->getContent() );
+ $this->assertTrue( $inherited->isInherited() );
+ $this->assertTrue( $inherited->hasOrigin() );
+ $this->assertFalse( $inherited->hasRevision() );
+
+ // make sure we didn't mess with the internal state of $parent
+ $this->assertFalse( $parent->isInherited() );
+ $this->assertSame( 7, $parent->getRevision() );
+
+ // This would happen while doing an edit, after saving the revision meta-data
+ // and content meta-data.
+ $saved = SlotRecord::newSaved(
+ 10,
+ $inherited->getContentId(),
+ $inherited->getAddress(),
+ $inherited
+ );
+ $this->assertSame( $parent->getContentId(), $saved->getContentId() );
+ $this->assertSame( $parent->getAddress(), $saved->getAddress() );
+ $this->assertSame( $parent->getContent(), $saved->getContent() );
+ $this->assertTrue( $saved->isInherited() );
+ $this->assertTrue( $saved->hasRevision() );
+ $this->assertSame( 10, $saved->getRevision() );
+
+ // make sure we didn't mess with the internal state of $parent or $inherited
+ $this->assertSame( 7, $parent->getRevision() );
+ $this->assertFalse( $inherited->hasRevision() );
+ }
+
+ public function testNewSaved() {
+ // This would happen while doing an edit, before saving revision meta-data.
+ $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+
+ // This would happen while doing an edit, after saving the revision meta-data
+ // and content meta-data.
+ $saved = SlotRecord::newSaved( 10, 20, 'theNewAddress', $unsaved );
+ $this->assertFalse( $saved->isInherited() );
+ $this->assertTrue( $saved->hasOrigin() );
+ $this->assertTrue( $saved->hasRevision() );
+ $this->assertTrue( $saved->hasAddress() );
+ $this->assertTrue( $saved->hasContentId() );
+ $this->assertSame( 'theNewAddress', $saved->getAddress() );
+ $this->assertSame( 20, $saved->getContentId() );
+ $this->assertSame( 'A', $saved->getContent()->getText() );
+ $this->assertSame( 10, $saved->getRevision() );
+ $this->assertSame( 10, $saved->getOrigin() );
+
+ // make sure we didn't mess with the internal state of $unsaved
+ $this->assertFalse( $unsaved->hasAddress() );
+ $this->assertFalse( $unsaved->hasContentId() );
+ $this->assertFalse( $unsaved->hasRevision() );
+ }
+
+ public function provideNewSaved_LogicException() {
+ $freshRow = $this->makeRow( [
+ 'content_id' => 10,
+ 'content_address' => 'address:1',
+ 'slot_origin' => 1,
+ 'slot_revision_id' => 1,
+ ] );
+
+ $freshSlot = new SlotRecord( $freshRow, new WikitextContent( 'A' ) );
+ yield 'mismatching address' => [ 1, 10, 'address:BAD', $freshSlot ];
+ yield 'mismatching revision' => [ 5, 10, 'address:1', $freshSlot ];
+ yield 'mismatching content ID' => [ 1, 17, 'address:1', $freshSlot ];
+
+ $inheritedRow = $this->makeRow( [
+ 'content_id' => null,
+ 'content_address' => null,
+ 'slot_origin' => 0,
+ 'slot_revision_id' => 1,
+ ] );
+
+ $inheritedSlot = new SlotRecord( $inheritedRow, new WikitextContent( 'A' ) );
+ yield 'inherited, but no address' => [ 1, 10, 'address:2', $inheritedSlot ];
+ }
+
+ /**
+ * @dataProvider provideNewSaved_LogicException
+ */
+ public function testNewSaved_LogicException(
+ $revisionId,
+ $contentId,
+ $contentAddress,
+ SlotRecord $protoSlot
+ ) {
+ $this->setExpectedException( LogicException::class );
+ SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
+ }
+
+ public function provideNewSaved_InvalidArgumentException() {
+ $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+
+ yield 'bad revision id' => [ 'xyzzy', 5, 'address', $unsaved ];
+ yield 'bad content id' => [ 7, 'xyzzy', 'address', $unsaved ];
+ yield 'bad content address' => [ 7, 5, 77, $unsaved ];
+ }
+
+ /**
+ * @dataProvider provideNewSaved_InvalidArgumentException
+ */
+ public function testNewSaved_InvalidArgumentException(
+ $revisionId,
+ $contentId,
+ $contentAddress,
+ SlotRecord $protoSlot
+ ) {
+ $this->setExpectedException( InvalidArgumentException::class );
+ SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
+ }
+
+ public function provideHasSameContent() {
+ $fail = function () {
+ self::fail( 'There should be no need to actually load the content.' );
+ };
+
+ $a100a1 = new SlotRecord(
+ $this->makeRow(
+ [
+ 'model_name' => 'A',
+ 'content_size' => 100,
+ 'content_sha1' => 'hash-a',
+ 'content_address' => 'xxx:a1',
+ ]
+ ),
+ $fail
+ );
+ $a100a1b = new SlotRecord(
+ $this->makeRow(
+ [
+ 'model_name' => 'A',
+ 'content_size' => 100,
+ 'content_sha1' => 'hash-a',
+ 'content_address' => 'xxx:a1',
+ ]
+ ),
+ $fail
+ );
+ $a100null = new SlotRecord(
+ $this->makeRow(
+ [
+ 'model_name' => 'A',
+ 'content_size' => 100,
+ 'content_sha1' => 'hash-a',
+ 'content_address' => null,
+ ]
+ ),
+ $fail
+ );
+ $a100a2 = new SlotRecord(
+ $this->makeRow(
+ [
+ 'model_name' => 'A',
+ 'content_size' => 100,
+ 'content_sha1' => 'hash-a',
+ 'content_address' => 'xxx:a2',
+ ]
+ ),
+ $fail
+ );
+ $b100a1 = new SlotRecord(
+ $this->makeRow(
+ [
+ 'model_name' => 'B',
+ 'content_size' => 100,
+ 'content_sha1' => 'hash-a',
+ 'content_address' => 'xxx:a1',
+ ]
+ ),
+ $fail
+ );
+ $a200a1 = new SlotRecord(
+ $this->makeRow(
+ [
+ 'model_name' => 'A',
+ 'content_size' => 200,
+ 'content_sha1' => 'hash-a',
+ 'content_address' => 'xxx:a2',
+ ]
+ ),
+ $fail
+ );
+ $a100x1 = new SlotRecord(
+ $this->makeRow(
+ [
+ 'model_name' => 'A',
+ 'content_size' => 100,
+ 'content_sha1' => 'hash-x',
+ 'content_address' => 'xxx:x1',
+ ]
+ ),
+ $fail
+ );
+
+ yield 'same instance' => [ $a100a1, $a100a1, true ];
+ yield 'no address' => [ $a100a1, $a100null, true ];
+ yield 'same address' => [ $a100a1, $a100a1b, true ];
+ yield 'different address' => [ $a100a1, $a100a2, true ];
+ yield 'different model' => [ $a100a1, $b100a1, false ];
+ yield 'different size' => [ $a100a1, $a200a1, false ];
+ yield 'different hash' => [ $a100a1, $a100x1, false ];
+ }
+
+ /**
+ * @dataProvider provideHasSameContent
+ */
+ public function testHasSameContent( SlotRecord $a, SlotRecord $b, $sameContent ) {
+ $this->assertSame( $sameContent, $a->hasSameContent( $b ) );
+ $this->assertSame( $sameContent, $b->hasSameContent( $a ) );
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * @covers WikiReference
+ */
+class WikiReferenceTest extends MediaWikiIntegrationTestCase {
+
+ public function provideGetDisplayName() {
+ return [
+ 'http' => [ 'foo.bar', 'http://foo.bar' ],
+ 'https' => [ 'foo.bar', 'http://foo.bar' ],
+
+ // apparently, this is the expected behavior
+ 'invalid' => [ 'purple kittens', 'purple kittens' ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetDisplayName
+ */
+ public function testGetDisplayName( $expected, $canonicalServer ) {
+ $reference = new WikiReference( $canonicalServer, '/wiki/$1' );
+ $this->assertEquals( $expected, $reference->getDisplayName() );
+ }
+
+ public function testGetCanonicalServer() {
+ $reference = new WikiReference( 'https://acme.com', '/wiki/$1', '//acme.com' );
+ $this->assertEquals( 'https://acme.com', $reference->getCanonicalServer() );
+ }
+
+ public function provideGetCanonicalUrl() {
+ return [
+ 'no fragment' => [
+ 'https://acme.com/wiki/Foo',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ null
+ ],
+ 'empty fragment' => [
+ 'https://acme.com/wiki/Foo',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ ''
+ ],
+ 'fragment' => [
+ 'https://acme.com/wiki/Foo#Bar',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ 'Bar'
+ ],
+ 'double fragment' => [
+ 'https://acme.com/wiki/Foo#Bar%23Xus',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ 'Bar#Xus'
+ ],
+ 'escaped fragment' => [
+ 'https://acme.com/wiki/Foo%23Bar',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo#Bar',
+ null
+ ],
+ 'empty path' => [
+ 'https://acme.com/Foo',
+ 'https://acme.com',
+ '//acme.com',
+ '/$1',
+ 'Foo',
+ null
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetCanonicalUrl
+ */
+ public function testGetCanonicalUrl(
+ $expected, $canonicalServer, $server, $path, $page, $fragmentId
+ ) {
+ $reference = new WikiReference( $canonicalServer, $path, $server );
+ $this->assertEquals( $expected, $reference->getCanonicalUrl( $page, $fragmentId ) );
+ }
+
+ /**
+ * @dataProvider provideGetCanonicalUrl
+ * @note getUrl is an alias for getCanonicalUrl
+ */
+ public function testGetUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
+ $reference = new WikiReference( $canonicalServer, $path, $server );
+ $this->assertEquals( $expected, $reference->getUrl( $page, $fragmentId ) );
+ }
+
+ public function provideGetFullUrl() {
+ return [
+ 'no fragment' => [
+ '//acme.com/wiki/Foo',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ null
+ ],
+ 'empty fragment' => [
+ '//acme.com/wiki/Foo',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ ''
+ ],
+ 'fragment' => [
+ '//acme.com/wiki/Foo#Bar',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ 'Bar'
+ ],
+ 'double fragment' => [
+ '//acme.com/wiki/Foo#Bar%23Xus',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ 'Bar#Xus'
+ ],
+ 'escaped fragment' => [
+ '//acme.com/wiki/Foo%23Bar',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo#Bar',
+ null
+ ],
+ 'empty path' => [
+ '//acme.com/Foo',
+ 'https://acme.com',
+ '//acme.com',
+ '/$1',
+ 'Foo',
+ null
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetFullUrl
+ */
+ public function testGetFullUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
+ $reference = new WikiReference( $canonicalServer, $path, $server );
+ $this->assertEquals( $expected, $reference->getFullUrl( $page, $fragmentId ) );
+ }
+
+}
+++ /dev/null
-<?php
-
-/**
- * For backward compatibility since 1.31
- */
-abstract class ApiTestCaseUpload extends ApiUploadTestCase {
-}
<?php
+
/**
- * n.b. Ensure that you can write to the images/ directory as the
- * user that will run tests.
- *
- * Note for reviewers: this intentionally duplicates functionality already in
- * "ApiSetup" and so on. This framework works better IMO and has less
- * strangeness (such as test cases inheriting from "ApiSetup"...) (and in the
- * case of the other Upload tests, this flat out just actually works... )
- *
- * @todo Port the other Upload tests, and other API tests to this framework
- *
- * @todo Broken test, reports false errors from time to time.
- * See https://phabricator.wikimedia.org/T28169
- *
- * @todo This is pretty sucky... needs to be prettified.
- *
* @group API
* @group Database
* @group medium
- * @group Broken
*
* @covers ApiUpload
*/
class ApiUploadTest extends ApiUploadTestCase {
- /**
- * Testing login
- * XXX this is a funny way of getting session context
- */
- public function testLogin() {
- $user = self::$users['uploader'];
- $userName = $user->getUser()->getName();
- $password = $user->getPassword();
-
- $params = [
- 'action' => 'login',
- 'lgname' => $userName,
- 'lgpassword' => $password
- ];
- list( $result, , $session ) = $this->doApiRequest( $params );
- $this->assertArrayHasKey( "login", $result );
- $this->assertArrayHasKey( "result", $result['login'] );
- $this->assertEquals( "NeedToken", $result['login']['result'] );
- $token = $result['login']['token'];
-
- $params = [
- 'action' => 'login',
- 'lgtoken' => $token,
- 'lgname' => $userName,
- 'lgpassword' => $password
- ];
- list( $result, , $session ) = $this->doApiRequest( $params, $session );
- $this->assertArrayHasKey( "login", $result );
- $this->assertArrayHasKey( "result", $result['login'] );
- $this->assertEquals( "Success", $result['login']['result'] );
-
- $this->assertNotEmpty( $session, 'API Login must return a session' );
-
- return $session;
+ private function filePath( $fileName ) {
+ return __DIR__ . '/../../data/media/' . $fileName;
}
- /**
- * @depends testLogin
- */
- public function testUploadRequiresToken( $session ) {
- $exception = false;
- try {
- $this->doApiRequest( [
- 'action' => 'upload'
- ] );
- } catch ( ApiUsageException $e ) {
- $exception = true;
- $this->assertContains( 'The "token" parameter must be set', $e->getMessage() );
- }
- $this->assertTrue( $exception, "Got exception" );
+ public function setUp() {
+ parent::setUp();
+ $this->tablesUsed[] = 'watchlist'; // This test might interfere with watchlists test.
+ $this->tablesUsed = array_merge( $this->tablesUsed, LocalFile::getQueryInfo()['tables'] );
+ $this->setService( 'RepoGroup', new RepoGroup(
+ [
+ 'class' => LocalRepo::class,
+ 'name' => 'temp',
+ 'backend' => new FSFileBackend( [
+ 'name' => 'temp-backend',
+ 'wikiId' => wfWikiID(),
+ 'basePath' => $this->getNewTempDirectory()
+ ] )
+ ],
+ [],
+ null
+ ) );
+ $this->resetServices();
}
- /**
- * @depends testLogin
- */
- public function testUploadMissingParams( $session ) {
- $exception = false;
- try {
- $this->doApiRequestWithToken( [
- 'action' => 'upload',
- ], $session, self::$users['uploader']->getUser() );
- } catch ( ApiUsageException $e ) {
- $exception = true;
- $this->assertEquals(
- 'One of the parameters "filekey", "file" and "url" is required.',
- $e->getMessage()
- );
- }
- $this->assertTrue( $exception, "Got exception" );
+ public function testUploadRequiresToken() {
+ $this->setExpectedException(
+ ApiUsageException::class,
+ 'The "token" parameter must be set'
+ );
+ $this->doApiRequest( [
+ 'action' => 'upload'
+ ] );
}
- /**
- * @depends testLogin
- */
- public function testUpload( $session ) {
- $extension = 'png';
- $mimeType = 'image/png';
-
- try {
- $randomImageGenerator = new RandomImageGenerator();
- $filePaths = $randomImageGenerator->writeImages( 1, $extension, $this->getNewTempDirectory() );
- } catch ( Exception $e ) {
- $this->markTestIncomplete( $e->getMessage() );
- }
-
- /** @var array $filePaths */
- $filePath = $filePaths[0];
- $fileSize = filesize( $filePath );
- $fileName = basename( $filePath );
-
- $this->deleteFileByFileName( $fileName );
- $this->deleteFileByContent( $filePath );
+ public function testUploadMissingParams() {
+ $this->setExpectedException(
+ ApiUsageException::class,
+ 'One of the parameters "filekey", "file" and "url" is required'
+ );
+ $this->doApiRequestWithToken( [
+ 'action' => 'upload',
+ ], null, self::$users['uploader']->getUser() );
+ }
- if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) {
- $this->markTestIncomplete( "Couldn't upload file!\n" );
- }
+ public function testUpload() {
+ $fileName = 'TestUpload.jpg';
+ $mimeType = 'image/jpeg';
+ $filePath = $this->filePath( 'yuv420.jpg' );
- $params = [
+ $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath );
+ list( $result ) = $this->doApiRequestWithToken( [
'action' => 'upload',
'filename' => $fileName,
'file' => 'dummy content',
'comment' => 'dummy comment',
'text' => "This is the page text for $fileName",
- ];
+ ], null, self::$users['uploader']->getUser() );
- $exception = false;
- try {
- list( $result, , ) = $this->doApiRequestWithToken( $params, $session,
- self::$users['uploader']->getUser() );
- } catch ( ApiUsageException $e ) {
- $exception = true;
- }
- $this->assertTrue( isset( $result['upload'] ) );
+ $this->assertArrayHasKey( 'upload', $result );
$this->assertEquals( 'Success', $result['upload']['result'] );
- $this->assertEquals( $fileSize, (int)$result['upload']['imageinfo']['size'] );
+ $this->assertSame( filesize( $filePath ), (int)$result['upload']['imageinfo']['size'] );
$this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] );
- $this->assertFalse( $exception );
-
- // clean up
- $this->deleteFileByFileName( $fileName );
}
- /**
- * @depends testLogin
- */
- public function testUploadZeroLength( $session ) {
- $mimeType = 'image/png';
-
+ public function testUploadZeroLength() {
$filePath = $this->getNewTempFile();
- $fileName = "apiTestUploadZeroLength.png";
-
- $this->deleteFileByFileName( $fileName );
+ $mimeType = 'image/jpeg';
+ $fileName = "ApiTestUploadZeroLength.jpg";
- if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) {
- $this->markTestIncomplete( "Couldn't upload file!\n" );
- }
+ $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath );
- $params = [
+ $this->setExpectedException(
+ ApiUsageException::class,
+ 'The file you submitted was empty'
+ );
+ $this->doApiRequestWithToken( [
'action' => 'upload',
'filename' => $fileName,
'file' => 'dummy content',
'comment' => 'dummy comment',
'text' => "This is the page text for $fileName",
- ];
-
- $exception = false;
- try {
- $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->getUser() );
- } catch ( ApiUsageException $e ) {
- $this->assertContains( 'The file you submitted was empty', $e->getMessage() );
- $exception = true;
- }
- $this->assertTrue( $exception );
-
- // clean up
- $this->deleteFileByFileName( $fileName );
+ ], null, self::$users['uploader']->getUser() );
}
- /**
- * @depends testLogin
- */
- public function testUploadSameFileName( $session ) {
- $extension = 'png';
- $mimeType = 'image/png';
-
- try {
- $randomImageGenerator = new RandomImageGenerator();
- $filePaths = $randomImageGenerator->writeImages( 2, $extension, $this->getNewTempDirectory() );
- } catch ( Exception $e ) {
- $this->markTestIncomplete( $e->getMessage() );
- }
-
- // we'll reuse this filename
- /** @var array $filePaths */
- $fileName = basename( $filePaths[0] );
-
- // clear any other files with the same name
- $this->deleteFileByFileName( $fileName );
+ public function testUploadSameFileName() {
+ $fileName = 'TestUploadSameFileName.jpg';
+ $mimeType = 'image/jpeg';
+ $filePaths = [
+ $this->filePath( 'yuv420.jpg' ),
+ $this->filePath( 'yuv444.jpg' )
+ ];
// we reuse these params
$params = [
// first upload .... should succeed
- if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[0] ) ) {
- $this->markTestIncomplete( "Couldn't upload file!\n" );
- }
-
- $exception = false;
- try {
- list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
- self::$users['uploader']->getUser() );
- } catch ( ApiUsageException $e ) {
- $exception = true;
- }
- $this->assertTrue( isset( $result['upload'] ) );
+ $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[0] );
+ list( $result ) = $this->doApiRequestWithToken( $params, null,
+ self::$users['uploader']->getUser() );
+ $this->assertArrayHasKey( 'upload', $result );
$this->assertEquals( 'Success', $result['upload']['result'] );
- $this->assertFalse( $exception );
// second upload with the same name (but different content)
- if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[1] ) ) {
- $this->markTestIncomplete( "Couldn't upload file!\n" );
- }
-
- $exception = false;
- try {
- list( $result, , ) = $this->doApiRequestWithToken( $params, $session,
- self::$users['uploader']->getUser() ); // FIXME: leaks a temporary file
- } catch ( ApiUsageException $e ) {
- $exception = true;
- }
- $this->assertTrue( isset( $result['upload'] ) );
+ $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[1] );
+ list( $result ) = $this->doApiRequestWithToken( $params, null,
+ self::$users['uploader']->getUser() );
+ $this->assertArrayHasKey( 'upload', $result );
$this->assertEquals( 'Warning', $result['upload']['result'] );
- $this->assertTrue( isset( $result['upload']['warnings'] ) );
- $this->assertTrue( isset( $result['upload']['warnings']['exists'] ) );
- $this->assertFalse( $exception );
-
- // clean up
- $this->deleteFileByFileName( $fileName );
+ $this->assertArrayHasKey( 'warnings', $result['upload'] );
+ $this->assertArrayHasKey( 'exists', $result['upload']['warnings'] );
}
- /**
- * @depends testLogin
- */
- public function testUploadSameContent( $session ) {
- $extension = 'png';
- $mimeType = 'image/png';
-
- try {
- $randomImageGenerator = new RandomImageGenerator();
- $filePaths = $randomImageGenerator->writeImages( 1, $extension, $this->getNewTempDirectory() );
- } catch ( Exception $e ) {
- $this->markTestIncomplete( $e->getMessage() );
- }
-
- /** @var array $filePaths */
- $fileNames[0] = basename( $filePaths[0] );
- $fileNames[1] = "SameContentAs" . $fileNames[0];
-
- // clear any other files with the same name or content
- $this->deleteFileByContent( $filePaths[0] );
- $this->deleteFileByFileName( $fileNames[0] );
- $this->deleteFileByFileName( $fileNames[1] );
+ public function testUploadSameContent() {
+ $fileNames = [ 'TestUploadSameContent_1.jpg', 'TestUploadSameContent_2.jpg' ];
+ $mimeType = 'image/jpeg';
+ $filePath = $this->filePath( 'yuv420.jpg' );
// first upload .... should succeed
-
- $params = [
+ $this->fakeUploadFile( 'file', $fileNames[0], $mimeType, $filePath );
+ list( $result ) = $this->doApiRequestWithToken( [
'action' => 'upload',
'filename' => $fileNames[0],
'file' => 'dummy content',
'comment' => 'dummy comment',
- 'text' => "This is the page text for " . $fileNames[0],
- ];
-
- if ( !$this->fakeUploadFile( 'file', $fileNames[0], $mimeType, $filePaths[0] ) ) {
- $this->markTestIncomplete( "Couldn't upload file!\n" );
- }
-
- $exception = false;
- try {
- list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
- self::$users['uploader']->getUser() );
- } catch ( ApiUsageException $e ) {
- $exception = true;
- }
- $this->assertTrue( isset( $result['upload'] ) );
+ 'text' => "This is the page text for {$fileNames[0]}",
+ ], null, self::$users['uploader']->getUser() );
+ $this->assertArrayHasKey( 'upload', $result );
$this->assertEquals( 'Success', $result['upload']['result'] );
- $this->assertFalse( $exception );
// second upload with the same content (but different name)
+ $this->fakeUploadFile( 'file', $fileNames[1], $mimeType, $filePath );
+ list( $result ) = $this->doApiRequestWithToken( [
+ 'action' => 'upload',
+ 'filename' => $fileNames[1],
+ 'file' => 'dummy content',
+ 'comment' => 'dummy comment',
+ 'text' => "This is the page text for {$fileNames[1]}",
+ ], null, self::$users['uploader']->getUser() );
- if ( !$this->fakeUploadFile( 'file', $fileNames[1], $mimeType, $filePaths[0] ) ) {
- $this->markTestIncomplete( "Couldn't upload file!\n" );
- }
-
- $params = [
- 'action' => 'upload',
- 'filename' => $fileNames[1],
- 'file' => 'dummy content',
- 'comment' => 'dummy comment',
- 'text' => "This is the page text for " . $fileNames[1],
- ];
-
- $exception = false;
- try {
- list( $result ) = $this->doApiRequestWithToken( $params, $session,
- self::$users['uploader']->getUser() ); // FIXME: leaks a temporary file
- } catch ( ApiUsageException $e ) {
- $exception = true;
- }
- $this->assertTrue( isset( $result['upload'] ) );
+ $this->assertArrayHasKey( 'upload', $result );
$this->assertEquals( 'Warning', $result['upload']['result'] );
- $this->assertTrue( isset( $result['upload']['warnings'] ) );
- $this->assertTrue( isset( $result['upload']['warnings']['duplicate'] ) );
- $this->assertFalse( $exception );
-
- // clean up
- $this->deleteFileByFileName( $fileNames[0] );
- $this->deleteFileByFileName( $fileNames[1] );
+ $this->assertArrayHasKey( 'warnings', $result['upload'] );
+ $this->assertArrayHasKey( 'duplicate', $result['upload']['warnings'] );
+ $this->assertArrayEquals( [ $fileNames[0] ], $result['upload']['warnings']['duplicate'] );
+ $this->assertArrayNotHasKey( 'exists', $result['upload']['warnings'] );
}
- /**
- * @depends testLogin
- */
- public function testUploadStash( $session ) {
- $this->setMwGlobals( [
- 'wgUser' => self::$users['uploader']->getUser(), // @todo FIXME: still used somewhere
- ] );
-
- $extension = 'png';
- $mimeType = 'image/png';
-
- try {
- $randomImageGenerator = new RandomImageGenerator();
- $filePaths = $randomImageGenerator->writeImages( 1, $extension, $this->getNewTempDirectory() );
- } catch ( Exception $e ) {
- $this->markTestIncomplete( $e->getMessage() );
- }
-
- /** @var array $filePaths */
- $filePath = $filePaths[0];
- $fileSize = filesize( $filePath );
- $fileName = basename( $filePath );
-
- $this->deleteFileByFileName( $fileName );
- $this->deleteFileByContent( $filePath );
-
- if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) {
- $this->markTestIncomplete( "Couldn't upload file!\n" );
- }
+ public function testUploadStash() {
+ $fileName = 'TestUploadStash.jpg';
+ $mimeType = 'image/jpeg';
+ $filePath = $this->filePath( 'yuv420.jpg' );
- $params = [
+ $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath );
+ list( $result ) = $this->doApiRequestWithToken( [
'action' => 'upload',
'stash' => 1,
'filename' => $fileName,
'file' => 'dummy content',
'comment' => 'dummy comment',
'text' => "This is the page text for $fileName",
- ];
+ ], null, self::$users['uploader']->getUser() );
- $exception = false;
- try {
- list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
- self::$users['uploader']->getUser() ); // FIXME: leaks a temporary file
- } catch ( ApiUsageException $e ) {
- $exception = true;
- }
- $this->assertFalse( $exception );
- $this->assertTrue( isset( $result['upload'] ) );
+ $this->assertArrayHasKey( 'upload', $result );
$this->assertEquals( 'Success', $result['upload']['result'] );
- $this->assertEquals( $fileSize, (int)$result['upload']['imageinfo']['size'] );
+ $this->assertSame( filesize( $filePath ), (int)$result['upload']['imageinfo']['size'] );
$this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] );
- $this->assertTrue( isset( $result['upload']['filekey'] ) );
+ $this->assertArrayHasKey( 'filekey', $result['upload'] );
$this->assertEquals( $result['upload']['sessionkey'], $result['upload']['filekey'] );
$filekey = $result['upload']['filekey'];
// XXX ...but how to test this, with a fake WebRequest with the session?
// now we should try to release the file from stash
- $params = [
+ $this->clearFakeUploads();
+ list( $result ) = $this->doApiRequestWithToken( [
'action' => 'upload',
'filekey' => $filekey,
'filename' => $fileName,
'comment' => 'dummy comment',
'text' => "This is the page text for $fileName, altered",
- ];
-
- $this->clearFakeUploads();
- $exception = false;
- try {
- list( $result ) = $this->doApiRequestWithToken( $params, $session,
- self::$users['uploader']->getUser() );
- } catch ( ApiUsageException $e ) {
- $exception = true;
- }
- $this->assertTrue( isset( $result['upload'] ) );
+ ], null, self::$users['uploader']->getUser() );
+ $this->assertArrayHasKey( 'upload', $result );
$this->assertEquals( 'Success', $result['upload']['result'] );
- $this->assertFalse( $exception, "No ApiUsageException exception." );
-
- // clean up
- $this->deleteFileByFileName( $fileName );
}
- /**
- * @depends testLogin
- */
- public function testUploadChunks( $session ) {
- $this->setMwGlobals( [
- // @todo FIXME: still used somewhere
- 'wgUser' => self::$users['uploader']->getUser(),
- ] );
-
- $chunkSize = 1048576;
- // Download a large image file
- // (using RandomImageGenerator for large files is not stable)
- // @todo Don't download files from wikimedia.org
+ public function testUploadChunks() {
+ $fileName = 'TestUploadChunks.jpg';
$mimeType = 'image/jpeg';
- $url = 'http://upload.wikimedia.org/wikipedia/commons/'
- . 'e/ed/Oberaargletscher_from_Oberaar%2C_2010_07.JPG';
- $filePath = $this->getNewTempDirectory() . '/Oberaargletscher_from_Oberaar.jpg';
- try {
- copy( $url, $filePath );
- } catch ( Exception $e ) {
- $this->markTestIncomplete( $e->getMessage() );
- }
-
+ $filePath = $this->filePath( 'yuv420.jpg' );
$fileSize = filesize( $filePath );
- $fileName = basename( $filePath );
+ $chunkSize = 20 * 1024; // The file is ~60kB, use 20kB chunks
- $this->deleteFileByFileName( $fileName );
- $this->deleteFileByContent( $filePath );
+ $this->setMwGlobals( [
+ 'wgMinUploadChunkSize' => $chunkSize
+ ] );
// Base upload params:
$params = [
];
// Upload chunks
- $chunkSessionKey = false;
- $resultOffset = 0;
- // Open the file:
- Wikimedia\suppressWarnings();
$handle = fopen( $filePath, "r" );
- Wikimedia\restoreWarnings();
-
- if ( $handle === false ) {
- $this->markTestIncomplete( "could not open file: $filePath" );
- }
-
+ $resultOffset = 0;
+ $filekey = false;
while ( !feof( $handle ) ) {
- // Get the current chunk
- Wikimedia\suppressWarnings();
$chunkData = fread( $handle, $chunkSize );
- Wikimedia\restoreWarnings();
// Upload the current chunk into the $_FILE object:
$this->fakeUploadChunk( 'chunk', 'blob', $mimeType, $chunkData );
-
- // Check for chunkSessionKey
- if ( !$chunkSessionKey ) {
- // Upload fist chunk ( and get the session key )
- try {
- list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
- self::$users['uploader']->getUser() );
- } catch ( ApiUsageException $e ) {
- $this->markTestIncomplete( $e->getMessage() );
- }
+ if ( !$filekey ) {
+ list( $result ) = $this->doApiRequestWithToken( $params, null,
+ self::$users['uploader']->getUser() );
// Make sure we got a valid chunk continue:
- $this->assertTrue( isset( $result['upload'] ) );
- $this->assertTrue( isset( $result['upload']['filekey'] ) );
- // If we don't get a session key mark test incomplete.
- if ( !isset( $result['upload']['filekey'] ) ) {
- $this->markTestIncomplete( "no filekey provided" );
- }
- $chunkSessionKey = $result['upload']['filekey'];
+ $this->assertArrayHasKey( 'upload', $result );
+ $this->assertArrayHasKey( 'filekey', $result['upload'] );
$this->assertEquals( 'Continue', $result['upload']['result'] );
- // First chunk should have chunkSize == offset
$this->assertEquals( $chunkSize, $result['upload']['offset'] );
+
+ $filekey = $result['upload']['filekey'];
$resultOffset = $result['upload']['offset'];
- continue;
- }
- // Filekey set to chunk session
- $params['filekey'] = $chunkSessionKey;
- // Update the offset ( always add chunkSize for subquent chunks
- // should be in-sync with $result['upload']['offset'] )
- $params['offset'] += $chunkSize;
- // Make sure param offset is insync with resultOffset:
- $this->assertEquals( $resultOffset, $params['offset'] );
- // Upload current chunk
- try {
- list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
- self::$users['uploader']->getUser() );
- } catch ( ApiUsageException $e ) {
- $this->markTestIncomplete( $e->getMessage() );
- }
- // Make sure we got a valid chunk continue:
- $this->assertTrue( isset( $result['upload'] ) );
- $this->assertTrue( isset( $result['upload']['filekey'] ) );
-
- // Check if we were on the last chunk:
- if ( $params['offset'] + $chunkSize >= $fileSize ) {
- $this->assertEquals( 'Success', $result['upload']['result'] );
- break;
} else {
- $this->assertEquals( 'Continue', $result['upload']['result'] );
- // update $resultOffset
- $resultOffset = $result['upload']['offset'];
+ // Filekey set to chunk session
+ $params['filekey'] = $filekey;
+ // Update the offset ( always add chunkSize for subquent chunks
+ // should be in-sync with $result['upload']['offset'] )
+ $params['offset'] += $chunkSize;
+ // Make sure param offset is insync with resultOffset:
+ $this->assertEquals( $resultOffset, $params['offset'] );
+ // Upload current chunk
+ list( $result ) = $this->doApiRequestWithToken( $params, null,
+ self::$users['uploader']->getUser() );
+ // Make sure we got a valid chunk continue:
+ $this->assertArrayHasKey( 'upload', $result );
+ $this->assertArrayHasKey( 'filekey', $result['upload'] );
+
+ // Check if we were on the last chunk:
+ if ( $params['offset'] + $chunkSize >= $fileSize ) {
+ $this->assertEquals( 'Success', $result['upload']['result'] );
+ break;
+ } else {
+ $this->assertEquals( 'Continue', $result['upload']['result'] );
+ $resultOffset = $result['upload']['offset'];
+ }
}
}
fclose( $handle );
// Check that we got a valid file result:
- wfDebug( __METHOD__
- . " hohoh filesize {$fileSize} info {$result['upload']['imageinfo']['size']}\n\n" );
$this->assertEquals( $fileSize, $result['upload']['imageinfo']['size'] );
$this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] );
- $this->assertTrue( isset( $result['upload']['filekey'] ) );
+ $this->assertArrayHasKey( 'filekey', $result['upload'] );
$filekey = $result['upload']['filekey'];
// Now we should try to release the file from stash
- $params = [
+ $this->clearFakeUploads();
+ list( $result ) = $this->doApiRequestWithToken( [
'action' => 'upload',
'filekey' => $filekey,
'filename' => $fileName,
'comment' => 'dummy comment',
'text' => "This is the page text for $fileName, altered",
- ];
- $this->clearFakeUploads();
- $exception = false;
- try {
- list( $result ) = $this->doApiRequestWithToken( $params, $session,
- self::$users['uploader']->getUser() );
- } catch ( ApiUsageException $e ) {
- $exception = true;
- }
- $this->assertTrue( isset( $result['upload'] ) );
+ ], null, self::$users['uploader']->getUser() );
+ $this->assertArrayHasKey( 'upload', $result );
$this->assertEquals( 'Success', $result['upload']['result'] );
- $this->assertFalse( $exception );
-
- // clean up
- $this->deleteFileByFileName( $fileName );
}
}
--- /dev/null
+<?php
+
+/**
+ * @covers DifferenceEngineSlotDiffRenderer
+ */
+class DifferenceEngineSlotDiffRendererTest extends MediaWikiIntegrationTestCase {
+
+ public function testGetDiff() {
+ $differenceEngine = new CustomDifferenceEngine();
+ $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+ $oldContent = ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT );
+ $newContent = ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT );
+
+ $diff = $slotDiffRenderer->getDiff( $oldContent, $newContent );
+ $this->assertEquals( 'xxx|yyy', $diff );
+
+ $diff = $slotDiffRenderer->getDiff( null, $newContent );
+ $this->assertEquals( '|yyy', $diff );
+
+ $diff = $slotDiffRenderer->getDiff( $oldContent, null );
+ $this->assertEquals( 'xxx|', $diff );
+ }
+
+ public function testAddModules() {
+ $output = $this->getMockBuilder( OutputPage::class )
+ ->disableOriginalConstructor()
+ ->setMethods( [ 'addModules' ] )
+ ->getMock();
+ $output->expects( $this->once() )
+ ->method( 'addModules' )
+ ->with( 'foo' );
+ $differenceEngine = new CustomDifferenceEngine();
+ $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+ $slotDiffRenderer->addModules( $output );
+ }
+
+ public function testGetExtraCacheKeys() {
+ $differenceEngine = new CustomDifferenceEngine();
+ $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+ $extraCacheKeys = $slotDiffRenderer->getExtraCacheKeys();
+ $this->assertSame( [ 'foo' ], $extraCacheKeys );
+ }
+
+}
--- /dev/null
+<?php
+
+use Wikimedia\Assert\ParameterTypeException;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @covers SlotDiffRenderer
+ */
+class SlotDiffRendererTest extends \MediaWikiIntegrationTestCase {
+
+ /**
+ * @dataProvider provideNormalizeContents
+ */
+ public function testNormalizeContents(
+ $oldContent, $newContent, $allowedClasses,
+ $expectedOldContent, $expectedNewContent, $expectedExceptionClass
+ ) {
+ $slotDiffRenderer = $this->getMockBuilder( SlotDiffRenderer::class )
+ ->getMock();
+ try {
+ // __call needs help deciding which parameter to take by reference
+ call_user_func_array( [ TestingAccessWrapper::newFromObject( $slotDiffRenderer ),
+ 'normalizeContents' ], [ &$oldContent, &$newContent, $allowedClasses ] );
+ $this->assertEquals( $expectedOldContent, $oldContent );
+ $this->assertEquals( $expectedNewContent, $newContent );
+ } catch ( Exception $e ) {
+ if ( !$expectedExceptionClass ) {
+ throw $e;
+ }
+ $this->assertInstanceOf( $expectedExceptionClass, $e );
+ }
+ }
+
+ public function provideNormalizeContents() {
+ return [
+ 'both null' => [ null, null, null, null, null, InvalidArgumentException::class ],
+ 'left null' => [
+ null, new WikitextContent( 'abc' ), null,
+ new WikitextContent( '' ), new WikitextContent( 'abc' ), null,
+ ],
+ 'right null' => [
+ new WikitextContent( 'def' ), null, null,
+ new WikitextContent( 'def' ), new WikitextContent( '' ), null,
+ ],
+ 'type filter' => [
+ new WikitextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
+ new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
+ ],
+ 'type filter (subclass)' => [
+ new WikitextContent( 'abc' ), new WikitextContent( 'def' ), TextContent::class,
+ new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
+ ],
+ 'type filter (null)' => [
+ new WikitextContent( 'abc' ), null, TextContent::class,
+ new WikitextContent( 'abc' ), new WikitextContent( '' ), null,
+ ],
+ 'type filter failure (left)' => [
+ new TextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
+ null, null, ParameterTypeException::class,
+ ],
+ 'type filter failure (right)' => [
+ new WikitextContent( 'abc' ), new TextContent( 'def' ), WikitextContent::class,
+ null, null, ParameterTypeException::class,
+ ],
+ 'type filter (array syntax)' => [
+ new WikitextContent( 'abc' ), new JsonContent( 'def' ),
+ [ JsonContent::class, WikitextContent::class ],
+ new WikitextContent( 'abc' ), new JsonContent( 'def' ), null,
+ ],
+ 'type filter failure (array syntax)' => [
+ new WikitextContent( 'abc' ), new CssContent( 'def' ),
+ [ JsonContent::class, WikitextContent::class ],
+ null, null, ParameterTypeException::class,
+ ],
+ ];
+ }
+
+}
* @covers FileBackendStoreShardDirIterator
* @covers FileBackendStoreShardFileIterator
* @covers FileBackendStoreShardListIterator
- * @covers FileJournal
* @covers FileOp
* @covers FileOpBatch
* @covers HTTPFileStreamer
* @covers MemoryFileBackend
* @covers MoveFileOp
* @covers MySqlLockManager
- * @covers NullFileJournal
* @covers NullFileOp
* @covers StoreFileOp
* @covers TempFSFile
--- /dev/null
+<?php
+
+use MediaWiki\MediaWikiServices;
+use Wikimedia\Timestamp\ConvertibleTimestamp;
+
+/**
+ * @coversDefaultClass DBFileJournal
+ * @covers ::__construct
+ * @covers ::getMasterDB
+ * @group Database
+ */
+class DBFileJournalIntegrationTest extends MediaWikiIntegrationTestCase {
+ public function addDBDataOnce() {
+ global $IP;
+ $db = MediaWikiServices::getInstance()->getDBLoadBalancer()->getConnection( DB_MASTER );
+ if ( $db->getType() !== 'mysql' ) {
+ return;
+ }
+ if ( !$db->tableExists( 'filejournal' ) ) {
+ $db->sourceFile( "$IP/maintenance/archives/patch-filejournal.sql" );
+ }
+ }
+
+ protected function setUp() {
+ parent::setUp();
+
+ $db = MediaWikiServices::getInstance()->getDBLoadBalancer()->getConnection( DB_MASTER );
+ if ( $db->getType() !== 'mysql' ) {
+ $this->markTestSkipped( 'No filejournal schema available for this database type' );
+ }
+
+ $this->tablesUsed[] = 'filejournal';
+ }
+
+ private function getJournal( $options = [] ) {
+ return FileJournal::factory(
+ $options + [ 'class' => DBFileJournal::class, 'domain' => wfWikiID() ],
+ 'local-backend' );
+ }
+
+ /**
+ * @covers ::doLogChangeBatch
+ */
+ public function testDoLogChangeBatch_exceptionDbConnect() {
+ $journal = $this->getJournal( [ 'domain' => 'no-such-domain' ] );
+
+ $this->assertEquals(
+ StatusValue::newFatal( 'filejournal-fail-dbconnect', 'local-backend' ),
+ $journal->logChangeBatch( [ [] ], 'batch' ) );
+ }
+
+ /**
+ * @covers ::doLogChangeBatch
+ */
+ public function testDoLogChangeBatch_exceptionDbQuery() {
+ MediaWikiServices::getInstance()->getConfiguredReadOnlyMode()->setReason( 'testing' );
+
+ $journal = $this->getJournal();
+
+ $this->assertEquals(
+ StatusValue::newFatal( 'filejournal-fail-dbquery', 'local-backend' ),
+ $journal->logChangeBatch(
+ [ [ 'op' => null, 'path' => '', 'newSha1' => false ] ], 'batch' ) );
+ }
+
+ /**
+ * @covers ::doLogChangeBatch
+ * @covers ::doGetCurrentPosition
+ */
+ public function testDoGetCurrentPosition() {
+ $journal = $this->getJournal();
+
+ $this->assertNull( $journal->getCurrentPosition() );
+
+ $journal->logChangeBatch(
+ [ [ 'op' => 'create', 'path' => '/path', 'newSha1' => false ] ], 'batch1' );
+
+ $this->assertSame( '1', $journal->getCurrentPosition() );
+
+ $journal->logChangeBatch(
+ [ [ 'op' => 'create', 'path' => '/path', 'newSha1' => false ] ], 'batch2' );
+
+ $this->assertSame( '2', $journal->getCurrentPosition() );
+ }
+
+ /**
+ * @covers ::doLogChangeBatch
+ * @covers ::doGetPositionAtTime
+ */
+ public function testDoGetPositionAtTime() {
+ $journal = $this->getJournal();
+
+ $now = time();
+
+ $this->assertFalse( $journal->getPositionAtTime( $now ) );
+
+ ConvertibleTimestamp::setFakeTime( $now - 86400 );
+
+ $journal->logChangeBatch(
+ [ [ 'op' => 'create', 'path' => '/path', 'newSha1' => false ] ], 'batch1' );
+
+ ConvertibleTimestamp::setFakeTime( $now - 3600 );
+
+ $journal->logChangeBatch(
+ [ [ 'op' => 'create', 'path' => '/path', 'newSha1' => false ] ], 'batch2' );
+
+ $this->assertFalse( $journal->getPositionAtTime( $now - 86401 ) );
+ $this->assertSame( '1', $journal->getPositionAtTime( $now - 86400 ) );
+ $this->assertSame( '1', $journal->getPositionAtTime( $now - 3601 ) );
+ $this->assertSame( '2', $journal->getPositionAtTime( $now - 3600 ) );
+ }
+
+ /**
+ * @param int $expectedStart First index expected to be returned (0-based)
+ * @param int|null $expectedCount Number of entries expected to be returned (null for all)
+ * @param string|null|false $expectedNext Expected value of $next, or false not to pass
+ * @param array $args If any third argument is present, $next will also be tested
+ * @dataProvider provideDoGetChangeEntries
+ * @covers ::doLogChangeBatch
+ * @covers ::doGetChangeEntries
+ */
+ public function testDoGetChangeEntries(
+ $expectedStart, $expectedCount, $expectedNext, array $args
+ ) {
+ $journal = $this->getJournal();
+
+ $i = 0;
+ $makeExpectedEntry = function ( $op, $path, $newSha1, $batch, $time ) use ( &$i ) {
+ $i++;
+ return [
+ 'id' => (string)$i,
+ 'batch_uuid' => $batch,
+ 'backend' => 'local-backend',
+ 'path' => $path,
+ 'op' => $op ?? '',
+ 'new_sha1' => $newSha1 !== false ? $newSha1 : '0',
+ 'timestamp' => ConvertibleTimestamp::convert( TS_MW, $time ),
+ ];
+ };
+
+ $expectedEntries = [];
+
+ $now = time();
+
+ ConvertibleTimestamp::setFakeTime( $now - 3600 );
+ $changes = [
+ [ 'op' => 'create', 'path' => '/path1',
+ 'newSha1' => base_convert( sha1( 'a' ), 16, 36 ) ],
+ [ 'op' => 'delete', 'path' => '/path2', 'newSha1' => false ],
+ [ 'op' => 'null', 'path' => '', 'newSha1' => false ],
+ ];
+ $this->assertEquals( StatusValue::newGood(),
+ $journal->logChangeBatch( $changes, 'batch1' ) );
+ foreach ( $changes as $change ) {
+ $expectedEntries[] = $makeExpectedEntry(
+ ...array_merge( array_values( $change ), [ 'batch1', $now - 3600 ] ) );
+ }
+
+ ConvertibleTimestamp::setFakeTime( $now - 60 );
+ $change = [ 'op' => 'update', 'path' => '/path1',
+ 'newSha1' => base_convert( sha1( 'b' ), 16, 36 ) ];
+ $this->assertEquals(
+ StatusValue::newGood(), $journal->logChangeBatch( [ $change ], 'batch2' ) );
+ $expectedEntries[] = $makeExpectedEntry(
+ ...array_merge( array_values( $change ), [ 'batch2', $now - 60 ] ) );
+
+ if ( $expectedNext === false ) {
+ $this->assertSame(
+ array_slice( $expectedEntries, $expectedStart, $expectedCount ),
+ $journal->getChangeEntries( ...$args )
+ );
+ } else {
+ $next = false;
+ $this->assertSame(
+ array_slice( $expectedEntries, $expectedStart, $expectedCount ),
+ $journal->getChangeEntries( $args[0], $args[1], $next )
+ );
+ $this->assertSame( $expectedNext, $next );
+ }
+ }
+
+ public static function provideDoGetChangeEntries() {
+ return [
+ 'No args' => [ 0, 4, false, [] ],
+ 'null' => [ 0, 4, false, [ null ] ],
+ '1' => [ 0, 4, false, [ 1 ] ],
+ '2' => [ 1, 3, false, [ 2 ] ],
+ '4' => [ 3, 1, false, [ 4 ] ],
+ '5' => [ 0, 0, false, [ 5 ] ],
+ 'null, 0' => [ 0, 4, null, [ null, 0 ] ],
+ '1, 0' => [ 0, 4, null, [ 1, 0 ] ],
+ '2, 0' => [ 1, 3, null, [ 2, 0 ] ],
+ '4, 0' => [ 3, 1, null, [ 4, 0 ] ],
+ '5, 0' => [ 0, 0, null, [ 5, 0 ] ],
+ '1, 1' => [ 0, 1, '2', [ 1, 1 ] ],
+ '1, 2' => [ 0, 2, '3', [ 1, 2 ] ],
+ '1, 4' => [ 0, 4, null, [ 1, 4 ] ],
+ '1, 5' => [ 0, 4, null, [ 1, 5 ] ],
+ '2, 2' => [ 1, 2, '4', [ 2, 2 ] ],
+ '1, 2 with no $next' => [ 0, 2, false, [ 1, 2 ] ],
+ ];
+ }
+
+ /**
+ * @covers ::doPurgeOldLogs
+ */
+ public function testDoPurgeOldLogs_noop() {
+ // If we tried to access the database, it would throw, because the domain doesn't exist
+ $journal = $this->getJournal( [ 'domain' => 'no-such-domain' ] );
+ $this->assertEquals( StatusValue::newGood(), $journal->purgeOldLogs() );
+ }
+
+ /**
+ * @covers ::doPurgeOldLogs
+ * @covers ::doLogChangeBatch
+ * @covers ::doGetChangeEntries
+ */
+ public function testDoPurgeOldLogs() {
+ $journal = $this->getJournal( [ 'ttlDays' => 1 ] );
+ $now = time();
+
+ // One day and one second ago
+ ConvertibleTimestamp::setFakeTime( $now - 86401 );
+ $this->assertEquals( StatusValue::newGood(), $journal->logChangeBatch(
+ [ [ 'op' => 'null', 'path' => '', 'newSha1' => false ] ], 'batch1' ) );
+
+ // One day ago exactly, won't get purged
+ ConvertibleTimestamp::setFakeTime( $now - 86400 );
+ $this->assertEquals( StatusValue::newGood(), $journal->logChangeBatch(
+ [ [ 'op' => 'null', 'path' => '', 'newSha1' => false ] ], 'batch2' ) );
+
+ ConvertibleTimestamp::setFakeTime( $now );
+ $this->assertCount( 2, $journal->getChangeEntries() );
+ $journal->purgeOldLogs();
+ $this->assertCount( 1, $journal->getChangeEntries() );
+ }
+}
--- /dev/null
+<?php
+
+class FileBackendDBRepoWrapperTest extends MediaWikiIntegrationTestCase {
+ protected $backendName = 'foo-backend';
+ protected $repoName = 'pureTestRepo';
+
+ /**
+ * @dataProvider getBackendPathsProvider
+ * @covers FileBackendDBRepoWrapper::getBackendPaths
+ */
+ public function testGetBackendPaths(
+ $mocks,
+ $latest,
+ $dbReadsExpected,
+ $dbReturnValue,
+ $originalPath,
+ $expectedBackendPath,
+ $message ) {
+ list( $dbMock, $backendMock, $wrapperMock ) = $mocks;
+
+ $dbMock->expects( $dbReadsExpected )
+ ->method( 'selectField' )
+ ->will( $this->returnValue( $dbReturnValue ) );
+
+ $newPaths = $wrapperMock->getBackendPaths( [ $originalPath ], $latest );
+
+ $this->assertEquals(
+ $expectedBackendPath,
+ $newPaths[0],
+ $message );
+ }
+
+ public function getBackendPathsProvider() {
+ $prefix = 'mwstore://' . $this->backendName . '/' . $this->repoName;
+ $mocksForCaching = $this->getMocks();
+
+ return [
+ [
+ $mocksForCaching,
+ false,
+ $this->once(),
+ '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+ $prefix . '-public/f/o/foobar.jpg',
+ $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+ 'Public path translated correctly',
+ ],
+ [
+ $mocksForCaching,
+ false,
+ $this->never(),
+ '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+ $prefix . '-public/f/o/foobar.jpg',
+ $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+ 'LRU cache leveraged',
+ ],
+ [
+ $this->getMocks(),
+ true,
+ $this->once(),
+ '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+ $prefix . '-public/f/o/foobar.jpg',
+ $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+ 'Latest obtained',
+ ],
+ [
+ $this->getMocks(),
+ true,
+ $this->never(),
+ '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+ $prefix . '-deleted/f/o/foobar.jpg',
+ $prefix . '-original/f/o/o/foobar',
+ 'Deleted path translated correctly',
+ ],
+ [
+ $this->getMocks(),
+ true,
+ $this->once(),
+ null,
+ $prefix . '-public/b/a/baz.jpg',
+ $prefix . '-public/b/a/baz.jpg',
+ 'Path left untouched if no sha1 can be found',
+ ],
+ ];
+ }
+
+ /**
+ * @covers FileBackendDBRepoWrapper::getFileContentsMulti
+ */
+ public function testGetFileContentsMulti() {
+ list( $dbMock, $backendMock, $wrapperMock ) = $this->getMocks();
+
+ $sha1Path = 'mwstore://' . $this->backendName . '/' . $this->repoName
+ . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9';
+ $filenamePath = 'mwstore://' . $this->backendName . '/' . $this->repoName
+ . '-public/f/o/foobar.jpg';
+
+ $dbMock->expects( $this->once() )
+ ->method( 'selectField' )
+ ->will( $this->returnValue( '96246614d75ba1703bdfd5d7660bb57407aaf5d9' ) );
+
+ $backendMock->expects( $this->once() )
+ ->method( 'getFileContentsMulti' )
+ ->will( $this->returnValue( [ $sha1Path => 'foo' ] ) );
+
+ $result = $wrapperMock->getFileContentsMulti( [ 'srcs' => [ $filenamePath ] ] );
+
+ $this->assertEquals(
+ [ $filenamePath => 'foo' ],
+ $result,
+ 'File contents paths translated properly'
+ );
+ }
+
+ protected function getMocks() {
+ $dbMock = $this->getMockBuilder( Wikimedia\Rdbms\IDatabase::class )
+ ->disableOriginalClone()
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $backendMock = $this->getMockBuilder( FSFileBackend::class )
+ ->setConstructorArgs( [ [
+ 'name' => $this->backendName,
+ 'wikiId' => wfWikiID()
+ ] ] )
+ ->getMock();
+
+ $wrapperMock = $this->getMockBuilder( FileBackendDBRepoWrapper::class )
+ ->setMethods( [ 'getDB' ] )
+ ->setConstructorArgs( [ [
+ 'backend' => $backendMock,
+ 'repoName' => $this->repoName,
+ 'dbHandleFactory' => null
+ ] ] )
+ ->getMock();
+
+ $wrapperMock->expects( $this->any() )->method( 'getDB' )->will( $this->returnValue( $dbMock ) );
+
+ return [ $dbMock, $backendMock, $wrapperMock ];
+ }
+}
[ '.e.x', 'e' ],
[ '..f.x', 'f' ],
[ 'g..x', 'g' ],
+ [ '01234567890123456789012345678901.x', '1234567890123456789012345678901' ],
];
}
--- /dev/null
+<?php
+/**
+ * @todo Could use a test of extended XMP segments. Hard to find programs that
+ * create example files, and creating my own in vim propbably wouldn't
+ * serve as a very good "test". (Adobe photoshop probably creates such files
+ * but it costs money). The implementation of it currently in MediaWiki is based
+ * solely on reading the standard, without any real world test files.
+ *
+ * @group Media
+ * @covers JpegMetadataExtractor
+ */
+class JpegMetadataExtractorTest extends MediaWikiIntegrationTestCase {
+
+ protected $filePath;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->filePath = __DIR__ . '/../../data/media/';
+ }
+
+ /**
+ * We also use this test to test padding bytes don't
+ * screw stuff up
+ *
+ * @param string $file Filename
+ *
+ * @dataProvider provideUtf8Comment
+ */
+ public function testUtf8Comment( $file ) {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . $file );
+ $this->assertEquals( [ 'UTF-8 JPEG Comment — ¼' ], $res['COM'] );
+ }
+
+ public static function provideUtf8Comment() {
+ return [
+ [ 'jpeg-comment-utf.jpg' ],
+ [ 'jpeg-padding-even.jpg' ],
+ [ 'jpeg-padding-odd.jpg' ],
+ ];
+ }
+
+ /** The file is iso-8859-1, but it should get auto converted */
+ public function testIso88591Comment() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-iso8859-1.jpg' );
+ $this->assertEquals( [ 'ISO-8859-1 JPEG Comment - ¼' ], $res['COM'] );
+ }
+
+ /** Comment values that are non-textual (random binary junk) should not be shown.
+ * The example test file has a comment with a 0x5 byte in it which is a control character
+ * and considered binary junk for our purposes.
+ */
+ public function testBinaryCommentStripped() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-binary.jpg' );
+ $this->assertEmpty( $res['COM'] );
+ }
+
+ /* Very rarely a file can have multiple comments.
+ * Order of comments is based on order inside the file.
+ */
+ public function testMultipleComment() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-multiple.jpg' );
+ $this->assertEquals( [ 'foo', 'bar' ], $res['COM'] );
+ }
+
+ public function testXMPExtraction() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
+ $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
+ $this->assertEquals( $expected, $res['XMP'] );
+ }
+
+ public function testPSIRExtraction() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
+ $expected = '50686f746f73686f7020332e30003842494d04040000000'
+ . '000181c02190004746573741c02190003666f6f1c020000020004';
+ $this->assertEquals( $expected, bin2hex( $res['PSIR'][0] ) );
+ }
+
+ public function testXMPExtractionAltAppId() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-alt.jpg' );
+ $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
+ $this->assertEquals( $expected, $res['XMP'] );
+ }
+
+ public function testIPTCHashComparisionNoHash() {
+ $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
+ $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
+
+ $this->assertEquals( 'iptc-no-hash', $res );
+ }
+
+ public function testIPTCHashComparisionBadHash() {
+ $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-bad-hash.jpg' );
+ $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
+
+ $this->assertEquals( 'iptc-bad-hash', $res );
+ }
+
+ public function testIPTCHashComparisionGoodHash() {
+ $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-good-hash.jpg' );
+ $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
+
+ $this->assertEquals( 'iptc-good-hash', $res );
+ }
+
+ public function testExifByteOrder() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'exif-user-comment.jpg' );
+ $expected = 'BE';
+ $this->assertEquals( $expected, $res['byteOrder'] );
+ }
+
+ public function testInfiniteRead() {
+ // test file truncated right after a segment, which previously
+ // caused an infinite loop looking for the next segment byte.
+ // Should get past infinite loop and throw in wfUnpack()
+ $this->setExpectedException( 'MWException' );
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop1.jpg' );
+ }
+
+ public function testInfiniteRead2() {
+ // test file truncated after a segment's marker and size, which
+ // would cause a seek past end of file. Seek past end of file
+ // doesn't actually fail, but prevents further reading and was
+ // devolving into the previous case (testInfiniteRead).
+ $this->setExpectedException( 'MWException' );
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop2.jpg' );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @covers ParserFactory
+ */
+class ParserFactoryTest extends MediaWikiIntegrationTestCase {
+ use FactoryArgTestTrait;
+
+ protected static function getFactoryClass() {
+ return ParserFactory::class;
+ }
+
+ protected static function getInstanceClass() {
+ return Parser::class;
+ }
+
+ protected static function getFactoryMethodName() {
+ return 'create';
+ }
+
+ protected static function getExtraClassArgCount() {
+ // The parser factory itself is passed to the parser
+ return 1;
+ }
+
+ protected function getOverriddenMockValueForParam( ReflectionParameter $param ) {
+ if ( $param->getPosition() === 0 ) {
+ return [ $this->createMock( MediaWiki\Config\ServiceOptions::class ) ];
+ }
+ return [];
+ }
+}
--- /dev/null
+<?php
+
+use MediaWiki\Site\MediaWikiPageNameNormalizer;
+
+/**
+ * @covers MediaWiki\Site\MediaWikiPageNameNormalizer
+ *
+ * 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
+ *
+ * @since 1.27
+ *
+ * @group Site
+ * @group medium
+ *
+ * @author Marius Hoch
+ */
+class MediaWikiPageNameNormalizerTest extends MediaWikiIntegrationTestCase {
+
+ /**
+ * @dataProvider normalizePageTitleProvider
+ */
+ public function testNormalizePageTitle( $expected, $pageName, $getResponse ) {
+ MediaWikiPageNameNormalizerTestMockHttp::$response = $getResponse;
+
+ $normalizer = new MediaWikiPageNameNormalizer(
+ new MediaWikiPageNameNormalizerTestMockHttp()
+ );
+
+ $this->assertSame(
+ $expected,
+ $normalizer->normalizePageName( $pageName, 'https://www.wikidata.org/w/api.php' )
+ );
+ }
+
+ public function normalizePageTitleProvider() {
+ // Response are taken from wikidata and kkwiki using the following API request
+ // api.php?action=query&prop=info&redirects=1&converttitles=1&format=json&titles=…
+ return [
+ 'universe (Q1)' => [
+ 'Q1',
+ 'Q1',
+ '{"batchcomplete":"","query":{"pages":{"129":{"pageid":129,"ns":0,'
+ . '"title":"Q1","contentmodel":"wikibase-item","pagelanguage":"en",'
+ . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
+ . '"touched":"2016-06-23T05:11:21Z","lastrevid":350004448,"length":58001}}}}'
+ ],
+ 'Q404 redirects to Q395' => [
+ 'Q395',
+ 'Q404',
+ '{"batchcomplete":"","query":{"redirects":[{"from":"Q404","to":"Q395"}],"pages"'
+ . ':{"601":{"pageid":601,"ns":0,"title":"Q395","contentmodel":"wikibase-item",'
+ . '"pagelanguage":"en","pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
+ . '"touched":"2016-06-23T08:00:20Z","lastrevid":350021914,"length":60108}}}}'
+ ],
+ 'D converted to Д (Latin to Cyrillic) (taken from kkwiki)' => [
+ 'Д',
+ 'D',
+ '{"batchcomplete":"","query":{"converted":[{"from":"D","to":"\u0414"}],'
+ . '"pages":{"510541":{"pageid":510541,"ns":0,"title":"\u0414",'
+ . '"contentmodel":"wikitext","pagelanguage":"kk","pagelanguagehtmlcode":"kk",'
+ . '"pagelanguagedir":"ltr","touched":"2015-11-22T09:16:18Z",'
+ . '"lastrevid":2373618,"length":3501}}}}'
+ ],
+ 'there is no Q0' => [
+ false,
+ 'Q0',
+ '{"batchcomplete":"","query":{"pages":{"-1":{"ns":0,"title":"Q0",'
+ . '"missing":"","contentmodel":"wikibase-item","pagelanguage":"en",'
+ . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr"}}}}'
+ ],
+ 'invalid title' => [
+ false,
+ '{{',
+ '{"batchcomplete":"","query":{"pages":{"-1":{"title":"{{",'
+ . '"invalidreason":"The requested page title contains invalid '
+ . 'characters: \"{\".","invalid":""}}}}'
+ ],
+ 'error on get' => [ false, 'ABC', false ]
+ ];
+ }
+
+}
+
+/**
+ * @private
+ * @see Http
+ */
+class MediaWikiPageNameNormalizerTestMockHttp extends Http {
+
+ /**
+ * @var mixed
+ */
+ public static $response;
+
+ public static function get( $url, array $options = [], $caller = __METHOD__ ) {
+ PHPUnit_Framework_Assert::assertInternalType( 'string', $url );
+ PHPUnit_Framework_Assert::assertInternalType( 'string', $caller );
+
+ return self::$response;
+ }
+}
--- /dev/null
+<?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
+ *
+ * @ingroup Site
+ * @ingroup Test
+ *
+ * @group Site
+ *
+ * @covers SiteExporter
+ *
+ * @author Daniel Kinzler
+ */
+class SiteExporterTest extends MediaWikiIntegrationTestCase {
+
+ public function testConstructor_InvalidArgument() {
+ $this->setExpectedException( InvalidArgumentException::class );
+
+ new SiteExporter( 'Foo' );
+ }
+
+ public function testExportSites() {
+ $foo = Site::newForType( Site::TYPE_UNKNOWN );
+ $foo->setGlobalId( 'Foo' );
+
+ $acme = Site::newForType( Site::TYPE_UNKNOWN );
+ $acme->setGlobalId( 'acme.com' );
+ $acme->setGroup( 'Test' );
+ $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
+ $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
+
+ $tmp = tmpfile();
+ $exporter = new SiteExporter( $tmp );
+
+ $exporter->exportSites( [ $foo, $acme ] );
+
+ fseek( $tmp, 0 );
+ $xml = fread( $tmp, 16 * 1024 );
+
+ $this->assertContains( '<sites ', $xml );
+ $this->assertContains( '<site>', $xml );
+ $this->assertContains( '<globalid>Foo</globalid>', $xml );
+ $this->assertContains( '</site>', $xml );
+ $this->assertContains( '<globalid>acme.com</globalid>', $xml );
+ $this->assertContains( '<group>Test</group>', $xml );
+ $this->assertContains( '<localid type="interwiki">acme</localid>', $xml );
+ $this->assertContains( '<path type="link">http://acme.com/</path>', $xml );
+ $this->assertContains( '</sites>', $xml );
+
+ // NOTE: HHVM (at least on wmf Jenkins) doesn't like file URLs.
+ $xsdFile = __DIR__ . '/../../../../docs/sitelist-1.0.xsd';
+ $xsdData = file_get_contents( $xsdFile );
+
+ $document = new DOMDocument();
+ $document->loadXML( $xml, LIBXML_NONET );
+ $document->schemaValidateSource( $xsdData );
+ }
+
+ private function newSiteStore( SiteList $sites ) {
+ $store = $this->getMockBuilder( SiteStore::class )->getMock();
+
+ $store->expects( $this->once() )
+ ->method( 'saveSites' )
+ ->will( $this->returnCallback( function ( $moreSites ) use ( $sites ) {
+ foreach ( $moreSites as $site ) {
+ $sites->setSite( $site );
+ }
+ } ) );
+
+ $store->expects( $this->any() )
+ ->method( 'getSites' )
+ ->will( $this->returnValue( new SiteList() ) );
+
+ return $store;
+ }
+
+ public function provideRoundTrip() {
+ $foo = Site::newForType( Site::TYPE_UNKNOWN );
+ $foo->setGlobalId( 'Foo' );
+
+ $acme = Site::newForType( Site::TYPE_UNKNOWN );
+ $acme->setGlobalId( 'acme.com' );
+ $acme->setGroup( 'Test' );
+ $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
+ $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
+
+ $dewiki = Site::newForType( Site::TYPE_MEDIAWIKI );
+ $dewiki->setGlobalId( 'dewiki' );
+ $dewiki->setGroup( 'wikipedia' );
+ $dewiki->setForward( true );
+ $dewiki->addLocalId( Site::ID_INTERWIKI, 'wikipedia' );
+ $dewiki->addLocalId( Site::ID_EQUIVALENT, 'de' );
+ $dewiki->setPath( Site::PATH_LINK, 'http://de.wikipedia.org/w/' );
+ $dewiki->setPath( MediaWikiSite::PATH_PAGE, 'http://de.wikipedia.org/wiki/' );
+ $dewiki->setSource( 'meta.wikimedia.org' );
+
+ return [
+ 'empty' => [
+ new SiteList()
+ ],
+
+ 'some' => [
+ new SiteList( [ $foo, $acme, $dewiki ] ),
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideRoundTrip()
+ */
+ public function testRoundTrip( SiteList $sites ) {
+ $tmp = tmpfile();
+ $exporter = new SiteExporter( $tmp );
+
+ $exporter->exportSites( $sites );
+
+ fseek( $tmp, 0 );
+ $xml = fread( $tmp, 16 * 1024 );
+
+ $actualSites = new SiteList();
+ $store = $this->newSiteStore( $actualSites );
+
+ $importer = new SiteImporter( $store );
+ $importer->importFromXML( $xml );
+
+ $this->assertEquals( $sites, $actualSites );
+ }
+
+}
--- /dev/null
+<?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
+ *
+ * @ingroup Site
+ * @ingroup Test
+ *
+ * @group Site
+ *
+ * @covers SiteImporter
+ *
+ * @author Daniel Kinzler
+ */
+class SiteImporterTest extends MediaWikiIntegrationTestCase {
+
+ private function newSiteImporter( array $expectedSites, $errorCount ) {
+ $store = $this->getMockBuilder( SiteStore::class )->getMock();
+
+ $store->expects( $this->once() )
+ ->method( 'saveSites' )
+ ->will( $this->returnCallback( function ( $sites ) use ( $expectedSites ) {
+ $this->assertSitesEqual( $expectedSites, $sites );
+ } ) );
+
+ $store->expects( $this->any() )
+ ->method( 'getSites' )
+ ->will( $this->returnValue( new SiteList() ) );
+
+ $errorHandler = $this->getMockBuilder( Psr\Log\LoggerInterface::class )->getMock();
+ $errorHandler->expects( $this->exactly( $errorCount ) )
+ ->method( 'error' );
+
+ $importer = new SiteImporter( $store );
+ $importer->setExceptionCallback( [ $errorHandler, 'error' ] );
+
+ return $importer;
+ }
+
+ public function assertSitesEqual( $expected, $actual, $message = '' ) {
+ $this->assertEquals(
+ $this->getSerializedSiteList( $expected ),
+ $this->getSerializedSiteList( $actual ),
+ $message
+ );
+ }
+
+ public function provideImportFromXML() {
+ $foo = Site::newForType( Site::TYPE_UNKNOWN );
+ $foo->setGlobalId( 'Foo' );
+
+ $acme = Site::newForType( Site::TYPE_UNKNOWN );
+ $acme->setGlobalId( 'acme.com' );
+ $acme->setGroup( 'Test' );
+ $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
+ $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
+
+ $dewiki = Site::newForType( Site::TYPE_MEDIAWIKI );
+ $dewiki->setGlobalId( 'dewiki' );
+ $dewiki->setGroup( 'wikipedia' );
+ $dewiki->setForward( true );
+ $dewiki->addLocalId( Site::ID_INTERWIKI, 'wikipedia' );
+ $dewiki->addLocalId( Site::ID_EQUIVALENT, 'de' );
+ $dewiki->setPath( Site::PATH_LINK, 'http://de.wikipedia.org/w/' );
+ $dewiki->setPath( MediaWikiSite::PATH_PAGE, 'http://de.wikipedia.org/wiki/' );
+ $dewiki->setSource( 'meta.wikimedia.org' );
+
+ return [
+ 'empty' => [
+ '<sites></sites>',
+ [],
+ ],
+ 'no sites' => [
+ '<sites><Foo><globalid>Foo</globalid></Foo><Bar><quux>Bla</quux></Bar></sites>',
+ [],
+ ],
+ 'minimal' => [
+ '<sites>' .
+ '<site><globalid>Foo</globalid></site>' .
+ '</sites>',
+ [ $foo ],
+ ],
+ 'full' => [
+ '<sites>' .
+ '<site><globalid>Foo</globalid></site>' .
+ '<site>' .
+ '<globalid>acme.com</globalid>' .
+ '<localid type="interwiki">acme</localid>' .
+ '<group>Test</group>' .
+ '<path type="link">http://acme.com/</path>' .
+ '</site>' .
+ '<site type="mediawiki">' .
+ '<source>meta.wikimedia.org</source>' .
+ '<globalid>dewiki</globalid>' .
+ '<localid type="interwiki">wikipedia</localid>' .
+ '<localid type="equivalent">de</localid>' .
+ '<group>wikipedia</group>' .
+ '<forward/>' .
+ '<path type="link">http://de.wikipedia.org/w/</path>' .
+ '<path type="page_path">http://de.wikipedia.org/wiki/</path>' .
+ '</site>' .
+ '</sites>',
+ [ $foo, $acme, $dewiki ],
+ ],
+ 'skip' => [
+ '<sites>' .
+ '<site><globalid>Foo</globalid></site>' .
+ '<site><barf>Foo</barf></site>' .
+ '<site>' .
+ '<globalid>acme.com</globalid>' .
+ '<localid type="interwiki">acme</localid>' .
+ '<silly>boop!</silly>' .
+ '<group>Test</group>' .
+ '<path type="link">http://acme.com/</path>' .
+ '</site>' .
+ '</sites>',
+ [ $foo, $acme ],
+ 1
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideImportFromXML
+ */
+ public function testImportFromXML( $xml, array $expectedSites, $errorCount = 0 ) {
+ $importer = $this->newSiteImporter( $expectedSites, $errorCount );
+ $importer->importFromXML( $xml );
+ }
+
+ public function testImportFromXML_malformed() {
+ $this->setExpectedException( Exception::class );
+
+ $store = $this->getMockBuilder( SiteStore::class )->getMock();
+ $importer = new SiteImporter( $store );
+ $importer->importFromXML( 'THIS IS NOT XML' );
+ }
+
+ public function testImportFromFile() {
+ $foo = Site::newForType( Site::TYPE_UNKNOWN );
+ $foo->setGlobalId( 'Foo' );
+
+ $acme = Site::newForType( Site::TYPE_UNKNOWN );
+ $acme->setGlobalId( 'acme.com' );
+ $acme->setGroup( 'Test' );
+ $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
+ $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
+
+ $dewiki = Site::newForType( Site::TYPE_MEDIAWIKI );
+ $dewiki->setGlobalId( 'dewiki' );
+ $dewiki->setGroup( 'wikipedia' );
+ $dewiki->setForward( true );
+ $dewiki->addLocalId( Site::ID_INTERWIKI, 'wikipedia' );
+ $dewiki->addLocalId( Site::ID_EQUIVALENT, 'de' );
+ $dewiki->setPath( Site::PATH_LINK, 'http://de.wikipedia.org/w/' );
+ $dewiki->setPath( MediaWikiSite::PATH_PAGE, 'http://de.wikipedia.org/wiki/' );
+ $dewiki->setSource( 'meta.wikimedia.org' );
+
+ $importer = $this->newSiteImporter( [ $foo, $acme, $dewiki ], 0 );
+
+ $file = __DIR__ . '/SiteImporterTest.xml';
+ $importer->importFromFile( $file );
+ }
+
+ /**
+ * @param Site[] $sites
+ *
+ * @return array[]
+ */
+ private function getSerializedSiteList( $sites ) {
+ $serialized = [];
+
+ foreach ( $sites as $site ) {
+ $key = $site->getGlobalId();
+ $data = unserialize( $site->serialize() );
+
+ $serialized[$key] = $data;
+ }
+
+ return $serialized;
+ }
+}
--- /dev/null
+<sites version="1.0" xmlns="http://www.mediawiki.org/xml/sitelist-1.0/">
+ <site><globalid>Foo</globalid></site>
+ <site>
+ <globalid>acme.com</globalid>
+ <localid type="interwiki">acme</localid>
+ <group>Test</group>
+ <path type="link">http://acme.com/</path>
+ </site>
+ <site type="mediawiki">
+ <source>meta.wikimedia.org</source>
+ <globalid>dewiki</globalid>
+ <localid type="interwiki">wikipedia</localid>
+ <localid type="equivalent">de</localid>
+ <group>wikipedia</group>
+ <forward/>
+ <path type="link">http://de.wikipedia.org/w/</path>
+ <path type="page_path">http://de.wikipedia.org/wiki/</path>
+ </site>
+</sites>
<?php
+use MediaWiki\MediaWikiServices;
use Wikimedia\TestingAccessWrapper;
/**
/** @var ContribsPager */
private $pager;
+ /** @var LinkRenderer */
+ private $linkRenderer;
+
function setUp() {
+ $this->linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
$context = new RequestContext();
$this->pager = new ContribsPager( $context, [
'start' => '2017-01-01',
'end' => '2017-02-02',
- ] );
+ ], $this->linkRenderer );
parent::setUp();
}
$pager = new ContribsPager( new RequestContext(), [
'start' => '',
'end' => '',
- ] );
+ ], $this->linkRenderer );
/** @var ContribsPager $pager */
$pager = TestingAccessWrapper::newFromObject( $pager );
'target' => '116.17.184.5/32',
'start' => '',
'end' => '',
- ] );
+ ], $this->linkRenderer );
/** @var ContribsPager $pager */
$pager = TestingAccessWrapper::newFromObject( $pager );
<?php
+
+use MediaWiki\MediaWikiServices;
+
/**
* Test class for ImageListPagerTest class.
*
* @covers ImageListPager::formatValue
*/
public function testFormatValuesThrowException() {
- $page = new ImageListPager( RequestContext::getMain() );
+ $page = new ImageListPager( RequestContext::getMain(), null, '', false, false,
+ MediaWikiServices::getInstance()->getLinkRenderer() );
$page->formatValue( 'invalid_field', 'invalid_value' );
}
}
class SpecialWatchlistTest extends SpecialPageTestBase {
public function setUp() {
parent::setUp();
-
+ $this->tablesUsed = [ 'watchlist' ];
$this->setTemporaryHook(
'ChangesListSpecialPageQuery',
null
*/
class BlockListPagerTest extends MediaWikiTestCase {
+ /**
+ * @var LinkRenderer
+ */
+ private $linkRenderer;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+ }
+
/**
* @covers ::formatValue
* @dataProvider formatValueEmptyProvider
$expected = $expected ?? MWTimestamp::getInstance()->format( 'H:i, j F Y' );
$row = $row ?: new stdClass;
- $pager = new BlockListPager( new SpecialPage(), [] );
+ $pager = new BlockListPager( new SpecialPage(), [], $this->linkRenderer );
$wrappedPager = TestingAccessWrapper::newFromObject( $pager );
$wrappedPager->mCurrentRow = $row;
'wgScript' => '/w/index.php',
] );
- $pager = new BlockListPager( new SpecialPage(), [] );
+ $pager = new BlockListPager( new SpecialPage(), [], $this->linkRenderer );
$row = (object)[
'ipb_id' => 0,
'ipb_sitewide' => 1,
'ipb_timestamp' => $this->db->timestamp( wfTimestamp( TS_MW ) ),
];
- $pager = new BlockListPager( new SpecialPage(), [] );
+ $pager = new BlockListPager( new SpecialPage(), [], $this->linkRenderer );
$pager->preprocessResults( [ $row ] );
foreach ( $links as $link ) {
'by_user_name' => 'Admin',
'ipb_sitewide' => 1,
];
- $pager = new BlockListPager( new SpecialPage(), [] );
+ $pager = new BlockListPager( new SpecialPage(), [], $this->linkRenderer );
$pager->preprocessResults( [ $row ] );
$this->assertObjectNotHasAttribute( 'ipb_restrictions', $row );
$result = $this->db->select( 'ipblocks', [ '*' ], [ 'ipb_id' => $block->getId() ] );
- $pager = new BlockListPager( new SpecialPage(), [] );
+ $pager = new BlockListPager( new SpecialPage(), [], $this->linkRenderer );
$pager->preprocessResults( $result );
$wrappedPager = TestingAccessWrapper::newFromObject( $pager );
--- /dev/null
+<?php
+
+/**
+ * @covers ZipDirectoryReader
+ */
+class ZipDirectoryReaderTest extends MediaWikiIntegrationTestCase {
+
+ protected $zipDir;
+ protected $entries;
+
+ protected function setUp() {
+ parent::setUp();
+ $this->zipDir = __DIR__ . '/../../data/zip';
+ }
+
+ function zipCallback( $entry ) {
+ $this->entries[] = $entry;
+ }
+
+ function readZipAssertError( $file, $error, $assertMessage ) {
+ $this->entries = [];
+ $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
+ $this->assertTrue( $status->hasMessage( $error ), $assertMessage );
+ }
+
+ function readZipAssertSuccess( $file, $assertMessage ) {
+ $this->entries = [];
+ $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
+ $this->assertTrue( $status->isOK(), $assertMessage );
+ }
+
+ public function testEmpty() {
+ $this->readZipAssertSuccess( 'empty.zip', 'Empty zip' );
+ }
+
+ public function testMultiDisk0() {
+ $this->readZipAssertError( 'split.zip', 'zip-unsupported',
+ 'Split zip error' );
+ }
+
+ public function testNoSignature() {
+ $this->readZipAssertError( 'nosig.zip', 'zip-wrong-format',
+ 'No signature should give "wrong format" error' );
+ }
+
+ public function testSimple() {
+ $this->readZipAssertSuccess( 'class.zip', 'Simple ZIP' );
+ $this->assertEquals( $this->entries, [ [
+ 'name' => 'Class.class',
+ 'mtime' => '20010115000000',
+ 'size' => 1,
+ ] ] );
+ }
+
+ public function testBadCentralEntrySignature() {
+ $this->readZipAssertError( 'wrong-central-entry-sig.zip', 'zip-bad',
+ 'Bad central entry error' );
+ }
+
+ public function testTrailingBytes() {
+ // Due to T40432 this is now zip-wrong-format instead of zip-bad
+ $this->readZipAssertError( 'trail.zip', 'zip-wrong-format',
+ 'Trailing bytes error' );
+ }
+
+ public function testWrongCDStart() {
+ $this->readZipAssertError( 'wrong-cd-start-disk.zip', 'zip-unsupported',
+ 'Wrong CD start disk error' );
+ }
+
+ public function testCentralDirectoryGap() {
+ $this->readZipAssertError( 'cd-gap.zip', 'zip-bad',
+ 'CD gap error' );
+ }
+
+ public function testCentralDirectoryTruncated() {
+ $this->readZipAssertError( 'cd-truncated.zip', 'zip-bad',
+ 'CD truncated error (should hit unpack() overrun)' );
+ }
+
+ public function testLooksLikeZip64() {
+ $this->readZipAssertError( 'looks-like-zip64.zip', 'zip-unsupported',
+ 'A file which looks like ZIP64 but isn\'t, should give error' );
+ }
+}
);
}
+ public function setup() {
+ parent::setup();
+
+ require_once __DIR__ . '/../common/TestSetup.php';
+ TestSetup::snapshotGlobals();
+ }
+
public function finalSetup() {
parent::finalSetup();
+++ /dev/null
-<?php
-
-namespace MediaWiki\Tests\Revision;
-
-use MediaWiki\Revision\MainSlotRoleHandler;
-use MediaWikiUnitTestCase;
-use PHPUnit\Framework\MockObject\MockObject;
-use Title;
-
-/**
- * @covers \MediaWiki\Revision\MainSlotRoleHandler
- */
-class MainSlotRoleHandlerTest extends MediaWikiUnitTestCase {
-
- private function makeTitleObject( $ns ) {
- /** @var Title|MockObject $title */
- $title = $this->getMockBuilder( Title::class )
- ->disableOriginalConstructor()
- ->getMock();
-
- $title->method( 'getNamespace' )
- ->willReturn( $ns );
-
- return $title;
- }
-
- /**
- * @covers \MediaWiki\Revision\MainSlotRoleHandler::__construct
- * @covers \MediaWiki\Revision\MainSlotRoleHandler::getRole()
- * @covers \MediaWiki\Revision\MainSlotRoleHandler::getNameMessageKey()
- * @covers \MediaWiki\Revision\MainSlotRoleHandler::getOutputLayoutHints()
- */
- public function testConstruction() {
- $handler = new MainSlotRoleHandler( [] );
- $this->assertSame( 'main', $handler->getRole() );
- $this->assertSame( 'slot-name-main', $handler->getNameMessageKey() );
-
- $hints = $handler->getOutputLayoutHints();
- $this->assertArrayHasKey( 'display', $hints );
- $this->assertArrayHasKey( 'region', $hints );
- $this->assertArrayHasKey( 'placement', $hints );
- }
-
- /**
- * @covers \MediaWiki\Revision\MainSlotRoleHandler::getDefaultModel()
- */
- public function testFetDefaultModel() {
- $handler = new MainSlotRoleHandler( [ 100 => CONTENT_MODEL_TEXT ] );
-
- // For the main handler, the namespace determins the default model
- $titleMain = $this->makeTitleObject( NS_MAIN );
- $this->assertSame( CONTENT_MODEL_WIKITEXT, $handler->getDefaultModel( $titleMain ) );
-
- $title100 = $this->makeTitleObject( 100 );
- $this->assertSame( CONTENT_MODEL_TEXT, $handler->getDefaultModel( $title100 ) );
- }
-
- /**
- * @covers \MediaWiki\Revision\MainSlotRoleHandler::isAllowedModel()
- */
- public function testIsAllowedModel() {
- $handler = new MainSlotRoleHandler( [] );
-
- // For the main handler, (nearly) all models are allowed
- $title = $this->makeTitleObject( NS_MAIN );
- $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_WIKITEXT, $title ) );
- $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_TEXT, $title ) );
- }
-
- /**
- * @covers \MediaWiki\Revision\MainSlotRoleHandler::supportsArticleCount()
- */
- public function testSupportsArticleCount() {
- $handler = new MainSlotRoleHandler( [] );
-
- $this->assertTrue( $handler->supportsArticleCount() );
- }
-
-}
+++ /dev/null
-<?php
-
-namespace MediaWiki\Tests\Revision;
-
-use InvalidArgumentException;
-use LogicException;
-use MediaWiki\Revision\IncompleteRevisionException;
-use MediaWiki\Revision\SlotRecord;
-use MediaWiki\Revision\SuppressedDataException;
-use MediaWikiUnitTestCase;
-use WikitextContent;
-
-/**
- * @covers \MediaWiki\Revision\SlotRecord
- */
-class SlotRecordTest extends MediaWikiUnitTestCase {
-
- private function makeRow( $data = [] ) {
- $data = $data + [
- 'slot_id' => 1234,
- 'slot_content_id' => 33,
- 'content_size' => '5',
- 'content_sha1' => 'someHash',
- 'content_address' => 'tt:456',
- 'model_name' => CONTENT_MODEL_WIKITEXT,
- 'format_name' => CONTENT_FORMAT_WIKITEXT,
- 'slot_revision_id' => '2',
- 'slot_origin' => '1',
- 'role_name' => 'myRole',
- ];
- return (object)$data;
- }
-
- public function testCompleteConstruction() {
- $row = $this->makeRow();
- $record = new SlotRecord( $row, new WikitextContent( 'A' ) );
-
- $this->assertTrue( $record->hasAddress() );
- $this->assertTrue( $record->hasContentId() );
- $this->assertTrue( $record->hasRevision() );
- $this->assertTrue( $record->isInherited() );
- $this->assertSame( 'A', $record->getContent()->getText() );
- $this->assertSame( 5, $record->getSize() );
- $this->assertSame( 'someHash', $record->getSha1() );
- $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
- $this->assertSame( 2, $record->getRevision() );
- $this->assertSame( 1, $record->getOrigin() );
- $this->assertSame( 'tt:456', $record->getAddress() );
- $this->assertSame( 33, $record->getContentId() );
- $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
- $this->assertSame( 'myRole', $record->getRole() );
- }
-
- public function testConstructionDeferred() {
- $row = $this->makeRow( [
- 'content_size' => null, // to be computed
- 'content_sha1' => null, // to be computed
- 'format_name' => function () {
- return CONTENT_FORMAT_WIKITEXT;
- },
- 'slot_revision_id' => '2',
- 'slot_origin' => '2',
- 'slot_content_id' => function () {
- return null;
- },
- ] );
-
- $content = function () {
- return new WikitextContent( 'A' );
- };
-
- $record = new SlotRecord( $row, $content );
-
- $this->assertTrue( $record->hasAddress() );
- $this->assertTrue( $record->hasRevision() );
- $this->assertFalse( $record->hasContentId() );
- $this->assertFalse( $record->isInherited() );
- $this->assertSame( 'A', $record->getContent()->getText() );
- $this->assertSame( 1, $record->getSize() );
- $this->assertNotEmpty( $record->getSha1() );
- $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
- $this->assertSame( 2, $record->getRevision() );
- $this->assertSame( 2, $record->getRevision() );
- $this->assertSame( 'tt:456', $record->getAddress() );
- $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
- $this->assertSame( 'myRole', $record->getRole() );
- }
-
- public function testNewUnsaved() {
- $record = SlotRecord::newUnsaved( 'myRole', new WikitextContent( 'A' ) );
-
- $this->assertFalse( $record->hasAddress() );
- $this->assertFalse( $record->hasContentId() );
- $this->assertFalse( $record->hasRevision() );
- $this->assertFalse( $record->isInherited() );
- $this->assertFalse( $record->hasOrigin() );
- $this->assertSame( 'A', $record->getContent()->getText() );
- $this->assertSame( 1, $record->getSize() );
- $this->assertNotEmpty( $record->getSha1() );
- $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
- $this->assertSame( 'myRole', $record->getRole() );
- }
-
- public function provideInvalidConstruction() {
- yield 'both null' => [ null, null ];
- yield 'null row' => [ null, new WikitextContent( 'A' ) ];
- yield 'array row' => [ [], new WikitextContent( 'A' ) ];
- yield 'empty row' => [ (object)[], new WikitextContent( 'A' ) ];
- yield 'null content' => [ (object)[], null ];
- }
-
- /**
- * @dataProvider provideInvalidConstruction
- */
- public function testInvalidConstruction( $row, $content ) {
- $this->setExpectedException( InvalidArgumentException::class );
- new SlotRecord( $row, $content );
- }
-
- public function testGetContentId_fails() {
- $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
- $this->setExpectedException( IncompleteRevisionException::class );
-
- $record->getContentId();
- }
-
- public function testGetAddress_fails() {
- $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
- $this->setExpectedException( IncompleteRevisionException::class );
-
- $record->getAddress();
- }
-
- public function provideIncomplete() {
- $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
- yield 'unsaved' => [ $unsaved ];
-
- $parent = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
- $inherited = SlotRecord::newInherited( $parent );
- yield 'inherited' => [ $inherited ];
- }
-
- /**
- * @dataProvider provideIncomplete
- */
- public function testGetRevision_fails( SlotRecord $record ) {
- $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
- $this->setExpectedException( IncompleteRevisionException::class );
-
- $record->getRevision();
- }
-
- /**
- * @dataProvider provideIncomplete
- */
- public function testGetOrigin_fails( SlotRecord $record ) {
- $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
- $this->setExpectedException( IncompleteRevisionException::class );
-
- $record->getOrigin();
- }
-
- public function provideHashStability() {
- yield [ '', 'phoiac9h4m842xq45sp7s6u21eteeq1' ];
- yield [ 'Lorem ipsum', 'hcr5u40uxr81d3nx89nvwzclfz6r9c5' ];
- }
-
- /**
- * @dataProvider provideHashStability
- */
- public function testHashStability( $text, $hash ) {
- // Changing the output of the hash function will break things horribly!
-
- $this->assertSame( $hash, SlotRecord::base36Sha1( $text ) );
-
- $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( $text ) );
- $this->assertSame( $hash, $record->getSha1() );
- }
-
- public function testHashComputed() {
- $row = $this->makeRow();
- $row->content_sha1 = '';
-
- $rec = new SlotRecord( $row, new WikitextContent( 'A' ) );
- $this->assertNotEmpty( $rec->getSha1() );
- }
-
- public function testNewWithSuppressedContent() {
- $input = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
- $output = SlotRecord::newWithSuppressedContent( $input );
-
- $this->setExpectedException( SuppressedDataException::class );
- $output->getContent();
- }
-
- public function testNewInherited() {
- $row = $this->makeRow( [ 'slot_revision_id' => 7, 'slot_origin' => 7 ] );
- $parent = new SlotRecord( $row, new WikitextContent( 'A' ) );
-
- // This would happen while doing an edit, before saving revision meta-data.
- $inherited = SlotRecord::newInherited( $parent );
-
- $this->assertSame( $parent->getContentId(), $inherited->getContentId() );
- $this->assertSame( $parent->getAddress(), $inherited->getAddress() );
- $this->assertSame( $parent->getContent(), $inherited->getContent() );
- $this->assertTrue( $inherited->isInherited() );
- $this->assertTrue( $inherited->hasOrigin() );
- $this->assertFalse( $inherited->hasRevision() );
-
- // make sure we didn't mess with the internal state of $parent
- $this->assertFalse( $parent->isInherited() );
- $this->assertSame( 7, $parent->getRevision() );
-
- // This would happen while doing an edit, after saving the revision meta-data
- // and content meta-data.
- $saved = SlotRecord::newSaved(
- 10,
- $inherited->getContentId(),
- $inherited->getAddress(),
- $inherited
- );
- $this->assertSame( $parent->getContentId(), $saved->getContentId() );
- $this->assertSame( $parent->getAddress(), $saved->getAddress() );
- $this->assertSame( $parent->getContent(), $saved->getContent() );
- $this->assertTrue( $saved->isInherited() );
- $this->assertTrue( $saved->hasRevision() );
- $this->assertSame( 10, $saved->getRevision() );
-
- // make sure we didn't mess with the internal state of $parent or $inherited
- $this->assertSame( 7, $parent->getRevision() );
- $this->assertFalse( $inherited->hasRevision() );
- }
-
- public function testNewSaved() {
- // This would happen while doing an edit, before saving revision meta-data.
- $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-
- // This would happen while doing an edit, after saving the revision meta-data
- // and content meta-data.
- $saved = SlotRecord::newSaved( 10, 20, 'theNewAddress', $unsaved );
- $this->assertFalse( $saved->isInherited() );
- $this->assertTrue( $saved->hasOrigin() );
- $this->assertTrue( $saved->hasRevision() );
- $this->assertTrue( $saved->hasAddress() );
- $this->assertTrue( $saved->hasContentId() );
- $this->assertSame( 'theNewAddress', $saved->getAddress() );
- $this->assertSame( 20, $saved->getContentId() );
- $this->assertSame( 'A', $saved->getContent()->getText() );
- $this->assertSame( 10, $saved->getRevision() );
- $this->assertSame( 10, $saved->getOrigin() );
-
- // make sure we didn't mess with the internal state of $unsaved
- $this->assertFalse( $unsaved->hasAddress() );
- $this->assertFalse( $unsaved->hasContentId() );
- $this->assertFalse( $unsaved->hasRevision() );
- }
-
- public function provideNewSaved_LogicException() {
- $freshRow = $this->makeRow( [
- 'content_id' => 10,
- 'content_address' => 'address:1',
- 'slot_origin' => 1,
- 'slot_revision_id' => 1,
- ] );
-
- $freshSlot = new SlotRecord( $freshRow, new WikitextContent( 'A' ) );
- yield 'mismatching address' => [ 1, 10, 'address:BAD', $freshSlot ];
- yield 'mismatching revision' => [ 5, 10, 'address:1', $freshSlot ];
- yield 'mismatching content ID' => [ 1, 17, 'address:1', $freshSlot ];
-
- $inheritedRow = $this->makeRow( [
- 'content_id' => null,
- 'content_address' => null,
- 'slot_origin' => 0,
- 'slot_revision_id' => 1,
- ] );
-
- $inheritedSlot = new SlotRecord( $inheritedRow, new WikitextContent( 'A' ) );
- yield 'inherited, but no address' => [ 1, 10, 'address:2', $inheritedSlot ];
- }
-
- /**
- * @dataProvider provideNewSaved_LogicException
- */
- public function testNewSaved_LogicException(
- $revisionId,
- $contentId,
- $contentAddress,
- SlotRecord $protoSlot
- ) {
- $this->setExpectedException( LogicException::class );
- SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
- }
-
- public function provideNewSaved_InvalidArgumentException() {
- $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-
- yield 'bad revision id' => [ 'xyzzy', 5, 'address', $unsaved ];
- yield 'bad content id' => [ 7, 'xyzzy', 'address', $unsaved ];
- yield 'bad content address' => [ 7, 5, 77, $unsaved ];
- }
-
- /**
- * @dataProvider provideNewSaved_InvalidArgumentException
- */
- public function testNewSaved_InvalidArgumentException(
- $revisionId,
- $contentId,
- $contentAddress,
- SlotRecord $protoSlot
- ) {
- $this->setExpectedException( InvalidArgumentException::class );
- SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
- }
-
- public function provideHasSameContent() {
- $fail = function () {
- self::fail( 'There should be no need to actually load the content.' );
- };
-
- $a100a1 = new SlotRecord(
- $this->makeRow(
- [
- 'model_name' => 'A',
- 'content_size' => 100,
- 'content_sha1' => 'hash-a',
- 'content_address' => 'xxx:a1',
- ]
- ),
- $fail
- );
- $a100a1b = new SlotRecord(
- $this->makeRow(
- [
- 'model_name' => 'A',
- 'content_size' => 100,
- 'content_sha1' => 'hash-a',
- 'content_address' => 'xxx:a1',
- ]
- ),
- $fail
- );
- $a100null = new SlotRecord(
- $this->makeRow(
- [
- 'model_name' => 'A',
- 'content_size' => 100,
- 'content_sha1' => 'hash-a',
- 'content_address' => null,
- ]
- ),
- $fail
- );
- $a100a2 = new SlotRecord(
- $this->makeRow(
- [
- 'model_name' => 'A',
- 'content_size' => 100,
- 'content_sha1' => 'hash-a',
- 'content_address' => 'xxx:a2',
- ]
- ),
- $fail
- );
- $b100a1 = new SlotRecord(
- $this->makeRow(
- [
- 'model_name' => 'B',
- 'content_size' => 100,
- 'content_sha1' => 'hash-a',
- 'content_address' => 'xxx:a1',
- ]
- ),
- $fail
- );
- $a200a1 = new SlotRecord(
- $this->makeRow(
- [
- 'model_name' => 'A',
- 'content_size' => 200,
- 'content_sha1' => 'hash-a',
- 'content_address' => 'xxx:a2',
- ]
- ),
- $fail
- );
- $a100x1 = new SlotRecord(
- $this->makeRow(
- [
- 'model_name' => 'A',
- 'content_size' => 100,
- 'content_sha1' => 'hash-x',
- 'content_address' => 'xxx:x1',
- ]
- ),
- $fail
- );
-
- yield 'same instance' => [ $a100a1, $a100a1, true ];
- yield 'no address' => [ $a100a1, $a100null, true ];
- yield 'same address' => [ $a100a1, $a100a1b, true ];
- yield 'different address' => [ $a100a1, $a100a2, true ];
- yield 'different model' => [ $a100a1, $b100a1, false ];
- yield 'different size' => [ $a100a1, $a200a1, false ];
- yield 'different hash' => [ $a100a1, $a100x1, false ];
- }
-
- /**
- * @dataProvider provideHasSameContent
- */
- public function testHasSameContent( SlotRecord $a, SlotRecord $b, $sameContent ) {
- $this->assertSame( $sameContent, $a->hasSameContent( $b ) );
- $this->assertSame( $sameContent, $b->hasSameContent( $a ) );
- }
-
-}
+++ /dev/null
-<?php
-
-/**
- * @covers WikiReference
- */
-class WikiReferenceTest extends MediaWikiUnitTestCase {
-
- public function provideGetDisplayName() {
- return [
- 'http' => [ 'foo.bar', 'http://foo.bar' ],
- 'https' => [ 'foo.bar', 'http://foo.bar' ],
-
- // apparently, this is the expected behavior
- 'invalid' => [ 'purple kittens', 'purple kittens' ],
- ];
- }
-
- /**
- * @dataProvider provideGetDisplayName
- */
- public function testGetDisplayName( $expected, $canonicalServer ) {
- $reference = new WikiReference( $canonicalServer, '/wiki/$1' );
- $this->assertEquals( $expected, $reference->getDisplayName() );
- }
-
- public function testGetCanonicalServer() {
- $reference = new WikiReference( 'https://acme.com', '/wiki/$1', '//acme.com' );
- $this->assertEquals( 'https://acme.com', $reference->getCanonicalServer() );
- }
-
- public function provideGetCanonicalUrl() {
- return [
- 'no fragment' => [
- 'https://acme.com/wiki/Foo',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- null
- ],
- 'empty fragment' => [
- 'https://acme.com/wiki/Foo',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- ''
- ],
- 'fragment' => [
- 'https://acme.com/wiki/Foo#Bar',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- 'Bar'
- ],
- 'double fragment' => [
- 'https://acme.com/wiki/Foo#Bar%23Xus',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- 'Bar#Xus'
- ],
- 'escaped fragment' => [
- 'https://acme.com/wiki/Foo%23Bar',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo#Bar',
- null
- ],
- 'empty path' => [
- 'https://acme.com/Foo',
- 'https://acme.com',
- '//acme.com',
- '/$1',
- 'Foo',
- null
- ],
- ];
- }
-
- /**
- * @dataProvider provideGetCanonicalUrl
- */
- public function testGetCanonicalUrl(
- $expected, $canonicalServer, $server, $path, $page, $fragmentId
- ) {
- $reference = new WikiReference( $canonicalServer, $path, $server );
- $this->assertEquals( $expected, $reference->getCanonicalUrl( $page, $fragmentId ) );
- }
-
- /**
- * @dataProvider provideGetCanonicalUrl
- * @note getUrl is an alias for getCanonicalUrl
- */
- public function testGetUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
- $reference = new WikiReference( $canonicalServer, $path, $server );
- $this->assertEquals( $expected, $reference->getUrl( $page, $fragmentId ) );
- }
-
- public function provideGetFullUrl() {
- return [
- 'no fragment' => [
- '//acme.com/wiki/Foo',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- null
- ],
- 'empty fragment' => [
- '//acme.com/wiki/Foo',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- ''
- ],
- 'fragment' => [
- '//acme.com/wiki/Foo#Bar',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- 'Bar'
- ],
- 'double fragment' => [
- '//acme.com/wiki/Foo#Bar%23Xus',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- 'Bar#Xus'
- ],
- 'escaped fragment' => [
- '//acme.com/wiki/Foo%23Bar',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo#Bar',
- null
- ],
- 'empty path' => [
- '//acme.com/Foo',
- 'https://acme.com',
- '//acme.com',
- '/$1',
- 'Foo',
- null
- ],
- ];
- }
-
- /**
- * @dataProvider provideGetFullUrl
- */
- public function testGetFullUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
- $reference = new WikiReference( $canonicalServer, $path, $server );
- $this->assertEquals( $expected, $reference->getFullUrl( $page, $fragmentId ) );
- }
-
-}
+++ /dev/null
-<?php
-
-/**
- * @covers DifferenceEngineSlotDiffRenderer
- */
-class DifferenceEngineSlotDiffRendererTest extends \MediaWikiUnitTestCase {
-
- public function testGetDiff() {
- $differenceEngine = new CustomDifferenceEngine();
- $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
- $oldContent = ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT );
- $newContent = ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT );
-
- $diff = $slotDiffRenderer->getDiff( $oldContent, $newContent );
- $this->assertEquals( 'xxx|yyy', $diff );
-
- $diff = $slotDiffRenderer->getDiff( null, $newContent );
- $this->assertEquals( '|yyy', $diff );
-
- $diff = $slotDiffRenderer->getDiff( $oldContent, null );
- $this->assertEquals( 'xxx|', $diff );
- }
-
- public function testAddModules() {
- $output = $this->getMockBuilder( OutputPage::class )
- ->disableOriginalConstructor()
- ->setMethods( [ 'addModules' ] )
- ->getMock();
- $output->expects( $this->once() )
- ->method( 'addModules' )
- ->with( 'foo' );
- $differenceEngine = new CustomDifferenceEngine();
- $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
- $slotDiffRenderer->addModules( $output );
- }
-
- public function testGetExtraCacheKeys() {
- $differenceEngine = new CustomDifferenceEngine();
- $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
- $extraCacheKeys = $slotDiffRenderer->getExtraCacheKeys();
- $this->assertSame( [ 'foo' ], $extraCacheKeys );
- }
-
-}
+++ /dev/null
-<?php
-
-use Wikimedia\Assert\ParameterTypeException;
-use Wikimedia\TestingAccessWrapper;
-
-/**
- * @covers SlotDiffRenderer
- */
-class SlotDiffRendererTest extends \MediaWikiUnitTestCase {
-
- /**
- * @dataProvider provideNormalizeContents
- */
- public function testNormalizeContents(
- $oldContent, $newContent, $allowedClasses,
- $expectedOldContent, $expectedNewContent, $expectedExceptionClass
- ) {
- $slotDiffRenderer = $this->getMockBuilder( SlotDiffRenderer::class )
- ->getMock();
- try {
- // __call needs help deciding which parameter to take by reference
- call_user_func_array( [ TestingAccessWrapper::newFromObject( $slotDiffRenderer ),
- 'normalizeContents' ], [ &$oldContent, &$newContent, $allowedClasses ] );
- $this->assertEquals( $expectedOldContent, $oldContent );
- $this->assertEquals( $expectedNewContent, $newContent );
- } catch ( Exception $e ) {
- if ( !$expectedExceptionClass ) {
- throw $e;
- }
- $this->assertInstanceOf( $expectedExceptionClass, $e );
- }
- }
-
- public function provideNormalizeContents() {
- return [
- 'both null' => [ null, null, null, null, null, InvalidArgumentException::class ],
- 'left null' => [
- null, new WikitextContent( 'abc' ), null,
- new WikitextContent( '' ), new WikitextContent( 'abc' ), null,
- ],
- 'right null' => [
- new WikitextContent( 'def' ), null, null,
- new WikitextContent( 'def' ), new WikitextContent( '' ), null,
- ],
- 'type filter' => [
- new WikitextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
- new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
- ],
- 'type filter (subclass)' => [
- new WikitextContent( 'abc' ), new WikitextContent( 'def' ), TextContent::class,
- new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
- ],
- 'type filter (null)' => [
- new WikitextContent( 'abc' ), null, TextContent::class,
- new WikitextContent( 'abc' ), new WikitextContent( '' ), null,
- ],
- 'type filter failure (left)' => [
- new TextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
- null, null, ParameterTypeException::class,
- ],
- 'type filter failure (right)' => [
- new WikitextContent( 'abc' ), new TextContent( 'def' ), WikitextContent::class,
- null, null, ParameterTypeException::class,
- ],
- 'type filter (array syntax)' => [
- new WikitextContent( 'abc' ), new JsonContent( 'def' ),
- [ JsonContent::class, WikitextContent::class ],
- new WikitextContent( 'abc' ), new JsonContent( 'def' ), null,
- ],
- 'type filter failure (array syntax)' => [
- new WikitextContent( 'abc' ), new CssContent( 'def' ),
- [ JsonContent::class, WikitextContent::class ],
- null, null, ParameterTypeException::class,
- ],
- ];
- }
-
-}
+++ /dev/null
-<?php
-
-class FileBackendDBRepoWrapperTest extends MediaWikiUnitTestCase {
- protected $backendName = 'foo-backend';
- protected $repoName = 'pureTestRepo';
-
- /**
- * @dataProvider getBackendPathsProvider
- * @covers FileBackendDBRepoWrapper::getBackendPaths
- */
- public function testGetBackendPaths(
- $mocks,
- $latest,
- $dbReadsExpected,
- $dbReturnValue,
- $originalPath,
- $expectedBackendPath,
- $message ) {
- list( $dbMock, $backendMock, $wrapperMock ) = $mocks;
-
- $dbMock->expects( $dbReadsExpected )
- ->method( 'selectField' )
- ->will( $this->returnValue( $dbReturnValue ) );
-
- $newPaths = $wrapperMock->getBackendPaths( [ $originalPath ], $latest );
-
- $this->assertEquals(
- $expectedBackendPath,
- $newPaths[0],
- $message );
- }
-
- public function getBackendPathsProvider() {
- $prefix = 'mwstore://' . $this->backendName . '/' . $this->repoName;
- $mocksForCaching = $this->getMocks();
-
- return [
- [
- $mocksForCaching,
- false,
- $this->once(),
- '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
- $prefix . '-public/f/o/foobar.jpg',
- $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
- 'Public path translated correctly',
- ],
- [
- $mocksForCaching,
- false,
- $this->never(),
- '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
- $prefix . '-public/f/o/foobar.jpg',
- $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
- 'LRU cache leveraged',
- ],
- [
- $this->getMocks(),
- true,
- $this->once(),
- '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
- $prefix . '-public/f/o/foobar.jpg',
- $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
- 'Latest obtained',
- ],
- [
- $this->getMocks(),
- true,
- $this->never(),
- '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
- $prefix . '-deleted/f/o/foobar.jpg',
- $prefix . '-original/f/o/o/foobar',
- 'Deleted path translated correctly',
- ],
- [
- $this->getMocks(),
- true,
- $this->once(),
- null,
- $prefix . '-public/b/a/baz.jpg',
- $prefix . '-public/b/a/baz.jpg',
- 'Path left untouched if no sha1 can be found',
- ],
- ];
- }
-
- /**
- * @covers FileBackendDBRepoWrapper::getFileContentsMulti
- */
- public function testGetFileContentsMulti() {
- list( $dbMock, $backendMock, $wrapperMock ) = $this->getMocks();
-
- $sha1Path = 'mwstore://' . $this->backendName . '/' . $this->repoName
- . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9';
- $filenamePath = 'mwstore://' . $this->backendName . '/' . $this->repoName
- . '-public/f/o/foobar.jpg';
-
- $dbMock->expects( $this->once() )
- ->method( 'selectField' )
- ->will( $this->returnValue( '96246614d75ba1703bdfd5d7660bb57407aaf5d9' ) );
-
- $backendMock->expects( $this->once() )
- ->method( 'getFileContentsMulti' )
- ->will( $this->returnValue( [ $sha1Path => 'foo' ] ) );
-
- $result = $wrapperMock->getFileContentsMulti( [ 'srcs' => [ $filenamePath ] ] );
-
- $this->assertEquals(
- [ $filenamePath => 'foo' ],
- $result,
- 'File contents paths translated properly'
- );
- }
-
- protected function getMocks() {
- $dbMock = $this->getMockBuilder( Wikimedia\Rdbms\IDatabase::class )
- ->disableOriginalClone()
- ->disableOriginalConstructor()
- ->getMock();
-
- $backendMock = $this->getMockBuilder( FSFileBackend::class )
- ->setConstructorArgs( [ [
- 'name' => $this->backendName,
- 'wikiId' => wfWikiID()
- ] ] )
- ->getMock();
-
- $wrapperMock = $this->getMockBuilder( FileBackendDBRepoWrapper::class )
- ->setMethods( [ 'getDB' ] )
- ->setConstructorArgs( [ [
- 'backend' => $backendMock,
- 'repoName' => $this->repoName,
- 'dbHandleFactory' => null
- ] ] )
- ->getMock();
-
- $wrapperMock->expects( $this->any() )->method( 'getDB' )->will( $this->returnValue( $dbMock ) );
-
- return [ $dbMock, $backendMock, $wrapperMock ];
- }
-}
--- /dev/null
+<?php
+
+require_once __DIR__ . '/TestFileJournal.php';
+
+use Wikimedia\Timestamp\ConvertibleTimestamp;
+
+/**
+ * @coversDefaultClass FileJournal
+ */
+class FileJournalTest extends MediaWikiUnitTestCase {
+ private function newObj( $options = [], $backend = '' ) {
+ return FileJournal::factory(
+ $options + [ 'class' => TestFileJournal::class ],
+ $backend
+ );
+ }
+
+ /**
+ * @covers ::factory
+ */
+ public function testConstructor_backend() {
+ $this->assertSame( 'some_backend', $this->newObj( [], 'some_backend' )->getBackend() );
+ }
+
+ /**
+ * @covers ::__construct
+ * @covers ::factory
+ */
+ public function testConstructor_ttlDays() {
+ $this->assertSame( 42, $this->newObj( [ 'ttlDays' => 42 ] )->getTtlDays() );
+ }
+
+ /**
+ * @covers ::__construct
+ * @covers ::factory
+ */
+ public function testConstructor_noTtlDays() {
+ $this->assertSame( false, $this->newObj()->getTtlDays() );
+ }
+
+ /**
+ * @covers ::__construct
+ * @covers ::factory
+ */
+ public function testConstructor_nullTtlDays() {
+ $this->assertSame( false, $this->newObj( [ 'ttlDays' => null ] )->getTtlDays() );
+ }
+
+ /**
+ * @covers ::factory
+ */
+ public function testFactory_invalidClass() {
+ $this->setExpectedException( UnexpectedValueException::class,
+ 'Expected instance of FileJournal, got stdClass' );
+
+ FileJournal::factory( [ 'class' => 'stdclass' ], '' );
+ }
+
+ /**
+ * @covers ::getTimestampedUUID
+ */
+ public function testGetTimestampedUUID() {
+ $obj = FileJournal::factory( [ 'class' => 'NullFileJournal' ], '' );
+ $uuids = [];
+ for ( $i = 0; $i < 10; $i++ ) {
+ $time1 = time();
+ $uuid = $obj->getTimestampedUUID();
+ $time2 = time();
+ $this->assertRegexp( '/^[0-9a-z]{31}$/', $uuid );
+ $this->assertArrayNotHasKey( $uuid, $uuids );
+ $uuids[$uuid] = true;
+
+ // Now test that the timestamp portion is as expected.
+ $time = ConvertibleTimestamp::convert( TS_UNIX, Wikimedia\base_convert(
+ substr( $uuid, 0, 9 ), 36, 10 ) );
+
+ $this->assertGreaterThanOrEqual( $time1, $time );
+ $this->assertLessThanOrEqual( $time2, $time );
+ }
+ }
+
+ /**
+ * @covers ::logChangeBatch
+ */
+ public function testLogChangeBatch() {
+ $this->assertEquals(
+ StatusValue::newGood( 'Logged' ), $this->newObj()->logChangeBatch( [ 1 ], '' ) );
+ }
+
+ /**
+ * @covers ::logChangeBatch
+ */
+ public function testLogChangeBatch_empty() {
+ $this->assertEquals( StatusValue::newGood(), $this->newObj()->logChangeBatch( [], '' ) );
+ }
+
+ /**
+ * @covers ::getCurrentPosition
+ */
+ public function testGetCurrentPosition() {
+ $this->assertEquals( 613, $this->newObj()->getCurrentPosition() );
+ }
+
+ /**
+ * @covers ::getPositionAtTime
+ */
+ public function testGetPositionAtTime() {
+ $this->assertEquals( 248, $this->newObj()->getPositionAtTime( 0 ) );
+ }
+
+ /**
+ * @dataProvider provideGetChangeEntries
+ * @covers ::getChangeEntries
+ * @param int|null $start
+ * @param int $limit
+ * @param string|null $expectedNext
+ * @param string[] $expectedReturn Expected id's of returned values
+ */
+ public function testGetChangeEntries( $start, $limit, $expectedNext, array $expectedReturn ) {
+ $expectedReturn = array_map(
+ function ( $val ) {
+ return [ 'id' => $val ];
+ }, $expectedReturn
+ );
+ $next = "Different from $expectedNext";
+ $ret = $this->newObj()->getChangeEntries( $start, $limit, $next );
+ $this->assertSame( $expectedNext, $next );
+ $this->assertSame( $expectedReturn, $ret );
+ }
+
+ public static function provideGetChangeEntries() {
+ return [
+ [ null, 0, null, [ 1, 2, 3 ] ],
+ [ null, 1, 2, [ 1 ] ],
+ [ null, 2, 3, [ 1, 2 ] ],
+ [ null, 3, null, [ 1, 2, 3 ] ],
+ [ 1, 0, null, [ 1, 2, 3 ] ],
+ [ 1, 2, 3, [ 1, 2 ] ],
+ [ 1, 1, 2, [ 1 ] ],
+ [ 2, 2, null, [ 2, 3 ] ],
+ ];
+ }
+
+ /**
+ * @covers ::purgeOldLogs
+ */
+ public function testPurgeOldLogs() {
+ $obj = $this->newObj();
+ $this->assertFalse( $obj->getPurged() );
+ $obj->purgeOldLogs();
+ $this->assertTrue( $obj->getPurged() );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @coversDefaultClass NullFileJournal
+ */
+class NullFileJournalTest extends MediaWikiUnitTestCase {
+ public function newObj() : NullFileJournal {
+ return FileJournal::factory( [ 'class' => NullFileJournal::class ], '' );
+ }
+
+ /**
+ * @covers ::doLogChangeBatch
+ */
+ public function testLogChangeBatch() {
+ $this->assertEquals( StatusValue::newGood(), $this->newObj()->logChangeBatch( [ 1 ], '' ) );
+ }
+
+ /**
+ * @covers ::doGetCurrentPosition
+ */
+ public function testGetCurrentPosition() {
+ $this->assertFalse( $this->newObj()->getCurrentPosition() );
+ }
+
+ /**
+ * @covers ::doGetPositionAtTime
+ */
+ public function testGetPositionAtTime() {
+ $this->assertFalse( $this->newObj()->getPositionAtTime( 2 ) );
+ }
+
+ /**
+ * @covers ::doGetChangeEntries
+ */
+ public function testGetChangeEntries() {
+ $next = 1;
+ $entries = $this->newObj()->getChangeEntries( null, 0, $next );
+ $this->assertSame( [], $entries );
+ $this->assertNull( $next );
+ }
+
+ /**
+ * @covers ::doPurgeOldLogs
+ */
+ public function testPurgeOldLogs() {
+ $this->assertEquals( StatusValue::newGood(), $this->newObj()->purgeOldLogs() );
+ }
+}
--- /dev/null
+<?php
+
+class TestFileJournal extends NullFileJournal {
+ /** @var bool */
+ private $purged = false;
+
+ public function getTtlDays() {
+ return $this->ttlDays;
+ }
+
+ public function getBackend() {
+ return $this->backend;
+ }
+
+ protected function doLogChangeBatch( array $entries, $batchId ) {
+ return StatusValue::newGood( 'Logged' );
+ }
+
+ protected function doGetCurrentPosition() {
+ return 613;
+ }
+
+ protected function doGetPositionAtTime( $time ) {
+ return 248;
+ }
+
+ protected function doGetChangeEntries( $start, $limit ) {
+ return array_slice( [
+ [ 'id' => 1 ],
+ [ 'id' => 2 ],
+ [ 'id' => 3 ],
+ ], $start === null ? 0 : $start - 1, $limit ? $limit : null );
+ }
+
+ protected function doPurgeOldLogs() {
+ $this->purged = true;
+ }
+
+ public function getPurged() {
+ return $this->purged;
+ }
+}
+++ /dev/null
-<?php
-/**
- * @todo Could use a test of extended XMP segments. Hard to find programs that
- * create example files, and creating my own in vim propbably wouldn't
- * serve as a very good "test". (Adobe photoshop probably creates such files
- * but it costs money). The implementation of it currently in MediaWiki is based
- * solely on reading the standard, without any real world test files.
- *
- * @group Media
- * @covers JpegMetadataExtractor
- */
-class JpegMetadataExtractorTest extends MediaWikiUnitTestCase {
-
- protected $filePath;
-
- protected function setUp() {
- parent::setUp();
-
- $this->filePath = __DIR__ . '/../../../data/media/';
- }
-
- /**
- * We also use this test to test padding bytes don't
- * screw stuff up
- *
- * @param string $file Filename
- *
- * @dataProvider provideUtf8Comment
- */
- public function testUtf8Comment( $file ) {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . $file );
- $this->assertEquals( [ 'UTF-8 JPEG Comment — ¼' ], $res['COM'] );
- }
-
- public static function provideUtf8Comment() {
- return [
- [ 'jpeg-comment-utf.jpg' ],
- [ 'jpeg-padding-even.jpg' ],
- [ 'jpeg-padding-odd.jpg' ],
- ];
- }
-
- /** The file is iso-8859-1, but it should get auto converted */
- public function testIso88591Comment() {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-iso8859-1.jpg' );
- $this->assertEquals( [ 'ISO-8859-1 JPEG Comment - ¼' ], $res['COM'] );
- }
-
- /** Comment values that are non-textual (random binary junk) should not be shown.
- * The example test file has a comment with a 0x5 byte in it which is a control character
- * and considered binary junk for our purposes.
- */
- public function testBinaryCommentStripped() {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-binary.jpg' );
- $this->assertEmpty( $res['COM'] );
- }
-
- /* Very rarely a file can have multiple comments.
- * Order of comments is based on order inside the file.
- */
- public function testMultipleComment() {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-multiple.jpg' );
- $this->assertEquals( [ 'foo', 'bar' ], $res['COM'] );
- }
-
- public function testXMPExtraction() {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
- $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
- $this->assertEquals( $expected, $res['XMP'] );
- }
-
- public function testPSIRExtraction() {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
- $expected = '50686f746f73686f7020332e30003842494d04040000000'
- . '000181c02190004746573741c02190003666f6f1c020000020004';
- $this->assertEquals( $expected, bin2hex( $res['PSIR'][0] ) );
- }
-
- public function testXMPExtractionAltAppId() {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-alt.jpg' );
- $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
- $this->assertEquals( $expected, $res['XMP'] );
- }
-
- public function testIPTCHashComparisionNoHash() {
- $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
- $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
-
- $this->assertEquals( 'iptc-no-hash', $res );
- }
-
- public function testIPTCHashComparisionBadHash() {
- $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-bad-hash.jpg' );
- $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
-
- $this->assertEquals( 'iptc-bad-hash', $res );
- }
-
- public function testIPTCHashComparisionGoodHash() {
- $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-good-hash.jpg' );
- $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
-
- $this->assertEquals( 'iptc-good-hash', $res );
- }
-
- public function testExifByteOrder() {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'exif-user-comment.jpg' );
- $expected = 'BE';
- $this->assertEquals( $expected, $res['byteOrder'] );
- }
-
- public function testInfiniteRead() {
- // test file truncated right after a segment, which previously
- // caused an infinite loop looking for the next segment byte.
- // Should get past infinite loop and throw in wfUnpack()
- $this->setExpectedException( 'MWException' );
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop1.jpg' );
- }
-
- public function testInfiniteRead2() {
- // test file truncated after a segment's marker and size, which
- // would cause a seek past end of file. Seek past end of file
- // doesn't actually fail, but prevents further reading and was
- // devolving into the previous case (testInfiniteRead).
- $this->setExpectedException( 'MWException' );
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop2.jpg' );
- }
-}
+++ /dev/null
-<?php
-
-/**
- * @covers ParserFactory
- */
-class ParserFactoryTest extends MediaWikiUnitTestCase {
- use FactoryArgTestTrait;
-
- protected static function getFactoryClass() {
- return ParserFactory::class;
- }
-
- protected static function getInstanceClass() {
- return Parser::class;
- }
-
- protected static function getFactoryMethodName() {
- return 'create';
- }
-
- protected static function getExtraClassArgCount() {
- // The parser factory itself is passed to the parser
- return 1;
- }
-
- protected function getOverriddenMockValueForParam( ReflectionParameter $param ) {
- if ( $param->getPosition() === 0 ) {
- return [ $this->createMock( MediaWiki\Config\ServiceOptions::class ) ];
- }
- return [];
- }
-}
+++ /dev/null
-<?php
-
-use MediaWiki\Site\MediaWikiPageNameNormalizer;
-
-/**
- * @covers MediaWiki\Site\MediaWikiPageNameNormalizer
- *
- * 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
- *
- * @since 1.27
- *
- * @group Site
- * @group medium
- *
- * @author Marius Hoch
- */
-class MediaWikiPageNameNormalizerTest extends MediaWikiUnitTestCase {
-
- /**
- * @dataProvider normalizePageTitleProvider
- */
- public function testNormalizePageTitle( $expected, $pageName, $getResponse ) {
- MediaWikiPageNameNormalizerTestMockHttp::$response = $getResponse;
-
- $normalizer = new MediaWikiPageNameNormalizer(
- new MediaWikiPageNameNormalizerTestMockHttp()
- );
-
- $this->assertSame(
- $expected,
- $normalizer->normalizePageName( $pageName, 'https://www.wikidata.org/w/api.php' )
- );
- }
-
- public function normalizePageTitleProvider() {
- // Response are taken from wikidata and kkwiki using the following API request
- // api.php?action=query&prop=info&redirects=1&converttitles=1&format=json&titles=…
- return [
- 'universe (Q1)' => [
- 'Q1',
- 'Q1',
- '{"batchcomplete":"","query":{"pages":{"129":{"pageid":129,"ns":0,'
- . '"title":"Q1","contentmodel":"wikibase-item","pagelanguage":"en",'
- . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
- . '"touched":"2016-06-23T05:11:21Z","lastrevid":350004448,"length":58001}}}}'
- ],
- 'Q404 redirects to Q395' => [
- 'Q395',
- 'Q404',
- '{"batchcomplete":"","query":{"redirects":[{"from":"Q404","to":"Q395"}],"pages"'
- . ':{"601":{"pageid":601,"ns":0,"title":"Q395","contentmodel":"wikibase-item",'
- . '"pagelanguage":"en","pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
- . '"touched":"2016-06-23T08:00:20Z","lastrevid":350021914,"length":60108}}}}'
- ],
- 'D converted to Д (Latin to Cyrillic) (taken from kkwiki)' => [
- 'Д',
- 'D',
- '{"batchcomplete":"","query":{"converted":[{"from":"D","to":"\u0414"}],'
- . '"pages":{"510541":{"pageid":510541,"ns":0,"title":"\u0414",'
- . '"contentmodel":"wikitext","pagelanguage":"kk","pagelanguagehtmlcode":"kk",'
- . '"pagelanguagedir":"ltr","touched":"2015-11-22T09:16:18Z",'
- . '"lastrevid":2373618,"length":3501}}}}'
- ],
- 'there is no Q0' => [
- false,
- 'Q0',
- '{"batchcomplete":"","query":{"pages":{"-1":{"ns":0,"title":"Q0",'
- . '"missing":"","contentmodel":"wikibase-item","pagelanguage":"en",'
- . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr"}}}}'
- ],
- 'invalid title' => [
- false,
- '{{',
- '{"batchcomplete":"","query":{"pages":{"-1":{"title":"{{",'
- . '"invalidreason":"The requested page title contains invalid '
- . 'characters: \"{\".","invalid":""}}}}'
- ],
- 'error on get' => [ false, 'ABC', false ]
- ];
- }
-
-}
-
-/**
- * @private
- * @see Http
- */
-class MediaWikiPageNameNormalizerTestMockHttp extends Http {
-
- /**
- * @var mixed
- */
- public static $response;
-
- public static function get( $url, array $options = [], $caller = __METHOD__ ) {
- PHPUnit_Framework_Assert::assertInternalType( 'string', $url );
- PHPUnit_Framework_Assert::assertInternalType( 'string', $caller );
-
- return self::$response;
- }
-}
+++ /dev/null
-<?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
- *
- * @ingroup Site
- * @ingroup Test
- *
- * @group Site
- *
- * @covers SiteExporter
- *
- * @author Daniel Kinzler
- */
-class SiteExporterTest extends MediaWikiUnitTestCase {
-
- public function testConstructor_InvalidArgument() {
- $this->setExpectedException( InvalidArgumentException::class );
-
- new SiteExporter( 'Foo' );
- }
-
- public function testExportSites() {
- $foo = Site::newForType( Site::TYPE_UNKNOWN );
- $foo->setGlobalId( 'Foo' );
-
- $acme = Site::newForType( Site::TYPE_UNKNOWN );
- $acme->setGlobalId( 'acme.com' );
- $acme->setGroup( 'Test' );
- $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
- $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
-
- $tmp = tmpfile();
- $exporter = new SiteExporter( $tmp );
-
- $exporter->exportSites( [ $foo, $acme ] );
-
- fseek( $tmp, 0 );
- $xml = fread( $tmp, 16 * 1024 );
-
- $this->assertContains( '<sites ', $xml );
- $this->assertContains( '<site>', $xml );
- $this->assertContains( '<globalid>Foo</globalid>', $xml );
- $this->assertContains( '</site>', $xml );
- $this->assertContains( '<globalid>acme.com</globalid>', $xml );
- $this->assertContains( '<group>Test</group>', $xml );
- $this->assertContains( '<localid type="interwiki">acme</localid>', $xml );
- $this->assertContains( '<path type="link">http://acme.com/</path>', $xml );
- $this->assertContains( '</sites>', $xml );
-
- // NOTE: HHVM (at least on wmf Jenkins) doesn't like file URLs.
- $xsdFile = __DIR__ . '/../../../../../docs/sitelist-1.0.xsd';
- $xsdData = file_get_contents( $xsdFile );
-
- $document = new DOMDocument();
- $document->loadXML( $xml, LIBXML_NONET );
- $document->schemaValidateSource( $xsdData );
- }
-
- private function newSiteStore( SiteList $sites ) {
- $store = $this->getMockBuilder( SiteStore::class )->getMock();
-
- $store->expects( $this->once() )
- ->method( 'saveSites' )
- ->will( $this->returnCallback( function ( $moreSites ) use ( $sites ) {
- foreach ( $moreSites as $site ) {
- $sites->setSite( $site );
- }
- } ) );
-
- $store->expects( $this->any() )
- ->method( 'getSites' )
- ->will( $this->returnValue( new SiteList() ) );
-
- return $store;
- }
-
- public function provideRoundTrip() {
- $foo = Site::newForType( Site::TYPE_UNKNOWN );
- $foo->setGlobalId( 'Foo' );
-
- $acme = Site::newForType( Site::TYPE_UNKNOWN );
- $acme->setGlobalId( 'acme.com' );
- $acme->setGroup( 'Test' );
- $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
- $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
-
- $dewiki = Site::newForType( Site::TYPE_MEDIAWIKI );
- $dewiki->setGlobalId( 'dewiki' );
- $dewiki->setGroup( 'wikipedia' );
- $dewiki->setForward( true );
- $dewiki->addLocalId( Site::ID_INTERWIKI, 'wikipedia' );
- $dewiki->addLocalId( Site::ID_EQUIVALENT, 'de' );
- $dewiki->setPath( Site::PATH_LINK, 'http://de.wikipedia.org/w/' );
- $dewiki->setPath( MediaWikiSite::PATH_PAGE, 'http://de.wikipedia.org/wiki/' );
- $dewiki->setSource( 'meta.wikimedia.org' );
-
- return [
- 'empty' => [
- new SiteList()
- ],
-
- 'some' => [
- new SiteList( [ $foo, $acme, $dewiki ] ),
- ],
- ];
- }
-
- /**
- * @dataProvider provideRoundTrip()
- */
- public function testRoundTrip( SiteList $sites ) {
- $tmp = tmpfile();
- $exporter = new SiteExporter( $tmp );
-
- $exporter->exportSites( $sites );
-
- fseek( $tmp, 0 );
- $xml = fread( $tmp, 16 * 1024 );
-
- $actualSites = new SiteList();
- $store = $this->newSiteStore( $actualSites );
-
- $importer = new SiteImporter( $store );
- $importer->importFromXML( $xml );
-
- $this->assertEquals( $sites, $actualSites );
- }
-
-}
+++ /dev/null
-<?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
- *
- * @ingroup Site
- * @ingroup Test
- *
- * @group Site
- *
- * @covers SiteImporter
- *
- * @author Daniel Kinzler
- */
-class SiteImporterTest extends MediaWikiUnitTestCase {
-
- private function newSiteImporter( array $expectedSites, $errorCount ) {
- $store = $this->getMockBuilder( SiteStore::class )->getMock();
-
- $store->expects( $this->once() )
- ->method( 'saveSites' )
- ->will( $this->returnCallback( function ( $sites ) use ( $expectedSites ) {
- $this->assertSitesEqual( $expectedSites, $sites );
- } ) );
-
- $store->expects( $this->any() )
- ->method( 'getSites' )
- ->will( $this->returnValue( new SiteList() ) );
-
- $errorHandler = $this->getMockBuilder( Psr\Log\LoggerInterface::class )->getMock();
- $errorHandler->expects( $this->exactly( $errorCount ) )
- ->method( 'error' );
-
- $importer = new SiteImporter( $store );
- $importer->setExceptionCallback( [ $errorHandler, 'error' ] );
-
- return $importer;
- }
-
- public function assertSitesEqual( $expected, $actual, $message = '' ) {
- $this->assertEquals(
- $this->getSerializedSiteList( $expected ),
- $this->getSerializedSiteList( $actual ),
- $message
- );
- }
-
- public function provideImportFromXML() {
- $foo = Site::newForType( Site::TYPE_UNKNOWN );
- $foo->setGlobalId( 'Foo' );
-
- $acme = Site::newForType( Site::TYPE_UNKNOWN );
- $acme->setGlobalId( 'acme.com' );
- $acme->setGroup( 'Test' );
- $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
- $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
-
- $dewiki = Site::newForType( Site::TYPE_MEDIAWIKI );
- $dewiki->setGlobalId( 'dewiki' );
- $dewiki->setGroup( 'wikipedia' );
- $dewiki->setForward( true );
- $dewiki->addLocalId( Site::ID_INTERWIKI, 'wikipedia' );
- $dewiki->addLocalId( Site::ID_EQUIVALENT, 'de' );
- $dewiki->setPath( Site::PATH_LINK, 'http://de.wikipedia.org/w/' );
- $dewiki->setPath( MediaWikiSite::PATH_PAGE, 'http://de.wikipedia.org/wiki/' );
- $dewiki->setSource( 'meta.wikimedia.org' );
-
- return [
- 'empty' => [
- '<sites></sites>',
- [],
- ],
- 'no sites' => [
- '<sites><Foo><globalid>Foo</globalid></Foo><Bar><quux>Bla</quux></Bar></sites>',
- [],
- ],
- 'minimal' => [
- '<sites>' .
- '<site><globalid>Foo</globalid></site>' .
- '</sites>',
- [ $foo ],
- ],
- 'full' => [
- '<sites>' .
- '<site><globalid>Foo</globalid></site>' .
- '<site>' .
- '<globalid>acme.com</globalid>' .
- '<localid type="interwiki">acme</localid>' .
- '<group>Test</group>' .
- '<path type="link">http://acme.com/</path>' .
- '</site>' .
- '<site type="mediawiki">' .
- '<source>meta.wikimedia.org</source>' .
- '<globalid>dewiki</globalid>' .
- '<localid type="interwiki">wikipedia</localid>' .
- '<localid type="equivalent">de</localid>' .
- '<group>wikipedia</group>' .
- '<forward/>' .
- '<path type="link">http://de.wikipedia.org/w/</path>' .
- '<path type="page_path">http://de.wikipedia.org/wiki/</path>' .
- '</site>' .
- '</sites>',
- [ $foo, $acme, $dewiki ],
- ],
- 'skip' => [
- '<sites>' .
- '<site><globalid>Foo</globalid></site>' .
- '<site><barf>Foo</barf></site>' .
- '<site>' .
- '<globalid>acme.com</globalid>' .
- '<localid type="interwiki">acme</localid>' .
- '<silly>boop!</silly>' .
- '<group>Test</group>' .
- '<path type="link">http://acme.com/</path>' .
- '</site>' .
- '</sites>',
- [ $foo, $acme ],
- 1
- ],
- ];
- }
-
- /**
- * @dataProvider provideImportFromXML
- */
- public function testImportFromXML( $xml, array $expectedSites, $errorCount = 0 ) {
- $importer = $this->newSiteImporter( $expectedSites, $errorCount );
- $importer->importFromXML( $xml );
- }
-
- public function testImportFromXML_malformed() {
- $this->setExpectedException( Exception::class );
-
- $store = $this->getMockBuilder( SiteStore::class )->getMock();
- $importer = new SiteImporter( $store );
- $importer->importFromXML( 'THIS IS NOT XML' );
- }
-
- public function testImportFromFile() {
- $foo = Site::newForType( Site::TYPE_UNKNOWN );
- $foo->setGlobalId( 'Foo' );
-
- $acme = Site::newForType( Site::TYPE_UNKNOWN );
- $acme->setGlobalId( 'acme.com' );
- $acme->setGroup( 'Test' );
- $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
- $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
-
- $dewiki = Site::newForType( Site::TYPE_MEDIAWIKI );
- $dewiki->setGlobalId( 'dewiki' );
- $dewiki->setGroup( 'wikipedia' );
- $dewiki->setForward( true );
- $dewiki->addLocalId( Site::ID_INTERWIKI, 'wikipedia' );
- $dewiki->addLocalId( Site::ID_EQUIVALENT, 'de' );
- $dewiki->setPath( Site::PATH_LINK, 'http://de.wikipedia.org/w/' );
- $dewiki->setPath( MediaWikiSite::PATH_PAGE, 'http://de.wikipedia.org/wiki/' );
- $dewiki->setSource( 'meta.wikimedia.org' );
-
- $importer = $this->newSiteImporter( [ $foo, $acme, $dewiki ], 0 );
-
- $file = __DIR__ . '/SiteImporterTest.xml';
- $importer->importFromFile( $file );
- }
-
- /**
- * @param Site[] $sites
- *
- * @return array[]
- */
- private function getSerializedSiteList( $sites ) {
- $serialized = [];
-
- foreach ( $sites as $site ) {
- $key = $site->getGlobalId();
- $data = unserialize( $site->serialize() );
-
- $serialized[$key] = $data;
- }
-
- return $serialized;
- }
-}
+++ /dev/null
-<sites version="1.0" xmlns="http://www.mediawiki.org/xml/sitelist-1.0/">
- <site><globalid>Foo</globalid></site>
- <site>
- <globalid>acme.com</globalid>
- <localid type="interwiki">acme</localid>
- <group>Test</group>
- <path type="link">http://acme.com/</path>
- </site>
- <site type="mediawiki">
- <source>meta.wikimedia.org</source>
- <globalid>dewiki</globalid>
- <localid type="interwiki">wikipedia</localid>
- <localid type="equivalent">de</localid>
- <group>wikipedia</group>
- <forward/>
- <path type="link">http://de.wikipedia.org/w/</path>
- <path type="page_path">http://de.wikipedia.org/wiki/</path>
- </site>
-</sites>
+++ /dev/null
-<?php
-
-/**
- * @covers ZipDirectoryReader
- * NOTE: this test is more like an integration test than a unit test
- */
-class ZipDirectoryReaderTest extends MediaWikiUnitTestCase {
-
- protected $zipDir;
- protected $entries;
-
- protected function setUp() {
- parent::setUp();
- $this->zipDir = __DIR__ . '/../../../data/zip';
- }
-
- function zipCallback( $entry ) {
- $this->entries[] = $entry;
- }
-
- function readZipAssertError( $file, $error, $assertMessage ) {
- $this->entries = [];
- $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
- $this->assertTrue( $status->hasMessage( $error ), $assertMessage );
- }
-
- function readZipAssertSuccess( $file, $assertMessage ) {
- $this->entries = [];
- $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
- $this->assertTrue( $status->isOK(), $assertMessage );
- }
-
- public function testEmpty() {
- $this->readZipAssertSuccess( 'empty.zip', 'Empty zip' );
- }
-
- public function testMultiDisk0() {
- $this->readZipAssertError( 'split.zip', 'zip-unsupported',
- 'Split zip error' );
- }
-
- public function testNoSignature() {
- $this->readZipAssertError( 'nosig.zip', 'zip-wrong-format',
- 'No signature should give "wrong format" error' );
- }
-
- public function testSimple() {
- $this->readZipAssertSuccess( 'class.zip', 'Simple ZIP' );
- $this->assertEquals( $this->entries, [ [
- 'name' => 'Class.class',
- 'mtime' => '20010115000000',
- 'size' => 1,
- ] ] );
- }
-
- public function testBadCentralEntrySignature() {
- $this->readZipAssertError( 'wrong-central-entry-sig.zip', 'zip-bad',
- 'Bad central entry error' );
- }
-
- public function testTrailingBytes() {
- // Due to T40432 this is now zip-wrong-format instead of zip-bad
- $this->readZipAssertError( 'trail.zip', 'zip-wrong-format',
- 'Trailing bytes error' );
- }
-
- public function testWrongCDStart() {
- $this->readZipAssertError( 'wrong-cd-start-disk.zip', 'zip-unsupported',
- 'Wrong CD start disk error' );
- }
-
- public function testCentralDirectoryGap() {
- $this->readZipAssertError( 'cd-gap.zip', 'zip-bad',
- 'CD gap error' );
- }
-
- public function testCentralDirectoryTruncated() {
- $this->readZipAssertError( 'cd-truncated.zip', 'zip-bad',
- 'CD truncated error (should hit unpack() overrun)' );
- }
-
- public function testLooksLikeZip64() {
- $this->readZipAssertError( 'looks-like-zip64.zip', 'zip-unsupported',
- 'A file which looks like ZIP64 but isn\'t, should give error' );
- }
-}