diff options
159 files changed, 1007 insertions, 399 deletions
diff --git a/RELEASE-NOTES-1.44 b/RELEASE-NOTES-1.44 index 12e99197e235..8c3f7e05a30a 100644 --- a/RELEASE-NOTES-1.44 +++ b/RELEASE-NOTES-1.44 @@ -156,7 +156,7 @@ For notes on 1.43.x and older releases, see HISTORY. * Updated wikimedia/minify from 2.8.0 to 2.9.0. * Updated wikimedia/php-session-serializer from 3.0.0 to 3.0.1. * Updated wikimedia/relpath from 4.0.1 to 4.0.2. -* Updated wikimedia/shellbox from 4.1.1 to 4.1.2. +* Updated wikimedia/shellbox from 4.1.1 to 4.2.0. * Updated wikimedia/timestamp from 4.1.1 to 4.2.0. * … @@ -271,6 +271,8 @@ because of Phabricator reports. been removed. * The `MediaWiki\User\Registration\IUserRegistrationProvider` interface now defines a required fetchRegistrationBatch() method. +* BagOStuff::clearLastError() and WANObjectCache::clearLastError(), both + deprecated in 1.43, have been removed. * The hook OutputPageMakeCategoryLinks, deprecated in 1.43, has been removed. * ApiPageSet::getTitles(), ApiPageSet::getGoodTitles(), ApiPageSet::getMissingTitles(), ApiPageSet::getGoodAndMissingTitles(), diff --git a/cache/.htaccess b/cache/.htaccess index b66e80882967..2e5c00314d2f 100644 --- a/cache/.htaccess +++ b/cache/.htaccess @@ -1 +1,2 @@ Require all denied +Satisfy All diff --git a/composer.json b/composer.json index bc6ca774bcf3..ac14af55c5dc 100644 --- a/composer.json +++ b/composer.json @@ -69,7 +69,7 @@ "wikimedia/minify": "2.9.0", "wikimedia/normalized-exception": "2.1.1", "wikimedia/object-factory": "5.0.1", - "wikimedia/parsoid": "0.21.0-a23", + "wikimedia/parsoid": "0.21.0-a24", "wikimedia/php-session-serializer": "3.0.1", "wikimedia/purtle": "2.0.0", "wikimedia/relpath": "4.0.2", @@ -78,7 +78,7 @@ "wikimedia/running-stat": "2.1.0", "wikimedia/scoped-callback": "5.0.0", "wikimedia/services": "4.0.0", - "wikimedia/shellbox": "4.1.2", + "wikimedia/shellbox": "4.2.0", "wikimedia/utfnormal": "4.0.0", "wikimedia/timestamp": "4.2.0", "wikimedia/wait-condition-loop": "2.0.2", diff --git a/includes/.htaccess b/includes/.htaccess index b66e80882967..2e5c00314d2f 100644 --- a/includes/.htaccess +++ b/includes/.htaccess @@ -1 +1,2 @@ Require all denied +Satisfy All diff --git a/includes/Rest/Handler/CompareHandler.php b/includes/Rest/Handler/CompareHandler.php index 2829f9f6a4cd..ce8267f8e454 100644 --- a/includes/Rest/Handler/CompareHandler.php +++ b/includes/Rest/Handler/CompareHandler.php @@ -111,11 +111,11 @@ class CompareHandler extends Handler { return $rev->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ); } - private function getRole() { + private function getRole(): string { return SlotRecord::MAIN; } - private function getRevisionText( $paramName ) { + private function getRevisionText( string $paramName ): string { if ( !isset( $this->textCache[$paramName] ) ) { $revision = $this->getRevision( $paramName ); try { diff --git a/includes/Rest/Handler/DiscoveryHandler.php b/includes/Rest/Handler/DiscoveryHandler.php index 04fc989269ce..0a80c0b61b37 100644 --- a/includes/Rest/Handler/DiscoveryHandler.php +++ b/includes/Rest/Handler/DiscoveryHandler.php @@ -69,7 +69,7 @@ class DiscoveryHandler extends Handler { ]; } - private function getInfoSpec() { + private function getInfoSpec(): array { return [ 'title' => $this->options->get( MainConfigNames::Sitename ), 'mediawiki' => MW_VERSION, diff --git a/includes/Rest/Handler/Helper/RestStatusTrait.php b/includes/Rest/Handler/Helper/RestStatusTrait.php index ec0257920430..1d19309b8cd3 100644 --- a/includes/Rest/Handler/Helper/RestStatusTrait.php +++ b/includes/Rest/Handler/Helper/RestStatusTrait.php @@ -37,7 +37,7 @@ trait RestStatusTrait { throw new LocalizedHttpException( $msg, $code, $data ); } - private function getStatusErrorKeys( StatusValue $status ) { + private function getStatusErrorKeys( StatusValue $status ): array { $keys = []; foreach ( $status->getMessages() as $msg ) { diff --git a/includes/Rest/Handler/LanguageLinksHandler.php b/includes/Rest/Handler/LanguageLinksHandler.php index fd63c7a5c948..400f9cd1eb40 100644 --- a/includes/Rest/Handler/LanguageLinksHandler.php +++ b/includes/Rest/Handler/LanguageLinksHandler.php @@ -122,7 +122,7 @@ class LanguageLinksHandler extends SimpleHandler { ->createJson( $this->fetchLinks( $page->getId() ) ); } - private function fetchLinks( $pageId ) { + private function fetchLinks( int $pageId ): array { $result = []; $res = $this->dbProvider->getReplicaDatabase()->newSelectQueryBuilder() ->select( [ 'll_title', 'll_lang' ] ) diff --git a/includes/Rest/Handler/ModuleSpecHandler.php b/includes/Rest/Handler/ModuleSpecHandler.php index 9025815c34b4..07ba328ea267 100644 --- a/includes/Rest/Handler/ModuleSpecHandler.php +++ b/includes/Rest/Handler/ModuleSpecHandler.php @@ -144,7 +144,7 @@ class ModuleSpecHandler extends SimpleHandler { return $operationSpec; } - private function getComponentsSpec( Module $module ) { + private function getComponentsSpec( Module $module ): array { $components = []; // XXX: also collect reusable components from handler specs (but how to avoid name collisions?). diff --git a/includes/Rest/Handler/PageHistoryCountHandler.php b/includes/Rest/Handler/PageHistoryCountHandler.php index 4c8c3b6e8dae..e699ff859806 100644 --- a/includes/Rest/Handler/PageHistoryCountHandler.php +++ b/includes/Rest/Handler/PageHistoryCountHandler.php @@ -108,7 +108,7 @@ class PageHistoryCountHandler extends SimpleHandler { ); } - private function normalizeType( $type ) { + private function normalizeType( string $type ): string { return self::DEPRECATED_COUNT_TYPES[$type] ?? $type; } diff --git a/includes/Rest/Handler/ParsoidHandler.php b/includes/Rest/Handler/ParsoidHandler.php index 092cf92f0894..5729dd1b4bc8 100644 --- a/includes/Rest/Handler/ParsoidHandler.php +++ b/includes/Rest/Handler/ParsoidHandler.php @@ -648,7 +648,7 @@ abstract class ParsoidHandler extends Handler { private function wtLint( PageConfig $pageConfig, array $attribs, ?array $linterOverrides = [] - ) { + ): array { $envOptions = $attribs['envOptions'] + [ 'linterOverrides' => $linterOverrides, 'offsetType' => $attribs['offsetType'], diff --git a/includes/Rest/Router.php b/includes/Rest/Router.php index ff2497d6d10b..6960a07f8db5 100644 --- a/includes/Rest/Router.php +++ b/includes/Rest/Router.php @@ -338,7 +338,7 @@ class Router { return $this->moduleMap; } - private function getModuleInfo( $module ): ?array { + private function getModuleInfo( string $module ): ?array { $map = $this->getModuleMap(); return $map[$module] ?? null; } diff --git a/includes/Rest/i18n/ar.json b/includes/Rest/i18n/ar.json index db780db3ae9f..7737778374e2 100644 --- a/includes/Rest/i18n/ar.json +++ b/includes/Rest/i18n/ar.json @@ -141,5 +141,6 @@ "rest-schema-desc-mock-desc": "وصف وهمي.", "rest-schema-desc-revision-metadata": "مراجعة بيانات التعريف", "rest-schema-desc-media-file": "معلومات حول الملف", + "rest-schema-desc-page-history": "تاريخ مراجعة الصفحة", "rest-schema-desc-search-results": "نتائج البحث" } diff --git a/includes/Storage/DerivedPageDataUpdater.php b/includes/Storage/DerivedPageDataUpdater.php index e8e596230585..83d68b309664 100644 --- a/includes/Storage/DerivedPageDataUpdater.php +++ b/includes/Storage/DerivedPageDataUpdater.php @@ -26,7 +26,6 @@ use MediaWiki\ChangeTags\ChangeTags; use MediaWiki\ChangeTags\ChangeTagsStore; use MediaWiki\Config\ServiceOptions; use MediaWiki\Content\Content; -use MediaWiki\Content\ContentHandler; use MediaWiki\Content\IContentHandlerFactory; use MediaWiki\Content\Transform\ContentTransformer; use MediaWiki\Deferred\DeferrableUpdate; @@ -36,11 +35,9 @@ use MediaWiki\Deferred\RefreshSecondaryDataUpdate; use MediaWiki\Deferred\SiteStatsUpdate; use MediaWiki\DomainEvent\DomainEventDispatcher; use MediaWiki\Edit\PreparedEdit; -use MediaWiki\Exception\MWUnknownContentModelException; use MediaWiki\HookContainer\HookContainer; use MediaWiki\HookContainer\HookRunner; use MediaWiki\JobQueue\JobQueueGroup; -use MediaWiki\JobQueue\Jobs\CategoryMembershipChangeJob; use MediaWiki\JobQueue\Jobs\ParsoidCachePrewarmJob; use MediaWiki\Language\Language; use MediaWiki\MainConfigNames; @@ -160,11 +157,6 @@ class DerivedPageDataUpdater implements LoggerAwareInterface, PreparedUpdate { private $articleCountMethod; /** - * @var bool see $wgRCWatchCategoryMembership - */ - private $rcWatchCategoryMembership = false; - - /** * Stores (most of) the $options parameter of prepareUpdate(). * @see prepareUpdate() * @@ -535,14 +527,6 @@ class DerivedPageDataUpdater implements LoggerAwareInterface, PreparedUpdate { } /** - * @param bool $rcWatchCategoryMembership - * @see $wgRCWatchCategoryMembership - */ - public function setRcWatchCategoryMembership( $rcWatchCategoryMembership ) { - $this->rcWatchCategoryMembership = $rcWatchCategoryMembership; - } - - /** * @return Title */ private function getTitle() { @@ -738,16 +722,6 @@ class DerivedPageDataUpdater implements LoggerAwareInterface, PreparedUpdate { return $this->getRawSlot( $role )->getContent(); } - /** - * @param string $role slot role name - * @return ContentHandler - * @throws MWUnknownContentModelException - */ - private function getContentHandler( $role ): ContentHandler { - return $this->contentHandlerFactory - ->getContentHandler( $this->getRawSlot( $role )->getModel() ); - } - private function usePrimary(): bool { // TODO: can we just set a flag to true in prepareContent()? return $this->wikiPage->wasLoadedFrom( IDBAccessObject::READ_LATEST ); @@ -1591,24 +1565,6 @@ class DerivedPageDataUpdater implements LoggerAwareInterface, PreparedUpdate { 'defer' => DeferredUpdates::POSTSEND ] ); - // TODO: MCR: check if *any* changed slot supports categories! - if ( $this->rcWatchCategoryMembership - && $this->getContentHandler( SlotRecord::MAIN )->supportsCategories() === true - && $event->isNominalContentChange() - && !$event->hasCause( PageRevisionUpdatedEvent::CAUSE_UNDELETE ) - ) { - // Note: jobs are pushed after deferred updates, so the job should be able to see - // the recent change entry (also done via deferred updates) and carry over any - // bot/deletion/IP flags, ect. - $this->jobQueueGroup->lazyPush( - CategoryMembershipChangeJob::newSpec( - $this->getTitle(), - $this->revision->getTimestamp(), - $event->hasCause( PageUpdater::CAUSE_IMPORT ) - ) - ); - } - $id = $this->getPageId(); $title = $this->getTitle(); $wikiPage = $this->getWikiPage(); diff --git a/includes/Storage/PageUpdaterFactory.php b/includes/Storage/PageUpdaterFactory.php index 7bb77215d916..108bd2e3e8be 100644 --- a/includes/Storage/PageUpdaterFactory.php +++ b/includes/Storage/PageUpdaterFactory.php @@ -304,9 +304,6 @@ class PageUpdaterFactory { $derivedDataUpdater->setLogger( $this->logger ); $derivedDataUpdater->setArticleCountMethod( $this->options->get( MainConfigNames::ArticleCountMethod ) ); - $derivedDataUpdater->setRcWatchCategoryMembership( - $this->options->get( MainConfigNames::RCWatchCategoryMembership ) - ); return $derivedDataUpdater; } diff --git a/includes/api/ApiComparePages.php b/includes/api/ApiComparePages.php index e2ec95843ba5..334765d06b1d 100644 --- a/includes/api/ApiComparePages.php +++ b/includes/api/ApiComparePages.php @@ -38,6 +38,7 @@ use MediaWiki\Revision\SlotRoleRegistry; use MediaWiki\Title\Title; use MediaWiki\User\TempUser\TempUserCreator; use MediaWiki\User\UserFactory; +use MediaWiki\User\UserIdentity; use Wikimedia\ParamValidator\ParamValidator; use Wikimedia\RequestTimeout\TimeoutException; @@ -671,7 +672,7 @@ class ApiComparePages extends ApiBase { } } - private function getUserForPreview() { + private function getUserForPreview(): UserIdentity { $user = $this->getUser(); if ( $this->tempUserCreator->shouldAutoCreate( $user, 'edit' ) ) { return $this->userFactory->newUnsavedTempUser( diff --git a/includes/api/ApiHelpParamValueMessage.php b/includes/api/ApiHelpParamValueMessage.php index 6949af8887a1..695cc7063d97 100644 --- a/includes/api/ApiHelpParamValueMessage.php +++ b/includes/api/ApiHelpParamValueMessage.php @@ -128,7 +128,7 @@ class ApiHelpParamValueMessage extends Message { return $this->message; } - private function subMessage( $key ) { + private function subMessage( string $key ): string { $msg = new Message( $key ); $msg->isInterface = $this->isInterface; $msg->language = $this->language; diff --git a/includes/api/ApiOptions.php b/includes/api/ApiOptions.php index a1ccea47fedf..e3ab7a3e35f7 100644 --- a/includes/api/ApiOptions.php +++ b/includes/api/ApiOptions.php @@ -85,7 +85,7 @@ class ApiOptions extends ApiOptionsBase { ); } - private function getGlobalParam() { + private function getGlobalParam(): string { return $this->extractRequestParams()['global']; } diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php index 5d5bd20848aa..f147178055cf 100644 --- a/includes/api/ApiParse.php +++ b/includes/api/ApiParse.php @@ -59,6 +59,7 @@ use MediaWiki\Title\TitleFormatter; use MediaWiki\Title\TitleValue; use MediaWiki\User\TempUser\TempUserCreator; use MediaWiki\User\UserFactory; +use MediaWiki\User\UserIdentity; use MediaWiki\Utils\UrlUtils; use MediaWiki\WikiMap\WikiMap; use Wikimedia\ParamValidator\ParamValidator; @@ -150,7 +151,7 @@ class ApiParse extends ApiBase { PageReference $page, ?RevisionRecord $revision, ParserOptions $popts - ) { + ): ParserOutput { $worker = new PoolCounterWorkViaCallback( 'ApiParser', $this->getPoolKey(), [ 'doWork' => function () use ( $content, $page, $revision, $popts ) { @@ -166,7 +167,7 @@ class ApiParse extends ApiBase { return $worker->execute(); } - private function getUserForPreview() { + private function getUserForPreview(): UserIdentity { $user = $this->getUser(); if ( $this->tempUserCreator->shouldAutoCreate( $user, 'edit' ) ) { return $this->userFactory->newUnsavedTempUser( @@ -178,7 +179,7 @@ class ApiParse extends ApiBase { private function getPageParserOutput( WikiPage $page, - $revId, + ?int $revId, ParserOptions $popts, bool $suppressCache ) { @@ -974,7 +975,7 @@ class ApiParse extends ApiBase { return $result; } - private function formatCategoryLinks( $links ) { + private function formatCategoryLinks( array $links ): array { $result = []; if ( !$links ) { @@ -1038,7 +1039,7 @@ class ApiParse extends ApiBase { return $result; } - private function formatIWLinks( $iw ) { + private function formatIWLinks( array $iw ): array { $result = []; foreach ( $iw as $linkTarget ) { $entry = []; @@ -1055,7 +1056,7 @@ class ApiParse extends ApiBase { return $result; } - private function formatHeadItems( $headItems ) { + private function formatHeadItems( array $headItems ): array { $result = []; foreach ( $headItems as $tag => $content ) { $entry = []; @@ -1067,7 +1068,7 @@ class ApiParse extends ApiBase { return $result; } - private function formatLimitReportData( $limitReportData ) { + private function formatLimitReportData( array $limitReportData ): array { $result = []; foreach ( $limitReportData as $name => $value ) { @@ -1084,7 +1085,7 @@ class ApiParse extends ApiBase { return $result; } - private function setIndexedTagNames( &$array, $mapping ) { + private function setIndexedTagNames( array &$array, array $mapping ) { foreach ( $mapping as $key => $name ) { if ( isset( $array[$key] ) ) { ApiResult::setIndexedTagName( $array[$key], $name ); diff --git a/includes/api/ApiQueryBacklinksprop.php b/includes/api/ApiQueryBacklinksprop.php index 819a60896428..342972254908 100644 --- a/includes/api/ApiQueryBacklinksprop.php +++ b/includes/api/ApiQueryBacklinksprop.php @@ -359,7 +359,7 @@ class ApiQueryBacklinksprop extends ApiQueryGeneratorBase { } } - private function setContinue( $row, $sortby ) { + private function setContinue( \stdClass $row, array $sortby ) { $cont = []; foreach ( $sortby as $field => $v ) { $cont[] = $row->$field; diff --git a/includes/api/ApiQueryExtLinksUsage.php b/includes/api/ApiQueryExtLinksUsage.php index ee28e4012cce..685ab9524884 100644 --- a/includes/api/ApiQueryExtLinksUsage.php +++ b/includes/api/ApiQueryExtLinksUsage.php @@ -190,7 +190,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { } } - private function setContinue( $orderBy, $row ) { + private function setContinue( array $orderBy, \stdClass $row ) { $fields = []; foreach ( $orderBy as $field ) { $fields[] = strtr( $row->$field, [ '%' => '%25', '|' => '%7C' ] ); diff --git a/includes/api/ApiQueryExternalLinks.php b/includes/api/ApiQueryExternalLinks.php index 9c47efba6342..67cb161a2582 100644 --- a/includes/api/ApiQueryExternalLinks.php +++ b/includes/api/ApiQueryExternalLinks.php @@ -127,7 +127,7 @@ class ApiQueryExternalLinks extends ApiQueryBase { } } - private function setContinue( $orderBy, $row ) { + private function setContinue( array $orderBy, \stdClass $row ) { $fields = []; foreach ( $orderBy as $field ) { $fields[] = strtr( $row->$field, [ '%' => '%25', '|' => '%7C' ] ); diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php index 9382d43652c7..b5bb5bcd9bcc 100644 --- a/includes/api/ApiQueryInfo.php +++ b/includes/api/ApiQueryInfo.php @@ -845,7 +845,7 @@ class ApiQueryInfo extends ApiQueryBase { } } - private function getAllVariants( $text, $ns = NS_MAIN ) { + private function getAllVariants( string $text, int $ns = NS_MAIN ): array { $result = []; foreach ( $this->languageConverter->getVariants() as $variant ) { $convertTitle = $this->languageConverter->autoConvert( $text, $variant ); diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php index 8d9bdfb92d9c..3d0f42aa303c 100644 --- a/includes/api/ApiQueryLogEvents.php +++ b/includes/api/ApiQueryLogEvents.php @@ -328,7 +328,7 @@ class ApiQueryLogEvents extends ApiQueryBase { $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'item' ); } - private function extractRowInfo( $row ) { + private function extractRowInfo( \stdClass $row ): array { $logEntry = DatabaseLogEntry::newFromRow( $row ); $vals = [ ApiResult::META_TYPE => 'assoc', diff --git a/includes/api/ApiQueryRevisionsBase.php b/includes/api/ApiQueryRevisionsBase.php index 2e246b0519b1..7f525d836aeb 100644 --- a/includes/api/ApiQueryRevisionsBase.php +++ b/includes/api/ApiQueryRevisionsBase.php @@ -43,6 +43,7 @@ use MediaWiki\Revision\SlotRoleRegistry; use MediaWiki\Title\Title; use MediaWiki\User\TempUser\TempUserCreator; use MediaWiki\User\UserFactory; +use MediaWiki\User\UserIdentity; use MediaWiki\User\UserNameUtils; use stdClass; use Wikimedia\ParamValidator\ParamValidator; @@ -788,7 +789,7 @@ abstract class ApiQueryRevisionsBase extends ApiQueryGeneratorBase { return $vals; } - private function getUserForPreview() { + private function getUserForPreview(): UserIdentity { $user = $this->getUser(); if ( $this->tempUserCreator->shouldAutoCreate( $user, 'edit' ) ) { return $this->userFactory->newUnsavedTempUser( diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php index a5007c560704..771d30bce773 100644 --- a/includes/api/ApiQuerySiteinfo.php +++ b/includes/api/ApiQuerySiteinfo.php @@ -950,7 +950,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { return $this->getResult()->addValue( 'query', $property, $config ); } - private function getAutoPromoteConds() { + private function getAutoPromoteConds(): array { $allowedConditions = []; foreach ( get_defined_constants() as $constantName => $constantValue ) { if ( strpos( $constantName, 'APCOND_' ) !== false ) { @@ -960,7 +960,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { return $allowedConditions; } - private function processAutoPromote( $input, $allowedConditions ) { + private function processAutoPromote( array $input, array $allowedConditions ): array { $data = []; foreach ( $input as $groupName => $conditions ) { $row = $this->recAutopromote( $conditions, $allowedConditions ); @@ -972,7 +972,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { return $data; } - private function appendAutoPromote( $property ) { + private function appendAutoPromote( string $property ): bool { return $this->getResult()->addValue( 'query', $property, @@ -983,7 +983,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { ); } - private function appendAutoPromoteOnce( $property ) { + private function appendAutoPromoteOnce( string $property ): bool { $allowedConditions = $this->getAutoPromoteConds(); $data = []; foreach ( $this->getConfig()->get( MainConfigNames::AutopromoteOnce ) as $key => $value ) { diff --git a/includes/api/ApiQueryUserContribs.php b/includes/api/ApiQueryUserContribs.php index 3ac4bc5b234f..9dced1c324f1 100644 --- a/includes/api/ApiQueryUserContribs.php +++ b/includes/api/ApiQueryUserContribs.php @@ -641,7 +641,7 @@ class ApiQueryUserContribs extends ApiQueryBase { return $vals; } - private function continueStr( $row ) { + private function continueStr( \stdClass $row ): string { if ( $this->multiUserMode ) { switch ( $this->orderBy ) { case 'name': diff --git a/includes/api/ApiQueryWatchlist.php b/includes/api/ApiQueryWatchlist.php index 5f6db828bb46..68461145baa0 100644 --- a/includes/api/ApiQueryWatchlist.php +++ b/includes/api/ApiQueryWatchlist.php @@ -268,7 +268,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { } } - private function getFieldsToInclude() { + private function getFieldsToInclude(): array { $includeFields = []; if ( $this->fld_flags ) { $includeFields[] = WatchedItemQueryService::INCLUDE_FLAGS; @@ -298,7 +298,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { return $includeFields; } - private function showParamsConflicting( array $show ) { + private function showParamsConflicting( array $show ): bool { return ( isset( $show[WatchedItemQueryService::FILTER_MINOR] ) && isset( $show[WatchedItemQueryService::FILTER_NOT_MINOR] ) ) || ( isset( $show[WatchedItemQueryService::FILTER_BOT] ) @@ -315,7 +315,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { && isset( $show[WatchedItemQueryService::FILTER_NOT_UNREAD] ) ); } - private function extractOutputData( WatchedItem $watchedItem, array $recentChangeInfo ) { + private function extractOutputData( WatchedItem $watchedItem, array $recentChangeInfo ): array { /* Determine the title of the page that has been changed. */ $target = $watchedItem->getTarget(); if ( $target instanceof LinkTarget ) { diff --git a/includes/api/ApiRevisionDelete.php b/includes/api/ApiRevisionDelete.php index 77bdb99dd18f..8a6df9da0636 100644 --- a/includes/api/ApiRevisionDelete.php +++ b/includes/api/ApiRevisionDelete.php @@ -134,7 +134,7 @@ class ApiRevisionDelete extends ApiBase { $result->addValue( null, $this->getModuleName(), $data ); } - private function extractStatusInfo( Status $status ) { + private function extractStatusInfo( Status $status ): array { $ret = [ 'status' => $status->isOK() ? 'Success' : 'Fail', ]; diff --git a/includes/api/ApiStashEdit.php b/includes/api/ApiStashEdit.php index f7bd10cff434..4147d3176dbe 100644 --- a/includes/api/ApiStashEdit.php +++ b/includes/api/ApiStashEdit.php @@ -28,6 +28,7 @@ use MediaWiki\Revision\SlotRecord; use MediaWiki\Storage\PageEditStash; use MediaWiki\User\TempUser\TempUserCreator; use MediaWiki\User\UserFactory; +use MediaWiki\User\UserIdentity; use Wikimedia\ParamValidator\ParamValidator; use Wikimedia\Stats\StatsFactory; @@ -208,7 +209,7 @@ class ApiStashEdit extends ApiBase { $this->getResult()->addValue( null, $this->getModuleName(), $ret ); } - private function getUserForPreview() { + private function getUserForPreview(): UserIdentity { $user = $this->getUser(); if ( $this->tempUserCreator->shouldAutoCreate( $user, 'edit' ) ) { return $this->userFactory->newUnsavedTempUser( diff --git a/includes/api/ApiWatch.php b/includes/api/ApiWatch.php index acb071a6a846..7d9dea1fffec 100644 --- a/includes/api/ApiWatch.php +++ b/includes/api/ApiWatch.php @@ -132,8 +132,8 @@ class ApiWatch extends ApiBase { } private function watchTitle( PageIdentity $page, User $user, array $params, - $compatibilityMode = false - ) { + bool $compatibilityMode = false + ): array { $res = [ 'title' => $this->titleFormatter->getPrefixedText( $page ), 'ns' => $page->getNamespace() ]; if ( !$this->watchlistManager->isWatchable( $page ) ) { diff --git a/includes/api/i18n/pt-br.json b/includes/api/i18n/pt-br.json index 9dba97e178e7..e9cb4c0d51a6 100644 --- a/includes/api/i18n/pt-br.json +++ b/includes/api/i18n/pt-br.json @@ -32,45 +32,52 @@ "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|Documentação]]\n* [[mw:Special:MyLanguage/API:Etiquette|Etiqueta & diretrizes de uso]]\n* [[mw:Special:MyLanguage/API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/postorius/lists/mediawiki-api.lists.wikimedia.org/ Lista de discussão]\n* [https://lists.wikimedia.org/postorius/lists/mediawiki-api-announce.lists.wikimedia.org/ Anúncios da API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Erros e pedidos]\n</div>\n<strong>Estado:</strong> A API do MediaWiki é uma interface madura e estável que é ativamente suportada e aprimorada. Por mais que tentemos evitar, ocasionalmente talvez precisemos fazer mudanças estruturais; inscreva-se na [https://lists.wikimedia.org/hyperkitty/list/mediawiki-api-announce@lists.wikimedia.org/ lista de discussão mediawiki-api-announce] para ser informado acerca das atualizações.\n\n<strong>Pedidos incorretos:</strong> Quando são enviados pedidos incorretos à API, será devolvido um cabeçalho HTTP com a chave \"MediaWiki-API-Error\" e depois tanto o valor desse cabeçalho como o código de erro devolvido serão definidos com o mesmo valor. Para mais informação, consulte [[mw:Special:MyLanguage/API:Errors_and_warnings|API:Erros e avisos]].\n\n<p class=\"mw-apisandbox-link\"><strong>Testes:</strong> Para testar facilmente pedidos à API, visite [[Special:ApiSandbox|Testes da API]].</p>", "apihelp-main-param-action": "Qual ação executar.", "apihelp-main-param-format": "O formato da saída.", - "apihelp-main-param-maxlag": "O atraso máximo pode ser usado quando o MediaWiki está instalado em um cluster replicado no banco de dados. Para salvar as ações que causam mais atraso na replicação do site, esse parâmetro pode fazer o cliente aguardar até que o atraso da replicação seja menor do que o valor especificado. Em caso de atraso excessivo, o código de erro <samp>maxlag</samp> é retornado com uma mensagem como <samp>Waiting for $host: $lag seconds lagged</samp>.<br />Veja [[mw:Special:MyLanguage/Manual:Maxlag_parameter|Manual: Maxlag parameter]] para mais informações.", - "apihelp-main-param-smaxage": "Define o cabeçalho HTTP de controle de cache <code>s-maxage</code> para esta quantidade de segundos. Erros não são armazenados em cache.", - "apihelp-main-param-maxage": "Define o cabeçalho HTTP de controle de cache <code>max-age</code> para esta quantidade de segundos. Erros não são armazenados em cache.", - "apihelp-main-param-assert": "Verifique se o usuário está logado se configurado para <kbd>user</kbd>, <em>not</em> logado se definido como <kbd>anon</kbd> ou tem o direito do usuário do bot se <kbd>bot</kbd>.", - "apihelp-main-param-assertuser": "Verificar que o usuário atual é o utilizador nomeado.", + "apihelp-main-param-maxlag": "O atraso máximo pode ser usado quando o MediaWiki está instalado em um cluster replicado no banco de dados. Para salvar as ações que causam mais atraso na replicação do site, esse parâmetro pode fazer o cliente aguardar até que o atraso da replicação seja menor do que o valor especificado. Em caso de atraso excessivo, o código de erro <samp>maxlag</samp> é retornado com uma mensagem como <samp>Waiting for $host: $lag seconds lagged</samp>.<br />Veja [[mw:Special:MyLanguage/Manual:Maxlag_parameter|Manual: Parâmetro maxlag]] para mais informações.", + "apihelp-main-param-smaxage": "Definir o cabeçalho HTTP de controle de cache <code>s-maxage</code> para esta quantidade de segundos. Erros não são armazenados em cache.", + "apihelp-main-param-maxage": "Definir o cabeçalho HTTP de controle de cache <code>max-age</code> para esta quantidade de segundos. Erros não são armazenados em cache.", + "apihelp-main-param-assert": "Verificar se o usuário está logado (incluindo possivelmente como usuário temporário) quando definido como <kbd>user</kbd>, se <em>não</em> está logado quando definido como <kbd>anon</kbd>, ou se tem o direito de usuário de robô quando definido como <kbd>bot</kbd>.", + "apihelp-main-param-assertuser": "Verificar se o usuário atual é o usuário nomeado.", "apihelp-main-param-requestid": "Qualquer valor dado aqui será incluído na resposta. Pode ser usado para distinguir requisições.", - "apihelp-main-param-servedby": "Incluir nos resultados o nome do servidor que serviu o pedido.", - "apihelp-main-param-curtimestamp": "Inclui o timestamp atual no resultado.", - "apihelp-main-param-responselanginfo": "Inclua os idiomas usados para <var>uselang</var> e <var>errorlang</var> no resultado.", - "apihelp-main-param-origin": "Ao acessar a API usando uma solicitação AJAX por domínio cruzado (CORS), defina isto como o domínio de origem. Isto deve estar incluso em toda solicitação ''pre-flight'', sendo portanto parte do URI da solicitação (ao invés do corpo do POST).\n\nPara solicitações autenticadas, isto deve corresponder a uma das origens no cabeçalho <code>Origin</code>, para que seja algo como <kbd>https://pt.wikipedia.org</kbd> ou <kbd>https://meta.wikimedia.org</kbd>. Se este parâmetro não corresponder ao cabeçalho <code>Origin</code>, uma resposta 403 será retornada. Se este parâmetro corresponder ao cabeçalho <code>Origin</code> e a origem for permitida (''whitelisted''), os cabeçalhos <code>Access-Control-Allow-Origin</code> e <code>Access-Control-Allow-Credentials</code> serão definidos.\n\nPara solicitações não autenticadas, especifique o valor <kbd>*</kbd>. Isto fará com que o cabeçalho <code>Access-Control-Allow-Origin</code> seja definido, porém o <code>Access-Control-Allow-Credentials</code> será <code>false</code> e todos os dados específicos para usuários tornar-se-ão restritos.", - "apihelp-main-param-uselang": "Linguagem a ser usada para traduções de mensagens. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> com <kbd>siprop=languages</kbd> retorna uma lista de códigos de idioma ou especifique <kbd>user</kbd> para usar a preferência de idioma do usuário atual ou especifique <kbd>content</kbd> para usar o idioma de conteúdo desta wiki.", - "apihelp-main-param-errorformat": "Formato a ser usado aviso e saída de texto de erro.\n; Texto simples: Texto wiki com tags HTML removidas e entidades substituídas.\n; Wikitext: Unparsed wikitext. \n; html: HTML.\n; Bruto: chave e parâmetros da mensagem.\n; Nenhum: sem saída de texto, apenas os códigos de erro.\n; Bc: Formato usado antes do MediaWiki 1.29. <var>errorlang</var> e <var>errorsuselocal</var> são ignorados.", + "apihelp-main-param-servedby": "Incluir nos resultados o hostname que serviu a requisição.", + "apihelp-main-param-curtimestamp": "Incluir no resultado o timestamp atual.", + "apihelp-main-param-responselanginfo": "Incluir no resultado os idiomas usados para <var>uselang</var> e <var>errorlang</var>.", + "apihelp-main-param-origin": "Ao acessar a API usando uma requisição AJAX por domínio cruzado (CORS), defina isto como o domínio de origem. Isto deve estar incluso em toda requisição “pre-flight”, sendo portanto parte do URI da requisição (ao invés do corpo do POST).\n\nPara requisições autenticadas, isto deve corresponder exatamente a uma das origens no cabeçalho <code>Origin</code>, para que seja algo como <kbd>https://pt.wikipedia.org</kbd> ou <kbd>https://meta.wikimedia.org</kbd>. Se este parâmetro não corresponder ao cabeçalho <code>Origin</code>, uma resposta 403 será retornada. Se este parâmetro corresponder ao cabeçalho <code>Origin</code> e a origem for permitida, os cabeçalhos <code>Access-Control-Allow-Origin</code> e <code>Access-Control-Allow-Credentials</code> serão definidos.\n\nPara requisições não autenticadas, especifique o valor <kbd>*</kbd>. Isto fará com que o cabeçalho <code>Access-Control-Allow-Origin</code> seja definido, porém o <code>Access-Control-Allow-Credentials</code> será <code>false</code> e todos os dados específicos de usuário tornar-se-ão restritos.", + "apihelp-main-param-crossorigin": "Ao acessar a API usando uma requisição AJAX por domínio cruzado (CORS) e um provedor de sessão seguro contra ataques de falsificação de requisição entre websites (CSRF), como o provedor OAuth, use isto em vez de <code>origin=*</code> para tornar a requisição autenticada (ou seja, evitar que ela não esteja logada). Isto deve estar incluso em toda requisição “pre-flight”, sendo portanto parte do URI da requisição (ao invés do corpo do POST).\n\nNote que a maioria dos provedores de sessão, incluindo sessões padrões baseadas em cookies, não são compatíveis com CORS autenticado e, portanto, não podem ser utilizadas com este parâmetro.", + "apihelp-main-param-uselang": "Idioma a ser usado para traduções de mensagens. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo&siprop=languages]]</kbd> retorna uma lista de códigos de idioma. Alternativamente, especifique <kbd>user</kbd> para usar a preferência de idioma do usuário atual ou especifique <kbd>content</kbd> para usar o idioma de conteúdo desta wiki.", + "apihelp-main-param-variant": "Variante do idioma. Funciona somente se o idioma-base for compatível com conversão para suas variantes.", + "apihelp-main-param-errorformat": "Formato a ser usado para textos de avisos e erros na saída", + "apihelp-main-paramvalue-errorformat-plaintext": "Wikitexto com marcações HTML removidas e entidades substituidas.", "apihelp-main-paramvalue-errorformat-wikitext": "Wikitexto não analisado.", "apihelp-main-paramvalue-errorformat-raw": "Chave e parâmetros da mensagem.", "apihelp-main-paramvalue-errorformat-none": "Sem saída de texto, apenas os códigos de erro.", "apihelp-main-paramvalue-errorformat-bc": "Formato usado antes do MediaWiki 1.29. <var>errorlang</var> e <var>errorsuselocal</var> serão ignorados.", - "apihelp-main-param-errorlang": "Linguagem a utilizar para avisos e erros. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> com <kbd>siprop=languages</kbd> retorna uma lista de códigos de idioma ou especifique <kbd>content</kbd> para usar o idioma do conteúdo desta wiki ou especifique <kbd>uselang</kbd> para usar o mesmo valor que o parâmetro <var>uselang</var>.", - "apihelp-main-param-errorsuselocal": "Se for dado, os textos de erro usarão mensagens customizadas localmente a partir do espaço nominal {{ns: MediaWiki}}.", + "apihelp-main-param-errorlang": "Idioma a ser utilizado para avisos e erros. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo&siprop=languages]]</kbd> retorna uma lista de códigos de idioma. Alternativamente, especifique <kbd>content</kbd> para usar o idioma do conteúdo desta wiki ou <kbd>uselang</kbd> para usar o mesmo valor que o parâmetro <var>uselang</var>.", + "apihelp-main-param-errorsuselocal": "Se fornecido, os textos de erro usarão mensagens personalizadas localmente a partir do espaço nominal {{ns:MediaWiki}}.", + "apihelp-acquiretempusername-summary": "Adquirir um nome de usuário temporário e armazená-lo na sessão atual caso a criação de contas temporárias esteja ativada e o usuário atual não estiver logado. Se um nome já foi armazenado, retorna esse nome.", + "apihelp-acquiretempusername-extended-description": "Se o usuário posteriormente realizar uma ação que resulte na criação de conta temporária, o nome de usuário armazenado será utilizado para a conta criada. Ele também poderá ser utilizado em previsões. Entretanto, a conta ainda não terá sido criada, e o nome não estará visível a outros usuários.", "apihelp-block-summary": "Bloquear um usuário.", - "apihelp-block-param-user": "Usuário para bloquear.", - "apihelp-block-param-userid": "Especifique <kbd>$1user=#<var>ID</var></kbd>.", - "apihelp-block-param-expiry": "Tempo de expiração. Pode ser relativo (por exemplo <kbd>5 meses</kbd> ou <kbd>2 semanas</kbd>) ou absoluto (por exemplo <kbd>2014-09-18T12:34:56Z</kbd>). Se definido para <kbd>infinite</kbd>, <kbd>indefinite</kbd> ou <kbd>never</kbd>, o bloqueio nunca irá expirar.", + "apihelp-block-param-id": "O ID de bloqueio a ser modificado.", + "apihelp-block-param-user": "Usuário a ser bloqueado.", + "apihelp-block-param-userid": "Ao invés, especifique <kbd>$1user=#<var>ID</var></kbd>.", + "apihelp-block-param-expiry": "Tempo de expiração. Pode ser relativo (p. ex., <kbd>5 months</kbd> ou <kbd>2 weeks</kbd>) ou absoluto (p. ex., <kbd>2014-09-18T12:34:56Z</kbd>). Se definido como <kbd>infinite</kbd>, <kbd>indefinite</kbd> ou <kbd>never</kbd>, o bloqueio nunca irá expirar.", "apihelp-block-param-reason": "Razão do bloqueio.", - "apihelp-block-param-anononly": "Bloqueia apenas usuários anônimos (ou seja. desativa edições anônimas para este endereço IP).", + "apihelp-block-param-anononly": "Bloqueia apenas usuários anônimos (ou seja, desativa edições anônimas para este endereço IP, incluindo edições de contas temporárias).", "apihelp-block-param-nocreate": "Prevenir a criação de conta.", - "apihelp-block-param-autoblock": "Bloquear automaticamente o endereço IP usado e quaisquer endereços IPs subsequentes que tentarem acessar a partir deles.", + "apihelp-block-param-autoblock": "Bloquear automaticamente o último endereço IP utilizado e quaisquer endereços IP subsequentes a partir dos quais houver tentativa de login.", "apihelp-block-param-noemail": "Impedir que o usuário envie e-mails através da wiki. (Requer o direito <code>blockemail</code>).", - "apihelp-block-param-hidename": "Oculta o nome do usuário do ''log'' de bloqueio. (Requer o direito <code>hideuser</code>).", + "apihelp-block-param-hidename": "Oculta o nome do usuário dos registros de bloqueio. (Requer o direito <code>hideuser</code>).", "apihelp-block-param-allowusertalk": "Permitir que o usuário edite sua própria página de discussão (depende de <var>[[mw:Special:MyLanguage/Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var>).", - "apihelp-block-param-reblock": "Se o usuário já estiver bloqueado, sobrescrever o bloqueio existente.", + "apihelp-block-param-reblock": "Se o usuário já estiver bloqueado por um único bloqueio, sobrescrever o bloqueio existente. Se o usuário estiver com múltiplos bloqueios, isso falhará; utilize o parâmetro <var>id</var> para especificar qual bloqueio será sobrescrito.", + "apihelp-block-param-newblock": "Adicionar outro bloqueio mesmo se o usuário já estiver bloqueado.", "apihelp-block-param-watchuser": "Vigiar as páginas de usuário e de discussão, do usuário ou do endereço IP.", "apihelp-block-param-watchlistexpiry": "Carimbo de data/hora de expiração da lista de páginas vigiadas. Omita esse parâmetro inteiramente para manter inalterada a validade atual.", "apihelp-block-param-tags": "As tags de alteração a serem aplicadas à entrada no registro de bloqueio.", - "apihelp-block-param-partial": "Bloquear o usuário de acessar a páginas ou espaços nominais específicos, em vez de a todo o site.", - "apihelp-block-param-pagerestrictions": "Lista de títulos que o bloqueio impedirá o usuário de editar. Só se aplica quando <var>'partial</var>' (parcial) estiver definido como 'true' (verdadeiro).", - "apihelp-block-param-namespacerestrictions": "Lista de títulos que o bloqueio impedirá o usuário de editar. Só se aplica quando <var>'partial</var>' (parcial) estiver definido como 'true' (verdadeiro).", - "apihelp-block-param-actionrestrictions": "Lista de ações para bloquear a execução do usuário. Aplica-se apenas quando <var>parcial</var> é definido como verdadeiro.", - "apihelp-block-example-ip-simple": "Bloquear endereço IP <kbd>192.0.2.5</kbd> por três dias com a razão;", - "apihelp-block-example-user-complex": "Bloquear usuário <kbd>Vandal</kbd> indefinidamente com a razão e o impedir de criar nova conta e de enviar e-mails.", + "apihelp-block-param-partial": "Bloquear o usuário de acessar a páginas ou espaços nominais específicos, em vez de a todo o website.", + "apihelp-block-param-pagerestrictions": "Lista de títulos que o bloqueio impedirá o usuário de editar. Só se aplica quando <var>partial</var> estiver definido como true.", + "apihelp-block-param-namespacerestrictions": "Lista de IDs de espaços nominais que o bloqueio impedirá o usuário de editar. Só se aplica quando <var>partial</var> estiver definido como true.", + "apihelp-block-param-actionrestrictions": "Lista de ações que o bloqueio impedirá o usuário de executar. Só se aplica quando <var>partial</var> estiver definido como true.", + "apihelp-block-example-ip-simple": "Bloquear endereço IP <kbd>192.0.2.5</kbd> por três dias com um motivo.", + "apihelp-block-example-user-complex": "Bloquear usuário <kbd>Vandal</kbd> indefinidamente com um motivo, e impedi-lo de criar nova conta e de enviar e-mails.", "apihelp-changeauthenticationdata-summary": "Alterar os dados de autenticação para o usuário atual.", "apihelp-changeauthenticationdata-example-password": "Tenta alterar a senha do usuário atual para <kbd>ExamplePassword</kbd>.", "apihelp-changecontentmodel-summary": "Alterar o modelo de conteúdo de uma página", @@ -88,7 +95,7 @@ "apihelp-checktoken-example-simple": "Testa a validade de um token <kbd>csrf</kbd>.", "apihelp-clearhasmsg-summary": "Limpa a etiqueta <code>hasmsg</code> do usuário atual.", "apihelp-clearhasmsg-example-1": "Limpa a etiqueta <code>hasmsg</code> do usuário atual.", - "apihelp-clientlogin-summary": "Faça o login no wiki usando o fluxo interativo.", + "apihelp-clientlogin-summary": "Entrar na wiki usando o fluxo interativo.", "apihelp-clientlogin-example-login": "Comeca o processo de logar na wiki como usuário <kbd>Exemple</kbd> com a senha <kbd>ExamplePassword</kbd>.", "apihelp-clientlogin-example-login2": "Continuar efetuando login após uma resposta <samp>UI</samp> para autenticação de dois fatores, fornecendo um <var>OATHToken</var> de <kbd>987654</ kbd>.", "apihelp-compare-summary": "Obter a diferença entre duas páginas.", @@ -197,7 +204,7 @@ "apihelp-expandtemplates-param-prop": "Quais peças de informação obter.\n\nNote que se nenhum valor for selecionado, o resultado conterá o texto wiki, mas o resultado será em um formato obsoleto.", "apihelp-expandtemplates-paramvalue-prop-wikitext": "O texto wiki expandido.", "apihelp-expandtemplates-paramvalue-prop-categories": "Quaisquer categorias presentes na entrada que não estão representadas na saída wikitext.", - "apihelp-expandtemplates-paramvalue-prop-properties": "Propriedades da página definidas por palavras mágicas expandidas no texto wiki.", + "apihelp-expandtemplates-paramvalue-prop-properties": "Propriedades da página definidas por palavras mágicas expandidas no wikitexto.", "apihelp-expandtemplates-paramvalue-prop-volatile": "Se a saída é volátil e não deve ser reutilizada em outro lugar dentro da página.", "apihelp-expandtemplates-paramvalue-prop-ttl": "O tempo máximo após o qual os caches do resultado devem ser invalidados.", "apihelp-expandtemplates-paramvalue-prop-modules": "Quaisquer módulos ResourceLoader que as funções do analisador solicitaram foram adicionados à saída. Contudo, <kbd>jsconfigvars</kbd> ou <kbd>encodedjsconfigvars</kbd> devem ser solicitados em conjunto com <kbd>modules</kbd>.", @@ -370,13 +377,13 @@ "apihelp-parse-param-prop": "Qual pedaço de informação obter:", "apihelp-parse-paramvalue-prop-text": "Fornece o texto analisado do texto wiki.", "apihelp-parse-paramvalue-prop-langlinks": "Fornece os links de idiomas do texto wiki analisado.", - "apihelp-parse-paramvalue-prop-categories": "Fornece as categorias no texto wiki analisado.", + "apihelp-parse-paramvalue-prop-categories": "Fornece as categorias no wikitexto analisado.", "apihelp-parse-paramvalue-prop-categorieshtml": "Fornece a versão HTML das categorias.", "apihelp-parse-paramvalue-prop-links": "Fornece os links internos do texto wiki analisado.", - "apihelp-parse-paramvalue-prop-templates": "Fornece a predefinição no texto wiki analisado.", - "apihelp-parse-paramvalue-prop-images": "Fornece as imagens no texto wiki analisado.", - "apihelp-parse-paramvalue-prop-externallinks": "Fornece os links externos no texto wiki analisado.", - "apihelp-parse-paramvalue-prop-sections": "Fornece as seções no texto wiki analisado.", + "apihelp-parse-paramvalue-prop-templates": "Fornece as predefinições no wikitexto analisado.", + "apihelp-parse-paramvalue-prop-images": "Fornece as imagens no wikitexto analisado.", + "apihelp-parse-paramvalue-prop-externallinks": "Fornece as ligações externas no wikitexto analisado.", + "apihelp-parse-paramvalue-prop-sections": "Fornece as seções no wikitexto analisado.", "apihelp-parse-paramvalue-prop-revid": "Adiciona o ID da revisão da página analisada.", "apihelp-parse-paramvalue-prop-displaytitle": "Adiciona o título do texto wiki analisado.", "apihelp-parse-paramvalue-prop-subtitle": "Adiciona o subtítulo de página da página a que foi feita a análise sintática.", @@ -386,9 +393,9 @@ "apihelp-parse-paramvalue-prop-jsconfigvars": "Fornece as variáveis de configuração JavaScript específicas da página. Para aplicar, use <code>mw.config.set()</code>.", "apihelp-parse-paramvalue-prop-encodedjsconfigvars": "Fornece as variáveis de configuração JavaScript específicas da página como uma string JSON.", "apihelp-parse-paramvalue-prop-indicators": "Fornece o HTML de indicadores de ''status'' de página utilizados na página.", - "apihelp-parse-paramvalue-prop-iwlinks": "Fornece links interwiki no texto wiki analisado.", + "apihelp-parse-paramvalue-prop-iwlinks": "Fornece ligações interwiki no wikitexto analisado.", "apihelp-parse-paramvalue-prop-wikitext": "Fornece o texto wiki original que foi analisado.", - "apihelp-parse-paramvalue-prop-properties": "Fornece várias propriedades definidas no texto wiki analisado.", + "apihelp-parse-paramvalue-prop-properties": "Fornece propriedades variadas definidas no wikitexto analisado.", "apihelp-parse-paramvalue-prop-limitreportdata": "Fornece o relatório limite de uma forma estruturada. Não informa dado, quando<var>$1disablelimitreport</var> está definido.", "apihelp-parse-paramvalue-prop-limitreporthtml": "Retorna a versão HTML do relatório de limite. Não retorna dados quando <var>$1disablelimitreport</var> está definido.", "apihelp-parse-paramvalue-prop-parsetree": "A árvore de análise XML do conteúdo da revisão (requer modelo de conteúdo <code>$1</code>)", diff --git a/includes/auth/LocalPasswordPrimaryAuthenticationProvider.php b/includes/auth/LocalPasswordPrimaryAuthenticationProvider.php index c37313376a44..b6d0384fd472 100644 --- a/includes/auth/LocalPasswordPrimaryAuthenticationProvider.php +++ b/includes/auth/LocalPasswordPrimaryAuthenticationProvider.php @@ -21,10 +21,14 @@ namespace MediaWiki\Auth; +use BadMethodCallException; use MediaWiki\Deferred\DeferredUpdates; use MediaWiki\MainConfigNames; use MediaWiki\Password\InvalidPassword; +use MediaWiki\Status\Status; use MediaWiki\User\UserRigorOptions; +use StatusValue; +use stdClass; use Wikimedia\Rdbms\DBAccessObjectUtils; use Wikimedia\Rdbms\IConnectionProvider; use Wikimedia\Rdbms\IDBAccessObject; @@ -60,8 +64,8 @@ class LocalPasswordPrimaryAuthenticationProvider * Check if the password has expired and needs a reset * * @param string $username - * @param \stdClass $row A row from the user table - * @return \stdClass|null + * @param stdClass $row A row from the user table + * @return stdClass|null */ protected function getPasswordResetData( $username, $row ) { $now = (int)wfTimestamp(); @@ -74,12 +78,12 @@ class LocalPasswordPrimaryAuthenticationProvider if ( (int)$expiration + $grace < $now ) { $data = [ 'hard' => true, - 'msg' => \MediaWiki\Status\Status::newFatal( 'resetpass-expired' )->getMessage(), + 'msg' => Status::newFatal( 'resetpass-expired' )->getMessage(), ]; } else { $data = [ 'hard' => false, - 'msg' => \MediaWiki\Status\Status::newFatal( 'resetpass-expired-soft' )->getMessage(), + 'msg' => Status::newFatal( 'resetpass-expired-soft' )->getMessage(), ]; } @@ -112,7 +116,7 @@ class LocalPasswordPrimaryAuthenticationProvider $oldRow = clone $row; // Check for *really* old password hashes that don't even have a type - // The old hash format was just an md5 hex hash, with no type information + // The old hash format was just an MD5 hex hash, with no type information if ( preg_match( '/^[0-9a-f]{32}$/', $row->user_password ) ) { $row->user_password = ":B:{$row->user_id}:{$row->user_password}"; } @@ -161,7 +165,9 @@ class LocalPasswordPrimaryAuthenticationProvider public function testUserCanAuthenticate( $username ) { $username = $this->userNameUtils->getCanonical( - $username, UserRigorOptions::RIGOR_USABLE ); + $username, + UserRigorOptions::RIGOR_USABLE + ); if ( $username === false ) { return false; } @@ -176,7 +182,7 @@ class LocalPasswordPrimaryAuthenticationProvider } // Check for *really* old password hashes that don't even have a type - // The old hash format was just an md5 hex hash, with no type information + // The old hash format was just an MD5 hex hash, with no type information if ( preg_match( '/^[0-9a-f]{32}$/', $row->user_password ) ) { return true; } @@ -186,7 +192,9 @@ class LocalPasswordPrimaryAuthenticationProvider public function testUserExists( $username, $flags = IDBAccessObject::READ_NORMAL ) { $username = $this->userNameUtils->getCanonical( - $username, UserRigorOptions::RIGOR_USABLE ); + $username, + UserRigorOptions::RIGOR_USABLE + ); if ( $username === false ) { return false; } @@ -205,12 +213,12 @@ class LocalPasswordPrimaryAuthenticationProvider // We only want to blank the password if something else will accept the // new authentication data, so return 'ignore' here. if ( $this->loginOnly ) { - return \StatusValue::newGood( 'ignored' ); + return StatusValue::newGood( 'ignored' ); } if ( get_class( $req ) === PasswordAuthenticationRequest::class ) { if ( !$checkData ) { - return \StatusValue::newGood(); + return StatusValue::newGood(); } $username = $this->userNameUtils->getCanonical( $req->username, @@ -222,7 +230,7 @@ class LocalPasswordPrimaryAuthenticationProvider ->where( [ 'user_name' => $username ] ) ->caller( __METHOD__ )->fetchRow(); if ( $row ) { - $sv = \StatusValue::newGood(); + $sv = StatusValue::newGood(); if ( $req->password !== null ) { if ( $req->password !== $req->retype ) { $sv->fatal( 'badretype' ); @@ -235,12 +243,12 @@ class LocalPasswordPrimaryAuthenticationProvider } } - return \StatusValue::newGood( 'ignored' ); + return StatusValue::newGood( 'ignored' ); } public function providerChangeAuthenticationData( AuthenticationRequest $req ) { - $username = $req->username !== null ? - $this->userNameUtils->getCanonical( $req->username, UserRigorOptions::RIGOR_USABLE ) + $username = $req->username !== null + ? $this->userNameUtils->getCanonical( $req->username, UserRigorOptions::RIGOR_USABLE ) : false; if ( $username === false ) { return; @@ -279,7 +287,7 @@ class LocalPasswordPrimaryAuthenticationProvider public function testForAccountCreation( $user, $creator, array $reqs ) { $req = AuthenticationRequest::getRequestByClass( $reqs, PasswordAuthenticationRequest::class ); - $ret = \StatusValue::newGood(); + $ret = StatusValue::newGood(); if ( !$this->loginOnly && $req && $req->username !== null && $req->password !== null ) { if ( $req->password !== $req->retype ) { $ret->fatal( 'badretype' ); @@ -294,7 +302,7 @@ class LocalPasswordPrimaryAuthenticationProvider public function beginPrimaryAccountCreation( $user, $creator, array $reqs ) { if ( $this->accountCreationType() === self::TYPE_NONE ) { - throw new \BadMethodCallException( 'Shouldn\'t call this when accountCreationType() is NONE' ); + throw new BadMethodCallException( 'Shouldn\'t call this when accountCreationType() is NONE' ); } $req = AuthenticationRequest::getRequestByClass( $reqs, PasswordAuthenticationRequest::class ); @@ -314,7 +322,7 @@ class LocalPasswordPrimaryAuthenticationProvider public function finishAccountCreation( $user, $creator, AuthenticationResponse $res ) { if ( $this->accountCreationType() === self::TYPE_NONE ) { - throw new \BadMethodCallException( 'Shouldn\'t call this when accountCreationType() is NONE' ); + throw new BadMethodCallException( 'Shouldn\'t call this when accountCreationType() is NONE' ); } // Now that the user is in the DB, set the password on it. diff --git a/includes/composer/ComposerVendorHtaccessCreator.php b/includes/composer/ComposerVendorHtaccessCreator.php index e2a079b021d5..ef835fa1d898 100644 --- a/includes/composer/ComposerVendorHtaccessCreator.php +++ b/includes/composer/ComposerVendorHtaccessCreator.php @@ -40,6 +40,8 @@ class ComposerVendorHtaccessCreator { return; } - file_put_contents( $fname, "Require all denied\n" ); + file_put_contents( $fname, + "Require all denied\n" . + "Satisfy All\n" ); } } diff --git a/includes/filerepo/ThumbnailEntryPoint.php b/includes/filerepo/ThumbnailEntryPoint.php index a1ae53359a1b..e8367ada0d2b 100644 --- a/includes/filerepo/ThumbnailEntryPoint.php +++ b/includes/filerepo/ThumbnailEntryPoint.php @@ -673,7 +673,7 @@ EOT; return false; } - private function vary( $header ) { + private function vary( string $header ) { $this->varyHeader[] = $header; } @@ -823,7 +823,7 @@ EOT; return false; } - private function maybeEnforceRateLimits( File $img, array $params ) { + private function maybeEnforceRateLimits( File $img, array $params ): bool { $authority = $this->getContext()->getAuthority(); $status = PermissionStatus::newEmpty(); diff --git a/includes/filerepo/file/File.php b/includes/filerepo/file/File.php index 30844f5c2059..d088b87fc001 100644 --- a/includes/filerepo/file/File.php +++ b/includes/filerepo/file/File.php @@ -1147,7 +1147,7 @@ abstract class File implements MediaHandlerState { return $thumbName; } - private function adjustThumbWidthForSteps( $params ) { + private function adjustThumbWidthForSteps( array $params ): array { $thumbnailSteps = MediaWikiServices::getInstance() ->getMainConfig()->get( MainConfigNames::ThumbnailSteps ); $thumbnailStepsRatio = MediaWikiServices::getInstance() @@ -2465,7 +2465,7 @@ abstract class File implements MediaHandlerState { } /** - * @return string + * @return string HTML */ public function getLongDesc() { $handler = $this->getHandler(); @@ -2477,7 +2477,7 @@ abstract class File implements MediaHandlerState { } /** - * @return string + * @return string HTML */ public function getShortDesc() { $handler = $this->getHandler(); @@ -2489,7 +2489,7 @@ abstract class File implements MediaHandlerState { } /** - * @return string + * @return string plain text */ public function getDimensionsString() { $handler = $this->getHandler(); diff --git a/includes/filerepo/file/FileSelectQueryBuilder.php b/includes/filerepo/file/FileSelectQueryBuilder.php index a61357b00949..23cf5597a9a1 100644 --- a/includes/filerepo/file/FileSelectQueryBuilder.php +++ b/includes/filerepo/file/FileSelectQueryBuilder.php @@ -73,7 +73,7 @@ class FileSelectQueryBuilder extends SelectQueryBuilder { return new FileSelectQueryBuilder( $db, 'archivedfile', $options ); } - private function initFileOld( $options ) { + private function initFileOld( array $options ) { $this->table( 'image' ) ->join( 'actor', 'image_actor', 'actor_id=img_actor' ) ->join( @@ -112,7 +112,7 @@ class FileSelectQueryBuilder extends SelectQueryBuilder { } } - private function initFileNew( $options ) { + private function initFileNew( array $options ) { $subquery = $this->newSubquery(); $subquery->table( 'file' ) ->join( 'filerevision', null, 'file_latest = fr_id' ) @@ -164,7 +164,7 @@ class FileSelectQueryBuilder extends SelectQueryBuilder { ->from( $subquery ); } - private function initOldFileOld( $options ) { + private function initOldFileOld( array $options ) { $this->table( 'oldimage' ) ->join( 'actor', 'oldimage_actor', 'actor_id=oi_actor' ) ->join( @@ -204,7 +204,7 @@ class FileSelectQueryBuilder extends SelectQueryBuilder { } } - private function initOldFileNew( $options ) { + private function initOldFileNew( array $options ) { $subquery = $this->newSubquery(); $subquery->table( 'filerevision' ) ->join( 'file', null, 'fr_file = file_id' ) @@ -253,7 +253,7 @@ class FileSelectQueryBuilder extends SelectQueryBuilder { ->from( $subquery ); } - private function initArchivedFile( $options ) { + private function initArchivedFile( array $options ) { $this->table( 'filearchive' ) ->join( 'actor', 'filearchive_actor', 'actor_id=fa_actor' ) ->join( diff --git a/includes/filerepo/file/OldLocalFile.php b/includes/filerepo/file/OldLocalFile.php index 76b624f1d5ad..8e4e7811b217 100644 --- a/includes/filerepo/file/OldLocalFile.php +++ b/includes/filerepo/file/OldLocalFile.php @@ -261,7 +261,9 @@ class OldLocalFile extends LocalFile { } } - private function buildQueryBuilderForLoad( IReadableDatabase $dbr, $options = [ 'omit-nonlazy' ] ) { + private function buildQueryBuilderForLoad( + IReadableDatabase $dbr, array $options = [ 'omit-nonlazy' ] + ): FileSelectQueryBuilder { $queryBuilder = FileSelectQueryBuilder::newForOldFile( $dbr, $options ); $queryBuilder->where( [ 'oi_name' => $this->getName() ] ) ->orderBy( 'oi_timestamp', SelectQueryBuilder::SORT_DESC ); diff --git a/includes/filerepo/file/UnregisteredLocalFile.php b/includes/filerepo/file/UnregisteredLocalFile.php index e09923859f0c..1f4ccd182794 100644 --- a/includes/filerepo/file/UnregisteredLocalFile.php +++ b/includes/filerepo/file/UnregisteredLocalFile.php @@ -199,7 +199,7 @@ class UnregisteredLocalFile extends File { return $info['metadata']; } - private function getSizeAndMetadata() { + private function getSizeAndMetadata(): array { if ( $this->sizeAndMetadata === null ) { if ( !$this->getHandler() ) { $this->sizeAndMetadata = [ 'width' => 0, 'height' => 0, 'metadata' => [] ]; diff --git a/includes/installer/Task/SqliteCreateDatabaseTask.php b/includes/installer/Task/SqliteCreateDatabaseTask.php index 204a572ed06a..4298db4547e2 100644 --- a/includes/installer/Task/SqliteCreateDatabaseTask.php +++ b/includes/installer/Task/SqliteCreateDatabaseTask.php @@ -130,7 +130,9 @@ EOT; } } # Put a .htaccess file in case the user didn't take our advice - file_put_contents( "$dir/.htaccess", "Require all denied\n" ); + file_put_contents( "$dir/.htaccess", + "Require all denied\n" . + "Satisfy All\n" ); return Status::newGood(); } diff --git a/includes/installer/i18n/zh-hant.json b/includes/installer/i18n/zh-hant.json index c081e46efc09..0321e4873481 100644 --- a/includes/installer/i18n/zh-hant.json +++ b/includes/installer/i18n/zh-hant.json @@ -169,6 +169,8 @@ "config-mysql-engine": "儲存引擎:", "config-mysql-innodb": "InnoDB(推薦)", "config-mysql-engine-help": "由於對同時連線有較好的處理能力,<strong>InnoDB</strong> 通常是最佳的選項。\n\n<strong>MyISAM</strong> 只在單人使用或者唯讀作業的情況之下才可能有較快的處理能力。\n相較於 InnoDB,MyISAM 也較容易出現資料損毀的情況。", + "config-server": "URL 主機名稱:", + "config-server-help": "用於存取您的 wiki 與 URL 的協定和主機名稱部分。自動偵測到的預設值在一般情況下會是正確的。", "config-site-name": "wiki 的名稱:", "config-site-name-help": "您所填入的內容會出現在瀏覽器的標題列以及各種其他地方。", "config-site-name-blank": "請輸入網站名稱。", @@ -302,6 +304,7 @@ "config-install-updates": "略過執行不需要的更新", "config-install-updates-failed": "<strong>錯誤:</strong> 插入更新鍵值至資料表失敗,並出現以下錯誤:$1", "config-install-sysop": "正在建立管理員使用者帳號", + "config-install-subscribe": "訂閱 mediawiki-announce", "config-install-subscribe-fail": "無法訂閱 [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ mediawiki-announce]:$1", "config-install-subscribe-notpossible": "未安裝 cURL,因此無法使用 <code>allow_url_fopen</code> 設定項目。", "config-install-subscribe-alreadysubscribed": "您已訂閱 mediawiki-announce。[https://lists.wikimedia.org/postorius/accounts/per-subscription-preferences/ 檢視或變更您的訂閱設定]。", @@ -315,6 +318,9 @@ "config-install-done-path": "<strong>恭喜!</strong>\n您已經成功安裝MediaWiki。\n\n安裝程式已自動產生 <code>LocalSettings.php</code> 檔案,\n該檔案中包含了您所有的設定項目。\n\n您需要下載該檔案,並將其放置在 <code>$4</code> 中,下載應已自動開始。\n\n若瀏覽器沒有提示您下載,或者您取消了下載,您可以點選下方連結重新下載:\n\n$3\n\n<strong>注意:</strong>如果您現在不下載此檔案,稍後結束安裝程式之後將無法再下載設定檔。\n\n當您完成本步驟後,您可以<strong>[$2 進入您的 wiki]</strong>。", "config-install-success": "MediaWiki 已安裝成功。您現在可以在 <$1$2> 上檢視您的 wiki。若您有任何問題,請閱讀常見問題清單:<https://www.mediawiki.org/wiki/Manual:FAQ/zh>,或是利用在頁面上所連結的支援論壇之一。", "config-install-db-success": "資料庫設定成功", + "config-install-generic": "正在執行任務「$1」", + "config-install-external-domains": "正在建立外部資料庫", + "config-skip-shared-domain": "略過可能的共享網域「$1」", "config-download-localsettings": "下載 <code>LocalSettings.php</code>", "config-help": "說明", "config-help-tooltip": "點選以展開", diff --git a/includes/jobqueue/jobs/ThumbnailRenderJob.php b/includes/jobqueue/jobs/ThumbnailRenderJob.php index 5fbdb3823bd6..f12532e27a9e 100644 --- a/includes/jobqueue/jobs/ThumbnailRenderJob.php +++ b/includes/jobqueue/jobs/ThumbnailRenderJob.php @@ -153,7 +153,7 @@ class ThumbnailRenderJob extends Job { return false; } - private function maybeEnqueueNextPage( $transformParams ) { + private function maybeEnqueueNextPage( array $transformParams ) { if ( ( $this->params['enqueueNextPage'] ?? false ) && ( $transformParams['page'] ?? 0 ) < ( $this->params['pageLimit'] ?? 0 ) diff --git a/includes/libs/ParamValidator/TypeDef/BooleanDef.php b/includes/libs/ParamValidator/TypeDef/BooleanDef.php index e2ab0cb1460b..19ee271bde00 100644 --- a/includes/libs/ParamValidator/TypeDef/BooleanDef.php +++ b/includes/libs/ParamValidator/TypeDef/BooleanDef.php @@ -57,7 +57,7 @@ class BooleanDef extends TypeDef { ); } - private function quoteVal( $v ) { + private function quoteVal( string $v ): ScalarParam { return new ScalarParam( ParamType::TEXT, "\"$v\"" ); } diff --git a/includes/libs/WRStats/LimitBatch.php b/includes/libs/WRStats/LimitBatch.php index 3758a40fc6e3..b83adb4cffda 100644 --- a/includes/libs/WRStats/LimitBatch.php +++ b/includes/libs/WRStats/LimitBatch.php @@ -72,7 +72,7 @@ class LimitBatch { return $this; } - private function queueOp( $type, $entity, $amount ) { + private function queueOp( string $type, ?EntityKey $entity, ?int $amount ) { $amount ??= $this->defaultAmount; if ( isset( $this->operations[$type] ) ) { throw new WRStatsError( 'Cannot queue multiple actions of the same type, ' . diff --git a/includes/libs/filebackend/FSFileBackend.php b/includes/libs/filebackend/FSFileBackend.php index 1d76f2bf9f5e..92ae176ee7e4 100644 --- a/includes/libs/filebackend/FSFileBackend.php +++ b/includes/libs/filebackend/FSFileBackend.php @@ -987,7 +987,8 @@ class FSFileBackend extends FileBackendStore { * @return string */ protected function htaccessPrivate() { - return "Require all denied\n"; + return "Require all denied\n" . + "Satisfy All\n"; } /** diff --git a/includes/libs/filebackend/FileBackendMultiWrite.php b/includes/libs/filebackend/FileBackendMultiWrite.php index 7be37112be27..7c59e161e396 100644 --- a/includes/libs/filebackend/FileBackendMultiWrite.php +++ b/includes/libs/filebackend/FileBackendMultiWrite.php @@ -776,7 +776,7 @@ class FileBackendMultiWrite extends FileBackend { return $this->backends[$this->masterIndex]->getFileList( $realParams ); } - private function getFileListForWrite( $params ) { + private function getFileListForWrite( array $params ): array { $files = []; // Get the list of thumbnails from all backends to allow // deleting all of them. Otherwise, old thumbnails existing on diff --git a/includes/libs/http/MultiHttpClient.php b/includes/libs/http/MultiHttpClient.php index 4aefaf1551b5..ff0bd5f6866c 100644 --- a/includes/libs/http/MultiHttpClient.php +++ b/includes/libs/http/MultiHttpClient.php @@ -734,7 +734,7 @@ class MultiHttpClient implements LoggerAwareInterface { } } - private function useReverseProxy( array &$req, $proxy ) { + private function useReverseProxy( array &$req, string $proxy ) { $parsedProxy = parse_url( $proxy ); if ( $parsedProxy === false ) { throw new InvalidArgumentException( "Invalid reverseProxy configured: $proxy" ); diff --git a/includes/libs/mime/MSCompoundFileReader.php b/includes/libs/mime/MSCompoundFileReader.php index ff6afe144088..398c2bdd8e9d 100644 --- a/includes/libs/mime/MSCompoundFileReader.php +++ b/includes/libs/mime/MSCompoundFileReader.php @@ -170,11 +170,11 @@ class MSCompoundFileReader { $this->valid = true; } - private function sectorOffset( $sectorId ) { + private function sectorOffset( int $sectorId ): int { return $this->sectorLength * ( $sectorId + 1 ); } - private function decodeClsid( $binaryClsid ) { + private function decodeClsid( string $binaryClsid ): string { $parts = unpack( 'Va/vb/vc/C8d', $binaryClsid ); return sprintf( "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", $parts['a'], @@ -220,7 +220,7 @@ class MSCompoundFileReader { return $data; } - private function bin2dec( $str, $offset, $length ) { + private function bin2dec( string $str, int $offset, int $length ): int { $value = 0; for ( $i = $length - 1; $i >= 0; $i-- ) { $value *= 256; @@ -229,7 +229,7 @@ class MSCompoundFileReader { return $value; } - private function readOffset( $offset, $length ) { + private function readOffset( int $offset, int $length ): string { $this->fseek( $offset ); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged $block = @fread( $this->file, $length ); @@ -243,7 +243,7 @@ class MSCompoundFileReader { return $block; } - private function readSector( $sectorId ) { + private function readSector( int $sectorId ): string { return $this->readOffset( $this->sectorOffset( $sectorId ), 1 << $this->header['sector_shift'] ); } @@ -256,7 +256,7 @@ class MSCompoundFileReader { throw new RuntimeException( $message, $code ); } - private function fseek( $offset ) { + private function fseek( int $offset ) { // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged $result = @fseek( $this->file, $offset ); if ( $result !== 0 ) { @@ -287,14 +287,14 @@ class MSCompoundFileReader { } } - private function getNextSectorIdFromFat( $sectorId ) { + private function getNextSectorIdFromFat( int $sectorId ): int { $entriesPerSector = intdiv( $this->sectorLength, 4 ); $fatSectorId = intdiv( $sectorId, $entriesPerSector ); $fatSectorArray = $this->getFatSector( $fatSectorId ); return $fatSectorArray[$sectorId % $entriesPerSector]; } - private function getFatSector( $fatSectorId ) { + private function getFatSector( int $fatSectorId ): array { if ( !isset( $this->fat[$fatSectorId] ) ) { $fat = []; if ( !isset( $this->difat[$fatSectorId] ) ) { diff --git a/includes/libs/mime/XmlTypeCheck.php b/includes/libs/mime/XmlTypeCheck.php index b86fd406bef7..274bcbaafc4c 100644 --- a/includes/libs/mime/XmlTypeCheck.php +++ b/includes/libs/mime/XmlTypeCheck.php @@ -197,7 +197,7 @@ class XmlTypeCheck { } } - private function readNext( XMLReader $reader ) { + private function readNext( XMLReader $reader ): bool { set_error_handler( function ( $line, $file ) { $this->wellFormed = false; return true; @@ -207,7 +207,7 @@ class XmlTypeCheck { return $ret; } - private function validate( $reader ) { + private function validate( XMLReader $reader ) { // First, move through anything that isn't an element, and // handle any processing instructions with the callback do { diff --git a/includes/libs/objectcache/BagOStuff.php b/includes/libs/objectcache/BagOStuff.php index 5edb35bbdd9f..5e73704c6088 100644 --- a/includes/libs/objectcache/BagOStuff.php +++ b/includes/libs/objectcache/BagOStuff.php @@ -557,17 +557,6 @@ abstract class BagOStuff implements } /** - * Clear the "last error" registry - * - * @since 1.23 - * @deprecated Since 1.38, hard deprecated in 1.43 - */ - public function clearLastError() { - wfDeprecated( __METHOD__, '1.38' ); - $this->lastError = self::ERR_NONE; - } - - /** * Set the "last error" registry due to a problem encountered during an attempted operation * * @param int $error BagOStuff:ERR_* constant diff --git a/includes/libs/objectcache/RESTBagOStuff.php b/includes/libs/objectcache/RESTBagOStuff.php index b2600b908c86..dcc7bd96a2f9 100644 --- a/includes/libs/objectcache/RESTBagOStuff.php +++ b/includes/libs/objectcache/RESTBagOStuff.php @@ -226,7 +226,7 @@ class RESTBagOStuff extends MediumSpecificBagOStuff { [ 'cacheKey' => $key ] ); } - $this->updateOpStats( self::METRIC_OP_SET, [ $key => [ strlen( $rbody ), 0 ] ] ); + $this->updateOpStats( self::METRIC_OP_SET, [ $key => [ strlen( $req['body'] ), 0 ] ] ); return $res; } diff --git a/includes/libs/objectcache/WANObjectCache.php b/includes/libs/objectcache/WANObjectCache.php index 925a2709158d..d2661d68bd7a 100644 --- a/includes/libs/objectcache/WANObjectCache.php +++ b/includes/libs/objectcache/WANObjectCache.php @@ -194,7 +194,7 @@ class WANObjectCache implements protected $coalesceScheme; /** @var TracerInterface */ - private $tracer = null; + private $tracer; /** @var array<int,array> List of (key, UNIX timestamp) tuples for get() cache misses */ private $missLog; @@ -461,6 +461,7 @@ class WANObjectCache implements // Also, if no $info parameter is provided, then it doesn't matter how it changes here. $legacyInfo = ( $info !== self::PASS_BY_REF ); + /** @noinspection PhpUnusedLocalVariableInspection */ $span = $this->startOperationSpan( __FUNCTION__, $key, $checkKeys ); $now = $this->getCurrentTime(); @@ -521,6 +522,7 @@ class WANObjectCache implements // Also, if no $info parameter is provided, then it doesn't matter how it changes here. $legacyInfo = ( $info !== self::PASS_BY_REF ); + /** @noinspection PhpUnusedLocalVariableInspection */ $span = $this->startOperationSpan( __FUNCTION__, $keys, $checkKeys ); $curTTLs = []; @@ -803,6 +805,7 @@ class WANObjectCache implements * @return bool Success */ final public function set( $key, $value, $ttl = self::TTL_INDEFINITE, array $opts = [] ) { + /** @noinspection PhpUnusedLocalVariableInspection */ $span = $this->startOperationSpan( __FUNCTION__, $key ); $keygroup = $this->determineKeyGroupForStats( $key ); @@ -1055,6 +1058,7 @@ class WANObjectCache implements * @return bool True if the item was purged or not found, false on failure */ final public function delete( $key, $ttl = self::HOLDOFF_TTL ) { + /** @noinspection PhpUnusedLocalVariableInspection */ $span = $this->startOperationSpan( __FUNCTION__, $key ); // Purge values must be stored under the value key so that WANObjectCache::set() @@ -1116,7 +1120,9 @@ class WANObjectCache implements * @return float UNIX timestamp */ final public function getCheckKeyTime( $key ) { + /** @noinspection PhpUnusedLocalVariableInspection */ $span = $this->startOperationSpan( __FUNCTION__, $key ); + return $this->getMultiCheckKeyTime( [ $key ] )[$key]; } @@ -1182,6 +1188,7 @@ class WANObjectCache implements * @since 1.31 */ final public function getMultiCheckKeyTime( array $keys ) { + /** @noinspection PhpUnusedLocalVariableInspection */ $span = $this->startOperationSpan( __FUNCTION__, $keys ); $checkSisterKeysByKey = []; @@ -1246,6 +1253,7 @@ class WANObjectCache implements * @return bool True if the item was purged or not found, false on failure */ public function touchCheckKey( $key, $holdoff = self::HOLDOFF_TTL ) { + /** @noinspection PhpUnusedLocalVariableInspection */ $span = $this->startOperationSpan( __FUNCTION__, $key ); $checkSisterKey = $this->makeSisterKey( $key, self::TYPE_TIMESTAMP ); @@ -1298,6 +1306,7 @@ class WANObjectCache implements * @return bool True if the item was purged or not found, false on failure */ public function resetCheckKey( $key ) { + /** @noinspection PhpUnusedLocalVariableInspection */ $span = $this->startOperationSpan( __FUNCTION__, $key ); $checkSisterKey = $this->makeSisterKey( $key, self::TYPE_TIMESTAMP ); @@ -1618,7 +1627,9 @@ class WANObjectCache implements final public function getWithSetCallback( $key, $ttl, $callback, array $opts = [], array $cbParams = [] ) { + /** @noinspection PhpUnusedLocalVariableInspection */ $span = $this->startOperationSpan( __FUNCTION__, $key ); + $version = $opts['version'] ?? null; $pcTTL = $opts['pcTTL'] ?? self::TTL_UNCACHEABLE; $pCache = ( $pcTTL >= 0 ) @@ -1828,6 +1839,7 @@ class WANObjectCache implements $preCallbackTime = $this->getCurrentTime(); ++$this->callbackDepth; // https://github.com/phan/phan/issues/4419 + /** @noinspection PhpUnusedLocalVariableInspection */ $value = null; try { $value = $callback( diff --git a/includes/libs/rdbms/ChronologyProtector.php b/includes/libs/rdbms/ChronologyProtector.php index 5b8783cbe047..95b9fd7d1440 100644 --- a/includes/libs/rdbms/ChronologyProtector.php +++ b/includes/libs/rdbms/ChronologyProtector.php @@ -591,7 +591,7 @@ class ChronologyProtector implements LoggerAwareInterface { $this->wallClockOverride =& $time; } - private function marshalPositions( array $positions ) { + private function marshalPositions( array $positions ): array { foreach ( $positions[ self::FLD_POSITIONS ] as $key => $pos ) { $positions[ self::FLD_POSITIONS ][ $key ] = $pos->toArray(); } diff --git a/includes/libs/rdbms/database/Database.php b/includes/libs/rdbms/database/Database.php index 6904f654ead4..4c2e9495d265 100644 --- a/includes/libs/rdbms/database/Database.php +++ b/includes/libs/rdbms/database/Database.php @@ -877,7 +877,9 @@ abstract class Database implements Stringable, IDatabaseForOwner, IMaintainableD return $status; } - private function handleErroredQuery( QueryStatus $status, $sql, $fname, $queryRuntime, $priorSessInfo ) { + private function handleErroredQuery( + QueryStatus $status, Query $sql, string $fname, float $queryRuntime, CriticalSessionInfo $priorSessInfo + ): int { $errflags = self::ERR_NONE; $error = $status->message; $errno = $status->code; diff --git a/includes/libs/rdbms/database/DatabaseMySQL.php b/includes/libs/rdbms/database/DatabaseMySQL.php index f7afce6b6c6c..2dd9cc904254 100644 --- a/includes/libs/rdbms/database/DatabaseMySQL.php +++ b/includes/libs/rdbms/database/DatabaseMySQL.php @@ -808,7 +808,7 @@ class DatabaseMySQL extends Database { } } - private function mysqlRealEscapeString( $s ) { + private function mysqlRealEscapeString( $s ): string { $conn = $this->getBindingHandle(); return $conn->real_escape_string( (string)$s ); diff --git a/includes/libs/rdbms/database/QueryBuilderFromRawSql.php b/includes/libs/rdbms/database/QueryBuilderFromRawSql.php index 3be8e0e13f69..af268f9cc2e2 100644 --- a/includes/libs/rdbms/database/QueryBuilderFromRawSql.php +++ b/includes/libs/rdbms/database/QueryBuilderFromRawSql.php @@ -99,7 +99,7 @@ class QueryBuilderFromRawSql { ); } - private static function isWriteQuery( $rawSql ) { + private static function isWriteQuery( string $rawSql ): bool { // Treat SELECT queries without FOR UPDATE queries as non-writes. This matches // how MySQL enforces read_only (FOR SHARE and LOCK IN SHADE MODE are allowed). // Handle (SELECT ...) UNION (SELECT ...) queries in a similar fashion. diff --git a/includes/libs/rdbms/dbal/DoctrineAbstractSchemaTrait.php b/includes/libs/rdbms/dbal/DoctrineAbstractSchemaTrait.php index 0f06ee2f4679..ab4b813bfe8c 100644 --- a/includes/libs/rdbms/dbal/DoctrineAbstractSchemaTrait.php +++ b/includes/libs/rdbms/dbal/DoctrineAbstractSchemaTrait.php @@ -31,7 +31,7 @@ trait DoctrineAbstractSchemaTrait { private AbstractPlatform $platform; - private function addTableToSchema( Schema $schema, array $schemaSpec ) { + private function addTableToSchema( Schema $schema, array $schemaSpec ): Schema { $prefix = $this->platform->getName() === 'postgresql' ? '' : '/*_*/'; $table = $schema->createTable( $prefix . $schemaSpec['name'] ); diff --git a/includes/libs/rdbms/expression/LikeValue.php b/includes/libs/rdbms/expression/LikeValue.php index 58b732927887..95bd801defa5 100644 --- a/includes/libs/rdbms/expression/LikeValue.php +++ b/includes/libs/rdbms/expression/LikeValue.php @@ -59,7 +59,7 @@ class LikeValue { return $dbQuoter->addQuotes( $s ) . ' ESCAPE ' . $dbQuoter->addQuotes( $escapeChar ); } - private function escapeLikeInternal( $s, $escapeChar = '`' ) { + private function escapeLikeInternal( string $s, string $escapeChar = '`' ): string { return str_replace( [ $escapeChar, '%', '_' ], [ "{$escapeChar}{$escapeChar}", "{$escapeChar}%", "{$escapeChar}_" ], diff --git a/includes/libs/rdbms/lbfactory/LBFactorySimple.php b/includes/libs/rdbms/lbfactory/LBFactorySimple.php index 4d90b309acd0..80f2eaedd44a 100644 --- a/includes/libs/rdbms/lbfactory/LBFactorySimple.php +++ b/includes/libs/rdbms/lbfactory/LBFactorySimple.php @@ -124,7 +124,7 @@ class LBFactorySimple extends LBFactory { return $lbs; } - private function newLoadBalancer( string $clusterName, array $servers ) { + private function newLoadBalancer( string $clusterName, array $servers ): ILoadBalancerForOwner { $lb = new LoadBalancer( array_merge( $this->baseLoadBalancerParams(), [ diff --git a/includes/libs/rdbms/loadbalancer/LoadBalancer.php b/includes/libs/rdbms/loadbalancer/LoadBalancer.php index 66e3a1ac30b8..db959970be33 100644 --- a/includes/libs/rdbms/loadbalancer/LoadBalancer.php +++ b/includes/libs/rdbms/loadbalancer/LoadBalancer.php @@ -233,7 +233,7 @@ class LoadBalancer implements ILoadBalancerForOwner { $this->defaultGroup = isset( $this->groupLoads[ $group ] ) ? $group : self::GROUP_GENERIC; } - private static function newTrackedConnectionsArray() { + private static function newTrackedConnectionsArray(): array { // Note that CATEGORY_GAUGE connections are untracked return [ self::CATEGORY_ROUND => [], diff --git a/includes/libs/rdbms/platform/SQLPlatform.php b/includes/libs/rdbms/platform/SQLPlatform.php index 6ac4f1c07865..a1bc9168485d 100644 --- a/includes/libs/rdbms/platform/SQLPlatform.php +++ b/includes/libs/rdbms/platform/SQLPlatform.php @@ -1596,7 +1596,7 @@ class SQLPlatform implements ISQLPlatform { ); } - private function scrubArray( $array, $listType = self::LIST_AND ) { + private function scrubArray( $array, int $listType = self::LIST_AND ): string { if ( is_array( $array ) ) { $scrubbedArray = []; foreach ( $array as $key => $value ) { diff --git a/includes/media/DjVuImage.php b/includes/media/DjVuImage.php index 1ff80602730e..d5a18e75bd6d 100644 --- a/includes/media/DjVuImage.php +++ b/includes/media/DjVuImage.php @@ -97,7 +97,7 @@ class DjVuImage { fclose( $file ); } - private function dumpForm( $file, $length, $indent ) { + private function dumpForm( $file, int $length, int $indent ) { $start = ftell( $file ); $secondary = fread( $file, 4 ); echo str_repeat( ' ', $indent * 4 ) . "($secondary)\n"; @@ -159,7 +159,7 @@ class DjVuImage { return $info; } - private function readChunk( $file ) { + private function readChunk( $file ): array { $header = fread( $file, 8 ); if ( strlen( $header ) < 8 ) { return [ false, 0 ]; @@ -169,7 +169,7 @@ class DjVuImage { return [ $arr['chunk'], $arr['length'] ]; } - private function skipChunk( $file, $chunkLength ) { + private function skipChunk( $file, int $chunkLength ) { fseek( $file, $chunkLength, SEEK_CUR ); if ( ( $chunkLength & 1 ) && !feof( $file ) ) { @@ -178,7 +178,7 @@ class DjVuImage { } } - private function getMultiPageInfo( $file, $formLength ) { + private function getMultiPageInfo( $file, int $formLength ) { // For now, we'll just look for the first page in the file // and report its information, hoping others are the same size. $start = ftell( $file ); @@ -366,7 +366,7 @@ EOR; return $json; } - private function pageTextCallback( string $match ) { + private function pageTextCallback( string $match ): string { # Get rid of invalid UTF-8 $val = UtfNormal\Validator::cleanUp( stripcslashes( $match ) ); return str_replace( '�', '', $val ); @@ -434,7 +434,7 @@ EOR; return $result; } - private function parseFormDjvu( $line ) { + private function parseFormDjvu( string $line ) { $parentLevel = strspn( $line, ' ' ); $line = strtok( "\n" ); # Find INFO diff --git a/includes/media/MediaHandler.php b/includes/media/MediaHandler.php index c5ec3c403c8b..cc7a092e3341 100644 --- a/includes/media/MediaHandler.php +++ b/includes/media/MediaHandler.php @@ -818,7 +818,7 @@ abstract class MediaHandler { * @stable to override * * @param File $file - * @return string + * @return string HTML */ public function getShortDesc( $file ) { return self::getGeneralShortDesc( $file ); @@ -830,7 +830,7 @@ abstract class MediaHandler { * @stable to override * * @param File $file - * @return string + * @return string HTML */ public function getLongDesc( $file ) { return self::getGeneralLongDesc( $file ); @@ -840,7 +840,7 @@ abstract class MediaHandler { * Used instead of getShortDesc if there is no handler registered for file. * * @param File $file - * @return string + * @return string HTML */ public static function getGeneralShortDesc( $file ) { global $wgLang; @@ -852,7 +852,7 @@ abstract class MediaHandler { * Used instead of getLongDesc if there is no handler registered for file. * * @param File $file - * @return string + * @return string HTML */ public static function getGeneralLongDesc( $file ) { return wfMessage( 'file-info' )->sizeParams( $file->getSize() ) @@ -882,7 +882,7 @@ abstract class MediaHandler { * @stable to override * * @param File $file - * @return string Dimensions + * @return string Dimensions (plain text) */ public function getDimensionsString( $file ) { return ''; diff --git a/includes/media/SVGReader.php b/includes/media/SVGReader.php index f988456e2f66..f68f446f1ccb 100644 --- a/includes/media/SVGReader.php +++ b/includes/media/SVGReader.php @@ -314,7 +314,7 @@ class SVGReader { } } - private function debug( $data ) { + private function debug( string $data ) { if ( $this->mDebug ) { wfDebug( "SVGReader: $data" ); } diff --git a/includes/parser/ParserOptions.php b/includes/parser/ParserOptions.php index e364bf0ae2b0..971442f2c486 100644 --- a/includes/parser/ParserOptions.php +++ b/includes/parser/ParserOptions.php @@ -1099,6 +1099,7 @@ class ParserOptions { * Get a ParserOptions object from a given user. * Language will be taken from $wgLang. * + * @since 1.13 * @param UserIdentity $user * @return ParserOptions */ @@ -1109,6 +1110,7 @@ class ParserOptions { /** * Get a ParserOptions object from a given user and language * + * @since 1.19 * @param UserIdentity $user * @param Language $lang * @return ParserOptions @@ -1120,6 +1122,7 @@ class ParserOptions { /** * Get a ParserOptions object from a IContextSource object * + * @since 1.19 * @param IContextSource $context * @return ParserOptions */ diff --git a/includes/parser/Parsoid/Config/SiteConfig.php b/includes/parser/Parsoid/Config/SiteConfig.php index 24a6eef635b4..4e91f19aa96d 100644 --- a/includes/parser/Parsoid/Config/SiteConfig.php +++ b/includes/parser/Parsoid/Config/SiteConfig.php @@ -749,6 +749,13 @@ class SiteConfig extends ISiteConfig { } /** @inheritDoc */ + protected function shouldValidateExtConfig(): bool { + // Only perform json schema validation for extension module + // configurations when running tests. + return defined( 'MW_PHPUNIT_TEST' ) || defined( 'MW_PARSER_TEST' ); + } + + /** @inheritDoc */ public function getMaxTemplateDepth(): int { return (int)$this->config->get( MainConfigNames::MaxTemplateDepth ); } diff --git a/includes/recentchanges/ChangeTrackingEventIngress.php b/includes/recentchanges/ChangeTrackingEventIngress.php index eaa85f53df74..3da2b54f4b35 100644 --- a/includes/recentchanges/ChangeTrackingEventIngress.php +++ b/includes/recentchanges/ChangeTrackingEventIngress.php @@ -5,10 +5,12 @@ namespace MediaWiki\RecentChanges; use LogicException; use MediaWiki\ChangeTags\ChangeTagsStore; use MediaWiki\Config\Config; +use MediaWiki\Content\IContentHandlerFactory; use MediaWiki\DomainEvent\DomainEventIngress; use MediaWiki\HookContainer\HookContainer; use MediaWiki\HookContainer\HookRunner; use MediaWiki\JobQueue\JobQueueGroup; +use MediaWiki\JobQueue\Jobs\CategoryMembershipChangeJob; use MediaWiki\JobQueue\Jobs\RevertedTagUpdateJob; use MediaWiki\MainConfigNames; use MediaWiki\Page\Event\PageRevisionUpdatedEvent; @@ -60,7 +62,8 @@ class ChangeTrackingEventIngress 'UserNameUtils', 'TalkPageNotificationManager', 'MainConfig', - 'JobQueueGroup' + 'JobQueueGroup', + 'ContentHandlerFactory', ], 'events' => [ // see registerListeners() PageRevisionUpdatedEvent::TYPE @@ -75,7 +78,9 @@ class ChangeTrackingEventIngress private UserNameUtils $userNameUtils; private TalkPageNotificationManager $talkPageNotificationManager; private JobQueueGroup $jobQueueGroup; + private IContentHandlerFactory $contentHandlerFactory; private bool $useRcPatrol; + private bool $rcWatchCategoryMembership; public function __construct( ChangeTagsStore $changeTagsStore, @@ -86,7 +91,8 @@ class ChangeTrackingEventIngress UserNameUtils $userNameUtils, TalkPageNotificationManager $talkPageNotificationManager, Config $mainConfig, - JobQueueGroup $jobQueueGroup + JobQueueGroup $jobQueueGroup, + IContentHandlerFactory $contentHandlerFactory ) { // NOTE: keep in sync with self::OBJECT_SPEC $this->changeTagsStore = $changeTagsStore; @@ -97,8 +103,12 @@ class ChangeTrackingEventIngress $this->userNameUtils = $userNameUtils; $this->talkPageNotificationManager = $talkPageNotificationManager; $this->jobQueueGroup = $jobQueueGroup; + $this->contentHandlerFactory = $contentHandlerFactory; $this->useRcPatrol = $mainConfig->get( MainConfigNames::UseRCPatrol ); + $this->rcWatchCategoryMembership = $mainConfig->get( + MainConfigNames::RCWatchCategoryMembership + ); } public static function newForTesting( @@ -110,7 +120,8 @@ class ChangeTrackingEventIngress UserNameUtils $userNameUtils, TalkPageNotificationManager $talkPageNotificationManager, Config $mainConfig, - JobQueueGroup $jobQueueGroup + JobQueueGroup $jobQueueGroup, + IContentHandlerFactory $contentHandlerFactory ) { $ingress = new self( $changeTagsStore, @@ -121,7 +132,8 @@ class ChangeTrackingEventIngress $userNameUtils, $talkPageNotificationManager, $mainConfig, - $jobQueueGroup + $jobQueueGroup, + $contentHandlerFactory ); $ingress->initSubscriber( self::OBJECT_SPEC ); return $ingress; @@ -163,12 +175,16 @@ class ChangeTrackingEventIngress ); } - if ( $event->isEffectiveContentChange() && !$event->isImplicit() ) { - $this->updateUserEditTrackerAfterPageUpdated( - $event->getPerformer() - ); + if ( $event->isEffectiveContentChange() ) { + $this->generateCategoryMembershipChanges( $event ); + + if ( !$event->isImplicit() ) { + $this->updateUserEditTrackerAfterPageUpdated( + $event->getPerformer() + ); - $this->updateNewTalkAfterPageUpdated( $event ); + $this->updateNewTalkAfterPageUpdated( $event ); + } } if ( $event->isRevert() && $event->isEffectiveContentChange() ) { @@ -176,6 +192,49 @@ class ChangeTrackingEventIngress } } + /** + * Create RC entries for category changes that resulted from this update + * if the relevant config is enabled. + * This should only be triggered for actual edits, not reconciliation events (T390636). + * + * @param PageRevisionUpdatedEvent $event + */ + private function generateCategoryMembershipChanges( PageRevisionUpdatedEvent $event ): void { + if ( $this->rcWatchCategoryMembership + && !$event->hasCause( PageRevisionUpdatedEvent::CAUSE_UNDELETE ) + && $this->anyChangedSlotSupportsCategories( $event ) + ) { + // Note: jobs are pushed after deferred updates, so the job should be able to see + // the recent change entry (also done via deferred updates) and carry over any + // bot/deletion/IP flags, ect. + $this->jobQueueGroup->lazyPush( + CategoryMembershipChangeJob::newSpec( + $event->getPage(), + $event->getLatestRevisionAfter()->getTimestamp(), + $event->hasCause( PageRevisionUpdatedEvent::CAUSE_IMPORT ) + ) + ); + } + } + + /** + * Determine whether any slots changed in this update supports categories. + * @param PageRevisionUpdatedEvent $event + * @return bool + */ + private function anyChangedSlotSupportsCategories( PageRevisionUpdatedEvent $event ): bool { + $slotsUpdate = $event->getSlotsUpdate(); + foreach ( $slotsUpdate->getModifiedRoles() as $role ) { + $model = $slotsUpdate->getModifiedSlot( $role )->getModel(); + + if ( $this->contentHandlerFactory->getContentHandler( $model )->supportsCategories() ) { + return true; + } + } + + return false; + } + private function updateChangeTagsAfterPageUpdated( array $tags, int $revId ) { $this->changeTagsStore->addTags( $tags, null, $revId ); } diff --git a/includes/specialpage/ContributionsSpecialPage.php b/includes/specialpage/ContributionsSpecialPage.php index 7a5a43811f0f..dec9fdfe807f 100644 --- a/includes/specialpage/ContributionsSpecialPage.php +++ b/includes/specialpage/ContributionsSpecialPage.php @@ -126,6 +126,17 @@ class ContributionsSpecialPage extends IncludableSpecialPage { * @inheritDoc */ public function execute( $par ) { + $request = $this->getRequest(); + $target = $request->getText( 'target' ); + + if ( $target !== '' ) { + // Update the value in the request so that code reading it + // directly form the request gets the trimmed value (T378279). + $request->setVal( 'target', trim( $target ) ); + } + + $target = trim( $par ?? $target ); + $this->setHeaders(); $this->outputHeader(); $this->checkPermissions(); @@ -144,11 +155,6 @@ class ContributionsSpecialPage extends IncludableSpecialPage { ] ); $this->addHelpLink( 'Help:User contributions' ); - $request = $this->getRequest(); - - $target = $par ?? $request->getVal( 'target', '' ); - '@phan-var string $target'; // getVal does not return null here - // Normalize underscores that may be present in the target parameter // if it was passed in as a path param, rather than a query param // where HTMLForm may have already performed preprocessing (T372444). diff --git a/includes/specials/SpecialBotPasswords.php b/includes/specials/SpecialBotPasswords.php index e660e09b0dd2..89c33301f0b1 100644 --- a/includes/specials/SpecialBotPasswords.php +++ b/includes/specials/SpecialBotPasswords.php @@ -347,7 +347,7 @@ class SpecialBotPasswords extends FormSpecialPage { return false; } - private function save( array $data ) { + private function save( array $data ): Status { $bp = BotPassword::newUnsaved( [ 'centralId' => $this->userId, 'appId' => $this->par, diff --git a/includes/specials/SpecialComparePages.php b/includes/specials/SpecialComparePages.php index dac7d2be5f94..241dea83c156 100644 --- a/includes/specials/SpecialComparePages.php +++ b/includes/specials/SpecialComparePages.php @@ -146,7 +146,7 @@ class SpecialComparePages extends SpecialPage { } } - private function revOrTitle( $revision, $title ) { + private function revOrTitle( ?int $revision, ?string $title ): ?int { if ( $revision ) { return $revision; } elseif ( $title ) { diff --git a/includes/specials/SpecialDoubleRedirects.php b/includes/specials/SpecialDoubleRedirects.php index 88e548a9e967..0cbbfb371be8 100644 --- a/includes/specials/SpecialDoubleRedirects.php +++ b/includes/specials/SpecialDoubleRedirects.php @@ -71,7 +71,7 @@ class SpecialDoubleRedirects extends QueryPage { return $this->msg( 'doubleredirectstext' )->parseAsBlock(); } - private function reallyGetQueryInfo( $namespace = null, $title = null ) { + private function reallyGetQueryInfo( ?int $namespace = null, ?string $title = null ): array { $limitToTitle = !( $namespace === null && $title === null ); $retval = [ 'tables' => [ diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php index 526c4ade508a..cdfd5dd707a9 100644 --- a/includes/specials/SpecialImport.php +++ b/includes/specials/SpecialImport.php @@ -276,7 +276,7 @@ class SpecialImport extends SpecialPage { } } - private function getMappingFormPart( $sourceName ) { + private function getMappingFormPart( string $sourceName ): array { $defaultNamespace = $this->getConfig()->get( MainConfigNames::ImportTargetNamespace ); return [ 'mapping' => [ diff --git a/includes/specials/SpecialLinkSearch.php b/includes/specials/SpecialLinkSearch.php index 450c72d30f23..48e34d52cb10 100644 --- a/includes/specials/SpecialLinkSearch.php +++ b/includes/specials/SpecialLinkSearch.php @@ -54,7 +54,7 @@ class SpecialLinkSearch extends QueryPage { private UrlUtils $urlUtils; - private function setParams( $params ) { + private function setParams( array $params ) { $this->mQuery = $params['query']; $this->mNs = $params['namespace']; $this->mProt = $params['protocol']; diff --git a/includes/specials/SpecialLog.php b/includes/specials/SpecialLog.php index c4b1cdd800ea..fd7298bea7e3 100644 --- a/includes/specials/SpecialLog.php +++ b/includes/specials/SpecialLog.php @@ -321,7 +321,7 @@ class SpecialLog extends SpecialPage { } } - private function getActionButtons( $formcontents ) { + private function getActionButtons( string $formcontents ): string { $canRevDelete = $this->getAuthority() ->isAllowedAll( 'deletedhistory', 'deletelogentry' ); $showTagEditUI = ChangeTags::showTagEditingUI( $this->getAuthority() ); diff --git a/includes/specials/SpecialMovePage.php b/includes/specials/SpecialMovePage.php index 94e44f0d61e9..2a4000ab2754 100644 --- a/includes/specials/SpecialMovePage.php +++ b/includes/specials/SpecialMovePage.php @@ -964,7 +964,7 @@ class SpecialMovePage extends UnlistedSpecialPage { $this->watchlistManager->setWatch( $this->watch, $this->getAuthority(), $nt ); } - private function showLogFragment( $title ) { + private function showLogFragment( Title $title ) { $moveLogPage = new LogPage( 'move' ); $out = $this->getOutput(); $out->addHTML( Xml::element( 'h2', null, $moveLogPage->getName()->text() ) ); @@ -1010,7 +1010,9 @@ class SpecialMovePage extends UnlistedSpecialPage { } } - private function showSubpagesList( $subpages, $pagecount, $msg, $truncatedMsg, $noSubpageMsg = false ) { + private function showSubpagesList( + TitleArrayFromResult $subpages, int $pagecount, string $msg, string $truncatedMsg, bool $noSubpageMsg = false + ) { $out = $this->getOutput(); # No subpages. diff --git a/includes/specials/SpecialNewPages.php b/includes/specials/SpecialNewPages.php index 0569d9752df7..eb2fdedb34cb 100644 --- a/includes/specials/SpecialNewPages.php +++ b/includes/specials/SpecialNewPages.php @@ -380,7 +380,7 @@ class SpecialNewPages extends IncludableSpecialPage { $out->addModuleStyles( 'mediawiki.special' ); } - private function getNewPagesPager() { + private function getNewPagesPager(): NewPagesPager { return new NewPagesPager( $this->getContext(), $this->getLinkRenderer(), diff --git a/includes/specials/SpecialPageLanguage.php b/includes/specials/SpecialPageLanguage.php index 0272f2c34ce5..57f3d58a284d 100644 --- a/includes/specials/SpecialPageLanguage.php +++ b/includes/specials/SpecialPageLanguage.php @@ -308,7 +308,7 @@ class SpecialPageLanguage extends FormSpecialPage { $this->getOutput()->redirect( $this->goToUrl ); } - private function showLogFragment( $title ) { + private function showLogFragment( string $title ): string { $moveLogPage = new LogPage( 'pagelang' ); $out1 = Xml::element( 'h2', null, $moveLogPage->getName()->text() ); $out2 = ''; diff --git a/includes/specials/SpecialRandomPage.php b/includes/specials/SpecialRandomPage.php index 29babcfd45aa..c4049c8380a5 100644 --- a/includes/specials/SpecialRandomPage.php +++ b/includes/specials/SpecialRandomPage.php @@ -61,7 +61,7 @@ class SpecialRandomPage extends SpecialPage { $this->namespaces = [ $ns ]; } - private function isValidNS( $ns ) { + private function isValidNS( $ns ): bool { return $ns !== false && $ns >= 0; } @@ -208,7 +208,7 @@ class SpecialRandomPage extends SpecialPage { ]; } - private function selectRandomPageFromDB( $randstr, $fname ) { + private function selectRandomPageFromDB( $randstr, string $fname ) { $dbr = $this->dbProvider->getReplicaDatabase(); $query = $this->getQueryInfo( $randstr ); diff --git a/includes/specials/SpecialRenameUser.php b/includes/specials/SpecialRenameUser.php index 040f19055861..a048d621f797 100644 --- a/includes/specials/SpecialRenameUser.php +++ b/includes/specials/SpecialRenameUser.php @@ -251,7 +251,7 @@ class SpecialRenameUser extends SpecialPage { } } - private function getWarnings( $oldName, $newName ) { + private function getWarnings( string $oldName, string $newName ): array { $warnings = []; $oldUser = $this->userFactory->newFromName( $oldName, $this->userFactory::RIGOR_NONE ); if ( $oldUser && !$oldUser->isTemp() && $oldUser->getBlock() ) { @@ -264,7 +264,9 @@ class SpecialRenameUser extends SpecialPage { return $warnings; } - private function showForm( $oldName, $newName, $warnings, $reason, $moveChecked, $suppressChecked ) { + private function showForm( + ?string $oldName, ?string $newName, array $warnings, string $reason, bool $moveChecked, bool $suppressChecked + ) { $performer = $this->getUser(); $formDescriptor = [ diff --git a/includes/specials/SpecialSpecialPages.php b/includes/specials/SpecialSpecialPages.php index f41939a4e625..68240ac1d3a6 100644 --- a/includes/specials/SpecialSpecialPages.php +++ b/includes/specials/SpecialSpecialPages.php @@ -97,7 +97,7 @@ class SpecialSpecialPages extends UnlistedSpecialPage { return $groups; } - private function outputPageList( $groups ) { + private function outputPageList( array $groups ) { $out = $this->getOutput(); // Legend diff --git a/includes/specials/SpecialStatistics.php b/includes/specials/SpecialStatistics.php index 9efc01638d93..5ce81a8f2bd5 100644 --- a/includes/specials/SpecialStatistics.php +++ b/includes/specials/SpecialStatistics.php @@ -168,7 +168,7 @@ class SpecialStatistics extends SpecialPage { return $pageStatsHtml; } - private function getEditStats() { + private function getEditStats(): string { return Html::rawElement( 'tr', [], Xml::tags( 'th', [ 'colspan' => '2' ], $this->msg( 'statistics-header-edits' )->parse() @@ -184,7 +184,7 @@ class SpecialStatistics extends SpecialPage { ); } - private function getUserStats() { + private function getUserStats(): string { return Html::rawElement( 'tr', [], Xml::tags( 'th', [ 'colspan' => '2' ], $this->msg( 'statistics-header-users' )->parse() @@ -210,7 +210,7 @@ class SpecialStatistics extends SpecialPage { ); } - private function getGroupStats() { + private function getGroupStats(): string { $linkRenderer = $this->getLinkRenderer(); $lang = $this->getLanguage(); $text = ''; diff --git a/includes/specials/SpecialTags.php b/includes/specials/SpecialTags.php index 625c82b97b79..159f02d5f9cf 100644 --- a/includes/specials/SpecialTags.php +++ b/includes/specials/SpecialTags.php @@ -193,8 +193,8 @@ class SpecialTags extends SpecialPage { } private function doTagRow( - $tag, $hitcount, $showManageActions, $showDeleteActions, $showEditLinks - ) { + string $tag, int $hitcount, bool $showManageActions, bool $showDeleteActions, bool $showEditLinks + ): string { $newRow = ''; $newRow .= Xml::tags( 'td', null, Xml::element( 'code', null, $tag ) ); diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php index 930c03e39944..e3486316e8ce 100644 --- a/includes/specials/SpecialUndelete.php +++ b/includes/specials/SpecialUndelete.php @@ -198,7 +198,7 @@ class SpecialUndelete extends SpecialPage { return true; } - private function loadRequest( $par ) { + private function loadRequest( ?string $par ) { $request = $this->getRequest(); $user = $this->getUser(); @@ -551,7 +551,7 @@ class SpecialUndelete extends SpecialPage { return true; } - private function showRevision( $timestamp ) { + private function showRevision( string $timestamp ) { if ( !preg_match( '/[0-9]{14}/', $timestamp ) ) { return; } @@ -1416,7 +1416,7 @@ class SpecialUndelete extends SpecialPage { return Xml::tags( 'li', $attribs, $revisionRow ) . "\n"; } - private function formatFileRow( $row ) { + private function formatFileRow( \stdClass $row ): string { $file = ArchivedFile::newFromRow( $row ); $ts = wfTimestamp( TS_MW, $row->fa_timestamp ); $user = $this->getUser(); diff --git a/includes/specials/SpecialUserRights.php b/includes/specials/SpecialUserRights.php index 79018cd27eb0..0fa6b38273be 100644 --- a/includes/specials/SpecialUserRights.php +++ b/includes/specials/SpecialUserRights.php @@ -291,7 +291,7 @@ class SpecialUserRights extends SpecialPage { } } - private function getSuccessURL() { + private function getSuccessURL(): string { return $this->getPageTitle( $this->mTarget )->getFullURL(); } diff --git a/includes/specials/SpecialVersion.php b/includes/specials/SpecialVersion.php index 6541721995e0..8584e96567dc 100644 --- a/includes/specials/SpecialVersion.php +++ b/includes/specials/SpecialVersion.php @@ -1227,7 +1227,7 @@ class SpecialVersion extends SpecialPage { return implode( "\n", $ret ); } - private function openExtType( ?string $text = null, ?string $name = null ) { + private function openExtType( ?string $text = null, ?string $name = null ): string { $out = ''; $opt = [ 'class' => 'wikitable plainlinks mw-installed-software' ]; diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php index 654e545d191e..4b4eb716770b 100644 --- a/includes/specials/SpecialWatchlist.php +++ b/includes/specials/SpecialWatchlist.php @@ -849,7 +849,7 @@ class SpecialWatchlist extends ChangesListSpecialPage { $this->setBottomText( $opts ); } - private function cutoffselector( $options ) { + private function cutoffselector( FormOptions $options ): string { $selected = (float)$options['days']; $maxDays = $this->getConfig()->get( MainConfigNames::RCMaxAge ) / ( 3600 * 24 ); if ( $selected <= 0 ) { diff --git a/includes/specials/SpecialWhatLinksHere.php b/includes/specials/SpecialWhatLinksHere.php index 81c95775248b..e2203c993e18 100644 --- a/includes/specials/SpecialWhatLinksHere.php +++ b/includes/specials/SpecialWhatLinksHere.php @@ -493,7 +493,7 @@ class SpecialWhatLinksHere extends FormSpecialPage { return Xml::openElement( 'ul', ( $level ? [] : [ 'id' => 'mw-whatlinkshere-list' ] ) ); } - private function listItem( stdClass $row, PageIdentity $nt, LinkTarget $target, bool $notClose = false ) { + private function listItem( stdClass $row, PageIdentity $nt, LinkTarget $target, bool $notClose = false ): string { $legacyTitle = $this->titleFactory->newFromPageIdentity( $nt ); if ( $row->rd_from || $row->page_is_redirect ) { @@ -594,7 +594,7 @@ class SpecialWhatLinksHere extends FormSpecialPage { return $this->getLanguage()->pipeList( $links ); } - private function getPrevNext( $prevNamespace, $prevPageId, $nextNamespace, $nextPageId ) { + private function getPrevNext( $prevNamespace, $prevPageId, $nextNamespace, $nextPageId ): string { $navBuilder = new PagerNavigationBuilder( $this->getContext() ); $navBuilder diff --git a/includes/specials/forms/PreferencesFormOOUI.php b/includes/specials/forms/PreferencesFormOOUI.php index 6ddf2793d288..e9642a18f263 100644 --- a/includes/specials/forms/PreferencesFormOOUI.php +++ b/includes/specials/forms/PreferencesFormOOUI.php @@ -141,7 +141,7 @@ class PreferencesFormOOUI extends OOUIHTMLForm { return $layout; } - private function isMobileLayout() { + private function isMobileLayout(): bool { if ( $this->useMobileLayout === null ) { $skin = $this->getSkin(); $this->useMobileLayout = false; diff --git a/includes/specials/pagers/AllMessagesTablePager.php b/includes/specials/pagers/AllMessagesTablePager.php index c06fdf99443e..01d9626f2b93 100644 --- a/includes/specials/pagers/AllMessagesTablePager.php +++ b/includes/specials/pagers/AllMessagesTablePager.php @@ -124,7 +124,7 @@ class AllMessagesTablePager extends TablePager { } } - private function getAllMessages( $descending ) { + private function getAllMessages( bool $descending ): array { $messageNames = $this->localisationCache->getSubitemList( 'en', 'messages' ); // Normalise message names so they look like page titles and sort correctly - T86139 diff --git a/includes/specials/pagers/ImageListPager.php b/includes/specials/pagers/ImageListPager.php index 0830e316e89e..7e78f7087bfc 100644 --- a/includes/specials/pagers/ImageListPager.php +++ b/includes/specials/pagers/ImageListPager.php @@ -190,7 +190,7 @@ class ImageListPager extends TablePager { return $conds + $this->mQueryConds; } - private function buildQueryConds() { + private function buildQueryConds(): array { $conds = [ 'file_deleted' => 0, 'fr_deleted' => 0, diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php index 48a8f1240273..17f97ee56fba 100644 --- a/includes/upload/UploadBase.php +++ b/includes/upload/UploadBase.php @@ -918,7 +918,7 @@ abstract class UploadBase { return $warnings; } - private function checkLocalFileWasDeleted( LocalFile $localFile ) { + private function checkLocalFileWasDeleted( LocalFile $localFile ): bool { return $localFile->wasDeleted() && !$localFile->exists(); } diff --git a/includes/upload/UploadFromChunks.php b/includes/upload/UploadFromChunks.php index 85a486dbeaf7..d484a3516022 100644 --- a/includes/upload/UploadFromChunks.php +++ b/includes/upload/UploadFromChunks.php @@ -449,7 +449,7 @@ class UploadFromChunks extends UploadFromFile { return $storeStatus; } - private function getChunkFileKey( $index = null ) { + private function getChunkFileKey( ?int $index = null ): string { return $this->mFileKey . '.' . ( $index ?? $this->getChunkIndex() ); } diff --git a/languages/.htaccess b/languages/.htaccess index b66e80882967..2e5c00314d2f 100644 --- a/languages/.htaccess +++ b/languages/.htaccess @@ -1 +1,2 @@ Require all denied +Satisfy All diff --git a/languages/i18n/en.json b/languages/i18n/en.json index 718828a07c33..b75303234f09 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -2655,9 +2655,11 @@ "block-confirm-yes": "Yes", "block-confirm-no": "No", "block-submit": "Submit", - "block-success": "[[Special:Contributions/$1|{{GENDER:$1|$1}}]] has been blocked. See the [[Special:BlockList|block list]] to review blocks.", + "block-success": "[[Special:Contributions/$1|{{GENDER:$1|$1}}]] ([[{{ns:user_talk}}:$1|talk]]) has been blocked. See the [[Special:BlockList|block list]] to review blocks.", "block-user-active-blocks": "Active blocks", "block-user-no-active-blocks": "No active blocks found", + "block-user-active-range-blocks": "Active range blocks", + "block-user-no-active-range-blocks": "No active range blocks found", "block-user-previous-blocks": "Block log", "block-user-no-previous-blocks": "No previous blocks found", "block-user-label-count-exceeds-limit": "$1+", @@ -2679,6 +2681,7 @@ "block-javascript-required": "JavaScript is required to use the Special:Block interface.", "block-removed": "Block has been removed.", "block-reblock-multi-legacy": "This user is blocked multiple times already and cannot be reblocked with this form.", + "block-view-target": "View", "unblockip": "Unblock user", "unblockiptext": "Use the form below to restore write access to a previously blocked IP address or username.", "unblock-target": "Unblock target", diff --git a/languages/i18n/preferences/nia.json b/languages/i18n/preferences/nia.json index 34cdac36382a..7d703aa975a7 100644 --- a/languages/i18n/preferences/nia.json +++ b/languages/i18n/preferences/nia.json @@ -4,7 +4,30 @@ "Slaia" ] }, + "preferences": "Preferensi", + "prefsnologintext2": "Ae bakha ena'ö tola ö'atulö'ö preferensi khöu.", + "prefsnologintext2-for-temp-user": "Fazökhi akun ena'ö tola ö'a'azökhi preferensi.", + "searchprefs": "Alui preferensi", + "searchprefs-noresults": "Lö nisöndra", + "searchprefs-results": "$1 {{PLURAL:$1|nisöndra}}", + "saveprefs": "Irö'ö", + "tooltip-preferences-save": "Irö'ö preferensi", + "savedprefs": "No te'irö'ö khöu preferensi.", + "prefs-back-title": "Mangawuli ba preferensi", + "prefs-tabs-navigation-hint": "Kie: Tola ö'oguna'ö dandra nono wana ba we'amöi ba tab tanö bö'ö si so ba gangolifa tab.", + "prefs-sections-navigation-hint": "Kie: Tola ö'oguna'ö hagu Tab awö Shift+Tab ba we'amöi ba ngawalö waosatö preferensi.", + "prefs-personal": "Profil zangoguna", + "prefs-description-personal": "Atulö'ö hewisa oroma, tefakhai ba fahuhuo ndra'ugö.", + "prefs-info": "Informasi danefa", + "username": "{{GENDER:$1|Töi zangoguna}}:", + "prefs-memberingroups": "{{GENDER:$2|Sifao awö}} ba {{PLURAL:$1|grup}}:", + "group-membership-link-with-expiry": "$1 (irege $2)", + "prefs-edits": "Fa'oya nibulö'ö:", + "prefs-registration": "Inötö tefasura'ö döi:", + "yourrealname": "Töi sindruhu:", + "prefs-help-realname": "Tola so tola lö'ö döi sindruhu.\nNa öbe döimö sindruhu, tola dania latötöi döimö ba ngawalö kontribusi si no öfalua.", "yourpassword": "Ono kusi:", + "prefs-resetpass": "Falali nonokusi", "passwordtooshort": "Si lö'ö-lö'önia so {{PLURAL:$1|1 hurufo|$1 hurufo}} nono kusi.", "passwordtoolong": "Ono kusi tebai töra moroi {{PLURAL:$1|1 hurufo|$1 hurufo}}.", "password-substring-username-match": "Böi te'oroma'ö nono kusi-mö ba döi zangoguna khöu.", @@ -13,46 +36,164 @@ "passwordincommonlist": "Ato zangoguna'ö ono kusi andrö khöu. Alui nono kusi bö'ö.", "prefs-help-yourpassword": "Famangawuli akun aktif. Faigi setelan onönöta ba $1", "tog-prefershttps": "Oguna'ö ''secure connection'' götö we'aso bakha ba gu'ö", + "prefs-help-prefershttps": "Awena dania mufasa preferensi da'a ba ginötö möi'ö zui bakha dania.", + "prefs-user-downloaddata-label": "Faigi data akun:", + "prefs-user-downloaddata-info": "Data akun khögu ba proyek andre", + "prefs-user-restoreprefs-label": "Fangawuli furi stelan:", + "prefs-user-restoreprefs-info": "Fangawuli furi ba preferensi si föföna (ba fefu waosatö)", + "prefs-i18n": "Internasionalisasi", + "yourlanguage": "Li:", + "yourgender": "Hewisa lakaoni khöu?", + "gender-notknown": "Labulö'ö nga'örö wiki", + "gender-unknown": "Ba ginötö latötöi ndra'ugö, heza tola, i'oguna'ö ngawua wehede netral molo'ö jender software andre", + "gender-female": "Ibulö'ö nga'örö wiki", + "gender-male": "Ibulö'ö nga'örö wiki", + "prefs-help-gender": "Tola te'atulö'ö ma zui lö'ö preferensi andre.\nI'oguna'ö ni'atulö'ö bakha ba da'ö dania software andre ba wanötöi ya'ugö ma zui niha bö'ö si faudu ba jender.\nOroma dania ba zato informasi andre.", + "yourvariant": "Variasi li nösi:", + "prefs-help-variant": "Varian ma zui ortografi ni'atulö'ömö ba wangoroma'ö nösi nga'örö wiki andre.", + "prefs-signature": "Tandratanga", "tog-oldsig": "Tandra tanga si no so:", + "yournick": "Tandratanga si bohou:", "tog-fancysig": "Sura dandra tanga hulö teks wiki (lö khai-khai otomatis)", - "tog-requireemail": "Fa'ohe'ö surel wamulö'ö nono kusi (password) ha na so alamat surel ba töi zangoguna.", + "prefs-help-signature": "Fanemali ba nga'örö wahuhuosa silötolalö'ö mutandratangaisi faoma \"<nowiki>~~~~</nowiki>\", sedöna dania tobali tandratangamö ba inötö tesura ia.", + "prefs-signature-invalid-warning": "Tandratanga khö tola fatundraha ba mato ha'uga wakake.", + "prefs-signature-invalid-new": "Lö atulö dandratanga si no so khöu. Lö tola ö'oguna'ö ia irege ö'atulö'ö ia.", + "prefs-signature-invalid-disallow": "Lö atulö dandratanga si no so khöu. Na lö ö'atulö'ö ia, ba te'oguna'ö manö dania dandratanga si föföna so ba ginötö latandratangaisi wanemali.", + "prefs-signature-highlight-error": "Oroma'ö heza alua zi fasala", + "prefs-signature-error-details": "Faigi gonönötania", + "badsig": "Tandratanga si so zi fasala.\nFareso kode HTML.", + "badsiglength": "Anau sibai dandratanga khöu.\nTebai töra ia moroi ba $1 {{PLURAL:$1|hurufo}}.", + "badsigsubst": "No oi faruka dandratanga andre khöu (duma-dumania <code>subst:</code> ma <code><nowiki>~~~~</nowiki></code>).", + "badsightml": "So kode HTML si lö atulö bakha ba dandratangamö andre:", + "badsiglinks": "Silötolalö'ö so khai-khai ba nga'örö zangoguna khöu ma zui ba kontribusi bakha ba dandratanga. Nönö ia, duma-dumania: <code>$1</code>.", + "badsiglinebreak": "Silötolalö'ö ha sara khoi teks wiki manö dandratanga.", + "prefs-email": "Opsi sura elektronis", + "youremail": "Email:", + "prefs-setemail": "Sura alamat email", + "prefs-changeemail": "Falali ma zui heta alamat email", + "prefs-help-email": "Tola so tola lö'ö alamat email, bahiza moguna ia na ö'andrö ena'ö nonokusi (password) sibohou, ba ginötö olifu ndra'ugö nonokusi si no so.", + "prefs-help-email-required": "Moguna mube'e alamat email.", + "prefs-help-email-others": "Tola göi öbe ena'ö lafa'ohe'ö khöu duria ira sangoguna bö'ö faoma khai-khai si so ba nga'örö huhuo zangoguna khöu. Lö sa te'oroma'ö khöra alamat emailmö.", + "tog-requireemail": "Fa'ohe'ö surel wamulö'ö nono kusi (password) ha na so alamat surel awö döi zangoguna.", + "prefs-help-requireemail": "Moguna da'a ena'ö terorogö privasi ba teba'agö email si lö faudu.", "noemailprefs": "Be alamat email bakha ba preferensi ena'ö tola te'oguna'ö fitur andrö.", "emailnotauthenticated": "Lö nasa tefaduhu'ö alamat email-mö.\nBörö da'ö lö tefa'ohe'ö khöu duria sanandrösa ba da'e tou.", "emailconfirmlink": "Faduhu'ö alamat email-mö", + "prefs-emailconfirm-label": "Faduhu'ö email", "emailauthenticated": "No tefaduhu'ö alamat email-mö ba $2 bözi $3.", + "allowemail": "Tehegö lafa'ohe'ö khögu email sangoguna bö'ö", + "email-allow-new-users-label": "Tehegö email moroi ba zangoguna sibohou sibai", + "prefs-help-email-allow-new-users": "Na auri da'a, tola dania lafa'ohe'ö khöu email ya'ira sangoguna si lö otomatis [[{{MediaWiki:grouppage-autoconfirmed}}|tefaduhugö]].", "tog-ccmeonemails": "Fa'ohe'ö khögu kopi surel nifa'ohe'ögu khö niha bö'ö", + "email-mutelist-label": "Böi tehegö ndra sangoguna andre ba wama'ohe'ö khögu email:", "tog-enotifwatchlistpages": "Faohe'ö khögu surel na tebulö sambua nga'örö ma berkas si so ba gangolita nihörögögu", "tog-enotifusertalkpages": "Fa'ohe'ö khögu surel na labulö'ö nga'örö wahuhuosa khögu", "tog-enotifminoredits": "Fa'ohe'ö göi khögu surel ba wamulö'ö side-ide", "tog-enotifrevealaddr": "Oroma'ö alamat surelgu ba notifikasi surel", + "prefs-user-pages": "Nga'örö zangoguna", + "prefs-rendering": "Khala-khala", + "prefs-description-rendering": "Atulö'ö guli, fa'ebolo, ba lala wombaso.", + "prefs-skin": "Uli", + "skin-preview": "Khala-khala", + "prefs-common-config": "CSS/JavaScript si faoma te'oguna'ö ba fefu guli:", + "prefs-custom-css": "CSS nifa'anö samösa", + "prefs-custom-js": "JavaScript nifa'anö samösa", + "prefs-custom-cssjs-safemode": "Lö hadöi CSS/JavaScript nifa'anö samösa börö auri iada'a \"safe mode\". Faigi hewisa [[#mw-input-wpforcesafemode|wamunu \"safe mode\"]] ena'ö tola auri CSS/JavaScript nifa'anö samösa.", + "prefs-skin-prefs": "Preferensi guli", + "prefs-skin-responsive": "Orifi mode responsif", + "prefs-help-skin-responsive": "Adaptasi layout ba wa'ebolo layar HP.", + "prefs-dateformat": "Format ginötö", + "datedefault": "Lö preferensi", + "prefs-timeoffset": "Inötö otalua", + "servertime": "Inötö server:", + "localtime": "Inötö lokal:", + "timezonelegend": "Zona ginötö:", + "timezoneuseserverdefault": "Oguna'ö zi tohöna ba wiki ($1)", + "timezoneuseoffset": "Bö'önia (otalua ginötö te'erai moroi ba UTC)", + "timezone-useoffset-placeholder": "Duma-dumania: \"-07:00\" or \"01:00\"", + "timezone-invalid": "No fasala zona ginötö ma otalua ginötö.", + "guesstimezone": "Fo'ösi moroi ba mbolokha gu'ö (browser)", + "timezoneregion-africa": "Afrika", + "timezoneregion-america": "Amerika", + "timezoneregion-antarctica": "Antartika", + "timezoneregion-arctic": "Arktik", + "timezoneregion-asia": "Asia", + "timezoneregion-atlantic": "Samudra Atlantik", + "timezoneregion-australia": "Australia", + "timezoneregion-europe": "Europa", + "timezoneregion-indian": "Samudera Hindia", + "timezoneregion-pacific": "Samudera Pasifik", + "prefs-files": "Berkas", + "imagemaxsize": "Fondrege wa'ebua gambara ba nga'örö wamotokhi:", + "thumbsize": "Fa'ebua gambara side-ide:", + "prefs-diffs": "Sifabö'ö", "tog-diffonly": "Böi oroma'ö nösi nga'örö tou diffs", "tog-norollbackdiff": "Böi oroma'ö diff aefa tefalua ''rollback''", + "prefs-advancedrendering": "Opsi bö'ö", "tog-underline": "Lala wanura khai-khai", "underline-default": "Uli ma default wuka gu'ö", "underline-never": "Lö'ö", "underline-always": "Selalu", "tog-showhiddencats": "Oroma'ö kategori si tobini", "tog-showrollbackconfirmation": "Oroma'ö konfirmasi na tehöndrögö khai-khai ''rollback''", + "tog-forcesafemode": "Lö mamalö orifi [[mw:Manual:Safemode|\"safe mode\"]]", + "prefs-help-forcesafemode": "Bunu skip awö lembar gaya ba wiki", + "prefs-editing": "Mamulö'ö", + "prefs-description-editing": "Atulö'ö hewisa öfalua, ö'osisi'ö ba öfaigi wamulö'ö.", + "prefs-advancedediting": "Opsi umum", "tog-editsectiononrightclick": "Be tola mubulö'ö seksi faoma fangöhöndrögö kambölö ba högö seksi", "tog-editondblclick": "Bulö'ö nga'örö na mendrua lahöndrögö", + "prefs-editor": "Samulö'ö", "editfont-style": "Ngawalö hurufo nahia wamulö'ö", "editfont-monospace": "Hurufo ''monospace''", "editfont-sansserif": "Hurufo ''sans-serif''", "editfont-serif": "Hurufo ''serif''", "tog-minordefault": "Tandrai fefu nibulö'ö otomatis side-ide", "tog-forceeditsummary": "Oroma'ö khögu na urugi nahia wanura fangadogo'ö si lö ösi (ma zui sura fangadogo'ö default)", + "tog-editrecovery": "Orifi fitur [[Special:EditRecovery|{{int:editrecovery}}]]", + "tog-editrecovery-help": "Tola öbe'e zöndrau ba [$1 nga'örö wahuhuosa proyek].", "tog-useeditwarning": "Oroma'ö na uröi sambua nga'örö si so nibulö'ö si lö ni'irö'ö", + "prefs-preview": "Khala-khala", "tog-previewonfirst": "Oroma'ö khala-khala na tebörögö wamulö'ö", "tog-previewontop": "Oroma'ö khala-khala föna kota bulö'ö", "tog-uselivepreview": "Oroma'ö khala-khala ba böi fuli halö nga'örö", + "prefs-discussion": "Nga'örö wahuhuosa", + "prefs-developertools": "Fakake ndra samazökhi software", + "prefs-rc": "Safuria tebulö", + "prefs-description-rc": "Atulö'ö gangolifa zafuria tebulö.", + "prefs-displayrc": "Opsi wangoroma'ö", + "recentchangesdays": "Fa'ara zafuria tebulö sedöna mu'oroma'ö:", + "recentchangesdays-max": "Fondrege ara $1 {{PLURAL:$1|ngaluo}}", + "recentchangescount": "Fa'oya wamulö'ö ni'oroma'ö ba zafuria tebulö, waö-waö nga'örö ba ba lahe wamulö'ö molo'ö standar:", + "prefs-help-recentchangescount": "Fondrege wa'oya: 1000", + "prefs-advancedrc": "Opsi bö'ö", "tog-usenewrc": "Sifabö'ö molo'ö nga'örö ba zifaö'ö faoma nihörögö safuria", + "rcfilters-preference-label": "Oguna'ö wangoroma'ö si lö JavaScript", + "rcfilters-preference-help": "Oroma'ö [[{{#special:RecentChanges}}|zafuria tebulö]] awö [[{{#special:RecentChangesLinked}}|si tebulö si fakhai]] si lö mutafi ma zui mutandrai.", + "prefs-changesrc": "Si tebulö ni'oroma'ö", "tog-hideminor": "Bini'ö wamulö'ö side-ide moroi ba zifabö'ö safuria", "tog-hidecategorization": "Bini'ö kategorisasi nga'örö", "tog-hidepatrolled": "Bini'ö nibulö'ö nitandrai mufareso moroi ba zifabö'ö safuria", "tog-newpageshidepatrolled": "Bini'ö nga'örö nitandrai mufareso moroi ba gangolifa nga'örö si bohou", "tog-shownumberswatching": "Oroma'ö wa'oya zangoguna sangohörögö", + "prefs-watchlist": "Nihörögö", + "prefs-description-watchlist": "Atulö'ö gangolifa nga'örö somasi'ö öhörögö.", + "prefs-editwatchlist": "Bulö'ö gangolifa nihörögö", + "prefs-editwatchlist-label": "Bulö'ö nösi si so ba gangolifa nihörögö khöu:", + "prefs-editwatchlist-edit": "Faigi ba heta nga'örö si so ba gangolifa nihörögö khöu", + "prefs-editwatchlist-raw": "Bulö'ö gangolifa nihörögö si lö na ö'ohalöŵögöigö", + "prefs-editwatchlist-clear": "Heta fefu nösi gangolita nihörögö khöu", + "prefs-displaywatchlist": "Opsi wamongoroma'ö", + "prefs-watchlist-days": "Ndrege wa'oya ngaluo ni'oroma'ö ba gangolifa nihörögö", + "prefs-watchlist-days-max": "Fondregenia $1 {{PLURAL:$1|ngaluo}}", + "prefs-watchlist-edits": "Ndrege wa'oya wamulö'ö ni'oroma'ö ba gangolita nihörögö:", + "prefs-watchlist-edits-max": "Fondrege wa'oya: 1000", + "prefs-advancedwatchlist": "Opsi bö'ö", "tog-extendwatchlist": "Oroma'ö fefu zifabö'ö, tenga ha safuria", "tog-watchlistunwatchlinks": "Nönö dandra wangöhörögö ({{int:Watchlist-unwatch}}/{{int:Watchlist-unwatch-undo}}) ena'ö te'oroma'ö nga'örö si no tebulö (moguna te'orifi JavaScript)", + "rcfilters-watchlist-preference-label": "Oguna'ö wangoroma'ö si lö JavaScript", + "rcfilters-watchlist-preference-help": "Oroma'ö [[{{#special:Watchlist}}|nga'örö nihörögö]] si lö mutafi'ö ma zui mutandrai.", + "prefs-changeswatchlist": "Si tebulö ni'oroma'ö", "tog-watchlisthideminor": "Bini'ö nibulö'ö side-ide moroi ba gangolita nihörögö", "tog-watchlisthidebots": "Bini'ö nibulö'ö robot moroi ba gangolita nihörögö", "tog-watchlisthideown": "Bini'ö nibulö'ögu moroi ba gangolita nihörögö", @@ -61,10 +202,27 @@ "tog-watchlistreloadautomatically": "Fuli halö gangolita nihörögö ero tebulö wanafi (moguna JavaScript)", "tog-watchlisthidecategorization": "Bini'ö kategorisasi nga'örö", "tog-watchlisthidepatrolled": "Bini'ö nibulö'ö si no mutandrai moroi ba gangolita nihörögö", + "prefs-pageswatchlist": "Nga'örö nihörögö", "tog-watchdefault": "Nönögö nga'örö ba berkas nibulö'ögu ba gangolita nihörögögu", "tog-watchmoves": "Nönögö nga'örö ba berkas nifawu'agu ba gangolita nihörögögu", "tog-watchdeletion": "Nönögö nga'örö ba berkas nitibo'ögu ba gangolita nihörögögu", "tog-watchcreations": "Nönögö nga'örö nifa'anögu ba berkas nifairögu ba gangolita nihörögö", "tog-watchuploads": "Nönögö berkas si bohou ufairö ba gangolita nihörögögu", - "tog-watchrollback": "Nönögö nga'örö nifangawuligögu (''rollback'') ba gangolita nihörögögu" + "tog-watchrollback": "Nönögö nga'örö nifangawuligögu (''rollback'') ba gangolita nihörögögu", + "prefs-tokenwatchlist": "Token", + "prefs-watchlist-token": "Token gangolita nihörögö:", + "prefs-watchlist-managetokens": "Atulö'ö token", + "prefs-help-tokenmanagement": "Tola öfaigi ba öfangawuli furi nonokusi akun-mö ena'ö tola öfaigi nga'örö gu'ö si so ba gangolita nihörögö khöu. Böi ombakha'ö nonokusi andrö, ena'ö böi la'ila lafaigi gangolita nihörögö khöu niha bö'ö.", + "prefs-searchoptions": "Alui", + "prefs-description-searchoptions": "Atulö'ö hewisa tefalua wamo'ösi otomatis awö lua-luania.", + "prefs-searchmisc": "Umum", + "searchlimit-label": "Fa'oya nisöndra ni'oroma'ö ero nga'örö:", + "searchlimit-help": "Fondrege wa'oya: $1", + "search-thumbnail-extra-namespaces-label": "Oroma'ö gambara side-ide ba Special:Search on Desktop", + "search-thumbnail-extra-namespaces-message": "Oroma'ö gambara side-ide $1 {{PLURAL:$2|ruang nama}} ba nga'örö {{#special:search}}", + "prefs-advancedsearchoptions": "Opsi bö'ö", + "prefs-misc": "Tanöbö'önia", + "prefs-reset-intro": "Ba nga'örö andre tola öfangawuli furi preferensi khöu ba wangatulö'ö si tohöna", + "prefs-reset-confirm": "Lau, omasido ufangawuli furi preferensi khögu.", + "restoreprefs": "Fangawuli fefu ba stelan si tohöna" } diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index 086df3bd8bcd..ab1f44781fb6 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -2925,6 +2925,8 @@ "block-success": "Message shown when a block is successfully applied on [[Special:Block]]. Similar to {{msg-mw|blockipsuccesstext}} except only basic wikitext is allowed.\n\nParameters:\n* $1 - target username", "block-user-active-blocks": "Header for the active blocks table", "block-user-no-active-blocks": "Message shown when there are no active blocks for the user", + "block-user-active-range-blocks": "Header for the 'Active range blocks' table shown at [[Special:Block]].", + "block-user-no-active-range-blocks": "Message shown at [[Special:Block]] when there are no active range blocks for an IP address.", "block-user-previous-blocks": "Header for the previous blocks table", "block-user-no-previous-blocks": "Message shown when there are no previous block log entries for the user", "block-user-label-count-exceeds-limit": "{{optional}}\nLabel displaying the total number of blocks or block events, with a '+' sign to indicate additional entries.\n\nParameters:\n* $1 - total number of blocks or log events", @@ -2946,6 +2948,7 @@ "block-javascript-required": "Error message shown when JavaScript is disabled on [[Special:Block]]", "block-removed": "Success message shown after a block has been removed.", "block-reblock-multi-legacy": "Error message shown on [[Special:Block]].", + "block-view-target": "Message for link to load an IP range in the [[Special:Block]] interface.\n{{Identical|View}}", "unblockip": "Used as title and legend for the form in [[Special:Unblock]].", "unblockiptext": "Used in the {{msg-mw|Unblockip}} form on [[Special:Unblock]].", "unblock-target": "Used as a title and legend for the form in [[Special:Unblock]]", diff --git a/maintenance/.htaccess b/maintenance/.htaccess index b66e80882967..2e5c00314d2f 100644 --- a/maintenance/.htaccess +++ b/maintenance/.htaccess @@ -1 +1,2 @@ Require all denied +Satisfy All diff --git a/maintenance/SqliteMaintenance.php b/maintenance/SqliteMaintenance.php index da9a3ab59485..609c4cc18387 100644 --- a/maintenance/SqliteMaintenance.php +++ b/maintenance/SqliteMaintenance.php @@ -109,7 +109,7 @@ class SqliteMaintenance extends Maintenance { } } - private function backup( DBConnRef $dbw, $fileName ) { + private function backup( DBConnRef $dbw, string $fileName ) { $this->output( "Backing up database:\n Locking..." ); $dbw->query( 'BEGIN IMMEDIATE TRANSACTION', __METHOD__ ); $ourFile = $dbw->__call( 'getDbFilePath', [] ); diff --git a/maintenance/benchmarks/benchmarkJsonCodec.php b/maintenance/benchmarks/benchmarkJsonCodec.php index 7ab1d0bd67b4..d5ec449955fe 100644 --- a/maintenance/benchmarks/benchmarkJsonCodec.php +++ b/maintenance/benchmarks/benchmarkJsonCodec.php @@ -76,7 +76,7 @@ class BenchmarkJsonCodec extends Benchmarker { ] ); } - private function loadData( $file ) { + private function loadData( string $file ) { if ( str_ends_with( $file, '.php' ) ) { $data = include $file; if ( !$data ) { diff --git a/maintenance/benchmarks/benchmarkSettings.php b/maintenance/benchmarks/benchmarkSettings.php index 2f49284c0860..66b12df55e82 100644 --- a/maintenance/benchmarks/benchmarkSettings.php +++ b/maintenance/benchmarks/benchmarkSettings.php @@ -46,7 +46,7 @@ class BenchmarkSettings extends Benchmarker { $this->addDescription( 'Benchmark loading settings files.' ); } - private function newSettingsBuilder() { + private function newSettingsBuilder(): SettingsBuilder { $extReg = new ExtensionRegistry(); $configBuilder = new ArrayConfigBuilder(); $phpIniSink = new NullIniSink(); diff --git a/maintenance/benchmarks/benchmarkTidy.php b/maintenance/benchmarks/benchmarkTidy.php index c193ee5dcc28..6ed3b3ccf58d 100644 --- a/maintenance/benchmarks/benchmarkTidy.php +++ b/maintenance/benchmarks/benchmarkTidy.php @@ -41,7 +41,7 @@ class BenchmarkTidy extends Benchmarker { $this->benchmark( $html ); } - private function benchmark( $html ) { + private function benchmark( string $html ) { $services = $this->getServiceContainer(); $contLang = $services->getContentLanguage(); $tidy = $services->getTidy(); diff --git a/maintenance/checkDependencies.php b/maintenance/checkDependencies.php index 69a646382d71..25fd10eb070e 100644 --- a/maintenance/checkDependencies.php +++ b/maintenance/checkDependencies.php @@ -86,7 +86,7 @@ class CheckDependencies extends Maintenance { } } - private function loadThing( &$dependencies, $name, $extensions, $skins ) { + private function loadThing( array &$dependencies, string $name, array $extensions, array $skins ) { $extDir = $this->getConfig()->get( MainConfigNames::ExtensionDirectory ); $styleDir = $this->getConfig()->get( MainConfigNames::StyleDirectory ); $queue = []; @@ -154,8 +154,8 @@ class CheckDependencies extends Maintenance { $this->addToDependencies( $dependencies, $extensions, $skins, $name ); } - private function addToDependencies( &$dependencies, $extensions, $skins, - $why = null, $status = null, $message = null + private function addToDependencies( array &$dependencies, array $extensions, array $skins, + ?string $why = null, ?string $status = null, ?string $message = null ) { $mainRegistry = ExtensionRegistry::getInstance(); $iter = [ 'extensions' => $extensions, 'skins' => $skins ]; @@ -186,7 +186,7 @@ class CheckDependencies extends Maintenance { } } - private function formatForHumans( $dependencies ) { + private function formatForHumans( array $dependencies ): string { $text = ''; foreach ( $dependencies as $type => $things ) { $text .= ucfirst( $type ) . "\n" . str_repeat( '=', strlen( $type ) ) . "\n"; diff --git a/maintenance/cleanupImages.php b/maintenance/cleanupImages.php index b4634e12cb05..d471e97039cf 100644 --- a/maintenance/cleanupImages.php +++ b/maintenance/cleanupImages.php @@ -28,6 +28,7 @@ use MediaWiki\FileRepo\LocalRepo; use MediaWiki\Parser\Sanitizer; use MediaWiki\Title\Title; +use Wikimedia\Rdbms\IReadableDatabase; // @codeCoverageIgnoreStart require_once __DIR__ . '/TableCleanup.php'; @@ -137,7 +138,7 @@ class CleanupImages extends TableCleanup { return $this->repo->getRootDirectory() . '/' . $this->repo->getHashPath( $name ) . $name; } - private function imageExists( $name, $db ) { + private function imageExists( string $name, IReadableDatabase $db ): bool { return (bool)$db->newSelectQueryBuilder() ->select( '1' ) ->from( 'image' ) @@ -146,7 +147,7 @@ class CleanupImages extends TableCleanup { ->fetchField(); } - private function pageExists( $name, $db ) { + private function pageExists( string $name, IReadableDatabase $db ): bool { return (bool)$db->newSelectQueryBuilder() ->select( '1' ) ->from( 'page' ) @@ -158,7 +159,7 @@ class CleanupImages extends TableCleanup { ->fetchField(); } - private function pokeFile( $orig, $new ) { + private function pokeFile( string $orig, string $new ) { $path = $this->filePath( $orig ); if ( !file_exists( $path ) ) { $this->output( "missing file: $path\n" ); @@ -232,12 +233,12 @@ class CleanupImages extends TableCleanup { } } - private function appendTitle( $name, $suffix ) { + private function appendTitle( string $name, string $suffix ): string { return preg_replace( '/^(.*)(\..*?)$/', "\\1$suffix\\2", $name ); } - private function buildSafeTitle( $name ) { + private function buildSafeTitle( string $name ) { $x = preg_replace_callback( '/([^' . Title::legalChars() . ']|~)/', [ $this, 'hexChar' ], diff --git a/maintenance/cleanupPreferences.php b/maintenance/cleanupPreferences.php index 0b4707b4411c..afd85bd4fe7c 100644 --- a/maintenance/cleanupPreferences.php +++ b/maintenance/cleanupPreferences.php @@ -32,6 +32,7 @@ use MediaWiki\MainConfigNames; use MediaWiki\Maintenance\Maintenance; use MediaWiki\User\Options\UserOptionsLookup; use Wikimedia\Rdbms\IExpression; +use Wikimedia\Rdbms\IReadableDatabase; use Wikimedia\Rdbms\LikeValue; /** @@ -99,7 +100,7 @@ class CleanupPreferences extends Maintenance { } } - private function deleteByWhere( $dbr, $startMessage, $where ) { + private function deleteByWhere( IReadableDatabase $dbr, string $startMessage, array $where ) { $this->output( $startMessage . "...\n" ); $dryRun = $this->hasOption( 'dry-run' ); diff --git a/maintenance/cleanupWatchlist.php b/maintenance/cleanupWatchlist.php index 61ef59bb2e36..f9a9dba53e65 100644 --- a/maintenance/cleanupWatchlist.php +++ b/maintenance/cleanupWatchlist.php @@ -80,7 +80,7 @@ class CleanupWatchlist extends TableCleanup { $this->progress( 0 ); } - private function removeWatch( $row ) { + private function removeWatch( \stdClass $row ): int { if ( !$this->dryrun && $this->hasOption( 'fix' ) ) { $dbw = $this->getPrimaryDB(); $dbw->newDeleteQueryBuilder() diff --git a/maintenance/compareParsers.php b/maintenance/compareParsers.php index 4e97cc03815d..09a683090c7e 100644 --- a/maintenance/compareParsers.php +++ b/maintenance/compareParsers.php @@ -123,7 +123,7 @@ class CompareParsers extends DumpIterator { } } - private function stripParameters( $text ) { + private function stripParameters( string $text ): string { if ( !$this->stripParametersEnabled ) { return $text; } @@ -191,7 +191,7 @@ class CompareParsers extends DumpIterator { } } - private static function checkParserLocally( $parserName ) { + private static function checkParserLocally( string $parserName ) { /* Look for the parser in a file appropriately named in the current folder */ if ( !class_exists( $parserName ) && file_exists( "$parserName.php" ) ) { global $wgAutoloadClasses; diff --git a/maintenance/convertExtensionToRegistration.php b/maintenance/convertExtensionToRegistration.php index 3c2f5c2975f8..bed732a5092a 100644 --- a/maintenance/convertExtensionToRegistration.php +++ b/maintenance/convertExtensionToRegistration.php @@ -226,7 +226,7 @@ class ConvertExtensionToRegistration extends Maintenance { } } - private function stripPath( $val, $dir ) { + private function stripPath( string $val, string $dir ): string { if ( $val === $dir ) { $val = ''; } elseif ( strpos( $val, $dir ) === 0 ) { diff --git a/maintenance/deleteAutoPatrolLogs.php b/maintenance/deleteAutoPatrolLogs.php index 00e5a5908c39..35949641e414 100644 --- a/maintenance/deleteAutoPatrolLogs.php +++ b/maintenance/deleteAutoPatrolLogs.php @@ -100,7 +100,7 @@ class DeleteAutoPatrolLogs extends Maintenance { } } - private function getRows( $fromId ) { + private function getRows( ?int $fromId ): array { $dbr = $this->getReplicaDB(); $before = $this->getOption( 'before', false ); @@ -127,7 +127,7 @@ class DeleteAutoPatrolLogs extends Maintenance { ->fetchFieldValues(); } - private function getRowsOld( $fromId ) { + private function getRowsOld( ?int $fromId ): ?array { $dbr = $this->getReplicaDB(); $batchSize = $this->getBatchSize(); $before = $this->getOption( 'before', false ); diff --git a/maintenance/deleteOldRevisions.php b/maintenance/deleteOldRevisions.php index ff62cdf24b86..0aeca0ae6531 100644 --- a/maintenance/deleteOldRevisions.php +++ b/maintenance/deleteOldRevisions.php @@ -46,7 +46,7 @@ class DeleteOldRevisions extends Maintenance { $this->doDelete( $this->hasOption( 'delete' ), $this->getArgs( 'page_id' ) ); } - private function doDelete( $delete = false, $pageIds = [] ) { + private function doDelete( bool $delete = false, array $pageIds = [] ) { # Data should come off the master, wrapped in a transaction $dbw = $this->getPrimaryDB(); $this->beginTransaction( $dbw, __METHOD__ ); diff --git a/maintenance/dumpUploads.php b/maintenance/dumpUploads.php index c476fa594e0c..901da886a543 100644 --- a/maintenance/dumpUploads.php +++ b/maintenance/dumpUploads.php @@ -115,7 +115,7 @@ By default, outputs relative paths against the parent directory of $wgUploadDire } } - private function outputItem( $name, $shared ) { + private function outputItem( string $name, bool $shared ) { $file = $this->getServiceContainer()->getRepoGroup()->findFile( $name ); if ( $file && $this->filterItem( $file, $shared ) ) { $filename = $file->getLocalRefPath(); @@ -126,7 +126,7 @@ By default, outputs relative paths against the parent directory of $wgUploadDire } } - private function filterItem( $file, $shared ) { + private function filterItem( File $file, bool $shared ): bool { return $shared || $file->isLocal(); } } diff --git a/maintenance/getConfiguration.php b/maintenance/getConfiguration.php index 22fcc8b3b8ec..52b27a8a15d3 100644 --- a/maintenance/getConfiguration.php +++ b/maintenance/getConfiguration.php @@ -187,7 +187,7 @@ class GetConfiguration extends Maintenance { return trim( $ret, "\n" ); } - private function isAllowedVariable( $value ) { + private function isAllowedVariable( $value ): bool { if ( is_array( $value ) ) { foreach ( $value as $v ) { if ( !$this->isAllowedVariable( $v ) ) { diff --git a/maintenance/importDump.php b/maintenance/importDump.php index ee3ff3ff1999..e7a6ee26d763 100644 --- a/maintenance/importDump.php +++ b/maintenance/importDump.php @@ -159,7 +159,7 @@ TEXT $this->nsFilter = array_unique( array_map( [ $this, 'getNsIndex' ], $namespaces ) ); } - private function getNsIndex( $namespace ) { + private function getNsIndex( string $namespace ): int { $contLang = $this->getServiceContainer()->getContentLanguage(); $result = $contLang->getNsIndex( $namespace ); if ( $result !== false ) { @@ -249,7 +249,7 @@ TEXT } } - private function report( $final = false ) { + private function report( bool $final = false ) { if ( $final xor ( $this->pageCount % $this->reportingInterval == 0 ) ) { $this->showReport(); } @@ -275,11 +275,11 @@ TEXT $this->waitForReplication(); } - private function progress( $string ) { + private function progress( string $string ) { fwrite( $this->stderr, $string . "\n" ); } - private function importFromFile( $filename ) { + private function importFromFile( string $filename ): bool { if ( preg_match( '/\.gz$/', $filename ) ) { $filename = 'compress.zlib://' . $filename; } elseif ( preg_match( '/\.bz2$/', $filename ) ) { @@ -296,7 +296,7 @@ TEXT return $this->importFromHandle( $file ); } - private function importFromStdin() { + private function importFromStdin(): bool { $file = fopen( 'php://stdin', 'rt' ); if ( self::posix_isatty( $file ) ) { $this->maybeHelp( true ); @@ -305,7 +305,7 @@ TEXT return $this->importFromHandle( $file ); } - private function importFromHandle( $handle ) { + private function importFromHandle( $handle ): bool { $this->startTime = microtime( true ); $user = User::newSystemUser( User::MAINTENANCE_SCRIPT_USER, [ 'steal' => true ] ); diff --git a/maintenance/importImages.php b/maintenance/importImages.php index c9a831dec084..aab6d5215d9b 100644 --- a/maintenance/importImages.php +++ b/maintenance/importImages.php @@ -523,7 +523,7 @@ class ImportImages extends Maintenance { return html_entity_decode( $matches[1] ); } - private function getFileUserFromSourceWiki( $wiki_host, $file ) { + private function getFileUserFromSourceWiki( string $wiki_host, string $file ) { $url = $wiki_host . '/api.php?action=query&format=xml&titles=File:' . rawurlencode( $file ) . '&prop=imageinfo&&iiprop=user'; $body = $this->getServiceContainer()->getHttpRequestFactory()->get( $url, [], __METHOD__ ); diff --git a/maintenance/includes/MaintenanceParameters.php b/maintenance/includes/MaintenanceParameters.php index 8d5855dc7904..a03b76935958 100644 --- a/maintenance/includes/MaintenanceParameters.php +++ b/maintenance/includes/MaintenanceParameters.php @@ -635,7 +635,7 @@ class MaintenanceParameters { return implode( '', $output ); } - private function formatHelpItems( array $items, $heading, $descWidth, $tab ) { + private function formatHelpItems( array $items, string $heading, int $descWidth, string $tab ): string { if ( $items === [] ) { return ''; } diff --git a/maintenance/includes/MaintenanceRunner.php b/maintenance/includes/MaintenanceRunner.php index 625fac947a9f..43f3441267d4 100644 --- a/maintenance/includes/MaintenanceRunner.php +++ b/maintenance/includes/MaintenanceRunner.php @@ -64,7 +64,7 @@ class MaintenanceRunner { $this->addDefaultParams(); } - private function getConfig() { + private function getConfig(): Config { if ( $this->config === null ) { $this->config = $this->getServiceContainer()->getMainConfig(); } @@ -222,7 +222,7 @@ class MaintenanceRunner { } } - private static function isAbsolutePath( $path ) { + private static function isAbsolutePath( string $path ): bool { if ( str_starts_with( $path, '/' ) ) { return true; } diff --git a/maintenance/includes/SevenZipStream.php b/maintenance/includes/SevenZipStream.php index a133c5f01832..d1dd4520468f 100644 --- a/maintenance/includes/SevenZipStream.php +++ b/maintenance/includes/SevenZipStream.php @@ -50,7 +50,7 @@ class SevenZipStream { } } - private function stripPath( $path ) { + private function stripPath( string $path ): string { $prefix = 'mediawiki.compress.7z://'; return substr( $path, strlen( $prefix ) ); diff --git a/maintenance/includes/TextPassDumper.php b/maintenance/includes/TextPassDumper.php index 0b82b17944ee..2703116d3614 100644 --- a/maintenance/includes/TextPassDumper.php +++ b/maintenance/includes/TextPassDumper.php @@ -376,7 +376,7 @@ TEXT $this->timeExceeded = true; } - private function checkIfTimeExceeded() { + private function checkIfTimeExceeded(): bool { if ( $this->maxTimeAllowed && ( $this->lastTime - $this->timeOfCheckpoint > $this->maxTimeAllowed ) ) { @@ -1042,7 +1042,7 @@ TEXT } } - private function isValidTextId( $id ) { + private function isValidTextId( string $id ): bool { if ( preg_match( '/:/', $id ) ) { return $id !== 'tt:0'; } elseif ( preg_match( '/^\d+$/', $id ) ) { diff --git a/maintenance/install.php b/maintenance/install.php index 2a4234e96167..85dc6c012e3c 100644 --- a/maintenance/install.php +++ b/maintenance/install.php @@ -230,7 +230,7 @@ class CommandLineInstaller extends Maintenance { return true; } - private function generateStrongPassword() { + private function generateStrongPassword(): string { $strongPassword = ''; $strongPasswordLength = 20; $strongPasswordChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-={}|;:,.<>?'; diff --git a/maintenance/language/generateCollationData.php b/maintenance/language/generateCollationData.php index a9047f73f986..ba55d34683be 100644 --- a/maintenance/language/generateCollationData.php +++ b/maintenance/language/generateCollationData.php @@ -117,7 +117,7 @@ class GenerateCollationData extends Maintenance { $uxr->readChars( [ $this, 'charCallback' ] ); } - private function charCallback( $data ) { + private function charCallback( array $data ) { // Skip non-printable characters, // but do not skip a normal space (U+0020) since // people like to use that as a fake no header symbol. diff --git a/maintenance/language/generateUcfirstOverrides.php b/maintenance/language/generateUcfirstOverrides.php index d6698eec9a27..7be7ca05edb7 100644 --- a/maintenance/language/generateUcfirstOverrides.php +++ b/maintenance/language/generateUcfirstOverrides.php @@ -74,7 +74,7 @@ class GenerateUcfirstOverrides extends Maintenance { ); } - private function loadJson( $filename ) { + private function loadJson( string $filename ) { $data = file_get_contents( $filename ); if ( $data === false ) { $msg = sprintf( "Could not load data from file '%s'\n", $filename ); diff --git a/maintenance/language/importExtensionMessages.php b/maintenance/language/importExtensionMessages.php index c5ebd5b7df00..2d11e19a7a4a 100644 --- a/maintenance/language/importExtensionMessages.php +++ b/maintenance/language/importExtensionMessages.php @@ -60,7 +60,7 @@ class ImportExtensionMessages extends Maintenance { $this->extensionDir = $config->get( MainConfigNames::ExtensionDirectory ); } - private function getMessagesDirs( $extData ) { + private function getMessagesDirs( array $extData ): array { if ( isset( $extData['MessagesDirs'] ) ) { $messagesDirs = []; foreach ( $extData['MessagesDirs'] as $dirs ) { @@ -78,7 +78,7 @@ class ImportExtensionMessages extends Maintenance { return $messagesDirs; } - private function processDir( $dir ) { + private function processDir( string $dir ) { $path = $this->extensionDir . "/{$this->extName}/$dir"; foreach ( new DirectoryIterator( $path ) as $file ) { @@ -91,7 +91,7 @@ class ImportExtensionMessages extends Maintenance { } } - private function processFile( $lang, $extI18nPath ) { + private function processFile( string $lang, string $extI18nPath ) { $extJson = file_get_contents( $extI18nPath ); if ( $extJson === false ) { $this->error( "Unable to read i18n file \"$extI18nPath\"" ); @@ -126,7 +126,7 @@ class ImportExtensionMessages extends Maintenance { $this->setCoreData( $lang, $coreData ); } - private function getCoreData( $lang ) { + private function getCoreData( string $lang ) { if ( !isset( $this->coreDataCache[$lang] ) ) { $corePath = MW_INSTALL_PATH . "/languages/i18n/$lang.json"; // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged @@ -142,7 +142,7 @@ class ImportExtensionMessages extends Maintenance { return $this->coreDataCache[$lang]; } - private function setCoreData( $lang, $data ) { + private function setCoreData( string $lang, $data ) { if ( !isset( $this->coreDataCache[$lang] ) ) { // Non-existent file, do not create return; diff --git a/maintenance/mcc.php b/maintenance/mcc.php index 99766423f96e..8b23ca428fcd 100644 --- a/maintenance/mcc.php +++ b/maintenance/mcc.php @@ -185,7 +185,7 @@ class Mcc extends Maintenance { } while ( !$quit ); } - private function mccGetHelp( $command ) { + private function mccGetHelp( $command ): string { $output = ''; $commandList = [ 'get' => 'grabs something', diff --git a/maintenance/migrateExternallinks.php b/maintenance/migrateExternallinks.php index 21f8cf5f6468..683c063af1cb 100644 --- a/maintenance/migrateExternallinks.php +++ b/maintenance/migrateExternallinks.php @@ -70,7 +70,7 @@ class MigrateExternallinks extends LoggedUpdateMaintenance { return true; } - private function handleBatch( $lowId ) { + private function handleBatch( int $lowId ): int { $batchSize = $this->getBatchSize(); // range is inclusive, let's subtract one. $highId = $lowId + $batchSize - 1; diff --git a/maintenance/migrateFileTables.php b/maintenance/migrateFileTables.php index d40205927344..c48a7c0352ce 100644 --- a/maintenance/migrateFileTables.php +++ b/maintenance/migrateFileTables.php @@ -141,7 +141,7 @@ class MigrateFileTables extends Maintenance { . "$totalRowsInserted rows have been inserted into filerevision table.\n" ); } - private function handleFile( stdClass $row ) { + private function handleFile( stdClass $row ): int { $repo = $this->getServiceContainer()->getRepoGroup() ->newCustomLocalRepo(); $dbw = $this->getPrimaryDB(); diff --git a/maintenance/migrateLinksTable.php b/maintenance/migrateLinksTable.php index 5eb0e2c20819..43e29d3aa695 100644 --- a/maintenance/migrateLinksTable.php +++ b/maintenance/migrateLinksTable.php @@ -94,7 +94,7 @@ class MigrateLinksTable extends LoggedUpdateMaintenance { return true; } - private function handlePageBatch( $lowPageId, $mapping, $table ) { + private function handlePageBatch( int $lowPageId, array $mapping, string $table ) { $batchSize = $this->getBatchSize(); $targetColumn = $mapping[$table]['target_id']; $pageIdColumn = $mapping[$table]['page_id']; diff --git a/maintenance/populateChangeTagDef.php b/maintenance/populateChangeTagDef.php index aa8b36cd491d..f75b3b64938c 100644 --- a/maintenance/populateChangeTagDef.php +++ b/maintenance/populateChangeTagDef.php @@ -187,7 +187,7 @@ class PopulateChangeTagDef extends LoggedUpdateMaintenance { } } - private function backpopulateChangeTagPerTag( $tagName, $tagId ) { + private function backpopulateChangeTagPerTag( string $tagName, int $tagId ) { $dbr = $this->getReplicaDB(); $dbw = $this->getPrimaryDB(); $sleep = (int)$this->getOption( 'sleep', 0 ); diff --git a/maintenance/purgePage.php b/maintenance/purgePage.php index 1f533bef1f30..8053e9366530 100644 --- a/maintenance/purgePage.php +++ b/maintenance/purgePage.php @@ -51,7 +51,7 @@ class PurgePage extends Maintenance { } } - private function purge( $titleText ) { + private function purge( string $titleText ) { $title = Title::newFromText( $titleText ); if ( $title === null ) { diff --git a/maintenance/rebuildImages.php b/maintenance/rebuildImages.php index fc506efe63a3..7983cbcdf8dd 100644 --- a/maintenance/rebuildImages.php +++ b/maintenance/rebuildImages.php @@ -40,6 +40,7 @@ use MediaWiki\Maintenance\Maintenance; use MediaWiki\Specials\SpecialUpload; use MediaWiki\User\User; use Wikimedia\Rdbms\IMaintainableDatabase; +use Wikimedia\Rdbms\SelectQueryBuilder; /** * Maintenance script to update image metadata records. @@ -128,7 +129,7 @@ class ImageBuilder extends Maintenance { $this->table = $table; } - private function progress( $updated ) { + private function progress( int $updated ) { $this->updated += $updated; $this->processed++; if ( $this->processed % 100 != 0 ) { @@ -155,7 +156,7 @@ class ImageBuilder extends Maintenance { flush(); } - private function buildTable( $table, $queryBuilder, $callback ) { + private function buildTable( string $table, SelectQueryBuilder $queryBuilder, callable $callback ) { $count = $this->dbw->newSelectQueryBuilder() ->select( 'count(*)' ) ->from( $table ) @@ -181,7 +182,7 @@ class ImageBuilder extends Maintenance { $this->buildTable( 'image', FileSelectQueryBuilder::newForFile( $this->getReplicaDB() ), $callback ); } - private function imageCallback( $row ) { + private function imageCallback( \stdClass $row ): bool { // Create a File object from the row // This will also upgrade it $file = $this->getRepo()->newFileFromRow( $row ); @@ -194,7 +195,7 @@ class ImageBuilder extends Maintenance { [ $this, 'oldimageCallback' ] ); } - private function oldimageCallback( $row ) { + private function oldimageCallback( \stdClass $row ): bool { // Create a File object from the row // This will also upgrade it if ( $row->oi_archive_name == '' ) { @@ -225,7 +226,7 @@ class ImageBuilder extends Maintenance { } } - private function addMissingImage( $filename, $fullpath ) { + private function addMissingImage( string $filename, string $fullpath ) { $timestamp = $this->dbw->timestamp( $this->getRepo()->getFileTimestamp( $fullpath ) ); $services = $this->getServiceContainer(); diff --git a/maintenance/storage/checkStorage.php b/maintenance/storage/checkStorage.php index 54add8a7334a..384ab296a1b1 100644 --- a/maintenance/storage/checkStorage.php +++ b/maintenance/storage/checkStorage.php @@ -400,7 +400,7 @@ class CheckStorage extends Maintenance { } } - private function addError( $type, $msg, $ids ) { + private function addError( string $type, string $msg, $ids ) { if ( is_array( $ids ) && count( $ids ) == 1 ) { $ids = reset( $ids ); } @@ -423,7 +423,7 @@ class CheckStorage extends Maintenance { $this->errors[$type] += array_fill_keys( $revIds, true ); } - private function checkExternalConcatBlobs( $externalConcatBlobs ) { + private function checkExternalConcatBlobs( array $externalConcatBlobs ) { if ( !count( $externalConcatBlobs ) ) { return; } @@ -465,7 +465,7 @@ class CheckStorage extends Maintenance { } } - private function restoreText( $revIds, $xml ) { + private function restoreText( array $revIds, string $xml ) { global $wgDBname; $tmpDir = wfTempDir(); diff --git a/maintenance/storage/moveToExternal.php b/maintenance/storage/moveToExternal.php index 8f66c7061c07..b27e33014700 100644 --- a/maintenance/storage/moveToExternal.php +++ b/maintenance/storage/moveToExternal.php @@ -129,7 +129,7 @@ class MoveToExternal extends Maintenance { return $this->doMoveToExternal(); } - private function doMoveToExternal() { + private function doMoveToExternal(): bool { $success = true; $dbr = $this->getReplicaDB(); @@ -253,7 +253,7 @@ class MoveToExternal extends Maintenance { return $success; } - private function compress( $text, $flags ) { + private function compress( string $text, array $flags ): array { if ( $this->gzip && !in_array( 'gzip', $flags ) ) { $flags[] = 'gzip'; $text = gzdeflate( $text ); @@ -261,7 +261,7 @@ class MoveToExternal extends Maintenance { return [ $text, $flags ]; } - private function resolveLegacyEncoding( $text, $flags ) { + private function resolveLegacyEncoding( string $text, array $flags ): array { if ( $this->legacyEncoding !== null && !in_array( 'utf-8', $flags ) && !in_array( 'utf8', $flags ) @@ -290,7 +290,7 @@ class MoveToExternal extends Maintenance { return [ $text, $flags ]; } - private function resolveStubs( $stubIDs ) { + private function resolveStubs( array $stubIDs ) { if ( $this->dryRun ) { print "Note: resolving stubs in dry run mode is expected to fail, " . "because the main blobs have not been moved to external storage.\n"; diff --git a/maintenance/storage/recompressTracked.php b/maintenance/storage/recompressTracked.php index 691cc43426c7..b6f39493704d 100644 --- a/maintenance/storage/recompressTracked.php +++ b/maintenance/storage/recompressTracked.php @@ -174,7 +174,7 @@ class RecompressTracked { } } - private function logToFile( $msg, $file ) { + private function logToFile( string $msg, string $file ) { $header = '[' . date( 'd\TH:i:s' ) . '] ' . wfHostname() . ' ' . posix_getpid(); if ( $this->childId !== false ) { $header .= "({$this->childId})"; diff --git a/maintenance/storage/trackBlobs.php b/maintenance/storage/trackBlobs.php index 8f6d83ee5320..5eef62f9cd75 100644 --- a/maintenance/storage/trackBlobs.php +++ b/maintenance/storage/trackBlobs.php @@ -111,7 +111,7 @@ class TrackBlobs extends Maintenance { $dbw->sourceFile( __DIR__ . '/blob_tracking.sql' ); } - private function getTextClause() { + private function getTextClause(): IExpression { if ( !$this->textClause ) { $dbr = $this->getReplicaDB(); $conds = []; @@ -128,7 +128,7 @@ class TrackBlobs extends Maintenance { return $this->textClause; } - private function interpretPointer( $text ) { + private function interpretPointer( string $text ) { if ( !preg_match( '!^DB://(\w+)/(\d+)(?:/([0-9a-fA-F]+)|)$!', $text, $m ) ) { return false; } diff --git a/maintenance/update.php b/maintenance/update.php index 4a748ba55138..209fc691447b 100755 --- a/maintenance/update.php +++ b/maintenance/update.php @@ -261,7 +261,7 @@ class UpdateMediaWiki extends Maintenance { parent::validateParamsAndArgs(); } - private function formatWarnings( array $warnings ) { + private function formatWarnings( array $warnings ): string { $text = ''; foreach ( $warnings as $warning ) { $warning = wordwrap( $warning, 75, "\n " ); diff --git a/maintenance/updateSearchIndex.php b/maintenance/updateSearchIndex.php index 21ca64d5d67c..fbec1c183144 100644 --- a/maintenance/updateSearchIndex.php +++ b/maintenance/updateSearchIndex.php @@ -89,7 +89,7 @@ class UpdateSearchIndex extends Maintenance { } } - private function doUpdateSearchIndex( $start, $end ) { + private function doUpdateSearchIndex( string $start, string $end ) { global $wgDisableSearchUpdate; $wgDisableSearchUpdate = false; diff --git a/resources/Resources.php b/resources/Resources.php index d70593e97cbe..2883843e5bd6 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -2350,6 +2350,7 @@ return [ 'mediawiki.Title', ], 'messages' => [ + 'api-error-unknownerror', 'blanknamespace', 'block-actions', 'block-added-message', @@ -2400,12 +2401,15 @@ return [ 'block-update', 'block-updated-message', 'block-user-active-blocks', + 'block-user-active-range-blocks', 'block-user-label-count-exceeds-limit', 'block-user-no-active-blocks', + 'block-user-no-active-range-blocks', 'block-user-no-previous-blocks', 'block-user-no-suppressed-blocks', 'block-user-previous-blocks', 'block-user-suppressed-blocks', + 'block-view-target', 'blockipsuccesssub', 'blocklist-actions-header', 'blocklist-by', diff --git a/resources/src/mediawiki.page.ready/ready.js b/resources/src/mediawiki.page.ready/ready.js index 999230f66ce4..51bf137df07b 100644 --- a/resources/src/mediawiki.page.ready/ready.js +++ b/resources/src/mediawiki.page.ready/ready.js @@ -245,33 +245,8 @@ function isSearchInput( element ) { * @param {string} moduleName Name of a module */ function loadSearchModule( moduleName ) { - // T251544: Collect search performance metrics to compare Vue search with - // mediawiki.searchSuggest performance. Marks and Measures will only be - // recorded on the Vector skin. - // - // Vue search isn't loaded through this function so we are only collecting - // legacy search performance metrics here. - - const shouldTestSearch = !!( moduleName === 'mediawiki.searchSuggest' && - mw.config.get( 'skin' ) === 'vector' && - window.performance && - performance.mark && - performance.measure && - - performance.getEntriesByName ), - loadStartMark = 'mwVectorLegacySearchLoadStart', - loadEndMark = 'mwVectorLegacySearchLoadEnd'; - function requestSearchModule() { - if ( shouldTestSearch ) { - performance.mark( loadStartMark ); - } - mw.loader.using( moduleName, () => { - if ( shouldTestSearch && performance.getEntriesByName( loadStartMark ).length ) { - performance.mark( loadEndMark ); - performance.measure( 'mwVectorLegacySearchLoadStartToLoadEnd', loadStartMark, loadEndMark ); - } - } ); + mw.loader.using( moduleName ); } // Load the module once a search input is focussed. diff --git a/resources/src/mediawiki.special.block/SpecialBlock.vue b/resources/src/mediawiki.special.block/SpecialBlock.vue index 54a0740bc1c3..fe9b4e8105de 100644 --- a/resources/src/mediawiki.special.block/SpecialBlock.vue +++ b/resources/src/mediawiki.special.block/SpecialBlock.vue @@ -57,6 +57,12 @@ @remove-block="onRemoveBlock" ></block-log> <block-log + v-if="mw.util.isIPAddress( store.targetUser, true )" + :key="`${submitCount}-active-ranges`" + :can-delete-log-entry="false" + block-log-type="active-ranges" + ></block-log> + <block-log :key="`${submitCount}-recent`" block-log-type="recent" :can-delete-log-entry="canDeleteLogEntry" @@ -483,7 +489,8 @@ module.exports = exports = defineComponent( { doRemoveBlock, showBlockLogs, formVisible, - wasRedirected + wasRedirected, + mw }; } } ); diff --git a/resources/src/mediawiki.special.block/components/BlockLog.vue b/resources/src/mediawiki.special.block/components/BlockLog.vue index b526a355f728..f6aa3ffff805 100644 --- a/resources/src/mediawiki.special.block/components/BlockLog.vue +++ b/resources/src/mediawiki.special.block/components/BlockLog.vue @@ -1,7 +1,7 @@ <template> <cdx-accordion :class="`mw-block-log mw-block-log__type-${ blockLogType }`" - :open="open || ( blockLogType === 'active' && alreadyBlocked )" + :open="open || ( blockLogType.includes( 'active' ) && alreadyBlocked )" > <template #title> {{ title }} @@ -37,6 +37,12 @@ <!-- Block parameters --> <td class="mw-block-log__parameters"> <ul v-if="item.action !== 'unblock'"> + <!-- target --> + <li v-if="blockLogType === 'active-ranges'"> + <a :href="mw.Title.makeTitle( -1, `Contributions/${ item.target }` ).getUrl()"> + {{ item.target }} + </a> + </li> <!-- block duration --> <li v-if="!!item.duration"> {{ item.duration }} @@ -160,7 +166,16 @@ </cdx-button> </span> </td> - <td v-else-if="blockLogType !== 'active' && canDeleteLogEntry"> + <td v-else-if="blockLogType === 'active-ranges'"> + <a + class="mw-block-log__actions" + :href="mw.util.getUrl( 'Special:Block', { wpTarget: item.target } )" + @click="( e ) => onViewIPRange( e, item.target )" + > + {{ $i18n( 'block-view-target' ).text() }} + </a> + </td> + <td v-else-if="!blockLogType.includes( 'active' ) && canDeleteLogEntry"> <a class="mw-block-log__actions" :href="mw.util.getUrl( 'Special:RevisionDelete', { type: 'logging', [`ids[${ item.logid }]`]: 1 } )" @@ -222,13 +237,16 @@ module.exports = exports = defineComponent( { const store = useBlockStore(); const { alreadyBlocked, blockId, targetUser } = storeToRefs( store ); let title = mw.message( 'block-user-previous-blocks' ).text(); - let emptyState = mw.message( 'block-user-no-previous-blocks' ).text(); + const emptyState = ref( mw.message( 'block-user-no-previous-blocks' ).text() ); if ( props.blockLogType === 'active' ) { title = mw.message( 'block-user-active-blocks' ).text(); - emptyState = mw.message( 'block-user-no-active-blocks' ).text(); + emptyState.value = mw.message( 'block-user-no-active-blocks' ).text(); + } else if ( props.blockLogType === 'active-ranges' ) { + title = mw.message( 'block-user-active-range-blocks' ).text(); + emptyState.value = mw.message( 'block-user-no-active-range-blocks' ).text(); } else if ( props.blockLogType === 'suppress' ) { title = mw.message( 'block-user-suppressed-blocks' ).text(); - emptyState = mw.message( 'block-user-no-suppressed-blocks' ).text(); + emptyState.value = mw.message( 'block-user-no-suppressed-blocks' ).text(); } const columns = []; @@ -243,9 +261,9 @@ module.exports = exports = defineComponent( { { id: 'reason', label: mw.message( 'blocklist-reason' ).text() }, { id: 'blockedby', label: mw.message( 'blocklist-by' ).text() }, { id: 'timestamp', label: mw.message( 'blocklist-timestamp' ).text(), width: '15%' }, - ...( props.blockLogType === 'active' || props.canDeleteLogEntry ) ? + ...( props.blockLogType.includes( 'active' ) || props.canDeleteLogEntry ) ? [ { - id: props.blockLogType === 'active' ? 'modify' : 'hide', + id: 'actions', label: mw.message( 'blocklist-actions-header' ) } ] : [] @@ -309,6 +327,18 @@ module.exports = exports = defineComponent( { } ) ); } + /** + * Load the given IP range as the target user and reset the form. + * + * @param {Event} e + * @param {string} target + */ + function onViewIPRange( e, target ) { + e.preventDefault(); + targetUser.value = target; + store.resetForm(); + } + watch( targetUser, ( newValue ) => { if ( newValue ) { store.getBlockLogData( props.blockLogType ).then( ( responses ) => { @@ -332,6 +362,15 @@ module.exports = exports = defineComponent( { } else { // List of active blocks. for ( const block of data.blocks ) { + const isRangeBlock = !!block.rangestart; + const isCurrentTarget = block.user === targetUser.value; + // Skip range blocks for 'active', or non-range blocks for 'active-ranges'. + if ( ( props.blockLogType === 'active' && isRangeBlock && !isCurrentTarget ) || + ( props.blockLogType === 'active-ranges' && ( !isRangeBlock || isCurrentTarget ) ) + ) { + continue; + } + newData.push( { // Store the entire API response, for passing in when editing the block. modify: block, @@ -358,6 +397,8 @@ module.exports = exports = defineComponent( { } logEntries.value = newData; + } ).catch( ( error ) => { + emptyState.value = mw.message( 'api-error-unknownerror', error ).text(); } ); } else { moreBlocks.value = false; @@ -381,7 +422,8 @@ module.exports = exports = defineComponent( { logEntriesCount, infoChip, shouldBlockFlagBeVisible, - mwNamespaces + mwNamespaces, + onViewIPRange }; } } ); diff --git a/resources/src/mediawiki.special.block/stores/block.js b/resources/src/mediawiki.special.block/stores/block.js index e8c67e8d12f4..15324052a89f 100644 --- a/resources/src/mediawiki.special.block/stores/block.js +++ b/resources/src/mediawiki.special.block/stores/block.js @@ -495,7 +495,11 @@ module.exports = exports = defineStore( 'block', () => { } let target = targetUser.value; - if ( mw.util.isIPAddress( target, true ) ) { + const isValidIpOrRange = mw.util.isIPAddress( target, true ); + const isIpRange = isValidIpOrRange && !mw.util.isIPAddress( target, false ); + + // Sanitize IP ranges for block log queries. + if ( isIpRange ) { target = util.sanitizeRange( target ); } @@ -523,7 +527,11 @@ module.exports = exports = defineStore( 'block', () => { params.list = 'logevents|blocks'; params.letype = 'block'; params.bkprop = 'id|user|by|timestamp|expiry|reason|parsedreason|range|flags|restrictions'; - params.bkusers = target; + if ( isValidIpOrRange ) { + params.bkip = target; + } else { + params.bkusers = target; + } const actualPromise = api.get( params ); actualPromise.then( ( data ) => { diff --git a/sql/.htaccess b/sql/.htaccess index b66e80882967..2e5c00314d2f 100644 --- a/sql/.htaccess +++ b/sql/.htaccess @@ -1 +1,2 @@ Require all denied +Satisfy All diff --git a/tests/.htaccess b/tests/.htaccess index b66e80882967..2e5c00314d2f 100644 --- a/tests/.htaccess +++ b/tests/.htaccess @@ -1 +1,2 @@ Require all denied +Satisfy All diff --git a/tests/jest/jest.setup.js b/tests/jest/jest.setup.js index 51880fb52bf6..9dc109d12649 100644 --- a/tests/jest/jest.setup.js +++ b/tests/jest/jest.setup.js @@ -83,6 +83,7 @@ const mw = { getUrl: jest.fn( ( pageName ) => '/wiki/' + pageName ), isIPAddress: jest.fn(), isInfinity: jest.fn(), + sanitizeIP: jest.fn(), getParamValue: jest.fn().mockReturnValue( null ) }, Rest: RestMock diff --git a/tests/jest/mediawiki.special.block/SpecialBlock.setup.js b/tests/jest/mediawiki.special.block/SpecialBlock.setup.js index 58c604dc6b88..66598823b700 100644 --- a/tests/jest/mediawiki.special.block/SpecialBlock.setup.js +++ b/tests/jest/mediawiki.special.block/SpecialBlock.setup.js @@ -603,6 +603,118 @@ function mockMwApiGet( additionalMocks = [] ) { } } }, + // IP with range blocks (T389987) + { + params: { + list: 'logevents|blocks', + letype: 'block', + letitle: 'User:1.2.3.20' + }, + response: { + query: { + blocks: [ + { + id: 2000, + user: '1.2.3.20', + by: 'Admin', + timestamp: '2025-03-31T23:40:04Z', + expiry: '2025-04-03T23:40:04Z', + 'duration-l10n': '3 days', + reason: 'Vandalism', + parsedreason: 'Vandalism', + automatic: false, + anononly: true, + nocreate: true, + autoblock: false, + noemail: false, + hidden: false, + allowusertalk: false, + partial: false, + restrictions: [] + }, { + id: 2001, + user: '1.2.0.0/18', + by: 'Admin', + timestamp: '2025-03-31T23:24:54Z', + expiry: '2025-10-01T23:24:54Z', + 'duration-l10n': '6 months', + reason: 'Spamming links to external sites', + parsedreason: 'Spamming links to external sites', + rangestart: '1.2.0.0', + rangeend: '1.2.63.255', + automatic: false, + anononly: true, + nocreate: true, + autoblock: false, + noemail: false, + hidden: false, + allowusertalk: true, + partial: false, + restrictions: [] + }, { + id: 2002, + user: '1.2.3.20/30', + by: 'Admin', + timestamp: '2025-03-31T23:22:16Z', + expiry: '2027-03-01T23:22:16Z', + 'duration-l10n': '1 year, 10 months and 29 days', + reason: 'Inserting false information', + parsedreason: 'Inserting false information', + rangestart: '1.2.3.20', + rangeend: '1.2.3.23', + automatic: false, + anononly: true, + nocreate: true, + autoblock: false, + noemail: false, + hidden: false, + allowusertalk: true, + partial: false, + restrictions: [] + } + ], + logevents: [ + { + logid: 3000, + ns: 2, + title: 'User:1.2.3.20', + pageid: 0, + logpage: 0, + params: { + duration: '3 days', + flags: [ + 'anononly', + 'nocreate', + 'nousertalk' + ], + blockId: 827, + sitewide: true, + expiry: '2025-04-03T23:40:04Z', + 'duration-l10n': '3 days' + }, + type: 'block', + action: 'block', + user: 'Admin', + timestamp: '2025-03-31T23:40:04Z', + parsedcomment: 'Unacceptable username' + } + ] + } + } + }, + { + params: { + list: 'logevents|blocks', + letype: 'block', + letitle: 'User:192.168.0.1' + }, + response: { + query: { + blocks: [], + logevents: [] + } + } + }, // Used in UserLookup { params: { diff --git a/tests/jest/mediawiki.special.block/SpecialBlock.test.js b/tests/jest/mediawiki.special.block/SpecialBlock.test.js index 9970666ea6dd..5bbe50e65cc2 100644 --- a/tests/jest/mediawiki.special.block/SpecialBlock.test.js +++ b/tests/jest/mediawiki.special.block/SpecialBlock.test.js @@ -371,6 +371,28 @@ describe( 'SpecialBlock', () => { expect( wrapper.find( '.mw-block-success' ).exists() ).toBeTruthy(); } ); + it( 'should show "Active blocks" and "Active range blocks" on the given IP', async () => { + mw.util.isIPAddress = jest.fn().mockReturnValue( true ); + wrapper = getSpecialBlock( { blockTargetUser: '1.2.3.20', blockTargetExists: true } ); + await flushPromises(); + expect( wrapper.find( '.mw-block-log__type-active' ).exists() ).toBeTruthy(); + expect( wrapper.findAll( '.mw-block-log__type-active tbody tr' ) ).toHaveLength( 1 ); + expect( wrapper.find( '.mw-block-log__type-active-ranges' ).exists() ).toBeTruthy(); + expect( wrapper.findAll( '.mw-block-log__type-active-ranges tbody tr' ) ).toHaveLength( 2 ); + } ); + + it( 'should show an empty "Active range blocks" for an IP with no range blocks', async () => { + mw.util.isIPAddress = jest.fn().mockReturnValue( true ); + wrapper = getSpecialBlock( { blockTargetUser: '192.168.0.1', blockTargetExists: true } ); + await flushPromises(); + expect( wrapper.find( '.mw-block-log__type-active' ).exists() ).toBeTruthy(); + expect( wrapper.find( '.mw-block-log__type-active tbody' ).text() ) + .toStrictEqual( 'block-user-no-active-blocks' ); + expect( wrapper.find( '.mw-block-log__type-active-ranges' ).exists() ).toBeTruthy(); + expect( wrapper.find( '.mw-block-log__type-active-ranges tbody' ).text() ) + .toStrictEqual( 'block-user-no-active-range-blocks' ); + } ); + afterEach( () => { wrapper.unmount(); } ); diff --git a/tests/parser/ParserTestRunner.php b/tests/parser/ParserTestRunner.php index e76c22e52f99..9e66e5a13a04 100644 --- a/tests/parser/ParserTestRunner.php +++ b/tests/parser/ParserTestRunner.php @@ -24,9 +24,6 @@ * @todo Make this more independent of the configuration (and if possible the database) * @file * @ingroup Testing - * @phan-file-suppress UnusedPluginSuppression,UnusedPluginFileSuppression - * Prevent phan from crashing CI if - * ParsoidParserHook::getParserTestConfigFileName() happens to exist */ use MediaWiki\Content\WikitextContent; @@ -501,10 +498,8 @@ class ParserTestRunner { ]; // Add magic words used in Parsoid-native extension modules if ( method_exists( ParsoidParserHook::class, 'getParserTestConfigFileName' ) ) { - // https://github.com/phan/phan/issues/2628 - // @phan-suppress-next-line PhanUndeclaredStaticMethod $filename = ParsoidParserHook::getParserTestConfigFileName(); - if ( $filename !== null && file_exists( $filename ) ) { + if ( file_exists( $filename ) ) { $config = json_decode( file_get_contents( $filename ), true ); $extraMagicWords = array_merge( $extraMagicWords, $config['magicwords'] ?? [] @@ -1387,10 +1382,10 @@ class ParserTestRunner { } if ( isset( $opts['maxincludesize'] ) ) { - $options->setMaxIncludeSize( $opts['maxincludesize'] ); + $options->setMaxIncludeSize( (int)$opts['maxincludesize'] ); } if ( isset( $opts['maxtemplatedepth'] ) ) { - $options->setMaxTemplateDepth( $opts['maxtemplatedepth'] ); + $options->setMaxTemplateDepth( (int)$opts['maxtemplatedepth'] ); } return [ $title, $options, $revProps['revid'] ]; @@ -1510,8 +1505,10 @@ class ParserTestRunner { $section = $opts['section']; $out = $parser->getSection( $wikitext, $section ); } elseif ( isset( $opts['replace'] ) ) { - $section = $opts['replace'][0]; - $replace = $opts['replace'][1]; + $o = $opts['replace']; + '@phan-var array $o'; // Phan gets confused about types + $section = $o[0]; + $replace = $o[1]; $out = $parser->replaceSection( $wikitext, $section, $replace ); } elseif ( isset( $opts['comment'] ) ) { $out = MediaWikiServices::getInstance()->getCommentFormatter()->format( $wikitext, $title, $local ); @@ -2063,6 +2060,7 @@ class ParserTestRunner { private function runSelserEditTest( Parsoid $parsoid, PageConfig $pageConfig, ParserTest $test, ParserTestMode $mode, Document $doc ): array { + // @phan-suppress-next-line PhanTypeMismatchArgumentNullable $test->changetree is non-null here $test->applyChanges( [], $doc, $test->changetree ); $editedHTML = ContentUtils::toXML( DOMCompat::getBody( $doc ) ); @@ -2159,7 +2157,7 @@ class ParserTestRunner { $doc = $this->fetchCachedDoc( $parsoid, $pageConfig, $test ); $test->seed = $i . ''; $test->changetree = $test->generateChanges( $doc ); - if ( $test->changetree ) { + if ( $test->changetree ) { // testing for [] not null // new mode with the generated changetree $nmode = new ParserTestMode( 'selser', $test->changetree ); [ $out, $expected ] = $this->runSelserEditTest( $parsoid, $pageConfig, $test, $nmode, $doc ); @@ -2171,6 +2169,7 @@ class ParserTestRunner { } // $test->changetree can be [] which is a NOP for testing // but not a NOP for duplicate change tree tests. + // @phan-suppress-next-line PhanTypeMismatchArgumentNullable $test->changetree is non-null here if ( $test->isDuplicateChangeTree( $test->changetree ) ) { // Once we get a duplicate change tree, we can no longer // generate and run new tests. So, be done now! diff --git a/tests/phpunit/includes/Storage/DerivedPageDataUpdaterTest.php b/tests/phpunit/includes/Storage/DerivedPageDataUpdaterTest.php index e292eea0a8d9..91614f56649d 100644 --- a/tests/phpunit/includes/Storage/DerivedPageDataUpdaterTest.php +++ b/tests/phpunit/includes/Storage/DerivedPageDataUpdaterTest.php @@ -2,6 +2,7 @@ namespace MediaWiki\Tests\Storage; +use ArrayUtils; use DummyContentHandlerForTesting; use MediaWiki\CommentStore\CommentStoreComment; use MediaWiki\Config\ServiceOptions; @@ -134,7 +135,7 @@ class DerivedPageDataUpdaterTest extends MediaWikiIntegrationTestCase { } $this->getDerivedPageDataUpdater( $page ); // flush cached instance after. - $this->runJobs(); // flush pending updates + $this->runJobs( [ 'minJobs' => 0 ] ); // flush pending updates return $rev; } @@ -1123,14 +1124,19 @@ class DerivedPageDataUpdaterTest extends MediaWikiIntegrationTestCase { } /** + * @dataProvider provideDoUpdatesParams + * * @covers \MediaWiki\Storage\DerivedPageDataUpdater::doUpdates() * @covers \MediaWiki\Storage\DerivedPageDataUpdater::doSecondaryDataUpdates() * @covers \MediaWiki\Storage\DerivedPageDataUpdater::doParserCacheUpdate() */ - public function testDoUpdates() { + public function testDoUpdates( + bool $simulateNullEdit, + bool $simulatePageCreation + ) { $page = $this->getPage( __METHOD__ ); - $content = [ SlotRecord::MAIN => new WikitextContent( 'first [[main]]' ) ]; + $content = [ SlotRecord::MAIN => new WikitextContent( 'current [[main]]' ) ]; $content['aux'] = new WikitextContent( 'Aux [[Nix]]' ); @@ -1142,7 +1148,29 @@ class DerivedPageDataUpdaterTest extends MediaWikiIntegrationTestCase { ); } - $rev = $this->createRevision( $page, 'first', $content ); + // If simulating a null edit, set up a previous revision with the same content as our change. + // Otherwise, initialize the previous revision with different content unless simulating page creation, + // in which case no previous revision should be created at all. + if ( !$simulatePageCreation ) { + $oldContent = $simulateNullEdit ? $content : [ SlotRecord::MAIN => new WikitextContent( 'first [[main]]' ) ]; + $firstRev = $this->createRevision( $page, 'first', $oldContent ); + } else { + $firstRev = null; + } + + $slotsUpdate = RevisionSlotsUpdate::newFromContent( $content ); + + $updater = $this->getServiceContainer() + ->getPageUpdaterFactory() + ->newDerivedPageDataUpdater( $page ); + $updater->prepareContent( $this->getTestUser()->getUserIdentity(), $slotsUpdate ); + + // Don't create a new revision if simulating a null edit. + if ( $simulateNullEdit ) { + $rev = $firstRev; + } else { + $rev = $this->createRevision( $page, 'current', $content ); + } $pageId = $page->getId(); $listenerCalled = 0; @@ -1169,10 +1197,10 @@ class DerivedPageDataUpdaterTest extends MediaWikiIntegrationTestCase { $pcache = $this->getServiceContainer()->getParserCache(); $pcache->deleteOptionsKey( $page ); - $updater = $this->getDerivedPageDataUpdater( $page, $rev ); $updater->setArticleCountMethod( 'link' ); $options = []; // TODO: test *all* the options... + $updater->prepareUpdate( $rev, $options ); $updater->doUpdates(); @@ -1207,9 +1235,19 @@ class DerivedPageDataUpdaterTest extends MediaWikiIntegrationTestCase { ->from( 'site_stats' ) ->where( '1=1' ) ->fetchRow(); - $this->assertSame( $oldStats->ss_total_pages + 1, (int)$stats->ss_total_pages ); - $this->assertSame( $oldStats->ss_total_edits + 1, (int)$stats->ss_total_edits ); - $this->assertSame( $oldStats->ss_good_articles + 1, (int)$stats->ss_good_articles ); + if ( $simulatePageCreation ) { + $this->assertSame( $oldStats->ss_total_pages + 1, (int)$stats->ss_total_pages ); + $this->assertSame( $oldStats->ss_good_articles + 1, (int)$stats->ss_good_articles ); + } else { + $this->assertSame( $oldStats->ss_total_pages, $stats->ss_total_pages ); + $this->assertSame( $oldStats->ss_good_articles, $stats->ss_good_articles ); + } + + if ( !$simulateNullEdit ) { + $this->assertSame( $oldStats->ss_total_edits + 1, (int)$stats->ss_total_edits ); + } else { + $this->assertSame( $oldStats->ss_total_edits, $stats->ss_total_edits ); + } $this->runDeferredUpdates(); $this->assertSame( 1, $listenerCalled, 'PageRevisionUpdatedEvent listener' ); @@ -1222,7 +1260,32 @@ class DerivedPageDataUpdaterTest extends MediaWikiIntegrationTestCase { // TODO: test newtalk update // TODO: test search update // TODO: test site stats good_articles while turning the page into (or back from) a redir. - // TODO: test category membership update (with setRcWatchCategoryMembership()) + } + + public static function provideDoUpdatesParams(): iterable { + $testCases = ArrayUtils::cartesianProduct( + // null or non-null edit + [ true, false ], + // page creation + [ true, false ] + ); + + foreach ( $testCases as $params ) { + [ $simulateNullEdit, $simulatePageCreation ] = $params; + + if ( $simulateNullEdit && $simulatePageCreation ) { + // Page creations cannot be null edits, so don't simulate an impossible scenario + continue; + } + + $description = sprintf( + '%s edit%s', + $simulateNullEdit ? 'null' : 'non-null', + $simulatePageCreation ? ', page creation, ' : '' + ); + + yield $description => $params; + } } /** diff --git a/tests/phpunit/includes/Storage/PageUpdaterTest.php b/tests/phpunit/includes/Storage/PageUpdaterTest.php index 7c8afdbae1da..904ac1069139 100644 --- a/tests/phpunit/includes/Storage/PageUpdaterTest.php +++ b/tests/phpunit/includes/Storage/PageUpdaterTest.php @@ -10,6 +10,7 @@ use MediaWiki\Content\TextContent; use MediaWiki\Content\WikitextContent; use MediaWiki\Deferred\DeferredUpdates; use MediaWiki\Json\FormatJson; +use MediaWiki\MainConfigNames; use MediaWiki\Message\Message; use MediaWiki\Page\Event\PageRevisionUpdatedEvent; use MediaWiki\Page\PageIdentity; @@ -49,6 +50,10 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase { protected function setUp(): void { parent::setUp(); + // Force enable RC entry creation for category changes + // so that tests can verify whether CategoryMembershipChangeJobs get enqueued. + $this->overrideConfigValue( MainConfigNames::RCWatchCategoryMembership, true ); + $slotRoleRegistry = $this->getServiceContainer()->getSlotRoleRegistry(); if ( !$slotRoleRegistry->isDefinedRole( 'aux' ) ) { @@ -69,6 +74,13 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase { // protect against service container resets $this->setService( 'SlotRoleRegistry', $slotRoleRegistry ); + + // Clear some extension hook handlers that may interfere with mock object expectations. + $this->clearHooks( [ + 'RevisionRecordInserted', + 'PageSaveComplete', + 'LinksUpdateComplete', + ] ); } private function getDummyTitle( $method ) { @@ -707,7 +719,8 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase { $this->expectChangeTrackingUpdates( 1, 0, 1, - $page->getNamespace() === NS_USER_TALK ? 1 : 0 + $page->getNamespace() === NS_USER_TALK ? 1 : 0, + 1 ); $this->expectSearchUpdates( 1 ); @@ -751,7 +764,7 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase { // Null edits should not go into recentchanges, should not // increment counters, and should not trigger talk page notifications. - $this->expectChangeTrackingUpdates( 0, 0, 0, 0 ); + $this->expectChangeTrackingUpdates( 0, 0, 0, 0, 0 ); // Update derived data on null edits $this->expectSearchUpdates( 1 ); @@ -796,7 +809,7 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase { // Silent dummy revisions should not go into recentchanges, // should not increment counters, and should not trigger talk page // notifications. - $this->expectChangeTrackingUpdates( 0, 0, 0, 0 ); + $this->expectChangeTrackingUpdates( 0, 0, 0, 0, 0 ); // Do not update derived data on dummy revisions! $this->expectSearchUpdates( 0 ); @@ -1262,7 +1275,7 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase { // Clear pending jobs so the spies don't get confused $this->runJobs(); - $this->expectChangeTrackingUpdates( 0, 0, 0, 0 ); + $this->expectChangeTrackingUpdates( 0, 0, 0, 0, 0 ); $this->expectSearchUpdates( 0 ); $updater = $page->newPageUpdater( $user ); @@ -1420,7 +1433,8 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase { $services->getUserNameUtils(), $services->getTalkPageNotificationManager(), $services->getMainConfig(), - $services->getJobQueueGroup() + $services->getJobQueueGroup(), + $services->getContentHandlerFactory() ); $services->getDomainEventSource() diff --git a/tests/phpunit/includes/api/ApiImportTest.php b/tests/phpunit/includes/api/ApiImportTest.php index a3a3203f26d6..4c6c26e26a1a 100644 --- a/tests/phpunit/includes/api/ApiImportTest.php +++ b/tests/phpunit/includes/api/ApiImportTest.php @@ -30,6 +30,13 @@ class ApiImportTest extends ApiUploadTestCase { } public function testImport() { + // Clear some extension hook handlers that may interfere with mock object expectations. + $this->clearHooks( [ + 'RevisionRecordInserted', + 'PageSaveComplete', + 'LinksUpdateComplete', + ] ); + $title = $this->getNonexistingTestPage()->getTitle(); // We expect two PageRevisionUpdated events, one triggered by @@ -89,7 +96,7 @@ class ApiImportTest extends ApiUploadTestCase { // Expect only non-edit recent changes entry, but no edit count // or user talk. - $this->expectChangeTrackingUpdates( 0, 1, 0, 0 ); + $this->expectChangeTrackingUpdates( 0, 1, 0, 0, 1 ); // Expect search updates to be triggered $this->expectSearchUpdates( 1 ); diff --git a/tests/phpunit/includes/filerepo/file/LocalFileTest.php b/tests/phpunit/includes/filerepo/file/LocalFileTest.php index 5a9b0a9ae327..8bba9ec1dfd2 100644 --- a/tests/phpunit/includes/filerepo/file/LocalFileTest.php +++ b/tests/phpunit/includes/filerepo/file/LocalFileTest.php @@ -896,8 +896,14 @@ class LocalFileTest extends MediaWikiIntegrationTestCase { * @covers \LocalFile */ public function testUpload_updatePropagation() { + // Clear some extension hook handlers that may interfere with mock object expectations. + $this->clearHooks( [ + 'PageSaveComplete', + 'RevisionRecordInserted', + ] ); + // Expect two non-edit recent changes entries but only one edit count. - $this->expectChangeTrackingUpdates( 0, 2, 1, 0 ); + $this->expectChangeTrackingUpdates( 0, 2, 1, 0, 1 ); // Expect only one search update, the re-upload doesn't change the page. $this->expectSearchUpdates( 1 ); diff --git a/tests/phpunit/includes/import/ImportableOldRevisionImporterTest.php b/tests/phpunit/includes/import/ImportableOldRevisionImporterTest.php index f974eec26f7e..ed064e19ed99 100644 --- a/tests/phpunit/includes/import/ImportableOldRevisionImporterTest.php +++ b/tests/phpunit/includes/import/ImportableOldRevisionImporterTest.php @@ -190,7 +190,7 @@ class ImportableOldRevisionImporterTest extends MediaWikiIntegrationTestCase { public function testUpdatePropagation( PageIdentity $title ) { $revision = $this->getWikiRevision( Title::castFromPageIdentity( $title ) ); - $this->expectChangeTrackingUpdates( 0, 0, 0, 0 ); + $this->expectChangeTrackingUpdates( 0, 0, 0, 0, 1 ); $this->expectSearchUpdates( 1 ); $this->expectLocalizationUpdate( $title->getNamespace() === NS_MEDIAWIKI ? 1 : 0 ); diff --git a/tests/phpunit/includes/page/MovePageTest.php b/tests/phpunit/includes/page/MovePageTest.php index be3043578b90..07aaf63a09f9 100644 --- a/tests/phpunit/includes/page/MovePageTest.php +++ b/tests/phpunit/includes/page/MovePageTest.php @@ -741,6 +741,14 @@ class MovePageTest extends MediaWikiIntegrationTestCase { * @dataProvider provideUpdatePropagation */ public function testUpdatePropagation( $old, $new, ?Content $content = null ) { + // Clear some extension hook handlers that may interfere with mock object expectations. + $this->clearHooks( [ + 'RevisionRecordInserted', + 'PageSaveComplete', + 'PageMoveComplete', + 'LinksUpdateComplete', + ] ); + $old = Title::newFromText( $old ); $new = Title::newFromText( $new ); @@ -754,7 +762,7 @@ class MovePageTest extends MediaWikiIntegrationTestCase { // Should be counted as user contributions (T163966) // Should generate an RC entry for the move log, but not for // the dummy revision or redirect page. - $this->expectChangeTrackingUpdates( 0, 1, 1, 0 ); + $this->expectChangeTrackingUpdates( 0, 1, 1, 0, 1 ); // The moved page and the redirect should both get re-indexed. $this->expectSearchUpdates( 2 ); diff --git a/tests/phpunit/includes/page/UndeletePageTest.php b/tests/phpunit/includes/page/UndeletePageTest.php index a33201382924..7a471a748db5 100644 --- a/tests/phpunit/includes/page/UndeletePageTest.php +++ b/tests/phpunit/includes/page/UndeletePageTest.php @@ -76,6 +76,9 @@ class UndeletePageTest extends MediaWikiIntegrationTestCase { $this->fail( $updater->getStatus()->getWikiText() ); } + // Run jobs that were enqueued by page creation now, since they might expect the page to exist. + $this->runJobs( [ 'minJobs' => 0 ] ); + $this->pages[] = [ 'page' => $page, 'revId' => $revisionRecord->getId() ]; $this->deletePage( $page, '', $performer ); } @@ -223,6 +226,12 @@ class UndeletePageTest extends MediaWikiIntegrationTestCase { * @covers \MediaWiki\Page\UndeletePage::undeleteUnsafe */ public function testUpdatePropagation( ProperPageIdentity $page, ?Content $content = null ) { + // Clear some extension hook handlers that may interfere with mock object expectations. + $this->clearHooks( [ + 'LinksUpdateComplete', + 'PageUndeleteComplete', + ] ); + $content ??= new WikitextContent( 'hi' ); $this->setupPage( $page->getDBkey(), $page->getNamespace(), $content ); $this->runJobs(); @@ -231,7 +240,7 @@ class UndeletePageTest extends MediaWikiIntegrationTestCase { // Should generate an RC entry for undeletion, // but not a regular page edit. - $this->expectChangeTrackingUpdates( 0, 1, 0, 0 ); + $this->expectChangeTrackingUpdates( 0, 1, 0, 0, 0 ); $this->expectSearchUpdates( 1 ); $this->expectLocalizationUpdate( $page->getNamespace() === NS_MEDIAWIKI ? 1 : 0 ); diff --git a/tests/phpunit/includes/page/WikiPageDbTest.php b/tests/phpunit/includes/page/WikiPageDbTest.php index 3efff6e309d0..d4e30e48fac2 100644 --- a/tests/phpunit/includes/page/WikiPageDbTest.php +++ b/tests/phpunit/includes/page/WikiPageDbTest.php @@ -1777,6 +1777,13 @@ more stuff PageIdentity $title, ?Content $content = null ) { + // Clear some extension hook handlers that may interfere with mock object expectations. + $this->clearHooks( [ + 'PageSaveComplete', + 'RevisionRecordInserted', + 'ArticleProtectComplete', + ] ); + $content ??= new TextContent( 'Lorem Ipsum' ); $page = $this->createPage( $title, $content ); @@ -1784,7 +1791,7 @@ more stuff $this->runJobs(); // Expect only non-edit recent changes entry - $this->expectChangeTrackingUpdates( 0, 1, 0, 0 ); + $this->expectChangeTrackingUpdates( 0, 1, 0, 0, 0 ); // Expect no resource module purges on protection $this->expectResourceLoaderUpdates( 0 ); diff --git a/tests/phpunit/includes/recentchanges/ChangeTrackingUpdateSpyTrait.php b/tests/phpunit/includes/recentchanges/ChangeTrackingUpdateSpyTrait.php index d7df06882f7a..dd31a56f75d5 100644 --- a/tests/phpunit/includes/recentchanges/ChangeTrackingUpdateSpyTrait.php +++ b/tests/phpunit/includes/recentchanges/ChangeTrackingUpdateSpyTrait.php @@ -2,6 +2,8 @@ namespace MediaWiki\Tests\recentchanges; +use MediaWiki\JobQueue\JobQueueGroup; +use MediaWiki\MainConfigNames; use MediaWiki\RecentChanges\RecentChange; use MediaWiki\User\TalkPageNotificationManager; use MediaWiki\User\UserEditTracker; @@ -25,8 +27,13 @@ trait ChangeTrackingUpdateSpyTrait { int $rcEdit, int $rcOther, int $userEditCount, - int $talkPageNotifications + int $talkPageNotifications, + int $categoryMembershipChangeJobs ) { + // Force enable RC entry creation for category changes + // to verify CategoryMembershipChangeJobs get enqueued irrespective of local configuration. + $this->overrideConfigValue( MainConfigNames::RCWatchCategoryMembership, true ); + // Hack for spying on RC insertions $rcEditStatus = $this->createMock( StatusValue::class ); $rcEditStatus->expects( $this->exactly( $rcEdit ) ) @@ -75,6 +82,25 @@ trait ChangeTrackingUpdateSpyTrait { ->method( 'removeUserHasNewMessages' ); $this->setService( 'TalkPageNotificationManager', $talkPageNotificationManager ); + + $categoryMembershipChangeJobStatus = $this->createMock( StatusValue::class ); + $categoryMembershipChangeJobStatus->expects( $this->exactly( $categoryMembershipChangeJobs ) ) + ->method( 'setOK' ); + + $jobQueueGroup = $this->createMock( JobQueueGroup::class ); + $jobQueueGroup->method( $this->logicalOr( 'push', 'lazyPush' ) ) + ->willReturnCallback( + static function ( $specs ) use ( $categoryMembershipChangeJobStatus ): void { + $specs = is_array( $specs ) ? $specs : [ $specs ]; + foreach ( $specs as $spec ) { + if ( $spec->getType() === 'categoryMembershipChange' ) { + $categoryMembershipChangeJobStatus->setOK( true ); + } + } + } + ); + + $this->setService( 'JobQueueGroup', $jobQueueGroup ); } } diff --git a/tests/phpunit/includes/specials/SpecialContributionsTest.php b/tests/phpunit/includes/specials/SpecialContributionsTest.php index c5f95739d3ac..67681a2d1dc6 100644 --- a/tests/phpunit/includes/specials/SpecialContributionsTest.php +++ b/tests/phpunit/includes/specials/SpecialContributionsTest.php @@ -106,6 +106,48 @@ class SpecialContributionsTest extends SpecialPageTestBase { $this->assertStringContainsString( 'mw-pager-body', $html ); } + /** + * @dataProvider executeForUserWithWhitespacesDataProvider + */ + public function testExecuteForUserWithWhitespaces( + string $expected, + string $target + ): void { + [ $html ] = $this->executeSpecialPage( $target ); + + // Assert that the Javascript code in the page contains the quoted + // username with the whitespaces removed. + $this->assertStringContainsString( + sprintf( '"%s"', $expected ), + $html + ); + } + + public function executeForUserWithWhitespacesDataProvider(): array { + return [ + 'With an empty target' => [ + 'expected' => '', + 'target' => '' + ], + 'With a target with no whitespaces' => [ + 'expected' => 'TopUser', + 'target' => 'TopUser' + ], + 'With a target with leading whitespaces' => [ + 'expected' => 'TopUser', + 'target' => ' TopUser' + ], + 'With a target with trailing whitespaces' => [ + 'expected' => 'TopUser', + 'target' => 'TopUser ' + ], + 'With a target with whitespaces at both sides' => [ + 'expected' => 'TopUser', + 'target' => ' TopUser ' + ], + ]; + } + public function testExecuteEmptyTarget() { [ $html ] = $this->executeSpecialPage(); // This 'topOnly' filter should always be added to Special:Contributions diff --git a/tests/phpunit/integration/includes/page/DeletePageTest.php b/tests/phpunit/integration/includes/page/DeletePageTest.php index 78e35dcaead9..c6f7f46ec397 100644 --- a/tests/phpunit/integration/includes/page/DeletePageTest.php +++ b/tests/phpunit/integration/includes/page/DeletePageTest.php @@ -478,6 +478,11 @@ class DeletePageTest extends MediaWikiIntegrationTestCase { * @covers \MediaWiki\Page\UndeletePage::undeleteUnsafe */ public function testUpdatePropagation( $name, ?Content $content = null ) { + // Clear some extension hook handlers that may interfere with mock object expectations. + $this->clearHooks( [ + 'PageDeleteComplete', + ] ); + $content ??= new WikitextContent( self::PAGE_TEXT ); $deleterUser = static::getTestSysop()->getUser(); $deleter = new UltimateAuthority( $deleterUser ); @@ -489,7 +494,8 @@ class DeletePageTest extends MediaWikiIntegrationTestCase { // but not a regular page edit. $this->expectChangeTrackingUpdates( 0, 1, 0, - $page->getNamespace() === NS_USER_TALK ? -1 : 0 + $page->getNamespace() === NS_USER_TALK ? -1 : 0, + 0 ); // TODO: Assert that the search index is updated after deletion. diff --git a/tests/phpunit/integration/includes/page/RollbackPageTest.php b/tests/phpunit/integration/includes/page/RollbackPageTest.php index 7ef37b73a795..973febea3eb3 100644 --- a/tests/phpunit/integration/includes/page/RollbackPageTest.php +++ b/tests/phpunit/integration/includes/page/RollbackPageTest.php @@ -588,6 +588,12 @@ class RollbackPageTest extends MediaWikiIntegrationTestCase { ?Content $content1 = null, ?Content $content2 = null ) { + // Clear some extension hook handlers that may interfere with mock object expectations. + $this->clearHooks( [ + 'PageSaveComplete', + 'RevisionRecordInserted', + ] ); + $page = $this->getServiceContainer()->getWikiPageFactory() ->newFromTitle( Title::newFromText( __METHOD__ ) ); @@ -602,7 +608,8 @@ class RollbackPageTest extends MediaWikiIntegrationTestCase { // Should generate an RC entry for rollback $this->expectChangeTrackingUpdates( 1, 0, 1, - $page->getNamespace() === NS_USER_TALK ? 1 : 0 + $page->getNamespace() === NS_USER_TALK ? 1 : 0, + 1 ); $this->expectSearchUpdates( 1 ); @@ -617,5 +624,7 @@ class RollbackPageTest extends MediaWikiIntegrationTestCase { ->newRollbackPage( $page, $admin, $user1 ) ->rollbackIfAllowed(); $this->assertStatusGood( $rollbackResult ); + + $this->runDeferredUpdates(); } } diff --git a/tests/phpunit/unit/includes/installer/Task/SqliteCreateDatabaseTaskTest.php b/tests/phpunit/unit/includes/installer/Task/SqliteCreateDatabaseTaskTest.php index f21544904400..fea3fec31c1b 100644 --- a/tests/phpunit/unit/includes/installer/Task/SqliteCreateDatabaseTaskTest.php +++ b/tests/phpunit/unit/includes/installer/Task/SqliteCreateDatabaseTaskTest.php @@ -29,7 +29,7 @@ class SqliteCreateDatabaseTaskTest extends MediaWikiUnitTestCase { $dir = sys_get_temp_dir() . '/' . uniqid( 'MediaWikiTest' ); $status = $task->createDataDir( $dir ); $this->assertStatusGood( $status ); - $this->assertSame( "Require all denied\n", file_get_contents( "$dir/.htaccess" ) ); + $this->assertSame( "Require all denied\nSatisfy All\n", file_get_contents( "$dir/.htaccess" ) ); unlink( "$dir/.htaccess" ); rmdir( $dir ); } diff --git a/tests/phpunit/unit/includes/libs/Stats/UnitTestingHelperTest.php b/tests/phpunit/unit/includes/libs/Stats/UnitTestingHelperTest.php index 38c5a71ffb9d..ff704b3fd2ca 100644 --- a/tests/phpunit/unit/includes/libs/Stats/UnitTestingHelperTest.php +++ b/tests/phpunit/unit/includes/libs/Stats/UnitTestingHelperTest.php @@ -95,11 +95,9 @@ class UnitTestingHelperTest extends TestCase { public function testMin() { $actual = $this->statsHelper->min( 'test{a="a"}' ); - // phpcs:ignore MediaWiki.PHPUnit.AssertEquals.Int - $this->assertEquals( 1, $actual ); + $this->assertSame( 1.0, $actual ); $actual = $this->statsHelperWithComponent->min( 'test{a="a"}' ); - //phpcs:ignore MediaWiki.PHPUnit.AssertEquals.Int - $this->assertEquals( 1, $actual ); + $this->assertSame( 1.0, $actual ); } public function testNoFilter() { |