"OO": false
},
"rules": {
- "dot-notation": 0
+ "dot-notation": 0,
+ "max-len": 0
}
}
<exclude name="MediaWiki.Commenting.FunctionComment.MissingParamTag" />
<exclude name="MediaWiki.Commenting.FunctionComment.MissingReturn" />
<exclude name="MediaWiki.Commenting.FunctionComment.ExtraParamComment" />
- <exclude name="MediaWiki.Commenting.FunctionComment.DefaultNullTypeParam" />
<exclude name="MediaWiki.Commenting.FunctionComment.WrongStyle" />
<exclude name="MediaWiki.Commenting.PhpunitAnnotations.NotClassTrait" />
<exclude name="MediaWiki.Commenting.IllegalSingleLineComment.IllegalSingleLineCommentStart" />
have been removed.
* The $wgUseAjax setting, deprecated in 1.31, is now ignored.
* The $wgSiteSupportPage setting, unused since 1.5, was removed.
+* The $wgBrowserBlacklist setting, deprecated in 1.30, was removed.
* The default quality of JPEG thumbnails generated by GD was reduced from 95 to
80. The quality of JPEG thumbnails is now configurable through the new setting
$wgJpegQuality (default 80). This aligns the quality to what ImageMagick uses.
use 'EditPageGetCheckboxesDefinition' instead.
* Linker::getLinkColour() and DummyLinker::getLinkColour(), deprecated since
1.28, were removed. LinkRenderer::getLinkClasses() should be used instead.
+* Wikimedia\Rdbms\LoadBalancer::getLaggedSlaveMode(), deprecated in 1.28, has
+ been removed. Use Wikimedia\Rdbms\LoadBalancer::getLaggedReplicaMode()
+ instead.
* mw.widgets.CategoryMultiselectWidget now uses TagMultiselectWidget instead of
CapsuleMultiselectWidget. The following methods may no longer be used:
* setItemsFromData: Use setValue instead
"selectorWithVariant": {
"type": "string"
},
+ "useDataURI": {
+ "type": "boolean"
+ },
"variants": {
"type": "object"
},
"selectorWithVariant": {
"type": "string"
},
+ "useDataURI": {
+ "type": "boolean"
+ },
"variants": {
"type": "object"
},
* 1) A block directly on the given user or IP
* 2) A rangeblock encompassing the given IP (smallest first)
* 3) An autoblock on the given IP
- * @param User|string $vagueTarget Also search for blocks affecting this target. Doesn't
+ * @param User|string|null $vagueTarget Also search for blocks affecting this target. Doesn't
* make any sense to use TYPE_AUTO / TYPE_ID here. Leave blank to skip IP lookups.
* @throws MWException
* @return bool Whether a relevant block was found
/**
* Get a set of SQL conditions which will select rangeblocks encompassing a given range
* @param string $start Hexadecimal IP representation
- * @param string $end Hexadecimal IP representation, or null to use $start = $end
+ * @param string|null $end Hexadecimal IP representation, or null to use $start = $end
* @return string
*/
public static function getRangeCond( $start, $end = null ) {
* Insert a block into the block table. Will fail if there is a conflicting
* block (same name and options) already in the database.
*
- * @param IDatabase $dbw If you have one available
+ * @param IDatabase|null $dbw If you have one available
* @return bool|array False on failure, assoc array on success:
* ('id' => block ID, 'autoIds' => array of autoblock IDs)
*/
* Calling this with a user, IP address or range will not select autoblocks, and will
* only select a block where the targets match exactly (so looking for blocks on
* 1.2.3.4 will not select 1.2.0.0/16 or even 1.2.3.4/32)
- * @param string|User|int $vagueTarget As above, but we will search for *any* block which
+ * @param string|User|int|null $vagueTarget As above, but we will search for *any* block which
* affects that target (so for an IP address, get ranges containing that IP; and also
* get any relevant autoblocks). Leave empty or blank to skip IP-based lookups.
* @param bool $fromMaster Whether to use the DB_MASTER database
* fields are null, the resulting Category object will represent an empty
* category if a title object was given. If the fields are null and no
* title was given, this method fails and returns false.
- * @param Title $title Optional title object for the category represented by
+ * @param Title|null $title Optional title object for the category represented by
* the given row. May be provided if it is already known, to avoid having
* to re-create a title object later.
* @return Category|false
*
* @since 1.30
* @since 1.31 Method signature changed, $key parameter added (with deprecated back compat)
- * @param string $key A key such as "rev_comment" identifying the comment
+ * @param string|null $key A key such as "rev_comment" identifying the comment
* field being fetched.
* @return string[] to include in the `$vars` to `IDatabase->select()`. All
* fields are aliased, so `+` is safe to use.
*
* @since 1.30
* @since 1.31 Method signature changed, $key parameter added (with deprecated back compat)
- * @param string $key A key such as "rev_comment" identifying the comment
+ * @param string|null $key A key such as "rev_comment" identifying the comment
* field being fetched.
* @return array With three keys:
* - tables: (string[]) to include in the `$table` to `IDatabase->select()`
* @since 1.31 Method signature changed, $key parameter added (with deprecated back compat)
* @param string $key A key such as "rev_comment" identifying the comment
* field being fetched.
- * @param object|array $row Result row.
+ * @param object|array|null $row Result row.
* @param bool $fallback If true, fall back as well as possible instead of throwing an exception.
* @return CommentStoreComment
*/
* @param IDatabase $db Database handle to use for lookup
* @param string $key A key such as "rev_comment" identifying the comment
* field being fetched.
- * @param object|array $row Result row.
+ * @param object|array|null $row Result row.
* @param bool $fallback If true, fall back as well as possible instead of throwing an exception.
* @return CommentStoreComment
*/
$comment = CommentStoreComment::newUnsavedComment( $comment, $data );
# Truncate comment in a Unicode-sensitive manner
- $comment->text = $this->lang->truncate( $comment->text, self::MAX_COMMENT_LENGTH );
- if ( mb_strlen( $comment->text, 'UTF-8' ) > self::COMMENT_CHARACTER_LIMIT ) {
- $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this->lang )->escaped();
- if ( mb_strlen( $ellipsis ) >= self::COMMENT_CHARACTER_LIMIT ) {
- // WTF?
- $ellipsis = '...';
- }
- $maxLength = self::COMMENT_CHARACTER_LIMIT - mb_strlen( $ellipsis, 'UTF-8' );
- $comment->text = mb_substr( $comment->text, 0, $maxLength, 'UTF-8' ) . $ellipsis;
- }
+ $comment->text = $this->lang->truncateForVisual( $comment->text, self::COMMENT_CHARACTER_LIMIT );
if ( $this->stage > MIGRATION_OLD && !$comment->id ) {
$dbData = $comment->data;
$comment = $this->createComment( $dbw, $comment, $data );
if ( $this->stage <= MIGRATION_WRITE_BOTH ) {
- $fields[$key] = $this->lang->truncate( $comment->text, 255 );
+ $fields[$key] = $this->lang->truncateForDatabase( $comment->text, 255 );
}
if ( $this->stage >= MIGRATION_WRITE_BOTH ) {
* @param IDatabase $dbw Database handle to insert on
* @param string $key A key such as "rev_comment" identifying the comment
* field being fetched.
- * @param string|Message|CommentStoreComment $comment As for `self::createComment()`
+ * @param string|Message|CommentStoreComment|null $comment As for `self::createComment()`
* @param array|null $data As for `self::createComment()`
* @return array Fields for the insert or update
*/
* @param IDatabase $dbw Database handle to insert on
* @param string $key A key such as "rev_comment" identifying the comment
* field being fetched.
- * @param string|Message|CommentStoreComment $comment As for `self::createComment()`
+ * @param string|Message|CommentStoreComment|null $comment As for `self::createComment()`
* @param array|null $data As for `self::createComment()`
* @return array Two values:
* - array Fields for the insert or update
$additionalSelfUrls = $this->getAdditionalSelfUrls();
$additionalSelfUrlsScript = $this->getAdditionalSelfUrlsScript();
- $nonceSrc = "'nonce-" . $this->nonce . "'";
// If no default-src is sent at all, it
// seems browsers (or at least some), interpret
$cssSrc = false;
$imgSrc = false;
$scriptSrc = [ "'unsafe-eval'", "'self'" ];
- if ( $mode !== self::FULL_MODE_RESTRICTED ) {
+ if (
+ $mode !== self::FULL_MODE_RESTRICTED &&
+ ( !isset( $policyConfig['useNonces'] ) || $policyConfig['useNonces'] )
+ ) {
+ $nonceSrc = "'nonce-" . $this->nonce . "'";
$scriptSrc[] = $nonceSrc;
}
$scriptSrc = array_merge( $scriptSrc, $additionalSelfUrlsScript );
}
/**
- * Is CSP currently enabled (i.e. Should we set nonce attribute)
+ * Should we set nonce attribute
*
* @param Config $config Configuration object
* @return bool
*/
- public static function isEnabled( Config $config ) {
- return $config->get( 'CSPHeader' ) !== false
- || $config->get( 'CSPReportOnlyHeader' ) !== false;
+ public static function isNonceRequired( Config $config ) {
+ $configs = [
+ $config->get( 'CSPHeader' ),
+ $config->get( 'CSPReportOnlyHeader' )
+ ];
+ foreach ( $configs as $headerConfig ) {
+ if (
+ $headerConfig === true ||
+ ( is_array( $headerConfig ) &&
+ !isset( $headerConfig['useNonces'] ) ) ||
+ ( is_array( $headerConfig ) &&
+ isset( $headerConfig['useNonces'] ) &&
+ $headerConfig['useNonces'] )
+ ) {
+ return true;
+ }
+ }
+ return false;
}
}
*/
$wgLegacyEncoding = false;
-/**
- * @deprecated since 1.30, does nothing
- */
-$wgBrowserBlackList = [];
-
/**
* If set to true, the MediaWiki 1.4 to 1.5 schema conversion will
* create stub reference rows in the text table instead of copying
* Maintain a log of page creations at Special:Log/create?
* @since 1.32
*/
-$wgPageCreationLog = false;
+$wgPageCreationLog = true;
/** @} */ # end logging }
* $wgCrossSiteAJAXdomains as an allowed load sources.
* 'unsafeFallback' Add unsafe-inline as a script source, as a fallback for
* browsers that do not understand nonce-sources [default on].
+ * 'useNonces' Require nonces on all inline scripts. If disabled and 'unsafeFallback'
+ * is on, then all inline scripts will be allowed [default true].
* 'script-src' Array of additional places that are allowed to have JS be loaded from.
* 'report-uri' true to use MW api [default], false to disable, string for alternate uri
* @warning May cause slowness on windows due to slow random number generator.
$wgCommentTableSchemaMigrationStage = MIGRATION_OLD;
/**
- * RevisionStore table schema migration stage (content, slots, content_models & slot_roles tables)
+ * RevisionStore table schema migration stage (content, slots, content_models & slot_roles tables).
+ * Use the SCHEMA_COMPAT_XXX flags. Supported values:
+ *
+ * - SCHEMA_COMPAT_OLD
+ * - SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
+ * - SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW
+ * - SCHEMA_COMPAT_OLD
+ *
+ * Note that reading the old and new schema at the same time is not supported.
+ * Attempting to set both read bits in $wgMultiContentRevisionSchemaMigrationStage
+ * will result in an InvalidArgumentException.
*
* @see Task: https://phabricator.wikimedia.org/T174028
* @see Commit: https://gerrit.wikimedia.org/r/#/c/378724/
*
* @since 1.32
- * @var int One of the MIGRATION_* constants
+ * @var int An appropriate combination of SCHEMA_COMPAT_XXX flags.
*/
$wgMultiContentRevisionSchemaMigrationStage = MIGRATION_OLD;
define( 'SHELL_MAX_ARG_STRLEN', '100000' );
/**@}*/
+/**@{
+ * Schema compatibility flags.
+ *
+ * Used as flags in a bit field that indicates whether the old or new schema (or both)
+ * are read or written.
+ *
+ * - SCHEMA_COMPAT_WRITE_OLD: Whether information is written to the old schema.
+ * - SCHEMA_COMPAT_READ_OLD: Whether information stored in the old schema is read.
+ * - SCHEMA_COMPAT_WRITE_NEW: Whether information is written to the new schema.
+ * - SCHEMA_COMPAT_READ_NEW: Whether information stored in the new schema is read.
+ */
+define( 'SCHEMA_COMPAT_WRITE_OLD', 0x01 );
+define( 'SCHEMA_COMPAT_READ_OLD', 0x02 );
+define( 'SCHEMA_COMPAT_WRITE_NEW', 0x10 );
+define( 'SCHEMA_COMPAT_READ_NEW', 0x20 );
+define( 'SCHEMA_COMPAT_WRITE_BOTH', SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_WRITE_NEW );
+define( 'SCHEMA_COMPAT_READ_BOTH', SCHEMA_COMPAT_READ_OLD | SCHEMA_COMPAT_READ_NEW );
+define( 'SCHEMA_COMPAT_OLD', SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_OLD );
+define( 'SCHEMA_COMPAT_NEW', SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_NEW );
+/**@}*/
+
/**@{
* Schema change migration flags.
*
* Used as values of a feature flag for an orderly transition from an old
- * schema to a new schema.
+ * schema to a new schema. The numeric values of these constants are compatible with the
+ * SCHEMA_COMPAT_XXX bitfield semantics. High bits are used to ensure that the numeric
+ * ordering follows the order in which the migration stages should be used.
*
* - MIGRATION_OLD: Only read and write the old schema. The new schema need not
* even exist. This is used from when the patch is merged until the schema
* - MIGRATION_NEW: Only read and write the new schema. The old schema (and the
* feature flag) may now be removed.
*/
-define( 'MIGRATION_OLD', 0 );
-define( 'MIGRATION_WRITE_BOTH', 1 );
-define( 'MIGRATION_WRITE_NEW', 2 );
-define( 'MIGRATION_NEW', 3 );
+define( 'MIGRATION_OLD', 0x00000000 | SCHEMA_COMPAT_OLD );
+define( 'MIGRATION_WRITE_BOTH', 0x10000000 | SCHEMA_COMPAT_READ_BOTH | SCHEMA_COMPAT_WRITE_BOTH );
+define( 'MIGRATION_WRITE_NEW', 0x20000000 | SCHEMA_COMPAT_READ_BOTH | SCHEMA_COMPAT_WRITE_NEW );
+define( 'MIGRATION_NEW', 0x30000000 | SCHEMA_COMPAT_NEW );
/**@}*/
} else {
// If we receive the last parameter of the request, we can fairly
// claim the POST request has not been truncated.
-
- // TODO: softened the check for cutover. Once we determine
- // that it is safe, we should complete the transition by
- // removing the "edittime" clause.
- $this->incompleteForm = ( !$request->getVal( 'wpUltimateParam' )
- && is_null( $this->edittime ) );
+ $this->incompleteForm = !$request->getVal( 'wpUltimateParam' );
}
if ( $this->incompleteForm ) {
# If the form is incomplete, force to preview.
* Builds a standard summary input with a label.
*
* @param string $summary The value of the summary input
- * @param string $labelText The html to place inside the label
- * @param array $inputAttrs Array of attrs to use on the input
+ * @param string|null $labelText The html to place inside the label
+ * @param array|null $inputAttrs Array of attrs to use on the input
*
* @return OOUI\FieldLayout OOUI FieldLayout with Label and Input
*/
* The $textoverride method can be used by subclasses overriding showContentForm
* to pass back to this method.
*
- * @param array $customAttribs Array of html attributes to use in the textarea
- * @param string $textoverride Optional text to override $this->textarea1 with
+ * @param array|null $customAttribs Array of html attributes to use in the textarea
+ * @param string|null $textoverride Optional text to override $this->textarea1 with
*/
protected function showTextbox1( $customAttribs = null, $textoverride = null ) {
if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) {
* Shows a bulletin board style toolbar for common editing functions.
* It can be disabled in the user preferences.
*
- * @param Title $title Title object for the page being edited (optional)
+ * @param Title|null $title Title object for the page being edited (optional)
* @return string
*/
public static function getEditToolbar( $title = null ) {
* @param string &$oldimage Archive name
* @param string $reason Reason of the deletion
* @param bool $suppress Whether to mark all deleted versions as restricted
- * @param User $user User object performing the request
+ * @param User|null $user User object performing the request
* @param array $tags Tags to apply to the deletion action
* @throws MWException
* @return Status
* available for accessing with getValue() or consumeValue() etc.
*
* @param WebRequest $r The request to fetch values from
- * @param array $optionKeys Which options to fetch the values for (default:
+ * @param array|null $optionKeys Which options to fetch the values for (default:
* all of them). Note that passing an empty array will also result in
* values for all keys being fetched.
* @throws MWException If the type of any option is invalid
use MediaWiki\Shell\Shell;
use Wikimedia\ScopedCallback;
use Wikimedia\Rdbms\DBReplicationWaitError;
+use Wikimedia\WrappedString;
/**
* Load an extension
* If $wgShowHostnames is true, the script will also set 'wgHostname' to the
* hostname of the server handling the request.
*
- * @param string $nonce Value from OutputPage::getCSPNonce
+ * @param string|null $nonce Value from OutputPage::getCSPNonce
* @return string|WrappedString HTML
*/
function wfReportTime( $nonce = null ) {
* For dealing with nullable timestamp columns in the database.
*
* @param int $outputtype
- * @param string $ts
+ * @param string|null $ts
* @return string
*/
function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
* Make directory, and make all parent directories if they don't exist
*
* @param string $dir Full path to directory to create
- * @param int $mode Chmod value to use, default is $wgDirectoryMode
- * @param string $caller Optional caller param for debugging.
+ * @param int|null $mode Chmod value to use, default is $wgDirectoryMode
+ * @param string|null $caller Optional caller param for debugging.
* @throws MWException
* @return bool
*/
* @param string $mine
* @param string $yours
* @param string &$result
- * @param string &$mergeAttemptResult
+ * @param string|null &$mergeAttemptResult
* @return bool
*/
function wfMerge( $old, $mine, $yours, &$result, &$mergeAttemptResult = null ) {
*
* @param string $name The image name to check
* @param Title|bool $contextTitle The page on which the image occurs, if known
- * @param string $blacklist Wikitext of a file blacklist
+ * @param string|null $blacklist Wikitext of a file blacklist
* @return bool
*/
function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) {
* literal "</script>" or (for XML) literal "]]>".
*
* @param string $contents JavaScript
- * @param string $nonce Nonce for CSP header, from OutputPage::getCSPNonce()
+ * @param string|null $nonce Nonce for CSP header, from OutputPage::getCSPNonce()
* @return string Raw HTML
*/
public static function inlineScript( $contents, $nonce = null ) {
if ( $nonce !== null ) {
$attrs['nonce'] = $nonce;
} else {
- if ( ContentSecurityPolicy::isEnabled( RequestContext::getMain()->getConfig() ) ) {
+ if ( ContentSecurityPolicy::isNonceRequired( RequestContext::getMain()->getConfig() ) ) {
wfWarn( "no nonce set on script. CSP will break it" );
}
}
* "<script src=foo.js></script>".
*
* @param string $url
- * @param string $nonce Nonce for CSP header, from OutputPage::getCSPNonce()
+ * @param string|null $nonce Nonce for CSP header, from OutputPage::getCSPNonce()
* @return string Raw HTML
*/
public static function linkedScript( $url, $nonce = null ) {
if ( $nonce !== null ) {
$attrs['nonce'] = $nonce;
} else {
- if ( ContentSecurityPolicy::isEnabled( RequestContext::getMain()->getConfig() ) ) {
+ if ( ContentSecurityPolicy::isNonceRequired( RequestContext::getMain()->getConfig() ) ) {
wfWarn( "no nonce set on script. CSP will break it" );
}
}
* red if the user has no edits?
* @param int $flags Customisation flags (e.g. Linker::TOOL_LINKS_NOBLOCK
* and Linker::TOOL_LINKS_EMAIL).
- * @param int $edits User edit count (optional, for performance)
+ * @param int|null $edits User edit count (optional, for performance)
* @return string HTML fragment
*/
public static function userToolLinks(
* @since 1.16.3
* @param int $userId User identifier
* @param string $userText User name or IP address
- * @param int $edits User edit count (optional, for performance)
+ * @param int|null $edits User edit count (optional, for performance)
* @return string
*/
public static function userToolLinksRedContribs( $userId, $userText, $edits = null ) {
* @since 1.16.3. $context added in 1.20. $options added in 1.21
*
* @param Revision $rev
- * @param IContextSource $context Context to use or null for the main context.
+ * @param IContextSource|null $context Context to use or null for the main context.
* @param array $options
* @return string
*/
*
* @since 1.23
* @param int $index Index to check
- * @param User $user User to check
+ * @param User|null $user User to check
* @return array
*/
public static function getRestrictionLevels( $index, User $user = null ) {
* Generate a purely relative timestamp, i.e., represent the time elapsed between
* the given base timestamp and this object.
*
- * @param MWTimestamp $relativeTo Relative base timestamp (defaults to now)
- * @param User $user Use to use offset for
- * @param Language $lang Language to use
+ * @param MWTimestamp|null $relativeTo Relative base timestamp (defaults to now)
+ * @param User|null $user Use to use offset for
+ * @param Language|null $lang Language to use
* @param array $chosenIntervals Intervals to use to represent it
* @return string Relative timestamp
*/
*
* Use factory instead: MagicWord::get
*
- * @param string $id The internal name of the magic word
+ * @param string|null $id The internal name of the magic word
* @param string[]|string $syn synonyms for the magic word
* @param bool $cs If magic word is case sensitive
*/
/**
* @see MediaWiki::preOutputCommit()
- * @param callable $postCommitWork [default: null]
+ * @param callable|null $postCommitWork [default: null]
* @since 1.26
*/
public function doPreOutputCommit( callable $postCommitWork = null ) {
* the user can receive a response (in case commit fails)
*
* @param IContextSource $context
- * @param callable $postCommitWork [default: null]
+ * @param callable|null $postCommitWork [default: null]
* @since 1.27
*/
public static function preOutputCommit(
use MediaWiki\Storage\RevisionFactory;
use MediaWiki\Storage\RevisionLookup;
use MediaWiki\Storage\RevisionStore;
-use MediaWiki\Storage\RevisionStoreFactory;
use OldRevisionImporter;
use UploadRevisionImporter;
use Wikimedia\Rdbms\LBFactory;
return $this->getService( 'RevisionStore' );
}
- /**
- * @since 1.32
- * @return RevisionStoreFactory
- */
- public function getRevisionStoreFactory() {
- return $this->getService( 'RevisionStoreFactory' );
- }
-
/**
* @since 1.31
* @return RevisionLookup
* message keys to try and use the first non-empty message for, or a
* MessageSpecifier to copy from.
* @param array $params Message parameters.
- * @param Language $language [optional] Language to use (defaults to current user language).
+ * @param Language|null $language [optional] Language to use (defaults to current user language).
* @throws InvalidArgumentException
*/
public function __construct( $key, $params = [], Language $language = null ) {
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
use MediaWiki\Session\SessionManager;
+use Wikimedia\Rdbms\IResultWrapper;
use Wikimedia\RelPath;
use Wikimedia\WrappedString;
use Wikimedia\WrappedStringList;
* Internal use only. Use OutputPage::addModules() if possible.
*
* @param string $file URL to file (absolute path, protocol-relative, or full url)
- * @param string $unused Previously used to change the cache-busting query parameter
+ * @param string|null $unused Previously used to change the cache-busting query parameter
*/
public function addScriptFile( $file, $unused = null ) {
if ( substr( $file, 0, 1 ) !== '/' && !preg_match( '#^[a-z]*://#i', $file ) ) {
/**
* @param array $categories
- * @return bool|ResultWrapper
+ * @return bool|IResultWrapper
*/
protected function addCategoryLinksToLBAndGetResult( array $categories ) {
# Add the links to a LinkBatch
* @param bool $interface Use interface language ($wgLang instead of
* $wgContLang) while parsing language sensitive magic words like GRAMMAR and PLURAL.
* This also disables LanguageConverter.
- * @param Language $language Target language object, will override $interface
+ * @param Language|null $language Target language object, will override $interface
* @throws MWException
* @return string HTML
*/
* Output a standard permission error page
*
* @param array $errors Error message keys or [key, param...] arrays
- * @param string $action Action that was denied or null if unknown
+ * @param string|null $action Action that was denied or null if unknown
*/
public function showPermissionsErrorPage( array $errors, $action = null ) {
foreach ( $errors as $key => $error ) {
* Format a list of error messages
*
* @param array $errors Array of arrays returned by Title::getUserPermissionsErrors
- * @param string $action Action that was denied or null if unknown
+ * @param string|null $action Action that was denied or null if unknown
* @return string The wikitext error-messages, formatted into a list.
*/
public function formatPermissionsErrorMessage( array $errors, $action = null ) {
*
* @param Title $title Title to link
* @param array $query Query string parameters
- * @param string $text Text of the link (input is not escaped)
+ * @param string|null $text Text of the link (input is not escaped)
* @param array $options Options array to pass to Linker
*/
public function addReturnTo( $title, array $query = [], $text = null, $options = [] ) {
* Add a "return to" link pointing to a specified title,
* or the title indicated in the request, or else the main page
*
- * @param mixed $unused
- * @param Title|string $returnto Title or String to return to
- * @param string $returntoquery Query string for the return to link
+ * @param mixed|null $unused
+ * @param Title|string|null $returnto Title or String to return to
+ * @param string|null $returntoquery Query string for the return to link
*/
public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
if ( $returnto == null ) {
* Add one or more variables to be set in mw.config in JavaScript
*
* @param string|array $keys Key or array of key/value pairs
- * @param mixed $value [optional] Value of the configuration variable
+ * @param mixed|null $value [optional] Value of the configuration variable
*/
public function addJsConfigVars( $keys, $value = null ) {
if ( is_array( $keys ) ) {
* @since 1.32
*/
public function getCSPNonce() {
- if ( !ContentSecurityPolicy::isEnabled( $this->getConfig() ) ) {
+ if ( !ContentSecurityPolicy::isNonceRequired( $this->getConfig() ) ) {
return false;
}
if ( $this->CSPNonce === null ) {
protected $id;
/**
- * @param Config $config
- * @param LoggerInterface $logger
+ * @param Config|null $config
+ * @param LoggerInterface|null $logger
*/
public function __construct( Config $config = null, LoggerInterface $logger = null ) {
$this->config = $config ?: RequestContext::getMain()->getConfig();
/**
* Show the input form with optional error message
*
- * @param string $err Error message or null if there's no error
+ * @param string|null $err Error message or null if there's no error
*/
function show( $err = null ) {
$out = $this->mContext->getOutput();
* Revision::FOR_PUBLIC to be displayed to all users
* Revision::FOR_THIS_USER to be displayed to $user
* Revision::RAW get the text regardless of permissions
- * @param User $user User object to check for, only if FOR_THIS_USER is passed
+ * @param User|null $user User object to check for, only if FOR_THIS_USER is passed
* to the $audience parameter
* @since 1.21
* @return Content|null
use MediaWiki\Shell\CommandFactory;
use MediaWiki\Storage\BlobStoreFactory;
use MediaWiki\Storage\NameTableStore;
-use MediaWiki\Storage\RevisionStoreFactory;
+use MediaWiki\Storage\RevisionStore;
use MediaWiki\Storage\SqlBlobStore;
use Wikimedia\ObjectFactory;
},
'RevisionStore' => function ( MediaWikiServices $services ) {
- return $services->getRevisionStoreFactory()->getRevisionStore();
- },
-
- 'RevisionStoreFactory' => function ( MediaWikiServices $services ) {
/** @var SqlBlobStore $blobStore */
$blobStore = $services->getService( '_SqlBlobStore' );
- $config = $services->getMainConfig();
- $store = new RevisionStoreFactory(
+ $store = new RevisionStore(
$services->getDBLoadBalancer(),
$blobStore,
$services->getMainWANObjectCache(),
$services->getContentModelStore(),
$services->getSlotRoleStore(),
$services->getMainConfig()->get( 'MultiContentRevisionSchemaMigrationStage' ),
- $services->getActorMigration(),
- LoggerFactory::getInstance( 'RevisionStore' ),
- $config->get( 'ContentHandlerUseDB' )
+ $services->getActorMigration()
);
+ $store->setLogger( LoggerFactory::getInstance( 'RevisionStore' ) );
+
+ $config = $services->getMainConfig();
+ $store->setContentHandlerUseDB( $config->get( 'ContentHandlerUseDB' ) );
+
return $store;
},
* Retrieves a configuration setting for a given wiki.
* @param string $settingName ID of the setting name to retrieve
* @param string $wiki Wiki ID of the wiki in question.
- * @param string $suffix The suffix of the wiki in question.
+ * @param string|null $suffix The suffix of the wiki in question.
* @param array $params List of parameters. $.'key' is replaced by $value in all returned data.
* @param array $wikiTags The tags assigned to the wiki.
* @return mixed The value of the setting requested.
/**
* Gets all settings for a wiki
* @param string $wiki Wiki ID of the wiki in question.
- * @param string $suffix The suffix of the wiki in question.
+ * @param string|null $suffix The suffix of the wiki in question.
* @param array $params List of parameters. $.'key' is replaced by $value in all returned data.
* @param array $wikiTags The tags assigned to the wiki.
* @return array Array of settings requested.
* Retrieves a configuration setting for a given wiki, forced to a boolean.
* @param string $setting ID of the setting name to retrieve
* @param string $wiki Wiki ID of the wiki in question.
- * @param string $suffix The suffix of the wiki in question.
+ * @param string|null $suffix The suffix of the wiki in question.
* @param array $wikiTags The tags assigned to the wiki.
* @return bool The value of the setting requested.
*/
* Retrieves the value of a given setting, and places it in its corresponding global variable.
* @param string $setting ID of the setting name to retrieve
* @param string $wiki Wiki ID of the wiki in question.
- * @param string $suffix The suffix of the wiki in question.
+ * @param string|null $suffix The suffix of the wiki in question.
* @param array $params List of parameters. $.'key' is replaced by $value in all returned data.
* @param array $wikiTags The tags assigned to the wiki.
*/
/**
* Retrieves the values of all settings, and places them in their corresponding global variables.
* @param string $wiki Wiki ID of the wiki in question.
- * @param string $suffix The suffix of the wiki in question.
+ * @param string|null $suffix The suffix of the wiki in question.
* @param array $params List of parameters. $.'key' is replaced by $value in all returned data.
* @param array $wikiTags The tags assigned to the wiki.
*/
* @param string|bool $shortContext A short enclosing context message name, to
* be used when there is a single error
* @param string|bool $longContext A long enclosing context message name, for a list
- * @param string|Language $lang Language to use for processing messages
+ * @param string|Language|null $lang Language to use for processing messages
* @return string
*/
public function getWikiText( $shortContext = false, $longContext = false, $lang = null ) {
*
* @param string|string[]|bool $shortContext A message name or an array of message names.
* @param string|string[]|bool $longContext A message name or an array of message names.
- * @param string|Language $lang Language to use for processing messages
+ * @param string|Language|null $lang Language to use for processing messages
* @return Message
*/
public function getMessage( $shortContext = false, $longContext = false, $lang = null ) {
* If $error is an array, the "params" field is a list of parameters for the message.
*
* @param array|string $error Code string or (key: code string, params: string[]) map
- * @param string|Language $lang Language to use for processing messages
+ * @param string|Language|null $lang Language to use for processing messages
* @return Message
*/
protected function getErrorMessage( $error, $lang = null ) {
/**
* Return an array with a Message object for each error.
* @param array $errors
- * @param string|Language $lang Language to use for processing messages
+ * @param string|Language|null $lang Language to use for processing messages
* @return Message[]
*/
protected function getErrorMessageArray( $errors, $lang = null ) {
* @param JobQueueGroup $jobQueueGroup
* @param MessageCache $messageCache
* @param Language $contentLanguage
- * @param LoggerInterface $saveParseLogger
+ * @param LoggerInterface|null $saveParseLogger
*/
public function __construct(
WikiPage $wikiPage,
* @param string $table
* @param string $idField
* @param string $nameField
- * @param callable $normalizationCallback Normalization to be applied to names before being
+ * @param callable|null $normalizationCallback Normalization to be applied to names before being
* saved or queried. This should be a callback that accepts and returns a single string.
* @param bool|string $wikiId The ID of the target wiki database. Use false for the local wiki.
- * @param callable $insertCallback Callback to change insert fields accordingly.
+ * @param callable|null $insertCallback Callback to change insert fields accordingly.
* This parameter was introduced in 1.32
*/
public function __construct(
return $this->loadBalancer->getConnection( $index, [], $this->wikiId, $flags );
}
+ /**
+ * Gets the cache key for names.
+ *
+ * The cache key is constructed based on the wiki ID passed to the constructor, and allows
+ * sharing of name tables cached for a specific database between wikis.
+ *
+ * @return string
+ */
private function getCacheKey() {
- return $this->cache->makeKey( 'NameTableSqlStore', $this->table, $this->wikiId );
+ return $this->cache->makeGlobalKey(
+ 'NameTableSqlStore',
+ $this->table,
+ $this->loadBalancer->resolveDomainID( $this->wikiId )
+ );
}
/**
// TODO: MCR: check the role and the content's model against the list of supported
// roles, see T194046.
+ if ( $role !== 'main' ) {
+ throw new InvalidArgumentException( 'Only the main slot is presently supported' );
+ }
+
$this->slotsUpdate->modifyContent( $role, $content );
}
* Use RevisionStore::getArchiveQueryInfo() to build a query that yields the
* required fields.
* @param int $queryFlags Flags for lazy loading behavior, see IDBAccessObject::READ_XXX.
- * @param Title $title
+ * @param Title|null $title
* @param array $overrides An associative array that allows fields in $row to be overwritten.
* Keys in this array correspond to field names in $row without the "ar_" prefix, so
* $overrides['user'] will override $row->ar_user, etc.
* MCR migration note: this replaces Revision::getPrevious
*
* @param RevisionRecord $rev
- * @param Title $title if known (optional)
+ * @param Title|null $title if known (optional)
*
* @return RevisionRecord|null
*/
* MCR migration note: this replaces Revision::getNext
*
* @param RevisionRecord $rev
- * @param Title $title if known (optional)
+ * @param Title|null $title if known (optional)
*
* @return RevisionRecord|null
*/
*/
private $slotRoleStore;
- /** @var int One of the MIGRATION_* constants */
+ /** @var int An appropriate combination of SCHEMA_COMPAT_XXX flags. */
private $mcrMigrationStage;
/**
* @param CommentStore $commentStore
* @param NameTableStore $contentModelStore
* @param NameTableStore $slotRoleStore
- * @param int $migrationStage
+ * @param int $mcrMigrationStage An appropriate combination of SCHEMA_COMPAT_XXX flags
* @param ActorMigration $actorMigration
* @param bool|string $wikiId
+ *
+ * @throws MWException if $mcrMigrationStage or $wikiId is invalid.
*/
public function __construct(
LoadBalancer $loadBalancer,
CommentStore $commentStore,
NameTableStore $contentModelStore,
NameTableStore $slotRoleStore,
- $migrationStage,
+ $mcrMigrationStage,
ActorMigration $actorMigration,
$wikiId = false
) {
Assert::parameterType( 'string|boolean', $wikiId, '$wikiId' );
- Assert::parameterType( 'integer', $migrationStage, '$migrationStage' );
+ Assert::parameterType( 'integer', $mcrMigrationStage, '$mcrMigrationStage' );
+ Assert::parameter(
+ ( $mcrMigrationStage & SCHEMA_COMPAT_READ_BOTH ) !== SCHEMA_COMPAT_READ_BOTH,
+ '$mcrMigrationStage',
+ 'Reading from the old and the new schema at the same time is not supported.'
+ );
+ Assert::parameter(
+ ( $mcrMigrationStage & SCHEMA_COMPAT_READ_BOTH ) !== 0,
+ '$mcrMigrationStage',
+ 'Reading needs to be enabled for the old or the new schema.'
+ );
+ Assert::parameter(
+ ( $mcrMigrationStage & SCHEMA_COMPAT_WRITE_BOTH ) !== 0,
+ '$mcrMigrationStage',
+ 'Writing needs to be enabled for the old or the new schema.'
+ );
+ Assert::parameter(
+ ( $mcrMigrationStage & SCHEMA_COMPAT_READ_OLD ) === 0
+ || ( $mcrMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) !== 0,
+ '$mcrMigrationStage',
+ 'Cannot read the old schema when not also writing it.'
+ );
+ Assert::parameter(
+ ( $mcrMigrationStage & SCHEMA_COMPAT_READ_NEW ) === 0
+ || ( $mcrMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) !== 0,
+ '$mcrMigrationStage',
+ 'Cannot read the new schema when not also writing it.'
+ );
$this->loadBalancer = $loadBalancer;
$this->blobStore = $blobStore;
$this->commentStore = $commentStore;
$this->contentModelStore = $contentModelStore;
$this->slotRoleStore = $slotRoleStore;
- $this->mcrMigrationStage = $migrationStage;
+ $this->mcrMigrationStage = $mcrMigrationStage;
$this->actorMigration = $actorMigration;
$this->wikiId = $wikiId;
$this->logger = new NullLogger();
}
+ /**
+ * @param int $flags A combination of SCHEMA_COMPAT_XXX flags.
+ * @return bool True if all the given flags were set in the $mcrMigrationStage
+ * parameter passed to the constructor.
+ */
+ private function hasMcrSchemaFlags( $flags ) {
+ return ( $this->mcrMigrationStage & $flags ) === $flags;
+ }
+
public function setLogger( LoggerInterface $logger ) {
$this->logger = $logger;
}
* @throws MWException
*/
public function setContentHandlerUseDB( $contentHandlerUseDB ) {
- if ( !$contentHandlerUseDB && $this->mcrMigrationStage > MIGRATION_OLD ) {
- throw new MWException(
- 'Content model must be stored in the database for multi content revision migration.'
- );
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW )
+ || $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_NEW )
+ ) {
+ if ( !$contentHandlerUseDB ) {
+ throw new MWException(
+ 'Content model must be stored in the database for multi content revision migration.'
+ );
+ }
}
$this->contentHandlerUseDB = $contentHandlerUseDB;
}
);
}
- // While inserting into the old schema make sure only the main slot is allowed.
- // TODO: support extra slots in MIGRATION_WRITE_BOTH mode!
- if ( $this->mcrMigrationStage <= MIGRATION_WRITE_BOTH && $slotRoles !== [ 'main' ] ) {
+ // If we are not writing into the new schema, we can't support extra slots.
+ if ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW ) && $slotRoles !== [ 'main' ] ) {
throw new InvalidArgumentException(
- 'Only the main slot is supported with MCR migration mode <= MIGRATION_WRITE_BOTH!'
+ 'Only the main slot is supported when not writing to the MCR enabled schema!'
+ );
+ }
+
+ // As long as we are not reading from the new schema, we don't want to write extra slots.
+ if ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_NEW ) && $slotRoles !== [ 'main' ] ) {
+ throw new InvalidArgumentException(
+ 'Only the main slot is supported when not reading from the MCR enabled schema!'
);
}
);
// Trigger exception if the main slot is missing.
- // Technically, this could go away with MIGRATION_NEW: while
+ // Technically, this could go away after MCR migration: while
// calling code may require a main slot to exist, RevisionStore
// really should not know or care about that requirement.
$rev->getSlot( 'main', RevisionRecord::RAW );
$newSlots[$role] = $slot;
// Write the main slot's text ID to the revision table for backwards compatibility
- if ( $slot->getRole() === 'main' && $this->mcrMigrationStage <= MIGRATION_WRITE_BOTH ) {
+ if ( $slot->getRole() === 'main'
+ && $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_OLD )
+ ) {
$blobAddress = $slot->getAddress();
$this->updateRevisionTextId( $dbw, $revisionId, $blobAddress );
}
}
// Write the main slot's text ID to the revision table for backwards compatibility
- if ( $protoSlot->getRole() === 'main' && $this->mcrMigrationStage <= MIGRATION_WRITE_BOTH ) {
+ if ( $protoSlot->getRole() === 'main'
+ && $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_OLD )
+ ) {
$this->updateRevisionTextId( $dbw, $revisionId, $blobAddress );
}
- if ( $this->mcrMigrationStage >= MIGRATION_WRITE_BOTH ) {
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW ) ) {
if ( $protoSlot->hasContentId() ) {
$contentId = $protoSlot->getContentId();
} else {
$revisionRow['rev_id'] = $rev->getId();
}
- if ( $this->mcrMigrationStage <= MIGRATION_WRITE_BOTH ) {
- // In non MCR more this IF section will relate to the main slot
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_OLD ) ) {
+ // In non MCR mode this IF section will relate to the main slot
$mainSlot = $rev->getSlot( 'main' );
$model = $mainSlot->getModel();
$format = $mainSlot->getFormat();
$blobFlags = null;
if ( is_object( $row ) ) {
- if ( $this->mcrMigrationStage >= MIGRATION_NEW ) {
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_NEW ) ) {
// Don't emulate from a row when using the new schema.
// Emulating from an array is still OK.
throw new LogicException( 'Can\'t emulate the main slot when using MCR schema.' );
$queryFlags,
Title $title
) {
- if ( $this->mcrMigrationStage < MIGRATION_NEW ) {
- // TODO: in MIGRATION_WRITE_BOTH, we could use the old and the new method:
- // e.g. call emulateMainSlot_1_29() if loadSlotRecords() fails.
-
+ if ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_NEW ) ) {
$mainSlot = $this->emulateMainSlot_1_29( $revisionRow, $queryFlags, $title );
$slots = new RevisionSlots( [ 'main' => $mainSlot ] );
} else {
}
if ( !empty( $fields['text_id'] ) ) {
- if ( $this->mcrMigrationStage >= MIGRATION_NEW ) {
- throw new MWException( "Cannot use text_id field with MCR schema" );
+ if ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
+ throw new MWException( "The text_id field is only available in the pre-MCR schema" );
}
if ( !empty( $fields['content'] ) ) {
*
* @param array $conditions
* @param int $flags (optional)
- * @param Title $title
+ * @param Title|null $title
*
* @return RevisionRecord|null
*/
* @param IDatabase $db
* @param array $conditions
* @param int $flags (optional)
- * @param Title $title
+ * @param Title|null $title
*
* @return RevisionRecord|null
*/
/**
* Finds the ID of a content row for a given revision and slot role.
* This can be used to re-use content rows even while the content ID
- * is still missing from SlotRecords, in MIGRATION_WRITE_BOTH mode.
+ * is still missing from SlotRecords, when writing to both the old and
+ * the new schema during MCR schema migration.
*
* @todo remove after MCR schema migration is complete.
*
* @return int|null
*/
private function findSlotContentId( IDatabase $db, $revId, $role ) {
- if ( $this->mcrMigrationStage < MIGRATION_WRITE_BOTH ) {
+ if ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW ) ) {
return null;
}
* - 'page': Join with the page table, and select fields to identify the page
* - 'user': Join with the user table, and select the user name
* - 'text': Join with the text table, and select fields to load page text. This
- * option is deprecated in MW 1.32 with MCR migration stage MIGRATION_WRITE_BOTH,
- * and disallowed with MIGRATION_MEW.
+ * option is deprecated in MW 1.32 when the MCR migration flag SCHEMA_COMPAT_WRITE_NEW
+ * is set, and disallowed when SCHEMA_COMPAT_READ_OLD is not set.
*
* @return array With three keys:
* - tables: (string[]) to include in the `$table` to `IDatabase->select()`
$ret['fields'] = array_merge( $ret['fields'], $actorQuery['fields'] );
$ret['joins'] = array_merge( $ret['joins'], $actorQuery['joins'] );
- if ( $this->mcrMigrationStage < MIGRATION_NEW ) {
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
$ret['fields'][] = 'rev_text_id';
if ( $this->contentHandlerUseDB ) {
}
if ( in_array( 'text', $options, true ) ) {
- if ( $this->mcrMigrationStage === MIGRATION_NEW ) {
+ if ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_OLD ) ) {
throw new InvalidArgumentException( 'text table can no longer be joined directly' );
- } elseif ( $this->mcrMigrationStage >= MIGRATION_WRITE_BOTH ) {
+ } elseif ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
+ // NOTE: even when this class is set to not read from the old schema, callers
+ // should still be able to join against the text table, as long as we are still
+ // writing the old schema for compatibility.
wfDeprecated( __METHOD__ . ' with `text` option', '1.32' );
}
'joins' => [],
];
- if ( $this->mcrMigrationStage < MIGRATION_NEW ) {
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
$db = $this->getDBConnectionRef( DB_REPLICA );
$ret['tables']['slots'] = 'revision';
$ret['fields']['model_name'] = 'NULL';
}
}
-
- // XXX: in MIGRATION_WRITE_BOTH mode, emulate *and* select - using a UNION?
- // See Anomie's idea at <https://gerrit.wikimedia.org/r/c/416465/
- // 8..10/includes/Storage/RevisionStore.php#2113>
} else {
$ret['tables'][] = 'slots';
$ret['tables'][] = 'slot_roles';
'joins' => $commentQuery['joins'] + $actorQuery['joins'],
];
- if ( $this->mcrMigrationStage < MIGRATION_NEW ) {
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
$ret['fields'][] = 'ar_text_id';
if ( $this->contentHandlerUseDB ) {
* MCR migration note: this replaces Revision::getPrevious
*
* @param RevisionRecord $rev
- * @param Title $title if known (optional)
+ * @param Title|null $title if known (optional)
*
* @return RevisionRecord|null
*/
* MCR migration note: this replaces Revision::getNext
*
* @param RevisionRecord $rev
- * @param Title $title if known (optional)
+ * @param Title|null $title if known (optional)
*
* @return RevisionRecord|null
*/
+++ /dev/null
-<?php
-
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * Attribution notice: when this file was created, much of its content was taken
- * from the Revision.php file as present in release 1.30. Refer to the history
- * of that file for original authorship.
- *
- * @file
- */
-
-namespace MediaWiki\Storage;
-
-use ActorMigration;
-use CommentStore;
-use Psr\Log\LoggerInterface;
-use WANObjectCache;
-use Wikimedia\Assert\Assert;
-use Wikimedia\Rdbms\LoadBalancer;
-
-/**
- * @since 1.32
- */
-class RevisionStoreFactory {
-
- /** @var SqlBlobStore */
- private $blobStore;
-
- /** @var LoadBalancer */
- private $loadBalancer;
-
- /** @var WANObjectCache */
- private $cache;
-
- /** @var CommentStore */
- private $commentStore;
-
- /** @var ActorMigration */
- private $actorMigration;
-
- /** @var NameTableStore */
- private $contentModelStore;
-
- /** @var NameTableStore */
- private $slotRoleStore;
-
- /** @var int One of the MIGRATION_* constants */
- private $mcrMigrationStage;
-
- /**
- * @var bool
- * @see $wgContentHandlerUseDB
- */
- private $contentHandlerUseDB;
-
- /** @var LoggerInterface */
- private $logger;
-
- /**
- * @todo $blobStore should be allowed to be any BlobStore!
- *
- * @param LoadBalancer $loadBalancer
- * @param SqlBlobStore $blobStore
- * @param WANObjectCache $cache
- * @param CommentStore $commentStore
- * @param NameTableStore $contentModelStore
- * @param NameTableStore $slotRoleStore
- * @param int $migrationStage
- * @param ActorMigration $actorMigration
- * @param LoggerInterface $logger
- * @param bool $contentHandlerUseDB see {@link $wgContentHandlerUseDB}
- */
- public function __construct(
- LoadBalancer $loadBalancer,
- SqlBlobStore $blobStore,
- WANObjectCache $cache,
- CommentStore $commentStore,
- NameTableStore $contentModelStore,
- NameTableStore $slotRoleStore,
- $migrationStage,
- ActorMigration $actorMigration,
- LoggerInterface $logger,
- $contentHandlerUseDB
- ) {
- Assert::parameterType( 'integer', $migrationStage, '$migrationStage' );
-
- $this->loadBalancer = $loadBalancer;
- $this->blobStore = $blobStore;
- $this->cache = $cache;
- $this->commentStore = $commentStore;
- $this->contentModelStore = $contentModelStore;
- $this->slotRoleStore = $slotRoleStore;
- $this->mcrMigrationStage = $migrationStage;
- $this->actorMigration = $actorMigration;
- $this->logger = $logger;
- $this->contentHandlerUseDB = $contentHandlerUseDB;
- }
-
- /**
- * @since 1.32
- *
- * @param bool|string $wikiId false for the current domain / wikid
- *
- * @return RevisionStore for the given wikiId with all necessary services and a logger
- */
- public function getRevisionStore( $wikiId = false ) {
- Assert::parameterType( 'string|boolean', $wikiId, '$wikiId' );
-
- $store = new RevisionStore(
- $this->loadBalancer,
- $this->blobStore,
- $this->cache,
- $this->commentStore,
- $this->contentModelStore,
- $this->slotRoleStore,
- $this->mcrMigrationStage,
- $this->actorMigration,
- $wikiId
- );
-
- $store->setLogger( $this->logger );
- $store->setContentHandlerUseDB( $this->contentHandlerUseDB );
-
- return $store;
- }
-
-}
// No negative caching; negative hits on text rows may be due to corrupted replica DBs
$blob = $this->cache->getWithSetCallback(
- // TODO: change key, since this is not necessarily revision text!
- $this->cache->makeKey( 'revisiontext', 'textid', $blobAddress ),
+ $this->getCacheKey( $blobAddress ),
$this->getCacheTTL(),
function ( $unused, &$ttl, &$setOpts ) use ( $blobAddress, $queryFlags ) {
list( $index ) = DBAccessObjectUtils::getDBOptions( $queryFlags );
return $blob;
}
+ /**
+ * Get a cache key for a given Blob address.
+ *
+ * The cache key is constructed in a way that allows cached blobs from the same database
+ * to be re-used between wikis. For example, enwiki and frwiki will use the same cache keys
+ * for blobs from the wikidatawiki database.
+ *
+ * @param string $blobAddress
+ * @return string
+ */
+ private function getCacheKey( $blobAddress ) {
+ return $this->cache->makeGlobalKey(
+ 'BlobStore',
+ 'address',
+ $this->dbLoadBalancer->resolveDomainID( $this->wikiId ),
+ $blobAddress
+ );
+ }
+
/**
* Expand a raw data blob according to the flags given.
*
* @param string|string[] $flags Blob flags, such as 'external' or 'gzip'.
* Note that not including 'utf-8' in $flags will cause the data to be decoded
* according to the legacy encoding specified via setLegacyEncoding.
- * @param string|null $cacheKey May be used for caching if given
+ * @param string|null $cacheKey A blob address for use in the cache key. If not given,
+ * caching is disabled.
*
* @return false|string The expanded blob or false on failure
*/
return false;
}
- if ( $cacheKey && $this->wikiId === false ) {
- // Make use of the wiki-local revision text cache.
+ if ( $cacheKey ) {
// The cached value should be decompressed, so handle that and return here.
- // NOTE: we rely on $this->cache being the right cache for $this->wikiId!
return $this->cache->getWithSetCallback(
- // TODO: change key, since this is not necessarily revision text!
- $this->cache->makeKey( 'revisiontext', 'textid', $cacheKey ),
+ $this->getCacheKey( $cacheKey ),
$this->getCacheTTL(),
function () use ( $url, $flags ) {
// No negative caching per BlobStore::getBlob()
protected $params;
/**
- * @param string $global Name of the global variable.
- * @param string|callable $class Name of the class of the real object
+ * @param string|null $global Name of the global variable.
+ * @param string|callable|null $class Name of the class of the real object
* or a factory function to call
* @param array $params Parameters to pass to constructor of the real object.
*/
protected $compileFlags = LightnCandy::FLAG_ERROR_EXCEPTION;
/**
- * @param string $templateDir
+ * @param string|null $templateDir
* @param bool $forceRecompile
*/
public function __construct( $templateDir = null, $forceRecompile = false ) {
* May provide false positives, but should never provide a false negative.
*
* @param string $action Action that permission needs to be checked for
- * @param User $user User to check (since 1.19); $wgUser will be used if not provided.
+ * @param User|null $user User to check (since 1.19); $wgUser will be used if not provided.
* @return bool
*/
public function quickUserCan( $action, $user = null ) {
* Can $user perform $action on this page?
*
* @param string $action Action that permission needs to be checked for
- * @param User $user User to check (since 1.19); $wgUser will be used if not
+ * @param User|null $user User to check (since 1.19); $wgUser will be used if not
* provided.
* @param string $rigor Same format as Title::getUserPermissionsErrors()
* @return bool
* Public for usage by LiquidThreads.
*
* @param array $rows Array of db result objects
- * @param string $oldFashionedRestrictions Comma-separated set of permission keys
+ * @param string|null $oldFashionedRestrictions Comma-separated set of permission keys
* indicating who can move or edit the page from the page table, (pre 1.10) rows.
* Edit and move sections are separated by a colon
* Example: "edit=autoconfirmed,sysop:move=sysop"
/**
* Load restrictions from the page_restrictions table
*
- * @param string $oldFashionedRestrictions Comma-separated set of permission keys
+ * @param string|null $oldFashionedRestrictions Comma-separated set of permission keys
* indicating who can move or edit the page from the page table, (pre 1.10) rows.
* Edit and move sections are separated by a colon
* Example: "edit=autoconfirmed,sysop:move=sysop"
/**
* Updates page_touched for this page; called from LinksUpdate.php
*
- * @param string $purgeTime [optional] TS_MW timestamp
+ * @param string|null $purgeTime [optional] TS_MW timestamp
* @return bool True if the update succeeded
*/
public function invalidateCache( $purgeTime = null ) {
/**
* Get the timestamp when this page was updated since the user last saw it.
*
- * @param User $user
+ * @param User|null $user
* @return string|null
*/
public function getNotificationTimestamp( $user = null ) {
* If no source and no default, returns null.
*
* @param string $name
- * @param array $default Optional default (or null)
+ * @param array|null $default Optional default (or null)
* @return array|null
*/
public function getArray( $name, $default = null ) {
* If an array is returned, contents are guaranteed to be integers.
*
* @param string $name
- * @param array $default Option default (or null)
+ * @param array|null $default Option default (or null)
* @return array Array of ints
*/
public function getIntArray( $name, $default = null ) {
* Get a cookie from the $_COOKIE jar
*
* @param string $key The name of the cookie
- * @param string $prefix A prefix to use for the cookie name, if not $wgCookiePrefix
- * @param mixed $default What to return if the value isn't found
+ * @param string|null $prefix A prefix to use for the cookie name, if not $wgCookiePrefix
+ * @param mixed|null $default What to return if the value isn't found
* @return mixed Cookie value or $default if the cookie not set
*/
public function getCookie( $key, $prefix = null, $default = null ) {
*
* @param string $wikiID Wiki'd id (generally database name)
* @param string $user User name (must be normalised before calling this function!)
- * @param string $text Link's text; optional, default to "User:$user"
+ * @param string|null $text Link's text; optional, default to "User:$user"
* @return string HTML link or false if the wiki was not found
*/
public static function foreignUserLink( $wikiID, $user, $text = null ) {
*
* @param string $wikiID Wiki'd id (generally database name)
* @param string $page Page name (must be normalised before calling this function!)
- * @param string $text Link's text; optional, default to $page
+ * @param string|null $text Link's text; optional, default to $page
* @return string|false HTML link or false if the wiki was not found
*/
public static function makeForeignLink( $wikiID, $page, $text = null ) {
* characters (<, >, &) are escaped but illegals are not touched.
*
* @param string $element Element name
- * @param array $attribs Name=>value pairs. Values will be escaped.
+ * @param array|null $attribs Name=>value pairs. Values will be escaped.
* @param string $contents Null to make an open tag only; '' for a contentless closed tag (default)
* @param bool $allowShortTag Whether '' in $contents will result in a contentless closed tag
* @return string
* This opens an XML element
*
* @param string $element Name of the element
- * @param array $attribs Array of attributes, see Xml::expandAttributes()
+ * @param array|null $attribs Array of attributes, see Xml::expandAttributes()
* @return string
*/
public static function openElement( $element, $attribs = null ) {
* Create a date selector
*
* @param string $selected The month which should be selected, default ''.
- * @param string $allmonths Value of a special item denoting all month.
+ * @param string|null $allmonths Value of a special item denoting all month.
* Null to not include (default).
* @param string $id Element identifier
* @return string Html string containing the month selector
*
* @param string $selected The language code of the selected language
* @param bool $customisedOnly If true only languages which have some content are listed
- * @param string $inLanguage The ISO code of the language to display the select list in (optional)
+ * @param string|null $inLanguage The ISO code of the language to display the select list in
* @param array $overrideAttrs Override the attributes of the select tag (since 1.20)
* @param Message|null $msg Label message key (since 1.20)
* @return array Array containing 2 items: label HTML and select list HTML
/**
* Convenience function to build an HTML drop-down list item.
* @param string $text Text for this item. Will be HTML escaped
- * @param string $value Form submission value; if empty, use text
+ * @param string|null $value Form submission value; if empty, use text
* @param bool $selected If true, will be the default selected item
* @param array $attribs Optional additional HTML attributes
* @return string HTML
* @param string $other Text for the "Other reasons" option
* @param string $selected Option which should be pre-selected
* @param string $class CSS classes for the drop-down
- * @param int $tabindex Value of the tabindex attribute
+ * @param int|null $tabindex Value of the tabindex attribute
* @return string
*/
public static function listDropDown( $name = '', $list = '', $other = '',
* @param array $fields Associative array, key is the name of a message that
* contains a description for the field, value is an HTML string
* containing the appropriate input.
- * @param string $submitLabel The name of a message containing a label for
+ * @param string|null $submitLabel The name of a message containing a label for
* the submit button.
* @param array $submitAttribs The attributes to add to the submit button
* @return string HTML form.
* Build a table of data
* @param array $rows An array of arrays of strings, each to be a row in a table
* @param array $attribs An array of attributes to apply to the table tag [optional]
- * @param array $headers An array of strings to use as table headers [optional]
+ * @param array|null $headers An array of strings to use as table headers [optional]
* @return string
*/
public static function buildTable( $rows, $attribs = [], $headers = null ) {
* Return true if we're to watch the page, false if not, null if no change.
* @param string $watchlist Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
* @param Title $titleObj The page under consideration
- * @param string $userOption The user option to consider when $watchlist=preferences.
+ * @param string|null $userOption The user option to consider when $watchlist=preferences.
* If not set will use watchdefault always and watchcreations if $titleObj doesn't exist.
* @return bool
*/
* @param int &$value Parameter value
* @param int|null $min Minimum value
* @param int|null $max Maximum value for users
- * @param int $botMax Maximum value for sysops/bots
+ * @param int|null $botMax Maximum value for sysops/bots
* @param bool $enforceLimits Whether to enforce (die) if value is outside limits
*/
protected function validateLimit( $paramName, &$value, $min, $max, $botMax = null,
* Set a watch (or unwatch) based the based on a watchlist parameter.
* @param string $watch Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
* @param Title $titleObj The article's title to change
- * @param string $userOption The user option to consider when $watch=preferences
+ * @param string|null $userOption The user option to consider when $watch=preferences
*/
protected function setWatch( $watch, $titleObj, $userOption = null ) {
$value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
* @since 1.25
* @param string|array|Message $msg
* @param IContextSource $context
- * @param array $params
+ * @param array|null $params
* @return Message|null
*/
public static function makeMessage( $msg, IContextSource $context, array $params = null ) {
* @param Page $page Object to work on
* @param User $user User doing the action
* @param string $oldimage Archive name
- * @param string &$reason Reason for the deletion. Autogenerated if null.
+ * @param string|null &$reason Reason for the deletion. Autogenerated if null.
* @param bool $suppress Whether to mark all deleted versions as restricted
* @param array $tags Tags to tag the deletion with
* @return Status
/**
* Constructs an instance of ApiMain that utilizes the module and format specified by $request.
*
- * @param IContextSource|WebRequest $context If this is an instance of
+ * @param IContextSource|WebRequest|null $context If this is an instance of
* FauxRequest, errors are thrown and no printing occurs
* @param bool $enableWrite Should be set to true if the api may modify data
*/
/**
* Log the preceding request
* @param float $time Time in seconds
- * @param Exception $e Exception caught while processing the request
+ * @param Exception|null $e Exception caught while processing the request
*/
protected function logRequest( $time, $e = null ) {
$request = $this->getRequest();
* Get module instance by name, or instantiate it if it does not exist
*
* @param string $moduleName Module name
- * @param string $group Optionally validate that the module is in a specific group
+ * @param string|null $group Optionally validate that the module is in a specific group
* @param bool $ignoreCache If true, force-creates a new instance and does not cache it
*
* @return ApiBase|null The new module instance, or null if failed
/**
* Get an array of modules in a specific group or all if no group is set.
- * @param string $group Optional group filter
+ * @param string|null $group Optional group filter
* @return array List of module names
*/
public function getNames( $group = null ) {
/**
* Create an array of (moduleName => moduleClass) for a specific group or for all.
- * @param string $group Name of the group to get or null for all
+ * @param string|null $group Name of the group to get or null for all
* @return array Name=>class map
*/
public function getNamesWithClasses( $group = null ) {
/**
* Returns true if the specific module is defined at all or in a specific group.
* @param string $moduleName Module name
- * @param string $group Group name to check against, or null to check all groups,
+ * @param string|null $group Group name to check against, or null to check all groups,
* @return bool True if defined
*/
public function isDefined( $moduleName, $group = null ) {
/**
* Get a list of redirect resolutions - maps a title to its redirect
* target. Includes generator data for redirect source when available.
- * @param ApiResult $result
+ * @param ApiResult|null $result
* @return array Array of prefixed_title (string) => Title object
* @since 1.21
*/
/**
* Get a list of title normalizations - maps a title to its normalized
* version in the form of result array.
- * @param ApiResult $result
+ * @param ApiResult|null $result
* @return array Array of raw_prefixed_title (string) => prefixed_title (string)
* @since 1.21
*/
/**
* Get a list of title conversions - maps a title to its converted
* version as a result array.
- * @param ApiResult $result
+ * @param ApiResult|null $result
* @return array Array of (from, to) strings
* @since 1.21
*/
/**
* Get a list of interwiki titles - maps a title to its interwiki
* prefix as result.
- * @param ApiResult $result
+ * @param ApiResult|null $result
* @param bool $iwUrl
* @return array Array of raw_prefixed_title (string) => interwiki_prefix (string)
* @since 1.21
/**
* Revision IDs that were not found in the database as result array.
- * @param ApiResult $result
+ * @param ApiResult|null $result
* @return array Array of revision IDs
* @since 1.21
*/
}
/**
- * @param ApiPageSet $resultPageSet
+ * @param ApiPageSet|null $resultPageSet
* @return void
*/
protected function run( ApiPageSet $resultPageSet = null ) {
}
/**
- * @param ApiPageSet $resultPageSet
+ * @param ApiPageSet|null $resultPageSet
* @return void
*/
protected function run( ApiPageSet $resultPageSet = null ) {
}
/**
- * @param ApiPageSet $resultPageSet
+ * @param ApiPageSet|null $resultPageSet
*/
private function run( ApiPageSet $resultPageSet = null ) {
$settings = self::$settings[$this->getModuleName()];
* Add an option such as LIMIT or USE INDEX. If an option was set
* before, the old value will be overwritten
* @param string $name Option name
- * @param string|string[] $value Option value
+ * @param string|string[]|null $value Option value
*/
protected function addOption( $name, $value = null ) {
if ( is_null( $value ) ) {
}
/**
- * @param string $query
- * @param string $protocol
+ * @param string|null $query
+ * @param string|null $protocol
* @return null|string
*/
public function prepareUrlQuerySearchString( $query = null, $protocol = null ) {
* Same as addPageSubItems(), but one element of $data at a time
* @param int $pageId Page ID
* @param array $item Data array à la ApiResult
- * @param string $elemname XML element name. If null, getModuleName()
+ * @param string|null $elemname XML element name. If null, getModuleName()
* is used
* @return bool Whether the element fit in the result
*/
}
/**
- * @param ApiPageSet $resultPageSet
+ * @param ApiPageSet|null $resultPageSet
* @return void
*/
public function run( $resultPageSet = null ) {
* @param File $file
* @param array $prop Array of properties to get (in the keys)
* @param ApiResult $result
- * @param array $thumbParams Containing 'width' and 'height' items, or null
+ * @param array|null $thumbParams Containing 'width' and 'height' items, or null
* @param array|bool|string $opts Options for data fetching.
* This is an array consisting of the keys:
* 'version': The metadata version for the metadata option
}
/**
- * @param ApiPageSet $resultPageSet
+ * @param ApiPageSet|null $resultPageSet
* @return void
*/
public function run( $resultPageSet = null ) {
}
/**
- * @param ApiPageSet $resultPageSet
+ * @param ApiPageSet|null $resultPageSet
*/
public function run( $resultPageSet = null ) {
$params = $this->extractRequestParams();
/**
* Generates and outputs the result of this query based upon the provided parameters.
*
- * @param ApiPageSet $resultPageSet
+ * @param ApiPageSet|null $resultPageSet
*/
public function run( $resultPageSet = null ) {
$user = $this->getUser();
}
/**
- * @param ApiPageSet $resultPageSet
+ * @param ApiPageSet|null $resultPageSet
* @return void
*/
abstract protected function run( ApiPageSet $resultPageSet = null );
* @since 1.25
* @param array &$arr
* @param string $type See ApiResult::META_TYPE
- * @param string $kvpKeyName See ApiResult::META_KVP_KEY_NAME
+ * @param string|null $kvpKeyName See ApiResult::META_KVP_KEY_NAME
*/
public static function setArrayType( array &$arr, $type, $kvpKeyName = null ) {
if ( !in_array( $type, [
* @since 1.25
* @param array|string|null $path See ApiResult::addValue()
* @param string $tag See ApiResult::META_TYPE
- * @param string $kvpKeyName See ApiResult::META_KVP_KEY_NAME
+ * @param string|null $kvpKeyName See ApiResult::META_KVP_KEY_NAME
*/
public function addArrayType( $path, $tag, $kvpKeyName = null ) {
$arr = &$this->path( $path );
* @since 1.25
* @param array &$arr
* @param string $type See ApiResult::META_TYPE
- * @param string $kvpKeyName See ApiResult::META_KVP_KEY_NAME
+ * @param string|null $kvpKeyName See ApiResult::META_KVP_KEY_NAME
*/
public static function setArrayTypeRecursive( array &$arr, $type, $kvpKeyName = null ) {
self::setArrayType( $arr, $type, $kvpKeyName );
* @since 1.25
* @param array|string|null $path See ApiResult::addValue()
* @param string $tag See ApiResult::META_TYPE
- * @param string $kvpKeyName See ApiResult::META_KVP_KEY_NAME
+ * @param string|null $kvpKeyName See ApiResult::META_KVP_KEY_NAME
*/
public function addArrayTypeRecursive( $path, $tag, $kvpKeyName = null ) {
$arr = &$this->path( $path );
*
* @since 1.25
* @param array|object $data
- * @param array &$metadata Store metadata here, if provided
+ * @param array|null &$metadata Store metadata here, if provided
* @return array|object
*/
public static function stripMetadataNonRecursive( $data, &$metadata = null ) {
"apihelp-options-summary": "Смени ги нагодувањата на тековниот корисник.",
"apihelp-options-extended-description": "Можат да се зададат само можностите заведени во јадрото или во едно од воспоставените додатоци, или пак можности со клуч кој ја има претставката <code>userjs-</code> (предвиден за употреба од кориснички скрипти).",
"apihelp-options-param-reset": "Ги враќа поставките по основно.",
- "apihelp-options-param-resetkinds": "Ð\9fисок на типови можности за повраток кога е зададена можноста <var>$1reset</var>.",
+ "apihelp-options-param-resetkinds": "Сисок на типови можности за повраток кога е зададена можноста <var>$1reset</var>.",
"apihelp-options-param-change": "Список на промени во форматот name=value (на пр. skin=vector). Вредностите не треба да содржат исправени црти. Ако не зададете вредност (дури ни знак за равенство), на пр., можност|другаможност|..., ќе биде зададена вредноста на можноста по основно.",
"apihelp-options-param-optionname": "Назив на можноста што треба да ѝ се зададе на вредноста дадена од <var>$1optionvalue</var>.",
"apihelp-options-param-optionvalue": "Вредноста на можноста укажана од <var>$1optionname</var>. Може да содржи исправени црти.",
"apihelp-protect-example-protect": "Een pagina beveiligen.",
"apihelp-purge-param-forcelinkupdate": "Werk de koppelingstabellen bij.",
"apihelp-purge-param-forcerecursivelinkupdate": "Werk de koppelingentabel bij, en werk de koppelingstabellen bij voor alle pagina's die deze pagina als sjabloon gebruiken.",
+ "apihelp-query+allcategories-summary": "Alle categorieën doorlopen.",
"apihelp-query+allcategories-param-dir": "Richting om in te sorteren.",
"apihelp-query+allcategories-param-limit": "Hoeveel categorieën te tonen.",
"apihelp-query+allcategories-paramvalue-prop-size": "Voegt het aantal pagina's in de categorie toe.",
"apihelp-query+allpages-param-limit": "Het totaal aantal pagina's dat getoont moeten worden.",
"apihelp-query+allredirects-summary": "Toon alle doorverwijzingen naar een naamruimte.",
"apihelp-query+allredirects-paramvalue-prop-title": "Voegt de titel van de doorverwijzing toe.",
+ "apihelp-query+allredirects-param-limit": "Hoeveel items er in totaal moeten worden getoond.",
"apihelp-query+allrevisions-summary": "Toon alle versies.",
+ "apihelp-query+allrevisions-param-namespace": "Alleen pagina's in deze naamruimte weergeven.",
"apihelp-query+allrevisions-example-user": "Toon de laatste 50 bijdragen van gebruiker <kbd>Example</kbd>.",
"apihelp-query+mystashedfiles-paramvalue-prop-type": "Vraag het MIME- en mediatype van het bestand op.",
"apihelp-query+mystashedfiles-param-limit": "Hoeveel bestanden te tonen.",
"apihelp-query+alltransclusions-param-namespace": "De door te lopen naamruimte.",
+ "apihelp-query+alltransclusions-param-limit": "Hoeveel items er in totaal moeten worden getoond.",
"apihelp-query+allusers-param-dir": "Richting om in te sorteren.",
"apihelp-query+allusers-param-excludegroup": "Sluit gebruikers in de gegeven groepen uit.",
"apihelp-query+allusers-paramvalue-prop-blockinfo": "Voegt informatie over een actuele blokkade van de gebruiker toe.",
"apihelp-query+categorymembers-param-dir": "Richting om in te sorteren.",
"apihelp-query+categorymembers-example-simple": "Toon de eerste 10 pagina's in <kbd>Category:Physics</kbd>.",
"apihelp-query+deletedrevisions-param-tag": "Alleen revisies met dit label weergeven.",
+ "apihelp-query+deletedrevs-paraminfo-modes": "{{PLURAL:$1|Modus|Modi}}: $2",
"apihelp-query+deletedrevs-param-tag": "Alleen revisies met dit label weergeven.",
"apihelp-query+embeddedin-param-namespace": "De door te lopen naamruimte.",
+ "apihelp-query+filearchive-example-simple": "Toon een lijst met alle verwijderde bestanden.",
"apihelp-query+fileusage-paramvalue-prop-pageid": "Pagina-ID van elke pagina.",
"apihelp-query+fileusage-paramvalue-prop-title": "Titel van elke pagina.",
+ "apihelp-query+imageinfo-paramvalue-prop-mediatype": "Voegt het mediatype van het bestand toe.",
"apihelp-query+imageusage-param-namespace": "De door te lopen naamruimte.",
"apihelp-query+imageusage-example-simple": "Toon pagina's die [[:File:Albert Einstein Head.jpg]] gebruiken.",
"apihelp-query+imageusage-example-generator": "Toon informatie over pagina's die [[:File:Albert Einstein Head.jpg]] gebruiken.",
"apihelp-query+iwbacklinks-param-prefix": "Voorvoegsel voor de interwiki.",
+ "apihelp-query+iwlinks-paramvalue-prop-url": "Voegt de volledige URL toe.",
"apihelp-query+langbacklinks-example-simple": "Toon de pagina's die verwijzen naar [[:fr:Test]].",
+ "apihelp-query+langlinks-paramvalue-prop-url": "Voegt de volledige URL toe.",
"apihelp-query+linkshere-paramvalue-prop-pageid": "Pagina-ID van elke pagina.",
"apihelp-query+linkshere-paramvalue-prop-title": "Titel van elke pagina.",
"apihelp-query+linkshere-param-namespace": "Toon alleen pagina's in deze naamruimten.",
*
* @param string $username
* @param Status $status From $this->checkPasswordValidity()
- * @param mixed $data Passed through to $this->getPasswordResetData()
+ * @param mixed|null $data Passed through to $this->getPasswordResetData()
*/
protected function setPasswordResetFlag( $username, Status $status, $data = null ) {
$reset = $this->getPasswordResetData( $username, $data );
* Fetch authentication data from the current session
* @protected For use by AuthenticationProviders
* @param string $key
- * @param mixed $default
+ * @param mixed|null $default
* @return mixed
*/
public function getAuthenticationSessionData( $key, $default = null ) {
/**
* @param AuthenticationRequest[] $reqs AuthenticationRequests needed to continue
* @param string $redirectTarget URL
- * @param mixed $redirectApiData Data suitable for adding to an ApiResult
+ * @param mixed|null $redirectApiData Data suitable for adding to an ApiResult
* @return AuthenticationResponse
* @see AuthenticationResponse::REDIRECT
*/
protected $warningLimit;
/**
- * @param array $conditions An array of arrays describing throttling conditions.
+ * @param array|null $conditions An array of arrays describing throttling conditions.
* Defaults to $wgPasswordAttemptThrottle. See documentation of that variable for format.
* @param array $params Parameters (all optional):
* - type: throttle type, used as a namespace for counters,
* @return bool
*/
public static function useFileCache( IContextSource $context, $mode = self::MODE_NORMAL ) {
+ global $wgContLang;
$config = MediaWikiServices::getInstance()->getMainConfig();
if ( !$config->get( 'UseFileCache' ) && $mode !== self::MODE_REBUILD ) {
$ulang = $context->getLanguage();
// Check that there are no other sources of variation
- if ( $user->getId() || $ulang->getCode() !== $config->get( 'LanguageCode' ) ) {
+ if ( $user->getId() || !$ulang->equals( $wgContLang ) ) {
return false;
}
* in order to avoid link table inconsistency), which was later removed
* for performance on wikis with a high edit rate.
*
- * @param bool $update
+ * @param bool|null $update
* @return bool
*/
public function forUpdate( $update = null ) {
* @param int $id Page's ID
* @param LinkTarget $target
* @param int $len Text's length
- * @param int $redir Whether the page is a redirect
+ * @param int|null $redir Whether the page is a redirect
* @param int $revision Latest revision's ID
* @param string|null $model Latest revision's content model ID
* @param string|null $lang Language code of the page, if not the content language
protected $wanCache;
/**
- * @param ResourceLoader $rl
- * @param LoggerInterface $logger
+ * @param ResourceLoader|null $rl
+ * @param LoggerInterface|null $logger
*/
public function __construct( ResourceLoader $rl = null, LoggerInterface $logger = null ) {
$this->resourceloader = $rl;
const LOCK_TTL = 30;
/**
- * Process local cache of loaded messages that are defined in
- * MediaWiki namespace. First array level is a language code,
- * second level is message key and the values are either message
- * content prefixed with space, or !NONEXISTENT for negative
- * caching.
- * @var array $mCache
+ * Process cache of loaded messages that are defined in MediaWiki namespace
+ *
+ * @var MapCacheLRU Map of (language code => key => " <MESSAGE>" or "!TOO BIG")
*/
- protected $mCache;
+ protected $cache;
/**
* @var bool[] Map of (language code => boolean)
/** @var Parser */
protected $mParser;
- /**
- * Variable for tracking which variables are already loaded
- * @var array $mLoadedLanguages
- */
- protected $mLoadedLanguages = [];
-
/**
* @var bool $mInParser
*/
$this->clusterCache = $clusterCache;
$this->srvCache = $serverCache;
+ $this->cache = new MapCacheLRU( 5 ); // limit size for sanity
+
$this->mDisable = !$useDB;
$this->mExpiry = $expiry;
}
* is disabled.
*
* @param string $code Language to which load messages
- * @param int $mode Use MessageCache::FOR_UPDATE to skip process cache [optional]
- * @throws MWException
+ * @param int|null $mode Use MessageCache::FOR_UPDATE to skip process cache [optional]
+ * @throws InvalidArgumentException
* @return bool
*/
protected function load( $code, $mode = null ) {
}
# Don't do double loading...
- if ( isset( $this->mLoadedLanguages[$code] ) && $mode != self::FOR_UPDATE ) {
+ if ( $this->cache->has( $code ) && $mode != self::FOR_UPDATE ) {
return true;
}
$staleCache = $cache;
} else {
$where[] = 'got from local cache';
+ $this->cache->set( $code, $cache );
$success = true;
- $this->mCache[$code] = $cache;
}
if ( !$success ) {
$staleCache = $cache;
} else {
$where[] = 'got from global cache';
- $this->mCache[$code] = $cache;
+ $this->cache->set( $code, $cache );
$this->saveToCaches( $cache, 'local-only', $code );
$success = true;
}
} elseif ( $staleCache ) {
# Use the stale cache while some other thread constructs the new one
$where[] = 'using stale cache';
- $this->mCache[$code] = $staleCache;
+ $this->cache->set( $code, $staleCache );
$success = true;
break;
} elseif ( $failedAttempts > 0 ) {
if ( !$success ) {
$where[] = 'loading FAILED - cache is disabled';
$this->mDisable = true;
- $this->mCache = false;
+ $this->cache->set( $code, null );
wfDebugLog( 'MessageCacheError', __METHOD__ . ": Failed to load $code\n" );
# This used to throw an exception, but that led to nasty side effects like
# the whole wiki being instantly down if the memcached server died
- } else {
- # All good, just record the success
- $this->mLoadedLanguages[$code] = true;
+ }
+
+ if ( !$this->cache->has( $code ) ) { // sanity
+ throw new LogicException( "Process cache for '$code' should be set by now." );
}
$info = implode( ', ', $where );
/**
* @param string $code
* @param array &$where List of wfDebug() comments
- * @param int $mode Use MessageCache::FOR_UPDATE to use DB_MASTER
+ * @param int|null $mode Use MessageCache::FOR_UPDATE to use DB_MASTER
* @return bool|string True on success or one of ("cantacquire", "disabled")
*/
protected function loadFromDBWithLock( $code, array &$where, $mode = null ) {
}
$cache = $this->loadFromDB( $code, $mode );
- $this->mCache[$code] = $cache;
+ $this->cache->set( $code, $cache );
$saveSuccess = $this->saveToCaches( $cache, 'all', $code );
if ( !$saveSuccess ) {
* on-demand from the database later.
*
* @param string $code Language code
- * @param int $mode Use MessageCache::FOR_UPDATE to skip process cache
+ * @param int|null $mode Use MessageCache::FOR_UPDATE to skip process cache
* @return array Loaded messages for storing in caches
*/
protected function loadFromDB( $code, $mode = null ) {
$mostused = [];
if ( $wgAdaptiveMessageCache && $code !== $wgLanguageCode ) {
- if ( !isset( $this->mCache[$wgLanguageCode] ) ) {
+ if ( !$this->cache->has( $wgLanguageCode ) ) {
$this->load( $wgLanguageCode );
}
- $mostused = array_keys( $this->mCache[$wgLanguageCode] );
+ $mostused = array_keys( $this->cache->get( $wgLanguageCode ) );
foreach ( $mostused as $key => $value ) {
$mostused[$key] = "$value/$code";
}
// (a) Update the process cache with the new message text
if ( $text === false ) {
// Page deleted
- $this->mCache[$code][$title] = '!NONEXISTENT';
+ $this->cache->setField( $code, $title, '!NONEXISTENT' );
} else {
// Ignore $wgMaxMsgCacheEntrySize so the process cache is up to date
- $this->mCache[$code][$title] = ' ' . $text;
+ $this->cache->setField( $code, $title, ' ' . $text );
}
// (b) Update the shared caches in a deferred update with a fresh DB snapshot
}
// Load the messages from the master DB to avoid race conditions
$cache = $this->loadFromDB( $code, self::FOR_UPDATE );
- $this->mCache[$code] = $cache;
- // Load the process cache values and set the per-title cache keys
+ // Check if an individual cache key should exist and update cache accordingly
$page = WikiPage::factory( Title::makeTitle( NS_MEDIAWIKI, $title ) );
$page->loadPageData( $page::READ_LATEST );
$text = $this->getMessageTextFromContent( $page->getContent() );
- // Check if an individual cache key should exist and update cache accordingly
if ( is_string( $text ) && strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
- $titleKey = $this->bigMessageCacheKey( $this->mCache[$code]['HASH'], $title );
- $this->wanCache->set( $titleKey, ' ' . $text, $this->mExpiry );
+ $this->wanCache->set(
+ $this->bigMessageCacheKey( $cache['HASH'], $title ),
+ ' ' . $text,
+ $this->mExpiry
+ );
}
// Mark this cache as definitely being "latest" (non-volatile) so
- // load() calls do try to refresh the cache with replica DB data
- $this->mCache[$code]['LATEST'] = time();
+ // load() calls do not try to refresh the cache with replica DB data
+ $cache['LATEST'] = time();
+ // Update the process cache
+ $this->cache->set( $code, $cache );
// Pre-emptively update the local datacenter cache so things like edit filter and
// blacklist changes are reflected immediately; these often use MediaWiki: pages.
// The datacenter handling replace() calls should be the same one handling edits
// as they require HTTP POST.
- $this->saveToCaches( $this->mCache[$code], 'all', $code );
+ $this->saveToCaches( $cache, 'all', $code );
// Release the lock now that the cache is saved
ScopedCallback::consume( $scopedLock );
public function getMsgFromNamespace( $title, $code ) {
$this->load( $code );
- if ( isset( $this->mCache[$code][$title] ) ) {
- $entry = $this->mCache[$code][$title];
+ if ( $this->cache->hasField( $code, $title ) ) {
+ $entry = $this->cache->getField( $code, $title );
if ( substr( $entry, 0, 1 ) === ' ' ) {
// The message exists and is not '!TOO BIG'
return (string)substr( $entry, 1 );
$message = false;
Hooks::run( 'MessagesPreLoad', [ $title, &$message, $code ] );
if ( $message !== false ) {
- $this->mCache[$code][$title] = ' ' . $message;
+ $this->cache->setField( $code, $title, ' ' . $message );
} else {
- $this->mCache[$code][$title] = '!NONEXISTENT';
+ $this->cache->setField( $code, $title, '!NONEXISTENT' );
}
return $message;
}
// Individual message cache key
- $titleKey = $this->bigMessageCacheKey( $this->mCache[$code]['HASH'], $title );
+ $bigKey = $this->bigMessageCacheKey( $this->cache->getField( $code, 'HASH' ), $title );
if ( $this->mCacheVolatile[$code] ) {
$entry = false;
// Make sure that individual keys respect the WAN cache holdoff period too
LoggerFactory::getInstance( 'MessageCache' )->debug(
__METHOD__ . ': loading volatile key \'{titleKey}\'',
- [ 'titleKey' => $titleKey, 'code' => $code ] );
+ [ 'titleKey' => $bigKey, 'code' => $code ] );
} else {
// Try the individual message cache
- $entry = $this->wanCache->get( $titleKey );
+ $entry = $this->wanCache->get( $bigKey );
}
if ( $entry !== false ) {
if ( substr( $entry, 0, 1 ) === ' ' ) {
- $this->mCache[$code][$title] = $entry;
+ $this->cache->setField( $code, $title, $entry );
// The message exists, so make sure a string is returned
return (string)substr( $entry, 1 );
} elseif ( $entry === '!NONEXISTENT' ) {
- $this->mCache[$code][$title] = '!NONEXISTENT';
+ $this->cache->setField( $code, $title, '!NONEXISTENT' );
return false;
} else {
// Corrupt/obsolete entry, delete it
- $this->wanCache->delete( $titleKey );
+ $this->wanCache->delete( $bigKey );
}
}
if ( $content ) {
$message = $this->getMessageTextFromContent( $content );
if ( is_string( $message ) ) {
- $this->mCache[$code][$title] = ' ' . $message;
- $this->wanCache->set( $titleKey, ' ' . $message, $this->mExpiry, $cacheOpts );
+ $this->cache->setField( $code, $title, ' ' . $message );
+ $this->wanCache->set( $bigKey, ' ' . $message, $this->mExpiry, $cacheOpts );
}
} else {
// A possibly temporary loading failure
LoggerFactory::getInstance( 'MessageCache' )->warning(
__METHOD__ . ': failed to load message page text for \'{titleKey}\'',
- [ 'titleKey' => $titleKey, 'code' => $code ] );
+ [ 'titleKey' => $bigKey, 'code' => $code ] );
$message = null; // no negative caching
}
} else {
if ( $message === false ) {
// Negative caching in case a "too big" message is no longer available (deleted)
- $this->mCache[$code][$title] = '!NONEXISTENT';
- $this->wanCache->set( $titleKey, '!NONEXISTENT', $this->mExpiry, $cacheOpts );
+ $this->cache->setField( $code, $title, '!NONEXISTENT' );
+ $this->wanCache->set( $bigKey, '!NONEXISTENT', $this->mExpiry, $cacheOpts );
}
return $message;
/**
* @param string $message
* @param bool $interface
- * @param Language $language
- * @param Title $title
+ * @param Language|null $language
+ * @param Title|null $title
* @return string
*/
public function transform( $message, $interface = false, $language = null, $title = null ) {
/**
* @param string $text
- * @param Title $title
+ * @param Title|null $title
* @param bool $linestart Whether or not this is at the start of a line
* @param bool $interface Whether this is an interface message
- * @param Language|string $language Language code
+ * @param Language|string|null $language Language code
* @return ParserOutput|string
*/
public function parse( $text, $title = null, $linestart = true,
*
* Mainly used after a mass rebuild
*/
- function clear() {
+ public function clear() {
$langs = Language::fetchLanguageNames( null, 'mw' );
foreach ( array_keys( $langs ) as $code ) {
$this->wanCache->touchCheckKey( $this->getCheckKey( $code ) );
}
-
- $this->mLoadedLanguages = [];
+ $this->cache->clear();
}
/**
global $wgContLang;
$this->load( $code );
- if ( !isset( $this->mCache[$code] ) ) {
+ if ( !$this->cache->has( $code ) ) {
// Apparently load() failed
return null;
}
// Remove administrative keys
- $cache = $this->mCache[$code];
+ $cache = $this->cache->get( $code );
unset( $cache['VERSION'] );
unset( $cache['EXPIRY'] );
unset( $cache['EXCESSIVE'] );
/**
* @param Title $pageTitle Title instance of the categorized page
- * @param Revision $revision Latest Revision instance of the categorized page
+ * @param Revision|null $revision Latest Revision instance of the categorized page
*
* @throws MWException
*/
/**
* @param string $timestamp Timestamp of the recent change to occur in TS_MW format
* @param Title $categoryTitle Title of the category a page is being added to or removed from
- * @param User $user User object of the user that made the change
+ * @param User|null $user User object of the user that made the change
* @param string $comment Change summary
* @param Title $pageTitle Title of the page that is being added or removed
* @param string $lastTimestamp Parent revision timestamp of this change in TS_MW format
*
* @param RecentChange &$rc Passed by reference
* @param bool $watched (default false)
- * @param int $linenumber (default null)
+ * @param int|null $linenumber (default null)
*
* @return string|bool
*/
* bot edit, or unpatrolled edit. In English it typically contains "N", "m", "b", or "!".
*
* @param string $flag One key of $wgRecentChangesFlags
- * @param IContextSource $context
+ * @param IContextSource|null $context
* @return string HTML
*/
public static function flag( $flag, IContextSource $context = null ) {
*
* @param int $old Number of bytes
* @param int $new Number of bytes
- * @param IContextSource $context
+ * @param IContextSource|null $context
* @return string
*/
public static function showCharacterDifference( $old, $new, IContextSource $context = null ) {
* Format the character difference of one or several changes.
*
* @param RecentChange $old
- * @param RecentChange $new Last change to use, if not provided, $old will be used
+ * @param RecentChange|null $new Last change to use, if not provided, $old will be used
* @return string HTML fragment
*/
public function formatCharacterDifference( RecentChange $old, RecentChange $new = null ) {
* field of this revision, if it's marked as deleted.
* @param RCCacheEntry|RecentChange $rc
* @param int $field
- * @param User $user User object to check, or null to use $wgUser
+ * @param User|null $user User object to check, or null to use $wgUser
* @return bool
*/
public static function userCan( $rc, $field, User $user = null ) {
}
}
- /** Inserts a rollback link
+ /**
+ * Insert a rollback link
*
* @param string &$s
* @param RecentChange &$rc
if ( $rc->mAttribs['rc_type'] == RC_EDIT
&& $rc->mAttribs['rc_this_oldid']
&& $rc->mAttribs['rc_cur_id']
+ && $rc->getAttribute( 'page_latest' ) == $rc->mAttribs['rc_this_oldid']
) {
- $page = $rc->getTitle();
- /** Check for rollback and edit permissions, disallow special pages, and only
+ $title = $rc->getTitle();
+ /** Check for rollback permissions, disallow special pages, and only
* show a link on the top-most revision */
- if ( $this->getUser()->isAllowed( 'rollback' )
- && $rc->mAttribs['page_latest'] == $rc->mAttribs['rc_this_oldid']
- ) {
+ if ( $title->quickUserCan( 'rollback', $this->getUser() ) ) {
$rev = new Revision( [
- 'title' => $page,
+ 'title' => $title,
'id' => $rc->mAttribs['rc_this_oldid'],
'user' => $rc->mAttribs['rc_user'],
'user_text' => $rc->mAttribs['rc_user_text'],
*
* @param RecentChange &$rc
* @param bool $watched
- * @param int $linenumber (default null)
+ * @param int|null $linenumber (default null)
*
* @return string
*/
*
* @param RecentChange &$rc Passed by reference
* @param bool $watched (default false)
- * @param int $linenumber (default null)
+ * @param int|null $linenumber (default null)
*
* @return string|bool
*/
/**
* Notify all the feeds about the change.
- * @param array $feeds Optional feeds to send to, defaults to $wgRCFeeds
+ * @param array|null $feeds Optional feeds to send to, defaults to $wgRCFeeds
*/
public function notifyRCFeeds( array $feeds = null ) {
global $wgRCFeeds;
*
* @param RecentChange|int $change RecentChange or corresponding rc_id
* @param bool $auto For automatic patrol
- * @param string|string[] $tags Change tags to add to the patrol log entry
+ * @param string|string[]|null $tags Change tags to add to the patrol log entry
* ($user should be able to add the specified tags before this is called)
* @return array See doMarkPatrolled(), or null if $change is not an existing rc_id
*/
* 'markedaspatrollederror-noautopatrol' as errors
* @param User $user User object doing the action
* @param bool $auto For automatic patrol
- * @param string|string[] $tags Change tags to add to the patrol log entry
+ * @param string|string[]|null $tags Change tags to add to the patrol log entry
* ($user should be able to add the specified tags before this is called)
* @return array Array of permissions errors, see Title::getUserPermissionsErrors()
*/
*
* @param string $timestamp Timestamp of the recent change to occur
* @param Title $categoryTitle Title of the category a page is being added to or removed from
- * @param User $user User object of the user that made the change
+ * @param User|null $user User object of the user that made the change
* @param string $comment Change summary
* @param Title $pageTitle Title of the page that is being added or removed
* @param int $oldRevId Parent revision ID of this change
* @param bool $bot true, if the change was made by a bot
* @param string $ip IP address of the user, if the change was made anonymously
* @param int $deleted Indicates whether the change has been deleted
- * @param bool $added true, if the category was added, false for removed
+ * @param bool|null $added true, if the category was added, false for removed
*
* @return RecentChange
*/
* @param int|null $rc_id The rc_id of the change to add the tags to
* @param int|null $rev_id The rev_id of the change to add the tags to
* @param int|null $log_id The log_id of the change to add the tags to
- * @param string $params Params to put in the ct_params field of table 'change_tag'
+ * @param string|null $params Params to put in the ct_params field of table 'change_tag'
* @param RecentChange|null $rc Recent change, in case the tagging accompanies the action
* (this should normally be the case)
*
* Pass a variable whose value is null if the rev_id is not relevant or unknown.
* @param int|null &$log_id The log_id of the change to add the tags to.
* Pass a variable whose value is null if the log_id is not relevant or unknown.
- * @param string $params Params to put in the ct_params field of table
+ * @param string|null $params Params to put in the ct_params field of table
* 'change_tag' when adding tags
* @param RecentChange|null $rc Recent change being tagged, in case the tagging accompanies
* the action
* @param string $tag
* @param string $reason
* @param User $user Who to attribute the action to
- * @param int $tagCount For deletion only, how many usages the tag had before
+ * @param int|null $tagCount For deletion only, how many usages the tag had before
* it was deleted.
* @param array $logEntryTags Change tags to apply to the entry
* that will be created in the tag management log
protected $model_id;
/**
- * @param string $modelId
+ * @param string|null $modelId
*
* @since 1.21
*/
/**
* @since 1.21
*
- * @param string $format
+ * @param string|null $format
*
* @return string
*
/**
* @since 1.21
*
- * @param Content $that
+ * @param Content|null $that
*
* @return bool
*
* @since 1.21
*
* @param Title $title
- * @param Content $old
+ * @param Content|null $old
* @param bool $recursive
- * @param ParserOutput $parserOutput
+ * @param ParserOutput|null $parserOutput
*
* @return DataUpdate[]
*
* Returns the English language, because code is English, and should be handled as such.
*
* @param Title $title
- * @param Content $content
+ * @param Content|null $content
*
* @return Language
*
* Returns the English language, because code is English, and should be handled as such.
*
* @param Title $title
- * @param Content $content
+ * @param Content|null $content
*
* @return Language
*
*
* @since 1.21
*
- * @param string $format The desired serialization format, or null for the default format.
+ * @param string|null $format The desired serialization format, or null for the default format.
*
* @return string Serialized form of this Content object.
*/
*
* @since 1.21
*
- * @param Content $that The Content object to compare to.
+ * @param Content|null $that The Content object to compare to.
*
* @return bool True if this Content object is equal to $that, false otherwise.
*/
* may call ParserOutput::recordOption() on the output object.
*
* @param Title $title The page title to use as a context for rendering.
- * @param int $revId Optional revision ID being rendered.
- * @param ParserOptions $options Any parser options.
+ * @param int|null $revId Optional revision ID being rendered.
+ * @param ParserOptions|null $options Any parser options.
* @param bool $generateHtml Whether to generate HTML (default: true). If false,
* the result of calling getText() on the ParserOutput object returned by
* this method is undefined.
* AbstractContent does.
*
* @param Title $title The context for determining the necessary updates
- * @param Content $old An optional Content object representing the
+ * @param Content|null $old An optional Content object representing the
* previous content, i.e. the content being replaced by this Content
* object.
* @param bool $recursive Whether to include recursive updates (default:
* false).
- * @param ParserOutput $parserOutput Optional ParserOutput object.
+ * @param ParserOutput|null $parserOutput Optional ParserOutput object.
* Provide if you have one handy, to avoid re-parsing of the content.
*
* @return DataUpdate[] A list of DataUpdate objects for putting information
*
* @since 1.21
*
- * @param Content $content
+ * @param Content|null $content
*
* @throws MWException If the content is not an instance of TextContent and
* wgContentHandlerTextFallback was set to 'fail'.
*
* @param string $text The textual representation, will be
* unserialized to create the Content object
- * @param Title $title The title of the page this text belongs to.
+ * @param Title|null $title The title of the page this text belongs to.
* Required if $modelId is not provided.
- * @param string $modelId The model to deserialize to. If not provided,
+ * @param string|null $modelId The model to deserialize to. If not provided,
* $title->getContentModel() is used.
- * @param string $format The format to use for deserialization. If not
+ * @param string|null $format The format to use for deserialization. If not
* given, the model's default format is used.
*
* @throws MWException If model ID or format is not supported or if the text can not be
* @since 1.21
*
* @param Content $content The Content object to serialize
- * @param string $format The desired serialization format
+ * @param string|null $format The desired serialization format
*
* @return string Serialized form of the content
*/
* @since 1.21
*
* @param string $blob Serialized form of the content
- * @param string $format The format used for serialization
+ * @param string|null $format The format used for serialization
*
* @return Content The Content object created by deserializing $blob
*/
* @since 1.21
*
* @param Title $title The page to determine the language for.
- * @param Content $content The page's content, if you have it handy, to avoid reloading it.
+ * @param Content|null $content The page's content, if you have it handy, to avoid reloading it.
*
* @return Language The page's language
*/
* @since 1.21
*
* @param Title $title The page to determine the language for.
- * @param Content $content The page's content, if you have it handy, to avoid reloading it.
+ * @param Content|null $content The page's content, if you have it handy, to avoid reloading it.
*
* @return Language The page's language for viewing
*/
* Specific content handlers may override it if they need different content handling.
*
* @param WikiPage $page
- * @param ParserCache $cache
+ * @param ParserCache|null $cache
* @return ParserOutput
*/
public function getParserOutputForIndexing( WikiPage $page, ParserCache $cache = null ) {
/**
* @param Message|string $msg A Message object, or a message key.
- * @param string[] $params An optional array of message parameters.
+ * @param string[]|null $params An optional array of message parameters.
*/
public function __construct( $msg, $params = null ) {
# XXX: messages may be wikitext, html or plain text! and maybe even something else entirely.
/**
* @param Title $title Unused.
- * @param int $revId Unused.
- * @param ParserOptions $options Unused.
+ * @param int|null $revId Unused.
+ * @param ParserOptions|null $options Unused.
* @param bool $generateHtml Whether to generate HTML (default: true).
*
* @return ParserOutput
$text = $this->getNativeData();
- $truncatedtext = $wgContLang->truncate(
+ $truncatedtext = $wgContLang->truncateForDatabase(
preg_replace( "/[\n\r]/", ' ', $text ),
max( 0, $maxlength ) );
* @since 1.21
*
* @param Content $that The other content object to compare this content object to.
- * @param Language $lang The language object to use for text segmentation.
+ * @param Language|null $lang The language object to use for text segmentation.
* If not given, $wgContentLang is used.
*
* @return Diff A diff representing the changes that would have to be
* Returns the content's text as-is.
*
* @param Content $content
- * @param string $format The serialization format to check
+ * @param string|null $format The serialization format to check
*
* @return mixed
*/
* @since 1.21
*
* @param string $text Serialized form of the content
- * @param string $format The format used for serialization
+ * @param string|null $format The format used for serialization
*
* @return Content The TextContent object wrapping $text
*/
* @param IMaintainableDatabase $db A database subclass
* @param array $tablesToClone An array of tables to clone, unprefixed
* @param string $newTablePrefix Prefix to assign to the tables
- * @param string $oldTablePrefix Prefix on current tables, if not $wgDBprefix
+ * @param string|null $oldTablePrefix Prefix on current tables, if not $wgDBprefix
* @param bool $dropCurrentTables
*/
public function __construct( IMaintainableDatabase $db, array $tablesToClone,
# works correctly across DB engines, we need to change the pre-
# fix back and forth so tableName() works right.
- self::changePrefix( $this->oldTablePrefix );
+ $this->db->tablePrefix( $this->oldTablePrefix );
$oldTableName = $this->db->tableName( $tbl, 'raw' );
- self::changePrefix( $this->newTablePrefix );
+ $this->db->tablePrefix( $this->newTablePrefix );
$newTableName = $this->db->tableName( $tbl, 'raw' );
// Postgres: Temp tables are automatically deleted upon end of session
*/
public function destroy( $dropTables = false ) {
if ( $dropTables ) {
- self::changePrefix( $this->newTablePrefix );
+ $this->db->tablePrefix( $this->newTablePrefix );
foreach ( $this->tablesToClone as $tbl ) {
$this->db->dropTable( $tbl );
}
}
- self::changePrefix( $this->oldTablePrefix );
+ $this->db->tablePrefix( $this->oldTablePrefix );
}
/**
*
* @param IDatabase $db
* @param string $name The script name (relative to $patchDir, without the '.sql' suffix)
- * @param string $patchDir The directory to find the script in. Use __DIR__ to search in the
+ * @param string|null $patchDir The directory to find the script in. Use __DIR__ to search in the
* directory the calling code is located in. If omitted, the "maintenance"
* directory will be used, where the scripts used by the updater are located.
*
class LineFormatter extends MonologLineFormatter {
/**
- * @param string $format The format of the message
- * @param string $dateFormat The format of the timestamp: one supported by DateTime::format
+ * @param string|null $format The format of the message
+ * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format
* @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries
* @param bool $ignoreEmptyContextAndExtra
* @param bool $includeStacktraces
* If you're using a real search engine, you'll probably want to override
* this behavior and do something nicer with the original wikitext.
* @param string $text
- * @param SearchEngine $se Search engine
+ * @param SearchEngine|null $se Search engine
* @return string
*/
public function updateText( $text, SearchEngine $se = null ) {
}
/**
- * @param int $i
+ * @param int|null $i
* @return string[]|string|null
*/
public function getClosing( $i = null ) {
/**#@-*/
/**
- * @param IContextSource $context Context to use, anything else will be ignored
+ * @param IContextSource|null $context Context to use, anything else will be ignored
* @param int $old Old ID we want to show and diff with.
* @param string|int $new Either revision ID or 'prev' or 'next'. Default: 0.
* @param int $rcid Deprecated, no longer used!
/**
* @param string|array|MessageSpecifier $messageSpec See Message::newFromSpecifier
* @param int $code
- * @param Exception|Throwable $previous The previous exception used for the exception chaining.
+ * @param Exception|Throwable|null $previous The previous exception used for the exception
+ * chaining.
*/
public function __construct( $messageSpec, $code = 0, $previous = null ) {
$this->messageSpec = $messageSpec;
*
* @param int $level Error level raised
* @param string $message
- * @param string $file
- * @param int $line
+ * @param string|null $file
+ * @param int|null $line
* @return bool
*
* @see logError()
*
* @since 1.25
*
- * @param int $level Error level raised
- * @param string $message Error message
- * @param string $file File that error was raised in
- * @param int $line Line number error was raised at
- * @param array $context Active symbol table point of error
- * @param array $trace Backtrace at point of error (undocumented HHVM
+ * @param int|null $level Error level raised
+ * @param string|null $message Error message
+ * @param string|null $file File that error was raised in
+ * @param int|null $line Line number error was raised at
+ * @param array|null $context Active symbol table point of error
+ * @param array|null $trace Backtrace at point of error (undocumented HHVM
* feature)
* @return bool Always returns false
*/
/**
* @param string $command
- * @param string $file
+ * @param string|null $file
*/
function __construct( $command, $file = null ) {
if ( !is_null( $file ) ) {
static $externalBlobCache = [];
$cacheID = ( $itemID === false ) ? "$cluster/$id" : "$cluster/$id/";
+
+ $wiki = $this->params['wiki'] ?? false;
+ $cacheID = ( $wiki === false ) ? $cacheID : "$cacheID@$wiki";
+
if ( isset( $externalBlobCache[$cacheID] ) ) {
wfDebugLog( 'ExternalStoreDB-cache',
"ExternalStoreDB::fetchBlob cache hit on $cacheID" );
/**
* @param Title $title
* @param FileRepo $repo
- * @param string|int $time
+ * @param string|int|null $time
* @return self
* @throws MWException
*/
$nt,
htmlspecialchars(
is_int( $this->getCaptionLength() ) ?
- $lang->truncate( $nt->getText(), $this->getCaptionLength() ) :
+ $lang->truncateForVisual( $nt->getText(), $this->getCaptionLength() ) :
$nt->getText()
),
[
}
/**
- * Length to truncate filename to in caption when using "showfilename" (if int).
+ * Length (in characters) to truncate filename to in caption when using "showfilename" (if int).
* A value of 'true' will truncate the filename to one line using CSS, while
* 'false' will disable truncating.
*
* Build a new HTMLForm from an array of field attributes
*
* @param array $descriptor Array of Field constructs, as described above
- * @param IContextSource $context Available since 1.18, will become compulsory in 1.18.
+ * @param IContextSource|null $context Available since 1.18, will become compulsory in 1.18.
* Obviates the need to call $form->setTitle()
* @param string $messagePrefix A prefix to go in front of default messages
*/
return $this;
}
+ /**
+ * Get the introductory message HTML.
+ *
+ * @since 1.32
+ *
+ * @return string
+ */
+ public function getPreText() {
+ return $this->mPre;
+ }
+
/**
* Add HTML to the header, inside the form.
*
use MediaWiki\Widget\UsersMultiselectWidget;
/**
- * Implements a capsule multiselect input field for user names.
+ * Implements a tag multiselect input field for user names.
*
* Besides the parameters recognized by HTMLUserTextField, additional recognized
* parameters are:
* @param string $url Url to use. If protocol-relative, will be expanded to an http:// URL
* @param array $options (optional) extra params to pass (see Http::request())
* @param string $caller The method making this request, for profiling
- * @param Profiler $profiler An instance of the profiler for profiling, or null
+ * @param Profiler|null $profiler An instance of the profiler for profiling, or null
*/
public function __construct(
$url, array $options = [], $caller = __METHOD__, $profiler = null
/**
* @param string $siteName
- * @param string $admin
+ * @param string|null $admin
* @param array $option
*/
function __construct( $siteName, $admin = null, array $option = [] ) {
/**
* @param Database &$db To perform updates on
* @param bool $shared Whether to perform updates on shared tables
- * @param Maintenance $maintenance Maintenance object which created us
+ * @param Maintenance|null $maintenance Maintenance object which created us
*/
protected function __construct( Database &$db, $shared, Maintenance $maintenance = null ) {
$this->db = $db;
* Obviously, only use this for updates that occur after the updatelog table was
* created!
* @param string $key Name of key to insert
- * @param string $val [optional] Value to insert along with the key
+ * @param string|null $val [optional] Value to insert along with the key
*/
public function insertUpdateRow( $key, $val = null ) {
$this->db->clearFlag( DBO_DDLMODE );
*
* @param string $path Path to the patch file
* @param bool $isFullPath Whether to treat $path as a relative or not
- * @param string $msg Description of the patch
+ * @param string|null $msg Description of the patch
* @return bool False if patch is skipped.
*/
protected function applyPatch( $path, $isFullPath = false, $msg = null ) {
* Installer variables are typically prefixed by an underscore.
*
* @param string $name
- * @param mixed $default
+ * @param mixed|null $default
*
* @return mixed
*/
* Get a session variable.
*
* @param string $name
- * @param array $default
+ * @param array|null $default
*
* @return array
*/
*/
public function label( $msg, $forId, $contents, $helpData = "" ) {
if ( strval( $msg ) == '' ) {
- $labelText = '\u{00A0}';
+ $labelText = "\u{00A0}";
} else {
$labelText = wfMessage( $msg )->escaped();
}
$items[$value] =
Xml::radio( $params['controlName'], $value, $checked, $itemAttribs ) .
- '\u{00A0}' .
+ "\u{00A0}" .
Xml::tags( 'label', [ 'for' => $id ], $this->parse(
isset( $params['itemLabels'] ) ?
wfMessage( $params['itemLabels'][$value] )->plain() :
return '<p>' .
Html::element( 'img', [ 'src' => $this->getVar( 'wgRightsIcon' ) ] ) .
- '\u{00A0}\u{00A0}' .
+ "\u{00A0}\u{00A0}" .
htmlspecialchars( $this->getVar( 'wgRightsText' ) ) .
"</p>\n" .
"<p style=\"text-align: center;\">" .
/**
* @param string $var
- * @param mixed $default
+ * @param mixed|null $default
*
* @return mixed
*/
/**
* Get the URL for a particular title (or with $1 if no title given)
*
- * @param string $title What text to put for the article name
+ * @param string|null $title What text to put for the article name
* @return string The URL
* @note Prior to 1.19 The getURL with an argument was broken.
* If you if you use this arg in an extension that supports MW earlier
}
/**
- * @param LoggerInterface $logger
+ * @param LoggerInterface|null $logger
*/
public function __construct( LoggerInterface $logger = null ) {
if ( $logger === null ) {
* @param string $type
* @param array $params Map of key/values
* @param array $opts Map of key/values; includes 'removeDuplicates'
- * @param Title $title Optional descriptive title
+ * @param Title|null $title Optional descriptive title
*/
public function __construct(
$type, array $params, array $opts = [], Title $title = null
* not a big problem in practice, but there are test cases)
*
* @param string $domain The domain to validate
- * @param string $originDomain (optional) the domain the cookie originates from
+ * @param string|null $originDomain (optional) the domain the cookie originates from
* @return bool
*/
public static function validateCookieDomain( $domain, $originDomain = null ) {
/**
* Clear one or several cache entries, or all cache entries
*
- * @param string|array $keys
+ * @param string|array|null $keys
* @return void
*/
public function clear( $keys = null ) {
/**
* Clear one or several cache entries, or all cache entries.
*
- * @param string|array $keys
+ * @param string|array|null $keys
* @return void
*/
public function clear( $keys = null ) {
/**
* Factory function for good results
*
- * @param mixed $value
+ * @param mixed|null $value
* @return static
*/
public static function newGood( $value = null ) {
* Change operation result
*
* @param bool $ok Whether the operation completed
- * @param mixed $value
+ * @param mixed|null $value
*/
public function setResult( $ok, $value = null ) {
$this->ok = (bool)$ok;
}
/**
- * @param string $markName The name of the mark that should
+ * @param string|null $markName The name of the mark that should
* be cleared. If not specified, all marks will be cleared.
*/
public function clearMarks( $markName = null ) {
*
* @param string $measureName
* @param string $startMark
- * @param string $endMark
+ * @param string|null $endMark
* @return array|bool The measure that has been created, or false if either
* the start mark or the end mark do not exist.
*/
*
* @see FileBackend::getFileStat()
*
- * @param array $paths Storage paths (optional)
+ * @param array|null $paths Storage paths (optional)
*/
abstract public function clearCache( array $paths = null );
*
* @see FileBackend::clearCache()
*
- * @param array $paths Storage paths (optional)
+ * @param array|null $paths Storage paths (optional)
*/
protected function doClearCache( array $paths = null ) {
}
/**
* @param array $creds From getAuthentication()
- * @param string $container
- * @param string $object
+ * @param string|null $container
+ * @param string|null $object
* @return string
*/
protected function storageUrl( array $creds, $container = null, $object = null ) {
* Get an array of file change log entries.
* A starting change ID and/or limit can be specified.
*
- * @param int $start Starting change ID or null
+ * @param int|null $start Starting change ID or null
* @param int $limit Maximum number of items to return
- * @param string &$next Updated to the ID of the next entry.
+ * @param string|null &$next Updated to the ID of the next entry.
* @return array List of associative arrays, each having:
* id : unique, monotonic, ID for this change
* batch_uuid : UUID for an operation batch
* This is useful for early release of locks before function scope is destroyed.
* This is the same as setting the lock object to null.
*
- * @param ScopedLock &$lock
+ * @param ScopedLock|null &$lock
* @since 1.21
*/
public static function release( ScopedLock &$lock = null ) {
* @todo analyse file if need be
* @todo look at multiple extension, separately and together.
*
- * @param string $path Full path to the image file, in case we have to look at the contents
+ * @param string|null $path Full path to the image file, in case we have to look at the contents
* (if null, only the MIME type is used to determine the media type code).
- * @param string $mime MIME type. If null it will be guessed using guessMimeType.
+ * @param string|null $mime MIME type. If null it will be guessed using guessMimeType.
*
* @return string A value to be used with the MEDIATYPE_xxx constants.
*/
* result stored in $this->filterMatchType.
*
* @param string $input a filename or string containing the XML element
- * @param callable $filterCallback (optional)
+ * @param callable|null $filterCallback (optional)
* Function to call to do additional custom validity checks from the
* SAX element handler event. This gives you access to the element
* namespace, name, attributes, and text contents.
* Alternative constructor: from filename
*
* @param string $fname the filename of an XML document
- * @param callable $filterCallback (optional)
+ * @param callable|null $filterCallback (optional)
* Function to call to do additional custom validity checks from the
* SAX element handler event. This gives you access to the element
* namespace, name, and attributes, but not to text contents.
* Alternative constructor: from string
*
* @param string $string a string containing an XML element
- * @param callable $filterCallback (optional)
+ * @param callable|null $filterCallback (optional)
* Function to call to do additional custom validity checks from the
* SAX element handler event. This gives you access to the element
* namespace, name, and attributes, but not to text contents.
*
* @param string $key
* @param int $flags Bitfield of BagOStuff::READ_* constants [optional]
- * @param int $oldFlags [unused]
+ * @param int|null $oldFlags [unused]
* @return mixed Returns false on failure and if the item does not exist
*/
public function get( $key, $flags = 0, $oldFlags = null ) {
*
* @since 1.27
* @param string $class Key class
- * @param string $component [optional] Key component (starting with a key collection name)
+ * @param string|null $component [optional] Key component (starting with a key collection name)
* @return string Colon-delimited list of $keyspace followed by escaped components of $args
*/
public function makeGlobalKey( $class, $component = null ) {
*
* @since 1.27
* @param string $class Key class
- * @param string $component [optional] Key component (starting with a key collection name)
+ * @param string|null $component [optional] Key component (starting with a key collection name)
* @return string Colon-delimited list of $keyspace followed by escaped components of $args
*/
public function makeKey( $class, $component = null ) {
* That method has cache slam avoiding features for hot/expensive keys.
*
* @param string $key Cache key made from makeKey() or makeGlobalKey()
- * @param mixed &$curTTL Approximate TTL left on the key if present/tombstoned [returned]
+ * @param mixed|null &$curTTL Approximate TTL left on the key if present/tombstoned [returned]
* @param array $checkKeys List of "check" keys
- * @param float &$asOf UNIX timestamp of cached value; null on failure [returned]
+ * @param float|null &$asOf UNIX timestamp of cached value; null on failure [returned]
* @return mixed Value of cache key or false on failure
*/
final public function get( $key, &$curTTL = null, array $checkKeys = [], &$asOf = null ) {
* @param int $ttl
* @param callback $callback
* @param array $opts Options map for getWithSetCallback()
- * @param float &$asOf Cache generation timestamp of returned value [returned]
+ * @param float|null &$asOf Cache generation timestamp of returned value [returned]
* @return mixed
* @note Callable type hints are not used to avoid class-autoloading
*/
/**
* @see BagOStuff::makeKey()
* @param string $class Key class
- * @param string $component [optional] Key component (starting with a key collection name)
+ * @param string|null $component [optional] Key component (starting with a key collection name)
* @return string Colon-delimited list of $keyspace followed by escaped components of $args
* @since 1.27
*/
/**
* @see BagOStuff::makeGlobalKey()
* @param string $class Key class
- * @param string $component [optional] Key component (starting with a key collection name)
+ * @param string|null $component [optional] Key component (starting with a key collection name)
* @return string Colon-delimited list of $keyspace followed by escaped components of $args
* @since 1.27
*/
return $this->__call( __FUNCTION__, func_get_args() );
}
+ /**
+ * @codeCoverageIgnore
+ */
public function getWikiID() {
return $this->getDomainID();
}
* @see WANObjectCache::getWithSetCallback()
*
* @param IDatabase $db1
- * @param IDatabase $db2 [optional]
+ * @param IDatabase|null $db2 [optional]
* @return array Map of values:
* - lag: highest lag of any of the DBs or false on error (e.g. replication stopped)
* - since: oldest UNIX timestamp of any of the DB lag estimates
/**
* Returns the text of the error message from previous MySQL operation
*
- * @param resource $conn Raw connection
+ * @param resource|null $conn Raw connection
* @return string
*/
abstract protected function mysqlError( $conn = null );
/**
* List all tables on the database
*
- * @param string $prefix Only show tables with this prefix, e.g. mw_
+ * @param string|null $prefix Only show tables with this prefix, e.g. mw_
* @param string $fname Calling function name
* @return array
*/
/**
* Lists VIEWs in the database
*
- * @param string $prefix Only show VIEWs with this prefix, eg.
+ * @param string|null $prefix Only show VIEWs with this prefix, eg.
* unit_test_, or $wgDBprefix. Default: null, would return all views.
* @param string $fname Name of calling function
* @return array
* Differentiates between a TABLE and a VIEW.
*
* @param string $name Name of the TABLE/VIEW to test
- * @param string $prefix
+ * @param string|null $prefix
* @return bool
* @since 1.22
*/
}
/**
- * @param mysqli $conn Optional connection object
+ * @param mysqli|null $conn Optional connection object
* @return string
*/
protected function mysqlError( $conn = null ) {
/**
* List all tables on the database
*
- * @param string $prefix Only show tables with this prefix, e.g. mw_
+ * @param string|null $prefix Only show tables with this prefix, e.g. mw_
* @param string $fname Calling function name
*
* @return array
/**
* Get/set the table prefix.
- * @param string $prefix The table prefix to set, or omitted to leave it unchanged.
+ * @param string|null $prefix The table prefix to set, or omitted to leave it unchanged.
* @return string The previous table prefix.
*/
public function tablePrefix( $prefix = null );
/**
* Get/set the db schema.
- * @param string $schema The database schema to set, or omitted to leave it unchanged.
+ * @param string|null $schema The database schema to set, or omitted to leave it unchanged.
* @return string The previous db schema.
*/
public function dbSchema( $schema = null );
* Get properties passed down from the server info array of the load
* balancer.
*
- * @param string $name The entry of the info array to get, or null to get the
+ * @param string|null $name The entry of the info array to get, or null to get the
* whole array
*
* @return array|mixed|null
* parameters, the member with the given name is set to the given value.
*
* @param string $name
- * @param array $value
+ * @param array|null $value
*/
public function setLBInfo( $name, $value = null );
* @since 1.31
* @see IDatabase::startAtomic
* @param string $fname
- * @param AtomicSectionIdentifier $sectionId Section ID from startAtomic();
+ * @param AtomicSectionIdentifier|null $sectionId Section ID from startAtomic();
* passing this enables cancellation of unclosed nested sections [optional]
* @throws DBError
*/
* The result is unquoted, and needs to be passed through addQuotes()
* before it can be included in raw SQL.
*
- * @param string|int $ts
+ * @param string|int|null $ts
*
* @return string
*/
/**
* Lists all the VIEWs in the database
*
- * @param string $prefix Only show VIEWs with this prefix, eg. unit_test_
+ * @param string|null $prefix Only show VIEWs with this prefix, eg. unit_test_
* @param string $fname Name of calling function
* @throws RuntimeException
* @return array
/**
* List all tables on the database
*
- * @param string $prefix Only show tables with this prefix, e.g. mw_
+ * @param string|null $prefix Only show tables with this prefix, e.g. mw_
* @param string $fname Calling function name
* @throws DBError
* @return array
*/
class DBConnectionError extends DBExpectedError {
/**
- * @param IDatabase $db Object throwing the error
+ * @param IDatabase|null $db Object throwing the error
* @param string $error Error text
*/
public function __construct( IDatabase $db = null, $error = 'unknown error' ) {
/**
* Construct a database error
- * @param IDatabase $db Object which threw the error
+ * @param IDatabase|null $db Object which threw the error
* @param string $error A simple error message to be used for debugging
* @param \Exception|\Throwable|null $prev Previous exception
*/
* @param int|string $errno
* @param string $sql
* @param string $fname
- * @param string $message Optional message, intended for subclases (optional)
+ * @param string|null $message Optional message, intended for subclases (optional)
*/
public function __construct( IDatabase $db, $error, $errno, $sql, $fname, $message = null ) {
if ( $message === null ) {
*/
public function destroy();
+ /**
+ * Get the local (and default) database domain ID of connection handles
+ *
+ * @see DatabaseDomain
+ * @return string Database domain ID; this specifies DB name, schema, and table prefix
+ * @since 1.32
+ */
+ public function getLocalDomainID();
+
+ /**
+ * @param DatabaseDomain|string|bool $domain Database domain
+ * @return string Value of $domain if provided or the local domain otherwise
+ * @since 1.32
+ */
+ public function resolveDomainID( $domain );
+
/**
* Create a new load balancer object. The resulting object will be untracked,
* not chronology-protected, and the caller is responsible for cleaning it up.
/**
* Determine if any master connection has pending/written changes from this request
- * @param float $age How many seconds ago is "recent" [defaults to LB lag wait timeout]
+ * @param float|null $age How many seconds ago is "recent" [defaults to LB lag wait timeout]
* @return bool
*/
public function hasOrMadeRecentMasterChanges( $age = null );
$this->forEachLBCallMethod( 'disable' );
}
+ public function getLocalDomainID() {
+ return $this->localDomain->getId();
+ }
+
+ public function resolveDomainID( $domain ) {
+ return ( $domain !== false ) ? (string)$domain : $this->getLocalDomainID();
+ }
+
public function shutdown(
$mode = self::SHUTDOWN_CHRONPROT_SYNC,
callable $workCallback = null,
*/
public function __construct( array $params );
+ /**
+ * Get the local (and default) database domain ID of connection handles
+ *
+ * @see DatabaseDomain
+ * @return string Database domain ID; this specifies DB name, schema, and table prefix
+ * @since 1.31
+ */
+ public function getLocalDomainID();
+
+ /**
+ * @param DatabaseDomain|string|bool $domain Database domain
+ * @return string Value of $domain if provided or the local domain otherwise
+ * @since 1.32
+ */
+ public function resolveDomainID( $domain );
+
/**
* Get the index of the reader connection, which may be a replica DB
*
* This can be used a faster proxy for waitForAll()
*
* @param DBMasterPos|bool $pos Master position or false
- * @param int $timeout Max seconds to wait; default is mWaitTimeout
+ * @param int|null $timeout Max seconds to wait; default is mWaitTimeout
* @return bool Success (able to connect and no timeouts reached)
*/
public function waitForOne( $pos, $timeout = null );
* Set the master wait position and wait for ALL replica DBs to catch up to it
*
* @param DBMasterPos|bool $pos Master position or false
- * @param int $timeout Max seconds to wait; default is mWaitTimeout
+ * @param int|null $timeout Max seconds to wait; default is mWaitTimeout
* @return bool Success (able to connect and no timeouts reached)
*/
public function waitForAll( $pos, $timeout = null );
* Check if this load balancer object had any recent or still
* pending writes issued against it by this PHP thread
*
- * @param float $age How many seconds ago is "recent" [defaults to mWaitTimeout]
+ * @param float|null $age How many seconds ago is "recent" [defaults to mWaitTimeout]
* @return bool
*/
public function hasOrMadeRecentMasterChanges( $age = null );
$this->defaultGroup = $params['defaultGroup'] ?? null;
}
- /**
- * Get the local (and default) database domain ID of connection handles
- *
- * @see DatabaseDomain
- * @return string Database domain ID; this specifies DB name, schema, and table prefix
- * @since 1.31
- */
public function getLocalDomainID() {
return $this->localDomain->getId();
}
+ public function resolveDomainID( $domain ) {
+ return ( $domain !== false ) ? (string)$domain : $this->getLocalDomainID();
+ }
+
/**
* Get a LoadMonitor instance
*
* Wait for a given replica DB to catch up to the master pos stored in $this
* @param int $index Server index
* @param bool $open Check the server even if a new connection has to be made
- * @param int $timeout Max seconds to wait; default is "waitTimeout" given to __construct()
+ * @param int|null $timeout Max seconds to wait; default is "waitTimeout" given to __construct()
* @return bool
*/
protected function doWait( $index, $open = false, $timeout = null ) {
}
public function getConnectionRef( $db, $groups = [], $domain = false, $flags = 0 ) {
- $domain = ( $domain !== false ) ? $domain : $this->localDomain;
+ $domain = $this->resolveDomainID( $domain );
return new DBConnRef( $this, $this->getConnection( $db, $groups, $domain, $flags ) );
}
public function getLazyConnectionRef( $db, $groups = [], $domain = false, $flags = 0 ) {
- $domain = ( $domain !== false ) ? $domain : $this->localDomain;
+ $domain = $this->resolveDomainID( $domain );
return new DBConnRef( $this, [ $db, $groups, $domain, $flags ] );
}
public function getMaintenanceConnectionRef( $db, $groups = [], $domain = false, $flags = 0 ) {
- $domain = ( $domain !== false ) ? $domain : $this->localDomain;
+ $domain = $this->resolveDomainID( $domain );
return new MaintainableDBConnRef(
$this, $this->getConnection( $db, $groups, $domain, $flags ) );
* See: https://github.com/phpredis/phpredis#scan
*
* @param int &$iterator
- * @param string $pattern
- * @param int $count
+ * @param string|null $pattern
+ * @param int|null $count
* @return array $res
*/
public function scan( &$iterator, $pattern = null, $count = null ) {
*
* @param string $key
* @param int &$iterator
- * @param string $pattern
- * @param int $count
+ * @param string|null $pattern
+ * @param int|null $count
* @return array $res
*/
public function sScan( $key, &$iterator, $pattern = null, $count = null ) {
*
* @param string $key
* @param int &$iterator
- * @param string $pattern
- * @param int $count
+ * @param string|null $pattern
+ * @param int|null $count
* @return array $res
*/
public function hScan( $key, &$iterator, $pattern = null, $count = null ) {
*
* @param string $key
* @param int &$iterator
- * @param string $pattern
- * @param int $count
+ * @param string|null $pattern
+ * @param int|null $count
* @return array $res
*/
public function zScan( $key, &$iterator, $pattern = null, $count = null ) {
*
* @param string $server A hostname/port combination or the absolute path of a UNIX socket.
* If a hostname is specified but no port, port 6379 will be used.
- * @param LoggerInterface $logger PSR-3 logger intance. [optional]
+ * @param LoggerInterface|null $logger PSR-3 logger intance. [optional]
* @return RedisConnRef|bool Returns false on failure
* @throws MWException
*/
* Adjust or reset the connection handle read timeout value
*
* @param Redis $conn
- * @param int $timeout Optional
+ * @param int|null $timeout Optional
*/
public function resetTimeout( Redis $conn, $timeout = null ) {
$conn->setOption( Redis::OPT_READ_TIMEOUT, $timeout ?: $this->readTimeout );
/**
* Insert the entry into the `logging` table.
*
- * @param IDatabase $dbw
+ * @param IDatabase|null $dbw
* @return int ID of the log entry
* @throws MWException
*/
* @param int|string $year Use 0 to start with no year preselected.
* @param int|string $month A month in the 1..12 range. Use 0 to start with no month
* preselected.
- * @param array $filter
+ * @param array|null $filter
* @param string $tagFilter Tag to select by default
- * @param string $action
+ * @param string|null $action
*/
public function showOptions( $types = [], $user = '', $page = '', $pattern = false, $year = 0,
$month = 0, $filter = null, $tagFilter = '', $action = null
*
* @param stdClass $row
* @param int $field
- * @param User $user User to check, or null to use $wgUser
+ * @param User|null $user User to check, or null to use $wgUser
* @return bool
*/
public static function userCan( $row, $field, User $user = null ) {
*
* @param int $bitfield Current field
* @param int $field
- * @param User $user User to check, or null to use $wgUser
+ * @param User|null $user User to check, or null to use $wgUser
* @return bool
*/
public static function userCanBitfield( $bitfield, $field, User $user = null ) {
*
* @param IDatabase $db
* @param string $audience Public/user
- * @param User $user User to check, or null to use $wgUser
+ * @param User|null $user User to check, or null to use $wgUser
* @return string|bool String on success, false on failure.
*/
public static function getExcludeClause( $db, $audience = 'public', User $user = null ) {
/**
* Helper to make a link to the page, taking the plaintext
* value in consideration.
- * @param Title $title The page
+ * @param Title|null $title The page
* @param array $parameters Query parameters
* @param string|null $html Linktext of the link as raw html
* @throws MWException
*
* @param int|RecentChange $rc Change identifier or RecentChange object
* @param bool $auto Was this patrol event automatic?
- * @param User $user User performing the action or null to use $wgUser
- * @param string|string[] $tags Change tags to add to the patrol log entry
+ * @param User|null $user User performing the action or null to use $wgUser
+ * @param string|string[]|null $tags Change tags to add to the patrol log entry
* ($user should be able to add the specified tags before this is called)
*
* @return bool
/**
* @param string $address String with an email address, or a User object
- * @param string $name Human-readable name if a string address is given
- * @param string $realName Human-readable real name if a string address is given
+ * @param string|null $name Human-readable name if a string address is given
+ * @param string|null $realName Human-readable real name if a string address is given
*/
function __construct( $address, $name = null, $realName = null ) {
if ( is_object( $address ) && $address instanceof User ) {
*
* @param string $text
* @param string $mime
- * @param array $params
+ * @param array|null $params
* @return array
*/
function getThumbType( $text, $mime, $params = null ) {
*
* @param string $ext Extension of original file
* @param string $mime MIME type of original file
- * @param array $params Handler specific rendering parameters
+ * @param array|null $params Handler specific rendering parameters
* @return array Thumbnail extension and MIME type
*/
function getThumbType( $ext, $mime, $params = null ) {
}
/**
- * @param string $title
+ * @param string|null $title
* @param string|array $params Query parameters to add
* @return array
*/
/**
* @param string $ext
* @param string $mime
- * @param array $params
+ * @param array|null $params
* @return bool
*/
function getThumbType( $ext, $mime, $params = null ) {
*
* @param string $ext
* @param string $mime
- * @param array $params
+ * @param array|null $params
* @return array
*/
function getThumbType( $ext, $mime, $params = null ) {
/**
* Constructor and clear the article
* @param Title $title Reference to a Title object.
- * @param int $oldId Revision ID, null to fetch from request, zero for current
+ * @param int|null $oldId Revision ID, null to fetch from request, zero for current
*/
public function __construct( Title $title, $oldId = null ) {
$this->mOldId = $oldId;
* @since 1.16 (r52326) for LiquidThreads
*
* @param int|null $oldid Revision ID or null
- * @param User $user The relevant user
+ * @param User|null $user The relevant user
* @return ParserOutput|bool ParserOutput or false if the given revision ID is not found
*/
public function getParserOutput( $oldid = null, User $user = null ) {
/**
* @param string $reason
* @param bool $suppress
- * @param int $u1 Unused
- * @param bool $u2 Unused
+ * @param int|null $u1 Unused
+ * @param bool|null $u2 Unused
* @param string &$error
* @return bool
*/
*/
use MediaWiki\MediaWikiServices;
-use Wikimedia\Rdbms\ResultWrapper;
+use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Storage\RevisionStore;
+use MediaWiki\Storage\SqlBlobStore;
+use Wikimedia\Assert\Assert;
+use Wikimedia\Rdbms\IResultWrapper;
use Wikimedia\Rdbms\IDatabase;
/**
$this->config = $config;
}
+ /**
+ * @return RevisionStore
+ */
+ private function getRevisionStore() {
+ // TODO: Refactor: delete()/undelete() should live in a PageStore service;
+ // Methods in PageArchive and RevisionStore that deal with archive revisions
+ // should move into an ArchiveStore service (but could still be implemented
+ // together with RevisionStore).
+ return MediaWikiServices::getInstance()->getRevisionStore();
+ }
+
public function doesWrites() {
return true;
}
* wrapper with (ar_namespace, ar_title, count) fields, ordered by page
* namespace/title.
*
- * @return ResultWrapper
+ * @deprecated since 1.32.
+ *
+ * @return IResultWrapper
*/
public static function listAllPages() {
+ wfDeprecated( __METHOD__, '1.32' );
+
$dbr = wfGetDB( DB_REPLICA );
return self::listPages( $dbr, '' );
* Returns result wrapper with (ar_namespace, ar_title, count) fields.
*
* @param string $term Search term
- * @return ResultWrapper
+ * @return IResultWrapper
*/
public static function listPagesBySearch( $term ) {
$title = Title::newFromText( $term );
* Returns result wrapper with (ar_namespace, ar_title, count) fields.
*
* @param string $prefix Title prefix
- * @return ResultWrapper
+ * @return IResultWrapper
*/
public static function listPagesByPrefix( $prefix ) {
$dbr = wfGetDB( DB_REPLICA );
/**
* @param IDatabase $dbr
* @param string|array $condition
- * @return bool|ResultWrapper
+ * @return bool|IResultWrapper
*/
protected static function listPages( $dbr, $condition ) {
return $dbr->select(
* List the revisions of the given page. Returns result wrapper with
* various archive table fields.
*
- * @return ResultWrapper
+ * @return IResultWrapper
*/
public function listRevisions() {
- $revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
+ $revisionStore = $this->getRevisionStore();
$queryInfo = $revisionStore->getArchiveQueryInfo();
$conds = [
'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDBkey(),
];
- $options = [ 'ORDER BY' => 'ar_timestamp DESC' ];
+
+ // NOTE: ordering by ar_timestamp and ar_id, to remove ambiguity.
+ // XXX: Ideally, we would be ordering by ar_timestamp and ar_rev_id, but since we
+ // don't have an index on ar_rev_id, that causes a file sort.
+ $options = [ 'ORDER BY' => 'ar_timestamp DESC, ar_id DESC' ];
ChangeTags::modifyDisplayQuery(
$queryInfo['tables'],
* Returns a result wrapper with various filearchive fields, or null
* if not a file page.
*
- * @return ResultWrapper
+ * @return IResultWrapper
* @todo Does this belong in Image for fuller encapsulation?
*/
public function listFiles() {
/**
* Return a Revision object containing data for the deleted revision.
- * Note that the result *may* or *may not* have a null page ID.
+ *
+ * @deprecated since 1.32, use getArchivedRevision() instead.
*
* @param string $timestamp
* @return Revision|null
*/
public function getRevision( $timestamp ) {
$dbr = wfGetDB( DB_REPLICA );
- $arQuery = Revision::getArchiveQueryInfo();
+ $rec = $this->getRevisionByConditions(
+ [ 'ar_timestamp' => $dbr->timestamp( $timestamp ) ]
+ );
+ return $rec ? new Revision( $rec ) : null;
+ }
+
+ /**
+ * Return the archived revision with the given ID.
+ *
+ * @param int $revId
+ * @return Revision|null
+ */
+ public function getArchivedRevision( $revId ) {
+ // Protect against code switching from getRevision() passing in a timestamp.
+ Assert::parameterType( 'integer', $revId, '$revId' );
+
+ $rec = $this->getRevisionByConditions( [ 'ar_rev_id' => $revId ] );
+ return $rec ? new Revision( $rec ) : null;
+ }
+
+ /**
+ * @param array $conditions
+ * @param array $options
+ *
+ * @return RevisionRecord|null
+ */
+ private function getRevisionByConditions( array $conditions, array $options = [] ) {
+ $dbr = wfGetDB( DB_REPLICA );
+ $arQuery = $this->getRevisionStore()->getArchiveQueryInfo();
+
+ $conditions = $conditions + [
+ 'ar_namespace' => $this->title->getNamespace(),
+ 'ar_title' => $this->title->getDBkey(),
+ ];
$row = $dbr->selectRow(
$arQuery['tables'],
$arQuery['fields'],
- [
- 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey(),
- 'ar_timestamp' => $dbr->timestamp( $timestamp )
- ],
+ $conditions,
__METHOD__,
- [],
+ $options,
$arQuery['joins']
);
if ( $row ) {
- return Revision::newFromArchiveRow( $row, [ 'title' => $this->title ] );
+ return $this->getRevisionStore()->newRevisionFromArchiveRow( $row, 0, $this->title );
}
return null;
// Check the previous deleted revision...
$row = $dbr->selectRow( 'archive',
- 'ar_timestamp',
+ [ 'ar_id', 'ar_timestamp' ],
[ 'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDBkey(),
'ar_timestamp < ' .
'ORDER BY' => 'ar_timestamp DESC',
'LIMIT' => 1 ] );
$prevDeleted = $row ? wfTimestamp( TS_MW, $row->ar_timestamp ) : false;
+ $prevDeletedId = $row ? intval( $row->ar_rev_id ) : null;
$row = $dbr->selectRow( [ 'page', 'revision' ],
[ 'rev_id', 'rev_timestamp' ],
if ( $prevLive && $prevLive > $prevDeleted ) {
// Most prior revision was live
- return Revision::newFromId( $prevLiveId );
+ $rec = $this->getRevisionStore()->getRevisionById( $prevLiveId );
+ $rec = $rec ? new Revision( $rec ) : null;
} elseif ( $prevDeleted ) {
// Most prior revision was deleted
- return $this->getRevision( $prevDeleted );
+ $rec = $this->getArchivedRevision( $prevDeletedId );
+ } else {
+ $rec = null;
}
- // No prior revision on this page.
- return null;
+ return $rec;
}
/**
- * Get the text from an archive row containing ar_text_id
+ * Get the text from an archive row containing ar_text_id.
+ *
+ * @deprecated since 1.32. In the MCR schema, ar_text_id no longer exists.
+ * Calling code should switch to getArchiveRevision().
+ *
+ * @todo remove in 1.33
*
- * @deprecated since 1.31
* @param object $row Database row
* @return string
*/
public function getTextFromRow( $row ) {
- $dbr = wfGetDB( DB_REPLICA );
- $text = $dbr->selectRow( 'text',
- [ 'old_text', 'old_flags' ],
- [ 'old_id' => $row->ar_text_id ],
- __METHOD__ );
+ wfDeprecated( __METHOD__, '1.32' );
+
+ if ( empty( $row->ar_text_id ) ) {
+ throw new InvalidArgumentException( '$row->ar_text_id must be set and not empty!' );
+ }
+
+ $address = SqlBlobStore::makeAddressFromTextId( $row->ar_text_id );
+ $blobStore = MediaWikiServices::getInstance()->getBlobStore();
- return Revision::getRevisionText( $text );
+ return $blobStore->getBlob( $address );
}
/**
*
* If there are no archived revisions for the page, returns NULL.
*
+ * @note this bypasses any audience checks.
+ *
+ * @deprecated since 1.32. For compatibility with the MCR schema,
+ * calling code should switch to getLastRevisionId() and getArchiveRevision().
+ *
+ * @todo remove in 1.33
+ *
* @return string|null
*/
public function getLastRevisionText() {
+ wfDeprecated( __METHOD__, '1.32' );
+
+ $revId = $this->getLastRevisionId();
+
+ if ( $revId ) {
+ $rev = $this->getArchivedRevision( $revId );
+ $content = $rev->getContent( RevisionRecord::RAW );
+ return $content->serialize();
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the ID of the latest deleted revision.
+ *
+ * @return int|false The revision's ID, or false if there is no deleted revision.
+ */
+ public function getLastRevisionId() {
$dbr = wfGetDB( DB_REPLICA );
- $row = $dbr->selectRow(
- [ 'archive', 'text' ],
- [ 'old_text', 'old_flags' ],
+ $revId = $dbr->selectField(
+ 'archive',
+ 'ar_rev_id',
[ 'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDBkey() ],
__METHOD__,
- [ 'ORDER BY' => 'ar_timestamp DESC, ar_id DESC' ],
- [ 'text' => [ 'JOIN', 'old_id = ar_text_id' ] ]
+ [ 'ORDER BY' => 'ar_timestamp DESC, ar_id DESC' ]
);
- if ( $row ) {
- return Revision::getRevisionText( $row );
- }
-
- return null;
+ return $revId ? intval( $revId ) : false;
}
/**
* Quick check if any archived revisions are present for the page.
+ * This says nothing about whether the page currently exists in the page table or not.
*
* @return bool
*/
public function isDeleted() {
$dbr = wfGetDB( DB_REPLICA );
- $n = $dbr->selectField( 'archive', 'COUNT(ar_title)',
+ $row = $dbr->selectRow(
+ [ 'archive' ],
+ '1', // We don't care about the value. Allow the database to optimize.
[ 'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDBkey() ],
__METHOD__
);
- return ( $n > 0 );
+ return (bool)$row;
}
/**
* @param string $comment
* @param array $fileVersions
* @param bool $unsuppress
- * @param User $user User performing the action, or null to use $wgUser
- * @param string|string[] $tags Change tags to add to log entry
+ * @param User|null $user User performing the action, or null to use $wgUser
+ * @param string|string[]|null $tags Change tags to add to log entry
* ($user should be able to add the specified tags before this is called)
* @return array|bool array(number of file revisions restored, number of image revisions
* restored, log message) on success, false on failure.
$oldWhere['ar_timestamp'] = array_map( [ &$dbw, 'timestamp' ], $timestamps );
}
- $revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
+ $revisionStore = $this->getRevisionStore();
$queryInfo = $revisionStore->getArchiveQueryInfo();
$queryInfo['tables'][] = 'revision';
$queryInfo['fields'][] = 'rev_id';
if ( $latestRestorableRow !== null ) {
$oldPageId = (int)$latestRestorableRow->ar_page_id; // pass this to ArticleUndelete hook
- // grab the content to check consistency with global state before restoring the page.
- $revision = Revision::newFromArchiveRow( $latestRestorableRow,
- [
- 'title' => $article->getTitle(), // used to derive default content model
- ]
+ // Grab the content to check consistency with global state before restoring the page.
+ // XXX: The only current use case is Wikibase, which tries to enforce uniqueness of
+ // certain things across all pages. There may be a better way to do that.
+ $revision = $revisionStore->newRevisionFromArchiveRow(
+ $latestRestorableRow,
+ 0,
+ $this->title
);
- $user = User::newFromName( $revision->getUserText( Revision::RAW ), false );
- $content = $revision->getContent( Revision::RAW );
- // NOTE: article ID may not be known yet. prepareSave() should not modify the database.
- $status = $content->prepareSave( $article, 0, -1, $user );
- if ( !$status->isOK() ) {
- $dbw->endAtomic( __METHOD__ );
+ // TODO: use User::newFromUserIdentity from If610c68f4912e
+ // TODO: The User isn't used for anything in prepareSave()! We should drop it.
+ $user = User::newFromName( $revision->getUser( RevisionRecord::RAW )->getName(), false );
- return $status;
+ foreach ( $revision->getSlotRoles() as $role ) {
+ $content = $revision->getContent( $role, RevisionRecord::RAW );
+
+ // NOTE: article ID may not be known yet. prepareSave() should not modify the database.
+ $status = $content->prepareSave( $article, 0, -1, $user );
+ if ( !$status->isOK() ) {
+ $dbw->endAtomic( __METHOD__ );
+
+ return $status;
+ }
}
}
$newid = false; // newly created page ID
$restored = 0; // number of revisions restored
- /** @var Revision $revision */
+ /** @var RevisionRecord|null $revision */
$revision = null;
$restoredPages = [];
// If there are no restorable revisions, we can skip most of the steps.
if ( $makepage ) {
// Check the state of the newest to-be version...
if ( !$unsuppress
- && ( $latestRestorableRow->ar_deleted & Revision::DELETED_TEXT )
+ && ( $latestRestorableRow->ar_deleted & RevisionRecord::DELETED_TEXT )
) {
$dbw->endAtomic( __METHOD__ );
if ( $latestRestorableRow->ar_timestamp > $previousTimestamp ) {
// Check the state of the newest to-be version...
if ( !$unsuppress
- && ( $latestRestorableRow->ar_deleted & Revision::DELETED_TEXT )
+ && ( $latestRestorableRow->ar_deleted & RevisionRecord::DELETED_TEXT )
) {
$dbw->endAtomic( __METHOD__ );
}
// Insert one revision at a time...maintaining deletion status
// unless we are specifically removing all restrictions...
- $revision = Revision::newFromArchiveRow( $row,
+ $revision = $revisionStore->newRevisionFromArchiveRow(
+ $row,
+ 0,
+ $this->title,
[
'page' => $pageId,
- 'title' => $this->title,
'deleted' => $unsuppress ? 0 : $row->ar_deleted
- ] );
+ ]
+ );
// This will also copy the revision to ip_changes if it was an IP edit.
- $revision->insertOn( $dbw );
+ $revisionStore->insertRevisionOn( $revision, $dbw );
$restored++;
+ $legacyRevision = new Revision( $revision );
Hooks::run( 'ArticleRevisionUndeleted',
- [ &$this->title, $revision, $row->ar_page_id ] );
+ [ &$this->title, $legacyRevision, $row->ar_page_id ] );
$restoredPages[$row->ar_page_id] = true;
}
if ( $restored ) {
$created = (bool)$newid;
// Attach the latest revision to the page...
- $wasnew = $article->updateIfNewerOn( $dbw, $revision );
+ // XXX: updateRevisionOn should probably move into a PageStore service.
+ $wasnew = $article->updateIfNewerOn( $dbw, $legacyRevision );
if ( $created || $wasnew ) {
// Update site stats, link tables, etc
+ // TODO: use DerivedPageDataUpdater from If610c68f4912e!
$article->doEditUpdates(
- $revision,
- User::newFromName( $revision->getUserText( Revision::RAW ), false ),
+ $legacyRevision,
+ User::newFromName( $revision->getUser( RevisionRecord::RAW )->getName(), false ),
[
'created' => $created,
'oldcountable' => $oldcountable,
* Revision::FOR_PUBLIC to be displayed to all users
* Revision::FOR_THIS_USER to be displayed to $wgUser
* Revision::RAW get the text regardless of permissions
- * @param User $user User object to check for, only if FOR_THIS_USER is passed
+ * @param User|null $user User object to check for, only if FOR_THIS_USER is passed
* to the $audience parameter
* @return Content|null The content of the current revision
*
* Revision::FOR_PUBLIC to be displayed to all users
* Revision::FOR_THIS_USER to be displayed to the given user
* Revision::RAW get the text regardless of permissions
- * @param User $user User object to check for, only if FOR_THIS_USER is passed
+ * @param User|null $user User object to check for, only if FOR_THIS_USER is passed
* to the $audience parameter
* @return int User ID for the user that made the last article revision
*/
* Revision::FOR_PUBLIC to be displayed to all users
* Revision::FOR_THIS_USER to be displayed to the given user
* Revision::RAW get the text regardless of permissions
- * @param User $user User object to check for, only if FOR_THIS_USER is passed
+ * @param User|null $user User object to check for, only if FOR_THIS_USER is passed
* to the $audience parameter
* @return User|null
*/
* Revision::FOR_PUBLIC to be displayed to all users
* Revision::FOR_THIS_USER to be displayed to the given user
* Revision::RAW get the text regardless of permissions
- * @param User $user User object to check for, only if FOR_THIS_USER is passed
+ * @param User|null $user User object to check for, only if FOR_THIS_USER is passed
* to the $audience parameter
* @return string Username of the user that made the last article revision
*/
* Revision::FOR_PUBLIC to be displayed to all users
* Revision::FOR_THIS_USER to be displayed to the given user
* Revision::RAW get the text regardless of permissions
- * @param User $user User object to check for, only if FOR_THIS_USER is passed
+ * @param User|null $user User object to check for, only if FOR_THIS_USER is passed
* to the $audience parameter
* @return string Comment stored for the last article revision
*/
* @param IDatabase $dbw
* @param Revision $revision For ID number, and text used to set
* length and redirect status fields
- * @param int $lastRevision If given, will not overwrite the page field
+ * @param int|null $lastRevision If given, will not overwrite the page field
* when different from the currently set value.
* Giving 0 indicates the new page flag should be set on.
- * @param bool $lastRevIsRedirect If given, will optimize adding and
+ * @param bool|null $lastRevIsRedirect If given, will optimize adding and
* removing rows in redirect table.
* @return bool Success; false if the page row was missing or page_latest changed
*/
* to perform updates, if the edit was already saved.
* @param RevisionSlotsUpdate|null $forUpdate The new content to be saved by the edit (pre PST),
* if the edit was not yet saved.
+ * @param bool $forEdit Only re-use if the cached DerivedPageDataUpdater has the current
+ * revision as the edit's parent revision. This ensures that the same
+ * DerivedPageDataUpdater cannot be re-used for two consecutive edits.
*
* @return DerivedPageDataUpdater
*/
private function getDerivedDataUpdater(
User $forUser = null,
RevisionRecord $forRevision = null,
- RevisionSlotsUpdate $forUpdate = null
+ RevisionSlotsUpdate $forUpdate = null,
+ $forEdit = false
) {
if ( !$forRevision && !$forUpdate ) {
// NOTE: can't re-use an existing derivedDataUpdater if we don't know what the caller is
&& !$this->derivedDataUpdater->isReusableFor(
$forUser,
$forRevision,
- $forUpdate
+ $forUpdate,
+ $forEdit ? $this->getLatest() : null
)
) {
$this->derivedDataUpdater = null;
* @since 1.32
*
* @param User $user
+ * @param RevisionSlotsUpdate|null $forUpdate If given, allows any cached ParserOutput
+ * that may already have been returned via getDerivedDataUpdater to be re-used.
*
* @return PageUpdater
*/
- public function newPageUpdater( User $user ) {
+ public function newPageUpdater( User $user, RevisionSlotsUpdate $forUpdate = null ) {
global $wgAjaxEditStash, $wgUseAutomaticEditSummaries, $wgPageCreationLog;
$pageUpdater = new PageUpdater(
$user,
$this, // NOTE: eventually, PageUpdater should not know about WikiPage
- $this->getDerivedDataUpdater( $user ),
+ $this->getDerivedDataUpdater( $user, null, $forUpdate, true ),
$this->getDBLoadBalancer(),
$this->getRevisionStore()
);
* restores or repeats. The new revision is expected to have the exact same content as
* the given original revision. This is used with rollbacks and with dummy "null" revisions
* which are created to record things like page moves.
- * @param User $user The user doing the edit
- * @param string $serialFormat IGNORED.
+ * @param User|null $user The user doing the edit
+ * @param string|null $serialFormat IGNORED.
* @param array|null $tags Change tags to apply to this edit
* Callers are responsible for permission checks
* (with ChangeTags::canAddTagsAccompanyingChange)
$flags = ( $flags & ~EDIT_MINOR );
}
+ $slotsUpdate = new RevisionSlotsUpdate();
+ $slotsUpdate->modifyContent( 'main', $content );
+
// NOTE: while doEditContent() executes, callbacks to getDerivedDataUpdater and
// prepareContentForEdit will generally use the DerivedPageDataUpdater that is also
// used by this PageUpdater. However, there is no guarantee for this.
- $updater = $this->newPageUpdater( $user );
+ $updater = $this->newPageUpdater( $user, $slotsUpdate );
$updater->setContent( 'main', $content );
$updater->setOriginalRevisionId( $originalRevId );
$updater->setUndidRevisionId( $undidRevId );
* @param int &$cascade Set to false if cascading protection isn't allowed.
* @param string $reason
* @param User $user The user updating the restrictions
- * @param string|string[] $tags Change tags to add to the pages and protection log entries
+ * @param string|string[]|null $tags Change tags to add to the pages and protection log entries
* ($user should be able to add the specified tags before this is called)
* @return Status Status object; if action is taken, $status->value is the log_id of the
* protection log entry.
* @param string $reason Delete reason for deletion log
* @param bool $suppress Suppress all revisions and log the deletion in
* the suppression log instead of the deletion log
- * @param int $u1 Unused
- * @param bool $u2 Unused
+ * @param int|null $u1 Unused
+ * @param bool|null $u2 Unused
* @param array|string &$error Array of errors to append to
- * @param User $user The deleting user
+ * @param User|null $user The deleting user
* @return bool True if successful
*/
public function doDeleteArticle(
* @param string $reason Delete reason for deletion log
* @param bool $suppress Suppress all revisions and log the deletion in
* the suppression log instead of the deletion log
- * @param int $u1 Unused
- * @param bool $u2 Unused
+ * @param int|null $u1 Unused
+ * @param bool|null $u2 Unused
* @param array|string &$error Array of errors to append to
- * @param User $deleter The deleting user
+ * @param User|null $deleter The deleting user
* @param array $tags Tags to apply to the deletion action
* @param string $logsubtype
* @return Status Status object; if successful, $status->value is the log_id of the
// Fetch all rows in case the DB needs that to properly lock them.
}
- // Get all of the page revisions
+ // If SCHEMA_COMPAT_WRITE_OLD is set, also select all extra fields we still write,
+ // so we can copy it to the archive table.
+ // We know the fields exist, otherwise SCHEMA_COMPAT_WRITE_OLD could not function.
+ if ( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
+ $revQuery['fields'][] = 'rev_text_id';
+
+ if ( $wgContentHandlerUseDB ) {
+ $revQuery['fields'][] = 'rev_content_model';
+ $revQuery['fields'][] = 'rev_content_format';
+ }
+ }
+
+ // Get all of the page revisions
$res = $dbw->select(
$revQuery['tables'],
$revQuery['fields'],
] + $commentStore->insert( $dbw, 'ar_comment', $comment )
+ $actorMigration->getInsertValues( $dbw, 'ar_user', $user );
- if ( $wgMultiContentRevisionSchemaMigrationStage < MIGRATION_NEW ) {
+ if ( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
$rowInsert['ar_text_id'] = $row->rev_text_id;
- }
- if (
- $wgContentHandlerUseDB &&
- $wgMultiContentRevisionSchemaMigrationStage <= MIGRATION_WRITE_BOTH
- ) {
- $rowInsert['ar_content_model'] = $row->rev_content_model;
- $rowInsert['ar_content_format'] = $row->rev_content_format;
+ if ( $wgContentHandlerUseDB ) {
+ $rowInsert['ar_content_model'] = $row->rev_content_model;
+ $rowInsert['ar_content_format'] = $row->rev_content_format;
+ }
}
+
$rowsInsert[] = $rowInsert;
$revids[] = $row->rev_id;
* Make a self-link
*
* @param string $text Text displayed on the link
- * @param array $query Associative array of parameter to be in the query string
- * @param string $type Link type used to create additional attributes, like "rel", "class" or
+ * @param array|null $query Associative array of parameter to be in the query string
+ * @param string|null $type Link type used to create additional attributes, like "rel", "class" or
* "title". Valid values (non-exhaustive list): 'first', 'last', 'prev', 'next', 'asc', 'desc'.
* @return string HTML fragment
*/
/**
* @param Parser $parser
* @param string $date
- * @param string $defaultPref
+ * @param string|null $defaultPref
*
* @return string
*/
*
* @param Parser $parser
* @param string $s The text to encode.
- * @param string $arg (optional): The type of encoding.
+ * @param string|null $arg (optional): The type of encoding.
* @return string
*/
public static function urlencode( $parser, $s = '', $arg = null ) {
/**
* @param Parser $parser
* @param string $num
- * @param string $arg
+ * @param string|null $arg
* @return string
*/
public static function formatnum( $parser, $num = '', $arg = null ) {
* Note: function name changed to "mwnamespace" rather than "namespace"
* to not break PHP 5.3
* @param Parser $parser
- * @param string $title
+ * @param string|null $title
* @return mixed|string
*/
public static function mwnamespace( $parser, $title = null ) {
* Functions to get and normalize pagenames, corresponding to the magic words
* of the same names
* @param Parser $parser
- * @param string $title
+ * @param string|null $title
* @return string
*/
public static function pagename( $parser, $title = null ) {
* can't be called too many times per page.
* @param Parser $parser
* @param string $name
- * @param string $arg1
- * @param string $arg2
+ * @param string|null $arg1
+ * @param string|null $arg2
* @return string
*/
public static function pagesincategory( $parser, $name = '', $arg1 = null, $arg2 = null ) {
*
* @param Parser $parser
* @param string $page Name of page to check (Default: empty string)
- * @param string $raw Should number be human readable with commas or just number
+ * @param string|null $raw Should number be human readable with commas or just number
* @return string
*/
public static function pagesize( $parser, $page = '', $raw = null ) {
/**
* Get the pageid of a specified page
* @param Parser $parser
- * @param string $title Title to get the pageid from
+ * @param string|null $title Title to get the pageid from
* @return int|null|string
* @since 1.23
*/
/**
* Get the id from the last revision of a specified page.
* @param Parser $parser
- * @param string $title Title to get the id from
+ * @param string|null $title Title to get the id from
* @return int|null|string
* @since 1.23
*/
/**
* Get the day from the last revision of a specified page.
* @param Parser $parser
- * @param string $title Title to get the day from
+ * @param string|null $title Title to get the day from
* @return string
* @since 1.23
*/
/**
* Get the day with leading zeros from the last revision of a specified page.
* @param Parser $parser
- * @param string $title Title to get the day from
+ * @param string|null $title Title to get the day from
* @return string
* @since 1.23
*/
/**
* Get the month with leading zeros from the last revision of a specified page.
* @param Parser $parser
- * @param string $title Title to get the month from
+ * @param string|null $title Title to get the month from
* @return string
* @since 1.23
*/
/**
* Get the month from the last revision of a specified page.
* @param Parser $parser
- * @param string $title Title to get the month from
+ * @param string|null $title Title to get the month from
* @return string
* @since 1.23
*/
/**
* Get the year from the last revision of a specified page.
* @param Parser $parser
- * @param string $title Title to get the year from
+ * @param string|null $title Title to get the year from
* @return string
* @since 1.23
*/
/**
* Get the timestamp from the last revision of a specified page.
* @param Parser $parser
- * @param string $title Title to get the timestamp from
+ * @param string|null $title Title to get the timestamp from
* @return string
* @since 1.23
*/
/**
* Get the user from the last revision of a specified page.
* @param Parser $parser
- * @param string $title Title to get the user from
+ * @param string|null $title Title to get the user from
* @return string
* @since 1.23
*/
* @param ParserOptions $options
* @param bool $linestart
* @param bool $clearState
- * @param int $revid Number to pass in {{REVISIONID}}
+ * @param int|null $revid Number to pass in {{REVISIONID}}
* @return ParserOutput A ParserOutput
*/
public function parse(
* Also removes comments.
* Do not call this function recursively.
* @param string $text
- * @param Title $title
+ * @param Title|null $title
* @param ParserOptions $options
* @param int|null $revid
* @param bool|PPFrame $frame
/**
* Accessor/mutator for the Title object
*
- * @param Title $x Title object or null to just get the current one
+ * @param Title|null $x Title object or null to just get the current one
* @return Title
*/
public function Title( $x = null ) {
/**
* Accessor/mutator for the ParserOptions object
*
- * @param ParserOptions $x New value or null to just get the current one
+ * @param ParserOptions|null $x New value or null to just get the current one
* @return ParserOptions Current ParserOptions object
*/
public function Options( $x = null ) {
* @since 1.21
* @param string|bool $url Optional URL, to extract the domain from for rel =>
* nofollow if appropriate
- * @param Title $title Optional Title, for wgNoFollowNsExceptions lookups
+ * @param Title|null $title Optional Title, for wgNoFollowNsExceptions lookups
* @return string|null Rel attribute for $url
*/
public static function getExternalLinkRel( $url = false, $title = null ) {
* @warning For interaction with the parser cache, use
* WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
* ParserOptions::newCanonical() instead.
- * @param User $user
- * @param Language $lang
+ * @param User|null $user
+ * @param Language|null $lang
*/
public function __construct( $user = null, $lang = null ) {
if ( $user === null ) {
*
* @since 1.17
* @param string[] $forOptions
- * @param Title $title Used to get the content language of the page (since r97636)
+ * @param Title|null $title Used to get the content language of the page (since r97636)
* @return string Page rendering hash
*/
public function optionsHash( $forOptions, $title = null ) {
* Add one or more variables to be set in mw.config in JavaScript.
*
* @param string|array $keys Key or array of key/value pairs.
- * @param mixed $value [optional] Value of the configuration variable.
+ * @param mixed|null $value [optional] Value of the configuration variable.
* @since 1.23
*/
public function addJsConfigVars( $keys, $value = null ) {
* Cleans up HTML, removes dangerous tags and attributes, and
* removes HTML comments
* @param string $text
- * @param callable $processCallback Callback to do any variable or parameter
+ * @param callable|null $processCallback Callback to do any variable or parameter
* replacements in HTML attribute values
* @param array|bool $args Arguments for the processing callback
* @param array $extratags For any extra tags to include
* @param array $removetags For any tags (default or extra) to exclude
- * @param callable $warnCallback (Deprecated) Callback allowing the
+ * @param callable|null $warnCallback (Deprecated) Callback allowing the
* addition of a tracking category when bad input is encountered.
* DO NOT ADD NEW PARAMETERS AFTER $warnCallback, since it will be
* removed shortly.
* @param int $revid ID of the revision being parsed.
* @param bool $useParserCache Whether to use the parser cache.
* operation.
- * @param Content|string $content Content to parse or null to load it; may
+ * @param Content|string|null $content Content to parse or null to load it; may
* also be given as a wikitext string, for BC.
*/
public function __construct( WikiPage $page, ParserOptions $parserOptions,
if ( $this->config->get( 'StructuredChangeFiltersShowPreference' ) ) {
$defaultPreferences['rcenhancedfilters-disable'] = [
'type' => 'toggle',
- 'section' => 'rc/opt-out',
+ 'section' => 'rc/optoutrc',
'label-message' => 'rcfilters-preference-label',
'help-message' => 'rcfilters-preference-help',
];
if ( $this->config->get( 'StructuredChangeFiltersShowWatchlistPreference' ) ) {
$defaultPreferences['wlenhancedfilters-disable'] = [
'type' => 'toggle',
- 'section' => 'watchlist/opt-out',
+ 'section' => 'watchlist/optoutwatchlist',
'label-message' => 'rcfilters-watchlist-preference-label',
'help-message' => 'rcfilters-watchlist-preference-help',
];
abstract public function scopedProfileIn( $section );
/**
- * @param SectionProfileCallback &$section
+ * @param SectionProfileCallback|null &$section
*/
public function scopedProfileOut( SectionProfileCallback &$section = null ) {
$section = null;
/**
* Register core modules and runs registration hooks.
- * @param Config $config [optional]
- * @param LoggerInterface $logger [optional]
+ * @param Config|null $config [optional]
+ * @param LoggerInterface|null $logger [optional]
*/
public function __construct( Config $config = null, LoggerInterface $logger = null ) {
global $IP;
* Register a module with the ResourceLoader system.
*
* @param mixed $name Name of module as a string or List of name/object pairs as an array
- * @param array $info Module info array. For backwards compatibility with 1.17alpha,
+ * @param array|null $info Module info array. For backwards compatibility with 1.17alpha,
* this may also be a ResourceLoaderModule object. Optional when using
* multiple-registration calling style.
* @throws MWException If a duplicate module registration is attempted
* Source IDs are typically the same as the Wiki ID or database name (e.g. lowercase a-z).
*
* @param array|string $id Source ID (string), or [ id1 => loadUrl, id2 => loadUrl, ... ]
- * @param string|array $loadUrl load.php url (string), or array with loadUrl key for
+ * @param string|array|null $loadUrl load.php url (string), or array with loadUrl key for
* backwards-compatibility.
* @throws MWException
*/
* Set the state of modules with the given names to the given states
*
* @param string $name
- * @param string $state
+ * @param string|null $state
* @return string JavaScript code
*/
public static function makeLoaderStateScript( $name, $state = null ) {
* Registers modules with the given names and parameters.
*
* @param string $name Module name
- * @param string $version Module version hash
- * @param array $dependencies List of module names on which this module depends
- * @param string $group Group which the module is in
- * @param string $source Source of the module, or 'local' if not foreign
- * @param string $skip Script body of the skip function
+ * @param string|null $version Module version hash
+ * @param array|null $dependencies List of module names on which this module depends
+ * @param string|null $group Group which the module is in
+ * @param string|null $source Source of the module, or 'local' if not foreign
+ * @param string|null $skip Script body of the skip function
* @return string JavaScript code
*/
public static function makeLoaderRegisterScript( $name, $version = null,
* Register sources with the given IDs and properties.
*
* @param string $id Source ID
- * @param string $loadUrl load.php url
+ * @param string|null $loadUrl load.php url
* @return string JavaScript code
*/
public static function makeLoaderSourcesScript( $id, $loadUrl = null ) {
* startup module if the client has adequate support for MediaWiki JavaScript code.
*
* @param string $script JavaScript code
- * @param string $nonce [optional] Content-Security-Policy nonce (from OutputPage::getCSPNonce)
+ * @param string|null $nonce [optional] Content-Security-Policy nonce
+ * (from OutputPage::getCSPNonce)
* @return string|WrappedString HTML
*/
public static function makeInlineScript( $script, $nonce = null ) {
* @param array $modules
* @param string $lang
* @param string $skin
- * @param string $user
- * @param string $version
+ * @param string|null $user
+ * @param string|null $version
* @param bool $debug
- * @param string $only
+ * @param string|null $only
* @param bool $printable
* @param bool $handheld
* @param array $extraQuery
* @param array $modules One or more module names
* @param string $only ResourceLoaderModule TYPE_ class constant
* @param array $extraQuery [optional] Array with extra query parameters for the request
- * @param string $nonce [optional] Content-Security-Policy nonce (from OutputPage::getCSPNonce)
+ * @param string|null $nonce [optional] Content-Security-Policy nonce
+ * (from OutputPage::getCSPNonce)
* @return string|WrappedStringList HTML
*/
public static function makeLoad( ResourceLoaderContext $mainContext, array $modules, $only,
*
* @param array $options List of options; if not given or empty, an empty module will be
* constructed
- * @param string $localBasePath Base path to prepend to all local paths in $options. Defaults
- * to $IP
- * @param string $remoteBasePath Base path to prepend to all remote paths in $options. Defaults
- * to $wgResourceBasePath
+ * @param string|null $localBasePath Base path to prepend to all local paths in $options.
+ * Defaults to $IP
+ * @param string|null $remoteBasePath Base path to prepend to all remote paths in $options.
+ * Defaults to $wgResourceBasePath
*
* Below is a description for the $options array:
* @throws InvalidArgumentException
* Implementation note: the amount of global state used in this function is staggering.
*
* @param array $options Module definition
- * @param string $localBasePath Path to use if not provided in module definition. Defaults
+ * @param string|null $localBasePath Path to use if not provided in module definition. Defaults
* to $IP
- * @param string $remoteBasePath Path to use if not provided in module definition. Defaults
+ * @param string|null $remoteBasePath Path to use if not provided in module definition. Defaults
* to $wgResourceBasePath
* @return array Array( localBasePath, remoteBasePath )
*/
*
* @param array $list List of lists to select from
* @param string $key Key to look for in $map
- * @param string $fallback Key to look for in $list if $key doesn't exist
+ * @param string|null $fallback Key to look for in $list if $key doesn't exist
* @return array List of elements from $map which matched $key or $fallback,
* or an empty list in case of no match
*/
* @private
* @param array $styles Map of media type to file paths to read, remap, and concatenate
* @param bool $flip
- * @param ResourceLoaderContext $context
+ * @param ResourceLoaderContext|null $context
* @return array List of concatenated and remapped CSS data from $styles,
* keyed by media type
* @throws MWException
protected $images = [];
protected $defaultColor = null;
+ protected $useDataURI = true;
protected $variants = [];
protected $prefix = null;
protected $selectorWithoutVariant = '.{prefix}-{name}';
*
* @param array $options List of options; if not given or empty, an empty module will be
* constructed
- * @param string $localBasePath Base path to prepend to all local paths in $options. Defaults
+ * @param string|null $localBasePath Base path to prepend to all local paths in $options. Defaults
* to $IP
*
* Below is a description for the $options array:
$this->{$member} = $option;
break;
+ case 'useDataURI':
+ $this->{$member} = (bool)$option;
+ break;
case 'defaultColor':
case 'prefix':
case 'selectorWithoutVariant':
$script,
$variant = null
) {
- $imageDataUri = $image->getDataUri( $context, $variant, 'original' );
+ $imageDataUri = $this->useDataURI ? $image->getDataUri( $context, $variant, 'original' ) : false;
$primaryUrl = $imageDataUri ?: $image->getUrl( $context, $script, $variant, 'original' );
$declarations = $this->getCssDeclarations(
$primaryUrl,
* Extract a local base path from module definition information.
*
* @param array $options Module definition
- * @param string $localBasePath Path to use if not provided in module definition. Defaults
+ * @param string|null $localBasePath Path to use if not provided in module definition. Defaults
* to $IP
* @return string Local base path
*/
}
/**
- * @param ResourceLoaderContext $context
+ * @param ResourceLoaderContext|null $context
* @return array
*/
public function getDependencies( ResourceLoaderContext $context = null ) {
}
/**
- * @param ResourceLoaderContext $context
+ * @param ResourceLoaderContext|null $context
* @return array
*/
public function getDependencies( ResourceLoaderContext $context = null ) {
* Note: It is expected that $context will be made non-optional in the near
* future.
*
- * @param ResourceLoaderContext $context
+ * @param ResourceLoaderContext|null $context
* @return array List of module names as strings
*/
public function getDependencies( ResourceLoaderContext $context = null ) {
}
/**
- * @param ResourceLoaderContext $context
+ * @param ResourceLoaderContext|null $context
* @return array
*/
public function getDependencies( ResourceLoaderContext $context = null ) {
protected $targets = [ 'desktop', 'mobile' ];
/**
- * @param ResourceLoaderContext $context
+ * @param ResourceLoaderContext|null $context
* @return array List of module names as strings
*/
public function getDependencies( ResourceLoaderContext $context = null ) {
protected $group;
/**
- * @param array $options For back-compat, this can be omitted in favour of overwriting getPages.
+ * @param array|null $options For back-compat, this can be omitted in favour of overwriting
+ * getPages.
*/
public function __construct( array $options = null ) {
if ( is_null( $options ) ) {
protected $db;
/**
- * @param IDatabase $db The database to search from
+ * @param IDatabase|null $db The database to search from
*/
public function __construct( IDatabase $db = null ) {
if ( $db ) {
*
* @todo This isn't ideal, we'd really like to have content-specific handling here
* @param Title $t Title we're indexing
- * @param Content $c Content of the page to index
+ * @param Content|null $c Content of the page to index
* @return string
*/
public function getTextFromContent( Title $t, Content $c = null ) {
/**
* Create SearchEngine of the given type.
- * @param string $type
+ * @param string|null $type
* @return SearchEngine
*/
public function create( $type = null ) {
* @param string $text
* @param int $start
* @param int $end
- * @param int &$posStart (out) actual start position
- * @param int &$posEnd (out) actual end position
+ * @param int|null &$posStart (out) actual start position
+ * @param int|null &$posEnd (out) actual end position
* @return string
*/
function extract( $text, $start, $end, &$posStart = null, &$posEnd = null ) {
}
--$contextlines;
// truncate function changes ... to relevant i18n message.
- $pre = $wgContLang->truncate( $m[1], - $contextchars, '...', false );
+ $pre = $wgContLang->truncateForVisual( $m[1], - $contextchars, '...', false );
if ( count( $m ) < 3 ) {
$post = '';
} else {
- $post = $wgContLang->truncate( $m[3], $contextchars, '...', false );
+ $post = $wgContLang->truncateForVisual( $m[3], $contextchars, '...', false );
}
$found = $m[2];
* @ingroup Search
*/
+use Wikimedia\Rdbms\IResultWrapper;
+
/**
* Search engine hook base class for Mssql (ConText).
* @ingroup Search
* @param int $id
* @param string $title
* @param string $text
- * @return bool|ResultWrapper
+ * @return bool|IResultWrapper
*/
function update( $id, $title, $text ) {
// We store the column data as UTF-8 byte order marked binary stream
*
* @param int $id
* @param string $title
- * @return bool|ResultWrapper
+ * @return bool|IResultWrapper
*/
function updateTitle( $id, $title ) {
$table = $this->db->tableName( 'searchindex' );
* Return a new SearchResult and initializes it with a title.
*
* @param Title $title
- * @param SearchResultSet $parentSet
+ * @param SearchResultSet|null $parentSet
* @return SearchResult
*/
public static function newFromTitle( $title, SearchResultSet $parentSet = null ) {
* @param \WebRequest $request
* @param string $key
* @param string $prefix
- * @param mixed $default
+ * @param mixed|null $default
* @return mixed
*/
protected function getCookie( $request, $key, $prefix, $default = null ) {
/**
* Fetch a value from the session
* @param string|int $key
- * @param mixed $default Returned if $this->exists( $key ) would be false
+ * @param mixed|null $default Returned if $this->exists( $key ) would be false
* @return mixed
*/
public function get( $key, $default = null ) {
/**
* Fetch a value from the session that was set with self::setSecret()
* @param string|int $key
- * @param mixed $default Returned if $this->exists( $key ) would be false or decryption fails
+ * @param mixed|null $default Returned if $this->exists( $key ) would be false or decryption fails
* @return mixed
*/
public function getSecret( $key, $default = null ) {
/**
* Set the "logged out" timestamp
- * @param int $ts
+ * @param int|null $ts
*/
public function setLoggedOutTimestamp( $ts = null ) {
$ts = (int)$ts;
namespace MediaWiki\Session;
use Language;
+use Message;
/**
* This exists to make IDEs happy, so they don't see the
* or MediaWikiServices::getSiteLookup() instead.
*
* @param null $sitesTable IGNORED
- * @param null $cache IGNORED
+ * @param BagOStuff|null $cache IGNORED
*
* @return SiteStore
*/
* If you pass "flat" as an option then the returned array will be a flat array
* of footer icons instead of a key/value array of footerlinks arrays broken
* up into categories.
- * @param string $option
+ * @param string|null $option
* @return array|mixed
*/
function getFooterLinks( $option = null ) {
* in the list of footer icons. This is mostly useful for skins which only
* display the text from footericons instead of the images and don't want a
* duplicate copyright statement because footerlinks already rendered one.
- * @param string $option
+ * @param string|null $option
* @return array
*/
function getFooterIcons( $option = null ) {
protected $config;
/**
- * @param Config $config
+ * @param Config|null $config
*/
function __construct( Config $config = null ) {
$this->data = [];
* Gets the template data requested
* @since 1.22
* @param string $name Key for the data
- * @param mixed $default Optional default (or null)
+ * @param mixed|null $default Optional default (or null)
* @return mixed The value of the data requested or the deafult
*/
public function get( $name, $default = null ) {
/**
* Outputs the HTML generated by other functions.
- * @param OutputPage $out
+ * @param OutputPage|null $out
*/
abstract function outputPage( OutputPage $out = null );
/**
* @param array $data
- * @param string $nonce OutputPage::getCSPNonce()
+ * @param string|null $nonce OutputPage::getCSPNonce()
* @return string|WrappedString HTML
*/
static function makeVariablesScript( $data, $nonce = null ) {
}
/**
- * @param OutputPage $out Defaults to $this->getOutput() if left as null
+ * @param OutputPage|null $out Defaults to $this->getOutput() if left as null
* @return string
*/
function subPageSubtitle( $out = null ) {
* the current page, if the section is included from a template)
* @param string $section The designation of the section being pointed to,
* to be included in the link, like "§ion=$section"
- * @param string $tooltip The tooltip to use for the link: will be escaped
+ * @param string|null $tooltip The tooltip to use for the link: will be escaped
* and wrapped in the 'editsectionhint' message
* @param string $lang Language code
* @return string HTML to use for edit link
/**
* initialize various variables and generate the template
*
- * @param OutputPage $out
+ * @param OutputPage|null $out
*/
function outputPage( OutputPage $out = null ) {
Profiler::instance()->setTemplated( true );
*
* @since 1.31
*
- * @param array $personalTools
+ * @param array|null $personalTools
* @param array $options
* @return string
*/
* Used to preserve POST data over a HTTP redirect.
*
* @param array $data
- * @param bool $wasPosted
+ * @param bool|null $wasPosted
*/
protected function setRequest( array $data, $wasPosted = null ) {
$request = $this->getContext()->getRequest();
* Load or initialize $authAction, $authRequests and $subPage.
* Subclasses should call this from execute() or otherwise ensure the variables are initialized.
* @param string $subPage Subpage of the special page.
- * @param string $authAction Override auth action specified in request (this is useful
+ * @param string|null $authAction Override auth action specified in request (this is useful
* when the form needs to be changed from <action> to <action>_CONTINUE after a successful
* authentication step)
* @param bool $reset Regenerate the requests even if a cached version is available
* Get an array value, or a default if it does not exist.
* @param array $array
* @param string $fieldName
- * @param mixed $default
+ * @param mixed|null $default
* @return mixed
*/
protected static function getField( array $array, $fieldName, $default = null ) {
];
/**
- * @param WebRequest $request
+ * @param WebRequest|null $request
*/
public function __construct( $request = null ) {
wfDeprecated( 'LoginForm', '1.27' );
/**
* Get or set whether this special page is listed in Special:SpecialPages
* @since 1.6
- * @param bool $x
+ * @param bool|null $x
* @return bool
*/
function listed( $x = null ) {
/**
* Whether the special page is being evaluated via transclusion
- * @param bool $x
+ * @param bool|null $x
* @return bool
*/
function including( $x = null ) {
* Note that this does not in any way check that the user is authorized to use this special page
* (use checkPermissions() for that).
*
- * @param string $level A security level. Can be an arbitrary string, defaults to the page name.
+ * @param string|null $level A security level. Can be an arbitrary string, defaults to the page
+ * name.
* @return bool False means a redirect to the reauthentication page has been set and processing
* of the special page should be aborted.
* @throws ErrorPageError If the security level cannot be met, even with reauthentication.
* Return categorised listable special pages which are available
* for the current user, and everyone.
*
- * @param User $user User object to check permissions, $wgUser will be used
+ * @param User|null $user User object to check permissions, $wgUser will be used
* if not provided
* @return array ( string => Specialpage )
*/
* @todo Should be in Block.php?
* @param string $par Subpage parameter passed to setup, or data value from
* the HTMLForm
- * @param WebRequest $request Optionally try and get data from a request too
+ * @param WebRequest|null $request Optionally try and get data from a request too
* @return array [ User|string|null, Block::TYPE_ constant|null ]
*/
public static function getTargetAndType( $par, WebRequest $request = null ) {
/**
* Process the form on POST submission.
* @param array $data
- * @param HTMLForm $form
+ * @param HTMLForm|null $form
* @return bool|array True for success, false for didn't-try, array of errors on failure
*/
public function onSubmit( array $data, HTMLForm $form = null ) {
* @return Status
*/
private function attemptChange( User $user, $newaddr ) {
- $authManager = AuthManager::singleton();
-
if ( $newaddr != '' && !Sanitizer::validateEmail( $newaddr ) ) {
return Status::newFatal( 'invalidemailaddress' );
}
*
* @param User $user
* @param string $editToken Edit token
- * @param Config $config optional for backwards compatibility
+ * @param Config|null $config optional for backwards compatibility
* @return string|null Null on success or string on error
*/
public static function getPermissionsError( $user, $editToken, Config $config = null ) {
$a = [];
if ( isset( $resetRoutes['username'] ) && $resetRoutes['username'] ) {
$a['Username'] = [
- 'type' => 'text',
+ 'type' => 'user',
'label-message' => 'passwordreset-username',
];
/**
* @param int $namespace
* @param string $prefix
- * @param string $from List all pages from this name (default false)
+ * @param string|null $from List all pages from this name (default false)
*/
protected function showPrefixChunk( $namespace, $prefix, $from = null ) {
global $wgContLang;
* specific title if one is set.
*
* @param string $permission
- * @param User $user
+ * @param User|null $user
* @return bool
*/
protected function isAllowed( $permission, User $user = null ) {
class SpecialUpload extends SpecialPage {
/**
* Get data POSTed through the form and assign them to the object
- * @param WebRequest $request Data posted.
+ * @param WebRequest|null $request Data posted.
*/
public function __construct( $request = null ) {
parent::__construct( 'Upload', 'upload' );
* @param string $license
* @param string $copyStatus
* @param string $source
- * @param Config $config Configuration object to load data from
+ * @param Config|null $config Configuration object to load data from
* @return string
*/
public static function getInitialPageText( $comment = '', $license = '',
}
function cutoffselector( $options ) {
- // Cast everything to strings immediately, so that we know all of the values have the same
- // precision, and can be compared with '==='. 2/24 has a few more decimal places than its
- // default string representation, for example, and would confuse comparisons.
-
- // Misleadingly, the 'days' option supports hours too.
- $days = array_map( 'strval', [ 1 / 24, 2 / 24, 6 / 24, 12 / 24, 1, 3, 7 ] );
-
- $userWatchlistOption = (string)$this->getUser()->getOption( 'watchlistdays' );
- // add the user preference, if it isn't available already
- if ( !in_array( $userWatchlistOption, $days ) && $userWatchlistOption !== '0' ) {
- $days[] = $userWatchlistOption;
- }
-
- $maxDays = (string)$this->maxDays;
- // add the maximum possible value, if it isn't available already
- if ( !in_array( $maxDays, $days ) ) {
- $days[] = $maxDays;
- }
-
- $selected = (string)$options['days'];
+ $selected = (float)$options['days'];
if ( $selected <= 0 ) {
- $selected = $maxDays;
- }
-
- // add the currently selected value, if it isn't available already
- if ( !in_array( $selected, $days ) ) {
- $days[] = $selected;
- }
+ $selected = $this->maxDays;
+ }
+
+ $selectedHours = round( $selected * 24 );
+
+ $hours = array_unique( array_filter( [
+ 1,
+ 2,
+ 6,
+ 12,
+ 24,
+ 72,
+ 168,
+ 24 * (float)$this->getUser()->getOption( 'watchlistdays', 0 ),
+ 24 * $this->maxDays,
+ $selectedHours
+ ] ) );
+ asort( $hours );
- $select = new XmlSelect( 'days', 'days', $selected );
+ $select = new XmlSelect( 'days', 'days', $selectedHours / 24 );
- asort( $days );
- foreach ( $days as $value ) {
- if ( $value < 1 ) {
- $name = $this->msg( 'hours' )->numParams( $value * 24 )->text();
+ foreach ( $hours as $value ) {
+ if ( $value < 24 ) {
+ $name = $this->msg( 'hours' )->numParams( $value )->text();
} else {
- $name = $this->msg( 'days' )->numParams( $value )->text();
+ $name = $this->msg( 'days' )->numParams( $value / 24 )->text();
}
- $select->addOption( $name, $value );
+ $select->addOption( $name, $value / 24 );
}
return $select->getHTML() . "\n<br />\n";
* @return string
*/
function getLegend( $key ) {
- $legend = parent::getLegend( $key );
+ $aliasKey = ( $key === 'optoutwatchlist' || $key === 'optoutrc' ) ? 'opt-out' : $key;
+ $legend = parent::getLegend( $aliasKey );
Hooks::run( 'PreferencesGetLegend', [ $this, $key, &$legend ] );
return $legend;
}
* @return string
*/
function getLegend( $key ) {
- $legend = parent::getLegend( $key );
+ $aliasKey = ( $key === 'optoutwatchlist' || $key === 'optoutrc' ) ? 'opt-out' : $key;
+ $legend = parent::getLegend( $aliasKey );
Hooks::run( 'PreferencesGetLegend', [ $this, $key, &$legend ] );
return $legend;
}
private $blockStatusByUid;
/**
- * @param IContextSource $context
+ * @param IContextSource|null $context
* @param FormOptions $opts
*/
function __construct( IContextSource $context = null, FormOptions $opts ) {
* @ingroup Pager
*/
use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\IResultWrapper;
use Wikimedia\Rdbms\FakeResultWrapper;
protected $userGroupCache;
/**
- * @param IContextSource $context
- * @param array $par (Default null)
- * @param bool $including Whether this page is being transcluded in
+ * @param IContextSource|null $context
+ * @param array|null $par (Default null)
+ * @param bool|null $including Whether this page is being transcluded in
* another page
*/
function __construct( IContextSource $context = null, $par = null, $including = null ) {
* Perform a clean/repair operation
* @param string $text HTML to check
* @param bool $stderr Whether to read result from STDERR rather than STDOUT
- * @param int &$retval Exit code (-1 on internal error)
+ * @param int|null &$retval Exit code (-1 on internal error)
* @return null|string
* @throws MWException
*/
*
* @param string $text HTML to check
* @param bool $stderr Whether to read result from STDERR rather than STDOUT
- * @param int &$retval Exit code (-1 on internal error)
+ * @param int|null &$retval Exit code (-1 on internal error)
* @return string|null
*/
protected function cleanWrapped( $text, $stderr = false, &$retval = null ) {
*
* @param string $text HTML to check
* @param bool $stderr Whether to read result from error status instead of output
- * @param int &$retval Exit code (-1 on internal error)
+ * @param int|null &$retval Exit code (-1 on internal error)
* @return string|null
*/
protected function cleanWrapped( $text, $stderr = false, &$retval = null ) {
*
* @param string $text HTML to check
* @param bool $stderr Whether to read result from error status instead of output
- * @param int &$retval Exit code (-1 on internal error)
+ * @param int|null &$retval Exit code (-1 on internal error)
* @return string|null
*/
protected function cleanWrapped( $text, $stderr = false, &$retval = null ) {
/**
* @param string $errorMessage Localisation message describing the error (since MW 1.26)
- * @param string $titleText The invalid title text (since MW 1.26)
+ * @param string|null $titleText The invalid title text (since MW 1.26)
* @param string[] $errorMessageParameters Additional parameters for the error message.
* $titleText will be appended if it's not null. (since MW 1.26)
*/
/**
* @param string $tempPath File system path to temporary file containing the upload
- * @param int $fileSize
+ * @param int|null $fileSize
*/
protected function setTempFile( $tempPath, $fileSize = null ) {
$this->mTempPath = $tempPath;
* file again.
*
* @deprecated since 1.28 Use tryStashFile() instead
- * @param User $user
+ * @param User|null $user
* @return UploadStashFile Stashed file
* @throws UploadStashBadPathException
* @throws UploadStashFileException
/**
* Implementation for stashFile() and tryStashFile().
*
- * @param User $user
+ * @param User|null $user
* @return UploadStashFile Stashed file
*/
protected function doStashFile( User $user = null ) {
* @todo Replace this with a whitelist filter!
* @param string $element
* @param array $attribs
- * @param array $data
+ * @param array|null $data
* @return bool
*/
public function checkSvgScriptCallback( $element, $attribs, $data = null ) {
* (should replace it eventually).
*
* @param FileRepo $repo
- * @param User $user (default null)
+ * @param User|null $user
*/
public function __construct( FileRepo $repo, $user = null ) {
// this might change based on wiki's configuration.
* database, along with other metadata.
*
* @param string $path Path to file you want stashed
- * @param string $sourceType The type of upload that generated this file
+ * @param string|null $sourceType The type of upload that generated this file
* (currently, I believe, 'file' or null)
* @throws UploadStashBadPathException
* @throws UploadStashFileException
/**
* @param string|array|MessageSpecifier $messageSpec See Message::newFromSpecifier
* @param int $code Exception code
- * @param Exception|Throwable $previous The previous exception used for the exception chaining.
+ * @param Exception|Throwable|null $previous The previous exception used for the exception
+ * chaining.
*/
public function __construct( $messageSpec, $code = 0, $previous = null ) {
$this->messageSpec = $messageSpec;
*
* @since 1.29 Fourth argument for displayPassword removed.
* @param User $performingUser The user that does the password reset
- * @param string $username The user whose password is reset
- * @param string $email Alternative way to specify the user
+ * @param string|null $username The user whose password is reset
+ * @param string|null $email Alternative way to specify the user
* @return StatusValue Will contain the passwords as a username => password array if the
* $displayPassword flag was set
* @throws LogicException When the user is not allowed to perform the action
* will be loaded once more from the database when accessing them.
*
* @param stdClass $row A row from the user table
- * @param array $data Further data to load into the object (see User::loadFromRow for valid keys)
+ * @param array|null $data Further data to load into the object
+ * (see User::loadFromRow for valid keys)
* @return User
*/
public static function newFromRow( $row, $data = null ) {
* @param string|array $groups A single group name or an array of group names
* @param int $limit Max number of users to return. The actual limit will never exceed 5000
* records; larger values are ignored.
- * @param int $after ID the user to start after
+ * @param int|null $after ID the user to start after
* @return UserArrayFromResult
*/
public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
* Initialize this object from a row from the user table.
*
* @param stdClass $row Row from the user table to load.
- * @param array $data Further user data to load into the object
+ * @param array|null $data Further user data to load into the object
*
* user_groups Array of arrays or stdClass result rows out of the user_groups
* table. Previously you were supposed to pass an array of strings
/**
* Update the 'You have new messages!' status.
* @param bool $val Whether the user has new messages
- * @param Revision $curRev New, as yet unseen revision of the user talk
+ * @param Revision|null $curRev New, as yet unseen revision of the user talk
* page. Ignored if null or !$val.
*/
public function setNewtalk( $val, $curRev = null ) {
* Get the user's current setting for a given option.
*
* @param string $oname The option to check
- * @param string|array $defaultOverride A default value returned if the option does not exist
+ * @param string|array|null $defaultOverride A default value returned if the option does not exist
* @param bool $ignoreHidden Whether to ignore the effects of $wgHiddenPrefs
* @return string|array|int|null User's current value for the option
* @see getBoolOption()
*
* @see User::listOptionKinds
* @param IContextSource $context
- * @param array $options Assoc. array with options keys to check as keys.
+ * @param array|null $options Assoc. array with options keys to check as keys.
* Defaults to $this->mOptions.
* @return array The key => kind mapping data
*/
* never expire.)
*
* @param string $group Name of the group to add
- * @param string $expiry Optional expiry timestamp in any format acceptable to
+ * @param string|null $expiry Optional expiry timestamp in any format acceptable to
* wfTimestamp(), or null if the group assignment should not expire
* @return bool
*/
*
* @param WebRequest|null $request WebRequest object to use; $wgRequest will be used if null
* is passed.
- * @param bool $secure Whether to force secure/insecure cookies or use default
+ * @param bool|null $secure Whether to force secure/insecure cookies or use default
* @param bool $rememberMe Whether to add a Token cookie for elongated sessions
*/
public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
* @param string $val Input value to compare
* @param string|array $salt Optional function-specific data for hashing
* @param WebRequest|null $request Object to use or null to use $wgRequest
- * @param int $maxage Fail tokens older than this, in seconds
+ * @param int|null $maxage Fail tokens older than this, in seconds
* @return bool Whether the token matches
*/
public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
* @param string $val Input value to compare
* @param string|array $salt Optional function-specific data for hashing
* @param WebRequest|null $request Object to use or null to use $wgRequest
- * @param int $maxage Fail tokens older than this, in seconds
+ * @param int|null $maxage Fail tokens older than this, in seconds
* @return bool Whether the token matches
*/
public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
* @param string $body Message body
* @param User|null $from Optional sending user; if unspecified, default
* $wgPasswordSender will be used.
- * @param string $replyto Reply-To address
+ * @param string|null $replyto Reply-To address
* @return Status
*/
public function sendMail( $subject, $body, $from = null, $replyto = null ) {
/**
* Load the user options either from cache, the database or an array
*
- * @param array $data Rows for the current user out of the user_properties table
+ * @param array|null $data Rows for the current user out of the user_properties table
*/
protected function loadOptions( $data = null ) {
global $wgContLang;
/**
* @param int $userId The ID of the user who belongs to the group
- * @param string $group The internal group name
+ * @param string|null $group The internal group name
* @param string|null $expiry Timestamp of expiry in TS_MW format, or null if no expiry
*/
public function __construct( $userId = 0, $group = null, $expiry = null ) {
private $ipAddresses = [ '0.0.0.0/0', '::/0' ];
/**
- * @param array $restrictions
+ * @param array|null $restrictions
* @throws InvalidArgumentException
*/
protected function __construct( array $restrictions = null ) {
* in the file to satisfy the request, an exception will be thrown.
*
* @param int $start The byte offset of the start of the block.
- * @param int $length The number of bytes to return. If omitted, the remainder
+ * @param int|null $length The number of bytes to return. If omitted, the remainder
* of the file will be returned.
*
* @return string
* @since 1.31
*
* @param User $user
- * @param int $unreadLimit
+ * @param int|null $unreadLimit
*
* @return int|bool The number of unread notifications
* true if greater than or equal to $unreadLimit
$textarea = new MultilineTextInputWidget( [
'name' => $this->inputName,
'value' => implode( "\n", $this->usersArray ),
- 'rows' => 25,
+ 'rows' => 10,
] );
$this->prependContent( $textarea );
}
/**
* Parse rules and flags.
- * @param string $variant Variant language code
+ * @param string|null $variant Variant language code
*/
public function parse( $variant = null ) {
if ( !$variant ) {
* @param string $ts 14-character timestamp
* YYYYMMDDHHMMSS
* 01234567890123
- * @param DateTimeZone $zone Timezone of $ts
+ * @param DateTimeZone|null $zone Timezone of $ts
* @param int &$ttl The amount of time (in seconds) the output may be cached for.
* Only makes sense if $ts is the current time.
* @todo handling of "o" format character for Iranian, Hebrew, Hijri & Thai?
# Add 543 years to the Gregorian calendar
# Months and days are identical
$gy_offset = $gy + 543;
+ # fix for dates between 1912 and 1941
+ # https://en.wikipedia.org/?oldid=836596673#New_year
+ if ( $gy >= 1912 && $gy <= 1940 ) {
+ if ( $gm <= 3 ) {
+ $gy_offset--;
+ }
+ $gm = ( $gm - 3 ) % 12;
+ }
} elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
# Minguo dates
# Deduct 1911 years from the Gregorian calendar
* match up with it.
*
* @param string $str The validated block duration in English
- * @param User $user User object to use timezone from or null for $wgUser
+ * @param User|null $user User object to use timezone from or null for $wgUser
* @param int $now Current timestamp, for formatting relative block durations
* @return string Somehow translated block duration
* @see LanguageFi.php for example implementation
* @return bool
*/
public function equals( Language $lang ) {
- return $lang->getCode() === $this->mCode;
+ return $lang === $this || $lang->getCode() === $this->mCode;
}
/**
/**
* Validate the variant
- * @param string $variant The variant to validate
+ * @param string|null $variant The variant to validate
* @return mixed Returns the variant if it is valid, null otherwise
*/
public function validateVariant( $variant = null ) {
/**
* @param string $str
- * @param User $user User object to use timezone from or null for $wgUser
+ * @param User|null $user User object to use timezone from or null for $wgUser
* @param int $now Current timestamp, for formatting relative block durations
* @return string
*/
"Macofe",
"MtDu",
"Abdeaitali",
- "Reda Benkhadra"
+ "Reda Benkhadra",
+ "Anass Sedrati"
]
},
"tog-underline": "Ṣetter l-lyanaṫ:",
"exif-giffilecomment": "ṫ-ṫĝliq dial milef GIF",
"exif-intellectualgenre": "noĝ l-madda",
"exif-scenecode": "cod mċhd IPTC",
+ "exif-orientation-1": "عادي",
"exif-orientation-4": "mḍwwer ofoqiyyen",
"exif-orientation-8": "mḍwwer 90° <abbr title=\"Ĝks (ṫ-ṫijah) Ĝaqarib s-Saĝa\">ĜĜS</abbr>",
"exif-componentsconfiguration-0": "ma kayn-ċ",
"welcomecreation-msg": "Creóse la to cuenta.\nNun t'escaezas de camudar les tos [[Special:Preferences|preferencies de {{SITENAME}}]].",
"yourname": "Nome d'usuariu:",
"userlogin-yourname": "Nome d'usuariu",
- "userlogin-yourname-ph": "Escriba'l so nome d'usuariu",
- "createacct-another-username-ph": "Escriba'l nome d'usuariu",
+ "userlogin-yourname-ph": "Escribe'l to nome d'usuariu",
+ "createacct-another-username-ph": "Escribe'l nome d'usuariu",
"yourpassword": "Contraseña:",
"userlogin-yourpassword": "Contraseña",
- "userlogin-yourpassword-ph": "Escriba la so contraseña",
- "createacct-yourpassword-ph": "Escriba una contraseña",
+ "userlogin-yourpassword-ph": "Escribe la contraseña",
+ "createacct-yourpassword-ph": "Escribe una contraseña",
"yourpasswordagain": "Escribi otra vuelta la contraseña:",
"createacct-yourpasswordagain": "Confirmar la contraseña",
- "createacct-yourpasswordagain-ph": "Escriba nuevamente la contraseña",
+ "createacct-yourpasswordagain-ph": "Escribe nuevamente la contraseña",
"userlogin-remembermypassword": "Caltener abierta la sesión",
"userlogin-signwithsecure": "Usar una conexón segura",
"cannotlogin-title": "Nun pudo aniciase sesión",
"logout": "Salir",
"userlogout": "Salir",
"notloggedin": "Nun aniciasti sesión",
- "userlogin-noaccount": "¿Nun tien una cuenta?",
+ "userlogin-noaccount": "¿Nun tienes una cuenta?",
"userlogin-joinproject": "Xunise a {{SITENAME}}",
"createaccount": "Crear una cuenta",
- "userlogin-resetpassword-link": "¿Escaeció la contraseña?",
+ "userlogin-resetpassword-link": "¿Escaecisti la contraseña?",
"userlogin-helplink2": "Ayuda del aniciu de sesión",
"userlogin-loggedin": "Yá anició sesión como {{GENDER:$1|$1}}.\nUtilice'l formulariu de más abaxo p'aniciar sesión como otru usuariu.",
"userlogin-reauth": "Tienes d'aniciar sesión de nueves pa comprobar que yes {{GENDER:$1|$1}}.",
"userlogin-createanother": "Crear otra cuenta",
"createacct-emailrequired": "Direición de corréu electrónicu",
"createacct-emailoptional": "Direición de corréu electrónicu (opcional)",
- "createacct-email-ph": "Escriba la so direición de corréu electrónicu",
- "createacct-another-email-ph": "Escriba la direición de corréu electrónicu",
+ "createacct-email-ph": "Escribe la to direición de corréu electrónicu",
+ "createacct-another-email-ph": "Escribe la direición de corréu electrónicu",
"createaccountmail": "Usar una contraseña al debalu temporal y unviala a la direición de corréu electrónicu conseñada",
"createaccountmail-help": "Pue usase pa crear una cuenta pa otra persona ensin saber la contraseña.",
"createacct-realname": "Nome real (opcional)",
"createacct-reason": "Motivu",
- "createacct-reason-ph": "Por qué quier crear otra cuenta",
+ "createacct-reason-ph": "Por qué vas crear otra cuenta",
"createacct-reason-help": "Mensaxe que s'amuesa nel rexistru de creación de cuentes",
"createacct-submit": "Crear la cuenta",
"createacct-another-submit": "Crear una cuenta",
"createacct-continue-submit": "Siguir cola creación de la cuenta",
"createacct-another-continue-submit": "Siguir cola creación de la cuenta",
- "createacct-benefit-heading": "{{SITENAME}} failu xente como vusté.",
+ "createacct-benefit-heading": "Persones como tu son les que construyen {{SITENAME}}.",
"createacct-benefit-body1": "{{PLURAL:$1|edición|ediciones}}",
"createacct-benefit-body2": "{{PLURAL:$1|páxina|páxines}}",
"createacct-benefit-body3": "{{PLURAL:$1|collaborador|collaboradores}} de recién",
"userexists": "El nome d'usuariu conseñáu yá ta usándose.\nPor favor escueyi un nome diferente.",
"loginerror": "Error d'aniciu de sesión",
"createacct-error": "Error de creación de cuenta",
- "createaccounterror": "Nun se pudo crear la cuenta: $1",
+ "createaccounterror": "Nun pudo crease la cuenta: $1",
"nocookiesnew": "La cuenta d'usuariu ta creada, pero nun aniciasti sesión.\n{{SITENAME}} usa «cookies» pa identificar a los usuarios.\nTienes les «cookies» desactivaes.\nPor favor actívales y anicia sesión col nuevu nome d'usuariu y contraseña.",
- "nocookieslogin": "{{SITENAME}} usa «cookies» pa identificar a los usuarios.\nTien les «cookies» desactivaes.\nPor favor activeles y vuelva a intentalo.",
- "nocookiesfornew": "La cuenta nun se creó porque nun pudimos confirmar l'orixe.\nComprueba que tienes activaes les «cookies», recarga esta páxina y vuelvi a intentalo.",
+ "nocookieslogin": "{{SITENAME}} usa «cookies» pa identificar a los usuarios.\nTien les «cookies» desactivaes.\nPor favor actívales y tenta otra vuelta.",
+ "nocookiesfornew": "La cuenta nun se creó porque nun pudimos confirmar l'orixe.\nComprueba que tienes activaes les «cookies», recarga esta páxina y tenta otra vuelta.",
"createacct-loginerror": "La cuenta creóse correchamente, pero nun pudo aniciase sesión automáticamente. Sigui col [[Special:UserLogin|accesu manual]].",
"noname": "Nun conseñasti un nome d'usuariu válidu.",
"loginsuccesstitle": "Identificáu",
"nouserspecified": "Has d'especificar un nome d'usuariu.",
"login-userblocked": "Esti usuariu ta bloquiáu. Nun se permite l'aniciu de sesión.",
"wrongpassword": "Escribisti un nome d'usuariu o contraseña incorreutu.\nTenta otra vuelta.",
- "wrongpasswordempty": "La contraseña taba en blanco.\nVuelvi a intentalo.",
+ "wrongpasswordempty": "La contraseña taba en blancu.\nTenta otra vuelta.",
"passwordtooshort": "Les contraseñes han de tener polo menos {{PLURAL:$1|1 caráuter|$1 caráuteres}}.",
"passwordtoolong": "Les contraseñes nun puen ser mayores de {{PLURAL:$1|1 caráuter|$1 caráuteres}}.",
"passwordtoopopular": "Les contraseñes más escoyíes de vezu nun pueden usase. Escueye una contraseña más difícil d'aldovinar.",
"throttled-mailpassword": "Yá s'unvió un corréu de reaniciu la clave {{PLURAL:$1|na postrer hora|nes postreres $1 hores}}.\nPa evitar abusos, namái s'unviará un corréu de reaniciu cada {{PLURAL:$1|hora|$1 hores}}.",
"mailerror": "Fallu al unviar el corréu: $1",
"acct_creation_throttle_hit": "Los visitantes d'esta wiki qu'usen la to direición IP yá crearon {{PLURAL:$1|1 cuenta|$1 cuentes}} nel periodu de $2, que ye'l máximu almitíu nesi tiempu.\nPoro, los visitantes qu'usen esta direición IP nun pueden crear más cuentes pol momentu.",
- "emailauthenticated": "La so direición de corréu electrónicu confirmóse'l $2 a les $3.",
- "emailnotauthenticated": "La so direición de corréu electrónicu inda nun se confirmó.\nNun s'unviará corréu pa nenguna de les funciones siguientes.",
+ "emailauthenticated": "La direición de corréu electrónicu confirmóse'l $2 a les $3.",
+ "emailnotauthenticated": "La direición de corréu electrónicu inda nun se confirmó.\nNun s'unviará corréu pa nenguna de les funciones siguientes.",
"noemailprefs": "Conseña una direición de corréu electrónicu nes tos preferencies pa que funcionen eses carauterístiques.",
"emailconfirmlink": "Confirmar la direición de corréu electrónicu",
"invalidemailaddress": "La direición de corréu electrónicu nun pue aceutase yá que paez tener un formatu inválidu.\nPor favor conseña una direición con formatu afayadizu o dexa baleru'l campu.",
"pt-createaccount": "Crear una cuenta",
"pt-userlogout": "Salir",
"php-mail-error-unknown": "Fallu desconocíu na función mail() de PHP.",
- "user-mail-no-addy": "Intentasti unviar un corréu electrónicu ensin direición de corréu.",
- "user-mail-no-body": "Trató d'unviar un corréu electrónicu con un cuerpu baleru o curtiu enforma.",
+ "user-mail-no-addy": "Tentasti unviar un corréu electrónicu ensin direición de corréu.",
+ "user-mail-no-body": "Tentasti unviar un corréu electrónicu col cuerpu vaciu o curtiu enforma.",
"changepassword": "Camudar la contraseña",
- "resetpass_announce": "P'acabar d'aniciar sesión, tien de definir equí una contraseña nueva.",
+ "resetpass_announce": "P'acabar d'aniciar sesión, tienes de configurar una contraseña nueva.",
"resetpass_text": "<!-- Amestar testu equí -->",
"resetpass_header": "Camudar la contraseña de la cuenta",
"oldpassword": "Contraseña antigua:",
"retypenew": "Vuelvi a escribir la contraseña nueva:",
"resetpass_submit": "Configurar la contraseña y aniciar sesión",
"changepassword-success": "Camudóse la contraseña.",
- "changepassword-throttled": "Ficisti demasiaos intentos d'aniciu de sesión recientes.\nPor favor espera $1 enantes d'intentalo otra vuelta.",
+ "changepassword-throttled": "Ficisti demasiaos intentos d'aniciar sesión de recien.\nPor favor espera $1 enantes de tentar otra vuelta.",
"botpasswords": "Contraseñes de bots",
"botpasswords-summary": "Les <em>contraseñes de bot</em> permiten l'accesu a una cuenta d'usuariu por aciu de la API sin usar les credenciales d'accesu de la cuenta principal. Los permisos d'usuariu disponibles al aniciar sesión con una contraseña de bot puen tar torgaos.\n\nSi nun sabes pa qué val esto, probablemente nun tendríes d'usalo. Naide tendría de pidite nunca que xeneres una d'estes y que-y la deas.",
"botpasswords-disabled": "Les contraseñes de bot tán desactivaes.",
"botpasswords-label-delete": "Desaniciar",
"botpasswords-label-resetpassword": "Reestablecer la contraseña",
"botpasswords-label-grants": "Permisos aplicables:",
- "botpasswords-help-grants": "Los permisos dan accesu a los permisos d'usuariu que yá tenga la cuenta. Activar un permisu equí nun da accesu a nengún permisu que la to cuenta nun tenga d'otra miente. Mira la [[Special:ListGrants|tabla de permisos]] pa más información.",
+ "botpasswords-help-grants": "Los permisos dan accesu a los permisos d'usuariu que yá tengas na cuenta. Activar un permisu equí nun da accesu a nengún permisu que la to cuenta nun tenga d'otra miente. Mira la [[Special:ListGrants|tabla de permisos]] pa más información.",
"botpasswords-label-grants-column": "Permitío",
"botpasswords-bad-appid": "El nome del bot \"$1\" nun ye válidu.",
"botpasswords-insert-failed": "Nun pudo amestase'l nome de bot «$1». ¿Taba añadíu yá?",
"resetpass-temp-emailed": "Anició sesión con un códigu temporal unviáu per corréu electrónicu.\nPa completar l'aniciu de sesión, tien de definir una nueva contraseña equí:",
"resetpass-temp-password": "Contraseña temporal:",
"resetpass-abort-generic": "Una estensión encaboxó'l cambiu de la contraseña.",
- "resetpass-expired": "La so contraseña caducó. Defina una nueva contraseña p'aniciar sesión.",
+ "resetpass-expired": "La to contraseña caducó. Configura una nueva contraseña p'aniciar sesión.",
"resetpass-expired-soft": "La contraseña caducó y precisa cambiase. Escueye agora una contraseña nueva, o pulsia «{{int:authprovider-resetpass-skip-label}}» pa cambiala sero.",
"resetpass-validity-soft": "La contraseña nun ye válida: $1\n\nEscueye agora una contraseña nueva, o pulsia «{{int:authprovider-resetpass-skip-label}}» pa cambiala sero.",
"passwordreset": "Reaniciar contraseña",
- "passwordreset-text-one": "Complete esti formulariu pa reaniciar la contraseña.",
- "passwordreset-text-many": "{{PLURAL:$1|Rellene unu de los campos pa recibir una contraseña temporal per corréu.}}",
+ "passwordreset-text-one": "Completa esti formulariu pa recibir per corréu una contraseña temporal.",
+ "passwordreset-text-many": "{{PLURAL:$1|Rellena unu de los campos pa recibir una contraseña temporal per corréu.}}",
"passwordreset-disabled": "Los reanicios de contraseña tán desactivaos nesta wiki.",
"passwordreset-emaildisabled": "Les funciones de corréu electrónicu tan desactivaes nesta wiki.",
"passwordreset-username": "Nome d'usuariu:",
"passwordreset-domain": "Dominiu:",
"passwordreset-email": "Direición de corréu electrónicu:",
"passwordreset-emailtitle": "Detalles de la cuenta en {{SITENAME}}",
- "passwordreset-emailtext-ip": "Dalguién (seique vusté, dende la direición IP $1)solicitó'l reaniciu de la so contraseña de {{SITENAME}} ($4).\n{{PLURAL:$3|La cuenta d'usuariu siguiente ta asociada|Les cuentes d'usuariu siguientes tán asociaes}}\na esta direición de corréu electrónicu:\n\n$2\n\n{{PLURAL:$3|Esta contraseña provisional caduca|Estes contraseñes provisionales caduquen}} {{PLURAL:$5|nun día|en $5 díes}}.\nTendría d'aniciar sesión y escoyer una contraseña nueva agora. Si esta solicitú la fizo otra persona,\no si recordó la clave orixinal y yá nun quier camudala, pue escaecer esti mensaxe y siguir\nusando la contraseña antigua.",
- "passwordreset-emailtext-user": "L'usuariu $1 de {{SITENAME}} solicitó un reaniciu de la so contraseña de {{SITENAME}} ($4). {{PLURAL:$3|La cuenta d'usuariu siguiente ta asociada|Les cuentes d'usuariu siguientes tán asociaes}} con esta direición de corréu electrónicu:\n\n$2\n\n{{PLURAL:$3|Esta contraseña provisional caduca|Estes contraseñes provisionales caduquen}} {{PLURAL:$5|nun día|en $5 díes}}.\nTendría d'aniciar sesión y escoyer una contraseña nueva agora. Si esta solicitú la fizo otra persona, o si recordó la clave orixinal y yá nun quier camudala, pue escaecer esti mensaxe y siguir usando la contraseña antigua.",
+ "passwordreset-emailtext-ip": "Dalguién (seique tu, dende la direición IP $1)solicitó'l reaniciu de la contraseña de {{SITENAME}} ($4).\n{{PLURAL:$3|La cuenta d'usuariu siguiente ta asociada|Les cuentes d'usuariu siguientes tán asociaes}}\na esta direición de corréu electrónicu:\n\n$2\n\n{{PLURAL:$3|Esta contraseña provisional caduca|Estes contraseñes provisionales caduquen}} {{PLURAL:$5|nun día|en $5 díes}}.\nTendríes d'aniciar sesión y escoyer una contraseña nueva agora. Si esta solicitú ye d'otra persona,\no si recordasti la clave orixinal y yá nun quies camudala, inora esti mensaxe y sigue\nusando la contraseña antigua.",
+ "passwordreset-emailtext-user": "L'usuariu $1 de {{SITENAME}} solicitó un reaniciu de la contraseña de {{SITENAME}} ($4). {{PLURAL:$3|La cuenta d'usuariu siguiente ta asociada|Les cuentes d'usuariu siguientes tán asociaes}} con esta direición de corréu electrónicu:\n\n$2\n\n{{PLURAL:$3|Esta contraseña provisional caduca|Estes contraseñes provisionales caduquen}} {{PLURAL:$5|nun día|en $5 díes}}.\nTendríes d'aniciar sesión y escoyer una contraseña nueva agora. Si esta solicitú ye d'otra persona, o si recordasti la clave orixinal y yá nun quies camudala, pues inorar esti mensaxe y siguir usando la contraseña antigua.",
"passwordreset-emailelement": "Nome d'usuariu: \n$1\n\nContraseña temporal: \n$2",
"passwordreset-emailsentemail": "Si esta direición de corréu electrónicu ta asociada cola to cuenta, unviaráse un corréu pa reaniciar la contraseña.",
"passwordreset-emailsentusername": "Si hai una direición de corréu electrónicu asociada con esti nome d'usuariu, unviaráse un corréu electrónicu pa reaniciar la contraseña.",
"Oldstoneage",
"Baloch Afghanistan",
"Mjbmr",
- "Macofe"
+ "Macofe",
+ "Matěj Suchánek"
]
},
"tog-underline": ":لینکانآ خط کش",
"yourdiff": "تفاوتان",
"copyrightwarning": "لطفا توجه بیت که کل نوشته یات ته {{SITENAME}} تحت $2 نشر بنت.(بچار په جزیات$1).\nاگه شما لوٹیت شمی نوشتانک اصلاح و دگه چهاپ مبنت، اچه آیانا ادان مهلیت.\nشما ما را قول دهیت که وتی چیزا بنویسیت یا چه یک دامین عمومی کپی کتگیت.\n''' نوشتانکی که کپی رایت دارند بی اجازه ادا هور مکنیت'''",
"copyrightwarning2": "لطفا توجه کنیت که کل مشارکاتن ته {{SITENAME}} شاید اصلاح, عوض و یا توسط دگه شرکت کننده آن حذف بنت.\nاگر شما نه لوٹیت شمی نوشتاک گون بی رحمی اصلاح مه بنت، اچه شما آیء ادان دیم مه دهیت.<br />\nشما هنچوش ما را قول دهیت که شما شی وت نوشتت یا ایء چه یک دامین عمومی یا هنچوشین آزاتین منبع کپی کتیت.(بچار $1 په جزییات).\n''' نوشتاکی که حق کپی دارنت بی اجازت دیم مه دهیت!'''",
- "longpageerror": "'''ارور: ائ سیاهگء که راهیگ کتگ ات {{PULAR:$1|یک کیلوبایت|$1 کیلوبایت}} اندازگ داریت. ائ اندازگ چه ماکسیممء {{PLURAL:$2|یک کیلوبایت|$2 کیلوبایت}} گیشتر انت.'''\nنبیت که آئرا ذخیرگ بکن ات.",
+ "longpageerror": "'''ارور: ائ سیاهگء که راهیگ کتگ ات {{PLURAL:$1|یک کیلوبایت|$1 کیلوبایت}} اندازگ داریت. ائ اندازگ چه ماکسیممء {{PLURAL:$2|یک کیلوبایت|$2 کیلوبایت}} گیشتر انت.'''\nنبیت که آئرا ذخیرگ بکن ات.",
"readonlywarning": "'''هژاری: دیتابیس پر جوڑ کتنء واسته کبل بیتگ انت، پمیشکا انیگء نبیت که وتی ادیتانء ذخیرگ بکن ات.'''\nاگان لوٹ ات وتی سیاهگء مان یک تکست فایلء کپی و پر آیوکء ذخیرگ بکن ات.\n\nهما ماسٹر که آئرا کبل کتگ ائ توضیحء درشان کتگ انت: $1",
"protectedpagewarning": "'''هوژاری: ائ تاک پراتکت بیتگ انت، گڑا تهنا کاربرانء گون ماسٹری حالتء توان انت که آئرا ادیت بکن انت'''\nآهرین سیاهگ که گون ائ بابتء همگرنچ انت جهلیگء آتکگ انت:",
"semiprotectedpagewarning": "'''بزان:''' ائ پیج کبل بیتگ تانکه تهنا رجستر کتگین کاربران بتوان انت آئرا ادیت بکن انت.\nآهرین سیاهگ که گون ائ بابتء همگرنچ انت جهلیگء آتکگ:",
"fileexists-no-change": "Гэтая загрузка зьяўляецца дакладнай копіяй цяперашняй вэрсіі <strong>[[:$1]]</strong>.",
"fileexists-duplicate-version": "Гэтая загрузка зьяўляецца дакладнай копіяй {{PLURAL:$2|1=старой вэрсіі|старых вэрсіяў}} файлу <strong>[[:$1]]</strong>.",
"file-exists-duplicate": "Гэты файл дублюе {{PLURAL:$1|1=наступны файл|наступныя файлы}}:",
- "file-deleted-duplicate": "Ð\9fадобнÑ\8b Ñ\84айл ([[:$1]]) Ñ\83жо вÑ\8bдалÑ\8fÑ\9eÑ\81Ñ\8f. Ð\9aалÑ\96 лаÑ\81ка, паглÑ\8fдзÑ\96Ñ\86е гÑ\96Ñ\81Ñ\82оÑ\80Ñ\8bÑ\8e вÑ\8bдаленÑ\8cнÑ\8fÑ\9e гÑ\8dÑ\82ага Ñ\84айла перад яго паўторнай загрузкай.",
+ "file-deleted-duplicate": "Ð\86дÑ\8dнÑ\82Ñ\8bÑ\87нÑ\8b Ñ\84айл ([[:$1]]) Ñ\83жо Ñ\80аней вÑ\8bдалÑ\8fÑ\9eÑ\81Ñ\8f. Ð\92ам Ñ\82Ñ\80Ñ\8dба паглÑ\8fдзеÑ\86Ñ\8c гÑ\96Ñ\81Ñ\82оÑ\80Ñ\8bÑ\8e вÑ\8bдаленÑ\8cнÑ\8fÑ\9e гÑ\8dÑ\82ага Ñ\84айлÑ\83 перад яго паўторнай загрузкай.",
"file-deleted-duplicate-notitle": "Файл, ідэнтычны гэтаму файлу, раней ужо быў выдалены, а назва файла была забароненая.\nВам трэба зьвярнуцца да некага з правамі прагляду зьвестак забароненых файлаў, каб прааналізаваць сытуацыю перад тым, як загружаць файл ізноў.",
"uploadwarning": "Папярэджаньне",
- "uploadwarning-text": "Калі ласка, зьмяніце апісаньне файла ніжэй і паспрабуйце ізноў.",
+ "uploadwarning-text": "Калі ласка, зьмяніце апісаньне файлу ніжэй і паспрабуйце ізноў.",
"uploadwarning-text-nostash": "Калі ласка, загрузіце файл ізноў, зьмніце апісаньне ніжэй і паспрабуйце яшчэ раз.",
"savefile": "Захаваць файл",
- "uploaddisabled": "Загрузка файлаў забароненая",
+ "uploaddisabled": "Загрузка файлаў забароненая.",
"copyuploaddisabled": "Загрузка праз URL-адрас адключаная.",
"uploaddisabledtext": "Загрузка файлаў забароненая.",
"php-uploaddisabledtext": "Загрузка файлаў была адключаная ў парамэтрах канфігурацыі PHP. Калі ласка, праверце значэньне парамэтра «file_uploads».",
"Irus",
"Kareyac",
"ShockD",
- "Fitoschido"
+ "Fitoschido",
+ "Vlad5250"
]
},
"tog-underline": "Подчертаване на препратките:",
"right-editcontentmodel": "Редактиране на модела на съдържанието на страница",
"right-editinterface": "Редактиране на потребителския интерфейс",
"right-editusercss": "Редактиране на CSS файловете на други потребители",
+ "right-edituserjson": "Редактиране на JSON файловете на други потребители",
"right-edituserjs": "Редактиране на JS файловете на други потребители",
"right-editmyusercss": "Редактиране на собствените потребителски CSS файлове",
+ "right-editmyuserjson": "Редактиране на собствените потребителски JSON файлове",
"right-editmyuserjs": "Редактиране на собствените потребителски JavaScript файлове",
"right-viewmywatchlist": "Преглеждане на собствения списък за наблюдение",
"right-editmywatchlist": "Редактиране на собствения списък за наблюдение. Забележете, че някои действия все пак ще добавят страници, дори и без текущото право.",
"confirm-unwatch-top": "Voleu treure aquesta pàgina de la llista de seguiment?",
"confirm-rollback-button": "D'acord",
"confirm-rollback-top": "Voleu revertir les modificacions a la pàgina?",
+ "colon-separator": ": ",
"quotation-marks": "«$1»",
"imgmultipageprev": "← pàgina anterior",
"imgmultipagenext": "pàgina següent →",
"rcfilters-view-tags-help-icon-tooltip": "زیاتر بزانە لەسەر دەستکارییە تاگکراوەکان",
"rcfilters-liveupdates-button": "نوێکردنەوەی زیندوو",
"rcnotefrom": "ژێرەوە {{PLURAL:$5|گۆڕانکارییەکەیە|گۆڕانکارییەکانە}} لە <strong>$3، $4</strong>ەوە (ھەتا <strong>$1</strong> نیشان دراوە).",
+ "rclistfromreset": "گەڕاندنەوەی ھەڵبژاردەی بەروار",
"rclistfrom": "گۆڕانکارییە نوێکان نیشان بدە بە دەستپێکردن لە $3 $2",
"rcshowhideminor": "دەستکارییە بچووکەکان $1",
"rcshowhideminor-show": "نیشان بدە",
"TMg",
"Macofe",
"Matma Rex",
- "Fitoschido"
+ "Fitoschido",
+ "Vlad5250"
]
},
"tog-underline": "Wótkaze pódšmarnuś:",
"right-editsemiprotected": "Boki wobźěłaś, kótarež su pśez \"{{int:protect-level-autoconfirmed}}\" šćitane",
"right-editinterface": "Wužywański pówjerch wobźěłaś",
"right-editusercss": "Dataje CSS drugich wužywarjow wobźěłaś",
+ "right-edituserjson": "Dataje JSON drugich wužywarjow wobźěłaś",
"right-edituserjs": "Dataje JS drugich wužywarjow wobźěłaś",
"right-editmyusercss": "Twóje swójske wužywarske CSS-dataje wobźěłaś",
+ "right-editmyuserjson": "Twóje swójske wužywarske JSON-dataje wobźěłaś",
"right-editmyuserjs": "Twóje swójske wužywarske JavaScript-dataje wobźěłaś",
"right-viewmywatchlist": "Se swójske wobglědowańki woglědaś",
"right-editmywatchlist": "Swóje wobglědowańki wobźěłaś. Wobglěduj, až někotare akcije hyšći pśidawaju boki bźez toś togo pšawa.",
"Alp Er Tunqa",
"Baloch Khan",
"Fitoschido",
- "Alireza Ivaz"
+ "Alireza Ivaz",
+ "Iriman",
+ "Matěj Suchánek"
]
},
"tog-underline": "خط کشیدن زیر پیوندها:",
"copyrightwarning": "لطفاً توجه داشتهباشید که همهٔ مشارکتها در {{SITENAME}} منتشرشده تحت $2 در نظر گرفتهمیشوند (برای جزئیات بیشتر $1 را ببینید).\nاگر نمیخواهید نوشتههایتان بیرحمانه ویرایش و توزیع شوند؛ بنابراین، آنها را اینجا ارائه نکنید.<br />\nشما همچنین به ما تعهد میکنید که خودتان این را نوشتهاید یا آن را از یک منبع با مالکیت عمومی یا مشابه آزاد آن برداشتهاید (برای جزئیات بیشتر $1 را ببینید).\n<strong>کارهای دارای حق تکثیر را بدون اجازه ارائه نکنید!</strong>",
"copyrightwarning2": "لطفاً توجه داشتهباشید که همهٔ مشارکتها در {{SITENAME}} ممکن است توسط دیگر مشارکتکنندگان تغییر یابند، ویرایش یا حذف شوند.\nاگر نمیخواهید نوشتههایتان بیرحمانه ویرایش شوند؛ بنابراین، آنها را اینجا ارائه نکنید.<br />\nشما همچنین به ما تعهد میکنید که خودتان این را نوشتهاید یا آن را از یک منبع با مالکیت عمومی یا مشابه آزاد آن برداشتهاید ($1 را برای جزئیات بیشتر ببینید).\n<strong>کارهای دارای حق تکثیر را بدون اجازه ارائه نکنید!</strong>",
"editpage-cannot-use-custom-model": "مدل محتوای این صفحه نمیتواند عوض شود.",
- "longpageerror": "'''خطا: متنی که ارسال کردهاید {{PULAR:$1|یک کیلوبایت|$1 کیلوبایت}} طول دارد. این مقدار از مقدار بیشینهٔ {{PLURAL:$2|یک کیلوبایت|$2 کیلوبایت}} بیشتر است.'''\nنمیتوان آن را ذخیره کرد.",
+ "longpageerror": "'''خطا: متنی که ارسال کردهاید {{PLURAL:$1|یک کیلوبایت|$1 کیلوبایت}} طول دارد. این مقدار از مقدار بیشینهٔ {{PLURAL:$2|یک کیلوبایت|$2 کیلوبایت}} بیشتر است.'''\nنمیتوان آن را ذخیره کرد.",
"readonlywarning": "<strong>هشدار: پایگاه داده برای نگهداری قفل شدهاست، به همین علت هماکنون نمیتوانید ویرایشهایتان را ذخیره کنید.</strong>\nاگر میخواهید متن را در یک پروندهٔ متنی کپی کنید و برای آینده ذخیرهاش کنید.\n\nمدیری که آن را قفل کرده این توضیح را ارائه کردهاست: $1",
"protectedpagewarning": "'''هشدار: این صفحه قفل شدهاست تا فقط کاربران با دسترسی مدیریت بتوانند ویرایشش کنند.'''\nآخرین موارد سیاهه در زیر آمدهاست:",
"semiprotectedpagewarning": "'''توجه:''' این صفحه قفل شدهاست تا تنها کاربران ثبتنامکرده قادر به ویرایش آن باشند.\nآخرین موارد سیاهه در زیر آمدهاست:",
"recentchanges-label-minor": "این یک ویرایش جزئی است",
"recentchanges-label-bot": "این ویرایش را یک ربات انجام داده است",
"recentchanges-label-unpatrolled": "این ویرایش هنوز گشتزنی نشده است",
- "recentchanges-label-plusminus": "حجم صفحه به اندازه این مقدار بایت تغییر یافته است",
+ "recentchanges-label-plusminus": "حجم صفحه به اندازه این مقدار بایت تغییر یافته است.",
"recentchanges-legend-heading": "<strong>اختصارها:</strong>",
"recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (همچنین به [[Special:NewPages|فهرست صفحات تازه]] نگاه کنید)",
"recentchanges-legend-plusminus": "(<em>±۱۲۳</em>)",
"filehist-dimensions": "ابعاد",
"filehist-filesize": "اندازهٔ پرونده",
"filehist-comment": "توضیح",
- "imagelinks": "بهکاررفتن پرونده",
+ "imagelinks": "کاربرد پرونده",
"linkstoimage": "{{PLURAL:$1|صفحهٔ|صفحههای}} زیر به این تصویر پیوند {{PLURAL:$1|دارد|دارند}}:",
"linkstoimage-more": "بیش از $1 صفحه به این پرونده پیوند {{PLURAL:$1|دارد|دارند}}.\nفهرست زیر تنها {{PLURAL:$1|اولین پیوند|اولین $1 پیوند}} به این صفحه را نشان میدهد.\n[[Special:WhatLinksHere/$2|فهرست کامل]] نیز موجود است.",
"nolinkstoimage": "این پرونده در هیچ صفحهای به کار نرفتهاست.",
"nav-login-createaccount": "Oanmelde",
"logout": "Ofmelde",
"userlogout": "Ofmelde",
- "notloggedin": "Net oanmelde",
+ "notloggedin": "Net oanmeld",
"userlogin-noaccount": "Hasto gjin akkount?",
"userlogin-joinproject": "Meidwaan {{SITENAME}}",
"createaccount": "Registrearje",
"upload": "Bied triem oan",
"uploadbtn": "Bied triem oan",
"reuploaddesc": "Werom nei oanbied-side.",
- "uploadnologin": "Net oanmelde",
+ "uploadnologin": "Net oanmeld",
"uploadnologintext": "Jo moatte [[Special:UserLogin|oanmeld]] wêze om in triem oanbiede te kinnen.",
"upload_directory_missing": "De heechlaadmap ($1) is der net en koe net oanmakke wurde troch de webserver.",
"upload_directory_read_only": "De webserver kin net skriuwe yn de oanbiedpad ($1).",
"Wumbolo",
"Fitoschido",
"Hamster",
- "BadDog"
+ "BadDog",
+ "Vlad5250"
]
},
"tog-underline": "Podcrtavanje poveznica",
"right-editcontentmodel": "Odredi modela sadržaja stranice",
"right-editinterface": "Uređivanje suradničkog sučelja",
"right-editusercss": "Uređivanje CSS stranica drugih suradnika",
+ "right-edituserjson": "Uređivanje JSON stranica drugih suradnika",
"right-edituserjs": "Uređivanje JS stranica drugih suradnika",
"right-editmyusercss": "Uređivanje vlastitih CSS stranica",
+ "right-editmyuserjson": "Uređivanje vlastitih JSON stranica",
"right-editmyuserjs": "Uređivanje vlastitih JavaScript stranica",
"right-viewmywatchlist": "Pregled svojeg popisa praćenih stranica",
"right-editmywatchlist": "Uređivanje vlastitog popisa praćenja. Određenim djelovanjima moguće je dodavati stranice i bez ovoga dopuštenja.",
"rcfilters-other-review-tools": "Ostali alati za pregledavanje:",
"rcfilters-group-results-by-page": "Grupiranje rezultata po stranici",
"rcfilters-activefilters": "Aktivni filtri",
+ "rcfilters-activefilters-hide": "Skrij",
+ "rcfilters-activefilters-show": "Pokaži",
"rcfilters-advancedfilters": "Napredni filtri",
"rcfilters-limit-title": "Rezultata za prikaz",
"rcfilters-limit-and-date-label": "{{PLURAL:$1|$1 izmjena|$1 izmjene|$1 izmjena}}, $2",
"rcfilters-watchlist-edit-watchlist-button": "Izmijeni popis praćenih stranica",
"rcfilters-preference-label": "Skrij poboljšanu inačicu nedavnih promjena",
"rcfilters-preference-help": "Vraća natrag stanje prije redizajna sučelja 2017., te svih oruđa dodanih tada i poslije toga.",
+ "rcfilters-watchlist-preference-label": "Sakrij poboljšanu inačicu popisa praćenja",
+ "rcfilters-watchlist-preference-help": "Vraća natrag stanje prije redizajna sučelja 2017., te svih oruđa dodanih tada i poslije toga.",
"rcnotefrom": "Ispod {{PLURAL:$5|je izmjena|su izmjene}} od <strong>$3, $4</strong> (prikazano ih do <strong>$1</strong>).",
"rclistfromreset": "Vrati na zadano izbor datuma",
"rclistfrom": "Prikaži nove promjene počevši od $3 $2",
"dellogpage": "Evidencija brisanja",
"dellogpagetext": "Dolje je popis nedavnih brisanja.\nSva vremena su prema poslužiteljevom vremenu.",
"deletionlog": "evidencija brisanja",
+ "logentry-create-create": "$1 {{GENDER:$2|stvorio|stvorila}} je stranicu $3",
"reverted": "Vraćeno na prijašnju inačicu",
"deletecomment": "Razlog:",
"deleteotherreason": "Drugi/dodatni razlog:",
"rcfilters-activefilters": "Aktív szűrők",
"rcfilters-activefilters-hide": "Elrejt",
"rcfilters-activefilters-show": "Mutat",
+ "rcfilters-activefilters-hide-tooltip": "Aktív szűrők dobozának elrejtése",
+ "rcfilters-activefilters-show-tooltip": "Aktív szűrők dobozának megjelenítése",
"rcfilters-advancedfilters": "Haladó szűrők",
"rcfilters-limit-title": "Megjelenítendő találatok száma",
"rcfilters-limit-and-date-label": "$1 változtatás, $2",
"resetpass-submit-loggedin": "Cambiar contrasigno",
"resetpass-submit-cancel": "Cancellar",
"resetpass-wrong-oldpass": "Le contrasigno temporari o actual es invalide.\nEs possibile que tu ha ja cambiate tu contrasigno o requestate un nove contrasigno temporari.",
- "resetpass-recycled": "Redefini tu contrasigno a un differente del actual, per favor.",
+ "resetpass-recycled": "Cambia tu contrasigno a un differente del actual, per favor.",
"resetpass-temp-emailed": "Tu ha aperite session con un codice temporari que tu recipeva in e-mail.\nPro completar le accesso, tu debe definir un nove contrasigno hic:",
"resetpass-temp-password": "Contrasigno temporari:",
"resetpass-abort-generic": "Le cambio del contrasigno ha essite abortate per un extension.",
"resetpass-expired": "Le contrasigno ha expirate. Per favor defini un nove contrasigno pro aperir session.",
- "resetpass-expired-soft": "Le contrasigno ha expirate e debe esser redefinite. Per favor elige un nove contrasigno ora, o clicca sur \"{{int:authprovider-resetpass-skip-label}}\" pro redefinir lo plus tarde.",
- "resetpass-validity-soft": "Le contrasigno non es valide: $1\n\nPer favor elige un nove contrasigno ora, o clicca sur \"{{int:authprovider-resetpass-skip-label}}\" pro redefinir lo plus tarde.",
+ "resetpass-expired-soft": "Le contrasigno ha expirate e debe esser cambiate. Per favor, elige un nove contrasigno ora, o clicca sur \"{{int:authprovider-resetpass-skip-label}}\" pro cambiar lo plus tarde.",
+ "resetpass-validity-soft": "Le contrasigno non es valide: $1\n\nPer favor, elige un nove contrasigno ora, o clicca sur \"{{int:authprovider-resetpass-skip-label}}\" pro cambiar lo plus tarde.",
"passwordreset": "Reinitialisar contrasigno",
"passwordreset-text-one": "Completa iste formulario pro reinitialisar tu contrasigno.",
"passwordreset-text-many": "{{PLURAL:$1|Completa un de iste campos pro reciper un contrasigno temporari in e-mail.}}",
"converter-manual-rule-error": "Error detegite in le regula manual de conversion de lingua",
"undo-success": "Le modification pote esser disfacite.\nPer favor controla le comparation infra pro verificar que tu vole facer isto, e postea salveguarda le modificationes infra pro assi disfacer le modification.",
"undo-failure": "Le modification non poteva esser annullate a causa de conflicto con modificationes intermedie.",
+ "undo-main-slot-only": "Le modification non poteva esser disfacite perque illo implica contento foras del cannellatura principal.",
"undo-norev": "Impossibile annullar le modification proque illo non existe o esseva delite.",
"undo-nochange": "Pare que iste modification ha jam essite disfacite.",
"undo-summary": "Annullava le version $1 per [[Special:Contributions/$2|$2]] ([[User talk:$2|Discussion]] | [[Special:Contributions/$2|{{MediaWiki:Contribslink}}]])",
"rcfilters-other-review-tools": "Altere instrumentos de revision",
"rcfilters-group-results-by-page": "Gruppar resultatos per pagina",
"rcfilters-activefilters": "Filtros active",
+ "rcfilters-activefilters-hide": "Celar",
+ "rcfilters-activefilters-show": "Monstrar",
+ "rcfilters-activefilters-hide-tooltip": "Celar le area de filtros active",
+ "rcfilters-activefilters-show-tooltip": "Monstrar le area de filtros active",
"rcfilters-advancedfilters": "Filtros avantiate",
"rcfilters-limit-title": "Resultatos a monstrar",
"rcfilters-limit-and-date-label": "$1 modification{{PLURAL:$1||es}}, $2",
"rcfilters-savedqueries-rename": "Renominar",
"rcfilters-savedqueries-setdefault": "Predefinir",
"rcfilters-savedqueries-unsetdefault": "Remover predefinition",
- "rcfilters-savedqueries-remove": "Remover",
+ "rcfilters-savedqueries-remove": "Deler",
"rcfilters-savedqueries-new-name-label": "Nomine",
"rcfilters-savedqueries-new-name-placeholder": "Describe le proposito del filtro",
"rcfilters-savedqueries-apply-label": "Crear filtro",
"rcfilters-empty-filter": "Nulle filtro active. Tote le contributiones es monstrate.",
"rcfilters-filterlist-title": "Filtros",
"rcfilters-filterlist-whatsthis": "Como functiona istes?",
- "rcfilters-filterlist-feedbacklink": "Da nos tu opinion sur iste (nove) instrumentos de filtrage",
+ "rcfilters-filterlist-feedbacklink": "Da nos tu opinion sur iste instrumentos de filtrage",
"rcfilters-highlightbutton-title": "Colorar le resultatos",
"rcfilters-highlightmenu-title": "Selige un color",
"rcfilters-highlightmenu-help": "Selige un color pro illuminar iste proprietate",
"לערי ריינהארט",
"아라",
"Macofe",
- "Nemo bis"
+ "Nemo bis",
+ "Matěj Suchánek"
]
},
"tog-underline": "Pinag-ugisan ti silpo:",
"deletereason-dropdown": "* Kadawyan a rasrason ti panagikkat\n** Spam\n** Bandalismo\n** Panaglabsing iti karbengan ti kopia\n** Kiddaw ti mannurat\n** Naputed a baw-ing",
"delete-edit-reasonlist": "Urnosen dagiti rason ti panagikkat",
"delete-toobig": "Daytoy a panid ket dakkel ti pakasaritaanna, sumurok a $1 {{PLURAL:a panagbaliwan|dagiti panagbaliwan}}.\nTi panagikkat ti kastoy a pammpanid ket naparitan tapno mapawilan ti saan nga inkarkaro a pannakadadael ti {{SITENAME}}.",
- "delete-warning-toobig": "Daytoy a panid ket adda ti dakkel unay a pakasaritaan ti panagurnos, ti kaadu nga $1 {{PLURAL:$1|panagbaliw|dagiti panagbaliw}}.\nTi panagikkat ket madisturbo ti panagpataray ti database ti {{SITNAME}};\nagal-aluadka a mangrugi.",
+ "delete-warning-toobig": "Daytoy a panid ket adda ti dakkel unay a pakasaritaan ti panagurnos, ti kaadu nga $1 {{PLURAL:$1|panagbaliw|dagiti panagbaliw}}.\nTi panagikkat ket madisturbo ti panagpataray ti database ti {{SITENAME}};\nagal-aluadka a mangrugi.",
"deleteprotected": "Saanmo a maikkat daytoy a panid gapu ta nasalaknibanen.",
"deleting-backlinks-warning": "<strong>Ballaag:</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|Dagiti dadduma a panid]] ket naisilpo wenno nailak-am manipud iti panid a gangani nga ikkatem.",
"deleting-subpages-warning": "<strong>Ballaag:</strong> Ti panid nga ik-ikkatem ket addaan [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|iti subpanid|kadagiti $1 a subpanid|51=kadagiti sumurok a 50 a subpanid}}]].",
"ip_range_toolarge": "Dagiti serra a nasakop a dakdakkel ngem /$1 ket saan a maipalubos.",
"proxyblocker": "Pannakbagi a panagserra",
"proxyblockreason": "Ti IP a pagtaengam ket naserraan ngamin ket daytoy ket nakalukat a panakbagi.\nPangngaasi a kontakem ti agit-ited ti serbisio ti Internetmo wenno teknikal a suporta ti gunglom ken ibagam kaniada ti nakaro a parikut ti seguridad.",
- "sorbsreason": "Ti IP a pagtaengam ket nailista a kasla \"nalukatan a pannakbagi\" iti DNSBL nga inusar babaen ti {{SITNAME}}.",
- "sorbs_create_account_reason": "Ti IP a pagtaengam ket nailista a kasla \"nalukatan a pannakbagi\" iti DNSBL nga inusar babaen ti {{SITNAME}}.\nSaanka a makapartuat ti pakabilangan.",
+ "sorbsreason": "Ti IP a pagtaengam ket nailista a kasla \"nalukatan a pannakbagi\" iti DNSBL nga inusar babaen ti {{SITENAME}}.",
+ "sorbs_create_account_reason": "Ti IP a pagtaengam ket nailista a kasla \"nalukatan a pannakbagi\" iti DNSBL nga inusar babaen ti {{SITENAME}}.\nSaanka a makapartuat ti pakabilangan.",
"softblockrangesreason": "Dagiti kontribusion ti di ammo ket saan a maipalubos manipud iti IP nga adresmo ($1). Pagngaasi a sumrek.",
"xffblockreason": "Ti maysa nga adres ti IP nga adda iti paulo ti X-Forwarded-For, mabalin a kukuam wenno ti pannakbagi a server nga us-usarem, ket naserraan. Ti kasisigud a rason ti pannakaserra idi ket: $1",
"cant-see-hidden-user": "Ti agar-aramat a kayatmo a serraan ket naserraanen ken nailemmeng.\nGapu ta awan met ti karbengam nga agilemming ti agar-aramat, saanmo a makita wenno maurnos ti serra ti agar-aramat.",
"monthsall": "amin",
"confirmemail": "Pasingkedan ti esurat a pagtaengam",
"confirmemail_noemail": "Awan ti umisu nga e-surat a pagtaengam a naikabil idiay [[Special:Preferences|kaykayat ti agar-aramat]].",
- "confirmemail_text": "Ti {{SITNAME}} ket masapulna a pasingkedam ti esurat a pagtaengam sakbay nga agusar ti esurat a langa.\nIpabalinmo dita baba a buton ti panagitulod ti pasingkedan a surat dita a pagtaengam.\nTi surat ket mangiraman ti panilpo nga aglaon ti maysa a kodigo;\nikabil ti panilpo dita pagbasabasam tapno mapasingkedam a ti esurat a pagtaengam ket umisu.",
+ "confirmemail_text": "Ti {{SITENAME}} ket masapulna a pasingkedam ti esurat a pagtaengam sakbay nga agusar ti esurat a langa.\nIpabalinmo dita baba a buton ti panagitulod ti pasingkedan a surat dita a pagtaengam.\nTi surat ket mangiraman ti panilpo nga aglaon ti maysa a kodigo;\nikabil ti panilpo dita pagbasabasam tapno mapasingkedam a ti esurat a pagtaengam ket umisu.",
"confirmemail_pending": "Ti pasingkedan a kodigo ket naipatulod kenkan:\nno kadamdama ka a nangaramid ti pakabilangam, aguray ka pay ti mano a minutos a sumangpet sakbay ka nga agpadas ti agkiddaw ti baro a kodigo.",
"confirmemail_send": "Agipatulod ti pasingkedan a kodigo",
"confirmemail_sent": "Naipatuloden ti pammasingked nga esurat.",
"excontentauthor": "чухьнахьадар: «$1», цу оагIон цаI марка воаца автор ва [[Special:Contributions/$2|$2]] ([[User talk:$2|дувцара оагIув]])",
"delete-confirm": "$1 — дӀаяккхар",
"delete-legend": "ДӀаяккхар",
- "confirmdeletetext": "Оаш дIадийхад бIарчча дIадаккхар оагIон а (е сурта), цун деррига хувцара истори а. '''Дехар да''', бакъде шоай из бокъонца де безам болаш долга а, из дича хургдар кхеташ долга а, из дар укх [[{{MediaWiki:Policy-url}}|бокъонашца]] долга.",
+ "historywarning": "<strong>Теркам:</strong> Хьо дӀаяккха гӀертача оагӀон тоадара истори я $1 {{PLURAL:$1|эрш}} чулоацаш:",
+ "confirmdeletetext": "Оаш дIадийхад еррига оагIув (е сурт) дIаяккхар а, цун деррига хувцара истори дIаяккхар а. '''Дехар да''', бакъде шоай из бокъонца де безам болаш долга а, из дича хургдар кхеташ долга а, из хьадар укх [[{{MediaWiki:Policy-url}}|бокъонашца]] долга а.",
"actioncomplete": "Кхоачашдаьд",
"actionfailed": "Кхоачашдаьдац",
"deletedtext": "«$1» дIаяьккхай.\nХьажа $2 тIехьара дIадаккхарий хьаязъяьрга бIаргтохаргболаш.",
"deletecomment": "Бахьан:",
"deleteotherreason": "Кхыдола бахьан/тIатохар:",
"deletereasonotherlist": "Кхыдола бахьан",
+ "deleting-backlinks-warning": "'''ДӀахьалхадаккхар:''' Iа дӀайоаккхаш йолча оагIонна тӀатовжаш я [[Special:WhatLinksHere/{{FULLPAGENAME}}|кхыйола оагӀонаш]].",
"rollbacklink": "юхаяккха",
"rollbacklinkcount": "юхататта $1 {{PLURAL:$1|нийсдар}}",
"rollback-success": "{{GENDER:$3|$1}} яха доакъашхочо тоадаьр юхататтад; юхайоалаяьй {{GENDER:$4|$2}} доакъашхочун эрш.",
"timezoneregion-europe": "Europa",
"timezoneregion-indian": "Indiana Oceano",
"timezoneregion-pacific": "Pacifico",
- "allowemail": "Permisez e-posti de altra uzanti",
+ "allowemail": "Permisez e-posti de altra uzeri",
"email-allow-new-users-label": "Permisez e-posti de la nova uzeri",
"email-blacklist-label": "Impedez la sequanta uzeri sendar e-posto a me:",
"prefs-namespaces": "Nomari",
"prefs-help-email": "Informar vua adreso di e-posto esas fakultativa, tamen ol bezonesos se vu oblivior vua pasovorto e bezonor la sendo di nova pasovorto.",
"prefs-help-email-others": "Vu povas anke selektar se permisos altri skribar a vu per e-posto, per ligilo en vua uzanto-pagino o diskuto-pagino.\nVua e-posto adreso ne revelesos, kande l'altri skribos a vu.",
"prefs-help-email-required": "E-postala adreso es bezonata.",
- "prefs-info": "Bazala informeso",
+ "prefs-info": "Bazala informo",
"prefs-i18n": "Internacioniigo",
"prefs-signature": "Signaturo",
"prefs-timeoffset": "Registro dil tempo",
"Ebraminio",
"Process cq",
"Alirezaaa",
- "Fitoschido"
+ "Fitoschido",
+ "Matěj Suchánek"
]
},
"tog-underline": "خط کیشائن ژێر پیوندەل:",
"copyrightwarning": "لطفاً توجه داشتهباشید که همهٔ مشارکتها در {{SITENAME}} منتشرشده تحت $2 در نظر گرفتهمیشوند (برای جزئیات بیشتر $1 را ببینید).\nاگر نمیخواهید نوشتههایتان بیرحمانه ویرایش و توزیع شوند؛ بنابراین، آنها را اینجا ارائه نکنید.<br />\nشما همچنین به ما تعهد میکنید که خودتان این را نوشتهاید یا آن را از یک منبع با مالکیت عمومی یا مشابه آزاد آن برداشتهاید (برای جزئیات بیشتر $1 را ببینید).\n<strong>کارهای دارای حق تکثیر را بدون اجازه ارائه نکنید!</strong>",
"copyrightwarning2": "لطفاً توجه داشتهباشید که همهٔ مشارکتها در {{SITENAME}} ممکن است توسط دیگر مشارکتکنندگان تغییر یابند، ویرایش یا حذف شوند.\nاگر نمیخواهید نوشتههایتان بیرحمانه ویرایش شوند؛ بنابراین، آنها را اینجا ارائه نکنید.<br />\nشما همچنین به ما تعهد میکنید که خودتان این را نوشتهاید یا آن را از یک منبع با مالکیت عمومی یا مشابه آزاد آن برداشتهاید ($1 را برای جزئیات بیشتر ببینید).\n<strong>کارهای دارای حق تکثیر را بدون اجازه ارائه نکنید!</strong>",
"editpage-cannot-use-custom-model": "مدل محتوای این صفحه نمیتواند عوض شود.",
- "longpageerror": "'''خطا: متنی که ارسال کردهاید {{PULAR:$1|یک کیلوبایت|$1 کیلوبایت}} طول دارد. این مقدار از مقدار بیشینهٔ {{PLURAL:$2|یک کیلوبایت|$2 کیلوبایت}} بیشتر است.'''\nنمیتوان آن را ذخیره کرد.",
+ "longpageerror": "'''خطا: متنی که ارسال کردهاید {{PLURAL:$1|یک کیلوبایت|$1 کیلوبایت}} طول دارد. این مقدار از مقدار بیشینهٔ {{PLURAL:$2|یک کیلوبایت|$2 کیلوبایت}} بیشتر است.'''\nنمیتوان آن را ذخیره کرد.",
"readonlywarning": "<strong>هشدار: پایگاه داده برای نگهداری قفل شدهاست، به همین علت هماکنون نمیتوانید ویرایشهایتان را ذخیره کنید.</strong>\nاگر میخواهید متن را در یک پروندهٔ متنی کپی کنید و برای آینده ذخیرهاش کنید.\n\nمدیری که آن را قفل کرده این توضیح را ارائه کردهاست: $1",
"protectedpagewarning": "'''هشدار: این صفحه قفل شدهاست تا فقط کاربران با دسترسی مدیریت بتوانند ویرایشش کنند.'''\nآخرین موارد سیاهه در زیر آمدهاست:",
"semiprotectedpagewarning": "'''توجه:''' این صفحه قفل شدهاست تا تنها کاربران ثبتنامکرده قادر به ویرایش آن باشند.\nآخرین موارد سیاهه در زیر آمدهاست:",
"cascadeprotected": "Ankehitriny dia voaaro ity pejy ity satria misy pejy voaaro {{PLURAL:$1|iray|$1}} mampiasa ity pejy ity. Io pejy io dia mampiasa ny fiarovana \"mirihana\":\n\n$2",
"namespaceprotected": "Tsy manana alalàna manova ny toeran'anarana « '''$1''' » ianao.",
"customcssprotected": "Tsy afaka manova ity pejy CSS ity ianao satria misy ny safidy manokan'ny mpikambana hafa.",
+ "customjsonprotected": "Tsy manana alalalana manova ity pejy JSON ity ianao satria misy ny safidin'olon-kafa izy ity.",
"customjsprotected": "Tsy afaka manova ity pejy JavaScript ity inaao satria misy ny safidin'ny mpikambana hafa.",
"mycustomcssprotected": "Tsy manana ny alalana ahafahana manova ity pejy CSS ity ianao.",
+ "mycustomjsonprotected": "Tsy manana alalana manova ity pejy JSON ity ianao.",
"mycustomjsprotected": "Tsy manana ny alalana ahafahana manova ity pejy JavaScript ity ianao.",
"myprivateinfoprotected": "Tsy manana alalana ahafahana manova ny fampahalalana tsy sarababem-bahoakanao ianao.",
"mypreferencesprotected": "Tsy manana alalana ahafahana manova ny safidinao ianao.",
"changepassword-success": "Voaova soa aman-tsara ny tenimiafinao!",
"changepassword-throttled": "Betsaka loatra ny andram-pidirana nataonao.\nAndraso $1 aloha ny mamerina.",
"botpasswords": "Tenimiafin-drôbô",
+ "botpasswords-summary": "Ny <em>Tenimiafina rôbô</em> dia manome alalana ny mampiasa ny kaontim-pikambana iray amin'ny alalana API ka tsy mila mampiasa ny tenimiafin'ny mpikambana. Ny zom-pikambana ananana rehefa tafiditra amin'ny alalana tenimiafina rôbô dia mety ho voafetra.\n\nRaha tsy fantatrao ny antony hanaovanao izany dia tsy tokony hanao izany ianao. Tsy tokony hisy ny olona hangataka anao hamoaka iray amin'itony ary hanome azy.",
"botpasswords-disabled": "Tsy ampiasaina ny tenimiafin-drôbô.",
+ "botpasswords-no-central-id": "Raha hampiasa tenimiafina rôbô dia tsy maintsy tafiditra anaty kaonty iombonan-tsehatra ianao.",
"botpasswords-existing": "Tenimiafin-drôbô efa misy",
"botpasswords-createnew": "Hamorona tenimiafina rôbô vaovao",
"botpasswords-editexisting": "Hanova tenimiafina rôbô efa misy",
+ "botpasswords-label-needsreset": "(mila famerenana ny tenimiafina)",
"botpasswords-label-appid": "Anarana rôbô:",
"botpasswords-label-create": "Foronina",
"botpasswords-label-update": "Vaozina",
"savechanges": "Hitahiry ny fiovana",
"publishpage": "Hamoaka pejy",
"publishchanges": "Hamoaka ny fiovana",
+ "savearticle-start": "Hitahiry pejy...",
+ "savechanges-start": "Mitahiry ny fiovana...",
+ "publishpage-start": "Hamoaka ny pejy...",
+ "publishchanges-start": "Hamoaka ny fiovana...",
"preview": "Topi-maso",
"showpreview": "Asehoy aloha",
"showdiff": "Asehoy ny fiovana",
"anoneditwarning": "<strong>Fampitandremana :</strong> Tsy niditra tamin'ny kaontinao ianao. Ho hitan'ny vahoaka ny adiresy IP-nao raha manova inona na inona ianao. Raha <strong>[$1 miditra amin'ny kaontinao]</strong> ianao dia ho anisan'ny tombontsoa anananao ny fanaovana ny fiovana amin'ny solonanaranao.",
"anonpreviewwarning": "''Tsy niditra ianao. Hampitahiry ny adiresy IP anao ao amin'ny tantaram-panovan'ity pejy ity ny fitehirizana ny fanovana.''",
"missingsummary": "'''Hafatra fampantsiahivana''' : tsy mbola nanome ny ambangovangom-panovanao ianao.\nRaha mbola tsindriano fanindroany eo amin'ny bokotra $1, ho voatahiry tsy fanambarana ny fanovanao.",
- "missingcommenttext": "Ampidiro ny ambangovangony azafady.",
+ "missingcommenttext": "Mampidira ambangovangony azafady.",
"missingcommentheader": "<strong>Fampatsiahivana: </strong> Tsy nampiditra lohahevitra ho an'ity hafatra ity ianao. Raha tsindrianao fanindroany \"$1\" dia ho tehirizina tsy misy lohahevitra ny hafatrao.",
"summary-preview": "Topi-mason'ny ambangovangom-panovana :",
"subject-preview": "Topi-mason-dohahevitra:",
"createaccounterror": "$1 ꯑꯦꯀꯥꯎꯟ ꯁꯥꯕꯥ ꯌꯥꯗꯔꯦ",
"nocookiesnew": "The user account was created, but you are not logged in.\n{{SITENAME}} uses cookies to log in users.\nYou have cookies disabled.\nPlease enable them, then log in with your new username and password.",
"nocookieslogin": "{{SITENAME}} uses cookies to log in users.\nYou have cookies disabled.\nPlease enable them and try again.",
+ "loginsuccesstitle": "ꯂꯣꯒ ꯏꯟ",
"login-userblocked": "ꯃꯁꯤꯒꯤ ꯁꯤꯖꯤꯟꯅꯔꯤꯕꯥꯁꯤ ꯊꯤꯡꯖꯤꯟꯈꯔꯦ? ꯂꯣꯒ ꯏꯟ ꯌꯥꯔꯥꯔꯣꯏ",
"passwordtooshort": "ꯄꯥꯁꯋꯔꯇ ꯁꯤ ꯌꯥꯝꯗꯔꯕꯗꯥ ꯃꯁꯤ ꯈꯔꯥꯁꯤ ꯌꯥꯎꯒꯗꯕꯅꯤ {{PLURAL:$1|1 character|$1 characters}}.",
"mailmypassword": "ꯄꯥꯁꯋ꯭ꯇ ꯁꯦꯝꯗꯣꯛꯄꯥ",
"pt-login-continue-button": "ꯂꯣꯘ ꯏꯟ ꯃꯈꯥ ꯆꯠꯊꯧ",
"pt-createaccount": "ꯑꯩꯒꯤ ꯑꯣꯏꯕꯥ ꯑꯃꯥ ꯁꯦꯝꯕꯥ",
"pt-userlogout": "Log out",
+ "changepassword": "ꯄꯥꯁꯋ꯭ꯔꯇ ꯍꯣꯡꯗꯣꯛꯄꯥ",
"oldpassword": "ꯑꯔꯤꯕꯥ ꯄꯥꯁꯋꯔꯇ",
"newpassword": "ꯑꯅꯧꯕꯥ ꯄꯥꯁꯋꯔꯇ",
"retypenew": "ꯑꯃꯨꯛꯍꯟꯅꯥ ꯑꯅꯧꯕꯥ ꯄꯥꯁꯋꯔꯇ ꯅꯝꯃꯨ",
"blankarticle": "<strong>Warning:</strong> The page you are creating is blank.\nIf you click \"$1\" again, the page will be created without any content.",
"anoneditwarning": "<strong>Warning:</strong> You are not logged in. Your IP address will be publicly visible if you make any edits. If you <strong>[$1 log in]</strong> or <strong>[$2 create an account]</strong>, your edits will be attributed to your username, along with other benefits.",
"loginreqlink": "Chang Sinba",
+ "accmailtitle": "ꯄꯥꯁꯋ꯭ꯔꯇ ꯊꯥꯕ",
"newarticle": "ꯑꯅꯧꯕꯥ",
"newarticletext": "You have followed a link to a page that does not exist yet.\nTo create the page, start typing in the box below (see the [$1 help page] for more info).\nIf you are here by mistake, click your browser's <strong>back</strong> button.",
"anontalkpagetext": "----\n<em>This is the discussion page for an anonymous user who has not created an account yet, or who does not use it.</em>\nWe therefore have to use the numerical IP address to identify him/her.\nSuch an IP address can be shared by several users.\nIf you are an anonymous user and feel that irrelevant comments have been directed at you, please [[Special:CreateAccount|create an account]] or [[Special:UserLogin|log in]] to avoid future confusion with other anonymous users.",
"editing": "$1 ꯁꯦꯝꯒꯠꯂꯤ",
"creating": "Creating $1",
"editingsection": "Editing $1 (section)",
+ "yourtext": "ꯅꯪꯒꯤ ꯇꯦꯀꯁ",
+ "yourdiff": "ꯈꯦꯠꯅꯕꯥ ꯁꯤꯡ",
+ "copyrightwarning": "Please note that all contributions to {{SITENAME}} are considered to be released under the $2 (see $1 for details).\nIf you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.<br />\nYou are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource.\n<strong>Do not submit copyrighted work without permission!</strong>",
"templatesused": "{{PLURAL:$1|Template|Templates}} used on this page:",
"template-protected": "ꯉꯥꯛꯊꯣꯛꯂꯕꯥ",
"template-semiprotected": "ꯇꯪꯈꯥꯏ ꯉꯥꯛꯊꯣꯛꯂꯕꯥ",
"hiddencategories": "This page is a member of {{PLURAL:$1|1 hidden category|$1 hidden categories}}:",
+ "permissionserrors": "ꯑꯌꯥꯕꯥꯗꯨ ꯁꯣꯏꯔꯦ",
"permissionserrorstext-withaction": "You do not have permission to $2, for the following {{PLURAL:$1|reason|reasons}}:",
"moveddeleted-notice": "ꯃꯁꯤꯒꯤ ꯂꯥꯃꯥꯏꯁꯤ ꯀꯛꯊꯠꯈꯔꯦ. \nꯀꯛꯊꯠꯄꯥ, ꯉꯥꯛꯊꯣꯛꯄꯥ ꯑꯃꯗꯤ ꯂꯣꯒ ꯂꯦꯡꯍꯟꯕꯥ ꯂꯥꯃꯥꯏꯒꯤꯗꯃꯛ ꯇꯨ ꯃꯈꯥꯒꯤ ꯁꯤꯗꯥ ꯔꯤꯐꯔꯦꯟꯁ ꯎꯨꯠꯂꯦ",
+ "edit-conflict": "ꯁꯦꯝꯒꯠꯐꯝꯒꯤ ꯈꯠꯅ ꯆꯩꯅꯕꯥ",
+ "content-json-empty-object": "ꯑꯍꯥꯡꯕꯥ ꯄꯣꯠꯁꯛ",
"viewpagelogs": "ꯃꯁꯤꯒꯤ ꯂꯥꯃꯥꯏꯁꯤꯒꯤ ꯅꯧꯅ ꯆꯪꯉꯨ",
"currentrev-asof": "$1 ꯒꯤ ꯅꯧꯅꯥ ꯑꯃꯨꯛꯍꯟꯅꯥ ꯌꯦꯡꯕꯥ ꯃꯤꯠꯌꯦꯡ",
"revisionasof": "Revision as of $1",
"currentrevisionlink": "ꯈꯋꯥꯏꯗꯒꯤ ꯅꯧꯅꯥ ꯑꯃꯨꯛ ꯌꯦꯡꯕꯥ",
"cur": "ꯍꯧ",
"last": "ꯃꯥꯃꯥꯡꯒꯤ",
+ "page_first": "ꯑꯍꯥꯟꯕ",
+ "page_last": "ꯑꯔꯣꯏꯕ",
"histlegend": "Diff selection: Mark the radio boxes of the revisions to compare and hit enter or the button at the bottom.<br />\nLegend: <strong>({{int:cur}})</strong> = difference with latest revision, <strong>({{int:last}})</strong> = difference with preceding revision, <strong>{{int:minoreditletter}}</strong> = minor edit.",
"history-fieldset-title": "ꯊꯤꯋꯨ ꯑꯃꯨꯛ ꯍꯝꯁꯟꯅꯥ ꯌꯦꯡꯅꯕꯥ",
"histfirst": "ꯈꯋꯥꯏꯗꯒꯤ ꯑꯔꯤꯕꯥ",
"histlast": "ꯑꯅꯧꯕꯥ",
"rev-delundel": "ꯑꯍꯣꯡꯕꯥ ꯎꯍꯟꯂꯤꯕꯥ",
+ "revdelete-show-file-submit": "ꯍꯣꯏ",
+ "revdelete-radio-unset": "ꯎꯍꯟꯕ",
"history-title": "Revision history of \"$1\"",
"difference-title": "$1 ꯒꯤ ꯑꯃꯨꯛꯍꯟꯕꯥ ꯈꯦꯠꯅꯕꯥꯒꯤ ꯃꯔꯛ",
"lineno": "ꯂꯥ ꯏ $1",
"tooltip-undo": "\"Undo\" reverts this edit and opens the edit form in preview mode. It allows adding a reason in the summary.",
"tooltip-summary": "ꯑꯇꯦꯟꯕꯥ ꯀꯨꯞꯅꯥ ꯁꯟꯗꯣꯛꯅꯩ ꯇꯥꯛꯄꯥ ꯏꯌꯨ",
"simpleantispam-label": "Anti-spam check.\nDo <strong>not</strong> fill this in!",
+ "pageinfo-header-edits": "ꯄꯨꯋꯥꯔꯤ ꯁꯦꯝꯒꯠꯄ",
"pageinfo-header-restrictions": "ꯉꯥꯛꯊꯣꯛꯂꯕꯥ ꯂꯥꯃꯥꯏ",
"pageinfo-robot-noindex": "ꯌꯥꯍꯟꯗꯕꯥ",
"pageinfo-subpages-name": "ꯂꯥꯃꯥꯏꯁꯤ ꯒꯤ ꯃꯅꯨꯡ ꯆꯟꯕꯥ ꯀꯨꯞꯊꯕꯥ ꯂꯥꯃꯥꯏꯁꯤꯡ",
"pageinfo-subpages-value": "$1 ($2 {{PLURAL:$2|redirect|redirects}}; $3 {{PLURAL:$3|non-redirect|non-redirects}})",
+ "pageinfo-firstuser": "ꯂꯥꯃꯥꯏ ꯁꯥꯔꯤꯕ ꯃꯤꯑꯣꯏꯁꯤꯡ",
+ "pageinfo-firsttime": "ꯂꯥꯃꯥꯏ ꯁꯥꯈꯤꯕꯒꯤ ꯆꯩꯆꯠ",
+ "pageinfo-lastuser": "ꯈꯋꯥꯏꯗꯒꯤ ꯅꯧꯕ ꯁꯦꯝꯒꯠꯂꯛꯂꯤꯕꯁꯤꯡ",
+ "pageinfo-lasttime": "ꯅꯧꯔꯤꯕ ꯁꯦꯝꯒꯠꯄꯒꯤ ꯆꯩꯆꯠ",
+ "pageinfo-edits": "ꯑꯄꯨꯟꯕ ꯁꯦꯝꯒꯠꯄꯒꯤ ꯃꯁꯤꯡ",
+ "pageinfo-authors": "ꯑꯄꯨꯟꯕ ꯑꯈꯟꯅꯕ ꯑꯌꯤꯕꯁꯤꯡꯒꯤ ꯃꯁꯤꯡ",
"pageinfo-magic-words": "Magic {{PLURAL:$1|word|words}} ($1)",
"pageinfo-toolboxlink": "ꯂꯥꯃꯥꯏꯒꯤ ꯃꯇꯥꯡꯗꯥ",
"pageinfo-contentpage-yes": "ꯍꯣꯏ",
"resetpass-submit-loggedin": "Endre passord",
"resetpass-submit-cancel": "Avbryt",
"resetpass-wrong-oldpass": "Ugyldig midlertidig eller aktivt passord.\nDet kan tenkes at allerede har gjennomført et vellykket bytte av passord, eller bedt om et nytt midlertidig passord.",
- "resetpass-recycled": "Vær vennlig å endre passordet til noe annen enn gjeldende passord.",
+ "resetpass-recycled": "Endre passordet ditt til noe annet enn det nåværende passordet.",
"resetpass-temp-emailed": "Du logget inn med en midlertidig kode sendt på e-post.\nFor å avslutte innloggingen må du angi et nytt passord her:",
"resetpass-temp-password": "Midlertidig passord:",
"resetpass-abort-generic": "Endring av passord har blitt avbrutt av en utvidelse.",
"resetpass-expired": "Passordet ditt har utløpt. Vær vennlig å angi et nytt passord for å logge inn.",
"resetpass-expired-soft": "Passordet ditt har utløpt og må endres. Vær vennlig å angi et nytt passord, eller klikk \"{{int:authprovider-resetpass-skip-label}}\" for å endre det senere.",
- "resetpass-validity-soft": "Ditt passord er ikke gyldig: $1",
+ "resetpass-validity-soft": "Passordet ditt er ikke gyldig: $1\n\nVelg et nytt passord nå, eller klikk på «{{int:authprovider-resetpass-skip-label}}» for å endre det seinere.",
"passwordreset": "Tilbakestilling av passord",
"passwordreset-text-one": "Fyll ut skjemaet for å tilbakestille passordet",
"passwordreset-text-many": "{{PLURAL:$1|Fyll inn ett av datafeltene for å tilbakestille passordet ditt via epost.}}",
"previewerrortext": "En feil oppsto mens dine endringer skulle forhåndsvises.",
"blockedtitle": "Brukeren er blokkert",
"blockedtext": "<strong>Ditt brukernavn eller din IP-adresse har blitt blokkert.</strong>\n\nBlokkeringen ble utført av $1. Grunnen som ble oppgitt var <em>$2</em>.\n\n* Blokkeringen begynte: $8\n* Blokkeringen opphører: $6\n* Blokkeringen ment for: $7\n\nDu kan kontakte $1 eller en annen [[{{MediaWiki:Grouppage-sysop}}|administrator]] for å diskutere blokkeringen.\nDu kan ikke bruke \"{{int:emailuser}}\"-funksjonen med mindre du har oppgitt en gyldig e-postadresse i [[Special:Preferences|innstillingene dine]] og du ikke har blitt blokkert fra å sende e-post.\nDin nåværende IP-adresse er $3, og blokkerings-ID-en er #$5.\nVennligst ta med all denne informasjonen ved henvendelser.",
- "autoblockedtext": "Din IP-adresse har blitt automatisk blokkert fordi den ble brukt av en annen bruker som ble blokkert av $1.\nDen oppgitte grunnen var:\n\n:'''$2'''\n\n* Blokkeringen begynte: $8\n* Blokkeringen utgår: $6\n* Blokkeringen er ment for: $7\n\nDu kan kontakte $1 eller en av de andre [[{{MediaWiki:Grouppage-sysop}}|administratorene]] for å diskutere blokkeringen.\n\nMerk at du ikke kan bruke «E-post til denne brukeren»-funksjonen med mindre du har registrert en gyldig e-postadresse i [[Special:Preferences|innstillingene dine]].\n\nDin IP-adresse er $3, og blokkerings-ID-en er #$5.\nVennligst ta med all denne informasjonen ved henvendelser.",
+ "autoblockedtext": "Din IP-adresse har blitt automatisk blokkert fordi den ble brukt av en annen bruker som ble blokkert av $1.\nDen oppgitte grunnen var:\n\n:'''$2'''\n\n* Blokkeringen begynte: $8\n* Blokkeringen utgår: $6\n* Blokkeringen er ment for: $7\n\nDu kan kontakte $1 eller en av de andre [[{{MediaWiki:Grouppage-sysop}}|administratorene]] for å diskutere blokkeringen.\n\nMerk at du ikke kan bruke «{{int:emailuser}}»-funksjonen med mindre du har registrert en gyldig e-postadresse i [[Special:Preferences|innstillingene dine]].\n\nDin IP-adresse er $3, og blokkerings-ID-en er #$5.\nVennligst ta med all denne informasjonen ved henvendelser.",
"systemblockedtext": "Ditt brukernavn eller IP-adresse har blitt blokkert automatisk av MediaWiki.\n\nBlokkeringen grunnes:\n\n:<em>$2</em>\n\n* Blokkeringen startet: $8\n* Blokkeringen gjelder til: $6\n* Blokkeringen er ment for: $7\n\nDin nåværende IP-adresse er $3.\nVennligst inkluder informasjonen over i alle spørsmål du spør angående dette.",
"blockednoreason": "ingen grunn gitt",
"whitelistedittext": "Du må $1 for å redigere artikler.",
"converter-manual-rule-error": "En feil ble oppdaget i en manuell språkkonverteringsregel",
"undo-success": "Redigeringen kan omgjøres. Sjekk sammenligningen under for å bekrefte at du vil gjøre dette, og lagre endringene for å fullføre omgjøringen.",
"undo-failure": "Redigeringen kunne ikke omgjøres på grunn av konflikterende etterfølgende redigeringer.",
+ "undo-main-slot-only": "Redigeringen kunne ikke omgjøres fordi den involverer innhold utenfor hovedspalten.",
"undo-norev": "Redigeringen kunne ikke fjernes fordi den ikke eksisterer eller ble slettet",
"undo-nochange": "Det ser ut til at redigeringen allerede er tilbakestilt.",
"undo-summary": "Fjerner revisjon $1 av [[Special:Contributions/$2|$2]] ([[User talk:$2|diskusjon]])",
"rcfilters-other-review-tools": "Andre gjennomgangsverktøy",
"rcfilters-group-results-by-page": "Grupper resultater etter side",
"rcfilters-activefilters": "Aktive filtre",
+ "rcfilters-activefilters-hide": "Skjul",
+ "rcfilters-activefilters-show": "Vis",
+ "rcfilters-activefilters-hide-tooltip": "Skjul området for aktive filtre",
+ "rcfilters-activefilters-show-tooltip": "Vis området for aktive filtre",
"rcfilters-advancedfilters": "Avanserte filtre",
"rcfilters-limit-title": "Antall resultater som skal vises",
"rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|endring|endringer}}, $2",
"rcfilters-savedqueries-rename": "Gi nytt navn",
"rcfilters-savedqueries-setdefault": "Sett som standard",
"rcfilters-savedqueries-unsetdefault": "Fjern som standard",
- "rcfilters-savedqueries-remove": "Fjern",
+ "rcfilters-savedqueries-remove": "Slett",
"rcfilters-savedqueries-new-name-label": "Navn",
"rcfilters-savedqueries-new-name-placeholder": "Beskriv formålet til filteret",
"rcfilters-savedqueries-apply-label": "Opprett filter",
"rcfilters-empty-filter": "Ingen aktive filtre. Alle bidrag vises.",
"rcfilters-filterlist-title": "Filtre",
"rcfilters-filterlist-whatsthis": "Hvordan virker dette?",
- "rcfilters-filterlist-feedbacklink": "Gi tilbakemelding på disse (nye) filterverktøyene",
+ "rcfilters-filterlist-feedbacklink": "Gi tilbakemelding på disse filterverktøyene",
"rcfilters-highlightbutton-title": "Marker resultater",
"rcfilters-highlightmenu-title": "Velg en farge",
"rcfilters-highlightmenu-help": "Velg en farge for å merke denne egenskapen",
"expansion-depth-exceeded-warning": "De pagina bevat te veel sjablonen",
"parser-unstrip-loop-warning": "Er is een \"unstrip\"-lus gedetecteerd",
"unstrip-depth-warning": "De recursielimiet ($1) voor \"unstrip\" is overschreden",
+ "unstrip-depth-category": "Pagina's waar de \"unstrip\" dieptelimiet is overschreden.",
+ "unstrip-size-warning": "De groottelimiet ($1) voor \"unstrip\" is overschreden",
+ "unstrip-size-category": "Pagina's waar de \"unstrip\" groottelimiet is overschreden",
"converter-manual-rule-error": "Er is een fout gedetecteerd in een handmatig toegevoegde taalconversieregel.",
"undo-success": "Deze bewerking kan ongedaan gemaakt worden.\nHieronder staat de tekst waarin de wijziging ongedaan is gemaakt.\nControleer voor het opslaan of het resultaat gewenst is.",
"undo-failure": "De wijziging kan niet ongedaan gemaakt worden vanwege andere strijdige wijzigingen.",
"limitreport-cputime-value": "$1 {{PLURAL:$1|seconde|seconden}}",
"limitreport-walltime": "Werkelijk tijdsgebruik",
"limitreport-walltime-value": "$1 {{PLURAL:$1|seconde|seconden}}",
- "limitreport-ppvisitednodes": "Aantal nodes bekeken tijdens de voorverwerking:",
- "limitreport-ppgeneratednodes": "Aantal nodes aangemaakt tijdens de voorverwerking:",
+ "limitreport-ppvisitednodes": "Aantal nodes bekeken tijdens de voorverwerking",
+ "limitreport-ppgeneratednodes": "Aantal nodes aangemaakt tijdens de voorverwerking",
"limitreport-postexpandincludesize": "Inclusiegrootte na uitbreiden",
"limitreport-postexpandincludesize-value": "$1/$2 {{PLURAL:$2|byte|bytes}}",
"limitreport-templateargumentsize": "Grootte sjabloonparameters",
"limitreport-templateargumentsize-value": "$1 / $2 {{PLURAL:$2|byte|bytes}}",
"limitreport-expansiondepth": "Hoogste uitbreidingsdiepte",
"limitreport-expensivefunctioncount": "Aantal kostbare parserfuncties",
+ "limitreport-unstrip-depth": "\"Unstrip\" recursiediepte",
+ "limitreport-unstrip-size": "\"Unstrip\" grootte na uitbreiden",
"limitreport-unstrip-size-value": "$1/$2 {{PLURAL:$2|byte|bytes}}",
"expandtemplates": "Sjablonen substitueren",
"expand_templates_intro": "Deze speciale pagina leest de opgegeven wikitekst in en substitueert recursief alle sjablonen in de wikitekst.\nHet substitueert ook alle parserfuncties zoals\n<code><nowiki>{{</nowiki>#language:…}}</code> en\nvariabelen als <code><nowiki>{{</nowiki>CURRENTDAY}}</code>.\nVrijwel alles tussen dubbele accolades wordt gesubstitueerd.",
"prefs-diffs": "Skilnader",
"prefs-help-prefershttps": "Denne innstillinga vil verta verksam neste gongen du loggar inn.",
"userrights": "Administrering av brukartilgang",
- "userrights-lookup-user": "Administrer brukargrupper",
+ "userrights-lookup-user": "Vel ein brukar",
"userrights-user-editname": "Skriv inn brukarnamn:",
- "editusergroup": "Endre brukargrupper",
+ "editusergroup": "Last inn brukargrupper",
"editinguser": "Endrar brukarrettane til brukaren '''[[User:$1|$1]]''' $2",
+ "viewinguserrights": "Viser brukarrettane til {{GENDER:$1|brukaren}} <strong>[[User:$1|$1]]</strong> $2",
"userrights-editusergroup": "Endre brukargrupper",
+ "userrights-viewusergroup": "Sjå {{GENDER:$1|brukargrupper}}",
"saveusergroups": "Lagre brukargrupper",
"userrights-groupsmember": "Medlem av:",
"userrights-groupsmember-auto": "Implisitt medlem av:",
"booksources-search": "Søk",
"booksources-text": "Nedanfor finn du ei liste over lenkjer til andre nettstader som sel nye og brukte bøker, og desse kan ha meir informasjon om bøker du leitar etter:",
"booksources-invalid-isbn": "Det oppgjevne ISBN-nummeret er ugyldig; sjekk med kjelda di om du har oppgjeve det rett.",
+ "magiclink-tracking-rfc": "Sider som nyttar magiske RFC-lenkjer",
+ "magiclink-tracking-pmid": "Sider som nyttar magiske PMID-lenkjer",
+ "magiclink-tracking-isbn": "Sider som nyttar magiske ISBN-lenkjer",
"specialloguserlabel": "Utøvar:",
"speciallogtitlelabel": "Mål (tittel eller {{ns:user}}:brukarnamn for brukar):",
"log": "Loggar",
"trackingcategories-msg": "Sporingskategori",
"trackingcategories-name": "Meldingsnamn",
"trackingcategories-desc": "Inkluderingsgrunnlag",
+ "restricted-displaytitle-ignored": "Sider med ignorerte visingstitlar",
+ "restricted-displaytitle-ignored-desc": "Ein <code><nowiki>{{DISPLAYTITLE}}</nowiki></code>-funksjon på denne sida er sett bort ifrå av di argumentet ikkje svarar til den faktiske tittelen til sida.",
"noindex-category-desc": "Sida vert ikkje indeksert av robotar av di ho inneheld trylleordet <code><nowiki>__NOINDEX__</nowiki></code> og er i eit namnerom der dette flagget er tillate.",
"trackingcategories-nodesc": "Inga skilding er tilgjengeleg.",
"trackingcategories-disabled": "Kategorien er avslegen",
"watchlisttools-raw": "Endre på overvakingslista i råformat",
"signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|diskusjon]])",
"duplicate-defaultsort": "Åtvaring: Standardsorteringa «$2» tar over for den tidlegare sorteringa «$1».",
+ "restricted-displaytitle": "<strong>Åtvaring:</strong> Visingstittelen «$1» vart sett bort frå sidan han ikkje svarar til den faktiske tittelen til sida.",
"version": "Versjon",
"version-extensions": "Installerte utvidingar",
"version-skins": "Installerte drakter",
"Anwar2",
"Acamicamacaraca",
"DeRudySoulStorm",
- "Railfail536"
+ "Railfail536",
+ "Vlad5250"
]
},
"tog-underline": "Podkreślenie linków:",
"right-browsearchive": "Przeszukiwanie usuniętych stron",
"right-undelete": "Odtwarzanie usuniętych stron",
"right-suppressrevision": "Podgląd, ukrywanie i odkrywanie wersji ukrytych przed wszystkimi",
- "right-viewsuppressed": "Umożliwia zobaczenie wersji ukrytych przed każdym użytkownikiem",
+ "right-viewsuppressed": "Podgląd wersji ukrytych przed każdym użytkownikiem",
"right-suppressionlog": "Podgląd rejestru ukrywania",
"right-block": "Blokowanie użytkownikom możliwości edycji",
"right-blockemail": "Blokowanie użytkownikom możliwości wysyłania wiadomości",
"rcfilters-savedqueries-apply-and-setdefault-label": "د فرض په ډول د فيلټر جوړول",
"rcfilters-savedqueries-cancel-label": "ناگارل",
"rcfilters-savedqueries-add-new-title": "د امستنې اوسنۍ فيلټر خوندي کړي",
+ "rcfilters-restore-default-filters": "د ډيفاولټ فلټرونه بیا تازه کول",
"rcfilters-search-placeholder": "د فلټر بدلونونه (د مینو کارول یا د فلټر نوم لټونه)",
"rcfilters-invalid-filter": "غلط فلټر",
"rcfilters-empty-filter": "هيڅ فعال فلټر نشته. ټولي سمونې ښکاره شوي.",
"RadiX",
"MokaAkashiyaPT",
"Athena in Wonderland",
- "Fitoschido"
+ "Fitoschido",
+ "Ldacosta"
]
},
"tog-underline": "Sublinhar hiperligações:",
"prefs-editor": "Used in [[Special:Preferences]], tab \"Editing\" ({{int:prefs-editing}}).\n\n{{Identical|Editor}}",
"prefs-preview": "Used in [[Special:Preferences]], tab \"Editing\".\n{{Identical|Preview}}",
"prefs-advancedrc": "Used in [[Special:Preferences]], tab \"Recent changes\".\n{{Identical|Advanced options}}",
- "prefs-opt-out": "Used in [[Special:Preferences]], tab \"Recent changes\".",
+ "prefs-opt-out": "Used in [[Special:Preferences]], tabs \"Recent changes\" and \"Watchlist\".",
"prefs-advancedrendering": "Used in [[Special:Preferences]], tab \"Appearence\".\n{{Identical|Advanced options}}",
"prefs-advancedsearchoptions": "Used in [[Special:Preferences]], tab \"Search options\".\n{{Identical|Advanced options}}",
"prefs-advancedwatchlist": "Used in [[Special:Preferences]], tab \"Watchlist\".\n{{Identical|Advanced options}}",
"Happy13241",
"Vcohen",
"AttemptToCallNil",
- "Stjn"
+ "Stjn",
+ "Vlad5250"
]
},
"tog-underline": "Подчёркивание ссылок:",
"pagetitle": "$1 — {{SITENAME}}",
"pagetitle-view-mainpage": "{{SITENAME}}",
"backlinksubtitle": "← $1",
- "retrievedfrom": "Источник — «$1»",
+ "retrievedfrom": "Источник — $1",
"youhavenewmessages": "{{PLURAL:$3|Вы получили}} $1 ($2).",
"youhavenewmessagesfromusers": "{{PLURAL:$4|Вы получили}} $1 от {{PLURAL:$3|$3 участника|$3 участников|1=другого участника}} ($2).",
"youhavenewmessagesmanyusers": "Вы получили $1 от множества пользователей ($2).",
"mergelog": "Журнал объединений",
"revertmerge": "Разделить",
"mergelogpagetext": "Ниже приведён список последних объединений историй страниц.",
- "history-title": "$1 — история изменений",
- "difference-title": "$1 — различия между версиями",
+ "history-title": "$1: история изменений",
+ "difference-title": "$1: различия между версиями",
"difference-title-multipage": "Разница между страницами «$1» и «$2»",
"difference-multipage": "(Различия между страницами)",
"lineno": "Строка $1:",
"userrights-irreversible-marker": "$1*",
"userrights-no-shorten-expiry-marker": "$1#",
"userrights-expiry-current": "Истекает $1",
- "userrights-expiry-none": "Ð\9dикогда",
+ "userrights-expiry-none": "Ð\91еÑ\81Ñ\81Ñ\80оÑ\87но",
"userrights-expiry": "Права истекают:",
"userrights-expiry-existing": "$2, $3",
"userrights-expiry-othertime": "Другое время:",
"right-movefile": "переименование файлов",
"right-suppressredirect": "подавление перенаправлений при переименовании страниц",
"right-upload": "загрузка файлов",
- "right-reupload": "запиÑ\81Ñ\8c Ñ\84айлов повеÑ\80Ñ\85 Ñ\81Ñ\83Ñ\89еÑ\81Ñ\82вÑ\83Ñ\8eÑ\89иÑ\85",
+ "right-reupload": "пеÑ\80езапиÑ\81Ñ\8c Ñ\81Ñ\83Ñ\89еÑ\81Ñ\82вÑ\83Ñ\8eÑ\89иÑ\85 Ñ\84айлов",
"right-reupload-own": "перезапись файлов тем же участником",
"right-reupload-shared": "подмена файлов из общих хранилищ локальными",
"right-upload_by_url": "загрузка файлов с адреса URL",
"right-purge": "очистка кэша страниц без подтверждения",
"right-autoconfirmed": "обход ограничений скорости на IP-адрес",
"right-bot": "автоматический процесс",
- "right-nominornewtalk": "малые правки на СО участников не создают для них уведомление о новом сообщении",
+ "right-nominornewtalk": "малые правки на страницах обсуждений участников не создают для них уведомление о новом сообщении",
"right-apihighlimits": "уменьшение ограничений на выполнение API-запросов",
"right-writeapi": "использование API для записи",
"right-delete": "удаление страниц",
"filerevert-success": "'''[[Media:$1|$1]]''' был возвращён к [$4 версии от $3, $2].",
"filerevert-badversion": "Не существует предыдущей локальной версии этого файла с указанной меткой времени.",
"filerevert-identical": "Текущая версия файла уже идентична выбранной.",
- "filedelete": "$1 — удаление",
+ "filedelete": "$1: удаление",
"filedelete-legend": "Удалить файл",
"filedelete-intro": "Вы собираетесь удалить файл '''[[Media:$1|$1]]''' со всей его историей.",
"filedelete-intro-old": "Вы удаляете версию <strong>[[Media:$1|$1]]</strong> от [$4 $3, $2].",
"excontent": "содержимое: «$1»",
"excontentauthor": "содержимое: «$1», единственным автором которого был [[Special:Contributions/$2|$2]] ([[User talk:$2|обсуждение]])",
"exbeforeblank": "содержимое до очистки: «$1»",
- "delete-confirm": "$1 — удаление",
+ "delete-confirm": "$1: удаление",
"delete-legend": "Удаление",
"historywarning": "<strong>Внимание:</strong> Вы собираетесь удалить страницу, у которой есть история правок, содержащая $1 {{PLURAL:$1|версию|версии|версий}}:",
"historyaction-submit": "Показать",
"anoncontribs": "Вклад",
"contribsub2": "Вклад {{GENDER:$3|$1}} ($2)",
"contributions-userdoesnotexist": "Не зарегистрировано учётной записи «$1».",
- "nocontribs": "Ð\98зменений, Ñ\81ооÑ\82веÑ\82Ñ\81Ñ\82вÑ\83Ñ\8eÑ\89иÑ\85 заданнÑ\8bм Ñ\83Ñ\81ловиÑ\8fм, найдено не бÑ\8bло.",
+ "nocontribs": "Ð\98зменений, Ñ\81ооÑ\82веÑ\82Ñ\81Ñ\82вÑ\83Ñ\8eÑ\89иÑ\85 заданнÑ\8bм Ñ\83Ñ\81ловиÑ\8fм, не найдено.",
"uctop": "(текущая)",
"month": "С месяца (и ранее):",
"year": "С года (и ранее):",
"Matma Rex",
"KHMELNYTSKYIA",
"Igor Kercsa",
- "Fitoschido"
+ "Fitoschido",
+ "Vlad5250"
]
},
"tog-underline": "Підкреслёвати одказы:",
"right-editsemiprotected": "Едітованя сторінок замкнутых на „{{int:protect-level-autoconfirmed}}“",
"right-editinterface": "Едітованя інтерфейсу хоснователя",
"right-editusercss": "Едітованя CSS файлів іншых хоснователїв",
+ "right-edituserjson": "Едітованя JSON файлів іншых хоснователїв",
"right-edituserjs": "Едітованя JS файлів іншых хоснователїв",
"right-editmyusercss": "Едітовати вашы властны хосновательскы CSS файлы.",
+ "right-editmyuserjson": "Едітовати вашы властны хосновательскы JSON файлы",
"right-editmyuserjs": "Едітовати вашы властны хосновательскы JavaScript файлы",
"right-viewmywatchlist": "Перезераня властного списку слїдованых сторінок",
"right-editmywatchlist": "Едітованя властного списку слїдованых сторінок. Усвідомте собі, же дакотры дїї будуть до нёго придавати сторінкы і без такого права.",
"tag-filter": "[[Special:Tags|ٽيگ]] ڇاڻي:",
"tag-filter-submit": "ڇاڻي",
"tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|ٽيگ|ٽيگز}}]]: $2)",
+ "tag-mw-new-redirect": "نئون چوريل",
"tag-mw-rollback-description": "واپس-ورايو ڳنڍڻي کي استعمال ڪندي پوين ترميمن کي واپس ورائيندڙ ترميمون",
"tags-title": "ٽيگس",
"tags-tag": "ٽيگ نانءُ",
"Xð",
"Pmikolas44",
"Fitoschido",
- "Matěj Suchánek"
+ "Matěj Suchánek",
+ "Vlad5250"
]
},
"tog-underline": "Podčiarkovať odkazy:",
"timezoneregion-indian": "Indický oceán",
"timezoneregion-pacific": "Tichý oceán",
"allowemail": "Povoliť prijímanie e-mailov od iných používateľov",
+ "email-allow-new-users-label": "Povoliť e-maily od úplne nových používateľov",
"email-blacklist-label": "Znemožniť týmto používateľom posielať mi e-maily:",
"prefs-searchoptions": "Vyhľadávanie",
"prefs-namespaces": "Menné priestory",
"prefs-dateformat": "Formát dátumu",
"prefs-timeoffset": "Časový posun",
"prefs-advancedediting": "Všeobecné možnosti",
+ "prefs-developertools": "Vývojárske nástroje",
"prefs-editor": "Používateľ",
"prefs-preview": "Náhľad",
"prefs-advancedrc": "Rozšírené možnosti",
"right-editcontentmodel": "Upravovať model obsahu stránky",
"right-editinterface": "Upravovať správy používateľského rozhrania",
"right-editusercss": "Upravovať CSS súbory ostatných používateľov",
+ "right-edituserjson": "Upravovať JSON súbory ostatných používateľov",
"right-edituserjs": "Upravovať JS súbory ostatných používateľov",
"right-editmyusercss": "Upraviť svoje vlastné používateľské súbory CSS",
+ "right-editmyuserjson": "Upraviť svoje vlastné používateľské súbory JSON",
"right-editmyuserjs": "Upraviť svoje vlastné používateľské súbory JavaScript",
"right-viewmywatchlist": "Zobraziť vlastný zoznam sledovaných stránok",
"right-editmywatchlist": "Upraviť vlastný zoznam sledovaných stránok. Všimnite si, že niektoré operácie budú môcť pridať stránky aj bez tohto oprávnenia.",
"rcfilters-advancedfilters": "Pokročilé filtre",
"rcfilters-limit-title": "Zobraziť zmeny",
"rcfilters-limit-and-date-label": "{{PLURAL:$1|Jedna úprava|$1 úpravy|$1 úprav}}, $2",
+ "rcfilters-date-popup-title": "Hľadané časové obdobie",
"rcfilters-days-title": "Posledné dni",
"rcfilters-hours-title": "Posledné hodiny",
"rcfilters-days-show-days": "$1 {{PLURAL:$1|deň|dni|dní}}",
"table_pager_limit_submit": "Spustiť",
"table_pager_empty": "Bez výsledkov",
"autosumm-blank": "Odstránený obsah stránky",
- "autosumm-replace": "Nahrádzam stránku textom „$1“",
+ "autosumm-replace": "Obsah stránky nahradený textom „$1“",
"autoredircomment": "Presmerovanie na [[$1]]",
+ "autosumm-removed-redirect": "Zrušené presmerovanie na [[$1]]",
+ "autosumm-changed-redirect-target": "Cieľ presmerovania zmenený z [[$1]] na [[$2]]",
"autosumm-new": "Vytvorená stránka „$1“",
"autosumm-newblank": "Vytvorená prázdna stránka",
"lag-warn-normal": "Úpravy za {{PLURAL:$1|poslednú sekundu|posledné $1 sekundy|posledných $1 sekúnd}} nemusia byť v tomto zozname zobrazené.",
"specialpages-group-wiki": "Údaje a nástroje",
"specialpages-group-redirects": "Špeciálne stránky, ktoré sú presmerovania",
"specialpages-group-spam": "Nástroje proti spamu",
- "specialpages-group-developer": "Nástroje vývojárov",
+ "specialpages-group-developer": "Vývojárske nástroje",
"blankpage": "Prázdna stránka",
"intentionallyblankpage": "Táto stránka je zámerne prázdna. Používa sa na meranie výkonnosti atď.",
"external_image_whitelist": " #Nechajte tento riadok presne tak, ako je<pre>\n#Časti regulárnych výrazov (tie, ktoré sa píšu medzi //) napíšte dolu\n#Budú porovnané s URL externých obrázkov\n#Tie, ktoré budú zodpovedať reg. výrazu sa zobrazia ako obrázky, inak sa zobrazí iba odkaz na obrázok\n#Riadky, ktoré začínajú znakom # sa považujú za komentáre\n#Na veľkosti písmen nezáleží\n\n#Napíšte všetky časti reg. výrazov nad tento riadok. Nechajte tento riadok presne tak, ako je</pre>",
"december-date": "$1. децембар",
"period-am": "преподне",
"period-pm": "поподне",
- "pagecategories": "{{PLURAL:$1|Категорија|Категорије|Категорија}}",
+ "pagecategories": "{{PLURAL:$1|Категорија|Категорије}}",
"category_header": "Странице у категорији „$1“",
"subcategories": "Поткатегорије",
"category-media-header": "Датотеке у категорији „$1“",
"permalink": "Трајна веза",
"print": "Штампај",
"view": "Погледај",
- "view-foreign": "Ð\92иди на пројекту $1",
+ "view-foreign": "Ð\9fогледаÑ\98 на пројекту $1",
"edit": "Уреди",
"edit-local": "Уреди локални опис",
"create": "Направи",
"pagetitle-view-mainpage": "{{SITENAME}}",
"backlinksubtitle": "← $1",
"retrievedfrom": "Преузето из „$1“",
- "youhavenewmessages": "Имате $1 ($2).",
+ "youhavenewmessages": "{{PLURAL:$3|Имате}} $1 ($2).",
"youhavenewmessagesfromusers": "{{PLURAL:$4|Имате}} $1 од {{PLURAL:$3|другог корисника|$3 корисника}} ($2).",
"youhavenewmessagesmanyusers": "Имате $1 од много корисника ($2).",
"newmessageslinkplural": "{{PLURAL:$1|нову поруку|нове поруке|нових порука}}",
"sort-descending": "Поређај опадајуће",
"sort-ascending": "Поређај растуће",
"nstab-main": "Страница",
- "nstab-user": "{{GENDER:{{BASEPAGENAME}}|Корисник|Корисница|Корисник}}",
+ "nstab-user": "Корисничка страница",
"nstab-media": "Медији",
- "nstab-special": "Ð\9fоÑ\81ебно",
- "nstab-project": "Ð\9fÑ\80оÑ\98екаÑ\82",
+ "nstab-special": "Ð\9fоÑ\81ебна Ñ\81Ñ\82Ñ\80аниÑ\86а",
+ "nstab-project": "СÑ\82Ñ\80аниÑ\86а пÑ\80оÑ\98екÑ\82а",
"nstab-image": "Датотека",
"nstab-mediawiki": "Порука",
"nstab-template": "Шаблон",
"userlogin-yourpassword-ph": "Унесите своју лозинку",
"createacct-yourpassword-ph": "Унесите лозинку",
"yourpasswordagain": "Поново унеси лозинку:",
- "createacct-yourpasswordagain": "Потврди лозинку",
+ "createacct-yourpasswordagain": "Потврдите лозинку",
"createacct-yourpasswordagain-ph": "Унесите лозинку поново",
"userlogin-remembermypassword": "Остави ме пријављеног/у",
"userlogin-signwithsecure": "Користите сигурну конекцију",
"showpreview": "Прикажи претпреглед",
"showdiff": "Прикажи измене",
"blankarticle": "<strong>Упозорење:</strong> Страница коју правите је празна.\nАко још једном притиснете „$1”, страница ће бити направљена без икаквог садржаја.",
- "anoneditwarning": "<strong>УпозоÑ\80еÑ\9aе:</strong> Ð\9dиÑ\81Ñ\82е пÑ\80иÑ\98авÑ\99ени. Ð\90ко обÑ\98авиÑ\82е Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83, Ð\92аÑ\88а IP адÑ\80еÑ\81а Ñ\9bе биÑ\82и Ñ\98авно видÑ\99ива Ñ\83 Ñ\9aеноÑ\98 иÑ\81Ñ\82оÑ\80иÑ\98и измена и дÑ\80Ñ\83где. Ð\90ко Ñ\81е <strong>[$1 пÑ\80иÑ\98авиÑ\82е]</strong> или <strong>[$2 оÑ\82воÑ\80иÑ\82е налог]</strong>, поÑ\80ед оÑ\81Ñ\82алиÑ\85 погодноÑ\81Ñ\82и коÑ\98е добиÑ\98аÑ\82е Ð\92аÑ\88е измене Ñ\9bе биÑ\82и пÑ\80ипиÑ\81иване Ð\92аÑ\88ем коÑ\80иÑ\81ниÑ\87ком именÑ\83.",
+ "anoneditwarning": "<strong>УпозоÑ\80еÑ\9aе:</strong> Ð\9dиÑ\81Ñ\82е пÑ\80иÑ\98авÑ\99ени. Ð\92аÑ\88а IP адÑ\80еÑ\81а Ñ\9bе биÑ\82и Ñ\98авно видÑ\99ива ако напÑ\80авиÑ\82е некÑ\83 изменÑ\83. Ð\90ко Ñ\81е <strong>[$1 пÑ\80иÑ\98авиÑ\82е]</strong> или <strong>[$2 оÑ\82воÑ\80иÑ\82е налог]</strong>, Ð\92аÑ\88е измене Ñ\9bе биÑ\82и пÑ\80ипиÑ\81иване Ð\92аÑ\88ем коÑ\80иÑ\81ниÑ\87ком именÑ\83, поÑ\80ед дÑ\80Ñ\83гиÑ\85 погодноÑ\81Ñ\82и.",
"anonpreviewwarning": "<em>Нисте пријављени. Ако објавите страницу, Ваша IP адреса ће бити јавно видљива у њеној историји измена и другде.</em>",
"missingsummary": "<strong>Подсетник:</strong> Нисте унели опис измене.\nАко поново кликнете на „$1”, Ваша измена ће бити сачувана без описа.",
"selfredirect": "<strong>Упозорење:</strong> Преусмеравате ову страницу на њу саму.\nМожда вам је одредишна страница за преусмерење погрешна или уређујете погрешну страницу.\nАко још једном притиснете „$1”, преусмерење ће свеједно бити направљено.",
"newarticle": "(нови)",
"newarticletext": "Дошли сте на страницу која још не постоји.\nДа бисте је направили, почните да куцате у прозор испод овог текста (погледајте [$1 страницу за помоћ]).\nАко сте овде дошли грешком, вратите се на претходну страницу.",
"anontalkpagetext": "----\n<em>Ово је страница за разговор с анонимним корисником који још нема налог или га не користи.</em>\nЗбог тога морамо да користимо бројчану IP адресу како бисмо га препознали.\nТакву адресу може делити више корисника.\nАко сте анонимни корисник и мислите да су вам упућене примедбе, [[Special:CreateAccount|отворите налог]] или се [[Special:UserLogin|пријавите]] да бисте избегли будућу забуну с осталим анонимним корисницима.",
- "noarticletext": "Ð\9dа овоÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86и Ñ\82Ñ\80енÑ\83Ñ\82но нема Ñ\81адÑ\80жаÑ\98а.\nМожете [[Special:Search/{{PAGENAME}}|потражити овај наслов]] на другим страницама,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} претражити сродне извештаје] или [{{fullurl:{{FULLPAGENAME}}|action=edit}} направити ову страницу]</span>.",
- "noarticletext-nopermission": "Ð\9dа овоÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86и Ñ\82Ñ\80енÑ\83Ñ\82но нема Ñ\81адÑ\80жаÑ\98а.\nМожете [[Special:Search/{{PAGENAME}}|потражити овај наслов]] на другим страницама или <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} претражити сродне дневнике]</span>, али немате дозволу да направите ову страницу.",
+ "noarticletext": "Ð\9dа овоÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86и Ñ\82Ñ\80енÑ\83Ñ\82но нема Ñ\82екÑ\81Ñ\82а.\nМожете [[Special:Search/{{PAGENAME}}|потражити овај наслов]] на другим страницама,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} претражити сродне извештаје] или [{{fullurl:{{FULLPAGENAME}}|action=edit}} направити ову страницу]</span>.",
+ "noarticletext-nopermission": "Ð\9dа овоÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86и Ñ\82Ñ\80енÑ\83Ñ\82но нема Ñ\82екÑ\81Ñ\82а.\nМожете [[Special:Search/{{PAGENAME}}|потражити овај наслов]] на другим страницама или <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} претражити сродне дневнике]</span>, али немате дозволу да направите ову страницу.",
"missing-revision": "Не могу да пронађем измену бр. $1 на страници под називом „{{FULLPAGENAME}}“.\n\nОво се обично дешава када пратите застарелу везу до странице која је обрисана.\nВише информација можете пронаћи у [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} дневнику брисања].",
"userpage-userdoesnotexist": "Кориснички налог „<nowiki>$1</nowiki>“ није отворен.\nРазмислите да ли заиста желите да направите/уредите ову страницу.",
"userpage-userdoesnotexist-view": "Кориснички налог „$1“ није отворен.",
"viewpagelogs": "Дневници ове странице",
"nohistory": "Не постоји историја измена ове странице.",
"currentrev": "Тренутна измена",
- "currentrev-asof": "Ð\9fоÑ\81ледÑ\9aа измена на датум $2 у $3",
+ "currentrev-asof": "ТÑ\80енÑ\83Ñ\82на измена на датум $2 у $3",
"revisionasof": "Измена на датум $2 у $3",
"revision-info": "Измена од $1 од стране {{GENDER:$6|корисника $2|кориснице $2}}$7",
"previousrevision": "← Старија измена",
"next-page": "следећа страница",
"prevn-title": "$1 {{PLURAL:$1|претходни резултат|претходна резултата|претходних резултата}}",
"nextn-title": "$1 {{PLURAL:$1|следећи резултат|следећа резултата|следећих резултата}}",
- "shown-title": "Прикажи $1 {{PLURAL:$1|резултат|резултата|резултата}} по страници",
+ "shown-title": "Прикажи $1 {{PLURAL:$1|резултат|резултата}} по страници",
"viewprevnext": "Погледај ($1 {{int:pipe-separator}} $2) ($3).",
"searchmenu-exists": "<strong>Постоји страница под називом „[[:$1]]”!</strong> {{PLURAL:$2|0=|Такође погледајте друге пронађене резултате претраге.}}",
- "searchmenu-new": "<strong>Направите страницу „[[:$1]]”!</strong> {{PLURAL:$2|0=|Такође погледајте резултат претраге.|Такође погледајте резултате претраге.}}",
+ "searchmenu-new": "<strong>Направите страницу „[[:$1]]” на овом викију!</strong> {{PLURAL:$2|0=|Такође погледајте страницу пронађену претрагом.|Такође погледајте резултате претраге.}}",
"searchprofile-articles": "Странице са садржајем",
"searchprofile-images": "Датотеке",
"searchprofile-everything": "Све",
"searchprofile-images-tooltip": "Претражите датотеке",
"searchprofile-everything-tooltip": "Претражите сав садржај (укључујући странице за разговор)",
"searchprofile-advanced-tooltip": "Претражите прилагођене именске просторе",
- "search-result-size": "$1 ({{PLURAL:$2|1 реч|$2 речи|$2 речи}})",
+ "search-result-size": "$1 ({{PLURAL:$2|1 реч|$2 речи}})",
"search-result-category-size": "{{PLURAL:$1|1 члан|$1 члана|$1 чланова}}, ({{PLURAL:$2|1 поткатегорија|$2 поткатегорије|$2 поткатегорија}}, {{PLURAL:$3|1 датотека|$3 датотеке|$3 датотека}})",
- "search-redirect": "(преусмерено са $1)",
+ "search-redirect": "(преусмерење са $1)",
"search-section": "(одељак $1)",
"search-category": "(категорија $1)",
"search-file-match": "(подудара се садржај датотеке)",
- "search-suggest": "Да ли сте мислили на: $1",
+ "search-suggest": "Да ли сте мислили: $1",
"search-rewritten": "Приказани резултати за $1. Ипак претражи $2.",
"search-interwiki-caption": "Резултати са сестринских пројеката",
"search-interwiki-default": "Резултати са $1:",
"showingresults": "Испод {{PLURAL:$1|је приказан <strong>1</strong> резултат|су приказана <strong>$1</strong> резултата|је приказано <strong>$1</strong> резултата}}, почев од броја <strong>$2</strong>.",
"showingresultsinrange": "Испод {{PLURAL:$1|је приказан <strong>1</strong> резултат|су приказана <strong>$1</strong> резултата|је приказано <strong>$1</strong> резултата}}, у распону од <strong>$2</strong> до <strong>$3</strong>.",
"search-showingresults": "{{PLURAL:$4|Резултат <strong>$1</strong> од <strong>$3</strong>|Резултати <strong>$1—$2</strong> од <strong>$3</strong>}}",
- "search-nonefound": "Ð\9dема поклапаÑ\9aа.",
+ "search-nonefound": "Ð\9dиÑ\81Ñ\83 пÑ\80онаÑ\92ени Ñ\80езÑ\83лÑ\82аÑ\82и коÑ\98и одговаÑ\80аÑ\98Ñ\83 Ñ\83пиÑ\82Ñ\83.",
"search-nonefound-thiswiki": "Нема резултата на овом сајту који се поклапају са термином претраге.",
"powersearch-legend": "Напредна претрага",
"powersearch-ns": "Претрага по именским просторима:",
"right-bot": "сматрање измена као аутоматски процес",
"right-nominornewtalk": "непоседовање мањих измена на страницама за разговор отвара прозор за нове поруке",
"right-apihighlimits": "коришћење виших граница за упите из API-ја",
- "right-writeapi": "пиÑ\81аÑ\9aе API-ја",
+ "right-writeapi": "могÑ\83Ñ\9bноÑ\81Ñ\82 пиÑ\81аÑ\9aа API-ја",
"right-delete": "брисање страница",
"right-bigdelete": "брисање страница с великом историјом",
"right-deletelogentry": "брисање и враћање одређених ставки у дневнику",
"recentchanges-label-plusminus": "Промена величине странице у бајтовима",
"recentchanges-legend-heading": "<strong>Легенда:</strong>",
"recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (такође погледајте [[Special:NewPages|списак нових страница]])",
+ "recentchanges-legend-plusminus": "(<em>±123</em>)",
"recentchanges-submit": "Прикажи",
"rcfilters-tag-remove": "Уклоните филтер „$1“",
"rcfilters-legend-heading": "<strong>Списак скраћеница:</strong>",
"rcfilters-target-page-placeholder": "Унесите име странице (или категорије)",
"rcnotefrom": "Испод {{PLURAL:$5|је измена|су измене}} од <strong>$3, $4</strong> (до <strong>$1</strong> приказано).",
"rclistfromreset": "Ресетуј одабир датума",
- "rclistfrom": "Прикажи нове измене почев од $2, $3",
+ "rclistfrom": "Прикажи нове измене почев од $3 у $2",
"rcshowhideminor": "$1 мање измене",
"rcshowhideminor-show": "Прикажи",
"rcshowhideminor-hide": "Сакриј",
"recentchangeslinked-feed": "Сродне измене",
"recentchangeslinked-toolbox": "Сродне измене",
"recentchangeslinked-title": "Сродне измене са „$1“",
- "recentchangeslinked-summary": "УнеÑ\81иÑ\82е име Ñ\81Ñ\82Ñ\80аниÑ\86е да биÑ\81Ñ\82е видели пÑ\80омене на Ñ\81Ñ\82Ñ\80аниÑ\86ама коÑ\98е Ñ\81Ñ\83 повезане Ñ\81а или Ñ\81а Ñ\82е Ñ\81Ñ\82Ñ\80аниÑ\86е. (Ð\94а биÑ\81Ñ\82е видели Ñ\87ланове каÑ\82егоÑ\80иÑ\98е, Ñ\83неÑ\81иÑ\82е {{ns:category}}:Ð\98ме каÑ\82егоÑ\80иÑ\98е). Ð\9fÑ\80омене на Ñ\81Ñ\82Ñ\80аниÑ\86ама коÑ\98е Ñ\81Ñ\83 на [[Special:Watchlist|Вашем списку надгледања]] су <strong>подебљане</strong>.",
+ "recentchangeslinked-summary": "УнеÑ\81иÑ\82е име Ñ\81Ñ\82Ñ\80аниÑ\86е да биÑ\81Ñ\82е видели измене Ñ\81Ñ\82Ñ\80аниÑ\86а коÑ\98е Ñ\81Ñ\83 повезане Ñ\81а или Ñ\81а Ñ\82е Ñ\81Ñ\82Ñ\80аниÑ\86е. (Ð\94а биÑ\81Ñ\82е видели Ñ\87ланове каÑ\82егоÑ\80иÑ\98е, Ñ\83неÑ\81иÑ\82е {{ns:category}}:Ð\98ме каÑ\82егоÑ\80иÑ\98е). Ð\98змене Ñ\81Ñ\82Ñ\80аниÑ\86а на [[Special:Watchlist|Вашем списку надгледања]] су <strong>подебљане</strong>.",
"recentchangeslinked-page": "Назив странице:",
"recentchangeslinked-to": "Прикажи измене страница које су повезане с датом страницом",
"recentchanges-page-added-to-category": "[[:$1]] је додата у категорију",
"upload-curl-error28": "Отпремање је истекло",
"upload-curl-error28-text": "Сервер не одговара на упит.\nПроверите да ли сајт ради, мало осачекајте и покушајте поново.\nПробајте касније када буде мање оптерећење.",
"license": "Лиценца:",
- "license-header": "Ð\9bиÑ\86енÑ\86а:",
+ "license-header": "Ð\9bиÑ\86енÑ\86иÑ\80аÑ\9aе",
"nolicense": "Није изабрано",
"licenses-edit": "Уреди избор лиценци",
"license-nopreview": "(преглед није доступан)",
"apisandbox-multivalue-all-namespaces": "$1 (сви именски простори)",
"apisandbox-multivalue-all-values": "$1 (све вредности)",
"booksources": "Штампани извори",
- "booksources-search-legend": "ТÑ\80ажи кÑ\9aижевне изворе",
+ "booksources-search-legend": "Ð\9fÑ\80еÑ\82Ñ\80ажи Ñ\88Ñ\82ампане изворе",
"booksources-isbn": "ISBN:",
"booksources-search": "Претражи",
"booksources-text": "Испод се налази списак веза ка сајтовима који се баве продајом нових и половних књига, а који би могли имати додатне податке о књигама које тражите:",
"watchnologin": "Нисте пријављени",
"addwatch": "Додај на списак надгледања",
"addedwatchtext": "Страница „[[:$1]]“ и њена страница за разговор је додата на Ваш [[Special:Watchlist|списак надгледања]].",
+ "addedwatchtext-talk": "Страница „[[:$1]]” и њена придружена страница је додата на Ваш [[Special:Watchlist|списак надгледања]]",
"addedwatchtext-short": "Страница „$1“ је додата на Ваш списак надгледања.",
"removewatch": "Уклони са списка надгледања",
- "removedwatchtext": "Страница „[[:$1]]“ и њена страница за разговор је уклоњена с вашег [[Special:Watchlist|списка надгледања]].",
+ "removedwatchtext": "Страница „[[:$1]]“ и њена страница за разговор је уклоњена са Вашег [[Special:Watchlist|списка надгледања]].",
"removedwatchtext-short": "Страница „$1“ је уклоњена с вашег списка надгледања.",
"watch": "Надгледај",
"watchthispage": "Надгледај ову страницу",
"wlshowhidemine": "моје измене",
"wlshowhidecategorization": "категоризацију страница",
"watchlist-options": "Опције списка надгледања",
- "watching": "Надгледање…",
- "unwatching": "УклаÑ\9aаÑ\9aе Ñ\81а Ñ\81пиÑ\81ка надгледаÑ\9aа...",
+ "watching": "Надгледам…",
+ "unwatching": "Ð\9fÑ\80еÑ\81Ñ\82аÑ\98ем да надгледам...",
"watcherrortext": "Дошло је до грешке при промени поставки вашег списка надгледања за „$1“.",
"enotif_reset": "Означи све странице као посећене",
"enotif_impersonal_salutation": "{{SITENAME}} корисник",
"dellogpage": "Дневник брисања",
"dellogpagetext": "Испод је списак последњих брисања.",
"deletionlog": "дневник брисања",
- "logentry-create-create": "$1 {{GENDER:$2|креирао је|креирала је}} страницу $3",
+ "logentry-create-create": "$1 је {{GENDER:$2|направио|направила}} страницу $3",
"reverted": "Враћено на ранију измену",
"deletecomment": "Разлог:",
"deleteotherreason": "Други/додатни разлог:",
"logentry-contentmodel-change-revert": "врати",
"protectlogpage": "Дневник заштите",
"protectlogtext": "Испод је списак заштићених страница.\nПогледајте [[Special:ProtectedPages|списак заштићених страница]] за више детаља.",
- "protectedarticle": "је заштитио „[[$1]]“",
- "modifiedarticleprotection": "промењен степен заштите за „[[$1]]“",
+ "protectedarticle": "је {{GENDER:|заштитио|заштитила}} страницу „[[$1]]“",
+ "modifiedarticleprotection": "је {{GENDER:|променио|променила}} степен заштите странице „[[$1]]“",
"unprotectedarticle": "је скинуо заштиту са странице „[[$1]]“",
"movedarticleprotection": "је преместио подешавања заштите са „[[$2]]“ на „[[$1]]“",
"protectedarticle-comment": "{{GENDER:$2|Заштићена}} страница [[$1]]",
"contribsub2": "За {{GENDER:$3|$1}} ($2)",
"contributions-userdoesnotexist": "Кориснички налог „$1“ није отворен.",
"nocontribs": "Нема измена које одговарају наведеним критеријумима.",
- "uctop": "(последња)",
+ "uctop": "(тренутна)",
"month": "од месеца (и раније):",
"year": "од године (и раније):",
"sp-contributions-newbies": "Прикажи само доприносе нових корисника",
"blocklogpage": "Дневник блокирања",
"blocklog-showlog": "{{GENDER:$1|Овај корисник је раније блокиран|Ова корисница је раније блокирана}}.\nИсторија блокирања се налази испод:",
"blocklog-showsuppresslog": "{{GENDER:$1|Овај корисник је раније блокиран и сакривен|Ова корисница је раније блокирана и сакривена}}.\nИсторија сакривања се налази испод:",
- "blocklogentry": "је блокирао [[$1]] с роком истицања од $2 $3",
- "reblock-logentry": "{{GENDER:|је променио|је променила|је променио}} подешавања за блокирање {{GENDER:$1|корисника|кориснице|корисника}} [[$1]] с роком истека од $2 ($3)",
+ "blocklogentry": "је блокирао [[$1]] са временом истицања од $2 $3",
+ "reblock-logentry": "{{GENDER:|је променио|је променила}} подешавања за блокирање {{GENDER:$1|корисника|кориснице}} [[$1]] са временом истека од $2 ($3)",
"blocklogtext": "Ово је дневник блокирања и деблокирања корисника.\nАутоматски блокиране ИП адресе нису наведене.\nТекуће забране и блокирања можете наћи [[Special:BlockList|овде]].",
"unblocklogentry": "је деблокирао $1",
"block-log-flags-anononly": "само анонимни корисници",
"tooltip-pt-login-private": "Морате да се пријавите да бисте користили овај Вики",
"tooltip-pt-logout": "Одјавите се",
"tooltip-pt-createaccount": "Предлажемо Вам да отворите налог и пријавите се, иако то није обавезно",
- "tooltip-ca-talk": "Разговор о садржају",
+ "tooltip-ca-talk": "Разговор о страници са садржајем",
"tooltip-ca-edit": "Уредите ову страницу",
"tooltip-ca-addsection": "Започните нови одељак",
"tooltip-ca-viewsource": "Ова страница је закључана. \nМожете да погледате њен изворни кôд",
"tooltip-ca-delete": "Обришите ову страницу",
"tooltip-ca-undelete": "Врати измене направљене на овој страници пре него што буде обрисана",
"tooltip-ca-move": "Премести ову страницу",
- "tooltip-ca-watch": "Додајте ову страницу на списак надгледања",
+ "tooltip-ca-watch": "Ð\94одаÑ\98Ñ\82е овÑ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 на Ñ\81воÑ\98 Ñ\81пиÑ\81ак надгледаÑ\9aа",
"tooltip-ca-unwatch": "Уклоните ову страницу са списка надгледања",
"tooltip-search": "Претражите пројекат {{SITENAME}}",
"tooltip-search-go": "Идите на страницу са тачно овим именом ако постоји",
"tooltip-n-mainpage": "Посетите главну страну",
"tooltip-n-mainpage-description": "Посетите главну страну",
"tooltip-n-portal": "О пројекту, шта можете да радите и где да пронађете ствари",
- "tooltip-n-currentevents": "Пронађите додатне информације о тренутним догађајима",
+ "tooltip-n-currentevents": "Пронађите додатне информације о актуелностима",
"tooltip-n-recentchanges": "Списак скорашњих измена на викију",
"tooltip-n-randompage": "Учитајте случајну страницу",
"tooltip-n-help": "Место где можете да научите нешто",
"tooltip-t-permalink": "Трајна веза ка овој измени странице",
"tooltip-ca-nstab-main": "Погледајте страницу са садржајем",
"tooltip-ca-nstab-user": "Погледајте корисничку страницу",
- "tooltip-ca-nstab-media": "Погледајте мултимедијалну датотеку",
+ "tooltip-ca-nstab-media": "Погледајте медијску страницу",
"tooltip-ca-nstab-special": "Ово је посебна страница. Не можете је мењати.",
"tooltip-ca-nstab-project": "Погледајте страницу пројекта",
- "tooltip-ca-nstab-image": "Прикажи страницу датотеке",
+ "tooltip-ca-nstab-image": "Погледајте страницу датотеке",
"tooltip-ca-nstab-mediawiki": "Погледајте системску поруку",
"tooltip-ca-nstab-template": "Погледајте шаблон",
"tooltip-ca-nstab-help": "Погледајте страницу за помоћ",
- "tooltip-ca-nstab-category": "Ð\9fогледаÑ\98Ñ\82е Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 каÑ\82егоÑ\80иÑ\98а",
+ "tooltip-ca-nstab-category": "Ð\9fогледаÑ\98Ñ\82е Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 каÑ\82егоÑ\80иÑ\98е",
"tooltip-minoredit": "Означите као мању измену",
"tooltip-save": "Сачувајте своје измене",
"tooltip-publish": "Објавите своје измене",
"tooltip-preview": "Прегледајте своје измене. Користите ово дугме пре чувања.",
- "tooltip-diff": "Погледајте које измене сте направили на тексту",
+ "tooltip-diff": "Погледајте које измене сте направили у тексту",
"tooltip-compareselectedversions": "Погледаjте разлике између две изабране измене ове странице.",
"tooltip-watch": "Додајте ову страницу на свој списак надгледања",
"tooltip-watchlistedit-normal-submit": "Уклоните наслове",
"tooltip-watchlistedit-raw-submit": "Ажурирај списак",
"tooltip-recreate": "Поново направите страницу иако је обрисана",
"tooltip-upload": "Започните отпремање",
- "tooltip-rollback": "â\80\9eÐ\92Ñ\80аÑ\82иâ\80\9c вÑ\80аÑ\9bа измене поÑ\81ледÑ\9aег коÑ\80иÑ\81ника једним кликом",
- "tooltip-undo": "Опција „поништи” враћа ову измену и отвара образац за уређивање у претпрегледном моду. Омогућава додавање разлога у опису измене.",
+ "tooltip-rollback": "â\80\9eÐ\92Ñ\80аÑ\82иâ\80\9c вÑ\80аÑ\9bа измене поÑ\81ледÑ\9aег допÑ\80иноÑ\81иоÑ\86а ове Ñ\81Ñ\82Ñ\80аниÑ\86е једним кликом",
+ "tooltip-undo": "„Поништи” враћа ову измену и отвара образац за уређивање у претпрегледном моду. Дозвољава додавање разлога у опису измене.",
"tooltip-preferences-save": "Сачувај подешавања",
"tooltip-summary": "Унесите кратак опис",
"interlanguage-link-title": "$1 — $2",
"spam_blanking": "Све измене садрже везе до $1. Чистим",
"spam_deleting": "Све измене садрже везе до $1. Бришем",
"simpleantispam-label": "Анти-спам провера. \n<strong>Не</strong> попуњавај ово унутра!",
- "pageinfo-title": "Ð\9fодаÑ\86и о „$1“",
+ "pageinfo-title": "Ð\98нÑ\84оÑ\80маÑ\86иÑ\98е за „$1“",
"pageinfo-not-current": "Нажалост, немогуће је прибавити ове податке за старије измене.",
- "pageinfo-header-basic": "Ð\9eÑ\81новни подаÑ\86и",
+ "pageinfo-header-basic": "Ð\9eÑ\81новне инÑ\84оÑ\80маÑ\86иÑ\98е",
"pageinfo-header-edits": "Историја измена",
"pageinfo-header-restrictions": "Заштита странице",
"pageinfo-header-properties": "Својства странице",
"pageinfo-robot-noindex": "Није дозвољено",
"pageinfo-watchers": "Број надгледача странице",
"pageinfo-visiting-watchers": "Број надгледача странице који су посетили скорашње измене",
- "pageinfo-few-watchers": "Ð\9cаÑ\9aе од $1 {{PLURAL:$1|пÑ\80аÑ\82иоÑ\86а|пÑ\80аÑ\82иоÑ\86а|пÑ\80аÑ\82илаÑ\86а}}",
+ "pageinfo-few-watchers": "Ð\9cаÑ\9aе од $1 {{PLURAL:$1|надгледаÑ\87а}}",
"pageinfo-redirects-name": "Број преусмерења на ову страницу",
"pageinfo-redirects-value": "$1",
- "pageinfo-subpages-name": "Ð\9fодÑ\81Ñ\82Ñ\80аниÑ\86е ове странице",
+ "pageinfo-subpages-name": "Ð\91Ñ\80оÑ\98 подÑ\81Ñ\82Ñ\80аниÑ\86а ове странице",
"pageinfo-subpages-value": "$1 ($2 {{PLURAL:$2|преусмерење|преусмерења|преусмерења}}; $3 {{PLURAL:$3|непреусмерење|непреусмерења|непреусмерења}})",
"pageinfo-firstuser": "Аутор странице",
"pageinfo-firsttime": "Датум стварања странице",
"svg-long-desc": "SVG датотека, номинално $1 × $2 пиксела, величина: $3",
"svg-long-desc-animated": "Анимирана SVG датотека, номинално: $1 × $2 пиксела, величина: $3",
"svg-long-error": "Неисправна SVG датотека: $1",
- "show-big-image": "Ð\9fÑ\83на велиÑ\87ина",
+ "show-big-image": "Ð\98звоÑ\80на даÑ\82оÑ\82ека",
"show-big-image-preview": "Величина овог приказа: $1.",
"show-big-image-preview-differ": "Величина $3 прегледа за ову $2 датотеку је $1.",
"show-big-image-other": "$2 {{PLURAL:$2|друга резолуција|друге резолуције|других резолуција}}: $1.",
"htmlform-title-not-exists": "$1 не постоји.",
"htmlform-user-not-exists": "<strong>$1</strong> не постоји.",
"htmlform-user-not-valid": "<strong>$1</strong> није исправно корисничко име.",
- "logentry-delete-delete": "$1 је {{GENDER:$2|обрисао|обрисала|обрисао}} страницу $3",
+ "logentry-delete-delete": "$1 је {{GENDER:$2|обрисао|обрисала}} страницу $3",
"logentry-delete-delete_redir": "$1 је {{GENDER:$2|обрисао|обрисала}} преусмерење $3 преписивањем",
"logentry-delete-restore": "$1 је {{GENDER:$2|вратио|вратила}} страницу $3 ($4)",
"logentry-delete-restore-nocount": "$1 је {{GENDER:$2|вратио|вратила}} страницу $3",
"Bugoslav",
"Acamicamacaraca",
"Fitoschido",
- "BadDog"
+ "BadDog",
+ "Vlad5250"
]
},
"tog-underline": "Podvlačenje veza:",
"right-editsemiprotected": "uređivanje stranica pod zaštitom „{{int:protect-level-autoconfirmed}}“",
"right-editinterface": "uređivanje korisničkog okruženja",
"right-editusercss": "uređivanje tuđih CSS datoteka",
+ "right-edituserjson": "uređivanje tuđih JSON datoteka",
"right-edituserjs": "uređivanje tuđih JavaScript datoteka",
"right-editmyusercss": "uređivanje sopstvenih CSS datoteka",
+ "right-editmyuserjson": "uređivanje sopstvenih JSON datoteka",
"right-editmyuserjs": "uređivanje sopstvenih JavaScript datoteka",
"right-viewmywatchlist": "vidi sopstveni spisak nadgledanja",
"right-editmywatchlist": "uređivanje sopstvenog spiska nadgledanja; neke preduzete radnje će svejedno dodati stranice na spisak i bez ovog prava",
"Uostofchuodnego",
"Przem(1)s",
"Fitoschido",
- "Matěj Suchánek"
+ "Matěj Suchánek",
+ "Vlad5250"
]
},
"tog-underline": "Podsztrychniyniy linkōw:",
"right-editprotected": "Sprowjej zawarte zajty (ze zawarćym kaskadowym)",
"right-editinterface": "Sprowjej interfejs użytkowńika",
"right-editusercss": "Sprowjej pliki CSS inkszych użytkowńikůw",
+ "right-edituserjson": "Sprowjej pliki JSON inkšych užytkowńikůw",
"right-edituserjs": "Sprowjej pliki JS inkšych užytkowńikůw",
"right-rollback": "Rewert drap sprawjyńo uostatńygo użytkowńika kery sprowjoł dano zajta",
"right-markbotedits": "Uoznocz rewertowane sprawjyńo kej sprawjyńo botůw",
"recentchangesdays": "ఇటీవలి మార్పులు లో చూపించవలసిన రోజులు:",
"recentchangesdays-max": "గరిష్ఠంగా $1 {{PLURAL:$1|రోజు|రోజులు}}",
"recentchangescount": "అప్రమేయంగా చూపించాల్సిన దిద్దుబాట్ల సంఖ్య:",
- "prefs-help-recentchangescount": "à°\87ది à°\87à°\9fà±\80వలి మారà±\8dà°ªà±\81à°²à±\81, à°ªà±\87à°\9cà±\80 à°\9aà°°à°¿à°¤à±\8dà°°à°²à±\81, మరియà±\81 à°\9aà°¿à°\9fà±\8dà°\9fాలà°\95à±\81 వరà±\8dతిసà±\8dà°¤à±\81à°\82ది.",
- "prefs-help-watchlist-token2": "మీ వీక్షణజాబితా యొక్క జాలవడ్డింపుకు చెందిన రహస్య తాళమిది.\nఈ తాళం తెలిసిన ఎవరైనా మీ వీక్షణజాబితాను చదవగలుగుతారు. అందుచేత దీన్ని ఎవరికీ ఇవ్వకండి.\n[[Special:ResetTokens|దాన్ని మార్చాలంటే ఇక్కడ నొక్కండి]].",
+ "prefs-help-recentchangescount": "à°\97à°°à°¿à°·à±\8dà° à°¸à°\82à°\96à±\8dà°¯: 1000",
+ "prefs-help-watchlist-token2": "మీ వీక్షణజాబితా యొక్క జాలవడ్డింపుకు చెందిన రహస్య తాళమిది.\nఈ తాళం తెలిసిన ఎవరైనా మీ వీక్షణజాబితాను చదవగలుగుతారు. అందుచేత దీన్ని ఎవరికీ ఇవ్వకండి.\nఅవసరమైతే [[Special:ResetTokens|దాన్ని మార్చుకోవచ్చు]].",
"savedprefs": "మీ అభిరుచులను భద్రపరిచాం.",
"savedrights": "{{GENDER:$1|$1}} వాడుకరి గుంపులు భద్రమయ్యాయి.",
"timezonelegend": "కాల మండలం:",
"right-siteadmin": "డేటాబేసును లాక్, అన్లాక్ చెయ్యి",
"right-override-export-depth": "5 లింకుల లోతు వరకు ఉన్న పేజీలతో సహా, పేజీలను ఎగుమతి చెయ్యి",
"right-sendemail": "ఇతర వాడుకరులకు ఈ-మెయిలు పంపించడం",
- "right-managechangetags": "డేటాబేసులో [[Special:Tags|ట్యాగుల]]ను సృష్టించడం, తొలగించడం",
+ "right-managechangetags": "[[Special:Tags|ట్యాగుల]]ను సృష్టించడం, (అ)చేతనం చెయ్యడం",
"right-applychangetags": "తన మార్పులతో [[Special:Tags|ట్యాగుల]]ను ఆపాదించడం",
"right-changetags": "విడి కూర్పులకు, చిట్టా పద్దులకు ఏవైనా [[Special:Tags|ట్యాగుల]]ను చేర్చడం, తొలగించడం",
- "right-deletechangetags": "[[ప్రత్యేక:Tags|ట్యాగులను]] డేటాబేసు నుండి తొలగించు",
+ "right-deletechangetags": "[[Special:Tags|ట్యాగులను]] డేటాబేసు నుండి తొలగించు",
"grant-generic": "\"$1\" హక్కుల కట్ట",
"grant-group-email": "ఈమెయిలు పంపించడం",
"grant-group-administration": "నిర్వాహక చర్యలు చేపట్టడం",
"pageswithprop-prophidden-binary": "binary లక్షణం విలువ దాచబడింది ($1)",
"doubleredirects": "జంట దారిమార్పులు",
"doubleredirectstext": "ఇతర దారిమార్పు పుటలకి తీసుకెళ్ళే దారిమార్పులని ఈ పుట చూపిస్తుంది.\nప్రతీ వరుసలో మొదటి మరియు రెండవ దారిమార్పులకు లంకెలు, ఆలానే రెండవ దారిమార్పు పుట యొక్క లక్ష్యం ఉన్నాయి. సాధారణంగా ఈ రెండవ దారిమార్పు యొక్క లక్ష్యమే \"అసలైనది\", అదే మొదటి దారిమార్పు యొక్క లక్ష్యంగా ఉండాలి.\n<del>కొట్టివేయబడిన</del> పద్దులు పరిష్కరించబడ్డవి.",
- "double-redirect-fixed-move": "[[$1]]ని తరలించారు, అది ప్రస్తుతం [[$2]]కి దారిమార్పు.",
+ "double-redirect-fixed-move": "[[$1]]ని తరలించారు.\nదాన్ని ఆటోమేటిగ్గా తాజాకరించాం. ప్రస్తుతం అది [[$2]]కి దారిమార్పు చేస్తోంది.",
"double-redirect-fixed-maintenance": "[[$1]] కు జమిలి దారిమార్పును [[$2]] కు అప్రమేయంగా సరిచేస్తున్నాం.",
"double-redirect-fixer": "దారిమార్పు సరిద్దువారు",
"brokenredirects": "తెగిపోయిన దారిమార్పులు",
"dellogpage": "తొలగింపుల చిట్టా",
"dellogpagetext": "ఇది ఇటీవలి తుడిచివేతల జాబితా.",
"deletionlog": "తొలగింపుల చిట్టా",
+ "logentry-create-create": "$3 పేజీని $1 {{GENDER:$2|సృష్టించారు}}",
"reverted": "పాత కూర్పుకు తీసుకువెళ్ళాం.",
"deletecomment": "కారణం:",
"deleteotherreason": "ఇతర/అదనపు కారణం:",
"newimages-summary": "ఇటీవలే ఎగుమతైన ఫైళ్ళను ఈ ప్రత్యేక పేజీ చూపిస్తుంది.",
"newimages-legend": "పడపోత",
"newimages-label": "ఫైలుపేరు (లేదా దానిలోని భాగం):",
+ "newimages-user": "ఐపీ చిరునామా లేదా వాడుకరి పేరు",
+ "newimages-newbies": "కొత్త ఖాతాల రచనలని మాత్రమే చూపించు",
"newimages-showbots": "బాట్లు చేసిన అప్లోడ్లు చూపించు",
"newimages-mediatype": "మాధ్యమ రకం:",
"noimages": "చూసేందుకు ఏమీ లేదు.",
"compare-title-not-exists": "మీరు పేర్కొన్న శీర్షిక లేనే లేదు.",
"compare-revision-not-exists": "మీరు పేర్కొన్న కూర్పు లేనే లేదు.",
"diff-form": "తేడాలు",
+ "permanentlink": "స్థిర లంకె",
"dberr-problems": "క్షమించండి! ఈ సైటు సాంకేతిక సమస్యలని ఎదుర్కొంటుంది.",
"dberr-again": "కొన్ని నిమిషాలాగి మళ్ళీ ప్రయత్నించండి.",
"dberr-info": "(డేటాబేసును చేరలేకున్నాం: $1)",
"log-action-filter-managetags-delete": "ట్యాగు తొలగింపు",
"log-action-filter-managetags-activate": "ట్యాగు చేతనం",
"log-action-filter-managetags-deactivate": "ట్యాగు అచేతనం",
+ "log-action-filter-protect-protect": "సంరక్షణ",
+ "log-action-filter-upload-upload": "కొత్త ఎక్కింపు",
"authmanager-userdoesnotexist": "వాడుకరి ఖాతా \"$1\" నమోదయి లేదు.",
"authmanager-userlogin-remembermypassword-help": "సెషను ముగిసిన తరువాత కూడా సంకేతపదాన్ని గుర్తుంచుకోమంటారా",
"authmanager-username-help": "ధ్రువీకరణ కోసం వాడుకరిపేరు.",
"restrictionsfield-help": "వరుసకొక్క ఐపీ అడ్రసు లేదా CIDR శ్రేణి. ప్రతీ ఒక్కదాన్నీ చేతనం చేసేందుకు, వాడండి:<pre>0.0.0.0/0\n::/0</pre>",
"revid": "కూర్పు $1",
"pageid": "పేజీ ఐడీ $1",
+ "pagedata-bad-title": "చెల్లని శీర్షిక: $1.",
+ "passwordpolicies": "సంకేతపదపు విధానాలు",
+ "passwordpolicies-group": "సమూహం",
"passwordpolicies-policies": "విధానాలు"
}
"resetpass-temp-password": "Geçici parola:",
"resetpass-abort-generic": "Parola değişikliği bir uzantı tarafından iptal edildi.",
"resetpass-expired": "Parolanızın süresi bitti. Lütfen, giriş için yeni bir parola oluşturun.",
- "resetpass-expired-soft": "Parolanızın süresi bitti ve değiştirilmesi gerekiyor. Lütfen, yeni bir parola seçin veya daha sonra oluşturmak için \"{{int:authprovider-resetpass-skip-label}}\" butonuna tıklayın.",
- "resetpass-validity-soft": "Parolanız geçerli değil: $1\n\nLütfen yeni bir şifre belirleyin ya da daha sonra sıfırlamak için \"{{int:authprovider-resetpass-skip-label}}\" bağlantısını tıklayın.",
+ "resetpass-expired-soft": "Parolanızın süresi bitti ve değiştirilmesi gerekiyor. Lütfen, yeni bir parola seçin veya daha sonra değiştirmek için \"{{int:authprovider-resetpass-skip-label}}\" düğmesine tıklayın.",
+ "resetpass-validity-soft": "Parolanız geçerli değil: $1\n\nLütfen yeni bir şifre belirleyin ya da daha sonra değiştirmek için \"{{int:authprovider-resetpass-skip-label}}\" bağlantısını tıklayın.",
"passwordreset": "Parola sıfırlama",
"passwordreset-text-one": "Parolanızı sıfırlamak için bu formu doldurun.",
"passwordreset-text-many": "{{PLURAL:$1|E-posta ile geçici bir parola almak için alanlardan birini doldurun.}}",
"stub-threshold-disabled": "Devre dışı",
"recentchangesdays": "Son değişikliklerde gösterilecek günler:",
"recentchangesdays-max": "(en fazla $1 {{PLURAL:$1|gün|gün}})",
- "recentchangescount": "Varsayılan olarak gösterilecek değişiklik sayısı:",
- "prefs-help-recentchangescount": "Bu, son değişiklikleri, sayfa geçmişlerini ve günlükleri içerir.",
- "prefs-help-watchlist-token2": "Bu izleme listenizin gizli anahtarıdır. Anahtarı bilen herkes izleme listenizi görebilir. Bu nedenle kimseyle paylaşmayın. [[Special:ResetTokens|Bu anahtarı sıfırlamak isterseniz buraya tıklayın]].",
+ "recentchangescount": "Son değişiliklerde, sayfa geçmişlerinde ve günlüklerde varsayılan olarak gösterilecek değişiklik sayısı:",
+ "prefs-help-recentchangescount": "Azami sayı: 1000",
+ "prefs-help-watchlist-token2": "Bu izleme listenizin gizli anahtarıdır.\nAnahtarı bilen herkes izleme listenizi görebilir, bu nedenle kimseyle paylaşmayın.\nEğer isterseniz, [[Special:ResetTokens|sıfırlayabilirsiniz]].",
"savedprefs": "Tercihleriniz kaydedildi.",
"savedrights": "{{GENDER:$1|$1}} için kullanıcı grupları kaydedildi.",
"timezonelegend": "Zaman dilimi:",
"prefs-files": "Dosyalar",
"prefs-custom-css": "Özel CSS",
"prefs-custom-js": "Özel JS",
- "prefs-common-config": "Tüm temalar için paylaşılan CSS/JS:",
+ "prefs-common-config": "Tüm temalar için paylaşılan CSS/JSON/Javascript:",
"prefs-reset-intro": "Bu sayfayı tercihlerinizi site varsayılanına döndürmek için kullanabilirsiniz. Bu geri alınamaz.",
"prefs-emailconfirm-label": "E-posta doğrulaması:",
"youremail": "E-posta:",
"userrights-groupsmember": "İçinde olduğu gruplar:",
"userrights-groupsmember-auto": "Saklı olarak içinde olduğu gruplar:",
"userrights-groupsmember-type": "$1",
- "userrights-groups-help": "Bu kullanıcının içinde olduğu grupları değiştirebilirsiniz:\n* Seçili bir kutu, kullanıcının o gruba dahil olduğunu anlatır.\n* Seçilmemiş bir kutu, kullanıcının o gruba dahil olmadığını anlatır.\n* *, grubu bir kez ekledikten sonra kaldıramayacağınızı veya tam tersi kaldırdıktan sonra ekleyemeyeceğiniz belirtir.\n* #, grubun sona erme zamanını yalnızca öne çekebileceğinizi, ileri alamayacağınızı belirtir.",
+ "userrights-groups-help": "Bu kullanıcının içinde olduğu grupları değiştirebilirsiniz:\n* Seçili bir kutu, kullanıcının o gruba dahil olduğunu anlatır.\n* Seçilmemiş bir kutu, kullanıcının o gruba dahil olmadığını anlatır.\n* *, grubu bir kez ekledikten sonra kaldıramayacağınızı veya tam tersi kaldırdıktan sonra ekleyemeyeceğiniz belirtir.\n* #, grup üyeliğinin sona erme zamanını yalnızca öne çekebileceğinizi, ileri alamayacağınızı belirtir.",
"userrights-reason": "Neden:",
"userrights-no-interwiki": "Diğer vikilerdeki kullanıcıların izinlerini değiştirmeye yetkiniz yok.",
"userrights-nodatabase": "$1 veritabanı mevcut veya bölgesel değil",
"right-siteadmin": "Veritabanını kilitle ve kilidi aç",
"right-override-export-depth": "Sayfaları, derinlik 5'e kadar bağlantılı sayfalarla beraber, dışa aktar",
"right-sendemail": "Diğer kullanıcılara e-posta gönder",
- "right-managechangetags": "Veritabanında [[Special:Tags|etiket]] oluşturma veya silme",
+ "right-managechangetags": "[[Special:Tags|Etiket]] oluşturma ve (de)aktive etme",
"right-applychangetags": "Değişiklikleriyle beraber [[Special:Tags|etiketleri]] uygula",
"right-changetags": "Tekil sürümler ve günlük kayıtlarına rastgele [[Special:Tags|etiket]] ekleme veya çıkarma",
"grant-group-email": "E-posta gönder",
"grant-group-other": "Çeşitli aktivite",
"grant-createeditmovepage": "Sayfaları oluşturma, düzenleme ve taşıma",
- "grant-editmycssjs": "Kullanıcı CSS/JavaScript'ini düzenle",
+ "grant-editmycssjs": "Kullanıcı CSS/JSON/JavaScript'ini düzenle",
"grant-editmyoptions": "Kullanıcı tercihlerini Düzenle",
"grant-editmywatchlist": "İzleme listeni düzenle",
"grant-editprotected": "Korumalı sayfaları Düzenle",
"rcfilters-savedqueries-rename": "Yeniden adlandır",
"rcfilters-savedqueries-setdefault": "Varsayılan olarak belirle",
"rcfilters-savedqueries-unsetdefault": "Varsayılan olmaktan çıkar",
- "rcfilters-savedqueries-remove": "Kaldır",
+ "rcfilters-savedqueries-remove": "Sil",
"rcfilters-savedqueries-new-name-label": "Ad",
"rcfilters-savedqueries-new-name-placeholder": "Süzgecin amacını tanımlayın",
"rcfilters-savedqueries-apply-label": "Süzgeç oluştur",
"rcfilters-empty-filter": "Etkin süzgeç bulunmuyor. Tüm katkıları gösteriliyor.",
"rcfilters-filterlist-title": "Süzgeçler",
"rcfilters-filterlist-whatsthis": "Bunlar nasıl çalışır?",
- "rcfilters-filterlist-feedbacklink": "Bu (yeni) süzgeç araçları konusunda ne düşündüğünüzü bize bildirin",
+ "rcfilters-filterlist-feedbacklink": "Bu süzgeç araçları konusunda ne düşündüğünüzü bize bildirin",
"rcfilters-highlightbutton-title": "Sonuçları vurgula",
"rcfilters-highlightmenu-title": "Bir renk seçin",
"rcfilters-highlightmenu-help": "Bu özelliği vurgulamak için bir renk seçin",
"rcfilters-filterlist-noresults": "Süzgeç bulunamadı",
"rcfilters-noresults-conflict": "Arama kriterleri çelişkili olduğu için hiçbir sonuç bulunamadı",
- "rcfilters-filtergroup-authorship": "Düzenleme sahipliği",
+ "rcfilters-filtergroup-authorship": "Katkı sahipliği",
"rcfilters-filter-editsbyself-label": "Senin değişiklikleriniz",
"rcfilters-filter-editsbyself-description": "Kendi katkılarınız.",
"rcfilters-filter-editsbyother-label": "Başkalarının düzenlemeleri",
"rcfilters-filter-editsbyother-description": "Kendi düzenlemeleriniz hariç bütün düzenlemeler.",
- "rcfilters-filtergroup-userExpLevel": "Deneyim düzeyi (yalnızca kayıtlı kullanıcılar için)",
+ "rcfilters-filtergroup-userExpLevel": "Kullanıcı kaydı ve deneyim",
"rcfilters-filter-user-experience-level-registered-label": "Kayıtlı",
"rcfilters-filter-user-experience-level-registered-description": "Oturum açmış editörler.",
"rcfilters-filter-user-experience-level-unregistered-label": "Kayıtlı olmayan",
"rcfilters-filter-user-experience-level-newcomer-label": "Yeni gelenler",
"rcfilters-filter-user-experience-level-newcomer-description": "10'dan az düzenlemesi veya 4 günden az etkinliği olan kayıtlı kullanıcılar.",
"rcfilters-filter-user-experience-level-learner-label": "Öğreniciler",
- "rcfilters-filter-user-experience-level-learner-description": "\"Yeni gelenler\"den daha fazla gün boyunca etkinlik ve daha fazla düzenleme, ancak \"Deneyimli kullanıcılar\"dan daha az etkinlik ve düzenleme.",
+ "rcfilters-filter-user-experience-level-learner-description": "Deneyimi \"Yeni gelenler\" ve \"Deneyimli kullanıcılar\" arasındaki editörler.",
"rcfilters-filter-user-experience-level-experienced-label": "Deneyimli kullanıcılar",
- "rcfilters-filter-user-experience-level-experienced-description": "30'dan fazla gün etkinlik ve 500'den fazla düzenleme.",
+ "rcfilters-filter-user-experience-level-experienced-description": "30'dan fazla gün etkinliğe ve 500'den fazla düzenlemeye sahip kayıtlı editörler.",
"rcfilters-filtergroup-automated": "Otomatikleştirilmiş katkılar",
"rcfilters-filter-bots-label": "Bot",
"rcfilters-filter-bots-description": "Otomatikleştirilmiş araçlar tarafından yapılan düzenlemeler.",
"anoncontribs": "Внесок",
"contribsub2": "Для {{GENDER:$3|$1}} ($2)",
"contributions-userdoesnotexist": "Обліковий запис користувача «$1» не зареєстровано.",
- "nocontribs": "Редагувань, що задовольняють заданим умовам не знайдено.",
+ "nocontribs": "Редагувань, що задовольняють заданим умовам, не знайдено.",
"uctop": "(поточна)",
"month": "До місяця (включно):",
"year": "До року (включно):",
"semicolon-separator": "; ",
"comma-separator": ", ",
"colon-separator": ": ",
- "pipe-separator": " • ",
+ "pipe-separator": " | ",
"word-separator": " ",
"ellipsis": "...",
"percent": "$1%",
"listingcontinuesabbrev": "جاری۔",
"index-category": "فہرست شدہ صفحات",
"noindex-category": "غیر فہرست شدہ صفحات",
- "broken-file-category": "فائل کے شکستہ روابط کے حامل صفحات",
+ "broken-file-category": "فائلوں کے شکستہ روابط پر مشتمل صفحات",
"categoryviewer-pagedlinks": "($1) ($2)",
"about": "تعارف",
"article": "صفحہ مواد",
"userinvalidconfigtitle": "<strong>انتباہ:</strong> یہاں «$1» نام سے کوئی پوشاک موجود نہیں۔ شخصی .css اور .js کے صفحات اپنے عنوان میں چھوٹے حروف استعمال کرتے ہیں، مثلاً {{ns:user}}:Foo/Vector.css کی بجائے {{ns:user}}:Foo/vector.css",
"updated": "(اپ ڈیٹڈ)",
"note": "'''نوٹ:'''",
- "previewnote": "'''یاد رکھیں، یہ صرف نمائش ہے ۔آپ کی ترامیم ابھی محفوظ نہیں کی گئیں۔'''",
+ "previewnote": "<strong>یاد رکھیں، یہ صرف نمائش ہے۔</strong>\nآپ کی ترامیم ابھی محفوظ نہیں کی گئیں!",
"continue-editing": "خانہ ترمیم میں جائیں",
"previewconflict": "اس نمائش میں خانہ ترمیم کے اوپر موجود متن جس انداز میں ظاہر ہو رہا ہے، محفوظ کرنے کے بعد اسی طرح نظر آئے گا۔",
"session_fail_preview": "معذرت! نشست کے مواد میں خامی کی وجہ سے آپ کی ترمیم مکمل نہیں ہو سکی۔\n\nشاید آپ اپنے کھاتے سے خارج ہو گئے ہیں۔ <strong>براہ کرم اس بات کی تصدیق کر لیں کہ آپ داخل ہیں اور دوبارہ کوشش کریں۔</strong> اگر آپ کو پھر بھی مشکل پیش آرہی ہو تو ایک بار [[Special:UserLogout|خارج ہو کر]] واپس داخل ہو جائیں اور اپنے براؤزر کو جانچ لیں کہ آیا وہ اس سائٹ کی کوکیز اخذ کر رہا ہے یا نہیں۔",
"recentchangeslinked-feed": "متعلقہ تبدیلیاں",
"recentchangeslinked-toolbox": "متعلقہ تبدیلیاں",
"recentchangeslinked-title": "\"$1\" سے متعلقہ تبدیلیاں",
- "recentchangeslinked-summary": "یہ ان تبدیلیوں کی فہرست ہے جو حال ہی میں کسی مخصوص صفحہ سے مربوط صفحات (یا مخصوص زمرہ کے اراکین) میں کی گئی ہیں۔\n\n[[Special:Watchlist|آپ کی زیر نظر فہرست]] میں یہ صفحات <strong>جلی</strong نظر آئیں گےـ",
+ "recentchangeslinked-summary": "یہ ان تبدیلیوں کی فہرست ہے جو حال ہی میں کسی مخصوص صفحہ سے مربوط صفحات (یا مخصوص زمرہ کے اراکین) میں کی گئی ہیں۔\n\n[[Special:Watchlist|آپ کی زیر نظر فہرست]] میں یہ صفحات <strong>جلی</strong> نظر آئیں گےـ",
"recentchangeslinked-page": "صفحہ کا نام:",
"recentchangeslinked-to": "اس کی بجائے درج کردہ صفحہ سے مربوط صفحات کی تبدیلیاں دکھائیں",
"recentchanges-page-added-to-category": "[[:$1]] کو زمرہ میں شامل کیا گیا",
"nonwrite-api-promise-error": "“Promise-Non-Write-API-Action”HTTP报头已发送,但请求是一个API编写模块。",
"internalerror": "内部错误",
"internalerror_info": "内部错误:$1",
- "internalerror-fatal-exception": "类型“$1”的致命错误",
+ "internalerror-fatal-exception": "类型“$1”的致命例外",
"filecopyerror": "无法将文件“$1”复制到“$2”。",
"filerenameerror": "无法将文件“$1”重命名为“$2”。",
"filedeleteerror": "无法删除文件“$1”。",
"resetpass-submit-loggedin": "更改密码",
"resetpass-submit-cancel": "取消",
"resetpass-wrong-oldpass": "临时密码或当前密码无效。您可能已经更改了您的密码,或者请求了新的临时密码。",
- "resetpass-recycled": "请将您的密码更改为除当前密码以外的其他密码。",
+ "resetpass-recycled": "请将您的密码更改成与当前密码不同的密码。",
"resetpass-temp-emailed": "您使用了通过电子邮件发送的临时密码登录。要完成登录,您必须在此设置一个新密码:",
"resetpass-temp-password": "临时密码:",
"resetpass-abort-generic": "密码更改已经被扩展程序中止。",
"exif-photometricinterpretation-1": "黑白(黑为0)",
"exif-photometricinterpretation-4": "透明遮罩",
"exif-photometricinterpretation-5": "分隔(可能是CMYK)",
+ "exif-photometricinterpretation-9": "CIE L*a*b*(ICC编码)",
+ "exif-photometricinterpretation-10": "CIE L*a*b*(ITU编码)",
"exif-photometricinterpretation-32803": "色彩滤镜矩阵",
+ "exif-photometricinterpretation-34892": "线性原始",
"exif-unknowndate": "未知日期",
"exif-orientation-1": "标准",
"exif-orientation-2": "水平翻转",
"right-override-export-depth": "匯出頁面包含連結內容,深度上限為 5 層",
"right-sendemail": "傳送電子郵件聯絡其他使用者",
"right-managechangetags": "建立並自資料庫 (取消) 啟用 [[Special:Tags|標籤]]",
- "right-applychangetags": "連同某個人的變更一起套用[[Special:Tags|標籤]]",
+ "right-applychangetags": "連同自己的變更一起套用[[Special:Tags|標籤]]",
"right-changetags": "加入與移除任何於各別修訂與日誌項目的[[Special:Tags|標籤]]",
"right-deletechangetags": "從資料庫刪除 [[Special:Tags|標籤]]",
"grant-generic": "\"$1\" 權限組合",
/**
* Do the actual work. All child classes will need to implement this
*
- * @return bool|null True for success, false for failure. Not returning
+ * @return bool|null|void True for success, false for failure. Not returning
* a value, or returning null, is also interpreted as success. Returning
* false for failure will cause doMaintenance.php to exit the process
* with a non-zero exit status.
* this will return an array.
*
* @param string $name The name of the param
- * @param mixed $default Anything you want, default null
+ * @param mixed|null $default Anything you want, default null
* @return mixed
*/
protected function getOption( $name, $default = null ) {
/**
* Get an argument.
* @param int $argId The integer value (from zero) for the arg
- * @param mixed $default The default if it doesn't exist
+ * @param mixed|null $default The default if it doesn't exist
* @return mixed
*/
protected function getArg( $argId = 0, $default = null ) {
/**
* Return input from stdin.
- * @param int $len The number of bytes to read. If null, just return the handle.
+ * @param int|null $len The number of bytes to read. If null, just return the handle.
* Maintenance::STDIN_ALL returns the full length
* @return mixed
*/
* Throw some output to the user. Scripts can call this with no fears,
* as we handle all --quiet stuff here
* @param string $out The text to show to the user
- * @param mixed $channel Unique identifier for the channel. See function outputChanneled.
+ * @param mixed|null $channel Unique identifier for the channel. See function outputChanneled.
*/
protected function output( $out, $channel = null ) {
// This is sometimes called very early, before Setup.php is included.
* same channel are concatenated, but any intervening messages in another
* channel start a new line.
* @param string $msg The message without trailing newline
- * @param string $channel Channel identifier or null for no
+ * @param string|null $channel Channel identifier or null for no
* channel. Channel comparison uses ===.
*/
public function outputChanneled( $msg, $channel = null ) {
* Run a child maintenance script. Pass all of the current arguments
* to it.
* @param string $maintClass A name of a child maintenance class
- * @param string $classFile Full path of where the child is
+ * @param string|null $classFile Full path of where the child is
* @return Maintenance
*/
public function runChild( $maintClass, $classFile = null ) {
* $mOptions becomes an array with keys set to the option names
* $mArgs becomes a zero-based array containing the non-option arguments
*
- * @param string $self The name of the script, if any
- * @param array $opts An array of options, in form of key=>value
- * @param array $args An array of command line arguments
+ * @param string|null $self The name of the script, if any
+ * @param array|null $opts An array of options, in form of key=>value
+ * @param array|null $args An array of command line arguments
*/
public function loadParamsAndArgs( $self = null, $opts = null, $args = null ) {
# If we were given opts or args, set those and return early
private $stubText = false; // include rev_text_id instead of text; for 2-pass dump
/**
- * @param array $args For backward compatibility
+ * @param array|null $args For backward compatibility
*/
function __construct( $args = null ) {
parent::__construct();
protected $db;
/**
- * @param array $args For backward compatibility
+ * @param array|null $args For backward compatibility
*/
function __construct( $args = null ) {
parent::__construct();
$dbw = $this->lbFactory->getMainLB()->getConnection( DB_MASTER );
$sleep = (int)$this->getOption( 'sleep', 10 );
$lastId = 0;
- $this->output( "Starting to add ct_tag_id = {$tagId} for ct_tag = {$tagName}" );
+ $this->output( "Starting to add ct_tag_id = {$tagId} for ct_tag = {$tagName}\n" );
while ( true ) {
// Given that indexes might not be there, it's better to use replica
$ids = $dbr->selectFieldValues(
);
continue;
} else {
- $this->output( "Updating ct_tag_id = {$tagId} up to row ct_id = {$lastId}" );
+ $this->output( "Updating ct_tag_id = {$tagId} up to row ct_id = {$lastId}\n" );
}
$dbw->update(
}
}
- $this->output( "Finished adding ct_tag_id = {$tagId} for ct_tag = {$tagName}" );
+ $this->output( "Finished adding ct_tag_id = {$tagId} for ct_tag = {$tagName}\n" );
}
}
$t0 = microtime( true );
- if ( $wgMultiContentRevisionSchemaMigrationStage < MIGRATION_WRITE_BOTH ) {
+ if ( ( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) === 0 ) {
$this->writeln(
- "...cannot update while \$wgMultiContentRevisionSchemaMigrationStage < MIGRATION_WRITE_BOTH"
+ '...cannot update while \$wgMultiContentRevisionSchemaMigrationStage '
+ . 'does not have the SCHEMA_COMPAT_WRITE_NEW bit set.'
);
return false;
}
use MediaWiki\Logger\LegacyLogger;
use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\IDatabase;
$optionsWithArgs = RecompressTracked::getOptionsWithArgs();
require __DIR__ . '/../commandLine.inc';
/**
* Gets a DB master connection for the given external cluster name
* @param string $cluster
- * @return Database
+ * @return IDatabase
*/
function getExtDB( $cluster ) {
$lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
},
"devDependencies": {
"deepmerge": "1.3.2",
- "eslint": "4.9.0",
- "eslint-config-wikimedia": "0.5.0",
+ "eslint": "5.0.1",
+ "eslint-config-wikimedia": "0.6.0",
"eslint-plugin-qunit": "3.2.1",
"grunt": "1.0.3",
"grunt-banana-checker": "0.6.0",
"grunt-contrib-copy": "1.0.0",
"grunt-contrib-watch": "1.1.0",
- "grunt-eslint": "20.1.0",
+ "grunt-eslint": "21.0.0",
"grunt-jsonlint": "1.1.0",
"grunt-karma": "2.0.0",
"grunt-stylelint": "0.10.0",
* The highest level, this stylesheet contains extra common styles for classes like
* .firstHeading, #contentSub, et cetera which are not outputted by MediaWiki but are common
* to skins like MonoBook, Vector, etc... Essentially this level is for styles that are
- * common to MonoBook clones. And since practically every skin that currently exists within
- * core is a MonoBook clone, all our core skins currently use this level.
+ * common to MonoBook clones.
*
* These modules are typically loaded by addModuleStyles(), which has absolutely no concept of
* dependency management. As a result they contain duplicate stylesheet references instead of
if ( ts.collationTable ) {
// Build array of key names
for ( key in ts.collationTable ) {
- // Check hasOwn to be safe
- if ( ts.collationTable.hasOwnProperty( key ) ) {
- keys.push( mw.RegExp.escape( key ) );
- }
+ keys.push( mw.RegExp.escape( key ) );
}
if ( keys.length ) {
ts.collationRegex = new RegExp( keys.join( '|' ), 'ig' );
tiff: 'tif',
ogv: 'ogg'
};
- if ( squish.hasOwnProperty( lower ) ) {
+ if ( Object.prototype.hasOwnProperty.call( squish, lower ) ) {
return squish[ lower ];
} else if ( /^[0-9a-z]+$/.test( lower ) ) {
return lower;
background-color: #eaecf0;
background-size: cover;
background-position: center center;
- padding: 1.5em;
- margin: -1.5em;
- margin-bottom: 1.5em;
+ /* Same as padding on `.oo-ui-bookletLayout-stackLayout > .oo-ui-panelLayout`,
+ * equals 20px at default font size */
+ padding: 1.42857143em;
+ margin: -1.42857143em;
+ margin-bottom: 1.42857143em;
position: relative;
}
// Copy data over from existing URI object
for ( prop in uri ) {
// Only copy direct properties, not inherited ones
- if ( uri.hasOwnProperty( prop ) ) {
+ if ( Object.prototype.hasOwnProperty.call( uri, prop ) ) {
// Deep copy object properties
if ( Array.isArray( uri[ prop ] ) || $.isPlainObject( uri[ prop ] ) ) {
this[ prop ] = $.extend( true, {}, uri[ prop ] );
function getFirstKey( obj ) {
var key;
for ( key in obj ) {
- if ( obj.hasOwnProperty( key ) ) {
- return key;
- }
+ return key;
}
}
};
for ( id in panes ) {
- if ( !panes.hasOwnProperty( id ) ) {
- continue;
- }
-
$( '<div>' )
.prop( {
className: 'mw-debug-pane',
.appendTo( $table );
for ( key in data ) {
- if ( !data.hasOwnProperty( key ) ) {
- continue;
- }
-
$( '<tr>' )
.append( $( '<th>' ).text( key ) )
.append( $( '<td>' ).text( data[ key ] ) )
Object.keys( inspect.reports );
reports.forEach( function ( name ) {
+ if ( console.group ) {
+ console.group( 'mw.inspect ' + name + ' report' );
+ } else {
+ console.log( 'mw.inspect ' + name + ' report' );
+ }
inspect.dumpTable( inspect.reports[ name ]() );
+ if ( console.group ) {
+ console.groupEnd( 'mw.inspect ' + name + ' report' );
+ }
} );
};
getAst: function ( key ) {
var wikiText;
- if ( !this.astCache.hasOwnProperty( key ) ) {
+ if ( !Object.prototype.hasOwnProperty.call( this.astCache, key ) ) {
wikiText = this.settings.messages.get( key );
if ( typeof wikiText !== 'string' ) {
wikiText = '⧼' + key + '⧽';
return oldParser.apply( this );
}
- if ( !this.map.hasOwnProperty( this.format ) ) {
+ if ( !Object.prototype.hasOwnProperty.call( this.map, this.format ) ) {
this.map[ this.format ] = mw.jqueryMsg.getMessageFunction( {
messages: this.map,
// For format 'escaped', escaping part is handled by mediawiki.js
for ( i = 0; i < arguments.length; i++ ) {
table = arguments[ i ];
for ( key in table ) {
- if ( table.hasOwnProperty( key ) ) {
- // The thousand separator should be deleted
- flipped[ table[ key ] ] = key === ',' ? '' : key;
- }
+ // The thousand separator should be deleted
+ flipped[ table[ key ] ] = key === ',' ? '' : key;
}
}
if ( transformTable ) {
convertedNumber = '';
for ( i = 0; i < numberString.length; i++ ) {
- if ( transformTable.hasOwnProperty( numberString[ i ] ) ) {
+ if ( Object.prototype.hasOwnProperty.call( transformTable, numberString[ i ] ) ) {
convertedNumber += transformTable[ numberString[ i ] ];
} else {
convertedNumber += numberString[ i ];
actionPaths = mw.config.get( 'wgActionPaths' );
for ( key in actionPaths ) {
- if ( actionPaths.hasOwnProperty( key ) ) {
- parts = actionPaths[ key ].split( '$1' );
- parts = parts.map( mw.RegExp.escape );
- m = new RegExp( parts.join( '(.+)' ) ).exec( url );
- if ( m && m[ 1 ] ) {
- return key;
- }
-
+ parts = actionPaths[ key ].split( '$1' );
+ parts = parts.map( mw.RegExp.escape );
+ m = new RegExp( parts.join( '(.+)' ) ).exec( url );
+ if ( m && m[ 1 ] ) {
+ return key;
}
}
var apiPromise,
deferred = $.Deferred();
- if ( moduleInfoCache.hasOwnProperty( module ) ) {
+ if ( Object.prototype.hasOwnProperty.call( moduleInfoCache, module ) ) {
return deferred
.resolve( moduleInfoCache[ module ] )
.promise( { abort: function () {} } );
subpages = page.getSubpages();
for ( i = 0; i < subpages.length; i++ ) {
- if ( pages.hasOwnProperty( subpages[ i ].key ) ) {
+ if ( Object.prototype.hasOwnProperty.call( pages, subpages[ i ].key ) ) {
checkPages.push( pages[ subpages[ i ].key ] );
}
}
if ( page.getSubpages ) {
subpages = page.getSubpages();
for ( j = 0; j < subpages.length; j++ ) {
- if ( !pages.hasOwnProperty( subpages[ j ].key ) ) {
+ if ( !Object.prototype.hasOwnProperty.call( pages, subpages[ j ].key ) ) {
subpages[ j ].indentLevel = page.indentLevel + 1;
pages[ subpages[ j ].key ] = new ApiSandbox.PageLayout( subpages[ j ] );
}
page.getQueryParams( params, displayParams );
subpages = page.getSubpages();
for ( i = 0; i < subpages.length; i++ ) {
- if ( pages.hasOwnProperty( subpages[ i ].key ) ) {
+ if ( Object.prototype.hasOwnProperty.call( pages, subpages[ i ].key ) ) {
checkPages.push( pages[ subpages[ i ].key ] );
}
}
// Force a 'fm' format with wrappedhtml=1, if available
if ( params.format !== undefined ) {
- if ( availableFormats.hasOwnProperty( params.format + 'fm' ) ) {
+ if ( Object.prototype.hasOwnProperty.call( availableFormats, params.format + 'fm' ) ) {
params.format = params.format + 'fm';
}
if ( params.format.substr( -2 ) === 'fm' ) {
subpages = page.getSubpages();
for ( i = 0; i < subpages.length; i++ ) {
- if ( pages.hasOwnProperty( subpages[ i ].key ) ) {
+ if ( Object.prototype.hasOwnProperty.call( pages, subpages[ i ].key ) ) {
checkPages.push( pages[ subpages[ i ].key ] );
}
}
page.apiCheckValid();
subpages = page.getSubpages();
for ( i = 0; i < subpages.length; i++ ) {
- if ( pages.hasOwnProperty( subpages[ i ].key ) ) {
+ if ( Object.prototype.hasOwnProperty.call( pages, subpages[ i ].key ) ) {
checkPages.push( pages[ subpages[ i ].key ] );
}
}
}
}
if ( params ) {
- tmp.widget.setApiValue( params.hasOwnProperty( name ) ? params[ name ] : undefined );
+ tmp.widget.setApiValue( Object.prototype.hasOwnProperty.call( params, name ) ? params[ name ] : undefined );
}
} else {
newVars = {};
buttons = [],
filterFmModules = function ( v ) {
return v.substr( -2 ) !== 'fm' ||
- !availableFormats.hasOwnProperty( v.substr( 0, v.length - 2 ) );
+ !Object.prototype.hasOwnProperty.call( availableFormats, v.substr( 0, v.length - 2 ) );
};
// This is something of a hack. We always want the 'format' and
this.loadFromQueryParams = params;
} else {
$.each( this.widgets, function ( name, widget ) {
- var v = params.hasOwnProperty( name ) ? params[ name ] : undefined;
+ var v = Object.prototype.hasOwnProperty.call( params, name ) ? params[ name ] : undefined;
widget.setApiValue( v );
} );
this.updateTemplatedParameters( params );
$( '<div>' ).addClass( 'mw-navigation-hint' )
.text( mw.msg( 'prefs-tabs-navigation-hint' ) )
.attr( 'tabIndex', 0 )
- .on( 'focus blur', function ( e ) {
- if ( e.type === 'blur' || e.type === 'focusout' ) {
- $( this ).css( 'height', '0' );
- } else {
- $( this ).css( 'height', 'auto' );
- }
- } ).prependTo( '#mw-content-text' );
+ .prependTo( '#mw-content-text' );
tabs = new OO.ui.IndexLayout( {
expanded: false,
/*
* Hide, but keep accessible for screen-readers.
*/
-.client-js .mw-navigation-hint {
- overflow: hidden;
- height: 0;
- zoom: 1;
+.client-js .mw-navigation-hint:not( :focus ) {
+ .mixin-screen-reader-text;
}
/* Override OOUI styles so that dropdowns near the bottom of the form don't get clipped,
clearTimeout( this.timeoutID );
}
// Check response cache
- if ( this.responseCache.hasOwnProperty( this.nameToCheck ) ) {
+ if ( Object.prototype.hasOwnProperty.call( this.responseCache, this.nameToCheck ) ) {
this.setWarning( this.responseCache[ this.nameToCheck ] );
return;
}
var template = $element.val(),
$spinner;
- if ( this.responseCache.hasOwnProperty( template ) ) {
+ if ( Object.prototype.hasOwnProperty.call( this.responseCache, template ) ) {
this.showPreview( this.responseCache[ template ], $previewContainer );
return;
}
* corresponding call to set().
*/
clear: function ( visibleTimeoutId ) {
- if ( activeTimeouts.hasOwnProperty( visibleTimeoutId ) ) {
+ if ( Object.prototype.hasOwnProperty.call( activeTimeouts, visibleTimeoutId ) ) {
activeTimeouts[ visibleTimeoutId ]();
}
}
var api = this.getApi(),
cache = this.constructor.static.interwikiPrefixesPromiseCache,
key = api.defaults.ajax.url;
- if ( !cache.hasOwnProperty( key ) ) {
+ if ( !Object.prototype.hasOwnProperty.call( cache, key ) ) {
cache[ key ] = api.get( {
action: 'query',
meta: 'siteinfo',
'HamcrestPHPUnitIntegration' => "$testDir/phpunit/HamcrestPHPUnitIntegration.php",
# tests/phpunit/includes
+ 'PageArchiveTestBase' => "$testDir/phpunit/includes/page/PageArchiveTestBase.php",
'RevisionDbTestBase' => "$testDir/phpunit/includes/RevisionDbTestBase.php",
'RevisionTestModifyableContent' => "$testDir/phpunit/includes/RevisionTestModifyableContent.php",
'RevisionTestModifyableContentHandler' => "$testDir/phpunit/includes/RevisionTestModifyableContentHandler.php",
'MediaWiki\Tests\Storage\McrSchemaDetection' => "$testDir/phpunit/includes/Storage/McrSchemaDetection.php",
'MediaWiki\Tests\Storage\McrSchemaOverride' => "$testDir/phpunit/includes/Storage/McrSchemaOverride.php",
'MediaWiki\Tests\Storage\McrWriteBothSchemaOverride' => "$testDir/phpunit/includes/Storage/McrWriteBothSchemaOverride.php",
+ 'MediaWiki\Tests\Storage\McrReadNewSchemaOverride' => "$testDir/phpunit/includes/Storage/McrReadNewSchemaOverride.php",
'MediaWiki\Tests\Storage\RevisionSlotsTest' => "$testDir/phpunit/includes/Storage/RevisionSlotsTest.php",
'MediaWiki\Tests\Storage\RevisionRecordTests' => "$testDir/phpunit/includes/Storage/RevisionRecordTests.php",
'MediaWiki\Tests\Storage\RevisionStoreDbTestBase' => "$testDir/phpunit/includes/Storage/RevisionStoreDbTestBase.php",
/**
* Get a Parser object
*
- * @param string $preprocessor
+ * @param string|null $preprocessor
* @return Parser
*/
function getParser( $preprocessor = null ) {
$this->dbClone = new CloneDatabase( $this->db, $this->listTables(), $prefix );
$this->dbClone->useTemporaryTables( $temporary );
$this->dbClone->cloneTableStructure();
+ CloneDatabase::changePrefix( $prefix );
if ( $dbType == 'oracle' ) {
$this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
"PhanTypeMismatchArgument",
// approximate error count: 13
"PhanTypeMismatchArgumentInternal",
- // approximate error count: 6
- "PhanTypeMismatchDeclaredParam",
- // approximate error count: 111
- "PhanTypeMismatchDeclaredParamNullable",
// approximate error count: 5
"PhanTypeMismatchDimAssignment",
// approximate error count: 2
*
* @param array|string $pairs Key to the global variable, or an array
* of key/value pairs.
- * @param mixed $value Value to set the global to (ignored
+ * @param mixed|null $value Value to set the global to (ignored
* if an array is given as first argument).
*
* @note To allow changes to global variables to take effect on global service instances,
*
* @since 1.27
*
- * @param Config $configOverrides Configuration overrides for the new MediaWikiServices instance.
+ * @param Config|null $configOverrides Configuration overrides for the new MediaWikiServices
+ * instance.
* @param callable[] $services An associative array of services to re-define. Keys are service
* names, values are callables.
*
* in which case the next two parameters are ignored; or a single string
* identifying a group, to use with the next two parameters.
* @param string|null $newKey
- * @param mixed $newValue
+ * @param mixed|null $newValue
*/
public function setGroupPermissions( $newPerms, $newKey = null, $newValue = null ) {
global $wgGroupPermissions;
* @since 1.18
*/
public function dbPrefix() {
- return $this->db->getType() == 'oracle' ? self::ORA_DB_PREFIX : self::DB_PREFIX;
+ return self::getTestPrefixFor( $this->db );
+ }
+
+ /**
+ * @param IDatabase $db
+ * @return string
+ * @since 1.32
+ */
+ public static function getTestPrefixFor( IDatabase $db ) {
+ return $db->getType() == 'oracle' ? self::ORA_DB_PREFIX : self::DB_PREFIX;
}
/**
* @since 1.25 ($namespace in 1.28)
* @param string|Title $pageName Page name or title
* @param string $text Page's content
- * @param int $namespace Namespace id (name cannot already contain namespace)
- * @param User $user If null, static::getTestSysop()->getUser() is used.
+ * @param int|null $namespace Namespace id (name cannot already contain namespace)
+ * @param User|null $user If null, static::getTestSysop()->getUser() is used.
* @return array Title object and page id
*/
protected function insertPage(
}
/**
- * Setups a database with the given prefix.
+ * Prepares the given database connection for usage in the context of usage tests.
+ * This sets up clones database tables and changes the table prefix as appropriate.
+ * If the database connection already has cloned tables, calling this method has no
+ * effect. The tables are not re-cloned or reset in that case.
+ *
+ * @param IMaintainableDatabase $db
+ */
+ protected function prepareConnectionForTesting( IMaintainableDatabase $db ) {
+ if ( !self::$dbSetup ) {
+ throw new LogicException(
+ 'Cannot use prepareConnectionForTesting()'
+ . ' if the test case is not defined to use the database!'
+ );
+ }
+
+ if ( isset( $db->_originalTablePrefix ) ) {
+ // The DB connection was already prepared for testing.
+ return;
+ }
+
+ $testPrefix = self::getTestPrefixFor( $db );
+ $oldPrefix = $db->tablePrefix();
+
+ $tablesCloned = self::listTables( $db );
+
+ if ( $oldPrefix === $testPrefix ) {
+ // The database connection already has the test prefix, but presumably not
+ // the cloned tables. This is the typical case, since the LBFactory will
+ // have the prefix set during testing, but LoadBalancers will still return
+ // connections that don't have the cloned table structure.
+ $oldPrefix = self::$oldTablePrefix;
+ }
+
+ $dbClone = new CloneDatabase( $db, $tablesCloned, $testPrefix, $oldPrefix );
+ $dbClone->useTemporaryTables( self::$useTemporaryTables );
+
+ $db->_originalTablePrefix = $oldPrefix;
+
+ if ( ( $db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) {
+ throw new LogicException( 'Cannot clone database tables' );
+ } else {
+ $dbClone->cloneTableStructure();
+ }
+ }
+
+ /**
+ * Setups a database with cloned tables using the given prefix.
*
* If reuseDB is true and certain conditions apply, it will just change the prefix.
* Otherwise, it will clone the tables and change the prefix.
*
- * Clones all tables in the given database (whatever database that connection has
- * open), to versions with the test prefix.
- *
* @param IMaintainableDatabase $db Database to use
- * @param string $prefix Prefix to use for test tables
+ * @param string|null $prefix Prefix to use for test tables. If not given, the prefix is determined
+ * automatically for $db.
* @return bool True if tables were cloned, false if only the prefix was changed
*/
- protected static function setupDatabaseWithTestPrefix( IMaintainableDatabase $db, $prefix ) {
- $tablesCloned = self::listTables( $db );
- $dbClone = new CloneDatabase( $db, $tablesCloned, $prefix );
- $dbClone->useTemporaryTables( self::$useTemporaryTables );
-
- $db->_originalTablePrefix = $db->tablePrefix();
+ protected static function setupDatabaseWithTestPrefix(
+ IMaintainableDatabase $db,
+ $prefix = null
+ ) {
+ if ( $prefix === null ) {
+ $prefix = self::getTestPrefixFor( $db );
+ }
if ( ( $db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) {
- CloneDatabase::changePrefix( $prefix );
-
+ $db->tablePrefix( $prefix );
return false;
- } else {
+ }
+
+ if ( !isset( $db->_originalTablePrefix ) ) {
+ $oldPrefix = $db->tablePrefix();
+
+ if ( $oldPrefix === $prefix ) {
+ // table already has the correct prefix, but presumably no cloned tables
+ $oldPrefix = self::$oldTablePrefix;
+ }
+
+ $db->tablePrefix( $oldPrefix );
+ $tablesCloned = self::listTables( $db );
+ $dbClone = new CloneDatabase( $db, $tablesCloned, $prefix, $oldPrefix );
+ $dbClone->useTemporaryTables( self::$useTemporaryTables );
+
$dbClone->cloneTableStructure();
- return true;
+
+ $db->tablePrefix( $prefix );
+ $db->_originalTablePrefix = $oldPrefix;
}
+
+ return true;
}
/**
if ( self::isUsingExternalStoreDB() ) {
self::setupExternalStoreTestDBs( $testPrefix );
}
+
+ // NOTE: Change the prefix in the LBFactory and $wgDBprefix, to prevent
+ // *any* database connections to operate on live data.
+ CloneDatabase::changePrefix( $testPrefix );
}
/**
/**
* Clones the External Store database(s) for testing
*
- * @param string $testPrefix Prefix for test tables
+ * @param string|null $testPrefix Prefix for test tables. Will be determined automatically
+ * if not given.
*/
- protected static function setupExternalStoreTestDBs( $testPrefix ) {
+ protected static function setupExternalStoreTestDBs( $testPrefix = null ) {
$connections = self::getExternalStoreDatabaseConnections();
foreach ( $connections as $dbw ) {
- // Hack: cloneTableStructure sets $wgDBprefix to the unit test
- // prefix,. Even though listTables now uses tablePrefix, that
- // itself is populated from $wgDBprefix by default.
-
- // We have to set it back, or we won't find the original 'blobs'
- // table to copy.
-
- $dbw->tablePrefix( self::$oldTablePrefix );
self::setupDatabaseWithTestPrefix( $dbw, $testPrefix );
}
}
return $tables;
}
+ /**
+ * Copy test data from one database connection to another.
+ *
+ * This should only be used for small data sets.
+ *
+ * @param IDatabase $source
+ * @param IDatabase $target
+ */
+ public function copyTestData( IDatabase $source, IDatabase $target ) {
+ $tables = self::listOriginalTables( $source, 'unprefixed' );
+
+ foreach ( $tables as $table ) {
+ $res = $source->select( $table, '*', [], __METHOD__ );
+ $allRows = [];
+
+ foreach ( $res as $row ) {
+ $allRows[] = (array)$row;
+ }
+
+ $target->insert( $table, $allRows, __METHOD__, [ 'IGNORE' ] );
+ }
+ }
+
/**
* @throws MWException
* @since 1.18
}
$stages = [
- MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_NEW ],
- MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_NEW ],
- MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
- MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
+ MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW ],
+ MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW,
+ MIGRATION_NEW ],
+ MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
+ MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
];
$nameKey = $key . '_text';
$actorKey = $key === 'ipb_by' ? 'ipb_by_actor' : substr( $key, 0, -5 ) . '_actor';
- foreach ( $stages as $writeStage => $readRange ) {
+ foreach ( $stages as $writeStage => $possibleReadStages ) {
if ( $key === 'ipb_by' ) {
$extraFields['ipb_address'] = __CLASS__ . "#$writeStage";
}
$callback( $id, $extraFields );
}
- for ( $readStage = $readRange[0]; $readStage <= $readRange[1]; $readStage++ ) {
+ foreach ( $possibleReadStages as $readStage ) {
$r = $this->makeMigration( $readStage );
$queryInfo = $r->getJoin( $key );
];
$stages = [
- MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_NEW ],
- MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_NEW ],
- MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
- MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
+ MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW ],
+ MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW,
+ MIGRATION_NEW ],
+ MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
+ MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
];
- foreach ( $stages as $writeStage => $readRange ) {
+ foreach ( $stages as $writeStage => $possibleReadStages ) {
if ( $key === 'ipb_reason' ) {
$extraFields['ipb_address'] = __CLASS__ . "#$writeStage";
}
$callback( $id );
}
- for ( $readStage = $readRange[0]; $readStage <= $readRange[1]; $readStage++ ) {
+ foreach ( $possibleReadStages as $readStage ) {
$rstore = $this->makeStore( $readStage );
$fieldRow = $this->db->selectRow(
];
$stages = [
- MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_NEW ],
- MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_NEW ],
- MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
- MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
+ MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW ],
+ MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW,
+ MIGRATION_NEW ],
+ MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
+ MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
];
- foreach ( $stages as $writeStage => $readRange ) {
+ foreach ( $stages as $writeStage => $possibleReadStages ) {
if ( $key === 'ipb_reason' ) {
$extraFields['ipb_address'] = __CLASS__ . "#$writeStage";
}
$callback( $id );
}
- for ( $readStage = $readRange[0]; $readStage <= $readRange[1]; $readStage++ ) {
+ foreach ( $possibleReadStages as $readStage ) {
$rstore = $this->makeStoreWithKey( $readStage, $key );
$fieldRow = $this->db->selectRow(
// @codingStandardsIgnoreStart Generic.Files.LineLength
return [
[ false, '', '', '' ],
+ [
+ [ 'useNonces' => false ],
+ "script-src 'unsafe-eval' 'self' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&",
+ "script-src 'unsafe-eval' 'self' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1&",
+ "script-src 'unsafe-eval' 'self' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'"
+ ],
[
true,
"script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&",
/**
* @dataProvider providerCSPIsEnabled
- * @covers ContentSecurityPolicy::isEnabled
+ * @covers ContentSecurityPolicy::isNonceRequired
*/
public function testCSPIsEnabled( $main, $reportOnly, $expected ) {
global $wgCSPReportOnlyHeader, $wgCSPHeader;
global $wgCSPHeader;
$oldReport = wfSetVar( $wgCSPReportOnlyHeader, $reportOnly );
$oldMain = wfSetVar( $wgCSPHeader, $main );
- $res = ContentSecurityPolicy::isEnabled( RequestContext::getMain()->getConfig() );
+ $res = ContentSecurityPolicy::isNonceRequired( RequestContext::getMain()->getConfig() );
wfSetVar( $wgCSPReportOnlyHeader, $oldReport );
wfSetVar( $wgCSPHeader, $oldMain );
$this->assertEquals( $res, $expected );
[ false, [], true ],
[ [], false, true ],
[ [ 'default-src' => [ 'foo.example.com' ] ], false, true ],
+ [ [ 'useNonces' => false ], [ 'useNonces' => false ], false ],
+ [ [ 'useNonces' => true ], [ 'useNonces' => false ], true ],
+ [ [ 'useNonces' => false ], [ 'useNonces' => true ], true ],
];
}
}
use MediaWiki\Storage\RevisionFactory;
use MediaWiki\Storage\RevisionLookup;
use MediaWiki\Storage\RevisionStore;
-use MediaWiki\Storage\RevisionStoreFactory;
use MediaWiki\Storage\SqlBlobStore;
/**
'BlobStore' => [ 'BlobStore', BlobStore::class ],
'_SqlBlobStore' => [ '_SqlBlobStore', SqlBlobStore::class ],
'RevisionStore' => [ 'RevisionStore', RevisionStore::class ],
- 'RevisionStoreFactory' => [ 'RevisionStoreFactory', RevisionStoreFactory::class ],
'RevisionLookup' => [ 'RevisionLookup', RevisionLookup::class ],
'RevisionFactory' => [ 'RevisionFactory', RevisionFactory::class ],
'ContentModelStore' => [ 'ContentModelStore', NameTableStore::class ],
+++ /dev/null
-<?php
-
-/**
- * Test class for page archiving.
- *
- * @group ContentHandler
- * @group Database
- * ^--- important, causes temporary tables to be used instead of the real database
- *
- * @group medium
- * ^--- important, causes tests not to fail with timeout
- */
-class PageArchiveTest extends MediaWikiTestCase {
-
- /**
- * @var PageArchive $archivedPage
- */
- private $archivedPage;
-
- /**
- * A logged out user who edited the page before it was archived.
- * @var string $ipEditor
- */
- private $ipEditor;
-
- /**
- * Revision ID of the IP edit
- * @var int $ipRevId
- */
- private $ipRevId;
-
- function __construct( $name = null, array $data = [], $dataName = '' ) {
- parent::__construct( $name, $data, $dataName );
-
- $this->tablesUsed = array_merge(
- $this->tablesUsed,
- [
- 'page',
- 'revision',
- 'ip_changes',
- 'text',
- 'archive',
- 'recentchanges',
- 'logging',
- 'page_props',
- ]
- );
- }
-
- protected function setUp() {
- parent::setUp();
-
- $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
- $this->overrideMwServices();
-
- // First create our dummy page
- $page = Title::newFromText( 'PageArchiveTest_thePage' );
- $page = new WikiPage( $page );
- $content = ContentHandler::makeContent(
- 'testing',
- $page->getTitle(),
- CONTENT_MODEL_WIKITEXT
- );
- $page->doEditContent( $content, 'testing', EDIT_NEW );
-
- // Insert IP revision
- $this->ipEditor = '2600:387:ed7:947e:8c16:a1ad:dd34:1dd7';
- $rev = new Revision( [
- 'text' => 'Lorem Ipsum',
- 'comment' => 'just a test',
- 'page' => $page->getId(),
- 'user_text' => $this->ipEditor,
- ] );
- $dbw = wfGetDB( DB_MASTER );
- $this->ipRevId = $rev->insertOn( $dbw );
-
- // Delete the page
- $page->doDeleteArticleReal( 'Just a test deletion' );
-
- $this->archivedPage = new PageArchive( $page->getTitle() );
- }
-
- /**
- * @covers PageArchive::undelete
- * @covers PageArchive::undeleteRevisions
- */
- public function testUndeleteRevisions() {
- // First make sure old revisions are archived
- $dbr = wfGetDB( DB_REPLICA );
- $arQuery = Revision::getArchiveQueryInfo();
- $res = $dbr->select(
- $arQuery['tables'],
- $arQuery['fields'],
- [ 'ar_rev_id' => $this->ipRevId ],
- __METHOD__,
- [],
- $arQuery['joins']
- );
- $row = $res->fetchObject();
- $this->assertEquals( $this->ipEditor, $row->ar_user_text );
-
- // Should not be in revision
- $res = $dbr->select( 'revision', '1', [ 'rev_id' => $this->ipRevId ] );
- $this->assertFalse( $res->fetchObject() );
-
- // Should not be in ip_changes
- $res = $dbr->select( 'ip_changes', '1', [ 'ipc_rev_id' => $this->ipRevId ] );
- $this->assertFalse( $res->fetchObject() );
-
- // Restore the page
- $this->archivedPage->undelete( [] );
-
- // Should be back in revision
- $revQuery = Revision::getQueryInfo();
- $res = $dbr->select(
- $revQuery['tables'],
- $revQuery['fields'],
- [ 'rev_id' => $this->ipRevId ],
- __METHOD__,
- [],
- $revQuery['joins']
- );
- $row = $res->fetchObject();
- $this->assertEquals( $this->ipEditor, $row->rev_user_text );
-
- // Should be back in ip_changes
- $res = $dbr->select( 'ip_changes', [ 'ipc_hex' ], [ 'ipc_rev_id' => $this->ipRevId ] );
- $row = $res->fetchObject();
- $this->assertEquals( IP::toHex( $this->ipEditor ), $row->ipc_hex );
- }
-
- /**
- * @covers PageArchive::listRevisions
- */
- public function testListRevisions() {
- $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
- $this->overrideMwServices();
-
- $revisions = $this->archivedPage->listRevisions();
- $this->assertEquals( 2, $revisions->numRows() );
-
- // Get the rows as arrays
- $row1 = (array)$revisions->current();
- $row2 = (array)$revisions->next();
- // Unset the timestamps (we assume they will be right...
- $this->assertInternalType( 'string', $row1['ar_timestamp'] );
- $this->assertInternalType( 'string', $row2['ar_timestamp'] );
- unset( $row1['ar_timestamp'] );
- unset( $row2['ar_timestamp'] );
-
- $this->assertEquals(
- [
- 'ar_minor_edit' => '0',
- 'ar_user' => '0',
- 'ar_user_text' => '2600:387:ed7:947e:8c16:a1ad:dd34:1dd7',
- 'ar_actor' => null,
- 'ar_len' => '11',
- 'ar_deleted' => '0',
- 'ar_rev_id' => '3',
- 'ar_sha1' => '0qdrpxl537ivfnx4gcpnzz0285yxryy',
- 'ar_page_id' => '2',
- 'ar_comment_text' => 'just a test',
- 'ar_comment_data' => null,
- 'ar_comment_cid' => null,
- 'ar_content_format' => null,
- 'ar_content_model' => null,
- 'ts_tags' => null,
- 'ar_id' => '2',
- 'ar_namespace' => '0',
- 'ar_title' => 'PageArchiveTest_thePage',
- 'ar_text_id' => '3',
- 'ar_parent_id' => '2',
- ],
- $row1
- );
- $this->assertEquals(
- [
- 'ar_minor_edit' => '0',
- 'ar_user' => '0',
- 'ar_user_text' => '127.0.0.1',
- 'ar_actor' => null,
- 'ar_len' => '7',
- 'ar_deleted' => '0',
- 'ar_rev_id' => '2',
- 'ar_sha1' => 'pr0s8e18148pxhgjfa0gjrvpy8fiyxc',
- 'ar_page_id' => '2',
- 'ar_comment_text' => 'testing',
- 'ar_comment_data' => null,
- 'ar_comment_cid' => null,
- 'ar_content_format' => null,
- 'ar_content_model' => null,
- 'ts_tags' => null,
- 'ar_id' => '1',
- 'ar_namespace' => '0',
- 'ar_title' => 'PageArchiveTest_thePage',
- 'ar_text_id' => '2',
- 'ar_parent_id' => '0',
- ],
- $row2
- );
- }
-
- /**
- * @covers PageArchive::listPagesBySearch
- */
- public function testListPagesBySearch() {
- $pages = PageArchive::listPagesBySearch( 'PageArchiveTest_thePage' );
- $this->assertSame( 1, $pages->numRows() );
-
- $page = (array)$pages->current();
-
- $this->assertSame(
- [
- 'ar_namespace' => '0',
- 'ar_title' => 'PageArchiveTest_thePage',
- 'count' => '2',
- ],
- $page
- );
- }
-
- /**
- * @covers PageArchive::listPagesBySearch
- */
- public function testListPagesByPrefix() {
- $pages = PageArchive::listPagesByPrefix( 'PageArchiveTest' );
- $this->assertSame( 1, $pages->numRows() );
-
- $page = (array)$pages->current();
-
- $this->assertSame(
- [
- 'ar_namespace' => '0',
- 'ar_title' => 'PageArchiveTest_thePage',
- 'count' => '2',
- ],
- $page
- );
- }
-
- /**
- * @covers PageArchive::getTextFromRow
- */
- public function testGetTextFromRow() {
- $row = (object)[ 'ar_text_id' => 2 ];
- $text = $this->archivedPage->getTextFromRow( $row );
- $this->assertSame( 'testing', $text );
- }
-
- /**
- * @covers PageArchive::getLastRevisionText
- */
- public function testGetLastRevisionText() {
- $text = $this->archivedPage->getLastRevisionText();
- $this->assertSame( 'Lorem Ipsum', $text );
- }
-
- /**
- * @covers PageArchive::isDeleted
- */
- public function testIsDeleted() {
- $this->assertTrue( $this->archivedPage->isDeleted() );
- }
-}
$rev = $this->testPage->getRevision();
// Clear any previous cache for the revision during creation
- $key = $cache->makeGlobalKey( RevisionStore::ROW_CACHE_KEY,
+ $key = $cache->makeGlobalKey(
+ RevisionStore::ROW_CACHE_KEY,
$db->getDomainID(),
$rev->getPage(),
$rev->getId()
--- /dev/null
+<?php
+use MediaWiki\Tests\Storage\McrReadNewSchemaOverride;
+
+/**
+ * Tests Revision against the intermediate MCR DB schema for use during schema migration.
+ *
+ * @covers Revision
+ *
+ * @group Revision
+ * @group Storage
+ * @group ContentHandler
+ * @group Database
+ * @group medium
+ */
+class RevisionMcrReadNewDbTest extends RevisionDbTestBase {
+
+ use McrReadNewSchemaOverride;
+
+ protected function getContentHandlerUseDB() {
+ return true;
+ }
+
+}
)
);
- $cacheKey = $cache->makeKey( 'revisiontext', 'textid', 'tt:7777' );
+ $cacheKey = $cache->makeGlobalKey(
+ 'BlobStore',
+ 'address',
+ $lb->getLocalDomainID(),
+ 'tt:7777'
+ );
$this->assertSame( 'AAAABBAAA', $cache->get( $cacheKey ) );
}
--- /dev/null
+<?php
+namespace MediaWiki\Tests\Storage;
+
+use CommentStoreComment;
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Storage\SlotRecord;
+use TextContent;
+use Title;
+use WikitextContent;
+
+/**
+ * Tests RevisionStore against the intermediate MCR DB schema for use during schema migration.
+ *
+ * @covers \MediaWiki\Storage\RevisionStore
+ *
+ * @group RevisionStore
+ * @group Storage
+ * @group Database
+ * @group medium
+ */
+class McrReadNewRevisionStoreDbTest extends RevisionStoreDbTestBase {
+
+ use McrReadNewSchemaOverride;
+
+ protected function assertRevisionExistsInDatabase( RevisionRecord $rev ) {
+ $numberOfSlots = count( $rev->getSlotRoles() );
+
+ // new schema is written
+ $this->assertSelect(
+ 'slots',
+ [ 'count(*)' ],
+ [ 'slot_revision_id' => $rev->getId() ],
+ [ [ (string)$numberOfSlots ] ]
+ );
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $revQuery = $store->getSlotsQueryInfo( [ 'content' ] );
+
+ $this->assertSelect(
+ $revQuery['tables'],
+ [ 'count(*)' ],
+ [
+ 'slot_revision_id' => $rev->getId(),
+ ],
+ [ [ (string)$numberOfSlots ] ],
+ [],
+ $revQuery['joins']
+ );
+
+ // Legacy schema is still being written
+ $this->assertSelect(
+ [ 'revision', 'text' ],
+ [ 'count(*)' ],
+ [ 'rev_id' => $rev->getId(), 'rev_text_id > 0' ],
+ [ [ 1 ] ],
+ [],
+ [ 'text' => [ 'INNER JOIN', [ 'rev_text_id = old_id' ] ] ]
+ );
+
+ parent::assertRevisionExistsInDatabase( $rev );
+ }
+
+ /**
+ * @param SlotRecord $a
+ * @param SlotRecord $b
+ */
+ protected function assertSameSlotContent( SlotRecord $a, SlotRecord $b ) {
+ parent::assertSameSlotContent( $a, $b );
+
+ // Assert that the same content ID has been used
+ $this->assertSame( $a->getContentId(), $b->getContentId() );
+ }
+
+ public function provideInsertRevisionOn_successes() {
+ foreach ( parent::provideInsertRevisionOn_successes() as $case ) {
+ yield $case;
+ }
+
+ yield 'Multi-slot revision insertion' => [
+ [
+ 'content' => [
+ 'main' => new WikitextContent( 'Chicken' ),
+ 'aux' => new TextContent( 'Egg' ),
+ ],
+ 'page' => true,
+ 'comment' => $this->getRandomCommentStoreComment(),
+ 'timestamp' => '20171117010101',
+ 'user' => true,
+ ],
+ ];
+ }
+
+ public function provideNewNullRevision() {
+ foreach ( parent::provideNewNullRevision() as $case ) {
+ yield $case;
+ }
+
+ yield [
+ Title::newFromText( 'UTPage_notAutoCreated' ),
+ [
+ 'content' => [
+ 'main' => new WikitextContent( 'Chicken' ),
+ 'aux' => new WikitextContent( 'Omelet' ),
+ ],
+ ],
+ CommentStoreComment::newUnsavedComment( __METHOD__ . ' comment multi' ),
+ ];
+ }
+
+ public function testGetQueryInfo_NoSlotDataJoin() {
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $queryInfo = $store->getQueryInfo();
+
+ // with the new schema enabled, query info should not join the main slot info
+ $this->assertFalse( array_key_exists( 'a_slot_data', $queryInfo['tables'] ) );
+ $this->assertFalse( array_key_exists( 'a_slot_data', $queryInfo['joins'] ) );
+ }
+
+ public function provideGetArchiveQueryInfo() {
+ yield [
+ [
+ 'tables' => [
+ 'archive',
+ ],
+ 'fields' => array_merge(
+ $this->getDefaultArchiveFields( false ),
+ [
+ 'ar_comment_text' => 'ar_comment',
+ 'ar_comment_data' => 'NULL',
+ 'ar_comment_cid' => 'NULL',
+ 'ar_user_text' => 'ar_user_text',
+ 'ar_user' => 'ar_user',
+ 'ar_actor' => 'NULL',
+ ]
+ ),
+ 'joins' => [
+ ],
+ ]
+ ];
+ }
+
+ public function provideGetQueryInfo() {
+ // TODO: more option variations
+ yield [
+ [ 'page', 'user' ],
+ [
+ 'tables' => [
+ 'revision',
+ 'page',
+ 'user',
+ ],
+ 'fields' => array_merge(
+ $this->getDefaultQueryFields( false ),
+ $this->getCommentQueryFields(),
+ $this->getActorQueryFields(),
+ [
+ 'page_namespace',
+ 'page_title',
+ 'page_id',
+ 'page_latest',
+ 'page_is_redirect',
+ 'page_len',
+ 'user_name',
+ ]
+ ),
+ 'joins' => [
+ 'page' => [ 'INNER JOIN', [ 'page_id = rev_page' ] ],
+ 'user' => [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ],
+ ],
+ ]
+ ];
+ }
+
+ public function provideGetSlotsQueryInfo() {
+ yield [
+ [],
+ [
+ 'tables' => [
+ 'slots',
+ 'slot_roles',
+ ],
+ 'fields' => array_merge(
+ [
+ 'slot_revision_id',
+ 'slot_content_id',
+ 'slot_origin',
+ 'role_name',
+ ]
+ ),
+ 'joins' => [
+ 'slot_roles' => [ 'INNER JOIN', [ 'slot_role_id = role_id' ] ],
+ ],
+ ]
+ ];
+ yield [
+ [ 'content' ],
+ [
+ 'tables' => [
+ 'slots',
+ 'slot_roles',
+ 'content',
+ 'content_models',
+ ],
+ 'fields' => array_merge(
+ [
+ 'slot_revision_id',
+ 'slot_content_id',
+ 'slot_origin',
+ 'role_name',
+ 'content_size',
+ 'content_sha1',
+ 'content_address',
+ 'model_name',
+ ]
+ ),
+ 'joins' => [
+ 'slot_roles' => [ 'INNER JOIN', [ 'slot_role_id = role_id' ] ],
+ 'content' => [ 'INNER JOIN', [ 'slot_content_id = content_id' ] ],
+ 'content_models' => [ 'INNER JOIN', [ 'content_model = model_id' ] ],
+ ],
+ ]
+ ];
+ }
+
+ public function provideNewMutableRevisionFromArray() {
+ foreach ( parent::provideNewMutableRevisionFromArray() as $case ) {
+ yield $case;
+ }
+
+ yield 'Basic array, multiple roles' => [
+ [
+ 'id' => 2,
+ 'page' => 1,
+ 'timestamp' => '20171017114835',
+ 'user_text' => '111.0.1.2',
+ 'user' => 0,
+ 'minor_edit' => false,
+ 'deleted' => 0,
+ 'len' => 29,
+ 'parent_id' => 1,
+ 'sha1' => '89qs83keq9c9ccw9olvvm4oc9oq50ii',
+ 'comment' => 'Goat Comment!',
+ 'content' => [
+ 'main' => new WikitextContent( 'Söme Cöntent' ),
+ 'aux' => new TextContent( 'Öther Cöntent' ),
+ ]
+ ]
+ ];
+ }
+
+}
--- /dev/null
+<?php
+namespace MediaWiki\Tests\Storage;
+
+use Wikimedia\Rdbms\IMaintainableDatabase;
+use MediaWiki\DB\PatchFileLocation;
+
+/**
+ * Trait providing schema overrides that allow tests to run against the intermediate MCR database
+ * schema for use during schema migration.
+ */
+trait McrReadNewSchemaOverride {
+
+ use PatchFileLocation;
+ use McrSchemaDetection;
+
+ /**
+ * @return int
+ */
+ protected function getMcrMigrationStage() {
+ return SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW;
+ }
+
+ /**
+ * @return string[]
+ */
+ protected function getMcrTablesToReset() {
+ return [ 'content', 'content_models', 'slots', 'slot_roles' ];
+ }
+
+ /**
+ * @override MediaWikiTestCase::getSchemaOverrides
+ * @return array[]
+ */
+ protected function getSchemaOverrides( IMaintainableDatabase $db ) {
+ $overrides = [
+ 'scripts' => [],
+ 'drop' => [],
+ 'create' => [],
+ 'alter' => [],
+ ];
+
+ if ( !$this->hasMcrTables( $db ) ) {
+ $overrides['create'] = [ 'slots', 'content', 'slot_roles', 'content_models', ];
+ $overrides['scripts'][] = $this->getSqlPatchPath( $db, 'patch-slot_roles' );
+ $overrides['scripts'][] = $this->getSqlPatchPath( $db, 'patch-content_models' );
+ $overrides['scripts'][] = $this->getSqlPatchPath( $db, 'patch-content' );
+ $overrides['scripts'][] = $this->getSqlPatchPath( $db, 'patch-slots' );
+ }
+
+ if ( !$this->hasPreMcrFields( $db ) ) {
+ $overrides['alter'][] = 'revision';
+ $overrides['scripts'][] = $this->getSqlPatchPath( $db, 'create-pre-mcr-fields', __DIR__ );
+ }
+
+ return $overrides;
+ }
+
+}
protected function assertRevisionExistsInDatabase( RevisionRecord $rev ) {
$numberOfSlots = count( $rev->getSlotRoles() );
+ // new schema is written
$this->assertSelect(
'slots',
[ 'count(*)' ],
$revQuery['joins']
);
- $this->assertSelect(
- 'content',
- [ 'count(*)' ],
- [ 'content_address' => $rev->getSlot( 'main' )->getAddress() ],
- [ [ 1 ] ]
- );
-
parent::assertRevisionExistsInDatabase( $rev );
}
}
protected function assertRevisionExistsInDatabase( RevisionRecord $rev ) {
+ // New schema is being written
$this->assertSelect(
'slots',
[ 'count(*)' ],
[ [ '1' ] ]
);
+ // Legacy schema is still being written
+ $this->assertSelect(
+ [ 'revision', 'text' ],
+ [ 'count(*)' ],
+ [ 'rev_id' => $rev->getId(), 'rev_text_id > 0' ],
+ [ [ 1 ] ],
+ [],
+ [ 'text' => [ 'INNER JOIN', [ 'rev_text_id = old_id' ] ] ]
+ );
+
parent::assertRevisionExistsInDatabase( $rev );
}
'role_name' => $db->addQuotes( 'main' ),
'content_size' => 'slots.rev_len',
'content_sha1' => 'slots.rev_sha1',
- 'content_address' =>
- 'CONCAT(' . $db->addQuotes( 'tt:' ) . ',slots.rev_text_id)',
+ 'content_address' => $db->buildConcat( [
+ $db->addQuotes( 'tt:' ), 'slots.rev_text_id' ] ),
'model_name' => 'slots.rev_content_model',
]
),
* @return int
*/
protected function getMcrMigrationStage() {
- return MIGRATION_WRITE_BOTH;
+ return SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD;
}
/**
'content_size' => 'slots.rev_len',
'content_sha1' => 'slots.rev_sha1',
'content_address' =>
- 'CONCAT(' . $db->addQuotes( 'tt:' ) . ',slots.rev_text_id)',
+ $db->buildConcat( [ $db->addQuotes( 'tt:' ), 'slots.rev_text_id' ] ),
'model_name' => 'NULL',
]
),
namespace MediaWiki\Tests\Storage;
use InvalidArgumentException;
+use MediaWiki\Storage\RevisionRecord;
use Revision;
use WikitextContent;
return $row;
}
+ protected function assertRevisionExistsInDatabase( RevisionRecord $rev ) {
+ // Legacy schema is still being written
+ $this->assertSelect(
+ [ 'revision', 'text' ],
+ [ 'count(*)' ],
+ [ 'rev_id' => $rev->getId(), 'rev_text_id > 0' ],
+ [ [ 1 ] ],
+ [],
+ [ 'text' => [ 'INNER JOIN', [ 'rev_text_id = old_id' ] ] ]
+ );
+
+ parent::assertRevisionExistsInDatabase( $rev );
+ }
+
public function provideGetArchiveQueryInfo() {
yield [
[
'content_size' => 'slots.rev_len',
'content_sha1' => 'slots.rev_sha1',
'content_address' =>
- 'CONCAT(' . $db->addQuotes( 'tt:' ) . ',slots.rev_text_id)',
+ $db->buildConcat( [ $db->addQuotes( 'tt:' ), 'slots.rev_text_id' ] ),
'model_name' => 'slots.rev_content_model',
]
),
+++ /dev/null
-<?php
-
-namespace MediaWiki\Tests\Storage;
-
-use ActorMigration;
-use CommentStore;
-use MediaWiki\Logger\LoggerFactory;
-use MediaWiki\Storage\NameTableStore;
-use MediaWiki\Storage\RevisionStore;
-use MediaWiki\Storage\RevisionStoreFactory;
-use MediaWiki\Storage\SqlBlobStore;
-use MediaWikiTestCase;
-use WANObjectCache;
-use Wikimedia\Rdbms\LoadBalancer;
-use Wikimedia\TestingAccessWrapper;
-
-class RevisionStoreFactoryTest extends MediaWikiTestCase {
-
- public function testValidConstruction_doesntCauseErrors() {
- new RevisionStoreFactory(
- $this->getMockLoadBalancer(),
- $this->getMockSqlBlobStore(),
- $this->getHashWANObjectCache(),
- $this->getMockCommentStore(),
- $this->getMockNameTableStore(),
- $this->getMockNameTableStore(),
- MIGRATION_OLD,
- ActorMigration::newMigration(),
- LoggerFactory::getInstance( 'someInstance' ),
- true
- );
- $this->assertTrue( true );
- }
-
- public function provideWikiIds() {
- yield [ true ];
- yield [ false ];
- yield [ 'somewiki' ];
- yield [ 'somewiki', MIGRATION_OLD , false ];
- yield [ 'somewiki', MIGRATION_NEW , true ];
- }
-
- /**
- * @dataProvider provideWikiIds
- */
- public function testGetRevisionStore(
- $wikiId,
- $mcrMigrationStage = MIGRATION_OLD,
- $contentHandlerUseDb = true
- ) {
- $lb = $this->getMockLoadBalancer();
- $blobStore = $this->getMockSqlBlobStore();
- $cache = $this->getHashWANObjectCache();
- $commentStore = $this->getMockCommentStore();
- $contentModelStore = $this->getMockNameTableStore();
- $slotRoleStore = $this->getMockNameTableStore();
- $actorMigration = ActorMigration::newMigration();
- $logger = LoggerFactory::getInstance( 'someInstance' );
-
- $factory = new RevisionStoreFactory(
- $lb,
- $blobStore,
- $cache,
- $commentStore,
- $contentModelStore,
- $slotRoleStore,
- $mcrMigrationStage,
- $actorMigration,
- $logger,
- $contentHandlerUseDb
- );
-
- $store = $factory->getRevisionStore( $wikiId );
- $wrapper = TestingAccessWrapper::newFromObject( $store );
-
- // ensure the correct object type is returned
- $this->assertInstanceOf( RevisionStore::class, $store );
-
- // ensure the RevisionStore is for the given wikiId
- $this->assertSame( $wikiId, $wrapper->wikiId );
-
- // ensure all other required services are correctly set
- $this->assertSame( $lb, $wrapper->loadBalancer );
- $this->assertSame( $blobStore, $wrapper->blobStore );
- $this->assertSame( $cache, $wrapper->cache );
- $this->assertSame( $commentStore, $wrapper->commentStore );
- $this->assertSame( $contentModelStore, $wrapper->contentModelStore );
- $this->assertSame( $slotRoleStore, $wrapper->slotRoleStore );
- $this->assertSame( $mcrMigrationStage, $wrapper->mcrMigrationStage );
- $this->assertSame( $actorMigration, $wrapper->actorMigration );
- $this->assertSame( $logger, $wrapper->logger );
- $this->assertSame( $contentHandlerUseDb, $store->getContentHandlerUseDB() );
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|NameTableStore
- */
- private function getMockNameTableStore() {
- return $this->getMockBuilder( NameTableStore::class )
- ->disableOriginalConstructor()->getMock();
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|LoadBalancer
- */
- private function getMockLoadBalancer() {
- return $this->getMockBuilder( LoadBalancer::class )
- ->disableOriginalConstructor()->getMock();
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|SqlBlobStore
- */
- private function getMockSqlBlobStore() {
- return $this->getMockBuilder( SqlBlobStore::class )
- ->disableOriginalConstructor()->getMock();
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|CommentStore
- */
- private function getMockCommentStore() {
- return $this->getMockBuilder( CommentStore::class )
- ->disableOriginalConstructor()->getMock();
- }
-
- private function getHashWANObjectCache() {
- return new WANObjectCache( [ 'cache' => new \HashBagOStuff() ] );
- }
-
-}
public function provideSetContentHandlerUseDB() {
return [
- // ContentHandlerUseDB can be true of false pre migration
- [ false, MIGRATION_OLD, false ],
- [ true, MIGRATION_OLD, false ],
- // During migration it can not be false
- [ false, MIGRATION_WRITE_BOTH, true ],
- // But it can be true
- [ true, MIGRATION_WRITE_BOTH, false ],
+ // ContentHandlerUseDB can be true of false pre migration.
+ [ false, SCHEMA_COMPAT_OLD, false ],
+ [ true, SCHEMA_COMPAT_OLD, false ],
+ // During and after migration it can not be false...
+ [ false, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, true ],
+ [ false, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, true ],
+ [ false, SCHEMA_COMPAT_NEW, true ],
+ // ...but it can be true.
+ [ true, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, false ],
+ [ true, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, false ],
+ [ true, SCHEMA_COMPAT_NEW, false ],
];
}
public function provideMigrationConstruction() {
return [
- [ MIGRATION_OLD, false ],
- [ MIGRATION_WRITE_BOTH, false ],
+ [ SCHEMA_COMPAT_OLD, false ],
+ [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, false ],
+ [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, false ],
+ [ SCHEMA_COMPAT_NEW, false ],
+ [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_BOTH, true ],
+ [ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_BOTH, true ],
+ [ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_BOTH, true ],
];
}
/**
* @param bool $collect Whether to collect logs. @see setCollect()
- * @param callable $filter Filter logs before collecting/printing. Signature is
+ * @param callable|null $filter Filter logs before collecting/printing. Signature is
* string|null function ( string $message, string $level, array $context );
* @param bool $collectContext Whether to keep the context passed to log
* (since 1.29, @see setCollectContext()).
*
* @param int $number Number of filenames to write
* @param string $format Optional, must be understood by ImageMagick, such as 'jpg' or 'gif'
- * @param string $dir Directory, optional (will default to current working directory)
+ * @param string|null $dir Directory, optional (will default to current working directory)
* @return array Filenames we just wrote
*/
function writeImages( $number, $format = 'jpg', $dir = null ) {
* Checks that the request's result matches the expected results.
* Assumes no rawcontinue and a complete batch.
* @param array $values Array is a two element array( request, expected_results )
- * @param array $session
+ * @param array|null $session
* @param bool $appendModule
- * @param User $user
+ * @param User|null $user
*/
protected function check( $values, array $session = null,
$appendModule = false, User $user = null
];
}
+ /**
+ * @covers LBFactory::getLocalDomainID()
+ * @covers LBFactory::resolveDomainID()
+ */
public function testLBFactorySimpleServer() {
global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
$dbr = $lb->getConnection( DB_REPLICA );
$this->assertTrue( $dbr->getLBInfo( 'master' ), 'DB_REPLICA also gets the master' );
+ $this->assertSame( 'my_test_wiki', $factory->resolveDomainID( 'my_test_wiki' ) );
+ $this->assertSame( $factory->getLocalDomainID(), $factory->resolveDomainID( false ) );
+
$factory->shutdown();
$lb->closeAll();
}
];
}
+ /**
+ * @covers LoadBalancer::getLocalDomainID()
+ * @covers LoadBalancer::resolveDomainID()
+ */
public function testWithoutReplica() {
global $wgDBname;
$ld = DatabaseDomain::newFromId( $lb->getLocalDomainID() );
$this->assertEquals( $wgDBname, $ld->getDatabase(), 'local domain DB set' );
$this->assertEquals( $this->dbPrefix(), $ld->getTablePrefix(), 'local domain prefix set' );
+ $this->assertSame( 'my_test_wiki', $lb->resolveDomainID( 'my_test_wiki' ) );
+ $this->assertSame( $ld->getId(), $lb->resolveDomainID( false ) );
+ $this->assertSame( $ld->getId(), $lb->resolveDomainID( $ld ) );
$this->assertFalse( $called );
$dbw = $lb->getConnection( DB_MASTER );
$this->assertContains( ' autocomplete="off"', $form->wrapForm( '' ) );
}
+ public function testGetPreText() {
+ $preText = 'TEST';
+ $form = $this->newInstance();
+ $form->setPreText( $preText );
+ $this->assertSame( $preText, $form->getPreText() );
+ }
+
}
*/
class DBConnRefTest extends PHPUnit\Framework\TestCase {
+ use MediaWikiCoversValidator;
use PHPUnit4And6Compat;
/**
new DBConnRef( $lb, 17 ); // bad constructor argument
}
- public function testGetWikiID() {
- $lb = $this->getMock( ILoadBalancer::class );
-
- // getWikiID is optimized to not create a connection
- $lb->expects( $this->never() )
- ->method( 'getConnection' );
-
- $ref = new DBConnRef( $lb, [ DB_REPLICA, [], 'dummy', 0 ] );
-
- $this->assertSame( 'dummy', $ref->getWikiID() );
- }
-
+ /**
+ * @covers Wikimedia\Rdbms\DBConnRef::getDomainId
+ */
public function testGetDomainID() {
$lb = $this->getMock( ILoadBalancer::class );
*
* File must be in the path returned by getFilePath()
* @param string $name File name
- * @param string $type MIME type [optional]
+ * @param string|null $type MIME type [optional]
* @return UnregisteredLocalFile
*/
protected function dataFile( $name, $type = null ) {
--- /dev/null
+<?php
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Tests\Storage\McrSchemaOverride;
+
+/**
+ * Test class for page archiving, using the new MCR schema.
+ *
+ * @group ContentHandler
+ * @group Database
+ * ^--- important, causes temporary tables to be used instead of the real database
+ *
+ * @group medium
+ * ^--- important, causes tests not to fail with timeout
+ */
+class PageArchiveMcrTest extends PageArchiveTestBase {
+
+ use McrSchemaOverride;
+
+ /**
+ * @covers PageArchive::listRevisions
+ */
+ public function testListRevisions_slots() {
+ $revisions = $this->archivedPage->listRevisions();
+
+ $revisionStore = MediaWikiServices::getInstance()->getInstance()->getRevisionStore();
+ $slotsQuery = $revisionStore->getSlotsQueryInfo( [ 'content' ] );
+
+ foreach ( $revisions as $row ) {
+ $this->assertSelect(
+ $slotsQuery['tables'],
+ 'count(*)',
+ [ 'slot_revision_id' => $row->ar_rev_id ],
+ [ [ 1 ] ],
+ [],
+ $slotsQuery['joins']
+ );
+ }
+ }
+
+ protected function getExpectedArchiveRows() {
+ return [
+ [
+ 'ar_minor_edit' => '0',
+ 'ar_user' => '0',
+ 'ar_user_text' => $this->ipEditor,
+ 'ar_actor' => null,
+ 'ar_len' => '11',
+ 'ar_deleted' => '0',
+ 'ar_rev_id' => strval( $this->ipRev->getId() ),
+ 'ar_timestamp' => $this->db->timestamp( $this->ipRev->getTimestamp() ),
+ 'ar_sha1' => '0qdrpxl537ivfnx4gcpnzz0285yxryy',
+ 'ar_page_id' => strval( $this->ipRev->getPageId() ),
+ 'ar_comment_text' => 'just a test',
+ 'ar_comment_data' => null,
+ 'ar_comment_cid' => null,
+ 'ts_tags' => null,
+ 'ar_id' => '2',
+ 'ar_namespace' => '0',
+ 'ar_title' => 'PageArchiveTest_thePage',
+ 'ar_parent_id' => strval( $this->ipRev->getParentId() ),
+ ],
+ [
+ 'ar_minor_edit' => '0',
+ 'ar_user' => (string)$this->getTestUser()->getUser()->getId(),
+ 'ar_user_text' => $this->getTestUser()->getUser()->getName(),
+ 'ar_actor' => null,
+ 'ar_len' => '7',
+ 'ar_deleted' => '0',
+ 'ar_rev_id' => strval( $this->firstRev->getId() ),
+ 'ar_timestamp' => $this->db->timestamp( $this->firstRev->getTimestamp() ),
+ 'ar_sha1' => 'pr0s8e18148pxhgjfa0gjrvpy8fiyxc',
+ 'ar_page_id' => strval( $this->firstRev->getPageId() ),
+ 'ar_comment_text' => 'testing',
+ 'ar_comment_data' => null,
+ 'ar_comment_cid' => null,
+ 'ts_tags' => null,
+ 'ar_id' => '1',
+ 'ar_namespace' => '0',
+ 'ar_title' => 'PageArchiveTest_thePage',
+ 'ar_parent_id' => '0',
+ ],
+ ];
+ }
+
+}
--- /dev/null
+<?php
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Storage\SqlBlobStore;
+use MediaWiki\Tests\Storage\PreMcrSchemaOverride;
+
+/**
+ * Test class for page archiving, using the pre-MCR schema.
+ *
+ * @group ContentHandler
+ * @group Database
+ * ^--- important, causes temporary tables to be used instead of the real database
+ *
+ * @group medium
+ * ^--- important, causes tests not to fail with timeout
+ */
+class PageArchivePreMcrTest extends PageArchiveTestBase {
+
+ use PreMcrSchemaOverride;
+
+ /**
+ * @covers PageArchive::getTextFromRow
+ */
+ public function testGetTextFromRow() {
+ $this->hideDeprecated( PageArchive::class . '::getTextFromRow' );
+
+ /** @var SqlBlobStore $blobStore */
+ $blobStore = MediaWikiServices::getInstance()->getBlobStore();
+
+ $textId = $blobStore->getTextIdFromAddress(
+ $this->firstRev->getSlot( 'main' )->getAddress()
+ );
+
+ $row = (object)[ 'ar_text_id' => $textId ];
+ $text = $this->archivedPage->getTextFromRow( $row );
+ $this->assertSame( 'testing', $text );
+ }
+
+ protected function getExpectedArchiveRows() {
+ /** @var SqlBlobStore $blobStore */
+ $blobStore = MediaWikiServices::getInstance()->getBlobStore();
+
+ return [
+ [
+ 'ar_minor_edit' => '0',
+ 'ar_user' => '0',
+ 'ar_user_text' => $this->ipEditor,
+ 'ar_actor' => null,
+ 'ar_len' => '11',
+ 'ar_deleted' => '0',
+ 'ar_rev_id' => strval( $this->ipRev->getId() ),
+ 'ar_timestamp' => $this->db->timestamp( $this->ipRev->getTimestamp() ),
+ 'ar_sha1' => '0qdrpxl537ivfnx4gcpnzz0285yxryy',
+ 'ar_page_id' => strval( $this->ipRev->getPageId() ),
+ 'ar_comment_text' => 'just a test',
+ 'ar_comment_data' => null,
+ 'ar_comment_cid' => null,
+ 'ar_content_format' => null,
+ 'ar_content_model' => null,
+ 'ts_tags' => null,
+ 'ar_id' => '2',
+ 'ar_namespace' => '0',
+ 'ar_title' => 'PageArchiveTest_thePage',
+ 'ar_text_id' => (string)$blobStore->getTextIdFromAddress(
+ $this->ipRev->getSlot( 'main' )->getAddress()
+ ),
+ 'ar_parent_id' => strval( $this->ipRev->getParentId() ),
+ ],
+ [
+ 'ar_minor_edit' => '0',
+ 'ar_user' => (string)$this->getTestUser()->getUser()->getId(),
+ 'ar_user_text' => $this->getTestUser()->getUser()->getName(),
+ 'ar_actor' => null,
+ 'ar_len' => '7',
+ 'ar_deleted' => '0',
+ 'ar_rev_id' => strval( $this->firstRev->getId() ),
+ 'ar_timestamp' => $this->db->timestamp( $this->firstRev->getTimestamp() ),
+ 'ar_sha1' => 'pr0s8e18148pxhgjfa0gjrvpy8fiyxc',
+ 'ar_page_id' => strval( $this->firstRev->getPageId() ),
+ 'ar_comment_text' => 'testing',
+ 'ar_comment_data' => null,
+ 'ar_comment_cid' => null,
+ 'ar_content_format' => null,
+ 'ar_content_model' => null,
+ 'ts_tags' => null,
+ 'ar_id' => '1',
+ 'ar_namespace' => '0',
+ 'ar_title' => 'PageArchiveTest_thePage',
+ 'ar_text_id' => (string)$blobStore->getTextIdFromAddress(
+ $this->firstRev->getSlot( 'main' )->getAddress()
+ ),
+ 'ar_parent_id' => '0',
+ ],
+ ];
+ }
+
+}
--- /dev/null
+<?php
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Storage\RevisionRecord;
+
+/**
+ * Base class for tests of PageArchive against different database schemas.
+ */
+abstract class PageArchiveTestBase extends MediaWikiTestCase {
+
+ /**
+ * @var int
+ */
+ protected $pageId;
+
+ /**
+ * @var PageArchive $archivedPage
+ */
+ protected $archivedPage;
+
+ /**
+ * A logged out user who edited the page before it was archived.
+ * @var string $ipEditor
+ */
+ protected $ipEditor;
+
+ /**
+ * Revision of the first (initial) edit
+ * @var RevisionRecord
+ */
+ protected $firstRev;
+
+ /**
+ * Revision of the IP edit (the second edit)
+ * @var RevisionRecord
+ */
+ protected $ipRev;
+
+ function __construct( $name = null, array $data = [], $dataName = '' ) {
+ parent::__construct( $name, $data, $dataName );
+
+ $this->tablesUsed = array_merge(
+ $this->tablesUsed,
+ [
+ 'page',
+ 'revision',
+ 'ip_changes',
+ 'text',
+ 'archive',
+ 'recentchanges',
+ 'logging',
+ 'page_props',
+ ]
+ );
+ }
+
+ protected function addCoreDBData() {
+ // Blank out to avoid failures when schema overrides imposed by subclasses
+ // affect revision storage.
+ }
+
+ /**
+ * @return int
+ */
+ abstract protected function getMcrMigrationStage();
+
+ /**
+ * @return string[]
+ */
+ abstract protected function getMcrTablesToReset();
+
+ /**
+ * @return bool
+ */
+ protected function getContentHandlerUseDB() {
+ return true;
+ }
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->tablesUsed += $this->getMcrTablesToReset();
+
+ $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->setMwGlobals( 'wgContentHandlerUseDB', $this->getContentHandlerUseDB() );
+ $this->setMwGlobals(
+ 'wgMultiContentRevisionSchemaMigrationStage',
+ $this->getMcrMigrationStage()
+ );
+ $this->overrideMwServices();
+
+ // First create our dummy page
+ $page = Title::newFromText( 'PageArchiveTest_thePage' );
+ $page = new WikiPage( $page );
+ $content = ContentHandler::makeContent(
+ 'testing',
+ $page->getTitle(),
+ CONTENT_MODEL_WIKITEXT
+ );
+
+ $user = $this->getTestUser()->getUser();
+ $page->doEditContent( $content, 'testing', EDIT_NEW, false, $user );
+
+ $this->pageId = $page->getId();
+ $this->firstRev = $page->getRevision()->getRevisionRecord();
+
+ // Insert IP revision
+ $this->ipEditor = '2001:db8::1';
+
+ $revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
+
+ $ipTimestamp = wfTimestamp(
+ TS_MW,
+ wfTimestamp( TS_UNIX, $this->firstRev->getTimestamp() ) + 1
+ );
+
+ $rev = $revisionStore->newMutableRevisionFromArray( [
+ 'text' => 'Lorem Ipsum',
+ 'comment' => 'just a test',
+ 'page' => $page->getId(),
+ 'user_text' => $this->ipEditor,
+ 'timestamp' => $ipTimestamp,
+ ] );
+
+ $dbw = wfGetDB( DB_MASTER );
+ $this->ipRev = $revisionStore->insertRevisionOn( $rev, $dbw );
+
+ // Delete the page
+ $page->doDeleteArticleReal( 'Just a test deletion' );
+
+ $this->archivedPage = new PageArchive( $page->getTitle() );
+ }
+
+ /**
+ * @covers PageArchive::undelete
+ * @covers PageArchive::undeleteRevisions
+ */
+ public function testUndeleteRevisions() {
+ // TODO: MCR: Test undeletion with multiple slots. Check that slots remain untouched.
+
+ // First make sure old revisions are archived
+ $dbr = wfGetDB( DB_REPLICA );
+ $arQuery = Revision::getArchiveQueryInfo();
+ $row = $dbr->selectRow(
+ $arQuery['tables'],
+ $arQuery['fields'],
+ [ 'ar_rev_id' => $this->ipRev->getId() ],
+ __METHOD__,
+ [],
+ $arQuery['joins']
+ );
+ $this->assertEquals( $this->ipEditor, $row->ar_user_text );
+
+ // Should not be in revision
+ $row = $dbr->selectRow( 'revision', '1', [ 'rev_id' => $this->ipRev->getId() ] );
+ $this->assertFalse( $row );
+
+ // Should not be in ip_changes
+ $row = $dbr->selectRow( 'ip_changes', '1', [ 'ipc_rev_id' => $this->ipRev->getId() ] );
+ $this->assertFalse( $row );
+
+ // Restore the page
+ $this->archivedPage->undelete( [] );
+
+ // Should be back in revision
+ $revQuery = Revision::getQueryInfo();
+ $row = $dbr->selectRow(
+ $revQuery['tables'],
+ $revQuery['fields'],
+ [ 'rev_id' => $this->ipRev->getId() ],
+ __METHOD__,
+ [],
+ $revQuery['joins']
+ );
+ $this->assertNotFalse( $row, 'row exists in revision table' );
+ $this->assertEquals( $this->ipEditor, $row->rev_user_text );
+
+ // Should be back in ip_changes
+ $row = $dbr->selectRow( 'ip_changes', [ 'ipc_hex' ], [ 'ipc_rev_id' => $this->ipRev->getId() ] );
+ $this->assertNotFalse( $row, 'row exists in ip_changes table' );
+ $this->assertEquals( IP::toHex( $this->ipEditor ), $row->ipc_hex );
+ }
+
+ abstract protected function getExpectedArchiveRows();
+
+ /**
+ * @covers PageArchive::listRevisions
+ */
+ public function testListRevisions() {
+ $revisions = $this->archivedPage->listRevisions();
+ $this->assertEquals( 2, $revisions->numRows() );
+
+ // Get the rows as arrays
+ $row0 = (array)$revisions->current();
+ $row1 = (array)$revisions->next();
+
+ $expectedRows = $this->getExpectedArchiveRows();
+
+ $this->assertEquals(
+ $expectedRows[0],
+ $row0
+ );
+ $this->assertEquals(
+ $expectedRows[1],
+ $row1
+ );
+ }
+
+ /**
+ * @covers PageArchive::listPagesBySearch
+ */
+ public function testListPagesBySearch() {
+ $pages = PageArchive::listPagesBySearch( 'PageArchiveTest_thePage' );
+ $this->assertSame( 1, $pages->numRows() );
+
+ $page = (array)$pages->current();
+
+ $this->assertSame(
+ [
+ 'ar_namespace' => '0',
+ 'ar_title' => 'PageArchiveTest_thePage',
+ 'count' => '2',
+ ],
+ $page
+ );
+ }
+
+ /**
+ * @covers PageArchive::listPagesBySearch
+ */
+ public function testListPagesByPrefix() {
+ $pages = PageArchive::listPagesByPrefix( 'PageArchiveTest' );
+ $this->assertSame( 1, $pages->numRows() );
+
+ $page = (array)$pages->current();
+
+ $this->assertSame(
+ [
+ 'ar_namespace' => '0',
+ 'ar_title' => 'PageArchiveTest_thePage',
+ 'count' => '2',
+ ],
+ $page
+ );
+ }
+
+ public function provideGetTextFromRowThrowsInvalidArgumentException() {
+ yield 'missing ar_text_id field' => [ [] ];
+ yield 'ar_text_id is null' => [ [ 'ar_text_id' => null ] ];
+ yield 'ar_text_id is zero' => [ [ 'ar_text_id' => 0 ] ];
+ yield 'ar_text_id is "0"' => [ [ 'ar_text_id' => '0' ] ];
+ }
+
+ /**
+ * @dataProvider provideGetTextFromRowThrowsInvalidArgumentException
+ * @covers PageArchive::getTextFromRow
+ */
+ public function testGetTextFromRowThrowsInvalidArgumentException( array $row ) {
+ $this->hideDeprecated( PageArchive::class . '::getTextFromRow' );
+ $this->setExpectedException( InvalidArgumentException::class );
+
+ $this->archivedPage->getTextFromRow( (object)$row );
+ }
+
+ /**
+ * @covers PageArchive::getLastRevisionText
+ */
+ public function testGetLastRevisionText() {
+ $this->hideDeprecated( PageArchive::class . '::getLastRevisionText' );
+
+ $text = $this->archivedPage->getLastRevisionText();
+ $this->assertSame( 'Lorem Ipsum', $text );
+ }
+
+ /**
+ * @covers PageArchive::getLastRevisionId
+ */
+ public function testGetLastRevisionId() {
+ $id = $this->archivedPage->getLastRevisionId();
+ $this->assertSame( $this->ipRev->getId(), $id );
+ }
+
+ /**
+ * @covers PageArchive::isDeleted
+ */
+ public function testIsDeleted() {
+ $this->assertTrue( $this->archivedPage->isDeleted() );
+ }
+
+ /**
+ * @covers PageArchive::getRevision
+ */
+ public function testGetRevision() {
+ $rev = $this->archivedPage->getRevision( $this->ipRev->getTimestamp() );
+ $this->assertNotNull( $rev );
+ $this->assertSame( $this->pageId, $rev->getPage() );
+
+ $rev = $this->archivedPage->getRevision( '22991212115555' );
+ $this->assertNull( $rev );
+ }
+
+ /**
+ * @covers PageArchive::getRevision
+ */
+ public function testGetArchivedRevision() {
+ $rev = $this->archivedPage->getArchivedRevision( $this->ipRev->getId() );
+ $this->assertNotNull( $rev );
+ $this->assertSame( $this->ipRev->getTimestamp(), $rev->getTimestamp() );
+ $this->assertSame( $this->pageId, $rev->getPage() );
+
+ $rev = $this->archivedPage->getArchivedRevision( 632546 );
+ $this->assertNull( $rev );
+ }
+
+}
/**
* @param string|Title|WikiPage $page
* @param string $text
- * @param int $model
+ * @param int|null $model
*
* @return WikiPage
*/
CONTENT_MODEL_WIKITEXT
);
+ $preparedEditBefore = $page->prepareContentForEdit( $content, null, $user1 );
+
$status = $page->doEditContent( $content, "[[testing]] 1", EDIT_NEW, false, $user1 );
$this->assertTrue( $status->isOK(), 'OK' );
$this->assertTrue( $status->value['revision']->getContent()->equals( $content ), 'equals' );
$rev = $page->getRevision();
+ $preparedEditAfter = $page->prepareContentForEdit( $content, $rev, $user1 );
+
$this->assertNotNull( $rev->getRecentChange() );
$this->assertSame( $rev->getId(), (int)$rev->getRecentChange()->getAttribute( 'rc_this_oldid' ) );
+ // make sure that cached ParserOutput gets re-used throughout
+ $this->assertSame( $preparedEditBefore->output, $preparedEditAfter->output );
+
$id = $page->getId();
// Test page creation logging
$this->assertEquals( 2, $n, 'pagelinks should contain two links from the page' );
}
+ /**
+ * @covers WikiPage::doEditContent
+ */
+ public function testDoEditContent_twice() {
+ $title = Title::newFromText( __METHOD__ );
+ $page = WikiPage::factory( $title );
+ $content = ContentHandler::makeContent( '$1 van $2', $title );
+
+ // Make sure we can do the exact same save twice.
+ // This tests checks that internal caches are reset as appropriate.
+ $status1 = $page->doEditContent( $content, __METHOD__ );
+ $status2 = $page->doEditContent( $content, __METHOD__ );
+
+ $this->assertTrue( $status1->isOK(), 'OK' );
+ $this->assertTrue( $status2->isOK(), 'OK' );
+
+ $this->assertTrue( isset( $status1->value['revision'] ), 'OK' );
+ $this->assertFalse( isset( $status2->value['revision'] ), 'OK' );
+ }
+
/**
* Undeletion is covered in PageArchiveTest::testUndeleteRevisions()
* TODO: Revision deletion
->method( 'getParserOutput' )
->willReturn( new ParserOutput( 'HTML' ) );
- $updater = $page->newPageUpdater( $user );
+ $preparedEditBefore = $page->prepareContentForEdit( $content, null, $user );
+
+ // provide context, so the cache can be kept in place
+ $slotsUpdate = new revisionSlotsUpdate();
+ $slotsUpdate->modifyContent( 'main', $content );
+
+ $updater = $page->newPageUpdater( $user, $slotsUpdate );
$updater->setContent( 'main', $content );
$revision = $updater->saveRevision(
CommentStoreComment::newUnsavedComment( 'test' ),
EDIT_NEW
);
+ $preparedEditAfter = $page->prepareContentForEdit( $content, $revision, $user );
+
$this->assertSame( $revision->getId(), $page->getLatest() );
+
+ // Parsed output must remain cached throughout.
+ $this->assertSame( $preparedEditBefore->output, $preparedEditAfter->output );
}
/**
$updater1->prepareUpdate( $revision );
- // Re-use updater with same revision or content
+ // Re-use updater with same revision or content, even if base changed
$this->assertSame( $updater1, $page->getDerivedDataUpdater( $user, $revision ) );
$slotsUpdate = RevisionSlotsUpdate::newFromContent(
);
$this->assertSame( $updater1, $page->getDerivedDataUpdater( $user, null, $slotsUpdate ) );
+ // Don't re-use for edit if base revision ID changed
+ $this->assertNotSame(
+ $updater1,
+ $page->getDerivedDataUpdater( $user, null, $slotsUpdate, true )
+ );
+
// Don't re-use with different user
$updater2a = $page->getDerivedDataUpdater( $admin, null, $slotsUpdate );
$updater2a->prepareContent( $admin, $slotsUpdate, false );
--- /dev/null
+<?php
+use MediaWiki\Tests\Storage\McrReadNewSchemaOverride;
+
+/**
+ * Tests WikiPage against the intermediate MCR DB schema for use during schema migration.
+ *
+ * @covers WikiPage
+ *
+ * @group WikiPage
+ * @group Storage
+ * @group ContentHandler
+ * @group Database
+ * @group medium
+ */
+class WikiPageMcrReadNewDbTest extends WikiPageDbTestBase {
+
+ use McrReadNewSchemaOverride;
+
+ protected function getContentHandlerUseDB() {
+ return true;
+ }
+
+}
);
}
+ /**
+ * @covers ResourceLoaderStartupModule::getAllModuleHashes
+ * @covers ResourceLoaderStartupModule::getDefinitionSummary
+ */
+ public function testGetVersionHash_varyModule() {
+ $context1 = $this->getResourceLoaderContext();
+ $rl1 = $context1->getResourceLoader();
+ $rl1->register( [
+ 'test.a' => new ResourceLoaderTestModule(),
+ 'test.b' => new ResourceLoaderTestModule(),
+ ] );
+ $module = new ResourceLoaderStartupModule();
+ $version1 = $module->getVersionHash( $context1 );
+
+ $context2 = $this->getResourceLoaderContext();
+ $rl2 = $context2->getResourceLoader();
+ $rl2->register( [
+ 'test.b' => new ResourceLoaderTestModule(),
+ 'test.c' => new ResourceLoaderTestModule(),
+ ] );
+ $module = new ResourceLoaderStartupModule();
+ $version2 = $module->getVersionHash( $context2 );
+
+ $context3 = $this->getResourceLoaderContext();
+ $rl3 = $context3->getResourceLoader();
+ $rl3->register( [
+ 'test.a' => new ResourceLoaderTestModule(),
+ 'test.b' => new ResourceLoaderTestModule( [ 'script' => 'different' ] ),
+ ] );
+ $module = new ResourceLoaderStartupModule();
+ $version3 = $module->getVersionHash( $context3 );
+
+ $this->assertEquals(
+ $version1,
+ $version2,
+ 'Module name is insignificant'
+ );
+
+ $this->assertNotEquals(
+ $version1,
+ $version3,
+ 'Hash change of any module impacts startup hash'
+ );
+ }
+
}
/**
* If you need a Session for testing but don't want to create a backend to
* construct one, use this.
- * @param object $backend Object to serve as the SessionBackend
+ * @param object|null $backend Object to serve as the SessionBackend
* @param int $index
- * @param LoggerInterface $logger
+ * @param LoggerInterface|null $logger
* @return Session
*/
public static function getDummySession( $backend = null, $index = -1, $logger = null ) {
'2555',
'Thai year'
],
+ [
+ 'xkY',
+ '19410101090705',
+ '2484',
+ '2484',
+ 'Thai year'
+ ],
[
'xoY',
'20120102090705',
/**
* @param Title $title
- * @param int $revId Unused.
+ * @param int|null $revId Unused.
* @param null|ParserOptions $options
* @param bool $generateHtml Whether to generate Html (default: true). If false, the result
* of calling getText() on the ParserOutput object returned by this method is undefined.
* @see ContentHandler::serializeContent
*
* @param Content $content
- * @param string $format
+ * @param string|null $format
*
* @return string
*/
* @see ContentHandler::unserializeContent
*
* @param string $blob
- * @param string $format Unused.
+ * @param string|null $format Unused.
*
* @return Content
*/
/**
* @param Title $title
- * @param int $revId Unused.
+ * @param int|null $revId Unused.
* @param null|ParserOptions $options
* @param bool $generateHtml Whether to generate Html (default: true). If false, the result
* of calling getText() on the ParserOutput object returned by this method is undefined.
* @see ContentHandler::serializeContent
*
* @param Content $content
- * @param string $format
+ * @param string|null $format
*
* @return string
*/
* @see ContentHandler::unserializeContent
*
* @param string $blob
- * @param string $format Unused.
+ * @param string|null $format Unused.
*
* @return Content
*/
* @see ContentHandler::unserializeContent
*
* @param string $blob
- * @param string $format
+ * @param string|null $format
*
* @return Content
*/
$element.css( 'height' );
// eslint-disable-next-line no-unused-expressions
el.innerHTML;
+ // eslint-disable-next-line no-self-assign
el.className = el.className;
// eslint-disable-next-line no-unused-expressions
document.documentElement.clientHeight;
*
* @param int $status
* @param string $msgHtml HTML
- * @param string $msgText Short error description, for internal logging. Defaults to $msgHtml.
+ * @param string|null $msgText Short error description, for internal logging. Defaults to $msgHtml.
* Only used for HTTP 500 errors.
* @param array $context Error context, for internal logging. Only used for HTTP 500 errors.
* @return void