Whitelist existing violations, but enable the sniff to prevent
any new occurrences.
-->
- <exclude-pattern>*/includes/specials/SpecialMostinterwikis\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialAncientpages\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialBrokenRedirects\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialConfirmemail\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialDeadendpages\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialDeletedContributions\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialDoubleRedirects\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialEmailInvalidate\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialFewestrevisions\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialFileDuplicateSearch\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialLinkSearch\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialListDuplicatedFiles\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialListredirects\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialLonelypages\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialLongpages\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialMIMEsearch\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialMediaStatistics\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialMostcategories\.php</exclude-pattern>
<exclude-pattern>*/includes/specials/SpecialMostimages\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialMostlinked\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialMostlinkedcategories\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialMostlinkedtemplates\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialMostrevisions\.php</exclude-pattern>
<exclude-pattern>*/includes/specials/SpecialMovepage\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialNewimages\.php</exclude-pattern>
<exclude-pattern>*/includes/specials/SpecialRandompage\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialShortpages\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialUncategorizedcategories\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialUncategorizedimages\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialUncategorizedpages\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialUncategorizedtemplates\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialUnusedcategories\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialUnusedimages\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialUnusedtemplates\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialUnwatchedpages\.php</exclude-pattern>
<exclude-pattern>*/includes/specials/SpecialUserrights\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialWantedcategories\.php</exclude-pattern>
<exclude-pattern>*/includes/specials/SpecialWantedfiles\.php</exclude-pattern>
<exclude-pattern>*/includes/specials/SpecialWantedpages\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialWantedtemplates\.php</exclude-pattern>
- <exclude-pattern>*/includes/specials/SpecialWithoutinterwiki\.php</exclude-pattern>
<exclude-pattern>*/maintenance/CodeCleanerGlobalsPass.inc</exclude-pattern>
<exclude-pattern>*/maintenance/archives/upgradeLogging\.php</exclude-pattern>
<exclude-pattern>*/maintenance/benchmarks/bench_HTTP_HTTPS\.php</exclude-pattern>
* $wgDBOracleDRCP - If you must use persistent connections, set DBO_PERSISTENT
in the 'flags' field for servers in $wgDBServers (or $wgLBFactoryConf).
* $wgMemCachedDebug - Set the cache "debug" field in $wgObjectCaches instead.
+* $wgActorTableSchemaMigrationStage has been removed. Extension code for
+ MediaWiki 1.31+ finding it unset should treat it as being SCHEMA_COMPAT_NEW.
=== New user-facing features in 1.34 ===
* Special:Mute has been added as a quick way for users to block unwanted emails
* (T222388) Special pages can now be specified as an ObjectFactory spec,
allowing the construction of special pages that require services to be
injected in their constructor.
+* (T222388) API modules can now be specified as an ObjectFactory spec,
+ allowing the construction of modules that require services to be injected
+ in their constructor.
=== External library changes in 1.34 ===
=== Action API changes in 1.34 ===
* The 'recenteditcount' response property from action=query list=allusers,
deprecated in 1.25, has been removed.
+* (T60993) action=query list=filearchive, list=alldeletedrevisions and
+ prop=deletedrevisions no longer require the 'deletedhistory' user right.
=== Action API internal changes in 1.34 ===
+* The exception thrown in ApiModuleManager::getModule has been changed
+ from an MWException to an UnexpectedValueException, thrown by ObjectFactory.
+ ApiModuleManager::getModule now also throws InvalidArgumentExceptions when
+ ObjectFactory is presented with an invalid spec or incorrectly constructed
+ objects.
* …
=== Languages updated in 1.34 ===
* CryptRand class
* CryptRand service
* Functions of the MWCryptRand class: singleton(), wasStrong() and generate().
+* Various Special Page PHP Classes were renamed (mostly casing changes):
+ * SpecialAncientpages => SpecialAncientPages
+ * SpecialConfirmemail => SpecialConfirmEmail
+ * SpecialDeadendpages => SpecialDeadendPages
+ * SpecialFewestrevisions => SpecialFewestRevisions
+ * SpecialListredirects => SpecialListRedirects
+ * SpecialLonelypages => SpecialLonelyPages
+ * SpecialLongpages => SpecialLongPages
+ * SpecialMIMEsearch => SpecialMIMESearch
+ * SpecialMostcategories => SpecialMostCategories
+ * SpecialMostinterwikis => SpecialMostInterwikis
+ * SpecialMostlinked => SpecialMostLinked
+ * SpecialMostlinkedcategories => SpecialMostLinkedCategories
+ * SpecialMostlinkedtemplates => SpecialMostLinkedTemplates
+ * SpecialMostrevisions => SpecialMostRevisions
+ * SpecialNewimages => SpecialNewFiles
+ * SpecialShortpages => SpecialShortPages
+ * SpecialUncategorizedcategories => SpecialUncategorizedCategories
+ * SpecialUncategorizedimages => SpecialUncategorizedImages
+ * SpecialUncategorizedpages => SpecialUncategorizedPages
+ * SpecialUncategorizedtemplates => SpecialUncategorizedTemplates
+ * SpecialUnusedcategories => SpecialUnusedCategories
+ * SpecialUnusedimages => SpecialUnusedImages
+ * SpecialUnusedtemplates => SpecialUnusedTemplates
+ * SpecialUnwatchedpages => SpecialUnwatchedPages
+ * SpecialWantedcategories => SpecialWantedCategories
+ * SpecialWantedtemplates => SpecialWantedTemplates
+ * SpecialWithoutinterwiki => SpecialWithoutInterwiki
* Language::setCode, deprecated in 1.32, was removed. Use Language::factory to
create a new Language object with a different language code.
* MWNamespace::clearCaches() has been removed. So has the $rebuild parameter
should only be used to unblock a blocked user.
* Parameters for index.php from PATH_INFO, such as the title, are no longer
written to $_GET.
-* …
+* The selectFields() methods on classes LocalFile, ArchivedFile, OldLocalFile,
+ DatabaseBlock, and RecentChange, deprecated in 1.31, have been removed. Use
+ the corresponding getQueryInfo() methods instead.
+* The following methods on Revision, deprecated since 1.31, have been removed.
+ Use RevisionStore::getQueryInfo() or RevisionStore::getArchiveQueryInfo()
+ instead.
+ * Revision::userJoinCond()
+ * Revision::pageJoinCond()
+ * Revision::selectFields()
+ * Revision::selectArchiveFields()
+ * Revision::selectTextFields()
+ * Revision::selectPageFields()
+ * Revision::selectUserFields()
=== Deprecations in 1.34 ===
* The MWNamespace class is deprecated. Use NamespaceInfo.
* Specifying a SpecialPage object for the list of special pages (either through
the SpecialPage_initList hook or by adding to $wgSpecialPages) is now
deprecated.
+* Use of ActorMigration with 'ar_user', 'img_user', 'oi_user', 'fa_user',
+ 'rc_user', 'log_user', and 'ipb_by' is deprecated. Queries should be adjusted
+ to use the corresponding actor fields directly. Note that use with
+ 'rev_user' is *not* deprecated at this time.
+* Specifying both the class and factory parameters for
+ ApiModuleManager::addModule is now deprecated. The ObjectFactory spec should
+ be used instead.
+* The UserIsHidden hook is deprecated. Use GetUserBlock instead, and add a
+ system block that hides the user.
+* The GetBlockedStatus hook is deprecated. Use GetUserBlock instead, to add or
+ remove a block.
+* $wgContentHandlerUseDB is deprecated and should always be true.
=== Other changes in 1.34 ===
* …
'AllMessagesTablePager' => __DIR__ . '/includes/specials/pagers/AllMessagesTablePager.php',
'AllTrans' => __DIR__ . '/maintenance/language/alltrans.php',
'AlphabeticPager' => __DIR__ . '/includes/pager/AlphabeticPager.php',
- 'AncientPagesPage' => __DIR__ . '/includes/specials/SpecialAncientpages.php',
'AnsiTermColorer' => __DIR__ . '/maintenance/term/MWTerm.php',
'ApiAMCreateAccount' => __DIR__ . '/includes/api/ApiAMCreateAccount.php',
'ApiAuthManagerHelper' => __DIR__ . '/includes/api/ApiAuthManagerHelper.php',
'BlockLogFormatter' => __DIR__ . '/includes/logging/BlockLogFormatter.php',
'BmpHandler' => __DIR__ . '/includes/media/BmpHandler.php',
'BotPassword' => __DIR__ . '/includes/user/BotPassword.php',
- 'BrokenRedirectsPage' => __DIR__ . '/includes/specials/SpecialBrokenRedirects.php',
'BufferingStatsdDataFactory' => __DIR__ . '/includes/libs/stats/BufferingStatsdDataFactory.php',
'CLIParser' => __DIR__ . '/maintenance/parse.php',
'CSSMin' => __DIR__ . '/includes/libs/CSSMin.php',
'ComposerJson' => __DIR__ . '/includes/libs/composer/ComposerJson.php',
'ComposerLock' => __DIR__ . '/includes/libs/composer/ComposerLock.php',
'ComposerPackageModifier' => __DIR__ . '/includes/composer/ComposerPackageModifier.php',
+ 'ComposerPhpunitXmlCoverageEdit' => __DIR__ . '/includes/composer/ComposerPhpunitXmlCoverageEdit.php',
'ComposerVendorHtaccessCreator' => __DIR__ . '/includes/composer/ComposerVendorHtaccessCreator.php',
'ComposerVersionNormalizer' => __DIR__ . '/includes/composer/ComposerVersionNormalizer.php',
'CompressOld' => __DIR__ . '/maintenance/storage/compressOld.php',
'DateFormats' => __DIR__ . '/maintenance/language/date-formats.php',
'DateFormatter' => __DIR__ . '/includes/parser/DateFormatter.php',
'DateFormatterFactory' => __DIR__ . '/includes/parser/DateFormatterFactory.php',
- 'DeadendPagesPage' => __DIR__ . '/includes/specials/SpecialDeadendpages.php',
'DeduplicateArchiveRevId' => __DIR__ . '/maintenance/deduplicateArchiveRevId.php',
'DeferrableCallback' => __DIR__ . '/includes/deferred/DeferrableCallback.php',
'DeferrableUpdate' => __DIR__ . '/includes/deferred/DeferrableUpdate.php',
'DeletePageJob' => __DIR__ . '/includes/jobqueue/jobs/DeletePageJob.php',
'DeleteSelfExternals' => __DIR__ . '/maintenance/deleteSelfExternals.php',
'DeletedContribsPager' => __DIR__ . '/includes/specials/pagers/DeletedContribsPager.php',
- 'DeletedContributionsPage' => __DIR__ . '/includes/specials/SpecialDeletedContributions.php',
'DependencyWrapper' => __DIR__ . '/includes/cache/dependency/DependencyWrapper.php',
'DeprecatedGlobal' => __DIR__ . '/includes/DeprecatedGlobal.php',
'DeprecatedInterfaceFinder' => __DIR__ . '/maintenance/findDeprecated.php',
'DjVuImage' => __DIR__ . '/includes/media/DjVuImage.php',
'DnsSrvDiscoverer' => __DIR__ . '/includes/libs/DnsSrvDiscoverer.php',
'DoubleRedirectJob' => __DIR__ . '/includes/jobqueue/jobs/DoubleRedirectJob.php',
- 'DoubleRedirectsPage' => __DIR__ . '/includes/specials/SpecialDoubleRedirects.php',
'DummyLinker' => __DIR__ . '/includes/DummyLinker.php',
'DummySearchIndexFieldDefinition' => __DIR__ . '/includes/search/DummySearchIndexFieldDefinition.php',
'DummyTermColorer' => __DIR__ . '/maintenance/term/MWTerm.php',
'EditPage' => __DIR__ . '/includes/EditPage.php',
'EditWatchlistCheckboxSeriesField' => __DIR__ . '/includes/specials/formfields/EditWatchlistCheckboxSeriesField.php',
'EditWatchlistNormalHTMLForm' => __DIR__ . '/includes/specials/forms/EditWatchlistNormalHTMLForm.php',
- 'EmailConfirmation' => __DIR__ . '/includes/specials/SpecialConfirmemail.php',
- 'EmailInvalidation' => __DIR__ . '/includes/specials/SpecialEmailInvalidate.php',
'EmailNotification' => __DIR__ . '/includes/mail/EmailNotification.php',
'EmaillingJob' => __DIR__ . '/includes/jobqueue/jobs/EmaillingJob.php',
'EmptyBagOStuff' => __DIR__ . '/includes/libs/objectcache/EmptyBagOStuff.php',
'FeedItem' => __DIR__ . '/includes/changes/FeedItem.php',
'FeedUtils' => __DIR__ . '/includes/FeedUtils.php',
'FetchText' => __DIR__ . '/maintenance/fetchText.php',
- 'FewestrevisionsPage' => __DIR__ . '/includes/specials/SpecialFewestrevisions.php',
'Field' => __DIR__ . '/includes/libs/rdbms/field/Field.php',
'File' => __DIR__ . '/includes/filerepo/file/File.php',
'FileAwareNodeVisitor' => __DIR__ . '/maintenance/findDeprecated.php',
'FileContentsHasher' => __DIR__ . '/includes/utils/FileContentsHasher.php',
'FileDeleteForm' => __DIR__ . '/includes/FileDeleteForm.php',
'FileDependency' => __DIR__ . '/includes/cache/dependency/FileDependency.php',
- 'FileDuplicateSearchPage' => __DIR__ . '/includes/specials/SpecialFileDuplicateSearch.php',
'FileJournal' => __DIR__ . '/includes/libs/filebackend/filejournal/FileJournal.php',
'FileOp' => __DIR__ . '/includes/libs/filebackend/fileop/FileOp.php',
'FileOpBatch' => __DIR__ . '/includes/libs/filebackend/FileOpBatch.php',
'LinkCache' => __DIR__ . '/includes/cache/LinkCache.php',
'LinkFilter' => __DIR__ . '/includes/LinkFilter.php',
'LinkHolderArray' => __DIR__ . '/includes/parser/LinkHolderArray.php',
- 'LinkSearchPage' => __DIR__ . '/includes/specials/SpecialLinkSearch.php',
'Linker' => __DIR__ . '/includes/Linker.php',
'LinksDeletionUpdate' => __DIR__ . '/includes/deferred/LinksDeletionUpdate.php',
'LinksUpdate' => __DIR__ . '/includes/deferred/LinksUpdate.php',
- 'ListDuplicatedFilesPage' => __DIR__ . '/includes/specials/SpecialListDuplicatedFiles.php',
'ListToggle' => __DIR__ . '/includes/ListToggle.php',
'ListVariants' => __DIR__ . '/maintenance/language/listVariants.php',
- 'ListredirectsPage' => __DIR__ . '/includes/specials/SpecialListredirects.php',
'LoadBalancer' => __DIR__ . '/includes/libs/rdbms/loadbalancer/LoadBalancer.php',
'LoadBalancerSingle' => __DIR__ . '/includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php',
'LocalFile' => __DIR__ . '/includes/filerepo/file/LocalFile.php',
'LoggedUpdateMaintenance' => __DIR__ . '/maintenance/Maintenance.php',
'LoginHelper' => __DIR__ . '/includes/specials/helpers/LoginHelper.php',
'LoginSignupSpecialPage' => __DIR__ . '/includes/specialpage/LoginSignupSpecialPage.php',
- 'LonelyPagesPage' => __DIR__ . '/includes/specials/SpecialLonelypages.php',
- 'LongPagesPage' => __DIR__ . '/includes/specials/SpecialLongpages.php',
- 'MIMEsearchPage' => __DIR__ . '/includes/specials/SpecialMIMEsearch.php',
'MSCompoundFileReader' => __DIR__ . '/includes/libs/mime/MSCompoundFileReader.php',
'MWCallableUpdate' => __DIR__ . '/includes/deferred/MWCallableUpdate.php',
'MWCallbackStream' => __DIR__ . '/includes/http/MWCallbackStream.php',
'McrUndoAction' => __DIR__ . '/includes/actions/McrUndoAction.php',
'MediaHandler' => __DIR__ . '/includes/media/MediaHandler.php',
'MediaHandlerFactory' => __DIR__ . '/includes/media/MediaHandlerFactory.php',
- 'MediaStatisticsPage' => __DIR__ . '/includes/specials/SpecialMediaStatistics.php',
'MediaTransformError' => __DIR__ . '/includes/media/MediaTransformError.php',
'MediaTransformInvalidParametersException' => __DIR__ . '/includes/media/MediaTransformInvalidParametersException.php',
'MediaTransformOutput' => __DIR__ . '/includes/media/MediaTransformOutput.php',
'MigrateUserGroup' => __DIR__ . '/maintenance/migrateUserGroup.php',
'MimeAnalyzer' => __DIR__ . '/includes/libs/mime/MimeAnalyzer.php',
'MinifyScript' => __DIR__ . '/maintenance/minify.php',
- 'MostcategoriesPage' => __DIR__ . '/includes/specials/SpecialMostcategories.php',
'MostimagesPage' => __DIR__ . '/includes/specials/SpecialMostimages.php',
- 'MostinterwikisPage' => __DIR__ . '/includes/specials/SpecialMostinterwikis.php',
- 'MostlinkedCategoriesPage' => __DIR__ . '/includes/specials/SpecialMostlinkedcategories.php',
- 'MostlinkedPage' => __DIR__ . '/includes/specials/SpecialMostlinked.php',
- 'MostlinkedTemplatesPage' => __DIR__ . '/includes/specials/SpecialMostlinkedtemplates.php',
- 'MostrevisionsPage' => __DIR__ . '/includes/specials/SpecialMostrevisions.php',
'MoveBatch' => __DIR__ . '/maintenance/moveBatch.php',
'MoveFileOp' => __DIR__ . '/includes/libs/filebackend/fileop/MoveFileOp.php',
'MoveLogFormatter' => __DIR__ . '/includes/logging/MoveLogFormatter.php',
'SerializedValueContainer' => __DIR__ . '/includes/libs/objectcache/serialized/SerializedValueContainer.php',
'SevenZipStream' => __DIR__ . '/maintenance/includes/SevenZipStream.php',
'ShiConverter' => __DIR__ . '/languages/classes/LanguageShi.php',
- 'ShortPagesPage' => __DIR__ . '/includes/specials/SpecialShortpages.php',
'ShowJobs' => __DIR__ . '/maintenance/showJobs.php',
'ShowSiteStats' => __DIR__ . '/maintenance/showSiteStats.php',
'Site' => __DIR__ . '/includes/site/Site.php',
'SpecialAllMessages' => __DIR__ . '/includes/specials/SpecialAllMessages.php',
'SpecialAllMyUploads' => __DIR__ . '/includes/specials/redirects/SpecialAllMyUploads.php',
'SpecialAllPages' => __DIR__ . '/includes/specials/SpecialAllPages.php',
+ 'SpecialAncientPages' => __DIR__ . '/includes/specials/SpecialAncientPages.php',
'SpecialApiHelp' => __DIR__ . '/includes/specials/SpecialApiHelp.php',
'SpecialApiSandbox' => __DIR__ . '/includes/specials/SpecialApiSandbox.php',
'SpecialAutoblockList' => __DIR__ . '/includes/specials/SpecialAutoblockList.php',
'SpecialBlockList' => __DIR__ . '/includes/specials/SpecialBlockList.php',
'SpecialBookSources' => __DIR__ . '/includes/specials/SpecialBookSources.php',
'SpecialBotPasswords' => __DIR__ . '/includes/specials/SpecialBotPasswords.php',
+ 'SpecialBrokenRedirects' => __DIR__ . '/includes/specials/SpecialBrokenRedirects.php',
'SpecialCachedPage' => __DIR__ . '/includes/specials/SpecialCachedPage.php',
'SpecialCategories' => __DIR__ . '/includes/specials/SpecialCategories.php',
'SpecialChangeContentModel' => __DIR__ . '/includes/specials/SpecialChangeContentModel.php',
'SpecialChangeEmail' => __DIR__ . '/includes/specials/SpecialChangeEmail.php',
'SpecialChangePassword' => __DIR__ . '/includes/specials/SpecialChangePassword.php',
'SpecialComparePages' => __DIR__ . '/includes/specials/SpecialComparePages.php',
+ 'SpecialConfirmEmail' => __DIR__ . '/includes/specials/SpecialConfirmEmail.php',
'SpecialContributions' => __DIR__ . '/includes/specials/SpecialContributions.php',
'SpecialCreateAccount' => __DIR__ . '/includes/specials/SpecialCreateAccount.php',
+ 'SpecialDeadendPages' => __DIR__ . '/includes/specials/SpecialDeadendPages.php',
+ 'SpecialDeletedContributions' => __DIR__ . '/includes/specials/SpecialDeletedContributions.php',
'SpecialDiff' => __DIR__ . '/includes/specials/SpecialDiff.php',
+ 'SpecialDoubleRedirects' => __DIR__ . '/includes/specials/SpecialDoubleRedirects.php',
'SpecialEditTags' => __DIR__ . '/includes/specials/SpecialEditTags.php',
'SpecialEditWatchlist' => __DIR__ . '/includes/specials/SpecialEditWatchlist.php',
+ 'SpecialEmailInvalidate' => __DIR__ . '/includes/specials/SpecialEmailInvalidate.php',
'SpecialEmailUser' => __DIR__ . '/includes/specials/SpecialEmailUser.php',
'SpecialExpandTemplates' => __DIR__ . '/includes/specials/SpecialExpandTemplates.php',
'SpecialExport' => __DIR__ . '/includes/specials/SpecialExport.php',
+ 'SpecialFewestRevisions' => __DIR__ . '/includes/specials/SpecialFewestRevisions.php',
+ 'SpecialFileDuplicateSearch' => __DIR__ . '/includes/specials/SpecialFileDuplicateSearch.php',
'SpecialFilepath' => __DIR__ . '/includes/specials/SpecialFilepath.php',
'SpecialGoToInterwiki' => __DIR__ . '/includes/specials/SpecialGoToInterwiki.php',
'SpecialImport' => __DIR__ . '/includes/specials/SpecialImport.php',
'SpecialJavaScriptTest' => __DIR__ . '/includes/specials/SpecialJavaScriptTest.php',
'SpecialLinkAccounts' => __DIR__ . '/includes/specials/SpecialLinkAccounts.php',
+ 'SpecialLinkSearch' => __DIR__ . '/includes/specials/SpecialLinkSearch.php',
'SpecialListAdmins' => __DIR__ . '/includes/specials/redirects/SpecialListAdmins.php',
'SpecialListBots' => __DIR__ . '/includes/specials/redirects/SpecialListBots.php',
+ 'SpecialListDuplicatedFiles' => __DIR__ . '/includes/specials/SpecialListDuplicatedFiles.php',
'SpecialListFiles' => __DIR__ . '/includes/specials/SpecialListFiles.php',
'SpecialListGrants' => __DIR__ . '/includes/specials/SpecialListGrants.php',
'SpecialListGroupRights' => __DIR__ . '/includes/specials/SpecialListGroupRights.php',
+ 'SpecialListRedirects' => __DIR__ . '/includes/specials/SpecialListRedirects.php',
'SpecialListUsers' => __DIR__ . '/includes/specials/SpecialListUsers.php',
'SpecialLockdb' => __DIR__ . '/includes/specials/SpecialLockdb.php',
'SpecialLog' => __DIR__ . '/includes/specials/SpecialLog.php',
+ 'SpecialLonelyPages' => __DIR__ . '/includes/specials/SpecialLonelyPages.php',
+ 'SpecialLongPages' => __DIR__ . '/includes/specials/SpecialLongPages.php',
+ 'SpecialMIMESearch' => __DIR__ . '/includes/specials/SpecialMIMESearch.php',
+ 'SpecialMediaStatistics' => __DIR__ . '/includes/specials/SpecialMediaStatistics.php',
'SpecialMergeHistory' => __DIR__ . '/includes/specials/SpecialMergeHistory.php',
+ 'SpecialMostCategories' => __DIR__ . '/includes/specials/SpecialMostCategories.php',
+ 'SpecialMostInterwikis' => __DIR__ . '/includes/specials/SpecialMostInterwikis.php',
+ 'SpecialMostLinked' => __DIR__ . '/includes/specials/SpecialMostLinked.php',
+ 'SpecialMostLinkedCategories' => __DIR__ . '/includes/specials/SpecialMostLinkedCategories.php',
+ 'SpecialMostLinkedTemplates' => __DIR__ . '/includes/specials/SpecialMostLinkedTemplates.php',
+ 'SpecialMostRevisions' => __DIR__ . '/includes/specials/SpecialMostRevisions.php',
'SpecialMute' => __DIR__ . '/includes/specials/SpecialMute.php',
'SpecialMyLanguage' => __DIR__ . '/includes/specials/SpecialMyLanguage.php',
'SpecialMycontributions' => __DIR__ . '/includes/specials/redirects/SpecialMycontributions.php',
'SpecialMypage' => __DIR__ . '/includes/specials/redirects/SpecialMypage.php',
'SpecialMytalk' => __DIR__ . '/includes/specials/redirects/SpecialMytalk.php',
'SpecialMyuploads' => __DIR__ . '/includes/specials/redirects/SpecialMyuploads.php',
- 'SpecialNewFiles' => __DIR__ . '/includes/specials/SpecialNewimages.php',
+ 'SpecialNewFiles' => __DIR__ . '/includes/specials/SpecialNewFiles.php',
'SpecialNewSection' => __DIR__ . '/includes/specials/SpecialNewSection.php',
'SpecialNewpages' => __DIR__ . '/includes/specials/SpecialNewpages.php',
'SpecialPage' => __DIR__ . '/includes/specialpage/SpecialPage.php',
'SpecialRevisionDelete' => __DIR__ . '/includes/specials/SpecialRevisionDelete.php',
'SpecialRunJobs' => __DIR__ . '/includes/specials/SpecialRunJobs.php',
'SpecialSearch' => __DIR__ . '/includes/specials/SpecialSearch.php',
+ 'SpecialShortPages' => __DIR__ . '/includes/specials/SpecialShortPages.php',
'SpecialSpecialpages' => __DIR__ . '/includes/specials/SpecialSpecialpages.php',
'SpecialStatistics' => __DIR__ . '/includes/specials/SpecialStatistics.php',
'SpecialTags' => __DIR__ . '/includes/specials/SpecialTags.php',
'SpecialTrackingCategories' => __DIR__ . '/includes/specials/SpecialTrackingCategories.php',
'SpecialUnblock' => __DIR__ . '/includes/specials/SpecialUnblock.php',
+ 'SpecialUncategorizedCategories' => __DIR__ . '/includes/specials/SpecialUncategorizedCategories.php',
+ 'SpecialUncategorizedImages' => __DIR__ . '/includes/specials/SpecialUncategorizedImages.php',
+ 'SpecialUncategorizedPages' => __DIR__ . '/includes/specials/SpecialUncategorizedPages.php',
+ 'SpecialUncategorizedTemplates' => __DIR__ . '/includes/specials/SpecialUncategorizedTemplates.php',
'SpecialUndelete' => __DIR__ . '/includes/specials/SpecialUndelete.php',
'SpecialUnlinkAccounts' => __DIR__ . '/includes/specials/SpecialUnlinkAccounts.php',
'SpecialUnlockdb' => __DIR__ . '/includes/specials/SpecialUnlockdb.php',
+ 'SpecialUnusedCategories' => __DIR__ . '/includes/specials/SpecialUnusedCategories.php',
+ 'SpecialUnusedImages' => __DIR__ . '/includes/specials/SpecialUnusedImages.php',
+ 'SpecialUnusedTemplates' => __DIR__ . '/includes/specials/SpecialUnusedTemplates.php',
+ 'SpecialUnwatchedPages' => __DIR__ . '/includes/specials/SpecialUnwatchedPages.php',
'SpecialUpload' => __DIR__ . '/includes/specials/SpecialUpload.php',
'SpecialUploadStash' => __DIR__ . '/includes/specials/SpecialUploadStash.php',
'SpecialUploadStashTooLargeException' => __DIR__ . '/includes/specials/exception/SpecialUploadStashTooLargeException.php',
'SpecialUserLogin' => __DIR__ . '/includes/specials/SpecialUserLogin.php',
'SpecialUserLogout' => __DIR__ . '/includes/specials/SpecialUserLogout.php',
'SpecialVersion' => __DIR__ . '/includes/specials/SpecialVersion.php',
+ 'SpecialWantedCategories' => __DIR__ . '/includes/specials/SpecialWantedCategories.php',
+ 'SpecialWantedTemplates' => __DIR__ . '/includes/specials/SpecialWantedTemplates.php',
'SpecialWatchlist' => __DIR__ . '/includes/specials/SpecialWatchlist.php',
'SpecialWhatLinksHere' => __DIR__ . '/includes/specials/SpecialWhatLinksHere.php',
+ 'SpecialWithoutInterwiki' => __DIR__ . '/includes/specials/SpecialWithoutInterwiki.php',
'SqlBagOStuff' => __DIR__ . '/includes/objectcache/SqlBagOStuff.php',
'SqlSearchResult' => __DIR__ . '/includes/search/SqlSearchResult.php',
'SqlSearchResultSet' => __DIR__ . '/includes/search/SqlSearchResultSet.php',
'UDPTransport' => __DIR__ . '/includes/libs/UDPTransport.php',
'UIDGenerator' => __DIR__ . '/includes/utils/UIDGenerator.php',
'UcdXmlReader' => __DIR__ . '/maintenance/language/generateCollationData.php',
- 'UncategorizedCategoriesPage' => __DIR__ . '/includes/specials/SpecialUncategorizedcategories.php',
- 'UncategorizedImagesPage' => __DIR__ . '/includes/specials/SpecialUncategorizedimages.php',
- 'UncategorizedPagesPage' => __DIR__ . '/includes/specials/SpecialUncategorizedpages.php',
- 'UncategorizedTemplatesPage' => __DIR__ . '/includes/specials/SpecialUncategorizedtemplates.php',
'Undelete' => __DIR__ . '/maintenance/undelete.php',
'UnifiedDiffFormatter' => __DIR__ . '/includes/diff/UnifiedDiffFormatter.php',
'UnknownContent' => __DIR__ . '/includes/content/UnknownContent.php',
'UnprotectAction' => __DIR__ . '/includes/actions/UnprotectAction.php',
'UnregisteredLocalFile' => __DIR__ . '/includes/filerepo/file/UnregisteredLocalFile.php',
'UnsupportedSlotDiffRenderer' => __DIR__ . '/includes/diff/UnsupportedSlotDiffRenderer.php',
- 'UnusedCategoriesPage' => __DIR__ . '/includes/specials/SpecialUnusedcategories.php',
- 'UnusedimagesPage' => __DIR__ . '/includes/specials/SpecialUnusedimages.php',
- 'UnusedtemplatesPage' => __DIR__ . '/includes/specials/SpecialUnusedtemplates.php',
'UnwatchAction' => __DIR__ . '/includes/actions/UnwatchAction.php',
- 'UnwatchedpagesPage' => __DIR__ . '/includes/specials/SpecialUnwatchedpages.php',
'UpdateArticleCount' => __DIR__ . '/maintenance/updateArticleCount.php',
'UpdateCollation' => __DIR__ . '/maintenance/updateCollation.php',
'UpdateDoubleWidthSearch' => __DIR__ . '/maintenance/updateDoubleWidthSearch.php',
'WANCacheReapUpdate' => __DIR__ . '/includes/deferred/WANCacheReapUpdate.php',
'WANObjectCache' => __DIR__ . '/includes/libs/objectcache/wancache/WANObjectCache.php',
'WANObjectCacheReaper' => __DIR__ . '/includes/libs/objectcache/wancache/WANObjectCacheReaper.php',
- 'WantedCategoriesPage' => __DIR__ . '/includes/specials/SpecialWantedcategories.php',
'WantedFilesPage' => __DIR__ . '/includes/specials/SpecialWantedfiles.php',
'WantedPagesPage' => __DIR__ . '/includes/specials/SpecialWantedpages.php',
'WantedQueryPage' => __DIR__ . '/includes/specialpage/WantedQueryPage.php',
- 'WantedTemplatesPage' => __DIR__ . '/includes/specials/SpecialWantedtemplates.php',
'WatchAction' => __DIR__ . '/includes/actions/WatchAction.php',
'WatchedItem' => __DIR__ . '/includes/watcheditem/WatchedItem.php',
'WatchedItemQueryService' => __DIR__ . '/includes/watcheditem/WatchedItemQueryService.php',
'WikitextContentHandler' => __DIR__ . '/includes/content/WikitextContentHandler.php',
'WikitextLogFormatter' => __DIR__ . '/includes/logging/WikitextLogFormatter.php',
'WinCacheBagOStuff' => __DIR__ . '/includes/libs/objectcache/WinCacheBagOStuff.php',
- 'WithoutInterwikiPage' => __DIR__ . '/includes/specials/SpecialWithoutinterwiki.php',
'WordLevelDiff' => __DIR__ . '/includes/diff/WordLevelDiff.php',
'WrapOldPasswords' => __DIR__ . '/maintenance/wrapOldPasswords.php',
'XCFHandler' => __DIR__ . '/includes/media/XCFHandler.php',
"autoload": {
"psr-0": {
"ComposerHookHandler": "includes/composer",
- "ComposerVendorHtaccessCreator": "includes/composer"
+ "ComposerVendorHtaccessCreator": "includes/composer",
+ "ComposerPhpunitXmlCoverageEdit":"includes/composer"
}
},
"autoload-dev": {
"phpunit": "phpunit",
"phpunit:unit": "phpunit --colors=always --testsuite=core:unit,extensions:unit,skins:unit",
"phpunit:integration": "phpunit --colors=always --testsuite=core:integration,extensions:integration,skins:integration",
- "phpunit:coverage": "phpunit --testsuite=core:unit --exclude-group Dump,Broken"
+ "phpunit:coverage": "phpunit --testsuite=core:unit --exclude-group Dump,Broken",
+ "phpunit:coverage-edit": "ComposerPhpunitXmlCoverageEdit::onEvent"
},
"config": {
"optimize-autoloader": true,
$user: user to promote.
&$promote: groups that will be added.
-'GetBlockedStatus': after loading blocking status of an user from the database
+'GetBlockedStatus': DEPRECATED since 1.34 - use GetUserBlock instead. After
+loading blocking status of a user from the database
&$user: user (object) being checked
'GetCacheVaryCookies': Get cookies that should vary cache options.
false if a UserGetRights hook might remove the named right.
$right: The user right being checked
-'UserIsHidden': Check if the user's name should be hidden. See User::isHidden().
+'UserIsHidden': DEPRECATED since 1.34 - use GetUserBlock instead, and add a
+system block that hides the user. Check if the user's name should be hidden.
+See User::isHidden().
$user: User in question.
&$hidden: Set true if the user's name should be hidden.
* This class handles the logic for the actor table migration.
*
* This is not intended to be a long-term part of MediaWiki; it will be
- * deprecated and removed along with $wgActorTableSchemaMigrationStage.
+ * deprecated and removed once actor migration is complete.
*
* @since 1.31
+ * @since 1.34 Use with 'ar_user', 'img_user', 'oi_user', 'fa_user',
+ * 'rc_user', 'log_user', and 'ipb_by' is deprecated. Callers should
+ * reference the corresponding actor fields directly.
*/
class ActorMigration {
/**
* Constant for extensions to feature-test whether $wgActorTableSchemaMigrationStage
- * expects MIGRATION_* or SCHEMA_COMPAT_*
+ * (in MW <1.34) expects MIGRATION_* or SCHEMA_COMPAT_*
*/
const MIGRATION_STAGE_SCHEMA_COMPAT = 1;
*/
private static $formerTempTables = [];
+ /**
+ * Define fields that are deprecated for use with this class.
+ * @var (string|null)[] Keys are '$key', value is null for soft deprecation
+ * or a string naming the deprecated version for hard deprecation.
+ */
+ private static $deprecated = [
+ 'ar_user' => null, // 1.34
+ 'img_user' => null, // 1.34
+ 'oi_user' => null, // 1.34
+ 'fa_user' => null, // 1.34
+ 'rc_user' => null, // 1.34
+ 'log_user' => null, // 1.34
+ 'ipb_by' => null, // 1.34
+ ];
+
+ /**
+ * Define fields that are removed for use with this class.
+ * @var string[] Keys are '$key', value is the MediaWiki version in which
+ * use was removed.
+ */
+ private static $removed = [];
+
/**
* Define fields that use non-standard mapping
* @var array Keys are the user id column name, values are arrays with two
return MediaWikiServices::getInstance()->getActorMigration();
}
+ /**
+ * Issue deprecation warning/error as appropriate.
+ * @param string $key
+ */
+ private static function checkDeprecation( $key ) {
+ if ( isset( self::$removed[$key] ) ) {
+ throw new InvalidArgumentException(
+ "Use of " . static::class . " for '$key' was removed in MediaWiki " . self::$removed[$key]
+ );
+ }
+ if ( !empty( self::$deprecated[$key] ) ) {
+ wfDeprecated( static::class . " for '$key'", self::$deprecated[$key], false, 3 );
+ }
+ }
+
/**
* Return an SQL condition to test if a user field is anonymous
* @param string $field Field name or SQL fragment
* @phan-return array{tables:string[],fields:string[],joins:array}
*/
public function getJoin( $key ) {
+ self::checkDeprecation( $key );
+
if ( !isset( $this->joinCache[$key] ) ) {
$tables = [];
$fields = [];
* @return array to merge into `$values` to `IDatabase->update()` or `$a` to `IDatabase->insert()`
*/
public function getInsertValues( IDatabase $dbw, $key, UserIdentity $user ) {
+ self::checkDeprecation( $key );
+
if ( isset( self::$tempTables[$key] ) ) {
throw new InvalidArgumentException( "Must use getInsertValuesWithTempTable() for $key" );
}
* and extra fields needed for the temp table.
*/
public function getInsertValuesWithTempTable( IDatabase $dbw, $key, UserIdentity $user ) {
+ self::checkDeprecation( $key );
+
if ( isset( self::$formerTempTables[$key] ) ) {
wfDeprecated( __METHOD__ . " for $key", self::$formerTempTables[$key] );
} elseif ( !isset( self::$tempTables[$key] ) ) {
* All tables and joins are aliased, so `+` is safe to use.
*/
public function getWhere( IDatabase $db, $key, $users, $useId = true ) {
+ self::checkDeprecation( $key );
+
$tables = [];
$conds = [];
$joins = [];
'wllimit' => 250,
'useeditwarning' => 1,
'prefershttps' => 1,
+ 'requireemail' => 0,
];
/**
],
];
+/**
+ * Temporary feature flag that controls whether users will see a checkbox allowing them to
+ * require providing email during password resets.
+ *
+ * @deprecated This feature is under development, don't assume this flag's existence or function
+ * outside of MediaWiki.
+ */
+$wgAllowRequiringEmailForResets = false;
+
/** @} */ # end user accounts }
/************************************************************************//**
* handling is less robust and less flexible.
*
* @since 1.21
+ * @deprecated since 1.34, and should always be set true.
*/
$wgContentHandlerUseDB = true;
*/
$wgXmlDumpSchemaVersion = XML_DUMP_SCHEMA_VERSION_10;
-/**
- * Actor table schema migration stage.
- *
- * Use the SCHEMA_COMPAT_XXX flags. Supported values:
- * - SCHEMA_COMPAT_OLD
- * - SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
- * - SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW
- * - SCHEMA_COMPAT_NEW
- *
- * Note that reading the old and new schema at the same time is not supported
- * in 1.32, but was (with significant query performance issues) in 1.31.
- *
- * @since 1.31
- * @since 1.32 changed allowed flags
- * @var int An appropriate combination of SCHEMA_COMPAT_XXX flags.
- */
-$wgActorTableSchemaMigrationStage = SCHEMA_COMPAT_NEW;
-
/**
* Flag to enable Partial Blocks. This allows an admin to prevent a user from editing specific pages
* or namespaces.
$mp = new MovePage( $oldSubpage, $newSubpage );
$method = $checkPermissions ? 'moveIfAllowed' : 'move';
+ /** @var Status $status */
$status = $mp->$method( $user, $reason, $createRedirect, $changeTags );
if ( $status->isOK() ) {
$status->setResult( true, $newSubpage->getPrefixedText() );
Hooks::run( 'TitleMoveStarting', [ $this->oldTitle, $this->newTitle, $user ] );
- $pageid = $this->oldTitle->getArticleID( Title::GAID_FOR_UPDATE );
+ $pageid = $this->oldTitle->getArticleID( Title::READ_LATEST );
$protected = $this->oldTitle->isProtected();
// Do the actual move; if this fails, it will throw an MWException(!)
*
* @note This class uses setter methods instead of a constructor so that
* it can be compatible with PHP 4, PHP 5 and PHP 7 (without warnings).
- *
- * @class
*/
class PHPVersionCheck {
/* @var string The number of the MediaWiki version used. */
return $rec ? new Revision( $rec ) : null;
}
- /**
- * Return the value of a select() JOIN conds array for the user table.
- * This will get user table rows for logged-in users.
- * @since 1.19
- * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'user' ] ) instead.
- * @return array
- */
- public static function userJoinCond() {
- global $wgActorTableSchemaMigrationStage;
-
- wfDeprecated( __METHOD__, '1.31' );
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
- // If code is using this instead of self::getQueryInfo(), there's
- // no way the join it's trying to do can work once the old fields
- // aren't being used anymore.
- throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__
- . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
- );
- }
-
- return [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ];
- }
-
- /**
- * Return the value of a select() page conds array for the page table.
- * This will assure that the revision(s) are not orphaned from live pages.
- * @since 1.19
- * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'page' ] ) instead.
- * @return array
- */
- public static function pageJoinCond() {
- wfDeprecated( __METHOD__, '1.31' );
- return [ 'JOIN', [ 'page_id = rev_page' ] ];
- }
-
- /**
- * Return the list of revision fields that should be selected to create
- * a new revision.
- * @deprecated since 1.31, use RevisionStore::getQueryInfo() instead.
- * @return array
- */
- public static function selectFields() {
- global $wgContentHandlerUseDB, $wgActorTableSchemaMigrationStage;
- global $wgMultiContentRevisionSchemaMigrationStage;
-
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
- // If code is using this instead of self::getQueryInfo(), there's a
- // decent chance it's going to try to directly access
- // $row->rev_user or $row->rev_user_text and we can't give it
- // useful values here once those aren't being used anymore.
- throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__
- . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
- );
- }
-
- if ( !( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ) {
- // If code is using this instead of self::getQueryInfo(), there's a
- // decent chance it's going to try to directly access
- // $row->rev_text_id or $row->rev_content_model and we can't give it
- // useful values here once those aren't being written anymore,
- // and may not exist at all.
- throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__ . ' when $wgMultiContentRevisionSchemaMigrationStage '
- . 'does not have SCHEMA_COMPAT_WRITE_OLD set.'
- );
- }
-
- wfDeprecated( __METHOD__, '1.31' );
-
- $fields = [
- 'rev_id',
- 'rev_page',
- 'rev_text_id',
- 'rev_timestamp',
- 'rev_user_text',
- 'rev_user',
- 'rev_actor' => 'NULL',
- 'rev_minor_edit',
- 'rev_deleted',
- 'rev_len',
- 'rev_parent_id',
- 'rev_sha1',
- ];
-
- $fields += CommentStore::getStore()->getFields( 'rev_comment' );
-
- if ( $wgContentHandlerUseDB ) {
- $fields[] = 'rev_content_format';
- $fields[] = 'rev_content_model';
- }
-
- return $fields;
- }
-
- /**
- * Return the list of revision fields that should be selected to create
- * a new revision from an archive row.
- * @deprecated since 1.31, use RevisionStore::getArchiveQueryInfo() instead.
- * @return array
- */
- public static function selectArchiveFields() {
- global $wgContentHandlerUseDB, $wgActorTableSchemaMigrationStage;
- global $wgMultiContentRevisionSchemaMigrationStage;
-
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
- // If code is using this instead of self::getQueryInfo(), there's a
- // decent chance it's going to try to directly access
- // $row->ar_user or $row->ar_user_text and we can't give it
- // useful values here once those aren't being used anymore.
- throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__
- . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
- );
- }
-
- if ( !( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ) {
- // If code is using this instead of self::getQueryInfo(), there's a
- // decent chance it's going to try to directly access
- // $row->ar_text_id or $row->ar_content_model and we can't give it
- // useful values here once those aren't being written anymore,
- // and may not exist at all.
- throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__ . ' when $wgMultiContentRevisionSchemaMigrationStage '
- . 'does not have SCHEMA_COMPAT_WRITE_OLD set.'
- );
- }
-
- wfDeprecated( __METHOD__, '1.31' );
-
- $fields = [
- 'ar_id',
- 'ar_page_id',
- 'ar_rev_id',
- 'ar_text_id',
- 'ar_timestamp',
- 'ar_user_text',
- 'ar_user',
- 'ar_actor' => 'NULL',
- 'ar_minor_edit',
- 'ar_deleted',
- 'ar_len',
- 'ar_parent_id',
- 'ar_sha1',
- ];
-
- $fields += CommentStore::getStore()->getFields( 'ar_comment' );
-
- if ( $wgContentHandlerUseDB ) {
- $fields[] = 'ar_content_format';
- $fields[] = 'ar_content_model';
- }
- return $fields;
- }
-
- /**
- * Return the list of text fields that should be selected to read the
- * revision text
- * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'text' ] ) instead.
- * @return array
- */
- public static function selectTextFields() {
- wfDeprecated( __METHOD__, '1.31' );
- return [
- 'old_text',
- 'old_flags'
- ];
- }
-
- /**
- * Return the list of page fields that should be selected from page table
- * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'page' ] ) instead.
- * @return array
- */
- public static function selectPageFields() {
- wfDeprecated( __METHOD__, '1.31' );
- return [
- 'page_namespace',
- 'page_title',
- 'page_id',
- 'page_latest',
- 'page_is_redirect',
- 'page_len',
- ];
- }
-
- /**
- * Return the list of user fields that should be selected from user table
- * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'user' ] ) instead.
- * @return array
- */
- public static function selectUserFields() {
- wfDeprecated( __METHOD__, '1.31' );
- return [ 'user_name' ];
- }
-
/**
* Return the tables, fields, and join conditions to be selected to create
* a new revision object.
$comment = CommentStoreComment::newUnsavedComment( $summary, null );
- $title = Title::newFromID( $pageId, Title::GAID_FOR_UPDATE );
+ $title = Title::newFromID( $pageId, Title::READ_LATEST );
if ( $title === null ) {
return null;
}
$canUseTitleNewFromId = ( $pageId !== null && $pageId > 0 && $this->dbDomain === false );
list( $dbMode, $dbOptions ) = DBAccessObjectUtils::getDBOptions( $queryFlags );
- $titleFlags = ( $dbMode == DB_MASTER ? Title::GAID_FOR_UPDATE : 0 );
// Loading by ID is best, but Title::newFromID does not support that for foreign IDs.
if ( $canUseTitleNewFromId ) {
+ $titleFlags = ( $dbMode == DB_MASTER ? Title::READ_LATEST : 0 );
// TODO: better foreign title handling (introduce TitleFactory)
$title = Title::newFromID( $pageId, $titleFlags );
if ( $title ) {
return [
'ActorMigration' => function ( MediaWikiServices $services ) : ActorMigration {
- return new ActorMigration(
- $services->getMainConfig()->get( 'ActorTableSchemaMigrationStage' )
- );
+ return new ActorMigration( SCHEMA_COMPAT_NEW );
},
'BadFileLookup' => function ( MediaWikiServices $services ) : BadFileLookup {
// Initialize the session
try {
$session = MediaWiki\Session\SessionManager::getGlobalSession();
- } catch ( OverflowException $ex ) {
- if ( isset( $ex->sessionInfos ) && count( $ex->sessionInfos ) >= 2 ) {
- // The exception is because the request had multiple possible
- // sessions tied for top priority. Report this to the user.
- $list = [];
- foreach ( $ex->sessionInfos as $info ) {
- $list[] = $info->getProvider()->describe( $wgContLang );
- }
- $list = $wgContLang->listToText( $list );
- throw new HttpError( 400,
- Message::newFromKey( 'sessionmanager-tie', $list )->inLanguage( $wgContLang )->plain()
- );
+ } catch ( MediaWiki\Session\SessionOverflowException $ex ) {
+ // The exception is because the request had multiple possible
+ // sessions tied for top priority. Report this to the user.
+ $list = [];
+ foreach ( $ex->getSessionInfos() as $info ) {
+ $list[] = $info->getProvider()->describe( $wgContLang );
}
-
- // Not the one we want, rethrow
- throw $ex;
+ $list = $wgContLang->listToText( $list );
+ throw new HttpError( 400,
+ Message::newFromKey( 'sessionmanager-tie', $list )->inLanguage( $wgContLang )->plain()
+ );
}
if ( $session->isPersistent() ) {
self::getCacheKey( $cache, $page->getTitle(), $page->getLatest() ),
WANObjectCache::TTL_WEEK,
function ( $oldValue, &$ttl, &$setOpts ) use ( $page, $config, $fname, $services ) {
- global $wgActorTableSchemaMigrationStage;
-
$title = $page->getTitle();
$id = $title->getArticleID();
$dbrWatchlist = wfGetDB( DB_REPLICA, 'watchlist' );
$setOpts += Database::getCacheSetOptions( $dbr, $dbrWatchlist );
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
- $tables = [ 'revision_actor_temp' ];
- $field = 'revactor_actor';
- $pageField = 'revactor_page';
- $tsField = 'revactor_timestamp';
- $joins = [];
- } else {
- $tables = [ 'revision' ];
- $field = 'rev_user_text';
- $pageField = 'rev_page';
- $tsField = 'rev_timestamp';
- $joins = [];
- }
+ $tables = [ 'revision_actor_temp' ];
+ $field = 'revactor_actor';
+ $pageField = 'revactor_page';
+ $tsField = 'revactor_timestamp';
+ $joins = [];
$watchedItemStore = $services->getWatchedItemStore();
}
$this->mResult->setErrorFormatter( $this->getErrorFormatter() );
- $this->mModuleMgr = new ApiModuleManager( $this );
+ $this->mModuleMgr = new ApiModuleManager(
+ $this,
+ MediaWikiServices::getInstance()->getObjectFactory()
+ );
$this->mModuleMgr->addModules( self::$Modules, 'action' );
$this->mModuleMgr->addModules( $config->get( 'APIModules' ), 'action' );
$this->mModuleMgr->addModules( self::$Formats, 'format' );
* @since 1.21
*/
+use MediaWiki\MediaWikiServices;
+use Wikimedia\ObjectFactory;
+
/**
* This class holds a list of modules and handles instantiation
*
* @var array[]
*/
private $mModules = [];
+ /**
+ * @var ObjectFactory
+ */
+ private $objectFactory;
/**
* Construct new module manager
+ *
* @param ApiBase $parentModule Parent module instance will be used during instantiation
+ * @param ObjectFactory|null $objectFactory Object factory to use when instantiating modules
*/
- public function __construct( ApiBase $parentModule ) {
+ public function __construct( ApiBase $parentModule, ObjectFactory $objectFactory = null ) {
$this->mParent = $parentModule;
+ $this->objectFactory = $objectFactory ?? MediaWikiServices::getInstance()->getObjectFactory();
}
/**
* Add a list of modules to the manager. Each module is described
- * by a module spec.
- *
- * Each module spec is an associative array containing at least
- * the 'class' key for the module's class, and optionally a
- * 'factory' key for the factory function to use for the module.
+ * by an ObjectFactory spec.
*
- * That factory function will be called with two parameters,
- * the parent module (an instance of ApiBase, usually ApiMain)
- * and the name the module was registered under. The return
- * value must be an instance of the class given in the 'class'
- * field.
+ * This simply calls `addModule()` for each module in `$modules`.
*
- * For backward compatibility, the module spec may also be a
- * simple string containing the module's class name. In that
- * case, the class' constructor will be called with the parent
- * module and module name as parameters, as described above.
- *
- * Examples for defining module specs:
- *
- * @code
- * $modules['foo'] = 'ApiFoo';
- * $modules['bar'] = [
- * 'class' => ApiBar::class,
- * 'factory' => function( $main, $name ) { ... }
- * ];
- * $modules['xyzzy'] = [
- * 'class' => ApiXyzzy::class,
- * 'factory' => [ XyzzyFactory::class, 'newApiModule' ]
- * ];
- * @endcode
- *
- * @param array $modules A map of ModuleName => ModuleSpec; The ModuleSpec
- * is either a string containing the module's class name, or an associative
- * array (see above for details).
+ * @see ApiModuleManager::addModule()
+ * @param array $modules A map of ModuleName => ModuleSpec
* @param string $group Which group modules belong to (action,format,...)
*/
public function addModules( array $modules, $group ) {
foreach ( $modules as $name => $moduleSpec ) {
- if ( is_array( $moduleSpec ) ) {
- $class = $moduleSpec['class'];
- $factory = ( $moduleSpec['factory'] ?? null );
- } else {
- $class = $moduleSpec;
- $factory = null;
- }
-
- $this->addModule( $name, $group, $class, $factory );
+ $this->addModule( $name, $group, $moduleSpec );
}
}
* classes who wish to add their own modules to their lexicon or override the
* behavior of inherent ones.
*
+ * ObjectFactory is used to instantiate the module when needed. The parent module
+ * (`$parentModule` from `__construct()`) and the `$name` are passed as extraArgs.
+ *
+ * @since 1.34, accepts an ObjectFactory spec as the third parameter. The old calling convention,
+ * passing a class name as parameter #3 and an optional factory callable as parameter #4, is
+ * deprecated.
* @param string $name The identifier for this module.
* @param string $group Name of the module group
- * @param string $class The class where this module is implemented.
- * @param callable|null $factory Callback for instantiating the module.
+ * @param string|array $spec The ObjectFactory spec for instantiating the module,
+ * or a class name to instantiate.
+ * @param callable|null $factory Callback for instantiating the module (deprecated).
*
* @throws InvalidArgumentException
*/
- public function addModule( $name, $group, $class, $factory = null ) {
+ public function addModule( $name, $group, $spec, $factory = null ) {
if ( !is_string( $name ) ) {
throw new InvalidArgumentException( '$name must be a string' );
}
throw new InvalidArgumentException( '$group must be a string' );
}
- if ( !is_string( $class ) ) {
- throw new InvalidArgumentException( '$class must be a string' );
- }
+ if ( is_string( $spec ) ) {
+ $spec = [
+ 'class' => $spec
+ ];
- if ( $factory !== null && !is_callable( $factory ) ) {
- throw new InvalidArgumentException( '$factory must be a callable (or null)' );
+ if ( is_callable( $factory ) ) {
+ wfDeprecated( __METHOD__ . ' with $class and $factory', '1.34' );
+ $spec['factory'] = $factory;
+ }
+ } elseif ( !is_array( $spec ) ) {
+ throw new InvalidArgumentException( '$spec must be a string or an array' );
+ } elseif ( !isset( $spec['class'] ) ) {
+ throw new InvalidArgumentException( '$spec must define a class name' );
}
$this->mGroups[$group] = null;
- $this->mModules[$name] = [ $group, $class, $factory ];
+ $this->mModules[$name] = [ $group, $spec ];
}
/**
return null;
}
- list( $moduleGroup, $moduleClass, $moduleFactory ) = $this->mModules[$moduleName];
+ list( $moduleGroup, $spec ) = $this->mModules[$moduleName];
if ( $group !== null && $moduleGroup !== $group ) {
return null;
return $this->mInstances[$moduleName];
} else {
// new instance
- $instance = $this->instantiateModule( $moduleName, $moduleClass, $moduleFactory );
+ $instance = $this->instantiateModule( $moduleName, $spec );
if ( !$ignoreCache ) {
// cache this instance in case it is needed later
* Instantiate the module using the given class or factory function.
*
* @param string $name The identifier for this module.
- * @param string $class The class where this module is implemented.
- * @param callable|null $factory Callback for instantiating the module.
+ * @param array $spec The ObjectFactory spec for instantiating the module.
*
- * @throws MWException
+ * @throws UnexpectedValueException
* @return ApiBase
*/
- private function instantiateModule( $name, $class, $factory = null ) {
- if ( $factory !== null ) {
- // create instance from factory
- $instance = call_user_func( $factory, $this->mParent, $name );
-
- if ( !$instance instanceof $class ) {
- throw new MWException(
- "The factory function for module $name did not return an instance of $class!"
- );
- }
- } else {
- // create instance from class name
- $instance = new $class( $this->mParent, $name );
- }
-
- return $instance;
+ private function instantiateModule( $name, $spec ) {
+ return $this->objectFactory->createObject(
+ $spec,
+ [
+ 'extraArgs' => [
+ $this->mParent,
+ $name
+ ],
+ 'assertClass' => $spec['class']
+ ]
+ );
}
/**
return array_keys( $this->mModules );
}
$result = [];
- foreach ( $this->mModules as $name => $grpCls ) {
- if ( $grpCls[0] === $group ) {
+ foreach ( $this->mModules as $name => $groupAndSpec ) {
+ if ( $groupAndSpec[0] === $group ) {
$result[] = $name;
}
}
*/
public function getNamesWithClasses( $group = null ) {
$result = [];
- foreach ( $this->mModules as $name => $grpCls ) {
- if ( $group === null || $grpCls[0] === $group ) {
- $result[$name] = $grpCls[1];
+ foreach ( $this->mModules as $name => $groupAndSpec ) {
+ if ( $group === null || $groupAndSpec[0] === $group ) {
+ $result[$name] = $groupAndSpec[1]['class'];
}
}
*/
public function getClassName( $module ) {
if ( isset( $this->mModules[$module] ) ) {
- return $this->mModules[$module][1];
+ return $this->mModules[$module][1]['class'];
}
return false;
$services = MediaWikiServices::getInstance();
$contLang = $services->getContentLanguage();
- foreach ( $titles as $title ) {
+ $titleObjects = [];
+ foreach ( $titles as $index => $title ) {
if ( is_string( $title ) ) {
try {
$titleObj = Title::newFromTextThrow( $title, $this->mDefaultNamespace );
} else {
$titleObj = $title;
}
+
+ $titleObjects[$index] = $titleObj;
+ }
+
+ // Get gender information
+ $genderCache = $services->getGenderCache();
+ $genderCache->doTitlesArray( $titleObjects, __METHOD__ );
+
+ foreach ( $titleObjects as $index => $titleObj ) {
+ $title = is_string( $titles[$index] ) ? $titles[$index] : false;
$unconvertedTitle = $titleObj->getPrefixedText();
$titleWasConverted = false;
if ( $titleObj->isExternal() ) {
) {
// Language::findVariantLink will modify titleText and titleObj into
// the canonical variant if possible
- $titleText = is_string( $title ) ? $title : $titleObj->getPrefixedText();
+ $titleText = $title !== false ? $title : $titleObj->getPrefixedText();
$contLang->findVariantLink( $titleText, $titleObj );
$titleWasConverted = $unconvertedTitle !== $titleObj->getPrefixedText();
}
if ( $titleWasConverted ) {
$this->mConvertedTitles[$unconvertedTitle] = $titleObj->getPrefixedText();
// In this case the page can't be Special.
- if ( is_string( $title ) && $title !== $unconvertedTitle ) {
+ if ( $title !== false && $title !== $unconvertedTitle ) {
$this->mNormalizedTitles[$title] = $unconvertedTitle;
}
- } elseif ( is_string( $title ) && $title !== $titleObj->getPrefixedText() ) {
+ } elseif ( $title !== false && $title !== $titleObj->getPrefixedText() ) {
$this->mNormalizedTitles[$title] = $titleObj->getPrefixedText();
}
-
- // Need gender information
- if (
- $services->getNamespaceInfo()->
- hasGenderDistinction( $titleObj->getNamespace() )
- ) {
- $usernames[] = $titleObj->getText();
- }
}
- // Get gender information
- $genderCache = $services->getGenderCache();
- $genderCache->doQuery( $usernames, __METHOD__ );
return $linkBatch;
}
* @file
*/
+use MediaWiki\MediaWikiServices;
use Wikimedia\Rdbms\IDatabase;
/**
public function __construct( ApiMain $main, $action ) {
parent::__construct( $main, $action );
- $this->mModuleMgr = new ApiModuleManager( $this );
+ $this->mModuleMgr = new ApiModuleManager(
+ $this,
+ MediaWikiServices::getInstance()->getObjectFactory()
+ );
// Allow custom modules to be added in LocalSettings.php
$config = $this->getConfig();
* @return void
*/
protected function run( ApiPageSet $resultPageSet = null ) {
- // Before doing anything at all, let's check permissions
- $this->checkUserRightsAny( 'deletedhistory' );
-
$user = $this->getUser();
$db = $this->getDB();
$params = $this->extractRequestParams( false );
}
// This means stricter restrictions
- if ( $this->fetchContent ) {
- $this->checkUserRightsAny( [ 'deletedtext', 'undelete' ] );
+ if ( ( $this->fld_comment || $this->fld_parsedcomment ) &&
+ !$this->getPermissionManager()->userHasRight( $user, 'deletedhistory' )
+ ) {
+ $this->dieWithError( 'apierror-cantview-deleted-comment', 'permissiondenied' );
+ }
+ if ( $this->fetchContent &&
+ !$this->getPermissionManager()->userHasAnyRight( $user, 'deletedtext', 'undelete' )
+ ) {
+ $this->dieWithError( 'apierror-cantview-deleted-revision-content', 'permissiondenied' );
}
$miser_ns = null;
if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
// Paranoia: avoid brute force searches (T19342)
- // (shouldn't be able to get here without 'deletedhistory', but
- // check it again just in case)
if ( !$this->getPermissionManager()->userHasRight( $user, 'deletedhistory' ) ) {
$bitmask = RevisionRecord::DELETED_USER;
} elseif ( !$this->getPermissionManager()
* @return void
*/
protected function run( ApiPageSet $resultPageSet = null ) {
- global $wgActorTableSchemaMigrationStage;
-
$db = $this->getDB();
$params = $this->extractRequestParams( false );
$services = MediaWikiServices::getInstance();
$tsField = 'rev_timestamp';
$idField = 'rev_id';
$pageField = 'rev_page';
- if ( $params['user'] !== null &&
- ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW )
- ) {
+ if ( $params['user'] !== null ) {
// The query is probably best done using the actor_timestamp index on
// revision_actor_temp. Use the denormalized fields from that table.
$tsField = 'revactor_timestamp';
}
public function execute() {
- global $wgActorTableSchemaMigrationStage;
-
$params = $this->extractRequestParams();
$activeUserDays = $this->getConfig()->get( 'ActiveUserDays' );
] ] );
// Actually count the actions using a subquery (T66505 and T66507)
- $tables = [ 'recentchanges' ];
- $joins = [];
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_OLD ) {
- $userCond = 'rc_user_text = user_name';
- } else {
- $tables[] = 'actor';
- $joins['actor'] = [ 'JOIN', 'rc_actor = actor_id' ];
- $userCond = 'actor_user = user_id';
- }
+ $tables = [ 'recentchanges', 'actor' ];
+ $joins = [
+ 'actor' => [ 'JOIN', 'rc_actor = actor_id' ],
+ ];
$timestamp = $db->timestamp( wfTimestamp( TS_UNIX ) - $activeUserSeconds );
$this->addFields( [
'recentactions' => '(' . $db->selectSQLText(
$tables,
'COUNT(*)',
[
- $userCond,
+ 'actor_user = user_id',
'rc_type != ' . $db->addQuotes( RC_EXTERNAL ), // no wikidata
'rc_log_type IS NULL OR rc_log_type != ' . $db->addQuotes( 'newusers' ),
'rc_timestamp >= ' . $db->addQuotes( $timestamp ),
}
public function execute() {
- global $wgActorTableSchemaMigrationStage;
-
$db = $this->getDB();
$params = $this->extractRequestParams();
$this->requireMaxOneParameter( $params, 'group', 'excludegroup', 'rights', 'excluderights' );
$result = $this->getResult();
$revQuery = MediaWikiServices::getInstance()->getRevisionStore()->getQueryInfo();
- // For SCHEMA_COMPAT_READ_NEW, target indexes on the
- // revision_actor_temp table, otherwise on the revision table.
- $pageField = ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW )
- ? 'revactor_page' : 'rev_page';
- $idField = ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW )
- ? 'revactor_actor' : $revQuery['fields']['rev_user'];
- $countField = ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW )
- ? 'revactor_actor' : $revQuery['fields']['rev_user_text'];
+ // Target indexes on the revision_actor_temp table.
+ $pageField = 'revactor_page';
+ $idField = 'revactor_actor';
+ $countField = 'revactor_actor';
// First, count anons
$this->addTables( $revQuery['tables'] );
protected function run( ApiPageSet $resultPageSet = null ) {
$user = $this->getUser();
- // Before doing anything at all, let's check permissions
- $this->checkUserRightsAny( 'deletedhistory' );
$pageSet = $this->getPageSet();
$pageMap = $pageSet->getGoodAndMissingTitlesByNamespace();
}
// This means stricter restrictions
- if ( $this->fetchContent ) {
- $this->checkUserRightsAny( [ 'deletedtext', 'undelete' ] );
+ if ( ( $this->fld_comment || $this->fld_parsedcomment ) &&
+ !$this->getPermissionManager()->userHasRight( $user, 'deletedhistory' )
+ ) {
+ $this->dieWithError( 'apierror-cantview-deleted-comment', 'permissiondenied' );
+ }
+ if ( $this->fetchContent &&
+ !$this->getPermissionManager()->userHasAnyRight( $user, 'deletedtext', 'undelete' )
+ ) {
+ $this->dieWithError( 'apierror-cantview-deleted-revision-content', 'permissiondenied' );
}
$dir = $params['dir'];
if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
// Paranoia: avoid brute force searches (T19342)
- // (shouldn't be able to get here without 'deletedhistory', but
- // check it again just in case)
if ( !$this->getPermissionManager()->userHasRight( $user, 'deletedhistory' ) ) {
$bitmask = RevisionRecord::DELETED_USER;
} elseif ( !$this->getPermissionManager()
}
public function execute() {
- // Before doing anything at all, let's check permissions
- $this->checkUserRightsAny( 'deletedhistory' );
-
$user = $this->getUser();
$db = $this->getDB();
$commentStore = CommentStore::getStore();
$fld_bitdepth = isset( $prop['bitdepth'] );
$fld_archivename = isset( $prop['archivename'] );
+ if ( $fld_description &&
+ !$this->getPermissionManager()->userHasRight( $user, 'deletedhistory' )
+ ) {
+ $this->dieWithError( 'apierror-cantview-deleted-description', 'permissiondenied' );
+ }
+ if ( $fld_metadata &&
+ !$this->getPermissionManager()->userHasAnyRight( $user, 'deletedtext', 'undelete' )
+ ) {
+ $this->dieWithError( 'apierror-cantview-deleted-metadata', 'permissiondenied' );
+ }
+
$fileQuery = ArchivedFile::getQueryInfo();
$this->addTables( $fileQuery['tables'] );
$this->addFields( $fileQuery['fields'] );
}
if ( $sha1 ) {
$this->addWhereFld( 'fa_sha1', $sha1 );
+ // Paranoia: avoid brute force searches (T19342)
+ if ( !$this->getPermissionManager()->userHasRight( $user, 'deletedtext' ) ) {
+ $bitmask = File::DELETED_FILE;
+ } elseif ( !$this->getPermissionManager()
+ ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' )
+ ) {
+ $bitmask = File::DELETED_FILE | File::DELETED_RESTRICTED;
+ } else {
+ $bitmask = 0;
+ }
+ if ( $bitmask ) {
+ $this->addWhere( $this->getDB()->bitAnd( 'fa_deleted', $bitmask ) . " != $bitmask" );
+ }
}
}
- // Exclude files this user can't view.
- if ( !$this->getPermissionManager()->userHasRight( $user, 'deletedtext' ) ) {
- $bitmask = File::DELETED_FILE;
- } elseif ( !$this->getPermissionManager()
- ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' )
- ) {
- $bitmask = File::DELETED_FILE | File::DELETED_RESTRICTED;
- } else {
- $bitmask = 0;
- }
- if ( $bitmask ) {
- $this->addWhere( $this->getDB()->bitAnd( 'fa_deleted', $bitmask ) . " != $bitmask" );
- }
-
$limit = $params['limit'];
$this->addOption( 'LIMIT', $limit + 1 );
$sort = ( $params['dir'] == 'descending' ? ' DESC' : '' );
break;
}
+ $canViewFile = RevisionRecord::userCanBitfield( $row->fa_deleted, File::DELETED_FILE, $user );
+
$file = [];
$file['id'] = (int)$row->fa_id;
$file['name'] = $row->fa_name;
$file['userid'] = (int)$row->fa_user;
$file['user'] = $row->fa_user_text;
}
- if ( $fld_sha1 ) {
+ if ( $fld_sha1 && $canViewFile ) {
$file['sha1'] = Wikimedia\base_convert( $row->fa_sha1, 36, 16, 40 );
}
if ( $fld_timestamp ) {
$file['timestamp'] = wfTimestamp( TS_ISO_8601, $row->fa_timestamp );
}
- if ( $fld_size || $fld_dimensions ) {
+ if ( ( $fld_size || $fld_dimensions ) && $canViewFile ) {
$file['size'] = $row->fa_size;
$pageCount = ArchivedFile::newFromRow( $row )->pageCount();
$file['height'] = $row->fa_height;
$file['width'] = $row->fa_width;
}
- if ( $fld_mediatype ) {
+ if ( $fld_mediatype && $canViewFile ) {
$file['mediatype'] = $row->fa_media_type;
}
- if ( $fld_metadata ) {
+ if ( $fld_metadata && $canViewFile ) {
$file['metadata'] = $row->fa_metadata
? ApiQueryImageInfo::processMetaData( unserialize( $row->fa_metadata ), $result )
: null;
}
- if ( $fld_bitdepth ) {
+ if ( $fld_bitdepth && $canViewFile ) {
$file['bitdepth'] = $row->fa_bits;
}
- if ( $fld_mime ) {
+ if ( $fld_mime && $canViewFile ) {
$file['mime'] = "$row->fa_major_mime/$row->fa_minor_mime";
}
if ( $fld_archivename && !is_null( $row->fa_archive_name ) ) {
}
protected function run( ApiPageSet $resultPageSet = null ) {
- global $wgActorTableSchemaMigrationStage;
-
$params = $this->extractRequestParams( false );
$revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
$idField = 'rev_id';
$tsField = 'rev_timestamp';
$pageField = 'rev_page';
- if ( $params['user'] !== null &&
- ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW )
- ) {
+ if ( $params['user'] !== null ) {
// We're going to want to use the page_actor_timestamp index (on revision_actor_temp)
// so use that table's denormalized fields.
$idField = 'revactor_rev';
}
}
- private $propertyFilter = [
+ private static $propertyFilter = [
'user', 'userid', 'comment', 'parsedcomment',
'mediatype', 'archivename', 'uploadwarning',
];
+ /**
+ * Returns all possible parameters to siiprop
+ *
+ * @param array|null $filter List of properties to filter out
+ * @return array
+ */
+ public static function getPropertyNames( $filter = null ) {
+ if ( $filter === null ) {
+ $filter = self::$propertyFilter;
+ }
+ return parent::getPropertyNames( $filter );
+ }
+
+ /**
+ * Returns messages for all possible parameters to siiprop
+ *
+ * @param array|null $filter List of properties to filter out
+ * @return array
+ */
+ public static function getPropertyMessages( $filter = null ) {
+ if ( $filter === null ) {
+ $filter = self::$propertyFilter;
+ }
+ return parent::getPropertyMessages( $filter );
+ }
+
public function getAllowedParams() {
return [
'filekey' => [
'prop' => [
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_DFLT => 'timestamp|url',
- ApiBase::PARAM_TYPE => self::getPropertyNames( $this->propertyFilter ),
+ ApiBase::PARAM_TYPE => self::getPropertyNames(),
ApiBase::PARAM_HELP_MSG => 'apihelp-query+imageinfo-param-prop',
- ApiBase::PARAM_HELP_MSG_PER_VALUE => self::getPropertyMessages( $this->propertyFilter )
+ ApiBase::PARAM_HELP_MSG_PER_VALUE => self::getPropertyMessages()
],
'urlwidth' => [
ApiBase::PARAM_TYPE => 'integer',
$fld_patrolled = false, $fld_tags = false, $fld_size = false, $fld_sizediff = false;
public function execute() {
- global $wgActorTableSchemaMigrationStage;
-
// Parse some parameters
$this->params = $this->extractRequestParams();
// a wiki with users "Test00000001" to "Test99999999"), use a
// generator with batched lookup and continuation.
$userIter = call_user_func( function () use ( $dbSecondary, $sort, $op, $fname ) {
- global $wgActorTableSchemaMigrationStage;
-
$fromName = false;
if ( !is_null( $this->params['continue'] ) ) {
$continue = explode( '|', $this->params['continue'] );
do {
$from = $fromName ? "$op= " . $dbSecondary->addQuotes( $fromName ) : false;
-
- // For the new schema, pull from the actor table. For the
- // old, pull from rev_user.
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
- $res = $dbSecondary->select(
- 'actor',
- [ 'actor_id', 'user_id' => 'COALESCE(actor_user,0)', 'user_name' => 'actor_name' ],
- array_merge( [ "actor_name$like" ], $from ? [ "actor_name $from" ] : [] ),
- $fname,
- [ 'ORDER BY' => [ "user_name $sort" ], 'LIMIT' => $limit ]
- );
- } else {
- $res = $dbSecondary->select(
- 'revision',
- [ 'actor_id' => 'NULL', 'user_id' => 'rev_user', 'user_name' => 'rev_user_text' ],
- array_merge( [ "rev_user_text$like" ], $from ? [ "rev_user_text $from" ] : [] ),
- $fname,
- [ 'DISTINCT', 'ORDER BY' => [ "rev_user_text $sort" ], 'LIMIT' => $limit ]
- );
- }
+ $res = $dbSecondary->select(
+ 'actor',
+ [ 'actor_id', 'user_id' => 'COALESCE(actor_user,0)', 'user_name' => 'actor_name' ],
+ array_merge( [ "actor_name$like" ], $from ? [ "actor_name $from" ] : [] ),
+ $fname,
+ [ 'ORDER BY' => [ "user_name $sort" ], 'LIMIT' => $limit ]
+ );
$count = 0;
$fromName = false;
$from = "$op= $fromId";
}
- // For the new schema, just select from the actor table. For the
- // old, select from user.
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
- $res = $dbSecondary->select(
- 'actor',
- [ 'actor_id', 'user_id' => 'actor_user', 'user_name' => 'actor_name' ],
- array_merge( [ 'actor_user' => $ids ], $from ? [ "actor_id $from" ] : [] ),
- __METHOD__,
- [ 'ORDER BY' => "user_id $sort" ]
- );
- } else {
- $res = $dbSecondary->select(
- 'user',
- [ 'actor_id' => 'NULL', 'user_id' => 'user_id', 'user_name' => 'user_name' ],
- array_merge( [ 'user_id' => $ids ], $from ? [ "user_id $from" ] : [] ),
- __METHOD__,
- [ 'ORDER BY' => "user_id $sort" ]
- );
- }
+ $res = $dbSecondary->select(
+ 'actor',
+ [ 'actor_id', 'user_id' => 'actor_user', 'user_name' => 'actor_name' ],
+ array_merge( [ 'actor_user' => $ids ], $from ? [ "actor_id $from" ] : [] ),
+ __METHOD__,
+ [ 'ORDER BY' => "user_id $sort" ]
+ );
$userIter = UserArray::newFromResult( $res );
$batchSize = count( $ids );
} else {
$from = "$op= " . $dbSecondary->addQuotes( $fromName );
}
- // For the new schema, just select from the actor table. For the
- // old, select from user then merge in any unknown users (IPs and imports).
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
- $res = $dbSecondary->select(
- 'actor',
- [ 'actor_id', 'user_id' => 'actor_user', 'user_name' => 'actor_name' ],
- array_merge( [ 'actor_name' => array_keys( $names ) ], $from ? [ "actor_id $from" ] : [] ),
- __METHOD__,
- [ 'ORDER BY' => "actor_name $sort" ]
- );
- $userIter = UserArray::newFromResult( $res );
- } else {
- $res = $dbSecondary->select(
- 'user',
- [ 'actor_id' => 'NULL', 'user_id', 'user_name' ],
- array_merge( [ 'user_name' => array_keys( $names ) ], $from ? [ "user_name $from" ] : [] ),
- __METHOD__
- );
- foreach ( $res as $row ) {
- $names[$row->user_name] = $row;
- }
- if ( $this->params['dir'] == 'newer' ) {
- ksort( $names, SORT_STRING );
- } else {
- krsort( $names, SORT_STRING );
- }
- $neg = $op === '>' ? -1 : 1;
- $userIter = call_user_func( function () use ( $names, $fromName, $neg ) {
- foreach ( $names as $name => $row ) {
- if ( $fromName === false || $neg * strcmp( $name, $fromName ) <= 0 ) {
- $user = $row ? User::newFromRow( $row ) : User::newFromName( $name, false );
- yield $user;
- }
- }
- } );
- }
+ $res = $dbSecondary->select(
+ 'actor',
+ [ 'actor_id', 'user_id' => 'actor_user', 'user_name' => 'actor_name' ],
+ array_merge( [ 'actor_name' => array_keys( $names ) ], $from ? [ "actor_id $from" ] : [] ),
+ __METHOD__,
+ [ 'ORDER BY' => "actor_name $sort" ]
+ );
+ $userIter = UserArray::newFromResult( $res );
$batchSize = count( $names );
}
- // With the new schema, the DB query will order by actor so update $this->orderBy to match.
- if ( $batchSize > 1 && ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ) {
+ // The DB query will order by actor so update $this->orderBy to match.
+ if ( $batchSize > 1 ) {
$this->orderBy = 'actor';
}
- // Use the 'contributions' replica, but only if we're querying by user ID (T216656).
- if ( $this->orderBy === 'id' &&
- !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW )
- ) {
- $this->selectNamedDB( 'contributions', DB_REPLICA, 'contributions' );
- }
-
$count = 0;
$limit = $this->params['limit'];
$userIter->rewind();
* @param int $limit
*/
private function prepareQuery( array $users, $limit ) {
- global $wgActorTableSchemaMigrationStage;
-
$this->resetQueryParams();
$db = $this->getDB();
$revQuery = MediaWikiServices::getInstance()->getRevisionStore()->getQueryInfo( [ 'page' ] );
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
- $revWhere = ActorMigration::newMigration()->getWhere( $db, 'rev_user', $users );
- $orderUserField = 'rev_actor';
- $userField = $this->orderBy === 'actor' ? 'revactor_actor' : 'actor_name';
- $tsField = 'revactor_timestamp';
- $idField = 'revactor_rev';
-
- // T221511: MySQL/MariaDB (10.1.37) can sometimes irrationally decide that querying `actor`
- // before `revision_actor_temp` and filesorting is somehow better than querying $limit+1 rows
- // from `revision_actor_temp`. Tell it not to reorder the query (and also reorder it ourselves
- // because as generated by RevisionStore it'll have `revision` first rather than
- // `revision_actor_temp`). But not when uctag is used, as it seems as likely to be harmed as
- // helped in that case, and not when there's only one User because in that case it fetches
- // the one `actor` row as a constant and doesn't filesort.
- if ( count( $users ) > 1 && !isset( $this->params['tag'] ) ) {
- $revQuery['joins']['revision'] = $revQuery['joins']['temp_rev_user'];
- unset( $revQuery['joins']['temp_rev_user'] );
- $this->addOption( 'STRAIGHT_JOIN' );
- // It isn't actually necesssary to reorder $revQuery['tables'] as Database does the right thing
- // when join conditions are given for all joins, but Gergő is wary of relying on that so pull
- // `revision_actor_temp` to the start.
- $revQuery['tables'] =
- [ 'temp_rev_user' => $revQuery['tables']['temp_rev_user'] ] + $revQuery['tables'];
- }
- } else {
- // If we're dealing with user names (rather than IDs) in read-old mode,
- // pass false for ActorMigration::getWhere()'s $useId parameter so
- // $revWhere['conds'] isn't an OR.
- $revWhere = ActorMigration::newMigration()
- ->getWhere( $db, 'rev_user', $users, $this->orderBy === 'id' );
- $orderUserField = $this->orderBy === 'id' ? 'rev_user' : 'rev_user_text';
- $userField = $revQuery['fields'][$orderUserField];
- $tsField = 'rev_timestamp';
- $idField = 'rev_id';
+ $revWhere = ActorMigration::newMigration()->getWhere( $db, 'rev_user', $users );
+ $orderUserField = 'rev_actor';
+ $userField = $this->orderBy === 'actor' ? 'revactor_actor' : 'actor_name';
+ $tsField = 'revactor_timestamp';
+ $idField = 'revactor_rev';
+
+ // T221511: MySQL/MariaDB (10.1.37) can sometimes irrationally decide that querying `actor`
+ // before `revision_actor_temp` and filesorting is somehow better than querying $limit+1 rows
+ // from `revision_actor_temp`. Tell it not to reorder the query (and also reorder it ourselves
+ // because as generated by RevisionStore it'll have `revision` first rather than
+ // `revision_actor_temp`). But not when uctag is used, as it seems as likely to be harmed as
+ // helped in that case, and not when there's only one User because in that case it fetches
+ // the one `actor` row as a constant and doesn't filesort.
+ if ( count( $users ) > 1 && !isset( $this->params['tag'] ) ) {
+ $revQuery['joins']['revision'] = $revQuery['joins']['temp_rev_user'];
+ unset( $revQuery['joins']['temp_rev_user'] );
+ $this->addOption( 'STRAIGHT_JOIN' );
+ // It isn't actually necesssary to reorder $revQuery['tables'] as Database does the right thing
+ // when join conditions are given for all joins, but Gergő is wary of relying on that so pull
+ // `revision_actor_temp` to the start.
+ $revQuery['tables'] =
+ [ 'temp_rev_user' => $revQuery['tables']['temp_rev_user'] ] + $revQuery['tables'];
}
$this->addTables( $revQuery['tables'] );
* @return string|null ISO 8601 timestamp of current user's last contribution or null if none
*/
protected function getLatestContributionTime() {
- global $wgActorTableSchemaMigrationStage;
-
$user = $this->getUser();
$dbr = $this->getDB();
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
- if ( $user->getActorId() === null ) {
- return null;
- }
- $res = $dbr->selectField( 'revision_actor_temp',
- 'MAX(revactor_timestamp)',
- [ 'revactor_actor' => $user->getActorId() ],
- __METHOD__
- );
- } else {
- if ( $user->isLoggedIn() ) {
- $conds = [ 'rev_user' => $user->getId() ];
- } else {
- $conds = [ 'rev_user_text' => $user->getName() ];
- }
- $res = $dbr->selectField( 'revision',
- 'MAX(rev_timestamp)',
- $conds,
- __METHOD__
- );
+ if ( $user->getActorId() === null ) {
+ return null;
}
+ $res = $dbr->selectField( 'revision_actor_temp',
+ 'MAX(revactor_timestamp)',
+ [ 'revactor_actor' => $user->getActorId() ],
+ __METHOD__
+ );
return $res ? wfTimestamp( TS_ISO_8601, $res ) : null;
}
$user = $this->getUser();
$this->checkUserRightsAny( RevisionDeleter::getRestriction( $params['type'] ) );
- // @TODO Use PermissionManager::isBlockedFrom() instead.
- $block = $user->getBlock();
- if ( $block ) {
- $this->dieBlocked( $block );
- }
-
if ( !$params['ids'] ) {
$this->dieWithError( [ 'apierror-paramempty', 'ids' ], 'paramempty_ids' );
}
$this->dieWithError( [ 'apierror-revdel-needtarget' ], 'needtarget' );
}
+ if ( $this->getPermissionManager()->isBlockedFrom( $user, $targetObj ) ) {
+ $this->dieBlocked( $user->getBlock() );
+ }
+
$list = RevisionDeleter::createList(
$params['type'], $this->getContext(), $targetObj, $params['ids']
);
$titles = $pageSet->getGoodTitles();
$title = reset( $titles );
if ( $title ) {
- $revid = $title->getNextRevisionID( $params['newerthanrevid'], Title::GAID_FOR_UPDATE );
+ $revid = $title->getNextRevisionID( $params['newerthanrevid'], Title::READ_LATEST );
if ( $revid ) {
$timestamp = $dbw->timestamp(
MediaWikiServices::getInstance()->getRevisionStore()->getTimestampFromId( $title, $revid )
*/
use MediaWiki\MediaWikiServices;
-use MediaWiki\Revision\RevisionStore;
/**
* @ingroup API
*/
class ApiTag extends ApiBase {
- /** @var RevisionStore */
+ use ApiBlockInfoTrait;
+
+ /** @var \MediaWiki\Revision\RevisionStore */
private $revisionStore;
public function execute() {
// make sure the user is allowed
$this->checkUserRightsAny( 'changetags' );
- // @TODO Use PermissionManager::isBlockedFrom() instead.
+ // Fail early if the user is sitewide blocked.
$block = $user->getBlock();
- if ( $block ) {
+ if ( $block && $block->isSitewide() ) {
$this->dieBlocked( $block );
}
}
protected function processIndividual( $type, $params, $id ) {
+ $user = $this->getUser();
$idResult = [ $type => $id ];
// validate the ID
switch ( $type ) {
case 'rcid':
$valid = RecentChange::newFromId( $id );
+ if ( $valid && $this->getPermissionManager()->isBlockedFrom( $user, $valid->getTitle() ) ) {
+ $idResult['status'] = 'error';
+ $idResult += $this->getErrorFormatter()->formatMessage( ApiMessage::create(
+ 'apierror-blocked',
+ 'blocked',
+ [ 'blockinfo' => $this->getBlockDetails( $user->getBlock() ) ]
+ ) );
+ return $idResult;
+ }
break;
case 'revid':
$valid = $this->revisionStore->getRevisionById( $id );
+ if (
+ $valid &&
+ $this->getPermissionManager()->isBlockedFrom( $user, $valid->getPageAsLinkTarget() )
+ ) {
+ $idResult['status'] = 'error';
+ $idResult += $this->getErrorFormatter()->formatMessage( ApiMessage::create(
+ 'apierror-blocked',
+ 'blocked',
+ [ 'blockinfo' => $this->getBlockDetails( $user->getBlock() ) ]
+ ) );
+ return $idResult;
+ }
break;
case 'logid':
$valid = self::validateLogId( $id );
"apierror-cantoverwrite-sharedfile": "الملف الهدف موجود في مستودع مشترك وليست لديك صلاحية لتجاوزه.",
"apierror-cantsend": "لم تقم بتسجيل الدخول أو ليس لديك عنوان بريد إلكتروني مؤكد أو غير مسموح لك بإرسال بريد إلكتروني إلى مستخدمين آخرين; لذلك لا يمكنك إرسال بريد إلكتروني.",
"apierror-cantundelete": "تعذر الاسترجاع: قد لا تكون المراجعات المطلوبة موجودة، أو ربما تم الاسترجاع بالفعل.",
+ "apierror-cantview-deleted-description": "ليست لديك صلايبة لعرض أوصاف الملفات المحذوفة.",
+ "apierror-cantview-deleted-metadata": "ليست لديك صلايبة لعرض البيانات الوصفية للملفات المحذوفة.",
"apierror-changeauth-norequest": "فشل في إنشاء طلب التغيير.",
"apierror-chunk-too-small": "الحد الأدنى لحجم القطعة هو $1 {{PLURAL:$1|بايت}} للقطع غير النهائية.",
"apierror-cidrtoobroad": "لا يُقبَل مدى $1 CIDR أكبر من /$2.",
"apierror-cantoverwrite-sharedfile": "The target file exists on a shared repository and you do not have permission to override it.",
"apierror-cantsend": "You are not logged in, you do not have a confirmed email address, or you are not allowed to send email to other users, so you cannot send email.",
"apierror-cantundelete": "Couldn't undelete: the requested revisions may not exist, or may have been undeleted already.",
+ "apierror-cantview-deleted-comment": "You don't have permission to view deleted comments.",
+ "apierror-cantview-deleted-description": "You don't have permission to view descriptions of deleted files.",
+ "apierror-cantview-deleted-metadata": "You don't have permission to view metadata of deleted files.",
+ "apierror-cantview-deleted-revision-content": "You don't have permission to view content of deleted revisions.",
"apierror-changeauth-norequest": "Failed to create change request.",
"apierror-chunk-too-small": "Minimum chunk size is $1 {{PLURAL:$1|byte|bytes}} for non-final chunks.",
"apierror-cidrtoobroad": "$1 CIDR ranges broader than /$2 are not accepted.",
"apierror-cantoverwrite-sharedfile": "Le fichier cible existe dans un dépôt partagé et vous n’avez pas le droit de l’écraser.",
"apierror-cantsend": "Vous n’êtes pas connecté, vous n’avez pas d’adresse de courriel confirmée, ou vous n’êtes pas autorisé à envoyer des courriels aux autres utilisateurs, donc vous ne pouvez envoyer de courriel.",
"apierror-cantundelete": "Impossible d’annuler : les révisions demandées peuvent ne plus exister, ou avoir déjà été annulées.",
+ "apierror-cantview-deleted-description": "Vous n’avez pas le droit d’afficher les descriptions des fichiers supprimés.",
+ "apierror-cantview-deleted-metadata": "Vous n’avez pas le droit d’afficher les métadonnées des fichiers supprimés.",
"apierror-changeauth-norequest": "Échec à la création de la requête de modification.",
"apierror-chunk-too-small": "La taille minimale d’un segment est de $1 {{PLURAL:$1|octet|octets}} pour les segments hors le dernier.",
"apierror-cidrtoobroad": "Les plages CIDR $1 plus large que /$2 ne sont pas acceptées.",
"InternerowyGołąb",
"CiaPan",
"Vlad5250",
- "Railfail536"
+ "Railfail536",
+ "Rail"
]
},
"apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|Dokumentacja]]\n* [[mw:Special:MyLanguage/API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Lista dyskusyjna]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Ogłoszenia dotyczące API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Błędy i propozycje]\n</div>\n<strong>Stan:</strong> Wszystkie funkcje opisane na tej stronie powinny działać, ale API nadal jest aktywnie rozwijane i mogą się zmienić w dowolnym czasie. Subskrybuj [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ listę dyskusyjną mediawiki-api-announce], aby móc na bieżąco dowiadywać się o aktualizacjach.\n\n<strong>Błędne żądania:</strong> Gdy zostanie wysłane błędne żądanie do API, zostanie wysłany w odpowiedzi nagłówek HTTP z kluczem \"MediaWiki-API-Error\" i zarówno jego wartość jak i wartość kodu błędu wysłanego w odpowiedzi będą miały taką samą wartość. Aby uzyskać więcej informacji, zobacz [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Błędy i ostrzeżenia]].\n\n<strong>Testowanie:</strong> Aby łatwo testować żądania API, zobacz [[Special:ApiSandbox]].",
"apierror-cantimport": "Nie masz uprawnień do importowania stron.",
"apierror-cantsend": "Nie jesteś zalogowany, nie masz potwierdzonego adresu e-mail, albo nie masz prawa wysyłać e-maili do innych użytkowników, więc nie możesz wysłać wiadomości e-mail.",
"apierror-cantundelete": "Nie można przywrócić: dana wersja nie istnieje albo została już przywrócona.",
+ "apierror-cantview-deleted-description": "Nie masz uprawnień do podglądu opisów usuniętych plików.",
+ "apierror-cantview-deleted-metadata": "Nie masz uprawnień do podglądu metadanych usuniętych plików.",
"apierror-exceptioncaught": "[$1] Stwierdzono wyjątek: $2",
"apierror-filedoesnotexist": "Plik nie istnieje.",
"apierror-import-unknownerror": "Nieznany błąd podczas importowania: $1.",
"apierror-cantoverwrite-sharedfile": "{{doc-apierror}}",
"apierror-cantsend": "{{doc-apierror}}",
"apierror-cantundelete": "{{doc-apierror}}",
+ "apierror-cantview-deleted-comment": "{{doc-apierror}}",
+ "apierror-cantview-deleted-description": "{{doc-apierror}}",
+ "apierror-cantview-deleted-metadata": "{{doc-apierror}}",
+ "apierror-cantview-deleted-revision-content": "{{doc-apierror}}",
"apierror-changeauth-norequest": "{{doc-apierror}}",
"apierror-chunk-too-small": "{{doc-apierror}}\n\nParameters:\n* $1 - Minimum size in bytes.",
"apierror-cidrtoobroad": "{{doc-apierror}}\n\nParameters:\n* $1 - \"IPv4\" or \"IPv6\"\n* $2 - Minimum CIDR mask length.",
"apierror-cantoverwrite-sharedfile": "目標檔案存在於分享儲存庫上,因此您沒有權限來覆蓋掉。",
"apierror-cantsend": "您尚未登入,您沒有已確認的電子郵件地址,或是您未被允許發送電子郵件給其他人,因此您不能發送電子郵件。",
"apierror-cantundelete": "無法取消刪除:請求的修訂可能不存在,或是可能已被取消刪除。",
+ "apierror-cantview-deleted-description": "您沒有權限來檢視被刪除檔案的描述內容。",
+ "apierror-cantview-deleted-metadata": "您沒有權限來檢視被刪除檔案的詮釋資料。",
"apierror-changeauth-norequest": "建立更改請求失敗。",
"apierror-chunk-too-small": "對於非最終塊,最小塊的大小為 $1 {{PLURAL:$1|位元組|位元組}}。",
"apierror-cidrtoobroad": "不能接受超出 /$2 的 $1 CIDR 範圍。",
}
/**
+ * Select a request by class name.
+ *
* @codingStandardsIgnoreStart
- * @template T
+ * @phan-template T
* @codingStandardsIgnoreEnd
- * Select a request by class name.
* @param AuthenticationRequest[] $reqs
* @param string $class Class name
* @phan-param class-string<T> $class
use ActorMigration;
use AutoCommitUpdate;
-use BadMethodCallException;
use CommentStore;
use DeferredUpdates;
use Hooks;
}
}
- /**
- * Return the list of ipblocks fields that should be selected to create
- * a new block.
- * @deprecated since 1.31, use self::getQueryInfo() instead.
- * @return array
- */
- public static function selectFields() {
- global $wgActorTableSchemaMigrationStage;
-
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
- // If code is using this instead of self::getQueryInfo(), there's a
- // decent chance it's going to try to directly access
- // $row->ipb_by or $row->ipb_by_text and we can't give it
- // useful values here once those aren't being used anymore.
- throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__
- . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
- );
- }
-
- wfDeprecated( __METHOD__, '1.31' );
- return [
- 'ipb_id',
- 'ipb_address',
- 'ipb_by',
- 'ipb_by_text',
- 'ipb_by_actor' => 'NULL',
- 'ipb_timestamp',
- 'ipb_auto',
- 'ipb_anon_only',
- 'ipb_create_account',
- 'ipb_enable_autoblock',
- 'ipb_expiry',
- 'ipb_deleted',
- 'ipb_block_email',
- 'ipb_allow_usertalk',
- 'ipb_parent_block_id',
- 'ipb_sitewide',
- ] + CommentStore::getStore()->getFields( 'ipb_reason' );
- }
-
/**
* Return the tables, fields, and join conditions to be selected to create
* a new block object.
*
* @param bool|null $update
* @return bool
+ * @deprecated Since 1.34
*/
public function forUpdate( $update = null ) {
return wfSetVar( $this->mForUpdate, $update );
* @param string $caller The calling method
*/
public function doQuery( array $userIds, $options = [], $caller = '' ) {
- global $wgActorTableSchemaMigrationStage;
-
$usersToCheck = [];
$usersToQuery = [];
// Lookup basic info for users not yet loaded...
if ( count( $usersToQuery ) ) {
$dbr = wfGetDB( DB_REPLICA );
- $tables = [ 'user' ];
+ $tables = [ 'user', 'actor' ];
$conds = [ 'user_id' => $usersToQuery ];
- $fields = [ 'user_name', 'user_real_name', 'user_registration', 'user_id' ];
- $joinConds = [];
-
- // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
- // but it does little harm and might be needed for write callers loading a User.
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
- $tables[] = 'actor';
- $fields[] = 'actor_id';
- $joinConds['actor'] = [
- ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ? 'JOIN' : 'LEFT JOIN',
- [ 'actor_user = user_id' ]
- ];
- }
+ $fields = [ 'user_name', 'user_real_name', 'user_registration', 'user_id', 'actor_id' ];
+ $joinConds = [
+ 'actor' => [ 'JOIN', 'actor_user = user_id' ],
+ ];
$comment = __METHOD__;
if ( strval( $caller ) !== '' ) {
$this->cache[$userId]['name'] = $row->user_name;
$this->cache[$userId]['real_name'] = $row->user_real_name;
$this->cache[$userId]['registration'] = $row->user_registration;
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
- $this->cache[$userId]['actor'] = $row->actor_id;
- }
+ $this->cache[$userId]['actor'] = $row->actor_id;
$usersToCheck[$userId] = $row->user_name;
}
}
* This will work on any MediaWiki installation.
*/
class LCStoreDB implements LCStore {
- /** @var string */
- private $currentLang;
- /** @var bool */
- private $writesDone = false;
+ /** @var string Language code */
+ private $code;
+ /** @var array Server configuration map */
+ private $server;
+
+ /** @var array Rows buffered for insertion */
+ private $batch = [];
+
/** @var IDatabase|null */
private $dbw;
- /** @var array */
- private $batch = [];
- /** @var bool */
+ /** @var bool Whether a batch of writes were recently written */
+ private $writesDone = false;
+ /** @var bool Whether the DB is read-only or otherwise unavailable for writes */
private $readOnly = false;
- /** @var array Server configuration map */
- private $server;
public function __construct( $params ) {
$this->server = $params['server'] ?? [];
$dbw = $this->getWriteConnection();
$this->readOnly = $dbw->isReadOnly();
- $this->currentLang = $code;
+ $this->code = $code;
$this->batch = [];
}
public function finishWrite() {
if ( $this->readOnly ) {
return;
- } elseif ( is_null( $this->currentLang ) ) {
+ } elseif ( is_null( $this->code ) ) {
throw new MWException( __CLASS__ . ': must call startWrite() before finishWrite()' );
}
$dbw = $this->getWriteConnection();
$dbw->startAtomic( __METHOD__ );
try {
- $dbw->delete( 'l10n_cache', [ 'lc_lang' => $this->currentLang ], __METHOD__ );
+ $dbw->delete( 'l10n_cache', [ 'lc_lang' => $this->code ], __METHOD__ );
foreach ( array_chunk( $this->batch, 500 ) as $rows ) {
$dbw->insert( 'l10n_cache', $rows, __METHOD__ );
}
$trxProfiler->setSilenced( $oldSilenced );
}
- $this->currentLang = null;
+ $this->code = null;
$this->batch = [];
}
public function set( $key, $value ) {
if ( $this->readOnly ) {
return;
- } elseif ( is_null( $this->currentLang ) ) {
+ } elseif ( is_null( $this->code ) ) {
throw new MWException( __CLASS__ . ': must call startWrite() before set()' );
}
$dbw = $this->getWriteConnection();
$this->batch[] = [
- 'lc_lang' => $this->currentLang,
+ 'lc_lang' => $this->code,
'lc_key' => $key,
'lc_value' => $dbw->encodeBlob( serialize( $value ) )
];
}
}
- /**
- * Return the list of recentchanges fields that should be selected to create
- * a new recentchanges object.
- * @deprecated since 1.31, use self::getQueryInfo() instead.
- * @return array
- */
- public static function selectFields() {
- global $wgActorTableSchemaMigrationStage;
-
- wfDeprecated( __METHOD__, '1.31' );
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
- // If code is using this instead of self::getQueryInfo(), there's a
- // decent chance it's going to try to directly access
- // $row->rc_user or $row->rc_user_text and we can't give it
- // useful values here once those aren't being used anymore.
- throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__
- . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
- );
- }
-
- return [
- 'rc_id',
- 'rc_timestamp',
- 'rc_user',
- 'rc_user_text',
- 'rc_actor' => 'NULL',
- 'rc_namespace',
- 'rc_title',
- 'rc_minor',
- 'rc_bot',
- 'rc_new',
- 'rc_cur_id',
- 'rc_this_oldid',
- 'rc_last_oldid',
- 'rc_type',
- 'rc_source',
- 'rc_patrolled',
- 'rc_ip',
- 'rc_old_len',
- 'rc_new_len',
- 'rc_deleted',
- 'rc_logid',
- 'rc_log_type',
- 'rc_log_action',
- 'rc_params',
- ] + CommentStore::getStore()->getFields( 'rc_comment' );
- }
-
/**
* Return the tables, fields, and join conditions to be selected to create
* a new recentchanges object.
->userHasRight( $user, 'applychangetags' )
) {
return Status::newFatal( 'tags-apply-no-permission' );
- } elseif ( $user->getBlock() ) {
- // @TODO Ensure that the block does not apply to the `applychangetags`
- // right.
+ } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
return Status::newFatal( 'tags-apply-blocked', $user->getName() );
}
}
->userHasRight( $user, 'changetags' )
) {
return Status::newFatal( 'tags-update-no-permission' );
- } elseif ( $user->getBlock() ) {
- // @TODO Ensure that the block does not apply to the `changetags`
- // right.
+ } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
return Status::newFatal( 'tags-update-blocked', $user->getName() );
}
}
->userHasRight( $user, 'managechangetags' )
) {
return Status::newFatal( 'tags-manage-no-permission' );
- } elseif ( $user->getBlock() ) {
- // @TODO Ensure that the block does not apply to the `managechangetags`
- // right.
+ } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
return Status::newFatal( 'tags-manage-blocked', $user->getName() );
}
}
->userHasRight( $user, 'managechangetags' )
) {
return Status::newFatal( 'tags-manage-no-permission' );
- } elseif ( $user->getBlock() ) {
- // @TODO Ensure that the block does not apply to the `managechangetags`
- // right.
+ } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
return Status::newFatal( 'tags-manage-blocked', $user->getName() );
}
}
->userHasRight( $user, 'managechangetags' )
) {
return Status::newFatal( 'tags-manage-no-permission' );
- } elseif ( $user->getBlock() ) {
- // @TODO Ensure that the block does not apply to the `managechangetags`
- // right.
+ } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
return Status::newFatal( 'tags-manage-blocked', $user->getName() );
}
}
->userHasRight( $user, 'deletechangetags' )
) {
return Status::newFatal( 'tags-delete-no-permission' );
- } elseif ( $user->getBlock() ) {
- // @TODO Ensure that the block does not apply to the `deletechangetags`
- // right.
+ } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
return Status::newFatal( 'tags-manage-blocked', $user->getName() );
}
}
--- /dev/null
+<?php
+
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/**
+ * Edit phpunit.xml to speed up code coverage generation.
+ *
+ * Usage: composer phpunit:coverage-edit -- extensions/ExtensionName
+ *
+ * This class runs *outside* of the normal MediaWiki
+ * environment and cannot depend upon any MediaWiki
+ * code.
+ */
+class ComposerPhpunitXmlCoverageEdit {
+
+ public static function onEvent( $event ) {
+ $IP = dirname( dirname( __DIR__ ) );
+ // TODO: Support passing arbitrary directories for core (or extensions/skins).
+ $args = $event->getArguments();
+ if ( count( $args ) !== 1 ) {
+ throw new InvalidArgumentException( 'Pass extensions/$extensionName as an argument, ' .
+ 'e.g. "composer phpunit:coverage-edit -- extensions/BoilerPlate"' );
+ }
+ $project = current( $args );
+ $phpunitXml = \PHPUnit\Util\Xml::loadFile( $IP . '/phpunit.xml.dist' );
+ $whitelist = iterator_to_array( $phpunitXml->getElementsByTagName( 'whitelist' ) );
+ /** @var DOMNode $childNode */
+ foreach ( $whitelist as $childNode ) {
+ $childNode->parentNode->removeChild( $childNode );
+ }
+ $whitelistElement = $phpunitXml->createElement( 'whitelist' );
+ $whitelistElement->setAttribute( 'addUncoveredFilesFromWhitelist', 'false' );
+ // TODO: Use AutoloadClasses from extension.json to load the relevant directories
+ foreach ( [ 'includes', 'src', 'maintenance' ] as $dir ) {
+ $dirElement = $phpunitXml->createElement( 'directory', $project . '/' . $dir );
+ $dirElement->setAttribute( 'suffix', '.php' );
+ $whitelistElement->appendChild( $dirElement );
+
+ }
+ $phpunitXml->getElementsByTagName( 'filter' )->item( 0 )
+ ->appendChild( $whitelistElement );
+ $phpunitXml->formatOutput = true;
+ $phpunitXml->save( $IP . '/phpunit.xml' );
+ }
+}
if ( !$this->mId ) {
// NOTE: subclasses may initialize mId before calling this constructor!
- $this->mId = $title->getArticleID( Title::GAID_FOR_UPDATE );
+ $this->mId = $title->getArticleID( Title::READ_LATEST );
}
if ( !$this->mId ) {
if ( $samePage && $this->mNewPage && $permissionManager->quickUserCan(
'edit', $user, $this->mNewPage
) ) {
- if ( $this->mNewRev->isCurrent() && $permissionManager->userCan(
+ if ( $this->mNewRev->isCurrent() && $permissionManager->quickUserCan(
'rollback', $user, $this->mNewPage
) ) {
$rollbackLink = Linker::generateRollback( $this->mNewRev, $this->getContext(),
use Wikimedia\Rdbms\DBConnRef;
use Wikimedia\Rdbms\MaintainableDBConnRef;
use Wikimedia\Rdbms\DatabaseDomain;
+use Wikimedia\Rdbms\DBUnexpectedError;
/**
* DB accessible external objects.
*/
public function store( $location, $data ) {
$dbw = $this->getMaster( $location );
- $dbw->insert( $this->getTable( $dbw ), [ 'blob_text' => $data ], __METHOD__ );
+ $dbw->insert(
+ $this->getTable( $dbw, $location ),
+ [ 'blob_text' => $data ],
+ __METHOD__
+ );
$id = $dbw->insertId();
if ( !$id ) {
throw new MWException( __METHOD__ . ': no insert ID' );
/**
* Get a replica DB connection for the specified cluster
*
+ * @since 1.34
* @param string $cluster Cluster name
* @return DBConnRef
*/
- public function getSlave( $cluster ) {
+ public function getReplica( $cluster ) {
$lb = $this->getLoadBalancer( $cluster );
return $lb->getConnectionRef(
);
}
+ /**
+ * Get a replica DB connection for the specified cluster
+ *
+ * @param string $cluster Cluster name
+ * @return DBConnRef
+ * @deprecated since 1.34
+ */
+ public function getSlave( $cluster ) {
+ return $this->getReplica( $cluster );
+ }
+
/**
* Get a master database connection for the specified cluster
*
* Get the 'blobs' table name for this database
*
* @param IDatabase $db
+ * @param string|null $cluster Cluster name
* @return string Table name ('blobs' by default)
*/
- public function getTable( $db ) {
- $table = $db->getLBInfo( 'blobs table' );
- if ( is_null( $table ) ) {
- $table = 'blobs';
+ public function getTable( $db, $cluster = null ) {
+ if ( $cluster !== null ) {
+ $lb = $this->getLoadBalancer( $cluster );
+ $info = $lb->getServerInfo( $lb->getWriterIndex() );
+ if ( isset( $info['blobs table'] ) ) {
+ return $info['blobs table'];
+ }
}
- return $table;
+ return $db->getLBInfo( 'blobs table' ) ?? 'blobs'; // b/c
+ }
+
+ /**
+ * Create the appropriate blobs table on this cluster
+ *
+ * @see getTable()
+ * @since 1.34
+ * @param string $cluster
+ */
+ public function initializeTable( $cluster ) {
+ global $IP;
+
+ static $supportedTypes = [ 'mysql', 'sqlite' ];
+
+ $dbw = $this->getMaster( $cluster );
+ if ( !in_array( $dbw->getType(), $supportedTypes, true ) ) {
+ throw new DBUnexpectedError( $dbw, "RDBMS type '{$dbw->getType()}' not supported." );
+ }
+
+ $sqlFilePath = "$IP/maintenance/storage/blobs.sql";
+ $sql = file_get_contents( $sqlFilePath );
+ if ( $sql === false ) {
+ throw new RuntimeException( "Failed to read '$sqlFilePath'." );
+ }
+
+ $rawTable = $this->getTable( $dbw, $cluster ); // e.g. "blobs_cluster23"
+ $encTable = $dbw->tableName( $rawTable );
+ $dbw->query(
+ str_replace(
+ [ '/*$wgDBprefix*/blobs', '/*_*/blobs' ],
+ [ $encTable, $encTable ],
+ $sql
+ ),
+ __METHOD__,
+ $dbw::QUERY_IGNORE_DBO_TRX
+ );
}
/**
$this->logger->debug( "ExternalStoreDB::fetchBlob cache miss on $cacheID" );
- $dbr = $this->getSlave( $cluster );
- $ret = $dbr->selectField( $this->getTable( $dbr ),
- 'blob_text', [ 'blob_id' => $id ], __METHOD__ );
+ $dbr = $this->getReplica( $cluster );
+ $ret = $dbr->selectField(
+ $this->getTable( $dbr, $cluster ),
+ 'blob_text',
+ [ 'blob_id' => $id ],
+ __METHOD__
+ );
if ( $ret === false ) {
$this->logger->info( "ExternalStoreDB::fetchBlob master fallback on $cacheID" );
// Try the master
$dbw = $this->getMaster( $cluster );
- $ret = $dbw->selectField( $this->getTable( $dbw ),
- 'blob_text', [ 'blob_id' => $id ], __METHOD__ );
+ $ret = $dbw->selectField(
+ $this->getTable( $dbw, $cluster ),
+ 'blob_text',
+ [ 'blob_id' => $id ],
+ __METHOD__
+ );
if ( $ret === false ) {
$this->logger->error( "ExternalStoreDB::fetchBlob master failed to find $cacheID" );
}
* Unlocated ids are not represented
*/
private function batchFetchBlobs( $cluster, array $ids ) {
- $dbr = $this->getSlave( $cluster );
+ $dbr = $this->getReplica( $cluster );
$res = $dbr->select(
- $this->getTable( $dbr ),
+ $this->getTable( $dbr, $cluster ),
[ 'blob_id', 'blob_text' ],
[ 'blob_id' => array_keys( $ids ) ],
__METHOD__
);
// Try the master
$dbw = $this->getMaster( $cluster );
- $res = $dbw->select( $this->getTable( $dbr ),
+ $res = $dbw->select(
+ $this->getTable( $dbr, $cluster ),
[ 'blob_id', 'blob_text' ],
[ 'blob_id' => array_keys( $ids ) ],
__METHOD__ );
return $file;
}
- /**
- * Fields in the filearchive table
- * @deprecated since 1.31, use self::getQueryInfo() instead.
- * @return string[]
- */
- static function selectFields() {
- global $wgActorTableSchemaMigrationStage;
-
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
- // If code is using this instead of self::getQueryInfo(), there's a
- // decent chance it's going to try to directly access
- // $row->fa_user or $row->fa_user_text and we can't give it
- // useful values here once those aren't being used anymore.
- throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__
- . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
- );
- }
-
- wfDeprecated( __METHOD__, '1.31' );
- return [
- 'fa_id',
- 'fa_name',
- 'fa_archive_name',
- 'fa_storage_key',
- 'fa_storage_group',
- 'fa_size',
- 'fa_bits',
- 'fa_width',
- 'fa_height',
- 'fa_metadata',
- 'fa_media_type',
- 'fa_major_mime',
- 'fa_minor_mime',
- 'fa_user',
- 'fa_user_text',
- 'fa_actor' => 'NULL',
- 'fa_timestamp',
- 'fa_deleted',
- 'fa_deleted_timestamp', /* Used by LocalFileRestoreBatch */
- 'fa_sha1',
- ] + MediaWikiServices::getInstance()->getCommentStore()->getFields( 'fa_description' );
- }
-
/**
* Return the tables, fields, and join conditions to be selected to create
* a new archivedfile object.
}
}
- /**
- * Fields in the image table
- * @deprecated since 1.31, use self::getQueryInfo() instead.
- * @return string[]
- */
- static function selectFields() {
- global $wgActorTableSchemaMigrationStage;
-
- wfDeprecated( __METHOD__, '1.31' );
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
- // If code is using this instead of self::getQueryInfo(), there's a
- // decent chance it's going to try to directly access
- // $row->img_user or $row->img_user_text and we can't give it
- // useful values here once those aren't being used anymore.
- throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__
- . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
- );
- }
-
- return [
- 'img_name',
- 'img_size',
- 'img_width',
- 'img_height',
- 'img_metadata',
- 'img_bits',
- 'img_media_type',
- 'img_major_mime',
- 'img_minor_mime',
- 'img_user',
- 'img_user_text',
- 'img_actor' => 'NULL',
- 'img_timestamp',
- 'img_sha1',
- ] + MediaWikiServices::getInstance()->getCommentStore()->getFields( 'img_description' );
- }
-
/**
* Return the tables, fields, and join conditions to be selected to create
* a new localfile object.
$oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null, $tags = [],
$createNullRevision = true, $revert = false
) {
- global $wgActorTableSchemaMigrationStage;
-
if ( is_null( $user ) ) {
global $wgUser;
$user = $wgUser;
'oi_major_mime' => 'img_major_mime',
'oi_minor_mime' => 'img_minor_mime',
'oi_sha1' => 'img_sha1',
+ 'oi_actor' => 'img_actor',
];
$joins = [];
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
- $fields['oi_user'] = 'img_user';
- $fields['oi_user_text'] = 'img_user_text';
- }
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
- $fields['oi_actor'] = 'img_actor';
- }
-
- if (
- ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_BOTH
- ) {
- // Upgrade any rows that are still old-style. Otherwise an upgrade
- // might be missed if a deletion happens while the migration script
- // is running.
- $res = $dbw->select(
- [ 'image' ],
- [ 'img_name', 'img_user', 'img_user_text' ],
- [ 'img_name' => $this->getName(), 'img_actor' => 0 ],
- __METHOD__
- );
- foreach ( $res as $row ) {
- $actorId = User::newFromAnyId( $row->img_user, $row->img_user_text, null )->getActorId( $dbw );
- $dbw->update(
- 'image',
- [ 'img_actor' => $actorId ],
- [ 'img_name' => $row->img_name, 'img_actor' => 0 ],
- __METHOD__
- );
- }
- }
-
# (T36993) Note: $oldver can be empty here, if the previous
# version of the file was broken. Allow registration of the new
# version to continue anyway, because that's better than having
}
protected function doDBInserts() {
- global $wgActorTableSchemaMigrationStage;
-
$now = time();
$dbw = $this->file->repo->getMasterDB();
'fa_minor_mime' => 'img_minor_mime',
'fa_description_id' => 'img_description_id',
'fa_timestamp' => 'img_timestamp',
- 'fa_sha1' => 'img_sha1'
+ 'fa_sha1' => 'img_sha1',
+ 'fa_actor' => 'img_actor',
];
$joins = [];
$commentStore->insert( $dbw, 'fa_deleted_reason', $this->reason )
);
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
- $fields['fa_user'] = 'img_user';
- $fields['fa_user_text'] = 'img_user_text';
- }
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
- $fields['fa_actor'] = 'img_actor';
- }
-
- if (
- ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_BOTH
- ) {
- // Upgrade any rows that are still old-style. Otherwise an upgrade
- // might be missed if a deletion happens while the migration script
- // is running.
- $res = $dbw->select(
- [ 'image' ],
- [ 'img_name', 'img_user', 'img_user_text' ],
- [ 'img_name' => $this->file->getName(), 'img_actor' => 0 ],
- __METHOD__
- );
- foreach ( $res as $row ) {
- $actorId = User::newFromAnyId( $row->img_user, $row->img_user_text, null )->getActorId( $dbw );
- $dbw->update(
- 'image',
- [ 'img_actor' => $actorId ],
- [ 'img_name' => $row->img_name, 'img_actor' => 0 ],
- __METHOD__
- );
- }
- }
-
$dbw->insertSelect( 'filearchive', $tables, $fields,
[ 'img_name' => $this->file->getName() ], __METHOD__, [], [], $joins );
}
}
}
- /**
- * Fields in the oldimage table
- * @deprecated since 1.31, use self::getQueryInfo() instead.
- * @return string[]
- */
- static function selectFields() {
- global $wgActorTableSchemaMigrationStage;
-
- wfDeprecated( __METHOD__, '1.31' );
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
- // If code is using this instead of self::getQueryInfo(), there's a
- // decent chance it's going to try to directly access
- // $row->oi_user or $row->oi_user_text and we can't give it
- // useful values here once those aren't being used anymore.
- throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__
- . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
- );
- }
-
- return [
- 'oi_name',
- 'oi_archive_name',
- 'oi_size',
- 'oi_width',
- 'oi_height',
- 'oi_metadata',
- 'oi_bits',
- 'oi_media_type',
- 'oi_major_mime',
- 'oi_minor_mime',
- 'oi_user',
- 'oi_user_text',
- 'oi_actor' => 'NULL',
- 'oi_timestamp',
- 'oi_deleted',
- 'oi_sha1',
- ] + MediaWikiServices::getInstance()->getCommentStore()->getFields( 'oi_description' );
- }
-
/**
* Return the tables, fields, and join conditions to be selected to create
* a new oldlocalfile object.
}
$this->setVar( '_Extensions', $status->value );
} elseif ( isset( $options['with-extensions'] ) ) {
- $this->setVar( '_Extensions', array_keys( $this->findExtensions() ) );
+ $status = $this->findExtensions();
+ if ( !$status->isOK() ) {
+ throw new InstallException( $status );
+ }
+ $this->setVar( '_Extensions', array_keys( $status->value ) );
}
// Set up the default skins
}
$skins = $status->value;
} else {
- $skins = array_keys( $this->findExtensions( 'skins' ) );
+ $status = $this->findExtensions( 'skins' );
+ if ( !$status->isOK() ) {
+ throw new InstallException( $status );
+ }
+ $skins = array_keys( $status->value );
}
$this->setVar( '_Skins', $skins );
* @since 1.31
*/
protected function migrateActors() {
- global $wgActorTableSchemaMigrationStage;
- if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) &&
- !$this->updateRowExists( 'MigrateActors' )
- ) {
+ if ( !$this->updateRowExists( 'MigrateActors' ) ) {
$this->output(
"Migrating actors to the 'actor' table, printing progress markers. For large\n" .
"databases, you may want to hit Ctrl-C and do this manually with\n" .
}
}
}
+
+ /**
+ * Only run a function if the `actor` table does not exist
+ *
+ * The transition to the actor table is dropping several indexes (and a few
+ * fields) that old upgrades want to add. This function is used to prevent
+ * those from running to re-add things when the `actor` table exists, while
+ * still allowing them to run if this really is an upgrade from an old MW
+ * version.
+ *
+ * @since 1.34
+ * @param string|array|static $func Normally this is the string naming the method on $this to
+ * call. It may also be an array callable. If passed $this, it's assumed to be a call from
+ * runUpdates() with $passSelf = true: $params[0] is assumed to be the real $func and $this
+ * is prepended to the rest of $params.
+ * @param mixed ...$params Parameters for `$func`
+ * @return mixed Whatever $func returns, or null when skipped.
+ */
+ protected function ifNoActorTable( $func, ...$params ) {
+ if ( $this->tableExists( 'actor' ) ) {
+ return null;
+ }
+
+ // Handle $passSelf from runUpdates().
+ $passSelf = false;
+ if ( $func === $this ) {
+ $passSelf = true;
+ $func = array_shift( $params );
+ }
+
+ if ( !is_array( $func ) && method_exists( $this, $func ) ) {
+ $func = [ $this, $func ];
+ } elseif ( $passSelf ) {
+ array_unshift( $params, $this );
+ }
+
+ // @phan-suppress-next-line PhanUndeclaredInvokeInCallable Phan is confused
+ return $func( ...$params );
+ }
}
*
* @param string $directory Directory to search in, relative to $IP, must be either "extensions"
* or "skins"
- * @return array[][] [ $extName => [ 'screenshots' => [ '...' ] ]
+ * @return Status An object containing an error list. If there were no errors, an associative
+ * array of information about the extension can be found in $status->value.
*/
public function findExtensions( $directory = 'extensions' ) {
switch ( $directory ) {
*
* @param string $type Either "extension" or "skin"
* @param string $directory Directory to search in, relative to $IP
- * @return array [ $extName => [ 'screenshots' => [ '...' ] ]
+ * @return Status An object containing an error list. If there were no errors, an associative
+ * array of information about the extension can be found in $status->value.
*/
protected function findExtensionsByType( $type = 'extension', $directory = 'extensions' ) {
if ( $this->getVar( 'IP' ) === null ) {
- return [];
+ return Status::newGood( [] );
}
$extDir = $this->getVar( 'IP' ) . '/' . $directory;
if ( !is_readable( $extDir ) || !is_dir( $extDir ) ) {
- return [];
+ return Status::newGood( [] );
}
$dh = opendir( $extDir );
$exts = [];
+ $status = new Status;
while ( ( $file = readdir( $dh ) ) !== false ) {
- if ( !is_dir( "$extDir/$file" ) ) {
+ // skip non-dirs and hidden directories
+ if ( !is_dir( "$extDir/$file" ) || $file[0] === '.' ) {
continue;
}
- $status = $this->getExtensionInfo( $type, $directory, $file );
- if ( $status->isOK() ) {
- $exts[$file] = $status->value;
+ $extStatus = $this->getExtensionInfo( $type, $directory, $file );
+ if ( $extStatus->isOK() ) {
+ $exts[$file] = $extStatus->value;
+ } elseif ( $extStatus->hasMessage( 'config-extension-not-found' ) ) {
+ // (T225512) The directory is not actually an extension. Downgrade to warning.
+ $status->warning( 'config-extension-not-found', $file );
+ } else {
+ $status->merge( $extStatus );
}
}
closedir( $dh );
uksort( $exts, 'strnatcasecmp' );
- return $exts;
+ $status->value = $exts;
+
+ return $status;
}
/**
} elseif ( $e->missingExtensions || $e->missingSkins ) {
// There's an extension missing in the dependency tree,
// so add those to the dependency list and try again
- return $this->readExtension(
+ $status = $this->readExtension(
$fullJsonFile,
array_merge( $extDeps, $e->missingExtensions ),
array_merge( $skinDeps, $e->missingSkins )
);
+ if ( !$status->isOK() && !$status->hasMessage( 'config-extension-dependency' ) ) {
+ $status = Status::newFatal( 'config-extension-dependency',
+ basename( dirname( $fullJsonFile ) ), $status->getMessage() );
+ }
+ return $status;
}
// Some other kind of dependency error?
return Status::newFatal( 'config-extension-dependency',
[ 'addTable', 'querycache_info', 'patch-querycacheinfo.sql' ],
[ 'addTable', 'filearchive', 'patch-filearchive.sql' ],
[ 'addField', 'ipblocks', 'ipb_anon_only', 'patch-ipb_anon_only.sql' ],
- [ 'addIndex', 'recentchanges', 'rc_ns_usertext', 'patch-recentchanges-utindex.sql' ],
- [ 'addIndex', 'recentchanges', 'rc_user_text', 'patch-rc_user_text-index.sql' ],
+ [ 'ifNoActorTable', 'addIndex', 'recentchanges', 'rc_ns_usertext',
+ 'patch-recentchanges-utindex.sql' ],
+ [ 'ifNoActorTable', 'addIndex', 'recentchanges', 'rc_user_text',
+ 'patch-rc_user_text-index.sql' ],
// 1.9
[ 'addField', 'user', 'user_newpass_time', 'patch-user_newpass_time.sql' ],
[ 'addField', 'ipblocks', 'ipb_block_email', 'patch-ipb_emailban.sql' ],
[ 'doCategorylinksIndicesUpdate' ],
[ 'addField', 'oldimage', 'oi_metadata', 'patch-oi_metadata.sql' ],
- [ 'addIndex', 'archive', 'usertext_timestamp', 'patch-archive-user-index.sql' ],
- [ 'addIndex', 'image', 'img_usertext_timestamp', 'patch-image-user-index.sql' ],
- [ 'addIndex', 'oldimage', 'oi_usertext_timestamp', 'patch-oldimage-user-index.sql' ],
+ [ 'ifNoActorTable', 'addIndex', 'archive', 'usertext_timestamp',
+ 'patch-archive-user-index.sql' ],
+ [ 'ifNoActorTable', 'addIndex', 'image', 'img_usertext_timestamp',
+ 'patch-image-user-index.sql' ],
+ [ 'ifNoActorTable', 'addIndex', 'oldimage', 'oi_usertext_timestamp',
+ 'patch-oldimage-user-index.sql' ],
[ 'addField', 'archive', 'ar_page_id', 'patch-archive-page_id.sql' ],
[ 'addField', 'image', 'img_sha1', 'patch-img_sha1.sql' ],
[ 'addTable', 'protected_titles', 'patch-protected_titles.sql' ],
// 1.13
- [ 'addField', 'ipblocks', 'ipb_by_text', 'patch-ipb_by_text.sql' ],
+ [ 'ifNoActorTable', 'addField', 'ipblocks', 'ipb_by_text', 'patch-ipb_by_text.sql' ],
[ 'addTable', 'page_props', 'patch-page_props.sql' ],
[ 'addTable', 'updatelog', 'patch-updatelog.sql' ],
[ 'addTable', 'category', 'patch-category.sql' ],
[ 'doPopulateParentId' ],
[ 'checkBin', 'protected_titles', 'pt_title', 'patch-pt_title-encoding.sql', ],
[ 'doMaybeProfilingMemoryUpdate' ],
- [ 'doFilearchiveIndicesUpdate' ],
+ [ 'ifNoActorTable', 'doFilearchiveIndicesUpdate' ],
// 1.14
[ 'addField', 'site_stats', 'ss_active_users', 'patch-ss_active_users.sql' ],
// 1.16
[ 'addTable', 'user_properties', 'patch-user_properties.sql' ],
[ 'addTable', 'log_search', 'patch-log_search.sql' ],
- [ 'addField', 'logging', 'log_user_text', 'patch-log_user_text.sql' ],
+ [ 'ifNoActorTable', 'addField', 'logging', 'log_user_text', 'patch-log_user_text.sql' ],
# listed separately from the previous update because 1.16 was released without this update
- [ 'doLogUsertextPopulation' ],
+ [ 'ifNoActorTable', 'doLogUsertextPopulation' ],
[ 'doLogSearchPopulation' ],
[ 'addTable', 'l10n_cache', 'patch-l10n_cache.sql' ],
[ 'dropIndex', 'change_tag', 'ct_rc_id', 'patch-change_tag-indexes.sql' ],
// 1.23
[ 'addField', 'recentchanges', 'rc_source', 'patch-rc_source.sql' ],
- [ 'addIndex', 'logging', 'log_user_text_type_time',
+ [ 'ifNoActorTable', 'addIndex', 'logging', 'log_user_text_type_time',
'patch-logging_user_text_type_time_index.sql' ],
- [ 'addIndex', 'logging', 'log_user_text_time', 'patch-logging_user_text_time_index.sql' ],
+ [ 'ifNoActorTable', 'addIndex', 'logging', 'log_user_text_time',
+ 'patch-logging_user_text_time_index.sql' ],
[ 'addField', 'page', 'page_links_updated', 'patch-page_links_updated.sql' ],
[ 'addField', 'user', 'user_password_expires', 'patch-user_password_expire.sql' ],
[ 'doNonUniquePlTlIl' ],
[ 'addField', 'change_tag', 'ct_id', 'patch-change_tag-ct_id.sql' ],
[ 'modifyField', 'recentchanges', 'rc_ip', 'patch-rc_ip_modify.sql' ],
- [ 'addIndex', 'archive', 'usertext_timestamp', 'patch-rename-ar_usertext_timestamp.sql' ],
+ [ 'ifNoActorTable', 'addIndex', 'archive', 'usertext_timestamp',
+ 'patch-rename-ar_usertext_timestamp.sql' ],
// 1.29
[ 'addField', 'externallinks', 'el_index_60', 'patch-externallinks-el_index_60.sql' ],
[ 'dropIndex', 'user_groups', 'ug_user_group', 'patch-user_groups-primary-key.sql' ],
[ 'addField', 'user_groups', 'ug_expiry', 'patch-user_groups-ug_expiry.sql' ],
- [ 'addIndex', 'image', 'img_user_timestamp', 'patch-image-user-index-2.sql' ],
+ [ 'ifNoActorTable', 'addIndex', 'image', 'img_user_timestamp', 'patch-image-user-index-2.sql' ],
// 1.30
[ 'modifyField', 'image', 'img_media_type', 'patch-add-3d.sql' ],
[ 'dropTable', 'tag_summary' ],
[ 'dropField', 'protected_titles', 'pt_reason', 'patch-drop-comment-fields.sql' ],
[ 'modifyTable', 'job', 'patch-job-params-mediumblob.sql' ],
+
+ // 1.34
+ [ 'dropField', 'logging', 'log_user', 'patch-drop-user-fields.sql' ],
];
}
[ 'addPgField', 'image', 'img_sha1', "TEXT NOT NULL DEFAULT ''" ],
[ 'addPgField', 'ipblocks', 'ipb_allow_usertalk', 'SMALLINT NOT NULL DEFAULT 0' ],
[ 'addPgField', 'ipblocks', 'ipb_anon_only', 'SMALLINT NOT NULL DEFAULT 0' ],
- [ 'addPgField', 'ipblocks', 'ipb_by_text', "TEXT NOT NULL DEFAULT ''" ],
+ [ 'ifNoActorTable', 'addPgField', 'ipblocks', 'ipb_by_text', "TEXT NOT NULL DEFAULT ''" ],
[ 'addPgField', 'ipblocks', 'ipb_block_email', 'SMALLINT NOT NULL DEFAULT 0' ],
[ 'addPgField', 'ipblocks', 'ipb_create_account', 'SMALLINT NOT NULL DEFAULT 1' ],
[ 'addPgField', 'ipblocks', 'ipb_deleted', 'SMALLINT NOT NULL DEFAULT 0' ],
[ 'addPgField', 'revision', 'rev_content_format', 'TEXT' ],
[ 'addPgField', 'site_stats', 'ss_active_users', "INTEGER DEFAULT '-1'" ],
[ 'addPgField', 'user_newtalk', 'user_last_timestamp', 'TIMESTAMPTZ' ],
- [ 'addPgField', 'logging', 'log_user_text', "TEXT NOT NULL DEFAULT ''" ],
+ [ 'ifNoActorTable', 'addPgField', 'logging', 'log_user_text', "TEXT NOT NULL DEFAULT ''" ],
[ 'addPgField', 'logging', 'log_page', 'INTEGER' ],
[ 'addPgField', 'interwiki', 'iw_api', "TEXT NOT NULL DEFAULT ''" ],
[ 'addPgField', 'interwiki', 'iw_wikiid', "TEXT NOT NULL DEFAULT ''" ],
[ 'checkOiDeleted' ],
# New indexes
- [ 'addPgIndex', 'archive', 'archive_user_text', '(ar_user_text)' ],
+ [ 'ifNoActorTable', 'addPgIndex', 'archive', 'archive_user_text', '(ar_user_text)' ],
[ 'addPgIndex', 'image', 'img_sha1', '(img_sha1)' ],
[ 'addPgIndex', 'ipblocks', 'ipb_parent_block_id', '(ipb_parent_block_id)' ],
[ 'addPgIndex', 'oldimage', 'oi_sha1', '(oi_sha1)' ],
[ 'addPgIndex', 'watchlist', 'wl_user', '(wl_user)' ],
[ 'addPgIndex', 'watchlist', 'wl_user_notificationtimestamp',
'(wl_user, wl_notificationtimestamp)' ],
- [ 'addPgIndex', 'logging', 'logging_user_type_time',
+ [ 'ifNoActorTable', 'addPgIndex', 'logging', 'logging_user_type_time',
'(log_user, log_type, log_timestamp)' ],
[ 'addPgIndex', 'logging', 'logging_page_id_time', '(log_page,log_timestamp)' ],
[ 'addPgIndex', 'iwlinks', 'iwl_prefix_from_title', '(iwl_prefix, iwl_from, iwl_title)' ],
[ 'addPgIndex', 'job', 'job_cmd_token', '(job_cmd, job_token, job_random)' ],
[ 'addPgIndex', 'job', 'job_cmd_token_id', '(job_cmd, job_token, job_id)' ],
[ 'addPgIndex', 'filearchive', 'fa_sha1', '(fa_sha1)' ],
- [ 'addPgIndex', 'logging', 'logging_user_text_type_time',
+ [ 'ifNoActorTable', 'addPgIndex', 'logging', 'logging_user_text_type_time',
'(log_user_text, log_type, log_timestamp)' ],
- [ 'addPgIndex', 'logging', 'logging_user_text_time', '(log_user_text, log_timestamp)' ],
+ [ 'ifNoActorTable', 'addPgIndex', 'logging', 'logging_user_text_time',
+ '(log_user_text, log_timestamp)' ],
[ 'checkIndex', 'pagelink_unique', [
[ 'pl_from', 'int4_ops', 'btree', 0 ],
[ 'checkIwlPrefix' ],
# All FK columns should be deferred
- [ 'changeFkeyDeferrable', 'archive', 'ar_user', 'mwuser(user_id) ON DELETE SET NULL' ],
+ [ 'ifNoActorTable', 'changeFkeyDeferrable', 'archive', 'ar_user',
+ 'mwuser(user_id) ON DELETE SET NULL' ],
[ 'changeFkeyDeferrable', 'categorylinks', 'cl_from', 'page(page_id) ON DELETE CASCADE' ],
[ 'changeFkeyDeferrable', 'externallinks', 'el_from', 'page(page_id) ON DELETE CASCADE' ],
[ 'changeFkeyDeferrable', 'filearchive', 'fa_deleted_user',
'mwuser(user_id) ON DELETE SET NULL' ],
- [ 'changeFkeyDeferrable', 'filearchive', 'fa_user', 'mwuser(user_id) ON DELETE SET NULL' ],
- [ 'changeFkeyDeferrable', 'image', 'img_user', 'mwuser(user_id) ON DELETE SET NULL' ],
+ [ 'ifNoActorTable', 'changeFkeyDeferrable', 'filearchive', 'fa_user',
+ 'mwuser(user_id) ON DELETE SET NULL' ],
+ [ 'ifNoActorTable', 'changeFkeyDeferrable', 'image', 'img_user',
+ 'mwuser(user_id) ON DELETE SET NULL' ],
[ 'changeFkeyDeferrable', 'imagelinks', 'il_from', 'page(page_id) ON DELETE CASCADE' ],
- [ 'changeFkeyDeferrable', 'ipblocks', 'ipb_by', 'mwuser(user_id) ON DELETE CASCADE' ],
+ [ 'ifNoActorTable', 'changeFkeyDeferrable', 'ipblocks', 'ipb_by',
+ 'mwuser(user_id) ON DELETE CASCADE' ],
[ 'changeFkeyDeferrable', 'ipblocks', 'ipb_user', 'mwuser(user_id) ON DELETE SET NULL' ],
[ 'changeFkeyDeferrable', 'ipblocks', 'ipb_parent_block_id',
'ipblocks(ipb_id) ON DELETE SET NULL' ],
[ 'changeFkeyDeferrable', 'langlinks', 'll_from', 'page(page_id) ON DELETE CASCADE' ],
- [ 'changeFkeyDeferrable', 'logging', 'log_user', 'mwuser(user_id) ON DELETE SET NULL' ],
+ [ 'ifNoActorTable', 'changeFkeyDeferrable', 'logging', 'log_user',
+ 'mwuser(user_id) ON DELETE SET NULL' ],
[ 'changeFkeyDeferrable', 'oldimage', 'oi_name',
'image(img_name) ON DELETE CASCADE ON UPDATE CASCADE' ],
- [ 'changeFkeyDeferrable', 'oldimage', 'oi_user', 'mwuser(user_id) ON DELETE SET NULL' ],
+ [ 'ifNoActorTable', 'changeFkeyDeferrable', 'oldimage', 'oi_user',
+ 'mwuser(user_id) ON DELETE SET NULL' ],
[ 'changeFkeyDeferrable', 'pagelinks', 'pl_from', 'page(page_id) ON DELETE CASCADE' ],
[ 'changeFkeyDeferrable', 'page_props', 'pp_page', 'page (page_id) ON DELETE CASCADE' ],
[ 'changeFkeyDeferrable', 'page_restrictions', 'pr_page',
'page(page_id) ON DELETE CASCADE' ],
[ 'changeFkeyDeferrable', 'protected_titles', 'pt_user',
'mwuser(user_id) ON DELETE SET NULL' ],
- [ 'changeFkeyDeferrable', 'recentchanges', 'rc_user',
+ [ 'ifNoActorTable', 'changeFkeyDeferrable', 'recentchanges', 'rc_user',
'mwuser(user_id) ON DELETE SET NULL' ],
[ 'changeFkeyDeferrable', 'redirect', 'rd_from', 'page(page_id) ON DELETE CASCADE' ],
[ 'changeFkeyDeferrable', 'revision', 'rev_page', 'page (page_id) ON DELETE CASCADE' ],
[ 'dropDefault', 'logging', 'log_comment_id' ],
[ 'dropPgField', 'protected_titles', 'pt_reason' ],
[ 'dropDefault', 'protected_titles', 'pt_reason_id' ],
+
+ // 1.34
+ [ 'dropPgIndex', 'archive', 'archive_user_text' ],
+ [ 'dropPgField', 'archive', 'ar_user' ],
+ [ 'dropPgField', 'archive', 'ar_user_text' ],
+ [ 'dropDefault', 'archive', 'ar_actor' ],
+ [ 'dropPgField', 'ipblocks', 'ipb_by' ],
+ [ 'dropPgField', 'ipblocks', 'ipb_by_text' ],
+ [ 'dropDefault', 'ipblocks', 'ipb_by_actor' ],
+ [ 'dropPgField', 'image', 'img_user' ],
+ [ 'dropPgField', 'image', 'img_user_text' ],
+ [ 'dropDefault', 'image', 'img_actor' ],
+ [ 'dropPgField', 'oldimage', 'oi_user' ],
+ [ 'dropPgField', 'oldimage', 'oi_user_text' ],
+ [ 'dropDefault', 'oldimage', 'oi_actor' ],
+ [ 'dropPgField', 'filearchive', 'fa_user' ],
+ [ 'dropPgField', 'filearchive', 'fa_user_text' ],
+ [ 'dropDefault', 'filearchive', 'fa_actor' ],
+ [ 'dropPgField', 'recentchanges', 'rc_user' ],
+ [ 'dropPgField', 'recentchanges', 'rc_user_text' ],
+ [ 'dropDefault', 'recentchanges', 'rc_actor' ],
+ [ 'dropPgIndex', 'logging', 'logging_user_time' ],
+ [ 'dropPgIndex', 'logging', 'logging_user_type_time' ],
+ [ 'dropPgIndex', 'logging', 'logging_user_text_type_time' ],
+ [ 'dropPgIndex', 'logging', 'logging_user_text_time' ],
+ [ 'dropPgField', 'logging', 'log_user' ],
+ [ 'dropPgField', 'logging', 'log_user_text' ],
+ [ 'dropDefault', 'logging', 'log_actor' ],
];
}
// 1.16
[ 'addTable', 'user_properties', 'patch-user_properties.sql' ],
[ 'addTable', 'log_search', 'patch-log_search.sql' ],
- [ 'addField', 'logging', 'log_user_text', 'patch-log_user_text.sql' ],
+ [ 'ifNoActorTable', 'addField', 'logging', 'log_user_text', 'patch-log_user_text.sql' ],
# listed separately from the previous update because 1.16 was released without this update
- [ 'doLogUsertextPopulation' ],
+ [ 'ifNoActorTable', 'doLogUsertextPopulation' ],
[ 'doLogSearchPopulation' ],
[ 'addTable', 'l10n_cache', 'patch-l10n_cache.sql' ],
[ 'dropIndex', 'change_tag', 'ct_rc_id', 'patch-change_tag-indexes.sql' ],
// 1.23
[ 'addField', 'recentchanges', 'rc_source', 'patch-rc_source.sql' ],
- [ 'addIndex', 'logging', 'log_user_text_type_time',
+ [ 'ifNoActorTable', 'addIndex', 'logging', 'log_user_text_type_time',
'patch-logging_user_text_type_time_index.sql' ],
- [ 'addIndex', 'logging', 'log_user_text_time', 'patch-logging_user_text_time_index.sql' ],
+ [ 'ifNoActorTable', 'addIndex', 'logging', 'log_user_text_time',
+ 'patch-logging_user_text_time_index.sql' ],
[ 'addField', 'page', 'page_links_updated', 'patch-page_links_updated.sql' ],
[ 'addField', 'user', 'user_password_expires', 'patch-user_password_expire.sql' ],
// 1.29
[ 'addField', 'externallinks', 'el_index_60', 'patch-externallinks-el_index_60.sql' ],
[ 'addField', 'user_groups', 'ug_expiry', 'patch-user_groups-ug_expiry.sql' ],
- [ 'addIndex', 'image', 'img_user_timestamp', 'patch-image-user-index-2.sql' ],
+ [ 'ifNoActorTable', 'addIndex', 'image', 'img_user_timestamp', 'patch-image-user-index-2.sql' ],
// 1.30
[ 'modifyField', 'image', 'img_media_type', 'patch-add-3d.sql' ],
[ 'dropField', 'recentchanges', 'rc_comment', 'patch-recentchanges-drop-rc_comment.sql' ],
[ 'dropField', 'logging', 'log_comment', 'patch-logging-drop-log_comment.sql' ],
[ 'dropField', 'protected_titles', 'pt_reason', 'patch-protected_titles-drop-pt_reason.sql' ],
+
+ // 1.34
+ [ 'dropField', 'archive', 'ar_user', 'patch-archive-drop-ar_user.sql' ],
+ [ 'dropField', 'ipblocks', 'ipb_by', 'patch-ipblocks-drop-ipb_by.sql' ],
+ [ 'dropField', 'image', 'img_user', 'patch-image-drop-img_user.sql' ],
+ [ 'dropField', 'oldimage', 'oi_user', 'patch-oldimage-drop-oi_user.sql' ],
+ [ 'dropField', 'filearchive', 'fa_user', 'patch-filearchive-drop-fa_user.sql' ],
+ [ 'dropField', 'recentchanges', 'rc_user', 'patch-recentchanges-drop-rc_user.sql' ],
+ [ 'dropField', 'logging', 'log_user', 'patch-logging-drop-log_user.sql' ],
];
}
$this->getFieldsetEnd()
);
- $skins = $this->parent->findExtensions( 'skins' );
+ $skins = $this->parent->findExtensions( 'skins' )->value;
+ '@phan-var array[] $skins';
$skinHtml = $this->getFieldsetStart( 'config-skins' );
$skinNames = array_map( 'strtolower', array_keys( $skins ) );
$this->getFieldsetEnd();
$this->addHTML( $skinHtml );
- $extensions = $this->parent->findExtensions();
+ $extensions = $this->parent->findExtensions()->value;
+ '@phan-var array[] $extensions';
$dependencyMap = [];
if ( $extensions ) {
return null;
}
+ /**
+ * @param string $name
+ * @param array $screenshots
+ */
private function makeScreenshotsLink( $name, $screenshots ) {
global $wgLang;
if ( count( $screenshots ) > 1 ) {
$links = [];
$counter = 1;
+
foreach ( $screenshots as $shot ) {
$links[] = Html::element(
'a',
* @return bool
*/
public function submitSkins() {
- $skins = array_keys( $this->parent->findExtensions( 'skins' ) );
+ $skins = array_keys( $this->parent->findExtensions( 'skins' )->value );
$this->parent->setVar( '_Skins', $skins );
if ( $skins ) {
$this->setVar( 'wgRightsIcon', '' );
}
- $skinsAvailable = array_keys( $this->parent->findExtensions( 'skins' ) );
+ $skinsAvailable = array_keys( $this->parent->findExtensions( 'skins' )->value );
$skinsToInstall = [];
foreach ( $skinsAvailable as $skin ) {
$this->parent->setVarsFromRequest( [ "skin-$skin" ] );
$retVal = false;
}
- $extsAvailable = array_keys( $this->parent->findExtensions() );
+ $extsAvailable = array_keys( $this->parent->findExtensions()->value );
$extsToInstall = [];
foreach ( $extsAvailable as $ext ) {
$this->parent->setVarsFromRequest( [ "ext-$ext" ] );
"config-restart": "Si, reinitia lo",
"config-welcome": "=== Verificationes del ambiente ===\nVerificationes de base essera ora exequite pro determinar si iste ambiente es apte pro le installation de MediaWiki.\nNon oblida de includer iste information si tu cerca adjuta pro completar le installation.",
"config-welcome-section-copyright": "=== Copyright and Terms ===\n\n$1\n\nIste programma es software libere; vos pote redistribuer lo e/o modificar lo sub le conditiones del Licentia Public General de GNU publicate per le Free Software Foundation; version 2 del Licentia, o (a vostre option) qualcunque version posterior.\n\nIste programma es distribuite in le sperantia que illo sia utile, ma '''sin garantia''', sin mesmo le implicite garantia de '''commercialisation''' o '''aptitude pro un proposito particular'''.\nVide le Licentia Public General de GNU pro plus detalios.\n\nVos deberea haber recipite [$2 un exemplar del Licentia Public General de GNU] con iste programma; si non, scribe al Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, o [https://www.gnu.org/copyleft/gpl.html lege lo in linea].",
- "config-sidebar": "* [https://www.mediawiki.org Pagina principal de MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Guida pro usatores]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Guida pro administratores]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]\n----\n* <doclink href=Readme>Lege me</doclink>\n* <doclink href=ReleaseNotes>Notas de iste version</doclink>\n* <doclink href=Copying>Conditiones de copia</doclink>\n* <doclink href=UpgradeDoc>Actualisation</doclink>",
+ "config-sidebar": "* [https://www.mediawiki.org Pagina principal de MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Guida pro usatores]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Guida pro administratores]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]",
+ "config-sidebar-readme": "Lege me",
+ "config-sidebar-relnotes": "Notas de iste version",
+ "config-sidebar-license": "Conditiones de copia",
+ "config-sidebar-upgrade": "Actualisation",
"config-env-good": "Le ambiente ha essite verificate.\nTu pote installar MediaWiki.",
"config-env-bad": "Le ambiente ha essite verificate.\nTu non pote installar MediaWiki.",
"config-env-php": "PHP $1 es installate.",
"config-uploads-not-safe": "'''Aviso:''' Le directorio predefinite pro files incargate <code>$1</code> es vulnerabile al execution arbitrari de scripts.\nBen que MediaWiki verifica tote le files incargate contra le menacias de securitate, il es altemente recommendate [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security remediar iste vulnerabilitate de securitate] ante de activar le incargamento de files.",
"config-no-cli-uploads-check": "'''Attention:''' Le directorio predefinite pro files incargate (<code>$1</code>) non es verificate contra le vulnerabilitate\nal execution arbitrari de scripts durante le installation de CLI.",
"config-brokenlibxml": "Vostre systema ha un combination de versiones de PHP e libxml2 que es defectuose e pote causar corruption celate de datos in MediaWiki e altere applicationes web.\nActualisa a libxml2 2.7.3 o plus recente ([https://bugs.php.net/bug.php?id=45996 problema reportate presso PHP]).\nInstallation abortate.",
- "config-suhosin-max-value-length": "Suhosin es installate e limita parametro <code>length</code> de GET a $1 bytes.\nLe componente ResourceLoader de MediaWiki va contornar iste limite, ma isto prejudicara le rendimento.\nSi possibile, tu deberea mitter <code>suhosin.get.max_value_length</code> a 1024 o superior in <code>php.ini</code>, e mitter <code>$wgResourceLoaderMaxQueryLength</code> al mesme valor in <code>LocalSettings.php</code>.",
+ "config-suhosin-max-value-length": "Suhosin es installate e limita parametro <code>length</code> de GET a $1 bytes.\nMediaWiki require que <code>suhosin.get.max_value_length</code> sia al minus $2. Disactiva iste parametro, o augmenta iste valor a $3 in <code>php.ini</code>.",
"config-using-32bit": "<strong>Attention:</strong> tu systema pare operar con integres de 32 bits. Isto [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:32-bit non es recommendate].",
"config-db-type": "Typo de base de datos:",
"config-db-host": "Servitor de base de datos:",
"config-uploads-not-safe": "<strong>警告:</strong> アップロードの既定ディレクトリ <code>$1</code> に、任意のスクリプト実行に関する脆弱性があります。\nMediaWiki はアップロードされたファイルのセキュリティ上の脅威を確認しますが、アップロードを有効化する前に、[https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security このセキュリティ上の脆弱性を解決する]ことを強く推奨します。",
"config-no-cli-uploads-check": "<strong>警告:</strong> アップロード用のデフォルトディレクトリ (<code>$1</code>) が、CLIでのインストール中に任意のスクリプト実行の脆弱性チェックを受けていません。",
"config-brokenlibxml": "このシステムで使われているPHPとlibxml2のバージョンのこの組み合わせにはバグがあります。具体的には、MediaWikiやその他のウェブアプリケーションでhiddenデータが破損する可能性があります。\nlibxml2を2.7.3以降のバージョンにアップグレードしてください([https://bugs.php.net/bug.php?id=45996 PHPでのバグ情報])。\nインストールを終了します。",
- "config-suhosin-max-value-length": "Suhosin がインストールされており、GET パラメーターの <code>length</code> を $1 バイトに制限しています。\nMediaWiki の ResourceLoader コンポーネントはこの制限を回避しますが、パフォーマンスは低下します。\n可能な限り、<code>php.ini</code> で <code>suhosin.get.max_value_length</code> を 1024 以上に設定し、同じ値を <code>LocalSettings.php</code> 内で <code>$wgResourceLoaderMaxQueryLength</code> に設定してください。",
+ "config-suhosin-max-value-length": "Suhosin がインストールされており、GET パラメーターの <code>length</code> を $1 バイトに制限しています。(訳注:\nMediaWiki の ResourceLoader コンポーネントはこの制限を回避しますが、パフォーマンスは低下します。)\n可能な限り、 <code>suhosin.get.max_value_length</code> を $2 以上に設定します。これを無効に変更するか、<code>php.ini</code> で $3 に増加してください。",
"config-using-32bit": "<strong>警告:</strong>システムが32ビットで動作しているようです。 これは[https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:32-bit 非推奨]です。",
"config-db-type": "データベースの種類:",
"config-db-host": "データベースのホスト:",
"Sethakill",
"Peter Bowman",
"Ankam",
- "Railfail536"
+ "Railfail536",
+ "Rail"
]
},
"config-desc": "Instalator MediaWiki",
$title = $page->getTitle();
// Get the latest ID since acquirePageLock() in runForTitle() flushed the transaction.
// This is used to detect edits/moves after loadPageData() but before the scope lock.
- // The works around the chicken/egg problem of determining the scope lock key name.
- $latest = $title->getLatestRevID( Title::GAID_FOR_UPDATE );
+ // The works around the chicken/egg problem of determining the scope lock key name
+ $latest = $title->getLatestRevID( Title::READ_LATEST );
$triggeringRevisionId = $this->params['triggeringRevisionId'] ?? null;
if ( $triggeringRevisionId && $triggeringRevisionId !== $latest ) {
$contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
$dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
AtEase::suppressWarnings();
- if ( is_dir( $dir ) ) {
- rmdir( $dir ); // remove directory if empty
- }
+ rmdir( $dir ); // remove directory if empty
AtEase::restoreWarnings();
return $status;
// Validate and sanitize the relative path (backend-specific)
$relPath = $this->resolveContainerPath( $shortCont, $relPath );
if ( $relPath !== null ) {
- // Prepend any wiki ID prefix to the container name
+ // Prepend any domain ID prefix to the container name
$container = $this->fullContainerName( $shortCont );
if ( self::isValidContainerName( $container ) ) {
// Validate and sanitize the container name (backend-specific)
}
/**
- * Get the full container name, including the wiki ID prefix
+ * Get the full container name, including the domain ID prefix
*
* @param string $container
* @return string
*/
use Wikimedia\AtEase\AtEase;
+use Wikimedia\Timestamp\ConvertibleTimestamp;
/**
* Simulation of a backend storage in memory.
$this->files[$dst] = [
'data' => $params['content'],
- 'mtime' => wfTimestamp( TS_MW, time() )
+ 'mtime' => ConvertibleTimestamp::convert( TS_MW, time() )
];
return $status;
$this->files[$dst] = [
'data' => $data,
- 'mtime' => wfTimestamp( TS_MW, time() )
+ 'mtime' => ConvertibleTimestamp::convert( TS_MW, time() )
];
return $status;
$this->files[$dst] = [
'data' => $this->files[$src]['data'],
- 'mtime' => wfTimestamp( TS_MW, time() )
+ 'mtime' => ConvertibleTimestamp::convert( TS_MW, time() )
];
return $status;
public $backend;
/** @var array */
public $resourcesToClose = [];
-
- public $call; // string; name that identifies the function called
+ /** @var callable name that identifies the function called */
+ public $call;
/**
* Close all open file handles
*/
use Wikimedia\AtEase\AtEase;
+use Wikimedia\Timestamp\ConvertibleTimestamp;
/**
* Class representing a non-directory file on the file system
* @return int|bool
*/
public function getSize() {
- return filesize( $this->path );
+ AtEase::suppressWarnings();
+ $size = filesize( $this->path );
+ AtEase::restoreWarnings();
+
+ return $size;
}
/**
$timestamp = filemtime( $this->path );
AtEase::restoreWarnings();
if ( $timestamp !== false ) {
- $timestamp = wfTimestamp( TS_MW, $timestamp );
+ $timestamp = ConvertibleTimestamp::convert( TS_MW, $timestamp );
}
return $timestamp;
if ( !isset( $options['serializer'] ) || $options['serializer'] === 'php' ) {
$this->serializer = Redis::SERIALIZER_PHP;
} elseif ( $options['serializer'] === 'igbinary' ) {
+ if ( !defined( 'Redis::SERIALIZER_IGBINARY' ) ) {
+ throw new InvalidArgumentException(
+ __CLASS__ . ': configured serializer "igbinary" not available' );
+ }
$this->serializer = Redis::SERIALIZER_IGBINARY;
} elseif ( $options['serializer'] === 'none' ) {
$this->serializer = Redis::SERIALIZER_NONE;
* @throws MWException
*/
public function insert( IDatabase $dbw = null ) {
- global $wgActorTableSchemaMigrationStage;
-
$dbw = $dbw ?: wfGetDB( DB_MASTER );
if ( $this->timestamp === null ) {
$params = $this->getParameters();
$relations = $this->relations;
- // Ensure actor relations are set
- if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) &&
- empty( $relations['target_author_actor'] )
- ) {
- $actorIds = [];
- if ( !empty( $relations['target_author_id'] ) ) {
- foreach ( $relations['target_author_id'] as $id ) {
- $actorIds[] = User::newFromId( $id )->getActorId( $dbw );
- }
- }
- if ( !empty( $relations['target_author_ip'] ) ) {
- foreach ( $relations['target_author_ip'] as $ip ) {
- $actorIds[] = User::newFromName( $ip, false )->getActorId( $dbw );
- }
- }
- if ( $actorIds ) {
- $relations['target_author_actor'] = $actorIds;
- $params['authorActors'] = $actorIds;
- }
- }
- if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ) {
- unset( $relations['target_author_id'], $relations['target_author_ip'] );
- unset( $params['authorIds'], $params['authorIPs'] );
- }
-
// Additional fields for which there's no space in the database table schema
$revId = $this->getAssociatedRevId();
if ( $revId ) {
// Try using the replica DB first, then try the master
$rev = $this->mTitle->getFirstRevision();
if ( !$rev ) {
- $rev = $this->mTitle->getFirstRevision( Title::GAID_FOR_UPDATE );
+ $rev = $this->mTitle->getFirstRevision( Title::READ_LATEST );
}
return $rev;
}
*/
protected function archiveRevisions( $dbw, $id, $suppress ) {
global $wgContentHandlerUseDB, $wgMultiContentRevisionSchemaMigrationStage,
- $wgActorTableSchemaMigrationStage, $wgDeleteRevisionsBatchSize;
+ $wgDeleteRevisionsBatchSize;
// Given the lock above, we can be confident in the title and page ID values
$namespace = $this->getTitle()->getNamespace();
$dbw->delete( 'revision', [ 'rev_id' => $revids ], __METHOD__ );
$dbw->delete( 'revision_comment_temp', [ 'revcomment_rev' => $revids ], __METHOD__ );
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
- $dbw->delete( 'revision_actor_temp', [ 'revactor_rev' => $revids ], __METHOD__ );
- }
+ $dbw->delete( 'revision_actor_temp', [ 'revactor_rev' => $revids ], __METHOD__ );
// Also delete records from ip_changes as applicable.
if ( count( $ipRevIds ) > 0 ) {
* @since 1.34
*/
public static $constructorOptions = [
+ 'AllowRequiringEmailForResets',
'AllowUserCss',
'AllowUserCssPrefs',
'AllowUserJs',
}
}
+ if ( $this->options->get( 'AllowRequiringEmailForResets' ) ) {
+ $defaultPreferences['requireemail'] = [
+ 'type' => 'toggle',
+ 'label-message' => 'tog-requireemail',
+ 'help-message' => 'prefs-help-requireemail',
+ 'section' => 'personal/email',
+ 'disabled' => $disableEmailPrefs,
+ ];
+ }
+
if ( $this->options->get( 'EnableUserEmail' ) && $user->isAllowed( 'sendemail' ) ) {
$defaultPreferences['disablemail'] = [
'id' => 'wpAllowEmail',
$errorText = implode( "\n\n", $this->errors );
$errorResponse = self::makeComment( $errorText );
if ( $context->shouldIncludeScripts() ) {
- $errorResponse .= 'if (window.console && console.error) {'
- . Xml::encodeJsCall( 'console.error', [ $errorText ] )
- . "}\n";
+ $errorResponse .= 'if (window.console && console.error) { console.error('
+ . self::encodeJsonForScript( $errorText )
+ . "); }\n";
}
// Prepend error info to the response
/**
* Returns JS code which, when called, will register a given list of messages.
*
- * @param mixed $messages Either an associative array mapping message key to value, or a
- * JSON-encoded message blob containing the same data, wrapped in an XmlJsCode object.
+ * @param mixed $messages Associative array mapping message key to value.
* @return string JavaScript code
*/
public static function makeMessageSetScript( $messages ) {
* @internal
* @since 1.32
* @param mixed $data
- * @return string JSON
+ * @return string|false JSON string, false on error
*/
public static function encodeJsonForScript( $data ) {
// Keep output as small as possible by disabling needless escape modes
* @throws Exception
*/
public static function makeConfigSetScript( array $configuration ) {
- $js = Xml::encodeJsCall(
- 'mw.config.set',
- [ $configuration ],
- self::inDebugMode()
- );
- if ( $js === false ) {
+ $json = self::encodeJsonForScript( $configuration );
+ if ( $json === false ) {
$e = new Exception(
'JSON serialization of config data failed. ' .
'This usually means the config data is not valid UTF-8.'
);
MWExceptionHandler::logException( $e );
- $js = Xml::encodeJsCall( 'mw.log.error', [ $e->__toString() ] );
+ return 'mw.log.error(' . self::encodeJsonForScript( $e->__toString() ) . ');';
}
- return $js;
+ return "mw.config.set($json);";
}
/**
] );
} else {
$chunk = ResourceLoader::makeInlineScript(
- Xml::encodeJsCall( 'mw.loader.load', [ $url ] ),
+ 'mw.loader.load(' . ResourceLoader::encodeJsonForScript( $url ) . ');',
$nonce
);
}
* @return string JavaScript code
*/
public function getScript( ResourceLoaderContext $context ) {
- $fileScript = parent::getScript( $context );
- $langDataScript = Xml::encodeJsCall(
- 'mw.language.setData',
- [
- $context->getLanguage(),
- $this->getData( $context )
- ],
- ResourceLoader::inDebugMode()
- );
- return $fileScript . $langDataScript;
+ return parent::getScript( $context )
+ . 'mw.language.setData('
+ . ResourceLoader::encodeJsonForScript( $context->getLanguage() ) . ','
+ . ResourceLoader::encodeJsonForScript( $this->getData( $context ) )
+ . ');';
}
/**
* @return string JavaScript code
*/
public function getScript( ResourceLoaderContext $context ) {
- return Xml::encodeJsCall(
- 'mw.user.options.set',
- [ User::getDefaultOptions() ],
- ResourceLoader::inDebugMode()
- );
+ return 'mw.user.options.set('
+ . ResourceLoader::encodeJsonForScript( User::getDefaultOptions() )
+ . ');';
}
}
*/
public function getScript( ResourceLoaderContext $context ) {
// Use FILTER_NOMIN annotation to prevent needless minification and caching (T84960).
- return ResourceLoader::FILTER_NOMIN . Xml::encodeJsCall(
- 'mw.user.options.set',
- [ $context->getUserObj()->getOptions( User::GETOPTIONS_EXCLUDE_DEFAULTS ) ],
- ResourceLoader::inDebugMode()
- );
+ return ResourceLoader::FILTER_NOMIN
+ . 'mw.user.options.set('
+ . ResourceLoader::encodeJsonForScript(
+ $context->getUserObj()->getOptions( User::GETOPTIONS_EXCLUDE_DEFAULTS )
+ )
+ . ');';
}
/**
*/
public function getScript( ResourceLoaderContext $context ) {
// Use FILTER_NOMIN annotation to prevent needless minification and caching (T84960).
- return ResourceLoader::FILTER_NOMIN . Xml::encodeJsCall(
- 'mw.user.tokens.set',
- [ $this->contextUserTokens( $context ) ],
- ResourceLoader::inDebugMode()
- );
+ return ResourceLoader::FILTER_NOMIN
+ . 'mw.user.tokens.set('
+ . ResourceLoader::encodeJsonForScript( $this->contextUserTokens( $context ) )
+ . ');';
}
/**
* @since 1.23 Added 'perItemStatus' param
*/
public function setVisibility( array $params ) {
- global $wgActorTableSchemaMigrationStage;
-
$status = Status::newGood();
$bitPars = $params['value'];
$missing = array_flip( $this->ids );
$this->clearFileOps();
$idsForLog = [];
- $authorIds = $authorIPs = $authorActors = [];
+ $authorActors = [];
if ( $perItemStatus ) {
$status->itemStatuses = [];
$virtualOldBits |= $removedBits;
$status->successCount++;
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
- if ( $item->getAuthorId() > 0 ) {
- $authorIds[] = $item->getAuthorId();
- } elseif ( IP::isIPAddress( $item->getAuthorName() ) ) {
- $authorIPs[] = $item->getAuthorName();
- }
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
- $actorId = $item->getAuthorActor();
- // During migration, the actor field might be empty. If so, populate
- // it here.
- if ( !$actorId ) {
- if ( $item->getAuthorId() > 0 ) {
- $user = User::newFromId( $item->getAuthorId() );
- } else {
- $user = User::newFromName( $item->getAuthorName(), false );
- }
- $actorId = $user->getActorId( $dbw );
- }
- $authorActors[] = $actorId;
- }
- } elseif ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
- $authorActors[] = $item->getAuthorActor();
- }
+ $authorActors[] = $item->getAuthorActor();
// Save the old and new bits in $visibilityChangeMap for
// later use.
// Log it
$authorFields = [];
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
- $authorFields['authorIds'] = $authorIds;
- $authorFields['authorIPs'] = $authorIPs;
- }
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
- $authorFields['authorActors'] = $authorActors;
- }
+ $authorFields['authorActors'] = $authorActors;
$this->updateLog(
$logType,
[
* title: The target title
* ids: The ID list
* comment: The log comment
- * authorIds: The array of the user IDs of the offenders
- * authorIPs: The array of the IP/anon user offenders
* authorActors: The array of the actor IDs of the offenders
* tags: The array of change tags to apply to the log entry
* @throws MWException
$relations = [
$field => $params['ids'],
];
- if ( isset( $params['authorIds'] ) ) {
- $relations += [
- 'target_author_id' => $params['authorIds'],
- 'target_author_ip' => $params['authorIPs'],
- ];
- }
if ( isset( $params['authorActors'] ) ) {
$relations += [
'target_author_actor' => $params['authorActors'],
* @return bool True on success, false on failure (e.g. invalid user ID)
*/
private static function setUsernameBitfields( $name, $userId, $op, IDatabase $dbw = null ) {
- global $wgActorTableSchemaMigrationStage;
-
if ( !$userId || ( $op !== '|' && $op !== '&' ) ) {
return false; // sanity check
}
$userTitle = Title::makeTitleSafe( NS_USER, $name );
$userDbKey = $userTitle->getDBkey();
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
+ $actorId = $dbw->selectField( 'actor', 'actor_id', [ 'actor_name' => $name ], __METHOD__ );
+ if ( $actorId ) {
# Hide name from live edits
- $dbw->update(
- 'revision',
- [ self::buildSetBitDeletedField( 'rev_deleted', $op, $delUser, $dbw ) ],
- [ 'rev_user' => $userId ],
- __METHOD__ );
+ $ids = $dbw->selectFieldValues(
+ 'revision_actor_temp', 'revactor_rev', [ 'revactor_actor' => $actorId ], __METHOD__
+ );
+ if ( $ids ) {
+ $dbw->update(
+ 'revision',
+ [ self::buildSetBitDeletedField( 'rev_deleted', $op, $delUser, $dbw ) ],
+ [ 'rev_id' => $ids ],
+ __METHOD__
+ );
+ }
# Hide name from deleted edits
$dbw->update(
'archive',
[ self::buildSetBitDeletedField( 'ar_deleted', $op, $delUser, $dbw ) ],
- [ 'ar_user_text' => $name ],
+ [ 'ar_actor' => $actorId ],
__METHOD__
);
$dbw->update(
'logging',
[ self::buildSetBitDeletedField( 'log_deleted', $op, $delUser, $dbw ) ],
- [ 'log_user' => $userId, 'log_type != ' . $dbw->addQuotes( 'suppress' ) ],
+ [ 'log_actor' => $actorId, 'log_type != ' . $dbw->addQuotes( 'suppress' ) ],
__METHOD__
);
$dbw->update(
'recentchanges',
[ self::buildSetBitDeletedField( 'rc_deleted', $op, $delUser, $dbw ) ],
- [ 'rc_user_text' => $name ],
+ [ 'rc_actor' => $actorId ],
__METHOD__
);
$dbw->update(
'oldimage',
[ self::buildSetBitDeletedField( 'oi_deleted', $op, $delUser, $dbw ) ],
- [ 'oi_user_text' => $name ],
+ [ 'oi_actor' => $actorId ],
__METHOD__
);
$dbw->update(
'filearchive',
[ self::buildSetBitDeletedField( 'fa_deleted', $op, $delUser, $dbw ) ],
- [ 'fa_user_text' => $name ],
+ [ 'fa_actor' => $actorId ],
__METHOD__
);
}
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
- $actorId = $dbw->selectField( 'actor', 'actor_id', [ 'actor_name' => $name ], __METHOD__ );
- if ( $actorId ) {
- # Hide name from live edits
- $ids = $dbw->selectFieldValues(
- 'revision_actor_temp', 'revactor_rev', [ 'revactor_actor' => $actorId ], __METHOD__
- );
- if ( $ids ) {
- $dbw->update(
- 'revision',
- [ self::buildSetBitDeletedField( 'rev_deleted', $op, $delUser, $dbw ) ],
- [ 'rev_id' => $ids ],
- __METHOD__
- );
- }
-
- # Hide name from deleted edits
- $dbw->update(
- 'archive',
- [ self::buildSetBitDeletedField( 'ar_deleted', $op, $delUser, $dbw ) ],
- [ 'ar_actor' => $actorId ],
- __METHOD__
- );
-
- # Hide name from logs
- $dbw->update(
- 'logging',
- [ self::buildSetBitDeletedField( 'log_deleted', $op, $delUser, $dbw ) ],
- [ 'log_actor' => $actorId, 'log_type != ' . $dbw->addQuotes( 'suppress' ) ],
- __METHOD__
- );
-
- # Hide name from RC
- $dbw->update(
- 'recentchanges',
- [ self::buildSetBitDeletedField( 'rc_deleted', $op, $delUser, $dbw ) ],
- [ 'rc_actor' => $actorId ],
- __METHOD__
- );
-
- # Hide name from live images
- $dbw->update(
- 'oldimage',
- [ self::buildSetBitDeletedField( 'oi_deleted', $op, $delUser, $dbw ) ],
- [ 'oi_actor' => $actorId ],
- __METHOD__
- );
-
- # Hide name from deleted images
- $dbw->update(
- 'filearchive',
- [ self::buildSetBitDeletedField( 'fa_deleted', $op, $delUser, $dbw ) ],
- [ 'fa_actor' => $actorId ],
- __METHOD__
- );
- }
- }
-
# Hide log entries pointing to the user page
$dbw->update(
'logging',
}
if ( count( $retInfos ) > 1 ) {
- $ex = new \OverflowException(
+ throw new SessionOverflowException(
+ $retInfos,
'Multiple sessions for this request tied for top priority: ' . implode( ', ', $retInfos )
);
- $ex->sessionInfos = $retInfos;
- throw $ex;
}
return $retInfos ? $retInfos[0] : null;
--- /dev/null
+<?php
+
+namespace MediaWiki\Session;
+
+/**
+ * OverflowException specific to the SessionManager, used when the request had multiple possible
+ * sessions tied for top priority.
+ *
+ * @since 1.34
+ */
+class SessionOverflowException extends \OverflowException {
+ /** @var SessionInfo[] */
+ private $sessionInfos;
+
+ /**
+ * @param SessionInfo[] $sessionInfos Must have at least two elements
+ * @param string $msg
+ * @throws \InvalidArgumentException If $sessionInfos has less than 2 elements
+ */
+ function __construct( array $sessionInfos, $msg ) {
+ if ( count( $sessionInfos ) < 2 ) {
+ throw new \InvalidArgumentException( 'Expected at least two SessionInfo objects.' );
+ }
+ parent::__construct( $msg );
+ $this->sessionInfos = $sessionInfos;
+ }
+
+ /**
+ * @return SessionInfo[]
+ */
+ public function getSessionInfos() : array {
+ return $this->sessionInfos;
+ }
+}
/**
* Load basic request parameters for this Special page.
- * @param string $subPage
*/
- private function loadRequestParameters( $subPage ) {
+ private function loadRequestParameters() {
if ( $this->mLoadedRequest ) {
return;
}
$request = $this->getRequest();
$this->mPosted = $request->wasPosted();
- $this->mIsReturn = $subPage === 'return';
$this->mAction = $request->getVal( 'action' );
$this->mFromHTTP = $request->getBool( 'fromhttp', false )
|| $request->getBool( 'wpFromhttp', false );
protected function load( $subPage ) {
global $wgSecureLogin;
- $this->loadRequestParameters( $subPage );
+ $this->loadRequestParameters();
if ( $this->mLoaded ) {
return;
}
protected function beforeExecute( $subPage ) {
// finish initializing the class before processing the request - T135924
- $this->loadRequestParameters( $subPage );
+ $this->loadRequestParameters();
return parent::beforeExecute( $subPage );
}
if ( $qp === null ) {
// QueryPage subclass, Special page name
$qp = [
- [ AncientPagesPage::class, 'Ancientpages' ],
- [ BrokenRedirectsPage::class, 'BrokenRedirects' ],
- [ DeadendPagesPage::class, 'Deadendpages' ],
- [ DoubleRedirectsPage::class, 'DoubleRedirects' ],
- [ FileDuplicateSearchPage::class, 'FileDuplicateSearch' ],
- [ ListDuplicatedFilesPage::class, 'ListDuplicatedFiles' ],
- [ LinkSearchPage::class, 'LinkSearch' ],
- [ ListredirectsPage::class, 'Listredirects' ],
- [ LonelyPagesPage::class, 'Lonelypages' ],
- [ LongPagesPage::class, 'Longpages' ],
- [ MediaStatisticsPage::class, 'MediaStatistics' ],
- [ MIMEsearchPage::class, 'MIMEsearch' ],
- [ MostcategoriesPage::class, 'Mostcategories' ],
+ [ SpecialAncientPages::class, 'Ancientpages' ],
+ [ SpecialBrokenRedirects::class, 'BrokenRedirects' ],
+ [ SpecialDeadendPages::class, 'Deadendpages' ],
+ [ SpecialDoubleRedirects::class, 'DoubleRedirects' ],
+ [ SpecialFileDuplicateSearch::class, 'FileDuplicateSearch' ],
+ [ SpecialListDuplicatedFiles::class, 'ListDuplicatedFiles' ],
+ [ SpecialLinkSearch::class, 'LinkSearch' ],
+ [ SpecialListRedirects::class, 'Listredirects' ],
+ [ SpecialLonelyPages::class, 'Lonelypages' ],
+ [ SpecialLongPages::class, 'Longpages' ],
+ [ SpecialMediaStatistics::class, 'MediaStatistics' ],
+ [ SpecialMIMESearch::class, 'MIMEsearch' ],
+ [ SpecialMostCategories::class, 'Mostcategories' ],
[ MostimagesPage::class, 'Mostimages' ],
- [ MostinterwikisPage::class, 'Mostinterwikis' ],
- [ MostlinkedCategoriesPage::class, 'Mostlinkedcategories' ],
- [ MostlinkedTemplatesPage::class, 'Mostlinkedtemplates' ],
- [ MostlinkedPage::class, 'Mostlinked' ],
- [ MostrevisionsPage::class, 'Mostrevisions' ],
- [ FewestrevisionsPage::class, 'Fewestrevisions' ],
- [ ShortPagesPage::class, 'Shortpages' ],
- [ UncategorizedCategoriesPage::class, 'Uncategorizedcategories' ],
- [ UncategorizedPagesPage::class, 'Uncategorizedpages' ],
- [ UncategorizedImagesPage::class, 'Uncategorizedimages' ],
- [ UncategorizedTemplatesPage::class, 'Uncategorizedtemplates' ],
- [ UnusedCategoriesPage::class, 'Unusedcategories' ],
- [ UnusedimagesPage::class, 'Unusedimages' ],
- [ WantedCategoriesPage::class, 'Wantedcategories' ],
+ [ SpecialMostInterwikis::class, 'Mostinterwikis' ],
+ [ SpecialMostLinkedCategories::class, 'Mostlinkedcategories' ],
+ [ SpecialMostLinkedTemplates::class, 'Mostlinkedtemplates' ],
+ [ SpecialMostLinked::class, 'Mostlinked' ],
+ [ SpecialMostRevisions::class, 'Mostrevisions' ],
+ [ SpecialFewestRevisions::class, 'Fewestrevisions' ],
+ [ SpecialShortPages::class, 'Shortpages' ],
+ [ SpecialUncategorizedCategories::class, 'Uncategorizedcategories' ],
+ [ SpecialUncategorizedPages::class, 'Uncategorizedpages' ],
+ [ SpecialUncategorizedImages::class, 'Uncategorizedimages' ],
+ [ SpecialUncategorizedTemplates::class, 'Uncategorizedtemplates' ],
+ [ SpecialUnusedCategories::class, 'Unusedcategories' ],
+ [ SpecialUnusedImages::class, 'Unusedimages' ],
+ [ SpecialWantedCategories::class, 'Wantedcategories' ],
[ WantedFilesPage::class, 'Wantedfiles' ],
[ WantedPagesPage::class, 'Wantedpages' ],
- [ WantedTemplatesPage::class, 'Wantedtemplates' ],
- [ UnwatchedpagesPage::class, 'Unwatchedpages' ],
- [ UnusedtemplatesPage::class, 'Unusedtemplates' ],
- [ WithoutInterwikiPage::class, 'Withoutinterwiki' ],
+ [ SpecialWantedTemplates::class, 'Wantedtemplates' ],
+ [ SpecialUnwatchedPages::class, 'Unwatchedpages' ],
+ [ SpecialUnusedTemplates::class, 'Unusedtemplates' ],
+ [ SpecialWithoutInterwiki::class, 'Withoutinterwiki' ],
];
Hooks::run( 'wgQueryPages', [ &$qp ] );
}
*/
private static $coreList = [
// Maintenance Reports
- 'BrokenRedirects' => \BrokenRedirectsPage::class,
- 'Deadendpages' => \DeadendPagesPage::class,
- 'DoubleRedirects' => \DoubleRedirectsPage::class,
- 'Longpages' => \LongPagesPage::class,
- 'Ancientpages' => \AncientPagesPage::class,
- 'Lonelypages' => \LonelyPagesPage::class,
- 'Fewestrevisions' => \FewestrevisionsPage::class,
- 'Withoutinterwiki' => \WithoutInterwikiPage::class,
+ 'BrokenRedirects' => \SpecialBrokenRedirects::class,
+ 'Deadendpages' => \SpecialDeadendPages::class,
+ 'DoubleRedirects' => \SpecialDoubleRedirects::class,
+ 'Longpages' => \SpecialLongPages::class,
+ 'Ancientpages' => \SpecialAncientPages::class,
+ 'Lonelypages' => \SpecialLonelyPages::class,
+ 'Fewestrevisions' => \SpecialFewestRevisions::class,
+ 'Withoutinterwiki' => \SpecialWithoutInterwiki::class,
'Protectedpages' => \SpecialProtectedpages::class,
'Protectedtitles' => \SpecialProtectedtitles::class,
- 'Shortpages' => \ShortPagesPage::class,
- 'Uncategorizedcategories' => \UncategorizedCategoriesPage::class,
- 'Uncategorizedimages' => \UncategorizedImagesPage::class,
- 'Uncategorizedpages' => \UncategorizedPagesPage::class,
- 'Uncategorizedtemplates' => \UncategorizedTemplatesPage::class,
- 'Unusedcategories' => \UnusedCategoriesPage::class,
- 'Unusedimages' => \UnusedimagesPage::class,
- 'Unusedtemplates' => \UnusedtemplatesPage::class,
- 'Unwatchedpages' => \UnwatchedpagesPage::class,
- 'Wantedcategories' => \WantedCategoriesPage::class,
+ 'Shortpages' => \SpecialShortPages::class,
+ 'Uncategorizedcategories' => \SpecialUncategorizedCategories::class,
+ 'Uncategorizedimages' => \SpecialUncategorizedImages::class,
+ 'Uncategorizedpages' => \SpecialUncategorizedPages::class,
+ 'Uncategorizedtemplates' => \SpecialUncategorizedTemplates::class,
+ 'Unusedcategories' => \SpecialUnusedCategories::class,
+ 'Unusedimages' => \SpecialUnusedImages::class,
+ 'Unusedtemplates' => \SpecialUnusedTemplates::class,
+ 'Unwatchedpages' => \SpecialUnwatchedPages::class,
+ 'Wantedcategories' => \SpecialWantedCategories::class,
'Wantedfiles' => \WantedFilesPage::class,
'Wantedpages' => \WantedPagesPage::class,
- 'Wantedtemplates' => \WantedTemplatesPage::class,
+ 'Wantedtemplates' => \SpecialWantedTemplates::class,
// List of pages
'Allpages' => \SpecialAllPages::class,
'Prefixindex' => \SpecialPrefixindex::class,
'Categories' => \SpecialCategories::class,
- 'Listredirects' => \ListredirectsPage::class,
+ 'Listredirects' => \SpecialListRedirects::class,
'PagesWithProp' => \SpecialPagesWithProp::class,
'TrackingCategories' => \SpecialTrackingCategories::class,
'ChangePassword' => \SpecialChangePassword::class,
'BotPasswords' => \SpecialBotPasswords::class,
'PasswordReset' => \SpecialPasswordReset::class,
- 'DeletedContributions' => \DeletedContributionsPage::class,
+ 'DeletedContributions' => \SpecialDeletedContributions::class,
'Preferences' => \SpecialPreferences::class,
'ResetTokens' => \SpecialResetTokens::class,
'Contributions' => \SpecialContributions::class,
// Media reports and uploads
'Listfiles' => \SpecialListFiles::class,
'Filepath' => \SpecialFilepath::class,
- 'MediaStatistics' => \MediaStatisticsPage::class,
- 'MIMEsearch' => \MIMEsearchPage::class,
- 'FileDuplicateSearch' => \FileDuplicateSearchPage::class,
+ 'MediaStatistics' => \SpecialMediaStatistics::class,
+ 'MIMEsearch' => \SpecialMIMESearch::class,
+ 'FileDuplicateSearch' => \SpecialFileDuplicateSearch::class,
'Upload' => \SpecialUpload::class,
'UploadStash' => \SpecialUploadStash::class,
- 'ListDuplicatedFiles' => \ListDuplicatedFilesPage::class,
+ 'ListDuplicatedFiles' => \SpecialListDuplicatedFiles::class,
// Data and tools
'ApiSandbox' => \SpecialApiSandbox::class,
'Unlockdb' => \SpecialUnlockdb::class,
// Redirecting special pages
- 'LinkSearch' => \LinkSearchPage::class,
+ 'LinkSearch' => \SpecialLinkSearch::class,
'Randompage' => \RandomPage::class,
'RandomInCategory' => \SpecialRandomInCategory::class,
'Randomredirect' => \SpecialRandomredirect::class,
'GoToInterwiki' => \SpecialGoToInterwiki::class,
// High use pages
- 'Mostlinkedcategories' => \MostlinkedCategoriesPage::class,
+ 'Mostlinkedcategories' => \SpecialMostLinkedCategories::class,
'Mostimages' => \MostimagesPage::class,
- 'Mostinterwikis' => \MostinterwikisPage::class,
- 'Mostlinked' => \MostlinkedPage::class,
- 'Mostlinkedtemplates' => \MostlinkedTemplatesPage::class,
- 'Mostcategories' => \MostcategoriesPage::class,
- 'Mostrevisions' => \MostrevisionsPage::class,
+ 'Mostinterwikis' => \SpecialMostInterwikis::class,
+ 'Mostlinked' => \SpecialMostLinked::class,
+ 'Mostlinkedtemplates' => \SpecialMostLinkedTemplates::class,
+ 'Mostcategories' => \SpecialMostCategories::class,
+ 'Mostrevisions' => \SpecialMostRevisions::class,
// Page tools
'ComparePages' => \SpecialComparePages::class,
'ApiHelp' => \SpecialApiHelp::class,
'Blankpage' => \SpecialBlankpage::class,
'Diff' => \SpecialDiff::class,
- 'EditTags' => \SpecialEditTags::class,
+ 'EditTags' => [
+ 'class' => \SpecialEditTags::class,
+ 'services' => [
+ 'PermissionManager',
+ ],
+ ],
'Emailuser' => \SpecialEmailUser::class,
'Movepage' => \MovePageForm::class,
'Mycontributions' => \SpecialMycontributions::class,
'NewSection' => \SpecialNewSection::class,
'PermanentLink' => \SpecialPermanentLink::class,
'Redirect' => \SpecialRedirect::class,
- 'Revisiondelete' => \SpecialRevisionDelete::class,
+ 'Revisiondelete' => [
+ 'class' => \SpecialRevisionDelete::class,
+ 'services' => [
+ 'PermissionManager',
+ ],
+ ],
'RunJobs' => \SpecialRunJobs::class,
'Specialpages' => \SpecialSpecialpages::class,
'PageData' => \SpecialPageData::class,
}
if ( $this->options->get( 'EmailAuthentication' ) ) {
- $this->list['Confirmemail'] = \EmailConfirmation::class;
- $this->list['Invalidateemail'] = \EmailInvalidation::class;
+ $this->list['Confirmemail'] = \SpecialConfirmEmail::class;
+ $this->list['Invalidateemail'] = \SpecialEmailInvalidate::class;
}
if ( $this->options->get( 'EnableEmail' ) ) {
--- /dev/null
+<?php
+/**
+ * Implements Special:Ancientpages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Implements Special:Ancientpages
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialAncientPages extends QueryPage {
+
+ function __construct( $name = 'Ancientpages' ) {
+ parent::__construct( $name );
+ }
+
+ public function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ public function getQueryInfo() {
+ $tables = [ 'page', 'revision' ];
+ $conds = [
+ 'page_namespace' =>
+ MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces(),
+ 'page_is_redirect' => 0
+ ];
+ $joinConds = [
+ 'revision' => [
+ 'JOIN', [
+ 'page_latest = rev_id'
+ ]
+ ],
+ ];
+
+ // Allow extensions to modify the query
+ Hooks::run( 'AncientPagesQuery', [ &$tables, &$conds, &$joinConds ] );
+
+ return [
+ 'tables' => $tables,
+ 'fields' => [
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'rev_timestamp'
+ ],
+ 'conds' => $conds,
+ 'join_conds' => $joinConds
+ ];
+ }
+
+ public function usesTimestamps() {
+ return true;
+ }
+
+ function sortDescending() {
+ return false;
+ }
+
+ public function preprocessResults( $db, $res ) {
+ $this->executeLBFromResultWrapper( $res );
+ }
+
+ /**
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
+ */
+ function formatResult( $skin, $result ) {
+ $d = $this->getLanguage()->userTimeAndDate( $result->value, $this->getUser() );
+ $title = Title::makeTitle( $result->namespace, $result->title );
+ $linkRenderer = $this->getLinkRenderer();
+ $link = $linkRenderer->makeKnownLink(
+ $title,
+ new HtmlArmor( MediaWikiServices::getInstance()->getContentLanguage()->
+ convert( htmlspecialchars( $title->getPrefixedText() ) ) )
+ );
+
+ return $this->getLanguage()->specialList( $link, htmlspecialchars( $d ) );
+ }
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
+}
+++ /dev/null
-<?php
-/**
- * Implements Special:Ancientpages
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- */
-
-use MediaWiki\MediaWikiServices;
-
-/**
- * Implements Special:Ancientpages
- *
- * @ingroup SpecialPage
- */
-class AncientPagesPage extends QueryPage {
-
- function __construct( $name = 'Ancientpages' ) {
- parent::__construct( $name );
- }
-
- public function isExpensive() {
- return true;
- }
-
- function isSyndicated() {
- return false;
- }
-
- public function getQueryInfo() {
- $tables = [ 'page', 'revision' ];
- $conds = [
- 'page_namespace' =>
- MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces(),
- 'page_is_redirect' => 0
- ];
- $joinConds = [
- 'revision' => [
- 'JOIN', [
- 'page_latest = rev_id'
- ]
- ],
- ];
-
- // Allow extensions to modify the query
- Hooks::run( 'AncientPagesQuery', [ &$tables, &$conds, &$joinConds ] );
-
- return [
- 'tables' => $tables,
- 'fields' => [
- 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'rev_timestamp'
- ],
- 'conds' => $conds,
- 'join_conds' => $joinConds
- ];
- }
-
- public function usesTimestamps() {
- return true;
- }
-
- function sortDescending() {
- return false;
- }
-
- public function preprocessResults( $db, $res ) {
- $this->executeLBFromResultWrapper( $res );
- }
-
- /**
- * @param Skin $skin
- * @param object $result Result row
- * @return string
- */
- function formatResult( $skin, $result ) {
- $d = $this->getLanguage()->userTimeAndDate( $result->value, $this->getUser() );
- $title = Title::makeTitle( $result->namespace, $result->title );
- $linkRenderer = $this->getLinkRenderer();
- $link = $linkRenderer->makeKnownLink(
- $title,
- new HtmlArmor( MediaWikiServices::getInstance()->getContentLanguage()->
- convert( htmlspecialchars( $title->getPrefixedText() ) ) )
- );
-
- return $this->getLanguage()->specialList( $link, htmlspecialchars( $d ) );
- }
-
- protected function getGroupName() {
- return 'maintenance';
- }
-}
'Options' => [
'type' => 'multiselect',
'options-messages' => [
- 'blocklist-userblocks' => 'userblocks',
'blocklist-tempblocks' => 'tempblocks',
+ 'blocklist-indefblocks' => 'indefblocks',
+ 'blocklist-userblocks' => 'userblocks',
'blocklist-addressblocks' => 'addressblocks',
'blocklist-rangeblocks' => 'rangeblocks',
],
*/
protected function getBlockListPager() {
$conds = [];
+ $db = $this->getDB();
# Is the user allowed to see hidden blocks?
if ( !$this->getUser()->isAllowed( 'hideuser' ) ) {
$conds['ipb_deleted'] = 0;
case DatabaseBlock::TYPE_IP:
case DatabaseBlock::TYPE_RANGE:
list( $start, $end ) = IP::parseRange( $target );
- $conds[] = wfGetDB( DB_REPLICA )->makeList(
+ $conds[] = $db->makeList(
[
'ipb_address' => $target,
DatabaseBlock::getRangeCond( $start, $end )
if ( in_array( 'userblocks', $this->options ) ) {
$conds['ipb_user'] = 0;
}
- if ( in_array( 'tempblocks', $this->options ) ) {
- $conds['ipb_expiry'] = 'infinity';
- }
if ( in_array( 'addressblocks', $this->options ) ) {
$conds[] = "ipb_user != 0 OR ipb_range_end > ipb_range_start";
}
$conds[] = "ipb_range_end = ipb_range_start";
}
+ $hideTemp = in_array( 'tempblocks', $this->options );
+ $hideIndef = in_array( 'indefblocks', $this->options );
+ if ( $hideTemp && $hideIndef ) {
+ // If both types are hidden, ensure query doesn't produce any results
+ $conds[] = '1=0';
+ } elseif ( $hideTemp ) {
+ $conds['ipb_expiry'] = $db->getInfinity();
+ } elseif ( $hideIndef ) {
+ $conds[] = "ipb_expiry != " . $db->addQuotes( $db->getInfinity() );
+ }
+
if ( $this->blockType === 'sitewide' ) {
- $conds[] = 'ipb_sitewide = 1';
+ $conds['ipb_sitewide'] = 1;
} elseif ( $this->blockType === 'partial' ) {
- $conds[] = 'ipb_sitewide = 0';
+ $conds['ipb_sitewide'] = 0;
}
return new BlockListPager( $this, $conds );
protected function getGroupName() {
return 'users';
}
+
+ /**
+ * Return a IDatabase object for reading
+ *
+ * @return IDatabase
+ */
+ protected function getDB() {
+ return wfGetDB( DB_REPLICA );
+ }
}
*
* @ingroup SpecialPage
*/
-class BrokenRedirectsPage extends QueryPage {
+class SpecialBrokenRedirects extends QueryPage {
function __construct( $name = 'BrokenRedirects' ) {
parent::__construct( $name );
}
--- /dev/null
+<?php
+/**
+ * Implements Special:Confirmemail
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * Special page allows users to request email confirmation message, and handles
+ * processing of the confirmation code when the link in the email is followed
+ *
+ * @ingroup SpecialPage
+ * @author Brion Vibber
+ * @author Rob Church <robchur@gmail.com>
+ */
+class SpecialConfirmEmail extends UnlistedSpecialPage {
+ public function __construct() {
+ parent::__construct( 'Confirmemail', 'editmyprivateinfo' );
+ }
+
+ public function doesWrites() {
+ return true;
+ }
+
+ /**
+ * Main execution point
+ *
+ * @param null|string $code Confirmation code passed to the page
+ * @throws PermissionsError
+ * @throws ReadOnlyError
+ * @throws UserNotLoggedIn
+ */
+ function execute( $code ) {
+ // Ignore things like master queries/connections on GET requests.
+ // It's very convenient to just allow formless link usage.
+ $trxProfiler = Profiler::instance()->getTransactionProfiler();
+
+ $this->setHeaders();
+ $this->checkReadOnly();
+ $this->checkPermissions();
+
+ // This could also let someone check the current email address, so
+ // require both permissions.
+ if ( !$this->getUser()->isAllowed( 'viewmyprivateinfo' ) ) {
+ throw new PermissionsError( 'viewmyprivateinfo' );
+ }
+
+ if ( $code === null || $code === '' ) {
+ $this->requireLogin( 'confirmemail_needlogin' );
+ if ( Sanitizer::validateEmail( $this->getUser()->getEmail() ) ) {
+ $this->showRequestForm();
+ } else {
+ $this->getOutput()->addWikiMsg( 'confirmemail_noemail' );
+ }
+ } else {
+ $old = $trxProfiler->setSilenced( true );
+ $this->attemptConfirm( $code );
+ $trxProfiler->setSilenced( $old );
+ }
+ }
+
+ /**
+ * Show a nice form for the user to request a confirmation mail
+ */
+ function showRequestForm() {
+ $user = $this->getUser();
+ $out = $this->getOutput();
+
+ if ( !$user->isEmailConfirmed() ) {
+ $descriptor = [];
+ if ( $user->isEmailConfirmationPending() ) {
+ $descriptor += [
+ 'pending' => [
+ 'type' => 'info',
+ 'raw' => true,
+ 'default' => "<div class=\"error mw-confirmemail-pending\">\n" .
+ $this->msg( 'confirmemail_pending' )->escaped() .
+ "\n</div>",
+ ],
+ ];
+ }
+
+ $out->addWikiMsg( 'confirmemail_text' );
+ $form = HTMLForm::factory( 'ooui', $descriptor, $this->getContext() );
+ $form
+ ->setMethod( 'post' )
+ ->setAction( $this->getPageTitle()->getLocalURL() )
+ ->setSubmitTextMsg( 'confirmemail_send' )
+ ->setSubmitCallback( [ $this, 'submitSend' ] );
+
+ $retval = $form->show();
+
+ if ( $retval === true ) {
+ // should never happen, but if so, don't let the user without any message
+ $out->addWikiMsg( 'confirmemail_sent' );
+ } elseif ( $retval instanceof Status && $retval->isGood() ) {
+ $out->addWikiTextAsInterface( $retval->getValue() );
+ }
+ } else {
+ // date and time are separate parameters to facilitate localisation.
+ // $time is kept for backward compat reasons.
+ // 'emailauthenticated' is also used in SpecialPreferences.php
+ $lang = $this->getLanguage();
+ $emailAuthenticated = $user->getEmailAuthenticationTimestamp();
+ $time = $lang->userTimeAndDate( $emailAuthenticated, $user );
+ $d = $lang->userDate( $emailAuthenticated, $user );
+ $t = $lang->userTime( $emailAuthenticated, $user );
+ $out->addWikiMsg( 'emailauthenticated', $time, $d, $t );
+ }
+ }
+
+ /**
+ * Callback for HTMLForm send confirmation mail.
+ *
+ * @return Status Status object with the result
+ */
+ public function submitSend() {
+ $status = $this->getUser()->sendConfirmationMail();
+ if ( $status->isGood() ) {
+ return Status::newGood( $this->msg( 'confirmemail_sent' )->text() );
+ } else {
+ return Status::newFatal( new RawMessage(
+ $status->getWikiText( 'confirmemail_sendfailed' )
+ ) );
+ }
+ }
+
+ /**
+ * Attempt to confirm the user's email address and show success or failure
+ * as needed; if successful, take the user to log in
+ *
+ * @param string $code Confirmation code
+ */
+ private function attemptConfirm( $code ) {
+ $user = User::newFromConfirmationCode( $code, User::READ_EXCLUSIVE );
+ if ( !is_object( $user ) ) {
+ $this->getOutput()->addWikiMsg( 'confirmemail_invalid' );
+
+ return;
+ }
+
+ // rate limit email confirmations
+ if ( $user->pingLimiter( 'confirmemail' ) ) {
+ $this->getOutput()->addWikiMsg( 'actionthrottledtext' );
+
+ return;
+ }
+
+ $user->confirmEmail();
+ $user->saveSettings();
+ $message = $this->getUser()->isLoggedIn() ? 'confirmemail_loggedin' : 'confirmemail_success';
+ $this->getOutput()->addWikiMsg( $message );
+
+ if ( !$this->getUser()->isLoggedIn() ) {
+ $title = SpecialPage::getTitleFor( 'Userlogin' );
+ $this->getOutput()->returnToMain( true, $title );
+ }
+ }
+}
+++ /dev/null
-<?php
-/**
- * Implements Special:Confirmemail
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- */
-
-/**
- * Special page allows users to request email confirmation message, and handles
- * processing of the confirmation code when the link in the email is followed
- *
- * @ingroup SpecialPage
- * @author Brion Vibber
- * @author Rob Church <robchur@gmail.com>
- */
-class EmailConfirmation extends UnlistedSpecialPage {
- public function __construct() {
- parent::__construct( 'Confirmemail', 'editmyprivateinfo' );
- }
-
- public function doesWrites() {
- return true;
- }
-
- /**
- * Main execution point
- *
- * @param null|string $code Confirmation code passed to the page
- * @throws PermissionsError
- * @throws ReadOnlyError
- * @throws UserNotLoggedIn
- */
- function execute( $code ) {
- // Ignore things like master queries/connections on GET requests.
- // It's very convenient to just allow formless link usage.
- $trxProfiler = Profiler::instance()->getTransactionProfiler();
-
- $this->setHeaders();
- $this->checkReadOnly();
- $this->checkPermissions();
-
- // This could also let someone check the current email address, so
- // require both permissions.
- if ( !$this->getUser()->isAllowed( 'viewmyprivateinfo' ) ) {
- throw new PermissionsError( 'viewmyprivateinfo' );
- }
-
- if ( $code === null || $code === '' ) {
- $this->requireLogin( 'confirmemail_needlogin' );
- if ( Sanitizer::validateEmail( $this->getUser()->getEmail() ) ) {
- $this->showRequestForm();
- } else {
- $this->getOutput()->addWikiMsg( 'confirmemail_noemail' );
- }
- } else {
- $old = $trxProfiler->setSilenced( true );
- $this->attemptConfirm( $code );
- $trxProfiler->setSilenced( $old );
- }
- }
-
- /**
- * Show a nice form for the user to request a confirmation mail
- */
- function showRequestForm() {
- $user = $this->getUser();
- $out = $this->getOutput();
-
- if ( !$user->isEmailConfirmed() ) {
- $descriptor = [];
- if ( $user->isEmailConfirmationPending() ) {
- $descriptor += [
- 'pending' => [
- 'type' => 'info',
- 'raw' => true,
- 'default' => "<div class=\"error mw-confirmemail-pending\">\n" .
- $this->msg( 'confirmemail_pending' )->escaped() .
- "\n</div>",
- ],
- ];
- }
-
- $out->addWikiMsg( 'confirmemail_text' );
- $form = HTMLForm::factory( 'ooui', $descriptor, $this->getContext() );
- $form
- ->setMethod( 'post' )
- ->setAction( $this->getPageTitle()->getLocalURL() )
- ->setSubmitTextMsg( 'confirmemail_send' )
- ->setSubmitCallback( [ $this, 'submitSend' ] );
-
- $retval = $form->show();
-
- if ( $retval === true ) {
- // should never happen, but if so, don't let the user without any message
- $out->addWikiMsg( 'confirmemail_sent' );
- } elseif ( $retval instanceof Status && $retval->isGood() ) {
- $out->addWikiTextAsInterface( $retval->getValue() );
- }
- } else {
- // date and time are separate parameters to facilitate localisation.
- // $time is kept for backward compat reasons.
- // 'emailauthenticated' is also used in SpecialPreferences.php
- $lang = $this->getLanguage();
- $emailAuthenticated = $user->getEmailAuthenticationTimestamp();
- $time = $lang->userTimeAndDate( $emailAuthenticated, $user );
- $d = $lang->userDate( $emailAuthenticated, $user );
- $t = $lang->userTime( $emailAuthenticated, $user );
- $out->addWikiMsg( 'emailauthenticated', $time, $d, $t );
- }
- }
-
- /**
- * Callback for HTMLForm send confirmation mail.
- *
- * @return Status Status object with the result
- */
- public function submitSend() {
- $status = $this->getUser()->sendConfirmationMail();
- if ( $status->isGood() ) {
- return Status::newGood( $this->msg( 'confirmemail_sent' )->text() );
- } else {
- return Status::newFatal( new RawMessage(
- $status->getWikiText( 'confirmemail_sendfailed' )
- ) );
- }
- }
-
- /**
- * Attempt to confirm the user's email address and show success or failure
- * as needed; if successful, take the user to log in
- *
- * @param string $code Confirmation code
- */
- private function attemptConfirm( $code ) {
- $user = User::newFromConfirmationCode( $code, User::READ_EXCLUSIVE );
- if ( !is_object( $user ) ) {
- $this->getOutput()->addWikiMsg( 'confirmemail_invalid' );
-
- return;
- }
-
- // rate limit email confirmations
- if ( $user->pingLimiter( 'confirmemail' ) ) {
- $this->getOutput()->addWikiMsg( 'actionthrottledtext' );
-
- return;
- }
-
- $user->confirmEmail();
- $user->saveSettings();
- $message = $this->getUser()->isLoggedIn() ? 'confirmemail_loggedin' : 'confirmemail_success';
- $this->getOutput()->addWikiMsg( $message );
-
- if ( !$this->getUser()->isLoggedIn() ) {
- $title = SpecialPage::getTitleFor( 'Userlogin' );
- $this->getOutput()->returnToMain( true, $title );
- }
- }
-}
$filterSelection = Html::rawElement( 'div', [], '' );
}
- $labelUsername = Xml::radioLabel(
+ $labelUsername = Xml::label(
$this->msg( 'sp-contributions-username' )->text(),
- 'contribs',
- 'user',
- 'user',
- true,
- [ 'class' => 'mw-input' ]
+ 'mw-target-user-or-ip'
);
$input = Html::input(
'target',
--- /dev/null
+<?php
+/**
+ * Implements Special:Deadenpages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * A special page that list pages that contain no link to other pages
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialDeadendPages extends PageQueryPage {
+
+ function __construct( $name = 'Deadendpages' ) {
+ parent::__construct( $name );
+ }
+
+ function getPageHeader() {
+ return $this->msg( 'deadendpagestext' )->parseAsBlock();
+ }
+
+ /**
+ * LEFT JOIN is expensive
+ *
+ * @return bool
+ */
+ function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ /**
+ * @return bool
+ */
+ function sortDescending() {
+ return false;
+ }
+
+ function getQueryInfo() {
+ return [
+ 'tables' => [ 'page', 'pagelinks' ],
+ 'fields' => [
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_title'
+ ],
+ 'conds' => [
+ 'pl_from IS NULL',
+ 'page_namespace' => MediaWikiServices::getInstance()->getNamespaceInfo()->
+ getContentNamespaces(),
+ 'page_is_redirect' => 0
+ ],
+ 'join_conds' => [
+ 'pagelinks' => [
+ 'LEFT JOIN',
+ [ 'page_id=pl_from' ]
+ ]
+ ]
+ ];
+ }
+
+ function getOrderFields() {
+ // For some crazy reason ordering by a constant
+ // causes a filesort
+ if ( count( MediaWikiServices::getInstance()->getNamespaceInfo()->
+ getContentNamespaces() ) > 1
+ ) {
+ return [ 'page_namespace', 'page_title' ];
+ } else {
+ return [ 'page_title' ];
+ }
+ }
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
+}
+++ /dev/null
-<?php
-/**
- * Implements Special:Deadenpages
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- */
-
-use MediaWiki\MediaWikiServices;
-
-/**
- * A special page that list pages that contain no link to other pages
- *
- * @ingroup SpecialPage
- */
-class DeadendPagesPage extends PageQueryPage {
-
- function __construct( $name = 'Deadendpages' ) {
- parent::__construct( $name );
- }
-
- function getPageHeader() {
- return $this->msg( 'deadendpagestext' )->parseAsBlock();
- }
-
- /**
- * LEFT JOIN is expensive
- *
- * @return bool
- */
- function isExpensive() {
- return true;
- }
-
- function isSyndicated() {
- return false;
- }
-
- /**
- * @return bool
- */
- function sortDescending() {
- return false;
- }
-
- function getQueryInfo() {
- return [
- 'tables' => [ 'page', 'pagelinks' ],
- 'fields' => [
- 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_title'
- ],
- 'conds' => [
- 'pl_from IS NULL',
- 'page_namespace' => MediaWikiServices::getInstance()->getNamespaceInfo()->
- getContentNamespaces(),
- 'page_is_redirect' => 0
- ],
- 'join_conds' => [
- 'pagelinks' => [
- 'LEFT JOIN',
- [ 'page_id=pl_from' ]
- ]
- ]
- ];
- }
-
- function getOrderFields() {
- // For some crazy reason ordering by a constant
- // causes a filesort
- if ( count( MediaWikiServices::getInstance()->getNamespaceInfo()->
- getContentNamespaces() ) > 1
- ) {
- return [ 'page_namespace', 'page_title' ];
- } else {
- return [ 'page_title' ];
- }
- }
-
- protected function getGroupName() {
- return 'maintenance';
- }
-}
* Implements Special:DeletedContributions to display archived revisions
* @ingroup SpecialPage
*/
-class DeletedContributionsPage extends SpecialPage {
+class SpecialDeletedContributions extends SpecialPage {
/** @var FormOptions */
protected $mOpts;
*
* @ingroup SpecialPage
*/
-class DoubleRedirectsPage extends QueryPage {
+class SpecialDoubleRedirects extends QueryPage {
function __construct( $name = 'DoubleRedirects' ) {
parent::__construct( $name );
}
* @ingroup SpecialPage
*/
+use MediaWiki\Permissions\PermissionManager;
+
/**
* Special page for adding and removing change tags to individual revisions.
* A lot of this is copied out of SpecialRevisiondelete.
/** @var string */
private $reason;
- public function __construct() {
+ /** @var PermissionManager */
+ private $permissionManager;
+
+ /**
+ * @inheritDoc
+ *
+ * @param PermissionManager $permissionManager
+ */
+ public function __construct( PermissionManager $permissionManager ) {
parent::__construct( 'EditTags', 'changetags' );
+
+ $this->permissionManager = $permissionManager;
}
public function doesWrites() {
$user = $this->getUser();
$request = $this->getRequest();
- // Check blocks
- // @TODO Use PermissionManager::isBlockedFrom() instead.
- $block = $user->getBlock();
- if ( $block ) {
- throw new UserBlockedError( $block );
- }
-
$this->setHeaders();
$this->outputHeader();
$output->addWikiMsg( 'undelete-header' );
return;
}
+
+ // Check blocks
+ if ( $this->permissionManager->isBlockedFrom( $user, $this->targetObj ) ) {
+ throw new UserBlockedError( $user->getBlock() );
+ }
+
// Give a link to the logs/hist for this page
$this->showConvenienceLinks();
*
* @ingroup SpecialPage
*/
-class EmailInvalidation extends UnlistedSpecialPage {
+class SpecialEmailInvalidate extends UnlistedSpecialPage {
public function __construct() {
parent::__construct( 'Invalidateemail', 'editmyprivateinfo' );
}
}
if ( $error ) {
- $out->wrapWikiMsg( "<div class='previewnote'>\n$1\n</div>", $error );
+ $out->wrapWikiMsg( "<div class='previewnote errorbox'>\n$1\n</div>", $error );
return;
}
}
--- /dev/null
+<?php
+/**
+ * Implements Special:Fewestrevisions
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Special page for listing the articles with the fewest revisions.
+ *
+ * @ingroup SpecialPage
+ * @author Martin Drashkov
+ */
+class SpecialFewestRevisions extends QueryPage {
+ function __construct( $name = 'Fewestrevisions' ) {
+ parent::__construct( $name );
+ }
+
+ public function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ public function getQueryInfo() {
+ return [
+ 'tables' => [ 'revision', 'page' ],
+ 'fields' => [
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'COUNT(*)',
+ ],
+ 'conds' => [
+ 'page_namespace' => MediaWikiServices::getInstance()->getNamespaceInfo()->
+ getContentNamespaces(),
+ 'page_id = rev_page',
+ 'page_is_redirect = 0',
+ ],
+ 'options' => [
+ 'GROUP BY' => [ 'page_namespace', 'page_title' ]
+ ]
+ ];
+ }
+
+ function sortDescending() {
+ return false;
+ }
+
+ /**
+ * @param Skin $skin
+ * @param object $result Database row
+ * @return string
+ */
+ function formatResult( $skin, $result ) {
+ $nt = Title::makeTitleSafe( $result->namespace, $result->title );
+ if ( !$nt ) {
+ return Html::element(
+ 'span',
+ [ 'class' => 'mw-invalidtitle' ],
+ Linker::getInvalidTitleDescription(
+ $this->getContext(),
+ $result->namespace,
+ $result->title
+ )
+ );
+ }
+ $linkRenderer = $this->getLinkRenderer();
+ $text = MediaWikiServices::getInstance()->getContentLanguage()->
+ convert( htmlspecialchars( $nt->getPrefixedText() ) );
+ $plink = $linkRenderer->makeLink( $nt, new HtmlArmor( $text ) );
+
+ $nl = $this->msg( 'nrevisions' )->numParams( $result->value )->text();
+ $redirect = isset( $result->redirect ) && $result->redirect ?
+ ' - ' . $this->msg( 'isredirect' )->escaped() : '';
+ $nlink = $linkRenderer->makeKnownLink(
+ $nt,
+ $nl,
+ [],
+ [ 'action' => 'history' ]
+ ) . $redirect;
+
+ return $this->getLanguage()->specialList( $plink, $nlink );
+ }
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
+}
+++ /dev/null
-<?php
-/**
- * Implements Special:Fewestrevisions
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- */
-
-use MediaWiki\MediaWikiServices;
-
-/**
- * Special page for listing the articles with the fewest revisions.
- *
- * @ingroup SpecialPage
- * @author Martin Drashkov
- */
-class FewestrevisionsPage extends QueryPage {
- function __construct( $name = 'Fewestrevisions' ) {
- parent::__construct( $name );
- }
-
- public function isExpensive() {
- return true;
- }
-
- function isSyndicated() {
- return false;
- }
-
- public function getQueryInfo() {
- return [
- 'tables' => [ 'revision', 'page' ],
- 'fields' => [
- 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'COUNT(*)',
- ],
- 'conds' => [
- 'page_namespace' => MediaWikiServices::getInstance()->getNamespaceInfo()->
- getContentNamespaces(),
- 'page_id = rev_page',
- 'page_is_redirect = 0',
- ],
- 'options' => [
- 'GROUP BY' => [ 'page_namespace', 'page_title' ]
- ]
- ];
- }
-
- function sortDescending() {
- return false;
- }
-
- /**
- * @param Skin $skin
- * @param object $result Database row
- * @return string
- */
- function formatResult( $skin, $result ) {
- $nt = Title::makeTitleSafe( $result->namespace, $result->title );
- if ( !$nt ) {
- return Html::element(
- 'span',
- [ 'class' => 'mw-invalidtitle' ],
- Linker::getInvalidTitleDescription(
- $this->getContext(),
- $result->namespace,
- $result->title
- )
- );
- }
- $linkRenderer = $this->getLinkRenderer();
- $text = MediaWikiServices::getInstance()->getContentLanguage()->
- convert( htmlspecialchars( $nt->getPrefixedText() ) );
- $plink = $linkRenderer->makeLink( $nt, new HtmlArmor( $text ) );
-
- $nl = $this->msg( 'nrevisions' )->numParams( $result->value )->text();
- $redirect = isset( $result->redirect ) && $result->redirect ?
- ' - ' . $this->msg( 'isredirect' )->escaped() : '';
- $nlink = $linkRenderer->makeKnownLink(
- $nt,
- $nl,
- [],
- [ 'action' => 'history' ]
- ) . $redirect;
-
- return $this->getLanguage()->specialList( $plink, $nlink );
- }
-
- protected function getGroupName() {
- return 'maintenance';
- }
-}
*
* @ingroup SpecialPage
*/
-class FileDuplicateSearchPage extends QueryPage {
+class SpecialFileDuplicateSearch extends QueryPage {
protected $hash = '', $filename = '';
/**
* Special:LinkSearch to search the external-links table.
* @ingroup SpecialPage
*/
-class LinkSearchPage extends QueryPage {
+class SpecialLinkSearch extends QueryPage {
/** @var array|bool */
private $mungedQuery = false;
+ /** @var string|null */
+ private $mQuery;
+ /** @var int|null */
+ private $mNs;
+ /** @var string|null */
+ private $mProt;
function setParams( $params ) {
$this->mQuery = $params['query'];
* a duplicate of the current version of some other file.
* @ingroup SpecialPage
*/
-class ListDuplicatedFilesPage extends QueryPage {
+class SpecialListDuplicatedFiles extends QueryPage {
function __construct( $name = 'ListDuplicatedFiles' ) {
parent::__construct( $name );
}
--- /dev/null
+<?php
+/**
+ * Implements Special:Listredirects
+ *
+ * Copyright © 2006 Rob Church
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @author Rob Church <robchur@gmail.com>
+ */
+
+use Wikimedia\Rdbms\IResultWrapper;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * Special:Listredirects - Lists all the redirects on the wiki.
+ * @ingroup SpecialPage
+ */
+class SpecialListRedirects extends QueryPage {
+ function __construct( $name = 'Listredirects' ) {
+ parent::__construct( $name );
+ }
+
+ public function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ function sortDescending() {
+ return false;
+ }
+
+ public function getQueryInfo() {
+ return [
+ 'tables' => [ 'p1' => 'page', 'redirect', 'p2' => 'page' ],
+ 'fields' => [ 'namespace' => 'p1.page_namespace',
+ 'title' => 'p1.page_title',
+ 'value' => 'p1.page_title',
+ 'rd_namespace',
+ 'rd_title',
+ 'rd_fragment',
+ 'rd_interwiki',
+ 'redirid' => 'p2.page_id' ],
+ 'conds' => [ 'p1.page_is_redirect' => 1 ],
+ 'join_conds' => [ 'redirect' => [
+ 'LEFT JOIN', 'rd_from=p1.page_id' ],
+ 'p2' => [ 'LEFT JOIN', [
+ 'p2.page_namespace=rd_namespace',
+ 'p2.page_title=rd_title' ] ] ]
+ ];
+ }
+
+ function getOrderFields() {
+ return [ 'p1.page_namespace', 'p1.page_title' ];
+ }
+
+ /**
+ * Cache page existence for performance
+ *
+ * @param IDatabase $db
+ * @param IResultWrapper $res
+ */
+ function preprocessResults( $db, $res ) {
+ if ( !$res->numRows() ) {
+ return;
+ }
+
+ $batch = new LinkBatch;
+ foreach ( $res as $row ) {
+ $batch->add( $row->namespace, $row->title );
+ $redirTarget = $this->getRedirectTarget( $row );
+ if ( $redirTarget ) {
+ $batch->addObj( $redirTarget );
+ }
+ }
+ $batch->execute();
+
+ // Back to start for display
+ $res->seek( 0 );
+ }
+
+ /**
+ * @param stdClass $row
+ * @return Title|null
+ */
+ protected function getRedirectTarget( $row ) {
+ if ( isset( $row->rd_title ) ) {
+ return Title::makeTitle( $row->rd_namespace,
+ $row->rd_title, $row->rd_fragment,
+ $row->rd_interwiki
+ );
+ } else {
+ $title = Title::makeTitle( $row->namespace, $row->title );
+ $article = WikiPage::factory( $title );
+
+ return $article->getRedirectTarget();
+ }
+ }
+
+ /**
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
+ */
+ function formatResult( $skin, $result ) {
+ $linkRenderer = $this->getLinkRenderer();
+ # Make a link to the redirect itself
+ $rd_title = Title::makeTitle( $result->namespace, $result->title );
+ $rd_link = $linkRenderer->makeLink(
+ $rd_title,
+ null,
+ [],
+ [ 'redirect' => 'no' ]
+ );
+
+ # Find out where the redirect leads
+ $target = $this->getRedirectTarget( $result );
+ if ( $target ) {
+ # Make a link to the destination page
+ $lang = $this->getLanguage();
+ $arr = $lang->getArrow() . $lang->getDirMark();
+ $targetLink = $linkRenderer->makeLink( $target, $target->getFullText() );
+
+ return "$rd_link $arr $targetLink";
+ } else {
+ return "<del>$rd_link</del>";
+ }
+ }
+
+ public function execute( $par ) {
+ $this->addHelpLink( 'Help:Redirects' );
+ parent::execute( $par );
+ }
+
+ protected function getGroupName() {
+ return 'pages';
+ }
+}
+++ /dev/null
-<?php
-/**
- * Implements Special:Listredirects
- *
- * Copyright © 2006 Rob Church
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- * @author Rob Church <robchur@gmail.com>
- */
-
-use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
-
-/**
- * Special:Listredirects - Lists all the redirects on the wiki.
- * @ingroup SpecialPage
- */
-class ListredirectsPage extends QueryPage {
- function __construct( $name = 'Listredirects' ) {
- parent::__construct( $name );
- }
-
- public function isExpensive() {
- return true;
- }
-
- function isSyndicated() {
- return false;
- }
-
- function sortDescending() {
- return false;
- }
-
- public function getQueryInfo() {
- return [
- 'tables' => [ 'p1' => 'page', 'redirect', 'p2' => 'page' ],
- 'fields' => [ 'namespace' => 'p1.page_namespace',
- 'title' => 'p1.page_title',
- 'value' => 'p1.page_title',
- 'rd_namespace',
- 'rd_title',
- 'rd_fragment',
- 'rd_interwiki',
- 'redirid' => 'p2.page_id' ],
- 'conds' => [ 'p1.page_is_redirect' => 1 ],
- 'join_conds' => [ 'redirect' => [
- 'LEFT JOIN', 'rd_from=p1.page_id' ],
- 'p2' => [ 'LEFT JOIN', [
- 'p2.page_namespace=rd_namespace',
- 'p2.page_title=rd_title' ] ] ]
- ];
- }
-
- function getOrderFields() {
- return [ 'p1.page_namespace', 'p1.page_title' ];
- }
-
- /**
- * Cache page existence for performance
- *
- * @param IDatabase $db
- * @param IResultWrapper $res
- */
- function preprocessResults( $db, $res ) {
- if ( !$res->numRows() ) {
- return;
- }
-
- $batch = new LinkBatch;
- foreach ( $res as $row ) {
- $batch->add( $row->namespace, $row->title );
- $redirTarget = $this->getRedirectTarget( $row );
- if ( $redirTarget ) {
- $batch->addObj( $redirTarget );
- }
- }
- $batch->execute();
-
- // Back to start for display
- $res->seek( 0 );
- }
-
- /**
- * @param stdClass $row
- * @return Title|null
- */
- protected function getRedirectTarget( $row ) {
- if ( isset( $row->rd_title ) ) {
- return Title::makeTitle( $row->rd_namespace,
- $row->rd_title, $row->rd_fragment,
- $row->rd_interwiki
- );
- } else {
- $title = Title::makeTitle( $row->namespace, $row->title );
- $article = WikiPage::factory( $title );
-
- return $article->getRedirectTarget();
- }
- }
-
- /**
- * @param Skin $skin
- * @param object $result Result row
- * @return string
- */
- function formatResult( $skin, $result ) {
- $linkRenderer = $this->getLinkRenderer();
- # Make a link to the redirect itself
- $rd_title = Title::makeTitle( $result->namespace, $result->title );
- $rd_link = $linkRenderer->makeLink(
- $rd_title,
- null,
- [],
- [ 'redirect' => 'no' ]
- );
-
- # Find out where the redirect leads
- $target = $this->getRedirectTarget( $result );
- if ( $target ) {
- # Make a link to the destination page
- $lang = $this->getLanguage();
- $arr = $lang->getArrow() . $lang->getDirMark();
- $targetLink = $linkRenderer->makeLink( $target, $target->getFullText() );
-
- return "$rd_link $arr $targetLink";
- } else {
- return "<del>$rd_link</del>";
- }
- }
-
- public function execute( $par ) {
- $this->addHelpLink( 'Help:Redirects' );
- parent::execute( $par );
- }
-
- protected function getGroupName() {
- return 'pages';
- }
-}
}
public function execute( $par ) {
- global $wgActorTableSchemaMigrationStage;
-
$this->setHeaders();
$this->outputHeader();
$out = $this->getOutput();
$offenderName = $opts->getValue( 'offender' );
$offender = empty( $offenderName ) ? null : User::newFromName( $offenderName, false );
if ( $offender ) {
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
- $qc = [ 'ls_field' => 'target_author_actor', 'ls_value' => $offender->getActorId() ];
- } elseif ( $offender->getId() > 0 ) {
- $qc = [ 'ls_field' => 'target_author_id', 'ls_value' => $offender->getId() ];
- } else {
- $qc = [ 'ls_field' => 'target_author_ip', 'ls_value' => $offender->getName() ];
- }
+ $qc = [ 'ls_field' => 'target_author_actor', 'ls_value' => $offender->getActorId() ];
}
} else {
// Allow extensions to add relations to their search types
--- /dev/null
+<?php
+/**
+ * Implements Special:Lonelypaages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * A special page looking for articles with no article linking to them,
+ * thus being lonely.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialLonelyPages extends PageQueryPage {
+ function __construct( $name = 'Lonelypages' ) {
+ parent::__construct( $name );
+ }
+
+ function getPageHeader() {
+ return $this->msg( 'lonelypagestext' )->parseAsBlock();
+ }
+
+ function sortDescending() {
+ return false;
+ }
+
+ function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ function getQueryInfo() {
+ $tables = [ 'page', 'pagelinks', 'templatelinks' ];
+ $conds = [
+ 'pl_namespace IS NULL',
+ 'page_namespace' => MediaWikiServices::getInstance()->getNamespaceInfo()->
+ getContentNamespaces(),
+ 'page_is_redirect' => 0,
+ 'tl_namespace IS NULL'
+ ];
+ $joinConds = [
+ 'pagelinks' => [
+ 'LEFT JOIN', [
+ 'pl_namespace = page_namespace',
+ 'pl_title = page_title'
+ ]
+ ],
+ 'templatelinks' => [
+ 'LEFT JOIN', [
+ 'tl_namespace = page_namespace',
+ 'tl_title = page_title'
+ ]
+ ]
+ ];
+
+ // Allow extensions to modify the query
+ Hooks::run( 'LonelyPagesQuery', [ &$tables, &$conds, &$joinConds ] );
+
+ return [
+ 'tables' => $tables,
+ 'fields' => [
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_title'
+ ],
+ 'conds' => $conds,
+ 'join_conds' => $joinConds
+ ];
+ }
+
+ function getOrderFields() {
+ // For some crazy reason ordering by a constant
+ // causes a filesort in MySQL 5
+ if ( count( MediaWikiServices::getInstance()->getNamespaceInfo()->
+ getContentNamespaces() ) > 1
+ ) {
+ return [ 'page_namespace', 'page_title' ];
+ } else {
+ return [ 'page_title' ];
+ }
+ }
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
+}
+++ /dev/null
-<?php
-/**
- * Implements Special:Lonelypaages
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- */
-
-use MediaWiki\MediaWikiServices;
-
-/**
- * A special page looking for articles with no article linking to them,
- * thus being lonely.
- *
- * @ingroup SpecialPage
- */
-class LonelyPagesPage extends PageQueryPage {
- function __construct( $name = 'Lonelypages' ) {
- parent::__construct( $name );
- }
-
- function getPageHeader() {
- return $this->msg( 'lonelypagestext' )->parseAsBlock();
- }
-
- function sortDescending() {
- return false;
- }
-
- function isExpensive() {
- return true;
- }
-
- function isSyndicated() {
- return false;
- }
-
- function getQueryInfo() {
- $tables = [ 'page', 'pagelinks', 'templatelinks' ];
- $conds = [
- 'pl_namespace IS NULL',
- 'page_namespace' => MediaWikiServices::getInstance()->getNamespaceInfo()->
- getContentNamespaces(),
- 'page_is_redirect' => 0,
- 'tl_namespace IS NULL'
- ];
- $joinConds = [
- 'pagelinks' => [
- 'LEFT JOIN', [
- 'pl_namespace = page_namespace',
- 'pl_title = page_title'
- ]
- ],
- 'templatelinks' => [
- 'LEFT JOIN', [
- 'tl_namespace = page_namespace',
- 'tl_title = page_title'
- ]
- ]
- ];
-
- // Allow extensions to modify the query
- Hooks::run( 'LonelyPagesQuery', [ &$tables, &$conds, &$joinConds ] );
-
- return [
- 'tables' => $tables,
- 'fields' => [
- 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_title'
- ],
- 'conds' => $conds,
- 'join_conds' => $joinConds
- ];
- }
-
- function getOrderFields() {
- // For some crazy reason ordering by a constant
- // causes a filesort in MySQL 5
- if ( count( MediaWikiServices::getInstance()->getNamespaceInfo()->
- getContentNamespaces() ) > 1
- ) {
- return [ 'page_namespace', 'page_title' ];
- } else {
- return [ 'page_title' ];
- }
- }
-
- protected function getGroupName() {
- return 'maintenance';
- }
-}
--- /dev/null
+<?php
+/**
+ * Implements Special:Longpages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialLongPages extends SpecialShortPages {
+ function __construct( $name = 'Longpages' ) {
+ parent::__construct( $name );
+ }
+
+ function sortDescending() {
+ return true;
+ }
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
+}
+++ /dev/null
-<?php
-/**
- * Implements Special:Longpages
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- */
-
-/**
- *
- * @ingroup SpecialPage
- */
-class LongPagesPage extends ShortPagesPage {
- function __construct( $name = 'Longpages' ) {
- parent::__construct( $name );
- }
-
- function sortDescending() {
- return true;
- }
-
- protected function getGroupName() {
- return 'maintenance';
- }
-}
--- /dev/null
+<?php
+/**
+ * Implements Special:MIMESearch
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Searches the database for files of the requested MIME type, comparing this with the
+ * 'img_major_mime' and 'img_minor_mime' fields in the image table.
+ * @ingroup SpecialPage
+ */
+class SpecialMIMESearch extends QueryPage {
+ protected $major, $minor, $mime;
+
+ function __construct( $name = 'MIMEsearch' ) {
+ parent::__construct( $name );
+ }
+
+ public function isExpensive() {
+ return false;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ function isCacheable() {
+ return false;
+ }
+
+ function linkParameters() {
+ return [ 'mime' => "{$this->major}/{$this->minor}" ];
+ }
+
+ public function getQueryInfo() {
+ $minorType = [];
+ if ( $this->minor !== '*' ) {
+ // Allow wildcard searching
+ $minorType['img_minor_mime'] = $this->minor;
+ }
+ $imgQuery = LocalFile::getQueryInfo();
+ $qi = [
+ 'tables' => $imgQuery['tables'],
+ 'fields' => [
+ 'namespace' => NS_FILE,
+ 'title' => 'img_name',
+ // Still have a value field just in case,
+ // but it isn't actually used for sorting.
+ 'value' => 'img_name',
+ 'img_size',
+ 'img_width',
+ 'img_height',
+ 'img_user_text' => $imgQuery['fields']['img_user_text'],
+ 'img_timestamp'
+ ],
+ 'conds' => [
+ 'img_major_mime' => $this->major,
+ // This is in order to trigger using
+ // the img_media_mime index in "range" mode.
+ // @todo how is order defined? use MimeAnalyzer::getMediaTypes?
+ 'img_media_type' => [
+ MEDIATYPE_BITMAP,
+ MEDIATYPE_DRAWING,
+ MEDIATYPE_AUDIO,
+ MEDIATYPE_VIDEO,
+ MEDIATYPE_MULTIMEDIA,
+ MEDIATYPE_UNKNOWN,
+ MEDIATYPE_OFFICE,
+ MEDIATYPE_TEXT,
+ MEDIATYPE_EXECUTABLE,
+ MEDIATYPE_ARCHIVE,
+ MEDIATYPE_3D,
+ ],
+ ] + $minorType,
+ 'join_conds' => $imgQuery['joins'],
+ ];
+
+ return $qi;
+ }
+
+ /**
+ * The index is on (img_media_type, img_major_mime, img_minor_mime)
+ * which unfortunately doesn't have img_name at the end for sorting.
+ * So tell db to sort it however it wishes (Its not super important
+ * that this report gives results in a logical order). As an aditional
+ * note, mysql seems to by default order things by img_name ASC, which
+ * is what we ideally want, so everything works out fine anyhow.
+ * @return array
+ */
+ function getOrderFields() {
+ return [];
+ }
+
+ /**
+ * Generate and output the form
+ */
+ function getPageHeader() {
+ $formDescriptor = [
+ 'mime' => [
+ 'type' => 'combobox',
+ 'options' => $this->getSuggestionsForTypes(),
+ 'name' => 'mime',
+ 'label-message' => 'mimetype',
+ 'required' => true,
+ 'default' => $this->mime,
+ ],
+ ];
+
+ HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
+ ->setSubmitTextMsg( 'ilsubmit' )
+ ->setAction( $this->getPageTitle()->getLocalURL() )
+ ->setMethod( 'get' )
+ ->prepareForm()
+ ->displayForm( false );
+ return '';
+ }
+
+ protected function getSuggestionsForTypes() {
+ $dbr = wfGetDB( DB_REPLICA );
+ $lastMajor = null;
+ $suggestions = [];
+ $result = $dbr->select(
+ [ 'image' ],
+ // We ignore img_media_type, but using it in the query is needed for MySQL to choose a
+ // sensible execution plan
+ [ 'img_media_type', 'img_major_mime', 'img_minor_mime' ],
+ [],
+ __METHOD__,
+ [ 'GROUP BY' => [ 'img_media_type', 'img_major_mime', 'img_minor_mime' ] ]
+ );
+ foreach ( $result as $row ) {
+ $major = $row->img_major_mime;
+ $minor = $row->img_minor_mime;
+ $suggestions[ "$major/$minor" ] = "$major/$minor";
+ if ( $lastMajor === $major ) {
+ // If there are at least two with the same major mime type, also include the wildcard
+ $suggestions[ "$major/*" ] = "$major/*";
+ }
+ $lastMajor = $major;
+ }
+ ksort( $suggestions );
+ return $suggestions;
+ }
+
+ public function execute( $par ) {
+ $this->addHelpLink( 'Help:Managing_files' );
+ $this->mime = $par ?: $this->getRequest()->getText( 'mime' );
+ $this->mime = trim( $this->mime );
+ list( $this->major, $this->minor ) = File::splitMime( $this->mime );
+
+ if ( $this->major == '' || $this->minor == '' || $this->minor == 'unknown' ||
+ !self::isValidType( $this->major )
+ ) {
+ $this->setHeaders();
+ $this->outputHeader();
+ $this->getPageHeader();
+ return;
+ }
+
+ parent::execute( $par );
+ }
+
+ /**
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
+ */
+ function formatResult( $skin, $result ) {
+ $linkRenderer = $this->getLinkRenderer();
+ $nt = Title::makeTitle( $result->namespace, $result->title );
+ $text = MediaWikiServices::getInstance()->getContentLanguage()
+ ->convert( htmlspecialchars( $nt->getText() ) );
+ $plink = $linkRenderer->makeLink(
+ Title::newFromText( $nt->getPrefixedText() ),
+ new HtmlArmor( $text )
+ );
+
+ $download = Linker::makeMediaLinkObj( $nt, $this->msg( 'download' )->escaped() );
+ $download = $this->msg( 'parentheses' )->rawParams( $download )->escaped();
+ $lang = $this->getLanguage();
+ $bytes = htmlspecialchars( $lang->formatSize( $result->img_size ) );
+ $dimensions = $this->msg( 'widthheight' )->numParams( $result->img_width,
+ $result->img_height )->escaped();
+ $user = $linkRenderer->makeLink(
+ Title::makeTitle( NS_USER, $result->img_user_text ),
+ $result->img_user_text
+ );
+
+ $time = $lang->userTimeAndDate( $result->img_timestamp, $this->getUser() );
+ $time = htmlspecialchars( $time );
+
+ return "$download $plink . . $dimensions . . $bytes . . $user . . $time";
+ }
+
+ /**
+ * @param string $type
+ * @return bool
+ */
+ protected static function isValidType( $type ) {
+ // From maintenance/tables.sql => img_major_mime
+ $types = [
+ 'unknown',
+ 'application',
+ 'audio',
+ 'image',
+ 'text',
+ 'video',
+ 'message',
+ 'model',
+ 'multipart',
+ 'chemical'
+ ];
+
+ return in_array( $type, $types );
+ }
+
+ public function preprocessResults( $db, $res ) {
+ $this->executeLBFromResultWrapper( $res );
+ }
+
+ protected function getGroupName() {
+ return 'media';
+ }
+}
+++ /dev/null
-<?php
-/**
- * Implements Special:MIMESearch
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- */
-
-use MediaWiki\MediaWikiServices;
-
-/**
- * Searches the database for files of the requested MIME type, comparing this with the
- * 'img_major_mime' and 'img_minor_mime' fields in the image table.
- * @ingroup SpecialPage
- */
-class MIMEsearchPage extends QueryPage {
- protected $major, $minor, $mime;
-
- function __construct( $name = 'MIMEsearch' ) {
- parent::__construct( $name );
- }
-
- public function isExpensive() {
- return false;
- }
-
- function isSyndicated() {
- return false;
- }
-
- function isCacheable() {
- return false;
- }
-
- function linkParameters() {
- return [ 'mime' => "{$this->major}/{$this->minor}" ];
- }
-
- public function getQueryInfo() {
- $minorType = [];
- if ( $this->minor !== '*' ) {
- // Allow wildcard searching
- $minorType['img_minor_mime'] = $this->minor;
- }
- $imgQuery = LocalFile::getQueryInfo();
- $qi = [
- 'tables' => $imgQuery['tables'],
- 'fields' => [
- 'namespace' => NS_FILE,
- 'title' => 'img_name',
- // Still have a value field just in case,
- // but it isn't actually used for sorting.
- 'value' => 'img_name',
- 'img_size',
- 'img_width',
- 'img_height',
- 'img_user_text' => $imgQuery['fields']['img_user_text'],
- 'img_timestamp'
- ],
- 'conds' => [
- 'img_major_mime' => $this->major,
- // This is in order to trigger using
- // the img_media_mime index in "range" mode.
- // @todo how is order defined? use MimeAnalyzer::getMediaTypes?
- 'img_media_type' => [
- MEDIATYPE_BITMAP,
- MEDIATYPE_DRAWING,
- MEDIATYPE_AUDIO,
- MEDIATYPE_VIDEO,
- MEDIATYPE_MULTIMEDIA,
- MEDIATYPE_UNKNOWN,
- MEDIATYPE_OFFICE,
- MEDIATYPE_TEXT,
- MEDIATYPE_EXECUTABLE,
- MEDIATYPE_ARCHIVE,
- MEDIATYPE_3D,
- ],
- ] + $minorType,
- 'join_conds' => $imgQuery['joins'],
- ];
-
- return $qi;
- }
-
- /**
- * The index is on (img_media_type, img_major_mime, img_minor_mime)
- * which unfortunately doesn't have img_name at the end for sorting.
- * So tell db to sort it however it wishes (Its not super important
- * that this report gives results in a logical order). As an aditional
- * note, mysql seems to by default order things by img_name ASC, which
- * is what we ideally want, so everything works out fine anyhow.
- * @return array
- */
- function getOrderFields() {
- return [];
- }
-
- /**
- * Generate and output the form
- */
- function getPageHeader() {
- $formDescriptor = [
- 'mime' => [
- 'type' => 'combobox',
- 'options' => $this->getSuggestionsForTypes(),
- 'name' => 'mime',
- 'label-message' => 'mimetype',
- 'required' => true,
- 'default' => $this->mime,
- ],
- ];
-
- HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
- ->setSubmitTextMsg( 'ilsubmit' )
- ->setAction( $this->getPageTitle()->getLocalURL() )
- ->setMethod( 'get' )
- ->prepareForm()
- ->displayForm( false );
- return '';
- }
-
- protected function getSuggestionsForTypes() {
- $dbr = wfGetDB( DB_REPLICA );
- $lastMajor = null;
- $suggestions = [];
- $result = $dbr->select(
- [ 'image' ],
- // We ignore img_media_type, but using it in the query is needed for MySQL to choose a
- // sensible execution plan
- [ 'img_media_type', 'img_major_mime', 'img_minor_mime' ],
- [],
- __METHOD__,
- [ 'GROUP BY' => [ 'img_media_type', 'img_major_mime', 'img_minor_mime' ] ]
- );
- foreach ( $result as $row ) {
- $major = $row->img_major_mime;
- $minor = $row->img_minor_mime;
- $suggestions[ "$major/$minor" ] = "$major/$minor";
- if ( $lastMajor === $major ) {
- // If there are at least two with the same major mime type, also include the wildcard
- $suggestions[ "$major/*" ] = "$major/*";
- }
- $lastMajor = $major;
- }
- ksort( $suggestions );
- return $suggestions;
- }
-
- public function execute( $par ) {
- $this->addHelpLink( 'Help:Managing_files' );
- $this->mime = $par ?: $this->getRequest()->getText( 'mime' );
- $this->mime = trim( $this->mime );
- list( $this->major, $this->minor ) = File::splitMime( $this->mime );
-
- if ( $this->major == '' || $this->minor == '' || $this->minor == 'unknown' ||
- !self::isValidType( $this->major )
- ) {
- $this->setHeaders();
- $this->outputHeader();
- $this->getPageHeader();
- return;
- }
-
- parent::execute( $par );
- }
-
- /**
- * @param Skin $skin
- * @param object $result Result row
- * @return string
- */
- function formatResult( $skin, $result ) {
- $linkRenderer = $this->getLinkRenderer();
- $nt = Title::makeTitle( $result->namespace, $result->title );
- $text = MediaWikiServices::getInstance()->getContentLanguage()
- ->convert( htmlspecialchars( $nt->getText() ) );
- $plink = $linkRenderer->makeLink(
- Title::newFromText( $nt->getPrefixedText() ),
- new HtmlArmor( $text )
- );
-
- $download = Linker::makeMediaLinkObj( $nt, $this->msg( 'download' )->escaped() );
- $download = $this->msg( 'parentheses' )->rawParams( $download )->escaped();
- $lang = $this->getLanguage();
- $bytes = htmlspecialchars( $lang->formatSize( $result->img_size ) );
- $dimensions = $this->msg( 'widthheight' )->numParams( $result->img_width,
- $result->img_height )->escaped();
- $user = $linkRenderer->makeLink(
- Title::makeTitle( NS_USER, $result->img_user_text ),
- $result->img_user_text
- );
-
- $time = $lang->userTimeAndDate( $result->img_timestamp, $this->getUser() );
- $time = htmlspecialchars( $time );
-
- return "$download $plink . . $dimensions . . $bytes . . $user . . $time";
- }
-
- /**
- * @param string $type
- * @return bool
- */
- protected static function isValidType( $type ) {
- // From maintenance/tables.sql => img_major_mime
- $types = [
- 'unknown',
- 'application',
- 'audio',
- 'image',
- 'text',
- 'video',
- 'message',
- 'model',
- 'multipart',
- 'chemical'
- ];
-
- return in_array( $type, $types );
- }
-
- public function preprocessResults( $db, $res ) {
- $this->executeLBFromResultWrapper( $res );
- }
-
- protected function getGroupName() {
- return 'media';
- }
-}
/**
* @ingroup SpecialPage
*/
-class MediaStatisticsPage extends QueryPage {
+class SpecialMediaStatistics extends QueryPage {
protected $totalCount = 0, $totalBytes = 0;
/**
--- /dev/null
+<?php
+/**
+ * Implements Special:Mostcategories
+ *
+ * Copyright © 2005 Ævar Arnfjörð Bjarmason
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ */
+
+use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\IResultWrapper;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * A special page that list pages that have highest category count
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialMostCategories extends QueryPage {
+ function __construct( $name = 'Mostcategories' ) {
+ parent::__construct( $name );
+ }
+
+ public function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ public function getQueryInfo() {
+ return [
+ 'tables' => [ 'categorylinks', 'page' ],
+ 'fields' => [
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'COUNT(*)'
+ ],
+ 'conds' => [ 'page_namespace' =>
+ MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces() ],
+ 'options' => [
+ 'HAVING' => 'COUNT(*) > 1',
+ 'GROUP BY' => [ 'page_namespace', 'page_title' ]
+ ],
+ 'join_conds' => [
+ 'page' => [
+ 'LEFT JOIN',
+ 'page_id = cl_from'
+ ]
+ ]
+ ];
+ }
+
+ /**
+ * @param IDatabase $db
+ * @param IResultWrapper $res
+ */
+ function preprocessResults( $db, $res ) {
+ $this->executeLBFromResultWrapper( $res );
+ }
+
+ /**
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
+ */
+ function formatResult( $skin, $result ) {
+ $title = Title::makeTitleSafe( $result->namespace, $result->title );
+ if ( !$title ) {
+ return Html::element(
+ 'span',
+ [ 'class' => 'mw-invalidtitle' ],
+ Linker::getInvalidTitleDescription(
+ $this->getContext(),
+ $result->namespace,
+ $result->title
+ )
+ );
+ }
+
+ $linkRenderer = $this->getLinkRenderer();
+ if ( $this->isCached() ) {
+ $link = $linkRenderer->makeLink( $title );
+ } else {
+ $link = $linkRenderer->makeKnownLink( $title );
+ }
+
+ $count = $this->msg( 'ncategories' )->numParams( $result->value )->escaped();
+
+ return $this->getLanguage()->specialList( $link, $count );
+ }
+
+ protected function getGroupName() {
+ return 'highuse';
+ }
+}
--- /dev/null
+<?php
+/**
+ * Implements Special:Mostinterwikis
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\IResultWrapper;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * A special page that listed pages that have highest interwiki count
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialMostInterwikis extends QueryPage {
+ function __construct( $name = 'Mostinterwikis' ) {
+ parent::__construct( $name );
+ }
+
+ public function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ public function getQueryInfo() {
+ return [
+ 'tables' => [
+ 'langlinks',
+ 'page'
+ ], 'fields' => [
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'COUNT(*)'
+ ], 'conds' => [
+ 'page_namespace' =>
+ MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces()
+ ], 'options' => [
+ 'HAVING' => 'COUNT(*) > 1',
+ 'GROUP BY' => [
+ 'page_namespace',
+ 'page_title'
+ ]
+ ], 'join_conds' => [
+ 'page' => [
+ 'LEFT JOIN',
+ 'page_id = ll_from'
+ ]
+ ]
+ ];
+ }
+
+ /**
+ * Pre-fill the link cache
+ *
+ * @param IDatabase $db
+ * @param IResultWrapper $res
+ */
+ function preprocessResults( $db, $res ) {
+ $this->executeLBFromResultWrapper( $res );
+ }
+
+ /**
+ * @param Skin $skin
+ * @param object $result
+ * @return string
+ */
+ function formatResult( $skin, $result ) {
+ $title = Title::makeTitleSafe( $result->namespace, $result->title );
+ if ( !$title ) {
+ return Html::element(
+ 'span',
+ [ 'class' => 'mw-invalidtitle' ],
+ Linker::getInvalidTitleDescription(
+ $this->getContext(),
+ $result->namespace,
+ $result->title
+ )
+ );
+ }
+
+ $linkRenderer = $this->getLinkRenderer();
+ if ( $this->isCached() ) {
+ $link = $linkRenderer->makeLink( $title );
+ } else {
+ $link = $linkRenderer->makeKnownLink( $title );
+ }
+
+ $count = $this->msg( 'ninterwikis' )->numParams( $result->value )->escaped();
+
+ return $this->getLanguage()->specialList( $link, $count );
+ }
+
+ protected function getGroupName() {
+ return 'highuse';
+ }
+}
--- /dev/null
+<?php
+/**
+ * Implements Special:Mostlinked
+ *
+ * Copyright © 2005 Ævar Arnfjörð Bjarmason, 2006 Rob Church
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ * @author Rob Church <robchur@gmail.com>
+ */
+
+use Wikimedia\Rdbms\IResultWrapper;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * A special page to show pages ordered by the number of pages linking to them.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialMostLinked extends QueryPage {
+ function __construct( $name = 'Mostlinked' ) {
+ parent::__construct( $name );
+ }
+
+ public function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ public function getQueryInfo() {
+ return [
+ 'tables' => [ 'pagelinks', 'page' ],
+ 'fields' => [
+ 'namespace' => 'pl_namespace',
+ 'title' => 'pl_title',
+ 'value' => 'COUNT(*)',
+ 'page_namespace'
+ ],
+ 'options' => [
+ 'HAVING' => 'COUNT(*) > 1',
+ 'GROUP BY' => [
+ 'pl_namespace', 'pl_title',
+ 'page_namespace'
+ ]
+ ],
+ 'join_conds' => [
+ 'page' => [
+ 'LEFT JOIN',
+ [
+ 'page_namespace = pl_namespace',
+ 'page_title = pl_title'
+ ]
+ ]
+ ]
+ ];
+ }
+
+ /**
+ * Pre-fill the link cache
+ *
+ * @param IDatabase $db
+ * @param IResultWrapper $res
+ */
+ function preprocessResults( $db, $res ) {
+ $this->executeLBFromResultWrapper( $res );
+ }
+
+ /**
+ * Make a link to "what links here" for the specified title
+ *
+ * @param Title $title Title being queried
+ * @param string $caption Text to display on the link
+ * @return string
+ */
+ function makeWlhLink( $title, $caption ) {
+ $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedDBkey() );
+
+ $linkRenderer = $this->getLinkRenderer();
+ return $linkRenderer->makeKnownLink( $wlh, $caption );
+ }
+
+ /**
+ * Make links to the page corresponding to the item,
+ * and the "what links here" page for it
+ *
+ * @param Skin $skin Skin to be used
+ * @param object $result Result row
+ * @return string
+ */
+ function formatResult( $skin, $result ) {
+ $title = Title::makeTitleSafe( $result->namespace, $result->title );
+ if ( !$title ) {
+ return Html::element(
+ 'span',
+ [ 'class' => 'mw-invalidtitle' ],
+ Linker::getInvalidTitleDescription(
+ $this->getContext(),
+ $result->namespace,
+ $result->title )
+ );
+ }
+
+ $linkRenderer = $this->getLinkRenderer();
+ $link = $linkRenderer->makeLink( $title );
+ $wlh = $this->makeWlhLink(
+ $title,
+ $this->msg( 'nlinks' )->numParams( $result->value )->text()
+ );
+
+ return $this->getLanguage()->specialList( $link, $wlh );
+ }
+
+ protected function getGroupName() {
+ return 'highuse';
+ }
+}
--- /dev/null
+<?php
+/**
+ * Implements Special:Mostlinkedcategories
+ *
+ * Copyright © 2005, Ævar Arnfjörð Bjarmason
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ */
+
+use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\IResultWrapper;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * A querypage to show categories ordered in descending order by the pages in them
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialMostLinkedCategories extends QueryPage {
+ function __construct( $name = 'Mostlinkedcategories' ) {
+ parent::__construct( $name );
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ public function getQueryInfo() {
+ return [
+ 'tables' => [ 'category' ],
+ 'fields' => [ 'title' => 'cat_title',
+ 'namespace' => NS_CATEGORY,
+ 'value' => 'cat_pages' ],
+ 'conds' => [ 'cat_pages > 0' ],
+ ];
+ }
+
+ function sortDescending() {
+ return true;
+ }
+
+ /**
+ * Fetch user page links and cache their existence
+ *
+ * @param IDatabase $db
+ * @param IResultWrapper $res
+ */
+ function preprocessResults( $db, $res ) {
+ $this->executeLBFromResultWrapper( $res );
+ }
+
+ /**
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
+ */
+ function formatResult( $skin, $result ) {
+ $nt = Title::makeTitleSafe( NS_CATEGORY, $result->title );
+ if ( !$nt ) {
+ return Html::element(
+ 'span',
+ [ 'class' => 'mw-invalidtitle' ],
+ Linker::getInvalidTitleDescription(
+ $this->getContext(),
+ NS_CATEGORY,
+ $result->title )
+ );
+ }
+
+ $text = MediaWikiServices::getInstance()->getContentLanguage()
+ ->convert( htmlspecialchars( $nt->getText() ) );
+ $plink = $this->getLinkRenderer()->makeLink( $nt, new HtmlArmor( $text ) );
+ $nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
+
+ return $this->getLanguage()->specialList( $plink, $nlinks );
+ }
+
+ protected function getGroupName() {
+ return 'highuse';
+ }
+}
--- /dev/null
+<?php
+/**
+ * Implements Special:Mostlinkedtemplates
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @author Rob Church <robchur@gmail.com>
+ */
+
+use Wikimedia\Rdbms\IResultWrapper;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * Special page lists templates with a large number of
+ * transclusion links, i.e. "most used" templates
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialMostLinkedTemplates extends QueryPage {
+ function __construct( $name = 'Mostlinkedtemplates' ) {
+ parent::__construct( $name );
+ }
+
+ /**
+ * Is this report expensive, i.e should it be cached?
+ *
+ * @return bool
+ */
+ public function isExpensive() {
+ return true;
+ }
+
+ /**
+ * Is there a feed available?
+ *
+ * @return bool
+ */
+ public function isSyndicated() {
+ return false;
+ }
+
+ /**
+ * Sort the results in descending order?
+ *
+ * @return bool
+ */
+ public function sortDescending() {
+ return true;
+ }
+
+ public function getQueryInfo() {
+ return [
+ 'tables' => [ 'templatelinks' ],
+ 'fields' => [
+ 'namespace' => 'tl_namespace',
+ 'title' => 'tl_title',
+ 'value' => 'COUNT(*)'
+ ],
+ 'options' => [ 'GROUP BY' => [ 'tl_namespace', 'tl_title' ] ]
+ ];
+ }
+
+ /**
+ * Pre-cache page existence to speed up link generation
+ *
+ * @param IDatabase $db
+ * @param IResultWrapper $res
+ */
+ public function preprocessResults( $db, $res ) {
+ $this->executeLBFromResultWrapper( $res );
+ }
+
+ /**
+ * Format a result row
+ *
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
+ */
+ public function formatResult( $skin, $result ) {
+ $title = Title::makeTitleSafe( $result->namespace, $result->title );
+ if ( !$title ) {
+ return Html::element(
+ 'span',
+ [ 'class' => 'mw-invalidtitle' ],
+ Linker::getInvalidTitleDescription(
+ $this->getContext(),
+ $result->namespace,
+ $result->title
+ )
+ );
+ }
+
+ return $this->getLanguage()->specialList(
+ $this->getLinkRenderer()->makeLink( $title ),
+ $this->makeWlhLink( $title, $result )
+ );
+ }
+
+ /**
+ * Make a "what links here" link for a given title
+ *
+ * @param Title $title Title to make the link for
+ * @param object $result Result row
+ * @return string
+ */
+ private function makeWlhLink( $title, $result ) {
+ $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() );
+ $label = $this->msg( 'ntransclusions' )->numParams( $result->value )->text();
+
+ return $this->getLinkRenderer()->makeLink( $wlh, $label );
+ }
+
+ protected function getGroupName() {
+ return 'highuse';
+ }
+}
--- /dev/null
+<?php
+/**
+ * Implements Special:Mostrevisions
+ *
+ * Copyright © 2005 Ævar Arnfjörð Bjarmason
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ */
+
+class SpecialMostRevisions extends SpecialFewestRevisions {
+ function __construct( $name = 'Mostrevisions' ) {
+ parent::__construct( $name );
+ }
+
+ function sortDescending() {
+ return true;
+ }
+
+ protected function getGroupName() {
+ return 'highuse';
+ }
+}
+++ /dev/null
-<?php
-/**
- * Implements Special:Mostcategories
- *
- * Copyright © 2005 Ævar Arnfjörð Bjarmason
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- */
-
-use MediaWiki\MediaWikiServices;
-use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
-
-/**
- * A special page that list pages that have highest category count
- *
- * @ingroup SpecialPage
- */
-class MostcategoriesPage extends QueryPage {
- function __construct( $name = 'Mostcategories' ) {
- parent::__construct( $name );
- }
-
- public function isExpensive() {
- return true;
- }
-
- function isSyndicated() {
- return false;
- }
-
- public function getQueryInfo() {
- return [
- 'tables' => [ 'categorylinks', 'page' ],
- 'fields' => [
- 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'COUNT(*)'
- ],
- 'conds' => [ 'page_namespace' =>
- MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces() ],
- 'options' => [
- 'HAVING' => 'COUNT(*) > 1',
- 'GROUP BY' => [ 'page_namespace', 'page_title' ]
- ],
- 'join_conds' => [
- 'page' => [
- 'LEFT JOIN',
- 'page_id = cl_from'
- ]
- ]
- ];
- }
-
- /**
- * @param IDatabase $db
- * @param IResultWrapper $res
- */
- function preprocessResults( $db, $res ) {
- $this->executeLBFromResultWrapper( $res );
- }
-
- /**
- * @param Skin $skin
- * @param object $result Result row
- * @return string
- */
- function formatResult( $skin, $result ) {
- $title = Title::makeTitleSafe( $result->namespace, $result->title );
- if ( !$title ) {
- return Html::element(
- 'span',
- [ 'class' => 'mw-invalidtitle' ],
- Linker::getInvalidTitleDescription(
- $this->getContext(),
- $result->namespace,
- $result->title
- )
- );
- }
-
- $linkRenderer = $this->getLinkRenderer();
- if ( $this->isCached() ) {
- $link = $linkRenderer->makeLink( $title );
- } else {
- $link = $linkRenderer->makeKnownLink( $title );
- }
-
- $count = $this->msg( 'ncategories' )->numParams( $result->value )->escaped();
-
- return $this->getLanguage()->specialList( $link, $count );
- }
-
- protected function getGroupName() {
- return 'highuse';
- }
-}
+++ /dev/null
-<?php
-/**
- * Implements Special:Mostinterwikis
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- */
-
-use MediaWiki\MediaWikiServices;
-use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
-
-/**
- * A special page that listed pages that have highest interwiki count
- *
- * @ingroup SpecialPage
- */
-class MostinterwikisPage extends QueryPage {
- function __construct( $name = 'Mostinterwikis' ) {
- parent::__construct( $name );
- }
-
- public function isExpensive() {
- return true;
- }
-
- function isSyndicated() {
- return false;
- }
-
- public function getQueryInfo() {
- return [
- 'tables' => [
- 'langlinks',
- 'page'
- ], 'fields' => [
- 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'COUNT(*)'
- ], 'conds' => [
- 'page_namespace' =>
- MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces()
- ], 'options' => [
- 'HAVING' => 'COUNT(*) > 1',
- 'GROUP BY' => [
- 'page_namespace',
- 'page_title'
- ]
- ], 'join_conds' => [
- 'page' => [
- 'LEFT JOIN',
- 'page_id = ll_from'
- ]
- ]
- ];
- }
-
- /**
- * Pre-fill the link cache
- *
- * @param IDatabase $db
- * @param IResultWrapper $res
- */
- function preprocessResults( $db, $res ) {
- $this->executeLBFromResultWrapper( $res );
- }
-
- /**
- * @param Skin $skin
- * @param object $result
- * @return string
- */
- function formatResult( $skin, $result ) {
- $title = Title::makeTitleSafe( $result->namespace, $result->title );
- if ( !$title ) {
- return Html::element(
- 'span',
- [ 'class' => 'mw-invalidtitle' ],
- Linker::getInvalidTitleDescription(
- $this->getContext(),
- $result->namespace,
- $result->title
- )
- );
- }
-
- $linkRenderer = $this->getLinkRenderer();
- if ( $this->isCached() ) {
- $link = $linkRenderer->makeLink( $title );
- } else {
- $link = $linkRenderer->makeKnownLink( $title );
- }
-
- $count = $this->msg( 'ninterwikis' )->numParams( $result->value )->escaped();
-
- return $this->getLanguage()->specialList( $link, $count );
- }
-
- protected function getGroupName() {
- return 'highuse';
- }
-}
+++ /dev/null
-<?php
-/**
- * Implements Special:Mostlinked
- *
- * Copyright © 2005 Ævar Arnfjörð Bjarmason, 2006 Rob Church
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- * @author Rob Church <robchur@gmail.com>
- */
-
-use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
-
-/**
- * A special page to show pages ordered by the number of pages linking to them.
- *
- * @ingroup SpecialPage
- */
-class MostlinkedPage extends QueryPage {
- function __construct( $name = 'Mostlinked' ) {
- parent::__construct( $name );
- }
-
- public function isExpensive() {
- return true;
- }
-
- function isSyndicated() {
- return false;
- }
-
- public function getQueryInfo() {
- return [
- 'tables' => [ 'pagelinks', 'page' ],
- 'fields' => [
- 'namespace' => 'pl_namespace',
- 'title' => 'pl_title',
- 'value' => 'COUNT(*)',
- 'page_namespace'
- ],
- 'options' => [
- 'HAVING' => 'COUNT(*) > 1',
- 'GROUP BY' => [
- 'pl_namespace', 'pl_title',
- 'page_namespace'
- ]
- ],
- 'join_conds' => [
- 'page' => [
- 'LEFT JOIN',
- [
- 'page_namespace = pl_namespace',
- 'page_title = pl_title'
- ]
- ]
- ]
- ];
- }
-
- /**
- * Pre-fill the link cache
- *
- * @param IDatabase $db
- * @param IResultWrapper $res
- */
- function preprocessResults( $db, $res ) {
- $this->executeLBFromResultWrapper( $res );
- }
-
- /**
- * Make a link to "what links here" for the specified title
- *
- * @param Title $title Title being queried
- * @param string $caption Text to display on the link
- * @return string
- */
- function makeWlhLink( $title, $caption ) {
- $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedDBkey() );
-
- $linkRenderer = $this->getLinkRenderer();
- return $linkRenderer->makeKnownLink( $wlh, $caption );
- }
-
- /**
- * Make links to the page corresponding to the item,
- * and the "what links here" page for it
- *
- * @param Skin $skin Skin to be used
- * @param object $result Result row
- * @return string
- */
- function formatResult( $skin, $result ) {
- $title = Title::makeTitleSafe( $result->namespace, $result->title );
- if ( !$title ) {
- return Html::element(
- 'span',
- [ 'class' => 'mw-invalidtitle' ],
- Linker::getInvalidTitleDescription(
- $this->getContext(),
- $result->namespace,
- $result->title )
- );
- }
-
- $linkRenderer = $this->getLinkRenderer();
- $link = $linkRenderer->makeLink( $title );
- $wlh = $this->makeWlhLink(
- $title,
- $this->msg( 'nlinks' )->numParams( $result->value )->text()
- );
-
- return $this->getLanguage()->specialList( $link, $wlh );
- }
-
- protected function getGroupName() {
- return 'highuse';
- }
-}
+++ /dev/null
-<?php
-/**
- * Implements Special:Mostlinkedcategories
- *
- * Copyright © 2005, Ævar Arnfjörð Bjarmason
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- */
-
-use MediaWiki\MediaWikiServices;
-use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
-
-/**
- * A querypage to show categories ordered in descending order by the pages in them
- *
- * @ingroup SpecialPage
- */
-class MostlinkedCategoriesPage extends QueryPage {
- function __construct( $name = 'Mostlinkedcategories' ) {
- parent::__construct( $name );
- }
-
- function isSyndicated() {
- return false;
- }
-
- public function getQueryInfo() {
- return [
- 'tables' => [ 'category' ],
- 'fields' => [ 'title' => 'cat_title',
- 'namespace' => NS_CATEGORY,
- 'value' => 'cat_pages' ],
- 'conds' => [ 'cat_pages > 0' ],
- ];
- }
-
- function sortDescending() {
- return true;
- }
-
- /**
- * Fetch user page links and cache their existence
- *
- * @param IDatabase $db
- * @param IResultWrapper $res
- */
- function preprocessResults( $db, $res ) {
- $this->executeLBFromResultWrapper( $res );
- }
-
- /**
- * @param Skin $skin
- * @param object $result Result row
- * @return string
- */
- function formatResult( $skin, $result ) {
- $nt = Title::makeTitleSafe( NS_CATEGORY, $result->title );
- if ( !$nt ) {
- return Html::element(
- 'span',
- [ 'class' => 'mw-invalidtitle' ],
- Linker::getInvalidTitleDescription(
- $this->getContext(),
- NS_CATEGORY,
- $result->title )
- );
- }
-
- $text = MediaWikiServices::getInstance()->getContentLanguage()
- ->convert( htmlspecialchars( $nt->getText() ) );
- $plink = $this->getLinkRenderer()->makeLink( $nt, new HtmlArmor( $text ) );
- $nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
-
- return $this->getLanguage()->specialList( $plink, $nlinks );
- }
-
- protected function getGroupName() {
- return 'highuse';
- }
-}
+++ /dev/null
-<?php
-/**
- * Implements Special:Mostlinkedtemplates
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- * @author Rob Church <robchur@gmail.com>
- */
-
-use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
-
-/**
- * Special page lists templates with a large number of
- * transclusion links, i.e. "most used" templates
- *
- * @ingroup SpecialPage
- */
-class MostlinkedTemplatesPage extends QueryPage {
- function __construct( $name = 'Mostlinkedtemplates' ) {
- parent::__construct( $name );
- }
-
- /**
- * Is this report expensive, i.e should it be cached?
- *
- * @return bool
- */
- public function isExpensive() {
- return true;
- }
-
- /**
- * Is there a feed available?
- *
- * @return bool
- */
- public function isSyndicated() {
- return false;
- }
-
- /**
- * Sort the results in descending order?
- *
- * @return bool
- */
- public function sortDescending() {
- return true;
- }
-
- public function getQueryInfo() {
- return [
- 'tables' => [ 'templatelinks' ],
- 'fields' => [
- 'namespace' => 'tl_namespace',
- 'title' => 'tl_title',
- 'value' => 'COUNT(*)'
- ],
- 'options' => [ 'GROUP BY' => [ 'tl_namespace', 'tl_title' ] ]
- ];
- }
-
- /**
- * Pre-cache page existence to speed up link generation
- *
- * @param IDatabase $db
- * @param IResultWrapper $res
- */
- public function preprocessResults( $db, $res ) {
- $this->executeLBFromResultWrapper( $res );
- }
-
- /**
- * Format a result row
- *
- * @param Skin $skin
- * @param object $result Result row
- * @return string
- */
- public function formatResult( $skin, $result ) {
- $title = Title::makeTitleSafe( $result->namespace, $result->title );
- if ( !$title ) {
- return Html::element(
- 'span',
- [ 'class' => 'mw-invalidtitle' ],
- Linker::getInvalidTitleDescription(
- $this->getContext(),
- $result->namespace,
- $result->title
- )
- );
- }
-
- return $this->getLanguage()->specialList(
- $this->getLinkRenderer()->makeLink( $title ),
- $this->makeWlhLink( $title, $result )
- );
- }
-
- /**
- * Make a "what links here" link for a given title
- *
- * @param Title $title Title to make the link for
- * @param object $result Result row
- * @return string
- */
- private function makeWlhLink( $title, $result ) {
- $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() );
- $label = $this->msg( 'ntransclusions' )->numParams( $result->value )->text();
-
- return $this->getLinkRenderer()->makeLink( $wlh, $label );
- }
-
- protected function getGroupName() {
- return 'highuse';
- }
-}
+++ /dev/null
-<?php
-/**
- * Implements Special:Mostrevisions
- *
- * Copyright © 2005 Ævar Arnfjörð Bjarmason
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- */
-
-class MostrevisionsPage extends FewestrevisionsPage {
- function __construct( $name = 'Mostrevisions' ) {
- parent::__construct( $name );
- }
-
- function sortDescending() {
- return true;
- }
-
- protected function getGroupName() {
- return 'highuse';
- }
-}
--- /dev/null
+<?php
+/**
+ * Implements Special:Newimages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+use MediaWiki\MediaWikiServices;
+
+class SpecialNewFiles extends IncludableSpecialPage {
+ /** @var FormOptions */
+ protected $opts;
+
+ /** @var string[] */
+ protected $mediaTypes;
+
+ public function __construct() {
+ parent::__construct( 'Newimages' );
+ }
+
+ public function execute( $par ) {
+ $context = new DerivativeContext( $this->getContext() );
+
+ $this->setHeaders();
+ $this->outputHeader();
+ $mimeAnalyzer = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
+ $this->mediaTypes = $mimeAnalyzer->getMediaTypes();
+
+ $out = $this->getOutput();
+ $this->addHelpLink( 'Help:New images' );
+
+ $opts = new FormOptions();
+
+ $opts->add( 'like', '' );
+ $opts->add( 'user', '' );
+ $opts->add( 'showbots', false );
+ $opts->add( 'hidepatrolled', false );
+ $opts->add( 'mediatype', $this->mediaTypes );
+ $opts->add( 'limit', 50 );
+ $opts->add( 'offset', '' );
+ $opts->add( 'start', '' );
+ $opts->add( 'end', '' );
+
+ $opts->fetchValuesFromRequest( $this->getRequest() );
+
+ if ( $par !== null ) {
+ $opts->setValue( is_numeric( $par ) ? 'limit' : 'like', $par );
+ }
+
+ // If start date comes after end date chronologically, swap them.
+ // They are swapped in the interface by JS.
+ $start = $opts->getValue( 'start' );
+ $end = $opts->getValue( 'end' );
+ if ( $start !== '' && $end !== '' && $start > $end ) {
+ $temp = $end;
+ $end = $start;
+ $start = $temp;
+
+ $opts->setValue( 'start', $start, true );
+ $opts->setValue( 'end', $end, true );
+
+ // also swap values in request object, which is used by HTMLForm
+ // to pre-populate the fields with the previous input
+ $request = $context->getRequest();
+ $context->setRequest( new DerivativeRequest(
+ $request,
+ [ 'start' => $start, 'end' => $end ] + $request->getValues(),
+ $request->wasPosted()
+ ) );
+ }
+
+ // if all media types have been selected, wipe out the array to prevent
+ // the pointless IN(...) query condition (which would have no effect
+ // because every possible type has been selected)
+ $missingMediaTypes = array_diff( $this->mediaTypes, $opts->getValue( 'mediatype' ) );
+ if ( empty( $missingMediaTypes ) ) {
+ $opts->setValue( 'mediatype', [] );
+ }
+
+ $opts->validateIntBounds( 'limit', 0, 500 );
+
+ $this->opts = $opts;
+
+ if ( !$this->including() ) {
+ $this->setTopText();
+ $this->buildForm( $context );
+ }
+
+ $pager = new NewFilesPager( $context, $opts, $this->getLinkRenderer() );
+
+ $out->addHTML( $pager->getBody() );
+ if ( !$this->including() ) {
+ $out->addHTML( $pager->getNavigationBar() );
+ }
+ }
+
+ protected function buildForm( IContextSource $context ) {
+ $mediaTypesText = array_map( function ( $type ) {
+ // mediastatistics-header-unknown, mediastatistics-header-bitmap,
+ // mediastatistics-header-drawing, mediastatistics-header-audio,
+ // mediastatistics-header-video, mediastatistics-header-multimedia,
+ // mediastatistics-header-office, mediastatistics-header-text,
+ // mediastatistics-header-executable, mediastatistics-header-archive,
+ // mediastatistics-header-3d,
+ return $this->msg( 'mediastatistics-header-' . strtolower( $type ) )->text();
+ }, $this->mediaTypes );
+ $mediaTypesOptions = array_combine( $mediaTypesText, $this->mediaTypes );
+ ksort( $mediaTypesOptions );
+
+ $formDescriptor = [
+ 'like' => [
+ 'type' => 'text',
+ 'label-message' => 'newimages-label',
+ 'name' => 'like',
+ ],
+
+ 'user' => [
+ 'class' => 'HTMLUserTextField',
+ 'label-message' => 'newimages-user',
+ 'name' => 'user',
+ ],
+
+ 'showbots' => [
+ 'type' => 'check',
+ 'label-message' => 'newimages-showbots',
+ 'name' => 'showbots',
+ ],
+
+ 'hidepatrolled' => [
+ 'type' => 'check',
+ 'label-message' => 'newimages-hidepatrolled',
+ 'name' => 'hidepatrolled',
+ ],
+
+ 'mediatype' => [
+ 'type' => 'multiselect',
+ 'flatlist' => true,
+ 'name' => 'mediatype',
+ 'label-message' => 'newimages-mediatype',
+ 'options' => $mediaTypesOptions,
+ 'default' => $this->mediaTypes,
+ ],
+
+ 'limit' => [
+ 'type' => 'hidden',
+ 'default' => $this->opts->getValue( 'limit' ),
+ 'name' => 'limit',
+ ],
+
+ 'offset' => [
+ 'type' => 'hidden',
+ 'default' => $this->opts->getValue( 'offset' ),
+ 'name' => 'offset',
+ ],
+
+ 'start' => [
+ 'type' => 'date',
+ 'label-message' => 'date-range-from',
+ 'name' => 'start',
+ ],
+
+ 'end' => [
+ 'type' => 'date',
+ 'label-message' => 'date-range-to',
+ 'name' => 'end',
+ ],
+ ];
+
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
+ unset( $formDescriptor['like'] );
+ }
+
+ if ( !$this->getUser()->useFilePatrol() ) {
+ unset( $formDescriptor['hidepatrolled'] );
+ }
+
+ HTMLForm::factory( 'ooui', $formDescriptor, $context )
+ // For the 'multiselect' field values to be preserved on submit
+ ->setFormIdentifier( 'specialnewimages' )
+ ->setWrapperLegendMsg( 'newimages-legend' )
+ ->setSubmitTextMsg( 'ilsubmit' )
+ ->setMethod( 'get' )
+ ->prepareForm()
+ ->displayForm( false );
+ }
+
+ protected function getGroupName() {
+ return 'changes';
+ }
+
+ /**
+ * Send the text to be displayed above the options
+ */
+ function setTopText() {
+ $message = $this->msg( 'newimagestext' )->inContentLanguage();
+ if ( !$message->isDisabled() ) {
+ $contLang = MediaWikiServices::getInstance()->getContentLanguage();
+ $this->getOutput()->addWikiTextAsContent(
+ Html::rawElement( 'div',
+ [
+
+ 'lang' => $contLang->getHtmlCode(),
+ 'dir' => $contLang->getDir()
+ ],
+ "\n" . $message->plain() . "\n"
+ )
+ );
+ }
+ }
+}
+++ /dev/null
-<?php
-/**
- * Implements Special:Newimages
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- */
-
-use MediaWiki\MediaWikiServices;
-
-class SpecialNewFiles extends IncludableSpecialPage {
- /** @var FormOptions */
- protected $opts;
-
- /** @var string[] */
- protected $mediaTypes;
-
- public function __construct() {
- parent::__construct( 'Newimages' );
- }
-
- public function execute( $par ) {
- $context = new DerivativeContext( $this->getContext() );
-
- $this->setHeaders();
- $this->outputHeader();
- $mimeAnalyzer = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
- $this->mediaTypes = $mimeAnalyzer->getMediaTypes();
-
- $out = $this->getOutput();
- $this->addHelpLink( 'Help:New images' );
-
- $opts = new FormOptions();
-
- $opts->add( 'like', '' );
- $opts->add( 'user', '' );
- $opts->add( 'showbots', false );
- $opts->add( 'hidepatrolled', false );
- $opts->add( 'mediatype', $this->mediaTypes );
- $opts->add( 'limit', 50 );
- $opts->add( 'offset', '' );
- $opts->add( 'start', '' );
- $opts->add( 'end', '' );
-
- $opts->fetchValuesFromRequest( $this->getRequest() );
-
- if ( $par !== null ) {
- $opts->setValue( is_numeric( $par ) ? 'limit' : 'like', $par );
- }
-
- // If start date comes after end date chronologically, swap them.
- // They are swapped in the interface by JS.
- $start = $opts->getValue( 'start' );
- $end = $opts->getValue( 'end' );
- if ( $start !== '' && $end !== '' && $start > $end ) {
- $temp = $end;
- $end = $start;
- $start = $temp;
-
- $opts->setValue( 'start', $start, true );
- $opts->setValue( 'end', $end, true );
-
- // also swap values in request object, which is used by HTMLForm
- // to pre-populate the fields with the previous input
- $request = $context->getRequest();
- $context->setRequest( new DerivativeRequest(
- $request,
- [ 'start' => $start, 'end' => $end ] + $request->getValues(),
- $request->wasPosted()
- ) );
- }
-
- // if all media types have been selected, wipe out the array to prevent
- // the pointless IN(...) query condition (which would have no effect
- // because every possible type has been selected)
- $missingMediaTypes = array_diff( $this->mediaTypes, $opts->getValue( 'mediatype' ) );
- if ( empty( $missingMediaTypes ) ) {
- $opts->setValue( 'mediatype', [] );
- }
-
- $opts->validateIntBounds( 'limit', 0, 500 );
-
- $this->opts = $opts;
-
- if ( !$this->including() ) {
- $this->setTopText();
- $this->buildForm( $context );
- }
-
- $pager = new NewFilesPager( $context, $opts, $this->getLinkRenderer() );
-
- $out->addHTML( $pager->getBody() );
- if ( !$this->including() ) {
- $out->addHTML( $pager->getNavigationBar() );
- }
- }
-
- protected function buildForm( IContextSource $context ) {
- $mediaTypesText = array_map( function ( $type ) {
- // mediastatistics-header-unknown, mediastatistics-header-bitmap,
- // mediastatistics-header-drawing, mediastatistics-header-audio,
- // mediastatistics-header-video, mediastatistics-header-multimedia,
- // mediastatistics-header-office, mediastatistics-header-text,
- // mediastatistics-header-executable, mediastatistics-header-archive,
- // mediastatistics-header-3d,
- return $this->msg( 'mediastatistics-header-' . strtolower( $type ) )->text();
- }, $this->mediaTypes );
- $mediaTypesOptions = array_combine( $mediaTypesText, $this->mediaTypes );
- ksort( $mediaTypesOptions );
-
- $formDescriptor = [
- 'like' => [
- 'type' => 'text',
- 'label-message' => 'newimages-label',
- 'name' => 'like',
- ],
-
- 'user' => [
- 'class' => 'HTMLUserTextField',
- 'label-message' => 'newimages-user',
- 'name' => 'user',
- ],
-
- 'showbots' => [
- 'type' => 'check',
- 'label-message' => 'newimages-showbots',
- 'name' => 'showbots',
- ],
-
- 'hidepatrolled' => [
- 'type' => 'check',
- 'label-message' => 'newimages-hidepatrolled',
- 'name' => 'hidepatrolled',
- ],
-
- 'mediatype' => [
- 'type' => 'multiselect',
- 'flatlist' => true,
- 'name' => 'mediatype',
- 'label-message' => 'newimages-mediatype',
- 'options' => $mediaTypesOptions,
- 'default' => $this->mediaTypes,
- ],
-
- 'limit' => [
- 'type' => 'hidden',
- 'default' => $this->opts->getValue( 'limit' ),
- 'name' => 'limit',
- ],
-
- 'offset' => [
- 'type' => 'hidden',
- 'default' => $this->opts->getValue( 'offset' ),
- 'name' => 'offset',
- ],
-
- 'start' => [
- 'type' => 'date',
- 'label-message' => 'date-range-from',
- 'name' => 'start',
- ],
-
- 'end' => [
- 'type' => 'date',
- 'label-message' => 'date-range-to',
- 'name' => 'end',
- ],
- ];
-
- if ( $this->getConfig()->get( 'MiserMode' ) ) {
- unset( $formDescriptor['like'] );
- }
-
- if ( !$this->getUser()->useFilePatrol() ) {
- unset( $formDescriptor['hidepatrolled'] );
- }
-
- HTMLForm::factory( 'ooui', $formDescriptor, $context )
- // For the 'multiselect' field values to be preserved on submit
- ->setFormIdentifier( 'specialnewimages' )
- ->setWrapperLegendMsg( 'newimages-legend' )
- ->setSubmitTextMsg( 'ilsubmit' )
- ->setMethod( 'get' )
- ->prepareForm()
- ->displayForm( false );
- }
-
- protected function getGroupName() {
- return 'changes';
- }
-
- /**
- * Send the text to be displayed above the options
- */
- function setTopText() {
- $message = $this->msg( 'newimagestext' )->inContentLanguage();
- if ( !$message->isDisabled() ) {
- $contLang = MediaWikiServices::getInstance()->getContentLanguage();
- $this->getOutput()->addWikiTextAsContent(
- Html::rawElement( 'div',
- [
-
- 'lang' => $contLang->getHtmlCode(),
- 'dir' => $contLang->getDir()
- ],
- "\n" . $message->plain() . "\n"
- )
- );
- }
- }
-}
* The web server should generally be configured to make this accessible via a canonical URL/URI,
* such as <http://my.domain.org/data/main/Foo>.
*
- * @class
* @ingroup SpecialPage
*/
class SpecialPageData extends SpecialPage {
*/
private $existingPropNames = null;
+ /**
+ * @var string|null
+ */
+ private $ns;
+
/**
* @var bool
*/
'label-message' => 'pageswithprop-prop',
'required' => true,
],
+ 'namespace' => [
+ 'type' => 'namespaceselect',
+ 'name' => 'namespace',
+ 'label-message' => 'namespace',
+ 'all' => null,
+ 'default' => null,
+ ],
'reverse' => [
'type' => 'check',
'name' => 'reverse',
public function onSubmit( $data, $form ) {
$this->propName = $data['propname'];
+ $this->ns = $data['namespace'];
parent::execute( $data['propname'] );
}
}
public function getQueryInfo() {
- return [
+ $query = [
'tables' => [ 'page_props', 'page' ],
'fields' => [
'page_id' => 'pp_page',
],
'options' => []
];
+
+ if ( $this->ns && isset( $this->ns ) ) {
+ $query['conds']['page_namespace'] = $this->ns;
+ }
+
+ return $query;
}
function getOrderFields() {
}
$userpage = Title::makeTitle( NS_USER, $username );
- return Status::newGood( $userpage->getFullURL( '', false, PROTO_CURRENT ) );
+ return Status::newGood( [
+ $userpage->getFullURL( '', false, PROTO_CURRENT ), 302
+ ] );
}
/**
*/
use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Permissions\PermissionManager;
/**
* Special page allowing users with the appropriate permissions to view
/** @var string */
private $otherReason;
+ /** @var PermissionManager */
+ private $permissionManager;
+
/**
* UI labels for each type.
*/
],
];
- public function __construct() {
+ /**
+ * @inheritDoc
+ *
+ * @param PermissionManager $permissionManager
+ */
+ public function __construct( PermissionManager $permissionManager ) {
parent::__construct( 'Revisiondelete', 'deleterevision' );
+
+ $this->permissionManager = $permissionManager;
}
public function doesWrites() {
$output = $this->getOutput();
$user = $this->getUser();
- // Check blocks
- // @TODO Use PermissionManager::isBlockedFrom() instead.
- $block = $user->getBlock();
- if ( $block ) {
- throw new UserBlockedError( $block );
- }
-
$this->setHeaders();
$this->outputHeader();
$request = $this->getRequest();
return;
}
+ // Check blocks
+ if ( $this->permissionManager->isBlockedFrom( $user, $this->targetObj ) ) {
+ throw new UserBlockedError( $user->getBlock() );
+ }
+
$this->typeLabels = self::$UILabels[$this->typeName];
$list = $this->getList();
$list->reset();
}
// Validate request parameters
- $optional = [ 'maxjobs' => 0, 'maxtime' => 30, 'type' => false, 'async' => true ];
+ $optional = [ 'maxjobs' => 0, 'maxtime' => 30, 'type' => false,
+ 'async' => true, 'stats' => false ];
$required = array_flip( [ 'title', 'tasks', 'signature', 'sigexpiry' ] );
$params = array_intersect_key( $this->getRequest()->getValues(), $required + $optional );
$missing = array_diff_key( $required, $params );
DeferredUpdates::POSTSEND
);
} else {
- $this->doRun( $params );
- print "Done\n";
+ $stats = $this->doRun( $params );
+
+ if ( $params['stats'] ) {
+ $this->getRequest()->response()->header( 'Content-Type: application/json' );
+ print FormatJson::encode( $stats );
+ } else {
+ print "Done\n";
+ }
}
}
protected function doRun( array $params ) {
$runner = new JobRunner( LoggerFactory::getInstance( 'runJobs' ) );
- $runner->run( [
+ return $runner->run( [
'type' => $params['type'],
'maxJobs' => $params['maxjobs'] ?: 1,
'maxTime' => $params['maxtime'] ?: 30
--- /dev/null
+<?php
+/**
+ * Implements Special:Shortpages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\IResultWrapper;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * SpecialShortpages extends QueryPage. It is used to return the shortest
+ * pages in the database.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialShortPages extends QueryPage {
+
+ function __construct( $name = 'Shortpages' ) {
+ parent::__construct( $name );
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ public function getQueryInfo() {
+ $config = $this->getConfig();
+ $blacklist = $config->get( 'ShortPagesNamespaceBlacklist' );
+ $tables = [ 'page' ];
+ $conds = [
+ 'page_namespace' => array_diff(
+ MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces(),
+ $blacklist
+ ),
+ 'page_is_redirect' => 0
+ ];
+ $joinConds = [];
+ $options = [ 'USE INDEX' => [ 'page' => 'page_redirect_namespace_len' ] ];
+
+ // Allow extensions to modify the query
+ Hooks::run( 'ShortPagesQuery', [ &$tables, &$conds, &$joinConds, &$options ] );
+
+ return [
+ 'tables' => $tables,
+ 'fields' => [
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_len'
+ ],
+ 'conds' => $conds,
+ 'join_conds' => $joinConds,
+ 'options' => $options
+ ];
+ }
+
+ public function reallyDoQuery( $limit, $offset = false ) {
+ $fname = static::class . '::reallyDoQuery';
+ $dbr = $this->getRecacheDB();
+ $query = $this->getQueryInfo();
+ $order = $this->getOrderFields();
+
+ if ( $this->sortDescending() ) {
+ foreach ( $order as &$field ) {
+ $field .= ' DESC';
+ }
+ }
+
+ $tables = isset( $query['tables'] ) ? (array)$query['tables'] : [];
+ $fields = isset( $query['fields'] ) ? (array)$query['fields'] : [];
+ $conds = isset( $query['conds'] ) ? (array)$query['conds'] : [];
+ $options = isset( $query['options'] ) ? (array)$query['options'] : [];
+ $join_conds = isset( $query['join_conds'] ) ? (array)$query['join_conds'] : [];
+
+ if ( $limit !== false ) {
+ $options['LIMIT'] = intval( $limit );
+ }
+
+ if ( $offset !== false ) {
+ $options['OFFSET'] = intval( $offset );
+ }
+
+ $namespaces = $conds['page_namespace'];
+ if ( count( $namespaces ) === 1 ) {
+ $options['ORDER BY'] = $order;
+ $res = $dbr->select( $tables, $fields, $conds, $fname,
+ $options, $join_conds
+ );
+ } else {
+ unset( $conds['page_namespace'] );
+ $options['INNER ORDER BY'] = $order;
+ $options['ORDER BY'] = [ 'value' . ( $this->sortDescending() ? ' DESC' : '' ) ];
+ $sql = $dbr->unionConditionPermutations(
+ $tables,
+ $fields,
+ [ 'page_namespace' => $namespaces ],
+ $conds,
+ $fname,
+ $options,
+ $join_conds
+ );
+ $res = $dbr->query( $sql, $fname );
+ }
+
+ return $res;
+ }
+
+ function getOrderFields() {
+ return [ 'page_len' ];
+ }
+
+ /**
+ * @param IDatabase $db
+ * @param IResultWrapper $res
+ */
+ function preprocessResults( $db, $res ) {
+ $this->executeLBFromResultWrapper( $res );
+ }
+
+ function sortDescending() {
+ return false;
+ }
+
+ /**
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
+ */
+ function formatResult( $skin, $result ) {
+ $dm = $this->getLanguage()->getDirMark();
+
+ $title = Title::makeTitleSafe( $result->namespace, $result->title );
+ if ( !$title ) {
+ return Html::element( 'span', [ 'class' => 'mw-invalidtitle' ],
+ Linker::getInvalidTitleDescription( $this->getContext(), $result->namespace, $result->title ) );
+ }
+
+ $linkRenderer = $this->getLinkRenderer();
+ $hlink = $linkRenderer->makeKnownLink(
+ $title,
+ $this->msg( 'hist' )->text(),
+ [],
+ [ 'action' => 'history' ]
+ );
+ $hlinkInParentheses = $this->msg( 'parentheses' )->rawParams( $hlink )->escaped();
+
+ if ( $this->isCached() ) {
+ $plink = $linkRenderer->makeLink( $title );
+ $exists = $title->exists();
+ } else {
+ $plink = $linkRenderer->makeKnownLink( $title );
+ $exists = true;
+ }
+
+ $size = $this->msg( 'nbytes' )->numParams( $result->value )->escaped();
+
+ return $exists
+ ? "${hlinkInParentheses} {$dm}{$plink} {$dm}[{$size}]"
+ : "<del>${hlinkInParentheses} {$dm}{$plink} {$dm}[{$size}]</del>";
+ }
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
+}
+++ /dev/null
-<?php
-/**
- * Implements Special:Shortpages
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- */
-
-use MediaWiki\MediaWikiServices;
-use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
-
-/**
- * SpecialShortpages extends QueryPage. It is used to return the shortest
- * pages in the database.
- *
- * @ingroup SpecialPage
- */
-class ShortPagesPage extends QueryPage {
-
- function __construct( $name = 'Shortpages' ) {
- parent::__construct( $name );
- }
-
- function isSyndicated() {
- return false;
- }
-
- public function getQueryInfo() {
- $config = $this->getConfig();
- $blacklist = $config->get( 'ShortPagesNamespaceBlacklist' );
- $tables = [ 'page' ];
- $conds = [
- 'page_namespace' => array_diff(
- MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces(),
- $blacklist
- ),
- 'page_is_redirect' => 0
- ];
- $joinConds = [];
- $options = [ 'USE INDEX' => [ 'page' => 'page_redirect_namespace_len' ] ];
-
- // Allow extensions to modify the query
- Hooks::run( 'ShortPagesQuery', [ &$tables, &$conds, &$joinConds, &$options ] );
-
- return [
- 'tables' => $tables,
- 'fields' => [
- 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_len'
- ],
- 'conds' => $conds,
- 'join_conds' => $joinConds,
- 'options' => $options
- ];
- }
-
- public function reallyDoQuery( $limit, $offset = false ) {
- $fname = static::class . '::reallyDoQuery';
- $dbr = $this->getRecacheDB();
- $query = $this->getQueryInfo();
- $order = $this->getOrderFields();
-
- if ( $this->sortDescending() ) {
- foreach ( $order as &$field ) {
- $field .= ' DESC';
- }
- }
-
- $tables = isset( $query['tables'] ) ? (array)$query['tables'] : [];
- $fields = isset( $query['fields'] ) ? (array)$query['fields'] : [];
- $conds = isset( $query['conds'] ) ? (array)$query['conds'] : [];
- $options = isset( $query['options'] ) ? (array)$query['options'] : [];
- $join_conds = isset( $query['join_conds'] ) ? (array)$query['join_conds'] : [];
-
- if ( $limit !== false ) {
- $options['LIMIT'] = intval( $limit );
- }
-
- if ( $offset !== false ) {
- $options['OFFSET'] = intval( $offset );
- }
-
- $namespaces = $conds['page_namespace'];
- if ( count( $namespaces ) === 1 ) {
- $options['ORDER BY'] = $order;
- $res = $dbr->select( $tables, $fields, $conds, $fname,
- $options, $join_conds
- );
- } else {
- unset( $conds['page_namespace'] );
- $options['INNER ORDER BY'] = $order;
- $options['ORDER BY'] = [ 'value' . ( $this->sortDescending() ? ' DESC' : '' ) ];
- $sql = $dbr->unionConditionPermutations(
- $tables,
- $fields,
- [ 'page_namespace' => $namespaces ],
- $conds,
- $fname,
- $options,
- $join_conds
- );
- $res = $dbr->query( $sql, $fname );
- }
-
- return $res;
- }
-
- function getOrderFields() {
- return [ 'page_len' ];
- }
-
- /**
- * @param IDatabase $db
- * @param IResultWrapper $res
- */
- function preprocessResults( $db, $res ) {
- $this->executeLBFromResultWrapper( $res );
- }
-
- function sortDescending() {
- return false;
- }
-
- /**
- * @param Skin $skin
- * @param object $result Result row
- * @return string
- */
- function formatResult( $skin, $result ) {
- $dm = $this->getLanguage()->getDirMark();
-
- $title = Title::makeTitleSafe( $result->namespace, $result->title );
- if ( !$title ) {
- return Html::element( 'span', [ 'class' => 'mw-invalidtitle' ],
- Linker::getInvalidTitleDescription( $this->getContext(), $result->namespace, $result->title ) );
- }
-
- $linkRenderer = $this->getLinkRenderer();
- $hlink = $linkRenderer->makeKnownLink(
- $title,
- $this->msg( 'hist' )->text(),
- [],
- [ 'action' => 'history' ]
- );
- $hlinkInParentheses = $this->msg( 'parentheses' )->rawParams( $hlink )->escaped();
-
- if ( $this->isCached() ) {
- $plink = $linkRenderer->makeLink( $title );
- $exists = $title->exists();
- } else {
- $plink = $linkRenderer->makeKnownLink( $title );
- $exists = true;
- }
-
- $size = $this->msg( 'nbytes' )->numParams( $result->value )->escaped();
-
- return $exists
- ? "${hlinkInParentheses} {$dm}{$plink} {$dm}[{$size}]"
- : "<del>${hlinkInParentheses} {$dm}{$plink} {$dm}[{$size}]</del>";
- }
-
- protected function getGroupName() {
- return 'maintenance';
- }
-}
--- /dev/null
+<?php
+/**
+ * Implements Special:Uncategorizedcategories
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * A special page that lists uncategorized categories
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialUncategorizedCategories extends SpecialUncategorizedPages {
+ /**
+ * Holds a list of categories, which shouldn't be listed on this special page,
+ * even if it is uncategorized.
+ * @var array
+ */
+ private $exceptionList = null;
+
+ function __construct( $name = 'Uncategorizedcategories' ) {
+ parent::__construct( $name );
+ $this->requestedNamespace = NS_CATEGORY;
+ }
+
+ /**
+ * Returns an array of category titles (usually without the namespace), which
+ * shouldn't be listed on this page, even if they're uncategorized.
+ *
+ * @return array
+ */
+ private function getExceptionList() {
+ if ( $this->exceptionList === null ) {
+ $this->exceptionList = [];
+ $exList = $this->msg( 'uncategorized-categories-exceptionlist' )
+ ->inContentLanguage()->plain();
+ $proposedTitles = explode( "\n", $exList );
+ foreach ( $proposedTitles as $count => $titleStr ) {
+ if ( strpos( $titleStr, '*' ) !== 0 ) {
+ continue;
+ }
+ $titleStr = preg_replace( "/^\\*\\s*/", '', $titleStr );
+ $title = Title::newFromText( $titleStr, NS_CATEGORY );
+ if ( $title && $title->getNamespace() !== NS_CATEGORY ) {
+ $title = Title::makeTitleSafe( NS_CATEGORY, $titleStr );
+ }
+ if ( $title ) {
+ $this->exceptionList[] = $title->getDBkey();
+ }
+ }
+ }
+ return $this->exceptionList;
+ }
+
+ public function getQueryInfo() {
+ $dbr = wfGetDB( DB_REPLICA );
+ $query = parent::getQueryInfo();
+ $exceptionList = $this->getExceptionList();
+ if ( $exceptionList ) {
+ $query['conds'][] = 'page_title not in ( ' . $dbr->makeList( $exceptionList ) . ' )';
+ }
+
+ return $query;
+ }
+
+ /**
+ * Formats the result
+ * @param Skin $skin The current skin
+ * @param object $result The query result
+ * @return string The category link
+ */
+ function formatResult( $skin, $result ) {
+ $title = Title::makeTitle( NS_CATEGORY, $result->title );
+ $text = $title->getText();
+
+ return $this->getLinkRenderer()->makeKnownLink( $title, $text );
+ }
+}
--- /dev/null
+<?php
+/**
+ * Implements Special:Uncategorizedimages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @author Rob Church <robchur@gmail.com>
+ */
+
+/**
+ * Special page lists images which haven't been categorised
+ *
+ * @ingroup SpecialPage
+ * @todo FIXME: Use an instance of UncategorizedPagesPage or something
+ */
+class SpecialUncategorizedImages extends ImageQueryPage {
+ function __construct( $name = 'Uncategorizedimages' ) {
+ parent::__construct( $name );
+ $this->addHelpLink( 'Help:Categories' );
+ }
+
+ function sortDescending() {
+ return false;
+ }
+
+ function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ function getQueryInfo() {
+ return [
+ 'tables' => [ 'page', 'categorylinks' ],
+ 'fields' => [ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_title' ],
+ 'conds' => [ 'cl_from IS NULL',
+ 'page_namespace' => NS_FILE,
+ 'page_is_redirect' => 0 ],
+ 'join_conds' => [ 'categorylinks' => [
+ 'LEFT JOIN', 'cl_from=page_id' ] ]
+ ];
+ }
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
+}
--- /dev/null
+<?php
+/**
+ * Implements Special:Uncategorizedpages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * A special page looking for page without any category.
+ *
+ * @ingroup SpecialPage
+ * @todo FIXME: Make $requestedNamespace selectable, unify all subclasses into one
+ */
+class SpecialUncategorizedPages extends PageQueryPage {
+ /** @var int|false */
+ protected $requestedNamespace = false;
+
+ function __construct( $name = 'Uncategorizedpages' ) {
+ parent::__construct( $name );
+ $this->addHelpLink( 'Help:Categories' );
+ }
+
+ function sortDescending() {
+ return false;
+ }
+
+ function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ function getQueryInfo() {
+ return [
+ 'tables' => [ 'page', 'categorylinks' ],
+ 'fields' => [
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_title'
+ ],
+ // default for page_namespace is all content namespaces (if requestedNamespace is false)
+ // otherwise, page_namespace is requestedNamespace
+ 'conds' => [
+ 'cl_from IS NULL',
+ 'page_namespace' => $this->requestedNamespace !== false
+ ? $this->requestedNamespace
+ : MediaWikiServices::getInstance()->getNamespaceInfo()->
+ getContentNamespaces(),
+ 'page_is_redirect' => 0
+ ],
+ 'join_conds' => [
+ 'categorylinks' => [ 'LEFT JOIN', 'cl_from = page_id' ]
+ ]
+ ];
+ }
+
+ function getOrderFields() {
+ // For some crazy reason ordering by a constant
+ // causes a filesort
+ if ( $this->requestedNamespace === false &&
+ count( MediaWikiServices::getInstance()->getNamespaceInfo()->
+ getContentNamespaces() ) > 1
+ ) {
+ return [ 'page_namespace', 'page_title' ];
+ }
+
+ return [ 'page_title' ];
+ }
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
+}
--- /dev/null
+<?php
+/**
+ * Implements Special:Uncategorizedtemplates
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @author Rob Church <robchur@gmail.com>
+ */
+
+/**
+ * Special page lists all uncategorised pages in the
+ * template namespace
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialUncategorizedTemplates extends SpecialUncategorizedPages {
+ public function __construct( $name = 'Uncategorizedtemplates' ) {
+ parent::__construct( $name );
+ $this->requestedNamespace = NS_TEMPLATE;
+ }
+}
+++ /dev/null
-<?php
-/**
- * Implements Special:Uncategorizedcategories
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- */
-
-/**
- * A special page that lists uncategorized categories
- *
- * @ingroup SpecialPage
- */
-class UncategorizedCategoriesPage extends UncategorizedPagesPage {
- /**
- * Holds a list of categories, which shouldn't be listed on this special page,
- * even if it is uncategorized.
- * @var array
- */
- private $exceptionList = null;
-
- function __construct( $name = 'Uncategorizedcategories' ) {
- parent::__construct( $name );
- $this->requestedNamespace = NS_CATEGORY;
- }
-
- /**
- * Returns an array of category titles (usually without the namespace), which
- * shouldn't be listed on this page, even if they're uncategorized.
- *
- * @return array
- */
- private function getExceptionList() {
- if ( $this->exceptionList === null ) {
- $this->exceptionList = [];
- $exList = $this->msg( 'uncategorized-categories-exceptionlist' )
- ->inContentLanguage()->plain();
- $proposedTitles = explode( "\n", $exList );
- foreach ( $proposedTitles as $count => $titleStr ) {
- if ( strpos( $titleStr, '*' ) !== 0 ) {
- continue;
- }
- $titleStr = preg_replace( "/^\\*\\s*/", '', $titleStr );
- $title = Title::newFromText( $titleStr, NS_CATEGORY );
- if ( $title && $title->getNamespace() !== NS_CATEGORY ) {
- $title = Title::makeTitleSafe( NS_CATEGORY, $titleStr );
- }
- if ( $title ) {
- $this->exceptionList[] = $title->getDBkey();
- }
- }
- }
- return $this->exceptionList;
- }
-
- public function getQueryInfo() {
- $dbr = wfGetDB( DB_REPLICA );
- $query = parent::getQueryInfo();
- $exceptionList = $this->getExceptionList();
- if ( $exceptionList ) {
- $query['conds'][] = 'page_title not in ( ' . $dbr->makeList( $exceptionList ) . ' )';
- }
-
- return $query;
- }
-
- /**
- * Formats the result
- * @param Skin $skin The current skin
- * @param object $result The query result
- * @return string The category link
- */
- function formatResult( $skin, $result ) {
- $title = Title::makeTitle( NS_CATEGORY, $result->title );
- $text = $title->getText();
-
- return $this->getLinkRenderer()->makeKnownLink( $title, $text );
- }
-}
+++ /dev/null
-<?php
-/**
- * Implements Special:Uncategorizedimages
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- * @author Rob Church <robchur@gmail.com>
- */
-
-/**
- * Special page lists images which haven't been categorised
- *
- * @ingroup SpecialPage
- * @todo FIXME: Use an instance of UncategorizedPagesPage or something
- */
-class UncategorizedImagesPage extends ImageQueryPage {
- function __construct( $name = 'Uncategorizedimages' ) {
- parent::__construct( $name );
- $this->addHelpLink( 'Help:Categories' );
- }
-
- function sortDescending() {
- return false;
- }
-
- function isExpensive() {
- return true;
- }
-
- function isSyndicated() {
- return false;
- }
-
- function getQueryInfo() {
- return [
- 'tables' => [ 'page', 'categorylinks' ],
- 'fields' => [ 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_title' ],
- 'conds' => [ 'cl_from IS NULL',
- 'page_namespace' => NS_FILE,
- 'page_is_redirect' => 0 ],
- 'join_conds' => [ 'categorylinks' => [
- 'LEFT JOIN', 'cl_from=page_id' ] ]
- ];
- }
-
- protected function getGroupName() {
- return 'maintenance';
- }
-}
+++ /dev/null
-<?php
-/**
- * Implements Special:Uncategorizedpages
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- */
-
-use MediaWiki\MediaWikiServices;
-
-/**
- * A special page looking for page without any category.
- *
- * @ingroup SpecialPage
- * @todo FIXME: Make $requestedNamespace selectable, unify all subclasses into one
- */
-class UncategorizedPagesPage extends PageQueryPage {
- /** @var int|false */
- protected $requestedNamespace = false;
-
- function __construct( $name = 'Uncategorizedpages' ) {
- parent::__construct( $name );
- $this->addHelpLink( 'Help:Categories' );
- }
-
- function sortDescending() {
- return false;
- }
-
- function isExpensive() {
- return true;
- }
-
- function isSyndicated() {
- return false;
- }
-
- function getQueryInfo() {
- return [
- 'tables' => [ 'page', 'categorylinks' ],
- 'fields' => [
- 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_title'
- ],
- // default for page_namespace is all content namespaces (if requestedNamespace is false)
- // otherwise, page_namespace is requestedNamespace
- 'conds' => [
- 'cl_from IS NULL',
- 'page_namespace' => $this->requestedNamespace !== false
- ? $this->requestedNamespace
- : MediaWikiServices::getInstance()->getNamespaceInfo()->
- getContentNamespaces(),
- 'page_is_redirect' => 0
- ],
- 'join_conds' => [
- 'categorylinks' => [ 'LEFT JOIN', 'cl_from = page_id' ]
- ]
- ];
- }
-
- function getOrderFields() {
- // For some crazy reason ordering by a constant
- // causes a filesort
- if ( $this->requestedNamespace === false &&
- count( MediaWikiServices::getInstance()->getNamespaceInfo()->
- getContentNamespaces() ) > 1
- ) {
- return [ 'page_namespace', 'page_title' ];
- }
-
- return [ 'page_title' ];
- }
-
- protected function getGroupName() {
- return 'maintenance';
- }
-}
+++ /dev/null
-<?php
-/**
- * Implements Special:Uncategorizedtemplates
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- * @author Rob Church <robchur@gmail.com>
- */
-
-/**
- * Special page lists all uncategorised pages in the
- * template namespace
- *
- * @ingroup SpecialPage
- */
-class UncategorizedTemplatesPage extends UncategorizedPagesPage {
- public function __construct( $name = 'Uncategorizedtemplates' ) {
- parent::__construct( $name );
- $this->requestedNamespace = NS_TEMPLATE;
- }
-}
* @ingroup SpecialPage
*/
class SpecialUndelete extends SpecialPage {
- private $mAction;
- private $mTarget;
- private $mTimestamp;
- private $mRestore;
- private $mRevdel;
- private $mInvert;
- private $mFilename;
- private $mTargetTimestamp;
- private $mAllowed;
- private $mCanView;
- private $mComment;
- private $mToken;
+ private $mAction;
+ private $mTarget;
+ private $mTimestamp;
+ private $mRestore;
+ private $mRevdel;
+ private $mInvert;
+ private $mFilename;
+ private $mTargetTimestamp;
+ private $mAllowed;
+ private $mCanView;
+ private $mComment;
+ private $mToken;
+ /** @var bool|null */
+ private $mPreview;
+ /** @var bool|null */
+ private $mDiff;
+ /** @var bool|null */
+ private $mDiffOnly;
+ /** @var bool|null */
+ private $mUnsuppress;
+ /** @var int[]|null */
+ private $mFileVersions;
/** @var Title */
private $mTargetObj;
--- /dev/null
+<?php
+/**
+ * Implements Special:Unusedcategories
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * @ingroup SpecialPage
+ */
+class SpecialUnusedCategories extends QueryPage {
+ function __construct( $name = 'Unusedcategories' ) {
+ parent::__construct( $name );
+ }
+
+ public function isExpensive() {
+ return true;
+ }
+
+ function getPageHeader() {
+ return $this->msg( 'unusedcategoriestext' )->parseAsBlock();
+ }
+
+ public function getQueryInfo() {
+ return [
+ 'tables' => [ 'page', 'categorylinks', 'page_props' ],
+ 'fields' => [
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_title'
+ ],
+ 'conds' => [
+ 'cl_from IS NULL',
+ 'page_namespace' => NS_CATEGORY,
+ 'page_is_redirect' => 0,
+ 'pp_page IS NULL'
+ ],
+ 'join_conds' => [
+ 'categorylinks' => [ 'LEFT JOIN', 'cl_to = page_title' ],
+ 'page_props' => [ 'LEFT JOIN', [
+ 'page_id = pp_page',
+ 'pp_propname' => 'expectunusedcategory'
+ ] ]
+ ]
+ ];
+ }
+
+ /**
+ * A should come before Z (T32907)
+ * @return bool
+ */
+ function sortDescending() {
+ return false;
+ }
+
+ /**
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
+ */
+ function formatResult( $skin, $result ) {
+ $title = Title::makeTitle( NS_CATEGORY, $result->title );
+
+ return $this->getLinkRenderer()->makeLink( $title, $title->getText() );
+ }
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
+
+ public function preprocessResults( $db, $res ) {
+ $this->executeLBFromResultWrapper( $res );
+ }
+}
--- /dev/null
+<?php
+/**
+ * Implements Special:Unusedimages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * A special page that lists unused images
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialUnusedImages extends ImageQueryPage {
+ function __construct( $name = 'Unusedimages' ) {
+ parent::__construct( $name );
+ }
+
+ function isExpensive() {
+ return true;
+ }
+
+ function sortDescending() {
+ return false;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ function getQueryInfo() {
+ $retval = [
+ 'tables' => [ 'image', 'imagelinks' ],
+ 'fields' => [
+ 'namespace' => NS_FILE,
+ 'title' => 'img_name',
+ 'value' => 'img_timestamp',
+ ],
+ 'conds' => [ 'il_to IS NULL' ],
+ 'join_conds' => [ 'imagelinks' => [ 'LEFT JOIN', 'il_to = img_name' ] ]
+ ];
+
+ if ( $this->getConfig()->get( 'CountCategorizedImagesAsUsed' ) ) {
+ // Order is significant
+ $retval['tables'] = [ 'image', 'page', 'categorylinks',
+ 'imagelinks' ];
+ $retval['conds']['page_namespace'] = NS_FILE;
+ $retval['conds'][] = 'cl_from IS NULL';
+ $retval['conds'][] = 'img_name = page_title';
+ $retval['join_conds']['categorylinks'] = [
+ 'LEFT JOIN', 'cl_from = page_id' ];
+ $retval['join_conds']['imagelinks'] = [
+ 'LEFT JOIN', 'il_to = page_title' ];
+ }
+
+ return $retval;
+ }
+
+ function usesTimestamps() {
+ return true;
+ }
+
+ function getPageHeader() {
+ if ( $this->getConfig()->get( 'CountCategorizedImagesAsUsed' ) ) {
+ return $this->msg(
+ 'unusedimagestext-categorizedimgisused'
+ )->parseAsBlock();
+ }
+ return $this->msg( 'unusedimagestext' )->parseAsBlock();
+ }
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
+}
--- /dev/null
+<?php
+/**
+ * Implements Special:Unusedtemplates
+ *
+ * Copyright © 2006 Rob Church
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @author Rob Church <robchur@gmail.com>
+ */
+
+/**
+ * A special page that lists unused templates
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialUnusedTemplates extends QueryPage {
+ function __construct( $name = 'Unusedtemplates' ) {
+ parent::__construct( $name );
+ }
+
+ public function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ function sortDescending() {
+ return false;
+ }
+
+ public function getQueryInfo() {
+ return [
+ 'tables' => [ 'page', 'templatelinks' ],
+ 'fields' => [
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_title'
+ ],
+ 'conds' => [
+ 'page_namespace' => NS_TEMPLATE,
+ 'tl_from IS NULL',
+ 'page_is_redirect' => 0
+ ],
+ 'join_conds' => [ 'templatelinks' => [
+ 'LEFT JOIN', [ 'tl_title = page_title',
+ 'tl_namespace = page_namespace' ] ] ]
+ ];
+ }
+
+ /**
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
+ */
+ function formatResult( $skin, $result ) {
+ $linkRenderer = $this->getLinkRenderer();
+ $title = Title::makeTitle( NS_TEMPLATE, $result->title );
+ $pageLink = $linkRenderer->makeKnownLink(
+ $title,
+ null,
+ [],
+ [ 'redirect' => 'no' ]
+ );
+ $wlhLink = $linkRenderer->makeKnownLink(
+ SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() ),
+ $this->msg( 'unusedtemplateswlh' )->text()
+ );
+
+ return $this->getLanguage()->specialList( $pageLink, $wlhLink );
+ }
+
+ function getPageHeader() {
+ return $this->msg( 'unusedtemplatestext' )->parseAsBlock();
+ }
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
+}
+++ /dev/null
-<?php
-/**
- * Implements Special:Unusedcategories
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- */
-
-/**
- * @ingroup SpecialPage
- */
-class UnusedCategoriesPage extends QueryPage {
- function __construct( $name = 'Unusedcategories' ) {
- parent::__construct( $name );
- }
-
- public function isExpensive() {
- return true;
- }
-
- function getPageHeader() {
- return $this->msg( 'unusedcategoriestext' )->parseAsBlock();
- }
-
- public function getQueryInfo() {
- return [
- 'tables' => [ 'page', 'categorylinks', 'page_props' ],
- 'fields' => [
- 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_title'
- ],
- 'conds' => [
- 'cl_from IS NULL',
- 'page_namespace' => NS_CATEGORY,
- 'page_is_redirect' => 0,
- 'pp_page IS NULL'
- ],
- 'join_conds' => [
- 'categorylinks' => [ 'LEFT JOIN', 'cl_to = page_title' ],
- 'page_props' => [ 'LEFT JOIN', [
- 'page_id = pp_page',
- 'pp_propname' => 'expectunusedcategory'
- ] ]
- ]
- ];
- }
-
- /**
- * A should come before Z (T32907)
- * @return bool
- */
- function sortDescending() {
- return false;
- }
-
- /**
- * @param Skin $skin
- * @param object $result Result row
- * @return string
- */
- function formatResult( $skin, $result ) {
- $title = Title::makeTitle( NS_CATEGORY, $result->title );
-
- return $this->getLinkRenderer()->makeLink( $title, $title->getText() );
- }
-
- protected function getGroupName() {
- return 'maintenance';
- }
-
- public function preprocessResults( $db, $res ) {
- $this->executeLBFromResultWrapper( $res );
- }
-}
+++ /dev/null
-<?php
-/**
- * Implements Special:Unusedimages
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- */
-
-/**
- * A special page that lists unused images
- *
- * @ingroup SpecialPage
- */
-class UnusedimagesPage extends ImageQueryPage {
- function __construct( $name = 'Unusedimages' ) {
- parent::__construct( $name );
- }
-
- function isExpensive() {
- return true;
- }
-
- function sortDescending() {
- return false;
- }
-
- function isSyndicated() {
- return false;
- }
-
- function getQueryInfo() {
- $retval = [
- 'tables' => [ 'image', 'imagelinks' ],
- 'fields' => [
- 'namespace' => NS_FILE,
- 'title' => 'img_name',
- 'value' => 'img_timestamp',
- ],
- 'conds' => [ 'il_to IS NULL' ],
- 'join_conds' => [ 'imagelinks' => [ 'LEFT JOIN', 'il_to = img_name' ] ]
- ];
-
- if ( $this->getConfig()->get( 'CountCategorizedImagesAsUsed' ) ) {
- // Order is significant
- $retval['tables'] = [ 'image', 'page', 'categorylinks',
- 'imagelinks' ];
- $retval['conds']['page_namespace'] = NS_FILE;
- $retval['conds'][] = 'cl_from IS NULL';
- $retval['conds'][] = 'img_name = page_title';
- $retval['join_conds']['categorylinks'] = [
- 'LEFT JOIN', 'cl_from = page_id' ];
- $retval['join_conds']['imagelinks'] = [
- 'LEFT JOIN', 'il_to = page_title' ];
- }
-
- return $retval;
- }
-
- function usesTimestamps() {
- return true;
- }
-
- function getPageHeader() {
- if ( $this->getConfig()->get( 'CountCategorizedImagesAsUsed' ) ) {
- return $this->msg(
- 'unusedimagestext-categorizedimgisused'
- )->parseAsBlock();
- }
- return $this->msg( 'unusedimagestext' )->parseAsBlock();
- }
-
- protected function getGroupName() {
- return 'maintenance';
- }
-}
+++ /dev/null
-<?php
-/**
- * Implements Special:Unusedtemplates
- *
- * Copyright © 2006 Rob Church
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- * @author Rob Church <robchur@gmail.com>
- */
-
-/**
- * A special page that lists unused templates
- *
- * @ingroup SpecialPage
- */
-class UnusedtemplatesPage extends QueryPage {
- function __construct( $name = 'Unusedtemplates' ) {
- parent::__construct( $name );
- }
-
- public function isExpensive() {
- return true;
- }
-
- function isSyndicated() {
- return false;
- }
-
- function sortDescending() {
- return false;
- }
-
- public function getQueryInfo() {
- return [
- 'tables' => [ 'page', 'templatelinks' ],
- 'fields' => [
- 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_title'
- ],
- 'conds' => [
- 'page_namespace' => NS_TEMPLATE,
- 'tl_from IS NULL',
- 'page_is_redirect' => 0
- ],
- 'join_conds' => [ 'templatelinks' => [
- 'LEFT JOIN', [ 'tl_title = page_title',
- 'tl_namespace = page_namespace' ] ] ]
- ];
- }
-
- /**
- * @param Skin $skin
- * @param object $result Result row
- * @return string
- */
- function formatResult( $skin, $result ) {
- $linkRenderer = $this->getLinkRenderer();
- $title = Title::makeTitle( NS_TEMPLATE, $result->title );
- $pageLink = $linkRenderer->makeKnownLink(
- $title,
- null,
- [],
- [ 'redirect' => 'no' ]
- );
- $wlhLink = $linkRenderer->makeKnownLink(
- SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() ),
- $this->msg( 'unusedtemplateswlh' )->text()
- );
-
- return $this->getLanguage()->specialList( $pageLink, $wlhLink );
- }
-
- function getPageHeader() {
- return $this->msg( 'unusedtemplatestext' )->parseAsBlock();
- }
-
- protected function getGroupName() {
- return 'maintenance';
- }
-}
--- /dev/null
+<?php
+/**
+ * Implements Special:Unwatchedpages
+ *
+ * Copyright © 2005 Ævar Arnfjörð Bjarmason
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ */
+
+use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\IResultWrapper;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * A special page that displays a list of pages that are not on anyones watchlist.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialUnwatchedPages extends QueryPage {
+
+ function __construct( $name = 'Unwatchedpages' ) {
+ parent::__construct( $name, 'unwatchedpages' );
+ }
+
+ public function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ /**
+ * Pre-cache page existence to speed up link generation
+ *
+ * @param IDatabase $db
+ * @param IResultWrapper $res
+ */
+ public function preprocessResults( $db, $res ) {
+ if ( !$res->numRows() ) {
+ return;
+ }
+
+ $batch = new LinkBatch();
+ foreach ( $res as $row ) {
+ $batch->add( $row->namespace, $row->title );
+ }
+ $batch->execute();
+
+ $res->seek( 0 );
+ }
+
+ public function getQueryInfo() {
+ $dbr = wfGetDB( DB_REPLICA );
+ return [
+ 'tables' => [ 'page', 'watchlist' ],
+ 'fields' => [
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_namespace'
+ ],
+ 'conds' => [
+ 'wl_title IS NULL',
+ 'page_is_redirect' => 0,
+ 'page_namespace != ' . $dbr->addQuotes( NS_MEDIAWIKI ),
+ ],
+ 'join_conds' => [ 'watchlist' => [
+ 'LEFT JOIN', [ 'wl_title = page_title',
+ 'wl_namespace = page_namespace' ] ] ]
+ ];
+ }
+
+ function sortDescending() {
+ return false;
+ }
+
+ function getOrderFields() {
+ return [ 'page_namespace', 'page_title' ];
+ }
+
+ /**
+ * Add the JS
+ * @param string|null $par
+ */
+ public function execute( $par ) {
+ parent::execute( $par );
+ $this->getOutput()->addModules( 'mediawiki.special.unwatchedPages' );
+ $this->addHelpLink( 'Help:Watchlist' );
+ }
+
+ /**
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
+ */
+ function formatResult( $skin, $result ) {
+ $nt = Title::makeTitleSafe( $result->namespace, $result->title );
+ if ( !$nt ) {
+ return Html::element( 'span', [ 'class' => 'mw-invalidtitle' ],
+ Linker::getInvalidTitleDescription( $this->getContext(), $result->namespace, $result->title ) );
+ }
+
+ $text = MediaWikiServices::getInstance()->getContentLanguage()->
+ convert( htmlspecialchars( $nt->getPrefixedText() ) );
+
+ $linkRenderer = $this->getLinkRenderer();
+
+ $plink = $linkRenderer->makeKnownLink( $nt, new HtmlArmor( $text ) );
+ $wlink = $linkRenderer->makeKnownLink(
+ $nt,
+ $this->msg( 'watch' )->text(),
+ [ 'class' => 'mw-watch-link' ],
+ [ 'action' => 'watch' ]
+ );
+
+ return $this->getLanguage()->specialList( $plink, $wlink );
+ }
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
+}
+++ /dev/null
-<?php
-/**
- * Implements Special:Unwatchedpages
- *
- * Copyright © 2005 Ævar Arnfjörð Bjarmason
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- */
-
-use MediaWiki\MediaWikiServices;
-use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
-
-/**
- * A special page that displays a list of pages that are not on anyones watchlist.
- *
- * @ingroup SpecialPage
- */
-class UnwatchedpagesPage extends QueryPage {
-
- function __construct( $name = 'Unwatchedpages' ) {
- parent::__construct( $name, 'unwatchedpages' );
- }
-
- public function isExpensive() {
- return true;
- }
-
- function isSyndicated() {
- return false;
- }
-
- /**
- * Pre-cache page existence to speed up link generation
- *
- * @param IDatabase $db
- * @param IResultWrapper $res
- */
- public function preprocessResults( $db, $res ) {
- if ( !$res->numRows() ) {
- return;
- }
-
- $batch = new LinkBatch();
- foreach ( $res as $row ) {
- $batch->add( $row->namespace, $row->title );
- }
- $batch->execute();
-
- $res->seek( 0 );
- }
-
- public function getQueryInfo() {
- $dbr = wfGetDB( DB_REPLICA );
- return [
- 'tables' => [ 'page', 'watchlist' ],
- 'fields' => [
- 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_namespace'
- ],
- 'conds' => [
- 'wl_title IS NULL',
- 'page_is_redirect' => 0,
- 'page_namespace != ' . $dbr->addQuotes( NS_MEDIAWIKI ),
- ],
- 'join_conds' => [ 'watchlist' => [
- 'LEFT JOIN', [ 'wl_title = page_title',
- 'wl_namespace = page_namespace' ] ] ]
- ];
- }
-
- function sortDescending() {
- return false;
- }
-
- function getOrderFields() {
- return [ 'page_namespace', 'page_title' ];
- }
-
- /**
- * Add the JS
- * @param string|null $par
- */
- public function execute( $par ) {
- parent::execute( $par );
- $this->getOutput()->addModules( 'mediawiki.special.unwatchedPages' );
- $this->addHelpLink( 'Help:Watchlist' );
- }
-
- /**
- * @param Skin $skin
- * @param object $result Result row
- * @return string
- */
- function formatResult( $skin, $result ) {
- $nt = Title::makeTitleSafe( $result->namespace, $result->title );
- if ( !$nt ) {
- return Html::element( 'span', [ 'class' => 'mw-invalidtitle' ],
- Linker::getInvalidTitleDescription( $this->getContext(), $result->namespace, $result->title ) );
- }
-
- $text = MediaWikiServices::getInstance()->getContentLanguage()->
- convert( htmlspecialchars( $nt->getPrefixedText() ) );
-
- $linkRenderer = $this->getLinkRenderer();
-
- $plink = $linkRenderer->makeKnownLink( $nt, new HtmlArmor( $text ) );
- $wlink = $linkRenderer->makeKnownLink(
- $nt,
- $this->msg( 'watch' )->text(),
- [ 'class' => 'mw-watch-link' ],
- [ 'action' => 'watch' ]
- );
-
- return $this->getLanguage()->specialList( $plink, $wlink );
- }
-
- protected function getGroupName() {
- return 'maintenance';
- }
-}
--- /dev/null
+<?php
+/**
+ * Implements Special:Wantedcategories
+ *
+ * Copyright © 2005 Ævar Arnfjörð Bjarmason
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * A querypage to list the most wanted categories - implements Special:Wantedcategories
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialWantedCategories extends WantedQueryPage {
+ private $currentCategoryCounts;
+
+ function __construct( $name = 'Wantedcategories' ) {
+ parent::__construct( $name );
+ }
+
+ function getQueryInfo() {
+ return [
+ 'tables' => [ 'categorylinks', 'page' ],
+ 'fields' => [
+ 'namespace' => NS_CATEGORY,
+ 'title' => 'cl_to',
+ 'value' => 'COUNT(*)'
+ ],
+ 'conds' => [ 'page_title IS NULL' ],
+ 'options' => [ 'GROUP BY' => 'cl_to' ],
+ 'join_conds' => [ 'page' => [ 'LEFT JOIN',
+ [ 'page_title = cl_to',
+ 'page_namespace' => NS_CATEGORY ] ] ]
+ ];
+ }
+
+ function preprocessResults( $db, $res ) {
+ parent::preprocessResults( $db, $res );
+
+ $this->currentCategoryCounts = [];
+
+ if ( !$res->numRows() || !$this->isCached() ) {
+ return;
+ }
+
+ // Fetch (hopefully) up-to-date numbers of pages in each category.
+ // This should be fast enough as we limit the list to a reasonable length.
+
+ $allCategories = [];
+ foreach ( $res as $row ) {
+ $allCategories[] = $row->title;
+ }
+
+ $categoryRes = $db->select(
+ 'category',
+ [ 'cat_title', 'cat_pages' ],
+ [ 'cat_title' => $allCategories ],
+ __METHOD__
+ );
+ foreach ( $categoryRes as $row ) {
+ $this->currentCategoryCounts[$row->cat_title] = intval( $row->cat_pages );
+ }
+
+ // Back to start for display
+ $res->seek( 0 );
+ }
+
+ /**
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
+ */
+ function formatResult( $skin, $result ) {
+ $nt = Title::makeTitle( $result->namespace, $result->title );
+ $text = new HtmlArmor( MediaWikiServices::getInstance()->getContentLanguage()
+ ->convert( htmlspecialchars( $nt->getText() ) ) );
+
+ if ( !$this->isCached() ) {
+ // We can assume the freshest data
+ $plink = $this->getLinkRenderer()->makeBrokenLink(
+ $nt,
+ $text
+ );
+ $nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
+ } else {
+ $plink = $this->getLinkRenderer()->makeLink( $nt, $text );
+
+ $currentValue = $this->currentCategoryCounts[$result->title] ?? 0;
+ $cachedValue = intval( $result->value ); // T76910
+
+ // If the category has been created or emptied since the list was refreshed, strike it
+ if ( $nt->isKnown() || $currentValue === 0 ) {
+ $plink = "<del>$plink</del>";
+ }
+
+ // Show the current number of category entries if it changed
+ if ( $currentValue !== $cachedValue ) {
+ $nlinks = $this->msg( 'nmemberschanged' )
+ ->numParams( $cachedValue, $currentValue )->escaped();
+ } else {
+ $nlinks = $this->msg( 'nmembers' )->numParams( $cachedValue )->escaped();
+ }
+ }
+
+ return $this->getLanguage()->specialList( $plink, $nlinks );
+ }
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
+}
--- /dev/null
+<?php
+/**
+ * Implements Special:Wantedtemplates
+ *
+ * Copyright © 2008, Danny B.
+ * Based on SpecialWantedcategories.php by Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ * makeWlhLink() taken from SpecialMostlinkedtemplates by Rob Church <robchur@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @author Danny B.
+ */
+
+/**
+ * A querypage to list the most wanted templates
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialWantedTemplates extends WantedQueryPage {
+ function __construct( $name = 'Wantedtemplates' ) {
+ parent::__construct( $name );
+ }
+
+ function getQueryInfo() {
+ return [
+ 'tables' => [ 'templatelinks', 'page' ],
+ 'fields' => [
+ 'namespace' => 'tl_namespace',
+ 'title' => 'tl_title',
+ 'value' => 'COUNT(*)'
+ ],
+ 'conds' => [
+ 'page_title IS NULL',
+ 'tl_namespace' => NS_TEMPLATE
+ ],
+ 'options' => [ 'GROUP BY' => [ 'tl_namespace', 'tl_title' ] ],
+ 'join_conds' => [ 'page' => [ 'LEFT JOIN',
+ [ 'page_namespace = tl_namespace',
+ 'page_title = tl_title' ] ] ]
+ ];
+ }
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
+}
+++ /dev/null
-<?php
-/**
- * Implements Special:Wantedcategories
- *
- * Copyright © 2005 Ævar Arnfjörð Bjarmason
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- */
-
-use MediaWiki\MediaWikiServices;
-
-/**
- * A querypage to list the most wanted categories - implements Special:Wantedcategories
- *
- * @ingroup SpecialPage
- */
-class WantedCategoriesPage extends WantedQueryPage {
- private $currentCategoryCounts;
-
- function __construct( $name = 'Wantedcategories' ) {
- parent::__construct( $name );
- }
-
- function getQueryInfo() {
- return [
- 'tables' => [ 'categorylinks', 'page' ],
- 'fields' => [
- 'namespace' => NS_CATEGORY,
- 'title' => 'cl_to',
- 'value' => 'COUNT(*)'
- ],
- 'conds' => [ 'page_title IS NULL' ],
- 'options' => [ 'GROUP BY' => 'cl_to' ],
- 'join_conds' => [ 'page' => [ 'LEFT JOIN',
- [ 'page_title = cl_to',
- 'page_namespace' => NS_CATEGORY ] ] ]
- ];
- }
-
- function preprocessResults( $db, $res ) {
- parent::preprocessResults( $db, $res );
-
- $this->currentCategoryCounts = [];
-
- if ( !$res->numRows() || !$this->isCached() ) {
- return;
- }
-
- // Fetch (hopefully) up-to-date numbers of pages in each category.
- // This should be fast enough as we limit the list to a reasonable length.
-
- $allCategories = [];
- foreach ( $res as $row ) {
- $allCategories[] = $row->title;
- }
-
- $categoryRes = $db->select(
- 'category',
- [ 'cat_title', 'cat_pages' ],
- [ 'cat_title' => $allCategories ],
- __METHOD__
- );
- foreach ( $categoryRes as $row ) {
- $this->currentCategoryCounts[$row->cat_title] = intval( $row->cat_pages );
- }
-
- // Back to start for display
- $res->seek( 0 );
- }
-
- /**
- * @param Skin $skin
- * @param object $result Result row
- * @return string
- */
- function formatResult( $skin, $result ) {
- $nt = Title::makeTitle( $result->namespace, $result->title );
- $text = new HtmlArmor( MediaWikiServices::getInstance()->getContentLanguage()
- ->convert( htmlspecialchars( $nt->getText() ) ) );
-
- if ( !$this->isCached() ) {
- // We can assume the freshest data
- $plink = $this->getLinkRenderer()->makeBrokenLink(
- $nt,
- $text
- );
- $nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
- } else {
- $plink = $this->getLinkRenderer()->makeLink( $nt, $text );
-
- $currentValue = $this->currentCategoryCounts[$result->title] ?? 0;
- $cachedValue = intval( $result->value ); // T76910
-
- // If the category has been created or emptied since the list was refreshed, strike it
- if ( $nt->isKnown() || $currentValue === 0 ) {
- $plink = "<del>$plink</del>";
- }
-
- // Show the current number of category entries if it changed
- if ( $currentValue !== $cachedValue ) {
- $nlinks = $this->msg( 'nmemberschanged' )
- ->numParams( $cachedValue, $currentValue )->escaped();
- } else {
- $nlinks = $this->msg( 'nmembers' )->numParams( $cachedValue )->escaped();
- }
- }
-
- return $this->getLanguage()->specialList( $plink, $nlinks );
- }
-
- protected function getGroupName() {
- return 'maintenance';
- }
-}
+++ /dev/null
-<?php
-/**
- * Implements Special:Wantedtemplates
- *
- * Copyright © 2008, Danny B.
- * Based on SpecialWantedcategories.php by Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- * makeWlhLink() taken from SpecialMostlinkedtemplates by Rob Church <robchur@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- * @author Danny B.
- */
-
-/**
- * A querypage to list the most wanted templates
- *
- * @ingroup SpecialPage
- */
-class WantedTemplatesPage extends WantedQueryPage {
- function __construct( $name = 'Wantedtemplates' ) {
- parent::__construct( $name );
- }
-
- function getQueryInfo() {
- return [
- 'tables' => [ 'templatelinks', 'page' ],
- 'fields' => [
- 'namespace' => 'tl_namespace',
- 'title' => 'tl_title',
- 'value' => 'COUNT(*)'
- ],
- 'conds' => [
- 'page_title IS NULL',
- 'tl_namespace' => NS_TEMPLATE
- ],
- 'options' => [ 'GROUP BY' => [ 'tl_namespace', 'tl_title' ] ],
- 'join_conds' => [ 'page' => [ 'LEFT JOIN',
- [ 'page_namespace = tl_namespace',
- 'page_title = tl_title' ] ] ]
- ];
- }
-
- protected function getGroupName() {
- return 'maintenance';
- }
-}
--- /dev/null
+<?php
+/**
+ * Implements Special:Withoutinterwiki
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @author Rob Church <robchur@gmail.com>
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Special page lists pages without language links
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialWithoutInterwiki extends PageQueryPage {
+ private $prefix = '';
+
+ function __construct( $name = 'Withoutinterwiki' ) {
+ parent::__construct( $name );
+ }
+
+ function execute( $par ) {
+ $this->prefix = Title::capitalize(
+ $this->getRequest()->getVal( 'prefix', $par ), NS_MAIN );
+ parent::execute( $par );
+ }
+
+ function getPageHeader() {
+ # Do not show useless input form if special page is cached
+ if ( $this->isCached() ) {
+ return '';
+ }
+
+ $formDescriptor = [
+ 'prefix' => [
+ 'label-message' => 'allpagesprefix',
+ 'name' => 'prefix',
+ 'id' => 'wiprefix',
+ 'type' => 'text',
+ 'size' => 20,
+ 'default' => $this->prefix
+ ]
+ ];
+
+ $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() );
+ $htmlForm->setWrapperLegend( '' )
+ ->setSubmitTextMsg( 'withoutinterwiki-submit' )
+ ->setMethod( 'get' )
+ ->prepareForm()
+ ->displayForm( false );
+ }
+
+ function sortDescending() {
+ return false;
+ }
+
+ function getOrderFields() {
+ return [ 'page_namespace', 'page_title' ];
+ }
+
+ function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ function getQueryInfo() {
+ $query = [
+ 'tables' => [ 'page', 'langlinks' ],
+ 'fields' => [
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_title'
+ ],
+ 'conds' => [
+ 'll_title IS NULL',
+ 'page_namespace' => MediaWikiServices::getInstance()->getNamespaceInfo()->
+ getContentNamespaces(),
+ 'page_is_redirect' => 0
+ ],
+ 'join_conds' => [ 'langlinks' => [ 'LEFT JOIN', 'll_from = page_id' ] ]
+ ];
+ if ( $this->prefix ) {
+ $dbr = wfGetDB( DB_REPLICA );
+ $query['conds'][] = 'page_title ' . $dbr->buildLike( $this->prefix, $dbr->anyString() );
+ }
+
+ return $query;
+ }
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
+}
+++ /dev/null
-<?php
-/**
- * Implements Special:Withoutinterwiki
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- * @author Rob Church <robchur@gmail.com>
- */
-
-use MediaWiki\MediaWikiServices;
-
-/**
- * Special page lists pages without language links
- *
- * @ingroup SpecialPage
- */
-class WithoutInterwikiPage extends PageQueryPage {
- private $prefix = '';
-
- function __construct( $name = 'Withoutinterwiki' ) {
- parent::__construct( $name );
- }
-
- function execute( $par ) {
- $this->prefix = Title::capitalize(
- $this->getRequest()->getVal( 'prefix', $par ), NS_MAIN );
- parent::execute( $par );
- }
-
- function getPageHeader() {
- # Do not show useless input form if special page is cached
- if ( $this->isCached() ) {
- return '';
- }
-
- $formDescriptor = [
- 'prefix' => [
- 'label-message' => 'allpagesprefix',
- 'name' => 'prefix',
- 'id' => 'wiprefix',
- 'type' => 'text',
- 'size' => 20,
- 'default' => $this->prefix
- ]
- ];
-
- $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() );
- $htmlForm->setWrapperLegend( '' )
- ->setSubmitTextMsg( 'withoutinterwiki-submit' )
- ->setMethod( 'get' )
- ->prepareForm()
- ->displayForm( false );
- }
-
- function sortDescending() {
- return false;
- }
-
- function getOrderFields() {
- return [ 'page_namespace', 'page_title' ];
- }
-
- function isExpensive() {
- return true;
- }
-
- function isSyndicated() {
- return false;
- }
-
- function getQueryInfo() {
- $query = [
- 'tables' => [ 'page', 'langlinks' ],
- 'fields' => [
- 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_title'
- ],
- 'conds' => [
- 'll_title IS NULL',
- 'page_namespace' => MediaWikiServices::getInstance()->getNamespaceInfo()->
- getContentNamespaces(),
- 'page_is_redirect' => 0
- ],
- 'join_conds' => [ 'langlinks' => [ 'LEFT JOIN', 'll_from = page_id' ] ]
- ];
- if ( $this->prefix ) {
- $dbr = wfGetDB( DB_REPLICA );
- $query['conds'][] = 'page_title ' . $dbr->buildLike( $this->prefix, $dbr->anyString() );
- }
-
- return $query;
- }
-
- protected function getGroupName() {
- return 'maintenance';
- }
-}
*/
private $blockStatusByUid;
+ /** @var int */
+ private $RCMaxAge;
+
+ /** @var string[] */
+ private $excludegroups;
+
/**
* @param IContextSource|null $context
* @param FormOptions $opts
function getQueryInfo( $data = null ) {
$dbr = $this->getDatabase();
- $useActor = (bool)(
- $this->getConfig()->get( 'ActorTableSchemaMigrationStage' ) & SCHEMA_COMPAT_READ_NEW
- );
-
$activeUserSeconds = $this->getConfig()->get( 'ActiveUserDays' ) * 86400;
$timestamp = $dbr->timestamp( wfTimestamp( TS_UNIX ) - $activeUserSeconds );
$fname = __METHOD__ . ' (' . $this->getSqlComment() . ')';
// Inner subselect to pull the active users out of querycachetwo
- $tables = [ 'querycachetwo', 'user' ];
- $fields = [ 'qcc_title', 'user_id' ];
+ $tables = [ 'querycachetwo', 'user', 'actor' ];
+ $fields = [ 'qcc_title', 'user_id', 'actor_id' ];
$jconds = [
'user' => [ 'JOIN', 'user_name = qcc_title' ],
+ 'actor' => [ 'JOIN', 'actor_user = user_id' ],
];
$conds = [
'qcc_type' => 'activeusers',
'ipblocks', '1', [ 'ipb_user=user_id', 'ipb_deleted' => 1 ]
) . ')';
}
- if ( $useActor ) {
- $tables[] = 'actor';
- $jconds['actor'] = [
- 'JOIN',
- 'actor_user = user_id',
- ];
- $fields[] = 'actor_id';
- }
$subquery = $dbr->buildSelectSubquery( $tables, $fields, $conds, $fname, $options, $jconds );
// Outer query to select the recent edit counts for the selected active users
$tables = [ 'qcc_users' => $subquery, 'recentchanges' ];
$jconds = [ 'recentchanges' => [ 'LEFT JOIN', [
- $useActor ? 'rc_actor = actor_id' : 'rc_user_text = qcc_title',
+ 'rc_actor = actor_id',
'rc_type != ' . $dbr->addQuotes( RC_EXTERNAL ), // Don't count wikidata.
'rc_type != ' . $dbr->addQuotes( RC_CATEGORIZE ), // Don't count categorization changes.
'rc_log_type IS NULL OR rc_log_type != ' . $dbr->addQuotes( 'newusers' ),
*/
protected $prefix;
+ /**
+ * @var string
+ */
+ protected $suffix;
+
/**
* @var Language
*/
/** @var array */
public $mConds;
+ /** @var int */
+ private $articleID;
+
+ /** @var int */
+ private $maxTimestamp;
+
public function __construct( SpecialMergeHistory $form, $conds, Title $source, Title $dest ) {
$this->mForm = $form;
$this->mConds = $conds;
- $this->title = $source;
$this->articleID = $source->getArticleID();
$dbr = wfGetDB( DB_REPLICA );
}
if ( $opts->getValue( 'hidepatrolled' ) ) {
- global $wgActorTableSchemaMigrationStage;
-
$tables[] = 'recentchanges';
$conds['rc_type'] = RC_LOG;
$conds['rc_log_type'] = 'upload';
$conds['rc_patrolled'] = RecentChange::PRC_UNPATROLLED;
$conds['rc_namespace'] = NS_FILE;
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
- $jcond = 'rc_actor = ' . $imgQuery['fields']['img_actor'];
- } else {
- $rcQuery = ActorMigration::newMigration()->getJoin( 'rc_user' );
- $tables += $rcQuery['tables'];
- $jconds += $rcQuery['joins'];
- $jcond = $rcQuery['fields']['rc_user'] . ' = ' . $imgQuery['fields']['img_user'];
- }
$jconds['recentchanges'] = [
'JOIN',
[
'rc_title = img_name',
- $jcond,
+ 'rc_actor = ' . $imgQuery['fields']['img_actor'],
'rc_timestamp = img_timestamp'
]
];
*/
public $mConds;
+ /** @var string|null */
+ private $level;
+
+ /** @var int|null */
+ private $namespace;
+
/**
* @param SpecialProtectedtitles $form
* @param array $conds
$this->mConds = $conds;
$this->level = $level;
$this->namespace = $namespace;
- $this->size = intval( $size );
parent::__construct( $form->getContext() );
}
$conds['pt_create_perm'] = $this->level;
}
- if ( !is_null( $this->namespace ) ) {
+ if ( $this->namespace !== null ) {
$conds[] = 'pt_namespace=' . $this->mDb->addQuotes( $this->namespace );
}
*/
protected $userGroupCache;
+ /** @var string */
+ protected $requestedGroup;
+
+ /** @var bool */
+ protected $editsOnly;
+
+ /** @var bool */
+ protected $temporaryGroupsOnly;
+
+ /** @var bool */
+ protected $creationSort;
+
+ /** @var bool|null */
+ protected $including;
+
+ /** @var string */
+ protected $requestedUser;
+
/**
* @param IContextSource|null $context
* @param array|null $par (Default null)
* Version number to tag cached versions of serialized User objects. Should be increased when
* {@link $mCacheVars} or one of it's members changes.
*/
- const VERSION = 13;
+ const VERSION = 14;
/**
* Exclude user options that are set to their default value.
case 'defaults':
$this->loadDefaults();
break;
- case 'name':
- // Make sure this thread sees its own changes
- $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
- if ( $lb->hasOrMadeRecentMasterChanges() ) {
- $flags |= self::READ_LATEST;
- $this->queryFlagsUsed = $flags;
- }
-
- $this->mId = self::idFromName( $this->mName, $flags );
- if ( !$this->mId ) {
- // Nonexistent user placeholder object
- $this->loadDefaults( $this->mName );
- } else {
- $this->loadFromId( $flags );
- }
- break;
case 'id':
// Make sure this thread sees its own changes, if the ID isn't 0
if ( $this->mId != 0 ) {
$this->loadFromId( $flags );
break;
case 'actor':
+ case 'name':
// Make sure this thread sees its own changes
$lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
if ( $lb->hasOrMadeRecentMasterChanges() ) {
list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
$row = wfGetDB( $index )->selectRow(
'actor',
- [ 'actor_user', 'actor_name' ],
- [ 'actor_id' => $this->mActorId ],
+ [ 'actor_id', 'actor_user', 'actor_name' ],
+ $this->mFrom === 'name' ? [ 'actor_name' => $this->mName ] : [ 'actor_id' => $this->mActorId ],
__METHOD__,
$options
);
if ( !$row ) {
// Ugh.
- $this->loadDefaults();
+ $this->loadDefaults( $this->mFrom === 'name' ? $this->mName : false );
} elseif ( $row->actor_user ) {
$this->mId = $row->actor_user;
$this->loadFromId( $flags );
} else {
- $this->loadDefaults( $row->actor_name );
+ $this->loadDefaults( $row->actor_name, $row->actor_id );
}
break;
case 'session':
* @return User The corresponding User object
*/
public static function newFromActorId( $id ) {
- global $wgActorTableSchemaMigrationStage;
-
- // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
- // but it does little harm and might be needed for write callers loading a User.
- if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) ) {
- throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__
- . ' when $wgActorTableSchemaMigrationStage lacks SCHEMA_COMPAT_NEW'
- );
- }
-
$u = new User;
$u->mActorId = $id;
$u->mFrom = 'actor';
* @return User
*/
public static function newFromAnyId( $userId, $userName, $actorId, $dbDomain = false ) {
- global $wgActorTableSchemaMigrationStage;
-
// Stop-gap solution for the problem described in T222212.
// Force the User ID and Actor ID to zero for users loaded from the database
// of another wiki, to prevent subtle data corruption and confusing failure modes.
$user = new User;
$user->mFrom = 'defaults';
- // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
- // but it does little harm and might be needed for write callers loading a User.
- if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) && $actorId !== null ) {
+ if ( $actorId !== null ) {
$user->mActorId = (int)$actorId;
if ( $user->mActorId !== 0 ) {
$user->mFrom = 'actor';
* the constructor does that instead.
*
* @param string|bool $name
+ * @param int|null $actorId
*/
- public function loadDefaults( $name = false ) {
+ public function loadDefaults( $name = false, $actorId = null ) {
$this->mId = 0;
$this->mName = $name;
- $this->mActorId = null;
+ $this->mActorId = $actorId;
$this->mRealName = '';
$this->mEmail = '';
$this->mOptionOverrides = null;
* user_properties Array with properties out of the user_properties table
*/
protected function loadFromRow( $row, $data = null ) {
- global $wgActorTableSchemaMigrationStage;
-
if ( !is_object( $row ) ) {
throw new InvalidArgumentException( '$row must be an object' );
}
$this->mGroupMemberships = null; // deferred
- // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
- // but it does little harm and might be needed for write callers loading a User.
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
- if ( isset( $row->actor_id ) ) {
- $this->mActorId = (int)$row->actor_id;
- if ( $this->mActorId !== 0 ) {
- $this->mFrom = 'actor';
- }
- $this->setItemLoaded( 'actor' );
- } else {
- $all = false;
+ if ( isset( $row->actor_id ) ) {
+ $this->mActorId = (int)$row->actor_id;
+ if ( $this->mActorId !== 0 ) {
+ $this->mFrom = 'actor';
}
+ $this->setItemLoaded( 'actor' );
+ } else {
+ $all = false;
}
if ( isset( $row->user_name ) && $row->user_name !== '' ) {
// Avoid PHP 7.1 warning of passing $this by reference
$thisUser = $this;
// Extensions
- Hooks::run( 'GetBlockedStatus', [ &$thisUser ] );
+ Hooks::run( 'GetBlockedStatus', [ &$thisUser ], '1.34' );
}
/**
if ( !$this->mHideName ) {
// Reset for hook
$this->mHideName = false;
- Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
+ Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ], '1.34' );
}
return (bool)$this->mHideName;
}
* @return int The user's ID; 0 if the user is anonymous or nonexistent
*/
public function getId() {
- if ( $this->mId === null && $this->mName !== null && self::isIP( $this->mName ) ) {
+ if ( $this->mId === null && $this->mName !== null &&
+ ( self::isIP( $this->mName ) || ExternalUserNames::isExternal( $this->mName ) )
+ ) {
// Special case, we know the user is anonymous
return 0;
}
* @return int The actor's ID, or 0 if no actor ID exists and $dbw was null
*/
public function getActorId( IDatabase $dbw = null ) {
- global $wgActorTableSchemaMigrationStage;
-
- // Technically we should always return 0 without SCHEMA_COMPAT_READ_NEW,
- // but it does little harm and might be needed for write callers loading a User.
- if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) ) {
- return 0;
- }
-
if ( !$this->isItemLoaded( 'actor' ) ) {
$this->load();
}
- // Currently $this->mActorId might be null if $this was loaded from a
- // cache entry that was written when $wgActorTableSchemaMigrationStage
- // was SCHEMA_COMPAT_OLD. Once that is no longer a possibility (i.e. when
- // User::VERSION is incremented after $wgActorTableSchemaMigrationStage
- // has been removed), that condition may be removed.
- if ( $this->mActorId === null || !$this->mActorId && $dbw ) {
+ if ( !$this->mActorId && $dbw ) {
$q = [
'actor_user' => $this->getId() ?: null,
'actor_name' => (string)$this->getName(),
];
- if ( $dbw ) {
- if ( $q['actor_user'] === null && self::isUsableName( $q['actor_name'] ) ) {
+ if ( $q['actor_user'] === null && self::isUsableName( $q['actor_name'] ) ) {
+ throw new CannotCreateActorException(
+ 'Cannot create an actor for a usable name that is not an existing user'
+ );
+ }
+ if ( $q['actor_name'] === '' ) {
+ throw new CannotCreateActorException( 'Cannot create an actor for a user with no name' );
+ }
+ $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
+ if ( $dbw->affectedRows() ) {
+ $this->mActorId = (int)$dbw->insertId();
+ } else {
+ // Outdated cache?
+ // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
+ $this->mActorId = (int)$dbw->selectField(
+ 'actor',
+ 'actor_id',
+ $q,
+ __METHOD__,
+ [ 'LOCK IN SHARE MODE' ]
+ );
+ if ( !$this->mActorId ) {
throw new CannotCreateActorException(
- 'Cannot create an actor for a usable name that is not an existing user'
- );
- }
- if ( $q['actor_name'] === '' ) {
- throw new CannotCreateActorException( 'Cannot create an actor for a user with no name' );
- }
- $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
- if ( $dbw->affectedRows() ) {
- $this->mActorId = (int)$dbw->insertId();
- } else {
- // Outdated cache?
- // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
- $this->mActorId = (int)$dbw->selectField(
- 'actor',
- 'actor_id',
- $q,
- __METHOD__,
- [ 'LOCK IN SHARE MODE' ]
+ "Cannot create actor ID for user_id={$this->getId()} user_name={$this->getName()}"
);
- if ( !$this->mActorId ) {
- throw new CannotCreateActorException(
- "Cannot create actor ID for user_id={$this->getId()} user_name={$this->getName()}"
- );
- }
}
- $this->invalidateCache();
- } else {
- list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $this->queryFlagsUsed );
- $db = wfGetDB( $index );
- $this->mActorId = (int)$db->selectField( 'actor', 'actor_id', $q, __METHOD__, $options );
}
+ $this->invalidateCache();
$this->setItemLoaded( 'actor' );
}
// If there is a new, unseen, revision, use its timestamp
$nextid = $oldid
- ? $title->getNextRevisionID( $oldid, Title::GAID_FOR_UPDATE )
+ ? $title->getNextRevisionID( $oldid, Title::READ_LATEST )
: null;
if ( $nextid ) {
$this->setNewtalk( true, Revision::newFromId( $nextid ) );
$dbw = wfGetDB( DB_MASTER );
$dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $newTouched ) {
- global $wgActorTableSchemaMigrationStage;
-
$dbw->update( 'user',
[ /* SET */
'user_name' => $this->mName,
);
}
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
- $dbw->update(
- 'actor',
- [ 'actor_name' => $this->mName ],
- [ 'actor_user' => $this->mId ],
- $fname
- );
- }
+ $dbw->update(
+ 'actor',
+ [ 'actor_name' => $this->mName ],
+ [ 'actor_user' => $this->mId ],
+ $fname
+ );
} );
$this->mTouched = $newTouched;
* @param IDatabase $dbw Writable database handle
*/
private function updateActorId( IDatabase $dbw ) {
- global $wgActorTableSchemaMigrationStage;
-
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
- $dbw->insert(
- 'actor',
- [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
- __METHOD__
- );
- $this->mActorId = (int)$dbw->insertId();
- }
+ $dbw->insert(
+ 'actor',
+ [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
+ __METHOD__
+ );
+ $this->mActorId = (int)$dbw->insertId();
}
/**
* - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
*/
public static function getQueryInfo() {
- global $wgActorTableSchemaMigrationStage;
-
$ret = [
- 'tables' => [ 'user' ],
+ 'tables' => [ 'user', 'user_actor' => 'actor' ],
'fields' => [
'user_id',
'user_name',
'user_email_token_expires',
'user_registration',
'user_editcount',
+ 'user_actor.actor_id',
+ ],
+ 'joins' => [
+ 'user_actor' => [ 'JOIN', 'user_actor.actor_user = user_id' ],
],
- 'joins' => [],
];
- // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
- // but it does little harm and might be needed for write callers loading a User.
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
- $ret['tables']['user_actor'] = 'actor';
- $ret['fields'][] = 'user_actor.actor_id';
- $ret['joins']['user_actor'] = [
- ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ? 'JOIN' : 'LEFT JOIN',
- [ 'user_actor.actor_user = user_id' ]
- ];
- }
-
return $ret;
}
* @license MIT
*/
class CheckMatrixWidget extends \OOUI\Widget {
-
- protected $name = '';
- protected $columns = [];
- protected $rows = [];
- protected $tooltips = [];
- protected $values = [];
- protected $forcedOn = [];
- protected $forcedOff = [];
+ /** @var string|null */
+ protected $name;
+ /** @var string|null */
+ protected $id;
+ /** @var array */
+ protected $columns;
+ /** @var array */
+ protected $rows;
+ /** @var array */
+ protected $tooltips;
+ /** @var array */
+ protected $values;
+ /** @var array */
+ protected $forcedOn;
+ /** @var array */
+ protected $forcedOff;
/**
* Operates similarly to MultiSelectWidget, but instead of using an array of
* @license MIT
*/
class ComplexTitleInputWidget extends \OOUI\Widget {
-
+ /** @var array */
+ protected $config;
protected $namespace = null;
protected $title = null;
* @license MIT
*/
class NamespaceInputWidget extends \OOUI\DropdownInputWidget {
-
- protected $includeAllValue = null;
+ /** @var string */
+ protected $includeAllValue;
+ /** @var int[] */
+ protected $exclude;
/**
* @param array $config Configuration options
* @license MIT
*/
class SelectWithInputWidget extends \OOUI\Widget {
-
- protected $textinput = null;
- protected $dropdowninput = null;
+ /** @var array */
+ protected $config;
+ /** @var TextInputWidget */
+ protected $textinput;
+ /** @var DropdownInputWidget */
+ protected $dropdowninput;
/**
* A version of the SelectWithInputWidget, with `or` set to true.
* @license MIT
*/
class SizeFilterWidget extends \OOUI\Widget {
-
- protected $radioselectinput = null;
- protected $textinput = null;
+ /** @var array */
+ protected $config;
+ /** @var LabelWidget */
+ protected $label;
+ /** @var RadioSelectInputWidget */
+ protected $radioselectinput;
+ /** @var TextInputWidget */
+ protected $textinput;
/**
* RadioSelectInputWidget and a TextInputWidget to set minimum or maximum byte size
* @license MIT
*/
abstract class TagMultiselectWidget extends \OOUI\Widget {
-
- protected $selectedArray = [];
- protected $inputName = null;
- protected $inputPlaceholder = null;
- protected $tagLimit = null;
+ /** @var array */
+ protected $selectedArray;
+ /** @var string|null */
+ protected $inputName;
+ /** @var string|null */
+ protected $inputPlaceholder;
+ /** @var array */
+ protected $input;
+ /** @var int|null */
+ protected $tagLimit;
/**
* @param array $config Configuration options
* - array $config['default'] Array of items to use as preset data
- * - array $config['name'] Name attribute (used in forms)
- * - array $config['placeholder'] Placeholder message for input
+ * - string $config['name'] Name attribute (used in forms)
+ * - string $config['placeholder'] Placeholder message for input
* - array $config['input'] Config options for the input widget
- * - number $config['tagLimit'] Maximum number of selected items
+ * - int $config['tagLimit'] Maximum number of selected items
*/
public function __construct( array $config = [] ) {
parent::__construct( $config );
// Properties
- if ( isset( $config['default'] ) ) {
- $this->selectedArray = $config['default'];
- }
- if ( isset( $config['name'] ) ) {
- $this->inputName = $config['name'];
- }
- if ( isset( $config['placeholder'] ) ) {
- $this->inputPlaceholder = $config['placeholder'];
- }
- if ( isset( $config['input'] ) ) {
- $this->input = $config['input'];
- } else {
- $this->input = [];
- }
- if ( isset( $config['tagLimit'] ) ) {
- $this->tagLimit = $config['tagLimit'];
- }
+ $this->selectedArray = $config['default'] ?? [];
+ $this->inputName = $config['name'] ?? null;
+ $this->inputPlaceholder = $config['placeholder'] ?? null;
+ $this->input = $config['input'] ?? [];
+ $this->tagLimit = $config['tagLimit'] ?? null;
$textarea = new MultilineTextInputWidget( array_merge( [
'name' => $this->inputName,
"content-model-css": "CSS",
"content-json-empty-object": "Prázdný objekt",
"content-json-empty-array": "Prázdné pole",
+ "unsupported-content-model": "<strong>Varování:</strong> Model obsahu $1 není na této wiki podporován.",
+ "unsupported-content-diff": "Rozdíly obsahů s modelem $1 nejsou podporovány.",
+ "unsupported-content-diff2": "Rozdíly mezi obsahy s modely $1 a $2 nejsou na této wiki podporovány.",
"deprecated-self-close-category": "Stránky s neplatnými sebeuzavírajícími HTML značkami",
"deprecated-self-close-category-desc": "Stránka obsahuje neplatné sebeuzavírající HTML značky, například <code><b/></code> nebo <code><span/></code>. Jejich chování se v zájmu konzistence se specifikací HTML5 brzy změní, proto je jejich použití ve wikitextu zastaralé.",
"duplicate-args-warning": "<strong>Upozornění:</strong> Stránka [[:$1]] volá [[:$2]] s více než jednou hodnotou parametru „$3“. Použije se jen poslední uvedená hodnota.",
"undo-norev": "Tuto editaci není možné vrátit, protože neexistuje nebo byla smazána.",
"undo-nochange": "Zdá se, že editace již byla zrušena.",
"undo-summary": "Zrušena verze $1 od uživatele [[Special:Contributions/$2|$2]] ([[User talk:$2|diskuse]])",
+ "undo-summary-anon": "Zrušena verze $1 od uživatele [[Special:Contributions/$2|$2]]",
"undo-summary-username-hidden": "Zrušena verze $1 od skrytého uživatele",
"cantcreateaccount-text": "Zakládání nových účtů z této IP adresy (<strong>$1</strong>) bylo zablokováno {{GENDER:$3|uživatelem|uživatelkou}} [[User:$3|$3]].\n\n$3 uvádí toto zdůvodnění: <em>$2</em>",
"cantcreateaccount-range-text": "Zakládání nových účtů z IP adres v rozsahu <strong>$1</strong>, který obsahuje i vaši IP adresu (<strong>$4</strong>), bylo zablokováno {{GENDER:$3|uživatelem|uživatelkou}} [[User:$3|$3]].\n\n$3 uvádí toto zdůvodnění: <em>$2</em>",
"alreadyrolled": "Nelze vrátit zpět poslední editaci [[:$1]] od uživatele [[User:$2|$2]] ([[User talk:$2|diskuse]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]), protože někdo jiný již stránku editoval nebo vrátil tuto změnu zpět.\n\nPoslední editaci této stránky {{GENDER:$3|provedl|provedla|provedl uživatel}} [[User:$3|$3]] ([[User talk:$3|diskuse]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
"editcomment": "Shrnutí editace bylo: <em>$1</em>.",
"revertpage": "Editace uživatele „[[Special:Contributions/$2|$2]]“ ([[User talk:$2|diskuse]]) vráceny do předchozího stavu, jehož autorem je „[[User:$1|$1]]“",
+ "revertpage-anon": "Editace uživatele „[[Special:Contributions/$2|$2]]“ vráceny do předchozího stavu, jehož autorem je „[[User:$1|$1]]“",
"revertpage-nouser": "Editace skrytého uživatele vráceny do předchozího stavu, jehož {{GENDER:$1|autorem|autorkou}} je „[[User:$1|$1]]“",
"rollback-success": "Editace {{GENDER:$3|uživatele|uživatelky}} $1 byly vráceny na poslední verzi od {{GENDER:$4|uživatele|uživatelky}} $2.",
"sessionfailure-title": "Chyba relace",
"checkbox-all": "Pêro",
"checkbox-none": "Çıniyo",
"checkbox-invert": "Dimlaşt ke",
- "allpages": "Pêro peli",
+ "allpages": "Perri pêro",
"nextpage": "Pela bahdoyêne ($1)",
"prevpage": "Pela veri ($1)",
"allpagesfrom": "Herfa kı pa liste bo:",
"tog-useeditwarning": "Warn me when I leave an edit page with unsaved changes",
"tog-prefershttps": "Always use a secure connection while logged in",
"tog-showrollbackconfirmation": "Show a confirmation prompt when clicking on a rollback link",
+ "tog-requireemail": "Require email for password resets",
"underline-always": "Always",
"underline-never": "Never",
"underline-default": "Skin or browser default",
"prefs-help-email": "Email address is optional, but is needed for password resets, should you forget your password.",
"prefs-help-email-others": "You can also choose to let others contact you by email through a link on your user or talk page.\nYour email address is not revealed when other users contact you.",
"prefs-help-email-required": "Email address is required.",
+ "prefs-help-requireemail": "If checked, will only send password reset emails if the resetting person has provided both username and email for this account.",
"prefs-info": "Basic information",
"prefs-i18n": "Internationalisation",
"prefs-signature": "Signature",
"ipblocklist-legend": "Find a blocked user",
"blocklist-userblocks": "Hide account blocks",
"blocklist-tempblocks": "Hide temporary blocks",
+ "blocklist-indefblocks": "Hide indefinite blocks",
"blocklist-addressblocks": "Hide single IP blocks",
"blocklist-type": "Type:",
"blocklist-type-opt-all": "All",
"Matma Rex",
"Sp5uhe",
"Stlmch",
- "Railfail536"
+ "Railfail536",
+ "Rail"
]
},
"exif-imagewidth": "Szerokość",
"blockedtitle": "L’utilisateur est bloqué.",
"blocked-email-user": "<strong>Votre nom d’utilisateur a été bloqué pour l’envoi de courriels. Vous pouvez toujours modifier d’autres pages sur ce wiki.</strong> Vous pouvez voir tous les détails du blocage sur [[Special:MyContributions|contributions du compte]].\n\nCe blocage a été fait par $1.\n\nLe motif fourni est <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Cible souhaitée du blocage : $7\n* ID du blocage nº $5",
"blockedtext-partial": "<strong>Votre nom d’utilisateur ou votre adresse IP a été bloqué pour effectuer des modifications sur cette page. Vous pouvez toujours modifier d’autres pages sur ce wiki.</strong> Vous pouvez voir tous les détails sur ce blocage sur [[Special:MyContributions|contributions du compte]].\n\nLe blocage a été effectué par $1.\n\nLe motif fourni est <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Cible souhaitée du blocage : $7\n* ID du blocage nº $5",
- "blockedtext": "<strong>Votre compte utilisateur ou votre adresse IP a été bloqué.</strong>\n\nLe blocage a été effectué par $1.\nLa raison invoquée est la suivante : <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Compte bloqué : $7.\n\nVous pouvez contacter $1 ou un autre [[{{MediaWiki:Grouppage-sysop}}|administrateur]] pour en discuter.\nVous ne pouvez utiliser la fonction « {{int:emailuser}} » que si une adresse de courriel valide est spécifiée dans vos [[Special:Preferences|préférences]] et que si cette fonctionnalité ne vous a pas été bloquée.\nVotre adresse IP actuelle est $3 et votre identifiant de blocage est $5.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
- "autoblockedtext": "Votre adresse IP a été bloquée automatiquement car elle a été utilisée par un autre utilisateur, lui-même bloqué par $1.\nLa raison invoquée est :\n\n: <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Compte bloqué : $7\n\nVous pouvez contacter $1 ou l’un des autres [[{{MediaWiki:Grouppage-sysop}}|administrateurs]] pour discuter de ce blocage.\n\nNotez que vous ne pourrez utiliser la fonctionnalité « {{int:emailuser}} » que si vous avez une adresse de courriel validée dans vos [[Special:Preferences|préférences]] et que cette fonctionnalité ne vous a pas été désactivée.\n\nVotre adresse IP actuelle est $3, et le numéro de blocage est $5.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
+ "blockedtext": "<strong>Votre compte utilisateur ou votre adresse IP a été bloqué.</strong>\n\nLe blocage a été effectué par $1.\nLa raison invoquée est la suivante : <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Compte bloqué : $7.\n\nVous pouvez contacter $1 ou un autre [[{{MediaWiki:Grouppage-sysop}}|administrateur]] pour en discuter.\nVous ne pouvez utiliser la fonction « {{int:emailuser}} » que si une adresse de courriel valide est spécifiée dans vos [[Special:Preferences|préférences]] et que si cette fonctionnalité ne vous a pas été bloquée.\nVotre adresse IP actuelle est $3 et votre identifiant de blocage est $5.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
+ "autoblockedtext": "Votre adresse IP a été bloquée automatiquement car elle a été utilisée par un autre utilisateur, lui-même bloqué par $1.\nLa raison invoquée est :\n\n: <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Compte bloqué : $7\n\nVous pouvez contacter $1 ou l’un des autres [[{{MediaWiki:Grouppage-sysop}}|administrateurs]] pour discuter de ce blocage.\n\nNotez que vous ne pourrez utiliser la fonctionnalité « {{int:emailuser}} » que si vous avez une adresse de courriel validée dans vos [[Special:Preferences|préférences]] et que cette fonctionnalité ne vous a pas été désactivée.\n\nVotre adresse IP actuelle est $3, et le numéro de blocage est $5.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
"systemblockedtext": "Votre nom d'utilisateur ou votre adresse IP ont été bloqués automatiquement par MediaWiki.\nLa raison donnée est la suivante:\n\n: <em>$2</em>.\n\n* Le début du blocage: $8\n* Expiration du délai de blocage: $6\n* Elément concerné: $7\n\nVotre adresse IP actuelle est $3.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
"blockednoreason": "aucune raison donnée",
"blockedtext-composite": "<strong>Votre nom d'utilisateur ou votre adresse IP ont été bloqués.</strong>\n\nLa raison invoquée est :\n\n: <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage le plus long : $6\n* $5\n\nVotre adresse IP actuelle est $3.\nVeuillez inclure tous les détails ci-dessus dans chaque demande que vous ferez.",
"blocked-notice-logextract": "Cet utilisateur est actuellement bloqué.\nLa dernière entrée du journal des blocages est affichée ci-dessous pour référence :",
"clearyourcache": "<strong>Note :</strong> après avoir enregistré vos modifications, il se peut que vous deviez forcer le rechargement complet du cache de votre navigateur pour voir les changements.\n* <strong>Firefox / Safari :</strong> maintenez la touche <em>Maj</em> (<em>Shift</em>) en cliquant sur le bouton <em>Actualiser</em> ou pressez <em>Ctrl-F5</em> ou <em>Ctrl-R</em> (<em>⌘-R</em> sur un Mac) \n* <strong>Google Chrome :</strong> appuyez sur <em>Ctrl-Maj-R</em> (<em>⌘-Shift-R</em> sur un Mac) \n* <strong>Internet Explorer :</strong> maintenez la touche <em>Ctrl</em> en cliquant sur le bouton <em>Actualiser</em> ou pressez <em>Ctrl-F5</em> \n* <strong>Opera :</strong> allez dans <em>Menu → Settings</em> (<em>Opera → Préférences</em> sur un Mac) et ensuite à <em>Confidentialité & sécurité → Effacer les données d’exploration → Images et fichiers en cache</em>.",
"usercssyoucanpreview": "<strong>Astuce :</strong> utilisez le bouton « {{int:showpreview}} » pour tester votre nouvelle feuille CSS avant de l’enregistrer.",
- "userjsonyoucanpreview": "<strong>Conseil :</strong> Utiliser le bouton « {{int:showpreview}} » pour tester votre nouveau JSON avant enregistrement.",
+ "userjsonyoucanpreview": "<strong>Conseil :</strong> utilisez le bouton « {{int:showpreview}} » pour tester votre nouveau JSON avant enregistrement.",
"userjsyoucanpreview": "<strong>Astuce :</strong> utilisez le bouton « {{int:showpreview}} » pour tester votre nouvelle feuille JavaScript avant de l’enregistrer.",
"usercsspreview": "<strong>Rappelez-vous que vous ne faites que prévisualiser votre propre feuille CSS. \nElle n’a pas encore été enregistrée !</strong>",
"userjsonpreview": "<strong>Rappelez-vous que vous êtes seulement en train de tester/voir un aperçu de votre configuration utilisateur JSON.\nElle n’a pas encore été enregistrée !</strong>",
"sitecsspreview": "<strong>Rappelez-vous que vous ne faites que prévisualiser cette feuille de style. \nElle n’a pas encore été enregistrée !</strong>",
"sitejsonpreview": "<strong>Souvenez-vous que vous ne faites que regarder un aperçu de cette configuration JSON.\nElle n’a pas encore été enregistrée !</strong>",
"sitejspreview": "<strong>Rappelez-vous que vous ne faites que prévisualiser ce code JavaScript. \nIl n’a pas encore été enregistré !</strong>",
- "userinvalidconfigtitle": "<strong>Attention :</strong> il n’existe pas d’habillage « $1 ». \nLes pages personnelles avec extensions .css, .json et .js utilisent des titres en minuscules, par exemple {{ns:user}}:Foo/vector.css et non {{ns:user}}:Foo/Vector.css.",
+ "userinvalidconfigtitle": "<strong>Attention :</strong> il n’existe pas d’habillage « $1 ».\nLes pages personnelles avec extensions .css, .json et .js utilisent des titres en minuscules, par exemple {{ns:user}}:Foo/vector.css et non {{ns:user}}:Foo/Vector.css.",
"updated": "(Mis à jour)",
"note": "<strong>Note :</strong>",
"previewnote": "<strong>Rappelez-vous que ce n’est qu’une prévisualisation.</strong>\nVos modifications n’ont pas encore été enregistrées !",
"uploaded-event-handler-on-svg": "Fixer des attributs de gestionnaire d’événement <code>$1=\"$2\"</code> n’est pas autorisé dans les fichiers SVG.",
"uploaded-href-attribute-svg": "<a> les éléments ne peuvent être liés (href) qu’aux cibles de données : (fichier inclus), http:// ou https://, ou un fragment (#, même document). Pour les autres éléments, comme <image>, seuls data: et fragment sont autorisés. Essayez les images incluses lors de l’export de votre SVG. <code><$1 $2=\"$3\"></code> obtenu.",
"uploaded-href-unsafe-target-svg": "Un href vers des données non sûres a été trouvé dans le fichier SVG téléversé : URI cible <code><$1 $2=\"$3\"></code>.",
- "uploaded-animate-svg": "Balise « animate » trouvée, qui pourrait modifier le href en utilisant l’attribut « from » <code><$1 $2=\"$3\"></code> dans le fichier SVG téléversé.",
+ "uploaded-animate-svg": "Balise « animate » trouvée qui pourrait modifier le href en utilisant l’attribut « from » <code><$1 $2=\"$3\"></code> dans le fichier SVG téléversé.",
"uploaded-setting-event-handler-svg": "Positionner les attributs du gestionnaire d’événements n'est pas possible, <code><$1 $2=\"$3\"></code> trouvé dans le fichier SVG téléversé.",
"uploaded-setting-href-svg": "L’utilisation de la balise « set » pour ajouter un attribut « href » à l’élément parent est interdite.",
- "uploaded-wrong-setting-svg": "L’utilisation de la balise « set » pour ajouter une cible distante, de données ou de type script, à un attribut quelconque, est interdite. <code><set to=\"$1\"></code> a été trouvé dans le fichier SVG téléversé.",
+ "uploaded-wrong-setting-svg": "L’utilisation de la balise « set » pour ajouter à un attribut quelconque une cible distante, de données ou de script est interdite. <code><set to=\"$1\"></code> a été trouvé dans le fichier SVG téléversé.",
"uploaded-setting-handler-svg": "Les SVG qui positionnent l’attribut « handler » avec distant/données/script sont bloqués. <code>$1=\"$2\"</code> a été trouvé dans le fichier SVG téléversé.",
"uploaded-remote-url-svg": "Les SVG qui positionnent un attribut de style avec une URL distante sont bloqués. <code>$1=\"$2\"</code> trouvé dans le fichier SVG téléversé.",
"uploaded-image-filter-svg": "Filtre d’image avec URL trouvé : <code><$1 $2=\"$3\"></code> dans le fichier SVG téléversé.",
"uploadstash-errclear": "La suppression des fichiers a échoué.",
"uploadstash-refresh": "Actualiser la liste des fichiers",
"uploadstash-thumbnail": "afficher la vignette",
- "uploadstash-exception": "Impossible de stocker le téléversement dans la réserve ($1) : « $2 ».",
+ "uploadstash-exception": "Impossible de stocker le téléversement dans la réserve ($1) : « $2 ».",
"uploadstash-bad-path": "Le chemin n’existe pas.",
"uploadstash-bad-path-invalid": "Le chemin n’est pas valide.",
"uploadstash-bad-path-unknown-type": "Type « $1 » inconnu.",
"category-file-count": "{{PLURAL:$2|ह्या वर्गांत फकत सकयली फायल आसपावता.|ह्या वर्गांत सकयल दिल्लीं {{PLURAL:$1|फायल|$1 फायलीं}} आसता, वट्ट फायलीं $2}}",
"listingcontinuesabbrev": "चालू.",
"noindex-category": "बिननिर्देशांकी पानां",
+ "broken-file-category": "तुटलेल्या फायलींचो दुवे आसलेलीं पानां",
"about": "विशीं",
"article": "मजकूराचीं पानां",
"newwindow": "(नव्या ज़ोणेलांत उकतें जाता)",
"mainpage-nstab": "मुखेल पान",
"nosuchaction": "असले तरेचे कार्य ना",
"nosuchspecialpage": "असले कांयच विशेश पान ना",
+ "nospecialpagetext": "<strong>तुवें एक अवैद खेरीत पान मागलां.</strong>\n\nएक खेरीत पानाची वळेरी तुका हांगासर मेळूं येता [[Special:SpecialPages|{{int:specialpages}}]].",
"error": "चूक",
"databaseerror": "डॅटाबॅज त्रुटी",
"databaseerror-textcl": "डॅटाबेज विरोध त्रुटी आयिल्ली आसा",
"badtitle": "चुकीचो माथाळो",
"badtitletext": "विनवणी केल्लें पानाचो माथाळो अवैध, रितो वा अयोग्य तरेन आंतरभाशी वा आंतर विकी माथाळ्या कडे जोडिल्लो आशिल्लो. तातूंत माथाळ्यांत वापरुं नजो अशी एक वा चड अक्षरां आसूं येतात.",
"viewsource": "उगम पळेयात",
+ "viewsource-title": "$1 खातीर मूळ पळय",
+ "viewsourcetext": "तुज्यान ह्या पानाचें मूळ पळोवंक आनी नकल करुंक जाता.",
"yourname": "वापरप्याचे नांव",
"userlogin-yourname": "वापरप्याचे नांव",
"userlogin-yourname-ph": "वापरप्याचे नांव घालात",
"accmailtitle": "गुपीत उतर धाडलां",
"newarticle": "(नवें)",
"newarticletext": "जें पान अजून अस्तित्वांत ना अशा पानाचे दुवे फाटल्यान तुमी आसात. पान रचपाक सकयले चौकटींत टायप करपाक सुरु करात (चड म्हायती खातीर [$1 आदाराचें पान] पळेयात) जर ह्या पानार तुमी चुकून पावल्यात तर ब्रावजराचो बॅक (<strong>फटीं</strong>) हो बटन दामात",
- "noarticletext": "सदà¥\8dया हà¥\8dया पानाà¤\9aà¥\87र à¤\95सलà¥\80à¤\9a मà¤\9cà¤\95à¥\82र ना. \nतà¥\81मà¥\80 हà¥\87र पानाà¤\82à¤\9aà¥\87र [[Special:Search/{{PAGENAME}}|हà¥\8b माथाळà¥\8b]] सà¥\8bदà¥\82à¤\82 शà¤\95तात,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} सà¤\82बà¤\82दà¥\80त लà¥\89à¤\97 सà¥\8bदà¥\82à¤\82 शà¤\95तात],\nवा हà¥\8dया पानाà¤\95 [{{fullurl:{{FULLPAGENAME}}|action=edit}} सà¤\82पादà¥\80त] à¤\95रà¥\82à¤\82 शà¤\95तात</span>।",
+ "noarticletext": "सधà¥\8dयाà¤\95 हà¥\87à¤\82 पान रिà¤\82तà¥\87 à¤\86सा.\nतà¥\81à¤\9cà¥\8dयान दà¥\82सऱà¥\8dया पानानà¥\80 [[Special:Search/{{PAGENAME}}| हà¥\8dया पानाà¤\9aà¥\87 नाà¤\82व सà¥\8bदà¥\82à¤\82à¤\95 à¤\9cाता]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAME}}}} सà¤\82बà¤\82धà¥\80 सतà¥\8dरानà¥\80 सà¥\8bदà¥\82à¤\82à¤\95 à¤\9cाता], वा [{{fullurl:{{FULLPAGENAME}}|action=edit}} हà¥\87à¤\82 पान रà¤\9aà¥\82à¤\82à¤\95 à¤\9cाता]</span>.",
"noarticletext-nopermission": "तुर्ताक ह्या पानाचेर कसलोच मजकूर ना. तुमी हेर पानांचेर [[Special:Search/{{PAGENAME}}|ह्या माथाळ्याचो सोद]] घेवं शकतात,\nवा <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} संबंदीत लॉग सोदूं शकतात]</span>, पूण तुमकां हें पानाची रचणूक करपाची परवानगी ना।",
"userpage-userdoesnotexist-view": "\"$1\" ह्या वापरप्याच्या खात्याची नोंदणी करूंक ना.",
"previewnote": "'''ही फकत एक दाखवण हें मतींत दवरात.'''\nतुमचें बदल आडून राखून दवरूंक ना!",
"hiddencategories": "हें पान {{PLURAL:$1|लिपिल्ले वर्गाचें}} आसा",
"permissionserrorstext-withaction": "ह्या {{PLURAL:$1|कारण|कारणां}}: खातीर तुका $2 मान्यताय ना.",
"recreate-moveddeleted-warn": "शिटकावणीः तुमी आदीं काडून उडयिल्लें पान परतून तयार करतात ह्या पानाचे फासून उडोवपी आनी दुसरे कडे व्हरपी लाग फकत सोपेपणा खातीर दिल्यात",
- "moveddeleted-notice": "हà¥\87à¤\82 पान à¤\95ाडà¥\82न à¤\89डयला.\nहà¥\8dया पानाà¤\9aà¥\8b à¤\95ाडà¥\82न à¤\89डà¥\8bवपà¥\80 à¤\86नà¥\80 हालà¥\8bवपà¥\80 लà¥\89à¤\97 संदर्भा खातीर सकयल दिला.",
+ "moveddeleted-notice": "हà¥\87à¤\82 पान à¤\95ाडà¥\82न à¤\89डयला.\nहà¥\8dया पानाà¤\9aà¥\87à¤\82 à¤\95ाडà¥\82न à¤\89डà¥\8bवपाà¤\9aà¥\87à¤\82, राà¤\96पाà¤\9aà¥\87à¤\82, à¤\86नà¥\80 हालà¥\8bवपाà¤\9aà¥\87à¤\82 सतà¥\8dर संदर्भा खातीर सकयल दिला.",
"content-model-wikitext": "विकीमजकूर",
"content-model-text": "सादोमजकूर",
"post-expand-template-inclusion-warning": "<strong>शिटकावणीः</strong> सांचो धरून आकार अगडबंब जाता, कांय सांच्याचो आसपाव जावचो ना.",
"page_first": "पयलें",
"page_last": "निमणें",
"histlegend": "फरकाची निवडणी : पुनर्नियाळांची तुळा करपा खातीर रेडियो चौकटीं चेर कुरु करात आनी ''एंटर'' ना तर तळाकडे आशिल्लो बुतांव दामात।<br />\nविवरण : <strong>({{int:cur}})</strong> = हालींची पुनर्नियाळा बरोबर फरक, <strong>({{int:last}})</strong> = आदली पुनर्नियाळा बरोबर फरक, <strong>{{int:minoreditletter}}</strong> = दाक्टें बदल।",
- "history-fieldset-title": "à¤\9aाळपाà¤\9aà¥\8b à¤\87तिहास",
+ "history-fieldset-title": "à¤\89à¤\9cळणà¥\8dयà¥\8b à¤\9aाळ",
"history-show-deleted": "फकत काडून उडयिल्लें",
"histfirst": "पोरणो",
"histlast": "नवो ताल्ल",
"searchprofile-advanced-tooltip": "खाशेल्या नांवथोळाणी सोदात",
"search-result-size": "$1 ({{PLURAL:$2|1 उतर|$2 उतरां}})",
"search-result-category-size": "{PLURAL:$1|1 सदस्य|$1 सदस्य}} ({{PLURAL:$2|1 उपगट|$2 उपगट}}, {{PLURAL:$3|1 फायल|$3 फायलीं}})",
- "search-redirect": "(पुनर्निर्देशन $1)",
+ "search-redirect": "($1 सावन पुनर्निर्देशीत)",
"search-section": "(विभाग $1)",
"search-suggest": "तुमकां $1 अशें म्हणपाचें आसलें?",
"search-rewritten": "$1 हाचो निकाल दाखयता.नाजाल्यार $2 हें सोदात.",
"recentchanges": "हालींचे बदल",
"recentchanges-legend": "हालींच जाल्ल्या बदलाचो विकल्प",
"recentchanges-summary": "ह्या विकीचेर हालींच जाल्ल्या बदलांचो माग ह्या भरणांतल्यान दवरात",
+ "recentchanges-noresult": "दिलेल्या काळाचे बदल ह्या निकशाक जुळनांत.",
"recentchanges-feed-description": "ह्या विकीचेर हालींच जाल्ल्या बदलांचो माग ह्या भरणांतल्यान दवरात.",
"recentchanges-label-newpage": "ह्या संपादनांन नवें पान निर्माण केला.",
"recentchanges-label-minor": "हें दाक्टे संपादन",
"recentchanges-label-plusminus": "ह्या पानाचो आकार इतल्या बाइट्सन बदललो",
"recentchanges-legend-heading": "<strong>कुंजी:</strong>",
"recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} ([[Special:NewPages| नव्या पानांची सुची]] पळयात)",
- "rcnotefrom": "$2 पासून केल्ले बदल सकयल दिल्यात ($1 मेरेन दाखयल्यात)",
+ "rcnotefrom": "सकयल <strong>$3, $4</strong> सावन {{PLURAL:$5 जालेले बदल दिल्यात}} (<strong>$1</strong> मेरेन {{PLURAL:$5|दाखयलां|दाखयल्यांत}}).",
"rclistfrom": "$3 $2 साकून नवें बदल दाखयात",
"rcshowhideminor": "$1 दाकट्यो बदल",
"rcshowhideminor-show": "दाखयात",
"rcshowhidebots-show": "दाखयात",
"rcshowhidebots-hide": "लिपयात",
"rcshowhideliu": "$1 अधिकृत नोंदीचे वापरपी",
+ "rcshowhideliu-show": "दाखयात",
"rcshowhideliu-hide": "लिपयात",
"rcshowhideanons": "$1 निनांवी वापरपी",
"rcshowhideanons-show": "दाखयात",
"rcshowhidemine": "$1 म्हजें संपादन आंकडे",
"rcshowhidemine-show": "दाखयात",
"rcshowhidemine-hide": "लिपयात",
- "rclinks": "फाà¤\9fलà¥\8dया $2 दिसाà¤\82नà¥\80 à¤\9cालà¥\8dलà¥\8b $1 बदल दाखयात",
+ "rclinks": "शà¥\87वà¤\9fà¤\9aà¥\87 $2 दिसानà¥\80à¤\82 à¤\9cालà¥\8dलà¥\87 $1 बदल दाखयात",
"diff": "फरक",
"hist": "इति",
"hide": "लिपयात",
"filehist-dimensions": "परिमाण",
"filehist-comment": "शेरो",
"imagelinks": "फायलिचो वापर",
- "linkstoimage": "हे फायलीक सकयल दिल्ल्यो पानाच्यो जोडण्यो {{PLURAL:$1|आसात}}.",
- "nolinkstoimage": "हà¥\87 फायलà¥\80à¤\95 दà¥\81वà¥\8b à¤\86शिलà¥\8dलà¥\80à¤\82 à¤\86नà¥\80à¤\95 पानाà¤\82 नात.",
+ "linkstoimage": "{{PLURAL:$1|हें पान|$1 हीं पानां}} ही फायल {{PLURAL:$1|वापरता|वापरतात}}:",
+ "nolinkstoimage": "हà¥\8dया फायलà¥\80à¤\95 वापरतात तसलà¥\80à¤\82 पानाà¤\82 नाà¤\82त.",
"sharedupload-desc-here": "ही फयल $1 हांगाची आनी ती हे प्रकल्पां खातीर वापरल्यार चलता. (तिच्या $2 ह्या फयलींतलें वर्णनाचे पान) तातूंतलें वर्णन सकयल दिलां.",
"upload-disallowed-here": "तूं ह्या फायलीचेर अधिलेखीत करूंक शकना",
"randompage": "खंयचेंय पान",
"contributions-title": "$1 खातीर वापरप्याचीं योगदानां",
"mycontris": "योगदान",
"anoncontribs": "योगदान",
+ "contribsub2": "{{GENDER:$3|$1}} हाच्यो ($2)",
"uctop": "हालीचें",
"month": "ह्या म्हयन्या सावन (आनी आदलें):",
"year": "ह्या वर्सा सावन (आनी आदलें):",
"sp-contributions-search": "योगदानां सोदात",
"sp-contributions-username": "आयपी नामो वा वापरप्याचें नांव",
"sp-contributions-toponly": "फकत सगळ्यांत हालींचे पुनर्नियाळ आशिल्लीं संपादन दाखयात",
+ "sp-contributions-newonly": "फकत तसलेचच बदल दाखय, जांचे वर्वीं पान रचलां",
"sp-contributions-submit": "सोद",
"whatlinkshere": "हाका कितें जडता",
"whatlinkshere-title": " \"$1\" हाका दुवे आशिल्लीं पानां",
"linkshere": "मुखावेली पानां <strong>$2</strong>: हाका जडतात",
"nolinkshere": "<strong>$2</strong> हाका खंयच्याच पानाचो दुवो ना",
"isredirect": "पुनर्निर्देशन पान",
- "istemplate": "$1 दूसरात-समावेस",
+ "istemplate": "दुरास्थ-समावेस",
"isimage": "फायलीचो दुवो",
"whatlinkshere-prev": "{{PLURAL:$1|आदलें|आदलीं $1}}",
"whatlinkshere-next": "{{PLURAL:$1|फुडलें|फुडलें $1}}",
"whatlinkshere-links": "← दुवे",
"whatlinkshere-hideredirs": "$1 पुनर्निर्देशन",
- "whatlinkshere-hidetrans": "$1 दà¥\82सà¥\8dरात-समावà¥\87श",
+ "whatlinkshere-hidetrans": "$1 दà¥\81रासà¥\8dथ-समावà¥\87स",
"whatlinkshere-hidelinks": "$1 दुवे",
"whatlinkshere-hideimages": "$1 फायल दुवे",
"whatlinkshere-filters": "गाळणे",
- "ipboptions": "2 वरां: 2hours ,1 दीस:1 day,3 दीस:3 days,1 सुमान:1 week,2 सुमनां:2 weeks,1 म्हयनो:1 month,3 म्हयने:3 months,6 म्हयने:6 months,1 वर्स:1 year,अनिश्चीत:infinte",
+ "ipboptions": "2 वरां:2 hours,1 दीस:1 day,3 दीस:3 days,1 सुमान:1 week,2 सुमनां:2 weeks,1 म्हयनो:1 month,3 म्हयने:3 months,6 म्हयने:6 months,1 वर्स:1 year,शेवट ना:infinite",
"ipblocklist": "आडायल्लें वापरपी",
"blocklink": "आडावणी",
"change-blocklink": "विभाग सुदारप",
"allmessagesdefault": "पूर्वनिर्धारित संदेशाचो मजकूर",
"thumbnail-more": "व्हड करात",
"thumbnail_error": "$1ः लघुप्रतिमा करतांनाची चूक",
- "tooltip-pt-userpage": "तुमचें वापरपाचें पान",
- "tooltip-pt-mytalk": "तुमचें चर्चेचें पान",
+ "tooltip-pt-userpage": "{{GENDER:|तुमचें वापरप्याचें}} पान",
+ "tooltip-pt-mytalk": "{{GENDER:|तुमचें}} भासाभासाचें पान",
"tooltip-pt-preferences": "{{GENDER:|तुमची}} पसंती",
"tooltip-pt-watchlist": "तुमी बदल करपा खातीर देखरेख करतात त्या पानांची वळेरी",
- "tooltip-pt-mycontris": "तुमच्या योगदानांची वळेरी",
+ "tooltip-pt-mycontris": "{{GENDER:|तुमच्या}} योगदानांची वळेरी",
"tooltip-pt-login": "सत्रारंभ करप बरें, पूण तशी सक्ती ना.",
"tooltip-pt-logout": "सत्र शेवट",
"tooltip-pt-createaccount": "तुमी खातें उगडून सत्रारंभ करचें अशें सुचयतात, पूण तें सक्तीचें ना.",
"tooltip-t-whatlinkshere": "हांगा दुवे आशिल्ल्या सगळ्या विकी पानांची वळेरी",
"tooltip-t-recentchangeslinked": "ह्या पानावेल्यान दुवे दिल्ल्या पानांतले हालींचे बदल",
"tooltip-feed-atom": "ह्या पाना खातीर ऍटम पूर्वण",
- "tooltip-t-contributions": "ह्या वापरप्याची योगदानाची वळेरी",
+ "tooltip-t-contributions": "{{GENDER:$1|ह्या वापरप्याची}} योगदानाची वळेरी",
"tooltip-t-emailuser": "{{GENDER:$1|ह्या उपेगकर्त्याक}} इ-मेल धाडात",
"tooltip-t-upload": "फायली अपलोड करात",
"tooltip-t-specialpages": "सगळ्या विशेश पानांची वळेरी",
"tooltip-rollback": "निमाण्या योगदान करप्यान ह्या पानाचेर केल्लें संपादन रोलबॅक (फाटीं घेयात) एकाच क्लीकान मूळ पदार हाडटा",
"tooltip-undo": "\"आदलें स्थितीर हाडचें\" ह्या बदलाक परत व्हरुन संपादन स्थितीन झलक रितीन दाखयतात.\nहाचेवरवीं सारांशान आदल्या स्थितीर हाडपाचें कारण बरोवं शकता.",
"tooltip-summary": "आपरोसाची नोंदणी करात",
- "simpleantispam-label": "à¤\8fनà¥\8dà¤\9fà¥\80-सà¥\8dपà¥\88म तपासप.\nहà¥\87 à¤à¤°à¥\80<strong>नà¤\95ाय</strong>!",
+ "simpleantispam-label": "सà¥\8dपमविरà¥\82ध तपासणà¥\80.\nहà¥\87à¤\82 à¤à¤° <strong>नाà¤\95ा</strong>!",
"pageinfo-toolboxlink": "पानाची म्हायती",
"pageinfo-contentpage-yes": "हय",
"previousdiff": "← आदलें संपादन",
"tog-enotifwatchlistpages": "Mhojea sadurvollerintlem pan vo fayl bodol'li zalear mhaka email dhadd",
"tog-shownumberswatching": "Nodor dovorpi vaporpeanche sonkhya dakhoi",
"tog-oldsig": "Tujea sod'dheachi soy:",
- "tog-uselivepreview": "Pan porot ugdinastana zolok dahkoi",
+ "tog-uselivepreview": "Pan porot ughoddnastana zholok dakhoi",
"tog-watchlisthideown": "Sadurvollerint mhoje bodol lipoi",
"tog-watchlisthidebots": "Sadurvollerint robotani kel'le bodol lipoi",
"tog-watchlisthideminor": "Sadurvollerint dhaktem bodol lipoi",
"listingcontinuesabbrev": "chalu",
"index-category": "Suchi-potran zodlelim panam",
"noindex-category": "Suchi-potran zoddunk-naslelim panam",
- "broken-file-category": "Tuttlolea faylinchea duve aslelim panam",
+ "broken-file-category": "Tuttlolea faylinche duve aslelim panam",
"about": "Hea vixoiavoir",
"article": "Vixoi sombondhi pan",
"newwindow": "(novea zonelant uktem zata)",
"youhavenewmessages": "Tumkam $1 ($2) asat.",
"youhavenewmessagesfromusers": "Tuka {{PLURAL:$3|ek vaporpi|$3 vaporpi}} koddlean $1 {{PLURAL:$4|asa|asat}} ($2).",
"newmessageslinkplural": "{{PLURAL:$1|novo sondex|999=nove sondex}}",
- "newmessagesdifflinkplural": "{{PLURAL:$1|nimanno bodol|999=nimanneo bodol}}",
+ "newmessagesdifflinkplural": "{{PLURAL:$1|nimanno bodol|999=nimanne bodol}}",
"youhavenewmessagesmulti": "$1 cher tuka noveo sondex asat",
"editsection": "bodol",
"editold": "bodol",
"databaseerror-textcl": "Totv-kox (database) sodtana chuk ghodli",
"databaseerror-query": "Anurodh: $1",
"databaseerror-error": "Chuk: $1",
- "missing-article": "Totv-kox (Database) hantun mellunk zai aslem tem mozkur \"$1\" $2 mellunk-nam.\n\nHorxim, oxem ek pornem frk vo eka panachea itihasacho duvo kadun udoila, tedna zata.\n\nOxem nhoi zalear, tuka softwer-an chuk sampodlam zait.\nUpkar korun eka [[Special:ListUsers/sysop|karbhari]]chea nodrek hadd, Internet Zago Sodpi (URL) hachi nond gheun.",
+ "missing-article": "Totv-kox (Database) hantun mellunk zai aslem tem mozkur \"$1\" $2 mellunk-nam.\n\nHorxim, oxem ek pornem frk vo eka panachea itihasacho duvo kaddun uddoila, ten’na zata.\n\nOxem nhoi zalear, tuka software-ant chuk sampoddlea zait.\nUpkar korun eka [[Special:ListUsers/sysop|karbhari]]chea nodrek hadd, Ontorzalleant Zago Sodpi (URL) hachi nond ghevn.",
"missingarticle-rev": "(uzollnni#: $1)",
"missingarticle-diff": "(Frk: $1, $2)",
"badtitle": "Chukichem nanv",
"loginerror": "Sotrorombhachi truti",
"createacct-error": "Khatem rochtanam truti",
"createaccounterror": "Khatem rochunk zaunk na: $1",
- "loginsuccesstitle": "Sotrorombh zalem",
+ "loginsuccesstitle": "Sotrarombh zalem",
"nosuchusershort": "\"$1\" hea nanvan konn vapurpi na.\nNanv boroitana chuk zali gai?",
"nouserspecified": "Vapurpeachem nanv diunk-uch zai.",
"login-userblocked": "Hea vapurpeak addaila. Sotrorombh korunk zaina.",
"emaildisabled": "Hi site mail dhadpak xokona.",
"accountcreated": "Khatem rochlem.",
"createaccount-title": "{{SITENAME}} -ak khatem rochlem",
- "login-abort-generic": "Tujem sotrorombh opexi tharlam - Nixfolit",
+ "login-abort-generic": "Tujem sotrorombh opexi tharlam - Nixfollit",
"login-migrated-generic": "Tujem khatem stholontrit zalam ani vapurpeachem nanv hea wikicher anink ostitvant na.",
"loginlanguagelabel": "Bhas: $1",
"pt-login": "Sotrorombh",
"passwordreset-domain": "Domain:",
"passwordreset-email": "Email potto:",
"passwordreset-emailelement": "Vapurpeachem nanv: \n$1\n\nTatpurtem gupitutor: \n$2",
- "passwordreset-emailsentemail": "Ho email pot'to tujea kontak zodlelem asa zalear, gupitutor portun tharaipacho email dhadlelem zatelem.",
- "changeemail": "Email potto bodol vo kad",
+ "passwordreset-emailsentemail": "Ho email pot'to tujea hixobant zoddlolo asa zalear, gupitutor portun tharavpacho email dhaddlelem zatelem.",
+ "changeemail": "Email po’tto bodol vo kadd",
"changeemail-oldemail": "Sodhyacho email potto:",
"changeemail-newemail": "Novo email potto:",
"changeemail-none": "(kai na)",
"anoneditwarning": "<strong>Chotrai:</strong> Tuven sotrorombh korunk nai. Tu bodol korit zalear tuzo IP pot'to soglleank polleunk zatelem. Tu <strong>[$1 sotrorombh korit]</strong> vo <strong>[$2 kont rochit]</strong> zalear, tuje bodol tuzo vaporpeachem nanvak zoddteleo ani anik-ui faide asat.",
"missingcommenttext": "Upkar korun tuzo xero boroi.",
"blockedtitle": "Vapurpeak addaila",
- "blockedtext": "<strong>Tujem vaporpeachem nanv vo IP pot'to addavpant aila.</strong>\n\nAddavop $1 hannem kelam.\nKaronn dilam tem <em>$2</em>.\n\n* Addavpachi survat: $8\n* Addavpachea somp’pacho vell: $6\n* Addavpak ievjila: $7\n\nTujean $1-ak vo dusrea [[{{MediaWiki:Grouppage-sysop}}|karbhariak]] addavnne bodol bhasabhas korunk sompork korunk zata. Tujean \"{{int:emailuser}}\" sobhavgunn vaprunk zaina kheriz ek void email pot'to tujea [[Special:Preferences|khatem posontint]] nischit kelea xivai ani tuka tem vaporpak addavnk na zalear. Tuzo chalont IP pot'to asa $3, ani addavnnecheo ank #$5 asa. Soglleo voileo bariksanno tum kortai tea vicharant somavex kor.",
+ "blockedtext": "<strong>Tujem vaporpeachem nanv vo IP pot'to addavpant aila.</strong>\n\nAddavop $1 hannem kelam.\nKaronn dilam tem <em>$2</em>.\n\n* Addavpachi survat: $8\n* Addavop sompovpacho vell: $6\n* Addavpak ievjila: $7\n\nTujean $1-ak vo dusrea [[{{MediaWiki:Grouppage-sysop}}|karbhariak]] addavnne bodol bhasabhas korunk sompork korunk zata. Tujean \"{{int:emailuser}}\" sobhavgunn vaprunk zaina kheriz ek void email pot'to tujea [[Special:Preferences|khatem posontint]] nischit kelea xivai ani tuka tem vaporpak addavnk na zalear. Tuzo chalont IP pot'to asa $3, ani addavnnecheo ank #$5 asa. Soglleo voileo bariksanno tum kortai tea vicharant somavex kor.",
"blockednoreason": "Kainch karonn diunk na",
"loginreqtitle": "Sotrorombh gorjechem",
"loginreqlink": "sotrorombh kor",
"permissionserrors": "Porvangechi chuk",
"permissionserrorstext-withaction": "$2, hem korpak tuka porvangi na, {{PLURAL:$1|hea karnnak lagon|hea karnnank lagun}}:",
"recreate-moveddeleted-warn": "<strong>Xittkavnni: Tum ek pan porot rochtai jem fattim kadun udoilelem.<strong>\n\nPanacho sompadon korop sarkem zalear dhean di.\nPan kadoupachem ani halovpachem sotr, sovloti khatir hangasor dilelem asa:",
- "moveddeleted-notice": "Hem pan kadun udoilelem asa.\nPanachea kadun udounechi, rakhpachi, ani hallovnechi sotr sondorba khatir sokoil dilea.",
+ "moveddeleted-notice": "Hem pan kaddun udoilelem asa.\nPanachem kaddun uddovpachem, rakhpachem, ani halovpachem sotr sondhorba khatir sokoil dila.",
"content-model-wikitext": "wikimozkur",
"content-model-text": "Sado mozkur",
"post-expand-template-inclusion-warning": "<strong>Chotrai:</strong> Sancho zoddpacho akar chod vhodlem asa.\nThodde sache zoddchenant.",
"page_last": "akhirchem",
"histlegend": "Frk nivoddni: Jeo uzollneo tuka comparar korunk zai, tenche fudle ''radio'' butao petoi ani ''Enter'' nazalear khalcho butao dab.<br />\nVivron: <strong>({{int:cur}})</strong> = halinchi uzollnie borobor forok, <strong>({{int:last}})</strong> = adli uzollnie borobor forok, <strong>{{int:minoreditletter}}</strong> = dhaktem bodol.",
"history-fieldset-title": "Uzollnneo chall",
- "history-show-deleted": "Fokot uzollnni kadun udoilelem",
+ "history-show-deleted": "Fokot uzollnni kadun uddoilolem",
"histfirst": "sogleavon adhlem",
"histlast": "sogleavon novem",
"history-feed-title": "Uzollnniancho itihas",
"revdel-restore": "Disnnem bodol",
"pagehist": "Panacho itihas",
"mergehistory-reason": "Karonn:",
- "mergelog": "Vilin korpacho sotr",
+ "mergelog": "Vilin korpachem sotr",
"revertmerge": "Doxim kor",
"history-title": "\"$1\" hachea uzollnnecho itihas",
"difference-title": "\"$1\"-chea avrutint ontor",
"prefs-help-email": "Email potto sokticho na, pun tum gupitutor visroxi zalear gupitutor punorsthapon korunk email pottechi goroz podta.",
"prefs-help-email-others": "Tujean dusreank tujea vapurpeacho panar vo bhasabhasache panar aslele eke email duve vorvim tuje xim sompork korunk diunk zata.\nDusre tuje xim sompork kortat tednam tuzo email potto tankam kollchenam.",
"userrights-user-editname": "Ek vapurpeachem nanv ghal:",
- "group-bot": "Robotam",
+ "group-bot": "Robottam",
"group-sysop": "Karbhari",
"group-all": "(soglle)",
"grouppage-bot": "{{ns:project}}:Robotam",
"right-move": "Panam haloi",
"right-writeapi": "Borovpeache API-cho upeog",
"newuserlogpage": "Vapurpi rochnnechem sotr",
- "rightslog": "Vaporpeachea hokancho sotr",
+ "rightslog": "Vaporpeachea hokanchem sotr",
"action-edit": "hem pan sudar",
"action-createaccount": "hem vaporpeachem khatem roch",
"nchanges": "$1 {{PLURAL:$1|bodol}}",
"rc-change-size-new": "$1 {{PLURAL:$1|byte|byti}} bodol kel'lea uprant",
"rc-enhanced-expand": "Bariksann dakhoi",
"rc-enhanced-hide": "Bariksann lipoi",
- "rc-old-title": "orombhant rochloli \"$1\" hea nanvan.",
+ "rc-old-title": "arombhant rochloli \"$1\" hea nanvan",
"recentchangeslinked": "Sombondit bodol",
"recentchangeslinked-feed": "Sombondit bodol",
"recentchangeslinked-toolbox": "Sombondit bodol",
"longpages": "Lamb panam",
"protectedpages-filters": "Challnneo:",
"listusers": "Vaporpeanchi volleri",
- "usercreated": "$3 hannem $1 disa $2 vaztam rochlelem",
+ "usercreated": "$3 hannem $1 disa $2 vorancher rochlolem",
"newpages": "Novim panam",
"move": "Zago bodol",
"pager-newer-n": "{{PLURAL:$1|novem 1|novim $1}}",
"specialloguserlabel": "Korpi:",
"speciallogtitlelabel": "Mokh (mathallo vo {{ns:user}}:vapurpeachem nanv):",
"log": "Sotram",
- "all-logs-page": "Soglle bhousache sotram",
- "alllogstext": "{{SITENAME}} hacheo sogllea uplobdh sotranchi ektthaim dakhovnni.\nTujean tuzo dekhavo ornum ieta ek sotracho prokar vinchun, vaporpeachem nanv (vhodle and dhakte okxora modem forok podta), vo porinnam zalolem pan (hanga-ui vhodle and dhakte okxora modem forok podta).",
+ "all-logs-page": "Sogllim bhousachim sotram",
+ "alllogstext": "{{SITENAME}} hacheo sogllea uplobdh sotranchi ektthaim dakhovnni.\nTujean tuzo dekhavo ornum ieta ek sotracho prokar vinchun, vaporpeachem nanv (vhoddlea ani dhakttea okxora modem forok poddtta), vo porinnam zalolem pan (hangai vhoddle and dhaktte okxora modem forok poddtta).",
"logempty": "Sotran zullpi nog nant.",
"allpages": "Sogllim panam",
"nextpage": "Fuddlem pan ($1)",
"allpagesfrom": "Hanga thavn suru zatelea panank dakhoi:",
"allarticles": "Sogllim panam",
"allpagessubmit": "Voch",
- "allpages-hide-redirects": "Punornirdexonam lipoi",
+ "allpages-hide-redirects": "Punornirdexona lipoi",
"categories": "Vorg",
"sp-deletedcontributions-contribs": "iogdan",
"linksearch-ns": "Nanv-tholl:",
"watch": "Nodor dovor",
"watchthispage": "Hea panar dixtt dovor",
"unwatch": "Nodor kadd",
- "watchlist-details": "Tujea Sadurvollerint {{PLURAL:$1|$1 pan asa|$1 panam asat}} (te-bhair ulovpachim panam asat).",
+ "watchlist-details": "Tujea Sadurvollerint {{PLURAL:$1|$1 pan asa|$1 panam asat}} (tea-bhair ulovpachim panam asat).",
"wlheader-showupdated": "Tujea fatle bhette san bodol'lean tim panam '''datt''' dakhoileant.",
- "wlnote": "Sokoil {{PLURAL:$1|ho nimanno bodol|hem nimanneo <strong>$1</strong> bodol}} nimannea {{PLURAL:$2|horan|<strong>$2</strong> horanim}}, $3, $4 porian.",
+ "wlnote": "Sokoil {{PLURAL:$1|ho nimanno bodol|hem nimanneo <strong>$1</strong> bodol}} nimannea {{PLURAL:$2|voran|<strong>$2</strong> voramni}}, $3, $4 porian.",
"watchlist-options": "Sadurvollericheo poryay",
"watching": "Disht dovortanv...",
"unwatching": "Disht kaddthanv...",
"linkshere": "Sokoilim panam <strong>$2</strong> ak zoddtat:",
"nolinkshere": "Khoincheim pan <strong>$2</strong> ak zoddna.",
"isredirect": "punornirdexon pan",
- "istemplate": "Durasth-somaves",
+ "istemplate": "durasth-somaves",
"isimage": "faylicho duvo",
"whatlinkshere-prev": "{{PLURAL:$1|adlem|adlem $1}}",
"whatlinkshere-next": "{{PLURAL:$1|fuddlem|fuddlim $1}}",
"whatlinkshere-links": "← duve",
- "whatlinkshere-hideredirs": "$1 punornirdexonam",
- "whatlinkshere-hidetrans": "$1 durasth-somaveso",
+ "whatlinkshere-hideredirs": "$1 punornirdexona",
+ "whatlinkshere-hidetrans": "$1 durasth-somavex",
"whatlinkshere-hidelinks": "$1 duve",
"whatlinkshere-hideimages": "$1 faylinche duve",
"whatlinkshere-filters": "Challnio",
"contribslink": "yogdan",
"blocklogpage": "addavnnechem sotr",
"blocklogentry": "[[$1]] addailelem $2 asun vellacho ont: $3",
- "reblock-logentry": "addavpachem bosovp bodol’lam [[$1]] hache khatir sompacho vell dilam $2 $3",
+ "reblock-logentry": "addavpachem bosovp bodol’lam [[$1]] hache khatir sompovpacho vell dila $2 $3",
"block-log-flags-nocreate": "Khatem rochop opatr kelam",
"proxyblocker": "Protinidhi-sirvidor addavpi",
"move-page": "$1 haloi",
"allmessagesdefault": "Default sondex mozkur",
"thumbnail-more": "Vhodlem kor",
"thumbnail_error": "Lhan-imaz toiar kortana chuk zali. Karonn: $1",
- "importlogpage": "Aiatacho sotr",
+ "importlogpage": "Aiatachem sotr",
"tooltip-pt-userpage": "{{GENDER:|Tujem vaporpeachem}} pan",
"tooltip-pt-mytalk": "{{GENDER:|Tumchem}} bhasabhasachem pan",
"tooltip-pt-preferences": "{{GENDER:|Tumcheo}} avddi",
"tooltip-ca-nstab-special": "Hem ek kherit pan, ani hem bodlunk zaina",
"tooltip-ca-nstab-project": "Prokolpachem pan polloi",
"tooltip-ca-nstab-image": "Faylichem pan polloi",
- "tooltip-ca-nstab-mediawiki": "Iontronacho sondex polloi",
+ "tooltip-ca-nstab-mediawiki": "Iontronnacho sondex polloi",
"tooltip-ca-nstab-template": "Sancho polloi",
"tooltip-ca-nstab-category": "Vorgachem pan polloi",
"tooltip-minoredit": "Haka ek kirkoll sudharop mhunn khunnay",
"tooltip-rollback": "\"Kovllop\" hea panak nimannea yogdan korpean kello (kelle) bodol eka kollant portota.",
"tooltip-undo": "\"Rodd' kor\" sudharop portita ani sudharopak Zholok ritin ukodta. Tem saran karon zoddunk dita.",
"tooltip-summary": "Mottvo sar ghal",
- "simpleantispam-label": "Spam-virudh topasni.\nHem bhori <strong>nakai</strong>!",
+ "simpleantispam-label": "Spam-virudh topasnni.\nHem bhor <strong>nakai</strong>!",
"pageinfo-title": "\"$1\" khatir mhaiti",
"pageinfo-header-basic": "Mull mhaiti",
"pageinfo-header-edits": "Bodolacho itihas",
"pageinfo-header-properties": "Panache gunndhorm",
"pageinfo-display-title": "Manddlolem mathallem",
"pageinfo-default-sort": "Default arin manddunk chavi",
- "pageinfo-length": "Panachi lambai (bayt-ant)",
+ "pageinfo-length": "Panachi lambai (baytt-ant)",
"pageinfo-article-id": "Panacho ank",
"pageinfo-language": "Panachea mozkurachi bhas",
"pageinfo-content-model": "Panachea mozkuracho nomuno",
- "pageinfo-robot-policy": "Robotam koddlean suchien ghalop",
+ "pageinfo-robot-policy": "Robotam koddlean suchent ghalop",
"pageinfo-robot-index": "Porvangi asa",
"pageinfo-robot-noindex": "Porvangi nam",
"pageinfo-watchers": "Panacher dixtt dovortoleancho ankddo",
"pageinfo-toolboxlink": "Panachi mahiti",
"pageinfo-contentpage": "Ek mozkurachem pan koxem dhorpant ailam",
"pageinfo-contentpage-yes": "Hoi",
- "patrol-log-page": "Paro korpeacho sotr",
+ "patrol-log-page": "Paro korpachem sotr",
"previousdiff": "← Adlo bodol",
"nextdiff": "Fuddlem bodol →",
"widthheightpage": "$1 × $2, $3 {{PLURAL:$3|pan|panam}}",
"redirect": "Fayl, vaporpi, pan, uzollnni vo sotr ank vorvim punornirdexon kor",
"redirect-summary": "Hem vixex pan punornirdexit korta eka faylik (faylichem nanv dilear), eke panak (uziollnecho ank vo panacho ank dilear), ek vaporpeachem panak (eke vaporpeache ank dilear), vo ek sotr nond (sotrachem ank dilear). Vapor: [[{{#Special:Redirect}}/file/Dekhik.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]], vo [[{{#Special:Redirect}}/logid/186]].",
"redirect-submit": "Voch",
- "redirect-lookup": "Suchien polloi:",
+ "redirect-lookup": "Suchint polloi:",
"redirect-value": "Mol:",
- "redirect-user": "Vaporpeacho ank",
+ "redirect-user": "Vaporpeancho ank",
"redirect-page": "Panacho ank",
"redirect-revision": "Panachi uzollnni",
"redirect-file": "Faylichem nanv",
"htmlform-title-not-exists": "$1 ostitvant na.",
"logentry-delete-delete": "$1, hannem {{GENDER:$2|kadun udoile}} pan $3",
"logentry-delete-restore": "$1 hannem {{GENDER:$2|porot haddlam}} pan $3 ($4)",
- "logentry-delete-revision": "$1 hannem {{PLURAL:$5|uzolliechem}} disnem $3, hea panar {{GENDER:$2|bodol’la}}: $4",
+ "logentry-delete-revision": "$1 hannem {{PLURAL:$5|uzollnnechem}} disnnem $3, hea panar {{GENDER:$2|bodol’la}}: $4",
"revdelete-content-hid": "mozkur lipoila",
"logentry-move-move": "$1, hannem $3 panak $4 {{GENDER:$2|haloilea}}",
"logentry-move-move-noredirect": "$1, hannem pan $3 savn $4 {{GENDER:$2|haloilam}} punornirdexon dorinastanam",
"logentry-move-move_redir": "$1 hannem pan $3 savn $4 {{GENDER:$2|haloilolo}} punornirdexonavoir",
- "logentry-patrol-patrol-auto": "$1-an $3, hea panachem $4, hea uzollniecho paro kelam mhonn apoap {{GENDER:$2|khunnailam}}.",
+ "logentry-patrol-patrol-auto": "$1-an $3, hea panachem $4, hea uzollnnecho paro kelam mhonn apoap {{GENDER:$2|khunnailam}}.",
"logentry-newusers-create": "Vapurpeacho kont $1 {{GENDER:$2|rochlam}}",
"logentry-newusers-autocreate": "Vaporpeachem khatem $1 apoap {{GENDER:$2|rochun}} ailem",
"logentry-upload-upload": "$1-an $3 {{GENDER:$2|upload kela}}",
"diff-multi-manyusers": "({{PLURAL:$1|Nije prikazana jedna međuinačica|Nisu prikazane $1 međuinačice|Nije prikazano $1 međuinačica}} više od {{PLURAL:$2|jednog|$2|$2}} suradnika)",
"difference-missing-revision": "{{PLURAL:$2|Uređivanje|$2 uređivanja}} sljedeće šifre ($1) ne {{PLURAL:$2|postoji|postoje}}.\n\nOvo je obično uzrokovano kada kliknete na zastarjelu poveznicu na stranice koja je obrisana.\nViše informacija možete pronaći u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} evidenciji brisanja].",
"searchresults": "Rezultati pretrage",
+ "search-filter-title-prefix": "Pretraži samo stranice koje počinju prefiksom »$1«",
"searchresults-title": "Rezultati pretrage za \"$1\"",
"titlematches": "Pronađene stranice prema naslovu",
"textmatches": "Pronađene stranice prema tekstu članka",
"sessionfailure": "ログインのセッションに問題が発生しました。\nセッション乗っ取りを防ぐため、操作を取り消しました。\nフォームを再送信してください。",
"changecontentmodel": "ページのコンテンツ・モデルの変更",
"changecontentmodel-legend": "コンテンツモデルを変更",
- "changecontentmodel-title-label": "ページ名",
+ "changecontentmodel-title-label": "ページ名:",
"changecontentmodel-current-label": "現在のコンテンツモデル",
- "changecontentmodel-model-label": "新しい コンテンツ モデル",
+ "changecontentmodel-model-label": "新しい コンテンツ モデル:",
"changecontentmodel-reason-label": "理由:",
"changecontentmodel-submit": "変更",
"changecontentmodel-success-title": "コンテンツ・モデルは変更されました",
"undo-norev": "Измената не можеше да биде вратена бидејќи не постои или била избришана.",
"undo-nochange": "Се чини дека измената (уредувањето) е веќе вратена.",
"undo-summary": "Откажано уредувањето $1 на уредникот [[Special:Contribs/$2|$2]] ([[User talk:$2|разговор]])",
+ "undo-summary-anon": "Отповикај ја преработката $1 на [[Special:Contributions/$2|$2]]",
"undo-summary-username-hidden": "Поништи ја преработката $1 на скриен корисник",
"cantcreateaccount-text": "Создавањето на корисничка сметка од оваа IP-адреса (<strong>$1</strong>) е блокирано од страна на [[User:$3|$3]].\n\nОбразложението дадено од страна на $3 е <em>$2</em>",
"cantcreateaccount-range-text": "Создавањето на сметки од IP-адреси во опсегот <strong>$1</strong> каде спаѓа вашата IP-адреса (<strong>$4</strong>) е блокирано од корисникот [[User:$3|$3]].\n\n$3 ја наведе следнава причина: <em>$2</em>",
"cantrollback": "Уредувањето не може да се отповика.\nПоследниот уредник е воедно и единствениот автор на страницата.",
"alreadyrolled": "Не може да се отповика последното уредување на страницата „[[:$1]]“ извршено од [[User:$2|$2]] ([[User talk:$2|разговор]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);\nнекој друг веќе ја изменил или отповикал страницата.\n\nПоследното уредување го изврши [[User:$3|$3]] ([[User talk:$3|разговор]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
"editcomment": "Коментарот на уредувањето беше: <em>$1</em>.",
- "revertpage": "Отстрането уредувањето на [[Special:Contributions/$2|$2]] ([[User talk:$2|разговор]]), вратено на последната верзија на [[User:$1|$1]]",
+ "revertpage": "Отповикани уредувањата на [[Special:Contributions/$2|$2]] ([[User talk:$2|разговор]]), враќајќи на последната преработка на [[User:$1|$1]]",
+ "revertpage-anon": "Отповикани уредувања на [[Special:Contributions/$2|$2]], враќајќи на последната преработка на [[User:$1|$1]]",
"revertpage-nouser": "Вратени уредувања од скриен корисник на последната преработка на {{GENDER:$1|[[User:$1|$1]]}}",
"rollback-success": "Откажани уредувањата на {{GENDER:$3|$1}};\nвратено на последната верзија на {{GENDER:$4|$2}}.",
"sessionfailure-title": "Седницата не успеа",
"Railfail536",
"Vlad5250",
"CiaPan",
- "BadDog"
+ "BadDog",
+ "Rail"
]
},
"tog-underline": "Podkreślenie linków:",
"tog-useeditwarning": "Used as label for the checkbox in [[Special:Preferences#mw-prefsection-editing|Special:Preferences]].",
"tog-prefershttps": "Toggle option used in [[Special:Preferences]] that indicates if the user wants to use a secure connection when logged in",
"tog-showrollbackconfirmation": "Toggle option used in [[Special:Preferences]] to enable/disable rollback confirmation prompt. Should be visible only to users with rollback rights.",
+ "tog-requireemail": "Toggle option used in [[Special:Preferences]]. Should be only visible to users who have confirmed their email address.\n\nSee also: {{msg-mw|prefs-help-requireemail}}",
"underline-always": "Used in [[Special:Preferences#mw-prefsection-rendering|Preferences]].\n\nThis option means \"always underline links\", there are also options {{msg-mw|Underline-never}} and {{msg-mw|Underline-default}}.\n\n{{Gender}}\n{{Identical|Always}}",
"underline-never": "Used in [[Special:Preferences#mw-prefsection-rendering|Preferences]].\n\nThis option means \"never underline links\", there are also options {{msg-mw|Underline-always}} and {{msg-mw|Underline-default}}.\n\n{{Gender}}\n{{Identical|Never}}",
"underline-default": "Used in [[Special:Preferences#mw-prefsection-rendering|Preferences]].\n\nThis option means \"underline links as in your user skin or your browser\", there are also options {{msg-mw|Underline-never}} and {{msg-mw|Underline-always}}.\n\n{{Gender}}\n{{Identical|Browser default}}",
"prefs-help-email": "Shown as explanation text on [[Special:Preferences]] > {{int:prefs-personal}} > {{int:email}}.\n\nSee also:\n* {{msg-mw|prefs-help-email-required|help}}\n* {{msg-mw|prefs-help-email-others|help}}\n* {{msg-mw|prefs-changeemail|link title}}\n* {{msg-mw|prefs-setemail|link title}}",
"prefs-help-email-others": "This text is shown on account creation, below the description of the e-mail address field (which is optional).\n\nSee also:\n* {{msg-mw|prefs-help-email-required|help}}\n* {{msg-mw|prefs-help-email|help}}\n* {{msg-mw|prefs-changeemail|link title}}\n* {{msg-mw|prefs-setemail|link title}}",
"prefs-help-email-required": "Shown as explanation text on [[Special:Preferences]] > {{int:prefs-personal}} > {{int:email}}.\n\nSee also:\n* {{msg-mw|prefs-help-email|help}}\n* {{msg-mw|prefs-help-email-others|help}}\n* {{msg-mw|prefs-changeemail|link title}}\n* {{msg-mw|prefs-setemail|link title}}",
+ "prefs-help-requireemail": "Shown as explanation text on [[Special:Preferences]] > {{int:prefs-personal}} > {{int:email}}.\n\nSee also: {{msg-mw|tog-requireemail}}",
"prefs-info": "Header for the box giving basic information on the user account, displayed on the 'user profile' tab of the [[Special:Preferences|user preferences]] special page.\n{{Identical|Basic information}}",
"prefs-i18n": "Field set legend for user preferences regarding the interface language",
"prefs-signature": "{{Identical|Signature}}",
"ipblocklist-legend": "Used as legend of the form in [[Special:BlockList]].\n\nSee also:\n* {{msg-mw|Ipblocklist-legend}}\n* {{msg-mw|Ipblocklist-submit}}",
"blocklist-userblocks": "Used as the label for the multi-select checkbox in the form on [[Special:BlockList]].\n{{Related|Blocklist-blocks}}",
"blocklist-tempblocks": "Used as the label for the multi-select checkbox in the form on [[Special:BlockList]].\n{{Related|Blocklist-blocks}}",
+ "blocklist-indefblocks": "Used as the label for the multi-select checkbox in the form on [[Special:BlockList]].\n{{Related|Blocklist-blocks}}",
"blocklist-addressblocks": "Used as the label for the multi-select checkbox in the form on [[Special:BlockList]].\n{{Related|Blocklist-blocks}}",
"blocklist-type": "Used as label for dropdown box in [[Special:BlockList]].",
"blocklist-type-opt-all": "Used as option for dropdown box in [[Special:BlockList]]. This is the default option and indicates that \"all\" blocks will be listed\n{{Identical|All}}",
"undo-norev": "Redigeringen kan inte göras ogjord eftersom den inte finns eller har raderats.",
"undo-nochange": "Det verkar som att redigeringen redan har blivit ogjord.",
"undo-summary": "Gör version $1 av [[Special:Contributions/$2|$2]] ([[User talk:$2|diskussion]]) ogjord",
+ "undo-summary-anon": "Ångra sidversionen $1 av [[Special:Contributions/$2|$2]]",
"undo-summary-username-hidden": "Gör version $1 av en dold användare ogjord",
"cantcreateaccount-text": "[[User:$3|$3]] har blockerat den här IP-adressen ('''$1''') från att registrera konton.\n\nAnledningen till blockeringen var \"$2\".",
"cantcreateaccount-range-text": "IP-adresserna i intervallet <strong>$1</strong>, som inkluderar din IP-adress (<strong>$4</strong>), har blockerats från att skapa konton av [[User:$3|$3]].\n\nAnledningen enligt $3 var <em>$2</em>",
"alreadyrolled": "Det gick inte att rulla tillbaka den senaste redigeringen av [[User:$2|$2]] ([[User talk:$2|diskussion]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]) på sidan [[:$1|$1]]. Någon annan har redan rullat tillbaka eller redigerat sidan.\n\nSidan ändrades senast av [[User:$3|$3]] ([[User talk:$3|diskussion]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]).",
"editcomment": "Redigeringskommentaren var: <em>$1</em>.",
"revertpage": "Återställde redigeringar av [[Special:Contributions/$2|$2]] ([[User talk:$2|användardiskussion]]) till senaste versionen av [[User:$1|$1]]",
+ "revertpage-anon": "Ångrade redigeringar av [[Special:Contributions/$2|$2]] till senaste sidversionen av [[User:$1|$1]]",
"revertpage-nouser": "Återställde redigeringar av en dold användare till den senaste versionen av {{GENDER:$1|[[User:$1|$1]]}}",
"rollback-success": "Återställde ändringar av {{GENDER:$3|$1}};\nändrade tillbaka till senaste versionen av {{GENDER:$4|$2}}.",
"sessionfailure-title": "Sessionsfel",
"morenotlisted": "Ńy je to kůmplytno lista",
"mypage": "Zajta",
"mytalk": "Dyskusyjŏ",
- "anontalk": "Godka tygo IP",
+ "anontalk": "Dyskusyjŏ",
"navigation": "Nawigacyjŏ",
"and": " i",
"faq": "FAQ",
"filereadonlyerror": "Ńy idźe pomjyńać plika \"$1\" abo repozytorjum \"$2\" terozki je zawarte.\n\nAdministrator kery zawarł wćepał kůmyntorz: \"$3\".",
"invalidtitle-knownnamespace": "Felerne mjano \"$3\" w przestrzeńy \"$2\".",
"invalidtitle-unknownnamespace": "Felerne mjano ze ńyznomům nůmerům raumu mjan $1: \"$2\"",
- "exception-nologin": "Ńy jest żeś zalogůwany",
+ "exception-nologin": "Niy je żeś wlogowany(ŏ)",
"exception-nologin-text": "Prosza [[Special:Userlogin|zaloguj śe]] coby mjeć mogebność przejśćo do tyj zajty abo akcyji.",
"virus-badscanner": "Felerno konfiguracyjo – ńyznany skaner antywirusowy ''$1''",
"virus-scanfailed": "skanowańy ńyudone (feler $1)",
"changepassword-success": "Twoje hasło zostoło půmyślńy půmjyńone!",
"botpasswords": "Hasła bota",
"resetpass_forbidden": "Ńy idźe sam půmjyńyć hasłůw.",
- "resetpass-no-info": "Muśisz być zalogowany, coby uzyskoć bezpostrzedńi dostymp do tyj zajty.",
+ "resetpass-no-info": "Żeby mieć bezpostrzedni dostymp do tyj strōny, trzeba sie zalogować.",
"resetpass-submit-loggedin": "Zmjyń hasło",
"resetpass-submit-cancel": "Uodćepej",
"resetpass-wrong-oldpass": "Felerne tymczasowe abo aktualne hasło.\nMożliwe co właśńy zmjyńiłżeś swoje hasło abo poprosiłżeś uo nowe tymczasowe hasło.",
"showpreview": "Pokŏż podglōnd",
"showdiff": "Pokŏż zmiany",
"anoneditwarning": "<strong>Pozōr:</strong> Niy je żeś wlogowany(ŏ). Jak zrobisz jakeś zmiany, to Twoja adresa IP bydzie publicznie widać. Jeźli <strong>[$1 sie wlogujesz]</strong> abo <strong>[$2 stworzisz kōnto]</strong>, to Twoje zmiany bydōm przipisane do kōnta społym ze inkszymi profitami.",
- "anonpreviewwarning": "Ńy jeżeś zalogowany. Twój IP ausdruk uostańy spamjyntany, eli ty bydźesz sprowjać zajte.",
+ "anonpreviewwarning": "<em>Niy je żeś wlogowany(ŏ). Spamiyntanie zmian zapisze twoja adresa IP we historyji edycyji tyj strōny.</em>",
"missingsummary": "'''Pozůr:''' Ńy wprowadźůł żeś uopisu pomjyńań. Kej go ńy chcesz wprowadzać, naćiś knefel Spamjyntej jeszcze roz.",
"missingcommenttext": "Wkludź kōmyntŏrz niżyj.",
"missingcommentheader": "'''Dej pozůr:''' Treść nagłůwka je blank - uzupełńij go! Jeli tego ńy zrobisz, Twůj kůmyntorz bydźe naszkryflony bez nagłůwka.",
"upload": "Zaladuj zbiōr",
"uploadbtn": "Prziślij zbiōr",
"reuploaddesc": "Nazod do formulařa uod wćepywańo.",
- "uploadnologin": "Ńy jest žeś zalogůwany",
+ "uploadnologin": "Niy je żeś wlogowany(ŏ)",
"uploadnologintext": "Muśyš śe [[Special:UserLogin|zalůgować]] ńim wćepńeš pliki.",
"upload_directory_missing": "Katalog lo wćepywanych plikůw ($1) ńy istńeje a serwer WWW ńy poradźi go utwořić.",
"upload_directory_read_only": "Serwer ńy može škryflać do katalůgu ($1) kery je přeznačůny na wćepywane pliki.",
"linksearch-text": "Idźe użyć symbola wjeloznacznygo „*”. Lů bajszpila „*.wikipedia.org” spowoduje sznupańy za wszyjstkimi linkůma kere prowadzům ku důmyńy „wikipedia.org” a jeij poddůmyn.<br />\nUobsůgiwane protokoły: $1",
"linksearch-line": "$1 link na zajće $2",
"linksearch-error": "Symbola wjeloznacznygo idźe użyć yno na anfangu mjana hosta.",
- "listusersfrom": "Pokaž užytkowńikůw začynojůnc uod:",
+ "listusersfrom": "Pokŏż używŏczōw ôd:",
"listusers-submit": "Uobejrzij",
"listusers-noresult": "Ńy znejdźůno žodnygo užytkowńika.",
"activeusers": "Lista aktywnych używŏczōw",
"watchlistfor2": "{{GENDER:$1|Używŏcza|Używŏczki}} $1 $2",
"nowatchlist": "Ńy ma žodnych pozycyji na liśće zajtůw, na kere dowoš pozůr.",
"watchlistanontext": "$1 coby uobejřeć abo sprowjać elymynty listy zajtůw, na kere dowoš pozůr",
- "watchnologin": "Ńy jest žeś zalůgowany",
+ "watchnologin": "Niy je żeś wlogowany(ŏ)",
"addedwatchtext": "Zajta \"[[:$1]]\" zostoua dodano do Twojij [[Special:Watchlist|listy artiklůw, na kere dowoš pozůr]].\nNa tyi liśće bydźeš mjou rejer přišuych sprowjyń tyi zajty i jeji zajty godki, a mjano zajty bydźeš mjou škryflane '''tustym''' na [[Special:RecentChanges|liśće půmjyńanych na ůostatku]], cobyś mjou wygoda w jei pomjyńańa filować.",
"removedwatchtext": "Artikel \"[[:$1]]\" zostou wyćepńjynty s [[Special:Watchlist|Twojij pozorlisty]].",
"watch": "Ôbserwuj",
"movepage-moved": "'''\"$1\" przećiśńjynto ku \"$2\"'''",
"articleexists": "Artikel ze takym mjanym już je, abo mjano je złe.\nWybjer inksze mjano.",
"cantmove-titleprotected": "Ńy możesz przećepnůńć zajty, beztuż co jeij nowe mjano je ńydozwolůne skuli zabezpjeczyńo przed utworzyńym",
- "movetalk": "Przećiś godke, jak możno.",
+ "movetalk": "Przeniyś zwiōnzanõ ze strōnōm dyskusyjõ",
"move-subpages": "Přećepńij podzajty",
"move-talk-subpages": "Jeli je to możliwe przekludź wszyjstke zajty godki podzajtůw",
"movepage-page-exists": "Zajta $1 już istńeje a ńy idźe jeij autůmatyczńy nadszkryflać.",
"grouppage-suppress": "{{ns:project}}:Назирләр",
"right-read": "Битләрне карау",
"right-edit": "Битләрне үзгәртү",
- "right-createpage": "биÑ\82лÓ\99Ñ\80 Ñ\8fÑ\81аÑ\83 (бÓ\99Ñ\85Ó\99Ñ\81 бÑ\83лмаганнаÑ\80Ñ\8bн)",
- "right-createtalk": "бÓ\99Ñ\85Ó\99Ñ\81 биÑ\82ен Ñ\8fÑ\81аÑ\83",
- "right-createaccount": "яңа кулланучы хисап язмасын ясау",
+ "right-createpage": "Ð\91иÑ\82лÓ\99Ñ\80не Ñ\82өзү (бÓ\99Ñ\85Ó\99Ñ\81 биÑ\82лÓ\99Ñ\80е бÑ\83лмаган)",
+ "right-createtalk": "Ð\91Ó\99Ñ\85Ó\99Ñ\81 биÑ\82лÓ\99Ñ\80ен Ñ\82өзү",
+ "right-createaccount": "Яңа кулланучы хисап язмасын төзү",
"right-minoredit": "\"кече төзәтмә\" тамгасын кую",
"right-move": "Битләрне күчерү",
"right-move-subpages": "Битләрне асбитләр белән бергә күчерү",
"editcomment": "編輯摘要為:<em>$1</em>。",
"revertpage": "已還原[[Special:Contributions/$2|$2]]([[User talk:$2|討論]])的編輯至最後由[[User:$1|$1]]所修訂的版本",
"revertpage-nouser": "已還原隱藏使用者的編輯為最後 {{GENDER:$1|[[User:$1|$1]]}} 修訂的版本",
- "rollback-success": "已還原 {{GENDER:$3|$1}} 所做的編輯;\n變更回由 {{GENDER:$4|$2}} 修訂的最後一個版本。",
+ "rollback-success": "已還原{{GENDER:$3|$1}}所做的編輯;變更回由{{GENDER:$4|$2}}修訂的最後一個版本。",
"sessionfailure-title": "連線階段失敗",
"sessionfailure": "您的登入連線階段似乎有問題,為了預防連線階段受到劫持攻擊,此動作已經被取消。請重新提交表單。",
"changecontentmodel": "變更頁面的內容模型",
* @defgroup Maintenance Maintenance
*/
-define( MW_ENTRY_POINT, 'cli' );
+define( 'MW_ENTRY_POINT', 'cli' );
// Bail on old versions of PHP, or if composer has not been run yet to install
// dependencies.
--- /dev/null
+--
+-- patch-drop-user-fields.sql
+--
+-- T188327. Drop old xx_user and xx_user_text fields, and defaults from xx_actor fields.
+
+ALTER TABLE /*_*/archive
+ DROP INDEX /*i*/ar_usertext_timestamp,
+ DROP COLUMN ar_user,
+ DROP COLUMN ar_user_text,
+ ALTER COLUMN ar_actor DROP DEFAULT;
+
+ALTER TABLE /*_*/ipblocks
+ DROP COLUMN ipb_by,
+ DROP COLUMN ipb_by_text,
+ ALTER COLUMN ipb_by_actor DROP DEFAULT;
+
+ALTER TABLE /*_*/image
+ DROP INDEX /*i*/img_user_timestamp,
+ DROP INDEX /*i*/img_usertext_timestamp,
+ DROP COLUMN img_user,
+ DROP COLUMN img_user_text,
+ ALTER COLUMN img_actor DROP DEFAULT;
+
+ALTER TABLE /*_*/oldimage
+ DROP INDEX /*i*/oi_usertext_timestamp,
+ DROP COLUMN oi_user,
+ DROP COLUMN oi_user_text,
+ ALTER COLUMN oi_actor DROP DEFAULT;
+
+ALTER TABLE /*_*/filearchive
+ DROP INDEX /*i*/fa_user_timestamp,
+ DROP COLUMN fa_user,
+ DROP COLUMN fa_user_text,
+ ALTER COLUMN fa_actor DROP DEFAULT;
+
+ALTER TABLE /*_*/recentchanges
+ DROP INDEX /*i*/rc_ns_usertext,
+ DROP INDEX /*i*/rc_user_text,
+ DROP COLUMN rc_user,
+ DROP COLUMN rc_user_text,
+ ALTER COLUMN rc_actor DROP DEFAULT;
+
+ALTER TABLE /*_*/logging
+ DROP INDEX /*i*/user_time,
+ DROP INDEX /*i*/log_user_type_time,
+ DROP INDEX /*i*/log_user_text_type_time,
+ DROP INDEX /*i*/log_user_text_time,
+ DROP COLUMN log_user,
+ DROP COLUMN log_user_text,
+ ALTER COLUMN log_actor DROP DEFAULT;
'callback' => 'processRow',
];
+ /** @var LocalRepo|null */
+ private $repo;
+
public function __construct() {
parent::__construct();
$this->addDescription( 'Script to clean up broken, unparseable upload filenames' );
* @return string
*/
private function filePath( $name ) {
- if ( !isset( $this->repo ) ) {
+ if ( $this->repo === null ) {
$this->repo = RepoGroup::singleton()->getLocalRepo();
}
* @param Title $title
*/
protected function moveInconsistentPage( $row, Title $title ) {
- if ( $title->exists( Title::GAID_FOR_UPDATE )
+ if ( $title->exists( Title::READ_LATEST )
|| $title->getInterwiki()
|| !$title->canExist()
) {
class CompareParsers extends DumpIterator {
private $count = 0;
+ /** @var bool */
+ private $saveFailed;
+ /** @var bool */
+ private $stripParametersEnabled;
+ /** @var bool */
+ private $showParsedOutput;
+ /** @var bool */
+ private $showDiff;
+ /** @var ParserOptions */
+ private $options;
+ /** @var int */
+ private $failed;
public function __construct() {
parent::__construct();
* @ingroup Maintenance
*/
abstract class DumpIterator extends Maintenance {
-
private $count = 0;
private $startTime;
+ /** @var string|bool|null */
+ private $from;
public function __construct() {
parent::__construct();
$revision->setTitle( Title::newFromText(
rawurldecode( basename( $this->getOption( 'file' ), '.txt' ) )
) );
+ $this->from = false;
$this->handleRevision( $revision );
return;
}
$this->count++;
- if ( isset( $this->from ) ) {
+ if ( $this->from !== false ) {
if ( $this->from != $title ) {
return;
}
}
public function execute() {
- if ( $this->getConfig()->get( 'AllDBsAreLocalhost' ) ) {
- $host = 'localhost';
- } elseif ( $this->hasOption( 'group' ) ) {
+ if ( $this->hasOption( 'group' ) ) {
$db = $this->getDB( DB_REPLICA, $this->getOption( 'group' ) );
$host = $db->getServer();
} else {
public $imageBasePath = false;
/** @var array|false */
public $nsFilter = false;
+ /** @var bool|resource */
+ public $stderr;
+ /** @var callable|null */
+ protected $importCallback;
+ /** @var callable|null */
+ protected $logItemCallback;
+ /** @var callable|null */
+ protected $uploadCallback;
+ /** @var int */
+ protected $startTime;
function __construct() {
parent::__construct();
public $dumpUploadFileContents = false;
public $orderRevs = false;
public $limitNamespaces = [];
+ /** @var bool|resource */
+ public $stderr;
protected $reportingInterval = 100;
protected $pageCount = 0;
protected $ID = 0;
+ /** @var int */
+ protected $startTime;
+ /** @var int */
+ protected $pageCountPart;
+ /** @var int */
+ protected $revCountPart;
+ /** @var int */
+ protected $maxCount;
+ /** @var int */
+ protected $timeOfCheckpoint;
+ /** @var ExportProgressFilter */
+ protected $egress;
+ /** @var string */
+ protected $buffer;
+ /** @var array|false */
+ protected $openElement;
+ /** @var bool */
+ protected $atStart;
+ /** @var string|null */
+ protected $thisRevModel;
+ /** @var string|null */
+ protected $thisRevFormat;
+ /** @var string */
+ protected $lastName;
+ /** @var string */
+ protected $state;
+
/**
* The dependency-injected database to use.
*
}
protected function doDBUpdates() {
- $actorTableSchemaMigrationStage = $this->getConfig()->get( 'ActorTableSchemaMigrationStage' );
-
- if ( !( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) ) {
- $this->output(
- "...cannot update while \$wgActorTableSchemaMigrationStage lacks SCHEMA_COMPAT_WRITE_NEW\n"
- );
- return false;
- }
-
$tables = $this->getOption( 'tables' );
if ( $tables !== null ) {
$this->tables = explode( ',', $tables );
return 0;
}
+ $dbw = $this->getDB( DB_MASTER );
+ if ( !$dbw->fieldExists( $table, $userField, __METHOD__ ) ) {
+ $this->output( "No need to migrate $table.$userField, field does not exist\n" );
+ return 0;
+ }
+
$complainedAboutUsers = [];
$primaryKey = (array)$primaryKey;
);
wfWaitForSlaves();
- $dbw = $this->getDB( DB_MASTER );
$actorIdSubquery = $this->makeActorIdSubquery( $dbw, $userField, $nameField );
$next = '1=1';
$countUpdated = 0;
* @param string $nameField User name field name
* @param string $newPrimaryKey Primary key of the new table.
* @param string $actorField Actor field name
+ * @return int Number of errors
*/
protected function migrateToTemp(
$table, $primaryKey, $extra, $userField, $nameField, $newPrimaryKey, $actorField
return 0;
}
+ $dbw = $this->getDB( DB_MASTER );
+ if ( !$dbw->fieldExists( $table, $userField, __METHOD__ ) ) {
+ $this->output( "No need to migrate $table.$userField, field does not exist\n" );
+ return 0;
+ }
+
$complainedAboutUsers = [];
$newTable = $table . '_actor_temp';
);
wfWaitForSlaves();
- $dbw = $this->getDB( DB_MASTER );
$actorIdSubquery = $this->makeActorIdSubquery( $dbw, $userField, $nameField );
$next = [];
$countUpdated = 0;
try {
$installer = InstallerOverrides::getCliInstaller( $siteName, $adminName, $this->mOptions );
} catch ( \MediaWiki\Installer\InstallException $e ) {
- $this->output( $e->getStatus()->getMessage()->parse() . "\n" );
+ $this->output( $e->getStatus()->getMessage()->text() . "\n" );
return false;
}
if ( !$envChecksOnly ) {
$status = $installer->execute();
if ( !$status->isGood() ) {
+ $installer->showStatusMessage( $status );
+
return false;
}
$installer->writeConfigurationFile( $this->getOption( 'confpath', $IP ) );
* @ingroup Maintenance
*/
class MWDocGen extends Maintenance {
+ /** @var string */
+ private $doxygen;
+ /** @var string */
+ private $mwVersion;
+ /** @var string */
+ private $output;
+ /** @var string */
+ private $input;
+ /** @var string */
+ private $inputFilter;
+ /** @var string */
+ private $template;
+ /** @var string[] */
+ private $excludes;
+ /** @var string[] */
+ private $excludePatterns;
+ /** @var bool */
+ private $doDot;
+ /** @var bool */
+ private $doMan;
/**
* Prepare Maintenance class
ar_parent_id INTEGER NULL,
ar_sha1 TEXT NOT NULL DEFAULT '',
ar_comment_id INTEGER NOT NULL,
- ar_user INTEGER NOT NULL DEFAULT 0 REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
- ar_user_text TEXT NOT NULL DEFAULT '',
- ar_actor INTEGER NOT NULL DEFAULT 0,
+ ar_actor INTEGER NOT NULL,
ar_timestamp TIMESTAMPTZ NOT NULL,
ar_minor_edit SMALLINT NOT NULL DEFAULT 0,
ar_rev_id INTEGER NOT NULL,
);
ALTER SEQUENCE archive_ar_id_seq OWNED BY archive.ar_id;
CREATE INDEX archive_name_title_timestamp ON archive (ar_namespace,ar_title,ar_timestamp);
-CREATE INDEX archive_user_text ON archive (ar_user_text);
CREATE INDEX archive_actor ON archive (ar_actor);
CREATE UNIQUE INDEX ar_revid_uniq ON archive (ar_rev_id);
ipb_id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('ipblocks_ipb_id_seq'),
ipb_address TEXT NULL,
ipb_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
- ipb_by INTEGER NOT NULL DEFAULT 0 REFERENCES mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
- ipb_by_text TEXT NOT NULL DEFAULT '',
- ipb_by_actor INTEGER NOT NULL DEFAULT 0,
+ ipb_by_actor INTEGER NOT NULL,
ipb_reason_id INTEGER NOT NULL,
ipb_timestamp TIMESTAMPTZ NOT NULL,
ipb_auto SMALLINT NOT NULL DEFAULT 0,
img_major_mime TEXT DEFAULT 'unknown',
img_minor_mime TEXT DEFAULT 'unknown',
img_description_id INTEGER NOT NULL,
- img_user INTEGER NOT NULL DEFAULT 0 REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
- img_user_text TEXT NOT NULL DEFAULT '',
- img_actor INTEGER NOT NULL DEFAULT 0,
+ img_actor INTEGER NOT NULL,
img_timestamp TIMESTAMPTZ,
img_sha1 TEXT NOT NULL DEFAULT ''
);
oi_height INTEGER NOT NULL,
oi_bits SMALLINT NULL,
oi_description_id INTEGER NOT NULL,
- oi_user INTEGER NOT NULL DEFAULT 0 REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
- oi_user_text TEXT NOT NULL DEFAULT '',
- oi_actor INTEGER NOT NULL DEFAULT 0,
+ oi_actor INTEGER NOT NULL,
oi_timestamp TIMESTAMPTZ NULL,
oi_metadata BYTEA NOT NULL DEFAULT '',
oi_media_type TEXT NULL,
fa_major_mime TEXT DEFAULT 'unknown',
fa_minor_mime TEXT DEFAULT 'unknown',
fa_description_id INTEGER NOT NULL,
- fa_user INTEGER NOT NULL DEFAULT 0 REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
- fa_user_text TEXT NOT NULL DEFAULT '',
- fa_actor INTEGER NOT NULL DEFAULT 0,
+ fa_actor INTEGER NOT NULL,
fa_timestamp TIMESTAMPTZ,
fa_deleted SMALLINT NOT NULL DEFAULT 0,
fa_sha1 TEXT NOT NULL DEFAULT ''
CREATE TABLE recentchanges (
rc_id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('recentchanges_rc_id_seq'),
rc_timestamp TIMESTAMPTZ NOT NULL,
- rc_user INTEGER NOT NULL DEFAULT 0 REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
- rc_user_text TEXT NOT NULL DEFAULT '',
- rc_actor INTEGER NOT NULL DEFAULT 0,
+ rc_actor INTEGER NOT NULL,
rc_namespace SMALLINT NOT NULL,
rc_title TEXT NOT NULL,
rc_comment_id INTEGER NOT NULL,
log_type TEXT NOT NULL,
log_action TEXT NOT NULL,
log_timestamp TIMESTAMPTZ NOT NULL,
- log_user INTEGER NOT NULL DEFAULT 0 REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
- log_actor INTEGER NOT NULL DEFAULT 0,
+ log_actor INTEGER NOT NULL,
log_namespace SMALLINT NOT NULL,
log_title TEXT NOT NULL,
log_comment_id INTEGER NOT NULL,
log_params TEXT,
log_deleted SMALLINT NOT NULL DEFAULT 0,
- log_user_text TEXT NOT NULL DEFAULT '',
log_page INTEGER
);
ALTER SEQUENCE logging_log_id_seq OWNED BY logging.log_id;
CREATE INDEX logging_type_name ON logging (log_type, log_timestamp);
-CREATE INDEX logging_user_time ON logging (log_timestamp, log_user);
CREATE INDEX logging_actor_time_backwards ON logging (log_timestamp, log_actor);
CREATE INDEX logging_page_time ON logging (log_namespace, log_title, log_timestamp);
CREATE INDEX logging_times ON logging (log_timestamp);
-CREATE INDEX logging_user_type_time ON logging (log_user, log_type, log_timestamp);
CREATE INDEX logging_actor_type_time ON logging (log_actor, log_type, log_timestamp);
CREATE INDEX logging_page_id_time ON logging (log_page, log_timestamp);
-CREATE INDEX logging_user_text_type_time ON logging (log_user_text, log_type, log_timestamp);
-CREATE INDEX logging_user_text_time ON logging (log_user_text, log_timestamp);
CREATE INDEX logging_actor_time ON logging (log_actor, log_timestamp);
CREATE INDEX logging_type_action ON logging (log_type, log_action, log_timestamp);
/* Variables for dressing up as a parser */
public $mTitle = 'PreprocessDump';
public $mPPNodeCount = 0;
+ /** @var Preprocessor */
+ public $mPreprocessor;
public function getStripList() {
$parser = MediaWikiServices::getInstance()->getParser();
class PPFuzzTest {
public $templates, $mainText, $title, $entryPoint, $output;
+ /** @var PPFuzzTester */
+ private $parent;
+ /** @var string */
+ public $nickname;
+ /** @var bool */
+ public $fancySig;
+
/**
* @param PPFuzzTester $tester
*/
* @license GPL-2.0-or-later
*/
-use Wikimedia\Rdbms\IDatabase;
-
require_once __DIR__ . '/Maintenance.php';
/**
* @return int Number of entries changed, or that would be changed
*/
private function doReassignEdits( &$from, &$to, $rc = false, $report = false ) {
- $actorTableSchemaMigrationStage = $this->getConfig()->get( 'ActorTableSchemaMigrationStage' );
-
$dbw = $this->getDB( DB_MASTER );
$this->beginTransaction( $dbw, __METHOD__ );
if ( $total ) {
# Reassign edits
$this->output( "\nReassigning current edits..." );
- if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
- $dbw->update(
- 'revision',
- [
- 'rev_user' => $to->getId(),
- 'rev_user_text' => $to->getName(),
- ],
- $from->isLoggedIn()
- ? [ 'rev_user' => $from->getId() ] : [ 'rev_user_text' => $from->getName() ],
- __METHOD__
- );
- }
- if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
- $dbw->update(
- 'revision_actor_temp',
- [ 'revactor_actor' => $to->getActorId( $dbw ) ],
- [ 'revactor_actor' => $from->getActorId() ],
- __METHOD__
- );
- }
+ $dbw->update(
+ 'revision_actor_temp',
+ [ 'revactor_actor' => $to->getActorId( $dbw ) ],
+ [ 'revactor_actor' => $from->getActorId() ],
+ __METHOD__
+ );
$this->output( "done.\nReassigning deleted edits..." );
$dbw->update( 'archive',
- $this->userSpecification( $dbw, $to, 'ar_user', 'ar_user_text', 'ar_actor' ),
+ [ 'ar_actor' => $to->getActorId( $dbw ) ],
[ $arQueryInfo['conds'] ], __METHOD__ );
$this->output( "done.\n" );
# Update recent changes if required
if ( $rc ) {
$this->output( "Updating recent changes..." );
$dbw->update( 'recentchanges',
- $this->userSpecification( $dbw, $to, 'rc_user', 'rc_user_text', 'rc_actor' ),
+ [ 'rc_actor' => $to->getActorId( $dbw ) ],
[ $rcQueryInfo['conds'] ], __METHOD__ );
$this->output( "done.\n" );
}
return (int)$total;
}
- /**
- * Return user specifications for an UPDATE
- * i.e. user => id, user_text => text
- *
- * @param IDatabase $dbw Database handle
- * @param User $user User for the spec
- * @param string $idfield Field name containing the identifier
- * @param string $utfield Field name containing the user text
- * @param string $acfield Field name containing the actor ID
- * @return array
- */
- private function userSpecification( IDatabase $dbw, &$user, $idfield, $utfield, $acfield ) {
- $actorTableSchemaMigrationStage = $this->getConfig()->get( 'ActorTableSchemaMigrationStage' );
-
- $ret = [];
- if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
- $ret += [
- $idfield => $user->getId(),
- $utfield => $user->getName(),
- ];
- }
- if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
- $ret += [ $acfield => $user->getActorId( $dbw ) ];
- }
- return $ret;
- }
-
/**
* Initialise the user object
*
* @ingroup Maintenance
*/
class ImageBuilder extends Maintenance {
-
/**
* @var IMaintainableDatabase
*/
protected $dbw;
+ /** @var bool */
+ private $dryrun;
+
+ /** @var LocalRepo|null */
+ private $repo;
+
+ /** @var int */
+ private $updated;
+
+ /** @var int */
+ private $processed;
+
+ /** @var int */
+ private $count;
+
+ /** @var int */
+ private $startTime;
+
+ /** @var string */
+ private $table;
+
function __construct() {
parent::__construct();
* @return LocalRepo
*/
function getRepo() {
- if ( !isset( $this->repo ) ) {
+ if ( $this->repo === null ) {
$this->repo = RepoGroup::singleton()->getLocalRepo();
}
* @ingroup Maintenance
*/
class RecountCategories extends Maintenance {
+ /** @var string */
+ private $mode;
+
+ /** @var int */
+ private $minimumId;
+
public function __construct() {
parent::__construct();
$this->addDescription( <<<'TEXT'
}
public function execute() {
- $actorTableSchemaMigrationStage = $this->getConfig()->get( 'ActorTableSchemaMigrationStage' );
-
$this->output( "Remove unused accounts\n\n" );
# Do an initial scan for inactive accounts and report the result
$delUser = [];
$delActor = [];
$dbr = $this->getDB( DB_REPLICA );
- if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
- $res = $dbr->select(
- [ 'user', 'actor' ],
- [ 'user_id', 'user_name', 'user_touched', 'actor_id' ],
- '',
- __METHOD__,
- [],
- [ 'actor' => [ 'LEFT JOIN', 'user_id = actor_user' ] ]
- );
- } else {
- $res = $dbr->select( 'user', [ 'user_id', 'user_name', 'user_touched' ], '', __METHOD__ );
- }
+ $res = $dbr->select(
+ [ 'user', 'actor' ],
+ [ 'user_id', 'user_name', 'user_touched', 'actor_id' ],
+ '',
+ __METHOD__,
+ [],
+ [ 'actor' => [ 'LEFT JOIN', 'user_id = actor_user' ] ]
+ );
if ( $this->hasOption( 'ignore-groups' ) ) {
$excludedGroups = explode( ',', $this->getOption( 'ignore-groups' ) );
} else {
$this->output( "\nDeleting unused accounts..." );
$dbw = $this->getDB( DB_MASTER );
$dbw->delete( 'user', [ 'user_id' => $delUser ], __METHOD__ );
- if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
- # Keep actor rows referenced from ipblocks
- $keep = $dbw->selectFieldValues(
- 'ipblocks', 'ipb_by_actor', [ 'ipb_by_actor' => $delActor ], __METHOD__
- );
- $del = array_diff( $delActor, $keep );
- if ( $del ) {
- $dbw->delete( 'actor', [ 'actor_id' => $del ], __METHOD__ );
- }
- if ( $keep ) {
- $dbw->update( 'actor', [ 'actor_user' => 0 ], [ 'actor_id' => $keep ], __METHOD__ );
- }
+ # Keep actor rows referenced from ipblocks
+ $keep = $dbw->selectFieldValues(
+ 'ipblocks', 'ipb_by_actor', [ 'ipb_by_actor' => $delActor ], __METHOD__
+ );
+ $del = array_diff( $delActor, $keep );
+ if ( $del ) {
+ $dbw->delete( 'actor', [ 'actor_id' => $del ], __METHOD__ );
+ }
+ if ( $keep ) {
+ $dbw->update( 'actor', [ 'actor_user' => 0 ], [ 'actor_id' => $keep ], __METHOD__ );
}
$dbw->delete( 'user_groups', [ 'ug_user' => $delUser ], __METHOD__ );
$dbw->delete( 'user_former_groups', [ 'ufg_user' => $delUser ], __METHOD__ );
$dbw->delete( 'user_properties', [ 'up_user' => $delUser ], __METHOD__ );
- if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
- $dbw->delete( 'logging', [ 'log_actor' => $delActor ], __METHOD__ );
- $dbw->delete( 'recentchanges', [ 'rc_actor' => $delActor ], __METHOD__ );
- }
- if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
- $dbw->delete( 'logging', [ 'log_user' => $delUser ], __METHOD__ );
- $dbw->delete( 'recentchanges', [ 'rc_user' => $delUser ], __METHOD__ );
- }
+ $dbw->delete( 'logging', [ 'log_actor' => $delActor ], __METHOD__ );
+ $dbw->delete( 'recentchanges', [ 'rc_actor' => $delActor ], __METHOD__ );
$this->output( "done.\n" );
# Update the site_stats.ss_users field
$users = $dbw->selectField( 'user', 'COUNT(*)', [], __METHOD__ );
private $count = 0;
private $outputDirectory, $startTime;
+ /** @var string */
+ private $prefix;
public function __construct() {
parent::__construct();
--- /dev/null
+--
+-- patch-archive-drop-ar_user.sql
+--
+-- T188327. Drop old xx_user and xx_user_text fields, and defaults from xx_actor fields.
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/archive_tmp;
+CREATE TABLE /*_*/archive_tmp (
+ ar_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ ar_namespace int NOT NULL default 0,
+ ar_title varchar(255) binary NOT NULL default '',
+ ar_comment_id bigint unsigned NOT NULL,
+ ar_actor bigint unsigned NOT NULL,
+ ar_timestamp binary(14) NOT NULL default '',
+ ar_minor_edit tinyint NOT NULL default 0,
+ ar_rev_id int unsigned NOT NULL,
+ ar_text_id int unsigned NOT NULL DEFAULT 0,
+ ar_deleted tinyint unsigned NOT NULL default 0,
+ ar_len int unsigned,
+ ar_page_id int unsigned,
+ ar_parent_id int unsigned default NULL,
+ ar_sha1 varbinary(32) NOT NULL default '',
+ ar_content_model varbinary(32) DEFAULT NULL,
+ ar_content_format varbinary(64) DEFAULT NULL
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/archive_tmp (
+ ar_id, ar_namespace, ar_title, ar_comment_id, ar_actor,
+ ar_timestamp, ar_minor_edit, ar_rev_id, ar_text_id, ar_deleted,
+ ar_len, ar_page_id, ar_parent_id, ar_sha1, ar_content_model, ar_content_format
+ ) SELECT
+ ar_id, ar_namespace, ar_title, ar_comment_id, ar_actor,
+ ar_timestamp, ar_minor_edit, ar_rev_id, ar_text_id, ar_deleted,
+ ar_len, ar_page_id, ar_parent_id, ar_sha1, ar_content_model, ar_content_format
+ FROM /*_*/archive;
+
+DROP TABLE /*_*/archive;
+ALTER TABLE /*_*/archive_tmp RENAME TO /*_*/archive;
+CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
+CREATE INDEX /*i*/ar_actor_timestamp ON /*_*/archive (ar_actor,ar_timestamp);
+CREATE UNIQUE INDEX /*i*/ar_revid_uniq ON /*_*/archive (ar_rev_id);
+
+COMMIT;
--- /dev/null
+--
+-- patch-filearchive-drop-fa_user.sql
+--
+-- T188327. Drop old xx_user and xx_user_text fields, and defaults from xx_actor fields.
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/filearchive_tmp;
+CREATE TABLE /*_*/filearchive_tmp (
+ fa_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ fa_name varchar(255) binary NOT NULL default '',
+ fa_archive_name varchar(255) binary default '',
+ fa_storage_group varbinary(16),
+ fa_storage_key varbinary(64) default '',
+ fa_deleted_user int,
+ fa_deleted_timestamp binary(14) default '',
+ fa_deleted_reason_id bigint unsigned NOT NULL,
+ fa_size int unsigned default 0,
+ fa_width int default 0,
+ fa_height int default 0,
+ fa_metadata mediumblob,
+ fa_bits int default 0,
+ fa_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE", "3D") default NULL,
+ fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart", "chemical") default "unknown",
+ fa_minor_mime varbinary(100) default "unknown",
+ fa_description_id bigint unsigned NOT NULL,
+ fa_actor bigint unsigned NOT NULL DEFAULT 0,
+ fa_timestamp binary(14) default '',
+ fa_deleted tinyint unsigned NOT NULL default 0,
+ fa_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/filearchive_tmp (
+ fa_id, fa_name, fa_archive_name, fa_storage_group, fa_storage_key,
+ fa_deleted_user, fa_deleted_timestamp, fa_deleted_reason_id,
+ fa_size, fa_width, fa_height, fa_metadata, fa_bits,
+ fa_media_type, fa_major_mime, fa_minor_mime, fa_description_id,
+ fa_actor, fa_timestamp, fa_deleted, fa_sha1
+ ) SELECT
+ fa_id, fa_name, fa_archive_name, fa_storage_group, fa_storage_key,
+ fa_deleted_user, fa_deleted_timestamp, fa_deleted_reason_id,
+ fa_size, fa_width, fa_height, fa_metadata, fa_bits,
+ fa_media_type, fa_major_mime, fa_minor_mime, fa_description_id,
+ fa_actor, fa_timestamp, fa_deleted, fa_sha1
+ FROM /*_*/filearchive;
+
+DROP TABLE /*_*/filearchive;
+ALTER TABLE /*_*/filearchive_tmp RENAME TO /*_*/filearchive;
+CREATE INDEX /*i*/fa_name ON /*_*/filearchive (fa_name, fa_timestamp);
+CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_storage_key);
+CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
+CREATE INDEX /*i*/fa_actor_timestamp ON /*_*/filearchive (fa_actor,fa_timestamp);
+CREATE INDEX /*i*/fa_sha1 ON /*_*/filearchive (fa_sha1(10));
+
+COMMIT;
--- /dev/null
+--
+-- patch-image-drop-img_description.sql
+--
+-- T188327. Drop old xx_user and xx_user_text fields, and defaults from xx_actor fields.
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/image_tmp;
+CREATE TABLE /*_*/image_tmp (
+ img_name varchar(255) binary NOT NULL default '' PRIMARY KEY,
+ img_size int unsigned NOT NULL default 0,
+ img_width int NOT NULL default 0,
+ img_height int NOT NULL default 0,
+ img_metadata mediumblob NOT NULL,
+ img_bits int NOT NULL default 0,
+ img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE", "3D") default NULL,
+ img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart", "chemical") NOT NULL default "unknown",
+ img_minor_mime varbinary(100) NOT NULL default "unknown",
+ img_description_id bigint unsigned NOT NULL,
+ img_actor bigint unsigned NOT NULL,
+ img_timestamp varbinary(14) NOT NULL default '',
+ img_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/image_tmp (
+ img_name, img_size, img_width, img_height, img_metadata, img_bits,
+ img_media_type, img_major_mime, img_minor_mime, img_description_id,
+ img_actor, img_timestamp, img_sha1
+ ) SELECT
+ img_name, img_size, img_width, img_height, img_metadata, img_bits,
+ img_media_type, img_major_mime, img_minor_mime, img_description_id,
+ img_actor, img_timestamp, img_sha1
+ FROM /*_*/image;
+
+DROP TABLE /*_*/image;
+ALTER TABLE /*_*/image_tmp RENAME TO /*_*/image;
+CREATE INDEX /*i*/img_actor_timestamp ON /*_*/image (img_actor,img_timestamp);
+CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
+CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp);
+CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1(10));
+CREATE INDEX /*i*/img_media_mime ON /*_*/image (img_media_type,img_major_mime,img_minor_mime);
+
+COMMIT;
--- /dev/null
+--
+-- patch-ipblocks-drop-ipb_by.sql
+--
+-- T188327. Drop old xx_user and xx_user_text fields, and defaults from xx_actor fields.
+
+BEGIN;
+
+DROP TABLE IF EXISTS ipblocks_tmp;
+CREATE TABLE /*_*/ipblocks_tmp (
+ ipb_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ ipb_address tinyblob NOT NULL,
+ ipb_user int unsigned NOT NULL default 0,
+ ipb_by_actor bigint unsigned NOT NULL,
+ ipb_reason_id bigint unsigned NOT NULL,
+ ipb_timestamp binary(14) NOT NULL default '',
+ ipb_auto bool NOT NULL default 0,
+ ipb_anon_only bool NOT NULL default 0,
+ ipb_create_account bool NOT NULL default 1,
+ ipb_enable_autoblock bool NOT NULL default '1',
+ ipb_expiry varbinary(14) NOT NULL default '',
+ ipb_range_start tinyblob NOT NULL,
+ ipb_range_end tinyblob NOT NULL,
+ ipb_deleted bool NOT NULL default 0,
+ ipb_block_email bool NOT NULL default 0,
+ ipb_allow_usertalk bool NOT NULL default 0,
+ ipb_parent_block_id int default NULL,
+ ipb_sitewide bool NOT NULL default 1
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/ipblocks_tmp (
+ ipb_id, ipb_address, ipb_user, ipb_by_actor, ipb_reason_id,
+ ipb_timestamp, ipb_auto, ipb_anon_only, ipb_create_account,
+ ipb_enable_autoblock, ipb_expiry, ipb_range_start, ipb_range_end,
+ ipb_deleted, ipb_block_email, ipb_allow_usertalk, ipb_parent_block_id, ipb_sitewide
+ ) SELECT
+ ipb_id, ipb_address, ipb_user, ipb_by_actor, ipb_reason_id,
+ ipb_timestamp, ipb_auto, ipb_anon_only, ipb_create_account,
+ ipb_enable_autoblock, ipb_expiry, ipb_range_start, ipb_range_end,
+ ipb_deleted, ipb_block_email, ipb_allow_usertalk, ipb_parent_block_id, ipb_sitewide
+ FROM /*_*/ipblocks;
+
+DROP TABLE /*_*/ipblocks;
+ALTER TABLE /*_*/ipblocks_tmp RENAME TO /*_*/ipblocks;
+CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only);
+CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user);
+CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8));
+CREATE INDEX /*i*/ipb_timestamp ON /*_*/ipblocks (ipb_timestamp);
+CREATE INDEX /*i*/ipb_expiry ON /*_*/ipblocks (ipb_expiry);
+CREATE INDEX /*i*/ipb_parent_block_id ON /*_*/ipblocks (ipb_parent_block_id);
+
+COMMIT;
--- /dev/null
+--
+-- patch-logging-drop-log_user.sql
+--
+-- T188327. Drop old xx_user and xx_user_text fields, and defaults from xx_actor fields.
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/logging_tmp;
+CREATE TABLE /*_*/logging_tmp (
+ log_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ log_type varbinary(32) NOT NULL default '',
+ log_action varbinary(32) NOT NULL default '',
+ log_timestamp binary(14) NOT NULL default '19700101000000',
+ log_actor bigint unsigned NOT NULL DEFAULT 0,
+ log_namespace int NOT NULL default 0,
+ log_title varchar(255) binary NOT NULL default '',
+ log_page int unsigned NULL,
+ log_comment_id bigint unsigned NOT NULL,
+ log_params blob NOT NULL,
+ log_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/logging_tmp (
+ log_id, log_type, log_action, log_timestamp, log_actor,
+ log_namespace, log_title, log_page, log_comment_id, log_params, log_deleted
+ ) SELECT
+ log_id, log_type, log_action, log_timestamp, log_actor,
+ log_namespace, log_title, log_page, log_comment_id, log_params, log_deleted
+ FROM /*_*/logging;
+
+DROP TABLE /*_*/logging;
+ALTER TABLE /*_*/logging_tmp RENAME TO /*_*/logging;
+CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp);
+CREATE INDEX /*i*/actor_time ON /*_*/logging (log_actor, log_timestamp);
+CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_timestamp);
+CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
+CREATE INDEX /*i*/log_actor_type_time ON /*_*/logging (log_actor, log_type, log_timestamp);
+CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp);
+CREATE INDEX /*i*/log_type_action ON /*_*/logging (log_type, log_action, log_timestamp);
+
+COMMIT;
--- /dev/null
+--
+-- patch-oldimage-drop-oi_user.sql
+--
+-- T188327. Drop old xx_user and xx_user_text fields, and defaults from xx_actor fields.
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/oldimage_tmp;
+CREATE TABLE /*_*/oldimage_tmp (
+ oi_name varchar(255) binary NOT NULL default '',
+ oi_archive_name varchar(255) binary NOT NULL default '',
+ oi_size int unsigned NOT NULL default 0,
+ oi_width int NOT NULL default 0,
+ oi_height int NOT NULL default 0,
+ oi_bits int NOT NULL default 0,
+ oi_description_id bigint unsigned NOT NULL,
+ oi_actor bigint unsigned NOT NULL,
+ oi_timestamp binary(14) NOT NULL default '',
+ oi_metadata mediumblob NOT NULL,
+ oi_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE", "3D") default NULL,
+ oi_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart", "chemical") NOT NULL default "unknown",
+ oi_minor_mime varbinary(100) NOT NULL default "unknown",
+ oi_deleted tinyint unsigned NOT NULL default 0,
+ oi_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/oldimage_tmp (
+ oi_name, oi_archive_name, oi_size, oi_width, oi_height, oi_bits,
+ oi_description_id, oi_actor, oi_timestamp, oi_metadata,
+ oi_media_type, oi_major_mime, oi_minor_mime, oi_deleted, oi_sha1
+ ) SELECT
+ oi_name, oi_archive_name, oi_size, oi_width, oi_height, oi_bits,
+ oi_description_id, oi_actor, oi_timestamp, oi_metadata,
+ oi_media_type, oi_major_mime, oi_minor_mime, oi_deleted, oi_sha1
+ FROM /*_*/oldimage;
+
+DROP TABLE /*_*/oldimage;
+ALTER TABLE /*_*/oldimage_tmp RENAME TO /*_*/oldimage;
+CREATE INDEX /*i*/oi_actor_timestamp ON /*_*/oldimage (oi_actor,oi_timestamp);
+CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
+CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name(14));
+CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1(10));
+
+COMMIT;
--- /dev/null
+--
+-- patch-recentchanges-drop-rc_user.sql
+--
+-- T188327. Drop old xx_user and xx_user_text fields, and defaults from xx_actor fields.
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/recentchanges_tmp;
+CREATE TABLE /*_*/recentchanges_tmp (
+ rc_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ rc_timestamp varbinary(14) NOT NULL default '',
+ rc_actor bigint unsigned NOT NULL DEFAULT 0,
+ rc_namespace int NOT NULL default 0,
+ rc_title varchar(255) binary NOT NULL default '',
+ rc_comment_id bigint unsigned NOT NULL,
+ rc_minor tinyint unsigned NOT NULL default 0,
+ rc_bot tinyint unsigned NOT NULL default 0,
+ rc_new tinyint unsigned NOT NULL default 0,
+ rc_cur_id int unsigned NOT NULL default 0,
+ rc_this_oldid int unsigned NOT NULL default 0,
+ rc_last_oldid int unsigned NOT NULL default 0,
+ rc_type tinyint unsigned NOT NULL default 0,
+ rc_source varchar(16) binary not null default '',
+ rc_patrolled tinyint unsigned NOT NULL default 0,
+ rc_ip varbinary(40) NOT NULL default '',
+ rc_old_len int,
+ rc_new_len int,
+ rc_deleted tinyint unsigned NOT NULL default 0,
+ rc_logid int unsigned NOT NULL default 0,
+ rc_log_type varbinary(255) NULL default NULL,
+ rc_log_action varbinary(255) NULL default NULL,
+ rc_params blob NULL
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/recentchanges_tmp (
+ rc_id, rc_timestamp, rc_actor, rc_namespace, rc_title,
+ rc_comment_id, rc_minor, rc_bot, rc_new, rc_cur_id, rc_this_oldid, rc_last_oldid,
+ rc_type, rc_source, rc_patrolled, rc_ip, rc_old_len, rc_new_len, rc_deleted,
+ rc_logid, rc_log_type, rc_log_action, rc_params
+ ) SELECT
+ rc_id, rc_timestamp, rc_actor, rc_namespace, rc_title,
+ rc_comment_id, rc_minor, rc_bot, rc_new, rc_cur_id, rc_this_oldid, rc_last_oldid,
+ rc_type, rc_source, rc_patrolled, rc_ip, rc_old_len, rc_new_len, rc_deleted,
+ rc_logid, rc_log_type, rc_log_action, rc_params
+ FROM /*_*/recentchanges;
+
+DROP TABLE /*_*/recentchanges;
+ALTER TABLE /*_*/recentchanges_tmp RENAME TO /*_*/recentchanges;
+CREATE INDEX /*i*/rc_timestamp ON /*_*/recentchanges (rc_timestamp);
+CREATE INDEX /*i*/rc_namespace_title_timestamp ON /*_*/recentchanges (rc_namespace, rc_title, rc_timestamp);
+CREATE INDEX /*i*/rc_cur_id ON /*_*/recentchanges (rc_cur_id);
+CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,rc_timestamp);
+CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip);
+CREATE INDEX /*i*/rc_ns_actor ON /*_*/recentchanges (rc_namespace, rc_actor);
+CREATE INDEX /*i*/rc_actor ON /*_*/recentchanges (rc_actor, rc_timestamp);
+CREATE INDEX /*i*/rc_name_type_patrolled_timestamp ON /*_*/recentchanges (rc_namespace, rc_type, rc_patrolled, rc_timestamp);
+CREATE INDEX /*i*/rc_this_oldid ON /*_*/recentchanges (rc_this_oldid);
+
+COMMIT;
-- Blobs table for external storage
-CREATE TABLE /*$wgDBprefix*/blobs (
+CREATE TABLE IF NOT EXISTS /*$wgDBprefix*/blobs (
blob_id integer UNSIGNED NOT NULL AUTO_INCREMENT,
blob_text longblob,
PRIMARY KEY (blob_id)
}
foreach ( $externalConcatBlobs as $cluster => $xBlobIds ) {
$blobIds = array_keys( $xBlobIds );
- $extDb =& $this->dbStore->getSlave( $cluster );
+ $extDb =& $this->dbStore->getReplica( $cluster );
$blobsTable = $this->dbStore->getTable( $extDb );
$res = $extDb->select( $blobsTable,
[ 'blob_id' ],
foreach ( $externalConcatBlobs as $cluster => $oldIds ) {
$blobIds = array_keys( $oldIds );
- $extDb =& $this->dbStore->getSlave( $cluster );
+ $extDb =& $this->dbStore->getReplica( $cluster );
$blobsTable = $this->dbStore->getTable( $extDb );
$headerLength = strlen( self::CONCAT_HEADER );
$res = $extDb->select( $blobsTable,
/** @var ConcatenatedGzipHistoryBlob|false */
public $cgz;
public $referrers;
+ /** @var array */
+ private $texts;
/**
* Create a transaction from a RecompressTracked object
-- Basic revision stuff...
ar_comment_id bigint unsigned NOT NULL,
- ar_user int unsigned NOT NULL default 0, -- Deprecated in favor of ar_actor
- ar_user_text varchar(255) binary NOT NULL DEFAULT '', -- Deprecated in favor of ar_actor
- ar_actor bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that ar_user/ar_user_text should be used)
+ ar_actor bigint unsigned NOT NULL,
ar_timestamp binary(14) NOT NULL default '',
ar_minor_edit tinyint NOT NULL default 0,
CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
-- Index for Special:DeletedContributions
-CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
CREATE INDEX /*i*/ar_actor_timestamp ON /*_*/archive (ar_actor,ar_timestamp);
-- Index for linking archive rows with tables that normally link with revision
-- Blocked user ID or 0 for IP blocks.
ipb_user int unsigned NOT NULL default 0,
- -- User ID who made the block.
- ipb_by int unsigned NOT NULL default 0, -- Deprecated in favor of ipb_by_actor
-
- -- User name of blocker
- ipb_by_text varchar(255) binary NOT NULL default '', -- Deprecated in favor of ipb_by_actor
-
-- Actor who made the block.
- ipb_by_actor bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that ipb_by/ipb_by_text should be used)
+ ipb_by_actor bigint unsigned NOT NULL,
-- Key to comment_id. Text comment made by blocker.
ipb_reason_id bigint unsigned NOT NULL,
-- This is displayed in image upload history and logs.
img_description_id bigint unsigned NOT NULL,
- -- user_id and user_name of uploader.
- -- Deprecated in favor of img_actor.
- img_user int unsigned NOT NULL default 0,
- img_user_text varchar(255) binary NOT NULL DEFAULT '',
-
-- actor_id of the uploader.
- -- ("DEFAULT 0" is temporary, signaling that img_user/img_user_text should be used)
- img_actor bigint unsigned NOT NULL DEFAULT 0,
+ img_actor bigint unsigned NOT NULL,
-- Time of the upload.
img_timestamp varbinary(14) NOT NULL default '',
) /*$wgDBTableOptions*/;
-- Used by Special:Newimages and ApiQueryAllImages
-CREATE INDEX /*i*/img_user_timestamp ON /*_*/image (img_user,img_timestamp);
-CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp);
CREATE INDEX /*i*/img_actor_timestamp ON /*_*/image (img_actor,img_timestamp);
-- Used by Special:ListFiles for sort-by-size
CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
oi_height int NOT NULL default 0,
oi_bits int NOT NULL default 0,
oi_description_id bigint unsigned NOT NULL,
- oi_user int unsigned NOT NULL default 0, -- Deprecated in favor of oi_actor
- oi_user_text varchar(255) binary NOT NULL DEFAULT '', -- Deprecated in favor of oi_actor
- oi_actor bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that oi_user/oi_user_text should be used)
+ oi_actor bigint unsigned NOT NULL,
oi_timestamp binary(14) NOT NULL default '',
oi_metadata mediumblob NOT NULL,
oi_sha1 varbinary(32) NOT NULL default ''
) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp);
CREATE INDEX /*i*/oi_actor_timestamp ON /*_*/oldimage (oi_actor,oi_timestamp);
CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
-- oi_archive_name truncated to 14 to avoid key length overflow
fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart", "chemical") default "unknown",
fa_minor_mime varbinary(100) default "unknown",
fa_description_id bigint unsigned NOT NULL,
- fa_user int unsigned default 0, -- Deprecated in favor of fa_actor
- fa_user_text varchar(255) binary DEFAULT '', -- Deprecated in favor of fa_actor
- fa_actor bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that fa_user/fa_user_text should be used)
+ fa_actor bigint unsigned NOT NULL,
fa_timestamp binary(14) default '',
-- Visibility of deleted revisions, bitfield
-- sort by deletion time
CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
-- sort by uploader
-CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
CREATE INDEX /*i*/fa_actor_timestamp ON /*_*/filearchive (fa_actor,fa_timestamp);
-- find file by sha1, 10 bytes will be enough for hashes to be indexed
CREATE INDEX /*i*/fa_sha1 ON /*_*/filearchive (fa_sha1(10));
rc_timestamp varbinary(14) NOT NULL default '',
-- As in revision
- rc_user int unsigned NOT NULL default 0, -- Deprecated in favor of rc_actor
- rc_user_text varchar(255) binary NOT NULL DEFAULT '', -- Deprecated in favor of rc_actor
- rc_actor bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that rc_user/rc_user_text should be used)
+ rc_actor bigint unsigned NOT NULL,
-- When pages are renamed, their RC entries do _not_ change.
rc_namespace int NOT NULL default 0,
CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip);
-- Probably intended for Special:NewPages namespace filter
-CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text);
CREATE INDEX /*i*/rc_ns_actor ON /*_*/recentchanges (rc_namespace, rc_actor);
-- SiteStats active user count, Special:ActiveUsers, Special:NewPages user filter
-CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp);
CREATE INDEX /*i*/rc_actor ON /*_*/recentchanges (rc_actor, rc_timestamp);
-- ApiQueryRecentChanges (T140108)
-- Timestamp. Duh.
log_timestamp binary(14) NOT NULL default '19700101000000',
- -- The user who performed this action; key to user_id
- log_user int unsigned NOT NULL default 0, -- Deprecated in favor of log_actor
-
- -- Name of the user who performed this action
- log_user_text varchar(255) binary NOT NULL default '', -- Deprecated in favor of log_actor
-
-- The actor who performed this action
- log_actor bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that log_user/log_user_text should be used)
+ log_actor bigint unsigned NOT NULL,
-- Key to the page affected. Where a user is the target,
-- this will point to the user page.
CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp);
-- Special:Log performer filter
-CREATE INDEX /*i*/user_time ON /*_*/logging (log_user, log_timestamp);
CREATE INDEX /*i*/actor_time ON /*_*/logging (log_actor, log_timestamp);
-- Special:Log title filter, log extract
CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
-- Special:Log filter by performer and type
-CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp);
CREATE INDEX /*i*/log_actor_type_time ON /*_*/logging (log_actor, log_type, log_timestamp);
-- Apparently just used for a few maintenance pages (findMissingFiles.php, Flow).
-- Special:Log action filter
CREATE INDEX /*i*/log_type_action ON /*_*/logging (log_type, log_action, log_timestamp);
--- Special:Log filter by type and anonymous performer
-CREATE INDEX /*i*/log_user_text_type_time ON /*_*/logging (log_user_text, log_type, log_timestamp);
-
--- Special:Log filter by anonymous performer
-CREATE INDEX /*i*/log_user_text_time ON /*_*/logging (log_user_text, log_timestamp);
-
CREATE TABLE /*_*/log_search (
-- The type of ID (rev ID, log ID, rev timestamp, username)
$ret[$option][$userValue] = ( $ret[$option][$userValue] ?? 0 ) + 1;
}
} else {
-
foreach ( $defaultOptions as $name => $defaultValue ) {
$userValue = $user->getOption( $name );
if ( $userValue != $defaultValue ) {
- $ret[$option][$userValue] = ( $ret[$option][$userValue] ?? 0 ) + 1;
+ $ret[$name][$userValue] = ( $ret[$name][$userValue] ?? 0 ) + 1;
}
}
}
],
'mediawiki.RegExp' => [
'deprecated' => 'Please use mw.util.escapeRegExp() instead.',
- 'scripts' => 'resources/src/mediawiki.RegExp.js',
'targets' => [ 'desktop', 'mobile' ],
'dependencies' => [
'mediawiki.util',
'mediawiki.util',
'mediawiki.Title',
'mediawiki.jqueryMsg',
- 'mediawiki.util',
],
'messages' => [
'watch',
'dependencies' => [
'mediawiki.api',
'mediawiki.jqueryMsg',
- 'jquery.throttle-debounce',
'mediawiki.htmlform.checker',
],
],
+++ /dev/null
-( function () {
- mw.RegExp = {};
- // Backwards-compatible alias; @deprecated since 1.34
- mw.log.deprecate( mw.RegExp, 'escape', mw.util.escapeRegExp, 'Use mw.util.escapeRegExp() instead.', 'mw.RegExp.escape' );
-}() );
'[^' + mw.config.get( 'wgLegalTitleChars' ) + ']' +
// URL percent encoding sequences interfere with the ability
// to round-trip titles -- you can't link to them consistently.
- '|%[0-9A-Fa-f]{2}' +
+ '|%[\\dA-Fa-f]{2}' +
// XML/HTML character references produce similar issues.
- '|&[A-Za-z0-9\u0080-\uFFFF]+;' +
- '|&#[0-9]+;' +
- '|&#x[0-9A-Fa-f]+;'
+ '|&[\\dA-Za-z\u0080-\uFFFF]+;' +
+ '|&#\\d+;' +
+ '|&#x[\\dA-Fa-f]+;'
),
// From MediaWikiTitleCodec::splitTitleString() in PHP
rWhitespace = /[ _\u00A0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]+/g,
// From MediaWikiTitleCodec::splitTitleString() in PHP
- rUnicodeBidi = /[\u200E\u200F\u202A-\u202E]/g,
+ rUnicodeBidi = /[\u200E\u200F\u202A-\u202E]+/g,
/**
* Slightly modified from Flinfo. Credit goes to Lupo and Flominator.
},
// URL encoding (possibly)
{
- pattern: /%([0-9A-Fa-f]{2})/g,
+ pattern: /%([\dA-Fa-f]{2})/g,
replace: '% $1',
generalRule: true
},
// HTML-character-entities
{
- pattern: /&(([A-Za-z0-9\x80-\xff]+|#[0-9]+|#x[0-9A-Fa-f]+);)/g,
+ pattern: /&(([\dA-Za-z\x80-\xff]+|#\d+|#x[\dA-Fa-f]+);)/g,
replace: '& $1',
generalRule: true
},
* @return {Object|boolean}
*/
parse = function ( title, defaultNamespace ) {
- var namespace, m, id, i, fragment, ext;
+ var namespace, m, id, i, fragment;
namespace = defaultNamespace === undefined ? NS_MAIN : defaultNamespace;
}
// Reject illegal characters
- if ( title.match( rInvalid ) ) {
+ if ( rInvalid.test( title ) ) {
return false;
}
return false;
}
- // For backwards-compatibility with old mw.Title, we separate the extension from the
- // rest of the title.
- i = title.lastIndexOf( '.' );
- if ( i === -1 || title.length <= i + 1 ) {
- // Extensions are the non-empty segment after the last dot
- ext = null;
- } else {
- ext = title.slice( i + 1 );
- title = title.slice( 0, i );
- }
-
return {
namespace: namespace,
title: title,
- ext: ext,
fragment: fragment
};
},
this.namespace = parsed.namespace;
this.title = parsed.title;
- this.ext = parsed.ext;
this.fragment = parsed.fragment;
}
t = Object.create( Title.prototype );
t.namespace = parsed.namespace;
t.title = parsed.title;
- t.ext = parsed.ext;
t.fragment = parsed.fragment;
return t;
* @return {mw.Title|null} A valid Title object or null if the title is invalid
*/
Title.newFromFileName = function ( uncleanName ) {
-
return Title.newFromUserInput( 'File:' + uncleanName, {
forUploading: true
} );
thumbPhpRegex = /thumb\.php/,
regexes = [
// Thumbnails
- /\/[a-f0-9]\/[a-f0-9]{2}\/([^\s/]+)\/[^\s/]+-[^\s/]*$/,
+ /\/[\da-f]\/[\da-f]{2}\/([^\s/]+)\/[^\s/]+-[^\s/]*$/,
// Full size images
- /\/[a-f0-9]\/[a-f0-9]{2}\/([^\s/]+)$/,
+ /\/[\da-f]\/[\da-f]{2}\/([^\s/]+)$/,
// Thumbnails in non-hashed upload directories
/\/([^\s/]+)\/[^\s/]+-(?:\1|thumbnail)[^\s/]*$/,
src = img.jquery ? img[ 0 ].src : img.src;
- matches = src.match( thumbPhpRegex );
-
- if ( matches ) {
+ if ( thumbPhpRegex.test( src ) ) {
return mw.Title.newFromText( 'File:' + mw.util.getParamValue( 'f', src ) );
}
Title.normalizeExtension = function ( extension ) {
var
lower = extension.toLowerCase(),
- squish = {
+ normalizations = {
htm: 'html',
jpeg: 'jpg',
mpeg: 'mpg',
tiff: 'tif',
ogv: 'ogg'
};
- if ( Object.prototype.hasOwnProperty.call( squish, lower ) ) {
- return squish[ lower ];
- } else if ( /^[0-9a-z]+$/.test( lower ) ) {
+ if ( Object.hasOwnProperty.call( normalizations, lower ) ) {
+ return normalizations[ lower ];
+ } else if ( /^[\da-z]+$/.test( lower ) ) {
return lower;
} else {
return '';
* @return {string}
*/
getName: function () {
- if (
- mw.config.get( 'wgCaseSensitiveNamespaces' ).indexOf( this.namespace ) !== -1 ||
- !this.title.length
- ) {
- return this.title;
+ var ext = this.getExtension();
+ if ( ext === null ) {
+ return this.getMain();
}
- return mw.Title.phpCharToUpper( this.title[ 0 ] ) + this.title.slice( 1 );
+ return this.getMain().slice( 0, -ext.length - 1 );
},
/**
* @return {string|null} Name extension or null if there is none
*/
getExtension: function () {
- return this.ext;
+ var lastDot = this.title.lastIndexOf( '.' );
+ if ( lastDot === -1 ) {
+ return null;
+ }
+ return this.title.slice( lastDot + 1 ) || null;
},
/**
* @return {string}
*/
getDotExtension: function () {
- return this.ext === null ? '' : '.' + this.ext;
+ var ext = this.getExtension();
+ return ext === null ? '' : '.' + ext;
},
/**
* @return {string}
*/
getMain: function () {
- return this.getName() + this.getDotExtension();
+ if (
+ mw.config.get( 'wgCaseSensitiveNamespaces' ).indexOf( this.namespace ) !== -1 ||
+ !this.title.length
+ ) {
+ return this.title;
+ }
+ return mw.Title.phpCharToUpper( this.title[ 0 ] ) + this.title.slice( 1 );
},
/**
{
"ß": "ß",
"ʼn": "ʼn",
+ "ƀ": "ƀ",
+ "ƚ": "ƚ",
"Dž": "Dž",
"dž": "Dž",
"Lj": "Lj",
"ǰ": "ǰ",
"Dz": "Dz",
"dz": "Dz",
- "ʝ": "Ʝ",
+ "ȼ": "ȼ",
+ "ȿ": "ȿ",
+ "ɀ": "ɀ",
+ "ɂ": "ɂ",
+ "ɇ": "ɇ",
+ "ɉ": "ɉ",
+ "ɋ": "ɋ",
+ "ɍ": "ɍ",
+ "ɏ": "ɏ",
+ "ɐ": "ɐ",
+ "ɑ": "ɑ",
+ "ɒ": "ɒ",
+ "ɜ": "ɜ",
+ "ɡ": "ɡ",
+ "ɥ": "ɥ",
+ "ɦ": "ɦ",
+ "ɪ": "ɪ",
+ "ɫ": "ɫ",
+ "ɬ": "ɬ",
+ "ɱ": "ɱ",
+ "ɽ": "ɽ",
+ "ʂ": "ʂ",
+ "ʇ": "ʇ",
+ "ʉ": "ʉ",
+ "ʌ": "ʌ",
+ "ʝ": "ʝ",
+ "ʞ": "ʞ",
"ͅ": "ͅ",
+ "ͱ": "ͱ",
+ "ͳ": "ͳ",
+ "ͷ": "ͷ",
+ "ͻ": "ͻ",
+ "ͼ": "ͼ",
+ "ͽ": "ͽ",
"ΐ": "ΐ",
"ΰ": "ΰ",
+ "ϗ": "ϗ",
+ "ϲ": "Σ",
+ "ϳ": "ϳ",
+ "ϸ": "ϸ",
+ "ϻ": "ϻ",
+ "ӏ": "ӏ",
+ "ӷ": "ӷ",
+ "ӻ": "ӻ",
+ "ӽ": "ӽ",
+ "ӿ": "ӿ",
+ "ԑ": "ԑ",
+ "ԓ": "ԓ",
+ "ԕ": "ԕ",
+ "ԗ": "ԗ",
+ "ԙ": "ԙ",
+ "ԛ": "ԛ",
+ "ԝ": "ԝ",
+ "ԟ": "ԟ",
+ "ԡ": "ԡ",
+ "ԣ": "ԣ",
+ "ԥ": "ԥ",
+ "ԧ": "ԧ",
+ "ԩ": "ԩ",
+ "ԫ": "ԫ",
+ "ԭ": "ԭ",
+ "ԯ": "ԯ",
"և": "և",
- "ᏸ": "Ᏸ",
- "ᏹ": "Ᏹ",
- "ᏺ": "Ᏺ",
- "ᏻ": "Ᏻ",
- "ᏼ": "Ᏼ",
- "ᏽ": "Ᏽ",
+ "ა": "ა",
+ "ბ": "ბ",
+ "გ": "გ",
+ "დ": "დ",
+ "ე": "ე",
+ "ვ": "ვ",
+ "ზ": "ზ",
+ "თ": "თ",
+ "ი": "ი",
+ "კ": "კ",
+ "ლ": "ლ",
+ "მ": "მ",
+ "ნ": "ნ",
+ "ო": "ო",
+ "პ": "პ",
+ "ჟ": "ჟ",
+ "რ": "რ",
+ "ს": "ს",
+ "ტ": "ტ",
+ "უ": "უ",
+ "ფ": "ფ",
+ "ქ": "ქ",
+ "ღ": "ღ",
+ "ყ": "ყ",
+ "შ": "შ",
+ "ჩ": "ჩ",
+ "ც": "ც",
+ "ძ": "ძ",
+ "წ": "წ",
+ "ჭ": "ჭ",
+ "ხ": "ხ",
+ "ჯ": "ჯ",
+ "ჰ": "ჰ",
+ "ჱ": "ჱ",
+ "ჲ": "ჲ",
+ "ჳ": "ჳ",
+ "ჴ": "ჴ",
+ "ჵ": "ჵ",
+ "ჶ": "ჶ",
+ "ჷ": "ჷ",
+ "ჸ": "ჸ",
+ "ჹ": "ჹ",
+ "ჺ": "ჺ",
+ "ჽ": "ჽ",
+ "ჾ": "ჾ",
+ "ჿ": "ჿ",
+ "ᏸ": "ᏸ",
+ "ᏹ": "ᏹ",
+ "ᏺ": "ᏺ",
+ "ᏻ": "ᏻ",
+ "ᏼ": "ᏼ",
+ "ᏽ": "ᏽ",
+ "ᲀ": "ᲀ",
+ "ᲁ": "ᲁ",
+ "ᲂ": "ᲂ",
+ "ᲃ": "ᲃ",
+ "ᲄ": "ᲄ",
+ "ᲅ": "ᲅ",
+ "ᲆ": "ᲆ",
+ "ᲇ": "ᲇ",
+ "ᲈ": "ᲈ",
+ "ᵹ": "ᵹ",
+ "ᵽ": "ᵽ",
+ "ᶎ": "ᶎ",
"ẖ": "ẖ",
"ẗ": "ẗ",
"ẘ": "ẘ",
"ẙ": "ẙ",
"ẚ": "ẚ",
+ "ỻ": "ỻ",
+ "ỽ": "ỽ",
+ "ỿ": "ỿ",
"ὐ": "ὐ",
"ὒ": "ὒ",
"ὔ": "ὔ",
"ῶ": "ῶ",
"ῷ": "ῷ",
"ῼ": "ῼ",
+ "ⅎ": "ⅎ",
"ⅰ": "ⅰ",
"ⅱ": "ⅱ",
"ⅲ": "ⅲ",
"ⅽ": "ⅽ",
"ⅾ": "ⅾ",
"ⅿ": "ⅿ",
+ "ↄ": "ↄ",
"ⓐ": "ⓐ",
"ⓑ": "ⓑ",
"ⓒ": "ⓒ",
"ⓧ": "ⓧ",
"ⓨ": "ⓨ",
"ⓩ": "ⓩ",
- "ꞵ": "Ꞵ",
- "ꞷ": "Ꞷ",
- "ꭓ": "Ꭓ",
- "ꭰ": "Ꭰ",
- "ꭱ": "Ꭱ",
- "ꭲ": "Ꭲ",
- "ꭳ": "Ꭳ",
- "ꭴ": "Ꭴ",
- "ꭵ": "Ꭵ",
- "ꭶ": "Ꭶ",
- "ꭷ": "Ꭷ",
- "ꭸ": "Ꭸ",
- "ꭹ": "Ꭹ",
- "ꭺ": "Ꭺ",
- "ꭻ": "Ꭻ",
- "ꭼ": "Ꭼ",
- "ꭽ": "Ꭽ",
- "ꭾ": "Ꭾ",
- "ꭿ": "Ꭿ",
- "ꮀ": "Ꮀ",
- "ꮁ": "Ꮁ",
- "ꮂ": "Ꮂ",
- "ꮃ": "Ꮃ",
- "ꮄ": "Ꮄ",
- "ꮅ": "Ꮅ",
- "ꮆ": "Ꮆ",
- "ꮇ": "Ꮇ",
- "ꮈ": "Ꮈ",
- "ꮉ": "Ꮉ",
- "ꮊ": "Ꮊ",
- "ꮋ": "Ꮋ",
- "ꮌ": "Ꮌ",
- "ꮍ": "Ꮍ",
- "ꮎ": "Ꮎ",
- "ꮏ": "Ꮏ",
- "ꮐ": "Ꮐ",
- "ꮑ": "Ꮑ",
- "ꮒ": "Ꮒ",
- "ꮓ": "Ꮓ",
- "ꮔ": "Ꮔ",
- "ꮕ": "Ꮕ",
- "ꮖ": "Ꮖ",
- "ꮗ": "Ꮗ",
- "ꮘ": "Ꮘ",
- "ꮙ": "Ꮙ",
- "ꮚ": "Ꮚ",
- "ꮛ": "Ꮛ",
- "ꮜ": "Ꮜ",
- "ꮝ": "Ꮝ",
- "ꮞ": "Ꮞ",
- "ꮟ": "Ꮟ",
- "ꮠ": "Ꮠ",
- "ꮡ": "Ꮡ",
- "ꮢ": "Ꮢ",
- "ꮣ": "Ꮣ",
- "ꮤ": "Ꮤ",
- "ꮥ": "Ꮥ",
- "ꮦ": "Ꮦ",
- "ꮧ": "Ꮧ",
- "ꮨ": "Ꮨ",
- "ꮩ": "Ꮩ",
- "ꮪ": "Ꮪ",
- "ꮫ": "Ꮫ",
- "ꮬ": "Ꮬ",
- "ꮭ": "Ꮭ",
- "ꮮ": "Ꮮ",
- "ꮯ": "Ꮯ",
- "ꮰ": "Ꮰ",
- "ꮱ": "Ꮱ",
- "ꮲ": "Ꮲ",
- "ꮳ": "Ꮳ",
- "ꮴ": "Ꮴ",
- "ꮵ": "Ꮵ",
- "ꮶ": "Ꮶ",
- "ꮷ": "Ꮷ",
- "ꮸ": "Ꮸ",
- "ꮹ": "Ꮹ",
- "ꮺ": "Ꮺ",
- "ꮻ": "Ꮻ",
- "ꮼ": "Ꮼ",
- "ꮽ": "Ꮽ",
- "ꮾ": "Ꮾ",
- "ꮿ": "Ꮿ",
+ "ⰰ": "ⰰ",
+ "ⰱ": "ⰱ",
+ "ⰲ": "ⰲ",
+ "ⰳ": "ⰳ",
+ "ⰴ": "ⰴ",
+ "ⰵ": "ⰵ",
+ "ⰶ": "ⰶ",
+ "ⰷ": "ⰷ",
+ "ⰸ": "ⰸ",
+ "ⰹ": "ⰹ",
+ "ⰺ": "ⰺ",
+ "ⰻ": "ⰻ",
+ "ⰼ": "ⰼ",
+ "ⰽ": "ⰽ",
+ "ⰾ": "ⰾ",
+ "ⰿ": "ⰿ",
+ "ⱀ": "ⱀ",
+ "ⱁ": "ⱁ",
+ "ⱂ": "ⱂ",
+ "ⱃ": "ⱃ",
+ "ⱄ": "ⱄ",
+ "ⱅ": "ⱅ",
+ "ⱆ": "ⱆ",
+ "ⱇ": "ⱇ",
+ "ⱈ": "ⱈ",
+ "ⱉ": "ⱉ",
+ "ⱊ": "ⱊ",
+ "ⱋ": "ⱋ",
+ "ⱌ": "ⱌ",
+ "ⱍ": "ⱍ",
+ "ⱎ": "ⱎ",
+ "ⱏ": "ⱏ",
+ "ⱐ": "ⱐ",
+ "ⱑ": "ⱑ",
+ "ⱒ": "ⱒ",
+ "ⱓ": "ⱓ",
+ "ⱔ": "ⱔ",
+ "ⱕ": "ⱕ",
+ "ⱖ": "ⱖ",
+ "ⱗ": "ⱗ",
+ "ⱘ": "ⱘ",
+ "ⱙ": "ⱙ",
+ "ⱚ": "ⱚ",
+ "ⱛ": "ⱛ",
+ "ⱜ": "ⱜ",
+ "ⱝ": "ⱝ",
+ "ⱞ": "ⱞ",
+ "ⱡ": "ⱡ",
+ "ⱥ": "ⱥ",
+ "ⱦ": "ⱦ",
+ "ⱨ": "ⱨ",
+ "ⱪ": "ⱪ",
+ "ⱬ": "ⱬ",
+ "ⱳ": "ⱳ",
+ "ⱶ": "ⱶ",
+ "ⲁ": "ⲁ",
+ "ⲃ": "ⲃ",
+ "ⲅ": "ⲅ",
+ "ⲇ": "ⲇ",
+ "ⲉ": "ⲉ",
+ "ⲋ": "ⲋ",
+ "ⲍ": "ⲍ",
+ "ⲏ": "ⲏ",
+ "ⲑ": "ⲑ",
+ "ⲓ": "ⲓ",
+ "ⲕ": "ⲕ",
+ "ⲗ": "ⲗ",
+ "ⲙ": "ⲙ",
+ "ⲛ": "ⲛ",
+ "ⲝ": "ⲝ",
+ "ⲟ": "ⲟ",
+ "ⲡ": "ⲡ",
+ "ⲣ": "ⲣ",
+ "ⲥ": "ⲥ",
+ "ⲧ": "ⲧ",
+ "ⲩ": "ⲩ",
+ "ⲫ": "ⲫ",
+ "ⲭ": "ⲭ",
+ "ⲯ": "ⲯ",
+ "ⲱ": "ⲱ",
+ "ⲳ": "ⲳ",
+ "ⲵ": "ⲵ",
+ "ⲷ": "ⲷ",
+ "ⲹ": "ⲹ",
+ "ⲻ": "ⲻ",
+ "ⲽ": "ⲽ",
+ "ⲿ": "ⲿ",
+ "ⳁ": "ⳁ",
+ "ⳃ": "ⳃ",
+ "ⳅ": "ⳅ",
+ "ⳇ": "ⳇ",
+ "ⳉ": "ⳉ",
+ "ⳋ": "ⳋ",
+ "ⳍ": "ⳍ",
+ "ⳏ": "ⳏ",
+ "ⳑ": "ⳑ",
+ "ⳓ": "ⳓ",
+ "ⳕ": "ⳕ",
+ "ⳗ": "ⳗ",
+ "ⳙ": "ⳙ",
+ "ⳛ": "ⳛ",
+ "ⳝ": "ⳝ",
+ "ⳟ": "ⳟ",
+ "ⳡ": "ⳡ",
+ "ⳣ": "ⳣ",
+ "ⳬ": "ⳬ",
+ "ⳮ": "ⳮ",
+ "ⳳ": "ⳳ",
+ "ⴀ": "ⴀ",
+ "ⴁ": "ⴁ",
+ "ⴂ": "ⴂ",
+ "ⴃ": "ⴃ",
+ "ⴄ": "ⴄ",
+ "ⴅ": "ⴅ",
+ "ⴆ": "ⴆ",
+ "ⴇ": "ⴇ",
+ "ⴈ": "ⴈ",
+ "ⴉ": "ⴉ",
+ "ⴊ": "ⴊ",
+ "ⴋ": "ⴋ",
+ "ⴌ": "ⴌ",
+ "ⴍ": "ⴍ",
+ "ⴎ": "ⴎ",
+ "ⴏ": "ⴏ",
+ "ⴐ": "ⴐ",
+ "ⴑ": "ⴑ",
+ "ⴒ": "ⴒ",
+ "ⴓ": "ⴓ",
+ "ⴔ": "ⴔ",
+ "ⴕ": "ⴕ",
+ "ⴖ": "ⴖ",
+ "ⴗ": "ⴗ",
+ "ⴘ": "ⴘ",
+ "ⴙ": "ⴙ",
+ "ⴚ": "ⴚ",
+ "ⴛ": "ⴛ",
+ "ⴜ": "ⴜ",
+ "ⴝ": "ⴝ",
+ "ⴞ": "ⴞ",
+ "ⴟ": "ⴟ",
+ "ⴠ": "ⴠ",
+ "ⴡ": "ⴡ",
+ "ⴢ": "ⴢ",
+ "ⴣ": "ⴣ",
+ "ⴤ": "ⴤ",
+ "ⴥ": "ⴥ",
+ "ⴧ": "ⴧ",
+ "ⴭ": "ⴭ",
+ "ꙁ": "ꙁ",
+ "ꙃ": "ꙃ",
+ "ꙅ": "ꙅ",
+ "ꙇ": "ꙇ",
+ "ꙉ": "ꙉ",
+ "ꙋ": "ꙋ",
+ "ꙍ": "ꙍ",
+ "ꙏ": "ꙏ",
+ "ꙑ": "ꙑ",
+ "ꙓ": "ꙓ",
+ "ꙕ": "ꙕ",
+ "ꙗ": "ꙗ",
+ "ꙙ": "ꙙ",
+ "ꙛ": "ꙛ",
+ "ꙝ": "ꙝ",
+ "ꙟ": "ꙟ",
+ "ꙡ": "ꙡ",
+ "ꙣ": "ꙣ",
+ "ꙥ": "ꙥ",
+ "ꙧ": "ꙧ",
+ "ꙩ": "ꙩ",
+ "ꙫ": "ꙫ",
+ "ꙭ": "ꙭ",
+ "ꚁ": "ꚁ",
+ "ꚃ": "ꚃ",
+ "ꚅ": "ꚅ",
+ "ꚇ": "ꚇ",
+ "ꚉ": "ꚉ",
+ "ꚋ": "ꚋ",
+ "ꚍ": "ꚍ",
+ "ꚏ": "ꚏ",
+ "ꚑ": "ꚑ",
+ "ꚓ": "ꚓ",
+ "ꚕ": "ꚕ",
+ "ꚗ": "ꚗ",
+ "ꚙ": "ꚙ",
+ "ꚛ": "ꚛ",
+ "ꜣ": "ꜣ",
+ "ꜥ": "ꜥ",
+ "ꜧ": "ꜧ",
+ "ꜩ": "ꜩ",
+ "ꜫ": "ꜫ",
+ "ꜭ": "ꜭ",
+ "ꜯ": "ꜯ",
+ "ꜳ": "ꜳ",
+ "ꜵ": "ꜵ",
+ "ꜷ": "ꜷ",
+ "ꜹ": "ꜹ",
+ "ꜻ": "ꜻ",
+ "ꜽ": "ꜽ",
+ "ꜿ": "ꜿ",
+ "ꝁ": "ꝁ",
+ "ꝃ": "ꝃ",
+ "ꝅ": "ꝅ",
+ "ꝇ": "ꝇ",
+ "ꝉ": "ꝉ",
+ "ꝋ": "ꝋ",
+ "ꝍ": "ꝍ",
+ "ꝏ": "ꝏ",
+ "ꝑ": "ꝑ",
+ "ꝓ": "ꝓ",
+ "ꝕ": "ꝕ",
+ "ꝗ": "ꝗ",
+ "ꝙ": "ꝙ",
+ "ꝛ": "ꝛ",
+ "ꝝ": "ꝝ",
+ "ꝟ": "ꝟ",
+ "ꝡ": "ꝡ",
+ "ꝣ": "ꝣ",
+ "ꝥ": "ꝥ",
+ "ꝧ": "ꝧ",
+ "ꝩ": "ꝩ",
+ "ꝫ": "ꝫ",
+ "ꝭ": "ꝭ",
+ "ꝯ": "ꝯ",
+ "ꝺ": "ꝺ",
+ "ꝼ": "ꝼ",
+ "ꝿ": "ꝿ",
+ "ꞁ": "ꞁ",
+ "ꞃ": "ꞃ",
+ "ꞅ": "ꞅ",
+ "ꞇ": "ꞇ",
+ "ꞌ": "ꞌ",
+ "ꞑ": "ꞑ",
+ "ꞓ": "ꞓ",
+ "ꞔ": "ꞔ",
+ "ꞗ": "ꞗ",
+ "ꞙ": "ꞙ",
+ "ꞛ": "ꞛ",
+ "ꞝ": "ꞝ",
+ "ꞟ": "ꞟ",
+ "ꞡ": "ꞡ",
+ "ꞣ": "ꞣ",
+ "ꞥ": "ꞥ",
+ "ꞧ": "ꞧ",
+ "ꞩ": "ꞩ",
+ "ꞵ": "ꞵ",
+ "ꞷ": "ꞷ",
+ "ꞹ": "ꞹ",
+ "ꞻ": "ꞻ",
+ "ꞽ": "ꞽ",
+ "ꞿ": "ꞿ",
+ "ꟃ": "ꟃ",
+ "ꭓ": "ꭓ",
+ "ꭰ": "ꭰ",
+ "ꭱ": "ꭱ",
+ "ꭲ": "ꭲ",
+ "ꭳ": "ꭳ",
+ "ꭴ": "ꭴ",
+ "ꭵ": "ꭵ",
+ "ꭶ": "ꭶ",
+ "ꭷ": "ꭷ",
+ "ꭸ": "ꭸ",
+ "ꭹ": "ꭹ",
+ "ꭺ": "ꭺ",
+ "ꭻ": "ꭻ",
+ "ꭼ": "ꭼ",
+ "ꭽ": "ꭽ",
+ "ꭾ": "ꭾ",
+ "ꭿ": "ꭿ",
+ "ꮀ": "ꮀ",
+ "ꮁ": "ꮁ",
+ "ꮂ": "ꮂ",
+ "ꮃ": "ꮃ",
+ "ꮄ": "ꮄ",
+ "ꮅ": "ꮅ",
+ "ꮆ": "ꮆ",
+ "ꮇ": "ꮇ",
+ "ꮈ": "ꮈ",
+ "ꮉ": "ꮉ",
+ "ꮊ": "ꮊ",
+ "ꮋ": "ꮋ",
+ "ꮌ": "ꮌ",
+ "ꮍ": "ꮍ",
+ "ꮎ": "ꮎ",
+ "ꮏ": "ꮏ",
+ "ꮐ": "ꮐ",
+ "ꮑ": "ꮑ",
+ "ꮒ": "ꮒ",
+ "ꮓ": "ꮓ",
+ "ꮔ": "ꮔ",
+ "ꮕ": "ꮕ",
+ "ꮖ": "ꮖ",
+ "ꮗ": "ꮗ",
+ "ꮘ": "ꮘ",
+ "ꮙ": "ꮙ",
+ "ꮚ": "ꮚ",
+ "ꮛ": "ꮛ",
+ "ꮜ": "ꮜ",
+ "ꮝ": "ꮝ",
+ "ꮞ": "ꮞ",
+ "ꮟ": "ꮟ",
+ "ꮠ": "ꮠ",
+ "ꮡ": "ꮡ",
+ "ꮢ": "ꮢ",
+ "ꮣ": "ꮣ",
+ "ꮤ": "ꮤ",
+ "ꮥ": "ꮥ",
+ "ꮦ": "ꮦ",
+ "ꮧ": "ꮧ",
+ "ꮨ": "ꮨ",
+ "ꮩ": "ꮩ",
+ "ꮪ": "ꮪ",
+ "ꮫ": "ꮫ",
+ "ꮬ": "ꮬ",
+ "ꮭ": "ꮭ",
+ "ꮮ": "ꮮ",
+ "ꮯ": "ꮯ",
+ "ꮰ": "ꮰ",
+ "ꮱ": "ꮱ",
+ "ꮲ": "ꮲ",
+ "ꮳ": "ꮳ",
+ "ꮴ": "ꮴ",
+ "ꮵ": "ꮵ",
+ "ꮶ": "ꮶ",
+ "ꮷ": "ꮷ",
+ "ꮸ": "ꮸ",
+ "ꮹ": "ꮹ",
+ "ꮺ": "ꮺ",
+ "ꮻ": "ꮻ",
+ "ꮼ": "ꮼ",
+ "ꮽ": "ꮽ",
+ "ꮾ": "ꮾ",
+ "ꮿ": "ꮿ",
"ff": "ff",
"fi": "fi",
"fl": "fl",
"ﬔ": "ﬔ",
"ﬕ": "ﬕ",
"ﬖ": "ﬖ",
- "ﬗ": "ﬗ"
+ "ﬗ": "ﬗ",
+ "𐑎": "𐑎",
+ "𐑏": "𐑏",
+ "𐓘": "𐓘",
+ "𐓙": "𐓙",
+ "𐓚": "𐓚",
+ "𐓛": "𐓛",
+ "𐓜": "𐓜",
+ "𐓝": "𐓝",
+ "𐓞": "𐓞",
+ "𐓟": "𐓟",
+ "𐓠": "𐓠",
+ "𐓡": "𐓡",
+ "𐓢": "𐓢",
+ "𐓣": "𐓣",
+ "𐓤": "𐓤",
+ "𐓥": "𐓥",
+ "𐓦": "𐓦",
+ "𐓧": "𐓧",
+ "𐓨": "𐓨",
+ "𐓩": "𐓩",
+ "𐓪": "𐓪",
+ "𐓫": "𐓫",
+ "𐓬": "𐓬",
+ "𐓭": "𐓭",
+ "𐓮": "𐓮",
+ "𐓯": "𐓯",
+ "𐓰": "𐓰",
+ "𐓱": "𐓱",
+ "𐓲": "𐓲",
+ "𐓳": "𐓳",
+ "𐓴": "𐓴",
+ "𐓵": "𐓵",
+ "𐓶": "𐓶",
+ "𐓷": "𐓷",
+ "𐓸": "𐓸",
+ "𐓹": "𐓹",
+ "𐓺": "𐓺",
+ "𐓻": "𐓻",
+ "𐳀": "𐳀",
+ "𐳁": "𐳁",
+ "𐳂": "𐳂",
+ "𐳃": "𐳃",
+ "𐳄": "𐳄",
+ "𐳅": "𐳅",
+ "𐳆": "𐳆",
+ "𐳇": "𐳇",
+ "𐳈": "𐳈",
+ "𐳉": "𐳉",
+ "𐳊": "𐳊",
+ "𐳋": "𐳋",
+ "𐳌": "𐳌",
+ "𐳍": "𐳍",
+ "𐳎": "𐳎",
+ "𐳏": "𐳏",
+ "𐳐": "𐳐",
+ "𐳑": "𐳑",
+ "𐳒": "𐳒",
+ "𐳓": "𐳓",
+ "𐳔": "𐳔",
+ "𐳕": "𐳕",
+ "𐳖": "𐳖",
+ "𐳗": "𐳗",
+ "𐳘": "𐳘",
+ "𐳙": "𐳙",
+ "𐳚": "𐳚",
+ "𐳛": "𐳛",
+ "𐳜": "𐳜",
+ "𐳝": "𐳝",
+ "𐳞": "𐳞",
+ "𐳟": "𐳟",
+ "𐳠": "𐳠",
+ "𐳡": "𐳡",
+ "𐳢": "𐳢",
+ "𐳣": "𐳣",
+ "𐳤": "𐳤",
+ "𐳥": "𐳥",
+ "𐳦": "𐳦",
+ "𐳧": "𐳧",
+ "𐳨": "𐳨",
+ "𐳩": "𐳩",
+ "𐳪": "𐳪",
+ "𐳫": "𐳫",
+ "𐳬": "𐳬",
+ "𐳭": "𐳭",
+ "𐳮": "𐳮",
+ "𐳯": "𐳯",
+ "𐳰": "𐳰",
+ "𐳱": "𐳱",
+ "𐳲": "𐳲",
+ "𑣀": "𑣀",
+ "𑣁": "𑣁",
+ "𑣂": "𑣂",
+ "𑣃": "𑣃",
+ "𑣄": "𑣄",
+ "𑣅": "𑣅",
+ "𑣆": "𑣆",
+ "𑣇": "𑣇",
+ "𑣈": "𑣈",
+ "𑣉": "𑣉",
+ "𑣊": "𑣊",
+ "𑣋": "𑣋",
+ "𑣌": "𑣌",
+ "𑣍": "𑣍",
+ "𑣎": "𑣎",
+ "𑣏": "𑣏",
+ "𑣐": "𑣐",
+ "𑣑": "𑣑",
+ "𑣒": "𑣒",
+ "𑣓": "𑣓",
+ "𑣔": "𑣔",
+ "𑣕": "𑣕",
+ "𑣖": "𑣖",
+ "𑣗": "𑣗",
+ "𑣘": "𑣘",
+ "𑣙": "𑣙",
+ "𑣚": "𑣚",
+ "𑣛": "𑣛",
+ "𑣜": "𑣜",
+ "𑣝": "𑣝",
+ "𑣞": "𑣞",
+ "𑣟": "𑣟",
+ "𖹠": "𖹠",
+ "𖹡": "𖹡",
+ "𖹢": "𖹢",
+ "𖹣": "𖹣",
+ "𖹤": "𖹤",
+ "𖹥": "𖹥",
+ "𖹦": "𖹦",
+ "𖹧": "𖹧",
+ "𖹨": "𖹨",
+ "𖹩": "𖹩",
+ "𖹪": "𖹪",
+ "𖹫": "𖹫",
+ "𖹬": "𖹬",
+ "𖹭": "𖹭",
+ "𖹮": "𖹮",
+ "𖹯": "𖹯",
+ "𖹰": "𖹰",
+ "𖹱": "𖹱",
+ "𖹲": "𖹲",
+ "𖹳": "𖹳",
+ "𖹴": "𖹴",
+ "𖹵": "𖹵",
+ "𖹶": "𖹶",
+ "𖹷": "𖹷",
+ "𖹸": "𖹸",
+ "𖹹": "𖹹",
+ "𖹺": "𖹺",
+ "𖹻": "𖹻",
+ "𖹼": "𖹼",
+ "𖹽": "𖹽",
+ "𖹾": "𖹾",
+ "𖹿": "𖹿",
+ "𞤢": "𞤢",
+ "𞤣": "𞤣",
+ "𞤤": "𞤤",
+ "𞤥": "𞤥",
+ "𞤦": "𞤦",
+ "𞤧": "𞤧",
+ "𞤨": "𞤨",
+ "𞤩": "𞤩",
+ "𞤪": "𞤪",
+ "𞤫": "𞤫",
+ "𞤬": "𞤬",
+ "𞤭": "𞤭",
+ "𞤮": "𞤮",
+ "𞤯": "𞤯",
+ "𞤰": "𞤰",
+ "𞤱": "𞤱",
+ "𞤲": "𞤲",
+ "𞤳": "𞤳",
+ "𞤴": "𞤴",
+ "𞤵": "𞤵",
+ "𞤶": "𞤶",
+ "𞤷": "𞤷",
+ "𞤸": "𞤸",
+ "𞤹": "𞤹",
+ "𞤺": "𞤺",
+ "𞤻": "𞤻",
+ "𞤼": "𞤼",
+ "𞤽": "𞤽",
+ "𞤾": "𞤾",
+ "𞤿": "𞤿",
+ "𞥀": "𞥀",
+ "𞥁": "𞥁",
+ "𞥂": "𞥂",
+ "𞥃": "𞥃"
}
* @return {string} Encoded string
*/
function rawurlencode( str ) {
- str = String( str );
- return encodeURIComponent( str )
+ return encodeURIComponent( String( str ) )
.replace( /!/g, '%21' ).replace( /'/g, '%27' ).replace( /\(/g, '%28' )
.replace( /\)/g, '%29' ).replace( /\*/g, '%2A' ).replace( /~/g, '%7E' );
}
rawurlencode: rawurlencode,
/**
- * Encode string into HTML id compatible form suitable for use in HTML
- * Analog to PHP Sanitizer::escapeIdForAttribute()
+ * Encode a string as CSS id, for use as HTML id attribute value.
*
- * @since 1.30
+ * Analog to `Sanitizer::escapeIdForAttribute()` in PHP.
*
+ * @since 1.30
* @param {string} str String to encode
* @return {string} Encoded string
*/
escapeIdForAttribute: function ( str ) {
- var mode = config.FragmentMode[ 0 ];
-
- return escapeIdInternal( str, mode );
+ return escapeIdInternal( str, config.FragmentMode[ 0 ] );
},
/**
- * Encode string into HTML id compatible form suitable for use in links
- * Analog to PHP Sanitizer::escapeIdForLink()
+ * Encode a string as URL fragment, for use as HTML anchor link.
*
- * @since 1.30
+ * Analog to `Sanitizer::escapeIdForLink()` in PHP.
*
+ * @since 1.30
* @param {string} str String to encode
* @return {string} Encoded string
*/
escapeIdForLink: function ( str ) {
- var mode = config.FragmentMode[ 0 ];
-
- return escapeIdInternal( str, mode );
+ return escapeIdInternal( str, config.FragmentMode[ 0 ] );
},
/**
* @return {string} Url of the page with name of `pageName`
*/
getUrl: function ( pageName, params ) {
- var titleFragmentStart, url, query,
- fragment = '',
+ var fragmentIdx, url, query, fragment,
title = typeof pageName === 'string' ? pageName : mw.config.get( 'wgPageName' );
// Find any fragment
- titleFragmentStart = title.indexOf( '#' );
- if ( titleFragmentStart !== -1 ) {
- fragment = title.slice( titleFragmentStart + 1 );
+ fragmentIdx = title.indexOf( '#' );
+ if ( fragmentIdx !== -1 ) {
+ fragment = title.slice( fragmentIdx + 1 );
// Exclude the fragment from the page name
- title = title.slice( 0, titleFragmentStart );
+ title = title.slice( 0, fragmentIdx );
}
// Produce query string
}
// Append the encoded fragment
- if ( fragment.length ) {
+ if ( fragment && fragment.length ) {
url += '#' + util.escapeIdForLink( fragment );
}
},
/**
- * Get address to a script in the wiki root.
- * For index.php use `mw.config.get( 'wgScript' )`.
+ * Get URL to a MediaWiki entry point.
*
* @since 1.18
- * @param {string} str Name of script (e.g. 'api'), defaults to 'index'
- * @return {string} Address to script (e.g. '/w/api.php' )
+ * @param {string} [str="index"] Name of MW entry point (e.g. 'index' or 'api')
+ * @return {string} URL to the script file (e.g. '/w/api.php' )
*/
wikiScript: function ( str ) {
- str = str || 'index';
- if ( str === 'index' ) {
+ if ( !str || str === 'index' ) {
return mw.config.get( 'wgScript' );
} else if ( str === 'load' ) {
return config.LoadScript;
*/
addCSS: function ( text ) {
var s = mw.loader.addStyleTag( text );
- return s.sheet || s.styleSheet || s;
+ return s.sheet;
},
/**
* @return {boolean}
*/
isIPv4Address: function ( address, allowBlock ) {
- var block, RE_IP_BYTE, RE_IP_ADD;
+ var block,
+ RE_IP_BYTE = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])',
+ RE_IP_ADD = '(?:' + RE_IP_BYTE + '\\.){3}' + RE_IP_BYTE;
if ( typeof address !== 'string' ) {
return false;
}
block = allowBlock ? '(?:\\/(?:3[0-2]|[12]?\\d))?' : '';
- RE_IP_BYTE = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])';
- RE_IP_ADD = '(?:' + RE_IP_BYTE + '\\.){3}' + RE_IP_BYTE;
return ( new RegExp( '^' + RE_IP_ADD + block + '$' ).test( address ) );
},
}
};
+// Backwards-compatible alias for mediawiki.RegExp module.
+// @deprecated since 1.34
+mw.RegExp = {};
+mw.log.deprecate( mw.RegExp, 'escape', util.escapeRegExp, 'Use mw.util.escapeRegExp() instead.', 'mw.RegExp.escape' );
+
// Not allowed outside unit tests
if ( window.QUnit ) {
util.setOptionsForTest = function ( opts ) {
* @return array
*/
private function listTables() {
- global $wgActorTableSchemaMigrationStage;
-
$tables = [ 'user', 'user_properties', 'user_former_groups', 'page', 'page_restrictions',
'protected_titles', 'revision', 'ip_changes', 'text', 'pagelinks', 'imagelinks',
'categorylinks', 'templatelinks', 'externallinks', 'langlinks', 'iwlinks',
'querycache', 'objectcache', 'job', 'l10n_cache', 'redirect', 'querycachetwo',
'archive', 'user_groups', 'page_props', 'category',
'slots', 'content', 'slot_roles', 'content_models',
- 'comment', 'revision_comment_temp',
+ 'comment', 'revision_comment_temp', 'actor', 'revision_actor_temp',
];
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
- // The new tables for actors are in use
- $tables[] = 'actor';
- $tables[] = 'revision_actor_temp';
- }
-
if ( in_array( $this->db->getType(), [ 'mysql', 'sqlite' ] ) ) {
array_push( $tables, 'searchindex' );
}
<?php
use MediaWiki\User\UserIdentity;
-use MediaWiki\MediaWikiServices;
+use Wikimedia\ScopedCallback;
use Wikimedia\TestingAccessWrapper;
/**
*/
class ActorMigrationTest extends MediaWikiLangTestCase {
+ protected $resetActorMigration = null;
+ protected static $amId = 0;
+
protected $tablesUsed = [
- 'revision',
- 'revision_actor_temp',
- 'ipblocks',
- 'recentchanges',
'actor',
];
+ protected function setUp() {
+ parent::setUp();
+
+ $w = TestingAccessWrapper::newFromClass( ActorMigration::class );
+ $data = [
+ 'tempTables' => $w->tempTables,
+ 'formerTempTables' => $w->formerTempTables,
+ 'deprecated' => $w->deprecated,
+ 'removed' => $w->removed,
+ 'specialFields' => $w->specialFields,
+ ];
+ $this->resetActorMigration = new ScopedCallback( function ( $w, $data ) {
+ foreach ( $data as $k => $v ) {
+ $w->$k = $v;
+ }
+ }, [ $w, $data ] );
+
+ $w->tempTables = [
+ 'am2_user' => [
+ 'table' => 'actormigration2_temp',
+ 'pk' => 'am2t_id',
+ 'field' => 'am2t_actor',
+ 'joinPK' => 'am2_id',
+ 'extra' => [],
+ ]
+ ];
+ $w->specialFields = [
+ 'am3_xxx' => [ 'am3_xxx_text', 'am3_xxx_actor' ],
+ ];
+ }
+
+ protected function tearDown() {
+ parent::tearDown();
+ ScopedCallback::consume( $this->resetActorMigration );
+ }
+
+ protected function getSchemaOverrides( IMaintainableDatabase $db ) {
+ return [
+ 'scripts' => [
+ __DIR__ . '/ActorMigrationTest.sql',
+ ],
+ 'drop' => [],
+ 'create' => [ 'actormigration1', 'actormigration2', 'actormigration2_temp', 'actormigration3' ],
+ 'alter' => [],
+ ];
+ }
+
/**
* @dataProvider provideConstructor
* @param int $stage
public static function provideGetJoin() {
return [
'Simple table, old' => [
- SCHEMA_COMPAT_OLD, 'rc_user', [
+ SCHEMA_COMPAT_OLD, 'am1_user', [
'tables' => [],
'fields' => [
- 'rc_user' => 'rc_user',
- 'rc_user_text' => 'rc_user_text',
- 'rc_actor' => 'NULL',
+ 'am1_user' => 'am1_user',
+ 'am1_user_text' => 'am1_user_text',
+ 'am1_actor' => 'NULL',
],
'joins' => [],
],
],
'Simple table, read-old' => [
- SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rc_user', [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'am1_user', [
'tables' => [],
'fields' => [
- 'rc_user' => 'rc_user',
- 'rc_user_text' => 'rc_user_text',
- 'rc_actor' => 'NULL',
+ 'am1_user' => 'am1_user',
+ 'am1_user_text' => 'am1_user_text',
+ 'am1_actor' => 'NULL',
],
'joins' => [],
],
],
'Simple table, read-new' => [
- SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rc_user', [
- 'tables' => [ 'actor_rc_user' => 'actor' ],
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'am1_user', [
+ 'tables' => [ 'actor_am1_user' => 'actor' ],
'fields' => [
- 'rc_user' => 'actor_rc_user.actor_user',
- 'rc_user_text' => 'actor_rc_user.actor_name',
- 'rc_actor' => 'rc_actor',
+ 'am1_user' => 'actor_am1_user.actor_user',
+ 'am1_user_text' => 'actor_am1_user.actor_name',
+ 'am1_actor' => 'am1_actor',
],
'joins' => [
- 'actor_rc_user' => [ 'JOIN', 'actor_rc_user.actor_id = rc_actor' ],
+ 'actor_am1_user' => [ 'JOIN', 'actor_am1_user.actor_id = am1_actor' ],
],
],
],
'Simple table, new' => [
- SCHEMA_COMPAT_NEW, 'rc_user', [
- 'tables' => [ 'actor_rc_user' => 'actor' ],
+ SCHEMA_COMPAT_NEW, 'am1_user', [
+ 'tables' => [ 'actor_am1_user' => 'actor' ],
'fields' => [
- 'rc_user' => 'actor_rc_user.actor_user',
- 'rc_user_text' => 'actor_rc_user.actor_name',
- 'rc_actor' => 'rc_actor',
+ 'am1_user' => 'actor_am1_user.actor_user',
+ 'am1_user_text' => 'actor_am1_user.actor_name',
+ 'am1_actor' => 'am1_actor',
],
'joins' => [
- 'actor_rc_user' => [ 'JOIN', 'actor_rc_user.actor_id = rc_actor' ],
+ 'actor_am1_user' => [ 'JOIN', 'actor_am1_user.actor_id = am1_actor' ],
],
],
],
- 'ipblocks, old' => [
- SCHEMA_COMPAT_OLD, 'ipb_by', [
+ 'Special name, old' => [
+ SCHEMA_COMPAT_OLD, 'am3_xxx', [
'tables' => [],
'fields' => [
- 'ipb_by' => 'ipb_by',
- 'ipb_by_text' => 'ipb_by_text',
- 'ipb_by_actor' => 'NULL',
+ 'am3_xxx' => 'am3_xxx',
+ 'am3_xxx_text' => 'am3_xxx_text',
+ 'am3_xxx_actor' => 'NULL',
],
'joins' => [],
],
],
- 'ipblocks, read-old' => [
- SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'ipb_by', [
+ 'Special name, read-old' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'am3_xxx', [
'tables' => [],
'fields' => [
- 'ipb_by' => 'ipb_by',
- 'ipb_by_text' => 'ipb_by_text',
- 'ipb_by_actor' => 'NULL',
+ 'am3_xxx' => 'am3_xxx',
+ 'am3_xxx_text' => 'am3_xxx_text',
+ 'am3_xxx_actor' => 'NULL',
],
'joins' => [],
],
],
- 'ipblocks, read-new' => [
- SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'ipb_by', [
- 'tables' => [ 'actor_ipb_by' => 'actor' ],
+ 'Special name, read-new' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'am3_xxx', [
+ 'tables' => [ 'actor_am3_xxx' => 'actor' ],
'fields' => [
- 'ipb_by' => 'actor_ipb_by.actor_user',
- 'ipb_by_text' => 'actor_ipb_by.actor_name',
- 'ipb_by_actor' => 'ipb_by_actor',
+ 'am3_xxx' => 'actor_am3_xxx.actor_user',
+ 'am3_xxx_text' => 'actor_am3_xxx.actor_name',
+ 'am3_xxx_actor' => 'am3_xxx_actor',
],
'joins' => [
- 'actor_ipb_by' => [ 'JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
+ 'actor_am3_xxx' => [ 'JOIN', 'actor_am3_xxx.actor_id = am3_xxx_actor' ],
],
],
],
- 'ipblocks, new' => [
- SCHEMA_COMPAT_NEW, 'ipb_by', [
- 'tables' => [ 'actor_ipb_by' => 'actor' ],
+ 'Special name, new' => [
+ SCHEMA_COMPAT_NEW, 'am3_xxx', [
+ 'tables' => [ 'actor_am3_xxx' => 'actor' ],
'fields' => [
- 'ipb_by' => 'actor_ipb_by.actor_user',
- 'ipb_by_text' => 'actor_ipb_by.actor_name',
- 'ipb_by_actor' => 'ipb_by_actor',
+ 'am3_xxx' => 'actor_am3_xxx.actor_user',
+ 'am3_xxx_text' => 'actor_am3_xxx.actor_name',
+ 'am3_xxx_actor' => 'am3_xxx_actor',
],
'joins' => [
- 'actor_ipb_by' => [ 'JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
+ 'actor_am3_xxx' => [ 'JOIN', 'actor_am3_xxx.actor_id = am3_xxx_actor' ],
],
],
],
- 'Revision, old' => [
- SCHEMA_COMPAT_OLD, 'rev_user', [
+ 'Temp table, old' => [
+ SCHEMA_COMPAT_OLD, 'am2_user', [
'tables' => [],
'fields' => [
- 'rev_user' => 'rev_user',
- 'rev_user_text' => 'rev_user_text',
- 'rev_actor' => 'NULL',
+ 'am2_user' => 'am2_user',
+ 'am2_user_text' => 'am2_user_text',
+ 'am2_actor' => 'NULL',
],
'joins' => [],
],
],
- 'Revision, read-old' => [
- SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rev_user', [
+ 'Temp table, read-old' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'am2_user', [
'tables' => [],
'fields' => [
- 'rev_user' => 'rev_user',
- 'rev_user_text' => 'rev_user_text',
- 'rev_actor' => 'NULL',
+ 'am2_user' => 'am2_user',
+ 'am2_user_text' => 'am2_user_text',
+ 'am2_actor' => 'NULL',
],
'joins' => [],
],
],
- 'Revision, read-new' => [
- SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rev_user', [
+ 'Temp table, read-new' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'am2_user', [
'tables' => [
- 'temp_rev_user' => 'revision_actor_temp',
- 'actor_rev_user' => 'actor',
+ 'temp_am2_user' => 'actormigration2_temp',
+ 'actor_am2_user' => 'actor',
],
'fields' => [
- 'rev_user' => 'actor_rev_user.actor_user',
- 'rev_user_text' => 'actor_rev_user.actor_name',
- 'rev_actor' => 'temp_rev_user.revactor_actor',
+ 'am2_user' => 'actor_am2_user.actor_user',
+ 'am2_user_text' => 'actor_am2_user.actor_name',
+ 'am2_actor' => 'temp_am2_user.am2t_actor',
],
'joins' => [
- 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
- 'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
+ 'temp_am2_user' => [ 'JOIN', 'temp_am2_user.am2t_id = am2_id' ],
+ 'actor_am2_user' => [ 'JOIN', 'actor_am2_user.actor_id = temp_am2_user.am2t_actor' ],
],
],
],
- 'Revision, new' => [
- SCHEMA_COMPAT_NEW, 'rev_user', [
+ 'Temp table, new' => [
+ SCHEMA_COMPAT_NEW, 'am2_user', [
'tables' => [
- 'temp_rev_user' => 'revision_actor_temp',
- 'actor_rev_user' => 'actor',
+ 'temp_am2_user' => 'actormigration2_temp',
+ 'actor_am2_user' => 'actor',
],
'fields' => [
- 'rev_user' => 'actor_rev_user.actor_user',
- 'rev_user_text' => 'actor_rev_user.actor_name',
- 'rev_actor' => 'temp_rev_user.revactor_actor',
+ 'am2_user' => 'actor_am2_user.actor_user',
+ 'am2_user_text' => 'actor_am2_user.actor_name',
+ 'am2_actor' => 'temp_am2_user.am2t_actor',
],
'joins' => [
- 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
- 'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
+ 'temp_am2_user' => [ 'JOIN', 'temp_am2_user.am2t_id = am2_id' ],
+ 'actor_am2_user' => [ 'JOIN', 'actor_am2_user.actor_id = temp_am2_user.am2t_actor' ],
],
],
],
return [
'Simple table, old' => [
- SCHEMA_COMPAT_OLD, 'rc_user', $genericUser, true, [
+ SCHEMA_COMPAT_OLD, 'am1_user', $genericUser, true, [
'tables' => [],
- 'orconds' => [ 'userid' => "rc_user = '1'" ],
+ 'orconds' => [ 'userid' => "am1_user = '1'" ],
'joins' => [],
],
],
'Simple table, read-old' => [
- SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rc_user', $genericUser, true, [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'am1_user', $genericUser, true, [
'tables' => [],
- 'orconds' => [ 'userid' => "rc_user = '1'" ],
+ 'orconds' => [ 'userid' => "am1_user = '1'" ],
'joins' => [],
],
],
'Simple table, read-new' => [
- SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rc_user', $genericUser, true, [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'am1_user', $genericUser, true, [
'tables' => [],
- 'orconds' => [ 'actor' => "rc_actor = '11'" ],
+ 'orconds' => [ 'actor' => "am1_actor = '11'" ],
'joins' => [],
],
],
'Simple table, new' => [
- SCHEMA_COMPAT_NEW, 'rc_user', $genericUser, true, [
+ SCHEMA_COMPAT_NEW, 'am1_user', $genericUser, true, [
'tables' => [],
- 'orconds' => [ 'actor' => "rc_actor = '11'" ],
+ 'orconds' => [ 'actor' => "am1_actor = '11'" ],
'joins' => [],
],
],
- 'ipblocks, old' => [
- SCHEMA_COMPAT_OLD, 'ipb_by', $genericUser, true, [
+ 'Special name, old' => [
+ SCHEMA_COMPAT_OLD, 'am3_xxx', $genericUser, true, [
'tables' => [],
- 'orconds' => [ 'userid' => "ipb_by = '1'" ],
+ 'orconds' => [ 'userid' => "am3_xxx = '1'" ],
'joins' => [],
],
],
- 'ipblocks, read-old' => [
- SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'ipb_by', $genericUser, true, [
+ 'Special name, read-old' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'am3_xxx', $genericUser, true, [
'tables' => [],
- 'orconds' => [ 'userid' => "ipb_by = '1'" ],
+ 'orconds' => [ 'userid' => "am3_xxx = '1'" ],
'joins' => [],
],
],
- 'ipblocks, read-new' => [
- SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'ipb_by', $genericUser, true, [
+ 'Special name, read-new' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'am3_xxx', $genericUser, true, [
'tables' => [],
- 'orconds' => [ 'actor' => "ipb_by_actor = '11'" ],
+ 'orconds' => [ 'actor' => "am3_xxx_actor = '11'" ],
'joins' => [],
],
],
- 'ipblocks, new' => [
- SCHEMA_COMPAT_NEW, 'ipb_by', $genericUser, true, [
+ 'Special name, new' => [
+ SCHEMA_COMPAT_NEW, 'am3_xxx', $genericUser, true, [
'tables' => [],
- 'orconds' => [ 'actor' => "ipb_by_actor = '11'" ],
+ 'orconds' => [ 'actor' => "am3_xxx_actor = '11'" ],
'joins' => [],
],
],
- 'Revision, old' => [
- SCHEMA_COMPAT_OLD, 'rev_user', $genericUser, true, [
+ 'Temp table, old' => [
+ SCHEMA_COMPAT_OLD, 'am2_user', $genericUser, true, [
'tables' => [],
- 'orconds' => [ 'userid' => "rev_user = '1'" ],
+ 'orconds' => [ 'userid' => "am2_user = '1'" ],
'joins' => [],
],
],
- 'Revision, read-old' => [
- SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rev_user', $genericUser, true, [
+ 'Temp table, read-old' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'am2_user', $genericUser, true, [
'tables' => [],
- 'orconds' => [ 'userid' => "rev_user = '1'" ],
+ 'orconds' => [ 'userid' => "am2_user = '1'" ],
'joins' => [],
],
],
- 'Revision, read-new' => [
- SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rev_user', $genericUser, true, [
+ 'Temp table, read-new' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'am2_user', $genericUser, true, [
'tables' => [
- 'temp_rev_user' => 'revision_actor_temp',
+ 'temp_am2_user' => 'actormigration2_temp',
],
- 'orconds' => [ 'actor' => "temp_rev_user.revactor_actor = '11'" ],
+ 'orconds' => [ 'actor' => "temp_am2_user.am2t_actor = '11'" ],
'joins' => [
- 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ 'temp_am2_user' => [ 'JOIN', 'temp_am2_user.am2t_id = am2_id' ],
],
],
],
- 'Revision, new' => [
- SCHEMA_COMPAT_NEW, 'rev_user', $genericUser, true, [
+ 'Temp table, new' => [
+ SCHEMA_COMPAT_NEW, 'am2_user', $genericUser, true, [
'tables' => [
- 'temp_rev_user' => 'revision_actor_temp',
+ 'temp_am2_user' => 'actormigration2_temp',
],
- 'orconds' => [ 'actor' => "temp_rev_user.revactor_actor = '11'" ],
+ 'orconds' => [ 'actor' => "temp_am2_user.am2t_actor = '11'" ],
'joins' => [
- 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ 'temp_am2_user' => [ 'JOIN', 'temp_am2_user.am2t_id = am2_id' ],
],
],
],
'Multiple users, old' => [
- SCHEMA_COMPAT_OLD, 'rc_user', $complicatedUsers, true, [
+ SCHEMA_COMPAT_OLD, 'am1_user', $complicatedUsers, true, [
'tables' => [],
'orconds' => [
- 'userid' => "rc_user IN ('1','2','3') ",
- 'username' => "rc_user_text IN ('192.168.12.34','192.168.12.35') "
+ 'userid' => "am1_user IN ('1','2','3') ",
+ 'username' => "am1_user_text IN ('192.168.12.34','192.168.12.35') "
],
'joins' => [],
],
],
'Multiple users, read-old' => [
- SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rc_user', $complicatedUsers, true, [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'am1_user', $complicatedUsers, true, [
'tables' => [],
'orconds' => [
- 'userid' => "rc_user IN ('1','2','3') ",
- 'username' => "rc_user_text IN ('192.168.12.34','192.168.12.35') "
+ 'userid' => "am1_user IN ('1','2','3') ",
+ 'username' => "am1_user_text IN ('192.168.12.34','192.168.12.35') "
],
'joins' => [],
],
],
'Multiple users, read-new' => [
- SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rc_user', $complicatedUsers, true, [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'am1_user', $complicatedUsers, true, [
'tables' => [],
- 'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
+ 'orconds' => [ 'actor' => "am1_actor IN ('11','12','34') " ],
'joins' => [],
],
],
'Multiple users, new' => [
- SCHEMA_COMPAT_NEW, 'rc_user', $complicatedUsers, true, [
+ SCHEMA_COMPAT_NEW, 'am1_user', $complicatedUsers, true, [
'tables' => [],
- 'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
+ 'orconds' => [ 'actor' => "am1_actor IN ('11','12','34') " ],
'joins' => [],
],
],
'Multiple users, no use ID, old' => [
- SCHEMA_COMPAT_OLD, 'rc_user', $complicatedUsers, false, [
+ SCHEMA_COMPAT_OLD, 'am1_user', $complicatedUsers, false, [
'tables' => [],
'orconds' => [
- 'username' => "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
+ 'username' => "am1_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
],
'joins' => [],
],
],
'Multiple users, read-old' => [
- SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rc_user', $complicatedUsers, false, [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'am1_user', $complicatedUsers, false, [
'tables' => [],
'orconds' => [
- 'username' => "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
+ 'username' => "am1_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
],
'joins' => [],
],
],
'Multiple users, read-new' => [
- SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rc_user', $complicatedUsers, false, [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'am1_user', $complicatedUsers, false, [
'tables' => [],
- 'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
+ 'orconds' => [ 'actor' => "am1_actor IN ('11','12','34') " ],
'joins' => [],
],
],
'Multiple users, new' => [
- SCHEMA_COMPAT_NEW, 'rc_user', $complicatedUsers, false, [
+ SCHEMA_COMPAT_NEW, 'am1_user', $complicatedUsers, false, [
'tables' => [],
- 'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
+ 'orconds' => [ 'actor' => "am1_actor IN ('11','12','34') " ],
'joins' => [],
],
],
* @param string $table
* @param string $key
* @param string $pk
- * @param array $extraFields
+ * @param bool $usesTemp
*/
- public function testInsertRoundTrip( $table, $key, $pk, $extraFields ) {
+ public function testInsertRoundTrip( $table, $key, $pk, $usesTemp ) {
$u = $this->getTestUser()->getUser();
$user = $this->getMock( UserIdentity::class );
$user->method( 'getId' )->willReturn( $u->getId() );
$user->method( 'getName' )->willReturn( $u->getName() );
- if ( $u->getActorId( $this->db ) ) {
- $user->method( 'getActorId' )->willReturn( $u->getActorId() );
- } else {
- $this->db->insert(
- 'actor',
- [ 'actor_user' => $u->getId(), 'actor_name' => $u->getName() ],
- __METHOD__
- );
- $user->method( 'getActorId' )->willReturn( $this->db->insertId() );
- }
+ $user->method( 'getActorId' )->willReturn( $u->getActorId( $this->db ) );
$stageNames = [
SCHEMA_COMPAT_OLD => 'old',
];
$nameKey = $key . '_text';
- $actorKey = $key === 'ipb_by' ? 'ipb_by_actor' : substr( $key, 0, -5 ) . '_actor';
+ $actorKey = ( $key === 'am3_xxx' ? $key : substr( $key, 0, -5 ) ) . '_actor';
foreach ( $stages as $writeStage => $possibleReadStages ) {
- if ( $key === 'ipb_by' ) {
- $extraFields['ipb_address'] = __CLASS__ . "#{$stageNames[$writeStage]}";
- }
-
$w = new ActorMigration( $writeStage );
- $usesTemp = $key === 'rev_user';
if ( $usesTemp ) {
list( $fields, $callback ) = $w->getInsertValuesWithTempTable( $this->db, $key, $user );
"new field, stage={$stageNames[$writeStage]}" );
}
- $this->db->insert( $table, $extraFields + $fields, __METHOD__ );
- $id = $this->db->insertId();
+ $id = ++self::$amId;
+ $this->db->insert( $table, [ $pk => $id ] + $fields, __METHOD__ );
if ( $usesTemp ) {
- $callback( $id, $extraFields );
+ $callback( $id, [] );
}
foreach ( $possibleReadStages as $readStage ) {
}
public static function provideInsertRoundTrip() {
- $db = wfGetDB( DB_REPLICA ); // for timestamps
-
- $comment = MediaWikiServices::getInstance()->getCommentStore()
- ->createComment( wfGetDB( DB_MASTER ), '' );
-
return [
- 'recentchanges' => [ 'recentchanges', 'rc_user', 'rc_id', [
- 'rc_timestamp' => $db->timestamp(),
- 'rc_namespace' => 0,
- 'rc_title' => 'Test',
- 'rc_this_oldid' => 42,
- 'rc_last_oldid' => 41,
- 'rc_source' => 'test',
- 'rc_comment_id' => $comment->id,
- ] ],
- 'ipblocks' => [ 'ipblocks', 'ipb_by', 'ipb_id', [
- 'ipb_range_start' => '',
- 'ipb_range_end' => '',
- 'ipb_timestamp' => $db->timestamp(),
- 'ipb_expiry' => $db->getInfinity(),
- 'ipb_reason_id' => $comment->id,
- ] ],
- 'revision' => [ 'revision', 'rev_user', 'rev_id', [
- 'rev_page' => 42,
- 'rev_len' => 0,
- 'rev_timestamp' => $db->timestamp(),
- ] ],
+ 'normal' => [ 'actormigration1', 'am1_user', 'am1_id', false ],
+ 'temp table' => [ 'actormigration2', 'am2_user', 'am2_id', true ],
+ 'special name' => [ 'actormigration3', 'am3_xxx', 'am3_id', false ],
];
}
* @dataProvider provideStages
* @param int $stage
* @expectedException InvalidArgumentException
- * @expectedExceptionMessage Must use getInsertValuesWithTempTable() for rev_user
+ * @expectedExceptionMessage Must use getInsertValuesWithTempTable() for am2_user
*/
public function testInsertWrong( $stage ) {
$m = new ActorMigration( $stage );
- $m->getInsertValues( $this->db, 'rev_user', $this->getTestUser()->getUser() );
+ $m->getInsertValues( $this->db, 'am2_user', $this->getTestUser()->getUser() );
}
/**
* @dataProvider provideStages
* @param int $stage
* @expectedException InvalidArgumentException
- * @expectedExceptionMessage Must use getInsertValues() for rc_user
+ * @expectedExceptionMessage Must use getInsertValues() for am1_user
*/
public function testInsertWithTempTableWrong( $stage ) {
$m = new ActorMigration( $stage );
- $m->getInsertValuesWithTempTable( $this->db, 'rc_user', $this->getTestUser()->getUser() );
+ $m->getInsertValuesWithTempTable( $this->db, 'am1_user', $this->getTestUser()->getUser() );
}
/**
*/
public function testInsertWithTempTableDeprecated( $stage ) {
$wrap = TestingAccessWrapper::newFromClass( ActorMigration::class );
- $wrap->formerTempTables += [ 'rc_user' => '1.30' ];
+ $wrap->formerTempTables += [ 'am1_user' => '1.30' ];
- $this->hideDeprecated( 'ActorMigration::getInsertValuesWithTempTable for rc_user' );
+ $this->hideDeprecated( 'ActorMigration::getInsertValuesWithTempTable for am1_user' );
$m = new ActorMigration( $stage );
list( $fields, $callback )
- = $m->getInsertValuesWithTempTable( $this->db, 'rc_user', $this->getTestUser()->getUser() );
+ = $m->getInsertValuesWithTempTable( $this->db, 'am1_user', $this->getTestUser()->getUser() );
$this->assertTrue( is_callable( $callback ) );
}
* @dataProvider provideStages
* @param int $stage
* @expectedException InvalidArgumentException
- * @expectedExceptionMessage $extra[rev_timestamp] is not provided
+ * @expectedExceptionMessage $extra[foo_timestamp] is not provided
*/
public function testInsertWithTempTableCallbackMissingFields( $stage ) {
+ $w = TestingAccessWrapper::newFromClass( ActorMigration::class );
+ $w->tempTables = [
+ 'foo_user' => [
+ 'table' => 'foo_temp',
+ 'pk' => 'footmp_id',
+ 'field' => 'footmp_actor',
+ 'joinPK' => 'foo_id',
+ 'extra' => [ 'footmp_timestamp' => 'foo_timestamp' ],
+ ],
+ ];
+
$m = new ActorMigration( $stage );
list( $fields, $callback )
- = $m->getInsertValuesWithTempTable( $this->db, 'rev_user', $this->getTestUser()->getUser() );
+ = $m->getInsertValuesWithTempTable( $this->db, 'foo_user', $this->getTestUser()->getUser() );
$callback( 1, [] );
}
* @param int $stage
*/
public function testInsertUserIdentity( $stage ) {
- $this->setMwGlobals( [
- // for User::getActorId()
- 'wgActorTableSchemaMigrationStage' => $stage
- ] );
-
$user = $this->getMutableTestUser()->getUser();
$userIdentity = $this->getMock( UserIdentity::class );
$userIdentity->method( 'getId' )->willReturn( $user->getId() );
$userIdentity->method( 'getName' )->willReturn( $user->getName() );
$userIdentity->method( 'getActorId' )->willReturn( 0 );
- list( $cFields, $cCallback ) = MediaWikiServices::getInstance()->getCommentStore()
- ->insertWithTempTable( $this->db, 'rev_comment', '' );
$m = new ActorMigration( $stage );
list( $fields, $callback ) =
- $m->getInsertValuesWithTempTable( $this->db, 'rev_user', $userIdentity );
- $extraFields = [
- 'rev_page' => 42,
- 'rev_len' => 0,
- 'rev_timestamp' => $this->db->timestamp(),
- ] + $cFields;
- $this->db->insert( 'revision', $extraFields + $fields, __METHOD__ );
- $id = $this->db->insertId();
- $callback( $id, $extraFields );
- $cCallback( $id );
-
- $qi = $m->getJoin( 'rev_user' );
+ $m->getInsertValuesWithTempTable( $this->db, 'am2_user', $userIdentity );
+ $id = ++self::$amId;
+ $this->db->insert( 'actormigration2', [ 'am2_id' => $id ] + $fields, __METHOD__ );
+ $callback( $id, [] );
+
+ $qi = $m->getJoin( 'am2_user' );
$row = $this->db->selectRow(
- [ 'revision' ] + $qi['tables'], $qi['fields'], [ 'rev_id' => $id ], __METHOD__, [], $qi['joins']
+ [ 'actormigration2' ] + $qi['tables'],
+ $qi['fields'],
+ [ 'am2_id' => $id ],
+ __METHOD__,
+ [],
+ $qi['joins']
);
- $this->assertSame( $user->getId(), (int)$row->rev_user );
- $this->assertSame( $user->getName(), $row->rev_user_text );
+ $this->assertSame( $user->getId(), (int)$row->am2_user );
+ $this->assertSame( $user->getName(), $row->am2_user_text );
$this->assertSame(
( $stage & SCHEMA_COMPAT_READ_NEW ) ? $user->getActorId() : 0,
- (int)$row->rev_actor
+ (int)$row->am2_actor
);
$m = new ActorMigration( $stage );
];
}
+ public function testCheckDeprecation() {
+ $wrap = TestingAccessWrapper::newFromClass( ActorMigration::class );
+ $wrap->deprecated += [ 'soft' => null, 'hard' => '1.34' ];
+ $wrap->removed += [ 'gone' => '1.34' ];
+
+ $this->hideDeprecated( 'ActorMigration for \'hard\'' );
+
+ $wrap->checkDeprecation( 'valid' );
+ $wrap->checkDeprecation( 'soft' );
+ $wrap->checkDeprecation( 'hard' );
+ try {
+ $wrap->checkDeprecation( 'gone' );
+ } catch ( InvalidArgumentException $ex ) {
+ $this->assertSame(
+ 'Use of ActorMigration for \'gone\' was removed in MediaWiki 1.34',
+ $ex->getMessage()
+ );
+ }
+ }
+
}
--- /dev/null
+-- These are carefully crafted to work in all five supported databases
+
+CREATE TABLE /*_*/actormigration1 (
+ am1_id integer not null,
+ am1_user integer,
+ am1_user_text varchar(200),
+ am1_actor integer
+);
+
+CREATE TABLE /*_*/actormigration2 (
+ am2_id integer not null,
+ am2_user integer,
+ am2_user_text varchar(200)
+);
+
+CREATE TABLE /*_*/actormigration2_temp (
+ am2t_id integer not null,
+ am2t_actor integer
+);
+
+CREATE TABLE /*_*/actormigration3 (
+ am3_id integer not null,
+ am3_xxx integer,
+ am3_xxx_text varchar(200),
+ am3_xxx_actor integer
+);
];
}
- protected function getOldActorQueryFields( $prefix ) {
- return [
- "{$prefix}_user" => "{$prefix}_user",
- "{$prefix}_user_text" => "{$prefix}_user_text",
- "{$prefix}_actor" => 'NULL',
- ];
- }
-
protected function getNewActorQueryFields( $prefix, $tmp = false ) {
return [
"{$prefix}_user" => "actor_{$prefix}_user.actor_user",
"{$prefix}_user_text" => "actor_{$prefix}_user.actor_name",
- "{$prefix}_actor" => $tmp ?: "{$prefix}_actor",
- ];
- }
-
- protected function getNewActorJoins( $prefix ) {
- return [
- "temp_{$prefix}_user" => [
- "JOIN",
- "temp_{$prefix}_user.revactor_{$prefix} = {$prefix}_id",
- ],
- "actor_{$prefix}_user" => [
- "JOIN",
- "actor_{$prefix}_user.actor_id = temp_{$prefix}_user.revactor_actor",
- ],
+ "{$prefix}_actor" => $tmp ? "temp_{$prefix}_user.{$prefix}actor_actor" : "{$prefix}_actor",
];
}
yield 'MCR, comment, actor' => [
[
'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
],
[
'tables' => [
'wgContentHandlerUseDB' => true,
'wgMultiContentRevisionSchemaMigrationStage'
=> SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
],
[
'tables' => [
'wgContentHandlerUseDB' => true,
'wgMultiContentRevisionSchemaMigrationStage'
=> SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
],
[
'tables' => [
'archive',
+ 'actor_ar_user' => 'actor',
'comment_ar_comment' => 'comment',
],
'fields' => array_merge(
$this->getArchiveQueryFields( true ),
$this->getContentHandlerQueryFields( 'ar' ),
- $this->getOldActorQueryFields( 'ar' ),
+ $this->getNewActorQueryFields( 'ar' ),
$this->getNewCommentQueryFields( 'ar' )
),
'joins' => [
'comment_ar_comment'
=> [ 'JOIN', 'comment_ar_comment.comment_id = ar_comment_id' ],
+ 'actor_ar_user' => [ 'JOIN', 'actor_ar_user.actor_id = ar_actor' ],
],
]
];
[
'wgContentHandlerUseDB' => false,
'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
],
[
'tables' => [
'archive',
+ 'actor_ar_user' => 'actor',
'comment_ar_comment' => 'comment',
],
'fields' => array_merge(
$this->getArchiveQueryFields( true ),
- $this->getOldActorQueryFields( 'ar' ),
+ $this->getNewActorQueryFields( 'ar' ),
$this->getNewCommentQueryFields( 'ar' )
),
'joins' => [
'comment_ar_comment'
=> [ 'JOIN', 'comment_ar_comment.comment_id = ar_comment_id' ],
+ 'actor_ar_user' => [ 'JOIN', 'actor_ar_user.actor_id = ar_actor' ],
],
]
];
[
'wgContentHandlerUseDB' => true,
'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
],
[ 'page', 'user' ],
[
],
'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
+ 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ 'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
],
]
];
'wgContentHandlerUseDB' => true,
'wgMultiContentRevisionSchemaMigrationStage'
=> SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
],
[ 'page', 'user' ],
[
$this->getNewActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
$this->getNewCommentQueryFields( 'rev' )
),
- 'joins' => array_merge(
- [
- 'page' => [ 'JOIN', [ 'page_id = rev_page' ] ],
- 'user' => [
- 'LEFT JOIN',
- [
- 'actor_rev_user.actor_user != 0',
- 'user_id = actor_rev_user.actor_user',
- ]
- ],
- 'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
- 'comment_rev_comment'
- => [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+ 'joins' => [
+ 'page' => [ 'JOIN', [ 'page_id = rev_page' ] ],
+ 'user' => [
+ 'LEFT JOIN',
+ [
+ 'actor_rev_user.actor_user != 0',
+ 'user_id = actor_rev_user.actor_user',
+ ]
],
- $this->getNewActorJoins( 'rev' )
- ),
+ 'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
+ 'comment_rev_comment'
+ => [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+ 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ 'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
+ ],
]
];
yield 'MCR read-new' => [
'wgContentHandlerUseDB' => true,
'wgMultiContentRevisionSchemaMigrationStage'
=> SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
],
[ 'page', 'user' ],
[
$this->getNewActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
$this->getNewCommentQueryFields( 'rev' )
),
- 'joins' => array_merge(
- [
- 'page' => [ 'JOIN', [ 'page_id = rev_page' ] ],
- 'user' => [
- 'LEFT JOIN',
- [
- 'actor_rev_user.actor_user != 0',
- 'user_id = actor_rev_user.actor_user'
- ]
- ],
- 'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
- 'comment_rev_comment'
- => [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+ 'joins' => [
+ 'page' => [ 'JOIN', [ 'page_id = rev_page' ] ],
+ 'user' => [
+ 'LEFT JOIN',
+ [
+ 'actor_rev_user.actor_user != 0',
+ 'user_id = actor_rev_user.actor_user'
+ ]
],
- $this->getNewActorJoins( 'rev' )
- ),
+ 'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
+ 'comment_rev_comment'
+ => [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+ 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ 'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
+ ],
]
];
yield 'MCR write-both/read-old' => [
'wgContentHandlerUseDB' => true,
'wgMultiContentRevisionSchemaMigrationStage'
=> SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
],
[],
[
'revision',
'temp_rev_comment' => 'revision_comment_temp',
'comment_rev_comment' => 'comment',
+ 'temp_rev_user' => 'revision_actor_temp',
+ 'actor_rev_user' => 'actor',
],
'fields' => array_merge(
$this->getRevisionQueryFields( true ),
$this->getContentHandlerQueryFields( 'rev' ),
- $this->getOldActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
+ $this->getNewActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
$this->getNewCommentQueryFields( 'rev' )
),
'joins' => [
'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
'comment_rev_comment'
=> [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+ 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ 'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
],
]
];
'wgContentHandlerUseDB' => true,
'wgMultiContentRevisionSchemaMigrationStage'
=> SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
],
[ 'page', 'user' ],
[
'user',
'temp_rev_comment' => 'revision_comment_temp',
'comment_rev_comment' => 'comment',
+ 'temp_rev_user' => 'revision_actor_temp',
+ 'actor_rev_user' => 'actor',
],
'fields' => array_merge(
$this->getRevisionQueryFields( true ),
$this->getContentHandlerQueryFields( 'rev' ),
$this->getUserQueryFields(),
$this->getPageQueryFields(),
- $this->getOldActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
+ $this->getNewActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
$this->getNewCommentQueryFields( 'rev' )
),
- 'joins' => array_merge(
- [
- 'page' => [ 'JOIN', [ 'page_id = rev_page' ] ],
- 'user' => [
- 'LEFT JOIN',
- [
- 'rev_user != 0',
- 'user_id = rev_user'
- ]
- ],
- 'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
- 'comment_rev_comment'
- => [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
- ]
- ),
+ 'joins' => [
+ 'page' => [ 'JOIN', [ 'page_id = rev_page' ] ],
+ 'user' => [
+ 'LEFT JOIN',
+ [
+ 'actor_rev_user.actor_user != 0',
+ 'user_id = actor_rev_user.actor_user',
+ ]
+ ],
+ 'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
+ 'comment_rev_comment'
+ => [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+ 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ 'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
+ ],
]
];
yield 'pre-MCR' => [
[
'wgContentHandlerUseDB' => true,
'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
],
[],
[
'revision',
'temp_rev_comment' => 'revision_comment_temp',
'comment_rev_comment' => 'comment',
+ 'temp_rev_user' => 'revision_actor_temp',
+ 'actor_rev_user' => 'actor',
],
'fields' => array_merge(
$this->getRevisionQueryFields( true ),
$this->getContentHandlerQueryFields( 'rev' ),
- $this->getOldActorQueryFields( 'rev' ),
+ $this->getNewActorQueryFields( 'rev', true ),
$this->getNewCommentQueryFields( 'rev' )
),
'joins' => [
'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
'comment_rev_comment'
=> [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+ 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ 'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
],
]
];
[
'wgContentHandlerUseDB' => true,
'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
],
[ 'page', 'user' ],
[
'revision', 'page', 'user',
'temp_rev_comment' => 'revision_comment_temp',
'comment_rev_comment' => 'comment',
+ 'temp_rev_user' => 'revision_actor_temp',
+ 'actor_rev_user' => 'actor',
],
'fields' => array_merge(
$this->getRevisionQueryFields( true ),
$this->getContentHandlerQueryFields( 'rev' ),
$this->getPageQueryFields(),
$this->getUserQueryFields(),
- $this->getOldActorQueryFields( 'rev' ),
+ $this->getNewActorQueryFields( 'rev', true ),
$this->getNewCommentQueryFields( 'rev' )
),
'joins' => [
'page' => [ 'JOIN', [ 'page_id = rev_page' ] ],
- 'user' => [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ],
+ 'user' => [ 'LEFT JOIN', [
+ 'actor_rev_user.actor_user != 0',
+ 'user_id = actor_rev_user.actor_user',
+ ] ],
'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
'comment_rev_comment'
=> [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+ 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ 'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
],
]
];
[
'wgContentHandlerUseDB' => false,
'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
],
[],
[
'revision',
'temp_rev_comment' => 'revision_comment_temp',
'comment_rev_comment' => 'comment',
+ 'temp_rev_user' => 'revision_actor_temp',
+ 'actor_rev_user' => 'actor',
],
'fields' => array_merge(
$this->getRevisionQueryFields( true ),
- $this->getOldActorQueryFields( 'rev' ),
+ $this->getNewActorQueryFields( 'rev', true ),
$this->getNewCommentQueryFields( 'rev' )
),
'joins' => [
'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
'comment_rev_comment'
=> [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+ 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ 'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
],
],
];
[
'wgContentHandlerUseDB' => false,
'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
],
[ 'page' ],
[
'revision', 'page',
'temp_rev_comment' => 'revision_comment_temp',
'comment_rev_comment' => 'comment',
+ 'temp_rev_user' => 'revision_actor_temp',
+ 'actor_rev_user' => 'actor',
],
'fields' => array_merge(
$this->getRevisionQueryFields( true ),
$this->getPageQueryFields(),
- $this->getOldActorQueryFields( 'rev' ),
+ $this->getNewActorQueryFields( 'rev', true ),
$this->getNewCommentQueryFields( 'rev' )
),
'joins' => [
'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
'comment_rev_comment'
=> [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+ 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ 'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
],
],
];
[
'wgContentHandlerUseDB' => false,
'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
],
[ 'user' ],
[
'revision', 'user',
'temp_rev_comment' => 'revision_comment_temp',
'comment_rev_comment' => 'comment',
+ 'temp_rev_user' => 'revision_actor_temp',
+ 'actor_rev_user' => 'actor',
],
'fields' => array_merge(
$this->getRevisionQueryFields( true ),
$this->getUserQueryFields(),
- $this->getOldActorQueryFields( 'rev' ),
+ $this->getNewActorQueryFields( 'rev', true ),
$this->getNewCommentQueryFields( 'rev' )
),
'joins' => [
- 'user' => [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ],
+ 'user' => [ 'LEFT JOIN', [
+ 'actor_rev_user.actor_user != 0',
+ 'user_id = actor_rev_user.actor_user',
+ ] ],
'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
'comment_rev_comment'
=> [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+ 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ 'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
],
],
];
[
'wgContentHandlerUseDB' => false,
'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
],
[ 'text' ],
[
'revision', 'text',
'temp_rev_comment' => 'revision_comment_temp',
'comment_rev_comment' => 'comment',
+ 'temp_rev_user' => 'revision_actor_temp',
+ 'actor_rev_user' => 'actor',
],
'fields' => array_merge(
$this->getRevisionQueryFields( true ),
$this->getTextQueryFields(),
- $this->getOldActorQueryFields( 'rev' ),
+ $this->getNewActorQueryFields( 'rev', true ),
$this->getNewCommentQueryFields( 'rev' )
),
'joins' => [
'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
'comment_rev_comment'
=> [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+ 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ 'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
],
],
];
[
'wgContentHandlerUseDB' => false,
'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
],
[ 'text', 'page', 'user' ],
[
'revision', 'page', 'user', 'text',
'temp_rev_comment' => 'revision_comment_temp',
'comment_rev_comment' => 'comment',
+ 'temp_rev_user' => 'revision_actor_temp',
+ 'actor_rev_user' => 'actor',
],
'fields' => array_merge(
$this->getRevisionQueryFields( true ),
$this->getPageQueryFields(),
$this->getUserQueryFields(),
$this->getTextQueryFields(),
- $this->getOldActorQueryFields( 'rev' ),
+ $this->getNewActorQueryFields( 'rev', true ),
$this->getNewCommentQueryFields( 'rev' )
),
'joins' => [
'user' => [
'LEFT JOIN',
[
- 'rev_user != 0',
- 'user_id = rev_user',
+ 'actor_rev_user.actor_user != 0',
+ 'user_id = actor_rev_user.actor_user',
],
],
'text' => [
'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
'comment_rev_comment'
=> [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+ 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ 'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
],
],
];
];
}
- public function provideSelectFields() {
- yield 'with model, comment, and actor' => [
- [
- 'wgContentHandlerUseDB' => true,
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
- ],
- 'fields' => array_merge(
- [
- 'rev_id',
- 'rev_page',
- 'rev_text_id',
- 'rev_timestamp',
- 'rev_user_text',
- 'rev_user',
- 'rev_actor' => 'NULL',
- 'rev_minor_edit',
- 'rev_deleted',
- 'rev_len',
- 'rev_parent_id',
- 'rev_sha1',
- ],
- $this->getContentHandlerQueryFields( 'rev' ),
- [
- 'rev_comment_pk' => 'rev_id',
- ]
- ),
- ];
- yield 'no mode, no comment, no actor' => [
- [
- 'wgContentHandlerUseDB' => false,
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
- ],
- 'fields' => array_merge(
- [
- 'rev_id',
- 'rev_page',
- 'rev_text_id',
- 'rev_timestamp',
- 'rev_user_text',
- 'rev_user',
- 'rev_actor' => 'NULL',
- 'rev_minor_edit',
- 'rev_deleted',
- 'rev_len',
- 'rev_parent_id',
- 'rev_sha1',
- 'rev_comment_pk' => 'rev_id',
- ]
- ),
- ];
- }
-
- public function provideSelectArchiveFields() {
- yield 'with model, comment, and actor' => [
- [
- 'wgContentHandlerUseDB' => true,
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
- ],
- 'fields' => array_merge(
- [
- 'ar_id',
- 'ar_page_id',
- 'ar_rev_id',
- 'ar_text_id',
- 'ar_timestamp',
- 'ar_user_text',
- 'ar_user',
- 'ar_actor' => 'NULL',
- 'ar_minor_edit',
- 'ar_deleted',
- 'ar_len',
- 'ar_parent_id',
- 'ar_sha1',
- ],
- $this->getContentHandlerQueryFields( 'ar' ),
- [
- 'ar_comment_id' => 'ar_comment_id',
- ]
- ),
- ];
- yield 'no mode, no comment, no actor' => [
- [
- 'wgContentHandlerUseDB' => false,
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
- ],
- 'fields' => array_merge(
- [
- 'ar_id',
- 'ar_page_id',
- 'ar_rev_id',
- 'ar_text_id',
- 'ar_timestamp',
- 'ar_user_text',
- 'ar_user',
- 'ar_actor' => 'NULL',
- 'ar_minor_edit',
- 'ar_deleted',
- 'ar_len',
- 'ar_parent_id',
- 'ar_sha1',
- 'ar_comment_id' => 'ar_comment_id',
- ]
- ),
- ];
- }
-
- /**
- * @dataProvider provideSelectFields
- * @covers Revision::selectFields
- */
- public function testRevisionSelectFields( $migrationStageSettings, $expected ) {
- $this->setMwGlobals( $migrationStageSettings );
-
- $this->hideDeprecated( 'Revision::selectFields' );
- $this->assertArrayEqualsIgnoringIntKeyOrder( $expected, Revision::selectFields() );
- }
-
- /**
- * @dataProvider provideSelectArchiveFields
- * @covers Revision::selectArchiveFields
- */
- public function testRevisionSelectArchiveFields( $migrationStageSettings, $expected ) {
- $this->setMwGlobals( $migrationStageSettings );
-
- $this->hideDeprecated( 'Revision::selectArchiveFields' );
- $this->assertArrayEqualsIgnoringIntKeyOrder( $expected, Revision::selectArchiveFields() );
- }
-
- /**
- * @covers Revision::userJoinCond
- */
- public function testRevisionUserJoinCond() {
- $this->hideDeprecated( 'Revision::userJoinCond' );
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_OLD );
- $this->assertEquals(
- [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ],
- Revision::userJoinCond()
- );
- }
-
- /**
- * @covers Revision::pageJoinCond
- */
- public function testRevisionPageJoinCond() {
- $this->hideDeprecated( 'Revision::pageJoinCond' );
- $this->assertEquals(
- [ 'JOIN', [ 'page_id = rev_page' ] ],
- Revision::pageJoinCond()
- );
- }
-
- /**
- * @covers Revision::selectTextFields
- */
- public function testRevisionSelectTextFields() {
- $this->hideDeprecated( 'Revision::selectTextFields' );
- $this->assertEquals(
- $this->getTextQueryFields(),
- Revision::selectTextFields()
- );
- }
-
- /**
- * @covers Revision::selectPageFields
- */
- public function testRevisionSelectPageFields() {
- $this->hideDeprecated( 'Revision::selectPageFields' );
- $this->assertEquals(
- $this->getPageQueryFields(),
- Revision::selectPageFields()
- );
- }
-
- /**
- * @covers Revision::selectUserFields
- */
- public function testRevisionSelectUserFields() {
- $this->hideDeprecated( 'Revision::selectUserFields' );
- $this->assertEquals(
- $this->getUserQueryFields(),
- Revision::selectUserFields()
- );
- }
-
/**
* @covers Revision::getArchiveQueryInfo
* @dataProvider provideArchiveQueryInfo
$this->setMwGlobals( [
'wgMultiContentRevisionSchemaMigrationStage' => $this->getMcrMigrationStage(),
'wgContentHandlerUseDB' => $this->getContentHandlerUseDB(),
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
] );
}
* @covers \MediaWiki\Revision\RevisionStore::getKnownCurrentRevision
*/
public function testGetKnownCurrentRevision_userNameChange() {
- global $wgActorTableSchemaMigrationStage;
-
$cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
$this->setService( 'MainWANObjectCache', $cache );
$this->db->update( 'user',
[ 'user_name' => $newUserName ],
[ 'user_id' => $rev->getUser()->getId() ] );
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
- $this->db->update( 'actor',
- [ 'actor_name' => $newUserName ],
- [ 'actor_user' => $rev->getUser()->getId() ] );
- }
+ $this->db->update( 'actor',
+ [ 'actor_name' => $newUserName ],
+ [ 'actor_user' => $rev->getUser()->getId() ] );
// Reload the revision and regrab the user name.
$revAfter = $store->getKnownCurrentRevision( $page->getTitle(), $rev->getId() );
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRow
*/
public function testNewRevisionFromRow_userNameChange() {
- global $wgActorTableSchemaMigrationStage;
-
$page = $this->getTestPage();
$text = __METHOD__;
/** @var Revision $rev */
$this->db->update( 'user',
[ 'user_name' => $newUserName ],
[ 'user_id' => $record->getUser()->getId() ] );
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
- $this->db->update( 'actor',
- [ 'actor_name' => $newUserName ],
- [ 'actor_user' => $record->getUser()->getId() ] );
- }
+ $this->db->update( 'actor',
+ [ 'actor_name' => $newUserName ],
+ [ 'actor_user' => $record->getUser()->getId() ] );
// Reload the record, passing $fromCache as true to force fresh info from the db,
// and regrab the user name
$this->setMwGlobals( [
'wgMultiContentRevisionSchemaMigrationStage' => $this->getMcrMigrationStage(),
'wgContentHandlerUseDB' => $this->getContentHandlerUseDB(),
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
] );
if ( !$this->testPage ) {
* @covers Revision::loadFromTitle
*/
public function testLoadFromTitle() {
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
$title = $this->getMockTitle();
$conditions = [
$this->assertEquals(
false,
$title->exists(),
- 'exists() should rely on link cache unless GAID_FOR_UPDATE is used'
+ 'exists() should rely on link cache unless READ_LATEST is used'
);
$this->assertEquals(
true,
- $title->exists( Title::GAID_FOR_UPDATE ),
- 'exists() should re-query database when GAID_FOR_UPDATE is used'
+ $title->exists( Title::READ_LATEST ),
+ 'exists() should re-query database when READ_LATEST is used'
);
}
$jobs->loadParamsAndArgs( null, [ 'quiet' => true ], null );
$jobs->execute();
- $this->assertFalse( Title::newFromText( $name )->exists( Title::GAID_FOR_UPDATE ) );
+ $this->assertFalse( Title::newFromText( $name )->exists( Title::READ_LATEST ) );
}
public function testDeleteNonexistent() {
$mock->method( 'needsToken' )->willReturn( true );
$api = new ApiMain( new FauxRequest( [ 'action' => 'testmodule' ] ) );
- $api->getModuleManager()->addModule( 'testmodule', 'action', get_class( $mock ),
- function () use ( $mock ) {
+ $api->getModuleManager()->addModule( 'testmodule', 'action', [
+ 'class' => get_class( $mock ),
+ 'factory' => function () use ( $mock ) {
return $mock;
}
- );
+ ] );
$api->execute();
}
$mock->method( 'mustBePosted' )->willReturn( false );
$api = new ApiMain( new FauxRequest( [ 'action' => 'testmodule' ] ) );
- $api->getModuleManager()->addModule( 'testmodule', 'action', get_class( $mock ),
- function () use ( $mock ) {
+ $api->getModuleManager()->addModule( 'testmodule', 'action', [
+ 'class' => get_class( $mock ),
+ 'factory' => function () use ( $mock ) {
return $mock;
}
- );
+ ] );
$api->execute();
}
$req->setRequestURL( "http://localhost" );
$api = new ApiMain( $req );
- $api->getModuleManager()->addModule( 'testmodule', 'action', get_class( $mock ),
- function () use ( $mock ) {
+ $api->getModuleManager()->addModule( 'testmodule', 'action', [
+ 'class' => get_class( $mock ),
+ 'factory' => function () use ( $mock ) {
return $mock;
}
- );
+ ] );
$wrapper = TestingAccessWrapper::newFromObject( $api );
$wrapper->mInternalMode = false;
<?php
+use MediaWiki\MediaWikiServices;
+use Wikimedia\ObjectFactory;
+
/**
* @covers ApiModuleManager
*
private function getModuleManager() {
$request = new FauxRequest();
$main = new ApiMain( $request );
- return new ApiModuleManager( $main );
+
+ return new ApiModuleManager( $main, MediaWikiServices::getInstance()->getObjectFactory() );
}
public function newApiLogin( $main, $action ) {
null,
],
- 'with factory' => [
+ 'with class and factory' => [
'login',
'action',
ApiLogin::class,
[ $this, 'newApiLogin' ],
],
- 'with closure' => [
- 'logout',
+ 'with spec (class only)' => [
+ 'login',
'action',
- ApiLogout::class,
- function ( ApiMain $main, $action ) {
- return new ApiLogout( $main, $action );
- },
+ [
+ 'class' => ApiLogin::class
+ ],
+ null,
+ ],
+
+ 'with spec' => [
+ 'login',
+ 'action',
+ [
+ 'class' => ApiLogin::class,
+ 'factory' => [ $this, 'newApiLogin' ],
+ ],
+ null,
],
+
+ 'with spec (using services)' => [
+ 'logout',
+ 'action',
+ [
+ 'class' => ApiLogout::class,
+ 'factory' => function ( ApiMain $main, $action, ObjectFactory $objectFactory ) {
+ return new ApiLogout( $main, $action );
+ },
+ 'services' => [
+ 'ObjectFactory'
+ ],
+ ],
+ null,
+ ]
];
}
/**
* @dataProvider addModuleProvider
*/
- public function testAddModule( $name, $group, $class, $factory = null ) {
+ public function testAddModule( $name, $group, $spec, $factory ) {
+ if ( $factory ) {
+ $this->hideDeprecated(
+ ApiModuleManager::class . '::addModule with $class and $factory'
+ );
+ }
+
$moduleManager = $this->getModuleManager();
- $moduleManager->addModule( $name, $group, $class, $factory );
+ $moduleManager->addModule( $name, $group, $spec, $factory );
$this->assertTrue( $moduleManager->isDefined( $name, $group ), 'isDefined' );
$this->assertNotNull( $moduleManager->getModule( $name, $group, true ), 'getModule' );
$moduleManager->getClassName( 'nonexistentmodule' )
);
}
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage $spec must define a class name
+ */
+ public function testAddModuleWithIncompleteSpec() {
+ $moduleManager = $this->getModuleManager();
+
+ $moduleManager->addModule(
+ 'logout',
+ 'action',
+ [
+ 'factory' => function ( ApiMain $main, $action ) {
+ return new ApiLogout( $main, $action );
+ },
+ ]
+ );
+ }
}
<?php
+use MediaWiki\MediaWikiServices;
+use Wikimedia\TestingAccessWrapper;
+
/**
* @group API
* @group medium
3 => [ "$userDbkey/subpage" => -3 ],
], $pageSet->getAllTitlesByNamespace() );
}
+
+ /**
+ * Test that ApiPageSet is calling GenderCache for provided user names to prefill the
+ * GenderCache and avoid a performance issue when loading each users' gender on it's own.
+ * The test is setting the "missLimit" to 0 on the GenderCache to trigger misses logic.
+ * When the "misses" property is no longer 0 at the end of the test,
+ * something was requested which is not part of the cache. Than the test is failing.
+ */
+ public function testGenderCaching() {
+ // Set up the user namespace to have gender aliases to trigger the gender cache
+ $this->setMwGlobals( [
+ 'wgExtraGenderNamespaces' => [ NS_USER => [ 'male' => 'Male', 'female' => 'Female' ] ]
+ ] );
+ $this->overrideMwServices();
+
+ // User names to test with - it is not needed that the user exists in the database
+ // to trigger gender cache
+ $userNames = [
+ 'Female',
+ 'Unknown',
+ 'Male',
+ ];
+
+ // Prepare the gender cache for testing - this is a fresh instance due to service override
+ $genderCache = TestingAccessWrapper::newFromObject(
+ MediaWikiServices::getInstance()->getGenderCache()
+ );
+ $genderCache->missLimit = 0;
+
+ // Do an api request to trigger ApiPageSet code
+ $this->doApiRequest( [
+ 'action' => 'query',
+ 'titles' => 'User:' . implode( '|User:', $userNames ),
+ ] );
+
+ $this->assertEquals( 0, $genderCache->misses,
+ 'ApiPageSet does not prefill the gender cache correctly' );
+ $this->assertEquals( $userNames, array_keys( $genderCache->cache ),
+ 'ApiPageSet does not prefill all users into the gender cache' );
+ }
}
$moduleManager->addModule(
'languageinfo',
'meta',
- ApiQueryLanguageinfo::class,
- function ( $parent, $name ) use ( $microtimeFunction ) {
- return new ApiQueryLanguageinfo(
- $parent,
- $name,
- $microtimeFunction
- );
- }
+ [
+ 'class' => ApiQueryLanguageinfo::class,
+ 'factory' => function ( $parent, $name ) use ( $microtimeFunction ) {
+ return new ApiQueryLanguageinfo(
+ $parent,
+ $name,
+ $microtimeFunction
+ );
+ }
+ ]
);
}
);
<?php
+use MediaWiki\Block\DatabaseBlock;
+use MediaWiki\Block\Restriction\PageRestriction;
+
/**
* Tests for action=revisiondelete
* @covers APIRevisionDelete
// Make a few edits for us to play with
for ( $i = 1; $i <= 5; $i++ ) {
self::editPage( self::$page, MWCryptRand::generateHex( 10 ), 'summary' );
- $this->revs[] = Title::newFromText( self::$page )
- ->getLatestRevID( Title::GAID_FOR_UPDATE );
+ $this->revs[] = Title::newFromText( self::$page )->getLatestRevID( Title::READ_LATEST );
}
}
$this->assertTrue( $item['texthidden'], 'texthidden' );
$this->assertEquals( $item['id'], $revid );
}
+
+ public function testPartiallyBlockedPage() {
+ $this->setExpectedApiException( 'apierror-blocked-partial' );
+
+ $user = static::getTestSysop()->getUser();
+
+ $block = new DatabaseBlock( [
+ 'address' => $user,
+ 'by' => static::getTestSysop()->getUser()->getId(),
+ 'sitewide' => false,
+ ] );
+
+ $block->setRestrictions( [
+ new PageRestriction( 0, Title::newFromText( self::$page )->getArticleID() )
+ ] );
+ $block->insert();
+
+ $revid = array_shift( $this->revs );
+
+ $this->doApiRequest( [
+ 'action' => 'revisiondelete',
+ 'type' => 'revision',
+ 'target' => self::$page,
+ 'ids' => $revid,
+ 'hide' => 'content|user|comment',
+ 'token' => $user->getEditToken(),
+ ] );
+ }
}
$main = new ApiMain( $context );
$printer = $this->getMockFormatter( $main, 'mockfm' );
$mm = $printer->getMain()->getModuleManager();
- $mm->addModule( 'mockfm', 'format', ApiFormatBase::class, function () {
- return $mock;
- } );
- if ( $registerNonHtml ) {
- $mm->addModule( 'mock', 'format', ApiFormatBase::class, function () {
+ $mm->addModule( 'mockfm', 'format', [
+ 'class' => ApiFormatBase::class,
+ 'factory' => function () {
return $mock;
- } );
+ }
+ ] );
+ if ( $registerNonHtml ) {
+ $mm->addModule( 'mock', 'format', [
+ 'class' => ApiFormatBase::class,
+ 'factory' => function () {
+ return $mock;
+ }
+ ] );
}
$printer->initPrinter();
* - class: If set, register 'name' with this class (and 'factory', if that's set)
* - factory: Used with 'class' to register at runtime
* - returnPrinter: Return the printer object
- * @param callable|null $factory Factory to use instead of the normal one
* @return string|array The string if $options['returnPrinter'] isn't set, or an array if it is:
* - text: Output text string
* - printer: ApiFormatBase
$context->setRequest( new FauxRequest( $params, true ) );
$main = new ApiMain( $context );
if ( isset( $options['class'] ) ) {
- $factory = $options['factory'] ?? null;
- $main->getModuleManager()->addModule( $printerName, 'format', $options['class'], $factory );
+ $spec = [
+ 'class' => $options['class']
+ ];
+
+ if ( isset( $options['factory'] ) ) {
+ $spec['factory'] = $options['factory'];
+ }
+
+ $main->getModuleManager()->addModule( $printerName, 'format', $spec );
}
$result = $main->getResult();
$result->addArrayType( null, 'default' );
*/
class ApiQueryUserContribsTest extends ApiTestCase {
public function addDBDataOnce() {
- global $wgActorTableSchemaMigrationStage;
-
- $reset = new \Wikimedia\ScopedCallback( function ( $v ) {
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', $v );
- }, [ $wgActorTableSchemaMigrationStage ] );
- // Needs to WRITE_BOTH so READ_OLD tests below work. READ mode here doesn't really matter.
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage',
- SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW );
-
$users = [
User::newFromName( '192.168.2.2', false ),
User::newFromName( '192.168.2.1', false ),
/**
* @dataProvider provideSorting
- * @param int $stage SCHEMA_COMPAT contants for $wgActorTableSchemaMigrationStage
* @param array $params Extra parameters for the query
* @param bool $reverse Reverse order?
* @param int $revs Number of revisions to expect
*/
- public function testSorting( $stage, $params, $reverse, $revs ) {
+ public function testSorting( $params, $reverse, $revs ) {
// FIXME: fails under sqlite
$this->markTestSkippedIfDbType( 'sqlite' );
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', $stage );
-
if ( isset( $params['ucuserids'] ) ) {
$params['ucuserids'] = implode( '|', array_map( 'User::idFromName', $params['ucuserids'] ) );
}
$users2 = [ __CLASS__ . ' A', __CLASS__ . ' B', __CLASS__ . ' D' ];
$ips = [ '192.168.2.1', '192.168.2.2', '192.168.2.3', '192.168.2.4' ];
- foreach (
- [
- 'old' => SCHEMA_COMPAT_OLD,
- 'read old' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
- 'read new' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
- 'new' => SCHEMA_COMPAT_NEW,
- ] as $stageName => $stage
- ) {
- foreach ( [ false, true ] as $reverse ) {
- $name = $stageName . ( $reverse ? ', reverse' : '' );
- yield "Named users, $name" => [ $stage, [ 'ucuser' => $users ], $reverse, 9 ];
- yield "Named users including a no-edit user, $name" => [
- $stage, [ 'ucuser' => $users2 ], $reverse, 6
- ];
- yield "IP users, $name" => [ $stage, [ 'ucuser' => $ips ], $reverse, 9 ];
- yield "All users, $name" => [
- $stage, [ 'ucuser' => array_merge( $users, $ips ) ], $reverse, 18
- ];
- yield "User IDs, $name" => [ $stage, [ 'ucuserids' => $users ], $reverse, 9 ];
- yield "Users by prefix, $name" => [ $stage, [ 'ucuserprefix' => __CLASS__ ], $reverse, 9 ];
- yield "IPs by prefix, $name" => [ $stage, [ 'ucuserprefix' => '192.168.2.' ], $reverse, 9 ];
- }
+ foreach ( [ false, true ] as $reverse ) {
+ $name = ( $reverse ? ', reverse' : '' );
+ yield "Named users, $name" => [ [ 'ucuser' => $users ], $reverse, 9 ];
+ yield "Named users including a no-edit user, $name" => [
+ [ 'ucuser' => $users2 ], $reverse, 6
+ ];
+ yield "IP users, $name" => [ [ 'ucuser' => $ips ], $reverse, 9 ];
+ yield "All users, $name" => [
+ [ 'ucuser' => array_merge( $users, $ips ) ], $reverse, 18
+ ];
+ yield "User IDs, $name" => [ [ 'ucuserids' => $users ], $reverse, 9 ];
+ yield "Users by prefix, $name" => [ [ 'ucuserprefix' => __CLASS__ ], $reverse, 9 ];
+ yield "IPs by prefix, $name" => [ [ 'ucuserprefix' => '192.168.2.' ], $reverse, 9 ];
}
}
- /**
- * @dataProvider provideInterwikiUser
- * @param int $stage SCHEMA_COMPAT constants for $wgActorTableSchemaMigrationStage
- */
- public function testInterwikiUser( $stage ) {
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', $stage );
-
+ public function testInterwikiUser() {
$params = [
'action' => 'query',
'list' => 'usercontribs',
$this->assertSame( $sorted, $ids, "IDs are sorted" );
}
- public static function provideInterwikiUser() {
- return [
- 'old' => [ SCHEMA_COMPAT_OLD ],
- 'read old' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD ],
- 'read new' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW ],
- 'new' => [ SCHEMA_COMPAT_NEW ],
- ];
- }
-
}
* @param array $selectFields
* @param string[]|null $row
* @param string[]|null $expectedFields
- * @param int $actorMigration
*/
public function testNewFromId( $id,
array $selectFields,
array $row = null,
- array $expectedFields = null,
- $actorMigration
+ array $expectedFields = null
) {
- $this->setMwGlobals( [
- 'wgActorTableSchemaMigrationStage' => $actorMigration,
- ] );
-
$row = $row ? (object)$row : null;
$db = $this->getMock( IDatabase::class );
$db->expects( self::once() )
}
public function provideNewFromId() {
- $oldTables = [
- 'tables' => [
- 'logging', 'user',
- 'comment_log_comment' => 'comment',
- ],
- 'fields' => [
- 'log_id',
- 'log_type',
- 'log_action',
- 'log_timestamp',
- 'log_namespace',
- 'log_title',
- 'log_params',
- 'log_deleted',
- 'user_id',
- 'user_name',
- 'user_editcount',
- 'log_comment_text' => 'comment_log_comment.comment_text',
- 'log_comment_data' => 'comment_log_comment.comment_data',
- 'log_comment_cid' => 'comment_log_comment.comment_id',
- 'log_user' => 'log_user',
- 'log_user_text' => 'log_user_text',
- 'log_actor' => 'NULL',
- ],
- 'options' => [],
- 'join_conds' => [
- 'user' => [ 'LEFT JOIN', 'user_id=log_user' ],
- 'comment_log_comment' => [ 'JOIN', 'comment_log_comment.comment_id = log_comment_id' ],
- ],
- ];
$newTables = [
'tables' => [
'logging',
return [
[
0,
- $oldTables + [ 'conds' => [ 'log_id' => 0 ] ],
- null,
+ $newTables + [ 'conds' => [ 'log_id' => 0 ] ],
null,
- SCHEMA_COMPAT_OLD,
+ null
],
[
123,
- $oldTables + [ 'conds' => [ 'log_id' => 123 ] ],
+ $newTables + [ 'conds' => [ 'log_id' => 123 ] ],
[
'log_id' => 123,
'log_type' => 'foobarize',
'log_comment_text' => 'test!',
'log_comment_data' => null,
],
- [ 'type' => 'foobarize', 'comment' => 'test!' ],
- SCHEMA_COMPAT_OLD,
+ [ 'type' => 'foobarize', 'comment' => 'test!' ]
],
[
567,
'log_comment_text' => 'test!',
'log_comment_data' => null,
],
- [ 'type' => 'foobarize', 'comment' => 'test!' ],
- SCHEMA_COMPAT_NEW,
+ [ 'type' => 'foobarize', 'comment' => 'test!' ]
],
];
}
$this->tablesUsed += $this->getMcrTablesToReset();
$this->setMwGlobals( [
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
'wgContentHandlerUseDB' => $this->getContentHandlerUseDB(),
'wgMultiContentRevisionSchemaMigrationStage' => $this->getMcrMigrationStage(),
] );
try {
$manager->getSessionForRequest( $request );
$this->fail( 'Expcected exception not thrown' );
- } catch ( \OverflowException $ex ) {
+ } catch ( SessionOverflowException $ex ) {
$this->assertStringStartsWith(
'Multiple sessions for this request tied for top priority: ',
$ex->getMessage()
);
- $this->assertCount( 2, $ex->sessionInfos );
- $this->assertContains( $request->info1, $ex->sessionInfos );
- $this->assertContains( $request->info2, $ex->sessionInfos );
+ $this->assertCount( 2, $ex->getSessionInfos() );
+ $this->assertContains( $request->info1, $ex->getSessionInfos() );
+ $this->assertContains( $request->info2, $ex->getSessionInfos() );
}
$this->assertFalse( $request->unpersist1 );
$this->assertFalse( $request->unpersist2 );
}
public function testRcHidemyselfFilter() {
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-
$user = $this->getTestUser()->getUser();
$user->getActorId( wfGetDB( DB_MASTER ) );
$this->assertConditions(
);
}
- public function testRcHidemyselfFilter_old() {
- $this->setMwGlobals(
- 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
- );
-
- $user = $this->getTestUser()->getUser();
- $user->getActorId( wfGetDB( DB_MASTER ) );
- $this->assertConditions(
- [ # expected
- "NOT((rc_user = '{$user->getId()}'))",
- ],
- [
- 'hidemyself' => 1,
- ],
- "rc conditions: hidemyself=1 (logged in)",
- $user
- );
-
- $user = User::newFromName( '10.11.12.13', false );
- $id = $user->getActorId( wfGetDB( DB_MASTER ) );
- $this->assertConditions(
- [ # expected
- "NOT((rc_user_text = '10.11.12.13'))",
- ],
- [
- 'hidemyself' => 1,
- ],
- "rc conditions: hidemyself=1 (anon)",
- $user
- );
- }
-
public function testRcHidebyothersFilter() {
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-
$user = $this->getTestUser()->getUser();
$user->getActorId( wfGetDB( DB_MASTER ) );
$this->assertConditions(
);
}
- public function testRcHidebyothersFilter_old() {
- $this->setMwGlobals(
- 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
- );
-
- $user = $this->getTestUser()->getUser();
- $user->getActorId( wfGetDB( DB_MASTER ) );
- $this->assertConditions(
- [ # expected
- "(rc_user_text = '{$user->getName()}')",
- ],
- [
- 'hidebyothers' => 1,
- ],
- "rc conditions: hidebyothers=1 (logged in)",
- $user
- );
-
- $user = User::newFromName( '10.11.12.13', false );
- $id = $user->getActorId( wfGetDB( DB_MASTER ) );
- $this->assertConditions(
- [ # expected
- "(rc_user_text = '10.11.12.13')",
- ],
- [
- 'hidebyothers' => 1,
- ],
- "rc conditions: hidebyothers=1 (anon)",
- $user
- );
- }
-
public function testRcHidepageedits() {
$this->assertConditions(
[ # expected
}
public function testFilterUserExpLevelAllExperienceLevels() {
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-
$this->assertConditions(
[
# expected
);
}
- public function testFilterUserExpLevelAllExperienceLevels_old() {
- $this->setMwGlobals(
- 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
- );
-
- $this->assertConditions(
- [
- # expected
- 'rc_user != 0',
- ],
- [
- 'userExpLevel' => 'newcomer;learner;experienced',
- ],
- "rc conditions: userExpLevel=newcomer;learner;experienced"
- );
- }
-
- public function testFilterUserExpLevelRegistrered() {
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-
+ public function testFilterUserExpLevelRegistered() {
$this->assertConditions(
[
# expected
);
}
- public function testFilterUserExpLevelRegistrered_old() {
- $this->setMwGlobals(
- 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
- );
-
- $this->assertConditions(
- [
- # expected
- 'rc_user != 0',
- ],
- [
- 'userExpLevel' => 'registered',
- ],
- "rc conditions: userExpLevel=registered"
- );
- }
-
- public function testFilterUserExpLevelUnregistrered() {
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-
+ public function testFilterUserExpLevelUnregistered() {
$this->assertConditions(
[
# expected
);
}
- public function testFilterUserExpLevelUnregistrered_old() {
- $this->setMwGlobals(
- 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
- );
-
- $this->assertConditions(
- [
- # expected
- 'rc_user = 0',
- ],
- [
- 'userExpLevel' => 'unregistered',
- ],
- "rc conditions: userExpLevel=unregistered"
- );
- }
-
- public function testFilterUserExpLevelRegistreredOrLearner() {
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-
+ public function testFilterUserExpLevelRegisteredOrLearner() {
$this->assertConditions(
[
# expected
);
}
- public function testFilterUserExpLevelRegistreredOrLearner_old() {
- $this->setMwGlobals(
- 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
- );
-
- $this->assertConditions(
- [
- # expected
- 'rc_user != 0',
- ],
- [
- 'userExpLevel' => 'registered;learner',
- ],
- "rc conditions: userExpLevel=registered;learner"
- );
- }
-
- public function testFilterUserExpLevelUnregistreredOrExperienced() {
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-
+ public function testFilterUserExpLevelUnregisteredOrExperienced() {
$conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] );
$this->assertRegExp(
);
}
- public function testFilterUserExpLevelUnregistreredOrExperienced_old() {
- $this->setMwGlobals(
- 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
- );
-
- $conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] );
-
- $this->assertRegExp(
- '/\(rc_user = 0\) OR '
- . '\(\(user_editcount >= 500\) AND \(user_registration <= \'[^\']+\'\)\)/',
- reset( $conds ),
- "rc conditions: userExpLevel=unregistered;experienced"
- );
- }
-
public function testFilterUserExpLevel() {
$now = time();
$this->setMwGlobals( [
/** List query pages that can not be tested automatically */
protected $manualTest = [
- LinkSearchPage::class
+ SpecialLinkSearch::class
];
/**
/**
* @group Database
- * @covers MIMEsearchPage
+ * @covers SpecialMIMESearch
*/
class SpecialMIMESearchTest extends MediaWikiTestCase {
- /** @var MIMEsearchPage */
+ /** @var SpecialMIMESearch */
private $page;
function setUp() {
- $this->page = new MIMEsearchPage;
+ $this->page = new SpecialMIMESearch;
$context = new RequestContext();
$context->setTitle( Title::makeTitle( NS_SPECIAL, 'MIMESearch' ) );
$context->setRequest( new FauxRequest() );
--- /dev/null
+<?php
+
+/**
+ * Test class for SpecialShortPages class
+ *
+ * @since 1.30
+ *
+ * @license GPL-2.0-or-later
+ */
+class SpecialShortPagesTest extends MediaWikiTestCase {
+
+ /**
+ * @dataProvider provideGetQueryInfoRespectsContentNs
+ * @covers SpecialShortPages::getQueryInfo()
+ */
+ public function testGetQueryInfoRespectsContentNS( $contentNS, $blacklistNS, $expectedNS ) {
+ $this->setMwGlobals( [
+ 'wgShortPagesNamespaceBlacklist' => $blacklistNS,
+ 'wgContentNamespaces' => $contentNS
+ ] );
+ $this->setTemporaryHook( 'ShortPagesQuery', function () {
+ // empty hook handler
+ } );
+
+ $page = new SpecialShortPages();
+ $queryInfo = $page->getQueryInfo();
+
+ $this->assertArrayHasKey( 'conds', $queryInfo );
+ $this->assertArrayHasKey( 'page_namespace', $queryInfo[ 'conds' ] );
+ $this->assertEquals( $expectedNS, $queryInfo[ 'conds' ][ 'page_namespace' ] );
+ }
+
+ public function provideGetQueryInfoRespectsContentNs() {
+ return [
+ [ [ NS_MAIN, NS_FILE ], [], [ NS_MAIN, NS_FILE ] ],
+ [ [ NS_MAIN, NS_TALK ], [ NS_FILE ], [ NS_MAIN, NS_TALK ] ],
+ [ [ NS_MAIN, NS_FILE ], [ NS_FILE ], [ NS_MAIN ] ],
+ // NS_MAIN namespace is always forced
+ [ [], [ NS_FILE ], [ NS_MAIN ] ]
+ ];
+ }
+
+}
+++ /dev/null
-<?php
-
-/**
- * Test class for SpecialShortpages class
- *
- * @since 1.30
- *
- * @license GPL-2.0-or-later
- */
-class SpecialShortpagesTest extends MediaWikiTestCase {
-
- /**
- * @dataProvider provideGetQueryInfoRespectsContentNs
- * @covers ShortPagesPage::getQueryInfo()
- */
- public function testGetQueryInfoRespectsContentNS( $contentNS, $blacklistNS, $expectedNS ) {
- $this->setMwGlobals( [
- 'wgShortPagesNamespaceBlacklist' => $blacklistNS,
- 'wgContentNamespaces' => $contentNS
- ] );
- $this->setTemporaryHook( 'ShortPagesQuery', function () {
- // empty hook handler
- } );
-
- $page = new ShortPagesPage();
- $queryInfo = $page->getQueryInfo();
-
- $this->assertArrayHasKey( 'conds', $queryInfo );
- $this->assertArrayHasKey( 'page_namespace', $queryInfo[ 'conds' ] );
- $this->assertEquals( $expectedNS, $queryInfo[ 'conds' ][ 'page_namespace' ] );
- }
-
- public function provideGetQueryInfoRespectsContentNs() {
- return [
- [ [ NS_MAIN, NS_FILE ], [], [ NS_MAIN, NS_FILE ] ],
- [ [ NS_MAIN, NS_TALK ], [ NS_FILE ], [ NS_MAIN, NS_TALK ] ],
- [ [ NS_MAIN, NS_FILE ], [ NS_FILE ], [ NS_MAIN ] ],
- // NS_MAIN namespace is always forced
- [ [], [ NS_FILE ], [ NS_MAIN ] ]
- ];
- }
-
-}
--- /dev/null
+<?php
+/**
+ * Tests for Special:UncategorizedCategories
+ */
+class SpecialUncategorizedCategoriesTest extends MediaWikiTestCase {
+ /**
+ * @dataProvider provideTestGetQueryInfoData
+ * @covers SpecialUncategorizedCategories::getQueryInfo
+ */
+ public function testGetQueryInfo( $msgContent, $expected ) {
+ $msg = new RawMessage( $msgContent );
+ $mockContext = $this->getMockBuilder( RequestContext::class )->getMock();
+ $mockContext->method( 'msg' )->willReturn( $msg );
+ $special = new SpecialUncategorizedCategories();
+ $special->setContext( $mockContext );
+ $this->assertEquals( [
+ 'tables' => [
+ 0 => 'page',
+ 1 => 'categorylinks',
+ ],
+ 'fields' => [
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_title',
+ ],
+ 'conds' => [
+ 0 => 'cl_from IS NULL',
+ 'page_namespace' => 14,
+ 'page_is_redirect' => 0,
+ ] + $expected,
+ 'join_conds' => [
+ 'categorylinks' => [
+ 0 => 'LEFT JOIN',
+ 1 => 'cl_from = page_id',
+ ],
+ ],
+ ], $special->getQueryInfo() );
+ }
+
+ public function provideTestGetQueryInfoData() {
+ return [
+ [
+ "* Stubs\n* Test\n* *\n* * test123",
+ [ 1 => "page_title not in ( 'Stubs','Test','*','*_test123' )" ]
+ ],
+ [
+ "Stubs\n* Test\n* *\n* * test123",
+ [ 1 => "page_title not in ( 'Test','*','*_test123' )" ]
+ ],
+ [
+ "* StubsTest\n* *\n* * test123",
+ [ 1 => "page_title not in ( 'StubsTest','*','*_test123' )" ]
+ ],
+ [ "", [] ],
+ [ "\n\n\n", [] ],
+ [ "\n", [] ],
+ [ "Test\n*Test2", [ 1 => "page_title not in ( 'Test2' )" ] ],
+ [ "Test", [] ],
+ [ "*Test\nTest2", [ 1 => "page_title not in ( 'Test' )" ] ],
+ [ "Test\nTest2", [] ],
+ ];
+ }
+}
+++ /dev/null
-<?php
-/**
- * Tests for Special:Uncategorizedcategories
- */
-class UncategorizedCategoriesPageTest extends MediaWikiTestCase {
- /**
- * @dataProvider provideTestGetQueryInfoData
- * @covers UncategorizedCategoriesPage::getQueryInfo
- */
- public function testGetQueryInfo( $msgContent, $expected ) {
- $msg = new RawMessage( $msgContent );
- $mockContext = $this->getMockBuilder( RequestContext::class )->getMock();
- $mockContext->method( 'msg' )->willReturn( $msg );
- $special = new UncategorizedCategoriesPage();
- $special->setContext( $mockContext );
- $this->assertEquals( [
- 'tables' => [
- 0 => 'page',
- 1 => 'categorylinks',
- ],
- 'fields' => [
- 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_title',
- ],
- 'conds' => [
- 0 => 'cl_from IS NULL',
- 'page_namespace' => 14,
- 'page_is_redirect' => 0,
- ] + $expected,
- 'join_conds' => [
- 'categorylinks' => [
- 0 => 'LEFT JOIN',
- 1 => 'cl_from = page_id',
- ],
- ],
- ], $special->getQueryInfo() );
- }
-
- public function provideTestGetQueryInfoData() {
- return [
- [
- "* Stubs\n* Test\n* *\n* * test123",
- [ 1 => "page_title not in ( 'Stubs','Test','*','*_test123' )" ]
- ],
- [
- "Stubs\n* Test\n* *\n* * test123",
- [ 1 => "page_title not in ( 'Test','*','*_test123' )" ]
- ],
- [
- "* StubsTest\n* *\n* * test123",
- [ 1 => "page_title not in ( 'StubsTest','*','*_test123' )" ]
- ],
- [ "", [] ],
- [ "\n\n\n", [] ],
- [ "\n", [] ],
- [ "Test\n*Test2", [ 1 => "page_title not in ( 'Test2' )" ] ],
- [ "Test", [] ],
- [ "*Test\nTest2", [ 1 => "page_title not in ( 'Test' )" ] ],
- [ "Test\nTest2", [] ],
- ];
- }
-}
$this->setMwGlobals( [
'wgGroupPermissions' => [],
'wgRevokePermissions' => [],
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
] );
$this->setUpPermissionGlobals();
'User::newFromActorId works for an anonymous user' );
}
- /**
- * Actor tests with SCHEMA_COMPAT_READ_OLD
- *
- * The only thing different from testActorId() is the behavior if the actor
- * row doesn't exist in the DB, since with SCHEMA_COMPAT_READ_NEW that
- * situation can't happen. But we copy all the other tests too just for good measure.
- *
- * @covers User::newFromActorId
- */
- public function testActorId_old() {
- $this->setMwGlobals( [
- 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
- ] );
-
- $domain = MediaWikiServices::getInstance()->getDBLoadBalancer()->getLocalDomainID();
- $this->hideDeprecated( 'User::selectFields' );
-
- // Newly-created user has an actor ID
- $user = User::createNew( 'UserTestActorIdOld1' );
- $id = $user->getId();
- $this->assertTrue( $user->getActorId() > 0, 'User::createNew sets an actor ID' );
-
- $user = User::newFromName( 'UserTestActorIdOld2' );
- $user->addToDatabase();
- $this->assertTrue( $user->getActorId() > 0, 'User::addToDatabase sets an actor ID' );
-
- $user = User::newFromName( 'UserTestActorIdOld1' );
- $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be retrieved for user loaded by name' );
-
- $user = User::newFromId( $id );
- $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be retrieved for user loaded by ID' );
-
- $user2 = User::newFromActorId( $user->getActorId() );
- $this->assertEquals( $user->getId(), $user2->getId(),
- 'User::newFromActorId works for an existing user' );
-
- $row = $this->db->selectRow( 'user', User::selectFields(), [ 'user_id' => $id ], __METHOD__ );
- $user = User::newFromRow( $row );
- $this->assertTrue( $user->getActorId() > 0,
- 'Actor ID can be retrieved for user loaded with User::selectFields()' );
-
- $this->db->delete( 'actor', [ 'actor_user' => $id ], __METHOD__ );
- User::purge( $domain, $id );
- // Because WANObjectCache->delete() stupidly doesn't delete from the process cache.
-
- MediaWikiServices::getInstance()->getMainWANObjectCache()->clearProcessCache();
-
- $user = User::newFromId( $id );
- $this->assertFalse( $user->getActorId() > 0, 'No Actor ID by default if none in database' );
- $this->assertTrue( $user->getActorId( $this->db ) > 0, 'Actor ID can be created if none in db' );
-
- $user->setName( 'UserTestActorIdOld4-renamed' );
- $user->saveSettings();
- $this->assertEquals(
- $user->getName(),
- $this->db->selectField(
- 'actor', 'actor_name', [ 'actor_id' => $user->getActorId() ], __METHOD__
- ),
- 'User::saveSettings updates actor table for name change'
- );
-
- // For sanity
- $ip = '192.168.12.34';
- $this->db->delete( 'actor', [ 'actor_name' => $ip ], __METHOD__ );
-
- $user = User::newFromName( $ip, false );
- $this->assertFalse( $user->getActorId() > 0, 'Anonymous user has no actor ID by default' );
- $this->assertTrue( $user->getActorId( $this->db ) > 0,
- 'Actor ID can be created for an anonymous user' );
-
- $user = User::newFromName( $ip, false );
- $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be loaded for an anonymous user' );
- $user2 = User::newFromActorId( $user->getActorId() );
- $this->assertEquals( $user->getName(), $user2->getName(),
- 'User::newFromActorId works for an anonymous user' );
- }
-
/**
* @covers User::newFromAnyId
*/
$logs[] = [
'log_type' => 'patrol',
'log_action' => 'patrol',
- 'log_user' => 7251,
+ 'log_actor' => 7251,
'log_params' => '',
'log_timestamp' => $dbw->timestamp( '20041223210426' ),
'log_namespace' => NS_MAIN,
$logs[] = [
'log_type' => 'patrol',
'log_action' => 'autopatrol',
- 'log_user' => 7252,
+ 'log_actor' => 7252,
'log_params' => '',
'log_timestamp' => $dbw->timestamp( '20051223210426' ),
'log_namespace' => NS_MAIN,
$logs[] = [
'log_type' => 'block',
'log_action' => 'block',
- 'log_user' => 7253,
+ 'log_actor' => 7253,
'log_params' => '',
'log_timestamp' => $dbw->timestamp( '20061223210426' ),
'log_namespace' => NS_MAIN,
$logs[] = [
'log_type' => 'patrol',
'log_action' => 'patrol',
- 'log_user' => 7253,
+ 'log_actor' => 7253,
'log_params' => 'nanana',
'log_timestamp' => $dbw->timestamp( '20061223210426' ),
'log_namespace' => NS_MAIN,
$logs[] = [
'log_type' => 'patrol',
'log_action' => 'autopatrol',
- 'log_user' => 7254,
+ 'log_actor' => 7254,
'log_params' => '',
'log_timestamp' => $dbw->timestamp( '20071223210426' ),
'log_namespace' => NS_MAIN,
$logs[] = [
'log_type' => 'patrol',
'log_action' => 'patrol',
- 'log_user' => 7255,
+ 'log_actor' => 7255,
'log_params' => serialize( [ '6::auto' => true ] ),
'log_timestamp' => $dbw->timestamp( '20081223210426' ),
'log_namespace' => NS_MAIN,
$logs[] = [
'log_type' => 'patrol',
'log_action' => 'patrol',
- 'log_user' => 7256,
+ 'log_actor' => 7256,
'log_params' => serialize( [ '6::auto' => false ] ),
'log_timestamp' => $dbw->timestamp( '20091223210426' ),
'log_namespace' => NS_MAIN,
$logs[] = [
'log_type' => 'patrol',
'log_action' => 'patrol',
- 'log_user' => 7257,
+ 'log_actor' => 7257,
'log_params' => "9227851\n0\n1",
'log_timestamp' => $dbw->timestamp( '20081223210426' ),
'log_namespace' => NS_MAIN,
$logs[] = [
'log_type' => 'patrol',
'log_action' => 'patrol',
- 'log_user' => 7258,
+ 'log_actor' => 7258,
'log_params' => "9227851\n0\n0",
'log_timestamp' => $dbw->timestamp( '20091223210426' ),
'log_namespace' => NS_MAIN,
(object)[
'log_type' => 'patrol',
'log_action' => 'patrol',
- 'log_user' => '7251',
+ 'log_actor' => '7251',
],
(object)[
'log_type' => 'patrol',
'log_action' => 'autopatrol',
- 'log_user' => '7252',
+ 'log_actor' => '7252',
],
(object)[
'log_type' => 'block',
'log_action' => 'block',
- 'log_user' => '7253',
+ 'log_actor' => '7253',
],
(object)[
'log_type' => 'patrol',
'log_action' => 'patrol',
- 'log_user' => '7253',
+ 'log_actor' => '7253',
],
(object)[
'log_type' => 'patrol',
'log_action' => 'autopatrol',
- 'log_user' => '7254',
+ 'log_actor' => '7254',
],
(object)[
'log_type' => 'patrol',
'log_action' => 'patrol',
- 'log_user' => '7255',
+ 'log_actor' => '7255',
],
(object)[
'log_type' => 'patrol',
'log_action' => 'patrol',
- 'log_user' => '7256',
+ 'log_actor' => '7256',
],
(object)[
'log_type' => 'patrol',
'log_action' => 'patrol',
- 'log_user' => '7257',
+ 'log_actor' => '7257',
],
(object)[
'log_type' => 'patrol',
'log_action' => 'patrol',
- 'log_user' => '7258',
+ 'log_actor' => '7258',
],
];
$remainingLogs = wfGetDB( DB_REPLICA )->select(
[ 'logging' ],
- [ 'log_type', 'log_action', 'log_user' ],
+ [ 'log_type', 'log_action', 'log_actor' ],
[],
__METHOD__,
[ 'ORDER BY' => 'log_id' ]
$remainingLogs = wfGetDB( DB_REPLICA )->select(
[ 'logging' ],
- [ 'log_type', 'log_action', 'log_user' ],
+ [ 'log_type', 'log_action', 'log_actor' ],
[],
__METHOD__,
[ 'ORDER BY' => 'log_id' ]
$deleted = [
'log_type' => 'patrol',
'log_action' => 'autopatrol',
- 'log_user' => '7254',
+ 'log_actor' => '7254',
];
$notDeleted = [
'log_type' => 'patrol',
'log_action' => 'autopatrol',
- 'log_user' => '7252',
+ 'log_actor' => '7252',
];
$remainingLogs = array_map(