* (T198214) The 'disabletidy' parameter to action=parse has been
deprecated; untidy output will not be supported by future wikitext
parsers.
+* Added intestactionsdetail to action=query&prop=info to allow retrieving the
+ reasons an action is not allowed.
+* Deprecated action=query&prop=info inprop=readable in favor of
+ intestactions=read.
=== Action API internal changes in 1.32 ===
* Added 'ApiParseMakeOutputPage' hook.
* ApiFeedContributions::feedItemAuthor()
* ApiFeedContributions::feedItemDesc()
* ApiQueryRevisionsBase::extractRevisionInfo()
+* The following deprecated methods have been removed:
+ * ApiBase::profileIn() (deprecated in 1.25)
+ * ApiBase::profileOut() (deprecated in 1.25)
+ * ApiBase::safeProfileOut() (deprecated in 1.25)
+ * ApiBase::profileDBIn() (deprecated in 1.25)
+ * ApiBase::profileDBOut() (deprecated in 1.25)
+ * ApiBase::dieUsage() (deprecated in 1.29)
+ * ApiBase::dieUsageMsg() (deprecated in 1.29)
+ * ApiBase::dieUsageMsgOrDebug() (deprecated in 1.29)
+ * ApiBase::getErrorFromStatus() (deprecated in 1.29)
+ * ApiBase::parseMsg() (deprecated in 1.29)
+ * ApiBase::setWarning() (deprecated in 1.29)
+ * ApiPageSet::getInvalidTitles() (deprecated in 1.26)
+ * ApiQueryLogEvents::addLogParams() (deprecated in 1.25)
+ * ApiUsageException::getCodeString() (deprecated in 1.29)
+ * ApiUsageException::getMessageArray() (deprecated in 1.29)
+* Class UsageException, deprecated in 1.29, has been removed.
+* ApiErrorFormatter: Added getFormat() and newWithFormat(). In particular, you
+ can now easily test $formatter->getFormat() === 'bc', and then call
+ $formatter->newWithFormat( 'plaintext' ) to get a non-BC formatter.
=== Languages updated in 1.32 ===
MediaWiki supports over 350 languages. Many localisations are updated regularly.
* The hook 'UnknownAction', deprecated since 1.19, has now been removed.
* The hook 'ParserLimitReport', deprecated since 1.22, has been removed. Use
the hooks 'ParserLimitReportPrepare' and 'ParserLimitReportFormat' instead.
+* The following deprecated API methods have been removed:
+ * ApiBase::profileIn() (deprecated in 1.25)
+ * ApiBase::profileOut() (deprecated in 1.25)
+ * ApiBase::safeProfileOut() (deprecated in 1.25)
+ * ApiBase::profileDBIn() (deprecated in 1.25)
+ * ApiBase::profileDBOut() (deprecated in 1.25)
+ * ApiBase::dieUsage() (deprecated in 1.29)
+ * ApiBase::dieUsageMsg() (deprecated in 1.29)
+ * ApiBase::dieUsageMsgOrDebug() (deprecated in 1.29)
+ * ApiBase::getErrorFromStatus() (deprecated in 1.29)
+ * ApiBase::parseMsg() (deprecated in 1.29)
+ * ApiBase::setWarning() (deprecated in 1.29)
+ * ApiPageSet::getInvalidTitles() (deprecated in 1.26)
+ * ApiQueryLogEvents::addLogParams() (deprecated in 1.25)
+ * ApiUsageException::getCodeString() (deprecated in 1.29)
+ * ApiUsageException::getMessageArray() (deprecated in 1.29)
+* Class UsageException, deprecated in 1.29, has been removed.
=== Deprecations in 1.32 ===
* HTMLForm::setSubmitProgressive() is deprecated. No need to call it. Submit
* The $wgUseKeyHeader configuration option and the
OutputPage::getKeyHeader() method have been deprecated; the relevant
draft IETF spec expired without becoming a standard.
+* Deprecated API action=query&prop=info inprop=readable in favor of
+ intestactions=read.
=== Other changes in 1.32 ===
* (T198811) The following tables have had their UNIQUE indexes turned into
'UploadStashWrongOwnerException' => __DIR__ . '/includes/upload/UploadStash.php',
'UploadStashZeroLengthFileException' => __DIR__ . '/includes/upload/UploadStash.php',
'UppercaseCollation' => __DIR__ . '/includes/collation/UppercaseCollation.php',
- 'UsageException' => __DIR__ . '/includes/api/UsageException.php',
'User' => __DIR__ . '/includes/user/User.php',
'UserArray' => __DIR__ . '/includes/user/UserArray.php',
'UserArrayFromResult' => __DIR__ . '/includes/user/UserArrayFromResult.php',
*/
$wgActorTableSchemaMigrationStage = SCHEMA_COMPAT_OLD;
-/**
- * Temporary option to disable the date picker from the Expiry Widget.
- *
- * @since 1.32
- * @deprecated 1.32
- * @var bool
- */
-$wgExpiryWidgetNoDatePicker = false;
-
/**
* change_tag table schema migration stage.
*
function wfWaitForSlaves(
$ifWritesSince = null, $wiki = false, $cluster = false, $timeout = null
) {
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+
if ( $cluster === '*' ) {
$cluster = false;
- $wiki = false;
+ $domain = false;
} elseif ( $wiki === false ) {
- $wiki = wfWikiID();
+ $domain = $lbFactory->getLocalDomainID();
+ } else {
+ $domain = $wiki;
}
$opts = [
- 'wiki' => $wiki,
+ 'domain' => $domain,
'cluster' => $cluster,
// B/C: first argument used to be "max seconds of lag"; ignore such values
'ifWritesSince' => ( $ifWritesSince > 1e9 ) ? $ifWritesSince : null
$opts['timeout'] = $timeout;
}
- $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
return $lbFactory->waitForReplication( $opts );
}
$dbw = wfGetDB( DB_MASTER );
$dbw->onTransactionPreCommitOrIdle(
- function () {
- ResourceLoaderWikiModule::invalidateModuleCache( $this, null, null, wfWikiID() );
+ function () use ( $dbw ) {
+ ResourceLoaderWikiModule::invalidateModuleCache(
+ $this, null, null, $dbw->getDomainId() );
},
__METHOD__
);
return $status;
}
+ /**
+ * Call wfTransactionalTimeLimit() if this request was POSTed
+ * @since 1.26
+ */
+ protected function useTransactionalTimeLimit() {
+ if ( $this->getRequest()->wasPosted() ) {
+ wfTransactionalTimeLimit();
+ }
+ }
+
/**@}*/
/************************************************************************//**
return false;
}
- /**
- * @deprecated since 1.25
- */
- public function profileIn() {
- wfDeprecated( __METHOD__, '1.25' );
- }
-
- /**
- * @deprecated since 1.25
- */
- public function profileOut() {
- wfDeprecated( __METHOD__, '1.25' );
- }
-
- /**
- * @deprecated since 1.25
- */
- public function safeProfileOut() {
- wfDeprecated( __METHOD__, '1.25' );
- }
-
- /**
- * @deprecated since 1.25
- */
- public function profileDBIn() {
- wfDeprecated( __METHOD__, '1.25' );
- }
-
- /**
- * @deprecated since 1.25
- */
- public function profileDBOut() {
- wfDeprecated( __METHOD__, '1.25' );
- }
-
- /**
- * Call wfTransactionalTimeLimit() if this request was POSTed
- * @since 1.26
- */
- protected function useTransactionalTimeLimit() {
- if ( $this->getRequest()->wasPosted() ) {
- wfTransactionalTimeLimit();
- }
- }
-
- /**
- * @deprecated since 1.29, use ApiBase::addWarning() instead
- * @param string $warning Warning message
- */
- public function setWarning( $warning ) {
- wfDeprecated( __METHOD__, '1.29' );
- $msg = new ApiRawMessage( $warning, 'warning' );
- $this->getErrorFormatter()->addWarning( $this->getModulePath(), $msg );
- }
-
- /**
- * Throw an ApiUsageException, which will (if uncaught) call the main module's
- * error handler and die with an error message.
- *
- * @deprecated since 1.29, use self::dieWithError() instead
- * @param string $description One-line human-readable description of the
- * error condition, e.g., "The API requires a valid action parameter"
- * @param string $errorCode Brief, arbitrary, stable string to allow easy
- * automated identification of the error, e.g., 'unknown_action'
- * @param int $httpRespCode HTTP response code
- * @param array|null $extradata Data to add to the "<error>" element; array in ApiResult format
- * @throws ApiUsageException always
- */
- public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
- wfDeprecated( __METHOD__, '1.29' );
- $this->dieWithError(
- new RawMessage( '$1', [ $description ] ),
- $errorCode,
- $extradata,
- $httpRespCode
- );
- }
-
- /**
- * Get error (as code, string) from a Status object.
- *
- * @since 1.23
- * @deprecated since 1.29, use ApiErrorFormatter::arrayFromStatus instead
- * @param Status $status
- * @param array|null &$extraData Set if extra data from IApiMessage is available (since 1.27)
- * @return array Array of code and error string
- * @throws MWException
- */
- public function getErrorFromStatus( $status, &$extraData = null ) {
- wfDeprecated( __METHOD__, '1.29' );
- if ( $status->isGood() ) {
- throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
- }
-
- $errors = $status->getErrorsByType( 'error' );
- if ( !$errors ) {
- // No errors? Assume the warnings should be treated as errors
- $errors = $status->getErrorsByType( 'warning' );
- }
- if ( !$errors ) {
- // Still no errors? Punt
- $errors = [ [ 'message' => 'unknownerror-nocode', 'params' => [] ] ];
- }
-
- if ( $errors[0]['message'] instanceof MessageSpecifier ) {
- $msg = $errors[0]['message'];
- } else {
- $msg = new Message( $errors[0]['message'], $errors[0]['params'] );
- }
- if ( !$msg instanceof IApiMessage ) {
- $key = $msg->getKey();
- $params = $msg->getParams();
- array_unshift( $params, self::$messageMap[$key] ?? $key );
- $msg = ApiMessage::create( $params );
- }
-
- return [
- $msg->getApiCode(),
- ApiErrorFormatter::stripMarkup( $msg->inLanguage( 'en' )->useDatabase( false )->text() )
- ];
- }
-
- /**
- * @deprecated since 1.29. Prior to 1.29, this was a public mapping from
- * arbitrary strings (often message keys used elsewhere in MediaWiki) to
- * API codes and message texts, and a few interfaces required poking
- * something in here. Now we're repurposing it to map those same strings
- * to i18n messages, and declaring that any interface that requires poking
- * at this is broken and needs replacing ASAP.
- */
- private static $messageMap = [
- 'unknownerror' => 'apierror-unknownerror',
- 'unknownerror-nocode' => 'apierror-unknownerror-nocode',
- 'ns-specialprotected' => 'ns-specialprotected',
- 'protectedinterface' => 'protectedinterface',
- 'namespaceprotected' => 'namespaceprotected',
- 'customcssprotected' => 'customcssprotected',
- 'customjsprotected' => 'customjsprotected',
- 'cascadeprotected' => 'cascadeprotected',
- 'protectedpagetext' => 'protectedpagetext',
- 'protect-cantedit' => 'protect-cantedit',
- 'deleteprotected' => 'deleteprotected',
- 'badaccess-group0' => 'badaccess-group0',
- 'badaccess-groups' => 'badaccess-groups',
- 'titleprotected' => 'titleprotected',
- 'nocreate-loggedin' => 'nocreate-loggedin',
- 'nocreatetext' => 'nocreatetext',
- 'movenologintext' => 'movenologintext',
- 'movenotallowed' => 'movenotallowed',
- 'confirmedittext' => 'confirmedittext',
- 'blockedtext' => 'apierror-blocked',
- 'autoblockedtext' => 'apierror-autoblocked',
- 'systemblockedtext' => 'apierror-systemblocked',
- 'actionthrottledtext' => 'apierror-ratelimited',
- 'alreadyrolled' => 'alreadyrolled',
- 'cantrollback' => 'cantrollback',
- 'readonlytext' => 'readonlytext',
- 'sessionfailure' => 'sessionfailure',
- 'cannotdelete' => 'cannotdelete',
- 'notanarticle' => 'apierror-missingtitle',
- 'selfmove' => 'selfmove',
- 'immobile_namespace' => 'apierror-immobilenamespace',
- 'articleexists' => 'articleexists',
- 'hookaborted' => 'hookaborted',
- 'cantmove-titleprotected' => 'cantmove-titleprotected',
- 'imagenocrossnamespace' => 'imagenocrossnamespace',
- 'imagetypemismatch' => 'imagetypemismatch',
- 'ip_range_invalid' => 'ip_range_invalid',
- 'range_block_disabled' => 'range_block_disabled',
- 'nosuchusershort' => 'nosuchusershort',
- 'badipaddress' => 'badipaddress',
- 'ipb_expiry_invalid' => 'ipb_expiry_invalid',
- 'ipb_already_blocked' => 'ipb_already_blocked',
- 'ipb_blocked_as_range' => 'ipb_blocked_as_range',
- 'ipb_cant_unblock' => 'ipb_cant_unblock',
- 'mailnologin' => 'apierror-cantsend',
- 'ipbblocked' => 'ipbblocked',
- 'ipbnounblockself' => 'ipbnounblockself',
- 'usermaildisabled' => 'usermaildisabled',
- 'blockedemailuser' => 'apierror-blockedfrommail',
- 'notarget' => 'apierror-notarget',
- 'noemail' => 'noemail',
- 'rcpatroldisabled' => 'rcpatroldisabled',
- 'markedaspatrollederror-noautopatrol' => 'markedaspatrollederror-noautopatrol',
- 'delete-toobig' => 'delete-toobig',
- 'movenotallowedfile' => 'movenotallowedfile',
- 'userrights-no-interwiki' => 'userrights-no-interwiki',
- 'userrights-nodatabase' => 'userrights-nodatabase',
- 'nouserspecified' => 'nouserspecified',
- 'noname' => 'noname',
- 'summaryrequired' => 'apierror-summaryrequired',
- 'import-rootpage-invalid' => 'import-rootpage-invalid',
- 'import-rootpage-nosubpage' => 'import-rootpage-nosubpage',
- 'readrequired' => 'apierror-readapidenied',
- 'writedisabled' => 'apierror-noapiwrite',
- 'writerequired' => 'apierror-writeapidenied',
- 'missingparam' => 'apierror-missingparam',
- 'invalidtitle' => 'apierror-invalidtitle',
- 'nosuchpageid' => 'apierror-nosuchpageid',
- 'nosuchrevid' => 'apierror-nosuchrevid',
- 'nosuchuser' => 'nosuchusershort',
- 'invaliduser' => 'apierror-invaliduser',
- 'invalidexpiry' => 'apierror-invalidexpiry',
- 'pastexpiry' => 'apierror-pastexpiry',
- 'create-titleexists' => 'apierror-create-titleexists',
- 'missingtitle-createonly' => 'apierror-missingtitle-createonly',
- 'cantblock' => 'apierror-cantblock',
- 'canthide' => 'apierror-canthide',
- 'cantblock-email' => 'apierror-cantblock-email',
- 'cantunblock' => 'apierror-permissiondenied-generic',
- 'cannotundelete' => 'cannotundelete',
- 'permdenied-undelete' => 'apierror-permissiondenied-generic',
- 'createonly-exists' => 'apierror-articleexists',
- 'nocreate-missing' => 'apierror-missingtitle',
- 'cantchangecontentmodel' => 'apierror-cantchangecontentmodel',
- 'nosuchrcid' => 'apierror-nosuchrcid',
- 'nosuchlogid' => 'apierror-nosuchlogid',
- 'protect-invalidaction' => 'apierror-protect-invalidaction',
- 'protect-invalidlevel' => 'apierror-protect-invalidlevel',
- 'toofewexpiries' => 'apierror-toofewexpiries',
- 'cantimport' => 'apierror-cantimport',
- 'cantimport-upload' => 'apierror-cantimport-upload',
- 'importnofile' => 'importnofile',
- 'importuploaderrorsize' => 'importuploaderrorsize',
- 'importuploaderrorpartial' => 'importuploaderrorpartial',
- 'importuploaderrortemp' => 'importuploaderrortemp',
- 'importcantopen' => 'importcantopen',
- 'import-noarticle' => 'import-noarticle',
- 'importbadinterwiki' => 'importbadinterwiki',
- 'import-unknownerror' => 'apierror-import-unknownerror',
- 'cantoverwrite-sharedfile' => 'apierror-cantoverwrite-sharedfile',
- 'sharedfile-exists' => 'apierror-fileexists-sharedrepo-perm',
- 'mustbeposted' => 'apierror-mustbeposted',
- 'show' => 'apierror-show',
- 'specialpage-cantexecute' => 'apierror-specialpage-cantexecute',
- 'invalidoldimage' => 'apierror-invalidoldimage',
- 'nodeleteablefile' => 'apierror-nodeleteablefile',
- 'fileexists-forbidden' => 'fileexists-forbidden',
- 'fileexists-shared-forbidden' => 'fileexists-shared-forbidden',
- 'filerevert-badversion' => 'filerevert-badversion',
- 'noimageredirect-anon' => 'apierror-noimageredirect-anon',
- 'noimageredirect-logged' => 'apierror-noimageredirect',
- 'spamdetected' => 'apierror-spamdetected',
- 'contenttoobig' => 'apierror-contenttoobig',
- 'noedit-anon' => 'apierror-noedit-anon',
- 'noedit' => 'apierror-noedit',
- 'wasdeleted' => 'apierror-pagedeleted',
- 'blankpage' => 'apierror-emptypage',
- 'editconflict' => 'editconflict',
- 'hashcheckfailed' => 'apierror-badmd5',
- 'missingtext' => 'apierror-notext',
- 'emptynewsection' => 'apierror-emptynewsection',
- 'revwrongpage' => 'apierror-revwrongpage',
- 'undo-failure' => 'undo-failure',
- 'content-not-allowed-here' => 'content-not-allowed-here',
- 'edit-hook-aborted' => 'edit-hook-aborted',
- 'edit-gone-missing' => 'edit-gone-missing',
- 'edit-conflict' => 'edit-conflict',
- 'edit-already-exists' => 'edit-already-exists',
- 'invalid-file-key' => 'apierror-invalid-file-key',
- 'nouploadmodule' => 'apierror-nouploadmodule',
- 'uploaddisabled' => 'uploaddisabled',
- 'copyuploaddisabled' => 'copyuploaddisabled',
- 'copyuploadbaddomain' => 'apierror-copyuploadbaddomain',
- 'copyuploadbadurl' => 'apierror-copyuploadbadurl',
- 'filename-tooshort' => 'filename-tooshort',
- 'filename-toolong' => 'filename-toolong',
- 'illegal-filename' => 'illegal-filename',
- 'filetype-missing' => 'filetype-missing',
- 'mustbeloggedin' => 'apierror-mustbeloggedin',
- ];
-
- /**
- * @deprecated do not use
- * @param array|string|MessageSpecifier $error Element of a getUserPermissionsErrors()-style array
- * @return ApiMessage
- */
- private function parseMsgInternal( $error ) {
- $msg = Message::newFromSpecifier( $error );
- if ( !$msg instanceof IApiMessage ) {
- $key = $msg->getKey();
- if ( isset( self::$messageMap[$key] ) ) {
- $params = $msg->getParams();
- array_unshift( $params, self::$messageMap[$key] );
- } else {
- $params = [ 'apierror-unknownerror', wfEscapeWikiText( $key ) ];
- }
- $msg = ApiMessage::create( $params );
- }
- return $msg;
- }
-
- /**
- * Return the error message related to a certain array
- * @deprecated since 1.29
- * @param array|string|MessageSpecifier $error Element of a getUserPermissionsErrors()-style array
- * @return array [ 'code' => code, 'info' => info ]
- */
- public function parseMsg( $error ) {
- wfDeprecated( __METHOD__, '1.29' );
- // Check whether someone passed the whole array, instead of one element as
- // documented. This breaks if it's actually an array of fallback keys, but
- // that's long-standing misbehavior introduced in r87627 to incorrectly
- // fix T30797.
- if ( is_array( $error ) ) {
- $first = reset( $error );
- if ( is_array( $first ) ) {
- wfDebug( __METHOD__ . ' was passed an array of arrays. ' . wfGetAllCallers( 5 ) );
- $error = $first;
- }
- }
-
- $msg = $this->parseMsgInternal( $error );
- return [
- 'code' => $msg->getApiCode(),
- 'info' => ApiErrorFormatter::stripMarkup(
- $msg->inLanguage( 'en' )->useDatabase( false )->text()
- ),
- 'data' => $msg->getApiData()
- ];
- }
-
- /**
- * Output the error message related to a certain array
- * @deprecated since 1.29, use ApiBase::dieWithError() instead
- * @param array|string|MessageSpecifier $error Element of a getUserPermissionsErrors()-style array
- * @throws ApiUsageException always
- */
- public function dieUsageMsg( $error ) {
- wfDeprecated( __METHOD__, '1.29' );
- $this->dieWithError( $this->parseMsgInternal( $error ) );
- }
-
- /**
- * Will only set a warning instead of failing if the global $wgDebugAPI
- * is set to true. Otherwise behaves exactly as dieUsageMsg().
- * @deprecated since 1.29, use ApiBase::dieWithErrorOrDebug() instead
- * @param array|string|MessageSpecifier $error Element of a getUserPermissionsErrors()-style array
- * @throws ApiUsageException
- * @since 1.21
- */
- public function dieUsageMsgOrDebug( $error ) {
- wfDeprecated( __METHOD__, '1.29' );
- $this->dieWithErrorOrDebug( $this->parseMsgInternal( $error ) );
- }
-
/**
* Return the description message.
*
$this->format = $format;
}
+ /**
+ * Return a formatter like this one but with a different format
+ *
+ * @since 1.32
+ * @param string $format New format.
+ * @return ApiErrorFormatter
+ */
+ public function newWithFormat( $format ) {
+ return new self( $this->result, $this->lang, $format, $this->useDB );
+ }
+
+ /**
+ * Fetch the format for this formatter
+ * @since 1.32
+ * @return string
+ */
+ public function getFormat() {
+ return $this->format;
+ }
+
/**
* Fetch the Language for this formatter
* @since 1.29
$msg = Message::newFromSpecifier( $exception );
$params = [];
} else {
- // Extract code and data from the exception, if applicable
- if ( $exception instanceof UsageException ) {
- $data = $exception->getMessageArray();
- if ( !$options['code'] ) {
- $options['code'] = $data['code'];
- }
- unset( $data['code'], $data['info'] );
- $options['data'] = array_merge( $data, $options['data'] );
- }
-
if ( isset( $options['wrap'] ) ) {
$msg = $options['wrap'];
} else {
parent::__construct( $result, Language::factory( 'en' ), 'none', false );
}
+ public function getFormat() {
+ return 'bc';
+ }
+
public function arrayFromStatus( StatusValue $status, $type = 'error', $format = null ) {
if ( $status->isGood() || !$status->getErrors() ) {
return [];
$feedItems[] = new FeedItem( $errorTitle, $errorText, '', '', '' );
}
} else {
- if ( $e instanceof UsageException ) {
- $errorCode = $e->getCodeString();
- } else {
- // Something is seriously wrong
- $errorCode = 'internal_api_error';
- }
+ // Something is seriously wrong
+ $errorCode = 'internal_api_error';
$errorTitle = $this->msg( 'api-feed-error-title', $errorCode );
$errorText = $e->getMessage();
$feedItems[] = new FeedItem( $errorTitle, $errorText, '', '', '' );
*/
protected function handleException( $e ) {
// T65145: Rollback any open database transactions
- if ( !( $e instanceof ApiUsageException || $e instanceof UsageException ) ) {
- // UsageExceptions are intentional, so don't rollback if that's the case
+ if ( !$e instanceof ApiUsageException ) {
+ // ApiUsageExceptions are intentional, so don't rollback if that's the case
MWExceptionHandler::rollbackMasterChangesAndLog( $e );
}
$this->addWarning( $error );
}
}
- } catch ( UsageException $ex ) {
- // The error printer itself is failing. Try suppressing its request
- // parameters and redo.
- $failed = true;
- $this->addWarning(
- [ 'apiwarn-errorprinterfailed-ex', $ex->getMessage() ], 'errorprinterfailed'
- );
}
if ( $failed ) {
$this->mPrinter = null;
* If an ApiUsageException, errors/warnings will be extracted from the
* embedded StatusValue.
*
- * If a base UsageException, the getMessageArray() method will be used to
- * extract the code and English message for a single error (no warnings).
- *
* Any other exception will be returned with a generic code and wrapper
* text around the exception's (presumably English) message as a single
* error (no warnings).
}
} elseif ( $type !== 'error' ) {
// None of the rest have any messages for non-error types
- } elseif ( $e instanceof UsageException ) {
- // User entered incorrect parameters - generate error response
- $data = Wikimedia\quietCall( [ $e, 'getMessageArray' ] );
- $code = $data['code'];
- $info = $data['info'];
- unset( $data['code'], $data['info'] );
- $messages[] = new ApiRawMessage( [ '$1', $info ], $code, $data );
} else {
// Something is seriously wrong
$config = $this->getConfig();
} else {
$path = null;
}
- if ( $e instanceof ApiUsageException || $e instanceof UsageException ) {
+ if ( $e instanceof ApiUsageException ) {
$link = wfExpandUrl( wfScript( 'api' ) );
$result->addContentValue(
$path,
return $this->mGoodTitles + $this->mMissingTitles;
}
- /**
- * Titles that were deemed invalid by Title::newFromText()
- * The array's index will be unique and negative for each item
- * @deprecated since 1.26, use self::getInvalidTitlesAndReasons()
- * @return string[] Array of strings (not Title objects)
- */
- public function getInvalidTitles() {
- wfDeprecated( __METHOD__, '1.26' );
- return array_map( function ( $t ) {
- return $t['title'];
- }, $this->mInvalidTitles );
- }
-
/**
* Titles that were deemed invalid by Title::newFromText()
* The array's index will be unique and negative for each item
* @return void
*/
public function showHiddenUsersAddBlockInfo( $showBlockInfo ) {
+ $db = $this->getDB();
+
$this->addTables( 'ipblocks' );
$this->addJoinConds( [
- 'ipblocks' => [ 'LEFT JOIN', 'ipb_user=user_id' ],
+ 'ipblocks' => [ 'LEFT JOIN', [
+ 'ipb_user=user_id',
+ 'ipb_expiry > ' . $db->addQuotes( $db->timestamp() ),
+ ] ],
] );
$this->addFields( 'ipb_deleted' );
if ( isset( $params['badfilecontexttitle'] ) ) {
$badFileContextTitle = Title::newFromText( $params['badfilecontexttitle'] );
if ( !$badFileContextTitle ) {
- $this->dieUsage( 'Invalid title in badfilecontexttitle parameter', 'invalid-title' );
+ $p = $this->getModulePrefix();
+ $this->dieWithError( [ 'apierror-bad-badfilecontexttitle', $p ], 'invalid-title' );
}
} else {
$badFileContextTitle = false;
$paramList = $h->parseParamString( $otherParams );
if ( !$paramList ) {
- // Just set a warning (instead of dieUsage), as in many cases
+ // Just set a warning (instead of dieWithError), as in many cases
// we could still render the image using width and height parameters,
// and this type of thing could happen between different versions of
// handlers.
return null; // force a continuation
}
+ $detailLevel = $this->params['testactionsdetail'];
+ $rigor = $detailLevel === 'quick' ? 'quick' : 'secure';
+ $errorFormatter = $this->getErrorFormatter();
+ if ( $errorFormatter->getFormat() === 'bc' ) {
+ // Eew, no. Use a more modern format here.
+ $errorFormatter = $errorFormatter->newWithFormat( 'plaintext' );
+ }
+
$user = $this->getUser();
$pageInfo['actions'] = [];
foreach ( $this->params['testactions'] as $action ) {
$this->countTestedActions++;
- $pageInfo['actions'][$action] = $title->userCan( $action, $user );
+
+ if ( $detailLevel === 'boolean' ) {
+ $pageInfo['actions'][$action] = $title->userCan( $action, $user );
+ } else {
+ $pageInfo['actions'][$action] = $errorFormatter->arrayFromStatus( $this->errorArrayToStatus(
+ $title->getUserPermissionsErrors( $action, $user, $rigor ),
+ $user
+ ) );
+ }
}
}
// need to be added to getCacheMode()
],
ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
+ ApiBase::PARAM_DEPRECATED_VALUES => [
+ 'readable' => true, // Since 1.32
+ ],
],
'testactions' => [
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_ISMULTI => true,
],
+ 'testactionsdetail' => [
+ ApiBase::PARAM_TYPE => [ 'boolean', 'full', 'quick' ],
+ ApiBase::PARAM_DFLT => 'boolean',
+ ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
+ ],
'token' => [
ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_ISMULTI => true,
$result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'item' );
}
- /**
- * @deprecated since 1.25 Use LogFormatter::formatParametersForApi instead
- * @param ApiResult $result
- * @param array &$vals
- * @param string $params
- * @param string $type
- * @param string $action
- * @param string $ts
- * @param bool $legacy
- * @return array
- */
- public static function addLogParams( $result, &$vals, $params, $type,
- $action, $ts, $legacy = false
- ) {
- wfDeprecated( __METHOD__, '1.25' );
-
- $entry = new ManualLogEntry( $type, $action );
- $entry->setParameters( $params );
- $entry->setTimestamp( $ts );
- $entry->setLegacy( $legacy );
- $formatter = LogFormatter::newFromEntry( $entry );
- $vals['params'] = $formatter->formatParametersForApi();
-
- return $vals;
- }
-
private function extractRowInfo( $row ) {
$logEntry = DatabaseLogEntry::newFromRow( $row );
$vals = [
* If possible, use ApiBase::dieWithError() instead of throwing this directly.
*
* @ingroup API
- * @note This currently extends UsageException for backwards compatibility, so
- * all the existing code that catches UsageException won't break when stuff
- * starts throwing ApiUsageException. Eventually UsageException will go away
- * and this will (probably) extend MWException directly.
*/
-class ApiUsageException extends UsageException implements ILocalizedException {
+class ApiUsageException extends MWException implements ILocalizedException {
protected $modulePath;
protected $status;
// customized by the local wiki.
$enMsg = clone $this->getApiMessage();
$enMsg->inLanguage( 'en' )->useDatabase( false );
- parent::__construct(
- ApiErrorFormatter::stripMarkup( $enMsg->text() ),
- $enMsg->getApiCode(),
- $httpCode,
- $enMsg->getApiData()
- );
+ parent::__construct( ApiErrorFormatter::stripMarkup( $enMsg->text() ), $httpCode );
}
/**
return $this->status;
}
- /**
- * @deprecated Do not use. This only exists here because UsageException is in
- * the inheritance chain for backwards compatibility.
- * @inheritDoc
- */
- public function getCodeString() {
- wfDeprecated( __METHOD__, '1.29' );
- return $this->getApiMessage()->getApiCode();
- }
-
- /**
- * @deprecated Do not use. This only exists here because UsageException is in
- * the inheritance chain for backwards compatibility.
- * @inheritDoc
- */
- public function getMessageArray() {
- wfDeprecated( __METHOD__, '1.29' );
- $enMsg = clone $this->getApiMessage();
- $enMsg->inLanguage( 'en' )->useDatabase( false );
-
- return [
- 'code' => $enMsg->getApiCode(),
- 'info' => ApiErrorFormatter::stripMarkup( $enMsg->text() ),
- ] + $enMsg->getApiData();
- }
-
/**
* @inheritDoc
*/
+++ /dev/null
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * This exception will be thrown when dieUsage is called to stop module execution.
- *
- * @ingroup API
- * @deprecated since 1.29, use ApiUsageException instead
- */
-class UsageException extends MWException {
-
- private $mCodestr;
-
- /**
- * @var null|array
- */
- private $mExtraData;
-
- /**
- * @param string $message
- * @param string $codestr
- * @param int $code
- * @param array|null $extradata
- */
- public function __construct( $message, $codestr, $code = 0, $extradata = null ) {
- parent::__construct( $message, $code );
- $this->mCodestr = $codestr;
- $this->mExtraData = $extradata;
-
- if ( !$this instanceof ApiUsageException ) {
- wfDeprecated( __METHOD__, '1.29' );
- }
-
- // This should never happen, so throw an exception about it that will
- // hopefully get logged with a backtrace (T138585)
- if ( !is_string( $codestr ) || $codestr === '' ) {
- throw new InvalidArgumentException( 'Invalid $codestr, was ' .
- ( $codestr === '' ? 'empty string' : gettype( $codestr ) )
- );
- }
- }
-
- /**
- * @return string
- */
- public function getCodeString() {
- wfDeprecated( __METHOD__, '1.29' );
- return $this->mCodestr;
- }
-
- /**
- * @return array
- */
- public function getMessageArray() {
- wfDeprecated( __METHOD__, '1.29' );
- $result = [
- 'code' => $this->mCodestr,
- 'info' => $this->getMessage()
- ];
- if ( is_array( $this->mExtraData ) ) {
- $result = array_merge( $result, $this->mExtraData );
- }
-
- return $result;
- }
-
- /**
- * @return string
- */
- public function __toString() {
- return "{$this->getCodeString()}: {$this->getMessage()}";
- }
-}
"apierror-assertnameduserfailed": "تأكيد أن المستخدم \"$1\" فشل.",
"apierror-assertuserfailed": "التأكيد على فشل تسجيل الدخول للمستخدم.",
"apierror-autoblocked": "عنوان الآيبي الخاص بك تم منعه تلقائيا; لأنه تم استخدامه بواسطة مستخدم ممنوع.",
+ "apierror-bad-badfilecontexttitle": "عنوان غير صالح في الوسيط <var>$1badfilecontexttitle</var>.",
"apierror-badconfig-resulttoosmall": "إن قيمة <code>$wgAPIMaxResultSize</code> في هذا الويكي صغيرة جدا لا تحتوي على معلومات النتائج الأساسية.",
"apierror-badcontinue": "متابعة غير صحيحة; يجب عليك تمرير القيمة الأصلية التي تم إرجاعها بواسطة الاستعلام السابق.",
"apierror-baddiff": "لا يمكن استرجاع الفرقك; إحدى أو كلا المراجعتين غير موجودة أو ليست لديك صلاحية لعرضها.",
"apiwarn-deprecation-withreplacement": "تم إيقاف <kbd>$1</kbd>; الرجاء استخدام <kbd>$2</kbd> بدلا من ذلك.",
"apiwarn-difftohidden": "لا يمكنك إجراء مقارنة مع r$1: المحتوى مخفي.",
"apiwarn-errorprinterfailed": "فشل خطأ الطباعة; سوف يعيد دون وسائط.",
- "apiwarn-errorprinterfailed-ex": "فشل خطأ الطباعة (سوف يعيد دون وسائط): $1.",
"apiwarn-ignoring-invalid-templated-value": "تجاهل القيمة <kbd>$2</kbd> في <var>$1</var> عند معالجة وسائط القوالب.",
"apiwarn-invalidcategory": "\"$1\" ليس تصنيفا.",
"apiwarn-invalidtitle": "\"$1\" ليس عنوانا صالحا.",
"apihelp-compare-paramvalue-prop-diff": "Das Unterschieds-HTML.",
"apihelp-compare-paramvalue-prop-diffsize": "Die Größe des Unterschieds-HTML in Bytes.",
"apihelp-compare-paramvalue-prop-title": "Die Seitentitel der Versionen „Von“ und „Nach“.",
+ "apihelp-compare-paramvalue-prop-size": "Die Größe der Versionen „from“ und „to“.",
"apihelp-compare-example-1": "Unterschied zwischen Version 1 und 2 abrufen",
"apihelp-createaccount-summary": "Erstellt ein neues Benutzerkonto.",
"apihelp-createaccount-param-preservestate": "Falls <kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd> für <samp>hasprimarypreservedstate</samp> wahr ausgegeben hat, sollten Anfragen, die als <samp>primary-required</samp> markiert wurden, ausgelassen werden. Falls ein nicht-leerer Wert für <samp>preservedusername</samp> zurückgegeben wurde, muss dieser Benutzername für den Parameter <var>username</var> verwendet werden.",
"apihelp-query+allrevisions-param-generatetitles": "Wenn als Generator verwendet, werden eher Titel als Bearbeitungs-IDs erzeugt.",
"apihelp-query+allrevisions-example-user": "Liste die letzten 50 Beiträge, sortiert nach Benutzer <kbd>Beispiel</kbd> auf.",
"apihelp-query+allrevisions-example-ns-main": "Liste die ersten 50 Bearbeitungen im Hauptnamensraum auf.",
+ "apihelp-query+mystashedfiles-summary": "Ruft eine Liste der Dateien im aktuellen Benutzeruploadspeicher ab.",
"apihelp-query+mystashedfiles-param-prop": "Welche Eigenschaften für die Dateien abgerufen werden sollen.",
"apihelp-query+mystashedfiles-paramvalue-prop-size": "Ruft die Dateigröße und Bildabmessungen ab.",
"apihelp-query+mystashedfiles-paramvalue-prop-type": "Ruft den MIME- und Medientyp der Datei ab.",
"apihelp-query+filearchive-paramvalue-prop-archivename": "Fügt den Dateinamen der Archivversion für die nicht-neuesten Versionen hinzu.",
"apihelp-query+filearchive-example-simple": "Eine Liste aller gelöschten Dateien auflisten",
"apihelp-query+filerepoinfo-summary": "Gebe Metainformationen über Bild-Repositorien zurück, die im Wiki eingerichtet sind.",
+ "apihelp-query+filerepoinfo-paramvalue-prop-displayname": "Der menschenlesbare Name des Repositoriumwikis.",
"apihelp-query+filerepoinfo-paramvalue-prop-local": "Ob dieses Repositorium das lokale ist oder nicht.",
"apihelp-query+filerepoinfo-paramvalue-prop-rootUrl": "Wurzel-URL-Pfad für Bildpfade.",
"apihelp-query+filerepoinfo-paramvalue-prop-scriptDirUrl": "Wurzel-URL-Pfad für die MediaWiki-Installation des Repositoriumwikis.",
"apihelp-query+info-paramvalue-prop-notificationtimestamp": "Der Beobachtungslisten-Benachrichtigungs-Zeitstempel jeder Seite.",
"apihelp-query+info-paramvalue-prop-subjectid": "Die Seitenkennung der Elternseite jeder Diskussionsseite.",
"apihelp-query+info-paramvalue-prop-readable": "Ob der Benutzer diese Seite betrachten darf.",
+ "apihelp-query+info-paramvalue-prop-preload": "Gibt den Text aus, der von EditFormPreloadText zurückgegeben wurde.",
"apihelp-query+info-paramvalue-prop-displaytitle": "Gibt die Art und Weise an, in der der Seitentitel tatsächlich angezeigt wird.",
"apihelp-query+info-paramvalue-prop-varianttitles": "Gibt den Anzeigetitel in allen Varianten der Sprache des Websiteinhalts aus.",
"apihelp-query+info-param-testactions": "Überprüft, ob der aktuelle Benutzer gewisse Aktionen auf der Seite ausführen kann.",
"apihelp-query+iwlinks-param-prefix": "Gibt nur Interwiki-Links mit diesem Präfix zurück.",
"apihelp-query+iwlinks-param-dir": "Die Auflistungsrichtung.",
"apihelp-query+iwlinks-example-simple": "Ruft die Interwikilinks von der Seite <kbd>Hauptseite</kbd> ab.",
+ "apihelp-query+langbacklinks-summary": "Findet alle Seiten, die auf den angegebenen Sprachlink verlinken.",
"apihelp-query+langbacklinks-param-lang": "Sprache für den Sprachlink.",
"apihelp-query+langbacklinks-param-limit": "Wie viele Gesamtseiten zurückgegeben werden sollen.",
"apihelp-query+langbacklinks-param-prop": "Zurückzugebende Eigenschaften:",
+ "apihelp-query+langbacklinks-paramvalue-prop-lllang": "Ergänzt den Sprachcode des Sprachlinks.",
"apihelp-query+langbacklinks-paramvalue-prop-lltitle": "Ergänzt den Titel des Sprachlinks.",
"apihelp-query+langbacklinks-param-dir": "Die Auflistungsrichtung.",
"apihelp-query+langbacklinks-example-simple": "Ruft Seiten ab, die auf [[:fr:Test]] verlinken.",
+ "apihelp-query+langlinks-summary": "Gibt alle Interlanguagelinks von den angegebenen Seiten zurück.",
"apihelp-query+langlinks-param-limit": "Wie viele Sprachlinks zurückgegeben werden sollen.",
"apihelp-query+langlinks-param-prop": "Zusätzlich zurückzugebende Eigenschaften jedes Interlanguage-Links:",
"apihelp-query+langlinks-paramvalue-prop-url": "Ergänzt die vollständige URL.",
"apihelp-query+linkshere-paramvalue-prop-pageid": "Die Seitenkennung jeder Seite.",
"apihelp-query+linkshere-paramvalue-prop-title": "Titel jeder Seite.",
"apihelp-query+linkshere-paramvalue-prop-redirect": "Markieren, falls die Seite eine Weiterleitung ist.",
+ "apihelp-query+linkshere-param-namespace": "Nur Seiten in diesen Namensräumen einschließen.",
"apihelp-query+linkshere-param-limit": "Wie viel zurückgegeben werden soll.",
"apihelp-query+linkshere-example-simple": "Holt eine Liste von Seiten, die auf [[Main Page]] verlinken.",
"apihelp-query+logevents-summary": "Ruft Ereignisse von Logbüchern ab.",
"apihelp-query+logevents-paramvalue-prop-user": "Ergänzt den verantwortlichen Benutzer für das Logbuchereignis.",
"apihelp-query+logevents-paramvalue-prop-timestamp": "Ergänzt den Zeitstempel des Logbucheintrags.",
"apihelp-query+logevents-paramvalue-prop-comment": "Ergänzt den Kommentar des Logbuchereignisses.",
+ "apihelp-query+logevents-paramvalue-prop-parsedcomment": "Ergänzt den geparsten Kommentar des Logbuchereignisses.",
"apihelp-query+logevents-paramvalue-prop-details": "Listet zusätzliche Einzelheiten über das Logbuchereignis auf.",
"apihelp-query+logevents-paramvalue-prop-tags": "Listet Markierungen für das Logbuchereignis auf.",
"apihelp-query+logevents-param-type": "Filtert nur Logbucheinträge mit diesem Typ heraus.",
"api-help-right-apihighlimits": "Höhere Beschränkungen in API-Anfragen verwenden (langsame Anfragen: $1; schnelle Anfragen: $2). Die Beschränkungen für langsame Anfragen werden auch auf Mehrwertparameter angewandt.",
"api-help-open-in-apisandbox": "<small>[in Spielwiese öffnen]</small>",
"api-help-authmanagerhelper-messageformat": "Zu verwendendes Format zur Rückgabe von Nachrichten.",
+ "apierror-bad-badfilecontexttitle": "Ungültiger Titel im Parameter <var>$1badfilecontexttitle</var>.",
"apierror-badgenerator-unknown": "<kbd>generator=$1</kbd> unbekannt.",
"apierror-badip": "Der IP-Parameter ist nicht gültig.",
"apierror-badmd5": "Die angegebene MD5-Prüfsumme war falsch.",
"apihelp-query+info-paramvalue-prop-notificationtimestamp": "The watchlist notification timestamp of each page.",
"apihelp-query+info-paramvalue-prop-subjectid": "The page ID of the parent page for each talk page.",
"apihelp-query+info-paramvalue-prop-url": "Gives a full URL, an edit URL, and the canonical URL for each page.",
- "apihelp-query+info-paramvalue-prop-readable": "Whether the user can read this page.",
+ "apihelp-query+info-paramvalue-prop-readable": "Whether the user can read this page. Use <kbd>intestactions=read</kbd> instead.",
"apihelp-query+info-paramvalue-prop-preload": "Gives the text returned by EditFormPreloadText.",
"apihelp-query+info-paramvalue-prop-displaytitle": "Gives the manner in which the page title is actually displayed.",
"apihelp-query+info-paramvalue-prop-varianttitles": "Gives the display title in all variants of the site content language.",
"apihelp-query+info-param-testactions": "Test whether the current user can perform certain actions on the page.",
+ "apihelp-query+info-param-testactionsdetail": "Detail level for <var>$1testactions</var>. Use the [[Special:ApiHelp/main|main module]]'s <var>errorformat</var> and <var>errorlang</var> parameters to control the format of the messages returned.",
+ "apihelp-query+info-paramvalue-testactionsdetail-boolean": "Return a boolean value for each action.",
+ "apihelp-query+info-paramvalue-testactionsdetail-full": "Return messages describing why the action is disallowed, or an empty array if it is allowed.",
+ "apihelp-query+info-paramvalue-testactionsdetail-quick": "Like <kbd>full</kbd> but skipping expensive checks.",
"apihelp-query+info-param-token": "Use [[Special:ApiHelp/query+tokens|action=query&meta=tokens]] instead.",
"apihelp-query+info-example-simple": "Get information about the page <kbd>Main Page</kbd>.",
"apihelp-query+info-example-protection": "Get general and protection information about the page <kbd>Main Page</kbd>.",
"apierror-assertnameduserfailed": "Assertion that the user is \"$1\" failed.",
"apierror-assertuserfailed": "Assertion that the user is logged in failed.",
"apierror-autoblocked": "Your IP address has been blocked automatically, because it was used by a blocked user.",
+ "apierror-bad-badfilecontexttitle": "Invalid title in <var>$1badfilecontexttitle</var> parameter.",
"apierror-badconfig-resulttoosmall": "The value of <code>$wgAPIMaxResultSize</code> on this wiki is too small to hold basic result information.",
"apierror-badcontinue": "Invalid continue param. You should pass the original value returned by the previous query.",
"apierror-baddiff": "The diff cannot be retrieved. One or both revisions do not exist or you do not have permission to view them.",
"apiwarn-deprecation-withreplacement": "<kbd>$1</kbd> has been deprecated. Please use <kbd>$2</kbd> instead.",
"apiwarn-difftohidden": "Couldn't diff to r$1: content is hidden.",
"apiwarn-errorprinterfailed": "Error printer failed. Will retry without params.",
- "apiwarn-errorprinterfailed-ex": "Error printer failed (will retry without params): $1",
"apiwarn-ignoring-invalid-templated-value": "Ignoring value <kbd>$2</kbd> in <var>$1</var> when processing templated parameters.",
"apiwarn-invalidcategory": "\"$1\" is not a category.",
"apiwarn-invalidtitle": "\"$1\" is not a valid title.",
"apierror-assertnameduserfailed": "La vérification que l’utilisateur est « $1 » a échoué.",
"apierror-assertuserfailed": "La vérification que l’utilisateur est connecté a échoué.",
"apierror-autoblocked": "Votre adresse IP a été bloquée automatiquement, parce qu’elle a été utilisée par un utilisateur bloqué.",
+ "apierror-bad-badfilecontexttitle": "Titre invalide dans le paramètre <var>$1badfilecontexttitle</var> .",
"apierror-badconfig-resulttoosmall": "La valeur de <code>$wgAPIMaxResultSize</code> sur ce wiki est trop petite pour contenir des informations de résultat basiques.",
"apierror-badcontinue": "Paramètre de continuation non valide. Vous devez passer la valeur d’origine renvoyée par la requête précédente.",
"apierror-baddiff": "La différence ne peut être récupérée. Une ou les deux révisions n’existent pas ou vous n’avez pas le droit de les voir.",
"apiwarn-deprecation-withreplacement": "<kbd>$1</kbd> est désuet. Veuillez utiliser <kbd>$2</kbd> à la place.",
"apiwarn-difftohidden": "Impossible de faire un diff avec r$1 : le contenu est masqué.",
"apiwarn-errorprinterfailed": "Erreur échec imprimante. Nouvel essai sans paramètres.",
- "apiwarn-errorprinterfailed-ex": "Erreur d’échec de l’impression (réessayera sans paramètres) : $1",
"apiwarn-ignoring-invalid-templated-value": "Ignorer la valeur <kbd>$2</kbd> dans <var>$1</var> en traitant les paramètres de modèle.",
"apiwarn-invalidcategory": "« $1 » n'est pas une catégorie.",
"apiwarn-invalidtitle": "« $1 » n’est pas un titre valide.",
"apiwarn-deprecation-withreplacement": "<kbd>$1</kbd> מיושן. יש להשתמש ב־<kbd>$2</kbd> במקום זה.",
"apiwarn-difftohidden": "לא היה אפשר לעשות השוואה עם גרסה $1: התוכן מוסתר.",
"apiwarn-errorprinterfailed": "מדפיס השגיאות לא עבד. ינסה שוב ללא פרמטרים.",
- "apiwarn-errorprinterfailed-ex": "מדפיס השגיאות לא עבד (ינסה שוב ללא פרמטרים): $1",
"apiwarn-ignoring-invalid-templated-value": "לא ייעשה שימוש בערך <kbd>$2</kbd> שבפרמטר <var>$1</var> בעת עיבוד הפרמטרים בתבנית.",
"apiwarn-invalidcategory": "\"$1\" אינה קטגוריה.",
"apiwarn-invalidtitle": "\"$1\" אינה כותרת תקינה.",
"api-help-authmanagerhelper-returnurl": "URL di ritorno per i flussi di autenticazione di terze parti, deve essere assoluto. E' necessario fornirlo, oppure va fornito <var>$1continue</var>.\n\nAlla ricezione di una risposta <samp>REDIRECT</samp>, in genere si apre un browser o una vista web all'URL specificato <samp>redirecttarget</samp> per un flusso di autenticazione di terze parti. Quando questo è completato, la terza parte invierà il browser o la vista web a questo URL. Dovresti estrarre qualsiasi parametro POST o della richiesta dall'URL e passarli come un request <var>$1continue</var> a questo modulo API.",
"api-help-authmanagerhelper-continue": "Questa richiesta è una continuazione dopo una precedente risposta <samp>UI</samp> o <samp>REDIRECT</samp>. È necessario fornirlo, oppure fornire <var>$1returnurl</var>.",
"api-help-authmanagerhelper-additional-params": "Questo modulo accetta parametri aggiuntivi a seconda delle richieste di autenticazione disponibili. Utilizza <kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd> con <kbd>amirequestsfor=$1</kbd> (o una precedente risposta da questo modulo, se applicabile) per determinare le richieste disponibili e i campi usati da queste.",
+ "apierror-bad-badfilecontexttitle": "Titolo non valido nel parametro <var>$1badfilecontexttitle</var>.",
"apierror-compare-notext": "Il parametro <var>$1</var> non può essere usato senza <var>$2</var>.",
"apierror-invalidoldimage": "Il parametro <var>oldimage</var> ha un formato non valido.",
"apierror-invaliduserid": "L'ID utente <var>$1</var> non è valido.",
"apierror-assertnameduserfailed": "사용자의 \"$1\" 지정 표명이 실패했습니다.",
"apierror-assertuserfailed": "사용자의 로그인 실패 표명이 발생했습니다.",
"apierror-autoblocked": "사용자의 IP 주소는 차단된 사용자에 의해 사용되었으므로 자동으로 차단된 상태입니다.",
+ "apierror-bad-badfilecontexttitle": "<var>$1badfilecontexttitle</var> 변수에 유효하지 않은 제목이 있습니다.",
"apierror-badgenerator-unknown": "알 수 없는 <kbd>generator=$1</kbd>.",
"apierror-badip": "IP 변수가 유효하지 않습니다.",
"apierror-badmd5": "제공된 MD5 해시가 잘못되었습니다.",
"apierror-assertnameduserfailed": "Afirmação de que o usuário é \"$1\" falhou.",
"apierror-assertuserfailed": "Afirmação de que o usuário está logado falhou.",
"apierror-autoblocked": "O seu endereço de IP foi bloqueado automaticamente, porque ele foi usado por um usuário bloqueado.",
+ "apierror-bad-badfilecontexttitle": "Título inválido no parâmetro <var>$1badfilecontexttitle</var>.",
"apierror-badconfig-resulttoosmall": "O valor de <code>$wgAPIMaxResultSize</code> nesta wiki é muito pequeno para manter a informação básica de resultados.",
"apierror-badcontinue": "Parâmetro continue inválido. Você deve passar o valor original retornado pela consulta anterior.",
"apierror-baddiff": "O diff não pode ser recuperado. Uma ou ambas as revisões não existem ou você não tem permissão para visualizá-las.",
"apiwarn-deprecation-withreplacement": "<kbd>$1</kbd> está obsoleto. Por favor, use <kbd>$2</kbd> em vez.",
"apiwarn-difftohidden": "Não foi possível diferenciar r$1: o conteúdo está oculto.",
"apiwarn-errorprinterfailed": "Falha na impressora de erro. Repetirá sem parâmetros.",
- "apiwarn-errorprinterfailed-ex": "Falha na impressora de erro (repetirá sem parâmetros): $1",
"apiwarn-ignoring-invalid-templated-value": "Ignorando o valor <kbd>$2</kbd> em <var>$1</var> ao processar parâmetros de predefinição.",
"apiwarn-invalidcategory": "\"$1\" não é uma categoria.",
"apiwarn-invalidtitle": "\"$1\" não é um título válido.",
"apiwarn-deprecation-withreplacement": "<kbd>$1</kbd> foi descontinuado. Em substituição, use <kbd>$2</kbd>, por favor.",
"apiwarn-difftohidden": "Não foi possível criar uma lista das diferenças em relação à r$1: o conteúdo está ocultado.",
"apiwarn-errorprinterfailed": "A impressora de erros falhou. Será feita nova tentativa sem parâmetros.",
- "apiwarn-errorprinterfailed-ex": "A impressora de erros falhou (será feita nova tentativa sem parâmetros): $1",
"apiwarn-ignoring-invalid-templated-value": "A ignorar o valor <kbd>$2</kbd> em <var>$1</var> ao processar parâmetros modelados.",
"apiwarn-invalidcategory": "\"$1\" não é uma categoria.",
"apiwarn-invalidtitle": "\"$1\" não é um título válido.",
"apihelp-query+info-paramvalue-prop-displaytitle": "{{doc-apihelp-paramvalue|query+info|prop|displaytitle}}",
"apihelp-query+info-paramvalue-prop-varianttitles": "{{doc-apihelp-paramvalue|query+info|prop|varianttitles}}",
"apihelp-query+info-param-testactions": "{{doc-apihelp-param|query+info|testactions}}",
+ "apihelp-query+info-param-testactionsdetail": "{{doc-apihelp-param|query+info|testactionsdetail}}",
+ "apihelp-query+info-paramvalue-testactionsdetail-boolean": "{{doc-apihelp-paramvalue|query+info|testactionsdetail|boolean}}",
+ "apihelp-query+info-paramvalue-testactionsdetail-full": "{{doc-apihelp-paramvalue|query+info|testactionsdetail|full}}",
+ "apihelp-query+info-paramvalue-testactionsdetail-quick": "{{doc-apihelp-paramvalue|query+info|testactionsdetail|quick}}",
"apihelp-query+info-param-token": "{{doc-apihelp-param|query+info|token}}",
"apihelp-query+info-example-simple": "{{doc-apihelp-example|query+info}}",
"apihelp-query+info-example-protection": "{{doc-apihelp-example|query+info}}",
"apierror-assertnameduserfailed": "{{doc-apierror}}\n\nParameters:\n* $1 - User name passed in.",
"apierror-assertuserfailed": "{{doc-apierror}}",
"apierror-autoblocked": "{{doc-apierror}}",
+ "apierror-bad-badfilecontexttitle": "{{doc-apierror}}\n\nParameters:\n* $1 - Module parameter prefix, e.g. \"bl\".",
"apierror-badconfig-resulttoosmall": "{{doc-apierror}}",
"apierror-badcontinue": "{{doc-apierror}}",
"apierror-baddiff": "{{doc-apierror}}",
"apiwarn-deprecation-withreplacement": "{{doc-apierror}}\n\nParameters:\n* $1 - Query string fragment that is deprecated, e.g. \"action=tokens\".\n* $2 - Query string fragment to use instead, e.g. \"action=tokens\".",
"apiwarn-difftohidden": "{{doc-apierror}}\n\nParameters:\n* $1 - Revision ID number.\n\n\"r\" is short for \"revision\". You may translate it.",
"apiwarn-errorprinterfailed": "{{doc-apierror}}",
- "apiwarn-errorprinterfailed-ex": "{{doc-apierror}}\n\nParameters:\n* $1 - Exception message, which may already end in punctuation. Probably in English.",
"apiwarn-ignoring-invalid-templated-value": "{{doc-apierror}}\n\nParameters:\n* $1 - Target parameter having a bad value.\n* $2 - The bad value being ignored.",
"apiwarn-invalidcategory": "{{doc-apierror}}\n\nParameters:\n* $1 - Supplied category name.",
"apiwarn-invalidtitle": "{{doc-apierror}}\n\nParameters:\n* $1 - Supplied title.",
"apierror-assertnameduserfailed": "Проверка того, что участник — «$1», провалилась.",
"apierror-assertuserfailed": "Проверка того, что участник авторизован, провалилась.",
"apierror-autoblocked": "Ваш IP-адрес был автоматически заблокирован, потому что он был использован заблокированным участником.",
+ "apierror-bad-badfilecontexttitle": "Неверное название в параметре <var>$1badfilecontexttitle</var>.",
"apierror-badconfig-resulttoosmall": "Значение <code>$wgAPIMaxResultSize</code> этой вики слишком мало, чтобы вместить базовую информацию о результате.",
"apierror-badcontinue": "Некорректный параметр continue. Вы должны передать значение, возвращённое предыдущим запросом.",
"apierror-baddiff": "Сравнение версий не может быть проведено. Одна или обе версии не существуют или у вас не достаточно прав чтобы просматривать их.",
"apiwarn-deprecation-withreplacement": "<kbd>$1</kbd> не поддерживается. Пожалуйста, используйте <kbd>$2</kbd>.",
"apiwarn-difftohidden": "Невозможно сравнить с r$1: содержимое скрыто.",
"apiwarn-errorprinterfailed": "Сборщик ошибок упал. Будет совершена повторная попытка без параметров.",
- "apiwarn-errorprinterfailed-ex": "Сборщик ошибок упал (будет совершена повторная попытка без параметров): $1",
"apiwarn-ignoring-invalid-templated-value": "При обработке шаблонных параметров значение <kbd>$2</kbd> параметра <var>$1</var> проигнорировано.",
"apiwarn-invalidcategory": "«$1» не является категорией.",
"apiwarn-invalidtitle": "«$1» не является некорректным заголовком.",
"apiwarn-deprecation-withreplacement": "<kbd>$1</kbd> є застарілим. Будь ласка, використовуйте натомість <kbd>$2</kbd>.",
"apiwarn-difftohidden": "Не вдалося відкрити версію r$1: вміст приховано.",
"apiwarn-errorprinterfailed": "Невдача через помилку принтера. Буде здійснено повторну спробу без параметрів.",
- "apiwarn-errorprinterfailed-ex": "Невдача через помилку принтера (буде здійснено повторну спробу без параметрів): $1",
"apiwarn-invalidcategory": "«$1» не є категорією.",
"apiwarn-invalidtitle": "«$1» не є коректною назвою.",
"apiwarn-invalidxmlstylesheetext": "Таблиця стилів повинна мати розширення <code>.xsl</code>.",
"apiwarn-deprecation-withreplacement": "<kbd>$1</kbd>已弃用。请改用<kbd>$2</kbd>。",
"apiwarn-difftohidden": "不能与r$1做差异比较:内容被隐藏。",
"apiwarn-errorprinterfailed": "错误打印失败。将在没有参数的前提下重试。",
- "apiwarn-errorprinterfailed-ex": "错误打印失败(将在没有参数的前提下重试):$1",
"apiwarn-ignoring-invalid-templated-value": "当处理模板参数时,忽略<var>$1</var>中的值<kbd>$2</kbd>。",
"apiwarn-invalidcategory": "“$1”不是一个分类。",
"apiwarn-invalidtitle": "“$1”不是一个有效的标题。",
*/
protected $cache;
+ /**
+ * Map of (lowercase message key => index) for all software defined messages
+ *
+ * @var array
+ */
+ protected $overridable;
+
/**
* @var bool[] Map of (language code => boolean)
*/
return true;
}
+ $this->overridable = array_flip( Language::getMessageKeysFor( $code ) );
+
# 8 lines of code just to say (once) that message cache is disabled
if ( $this->mDisable ) {
static $shownDisabled = false;
$this->cache->getField( $code, 'HASH' )
);
} else {
- // Message page does not exist or does not override a software message.
- // Load the message page, utilizing the individual message cache.
- $entry = $this->loadCachedMessagePageEntry(
- $title,
- $code,
- $this->cache->getField( $code, 'HASH' )
- );
- if ( substr( $entry, 0, 1 ) !== ' ' ) {
+ // Message page either does not exist or does not override a software message
+ if ( !isset( $this->overridable[$this->contLang->lcfirst( $title )] ) ) {
+ // Message page does not override any software-defined message. A custom
+ // message might be defined to have content or settings specific to the wiki.
+ // Load the message page, utilizing the individual message cache as needed.
+ $entry = $this->loadCachedMessagePageEntry(
+ $title,
+ $code,
+ $this->cache->getField( $code, 'HASH' )
+ );
+ }
+ if ( $entry === null || substr( $entry, 0, 1 ) !== ' ' ) {
// Message does not have a MediaWiki page definition; try hook handlers
$message = false;
Hooks::run( 'MessagesPreLoad', [ $title, &$message, $code ] );
ini_set( 'apc.cache_by_default', $_apcEnabled );
Wikimedia\restoreWarnings();
+ $data = [];
if ( $_fileType == 'core' || $_fileType == 'extension' ) {
-
- // Lnguage files aren't required to contain all the possible variables, so suppress warnings
- // when variables don't exist in tests
- Wikimedia\suppressWarnings();
- $data = compact( self::$allKeys );
- Wikimedia\restoreWarnings();
+ foreach ( self::$allKeys as $key ) {
+ // Not all keys are set in language files, so
+ // check they exist first
+ if ( isset( $$key ) ) {
+ $data[$key] = $$key;
+ }
+ }
} elseif ( $_fileType == 'aliases' ) {
- $data = compact( 'aliases' );
+ if ( isset( $aliases ) ) {
+ /** @suppress PhanUndeclaredVariable */
+ $data['aliases'] = $aliases;
+ }
} else {
throw new MWException( __METHOD__ . ": Invalid file type: $_fileType" );
}
break;
};
}
- $conds['ct_tag_id'] = $filterTagIds;
+
+ if ( $filterTagIds !== [] ) {
+ $conds['ct_tag_id'] = $filterTagIds;
+ }
} else {
$conds['ct_tag'] = $filter_tag;
}
return $this->newNotOkStatus();
}
- $user = $importableRevision->getUserObj() ?: User::newFromName( $importableRevision->getUser() );
+ $user = $importableRevision->getUserObj()
+ ?: User::newFromName( $importableRevision->getUser(), false );
# Do the actual upload
if ( $archiveName ) {
if ( $count == 0 ) {
$lbFactory = $services->getDBLoadBalancerFactory();
$lbFactory->waitForReplication( [
- 'wiki' => wfWikiID(), 'timeout' => self::REPLICATION_WAIT_TIMEOUT ] );
+ 'domain' => $lbFactory->getLocalDomainID(),
+ 'timeout' => self::REPLICATION_WAIT_TIMEOUT
+ ] );
}
$this->db->insert( 'templatelinks',
[
}
// Bail out if there is too much DB lag.
// This check should not block as we want to try other wiki queues.
- list( , $maxLag ) = $lbFactory->getMainLB( wfWikiID() )->getMaxLag();
+ list( , $maxLag ) = $lbFactory->getMainLB()->getMaxLag();
if ( $maxLag >= self::MAX_ALLOWED_LAG ) {
$response['reached'] = 'replica-lag-limit';
return $response;
$syncThreshold = $this->config->get( 'JobSerialCommitThreshold' );
$time = false;
- $lb = $lbFactory->getMainLB( wfWikiID() );
+ $lb = $lbFactory->getMainLB();
if ( $syncThreshold !== false && $lb->getServerCount() > 1 ) {
// Generally, there is one master connection to the local DB
$dbwSerial = $lb->getAnyOpenConnection( $lb->getWriterIndex() );
if ( !isset( $this->params['range'] ) ) {
$lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
if ( !$lbFactory->waitForReplication( [
- 'wiki' => wfWikiID(),
+ 'domain' => $lbFactory->getLocalDomainID(),
'timeout' => self::LAG_WAIT_TIMEOUT
] ) ) { // only try so hard
$stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
$database = null;
}
+ if ( $schema === '' ) {
+ $schema = null;
+ }
+
return new self( $database, $schema, $prefix );
}
/**
* @param DatabaseDomain|string $other
- * @return bool
+ * @return bool Whether the domain instances are the same by value
*/
public function equals( $other ) {
- if ( $other instanceof DatabaseDomain ) {
+ if ( $other instanceof self ) {
return (
$this->database === $other->database &&
$this->schema === $other->schema &&
return ( $this->getId() === $other );
}
+ /**
+ * Check whether the domain $other meets the specifications of this domain
+ *
+ * If this instance has a null database specifier, then $other can have any database
+ * specified, including the null, and likewise if the schema specifier is null. This
+ * is not transitive like equals() since a domain that explicitly wants a certain
+ * database or schema cannot be satisfied by one of another (nor null). If the prefix
+ * is empty and the DB and schema are both null, then the entire domain is considered
+ * unspecified, and any prefix of $other is considered compatible.
+ *
+ * @param DatabaseDomain|string $other
+ * @return bool
+ * @since 1.32
+ */
+ public function isCompatible( $other ) {
+ if ( $this->isUnspecified() ) {
+ return true; // even the prefix doesn't matter
+ }
+
+ $other = ( $other instanceof self ) ? $other : self::newFromId( $other );
+
+ return (
+ ( $this->database === $other->database || $this->database === null ) &&
+ ( $this->schema === $other->schema || $this->schema === null ) &&
+ $this->prefix === $other->prefix
+ );
+ }
+
+ /**
+ * @return bool
+ * @since 1.32
+ */
+ public function isUnspecified() {
+ return (
+ $this->database === null && $this->schema === null && $this->prefix === ''
+ );
+ }
+
/**
* @return string|null Database name
*/
if ( $this->schema !== null ) {
$parts[] = $this->schema;
}
- if ( $this->prefix != '' ) {
+ if ( $this->prefix != '' || $this->schema !== null ) {
+ // If there is a schema, then we need the prefix to disambiguate.
+ // For engines like Postgres that use schemas, this awkwardness is hopefully
+ // avoided since it is easy to have one DB per server (to avoid having many users)
+ // and use schema/prefix to have wiki farms. For example, a domain schemes could be
+ // wiki-<project>-<language>, e.g. "wiki-fitness-es"/"wiki-sports-fr"/"wiki-news-en".
$parts[] = $this->prefix;
}
use EmptyBagOStuff;
use WANObjectCache;
use ArrayUtils;
+use UnexpectedValueException;
use InvalidArgumentException;
use RuntimeException;
use Exception;
}
}
+ // Final sanity check to make sure the right domain is selected
+ if (
+ $conn instanceof IDatabase &&
+ !$this->localDomain->isCompatible( $conn->getDomainID() )
+ ) {
+ throw new UnexpectedValueException(
+ "Got connection to '{$conn->getDomainID()}', " .
+ "but expected local domain ('{$this->localDomain}')." );
+ }
+
return $conn;
}
}
}
- // Increment reference count
if ( $conn instanceof IDatabase ) {
+ // Final sanity check to make sure the right domain is selected
+ if ( !$domainInstance->isCompatible( $conn->getDomainID() ) ) {
+ throw new UnexpectedValueException(
+ "Got connection to '{$conn->getDomainID()}', but expected '$domain'." );
+ }
+ // Increment reference count
$refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
$conn->setLBInfo( 'foreignPoolRefCount', $refCount + 1 );
}
$text = preg_replace_callback(
self::EDITSECTION_REGEX,
function ( $m ) {
- global $wgOut, $wgLang;
$editsectionPage = Title::newFromText( htmlspecialchars_decode( $m[1] ) );
$editsectionSection = htmlspecialchars_decode( $m[2] );
$editsectionContent = isset( $m[4] ) ? Sanitizer::decodeCharReferences( $m[3] ) : null;
throw new MWException( "Bad parser output text." );
}
- $skin = $wgOut->getSkin();
- return $skin->doEditSectionLink( $editsectionPage,
+ $context = RequestContext::getMain();
+ return $context->getSkin()->doEditSectionLink(
+ $editsectionPage,
$editsectionSection,
$editsectionContent,
- $wgLang
+ $context->getLanguage()
);
},
$text
$this->msg( 'apihelp-no-such-module', $moduleName )->inContentLanguage()->parse()
) );
return;
- } catch ( UsageException $ex ) {
- $this->getOutput()->addHTML( Html::rawElement( 'span', [ 'class' => 'error' ],
- $this->msg( 'apihelp-no-such-module', $moduleName )->inContentLanguage()->parse()
- ) );
- return;
}
ApiHelp::getHelp( $this->getContext(), $module, $options );
* @param int $userId
*/
public static function purge( $wikiId, $userId ) {
- $cache = ObjectCache::getMainWANInstance();
+ $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
$key = $cache->makeGlobalKey( 'user', 'id', $wikiId, $userId );
$cache->delete( $key );
}
* @return string
*/
protected function getCacheKey( WANObjectCache $cache ) {
- return $cache->makeGlobalKey( 'user', 'id', wfWikiID(), $this->mId );
+ $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+
+ return $cache->makeGlobalKey( 'user', 'id', $lbFactory->getLocalDomainID(), $this->mId );
}
/**
*/
protected $relativeInput;
- /**
- * @var bool
- */
- protected $noDatePicker;
-
/**
* @var bool
*/
public function __construct( Widget $relativeInput, array $options = [] ) {
$config = \RequestContext::getMain()->getConfig();
- $options['noDatePicker'] = $config->get( 'ExpiryWidgetNoDatePicker' );
-
parent::__construct( $options );
- $this->noDatePicker = $options['noDatePicker'];
$this->required = $options['required'] ?? false;
// Properties
$this->relativeInput->addClasses( [ 'mw-widget-ExpiryWidget-relative' ] );
// Initialization
- $classes = [
- 'mw-widget-ExpiryWidget',
- ];
- if ( $options['noDatePicker'] === false ) {
- $classes[] = 'mw-widget-ExpiryWidget-hasDatePicker';
- }
$this
- ->addClasses( $classes )
+ ->addClasses( [
+ 'mw-widget-ExpiryWidget',
+ 'mw-widget-ExpiryWidget-hasDatePicker'
+ ] )
->appendContent( $this->relativeInput );
}
* {@inheritdoc}
*/
public function getConfig( &$config ) {
- $config['noDatePicker'] = $this->noDatePicker;
$config['required'] = $this->required;
$config['relativeInput'] = [];
$this->relativeInput->getConfig( $config['relativeInput'] );
"right-editusercss": "Редактиране на CSS файловете на други потребители",
"right-edituserjson": "Редактиране на JSON файловете на други потребители",
"right-edituserjs": "Редактиране на JavaScript файловете на други потребители",
+ "right-editsitecss": "Редактиране на CSS за цялото уики",
+ "right-editsitejson": "Редактиране на JSON за цялото уики",
+ "right-editsitejs": "Редактиране на JavaScript за цялото уики",
"right-editmyusercss": "Редактиране на собствените потребителски CSS файлове",
"right-editmyuserjson": "Редактиране на собствените потребителски JSON файлове",
"right-editmyuserjs": "Редактиране на собствените потребителски JavaScript файлове",
"Fitoschido"
]
},
- "tog-underline": "ھێڵ ھێنان بەژێر بەستەرەکان:",
+ "tog-underline": "ھێڵھێنان بەژێر بەستەرەکان:",
"tog-hideminor": "دەستکارییە بچووکەکان لە دوایین گۆڕانکارییەکاندا بشارەوە",
"tog-hidepatrolled": "لە دوایین گۆڕانکارییەکاندا دەستکارییە پاس دراوەکان بشارەوە",
"tog-newpageshidepatrolled": "لە پێرستی پەڕە نوێکاندا پەڕە پاس دراوەکان بشارەوە",
"yourpasswordagain": "دیسان تێپەڕوشەکە بنووسەوە:",
"createacct-yourpasswordagain": "تێپەروشە پشتڕاست بکەرەوە",
"createacct-yourpasswordagain-ph": "تێپەروشە دیسان بنووسەوە",
- "userlogin-remembermypassword": "چوونەژوورەوەکەم ڕابگرە",
+ "userlogin-remembermypassword": "لەژوورەوە بمھێڵەرەوە",
"userlogin-signwithsecure": "پەیوەندیی دڵنیا بەکاربھێنە",
"cannotlogin-title": "ناتوانیت بچیتە ژوورەوە",
"cannotlogin-text": "توانای چوونەژوورەوەت نییە",
"wrongpasswordempty": "تێپەڕەوشەی لێدراو بەتاڵبوو.\nتکایە هەوڵ بدەوە.",
"passwordtooshort": "تێپەڕوشەکەت لانی کەم دەبێ {{PLURAL:$1|١ پیت|$1 پیت}} بێت.",
"passwordtoolong": "تێپەڕ وشەکان ناتوانرێت لە {{PLURAL:$1|١ کارەکتەر|$1 کارەکتەر}} درێژتر بێت.",
- "passwordtoopopular": "تێپەڕ وشە باوەکان ناتواندرێت دابنرێن. تکایە تێپەڕ وشەیەکی دەگمەنتر ھەڵبژێرە.",
+ "passwordtoopopular": "تێپەڕەوشە باوەکان ناکرێت دابنرێن. تکایە تێپەڕەوشەیەکی دەگمەنتر ھەڵبژێرە.",
"password-name-match": "تێپەڕوشەکەت ئەبێ جیاواز بێت لە ناوی بەکارهێنەریت.",
"password-login-forbidden": "بەکارهێنانی ئەم ناوی بەکارهێنەر و تێپەڕەووشەیە قەدەغەکراوە.",
"mailmypassword": "تێپەڕوشەکە ڕێک بخەوە",
"resetpass-temp-emailed": "تۆ بە تێپەڕوشەیەکی کاتیی ھاتوویتە ژوورەوە. بۆ تەواوکردنی چوونە ژوورەوە تێپەڕوشەیەکی نوێ لێرە دابنێ.",
"resetpass-temp-password": "تێپەڕوشەی کاتی:",
"resetpass-expired": "تێپەڕ وشەکەت بەسەر چووە، تکایە تێپەڕ وشەیەکی نوێ دابنێ بۆ چوونە ژوورەوە",
- "resetpass-validity-soft": "تێپەڕ وشەکەت دروست نییە: $1",
+ "resetpass-validity-soft": "تێپەڕ وشەکەت دروست نییە: $1\n\nتکایە تێپەڕەوشەیەکی نوێ ھەڵبژێرە ئێستا، یان کرتە بکە لە \"{{int:authprovider-resetpass-skip-label}}\" بۆ گۆڕینی لە دواییدا.",
"passwordreset": "ڕێکخستنەوەی تێپەڕوشە",
"passwordreset-text-one": "ئەم فۆرمە تەواو بکە بۆ بەدەستھێنانی تێپەڕ وشەیەکی کاتیی بە ئیمەیڵ",
"passwordreset-text-many": "{{PLURAL:$1|یەکێک لەم بۆشاییانە بڕ بکەرەوە بۆ بەدەستھێنانی تێپەڕ وشەیەکی کاتیی بە ئیمەیڵ}}",
"anonpreviewwarning": "«نەڕۆشتوویتە ژوورەوە. پاشەکەوتکردن، ئەدرەسی IPەکەت لە مێژووی دەستکاریی ئەم پەڕە تۆمار دەکات.»",
"missingsummary": "'''وە بیر خستنەوە:''' پوختەیەکت نەنووسیوە بۆ چۆنیەتی گۆڕانکارییەکەت.\nئەگەر جارێکی تر پاشکەوت کردن لێبدەی، بێ پوختە تۆمار دەکرێ.",
"selfredirect": "<strong>ئاگاداری:</strong> تۆ خەریکی گواستنەوەی ئەم پەڕەیەیت بۆ سەر خۆی. لەوانەیە خەریکی گواستنەوەی پەڕەیەکی ھەڵە بیت یان ھەوڵی گواستنەوە دەدەیت بۆ ئامانجێکی ھەڵە. \nئەگەر دووبارە کرتە لەسەر «$1» بکەیتەوە، ڕەوانەکەرەکە دروست دەکرێت بەھەرحاڵ.",
- "missingcommenttext": "تکاÛ\8cÛ\95 Ù\84Û\95 Ø®Ù\88ارÛ\95Ù\88Û\95 شرÛ\86Ú¤Û\95Û\8cÛ\95ک بنووسە.",
+ "missingcommenttext": "تکاÛ\8cÛ\95 Ù\84Û\8eدÙ\88اÙ\86Û\8eک بنووسە.",
"missingcommentheader": "'''بیرهێنانەوە:''' بۆ ئەم بۆچوونەت سەردێڕ\\بابەت ڕاچاو نەکردووە.\nئەگەر دیسان «$1» لێبدەی، دەستکاریەکەت بێ سەردێڕ یان بابەت پاشەکەوت دەبێ.",
"summary-preview": "پێشبینینی کورتەی دەستکاری:",
"subject-preview": "پێشبینینی بابەت:",
"revdelete-legend": "ڕێکخستنی سنووردارکردنی دیاریکردن",
"revdelete-hide-text": "دەقی پێداچوونەوە",
"revdelete-hide-image": "ناوەڕۆکی پەڕگە بشارەوە",
- "revdelete-hide-name": "داشاردÙ\86Û\8c Ù\85Û\95بÛ\95ست Ù\88 کردÛ\95Ù\88Û\95",
+ "revdelete-hide-name": "شاردÙ\86Û\95Ù\88Û\95Û\8c ئاÙ\85اÙ\86ج Ù\88 Ú\95اگÛ\95Û\8cÛ\8eÙ\86Û\95کاÙ\86",
"revdelete-hide-comment": "کورتەی دەستکاری",
"revdelete-hide-user": "ناوی بەکارھێنەر/ناونیشانی ئایپی دەستکاریکەر",
"revdelete-hide-restricted": "بەرگری دراوە لە بەڕێوبەران هەر وەک ئەوانی دیکە",
"recentchangesdays": "ژمارە ڕۆژە نیشاندراوەکان لە دوایین گۆڕانکارییەکان:",
"recentchangesdays-max": "(ئەوپەڕی $1 {{PLURAL:$1|ڕۆژە|ڕۆژە}})",
"recentchangescount": "ژمارەی گۆڕانکارییەکان کە نیشان ئەدرێن لە حاڵەتی دیفاڵت:",
- "prefs-help-recentchangescount": "ئÛ\95Ù\85Û\95 دÙ\88اÛ\8cÛ\8cÙ\86 Ú¯Û\86Ú\95اÙ\86کارÛ\8cÛ\8cÛ\95کاÙ\86Ø\8c Ù\85Û\8eÚ\98Ù\88Ù\88Û\8c Ù¾Û\95Ú\95Û\95کاÙ\86 Ù\88 Ù\84Û\86Ú¯Û\95کاÙ\86Û\8cØ´ Ù\84Û\95بÛ\95ردÛ\95گرÛ\8eت.",
+ "prefs-help-recentchangescount": "زÛ\86رترÛ\8cÙ\86 Ú\98Ù\85ارÛ\95: Ù¡Ù Ù Ù ",
"prefs-help-watchlist-token2": "ئەمە کلیلێکی تایبەتیی پێڕستی چاودێرییەکەتە. ھەرکەسێک بیزانێت دەتوانێت پێڕستی چاودێرییەکەت ببینێت، بۆیە لای خۆت بیپارێزە.\nئەگەر پێویستی کرد، [[Special:ResetTokens|دەتوانیت بیگۆڕیت]].",
"savedprefs": "ھەڵبژاردەکانت پاشەکەوت کران",
"timezonelegend": "ناوچەی کاتی:",
"prefs-files": "پەڕگەکان",
"prefs-custom-css": "CSSی دڵخواز",
"prefs-custom-js": "جاڤاسکریپتی دڵخواز",
- "prefs-common-config": "سیئێسئێس/جاڤاسکریپتی ھاوبەش بۆ گشت پێستەکان:",
+ "prefs-common-config": "سیئێسئێس/جەیسن/جاڤاسکریپتی ھاوبەش بۆ گشت پێستەکان:",
"prefs-reset-intro": "دەتوانی لەم لاپەڕە بۆ گەڕانەوەی هەڵبژاردەکانت بۆ بنچینەیی ماڵپەر کەڵک وەرگریت.\nگەر ئەوە بکەی ئیتر گۆڕانەکەت ناگەڕێتەوە.",
"prefs-emailconfirm-label": "پشتڕاستکردنەوەی ئیمەیل:",
"youremail": "ئیمەیل:",
"badsig": "کۆدی ئیمزای ھەڵە.\nبە تاگە HTMLکاندا بچۆرەوە.",
"badsiglength": "واژووەکەت زۆر درێژە.\nواژوو نابێ لە $1 {{PLURAL:$1|نووسە}} درێژتر بێت.",
"yourgender": "پێت خۆشە چۆن وەسف بکرێیت؟",
- "gender-unknown": "Ù¾Û\8eÙ\85 Ø®Û\86Ø´Û\95 باسÛ\8c Ù\86Û\95Ú©Û\95Ù\85",
+ "gender-unknown": "Ù\84Û\95کاتÛ\8c ئاÙ\85اÚ\98Û\95داÙ\86 بÛ\95تÛ\86Ø\8c Ù\86Û\95رÙ\85اÙ\85Û\8eرÛ\95Ú©Û\95 Ù\88Ø´Û\95Û\8c Ú\95Û\95Ú¯Û\95زھاÙ\88تا بÛ\95کار دÛ\95بات بÛ\95Ù¾Û\8eÛ\8c تÙ\88اÙ\86ا",
"gender-male": "نێر",
"gender-female": "مێ",
"prefs-help-gender": "ئەم ھەڵبژاردەیە دڵخوازانەیە.\nبۆ بانگکردن و ئاماژەپێکردن بە شێوەیەکی دروست لەلایەن نەرمامێرەوە بەکاردێت.\nئەم زانیارییە گشتی دەبێت.",
"group-sysop": "بەڕێوەبەران",
"group-interface-admin": "بەڕێوەبەرانی ڕووکار",
"group-bureaucrat": "بیوروکراتەکان",
- "group-suppress": "چاودێرەکان",
+ "group-suppress": "سەرکووتکەرەکان",
"group-all": "(ھەموو)",
"group-user-member": "{{GENDER:$1|بەکارھێنەر}}",
"group-autoconfirmed-member": "{{GENDER:$1|بەکارھێنەرانی پەسەندکراوی خۆگەڕ}}",
"badarticleerror": "Seda toimingut ei saa sellel leheküljel sooritada.",
"cannotdelete": "Lehekülge või faili \"$1\" ei saa kustutada.\nVõimalik, et keegi on selle juba kustutanud.",
"cannotdelete-title": "Lehekülge \"$1\" ei saa kustutada",
+ "delete-scheduled": "Lehekülje \"$1\" kustutamine ootab järge.\nPalun ole kannatlik.",
"delete-hook-aborted": "Haak katkestas kustutamise.\nSeletust pole toodud.",
"no-null-revision": "Lehekülje \"$1\" nullredaktsiooni ei õnnestunud teha.",
"badtitle": "Vigane pealkiri",
"movepage-moved": "<strong>\"$1\" on teisaldatud pealkirja \"$2\" alla.</strong>",
"movepage-moved-redirect": "Ümbersuunamisleht loodud.",
"movepage-moved-noredirect": "Ümbersuunamist ei loodud.",
+ "movepage-delete-first": "Sihtlehekülge ei saa teisaldamise käigus kustutada, kuna sellel on liiga palju redaktsioone. Palun kustuta lehekülg kõigepealt käsitsi ja proovi seejärel uuesti.",
"articleexists": "Sellise pealkirjaga lehekülg on juba olemas või pole valitud pealkiri lubatav.\nPalun vali teistsugune pealkiri.",
"cantmove-titleprotected": "Lehte ei saa sinna teisaldada, sest uus pealkiri on artikli loomise eest kaitstud",
"movetalk": "Teisalda seonduv arutelulehekülg",
"pageinfo-category-files": "Faile",
"pageinfo-user-id": "Kasutaja identifikaator",
"pageinfo-file-hash": "Räsiväärtus",
+ "pageinfo-view-protect-log": "Vaata selle lehekülje kaitsmislogi.",
"markaspatrolleddiff": "Märgi kontrollituks",
"markaspatrolledtext": "Märgi see leht kontrollituks",
"markaspatrolledtext-file": "Märgi see failiversioon kontrollituks",
"rcfilters-view-tags": "Redakturi etiketizata",
"rcfilters-view-namespaces-tooltip": "Filtrar rezulti segun nomo",
"rcfilters-liveupdates-button": "Quika aktualigi",
+ "rcfilters-watchlist-markseen-button": "Indikar \"vidita\" en omna modifikuri",
"rcnotefrom": "Infre {{PLURAL:$5|esas la chanjo|esas la chanji}} de <strong>$3, $4</strong> (montrata til <strong>$1</strong>).",
"rclistfrom": "Montrar nova chanji startante de $3 $2",
"rcshowhideminor": "$1 mikra redakturi",
"sp-contributions-hideminor": "Celar kurta redaktaji",
"sp-contributions-submit": "Serchez",
"whatlinkshere": "Quo ligesas adhike",
- "whatlinkshere-title": "Pagini qui ligas ad \"$1\"",
+ "whatlinkshere-title": "Pagini qui ligesas a(d) \"$1\"",
"whatlinkshere-page": "Pagino:",
"linkshere": "Ca pagini esas ligilizita a(d) <strong>$2</strong>:",
"nolinkshere": "Nula pagino ligas ad <strong>$2</strong>.",
"tooltip-n-recentchanges": "Listo di recenta chanji en la wiki.",
"tooltip-n-randompage": "Vizitez hazarda pagino",
"tooltip-n-help": "La loko por trovar ulo.",
- "tooltip-t-whatlinkshere": "Montrez omna wiki pagini qui ligas ad hike",
+ "tooltip-t-whatlinkshere": "Listo di omna Wikipagini qui ligesas adhike",
"tooltip-t-recentchangeslinked": "Recenta chanji di pagini ligita de ca pagino",
"tooltip-feed-rss": "RSS provizero por ica pagino",
"tooltip-feed-atom": "Atom provizero por ica pagino",
"filehist-comment": "Komentārs",
"imagelinks": "Faila lietojums",
"linkstoimage": "Šo failu izmanto {{PLURAL:$1|šajās $1 lapās|šajā $1 lapā|šajās $1 lapās}}:",
- "linkstoimage-more": "Uz šo failu ir saites vairāk nekā $1 {{PLURAL:$1|lapās|lapā|lapās}}.\nŠajā sarakstā ir tikai {{PLURAL:$1|pirmās $1 saistītās lapas|pirmā $1 saistītā lapa|pirmās $1 saistītās lapas}} uz šo failu.\nPieejams arī [[Special:WhatLinksHere/$2|pilns saraksts]].",
+ "linkstoimage-more": "Šo failu izmanto vairāk nekā $1 {{PLURAL:$1|lapās|lapā|lapās}}.\nŠajā sarakstā ir tikai {{PLURAL:$1|pirmās $1 lapas|pirmā lapa|pirmās $1 lapas}}, kas izmanto šo failu.\nPieejams arī [[Special:WhatLinksHere/$2|pilns saraksts]].",
"nolinkstoimage": "Šo failu neizmanto nevienā lapā.",
"morelinkstoimage": "Skatīt [[Special:WhatLinksHere/$1|vairāk saites]] uz šo failu.",
"linkstoimage-redirect": "$1 (faila pāradresācija) $2",
"right-nominornewtalk": "Невыписованя новых повідомлїнь по малых управах діскузной сторінкы",
"right-apihighlimits": "Хоснованя высшых лімітів в API запытах",
"right-writeapi": "Хосновати API про писаня",
- "right-delete": "Ð\97мазаня сторінок",
+ "right-delete": "Ð\9cазаня сторінок",
"right-bigdelete": "Мазаня сторінок з довгов історіёв",
"right-deletelogentry": "Мазаня тай обновлїня окремых записів лоґів\n,",
"right-deleterevision": "Мазаня і обновованя конкретных ревізій сторінок",
"group-autoconfirmed": "خود توثیق شدہ صارفین",
"group-bot": "روبہ جات",
"group-sysop": "منتظمین",
+ "group-interface-admin": "انٹرفیس منتظمین",
"group-bureaucrat": "مامورین اداری",
"group-suppress": "Suppressors",
"group-all": "(تمام)",
"group-autoconfirmed-member": "خودتوثیق شدہ صارف",
"group-bot-member": "خودکار صارف",
"group-sysop-member": "{{GENDER:$1|منتظم}}",
+ "group-interface-admin-member": "{{GENDER:$1|انٹرفیس منتظم}}",
"group-bureaucrat-member": "{{GENDER:$1|مامور اداری}}",
"group-suppress-member": "{{GENDER:$1|suppressor}}",
"grouppage-user": "{{ns:project}}:صارفین",
"grouppage-autoconfirmed": "{{ns:project}}:خود توثیق شدہ صارف",
"grouppage-bot": "{{ns:project}}:روبہ جات",
"grouppage-sysop": "{{ns:project}}:منتظمین",
+ "grouppage-interface-admin": "{{ns:project}}:انٹرفیس منتظمین",
"grouppage-bureaucrat": "{{ns:project}}:مامورین اداری",
"grouppage-suppress": "{{ns:project}}:پوشیدگی",
"right-read": "مطالعہ صفحات",
"badarticleerror": "מען קען נישט טאן די אקציע וואס איר ווילט אויף דעם בלאט.",
"cannotdelete": "נישט געווען מעגלעך אויסמעקן דעם בלאט אדער די טעקע \"$1\".\nקען זיין אז דאס איז שוין געווארן אויסגעמעקט דורך אן אנדערן.",
"cannotdelete-title": "מען קען נישט אויסמעקן בלאט \"$1\"",
+ "delete-scheduled": "דעם בלאט \"$1\" פלאנירט מען אויסצומעקן.\nהאטס געדולד.",
"delete-hook-aborted": "אויסמעקונג אנולירט דורך hook.\nנישט געגעבן קיין דערקלערונג.",
"no-null-revision": "נישט מעגלעך צו שאפן א נול־ווערסיע פונעם בלאט \"$1\".",
"badtitle": "שלעכט קעפל",
"botpasswords-existing": "עקזיסטירנדע באט פאסווערטער",
"botpasswords-createnew": "שאפֿן א ניי באט פאסווארט",
"botpasswords-editexisting": "רעדאקטירן אן עקזיסטירנדיק באט פאסווארט",
+ "botpasswords-label-needsreset": "(פאסווארט דארף ווערן צוריקגעשטעלט)",
"botpasswords-label-appid": "באט נאמען:",
"botpasswords-label-create": "שאַפֿן",
"botpasswords-label-update": "דערהײַנטיקן",
"prefs-watchlist-edits": "מאַקסימום צאָל ענדערונגען צו ווייַזן אין אויפֿפאַסונג ליסטע:",
"prefs-watchlist-edits-max": "מאַקסימום נומער: 1000",
"prefs-watchlist-token": "אויפֿפאַסונג ליסטע סימן:",
+ "prefs-watchlist-managetokens": "פֿארוואלטן סימנים",
"prefs-misc": "פֿאַרשידנס",
"prefs-resetpass": "טוישן פאַסווארט",
"prefs-changeemail": "ענדערן אדער אראפנעמען ע-פּאָסט אַדרעס",
"group-autoconfirmed": "באַשטעטיקטע באַניצער",
"group-bot": "באטס",
"group-sysop": "סיסאפן",
+ "group-interface-admin": "אייבערפֿלאך־אדמיניסטראטארן",
"group-bureaucrat": "ביוראקראטן",
"group-suppress": "אונטערדריקער",
"group-all": "(אלע)",
"group-autoconfirmed-member": "{{GENDER:$1|באַשטעטיקטער באַניצער|באַשטעטיקטע באַניצערין}}",
"group-bot-member": "{{GENDER:$1|באט}}",
"group-sysop-member": "{{GENDER:$1|סיסאפ}}",
+ "group-interface-admin-member": "{{GENDER:$1|אייבערפֿלאך־אדמיניסטראטאר}}",
"group-bureaucrat-member": "{{GENDER:$1|ביוראקראַט}}",
"group-suppress-member": "{{GENDER:$1|אונטעדריקער}}",
"grouppage-user": "{{ns:project}}:אײַנגעשריבענער באניצער",
"action-editcontentmodel": "רעדאקטירן אינהאלט־מאדעל פון א בלאט",
"action-managechangetags": "שאפן און (אומ)אקטיווירן טאגן פון דער דאטנבאזע",
"action-applychangetags": "אנווענדן טאגן צוזאמען מיט אייערע ענדערונגען",
+ "action-deletechangetags": "אויסמעקן טאגן פון דער דאטנבאזע",
"action-purge": "אויסרייניגן דעם דאזיגן בלאט",
"nchanges": "{{PLURAL:$1|ענדערונג|$1 ענדערונגען}}",
"enhancedrc-since-last-visit": "$1 {{PLURAL:$1|זײַט לעצטן וויזיט}}",
"rcfilters-savedqueries-new-name-label": "נאָמען",
"rcfilters-savedqueries-apply-label": "שאפן פילטער",
"rcfilters-savedqueries-cancel-label": "אַנולירן",
+ "rcfilters-clear-all-filters": "אפראמען אלע פֿילטערן",
"rcfilters-show-new-changes": "ווייזן די נייעסטע ענדערונגען",
"rcfilters-search-placeholder": "פֿילטערן ענדערונגען (ניצט מעניו אדער זוכט פֿילטער־נאמען)",
"rcfilters-invalid-filter": "אומגילטיגער פֿילטער",
"rcfilters-filter-minor-description": "רעדאקטירונגען וואס דער שרייבער האט מארקירט פֿארמינערט.",
"rcfilters-filter-watchlist-watched-label": "אויף דער אויפֿפאַסונג ליסטע",
"rcfilters-filter-watchlist-notwatched-label": "נישט אויף דער אויפֿפאַסונג ליסטע",
+ "rcfilters-filter-watchlistactivity-seen-label": "געזעענע ענדערונגען",
"rcfilters-filtergroup-changetype": "טיפ ענדערונג",
"rcfilters-filter-pageedits-label": "בלאט רעדאקטירונגען",
"rcfilters-filter-newpages-label": "בלאַט־שאַפֿונגען",
"filehist-filesize": "טעקע גרייס",
"filehist-comment": "באמערקונג",
"imagelinks": "טעקע באַניץ",
- "linkstoimage": "{{PLURAL:$1|×\93ער פ×\90×\9c×\92× ×\93ער ×\91×\9c×\90×\98 × ×\99צ×\98|×\93×\99 פ×\90×\9c×\92× ×\93×¢ ×\91×\9c×¢×\98ער × ×\99צ×\9f}} ×\93×\90ס ×\93×\90×\96×\99×\92×¢ ×\91×\99×\9c×\93:",
+ "linkstoimage": "{{PLURAL:$1|×\93ער פ×\90×\9c×\92× ×\93ער ×\91×\9c×\90×\98 × ×\99צ×\98|×\93×\99 פ×\90×\9c×\92× ×\93×¢ ×\91×\9c×¢×\98ער × ×\99צ×\9f}} ×\93×\99 ×\93×\90×\96×\99×\92×¢ ×\98עקע:",
"linkstoimage-more": "מער ווי $1 {{PLURAL:$1|בלאַט ניצט|בלעטער ניצן}} די דאזיגע טעקע.\nדי פֿאלגנדע ליסטע ווײַזט נאר {{PLURAL:$1|דעם ערשטן בלאַט וואס ניצט|די ערשטע $1 בלעטער וואס ניצן}} די טעקע.\nס'איז פֿאַראַן א [[Special:WhatLinksHere/$2|פֿולע רשימה]].",
- "nolinkstoimage": "× ×\99ש×\98×\90 ק×\99×\99×\9f ×\91×\9c×¢×\98ער ×\95×\95×\90ס פ×\90ר×\91×\99× ×\93×\9f צ×\95 די טעקע.",
+ "nolinkstoimage": "× ×\99ש×\98×\90 ק×\99×\99×\9f ×\91×\9c×¢×\98ער ×\95×\95×\90ס × ×\99צ×\9f די טעקע.",
"morelinkstoimage": "באַקוקן [[Special:WhatLinksHere/$1|מער לינקען]] צו דער טעקע.",
"linkstoimage-redirect": "$1 (טעקע ווײַטערפֿירונג) $2",
"duplicatesoffile": "די פֿאלגנדע {{PLURAL:$1|טעקע דופליקירט|$1 טעקעס דופליקירן}} די דאזיגע טעקע ([[Special:FileDuplicateSearch/$2|נאך פרטים]]):",
"cachedspecial-refresh-now": "באקוקן די לעצטע.",
"categories": "קאַטעגאָריעס",
"categories-submit": "ווייזן",
- "categoriespagetext": "×\93×\99 פֿ×\90×\9c×\92×¢× ×\93×¢ {{PLURAL:$1| ק×\90Ö·×\98×¢×\92×\90ָר×\99×¢ ×\90Ö·× ×\98×\94×\90Ö·×\9c×\98|ק×\90Ö·×\98×¢×\92×\90ָר×\99עס ×\90Ö·× ×\98×\94×\90Ö·×\9c×\98×\9f}} ×\91×\9c×¢×\98ער ×\90×\93ער ×\9e×¢×\93×\99×¢.\n[[Special:UnusedCategories|×\90×\95×\9e×\91×\90Ö·× ×\99צ×\98×¢ ק×\90Ö·×\98×¢×\92×\90ר×\99עס]] ×\96×¢× ×¢×\9f × ×\99ש×\98 ×\92×¢×\95×\95×\99×\96×\9f ×\93×\90.\nזעט אויך [[Special:WantedCategories|געזוכטע קאַטעגאריעס]].",
+ "categoriespagetext": "×\93×\99 פֿ×\90×\9c×\92×¢× ×\93×¢ {{PLURAL:$1| ק×\90Ö·×\98×¢×\92×\90ָר×\99×¢ עק×\96×\99ס×\98×\99ר×\98|ק×\90Ö·×\98×¢×\92×\90ָר×\99עס עק×\96×\99ס×\98×\99ר×\9f}} ×\90×\95×\99×£ ×\93ער ×\95×\95×\99ק×\99, ×\90×\95×\9f ×\90×\99×\96 ×\90פשר ×\90×\99×\9f ×\91×\90× ×\99×¥ ×\90פשר × ×\99ש×\98.\n\nזעט אויך [[Special:WantedCategories|געזוכטע קאַטעגאריעס]].",
"categoriesfrom": "ווײַזן קאַטעגאריעס אָנהייבנדיג פֿון:",
"deletedcontributions": "אויסגעמעקטע באַניצער בײַשטײַערונגען",
"deletedcontributions-title": "אויסגעמעקטע באַניצער בײַשטײַערונגען",
"move-page-legend": "באַוועגן בלאַט",
"movepagetext": "זיך באניצן מיט דעם פֿארעם וועט פֿארענדערן דעם נאמען פֿון דעם בלאט, און וועט אריבערפֿירן זיין געשיכטע צום נייעם נאמען.\nדאס אלטע קעפל וועט ווערן א ווייטערפֿירונג בלאט צום נייעם קעפל.\n\nאיר קענט דערהיינטיגן ווייטערפֿירונגען צום אלטן נאמען אויטאמאטיש.\n\nטאמער נישט, טוט פֿארזיכערן אז עס איז נישטא קיין [[Special:DoubleRedirects|געטאפלטע]] אדער [[Special:BrokenRedirects|צעבראכענע ווייטערפֿירונגען]].\n\nאיר זענט פֿאראנטווארטלעך זיכער מאכן אז אלע פֿארבינדונגען ווערן געריכטעט צום געהעריגן ציל.\n\nדער בלאט וועט <strong> נישט</strong> ווערן אריבערגעפֿירט אויב עס איז שוין דא א בלאט אונטער דעם נייעם נאמען, אחוץ ווען ער איז א ווייטערפֿירונג בלאט, און ער האט נישט קיין געשיכטע פון ענדערונגען.\nפשט דערפֿון, אז איר קענט איבערקערן א ווייטערפֿירונג וואס איר האט אט געמאכט בטעות, און איר קענט נישט אריבערשרייבן אן עקסיסטירנדן בלאט.\n\n<strong>הערה:</strong>\n אזא ענדערונג קען זיין דראסטיש און נישט געווינטשען פאר א פאפולערן בלאט;\nביטע פֿארזיכערט אז איר פֿארשטייט די ווייטגרייכנדע קאנסקווענסן צו דער אקציע בעפֿאר איר פֿירט דאס אויס.",
"movepagetext-noredirectfixer": "זיך באניצן מיט דעם פֿארעם אונטן וועט פֿארענדערן דעם נאמען פֿון דעם בלאט, און וועט אריבערפֿירן זיין געשיכטע צום נייעם נאמען.\n\nדאס אלטע קעפל וועט ווערן א ווייטערפֿירן בלאט צום נײַעם נאמען.\n\nטוט פֿארזיכערן אז עס בלײַבן נישט קיין [[Special:DoubleRedirects|געטאפלטע]] אדער [[Special:BrokenRedirects|צעבראכענע]] ווייטערפֿירונגען.\n\nאיר זענט פֿאראנטווארטלעך זיכער מאכן אז אלע פֿארבינדונגען ווערן געריכטעט צו דער געהעריגער ריכטונג.\n\nאַכטונג: דער בלאַט וועט <strong>נישט</strong> ווערן אַריבערגעפֿירט אויב עס איז שוין דאָ א בלאט אונטער דעם נײַעם נאמען, אחוץ ווען ער איז א ווײַטערפֿירונג בלאט, און ער האט נישט קיין געשיכטע פון ענדערונגען.\nפשט דערפֿון, אז איר קענט איבערקערן א ווייטערפֿירונג וואס איר האט אט געמאכט בטעות, און איר קענט נישט אריבערשרײַבן אַן עקסיסטירנדן בלאט.\n\n<strong>הערה:</strong> אזא ענדערונג קען זיין דראַסטיש און נישט געוואונטשן פֿאַר א פאפולערן בלאַט; ביטע פֿאַרזיכערט אז איר פֿאַרשטייט די ווײַטגרייכנדע קאנסעקווענסן צו דער אַקציע בעפֿאַר איר גייט ווײַטער.",
- "movepagetalktext": "×\98×\90×\9eער צ×\99×\99×\9b× ×¡ איר דאס קעסטל, וועט דער אסאסיציאירטער רעדן בלאט ווערן באַוועגט אויטאמאֵטיש צום נײַעם קעפל, אחוץ ווען ס'איז שוין דא א נישט-ליידיגער רעדן־בלאט.\n\nאין דעם פֿאל, וועט איר דארפֿן באַוועגן אדער צונויפֿגיסן דעם בלאט האַנטלעך, ווען איר ווילט.",
+ "movepagetalktext": "×\98×\90×\9eער צ×\99×\99×\9b× ×\98 איר דאס קעסטל, וועט דער אסאסיציאירטער רעדן בלאט ווערן באַוועגט אויטאמאֵטיש צום נײַעם קעפל, אחוץ ווען ס'איז שוין דא א נישט-ליידיגער רעדן־בלאט.\n\nאין דעם פֿאל, וועט איר דארפֿן באַוועגן אדער צונויפֿגיסן דעם בלאט האַנטלעך, ווען איר ווילט.",
"moveuserpage-warning": "'''ווארענונג:''' איר האלט ביי באוועגן א באניצער בלאט. ביטע באמערקט אז נאר דער בלאט ווערט באוועגט אבער דער באניצער נאמען ווערט ''נישט'' געענדערט.",
"movecategorypage-warning": "<strong>ווארענונג:</strong> איר האלט ביי באוועגן א קאטעגאריע בלאט. גיט אכט אז נאר דער בלאט וועט ווערן באוועגט, אבער די בלעטער אין דער אלטער קאטעגאריע וועט מען <em>נישט</em> ארײַנשטעלן אין דער נייער קאטעגאריע.",
"movenologintext": "איר דארפֿט זיך אײַנשרײַבן און זײַן [[Special:UserLogin|אַרײַנלאגירט]] צו באַוועגן א בלאַט.",
"previousdiff": "פריערדיקער אונטערשייד →",
"nextdiff": "קומענדיקע ווערסיע ←",
"mediawarning": "'''ווארענונג''': דער טעקע טיפ קען אנטהאלטן בייזוויליקן קאד.\nדורכפירן דעם קאד קען שעדיקן אייער סיסטעם.",
- "imagemaxsize": "מאקסימאלע בילד גרייס :<br />''(פאר טעקע באשרייבונג בלעטער)''",
+ "imagemaxsize": "מאקסימאלע בילד גרייס אויף טעקע באשרייבונג בלעטער:",
"thumbsize": "קליינבילד גרייס:",
"widthheight": "$1 × $2",
"widthheightpage": "$1 × $2, {{PLURAL:$3|איין בלאט|$3 בלעטער}}",
];
$bookstoreList = [
- 'Aladin.co.kr' => 'http://www.aladin.co.kr/catalog/book.asp?ISBN=$1',
+ 'Aladin.co.kr' => 'https://www.aladin.co.kr/catalog/book.asp?ISBN=$1',
+ 'National Library of Korea' => 'http://www.nl.go.kr/search/web_search/search/list.php?search_field1=all&tmode=1&value1=$1',
+ 'Naver' => 'https://book.naver.com/search/search.nhn?query=$1',
'inherit' => true,
];
$json['config'][$name]['merge_strategy'] = $value[ExtensionRegistry::MERGE_STRATEGY];
unset( $value[ExtensionRegistry::MERGE_STRATEGY] );
}
+ if ( isset( $config["@$name"] ) ) {
+ // Put 'description' first for better human-legibility.
+ $json['config'][$name] = array_merge(
+ [ 'description' => $config["@$name"] ],
+ $json['config'][$name]
+ );
+ }
}
}
}
display: inline;
margin: 0;
padding: 0;
- list-style: none;
- list-style-type: none;
- list-style-image: none;
+ list-style: none none;
vertical-align: middle !ie;
}
-// Common Less mixin library for MediaWiki
+// Common LESS mixin library for MediaWiki
//
// By default the folder containing this file is included in the LESS import paths,
// which makes this file importable by all less files via `@import 'mediawiki.mixins';`.
// Parent constructor
mw.widgets.ExpiryWidget.parent.call( this, config );
- // If the wiki does not want the date picker, then initialize the relative
- // field and exit.
- if ( config.noDatePicker ) {
- this.relativeField.on( 'change', function ( event ) {
- // Emit a change event for this widget.
- this.emit( 'change', event );
- }.bind( this ) );
-
- // Initialization
- this.$element
- .addClass( 'mw-widget-ExpiryWidget' )
- .append(
- this.relativeField.$element
- );
-
- return;
- }
-
// Properties
this.inputSwitch = new OO.ui.ButtonSelectWidget( {
tabIndex: -1,
$result = new ApiResult( 8388608 );
$formatter = new ApiErrorFormatter( $result, Language::factory( 'de' ), 'wikitext', false );
$this->assertSame( 'de', $formatter->getLanguage()->getCode() );
+ $this->assertSame( 'wikitext', $formatter->getFormat() );
$formatter->addMessagesFromStatus( null, Status::newGood() );
$this->assertSame(
);
}
+ /**
+ * @covers ApiErrorFormatter
+ * @covers ApiErrorFormatter_BackCompat
+ */
+ public function testNewWithFormat() {
+ $result = new ApiResult( 8388608 );
+ $formatter = new ApiErrorFormatter( $result, Language::factory( 'de' ), 'wikitext', false );
+ $formatter2 = $formatter->newWithFormat( 'html' );
+
+ $this->assertSame( $formatter->getLanguage(), $formatter2->getLanguage() );
+ $this->assertSame( 'html', $formatter2->getFormat() );
+
+ $formatter3 = new ApiErrorFormatter_BackCompat( $result );
+ $formatter4 = $formatter3->newWithFormat( 'html' );
+ $this->assertNotInstanceOf( ApiErrorFormatter_BackCompat::class, $formatter4 );
+ $this->assertSame( $formatter3->getLanguage(), $formatter4->getLanguage() );
+ $this->assertSame( 'html', $formatter4->getFormat() );
+ }
+
/**
* @covers ApiErrorFormatter
* @dataProvider provideErrorFormatter
$formatter = new ApiErrorFormatter_BackCompat( $result );
$this->assertSame( 'en', $formatter->getLanguage()->getCode() );
+ $this->assertSame( 'bc', $formatter->getFormat() );
$this->assertSame( [], $formatter->arrayFromStatus( Status::newGood() ) );
* @param array $expect
*/
public function testGetMessageFromException( $exception, $options, $expect ) {
- if ( $exception instanceof UsageException ) {
- $this->hideDeprecated( 'UsageException::getMessageArray' );
- }
-
$result = new ApiResult( 8388608 );
$formatter = new ApiErrorFormatter( $result, Language::factory( 'en' ), 'html', false );
* @param array $expect
*/
public function testGetMessageFromException_BC( $exception, $options, $expect ) {
- if ( $exception instanceof UsageException ) {
- $this->hideDeprecated( 'UsageException::getMessageArray' );
- }
-
$result = new ApiResult( 8388608 );
$formatter = new ApiErrorFormatter_BackCompat( $result );
}
public static function provideGetMessageFromException() {
- Wikimedia\suppressWarnings();
- $usageException = new UsageException(
- '<b>Something broke!</b>', 'ue-code', 0, [ 'xxx' => 'yyy', 'baz' => 23 ]
- );
- Wikimedia\restoreWarnings();
-
return [
'Normal exception' => [
new RuntimeException( '<b>Something broke!</b>' ),
'data' => [ 'foo' => 'bar', 'baz' => 42 ],
]
],
- 'UsageException' => [
- $usageException,
- [],
- [
- 'text' => '<b>Something broke!</b>',
- 'code' => 'ue-code',
- 'data' => [ 'xxx' => 'yyy', 'baz' => 23 ],
- ]
- ],
- 'UsageException, wrapped' => [
- $usageException,
- [ 'wrap' => 'parentheses', 'code' => 'some-code', 'data' => [ 'foo' => 'bar', 'baz' => 42 ] ],
- [
- 'text' => '(<b>Something broke!</b>)',
- 'code' => 'some-code',
- 'data' => [ 'xxx' => 'yyy', 'baz' => 42, 'foo' => 'bar' ],
- ]
- ],
'LocalizedException' => [
new LocalizedException( [ 'returnto', '<b>FooBar</b>' ] ),
[],
MWExceptionHandler::getRedactedTraceAsString( $dbex )
)->inLanguage( 'en' )->useDatabase( false )->text();
- Wikimedia\suppressWarnings();
- $usageEx = new UsageException( 'Usage exception!', 'ue', 0, [ 'foo' => 'bar' ] );
- Wikimedia\restoreWarnings();
-
$apiEx1 = new ApiUsageException( null,
StatusValue::newFatal( new ApiRawMessage( 'An error', 'sv-error1' ) ) );
TestingAccessWrapper::newFromObject( $apiEx1 )->modulePath = 'foo+bar';
'servedby' => wfHostname(),
]
],
- [
- $usageEx,
- [ 'existing-error', 'ue' ],
- [
- 'warnings' => [
- [ 'code' => 'existing-warning', 'text' => 'existing warning', 'module' => 'main' ],
- ],
- 'errors' => [
- [ 'code' => 'existing-error', 'text' => 'existing error', 'module' => 'main' ],
- [ 'code' => 'ue', 'text' => "Usage exception!", 'data' => [ 'foo' => 'bar' ] ]
- ],
- 'docref' => "See $doclink for API usage. Subscribe to the mediawiki-api-announce mailing " .
- "list at <https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce> " .
- "for notice of API deprecations and breaking changes.",
- 'servedby' => wfHostname(),
- ]
- ],
[
$apiEx1,
[ 'existing-error', 'sv-error1', 'sv-error2' ],
--- /dev/null
+<?php
+
+/**
+ * @group API
+ * @group medium
+ * @group Database
+ *
+ * @coversDefaultClass ApiQueryInfo
+ */
+class ApiQueryInfoTest extends ApiTestCase {
+
+ /**
+ * @covers ::execute
+ * @covers ::extractPageInfo
+ */
+ public function testExecute() {
+ $page = $this->getExistingTestPage( 'Pluto' );
+ $title = $page->getTitle();
+
+ list( $data ) = $this->doApiRequest( [
+ 'action' => 'query',
+ 'prop' => 'info',
+ 'titles' => $title->getText(),
+ ] );
+
+ $this->assertArrayHasKey( 'query', $data );
+ $this->assertArrayHasKey( 'pages', $data['query'] );
+ $this->assertArrayHasKey( $page->getId(), $data['query']['pages'] );
+
+ $info = $data['query']['pages'][$page->getId()];
+ $this->assertSame( $page->getId(), $info['pageid'] );
+ $this->assertSame( $title->getNamespace(), $info['ns'] );
+ $this->assertSame( $title->getText(), $info['title'] );
+ $this->assertSame( $title->getContentModel(), $info['contentmodel'] );
+ $this->assertSame( $title->getPageLanguage()->getCode(), $info['pagelanguage'] );
+ $this->assertSame( $title->getPageLanguage()->getHtmlCode(), $info['pagelanguagehtmlcode'] );
+ $this->assertSame( $title->getPageLanguage()->getDir(), $info['pagelanguagedir'] );
+ $this->assertSame( wfTimestamp( TS_ISO_8601, $title->getTouched() ), $info['touched'] );
+ $this->assertSame( $title->getLatestRevID(), $info['lastrevid'] );
+ $this->assertSame( $title->getLength(), $info['length'] );
+ $this->assertSame( $title->isNewPage(), $info['new'] );
+ $this->assertArrayNotHasKey( 'actions', $info );
+ }
+
+ /**
+ * @covers ::execute
+ * @covers ::extractPageInfo
+ */
+ public function testExecuteEditActions() {
+ $page = $this->getExistingTestPage( 'Pluto' );
+ $title = $page->getTitle();
+
+ list( $data ) = $this->doApiRequest( [
+ 'action' => 'query',
+ 'prop' => 'info',
+ 'titles' => $title->getText(),
+ 'intestactions' => 'edit'
+ ] );
+
+ $this->assertArrayHasKey( 'query', $data );
+ $this->assertArrayHasKey( 'pages', $data['query'] );
+ $this->assertArrayHasKey( $page->getId(), $data['query']['pages'] );
+
+ $info = $data['query']['pages'][$page->getId()];
+ $this->assertArrayHasKey( 'actions', $info );
+ $this->assertArrayHasKey( 'edit', $info['actions'] );
+ $this->assertTrue( $info['actions']['edit'] );
+ }
+
+ /**
+ * @covers ::execute
+ * @covers ::extractPageInfo
+ */
+ public function testExecuteEditActionsFull() {
+ $page = $this->getExistingTestPage( 'Pluto' );
+ $title = $page->getTitle();
+
+ list( $data ) = $this->doApiRequest( [
+ 'action' => 'query',
+ 'prop' => 'info',
+ 'titles' => $title->getText(),
+ 'intestactions' => 'edit',
+ 'intestactionsdetail' => 'full',
+ ] );
+
+ $this->assertArrayHasKey( 'query', $data );
+ $this->assertArrayHasKey( 'pages', $data['query'] );
+ $this->assertArrayHasKey( $page->getId(), $data['query']['pages'] );
+
+ $info = $data['query']['pages'][$page->getId()];
+ $this->assertArrayHasKey( 'actions', $info );
+ $this->assertArrayHasKey( 'edit', $info['actions'] );
+ $this->assertInternalType( 'array', $info['actions']['edit'] );
+ $this->assertSame( [], $info['actions']['edit'] );
+ }
+
+ /**
+ * @covers ::execute
+ * @covers ::extractPageInfo
+ */
+ public function testExecuteEditActionsFullBlock() {
+ $badActor = $this->getTestUser()->getUser();
+ $sysop = $this->getTestSysop()->getUser();
+
+ $block = new \Block( [
+ 'address' => $badActor->getName(),
+ 'user' => $badActor->getId(),
+ 'by' => $sysop->getId(),
+ 'expiry' => 'infinity',
+ 'sitewide' => 0,
+ 'enableAutoblock' => true,
+ ] );
+
+ $block->insert();
+
+ $page = $this->getExistingTestPage( 'Pluto' );
+ $title = $page->getTitle();
+
+ list( $data ) = $this->doApiRequest( [
+ 'action' => 'query',
+ 'prop' => 'info',
+ 'titles' => $title->getText(),
+ 'intestactions' => 'edit',
+ 'intestactionsdetail' => 'full',
+ ], null, false, $badActor );
+
+ $block->delete();
+
+ $this->assertArrayHasKey( 'query', $data );
+ $this->assertArrayHasKey( 'pages', $data['query'] );
+ $this->assertArrayHasKey( $page->getId(), $data['query']['pages'] );
+
+ $info = $data['query']['pages'][$page->getId()];
+ $this->assertArrayHasKey( 'actions', $info );
+ $this->assertArrayHasKey( 'edit', $info['actions'] );
+ $this->assertInternalType( 'array', $info['actions']['edit'] );
+ $this->assertArrayHasKey( 0, $info['actions']['edit'] );
+ $this->assertArrayHasKey( 'code', $info['actions']['edit'][0] );
+ $this->assertSame( 'blocked', $info['actions']['edit'][0]['code'] );
+ $this->assertArrayHasKey( 'data', $info['actions']['edit'][0] );
+ $this->assertArrayHasKey( 'blockinfo', $info['actions']['edit'][0]['data'] );
+ $this->assertArrayHasKey( 'blockid', $info['actions']['edit'][0]['data']['blockinfo'] );
+ $this->assertSame( $block->getId(), $info['actions']['edit'][0]['data']['blockinfo']['blockid'] );
+ }
+
+}
[ 'ćaB', 'ćaB' ],
];
}
+
+ public function testNoDBAccess() {
+ global $wgContLanguageCode;
+
+ $dbr = wfGetDB( DB_REPLICA );
+
+ MessageCache::singleton()->getMsgFromNamespace( 'allpages', $wgContLanguageCode );
+
+ $this->assertEquals( 0, $dbr->trxLevel() );
+ $dbr->setFlag( DBO_TRX, $dbr::REMEMBER_PRIOR ); // make queries trigger TRX
+
+ MessageCache::singleton()->getMsgFromNamespace( 'go', $wgContLanguageCode );
+
+ $dbr->restoreFlags();
+
+ $this->assertEquals( 0, $dbr->trxLevel(), "No DB read queries" );
+ }
}
$this->assertSame( null, $domain->getSchema() );
$this->assertSame( '', $domain->getTablePrefix() );
}
+
+ public static function provideIsCompatible() {
+ return [
+ 'Basic' =>
+ [ 'foo', 'foo', null, '', true ],
+ 'db+prefix' =>
+ [ 'foo-bar', 'foo', null, 'bar', true ],
+ 'db+schema+prefix' =>
+ [ 'foo-bar-baz', 'foo', 'bar', 'baz', true ],
+ 'db+dontcare_schema+prefix' =>
+ [ 'foo-bar-baz', 'foo', null, 'baz', false ],
+ '?h -> -' =>
+ [ 'foo?hbar-baz-baa', 'foo-bar', 'baz', 'baa', true ],
+ '?? -> ?' =>
+ [ 'foo??bar-baz-baa', 'foo?bar', 'baz', 'baa', true ],
+ 'Nothing' =>
+ [ '', null, null, '', true ],
+ 'dontcaredb+dontcaredbschema+prefix' =>
+ [ 'mywiki-mediawiki-prefix', null, null, 'prefix', false ],
+ 'dontcaredb+schema+prefix' =>
+ [ 'mywiki-schema-prefix', null, 'schema', 'prefix', false ],
+ 'db+dontcareschema+prefix' =>
+ [ 'mywiki-schema-prefix', 'mywiki', null, 'prefix', false ],
+ 'postgres-db-jobqueue' =>
+ [ 'postgres-mediawiki-', 'postgres', null, '', false ]
+ ];
+ }
+
+ /**
+ * @dataProvider provideIsCompatible
+ * @covers Wikimedia\Rdbms\DatabaseDomain::isCompatible
+ */
+ public function testIsCompatible( $id, $db, $schema, $prefix, $transitive ) {
+ $compareIdObj = DatabaseDomain::newFromId( $id );
+ $this->assertInstanceOf( DatabaseDomain::class, $compareIdObj );
+
+ $fromId = new DatabaseDomain( $db, $schema, $prefix );
+
+ $this->assertTrue( $fromId->isCompatible( $id ), 'constructed equals string' );
+ $this->assertTrue( $fromId->isCompatible( $compareIdObj ), 'fromId equals string' );
+
+ $this->assertEquals( $transitive, $compareIdObj->isCompatible( $fromId ),
+ 'test transitivity of nulls components' );
+ }
+
+ public static function provideIsCompatible2() {
+ return [
+ 'db+schema+prefix' =>
+ [ 'mywiki-schema-prefix', 'thatwiki', 'schema', 'prefix' ],
+ 'dontcaredb+dontcaredbschema+prefix' =>
+ [ 'thatwiki-mediawiki-otherprefix', null, null, 'prefix' ],
+ 'dontcaredb+schema+prefix' =>
+ [ 'mywiki-otherschema-prefix', null, 'schema', 'prefix' ],
+ 'db+dontcareschema+prefix' =>
+ [ 'notmywiki-schema-prefix', 'mywiki', null, 'prefix' ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideIsCompatible2
+ * @covers Wikimedia\Rdbms\DatabaseDomain::isCompatible
+ */
+ public function testIsCompatible2( $id, $db, $schema, $prefix ) {
+ $compareIdObj = DatabaseDomain::newFromId( $id );
+ $this->assertInstanceOf( DatabaseDomain::class, $compareIdObj );
+
+ $fromId = new DatabaseDomain( $db, $schema, $prefix );
+
+ $this->assertFalse( $fromId->isCompatible( $id ), 'constructed equals string' );
+ $this->assertFalse( $fromId->isCompatible( $compareIdObj ), 'fromId equals string' );
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\DatabaseDomain::isUnspecified
+ */
+ public function testIsUnspecified() {
+ $domain = new DatabaseDomain( null, null, '' );
+ $this->assertTrue( $domain->isUnspecified() );
+ $domain = new DatabaseDomain( 'mywiki', null, '' );
+ $this->assertFalse( $domain->isUnspecified() );
+ $domain = new DatabaseDomain( 'mywiki', null, '' );
+ $this->assertFalse( $domain->isUnspecified() );
+ }
}
\Wikimedia\quietCall( 'session_start' );
$backend->unpersist();
$this->assertSame( self::SESSIONID . 'x', session_id() );
+ session_write_close();
session_id( self::SESSIONID );
$wrap->persist = true;
$context->setRequest( $request );
$id = $request->getSession()->getId();
+ session_write_close();
session_id( '' );
$session = SessionManager::getGlobalSession();
$this->assertSame( $id, $session->getId() );
}
public function testActorId() {
+ $domain = MediaWikiServices::getInstance()->getDBLoadBalancer()->getLocalDomainID();
$this->hideDeprecated( 'User::selectFields' );
// Newly-created user has an actor ID
'Actor ID can be retrieved for user loaded with User::selectFields()' );
$this->db->delete( 'actor', [ 'actor_user' => $id ], __METHOD__ );
- User::purge( wfWikiId(), $id );
+ User::purge( $domain, $id );
// Because WANObjectCache->delete() stupidly doesn't delete from the process cache.
ObjectCache::getMainWANInstance()->clearProcessCache();
npm run selenium
-By default, Chrome will run in headless mode. If you want to see Chrome, set DISPLAY
-environment variable to any value:
+There are three supported modes of running the tests:
+
+- Headless. It's the default. You will not see the browser while tests are
+ running because it's running in a headless mode. This mode should run fine
+ on all supported platforms.
+- Headless recording. Set DISPLAY environment variable to a value that starts
+ with colon (`:`) and video of each test will be recorded. Browser will run
+ headless. Recording videos works only on Linux.
+- Visible. If you want to see the browser, set DISPLAY environment variable to
+ any value that does not start with colon. This mode will not work in a
+ headless environment like MediaWiki-Vagrant.
+
+Example recording session:
+
+ sudo apt-get install chromedriver ffmpeg xvfb
+ export DISPLAY=:94
+ Xvfb "$DISPLAY" -screen 0 1280x1024x24 &
+ npm run selenium
+
+Example visible session:
DISPLAY=1 npm run selenium
const fs = require( 'fs' ),
path = require( 'path' ),
- saveScreenshot = require( 'wdio-mediawiki' ).saveScreenshot,
- logPath = process.env.LOG_DIR || __dirname + '/log';
+ logPath = process.env.LOG_DIR || path.join( __dirname, '/log' );
+let ffmpeg;
+
+// get current test title and clean it, to use it as file name
+function fileName( title ) {
+ return encodeURIComponent( title.replace( /\s+/g, '-' ) );
+}
+
+// build file path
+function filePath( test, screenshotPath, extension ) {
+ return path.join( screenshotPath, `${fileName( test.parent )}-${fileName( test.title )}.${extension}` );
+}
+
+// relative path
function relPath( foo ) {
return path.resolve( __dirname, '../..', foo );
}
// =====
// See also: http://webdriver.io/guide/testrunner/configurationfile.html
+ /**
+ * Function to be executed before a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
+ * @param {Object} test test details
+ */
+ beforeTest: function ( test ) {
+ if ( process.env.DISPLAY && process.env.DISPLAY.startsWith( ':' ) ) {
+ let videoPath = filePath( test, this.screenshotPath, 'mp4' );
+ const { spawn } = require( 'child_process' );
+ ffmpeg = spawn( 'ffmpeg', [
+ '-f', 'x11grab', // grab the X11 display
+ '-video_size', '1280x1024', // video size
+ '-i', process.env.DISPLAY, // input file url
+ '-loglevel', 'error', // log only errors
+ '-y', // overwrite output files without asking
+ '-pix_fmt', 'yuv420p', // QuickTime Player support, "Use -pix_fmt yuv420p for compatibility with outdated media players"
+ videoPath // output file
+ ] );
+
+ ffmpeg.stdout.on( 'data', ( data ) => {
+ console.log( `ffmpeg stdout: ${data}` );
+ } );
+
+ ffmpeg.stderr.on( 'data', ( data ) => {
+ console.log( `ffmpeg stderr: ${data}` );
+ } );
+
+ ffmpeg.on( 'close', ( code ) => {
+ console.log( '\n\tVideo location:', videoPath, '\n' );
+ console.log( `ffmpeg exited with code ${code}` );
+ } );
+ }
+ },
+
/**
* Save a screenshot when test fails.
*
* @param {Object} test Mocha Test object
*/
afterTest: function ( test ) {
- var filePath;
- if ( !test.passed ) {
- filePath = saveScreenshot( test.title );
- console.log( '\n\tScreenshot: ' + filePath + '\n' );
+ if ( ffmpeg ) {
+ // stop video recording
+ ffmpeg.kill( 'SIGINT' );
+ }
+
+ // if test passed, ignore, else take and save screenshot
+ if ( test.passed ) {
+ return;
}
+ // save screenshot
+ let screenshotPath = filePath( test, this.screenshotPath, 'png' );
+ browser.saveScreenshot( screenshotPath );
+ console.log( '\n\tScreenshot location:', screenshotPath, '\n' );
}
};