$cfg['suppress_issue_types'] = array_merge( $cfg['suppress_issue_types'], [
// approximate error count: 22
"PhanAccessMethodInternal",
- // approximate error count: 22
- "PhanCommentParamWithoutRealParam",
// approximate error count: 19
"PhanParamReqAfterOpt",
- // approximate error count: 20
- "PhanParamSignatureMismatch",
// approximate error count: 110
"PhanParamTooMany",
// approximate error count: 63
"PhanTypeArraySuspicious",
- // approximate error count: 28
- "PhanTypeArraySuspiciousNullable",
- // approximate error count: 22
- "PhanTypeComparisonFromArray",
// approximate error count: 88
"PhanTypeInvalidDimOffset",
// approximate error count: 60
"PhanTypeMismatchArgument",
- // approximate error count: 20
- "PhanTypeMismatchArgumentInternal",
- // approximate error count: 40
- "PhanTypeMismatchProperty",
// approximate error count: 36
"PhanUndeclaredConstant",
// approximate error count: 219
}
public function stop() {
}
- public function getLog() {
+ public function getLog() : ExcimerLog {
}
public function flush() {
}
}
function formatCollapsed() {
}
+ /**
+ * @return array[]
+ */
function aggregateByFunction() {
}
+ /**
+ * @return int
+ */
function getEventCount() {
}
function current() {
Starting with MediaWiki 1.2.0, it's possible to install and configure the wiki
"in-place", as long as you have the necessary prerequisites available.
-Required software:
-* Web server with PHP 7.0.0 or HHVM 3.18.5 or higher.
+Required software as of MediaWiki 1.34.0:
+
+* Web server with PHP 7.0.13 or higher, plus the following extesnsions:
+** ctype
+** dom
+** fileinfo
+** iconv
+** json
+** mbstring
+** xml
* A SQL server, the following types are supported
** MySQL 5.5.8 or higher
** PostgreSQL 9.2 or higher
== Compatibility ==
MediaWiki 1.34 requires PHP 7.0.13 or later. Although HHVM 3.18.5 or later is
supported, it is generally advised to use PHP 7.0.13 or later for long term
-support.
+support. It also requires the following PHP extensions:
+
+* ctype
+* dom
+* fileinfo
+* iconv
+* json
+* mbstring
+* xml
MySQL/MariaDB is the recommended DBMS. PostgreSQL or SQLite can also be used,
but support for them is somewhat less mature.
"composer/semver": "1.5.0",
"cssjanus/cssjanus": "1.3.0",
"ext-ctype": "*",
+ "ext-dom": "*",
"ext-fileinfo": "*",
"ext-iconv": "*",
"ext-json": "*",
'MediaWiki\\Edit\\' => __DIR__ . '/edit/',
'MediaWiki\\EditPage\\' => __DIR__ . '/editpage/',
'MediaWiki\\Linker\\' => __DIR__ . '/linker/',
+ 'MediaWiki\\Message\\' => __DIR__ . '/Message',
'MediaWiki\\Permissions\\' => __DIR__ . '/Permissions/',
'MediaWiki\\Preferences\\' => __DIR__ . '/preferences/',
'MediaWiki\\Rest\\' => __DIR__ . '/Rest/',
'MediaWiki\\Sparql\\' => __DIR__ . '/sparql/',
'MediaWiki\\Storage\\' => __DIR__ . '/Storage/',
'MediaWiki\\Tidy\\' => __DIR__ . '/tidy/',
+ 'Wikimedia\\Message\\' => __DIR__ . '/libs/Message/',
'Wikimedia\\ParamValidator\\' => __DIR__ . '/libs/ParamValidator/',
'Wikimedia\\Services\\' => __DIR__ . '/libs/services/',
];
* $wgTiffThumbnailType = [ 'jpg', 'image/jpeg' ];
* @endcode
*/
-$wgTiffThumbnailType = false;
+$wgTiffThumbnailType = [];
/**
* If rendered thumbnail files are older than this timestamp, they
/**
* @return array
+ * @suppress PhanParamSignatureMismatch
*/
public function getValues() {
return $this->data;
if ( isset( $ctx['forwarded_for'] ) ||
isset( $ctx['client_ip'] ) ||
isset( $ctx['from'] ) ) {
+ // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
$ctx['proxy'] = $_SERVER['REMOTE_ADDR'];
}
$result = unpack( $format, $data );
Wikimedia\restoreWarnings();
+ // @phan-suppress-next-line PhanTypeComparisonFromArray Phan issue #3160
if ( $result === false ) {
// If it cannot extract the packed data.
throw new MWException( "unpack could not unpack binary data" );
Profiler::instance()->logDataPageOutputOnly();
} catch ( Exception $e ) {
// An error may already have been shown in run(), so just log it to be safe
- MWExceptionHandler::rollbackMasterChangesAndLog( $e );
+ MWExceptionHandler::logException( $e );
}
// Disable WebResponse setters for post-send processing (T191537).
use MediaWiki\FileBackend\FSFile\TempFSFileFactory;
use MediaWiki\FileBackend\LockManager\LockManagerGroupFactory;
use MediaWiki\Http\HttpRequestFactory;
+use Wikimedia\Message\IMessageFormatterFactory;
use MediaWiki\Page\MovePageFactory;
use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Preferences\PreferencesFactory;
return $this->getService( 'MessageCache' );
}
+ /**
+ * @since 1.34
+ * @return IMessageFormatterFactory
+ */
+ public function getMessageFormatterFactory() {
+ return $this->getService( 'MessageFormatterFactory' );
+ }
+
/**
* @since 1.28
* @return MimeAnalyzer
--- /dev/null
+<?php
+
+namespace MediaWiki\Message;
+
+use Wikimedia\Message\IMessageFormatterFactory;
+use Wikimedia\Message\ITextFormatter;
+
+/**
+ * The MediaWiki-specific implementation of IMessageFormatterFactory
+ */
+class MessageFormatterFactory implements IMessageFormatterFactory {
+ private $textFormatters = [];
+
+ /**
+ * Required parameters may be added to this function without deprecation.
+ * External callers should use MediaWikiServices::getMessageFormatterFactory().
+ *
+ * @internal
+ */
+ public function __construct() {
+ }
+
+ public function getTextFormatter( $langCode ): ITextFormatter {
+ if ( !isset( $this->textFormatters[$langCode] ) ) {
+ $this->textFormatters[$langCode] = new TextFormatter( $langCode );
+ }
+ return $this->textFormatters[$langCode];
+ }
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Message;
+
+use Wikimedia\Message\ITextFormatter;
+use Wikimedia\Message\ListParam;
+use Wikimedia\Message\MessageParam;
+use Wikimedia\Message\MessageValue;
+use Wikimedia\Message\ParamType;
+use Message;
+
+/**
+ * The MediaWiki-specific implementation of ITextFormatter
+ */
+class TextFormatter implements ITextFormatter {
+ /** @var string */
+ private $langCode;
+
+ /**
+ * Construct a TextFormatter.
+ *
+ * The type signature may change without notice as dependencies are added
+ * to the constructor. External callers should use
+ * MediaWikiServices::getMessageFormatterFactory()
+ *
+ * @internal
+ */
+ public function __construct( $langCode ) {
+ $this->langCode = $langCode;
+ }
+
+ /**
+ * Allow the Message class to be mocked in tests by constructing objects in
+ * a protected method.
+ *
+ * @internal
+ * @param string $key
+ * @return Message
+ */
+ protected function createMessage( $key ) {
+ return new Message( $key );
+ }
+
+ public function getLangCode() {
+ return $this->langCode;
+ }
+
+ private static function convertParam( MessageParam $param ) {
+ if ( $param instanceof ListParam ) {
+ $convertedElements = [];
+ foreach ( $param->getValue() as $element ) {
+ $convertedElements[] = self::convertParam( $element );
+ }
+ return Message::listParam( $convertedElements, $param->getListType() );
+ } elseif ( $param instanceof MessageParam ) {
+ if ( $param->getType() === ParamType::TEXT ) {
+ return $param->getValue();
+ } else {
+ return [ $param->getType() => $param->getValue() ];
+ }
+ } else {
+ throw new \InvalidArgumentException( 'Invalid message parameter type' );
+ }
+ }
+
+ public function format( MessageValue $mv ) {
+ $message = $this->createMessage( $mv->getKey() );
+ foreach ( $mv->getParams() as $param ) {
+ $message->params( self::convertParam( $param ) );
+ }
+ $message->inLanguage( $this->langCode );
+ return $message->text();
+ }
+}
* @todo document
*/
class OutputPage extends ContextSource {
- /** @var array Should be private. Used with addMeta() which adds "<meta>" */
+ /** @var string[][] Should be private. Used with addMeta() which adds "<meta>" */
protected $mMetatags = [];
/** @var array */
* @param string $text Wikitext
* @param Title $title
* @param bool $linestart Is this the start of a line?
- * @param bool $tidy Whether to use tidy.
- * Setting this to false (or omitting it) is deprecated
- * since 1.32; all wikitext should be tidied.
* @param bool $interface Whether it is an interface message
* (for example disables conversion)
* @param string $wrapperClass if not empty, wraps the output in
* a `<div class="$wrapperClass">`
- * @private
*/
private function addWikiTextTitleInternal(
$text, Title $title, $linestart, $interface, $wrapperClass = null
/** @var NamespaceInfo */
private $nsInfo;
- /** @var string[] Cached results of getAllRights() */
- private $allRights = false;
+ /** @var string[]|null Cached results of getAllRights() */
+ private $allRights;
/** @var string[][] Cached user rights */
private $usersRights = null;
* Check if user is allowed to make any action
*
* @param UserIdentity $user
- * // TODO: HHVM can't create mocks with variable params @param string ...$actions
+ * // TODO: HHVM bug T228695#5450847 @param string ...$actions
+ * @suppress PhanCommentParamWithoutRealParam
* @return bool True if user is allowed to perform *any* of the given actions
* @since 1.34
*/
* Check if user is allowed to make all actions
*
* @param UserIdentity $user
- * // TODO: HHVM can't create mocks with variable params @param string ...$actions
+ * // TODO: HHVM bug T228695#5450847 @param string ...$actions
+ * @suppress PhanCommentParamWithoutRealParam
* @return bool True if user is allowed to perform *all* of the given actions
* @since 1.34
*/
* @return string[] Array of permission names
*/
public function getAllPermissions() {
- if ( $this->allRights === false ) {
+ if ( $this->allRights === null ) {
if ( count( $this->options->get( 'AvailableRights' ) ) ) {
$this->allRights = array_unique( array_merge(
$this->coreRights,
* better served by an HTTP header parsing library which provides the full
* parse tree.
*
- * @param string $name The header name
* @param string|string[] $value The input header value
* @return array
*/
--- /dev/null
+<?php
+
+namespace MediaWiki\Rest;
+
+use Wikimedia\Message\MessageValue;
+
+class LocalizedHttpException extends HttpException {
+ public function __construct( MessageValue $message, $code = 500 ) {
+ parent::__construct( 'Localized exception with key ' . $message->getKey(), $code );
+ }
+}
use MediaWiki\Linker\LinkRendererFactory;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
+use Wikimedia\Message\IMessageFormatterFactory;
+use MediaWiki\Message\MessageFormatterFactory;
use MediaWiki\Page\MovePageFactory;
use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Preferences\PreferencesFactory;
);
},
+ 'MessageFormatterFactory' =>
+ function ( MediaWikiServices $services ) : IMessageFormatterFactory {
+ return new MessageFormatterFactory();
+ },
+
'MimeAnalyzer' => function ( MediaWikiServices $services ) : MimeAnalyzer {
$logger = LoggerFactory::getInstance( 'Mime' );
$mainConfig = $services->getMainConfig();
$wgMemc = ObjectCache::getLocalClusterInstance();
$messageMemc = wfGetMessageCacheStorage();
-wfDebugLog( 'caches',
- 'cluster: ' . get_class( $wgMemc ) .
- ', WAN: ' . ( $wgMainWANCache === CACHE_NONE ? 'CACHE_NONE' : $wgMainWANCache ) .
- ', stash: ' . $wgMainStash .
- ', message: ' . get_class( $messageMemc ) .
- ', session: ' . get_class( ObjectCache::getInstance( $wgSessionCacheType ) )
-);
-
// Most of the config is out, some might want to run hooks here.
Hooks::run( 'SetupAfterCache' );
/** @var bool Whether a page has any subpages */
private $mHasSubpages;
- /** @var bool The (string) language code of the page's language and content code. */
- private $mPageLanguage = false;
+ /** @var array|null The (string) language code of the page's language and content code. */
+ private $mPageLanguage;
/** @var string|bool|null The page language code from the database, null if not saved in
* the database or false if not loaded, yet.
$this->mLatestID = false;
$this->mContentModel = false;
$this->mEstimateRevisions = null;
- $this->mPageLanguage = false;
+ $this->mPageLanguage = null;
$this->mDbPageLanguage = false;
$this->mIsBigDeletion = null;
}
* @ingroup HTTP
*/
class WebRequest {
- protected $data, $headers = [];
+ /** @var array */
+ protected $data;
+ /** @var array */
+ protected $headers = [];
/**
* Flag to make WebRequest::getHeader return an array of values.
/**
* Clean up a field array for output
- * @param ApiBase $module For context and parameters 'mergerequestfields'
- * and 'messageformat'
* @param array $fields
* @return array
*/
/** @var array Maps extension paths to info arrays */
private static $extensionInfo = null;
- /** @var int[][][] Cache for self::filterIDs() */
+ /** @var stdClass[][] Cache for self::filterIDs() */
private static $filterIDsCache = [];
/** $var array Map of web UI block messages to corresponding API messages and codes */
/** @var Title $newTitle */
foreach ( $titles as $id => $newTitle ) {
- if ( !isset( $titles[$id - 1] ) ) {
- $titles[$id - 1] = $oldTitle;
- }
+ $titles[ $id - 1 ] = $titles[ $id - 1 ] ?? $oldTitle;
$redirValues[] = [
'from' => $titles[$id - 1]->getPrefixedText(),
* @param int $successCount
* @param array $pageInfo
* @return void
+ * @suppress PhanParamSignatureMismatch
*/
public function reportPage( $title, $foreignTitle, $revisionCount, $successCount, $pageInfo ) {
// Add a result entry
* @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}>
+ * Note that phan annotations don't support keys containing a space.
*/
private function search( $search, array $params ) {
$searchEngine = $this->buildSearchEngine( $params );
if ( is_string( $r['extract'] ) && $r['extract'] !== '' ) {
$item['Description'] = $r['extract'];
}
+ // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
if ( is_array( $r['image'] ) && isset( $r['image']['source'] ) ) {
$item['Image'] = array_intersect_key( $r['image'], $imageKeys );
}
}
}
- $fit = $result->addValue( [ 'query', $this->getModuleName() ],
- null, $data[$u] );
+ // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
+ $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $data[$u] );
if ( !$fit ) {
if ( $useNames ) {
$this->setContinueEnumParameter( 'users',
return;
}
- // The user will abort the AJAX request by pressing "save", so ignore that
- ignore_user_abort( true );
-
if ( $user->pingLimiter( 'stashedit' ) ) {
$status = 'ratelimited';
} else {
}
$options['sensitive'] = !empty( $options['sensitive'] );
+ // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
+ $type = $options['type'];
if ( !array_key_exists( $name, $merged ) ) {
$merged[$name] = $options;
- } elseif ( $merged[$name]['type'] !== $options['type'] ) {
+ } elseif ( $merged[$name]['type'] !== $type ) {
throw new \UnexpectedValueException( "Field type conflict for \"$name\", " .
- "\"{$merged[$name]['type']}\" vs \"{$options['type']}\""
+ "\"{$merged[$name]['type']}\" vs \"$type\""
);
} else {
if ( isset( $options['options'] ) ) {
* Function that gets called when initialization is done.
*
* @since 1.20
- * @var callable
+ * @var callable|null
*/
- protected $onInitHandler = false;
+ protected $onInitHandler;
/**
* Elements to build a cache key with.
$this->hasCached = is_array( $cachedChunks );
$this->cachedChunks = $this->hasCached ? $cachedChunks : [];
- if ( $this->onInitHandler !== false ) {
+ if ( $this->onInitHandler !== null ) {
call_user_func( $this->onInitHandler, $this->hasCached );
}
}
$class = $wgParserConf['class'];
if ( $class == ParserDiffTest::class ) {
# Uncloneable
+ // @phan-suppress-next-line PhanTypeMismatchProperty
$this->mParser = new $class( $wgParserConf );
} else {
$this->mParser = clone $parser;
*/
class LCStoreCDB implements LCStore {
- /** @var Reader[] */
+ /** @var Reader[]|false[] */
private $readers;
/** @var Writer */
if ( in_array( $key, self::$mergeableMapKeys ) ) {
$value = $value + $fallbackValue;
} elseif ( in_array( $key, self::$mergeableListKeys ) ) {
+ // @phan-suppress-next-line PhanTypeMismatchArgumentInternal
$value = array_unique( array_merge( $fallbackValue, $value ) );
} elseif ( in_array( $key, self::$mergeableAliasListKeys ) ) {
$value = array_merge_recursive( $value, $fallbackValue );
if ( !$code ) {
throw new MWException( "Invalid language code requested" );
}
- $this->recachedLangs[$code] = true;
+ $this->recachedLangs[ $code ] = true;
# Initial values
$initialData = array_fill_keys( self::$allKeys, null );
# Load the primary localisation from the source file
$data = $this->readSourceFilesAndRegisterDeps( $code, $deps );
- if ( $data === false ) {
- $this->logger->debug( __METHOD__ . ": no localisation file for $code, using fallback to en" );
- $coreData['fallback'] = 'en';
- } else {
- $this->logger->debug( __METHOD__ . ": got localisation for $code from source" );
+ $this->logger->debug( __METHOD__ . ": got localisation for $code from source" );
- # Merge primary localisation
- foreach ( $data as $key => $value ) {
- $this->mergeItem( $key, $coreData[$key], $value );
- }
+ # Merge primary localisation
+ foreach ( $data as $key => $value ) {
+ $this->mergeItem( $key, $coreData[ $key ], $value );
}
# Fill in the fallback if it's not there already
# Load the secondary localisation from the source file to
# avoid infinite cycles on cyclic fallbacks
$fbData = $this->readSourceFilesAndRegisterDeps( $csCode, $deps );
- if ( $fbData !== false ) {
- # Only merge the keys that make sense to merge
- foreach ( self::$allKeys as $key ) {
- if ( !isset( $fbData[$key] ) ) {
- continue;
- }
-
- if ( is_null( $coreData[$key] ) || $this->isMergeableKey( $key ) ) {
- $this->mergeItem( $key, $csData[$key], $fbData[$key] );
- }
+ # Only merge the keys that make sense to merge
+ foreach ( self::$allKeys as $key ) {
+ if ( !isset( $fbData[ $key ] ) ) {
+ continue;
+ }
+
+ if ( is_null( $coreData[ $key ] ) || $this->isMergeableKey( $key ) ) {
+ $this->mergeItem( $key, $csData[ $key ], $fbData[ $key ] );
}
}
}
public $mExtra = [];
/**
- * @var Title
+ * @var Title|false
*/
public $mTitle = false;
/**
- * @var User
+ * @var User|false
*/
private $mPerformer = false;
* @param string|string[]|MessageSpecifier $key Message key, or array of keys,
* or a MessageSpecifier.
* @param mixed $args,...
+ * @suppress PhanCommentParamWithoutRealParam HHVM bug T228695#5450847
* @return Message
*/
public function msg( $key /* $args */ ) {
* @param string|string[]|MessageSpecifier $key Message key, or array of keys,
* or a MessageSpecifier.
* @param mixed $args,... Arguments to wfMessage
+ * @suppress PhanCommentParamWithoutRealParam HHVM bug T228695#5450847
* @return Message
*/
public function msg( $key ) {
* @param string|string[]|MessageSpecifier $key Message key, or array of keys,
* or a MessageSpecifier.
* @param mixed $args,...
+ * @suppress PhanCommentParamWithoutRealParam HHVM bug T228695#5450847
* @return Message
*/
public function msg( $key ) {
* @param Diff $diff A Diff object.
*
* @return array[] List of associative arrays, each describing a difference.
+ * @suppress PhanParamSignatureMismatch
*/
public function format( $diff ) {
$oldline = 1;
public $type;
/**
- * @var string[]
+ * @var string[]|false
*/
public $orig;
/**
- * @var string[]
+ * @var string[]|false
*/
public $closing;
/**
* @param DumpOutput &$sink
- * @param array $param
+ * @param string $param
* @throws MWException
*/
function __construct( &$sink, $param ) {
"NS_CATEGORY" => NS_CATEGORY,
"NS_CATEGORY_TALK" => NS_CATEGORY_TALK ];
- if ( $param { 0 } == '!' ) {
+ if ( $param[0] == '!' ) {
$this->invert = true;
$param = substr( $param, 1 );
}
*/
class DumpPipeOutput extends DumpFileOutput {
protected $command, $filename;
+ /** @var resource|bool */
protected $procOpenResource = false;
/**
/**
* @param string $virtualUrl
- * @return false
+ * @return array
*/
function getFileProps( $virtualUrl ) {
- return false;
+ return [];
}
/**
? count( $data['query']['redirects'] ) - 1
: -1;
if ( $lastRedirect >= 0 ) {
+ // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
$newtitle = Title::newFromText( $data['query']['redirects'][$lastRedirect]['to'] );
$img = new self( $newtitle, $repo, $info, true );
$img->redirectedFrom( $title->getDBkey() );
protected $mHideBadImages;
/**
- * @var Parser Registered parser object for output callbacks
+ * @var Parser|false Registered parser object for output callbacks
*/
public $mParser;
/** @var array */
protected $mAttribs = [];
- /** @var bool */
- private static $modeMapping = false;
+ /** @var array */
+ private static $modeMapping;
/**
* Get a new image gallery. This is the method other callers
}
private static function loadModes() {
- if ( self::$modeMapping === false ) {
+ if ( self::$modeMapping === null ) {
self::$modeMapping = [
'traditional' => TraditionalImageGallery::class,
'nolines' => NolinesImageGallery::class,
* Improves compression ratio by concatenating like objects before gzipping
*/
class ConcatenatedGzipHistoryBlob implements HistoryBlob {
- public $mVersion = 0, $mCompressed = false, $mItems = [], $mDefaultHash = '';
+ public $mVersion = 0;
+ public $mCompressed = false;
+ /**
+ * @var array|string
+ * @fixme Why are some methods treating it as an array, and others as a string, unconditionally?
+ */
+ public $mItems = [];
+ public $mDefaultHash = '';
public $mSize = 0;
public $mMaxSize = 10000000;
public $mMaxCount = 100;
*
* @param string $displayFormat
* @param mixed $arguments,... Additional arguments to pass to the constructor.
+ * @suppress PhanCommentParamWithoutRealParam HHVM bug T228695#5450847
* @return HTMLForm
*/
public static function factory( $displayFormat/*, $arguments...*/ ) {
* be a subclass of this.
*/
abstract class HTMLFormField {
+ /** @var array|array[] */
public $mParams;
protected $mValidationCallback;
* The old name of autocomplete-data[-messages] was autocomplete[-messages] which is still
* recognized but deprecated since MediaWiki 1.29 since it conflicts with how autocomplete is
* used in HTMLTextField.
+ *
+ * @phan-file-suppress PhanTypeMismatchProperty This is doing weird things with mClass
*/
class HTMLAutoCompleteSelectField extends HTMLTextField {
protected $autocompleteData = [];
* mParams['columns'] is an array with column labels as keys and column tags as values.
*
* @param array $value Array of the options that should be checked
+ * @suppress PhanParamSignatureMismatch
*
* @return string
*/
* @since 1.28
* @param string[] $value
* @return string|OOUI\CheckboxMultiselectInputWidget
+ * @suppress PhanParamSignatureMismatch
*/
public function getInputOOUI( $value ) {
$this->mParent->getOutput()->addModules( 'oojs-ui-widgets' );
protected $handler = null;
protected $sink = null;
+ /** @var array */
protected $guzzleOptions = [ 'http_errors' => false ];
/**
*
* @param string|string[]|MessageSpecifier $key
* @param mixed $param,... Parameters as strings.
+ * @suppress PhanCommentParamWithoutRealParam HHVM bug T228695#5450847
*
* @return Message
*/
* @param string|string[]|MessageSpecifier $key Message key, or array of keys,
* or a MessageSpecifier.
* @param mixed $params,... Normal message parameters
+ * @suppress PhanCommentParamWithoutRealParam HHVM bug T228695#5450847
* @return Message
*/
public function msg( $key /*...*/ );
throw new InvalidArgumentException( "Invalid ring source specified." );
}
+ // Short-circuit for the common single-location case. Note that if there was only one
+ // location and it was ejected from the live ring, getLiveRing() would have error out.
+ if ( count( $this->weightByLocation ) == 1 ) {
+ return ( $limit > 0 ) ? [ $ring[0][self::KEY_LOCATION] ] : [];
+ }
+
// Locate the node index for this item's position on the hash ring
$itemIndex = $this->findNodeIndexForPosition( $this->getItemPosition( $item ), $ring );
--- /dev/null
+<?php
+
+namespace Wikimedia\Message;
+
+/**
+ * A simple factory providing a message formatter for a given language code.
+ *
+ * @see ITextFormatter
+ */
+interface IMessageFormatterFactory {
+ /**
+ * Get a text message formatter for a given language.
+ *
+ * @param string $langCode The language code
+ * @return ITextFormatter
+ */
+ public function getTextFormatter( $langCode ): ITextFormatter;
+}
--- /dev/null
+<?php
+
+namespace Wikimedia\Message;
+
+/**
+ * ITextFormatter is a simplified interface to the Message class. It converts
+ * MessageValue message specifiers to localized text in a certain language.
+ *
+ * MessageValue supports message keys, and parameters with a wide variety of
+ * types. It does not expose any details of how messages are retrieved from
+ * storage or what format they are stored in.
+ *
+ * Thus, TextFormatter supports single message keys, but not the concept of
+ * presence or absence of a key from storage. So it does not support
+ * fallback sequences of multiple keys.
+ *
+ * The caller cannot modify the details of message translation, such as which
+ * of multiple sources the message is taken from. Any such flags may be injected
+ * into the factory constructor.
+ *
+ * Implementations of TextFormatter are not required to perfectly format
+ * any message in any language. Implementations should make a best effort to
+ * produce human-readable text.
+ *
+ * @package MediaWiki\MessageFormatter
+ */
+interface ITextFormatter {
+ /**
+ * Get the internal language code in which format() is
+ * @return string
+ */
+ function getLangCode();
+
+ /**
+ * Convert a MessageValue to text.
+ *
+ * The result is not safe for use as raw HTML.
+ *
+ * @param MessageValue $message
+ * @return string
+ */
+ function format( MessageValue $message );
+}
--- /dev/null
+<?php
+
+namespace Wikimedia\Message;
+
+/**
+ * The class for list parameters
+ */
+class ListParam extends MessageParam {
+ private $listType;
+
+ /**
+ * @param string $listType One of the ListType constants:
+ * - ListType::COMMA: A comma-separated list
+ * - ListType::SEMICOLON: A semicolon-separated list
+ * - ListType::PIPE: A pipe-separated list
+ * - ListType::TEXT: A natural language list, separated by commas and
+ * the word "and".
+ * @param (MessageParam|string)[] $elements An array of parameters
+ */
+ public function __construct( $listType, array $elements ) {
+ $this->type = ParamType::LIST;
+ $this->listType = $listType;
+ $this->value = [];
+ foreach ( $elements as $element ) {
+ if ( $element instanceof MessageParam ) {
+ $this->value[] = $element;
+ } elseif ( is_scalar( $element ) ) {
+ $this->value[] = new TextParam( ParamType::TEXT, $element );
+ } else {
+ throw new \InvalidArgumentException(
+ 'ListParam elements must be MessageParam or scalar' );
+ }
+ }
+ }
+
+ /**
+ * Get the type of the list
+ *
+ * @return string One of the ListType constants
+ */
+ public function getListType() {
+ return $this->listType;
+ }
+
+ public function dump() {
+ $contents = '';
+ foreach ( $this->value as $element ) {
+ $contents .= $element->dump();
+ }
+ return "<{$this->type} listType=\"{$this->listType}\">$contents</{$this->type}>";
+ }
+}
--- /dev/null
+<?php
+
+namespace Wikimedia\Message;
+
+/**
+ * The constants used to specify list types. The values of the constants are an
+ * unstable implementation detail and correspond to the names of the list types
+ * in the Message class.
+ */
+class ListType {
+ /** A comma-separated list */
+ const COMMA = 'comma';
+
+ /** A semicolon-separated list */
+ const SEMICOLON = 'semicolon';
+
+ /** A pipe-separated list */
+ const PIPE = 'pipe';
+
+ /** A natural-language list separated by "and" */
+ const AND = 'text';
+}
--- /dev/null
+<?php
+
+namespace Wikimedia\Message;
+
+/**
+ * The base class for message parameters.
+ */
+abstract class MessageParam {
+ protected $type;
+ protected $value;
+
+ /**
+ * Get the type of the parameter.
+ *
+ * @return string One of the ParamType constants
+ */
+ public function getType() {
+ return $this->type;
+ }
+
+ /**
+ * Get the input value of the parameter
+ *
+ * @return int|float|string|array
+ */
+ public function getValue() {
+ return $this->value;
+ }
+
+ /**
+ * Dump the object for testing/debugging
+ *
+ * @return string
+ */
+ abstract public function dump();
+}
--- /dev/null
+<?php
+
+namespace Wikimedia\Message;
+
+/**
+ * A MessageValue holds a key and an array of parameters
+ */
+class MessageValue {
+ /** @var string */
+ private $key;
+
+ /** @var MessageParam[] */
+ private $params;
+
+ /**
+ * @param string $key
+ * @param array $params Each element of the parameter array
+ * may be either a MessageParam or a scalar. If it is a scalar, it is
+ * converted to a parameter of type TEXT.
+ */
+ public function __construct( $key, $params = [] ) {
+ $this->key = $key;
+ $this->params = [];
+ $this->params( ...$params );
+ }
+
+ /**
+ * Get the message key
+ *
+ * @return string
+ */
+ public function getKey() {
+ return $this->key;
+ }
+
+ /**
+ * Get the parameter array
+ *
+ * @return MessageParam[]
+ */
+ public function getParams() {
+ return $this->params;
+ }
+
+ /**
+ * Chainable mutator which adds text parameters and MessageParam parameters
+ *
+ * @param mixed ...$values Scalar or MessageParam values
+ * @return MessageValue
+ */
+ public function params( ...$values ) {
+ foreach ( $values as $value ) {
+ if ( $value instanceof MessageParam ) {
+ $this->params[] = $value;
+ } else {
+ $this->params[] = new TextParam( ParamType::TEXT, $value );
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Chainable mutator which adds text parameters with a common type
+ *
+ * @param string $type One of the ParamType constants
+ * @param mixed ...$values Scalar values
+ * @return MessageValue
+ */
+ public function textParamsOfType( $type, ...$values ) {
+ foreach ( $values as $value ) {
+ $this->params[] = new TextParam( $type, $value );
+ }
+ return $this;
+ }
+
+ /**
+ * Chainable mutator which adds list parameters with a common type
+ *
+ * @param string $listType One of the ListType constants
+ * @param array ...$values Each value should be an array of list items.
+ * @return MessageValue
+ */
+ public function listParamsOfType( $listType, ...$values ) {
+ foreach ( $values as $value ) {
+ $this->params[] = new ListParam( $listType, $value );
+ }
+ return $this;
+ }
+
+ /**
+ * Chainable mutator which adds parameters of type text.
+ *
+ * @param string ...$values
+ * @return MessageValue
+ */
+ public function textParams( ...$values ) {
+ return $this->textParamsOfType( ParamType::TEXT, ...$values );
+ }
+
+ /**
+ * Chainable mutator which adds numeric parameters
+ *
+ * @param mixed ...$values
+ * @return MessageValue
+ */
+ public function numParams( ...$values ) {
+ return $this->textParamsOfType( ParamType::NUM, ...$values );
+ }
+
+ /**
+ * Chainable mutator which adds parameters which are a duration specified
+ * in seconds. This is similar to timePeriodParams() except that the result
+ * will be more verbose.
+ *
+ * @param int|float ...$values
+ * @return MessageValue
+ */
+ public function longDurationParams( ...$values ) {
+ return $this->textParamsOfType( ParamType::DURATION_LONG, ...$values );
+ }
+
+ /**
+ * Chainable mutator which adds parameters which are a time period in seconds.
+ * This is similar to durationParams() except that the result will be more
+ * compact.
+ *
+ * @param int|float ...$values
+ * @return MessageValue
+ */
+ public function shortDurationParams( ...$values ) {
+ return $this->textParamsOfType( ParamType::DURATION_SHORT, ...$values );
+ }
+
+ /**
+ * Chainable mutator which adds parameters which are an expiry timestamp
+ * as used in the MediaWiki database schema.
+ *
+ * @param string ...$values
+ * @return MessageValue
+ */
+ public function expiryParams( ...$values ) {
+ return $this->textParamsOfType( ParamType::EXPIRY, ...$values );
+ }
+
+ /**
+ * Chainable mutator which adds parameters which are a number of bytes.
+ *
+ * @param int ...$values
+ * @return MessageValue
+ */
+ public function sizeParams( ...$values ) {
+ return $this->textParamsOfType( ParamType::SIZE, ...$values );
+ }
+
+ /**
+ * Chainable mutator which adds parameters which are a number of bits per
+ * second.
+ *
+ * @param int|float ...$values
+ * @return MessageValue
+ */
+ public function bitrateParams( ...$values ) {
+ return $this->textParamsOfType( ParamType::BITRATE, ...$values );
+ }
+
+ /**
+ * Chainable mutator which adds parameters of type "raw".
+ *
+ * @param mixed ...$values
+ * @return MessageValue
+ */
+ public function rawParams( ...$values ) {
+ return $this->textParamsOfType( ParamType::RAW, ...$values );
+ }
+
+ /**
+ * Chainable mutator which adds parameters of type "plaintext".
+ */
+ public function plaintextParams( ...$values ) {
+ return $this->textParamsOfType( ParamType::PLAINTEXT, ...$values );
+ }
+
+ /**
+ * Chainable mutator which adds comma lists. Each comma list is an array of
+ * list elements, and each list element is either a MessageParam or a
+ * string. String parameters are converted to parameters of type "text".
+ *
+ * The list parameters thus created are formatted as a comma-separated list,
+ * or some local equivalent.
+ *
+ * @param (MessageParam|string)[] ...$values
+ * @return MessageValue
+ */
+ public function commaListParams( ...$values ) {
+ return $this->listParamsOfType( ListType::COMMA, ...$values );
+ }
+
+ /**
+ * Chainable mutator which adds semicolon lists. Each semicolon list is an
+ * array of list elements, and each list element is either a MessageParam
+ * or a string. String parameters are converted to parameters of type
+ * "text".
+ *
+ * The list parameters thus created are formatted as a semicolon-separated
+ * list, or some local equivalent.
+ *
+ * @param (MessageParam|string)[] ...$values
+ * @return MessageValue
+ */
+ public function semicolonListParams( ...$values ) {
+ return $this->listParamsOfType( ListType::SEMICOLON, ...$values );
+ }
+
+ /**
+ * Chainable mutator which adds pipe lists. Each pipe list is an array of
+ * list elements, and each list element is either a MessageParam or a
+ * string. String parameters are converted to parameters of type "text".
+ *
+ * The list parameters thus created are formatted as a pipe ("|") -separated
+ * list, or some local equivalent.
+ *
+ * @param (MessageParam|string)[] ...$values
+ * @return MessageValue
+ */
+ public function pipeListParams( ...$values ) {
+ return $this->listParamsOfType( ListType::PIPE, ...$values );
+ }
+
+ /**
+ * Chainable mutator which adds text lists. Each text list is an array of
+ * list elements, and each list element is either a MessageParam or a
+ * string. String parameters are converted to parameters of type "text".
+ *
+ * The list parameters thus created, when formatted, are joined as in natural
+ * language. In English, this means a comma-separated list, with the last
+ * two elements joined with "and".
+ *
+ * @param (MessageParam|string)[] ...$values
+ * @return MessageValue
+ */
+ public function textListParams( ...$values ) {
+ return $this->listParamsOfType( ListType::AND, ...$values );
+ }
+
+ /**
+ * Dump the object for testing/debugging
+ *
+ * @return string
+ */
+ public function dump() {
+ $contents = '';
+ foreach ( $this->params as $param ) {
+ $contents .= $param->dump();
+ }
+ return '<message key="' . htmlspecialchars( $this->key ) . '">' .
+ $contents . '</message>';
+ }
+}
--- /dev/null
+<?php
+
+namespace Wikimedia\Message;
+
+/**
+ * The constants used to specify parameter types. The values of the constants
+ * are an unstable implementation detail, and correspond to the names of the
+ * parameter types in the Message class.
+ */
+class ParamType {
+ /** A simple text parameter */
+ const TEXT = 'text';
+
+ /** A number, to be formatted using local digits and separators */
+ const NUM = 'num';
+
+ /** A number of seconds, to be formatted as natural language text. */
+ const DURATION_LONG = 'duration';
+
+ /** A number of seconds, to be formatted in an abbreviated way. */
+ const DURATION_SHORT = 'timeperiod';
+
+ /**
+ * An expiry time for a block. The input is either a timestamp in one
+ * of the formats accepted by the Wikimedia\Timestamp library, or
+ * "infinity" for an infinite block.
+ */
+ const EXPIRY = 'expiry';
+
+ /** A number of bytes. */
+ const SIZE = 'size';
+
+ /** A number of bits per second. */
+ const BITRATE = 'bitrate';
+
+ /** The list type (ListParam) */
+ const LIST = 'list';
+
+ /**
+ * A text parameter which is substituted after preprocessing, and so is
+ * not available to the preprocessor and cannot be modified by it.
+ */
+ const RAW = 'raw';
+
+ /** Reserved for future use. */
+ const PLAINTEXT = 'plaintext';
+}
--- /dev/null
+<?php
+
+namespace Wikimedia\Message;
+
+class TextParam extends MessageParam {
+ /**
+ * Construct a text parameter
+ *
+ * @param string $type May be one of:
+ * - ParamType::TEXT: A simple text parameter
+ * - ParamType::NUM: A number, to be formatted using local digits and
+ * separators
+ * - ParamType::DURATION_LONG: A number of seconds, to be formatted as natural
+ * language text.
+ * - ParamType::DURATION_SHORT: A number of seconds, to be formatted in an
+ * abbreviated way.
+ * - ParamType::EXPIRY: An expiry time for a block. The input is either
+ * a timestamp in one of the formats accepted by the Wikimedia\Timestamp
+ * library, or "infinity" for an infinite block.
+ * - ParamType::SIZE: A number of bytes.
+ * - ParamType::BITRATE: A number of bits per second.
+ * - ParamType::RAW: A text parameter which is substituted after
+ * preprocessing, and so is not available to the preprocessor and cannot
+ * be modified by it.
+ * - ParamType::PLAINTEXT: Reserved for future use.
+ *
+ * @param string|int|float $value
+ */
+ public function __construct( $type, $value ) {
+ $this->type = $type;
+ $this->value = $value;
+ }
+
+ public function dump() {
+ return "<{$this->type}>" . htmlspecialchars( $this->value ) . "</{$this->type}>";
+ }
+}
* @file
* @ingroup FileBackend
*/
+
use Wikimedia\AtEase\AtEase;
use Wikimedia\Timestamp\ConvertibleTimestamp;
* @return StatusValue
*/
final public function createInternal( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
if ( strlen( $params['content'] ) > $this->maxFileSizeInternal() ) {
$status = $this->newStatus( 'backend-fail-maxsize',
* @return StatusValue
*/
final public function storeInternal( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
if ( filesize( $params['src'] ) > $this->maxFileSizeInternal() ) {
$status = $this->newStatus( 'backend-fail-maxsize',
* @return StatusValue
*/
final public function copyInternal( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->doCopyInternal( $params );
$this->clearCache( [ $params['dst'] ] );
* @return StatusValue
*/
final public function deleteInternal( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->doDeleteInternal( $params );
$this->clearCache( [ $params['src'] ] );
* @return StatusValue
*/
final public function moveInternal( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->doMoveInternal( $params );
$this->clearCache( [ $params['src'], $params['dst'] ] );
* @return StatusValue
*/
final public function describeInternal( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
if ( count( $params['headers'] ) ) {
$status = $this->doDescribeInternal( $params );
}
final public function concatenate( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->newStatus();
// Try to lock the source files for the scope of this function
+ /** @noinspection PhpUnusedLocalVariableInspection */
$scopeLockS = $this->getScopedFileLocks( $params['srcs'], LockManager::LOCK_UW, $status );
if ( $status->isOK() ) {
// Actually do the file concatenation...
}
final protected function doPrepare( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->newStatus();
}
final protected function doSecure( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->newStatus();
}
final protected function doPublish( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->newStatus();
}
final protected function doClean( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->newStatus();
// Attempt to lock this directory...
$filesLockEx = [ $params['dir'] ];
+ /** @noinspection PhpUnusedLocalVariableInspection */
$scopedLockE = $this->getScopedFileLocks( $filesLockEx, LockManager::LOCK_EX, $status );
if ( !$status->isOK() ) {
return $status; // abort
}
final public function fileExists( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$stat = $this->getFileStat( $params );
}
final public function getFileTimestamp( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$stat = $this->getFileStat( $params );
}
final public function getFileSize( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$stat = $this->getFileStat( $params );
if ( $path === null ) {
return false; // invalid storage path
}
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$latest = !empty( $params['latest'] ); // use latest data?
abstract protected function doGetFileStat( array $params );
public function getFileContentsMulti( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$params = $this->setConcurrencyFlags( $params );
if ( $path === null ) {
return false; // invalid storage path
}
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$latest = !empty( $params['latest'] ); // use latest data?
if ( $this->cheapCache->hasField( $path, 'xattr', self::CACHE_TTL ) ) {
if ( $path === null ) {
return false; // invalid storage path
}
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$latest = !empty( $params['latest'] ); // use latest data?
if ( $this->cheapCache->hasField( $path, 'sha1', self::CACHE_TTL ) ) {
}
final public function getFileProps( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$fsFile = $this->getLocalReference( $params );
$props = $fsFile ? $fsFile->getProps() : FSFile::placeholderProps();
}
final public function getLocalReferenceMulti( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$params = $this->setConcurrencyFlags( $params );
}
final public function getLocalCopyMulti( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$params = $this->setConcurrencyFlags( $params );
}
final public function streamFile( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->newStatus();
}
final protected function doOperationsInternal( array $ops, array $opts ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->newStatus();
// Build up a list of files to lock...
$paths = $this->getPathsToLockForOpsInternal( $performOps );
// Try to lock those files for the scope of this function...
-
+ /** @noinspection PhpUnusedLocalVariableInspection */
$scopeLock = $this->getScopedFileLocks( $paths, 'mixed', $status );
if ( !$status->isOK() ) {
return $status; // abort
}
final protected function doQuickOperationsInternal( array $ops ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->newStatus();
* @throws FileBackendError
*/
final public function executeOpHandlesInternal( array $fileOpHandles ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
foreach ( $fileOpHandles as $fileOpHandle ) {
*/
protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
if ( count( $fileOpHandles ) ) {
- throw new LogicException( "Backend does not support asynchronous operations." );
+ throw new FileBackendError( "Backend does not support asynchronous operations." );
}
return [];
}
final public function preloadFileStat( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$success = true; // no network errors
* @param array $items
*/
final protected function primeContainerCache( array $items ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$paths = []; // list of storage paths
* @param array $items List of storage paths
*/
final protected function primeFileCache( array $items ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$paths = []; // list of storage paths
$stat = $this->getFileStat( $params );
}
+ // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
return $stat['xattr'];
} else {
return false;
* This is true for the request headers and the response headers. Integer-indexed
* method/URL entries will also be changed to use the corresponding string keys.
*
- * @param array $reqs Map of HTTP request arrays
+ * @param array[] $reqs Map of HTTP request arrays
* @param array $opts
* - connTimeout : connection timeout per request (seconds)
* - reqTimeout : post-connection timeout per request (seconds)
*
* @see MultiHttpClient::runMulti()
*
- * @param array $reqs Map of HTTP request arrays
+ * @param array[] $reqs Map of HTTP request arrays
* @param array $opts
* - connTimeout : connection timeout per request (seconds)
* - reqTimeout : post-connection timeout per request (seconds)
* - reqTimeout : default request timeout
* @return resource
* @throws Exception
+ * @suppress PhanTypeMismatchArgumentInternal
*/
protected function getCurlHandle( array &$req, array $opts = [] ) {
$ch = curl_init();
/**
* Normalize request information
*
- * @param array $reqs the requests to normalize
+ * @param array[] $reqs the requests to normalize
*/
private function normalizeRequests( array &$reqs ) {
foreach ( $reqs as &$req ) {
final protected function doLockByType( array $pathsByType ) {
$status = StatusValue::newGood();
- $pathsToLock = []; // (bucket => type => paths)
+ $pathsByTypeByBucket = []; // (bucket => type => paths)
// Get locks that need to be acquired (buckets => locks)...
foreach ( $pathsByType as $type => $paths ) {
foreach ( $paths as $path ) {
++$this->locksHeld[$path][$type];
} else {
$bucket = $this->getBucketFromPath( $path );
- $pathsToLock[$bucket][$type][] = $path;
+ $pathsByTypeByBucket[$bucket][$type][] = $path;
}
}
}
+ // Acquire locks in each bucket in bucket order to reduce contention. Any blocking
+ // mutexes during the acquisition step will not involve circular waiting on buckets.
+ ksort( $pathsByTypeByBucket );
+
$lockedPaths = []; // files locked in this attempt (type => paths)
// Attempt to acquire these locks...
- foreach ( $pathsToLock as $bucket => $pathsToLockByType ) {
+ foreach ( $pathsByTypeByBucket as $bucket => $bucketPathsByType ) {
// Try to acquire the locks for this bucket
- $status->merge( $this->doLockingRequestBucket( $bucket, $pathsToLockByType ) );
+ $status->merge( $this->doLockingRequestBucket( $bucket, $bucketPathsByType ) );
if ( !$status->isOK() ) {
$status->merge( $this->doUnlockByType( $lockedPaths ) );
return $status;
}
// Record these locks as active
- foreach ( $pathsToLockByType as $type => $paths ) {
+ foreach ( $bucketPathsByType as $type => $paths ) {
foreach ( $paths as $path ) {
$this->locksHeld[$path][$type] = 1; // locked
// Keep track of what locks were made in this attempt
protected function doUnlockByType( array $pathsByType ) {
$status = StatusValue::newGood();
- $pathsToUnlock = []; // (bucket => type => paths)
+ $pathsByTypeByBucket = []; // (bucket => type => paths)
foreach ( $pathsByType as $type => $paths ) {
foreach ( $paths as $path ) {
if ( !isset( $this->locksHeld[$path][$type] ) ) {
if ( $this->locksHeld[$path][$type] <= 0 ) {
unset( $this->locksHeld[$path][$type] );
$bucket = $this->getBucketFromPath( $path );
- $pathsToUnlock[$bucket][$type][] = $path;
+ $pathsByTypeByBucket[$bucket][$type][] = $path;
}
if ( $this->locksHeld[$path] === [] ) {
unset( $this->locksHeld[$path] ); // no SH or EX locks left for key
// Remove these specific locks if possible, or at least release
// all locks once this process is currently not holding any locks.
- foreach ( $pathsToUnlock as $bucket => $pathsToUnlockByType ) {
- $status->merge( $this->doUnlockingRequestBucket( $bucket, $pathsToUnlockByType ) );
+ foreach ( $pathsByTypeByBucket as $bucket => $bucketPathsByType ) {
+ $status->merge( $this->doUnlockingRequestBucket( $bucket, $bucketPathsByType ) );
}
if ( $this->locksHeld === [] ) {
$status->merge( $this->releaseAllLocks() );
}
/**
- * @param string $fname the filename
+ * @param string $xml
+ * @param bool $isFile
*/
private function validateFromInput( $xml, $isFile ) {
$reader = new XMLReader();
*
* @ingroup Cache
* @ingroup Redis
+ * @phan-file-suppress PhanTypeComparisonFromArray It's unclear whether exec() can return false
*/
class RedisBagOStuff extends MediumSpecificBagOStuff {
/** @var RedisConnectionPool */
* @ingroup Database
* @since 1.22
* @see Database
+ * @phan-file-suppress PhanParamSignatureMismatch resource vs mysqli_result
*/
class DatabaseMysqli extends DatabaseMysqlBase {
/**
* that field to. The data will be quoted by IDatabase::addQuotes().
* Values with integer keys form unquoted SET statements, which can be used for
* things like "field = field + 1" or similar computed values.
- * @param array $conds An array of conditions (WHERE). See
+ * @param array|string $conds An array of conditions (WHERE). See
* IDatabase::select() for the details of the format of condition
* arrays. Use '*' to update all rows.
* @param string $fname The function name of the caller (from __METHOD__),
* @param string $joinTable The other table.
* @param string $delVar The variable to join on, in the first table.
* @param string $joinVar The variable to join on, in the second table.
- * @param array $conds Condition array of field names mapped to variables,
+ * @param array|string $conds Condition array of field names mapped to variables,
* ANDed together in the WHERE clause
* @param string $fname Calling function name (use __METHOD__) for logs/profiling
* @throws DBError If an error occurs, see IDatabase::query()
* @see https://dev.mysql.com/doc/refman/5.6/en/replication-gtids-concepts.html
*/
class MySQLMasterPos implements DBMasterPos {
- /** @var int One of (BINARY_LOG, GTID_MYSQL, GTID_MARIA) */
+ /** @var string One of (BINARY_LOG, GTID_MYSQL, GTID_MARIA) */
private $style;
/** @var string|null Base name of all Binary Log files */
private $binLog;
/** @var DatabaseDomain Local DB domain ID and default for selectDB() calls */
private $localDomain;
- /** @var Database[][][] Map of (connection category => server index => IDatabase[]) */
+ /**
+ * @var IDatabase[][][]|Database[][][] Map of (connection category => server index => IDatabase[])
+ */
private $conns;
/** @var array[] Map of (server index => server config array) */
private $tableAliases = [];
/** @var string[] Map of (index alias => index) */
private $indexAliases = [];
- /** @var array[] Map of (name => callable) */
+ /** @var callable[] Map of (name => callable) */
private $trxRecurringCallbacks = [];
/** @var bool[] Map of (domain => whether to use "temp tables only" mode) */
private $tempTablesOnlyMode = [];
*
* @since 1.26
* @param string $blob
- * @return array
+ * @return array|false
*/
public static function extractParams( $blob ) {
return unserialize( $blob );
/**
* @param File $image
- * @param array $metadata
+ * @param string $metadata
* @return bool|int
*/
public function isMetadataValid( $image, $metadata ) {
* Exif::getFilteredData() or BitmapMetadataHandler )
* @return array
* @since 1.23
+ * @suppress PhanTypeArraySuspiciousNullable
*/
public function makeFormattedData( $tags ) {
$resolutionunit = !isset( $tags['ResolutionUnit'] ) || $tags['ResolutionUnit'] == 2 ? 2 : 3;
* @param string $ext
* @param string $mime
* @param array|null $params
- * @return bool
+ * @return array
*/
public function getThumbType( $ext, $mime, $params = null ) {
global $wgTiffThumbnailType;
$this->numServerShards = count( $this->serverInfos );
} else {
// Default to using the main wiki's database servers
- $this->serverInfos = false;
+ $this->serverInfos = [];
$this->numServerShards = 1;
$this->attrMap[self::ATTR_SYNCWRITES] = self::QOS_SYNCWRITES_BE;
}
*/
public $mLatest = false;
- /** @var PreparedEdit Map of cache fields (text, parser output, ect) for a proposed/new edit */
+ /**
+ * @var PreparedEdit|false Map of cache fields (text, parser output, ect) for a proposed/new edit
+ */
public $mPreparedEdit = false;
/**
*
* @param int|bool $openingCount
* @return array
+ * @suppress PhanParamSignatureMismatch
*/
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 ) {
* @param string $sep
* @param int $flags
* @param string|PPNode $args,...
+ * @suppress PhanCommentParamWithoutRealParam HHVM bug T228695#5450847
* @return string
*/
public function implodeWithFlags( $sep, $flags /*, ... */ );
* Implode with no flags specified
* @param string $sep
* @param string|PPNode $args,...
+ * @suppress PhanCommentParamWithoutRealParam HHVM bug T228695#5450847
* @return string
*/
public function implode( $sep /*, ... */ );
* Makes an object that, when expand()ed, will be the same as one obtained
* with implode()
* @param string $sep
- * @param string|PPNode $args,...
+ * @param string|PPNode ...$args
+ * @suppress PhanCommentParamWithoutRealParam HHVM bug T228695#5450847
* @return PPNode
*/
- public function virtualImplode( $sep /*, ... */ );
+ public function virtualImplode( $sep /* ...$args */ );
/**
* Virtual implode with brackets
* @param string $start
* @param string $sep
* @param string $end
- * @param string|PPNode $args,...
+ * @param string|PPNode ...$args
+ * @suppress PhanCommentParamWithoutRealParam HHVM bug T228695#5450847
* @return PPNode
*/
- public function virtualBracketedImplode( $start, $sep, $end /*, ... */ );
+ public function virtualBracketedImplode( $start, $sep, $end /* ...$args */ );
/**
* Returns true if there are no arguments in this frame
* @param string $sep
* @param string|PPNode_DOM|DOMNode ...$args
* @return array
+ * @suppress PhanParamSignatureMismatch
*/
public function virtualImplode( $sep, ...$args ) {
$out = [];
* @param string $end
* @param string|PPNode_DOM|DOMNode ...$args
* @return array
+ * @suppress PhanParamSignatureMismatch
*/
public function virtualBracketedImplode( $start, $sep, $end, ...$args ) {
$out = [ $start ];
*/
public function replaceExternalLinks( $text ) {
$bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
+ // @phan-suppress-next-line PhanTypeComparisonFromArray See phan issue #3161
if ( $bits === false ) {
throw new MWException( "PCRE needs to be compiled with "
. "--enable-unicode-properties in order for MediaWiki to function" );
*/
protected function getTimeZoneList( Language $language ) {
$identifiers = DateTimeZone::listIdentifiers();
+ // @phan-suppress-next-line PhanTypeComparisonFromArray See phan issue #3162
if ( $identifiers === false ) {
return [];
}
<?php
class ProfilerExcimer extends Profiler {
+ /** @var ExcimerProfiler */
private $cpuProf;
+ /** @var ExcimerProfiler */
private $realProf;
private $period;
*/
private $context;
+ /** @var int|array */
protected $modules = self::INHERIT_VALUE;
protected $language = self::INHERIT_VALUE;
protected $direction = self::INHERIT_VALUE;
if ( $this->modules === self::INHERIT_VALUE ) {
return $this->context->getModules();
}
- // @phan-suppress-next-line PhanTypeMismatchReturn
+
return $this->modules;
}
protected $direction;
protected $hash;
protected $userObj;
+ /** @var ResourceLoaderImage|false */
protected $imageObj;
/**
* @param string|string[]|MessageSpecifier $key Message key, or array of keys,
* or a MessageSpecifier.
* @param mixed $args,...
+ * @suppress PhanCommentParamWithoutRealParam HHVM bug T228695#5450847
* @return Message
*/
public function msg( $key ) {
// clear get_last_error without actually raising an error
// from https://www.php.net/manual/en/function.error-get-last.php#113518
- // TODO replace with clear_last_error when requirements are bumped to PHP7
+ // TODO replace with error_clear_last after dropping HHVM
+ // @phan-suppress-next-line PhanTypeMismatchArgumentInternal
set_error_handler( function () {
}, 0 );
AtEase::suppressWarnings();
*
* @since 1.21
*
- * @var array[]
+ * @var array[]|false
*/
protected $localIds = [];
*
* @param string $name Message name
* @param mixed $params,... Message params
+ * @suppress PhanCommentParamWithoutRealParam HHVM bug T228695#5450847
* @return Message
*/
public function getMsg( $name /* ... */ ) {
/**
* Process the form on POST submission.
* @param array $data
- * @param HTMLForm $form
+ * @param HTMLForm|null $form
+ * @suppress PhanCommentParamWithoutRealParam Many implementations don't have $form
* @return bool|string|array|Status As documented for HTMLForm::trySubmit.
*/
- abstract public function onSubmit( array $data /* $form = null */ );
+ abstract public function onSubmit( array $data /* HTMLForm $form = null */ );
/**
* Do something exciting on successful processing of the form, most likely to show a
'restrictions' => $data['restrictions'],
'grants' => array_merge(
MWGrants::getHiddenGrants(),
+ // @phan-suppress-next-next-line PhanTypeMismatchArgumentInternal See phan issue #3163,
+ // it's probably failing to infer the type of $data['grants']
preg_replace( '/^grant-/', '', $data['grants'] )
)
] );
];
foreach ( $changeGroups as $messageKey => $changeGroup ) {
+ // @phan-suppress-next-line PhanTypeComparisonFromArray
if ( $changeGroup === true ) {
// For grep: listgrouprights-addgroup-all, listgrouprights-removegroup-all,
// listgrouprights-addgroup-self-all, listgrouprights-removegroup-self-all
private $mOriginalLogCallback = null;
private $mOriginalPageOutCallback = null;
private $mLogItemCount = 0;
+ private $mPageCount;
+ private $mIsUpload;
+ private $mInterwiki;
/**
* @param WikiImporter $importer
return $headers;
}
+ /**
+ * @param string $name
+ * @param string $value
+ * @return string
+ * @suppress PhanTypeArraySuspiciousNullable
+ */
function formatValue( $name, $value ) {
static $msg = null;
if ( $msg === null ) {
return $this->tagFilter;
}
- /**
- * @deprecated since 1.34, redundant.
- *
- * @return string "users"
- */
- public function getContribs() {
- // Brought back for backwards compatibility, see T231540.
- return 'users';
- }
-
/**
* @return string
*/
* Check a block of CSS or CSS fragment for anything that looks like
* it is bringing in remote code.
* @param string $value a string of CSS
- * @param bool $propOnly only check css properties (start regex with :)
* @return bool true if the CSS contains an illegal string, false if otherwise
*/
private static function checkCssFragment( $value ) {
/**
* Check if a given user has permission to use this functionality.
* @param User $user
- * @param bool $displayPassword If set, also check whether the user is allowed to reset the
- * password of another user and see the temporary password.
* @since 1.29 Second argument for displayPassword removed.
* @return StatusValue
*/
'mActorId',
];
- /**
- * @var string[]
- * @var string[] Cached results of getAllRights()
- */
- protected static $mAllRights = false;
-
/** Cache variables */
// @{
/** @var int */
* @return bool
*/
public function addNewUserLogEntryAutoCreate() {
+ wfDeprecated( __METHOD__, '1.27' );
$this->addNewUserLogEntry( 'autocreate' );
return true;
$joinConds
);
- return $res === false ? [] : $res;
+ return $res;
}
}
protected $startToken;
/**
- * @var array List of tokens that are members of the current expect sequence
+ * @var array[]|string[] List of tokens that are members of the current expect sequence
*/
protected $tokens;
/**
* Accepts the next token in an expect sequence
*
- * @param array $token
+ * @param array|string $token
*/
protected function tryEndExpect( $token ) {
switch ( $this->startToken[0] ) {
* @param string $profile The currently selected profile
* @param string $term The user provided search terms
* @return string HTML
+ * @suppress PhanTypeArraySuspiciousNullable
*/
protected function profileTabsHtml( $profile, $term ) {
$bareterm = $this->startsWithImage( $term )
public $mVariants, $mCode, $mLoaded = false;
public $mMagicExtensions = [];
- private $mHtmlCode = null, $mParentLanguage = false;
+ private $mHtmlCode = null;
+ /** @var Language|false */
+ private $mParentLanguage = false;
public $dateFormatStrings = [];
public $mExtendedSpecialPageAliases;
}
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' ) {
# The above mixing may leave namespaces out of canonical order.
# Re-order by namespace ID number...
+ // @phan-suppress-next-line PhanTypeMismatchArgumentInternal
ksort( $this->namespaceNames );
Hooks::run( 'LanguageGetNamespaces', [ &$this->namespaceNames ] );
$fallbackChain = array_reverse( $fallbackChain );
foreach ( $fallbackChain as $code ) {
if ( isset( $newWords[$code] ) ) {
+ // @phan-suppress-next-line PhanTypeMismatchProperty
$this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
}
}
public $mTablesLoaded = false;
/**
- * @var ReplacementArray[]
- * @phan-var array<string,ReplacementArray>
+ * @var ReplacementArray[]|bool[]
*/
public $mTables;
}
$this->mTablesLoaded = true;
- $this->mTables = false;
+ $this->mTables = null;
$cache = ObjectCache::getInstance( $wgLanguageConverterCacheType );
$cacheKey = $cache->makeKey( 'conversiontables', $this->mMainLanguageCode );
if ( $fromCache ) {
'bh' => 'भोजपुरी', # Bihari macro language. Falls back to Bhojpuri (bho)
'bho' => 'भोजपुरी', # Bhojpuri
'bi' => 'Bislama', # Bislama
- 'bjn' => 'Bahasa Banjar', # Banjarese
+ 'bjn' => 'Banjar', # Banjarese
'bm' => 'bamanankan', # Bambara
'bn' => 'বাংলা', # Bengali
'bo' => 'བོད་ཡིག', # Tibetan
'st' => 'Sesotho', # Southern Sotho
'sty' => 'cебертатар', # Siberian Tatar
'stq' => 'Seeltersk', # Saterland Frisian
- 'su' => 'Basa Sunda', # Sundanese
+ 'su' => 'Sunda', # Sundanese
'sv' => 'svenska', # Swedish
'sw' => 'Kiswahili', # Swahili
'szl' => 'ślůnski', # Silesian
"sessionfailure": "يبدو أنه هناك مشكلة في جلسة الدخول الخاصة بك؛\nلذلك فقد ألغيت هذه العملية كإجراء احترازي ضد الاختراق.\nمن فضلك أعد إرسال الاستمارة مرة أخرى.",
"changecontentmodel": "تغيير نموذج المحتوى لصفحة",
"changecontentmodel-legend": "غير نموذج المحتوى",
- "changecontentmodel-title-label": "عنوان الصفحة",
+ "changecontentmodel-title-label": "عنوان الصفحة:",
"changecontentmodel-current-label": "نموذج المحتوى الحالي:",
- "changecontentmodel-model-label": "نموذج محتوى جديد",
+ "changecontentmodel-model-label": "نموذج محتوى جديد:",
"changecontentmodel-reason-label": "السبب:",
"changecontentmodel-submit": "تغيير",
"changecontentmodel-success-title": "نموذج المحتوى تم تغييره",
"blockednoreason": "прычына не пазначана",
"blockedtext-composite": "<strong>Вашае імя ўдзельніка ці IP-адрас былі заблякаваныя.</strong>\n\nПададзеная прычына:\n\n:<em>$2</em>.\n\n* Пачатак блякаваньня: $8\n* Сканчэньне найдаўжэйшага з блякаваньняў: $6\n\n* $5\n\nВаш цяперашні IP-адрас — $3.\nКалі ласка, дадайце ўсе падрабязнасьці, прыведзеныя вышэй, у запыты, што вы будзеце рабіць.",
"blockedtext-composite-ids": "Адпаведныя ідэнтыфікатары блякаваньня: $1 (ваш IP-адрас таксама можа знаходзіцца ў чорным сьпісе)",
+ "blockedtext-composite-no-ids": "Ваш ІП-адрас наяўны ў некалькіх чорных сьпісах",
"blockedtext-composite-reason": "Маецца некалькі блякаваньняў вашага рахунку і/ці IP-адрасу",
"whitelistedittext": "Вам трэба $1, каб рэдагаваць старонкі.",
"confirmedittext": "Вы мусіце пацьвердзіць Ваш адрас электроннай пошты перад рэдагаваньнем старонак. Калі ласка, пазначце і пацьвердзіце адрас электроннай пошты праз Вашы [[Special:Preferences|налады]].",
"nocreate-loggedin": "Вы ня маеце дазволу на стварэньне новых старонак.",
"sectioneditnotsupported-title": "Рэдагаваньне сэкцыяў не падтрымліваецца",
"sectioneditnotsupported-text": "Рэдагаваньне сэкцыяў не падтрымліваецца на гэтай старонцы.",
+ "modeleditnotsupported-title": "Рэдагаваньне ня падтрымоўваецца",
+ "modeleditnotsupported-text": "Рэдагаваньне ня падтрымоўваецца для мадэлі са зьместам $1",
"permissionserrors": "Памылка дазволу",
"permissionserrorstext": "Вы ня маеце дазволу на гэтае дзеяньне з {{PLURAL:$1|1=наступнай прычыны|наступных прычынаў}}:",
"permissionserrorstext-withaction": "Вы ня маеце дазволу на $2 з {{PLURAL:$1|1=наступнай прычыны|наступных прычынаў}}:",
"content-model-css": "CSS",
"content-json-empty-object": "Пусты аб’ект",
"content-json-empty-array": "Пусты масіў",
+ "unsupported-content-model": "<strong>Увага:</strong> Мадэль са зьместам $1 ня падтрымоўвацца на гэтай вікі.",
"deprecated-self-close-category": "Старонкі зь няслушнымі самазакрытымі HTML-тэгамі",
"deprecated-self-close-category-desc": "Старонка ўтрымлівае няслушныя самазакрытыя HTML-тэгі, такія як <code><b/></code> ці <code><span/></code>. Іх паводзіны ў хуткім часе будуць зьмененыя ў адпаведнасьці з спэцыфікацыяй HTML5, таму іх ўжываньне ў вікітэксьце лічыцца састарэлым.",
"duplicate-args-warning": "<strong>Папярэджаньне:</strong> [[:$1]] выклікае [[:$2]] з больш чым адным значэньнем парамэтру «$3». Толькі апошняе з пададзеных значэньняў будзе ўжытае.",
"rcfilters-clear-all-filters": "Ачысьціць усе фільтры",
"rcfilters-show-new-changes": "Праглядзець новыя зьмены з $1",
"rcfilters-search-placeholder": "Фільтар зьменаў (ужывайце мэню ці пошук дзеля назвы фільтру)",
+ "rcfilters-search-placeholder-mobile": "Фільтары",
"rcfilters-invalid-filter": "Няслушны фільтар",
"rcfilters-empty-filter": "Няма актыўных фільтраў. Паказаны ўвесь унёсак.",
"rcfilters-filterlist-title": "Фільтры",
"rcfilters-filter-showlinkedto-label": "Паказаць зьмены старонак, якія спасылаюцца на",
"rcfilters-filter-showlinkedto-option-label": "<strong>Старонкі, якія спасылаюцца на</strong> абраную старонку",
"rcfilters-target-page-placeholder": "Увядзіце назву старонкі (ці катэгорыі)",
+ "rcfilters-allcontents-label": "Увесь зьмест",
+ "rcfilters-alldiscussions-label": "Усе абмеркаваньні",
"rcnotefrom": "Ніжэй {{PLURAL:$5|знаходзіцца зьмена|знаходзяцца зьмены}} з <strong>$4 $3</strong> (да <strong>$1</strong> на старонку).",
"rclistfromreset": "Скінуць выбар даты",
"rclistfrom": "Паказаць зьмены з $2 $3",
"changecontentmodel": "Зьмена мадэлі зьместу старонкі",
"changecontentmodel-legend": "Зьмена мадэлі зьместу",
"changecontentmodel-title-label": "Назва старонкі",
+ "changecontentmodel-current-label": "Бягучая мадэль зьместу:",
"changecontentmodel-model-label": "Новая мадэль зьместу",
"changecontentmodel-reason-label": "Прычына:",
"changecontentmodel-submit": "Зьмяніць",
"permanentlink": "Сталая спасылка",
"permanentlink-revid": "Ідэнтыфікатар вэрсіі",
"permanentlink-submit": "Перайсьці да вэрсіі",
+ "newsection": "Новы разьдзел",
+ "newsection-page": "Мэтавая старонка",
+ "newsection-submit": "Перайсьці да старонкі",
"dberr-problems": "Прабачце! На гэтым сайце ўзьніклі тэхнічныя цяжкасьці.",
"dberr-again": "Паспрабуйце пачакаць некалькі хвілінаў і абнавіць.",
"dberr-info": "(Немагчыма злучыцца з базай зьвестак: $1)",
"exif-scenetype-1": "Unha imaxe fotografada directamente",
"exif-customrendered-0": "Procesamento normal",
"exif-customrendered-1": "Procesamento personalizado",
+ "exif-customrendered-2": "HDR (orixinal non gardado)",
+ "exif-customrendered-3": "HDR (orixinal gardado)",
+ "exif-customrendered-4": "Orixinal (para HDR)",
+ "exif-customrendered-6": "Panorama",
+ "exif-customrendered-7": "Retrato HDR",
+ "exif-customrendered-8": "Retrato",
"exif-exposuremode-0": "Exposición automática",
"exif-exposuremode-1": "Exposición manual",
"exif-exposuremode-2": "Compensación automática da exposición",
"nocreate-loggedin": "Vous n'avez pas la permission de créer de nouvelles pages.",
"sectioneditnotsupported-title": "Modification de section non prise en charge",
"sectioneditnotsupported-text": "La modification d’une section n’est pas prise en charge pour cette page.",
+ "modeleditnotsupported-title": "Modification non supportée",
+ "modeleditnotsupported-text": "La modification n’est pas supportée pour le modèle de contenu $1.",
"permissionserrors": "Erreur de permissions",
"permissionserrorstext": "Vous n'avez pas la permission d'effectuer l'opération demandée pour {{PLURAL:$1|la raison suivante|les raisons suivantes}} :",
"permissionserrorstext-withaction": "Vous ne pouvez pas $2, pour {{PLURAL:$1|la raison suivante|les raisons suivantes}} :",
"content-model-json": "JSON",
"content-json-empty-object": "Objet vide",
"content-json-empty-array": "Tableau vide",
+ "unsupported-content-model": "<strong>Attention :</strong> Le modèle de contenu $1 n’est pas supporté sur ce wiki.",
+ "unsupported-content-diff": "Les diffs ne sont pas supportés pour le modèle de contenu $1.",
+ "unsupported-content-diff2": "Les diffs entre les modèles de contenu $1 et $2 ne sont pas supportés sur ce wiki.",
"deprecated-self-close-category": "Pages utilisant des balises HTML auto-fermantes non valides",
"deprecated-self-close-category-desc": "La page contient des balises HTML auto-fermantes non valides, comme <code><b/></code> ou <code><span/></code>. Le comportement de celles-ci changera prochainement pour être en accord avec la spécification HTML5, donc leur utilisation dans le wikitexte est désuète.",
"duplicate-args-warning": "<strong>Avertissement :</strong> [[:$1]] appelle [[:$2]] avec plus d'une valeur pour le paramètre « $3 ». Seule la dernière valeur fournie sera utilisée.",
"systemblockedtext": "O seu nome de usuario ou enderezo IP foi bloqueado automaticamente polo sistema MediaWiki.\nO motivo do bloqueo é:\n\n:<em>$2</em>\n\n* Comezo do bloqueo: $8\n* Expiración do bloqueo: $6\n* Destinatario do bloqueo: $7\n\nO seu enderezo IP actual é $3.\nPor favor, inclúa todos estes detalles en calquera consulta que realice.",
"blockednoreason": "non se deu ningunha razón",
"blockedtext-composite": "<strong>O seu nome de usuario ou enderezo IP foron bloqueados.</strong>\n\nO motivo dado é:\n\n:<em>$2</em>.\n\n* Comezo do bloqueo: $8\n* Remate do bloqueo máis longo: $6\n\n* $5\n\nO seu enderezo IP actual é $3.\nPor favor, inclúa todos os detalles de arriba en calquera contacto sobre este asunto.",
+ "blockedtext-composite-no-ids": "O seu enderezo IP aparece en múltiples listas negras",
"blockedtext-composite-reason": "Existen varios bloqueos contra a súa conta ou enderezo IP",
"whitelistedittext": "Debe $1 para poder editar páxinas.",
"confirmedittext": "Debe confirmar o correo electrónico antes de comezar a editar. Por favor, configure e dea validez ao correo mediante as súas [[Special:Preferences|preferencias de usuario]].",
"permanentlink": "Ligazón permanente",
"permanentlink-revid": "ID da revisión",
"permanentlink-submit": "Ir á revisión",
+ "newsection": "Nova sección",
+ "newsection-page": "Páxina de destino",
"newsection-submit": "Ir á páxina",
"dberr-problems": "Sentímolo! Este sitio está experimentando dificultades técnicas.",
"dberr-again": "Por favor, agarde uns minutos e logo probe a cargar de novo a páxina.",
"nocreate-loggedin": "Tu non ha le permission de crear nove paginas.",
"sectioneditnotsupported-title": "Modification de sectiones non supportate",
"sectioneditnotsupported-text": "Non es possibile modificar sectiones individual in iste pagina de modification.",
+ "modeleditnotsupported-title": "Modification non supportate",
+ "modeleditnotsupported-text": "Non es possibile modificar contento del modello $1.",
"permissionserrors": "Error de permission",
"permissionserrorstext": "Tu non ha le permission de facer isto, pro le sequente {{PLURAL:$1|motivo|motivos}}:",
"permissionserrorstext-withaction": "Tu non ha le permission de $2, pro le sequente {{PLURAL:$1|motivo|motivos}}:",
"content-model-css": "CSS",
"content-json-empty-object": "Objecto vacue",
"content-json-empty-array": "Array vacue",
+ "unsupported-content-model": "<strong>Attention:</strong> Le modello de contento $1 non es supportate sur iste wiki.",
"deprecated-self-close-category": "Paginas que usa etiquettas HTML auto-claudite non valide",
"deprecated-self-close-category-desc": "Le pagina contine etiquettas HTML auto-claudite non valide, como <code><b/></code> o <code><span/></code>. Le comportamento de istes cambiara proximemente pro esser in accordo con le specification HTML5, dunque lor uso in wikitexto es obsolete.",
"duplicate-args-warning": "<strong>Attention:</strong> [[:$1]] appella [[:$2]] con plure valores pro le parametro \"$3\". Solmente le ultime valor fornite essera usate.",
"monday": "måndag",
"tuesday": "dinsdag",
"wednesday": "woonsdag",
- "thursday": "donderdag",
+ "thursday": "dunderdag",
"friday": "vrydag",
"saturday": "såterdag",
- "sun": "zun",
- "mon": "mao",
- "tue": "die",
+ "sun": "sün",
+ "mon": "mån",
+ "tue": "din",
"wed": "woo",
- "thu": "don",
- "fri": "vrie",
- "sat": "zao",
+ "thu": "dun",
+ "fri": "vry",
+ "sat": "såt",
"january": "januåri",
"february": "februåri",
"march": "määrt",
"october-date": "$1 oktober",
"november-date": "$1 november",
"december-date": "$1 desember",
- "pagecategories": "{{PLURAL:$1|Kategorie|Kategorieën}}",
+ "pagecategories": "{{PLURAL:$1|Kategory|Kategoryen}}",
"category_header": "Artikels in kategorie $1",
"subcategories": "Subkategorieën",
"category-media-header": "Media in kategorie \"$1\"",
"mypage": "Gebrukerszied",
"mytalk": "Myn oaverleg",
"anontalk": "Oaverleg",
- "navigation": "Navigasie",
+ "navigation": "Navigaty",
"and": " en",
"faq": "Vragen die vake esteld wörden",
"actions": "Haandeling",
"namespaces": "Naamrüümdes",
- "variants": "Variaanten",
+ "variants": "Varianten",
"navigation-heading": "Navigasiemenu",
"errorpagetitle": "Foutmelding",
"returnto": "Weerumme naor $1.",
"tagline": "Van {{SITENAME}}",
- "help": "Hulpe",
- "search": "Zeuken",
- "searchbutton": "Zeuken",
+ "help": "Hülpe",
+ "search": "Söken",
+ "searchbutton": "Söken",
"go": "Artikel",
"searcharticle": "Artikel",
"history": "Geschiedenisse",
"viewhelppage": "Hulpzied bekieken",
"categorypage": "Kategoriezied bekieken",
"viewtalkpage": "Bekiek overlegzied",
- "otherlanguages": "Andere talen",
+ "otherlanguages": "Andere språken",
"redirectedfrom": "(deurestuurd vanaof \"$1\")",
"redirectpagesub": "Deurverwieszied",
"redirectto": "Deurverwiezen naor:",
"lastmodifiedat": "Disse syde is et lätst ewysigd up $1 üm $2.",
"viewcount": "Disse zied is $1 {{PLURAL:$1|keer|keer}} bekeken.",
"protectedpage": "Beveiligden zied",
- "jumpto": "Gao naor:",
- "jumptonavigation": "navigasie",
+ "jumpto": "Gå når:",
+ "jumptonavigation": "navigaty",
"jumptosearch": "zeuk",
"view-pool-error": "De servers bin op heden overbelast.\nTe veule gebrukers proberen disse zied te bekieken.\nWacht effen veurda'j opniej toegang proberen te kriegen tot disse zied.\n\n$1",
"generic-pool-error": "De servers bin op heden overbelast.\nTe veule gebrukers proberen disse zied te bekieken.\nWacht effen veurda'j opniej toegang proberen te kriegen tot disse zied.",
"pool-queuefull": "De wachtrie van de poel is vol",
"pool-errorunknown": "Onbekende fout",
"pool-servererror": "De dienst \"pool counter\" is niet beschikbaor ($1).",
- "aboutsite": "Over {{SITENAME}}",
+ "aboutsite": "Oaver {{SITENAME}}",
"aboutpage": "Project:Info",
"copyright": "De inhoud is beschikbaor onder de $1 as der niks aanders an-egeven is.",
"copyrightpage": "{{ns:project}}:Auteursrechten",
- "currentevents": "In t niejs",
- "currentevents-url": "Project:In t niejs",
- "disclaimers": "Veurbehold",
- "disclaimerpage": "Project:Veurbehoud",
+ "currentevents": "In et nys",
+ "currentevents-url": "Project:In et nys",
+ "disclaimers": "Vöärbehold",
+ "disclaimerpage": "Project:Vöärbehold",
"edithelp": "Hulpe mit bewarken",
"helppage-top-gethelp": "Hulpe",
"mainpage": "Vöärblad",
"mainpage-description": "Vöärblad",
"policy-url": "Project:Beleid",
- "portal": "Gebrukersportål",
- "portal-url": "Project:Gebrukersportaol",
- "privacy": "Gegevensbeleid",
- "privacypage": "Project:Gegevensbeleid",
+ "portal": "Gemeynskapsportaal",
+ "portal-url": "Project:Gemeynskapsportaal",
+ "privacy": "Gegeavensbeleid",
+ "privacypage": "Project:Gegeavensbeleid",
"badaccess": "Gien toestemming",
"badaccess-group0": "Je hebben gien toestemming um disse aksie uut te voeren.",
"badaccess-groups": "Disse aksie kan allinnig uutevoerd wörden deur gebrukers uut {{PLURAL:$2|de groep|één van de groepen}}: $1.",
"site-atom-feed": "$1 Atom-voer",
"page-rss-feed": "\"$1\" RSS-voer",
"page-atom-feed": "\"$1\" Atom-voer",
- "red-link-title": "$1 (zied besteet nog niet)",
+ "red-link-title": "$1 (syde besteyt noch neet)",
"sort-descending": "Aoflopend sorteren",
"sort-ascending": "Oplopend sorteren",
"nstab-main": "Artikel",
"newpageletter": "N",
"boteditletter": "B",
"unpatrolledletter": "!",
- "rc-change-size-new": "$1 {{PLURAL:$1|byte|bytes}} nao de wieziging",
+ "rc-change-size-new": "$1 {{PLURAL:$1|byte|bytes}} nå de wysiging",
"newsectionsummary": "Ny underwarp: /* $1 */",
"rc-enhanced-expand": "Details bekieken",
"rc-enhanced-hide": "Details verbargen",
"confirm": "Bevestigen",
"excontent": "De tekste was: '$1'",
"excontentauthor": "De tekste was: '$1' (zied an-emaakt deur: [[Special:Contributions/$2|$2]])",
- "exbeforeblank": "veurdat disse zied leegemaakt wörden stung hier: '$1'",
+ "exbeforeblank": "vöärdat disse syde leadigmaked wördde stünd hyr: '$1'",
"delete-confirm": "\"$1\" vortdoon",
"delete-legend": "Vortdoon",
"historywarning": "'''Waorschuwing''': de zied die'j vortdoon willen, hef $1 {{PLURAL:$1|versie|versies}}:",
"tooltip-invert": "Vink dit vakjen an um wiezigingen an ziejen binnen de ekeuzen naamruumte te verbargen (en de biebeheurende naamruumte as dat an-evinkt is)",
"namespace_association": "Naamruumte die hieran ekoppeld is",
"tooltip-namespace_association": "Vink dit vakjen an um oek de overlegnaamruumte, of in t ummekeren geval de naamruumte zelf, derbie te doon die bie disse naamruumte heurt.",
- "blanknamespace": "(Heufdnaamruumte)",
+ "blanknamespace": "(Höyvdnaamrüümde)",
"contributions": "{{GENDER:$1|Gebrukersbydragen}}",
"contributions-title": "Biedragen van $1",
"mycontris": "Myn bydragen",
"tooltip-pt-preferences": "{{GENDER:|Miene}} vuurkeuren",
"tooltip-pt-watchlist": "Lieste van zieden die op miene volglieste stoan",
"tooltip-pt-mycontris": "Oaverzicht van {{GENDER:|oew}} biejdreagen",
- "tooltip-pt-login": "Iej wördt van harte oetneugd um oe an te melden as gebroeker, mer t is nich verplicht",
+ "tooltip-pt-login": "Y wördt van harte uutnöygd üm u an te melden as gebruker, mär et is nich verplicht",
"tooltip-pt-logout": "Ofmaelden",
- "tooltip-pt-createaccount": "Schrief je eigen veural in en meld je an, mer t is niet verplicht.",
- "tooltip-ca-talk": "Loat n oaverlegtekst oaver disse ziede zeen",
- "tooltip-ca-edit": "Beweark disse ziede",
+ "tooltip-pt-createaccount": "Skryv juw eigen vöäral in en meld juw eigen an. Dit is lykewels neet verplicht.",
+ "tooltip-ca-talk": "Låt een oaverlegtekst oaver disse syde seen",
+ "tooltip-ca-edit": "Beweark disse syde",
"tooltip-ca-addsection": "Niej oonderwaerp tovogen",
"tooltip-ca-viewsource": "Disse ziede is beveiligd taegen veraanderen. Iej könt wal kieken noar de ziede",
- "tooltip-ca-history": "Oaldere versies van disse ziede",
+ "tooltip-ca-history": "Oldere versys van disse syde",
"tooltip-ca-protect": "Beveilig disse ziede taegen veraanderen",
"tooltip-ca-unprotect": "De beveiliging vuur disse ziede wiezigen",
"tooltip-ca-delete": "Smiet disse ziede vort",
"tooltip-ca-move": "Gef disse ziede nen aanderen titel",
"tooltip-ca-watch": "Voog disse ziede to an oewe volglieste",
"tooltip-ca-unwatch": "Smiet disse ziede van oewe voalglieste",
- "tooltip-search": "{{SITENAME}} duurzeuken",
- "tooltip-search-go": "Noar n ziede mit disse naam goan as t besteet",
- "tooltip-search-fulltext": "Zeuk noar zieden woar disse tekst in steet",
- "tooltip-p-logo": "Goa noar t vuurblad",
+ "tooltip-search": "{{SITENAME}} döärsöken",
+ "tooltip-search-go": "Når een syde mid disse name gån as et besteyt",
+ "tooltip-search-fulltext": "Söök når syden wår disse tekst in steyt",
+ "tooltip-p-logo": "Gå når et vöärblad",
"tooltip-n-mainpage": "Goa noar t vuurblad",
- "tooltip-n-mainpage-description": "Goa noar t vuurblad",
- "tooltip-n-portal": "Informoasie oaver t projekt: wel, wat, ho en woarum",
- "tooltip-n-currentevents": "Achtergroondinformoasie oaver dinge in t niejs",
- "tooltip-n-recentchanges": "Lieste van pas verrichte veraanderingen",
- "tooltip-n-randompage": "Loat ne willekeurige ziede zeen",
- "tooltip-n-help": "Hölpinformoasie oaver {{SITENAME}}",
- "tooltip-t-whatlinkshere": "Lieste van alle zieden die hiernoar verwiezen",
- "tooltip-t-recentchangeslinked": "Pas verrichte veraanderingen die noar disse ziede verwiezen",
+ "tooltip-n-mainpage-description": "Gå når et vöärblad",
+ "tooltip-n-portal": "Informaty oaver et projekt: wel, wat, ho en wårümme",
+ "tooltip-n-currentevents": "Achtergrundinformaty oaver dinge in et nys",
+ "tooltip-n-recentchanges": "Lyste van pas verrichte veranderingen",
+ "tooltip-n-randompage": "Låt ne willeköärige syde seen",
+ "tooltip-n-help": "Hülpinformaty oaver {{SITENAME}}",
+ "tooltip-t-whatlinkshere": "Lyste van alle syden dee når disse syde verwysen",
+ "tooltip-t-recentchangeslinked": "Pas verrichte veranderingen dee når disse syde verwysen",
"tooltip-feed-rss": "RSS-voer vuur disse ziede",
"tooltip-feed-atom": "Atom-voer vuur disse ziede",
"tooltip-t-contributions": "Lieste met biejdreagen van {{GENDER:$1|disse gebroeker}}",
"tooltip-t-emailuser": "Stüür disse {{GENDER:$1|gebruker}} een netpostbericht",
"tooltip-t-info": "Meer informasie over disse zied",
- "tooltip-t-upload": "Laad ofbeeldingen en/of geluudsmateriaal",
+ "tooltip-t-upload": "Laad afbealdingen en/of gelüüdsmateriaal",
"tooltip-t-specialpages": "Lieste van alle biejzeundere zieden",
- "tooltip-t-print": "De ofdrukboare versie van disse ziede",
- "tooltip-t-permalink": "Verbeending vuur altied noar de versie van disse ziede van vandaag-an-n-dag",
- "tooltip-ca-nstab-main": "Loat n tekst van t artikel zeen",
+ "tooltip-t-print": "De afdrükbåre versy van disse syde",
+ "tooltip-t-permalink": "Permanente verwysing når disse versy van de syde",
+ "tooltip-ca-nstab-main": "Låt een tekst van et artikel seen",
"tooltip-ca-nstab-user": "Loat de gebroekersbladziede zeen",
"tooltip-ca-nstab-media": "Loat n mediatekst zeen",
"tooltip-ca-nstab-special": "Dit is ne biejzeundere ziede die'j nich könt veraanderen",
"table_pager_limit_label": "Zaken per zied:",
"table_pager_limit_submit": "Zeuk",
"table_pager_empty": "Gien resultaoten",
- "autosumm-blank": "Zied leegemaakt",
+ "autosumm-blank": "Syde leadigmaked",
"autosumm-replace": "Tekste vervöngen deur '$1'",
"autoredircomment": "döärverwysing når [[$1]]",
"autosumm-changed-redirect-target": "Döärverwysingsdool ewysigd van [[$1]] når [[$2]]",
"tag-mw-new-redirect": "Nye döärverwysing",
"tag-mw-removed-redirect": "Döärverwysing vordedån",
"tag-mw-changed-redirect-target": "Döärverwysingsdool ewysigd",
+ "tag-mw-blank": "Leadigmaked",
"tags-title": "Etiket",
"tags-intro": "Op disse zied staon de etiketten waormee de programmatuur elke bewarking kan markeren, en de betekenisse dervan.",
"tags-tag": "Etiketnaam",
"mostinterwikis": "ߞߐߜߍ ߡߍ߲ ߠߎ߬ ߦߋ߫ ߥߞߌ ߣߌ߫ ߢߐ߲ߕߍ ߝߊ߲߬ߓߊ ߘߐ߫",
"mostrevisions": "ߟߢߊ߬ߟߌ߬ ߦߙߌߞߊ߫ ߦߋ߫ ߞߐߜߍ ߡߍ߲ ߠߎ߬ ߘߐ߫",
"prefixindex": "ߞߐߜߍ ߡߍ߲ ߠߎ߬ ߓߍ߯ ߟߊߝߟߐߣߍ߲߫",
+ "prefixindex-namespace": "ߢߍߣߙߊ ߦߋ߫ ߞߐߜߍ ߡߍ߲ ߓߍ߯ ߟߊ߫ ($1 ߕߐ߯ߛߓߍ ߞߣߍ)",
"prefixindex-submit": "ߊ߬ ߦߌ߬ߘߊ߬",
+ "prefixindex-strip": "ߢߍߣߙߊ ߟߎ߬ ߢߡߊߘߏ߲߰ ߞߐߝߟߌ ߟߎ߬ ߘߐ߫.",
"shortpages": "ߞߐߜߍ߫ ߛߎߘߎ߲ ߠߎ߬",
"longpages": "ߞߐߜߍ߫ ߖߊ߲ ߠߎ߬",
+ "deadendpages": "ߞߐߜߍ߫ ߘߏ߲߬ߘߊ߬ߒߕߊ߲ ߠߎ߬",
"deadendpagestext": "ߓߌ߬ߟߊ߬ߢߐ߲߰ߡߊ߬ ߕߍ߫ ߞߐߜߍ ߢߌ߲߬ ߠߎ߬ ߣߌ߫ ߞߐߜߍ ߕߐ߭ ߟߎ߬ ߕߍ߫ {{SITENAME}} ߘߐ߫.",
"protectedpages": "ߞߐߜߍ߫ ߟߊߞߊ߲ߘߊߣߍ߲ ߠߎ߬",
"protectedpages-filters": "ߛߍ߲ߛߍ߲ߟߊ߲ ߠߎ߬:",
+ "protectedpages-indef": "ߟߊ߬ߞߊ߲߬ߘߊ߬ߟߌ ߘߊ߲߬ߠߊߕߍ߰ߓߊߟߌ ߘߐߙߐ߲߫",
+ "protectedpages-noredirect": "ߟߊ߬ߞߎ߲߬ߛߌ߲߬ߠߌ߲ ߢߡߊߘߏ߲߰",
"protectedpages-timestamp": "ߕߎ߬ߡߊ ߓߊ߬ߘߌ߬ߟߊ߲",
"protectedpages-page": "ߞߐߜߍ",
"protectedpages-expiry": "ߊ߬ ߛߕߊ ߓߘߊ߫ ߝߊ߫",
"sessionfailure": "Wydaje się, że wystąpił błąd z Twoją sesją zalogowania;\nto działanie zostało anulowane, aby uniknąć przechwycenia sesji.\nPrześlij formularz jeszcze raz.",
"changecontentmodel": "Edycja modelu zawartości strony",
"changecontentmodel-legend": "Zmienić model zawartości",
- "changecontentmodel-title-label": "Tytuł strony",
+ "changecontentmodel-title-label": "Tytuł strony:",
"changecontentmodel-current-label": "Obecny model zawartości:",
- "changecontentmodel-model-label": "Nowy model zawartości",
+ "changecontentmodel-model-label": "Nowy model zawartości:",
"changecontentmodel-reason-label": "Powód:",
"changecontentmodel-submit": "Zmień",
"changecontentmodel-success-title": "Model zawartości został zmieniony",
"nocreate-loggedin": "Você não possui permissão para criar novas páginas.",
"sectioneditnotsupported-title": "Edição por seções não suportada",
"sectioneditnotsupported-text": "Edição por seções não suportada nesta página.",
+ "modeleditnotsupported-title": "Edição não suportada",
+ "modeleditnotsupported-text": "A edição não é suportada para o modelo de conteúdo $1.",
"permissionserrors": "Erro de permissão",
"permissionserrorstext": "Você não possui permissão de fazer isso, {{PLURAL:$1|pelo seguinte motivo|pelos seguintes motivos}}:",
"permissionserrorstext-withaction": "Você não possui permissão para $2, {{PLURAL:$1|pelo seguinte motivo|pelos motivos a seguir}}:",
"content-model-json": "JSON",
"content-json-empty-object": "Objeto vazio",
"content-json-empty-array": "Array vazia",
+ "unsupported-content-model": "<strong>Aviso:</strong> O modelo de conteúdo $1 não é suportado nessa wiki.",
+ "unsupported-content-diff": "Diffs não são suportados para o modelo de conteúdo $1.",
+ "unsupported-content-diff2": "Diferenças entre os modelos de conteúdo $1 e $2 não são suportadas nessa wiki.",
"deprecated-self-close-category": "Páginas com etiquetas HTML de autofechamento não válidas",
"deprecated-self-close-category-desc": "A página contém tags HTML auto-fechadas inválidas, como <code><b/></code> ou <code><span/></code>. O comportamento destas mudará em breve para coincidam com as especificações do HTML5, pelo que seu uso no wikitext está obsoleto.",
"duplicate-args-warning": "<strong> Aviso: </strong> [[:$1]] está chamando [[:$2]] com mais de um valor para o parâmetro \"$3\". Será utilizado apenas o último valor fornecido.",
"nocreate-loggedin": "У вас нет разрешения создавать новые страницы.",
"sectioneditnotsupported-title": "Редактирование разделов не поддерживается",
"sectioneditnotsupported-text": "На этой странице не поддерживается редактирование разделов",
+ "modeleditnotsupported-title": "Редактирование не поддерживается",
+ "modeleditnotsupported-text": "Редактирование не поддерживается моделью содержимого $1.",
"permissionserrors": "Ошибка прав доступа",
"permissionserrorstext": "У вас нет прав на выполнение этой операции по {{PLURAL:$1|1=следующей причине|следующим причинам}}:",
"permissionserrorstext-withaction": "У вас нет прав на выполнение действия «$2» по {{PLURAL:$1|1=следующей причине|следующим причинам}}:",
"content-model-json": "JSON",
"content-json-empty-object": "Пустой объект",
"content-json-empty-array": "Пустой массив",
+ "unsupported-content-model": "<strong>Внимание:</strong> Модель содержимого $1 не поддерживается на этой вики.",
+ "unsupported-content-diff": "Изменения (различия) не поддерживаются моделью содержимого $1.",
+ "unsupported-content-diff2": "Изменения (различия) между моделями содержимого $1 и $2 не поддерживаются на этой вики.",
"deprecated-self-close-category": "Страницы, использующие недопустимые самозакрывающиеся HTML-теги",
"deprecated-self-close-category-desc": "Страница содержит недопустимые самозакрывающиеся HTML-теги, такие как <code><b/></code> или <code><span/></code>. В скором времени их действие изменится, чтобы соответствовать спецификации HTML5, так что использование этих устаревших тегов в вики-тексте нежелательно.",
"duplicate-args-warning": "<strong>Внимание:</strong> [[:$1]] вызывает [[:$2]] с более чем одним значением параметра «$3». Будет использовано только последнее указанное значение.",
"nocreate-loggedin": "Nemate dopuštenje da kreirate nove stranice.",
"sectioneditnotsupported-title": "Uređivanje sekcije nije podržano",
"sectioneditnotsupported-text": "Uređivanje sekcije nije podržano na ovoj stranici.",
+ "modeleditnotsupported-title": "Uređivanje nije podržano",
+ "modeleditnotsupported-text": "Uređivanje nije podržano za sadržajni model $1.",
"permissionserrors": "Greška pri odobrenju",
"permissionserrorstext": "Nemate dopuštenje da to uradite, iz {{PLURAL:$1|slijedećeg razloga|slijedećih razloga}}:",
"permissionserrorstext-withaction": "Nemate dozvolu za $2, zbog {{PLURAL:$1|sljedećeg|sljedećih}} razloga:",
"content-model-css": "CSS",
"content-json-empty-object": "Prazan objekat",
"content-json-empty-array": "Prazan niz",
+ "unsupported-content-model": "<strong>Upozorenje:</strong> Sadržajni model $1 nije podržan na ovom wikiju.",
+ "unsupported-content-diff": "Razlike nisu podržane za sadržajni model $1.",
+ "unsupported-content-diff2": "Razlike između sadržajnih modela $1 i $2 nisu podržane na ovom wikiju.",
"deprecated-self-close-category": "Stranice s neispravnim samozatvorenim HTML oznakama",
"deprecated-self-close-category-desc": "Stranica sadrži neispravne samozatvorene HTML oznake, kao što su <code><b/></code> ili <code><span/></code>. Njihovo funkcioniranje uskoro će se promijeniti da bude u skladu sa specifikacijama za HTML5. Ovo znači da su zastarjeli i ne bi se trebali upotrebljavati u wikitekstu.",
"duplicate-args-warning": "<strong>Upozorenje:</strong> [[:$1]] poziva na [[:$2]] sa više od jedne vrijednosti za parametar \"$3\". Koristit će se samo posljednja navedena vrijednost.",
"logentry-contentmodel-change-revert": "還原",
"protectlogpage": "保護日誌",
"protectlogtext": "以下為變更頁面保護的清單。\n請參考 [[Special:ProtectedPages|受保護頁面清單]] 檢視目前受保護頁面。",
- "protectedarticle": "已保護 \"[[$1]]\"",
+ "protectedarticle": "已保護「[[$1]]」",
"modifiedarticleprotection": "已變更 \"[[$1]]\" 的保護層級",
"unprotectedarticle": "已解除「[[$1]]」的保護",
"movedarticleprotection": "已移動 \"[[$2]]\" 的保護設定至 \"[[$1]]\"",
"import-interwiki-submit": "匯入",
"import-mapping-default": "匯入至預設位置",
"import-mapping-namespace": "匯入至命名空間:",
- "import-mapping-subpage": "匯入做為以下頁面的子頁面:",
+ "import-mapping-subpage": "匯入作爲以下頁面的子頁面:",
"import-upload-filename": "檔案名稱:",
"import-upload-username-prefix": "跨 wiki 字首:",
"import-assign-known-users": "分配編輯至所命名使用者已存在本地的本地使用者",
$this->fatalError( "Error: Closures cannot be converted to JSON. " .
"Please move your extension function somewhere else."
);
- }
- // check if $func exists in the global scope
- if ( function_exists( $func ) ) {
- // @phan-suppress-next-next-line PhanTypeSuspiciousStringExpression
+ } elseif ( function_exists( $func ) ) {
+ // check if $func exists in the global scope
$this->fatalError( "Error: Global functions cannot be converted to JSON. " .
"Please move your extension function ($func) into a class."
);
$this->fatalError( "Error: Closures cannot be converted to JSON. " .
"Please move the handler for $hookName somewhere else."
);
- }
- // Check if $func exists in the global scope
- if ( function_exists( $func ) ) {
+ } elseif ( function_exists( $func ) ) {
+ // Check if $func exists in the global scope
$this->fatalError( "Error: Global functions cannot be converted to JSON. " .
"Please move the handler for $hookName inside a class."
);
// backends in FileBackendMultiWrite (since they get writes second, they have
// higher timestamps). However, when copying the other way, this hits loads of
// false positives (possibly 100%) and wastes a bunch of time on GETs/PUTs.
+ // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
$same = ( $srcStat['mtime'] <= $dstStat['mtime'] );
} else {
// This is the slowest method which does many per-file HEADs (unless an object
/**
* A resource pointing to a sitemap file
*
- * @var resource
+ * @var resource|false
*/
public $file;
public $uploads = false;
protected $uploadCount = 0;
public $imageBasePath = false;
+ /** @var array|false */
public $nsFilter = false;
function __construct() {
protected $bufferSize = 524288; // In bytes. Maximum size to read from the stub in on go.
- protected $php = "php";
+ /** @var array */
+ protected $php = [];
protected $spawn = false;
/**
protected $spawnProc = false;
/**
- * @var bool|resource
+ * @var resource
*/
- protected $spawnWrite = false;
+ protected $spawnWrite;
/**
- * @var bool|resource
+ * @var resource
*/
- protected $spawnRead = false;
+ protected $spawnRead;
/**
* @var bool|resource
/**
* @throws MWException Failure to parse XML input
- * @param string $input
+ * @param resource $input
* @return bool
*/
function readDump( $input ) {
if ( $this->spawnRead ) {
fclose( $this->spawnRead );
}
- $this->spawnRead = false;
+ $this->spawnRead = null;
if ( $this->spawnWrite ) {
fclose( $this->spawnWrite );
}
- $this->spawnWrite = false;
+ $this->spawnWrite = null;
if ( $this->spawnErr ) {
fclose( $this->spawnErr );
}
/**
* @param MediaWiki\Revision\RevisionStore $revStore
- * @param string $emptySha1
* @return int
*/
protected function doSha1LegacyUpdates( $revStore ) {
<?php
/**
- * Purge all languages from the message cache.
- *
* 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
* @ingroup Maintenance
*/
+use MediaWiki\MediaWikiServices;
+
require_once __DIR__ . '/Maintenance.php';
/**
- * Maintenance script that purges all languages from the message cache.
+ * Maintenance script that purges cache used by MessageCache.
*
* @ingroup Maintenance
*/
class RebuildMessages extends Maintenance {
public function __construct() {
parent::__construct();
- $this->addDescription( 'Purge all language messages from the cache' );
+ $this->addDescription( 'Purge the MessageCache for all interface languages.' );
}
public function execute() {
- global $wgLocalDatabases, $wgDBname, $wgEnableSidebarCache, $messageMemc;
- if ( $wgLocalDatabases ) {
- $databases = $wgLocalDatabases;
- } else {
- $databases = [ $wgDBname ];
- }
-
- foreach ( $databases as $db ) {
- $this->output( "Deleting message cache for {$db}... " );
- $messageMemc->delete( "{$db}:messages" );
- if ( $wgEnableSidebarCache ) {
- $messageMemc->delete( "{$db}:sidebar" );
- }
- $this->output( "Deleted\n" );
- }
+ $this->output( "Purging message cache for all languages on this wiki... " );
+ $messageCache = MediaWikiServices::getInstance()->getMessageCache();
+ $messageCache->clear();
+ $this->output( "Done\n" );
}
}
/** @var RecompressTracked */
public $parent;
public $blobClass;
- /** @var ConcatenatedGzipHistoryBlob */
+ /** @var ConcatenatedGzipHistoryBlob|false */
public $cgz;
public $referrers;
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Message;
+
+use MediaWiki\Message\TextFormatter;
+use MediaWikiTestCase;
+use Message;
+use Wikimedia\Message\MessageValue;
+use Wikimedia\Message\ParamType;
+use Wikimedia\Message\TextParam;
+
+/**
+ * @covers \MediaWiki\Message\TextFormatter
+ * @covers \Wikimedia\Message\MessageValue
+ * @covers \Wikimedia\Message\ListParam
+ * @covers \Wikimedia\Message\TextParam
+ * @covers \Wikimedia\Message\MessageParam
+ */
+class TextFormatterTest extends MediaWikiTestCase {
+ private function createTextFormatter( $langCode ) {
+ return new class( $langCode ) extends TextFormatter {
+ public function __construct( $langCode ) {
+ parent::__construct( $langCode );
+ }
+
+ protected function createMessage( $key ) {
+ return new FakeMessage( $key );
+ }
+ };
+ }
+
+ public function testGetLangCode() {
+ $formatter = $this->createTextFormatter( 'fr' );
+ $this->assertSame( 'fr', $formatter->getLangCode() );
+ }
+
+ public function testFormatBitrate() {
+ $formatter = $this->createTextFormatter( 'en' );
+ $mv = ( new MessageValue( 'test' ) )->bitrateParams( 100, 200 );
+ $result = $formatter->format( $mv );
+ $this->assertSame( 'test 100 bps 200 bps', $result );
+ }
+
+ public function testFormatList() {
+ $formatter = $this->createTextFormatter( 'en' );
+ $mv = ( new MessageValue( 'test' ) )->commaListParams( [
+ 'a',
+ new TextParam( ParamType::BITRATE, 100 ),
+ ] );
+ $result = $formatter->format( $mv );
+ $this->assertSame( 'test a, 100 bps $2', $result );
+ }
+}
+
+class FakeMessage extends Message {
+ public function fetchMessage() {
+ return "{$this->getKey()} $1 $2";
+ }
+}
use MediaWiki\Block\BlockRestrictionStore;
use MediaWiki\Block\CompositeBlock;
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Block\Restriction\PageRestriction;
use MediaWiki\Block\Restriction\NamespaceRestriction;
use MediaWiki\Block\SystemBlock;
private function getPartialBlocks() {
$sysopId = $this->getTestSysop()->getUser()->getId();
- $userBlock = new Block( [
+ $userBlock = new DatabaseBlock( [
'address' => $this->getTestUser()->getUser(),
'by' => $sysopId,
'sitewide' => false,
] );
- $ipBlock = new Block( [
+ $ipBlock = new DatabaseBlock( [
'address' => '127.0.0.1',
'by' => $sysopId,
'sitewide' => false,
return [
'Sitewide block and partial block' => [
[
- new Block( [
+ new DatabaseBlock( [
'sitewide' => false,
'blockEmail' => true,
'allowUsertalk' => true,
] ),
- new Block( [
+ new DatabaseBlock( [
'sitewide' => true,
'blockEmail' => false,
'allowUsertalk' => false,
],
'Partial block and system block' => [
[
- new Block( [
+ new DatabaseBlock( [
'sitewide' => false,
'blockEmail' => true,
'allowUsertalk' => false,
],
'System block and user name hiding block' => [
[
- new Block( [
+ new DatabaseBlock( [
'hideName' => true,
'sitewide' => true,
'blockEmail' => true,
],
'Two lenient partial blocks' => [
[
- new Block( [
+ new DatabaseBlock( [
'sitewide' => false,
'blockEmail' => false,
'allowUsertalk' => true,
] ),
- new Block( [
+ new DatabaseBlock( [
'sitewide' => false,
'blockEmail' => false,
'allowUsertalk' => true,
return [
'Read is not blocked' => [
[
- new Block(),
- new Block(),
+ new DatabaseBlock(),
+ new DatabaseBlock(),
],
'read',
false,
],
'Email is blocked if blocked by any blocks' => [
[
- new Block( [
+ new DatabaseBlock( [
'blockEmail' => true,
] ),
- new Block( [
+ new DatabaseBlock( [
'blockEmail' => false,
] ),
],
}
}
+ public function testHashRingSingleLocation() {
+ // SHA-1 based and weighted
+ $ring = new HashRing( [ 's1' => 1 ], 'sha1' );
+
+ $this->assertEquals(
+ [ 's1' => 1 ],
+ $ring->getLocationWeights(),
+ 'Normalized location weights'
+ );
+
+ for ( $i = 0; $i < 5; $i++ ) {
+ $this->assertEquals(
+ 's1',
+ $ring->getLocation( "hello$i" ),
+ 'Items placed at proper locations'
+ );
+ $this->assertEquals(
+ [ 's1' ],
+ $ring->getLocations( "hello$i", 2 ),
+ 'Items placed at proper locations'
+ );
+ }
+
+ $this->assertEquals( [], $ring->getLocations( "helloX", 0 ), "Limit of 0" );
+ }
+
public function testHashRingMapping() {
// SHA-1 based and weighted
$ring = new HashRing(
--- /dev/null
+<?php
+
+namespace Wikimedia\Tests\Message;
+
+use Wikimedia\Message\ListType;
+use Wikimedia\Message\MessageValue;
+use Wikimedia\Message\ParamType;
+use Wikimedia\Message\TextParam;
+use MediaWikiTestCase;
+
+/**
+ * @covers \Wikimedia\Message\MessageValue
+ * @covers \Wikimedia\Message\ListParam
+ * @covers \Wikimedia\Message\TextParam
+ * @covers \Wikimedia\Message\MessageParam
+ */
+class MessageValueTest extends MediaWikiTestCase {
+ public static function provideConstruct() {
+ return [
+ [
+ [],
+ '<message key="key"></message>',
+ ],
+ [
+ [ 'a' ],
+ '<message key="key"><text>a</text></message>'
+ ],
+ [
+ [ new TextParam( ParamType::BITRATE, 100 ) ],
+ '<message key="key"><bitrate>100</bitrate></message>'
+ ],
+ ];
+ }
+
+ /** @dataProvider provideConstruct */
+ public function testConstruct( $input, $expected ) {
+ $mv = new MessageValue( 'key', $input );
+ $this->assertSame( $expected, $mv->dump() );
+ }
+
+ public function testGetKey() {
+ $mv = new MessageValue( 'key' );
+ $this->assertSame( 'key', $mv->getKey() );
+ }
+
+ public function testParams() {
+ $mv = new MessageValue( 'key' );
+ $mv->params( 1, 'x' );
+ $mv2 = $mv->params( new TextParam( ParamType::BITRATE, 100 ) );
+ $this->assertSame(
+ '<message key="key"><text>1</text><text>x</text><bitrate>100</bitrate></message>',
+ $mv->dump() );
+ $this->assertSame( $mv, $mv2 );
+ }
+
+ public function testTextParamsOfType() {
+ $mv = new MessageValue( 'key' );
+ $mv2 = $mv->textParamsOfType( ParamType::BITRATE, 1, 2 );
+ $this->assertSame( '<message key="key">' .
+ '<bitrate>1</bitrate><bitrate>2</bitrate>' .
+ '</message>',
+ $mv->dump() );
+ $this->assertSame( $mv, $mv2 );
+ }
+
+ public function testListParamsOfType() {
+ $mv = new MessageValue( 'key' );
+ $mv2 = $mv->listParamsOfType( ListType::COMMA, [ 'a' ], [ 'b', 'c' ] );
+ $this->assertSame( '<message key="key">' .
+ '<list listType="comma"><text>a</text></list>' .
+ '<list listType="comma"><text>b</text><text>c</text></list>' .
+ '</message>',
+ $mv->dump() );
+ $this->assertSame( $mv, $mv2 );
+ }
+
+ public function testTextParams() {
+ $mv = new MessageValue( 'key' );
+ $mv2 = $mv->textParams( 'a', 'b' );
+ $this->assertSame( '<message key="key">' .
+ '<text>a</text>' .
+ '<text>b</text>' .
+ '</message>',
+ $mv->dump() );
+ $this->assertSame( $mv, $mv2 );
+ }
+
+ public function testNumParams() {
+ $mv = new MessageValue( 'key' );
+ $mv2 = $mv->numParams( 1, 2 );
+ $this->assertSame( '<message key="key">' .
+ '<num>1</num>' .
+ '<num>2</num>' .
+ '</message>',
+ $mv->dump() );
+ $this->assertSame( $mv, $mv2 );
+ }
+
+ public function testLongDurationParams() {
+ $mv = new MessageValue( 'key' );
+ $mv2 = $mv->longDurationParams( 1, 2 );
+ $this->assertSame( '<message key="key">' .
+ '<duration>1</duration>' .
+ '<duration>2</duration>' .
+ '</message>',
+ $mv->dump() );
+ $this->assertSame( $mv, $mv2 );
+ }
+
+ public function testShortDurationParams() {
+ $mv = new MessageValue( 'key' );
+ $mv2 = $mv->shortDurationParams( 1, 2 );
+ $this->assertSame( '<message key="key">' .
+ '<timeperiod>1</timeperiod>' .
+ '<timeperiod>2</timeperiod>' .
+ '</message>',
+ $mv->dump() );
+ $this->assertSame( $mv, $mv2 );
+ }
+
+ public function testExpiryParams() {
+ $mv = new MessageValue( 'key' );
+ $mv2 = $mv->expiryParams( 1, 2 );
+ $this->assertSame( '<message key="key">' .
+ '<expiry>1</expiry>' .
+ '<expiry>2</expiry>' .
+ '</message>',
+ $mv->dump() );
+ $this->assertSame( $mv, $mv2 );
+ }
+
+ public function testSizeParams() {
+ $mv = new MessageValue( 'key' );
+ $mv2 = $mv->sizeParams( 1, 2 );
+ $this->assertSame( '<message key="key">' .
+ '<size>1</size>' .
+ '<size>2</size>' .
+ '</message>',
+ $mv->dump() );
+ $this->assertSame( $mv, $mv2 );
+ }
+
+ public function testBitrateParams() {
+ $mv = new MessageValue( 'key' );
+ $mv2 = $mv->bitrateParams( 1, 2 );
+ $this->assertSame( '<message key="key">' .
+ '<bitrate>1</bitrate>' .
+ '<bitrate>2</bitrate>' .
+ '</message>',
+ $mv->dump() );
+ $this->assertSame( $mv, $mv2 );
+ }
+
+ public function testRawParams() {
+ $mv = new MessageValue( 'key' );
+ $mv2 = $mv->rawParams( 1, 2 );
+ $this->assertSame( '<message key="key">' .
+ '<raw>1</raw>' .
+ '<raw>2</raw>' .
+ '</message>',
+ $mv->dump() );
+ $this->assertSame( $mv, $mv2 );
+ }
+
+ public function testPlaintextParams() {
+ $mv = new MessageValue( 'key' );
+ $mv2 = $mv->plaintextParams( 1, 2 );
+ $this->assertSame( '<message key="key">' .
+ '<plaintext>1</plaintext>' .
+ '<plaintext>2</plaintext>' .
+ '</message>',
+ $mv->dump() );
+ $this->assertSame( $mv, $mv2 );
+ }
+
+ public function testCommaListParams() {
+ $mv = new MessageValue( 'key' );
+ $mv2 = $mv->commaListParams( [ 'a', 'b' ] );
+ $this->assertSame( '<message key="key">' .
+ '<list listType="comma">' .
+ '<text>a</text><text>b</text>' .
+ '</list></message>',
+ $mv->dump() );
+ $this->assertSame( $mv, $mv2 );
+ }
+
+ public function tesSemicolonListParams() {
+ $mv = new MessageValue( 'key' );
+ $mv2 = $mv->semicolonListParams( [ 'a', 'b' ] );
+ $this->assertSame( '<message key="key">' .
+ '<list listType="semicolon">' .
+ '<text>a</text><text>b</text>' .
+ '</list></message>',
+ $mv->dump() );
+ $this->assertSame( $mv, $mv2 );
+ }
+
+ public function testPipeListParams() {
+ $mv = new MessageValue( 'key' );
+ $mv2 = $mv->pipeListParams( [ 'a', 'b' ] );
+ $this->assertSame( '<message key="key">' .
+ '<list listType="pipe">' .
+ '<text>a</text><text>b</text>' .
+ '</list></message>',
+ $mv->dump() );
+ $this->assertSame( $mv, $mv2 );
+ }
+
+ public function testTextListParams() {
+ $mv = new MessageValue( 'key' );
+ $mv2 = $mv->textListParams( [ 'a', 'b' ] );
+ $this->assertSame( '<message key="key">' .
+ '<list listType="text">' .
+ '<text>a</text><text>b</text>' .
+ '</list></message>',
+ $mv->dump() );
+ $this->assertSame( $mv, $mv2 );
+ }
+}
}
protected function setUp() {
- global $IP, $messageMemc, $wgMemc, $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory,
+ global $IP, $wgMemc, $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory,
$wgParserCacheType, $wgNamespaceAliases, $wgNamespaceProtection;
$tmpDir = $this->getNewTempDirectory();
$wgParserCacheType = CACHE_NONE;
DeferredUpdates::clearPendingUpdates();
$wgMemc = ObjectCache::getLocalClusterInstance();
- $messageMemc = wfGetMessageCacheStorage();
RequestContext::resetMain();
$context = RequestContext::getMain();