summaryrefslogtreecommitdiff
path: root/includes
diff options
context:
space:
mode:
Diffstat (limited to 'includes')
-rw-r--r--includes/Action.php62
-rw-r--r--includes/AjaxResponse.php4
-rw-r--r--includes/ArrayUtils.php69
-rw-r--r--includes/Article.php282
-rw-r--r--includes/AuthPlugin.php16
-rw-r--r--includes/AutoLoader.php185
-rw-r--r--includes/Autopromote.php12
-rw-r--r--includes/Block.php146
-rw-r--r--includes/CacheHelper.php2
-rw-r--r--includes/Category.php92
-rw-r--r--includes/CategoryPage.php4
-rw-r--r--includes/CategoryViewer.php85
-rw-r--r--includes/Categoryfinder.php39
-rw-r--r--includes/Cdb.php8
-rw-r--r--includes/Cdb_PHP.php24
-rw-r--r--includes/ChangeTags.php33
-rw-r--r--includes/ChangesFeed.php22
-rw-r--r--includes/ChangesList.php150
-rw-r--r--includes/Collation.php295
-rw-r--r--includes/ConfEditor.php12
-rw-r--r--includes/Cookie.php31
-rw-r--r--includes/CryptRand.php22
-rw-r--r--includes/DataUpdate.php10
-rw-r--r--includes/DefaultSettings.php1266
-rw-r--r--includes/DeferredUpdates.php6
-rw-r--r--includes/Defines.php56
-rw-r--r--includes/DeprecatedGlobal.php2
-rw-r--r--includes/EditPage.php1132
-rw-r--r--includes/Exception.php87
-rw-r--r--includes/Export.php119
-rw-r--r--includes/ExternalEdit.php4
-rw-r--r--includes/ExternalStore.php172
-rw-r--r--includes/ExternalUser.php16
-rw-r--r--includes/FakeTitle.php6
-rw-r--r--includes/Fallback.php27
-rw-r--r--includes/Feed.php28
-rw-r--r--includes/FeedUtils.php90
-rw-r--r--includes/FileDeleteForm.php18
-rw-r--r--includes/ForkController.php4
-rw-r--r--includes/FormOptions.php32
-rw-r--r--includes/GitInfo.php16
-rw-r--r--includes/GlobalFunctions.php448
-rw-r--r--includes/HTMLForm.php286
-rw-r--r--includes/HistoryBlob.php51
-rw-r--r--includes/Hooks.php120
-rw-r--r--includes/Html.php230
-rw-r--r--includes/HttpFunctions.php46
-rw-r--r--includes/IP.php42
-rw-r--r--includes/ImageGallery.php30
-rw-r--r--includes/ImagePage.php190
-rw-r--r--includes/Import.php144
-rw-r--r--includes/Init.php4
-rw-r--r--includes/Licenses.php9
-rw-r--r--includes/LinkFilter.php40
-rw-r--r--includes/Linker.php454
-rw-r--r--includes/LinksUpdate.php100
-rw-r--r--includes/MagicWord.php26
-rw-r--r--includes/MappedIterator.php96
-rw-r--r--includes/Message.php165
-rw-r--r--includes/MessageBlobStore.php33
-rw-r--r--includes/Metadata.php57
-rw-r--r--includes/MimeMagic.php251
-rw-r--r--includes/Namespace.php63
-rw-r--r--includes/OutputHandler.php10
-rw-r--r--includes/OutputPage.php390
-rw-r--r--includes/PHPVersionError.php33
-rw-r--r--includes/Pager.php61
-rw-r--r--includes/PathRouter.php10
-rw-r--r--includes/PoolCounter.php14
-rw-r--r--includes/Preferences.php113
-rw-r--r--includes/PrefixSearch.php10
-rw-r--r--includes/ProtectionForm.php62
-rw-r--r--includes/ProxyTools.php6
-rw-r--r--includes/QueryPage.php86
-rw-r--r--includes/RecentChange.php235
-rw-r--r--includes/Revision.php483
-rw-r--r--includes/RevisionList.php4
-rw-r--r--includes/Sanitizer.php207
-rw-r--r--includes/ScopedCallback.php40
-rw-r--r--includes/SeleniumWebSettings.php27
-rw-r--r--includes/Setup.php36
-rw-r--r--includes/SiteConfiguration.php163
-rw-r--r--includes/SiteStats.php22
-rw-r--r--includes/Skin.php76
-rw-r--r--includes/SkinLegacy.php22
-rw-r--r--includes/SkinTemplate.php154
-rw-r--r--includes/SpecialPage.php177
-rw-r--r--includes/SpecialPageFactory.php59
-rw-r--r--includes/SqlDataUpdate.php14
-rw-r--r--includes/SquidPurgeClient.php47
-rw-r--r--includes/Status.php76
-rw-r--r--includes/StreamFile.php19
-rw-r--r--includes/StringUtils.php73
-rw-r--r--includes/StubObject.php23
-rw-r--r--includes/Timestamp.php42
-rw-r--r--includes/Title.php889
-rw-r--r--includes/TitleArray.php4
-rw-r--r--includes/UIDGenerator.php350
-rw-r--r--includes/User.php903
-rw-r--r--includes/UserMailer.php245
-rw-r--r--includes/UserRightsProxy.php14
-rw-r--r--includes/ViewCountUpdate.php17
-rw-r--r--includes/WatchedItem.php75
-rw-r--r--includes/WebRequest.php121
-rw-r--r--includes/WebResponse.php56
-rw-r--r--includes/WebStart.php19
-rw-r--r--includes/Wiki.php88
-rw-r--r--includes/WikiError.php2
-rw-r--r--includes/WikiFilePage.php9
-rw-r--r--includes/WikiMap.php26
-rw-r--r--includes/WikiPage.php1320
-rw-r--r--includes/Xml.php202
-rw-r--r--includes/XmlTypeCheck.php2
-rw-r--r--includes/ZhClient.php10
-rw-r--r--includes/ZhConversion.php792
-rw-r--r--includes/ZipDirectoryReader.php40
-rw-r--r--includes/actions/CachedAction.php36
-rw-r--r--includes/actions/CreditsAction.php13
-rw-r--r--includes/actions/DeleteAction.php11
-rw-r--r--includes/actions/EditAction.php21
-rw-r--r--includes/actions/HistoryAction.php77
-rw-r--r--includes/actions/InfoAction.php320
-rw-r--r--includes/actions/MarkpatrolledAction.php5
-rw-r--r--includes/actions/ProtectAction.php20
-rw-r--r--includes/actions/PurgeAction.php15
-rw-r--r--includes/actions/RawAction.php35
-rw-r--r--includes/actions/RenderAction.php11
-rw-r--r--includes/actions/RevertAction.php6
-rw-r--r--includes/actions/RevisiondeleteAction.php5
-rw-r--r--includes/actions/RollbackAction.php35
-rw-r--r--includes/actions/ViewAction.php11
-rw-r--r--includes/actions/WatchAction.php10
-rw-r--r--includes/api/ApiBase.php386
-rw-r--r--includes/api/ApiBlock.php29
-rw-r--r--includes/api/ApiComparePages.php22
-rw-r--r--includes/api/ApiCreateAccount.php298
-rw-r--r--includes/api/ApiDelete.php25
-rw-r--r--includes/api/ApiDisabled.php8
-rw-r--r--includes/api/ApiEditPage.php181
-rw-r--r--includes/api/ApiEmailUser.php10
-rw-r--r--includes/api/ApiExpandTemplates.php10
-rw-r--r--includes/api/ApiFeedContributions.php26
-rw-r--r--includes/api/ApiFeedWatchlist.php20
-rw-r--r--includes/api/ApiFileRevert.php16
-rw-r--r--includes/api/ApiFormatBase.php48
-rw-r--r--includes/api/ApiFormatDbg.php8
-rw-r--r--includes/api/ApiFormatDump.php8
-rw-r--r--includes/api/ApiFormatJson.php6
-rw-r--r--includes/api/ApiFormatNone.php (renamed from includes/HttpFunctions.old.php)30
-rw-r--r--includes/api/ApiFormatPhp.php8
-rw-r--r--includes/api/ApiFormatRaw.php4
-rw-r--r--includes/api/ApiFormatTxt.php8
-rw-r--r--includes/api/ApiFormatWddx.php8
-rw-r--r--includes/api/ApiFormatXml.php20
-rw-r--r--includes/api/ApiFormatYaml.php6
-rw-r--r--includes/api/ApiHelp.php91
-rw-r--r--includes/api/ApiImageRotate.php232
-rw-r--r--includes/api/ApiImport.php14
-rw-r--r--includes/api/ApiLogin.php8
-rw-r--r--includes/api/ApiLogout.php8
-rw-r--r--includes/api/ApiMain.php289
-rw-r--r--includes/api/ApiModuleManager.php171
-rw-r--r--includes/api/ApiMove.php25
-rw-r--r--includes/api/ApiOpenSearch.php8
-rw-r--r--includes/api/ApiOptions.php68
-rw-r--r--includes/api/ApiPageSet.php560
-rw-r--r--includes/api/ApiParamInfo.php114
-rw-r--r--includes/api/ApiParse.php132
-rw-r--r--includes/api/ApiPatrol.php8
-rw-r--r--includes/api/ApiProtect.php10
-rw-r--r--includes/api/ApiPurge.php142
-rw-r--r--includes/api/ApiQuery.php649
-rw-r--r--includes/api/ApiQueryAllCategories.php16
-rw-r--r--includes/api/ApiQueryAllImages.php63
-rw-r--r--includes/api/ApiQueryAllLinks.php138
-rw-r--r--includes/api/ApiQueryAllMessages.php21
-rw-r--r--includes/api/ApiQueryAllPages.php20
-rw-r--r--includes/api/ApiQueryAllUsers.php18
-rw-r--r--includes/api/ApiQueryBacklinks.php35
-rw-r--r--includes/api/ApiQueryBase.php138
-rw-r--r--includes/api/ApiQueryBlocks.php18
-rw-r--r--includes/api/ApiQueryCategories.php11
-rw-r--r--includes/api/ApiQueryCategoryInfo.php5
-rw-r--r--includes/api/ApiQueryCategoryMembers.php17
-rw-r--r--includes/api/ApiQueryDeletedrevs.php23
-rw-r--r--includes/api/ApiQueryDisabled.php8
-rw-r--r--includes/api/ApiQueryDuplicateFiles.php19
-rw-r--r--includes/api/ApiQueryExtLinksUsage.php20
-rw-r--r--includes/api/ApiQueryExternalLinks.php16
-rw-r--r--includes/api/ApiQueryFilearchive.php35
-rw-r--r--includes/api/ApiQueryIWBacklinks.php10
-rw-r--r--includes/api/ApiQueryIWLinks.php10
-rw-r--r--includes/api/ApiQueryImageInfo.php122
-rw-r--r--includes/api/ApiQueryImages.php17
-rw-r--r--includes/api/ApiQueryInfo.php149
-rw-r--r--includes/api/ApiQueryLangBacklinks.php10
-rw-r--r--includes/api/ApiQueryLangLinks.php12
-rw-r--r--includes/api/ApiQueryLinks.php17
-rw-r--r--includes/api/ApiQueryLogEvents.php51
-rw-r--r--includes/api/ApiQueryORM.php264
-rw-r--r--includes/api/ApiQueryPagePropNames.php116
-rw-r--r--includes/api/ApiQueryPageProps.php17
-rw-r--r--includes/api/ApiQueryPagesWithProp.php189
-rw-r--r--includes/api/ApiQueryProtectedTitles.php9
-rw-r--r--includes/api/ApiQueryQueryPage.php8
-rw-r--r--includes/api/ApiQueryRandom.php6
-rw-r--r--includes/api/ApiQueryRecentChanges.php63
-rw-r--r--includes/api/ApiQueryRevisions.php180
-rw-r--r--includes/api/ApiQuerySearch.php8
-rw-r--r--includes/api/ApiQuerySiteinfo.php57
-rw-r--r--includes/api/ApiQueryStashImageInfo.php7
-rw-r--r--includes/api/ApiQueryTags.php6
-rw-r--r--includes/api/ApiQueryUserContributions.php13
-rw-r--r--includes/api/ApiQueryUserInfo.php10
-rw-r--r--includes/api/ApiQueryUsers.php76
-rw-r--r--includes/api/ApiQueryWatchlist.php18
-rw-r--r--includes/api/ApiQueryWatchlistRaw.php10
-rw-r--r--includes/api/ApiResult.php99
-rw-r--r--includes/api/ApiRollback.php10
-rw-r--r--includes/api/ApiRsd.php12
-rw-r--r--includes/api/ApiSetNotificationTimestamp.php66
-rw-r--r--includes/api/ApiTokens.php70
-rw-r--r--includes/api/ApiUnblock.php8
-rw-r--r--includes/api/ApiUndelete.php18
-rw-r--r--includes/api/ApiUpload.php275
-rw-r--r--includes/api/ApiUserrights.php8
-rw-r--r--includes/api/ApiWatch.php25
-rw-r--r--includes/cache/BacklinkCache.php (renamed from includes/BacklinkCache.php)89
-rw-r--r--includes/cache/CacheDependency.php12
-rw-r--r--includes/cache/FileCacheBase.php6
-rw-r--r--includes/cache/GenderCache.php8
-rw-r--r--includes/cache/HTMLCacheUpdate.php226
-rw-r--r--includes/cache/HTMLFileCache.php7
-rw-r--r--includes/cache/LinkBatch.php2
-rw-r--r--includes/cache/LinkCache.php32
-rw-r--r--includes/cache/LocalisationCache.php (renamed from includes/LocalisationCache.php)29
-rw-r--r--includes/cache/MessageCache.php243
-rw-r--r--includes/cache/ProcessCacheLRU.php19
-rw-r--r--includes/cache/SquidUpdate.php15
-rw-r--r--includes/cache/UserCache.php12
-rw-r--r--includes/clientpool/RedisConnectionPool.php312
-rw-r--r--includes/content/AbstractContent.php444
-rw-r--r--includes/content/Content.php508
-rw-r--r--includes/content/ContentHandler.php1114
-rw-r--r--includes/content/CssContent.php65
-rw-r--r--includes/content/CssContentHandler.php67
-rw-r--r--includes/content/JavaScriptContent.php66
-rw-r--r--includes/content/JavaScriptContentHandler.php67
-rw-r--r--includes/content/MessageContent.php158
-rw-r--r--includes/content/TextContent.php286
-rw-r--r--includes/content/TextContentHandler.php115
-rw-r--r--includes/content/WikitextContent.php322
-rw-r--r--includes/content/WikitextContentHandler.php98
-rw-r--r--includes/context/ContextSource.php22
-rw-r--r--includes/context/DerivativeContext.php24
-rw-r--r--includes/context/IContextSource.php15
-rw-r--r--includes/context/RequestContext.php137
-rw-r--r--includes/dao/DBAccessBase.php92
-rw-r--r--includes/dao/IDBAccessObject.php4
-rw-r--r--includes/db/CloneDatabase.php23
-rw-r--r--includes/db/Database.php625
-rw-r--r--includes/db/DatabaseError.php22
-rw-r--r--includes/db/DatabaseIbm_db2.php1721
-rw-r--r--includes/db/DatabaseMssql.php105
-rw-r--r--includes/db/DatabaseMysql.php61
-rw-r--r--includes/db/DatabaseOracle.php76
-rw-r--r--includes/db/DatabasePostgres.php156
-rw-r--r--includes/db/DatabaseSqlite.php47
-rw-r--r--includes/db/DatabaseUtility.php13
-rw-r--r--includes/db/IORMRow.php5
-rw-r--r--includes/db/IORMTable.php91
-rw-r--r--includes/db/LBFactory.php22
-rw-r--r--includes/db/LBFactory_Multi.php11
-rw-r--r--includes/db/LBFactory_Single.php2
-rw-r--r--includes/db/LoadBalancer.php150
-rw-r--r--includes/db/LoadMonitor.php9
-rw-r--r--includes/db/ORMIterator.php4
-rw-r--r--includes/db/ORMResult.php2
-rw-r--r--includes/db/ORMRow.php47
-rw-r--r--includes/db/ORMTable.php295
-rw-r--r--includes/debug/Debug.php11
-rw-r--r--includes/diff/DairikiDiff.php79
-rw-r--r--includes/diff/DifferenceEngine.php191
-rw-r--r--includes/diff/WikiDiff3.php5
-rw-r--r--includes/extauth/MediaWiki.php24
-rw-r--r--includes/externalstore/ExternalStore.php178
-rw-r--r--includes/externalstore/ExternalStoreDB.php (renamed from includes/ExternalStoreDB.php)114
-rw-r--r--includes/externalstore/ExternalStoreHttp.php (renamed from includes/ExternalStoreHttp.php)20
-rw-r--r--includes/externalstore/ExternalStoreMedium.php60
-rw-r--r--includes/externalstore/ExternalStoreMwstore.php72
-rw-r--r--includes/filebackend/FSFile.php49
-rw-r--r--includes/filebackend/FSFileBackend.php341
-rw-r--r--includes/filebackend/FileBackend.php370
-rw-r--r--includes/filebackend/FileBackendGroup.php10
-rw-r--r--includes/filebackend/FileBackendMultiWrite.php91
-rw-r--r--includes/filebackend/FileBackendStore.php502
-rw-r--r--includes/filebackend/FileOp.php332
-rw-r--r--includes/filebackend/FileOpBatch.php62
-rw-r--r--includes/filebackend/README208
-rw-r--r--includes/filebackend/SwiftFileBackend.php483
-rw-r--r--includes/filebackend/TempFSFile.php13
-rw-r--r--includes/filebackend/filejournal/DBFileJournal.php32
-rw-r--r--includes/filebackend/filejournal/FileJournal.php65
-rw-r--r--includes/filebackend/lockmanager/DBLockManager.php227
-rw-r--r--includes/filebackend/lockmanager/FSLockManager.php42
-rw-r--r--includes/filebackend/lockmanager/LSLockManager.php8
-rw-r--r--includes/filebackend/lockmanager/LockManager.php324
-rw-r--r--includes/filebackend/lockmanager/LockManagerGroup.php54
-rw-r--r--includes/filebackend/lockmanager/MemcLockManager.php94
-rw-r--r--includes/filebackend/lockmanager/QuorumLockManager.php230
-rw-r--r--includes/filebackend/lockmanager/ScopedLock.php102
-rw-r--r--includes/filerepo/FSRepo.php8
-rw-r--r--includes/filerepo/FileRepo.php253
-rw-r--r--includes/filerepo/ForeignAPIRepo.php18
-rw-r--r--includes/filerepo/ForeignDBRepo.php2
-rw-r--r--includes/filerepo/ForeignDBViaLBRepo.php2
-rw-r--r--includes/filerepo/LocalRepo.php53
-rw-r--r--includes/filerepo/README23
-rw-r--r--includes/filerepo/RepoGroup.php24
-rw-r--r--includes/filerepo/file/ArchivedFile.php152
-rw-r--r--includes/filerepo/file/File.php208
-rw-r--r--includes/filerepo/file/ForeignAPIFile.php8
-rw-r--r--includes/filerepo/file/ForeignDBFile.php15
-rw-r--r--includes/filerepo/file/LocalFile.php281
-rw-r--r--includes/filerepo/file/OldLocalFile.php65
-rw-r--r--includes/filerepo/file/UnregisteredLocalFile.php22
-rw-r--r--includes/installer/CliInstaller.php6
-rw-r--r--includes/installer/DatabaseInstaller.php23
-rw-r--r--includes/installer/DatabaseUpdater.php449
-rw-r--r--includes/installer/Ibm_db2Installer.php270
-rw-r--r--includes/installer/Ibm_db2Updater.php91
-rw-r--r--includes/installer/InstallDocFormatter.php6
-rw-r--r--includes/installer/Installer.i18n.php3294
-rw-r--r--includes/installer/Installer.php67
-rw-r--r--includes/installer/LocalSettingsGenerator.php50
-rw-r--r--includes/installer/MysqlInstaller.php17
-rw-r--r--includes/installer/MysqlUpdater.php106
-rw-r--r--includes/installer/OracleInstaller.php9
-rw-r--r--includes/installer/OracleUpdater.php29
-rw-r--r--includes/installer/PostgresInstaller.php21
-rw-r--r--includes/installer/PostgresUpdater.php96
-rw-r--r--includes/installer/SqliteInstaller.php6
-rw-r--r--includes/installer/SqliteUpdater.php34
-rw-r--r--includes/installer/WebInstaller.php37
-rw-r--r--includes/installer/WebInstallerOutput.php8
-rw-r--r--includes/installer/WebInstallerPage.php49
-rw-r--r--includes/interwiki/Interwiki.php96
-rw-r--r--includes/job/Job.php416
-rw-r--r--includes/job/JobQueue.php435
-rw-r--r--includes/job/JobQueueAggregator.php139
-rw-r--r--includes/job/JobQueueAggregatorMemc.php117
-rw-r--r--includes/job/JobQueueAggregatorRedis.php165
-rw-r--r--includes/job/JobQueueDB.php716
-rw-r--r--includes/job/JobQueueGroup.php351
-rw-r--r--includes/job/README81
-rw-r--r--includes/job/RefreshLinksJob.php202
-rw-r--r--includes/job/jobs/AssembleUploadChunksJob.php118
-rw-r--r--includes/job/jobs/DoubleRedirectJob.php (renamed from includes/job/DoubleRedirectJob.php)65
-rw-r--r--includes/job/jobs/DuplicateJob.php59
-rw-r--r--includes/job/jobs/EmaillingJob.php (renamed from includes/job/EmaillingJob.php)5
-rw-r--r--includes/job/jobs/EnotifNotifyJob.php (renamed from includes/job/EnotifNotifyJob.php)3
-rw-r--r--includes/job/jobs/HTMLCacheUpdateJob.php254
-rw-r--r--includes/job/jobs/NullJob.php60
-rw-r--r--includes/job/jobs/PublishStashedFileJob.php130
-rw-r--r--includes/job/jobs/RefreshLinksJob.php226
-rw-r--r--includes/job/jobs/UploadFromUrlJob.php (renamed from includes/job/UploadFromUrlJob.php)4
-rw-r--r--includes/json/FormatJson.php19
-rw-r--r--includes/json/Services_JSON.php24
-rw-r--r--includes/libs/CSSJanus.php15
-rw-r--r--includes/libs/CSSMin.php14
-rw-r--r--includes/libs/GenericArrayObject.php15
-rw-r--r--includes/libs/IEContentAnalyzer.php13
-rw-r--r--includes/libs/IEUrlExtension.php12
-rw-r--r--includes/libs/JavaScriptMinifier.php20
-rw-r--r--includes/libs/jsminplus.php6
-rw-r--r--includes/limit.sh102
-rw-r--r--includes/logging/LogEntry.php78
-rw-r--r--includes/logging/LogEventsList.php84
-rw-r--r--includes/logging/LogFormatter.php234
-rw-r--r--includes/logging/LogPage.php75
-rw-r--r--includes/logging/LogPager.php44
-rw-r--r--includes/media/BMP.php2
-rw-r--r--includes/media/Bitmap.php84
-rw-r--r--includes/media/BitmapMetadataHandler.php60
-rw-r--r--includes/media/DjVu.php10
-rw-r--r--includes/media/DjVuImage.php27
-rw-r--r--includes/media/Exif.php169
-rw-r--r--includes/media/ExifBitmap.php15
-rw-r--r--includes/media/FormatMetadata.php102
-rw-r--r--includes/media/GIF.php26
-rw-r--r--includes/media/GIFMetadataExtractor.php46
-rw-r--r--includes/media/IPTC.php136
-rw-r--r--includes/media/ImageHandler.php7
-rw-r--r--includes/media/Jpeg.php35
-rw-r--r--includes/media/JpegMetadataExtractor.php57
-rw-r--r--includes/media/MediaHandler.php112
-rw-r--r--includes/media/MediaTransformOutput.php43
-rw-r--r--includes/media/PNG.php24
-rw-r--r--includes/media/PNGMetadataExtractor.php4
-rw-r--r--includes/media/SVG.php67
-rw-r--r--includes/media/SVGMetadataExtractor.php39
-rw-r--r--includes/media/Tiff.php7
-rw-r--r--includes/media/XCF.php4
-rw-r--r--includes/media/XMP.php657
-rw-r--r--includes/media/XMPInfo.php63
-rw-r--r--includes/media/XMPValidate.php193
-rw-r--r--includes/mime.types2
-rw-r--r--includes/mobile/DeviceDetection.php459
-rw-r--r--includes/normal/Makefile2
-rw-r--r--includes/normal/RandomTest.php2
-rw-r--r--includes/normal/Utf8CaseGenerate.php6
-rw-r--r--includes/normal/Utf8Test.php7
-rw-r--r--includes/normal/UtfNormal.php32
-rw-r--r--includes/normal/UtfNormalBench.php12
-rw-r--r--includes/normal/UtfNormalDefines.php4
-rw-r--r--includes/normal/UtfNormalGenerate.php4
-rw-r--r--includes/normal/UtfNormalMemStress.php10
-rw-r--r--includes/normal/UtfNormalTest.php8
-rw-r--r--includes/normal/UtfNormalTest2.php4
-rw-r--r--includes/normal/UtfNormalUtil.php6
-rw-r--r--includes/objectcache/APCBagOStuff.php43
-rw-r--r--includes/objectcache/BagOStuff.php131
-rw-r--r--includes/objectcache/DBABagOStuff.php63
-rw-r--r--includes/objectcache/EhcacheBagOStuff.php72
-rw-r--r--includes/objectcache/EmptyBagOStuff.php25
-rw-r--r--includes/objectcache/HashBagOStuff.php28
-rw-r--r--includes/objectcache/MemcachedBagOStuff.php27
-rw-r--r--includes/objectcache/MemcachedClient.php187
-rw-r--r--includes/objectcache/MemcachedPeclBagOStuff.php27
-rw-r--r--includes/objectcache/MemcachedPhpBagOStuff.php3
-rw-r--r--includes/objectcache/MultiWriteBagOStuff.php29
-rw-r--r--includes/objectcache/ObjectCache.php8
-rw-r--r--includes/objectcache/ObjectCacheSessionHandler.php8
-rw-r--r--includes/objectcache/RedisBagOStuff.php189
-rw-r--r--includes/objectcache/SqlBagOStuff.php528
-rw-r--r--includes/objectcache/WinCacheBagOStuff.php47
-rw-r--r--includes/objectcache/XCacheBagOStuff.php40
-rw-r--r--includes/parser/CacheTime.php2
-rw-r--r--includes/parser/CoreLinkFunctions.php12
-rw-r--r--includes/parser/CoreParserFunctions.php76
-rw-r--r--includes/parser/CoreTagHooks.php1
-rw-r--r--includes/parser/DateFormatter.php54
-rw-r--r--includes/parser/LinkHolderArray.php96
-rw-r--r--includes/parser/Parser.php744
-rw-r--r--includes/parser/ParserCache.php7
-rw-r--r--includes/parser/ParserOptions.php86
-rw-r--r--includes/parser/ParserOutput.php208
-rw-r--r--includes/parser/Parser_LinkHooks.php118
-rw-r--r--includes/parser/Preprocessor.php7
-rw-r--r--includes/parser/Preprocessor_DOM.php77
-rw-r--r--includes/parser/Preprocessor_Hash.php76
-rw-r--r--includes/parser/Preprocessor_HipHop.hphp2013
-rw-r--r--includes/parser/StripState.php7
-rw-r--r--includes/parser/Tidy.php58
-rw-r--r--includes/profiler/Profiler.php174
-rw-r--r--includes/profiler/ProfilerSimple.php26
-rw-r--r--includes/profiler/ProfilerSimpleText.php18
-rw-r--r--includes/profiler/ProfilerSimpleTrace.php22
-rw-r--r--includes/profiler/ProfilerSimpleUDP.php2
-rw-r--r--includes/resourceloader/ResourceLoader.php93
-rw-r--r--includes/resourceloader/ResourceLoaderContext.php18
-rw-r--r--includes/resourceloader/ResourceLoaderFileModule.php139
-rw-r--r--includes/resourceloader/ResourceLoaderLanguageDataModule.php34
-rw-r--r--includes/resourceloader/ResourceLoaderModule.php56
-rw-r--r--includes/resourceloader/ResourceLoaderNoscriptModule.php2
-rw-r--r--includes/resourceloader/ResourceLoaderSiteModule.php6
-rw-r--r--includes/resourceloader/ResourceLoaderStartUpModule.php55
-rw-r--r--includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php2
-rw-r--r--includes/resourceloader/ResourceLoaderUserTokensModule.php3
-rw-r--r--includes/resourceloader/ResourceLoaderWikiModule.php36
-rw-r--r--includes/revisiondelete/RevisionDelete.php13
-rw-r--r--includes/revisiondelete/RevisionDeleteAbstracts.php7
-rw-r--r--includes/revisiondelete/RevisionDeleter.php4
-rw-r--r--includes/search/SearchEngine.php83
-rw-r--r--includes/search/SearchIBM_DB2.php234
-rw-r--r--includes/search/SearchMssql.php19
-rw-r--r--includes/search/SearchMySQL.php8
-rw-r--r--includes/search/SearchOracle.php142
-rw-r--r--includes/search/SearchPostgres.php44
-rw-r--r--includes/search/SearchSqlite.php15
-rw-r--r--includes/search/SearchUpdate.php13
-rw-r--r--includes/site/MediaWikiSite.php352
-rw-r--r--includes/site/Site.php702
-rw-r--r--includes/site/SiteList.php300
-rw-r--r--includes/site/SiteSQLStore.php491
-rw-r--r--includes/site/SiteStore.php85
-rw-r--r--includes/specials/SpecialActiveusers.php37
-rw-r--r--includes/specials/SpecialAllmessages.php46
-rw-r--r--includes/specials/SpecialAllpages.php96
-rw-r--r--includes/specials/SpecialAncientpages.php8
-rw-r--r--includes/specials/SpecialBlankpage.php2
-rw-r--r--includes/specials/SpecialBlock.php100
-rw-r--r--includes/specials/SpecialBlockList.php34
-rw-r--r--includes/specials/SpecialBlockme.php6
-rw-r--r--includes/specials/SpecialBooksources.php42
-rw-r--r--includes/specials/SpecialBrokenRedirects.php63
-rw-r--r--includes/specials/SpecialCachedPage.php4
-rw-r--r--includes/specials/SpecialCategories.php8
-rw-r--r--includes/specials/SpecialChangeEmail.php14
-rw-r--r--includes/specials/SpecialChangePassword.php73
-rw-r--r--includes/specials/SpecialComparePages.php31
-rw-r--r--includes/specials/SpecialConfirmemail.php10
-rw-r--r--includes/specials/SpecialContributions.php89
-rw-r--r--includes/specials/SpecialDeadendpages.php4
-rw-r--r--includes/specials/SpecialDeletedContributions.php19
-rw-r--r--includes/specials/SpecialDisambiguations.php14
-rw-r--r--includes/specials/SpecialDoubleRedirects.php81
-rw-r--r--includes/specials/SpecialEditWatchlist.php57
-rw-r--r--includes/specials/SpecialEmailuser.php13
-rw-r--r--includes/specials/SpecialExport.php17
-rw-r--r--includes/specials/SpecialFewestrevisions.php5
-rw-r--r--includes/specials/SpecialFileDuplicateSearch.php24
-rw-r--r--includes/specials/SpecialFilepath.php6
-rw-r--r--includes/specials/SpecialImport.php36
-rw-r--r--includes/specials/SpecialJavaScriptTest.php19
-rw-r--r--includes/specials/SpecialLinkSearch.php45
-rw-r--r--includes/specials/SpecialListfiles.php12
-rw-r--r--includes/specials/SpecialListgrouprights.php26
-rw-r--r--includes/specials/SpecialListredirects.php20
-rw-r--r--includes/specials/SpecialListusers.php44
-rw-r--r--includes/specials/SpecialLockdb.php4
-rw-r--r--includes/specials/SpecialLog.php4
-rw-r--r--includes/specials/SpecialLonelypages.php9
-rw-r--r--includes/specials/SpecialLongpages.php4
-rw-r--r--includes/specials/SpecialMIMEsearch.php19
-rw-r--r--includes/specials/SpecialMergeHistory.php53
-rw-r--r--includes/specials/SpecialMostcategories.php13
-rw-r--r--includes/specials/SpecialMostimages.php12
-rw-r--r--includes/specials/SpecialMostinterwikis.php13
-rw-r--r--includes/specials/SpecialMostlinked.php15
-rw-r--r--includes/specials/SpecialMostlinkedcategories.php12
-rw-r--r--includes/specials/SpecialMostlinkedtemplates.php5
-rw-r--r--includes/specials/SpecialMostrevisions.php4
-rw-r--r--includes/specials/SpecialMovepage.php65
-rw-r--r--includes/specials/SpecialNewimages.php23
-rw-r--r--includes/specials/SpecialNewpages.php57
-rw-r--r--includes/specials/SpecialPagesWithProp.php138
-rw-r--r--includes/specials/SpecialPasswordReset.php26
-rw-r--r--includes/specials/SpecialPopularpages.php10
-rw-r--r--includes/specials/SpecialPreferences.php6
-rw-r--r--includes/specials/SpecialPrefixindex.php30
-rw-r--r--includes/specials/SpecialProtectedpages.php46
-rw-r--r--includes/specials/SpecialProtectedtitles.php28
-rw-r--r--includes/specials/SpecialRandompage.php10
-rw-r--r--includes/specials/SpecialRandomredirect.php2
-rw-r--r--includes/specials/SpecialRecentchanges.php100
-rw-r--r--includes/specials/SpecialRecentchangeslinked.php46
-rw-r--r--includes/specials/SpecialRevisiondelete.php45
-rw-r--r--includes/specials/SpecialSearch.php131
-rw-r--r--includes/specials/SpecialShortpages.php4
-rw-r--r--includes/specials/SpecialSpecialpages.php17
-rw-r--r--includes/specials/SpecialStatistics.php63
-rw-r--r--includes/specials/SpecialTags.php4
-rw-r--r--includes/specials/SpecialUnblock.php29
-rw-r--r--includes/specials/SpecialUncategorizedcategories.php13
-rw-r--r--includes/specials/SpecialUncategorizedimages.php3
-rw-r--r--includes/specials/SpecialUncategorizedpages.php11
-rw-r--r--includes/specials/SpecialUndelete.php352
-rw-r--r--includes/specials/SpecialUnlockdb.php4
-rw-r--r--includes/specials/SpecialUnusedcategories.php8
-rw-r--r--includes/specials/SpecialUnusedimages.php3
-rw-r--r--includes/specials/SpecialUnusedtemplates.php18
-rw-r--r--includes/specials/SpecialUnwatchedpages.php17
-rw-r--r--includes/specials/SpecialUpload.php98
-rw-r--r--includes/specials/SpecialUploadStash.php34
-rw-r--r--includes/specials/SpecialUserlogin.php330
-rw-r--r--includes/specials/SpecialUserlogout.php9
-rw-r--r--includes/specials/SpecialUserrights.php106
-rw-r--r--includes/specials/SpecialVersion.php137
-rw-r--r--includes/specials/SpecialWantedcategories.php4
-rw-r--r--includes/specials/SpecialWantedfiles.php6
-rw-r--r--includes/specials/SpecialWantedpages.php11
-rw-r--r--includes/specials/SpecialWantedtemplates.php4
-rw-r--r--includes/specials/SpecialWatchlist.php116
-rw-r--r--includes/specials/SpecialWhatlinkshere.php22
-rw-r--r--includes/specials/SpecialWithoutinterwiki.php4
-rw-r--r--includes/templates/NoLocalSettings.php4
-rw-r--r--includes/templates/Usercreate.php58
-rw-r--r--includes/templates/Userlogin.php4
-rw-r--r--includes/tidy.conf5
-rw-r--r--includes/upload/UploadBase.php159
-rw-r--r--includes/upload/UploadFromChunks.php56
-rw-r--r--includes/upload/UploadFromFile.php12
-rw-r--r--includes/upload/UploadFromStash.php17
-rw-r--r--includes/upload/UploadFromUrl.php22
-rw-r--r--includes/upload/UploadStash.php74
-rw-r--r--includes/zhtable/Makefile336
-rw-r--r--includes/zhtable/Makefile.py391
-rw-r--r--includes/zhtable/README33
-rw-r--r--includes/zhtable/printutf8.c99
-rw-r--r--includes/zhtable/simp2trad.manual372
-rw-r--r--includes/zhtable/simp2trad_noconvert.manual139
-rw-r--r--includes/zhtable/simp2trad_supp_set.manual2
-rw-r--r--includes/zhtable/simpphrases.manual2239
-rw-r--r--includes/zhtable/simpphrases_exclude.manual21
-rw-r--r--includes/zhtable/toCN.manual275
-rw-r--r--includes/zhtable/toHK.manual2300
-rw-r--r--includes/zhtable/toSG.manual21
-rw-r--r--includes/zhtable/toSimp.manual165
-rw-r--r--includes/zhtable/toTW.manual411
-rw-r--r--includes/zhtable/toTrad.manual186
-rw-r--r--includes/zhtable/trad2simp.manual150
-rw-r--r--includes/zhtable/trad2simp_noconvert.manual5
-rw-r--r--includes/zhtable/trad2simp_supp_set.manual3
-rw-r--r--includes/zhtable/tradphrases.manual4310
-rw-r--r--includes/zhtable/tradphrases_exclude.manual330
606 files changed, 38571 insertions, 33188 deletions
diff --git a/includes/Action.php b/includes/Action.php
index 51922251..2e0c88ba 100644
--- a/includes/Action.php
+++ b/includes/Action.php
@@ -32,13 +32,13 @@
*
* Actions generally fall into two groups: the show-a-form-then-do-something-with-the-input
* format (protect, delete, move, etc), and the just-do-something format (watch, rollback,
- * patrol, etc). The FormAction and FormlessAction classes respresent these two groups.
+ * patrol, etc). The FormAction and FormlessAction classes represent these two groups.
*/
abstract class Action {
/**
* Page on which we're performing the action
- * @var Page $page
+ * @var WikiPage|Article|ImagePage|CategoryPage|Page $page
*/
protected $page;
@@ -61,7 +61,7 @@ abstract class Action {
* @param $overrides Array
* @return bool|null|string
*/
- private final static function getClass( $action, array $overrides ) {
+ final private static function getClass( $action, array $overrides ) {
global $wgActions;
$action = strtolower( $action );
@@ -88,7 +88,7 @@ abstract class Action {
* @return Action|bool|null false if the action is disabled, null
* if it is not recognised
*/
- public final static function factory( $action, Page $page, IContextSource $context = null ) {
+ final public static function factory( $action, Page $page, IContextSource $context = null ) {
$class = self::getClass( $action, $page->getActionOverrides() );
if ( $class ) {
$obj = new $class( $page, $context );
@@ -106,7 +106,7 @@ abstract class Action {
* @param $context IContextSource
* @return string: action name
*/
- public final static function getActionName( IContextSource $context ) {
+ final public static function getActionName( IContextSource $context ) {
global $wgActions;
$request = $context->getRequest();
@@ -147,10 +147,10 @@ abstract class Action {
/**
* Check if a given action is recognised, even if it's disabled
*
- * @param $name String: name of an action
+ * @param string $name name of an action
* @return Bool
*/
- public final static function exists( $name ) {
+ final public static function exists( $name ) {
return self::getClass( $name, array() ) !== null;
}
@@ -158,7 +158,7 @@ abstract class Action {
* Get the IContextSource in use here
* @return IContextSource
*/
- public final function getContext() {
+ final public function getContext() {
if ( $this->context instanceof IContextSource ) {
return $this->context;
}
@@ -170,7 +170,7 @@ abstract class Action {
*
* @return WebRequest
*/
- public final function getRequest() {
+ final public function getRequest() {
return $this->getContext()->getRequest();
}
@@ -179,7 +179,7 @@ abstract class Action {
*
* @return OutputPage
*/
- public final function getOutput() {
+ final public function getOutput() {
return $this->getContext()->getOutput();
}
@@ -188,7 +188,7 @@ abstract class Action {
*
* @return User
*/
- public final function getUser() {
+ final public function getUser() {
return $this->getContext()->getUser();
}
@@ -197,7 +197,7 @@ abstract class Action {
*
* @return Skin
*/
- public final function getSkin() {
+ final public function getSkin() {
return $this->getContext()->getSkin();
}
@@ -206,7 +206,7 @@ abstract class Action {
*
* @return Language
*/
- public final function getLanguage() {
+ final public function getLanguage() {
return $this->getContext()->getLanguage();
}
@@ -216,7 +216,7 @@ abstract class Action {
* @deprecated 1.19 Use getLanguage instead
* @return Language
*/
- public final function getLang() {
+ final public function getLang() {
wfDeprecated( __METHOD__, '1.19' );
return $this->getLanguage();
}
@@ -225,7 +225,7 @@ abstract class Action {
* Shortcut to get the Title object from the page
* @return Title
*/
- public final function getTitle() {
+ final public function getTitle() {
return $this->page->getTitle();
}
@@ -235,7 +235,7 @@ abstract class Action {
*
* @return Message object
*/
- public final function msg() {
+ final public function msg() {
$params = func_get_args();
return call_user_func_array( array( $this->getContext(), 'msg' ), $params );
}
@@ -255,7 +255,7 @@ abstract class Action {
* Return the name of the action this object responds to
* @return String lowercase
*/
- public abstract function getName();
+ abstract public function getName();
/**
* Get the permission required to perform this action. Often, but not always,
@@ -272,7 +272,7 @@ abstract class Action {
* must throw subclasses of ErrorPageError
*
* @param $user User: the user to check, or null to use the context user
- * @throws ErrorPageError
+ * @throws UserBlockedError|ReadOnlyError|PermissionsError
* @return bool True on success
*/
protected function checkCanExecute( User $user ) {
@@ -350,13 +350,13 @@ abstract class Action {
* $this->getOutput(), etc.
* @throws ErrorPageError
*/
- public abstract function show();
+ abstract public function show();
/**
* Execute the action in a silent fashion: do not display anything or release any errors.
* @return Bool whether execution was successful
*/
- public abstract function execute();
+ abstract public function execute();
}
/**
@@ -368,7 +368,7 @@ abstract class FormAction extends Action {
* Get an HTMLForm descriptor array
* @return Array
*/
- protected abstract function getFormFields();
+ abstract protected function getFormFields();
/**
* Add pre- or post-text to the form
@@ -388,7 +388,7 @@ abstract class FormAction extends Action {
protected function alterForm( HTMLForm $form ) {}
/**
- * Get the HTMLForm to control behaviour
+ * Get the HTMLForm to control behavior
* @return HTMLForm|null
*/
protected function getForm() {
@@ -406,7 +406,7 @@ abstract class FormAction extends Action {
$this->getRequest()->getQueryValues(),
array( 'action' => null, 'title' => null )
);
- $form->addHiddenField( 'redirectparams', wfArrayToCGI( $params ) );
+ $form->addHiddenField( 'redirectparams', wfArrayToCgi( $params ) );
$form->addPreText( $this->preText() );
$form->addPostText( $this->postText() );
@@ -425,21 +425,21 @@ abstract class FormAction extends Action {
* @param $data Array
* @return Bool|Array true for success, false for didn't-try, array of errors on failure
*/
- public abstract function onSubmit( $data );
+ abstract public function onSubmit( $data );
/**
* Do something exciting on successful processing of the form. This might be to show
* a confirmation message (watch, rollback, etc) or to redirect somewhere else (edit,
* protect, etc).
*/
- public abstract function onSuccess();
+ abstract public function onSuccess();
/**
* The basic pattern for actions is to display some sort of HTMLForm UI, maybe with
* some stuff underneath (history etc); to do some processing on submission of that
* form (delete, protect, etc) and to do something exciting on 'success', be that
* display something new or redirect to somewhere. Some actions have more exotic
- * behaviour, but that's what subclassing is for :D
+ * behavior, but that's what subclassing is for :D
*/
public function show() {
$this->setHeaders();
@@ -455,9 +455,10 @@ abstract class FormAction extends Action {
/**
* @see Action::execute()
- * @throws ErrorPageError
+ *
* @param $data array|null
* @param $captureErrors bool
+ * @throws ErrorPageError|Exception
* @return bool
*/
public function execute( array $data = null, $captureErrors = true ) {
@@ -507,7 +508,7 @@ abstract class FormlessAction extends Action {
* @return String|null will be added to the HTMLForm if present, or just added to the
* output if not. Return null to not add anything
*/
- public abstract function onView();
+ abstract public function onView();
/**
* We don't want an HTMLForm
@@ -544,8 +545,9 @@ abstract class FormlessAction extends Action {
/**
* Execute the action silently, not giving any output. Since these actions don't have
* forms, they probably won't have any data, but some (eg rollback) may do
- * @param $data Array values that would normally be in the GET request
- * @param $captureErrors Bool whether to catch exceptions and just return false
+ * @param array $data values that would normally be in the GET request
+ * @param bool $captureErrors whether to catch exceptions and just return false
+ * @throws ErrorPageError|Exception
* @return Bool whether execution was successful
*/
public function execute( array $data = null, $captureErrors = true ) {
diff --git a/includes/AjaxResponse.php b/includes/AjaxResponse.php
index 6bf94ccb..138f808a 100644
--- a/includes/AjaxResponse.php
+++ b/includes/AjaxResponse.php
@@ -172,7 +172,7 @@ class AjaxResponse {
# tell the client to use a cached copy, without a way to purge it.
if ( $wgUseSquid ) {
- # Expect explicite purge of the proxy cache, but require end user agents
+ # Expect explicit purge of the proxy cache, but require end user agents
# to revalidate against the proxy on each visit.
# Surrogate-Control controls our Squid, Cache-Control downstream caches
@@ -204,7 +204,7 @@ class AjaxResponse {
/**
* checkLastModified tells the client to use the client-cached response if
- * possible. If sucessful, the AjaxResponse is disabled so that
+ * possible. If successful, the AjaxResponse is disabled so that
* any future call to AjaxResponse::printText() have no effect.
*
* @param $timestamp string
diff --git a/includes/ArrayUtils.php b/includes/ArrayUtils.php
new file mode 100644
index 00000000..0b74f06a
--- /dev/null
+++ b/includes/ArrayUtils.php
@@ -0,0 +1,69 @@
+<?php
+
+class ArrayUtils {
+ /**
+ * Sort the given array in a pseudo-random order which depends only on the
+ * given key and each element value. This is typically used for load
+ * balancing between servers each with a local cache.
+ *
+ * Keys are preserved. The input array is modified in place.
+ *
+ * Note: Benchmarking on PHP 5.3 and 5.4 indicates that for small
+ * strings, md5() is only 10% slower than hash('joaat',...) etc.,
+ * since the function call overhead dominates. So there's not much
+ * justification for breaking compatibility with installations
+ * compiled with ./configure --disable-hash.
+ *
+ * @param $array The array to sort
+ * @param $key The string key
+ * @param $separator A separator used to delimit the array elements and the
+ * key. This can be chosen to provide backwards compatibility with
+ * various consistent hash implementations that existed before this
+ * function was introduced.
+ */
+ public static function consistentHashSort( &$array, $key, $separator = "\000" ) {
+ $hashes = array();
+ foreach ( $array as $elt ) {
+ $hashes[$elt] = md5( $elt . $separator . $key );
+ }
+ uasort( $array, function ( $a, $b ) use ( $hashes ) {
+ return strcmp( $hashes[$a], $hashes[$b] );
+ } );
+ }
+
+ /**
+ * Given an array of non-normalised probabilities, this function will select
+ * an element and return the appropriate key
+ *
+ * @param $weights array
+ *
+ * @return bool|int|string
+ */
+ public static function pickRandom( $weights ){
+ if ( !is_array( $weights ) || count( $weights ) == 0 ) {
+ return false;
+ }
+
+ $sum = array_sum( $weights );
+ if ( $sum == 0 ) {
+ # No loads on any of them
+ # In previous versions, this triggered an unweighted random selection,
+ # but this feature has been removed as of April 2006 to allow for strict
+ # separation of query groups.
+ return false;
+ }
+ $max = mt_getrandmax();
+ $rand = mt_rand( 0, $max ) / $max * $sum;
+
+ $sum = 0;
+ foreach ( $weights as $i => $w ) {
+ $sum += $w;
+ # Do not return keys if they have 0 weight.
+ # Note that the "all 0 weight" case is handed above
+ if ( $w > 0 && $sum >= $rand ) {
+ break;
+ }
+ }
+ return $i;
+ }
+}
diff --git a/includes/Article.php b/includes/Article.php
index 9ab4b6ba..9b4afe44 100644
--- a/includes/Article.php
+++ b/includes/Article.php
@@ -33,7 +33,7 @@
*
* @internal documentation reviewed 15 Mar 2010
*/
-class Article extends Page {
+class Article implements Page {
/**@{{
* @private
*/
@@ -57,10 +57,17 @@ class Article extends Page {
public $mParserOptions;
/**
- * Content of the revision we are working on
+ * Text of the revision we are working on
* @var string $mContent
*/
- var $mContent; // !<
+ var $mContent; // !< #BC cruft
+
+ /**
+ * Content of the revision we are working on
+ * @var Content
+ * @since 1.21
+ */
+ var $mContentObject; // !<
/**
* Is the content ($mContent) already loaded?
@@ -127,7 +134,7 @@ class Article extends Page {
/**
* Constructor from a page id
- * @param $id Int article ID to load
+ * @param int $id article ID to load
* @return Article|null
*/
public static function newFromID( $id ) {
@@ -231,9 +238,32 @@ class Article extends Page {
* This function has side effects! Do not use this function if you
* only want the real revision text if any.
*
+ * @deprecated in 1.21; use WikiPage::getContent() instead
+ *
* @return string Return the text of this revision
*/
public function getContent() {
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+ $content = $this->getContentObject();
+ return ContentHandler::getContentText( $content );
+ }
+
+ /**
+ * Returns a Content object representing the pages effective display content,
+ * not necessarily the revision's content!
+ *
+ * Note that getContent/loadContent do not follow redirects anymore.
+ * If you need to fetch redirectable content easily, try
+ * the shortcut in WikiPage::getRedirectTarget()
+ *
+ * This function has side effects! Do not use this function if you
+ * only want the real revision text if any.
+ *
+ * @return Content Return the content of this revision
+ *
+ * @since 1.21
+ */
+ protected function getContentObject() {
wfProfileIn( __METHOD__ );
if ( $this->mPage->getID() === 0 ) {
@@ -244,19 +274,19 @@ class Article extends Page {
if ( $text === false ) {
$text = '';
}
+
+ $content = ContentHandler::makeContent( $text, $this->getTitle() );
} else {
$message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
- $text = wfMessage( $message )->text();
+ $content = new MessageContent( $message, null, 'parsemag' );
}
- wfProfileOut( __METHOD__ );
-
- return $text;
} else {
- $this->fetchContent();
- wfProfileOut( __METHOD__ );
-
- return $this->mContent;
+ $this->fetchContentObject();
+ $content = $this->mContentObject;
}
+
+ wfProfileOut( __METHOD__ );
+ return $content;
}
/**
@@ -336,22 +366,60 @@ class Article extends Page {
* Get text of an article from database
* Does *NOT* follow redirects.
*
+ * @protected
+ * @note this is really internal functionality that should really NOT be used by other functions. For accessing
+ * article content, use the WikiPage class, especially WikiBase::getContent(). However, a lot of legacy code
+ * uses this method to retrieve page text from the database, so the function has to remain public for now.
+ *
* @return mixed string containing article contents, or false if null
+ * @deprecated in 1.21, use WikiPage::getContent() instead
*/
- function fetchContent() {
- if ( $this->mContentLoaded ) {
+ function fetchContent() { #BC cruft!
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
+ if ( $this->mContentLoaded && $this->mContent ) {
return $this->mContent;
}
wfProfileIn( __METHOD__ );
+ $content = $this->fetchContentObject();
+
+ $this->mContent = ContentHandler::getContentText( $content ); #@todo: get rid of mContent everywhere!
+ ContentHandler::runLegacyHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
+
+ wfProfileOut( __METHOD__ );
+
+ return $this->mContent;
+ }
+
+ /**
+ * Get text content object
+ * Does *NOT* follow redirects.
+ * TODO: when is this null?
+ *
+ * @note code that wants to retrieve page content from the database should use WikiPage::getContent().
+ *
+ * @return Content|null|boolean false
+ *
+ * @since 1.21
+ */
+ protected function fetchContentObject() {
+ if ( $this->mContentLoaded ) {
+ return $this->mContentObject;
+ }
+
+ wfProfileIn( __METHOD__ );
+
$this->mContentLoaded = true;
+ $this->mContent = null;
$oldid = $this->getOldID();
# Pre-fill content with error message so that if something
# fails we'll have something telling us what we intended.
- $this->mContent = wfMessage( 'missing-revision', $oldid )->plain();
+ //XXX: this isn't page content but a UI message. horrible.
+ $this->mContentObject = new MessageContent( 'missing-revision', array( $oldid ), array() );
if ( $oldid ) {
# $this->mRevision might already be fetched by getOldIDFromRequest()
@@ -371,6 +439,7 @@ class Article extends Page {
}
$this->mRevision = $this->mPage->getRevision();
+
if ( !$this->mRevision ) {
wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " . $this->mPage->getLatest() . "\n" );
wfProfileOut( __METHOD__ );
@@ -380,14 +449,14 @@ class Article extends Page {
// @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
// We should instead work with the Revision object when we need it...
- $this->mContent = $this->mRevision->getText( Revision::FOR_THIS_USER ); // Loads if user is allowed
+ $this->mContentObject = $this->mRevision->getContent( Revision::FOR_THIS_USER, $this->getContext()->getUser() ); // Loads if user is allowed
$this->mRevIdFetched = $this->mRevision->getId();
- wfRunHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
+ wfRunHooks( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) );
wfProfileOut( __METHOD__ );
- return $this->mContent;
+ return $this->mContentObject;
}
/**
@@ -420,7 +489,7 @@ class Article extends Page {
* @return Revision|null
*/
public function getRevisionFetched() {
- $this->fetchContent();
+ $this->fetchContentObject();
return $this->mRevision;
}
@@ -443,7 +512,7 @@ class Article extends Page {
* page of the given title.
*/
public function view() {
- global $wgParser, $wgUseFileCache, $wgUseETag, $wgDebugToolbar;
+ global $wgUseFileCache, $wgUseETag, $wgDebugToolbar;
wfProfileIn( __METHOD__ );
@@ -580,7 +649,7 @@ class Article extends Page {
break;
case 3:
# This will set $this->mRevision if needed
- $this->fetchContent();
+ $this->fetchContentObject();
# Are we looking at an old revision
if ( $oldid && $this->mRevision ) {
@@ -604,18 +673,25 @@ class Article extends Page {
wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
$this->showCssOrJsPage();
$outputDone = true;
- } elseif( !wfRunHooks( 'ArticleViewCustom', array( $this->mContent, $this->getTitle(), $outputPage ) ) ) {
+ } elseif( !wfRunHooks( 'ArticleContentViewCustom',
+ array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) {
+
+ # Allow extensions do their own custom view for certain pages
+ $outputDone = true;
+ } elseif( !ContentHandler::runLegacyHooks( 'ArticleViewCustom',
+ array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) {
+
# Allow extensions do their own custom view for certain pages
$outputDone = true;
} else {
- $text = $this->getContent();
- $rt = Title::newFromRedirectArray( $text );
+ $content = $this->getContentObject();
+ $rt = $content ? $content->getRedirectChain() : null;
if ( $rt ) {
wfDebug( __METHOD__ . ": showing redirect=no page\n" );
# Viewing a redirect page (e.g. with parameter redirect=no)
$outputPage->addHTML( $this->viewRedirect( $rt ) );
# Parse just to get categories, displaytitle, etc.
- $this->mParserOutput = $wgParser->parse( $text, $this->getTitle(), $parserOptions );
+ $this->mParserOutput = $content->getParserOutput( $this->getTitle(), $oldid, $parserOptions, false );
$outputPage->addParserOutputNoText( $this->mParserOutput );
$outputDone = true;
}
@@ -625,8 +701,8 @@ class Article extends Page {
# Run the parse, protected by a pool counter
wfDebug( __METHOD__ . ": doing uncached parse\n" );
- $poolArticleView = new PoolWorkArticleView( $this, $parserOptions,
- $this->getRevIdFetched(), $useParserCache, $this->getContent() );
+ $poolArticleView = new PoolWorkArticleView( $this->getPage(), $parserOptions,
+ $this->getRevIdFetched(), $useParserCache, $this->getContentObject() );
if ( !$poolArticleView->execute() ) {
$error = $poolArticleView->getError();
@@ -690,6 +766,8 @@ class Article extends Page {
$this->showViewFooter();
$this->mPage->doViewUpdates( $user );
+ $outputPage->addModules( 'mediawiki.action.view.postEdit' );
+
wfProfileOut( __METHOD__ );
}
@@ -708,6 +786,8 @@ class Article extends Page {
/**
* Show a diff page according to current request variables. For use within
* Article::view() only, other callers should use the DifferenceEngine class.
+ *
+ * @todo: make protected
*/
public function showDiffPage() {
$request = $this->getContext()->getRequest();
@@ -719,7 +799,17 @@ class Article extends Page {
$unhide = $request->getInt( 'unhide' ) == 1;
$oldid = $this->getOldID();
- $de = new DifferenceEngine( $this->getContext(), $oldid, $diff, $rcid, $purge, $unhide );
+ $rev = $this->getRevisionFetched();
+
+ if ( !$rev ) {
+ $this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) );
+ $this->getContext()->getOutput()->addWikiMsg( 'difference-missing-revision', $oldid, 1 );
+ return;
+ }
+
+ $contentHandler = $rev->getContentHandler();
+ $de = $contentHandler->createDifferenceEngine( $this->getContext(), $oldid, $diff, $rcid, $purge, $unhide );
+
// DifferenceEngine directly fetched the revision:
$this->mRevIdFetched = $de->mNewid;
$de->showDiffPage( $diffOnly );
@@ -736,29 +826,34 @@ class Article extends Page {
*
* This is hooked by SyntaxHighlight_GeSHi to do syntax highlighting of these
* page views.
+ *
+ * @param bool $showCacheHint whether to show a message telling the user to clear the browser cache (default: true).
*/
- protected function showCssOrJsPage() {
- $dir = $this->getContext()->getLanguage()->getDir();
- $lang = $this->getContext()->getLanguage()->getCode();
-
+ protected function showCssOrJsPage( $showCacheHint = true ) {
$outputPage = $this->getContext()->getOutput();
- $outputPage->wrapWikiMsg( "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
- 'clearyourcache' );
- // Give hooks a chance to customise the output
- if ( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->getTitle(), $outputPage ) ) ) {
- // Wrap the whole lot in a <pre> and don't parse
- $m = array();
- preg_match( '!\.(css|js)$!u', $this->getTitle()->getText(), $m );
- $outputPage->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
- $outputPage->addHTML( htmlspecialchars( $this->mContent ) );
- $outputPage->addHTML( "\n</pre>\n" );
+ if ( $showCacheHint ) {
+ $dir = $this->getContext()->getLanguage()->getDir();
+ $lang = $this->getContext()->getLanguage()->getCode();
+
+ $outputPage->wrapWikiMsg( "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
+ 'clearyourcache' );
+ }
+
+ $this->fetchContentObject();
+
+ if ( $this->mContentObject ) {
+ // Give hooks a chance to customise the output
+ if ( ContentHandler::runLegacyHooks( 'ShowRawCssJs', array( $this->mContentObject, $this->getTitle(), $outputPage ) ) ) {
+ $po = $this->mContentObject->getParserOutput( $this->getTitle() );
+ $outputPage->addHTML( $po->getText() );
+ }
}
}
/**
* Get the robot policy to be used for the current view
- * @param $action String the action= GET parameter
+ * @param string $action the action= GET parameter
* @param $pOutput ParserOutput
* @return Array the policy that should be set
* TODO: actions other than 'view'
@@ -768,15 +863,21 @@ class Article extends Page {
$ns = $this->getTitle()->getNamespace();
- if ( $ns == NS_USER || $ns == NS_USER_TALK ) {
- # Don't index user and user talk pages for blocked users (bug 11443)
- if ( !$this->getTitle()->isSubpage() ) {
- if ( Block::newFromTarget( null, $this->getTitle()->getText() ) instanceof Block ) {
- return array(
- 'index' => 'noindex',
- 'follow' => 'nofollow'
- );
- }
+ # Don't index user and user talk pages for blocked users (bug 11443)
+ if ( ( $ns == NS_USER || $ns == NS_USER_TALK ) && !$this->getTitle()->isSubpage() ) {
+ $specificTarget = null;
+ $vagueTarget = null;
+ $titleText = $this->getTitle()->getText();
+ if ( IP::isValid( $titleText ) ) {
+ $vagueTarget = $titleText;
+ } else {
+ $specificTarget = $titleText;
+ }
+ if ( Block::newFromTarget( $specificTarget, $vagueTarget ) instanceof Block ) {
+ return array(
+ 'index' => 'noindex',
+ 'follow' => 'nofollow'
+ );
}
}
@@ -892,9 +993,7 @@ class Article extends Page {
}
// Add a <link rel="canonical"> tag
- $outputPage->addLink( array( 'rel' => 'canonical',
- 'href' => $this->getTitle()->getLocalURL() )
- );
+ $outputPage->setCanonicalUrl( $this->getTitle()->getLocalURL() );
// Tell the output object that the user arrived at this article through a redirect
$outputPage->setRedirectedFrom( $this->mRedirectedFrom );
@@ -948,6 +1047,8 @@ class Article extends Page {
* If patrol is possible, output a patrol UI box. This is called from the
* footer section of ordinary page views. If patrol is not possible or not
* desired, does nothing.
+ * Side effect: When the patrol link is build, this method will call
+ * OutputPage::preventClickjacking() and load mediawiki.page.patrol.ajax.
*/
public function showPatrolFooter() {
$request = $this->getContext()->getRequest();
@@ -960,7 +1061,9 @@ class Article extends Page {
}
$token = $user->getEditToken( $rcid );
+
$outputPage->preventClickjacking();
+ $outputPage->addModules( 'mediawiki.page.patrol.ajax' );
$link = Linker::linkKnown(
$this->getTitle(),
@@ -987,6 +1090,7 @@ class Article extends Page {
public function showMissingArticle() {
global $wgSend404Code;
$outputPage = $this->getContext()->getOutput();
+ $validUserPage = false;
# Show info in user (talk) namespace. Does the user exist? Is he blocked?
if ( $this->getTitle()->getNamespace() == NS_USER || $this->getTitle()->getNamespace() == NS_USER_TALK ) {
@@ -1013,6 +1117,9 @@ class Article extends Page {
)
)
);
+ $validUserPage = true;
+ } else {
+ $validUserPage = true;
}
}
@@ -1020,13 +1127,13 @@ class Article extends Page {
# Show delete and move logs
LogEventsList::showLogExtract( $outputPage, array( 'delete', 'move' ), $this->getTitle(), '',
- array( 'lim' => 10,
+ array( 'lim' => 10,
'conds' => array( "log_action != 'revision'" ),
'showIfEmpty' => false,
'msgKey' => array( 'moveddeleted-notice' ) )
);
- if ( !$this->mPage->hasViewableContent() && $wgSend404Code ) {
+ if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) {
// If there's no backing content, send a 404 Not Found
// for better machine handling of broken links.
$this->getContext()->getRequest()->response()->header( "HTTP/1.1 404 Not Found" );
@@ -1104,7 +1211,7 @@ class Article extends Page {
* Revision as of \<date\>; view current revision
* \<- Previous version | Next Version -\>
*
- * @param $oldid int: revision ID of this article revision
+ * @param int $oldid revision ID of this article revision
*/
public function setOldSubtitle( $oldid = 0 ) {
if ( !wfRunHooks( 'DisplayOldSubtitle', array( &$this, &$oldid ) ) ) {
@@ -1166,7 +1273,7 @@ class Article extends Page {
'oldid' => $oldid
) + $extraParams
);
- $prev = $this->getTitle()->getPreviousRevisionID( $oldid ) ;
+ $prev = $this->getTitle()->getPreviousRevisionID( $oldid );
$prevlink = $prev
? Linker::linkKnown(
$this->getTitle(),
@@ -1377,7 +1484,13 @@ class Article extends Page {
// Generate deletion reason
$hasHistory = false;
if ( !$reason ) {
- $reason = $this->generateReason( $hasHistory );
+ try {
+ $reason = $this->generateReason( $hasHistory );
+ } catch ( MWException $e ) {
+ # if a page is horribly broken, we still want to be able to delete it. so be lenient about errors here.
+ wfDebug( "Error while building auto delete summary: $e" );
+ $reason = '';
+ }
}
// If the page has a history, insert a warning
@@ -1406,7 +1519,7 @@ class Article extends Page {
/**
* Output deletion confirmation dialog
* @todo FIXME: Move to another file?
- * @param $reason String: prefilled reason
+ * @param string $reason prefilled reason
*/
public function confirmDelete( $reason ) {
wfDebug( "Article::confirmDelete\n" );
@@ -1614,9 +1727,11 @@ class Article extends Page {
*
* @param $oldid mixed integer Revision ID or null
* @param $user User The relevant user
- * @return ParserOutput or false if the given revsion ID is not found
+ * @return ParserOutput or false if the given revision ID is not found
*/
public function getParserOutput( $oldid = null, User $user = null ) {
+ //XXX: bypasses mParserOptions and thus setParserOptions()
+
if ( $user === null ) {
$parserOptions = $this->getParserOptions();
} else {
@@ -1627,6 +1742,21 @@ class Article extends Page {
}
/**
+ * Override the ParserOptions used to render the primary article wikitext.
+ *
+ * @param ParserOptions $options
+ * @throws MWException if the parser options where already initialized.
+ */
+ public function setParserOptions( ParserOptions $options ) {
+ if ( $this->mParserOptions ) {
+ throw new MWException( "can't change parser options after they have already been set" );
+ }
+
+ // clone, so if $options is modified later, it doesn't confuse the parser cache.
+ $this->mParserOptions = clone $options;
+ }
+
+ /**
* Get parser options suitable for rendering the primary article wikitext
* @return ParserOptions
*/
@@ -1757,15 +1887,16 @@ class Article extends Page {
*
* @deprecated in 1.18; call OutputPage::redirect() directly
* @param $noRedir Boolean: add redirect=no
- * @param $sectionAnchor String: section to redirect to, including "#"
- * @param $extraQuery String: extra query params
+ * @param string $sectionAnchor section to redirect to, including "#"
+ * @param string $extraQuery extra query params
*/
public function doRedirect( $noRedir = false, $sectionAnchor = '', $extraQuery = '' ) {
wfDeprecated( __METHOD__, '1.18' );
if ( $noRedir ) {
$query = 'redirect=no';
- if ( $extraQuery )
+ if ( $extraQuery ) {
$query .= "&$extraQuery";
+ }
} else {
$query = $extraQuery;
}
@@ -1777,7 +1908,7 @@ class Article extends Page {
* Use PHP's magic __get handler to handle accessing of
* raw WikiPage fields for backwards compatibility.
*
- * @param $fname String Field name
+ * @param string $fname Field name
*/
public function __get( $fname ) {
if ( property_exists( $this->mPage, $fname ) ) {
@@ -1791,7 +1922,7 @@ class Article extends Page {
* Use PHP's magic __set handler to handle setting of
* raw WikiPage fields for backwards compatibility.
*
- * @param $fname String Field name
+ * @param string $fname Field name
* @param $fvalue mixed New value
*/
public function __set( $fname, $fvalue ) {
@@ -1810,8 +1941,8 @@ class Article extends Page {
* Use PHP's magic __call handler to transform instance calls to
* WikiPage functions for backwards compatibility.
*
- * @param $fname String Name of called method
- * @param $args Array Arguments to the method
+ * @param string $fname Name of called method
+ * @param array $args Arguments to the method
* @return mixed
*/
public function __call( $fname, $args ) {
@@ -1844,7 +1975,13 @@ class Article extends Page {
* @return bool
*/
public function updateRestrictions( $limit = array(), $reason = '', &$cascade = 0, $expiry = array() ) {
- return $this->mPage->updateRestrictions( $limit, $reason, $cascade, $expiry );
+ return $this->mPage->doUpdateRestrictions(
+ $limit,
+ $expiry,
+ $cascade,
+ $reason,
+ $this->getContext()->getUser()
+ );
}
/**
@@ -1891,7 +2028,9 @@ class Article extends Page {
* @return mixed
*/
public function generateReason( &$hasHistory ) {
- return $this->mPage->getAutoDeleteReason( $hasHistory );
+ $title = $this->mPage->getTitle();
+ $handler = ContentHandler::getForTitle( $title );
+ return $handler->getAutoDeleteReason( $title, $hasHistory );
}
// ****** B/C functions for static methods ( __callStatic is PHP>=5.3 ) ****** //
@@ -1929,6 +2068,7 @@ class Article extends Page {
* @param $newtext
* @param $flags
* @return string
+ * @deprecated since 1.21, use ContentHandler::getAutosummary() instead
*/
public static function getAutosummary( $oldtext, $newtext, $flags ) {
return WikiPage::getAutosummary( $oldtext, $newtext, $flags );
diff --git a/includes/AuthPlugin.php b/includes/AuthPlugin.php
index 2e42439c..a4658176 100644
--- a/includes/AuthPlugin.php
+++ b/includes/AuthPlugin.php
@@ -46,7 +46,7 @@ class AuthPlugin {
* you might need to munge it (for instance, for lowercase initial
* letters).
*
- * @param $username String: username.
+ * @param string $username username.
* @return bool
*/
public function userExists( $username ) {
@@ -60,8 +60,8 @@ class AuthPlugin {
* you might need to munge it (for instance, for lowercase initial
* letters).
*
- * @param $username String: username.
- * @param $password String: user password.
+ * @param string $username username.
+ * @param string $password user password.
* @return bool
*/
public function authenticate( $username, $password ) {
@@ -73,7 +73,7 @@ class AuthPlugin {
* Modify options in the login template.
*
* @param $template UserLoginTemplate object.
- * @param $type String 'signup' or 'login'. Added in 1.16.
+ * @param string $type 'signup' or 'login'. Added in 1.16.
*/
public function modifyUITemplate( &$template, &$type ) {
# Override this!
@@ -83,7 +83,7 @@ class AuthPlugin {
/**
* Set the domain this plugin is supposed to use when authenticating.
*
- * @param $domain String: authentication domain.
+ * @param string $domain authentication domain.
*/
public function setDomain( $domain ) {
$this->domain = $domain;
@@ -105,7 +105,7 @@ class AuthPlugin {
/**
* Check to see if the specific domain is a valid domain.
*
- * @param $domain String: authentication domain.
+ * @param string $domain authentication domain.
* @return bool
*/
public function validDomain( $domain ) {
@@ -194,7 +194,7 @@ class AuthPlugin {
* Return true if successful.
*
* @param $user User object.
- * @param $password String: password.
+ * @param string $password password.
* @return bool
*/
public function setPassword( $user, $password ) {
@@ -251,7 +251,7 @@ class AuthPlugin {
* Check if a user should authenticate locally if the global authentication fails.
* If either this or strict() returns true, local authentication is not used.
*
- * @param $username String: username.
+ * @param string $username username.
* @return Boolean
*/
public function strictUserAuth( $username ) {
diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php
index a8b22027..3555e2c3 100644
--- a/includes/AutoLoader.php
+++ b/includes/AutoLoader.php
@@ -33,12 +33,12 @@ $wgAutoloadLocalClasses = array(
'AjaxDispatcher' => 'includes/AjaxDispatcher.php',
'AjaxResponse' => 'includes/AjaxResponse.php',
'AlphabeticPager' => 'includes/Pager.php',
+ 'ArrayUtils' => 'includes/ArrayUtils.php',
'Article' => 'includes/Article.php',
'AtomFeed' => 'includes/Feed.php',
'AuthPlugin' => 'includes/AuthPlugin.php',
'AuthPluginUser' => 'includes/AuthPlugin.php',
'Autopromote' => 'includes/Autopromote.php',
- 'BacklinkCache' => 'includes/BacklinkCache.php',
'BadTitleError' => 'includes/Exception.php',
'BaseTemplate' => 'includes/SkinTemplate.php',
'Block' => 'includes/Block.php',
@@ -71,8 +71,6 @@ $wgAutoloadLocalClasses = array(
'DeferredUpdates' => 'includes/DeferredUpdates.php',
'DeprecatedGlobal' => 'includes/DeprecatedGlobal.php',
'DerivativeRequest' => 'includes/WebRequest.php',
- 'DeviceDetection' => 'includes/mobile/DeviceDetection.php',
- 'DeviceProperties' => 'includes/mobile/DeviceDetection.php',
'DiffHistoryBlob' => 'includes/HistoryBlob.php',
'DoubleReplacer' => 'includes/StringUtils.php',
'DummyLinker' => 'includes/Linker.php',
@@ -93,9 +91,11 @@ $wgAutoloadLocalClasses = array(
'ErrorPageError' => 'includes/Exception.php',
'ExplodeIterator' => 'includes/StringUtils.php',
'ExternalEdit' => 'includes/ExternalEdit.php',
- 'ExternalStore' => 'includes/ExternalStore.php',
- 'ExternalStoreDB' => 'includes/ExternalStoreDB.php',
- 'ExternalStoreHttp' => 'includes/ExternalStoreHttp.php',
+ 'ExternalStore' => 'includes/externalstore/ExternalStore.php',
+ 'ExternalStoreDB' => 'includes/externalstore/ExternalStoreDB.php',
+ 'ExternalStoreHttp' => 'includes/externalstore/ExternalStoreHttp.php',
+ 'ExternalStoreMedium' => 'includes/externalstore/ExternalStoreMedium.php',
+ 'ExternalStoreMwstore' => 'includes/externalstore/ExternalStoreMwstore.php',
'ExternalUser' => 'includes/ExternalUser.php',
'FakeTitle' => 'includes/FakeTitle.php',
'Fallback' => 'includes/Fallback.php',
@@ -119,6 +119,7 @@ $wgAutoloadLocalClasses = array(
'Html' => 'includes/Html.php',
'HTMLApiField' => 'includes/HTMLForm.php',
'HTMLCheckField' => 'includes/HTMLForm.php',
+ 'HTMLCheckMatrix' => 'includes/HTMLForm.php',
'HTMLEditTools' => 'includes/HTMLForm.php',
'HTMLFloatField' => 'includes/HTMLForm.php',
'HTMLForm' => 'includes/HTMLForm.php',
@@ -136,11 +137,8 @@ $wgAutoloadLocalClasses = array(
'HTMLTextField' => 'includes/HTMLForm.php',
'Http' => 'includes/HttpFunctions.php',
'HttpError' => 'includes/Exception.php',
- 'HttpRequest' => 'includes/HttpFunctions.old.php',
'ICacheHelper' => 'includes/CacheHelper.php',
'IcuCollation' => 'includes/Collation.php',
- 'IDeviceProperties' => 'includes/mobile/DeviceDetection.php',
- 'IDeviceDetector' => 'includes/mobile/DeviceDetection.php',
'IdentityCollation' => 'includes/Collation.php',
'ImageGallery' => 'includes/ImageGallery.php',
'ImageHistoryList' => 'includes/ImagePage.php',
@@ -153,10 +151,11 @@ $wgAutoloadLocalClasses = array(
'IndexPager' => 'includes/Pager.php',
'Interwiki' => 'includes/interwiki/Interwiki.php',
'IP' => 'includes/IP.php',
- 'LCStore_Accel' => 'includes/LocalisationCache.php',
- 'LCStore_CDB' => 'includes/LocalisationCache.php',
- 'LCStore_DB' => 'includes/LocalisationCache.php',
- 'LCStore_Null' => 'includes/LocalisationCache.php',
+ 'LCStore' => 'includes/cache/LocalisationCache.php',
+ 'LCStore_Accel' => 'includes/cache/LocalisationCache.php',
+ 'LCStore_CDB' => 'includes/cache/LocalisationCache.php',
+ 'LCStore_DB' => 'includes/cache/LocalisationCache.php',
+ 'LCStore_Null' => 'includes/cache/LocalisationCache.php',
'LegacyTemplate' => 'includes/SkinLegacy.php',
'License' => 'includes/Licenses.php',
'Licenses' => 'includes/Licenses.php',
@@ -164,11 +163,12 @@ $wgAutoloadLocalClasses = array(
'LinkFilter' => 'includes/LinkFilter.php',
'LinksUpdate' => 'includes/LinksUpdate.php',
'LinksDeletionUpdate' => 'includes/LinksUpdate.php',
- 'LocalisationCache' => 'includes/LocalisationCache.php',
- 'LocalisationCache_BulkLoad' => 'includes/LocalisationCache.php',
+ 'LocalisationCache' => 'includes/cache/LocalisationCache.php',
+ 'LocalisationCache_BulkLoad' => 'includes/cache/LocalisationCache.php',
'MagicWord' => 'includes/MagicWord.php',
'MagicWordArray' => 'includes/MagicWord.php',
'MailAddress' => 'includes/UserMailer.php',
+ 'MappedIterator' => 'includes/MappedIterator.php',
'MediaWiki' => 'includes/Wiki.php',
'MediaWiki_I18N' => 'includes/SkinTemplate.php',
'Message' => 'includes/Message.php',
@@ -183,7 +183,7 @@ $wgAutoloadLocalClasses = array(
'MWNamespace' => 'includes/Namespace.php',
'OldChangesList' => 'includes/ChangesList.php',
'OutputPage' => 'includes/OutputPage.php',
- 'Page' => 'includes/WikiPage.php',
+ 'Page' => 'includes/WikiPage.php',
'PageQueryPage' => 'includes/PageQueryPage.php',
'Pager' => 'includes/Pager.php',
'PasswordError' => 'includes/User.php',
@@ -201,10 +201,12 @@ $wgAutoloadLocalClasses = array(
'ProtectionForm' => 'includes/ProtectionForm.php',
'QueryPage' => 'includes/QueryPage.php',
'QuickTemplate' => 'includes/SkinTemplate.php',
+ 'RawMessage' => 'includes/Message.php',
'RCCacheEntry' => 'includes/ChangesList.php',
'RdfMetaData' => 'includes/Metadata.php',
'ReadOnlyError' => 'includes/Exception.php',
'RecentChange' => 'includes/RecentChange.php',
+ 'RedirectSpecialArticle' => 'includes/SpecialPage.php',
'RedirectSpecialPage' => 'includes/SpecialPage.php',
'RegexlikeReplacer' => 'includes/StringUtils.php',
'ReplacementArray' => 'includes/StringUtils.php',
@@ -219,6 +221,7 @@ $wgAutoloadLocalClasses = array(
'Sanitizer' => 'includes/Sanitizer.php',
'DataUpdate' => 'includes/DataUpdate.php',
'SqlDataUpdate' => 'includes/SqlDataUpdate.php',
+ 'ScopedCallback' => 'includes/ScopedCallback.php',
'ScopedPHPTimeout' => 'includes/ScopedPHPTimeout.php',
'SiteConfiguration' => 'includes/SiteConfiguration.php',
'SiteStats' => 'includes/SiteStats.php',
@@ -247,10 +250,12 @@ $wgAutoloadLocalClasses = array(
'StubUserLang' => 'includes/StubObject.php',
'TablePager' => 'includes/Pager.php',
'MWTimestamp' => 'includes/Timestamp.php',
+ 'TimestampException' => 'includes/Timestamp.php',
'Title' => 'includes/Title.php',
'TitleArray' => 'includes/TitleArray.php',
'TitleArrayFromResult' => 'includes/TitleArray.php',
'ThrottledError' => 'includes/Exception.php',
+ 'UIDGenerator' => 'includes/UIDGenerator.php',
'UnlistedSpecialPage' => 'includes/SpecialPage.php',
'UploadSourceAdapter' => 'includes/Import.php',
'UppercaseCollation' => 'includes/Collation.php',
@@ -272,9 +277,9 @@ $wgAutoloadLocalClasses = array(
'WikiError' => 'includes/WikiError.php',
'WikiErrorMsg' => 'includes/WikiError.php',
'WikiExporter' => 'includes/Export.php',
- 'WikiFilePage' => 'includes/WikiFilePage.php',
+ 'WikiFilePage' => 'includes/WikiFilePage.php',
'WikiImporter' => 'includes/Import.php',
- 'WikiPage' => 'includes/WikiPage.php',
+ 'WikiPage' => 'includes/WikiPage.php',
'WikiRevision' => 'includes/Import.php',
'WikiMap' => 'includes/WikiMap.php',
'WikiReference' => 'includes/WikiMap.php',
@@ -289,6 +294,21 @@ $wgAutoloadLocalClasses = array(
'ZipDirectoryReader' => 'includes/ZipDirectoryReader.php',
'ZipDirectoryReaderError' => 'includes/ZipDirectoryReader.php',
+ # content handler
+ 'AbstractContent' => 'includes/content/AbstractContent.php',
+ 'ContentHandler' => 'includes/content/ContentHandler.php',
+ 'Content' => 'includes/content/Content.php',
+ 'CssContentHandler' => 'includes/content/CssContentHandler.php',
+ 'CssContent' => 'includes/content/CssContent.php',
+ 'JavaScriptContentHandler' => 'includes/content/JavaScriptContentHandler.php',
+ 'JavaScriptContent' => 'includes/content/JavaScriptContent.php',
+ 'MessageContent' => 'includes/content/MessageContent.php',
+ 'MWContentSerializationException' => 'includes/content/ContentHandler.php',
+ 'TextContentHandler' => 'includes/content/TextContentHandler.php',
+ 'TextContent' => 'includes/content/TextContent.php',
+ 'WikitextContentHandler' => 'includes/content/WikitextContentHandler.php',
+ 'WikitextContent' => 'includes/content/WikitextContent.php',
+
# includes/actions
'CachedAction' => 'includes/actions/CachedAction.php',
'CreditsAction' => 'includes/actions/CreditsAction.php',
@@ -318,6 +338,7 @@ $wgAutoloadLocalClasses = array(
'ApiBase' => 'includes/api/ApiBase.php',
'ApiBlock' => 'includes/api/ApiBlock.php',
'ApiComparePages' => 'includes/api/ApiComparePages.php',
+ 'ApiCreateAccount' => 'includes/api/ApiCreateAccount.php',
'ApiDelete' => 'includes/api/ApiDelete.php',
'ApiDisabled' => 'includes/api/ApiDisabled.php',
'ApiEditPage' => 'includes/api/ApiEditPage.php',
@@ -331,6 +352,7 @@ $wgAutoloadLocalClasses = array(
'ApiFormatDump' => 'includes/api/ApiFormatDump.php',
'ApiFormatFeedWrapper' => 'includes/api/ApiFormatBase.php',
'ApiFormatJson' => 'includes/api/ApiFormatJson.php',
+ 'ApiFormatNone' => 'includes/api/ApiFormatNone.php',
'ApiFormatPhp' => 'includes/api/ApiFormatPhp.php',
'ApiFormatRaw' => 'includes/api/ApiFormatRaw.php',
'ApiFormatTxt' => 'includes/api/ApiFormatTxt.php',
@@ -339,11 +361,13 @@ $wgAutoloadLocalClasses = array(
'ApiFormatXmlRsd' => 'includes/api/ApiRsd.php',
'ApiFormatYaml' => 'includes/api/ApiFormatYaml.php',
'ApiHelp' => 'includes/api/ApiHelp.php',
+ 'ApiImageRotate' => 'includes/api/ApiImageRotate.php',
'ApiImport' => 'includes/api/ApiImport.php',
'ApiImportReporter' => 'includes/api/ApiImport.php',
'ApiLogin' => 'includes/api/ApiLogin.php',
'ApiLogout' => 'includes/api/ApiLogout.php',
'ApiMain' => 'includes/api/ApiMain.php',
+ 'ApiModuleManager' => 'includes/api/ApiModuleManager.php',
'ApiMove' => 'includes/api/ApiMove.php',
'ApiOpenSearch' => 'includes/api/ApiOpenSearch.php',
'ApiOptions' => 'includes/api/ApiOptions.php',
@@ -383,7 +407,10 @@ $wgAutoloadLocalClasses = array(
'ApiQueryLangLinks' => 'includes/api/ApiQueryLangLinks.php',
'ApiQueryLinks' => 'includes/api/ApiQueryLinks.php',
'ApiQueryLogEvents' => 'includes/api/ApiQueryLogEvents.php',
+ 'ApiQueryORM' => 'includes/api/ApiQueryORM.php',
'ApiQueryPageProps' => 'includes/api/ApiQueryPageProps.php',
+ 'ApiQueryPagesWithProp' => 'includes/api/ApiQueryPagesWithProp.php',
+ 'ApiQueryPagePropNames' => 'includes/api/ApiQueryPagePropNames.php',
'ApiQueryProtectedTitles' => 'includes/api/ApiQueryProtectedTitles.php',
'ApiQueryQueryPage' => 'includes/api/ApiQueryQueryPage.php',
'ApiQueryRandom' => 'includes/api/ApiQueryRandom.php',
@@ -410,6 +437,7 @@ $wgAutoloadLocalClasses = array(
'UsageException' => 'includes/api/ApiMain.php',
# includes/cache
+ 'BacklinkCache' => 'includes/cache/BacklinkCache.php',
'CacheDependency' => 'includes/cache/CacheDependency.php',
'ConstantDependency' => 'includes/cache/CacheDependency.php',
'DependencyWrapper' => 'includes/cache/CacheDependency.php',
@@ -418,7 +446,6 @@ $wgAutoloadLocalClasses = array(
'GenderCache' => 'includes/cache/GenderCache.php',
'GlobalDependency' => 'includes/cache/CacheDependency.php',
'HTMLCacheUpdate' => 'includes/cache/HTMLCacheUpdate.php',
- 'HTMLCacheUpdateJob' => 'includes/cache/HTMLCacheUpdate.php',
'HTMLFileCache' => 'includes/cache/HTMLFileCache.php',
'LinkBatch' => 'includes/cache/LinkBatch.php',
'LinkCache' => 'includes/cache/LinkCache.php',
@@ -430,6 +457,10 @@ $wgAutoloadLocalClasses = array(
'TitleDependency' => 'includes/cache/CacheDependency.php',
'TitleListDependency' => 'includes/cache/CacheDependency.php',
+ # includes/clientpool
+ 'RedisConnectionPool' => 'includes/clientpool/RedisConnectionPool.php',
+ 'RedisConnRef' => 'includes/clientpool/RedisConnectionPool.php',
+
# includes/context
'ContextSource' => 'includes/context/ContextSource.php',
'DerivativeContext' => 'includes/context/DerivativeContext.php',
@@ -438,14 +469,13 @@ $wgAutoloadLocalClasses = array(
# includes/dao
'IDBAccessObject' => 'includes/dao/IDBAccessObject.php',
+ 'DBAccessBase' => 'includes/dao/DBAccessBase.php',
# includes/db
'Blob' => 'includes/db/DatabaseUtility.php',
'ChronologyProtector' => 'includes/db/LBFactory.php',
'CloneDatabase' => 'includes/db/CloneDatabase.php',
- 'Database' => 'includes/db/DatabaseMysql.php',
'DatabaseBase' => 'includes/db/Database.php',
- 'DatabaseIbm_db2' => 'includes/db/DatabaseIbm_db2.php',
'DatabaseMssql' => 'includes/db/DatabaseMssql.php',
'DatabaseMysql' => 'includes/db/DatabaseMysql.php',
'DatabaseOracle' => 'includes/db/DatabaseOracle.php',
@@ -464,10 +494,6 @@ $wgAutoloadLocalClasses = array(
'DBUnexpectedError' => 'includes/db/DatabaseError.php',
'FakeResultWrapper' => 'includes/db/DatabaseUtility.php',
'Field' => 'includes/db/DatabaseUtility.php',
- 'IBM_DB2Blob' => 'includes/db/DatabaseIbm_db2.php',
- 'IBM_DB2Field' => 'includes/db/DatabaseIbm_db2.php',
- 'IBM_DB2Helper' => 'includes/db/DatabaseIbm_db2.php',
- 'IBM_DB2Result' => 'includes/db/DatabaseIbm_db2.php',
'LBFactory' => 'includes/db/LBFactory.php',
'LBFactory_Fake' => 'includes/db/LBFactory.php',
'LBFactory_Multi' => 'includes/db/LBFactory_Multi.php',
@@ -548,13 +574,14 @@ $wgAutoloadLocalClasses = array(
'NullFileJournal' => 'includes/filebackend/filejournal/FileJournal.php',
'LockManagerGroup' => 'includes/filebackend/lockmanager/LockManagerGroup.php',
'LockManager' => 'includes/filebackend/lockmanager/LockManager.php',
- 'ScopedLock' => 'includes/filebackend/lockmanager/LockManager.php',
+ 'ScopedLock' => 'includes/filebackend/lockmanager/ScopedLock.php',
'FSLockManager' => 'includes/filebackend/lockmanager/FSLockManager.php',
'DBLockManager' => 'includes/filebackend/lockmanager/DBLockManager.php',
'LSLockManager' => 'includes/filebackend/lockmanager/LSLockManager.php',
'MemcLockManager' => 'includes/filebackend/lockmanager/MemcLockManager.php',
- 'QuorumLockManager' => 'includes/filebackend/lockmanager/LockManager.php',
- 'MySqlLockManager'=> 'includes/filebackend/lockmanager/DBLockManager.php',
+ 'QuorumLockManager' => 'includes/filebackend/lockmanager/QuorumLockManager.php',
+ 'MySqlLockManager' => 'includes/filebackend/lockmanager/DBLockManager.php',
+ 'PostgreSqlLockManager' => 'includes/filebackend/lockmanager/DBLockManager.php',
'NullLockManager' => 'includes/filebackend/lockmanager/LockManager.php',
'FileOp' => 'includes/filebackend/FileOp.php',
'FileOpBatch' => 'includes/filebackend/FileOpBatch.php',
@@ -562,8 +589,8 @@ $wgAutoloadLocalClasses = array(
'CopyFileOp' => 'includes/filebackend/FileOp.php',
'MoveFileOp' => 'includes/filebackend/FileOp.php',
'DeleteFileOp' => 'includes/filebackend/FileOp.php',
- 'ConcatenateFileOp' => 'includes/filebackend/FileOp.php',
'CreateFileOp' => 'includes/filebackend/FileOp.php',
+ 'DescribeFileOp' => 'includes/filebackend/FileOp.php',
'NullFileOp' => 'includes/filebackend/FileOp.php',
# includes/filerepo
@@ -594,11 +621,8 @@ $wgAutoloadLocalClasses = array(
'CliInstaller' => 'includes/installer/CliInstaller.php',
'DatabaseInstaller' => 'includes/installer/DatabaseInstaller.php',
'DatabaseUpdater' => 'includes/installer/DatabaseUpdater.php',
- 'Ibm_db2Installer' => 'includes/installer/Ibm_db2Installer.php',
- 'Ibm_db2Updater' => 'includes/installer/Ibm_db2Updater.php',
'InstallDocFormatter' => 'includes/installer/InstallDocFormatter.php',
'Installer' => 'includes/installer/Installer.php',
- 'LBFactory_InstallerFake' => 'includes/installer/Installer.php',
'LocalSettingsGenerator' => 'includes/installer/LocalSettingsGenerator.php',
'MysqlInstaller' => 'includes/installer/MysqlInstaller.php',
'MysqlUpdater' => 'includes/installer/MysqlUpdater.php',
@@ -631,13 +655,26 @@ $wgAutoloadLocalClasses = array(
'WebInstallerPage' => 'includes/installer/WebInstallerPage.php',
# includes/job
- 'DoubleRedirectJob' => 'includes/job/DoubleRedirectJob.php',
- 'EmaillingJob' => 'includes/job/EmaillingJob.php',
- 'EnotifNotifyJob' => 'includes/job/EnotifNotifyJob.php',
'Job' => 'includes/job/Job.php',
- 'RefreshLinksJob' => 'includes/job/RefreshLinksJob.php',
- 'RefreshLinksJob2' => 'includes/job/RefreshLinksJob.php',
- 'UploadFromUrlJob' => 'includes/job/UploadFromUrlJob.php',
+ 'JobQueue' => 'includes/job/JobQueue.php',
+ 'JobQueueAggregator' => 'includes/job/JobQueueAggregator.php',
+ 'JobQueueAggregatorMemc' => 'includes/job/JobQueueAggregatorMemc.php',
+ 'JobQueueAggregatorRedis' => 'includes/job/JobQueueAggregatorRedis.php',
+ 'JobQueueDB' => 'includes/job/JobQueueDB.php',
+ 'JobQueueGroup' => 'includes/job/JobQueueGroup.php',
+
+ # includes/job/jobs
+ 'DoubleRedirectJob' => 'includes/job/jobs/DoubleRedirectJob.php',
+ 'DuplicateJob' => 'includes/job/jobs/DuplicateJob.php',
+ 'EmaillingJob' => 'includes/job/jobs/EmaillingJob.php',
+ 'EnotifNotifyJob' => 'includes/job/jobs/EnotifNotifyJob.php',
+ 'HTMLCacheUpdateJob' => 'includes/job/jobs/HTMLCacheUpdateJob.php',
+ 'NullJob' => 'includes/job/jobs/NullJob.php',
+ 'RefreshLinksJob' => 'includes/job/jobs/RefreshLinksJob.php',
+ 'RefreshLinksJob2' => 'includes/job/jobs/RefreshLinksJob.php',
+ 'UploadFromUrlJob' => 'includes/job/jobs/UploadFromUrlJob.php',
+ 'AssembleUploadChunksJob' => 'includes/job/jobs/AssembleUploadChunksJob.php',
+ 'PublishStashedFileJob' => 'includes/job/jobs/PublishStashedFileJob.php',
# includes/json
'FormatJson' => 'includes/json/FormatJson.php',
@@ -676,6 +713,7 @@ $wgAutoloadLocalClasses = array(
'PatrolLog' => 'includes/logging/PatrolLog.php',
'PatrolLogFormatter' => 'includes/logging/LogFormatter.php',
'RCDatabaseLogEntry' => 'includes/logging/LogEntry.php',
+ 'RightsLogFormatter' => 'includes/logging/LogFormatter.php',
# includes/media
'BitmapHandler' => 'includes/media/Bitmap.php',
@@ -747,35 +785,24 @@ $wgAutoloadLocalClasses = array(
'MWTidyWrapper' => 'includes/parser/Tidy.php',
'PPCustomFrame_DOM' => 'includes/parser/Preprocessor_DOM.php',
'PPCustomFrame_Hash' => 'includes/parser/Preprocessor_Hash.php',
- 'PPCustomFrame_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
'PPDAccum_Hash' => 'includes/parser/Preprocessor_Hash.php',
- 'PPDAccum_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
'PPDPart' => 'includes/parser/Preprocessor_DOM.php',
'PPDPart_Hash' => 'includes/parser/Preprocessor_Hash.php',
- 'PPDPart_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
'PPDStack' => 'includes/parser/Preprocessor_DOM.php',
'PPDStackElement' => 'includes/parser/Preprocessor_DOM.php',
'PPDStackElement_Hash' => 'includes/parser/Preprocessor_Hash.php',
- 'PPDStackElement_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
'PPDStack_Hash' => 'includes/parser/Preprocessor_Hash.php',
- 'PPDStack_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
'PPFrame' => 'includes/parser/Preprocessor.php',
'PPFrame_DOM' => 'includes/parser/Preprocessor_DOM.php',
'PPFrame_Hash' => 'includes/parser/Preprocessor_Hash.php',
- 'PPFrame_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
'PPNode' => 'includes/parser/Preprocessor.php',
'PPNode_DOM' => 'includes/parser/Preprocessor_DOM.php',
'PPNode_Hash_Array' => 'includes/parser/Preprocessor_Hash.php',
'PPNode_Hash_Attr' => 'includes/parser/Preprocessor_Hash.php',
'PPNode_Hash_Text' => 'includes/parser/Preprocessor_Hash.php',
'PPNode_Hash_Tree' => 'includes/parser/Preprocessor_Hash.php',
- 'PPNode_HipHop_Array' => 'includes/parser/Preprocessor_HipHop.hphp',
- 'PPNode_HipHop_Attr' => 'includes/parser/Preprocessor_HipHop.hphp',
- 'PPNode_HipHop_Text' => 'includes/parser/Preprocessor_HipHop.hphp',
- 'PPNode_HipHop_Tree' => 'includes/parser/Preprocessor_HipHop.hphp',
'PPTemplateFrame_DOM' => 'includes/parser/Preprocessor_DOM.php',
'PPTemplateFrame_Hash' => 'includes/parser/Preprocessor_Hash.php',
- 'PPTemplateFrame_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
'Parser' => 'includes/parser/Parser.php',
'ParserCache' => 'includes/parser/ParserCache.php',
'ParserOptions' => 'includes/parser/ParserOptions.php',
@@ -785,7 +812,6 @@ $wgAutoloadLocalClasses = array(
'Preprocessor' => 'includes/parser/Preprocessor.php',
'Preprocessor_DOM' => 'includes/parser/Preprocessor_DOM.php',
'Preprocessor_Hash' => 'includes/parser/Preprocessor_Hash.php',
- 'Preprocessor_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
'StripState' => 'includes/parser/StripState.php',
# includes/profiler
@@ -827,7 +853,6 @@ $wgAutoloadLocalClasses = array(
'RevDel_LogList' => 'includes/revisiondelete/RevisionDelete.php',
'RevDel_RevisionItem' => 'includes/revisiondelete/RevisionDelete.php',
'RevDel_RevisionList' => 'includes/revisiondelete/RevisionDelete.php',
- 'RevisionDelete' => 'includes/revisiondelete/RevisionDelete.php',
'RevisionDeleter' => 'includes/revisiondelete/RevisionDeleter.php',
'RevisionDeleteUser' => 'includes/revisiondelete/RevisionDeleteUser.php',
@@ -839,7 +864,6 @@ $wgAutoloadLocalClasses = array(
'SearchEngine' => 'includes/search/SearchEngine.php',
'SearchEngineDummy' => 'includes/search/SearchEngine.php',
'SearchHighlighter' => 'includes/search/SearchEngine.php',
- 'SearchIBM_DB2' => 'includes/search/SearchIBM_DB2.php',
'SearchMssql' => 'includes/search/SearchMssql.php',
'SearchMySQL' => 'includes/search/SearchMySQL.php',
'SearchNearMatchResultSet' => 'includes/search/SearchEngine.php',
@@ -854,6 +878,16 @@ $wgAutoloadLocalClasses = array(
'SqliteSearchResultSet' => 'includes/search/SearchSqlite.php',
'SqlSearchResultSet' => 'includes/search/SearchEngine.php',
+ # includes/site
+ 'MediaWikiSite' => 'includes/site/MediaWikiSite.php',
+ 'Site' => 'includes/site/Site.php',
+ 'SiteObject' => 'includes/site/Site.php',
+ 'SiteArray' => 'includes/site/SiteList.php',
+ 'SiteList' => 'includes/site/SiteList.php',
+ 'SiteSQLStore' => 'includes/site/SiteSQLStore.php',
+ 'Sites' => 'includes/site/SiteSQLStore.php',
+ 'SiteStore' => 'includes/site/SiteStore.php',
+
# includes/specials
'ActiveUsersPager' => 'includes/specials/SpecialActiveusers.php',
'AllmessagesTablePager' => 'includes/specials/SpecialAllmessages.php',
@@ -862,8 +896,6 @@ $wgAutoloadLocalClasses = array(
'BrokenRedirectsPage' => 'includes/specials/SpecialBrokenRedirects.php',
'CategoryPager' => 'includes/specials/SpecialCategories.php',
'ContribsPager' => 'includes/specials/SpecialContributions.php',
- 'DBLockForm' => 'includes/specials/SpecialLockdb.php',
- 'DBUnlockForm' => 'includes/specials/SpecialUnlockdb.php',
'DeadendPagesPage' => 'includes/specials/SpecialDeadendpages.php',
'DeletedContribsPager' => 'includes/specials/SpecialDeletedContributions.php',
'DeletedContributionsPage' => 'includes/specials/SpecialDeletedContributions.php',
@@ -928,10 +960,10 @@ $wgAutoloadLocalClasses = array(
'SpecialLockdb' => 'includes/specials/SpecialLockdb.php',
'SpecialLog' => 'includes/specials/SpecialLog.php',
'SpecialMergeHistory' => 'includes/specials/SpecialMergeHistory.php',
- 'SpecialMostlinkedtemplates' => 'includes/specials/SpecialMostlinkedtemplates.php',
'SpecialNewFiles' => 'includes/specials/SpecialNewimages.php',
'SpecialNewpages' => 'includes/specials/SpecialNewpages.php',
'SpecialPasswordReset' => 'includes/specials/SpecialPasswordReset.php',
+ 'SpecialPagesWithProp' => 'includes/specials/SpecialPagesWithProp.php',
'SpecialPermanentLink' => 'includes/SpecialPage.php',
'SpecialPreferences' => 'includes/specials/SpecialPreferences.php',
'SpecialPrefixindex' => 'includes/specials/SpecialPrefixindex.php',
@@ -989,7 +1021,6 @@ $wgAutoloadLocalClasses = array(
'UploadFromUrl' => 'includes/upload/UploadFromUrl.php',
'UploadStash' => 'includes/upload/UploadStash.php',
'UploadStashBadPathException' => 'includes/upload/UploadStash.php',
- 'UploadStashBadVersionException' => 'includes/upload/UploadStash.php',
'UploadStashFile' => 'includes/upload/UploadStash.php',
'UploadStashFileException' => 'includes/upload/UploadStash.php',
'UploadStashFileNotFoundException' => 'includes/upload/UploadStash.php',
@@ -1004,20 +1035,29 @@ $wgAutoloadLocalClasses = array(
'FakeConverter' => 'languages/Language.php',
'Language' => 'languages/Language.php',
'LanguageConverter' => 'languages/LanguageConverter.php',
+ 'CLDRPluralRuleConverter' => 'languages/utils/CLDRPluralRuleEvaluator.php',
+ 'CLDRPluralRuleConverter_Expression' => 'languages/utils/CLDRPluralRuleEvaluator.php',
+ 'CLDRPluralRuleConverter_Fragment' => 'languages/utils/CLDRPluralRuleEvaluator.php',
+ 'CLDRPluralRuleConverter_Operator' => 'languages/utils/CLDRPluralRuleEvaluator.php',
'CLDRPluralRuleEvaluator' => 'languages/utils/CLDRPluralRuleEvaluator.php',
+ 'CLDRPluralRuleEvaluator_Range' => 'languages/utils/CLDRPluralRuleEvaluator.php',
'CLDRPluralRuleError' => 'languages/utils/CLDRPluralRuleEvaluator.php',
# maintenance
+ 'BackupDumper' => 'maintenance/backup.inc',
'ConvertLinks' => 'maintenance/convertLinks.php',
'DeleteArchivedFilesImplementation' => 'maintenance/deleteArchivedFiles.inc',
'DeleteArchivedRevisionsImplementation' => 'maintenance/deleteArchivedRevisions.inc',
'DeleteDefaultMessages' => 'maintenance/deleteDefaultMessages.php',
+ 'DumpDBZip2Output' => 'maintenance/backup.inc',
+ 'ExportProgressFilter' => 'maintenance/backup.inc',
'FakeMaintenance' => 'maintenance/Maintenance.php',
+ 'FixExtLinksProtocolRelative' => 'maintenance/fixExtLinksProtocolRelative.php',
'LoggedUpdateMaintenance' => 'maintenance/Maintenance.php',
'Maintenance' => 'maintenance/Maintenance.php',
- 'FixExtLinksProtocolRelative' => 'maintenance/fixExtLinksProtocolRelative.php',
'PopulateCategory' => 'maintenance/populateCategory.php',
'PopulateImageSha1' => 'maintenance/populateImageSha1.php',
+ 'PopulateFilearchiveSha1' => 'maintenance/populateFilearchiveSha1.php',
'PopulateLogSearch' => 'maintenance/populateLogSearch.php',
'PopulateLogUsertext' => 'maintenance/populateLogUsertext.php',
'PopulateParentId' => 'maintenance/populateParentId.php',
@@ -1040,40 +1080,13 @@ $wgAutoloadLocalClasses = array(
'wikiStatsOutput' => 'maintenance/language/StatOutputs.php',
# maintenance/term
- 'AnsiTermColorer' => 'maintenance/term/MWTerm.php',
+ 'AnsiTermColorer' => 'maintenance/term/MWTerm.php',
'DummyTermColorer' => 'maintenance/term/MWTerm.php',
# mw-config
'InstallerOverrides' => 'mw-config/overrides.php',
'MyLocalSettingsGenerator' => 'mw-config/overrides.php',
- # tests
- 'DbTestPreviewer' => 'tests/testHelpers.inc',
- 'DbTestRecorder' => 'tests/testHelpers.inc',
- 'DelayedParserTest' => 'tests/testHelpers.inc',
- 'TestFileIterator' => 'tests/testHelpers.inc',
- 'TestRecorder' => 'tests/testHelpers.inc',
-
- # tests/phpunit/includes
- 'GenericArrayObjectTest' => 'tests/phpunit/includes/libs/GenericArrayObjectTest.php',
-
- # tests/phpunit/includes/db
- 'ORMRowTest' => 'tests/phpunit/includes/db/ORMRowTest.php',
-
- # tests/parser
- 'ParserTest' => 'tests/parser/parserTest.inc',
- 'ParserTestParserHook' => 'tests/parser/parserTestsParserHook.php',
-
- # tests/selenium
- 'Selenium' => 'tests/selenium/Selenium.php',
- 'SeleniumLoader' => 'tests/selenium/SeleniumLoader.php',
- 'SeleniumTestCase' => 'tests/selenium/SeleniumTestCase.php',
- 'SeleniumTestConsoleLogger' => 'tests/selenium/SeleniumTestConsoleLogger.php',
- 'SeleniumTestHTMLLogger' => 'tests/selenium/SeleniumTestHTMLLogger.php',
- 'SeleniumTestListener' => 'tests/selenium/SeleniumTestListener.php',
- 'SeleniumTestSuite' => 'tests/selenium/SeleniumTestSuite.php',
- 'SeleniumConfig' => 'tests/selenium/SeleniumConfig.php',
-
# skins
'CologneBlueTemplate' => 'skins/CologneBlue.php',
'ModernTemplate' => 'skins/Modern.php',
@@ -1096,7 +1109,7 @@ class AutoLoader {
/**
* autoload - take a class name and attempt to load it
*
- * @param $className String: name of class we're looking for.
+ * @param string $className name of class we're looking for.
* @return bool Returning false is important on failure as
* it allows Zend to try and look in other registered autoloaders
* as well.
diff --git a/includes/Autopromote.php b/includes/Autopromote.php
index 9c77855d..604b9248 100644
--- a/includes/Autopromote.php
+++ b/includes/Autopromote.php
@@ -54,7 +54,7 @@ class Autopromote {
* Does not return groups the user already belongs to or has once belonged.
*
* @param $user User The user to get the groups for
- * @param $event String key in $wgAutopromoteOnce (each one has groups/criteria)
+ * @param string $event key in $wgAutopromoteOnce (each one has groups/criteria)
*
* @return array Groups the user should be promoted to.
*
@@ -140,8 +140,8 @@ class Autopromote {
return true;
}
}
- # If we got here, the array presumably does not contain other condi-
- # tions; it's not recursive. Pass it off to self::checkCondition.
+ // If we got here, the array presumably does not contain other conditions;
+ // it's not recursive. Pass it off to self::checkCondition.
if ( !is_array( $cond ) ) {
$cond = array( $cond );
}
@@ -152,11 +152,11 @@ class Autopromote {
/**
* As recCheckCondition, but *not* recursive. The only valid conditions
* are those whose first element is APCOND_EMAILCONFIRMED/APCOND_EDITCOUNT/
- * APCOND_AGE. Other types will throw an exception if no extension evalu-
- * ates them.
+ * APCOND_AGE. Other types will throw an exception if no extension evaluates them.
*
- * @param $cond Array: A condition, which must not contain other conditions
+ * @param array $cond A condition, which must not contain other conditions
* @param $user User The user to check the condition against
+ * @throws MWException
* @return bool Whether the condition is true for the user
*/
private static function checkCondition( $cond, User $user ) {
diff --git a/includes/Block.php b/includes/Block.php
index 732699dc..7ee36ce9 100644
--- a/includes/Block.php
+++ b/includes/Block.php
@@ -65,11 +65,11 @@ class Block {
$timestamp = 0, $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0, $enableAutoblock = 0,
$hideName = 0, $blockEmail = 0, $allowUsertalk = 0, $byText = '' )
{
- if( $timestamp === 0 ){
+ if( $timestamp === 0 ) {
$timestamp = wfTimestampNow();
}
- if( count( func_get_args() ) > 0 ){
+ if( count( func_get_args() ) > 0 ) {
# Soon... :D
# wfDeprecated( __METHOD__ . " with arguments" );
}
@@ -106,7 +106,7 @@ class Block {
* user ID. Tries the user ID first, and if that doesn't work, tries
* the address.
*
- * @param $address String: IP address of user/anon
+ * @param string $address IP address of user/anon
* @param $user Integer: user id of user
* @return Block Object
* @deprecated since 1.18
@@ -164,7 +164,7 @@ class Block {
/**
* Check if two blocks are effectively equal. Doesn't check irrelevant things like
- * the blocking user or the block timestamp, only things which affect the blocked user *
+ * the blocking user or the block timestamp, only things which affect the blocked user
*
* @param $block Block
*
@@ -199,23 +199,23 @@ class Block {
/**
* Get a block from the DB, with either the given address or the given username
*
- * @param $address string The IP address of the user, or blank to skip IP blocks
- * @param $user int The user ID, or zero for anonymous users
+ * @param string $address The IP address of the user, or blank to skip IP blocks
+ * @param int $user The user ID, or zero for anonymous users
* @return Boolean: the user is blocked from editing
* @deprecated since 1.18
*/
public function load( $address = '', $user = 0 ) {
wfDeprecated( __METHOD__, '1.18' );
- if( $user ){
+ if( $user ) {
$username = User::whoIs( $user );
$block = self::newFromTarget( $username, $address );
} else {
$block = self::newFromTarget( null, $address );
}
- if( $block instanceof Block ){
+ if( $block instanceof Block ) {
# This is mildly evil, but hey, it's B/C :D
- foreach( $block as $variable => $value ){
+ foreach( $block as $variable => $value ) {
$this->$variable = $value;
}
return true;
@@ -227,16 +227,17 @@ class Block {
/**
* Load a block from the database which affects the already-set $this->target:
* 1) A block directly on the given user or IP
- * 2) A rangeblock encompasing the given IP (smallest first)
+ * 2) A rangeblock encompassing the given IP (smallest first)
* 3) An autoblock on the given IP
* @param $vagueTarget User|String also search for blocks affecting this target. Doesn't
* make any sense to use TYPE_AUTO / TYPE_ID here. Leave blank to skip IP lookups.
+ * @throws MWException
* @return Bool whether a relevant block was found
*/
protected function newLoad( $vagueTarget = null ) {
$db = wfGetDB( $this->mFromMaster ? DB_MASTER : DB_SLAVE );
- if( $this->type !== null ){
+ if( $this->type !== null ) {
$conds = array(
'ipb_address' => array( (string)$this->target ),
);
@@ -246,11 +247,11 @@ class Block {
# Be aware that the != '' check is explicit, since empty values will be
# passed by some callers (bug 29116)
- if( $vagueTarget != ''){
+ if( $vagueTarget != '' ) {
list( $target, $type ) = self::parseTarget( $vagueTarget );
switch( $type ) {
case self::TYPE_USER:
- # Slightly wierd, but who are we to argue?
+ # Slightly weird, but who are we to argue?
$conds['ipb_address'][] = (string)$target;
break;
@@ -284,20 +285,20 @@ class Block {
# This is begging for $this = $bestBlock, but that's not allowed in PHP :(
$bestBlockPreventsEdit = null;
- foreach( $res as $row ){
+ foreach( $res as $row ) {
$block = self::newFromRow( $row );
# Don't use expired blocks
- if( $block->deleteIfExpired() ){
+ if( $block->deleteIfExpired() ) {
continue;
}
# Don't use anon only blocks on users
- if( $this->type == self::TYPE_USER && !$block->isHardblock() ){
+ if( $this->type == self::TYPE_USER && !$block->isHardblock() ) {
continue;
}
- if( $block->getType() == self::TYPE_RANGE ){
+ if( $block->getType() == self::TYPE_RANGE ) {
# This is the number of bits that are allowed to vary in the block, give
# or take some floating point errors
$end = wfBaseconvert( $block->getRangeEnd(), 16, 10 );
@@ -306,20 +307,20 @@ class Block {
# This has the nice property that a /32 block is ranked equally with a
# single-IP block, which is exactly what it is...
- $score = self::TYPE_RANGE - 1 + ( $size / 128 );
+ $score = self::TYPE_RANGE - 1 + ( $size / 128 );
} else {
$score = $block->getType();
}
- if( $score < $bestBlockScore ){
+ if( $score < $bestBlockScore ) {
$bestBlockScore = $score;
$bestRow = $row;
$bestBlockPreventsEdit = $block->prevents( 'edit' );
}
}
- if( $bestRow !== null ){
+ if( $bestRow !== null ) {
$this->initFromRow( $bestRow );
$this->prevents( 'edit', $bestBlockPreventsEdit );
return true;
@@ -329,9 +330,9 @@ class Block {
}
/**
- * Get a set of SQL conditions which will select rangeblocks encompasing a given range
- * @param $start String Hexadecimal IP representation
- * @param $end String Hexadecimal IP represenation, or null to use $start = $end
+ * Get a set of SQL conditions which will select rangeblocks encompassing a given range
+ * @param string $start Hexadecimal IP representation
+ * @param string $end Hexadecimal IP representation, or null to use $start = $end
* @return String
*/
public static function getRangeCond( $start, $end = null ) {
@@ -370,9 +371,9 @@ class Block {
protected static function getIpFragment( $hex ) {
global $wgBlockCIDRLimit;
if ( substr( $hex, 0, 3 ) == 'v6-' ) {
- return 'v6-' . substr( substr( $hex, 3 ), 0, floor( $wgBlockCIDRLimit['IPv6'] / 4 ) );
+ return 'v6-' . substr( substr( $hex, 3 ), 0, floor( $wgBlockCIDRLimit['IPv6'] / 4 ) );
} else {
- return substr( $hex, 0, floor( $wgBlockCIDRLimit['IPv4'] / 4 ) );
+ return substr( $hex, 0, floor( $wgBlockCIDRLimit['IPv4'] / 4 ) );
}
}
@@ -417,7 +418,7 @@ class Block {
* @param $row ResultWrapper row from the ipblocks table
* @return Block
*/
- public static function newFromRow( $row ){
+ public static function newFromRow( $row ) {
$block = new Block;
$block->initFromRow( $row );
return $block;
@@ -426,6 +427,7 @@ class Block {
/**
* Delete the row from the IP blocks table.
*
+ * @throws MWException
* @return Boolean
*/
public function delete() {
@@ -463,7 +465,7 @@ class Block {
Block::purgeExpired();
$row = $this->getDatabaseArray();
- $row['ipb_id'] = $dbw->nextSequenceValue("ipblocks_ipb_id_seq");
+ $row['ipb_id'] = $dbw->nextSequenceValue( "ipblocks_ipb_id_seq" );
$dbw->insert(
'ipblocks',
@@ -486,7 +488,7 @@ class Block {
* Update a block in the DB with new parameters.
* The ID field needs to be loaded first.
*
- * @return Int number of affected rows, which should probably be 1 or something's
+ * @return Int number of affected rows, which should probably be 1 or something has
* gone slightly awry
*/
public function update() {
@@ -508,8 +510,8 @@ class Block {
* @param $db DatabaseBase
* @return Array
*/
- protected function getDatabaseArray( $db = null ){
- if( !$db ){
+ protected function getDatabaseArray( $db = null ) {
+ if( !$db ) {
$db = wfGetDB( DB_SLAVE );
}
$expiry = $db->encodeExpiry( $this->mExpiry );
@@ -534,10 +536,10 @@ class Block {
'ipb_expiry' => $expiry,
'ipb_range_start' => $this->getRangeStart(),
'ipb_range_end' => $this->getRangeEnd(),
- 'ipb_deleted' => intval( $this->mHideName ), // typecast required for SQLite
+ 'ipb_deleted' => intval( $this->mHideName ), // typecast required for SQLite
'ipb_block_email' => $this->prevents( 'sendemail' ),
'ipb_allow_usertalk' => !$this->prevents( 'editownusertalk' ),
- 'ipb_parent_block_id' => $this->mParentBlockId
+ 'ipb_parent_block_id' => $this->mParentBlockId
);
return $a;
@@ -570,10 +572,17 @@ class Block {
* blocked by this Block. This will use the recentchanges table.
*
* @param Block $block
- * @param Array &$blockIds
+ * @param array &$blockIds
* @return Array: block IDs of retroactive autoblocks made
*/
protected static function defaultRetroactiveAutoblock( Block $block, array &$blockIds ) {
+ global $wgPutIPinRC;
+
+ // No IPs are in recentchanges table, so nothing to select
+ if( !$wgPutIPinRC ) {
+ return;
+ }
+
$dbr = wfGetDB( DB_SLAVE );
$options = array( 'ORDER BY' => 'rc_timestamp DESC' );
@@ -583,9 +592,9 @@ class Block {
$options['LIMIT'] = 1;
$res = $dbr->select( 'recentchanges', array( 'rc_ip' ), $conds,
- __METHOD__ , $options );
+ __METHOD__, $options );
- if ( !$dbr->numRows( $res ) ) {
+ if ( !$res->numRows() ) {
# No results, don't autoblock anything
wfDebug( "No IP found to retroactively autoblock\n" );
} else {
@@ -602,7 +611,7 @@ class Block {
* Checks whether a given IP is on the autoblock whitelist.
* TODO: this probably belongs somewhere else, but not sure where...
*
- * @param $ip String: The IP to check
+ * @param string $ip The IP to check
* @return Boolean
*/
public static function isWhitelistedFromAutoblocks( $ip ) {
@@ -645,7 +654,7 @@ class Block {
/**
* Autoblocks the given IP, referring to this Block.
*
- * @param $autoblockIP String: the IP to autoblock.
+ * @param string $autoblockIP the IP to autoblock.
* @return mixed: block ID if an autoblock was inserted, false if not.
*/
public function doAutoblock( $autoblockIP ) {
@@ -687,7 +696,7 @@ class Block {
wfDebug( "Autoblocking {$this->getTarget()}@" . $autoblockIP . "\n" );
$autoblock->setTarget( $autoblockIP );
$autoblock->setBlocker( $this->getBlocker() );
- $autoblock->mReason = wfMessage( 'autoblocker', $this->getTarget(), $this->mReason )->inContentLanguage()->text();
+ $autoblock->mReason = wfMessage( 'autoblocker', $this->getTarget(), $this->mReason )->inContentLanguage()->plain();
$timestamp = wfTimestampNow();
$autoblock->mTimestamp = $timestamp;
$autoblock->mAuto = 1;
@@ -780,6 +789,7 @@ class Block {
/**
* Get the IP address at the start of the range in Hex form
+ * @throws MWException
* @return String IP in Hex form
*/
public function getRangeStart() {
@@ -797,6 +807,7 @@ class Block {
/**
* Get the IP address at the start of the range in Hex form
+ * @throws MWException
* @return String IP in Hex form
*/
public function getRangeEnd() {
@@ -934,7 +945,7 @@ class Block {
/**
* Encode expiry for DB
*
- * @param $expiry String: timestamp for expiry, or
+ * @param string $expiry timestamp for expiry, or
* @param $db DatabaseBase object
* @return String
* @deprecated since 1.18; use $dbw->encodeExpiry() instead
@@ -947,8 +958,8 @@ class Block {
/**
* Decode expiry which has come from the DB
*
- * @param $expiry String: Database expiry format
- * @param $timestampType Int Requested timestamp format
+ * @param string $expiry Database expiry format
+ * @param int $timestampType Requested timestamp format
* @return String
* @deprecated since 1.18; use $wgLang->formatExpiry() instead
*/
@@ -971,9 +982,9 @@ class Block {
}
/**
- * Gets rid of uneeded numbers in quad-dotted/octet IP strings
+ * Gets rid of unneeded numbers in quad-dotted/octet IP strings
* For example, 127.111.113.151/24 -> 127.111.113.0/24
- * @param $range String: IP address to normalize
+ * @param string $range IP address to normalize
* @return string
* @deprecated since 1.18, call IP::sanitizeRange() directly
*/
@@ -986,9 +997,11 @@ class Block {
* Purge expired blocks from the ipblocks table
*/
public static function purgeExpired() {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->delete( 'ipblocks',
- array( 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), __METHOD__ );
+ if ( !wfReadOnly() ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->delete( 'ipblocks',
+ array( 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), __METHOD__ );
+ }
}
/**
@@ -1005,7 +1018,7 @@ class Block {
/**
* Convert a submitted expiry time, which may be relative ("2 weeks", etc) or absolute
* ("24 May 2034"), into an absolute timestamp we can put into the database.
- * @param $expiry String: whatever was typed into the form
+ * @param string $expiry whatever was typed into the form
* @return String: timestamp or "infinity" string for th DB implementation
* @deprecated since 1.18 moved to SpecialBlock::parseExpiryInput()
*/
@@ -1029,7 +1042,7 @@ class Block {
* @param $vagueTarget String|User|Int as above, but we will search for *any* block which
* affects that target (so for an IP address, get ranges containing that IP; and also
* get any relevant autoblocks). Leave empty or blank to skip IP-based lookups.
- * @param $fromMaster Bool whether to use the DB_MASTER database
+ * @param bool $fromMaster whether to use the DB_MASTER database
* @return Block|null (null if no relevant block could be found). The target and type
* of the returned Block will refer to the actual block which was found, which might
* not be the same as the target you gave if you used $vagueTarget!
@@ -1037,24 +1050,24 @@ class Block {
public static function newFromTarget( $specificTarget, $vagueTarget = null, $fromMaster = false ) {
list( $target, $type ) = self::parseTarget( $specificTarget );
- if( $type == Block::TYPE_ID || $type == Block::TYPE_AUTO ){
+ if( $type == Block::TYPE_ID || $type == Block::TYPE_AUTO ) {
return Block::newFromID( $target );
- } elseif( $target === null && $vagueTarget == '' ){
+ } elseif( $target === null && $vagueTarget == '' ) {
# We're not going to find anything useful here
# Be aware that the == '' check is explicit, since empty values will be
# passed by some callers (bug 29116)
return null;
- } elseif( in_array( $type, array( Block::TYPE_USER, Block::TYPE_IP, Block::TYPE_RANGE ) ) ) {
+ } elseif( in_array( $type, array( Block::TYPE_USER, Block::TYPE_IP, Block::TYPE_RANGE, null ) ) ) {
$block = new Block();
$block->fromMaster( $fromMaster );
- if( $type !== null ){
+ if( $type !== null ) {
$block->setTarget( $target );
}
- if( $block->newLoad( $vagueTarget ) ){
+ if( $block->newLoad( $vagueTarget ) ) {
return $block;
}
}
@@ -1062,22 +1075,23 @@ class Block {
}
/**
- * From an existing Block, get the target and the type of target. Note that it is
- * always safe to treat the target as a string; for User objects this will return
- * User::__toString() which in turn gives User::getName().
+ * From an existing Block, get the target and the type of target.
+ * Note that, except for null, it is always safe to treat the target
+ * as a string; for User objects this will return User::__toString()
+ * which in turn gives User::getName().
*
- * @param $target String|Int|User
- * @return array( User|String, Block::TYPE_ constant )
+ * @param $target String|Int|User|null
+ * @return array( User|String|null, Block::TYPE_ constant|null )
*/
public static function parseTarget( $target ) {
# We may have been through this before
- if( $target instanceof User ){
- if( IP::isValid( $target->getName() ) ){
+ if( $target instanceof User ) {
+ if( IP::isValid( $target->getName() ) ) {
return array( $target, self::TYPE_IP );
} else {
return array( $target, self::TYPE_USER );
}
- } elseif( $target === null ){
+ } elseif( $target === null ) {
return array( null, null );
}
@@ -1098,7 +1112,7 @@ class Block {
# Consider the possibility that this is not a username at all
# but actually an old subpage (bug #29797)
- if( strpos( $target, '/' ) !== false ){
+ if( strpos( $target, '/' ) !== false ) {
# An old subpage, drill down to the user behind it
$parts = explode( '/', $target );
$target = $parts[0];
@@ -1165,7 +1179,7 @@ class Block {
* Set the target for this block, and update $this->type accordingly
* @param $target Mixed
*/
- public function setTarget( $target ){
+ public function setTarget( $target ) {
list( $this->target, $this->type ) = self::parseTarget( $target );
}
@@ -1173,15 +1187,15 @@ class Block {
* Get the user who implemented this block
* @return User|string Local User object or string for a foreign user
*/
- public function getBlocker(){
+ public function getBlocker() {
return $this->blocker;
}
/**
* Set the user who implemented (or will implement) this block
- * @param $user User|string Local User object or username string for foriegn users
+ * @param $user User|string Local User object or username string for foreign users
*/
- public function setBlocker( $user ){
+ public function setBlocker( $user ) {
$this->blocker = $user;
}
}
diff --git a/includes/CacheHelper.php b/includes/CacheHelper.php
index ac46fc42..f0ae5a31 100644
--- a/includes/CacheHelper.php
+++ b/includes/CacheHelper.php
@@ -18,7 +18,7 @@
* http://www.gnu.org/copyleft/gpl.html
*
* @file
- * @licence GNU GPL v2 or later
+ * @license GNU GPL v2 or later
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
diff --git a/includes/Category.php b/includes/Category.php
index b7b12e8a..868d6c46 100644
--- a/includes/Category.php
+++ b/includes/Category.php
@@ -44,6 +44,7 @@ class Category {
/**
* Set up all member variables using a database query.
+ * @throws MWException
* @return bool True on success, false on failure.
*/
protected function initialize() {
@@ -57,6 +58,9 @@ class Category {
# Already initialized
return true;
}
+
+ wfProfileIn( __METHOD__ );
+
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow(
'category',
@@ -65,15 +69,17 @@ class Category {
__METHOD__
);
+ wfProfileOut( __METHOD__ );
+
if ( !$row ) {
# Okay, there were no contents. Nothing to initialize.
if ( $this->mTitle ) {
# If there is a title object but no record in the category table, treat this as an empty category
- $this->mID = false;
- $this->mName = $this->mTitle->getDBkey();
- $this->mPages = 0;
+ $this->mID = false;
+ $this->mName = $this->mTitle->getDBkey();
+ $this->mPages = 0;
$this->mSubcats = 0;
- $this->mFiles = 0;
+ $this->mFiles = 0;
return true;
} else {
@@ -81,11 +87,11 @@ class Category {
}
}
- $this->mID = $row->cat_id;
- $this->mName = $row->cat_title;
- $this->mPages = $row->cat_pages;
+ $this->mID = $row->cat_id;
+ $this->mName = $row->cat_title;
+ $this->mPages = $row->cat_pages;
$this->mSubcats = $row->cat_subcats;
- $this->mFiles = $row->cat_files;
+ $this->mFiles = $row->cat_files;
# (bug 13683) If the count is negative, then 1) it's obviously wrong
# and should not be kept, and 2) we *probably* don't have to scan many
@@ -100,7 +106,7 @@ class Category {
/**
* Factory function.
*
- * @param $name Array: A category name (no "Category:" prefix). It need
+ * @param array $name A category name (no "Category:" prefix). It need
* not be normalized, with spaces replaced by underscores.
* @return mixed Category, or false on a totally invalid name
*/
@@ -161,7 +167,7 @@ class Category {
# NOTE: the row often results from a LEFT JOIN on categorylinks. This may result in
# all the cat_xxx fields being null, if the category page exists, but nothing
- # was ever added to the category. This case should be treated linke an empty
+ # was ever added to the category. This case should be treated link an empty
# category, if possible.
if ( $row->cat_title === null ) {
@@ -173,41 +179,53 @@ class Category {
$cat->mName = $title->getDBkey(); # if we have a title object, fetch the category name from there
}
- $cat->mID = false;
+ $cat->mID = false;
$cat->mSubcats = 0;
- $cat->mPages = 0;
- $cat->mFiles = 0;
+ $cat->mPages = 0;
+ $cat->mFiles = 0;
} else {
- $cat->mName = $row->cat_title;
- $cat->mID = $row->cat_id;
+ $cat->mName = $row->cat_title;
+ $cat->mID = $row->cat_id;
$cat->mSubcats = $row->cat_subcats;
- $cat->mPages = $row->cat_pages;
- $cat->mFiles = $row->cat_files;
+ $cat->mPages = $row->cat_pages;
+ $cat->mFiles = $row->cat_files;
}
return $cat;
}
/** @return mixed DB key name, or false on failure */
- public function getName() { return $this->getX( 'mName' ); }
+ public function getName() {
+ return $this->getX( 'mName' );
+ }
/** @return mixed Category ID, or false on failure */
- public function getID() { return $this->getX( 'mID' ); }
+ public function getID() {
+ return $this->getX( 'mID' );
+ }
/** @return mixed Total number of member pages, or false on failure */
- public function getPageCount() { return $this->getX( 'mPages' ); }
+ public function getPageCount() {
+ return $this->getX( 'mPages' );
+ }
/** @return mixed Number of subcategories, or false on failure */
- public function getSubcatCount() { return $this->getX( 'mSubcats' ); }
+ public function getSubcatCount() {
+ return $this->getX( 'mSubcats' );
+ }
/** @return mixed Number of member files, or false on failure */
- public function getFileCount() { return $this->getX( 'mFiles' ); }
+ public function getFileCount() {
+ return $this->getX( 'mFiles' );
+ }
/**
* @return Title|bool Title for this category, or false on failure.
*/
public function getTitle() {
- if ( $this->mTitle ) return $this->mTitle;
+ if ( $this->mTitle ) {
+ return $this->mTitle;
+ }
if ( !$this->initialize() ) {
return false;
@@ -225,20 +243,22 @@ class Category {
* @return TitleArray object for category members.
*/
public function getMembers( $limit = false, $offset = '' ) {
+ wfProfileIn( __METHOD__ );
+
$dbr = wfGetDB( DB_SLAVE );
$conds = array( 'cl_to' => $this->getName(), 'cl_from = page_id' );
$options = array( 'ORDER BY' => 'cl_sortkey' );
if ( $limit ) {
- $options[ 'LIMIT' ] = $limit;
+ $options['LIMIT'] = $limit;
}
if ( $offset !== '' ) {
$conds[] = 'cl_sortkey > ' . $dbr->addQuotes( $offset );
}
- return TitleArray::newFromResult(
+ $result = TitleArray::newFromResult(
$dbr->select(
array( 'page', 'categorylinks' ),
array( 'page_id', 'page_namespace', 'page_title', 'page_len',
@@ -248,6 +268,10 @@ class Category {
$options
)
);
+
+ wfProfileOut( __METHOD__ );
+
+ return $result;
}
/**
@@ -258,7 +282,7 @@ class Category {
if ( !$this->initialize() ) {
return false;
}
- return $this-> { $key } ;
+ return $this->{$key};
}
/**
@@ -278,8 +302,10 @@ class Category {
}
}
+ wfProfileIn( __METHOD__ );
+
$dbw = wfGetDB( DB_MASTER );
- $dbw->begin( __METHOD__ );
+ $dbw->begin( __METHOD__ );
# Insert the row if it doesn't exist yet (e.g., this is being run via
# update.php from a pre-1.16 schema). TODO: This will cause lots and
@@ -302,12 +328,12 @@ class Category {
$result = $dbw->selectRow(
array( 'categorylinks', 'page' ),
array( 'pages' => 'COUNT(*)',
- 'subcats' => "COUNT($cond1)",
- 'files' => "COUNT($cond2)"
+ 'subcats' => "COUNT($cond1)",
+ 'files' => "COUNT($cond2)"
),
array( 'cl_to' => $this->mName, 'page_id = cl_from' ),
__METHOD__,
- 'LOCK IN SHARE MODE'
+ array( 'LOCK IN SHARE MODE' )
);
$ret = $dbw->update(
'category',
@@ -321,10 +347,12 @@ class Category {
);
$dbw->commit( __METHOD__ );
+ wfProfileOut( __METHOD__ );
+
# Now we should update our local counts.
- $this->mPages = $result->pages;
+ $this->mPages = $result->pages;
$this->mSubcats = $result->subcats;
- $this->mFiles = $result->files;
+ $this->mFiles = $result->files;
return $ret;
}
diff --git a/includes/CategoryPage.php b/includes/CategoryPage.php
index 32e270e8..43ab4dbd 100644
--- a/includes/CategoryPage.php
+++ b/includes/CategoryPage.php
@@ -40,7 +40,7 @@ class CategoryPage extends Article {
/**
* Constructor from a page id
- * @param $id Int article ID to load
+ * @param int $id article ID to load
* @return CategoryPage|null
*/
public static function newFromID( $id ) {
@@ -56,7 +56,7 @@ class CategoryPage extends Article {
$diffOnly = $request->getBool( 'diffonly',
$this->getContext()->getUser()->getOption( 'diffonly' ) );
- if ( isset( $diff ) && $diffOnly ) {
+ if ( $diff !== null && $diffOnly ) {
parent::view();
return;
}
diff --git a/includes/CategoryViewer.php b/includes/CategoryViewer.php
index 3bb2bc9b..970adb53 100644
--- a/includes/CategoryViewer.php
+++ b/includes/CategoryViewer.php
@@ -71,11 +71,12 @@ class CategoryViewer extends ContextSource {
* @since 1.19 $context is a second, required parameter
* @param $title Title
* @param $context IContextSource
- * @param $from String
- * @param $until String
+ * @param array $from An array with keys page, subcat,
+ * and file for offset of results of each section (since 1.17)
+ * @param array $until An array with 3 keys for until of each section (since 1.17)
* @param $query Array
*/
- function __construct( $title, IContextSource $context, $from = '', $until = '', $query = array() ) {
+ function __construct( $title, IContextSource $context, $from = array(), $until = array(), $query = array() ) {
global $wgCategoryPagingLimit;
$this->title = $title;
$this->setContext( $context );
@@ -173,7 +174,7 @@ class CategoryViewer extends ContextSource {
/**
* Add a subcategory to the internal lists, using a title object
- * @deprecated since 1.17 kept for compatibility, please use addSubcategoryObject instead
+ * @deprecated since 1.17 kept for compatibility, use addSubcategoryObject instead
*/
function addSubcategory( Title $title, $sortkey, $pageLength ) {
wfDeprecated( __METHOD__, '1.17' );
@@ -181,14 +182,14 @@ class CategoryViewer extends ContextSource {
}
/**
- * Get the character to be used for sorting subcategories.
- * If there's a link from Category:A to Category:B, the sortkey of the resulting
- * entry in the categorylinks table is Category:A, not A, which it SHOULD be.
- * Workaround: If sortkey == "Category:".$title, than use $title for sorting,
- * else use sortkey...
- *
- * @param Title $title
- * @param string $sortkey The human-readable sortkey (before transforming to icu or whatever).
+ * Get the character to be used for sorting subcategories.
+ * If there's a link from Category:A to Category:B, the sortkey of the resulting
+ * entry in the categorylinks table is Category:A, not A, which it SHOULD be.
+ * Workaround: If sortkey == "Category:".$title, than use $title for sorting,
+ * else use sortkey...
+ *
+ * @param Title $title
+ * @param string $sortkey The human-readable sortkey (before transforming to icu or whatever).
* @return string
*/
function getSubcategorySortChar( $title, $sortkey ) {
@@ -248,7 +249,7 @@ class CategoryViewer extends ContextSource {
$link = Linker::link( $title );
if ( $isRedirect ) {
// This seems kind of pointless given 'mw-redirect' class,
- // but keeping for back-compatiability with user css.
+ // but keeping for back-compatibility with user css.
$link = '<span class="redirect-in-category">' . $link . '</span>';
}
$this->articles[] = $link;
@@ -259,15 +260,15 @@ class CategoryViewer extends ContextSource {
function finaliseCategoryState() {
if ( $this->flip['subcat'] ) {
- $this->children = array_reverse( $this->children );
+ $this->children = array_reverse( $this->children );
$this->children_start_char = array_reverse( $this->children_start_char );
}
if ( $this->flip['page'] ) {
- $this->articles = array_reverse( $this->articles );
+ $this->articles = array_reverse( $this->articles );
$this->articles_start_char = array_reverse( $this->articles_start_char );
}
if ( !$this->showGallery && $this->flip['file'] ) {
- $this->imgsNoGallery = array_reverse( $this->imgsNoGallery );
+ $this->imgsNoGallery = array_reverse( $this->imgsNoGallery );
$this->imgsNoGallery_start_char = array_reverse( $this->imgsNoGallery_start_char );
}
}
@@ -287,10 +288,10 @@ class CategoryViewer extends ContextSource {
# the collation in the database differs from the one
# set in $wgCategoryCollation, pagination might go totally haywire.
$extraConds = array( 'cl_type' => $type );
- if ( $this->from[$type] !== null ) {
+ if ( isset( $this->from[$type] ) && $this->from[$type] !== null ) {
$extraConds[] = 'cl_sortkey >= '
. $dbr->addQuotes( $this->collation->getSortKey( $this->from[$type] ) );
- } elseif ( $this->until[$type] !== null ) {
+ } elseif ( isset( $this->until[$type] ) && $this->until[$type] !== null ) {
$extraConds[] = 'cl_sortkey < '
. $dbr->addQuotes( $this->collation->getSortKey( $this->until[$type] ) );
$this->flip[$type] = true;
@@ -302,7 +303,7 @@ class CategoryViewer extends ContextSource {
'page_is_redirect', 'cl_sortkey', 'cat_id', 'cat_title',
'cat_subcats', 'cat_pages', 'cat_files',
'cl_sortkey_prefix', 'cl_collation' ),
- array_merge( array( 'cl_to' => $this->title->getDBkey() ), $extraConds ),
+ array_merge( array( 'cl_to' => $this->title->getDBkey() ), $extraConds ),
__METHOD__,
array(
'USE INDEX' => array( 'categorylinks' => 'cl_sortkey' ),
@@ -310,8 +311,11 @@ class CategoryViewer extends ContextSource {
'ORDER BY' => $this->flip[$type] ? 'cl_sortkey DESC' : 'cl_sortkey',
),
array(
- 'categorylinks' => array( 'INNER JOIN', 'cl_from = page_id' ),
- 'category' => array( 'LEFT JOIN', 'cat_title = page_title AND page_namespace = ' . NS_CATEGORY )
+ 'categorylinks' => array( 'INNER JOIN', 'cl_from = page_id' ),
+ 'category' => array( 'LEFT JOIN', array(
+ 'cat_title = page_title',
+ 'page_namespace' => NS_CATEGORY
+ ))
)
);
@@ -388,7 +392,7 @@ class CategoryViewer extends ContextSource {
$r = '';
# @todo FIXME: Here and in the other two sections: we don't need to bother
- # with this rigamarole if the entire category contents fit on one page
+ # with this rigmarole if the entire category contents fit on one page
# and have already been retrieved. We can just use $rescnt in that
# case and save a query and some logic.
$dbcnt = $this->cat->getPageCount() - $this->cat->getSubcatCount()
@@ -437,13 +441,13 @@ class CategoryViewer extends ContextSource {
* Get the paging links for a section (subcats/pages/files), to go at the top and bottom
* of the output.
*
- * @param $type String: 'page', 'subcat', or 'file'
+ * @param string $type 'page', 'subcat', or 'file'
* @return String: HTML output, possibly empty if there are no other pages
*/
private function getSectionPagingLinks( $type ) {
- if ( $this->until[$type] !== null ) {
+ if ( isset( $this->until[$type] ) && $this->until[$type] !== null ) {
return $this->pagingLinks( $this->nextPage[$type], $this->until[$type], $type );
- } elseif ( $this->nextPage[$type] !== null || $this->from[$type] !== null ) {
+ } elseif ( $this->nextPage[$type] !== null || ( isset( $this->from[$type] ) && $this->from[$type] !== null ) ) {
return $this->pagingLinks( $this->from[$type], $this->nextPage[$type], $type );
} else {
return '';
@@ -469,7 +473,7 @@ class CategoryViewer extends ContextSource {
*/
function formatList( $articles, $articles_start_char, $cutoff = 6 ) {
$list = '';
- if ( count ( $articles ) > $cutoff ) {
+ if ( count( $articles ) > $cutoff ) {
$list = self::columnList( $articles, $articles_start_char );
} elseif ( count( $articles ) > 0 ) {
// for short lists of articles in categories.
@@ -478,7 +482,7 @@ class CategoryViewer extends ContextSource {
$pageLang = $this->title->getPageLanguage();
$attribs = array( 'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(),
- 'class' => 'mw-content-'.$pageLang->getDir() );
+ 'class' => 'mw-content-' . $pageLang->getDir() );
$list = Html::rawElement( 'div', $attribs, $list );
return $list;
@@ -569,9 +573,9 @@ class CategoryViewer extends ContextSource {
/**
* Create paging links, as a helper method to getSectionPagingLinks().
*
- * @param $first String The 'until' parameter for the generated URL
- * @param $last String The 'from' parameter for the genererated URL
- * @param $type String A prefix for parameters, 'page' or 'subcat' or
+ * @param string $first The 'until' parameter for the generated URL
+ * @param string $last The 'from' parameter for the generated URL
+ * @param string $type A prefix for parameters, 'page' or 'subcat' or
* 'file'
* @return String HTML
*/
@@ -604,7 +608,7 @@ class CategoryViewer extends ContextSource {
);
}
- return $this->msg('categoryviewer-pagedlinks')->rawParams($prevLink, $nextLink)->escaped();
+ return $this->msg( 'categoryviewer-pagedlinks' )->rawParams( $prevLink, $nextLink )->escaped();
}
/**
@@ -612,7 +616,8 @@ class CategoryViewer extends ContextSource {
* corresponds to the correct segment of the category.
*
* @param Title $title: The title (usually $this->title)
- * @param String $section: Which section
+ * @param string $section: Which section
+ * @throws MWException
* @return Title
*/
private function addFragmentToTitle( $title, $section ) {
@@ -634,6 +639,7 @@ class CategoryViewer extends ContextSource {
return Title::makeTitle( $title->getNamespace(),
$title->getDBkey(), $fragment );
}
+
/**
* What to do if the category table conflicts with the number of results
* returned? This function says what. Each type is considered independently
@@ -644,9 +650,9 @@ class CategoryViewer extends ContextSource {
* category-subcat-count-limited, category-file-count,
* category-file-count-limited.
*
- * @param $rescnt Int: The number of items returned by our database query.
- * @param $dbcnt Int: The number of items according to the category table.
- * @param $type String: 'subcat', 'article', or 'file'
+ * @param int $rescnt The number of items returned by our database query.
+ * @param int $dbcnt The number of items according to the category table.
+ * @param string $type 'subcat', 'article', or 'file'
* @return String: A message giving the number of items, to output to HTML.
*/
private function getCountMessage( $rescnt, $dbcnt, $type ) {
@@ -671,12 +677,15 @@ class CategoryViewer extends ContextSource {
}
$fromOrUntil = false;
- if ( $this->from[$pagingType] !== null || $this->until[$pagingType] !== null ) {
+ if ( ( isset( $this->from[$pagingType] ) && $this->from[$pagingType] !== null ) ||
+ ( isset( $this->until[$pagingType] ) && $this->until[$pagingType] !== null )
+ ) {
$fromOrUntil = true;
}
- if ( $dbcnt == $rescnt || ( ( $rescnt == $this->limit || $fromOrUntil )
- && $dbcnt > $rescnt ) ) {
+ if ( $dbcnt == $rescnt ||
+ ( ( $rescnt == $this->limit || $fromOrUntil ) && $dbcnt > $rescnt )
+ ) {
# Case 1: seems sane.
$totalcnt = $dbcnt;
} elseif ( $rescnt < $this->limit && !$fromOrUntil ) {
diff --git a/includes/Categoryfinder.php b/includes/Categoryfinder.php
index e2b6a0ca..6ef224b6 100644
--- a/includes/Categoryfinder.php
+++ b/includes/Categoryfinder.php
@@ -28,17 +28,17 @@
*
* Example use :
* <code>
- * # Determines whether the article with the page_id 12345 is in both
- * # "Category 1" and "Category 2" or their subcategories, respectively
+ * # Determines whether the article with the page_id 12345 is in both
+ * # "Category 1" and "Category 2" or their subcategories, respectively
*
- * $cf = new Categoryfinder;
- * $cf->seed(
- * array( 12345 ),
- * array( 'Category 1', 'Category 2' ),
- * 'AND'
- * );
- * $a = $cf->run();
- * print implode( ',' , $a );
+ * $cf = new Categoryfinder;
+ * $cf->seed(
+ * array( 12345 ),
+ * array( 'Category 1', 'Category 2' ),
+ * 'AND'
+ * );
+ * $a = $cf->run();
+ * print implode( ',' , $a );
* </code>
*
*/
@@ -66,7 +66,7 @@ class Categoryfinder {
* Initializes the instance. Do this prior to calling run().
* @param $article_ids Array of article IDs
* @param $categories FIXME
- * @param $mode String: FIXME, default 'AND'.
+ * @param string $mode FIXME, default 'AND'.
* @todo FIXME: $categories/$mode
*/
function seed( $article_ids, $categories, $mode = 'AND' ) {
@@ -111,9 +111,9 @@ class Categoryfinder {
/**
* This functions recurses through the parent representation, trying to match the conditions
- * @param $id int The article/category to check
- * @param $conds array The array of categories to match
- * @param $path array used to check for recursion loops
+ * @param int $id The article/category to check
+ * @param array $conds The array of categories to match
+ * @param array $path used to check for recursion loops
* @return bool Does this match the conditions?
*/
function check( $id, &$conds, $path = array() ) {
@@ -124,7 +124,7 @@ class Categoryfinder {
$path[] = $id;
- # Shortcut (runtime paranoia): No contitions=all matched
+ # Shortcut (runtime paranoia): No conditions=all matched
if ( count( $conds ) == 0 ) {
return true;
}
@@ -135,7 +135,7 @@ class Categoryfinder {
# iterate through the parents
foreach ( $this->parents[$id] as $p ) {
- $pname = $p->cl_to ;
+ $pname = $p->cl_to;
# Is this a condition?
if ( isset( $conds[$pname] ) ) {
@@ -172,6 +172,8 @@ class Categoryfinder {
* Scans a "parent layer" of the articles/categories in $this->next
*/
function scan_next_layer() {
+ wfProfileIn( __METHOD__ );
+
# Find all parents of the article currently in $this->next
$layer = array();
$res = $this->dbr->select(
@@ -209,7 +211,7 @@ class Categoryfinder {
$res = $this->dbr->select(
/* FROM */ 'page',
/* SELECT */ array( 'page_id', 'page_title' ),
- /* WHERE */ array( 'page_namespace' => NS_CATEGORY , 'page_title' => $layer ),
+ /* WHERE */ array( 'page_namespace' => NS_CATEGORY, 'page_title' => $layer ),
__METHOD__ . '-2'
);
foreach ( $res as $o ) {
@@ -225,6 +227,7 @@ class Categoryfinder {
foreach ( $layer as $v ) {
$this->deadend[$v] = $v;
}
- }
+ wfProfileOut( __METHOD__ );
+ }
}
diff --git a/includes/Cdb.php b/includes/Cdb.php
index ae2e5b18..a142c7cb 100644
--- a/includes/Cdb.php
+++ b/includes/Cdb.php
@@ -133,8 +133,9 @@ class CdbReader_DBA {
}
function close() {
- if( isset($this->handle) )
+ if( isset( $this->handle ) ) {
dba_close( $this->handle );
+ }
unset( $this->handle );
}
@@ -143,7 +144,6 @@ class CdbReader_DBA {
}
}
-
/**
* Writer class which uses the DBA extension
*/
@@ -164,8 +164,9 @@ class CdbWriter_DBA {
}
function close() {
- if( isset($this->handle) )
+ if( isset( $this->handle ) ) {
dba_close( $this->handle );
+ }
if ( wfIsWindows() ) {
unlink( $this->realFileName );
}
@@ -181,4 +182,3 @@ class CdbWriter_DBA {
}
}
}
-
diff --git a/includes/Cdb_PHP.php b/includes/Cdb_PHP.php
index 02be65f3..71b55f87 100644
--- a/includes/Cdb_PHP.php
+++ b/includes/Cdb_PHP.php
@@ -126,6 +126,7 @@ class CdbReader_PHP extends CdbReader {
/**
* @param $fileName string
+ * @throws MWException
*/
function __construct( $fileName ) {
$this->fileName = $fileName;
@@ -179,7 +180,7 @@ class CdbReader_PHP extends CdbReader {
protected function read( $length, $pos ) {
if ( fseek( $this->handle, $pos ) == -1 ) {
// This can easily happen if the internal pointers are incorrect
- throw new MWException(
+ throw new MWException(
'Seek failed, file "' . $this->fileName . '" may be corrupted.' );
}
@@ -198,12 +199,13 @@ class CdbReader_PHP extends CdbReader {
/**
* Unpack an unsigned integer and throw an exception if it needs more than 31 bits
* @param $s
- * @return
+ * @throws MWException
+ * @return mixed
*/
protected function unpack31( $s ) {
$data = unpack( 'V', $s );
if ( $data[1] > 0x7fffffff ) {
- throw new MWException(
+ throw new MWException(
'Error in CDB file "' . $this->fileName . '", integer too big.' );
}
return $data[1];
@@ -330,10 +332,10 @@ class CdbWriter_PHP extends CdbWriter {
*/
public function close() {
$this->finish();
- if( isset($this->handle) ) {
+ if( isset( $this->handle ) ) {
fclose( $this->handle );
}
- if ( wfIsWindows() && file_exists($this->realFileName) ) {
+ if ( wfIsWindows() && file_exists( $this->realFileName ) ) {
unlink( $this->realFileName );
}
if ( !rename( $this->tmpFileName, $this->realFileName ) ) {
@@ -349,7 +351,7 @@ class CdbWriter_PHP extends CdbWriter {
protected function write( $buf ) {
$len = fwrite( $this->handle, $buf );
if ( $len !== strlen( $buf ) ) {
- $this->throwException( 'Error writing to CDB file "'.$this->tmpFileName.'".' );
+ $this->throwException( 'Error writing to CDB file "' . $this->tmpFileName . '".' );
}
}
@@ -361,7 +363,7 @@ class CdbWriter_PHP extends CdbWriter {
$newpos = $this->pos + $len;
if ( $newpos > 0x7fffffff ) {
$this->throwException(
- 'A value in the CDB file "'.$this->tmpFileName.'" is too large.' );
+ 'A value in the CDB file "' . $this->tmpFileName . '" is too large.' );
}
$this->pos = $newpos;
}
@@ -390,10 +392,10 @@ class CdbWriter_PHP extends CdbWriter {
*/
protected function addbegin( $keylen, $datalen ) {
if ( $keylen > 0x7fffffff ) {
- $this->throwException( 'Key length too long in file "'.$this->tmpFileName.'".' );
+ $this->throwException( 'Key length too long in file "' . $this->tmpFileName . '".' );
}
if ( $datalen > 0x7fffffff ) {
- $this->throwException( 'Data length too long in file "'.$this->tmpFileName.'".' );
+ $this->throwException( 'Data length too long in file "' . $this->tmpFileName . '".' );
}
$buf = pack( 'VV', $keylen, $datalen );
$this->write( $buf );
@@ -468,14 +470,14 @@ class CdbWriter_PHP extends CdbWriter {
// Write the pointer array at the start of the file
rewind( $this->handle );
if ( ftell( $this->handle ) != 0 ) {
- $this->throwException( 'Error rewinding to start of file "'.$this->tmpFileName.'".' );
+ $this->throwException( 'Error rewinding to start of file "' . $this->tmpFileName . '".' );
}
$this->write( $final );
}
/**
* Clean up the temp file and throw an exception
- *
+ *
* @param $msg string
* @throws MWException
*/
diff --git a/includes/ChangeTags.php b/includes/ChangeTags.php
index 0ebc926f..3adf58f8 100644
--- a/includes/ChangeTags.php
+++ b/includes/ChangeTags.php
@@ -25,8 +25,8 @@ class ChangeTags {
/**
* Creates HTML for the given tags
*
- * @param $tags String: Comma-separated list of tags
- * @param $page String: A label for the type of action which is being displayed,
+ * @param string $tags Comma-separated list of tags
+ * @param string $page A label for the type of action which is being displayed,
* for example: 'history', 'contributions' or 'newpages'
*
* @return Array with two items: (html, classes)
@@ -62,7 +62,7 @@ class ChangeTags {
/**
* Get a short description for a tag
*
- * @param $tag String: tag
+ * @param string $tag tag
*
* @return String: Short description of the tag from "mediawiki:tag-$tag" if this message exists,
* html-escaped version of $tag otherwise
@@ -75,12 +75,13 @@ class ChangeTags {
/**
* Add tags to a change given its rc_id, rev_id and/or log_id
*
- * @param $tags String|Array: Tags to add to the change
+ * @param string|array $tags Tags to add to the change
* @param $rc_id int: rc_id of the change to add the tags to
* @param $rev_id int: rev_id of the change to add the tags to
* @param $log_id int: log_id of the change to add the tags to
- * @param $params String: params to put in the ct_params field of tabel 'change_tag'
+ * @param string $params params to put in the ct_params field of table 'change_tag'
*
+ * @throws MWException
* @return bool: false if no changes are made, otherwise true
*
* @exception MWException when $rc_id, $rev_id and $log_id are all null
@@ -159,17 +160,16 @@ class ChangeTags {
* Handles selecting tags, and filtering.
* Needs $tables to be set up properly, so we can figure out which join conditions to use.
*
- * @param $tables String|Array: Tabel names, see DatabaseBase::select
- * @param $fields String|Array: Fields used in query, see DatabaseBase::select
- * @param $conds String|Array: conditions used in query, see DatabaseBase::select
+ * @param string|array $tables Table names, see DatabaseBase::select
+ * @param string|array $fields Fields used in query, see DatabaseBase::select
+ * @param string|array $conds conditions used in query, see DatabaseBase::select
* @param $join_conds Array: join conditions, see DatabaseBase::select
- * @param $options Array: options, see Database::select
- * @param $filter_tag String: tag to select on
- *
- * @exception MWException when unable to determine appropriate JOIN condition for tagging
+ * @param array $options options, see Database::select
+ * @param bool|string $filter_tag Tag to select on
*
+ * @throws MWException When unable to determine appropriate JOIN condition for tagging
*/
- static function modifyDisplayQuery( &$tables, &$fields, &$conds,
+ static function modifyDisplayQuery( &$tables, &$fields, &$conds,
&$join_conds, &$options, $filter_tag = false ) {
global $wgRequest, $wgUseTagFilter;
@@ -211,7 +211,7 @@ class ChangeTags {
/**
* Build a text box to select a change tag
*
- * @param $selected String: tag to select by default
+ * @param string $selected tag to select by default
* @param $fullForm Boolean:
* - if false, then it returns an array of (label, form).
* - if true, it returns an entire form around the selector.
@@ -221,11 +221,12 @@ class ChangeTags {
* - if $fullForm is false: Array with
* - if $fullForm is true: String, html fragment
*/
- public static function buildTagFilterSelector( $selected='', $fullForm = false, Title $title = null ) {
+ public static function buildTagFilterSelector( $selected = '', $fullForm = false, Title $title = null ) {
global $wgUseTagFilter;
- if ( !$wgUseTagFilter || !count( self::listDefinedTags() ) )
+ if ( !$wgUseTagFilter || !count( self::listDefinedTags() ) ) {
return $fullForm ? '' : array();
+ }
$data = array( Html::rawElement( 'label', array( 'for' => 'tagfilter' ), wfMessage( 'tag-filter' )->parse() ),
Xml::input( 'tagfilter', 20, $selected, array( 'class' => 'mw-tagfilter-input' ) ) );
diff --git a/includes/ChangesFeed.php b/includes/ChangesFeed.php
index ee4c2d64..476de5bd 100644
--- a/includes/ChangesFeed.php
+++ b/includes/ChangesFeed.php
@@ -31,8 +31,8 @@ class ChangesFeed {
/**
* Constructor
*
- * @param $format String: feed's format (either 'rss' or 'atom')
- * @param $type String: type of feed (for cache keys)
+ * @param string $format feed's format (either 'rss' or 'atom')
+ * @param string $type type of feed (for cache keys)
*/
public function __construct( $format, $type ) {
$this->format = $format;
@@ -42,9 +42,9 @@ class ChangesFeed {
/**
* Get a ChannelFeed subclass object to use
*
- * @param $title String: feed's title
- * @param $description String: feed's description
- * @param $url String: url of origin page
+ * @param string $title feed's title
+ * @param string $description feed's description
+ * @param string $url url of origin page
* @return ChannelFeed subclass or false on failure
*/
public function getFeedObject( $title, $description, $url ) {
@@ -110,9 +110,9 @@ class ChangesFeed {
/**
* Save to feed result to $messageMemc
*
- * @param $feed String: feed's content
- * @param $timekey String: memcached key of the last modification
- * @param $key String: memcached key of the content
+ * @param string $feed feed's content
+ * @param string $timekey memcached key of the last modification
+ * @param string $key memcached key of the content
*/
public function saveToCache( $feed, $timekey, $key ) {
global $messageMemc;
@@ -125,8 +125,8 @@ class ChangesFeed {
* Try to load the feed result from $messageMemc
*
* @param $lastmod Integer: timestamp of the last item in the recentchanges table
- * @param $timekey String: memcached key of the last modification
- * @param $key String: memcached key of the content
+ * @param string $timekey memcached key of the last modification
+ * @param string $key memcached key of the content
* @return string|bool feed's content on cache hit or false on cache miss
*/
public function loadFromCache( $lastmod, $timekey, $key ) {
@@ -135,7 +135,7 @@ class ChangesFeed {
$feedLastmod = $messageMemc->get( $timekey );
if( ( $wgFeedCacheTimeout > 0 ) && $feedLastmod ) {
- /**
+ /**
* If the cached feed was rendered very recently, we may
* go ahead and use it even if there have been edits made
* since it was rendered. This keeps a swarm of requests
diff --git a/includes/ChangesList.php b/includes/ChangesList.php
index 84677124..8461001e 100644
--- a/includes/ChangesList.php
+++ b/includes/ChangesList.php
@@ -30,7 +30,7 @@
*/
class RCCacheEntry extends RecentChange {
var $secureName, $link;
- var $curlink , $difflink, $lastlink, $usertalklink, $versionlink;
+ var $curlink, $difflink, $lastlink, $usertalklink, $versionlink;
var $userlink, $timestamp, $watched;
/**
@@ -60,7 +60,7 @@ class ChangesList extends ContextSource {
protected $message;
/**
- * Changeslist contructor
+ * Changeslist constructor
*
* @param $obj Skin or IContextSource
*/
@@ -80,7 +80,7 @@ class ChangesList extends ContextSource {
* This first argument used to be an User object.
*
* @deprecated in 1.18; use newFromContext() instead
- * @param $unused string|User Unused
+ * @param string|User $unused Unused
* @return ChangesList|EnhancedChangesList|OldChangesList derivative
*/
public static function newFromUser( $unused ) {
@@ -130,13 +130,13 @@ class ChangesList extends ContextSource {
/**
* Returns the appropriate flags for new page, minor change and patrolling
- * @param $flags Array Associative array of 'flag' => Bool
- * @param $nothing String to use for empty space
+ * @param array $flags Associative array of 'flag' => Bool
+ * @param string $nothing to use for empty space
* @return String
*/
protected function recentChangesFlags( $flags, $nothing = '&#160;' ) {
$f = '';
- foreach( array( 'newpage', 'minor', 'bot', 'unpatrolled' ) as $flag ){
+ foreach( array( 'newpage', 'minor', 'bot', 'unpatrolled' ) as $flag ) {
$f .= isset( $flags[$flag] ) && $flags[$flag]
? self::flag( $flag )
: $nothing;
@@ -150,7 +150,7 @@ class ChangesList extends ContextSource {
* unpatrolled edit. By default in English it will contain "N", "m", "b",
* "!" respectively, plus it will have an appropriate title and class.
*
- * @param $flag String: 'newpage', 'unpatrolled', 'minor', or 'bot'
+ * @param string $flag 'newpage', 'unpatrolled', 'minor', or 'bot'
* @return String: Raw HTML
*/
public static function flag( $flag ) {
@@ -217,7 +217,7 @@ class ChangesList extends ContextSource {
$lang = $context->getLanguage();
$code = $lang->getCode();
static $fastCharDiff = array();
- if ( !isset($fastCharDiff[$code]) ) {
+ if ( !isset( $fastCharDiff[$code] ) ) {
$fastCharDiff[$code] = $wgMiserMode || $context->msg( 'rc-change-size' )->plain() === '$1';
}
@@ -286,6 +286,10 @@ class ChangesList extends ContextSource {
}
}
+ /**
+ * @param string $s HTML to update
+ * @param $rc_timestamp mixed
+ */
public function insertDateHeader( &$s, $rc_timestamp ) {
# Make date header if necessary
$date = $this->getLanguage()->userDate( $rc_timestamp, $this->getUser() );
@@ -299,6 +303,11 @@ class ChangesList extends ContextSource {
}
}
+ /**
+ * @param string $s HTML to update
+ * @param $title Title
+ * @param $logtype string
+ */
public function insertLog( &$s, $title, $logtype ) {
$page = new LogPage( $logtype );
$logname = $page->getName()->escaped();
@@ -306,7 +315,7 @@ class ChangesList extends ContextSource {
}
/**
- * @param $s
+ * @param string $s HTML to update
* @param $rc RecentChange
* @param $unpatrolled
*/
@@ -319,7 +328,7 @@ class ChangesList extends ContextSource {
} else {
$query = array(
'curid' => $rc->mAttribs['rc_cur_id'],
- 'diff' => $rc->mAttribs['rc_this_oldid'],
+ 'diff' => $rc->mAttribs['rc_this_oldid'],
'oldid' => $rc->mAttribs['rc_last_oldid']
);
@@ -349,7 +358,7 @@ class ChangesList extends ContextSource {
}
/**
- * @param $s
+ * @param string $s HTML to update
* @param $rc RecentChange
* @param $unpatrolled
* @param $watched
@@ -369,7 +378,7 @@ class ChangesList extends ContextSource {
array( 'class' => 'mw-changeslist-title' ),
$params
);
- if( $this->isDeleted($rc,Revision::DELETED_TEXT) ) {
+ if( $this->isDeleted( $rc, Revision::DELETED_TEXT ) ) {
$articlelink = '<span class="history-deleted">' . $articlelink . '</span>';
}
# To allow for boldening pages watched by this user
@@ -378,21 +387,34 @@ class ChangesList extends ContextSource {
$articlelink .= $this->getLanguage()->getDirMark();
wfRunHooks( 'ChangesListInsertArticleLink',
- array(&$this, &$articlelink, &$s, &$rc, $unpatrolled, $watched) );
+ array( &$this, &$articlelink, &$s, &$rc, $unpatrolled, $watched ) );
$s .= " $articlelink";
}
/**
- * @param $s
+ * Get the timestamp from $rc formatted with current user's settings
+ * and a separator
+ *
* @param $rc RecentChange
+ * @return string HTML fragment
*/
- public function insertTimestamp( &$s, $rc ) {
- $s .= $this->message['semicolon-separator'] . '<span class="mw-changeslist-date">' .
+ public function getTimestamp( $rc ) {
+ return $this->message['semicolon-separator'] . '<span class="mw-changeslist-date">' .
$this->getLanguage()->userTime( $rc->mAttribs['rc_timestamp'], $this->getUser() ) . '</span> <span class="mw-changeslist-separator">. .</span> ';
}
/**
+ * Insert time timestamp string from $rc into $s
+ *
+ * @param string $s HTML to update
+ * @param $rc RecentChange
+ */
+ public function insertTimestamp( &$s, $rc ) {
+ $s .= $this->getTimestamp( $rc );
+ }
+
+ /**
* Insert links to user page, user talk page and eventually a blocking link
*
* @param &$s String HTML to update
@@ -435,6 +457,7 @@ class ChangesList extends ContextSource {
return Linker::commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() );
}
}
+ return '';
}
/**
@@ -511,15 +534,15 @@ class ChangesList extends ContextSource {
$page = $rc->getTitle();
/** Check for rollback and edit permissions, disallow special pages, and only
* show a link on the top-most revision */
- if ( $this->getUser()->isAllowed('rollback') && $rc->mAttribs['page_latest'] == $rc->mAttribs['rc_this_oldid'] )
+ if ( $this->getUser()->isAllowed( 'rollback' ) && $rc->mAttribs['page_latest'] == $rc->mAttribs['rc_this_oldid'] )
{
$rev = new Revision( array(
- 'id' => $rc->mAttribs['rc_this_oldid'],
- 'user' => $rc->mAttribs['rc_user'],
+ 'title' => $page,
+ 'id' => $rc->mAttribs['rc_this_oldid'],
+ 'user' => $rc->mAttribs['rc_user'],
'user_text' => $rc->mAttribs['rc_user_text'],
- 'deleted' => $rc->mAttribs['rc_deleted']
+ 'deleted' => $rc->mAttribs['rc_deleted']
) );
- $rev->setTitle( $page );
$s .= ' '.Linker::generateRollback( $rev, $this->getContext() );
}
}
@@ -531,16 +554,16 @@ class ChangesList extends ContextSource {
* @param $classes
*/
public function insertTags( &$s, &$rc, &$classes ) {
- if ( empty($rc->mAttribs['ts_tags']) )
+ if ( empty( $rc->mAttribs['ts_tags'] ) )
return;
- list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow( $rc->mAttribs['ts_tags'], 'changeslist' );
+ list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow( $rc->mAttribs['ts_tags'], 'changeslist' );
$classes = array_merge( $classes, $newClasses );
$s .= ' ' . $tagSummary;
}
public function insertExtra( &$s, &$rc, &$classes ) {
- ## Empty, used for subclassers to add anything special.
+ // Empty, used for subclasses to add anything special.
}
protected function showAsUnpatrolled( RecentChange $rc ) {
@@ -556,7 +579,6 @@ class ChangesList extends ContextSource {
}
}
-
/**
* Generate a list of changes using the good old system (no javascript)
*/
@@ -565,9 +587,10 @@ class OldChangesList extends ChangesList {
* Format a line using the old system (aka without any javascript).
*
* @param $rc RecentChange, passed by reference
- * @param $watched Bool (default false)
- * @param $linenumber Int (default null)
- * @return string
+ * @param bool $watched (default false)
+ * @param int $linenumber (default null)
+ *
+ * @return string|bool
*/
public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) {
global $wgRCShowChangedSize;
@@ -655,17 +678,19 @@ class OldChangesList extends ChangesList {
}
if( $this->watchlist ) {
- $classes[] = Sanitizer::escapeClass( 'watchlist-'.$rc->mAttribs['rc_namespace'].'-'.$rc->mAttribs['rc_title'] );
+ $classes[] = Sanitizer::escapeClass( 'watchlist-' . $rc->mAttribs['rc_namespace'] . '-' . $rc->mAttribs['rc_title'] );
}
- wfRunHooks( 'OldChangesListRecentChangesLine', array(&$this, &$s, $rc) );
+ if ( !wfRunHooks( 'OldChangesListRecentChangesLine', array( &$this, &$s, $rc, &$classes ) ) ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
wfProfileOut( __METHOD__ );
- return "$dateheader<li class=\"".implode( ' ', $classes )."\">".$s."</li>\n";
+ return "$dateheader<li class=\"" . implode( ' ', $classes ) . "\">" . $s . "</li>\n";
}
}
-
/**
* Generate a list of changes using an Enhanced system (uses javascript).
*/
@@ -795,7 +820,7 @@ class EnhancedChangesList extends ChangesList {
$lastLink = $this->message['last'];
} else {
$lastLink = Linker::linkKnown( $rc->getTitle(), $this->message['last'],
- array(), $curIdEq + array('diff' => $thisOldid, 'oldid' => $lastOldid) + $rcIdQuery );
+ array(), $curIdEq + array( 'diff' => $thisOldid, 'oldid' => $lastOldid ) + $rcIdQuery );
}
# Make user links
@@ -807,7 +832,7 @@ class EnhancedChangesList extends ChangesList {
}
$rc->lastlink = $lastLink;
- $rc->curlink = $curLink;
+ $rc->curlink = $curLink;
$rc->difflink = $diffLink;
# Put accumulated information into the cache, for later display
@@ -816,10 +841,10 @@ class EnhancedChangesList extends ChangesList {
$secureName = $title->getPrefixedDBkey();
if( $type == RC_MOVE || $type == RC_MOVE_OVER_REDIRECT ) {
# Use an @ character to prevent collision with page names
- $this->rc_cache['@@' . ($this->rcMoveIndex++)] = array($rc);
+ $this->rc_cache['@@' . ($this->rcMoveIndex++)] = array( $rc );
} else {
# Logs are grouped by type
- if( $type == RC_LOG ){
+ if( $type == RC_LOG ) {
$secureName = SpecialPage::getTitleFor( 'Log', $logType )->getPrefixedDBkey();
}
if( !isset( $this->rc_cache[$secureName] ) ) {
@@ -862,6 +887,8 @@ class EnhancedChangesList extends ChangesList {
# Other properties
$unpatrolled = false;
$isnew = false;
+ $allBots = true;
+ $allMinors = true;
$curId = $currentRevision = 0;
# Some catalyst variables...
$namehidden = true;
@@ -895,7 +922,13 @@ class EnhancedChangesList extends ChangesList {
$currentRevision = $rcObj->mAttribs['rc_this_oldid'];
}
- $bot = $rcObj->mAttribs['rc_bot'];
+ if( !$rcObj->mAttribs['rc_bot'] ) {
+ $allBots = false;
+ }
+ if( !$rcObj->mAttribs['rc_minor'] ) {
+ $allMinors = false;
+ }
+
$userlinks[$u]++;
}
@@ -917,19 +950,19 @@ class EnhancedChangesList extends ChangesList {
implode( $this->message['semicolon-separator'], $users )
)->escaped() . '</span>';
- $tl = '<span class="mw-collapsible-toggle mw-enhancedchanges-arrow"></span>';
+ $tl = '<span class="mw-collapsible-toggle mw-enhancedchanges-arrow mw-enhancedchanges-arrow-space"></span>';
$r .= "<td>$tl</td>";
# Main line
$r .= '<td class="mw-enhanced-rc">' . $this->recentChangesFlags( array(
- 'newpage' => $isnew,
- 'minor' => false,
- 'unpatrolled' => $unpatrolled,
- 'bot' => $bot ,
+ 'newpage' => $isnew, # show, when one have this flag
+ 'minor' => $allMinors, # show only, when all have this flag
+ 'unpatrolled' => $unpatrolled, # show, when one have this flag
+ 'bot' => $allBots, # show only, when all have this flag
) );
# Timestamp
- $r .= '&#160;'.$block[0]->timestamp.'&#160;</td><td>';
+ $r .= '&#160;' . $block[0]->timestamp . '&#160;</td><td>';
# Article link
if( $namehidden ) {
@@ -944,7 +977,7 @@ class EnhancedChangesList extends ChangesList {
$queryParams['curid'] = $curId;
# Changes message
- $n = count($block);
+ $n = count( $block );
static $nchanges = array();
if ( !isset( $nchanges[$n] ) ) {
$nchanges[$n] = $this->msg( 'nchanges' )->numParams( $n )->escaped();
@@ -999,7 +1032,7 @@ class EnhancedChangesList extends ChangesList {
# Character difference (does not apply if only log items)
if( $wgRCShowChangedSize && !$allLogs ) {
$last = 0;
- $first = count($block) - 1;
+ $first = count( $block ) - 1;
# Some events (like logs) have an "empty" size, so we need to skip those...
while( $last < $first && $block[$last]->mAttribs['rc_new_len'] === null ) {
$last++;
@@ -1018,7 +1051,7 @@ class EnhancedChangesList extends ChangesList {
}
$r .= $users;
- $r .= $this->numberofWatchingusers($block[0]->numberofWatchingusers);
+ $r .= $this->numberofWatchingusers( $block[0]->numberofWatchingusers );
# Sub-entries
foreach( $block as $rcObj ) {
@@ -1046,7 +1079,7 @@ class EnhancedChangesList extends ChangesList {
$link = $rcObj->timestamp;
# Revision link
} elseif( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ) {
- $link = '<span class="history-deleted">'.$rcObj->timestamp.'</span> ';
+ $link = '<span class="history-deleted">' . $rcObj->timestamp . '</span> ';
} else {
if ( $rcObj->unpatrolled && $type == RC_NEW) {
$params['rcid'] = $rcObj->mAttribs['rc_id'];
@@ -1058,8 +1091,9 @@ class EnhancedChangesList extends ChangesList {
array(),
$params
);
- if( $this->isDeleted($rcObj,Revision::DELETED_TEXT) )
- $link = '<span class="history-deleted">'.$link.'</span> ';
+ if( $this->isDeleted( $rcObj, Revision::DELETED_TEXT ) ) {
+ $link = '<span class="history-deleted">' . $link . '</span> ';
+ }
}
$r .= $link . '</span>';
@@ -1103,12 +1137,12 @@ class EnhancedChangesList extends ChangesList {
/**
* Generate HTML for an arrow or placeholder graphic
- * @param $dir String: one of '', 'd', 'l', 'r'
- * @param $alt String: text
- * @param $title String: text
+ * @param string $dir one of '', 'd', 'l', 'r'
+ * @param string $alt text
+ * @param string $title text
* @return String: HTML "<img>" tag
*/
- protected function arrow( $dir, $alt='', $title='' ) {
+ protected function arrow( $dir, $alt = '', $title = '' ) {
global $wgStylePath;
$encUrl = htmlspecialchars( $wgStylePath . '/common/images/Arr_' . $dir . '.png' );
$encAlt = htmlspecialchars( $alt );
@@ -1170,7 +1204,7 @@ class EnhancedChangesList extends ChangesList {
$r = Html::openElement( 'table', array( 'class' => $classes ) ) .
Html::openElement( 'tr' );
- $r .= '<td class="mw-enhanced-rc"><span class="mw-enhancedchanges-arrow mw-enhancedchanges-arrow-space"></span>';
+ $r .= '<td class="mw-enhanced-rc"><span class="mw-enhancedchanges-arrow-space"></span>';
# Flag and Timestamp
if( $type == RC_MOVE || $type == RC_MOVE_OVER_REDIRECT ) {
$r .= '&#160;&#160;&#160;&#160;'; // 4 flags -> 4 spaces
@@ -1182,7 +1216,7 @@ class EnhancedChangesList extends ChangesList {
'bot' => $rcObj->mAttribs['rc_bot'],
) );
}
- $r .= '&#160;'.$rcObj->timestamp.'&#160;</td><td>';
+ $r .= '&#160;' . $rcObj->timestamp . '&#160;</td><td>';
# Article or log link
if( $logType ) {
$logPage = new LogPage( $logType );
@@ -1214,7 +1248,7 @@ class EnhancedChangesList extends ChangesList {
if ( $type == RC_LOG ) {
$r .= $this->insertLogEntry( $rcObj );
} else {
- $r .= ' '.$rcObj->userlink . $rcObj->usertalklink;
+ $r .= ' ' . $rcObj->userlink . $rcObj->usertalklink;
$r .= $this->insertComment( $rcObj );
$this->insertRollback( $r, $rcObj );
}
@@ -1222,7 +1256,7 @@ class EnhancedChangesList extends ChangesList {
# Tags
$this->insertTags( $r, $rcObj, $classes );
# Show how many people are watching this if enabled
- $r .= $this->numberofWatchingusers($rcObj->numberofWatchingusers);
+ $r .= $this->numberofWatchingusers( $rcObj->numberofWatchingusers );
$r .= "</td></tr></table>\n";
@@ -1255,7 +1289,7 @@ class EnhancedChangesList extends ChangesList {
wfProfileOut( __METHOD__ );
- return '<div>'.$blockOut.'</div>';
+ return '<div>' . $blockOut . '</div>';
}
/**
diff --git a/includes/Collation.php b/includes/Collation.php
index ad2b94b1..6bba019b 100644
--- a/includes/Collation.php
+++ b/includes/Collation.php
@@ -48,8 +48,12 @@ abstract class Collation {
case 'uca-default':
return new IcuCollation( 'root' );
default:
- # Provide a mechanism for extensions to hook in.
+ $match = array();
+ if ( preg_match( '/^uca-([a-z-]+)$/', $collationName, $match ) ) {
+ return new IcuCollation( $match[1] );
+ }
+ # Provide a mechanism for extensions to hook in.
$collationObject = null;
wfRunHooks( 'Collation::factory', array( $collationName, &$collationObject ) );
@@ -144,18 +148,19 @@ class IdentityCollation extends Collation {
}
}
-
class IcuCollation extends Collation {
+ const FIRST_LETTER_VERSION = 1;
+
var $primaryCollator, $mainCollator, $locale;
var $firstLetterData;
/**
* Unified CJK blocks.
*
- * The same definition of a CJK block must be used for both Collation and
- * generateCollationData.php. These blocks are omitted from the first
- * letter data, as an optimisation measure and because the default UCA table
- * is pretty useless for sorting Chinese text anyway. Japanese and Korean
+ * The same definition of a CJK block must be used for both Collation and
+ * generateCollationData.php. These blocks are omitted from the first
+ * letter data, as an optimisation measure and because the default UCA table
+ * is pretty useless for sorting Chinese text anyway. Japanese and Korean
* blocks are not included here, because they are smaller and more useful.
*/
static $cjkBlocks = array(
@@ -176,11 +181,105 @@ class IcuCollation extends Collation {
array( 0x2F800, 0x2FA1F ), // CJK Compatibility Ideographs Supplement
);
+ /**
+ * Additional characters (or character groups) to be considered separate
+ * letters for given languages, or to be removed from the list of such
+ * letters (denoted by keys starting with '-').
+ *
+ * These are additions to (or subtractions from) the data stored in the
+ * first-letters-root.ser file (which among others includes full basic latin,
+ * cyrillic and greek alphabets).
+ *
+ * "Separate letter" is a letter that would have a separate heading/section
+ * for it in a dictionary or a phone book in this language. This data isn't
+ * used for sorting (the ICU library handles that), only for deciding which
+ * characters (or character groups) to use as headings.
+ *
+ * Initially generated based on the primary level of Unicode collation
+ * tailorings available at http://developer.mimer.com/charts/tailorings.htm ,
+ * later modified.
+ *
+ * Empty arrays are intended; this signifies that the data for the language is
+ * available and that there are, in fact, no additional letters to consider.
+ */
+ static $tailoringFirstLetters = array(
+ // Verified by native speakers
+ 'be' => array( "Ё" ),
+ 'be-tarask' => array( "Ё" ),
+ 'en' => array(),
+ 'fi' => array( "Å", "Ä", "Ö" ),
+ 'hu' => array( "Cs", "Dz", "Dzs", "Gy", "Ly", "Ny", "Ö", "Sz", "Ty", "Ü", "Zs" ),
+ 'it' => array(),
+ 'pl' => array( "Ą", "Ć", "Ę", "Ł", "Ń", "Ó", "Ś", "Ź", "Ż" ),
+ 'pt' => array(),
+ 'ru' => array(),
+ 'sv' => array( "Å", "Ä", "Ö" ),
+ 'uk' => array( "Ґ", "Ь" ),
+ 'vi' => array( "Ă", "Â", "Đ", "Ê", "Ô", "Ơ", "Ư" ),
+ // Not verified, but likely correct
+ 'af' => array(),
+ 'ast' => array( "Ch", "Ll", "Ñ" ),
+ 'az' => array( "Ç", "Ə", "Ğ", "İ", "Ö", "Ş", "Ü" ),
+ 'bg' => array(),
+ 'br' => array( "Ch", "C'h" ),
+ 'bs' => array( "Č", "Ć", "Dž", "Đ", "Lj", "Nj", "Š", "Ž" ),
+ 'ca' => array(),
+ 'co' => array(),
+ 'cs' => array( "Č", "Ch", "Ř", "Š", "Ž" ),
+ 'cy' => array( "Ch", "Dd", "Ff", "Ng", "Ll", "Ph", "Rh", "Th" ),
+ 'da' => array( "Æ", "Ø", "Å" ),
+ 'de' => array(),
+ 'dsb' => array( "Č", "Ć", "Dź", "Ě", "Ch", "Ł", "Ń", "Ŕ", "Š", "Ś", "Ž", "Ź" ),
+ 'el' => array(),
+ 'eo' => array( "Ĉ", "Ĝ", "Ĥ", "Ĵ", "Ŝ", "Ŭ" ),
+ 'es' => array( "Ñ" ),
+ 'et' => array( "Š", "Ž", "Õ", "Ä", "Ö", "Ü" ),
+ 'eu' => array( "Ñ" ),
+ 'fo' => array( "Á", "Ð", "Í", "Ó", "Ú", "Ý", "Æ", "Ø", "Å" ),
+ 'fr' => array(),
+ 'fur' => array( "À", "Á", "Â", "È", "Ì", "Ò", "Ù" ),
+ 'fy' => array(),
+ 'ga' => array(),
+ 'gd' => array(),
+ 'gl' => array( "Ch", "Ll", "Ñ" ),
+ 'hr' => array( "Č", "Ć", "Dž", "Đ", "Lj", "Nj", "Š", "Ž" ),
+ 'hsb' => array( "Č", "Dź", "Ě", "Ch", "Ł", "Ń", "Ř", "Š", "Ć", "Ž" ),
+ 'is' => array( "Á", "Ð", "É", "Í", "Ó", "Ú", "Ý", "Þ", "Æ", "Ö", "Å" ),
+ 'kk' => array( "Ү", "І" ),
+ 'kl' => array( "Æ", "Ø", "Å" ),
+ 'ku' => array( "Ç", "Ê", "Î", "Ş", "Û" ),
+ 'ky' => array( "Ё" ),
+ 'la' => array(),
+ 'lb' => array(),
+ 'lt' => array( "Č", "Š", "Ž" ),
+ 'lv' => array( "Č", "Ģ", "Ķ", "Ļ", "Ņ", "Š", "Ž" ),
+ 'mk' => array(),
+ 'mo' => array( "Ă", "Â", "Î", "Ş", "Ţ" ),
+ 'mt' => array( "Ċ", "Ġ", "Għ", "Ħ", "Ż" ),
+ 'nl' => array(),
+ 'no' => array( "Æ", "Ø", "Å" ),
+ 'oc' => array(),
+ 'rm' => array(),
+ 'ro' => array( "Ă", "Â", "Î", "Ş", "Ţ" ),
+ 'rup' => array( "Ă", "Â", "Î", "Ľ", "Ń", "Ş", "Ţ" ),
+ 'sco' => array(),
+ 'sk' => array( "Ä", "Č", "Ch", "Ô", "Š", "Ž" ),
+ 'sl' => array( "Č", "Š", "Ž" ),
+ 'smn' => array( "Á", "Č", "Đ", "Ŋ", "Š", "Ŧ", "Ž", "Æ", "Ø", "Å", "Ä", "Ö" ),
+ 'sq' => array( "Ç", "Dh", "Ë", "Gj", "Ll", "Nj", "Rr", "Sh", "Th", "Xh", "Zh" ),
+ 'sr' => array(),
+ 'tk' => array( "Ç", "Ä", "Ž", "Ň", "Ö", "Ş", "Ü", "Ý" ),
+ 'tl' => array( "Ñ", "Ng" ),
+ 'tr' => array( "Ç", "Ğ", "İ", "Ö", "Ş", "Ü" ),
+ 'tt' => array( "Ә", "Ө", "Ү", "Җ", "Ң", "Һ" ),
+ 'uz' => array( "Ch", "G'", "Ng", "O'", "Sh" ),
+ );
+
const RECORD_LENGTH = 14;
function __construct( $locale ) {
if ( !extension_loaded( 'intl' ) ) {
- throw new MWException( 'An ICU collation was requested, ' .
+ throw new MWException( 'An ICU collation was requested, ' .
'but the intl extension is not available.' );
}
$this->locale = $locale;
@@ -218,8 +317,8 @@ class IcuCollation extends Collation {
// Check for CJK
$firstChar = mb_substr( $string, 0, 1, 'UTF-8' );
- if ( ord( $firstChar ) > 0x7f
- && self::isCjk( utf8ToCodepoint( $firstChar ) ) )
+ if ( ord( $firstChar ) > 0x7f
+ && self::isCjk( utf8ToCodepoint( $firstChar ) ) )
{
return $firstChar;
}
@@ -249,25 +348,37 @@ class IcuCollation extends Collation {
$cacheKey = wfMemcKey( 'first-letters', $this->locale );
$cacheEntry = $cache->get( $cacheKey );
- if ( $cacheEntry ) {
+ if ( $cacheEntry && isset( $cacheEntry['version'] )
+ && $cacheEntry['version'] == self::FIRST_LETTER_VERSION )
+ {
$this->firstLetterData = $cacheEntry;
return $this->firstLetterData;
}
// Generate data from serialized data file
- $letters = wfGetPrecompiledData( "first-letters-{$this->locale}.ser" );
- if ( $letters === false ) {
- throw new MWException( "MediaWiki does not support ICU locale " .
- "\"{$this->locale}\"" );
+ if ( isset ( self::$tailoringFirstLetters[$this->locale] ) ) {
+ $letters = wfGetPrecompiledData( "first-letters-root.ser" );
+ // Append additional characters
+ $letters = array_merge( $letters, self::$tailoringFirstLetters[$this->locale] );
+ // Remove unnecessary ones, if any
+ if ( isset( self::$tailoringFirstLetters[ '-' . $this->locale ] ) ) {
+ $letters = array_diff( $letters, self::$tailoringFirstLetters[ '-' . $this->locale ] );
+ }
+ } else {
+ $letters = wfGetPrecompiledData( "first-letters-{$this->locale}.ser" );
+ if ( $letters === false ) {
+ throw new MWException( "MediaWiki does not support ICU locale " .
+ "\"{$this->locale}\"" );
+ }
}
// Sort the letters.
//
// It's impossible to have the precompiled data file properly sorted,
- // because the sort order changes depending on ICU version. If the
- // array is not properly sorted, the binary search will return random
- // results.
+ // because the sort order changes depending on ICU version. If the
+ // array is not properly sorted, the binary search will return random
+ // results.
//
// We also take this opportunity to remove primary collisions.
$letterMap = array();
@@ -284,9 +395,76 @@ class IcuCollation extends Collation {
}
}
ksort( $letterMap, SORT_STRING );
+ // Remove duplicate prefixes. Basically if something has a sortkey
+ // which is a prefix of some other sortkey, then it is an
+ // expansion and probably should not be considered a section
+ // header.
+ //
+ // For example 'þ' is sometimes sorted as if it is the letters
+ // 'th'. Other times it is its own primary element. Another
+ // example is '₨'. Sometimes its a currency symbol. Sometimes it
+ // is an 'R' followed by an 's'.
+ //
+ // Additionally an expanded element should always sort directly
+ // after its first element due to they way sortkeys work.
+ //
+ // UCA sortkey elements are of variable length but no collation
+ // element should be a prefix of some other element, so I think
+ // this is safe. See:
+ // * https://ssl.icu-project.org/repos/icu/icuhtml/trunk/design/collation/ICU_collation_design.htm
+ // * http://site.icu-project.org/design/collation/uca-weight-allocation
+ //
+ // Additionally, there is something called primary compression to
+ // worry about. Basically, if you have two primary elements that
+ // are more than one byte and both start with the same byte then
+ // the first byte is dropped on the second primary. Additionally
+ // either \x03 or \xFF may be added to mean that the next primary
+ // does not start with the first byte of the first primary.
+ //
+ // This shouldn't matter much, as the first primary is not
+ // changed, and that is what we are comparing against.
+ //
+ // tl;dr: This makes some assumptions about how icu implements
+ // collations. It seems incredibly unlikely these assumptions
+ // will change, but nonetheless they are assumptions.
+
+ $prev = false;
+ $duplicatePrefixes = array();
+ foreach( $letterMap as $key => $value ) {
+ // Remove terminator byte. Otherwise the prefix
+ // comparison will get hung up on that.
+ $trimmedKey = rtrim( $key, "\0" );
+ if ( $prev === false || $prev === '' ) {
+ $prev = $trimmedKey;
+ // We don't yet have a collation element
+ // to compare against, so continue.
+ continue;
+ }
+
+ // Due to the fact the array is sorted, we only have
+ // to compare with the element directly previous
+ // to the current element (skipping expansions).
+ // An element "X" will always sort directly
+ // before "XZ" (Unless we have "XY", but we
+ // do not update $prev in that case).
+ if ( substr( $trimmedKey, 0, strlen( $prev ) ) === $prev ) {
+ $duplicatePrefixes[] = $key;
+ // If this is an expansion, we don't want to
+ // compare the next element to this element,
+ // but to what is currently $prev
+ continue;
+ }
+ $prev = $trimmedKey;
+ }
+ foreach( $duplicatePrefixes as $badKey ) {
+ wfDebug( "Removing '{$letterMap[$badKey]}' from first letters." );
+ unset( $letterMap[$badKey] );
+ // This code assumes that unsetting does not change sort order.
+ }
$data = array(
'chars' => array_values( $letterMap ),
- 'keys' => array_keys( $letterMap )
+ 'keys' => array_keys( $letterMap ),
+ 'version' => self::FIRST_LETTER_VERSION,
);
// Reduce memory usage before caching
@@ -320,23 +498,27 @@ class IcuCollation extends Collation {
}
/**
- * Do a binary search, and return the index of the largest item that sorts
+ * Do a binary search, and return the index of the largest item that sorts
* less than or equal to the target value.
*
- * @param $valueCallback array A function to call to get the value with
+ * @param array $valueCallback A function to call to get the value with
* a given array index.
- * @param $valueCount int The number of items accessible via $valueCallback,
+ * @param int $valueCount The number of items accessible via $valueCallback,
* indexed from 0 to $valueCount - 1
- * @param $comparisonCallback array A callback to compare two values, returning
+ * @param array $comparisonCallback A callback to compare two values, returning
* -1, 0 or 1 in the style of strcmp().
- * @param $target string The target value to find.
+ * @param string $target The target value to find.
*
* @return int|bool The item index of the lower bound, or false if the target value
* sorts before all items.
*/
function findLowerBound( $valueCallback, $valueCount, $comparisonCallback, $target ) {
+ if ( $valueCount === 0 ) {
+ return false;
+ }
+
$min = 0;
- $max = $valueCount - 1;
+ $max = $valueCount;
do {
$mid = $min + ( ( $max - $min ) >> 1 );
$item = call_user_func( $valueCallback, $mid );
@@ -351,12 +533,15 @@ class IcuCollation extends Collation {
}
} while ( $min < $max - 1 );
- if ( $min == 0 && $max == 0 && $comparison > 0 ) {
- // Before the first item
- return false;
- } else {
- return $min;
+ if ( $min == 0 ) {
+ $item = call_user_func( $valueCallback, $min );
+ $comparison = call_user_func( $comparisonCallback, $target, $item );
+ if ( $comparison < 0 ) {
+ // Before the first item
+ return false;
+ }
}
+ return $min;
}
static function isCjk( $codepoint ) {
@@ -367,5 +552,55 @@ class IcuCollation extends Collation {
}
return false;
}
-}
+ /**
+ * Return the version of ICU library used by PHP's intl extension,
+ * or false when the extension is not installed of the version
+ * can't be determined.
+ *
+ * The constant INTL_ICU_VERSION this function refers to isn't really
+ * documented. It is available since PHP 5.3.7 (see PHP bug 54561).
+ * This function will return false on older PHPs.
+ *
+ * @since 1.21
+ * @return string|false
+ */
+ static function getICUVersion() {
+ return defined( 'INTL_ICU_VERSION' ) ? INTL_ICU_VERSION : false;
+ }
+
+ /**
+ * Return the version of Unicode appropriate for the version of ICU library
+ * currently in use, or false when it can't be determined.
+ *
+ * @since 1.21
+ * @return string|false
+ */
+ static function getUnicodeVersionForICU() {
+ $icuVersion = IcuCollation::getICUVersion();
+ if ( !$icuVersion ) {
+ return false;
+ }
+
+ $versionPrefix = substr( $icuVersion, 0, 3 );
+ // Source: http://site.icu-project.org/download
+ $map = array(
+ '50.' => '6.2',
+ '49.' => '6.1',
+ '4.8' => '6.0',
+ '4.6' => '6.0',
+ '4.4' => '5.2',
+ '4.2' => '5.1',
+ '4.0' => '5.1',
+ '3.8' => '5.0',
+ '3.6' => '5.0',
+ '3.4' => '4.1',
+ );
+
+ if ( isset( $map[$versionPrefix] ) ) {
+ return $map[$versionPrefix];
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/includes/ConfEditor.php b/includes/ConfEditor.php
index b68fc762..1d9ca921 100644
--- a/includes/ConfEditor.php
+++ b/includes/ConfEditor.php
@@ -66,7 +66,6 @@ class ConfEditor {
*/
var $stateStack;
-
/**
* The path stack is a stack of associative arrays with the following elements:
* name The name of top level of the path
@@ -128,7 +127,7 @@ class ConfEditor {
/**
* Edit the text. Returns the edited text.
- * @param $ops Array of operations.
+ * @param array $ops of operations.
*
* Operations are given as an associative array, with members:
* type: One of delete, set, append or insert (required)
@@ -159,6 +158,7 @@ class ConfEditor {
* insert
* Insert a new element at the start of the array.
*
+ * @throws MWException
* @return string
*/
public function edit( $ops ) {
@@ -306,7 +306,7 @@ class ConfEditor {
* setVar( $arr, 'foo/bar', 'baz', 3 ); will set
* $arr['foo']['bar']['baz'] = 3;
* @param $array array
- * @param $path string slash-delimited path
+ * @param string $path slash-delimited path
* @param $key mixed Key
* @param $value mixed Value
*/
@@ -392,6 +392,8 @@ class ConfEditor {
* Finds the source byte region which you would want to delete, if $pathName
* was to be deleted. Includes the leading spaces and tabs, the trailing line
* break, and any comments in between.
+ * @param $pathName
+ * @throws MWException
* @return array
*/
function findDeletionRegion( $pathName ) {
@@ -450,6 +452,8 @@ class ConfEditor {
* or semicolon.
*
* The end position is the past-the-end (end + 1) value as per convention.
+ * @param $pathName
+ * @throws MWException
* @return array
*/
function findValueRegion( $pathName ) {
@@ -1055,6 +1059,7 @@ class ConfEditorParseError extends MWException {
return "$line\n" .str_repeat( ' ', $this->colNum - 1 ) . "^\n";
}
}
+ return '';
}
}
@@ -1089,4 +1094,3 @@ class ConfEditorToken {
return $this->type == 'END';
}
}
-
diff --git a/includes/Cookie.php b/includes/Cookie.php
index 7984d63e..27a85072 100644
--- a/includes/Cookie.php
+++ b/includes/Cookie.php
@@ -40,14 +40,15 @@ class Cookie {
/**
* Sets a cookie. Used before a request to set up any individual
- * cookies. Used internally after a request to parse the
+ * cookies. Used internally after a request to parse the
* Set-Cookie headers.
*
- * @param $value String: the value of the cookie
- * @param $attr Array: possible key/values:
- * expires A date string
- * path The path this cookie is used on
- * domain Domain this cookie is used on
+ * @param string $value the value of the cookie
+ * @param array $attr possible key/values:
+ * expires A date string
+ * path The path this cookie is used on
+ * domain Domain this cookie is used on
+ * @throws MWException
*/
public function set( $value, $attr ) {
$this->value = $value;
@@ -83,8 +84,8 @@ class Cookie {
* @todo fixme fails to detect 3-letter top-level domains
* @todo fixme fails to detect 2-letter top-level domains for single-domain use (probably not a big problem in practice, but there are test cases)
*
- * @param $domain String: the domain to validate
- * @param $originDomain String: (optional) the domain the cookie originates from
+ * @param string $domain the domain to validate
+ * @param string $originDomain (optional) the domain the cookie originates from
* @return Boolean
*/
public static function validateCookieDomain( $domain, $originDomain = null ) {
@@ -112,7 +113,7 @@ class Cookie {
}
// Don't allow cookies for "co.uk" or "gov.uk", etc, but allow "supermarket.uk"
- if ( strrpos( $domain, "." ) - strlen( $domain ) == -3 ) {
+ if ( strrpos( $domain, "." ) - strlen( $domain ) == -3 ) {
if ( ( count( $dc ) == 2 && strlen( $dc[0] ) <= 2 )
|| ( count( $dc ) == 3 && strlen( $dc[0] ) == "" && strlen( $dc[1] ) <= 2 ) ) {
return false;
@@ -141,8 +142,8 @@ class Cookie {
/**
* Serialize the cookie jar into a format useful for HTTP Request headers.
*
- * @param $path String: the path that will be used. Required.
- * @param $domain String: the domain that will be used. Required.
+ * @param string $path the path that will be used. Required.
+ * @param string $domain the domain that will be used. Required.
* @return String
*/
public function serializeToHttpRequest( $path, $domain ) {
@@ -164,8 +165,8 @@ class Cookie {
protected function canServeDomain( $domain ) {
if ( $domain == $this->domain
|| ( strlen( $domain ) > strlen( $this->domain )
- && substr( $this->domain, 0, 1 ) == '.'
- && substr_compare( $domain, $this->domain, -strlen( $this->domain ),
+ && substr( $this->domain, 0, 1 ) == '.'
+ && substr_compare( $domain, $this->domain, -strlen( $this->domain ),
strlen( $this->domain ), true ) == 0 ) ) {
return true;
}
@@ -193,7 +194,7 @@ class CookieJar {
private $cookie = array();
/**
- * Set a cookie in the cookie jar. Make sure only one cookie per-name exists.
+ * Set a cookie in the cookie jar. Make sure only one cookie per-name exists.
* @see Cookie::set()
*/
public function setCookie ( $name, $value, $attr ) {
@@ -231,7 +232,7 @@ class CookieJar {
* Parse the content of an Set-Cookie HTTP Response header.
*
* @param $cookie String
- * @param $domain String: cookie's domain
+ * @param string $domain cookie's domain
* @return null
*/
public function parseCookieResponseHeader ( $cookie, $domain ) {
diff --git a/includes/CryptRand.php b/includes/CryptRand.php
index 858eebf2..d0305d8b 100644
--- a/includes/CryptRand.php
+++ b/includes/CryptRand.php
@@ -79,7 +79,7 @@ class MWCryptRand {
// Include some information about the filesystem's current state in the random state
$files = array();
- // We know this file is here so grab some info about ourself
+ // We know this file is here so grab some info about ourselves
$files[] = __FILE__;
// We must also have a parent folder, and with the usual file structure, a grandparent
@@ -106,7 +106,11 @@ class MWCryptRand {
}
}
// The absolute filename itself will differ from install to install so don't leave it out
- $state .= realpath( $file );
+ if( ( $path = realpath( $file ) ) !== false ) {
+ $state .= $path;
+ } else {
+ $state .= $file;
+ }
$state .= implode( '', $stat );
} else {
// The fact that the file isn't there is worth at least a
@@ -144,7 +148,7 @@ class MWCryptRand {
/**
* Randomly hash data while mixing in clock drift data for randomness
*
- * @param $data string The data to randomly hash.
+ * @param string $data The data to randomly hash.
* @return String The hashed bytes
* @author Tim Starling
*/
@@ -158,7 +162,7 @@ class MWCryptRand {
$buffer = str_repeat( ' ', $bufLength );
$bufPos = 0;
- // Iterate for $duration seconds or at least $minIerations number of iterations
+ // Iterate for $duration seconds or at least $minIterations number of iterations
$iterations = 0;
$startTime = microtime( true );
$currentTime = $startTime;
@@ -391,7 +395,7 @@ class MWCryptRand {
// We hash the random state with more salt to avoid the state from leaking
// out and being used to predict the /randomness/ that follows.
if ( strlen( $buffer ) < $bytes ) {
- wfDebug( __METHOD__ . ": Falling back to using a pseudo random state to generate randomness.\n" );
+ wfDebug( __METHOD__ . ": Falling back to using a pseudo random state to generate randomness.\n" );
}
while ( strlen( $buffer ) < $bytes ) {
wfProfileIn( __METHOD__ . '-fallback' );
@@ -464,8 +468,8 @@ class MWCryptRand {
* You can use MWCryptRand::wasStrong() if you wish to know if the source used
* was cryptographically strong.
*
- * @param $bytes int the number of bytes of random data to generate
- * @param $forceStrong bool Pass true if you want generate to prefer cryptographically
+ * @param int $bytes the number of bytes of random data to generate
+ * @param bool $forceStrong Pass true if you want generate to prefer cryptographically
* strong sources of entropy even if reading from them may steal
* more entropy from the system than optimal.
* @return String Raw binary random data
@@ -480,8 +484,8 @@ class MWCryptRand {
* You can use MWCryptRand::wasStrong() if you wish to know if the source used
* was cryptographically strong.
*
- * @param $chars int the number of hex chars of random data to generate
- * @param $forceStrong bool Pass true if you want generate to prefer cryptographically
+ * @param int $chars the number of hex chars of random data to generate
+ * @param bool $forceStrong Pass true if you want generate to prefer cryptographically
* strong sources of entropy even if reading from them may steal
* more entropy from the system than optimal.
* @return String Hexadecimal random data
diff --git a/includes/DataUpdate.php b/includes/DataUpdate.php
index 377b64c0..c1076b23 100644
--- a/includes/DataUpdate.php
+++ b/includes/DataUpdate.php
@@ -34,7 +34,7 @@ abstract class DataUpdate implements DeferrableUpdate {
/**
* Constructor
*/
- public function __construct( ) {
+ public function __construct() {
# noop
}
@@ -67,15 +67,15 @@ abstract class DataUpdate implements DeferrableUpdate {
*
* This methods supports transactions logic by first calling beginTransaction()
* on all updates in the array, then calling doUpdate() on each, and, if all goes well,
- * then calling commitTransaction() on each update. If an error occurrs,
- * rollbackTransaction() will be called on any update object that had beginTranscation()
+ * then calling commitTransaction() on each update. If an error occurs,
+ * rollbackTransaction() will be called on any update object that had beginTransaction()
* called but not yet commitTransaction().
*
* This allows for limited transactional logic across multiple backends for storing
* secondary data.
*
- * @static
- * @param $updates array a list of DataUpdate instances
+ * @param array $updates a list of DataUpdate instances
+ * @throws Exception|null
*/
public static function runUpdates( $updates ) {
if ( empty( $updates ) ) return; # nothing to do
diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php
index 710605ad..9d024c86 100644
--- a/includes/DefaultSettings.php
+++ b/includes/DefaultSettings.php
@@ -47,7 +47,7 @@
* This is not a valid entry point, perform no further processing unless
* MEDIAWIKI is defined
*/
-if( !defined( 'MEDIAWIKI' ) ) {
+if ( !defined( 'MEDIAWIKI' ) ) {
echo "This file is part of MediaWiki and is not a valid entry point\n";
die( 1 );
}
@@ -55,13 +55,19 @@ if( !defined( 'MEDIAWIKI' ) ) {
/**
* wgConf hold the site configuration.
* Not used for much in a default install.
+ * @since 1.5
*/
$wgConf = new SiteConfiguration;
-/** MediaWiki version number */
-$wgVersion = '1.20.6';
+/**
+ * MediaWiki version number
+ * @since 1.2
+ */
+$wgVersion = '1.21.1';
-/** Name of the site. It must be changed in LocalSettings.php */
+/**
+ * Name of the site. It must be changed in LocalSettings.php
+ */
$wgSitename = 'MediaWiki';
/**
@@ -87,6 +93,7 @@ $wgServer = WebRequest::detectServer();
* Must be fully qualified, even if $wgServer is protocol-relative.
*
* Defaults to $wgServer, expanded to a fully qualified http:// URL if needed.
+ * @since 1.18
*/
$wgCanonicalServer = false;
@@ -104,7 +111,7 @@ $wgCanonicalServer = false;
* Other paths will be set to defaults based on it unless they are directly
* set in LocalSettings.php
*/
-$wgScriptPath = '/wiki';
+$wgScriptPath = '/wiki';
/**
* Whether to support URLs like index.php/Page_title These often break when PHP
@@ -121,11 +128,11 @@ $wgScriptPath = '/wiki';
* The default $wgArticlePath will be set based on this value at runtime, but if
* you have customized it, having this incorrectly set to true can cause
* redirect loops when "pretty URLs" are used.
+ * @since 1.2.1
*/
-$wgUsePathInfo =
- ( strpos( php_sapi_name(), 'cgi' ) === false ) &&
- ( strpos( php_sapi_name(), 'apache2filter' ) === false ) &&
- ( strpos( php_sapi_name(), 'isapi' ) === false );
+$wgUsePathInfo = ( strpos( PHP_SAPI, 'cgi' ) === false ) &&
+ ( strpos( PHP_SAPI, 'apache2filter' ) === false ) &&
+ ( strpos( PHP_SAPI, 'isapi' ) === false );
/**
* The extension to append to script names by default. This can either be .php
@@ -133,9 +140,9 @@ $wgUsePathInfo =
*
* Some hosting providers use PHP 4 for *.php files, and PHP 5 for *.php5. This
* variable is provided to support those providers.
+ * @since 1.11
*/
-$wgScriptExtension = '.php';
-
+$wgScriptExtension = '.php';
/**@}*/
@@ -176,12 +183,14 @@ $wgRedirectScript = false;
* The URL path to load.php.
*
* Defaults to "{$wgScriptPath}/load{$wgScriptExtension}".
+ * @since 1.17
*/
$wgLoadScript = false;
/**
* The URL path of the skins directory.
* Defaults to "{$wgScriptPath}/skins".
+ * @since 1.3
*/
$wgStylePath = false;
$wgStyleSheetPath = &$wgStylePath;
@@ -189,6 +198,7 @@ $wgStyleSheetPath = &$wgStylePath;
/**
* The URL path of the skins directory. Should not point to an external domain.
* Defaults to "{$wgScriptPath}/skins".
+ * @since 1.17
*/
$wgLocalStylePath = false;
@@ -202,6 +212,7 @@ $wgExtensionAssetsPath = false;
/**
* Filesystem stylesheets directory.
* Defaults to "{$IP}/skins".
+ * @since 1.3
*/
$wgStyleDirectory = false;
@@ -239,12 +250,14 @@ $wgLogo = false;
/**
* The URL path of the shortcut icon.
+ * @since 1.6
*/
$wgFavicon = '/favicon.ico';
/**
* The URL path of the icon for iPhone and iPod Touch web app bookmarks.
* Defaults to no icon.
+ * @since 1.12
*/
$wgAppleTouchIcon = false;
@@ -268,6 +281,7 @@ $wgTmpDirectory = false;
/**
* If set, this URL is added to the start of $wgUploadPath to form a complete
* upload URL.
+ * @since 1.4
*/
$wgUploadBaseUrl = '';
@@ -276,6 +290,7 @@ $wgUploadBaseUrl = '';
* Full thumbnail URL will be like $wgUploadStashScalerBaseUrl/e/e6/Foo.jpg/123px-Foo.jpg
* where 'e6' are the first two characters of the MD5 hash of the file name.
* If $wgUploadStashScalerBaseUrl is set to false, thumbs are rendered locally as needed.
+ * @since 1.17
*/
$wgUploadStashScalerBaseUrl = false;
@@ -291,6 +306,7 @@ $wgUploadStashScalerBaseUrl = false;
*
* There must be an appropriate script or rewrite rule in place to handle these
* URLs.
+ * @since 1.5
*/
$wgActionPaths = array();
@@ -313,6 +329,20 @@ $wgUploadStashMaxAge = 6 * 3600; // 6 hours
$wgAllowImageMoving = true;
/**
+ * Enable deferred upload tasks that use the job queue.
+ * Only enable this if job runners are set up for both the
+ * 'AssembleUploadChunks' and 'PublishStashedFile' job types.
+ */
+$wgEnableAsyncUploads = false;
+
+/**
+ * Allow chunked uploads. This should only really be needed if you
+ * use the UploadWizard extension or allow huge file uploads.
+ * https://www.mediawiki.org/wiki/API:Upload#Chunked_uploading
+ */
+$wgAllowChunkedUploads = false;
+
+/**
* These are additional characters that should be replaced with '-' in filenames
*/
$wgIllegalFileChars = ":";
@@ -353,7 +383,7 @@ $wgImgAuthPublicTest = true;
* FSRepo is also supported for backwards compatibility.
*
* - name A unique name for the repository (but $wgLocalFileRepo should be 'local').
- * The name should consist of alpha-numberic characters.
+ * The name should consist of alpha-numeric characters.
* - backend A file backend name (see $wgFileBackends).
*
* For most core repos:
@@ -361,7 +391,9 @@ $wgImgAuthPublicTest = true;
* container : backend container name the zone is in
* directory : root path within container for the zone
* url : base URL to the root of the zone
- * handlerUrl : base script handled URL to the root of the zone
+ * urlsByExt : map of file extension types to base URLs
+ * (useful for using a different cache for videos)
+ * handlerUrl : base script-handled URL to the root of the zone
* (see FileRepo::getZoneHandlerUrl() function)
* Zones default to using "<repo name>-<zone name>" as the container name
* and default to using the container root as the zone's root directory.
@@ -405,7 +437,7 @@ $wgImgAuthPublicTest = true;
*
* ForeignDBRepo:
* - dbType, dbServer, dbUser, dbPassword, dbName, dbFlags
- * equivalent to the corresponding member of $wgDBservers
+ * equivalent to the corresponding member of $wgDBservers
* - tablePrefix Table prefix, the foreign wiki's $wgDBprefix
* - hasSharedCache True if the wiki's shared cache is accessible via the local $wgMemc
*
@@ -416,7 +448,7 @@ $wgImgAuthPublicTest = true;
* If you leave $wgLocalFileRepo set to false, Setup will fill in appropriate values.
* Otherwise, set $wgLocalFileRepo to a repository structure as described above.
* If you set $wgUseInstantCommons to true, it will add an entry for Commons.
- * If you set $wgForeignFileRepos to an array of repostory structures, those will
+ * If you set $wgForeignFileRepos to an array of repository structures, those will
* be searched after the local file repo.
* Otherwise, you will only have access to local media files.
*
@@ -436,14 +468,34 @@ $wgUseInstantCommons = false;
/**
* File backend structure configuration.
+ *
* This is an array of file backend configuration arrays.
* Each backend configuration has the following parameters:
- * - 'name' : A unique name for the backend
- * - 'class' : The file backend class to use
- * - 'wikiId' : A unique string that identifies the wiki (container prefix)
- * - 'lockManager' : The name of a lock manager (see $wgLockManagers)
- *
- * Additional parameters are specific to the class used.
+ * - 'name' : A unique name for the backend
+ * - 'class' : The file backend class to use
+ * - 'wikiId' : A unique string that identifies the wiki (container prefix)
+ * - 'lockManager' : The name of a lock manager (see $wgLockManagers)
+ *
+ * See FileBackend::__construct() for more details.
+ * Additional parameters are specific to the file backend class used.
+ * These settings should be global to all wikis when possible.
+ *
+ * There are two particularly important aspects about each backend:
+ * - a) Whether it is fully qualified or wiki-relative.
+ * By default, the paths of files are relative to the current wiki,
+ * which works via prefixing them with the current wiki ID when accessed.
+ * Setting 'wikiId' forces the backend to be fully qualified by prefixing
+ * all paths with the specified value instead. This can be useful if
+ * multiple wikis need to share the same data. Note that 'name' is *not*
+ * part of any prefix and thus should not be relied upon for namespacing.
+ * - b) Whether it is only defined for some wikis or is defined on all
+ * wikis in the wiki farm. Defining a backend globally is useful
+ * if multiple wikis need to share the same data.
+ * One should be aware of these aspects when configuring a backend for use with
+ * any basic feature or plugin. For example, suppose an extension stores data for
+ * different wikis in different directories and sometimes needs to access data from
+ * a foreign wiki's directory in order to render a page on given wiki. The extension
+ * would need a fully qualified backend that is defined on all wikis in the wiki farm.
*/
$wgFileBackends = array();
@@ -452,7 +504,10 @@ $wgFileBackends = array();
* Each backend configuration has the following parameters:
* - 'name' : A unique name for the lock manager
* - 'class' : The lock manger class to use
- * Additional parameters are specific to the class used.
+ *
+ * See LockManager::__construct() for more details.
+ * Additional parameters are specific to the lock manager class used.
+ * These settings should be global to all wikis.
*/
$wgLockManagers = array();
@@ -531,6 +586,13 @@ $wgAllowAsyncCopyUploads = false;
$wgCopyUploadsDomains = array();
/**
+ * Enable copy uploads from Special:Upload. $wgAllowCopyUploads must also be
+ * true. If $wgAllowCopyUploads is true, but this is false, you will only be
+ * able to perform copy uploads from the API or extensions (e.g. UploadWizard).
+ */
+$wgCopyUploadsFromSpecialUpload = false;
+
+/**
* Proxy to use for copy upload requests.
* @since 1.20
*/
@@ -553,7 +615,7 @@ $wgCopyUploadProxy = false;
* will have a maximum of 500 kB.
*
*/
-$wgMaxUploadSize = 1024*1024*100; # 100MB
+$wgMaxUploadSize = 1024 * 1024 * 100; # 100MB
/**
* Point the upload navigation link to an external URL
@@ -654,7 +716,7 @@ $wgFileBlacklist = array(
*/
$wgMimeTypeBlacklist = array(
# HTML may contain cookie-stealing JavaScript and web bugs
- 'text/html', 'text/javascript', 'text/x-javascript', 'application/x-shellscript',
+ 'text/html', 'text/javascript', 'text/x-javascript', 'application/x-shellscript',
# PHP scripts may execute arbitrary code on the server
'application/x-php', 'text/x-php',
# Other types that may be interpreted by some servers
@@ -712,10 +774,10 @@ $wgUploadSizeWarning = false;
*/
$wgTrustedMediaFormats = array(
MEDIATYPE_BITMAP, //all bitmap formats
- MEDIATYPE_AUDIO, //all audio formats
- MEDIATYPE_VIDEO, //all plain video formats
- "image/svg+xml", //svg (only needed if inline rendering of svg is not supported)
- "application/pdf", //PDF files
+ MEDIATYPE_AUDIO, //all audio formats
+ MEDIATYPE_VIDEO, //all plain video formats
+ "image/svg+xml", //svg (only needed if inline rendering of svg is not supported)
+ "application/pdf", //PDF files
#"application/x-shockwave-flash", //flash/shockwave movie
);
@@ -724,18 +786,35 @@ $wgTrustedMediaFormats = array(
* Each entry in the array maps a MIME type to a class name
*/
$wgMediaHandlers = array(
- 'image/jpeg' => 'JpegHandler',
- 'image/png' => 'PNGHandler',
- 'image/gif' => 'GIFHandler',
- 'image/tiff' => 'TiffHandler',
+ 'image/jpeg' => 'JpegHandler',
+ 'image/png' => 'PNGHandler',
+ 'image/gif' => 'GIFHandler',
+ 'image/tiff' => 'TiffHandler',
'image/x-ms-bmp' => 'BmpHandler',
- 'image/x-bmp' => 'BmpHandler',
- 'image/x-xcf' => 'XCFHandler',
- 'image/svg+xml' => 'SvgHandler', // official
- 'image/svg' => 'SvgHandler', // compat
+ 'image/x-bmp' => 'BmpHandler',
+ 'image/x-xcf' => 'XCFHandler',
+ 'image/svg+xml' => 'SvgHandler', // official
+ 'image/svg' => 'SvgHandler', // compat
'image/vnd.djvu' => 'DjVuHandler', // official
- 'image/x.djvu' => 'DjVuHandler', // compat
- 'image/x-djvu' => 'DjVuHandler', // compat
+ 'image/x.djvu' => 'DjVuHandler', // compat
+ 'image/x-djvu' => 'DjVuHandler', // compat
+);
+
+/**
+ * Plugins for page content model handling.
+ * Each entry in the array maps a model id to a class name.
+ *
+ * @since 1.21
+ */
+$wgContentHandlers = array(
+ // the usual case
+ CONTENT_MODEL_WIKITEXT => 'WikitextContentHandler',
+ // dumb version, no syntax highlighting
+ CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler',
+ // dumb version, no syntax highlighting
+ CONTENT_MODEL_CSS => 'CssContentHandler',
+ // plain text, for use by extensions etc
+ CONTENT_MODEL_TEXT => 'TextContentHandler',
);
/**
@@ -778,6 +857,13 @@ $wgImageMagickTempDir = false;
*/
$wgCustomConvertCommand = false;
+/** used for lossless jpeg rotation
+ *
+ * @since 1.21
+ * **/
+$wgJpegTran = '/usr/bin/jpegtran';
+
+
/**
* Some tests and extensions use exiv2 to manipulate the EXIF metadata in some
* image formats.
@@ -798,10 +884,10 @@ $wgSVGConverters = array(
'sodipodi' => '$path/sodipodi -z -w $width -f $input -e $output',
'inkscape' => '$path/inkscape -z -w $width -f $input -e $output',
'batik' => 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input',
- 'rsvg' => '$path/rsvg -w$width -h$height $input $output',
+ 'rsvg' => '$path/rsvg -w $width -h $height $input $output',
'imgserv' => '$path/imgserv-wrapper -i svg -o png -w$width $input $output',
'ImagickExt' => array( 'SvgHandler::rasterizeImagickExt' ),
- );
+);
/** Pick a converter defined in $wgSVGConverters */
$wgSVGConverter = 'ImageMagick';
@@ -864,7 +950,7 @@ $wgMaxAnimatedGifArea = 1.25e7;
* $wgTiffThumbnailType = array( 'jpg', 'image/jpeg' );
* @endcode
*/
- $wgTiffThumbnailType = false;
+$wgTiffThumbnailType = false;
/**
* If rendered thumbnail files are older than this timestamp, they
@@ -897,8 +983,8 @@ $wgIgnoreImageErrors = false;
$wgGenerateThumbnailOnParse = true;
/**
-* Show thumbnails for old images on the image description page
-*/
+ * Show thumbnails for old images on the image description page
+ */
$wgShowArchiveThumbnails = true;
/** Obsolete, always true, kept for compatibility with extensions */
@@ -912,7 +998,7 @@ $wgUseImageResize = true;
$wgEnableAutoRotation = null;
/**
- * Internal name of virus scanner. This servers as a key to the
+ * Internal name of virus scanner. This serves as a key to the
* $wgAntivirusSetup array. Set this to NULL to disable virus scanning. If not
* null, every file uploaded will be scanned for viruses.
*/
@@ -930,7 +1016,7 @@ $wgAntivirus = null;
* "command" is the full command to call the virus scanner - %f will be
* replaced with the name of the file to scan. If not present, the filename
* will be appended to the command. Note that this must be overwritten if the
- * scanner is not in the system path; in that case, plase set
+ * scanner is not in the system path; in that case, please set
* $wgAntivirusSetup[$wgAntivirus]['command'] to the desired command with full
* path.
*
@@ -940,8 +1026,8 @@ $wgAntivirus = null;
* the scan to be failed. This will pass the file if $wgAntivirusRequired
* is not set.
* - An exit code mapped to AV_SCAN_ABORTED causes the function to consider
- * the file to have an usupported format, which is probably imune to
- * virusses. This causes the file to pass.
+ * the file to have an unsupported format, which is probably immune to
+ * viruses. This causes the file to pass.
* - An exit code mapped to AV_NO_VIRUS will cause the file to pass, meaning
* no virus was found.
* - All other codes (like AV_VIRUS_FOUND) will cause the function to report
@@ -955,21 +1041,18 @@ $wgAntivirus = null;
$wgAntivirusSetup = array(
#setup for clamav
- 'clamav' => array (
- 'command' => "clamscan --no-summary ",
-
- 'codemap' => array (
- "0" => AV_NO_VIRUS, # no virus
- "1" => AV_VIRUS_FOUND, # virus found
- "52" => AV_SCAN_ABORTED, # unsupported file format (probably imune)
- "*" => AV_SCAN_FAILED, # else scan failed
+ 'clamav' => array(
+ 'command' => 'clamscan --no-summary ',
+ 'codemap' => array(
+ "0" => AV_NO_VIRUS, # no virus
+ "1" => AV_VIRUS_FOUND, # virus found
+ "52" => AV_SCAN_ABORTED, # unsupported file format (probably immune)
+ "*" => AV_SCAN_FAILED, # else scan failed
),
-
'messagepattern' => '/.*?:(.*)/sim',
),
);
-
/** Determines if a failed virus scan (AV_SCAN_FAILED) will cause the file to be rejected. */
$wgAntivirusRequired = true;
@@ -977,13 +1060,13 @@ $wgAntivirusRequired = true;
$wgVerifyMimeType = true;
/** Sets the mime type definition file to use by MimeMagic.php. */
-$wgMimeTypeFile = "includes/mime.types";
-#$wgMimeTypeFile= "/etc/mime.types";
-#$wgMimeTypeFile= null; #use built-in defaults only.
+$wgMimeTypeFile = 'includes/mime.types';
+#$wgMimeTypeFile = '/etc/mime.types';
+#$wgMimeTypeFile = null; #use built-in defaults only.
/** Sets the mime type info file to use by MimeMagic.php. */
-$wgMimeInfoFile= "includes/mime.info";
-#$wgMimeInfoFile= null; #use built-in defaults only.
+$wgMimeInfoFile = 'includes/mime.info';
+#$wgMimeInfoFile = null; #use built-in defaults only.
/**
* Switch for loading the FileInfo extension by PECL at runtime.
@@ -1016,11 +1099,11 @@ $wgTrivialMimeDetection = false;
* array = ( 'rootElement' => 'associatedMimeType' )
*/
$wgXMLMimeTypes = array(
- 'http://www.w3.org/2000/svg:svg' => 'image/svg+xml',
- 'svg' => 'image/svg+xml',
+ 'http://www.w3.org/2000/svg:svg' => 'image/svg+xml',
+ 'svg' => 'image/svg+xml',
'http://www.lysator.liu.se/~alla/dia/:diagram' => 'application/x-dia-diagram',
- 'http://www.w3.org/1999/xhtml:html' => 'text/html', // application/xhtml+xml?
- 'html' => 'text/html', // application/xhtml+xml?
+ 'http://www.w3.org/1999/xhtml:html' => 'text/html', // application/xhtml+xml?
+ 'html' => 'text/html', // application/xhtml+xml?
);
/**
@@ -1056,7 +1139,7 @@ $wgThumbLimits = array(
/**
* Default parameters for the "<gallery>" tag
*/
-$wgGalleryOptions = array (
+$wgGalleryOptions = array(
'imagesPerRow' => 0, // Default number of images per-row in the gallery. 0 -> Adapt to screensize
'imageWidth' => 120, // Width of the cells containing images in galleries (in "px")
'imageHeight' => 120, // Height of the cells containing images in galleries (in "px")
@@ -1077,6 +1160,16 @@ $wgThumbUpright = 0.75;
$wgDirectoryMode = 0777;
/**
+ * Generate and use thumbnails suitable for screens with 1.5 and 2.0 pixel densities.
+ *
+ * This means a 320x240 use of an image on the wiki will also generate 480x360 and 640x480
+ * thumbnails, output via data-src-1-5 and data-src-2-0. Runtime JavaScript switches the
+ * images in after loading the original low-resolution versions depending on the reported
+ * window.devicePixelRatio.
+ */
+$wgResponsiveImages = true;
+
+/**
* @name DJVU settings
* @{
*/
@@ -1182,7 +1275,7 @@ $wgEnableUserEmail = true;
* instead of From. ($wgEmergencyContact will be used as From.)
*
* Some mailers (eg sSMTP) set the SMTP envelope sender to the From value,
- * which can cause problems with SPF validation and leak recipient addressses
+ * which can cause problems with SPF validation and leak recipient addresses
* when bounces are sent to the sender.
*/
$wgUserEmailUseReplyTo = false;
@@ -1211,12 +1304,12 @@ $wgUserEmailConfirmationTokenExpiry = 7 * 24 * 60 * 60;
*
* @code
* $wgSMTP = array(
- * 'host' => 'SMTP domain',
- * 'IDHost' => 'domain for MessageID',
- * 'port' => '25',
- * 'auth' => [true|false],
- * 'username' => [SMTP username],
- * 'password' => [SMTP password],
+ * 'host' => 'SMTP domain',
+ * 'IDHost' => 'domain for MessageID',
+ * 'port' => '25',
+ * 'auth' => [true|false],
+ * 'username' => [SMTP username],
+ * 'password' => [SMTP password],
* );
* @endcode
*/
@@ -1229,6 +1322,12 @@ $wgSMTP = false;
$wgAdditionalMailParams = null;
/**
+ * For parts of the system that have been updated to provide HTML email content, send
+ * both text and HTML parts as the body of the email
+ */
+$wgAllowHTMLEmail = false;
+
+/**
* True: from page editor if s/he opted-in. False: Enotif mails appear to come
* from $wgEmergencyContact
*/
@@ -1375,12 +1474,16 @@ $wgAllDBsAreLocalhost = false;
* preferences shared (preferences were stored in the user table prior to 1.16)
*
* $wgSharedTables may be customized with a list of tables to share in the shared
- * datbase. However it is advised to limit what tables you do share as many of
+ * database. However it is advised to limit what tables you do share as many of
* MediaWiki's tables may have side effects if you try to share them.
- * EXPERIMENTAL
*
* $wgSharedPrefix is the table prefix for the shared database. It defaults to
* $wgDBprefix.
+ *
+ * @deprecated In new code, use the $wiki parameter to wfGetLB() to access
+ * remote databases. Using wfGetLB() allows the shared database to reside on
+ * separate servers to the wiki's own database, with suitable configuration
+ * of $wgLBFactoryConf.
*/
$wgSharedDB = null;
@@ -1459,7 +1562,7 @@ $wgDBerrorLog = false;
* Timezone to use in the error log.
* Defaults to the wiki timezone ($wgLocaltimezone).
*
- * A list of useable timezones can found at:
+ * A list of usable timezones can found at:
* http://php.net/manual/en/timezones.php
*
* @par Examples:
@@ -1526,7 +1629,6 @@ $wgOldChangeTagsIndex = false;
/**@}*/ # End of DB settings }
-
/************************************************************************//**
* @name Text storage
* @{
@@ -1560,7 +1662,7 @@ $wgExternalStores = false;
* Create a cluster named 'cluster1' containing three servers:
* @code
* $wgExternalServers = array(
- * 'cluster1' => array( 'srv28', 'srv29', 'srv30' )
+ * 'cluster1' => array( 'srv28', 'srv29', 'srv30' )
* );
* @endcode
*
@@ -1758,13 +1860,13 @@ $wgDBAhandler = 'db3';
/**
* Deprecated alias for $wgSessionsInObjectCache.
*
- * @deprecated Use $wgSessionsInObjectCache
+ * @deprecated since 1.20; Use $wgSessionsInObjectCache
*/
$wgSessionsInMemcached = false;
/**
* Store sessions in an object cache, configured by $wgSessionCacheType. This
- * can be useful to improve performance, or to avoid the locking behaviour of
+ * can be useful to improve performance, or to avoid the locking behavior of
* PHP's default session handler, which tends to prevent multiple requests for
* the same user from acting concurrently.
*/
@@ -1815,9 +1917,10 @@ $wgUseLocalMessageCache = false;
$wgLocalMessageCacheSerialized = true;
/**
- * Instead of caching everything, keep track which messages are requested and
- * load only most used messages. This only makes sense if there is lots of
- * interface messages customised in the wiki (like hundreds in many languages).
+ * Instead of caching everything, only cache those messages which have
+ * been customised in the site content language. This means that
+ * MediaWiki:Foo/ja is ignored if MediaWiki:Foo doesn't exist.
+ * This option is probably only useful for translatewiki.net.
*/
$wgAdaptiveMessageCache = false;
@@ -2032,6 +2135,27 @@ $wgSquidServersNoPurge = array();
$wgMaxSquidPurgeTitles = 400;
/**
+ * Whether to use a Host header in purge requests sent to the proxy servers
+ * configured in $wgSquidServers. Set this to false to support Squid
+ * configured in forward-proxy mode.
+ *
+ * If this is set to true, a Host header will be sent, and only the path
+ * component of the URL will appear on the request line, as if the request
+ * were a non-proxy HTTP 1.1 request. Varnish only supports this style of
+ * request. Squid supports this style of request only if reverse-proxy mode
+ * (http_port ... accel) is enabled.
+ *
+ * If this is set to false, no Host header will be sent, and the absolute URL
+ * will be sent in the request line, as is the standard for an HTTP proxy
+ * request in both HTTP 1.0 and 1.1. This style of request is not supported
+ * by Varnish, but is supported by Squid in either configuration (forward or
+ * reverse).
+ *
+ * @since 1.21
+ */
+$wgSquidPurgeUseHostHeader = true;
+
+/**
* Routing configuration for HTCP multicast purging. Add elements here to
* enable HTCP and determine which purges are sent where. If set to an empty
* array, HTCP is disabled.
@@ -2073,13 +2197,13 @@ $wgHTCPMulticastRouting = array();
* setting is ignored. If $wgHTCPMulticastRouting is not set and this setting
* is, it is used to populate $wgHTCPMulticastRouting.
*
- * @deprecated in favor of $wgHTCPMulticastRouting
+ * @deprecated since 1.20 in favor of $wgHTCPMulticastRouting
*/
$wgHTCPMulticastAddress = false;
/**
* HTCP multicast port.
- * @deprecated in favor of $wgHTCPMulticastRouting
+ * @deprecated since 1.20 in favor of $wgHTCPMulticastRouting
* @see $wgHTCPMulticastAddress
*/
$wgHTCPPort = 4827;
@@ -2100,10 +2224,30 @@ $wgUsePrivateIPs = false;
* @{
*/
-/** Site language code, should be one of ./languages/Language(.*).php */
+/**
+ * Site language code. See languages/Names.php for languages supported by
+ * MediaWiki out of the box. Not all languages listed there have translations,
+ * see languages/messages/ for the list of languages with some localisation.
+ *
+ * Warning: Don't use language codes listed in $wgDummyLanguageCodes like "no"
+ * for Norwegian (use "nb" instead), or things will break unexpectedly.
+ *
+ * This defines the default interface language for all users, but users can
+ * change it in their preferences.
+ *
+ * This also defines the language of pages in the wiki. The content is wrapped
+ * in a html element with lang=XX attribute. This behavior can be overridden
+ * via hooks, see Title::getPageLanguage.
+ */
$wgLanguageCode = 'en';
/**
+ * Language cache size, or really how many languages can we handle
+ * simultaneously without degrading to crawl speed.
+ */
+$wgLangObjCacheSize = 10;
+
+/**
* Some languages need different word forms, usually for different cases.
* Used in Language::convertGrammar().
*
@@ -2125,7 +2269,7 @@ $wgExtraLanguageNames = array();
/**
* List of language codes that don't correspond to an actual language.
- * These codes are mostly leftoffs from renames, or other legacy things.
+ * These codes are mostly left-offs from renames, or other legacy things.
* This array makes them not appear as a selectable language on the installer,
* and excludes them when running the transstat.php script.
*/
@@ -2249,24 +2393,16 @@ $wgBrowserBlackList = array(
* requires that the cur table be kept around for those revisions
* to remain viewable.
*
- * maintenance/migrateCurStubs.php can be used to complete the
- * migration in the background once the wiki is back online.
- *
* This option affects the updaters *only*. Any present cur stub
* revisions will be readable at runtime regardless of this setting.
*/
$wgLegacySchemaConversion = false;
/**
- * Enable to allow rewriting dates in page text.
- * DOES NOT FORMAT CORRECTLY FOR MOST LANGUAGES.
- */
-$wgUseDynamicDates = false;
-/**
* Enable dates like 'May 12' instead of '12 May', this only takes effect if
* the interface is set to English.
*/
-$wgAmericanDates = false;
+$wgAmericanDates = false;
/**
* For Hindi and Arabic use local numerals instead of Western style (0-9)
* numerals in interface.
@@ -2295,7 +2431,7 @@ $wgDisableLangConversion = false;
/** Whether to enable language variant conversion for links. */
$wgDisableTitleConversion = false;
-/** Whether to enable cononical language links in meta data. */
+/** Whether to enable canonical language links in meta data. */
$wgCanonicalLanguageLinks = true;
/** Default variant code, if false, the default will be the language code */
@@ -2319,9 +2455,9 @@ $wgDisabledVariants = array();
*
* @par Example:
* @code
- * $wgLanguageCode = 'sr';
- * $wgVariantArticlePath = '/$2/$1';
- * $wgArticlePath = '/wiki/$1';
+ * $wgLanguageCode = 'sr';
+ * $wgVariantArticlePath = '/$2/$1';
+ * $wgArticlePath = '/wiki/$1';
* @endcode
*
* A link to /wiki/ would be redirected to /sr/Главна_страна
@@ -2354,7 +2490,7 @@ $wgLoginLanguageSelector = false;
* To allow language-specific main page and community
* portal:
* @code
- * $wgForceUIMsgAsContentMsg = array( 'mainpage', 'portal-url' );
+ * $wgForceUIMsgAsContentMsg = array( 'mainpage', 'portal-url' );
* @endcode
*/
$wgForceUIMsgAsContentMsg = array();
@@ -2370,7 +2506,7 @@ $wgForceUIMsgAsContentMsg = array();
* Timezones can be translated by editing MediaWiki messages of type
* timezone-nameinlowercase like timezone-utc.
*
- * A list of useable timezones can found at:
+ * A list of usable timezones can found at:
* http://php.net/manual/en/timezones.php
*
* @par Examples:
@@ -2389,23 +2525,12 @@ $wgLocaltimezone = null;
* for anonymous users and new user accounts.
*
* This setting is used for most date/time displays in the software, and is
- * overrideable in user preferences. It is *not* used for signature timestamps.
+ * overridable in user preferences. It is *not* used for signature timestamps.
*
* By default, this will be set to match $wgLocaltimezone.
*/
$wgLocalTZoffset = null;
-/**
- * If set to true, this will roll back a few bug fixes introduced in 1.19,
- * emulating the 1.18 behaviour, to avoid introducing bug 34832. In 1.19,
- * language variant conversion is disabled in interface messages. Setting this
- * to true re-enables it.
- *
- * @todo This variable should be removed (implicitly false) in 1.20 or earlier.
- */
-$wgBug34832TransitionalRollback = true;
-
-
/** @} */ # End of language/charset settings
/*************************************************************************//**
@@ -2499,7 +2624,7 @@ $wgWellFormedXml = true;
* @par Example:
* @code
* $wgXhtmlNamespaces['svg'] = 'http://www.w3.org/2000/svg';
- * @endCode
+ * @endcode
* Normally we wouldn't have to define this in the root "<html>"
* element, but IE needs it there in some circumstances.
*
@@ -2527,12 +2652,12 @@ $wgSiteNotice = '';
/**
* A subtitle to add to the tagline, for skins that have it/
*/
-$wgExtraSubtitle = '';
+$wgExtraSubtitle = '';
/**
* If this is set, a "donate" link will appear in the sidebar. Set it to a URL.
*/
-$wgSiteSupportPage = '';
+$wgSiteSupportPage = '';
/**
* Validate the overall output using tidy and refuse
@@ -2684,7 +2809,7 @@ $wgExperimentalHtmlIds = false;
* for the icon, the following keys are used:
* - src: An absolute url to the image to use for the icon, this is recommended
* but not required, however some skins will ignore icons without an image
- * - url: The url to use in the a element arround the text or icon, if not set an a element will not be outputted
+ * - url: The url to use in the a element around the text or icon, if not set an a element will not be outputted
* - alt: This is the text form of the icon, it will be displayed without an image in
* skins like Modern or if src is not set, and will otherwise be used as
* the alt="" for the image. This key is required.
@@ -2719,7 +2844,7 @@ $wgUseCombinedLoginLink = false;
* - true = use an icon search button
* - false = use Go & Search buttons
*/
-$wgVectorUseSimpleSearch = false;
+$wgVectorUseSimpleSearch = true;
/**
* Watch and unwatch as an icon rather than a link for Vector skin only.
@@ -2754,16 +2879,24 @@ $wgBetterDirectionality = true;
*/
$wgSend404Code = true;
-
/**
* The $wgShowRollbackEditCount variable is used to show how many edits will be
- * rollback. The numeric value of the varible are the limit up to are counted.
- * If the value is false or 0, the edits are not counted.
+ * rollback. The numeric value of the variable are the limit up to are counted.
+ * If the value is false or 0, the edits are not counted. Disabling this will
+ * furthermore prevent MediaWiki from hiding some useless rollback links.
*
* @since 1.20
*/
$wgShowRollbackEditCount = 10;
+/**
+ * Output a <link rel="canonical"> tag on every page indicating the canonical
+ * server which should be used, i.e. $wgServer or $wgCanonicalServer. Since
+ * detection of the current server is unreliable, the link is sent
+ * unconditionally.
+ */
+$wgEnableCanonicalServerLink = false;
+
/** @} */ # End of output format settings }
/*************************************************************************//**
@@ -2890,21 +3023,21 @@ $wgPreloadJavaScriptMwUtil = false;
*
* @par Example of legacy code:
* @code{,js}
- * if ( window.wgRestrictionEdit ) { ... }
+ * if ( window.wgRestrictionEdit ) { ... }
* @endcode
* or:
* @code{,js}
- * if ( wgIsArticle ) { ... }
+ * if ( wgIsArticle ) { ... }
* @endcode
*
* Instead, one needs to use mw.config.
* @par Example using mw.config global configuration:
* @code{,js}
- * if ( mw.config.exists('wgRestrictionEdit') ) { ... }
+ * if ( mw.config.exists('wgRestrictionEdit') ) { ... }
* @endcode
* or:
* @code{,js}
- * if ( mw.config.get('wgIsArticle') ) { ... }
+ * if ( mw.config.get('wgIsArticle') ) { ... }
* @endcode
*/
$wgLegacyJavaScriptGlobals = true;
@@ -2950,7 +3083,6 @@ $wgResourceLoaderExperimentalAsyncLoading = false;
/** @} */ # End of resource loader settings }
-
/*************************************************************************//**
* @name Page title and interwiki link settings
* @{
@@ -3085,13 +3217,13 @@ $wgInterwikiExpiry = 10800;
$wgInterwikiCache = false;
/**
* Specify number of domains to check for messages.
- * - 1: Just wiki(db)-level
- * - 2: wiki and global levels
- * - 3: site levels
+ * - 1: Just wiki(db)-level
+ * - 2: wiki and global levels
+ * - 3: site levels
*/
$wgInterwikiScopes = 3;
/**
- * $wgInterwikiFallbackSite - if unable to resolve from cache
+ * $wgInterwikiFallbackSite - if unable to resolve from cache
*/
$wgInterwikiFallbackSite = 'wiki';
/** @} */ # end of Interwiki caching settings.
@@ -3133,7 +3265,7 @@ $wgCapitalLinks = true;
*
* @par Example:
* @code
- * $wgCapitalLinkOverrides[ NS_FILE ] = false;
+ * $wgCapitalLinkOverrides[ NS_FILE ] = false;
* @endcode
*/
$wgCapitalLinkOverrides = array();
@@ -3142,16 +3274,18 @@ $wgCapitalLinkOverrides = array();
* See Language.php for a list of namespaces.
*/
$wgNamespacesWithSubpages = array(
- NS_TALK => true,
- NS_USER => true,
- NS_USER_TALK => true,
- NS_PROJECT_TALK => true,
- NS_FILE_TALK => true,
- NS_MEDIAWIKI => true,
+ NS_TALK => true,
+ NS_USER => true,
+ NS_USER_TALK => true,
+ NS_PROJECT => true,
+ NS_PROJECT_TALK => true,
+ NS_FILE_TALK => true,
+ NS_MEDIAWIKI => true,
NS_MEDIAWIKI_TALK => true,
- NS_TEMPLATE_TALK => true,
- NS_HELP_TALK => true,
- NS_CATEGORY_TALK => true
+ NS_TEMPLATE_TALK => true,
+ NS_HELP => true,
+ NS_HELP_TALK => true,
+ NS_CATEGORY_TALK => true
);
/**
@@ -3195,7 +3329,7 @@ $wgInvalidRedirectTargets = array( 'Filepath', 'Mypage', 'Mytalk' );
* class The class name
*
* preprocessorClass The preprocessor class. Two classes are currently available:
- * Preprocessor_Hash, which uses plain PHP arrays for tempoarary
+ * Preprocessor_Hash, which uses plain PHP arrays for temporary
* storage, and Preprocessor_DOM, which uses the DOM module for
* temporary storage. Preprocessor_DOM generally uses less memory;
* the speed of the two is roughly the same.
@@ -3225,12 +3359,16 @@ $wgMaxTocLevel = 999;
$wgMaxPPNodeCount = 1000000;
/**
- * A complexity limit on template expansion: the maximum number of nodes
- * generated by Preprocessor::preprocessToObj()
+ * A complexity limit on template expansion: the maximum number of elements
+ * generated by Preprocessor::preprocessToObj(). This allows you to limit the
+ * amount of memory used by the Preprocessor_DOM node cache: testing indicates
+ * that each element uses about 160 bytes of memory on a 64-bit processor, so
+ * this default corresponds to about 155 MB.
+ *
+ * When the limit is exceeded, an exception is thrown.
*/
$wgMaxGeneratedPPNodeCount = 1000000;
-
/**
* Maximum recursion depth for templates within templates.
* The current parser adds two levels to the PHP call stack for each template,
@@ -3248,7 +3386,7 @@ $wgUrlProtocols = array(
'https://',
'ftp://',
'irc://',
- 'ircs://', // @bug 28503
+ 'ircs://', // @bug 28503
'gopher://',
'telnet://', // Well if we're going to support the above.. -ævar
'nntp://', // @bug 3808 RFC 1738
@@ -3324,7 +3462,7 @@ $wgAlwaysUseTidy = false;
/** @see $wgUseTidy */
$wgTidyBin = 'tidy';
/** @see $wgUseTidy */
-$wgTidyConf = $IP.'/includes/tidy.conf';
+$wgTidyConf = $IP . '/includes/tidy.conf';
/** @see $wgUseTidy */
$wgTidyOpts = '';
/** @see $wgUseTidy */
@@ -3520,77 +3658,70 @@ $wgReservedUsernames = array(
*
*/
$wgDefaultUserOptions = array(
- 'ccmeonemails' => 0,
- 'cols' => 80,
- 'date' => 'default',
- 'diffonly' => 0,
- 'disablemail' => 0,
- 'disablesuggest' => 0,
- 'editfont' => 'default',
- 'editondblclick' => 0,
- 'editsection' => 1,
+ 'ccmeonemails' => 0,
+ 'cols' => 80,
+ 'date' => 'default',
+ 'diffonly' => 0,
+ 'disablemail' => 0,
+ 'disablesuggest' => 0,
+ 'editfont' => 'default',
+ 'editondblclick' => 0,
+ 'editsection' => 1,
'editsectiononrightclick' => 0,
- 'enotifminoredits' => 0,
- 'enotifrevealaddr' => 0,
- 'enotifusertalkpages' => 1,
- 'enotifwatchlistpages' => 0,
- 'extendwatchlist' => 0,
- 'externaldiff' => 0,
- 'externaleditor' => 0,
- 'fancysig' => 0,
- 'forceeditsummary' => 0,
- 'gender' => 'unknown',
- 'hideminor' => 0,
- 'hidepatrolled' => 0,
- 'imagesize' => 2,
- 'justify' => 0,
- 'math' => 1,
- 'minordefault' => 0,
- 'newpageshidepatrolled' => 0,
- 'nocache' => 0,
- 'noconvertlink' => 0,
- 'norollbackdiff' => 0,
- 'numberheadings' => 0,
- 'previewonfirst' => 0,
- 'previewontop' => 1,
- 'quickbar' => 5,
- 'rcdays' => 7,
- 'rclimit' => 50,
- 'rememberpassword' => 0,
- 'rows' => 25,
- 'searchlimit' => 20,
- 'showhiddencats' => 0,
- 'showjumplinks' => 1,
- 'shownumberswatching' => 1,
- 'showtoc' => 1,
- 'showtoolbar' => 1,
- 'skin' => false,
- 'stubthreshold' => 0,
- 'thumbsize' => 2,
- 'underline' => 2,
- 'uselivepreview' => 0,
- 'usenewrc' => 0,
- 'watchcreations' => 0,
- 'watchdefault' => 0,
- 'watchdeletion' => 0,
- 'watchlistdays' => 3.0,
- 'watchlisthideanons' => 0,
- 'watchlisthidebots' => 0,
- 'watchlisthideliu' => 0,
- 'watchlisthideminor' => 0,
- 'watchlisthideown' => 0,
- 'watchlisthidepatrolled' => 0,
- 'watchmoves' => 0,
- 'wllimit' => 250,
+ 'enotifminoredits' => 0,
+ 'enotifrevealaddr' => 0,
+ 'enotifusertalkpages' => 1,
+ 'enotifwatchlistpages' => 0,
+ 'extendwatchlist' => 0,
+ 'externaldiff' => 0,
+ 'externaleditor' => 0,
+ 'fancysig' => 0,
+ 'forceeditsummary' => 0,
+ 'gender' => 'unknown',
+ 'hideminor' => 0,
+ 'hidepatrolled' => 0,
+ 'imagesize' => 2,
+ 'justify' => 0,
+ 'math' => 1,
+ 'minordefault' => 0,
+ 'newpageshidepatrolled' => 0,
+ 'nocache' => 0,
+ 'noconvertlink' => 0,
+ 'norollbackdiff' => 0,
+ 'numberheadings' => 0,
+ 'previewonfirst' => 0,
+ 'previewontop' => 1,
+ 'quickbar' => 5,
+ 'rcdays' => 7,
+ 'rclimit' => 50,
+ 'rememberpassword' => 0,
+ 'rows' => 25,
+ 'searchlimit' => 20,
+ 'showhiddencats' => 0,
+ 'showjumplinks' => 1,
+ 'shownumberswatching' => 1,
+ 'showtoc' => 1,
+ 'showtoolbar' => 1,
+ 'skin' => false,
+ 'stubthreshold' => 0,
+ 'thumbsize' => 2,
+ 'underline' => 2,
+ 'uselivepreview' => 0,
+ 'usenewrc' => 0,
+ 'watchcreations' => 0,
+ 'watchdefault' => 0,
+ 'watchdeletion' => 0,
+ 'watchlistdays' => 3.0,
+ 'watchlisthideanons' => 0,
+ 'watchlisthidebots' => 0,
+ 'watchlisthideliu' => 0,
+ 'watchlisthideminor' => 0,
+ 'watchlisthideown' => 0,
+ 'watchlisthidepatrolled' => 0,
+ 'watchmoves' => 0,
+ 'wllimit' => 250,
);
-/**
- * Whether or not to allow and use real name fields.
- * @deprecated since 1.16, use $wgHiddenPrefs[] = 'realname' below to disable real
- * names
- */
-$wgAllowRealName = true;
-
/** An array of preferences to not show for the user */
$wgHiddenPrefs = array();
@@ -3672,11 +3803,18 @@ $wgAllowPrefChange = array();
/**
* This is to let user authenticate using https when they come from http.
* Based on an idea by George Herbert on wikitech-l:
- * http://lists.wikimedia.org/pipermail/wikitech-l/2010-October/050065.html
+ * http://lists.wikimedia.org/pipermail/wikitech-l/2010-October/050039.html
* @since 1.17
*/
$wgSecureLogin = false;
+/**
+ * By default, keep users logged in via HTTPS when $wgSecureLogin is also
+ * true. Users opt-out of HTTPS when they login by de-selecting the checkbox.
+ * @since 1.21
+ */
+$wgSecureLoginDefaultHTTPS = true;
+
/** @} */ # end user accounts }
/************************************************************************//**
@@ -3745,6 +3883,34 @@ $wgBlockDisablesLogin = false;
$wgWhitelistRead = false;
/**
+ * Pages anonymous user may see, set as an array of regular expressions.
+ *
+ * This function will match the regexp against the title name, which
+ * is without underscore.
+ *
+ * @par Example:
+ * To whitelist [[Main Page]]:
+ * @code
+ * $wgWhitelistReadRegexp = array( "/Main Page/" );
+ * @endcode
+ *
+ * @note Unless ^ and/or $ is specified, a regular expression might match
+ * pages not intended to be whitelisted. The above example will also
+ * whitelist a page named 'Security Main Page'.
+ *
+ * @par Example:
+ * To allow reading any page starting with 'User' regardless of the case:
+ * @code
+ * $wgWhitelistReadRegexp = array( "@^UsEr.*@i" );
+ * @endcode
+ * Will allow both [[User is banned]] and [[User:JohnDoe]]
+ *
+ * @note This will only work if $wgGroupPermissions['*']['read'] is false --
+ * see below. Otherwise, ALL pages are accessible, regardless of this setting.
+ */
+$wgWhitelistReadRegexp = false;
+
+/**
* Should editors be required to have a validated e-mail
* address before being allowed to edit?
*/
@@ -3778,93 +3944,93 @@ $wgGroupPermissions = array();
/** @cond file_level_code */
// Implicit group for all visitors
-$wgGroupPermissions['*']['createaccount'] = true;
-$wgGroupPermissions['*']['read'] = true;
-$wgGroupPermissions['*']['edit'] = true;
-$wgGroupPermissions['*']['createpage'] = true;
-$wgGroupPermissions['*']['createtalk'] = true;
-$wgGroupPermissions['*']['writeapi'] = true;
-//$wgGroupPermissions['*']['patrolmarks'] = false; // let anons see what was patrolled
+$wgGroupPermissions['*']['createaccount'] = true;
+$wgGroupPermissions['*']['read'] = true;
+$wgGroupPermissions['*']['edit'] = true;
+$wgGroupPermissions['*']['createpage'] = true;
+$wgGroupPermissions['*']['createtalk'] = true;
+$wgGroupPermissions['*']['writeapi'] = true;
+#$wgGroupPermissions['*']['patrolmarks'] = false; // let anons see what was patrolled
// Implicit group for all logged-in accounts
-$wgGroupPermissions['user']['move'] = true;
-$wgGroupPermissions['user']['move-subpages'] = true;
+$wgGroupPermissions['user']['move'] = true;
+$wgGroupPermissions['user']['move-subpages'] = true;
$wgGroupPermissions['user']['move-rootuserpages'] = true; // can move root userpages
-$wgGroupPermissions['user']['movefile'] = true;
-$wgGroupPermissions['user']['read'] = true;
-$wgGroupPermissions['user']['edit'] = true;
-$wgGroupPermissions['user']['createpage'] = true;
-$wgGroupPermissions['user']['createtalk'] = true;
-$wgGroupPermissions['user']['writeapi'] = true;
-$wgGroupPermissions['user']['upload'] = true;
-$wgGroupPermissions['user']['reupload'] = true;
-$wgGroupPermissions['user']['reupload-shared'] = true;
-$wgGroupPermissions['user']['minoredit'] = true;
-$wgGroupPermissions['user']['purge'] = true; // can use ?action=purge without clicking "ok"
-$wgGroupPermissions['user']['sendemail'] = true;
+$wgGroupPermissions['user']['movefile'] = true;
+$wgGroupPermissions['user']['read'] = true;
+$wgGroupPermissions['user']['edit'] = true;
+$wgGroupPermissions['user']['createpage'] = true;
+$wgGroupPermissions['user']['createtalk'] = true;
+$wgGroupPermissions['user']['writeapi'] = true;
+$wgGroupPermissions['user']['upload'] = true;
+$wgGroupPermissions['user']['reupload'] = true;
+$wgGroupPermissions['user']['reupload-shared'] = true;
+$wgGroupPermissions['user']['minoredit'] = true;
+$wgGroupPermissions['user']['purge'] = true; // can use ?action=purge without clicking "ok"
+$wgGroupPermissions['user']['sendemail'] = true;
// Implicit group for accounts that pass $wgAutoConfirmAge
$wgGroupPermissions['autoconfirmed']['autoconfirmed'] = true;
// Users with bot privilege can have their edits hidden
// from various log pages by default
-$wgGroupPermissions['bot']['bot'] = true;
-$wgGroupPermissions['bot']['autoconfirmed'] = true;
-$wgGroupPermissions['bot']['nominornewtalk'] = true;
-$wgGroupPermissions['bot']['autopatrol'] = true;
+$wgGroupPermissions['bot']['bot'] = true;
+$wgGroupPermissions['bot']['autoconfirmed'] = true;
+$wgGroupPermissions['bot']['nominornewtalk'] = true;
+$wgGroupPermissions['bot']['autopatrol'] = true;
$wgGroupPermissions['bot']['suppressredirect'] = true;
-$wgGroupPermissions['bot']['apihighlimits'] = true;
-$wgGroupPermissions['bot']['writeapi'] = true;
-#$wgGroupPermissions['bot']['editprotected'] = true; // can edit all protected pages without cascade protection enabled
+$wgGroupPermissions['bot']['apihighlimits'] = true;
+$wgGroupPermissions['bot']['writeapi'] = true;
+#$wgGroupPermissions['bot']['editprotected'] = true; // can edit all protected pages without cascade protection enabled
// Most extra permission abilities go to this group
-$wgGroupPermissions['sysop']['block'] = true;
-$wgGroupPermissions['sysop']['createaccount'] = true;
-$wgGroupPermissions['sysop']['delete'] = true;
-$wgGroupPermissions['sysop']['bigdelete'] = true; // can be separately configured for pages with > $wgDeleteRevisionsLimit revs
-$wgGroupPermissions['sysop']['deletedhistory'] = true; // can view deleted history entries, but not see or restore the text
-$wgGroupPermissions['sysop']['deletedtext'] = true; // can view deleted revision text
-$wgGroupPermissions['sysop']['undelete'] = true;
-$wgGroupPermissions['sysop']['editinterface'] = true;
-$wgGroupPermissions['sysop']['editusercss'] = true;
-$wgGroupPermissions['sysop']['edituserjs'] = true;
-$wgGroupPermissions['sysop']['import'] = true;
-$wgGroupPermissions['sysop']['importupload'] = true;
-$wgGroupPermissions['sysop']['move'] = true;
-$wgGroupPermissions['sysop']['move-subpages'] = true;
+$wgGroupPermissions['sysop']['block'] = true;
+$wgGroupPermissions['sysop']['createaccount'] = true;
+$wgGroupPermissions['sysop']['delete'] = true;
+$wgGroupPermissions['sysop']['bigdelete'] = true; // can be separately configured for pages with > $wgDeleteRevisionsLimit revs
+$wgGroupPermissions['sysop']['deletedhistory'] = true; // can view deleted history entries, but not see or restore the text
+$wgGroupPermissions['sysop']['deletedtext'] = true; // can view deleted revision text
+$wgGroupPermissions['sysop']['undelete'] = true;
+$wgGroupPermissions['sysop']['editinterface'] = true;
+$wgGroupPermissions['sysop']['editusercss'] = true;
+$wgGroupPermissions['sysop']['edituserjs'] = true;
+$wgGroupPermissions['sysop']['import'] = true;
+$wgGroupPermissions['sysop']['importupload'] = true;
+$wgGroupPermissions['sysop']['move'] = true;
+$wgGroupPermissions['sysop']['move-subpages'] = true;
$wgGroupPermissions['sysop']['move-rootuserpages'] = true;
-$wgGroupPermissions['sysop']['patrol'] = true;
-$wgGroupPermissions['sysop']['autopatrol'] = true;
-$wgGroupPermissions['sysop']['protect'] = true;
-$wgGroupPermissions['sysop']['proxyunbannable'] = true;
-$wgGroupPermissions['sysop']['rollback'] = true;
-$wgGroupPermissions['sysop']['upload'] = true;
-$wgGroupPermissions['sysop']['reupload'] = true;
-$wgGroupPermissions['sysop']['reupload-shared'] = true;
-$wgGroupPermissions['sysop']['unwatchedpages'] = true;
-$wgGroupPermissions['sysop']['autoconfirmed'] = true;
-$wgGroupPermissions['sysop']['ipblock-exempt'] = true;
-$wgGroupPermissions['sysop']['blockemail'] = true;
-$wgGroupPermissions['sysop']['markbotedits'] = true;
-$wgGroupPermissions['sysop']['apihighlimits'] = true;
-$wgGroupPermissions['sysop']['browsearchive'] = true;
-$wgGroupPermissions['sysop']['noratelimit'] = true;
-$wgGroupPermissions['sysop']['movefile'] = true;
-$wgGroupPermissions['sysop']['unblockself'] = true;
+$wgGroupPermissions['sysop']['patrol'] = true;
+$wgGroupPermissions['sysop']['autopatrol'] = true;
+$wgGroupPermissions['sysop']['protect'] = true;
+$wgGroupPermissions['sysop']['proxyunbannable'] = true;
+$wgGroupPermissions['sysop']['rollback'] = true;
+$wgGroupPermissions['sysop']['upload'] = true;
+$wgGroupPermissions['sysop']['reupload'] = true;
+$wgGroupPermissions['sysop']['reupload-shared'] = true;
+$wgGroupPermissions['sysop']['unwatchedpages'] = true;
+$wgGroupPermissions['sysop']['autoconfirmed'] = true;
+$wgGroupPermissions['sysop']['ipblock-exempt'] = true;
+$wgGroupPermissions['sysop']['blockemail'] = true;
+$wgGroupPermissions['sysop']['markbotedits'] = true;
+$wgGroupPermissions['sysop']['apihighlimits'] = true;
+$wgGroupPermissions['sysop']['browsearchive'] = true;
+$wgGroupPermissions['sysop']['noratelimit'] = true;
+$wgGroupPermissions['sysop']['movefile'] = true;
+$wgGroupPermissions['sysop']['unblockself'] = true;
$wgGroupPermissions['sysop']['suppressredirect'] = true;
-#$wgGroupPermissions['sysop']['upload_by_url'] = true;
-#$wgGroupPermissions['sysop']['mergehistory'] = true;
+#$wgGroupPermissions['sysop']['upload_by_url'] = true;
+#$wgGroupPermissions['sysop']['mergehistory'] = true;
// Permission to change users' group assignments
-$wgGroupPermissions['bureaucrat']['userrights'] = true;
+$wgGroupPermissions['bureaucrat']['userrights'] = true;
$wgGroupPermissions['bureaucrat']['noratelimit'] = true;
// Permission to change users' groups assignments across wikis
#$wgGroupPermissions['bureaucrat']['userrights-interwiki'] = true;
// Permission to export pages including linked pages regardless of $wgExportMaxLinkDepth
#$wgGroupPermissions['bureaucrat']['override-export-depth'] = true;
-#$wgGroupPermissions['sysop']['deletelogentry'] = true;
-#$wgGroupPermissions['sysop']['deleterevision'] = true;
+#$wgGroupPermissions['sysop']['deletelogentry'] = true;
+#$wgGroupPermissions['sysop']['deleterevision'] = true;
// To hide usernames from users and Sysops
#$wgGroupPermissions['suppress']['hideuser'] = true;
// To hide revisions/log items from users and Sysops
@@ -3962,7 +4128,7 @@ $wgNamespaceProtection = array();
* namespaces constants (NS_USER, NS_MAIN...).
*
* Among other things, this may be useful to enforce read-restrictions
- * which may otherwise be bypassed by using the template machanism.
+ * which may otherwise be bypassed by using the template mechanism.
*/
$wgNonincludableNamespaces = array();
@@ -4037,11 +4203,11 @@ $wgAutopromote = array(
*
* The format is:
* @code
- * array( event => criteria, ... )
+ * array( event => criteria, ... )
* @endcode
* Where event is either:
- * - 'onEdit' (when user edits)
- * - 'onView' (when user views the wiki)
+ * - 'onEdit' (when user edits)
+ * - 'onView' (when user views the wiki)
*
* Criteria has the same format as $wgAutopromote
*
@@ -4100,7 +4266,8 @@ $wgDeleteRevisionsLimit = 0;
/**
* Number of accounts each IP address may create, 0 to disable.
*
- * @warning Requires memcached */
+ * @warning Requires memcached
+ */
$wgAccountCreationThrottle = 0;
/**
@@ -4191,25 +4358,25 @@ $wgProxyWhitelist = array();
*/
$wgRateLimits = array(
'edit' => array(
- 'anon' => null, // for any and all anonymous edits (aggregate)
- 'user' => null, // for each logged-in user
+ 'anon' => null, // for any and all anonymous edits (aggregate)
+ 'user' => null, // for each logged-in user
'newbie' => null, // for each recent (autoconfirmed) account; overrides 'user'
- 'ip' => null, // for each anon and recent account
+ 'ip' => null, // for each anon and recent account
'subnet' => null, // ... with final octet removed
- ),
+ ),
'move' => array(
- 'user' => null,
+ 'user' => null,
'newbie' => null,
- 'ip' => null,
+ 'ip' => null,
'subnet' => null,
- ),
+ ),
'mailpassword' => array(
'anon' => null,
- ),
+ ),
'emailuser' => array(
'user' => null,
- ),
- );
+ ),
+);
/**
* Set to a filename to log rate limiter hits.
@@ -4225,6 +4392,7 @@ $wgRateLimitsExcludedIPs = array();
/**
* Log IP addresses in the recentchanges table; can be accessed only by
* extensions (e.g. CheckUser) or a DB admin
+ * Used for retroactive autoblocks
*/
$wgPutIPinRC = true;
@@ -4261,14 +4429,26 @@ $wgBlockOpenProxies = false;
/** Port we want to scan for a proxy */
$wgProxyPorts = array( 80, 81, 1080, 3128, 6588, 8000, 8080, 8888, 65506 );
/** Script used to scan */
-$wgProxyScriptPath = "$IP/maintenance/proxy_check.php";
+$wgProxyScriptPath = "$IP/maintenance/proxyCheck.php";
/** */
$wgProxyMemcExpiry = 86400;
/** This should always be customised in LocalSettings.php */
$wgSecretKey = false;
-/** big list of banned IP addresses, in the keys not the values */
+
+/**
+ * Big list of banned IP addresses.
+ *
+ * This can have the following formats:
+ * - An array of addresses, either in the values
+ * or the keys (for backward compatibility)
+ * - A string, in that case this is the path to a file
+ * containing the list of IP addresses, one per line
+ */
$wgProxyList = array();
-/** deprecated */
+
+/**
+ * @deprecated since 1.14
+ */
$wgProxyKey = false;
/** @} */ # end of proxy scanner settings
@@ -4281,7 +4461,7 @@ $wgProxyKey = false;
/**
* Default cookie expiration time. Setting to 0 makes all cookies session-only.
*/
-$wgCookieExpiration = 180*86400;
+$wgCookieExpiration = 180 * 86400;
/**
* Set to set an explicit domain on the login cookies eg, "justthis.domain.org"
@@ -4289,7 +4469,6 @@ $wgCookieExpiration = 180*86400;
*/
$wgCookieDomain = '';
-
/**
* Set this variable if you want to restrict cookies to a certain path within
* the domain specified by $wgCookieDomain.
@@ -4343,7 +4522,7 @@ $wgCacheVaryCookies = array();
/** Override to customise the session name */
$wgSessionName = false;
-/** @} */ # end of cookie settings }
+/** @} */ # end of cookie settings }
/************************************************************************//**
* @name LaTeX (mathematical formulas)
@@ -4507,7 +4686,9 @@ $wgProfileOnly = false;
* Log sums from profiling into "profiling" table in db.
*
* You have to create a 'profiling' table in your database before using
- * this feature, see maintenance/archives/patch-profiling.sql
+ * this feature. Run set $wgProfileToDatabase to true in
+ * LocalSettings.php and run maintenance/update.php or otherwise
+ * manually add patch-profiling.sql to your database.
*
* To enable profiling, edit StartProfiler.php
*/
@@ -4561,6 +4742,13 @@ $wgAggregateStatsID = false;
$wgDisableCounters = false;
/**
+ * InfoAction retrieves a list of transclusion links (both to and from).
+ * This number puts a limit on that query in the case of highly transcluded
+ * templates.
+ */
+$wgPageInfoTransclusionLimit = 50;
+
+/**
* Set this to an integer to only do synchronous site_stats updates
* one every *this many* updates. The other requests go into pending
* delta values in $wgMemc. Make sure that $wgMemc is a global cache.
@@ -4621,7 +4809,6 @@ $wgJavaScriptTestConfig = array(
),
);
-
/**
* Overwrite the caching key prefix with custom value.
* @since 1.19
@@ -4650,7 +4837,7 @@ $wgDebugToolbar = false;
$wgDisableTextSearch = false;
/**
- * Set to true to have nicer highligted text in search results,
+ * Set to true to have nicer highlighted text in search results,
* by default off due to execution overhead
*/
$wgAdvancedSearchHighlighting = false;
@@ -4676,7 +4863,7 @@ $wgCountTotalSearchHits = false;
/**
* Template for OpenSearch suggestions, defaults to API action=opensearch
*
- * Sites with heavy load would tipically have these point to a custom
+ * Sites with heavy load would typically have these point to a custom
* PHP wrapper to avoid firing up mediawiki for every keystroke
*
* Placeholders: {searchTerms}
@@ -4724,7 +4911,7 @@ $wgNamespacesToBeSearchedDefault = array(
*/
$wgNamespacesToBeSearchedHelp = array(
NS_PROJECT => true,
- NS_HELP => true,
+ NS_HELP => true,
);
/**
@@ -4751,10 +4938,10 @@ $wgDisableInternalSearch = false;
* To forward to Google you'd have something like:
* @code
* $wgSearchForwardUrl =
- * 'http://www.google.com/search?q=$1' .
- * '&domains=http://example.com' .
- * '&sitesearch=http://example.com' .
- * '&ie=utf-8&oe=utf-8';
+ * 'http://www.google.com/search?q=$1' .
+ * '&domains=http://example.com' .
+ * '&sitesearch=http://example.com' .
+ * '&ie=utf-8&oe=utf-8';
* @endcode
*/
$wgSearchForwardUrl = null;
@@ -4768,14 +4955,14 @@ $wgUseTwoButtonsSearchForm = true;
/**
* Array of namespaces to generate a Google sitemap for when the
- * maintenance/generateSitemap.php script is run, or false if one is to be ge-
- * nerated for all namespaces.
+ * maintenance/generateSitemap.php script is run, or false if one is to be
+ * generated for all namespaces.
*/
$wgSitemapNamespaces = false;
/**
* Custom namespace priorities for sitemaps. Setting this will allow you to
- * set custom priorities to namsepaces when sitemaps are generated using the
+ * set custom priorities to namespaces when sitemaps are generated using the
* maintenance/generateSitemap.php script.
*
* This should be a map of namespace IDs to priority
@@ -4805,7 +4992,7 @@ $wgEnableSearchContributorsByIP = true;
/**
* Path to the GNU diff3 utility. If the file doesn't exist, edit conflicts will
- * fall back to the old behaviour (no merging).
+ * fall back to the old behavior (no merging).
*/
$wgDiff3 = '/usr/bin/diff3';
@@ -4816,7 +5003,7 @@ $wgDiff = '/usr/bin/diff';
/**
* Which namespaces have special treatment where they should be preview-on-open
- * Internaly only Category: pages apply, but using this extensions (e.g. Semantic MediaWiki)
+ * Internally only Category: pages apply, but using this extensions (e.g. Semantic MediaWiki)
* can specify namespaces of pages they have special treatment for
*/
$wgPreviewOnOpenNamespaces = array(
@@ -4858,7 +5045,7 @@ $wgUseAutomaticEditSummaries = true;
* @cond file_level_code
* Set $wgCommandLineMode if it's not set already, to avoid notices
*/
-if( !isset( $wgCommandLineMode ) ) {
+if ( !isset( $wgCommandLineMode ) ) {
$wgCommandLineMode = false;
}
/** @endcond */
@@ -5032,7 +5219,7 @@ $wgOverrideSiteFeed = array();
* $wgOut->isSyndicated() is true.
*/
$wgFeedClasses = array(
- 'rss' => 'RSSFeed',
+ 'rss' => 'RSSFeed',
'atom' => 'AtomFeed',
);
@@ -5078,6 +5265,15 @@ $wgAllowCategorizedRecentChanges = false;
*/
$wgUseTagFilter = true;
+/**
+ * If set to an integer, pages that are watched by this many users or more
+ * will not require the unwatchedpages permission to view the number of
+ * watchers.
+ *
+ * @since 1.21
+ */
+$wgUnwatchedPageThreshold = false;
+
/** @} */ # end RC/watchlist }
/************************************************************************//**
@@ -5180,8 +5376,8 @@ $wgExportAllowHistory = true;
$wgExportMaxHistory = 0;
/**
-* Return distinct author list (when not returning full history)
-*/
+ * Return distinct author list (when not returning full history)
+ */
$wgExportAllowListContributors = false;
/**
@@ -5198,13 +5394,13 @@ $wgExportAllowListContributors = false;
$wgExportMaxLinkDepth = 0;
/**
-* Whether to allow the "export all pages in namespace" option
-*/
+ * Whether to allow the "export all pages in namespace" option
+ */
$wgExportFromNamespaces = false;
/**
-* Whether to allow exporting the entire wiki into a single file
-*/
+ * Whether to allow exporting the entire wiki into a single file
+ */
$wgExportAllowAll = false;
/** @} */ # end of import/export }
@@ -5287,7 +5483,7 @@ $wgAutoloadClasses = array();
* 'version' => 1.9,
* 'path' => __FILE__,
* 'author' => 'Foo Barstein',
- * 'url' => 'http://wwww.example.com/Example%20Extension/',
+ * 'url' => 'http://www.example.com/Example%20Extension/',
* 'description' => 'An example extension',
* 'descriptionmsg' => 'exampleextension-desc',
* );
@@ -5296,6 +5492,11 @@ $wgAutoloadClasses = array();
* Where $type is 'specialpage', 'parserhook', 'variable', 'media' or 'other'.
* Where 'descriptionmsg' can be an array with message key and parameters:
* 'descriptionmsg' => array( 'exampleextension-desc', param1, param2, ... ),
+ *
+ * author can be a string or an array of strings. Authors can be linked using
+ * the regular wikitext link syntax. To have an internationalized version of
+ * "and others" show, add an element "...". This element can also be linked,
+ * for instance "[http://example ...]".
*/
$wgExtensionCredits = array();
@@ -5347,11 +5548,14 @@ $wgJobClasses = array(
'enotifNotify' => 'EnotifNotifyJob',
'fixDoubleRedirect' => 'DoubleRedirectJob',
'uploadFromUrl' => 'UploadFromUrlJob',
+ 'AssembleUploadChunks' => 'AssembleUploadChunksJob',
+ 'PublishStashedFile' => 'PublishStashedFileJob',
+ 'null' => 'NullJob'
);
/**
-
- * Jobs that must be explicitly requested, i.e. aren't run by job runners unless special flags are set.
+ * Jobs that must be explicitly requested, i.e. aren't run by job runners unless
+ * special flags are set. The values here are keys of $wgJobClasses.
*
* These can be:
* - Very long-running jobs.
@@ -5359,7 +5563,25 @@ $wgJobClasses = array(
* - Jobs that you want to run on specialized machines ( like transcoding, or a particular
* machine on your cluster has 'outside' web access you could restrict uploadFromUrl )
*/
-$wgJobTypesExcludedFromDefaultQueue = array();
+$wgJobTypesExcludedFromDefaultQueue = array( 'AssembleUploadChunks', 'PublishStashedFile' );
+
+/**
+ * Map of job types to configuration arrays.
+ * This determines which queue class and storage system is used for each job type.
+ * Job types that do not have explicit configuration will use the 'default' config.
+ * These settings should be global to all wikis.
+ */
+$wgJobTypeConf = array(
+ 'default' => array( 'class' => 'JobQueueDB', 'order' => 'random' ),
+);
+
+/**
+ * Which aggregator to use for tracking which queues have jobs.
+ * These settings should be global to all wikis.
+ */
+$wgJobQueueAggregator = array(
+ 'class' => 'JobQueueAggregatorMemc'
+);
/**
* Additional functions to be performed with updateSpecialPages.
@@ -5486,7 +5708,7 @@ $wgLogRestrictions = array(
*
* @par Example:
* @code
- * $wgFilterLogTypes => array(
+ * $wgFilterLogTypes = array(
* 'move' => true,
* 'import' => false,
* );
@@ -5513,16 +5735,16 @@ $wgFilterLogTypes = array(
* where TYPE is your log type, yoy don't need to use this array.
*/
$wgLogNames = array(
- '' => 'all-logs-page',
- 'block' => 'blocklogpage',
+ '' => 'all-logs-page',
+ 'block' => 'blocklogpage',
'protect' => 'protectlogpage',
- 'rights' => 'rightslog',
- 'delete' => 'dellogpage',
- 'upload' => 'uploadlogpage',
- 'move' => 'movelogpage',
- 'import' => 'importlogpage',
- 'patrol' => 'patrol-log-page',
- 'merge' => 'mergelog',
+ 'rights' => 'rightslog',
+ 'delete' => 'dellogpage',
+ 'upload' => 'uploadlogpage',
+ 'move' => 'movelogpage',
+ 'import' => 'importlogpage',
+ 'patrol' => 'patrol-log-page',
+ 'merge' => 'mergelog',
'suppress' => 'suppressionlog',
);
@@ -5536,16 +5758,16 @@ $wgLogNames = array(
* where TYPE is your log type, yoy don't need to use this array.
*/
$wgLogHeaders = array(
- '' => 'alllogstext',
- 'block' => 'blocklogtext',
+ '' => 'alllogstext',
+ 'block' => 'blocklogtext',
'protect' => 'protectlogtext',
- 'rights' => 'rightslogtext',
- 'delete' => 'dellogpagetext',
- 'upload' => 'uploadlogpagetext',
- 'move' => 'movelogpagetext',
- 'import' => 'importlogpagetext',
- 'patrol' => 'patrol-log-header',
- 'merge' => 'mergelogpagetext',
+ 'rights' => 'rightslogtext',
+ 'delete' => 'dellogpagetext',
+ 'upload' => 'uploadlogpagetext',
+ 'move' => 'movelogpagetext',
+ 'import' => 'importlogpagetext',
+ 'patrol' => 'patrol-log-header',
+ 'merge' => 'mergelogpagetext',
'suppress' => 'suppressionlogtext',
);
@@ -5556,23 +5778,21 @@ $wgLogHeaders = array(
* Extensions with custom log types may add to this array.
*/
$wgLogActions = array(
- 'block/block' => 'blocklogentry',
- 'block/unblock' => 'unblocklogentry',
- 'block/reblock' => 'reblock-logentry',
- 'protect/protect' => 'protectedarticle',
- 'protect/modify' => 'modifiedarticleprotection',
- 'protect/unprotect' => 'unprotectedarticle',
- 'protect/move_prot' => 'movedarticleprotection',
- 'rights/rights' => 'rightslogentry',
- 'rights/autopromote' => 'rightslogentry-autopromote',
- 'upload/upload' => 'uploadedimage',
- 'upload/overwrite' => 'overwroteimage',
- 'upload/revert' => 'uploadedimage',
- 'import/upload' => 'import-logentry-upload',
- 'import/interwiki' => 'import-logentry-interwiki',
- 'merge/merge' => 'pagemerge-logentry',
- 'suppress/block' => 'blocklogentry',
- 'suppress/reblock' => 'reblock-logentry',
+ 'block/block' => 'blocklogentry',
+ 'block/unblock' => 'unblocklogentry',
+ 'block/reblock' => 'reblock-logentry',
+ 'protect/protect' => 'protectedarticle',
+ 'protect/modify' => 'modifiedarticleprotection',
+ 'protect/unprotect' => 'unprotectedarticle',
+ 'protect/move_prot' => 'movedarticleprotection',
+ 'upload/upload' => 'uploadedimage',
+ 'upload/overwrite' => 'overwroteimage',
+ 'upload/revert' => 'uploadedimage',
+ 'import/upload' => 'import-logentry-upload',
+ 'import/interwiki' => 'import-logentry-interwiki',
+ 'merge/merge' => 'pagemerge-logentry',
+ 'suppress/block' => 'blocklogentry',
+ 'suppress/reblock' => 'reblock-logentry',
);
/**
@@ -5582,16 +5802,18 @@ $wgLogActions = array(
* @see LogFormatter
*/
$wgLogActionsHandlers = array(
- 'move/move' => 'MoveLogFormatter',
- 'move/move_redir' => 'MoveLogFormatter',
- 'delete/delete' => 'DeleteLogFormatter',
- 'delete/restore' => 'DeleteLogFormatter',
- 'delete/revision' => 'DeleteLogFormatter',
- 'delete/event' => 'DeleteLogFormatter',
+ 'move/move' => 'MoveLogFormatter',
+ 'move/move_redir' => 'MoveLogFormatter',
+ 'delete/delete' => 'DeleteLogFormatter',
+ 'delete/restore' => 'DeleteLogFormatter',
+ 'delete/revision' => 'DeleteLogFormatter',
+ 'delete/event' => 'DeleteLogFormatter',
'suppress/revision' => 'DeleteLogFormatter',
- 'suppress/event' => 'DeleteLogFormatter',
- 'suppress/delete' => 'DeleteLogFormatter',
- 'patrol/patrol' => 'PatrolLogFormatter',
+ 'suppress/event' => 'DeleteLogFormatter',
+ 'suppress/delete' => 'DeleteLogFormatter',
+ 'patrol/patrol' => 'PatrolLogFormatter',
+ 'rights/rights' => 'RightsLogFormatter',
+ 'rights/autopromote' => 'RightsLogFormatter',
);
/**
@@ -5620,110 +5842,10 @@ $wgDisableQueryPageUpdate = false;
/**
* List of special pages, followed by what subtitle they should go under
* at Special:SpecialPages
+ *
+ * @deprecated 1.21 Override SpecialPage::getGroupName instead
*/
-$wgSpecialPageGroups = array(
- 'DoubleRedirects' => 'maintenance',
- 'BrokenRedirects' => 'maintenance',
- 'Lonelypages' => 'maintenance',
- 'Uncategorizedpages' => 'maintenance',
- 'Uncategorizedcategories' => 'maintenance',
- 'Uncategorizedimages' => 'maintenance',
- 'Uncategorizedtemplates' => 'maintenance',
- 'Unusedcategories' => 'maintenance',
- 'Unusedimages' => 'maintenance',
- 'Protectedpages' => 'maintenance',
- 'Protectedtitles' => 'maintenance',
- 'Unusedtemplates' => 'maintenance',
- 'Withoutinterwiki' => 'maintenance',
- 'Longpages' => 'maintenance',
- 'Shortpages' => 'maintenance',
- 'Ancientpages' => 'maintenance',
- 'Deadendpages' => 'maintenance',
- 'Wantedpages' => 'maintenance',
- 'Wantedcategories' => 'maintenance',
- 'Wantedfiles' => 'maintenance',
- 'Wantedtemplates' => 'maintenance',
- 'Unwatchedpages' => 'maintenance',
- 'Fewestrevisions' => 'maintenance',
-
- 'Userlogin' => 'login',
- 'Userlogout' => 'login',
- 'CreateAccount' => 'login',
-
- 'Recentchanges' => 'changes',
- 'Recentchangeslinked' => 'changes',
- 'Watchlist' => 'changes',
- 'Newimages' => 'changes',
- 'Newpages' => 'changes',
- 'Log' => 'changes',
- 'Tags' => 'changes',
-
- 'Upload' => 'media',
- 'Listfiles' => 'media',
- 'MIMEsearch' => 'media',
- 'FileDuplicateSearch' => 'media',
- 'Filepath' => 'media',
-
- 'Listusers' => 'users',
- 'Activeusers' => 'users',
- 'Listgrouprights' => 'users',
- 'BlockList' => 'users',
- 'Contributions' => 'users',
- 'Emailuser' => 'users',
- 'Listadmins' => 'users',
- 'Listbots' => 'users',
- 'Userrights' => 'users',
- 'Block' => 'users',
- 'Unblock' => 'users',
- 'Preferences' => 'users',
- 'ChangeEmail' => 'users',
- 'ChangePassword' => 'users',
- 'DeletedContributions' => 'users',
- 'PasswordReset' => 'users',
-
- 'Mostlinked' => 'highuse',
- 'Mostlinkedcategories' => 'highuse',
- 'Mostlinkedtemplates' => 'highuse',
- 'Mostcategories' => 'highuse',
- 'Mostimages' => 'highuse',
- 'Mostinterwikis' => 'highuse',
- 'Mostrevisions' => 'highuse',
-
- 'Allpages' => 'pages',
- 'Prefixindex' => 'pages',
- 'Listredirects' => 'pages',
- 'Categories' => 'pages',
- 'Disambiguations' => 'pages',
-
- 'Randompage' => 'redirects',
- 'Randomredirect' => 'redirects',
- 'Mypage' => 'redirects',
- 'Mytalk' => 'redirects',
- 'Mycontributions' => 'redirects',
- 'Search' => 'redirects',
- 'LinkSearch' => 'redirects',
-
- 'ComparePages' => 'pagetools',
- 'Movepage' => 'pagetools',
- 'MergeHistory' => 'pagetools',
- 'Revisiondelete' => 'pagetools',
- 'Undelete' => 'pagetools',
- 'Export' => 'pagetools',
- 'Import' => 'pagetools',
- 'Whatlinkshere' => 'pagetools',
-
- 'Statistics' => 'wiki',
- 'Version' => 'wiki',
- 'Lockdb' => 'wiki',
- 'Unlockdb' => 'wiki',
- 'Allmessages' => 'wiki',
- 'Popularpages' => 'wiki',
-
- 'Specialpages' => 'other',
- 'Blockme' => 'other',
- 'Booksources' => 'other',
- 'JavaScriptTest' => 'other',
-);
+$wgSpecialPageGroups = array();
/** Whether or not to sort special pages in Special:Specialpages */
@@ -5759,24 +5881,24 @@ $wgMaxRedirectLinksRetrieved = 500;
* Unsetting core actions will probably cause things to complain loudly.
*/
$wgActions = array(
- 'credits' => true,
- 'delete' => true,
- 'edit' => true,
- 'history' => true,
- 'info' => true,
- 'markpatrolled' => true,
- 'protect' => true,
- 'purge' => true,
- 'raw' => true,
- 'render' => true,
- 'revert' => true,
+ 'credits' => true,
+ 'delete' => true,
+ 'edit' => true,
+ 'history' => true,
+ 'info' => true,
+ 'markpatrolled' => true,
+ 'protect' => true,
+ 'purge' => true,
+ 'raw' => true,
+ 'render' => true,
+ 'revert' => true,
'revisiondelete' => true,
- 'rollback' => true,
- 'submit' => true,
- 'unprotect' => true,
- 'unwatch' => true,
- 'view' => true,
- 'watch' => true,
+ 'rollback' => true,
+ 'submit' => true,
+ 'unprotect' => true,
+ 'unwatch' => true,
+ 'view' => true,
+ 'watch' => true,
);
/**
@@ -5818,14 +5940,14 @@ $wgNamespaceRobotPolicies = array();
/**
* Robot policies per article. These override the per-namespace robot policies.
- * Must be in the form of an array where the key part is a properly canonical-
- * ised text form title and the value is a robot policy.
+ * Must be in the form of an array where the key part is a properly canonicalised
+ * text form title and the value is a robot policy.
*
* @par Example:
* @code
* $wgArticleRobotPolicies = array(
- * 'Main Page' => 'noindex,follow',
- * 'User:Bob' => 'index,follow',
+ * 'Main Page' => 'noindex,follow',
+ * 'User:Bob' => 'index,follow',
* );
* @endcode
*
@@ -5882,6 +6004,22 @@ $wgEnableAPI = true;
$wgEnableWriteAPI = true;
/**
+ *
+ * WARNING: SECURITY THREAT - debug use only
+ *
+ * Disables many security checks in the API for debugging purposes.
+ * This flag should never be used on the production servers, as it introduces
+ * a number of potential security holes. Even when enabled, the validation
+ * will still be performed, but instead of failing, API will return a warning.
+ * Also, there will always be a warning notifying that this flag is set.
+ * At this point, the flag allows GET requests to go through for modules
+ * requiring POST.
+ *
+ * @since 1.21
+ */
+$wgDebugAPI = false;
+
+/**
* API module extensions.
* Associative array mapping module name to class name.
* Extension modules may override the core modules.
@@ -5893,6 +6031,12 @@ $wgAPIPropModules = array();
$wgAPIListModules = array();
/**
+ * This variable is ignored. To add your module to the API, please add it to $wgAPI*Modules
+ * @deprecated since 1.21
+ */
+$wgAPIGeneratorModules = array();
+
+/**
* Maximum amount of rows to scan in a DB query in the API
* The default value is generally fine
*/
@@ -5919,7 +6063,7 @@ $wgAPIRequestLog = false;
/**
* Set the timeout for the API help text cache. If set to 0, caching disabled
*/
-$wgAPICacheHelpTimeout = 60*60;
+$wgAPICacheHelpTimeout = 60 * 60;
/**
* Enable AJAX framework
@@ -5961,10 +6105,10 @@ $wgAjaxLicensePreview = true;
* @par Example:
* @code
* $wgCrossSiteAJAXdomains = array(
- * 'www.mediawiki.org',
- * '*.wikipedia.org',
- * '*.wikimedia.org',
- * '*.wiktionary.org',
+ * 'www.mediawiki.org',
+ * '*.wikipedia.org',
+ * '*.wikimedia.org',
+ * '*.wiktionary.org',
* );
* @endcode
*/
@@ -5997,11 +6141,42 @@ $wgMaxShellMemory = 102400;
$wgMaxShellFileSize = 102400;
/**
- * Maximum CPU time in seconds for shell processes under linux
+ * Maximum CPU time in seconds for shell processes under Linux
*/
$wgMaxShellTime = 180;
/**
+ * Maximum wall clock time (i.e. real time, of the kind the clock on the wall
+ * would measure) in seconds for shell processes under Linux
+ */
+$wgMaxShellWallClockTime = 180;
+
+/**
+ * Under Linux: a cgroup directory used to constrain memory usage of shell
+ * commands. The directory must be writable by the user which runs MediaWiki.
+ *
+ * If specified, this is used instead of ulimit, which is inaccurate, and
+ * causes malloc() to return NULL, which exposes bugs in C applications, making
+ * them segfault or deadlock.
+ *
+ * A wrapper script will create a cgroup for each shell command that runs, as
+ * a subgroup of the specified cgroup. If the memory limit is exceeded, the
+ * kernel will send a SIGKILL signal to a process in the subgroup.
+ *
+ * @par Example:
+ * @code
+ * mkdir -p /sys/fs/cgroup/memory/mediawiki
+ * mkdir -m 0777 /sys/fs/cgroup/memory/mediawiki/job
+ * echo '$wgShellCgroup = "/sys/fs/cgroup/memory/mediawiki/job";' >> LocalSettings.php
+ * @endcode
+ *
+ * The reliability of cgroup cleanup can be improved by installing a
+ * notify_on_release script in the root cgroup, see e.g.
+ * https://gerrit.wikimedia.org/r/#/c/40784
+ */
+$wgShellCgroup = false;
+
+/**
* Executable path of the PHP cli binary (php/php5). Should be set up on install.
*/
$wgPhpCli = '/usr/bin/php';
@@ -6061,6 +6236,15 @@ $wgUpdateRowsPerJob = 500;
*/
$wgUpdateRowsPerQuery = 100;
+/**
+ * Do not purge all the pages that use a page when it is edited
+ * if there are more than this many such pages. This is used to
+ * avoid invalidating a large portion of the squid/parser cache.
+ *
+ * This setting should factor in any squid/parser cache expiry settings.
+ */
+$wgMaxBacklinksInvalidate = false;
+
/** @} */ # End job queue }
/************************************************************************//**
@@ -6113,20 +6297,6 @@ $wgCompiledFiles = array();
/** @} */ # End of HipHop compilation }
-
-/************************************************************************//**
- * @name Mobile support
- * @{
- */
-
-/**
- * Name of the class used for mobile device detection, must be inherited from
- * IDeviceDetector.
- */
-$wgDeviceDetectionClass = 'DeviceDetection';
-
-/** @} */ # End of Mobile support }
-
/************************************************************************//**
* @name Miscellaneous
* @{
@@ -6211,6 +6381,57 @@ $wgDBtestuser = ''; //db user that has permission to create and drop the test da
$wgDBtestpassword = '';
/**
+ * Associative array mapping namespace IDs to the name of the content model pages in that namespace should have by
+ * default (use the CONTENT_MODEL_XXX constants). If no special content type is defined for a given namespace,
+ * pages in that namespace will use the CONTENT_MODEL_WIKITEXT (except for the special case of JS and CS pages).
+ *
+ * @since 1.21
+ */
+$wgNamespaceContentModels = array();
+
+/**
+ * How to react if a plain text version of a non-text Content object is requested using ContentHandler::getContentText():
+ *
+ * * 'ignore': return null
+ * * 'fail': throw an MWException
+ * * 'serialize': serialize to default format
+ *
+ * @since 1.21
+ */
+$wgContentHandlerTextFallback = 'ignore';
+
+/**
+ * Set to false to disable use of the database fields introduced by the ContentHandler facility.
+ * This way, the ContentHandler facility can be used without any additional information in the database.
+ * A page's content model is then derived solely from the page's title. This however means that changing
+ * a page's default model (e.g. using $wgNamespaceContentModels) will break the page and/or make the content
+ * inaccessible. This also means that pages can not be moved to a title that would default to a different
+ * content model.
+ *
+ * Overall, with $wgContentHandlerUseDB = false, no database updates are needed, but content handling
+ * is less robust and less flexible.
+ *
+ * @since 1.21
+ */
+$wgContentHandlerUseDB = true;
+
+/**
+ * Determines which types of text are parsed as wikitext. This does not imply that these kinds
+ * of texts are also rendered as wikitext, it only means that links, magic words, etc will have
+ * the effect on the database they would have on a wikitext page.
+ *
+ * @todo: On the long run, it would be nice to put categories etc into a separate structure,
+ * or at least parse only the contents of comments in the scripts.
+ *
+ * @since 1.21
+ */
+$wgTextModelsToParse = array(
+ CONTENT_MODEL_WIKITEXT, // Just for completeness, wikitext will always be parsed.
+ CONTENT_MODEL_JAVASCRIPT, // Make categories etc work, people put them into comments.
+ CONTENT_MODEL_CSS, // Make categories etc work, people put them into comments.
+);
+
+/**
* Whether the user must enter their password to change their e-mail address
*
* @since 1.20
@@ -6218,6 +6439,15 @@ $wgDBtestpassword = '';
$wgRequirePasswordforEmailChange = true;
/**
+ * Register handlers for specific types of sites.
+ *
+ * @since 1.20
+ */
+$wgSiteTypes = array(
+ 'mediawiki' => 'MediaWikiSite',
+);
+
+/**
* For really cool vim folding this needs to be at the end:
* vim: foldmarker=@{,@} foldmethod=marker
* @}
diff --git a/includes/DeferredUpdates.php b/includes/DeferredUpdates.php
index b4989a69..89c4df68 100644
--- a/includes/DeferredUpdates.php
+++ b/includes/DeferredUpdates.php
@@ -34,7 +34,7 @@ interface DeferrableUpdate {
}
/**
- * Class for mananging the deferred updates.
+ * Class for managing the deferred updates.
*
* @since 1.19
*/
@@ -66,7 +66,7 @@ class DeferredUpdates {
/**
* Do any deferred updates and clear the list
*
- * @param $commit String: set to 'commit' to commit after every update to
+ * @param string $commit set to 'commit' to commit after every update to
* prevent lock contention
*/
public static function doUpdates( $commit = '' ) {
@@ -92,7 +92,7 @@ class DeferredUpdates {
$update->doUpdate();
if ( $doCommit && $dbw->trxLevel() ) {
- $dbw->commit( __METHOD__ );
+ $dbw->commit( __METHOD__, 'flush' );
}
} catch ( MWException $e ) {
// We don't want exceptions thrown during deferred updates to
diff --git a/includes/Defines.php b/includes/Defines.php
index be9f9816..c4a86335 100644
--- a/includes/Defines.php
+++ b/includes/Defines.php
@@ -39,7 +39,7 @@ define( 'MW_SPECIALPAGE_VERSION', 2 );
define( 'DBO_DEBUG', 1 );
define( 'DBO_NOBUFFER', 2 );
define( 'DBO_IGNORE', 4 );
-define( 'DBO_TRX', 8 );
+define( 'DBO_TRX', 8 ); // automatically start transaction on first query
define( 'DBO_DEFAULT', 16 );
define( 'DBO_PERSISTENT', 32 );
define( 'DBO_SYSDBA', 64 ); //for oracle maintenance
@@ -54,13 +54,12 @@ define( 'DBO_COMPRESS', 512 );
*/
define( 'DB_SLAVE', -1 ); # Read from the slave (or only server)
define( 'DB_MASTER', -2 ); # Write to master (or only server)
-define( 'DB_LAST', -3 ); # Whatever database was used last
/**@}*/
# Obsolete aliases
define( 'DB_READ', -1 );
define( 'DB_WRITE', -2 );
-
+define( 'DB_LAST', -3 ); # deprecated since 2008, usage throws exception
/**@{
* Virtual namespaces; don't appear in the page database
@@ -138,7 +137,7 @@ define( 'MEDIATYPE_ARCHIVE', 'ARCHIVE' ); // archive file (zip, tar, etc)
*/
define( 'AV_NO_VIRUS', 0 ); #scan ok, no virus found
define( 'AV_VIRUS_FOUND', 1 ); #virus found!
-define( 'AV_SCAN_ABORTED', -1 ); #scan aborted, the file is probably imune
+define( 'AV_SCAN_ABORTED', -1 ); #scan aborted, the file is probably immune
define( 'AV_SCAN_FAILED', false ); #scan failed (scanner not found or error in scanner)
/**@}*/
@@ -171,11 +170,12 @@ define( 'MW_DATE_ISO', 'ISO 8601' );
/**@{
* RecentChange type identifiers
*/
-define( 'RC_EDIT', 0);
-define( 'RC_NEW', 1);
-define( 'RC_MOVE', 2); // obsolete
-define( 'RC_LOG', 3);
-define( 'RC_MOVE_OVER_REDIRECT', 4); // obsolete
+define( 'RC_EDIT', 0 );
+define( 'RC_NEW', 1 );
+define( 'RC_MOVE', 2 ); // obsolete
+define( 'RC_LOG', 3 );
+define( 'RC_MOVE_OVER_REDIRECT', 4 ); // obsolete
+define( 'RC_EXTERNAL', 5 );
/**@}*/
/**@{
@@ -199,7 +199,6 @@ define( 'LIST_AND', 1 );
define( 'LIST_SET', 2 );
define( 'LIST_NAMES', 3);
define( 'LIST_OR', 4);
-define( 'LIST_SET_PREPARED', 8); // List of (?, ?, ?) for DatabaseIbm_db2
/**@}*/
/**
@@ -213,6 +212,7 @@ require_once __DIR__.'/normal/UtfNormalDefines.php';
define( 'MW_SUPPORTS_EDITFILTERMERGED', 1 );
define( 'MW_SUPPORTS_PARSERFIRSTCALLINIT', 1 );
define( 'MW_SUPPORTS_LOCALISATIONCACHE', 1 );
+define( 'MW_SUPPORTS_CONTENTHANDLER', 1 );
/**@}*/
/** Support for $wgResourceModules */
@@ -225,7 +225,7 @@ define( 'MW_SUPPORTS_RESOURCE_MODULES', 1 );
define( 'OT_HTML', 1 );
define( 'OT_WIKI', 2 );
define( 'OT_PREPROCESS', 3 );
-define( 'OT_MSG' , 3 ); // b/c alias for OT_PREPROCESS
+define( 'OT_MSG', 3 ); // b/c alias for OT_PREPROCESS
define( 'OT_PLAIN', 4 );
/**@}*/
@@ -261,7 +261,7 @@ define( 'APCOND_BLOCKED', 8 );
define( 'APCOND_ISBOT', 9 );
/**@}*/
-/**
+/** @{
* Protocol constants for wfExpandUrl()
*/
define( 'PROTO_HTTP', 'http://' );
@@ -270,3 +270,35 @@ define( 'PROTO_RELATIVE', '//' );
define( 'PROTO_CURRENT', null );
define( 'PROTO_CANONICAL', 1 );
define( 'PROTO_INTERNAL', 2 );
+/**@}*/
+
+/**@{
+ * Content model ids, used by Content and ContentHandler.
+ * These IDs will be exposed in the API and XML dumps.
+ *
+ * Extensions that define their own content model IDs should take
+ * care to avoid conflicts. Using the extension name as a prefix is recommended,
+ * for example 'myextension-somecontent'.
+ */
+define( 'CONTENT_MODEL_WIKITEXT', 'wikitext' );
+define( 'CONTENT_MODEL_JAVASCRIPT', 'javascript' );
+define( 'CONTENT_MODEL_CSS', 'css' );
+define( 'CONTENT_MODEL_TEXT', 'text' );
+/**@}*/
+
+/**@{
+ * Content formats, used by Content and ContentHandler.
+ * These should be MIME types, and will be exposed in the API and XML dumps.
+ *
+ * Extensions are free to use the below formats, or define their own.
+ * It is recommended to stick with the conventions for MIME types.
+ */
+define( 'CONTENT_FORMAT_WIKITEXT', 'text/x-wiki' ); // wikitext
+define( 'CONTENT_FORMAT_JAVASCRIPT', 'text/javascript' ); // for js pages
+define( 'CONTENT_FORMAT_CSS', 'text/css' ); // for css pages
+define( 'CONTENT_FORMAT_TEXT', 'text/plain' ); // for future use, e.g. with some plain-html messages.
+define( 'CONTENT_FORMAT_HTML', 'text/html' ); // for future use, e.g. with some plain-html messages.
+define( 'CONTENT_FORMAT_SERIALIZED', 'application/vnd.php.serialized' ); // for future use with the api and for extensions
+define( 'CONTENT_FORMAT_JSON', 'application/json' ); // for future use with the api, and for use by extensions
+define( 'CONTENT_FORMAT_XML', 'application/xml' ); // for future use with the api, and for use by extensions
+/**@}*/
diff --git a/includes/DeprecatedGlobal.php b/includes/DeprecatedGlobal.php
index 4d7b9689..d48bd0b0 100644
--- a/includes/DeprecatedGlobal.php
+++ b/includes/DeprecatedGlobal.php
@@ -27,7 +27,7 @@
*/
class DeprecatedGlobal extends StubObject {
- // The m's are to stay consistent with parent class.
+ // The m's are to stay consistent with parent class.
protected $mRealValue, $mVersion;
function __construct( $name, $realValue, $version = false ) {
diff --git a/includes/EditPage.php b/includes/EditPage.php
index b762cad1..8b2dbb5f 100644
--- a/includes/EditPage.php
+++ b/includes/EditPage.php
@@ -40,90 +40,90 @@ class EditPage {
/**
* Status: Article successfully updated
*/
- const AS_SUCCESS_UPDATE = 200;
+ const AS_SUCCESS_UPDATE = 200;
/**
* Status: Article successfully created
*/
- const AS_SUCCESS_NEW_ARTICLE = 201;
+ const AS_SUCCESS_NEW_ARTICLE = 201;
/**
* Status: Article update aborted by a hook function
*/
- const AS_HOOK_ERROR = 210;
+ const AS_HOOK_ERROR = 210;
/**
* Status: A hook function returned an error
*/
- const AS_HOOK_ERROR_EXPECTED = 212;
+ const AS_HOOK_ERROR_EXPECTED = 212;
/**
- * Status: User is blocked from editting this page
+ * Status: User is blocked from editing this page
*/
- const AS_BLOCKED_PAGE_FOR_USER = 215;
+ const AS_BLOCKED_PAGE_FOR_USER = 215;
/**
* Status: Content too big (> $wgMaxArticleSize)
*/
- const AS_CONTENT_TOO_BIG = 216;
+ const AS_CONTENT_TOO_BIG = 216;
/**
* Status: User cannot edit? (not used)
*/
- const AS_USER_CANNOT_EDIT = 217;
+ const AS_USER_CANNOT_EDIT = 217;
/**
* Status: this anonymous user is not allowed to edit this page
*/
- const AS_READ_ONLY_PAGE_ANON = 218;
+ const AS_READ_ONLY_PAGE_ANON = 218;
/**
* Status: this logged in user is not allowed to edit this page
*/
- const AS_READ_ONLY_PAGE_LOGGED = 219;
+ const AS_READ_ONLY_PAGE_LOGGED = 219;
/**
* Status: wiki is in readonly mode (wfReadOnly() == true)
*/
- const AS_READ_ONLY_PAGE = 220;
+ const AS_READ_ONLY_PAGE = 220;
/**
* Status: rate limiter for action 'edit' was tripped
*/
- const AS_RATE_LIMITED = 221;
+ const AS_RATE_LIMITED = 221;
/**
- * Status: article was deleted while editting and param wpRecreate == false or form
+ * Status: article was deleted while editing and param wpRecreate == false or form
* was not posted
*/
- const AS_ARTICLE_WAS_DELETED = 222;
+ const AS_ARTICLE_WAS_DELETED = 222;
/**
* Status: user tried to create this page, but is not allowed to do that
* ( Title->usercan('create') == false )
*/
- const AS_NO_CREATE_PERMISSION = 223;
+ const AS_NO_CREATE_PERMISSION = 223;
/**
* Status: user tried to create a blank page
*/
- const AS_BLANK_ARTICLE = 224;
+ const AS_BLANK_ARTICLE = 224;
/**
* Status: (non-resolvable) edit conflict
*/
- const AS_CONFLICT_DETECTED = 225;
+ const AS_CONFLICT_DETECTED = 225;
/**
* Status: no edit summary given and the user has forceeditsummary set and the user is not
- * editting in his own userspace or talkspace and wpIgnoreBlankSummary == false
+ * editing in his own userspace or talkspace and wpIgnoreBlankSummary == false
*/
- const AS_SUMMARY_NEEDED = 226;
+ const AS_SUMMARY_NEEDED = 226;
/**
* Status: user tried to create a new section without content
*/
- const AS_TEXTBOX_EMPTY = 228;
+ const AS_TEXTBOX_EMPTY = 228;
/**
* Status: article is too big (> $wgMaxArticleSize), after merging in the new section
@@ -133,32 +133,57 @@ class EditPage {
/**
* not used
*/
- const AS_OK = 230;
+ const AS_OK = 230;
/**
- * Status: WikiPage::doEdit() was unsuccessfull
+ * Status: WikiPage::doEdit() was unsuccessful
*/
- const AS_END = 231;
+ const AS_END = 231;
/**
* Status: summary contained spam according to one of the regexes in $wgSummarySpamRegex
*/
- const AS_SPAM_ERROR = 232;
+ const AS_SPAM_ERROR = 232;
/**
* Status: anonymous user is not allowed to upload (User::isAllowed('upload') == false)
*/
- const AS_IMAGE_REDIRECT_ANON = 233;
+ const AS_IMAGE_REDIRECT_ANON = 233;
/**
* Status: logged in user is not allowed to upload (User::isAllowed('upload') == false)
*/
- const AS_IMAGE_REDIRECT_LOGGED = 234;
+ const AS_IMAGE_REDIRECT_LOGGED = 234;
+
+ /**
+ * Status: can't parse content
+ */
+ const AS_PARSE_ERROR = 240;
/**
* HTML id and name for the beginning of the edit form.
*/
- const EDITFORM_ID = 'editform';
+ const EDITFORM_ID = 'editform';
+
+ /**
+ * Prefix of key for cookie used to pass post-edit state.
+ * The revision id edited is added after this
+ */
+ const POST_EDIT_COOKIE_KEY_PREFIX = 'PostEditRevision';
+
+ /**
+ * Duration of PostEdit cookie, in seconds.
+ * The cookie will be removed instantly if the JavaScript runs.
+ *
+ * Otherwise, though, we don't want the cookies to accumulate.
+ * RFC 2109 ( https://www.ietf.org/rfc/rfc2109.txt ) specifies a possible limit of only 20 cookies per domain.
+ * This still applies at least to some versions of IE without full updates:
+ * https://blogs.msdn.com/b/ieinternals/archive/2009/08/20/wininet-ie-cookie-internals-faq.aspx
+ *
+ * A value of 20 minutes should be enough to take into account slow loads and minor
+ * clock skew while still avoiding cookie accumulation when JavaScript is turned off.
+ */
+ const POST_EDIT_COOKIE_DURATION = 1200;
/**
* @var Article
@@ -214,6 +239,7 @@ class EditPage {
var $textbox1 = '', $textbox2 = '', $summary = '', $nosummary = false;
var $edittime = '', $section = '', $sectiontitle = '', $starttime = '';
var $oldid = 0, $editintro = '', $scrolltop = null, $bot = true;
+ var $contentModel = null, $contentFormat = null;
# Placeholders for text injection by hooks (must be HTML)
# extensions should take care to _append_ to the present value
@@ -225,20 +251,32 @@ class EditPage {
public $editFormTextBottom = '';
public $editFormTextAfterContent = '';
public $previewTextAfterContent = '';
- public $mPreloadText = '';
+ public $mPreloadContent = null;
- /* $didSave should be set to true whenever an article was succesfully altered. */
+ /* $didSave should be set to true whenever an article was successfully altered. */
public $didSave = false;
public $undidRev = 0;
public $suppressIntro = false;
/**
+ * Set to true to allow editing of non-text content types.
+ *
+ * @var bool
+ */
+ public $allowNonTextContent = false;
+
+ /**
* @param $article Article
*/
public function __construct( Article $article ) {
$this->mArticle = $article;
$this->mTitle = $article->getTitle();
+
+ $this->contentModel = $this->mTitle->getContentModel();
+
+ $handler = ContentHandler::getForModelID( $this->contentModel );
+ $this->contentFormat = $handler->getDefaultFormat();
}
/**
@@ -267,7 +305,7 @@ class EditPage {
/**
* Get the context title object.
- * If not set, $wgTitle will be returned. This behavior might changed in
+ * If not set, $wgTitle will be returned. This behavior might change in
* the future to return $this->mTitle instead.
*
* @return Title object
@@ -359,11 +397,10 @@ class EditPage {
$this->isConflict = false;
// css / js subpages of user pages get a special treatment
- $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage();
- $this->isCssSubpage = $this->mTitle->isCssSubpage();
- $this->isJsSubpage = $this->mTitle->isJsSubpage();
+ $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage();
+ $this->isCssSubpage = $this->mTitle->isCssSubpage();
+ $this->isJsSubpage = $this->mTitle->isJsSubpage();
$this->isWrongCaseCssJsPage = $this->isWrongCaseCssJsPage();
- $this->isNew = !$this->mTitle->exists() || $this->section == 'new';
# Show applicable editing introductions
if ( $this->formtype == 'initial' || $this->firsttime ) {
@@ -392,10 +429,13 @@ class EditPage {
wfProfileOut( __METHOD__ );
return;
}
- if ( !$this->mTitle->getArticleID() )
+
+ if ( !$this->mTitle->getArticleID() ) {
wfRunHooks( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) );
- else
+ } else {
wfRunHooks( 'EditFormInitialText', array( $this ) );
+ }
+
}
$this->showEditForm();
@@ -436,8 +476,9 @@ class EditPage {
* "View source for ..." page displaying the source code after the error message.
*
* @since 1.19
- * @param $permErrors Array of permissions errors, as returned by
+ * @param array $permErrors of permissions errors, as returned by
* Title::getUserPermissionsErrors().
+ * @throws PermissionsError
*/
protected function displayPermissionsError( array $permErrors ) {
global $wgRequest, $wgOut;
@@ -450,15 +491,16 @@ class EditPage {
return;
}
- $content = $this->getContent();
+ $content = $this->getContentObject();
# Use the normal message if there's nothing to display
- if ( $this->firsttime && $content === '' ) {
+ if ( $this->firsttime && ( !$content || $content->isEmpty() ) ) {
$action = $this->mTitle->exists() ? 'edit' :
( $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage' );
throw new PermissionsError( $action, $permErrors );
}
+ $wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->setPageTitle( wfMessage( 'viewsource-title', $this->getContextTitle()->getPrefixedText() ) );
$wgOut->addBacklinkSubtitle( $this->getContextTitle() );
$wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $permErrors, 'edit' ) );
@@ -467,13 +509,14 @@ class EditPage {
# If the user made changes, preserve them when showing the markup
# (This happens when a user is blocked during edit, for instance)
if ( !$this->firsttime ) {
- $content = $this->textbox1;
+ $text = $this->textbox1;
$wgOut->addWikiMsg( 'viewyourtext' );
} else {
+ $text = $this->toEditText( $content );
$wgOut->addWikiMsg( 'viewsourcetext' );
}
- $this->showTextbox( $content, 'wpTextbox1', array( 'readonly' ) );
+ $this->showTextbox( $text, 'wpTextbox1', array( 'readonly' ) );
$wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
Linker::formatTemplates( $this->getTemplates() ) ) );
@@ -520,11 +563,11 @@ class EditPage {
// Nothing *to* preview for new sections
return false;
} elseif ( ( $wgRequest->getVal( 'preload' ) !== null || $this->mTitle->exists() ) && $wgUser->getOption( 'previewonfirst' ) ) {
- // Standard preference behaviour
+ // Standard preference behavior
return true;
} elseif ( !$this->mTitle->exists() &&
- isset( $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] ) &&
- $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] )
+ isset( $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] ) &&
+ $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] )
{
// Categories are special
return true;
@@ -554,13 +597,15 @@ class EditPage {
}
/**
- * Does this EditPage class support section editing?
- * This is used by EditPage subclasses to indicate their ui cannot handle section edits
+ * Returns whether section editing is supported for the current page.
+ * Subclasses may override this to replace the default behavior, which is
+ * to check ContentHandler::supportsSections.
*
- * @return bool
+ * @return bool true if this edit page supports sections, false otherwise.
*/
protected function isSectionEditSupported() {
- return true;
+ $contentHandler = ContentHandler::getForTitle( $this->mTitle );
+ return $contentHandler->supportsSections();
}
/**
@@ -568,13 +613,19 @@ class EditPage {
* @param $request WebRequest
*/
function importFormData( &$request ) {
- global $wgLang, $wgUser;
+ global $wgContLang, $wgUser;
wfProfileIn( __METHOD__ );
# Section edit can come from either the form or a link
$this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
+ if ( $this->section !== null && $this->section !== '' && !$this->isSectionEditSupported() ) {
+ throw new ErrorPageError( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
+ }
+
+ $this->isNew = !$this->mTitle->exists() || $this->section == 'new';
+
if ( $request->wasPosted() ) {
# These fields need to be checked for encoding.
# Also remove trailing whitespace, but don't remove _initial_
@@ -586,13 +637,15 @@ class EditPage {
// modified by subclasses
wfProfileIn( get_class( $this ) . "::importContentFormData" );
$textbox1 = $this->importContentFormData( $request );
- if ( isset( $textbox1 ) )
+ if ( $textbox1 !== null ) {
$this->textbox1 = $textbox1;
+ }
+
wfProfileOut( get_class( $this ) . "::importContentFormData" );
}
# Truncate for whole multibyte characters
- $this->summary = $wgLang->truncate( $request->getText( 'wpSummary' ), 255 );
+ $this->summary = $wgContLang->truncate( $request->getText( 'wpSummary' ), 255 );
# If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
# header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
@@ -604,7 +657,7 @@ class EditPage {
# currently doing double duty as both edit summary and section title. Right now this
# is just to allow API edits to work around this limitation, but this should be
# incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
- $this->sectiontitle = $wgLang->truncate( $request->getText( 'wpSectionTitle' ), 255 );
+ $this->sectiontitle = $wgContLang->truncate( $request->getText( 'wpSectionTitle' ), 255 );
$this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->sectiontitle );
$this->edittime = $request->getVal( 'wpEdittime' );
@@ -661,7 +714,7 @@ class EditPage {
$this->starttime = null;
}
- $this->recreate = $request->getCheck( 'wpRecreate' );
+ $this->recreate = $request->getCheck( 'wpRecreate' );
$this->minoredit = $request->getCheck( 'wpMinoredit' );
$this->watchthis = $request->getCheck( 'wpWatchthis' );
@@ -679,18 +732,18 @@ class EditPage {
} else {
# Not a posted form? Start with nothing.
wfDebug( __METHOD__ . ": Not a posted form.\n" );
- $this->textbox1 = '';
- $this->summary = '';
+ $this->textbox1 = '';
+ $this->summary = '';
$this->sectiontitle = '';
- $this->edittime = '';
- $this->starttime = wfTimestampNow();
- $this->edit = false;
- $this->preview = false;
- $this->save = false;
- $this->diff = false;
- $this->minoredit = false;
- $this->watchthis = $request->getBool( 'watchthis', false ); // Watch may be overriden by request parameters
- $this->recreate = false;
+ $this->edittime = '';
+ $this->starttime = wfTimestampNow();
+ $this->edit = false;
+ $this->preview = false;
+ $this->save = false;
+ $this->diff = false;
+ $this->minoredit = false;
+ $this->watchthis = $request->getBool( 'watchthis', false ); // Watch may be overridden by request parameters
+ $this->recreate = false;
// When creating a new section, we can preload a section title by passing it as the
// preloadtitle parameter in the URL (Bug 13100)
@@ -711,10 +764,17 @@ class EditPage {
}
}
+ $this->oldid = $request->getInt( 'oldid' );
+
$this->bot = $request->getBool( 'bot', true );
$this->nosummary = $request->getBool( 'nosummary' );
- $this->oldid = $request->getInt( 'oldid' );
+ $content_handler = ContentHandler::getForTitle( $this->mTitle );
+ $this->contentModel = $request->getText( 'model', $content_handler->getModelID() ); #may be overridden by revision
+ $this->contentFormat = $request->getText( 'format', $content_handler->getDefaultFormat() ); #may be overridden by revision
+
+ #TODO: check if the desired model is allowed in this namespace, and if a transition from the page's current model to the new model is allowed
+ #TODO: check if the desired content model supports the given content format!
$this->live = $request->getCheck( 'live' );
$this->editintro = $request->getText( 'editintro',
@@ -730,7 +790,7 @@ class EditPage {
/**
* Subpage overridable method for extracting the page content data from the
* posted form to be placed in $this->textbox1, if using customized input
- * this method should be overrided and return the page text that will be used
+ * this method should be overridden and return the page text that will be used
* for saving, preview parsing and so on...
*
* @param $request WebRequest
@@ -747,7 +807,13 @@ class EditPage {
function initialiseForm() {
global $wgUser;
$this->edittime = $this->mArticle->getTimestamp();
- $this->textbox1 = $this->getContent( false );
+
+ $content = $this->getContentObject( false ); #TODO: track content object?!
+ if ( $content === false ) {
+ return false;
+ }
+ $this->textbox1 = $this->toEditText( $content );
+
// activate checkboxes if user wants them to be always active
# Sort out the "watch" checkbox
if ( $wgUser->getOption( 'watchdefault' ) ) {
@@ -773,36 +839,65 @@ class EditPage {
/**
* Fetch initial editing page content.
*
- * @param $def_text string
+ * @param $def_text string|bool
* @return mixed string on success, $def_text for invalid sections
* @private
+ * @deprecated since 1.21, get WikiPage::getContent() instead.
*/
- function getContent( $def_text = '' ) {
- global $wgOut, $wgRequest, $wgParser;
+ function getContent( $def_text = false ) {
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
+ if ( $def_text !== null && $def_text !== false && $def_text !== '' ) {
+ $def_content = $this->toEditContent( $def_text );
+ } else {
+ $def_content = false;
+ }
+
+ $content = $this->getContentObject( $def_content );
+
+ // Note: EditPage should only be used with text based content anyway.
+ return $this->toEditText( $content );
+ }
+
+ /**
+ * @param Content|null $def_content The default value to return
+ *
+ * @return mixed Content on success, $def_content for invalid sections
+ *
+ * @since 1.21
+ */
+ protected function getContentObject( $def_content = null ) {
+ global $wgOut, $wgRequest;
wfProfileIn( __METHOD__ );
- $text = false;
+ $content = false;
// For message page not locally set, use the i18n message.
// For other non-existent articles, use preload text if any.
if ( !$this->mTitle->exists() || $this->section == 'new' ) {
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section != 'new' ) {
# If this is a system message, get the default text.
- $text = $this->mTitle->getDefaultMessageText();
+ $msg = $this->mTitle->getDefaultMessageText();
+
+ $content = $this->toEditContent( $msg );
}
- if ( $text === false ) {
+ if ( $content === false ) {
# If requested, preload some text.
$preload = $wgRequest->getVal( 'preload',
// Custom preload text for new sections
$this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
- $text = $this->getPreloadedText( $preload );
+
+ $content = $this->getPreloadedContent( $preload );
}
// For existing pages, get text based on "undo" or section parameters.
} else {
if ( $this->section != '' ) {
// Get section edit text (returns $def_text for invalid sections)
- $text = $wgParser->getSection( $this->getOriginalContent(), $this->section, $def_text );
+ $orig = $this->getOriginalContent();
+ $content = $orig ? $orig->getSection( $this->section ) : null;
+
+ if ( !$content ) $content = $def_content;
} else {
$undoafter = $wgRequest->getInt( 'undoafter' );
$undo = $wgRequest->getInt( 'undo' );
@@ -818,15 +913,16 @@ class EditPage {
# Sanity check, make sure it's the right page,
# the revisions exist and they were not deleted.
- # Otherwise, $text will be left as-is.
+ # Otherwise, $content will be left as-is.
if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
$undorev->getPage() == $oldrev->getPage() &&
$undorev->getPage() == $this->mTitle->getArticleID() &&
!$undorev->isDeleted( Revision::DELETED_TEXT ) &&
!$oldrev->isDeleted( Revision::DELETED_TEXT ) ) {
- $text = $this->mArticle->getUndoText( $undorev, $oldrev );
- if ( $text === false ) {
+ $content = $this->mArticle->getUndoContent( $undorev, $oldrev );
+
+ if ( $content === false ) {
# Warn the user that something went wrong
$undoMsg = 'failure';
} else {
@@ -859,14 +955,14 @@ class EditPage {
wfMessage( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
}
- if ( $text === false ) {
- $text = $this->getOriginalContent();
+ if ( $content === false ) {
+ $content = $this->getOriginalContent();
}
}
}
wfProfileOut( __METHOD__ );
- return $text;
+ return $content;
}
/**
@@ -876,38 +972,51 @@ class EditPage {
* section replaced in its context (using WikiPage::replaceSection())
* to the original text of the edit.
*
- * This difers from Article::getContent() that when a missing revision is
- * encountered the result will be an empty string and not the
+ * This differs from Article::getContent() that when a missing revision is
+ * encountered the result will be null and not the
* 'missing-revision' message.
*
* @since 1.19
- * @return string
+ * @return Content|null
*/
private function getOriginalContent() {
if ( $this->section == 'new' ) {
- return $this->getCurrentText();
+ return $this->getCurrentContent();
}
$revision = $this->mArticle->getRevisionFetched();
if ( $revision === null ) {
- return '';
+ if ( !$this->contentModel ) $this->contentModel = $this->getTitle()->getContentModel();
+ $handler = ContentHandler::getForModelID( $this->contentModel );
+
+ return $handler->makeEmptyContent();
}
- return $this->mArticle->getContent();
+ $content = $revision->getContent();
+ return $content;
}
/**
- * Get the actual text of the page. This is basically similar to
- * WikiPage::getRawText() except that when the page doesn't exist an empty
- * string is returned instead of false.
+ * Get the current content of the page. This is basically similar to
+ * WikiPage::getContent( Revision::RAW ) except that when the page doesn't exist an empty
+ * content object is returned instead of null.
*
- * @since 1.19
- * @return string
+ * @since 1.21
+ * @return Content
*/
- private function getCurrentText() {
- $text = $this->mArticle->getRawText();
- if ( $text === false ) {
- return '';
+ protected function getCurrentContent() {
+ $rev = $this->mArticle->getRevision();
+ $content = $rev ? $rev->getContent( Revision::RAW ) : null;
+
+ if ( $content === false || $content === null ) {
+ if ( !$this->contentModel ) $this->contentModel = $this->getTitle()->getContentModel();
+ $handler = ContentHandler::getForModelID( $this->contentModel );
+
+ return $handler->makeEmptyContent();
} else {
- return $text;
+ # nasty side-effect, but needed for consistency
+ $this->contentModel = $rev->getContentModel();
+ $this->contentFormat = $rev->getContentFormat();
+
+ return $content;
}
}
@@ -915,47 +1024,111 @@ class EditPage {
* Use this method before edit() to preload some text into the edit box
*
* @param $text string
+ * @deprecated since 1.21, use setPreloadedContent() instead.
*/
public function setPreloadedText( $text ) {
- $this->mPreloadText = $text;
+ ContentHandler::deprecated( __METHOD__, "1.21" );
+
+ $content = $this->toEditContent( $text );
+
+ $this->setPreloadedContent( $content );
+ }
+
+ /**
+ * Use this method before edit() to preload some content into the edit box
+ *
+ * @param $content Content
+ *
+ * @since 1.21
+ */
+ public function setPreloadedContent( Content $content ) {
+ $this->mPreloadContent = $content;
}
/**
* Get the contents to be preloaded into the box, either set by
* an earlier setPreloadText() or by loading the given page.
*
- * @param $preload String: representing the title to preload from.
+ * @param string $preload representing the title to preload from.
+ *
* @return String
+ *
+ * @deprecated since 1.21, use getPreloadedContent() instead
*/
protected function getPreloadedText( $preload ) {
- global $wgUser, $wgParser;
+ ContentHandler::deprecated( __METHOD__, "1.21" );
+
+ $content = $this->getPreloadedContent( $preload );
+ $text = $this->toEditText( $content );
- if ( !empty( $this->mPreloadText ) ) {
- return $this->mPreloadText;
+ return $text;
+ }
+
+ /**
+ * Get the contents to be preloaded into the box, either set by
+ * an earlier setPreloadText() or by loading the given page.
+ *
+ * @param string $preload representing the title to preload from.
+ *
+ * @return Content
+ *
+ * @since 1.21
+ */
+ protected function getPreloadedContent( $preload ) {
+ global $wgUser;
+
+ if ( !empty( $this->mPreloadContent ) ) {
+ return $this->mPreloadContent;
}
+ $handler = ContentHandler::getForTitle( $this->getTitle() );
+
if ( $preload === '' ) {
- return '';
+ return $handler->makeEmptyContent();
}
$title = Title::newFromText( $preload );
# Check for existence to avoid getting MediaWiki:Noarticletext
- if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
- return '';
+ if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
+ //TODO: somehow show a warning to the user!
+ return $handler->makeEmptyContent();
}
$page = WikiPage::factory( $title );
if ( $page->isRedirect() ) {
$title = $page->getRedirectTarget();
# Same as before
- if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
- return '';
+ if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
+ //TODO: somehow show a warning to the user!
+ return $handler->makeEmptyContent();
}
$page = WikiPage::factory( $title );
}
$parserOptions = ParserOptions::newFromUser( $wgUser );
- return $wgParser->getPreloadText( $page->getRawText(), $title, $parserOptions );
+ $content = $page->getContent( Revision::RAW );
+
+ if ( !$content ) {
+ //TODO: somehow show a warning to the user!
+ return $handler->makeEmptyContent();
+ }
+
+ if ( $content->getModel() !== $handler->getModelID() ) {
+ $converted = $content->convert( $handler->getModelID() );
+
+ if ( !$converted ) {
+ //TODO: somehow show a warning to the user!
+ wfDebug( "Attempt to preload incompatible content: "
+ . "can't convert " . $content->getModel()
+ . " to " . $handler->getModelID() );
+
+ return $handler->makeEmptyContent();
+ }
+
+ $content = $converted;
+ }
+
+ return $content->preloadTransform( $title, $parserOptions );
}
/**
@@ -974,7 +1147,35 @@ class EditPage {
}
/**
+ * Sets post-edit cookie indicating the user just saved a particular revision.
+ *
+ * This uses a temporary cookie for each revision ID so separate saves will never
+ * interfere with each other.
+ *
+ * The cookie is deleted in the mediawiki.action.view.postEdit JS module after
+ * the redirect. It must be clearable by JavaScript code, so it must not be
+ * marked HttpOnly. The JavaScript code converts the cookie to a wgPostEdit config
+ * variable.
+ *
+ * Since WebResponse::setcookie does not allow forcing HttpOnly for a single
+ * cookie, we have to use PHP's setcookie() directly.
+ *
+ * We use a path of '/' since wgCookiePath is not exposed to JS
+ *
+ * If the variable were set on the server, it would be cached, which is unwanted
+ * since the post-edit state should only apply to the load right after the save.
+ */
+ protected function setPostEditCookie() {
+ global $wgCookiePrefix, $wgCookieDomain;
+ $revisionId = $this->mArticle->getLatest();
+ $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId;
+
+ setcookie( $wgCookiePrefix . $postEditKey, '1', time() + self::POST_EDIT_COOKIE_DURATION, '/', $wgCookieDomain );
+ }
+
+ /**
* Attempt submission
+ * @throws UserBlockedError|ReadOnlyError|ThrottledError|PermissionsError
* @return bool false if output is done, true if the rest of the form should be displayed
*/
function attemptSave() {
@@ -987,6 +1188,9 @@ class EditPage {
// FIXME: once the interface for internalAttemptSave() is made nicer, this should use the message in $status
if ( $status->value == self::AS_SUCCESS_UPDATE || $status->value == self::AS_SUCCESS_NEW_ARTICLE ) {
$this->didSave = true;
+ if ( !$resultDetails['nullEdit'] ) {
+ $this->setPostEditCookie();
+ }
}
switch ( $status->value ) {
@@ -1003,6 +1207,10 @@ class EditPage {
case self::AS_HOOK_ERROR:
return false;
+ case self::AS_PARSE_ERROR:
+ $wgOut->addWikiText( '<div class="error">' . $status->getWikiText() . '</div>' );
+ return true;
+
case self::AS_SUCCESS_NEW_ARTICLE:
$query = $resultDetails['redirect'] ? 'redirect=no' : '';
$anchor = isset ( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
@@ -1067,10 +1275,62 @@ class EditPage {
}
/**
+ * Run hooks that can filter edits just before they get saved.
+ *
+ * @param Content $content the Content to filter.
+ * @param Status $status for reporting the outcome to the caller
+ * @param User $user the user performing the edit
+ *
+ * @return bool
+ */
+ protected function runPostMergeFilters( Content $content, Status $status, User $user ) {
+ // Run old style post-section-merge edit filter
+ if ( !ContentHandler::runLegacyHooks( 'EditFilterMerged',
+ array( $this, $content, &$this->hookError, $this->summary ) ) ) {
+
+ # Error messages etc. could be handled within the hook...
+ $status->fatal( 'hookaborted' );
+ $status->value = self::AS_HOOK_ERROR;
+ return false;
+ } elseif ( $this->hookError != '' ) {
+ # ...or the hook could be expecting us to produce an error
+ $status->fatal( 'hookaborted' );
+ $status->value = self::AS_HOOK_ERROR_EXPECTED;
+ return false;
+ }
+
+ // Run new style post-section-merge edit filter
+ if ( !wfRunHooks( 'EditFilterMergedContent',
+ array( $this->mArticle->getContext(), $content, $status, $this->summary,
+ $user, $this->minoredit ) ) ) {
+
+ # Error messages etc. could be handled within the hook...
+ // XXX: $status->value may already be something informative...
+ $this->hookError = $status->getWikiText();
+ $status->fatal( 'hookaborted' );
+ $status->value = self::AS_HOOK_ERROR;
+ return false;
+ } elseif ( !$status->isOK() ) {
+ # ...or the hook could be expecting us to produce an error
+ // FIXME this sucks, we should just use the Status object throughout
+ $this->hookError = $status->getWikiText();
+ $status->fatal( 'hookaborted' );
+ $status->value = self::AS_HOOK_ERROR_EXPECTED;
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
* Attempt submission (no UI)
*
- * @param $result
- * @param $bot bool
+ * @param array $result array to add statuses to, currently with the possible keys:
+ * spam - string - Spam string from content if any spam is detected by matchSpamRegex
+ * sectionanchor - string - Section anchor for a section save
+ * nullEdit - boolean - Set if doEditContent is OK. True if null edit, false otherwise.
+ * redirect - boolean - Set if doEditContent is OK. True if resulting revision is a redirect
+ * @param bool $bot True if edit is being made under the bot right.
*
* @return Status object, possibly with a message, but always with one of the AS_* constants in $status->value,
*
@@ -1083,7 +1343,7 @@ class EditPage {
$status = Status::newGood();
- wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ );
wfProfileIn( __METHOD__ . '-checks' );
if ( !wfRunHooks( 'EditPage::attemptSave', array( $this ) ) ) {
@@ -1091,19 +1351,30 @@ class EditPage {
$status->fatal( 'hookaborted' );
$status->value = self::AS_HOOK_ERROR;
wfProfileOut( __METHOD__ . '-checks' );
- wfProfileOut( __METHOD__ );
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ try {
+ # Construct Content object
+ $textbox_content = $this->toEditContent( $this->textbox1 );
+ } catch ( MWContentSerializationException $ex ) {
+ $status->fatal( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
+ $status->value = self::AS_PARSE_ERROR;
+ wfProfileOut( __METHOD__ . '-checks' );
+ wfProfileOut( __METHOD__ );
return $status;
}
# Check image redirect
if ( $this->mTitle->getNamespace() == NS_FILE &&
- Title::newFromRedirect( $this->textbox1 ) instanceof Title &&
+ $textbox_content->isRedirect() &&
!$wgUser->isAllowed( 'upload' ) ) {
$code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
$status->setResult( false, $code );
wfProfileOut( __METHOD__ . '-checks' );
- wfProfileOut( __METHOD__ );
+ wfProfileOut( __METHOD__ );
return $status;
}
@@ -1209,7 +1480,7 @@ class EditPage {
if ( $new ) {
// Late check for create permission, just in case *PARANOIA*
- if ( !$this->mTitle->userCan( 'create' ) ) {
+ if ( !$this->mTitle->userCan( 'create', $wgUser ) ) {
$status->fatal( 'nocreatetext' );
$status->value = self::AS_NO_CREATE_PERMISSION;
wfDebug( __METHOD__ . ": no create permission\n" );
@@ -1224,28 +1495,18 @@ class EditPage {
return $status;
}
- // Run post-section-merge edit filter
- if ( !wfRunHooks( 'EditFilterMerged', array( $this, $this->textbox1, &$this->hookError, $this->summary ) ) ) {
- # Error messages etc. could be handled within the hook...
- $status->fatal( 'hookaborted' );
- $status->value = self::AS_HOOK_ERROR;
- wfProfileOut( __METHOD__ );
- return $status;
- } elseif ( $this->hookError != '' ) {
- # ...or the hook could be expecting us to produce an error
- $status->fatal( 'hookaborted' );
- $status->value = self::AS_HOOK_ERROR_EXPECTED;
+ if ( !$this->runPostMergeFilters( $textbox_content, $status, $wgUser ) ) {
wfProfileOut( __METHOD__ );
return $status;
}
- $text = $this->textbox1;
+ $content = $textbox_content;
+
$result['sectionanchor'] = '';
if ( $this->section == 'new' ) {
if ( $this->sectiontitle !== '' ) {
// Insert the section title above the content.
- $text = wfMessage( 'newsectionheaderdefaultlevel', $this->sectiontitle )
- ->inContentLanguage()->text() . "\n\n" . $text;
+ $content = $content->addSectionHeader( $this->sectiontitle );
// Jump to the new section
$result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
@@ -1260,8 +1521,7 @@ class EditPage {
}
} elseif ( $this->summary !== '' ) {
// Insert the section title above the content.
- $text = wfMessage( 'newsectionheaderdefaultlevel', $this->summary )
- ->inContentLanguage()->text() . "\n\n" . $text;
+ $content = $content->addSectionHeader( $this->summary );
// Jump to the new section
$result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
@@ -1275,10 +1535,13 @@ class EditPage {
$status->value = self::AS_SUCCESS_NEW_ARTICLE;
- } else {
+ } else { # not $new
# Article exists. Check for edit conflict.
+
+ $this->mArticle->clear(); # Force reload of dates, etc.
$timestamp = $this->mArticle->getTimestamp();
+
wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
if ( $timestamp != $this->edittime ) {
@@ -1295,7 +1558,8 @@ class EditPage {
$this->isConflict = false;
wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
}
- } elseif ( $this->section == '' && Revision::userWasLastToEdit( DB_MASTER, $this->mTitle->getArticleID(), $wgUser->getId(), $this->edittime ) ) {
+ } elseif ( $this->section == '' && Revision::userWasLastToEdit( DB_MASTER, $this->mTitle->getArticleID(),
+ $wgUser->getId(), $this->edittime ) ) {
# Suppress edit conflict with self, except for section edits where merging is required.
wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
$this->isConflict = false;
@@ -1310,26 +1574,31 @@ class EditPage {
$sectionTitle = $this->summary;
}
+ $content = null;
+
if ( $this->isConflict ) {
- wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '{$timestamp}')\n" );
- $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle, $this->edittime );
+ wfDebug( __METHOD__ . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
+ . " (article time '{$timestamp}')\n" );
+
+ $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle, $this->edittime );
} else {
- wfDebug( __METHOD__ . ": getting section '$this->section'\n" );
- $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle );
+ wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" );
+ $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle );
}
- if ( is_null( $text ) ) {
+
+ if ( is_null( $content ) ) {
wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
$this->isConflict = true;
- $text = $this->textbox1; // do not try to merge here!
+ $content = $textbox_content; // do not try to merge here!
} elseif ( $this->isConflict ) {
# Attempt merge
- if ( $this->mergeChangesInto( $text ) ) {
+ if ( $this->mergeChangesIntoContent( $content ) ) {
// Successful merge! Maybe we should tell the user the good news?
$this->isConflict = false;
wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
} else {
$this->section = '';
- $this->textbox1 = $text;
+ $this->textbox1 = ContentHandler::getContentText( $content );
wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
}
}
@@ -1340,58 +1609,45 @@ class EditPage {
return $status;
}
- // Run post-section-merge edit filter
- if ( !wfRunHooks( 'EditFilterMerged', array( $this, $text, &$this->hookError, $this->summary ) ) ) {
- # Error messages etc. could be handled within the hook...
- $status->fatal( 'hookaborted' );
- $status->value = self::AS_HOOK_ERROR;
- wfProfileOut( __METHOD__ );
- return $status;
- } elseif ( $this->hookError != '' ) {
- # ...or the hook could be expecting us to produce an error
- $status->fatal( 'hookaborted' );
- $status->value = self::AS_HOOK_ERROR_EXPECTED;
+ if ( !$this->runPostMergeFilters( $content, $status, $wgUser ) ) {
wfProfileOut( __METHOD__ );
return $status;
}
- # Handle the user preference to force summaries here, but not for null edits
- if ( $this->section != 'new' && !$this->allowBlankSummary
- && $this->getOriginalContent() != $text
- && !Title::newFromRedirect( $text ) ) # check if it's not a redirect
- {
- if ( md5( $this->summary ) == $this->autoSumm ) {
+ if ( $this->section == 'new' ) {
+ // Handle the user preference to force summaries here
+ if ( !$this->allowBlankSummary && trim( $this->summary ) == '' ) {
$this->missingSummary = true;
- $status->fatal( 'missingsummary' );
+ $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
$status->value = self::AS_SUMMARY_NEEDED;
wfProfileOut( __METHOD__ );
return $status;
}
- }
- # And a similar thing for new sections
- if ( $this->section == 'new' && !$this->allowBlankSummary ) {
- if ( trim( $this->summary ) == '' ) {
- $this->missingSummary = true;
- $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
- $status->value = self::AS_SUMMARY_NEEDED;
+ // Do not allow the user to post an empty comment
+ if ( $this->textbox1 == '' ) {
+ $this->missingComment = true;
+ $status->fatal( 'missingcommenttext' );
+ $status->value = self::AS_TEXTBOX_EMPTY;
wfProfileOut( __METHOD__ );
return $status;
}
+ } elseif ( !$this->allowBlankSummary
+ && !$content->equals( $this->getOriginalContent() )
+ && !$content->isRedirect()
+ && md5( $this->summary ) == $this->autoSumm
+ ) {
+ $this->missingSummary = true;
+ $status->fatal( 'missingsummary' );
+ $status->value = self::AS_SUMMARY_NEEDED;
+ wfProfileOut( __METHOD__ );
+ return $status;
}
# All's well
wfProfileIn( __METHOD__ . '-sectionanchor' );
$sectionanchor = '';
if ( $this->section == 'new' ) {
- if ( $this->textbox1 == '' ) {
- $this->missingComment = true;
- $status->fatal( 'missingcommenttext' );
- $status->value = self::AS_TEXTBOX_EMPTY;
- wfProfileOut( __METHOD__ . '-sectionanchor' );
- wfProfileOut( __METHOD__ );
- return $status;
- }
if ( $this->sectiontitle !== '' ) {
$sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
// If no edit summary was specified, create one automatically from the section
@@ -1428,14 +1684,14 @@ class EditPage {
// merged the section into full text. Clear the section field
// so that later submission of conflict forms won't try to
// replace that into a duplicated mess.
- $this->textbox1 = $text;
+ $this->textbox1 = $this->toEditText( $content );
$this->section = '';
$status->value = self::AS_SUCCESS_UPDATE;
}
// Check for length errors again now that the section is merged in
- $this->kblength = (int)( strlen( $text ) / 1024 );
+ $this->kblength = (int)( strlen( $this->toEditText( $content ) ) / 1024 );
if ( $this->kblength > $wgMaxArticleSize ) {
$this->tooBig = true;
$status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
@@ -1448,14 +1704,10 @@ class EditPage {
( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
( $bot ? EDIT_FORCE_BOT : 0 );
- $doEditStatus = $this->mArticle->doEdit( $text, $this->summary, $flags );
+ $doEditStatus = $this->mArticle->doEditContent( $content, $this->summary, $flags,
+ false, null, $this->contentFormat );
- if ( $doEditStatus->isOK() ) {
- $result['redirect'] = Title::newFromRedirect( $text ) !== null;
- $this->commitWatch();
- wfProfileOut( __METHOD__ );
- return $status;
- } else {
+ if ( !$doEditStatus->isOK() ) {
// Failure from doEdit()
// Show the edit conflict page for certain recognized errors from doEdit(),
// but don't show it for errors from extension hooks
@@ -1470,63 +1722,107 @@ class EditPage {
wfProfileOut( __METHOD__ );
return $doEditStatus;
}
+
+ $result['nullEdit'] = $doEditStatus->hasMessage( 'edit-no-change' );
+ $result['redirect'] = $content->isRedirect();
+ $this->updateWatchlist();
+ wfProfileOut( __METHOD__ );
+ return $status;
}
/**
- * Commit the change of watch status
+ * Register the change of watch status
*/
- protected function commitWatch() {
+ protected function updateWatchlist() {
global $wgUser;
+
if ( $wgUser->isLoggedIn() && $this->watchthis != $wgUser->isWatched( $this->mTitle ) ) {
+ $fname = __METHOD__;
+ $title = $this->mTitle;
+ $watch = $this->watchthis;
+
+ // Do this in its own transaction to reduce contention...
$dbw = wfGetDB( DB_MASTER );
- $dbw->begin( __METHOD__ );
- if ( $this->watchthis ) {
- WatchAction::doWatch( $this->mTitle, $wgUser );
- } else {
- WatchAction::doUnwatch( $this->mTitle, $wgUser );
- }
- $dbw->commit( __METHOD__ );
+ $dbw->onTransactionIdle( function() use ( $dbw, $title, $watch, $wgUser, $fname ) {
+ $dbw->begin( $fname );
+ if ( $watch ) {
+ WatchAction::doWatch( $title, $wgUser );
+ } else {
+ WatchAction::doUnwatch( $title, $wgUser );
+ }
+ $dbw->commit( $fname );
+ } );
}
}
/**
- * @private
- * @todo document
+ * Attempts to merge text content with base and current revisions
*
* @param $editText string
*
* @return bool
+ * @deprecated since 1.21, use mergeChangesIntoContent() instead
*/
function mergeChangesInto( &$editText ) {
+ ContentHandler::deprecated( __METHOD__, "1.21" );
+
+ $editContent = $this->toEditContent( $editText );
+
+ $ok = $this->mergeChangesIntoContent( $editContent );
+
+ if ( $ok ) {
+ $editText = $this->toEditText( $editContent );
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Attempts to do 3-way merge of edit content with a base revision
+ * and current content, in case of edit conflict, in whichever way appropriate
+ * for the content type.
+ *
+ * @since 1.21
+ *
+ * @param $editContent
+ *
+ * @return bool
+ */
+ private function mergeChangesIntoContent( &$editContent ) {
wfProfileIn( __METHOD__ );
$db = wfGetDB( DB_MASTER );
// This is the revision the editor started from
$baseRevision = $this->getBaseRevision();
- if ( is_null( $baseRevision ) ) {
+ $baseContent = $baseRevision ? $baseRevision->getContent() : null;
+
+ if ( is_null( $baseContent ) ) {
wfProfileOut( __METHOD__ );
return false;
}
- $baseText = $baseRevision->getText();
// The current state, we want to merge updates into it
$currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
- if ( is_null( $currentRevision ) ) {
+ $currentContent = $currentRevision ? $currentRevision->getContent() : null;
+
+ if ( is_null( $currentContent ) ) {
wfProfileOut( __METHOD__ );
return false;
}
- $currentText = $currentRevision->getText();
- $result = '';
- if ( wfMerge( $baseText, $editText, $currentText, $result ) ) {
- $editText = $result;
+ $handler = ContentHandler::getForModelID( $baseContent->getModel() );
+
+ $result = $handler->merge3( $baseContent, $editContent, $currentContent );
+
+ if ( $result ) {
+ $editContent = $result;
wfProfileOut( __METHOD__ );
return true;
- } else {
- wfProfileOut( __METHOD__ );
- return false;
}
+
+ wfProfileOut( __METHOD__ );
+ return false;
}
/**
@@ -1690,10 +1986,13 @@ class EditPage {
# Give a notice if the user is editing a deleted/moved page...
if ( !$this->mTitle->exists() ) {
LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->mTitle,
- '', array( 'lim' => 10,
- 'conds' => array( "log_action != 'revision'" ),
- 'showIfEmpty' => false,
- 'msgKey' => array( 'recreate-moveddeleted-warn' ) )
+ '',
+ array(
+ 'lim' => 10,
+ 'conds' => array( "log_action != 'revision'" ),
+ 'showIfEmpty' => false,
+ 'msgKey' => array( 'recreate-moveddeleted-warn' )
+ )
);
}
}
@@ -1711,17 +2010,77 @@ class EditPage {
// Added using template syntax, to take <noinclude>'s into account.
$wgOut->addWikiTextTitleTidy( '{{:' . $title->getFullText() . '}}', $this->mTitle );
return true;
- } else {
- return false;
}
- } else {
- return false;
}
+ return false;
+ }
+
+ /**
+ * Gets an editable textual representation of $content.
+ * The textual representation can be turned by into a Content object by the
+ * toEditContent() method.
+ *
+ * If $content is null or false or a string, $content is returned unchanged.
+ *
+ * If the given Content object is not of a type that can be edited using the text base EditPage,
+ * an exception will be raised. Set $this->allowNonTextContent to true to allow editing of non-textual
+ * content.
+ *
+ * @param Content|null|bool|string $content
+ * @return String the editable text form of the content.
+ *
+ * @throws MWException if $content is not an instance of TextContent and $this->allowNonTextContent is not true.
+ */
+ protected function toEditText( $content ) {
+ if ( $content === null || $content === false ) {
+ return $content;
+ }
+
+ if ( is_string( $content ) ) {
+ return $content;
+ }
+
+ if ( !$this->allowNonTextContent && !( $content instanceof TextContent ) ) {
+ throw new MWException( "This content model can not be edited as text: "
+ . ContentHandler::getLocalizedName( $content->getModel() ) );
+ }
+
+ return $content->serialize( $this->contentFormat );
+ }
+
+ /**
+ * Turns the given text into a Content object by unserializing it.
+ *
+ * If the resulting Content object is not of a type that can be edited using the text base EditPage,
+ * an exception will be raised. Set $this->allowNonTextContent to true to allow editing of non-textual
+ * content.
+ *
+ * @param string|null|bool $text Text to unserialize
+ * @return Content The content object created from $text. If $text was false or null, false resp. null will be
+ * returned instead.
+ *
+ * @throws MWException if unserializing the text results in a Content object that is not an instance of TextContent
+ * and $this->allowNonTextContent is not true.
+ */
+ protected function toEditContent( $text ) {
+ if ( $text === false || $text === null ) {
+ return $text;
+ }
+
+ $content = ContentHandler::makeContent( $text, $this->getTitle(),
+ $this->contentModel, $this->contentFormat );
+
+ if ( !$this->allowNonTextContent && !( $content instanceof TextContent ) ) {
+ throw new MWException( "This content model can not be edited as text: "
+ . ContentHandler::getLocalizedName( $content->getModel() ) );
+ }
+
+ return $content;
}
/**
* Send the edit form and related headers to $wgOut
- * @param $formCallback Callback that takes an OutputPage parameter; will be called
+ * @param $formCallback Callback|null that takes an OutputPage parameter; will be called
* during form output near the top, for captchas and the like.
*/
function showEditForm( $formCallback = null ) {
@@ -1767,6 +2126,8 @@ class EditPage {
}
}
+ //@todo: add EditForm plugin interface and use it here!
+ // search for textarea1 and textares2, and allow EditForm to override all uses.
$wgOut->addHTML( Html::openElement( 'form', array( 'id' => self::EDITFORM_ID, 'name' => self::EDITFORM_ID,
'method' => 'post', 'action' => $this->getActionURL( $this->getContextTitle() ),
'enctype' => 'multipart/form-data' ) ) );
@@ -1820,7 +2181,7 @@ class EditPage {
}
if ( $this->hasPresetSummary ) {
- // If a summary has been preset using &summary= we dont want to prompt for
+ // If a summary has been preset using &summary= we don't want to prompt for
// a different summary. Only prompt for a summary if the summary is blanked.
// (Bug 17416)
$this->autoSumm = md5( '' );
@@ -1831,6 +2192,9 @@ class EditPage {
$wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
+ $wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) );
+ $wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) );
+
if ( $this->section == 'new' ) {
$this->showSummaryInput( true, $this->summary );
$wgOut->addHTML( $this->getSummaryPreview( true, $this->summary ) );
@@ -1843,12 +2207,14 @@ class EditPage {
}
if ( $this->isConflict ) {
- // In an edit conflict bypass the overrideable content form method
+ // In an edit conflict bypass the overridable content form method
// and fallback to the raw wpTextbox1 since editconflicts can't be
// resolved between page source edits and custom ui edits using the
// custom edit ui.
$this->textbox2 = $this->textbox1;
- $this->textbox1 = $this->getCurrentText();
+
+ $content = $this->getCurrentContent();
+ $this->textbox1 = $this->toEditText( $content );
$this->showTextbox1();
} else {
@@ -1874,7 +2240,13 @@ class EditPage {
Linker::formatHiddenCategories( $this->mArticle->getHiddenCategories() ) ) );
if ( $this->isConflict ) {
- $this->showConflict();
+ try {
+ $this->showConflict();
+ } catch ( MWContentSerializationException $ex ) {
+ // this can't really happen, but be nice if it does.
+ $msg = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
+ $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
+ }
}
$wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" );
@@ -1909,30 +2281,8 @@ class EditPage {
$wgOut->addWikiMsg( 'talkpagetext' );
}
- # Optional notices on a per-namespace and per-page basis
- $editnotice_ns = 'editnotice-' . $this->mTitle->getNamespace();
- $editnotice_ns_message = wfMessage( $editnotice_ns );
- if ( $editnotice_ns_message->exists() ) {
- $wgOut->addWikiText( $editnotice_ns_message->plain() );
- }
- if ( MWNamespace::hasSubpages( $this->mTitle->getNamespace() ) ) {
- $parts = explode( '/', $this->mTitle->getDBkey() );
- $editnotice_base = $editnotice_ns;
- while ( count( $parts ) > 0 ) {
- $editnotice_base .= '-' . array_shift( $parts );
- $editnotice_base_msg = wfMessage( $editnotice_base );
- if ( $editnotice_base_msg->exists() ) {
- $wgOut->addWikiText( $editnotice_base_msg->plain() );
- }
- }
- } else {
- # Even if there are no subpages in namespace, we still don't want / in MW ns.
- $editnoticeText = $editnotice_ns . '-' . str_replace( '/', '-', $this->mTitle->getDBkey() );
- $editnoticeMsg = wfMessage( $editnoticeText );
- if ( $editnoticeMsg->exists() ) {
- $wgOut->addWikiText( $editnoticeMsg->plain() );
- }
- }
+ // Add edit notices
+ $wgOut->addHTML( implode( "\n", $this->mTitle->getEditNotices() ) );
if ( $this->isConflict ) {
$wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1\n</div>", 'explainconflict' );
@@ -1948,7 +2298,7 @@ class EditPage {
if ( $this->section != '' && $this->section != 'new' ) {
if ( !$this->summary && !$this->preview && !$this->diff ) {
- $sectionTitle = self::extractSectionTitle( $this->textbox1 );
+ $sectionTitle = self::extractSectionTitle( $this->textbox1 ); //FIXME: use Content object
if ( $sectionTitle !== false ) {
$this->summary = "/* $sectionTitle */ ";
}
@@ -1980,7 +2330,7 @@ class EditPage {
if ( $revision ) {
// Let sysop know that this will make private content public if saved
- if ( !$revision->userCan( Revision::DELETED_TEXT ) ) {
+ if ( !$revision->userCan( Revision::DELETED_TEXT, $wgUser ) ) {
$wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
} elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
$wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
@@ -2014,10 +2364,13 @@ class EditPage {
$wgOut->wrapWikiMsg( "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>", array( 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ) );
}
if ( $this->formtype !== 'preview' ) {
- if ( $this->isCssSubpage )
+ if ( $this->isCssSubpage ) {
$wgOut->wrapWikiMsg( "<div id='mw-usercssyoucanpreview'>\n$1\n</div>", array( 'usercssyoucanpreview' ) );
- if ( $this->isJsSubpage )
+ }
+
+ if ( $this->isJsSubpage ) {
$wgOut->wrapWikiMsg( "<div id='mw-userjsyoucanpreview'>\n$1\n</div>", array( 'userjsyoucanpreview' ) );
+ }
}
}
}
@@ -2073,7 +2426,6 @@ class EditPage {
$this->showHeaderCopyrightWarning();
}
-
/**
* Standard summary input and label (wgSummary), abstracted so EditPage
* subclasses may reorganize the form.
@@ -2081,15 +2433,15 @@ class EditPage {
* inferred by the id given to the input. You can remove them both by
* passing array( 'id' => false ) to $userInputAttrs.
*
- * @param $summary string The value of the summary input
- * @param $labelText string The html to place inside the label
- * @param $inputAttrs array of attrs to use on the input
- * @param $spanLabelAttrs array of attrs to use on the span inside the label
+ * @param string $summary The value of the summary input
+ * @param string $labelText The html to place inside the label
+ * @param array $inputAttrs of attrs to use on the input
+ * @param array $spanLabelAttrs of attrs to use on the span inside the label
*
* @return array An array in the format array( $label, $input )
*/
function getSummaryInput( $summary = "", $labelText = null, $inputAttrs = null, $spanLabelAttrs = null ) {
- // Note: the maxlength is overriden in JS to 255 and to make it use UTF-8 bytes, not characters.
+ // Note: the maxlength is overridden in JS to 255 and to make it use UTF-8 bytes, not characters.
$inputAttrs = ( is_array( $inputAttrs ) ? $inputAttrs : array() ) + array(
'id' => 'wpSummary',
'maxlength' => '200',
@@ -2118,7 +2470,7 @@ class EditPage {
* @param $isSubjectPreview Boolean: true if this is the section subject/title
* up top, or false if this is the comment summary
* down below the textarea
- * @param $summary String: The text of the summary to display
+ * @param string $summary The text of the summary to display
* @return String
*/
protected function showSummaryInput( $isSubjectPreview, $summary = "" ) {
@@ -2144,18 +2496,22 @@ class EditPage {
* @param $isSubjectPreview Boolean: true if this is the section subject/title
* up top, or false if this is the comment summary
* down below the textarea
- * @param $summary String: the text of the summary to display
+ * @param string $summary the text of the summary to display
* @return String
*/
protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) {
- if ( !$summary || ( !$this->preview && !$this->diff ) )
+ // avoid spaces in preview, gets always trimmed on save
+ $summary = trim( $summary );
+ if ( !$summary || ( !$this->preview && !$this->diff ) ) {
return "";
+ }
global $wgParser;
- if ( $isSubjectPreview )
+ if ( $isSubjectPreview ) {
$summary = wfMessage( 'newsectionsummary', $wgParser->stripSectionName( $summary ) )
->inContentLanguage()->text();
+ }
$message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
@@ -2174,8 +2530,9 @@ class EditPage {
HTML
);
- if ( !$this->checkUnicodeCompliantBrowser() )
+ if ( !$this->checkUnicodeCompliantBrowser() ) {
$wgOut->addHTML( Html::hidden( 'safemode', '1' ) );
+ }
}
protected function showFormAfterText() {
@@ -2212,8 +2569,8 @@ HTML
* The $textoverride method can be used by subclasses overriding showContentForm
* to pass back to this method.
*
- * @param $customAttribs array of html attributes to use in the textarea
- * @param $textoverride String: optional text to override $this->textarea1 with
+ * @param array $customAttribs of html attributes to use in the textarea
+ * @param string $textoverride optional text to override $this->textarea1 with
*/
protected function showTextbox1( $customAttribs = null, $textoverride = null ) {
if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) {
@@ -2255,10 +2612,10 @@ HTML
$this->showTextbox( $this->textbox2, 'wpTextbox2', array( 'tabindex' => 6, 'readonly' ) );
}
- protected function showTextbox( $content, $name, $customAttribs = array() ) {
+ protected function showTextbox( $text, $name, $customAttribs = array() ) {
global $wgOut, $wgUser;
- $wikitext = $this->safeUnicodeOutput( $content );
+ $wikitext = $this->safeUnicodeOutput( $text );
if ( strval( $wikitext ) !== '' ) {
// Ensure there's a newline at the end, otherwise adding lines
// is awkward.
@@ -2285,13 +2642,15 @@ HTML
protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
global $wgOut;
$classes = array();
- if ( $isOnTop )
+ if ( $isOnTop ) {
$classes[] = 'ontop';
+ }
$attribs = array( 'id' => 'wikiPreview', 'class' => implode( ' ', $classes ) );
- if ( $this->formtype != 'preview' )
+ if ( $this->formtype != 'preview' ) {
$attribs['style'] = 'display: none;';
+ }
$wgOut->addHTML( Xml::openElement( 'div', $attribs ) );
@@ -2302,7 +2661,12 @@ HTML
$wgOut->addHTML( '</div>' );
if ( $this->formtype == 'diff' ) {
- $this->showDiff();
+ try {
+ $this->showDiff();
+ } catch ( MWContentSerializationException $ex ) {
+ $msg = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
+ $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
+ }
}
}
@@ -2310,7 +2674,7 @@ HTML
* Append preview output to $wgOut.
* Includes category rendering if this is a category page.
*
- * @param $text String: the HTML to be output for the preview.
+ * @param string $text the HTML to be output for the preview.
*/
protected function showPreview( $text ) {
global $wgOut;
@@ -2334,7 +2698,7 @@ HTML
* save and then make a comparison.
*/
function showDiff() {
- global $wgUser, $wgContLang, $wgParser, $wgOut;
+ global $wgUser, $wgContLang, $wgOut;
$oldtitlemsg = 'currentrev';
# if message does not exist, show diff against the preloaded default
@@ -2342,24 +2706,43 @@ HTML
$oldtext = $this->mTitle->getDefaultMessageText();
if( $oldtext !== false ) {
$oldtitlemsg = 'defaultmessagetext';
+ $oldContent = $this->toEditContent( $oldtext );
+ } else {
+ $oldContent = null;
}
} else {
- $oldtext = $this->mArticle->getRawText();
+ $oldContent = $this->getCurrentContent();
}
- $newtext = $this->mArticle->replaceSection(
- $this->section, $this->textbox1, $this->summary, $this->edittime );
- wfRunHooks( 'EditPageGetDiffText', array( $this, &$newtext ) );
+ $textboxContent = $this->toEditContent( $this->textbox1 );
+
+ $newContent = $this->mArticle->replaceSectionContent(
+ $this->section, $textboxContent,
+ $this->summary, $this->edittime );
- $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
- $newtext = $wgParser->preSaveTransform( $newtext, $this->mTitle, $wgUser, $popts );
+ if ( $newContent ) {
+ ContentHandler::runLegacyHooks( 'EditPageGetDiffText', array( $this, &$newContent ) );
+ wfRunHooks( 'EditPageGetDiffContent', array( $this, &$newContent ) );
+
+ $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
+ $newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts );
+ }
- if ( $oldtext !== false || $newtext != '' ) {
+ if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
$oldtitle = wfMessage( $oldtitlemsg )->parse();
$newtitle = wfMessage( 'yourtext' )->parse();
- $de = new DifferenceEngine( $this->mArticle->getContext() );
- $de->setText( $oldtext, $newtext );
+ if ( !$oldContent ) {
+ $oldContent = $newContent->getContentHandler()->makeEmptyContent();
+ }
+
+ if ( !$newContent ) {
+ $newContent = $oldContent->getContentHandler()->makeEmptyContent();
+ }
+
+ $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->mArticle->getContext() );
+ $de->setContent( $oldContent, $newContent );
+
$difftext = $de->getDiff( $oldtitle, $newtitle );
$de->showDiffStyle();
} else {
@@ -2463,7 +2846,9 @@ HTML
wfMessage( 'newwindow' )->parse();
$wgOut->addHTML( " <span class='cancelLink'>{$cancel}</span>\n" );
$wgOut->addHTML( " <span class='editHelp'>{$edithelp}</span>\n" );
- $wgOut->addHTML( "</div><!-- editButtons -->\n</div><!-- editOptions -->\n" );
+ $wgOut->addHTML( "</div><!-- editButtons -->\n" );
+ wfRunHooks( 'EditPage::showStandardInputs:options', array( $this, $wgOut, &$tabindex ) );
+ $wgOut->addHTML( "</div><!-- editOptions -->\n" );
}
/**
@@ -2476,8 +2861,12 @@ HTML
if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
$wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
- $de = new DifferenceEngine( $this->mArticle->getContext() );
- $de->setText( $this->textbox2, $this->textbox1 );
+ $content1 = $this->toEditContent( $this->textbox1 );
+ $content2 = $this->toEditContent( $this->textbox2 );
+
+ $handler = ContentHandler::getForModelID( $this->contentModel );
+ $de = $handler->createDifferenceEngine( $this->mArticle->getContext() );
+ $de->setContent( $content2, $content1 );
$de->showDiff(
wfMessage( 'yourtext' )->parse(),
wfMessage( 'storedversion' )->text()
@@ -2548,40 +2937,47 @@ HTML
$dbr = wfGetDB( DB_SLAVE );
$data = $dbr->selectRow(
array( 'logging', 'user' ),
- array( 'log_type',
- 'log_action',
- 'log_timestamp',
- 'log_user',
- 'log_namespace',
- 'log_title',
- 'log_comment',
- 'log_params',
- 'log_deleted',
- 'user_name' ),
- array( 'log_namespace' => $this->mTitle->getNamespace(),
- 'log_title' => $this->mTitle->getDBkey(),
- 'log_type' => 'delete',
- 'log_action' => 'delete',
- 'user_id=log_user' ),
+ array(
+ 'log_type',
+ 'log_action',
+ 'log_timestamp',
+ 'log_user',
+ 'log_namespace',
+ 'log_title',
+ 'log_comment',
+ 'log_params',
+ 'log_deleted',
+ 'user_name'
+ ), array(
+ 'log_namespace' => $this->mTitle->getNamespace(),
+ 'log_title' => $this->mTitle->getDBkey(),
+ 'log_type' => 'delete',
+ 'log_action' => 'delete',
+ 'user_id=log_user'
+ ),
__METHOD__,
array( 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' )
);
// Quick paranoid permission checks...
if ( is_object( $data ) ) {
- if ( $data->log_deleted & LogPage::DELETED_USER )
+ if ( $data->log_deleted & LogPage::DELETED_USER ) {
$data->user_name = wfMessage( 'rev-deleted-user' )->escaped();
- if ( $data->log_deleted & LogPage::DELETED_COMMENT )
+ }
+
+ if ( $data->log_deleted & LogPage::DELETED_COMMENT ) {
$data->log_comment = wfMessage( 'rev-deleted-comment' )->escaped();
+ }
}
return $data;
}
/**
* Get the rendered text for previewing.
+ * @throws MWException
* @return string
*/
function getPreviewText() {
- global $wgOut, $wgUser, $wgParser, $wgRawHtml, $wgLang;
+ global $wgOut, $wgUser, $wgRawHtml, $wgLang;
wfProfileIn( __METHOD__ );
@@ -2600,82 +2996,96 @@ HTML
return $parsedNote;
}
- if ( $this->mTriedSave && !$this->mTokenOk ) {
- if ( $this->mTokenOkExceptSuffix ) {
- $note = wfMessage( 'token_suffix_mismatch' )->plain();
- } else {
- $note = wfMessage( 'session_fail_preview' )->plain();
- }
- } elseif ( $this->incompleteForm ) {
- $note = wfMessage( 'edit_form_incomplete' )->plain();
- } else {
- $note = wfMessage( 'previewnote' )->plain() .
- ' [[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' . wfMessage( 'continue-editing' )->text() . ']]';
- }
+ $note = '';
- $parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() );
+ try {
+ $content = $this->toEditContent( $this->textbox1 );
- $parserOptions->setEditSection( false );
- $parserOptions->setIsPreview( true );
- $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
+ $previewHTML = '';
+ if ( !wfRunHooks( 'AlternateEditPreview', array( $this, &$content, &$previewHTML, &$this->mParserOutput ) ) ) {
+ wfProfileOut( __METHOD__ );
+ return $previewHTML;
+ }
+
+ if ( $this->mTriedSave && !$this->mTokenOk ) {
+ if ( $this->mTokenOkExceptSuffix ) {
+ $note = wfMessage( 'token_suffix_mismatch' )->plain();
- # don't parse non-wikitext pages, show message about preview
- if ( $this->mTitle->isCssJsSubpage() || !$this->mTitle->isWikitextPage() ) {
- if ( $this->mTitle->isCssJsSubpage() ) {
- $level = 'user';
- } elseif ( $this->mTitle->isCssOrJsPage() ) {
- $level = 'site';
- } else {
- $level = false;
- }
-
- # Used messages to make sure grep find them:
- # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
- $class = 'mw-code';
- if ( $level ) {
- if ( preg_match( "/\\.css$/", $this->mTitle->getText() ) ) {
- $previewtext = "<div id='mw-{$level}csspreview'>\n" . wfMessage( "{$level}csspreview" )->text() . "\n</div>";
- $class .= " mw-css";
- } elseif ( preg_match( "/\\.js$/", $this->mTitle->getText() ) ) {
- $previewtext = "<div id='mw-{$level}jspreview'>\n" . wfMessage( "{$level}jspreview" )->text() . "\n</div>";
- $class .= " mw-js";
} else {
- throw new MWException( 'A CSS/JS (sub)page but which is not css nor js!' );
+ $note = wfMessage( 'session_fail_preview' )->plain();
}
- $parserOutput = $wgParser->parse( $previewtext, $this->mTitle, $parserOptions );
- $previewHTML = $parserOutput->getText();
+ } elseif ( $this->incompleteForm ) {
+ $note = wfMessage( 'edit_form_incomplete' )->plain();
} else {
- $previewHTML = '';
+ $note = wfMessage( 'previewnote' )->plain() .
+ ' [[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' . wfMessage( 'continue-editing' )->text() . ']]';
}
- $previewHTML .= "<pre class=\"$class\" dir=\"ltr\">\n" . htmlspecialchars( $this->textbox1 ) . "\n</pre>\n";
- } else {
- $toparse = $this->textbox1;
+ $parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() );
+ $parserOptions->setEditSection( false );
+ $parserOptions->setIsPreview( true );
+ $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
- # If we're adding a comment, we need to show the
- # summary as the headline
- if ( $this->section == "new" && $this->summary != "" ) {
- $toparse = wfMessage( 'newsectionheaderdefaultlevel', $this->summary )->inContentLanguage()->text() . "\n\n" . $toparse;
- }
+ # don't parse non-wikitext pages, show message about preview
+ if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
+ if( $this->mTitle->isCssJsSubpage() ) {
+ $level = 'user';
+ } elseif( $this->mTitle->isCssOrJsPage() ) {
+ $level = 'site';
+ } else {
+ $level = false;
+ }
- wfRunHooks( 'EditPageGetPreviewText', array( $this, &$toparse ) );
+ if ( $content->getModel() == CONTENT_MODEL_CSS ) {
+ $format = 'css';
+ } elseif ( $content->getModel() == CONTENT_MODEL_JAVASCRIPT ) {
+ $format = 'js';
+ } else {
+ $format = false;
+ }
- $toparse = $wgParser->preSaveTransform( $toparse, $this->mTitle, $wgUser, $parserOptions );
- $parserOutput = $wgParser->parse( $toparse, $this->mTitle, $parserOptions );
+ # Used messages to make sure grep find them:
+ # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
+ if( $level && $format ) {
+ $note = "<div id='mw-{$level}{$format}preview'>" . wfMessage( "{$level}{$format}preview" )->text() . "</div>";
+ }
+ }
- $rt = Title::newFromRedirectArray( $this->textbox1 );
+ $rt = $content->getRedirectChain();
if ( $rt ) {
$previewHTML = $this->mArticle->viewRedirect( $rt, false );
} else {
- $previewHTML = $parserOutput->getText();
- }
- $this->mParserOutput = $parserOutput;
- $wgOut->addParserOutputNoText( $parserOutput );
+ # If we're adding a comment, we need to show the
+ # summary as the headline
+ if ( $this->section === "new" && $this->summary !== "" ) {
+ $content = $content->addSectionHeader( $this->summary );
+ }
+
+ $hook_args = array( $this, &$content );
+ ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args );
+ wfRunHooks( 'EditPageGetPreviewContent', $hook_args );
+
+ $parserOptions->enableLimitReport();
+
+ # For CSS/JS pages, we should have called the ShowRawCssJs hook here.
+ # But it's now deprecated, so never mind
- if ( count( $parserOutput->getWarnings() ) ) {
- $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
+ $content = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
+ $parserOutput = $content->getParserOutput( $this->getArticle()->getTitle(), null, $parserOptions );
+
+ $previewHTML = $parserOutput->getText();
+ $this->mParserOutput = $parserOutput;
+ $wgOut->addParserOutputNoText( $parserOutput );
+
+ if ( count( $parserOutput->getWarnings() ) ) {
+ $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
+ }
}
+ } catch ( MWContentSerializationException $ex ) {
+ $m = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
+ $note .= "\n\n" . $m->parse();
+ $previewHTML = '';
}
if ( $this->isConflict ) {
@@ -2887,8 +3297,8 @@ HTML
* Returns an array of html code of the following checkboxes:
* minor and watch
*
- * @param $tabindex int Current tabindex
- * @param $checked Array of checkbox => bool, where bool indicates the checked
+ * @param int $tabindex Current tabindex
+ * @param array $checked of checkbox => bool, where bool indicates the checked
* status of the checkbox
*
* @return array
@@ -2938,7 +3348,7 @@ HTML
* Returns an array of html code of the following buttons:
* save, diff, preview and live
*
- * @param $tabindex int Current tabindex
+ * @param int $tabindex Current tabindex
*
* @return array
*/
@@ -3067,7 +3477,7 @@ HTML
/**
* Produce the stock "your edit contains spam" page
*
- * @param $match string Text which triggered one or more filters
+ * @param string|bool $match Text which triggered one or more filters
* @deprecated since 1.17 Use method spamPageWithContent() instead
*/
static function spamPage( $match = false ) {
@@ -3096,7 +3506,7 @@ HTML
global $wgOut, $wgLang;
$this->textbox2 = $this->textbox1;
- if( is_array( $match ) ){
+ if( is_array( $match ) ) {
$match = $wgLang->listToText( $match );
}
$wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) );
@@ -3210,7 +3620,7 @@ HTML
* @private
*/
function makesafe( $invalue ) {
- // Armor existing references for reversability.
+ // Armor existing references for reversibility.
$invalue = strtr( $invalue, array( "&#x" => "&#x0" ) );
$bytesleft = 0;
@@ -3262,7 +3672,7 @@ HTML
$i++;
} while ( ctype_xdigit( $invalue[$i] ) && ( $i < strlen( $invalue ) ) );
- // Do some sanity checks. These aren't needed for reversability,
+ // Do some sanity checks. These aren't needed for reversibility,
// but should help keep the breakage down if the editor
// breaks one of the entities whilst editing.
if ( ( substr( $invalue, $i, 1 ) == ";" ) and ( strlen( $hexstring ) <= 6 ) ) {
@@ -3275,7 +3685,7 @@ HTML
$result .= substr( $invalue, $i, 1 );
}
}
- // reverse the transform that we made for reversability reasons.
+ // reverse the transform that we made for reversibility reasons.
return strtr( $result, array( "&#x0" => "&#x" ) );
}
}
diff --git a/includes/Exception.php b/includes/Exception.php
index 714f73e8..0bd7a2a7 100644
--- a/includes/Exception.php
+++ b/includes/Exception.php
@@ -64,8 +64,8 @@ class MWException extends Exception {
/**
* Run hook to allow extensions to modify the text of the exception
*
- * @param $name string: class name of the exception
- * @param $args array: arguments to pass to the callback functions
+ * @param string $name class name of the exception
+ * @param array $args arguments to pass to the callback functions
* @return string|null string to output or null if any hook has been called
*/
function runHooks( $name, $args = array() ) {
@@ -83,7 +83,7 @@ class MWException extends Exception {
$callargs = array_merge( array( $this ), $args );
foreach ( $hooks as $hook ) {
- if ( is_string( $hook ) || ( is_array( $hook ) && count( $hook ) >= 2 && is_string( $hook[0] ) ) ) { // 'function' or array( 'class', hook' )
+ if ( is_string( $hook ) || ( is_array( $hook ) && count( $hook ) >= 2 && is_string( $hook[0] ) ) ) { // 'function' or array( 'class', hook' )
$result = call_user_func_array( $hook, $callargs );
} else {
$result = null;
@@ -99,8 +99,8 @@ class MWException extends Exception {
/**
* Get a message from i18n
*
- * @param $key string: message name
- * @param $fallback string: default message if the message cache can't be
+ * @param string $key message name
+ * @param string $fallback default message if the message cache can't be
* called by the exception
* The function also has other parameters that are arguments for the message
* @return string message with arguments replaced
@@ -171,7 +171,7 @@ class MWException extends Exception {
/**
* Get a random ID for this error.
- * This allows to link the exception to its correspoding log entry when
+ * This allows to link the exception to its corresponding log entry when
* $wgShowExceptionDetails is set to false.
*
* @return string
@@ -262,7 +262,7 @@ class MWException extends Exception {
if ( defined( 'MW_API' ) ) {
// Unhandled API exception, we can't be sure that format printer is alive
header( 'MediaWiki-API-Error: internal_api_error_' . get_class( $this ) );
- wfHttpError(500, 'Internal Server Error', $this->getText() );
+ wfHttpError( 500, 'Internal Server Error', $this->getText() );
} elseif ( self::isCommandLine() ) {
MWExceptionHandler::printError( $this->getText() );
} else {
@@ -320,16 +320,16 @@ class ErrorPageError extends MWException {
/**
* Note: these arguments are keys into wfMessage(), not text!
*
- * @param $title string|Message Message key (string) for page title, or a Message object
- * @param $msg string|Message Message key (string) for error text, or a Message object
- * @param $params array with parameters to wfMessage()
+ * @param string|Message $title Message key (string) for page title, or a Message object
+ * @param string|Message $msg Message key (string) for error text, or a Message object
+ * @param array $params with parameters to wfMessage()
*/
function __construct( $title, $msg, $params = null ) {
$this->title = $title;
$this->msg = $msg;
$this->params = $params;
- if( $msg instanceof Message ){
+ if( $msg instanceof Message ) {
parent::__construct( $msg );
} else {
parent::__construct( wfMessage( $msg )->text() );
@@ -354,8 +354,8 @@ class ErrorPageError extends MWException {
*/
class BadTitleError extends ErrorPageError {
/**
- * @param $msg string|Message A message key (default: 'badtitletext')
- * @param $params Array parameter to wfMessage()
+ * @param string|Message $msg A message key (default: 'badtitletext')
+ * @param array $params parameter to wfMessage()
*/
function __construct( $msg = 'badtitletext', $params = null ) {
parent::__construct( 'badtitle', $msg, $params );
@@ -423,7 +423,7 @@ class PermissionsError extends ErrorPageError {
* @ingroup Exception
*/
class ReadOnlyError extends ErrorPageError {
- public function __construct(){
+ public function __construct() {
parent::__construct(
'readonly',
'readonlytext',
@@ -439,14 +439,14 @@ class ReadOnlyError extends ErrorPageError {
* @ingroup Exception
*/
class ThrottledError extends ErrorPageError {
- public function __construct(){
+ public function __construct() {
parent::__construct(
'actionthrottled',
'actionthrottledtext'
);
}
- public function report(){
+ public function report() {
global $wgOut;
$wgOut->setStatusCode( 503 );
parent::report();
@@ -460,7 +460,7 @@ class ThrottledError extends ErrorPageError {
* @ingroup Exception
*/
class UserBlockedError extends ErrorPageError {
- public function __construct( Block $block ){
+ public function __construct( Block $block ) {
global $wgLang, $wgRequest;
$blocker = $block->getBlocker();
@@ -500,25 +500,24 @@ class UserBlockedError extends ErrorPageError {
/**
* Shows a generic "user is not logged in" error page.
*
- * This is essentially an ErrorPageError exception which by default use the
+ * This is essentially an ErrorPageError exception which by default uses the
* 'exception-nologin' as a title and 'exception-nologin-text' for the message.
* @see bug 37627
* @since 1.20
*
* @par Example:
* @code
- * if( $user->isAnon ) {
+ * if( $user->isAnon() ) {
* throw new UserNotLoggedIn();
* }
* @endcode
*
- * Please note the parameters are mixed up compared to ErrorPageError, this
- * is done to be able to simply specify a reason whitout overriding the default
- * title.
+ * Note the parameter order differs from ErrorPageError, this allows you to
+ * simply specify a reason without overriding the default title.
*
* @par Example:
* @code
- * if( $user->isAnon ) {
+ * if( $user->isAnon() ) {
* throw new UserNotLoggedIn( 'action-require-loggedin' );
* }
* @endcode
@@ -533,11 +532,11 @@ class UserNotLoggedIn extends ErrorPageError {
* @param $titleMsg A message key to set the page title.
* Optional, default: 'exception-nologin'
* @param $params Parameters to wfMessage().
- * Optiona, default: null
+ * Optional, default: null
*/
public function __construct(
$reasonMsg = 'exception-nologin-text',
- $titleMsg = 'exception-nologin',
+ $titleMsg = 'exception-nologin',
$params = null
) {
parent::__construct( $titleMsg, $reasonMsg, $params );
@@ -558,24 +557,48 @@ class HttpError extends MWException {
* Constructor
*
* @param $httpCode Integer: HTTP status code to send to the client
- * @param $content String|Message: content of the message
- * @param $header String|Message: content of the header (\<title\> and \<h1\>)
+ * @param string|Message $content content of the message
+ * @param string|Message $header content of the header (\<title\> and \<h1\>)
*/
- public function __construct( $httpCode, $content, $header = null ){
+ public function __construct( $httpCode, $content, $header = null ) {
parent::__construct( $content );
$this->httpCode = (int)$httpCode;
$this->header = $header;
$this->content = $content;
}
+ /**
+ * Returns the HTTP status code supplied to the constructor.
+ *
+ * @return int
+ */
+ public function getStatusCode() {
+ return $this->httpCode;
+ }
+
+ /**
+ * Report the HTTP error.
+ * Sends the appropriate HTTP status code and outputs an
+ * HTML page with an error message.
+ */
public function report() {
$httpMessage = HttpStatus::getMessage( $this->httpCode );
- header( "Status: {$this->httpCode} {$httpMessage}" );
+ header( "Status: {$this->httpCode} {$httpMessage}", true, $this->httpCode );
header( 'Content-type: text/html; charset=utf-8' );
+ print $this->getHTML();
+ }
+
+ /**
+ * Returns HTML for reporting the HTTP error.
+ * This will be a minimal but complete HTML document.
+ *
+ * @return string HTML
+ */
+ public function getHTML() {
if ( $this->header === null ) {
- $header = $httpMessage;
+ $header = HttpStatus::getMessage( $this->httpCode );
} elseif ( $this->header instanceof Message ) {
$header = $this->header->escaped();
} else {
@@ -588,7 +611,7 @@ class HttpError extends MWException {
$content = htmlspecialchars( $this->content );
}
- print "<!DOCTYPE html>\n".
+ return "<!DOCTYPE html>\n".
"<html><head><title>$header</title></head>\n" .
"<body><h1>$header</h1><p>$content</p></body></html>\n";
}
@@ -661,7 +684,7 @@ class MWExceptionHandler {
* Print a message, if possible to STDERR.
* Use this in command line mode only (see isCommandLine)
*
- * @param $message string Failure text
+ * @param string $message Failure text
*/
public static function printError( $message ) {
# NOTE: STDERR may not be available, especially if php-cgi is used from the command line (bug #15602).
diff --git a/includes/Export.php b/includes/Export.php
index f01fb237..d8cc0242 100644
--- a/includes/Export.php
+++ b/includes/Export.php
@@ -31,8 +31,8 @@
* @ingroup SpecialPage Dump
*/
class WikiExporter {
- var $list_authors = false ; # Return distinct author list (when not returning full history)
- var $author_list = "" ;
+ var $list_authors = false; # Return distinct author list (when not returning full history)
+ var $author_list = "";
var $dumpUploads = false;
var $dumpUploadFileContents = false;
@@ -63,7 +63,7 @@ class WikiExporter {
* @return string
*/
public static function schemaVersion() {
- return "0.7";
+ return "0.8";
}
/**
@@ -80,17 +80,17 @@ class WikiExporter {
* offset: non-inclusive offset at which to start the query
* limit: maximum number of rows to return
* dir: "asc" or "desc" timestamp order
- * @param $buffer Int: one of WikiExporter::BUFFER or WikiExporter::STREAM
- * @param $text Int: one of WikiExporter::TEXT or WikiExporter::STUB
+ * @param int $buffer one of WikiExporter::BUFFER or WikiExporter::STREAM
+ * @param int $text one of WikiExporter::TEXT or WikiExporter::STUB
*/
- function __construct( &$db, $history = WikiExporter::CURRENT,
+ function __construct( $db, $history = WikiExporter::CURRENT,
$buffer = WikiExporter::BUFFER, $text = WikiExporter::TEXT ) {
- $this->db =& $db;
+ $this->db = $db;
$this->history = $history;
- $this->buffer = $buffer;
- $this->writer = new XmlDumpWriter();
- $this->sink = new DumpOutput();
- $this->text = $text;
+ $this->buffer = $buffer;
+ $this->writer = new XmlDumpWriter();
+ $this->sink = new DumpOutput();
+ $this->text = $text;
}
/**
@@ -126,7 +126,7 @@ class WikiExporter {
/**
* Dumps a series of page and revision records for those pages
* in the database falling within the page_id range given.
- * @param $start Int: inclusive lower limit (this id is included)
+ * @param int $start inclusive lower limit (this id is included)
* @param $end Int: Exclusive upper limit (this id is not included)
* If 0, no upper limit.
*/
@@ -141,7 +141,7 @@ class WikiExporter {
/**
* Dumps a series of page and revision records for those pages
* in the database with revisions falling within the rev_id range given.
- * @param $start Int: inclusive lower limit (this id is included)
+ * @param int $start inclusive lower limit (this id is included)
* @param $end Int: Exclusive upper limit (this id is not included)
* If 0, no upper limit.
*/
@@ -226,7 +226,7 @@ class WikiExporter {
foreach ( $res as $row ) {
$this->author_list .= "<contributor>" .
"<username>" .
- htmlentities( $row->rev_user_text ) .
+ htmlentities( $row->rev_user_text ) .
"</username>" .
"<id>" .
$row->rev_user .
@@ -330,7 +330,7 @@ class WikiExporter {
$join['revision'] = array( 'INNER JOIN', 'page_id=rev_page' );
} elseif ( $this->history & WikiExporter::CURRENT ) {
# Latest revision dumps...
- if ( $this->list_authors && $cond != '' ) { // List authors, if so desired
+ if ( $this->list_authors && $cond != '' ) { // List authors, if so desired
$this->do_list_authors( $cond );
}
$join['revision'] = array( 'INNER JOIN', 'page_id=rev_page AND page_latest=rev_id' );
@@ -348,7 +348,7 @@ class WikiExporter {
$join['revision'] = array( 'INNER JOIN', 'page_id=rev_page' );
$opts['ORDER BY'] = array( 'rev_page ASC', 'rev_id ASC' );
} else {
- # Uknown history specification parameter?
+ # Unknown history specification parameter?
wfProfileOut( __METHOD__ );
throw new MWException( __METHOD__ . " given invalid history dump type." );
}
@@ -427,10 +427,10 @@ class WikiExporter {
protected function outputPageStream( $resultset ) {
$last = null;
foreach ( $resultset as $row ) {
- if ( is_null( $last ) ||
+ if ( $last === null ||
$last->page_namespace != $row->page_namespace ||
- $last->page_title != $row->page_title ) {
- if ( isset( $last ) ) {
+ $last->page_title != $row->page_title ) {
+ if ( $last !== null ) {
$output = '';
if ( $this->dumpUploads ) {
$output .= $this->writer->writeUploads( $last, $this->dumpUploadFileContents );
@@ -445,7 +445,7 @@ class WikiExporter {
$output = $this->writer->writeRevision( $row );
$this->sink->writeRevision( $row, $output );
}
- if ( isset( $last ) ) {
+ if ( $last !== null ) {
$output = '';
if ( $this->dumpUploads ) {
$output .= $this->writer->writeUploads( $last, $this->dumpUploadFileContents );
@@ -498,7 +498,7 @@ class XmlDumpWriter {
'xmlns' => "http://www.mediawiki.org/xml/export-$ver/",
'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
'xsi:schemaLocation' => "http://www.mediawiki.org/xml/export-$ver/ " .
- "http://www.mediawiki.org/xml/export-$ver.xsd",
+ "http://www.mediawiki.org/xml/export-$ver.xsd", #TODO: how do we get a new version up there?
'version' => $ver,
'xml:lang' => $wgLanguageCode ),
null ) .
@@ -634,37 +634,31 @@ class XmlDumpWriter {
function writeRevision( $row ) {
wfProfileIn( __METHOD__ );
- $out = " <revision>\n";
+ $out = " <revision>\n";
$out .= " " . Xml::element( 'id', null, strval( $row->rev_id ) ) . "\n";
- if( $row->rev_parent_id ) {
+ if( isset( $row->rev_parent_id ) && $row->rev_parent_id ) {
$out .= " " . Xml::element( 'parentid', null, strval( $row->rev_parent_id ) ) . "\n";
}
$out .= $this->writeTimestamp( $row->rev_timestamp );
- if ( $row->rev_deleted & Revision::DELETED_USER ) {
+ if ( isset( $row->rev_deleted ) && ( $row->rev_deleted & Revision::DELETED_USER ) ) {
$out .= " " . Xml::element( 'contributor', array( 'deleted' => 'deleted' ) ) . "\n";
} else {
$out .= $this->writeContributor( $row->rev_user, $row->rev_user_text );
}
- if ( $row->rev_minor_edit ) {
- $out .= " <minor/>\n";
+ if ( isset( $row->rev_minor_edit ) && $row->rev_minor_edit ) {
+ $out .= " <minor/>\n";
}
- if ( $row->rev_deleted & Revision::DELETED_COMMENT ) {
+ if ( isset( $row->rev_deleted ) && ( $row->rev_deleted & Revision::DELETED_COMMENT ) ) {
$out .= " " . Xml::element( 'comment', array( 'deleted' => 'deleted' ) ) . "\n";
} elseif ( $row->rev_comment != '' ) {
$out .= " " . Xml::elementClean( 'comment', array(), strval( $row->rev_comment ) ) . "\n";
}
- if ( $row->rev_sha1 && !( $row->rev_deleted & Revision::DELETED_TEXT ) ) {
- $out .= " " . Xml::element('sha1', null, strval( $row->rev_sha1 ) ) . "\n";
- } else {
- $out .= " <sha1/>\n";
- }
-
$text = '';
- if ( $row->rev_deleted & Revision::DELETED_TEXT ) {
+ if ( isset( $row->rev_deleted ) && ( $row->rev_deleted & Revision::DELETED_TEXT ) ) {
$out .= " " . Xml::element( 'text', array( 'deleted' => 'deleted' ) ) . "\n";
} elseif ( isset( $row->old_text ) ) {
// Raw text from the database may have invalid chars
@@ -679,6 +673,34 @@ class XmlDumpWriter {
"" ) . "\n";
}
+ if ( isset( $row->rev_sha1 ) && $row->rev_sha1 && !( $row->rev_deleted & Revision::DELETED_TEXT ) ) {
+ $out .= " " . Xml::element( 'sha1', null, strval( $row->rev_sha1 ) ) . "\n";
+ } else {
+ $out .= " <sha1/>\n";
+ }
+
+ if ( isset( $row->rev_content_model ) && !is_null( $row->rev_content_model ) ) {
+ $content_model = strval( $row->rev_content_model );
+ } else {
+ // probably using $wgContentHandlerUseDB = false;
+ // @todo: test!
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+ $content_model = ContentHandler::getDefaultModelFor( $title );
+ }
+
+ $out .= " " . Xml::element( 'model', null, strval( $content_model ) ) . "\n";
+
+ if ( isset( $row->rev_content_format ) && !is_null( $row->rev_content_format ) ) {
+ $content_format = strval( $row->rev_content_format );
+ } else {
+ // probably using $wgContentHandlerUseDB = false;
+ // @todo: test!
+ $content_handler = ContentHandler::getForModelID( $content_model );
+ $content_format = $content_handler->getDefaultFormat();
+ }
+
+ $out .= " " . Xml::element( 'format', null, strval( $content_format ) ) . "\n";
+
wfRunHooks( 'XmlDumpWriterWriteRevision', array( &$this, &$out, $row, $text ) );
$out .= " </revision>\n";
@@ -698,7 +720,7 @@ class XmlDumpWriter {
function writeLogItem( $row ) {
wfProfileIn( __METHOD__ );
- $out = " <logitem>\n";
+ $out = " <logitem>\n";
$out .= " " . Xml::element( 'id', null, strval( $row->log_id ) ) . "\n";
$out .= $this->writeTimestamp( $row->log_timestamp, " " );
@@ -736,7 +758,7 @@ class XmlDumpWriter {
/**
* @param $timestamp string
- * @param $indent string Default to six spaces
+ * @param string $indent Default to six spaces
* @return string
*/
function writeTimestamp( $timestamp, $indent = " " ) {
@@ -747,7 +769,7 @@ class XmlDumpWriter {
/**
* @param $id
* @param $text string
- * @param $indent string Default to six spaces
+ * @param string $indent Default to six spaces
* @return string
*/
function writeContributor( $id, $text, $indent = " " ) {
@@ -849,9 +871,8 @@ class XmlDumpWriter {
}
}
-
/**
- * Base class for output stream; prints to stdout or buffer or whereever.
+ * Base class for output stream; prints to stdout or buffer or wherever.
* @ingroup Dump
*/
class DumpOutput {
@@ -918,7 +939,6 @@ class DumpOutput {
* @param $newname mixed File name. May be a string or an array with one element
*/
function closeRenameAndReopen( $newname ) {
- return;
}
/**
@@ -926,10 +946,9 @@ class DumpOutput {
* Use this for the last piece of a file written out
* at specified checkpoints (e.g. every n hours).
* @param $newname mixed File name. May be a string or an array with one element
- * @param $open bool If true, a new file with the old filename will be opened again for writing (default: false)
+ * @param bool $open If true, a new file with the old filename will be opened again for writing (default: false)
*/
function closeAndRename( $newname, $open = false ) {
- return;
}
/**
@@ -938,7 +957,7 @@ class DumpOutput {
* @return null
*/
function getFilenames() {
- return NULL;
+ return null;
}
}
@@ -987,7 +1006,7 @@ class DumpFileOutput extends DumpOutput {
* @throws MWException
*/
function renameOrException( $newname ) {
- if (! rename( $this->filename, $newname ) ) {
+ if ( !rename( $this->filename, $newname ) ) {
throw new MWException( __METHOD__ . ": rename of file {$this->filename} to $newname failed\n" );
}
}
@@ -1050,7 +1069,7 @@ class DumpPipeOutput extends DumpFileOutput {
*/
function __construct( $command, $file = null ) {
if ( !is_null( $file ) ) {
- $command .= " > " . wfEscapeShellArg( $file );
+ $command .= " > " . wfEscapeShellArg( $file );
}
$this->startCommand( $command );
@@ -1106,7 +1125,7 @@ class DumpPipeOutput extends DumpFileOutput {
$this->renameOrException( $newname );
if ( $open ) {
$command = $this->command;
- $command .= " > " . wfEscapeShellArg( $this->filename );
+ $command .= " > " . wfEscapeShellArg( $this->filename );
$this->startCommand( $command );
}
}
@@ -1325,6 +1344,7 @@ class DumpNamespaceFilter extends DumpFilter {
/**
* @param $sink DumpOutput
* @param $param
+ * @throws MWException
*/
function __construct( &$sink, $param ) {
parent::__construct( $sink );
@@ -1338,7 +1358,7 @@ class DumpNamespaceFilter extends DumpFilter {
"NS_PROJECT_TALK" => NS_PROJECT_TALK,
"NS_FILE" => NS_FILE,
"NS_FILE_TALK" => NS_FILE_TALK,
- "NS_IMAGE" => NS_IMAGE, // NS_IMAGE is an alias for NS_FILE
+ "NS_IMAGE" => NS_IMAGE, // NS_IMAGE is an alias for NS_FILE
"NS_IMAGE_TALK" => NS_IMAGE_TALK,
"NS_MEDIAWIKI" => NS_MEDIAWIKI,
"NS_MEDIAWIKI_TALK" => NS_MEDIAWIKI_TALK,
@@ -1378,7 +1398,6 @@ class DumpNamespaceFilter extends DumpFilter {
}
}
-
/**
* Dump output filter to include only the last revision in each page sequence.
* @ingroup Dump
@@ -1423,7 +1442,7 @@ class DumpLatestFilter extends DumpFilter {
}
/**
- * Base class for output stream; prints to stdout or buffer or whereever.
+ * Base class for output stream; prints to stdout or buffer or wherever.
* @ingroup Dump
*/
class DumpMultiWriter {
@@ -1506,7 +1525,7 @@ class DumpMultiWriter {
function getFilenames() {
$filenames = array();
for ( $i = 0; $i < $this->count; $i++ ) {
- $filenames[] = $this->sinks[$i]->getFilenames();
+ $filenames[] = $this->sinks[$i]->getFilenames();
}
return $filenames;
}
diff --git a/includes/ExternalEdit.php b/includes/ExternalEdit.php
index 34683253..11e94230 100644
--- a/includes/ExternalEdit.php
+++ b/includes/ExternalEdit.php
@@ -40,7 +40,7 @@ class ExternalEdit extends ContextSource {
* Check whether external edit or diff should be used.
*
* @param $context IContextSource context to use
- * @param $type String can be either 'edit' or 'diff'
+ * @param string $type can be either 'edit' or 'diff'
* @return Bool
*/
public static function useExternalEngine( IContextSource $context, $type ) {
@@ -87,7 +87,7 @@ class ExternalEdit extends ContextSource {
'URL' => $image->getCanonicalURL()
)
);
- } else{
+ } else {
$urls = array();
}
} else {
diff --git a/includes/ExternalStore.php b/includes/ExternalStore.php
deleted file mode 100644
index 61d4ef7c..00000000
--- a/includes/ExternalStore.php
+++ /dev/null
@@ -1,172 +0,0 @@
-<?php
-/**
- * Data storage in external repositories.
- *
- * 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
- */
-
-/**
- * @defgroup ExternalStorage ExternalStorage
- */
-
-/**
- * Constructor class for data kept in external repositories
- *
- * External repositories might be populated by maintenance/async
- * scripts, thus partial moving of data may be possible, as well
- * as possibility to have any storage format (i.e. for archives)
- *
- * @ingroup ExternalStorage
- */
-class ExternalStore {
- var $mParams;
-
- function __construct( $params = array() ) {
- $this->mParams = $params;
- }
-
- /**
- * Fetch data from given URL
- *
- * @param $url String: The URL of the text to get
- * @param $params Array: associative array of parameters for the ExternalStore object.
- * @return string|bool The text stored or false on error
- */
- static function fetchFromURL( $url, $params = array() ) {
- global $wgExternalStores;
-
- if( !$wgExternalStores )
- return false;
-
- $parts = explode( '://', $url, 2 );
-
- if ( count( $parts ) != 2 ) {
- return false;
- }
-
- list( $proto, $path ) = $parts;
-
- if ( $path == '' ) { // Bad URL
- return false;
- }
-
- $store = self::getStoreObject( $proto, $params );
- if ( $store === false )
- return false;
- return $store->fetchFromURL( $url );
- }
-
- /**
- * Get an external store object of the given type, with the given parameters
- *
- * @param $proto String: type of external storage, should be a value in $wgExternalStores
- * @param $params Array: associative array of parameters for the ExternalStore object.
- * @return ExternalStore subclass or false on error
- */
- static function getStoreObject( $proto, $params = array() ) {
- global $wgExternalStores;
- if( !$wgExternalStores )
- return false;
- /* Protocol not enabled */
- if( !in_array( $proto, $wgExternalStores ) )
- return false;
-
- $class = 'ExternalStore' . ucfirst( $proto );
- /* Any custom modules should be added to $wgAutoLoadClasses for on-demand loading */
- if( !MWInit::classExists( $class ) ) {
- return false;
- }
-
- return new $class($params);
- }
-
- /**
- * Store a data item to an external store, identified by a partial URL
- * The protocol part is used to identify the class, the rest is passed to the
- * class itself as a parameter.
- * @param $url
- * @param $data
- * @param $params array
- * @return string|bool The URL of the stored data item, or false on error
- */
- static function insert( $url, $data, $params = array() ) {
- list( $proto, $params ) = explode( '://', $url, 2 );
- $store = self::getStoreObject( $proto, $params );
- if ( $store === false ) {
- return false;
- } else {
- return $store->store( $params, $data );
- }
- }
-
- /**
- * Like insert() above, but does more of the work for us.
- * This function does not need a url param, it builds it by
- * itself. It also fails-over to the next possible clusters.
- *
- * @param $data String
- * @param $storageParams Array: associative array of parameters for the ExternalStore object.
- * @return string The URL of the stored data item, or false on error
- */
- public static function insertToDefault( $data, $storageParams = array() ) {
- global $wgDefaultExternalStore;
- $tryStores = (array)$wgDefaultExternalStore;
- $error = false;
- while ( count( $tryStores ) > 0 ) {
- $index = mt_rand(0, count( $tryStores ) - 1);
- $storeUrl = $tryStores[$index];
- wfDebug( __METHOD__.": trying $storeUrl\n" );
- list( $proto, $params ) = explode( '://', $storeUrl, 2 );
- $store = self::getStoreObject( $proto, $storageParams );
- if ( $store === false ) {
- throw new MWException( "Invalid external storage protocol - $storeUrl" );
- }
- try {
- $url = $store->store( $params, $data ); // Try to save the object
- } catch ( DBConnectionError $error ) {
- $url = false;
- } catch( DBQueryError $error ) {
- $url = false;
- }
- if ( $url ) {
- return $url; // Done!
- } else {
- unset( $tryStores[$index] ); // Don't try this one again!
- $tryStores = array_values( $tryStores ); // Must have consecutive keys
- wfDebugLog( 'ExternalStorage', "Unable to store text to external storage $storeUrl" );
- }
- }
- // All stores failed
- if ( $error ) {
- // Rethrow the last connection error
- throw $error;
- } else {
- throw new MWException( "Unable to store text to external storage" );
- }
- }
-
- /**
- * @param $data
- * @param $wiki
- *
- * @return string
- */
- public static function insertToForeignDefault( $data, $wiki ) {
- return self::insertToDefault( $data, array( 'wiki' => $wiki ) );
- }
-}
diff --git a/includes/ExternalUser.php b/includes/ExternalUser.php
index 9a01deb7..580b9896 100644
--- a/includes/ExternalUser.php
+++ b/includes/ExternalUser.php
@@ -128,7 +128,7 @@ abstract class ExternalUser {
* @param $name string
* @return bool Success?
*/
- protected abstract function initFromName( $name );
+ abstract protected function initFromName( $name );
/**
* Given an id, which was at some previous point in history returned by
@@ -138,7 +138,7 @@ abstract class ExternalUser {
* @param $id string
* @return bool Success?
*/
- protected abstract function initFromId( $id );
+ abstract protected function initFromId( $id );
/**
* Try to magically initialize the user from cookies or similar information
@@ -278,23 +278,23 @@ abstract class ExternalUser {
* This is part of the core code and is not overridable by specific
* plugins. It's in this class only for convenience.
*
- * @param $id int user_id
+ * @param int $id user_id
*/
- public final function linkToLocal( $id ) {
+ final public function linkToLocal( $id ) {
$dbw = wfGetDB( DB_MASTER );
$dbw->replace( 'external_user',
array( 'eu_local_id', 'eu_external_id' ),
array( 'eu_local_id' => $id,
- 'eu_external_id' => $this->getId() ),
+ 'eu_external_id' => $this->getId() ),
__METHOD__ );
}
-
+
/**
* Check whether this external user id is already linked with
* a local user.
* @return Mixed User if the account is linked, Null otherwise.
*/
- public final function getLocalUser(){
+ final public function getLocalUser() {
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow(
'external_user',
@@ -305,5 +305,5 @@ abstract class ExternalUser {
? User::newFromId( $row->eu_local_id )
: null;
}
-
+
}
diff --git a/includes/FakeTitle.php b/includes/FakeTitle.php
index 60f7600d..efa213fb 100644
--- a/includes/FakeTitle.php
+++ b/includes/FakeTitle.php
@@ -114,9 +114,9 @@ class FakeTitle extends Title {
function getParentCategories() { $this->error(); }
function getParentCategoryTree( $children = array() ) { $this->error(); }
function pageCond() { $this->error(); }
- function getPreviousRevisionID( $revId, $flags=0 ) { $this->error(); }
- function getNextRevisionID( $revId, $flags=0 ) { $this->error(); }
- function getFirstRevision( $flags=0 ) { $this->error(); }
+ function getPreviousRevisionID( $revId, $flags = 0 ) { $this->error(); }
+ function getNextRevisionID( $revId, $flags = 0 ) { $this->error(); }
+ function getFirstRevision( $flags = 0 ) { $this->error(); }
function isNewPage() { $this->error(); }
function getEarliestRevTime( $flags = 0 ) { $this->error(); }
function countRevisionsBetween( $old, $new ) { $this->error(); }
diff --git a/includes/Fallback.php b/includes/Fallback.php
index 4b138c11..2e19a095 100644
--- a/includes/Fallback.php
+++ b/includes/Fallback.php
@@ -149,13 +149,12 @@ class Fallback {
return $total;
}
-
/**
* Fallback implementation of mb_strpos, hardcoded to UTF-8.
* @param $haystack String
* @param $needle String
- * @param $offset String: optional start position
- * @param $encoding String: optional encoding; ignored
+ * @param string $offset optional start position
+ * @param string $encoding optional encoding; ignored
* @return int
*/
public static function mb_strpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
@@ -175,8 +174,8 @@ class Fallback {
* Fallback implementation of mb_strrpos, hardcoded to UTF-8.
* @param $haystack String
* @param $needle String
- * @param $offset String: optional start position
- * @param $encoding String: optional encoding; ignored
+ * @param string $offset optional start position
+ * @param string $encoding optional encoding; ignored
* @return int
*/
public static function mb_strrpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
@@ -192,22 +191,4 @@ class Fallback {
return false;
}
}
-
- /**
- * Fallback implementation of stream_resolve_include_path()
- * Native stream_resolve_include_path is available for PHP 5 >= 5.3.2
- * @param $filename String
- * @return String
- */
- public static function stream_resolve_include_path( $filename ) {
- $pathArray = explode( PATH_SEPARATOR, get_include_path() );
- foreach ( $pathArray as $path ) {
- $fullFilename = $path . DIRECTORY_SEPARATOR . $filename;
- if ( file_exists( $fullFilename ) ) {
- return $fullFilename;
- }
- }
- return false;
- }
-
}
diff --git a/includes/Feed.php b/includes/Feed.php
index f9dbf5ba..caf2e571 100644
--- a/includes/Feed.php
+++ b/includes/Feed.php
@@ -52,11 +52,11 @@ class FeedItem {
/**
* Constructor
*
- * @param $title String|Title Item's title
+ * @param string|Title $title Item's title
* @param $description String
- * @param $url String: URL uniquely designating the item.
- * @param $date String: Item's date
- * @param $author String: Author's user name
+ * @param string $url URL uniquely designating the item.
+ * @param string $date Item's date
+ * @param string $author Author's user name
* @param $comments String
*/
function __construct( $title, $description, $url, $date = '', $author = '', $comments = '' ) {
@@ -72,7 +72,7 @@ class FeedItem {
/**
* Encode $string so that it can be safely embedded in a XML document
*
- * @param $string String: string to encode
+ * @param string $string string to encode
* @return String
*/
public function xmlEncode( $string ) {
@@ -95,7 +95,7 @@ class FeedItem {
/**
* set the unique id of an item
*
- * @param $uniqueId String: unique id for the item
+ * @param string $uniqueId unique id for the item
* @param $rssIsPermalink Boolean: set to true if the guid (unique id) is a permalink (RSS feeds only)
*/
public function setUniqueId( $uniqueId, $rssIsPermalink = false ) {
@@ -170,7 +170,7 @@ class FeedItem {
/**
* Quickie hack... strip out wikilinks to more legible form from the comment.
*
- * @param $text String: wikitext
+ * @param string $text wikitext
* @return String
*/
public static function stripComment( $text ) {
@@ -243,9 +243,9 @@ abstract class ChannelFeed extends FeedItem {
*/
function contentType() {
global $wgRequest;
- $ctype = $wgRequest->getVal('ctype','application/xml');
- $allowedctypes = array('application/xml','text/xml','application/rss+xml','application/atom+xml');
- return (in_array($ctype, $allowedctypes) ? $ctype : 'application/xml');
+ $ctype = $wgRequest->getVal( 'ctype', 'application/xml' );
+ $allowedctypes = array( 'application/xml', 'text/xml', 'application/rss+xml', 'application/atom+xml' );
+ return (in_array( $ctype, $allowedctypes ) ? $ctype : 'application/xml');
}
/**
@@ -282,7 +282,7 @@ class RSSFeed extends ChannelFeed {
}
/**
- * Ouput an RSS 2.0 header
+ * Output an RSS 2.0 header
*/
function outHeader() {
global $wgVersion;
@@ -318,7 +318,7 @@ class RSSFeed extends ChannelFeed {
}
/**
- * Ouput an RSS 2.0 footer
+ * Output an RSS 2.0 footer
*/
function outFooter() {
?>
@@ -362,7 +362,7 @@ class AtomFeed extends ChannelFeed {
}
/**
- * Atom 1.0 requires a unique, opaque IRI as a unique indentifier
+ * Atom 1.0 requires a unique, opaque IRI as a unique identifier
* for every feed we create. For now just use the URL, but who
* can tell if that's right? If we put options on the feed, do we
* have to change the id? Maybe? Maybe not.
@@ -409,7 +409,7 @@ class AtomFeed extends ChannelFeed {
}
/**
- * Outputs the footer for Atom 1.0 feed (basicly '\</feed\>').
+ * Outputs the footer for Atom 1.0 feed (basically '\</feed\>').
*/
function outFooter() {?>
</feed><?php
diff --git a/includes/FeedUtils.php b/includes/FeedUtils.php
index 11b2675d..adc1f781 100644
--- a/includes/FeedUtils.php
+++ b/includes/FeedUtils.php
@@ -33,13 +33,13 @@ class FeedUtils {
* If the feed should be purged; $timekey and $key will be removed from
* $messageMemc
*
- * @param $timekey String: cache key of the timestamp of the last item
- * @param $key String: cache key of feed's content
+ * @param string $timekey cache key of the timestamp of the last item
+ * @param string $key cache key of feed's content
*/
public static function checkPurge( $timekey, $key ) {
global $wgRequest, $wgUser, $messageMemc;
$purge = $wgRequest->getVal( 'action' ) === 'purge';
- if ( $purge && $wgUser->isAllowed('purge') ) {
+ if ( $purge && $wgUser->isAllowed( 'purge' ) ) {
$messageMemc->delete( $timekey );
$messageMemc->delete( $key );
}
@@ -48,7 +48,7 @@ class FeedUtils {
/**
* Check whether feeds can be used and that $type is a valid feed type
*
- * @param $type String: feed type, as requested by the user
+ * @param string $type feed type, as requested by the user
* @return Boolean
*/
public static function checkFeedOutput( $type ) {
@@ -85,9 +85,9 @@ class FeedUtils {
$row->rc_last_oldid, $row->rc_this_oldid,
$timestamp,
($row->rc_deleted & Revision::DELETED_COMMENT)
- ? wfMessage('rev-deleted-comment')->escaped()
+ ? wfMessage( 'rev-deleted-comment' )->escaped()
: $row->rc_comment,
- $actiontext
+ $actiontext
);
}
@@ -98,15 +98,15 @@ class FeedUtils {
* @param $oldid Integer: old revision's id
* @param $newid Integer: new revision's id
* @param $timestamp Integer: new revision's timestamp
- * @param $comment String: new revision's comment
- * @param $actiontext String: text of the action; in case of log event
+ * @param string $comment new revision's comment
+ * @param string $actiontext text of the action; in case of log event
* @return String
*/
- public static function formatDiffRow( $title, $oldid, $newid, $timestamp, $comment, $actiontext='' ) {
+ public static function formatDiffRow( $title, $oldid, $newid, $timestamp, $comment, $actiontext = '' ) {
global $wgFeedDiffCutoff, $wgLang;
wfProfileIn( __METHOD__ );
- # log enties
+ // log entries
$completeText = '<p>' . implode( ' ',
array_filter(
array(
@@ -115,7 +115,7 @@ class FeedUtils {
// NOTE: Check permissions for anonymous users, not current user.
// No "privileged" version should end up in the cache.
- // Most feed readers will not log in anway.
+ // Most feed readers will not log in anyway.
$anon = new User();
$accErrors = $title->getUserPermissionsErrors( 'read', $anon, true );
@@ -138,13 +138,23 @@ class FeedUtils {
$diffText = '';
// Don't bother generating the diff if we won't be able to show it
if ( $wgFeedDiffCutoff > 0 ) {
- $de = new DifferenceEngine( $title, $oldid, $newid );
- $diffText = $de->getDiff(
- wfMessage( 'previousrevision' )->text(), // hack
- wfMessage( 'revisionasof',
- $wgLang->timeanddate( $timestamp ),
- $wgLang->date( $timestamp ),
- $wgLang->time( $timestamp ) )->text() );
+ $rev = Revision::newFromId( $oldid );
+
+ if ( !$rev ) {
+ $diffText = false;
+ } else {
+ $context = clone RequestContext::getMain();
+ $context->setTitle( $title );
+
+ $contentHandler = $rev->getContentHandler();
+ $de = $contentHandler->createDifferenceEngine( $context, $oldid, $newid );
+ $diffText = $de->getDiff(
+ wfMessage( 'previousrevision' )->text(), // hack
+ wfMessage( 'revisionasof',
+ $wgLang->timeanddate( $timestamp ),
+ $wgLang->date( $timestamp ),
+ $wgLang->time( $timestamp ) )->text() );
+ }
}
if ( $wgFeedDiffCutoff <= 0 || ( strlen( $diffText ) > $wgFeedDiffCutoff ) ) {
@@ -162,16 +172,36 @@ class FeedUtils {
} else {
$rev = Revision::newFromId( $newid );
if( $wgFeedDiffCutoff <= 0 || is_null( $rev ) ) {
- $newtext = '';
+ $newContent = ContentHandler::getForTitle( $title )->makeEmptyContent();
+ } else {
+ $newContent = $rev->getContent();
+ }
+
+ if ( $newContent instanceof TextContent ) {
+ // only textual content has a "source view".
+ $text = $newContent->getNativeData();
+
+ if ( $wgFeedDiffCutoff <= 0 || strlen( $text ) > $wgFeedDiffCutoff ) {
+ $html = null;
+ } else {
+ $html = nl2br( htmlspecialchars( $text ) );
+ }
} else {
- $newtext = $rev->getText();
+ //XXX: we could get an HTML representation of the content via getParserOutput, but that may
+ // contain JS magic and generally may not be suitable for inclusion in a feed.
+ // Perhaps Content should have a getDescriptiveHtml method and/or a getSourceText method.
+ //Compare also ApiFeedContributions::feedItemDesc
+ $html = null;
}
- if ( $wgFeedDiffCutoff <= 0 || strlen( $newtext ) > $wgFeedDiffCutoff ) {
+
+ if ( $html === null ) {
+
// Omit large new page diffs, bug 29110
+ // Also use diff link for non-textual content
$diffText = self::getDiffLink( $title, $newid );
} else {
$diffText = '<p><b>' . wfMessage( 'newpage' )->text() . '</b></p>' .
- '<div>' . nl2br( htmlspecialchars( $newtext ) ) . '</div>';
+ '<div>' . $html . '</div>';
}
}
$completeText .= $diffText;
@@ -192,7 +222,7 @@ class FeedUtils {
protected static function getDiffLink( Title $title, $newid, $oldid = null ) {
$queryParameters = ($oldid == null)
? "diff={$newid}"
- : "diff={$newid}&oldid={$oldid}" ;
+ : "diff={$newid}&oldid={$oldid}";
$diffUrl = $title->getFullUrl( $queryParameters );
$diffLink = Html::element( 'a', array( 'href' => $diffUrl ),
@@ -206,18 +236,18 @@ class FeedUtils {
* Might be 'cleaner' to use DOM or XSLT or something,
* but *gack* it's a pain in the ass.
*
- * @param $text String: diff's HTML output
+ * @param string $text diff's HTML output
* @return String: modified HTML
*/
public static function applyDiffStyle( $text ) {
$styles = array(
'diff' => 'background-color: white; color:black;',
- 'diff-otitle' => 'background-color: white; color:black;',
- 'diff-ntitle' => 'background-color: white; color:black;',
- 'diff-addedline' => 'background: #cfc; color:black; font-size: smaller;',
- 'diff-deletedline' => 'background: #ffa; color:black; font-size: smaller;',
- 'diff-context' => 'background: #eee; color:black; font-size: smaller;',
- 'diffchange' => 'color: red; font-weight: bold; text-decoration: none;',
+ 'diff-otitle' => 'background-color: white; color:black; text-align: center;',
+ 'diff-ntitle' => 'background-color: white; color:black; text-align: center;',
+ 'diff-addedline' => 'color:black; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;',
+ 'diff-deletedline' => 'color:black; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;',
+ 'diff-context' => 'background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;',
+ 'diffchange' => 'font-weight: bold; text-decoration: none;',
);
foreach( $styles as $class => $style ) {
diff --git a/includes/FileDeleteForm.php b/includes/FileDeleteForm.php
index e75ad729..28403cca 100644
--- a/includes/FileDeleteForm.php
+++ b/includes/FileDeleteForm.php
@@ -80,13 +80,13 @@ class FileDeleteForm {
$this->oldimage = $wgRequest->getText( 'oldimage', false );
$token = $wgRequest->getText( 'wpEditToken' );
# Flag to hide all contents of the archived revisions
- $suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgUser->isAllowed('suppressrevision');
+ $suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgUser->isAllowed( 'suppressrevision' );
if( $this->oldimage ) {
$this->oldfile = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $this->title, $this->oldimage );
}
- if( !self::haveDeletableFile($this->file, $this->oldfile, $this->oldimage) ) {
+ if( !self::haveDeletableFile( $this->file, $this->oldfile, $this->oldimage ) ) {
$wgOut->addHTML( $this->prepareMessage( 'filedelete-nofile' ) );
$wgOut->addReturnTo( $this->title );
return;
@@ -140,10 +140,11 @@ class FileDeleteForm {
*
* @param $title Title object
* @param File $file: file object
- * @param $oldimage String: archive name
- * @param $reason String: reason of the deletion
+ * @param string $oldimage archive name
+ * @param string $reason reason of the deletion
* @param $suppress Boolean: whether to mark all deleted versions as restricted
* @param $user User object performing the request
+ * @throws MWException
* @return bool|Status
*/
public static function doDelete( &$title, &$file, &$oldimage, $reason, $suppress, User $user = null ) {
@@ -308,7 +309,7 @@ class FileDeleteForm {
* showing an appropriate message depending upon whether
* it's a current file or an old version
*
- * @param $message String: message base
+ * @param string $message message base
* @return String
*/
private function prepareMessage( $message ) {
@@ -343,7 +344,7 @@ class FileDeleteForm {
*
* @return bool
*/
- public static function isValidOldSpec($oldimage) {
+ public static function isValidOldSpec( $oldimage ) {
return strlen( $oldimage ) >= 16
&& strpos( $oldimage, '/' ) === false
&& strpos( $oldimage, '\\' ) === false;
@@ -359,7 +360,7 @@ class FileDeleteForm {
* @param $oldimage File
* @return bool
*/
- public static function haveDeletableFile(&$file, &$oldfile, $oldimage) {
+ public static function haveDeletableFile( &$file, &$oldfile, $oldimage ) {
return $oldimage
? $oldfile && $oldfile->exists() && $oldfile->isLocal()
: $file && $file->exists() && $file->isLocal();
@@ -374,8 +375,9 @@ class FileDeleteForm {
$q = array();
$q['action'] = 'delete';
- if( $this->oldimage )
+ if( $this->oldimage ) {
$q['oldimage'] = $this->oldimage;
+ }
return $this->title->getLocalUrl( $q );
}
diff --git a/includes/ForkController.php b/includes/ForkController.php
index 448bc03b..89ad9553 100644
--- a/includes/ForkController.php
+++ b/includes/ForkController.php
@@ -53,7 +53,7 @@ class ForkController {
const RESTART_ON_ERROR = 1;
public function __construct( $numProcs, $flags = 0 ) {
- if ( php_sapi_name() != 'cli' ) {
+ if ( PHP_SAPI != 'cli' ) {
throw new MWException( "ForkController cannot be used from the web." );
}
$this->procsToStart = $numProcs;
@@ -140,7 +140,7 @@ class ForkController {
// Don't share DB, storage, or memcached connections
wfGetLBFactory()->destroyInstance();
FileBackendGroup::destroySingleton();
- LockManagerGroup::destroySingleton();
+ LockManagerGroup::destroySingletons();
ObjectCache::clear();
$wgMemc = null;
}
diff --git a/includes/FormOptions.php b/includes/FormOptions.php
index 33bbd86a..8477ed98 100644
--- a/includes/FormOptions.php
+++ b/includes/FormOptions.php
@@ -2,7 +2,7 @@
/**
* Helper class to keep track of options when mixing links and form elements.
*
- * Copyright © 2008, Niklas Laxstiröm
+ * Copyright © 2008, Niklas Laxström
* Copyright © 2011, Antoine Musso
*
* This program is free software; you can redistribute it and/or modify
@@ -22,7 +22,7 @@
*
* @file
* @author Niklas Laxström
- * @author Antoine Musso
+ * @author Antoine Musso
*/
/**
@@ -83,6 +83,7 @@ class FormOptions implements ArrayAccess {
* which will be assumed as INT if the data is an integer.
*
* @param $data Mixed: value to guess type for
+ * @throws MWException
* @exception MWException Unsupported datatype
* @return int Type constant
*/
@@ -103,8 +104,9 @@ class FormOptions implements ArrayAccess {
/**
* Verify the given option name exist.
*
- * @param $name String: option name
+ * @param string $name option name
* @param $strict Boolean: throw an exception when the option does not exist (default false)
+ * @throws MWException
* @return Boolean: true if option exist, false otherwise
*/
public function validateName( $name, $strict = false ) {
@@ -121,7 +123,7 @@ class FormOptions implements ArrayAccess {
/**
* Use to set the value of an option.
*
- * @param $name String: option name
+ * @param string $name option name
* @param $value Mixed: value for the option
* @param $force Boolean: whether to set the value when it is equivalent to the default value for this option (default false).
* @return null
@@ -141,7 +143,7 @@ class FormOptions implements ArrayAccess {
* Get the value for the given option name.
* Internally use getValueReal()
*
- * @param $name String: option name
+ * @param string $name option name
* @return Mixed
*/
public function getValue( $name ) {
@@ -152,7 +154,7 @@ class FormOptions implements ArrayAccess {
/**
* @todo Document
- * @param $option Array: array structure describing the option
+ * @param array $option array structure describing the option
* @return Mixed. Value or the default value if it is null
*/
protected function getValueReal( $option ) {
@@ -166,7 +168,7 @@ class FormOptions implements ArrayAccess {
/**
* Delete the option value.
* This will make future calls to getValue() return the default value.
- * @param $name String: option name
+ * @param string $name option name
* @return null
*/
public function reset( $name ) {
@@ -176,8 +178,9 @@ class FormOptions implements ArrayAccess {
/**
* @todo Document
- * @param $name String: option name
- * @return null
+ * @param string $name Option name
+ * @throws MWException If option does not exist.
+ * @return mixed Value or the default value if it is null.
*/
public function consumeValue( $name ) {
$this->validateName( $name, true );
@@ -188,7 +191,7 @@ class FormOptions implements ArrayAccess {
/**
* @todo Document
- * @param $names Array: array of option names
+ * @param array $names array of option names
* @return null
*/
public function consumeValues( /*Array*/ $names ) {
@@ -205,11 +208,12 @@ class FormOptions implements ArrayAccess {
/**
* Validate and set an option integer value
- * The value will be altered to fit in the range.
+ * The value will be altered to fit in the range.
*
- * @param $name String: option name
- * @param $min Int: minimum value
- * @param $max Int: maximum value
+ * @param string $name option name
+ * @param int $min minimum value
+ * @param int $max maximum value
+ * @throws MWException
* @exception MWException Option is not of type int
* @return null
*/
diff --git a/includes/GitInfo.php b/includes/GitInfo.php
index c3c30733..6f7f8020 100644
--- a/includes/GitInfo.php
+++ b/includes/GitInfo.php
@@ -41,10 +41,18 @@ class GitInfo {
private static $viewers = false;
/**
- * @param $dir string The root directory of the repo where the .git dir can be found
+ * @param string $dir The root directory of the repo where the .git dir can be found
*/
public function __construct( $dir ) {
- $this->basedir = "{$dir}/.git/";
+ $this->basedir = "{$dir}/.git";
+ if ( is_readable( $this->basedir ) && !is_dir( $this->basedir ) ) {
+ $GITfile = file_get_contents( $this->basedir );
+ if ( strlen( $GITfile ) > 8 && substr( $GITfile, 0, 8 ) === 'gitdir: ' ) {
+ $path = rtrim( substr( $GITfile, 8 ), "\r\n" );
+ $isAbsolute = $path[0] === '/' || substr( $path, 1, 1 ) === ':';
+ $this->basedir = $isAbsolute ? $path : "{$dir}/{$path}";
+ }
+ }
}
/**
@@ -62,7 +70,7 @@ class GitInfo {
/**
* Check if a string looks like a hex encoded SHA1 hash
*
- * @param $str string The string to check
+ * @param string $str The string to check
* @return bool Whether or not the string looks like a SHA1
*/
public static function isSHA1( $str ) {
@@ -102,7 +110,7 @@ class GitInfo {
}
// If not a SHA1 it may be a ref:
- $REFfile = "{$this->basedir}{$HEAD}";
+ $REFfile = "{$this->basedir}/{$HEAD}";
if ( !is_readable( $REFfile ) ) {
return false;
}
diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php
index 50758c89..016736f4 100644
--- a/includes/GlobalFunctions.php
+++ b/includes/GlobalFunctions.php
@@ -50,7 +50,7 @@ if ( !function_exists( 'mb_substr' ) ) {
* @codeCoverageIgnore
* @return string
*/
- function mb_substr( $str, $start, $count='end' ) {
+ function mb_substr( $str, $start, $count = 'end' ) {
return Fallback::mb_substr( $str, $start, $count );
}
@@ -94,7 +94,6 @@ if( !function_exists( 'mb_strrpos' ) ) {
}
}
-
// Support for Wietse Venema's taint feature
if ( !function_exists( 'istainted' ) ) {
/**
@@ -168,7 +167,8 @@ function wfArrayLookup( $a, $b ) {
* @param $key String|Int
* @param $value Mixed
* @param $default Mixed
- * @param $changed Array to alter
+ * @param array $changed to alter
+ * @throws MWException
*/
function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
if ( is_null( $changed ) ) {
@@ -232,8 +232,8 @@ function wfMergeErrorArrays( /*...*/ ) {
/**
* Insert array into another array after the specified *KEY*
*
- * @param $array Array: The array.
- * @param $insert Array: The array to insert.
+ * @param array $array The array.
+ * @param array $insert The array to insert.
* @param $after Mixed: The key to insert after
* @return Array
*/
@@ -311,12 +311,12 @@ function wfRandom() {
}
/**
- * Get a random string containing a number of pesudo-random hex
+ * Get a random string containing a number of pseudo-random hex
* characters.
* @note This is not secure, if you are trying to generate some sort
* of token please use MWCryptRand instead.
*
- * @param $length int The length of the string to generate
+ * @param int $length The length of the string to generate
* @return String
* @since 1.20
*/
@@ -349,7 +349,7 @@ function wfRandomString( $length = 32 ) {
*
* @param $s String:
* @return string
-*/
+ */
function wfUrlencode( $s ) {
static $needle;
if ( is_null( $s ) ) {
@@ -379,8 +379,8 @@ function wfUrlencode( $s ) {
* "days=7&limit=100". Options in the first array override options in the second.
* Options set to null or false will not be output.
*
- * @param $array1 Array ( String|Array )
- * @param $array2 Array ( String|Array )
+ * @param array $array1 ( String|Array )
+ * @param array $array2 ( String|Array )
* @param $prefix String
* @return String
*/
@@ -391,7 +391,7 @@ function wfArrayToCgi( $array1, $array2 = null, $prefix = '' ) {
$cgi = '';
foreach ( $array1 as $key => $value ) {
- if ( !is_null($value) && $value !== false ) {
+ if ( !is_null( $value ) && $value !== false ) {
if ( $cgi != '' ) {
$cgi .= '&';
}
@@ -422,11 +422,11 @@ function wfArrayToCgi( $array1, $array2 = null, $prefix = '' ) {
/**
* This is the logical opposite of wfArrayToCgi(): it accepts a query string as
- * its argument and returns the same string in array form. This allows compa-
- * tibility with legacy functions that accept raw query strings instead of nice
+ * its argument and returns the same string in array form. This allows compatibility
+ * with legacy functions that accept raw query strings instead of nice
* arrays. Of course, keys and values are urldecode()d.
*
- * @param $query String: query string
+ * @param string $query query string
* @return array Array version of input
*/
function wfCgiToArray( $query ) {
@@ -506,7 +506,7 @@ function wfAppendQuery( $url, $query ) {
* @todo this won't work with current-path-relative URLs
* like "subdir/foo.html", etc.
*
- * @param $url String: either fully-qualified or a local path + query
+ * @param string $url either fully-qualified or a local path + query
* @param $defaultProto Mixed: one of the PROTO_* constants. Determines the
* protocol to use if $url or $wgServer is
* protocol-relative
@@ -576,7 +576,7 @@ function wfExpandUrl( $url, $defaultProto = PROTO_CURRENT ) {
* @todo Need to integrate this into wfExpandUrl (bug 32168)
*
* @since 1.19
- * @param $urlParts Array URL parts, as output from wfParseUrl
+ * @param array $urlParts URL parts, as output from wfParseUrl
* @return string URL assembled from its component parts
*/
function wfAssembleUrl( $urlParts ) {
@@ -628,7 +628,7 @@ function wfAssembleUrl( $urlParts ) {
*
* @todo Need to integrate this into wfExpandUrl (bug 32168)
*
- * @param $urlPath String URL path, potentially containing dot-segments
+ * @param string $urlPath URL path, potentially containing dot-segments
* @return string URL path with all dot-segments removed
*/
function wfRemoveDotSegments( $urlPath ) {
@@ -705,7 +705,7 @@ function wfRemoveDotSegments( $urlPath ) {
/**
* Returns a regular expression of url protocols
*
- * @param $includeProtocolRelative bool If false, remove '//' from the returned protocol list.
+ * @param bool $includeProtocolRelative If false, remove '//' from the returned protocol list.
* DO NOT USE this directly, use wfUrlProtocolsWithoutProtRel() instead
* @return String
*/
@@ -765,7 +765,7 @@ function wfUrlProtocolsWithoutProtRel() {
* 2) Handles protocols that don't use :// (e.g., mailto: and news: , as well as protocol-relative URLs) correctly
* 3) Adds a "delimiter" element to the array, either '://', ':' or '//' (see (2))
*
- * @param $url String: a URL to parse
+ * @param string $url a URL to parse
* @return Array: bits of the URL in an associative array, per PHP docs
*/
function wfParseUrl( $url ) {
@@ -808,9 +808,14 @@ function wfParseUrl( $url ) {
if ( !isset( $bits['host'] ) ) {
$bits['host'] = '';
- /* parse_url loses the third / for file:///c:/ urls (but not on variants) */
- if ( substr( $bits['path'], 0, 1 ) !== '/' ) {
- $bits['path'] = '/' . $bits['path'];
+ // bug 45069
+ if ( isset( $bits['path'] ) ) {
+ /* parse_url loses the third / for file:///c:/ urls (but not on variants) */
+ if ( substr( $bits['path'], 0, 1 ) !== '/' ) {
+ $bits['path'] = '/' . $bits['path'];
+ }
+ } else {
+ $bits['path'] = '';
}
}
@@ -845,8 +850,6 @@ function wfExpandIRI_callback( $matches ) {
return urldecode( $matches[1] );
}
-
-
/**
* Make URL indexes, appropriate for the el_index field of externallinks.
*
@@ -903,8 +906,8 @@ function wfMakeUrlIndexes( $url ) {
/**
* Check whether a given URL has a domain that occurs in a given set of domains
- * @param $url string URL
- * @param $domains array Array of domains (strings)
+ * @param string $url URL
+ * @param array $domains Array of domains (strings)
* @return bool True if the host part of $url ends in one of the strings in $domains
*/
function wfMatchesDomainList( $url, $domains ) {
@@ -932,7 +935,7 @@ function wfMatchesDomainList( $url, $domains ) {
* $wgDebugComments - if on, some debug items may appear in comments in the HTML output.
*
* @param $text String
- * @param $logonly Bool: set true to avoid appearing in HTML when $wgDebugComments is set
+ * @param bool $logonly set true to avoid appearing in HTML when $wgDebugComments is set
*/
function wfDebug( $text, $logonly = false ) {
global $wgDebugLogFile, $wgProfileOnly, $wgDebugRawPage, $wgDebugLogPrefix;
@@ -1004,7 +1007,7 @@ function wfDebugTimer() {
/**
* Send a line giving PHP memory usage.
*
- * @param $exact Bool: print exact values instead of kilobytes (default: false)
+ * @param bool $exact print exact values instead of kilobytes (default: false)
*/
function wfDebugMem( $exact = false ) {
$mem = memory_get_usage();
@@ -1022,7 +1025,7 @@ function wfDebugMem( $exact = false ) {
*
* @param $logGroup String
* @param $text String
- * @param $public Bool: whether to log the event in the public log if no private
+ * @param bool $public whether to log the event in the public log if no private
* log file is specified, (default true)
*/
function wfDebugLog( $logGroup, $text, $public = true ) {
@@ -1036,14 +1039,14 @@ function wfDebugLog( $logGroup, $text, $public = true ) {
wfErrorLog( "$time $host $wiki: $text", $wgDebugLogGroups[$logGroup] );
}
} elseif ( $public === true ) {
- wfDebug( $text, true );
+ wfDebug( "[$logGroup] $text", true );
}
}
/**
* Log for database errors
*
- * @param $text String: database error message.
+ * @param string $text database error message.
*/
function wfLogDBError( $text ) {
global $wgDBerrorLog, $wgDBerrorLogTZ;
@@ -1076,9 +1079,9 @@ function wfLogDBError( $text ) {
* Throws a warning that $function is deprecated
*
* @param $function String
- * @param $version String|bool: Version of MediaWiki that the function was deprecated in (Added in 1.19).
- * @param $component String|bool: Added in 1.19.
- * @param $callerOffset integer: How far up the callstack is the original
+ * @param string|bool $version Version of MediaWiki that the function was deprecated in (Added in 1.19).
+ * @param string|bool $component Added in 1.19.
+ * @param $callerOffset integer: How far up the call stack is the original
* caller. 2 = function that called the function that called
* wfDeprecated (Added in 1.20)
*
@@ -1092,7 +1095,7 @@ function wfDeprecated( $function, $version = false, $component = false, $callerO
* Send a warning either to the debug log or in a PHP error depending on
* $wgDevelopmentWarnings
*
- * @param $msg String: message to send
+ * @param string $msg message to send
* @param $callerOffset Integer: number of items to go back in the backtrace to
* find the correct caller (1 = function calling wfWarn, ...)
* @param $level Integer: PHP error level; only used when $wgDevelopmentWarnings
@@ -1109,7 +1112,8 @@ function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
* send lines to the specified port, prefixed by the specified prefix and a space.
*
* @param $text String
- * @param $file String filename
+ * @param string $file filename
+ * @throws MWException
*/
function wfErrorLog( $text, $file ) {
if ( substr( $file, 0, 4 ) == 'udp:' ) {
@@ -1212,9 +1216,18 @@ function wfLogProfilingData() {
if ( $wgUser->isItemLoaded( 'id' ) && $wgUser->isAnon() ) {
$forward .= ' anon';
}
+
+ // Command line script uses a FauxRequest object which does not have
+ // any knowledge about an URL and throw an exception instead.
+ try {
+ $requestUrl = $wgRequest->getRequestURL();
+ } catch ( MWException $e ) {
+ $requestUrl = 'n/a';
+ }
+
$log = sprintf( "%s\t%04.3f\t%s\n",
gmdate( 'YmdHis' ), $elapsed,
- urldecode( $wgRequest->getRequestURL() . $forward ) );
+ urldecode( $requestUrl . $forward ) );
wfErrorLog( $log . $profiler->getOutput(), $wgDebugLogFile );
}
@@ -1224,17 +1237,21 @@ function wfLogProfilingData() {
*
* @param $key String
* @param $count Int
+ * @return void
*/
function wfIncrStats( $key, $count = 1 ) {
global $wgStatsMethod;
$count = intval( $count );
+ if ( $count == 0 ) {
+ return;
+ }
if( $wgStatsMethod == 'udp' ) {
- global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgDBname, $wgAggregateStatsID;
+ global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgAggregateStatsID;
static $socket;
- $id = $wgAggregateStatsID !== false ? $wgAggregateStatsID : $wgDBname;
+ $id = $wgAggregateStatsID !== false ? $wgAggregateStatsID : wfWikiID();
if ( !$socket ) {
$socket = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
@@ -1366,9 +1383,13 @@ function wfUILang() {
}
/**
- * This is the new function for getting translated interface messages.
- * See the Message class for documentation how to use them.
- * The intention is that this function replaces all old wfMsg* functions.
+ * This is the function for getting translated interface messages.
+ *
+ * @see Message class for documentation how to use them.
+ * @see https://www.mediawiki.org/wiki/Manual:Messages_API
+ *
+ * This function replaces all old wfMsg* functions.
+ *
* @param $key \string Message key.
* Varargs: normal message parameters.
* @return Message
@@ -1404,7 +1425,7 @@ function wfMessageFallback( /*...*/ ) {
*
* @deprecated since 1.18
*
- * @param $key String: lookup key for the message, usually
+ * @param string $key lookup key for the message, usually
* defined in languages/Language.php
*
* Parameters to the message, which can be used to insert variable text into
@@ -1416,6 +1437,8 @@ function wfMessageFallback( /*...*/ ) {
* @return String
*/
function wfMsg( $key ) {
+ wfDeprecated( __METHOD__, '1.21' );
+
$args = func_get_args();
array_shift( $args );
return wfMsgReal( $key, $args );
@@ -1430,6 +1453,8 @@ function wfMsg( $key ) {
* @return String
*/
function wfMsgNoTrans( $key ) {
+ wfDeprecated( __METHOD__, '1.21' );
+
$args = func_get_args();
array_shift( $args );
return wfMsgReal( $key, $args, true, false, false );
@@ -1456,11 +1481,13 @@ function wfMsgNoTrans( $key ) {
*
* @deprecated since 1.18
*
- * @param $key String: lookup key for the message, usually
+ * @param string $key lookup key for the message, usually
* defined in languages/Language.php
* @return String
*/
function wfMsgForContent( $key ) {
+ wfDeprecated( __METHOD__, '1.21' );
+
global $wgForceUIMsgAsContentMsg;
$args = func_get_args();
array_shift( $args );
@@ -1482,6 +1509,8 @@ function wfMsgForContent( $key ) {
* @return String
*/
function wfMsgForContentNoTrans( $key ) {
+ wfDeprecated( __METHOD__, '1.21' );
+
global $wgForceUIMsgAsContentMsg;
$args = func_get_args();
array_shift( $args );
@@ -1499,7 +1528,7 @@ function wfMsgForContentNoTrans( $key ) {
*
* @deprecated since 1.18
*
- * @param $key String: key to get.
+ * @param string $key key to get.
* @param $args
* @param $useDB Boolean
* @param $forContent Mixed: Language code, or false for user lang, true for content lang.
@@ -1507,6 +1536,8 @@ function wfMsgForContentNoTrans( $key ) {
* @return String: the requested message.
*/
function wfMsgReal( $key, $args, $useDB = true, $forContent = false, $transform = true ) {
+ wfDeprecated( __METHOD__, '1.21' );
+
wfProfileIn( __METHOD__ );
$message = wfMsgGetKey( $key, $useDB, $forContent, $transform );
$message = wfMsgReplaceArgs( $message, $args );
@@ -1521,12 +1552,14 @@ function wfMsgReal( $key, $args, $useDB = true, $forContent = false, $transform
*
* @param $key String
* @param $useDB Bool
- * @param $langCode String: Code of the language to get the message for, or
+ * @param string $langCode Code of the language to get the message for, or
* behaves as a content language switch if it is a boolean.
* @param $transform Boolean: whether to parse magic words, etc.
* @return string
*/
function wfMsgGetKey( $key, $useDB = true, $langCode = false, $transform = true ) {
+ wfDeprecated( __METHOD__, '1.21' );
+
wfRunHooks( 'NormalizeMessageKey', array( &$key, &$useDB, &$langCode, &$transform ) );
$cache = MessageCache::singleton();
@@ -1581,6 +1614,8 @@ function wfMsgReplaceArgs( $message, $args ) {
* @return string
*/
function wfMsgHtml( $key ) {
+ wfDeprecated( __METHOD__, '1.21' );
+
$args = func_get_args();
array_shift( $args );
return wfMsgReplaceArgs( htmlspecialchars( wfMsgGetKey( $key ) ), $args );
@@ -1600,6 +1635,8 @@ function wfMsgHtml( $key ) {
* @return string
*/
function wfMsgWikiHtml( $key ) {
+ wfDeprecated( __METHOD__, '1.21' );
+
$args = func_get_args();
array_shift( $args );
return wfMsgReplaceArgs(
@@ -1613,8 +1650,8 @@ function wfMsgWikiHtml( $key ) {
*
* @deprecated since 1.18
*
- * @param $key String: key of the message
- * @param $options Array: processing rules. Can take the following options:
+ * @param string $key key of the message
+ * @param array $options processing rules. Can take the following options:
* <i>parse</i>: parses wikitext to HTML
* <i>parseinline</i>: parses wikitext to HTML and removes the surrounding
* p's added by parser or tidy
@@ -1625,12 +1662,14 @@ function wfMsgWikiHtml( $key ) {
* <i>content</i>: fetch message for content language instead of interface
* Also can accept a single associative argument, of the form 'language' => 'xx':
* <i>language</i>: Language object or language code to fetch message for
- * (overriden by <i>content</i>).
+ * (overridden by <i>content</i>).
* Behavior for conflicting options (e.g., parse+parseinline) is undefined.
*
* @return String
*/
function wfMsgExt( $key, $options ) {
+ wfDeprecated( __METHOD__, '1.21' );
+
$args = func_get_args();
array_shift( $args );
array_shift( $args );
@@ -1703,7 +1742,7 @@ function wfMsgExt( $key, $options ) {
/**
* Since wfMsg() and co suck, they don't return false if the message key they
* looked up didn't exist but a XHTML string, this function checks for the
- * nonexistance of messages by checking the MessageCache::get() result directly.
+ * nonexistence of messages by checking the MessageCache::get() result directly.
*
* @deprecated since 1.18. Use Message::isDisabled().
*
@@ -1711,6 +1750,8 @@ function wfMsgExt( $key, $options ) {
* @return Boolean True if the message *doesn't* exist.
*/
function wfEmptyMsg( $key ) {
+ wfDeprecated( __METHOD__, '1.21' );
+
return MessageCache::singleton()->get( $key, /*useDB*/true, /*content*/false ) === false;
}
@@ -1718,7 +1759,8 @@ function wfEmptyMsg( $key ) {
* Throw a debugging exception. This function previously once exited the process,
* but now throws an exception instead, with similar results.
*
- * @param $msg String: message shown when dying.
+ * @param string $msg message shown when dying.
+ * @throws MWException
*/
function wfDebugDieBacktrace( $msg = '' ) {
throw new MWException( $msg );
@@ -1783,13 +1825,13 @@ function wfReportTime() {
*
* With Zend Optimizer 3.2.0 loaded, this causes segfaults under somewhat
* murky circumstances, which may be triggered in part by stub objects
- * or other fancy talkin'.
+ * or other fancy talking'.
*
* Will return an empty array if Zend Optimizer is detected or if
* debug_backtrace is disabled, otherwise the output from
* debug_backtrace() (trimmed).
*
- * @param $limit int This parameter can be used to limit the number of stack frames returned
+ * @param int $limit This parameter can be used to limit the number of stack frames returned
*
* @return array of backtrace information
*/
@@ -1895,7 +1937,7 @@ function wfGetCaller( $level = 2 ) {
* Return a string consisting of callers in the stack. Useful sometimes
* for profiling specific points.
*
- * @param $limit int The maximum depth of the stack frame to return, or false for
+ * @param int $limit The maximum depth of the stack frame to return, or false for
* the entire stack.
* @return String
*/
@@ -1920,10 +1962,8 @@ function wfFormatStackFrame( $frame ) {
$frame['function'];
}
-
/* Some generic result counters, pulled out of SearchEngine */
-
/**
* @todo document
*
@@ -1941,8 +1981,8 @@ function wfShowingResults( $offset, $limit ) {
* @param $offset String
* @param $limit Integer
* @param $link String
- * @param $query String: optional URL query parameter string
- * @param $atend Bool: optional param for specified if this is the last page
+ * @param string $query optional URL query parameter string
+ * @param bool $atend optional param for specified if this is the last page
* @return String
* @deprecated in 1.19; use Language::viewPrevNext() instead
*/
@@ -1968,8 +2008,8 @@ function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) {
/**
* Make a list item, used by various special pages
*
- * @param $page String Page link
- * @param $details String Text between brackets
+ * @param string $page Page link
+ * @param string $details Text between brackets
* @param $oppositedm Boolean Add the direction mark opposite to your
* language, to display text properly
* @return String
@@ -2018,8 +2058,8 @@ function wfClientAcceptsGzip( $force = false ) {
* Obtain the offset and limit values from the request string;
* used in special pages
*
- * @param $deflimit Int default limit if none supplied
- * @param $optionname String Name of a user preference to check against
+ * @param int $deflimit default limit if none supplied
+ * @param string $optionname Name of a user preference to check against
* @return array
*
*/
@@ -2034,7 +2074,7 @@ function wfCheckLimits( $deflimit = 50, $optionname = 'rclimit' ) {
* is achieved by substituting certain characters with HTML entities.
* As required by the callers, "<nowiki>" is not used.
*
- * @param $text String: text to be escaped
+ * @param string $text text to be escaped
* @return String
*/
function wfEscapeWikiText( $text ) {
@@ -2050,7 +2090,7 @@ function wfEscapeWikiText( $text ) {
}
/**
- * Get the current unix timetstamp with microseconds. Useful for profiling
+ * Get the current unix timestamp with microseconds. Useful for profiling
* @return Float
*/
function wfTime() {
@@ -2207,7 +2247,7 @@ function wfClearOutputBuffers() {
* factors
*
* @param $accept String
- * @param $def String default
+ * @param string $def default
* @return Array
*/
function wfAcceptToPrefs( $accept, $def = '*/*' ) {
@@ -2267,8 +2307,8 @@ function mimeTypeMatch( $type, $avail ) {
* array of type to preference (preference is a float between 0.0 and 1.0).
* Wildcards in the types are acceptable.
*
- * @param $cprefs Array: client's acceptable type list
- * @param $sprefs Array: server's offered types
+ * @param array $cprefs client's acceptable type list
+ * @param array $sprefs server's offered types
* @return string
*
* @todo FIXME: Doesn't handle params like 'text/plain; charset=UTF-8'
@@ -2390,11 +2430,6 @@ define( 'TS_ORACLE', 6 );
define( 'TS_POSTGRES', 7 );
/**
- * DB2 format time
- */
-define( 'TS_DB2', 8 );
-
-/**
* ISO 8601 basic format with no timezone: 19860209T200000Z. This is used by ResourceLoader
*/
define( 'TS_ISO_8601_BASIC', 9 );
@@ -2405,7 +2440,7 @@ define( 'TS_ISO_8601_BASIC', 9 );
* @param $outputtype Mixed: A timestamp in one of the supported formats, the
* function will autodetect which format is supplied and act
* accordingly.
- * @param $ts Mixed: the timestamp to convert or 0 for the current timestamp
+ * @param $ts Mixed: optional timestamp to convert, default 0 for the current time
* @return Mixed: String / false The same date in the format specified in $outputtype or false
*/
function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
@@ -2413,7 +2448,7 @@ function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
$timestamp = new MWTimestamp( $ts );
return $timestamp->getTimestamp( $outputtype );
} catch( TimestampException $e ) {
- wfDebug("wfTimestamp() fed bogus time value: TYPE=$outputtype; VALUE=$ts\n");
+ wfDebug( "wfTimestamp() fed bogus time value: TYPE=$outputtype; VALUE=$ts\n" );
return false;
}
}
@@ -2509,9 +2544,10 @@ function wfTempDir() {
/**
* Make directory, and make all parent directories if they don't exist
*
- * @param $dir String: full path to directory to create
+ * @param string $dir full path to directory to create
* @param $mode Integer: chmod value to use, default is $wgDirectoryMode
- * @param $caller String: optional caller param for debugging.
+ * @param string $caller optional caller param for debugging.
+ * @throws MWException
* @return bool
*/
function wfMkdirParents( $dir, $mode = null, $caller = null ) {
@@ -2585,12 +2621,14 @@ function wfPercent( $nr, $acc = 2, $round = true ) {
/**
* Find out whether or not a mixed variable exists in a string
*
+ * @deprecated Just use str(i)pos
* @param $needle String
* @param $str String
* @param $insensitive Boolean
* @return Boolean
*/
function in_string( $needle, $str, $insensitive = false ) {
+ wfDeprecated( __METHOD__, '1.21' );
$func = 'strpos';
if( $insensitive ) $func = 'stripos';
@@ -2633,9 +2671,9 @@ function wfIniGetBool( $setting ) {
* Wrapper function for PHP's dl(). This doesn't work in most situations from
* PHP 5.3 onward, and is usually disabled in shared environments anyway.
*
- * @param $extension String A PHP extension. The file suffix (.so or .dll)
+ * @param string $extension A PHP extension. The file suffix (.so or .dll)
* should be omitted
- * @param $fileName String Name of the library, if not $extension.suffix
+ * @param string $fileName Name of the library, if not $extension.suffix
* @return Bool - Whether or not the extension is loaded
*/
function wfDl( $extension, $fileName = null ) {
@@ -2644,8 +2682,7 @@ function wfDl( $extension, $fileName = null ) {
}
$canDl = false;
- $sapi = php_sapi_name();
- if( $sapi == 'cli' || $sapi == 'cgi' || $sapi == 'embed' ) {
+ if( PHP_SAPI == 'cli' || PHP_SAPI == 'cgi' || PHP_SAPI == 'embed' ) {
$canDl = ( function_exists( 'dl' ) && is_callable( 'dl' )
&& wfIniGetBool( 'enable_dl' ) && !wfIniGetBool( 'safe_mode' ) );
}
@@ -2673,7 +2710,7 @@ function wfDl( $extension, $fileName = null ) {
* @param varargs
* @return String
*/
-function wfEscapeShellArg( ) {
+function wfEscapeShellArg() {
wfInitShellLocale();
$args = func_get_args();
@@ -2729,17 +2766,18 @@ function wfEscapeShellArg( ) {
/**
* Execute a shell command, with time and memory limits mirrored from the PHP
* configuration if supported.
- * @param $cmd String Command line, properly escaped for shell.
+ * @param string $cmd Command line, properly escaped for shell.
* @param &$retval null|Mixed optional, will receive the program's exit code.
* (non-zero is usually failure)
- * @param $environ Array optional environment variables which should be
+ * @param array $environ optional environment variables which should be
* added to the executed command environment.
- * @param $limits Array optional array with limits(filesize, memory, time)
+ * @param array $limits optional array with limits(filesize, memory, time, walltime)
* this overwrites the global wgShellMax* limits.
* @return string collected stdout as a string (trailing newlines stripped)
*/
function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array() ) {
- global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime;
+ global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime,
+ $wgMaxShellWallClockTime, $wgShellCgroup;
static $disabled;
if ( is_null( $disabled ) ) {
@@ -2786,15 +2824,27 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array
$cmd = $envcmd . $cmd;
if ( php_uname( 's' ) == 'Linux' ) {
- $time = intval ( isset($limits['time']) ? $limits['time'] : $wgMaxShellTime );
- $mem = intval ( isset($limits['memory']) ? $limits['memory'] : $wgMaxShellMemory );
- $filesize = intval ( isset($limits['filesize']) ? $limits['filesize'] : $wgMaxShellFileSize );
-
- if ( $time > 0 && $mem > 0 ) {
- $script = "$IP/bin/ulimit4.sh";
- if ( is_executable( $script ) ) {
- $cmd = '/bin/bash ' . escapeshellarg( $script ) . " $time $mem $filesize " . escapeshellarg( $cmd );
- }
+ $time = intval ( isset( $limits['time'] ) ? $limits['time'] : $wgMaxShellTime );
+ if ( isset( $limits['walltime'] ) ) {
+ $wallTime = intval( $limits['walltime'] );
+ } elseif ( isset( $limits['time'] ) ) {
+ $wallTime = $time;
+ } else {
+ $wallTime = intval( $wgMaxShellWallClockTime );
+ }
+ $mem = intval ( isset( $limits['memory'] ) ? $limits['memory'] : $wgMaxShellMemory );
+ $filesize = intval ( isset( $limits['filesize'] ) ? $limits['filesize'] : $wgMaxShellFileSize );
+
+ if ( $time > 0 || $mem > 0 || $filesize > 0 || $wallTime > 0 ) {
+ $cmd = '/bin/bash ' . escapeshellarg( "$IP/includes/limit.sh" ) . ' ' .
+ escapeshellarg( $cmd ) . ' ' .
+ escapeshellarg(
+ "MW_CPU_LIMIT=$time; " .
+ 'MW_CGROUP=' . escapeshellarg( $wgShellCgroup ) . '; ' .
+ "MW_MEM_LIMIT=$mem; " .
+ "MW_FILE_SIZE_LIMIT=$filesize; " .
+ "MW_WALL_CLOCK_LIMIT=$wallTime"
+ );
}
}
wfDebug( "wfShellExec: $cmd\n" );
@@ -2840,9 +2890,9 @@ function wfShellMaintenanceCmd( $script, array $parameters = array(), array $opt
* Generate a shell-escaped command line string to run a MediaWiki cli script.
* Note that $parameters should be a flat array and an option with an argument
* should consist of two consecutive items in the array (do not use "--option value").
- * @param $script string MediaWiki cli script path
- * @param $parameters Array Arguments and options to the script
- * @param $options Array Associative array of options:
+ * @param string $script MediaWiki cli script path
+ * @param array $parameters Arguments and options to the script
+ * @param array $options Associative array of options:
* 'php': The path to the php executable
* 'wrapper': Path to a PHP wrapper to handle the maintenance script
* @return Array
@@ -2891,15 +2941,19 @@ function wfMerge( $old, $mine, $yours, &$result ) {
$mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' );
$yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' );
- fwrite( $oldtextFile, $old );
+ # NOTE: diff3 issues a warning to stderr if any of the files does not end with
+ # a newline character. To avoid this, we normalize the trailing whitespace before
+ # creating the diff.
+
+ fwrite( $oldtextFile, rtrim( $old ) . "\n" );
fclose( $oldtextFile );
- fwrite( $mytextFile, $mine );
+ fwrite( $mytextFile, rtrim( $mine ) . "\n" );
fclose( $mytextFile );
- fwrite( $yourtextFile, $yours );
+ fwrite( $yourtextFile, rtrim( $yours ) . "\n" );
fclose( $yourtextFile );
# Check for a conflict
- $cmd = $wgDiff3 . ' -a --overlap-only ' .
+ $cmd = wfEscapeShellArg( $wgDiff3 ) . ' -a --overlap-only ' .
wfEscapeShellArg( $mytextName ) . ' ' .
wfEscapeShellArg( $oldtextName ) . ' ' .
wfEscapeShellArg( $yourtextName );
@@ -2913,7 +2967,7 @@ function wfMerge( $old, $mine, $yours, &$result ) {
pclose( $handle );
# Merge differences
- $cmd = $wgDiff3 . ' -a -e --merge ' .
+ $cmd = wfEscapeShellArg( $wgDiff3 ) . ' -a -e --merge ' .
wfEscapeShellArg( $mytextName, $oldtextName, $yourtextName );
$handle = popen( $cmd, 'r' );
$result = '';
@@ -2940,9 +2994,9 @@ function wfMerge( $old, $mine, $yours, &$result ) {
* Returns unified plain-text diff of two texts.
* Useful for machine processing of diffs.
*
- * @param $before String: the text before the changes.
- * @param $after String: the text after the changes.
- * @param $params String: command-line options for the diff command.
+ * @param string $before the text before the changes.
+ * @param string $after the text after the changes.
+ * @param string $params command-line options for the diff command.
* @return String: unified diff of $before and $after
*/
function wfDiff( $before, $after, $params = '-u' ) {
@@ -3022,6 +3076,7 @@ function wfDiff( $before, $after, $params = '-u' ) {
*
* @param $req_ver Mixed: the version to check, can be a string, an integer, or
* a float
+ * @throws MWException
*/
function wfUsePHP( $req_ver ) {
$php_ver = PHP_VERSION;
@@ -3043,6 +3098,7 @@ function wfUsePHP( $req_ver ) {
*
* @param $req_ver Mixed: the version to check, can be a string, an integer, or
* a float
+ * @throws MWException
*/
function wfUseMW( $req_ver ) {
global $wgVersion;
@@ -3061,7 +3117,7 @@ function wfUseMW( $req_ver ) {
* We'll consider it so always, as we don't want '\s' in our Unix paths either.
*
* @param $path String
- * @param $suffix String: to remove if present
+ * @param string $suffix to remove if present
* @return String
*/
function wfBaseName( $path, $suffix = '' ) {
@@ -3081,8 +3137,8 @@ function wfBaseName( $path, $suffix = '' ) {
* May explode on non-matching case-insensitive paths,
* funky symlinks, etc.
*
- * @param $path String: absolute destination path including target filename
- * @param $from String: Absolute source path, directory only
+ * @param string $path absolute destination path including target filename
+ * @param string $from Absolute source path, directory only
* @return String
*/
function wfRelativePath( $path, $from ) {
@@ -3140,91 +3196,105 @@ function wfDoUpdates( $commit = '' ) {
* Supports base 2 through 36; digit values 10-36 are represented
* as lowercase letters a-z. Input is case-insensitive.
*
- * @param $input String: of digits
- * @param $sourceBase Integer: 2-36
- * @param $destBase Integer: 2-36
- * @param $pad Integer: 1 or greater
- * @param $lowercase Boolean
- * @return String or false on invalid input
- */
-function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = true ) {
- $input = strval( $input );
- if( $sourceBase < 2 ||
+ * @param string $input Input number
+ * @param int $sourceBase Base of the input number
+ * @param int $destBase Desired base of the output
+ * @param int $pad Minimum number of digits in the output (pad with zeroes)
+ * @param bool $lowercase Whether to output in lowercase or uppercase
+ * @param string $engine Either "gmp", "bcmath", or "php"
+ * @return string|bool The output number as a string, or false on error
+ */
+function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = true, $engine = 'auto' ) {
+ $input = (string)$input;
+ if(
+ $sourceBase < 2 ||
$sourceBase > 36 ||
$destBase < 2 ||
$destBase > 36 ||
- $pad < 1 ||
- $sourceBase != intval( $sourceBase ) ||
- $destBase != intval( $destBase ) ||
- $pad != intval( $pad ) ||
- !is_string( $input ) ||
- $input == '' ) {
+ $sourceBase != (int) $sourceBase ||
+ $destBase != (int) $destBase ||
+ $pad != (int) $pad ||
+ !preg_match( "/^[" . substr( '0123456789abcdefghijklmnopqrstuvwxyz', 0, $sourceBase ) . "]+$/i", $input )
+ ) {
return false;
}
- $digitChars = ( $lowercase ) ? '0123456789abcdefghijklmnopqrstuvwxyz' : '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
- $inDigits = array();
- $outChars = '';
- // Decode and validate input string
- $input = strtolower( $input );
- for( $i = 0; $i < strlen( $input ); $i++ ) {
- $n = strpos( $digitChars, $input[$i] );
- if( $n === false || $n > $sourceBase ) {
- return false;
+ static $baseChars = array (
+ 10 => 'a', 11 => 'b', 12 => 'c', 13 => 'd', 14 => 'e', 15 => 'f',
+ 16 => 'g', 17 => 'h', 18 => 'i', 19 => 'j', 20 => 'k', 21 => 'l',
+ 22 => 'm', 23 => 'n', 24 => 'o', 25 => 'p', 26 => 'q', 27 => 'r',
+ 28 => 's', 29 => 't', 30 => 'u', 31 => 'v', 32 => 'w', 33 => 'x',
+ 34 => 'y', 35 => 'z',
+
+ '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5,
+ '6' => 6, '7' => 7, '8' => 8, '9' => 9, 'a' => 10, 'b' => 11,
+ 'c' => 12, 'd' => 13, 'e' => 14, 'f' => 15, 'g' => 16, 'h' => 17,
+ 'i' => 18, 'j' => 19, 'k' => 20, 'l' => 21, 'm' => 22, 'n' => 23,
+ 'o' => 24, 'p' => 25, 'q' => 26, 'r' => 27, 's' => 28, 't' => 29,
+ 'u' => 30, 'v' => 31, 'w' => 32, 'x' => 33, 'y' => 34, 'z' => 35
+ );
+
+ if( extension_loaded( 'gmp' ) && ( $engine == 'auto' || $engine == 'gmp' ) ) {
+ $result = gmp_strval( gmp_init( $input, $sourceBase ), $destBase );
+ } elseif( extension_loaded( 'bcmath' ) && ( $engine == 'auto' || $engine == 'bcmath' ) ) {
+ $decimal = '0';
+ foreach( str_split( strtolower( $input ) ) as $char ) {
+ $decimal = bcmul( $decimal, $sourceBase );
+ $decimal = bcadd( $decimal, $baseChars[$char] );
}
- $inDigits[] = $n;
- }
- // Iterate over the input, modulo-ing out an output digit
- // at a time until input is gone.
- while( count( $inDigits ) ) {
- $work = 0;
- $workDigits = array();
+ for( $result = ''; bccomp( $decimal, 0 ); $decimal = bcdiv( $decimal, $destBase, 0 ) ) {
+ $result .= $baseChars[bcmod( $decimal, $destBase )];
+ }
- // Long division...
- foreach( $inDigits as $digit ) {
- $work *= $sourceBase;
- $work += $digit;
+ $result = strrev( $result );
+ } else {
+ $inDigits = array();
+ foreach( str_split( strtolower( $input ) ) as $char ) {
+ $inDigits[] = $baseChars[$char];
+ }
- if( $work < $destBase ) {
- // Gonna need to pull another digit.
- if( count( $workDigits ) ) {
- // Avoid zero-padding; this lets us find
- // the end of the input very easily when
- // length drops to zero.
- $workDigits[] = 0;
- }
- } else {
- // Finally! Actual division!
- $workDigits[] = intval( $work / $destBase );
+ // Iterate over the input, modulo-ing out an output digit
+ // at a time until input is gone.
+ $result = '';
+ while( $inDigits ) {
+ $work = 0;
+ $workDigits = array();
+
+ // Long division...
+ foreach( $inDigits as $digit ) {
+ $work *= $sourceBase;
+ $work += $digit;
- // Isn't it annoying that most programming languages
- // don't have a single divide-and-remainder operator,
- // even though the CPU implements it that way?
- $work = $work % $destBase;
+ if( $workDigits || $work >= $destBase ) {
+ $workDigits[] = (int) ( $work / $destBase );
+ }
+ $work %= $destBase;
}
- }
- // All that division leaves us with a remainder,
- // which is conveniently our next output digit.
- $outChars .= $digitChars[$work];
+ // All that division leaves us with a remainder,
+ // which is conveniently our next output digit.
+ $result .= $baseChars[$work];
+
+ // And we continue!
+ $inDigits = $workDigits;
+ }
- // And we continue!
- $inDigits = $workDigits;
+ $result = strrev( $result );
}
- while( strlen( $outChars ) < $pad ) {
- $outChars .= '0';
+ if( !$lowercase ) {
+ $result = strtoupper( $result );
}
- return strrev( $outChars );
+ return str_pad( $result, $pad, '0', STR_PAD_LEFT );
}
/**
* Create an object with a given name and an array of construct parameters
*
* @param $name String
- * @param $p Array: parameters
+ * @param array $p parameters
* @return object
* @deprecated since 1.18, warnings in 1.18, removal in 1.20
*/
@@ -3251,7 +3321,7 @@ function wfHttpOnlySafe() {
}
/**
- * Check if there is sufficent entropy in php's built-in session generation
+ * Check if there is sufficient entropy in php's built-in session generation
* @return bool true = there is sufficient entropy
*/
function wfCheckEntropy() {
@@ -3414,7 +3484,7 @@ function wfSplitWikiID( $wiki ) {
* belongs to. May contain a single string if the query is only
* in one group.
*
- * @param $wiki String: the wiki ID, or false for the current wiki
+ * @param string $wiki the wiki ID, or false for the current wiki
*
* Note: multiple calls to wfGetDB(DB_SLAVE) during the course of one request
* will always return the same object, unless the underlying connection or load
@@ -3432,7 +3502,7 @@ function &wfGetDB( $db, $groups = array(), $wiki = false ) {
/**
* Get a load balancer object.
*
- * @param $wiki String: wiki ID, or false for the current wiki
+ * @param string $wiki wiki ID, or false for the current wiki
* @return LoadBalancer
*/
function wfGetLB( $wiki = false ) {
@@ -3452,8 +3522,8 @@ function &wfGetLBFactory() {
* Find a file.
* Shortcut for RepoGroup::singleton()->findFile()
*
- * @param $title String or Title object
- * @param $options array Associative array of options:
+ * @param string $title or Title object
+ * @param array $options Associative array of options:
* time: requested time for an archived image, or false for the
* current version. An image object will be returned which was
* created at the specified time.
@@ -3511,7 +3581,7 @@ function wfQueriesMustScale() {
* extensions; this is a wrapper around $wgScriptExtension etc.
* except for 'index' and 'load' which use $wgScript/$wgLoadScript
*
- * @param $script String: script filename, sans extension
+ * @param string $script script filename, sans extension
* @return String
*/
function wfScript( $script = 'index' ) {
@@ -3560,16 +3630,6 @@ function wfBoolToStr( $value ) {
}
/**
- * Load an extension messages file
- *
- * @deprecated since 1.16, warnings in 1.18, remove in 1.20
- * @codeCoverageIgnore
- */
-function wfLoadExtensionMessages() {
- wfDeprecated( __FUNCTION__, '1.16' );
-}
-
-/**
* Get a platform-independent path to the null file, e.g. /dev/null
*
* @return string
@@ -3642,8 +3702,8 @@ function wfCountDown( $n ) {
* characters before hashing.
* @return string
* @codeCoverageIgnore
- * @deprecated since 1.20; Please use MWCryptRand for security purposes and wfRandomString for pesudo-random strings
- * @warning This method is NOT secure. Additionally it has many callers that use it for pesudo-random purposes.
+ * @deprecated since 1.20; Please use MWCryptRand for security purposes and wfRandomString for pseudo-random strings
+ * @warning This method is NOT secure. Additionally it has many callers that use it for pseudo-random purposes.
*/
function wfGenerateToken( $salt = '' ) {
wfDeprecated( __METHOD__, '1.20' );
@@ -3732,7 +3792,7 @@ function wfShorthandToInteger( $string = '' ) {
* Get the normalised IETF language tag
* See unit test for examples.
*
- * @param $code String: The language code.
+ * @param string $code The language code.
* @return String: The language code which complying with BCP 47 standards.
*/
function wfBCP47( $code ) {
@@ -3815,8 +3875,8 @@ function wfGetLangConverterCacheStorage() {
/**
* Call hook functions defined in $wgHooks
*
- * @param $event String: event name
- * @param $args Array: parameters passed to hook functions
+ * @param string $event event name
+ * @param array $args parameters passed to hook functions
* @return Boolean True if no handler aborted the hook
*/
function wfRunHooks( $event, $args = array() ) {
@@ -3826,9 +3886,9 @@ function wfRunHooks( $event, $args = array() ) {
/**
* Wrapper around php's unpack.
*
- * @param $format String: The format string (See php's docs)
+ * @param string $format The format string (See php's docs)
* @param $data: A binary string of binary data
- * @param $length integer or false: The minimun length of $data. This is to
+ * @param $length integer or false: The minimum length of $data. This is to
* prevent reading beyond the end of $data. false to disable the check.
*
* Also be careful when using this function to read unsigned 32 bit integer
@@ -3868,9 +3928,9 @@ function wfUnpack( $format, $data, $length=false ) {
* * Any subsequent links on the same line are considered to be exceptions,
* i.e. articles where the image may occur inline.
*
- * @param $name string the image name to check
+ * @param string $name the image name to check
* @param $contextTitle Title|bool the page on which the image occurs, if known
- * @param $blacklist string wikitext of a file blacklist
+ * @param string $blacklist wikitext of a file blacklist
* @return bool
*/
function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) {
diff --git a/includes/HTMLForm.php b/includes/HTMLForm.php
index 6f89d5b8..68639739 100644
--- a/includes/HTMLForm.php
+++ b/includes/HTMLForm.php
@@ -112,6 +112,7 @@ class HTMLForm extends ContextSource {
'submit' => 'HTMLSubmitField',
'hidden' => 'HTMLHiddenField',
'edittools' => 'HTMLEditTools',
+ 'checkmatrix' => 'HTMLCheckMatrix',
// HTMLTextField will output the correct type="" attribute automagically.
// There are about four zillion other HTML5 input types, like url, but
@@ -189,10 +190,10 @@ class HTMLForm extends ContextSource {
/**
* Build a new HTMLForm from an array of field attributes
- * @param $descriptor Array of Field constructs, as described above
+ * @param array $descriptor of Field constructs, as described above
* @param $context IContextSource available since 1.18, will become compulsory in 1.18.
* Obviates the need to call $form->setTitle()
- * @param $messagePrefix String a prefix to go in front of default messages
+ * @param string $messagePrefix a prefix to go in front of default messages
*/
public function __construct( $descriptor, /*IContextSource*/ $context = null, $messagePrefix = '' ) {
if ( $context instanceof IContextSource ) {
@@ -247,8 +248,9 @@ class HTMLForm extends ContextSource {
/**
* Set format in which to display the form
- * @param $format String the name of the format to use, must be one of
+ * @param string $format the name of the format to use, must be one of
* $this->availableDisplayFormats
+ * @throws MWException
* @since 1.20
* @return HTMLForm $this for chaining calls (since 1.20)
*/
@@ -279,7 +281,8 @@ class HTMLForm extends ContextSource {
/**
* Initialise a new Object for the field
* @param $fieldname string
- * @param $descriptor string input Descriptor, as described above
+ * @param string $descriptor input Descriptor, as described above
+ * @throws MWException
* @return HTMLFormField subclass
*/
static function loadInputFromParameters( $fieldname, $descriptor ) {
@@ -313,6 +316,7 @@ class HTMLForm extends ContextSource {
* @attention When doing method chaining, that should be the very last
* method call before displayForm().
*
+ * @throws MWException
* @return HTMLForm $this for chaining calls (since 1.20)
*/
function prepareForm() {
@@ -374,11 +378,12 @@ class HTMLForm extends ContextSource {
}
/**
- * Validate all the fields, and call the submision callback
+ * Validate all the fields, and call the submission callback
* function if everything is kosher.
+ * @throws MWException
* @return Mixed Bool true == Successful submission, Bool false
- * == No submission attempted, anything else == Error to
- * display.
+ * == No submission attempted, anything else == Error to
+ * display.
*/
function trySubmit() {
# Check for validation
@@ -412,7 +417,7 @@ class HTMLForm extends ContextSource {
/**
* Set a callback to a function to do something with the form
* once it's been successfully validated.
- * @param $cb String function name. The function will be passed
+ * @param string $cb function name. The function will be passed
* the output from HTMLForm::filterDataForSubmit, and must
* return Bool true on success, Bool false if no submission
* was attempted, or String HTML output to display on error.
@@ -436,7 +441,7 @@ class HTMLForm extends ContextSource {
/**
* Set the introductory message, overwriting any existing message.
- * @param $msg String complete text of message to display
+ * @param string $msg complete text of message to display
* @return HTMLForm $this for chaining calls (since 1.20)
*/
function setIntro( $msg ) {
@@ -447,7 +452,7 @@ class HTMLForm extends ContextSource {
/**
* Set the introductory message, overwriting any existing message.
* @since 1.19
- * @param $msg String complete text of message to display
+ * @param string $msg complete text of message to display
* @return HTMLForm $this for chaining calls (since 1.20)
*/
function setPreText( $msg ) {
@@ -457,7 +462,7 @@ class HTMLForm extends ContextSource {
/**
* Add introductory text.
- * @param $msg String complete text of message to display
+ * @param string $msg complete text of message to display
* @return HTMLForm $this for chaining calls (since 1.20)
*/
function addPreText( $msg ) {
@@ -467,8 +472,8 @@ class HTMLForm extends ContextSource {
/**
* Add header text, inside the form.
- * @param $msg String complete text of message to display
- * @param $section string The section to add the header to
+ * @param string $msg complete text of message to display
+ * @param string $section The section to add the header to
* @return HTMLForm $this for chaining calls (since 1.20)
*/
function addHeaderText( $msg, $section = null ) {
@@ -486,7 +491,7 @@ class HTMLForm extends ContextSource {
/**
* Set header text, inside the form.
* @since 1.19
- * @param $msg String complete text of message to display
+ * @param string $msg complete text of message to display
* @param $section The section to add the header to
* @return HTMLForm $this for chaining calls (since 1.20)
*/
@@ -501,8 +506,8 @@ class HTMLForm extends ContextSource {
/**
* Add footer text, inside the form.
- * @param $msg String complete text of message to display
- * @param $section string The section to add the footer text to
+ * @param string $msg complete text of message to display
+ * @param string $section The section to add the footer text to
* @return HTMLForm $this for chaining calls (since 1.20)
*/
function addFooterText( $msg, $section = null ) {
@@ -520,8 +525,8 @@ class HTMLForm extends ContextSource {
/**
* Set footer text, inside the form.
* @since 1.19
- * @param $msg String complete text of message to display
- * @param $section string The section to add the footer text to
+ * @param string $msg complete text of message to display
+ * @param string $section The section to add the footer text to
* @return HTMLForm $this for chaining calls (since 1.20)
*/
function setFooterText( $msg, $section = null ) {
@@ -535,7 +540,7 @@ class HTMLForm extends ContextSource {
/**
* Add text to the end of the display.
- * @param $msg String complete text of message to display
+ * @param string $msg complete text of message to display
* @return HTMLForm $this for chaining calls (since 1.20)
*/
function addPostText( $msg ) {
@@ -545,7 +550,7 @@ class HTMLForm extends ContextSource {
/**
* Set text at the end of the display.
- * @param $msg String complete text of message to display
+ * @param string $msg complete text of message to display
* @return HTMLForm $this for chaining calls (since 1.20)
*/
function setPostText( $msg ) {
@@ -555,8 +560,8 @@ class HTMLForm extends ContextSource {
/**
* Add a hidden field to the output
- * @param $name String field name. This will be used exactly as entered
- * @param $value String field value
+ * @param string $name field name. This will be used exactly as entered
+ * @param string $value field value
* @param $attribs Array
* @return HTMLForm $this for chaining calls (since 1.20)
*/
@@ -568,9 +573,9 @@ class HTMLForm extends ContextSource {
/**
* Add a button to the form
- * @param $name String field name.
- * @param $value String field value
- * @param $id String DOM id for the button (default: null)
+ * @param string $name field name.
+ * @param string $value field value
+ * @param string $id DOM id for the button (default: null)
* @param $attribs Array
* @return HTMLForm $this for chaining calls (since 1.20)
*/
@@ -620,7 +625,7 @@ class HTMLForm extends ContextSource {
/**
* Wrap the form innards in an actual "<form>" element
- * @param $html String HTML contents to wrap.
+ * @param string $html HTML contents to wrap.
* @return String wrapped HTML.
*/
function wrapForm( $html ) {
@@ -635,9 +640,9 @@ class HTMLForm extends ContextSource {
: 'application/x-www-form-urlencoded';
# Attributes
$attribs = array(
- 'action' => $this->mAction === false ? $this->getTitle()->getFullURL() : $this->mAction,
- 'method' => $this->mMethod,
- 'class' => 'visualClear',
+ 'action' => $this->mAction === false ? $this->getTitle()->getFullURL() : $this->mAction,
+ 'method' => $this->mMethod,
+ 'class' => 'visualClear',
'enctype' => $encType,
);
if ( !empty( $this->mId ) ) {
@@ -708,8 +713,8 @@ class HTMLForm extends ContextSource {
foreach ( $this->mButtons as $button ) {
$attrs = array(
- 'type' => 'submit',
- 'name' => $button['name'],
+ 'type' => 'submit',
+ 'name' => $button['name'],
'value' => $button['value']
);
@@ -760,7 +765,7 @@ class HTMLForm extends ContextSource {
/**
* Format a stack of error messages into a single HTML string
- * @param $errors Array of message keys/values
+ * @param array $errors of message keys/values
* @return String HTML, a "<ul>" list of errors
*/
public static function formatErrors( $errors ) {
@@ -788,7 +793,7 @@ class HTMLForm extends ContextSource {
/**
* Set the text for the submit button
- * @param $t String plaintext.
+ * @param string $t plaintext.
* @return HTMLForm $this for chaining calls (since 1.20)
*/
function setSubmitText( $t ) {
@@ -799,7 +804,7 @@ class HTMLForm extends ContextSource {
/**
* Set the text for the submit button to a message
* @since 1.19
- * @param $msg String message key
+ * @param string $msg message key
* @return HTMLForm $this for chaining calls (since 1.20)
*/
public function setSubmitTextMsg( $msg ) {
@@ -818,7 +823,7 @@ class HTMLForm extends ContextSource {
}
/**
- * @param $name String Submit button name
+ * @param string $name Submit button name
* @return HTMLForm $this for chaining calls (since 1.20)
*/
public function setSubmitName( $name ) {
@@ -827,7 +832,7 @@ class HTMLForm extends ContextSource {
}
/**
- * @param $name String Tooltip for the submit button
+ * @param string $name Tooltip for the submit button
* @return HTMLForm $this for chaining calls (since 1.20)
*/
public function setSubmitTooltip( $name ) {
@@ -847,7 +852,7 @@ class HTMLForm extends ContextSource {
}
/**
- * @param $id String DOM id for the form
+ * @param string $id DOM id for the form
* @return HTMLForm $this for chaining calls (since 1.20)
*/
public function setId( $id ) {
@@ -857,7 +862,7 @@ class HTMLForm extends ContextSource {
/**
* Prompt the whole form to be wrapped in a "<fieldset>", with
* this text as its "<legend>" element.
- * @param $legend String HTML to go inside the "<legend>" element.
+ * @param string $legend HTML to go inside the "<legend>" element.
* Will be escaped
* @return HTMLForm $this for chaining calls (since 1.20)
*/
@@ -870,7 +875,7 @@ class HTMLForm extends ContextSource {
* Prompt the whole form to be wrapped in a "<fieldset>", with
* this message as its "<legend>" element.
* @since 1.19
- * @param $msg String message key
+ * @param string $msg message key
* @return HTMLForm $this for chaining calls (since 1.20)
*/
public function setWrapperLegendMsg( $msg ) {
@@ -881,7 +886,7 @@ class HTMLForm extends ContextSource {
/**
* Set the prefix for various default messages
* @todo currently only used for the "<fieldset>" legend on forms
- * with multiple sections; should be used elsewhre?
+ * with multiple sections; should be used elsewhere?
* @param $p String
* @return HTMLForm $this for chaining calls (since 1.20)
*/
@@ -927,8 +932,8 @@ class HTMLForm extends ContextSource {
/**
* @todo Document
* @param $fields array[]|HTMLFormField[] array of fields (either arrays or objects)
- * @param $sectionName string ID attribute of the "<table>" tag for this section, ignored if empty
- * @param $fieldsetIDPrefix string ID prefix for the "<fieldset>" tag of each subsection, ignored if empty
+ * @param string $sectionName ID attribute of the "<table>" tag for this section, ignored if empty
+ * @param string $fieldsetIDPrefix ID prefix for the "<fieldset>" tag of each subsection, ignored if empty
* @return String
*/
public function displaySection( $fields, $sectionName = '', $fieldsetIDPrefix = '' ) {
@@ -952,7 +957,7 @@ class HTMLForm extends ContextSource {
$hasLabel = true;
}
} elseif ( is_array( $value ) ) {
- $section = $this->displaySection( $value, $key );
+ $section = $this->displaySection( $value, $key, "$fieldsetIDPrefix$key-" );
$legend = $this->getLegend( $key );
if ( isset( $this->mSectionHeaders[$key] ) ) {
$section = $this->mSectionHeaders[$key] . $section;
@@ -1025,7 +1030,7 @@ class HTMLForm extends ContextSource {
/**
* Stop a reset button being shown for this form
- * @param $suppressReset Bool set to false to re-enable the
+ * @param bool $suppressReset set to false to re-enable the
* button again
* @return HTMLForm $this for chaining calls (since 1.20)
*/
@@ -1095,7 +1100,7 @@ abstract class HTMLFormField {
* This function must be implemented to return the HTML to generate
* the input object itself. It should not implement the surrounding
* table cells/rows, or labels/help messages.
- * @param $value String the value to set the input to; eg a default
+ * @param string $value the value to set the input to; eg a default
* text for a text input.
* @return String valid HTML.
*/
@@ -1104,7 +1109,7 @@ abstract class HTMLFormField {
/**
* Get a translated interface message
*
- * This is a wrapper arround $this->mParent->msg() if $this->mParent is set
+ * This is a wrapper around $this->mParent->msg() if $this->mParent is set
* and wfMessage() otherwise.
*
* Parameters are the same as wfMessage().
@@ -1127,8 +1132,8 @@ abstract class HTMLFormField {
* Override this function to add specific validation checks on the
* field input. Don't forget to call parent::validate() to ensure
* that the user-defined callback mValidationCallback is still run
- * @param $value String the value the field was submitted with
- * @param $alldata Array the data collected from the form
+ * @param string $value the value the field was submitted with
+ * @param array $alldata the data collected from the form
* @return Mixed Bool true on success, or String error to display.
*/
function validate( $value, $alldata ) {
@@ -1177,7 +1182,8 @@ abstract class HTMLFormField {
/**
* Initialise the object
- * @param $params array Associative Array. See HTMLForm doc for syntax.
+ * @param array $params Associative Array. See HTMLForm doc for syntax.
+ * @throws MWException
*/
function __construct( $params ) {
$this->mParams = $params;
@@ -1245,7 +1251,7 @@ abstract class HTMLFormField {
/**
* Get the complete table row for the input, including help text,
* labels, and whatever.
- * @param $value String the value to set the input to.
+ * @param string $value the value to set the input to.
* @return String complete HTML table row.
*/
function getTableRow( $value ) {
@@ -1289,7 +1295,7 @@ abstract class HTMLFormField {
* Get the complete div for the input, including help text,
* labels, and whatever.
* @since 1.20
- * @param $value String the value to set the input to.
+ * @param string $value the value to set the input to.
* @return String complete HTML table row.
*/
public function getDiv( $value ) {
@@ -1316,13 +1322,12 @@ abstract class HTMLFormField {
* Get the complete raw fields for the input, including help text,
* labels, and whatever.
* @since 1.20
- * @param $value String the value to set the input to.
+ * @param string $value the value to set the input to.
* @return String complete HTML table row.
*/
public function getRaw( $value ) {
- list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
+ list( $errors, ) = $this->getErrorsAndErrorClass( $value );
$inputHtml = $this->getInputHTML( $value );
- $fieldType = get_class( $this );
$helptext = $this->getHelpTextHtmlRaw( $this->getHelpText() );
$cellAttributes = array();
$label = $this->getLabelHtml( $cellAttributes );
@@ -1415,7 +1420,7 @@ abstract class HTMLFormField {
/**
* Determine form errors to display and their classes
* @since 1.20
- * @param $value String the value of the input
+ * @param string $value the value of the input
* @return Array
*/
public function getErrorsAndErrorClass( $value ) {
@@ -1483,7 +1488,7 @@ abstract class HTMLFormField {
/**
* flatten an array of options to a single array, for instance,
* a set of "<options>" inside "<optgroups>".
- * @param $options array Associative Array with values either Strings
+ * @param array $options Associative Array with values either Strings
* or Arrays
* @return Array flattened input
*/
@@ -1778,6 +1783,170 @@ class HTMLCheckField extends HTMLFormField {
}
/**
+ * A checkbox matrix
+ * Operates similarly to HTMLMultiSelectField, but instead of using an array of
+ * options, uses an array of rows and an array of columns to dynamically
+ * construct a matrix of options.
+ */
+class HTMLCheckMatrix extends HTMLFormField {
+
+ function validate( $value, $alldata ) {
+ $rows = $this->mParams['rows'];
+ $columns = $this->mParams['columns'];
+
+ // Make sure user-defined validation callback is run
+ $p = parent::validate( $value, $alldata );
+ if ( $p !== true ) {
+ return $p;
+ }
+
+ // Make sure submitted value is an array
+ if ( !is_array( $value ) ) {
+ return false;
+ }
+
+ // If all options are valid, array_intersect of the valid options
+ // and the provided options will return the provided options.
+ $validOptions = array();
+ foreach ( $rows as $rowTag ) {
+ foreach ( $columns as $columnTag ) {
+ $validOptions[] = $columnTag . '-' . $rowTag;
+ }
+ }
+ $validValues = array_intersect( $value, $validOptions );
+ if ( count( $validValues ) == count( $value ) ) {
+ return true;
+ } else {
+ return $this->msg( 'htmlform-select-badoption' )->parse();
+ }
+ }
+
+ /**
+ * Build a table containing a matrix of checkbox options.
+ * The value of each option is a combination of the row tag and column tag.
+ * mParams['rows'] is an array with row labels as keys and row tags as values.
+ * mParams['columns'] is an array with column labels as keys and column tags as values.
+ * @param array $value of the options that should be checked
+ * @return String
+ */
+ function getInputHTML( $value ) {
+ $html = '';
+ $tableContents = '';
+ $attribs = array();
+ $rows = $this->mParams['rows'];
+ $columns = $this->mParams['columns'];
+
+ // If the disabled param is set, disable all the options
+ if ( !empty( $this->mParams['disabled'] ) ) {
+ $attribs['disabled'] = 'disabled';
+ }
+
+ // Build the column headers
+ $headerContents = Html::rawElement( 'td', array(), '&#160;' );
+ foreach ( $columns as $columnLabel => $columnTag ) {
+ $headerContents .= Html::rawElement( 'td', array(), $columnLabel );
+ }
+ $tableContents .= Html::rawElement( 'tr', array(), "\n$headerContents\n" );
+
+ // Build the options matrix
+ foreach ( $rows as $rowLabel => $rowTag ) {
+ $rowContents = Html::rawElement( 'td', array(), $rowLabel );
+ foreach ( $columns as $columnTag ) {
+ // Knock out any options that are not wanted
+ if ( isset( $this->mParams['remove-options'] )
+ && in_array( "$columnTag-$rowTag", $this->mParams['remove-options'] ) )
+ {
+ $rowContents .= Html::rawElement( 'td', array(), '&#160;' );
+ } else {
+ // Construct the checkbox
+ $thisAttribs = array(
+ 'id' => "{$this->mID}-$columnTag-$rowTag",
+ 'value' => $columnTag . '-' . $rowTag
+ );
+ $checkbox = Xml::check(
+ $this->mName . '[]',
+ in_array( $columnTag . '-' . $rowTag, (array)$value, true ),
+ $attribs + $thisAttribs );
+ $rowContents .= Html::rawElement( 'td', array(), $checkbox );
+ }
+ }
+ $tableContents .= Html::rawElement( 'tr', array(), "\n$rowContents\n" );
+ }
+
+ // Put it all in a table
+ $html .= Html::rawElement( 'table', array( 'class' => 'mw-htmlform-matrix' ),
+ Html::rawElement( 'tbody', array(), "\n$tableContents\n" ) ) . "\n";
+
+ return $html;
+ }
+
+ /**
+ * Get the complete table row for the input, including help text,
+ * labels, and whatever.
+ * We override this function since the label should always be on a separate
+ * line above the options in the case of a checkbox matrix, i.e. it's always
+ * a "vertical-label".
+ * @param string $value the value to set the input to
+ * @return String complete HTML table row
+ */
+ function getTableRow( $value ) {
+ list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
+ $inputHtml = $this->getInputHTML( $value );
+ $fieldType = get_class( $this );
+ $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() );
+ $cellAttributes = array( 'colspan' => 2 );
+
+ $label = $this->getLabelHtml( $cellAttributes );
+
+ $field = Html::rawElement(
+ 'td',
+ array( 'class' => 'mw-input' ) + $cellAttributes,
+ $inputHtml . "\n$errors"
+ );
+
+ $html = Html::rawElement( 'tr',
+ array( 'class' => 'mw-htmlform-vertical-label' ), $label );
+ $html .= Html::rawElement( 'tr',
+ array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
+ $field );
+
+ return $html . $helptext;
+ }
+
+ /**
+ * @param $request WebRequest
+ * @return Array
+ */
+ function loadDataFromRequest( $request ) {
+ if ( $this->mParent->getMethod() == 'post' ) {
+ if ( $request->wasPosted() ) {
+ // Checkboxes are not added to the request arrays if they're not checked,
+ // so it's perfectly possible for there not to be an entry at all
+ return $request->getArray( $this->mName, array() );
+ } else {
+ // That's ok, the user has not yet submitted the form, so show the defaults
+ return $this->getDefault();
+ }
+ } else {
+ // This is the impossible case: if we look at $_GET and see no data for our
+ // field, is it because the user has not yet submitted the form, or that they
+ // have submitted it with all the options unchecked. We will have to assume the
+ // latter, which basically means that you can't specify 'positive' defaults
+ // for GET forms.
+ return $request->getArray( $this->mName, array() );
+ }
+ }
+
+ function getDefault() {
+ if ( isset( $this->mDefault ) ) {
+ return $this->mDefault;
+ } else {
+ return array();
+ }
+ }
+}
+
+/**
* A select dropdown field. Basically a wrapper for Xmlselect class
*/
class HTMLSelectField extends HTMLFormField {
@@ -2053,8 +2222,8 @@ class HTMLSelectAndOtherField extends HTMLSelectField {
/**
* Build a drop-down box from a textual list.
- * @param $string String message text
- * @param $otherName String name of "other reason" option
+ * @param string $string message text
+ * @param string $otherName name of "other reason" option
* @return Array
* TODO: this is copied from Xml::listDropDown(), deprecate/avoid duplication?
*/
@@ -2188,7 +2357,6 @@ class HTMLSelectAndOtherField extends HTMLSelectField {
*/
class HTMLRadioField extends HTMLFormField {
-
function validate( $value, $alldata ) {
$p = parent::validate( $value, $alldata );
diff --git a/includes/HistoryBlob.php b/includes/HistoryBlob.php
index bb8ec5e3..1af733a2 100644
--- a/includes/HistoryBlob.php
+++ b/includes/HistoryBlob.php
@@ -19,10 +19,10 @@
*
* @file
*/
-
+
/**
- * Base class for general text storage via the "object" flag in old_flags, or
- * two-part external storage URLs. Used for represent efficient concatenated
+ * Base class for general text storage via the "object" flag in old_flags, or
+ * two-part external storage URLs. Used for represent efficient concatenated
* storage, and migration-related pointer objects.
*/
interface HistoryBlob
@@ -178,12 +178,11 @@ class ConcatenatedGzipHistoryBlob implements HistoryBlob
* @return bool
*/
public function isHappy() {
- return $this->mSize < $this->mMaxSize
+ return $this->mSize < $this->mMaxSize
&& count( $this->mItems ) < $this->mMaxCount;
}
}
-
/**
* Pointer object for an item within a CGZ blob stored in the text table.
*/
@@ -199,7 +198,7 @@ class HistoryBlobStub {
var $mOldId, $mHash, $mRef;
/**
- * @param $hash string the content hash of the text
+ * @param string $hash the content hash of the text
* @param $oldid Integer the old_id for the CGZ object
*/
function __construct( $hash = '', $oldid = 0 ) {
@@ -232,8 +231,6 @@ class HistoryBlobStub {
* @return string
*/
function getText() {
- $fname = 'HistoryBlobStub::getText';
-
if( isset( self::$blobCache[$this->mOldId] ) ) {
$obj = self::$blobCache[$this->mOldId];
} else {
@@ -244,13 +241,12 @@ class HistoryBlobStub {
}
$flags = explode( ',', $row->old_flags );
if( in_array( 'external', $flags ) ) {
- $url=$row->old_text;
+ $url = $row->old_text;
$parts = explode( '://', $url, 2 );
if ( !isset( $parts[1] ) || $parts[1] == '' ) {
- wfProfileOut( $fname );
return false;
}
- $row->old_text = ExternalStore::fetchFromUrl($url);
+ $row->old_text = ExternalStore::fetchFromUrl( $url );
}
if( !in_array( 'object', $flags ) ) {
@@ -288,7 +284,6 @@ class HistoryBlobStub {
}
}
-
/**
* To speed up conversion from 1.4 to 1.5 schema, text rows can refer to the
* leftover cur table as the backend. This avoids expensively copying hundreds
@@ -341,12 +336,12 @@ class DiffHistoryBlob implements HistoryBlob {
/** Total uncompressed size */
var $mSize = 0;
- /**
- * Array of diffs. If a diff D from A to B is notated D = B - A, and Z is
+ /**
+ * Array of diffs. If a diff D from A to B is notated D = B - A, and Z is
* an empty string:
*
* { item[map[i]] - item[map[i-1]] where i > 0
- * diff[i] = {
+ * diff[i] = {
* { item[map[i]] - Z where i = 0
*/
var $mDiffs;
@@ -379,7 +374,7 @@ class DiffHistoryBlob implements HistoryBlob {
* The maximum number of text items before the object becomes sad
*/
var $mMaxCount = 100;
-
+
/** Constants from xdiff.h */
const XDL_BDOP_INS = 1;
const XDL_BDOP_CPY = 2;
@@ -398,7 +393,7 @@ class DiffHistoryBlob implements HistoryBlob {
*/
function addItem( $text ) {
if ( $this->mFrozen ) {
- throw new MWException( __METHOD__.": Cannot add more items after sleep/wakeup" );
+ throw new MWException( __METHOD__ . ": Cannot add more items after sleep/wakeup" );
}
$this->mItems[] = $text;
@@ -433,7 +428,7 @@ class DiffHistoryBlob implements HistoryBlob {
* @throws MWException
*/
function compress() {
- if ( !function_exists( 'xdiff_string_rabdiff' ) ){
+ if ( !function_exists( 'xdiff_string_rabdiff' ) ) {
throw new MWException( "Need xdiff 1.5+ support to write DiffHistoryBlob\n" );
}
if ( isset( $this->mDiffs ) ) {
@@ -534,18 +529,18 @@ class DiffHistoryBlob implements HistoryBlob {
# Pure PHP implementation
$header = unpack( 'Vofp/Vcsize', substr( $diff, 0, 8 ) );
-
+
# Check the checksum if hash/mhash is available
$ofp = $this->xdiffAdler32( $base );
if ( $ofp !== false && $ofp !== substr( $diff, 0, 4 ) ) {
- wfDebug( __METHOD__. ": incorrect base checksum\n" );
+ wfDebug( __METHOD__ . ": incorrect base checksum\n" );
return false;
}
if ( $header['csize'] != strlen( $base ) ) {
- wfDebug( __METHOD__. ": incorrect base length\n" );
+ wfDebug( __METHOD__ . ": incorrect base length\n" );
return false;
}
-
+
$p = 8;
$out = '';
while ( $p < strlen( $diff ) ) {
@@ -571,7 +566,7 @@ class DiffHistoryBlob implements HistoryBlob {
$out .= substr( $base, $x['off'], $x['csize'] );
break;
default:
- wfDebug( __METHOD__.": invalid op\n" );
+ wfDebug( __METHOD__ . ": invalid op\n" );
return false;
}
}
@@ -579,7 +574,7 @@ class DiffHistoryBlob implements HistoryBlob {
}
/**
- * Compute a binary "Adler-32" checksum as defined by LibXDiff, i.e. with
+ * Compute a binary "Adler-32" checksum as defined by LibXDiff, i.e. with
* the bytes backwards and initialised with 0 instead of 1. See bug 34428.
*
* Returns false if no hashing library is available
@@ -589,8 +584,8 @@ class DiffHistoryBlob implements HistoryBlob {
if ( $init === null ) {
$init = str_repeat( "\xf0", 205 ) . "\xee" . str_repeat( "\xf0", 67 ) . "\x02";
}
- // The real Adler-32 checksum of $init is zero, so it initialises the
- // state to zero, as it is at the start of LibXDiff's checksum
+ // The real Adler-32 checksum of $init is zero, so it initialises the
+ // state to zero, as it is at the start of LibXDiff's checksum
// algorithm. Appending the subject string then simulates LibXDiff.
if ( function_exists( 'hash' ) ) {
$hash = hash( 'adler32', $init . $s, true );
@@ -664,7 +659,7 @@ class DiffHistoryBlob implements HistoryBlob {
if ( isset( $info['base'] ) ) {
// Old format
$this->mDiffMap = range( 0, count( $this->mDiffs ) - 1 );
- array_unshift( $this->mDiffs,
+ array_unshift( $this->mDiffs,
pack( 'VVCV', 0, 0, self::XDL_BDOP_INSB, strlen( $info['base'] ) ) .
$info['base'] );
} else {
@@ -687,7 +682,7 @@ class DiffHistoryBlob implements HistoryBlob {
* @return bool
*/
function isHappy() {
- return $this->mSize < $this->mMaxSize
+ return $this->mSize < $this->mMaxSize
&& count( $this->mItems ) < $this->mMaxCount;
}
diff --git a/includes/Hooks.php b/includes/Hooks.php
index bc39f2fc..8cc7acea 100644
--- a/includes/Hooks.php
+++ b/includes/Hooks.php
@@ -23,23 +23,47 @@
* @file
*/
+/**
+ * @since 1.18
+ */
class MWHookException extends MWException {}
/**
* Hooks class.
*
* Used to supersede $wgHooks, because globals are EVIL.
+ *
+ * @since 1.18
*/
class Hooks {
protected static $handlers = array();
/**
+ * Clears hooks registered via Hooks::register(). Does not touch $wgHooks.
+ * This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined.
+ *
+ * @since 1.21
+ *
+ * @param string $name the name of the hook to clear.
+ *
+ * @throws MWException if not in testing mode.
+ */
+ public static function clear( $name ) {
+ if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+ throw new MWException( 'can not reset hooks in operation.' );
+ }
+
+ unset( self::$handlers[$name] );
+ }
+
+ /**
* Attach an event handler to a given hook
*
- * @param $name Mixed: name of hook
+ * @since 1.18
+ *
+ * @param string $name name of hook
* @param $callback Mixed: callback function to attach
- * @return void
*/
public static function register( $name, $callback ) {
if( !isset( self::$handlers[$name] ) ) {
@@ -51,69 +75,84 @@ class Hooks {
/**
* Returns true if a hook has a function registered to it.
+ * The function may have been registered either via Hooks::register or in $wgHooks.
+ *
+ * @since 1.18
*
- * @param $name Mixed: name of hook
- * @return Boolean: true if a hook has a function registered to it
+ * @param string $name name of hook
+ * @return Boolean: true if the hook has a function registered to it
*/
public static function isRegistered( $name ) {
- if( !isset( self::$handlers[$name] ) ) {
- self::$handlers[$name] = array();
- }
+ global $wgHooks;
- return ( count( self::$handlers[$name] ) != 0 );
+ return !empty( $wgHooks[$name] ) || !empty( self::$handlers[$name] );
}
/**
* Returns an array of all the event functions attached to a hook
+ * This combines functions registered via Hooks::register and with $wgHooks.
+ * @since 1.18
+ *
+ * @throws MWException
+ * @throws FatalError
+ * @param string $name name of the hook
*
- * @param $name Mixed: name of the hook
* @return array
*/
public static function getHandlers( $name ) {
- if( !isset( self::$handlers[$name] ) ) {
+ global $wgHooks;
+
+ // Return quickly in the most common case
+ if ( empty( self::$handlers[$name] ) && empty( $wgHooks[$name] ) ) {
return array();
}
- return self::$handlers[$name];
+ if ( !is_array( self::$handlers ) ) {
+ throw new MWException( "Local hooks array is not an array!\n" );
+ }
+
+ if ( !is_array( $wgHooks ) ) {
+ throw new MWException( "Global hooks array is not an array!\n" );
+ }
+
+ if ( empty( Hooks::$handlers[$name] ) ) {
+ $hooks = $wgHooks[$name];
+ } elseif ( empty( $wgHooks[$name] ) ) {
+ $hooks = Hooks::$handlers[$name];
+ } else {
+ // so they are both not empty...
+ $hooks = array_merge( Hooks::$handlers[$name], $wgHooks[$name] );
+ }
+
+ if ( !is_array( $hooks ) ) {
+ throw new MWException( "Hooks array for event '$name' is not an array!\n" );
+ }
+
+ return $hooks;
}
/**
* Call hook functions defined in Hooks::register
*
- * Because programmers assign to $wgHooks, we need to be very
- * careful about its contents. So, there's a lot more error-checking
- * in here than would normally be necessary.
+ * @param string $event event name
+ * @param $args Array: parameters passed to hook functions
*
- * @param $event String: event name
- * @param $args Array: parameters passed to hook functions
+ * @throws MWException
+ * @throws FatalError
* @return Boolean True if no handler aborted the hook
*/
public static function run( $event, $args = array() ) {
global $wgHooks;
// Return quickly in the most common case
- if ( !isset( self::$handlers[$event] ) && !isset( $wgHooks[$event] ) ) {
+ if ( empty( self::$handlers[$event] ) && empty( $wgHooks[$event] ) ) {
return true;
}
- if ( !is_array( self::$handlers ) ) {
- throw new MWException( "Local hooks array is not an array!\n" );
- }
-
- if ( !is_array( $wgHooks ) ) {
- throw new MWException( "Global hooks array is not an array!\n" );
- }
+ wfProfileIn( 'hook: ' . $event );
+ $hooks = self::getHandlers( $event );
- $new_handlers = (array) self::$handlers;
- $old_handlers = (array) $wgHooks;
-
- $hook_array = array_merge( $new_handlers, $old_handlers );
-
- if ( !is_array( $hook_array[$event] ) ) {
- throw new MWException( "Hooks array for event '$event' is not an array!\n" );
- }
-
- foreach ( $hook_array[$event] as $index => $hook ) {
+ foreach ( $hooks as $hook ) {
$object = null;
$method = null;
$func = null;
@@ -131,7 +170,7 @@ class Hooks {
if ( count( $hook ) < 1 ) {
throw new MWException( 'Empty array in hooks for ' . $event . "\n" );
} elseif ( is_object( $hook[0] ) ) {
- $object = $hook_array[$event][$index][0];
+ $object = $hook[0];
if ( $object instanceof Closure ) {
$closure = true;
if ( count( $hook ) > 1 ) {
@@ -161,7 +200,7 @@ class Hooks {
} elseif ( is_string( $hook ) ) { # functions look like strings, too
$func = $hook;
} elseif ( is_object( $hook ) ) {
- $object = $hook_array[$event][$index];
+ $object = $hook;
if ( $object instanceof Closure ) {
$closure = true;
} else {
@@ -249,18 +288,23 @@ class Hooks {
);
}
} elseif ( !$retval ) {
+ wfProfileOut( 'hook: ' . $event );
return false;
}
}
+ wfProfileOut( 'hook: ' . $event );
return true;
}
/**
* This REALLY should be protected... but it's public for compatibility
*
- * @param $errno int Unused
- * @param $errstr String: error message
+ * @since 1.18
+ *
+ * @param int $errno Unused
+ * @param string $errstr error message
+ * @throws MWHookException
* @return Boolean: false
*/
public static function hookErrorHandler( $errno, $errstr ) {
diff --git a/includes/Html.php b/includes/Html.php
index b33d6fbb..af4b4bbf 100644
--- a/includes/Html.php
+++ b/includes/Html.php
@@ -48,7 +48,7 @@
* @since 1.16
*/
class Html {
- # List of void elements from HTML5, section 8.1.2 as of 2011-08-12
+ // List of void elements from HTML5, section 8.1.2 as of 2011-08-12
private static $voidElements = array(
'area',
'base',
@@ -68,8 +68,8 @@ class Html {
'wbr',
);
- # Boolean attributes, which may have the value omitted entirely. Manually
- # collected from the HTML5 spec as of 2011-08-12.
+ // Boolean attributes, which may have the value omitted entirely. Manually
+ // collected from the HTML5 spec as of 2011-08-12.
private static $boolAttribs = array(
'async',
'autofocus',
@@ -97,7 +97,7 @@ class Html {
'selected',
'truespeed',
'typemustmatch',
- # HTML5 Microdata
+ // HTML5 Microdata
'itemscope',
);
@@ -126,11 +126,11 @@ class Html {
* content model. If $wgWellFormedXml is false, then a few bytes will be
* shaved off the HTML output as well.
*
- * @param $element string The element's name, e.g., 'a'
- * @param $attribs array Associative array of attributes, e.g., array(
+ * @param string $element The element's name, e.g., 'a'
+ * @param array $attribs Associative array of attributes, e.g., array(
* 'href' => 'http://www.mediawiki.org/' ). See expandAttributes() for
* further documentation.
- * @param $contents string The raw HTML contents of the element: *not*
+ * @param string $contents The raw HTML contents of the element: *not*
* escaped!
* @return string Raw HTML
*/
@@ -139,7 +139,7 @@ class Html {
$start = self::openElement( $element, $attribs );
if ( in_array( $element, self::$voidElements ) ) {
if ( $wgWellFormedXml ) {
- # Silly XML.
+ // Silly XML.
return substr( $start, 0, -1 ) . ' />';
}
return $start;
@@ -160,8 +160,8 @@ class Html {
*/
public static function element( $element, $attribs = array(), $contents = '' ) {
return self::rawElement( $element, $attribs, strtr( $contents, array(
- # There's no point in escaping quotes, >, etc. in the contents of
- # elements.
+ // There's no point in escaping quotes, >, etc. in the contents of
+ // elements.
'&' => '&amp;',
'<' => '&lt;'
) ) );
@@ -179,24 +179,20 @@ class Html {
public static function openElement( $element, $attribs = array() ) {
global $wgHtml5, $wgWellFormedXml;
$attribs = (array)$attribs;
- # This is not required in HTML5, but let's do it anyway, for
- # consistency and better compression.
+ // This is not required in HTML5, but let's do it anyway, for
+ // consistency and better compression.
$element = strtolower( $element );
- # In text/html, initial <html> and <head> tags can be omitted under
- # pretty much any sane circumstances, if they have no attributes. See:
- # <http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#optional-tags>
+ // In text/html, initial <html> and <head> tags can be omitted under
+ // pretty much any sane circumstances, if they have no attributes. See:
+ // <http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#optional-tags>
if ( !$wgWellFormedXml && !$attribs
&& in_array( $element, array( 'html', 'head' ) ) ) {
return '';
}
- # Remove HTML5-only attributes if we aren't doing HTML5, and disable
- # form validation regardless (see bug 23769 and the more detailed
- # comment in expandAttributes())
+ // Remove invalid input types
if ( $element == 'input' ) {
- # Whitelist of types that don't cause validation. All except
- # 'search' are valid in XHTML1.
$validTypes = array(
'hidden',
'text',
@@ -208,9 +204,9 @@ class Html {
'image',
'reset',
'button',
- 'search',
);
+ // Allow more input types in HTML5 mode
if( $wgHtml5 ) {
$validTypes = array_merge( $validTypes, array(
'datetime',
@@ -232,17 +228,19 @@ class Html {
&& !in_array( $attribs['type'], $validTypes ) ) {
unset( $attribs['type'] );
}
-
- if ( isset( $attribs['type'] ) && $attribs['type'] == 'search'
- && !$wgHtml5 ) {
- unset( $attribs['type'] );
- }
}
if ( !$wgHtml5 && $element == 'textarea' && isset( $attribs['maxlength'] ) ) {
unset( $attribs['maxlength'] );
}
+ // According to standard the default type for <button> elements is "submit".
+ // Depending on compatibility mode IE might use "button", instead.
+ // We enforce the standard "submit".
+ if ( $element == 'button' && !isset( $attribs['type'] ) ) {
+ $attribs['type'] = 'submit';
+ }
+
return "<$element" . self::expandAttributes(
self::dropDefaults( $element, $attribs ) ) . '>';
}
@@ -252,7 +250,7 @@ class Html {
* it returns the empty string when that's guaranteed to be safe.
*
* @since 1.17
- * @param $element string Name of the element, e.g., 'a'
+ * @param string $element Name of the element, e.g., 'a'
* @return string A closing tag, if required
*/
public static function closeElement( $element ) {
@@ -260,8 +258,8 @@ class Html {
$element = strtolower( $element );
- # Reference:
- # http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#optional-tags
+ // Reference:
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#optional-tags
if ( !$wgWellFormedXml && in_array( $element, array(
'html',
'head',
@@ -289,28 +287,27 @@ class Html {
* only guarantees that the output array should be functionally identical
* to the input array (currently per the HTML 5 draft as of 2009-09-06).
*
- * @param $element string Name of the element, e.g., 'a'
- * @param $attribs array Associative array of attributes, e.g., array(
+ * @param string $element Name of the element, e.g., 'a'
+ * @param array $attribs Associative array of attributes, e.g., array(
* 'href' => 'http://www.mediawiki.org/' ). See expandAttributes() for
* further documentation.
* @return array An array of attributes functionally identical to $attribs
*/
private static function dropDefaults( $element, $attribs ) {
- # Don't bother doing anything if we aren't outputting HTML5; it's too
- # much of a pain to maintain two sets of defaults.
+ // Don't bother doing anything if we aren't outputting HTML5; it's too
+ // much of a pain to maintain two sets of defaults.
global $wgHtml5;
if ( !$wgHtml5 ) {
return $attribs;
}
- # Whenever altering this array, please provide a covering test case
- # in HtmlTest::provideElementsWithAttributesHavingDefaultValues
+ // Whenever altering this array, please provide a covering test case
+ // in HtmlTest::provideElementsWithAttributesHavingDefaultValues
static $attribDefaults = array(
'area' => array( 'shape' => 'rect' ),
'button' => array(
'formaction' => 'GET',
'formenctype' => 'application/x-www-form-urlencoded',
- 'type' => 'submit',
),
'canvas' => array(
'height' => '150',
@@ -329,8 +326,8 @@ class Html {
'keygen' => array( 'keytype' => 'rsa' ),
'link' => array( 'media' => 'all' ),
'menu' => array( 'type' => 'list' ),
- # Note: the use of text/javascript here instead of other JavaScript
- # MIME types follows the HTML5 spec.
+ // Note: the use of text/javascript here instead of other JavaScript
+ // MIME types follows the HTML5 spec.
'script' => array( 'type' => 'text/javascript' ),
'style' => array(
'media' => 'all',
@@ -349,7 +346,7 @@ class Html {
$value = strval( $value );
}
- # Simple checks using $attribDefaults
+ // Simple checks using $attribDefaults
if ( isset( $attribDefaults[$element][$lcattrib] ) &&
$attribDefaults[$element][$lcattrib] == $value ) {
unset( $attribs[$attrib] );
@@ -360,7 +357,7 @@ class Html {
}
}
- # More subtle checks
+ // More subtle checks
if ( $element === 'link' && isset( $attribs['type'] )
&& strval( $attribs['type'] ) == 'text/css' ) {
unset( $attribs['type'] );
@@ -392,12 +389,12 @@ class Html {
if ( in_array( 'multiple', $attribs )
|| ( isset( $attribs['multiple'] ) && $attribs['multiple'] !== false )
) {
- # A multi-select
+ // A multi-select
if ( strval( $attribs['size'] ) == '4' ) {
unset( $attribs['size'] );
}
} else {
- # Single select
+ // Single select
if ( strval( $attribs['size'] ) == '1' ) {
unset( $attribs['size'] );
}
@@ -438,7 +435,7 @@ class Html {
* // gives '<em class="bar quux"></em>'
* @endcode
*
- * @param $attribs array Associative array of attributes, e.g., array(
+ * @param array $attribs Associative array of attributes, e.g., array(
* 'href' => 'http://www.mediawiki.org/' ). Values will be HTML-escaped.
* A value of false means to omit the attribute. For boolean attributes,
* you can omit the key, e.g., array( 'checked' ) instead of
@@ -456,33 +453,32 @@ class Html {
continue;
}
- # For boolean attributes, support array( 'foo' ) instead of
- # requiring array( 'foo' => 'meaningless' ).
+ // For boolean attributes, support array( 'foo' ) instead of
+ // requiring array( 'foo' => 'meaningless' ).
if ( is_int( $key )
&& in_array( strtolower( $value ), self::$boolAttribs ) ) {
$key = $value;
}
- # Not technically required in HTML5, but required in XHTML 1.0,
- # and we'd like consistency and better compression anyway.
+ // Not technically required in HTML5, but required in XHTML 1.0,
+ // and we'd like consistency and better compression anyway.
$key = strtolower( $key );
- # Here we're blacklisting some HTML5-only attributes...
- if ( !$wgHtml5 && in_array( $key, self::$HTMLFiveOnlyAttribs )
- ) {
+ // Here we're blacklisting some HTML5-only attributes...
+ if ( !$wgHtml5 && in_array( $key, self::$HTMLFiveOnlyAttribs ) ) {
continue;
}
- # Bug 23769: Blacklist all form validation attributes for now. Current
- # (June 2010) WebKit has no UI, so the form just refuses to submit
- # without telling the user why, which is much worse than failing
- # server-side validation. Opera is the only other implementation at
- # this time, and has ugly UI, so just kill the feature entirely until
- # we have at least one good implementation.
+ // Bug 23769: Blacklist all form validation attributes for now. Current
+ // (June 2010) WebKit has no UI, so the form just refuses to submit
+ // without telling the user why, which is much worse than failing
+ // server-side validation. Opera is the only other implementation at
+ // this time, and has ugly UI, so just kill the feature entirely until
+ // we have at least one good implementation.
- # As the default value of "1" for "step" rejects decimal
- # numbers to be entered in 'type="number"' fields, allow
- # the special case 'step="any"'.
+ // As the default value of "1" for "step" rejects decimal
+ // numbers to be entered in 'type="number"' fields, allow
+ // the special case 'step="any"'.
if ( in_array( $key, array( 'max', 'min', 'pattern', 'required' ) ) ||
$key === 'step' && $value !== 'any' ) {
@@ -495,20 +491,19 @@ class Html {
'class', // html4, html5
'accesskey', // as of html5, multiple space-separated values allowed
// html4-spec doesn't document rel= as space-separated
- // but has been used like that and is now documented as such
+ // but has been used like that and is now documented as such
// in the html5-spec.
'rel',
);
- # Specific features for attributes that allow a list of space-separated values
+ // Specific features for attributes that allow a list of space-separated values
if ( in_array( $key, $spaceSeparatedListAttributes ) ) {
// Apply some normalization and remove duplicates
- // Convert into correct array. Array can contain space-seperated
+ // Convert into correct array. Array can contain space-separated
// values. Implode/explode to get those into the main array as well.
if ( is_array( $value ) ) {
// If input wasn't an array, we can skip this step
-
$newValue = array();
foreach ( $value as $k => $v ) {
if ( is_string( $v ) ) {
@@ -517,7 +512,7 @@ class Html {
if ( !isset( $value[$v] ) ) {
// As a special case don't set 'foo' if a
// separate 'foo' => true/false exists in the array
- // keys should be authoritive
+ // keys should be authoritative
$newValue[] = $v;
}
} elseif ( $v ) {
@@ -538,14 +533,14 @@ class Html {
$value = implode( ' ', array_unique( $value ) );
}
- # See the "Attributes" section in the HTML syntax part of HTML5,
- # 9.1.2.3 as of 2009-08-10. Most attributes can have quotation
- # marks omitted, but not all. (Although a literal " is not
- # permitted, we don't check for that, since it will be escaped
- # anyway.)
+ // See the "Attributes" section in the HTML syntax part of HTML5,
+ // 9.1.2.3 as of 2009-08-10. Most attributes can have quotation
+ // marks omitted, but not all. (Although a literal " is not
+ // permitted, we don't check for that, since it will be escaped
+ // anyway.)
#
- # See also research done on further characters that need to be
- # escaped: http://code.google.com/p/html5lib/issues/detail?id=93
+ // See also research done on further characters that need to be
+ // escaped: http://code.google.com/p/html5lib/issues/detail?id=93
$badChars = "\\x00- '=<>`/\x{00a0}\x{1680}\x{180e}\x{180F}\x{2000}\x{2001}"
. "\x{2002}\x{2003}\x{2004}\x{2005}\x{2006}\x{2007}\x{2008}\x{2009}"
. "\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}";
@@ -557,9 +552,9 @@ class Html {
}
if ( in_array( $key, self::$boolAttribs ) ) {
- # In XHTML 1.0 Transitional, the value needs to be equal to the
- # key. In HTML5, we can leave the value empty instead. If we
- # don't need well-formed XML, we can omit the = entirely.
+ // In XHTML 1.0 Transitional, the value needs to be equal to the
+ // key. In HTML5, we can leave the value empty instead. If we
+ // don't need well-formed XML, we can omit the = entirely.
if ( !$wgWellFormedXml ) {
$ret .= " $key";
} elseif ( $wgHtml5 ) {
@@ -568,16 +563,16 @@ class Html {
$ret .= " $key=\"$key\"";
}
} else {
- # Apparently we need to entity-encode \n, \r, \t, although the
- # spec doesn't mention that. Since we're doing strtr() anyway,
- # and we don't need <> escaped here, we may as well not call
- # htmlspecialchars().
- # @todo FIXME: Verify that we actually need to
- # escape \n\r\t here, and explain why, exactly.
+ // Apparently we need to entity-encode \n, \r, \t, although the
+ // spec doesn't mention that. Since we're doing strtr() anyway,
+ // and we don't need <> escaped here, we may as well not call
+ // htmlspecialchars().
+ // @todo FIXME: Verify that we actually need to
+ // escape \n\r\t here, and explain why, exactly.
#
- # We could call Sanitizer::encodeAttribute() for this, but we
- # don't because we're stubborn and like our marginal savings on
- # byte size from not having to encode unnecessary quotes.
+ // We could call Sanitizer::encodeAttribute() for this, but we
+ // don't because we're stubborn and like our marginal savings on
+ // byte size from not having to encode unnecessary quotes.
$map = array(
'&' => '&amp;',
'"' => '&quot;',
@@ -586,12 +581,11 @@ class Html {
"\t" => '&#9;'
);
if ( $wgWellFormedXml ) {
- # This is allowed per spec: <http://www.w3.org/TR/xml/#NT-AttValue>
- # But reportedly it breaks some XML tools?
- # @todo FIXME: Is this really true?
+ // This is allowed per spec: <http://www.w3.org/TR/xml/#NT-AttValue>
+ // But reportedly it breaks some XML tools?
+ // @todo FIXME: Is this really true?
$map['<'] = '&lt;';
}
-
$ret .= " $key=$quote" . strtr( $value, $map ) . $quote;
}
}
@@ -604,7 +598,7 @@ class Html {
* @todo do some useful escaping as well, like if $contents contains
* literal "</script>" or (for XML) literal "]]>".
*
- * @param $contents string JavaScript
+ * @param string $contents JavaScript
* @return string Raw HTML
*/
public static function inlineScript( $contents ) {
@@ -647,7 +641,7 @@ class Html {
* (if any). TODO: do some useful escaping as well, like if $contents
* contains literal "</style>" (admittedly unlikely).
*
- * @param $contents string CSS
+ * @param string $contents CSS
* @param $media mixed A media type string, like 'screen'
* @return string Raw HTML
*/
@@ -689,7 +683,7 @@ class Html {
* @param $name string name attribute
* @param $value mixed value attribute
* @param $type string type attribute
- * @param $attribs array Associative array of miscellaneous extra
+ * @param array $attribs Associative array of miscellaneous extra
* attributes, passed to Html::element()
* @return string Raw HTML
*/
@@ -706,7 +700,7 @@ class Html {
*
* @param $name string name attribute
* @param $value string value attribute
- * @param $attribs array Associative array of miscellaneous extra
+ * @param array $attribs Associative array of miscellaneous extra
* attributes, passed to Html::element()
* @return string Raw HTML
*/
@@ -724,7 +718,7 @@ class Html {
*
* @param $name string name attribute
* @param $value string value attribute
- * @param $attribs array Associative array of miscellaneous extra
+ * @param array $attribs Associative array of miscellaneous extra
* attributes, passed to Html::element()
* @return string Raw HTML
*/
@@ -743,7 +737,7 @@ class Html {
}
}
- if (substr($value, 0, 1) == "\n") {
+ if ( substr( $value, 0, 1 ) == "\n" ) {
// Workaround for bug 12130: browsers eat the initial newline
// assuming that it's just for show, but they do keep the later
// newlines, which we may want to preserve during editing.
@@ -763,12 +757,12 @@ class Html {
* - label: text for label to add before the field
* - exclude: [optional] Array of namespace ids to exclude
* - disable: [optional] Array of namespace ids for which the option should be disabled in the selector
- * @param $selectAttribs array HTML attributes for the generated select element.
+ * @param array $selectAttribs HTML attributes for the generated select element.
* - id: [optional], default: 'namespace'
* - name: [optional], default: 'namespace'
* @return string HTML code to select a namespace.
*/
- public static function namespaceSelector( Array $params = array(), Array $selectAttribs = array() ) {
+ public static function namespaceSelector( array $params = array(), array $selectAttribs = array() ) {
global $wgContLang;
ksort( $selectAttribs );
@@ -802,7 +796,7 @@ class Html {
// Value is provided by user, the name shown is localized for the user.
$options[$params['all']] = wfMessage( 'namespacesall' )->text();
}
- // Add all namespaces as options (in the content langauge)
+ // Add all namespaces as options (in the content language)
$options += $wgContLang->getFormattedNamespaces();
// Convert $options to HTML and filter out namespaces below 0
@@ -811,10 +805,12 @@ class Html {
if ( $nsId < NS_MAIN || in_array( $nsId, $params['exclude'] ) ) {
continue;
}
- if ( $nsId === 0 ) {
+ if ( $nsId === NS_MAIN ) {
// For other namespaces use use the namespace prefix as label, but for
- // main we don't use "" but the user message descripting it (e.g. "(Main)" or "(Article)")
+ // main we don't use "" but the user message describing it (e.g. "(Main)" or "(Article)")
$nsName = wfMessage( 'blanknamespace' )->text();
+ } elseif ( is_int( $nsId ) ) {
+ $nsName = $wgContLang->convertNamespace( $nsId );
}
$optionsHtml[] = Html::element(
'option', array(
@@ -825,6 +821,14 @@ class Html {
);
}
+ if ( !array_key_exists( 'id', $selectAttribs ) ) {
+ $selectAttribs['id'] = 'namespace';
+ }
+
+ if ( !array_key_exists( 'name', $selectAttribs ) ) {
+ $selectAttribs['name'] = 'namespace';
+ }
+
$ret = '';
if ( isset( $params['label'] ) ) {
$ret .= Html::element(
@@ -848,7 +852,7 @@ class Html {
* Constructs the opening html-tag with necessary doctypes depending on
* global variables.
*
- * @param $attribs array Associative array of miscellaneous extra
+ * @param array $attribs Associative array of miscellaneous extra
* attributes, passed to Html::element() of html tag.
* @return string Raw HTML
*/
@@ -910,10 +914,10 @@ class Html {
/**
* Get HTML for an info box with an icon.
*
- * @param $text String: wikitext, get this with wfMessage()->plain()
- * @param $icon String: icon name, file in skins/common/images
- * @param $alt String: alternate text for the icon
- * @param $class String: additional class name to add to the wrapper div
+ * @param string $text wikitext, get this with wfMessage()->plain()
+ * @param string $icon icon name, file in skins/common/images
+ * @param string $alt alternate text for the icon
+ * @param string $class additional class name to add to the wrapper div
* @param $useStylePath
*
* @return string
@@ -925,7 +929,7 @@ class Html {
$icon = $wgStylePath.'/common/images/'.$icon;
}
- $s = Html::openElement( 'div', array( 'class' => "mw-infobox $class") );
+ $s = Html::openElement( 'div', array( 'class' => "mw-infobox $class" ) );
$s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-left' ) ).
Html::element( 'img',
@@ -947,4 +951,22 @@ class Html {
return $s;
}
+
+ /**
+ * Generate a srcset attribute value from an array mapping pixel densities
+ * to URLs. Note that srcset supports width and height values as well, which
+ * are not used here.
+ *
+ * @param array $urls
+ * @return string
+ */
+ static function srcSet( $urls ) {
+ $candidates = array();
+ foreach( $urls as $density => $url ) {
+ // Image candidate syntax per current whatwg live spec, 2012-09-23:
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content-1.html#attr-img-srcset
+ $candidates[] = "{$url} {$density}x";
+ }
+ return implode( ", ", $candidates );
+ }
}
diff --git a/includes/HttpFunctions.php b/includes/HttpFunctions.php
index 8e48da46..dc65c67e 100644
--- a/includes/HttpFunctions.php
+++ b/includes/HttpFunctions.php
@@ -35,9 +35,9 @@ class Http {
/**
* Perform an HTTP request
*
- * @param $method String: HTTP method. Usually GET/POST
- * @param $url String: full URL to act on. If protocol-relative, will be expanded to an http:// URL
- * @param $options Array: options to pass to MWHttpRequest object.
+ * @param string $method HTTP method. Usually GET/POST
+ * @param string $url full URL to act on. If protocol-relative, will be expanded to an http:// URL
+ * @param array $options options to pass to MWHttpRequest object.
* Possible keys for the array:
* - timeout Timeout length in seconds
* - postData An array of key-value pairs or a url-encoded form data
@@ -103,7 +103,7 @@ class Http {
/**
* Check if the URL can be served by localhost
*
- * @param $url String: full url to check
+ * @param string $url full url to check
* @return Boolean
*/
public static function isLocalURL( $url ) {
@@ -209,8 +209,8 @@ class MWHttpRequest {
public $status;
/**
- * @param $url String: url to use. If protocol-relative, will be expanded to an http:// URL
- * @param $options Array: (optional) extra params to pass (see Http::request())
+ * @param string $url url to use. If protocol-relative, will be expanded to an http:// URL
+ * @param array $options (optional) extra params to pass (see Http::request())
*/
protected function __construct( $url, $options = array() ) {
global $wgHTTPTimeout;
@@ -234,7 +234,7 @@ class MWHttpRequest {
}
$members = array( "postData", "proxy", "noProxy", "sslVerifyHost", "caInfo",
- "method", "followRedirects", "maxRedirects", "sslVerifyCert", "callback" );
+ "method", "followRedirects", "maxRedirects", "sslVerifyCert", "callback" );
foreach ( $members as $o ) {
if ( isset( $options[$o] ) ) {
@@ -263,8 +263,9 @@ class MWHttpRequest {
/**
* Generate a new request object
- * @param $url String: url to use
- * @param $options Array: (optional) extra params to pass (see Http::request())
+ * @param string $url url to use
+ * @param array $options (optional) extra params to pass (see Http::request())
+ * @throws MWException
* @return CurlHttpRequest|PhpHttpRequest
* @see MWHttpRequest::__construct
*/
@@ -273,7 +274,7 @@ class MWHttpRequest {
Http::$httpEngine = function_exists( 'curl_init' ) ? 'curl' : 'php';
} elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) {
throw new MWException( __METHOD__ . ': curl (http://php.net/curl) is not installed, but' .
- ' Http::$httpEngine is set to "curl"' );
+ ' Http::$httpEngine is set to "curl"' );
}
switch( Http::$httpEngine ) {
@@ -317,21 +318,24 @@ class MWHttpRequest {
public function proxySetup() {
global $wgHTTPProxy;
- if ( $this->proxy || !$this->noProxy ) {
+ // If there is an explicit proxy set and proxies are not disabled, then use it
+ if ( $this->proxy && !$this->noProxy ) {
return;
}
+ // Otherwise, fallback to $wgHTTPProxy/http_proxy (when set) if this is not a machine
+ // local URL and proxies are not disabled
if ( Http::isLocalURL( $this->url ) || $this->noProxy ) {
$this->proxy = '';
} elseif ( $wgHTTPProxy ) {
- $this->proxy = $wgHTTPProxy ;
+ $this->proxy = $wgHTTPProxy;
} elseif ( getenv( "http_proxy" ) ) {
$this->proxy = getenv( "http_proxy" );
}
}
/**
- * Set the refererer header
+ * Set the referrer header
*/
public function setReferer( $url ) {
$this->setHeader( 'Referer', $url );
@@ -393,6 +397,7 @@ class MWHttpRequest {
* will be aborted.
*
* @param $callback Callback
+ * @throws MWException
*/
public function setCallback( $callback ) {
if ( !is_callable( $callback ) ) {
@@ -445,7 +450,7 @@ class MWHttpRequest {
/**
* Parses the headers, including the HTTP status code and any
- * Set-Cookie headers. This function expectes the headers to be
+ * Set-Cookie headers. This function expects the headers to be
* found in an array in the member variable headerList.
*/
protected function parseHeader() {
@@ -501,7 +506,6 @@ class MWHttpRequest {
return (int)$this->respStatus;
}
-
/**
* Returns true if the last status code was a redirect.
*
@@ -579,8 +583,8 @@ class MWHttpRequest {
}
/**
- * Sets a cookie. Used before a request to set up any individual
- * cookies. Used internally after a request to parse the
+ * Sets a cookie. Used before a request to set up any individual
+ * cookies. Used internally after a request to parse the
* Set-Cookie headers.
* @see Cookie::set
* @param $name
@@ -631,14 +635,14 @@ class MWHttpRequest {
$locations = $headers[ 'location' ];
$domain = '';
$foundRelativeURI = false;
- $countLocations = count($locations);
+ $countLocations = count( $locations );
for ( $i = $countLocations - 1; $i >= 0; $i-- ) {
$url = parse_url( $locations[ $i ] );
- if ( isset($url[ 'host' ]) ) {
+ if ( isset( $url['host'] ) ) {
$domain = $url[ 'scheme' ] . '://' . $url[ 'host' ];
- break; //found correct URI (with host)
+ break; //found correct URI (with host)
} else {
$foundRelativeURI = true;
}
@@ -810,7 +814,7 @@ class PhpHttpRequest extends MWHttpRequest {
parent::execute();
if ( is_array( $this->postData ) ) {
- $this->postData = wfArrayToCGI( $this->postData );
+ $this->postData = wfArrayToCgi( $this->postData );
}
if ( $this->parsedUrl['scheme'] != 'http' &&
diff --git a/includes/IP.php b/includes/IP.php
index 10c707e7..72b9a52c 100644
--- a/includes/IP.php
+++ b/includes/IP.php
@@ -18,7 +18,7 @@
* http://www.gnu.org/copyleft/gpl.html
*
* @file
- * @author Antoine Musso <hashar at free dot fr>, Aaron Schulz
+ * @author Antoine Musso "<hashar at free dot fr>", Aaron Schulz
*/
// Some regex definition to "play" with IP address and IP address blocks
@@ -77,7 +77,7 @@ class IP {
* SIIT IPv4-translated addresses are rejected.
* Note: canonicalize() tries to convert translated addresses to IPv4.
*
- * @param $ip String: possible IP address
+ * @param string $ip possible IP address
* @return Boolean
*/
public static function isIPAddress( $ip ) {
@@ -88,7 +88,7 @@ class IP {
* Given a string, determine if it as valid IP in IPv6 only.
* Note: Unlike isValid(), this looks for networks too.
*
- * @param $ip String: possible IP address
+ * @param string $ip possible IP address
* @return Boolean
*/
public static function isIPv6( $ip ) {
@@ -99,7 +99,7 @@ class IP {
* Given a string, determine if it as valid IP in IPv4 only.
* Note: Unlike isValid(), this looks for networks too.
*
- * @param $ip String: possible IP address
+ * @param string $ip possible IP address
* @return Boolean
*/
public static function isIPv4( $ip ) {
@@ -137,7 +137,7 @@ class IP {
* IPv6 addresses in octet notation are expanded to 8 words.
* IPv4 addresses are just trimmed.
*
- * @param $ip String: IP address in quad or octet form (CIDR or not).
+ * @param string $ip IP address in quad or octet form (CIDR or not).
* @return String
*/
public static function sanitizeIP( $ip ) {
@@ -180,7 +180,7 @@ class IP {
$ip
);
}
- // Remove leading zereos from each bloc as needed
+ // Remove leading zeros from each bloc as needed
$ip = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip );
return $ip;
}
@@ -241,7 +241,7 @@ class IP {
*
* A bare IPv6 address is accepted despite the lack of square brackets.
*
- * @param $both string The string with the host and port
+ * @param string $both The string with the host and port
* @return array
*/
public static function splitHostAndPort( $both ) {
@@ -316,7 +316,7 @@ class IP {
/**
* Convert an IPv4 or IPv6 hexadecimal representation back to readable format
*
- * @param $hex String: number, with "v6-" prefix if it is IPv6
+ * @param string $hex number, with "v6-" prefix if it is IPv6
* @return String: quad-dotted (IPv4) or octet notation (IPv6)
*/
public static function formatHex( $hex ) {
@@ -444,7 +444,7 @@ class IP {
* function for an IPv6 address will be prefixed with "v6-", a non-
* hexadecimal string which sorts after the IPv4 addresses.
*
- * @param $ip String: quad dotted/octet IP address.
+ * @param string $ip quad dotted/octet IP address.
* @return String
*/
public static function toHex( $ip ) {
@@ -462,7 +462,7 @@ class IP {
/**
* Given an IPv6 address in octet notation, returns a pure hex string.
*
- * @param $ip String: octet ipv6 IP address.
+ * @param string $ip octet ipv6 IP address.
* @return String: pure hex (uppercase)
*/
private static function IPv6ToRawHex( $ip ) {
@@ -482,7 +482,7 @@ class IP {
* Like ip2long() except that it actually works and has a consistent error return value.
* Comes from ProxyTools.php
*
- * @param $ip String: quad dotted IP address.
+ * @param string $ip quad dotted IP address.
* @return Mixed: string/int/false
*/
public static function toUnsigned( $ip ) {
@@ -509,7 +509,7 @@ class IP {
* Convert a network specification in CIDR notation
* to an integer network and a number of bits
*
- * @param $range String: IP with CIDR prefix
+ * @param string $range IP with CIDR prefix
* @return array(int or string, int)
*/
public static function parseCIDR( $range ) {
@@ -551,7 +551,7 @@ class IP {
* 2001:0db8:85a3::7344/96 CIDR
* 2001:0db8:85a3::7344 - 2001:0db8:85a3::7344 Explicit range
* 2001:0db8:85a3::7344 Single IP
- * @param $range String: IP range
+ * @param string $range IP range
* @return array(string, string)
*/
public static function parseRange( $range ) {
@@ -692,8 +692,8 @@ class IP {
/**
* Determine if a given IPv4/IPv6 address is in a given CIDR network
*
- * @param $addr String: the address to check against the given range.
- * @param $range String: the range to check the given address against.
+ * @param string $addr the address to check against the given range.
+ * @param string $range the range to check the given address against.
* @return Boolean: whether or not the given address is in the given range.
*/
public static function isInRange( $addr, $range ) {
@@ -710,11 +710,13 @@ class IP {
* This currently only checks a few IPV4-to-IPv6 related cases. More
* unusual representations may be added later.
*
- * @param $addr String: something that might be an IP address
+ * @param string $addr something that might be an IP address
* @return String: valid dotted quad IPv4 address or null
*/
public static function canonicalize( $addr ) {
- $addr = preg_replace( '/\%.*/','', $addr ); // remove zone info (bug 35738)
+ // remove zone info (bug 35738)
+ $addr = preg_replace( '/\%.*/', '', $addr );
+
if ( self::isValid( $addr ) ) {
return $addr;
}
@@ -740,13 +742,13 @@ class IP {
return long2ip( ( hexdec( $m[1] ) << 16 ) + hexdec( $m[2] ) );
}
- return null; // give up
+ return null; // give up
}
/**
- * Gets rid of uneeded numbers in quad-dotted/octet IP strings
+ * Gets rid of unneeded numbers in quad-dotted/octet IP strings
* For example, 127.111.113.151/24 -> 127.111.113.0/24
- * @param $range String: IP address to normalize
+ * @param string $range IP address to normalize
* @return string
*/
public static function sanitizeRange( $range ) {
diff --git a/includes/ImageGallery.php b/includes/ImageGallery.php
index 91c3190f..1556ad94 100644
--- a/includes/ImageGallery.php
+++ b/includes/ImageGallery.php
@@ -93,7 +93,7 @@ class ImageGallery {
/**
* Set the caption (as plain text)
*
- * @param $caption string Caption
+ * @param string $caption Caption
*/
function setCaption( $caption ) {
$this->mCaption = htmlspecialchars( $caption );
@@ -102,7 +102,7 @@ class ImageGallery {
/**
* Set the caption (as HTML)
*
- * @param $caption String: Caption
+ * @param string $caption Caption
*/
public function setCaptionHtml( $caption ) {
$this->mCaption = $caption;
@@ -161,7 +161,7 @@ class ImageGallery {
* @param $alt String: Alt text for the image
* @param $link String: Override image link (optional)
*/
- function add( $title, $html = '', $alt = '', $link = '') {
+ function add( $title, $html = '', $alt = '', $link = '' ) {
if ( $title instanceof File ) {
// Old calling convention
$title = $title->getTitle();
@@ -220,7 +220,7 @@ class ImageGallery {
* Note -- if taking from user input, you should probably run through
* Sanitizer::validateAttributes() first.
*
- * @param $attribs Array of HTML attribute pairs
+ * @param array $attribs of HTML attribute pairs
*/
function setAttributes( $attribs ) {
$this->mAttribs = $attribs;
@@ -238,8 +238,6 @@ class ImageGallery {
* @return string
*/
function toHTML() {
- global $wgLang;
-
if ( $this->mPerRow > 0 ) {
$maxwidth = $this->mPerRow * ( $this->mWidths + self::THUMB_PADDING + self::GB_PADDING + self::GB_BORDERS );
$oldStyle = isset( $this->mAttribs['style'] ) ? $this->mAttribs['style'] : '';
@@ -255,6 +253,7 @@ class ImageGallery {
$output .= "\n\t<li class='gallerycaption'>{$this->mCaption}</li>";
}
+ $lang = $this->getLang();
$params = array(
'width' => $this->mWidths,
'height' => $this->mHeights
@@ -337,7 +336,7 @@ class ImageGallery {
if( $this->mShowBytes ) {
if( $img ) {
- $fileSize = htmlspecialchars( $wgLang->formatSize( $img->getSize() ) );
+ $fileSize = htmlspecialchars( $lang->formatSize( $img->getSize() ) );
} else {
$fileSize = wfMessage( 'filemissing' )->escaped();
}
@@ -349,19 +348,19 @@ class ImageGallery {
$textlink = $this->mShowFilename ?
Linker::link(
$nt,
- htmlspecialchars( $wgLang->truncate( $nt->getText(), $this->mCaptionLength ) ),
+ htmlspecialchars( $lang->truncate( $nt->getText(), $this->mCaptionLength ) ),
array(),
array(),
array( 'known', 'noclasses' )
) . "<br />\n" :
- '' ;
+ '';
# ATTENTION: The newline after <div class="gallerytext"> is needed to accommodate htmltidy which
# in version 4.8.6 generated crackpot html in its absence, see:
# http://bugzilla.wikimedia.org/show_bug.cgi?id=1765 -Ævar
# Weird double wrapping (the extra div inside the li) needed due to FF2 bug
- # Can be safely removed if FF2 falls completely out of existance
+ # Can be safely removed if FF2 falls completely out of existence
$output .=
"\n\t\t" . '<li class="gallerybox" style="width: ' . ( $this->mWidths + self::THUMB_PADDING + self::GB_PADDING ) . 'px">'
. '<div style="width: ' . ( $this->mWidths + self::THUMB_PADDING + self::GB_PADDING ) . 'px">'
@@ -403,4 +402,15 @@ class ImageGallery {
: false;
}
+ /**
+ * Determines the correct language to be used for this image gallery
+ * @return Language object
+ */
+ private function getLang() {
+ global $wgLang;
+ return $this->mParser
+ ? $this->mParser->getTargetLanguage()
+ : $wgLang;
+ }
+
} //class
diff --git a/includes/ImagePage.php b/includes/ImagePage.php
index 6f1b1a15..b3a485aa 100644
--- a/includes/ImagePage.php
+++ b/includes/ImagePage.php
@@ -50,7 +50,7 @@ class ImagePage extends Article {
/**
* Constructor from a page id
- * @param $id Int article ID to load
+ * @param int $id article ID to load
* @return ImagePage|null
*/
public static function newFromID( $id ) {
@@ -108,7 +108,7 @@ class ImagePage extends Article {
$diff = $request->getVal( 'diff' );
$diffOnly = $request->getBool( 'diffonly', $this->getContext()->getUser()->getOption( 'diffonly' ) );
- if ( $this->getTitle()->getNamespace() != NS_FILE || ( isset( $diff ) && $diffOnly ) ) {
+ if ( $this->getTitle()->getNamespace() != NS_FILE || ( $diff !== null && $diffOnly ) ) {
parent::view();
return;
}
@@ -116,7 +116,7 @@ class ImagePage extends Article {
$this->loadFile();
if ( $this->getTitle()->getNamespace() == NS_FILE && $this->mPage->getFile()->getRedirected() ) {
- if ( $this->getTitle()->getDBkey() == $this->mPage->getFile()->getName() || isset( $diff ) ) {
+ if ( $this->getTitle()->getDBkey() == $this->mPage->getFile()->getName() || $diff !== null ) {
// mTitle is the same as the redirect target so ask Article
// to perform the redirect for us.
$request->setVal( 'diffonly', 'true' );
@@ -156,8 +156,10 @@ class ImagePage extends Article {
$pageLang = $this->getTitle()->getPageViewLanguage();
$out->addHTML( Xml::openElement( 'div', array( 'id' => 'mw-imagepage-content',
'lang' => $pageLang->getHtmlCode(), 'dir' => $pageLang->getDir(),
- 'class' => 'mw-content-'.$pageLang->getDir() ) ) );
+ 'class' => 'mw-content-' . $pageLang->getDir() ) ) );
+
parent::view();
+
$out->addHTML( Xml::closeElement( 'div' ) );
} else {
# Just need to set the right headers
@@ -248,7 +250,7 @@ class ImagePage extends Article {
*
* @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata().
*
- * @param $metadata Array: the array containing the EXIF data
+ * @param array $metadata the array containing the EXIF data
* @return String The metadata table. This is treated as Wikitext (!)
*/
protected function makeMetadataTable( $metadata ) {
@@ -260,7 +262,7 @@ class ImagePage extends Article {
# @todo FIXME: Why is this using escapeId for a class?!
$class = Sanitizer::escapeId( $v['id'] );
if ( $type == 'collapsed' ) {
- $class .= ' collapsable';
+ $class .= ' collapsable'; // sic
}
$r .= "<tr class=\"$class\">\n";
$r .= "<th>{$v['name']}</th>\n";
@@ -272,18 +274,18 @@ class ImagePage extends Article {
}
/**
- * Overloading Article's getContent method.
+ * Overloading Article's getContentObject method.
*
* Omit noarticletext if sharedupload; text will be fetched from the
* shared upload server if possible.
* @return string
*/
- public function getContent() {
+ public function getContentObject() {
$this->loadFile();
if ( $this->mPage->getFile() && !$this->mPage->getFile()->isLocal() && 0 == $this->getID() ) {
- return '';
+ return null;
}
- return parent::getContent();
+ return parent::getContentObject();
}
protected function openShowImage() {
@@ -296,18 +298,7 @@ class ImagePage extends Article {
$dirmark = $lang->getDirMarkEntity();
$request = $this->getContext()->getRequest();
- $sizeSel = intval( $user->getOption( 'imagesize' ) );
- if ( !isset( $wgImageLimits[$sizeSel] ) ) {
- $sizeSel = User::getDefaultOption( 'imagesize' );
-
- // The user offset might still be incorrect, specially if
- // $wgImageLimits got changed (see bug #8858).
- if ( !isset( $wgImageLimits[$sizeSel] ) ) {
- // Default to the first offset in $wgImageLimits
- $sizeSel = 0;
- }
- }
- $max = $wgImageLimits[$sizeSel];
+ $max = $this->getImageLimitsFromOption( $user, 'imagesize' );
$maxWidth = $max[0];
$maxHeight = $max[1];
@@ -325,7 +316,8 @@ class ImagePage extends Article {
$height_orig = $this->displayImg->getHeight( $page );
$height = $height_orig;
- $longDesc = wfMessage( 'parentheses', $this->displayImg->getLongDesc() )->text();
+ $filename = wfEscapeWikiText( $this->displayImg->getName() );
+ $linktext = $filename;
wfRunHooks( 'ImageOpenShowImageInlineBefore', array( &$this, &$out ) );
@@ -338,27 +330,24 @@ class ImagePage extends Article {
if ( $width > $maxWidth || $height > $maxHeight ) {
# Calculate the thumbnail size.
# First case, the limiting factor is the width, not the height.
- if ( $width / $height >= $maxWidth / $maxHeight ) {
- $height = round( $height * $maxWidth / $width );
+ if ( $width / $height >= $maxWidth / $maxHeight ) { // FIXME: Possible division by 0. bug 36911
+ $height = round( $height * $maxWidth / $width ); // FIXME: Possible division by 0. bug 36911
$width = $maxWidth;
# Note that $height <= $maxHeight now.
} else {
- $newwidth = floor( $width * $maxHeight / $height );
- $height = round( $height * $newwidth / $width );
+ $newwidth = floor( $width * $maxHeight / $height ); // FIXME: Possible division by 0. bug 36911
+ $height = round( $height * $newwidth / $width ); // FIXME: Possible division by 0. bug 36911
$width = $newwidth;
# Note that $height <= $maxHeight now, but might not be identical
# because of rounding.
}
- $msgbig = wfMessage( 'show-big-image' )->escaped();
+ $linktext = wfMessage( 'show-big-image' )->escaped();
if ( $this->displayImg->getRepo()->canTransformVia404() ) {
$thumbSizes = $wgImageLimits;
} else {
# Creating thumb links triggers thumbnail generation.
# Just generate the thumb for the current users prefs.
- $thumbOption = $user->getOption( 'thumbsize' );
- $thumbSizes = array( isset( $wgImageLimits[$thumbOption] )
- ? $wgImageLimits[$thumbOption]
- : $wgImageLimits[User::getDefaultOption( 'thumbsize' )] );
+ $thumbSizes = array( $this->getImageLimitsFromOption( $user, 'thumbsize' ) );
}
# Generate thumbnails or thumbnail links as needed...
$otherSizes = array();
@@ -366,12 +355,19 @@ class ImagePage extends Article {
if ( $size[0] < $width_orig && $size[1] < $height_orig
&& $size[0] != $width && $size[1] != $height )
{
- $otherSizes[] = $this->makeSizeLink( $params, $size[0], $size[1] );
+ $sizeLink = $this->makeSizeLink( $params, $size[0], $size[1] );
+ if ( $sizeLink ) {
+ $otherSizes[] = $sizeLink;
+ }
}
}
- $msgsmall = wfMessage( 'show-big-image-preview' )->
- rawParams( $this->makeSizeLink( $params, $width, $height ) )->
- parse();
+ $msgsmall = '';
+ $sizeLinkBigImagePreview = $this->makeSizeLink( $params, $width, $height );
+ if ( $sizeLinkBigImagePreview ) {
+ $msgsmall .= wfMessage( 'show-big-image-preview' )->
+ rawParams( $sizeLinkBigImagePreview )->
+ parse();
+ }
if ( count( $otherSizes ) ) {
$msgsmall .= ' ' .
Html::rawElement( 'span', array( 'class' => 'mw-filepage-other-resolutions' ),
@@ -379,7 +375,7 @@ class ImagePage extends Article {
params( count( $otherSizes ) )->parse()
);
}
- } elseif ( $width == 0 && $height == 0 ){
+ } elseif ( $width == 0 && $height == 0 ) {
# Some sort of audio file that doesn't have dimensions
# Don't output a no hi res message for such a file
$msgsmall = '';
@@ -395,7 +391,6 @@ class ImagePage extends Article {
$params['height'] = $height;
$thumbnail = $this->displayImg->transform( $params );
- $showLink = true;
$anchorclose = Html::rawElement( 'div', array( 'class' => 'mw-filepage-resolutioninfo' ), $msgsmall );
$isMulti = $this->displayImg->isMultipage() && $this->displayImg->pageCount() > 1;
@@ -469,48 +464,39 @@ class ImagePage extends Article {
"<hr />$thumb1\n$thumb2<br style=\"clear: both\" /></div></td></tr></table>"
);
}
- } else {
+ } elseif ( $this->displayImg->isSafeFile() ) {
# if direct link is allowed but it's not a renderable image, show an icon.
- if ( $this->displayImg->isSafeFile() ) {
- $icon = $this->displayImg->iconThumb();
+ $icon = $this->displayImg->iconThumb();
- $out->addHTML( '<div class="fullImageLink" id="file">' .
- $icon->toHtml( array( 'file-link' => true ) ) .
- "</div>\n" );
- }
-
- $showLink = true;
+ $out->addHTML( '<div class="fullImageLink" id="file">' .
+ $icon->toHtml( array( 'file-link' => true ) ) .
+ "</div>\n" );
}
- if ( $showLink ) {
- $filename = wfEscapeWikiText( $this->displayImg->getName() );
- $linktext = $filename;
- if ( isset( $msgbig ) ) {
- $linktext = wfEscapeWikiText( $msgbig );
- }
- $medialink = "[[Media:$filename|$linktext]]";
-
- if ( !$this->displayImg->isSafeFile() ) {
- $warning = wfMessage( 'mediawarning' )->plain();
- // dirmark is needed here to separate the file name, which
- // most likely ends in Latin characters, from the description,
- // which may begin with the file type. In RTL environment
- // this will get messy.
- // The dirmark, however, must not be immediately adjacent
- // to the filename, because it can get copied with it.
- // See bug 25277.
- $out->addWikiText( <<<EOT
+ $longDesc = wfMessage( 'parentheses', $this->displayImg->getLongDesc() )->text();
+
+ $medialink = "[[Media:$filename|$linktext]]";
+
+ if ( !$this->displayImg->isSafeFile() ) {
+ $warning = wfMessage( 'mediawarning' )->plain();
+ // dirmark is needed here to separate the file name, which
+ // most likely ends in Latin characters, from the description,
+ // which may begin with the file type. In RTL environment
+ // this will get messy.
+ // The dirmark, however, must not be immediately adjacent
+ // to the filename, because it can get copied with it.
+ // See bug 25277.
+ $out->addWikiText( <<<EOT
<div class="fullMedia"><span class="dangerousLink">{$medialink}</span> $dirmark<span class="fileInfo">$longDesc</span></div>
<div class="mediaWarning">$warning</div>
EOT
- );
- } else {
- $out->addWikiText( <<<EOT
+ );
+ } else {
+ $out->addWikiText( <<<EOT
<div class="fullMedia">{$medialink} {$dirmark}<span class="fileInfo">$longDesc</span>
</div>
EOT
- );
- }
+ );
}
// Add cannot animate thumbnail warning
@@ -545,7 +531,7 @@ EOT
array( 'delete', 'move' ),
$this->getTitle()->getPrefixedText(),
'',
- array( 'lim' => 10,
+ array( 'lim' => 10,
'conds' => array( "log_action != 'revision'" ),
'showIfEmpty' => false,
'msgKey' => array( 'moveddeleted-notice' )
@@ -564,7 +550,7 @@ EOT
$nofile = 'filepage-nofile';
}
// Note, if there is an image description page, but
- // no image, then this setRobotPolicy is overriden
+ // no image, then this setRobotPolicy is overridden
// by Article::View().
$out->setRobotPolicy( 'noindex,nofollow' );
$out->wrapWikiMsg( "<div id='mw-imagepage-nofile' class='plainlinks'>\n$1\n</div>", $nofile );
@@ -579,7 +565,7 @@ EOT
/**
* Creates an thumbnail of specified size and returns an HTML link to it
- * @param $params array Scaler parameters
+ * @param array $params Scaler parameters
* @param $width int
* @param $height int
* @return string
@@ -612,13 +598,13 @@ EOT
/* Add canonical to head if there is no local page for this shared file */
if( $descUrl && $this->mPage->getID() == 0 ) {
- $out->addLink( array( 'rel' => 'canonical', 'href' => $descUrl ) );
+ $out->setCanonicalUrl( $descUrl );
}
$wrap = "<div class=\"sharedUploadNotice\">\n$1\n</div>\n";
$repo = $this->mPage->getFile()->getRepo()->getDisplayName();
- if ( $descUrl && $descText && wfMessage( 'sharedupload-desc-here' )->plain() !== '-' ) {
+ if ( $descUrl && $descText && wfMessage( 'sharedupload-desc-here' )->plain() !== '-' ) {
$out->wrapWikiMsg( $wrap, array( 'sharedupload-desc-here', $repo, $descUrl ) );
} elseif ( $descUrl && wfMessage( 'sharedupload-desc-there' )->plain() !== '-' ) {
$out->wrapWikiMsg( $wrap, array( 'sharedupload-desc-there', $repo, $descUrl ) );
@@ -637,7 +623,7 @@ EOT
return $uploadTitle->getFullURL( array(
'wpDestFile' => $this->mPage->getFile()->getName(),
'wpForReUpload' => 1
- ) );
+ ) );
}
/**
@@ -794,9 +780,14 @@ EOT
$link = Linker::linkKnown( Title::makeTitle( $element->page_namespace, $element->page_title ) );
if ( !isset( $redirects[$element->page_title] ) ) {
+ # No redirects
$liContents = $link;
+ } elseif ( count( $redirects[$element->page_title] ) === 0 ) {
+ # Redirect without usages
+ $liContents = wfMessage( 'linkstoimage-redirect' )->rawParams( $link, '' )->parse();
} else {
- $ul = "<ul class='mw-imagepage-redirectstofile'>\n";
+ # Redirect with usages
+ $li = '';
foreach ( $redirects[$element->page_title] as $row ) {
$currentCount++;
if ( $currentCount > $limit ) {
@@ -804,13 +795,18 @@ EOT
}
$link2 = Linker::linkKnown( Title::makeTitle( $row->page_namespace, $row->page_title ) );
- $ul .= Html::rawElement(
+ $li .= Html::rawElement(
'li',
array( 'class' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ),
$link2
) . "\n";
}
- $ul .= '</ul>';
+
+ $ul = Html::rawElement(
+ 'ul',
+ array( 'class' => 'mw-imagepage-redirectstofile' ),
+ $li
+ ) . "\n";
$liContents = wfMessage( 'linkstoimage-redirect' )->rawParams(
$link, $ul )->parse();
}
@@ -908,6 +904,34 @@ EOT
return $a->page_namespace - $b->page_namespace;
}
}
+
+ /**
+ * Returns the corresponding $wgImageLimits entry for the selected user option
+ *
+ * @param $user User
+ * @param string $optionName Name of a option to check, typically imagesize or thumbsize
+ * @return array
+ * @since 1.21
+ */
+ public function getImageLimitsFromOption( $user, $optionName ) {
+ global $wgImageLimits;
+
+ $option = $user->getIntOption( $optionName );
+ if ( !isset( $wgImageLimits[$option] ) ) {
+ $option = User::getDefaultOption( $optionName );
+ }
+
+ // The user offset might still be incorrect, specially if
+ // $wgImageLimits got changed (see bug #8858).
+ if ( !isset( $wgImageLimits[$option] ) ) {
+ // Default to the first offset in $wgImageLimits
+ $option = 0;
+ }
+
+ return isset( $wgImageLimits[$option] )
+ ? $wgImageLimits[$option]
+ : array( 800, 600 ); // if nothing is set, fallback to a hardcoded default
+ }
}
/**
@@ -1041,9 +1065,9 @@ class ImageHistoryList extends ContextSource {
} else {
list( $ts, ) = explode( '!', $img, 2 );
$query = array(
- 'type' => 'oldimage',
+ 'type' => 'oldimage',
'target' => $this->title->getPrefixedText(),
- 'ids' => $ts,
+ 'ids' => $ts,
);
$del = Linker::revDeleteLink( $query,
$file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
@@ -1163,7 +1187,7 @@ class ImageHistoryList extends ContextSource {
protected function getThumbForLine( $file ) {
$lang = $this->getLanguage();
$user = $this->getUser();
- if ( $file->allowInlineDisplay() && $file->userCan( File::DELETED_FILE,$user )
+ if ( $file->allowInlineDisplay() && $file->userCan( File::DELETED_FILE, $user )
&& !$file->isDeleted( File::DELETED_FILE ) )
{
$params = array(
@@ -1223,7 +1247,7 @@ class ImageHistoryPseudoPager extends ReverseChronologicalPager {
* @param ImagePage $imagePage
*/
function __construct( $imagePage ) {
- parent::__construct();
+ parent::__construct( $imagePage->getContext() );
$this->mImagePage = $imagePage;
$this->mTitle = clone ( $imagePage->getTitle() );
$this->mTitle->setFragment( '#filehistory' );
diff --git a/includes/Import.php b/includes/Import.php
index 480239fe..bb5d6349 100644
--- a/includes/Import.php
+++ b/includes/Import.php
@@ -225,7 +225,7 @@ class WikiImporter {
} else {
// set namespace to 'all', so the namespace check in processTitle() can passed
$this->setTargetNamespace( null );
- $this->mTargetRootPage = $title->getPrefixedDBKey();
+ $this->mTargetRootPage = $title->getPrefixedDBkey();
}
}
}
@@ -252,8 +252,16 @@ class WikiImporter {
* @return bool
*/
public function importRevision( $revision ) {
- $dbw = wfGetDB( DB_MASTER );
- return $dbw->deadlockLoop( array( $revision, 'importOldRevision' ) );
+ try {
+ $dbw = wfGetDB( DB_MASTER );
+ return $dbw->deadlockLoop( array( $revision, 'importOldRevision' ) );
+ } catch ( MWContentSerializationException $ex ) {
+ $this->notice( 'import-error-unserialize',
+ $revision->getTitle()->getPrefixedText(),
+ $revision->getID(),
+ $revision->getModel(),
+ $revision->getFormat() );
+ }
}
/**
@@ -322,8 +330,8 @@ class WikiImporter {
* @param $title Title
* @param $origTitle Title
* @param $revCount Integer
- * @param $sucCount Int: number of revisions for which callback returned true
- * @param $pageInfo Array: associative array of page information
+ * @param int $sucCount number of revisions for which callback returned true
+ * @param array $pageInfo associative array of page information
*/
private function pageOutCallback( $title, $origTitle, $revCount, $sucCount, $pageInfo ) {
if( isset( $this->mPageOutCallback ) ) {
@@ -392,7 +400,7 @@ class WikiImporter {
/** Left in for debugging */
private function dumpElement() {
static $lookup = null;
- if (!$lookup) {
+ if ( !$lookup ) {
$xmlReaderConstants = array(
"NONE",
"ELEMENT",
@@ -429,6 +437,7 @@ class WikiImporter {
/**
* Primary entry point
+ * @throws MWException
* @return bool
*/
public function doImport() {
@@ -441,7 +450,7 @@ class WikiImporter {
if ( $this->reader->name != 'mediawiki' ) {
libxml_disable_entity_loader( $oldDisable );
- throw new MWException( "Expected <mediawiki> tag, got " .
+ throw new MWException( "Expected <mediawiki> tag, got ".
$this->reader->name );
}
$this->debug( "<mediawiki> tag is correct." );
@@ -470,7 +479,7 @@ class WikiImporter {
$skip = true;
}
- if ($skip) {
+ if ( $skip ) {
$keepReading = $this->reader->next();
$skip = false;
$this->debug( "Skip" );
@@ -507,7 +516,7 @@ class WikiImporter {
while ( $this->reader->read() ) {
if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
- $this->reader->name == 'logitem') {
+ $this->reader->name == 'logitem' ) {
break;
}
@@ -570,7 +579,7 @@ class WikiImporter {
while ( $skip ? $this->reader->next() : $this->reader->read() ) {
if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
- $this->reader->name == 'page') {
+ $this->reader->name == 'page' ) {
break;
}
@@ -618,13 +627,13 @@ class WikiImporter {
$this->debug( "Enter revision handler" );
$revisionInfo = array();
- $normalFields = array( 'id', 'timestamp', 'comment', 'minor', 'text' );
+ $normalFields = array( 'id', 'timestamp', 'comment', 'minor', 'model', 'format', 'text' );
$skip = false;
while ( $skip ? $this->reader->next() : $this->reader->read() ) {
if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
- $this->reader->name == 'revision') {
+ $this->reader->name == 'revision' ) {
break;
}
@@ -663,6 +672,12 @@ class WikiImporter {
if ( isset( $revisionInfo['text'] ) ) {
$revision->setText( $revisionInfo['text'] );
}
+ if ( isset( $revisionInfo['model'] ) ) {
+ $revision->setModel( $revisionInfo['model'] );
+ }
+ if ( isset( $revisionInfo['format'] ) ) {
+ $revision->setFormat( $revisionInfo['format'] );
+ }
$revision->setTitle( $pageInfo['_title'] );
if ( isset( $revisionInfo['timestamp'] ) ) {
@@ -704,7 +719,7 @@ class WikiImporter {
while ( $skip ? $this->reader->next() : $this->reader->read() ) {
if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
- $this->reader->name == 'upload') {
+ $this->reader->name == 'upload' ) {
break;
}
@@ -801,7 +816,7 @@ class WikiImporter {
while ( $this->reader->read() ) {
if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
- $this->reader->name == 'contributor') {
+ $this->reader->name == 'contributor' ) {
break;
}
@@ -889,7 +904,7 @@ class UploadSourceAdapter {
* @return bool
*/
function stream_open( $path, $mode, $options, &$opened_path ) {
- $url = parse_url($path);
+ $url = parse_url( $path );
$id = $url['host'];
if ( !isset( self::$sourceRegistrations[$id] ) ) {
@@ -910,22 +925,22 @@ class UploadSourceAdapter {
$leave = false;
while ( !$leave && !$this->mSource->atEnd() &&
- strlen($this->mBuffer) < $count ) {
+ strlen( $this->mBuffer ) < $count ) {
$read = $this->mSource->readChunk();
- if ( !strlen($read) ) {
+ if ( !strlen( $read ) ) {
$leave = true;
}
$this->mBuffer .= $read;
}
- if ( strlen($this->mBuffer) ) {
+ if ( strlen( $this->mBuffer ) ) {
$return = substr( $this->mBuffer, 0, $count );
$this->mBuffer = substr( $this->mBuffer, $count );
}
- $this->mPosition += strlen($return);
+ $this->mPosition += strlen( $return );
return $return;
}
@@ -1015,7 +1030,10 @@ class WikiRevision {
var $timestamp = "20010115000000";
var $user = 0;
var $user_text = "";
+ var $model = null;
+ var $format = null;
var $text = "";
+ var $content = null;
var $comment = "";
var $minor = false;
var $type = "";
@@ -1072,6 +1090,20 @@ class WikiRevision {
}
/**
+ * @param $model
+ */
+ function setModel( $model ) {
+ $this->model = $model;
+ }
+
+ /**
+ * @param $format
+ */
+ function setFormat( $format ) {
+ $this->format = $format;
+ }
+
+ /**
* @param $text
*/
function setText( $text ) {
@@ -1194,12 +1226,55 @@ class WikiRevision {
/**
* @return string
+ *
+ * @deprecated Since 1.21, use getContent() instead.
*/
function getText() {
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
return $this->text;
}
/**
+ * @return Content
+ */
+ function getContent() {
+ if ( is_null( $this->content ) ) {
+ $this->content =
+ ContentHandler::makeContent(
+ $this->text,
+ $this->getTitle(),
+ $this->getModel(),
+ $this->getFormat()
+ );
+ }
+
+ return $this->content;
+ }
+
+ /**
+ * @return String
+ */
+ function getModel() {
+ if ( is_null( $this->model ) ) {
+ $this->model = $this->getTitle()->getContentModel();
+ }
+
+ return $this->model;
+ }
+
+ /**
+ * @return String
+ */
+ function getFormat() {
+ if ( is_null( $this->model ) ) {
+ $this->format = ContentHandler::getForTitle( $this->getTitle() )->getDefaultFormat();
+ }
+
+ return $this->format;
+ }
+
+ /**
* @return string
*/
function getComment() {
@@ -1322,7 +1397,7 @@ class WikiRevision {
array( 'rev_page' => $pageId,
'rev_timestamp' => $dbw->timestamp( $this->timestamp ),
'rev_user_text' => $userText,
- 'rev_comment' => $this->getComment() ),
+ 'rev_comment' => $this->getComment() ),
__METHOD__
);
if( $prior ) {
@@ -1337,12 +1412,15 @@ class WikiRevision {
# @todo FIXME: Use original rev_id optionally (better for backups)
# Insert the row
$revision = new Revision( array(
- 'page' => $pageId,
- 'text' => $this->getText(),
- 'comment' => $this->getComment(),
- 'user' => $userId,
- 'user_text' => $userText,
- 'timestamp' => $this->timestamp,
+ 'title' => $this->title,
+ 'page' => $pageId,
+ 'content_model' => $this->getModel(),
+ 'content_format' => $this->getFormat(),
+ 'text' => $this->getContent()->serialize( $this->getFormat() ), //XXX: just set 'content' => $this->getContent()?
+ 'comment' => $this->getComment(),
+ 'user' => $userId,
+ 'user_text' => $userText,
+ 'timestamp' => $this->timestamp,
'minor_edit' => $this->minor,
) );
$revision->insertOn( $dbw );
@@ -1371,13 +1449,13 @@ class WikiRevision {
// @todo FIXME: Use original log ID (better for backups)
$prior = $dbw->selectField( 'logging', '1',
array( 'log_type' => $this->getType(),
- 'log_action' => $this->getAction(),
+ 'log_action' => $this->getAction(),
'log_timestamp' => $dbw->timestamp( $this->timestamp ),
'log_namespace' => $this->getTitle()->getNamespace(),
- 'log_title' => $this->getTitle()->getDBkey(),
- 'log_comment' => $this->getComment(),
+ 'log_title' => $this->getTitle()->getDBkey(),
+ 'log_comment' => $this->getComment(),
#'log_user_text' => $this->user_text,
- 'log_params' => $this->params ),
+ 'log_params' => $this->params ),
__METHOD__
);
// @todo FIXME: This could fail slightly for multiple matches :P
@@ -1460,7 +1538,7 @@ class WikiRevision {
}
if ( $status->isGood() ) {
- wfDebug( __METHOD__ . ": Succesful\n" );
+ wfDebug( __METHOD__ . ": Successful\n" );
return true;
} else {
wfDebug( __METHOD__ . ': failed: ' . $status->getXml() . "\n" );
@@ -1575,11 +1653,11 @@ class ImportStreamSource {
static function newFromUpload( $fieldname = "xmlimport" ) {
$upload =& $_FILES[$fieldname];
- if( !isset( $upload ) || !$upload['name'] ) {
+ if( $upload === null || !$upload['name'] ) {
return Status::newFatal( 'importnofile' );
}
if( !empty( $upload['error'] ) ) {
- switch($upload['error']){
+ switch( $upload['error'] ) {
case 1: # The uploaded file exceeds the upload_max_filesize directive in php.ini.
return Status::newFatal( 'importuploaderrorsize' );
case 2: # The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.
diff --git a/includes/Init.php b/includes/Init.php
index a8540f2c..66f9544d 100644
--- a/includes/Init.php
+++ b/includes/Init.php
@@ -120,7 +120,7 @@ class MWInit {
*
* require( MWInit::extSetupPath( 'ParserFunctions/ParserFunctions.php' ) );
*
- * @param $extRel string The path relative to the extensions directory, as defined by
+ * @param string $extRel The path relative to the extensions directory, as defined by
* $wgExtensionsDirectory.
*
* @return string
@@ -173,7 +173,7 @@ class MWInit {
}
/**
- * Determine wether a method exists within a class, using a method which works
+ * Determine whether a method exists within a class, using a method which works
* under HipHop.
*
* Note that under HipHop when method_exists is given a string for it's class
diff --git a/includes/Licenses.php b/includes/Licenses.php
index ba504a99..e8aee0b5 100644
--- a/includes/Licenses.php
+++ b/includes/Licenses.php
@@ -117,7 +117,7 @@ class Licenses extends HTMLFormField {
* @param $depth int
*/
protected function makeHtml( $tagset, $depth = 0 ) {
- foreach ( $tagset as $key => $val )
+ foreach ( $tagset as $key => $val ) {
if ( is_array( $val ) ) {
$this->html .= $this->outputOption(
$key, '',
@@ -135,6 +135,7 @@ class Licenses extends HTMLFormField {
$depth
);
}
+ }
}
/**
@@ -148,8 +149,10 @@ class Licenses extends HTMLFormField {
$msgObj = $this->msg( $message );
$text = $msgObj->exists() ? $msgObj->text() : $message;
$attribs['value'] = $value;
- if ( $value === $this->selected )
+ if ( $value === $this->selected ) {
$attribs['selected'] = 'selected';
+ }
+
$val = str_repeat( /* &nbsp */ "\xc2\xa0", $depth * 2 ) . $text;
return str_repeat( "\t", $depth ) . Xml::element( 'option', $attribs, $val ) . "\n";
}
@@ -208,7 +211,7 @@ class License {
/**
* Constructor
*
- * @param $str String: license name??
+ * @param string $str license name??
*/
function __construct( $str ) {
list( $text, $template ) = explode( '|', strrev( $str ), 2 );
diff --git a/includes/LinkFilter.php b/includes/LinkFilter.php
index 214f4959..11b65595 100644
--- a/includes/LinkFilter.php
+++ b/includes/LinkFilter.php
@@ -20,10 +20,9 @@
* @file
*/
-
/**
* Some functions to help implement an external link filter for spam control.
- *
+ *
* @todo implement the filter. Currently these are just some functions to help
* maintenance/cleanupSpam.php remove links to a single specified domain. The
* next thing is to implement functions for checking a given page against a big
@@ -34,13 +33,22 @@
class LinkFilter {
/**
- * Check whether $text contains a link to $filterEntry
+ * Check whether $content contains a link to $filterEntry
*
- * @param $text String: text to check
- * @param $filterEntry String: domainparts, see makeRegex() for more details
+ * @param $content Content: content to check
+ * @param string $filterEntry domainparts, see makeRegex() for more details
* @return Integer: 0 if no match or 1 if there's at least one match
*/
- static function matchEntry( $text, $filterEntry ) {
+ static function matchEntry( Content $content, $filterEntry ) {
+ if ( !( $content instanceof TextContent ) ) {
+ //TODO: handle other types of content too.
+ // Maybe create ContentHandler::matchFilter( LinkFilter ).
+ // Think about a common base class for LinkFilter and MagicWord.
+ return 0;
+ }
+
+ $text = $content->getNativeData();
+
$regex = LinkFilter::makeRegex( $filterEntry );
return preg_match( $regex, $text );
}
@@ -48,7 +56,7 @@ class LinkFilter {
/**
* Builds a regex pattern for $filterEntry.
*
- * @param $filterEntry String: URL, if it begins with "*.", it'll be
+ * @param string $filterEntry URL, if it begins with "*.", it'll be
* replaced to match any subdomain
* @return String: regex pattern, for preg_match()
*/
@@ -76,11 +84,11 @@ class LinkFilter {
*
* Asterisks in any other location are considered invalid.
*
- * @param $filterEntry String: domainparts
+ * @param string $filterEntry domainparts
* @param $prot String: protocol
* @return Array to be passed to DatabaseBase::buildLike() or false on error
*/
- public static function makeLikeArray( $filterEntry , $prot = 'http://' ) {
+ public static function makeLikeArray( $filterEntry, $prot = 'http://' ) {
$db = wfGetDB( DB_MASTER );
if ( substr( $filterEntry, 0, 2 ) == '*.' ) {
$subdomains = true;
@@ -109,18 +117,18 @@ class LinkFilter {
}
// Reverse the labels in the hostname, convert to lower case
// For emails reverse domainpart only
- if ( $prot == 'mailto:' && strpos($host, '@') ) {
- // complete email adress
+ if ( $prot == 'mailto:' && strpos( $host, '@' ) ) {
+ // complete email address
$mailparts = explode( '@', $host );
$domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) );
$host = $domainpart . '@' . $mailparts[0];
$like = array( "$prot$host", $db->anyString() );
} elseif ( $prot == 'mailto:' ) {
- // domainpart of email adress only. do not add '.'
- $host = strtolower( implode( '.', array_reverse( explode( '.', $host ) ) ) );
- $like = array( "$prot$host", $db->anyString() );
+ // domainpart of email address only. do not add '.'
+ $host = strtolower( implode( '.', array_reverse( explode( '.', $host ) ) ) );
+ $like = array( "$prot$host", $db->anyString() );
} else {
- $host = strtolower( implode( '.', array_reverse( explode( '.', $host ) ) ) );
+ $host = strtolower( implode( '.', array_reverse( explode( '.', $host ) ) ) );
if ( substr( $host, -1, 1 ) !== '.' ) {
$host .= '.';
}
@@ -140,7 +148,7 @@ class LinkFilter {
/**
* Filters an array returned by makeLikeArray(), removing everything past first pattern placeholder.
*
- * @param $arr array: array to filter
+ * @param array $arr array to filter
* @return array filtered array
*/
public static function keepOneWildcard( $arr ) {
diff --git a/includes/Linker.php b/includes/Linker.php
index 56626bd7..972adfce 100644
--- a/includes/Linker.php
+++ b/includes/Linker.php
@@ -33,13 +33,13 @@ class Linker {
* Flags for userToolLinks()
*/
const TOOL_LINKS_NOBLOCK = 1;
- const TOOL_LINKS_EMAIL = 2;
+ const TOOL_LINKS_EMAIL = 2;
/**
- * Get the appropriate HTML attributes to add to the "a" element of an ex-
- * ternal link, as created by [wikisyntax].
+ * Get the appropriate HTML attributes to add to the "a" element of an
+ * external link, as created by [wikisyntax].
*
- * @param $class String: the contents of the class attribute; if an empty
+ * @param string $class the contents of the class attribute; if an empty
* string is passed, which is the default value, defaults to 'external'.
* @return string
* @deprecated since 1.18 Just pass the external class directly to something using Html::expandAttributes
@@ -50,13 +50,12 @@ class Linker {
}
/**
- * Get the appropriate HTML attributes to add to the "a" element of an in-
- * terwiki link.
+ * Get the appropriate HTML attributes to add to the "a" element of an interwiki link.
*
- * @param $title String: the title text for the link, URL-encoded (???) but
+ * @param string $title the title text for the link, URL-encoded (???) but
* not HTML-escaped
- * @param $unused String: unused
- * @param $class String: the contents of the class attribute; if an empty
+ * @param string $unused unused
+ * @param string $class the contents of the class attribute; if an empty
* string is passed, which is the default value, defaults to 'external'.
* @return string
*/
@@ -73,13 +72,12 @@ class Linker {
}
/**
- * Get the appropriate HTML attributes to add to the "a" element of an in-
- * ternal link.
+ * Get the appropriate HTML attributes to add to the "a" element of an internal link.
*
- * @param $title String: the title text for the link, URL-encoded (???) but
+ * @param string $title the title text for the link, URL-encoded (???) but
* not HTML-escaped
- * @param $unused String: unused
- * @param $class String: the contents of the class attribute, default none
+ * @param string $unused unused
+ * @param string $class the contents of the class attribute, default none
* @return string
*/
static function getInternalLinkAttributes( $title, $unused = null, $class = '' ) {
@@ -89,12 +87,12 @@ class Linker {
}
/**
- * Get the appropriate HTML attributes to add to the "a" element of an in-
- * ternal link, given the Title object for the page we want to link to.
+ * Get the appropriate HTML attributes to add to the "a" element of an internal
+ * link, given the Title object for the page we want to link to.
*
* @param $nt Title
- * @param $unused String: unused
- * @param $class String: the contents of the class attribute, default none
+ * @param string $unused unused
+ * @param string $class the contents of the class attribute, default none
* @param $title Mixed: optional (unescaped) string to use in the title
* attribute; if false, default to the name of the page we're linking to
* @return string
@@ -171,15 +169,15 @@ class Linker {
* the link text. This is raw HTML and will not be escaped. If null,
* defaults to the prefixed text of the Title; or if the Title is just a
* fragment, the contents of the fragment.
- * @param $customAttribs array A key => value array of extra HTML attri-
- * butes, such as title and class. (href is ignored.) Classes will be
+ * @param array $customAttribs A key => value array of extra HTML attributes,
+ * such as title and class. (href is ignored.) Classes will be
* merged with the default classes, while other attributes will replace
* default attributes. All passed attribute values will be HTML-escaped.
* A false attribute value means to suppress that attribute.
* @param $query array The query string to append to the URL
* you're linking to, in key => value array form. Query keys and values
* will be URL-encoded.
- * @param $options string|array String or array of strings:
+ * @param string|array $options String or array of strings:
* 'known': Page is known to exist, so don't check if it does.
* 'broken': Page is known not to exist, so don't check if it does.
* 'noclasses': Don't add any classes automatically (includes "new",
@@ -188,6 +186,8 @@ class Linker {
* cons.
* 'forcearticlepath': Use the article path always, even with a querystring.
* Has compatibility issues on some setups, so avoid wherever possible.
+ * 'http': Force a full URL with http:// as the scheme.
+ * 'https': Force a full URL with https:// as the scheme.
* @return string HTML <a> attribute
*/
public static function link(
@@ -273,7 +273,7 @@ class Linker {
* Returns the Url used to link to a Title
*
* @param $target Title
- * @param $query Array: query parameters
+ * @param array $query query parameters
* @param $options Array
* @return String
*/
@@ -294,7 +294,16 @@ class Linker {
$query['action'] = 'edit';
$query['redlink'] = '1';
}
- $ret = $target->getLinkURL( $query );
+
+ if ( in_array( 'http', $options ) ) {
+ $proto = PROTO_HTTP;
+ } elseif ( in_array( 'https', $options ) ) {
+ $proto = PROTO_HTTPS;
+ } else {
+ $proto = PROTO_RELATIVE;
+ }
+
+ $ret = $target->getLinkURL( $query, false, $proto );
wfProfileOut( __METHOD__ );
return $ret;
}
@@ -371,13 +380,13 @@ class Linker {
* @return string
*/
private static function linkText( $target ) {
- # We might be passed a non-Title by make*LinkObj(). Fail gracefully.
+ // We might be passed a non-Title by make*LinkObj(). Fail gracefully.
if ( !$target instanceof Title ) {
return '';
}
- # If the target is just a fragment, with no title, we return the frag-
- # ment text. Otherwise, we return the title text itself.
+ // If the target is just a fragment, with no title, we return the fragment
+ // text. Otherwise, we return the title text itself.
if ( $target->getPrefixedText() === '' && $target->getFragment() !== '' ) {
return htmlspecialchars( $target->getFragment() );
}
@@ -413,10 +422,10 @@ class Linker {
* despite $query not being used.
*
* @param $nt Title
- * @param $html String [optional]
- * @param $query String [optional]
- * @param $trail String [optional]
- * @param $prefix String [optional]
+ * @param string $html [optional]
+ * @param string $query [optional]
+ * @param string $trail [optional]
+ * @param string $prefix [optional]
*
*
* @return string
@@ -435,8 +444,9 @@ class Linker {
* a value indicating that the title object is invalid.
*
* @param $context IContextSource context to use to get the messages
- * @param $namespace int Namespace number
- * @param $title string Text of the title, without the namespace part
+ * @param int $namespace Namespace number
+ * @param string $title Text of the title, without the namespace part
+ * @return string
*/
public static function getInvalidTitleDescription( IContextSource $context, $namespace, $title ) {
global $wgContLang;
@@ -522,7 +532,7 @@ class Linker {
* @param $parser Parser object
* @param $title Title object of the file (not the currently viewed page)
* @param $file File object, or false if it doesn't exist
- * @param $frameParams Array: associative array of parameters external to the media handler.
+ * @param array $frameParams associative array of parameters external to the media handler.
* Boolean parameters are indicated by presence or absence, the value is arbitrary and
* will often be false.
* thumbnail If present, downscale and frame
@@ -540,13 +550,13 @@ class Linker {
* caption HTML for image caption.
* link-url URL to link to
* link-title Title object to link to
- * link-target Value for the target attribue, only with link-url
+ * link-target Value for the target attribute, only with link-url
* no-link Boolean, suppress description link
*
- * @param $handlerParams Array: associative array of media handler parameters, to be passed
+ * @param array $handlerParams associative array of media handler parameters, to be passed
* to transform(). Typical keys are "width" and "page".
- * @param $time String: timestamp of the file, set as false for current
- * @param $query String: query params for desc url
+ * @param string $time timestamp of the file, set as false for current
+ * @param string $query query params for desc url
* @param $widthOption: Used by the parser to remember the user preference thumbnailsize
* @since 1.20
* @return String: HTML for an image, with links, wrappers, etc.
@@ -588,9 +598,9 @@ class Linker {
$prefix = $postfix = '';
if ( 'center' == $fp['align'] ) {
- $prefix = '<div class="center">';
+ $prefix = '<div class="center">';
$postfix = '</div>';
- $fp['align'] = 'none';
+ $fp['align'] = 'none';
}
if ( $file && !isset( $hp['width'] ) ) {
if ( isset( $hp['height'] ) && $file->isVectorized() ) {
@@ -604,7 +614,7 @@ class Linker {
if ( isset( $fp['thumbnail'] ) || isset( $fp['framed'] ) || isset( $fp['frameless'] ) || !$hp['width'] ) {
global $wgThumbLimits, $wgThumbUpright;
- if ( !isset( $widthOption ) || !isset( $wgThumbLimits[$widthOption] ) ) {
+ if ( $widthOption === null || !isset( $wgThumbLimits[$widthOption] ) ) {
$widthOption = User::getDefaultOption( 'thumbsize' );
}
@@ -648,7 +658,7 @@ class Linker {
if ( $file && isset( $fp['frameless'] ) ) {
$srcWidth = $file->getWidth( $page );
# For "frameless" option: do not present an image bigger than the source (for bitmap-style images)
- # This is the same behaviour as the "thumb" option does it already.
+ # This is the same behavior as the "thumb" option does it already.
if ( $srcWidth && !$file->mustRender() && $hp['width'] > $srcWidth ) {
$hp['width'] = $srcWidth;
}
@@ -664,12 +674,14 @@ class Linker {
if ( !$thumb ) {
$s = self::makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time == true );
} else {
+ self::processResponsiveImages( $file, $thumb, $hp );
$params = array(
'alt' => $fp['alt'],
'title' => $fp['title'],
'valign' => isset( $fp['valign'] ) ? $fp['valign'] : false,
'img-class' => $fp['class'] );
if ( isset( $fp['border'] ) ) {
+ // TODO: BUG? Both values are identical
$params['img-class'] .= ( $params['img-class'] !== '' ) ? ' thumbborder' : 'thumbborder';
}
$params = self::getImageLinkMTOParams( $fp, $query, $parser ) + $params;
@@ -696,8 +708,8 @@ class Linker {
/**
* Get the link parameters for MediaTransformOutput::toHtml() from given
* frame parameters supplied by the Parser.
- * @param $frameParams array The frame parameters
- * @param $query string An optional query string to add to description page links
+ * @param array $frameParams The frame parameters
+ * @param string $query An optional query string to add to description page links
* @return array
*/
private static function getImageLinkMTOParams( $frameParams, $query = '', $parser = null ) {
@@ -711,7 +723,7 @@ class Linker {
$extLinkAttrs = $parser->getExternalLinkAttribs( $frameParams['link-url'] );
foreach ( $extLinkAttrs as $name => $val ) {
// Currently could include 'rel' and 'target'
- $mtoParams['parser-extlink-'.$name] = $val;
+ $mtoParams['parser-extlink-' . $name] = $val;
}
}
} elseif ( isset( $frameParams['link-title'] ) && $frameParams['link-title'] !== '' ) {
@@ -738,7 +750,7 @@ class Linker {
* @return mixed
*/
public static function makeThumbLinkObj( Title $title, $file, $label = '', $alt,
- $align = 'right', $params = array(), $framed = false , $manualthumb = "" )
+ $align = 'right', $params = array(), $framed = false, $manualthumb = "" )
{
$frameParams = array(
'alt' => $alt,
@@ -784,6 +796,7 @@ class Linker {
$hp['width'] = isset( $fp['upright'] ) ? 130 : 180;
}
$thumb = false;
+ $noscale = false;
if ( !$exists ) {
$outerWidth = $hp['width'] + 2;
@@ -802,9 +815,10 @@ class Linker {
} elseif ( isset( $fp['framed'] ) ) {
// Use image dimensions, don't scale
$thumb = $file->getUnscaledThumb( $hp );
+ $noscale = true;
} else {
# Do not present an image bigger than the source, for bitmap-style images
- # This is a hack to maintain compatibility with arbitrary pre-1.10 behaviour
+ # This is a hack to maintain compatibility with arbitrary pre-1.10 behavior
$srcWidth = $file->getWidth( $page );
if ( $srcWidth && !$file->mustRender() && $hp['width'] > $srcWidth ) {
$hp['width'] = $srcWidth;
@@ -835,6 +849,9 @@ class Linker {
$s .= wfMessage( 'thumbnail_error', '' )->escaped();
$zoomIcon = '';
} else {
+ if ( !$noscale ) {
+ self::processResponsiveImages( $file, $thumb, $hp );
+ }
$params = array(
'alt' => $fp['alt'],
'title' => $fp['title'],
@@ -862,11 +879,42 @@ class Linker {
}
/**
+ * Process responsive images: add 1.5x and 2x subimages to the thumbnail, where
+ * applicable.
+ *
+ * @param File $file
+ * @param MediaOutput $thumb
+ * @param array $hp image parameters
+ */
+ protected static function processResponsiveImages( $file, $thumb, $hp ) {
+ global $wgResponsiveImages;
+ if ( $wgResponsiveImages ) {
+ $hp15 = $hp;
+ $hp15['width'] = round( $hp['width'] * 1.5 );
+ $hp20 = $hp;
+ $hp20['width'] = $hp['width'] * 2;
+ if ( isset( $hp['height'] ) ) {
+ $hp15['height'] = round( $hp['height'] * 1.5 );
+ $hp20['height'] = $hp['height'] * 2;
+ }
+
+ $thumb15 = $file->transform( $hp15 );
+ $thumb20 = $file->transform( $hp20 );
+ if ( $thumb15->url !== $thumb->url ) {
+ $thumb->responsiveUrls['1.5'] = $thumb15->url;
+ }
+ if ( $thumb20->url !== $thumb->url ) {
+ $thumb->responsiveUrls['2'] = $thumb20->url;
+ }
+ }
+ }
+
+ /**
* Make a "broken" link to an image
*
* @param $title Title object
- * @param $label String: link label (plain text)
- * @param $query String: query string
+ * @param string $label link label (plain text)
+ * @param string $query query string
* @param $unused1 Unused parameter kept for b/c
* @param $unused2 Unused parameter kept for b/c
* @param $time Boolean: a file of a certain timestamp was requested
@@ -898,17 +946,17 @@ class Linker {
return '<a href="' . htmlspecialchars( $href ) . '" class="new" title="' .
htmlspecialchars( $title->getPrefixedText(), ENT_QUOTES ) . '">' .
$encLabel . '</a>';
- } else {
- wfProfileOut( __METHOD__ );
- return self::linkKnown( $title, $encLabel, array(), wfCgiToArray( $query ) );
}
+
+ wfProfileOut( __METHOD__ );
+ return self::linkKnown( $title, $encLabel, array(), wfCgiToArray( $query ) );
}
/**
* Get the URL to upload a certain file
*
* @param $destFile Title object of the file to upload
- * @param $query String: urlencoded query string to prepend
+ * @param string $query urlencoded query string to prepend
* @return String: urlencoded URL
*/
protected static function getUploadUrl( $destFile, $query = '' ) {
@@ -931,8 +979,8 @@ class Linker {
* Create a direct link to a given uploaded file.
*
* @param $title Title object.
- * @param $html String: pre-sanitized HTML
- * @param $time string: MW timestamp of file creation time
+ * @param string $html pre-sanitized HTML
+ * @param string $time MW timestamp of file creation time
* @return String: HTML
*/
public static function makeMediaLinkObj( $title, $html = '', $time = false ) {
@@ -946,7 +994,7 @@ class Linker {
*
* @param $title Title object.
* @param $file File|bool mixed File object or false
- * @param $html String: pre-sanitized HTML
+ * @param string $html pre-sanitized HTML
* @return String: HTML
*
* @todo Handle invalid or missing images better.
@@ -979,19 +1027,21 @@ class Linker {
$key = strtolower( $name );
}
- return self::linkKnown( SpecialPage::getTitleFor( $name ) , wfMessage( $key )->text() );
+ return self::linkKnown( SpecialPage::getTitleFor( $name ), wfMessage( $key )->text() );
}
/**
* Make an external link
- * @param $url String: URL to link to
- * @param $text String: text of link
+ * @param string $url URL to link to
+ * @param string $text text of link
* @param $escape Boolean: do we escape the link text?
- * @param $linktype String: type of external link. Gets added to the classes
- * @param $attribs Array of extra attributes to <a>
+ * @param string $linktype type of external link. Gets added to the classes
+ * @param array $attribs of extra attributes to <a>
+ * @param $title Title|null Title object used for title specific link attributes
* @return string
*/
- public static function makeExternalLink( $url, $text, $escape = true, $linktype = '', $attribs = array() ) {
+ public static function makeExternalLink( $url, $text, $escape = true, $linktype = '', $attribs = array(), $title = null ) {
+ global $wgTitle;
$class = "external";
if ( $linktype ) {
$class .= " $linktype";
@@ -1004,6 +1054,11 @@ class Linker {
if ( $escape ) {
$text = htmlspecialchars( $text );
}
+
+ if ( !$title ) {
+ $title = $wgTitle;
+ }
+ $attribs['rel'] = Parser::getExternalLinkRel( $url, $title );
$link = '';
$success = wfRunHooks( 'LinkerMakeExternalLink',
array( &$url, &$text, &$link, &$attribs, $linktype ) );
@@ -1018,14 +1073,17 @@ class Linker {
/**
* Make user link (or user contributions for unregistered users)
* @param $userId Integer: user id in database.
- * @param $userName String: user name in database.
- * @param $altUserName String: text to display instead of the user name (optional)
+ * @param string $userName user name in database.
+ * @param string $altUserName text to display instead of the user name (optional)
* @return String: HTML fragment
* @since 1.19 Method exists for a long time. $altUserName was added in 1.19.
*/
public static function userLink( $userId, $userName, $altUserName = false ) {
if ( $userId == 0 ) {
$page = SpecialPage::getTitleFor( 'Contributions', $userName );
+ if ( $altUserName === false ) {
+ $altUserName = IP::prettifyIP( $userName );
+ }
} else {
$page = Title::makeTitle( NS_USER, $userName );
}
@@ -1041,7 +1099,7 @@ class Linker {
* Generate standard user tool links (talk, contributions, block link, etc.)
*
* @param $userId Integer: user identifier
- * @param $userText String: user name or IP address
+ * @param string $userText user name or IP address
* @param $redContribsWhenNoEdits Boolean: should the contributions link be
* red if the user has no edits?
* @param $flags Integer: customisation flags (e.g. Linker::TOOL_LINKS_NOBLOCK and Linker::TOOL_LINKS_EMAIL)
@@ -1064,8 +1122,11 @@ class Linker {
// check if the user has an edit
$attribs = array();
if ( $redContribsWhenNoEdits ) {
- $count = !is_null( $edits ) ? $edits : User::edits( $userId );
- if ( $count == 0 ) {
+ if ( intval( $edits ) === 0 && $edits !== 0 ) {
+ $user = User::newFromId( $userId );
+ $edits = $user->getEditCount();
+ }
+ if ( $edits === 0 ) {
$attribs['class'] = 'new';
}
}
@@ -1096,7 +1157,7 @@ class Linker {
/**
* Alias for userToolLinks( $userId, $userText, true );
* @param $userId Integer: user identifier
- * @param $userText String: user name or IP address
+ * @param string $userText user name or IP address
* @param $edits Integer: user edit count (optional, for performance)
* @return String
*/
@@ -1104,10 +1165,9 @@ class Linker {
return self::userToolLinks( $userId, $userText, true, 0, $edits );
}
-
/**
* @param $userId Integer: user id in database.
- * @param $userText String: user name in database.
+ * @param string $userText user name in database.
* @return String: HTML fragment with user talk link
*/
public static function userTalkLink( $userId, $userText ) {
@@ -1118,7 +1178,7 @@ class Linker {
/**
* @param $userId Integer: userid
- * @param $userText String: user name in database.
+ * @param string $userText user name in database.
* @return String: HTML fragment with block link
*/
public static function blockLink( $userId, $userText ) {
@@ -1129,7 +1189,7 @@ class Linker {
/**
* @param $userId Integer: userid
- * @param $userText String: user name in database.
+ * @param string $userText user name in database.
* @return String: HTML fragment with e-mail user link
*/
public static function emailLink( $userId, $userText ) {
@@ -1186,7 +1246,7 @@ class Linker {
/**
* This function is called by all recent changes variants, by the page history,
* and by the user contributions list. It is responsible for formatting edit
- * comments. It escapes any HTML in the comment, but adds some CSS to format
+ * summaries. It escapes any HTML in the summary, but adds some CSS to format
* auto-generated comments (from section editing) and formats [[wikilinks]].
*
* @author Erik Moeller <moeller@scireview.de>
@@ -1223,13 +1283,14 @@ class Linker {
static $autocommentLocal;
/**
+ * Converts autogenerated comments in edit summaries into section links.
* The pattern for autogen comments is / * foo * /, which makes for
* some nasty regex.
* We look for all comments, match any text before and after the comment,
* add a separator where needed and format the comment itself with CSS
* Called by Linker::formatComment.
*
- * @param $comment String: comment text
+ * @param string $comment comment text
* @param $title Title|null An optional title object used to links to sections
* @param $local Boolean: whether section links should refer to local page
* @return String: formatted comment
@@ -1248,6 +1309,7 @@ class Linker {
}
/**
+ * Helper function for Linker::formatAutocomments
* @param $match
* @return string
*/
@@ -1312,7 +1374,7 @@ class Linker {
* is ignored
*
* @todo FIXME: Doesn't handle sub-links as in image thumb texts like the main parser
- * @param $comment String: text to format links in
+ * @param string $comment text to format links in
* @param $title Title|null An optional title object used to links to sections
* @param $local Boolean: whether section links should refer to local page
* @return String
@@ -1502,7 +1564,7 @@ class Linker {
public static function commentBlock( $comment, $title = null, $local = false ) {
// '*' used to be the comment inserted by the software way back
// in antiquity in case none was provided, here for backwards
- // compatability, acc. to brion -ævar
+ // compatibility, acc. to brion -ævar
if ( $comment == '' || $comment == '*' ) {
return '';
} else {
@@ -1600,7 +1662,7 @@ class Linker {
/**
* Wraps the TOC in a table and provides the hide/collapse javascript.
*
- * @param $toc String: html of the Table Of Contents
+ * @param string $toc html of the Table Of Contents
* @param $lang String|Language|false: Language for the toc title, defaults to user language
* @return String: full html of the TOC
*/
@@ -1608,18 +1670,17 @@ class Linker {
$lang = wfGetLangObj( $lang );
$title = wfMessage( 'toc' )->inLanguage( $lang )->escaped();
- return
- '<table id="toc" class="toc"><tr><td>'
- . '<div id="toctitle"><h2>' . $title . "</h2></div>\n"
- . $toc
- . "</ul>\n</td></tr></table>\n";
+ return '<table id="toc" class="toc"><tr><td>'
+ . '<div id="toctitle"><h2>' . $title . "</h2></div>\n"
+ . $toc
+ . "</ul>\n</td></tr></table>\n";
}
/**
* Generate a table of contents from a section tree
* Currently unused.
*
- * @param $tree array Return value of ParserOutput::getSections()
+ * @param array $tree Return value of ParserOutput::getSections()
* @return String: HTML fragment
*/
public static function generateTOC( $tree ) {
@@ -1647,12 +1708,12 @@ class Linker {
* Create a headline for content
*
* @param $level Integer: the level of the headline (1-6)
- * @param $attribs String: any attributes for the headline, starting with
+ * @param string $attribs any attributes for the headline, starting with
* a space and ending with '>'
* This *must* be at least '>' for no attribs
- * @param $anchor String: the anchor to give the headline (the bit after the #)
- * @param $html String: html for the text of the header
- * @param $link String: HTML to add for the section edit link
+ * @param string $anchor the anchor to give the headline (the bit after the #)
+ * @param string $html html for the text of the header
+ * @param string $link HTML to add for the section edit link
* @param $legacyAnchor Mixed: a second, optional anchor to give for
* backward compatibility (false to omit)
*
@@ -1699,19 +1760,101 @@ class Linker {
* changes, so this allows sysops to combat a busy vandal without bothering
* other users.
*
+ * If the option verify is set this function will return the link only in case the
+ * revision can be reverted. Please note that due to performance limitations
+ * it might be assumed that a user isn't the only contributor of a page while
+ * (s)he is, which will lead to useless rollback links. Furthermore this wont
+ * work if $wgShowRollbackEditCount is disabled, so this can only function
+ * as an additional check.
+ *
+ * If the option noBrackets is set the rollback link wont be enclosed in []
+ *
* @param $rev Revision object
* @param $context IContextSource context to use or null for the main context.
+ * @param $options array
* @return string
*/
- public static function generateRollback( $rev, IContextSource $context = null ) {
+ public static function generateRollback( $rev, IContextSource $context = null, $options = array( 'verify' ) ) {
if ( $context === null ) {
$context = RequestContext::getMain();
}
+ $editCount = false;
+ if ( in_array( 'verify', $options ) ) {
+ $editCount = self::getRollbackEditCount( $rev, true );
+ if ( $editCount === false ) {
+ return '';
+ }
+ }
+
+ $inner = self::buildRollbackLink( $rev, $context, $editCount );
+
+ if ( !in_array( 'noBrackets', $options ) ) {
+ $inner = $context->msg( 'brackets' )->rawParams( $inner )->plain();
+ }
- return '<span class="mw-rollback-link">'
- . $context->msg( 'brackets' )->rawParams(
- self::buildRollbackLink( $rev, $context ) )->plain()
- . '</span>';
+ return '<span class="mw-rollback-link">' . $inner . '</span>';
+ }
+
+ /**
+ * This function will return the number of revisions which a rollback
+ * would revert and, if $verify is set it will verify that a revision
+ * can be reverted (that the user isn't the only contributor and the
+ * revision we might rollback to isn't deleted). These checks can only
+ * function as an additional check as this function only checks against
+ * the last $wgShowRollbackEditCount edits.
+ *
+ * Returns null if $wgShowRollbackEditCount is disabled or false if $verify
+ * is set and the user is the only contributor of the page.
+ *
+ * @param $rev Revision object
+ * @param bool $verify Try to verify that this revision can really be rolled back
+ * @return integer|bool|null
+ */
+ public static function getRollbackEditCount( $rev, $verify ) {
+ global $wgShowRollbackEditCount;
+ if ( !is_int( $wgShowRollbackEditCount ) || !$wgShowRollbackEditCount > 0 ) {
+ // Nothing has happened, indicate this by returning 'null'
+ return null;
+ }
+
+ $dbr = wfGetDB( DB_SLAVE );
+
+ // Up to the value of $wgShowRollbackEditCount revisions are counted
+ $res = $dbr->select(
+ 'revision',
+ array( 'rev_user_text', 'rev_deleted' ),
+ // $rev->getPage() returns null sometimes
+ array( 'rev_page' => $rev->getTitle()->getArticleID() ),
+ __METHOD__,
+ array(
+ 'USE INDEX' => array( 'revision' => 'page_timestamp' ),
+ 'ORDER BY' => 'rev_timestamp DESC',
+ 'LIMIT' => $wgShowRollbackEditCount + 1
+ )
+ );
+
+ $editCount = 0;
+ $moreRevs = false;
+ foreach ( $res as $row ) {
+ if ( $rev->getRawUserText() != $row->rev_user_text ) {
+ if ( $verify && ( $row->rev_deleted & Revision::DELETED_TEXT || $row->rev_deleted & Revision::DELETED_USER ) ) {
+ // If the user or the text of the revision we might rollback to is deleted in some way we can't rollback
+ // Similar to the sanity checks in WikiPage::commitRollback
+ return false;
+ }
+ $moreRevs = true;
+ break;
+ }
+ $editCount++;
+ }
+
+ if ( $verify && $editCount <= $wgShowRollbackEditCount && !$moreRevs ) {
+ // We didn't find at least $wgShowRollbackEditCount revisions made by the current user
+ // and there weren't any other revisions. That means that the current user is the only
+ // editor, so we can't rollback
+ return false;
+ }
+ return $editCount;
}
/**
@@ -1719,11 +1862,12 @@ class Linker {
*
* @param $rev Revision object
* @param $context IContextSource context to use or null for the main context.
+ * @param $editCount integer Number of edits that would be reverted
* @return String: HTML fragment
*/
- public static function buildRollbackLink( $rev, IContextSource $context = null ) {
+ public static function buildRollbackLink( $rev, IContextSource $context = null, $editCount = false ) {
global $wgShowRollbackEditCount, $wgMiserMode;
-
+
// To config which pages are effected by miser mode
$disableRollbackEditCountSpecialPage = array( 'Recentchanges', 'Watchlist' );
@@ -1753,25 +1897,8 @@ class Linker {
}
if( !$disableRollbackEditCount && is_int( $wgShowRollbackEditCount ) && $wgShowRollbackEditCount > 0 ) {
- $dbr = wfGetDB( DB_SLAVE );
-
- // Up to the value of $wgShowRollbackEditCount revisions are counted
- $res = $dbr->select( 'revision',
- array( 'rev_id', 'rev_user_text' ),
- // $rev->getPage() returns null sometimes
- array( 'rev_page' => $rev->getTitle()->getArticleID() ),
- __METHOD__,
- array( 'USE INDEX' => 'page_timestamp',
- 'ORDER BY' => 'rev_timestamp DESC',
- 'LIMIT' => $wgShowRollbackEditCount + 1 )
- );
-
- $editCount = 0;
- while( $row = $dbr->fetchObject( $res ) ) {
- if( $rev->getUserText() != $row->rev_user_text ) {
- break;
- }
- $editCount++;
+ if ( !is_numeric( $editCount ) ) {
+ $editCount = self::getRollbackEditCount( $rev, false );
}
if( $editCount > $wgShowRollbackEditCount ) {
@@ -1801,13 +1928,19 @@ class Linker {
/**
* Returns HTML for the "templates used on this page" list.
*
- * @param $templates Array of templates from Article::getUsedTemplate
- * or similar
- * @param $preview Boolean: whether this is for a preview
- * @param $section Boolean: whether this is for a section edit
+ * Make an HTML list of templates, and then add a "More..." link at
+ * the bottom. If $more is null, do not add a "More..." link. If $more
+ * is a Title, make a link to that title and use it. If $more is a string,
+ * directly paste it in as the link (escaping needs to be done manually).
+ * Finally, if $more is a Message, call toString().
+ *
+ * @param array $templates Array of templates from Article::getUsedTemplate or similar
+ * @param bool $preview Whether this is for a preview
+ * @param bool $section Whether this is for a section edit
+ * @param Title|Message|string|null $more An escaped link for "More..." of the templates
* @return String: HTML output
*/
- public static function formatTemplates( $templates, $preview = false, $section = false ) {
+ public static function formatTemplates( $templates, $preview = false, $section = false, $more = null ) {
wfProfileIn( __METHOD__ );
$outText = '';
@@ -1858,18 +1991,29 @@ class Linker {
array( 'action' => 'edit' )
);
}
- $outText .= '<li>' . self::link( $titleObj ) . ' (' . $editLink . ') ' . $protected . '</li>';
+ $outText .= '<li>' . self::link( $titleObj )
+ . wfMessage( 'word-separator' )->escaped()
+ . wfMessage( 'parentheses' )->rawParams( $editLink )->escaped()
+ . wfMessage( 'word-separator' )->escaped()
+ . $protected . '</li>';
}
+
+ if ( $more instanceof Title ) {
+ $outText .= '<li>' . self::link( $more, wfMessage( 'moredotdotdot' ) ) . '</li>';
+ } elseif ( $more ) {
+ $outText .= "<li>$more</li>";
+ }
+
$outText .= '</ul>';
}
- wfProfileOut( __METHOD__ );
+ wfProfileOut( __METHOD__ );
return $outText;
}
/**
* Returns HTML for the "hidden categories on this page" list.
*
- * @param $hiddencats Array of hidden categories from Article::getHiddenCategories
+ * @param array $hiddencats of hidden categories from Article::getHiddenCategories
* or similar
* @return String: HTML output
*/
@@ -1888,7 +2032,7 @@ class Linker {
}
$outText .= '</ul>';
}
- wfProfileOut( __METHOD__ );
+ wfProfileOut( __METHOD__ );
return $outText;
}
@@ -1896,7 +2040,7 @@ class Linker {
* Format a size in bytes for output, using an appropriate
* unit (B, KB, MB or GB) according to the magnitude in question
*
- * @param $size int Size to format
+ * @param int $size Size to format
* @return String
*/
public static function formatSize( $size ) {
@@ -1910,7 +2054,7 @@ class Linker {
* isn't always, because sometimes the accesskey needs to go on a different
* element than the id, for reverse-compatibility, etc.)
*
- * @param $name String: id of the element, minus prefixes.
+ * @param string $name id of the element, minus prefixes.
* @param $options Mixed: null or the string 'withaccess' to add an access-
* key hint
* @return String: contents of the title attribute (which you must HTML-
@@ -1928,7 +2072,7 @@ class Linker {
# Compatibility: formerly some tooltips had [alt-.] hardcoded
$tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
# Message equal to '-' means suppress it.
- if ( $tooltip == '-' ) {
+ if ( $tooltip == '-' ) {
$tooltip = false;
}
}
@@ -1956,7 +2100,7 @@ class Linker {
* the id but isn't always, because sometimes the accesskey needs to go on
* a different element than the id, for reverse-compatibility, etc.)
*
- * @param $name String: id of the element, minus prefixes.
+ * @param string $name id of the element, minus prefixes.
* @return String: contents of the accesskey attribute (which you must HTML-
* escape), or false for no accesskey attribute
*/
@@ -2010,17 +2154,17 @@ class Linker {
// RevDelete links using revision ID are stable across
// page deletion and undeletion; use when possible.
$query = array(
- 'type' => 'revision',
+ 'type' => 'revision',
'target' => $title->getPrefixedDBkey(),
- 'ids' => $rev->getId()
+ 'ids' => $rev->getId()
);
} else {
// Older deleted entries didn't save a revision ID.
// We have to refer to these by timestamp, ick!
$query = array(
- 'type' => 'archive',
+ 'type' => 'archive',
'target' => $title->getPrefixedDBkey(),
- 'ids' => $rev->getTimestamp()
+ 'ids' => $rev->getTimestamp()
);
}
return Linker::revDeleteLink( $query,
@@ -2031,7 +2175,7 @@ class Linker {
/**
* Creates a (show/hide) link for deleting revisions/log entries
*
- * @param $query Array: query parameters to be passed to link()
+ * @param array $query query parameters to be passed to link()
* @param $restricted Boolean: set to true to use a "<strong>" instead of a "<span>"
* @param $delete Boolean: set to true to use (show/hide) rather than (show)
*
@@ -2070,17 +2214,17 @@ class Linker {
* This function is a shortcut to makeBrokenLinkObj(Title::newFromText($title),...). Do not call
* it if you already have a title object handy. See makeBrokenLinkObj for further documentation.
*
- * @param $title String: The text of the title
- * @param $text String: Link text
- * @param $query String: Optional query part
- * @param $trail String: Optional trail. Alphabetic characters at the start of this string will
+ * @param string $title The text of the title
+ * @param string $text Link text
+ * @param string $query Optional query part
+ * @param string $trail Optional trail. Alphabetic characters at the start of this string will
* be included in the link text. Other characters will be appended after
* the end of the link.
* @return string
*/
static function makeBrokenLink( $title, $text = '', $query = '', $trail = '' ) {
wfDeprecated( __METHOD__, '1.16' );
-
+
$nt = Title::newFromText( $title );
if ( $nt instanceof Title ) {
return self::makeBrokenLinkObj( $nt, $text, $query, $trail );
@@ -2091,7 +2235,7 @@ class Linker {
}
/**
- * @deprecated since 1.16 Use link()
+ * @deprecated since 1.16 Use link(); warnings since 1.21
*
* Make a link for a title which may or may not be in the database. If you need to
* call this lots of times, pre-fill the link cache with a LinkBatch, otherwise each
@@ -2100,16 +2244,16 @@ class Linker {
* @param $nt Title: the title object to make the link from, e.g. from
* Title::newFromText.
* @param $text String: link text
- * @param $query String: optional query part
- * @param $trail String: optional trail. Alphabetic characters at the start of this string will
+ * @param string $query optional query part
+ * @param string $trail optional trail. Alphabetic characters at the start of this string will
* be included in the link text. Other characters will be appended after
* the end of the link.
- * @param $prefix String: optional prefix. As trail, only before instead of after.
+ * @param string $prefix optional prefix. As trail, only before instead of after.
* @return string
*/
static function makeLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
- # wfDeprecated( __METHOD__, '1.16' ); // See r105985 and it's revert. Somewhere still used.
-
+ wfDeprecated( __METHOD__, '1.21' );
+
wfProfileIn( __METHOD__ );
$query = wfCgiToArray( $query );
list( $inside, $trail ) = self::splitTrail( $trail );
@@ -2124,7 +2268,7 @@ class Linker {
}
/**
- * @deprecated since 1.16 Use link()
+ * @deprecated since 1.16 Use link(); warnings since 1.21
*
* Make a link for a title which definitely exists. This is faster than makeLinkObj because
* it doesn't have to do a database query. It's also valid for interwiki titles and special
@@ -2134,16 +2278,16 @@ class Linker {
* @param $text String: text to replace the title
* @param $query String: link target
* @param $trail String: text after link
- * @param $prefix String: text before link text
- * @param $aprops String: extra attributes to the a-element
+ * @param string $prefix text before link text
+ * @param string $aprops extra attributes to the a-element
* @param $style String: style to apply - if empty, use getInternalLinkAttributesObj instead
* @return string the a-element
*/
static function makeKnownLinkObj(
- $title, $text = '', $query = '', $trail = '', $prefix = '' , $aprops = '', $style = ''
+ $title, $text = '', $query = '', $trail = '', $prefix = '', $aprops = '', $style = ''
) {
- # wfDeprecated( __METHOD__, '1.16' ); // See r105985 and it's revert. Somewhere still used.
-
+ wfDeprecated( __METHOD__, '1.21' );
+
wfProfileIn( __METHOD__ );
if ( $text == '' ) {
@@ -2170,16 +2314,16 @@ class Linker {
*
* @param $title Title object of the target page
* @param $text String: Link text
- * @param $query String: Optional query part
- * @param $trail String: Optional trail. Alphabetic characters at the start of this string will
+ * @param string $query Optional query part
+ * @param string $trail Optional trail. Alphabetic characters at the start of this string will
* be included in the link text. Other characters will be appended after
* the end of the link.
- * @param $prefix String: Optional prefix
+ * @param string $prefix Optional prefix
* @return string
*/
static function makeBrokenLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '' ) {
wfDeprecated( __METHOD__, '1.16' );
-
+
wfProfileIn( __METHOD__ );
list( $inside, $trail ) = self::splitTrail( $trail );
@@ -2206,12 +2350,12 @@ class Linker {
* @param $trail String: optional trail. Alphabetic characters at the start of this string will
* be included in the link text. Other characters will be appended after
* the end of the link.
- * @param $prefix String: Optional prefix
+ * @param string $prefix Optional prefix
* @return string
*/
static function makeColouredLinkObj( $nt, $colour, $text = '', $query = '', $trail = '', $prefix = '' ) {
wfDeprecated( __METHOD__, '1.16' );
-
+
if ( $colour != '' ) {
$style = self::getInternalLinkAttributesObj( $nt, $text, $colour );
} else {
@@ -2268,8 +2412,8 @@ class DummyLinker {
* Use PHP's magic __call handler to transform instance calls to a dummy instance
* into static calls to the new Linker for backwards compatibility.
*
- * @param $fname String Name of called method
- * @param $args Array Arguments to the method
+ * @param string $fname Name of called method
+ * @param array $args Arguments to the method
* @return mixed
*/
public function __call( $fname, $args ) {
diff --git a/includes/LinksUpdate.php b/includes/LinksUpdate.php
index 87db4d60..d99ae22d 100644
--- a/includes/LinksUpdate.php
+++ b/includes/LinksUpdate.php
@@ -49,6 +49,7 @@ class LinksUpdate extends SqlDataUpdate {
* @param $title Title of the page we're updating
* @param $parserOutput ParserOutput: output from a full parse of this page
* @param $recursive Boolean: queue jobs for recursive updates?
+ * @throws MWException
*/
function __construct( $title, $parserOutput, $recursive = true ) {
parent::__construct( false ); // no implicit transaction
@@ -71,6 +72,7 @@ class LinksUpdate extends SqlDataUpdate {
}
$this->mParserOutput = $parserOutput;
+
$this->mLinks = $parserOutput->getLinks();
$this->mImages = $parserOutput->getImages();
$this->mTemplates = $parserOutput->getTemplates();
@@ -211,14 +213,14 @@ class LinksUpdate extends SqlDataUpdate {
$existing = $this->getExistingImages();
$imageUpdates = array_diff_key( $existing, $this->mImages ) + array_diff_key( $this->mImages, $existing );
- $this->dumbTableUpdate( 'pagelinks', $this->getLinkInsertions(), 'pl_from' );
- $this->dumbTableUpdate( 'imagelinks', $this->getImageInsertions(), 'il_from' );
+ $this->dumbTableUpdate( 'pagelinks', $this->getLinkInsertions(), 'pl_from' );
+ $this->dumbTableUpdate( 'imagelinks', $this->getImageInsertions(), 'il_from' );
$this->dumbTableUpdate( 'categorylinks', $this->getCategoryInsertions(), 'cl_from' );
$this->dumbTableUpdate( 'templatelinks', $this->getTemplateInsertions(), 'tl_from' );
$this->dumbTableUpdate( 'externallinks', $this->getExternalInsertions(), 'el_from' );
- $this->dumbTableUpdate( 'langlinks', $this->getInterlangInsertions(),'ll_from' );
- $this->dumbTableUpdate( 'iwlinks', $this->getInterwikiInsertions(),'iwl_from' );
- $this->dumbTableUpdate( 'page_props', $this->getPropertyInsertions(), 'pp_page' );
+ $this->dumbTableUpdate( 'langlinks', $this->getInterlangInsertions(), 'll_from' );
+ $this->dumbTableUpdate( 'iwlinks', $this->getInterwikiInsertions(), 'iwl_from' );
+ $this->dumbTableUpdate( 'page_props', $this->getPropertyInsertions(), 'pp_page' );
# Update the cache of all the category pages and image description
# pages which were changed, and fix the category table count
@@ -236,26 +238,20 @@ class LinksUpdate extends SqlDataUpdate {
}
function queueRecursiveJobs() {
- global $wgUpdateRowsPerJob;
wfProfileIn( __METHOD__ );
- $cache = $this->mTitle->getBacklinkCache();
- $batches = $cache->partition( 'templatelinks', $wgUpdateRowsPerJob );
- if ( !$batches ) {
- wfProfileOut( __METHOD__ );
- return;
- }
- $jobs = array();
- foreach ( $batches as $batch ) {
- list( $start, $end ) = $batch;
- $params = array(
- 'table' => 'templatelinks',
- 'start' => $start,
- 'end' => $end,
+ if ( $this->mTitle->getBacklinkCache()->hasLinks( 'templatelinks' ) ) {
+ $job = new RefreshLinksJob2(
+ $this->mTitle,
+ array(
+ 'table' => 'templatelinks',
+ ) + Job::newRootJobParams( // "overall" refresh links job info
+ "refreshlinks:templatelinks:{$this->mTitle->getPrefixedText()}"
+ )
);
- $jobs[] = new RefreshLinksJob2( $this->mTitle, $params );
+ JobQueueGroup::singleton()->push( $job );
+ JobQueueGroup::singleton()->deduplicateRootJob( $job );
}
- Job::batchInsert( $jobs );
wfProfileOut( __METHOD__ );
}
@@ -269,8 +265,8 @@ class LinksUpdate extends SqlDataUpdate {
/**
* Update all the appropriate counts in the category table.
- * @param $added array associative array of category name => sort key
- * @param $deleted array associative array of category name => sort key
+ * @param array $added associative array of category name => sort key
+ * @param array $deleted associative array of category name => sort key
*/
function updateCategoryCounts( $added, $deleted ) {
$a = WikiPage::factory( $this->mTitle );
@@ -295,7 +291,7 @@ class LinksUpdate extends SqlDataUpdate {
$this->mDb->delete( $table, array( $fromField => $this->mId ), __METHOD__ );
if ( count( $insertions ) ) {
# The link array was constructed without FOR UPDATE, so there may
- # be collisions. This may cause minor link table inconsistencies,
+ # be collisions. This may cause minor link table inconsistencies,
# which is better than crippling the site with lock contention.
$this->mDb->insert( $table, $insertions, __METHOD__, array( 'IGNORE' ) );
}
@@ -346,6 +342,7 @@ class LinksUpdate extends SqlDataUpdate {
}
if ( count( $insertions ) ) {
$this->mDb->insert( $table, $insertions, __METHOD__, 'IGNORE' );
+ wfRunHooks( 'LinksUpdateAfterInsert', array( $this, $table, $insertions ) );
}
}
@@ -363,9 +360,9 @@ class LinksUpdate extends SqlDataUpdate {
: $dbkeys;
foreach ( $diffs as $dbk => $id ) {
$arr[] = array(
- 'pl_from' => $this->mId,
+ 'pl_from' => $this->mId,
'pl_namespace' => $ns,
- 'pl_title' => $dbk
+ 'pl_title' => $dbk
);
}
}
@@ -383,9 +380,9 @@ class LinksUpdate extends SqlDataUpdate {
$diffs = isset( $existing[$ns] ) ? array_diff_key( $dbkeys, $existing[$ns] ) : $dbkeys;
foreach ( $diffs as $dbk => $id ) {
$arr[] = array(
- 'tl_from' => $this->mId,
+ 'tl_from' => $this->mId,
'tl_namespace' => $ns,
- 'tl_title' => $dbk
+ 'tl_title' => $dbk
);
}
}
@@ -404,7 +401,7 @@ class LinksUpdate extends SqlDataUpdate {
foreach( $diffs as $iname => $dummy ) {
$arr[] = array(
'il_from' => $this->mId,
- 'il_to' => $iname
+ 'il_to' => $iname
);
}
return $arr;
@@ -421,9 +418,9 @@ class LinksUpdate extends SqlDataUpdate {
foreach( $diffs as $url => $dummy ) {
foreach( wfMakeUrlIndexes( $url ) as $index ) {
$arr[] = array(
- 'el_from' => $this->mId,
- 'el_to' => $url,
- 'el_index' => $index,
+ 'el_from' => $this->mId,
+ 'el_to' => $url,
+ 'el_index' => $index,
);
}
}
@@ -433,7 +430,7 @@ class LinksUpdate extends SqlDataUpdate {
/**
* Get an array of category insertions
*
- * @param $existing array mapping existing category names to sort keys. If both
+ * @param array $existing mapping existing category names to sort keys. If both
* match a link in $this, the link will be omitted from the output
*
* @return array
@@ -462,8 +459,8 @@ class LinksUpdate extends SqlDataUpdate {
$this->mTitle->getCategorySortkey( $prefix ) );
$arr[] = array(
- 'cl_from' => $this->mId,
- 'cl_to' => $name,
+ 'cl_from' => $this->mId,
+ 'cl_to' => $name,
'cl_sortkey' => $sortkey,
'cl_timestamp' => $this->mDb->timestamp(),
'cl_sortkey_prefix' => $prefix,
@@ -477,7 +474,7 @@ class LinksUpdate extends SqlDataUpdate {
/**
* Get an array of interlanguage link insertions
*
- * @param $existing Array mapping existing language codes to titles
+ * @param array $existing mapping existing language codes to titles
*
* @return array
*/
@@ -486,8 +483,8 @@ class LinksUpdate extends SqlDataUpdate {
$arr = array();
foreach( $diffs as $lang => $title ) {
$arr[] = array(
- 'll_from' => $this->mId,
- 'll_lang' => $lang,
+ 'll_from' => $this->mId,
+ 'll_lang' => $lang,
'll_title' => $title
);
}
@@ -504,9 +501,9 @@ class LinksUpdate extends SqlDataUpdate {
$arr = array();
foreach ( $diffs as $name => $value ) {
$arr[] = array(
- 'pp_page' => $this->mId,
- 'pp_propname' => $name,
- 'pp_value' => $value,
+ 'pp_page' => $this->mId,
+ 'pp_propname' => $name,
+ 'pp_value' => $value,
);
}
return $arr;
@@ -524,9 +521,9 @@ class LinksUpdate extends SqlDataUpdate {
$diffs = isset( $existing[$prefix] ) ? array_diff_key( $dbkeys, $existing[$prefix] ) : $dbkeys;
foreach ( $diffs as $dbk => $id ) {
$arr[] = array(
- 'iwl_from' => $this->mId,
+ 'iwl_from' => $this->mId,
'iwl_prefix' => $prefix,
- 'iwl_title' => $dbk
+ 'iwl_title' => $dbk
);
}
}
@@ -823,11 +820,16 @@ class LinksDeletionUpdate extends SqlDataUpdate {
* Constructor
*
* @param $page WikiPage Page we are updating
+ * @throws MWException
*/
function __construct( WikiPage $page ) {
parent::__construct( false ); // no implicit transaction
$this->mPage = $page;
+
+ if ( !$page->exists() ) {
+ throw new MWException( "Page ID not known, perhaps the page doesn't exist?" );
+ }
}
/**
@@ -879,4 +881,16 @@ class LinksDeletionUpdate extends SqlDataUpdate {
__METHOD__ );
}
}
+
+ /**
+ * Update all the appropriate counts in the category table.
+ * @param array $added associative array of category name => sort key
+ * @param array $deleted associative array of category name => sort key
+ */
+ function updateCategoryCounts( $added, $deleted ) {
+ $a = WikiPage::factory( $this->mTitle );
+ $a->updateCategoryCounts(
+ array_keys( $added ), array_keys( $deleted )
+ );
+ }
}
diff --git a/includes/MagicWord.php b/includes/MagicWord.php
index 42791f57..7b669249 100644
--- a/includes/MagicWord.php
+++ b/includes/MagicWord.php
@@ -217,7 +217,7 @@ class MagicWord {
/**#@-*/
- function __construct($id = 0, $syn = array(), $cs = false) {
+ function __construct( $id = 0, $syn = array(), $cs = false ) {
$this->mId = $id;
$this->mSynonyms = (array)$syn;
$this->mCaseSensitive = $cs;
@@ -282,6 +282,7 @@ class MagicWord {
*/
static function getDoubleUnderscoreArray() {
if ( is_null( self::$mDoubleUnderscoreArray ) ) {
+ wfRunHooks( 'GetDoubleUnderscoreIDs', array( &self::$mDoubleUnderscoreIDs ) );
self::$mDoubleUnderscoreArray = new MagicWordArray( self::$mDoubleUnderscoreIDs );
}
return self::$mDoubleUnderscoreArray;
@@ -366,7 +367,7 @@ class MagicWord {
* @return string
*/
function getRegex() {
- if ($this->mRegex == '' ) {
+ if ( $this->mRegex == '' ) {
$this->initRegex();
}
return $this->mRegex;
@@ -392,7 +393,7 @@ class MagicWord {
* @return string
*/
function getRegexStart() {
- if ($this->mRegex == '' ) {
+ if ( $this->mRegex == '' ) {
$this->initRegex();
}
return $this->mRegexStart;
@@ -404,7 +405,7 @@ class MagicWord {
* @return string
*/
function getBaseRegex() {
- if ($this->mRegex == '') {
+ if ( $this->mRegex == '' ) {
$this->initRegex();
}
return $this->mBaseRegex;
@@ -453,9 +454,9 @@ class MagicWord {
# blank elements and re-sort the indices.
# See also bug 6526
- $matches = array_values(array_filter($matches));
+ $matches = array_values( array_filter( $matches ) );
- if ( count($matches) == 1 ) {
+ if ( count( $matches ) == 1 ) {
return $matches[0];
} else {
return $matches[1];
@@ -463,7 +464,6 @@ class MagicWord {
}
}
-
/**
* Returns true if the text matches the word, and alters the
* input string, removing all instances of the word
@@ -534,7 +534,7 @@ class MagicWord {
*
* @return string
*/
- function getVariableRegex() {
+ function getVariableRegex() {
if ( $this->mVariableRegex == '' ) {
$this->initRegex();
}
@@ -577,7 +577,7 @@ class MagicWord {
*
* @return bool
*/
- function getWasModified(){
+ function getWasModified() {
return $this->mModified;
}
@@ -594,10 +594,10 @@ class MagicWord {
*
* @return bool
*/
- function replaceMultiple( $magicarr, $subject, &$result ){
+ function replaceMultiple( $magicarr, $subject, &$result ) {
$search = array();
$replace = array();
- foreach( $magicarr as $id => $replacement ){
+ foreach( $magicarr as $id => $replacement ) {
$mw = MagicWord::get( $id );
$search[] = $mw->getRegex();
$replace[] = $replacement;
@@ -617,7 +617,7 @@ class MagicWord {
function addToArray( &$array, $value ) {
global $wgContLang;
foreach ( $this->mSynonyms as $syn ) {
- $array[$wgContLang->lc($syn)] = $value;
+ $array[$wgContLang->lc( $syn )] = $value;
}
}
@@ -811,7 +811,7 @@ class MagicWordArray {
return array( $magicName, $paramValue );
}
// This shouldn't happen either
- throw new MWException( __METHOD__.': parameter not found' );
+ throw new MWException( __METHOD__ . ': parameter not found' );
}
/**
diff --git a/includes/MappedIterator.php b/includes/MappedIterator.php
new file mode 100644
index 00000000..b4376f44
--- /dev/null
+++ b/includes/MappedIterator.php
@@ -0,0 +1,96 @@
+<?php
+/**
+ * Convenience class for generating iterators from iterators.
+ *
+ * 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
+ * @author Aaron Schulz
+ */
+
+/**
+ * Convenience class for generating iterators from iterators.
+ *
+ * @since 1.21
+ */
+class MappedIterator implements Iterator {
+ /** @var Iterator */
+ protected $baseIterator;
+ /** @var Closure */
+ protected $vCallback;
+
+ /**
+ * Build an new iterator from a base iterator by having the former wrap the
+ * later, returning the result of "value" callback for each current() invocation.
+ * The callback takes the result of current() on the base iterator as an argument.
+ * The keys of the base iterator are reused verbatim.
+ *
+ * @param Iterator|Array $iter
+ * @param Closure $vCallback
+ * @throws MWException
+ */
+ public function __construct( $iter, Closure $vCallback ) {
+ if ( is_array( $iter ) ) {
+ $this->baseIterator = new ArrayIterator( $iter );
+ } elseif ( $iter instanceof Iterator ) {
+ $this->baseIterator = $iter;
+ } else {
+ throw new MWException( "Invalid base iterator provided." );
+ }
+ $this->vCallback = $vCallback;
+ }
+
+ /**
+ * @return void
+ */
+ public function rewind() {
+ $this->baseIterator->rewind();
+ }
+
+ /**
+ * @return Mixed|null Returns null if out of range
+ */
+ public function current() {
+ if ( !$this->baseIterator->valid() ) {
+ return null; // out of range
+ }
+ return call_user_func_array( $this->vCallback, array( $this->baseIterator->current() ) );
+ }
+
+ /**
+ * @return Mixed|null Returns null if out of range
+ */
+ public function key() {
+ if ( !$this->baseIterator->valid() ) {
+ return null; // out of range
+ }
+ return $this->baseIterator->key();
+ }
+
+ /**
+ * @return void
+ */
+ public function next() {
+ $this->baseIterator->next();
+ }
+
+ /**
+ * @return bool
+ */
+ public function valid() {
+ return $this->baseIterator->valid();
+ }
+}
diff --git a/includes/Message.php b/includes/Message.php
index b0147b9a..5719f830 100644
--- a/includes/Message.php
+++ b/includes/Message.php
@@ -22,11 +22,11 @@
*/
/**
- * The Message class provides methods which fullfil two basic services:
+ * The Message class provides methods which fulfil two basic services:
* - fetching interface messages
* - processing messages into a variety of formats
*
- * First implemented with MediaWiki 1.17, the Message class is intented to
+ * First implemented with MediaWiki 1.17, the Message class is intended to
* replace the old wfMsg* functions that over time grew unusable.
* @see https://www.mediawiki.org/wiki/Manual:Messages_API for equivalences
* between old and new functions.
@@ -203,6 +203,11 @@ class Message {
protected $title = null;
/**
+ * Content object representing the message
+ */
+ protected $content = null;
+
+ /**
* @var string
*/
protected $message;
@@ -211,7 +216,7 @@ class Message {
* Constructor.
* @since 1.17
* @param $key: message key, or array of message keys to try and use the first non-empty message for
- * @param $params Array message parameters
+ * @param array $params message parameters
* @return Message: $this
*/
public function __construct( $key, $params = array() ) {
@@ -222,11 +227,44 @@ class Message {
}
/**
+ * Returns the message key
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getKey() {
+ return $this->key;
+ }
+
+ /**
+ * Returns the message parameters
+ *
+ * @since 1.21
+ *
+ * @return string[]
+ */
+ public function getParams() {
+ return $this->parameters;
+ }
+
+ /**
+ * Returns the message format
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getFormat() {
+ return $this->format;
+ }
+
+ /**
* Factory function that is just wrapper for the real constructor. It is
- * intented to be used instead of the real constructor, because it allows
+ * intended to be used instead of the real constructor, because it allows
* chaining method calls, while new objects don't.
* @since 1.17
- * @param $key String: message key
+ * @param string $key message key
* @param Varargs: parameters as Strings
* @return Message: $this
*/
@@ -247,9 +285,9 @@ class Message {
public static function newFallbackSequence( /*...*/ ) {
$keys = func_get_args();
if ( func_num_args() == 1 ) {
- if ( is_array($keys[0]) ) {
+ if ( is_array( $keys[0] ) ) {
// Allow an array to be passed as the first argument instead
- $keys = array_values($keys[0]);
+ $keys = array_values( $keys[0] );
} else {
// Optimize a single string to not need special fallback handling
$keys = $keys[0];
@@ -332,6 +370,7 @@ class Message {
* turned off.
* @since 1.17
* @param $lang Mixed: language code or Language object.
+ * @throws MWException
* @return Message: $this
*/
public function inLanguage( $lang ) {
@@ -405,6 +444,18 @@ class Message {
}
/**
+ * Returns the message as a Content object.
+ * @return Content
+ */
+ public function content() {
+ if ( !$this->content ) {
+ $this->content = new MessageContent( $this );
+ }
+
+ return $this->content;
+ }
+
+ /**
* Returns the message parsed from wikitext to HTML.
* @since 1.17
* @return String: HTML
@@ -413,13 +464,22 @@ class Message {
$string = $this->fetchMessage();
if ( $string === false ) {
- $key = htmlspecialchars( is_array( $this->key ) ? $this->key[0] : $this->key );
+ $key = htmlspecialchars( is_array( $this->key ) ? $this->key[0] : $this->key );
if ( $this->format === 'plain' ) {
return '<' . $key . '>';
}
return '&lt;' . $key . '&gt;';
}
+ # Replace $* with a list of parameters for &uselang=qqx.
+ if ( strpos( $string, '$*' ) !== false ) {
+ $paramlist = '';
+ if ( $this->parameters !== array() ) {
+ $paramlist = ': $' . implode( ', $', range( 1, count( $this->parameters ) ) );
+ }
+ $string = str_replace( '$*', $paramlist, $string );
+ }
+
# Replace parameters before text parsing
$string = $this->replaceParameters( $string, 'before' );
@@ -430,11 +490,11 @@ class Message {
if( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $string, $m ) ) {
$string = $m[1];
}
- } elseif( $this->format === 'block-parse' ){
+ } elseif( $this->format === 'block-parse' ) {
$string = $this->parseText( $string );
- } elseif( $this->format === 'text' ){
+ } elseif( $this->format === 'text' ) {
$string = $this->transformText( $string );
- } elseif( $this->format === 'escaped' ){
+ } elseif( $this->format === 'escaped' ) {
$string = $this->transformText( $string );
$string = htmlspecialchars( $string, ENT_QUOTES, 'UTF-8', false );
}
@@ -447,13 +507,30 @@ class Message {
/**
* Magic method implementation of the above (for PHP >= 5.2.0), so we can do, eg:
- * $foo = Message::get($key);
+ * $foo = Message::get( $key );
* $string = "<abbr>$foo</abbr>";
* @since 1.18
* @return String
*/
public function __toString() {
- return $this->toString();
+ // PHP doesn't allow __toString to throw exceptions and will
+ // trigger a fatal error if it does. So, catch any exceptions.
+
+ try {
+ return $this->toString();
+ } catch ( Exception $ex ) {
+ try {
+ trigger_error( "Exception caught in " . __METHOD__ . " (message " . $this->key . "): "
+ . $ex, E_USER_WARNING );
+ } catch ( Exception $ex ) {
+ // Doh! Cause a fatal error after all?
+ }
+
+ if ( $this->format === 'plain' ) {
+ return '<' . $this->key . '>';
+ }
+ return '&lt;' . $this->key . '&gt;';
+ }
}
/**
@@ -477,7 +554,7 @@ class Message {
}
/**
- * Returns the message text as-is, only parameters are subsituted.
+ * Returns the message text as-is, only parameters are substituted.
* @since 1.17
* @return String: Unescaped untransformed message text.
*/
@@ -530,7 +607,7 @@ class Message {
/**
* Check whether a message does not exist, is an empty string, or is "-"
* @since 1.18
- * @return Bool: true if is is and false if not
+ * @return Bool: true if it is and false if not
*/
public function isDisabled() {
$message = $this->fetchMessage();
@@ -556,10 +633,10 @@ class Message {
}
/**
- * Substitutes any paramaters into the message text.
+ * Substitutes any parameters into the message text.
* @since 1.17
- * @param $message String: the message text
- * @param $type String: either before or after
+ * @param string $message the message text
+ * @param string $type either before or after
* @return String
*/
protected function replaceParameters( $message, $type = 'before' ) {
@@ -577,7 +654,7 @@ class Message {
/**
* Extracts the parameter type and preprocessed the value if needed.
* @since 1.18
- * @param $param String|Array: Parameter as defined in this class.
+ * @param string|array $param Parameter as defined in this class.
* @return Tuple(type, value)
*/
protected function extractParam( $param ) {
@@ -601,17 +678,18 @@ class Message {
/**
* Wrapper for what ever method we use to parse wikitext.
* @since 1.17
- * @param $string String: Wikitext message contents
+ * @param string $string Wikitext message contents
* @return string Wikitext parsed into HTML
*/
protected function parseText( $string ) {
- return MessageCache::singleton()->parse( $string, $this->title, /*linestart*/true, $this->interface, $this->language )->getText();
+ $out = MessageCache::singleton()->parse( $string, $this->title, /*linestart*/true, $this->interface, $this->language );
+ return is_object( $out ) ? $out->getText() : $out;
}
/**
* Wrapper for what ever method we use to {{-transform wikitext.
* @since 1.17
- * @param $string String: Wikitext message contents
+ * @param string $string Wikitext message contents
* @return string Wikitext with {{-constructs replaced with their values.
*/
protected function transformText( $string ) {
@@ -621,6 +699,7 @@ class Message {
/**
* Wrapper for what ever method we use to get message contents
* @since 1.17
+ * @throws MWException
* @return string
*/
protected function fetchMessage() {
@@ -645,3 +724,45 @@ class Message {
}
}
+
+/**
+ * Variant of the Message class.
+ *
+ * Rather than treating the message key as a lookup
+ * value (which is passed to the MessageCache and
+ * translated as necessary), a RawMessage key is
+ * treated as the actual message.
+ *
+ * All other functionality (parsing, escaping, etc.)
+ * is preserved.
+ *
+ * @since 1.21
+ */
+class RawMessage extends Message {
+ /**
+ * Call the parent constructor, then store the key as
+ * the message.
+ *
+ * @param string $key Message to use
+ * @param array $params Parameters for the message
+ * @see Message::__construct
+ */
+ public function __construct( $key, $params = array() ) {
+ parent::__construct( $key, $params );
+ // The key is the message.
+ $this->message = $key;
+ }
+
+ /**
+ * Fetch the message (in this case, the key).
+ *
+ * @return string
+ */
+ public function fetchMessage() {
+ // Just in case the message is unset somewhere.
+ if( !isset( $this->message ) ) {
+ $this->message = $this->key;
+ }
+ return $this->message;
+ }
+}
diff --git a/includes/MessageBlobStore.php b/includes/MessageBlobStore.php
index c96ea56e..8a8142b7 100644
--- a/includes/MessageBlobStore.php
+++ b/includes/MessageBlobStore.php
@@ -29,7 +29,7 @@
* A message blob is a JSON object containing the interface messages for a
* certain resource in a certain language. These message blobs are cached
* in the msg_resource table and automatically invalidated when one of their
- * consistuent messages or the resource itself is changed.
+ * constituent messages or the resource itself is changed.
*/
class MessageBlobStore {
@@ -37,8 +37,8 @@ class MessageBlobStore {
* Get the message blobs for a set of modules
*
* @param $resourceLoader ResourceLoader object
- * @param $modules array Array of module objects keyed by module name
- * @param $lang string Language code
+ * @param array $modules Array of module objects keyed by module name
+ * @param string $lang Language code
* @return array An array mapping module names to message blobs
*/
public static function get( ResourceLoader $resourceLoader, $modules, $lang ) {
@@ -68,9 +68,9 @@ class MessageBlobStore {
* present, it is not regenerated; instead, the preexisting blob
* is fetched and returned.
*
- * @param $name String: module name
+ * @param string $name module name
* @param $module ResourceLoaderModule object
- * @param $lang String: language code
+ * @param string $lang language code
* @return mixed Message blob or false if the module has no messages
*/
public static function insertMessageBlob( $name, ResourceLoaderModule $module, $lang ) {
@@ -125,9 +125,9 @@ class MessageBlobStore {
/**
* Update the message blob for a given module in a given language
*
- * @param $name String: module name
+ * @param string $name module name
* @param $module ResourceLoaderModule object
- * @param $lang String: language code
+ * @param string $lang language code
* @return String Regenerated message blob, or null if there was no blob for the given module/language pair
*/
public static function updateModule( $name, ResourceLoaderModule $module, $lang ) {
@@ -195,7 +195,7 @@ class MessageBlobStore {
/**
* Update a single message in all message blobs it occurs in.
*
- * @param $key String: message key
+ * @param string $key message key
*/
public static function updateMessage( $key ) {
try {
@@ -255,8 +255,8 @@ class MessageBlobStore {
/**
* Create an update queue for updateMessage()
*
- * @param $key String: message key
- * @param $prevUpdates Array: updates queue to refresh or null to build a fresh update queue
+ * @param string $key message key
+ * @param array $prevUpdates updates queue to refresh or null to build a fresh update queue
* @return Array: updates queue
*/
private static function getUpdatesForMessage( $key, $prevUpdates = null ) {
@@ -306,9 +306,9 @@ class MessageBlobStore {
/**
* Reencode a message blob with the updated value for a message
*
- * @param $blob String: message blob (JSON object)
- * @param $key String: message key
- * @param $lang String: language code
+ * @param string $blob message blob (JSON object)
+ * @param string $key message key
+ * @param string $lang language code
* @return Message blob with $key replaced with its new value
*/
private static function reencodeBlob( $blob, $key, $lang ) {
@@ -323,8 +323,9 @@ class MessageBlobStore {
* Modules whose blobs are not in the database are silently dropped.
*
* @param $resourceLoader ResourceLoader object
- * @param $modules Array of module names
- * @param $lang String: language code
+ * @param array $modules of module names
+ * @param string $lang language code
+ * @throws MWException
* @return array Array mapping module names to blobs
*/
private static function getFromDB( ResourceLoader $resourceLoader, $modules, $lang ) {
@@ -360,7 +361,7 @@ class MessageBlobStore {
* Generate the message blob for a given module in a given language.
*
* @param $module ResourceLoaderModule object
- * @param $lang String: language code
+ * @param string $lang language code
* @return String: JSON object
*/
private static function generateMessageBlob( ResourceLoaderModule $module, $lang ) {
diff --git a/includes/Metadata.php b/includes/Metadata.php
index 0ca15393..0b8014a7 100644
--- a/includes/Metadata.php
+++ b/includes/Metadata.php
@@ -34,7 +34,7 @@ abstract class RdfMetaData {
$this->mArticle = $article;
}
- public abstract function show();
+ abstract public function show();
protected function setup() {
global $wgOut, $wgRequest;
@@ -42,7 +42,7 @@ abstract class RdfMetaData {
$httpaccept = isset( $_SERVER['HTTP_ACCEPT'] ) ? $_SERVER['HTTP_ACCEPT'] : null;
$rdftype = wfNegotiateType( wfAcceptToPrefs( $httpaccept ), wfAcceptToPrefs( self::RDF_TYPE_PREFS ) );
- if( !$rdftype ){
+ if( !$rdftype ) {
throw new HttpError( 406, wfMessage( 'notacceptable' ) );
}
@@ -70,7 +70,7 @@ abstract class RdfMetaData {
$lastEditor = User::newFromId( $this->mArticle->getUser() );
$this->person( 'creator', $lastEditor );
- foreach( $this->mArticle->getContributors() as $user ){
+ foreach( $this->mArticle->getContributors() as $user ) {
$this->person( 'contributor', $user );
}
@@ -82,10 +82,10 @@ abstract class RdfMetaData {
print "\t\t<dc:{$name}>{$value}</dc:{$name}>\n";
}
- protected function date($timestamp) {
- return substr($timestamp, 0, 4) . '-'
- . substr($timestamp, 4, 2) . '-'
- . substr($timestamp, 6, 2);
+ protected function date( $timestamp ) {
+ return substr( $timestamp, 0, 4 ) . '-'
+ . substr( $timestamp, 4, 2 ) . '-'
+ . substr( $timestamp, 6, 2 );
}
protected function pageOrString( $name, $page, $str ) {
@@ -95,7 +95,7 @@ abstract class RdfMetaData {
$nt = Title::newFromText( $page );
}
- if( !$nt || $nt->getArticleID() == 0 ){
+ if( !$nt || $nt->getArticleID() == 0 ) {
$this->element( $name, $str );
} else {
$this->page( $name, $nt );
@@ -110,13 +110,13 @@ abstract class RdfMetaData {
$this->url( $name, $title->getFullUrl() );
}
- protected function url($name, $url) {
+ protected function url( $name, $url ) {
$url = htmlspecialchars( $url );
print "\t\t<dc:{$name} rdf:resource=\"{$url}\" />\n";
}
protected function person( $name, User $user ) {
- if( $user->isAnon() ){
+ if( $user->isAnon() ) {
$this->element( $name, wfMessage( 'anonymous' )->numParams( 1 )->text() );
} else {
$real = $user->getRealName();
@@ -141,19 +141,19 @@ abstract class RdfMetaData {
global $wgRightsPage, $wgRightsUrl, $wgRightsText;
if( $wgRightsPage && ( $nt = Title::newFromText( $wgRightsPage ) )
- && ($nt->getArticleID() != 0)) {
- $this->page('rights', $nt);
- } elseif( $wgRightsUrl ){
- $this->url('rights', $wgRightsUrl);
- } elseif( $wgRightsText ){
+ && ( $nt->getArticleID() != 0 ) ) {
+ $this->page( 'rights', $nt );
+ } elseif( $wgRightsUrl ) {
+ $this->url( 'rights', $wgRightsUrl );
+ } elseif( $wgRightsText ) {
$this->element( 'rights', $wgRightsText );
}
}
- protected function getTerms( $url ){
+ protected function getTerms( $url ) {
global $wgLicenseTerms;
- if( $wgLicenseTerms ){
+ if( $wgLicenseTerms ) {
return $wgLicenseTerms;
} else {
$known = $this->getKnownLicenses();
@@ -166,23 +166,23 @@ abstract class RdfMetaData {
}
protected function getKnownLicenses() {
- $ccLicenses = array('by', 'by-nd', 'by-nd-nc', 'by-nc',
- 'by-nc-sa', 'by-sa');
- $ccVersions = array('1.0', '2.0');
+ $ccLicenses = array( 'by', 'by-nd', 'by-nd-nc', 'by-nc',
+ 'by-nc-sa', 'by-sa' );
+ $ccVersions = array( '1.0', '2.0' );
$knownLicenses = array();
- foreach ($ccVersions as $version) {
- foreach ($ccLicenses as $license) {
- if( $version == '2.0' && substr( $license, 0, 2) != 'by' ) {
+ foreach ( $ccVersions as $version ) {
+ foreach ( $ccLicenses as $license ) {
+ if( $version == '2.0' && substr( $license, 0, 2 ) != 'by' ) {
# 2.0 dropped the non-attribs licenses
continue;
}
$lurl = "http://creativecommons.org/licenses/{$license}/{$version}/";
- $knownLicenses[$lurl] = explode('-', $license);
+ $knownLicenses[$lurl] = explode( '-', $license );
$knownLicenses[$lurl][] = 're';
$knownLicenses[$lurl][] = 'di';
$knownLicenses[$lurl][] = 'no';
- if (!in_array('nd', $knownLicenses[$lurl])) {
+ if ( !in_array( 'nd', $knownLicenses[$lurl] ) ) {
$knownLicenses[$lurl][] = 'de';
}
}
@@ -191,13 +191,12 @@ abstract class RdfMetaData {
/* Handle the GPL and LGPL, too. */
$knownLicenses['http://creativecommons.org/licenses/GPL/2.0/'] =
- array('de', 're', 'di', 'no', 'sa', 'sc');
+ array( 'de', 're', 'di', 'no', 'sa', 'sc' );
$knownLicenses['http://creativecommons.org/licenses/LGPL/2.1/'] =
- array('de', 're', 'di', 'no', 'sa', 'sc');
+ array( 'de', 're', 'di', 'no', 'sa', 'sc' );
$knownLicenses['http://www.gnu.org/copyleft/fdl.html'] =
- array('de', 're', 'di', 'no', 'sa', 'sc');
+ array( 'de', 're', 'di', 'no', 'sa', 'sc' );
return $knownLicenses;
}
}
-
diff --git a/includes/MimeMagic.php b/includes/MimeMagic.php
index 1873e7bf..edabd54c 100644
--- a/includes/MimeMagic.php
+++ b/includes/MimeMagic.php
@@ -25,21 +25,21 @@
* This is used as a fallback to mime.types files.
* An extensive list of well known mime types is provided by
* the file mime.types in the includes directory.
- *
+ *
* This list concatenated with mime.types is used to create a mime <-> ext
* map. Each line contains a mime type followed by a space separated list of
- * extensions. If multiple extensions for a single mime type exist or if
+ * extensions. If multiple extensions for a single mime type exist or if
* multiple mime types exist for a single extension then in most cases
* MediaWiki assumes that the first extension following the mime type is the
* canonical extension, and the first time a mime type appears for a certain
* extension is considered the canonical mime type.
- *
+ *
* (Note that appending $wgMimeTypeFile to the end of MM_WELL_KNOWN_MIME_TYPES
- * sucks because you can't redefine canonical types. This could be fixed by
+ * sucks because you can't redefine canonical types. This could be fixed by
* appending MM_WELL_KNOWN_MIME_TYPES behind $wgMimeTypeFile, but who knows
* what will break? In practice this probably isn't a problem anyway -- Bryan)
*/
-define('MM_WELL_KNOWN_MIME_TYPES',<<<END_STRING
+define('MM_WELL_KNOWN_MIME_TYPES', <<<END_STRING
application/ogg ogx ogg ogm ogv oga spx
application/pdf pdf
application/vnd.oasis.opendocument.chart odc
@@ -70,7 +70,7 @@ image/x-bmp bmp
image/gif gif
image/jpeg jpeg jpg jpe
image/png png
-image/svg+xml svg
+image/svg+xml svg
image/svg svg
image/tiff tiff tif
image/vnd.djvu djvu
@@ -144,21 +144,21 @@ END_STRING
class MimeMagic {
/**
- * Mapping of media types to arrays of mime types.
- * This is used by findMediaType and getMediaType, respectively
- */
+ * Mapping of media types to arrays of mime types.
+ * This is used by findMediaType and getMediaType, respectively
+ */
var $mMediaTypes = null;
/** Map of mime type aliases
- */
+ */
var $mMimeTypeAliases = null;
- /** map of mime types to file extensions (as a space seprarated list)
- */
+ /** map of mime types to file extensions (as a space separated list)
+ */
var $mMimeToExt = null;
- /** map of file extensions types to mime types (as a space seprarated list)
- */
+ /** map of file extensions types to mime types (as a space separated list)
+ */
var $mExtToMime = null;
/** IEContentAnalyzer instance
@@ -179,8 +179,8 @@ class MimeMagic {
*/
function __construct() {
/**
- * --- load mime.types ---
- */
+ * --- load mime.types ---
+ */
global $wgMimeTypeFile, $IP, $wgLoadFileinfoExtension;
@@ -197,14 +197,14 @@ class MimeMagic {
if ( $wgMimeTypeFile ) {
if ( is_file( $wgMimeTypeFile ) and is_readable( $wgMimeTypeFile ) ) {
- wfDebug( __METHOD__.": loading mime types from $wgMimeTypeFile\n" );
+ wfDebug( __METHOD__ . ": loading mime types from $wgMimeTypeFile\n" );
$types .= "\n";
$types .= file_get_contents( $wgMimeTypeFile );
} else {
- wfDebug( __METHOD__.": can't load mime types from $wgMimeTypeFile\n" );
+ wfDebug( __METHOD__ . ": can't load mime types from $wgMimeTypeFile\n" );
}
} else {
- wfDebug( __METHOD__.": no mime types file defined, using build-ins only.\n" );
+ wfDebug( __METHOD__ . ": no mime types file defined, using build-ins only.\n" );
}
$types = str_replace( array( "\r\n", "\n\r", "\n\n", "\r\r", "\r" ), "\n", $types );
@@ -213,7 +213,7 @@ class MimeMagic {
$this->mMimeToExt = array();
$this->mToMime = array();
- $lines = explode( "\n",$types );
+ $lines = explode( "\n", $types );
foreach ( $lines as $s ) {
$s = trim( $s );
if ( empty( $s ) ) {
@@ -231,7 +231,7 @@ class MimeMagic {
}
$mime = substr( $s, 0, $i );
- $ext = trim( substr($s, $i+1 ) );
+ $ext = trim( substr( $s, $i+1 ) );
if ( empty( $ext ) ) {
continue;
@@ -272,17 +272,17 @@ class MimeMagic {
if ( $wgMimeInfoFile ) {
if ( is_file( $wgMimeInfoFile ) and is_readable( $wgMimeInfoFile ) ) {
- wfDebug( __METHOD__.": loading mime info from $wgMimeInfoFile\n" );
+ wfDebug( __METHOD__ . ": loading mime info from $wgMimeInfoFile\n" );
$info .= "\n";
$info .= file_get_contents( $wgMimeInfoFile );
} else {
- wfDebug(__METHOD__.": can't load mime info from $wgMimeInfoFile\n");
+ wfDebug( __METHOD__ . ": can't load mime info from $wgMimeInfoFile\n" );
}
} else {
- wfDebug(__METHOD__.": no mime info file defined, using build-ins only.\n");
+ wfDebug( __METHOD__ . ": no mime info file defined, using build-ins only.\n" );
}
- $info = str_replace( array( "\r\n", "\n\r", "\n\n", "\r\r", "\r" ), "\n", $info);
+ $info = str_replace( array( "\r\n", "\n\r", "\n\n", "\r\r", "\r" ), "\n", $info );
$info = str_replace( "\t", " ", $info );
$this->mMimeTypeAliases = array();
@@ -330,9 +330,9 @@ class MimeMagic {
$this->mMediaTypes[$mtype][] = $mime;
}
- if ( sizeof( $m ) > 1 ) {
+ if ( count( $m ) > 1 ) {
$main = $m[0];
- for ( $i=1; $i<sizeof($m); $i += 1 ) {
+ for ( $i = 1; $i < count( $m ); $i += 1 ) {
$mime = $m[$i];
$this->mMimeTypeAliases[$mime] = $main;
}
@@ -346,17 +346,17 @@ class MimeMagic {
* @return MimeMagic
*/
public static function &singleton() {
- if ( !isset( self::$instance ) ) {
+ if ( self::$instance === null ) {
self::$instance = new MimeMagic;
}
return self::$instance;
}
- /**
- * Returns a list of file extensions for a given mime type as a space
+ /**
+ * Returns a list of file extensions for a given mime type as a space
* separated string or null if the mime type was unrecognized. Resolves
* mime type aliases.
- *
+ *
* @param $mime string
* @return string|null
*/
@@ -379,10 +379,10 @@ class MimeMagic {
return null;
}
- /**
- * Returns a list of mime types for a given file extension as a space
+ /**
+ * Returns a list of mime types for a given file extension as a space
* separated string or null if the extension was unrecognized.
- *
+ *
* @param $ext string
* @return string|null
*/
@@ -393,10 +393,10 @@ class MimeMagic {
return $r;
}
- /**
+ /**
* Returns a single mime type for a given file extension or null if unknown.
* This is always the first type from the list returned by getTypesForExtension($ext).
- *
+ *
* @param $ext string
* @return string|null
*/
@@ -413,12 +413,11 @@ class MimeMagic {
return $m;
}
-
- /**
- * Tests if the extension matches the given mime type. Returns true if a
- * match was found, null if the mime type is unknown, and false if the
+ /**
+ * Tests if the extension matches the given mime type. Returns true if a
+ * match was found, null if the mime type is unknown, and false if the
* mime type is known but no matches where found.
- *
+ *
* @param $extension string
* @param $mime string
* @return bool|null
@@ -427,21 +426,21 @@ class MimeMagic {
$ext = $this->getExtensionsForType( $mime );
if ( !$ext ) {
- return null; // Unknown mime type
+ return null; // Unknown mime type
}
$ext = explode( ' ', $ext );
$extension = strtolower( $extension );
- return in_array( $extension, $ext );
+ return in_array( $extension, $ext );
}
- /**
- * Returns true if the mime type is known to represent an image format
+ /**
+ * Returns true if the mime type is known to represent an image format
* supported by the PHP GD library.
*
* @param $mime string
- *
+ *
* @return bool
*/
public function isPHPImageType( $mime ) {
@@ -489,32 +488,32 @@ class MimeMagic {
return in_array( strtolower( $extension ), $types );
}
- /**
+ /**
* Improves a mime type using the file extension. Some file formats are very generic,
- * so their mime type is not very meaningful. A more useful mime type can be derived
- * by looking at the file extension. Typically, this method would be called on the
+ * so their mime type is not very meaningful. A more useful mime type can be derived
+ * by looking at the file extension. Typically, this method would be called on the
* result of guessMimeType().
- *
+ *
* Currently, this method does the following:
*
* If $mime is "unknown/unknown" and isRecognizableExtension( $ext ) returns false,
- * return the result of guessTypesForExtension($ext).
+ * return the result of guessTypesForExtension($ext).
*
* If $mime is "application/x-opc+zip" and isMatchingExtension( $ext, $mime )
- * gives true, return the result of guessTypesForExtension($ext).
+ * gives true, return the result of guessTypesForExtension($ext).
*
- * @param $mime String: the mime type, typically guessed from a file's content.
- * @param $ext String: the file extension, as taken from the file name
+ * @param string $mime the mime type, typically guessed from a file's content.
+ * @param string $ext the file extension, as taken from the file name
*
* @return string the mime type
*/
public function improveTypeFromExtension( $mime, $ext ) {
if ( $mime === 'unknown/unknown' ) {
if ( $this->isRecognizableExtension( $ext ) ) {
- wfDebug( __METHOD__. ': refusing to guess mime type for .' .
+ wfDebug( __METHOD__ . ': refusing to guess mime type for .' .
"$ext file, we should have recognized it\n" );
} else {
- // Not something we can detect, so simply
+ // Not something we can detect, so simply
// trust the file extension
$mime = $this->guessTypesForExtension( $ext );
}
@@ -525,7 +524,7 @@ class MimeMagic {
// find the proper mime type for that file extension
$mime = $this->guessTypesForExtension( $ext );
} else {
- wfDebug( __METHOD__. ": refusing to guess better type for $mime file, " .
+ wfDebug( __METHOD__ . ": refusing to guess better type for $mime file, " .
".$ext is not a known OPC extension.\n" );
$mime = 'application/zip';
}
@@ -535,34 +534,34 @@ class MimeMagic {
$mime = $this->mMimeTypeAliases[$mime];
}
- wfDebug(__METHOD__.": improved mime type for .$ext: $mime\n");
+ wfDebug( __METHOD__ . ": improved mime type for .$ext: $mime\n" );
return $mime;
}
- /**
- * Mime type detection. This uses detectMimeType to detect the mime type
- * of the file, but applies additional checks to determine some well known
- * file formats that may be missed or misinterpreter by the default mime
- * detection (namely XML based formats like XHTML or SVG, as well as ZIP
+ /**
+ * Mime type detection. This uses detectMimeType to detect the mime type
+ * of the file, but applies additional checks to determine some well known
+ * file formats that may be missed or misinterpreted by the default mime
+ * detection (namely XML based formats like XHTML or SVG, as well as ZIP
* based formats like OPC/ODF files).
*
- * @param $file String: the file to check
+ * @param string $file the file to check
* @param $ext Mixed: the file extension, or true (default) to extract it from the filename.
- * Set it to false to ignore the extension. DEPRECATED! Set to false, use
+ * Set it to false to ignore the extension. DEPRECATED! Set to false, use
* improveTypeFromExtension($mime, $ext) later to improve mime type.
*
* @return string the mime type of $file
*/
public function guessMimeType( $file, $ext = true ) {
if ( $ext ) { // TODO: make $ext default to false. Or better, remove it.
- wfDebug( __METHOD__.": WARNING: use of the \$ext parameter is deprecated. " .
+ wfDebug( __METHOD__ . ": WARNING: use of the \$ext parameter is deprecated. " .
"Use improveTypeFromExtension(\$mime, \$ext) instead.\n" );
}
$mime = $this->doGuessMimeType( $file, $ext );
if( !$mime ) {
- wfDebug( __METHOD__.": internal type detection failed for $file (.$ext)...\n" );
+ wfDebug( __METHOD__ . ": internal type detection failed for $file (.$ext)...\n" );
$mime = $this->detectMimeType( $file, $ext );
}
@@ -570,7 +569,7 @@ class MimeMagic {
$mime = $this->mMimeTypeAliases[$mime];
}
- wfDebug(__METHOD__.": guessed mime type of $file: $mime\n");
+ wfDebug( __METHOD__ . ": guessed mime type of $file: $mime\n" );
return $mime;
}
@@ -587,7 +586,7 @@ class MimeMagic {
// @todo FIXME: Shouldn't this be rb?
$f = fopen( $file, 'rt' );
wfRestoreWarnings();
-
+
if( !$f ) {
return 'unknown/unknown';
}
@@ -629,7 +628,7 @@ class MimeMagic {
$doctype = strpos( $head, "\x42\x82" );
if ( $doctype ) {
// Next byte is datasize, then data (sizes larger than 1 byte are very stupid muxers)
- $data = substr($head, $doctype+3, 8);
+ $data = substr( $head, $doctype+3, 8 );
if ( strncmp( $data, "matroska", 8 ) == 0 ) {
wfDebug( __METHOD__ . ": recognized file as video/x-matroska\n" );
return "video/x-matroska";
@@ -661,12 +660,11 @@ class MimeMagic {
* strings like "<? ", but should it be axed completely?
*/
if ( ( strpos( $head, '<?php' ) !== false ) ||
-
- ( strpos( $head, "<\x00?\x00p\x00h\x00p" ) !== false ) ||
- ( strpos( $head, "<\x00?\x00 " ) !== false ) ||
- ( strpos( $head, "<\x00?\x00\n" ) !== false ) ||
- ( strpos( $head, "<\x00?\x00\t" ) !== false ) ||
- ( strpos( $head, "<\x00?\x00=" ) !== false ) ) {
+ ( strpos( $head, "<\x00?\x00p\x00h\x00p" ) !== false ) ||
+ ( strpos( $head, "<\x00?\x00 " ) !== false ) ||
+ ( strpos( $head, "<\x00?\x00\n" ) !== false ) ||
+ ( strpos( $head, "<\x00?\x00\t" ) !== false ) ||
+ ( strpos( $head, "<\x00?\x00=" ) !== false ) ) {
wfDebug( __METHOD__ . ": recognized $file as application/x-php\n" );
return 'application/x-php';
@@ -698,11 +696,11 @@ class MimeMagic {
} elseif ( substr( $head, 0, 7) == "\xfe\xff\x00#\x00!" ) {
$script_type = "UTF-16BE";
} elseif ( substr( $head, 0, 7 ) == "\xff\xfe#\x00!" ) {
- $script_type= "UTF-16LE";
+ $script_type = "UTF-16LE";
}
if ( $script_type ) {
- if ( $script_type !== "UTF-8" && $script_type !== "ASCII") {
+ if ( $script_type !== "UTF-8" && $script_type !== "ASCII" ) {
// Quick and dirty fold down to ASCII!
$pack = array( 'UTF-16BE' => 'n*', 'UTF-16LE' => 'v*' );
$chars = unpack( $pack[$script_type], substr( $head, 2 ) );
@@ -720,14 +718,14 @@ class MimeMagic {
if ( preg_match( '%/?([^\s]+/)(\w+)%', $head, $match ) ) {
$mime = "application/x-{$match[2]}";
- wfDebug( __METHOD__.": shell script recognized as $mime\n" );
+ wfDebug( __METHOD__ . ": shell script recognized as $mime\n" );
return $mime;
}
}
// Check for ZIP variants (before getimagesize)
if ( strpos( $tail, "PK\x05\x06" ) !== false ) {
- wfDebug( __METHOD__.": ZIP header present in $file\n" );
+ wfDebug( __METHOD__ . ": ZIP header present in $file\n" );
return $this->detectZipType( $head, $tail, $ext );
}
@@ -737,36 +735,36 @@ class MimeMagic {
if( $gis && isset( $gis['mime'] ) ) {
$mime = $gis['mime'];
- wfDebug( __METHOD__.": getimagesize detected $file as $mime\n" );
+ wfDebug( __METHOD__ . ": getimagesize detected $file as $mime\n" );
return $mime;
}
// Also test DjVu
$deja = new DjVuImage( $file );
if( $deja->isValid() ) {
- wfDebug( __METHOD__.": detected $file as image/vnd.djvu\n" );
+ wfDebug( __METHOD__ . ": detected $file as image/vnd.djvu\n" );
return 'image/vnd.djvu';
}
return false;
}
-
+
/**
* Detect application-specific file type of a given ZIP file from its
* header data. Currently works for OpenDocument and OpenXML types...
* If can't tell, returns 'application/zip'.
*
- * @param $header String: some reasonably-sized chunk of file header
+ * @param string $header some reasonably-sized chunk of file header
* @param $tail String: the tail of the file
* @param $ext Mixed: the file extension, or true to extract it from the filename.
- * Set it to false (default) to ignore the extension. DEPRECATED! Set to false,
+ * Set it to false (default) to ignore the extension. DEPRECATED! Set to false,
* use improveTypeFromExtension($mime, $ext) later to improve mime type.
*
* @return string
*/
function detectZipType( $header, $tail = null, $ext = false ) {
if( $ext ) { # TODO: remove $ext param
- wfDebug( __METHOD__.": WARNING: use of the \$ext parameter is deprecated. " .
+ wfDebug( __METHOD__ . ": WARNING: use of the \$ext parameter is deprecated. " .
"Use improveTypeFromExtension(\$mime, \$ext) instead.\n" );
}
@@ -797,30 +795,31 @@ class MimeMagic {
if ( preg_match( $opendocRegex, substr( $header, 30 ), $matches ) ) {
$mime = $matches[1];
- wfDebug( __METHOD__.": detected $mime from ZIP archive\n" );
+ wfDebug( __METHOD__ . ": detected $mime from ZIP archive\n" );
} elseif ( preg_match( $openxmlRegex, substr( $header, 30 ) ) ) {
$mime = "application/x-opc+zip";
- # TODO: remove the block below, as soon as improveTypeFromExtension is used everywhere
- if ( $ext !== true && $ext !== false ) {
+ # TODO: remove the block below, as soon as improveTypeFromExtension is used everywhere
+ if ( $ext !== true && $ext !== false ) {
/** This is the mode used by getPropsFromPath
- * These mime's are stored in the database, where we don't really want
- * x-opc+zip, because we use it only for internal purposes
- */
+ * These mime's are stored in the database, where we don't really want
+ * x-opc+zip, because we use it only for internal purposes
+ */
if ( $this->isMatchingExtension( $ext, $mime) ) {
/* A known file extension for an OPC file,
- * find the proper mime type for that file extension */
+ * find the proper mime type for that file extension
+ */
$mime = $this->guessTypesForExtension( $ext );
} else {
$mime = "application/zip";
}
}
- wfDebug( __METHOD__.": detected an Open Packaging Conventions archive: $mime\n" );
- } elseif ( substr( $header, 0, 8 ) == "\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1" &&
+ wfDebug( __METHOD__ . ": detected an Open Packaging Conventions archive: $mime\n" );
+ } elseif ( substr( $header, 0, 8 ) == "\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1" &&
($headerpos = strpos( $tail, "PK\x03\x04" ) ) !== false &&
preg_match( $openxmlRegex, substr( $tail, $headerpos + 30 ) ) ) {
if ( substr( $header, 512, 4) == "\xEC\xA5\xC1\x00" ) {
$mime = "application/msword";
- }
+ }
switch( substr( $header, 512, 6) ) {
case "\xEC\xA5\xC1\x00\x0E\x00":
case "\xEC\xA5\xC1\x00\x1C\x00":
@@ -843,27 +842,27 @@ class MimeMagic {
break;
}
- wfDebug( __METHOD__.": detected a MS Office document with OPC trailer\n");
+ wfDebug( __METHOD__ . ": detected a MS Office document with OPC trailer\n" );
} else {
- wfDebug( __METHOD__.": unable to identify type of ZIP archive\n" );
+ wfDebug( __METHOD__ . ": unable to identify type of ZIP archive\n" );
}
return $mime;
}
- /**
- * Internal mime type detection. Detection is done using an external
- * program, if $wgMimeDetectorCommand is set. Otherwise, the fileinfo
- * extension and mime_content_type are tried (in this order), if they
- * are available. If the dections fails and $ext is not false, the mime
+ /**
+ * Internal mime type detection. Detection is done using an external
+ * program, if $wgMimeDetectorCommand is set. Otherwise, the fileinfo
+ * extension and mime_content_type are tried (in this order), if they
+ * are available. If the detections fails and $ext is not false, the mime
* type is guessed from the file extension, using guessTypesForExtension.
- *
- * If the mime type is still unknown, getimagesize is used to detect the
- * mime type if the file is an image. If no mime type can be determined,
+ *
+ * If the mime type is still unknown, getimagesize is used to detect the
+ * mime type if the file is an image. If no mime type can be determined,
* this function returns 'unknown/unknown'.
*
- * @param $file String: the file to check
+ * @param string $file the file to check
* @param $ext Mixed: the file extension, or true (default) to extract it from the filename.
- * Set it to false to ignore the extension. DEPRECATED! Set to false, use
+ * Set it to false to ignore the extension. DEPRECATED! Set to false, use
* improveTypeFromExtension($mime, $ext) later to improve mime type.
*
* @return string the mime type of $file
@@ -872,7 +871,7 @@ class MimeMagic {
global $wgMimeDetectorCommand;
if ( $ext ) { # TODO: make $ext default to false. Or better, remove it.
- wfDebug( __METHOD__.": WARNING: use of the \$ext parameter is deprecated. Use improveTypeFromExtension(\$mime, \$ext) instead.\n" );
+ wfDebug( __METHOD__ . ": WARNING: use of the \$ext parameter is deprecated. Use improveTypeFromExtension(\$mime, \$ext) instead.\n" );
}
$m = null;
@@ -898,22 +897,22 @@ class MimeMagic {
$m = finfo_file( $mime_magic_resource, $file );
finfo_close( $mime_magic_resource );
} else {
- wfDebug( __METHOD__.": finfo_open failed on ".FILEINFO_MIME."!\n" );
+ wfDebug( __METHOD__ . ": finfo_open failed on ".FILEINFO_MIME."!\n" );
}
} elseif ( function_exists( "mime_content_type" ) ) {
# NOTE: this function is available since PHP 4.3.0, but only if
# PHP was compiled with --with-mime-magic or, before 4.3.2, with --enable-mime-magic.
#
- # On Windows, you must set mime_magic.magicfile in php.ini to point to the mime.magic file bundeled with PHP;
+ # On Windows, you must set mime_magic.magicfile in php.ini to point to the mime.magic file bundled with PHP;
# sometimes, this may even be needed under linus/unix.
#
# Also note that this has been DEPRECATED in favor of the fileinfo extension by PECL, see above.
# see http://www.php.net/manual/en/ref.mime-magic.php for details.
- $m = mime_content_type($file);
+ $m = mime_content_type( $file );
} else {
- wfDebug( __METHOD__.": no magic mime detector found!\n" );
+ wfDebug( __METHOD__ . ": no magic mime detector found!\n" );
}
if ( $m ) {
@@ -925,7 +924,7 @@ class MimeMagic {
if ( strpos( $m, 'unknown' ) !== false ) {
$m = null;
} else {
- wfDebug( __METHOD__.": magic mime type of $file: $m\n" );
+ wfDebug( __METHOD__ . ": magic mime type of $file: $m\n" );
return $m;
}
}
@@ -937,11 +936,11 @@ class MimeMagic {
}
if ( $ext ) {
if( $this->isRecognizableExtension( $ext ) ) {
- wfDebug( __METHOD__. ": refusing to guess mime type for .$ext file, we should have recognized it\n" );
+ wfDebug( __METHOD__ . ": refusing to guess mime type for .$ext file, we should have recognized it\n" );
} else {
$m = $this->guessTypesForExtension( $ext );
if ( $m ) {
- wfDebug( __METHOD__.": extension mime type of $file: $m\n" );
+ wfDebug( __METHOD__ . ": extension mime type of $file: $m\n" );
return $m;
}
}
@@ -962,9 +961,9 @@ class MimeMagic {
* @todo analyse file if need be
* @todo look at multiple extension, separately and together.
*
- * @param $path String: full path to the image file, in case we have to look at the contents
+ * @param string $path full path to the image file, in case we have to look at the contents
* (if null, only the mime type is used to determine the media type code).
- * @param $mime String: mime type. If null it will be guessed using guessMimeType.
+ * @param string $mime mime type. If null it will be guessed using guessMimeType.
*
* @return (int?string?) a value to be used with the MEDIATYPE_xxx constants.
*/
@@ -1037,17 +1036,17 @@ class MimeMagic {
return $type;
}
- /**
+ /**
* Returns a media code matching the given mime type or file extension.
* File extensions are represented by a string starting with a dot (.) to
* distinguish them from mime types.
*
- * This funktion relies on the mapping defined by $this->mMediaTypes
+ * This function relies on the mapping defined by $this->mMediaTypes
* @access private
* @return int|string
*/
function findMediaType( $extMime ) {
- if ( strpos( $extMime, '.' ) === 0 ) {
+ if ( strpos( $extMime, '.' ) === 0 ) {
// If it's an extension, look up the mime types
$m = $this->getTypesForExtension( substr( $extMime, 1 ) );
if ( !$m ) {
@@ -1066,7 +1065,7 @@ class MimeMagic {
foreach ( $m as $mime ) {
foreach ( $this->mMediaTypes as $type => $codes ) {
- if ( in_array($mime, $codes, true ) ) {
+ if ( in_array( $mime, $codes, true ) ) {
return $type;
}
}
@@ -1076,12 +1075,12 @@ class MimeMagic {
}
/**
- * Get the MIME types that various versions of Internet Explorer would
+ * Get the MIME types that various versions of Internet Explorer would
* detect from a chunk of the content.
*
- * @param $fileName String: the file name (unused at present)
- * @param $chunk String: the first 256 bytes of the file
- * @param $proposed String: the MIME type proposed by the server
+ * @param string $fileName the file name (unused at present)
+ * @param string $chunk the first 256 bytes of the file
+ * @param string $proposed the MIME type proposed by the server
* @return Array
*/
public function getIEMimeTypes( $fileName, $chunk, $proposed ) {
diff --git a/includes/Namespace.php b/includes/Namespace.php
index 2e2b8d61..fccfbedb 100644
--- a/includes/Namespace.php
+++ b/includes/Namespace.php
@@ -48,6 +48,7 @@ class MWNamespace {
* @param $index
* @param $method
*
+ * @throws MWException
* @return bool
*/
private static function isMethodValidFor( $index, $method ) {
@@ -60,13 +61,13 @@ class MWNamespace {
/**
* Can pages in the given namespace be moved?
*
- * @param $index Int: namespace index
+ * @param int $index namespace index
* @return bool
*/
public static function isMovable( $index ) {
global $wgAllowImageMoving;
- $result = !( $index < NS_MAIN || ( $index == NS_FILE && !$wgAllowImageMoving ) || $index == NS_CATEGORY );
+ $result = !( $index < NS_MAIN || ( $index == NS_FILE && !$wgAllowImageMoving ) || $index == NS_CATEGORY );
/**
* @since 1.20
@@ -79,7 +80,7 @@ class MWNamespace {
/**
* Is the given namespace is a subject (non-talk) namespace?
*
- * @param $index Int: namespace index
+ * @param int $index namespace index
* @return bool
* @since 1.19
*/
@@ -100,7 +101,7 @@ class MWNamespace {
/**
* Is the given namespace a talk namespace?
*
- * @param $index Int: namespace index
+ * @param int $index namespace index
* @return bool
*/
public static function isTalk( $index ) {
@@ -111,7 +112,7 @@ class MWNamespace {
/**
* Get the talk namespace index for a given namespace
*
- * @param $index Int: namespace index
+ * @param int $index namespace index
* @return int
*/
public static function getTalk( $index ) {
@@ -125,7 +126,7 @@ class MWNamespace {
* Get the subject namespace index for a given namespace
* Special namespaces (NS_MEDIA, NS_SPECIAL) are always the subject.
*
- * @param $index Int: Namespace index
+ * @param int $index Namespace index
* @return int
*/
public static function getSubject( $index ) {
@@ -144,7 +145,7 @@ class MWNamespace {
* For talk namespaces, returns the subject (non-talk) namespace
* For subject (non-talk) namespaces, returns the talk namespace
*
- * @param $index Int: namespace index
+ * @param int $index namespace index
* @return int or null if no associated namespace could be found
*/
public static function getAssociated( $index ) {
@@ -180,8 +181,8 @@ class MWNamespace {
* of this function rather than directly doing comparison will make
* sure that code will not potentially break.
*
- * @param $ns1 int The first namespace index
- * @param $ns2 int The second namespae index
+ * @param int $ns1 The first namespace index
+ * @param int $ns2 The second namespace index
*
* @return bool
* @since 1.19
@@ -195,8 +196,8 @@ class MWNamespace {
* eg: NS_USER and NS_USER wil return true, as well
* NS_USER and NS_USER_TALK will return true.
*
- * @param $ns1 int The first namespace index
- * @param $ns2 int The second namespae index
+ * @param int $ns1 The first namespace index
+ * @param int $ns2 The second namespace index
*
* @return bool
* @since 1.19
@@ -209,12 +210,14 @@ class MWNamespace {
* Returns array of all defined namespaces with their canonical
* (English) names.
*
+ * @param bool $rebuild rebuild namespace list (default = false). Used for testing.
+ *
* @return array
* @since 1.17
*/
- public static function getCanonicalNamespaces() {
+ public static function getCanonicalNamespaces( $rebuild = false ) {
static $namespaces = null;
- if ( $namespaces === null ) {
+ if ( $namespaces === null || $rebuild ) {
global $wgExtraNamespaces, $wgCanonicalNamespaceNames;
$namespaces = array( NS_MAIN => '' ) + $wgCanonicalNamespaceNames;
if ( is_array( $wgExtraNamespaces ) ) {
@@ -228,7 +231,7 @@ class MWNamespace {
/**
* Returns the canonical (English) name for a given index
*
- * @param $index Int: namespace index
+ * @param int $index namespace index
* @return string or false if no canonical definition.
*/
public static function getCanonicalName( $index ) {
@@ -244,7 +247,7 @@ class MWNamespace {
* Returns the index for a given canonical name, or NULL
* The input *must* be converted to lower case first
*
- * @param $name String: namespace name
+ * @param string $name namespace name
* @return int
*/
public static function getCanonicalIndex( $name ) {
@@ -284,18 +287,18 @@ class MWNamespace {
/**
* Can this namespace ever have a talk namespace?
*
- * @param $index Int: namespace index
+ * @param int $index namespace index
* @return bool
*/
- public static function canTalk( $index ) {
+ public static function canTalk( $index ) {
return $index >= NS_MAIN;
- }
+ }
/**
* Does this namespace contain content, for the purposes of calculating
* statistics, etc?
*
- * @param $index Int: index to check
+ * @param int $index index to check
* @return bool
*/
public static function isContent( $index ) {
@@ -316,7 +319,7 @@ class MWNamespace {
/**
* Does the namespace allow subpages?
*
- * @param $index int Index to check
+ * @param int $index Index to check
* @return bool
*/
public static function hasSubpages( $index ) {
@@ -369,7 +372,7 @@ class MWNamespace {
/**
* Is the namespace first-letter capitalized?
*
- * @param $index int Index to check
+ * @param int $index Index to check
* @return bool
*/
public static function isCapitalized( $index ) {
@@ -397,7 +400,7 @@ class MWNamespace {
* genders. Not all languages make a distinction here.
*
* @since 1.18
- * @param $index int Index to check
+ * @param int $index Index to check
* @return bool
*/
public static function hasGenderDistinction( $index ) {
@@ -408,7 +411,7 @@ class MWNamespace {
* It is not possible to use pages from this namespace as template?
*
* @since 1.20
- * @param $index int Index to check
+ * @param int $index Index to check
* @return bool
*/
public static function isNonincludable( $index ) {
@@ -416,4 +419,18 @@ class MWNamespace {
return $wgNonincludableNamespaces && in_array( $index, $wgNonincludableNamespaces );
}
+ /**
+ * Get the default content model for a namespace
+ * This does not mean that all pages in that namespace have the model
+ *
+ * @since 1.21
+ * @param int $index Index to check
+ * @return null|string default model name for the given namespace, if set
+ */
+ public static function getNamespaceContentModel( $index ) {
+ global $wgNamespaceContentModels;
+ return isset( $wgNamespaceContentModels[$index] )
+ ? $wgNamespaceContentModels[$index]
+ : null;
+ }
}
diff --git a/includes/OutputHandler.php b/includes/OutputHandler.php
index 46a43f63..6b40c307 100644
--- a/includes/OutputHandler.php
+++ b/includes/OutputHandler.php
@@ -22,9 +22,9 @@
/**
* Standard output handler for use with ob_start
- *
+ *
* @param $s string
- *
+ *
* @return string
*/
function wfOutputHandler( $s ) {
@@ -85,14 +85,14 @@ function wfRequestExtension() {
/**
* Handler that compresses data with gzip if allowed by the Accept header.
* Unlike ob_gzhandler, it works for HEAD requests too.
- *
+ *
* @param $s string
*
* @return string
*/
function wfGzipHandler( $s ) {
if( !function_exists( 'gzencode' ) ) {
- wfDebug( __FUNCTION__ . "() skipping compression (gzencode unavaible)\n" );
+ wfDebug( __FUNCTION__ . "() skipping compression (gzencode unavailable)\n" );
return $s;
}
if( headers_sent() ) {
@@ -156,7 +156,7 @@ function wfMangleFlashPolicy( $s ) {
* @param $length int
*/
function wfDoContentLength( $length ) {
- if ( !headers_sent() && $_SERVER['SERVER_PROTOCOL'] == 'HTTP/1.0' ) {
+ if ( !headers_sent() && isset( $_SERVER['SERVER_PROTOCOL'] ) && $_SERVER['SERVER_PROTOCOL'] == 'HTTP/1.0' ) {
header( "Content-Length: $length" );
}
}
diff --git a/includes/OutputPage.php b/includes/OutputPage.php
index b4a81bb1..1e0c396a 100644
--- a/includes/OutputPage.php
+++ b/includes/OutputPage.php
@@ -43,6 +43,7 @@ class OutputPage extends ContextSource {
var $mKeywords = array();
var $mLinktags = array();
+ var $mCanonicalUrl = false;
/// Additional stylesheets. Looks like this is for extensions. Might be replaced by resource loader.
var $mExtStyles = array();
@@ -122,7 +123,7 @@ class OutputPage extends ContextSource {
var $mScripts = '';
/**
- * Inline CSS styles. Use addInlineStyle() sparsingly
+ * Inline CSS styles. Use addInlineStyle() sparingly
*/
var $mInlineStyles = '';
@@ -248,6 +249,11 @@ class OutputPage extends ContextSource {
private $mRedirectedFrom = null;
/**
+ * Additional key => value data
+ */
+ private $mProperties = array();
+
+ /**
* Constructor for OutputPage. This should not be called directly.
* Instead a new RequestContext should be created and it will implicitly create
* a OutputPage tied to that context.
@@ -255,7 +261,7 @@ class OutputPage extends ContextSource {
function __construct( IContextSource $context = null ) {
if ( $context === null ) {
# Extensions should use `new RequestContext` instead of `new OutputPage` now.
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.18' );
} else {
$this->setContext( $context );
}
@@ -264,8 +270,8 @@ class OutputPage extends ContextSource {
/**
* Redirect to $url rather than displaying the normal page
*
- * @param $url String: URL
- * @param $responsecode String: HTTP status code
+ * @param string $url URL
+ * @param string $responsecode HTTP status code
*/
public function redirect( $url, $responsecode = '302' ) {
# Strip newlines as a paranoia check for header injection in PHP<5.1.2
@@ -295,8 +301,8 @@ class OutputPage extends ContextSource {
* Add a new "<meta>" tag
* To add an http-equiv meta tag, precede the name with "http:"
*
- * @param $name String tag name
- * @param $val String tag value
+ * @param string $name tag name
+ * @param string $val tag value
*/
function addMeta( $name, $val ) {
array_push( $this->mMetatags, array( $name, $val ) );
@@ -305,7 +311,7 @@ class OutputPage extends ContextSource {
/**
* Add a keyword or a list of keywords in the page header
*
- * @param $text String or array of strings
+ * @param string $text or array of strings
*/
function addKeyword( $text ) {
if( is_array( $text ) ) {
@@ -316,9 +322,11 @@ class OutputPage extends ContextSource {
}
/**
- * Add a new \<link\> tag to the page header
+ * Add a new \<link\> tag to the page header.
+ *
+ * Note: use setCanonicalUrl() for rel=canonical.
*
- * @param $linkarr Array: associative array of attributes.
+ * @param array $linkarr associative array of attributes.
*/
function addLink( $linkarr ) {
array_push( $this->mLinktags, $linkarr );
@@ -327,7 +335,7 @@ class OutputPage extends ContextSource {
/**
* Add a new \<link\> with "rel" attribute set to "meta"
*
- * @param $linkarr Array: associative array mapping attribute names to their
+ * @param array $linkarr associative array mapping attribute names to their
* values, both keys and values will be escaped, and the
* "rel" attribute will be automatically added
*/
@@ -337,6 +345,14 @@ class OutputPage extends ContextSource {
}
/**
+ * Set the URL to be used for the <link rel=canonical>. This should be used
+ * in preference to addLink(), to avoid duplicate link tags.
+ */
+ function setCanonicalUrl( $url ) {
+ $this->mCanonicalUrl = $url;
+ }
+
+ /**
* Get the value of the "rel" attribute for metadata links
*
* @return String
@@ -355,7 +371,7 @@ class OutputPage extends ContextSource {
/**
* Add raw HTML to the list of scripts (including \<script\> tag, etc.)
*
- * @param $script String: raw HTML
+ * @param string $script raw HTML
*/
function addScript( $script ) {
$this->mScripts .= $script . "\n";
@@ -364,7 +380,7 @@ class OutputPage extends ContextSource {
/**
* Register and add a stylesheet from an extension directory.
*
- * @param $url String path to sheet. Provide either a full url (beginning
+ * @param string $url path to sheet. Provide either a full url (beginning
* with 'http', etc) or a relative path from the document root
* (beginning with '/'). Otherwise it behaves identically to
* addStyle() and draws from the /skins folder.
@@ -385,9 +401,9 @@ class OutputPage extends ContextSource {
/**
* Add a JavaScript file out of skins/common, or a given relative path.
*
- * @param $file String: filename in skins/common or complete on-server path
+ * @param string $file filename in skins/common or complete on-server path
* (/foo/bar.js)
- * @param $version String: style version of the file. Defaults to $wgStyleVersion
+ * @param string $version style version of the file. Defaults to $wgStyleVersion
*/
public function addScriptFile( $file, $version = null ) {
global $wgStylePath, $wgStyleVersion;
@@ -405,7 +421,7 @@ class OutputPage extends ContextSource {
/**
* Add a self-contained script tag with the given contents
*
- * @param $script String: JavaScript text, no "<script>" tags
+ * @param string $script JavaScript text, no "<script>" tags
*/
public function addInlineScript( $script ) {
$this->mScripts .= Html::inlineScript( "\n$script\n" ) . "\n";
@@ -424,14 +440,14 @@ class OutputPage extends ContextSource {
* Filter an array of modules to remove insufficiently trustworthy members, and modules
* which are no longer registered (eg a page is cached before an extension is disabled)
* @param $modules Array
- * @param $position String if not null, only return modules with this position
+ * @param string $position if not null, only return modules with this position
* @param $type string
* @return Array
*/
- protected function filterModules( $modules, $position = null, $type = ResourceLoaderModule::TYPE_COMBINED ){
+ protected function filterModules( $modules, $position = null, $type = ResourceLoaderModule::TYPE_COMBINED ) {
$resourceLoader = $this->getResourceLoader();
$filteredModules = array();
- foreach( $modules as $val ){
+ foreach( $modules as $val ) {
$module = $resourceLoader->getModule( $val );
if( $module instanceof ResourceLoaderModule
&& $module->getOrigin() <= $this->getAllowedModules( $type )
@@ -446,8 +462,8 @@ class OutputPage extends ContextSource {
/**
* Get the list of modules to include on this page
*
- * @param $filter Bool whether to filter out insufficiently trustworthy modules
- * @param $position String if not null, only return modules with this position
+ * @param bool $filter whether to filter out insufficiently trustworthy modules
+ * @param string $position if not null, only return modules with this position
* @param $param string
* @return Array of module names
*/
@@ -501,13 +517,15 @@ class OutputPage extends ContextSource {
* @return Array of module names
*/
public function getModuleStyles( $filter = false, $position = null ) {
- return $this->getModules( $filter, $position, 'mModuleStyles' );
+ return $this->getModules( $filter, $position, 'mModuleStyles' );
}
/**
- * Add only CSS of one or more modules recognized by the resource loader. Module
- * styles added through this function will be loaded by the resource loader when
- * the page loads.
+ * Add only CSS of one or more modules recognized by the resource loader.
+ *
+ * Module styles added through this function will be added using standard link CSS
+ * tags, rather than as a combined Javascript and CSS package. Thus, they will
+ * load when JavaScript is disabled (unless CSS also happens to be disabled).
*
* @param $modules Mixed: module name (string) or array of module names
*/
@@ -563,8 +581,8 @@ class OutputPage extends ContextSource {
/**
* Add or replace an header item to the output
*
- * @param $name String: item name
- * @param $value String: raw HTML
+ * @param string $name item name
+ * @param string $value raw HTML
*/
public function addHeadItem( $name, $value ) {
$this->mHeadItems[$name] = $value;
@@ -573,7 +591,7 @@ class OutputPage extends ContextSource {
/**
* Check if the header item $name is already set
*
- * @param $name String: item name
+ * @param string $name item name
* @return Boolean
*/
public function hasHeadItem( $name ) {
@@ -583,7 +601,7 @@ class OutputPage extends ContextSource {
/**
* Set the value of the ETag HTTP header, only used if $wgUseETag is true
*
- * @param $tag String: value of "ETag" header
+ * @param string $tag value of "ETag" header
*/
function setETag( $tag ) {
$this->mETag = $tag;
@@ -610,8 +628,34 @@ class OutputPage extends ContextSource {
}
/**
+ * Set an additional output property
+ * @since 1.21
+ *
+ * @param string $name
+ * @param mixed $value
+ */
+ public function setProperty( $name, $value ) {
+ $this->mProperties[$name] = $value;
+ }
+
+ /**
+ * Get an additional output property
+ * @since 1.21
+ *
+ * @param $name
+ * @return mixed: Property value or null if not found
+ */
+ public function getProperty( $name ) {
+ if ( isset( $this->mProperties[$name] ) ) {
+ return $this->mProperties[$name];
+ } else {
+ return null;
+ }
+ }
+
+ /**
* checkLastModified tells the client to use the client-cached page if
- * possible. If sucessful, the OutputPage is disabled so that
+ * possible. If successful, the OutputPage is disabled so that
* any future call to OutputPage->output() have no effect.
*
* Side effect: sets mLastModified for Last-Modified header
@@ -704,7 +748,7 @@ class OutputPage extends ContextSource {
/**
* Override the last modified timestamp
*
- * @param $timestamp String: new timestamp, in a format readable by
+ * @param string $timestamp new timestamp, in a format readable by
* wfTimestamp()
*/
public function setLastModified( $timestamp ) {
@@ -714,7 +758,7 @@ class OutputPage extends ContextSource {
/**
* Set the robot policy for the page: <http://www.robotstxt.org/meta.html>
*
- * @param $policy String: the literal string to output as the contents of
+ * @param string $policy the literal string to output as the contents of
* the meta tag. Will be parsed according to the spec and output in
* standardized form.
* @return null
@@ -734,7 +778,7 @@ class OutputPage extends ContextSource {
* Set the index policy for the page, but leave the follow policy un-
* touched.
*
- * @param $policy string Either 'index' or 'noindex'.
+ * @param string $policy Either 'index' or 'noindex'.
* @return null
*/
public function setIndexPolicy( $policy ) {
@@ -748,7 +792,7 @@ class OutputPage extends ContextSource {
* Set the follow policy for the page, but leave the index policy un-
* touched.
*
- * @param $policy String: either 'follow' or 'nofollow'.
+ * @param string $policy either 'follow' or 'nofollow'.
* @return null
*/
public function setFollowPolicy( $policy ) {
@@ -762,7 +806,7 @@ class OutputPage extends ContextSource {
* Set the new value of the "action text", this will be added to the
* "HTML title", separated from it with " - ".
*
- * @param $text String: new value of the "action text"
+ * @param string $text new value of the "action text"
*/
public function setPageTitleActionText( $text ) {
$this->mPageTitleActionText = $text;
@@ -777,6 +821,7 @@ class OutputPage extends ContextSource {
if ( isset( $this->mPageTitleActionText ) ) {
return $this->mPageTitleActionText;
}
+ return '';
}
/**
@@ -851,11 +896,10 @@ class OutputPage extends ContextSource {
$this->getContext()->setTitle( $t );
}
-
/**
- * Replace the subtile with $str
+ * Replace the subtitle with $str
*
- * @param $str String|Message: new value of the subtitle
+ * @param string|Message $str new value of the subtitle. String should be safe HTML.
*/
public function setSubtitle( $str ) {
$this->clearSubtitle();
@@ -866,7 +910,7 @@ class OutputPage extends ContextSource {
* Add $str to the subtitle
*
* @deprecated in 1.19; use addSubtitle() instead
- * @param $str String|Message to add to the subtitle
+ * @param string|Message $str to add to the subtitle
*/
public function appendSubtitle( $str ) {
$this->addSubtitle( $str );
@@ -875,7 +919,7 @@ class OutputPage extends ContextSource {
/**
* Add $str to the subtitle
*
- * @param $str String|Message to add to the subtitle
+ * @param string|Message $str to add to the subtitle. String should be safe HTML.
*/
public function addSubtitle( $str ) {
if ( $str instanceof Message ) {
@@ -987,7 +1031,7 @@ class OutputPage extends ContextSource {
* for the new version
* @see addFeedLink()
*
- * @param $val String: query to append to feed links or false to output
+ * @param string $val query to append to feed links or false to output
* default links
*/
public function setFeedAppendQuery( $val ) {
@@ -1007,8 +1051,8 @@ class OutputPage extends ContextSource {
/**
* Add a feed link to the page header
*
- * @param $format String: feed type, should be a key of $wgFeedClasses
- * @param $href String: URL
+ * @param string $format feed type, should be a key of $wgFeedClasses
+ * @param string $href URL
*/
public function addFeedLink( $format, $href ) {
global $wgAdvertisedFeedTypes;
@@ -1092,7 +1136,7 @@ class OutputPage extends ContextSource {
/**
* Add new language links
*
- * @param $newLinkArray array Associative array mapping language code to the page
+ * @param array $newLinkArray Associative array mapping language code to the page
* name
*/
public function addLanguageLinks( $newLinkArray ) {
@@ -1102,7 +1146,7 @@ class OutputPage extends ContextSource {
/**
* Reset the language links and add new language links
*
- * @param $newLinkArray array Associative array mapping language code to the page
+ * @param array $newLinkArray Associative array mapping language code to the page
* name
*/
public function setLanguageLinks( $newLinkArray ) {
@@ -1121,7 +1165,7 @@ class OutputPage extends ContextSource {
/**
* Add an array of categories, with names in the keys
*
- * @param $categories Array mapping category name => sort key
+ * @param array $categories mapping category name => sort key
*/
public function addCategoryLinks( $categories ) {
global $wgContLang;
@@ -1182,7 +1226,7 @@ class OutputPage extends ContextSource {
/**
* Reset the category links (but not the category list) and add $categories
*
- * @param $categories Array mapping category name => sort key
+ * @param array $categories mapping category name => sort key
*/
public function setCategoryLinks( $categories ) {
$this->mCategoryLinks = array();
@@ -1236,11 +1280,11 @@ class OutputPage extends ContextSource {
/**
* Show what level of JavaScript / CSS untrustworthiness is allowed on this page
* @see ResourceLoaderModule::$origin
- * @param $type String ResourceLoaderModule TYPE_ constant
+ * @param string $type ResourceLoaderModule TYPE_ constant
* @return Int ResourceLoaderModule ORIGIN_ class constant
*/
- public function getAllowedModules( $type ){
- if( $type == ResourceLoaderModule::TYPE_COMBINED ){
+ public function getAllowedModules( $type ) {
+ if( $type == ResourceLoaderModule::TYPE_COMBINED ) {
return min( array_values( $this->mAllowedModules ) );
} else {
return isset( $this->mAllowedModules[$type] )
@@ -1254,23 +1298,23 @@ class OutputPage extends ContextSource {
* @param $type String ResourceLoaderModule TYPE_ constant
* @param $level Int ResourceLoaderModule class constant
*/
- public function setAllowedModules( $type, $level ){
+ public function setAllowedModules( $type, $level ) {
$this->mAllowedModules[$type] = $level;
}
/**
- * As for setAllowedModules(), but don't inadvertantly make the page more accessible
+ * As for setAllowedModules(), but don't inadvertently make the page more accessible
* @param $type String
* @param $level Int ResourceLoaderModule class constant
*/
- public function reduceAllowedModules( $type, $level ){
- $this->mAllowedModules[$type] = min( $this->getAllowedModules($type), $level );
+ public function reduceAllowedModules( $type, $level ) {
+ $this->mAllowedModules[$type] = min( $this->getAllowedModules( $type ), $level );
}
/**
* Prepend $text to the body HTML
*
- * @param $text String: HTML
+ * @param string $text HTML
*/
public function prependHTML( $text ) {
$this->mBodytext = $text . $this->mBodytext;
@@ -1279,7 +1323,7 @@ class OutputPage extends ContextSource {
/**
* Append $text to the body HTML
*
- * @param $text String: HTML
+ * @param string $text HTML
*/
public function addHTML( $text ) {
$this->mBodytext .= $text;
@@ -1423,14 +1467,17 @@ class OutputPage extends ContextSource {
* @param $interface Boolean: is this text in the user interface language?
*/
public function addWikiText( $text, $linestart = true, $interface = true ) {
- $title = $this->getTitle(); // Work arround E_STRICT
+ $title = $this->getTitle(); // Work around E_STRICT
+ if ( !$title ) {
+ throw new MWException( 'Title is null' );
+ }
$this->addWikiTextTitle( $text, $title, $linestart, /*tidy*/false, $interface );
}
/**
* Add wikitext with a custom Title object
*
- * @param $text String: wikitext
+ * @param string $text wikitext
* @param $title Title object
* @param $linestart Boolean: is this the start of a line?
*/
@@ -1441,7 +1488,7 @@ class OutputPage extends ContextSource {
/**
* Add wikitext with a custom Title object and tidy enabled.
*
- * @param $text String: wikitext
+ * @param string $text wikitext
* @param $title Title object
* @param $linestart Boolean: is this the start of a line?
*/
@@ -1452,7 +1499,7 @@ class OutputPage extends ContextSource {
/**
* Add wikitext with tidy enabled
*
- * @param $text String: wikitext
+ * @param string $text wikitext
* @param $linestart Boolean: is this the start of a line?
*/
public function addWikiTextTidy( $text, $linestart = true ) {
@@ -1463,14 +1510,14 @@ class OutputPage extends ContextSource {
/**
* Add wikitext with a custom Title object
*
- * @param $text String: wikitext
+ * @param string $text wikitext
* @param $title Title object
* @param $linestart Boolean: is this the start of a line?
* @param $tidy Boolean: whether to use tidy
* @param $interface Boolean: whether it is an interface message
* (for example disables conversion)
*/
- public function addWikiTextTitle( $text, &$title, $linestart, $tidy = false, $interface = false ) {
+ public function addWikiTextTitle( $text, Title $title, $linestart, $tidy = false, $interface = false ) {
global $wgParser;
wfProfileIn( __METHOD__ );
@@ -1550,7 +1597,6 @@ class OutputPage extends ContextSource {
$this->addHTML( $text );
}
-
/**
* Add the output of a QuickTemplate to the output buffer
*
@@ -1571,9 +1617,10 @@ class OutputPage extends ContextSource {
* @param $interface Boolean: use interface language ($wgLang instead of
* $wgContLang) while parsing language sensitive magic
* words like GRAMMAR and PLURAL. This also disables
- * LanguageConverter.
+ * LanguageConverter.
* @param $language Language object: target language object, will override
* $interface
+ * @throws MWException
* @return String: HTML
*/
public function parse( $text, $linestart = true, $interface = false, $language = null ) {
@@ -1695,7 +1742,7 @@ class OutputPage extends ContextSource {
/**
* Add an HTTP header that will influence on the cache
*
- * @param $header String: header name
+ * @param string $header header name
* @param $option Array|null
* @todo FIXME: Document the $option parameter; it appears to be for
* X-Vary-Options but what format is acceptable?
@@ -1769,14 +1816,12 @@ class OutputPage extends ContextSource {
} else {
$aloption[] = 'string-contains=' . $variant;
- // IE and some other browsers use another form of language code
- // in their Accept-Language header, like "zh-CN" or "zh-TW".
+ // IE and some other browsers use BCP 47 standards in
+ // their Accept-Language header, like "zh-CN" or "zh-Hant".
// We should handle these too.
- $ievariant = explode( '-', $variant );
- if ( count( $ievariant ) == 2 ) {
- $ievariant[1] = strtoupper( $ievariant[1] );
- $ievariant = implode( '-', $ievariant );
- $aloption[] = 'string-contains=' . $ievariant;
+ $variantBCP47 = wfBCP47( $variant );
+ if ( $variantBCP47 !== $variant ) {
+ $aloption[] = 'string-contains=' . $variantBCP47;
}
}
}
@@ -1860,7 +1905,7 @@ class OutputPage extends ContextSource {
wfDebug( __METHOD__ . ": proxy caching with ESI; {$this->mLastModified} **\n", false );
# start with a shorter timeout for initial testing
# header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
- $response->header( 'Surrogate-Control: max-age='.$wgSquidMaxage.'+'.$this->mSquidMaxage.', content="ESI/1.0"');
+ $response->header( 'Surrogate-Control: max-age=' . $wgSquidMaxage . '+' . $this->mSquidMaxage . ', content="ESI/1.0"' );
$response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
} else {
# We'll purge the proxy cache for anons explicitly, but require end user agents
@@ -1870,7 +1915,7 @@ class OutputPage extends ContextSource {
wfDebug( __METHOD__ . ": local proxy caching; {$this->mLastModified} **\n", false );
# start with a shorter timeout for initial testing
# header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
- $response->header( 'Cache-Control: s-maxage='.$this->mSquidMaxage.', must-revalidate, max-age=0' );
+ $response->header( 'Cache-Control: s-maxage=' . $this->mSquidMaxage . ', must-revalidate, max-age=0' );
}
} else {
# We do want clients to cache if they can, but they *must* check for updates
@@ -1879,7 +1924,7 @@ class OutputPage extends ContextSource {
$response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
$response->header( "Cache-Control: private, must-revalidate, max-age=0" );
}
- if($this->mLastModified) {
+ if( $this->mLastModified ) {
$response->header( "Last-Modified: {$this->mLastModified}" );
}
} else {
@@ -1894,7 +1939,7 @@ class OutputPage extends ContextSource {
}
/**
- * Get the message associed with the HTTP response code $code
+ * Get the message associated with the HTTP response code $code
*
* @param $code Integer: status code
* @return String or null: message or null if $code is not in the list of
@@ -1903,7 +1948,7 @@ class OutputPage extends ContextSource {
* @deprecated since 1.18 Use HttpStatus::getMessage() instead.
*/
public static function getStatusMessage( $code ) {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.18' );
return HttpStatus::getMessage( $code );
}
@@ -1994,14 +2039,16 @@ class OutputPage extends ContextSource {
wfRunHooks( 'AfterFinalPageOutput', array( $this ) );
$this->sendCacheControl();
+
ob_end_flush();
+
wfProfileOut( __METHOD__ );
}
/**
* Actually output something with print().
*
- * @param $ins String: the string to output
+ * @param string $ins the string to output
*/
public function out( $ins ) {
print $ins;
@@ -2020,8 +2067,8 @@ class OutputPage extends ContextSource {
* indexing, clear the current text and redirect, set the page's title
* and optionally an custom HTML title (content of the "<title>" tag).
*
- * @param $pageTitle String|Message will be passed directly to setPageTitle()
- * @param $htmlTitle String|Message will be passed directly to setHTMLTitle();
+ * @param string|Message $pageTitle will be passed directly to setPageTitle()
+ * @param string|Message $htmlTitle will be passed directly to setHTMLTitle();
* optional, if not passed the "<title>" attribute will be
* based on $pageTitle
*/
@@ -2047,7 +2094,7 @@ class OutputPage extends ContextSource {
*
* @param $title Mixed: message key (string) for page title, or a Message object
* @param $msg Mixed: message key (string) for page text, or a Message object
- * @param $params Array: message parameters; ignored if $msg is a Message object
+ * @param array $params message parameters; ignored if $msg is a Message object
*/
public function showErrorPage( $title, $msg, $params = array() ) {
if( !$title instanceof Message ) {
@@ -2056,8 +2103,8 @@ class OutputPage extends ContextSource {
$this->prepareErrorPage( $title );
- if ( $msg instanceof Message ){
- $this->addHTML( $msg->parse() );
+ if ( $msg instanceof Message ) {
+ $this->addHTML( $msg->parseAsBlock() );
} else {
$this->addWikiMsgArray( $msg, $params );
}
@@ -2068,12 +2115,10 @@ class OutputPage extends ContextSource {
/**
* Output a standard permission error page
*
- * @param $errors Array: error message keys
- * @param $action String: action that was denied or null if unknown
+ * @param array $errors error message keys
+ * @param string $action action that was denied or null if unknown
*/
public function showPermissionsErrorPage( $errors, $action = null ) {
- global $wgGroupPermissions;
-
// For some action (read, edit, create and upload), display a "login to do this action"
// error if all of the following conditions are met:
// 1. the user is not logged in
@@ -2082,8 +2127,8 @@ class OutputPage extends ContextSource {
if ( in_array( $action, array( 'read', 'edit', 'createpage', 'createtalk', 'upload' ) )
&& $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
&& ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
- && ( ( isset( $wgGroupPermissions['user'][$action] ) && $wgGroupPermissions['user'][$action] )
- || ( isset( $wgGroupPermissions['autoconfirmed'][$action] ) && $wgGroupPermissions['autoconfirmed'][$action] ) )
+ && ( User::groupHasPermission( 'user', $action )
+ || User::groupHasPermission( 'autoconfirmed', $action ) )
) {
$displayReturnto = null;
@@ -2115,7 +2160,7 @@ class OutputPage extends ContextSource {
unset( $returntoquery['title'] );
unset( $returntoquery['returnto'] );
unset( $returntoquery['returntoquery'] );
- $query['returntoquery'] = wfArrayToCGI( $returntoquery );
+ $query['returntoquery'] = wfArrayToCgi( $returntoquery );
}
}
$loginLink = Linker::linkKnown(
@@ -2155,7 +2200,8 @@ class OutputPage extends ContextSource {
/**
* Display an error page noting that a given permission bit is required.
* @deprecated since 1.18, just throw the exception directly
- * @param $permission String: key required
+ * @param string $permission key required
+ * @throws PermissionsError
*/
public function permissionRequired( $permission ) {
throw new PermissionsError( $permission );
@@ -2173,8 +2219,8 @@ class OutputPage extends ContextSource {
/**
* Format a list of error messages
*
- * @param $errors Array of arrays returned by Title::getUserPermissionsErrors
- * @param $action String: action that was denied or null if unknown
+ * @param array $errors of arrays returned by Title::getUserPermissionsErrors
+ * @param string $action action that was denied or null if unknown
* @return String: the wikitext error-messages, formatted into a list.
*/
public function formatPermissionsErrorMessage( $errors, $action = null ) {
@@ -2226,6 +2272,7 @@ class OutputPage extends ContextSource {
* @param $protected Boolean: is this a permissions error?
* @param $reasons Array: list of reasons for this error, as returned by Title::getUserPermissionsErrors().
* @param $action String: action that was denied or null if unknown
+ * @throws ReadOnlyError
*/
public function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
$this->setRobotPolicy( 'noindex,nofollow' );
@@ -2257,7 +2304,7 @@ class OutputPage extends ContextSource {
$pageLang = $this->getTitle()->getPageLanguage();
$params = array(
- 'id' => 'wpTextbox1',
+ 'id' => 'wpTextbox1',
'name' => 'wpTextbox1',
'cols' => $this->getUser()->getOption( 'cols' ),
'rows' => $this->getUser()->getOption( 'rows' ),
@@ -2284,7 +2331,7 @@ $templates
}
/**
- * Turn off regular page output and return an error reponse
+ * Turn off regular page output and return an error response
* for when rate limiting has triggered.
*/
public function rateLimited() {
@@ -2341,13 +2388,22 @@ $templates
* Add a "return to" link pointing to a specified title
*
* @param $title Title to link
- * @param $query Array query string parameters
- * @param $text String text of the link (input is not escaped)
- */
- public function addReturnTo( $title, $query = array(), $text = null ) {
- $this->addLink( array( 'rel' => 'next', 'href' => $title->getFullURL() ) );
+ * @param array $query query string parameters
+ * @param string $text text of the link (input is not escaped)
+ * @param $options Options array to pass to Linker
+ */
+ public function addReturnTo( $title, $query = array(), $text = null, $options = array() ) {
+ if( in_array( 'http', $options ) ) {
+ $proto = PROTO_HTTP;
+ } elseif( in_array( 'https', $options ) ) {
+ $proto = PROTO_HTTPS;
+ } else {
+ $proto = PROTO_RELATIVE;
+ }
+
+ $this->addLink( array( 'rel' => 'next', 'href' => $title->getFullURL( '', false, $proto ) ) );
$link = $this->msg( 'returnto' )->rawParams(
- Linker::link( $title, $text, array(), $query ) )->escaped();
+ Linker::link( $title, $text, array(), $query, $options ) )->escaped();
$this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
}
@@ -2357,7 +2413,7 @@ $templates
*
* @param $unused
* @param $returnto Title or String to return to
- * @param $returntoquery String: query string for the return to link
+ * @param string $returntoquery query string for the return to link
*/
public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
if ( $returnto == null ) {
@@ -2451,7 +2507,7 @@ $templates
*/
private function addDefaultModules() {
global $wgIncludeLegacyJavaScript, $wgPreloadJavaScriptMwUtil, $wgUseAjax,
- $wgAjaxWatch;
+ $wgAjaxWatch, $wgResponsiveImages;
// Add base resources
$this->addModules( array(
@@ -2459,7 +2515,7 @@ $templates
'mediawiki.page.startup',
'mediawiki.page.ready',
) );
- if ( $wgIncludeLegacyJavaScript ){
+ if ( $wgIncludeLegacyJavaScript ) {
$this->addModules( 'mediawiki.legacy.wikibits' );
}
@@ -2492,6 +2548,11 @@ $templates
if ( $this->isArticle() && $this->getUser()->getOption( 'editondblclick' ) ) {
$this->addModules( 'mediawiki.action.view.dblClickEdit' );
}
+
+ // Support for high-density display images
+ if ( $wgResponsiveImages ) {
+ $this->addModules( 'mediawiki.hidpi' );
+ }
}
/**
@@ -2509,9 +2570,9 @@ $templates
/**
* TODO: Document
* @param $modules Array/string with the module name(s)
- * @param $only String ResourceLoaderModule TYPE_ class constant
+ * @param string $only ResourceLoaderModule TYPE_ class constant
* @param $useESI boolean
- * @param $extraQuery Array with extra query parameters to add to each request. array( param => value )
+ * @param array $extraQuery with extra query parameters to add to each request. array( param => value )
* @param $loadCall boolean If true, output an (asynchronous) mw.loader.load() call rather than a "<script src='...'>" tag
* @return string html "<script>" and "<style>" tags
*/
@@ -2634,7 +2695,7 @@ $templates
}
// Special handling for the user group; because users might change their stuff
// on-wiki like user pages, or user preferences; we need to find the highest
- // timestamp of these user-changable modules so we can ensure cache misses on change
+ // timestamp of these user-changeable modules so we can ensure cache misses on change
// This should NOT be done for the site group (bug 27564) because anons get that too
// and we shouldn't be putting timestamps in Squid-cached HTML
$version = null;
@@ -2682,7 +2743,7 @@ $templates
}
}
- if( $group == 'noscript' ){
+ if( $group == 'noscript' ) {
$links .= Html::rawElement( 'noscript', array(), $link ) . "\n";
} else {
$links .= $link . "\n";
@@ -2890,21 +2951,20 @@ $templates
$this->mJsConfigVars[$keys] = $value;
}
-
/**
* Get an array containing the variables to be set in mw.config in JavaScript.
*
* DO NOT CALL THIS FROM OUTSIDE OF THIS CLASS OR Skin::makeGlobalVariablesScript().
* This is only public until that function is removed. You have been warned.
*
- * Do not add things here which can be evaluated in ResourceLoaderStartupScript
+ * Do not add things here which can be evaluated in ResourceLoaderStartUpModule
* - in other words, page-independent/site-wide variables (without state).
* You will only be adding bloat to the html page and causing page caches to
* have to be purged on configuration changes.
* @return array
*/
public function getJSVars() {
- global $wgUseAjax, $wgContLang;
+ global $wgContLang;
$latestRevID = 0;
$pageID = 0;
@@ -2942,18 +3002,20 @@ $templates
implode( "\t", $digitTransTable ),
);
+ $user = $this->getUser();
+
$vars = array(
'wgCanonicalNamespace' => $nsname,
'wgCanonicalSpecialPageName' => $canonicalName,
'wgNamespaceNumber' => $title->getNamespace(),
- 'wgPageName' => $title->getPrefixedDBKey(),
+ 'wgPageName' => $title->getPrefixedDBkey(),
'wgTitle' => $title->getText(),
'wgCurRevisionId' => $latestRevID,
'wgArticleId' => $pageID,
'wgIsArticle' => $this->isArticle(),
'wgAction' => Action::getActionName( $this->getContext() ),
- 'wgUserName' => $this->getUser()->isAnon() ? null : $this->getUser()->getName(),
- 'wgUserGroups' => $this->getUser()->getEffectiveGroups(),
+ 'wgUserName' => $user->isAnon() ? null : $user->getName(),
+ 'wgUserGroups' => $user->getEffectiveGroups(),
'wgCategories' => $this->getCategories(),
'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
'wgPageContentLanguage' => $lang->getCode(),
@@ -2962,11 +3024,17 @@ $templates
'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
'wgMonthNames' => $lang->getMonthNamesArray(),
'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(),
- 'wgRelevantPageName' => $relevantTitle->getPrefixedDBKey(),
+ 'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
);
+ if ( $user->isLoggedIn() ) {
+ $vars['wgUserId'] = $user->getId();
+ $vars['wgUserEditCount'] = $user->getEditCount();
+ $userReg = wfTimestampOrNull( TS_UNIX, $user->getRegistration() );
+ $vars['wgUserRegistration'] = $userReg !== null ? ( $userReg * 1000 ) : null;
+ }
if ( $wgContLang->hasVariants() ) {
$vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
- }
+ }
foreach ( $title->getRestrictionTypes() as $type ) {
$vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
}
@@ -2974,7 +3042,7 @@ $templates
$vars['wgIsMainPage'] = true;
}
if ( $this->mRedirectedFrom ) {
- $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBKey();
+ $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey();
}
// Allow extensions to add their custom variables to the mw.config map.
@@ -3012,7 +3080,7 @@ $templates
}
/**
- * @param $addContentType bool: Whether "<meta>" specifying content type should be returned
+ * @param bool $addContentType Whether "<meta>" specifying content type should be returned
*
* @return array in format "link name or number => 'link html'".
*/
@@ -3025,6 +3093,8 @@ $templates
$tags = array();
+ $canonicalUrl = $this->mCanonicalUrl;
+
if ( $addContentType ) {
if ( $wgHtml5 ) {
# More succinct than <meta http-equiv=Content-Type>, has the
@@ -3035,7 +3105,7 @@ $templates
'http-equiv' => 'Content-Type',
'content' => "$wgMimeType; charset=UTF-8"
) );
- $tags['meta-content-style-type'] = Html::element( 'meta', array( // bug 15835
+ $tags['meta-content-style-type'] = Html::element( 'meta', array( // bug 15835
'http-equiv' => 'Content-Style-Type',
'content' => 'text/css'
) );
@@ -3064,7 +3134,7 @@ $templates
);
$tags['meta-keywords'] = Html::element( 'meta', array(
'name' => 'keywords',
- 'content' => preg_replace(
+ 'content' => preg_replace(
array_keys( $strip ),
array_values( $strip ),
implode( ',', $this->mKeywords )
@@ -3151,7 +3221,6 @@ $templates
) );
}
-
# Language variants
if ( !$wgDisableLangConversion && $wgCanonicalLanguageLinks ) {
$lang = $this->getTitle()->getPageLanguage();
@@ -3169,10 +3238,7 @@ $templates
);
}
} else {
- $tags['canonical'] = Html::element( 'link', array(
- 'rel' => 'canonical',
- 'href' => $this->getTitle()->getCanonicalUrl()
- ) );
+ $canonicalUrl = $this->getTitle()->getLocalURL();
}
}
}
@@ -3241,12 +3307,30 @@ $templates
}
}
}
+
+ # Canonical URL
+ global $wgEnableCanonicalServerLink;
+ if ( $wgEnableCanonicalServerLink ) {
+ if ( $canonicalUrl !== false ) {
+ $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL );
+ } else {
+ $reqUrl = $this->getRequest()->getRequestURL();
+ $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL );
+ }
+ }
+ if ( $canonicalUrl !== false ) {
+ $tags[] = Html::element( 'link', array(
+ 'rel' => 'canonical',
+ 'href' => $canonicalUrl
+ ) );
+ }
+
return $tags;
}
/**
* @param $unused
- * @param $addContentType bool: Whether "<meta>" specifying content type should be returned
+ * @param bool $addContentType Whether "<meta>" specifying content type should be returned
*
* @return string HTML tag links to be put in the header.
*/
@@ -3257,9 +3341,9 @@ $templates
/**
* Generate a "<link rel/>" for a feed.
*
- * @param $type String: feed type
- * @param $url String: URL to the feed
- * @param $text String: value of the "title" attribute
+ * @param string $type feed type
+ * @param string $url URL to the feed
+ * @param string $text value of the "title" attribute
* @return String: HTML fragment
*/
private function feedLink( $type, $url, $text ) {
@@ -3275,10 +3359,10 @@ $templates
* Add a local or specified stylesheet, with the given media options.
* Meant primarily for internal use...
*
- * @param $style String: URL to the file
- * @param $media String: to specify a media type, 'screen', 'printable', 'handheld' or any.
- * @param $condition String: for IE conditional comments, specifying an IE version
- * @param $dir String: set to 'rtl' or 'ltr' for direction-specific sheets
+ * @param string $style URL to the file
+ * @param string $media to specify a media type, 'screen', 'printable', 'handheld' or any.
+ * @param string $condition for IE conditional comments, specifying an IE version
+ * @param string $dir set to 'rtl' or 'ltr' for direction-specific sheets
*/
public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
$options = array();
@@ -3299,7 +3383,7 @@ $templates
/**
* Adds inline CSS styles
* @param $style_css Mixed: inline CSS
- * @param $flip String: Set to 'flip' to flip the CSS if needed
+ * @param string $flip Set to 'flip' to flip the CSS if needed
*/
public function addInlineStyle( $style_css, $flip = 'noflip' ) {
if( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
@@ -3316,8 +3400,7 @@ $templates
* @return string
*/
public function buildCssLinks() {
- global $wgUseSiteCss, $wgAllowUserCss, $wgAllowUserCssPrefs,
- $wgLang, $wgContLang;
+ global $wgUseSiteCss, $wgAllowUserCss, $wgAllowUserCssPrefs, $wgContLang;
$this->getSkin()->setupSkinUserCss( $this );
@@ -3333,7 +3416,7 @@ $templates
if ( $wgUseSiteCss ) {
$moduleStyles[] = 'site';
$moduleStyles[] = 'noscript';
- if( $this->getUser()->isLoggedIn() ){
+ if( $this->getUser()->isLoggedIn() ) {
$moduleStyles[] = 'user.groups';
}
}
@@ -3351,7 +3434,7 @@ $templates
// If needed, Janus it first. This is user-supplied CSS, so it's
// assumed to be right for the content language directionality.
$previewedCSS = $this->getRequest()->getText( 'wpTextbox1' );
- if ( $wgLang->getDir() !== $wgContLang->getDir() ) {
+ if ( $this->getLanguage()->getDir() !== $wgContLang->getDir() ) {
$previewedCSS = CSSJanus::transform( $previewedCSS, true, false );
}
$otherTags .= Html::inlineStyle( $previewedCSS );
@@ -3426,8 +3509,8 @@ $templates
/**
* Generate \<link\> tags for stylesheets
*
- * @param $style String: URL to the file
- * @param $options Array: option, can contain 'condition', 'dir', 'media'
+ * @param string $style URL to the file
+ * @param array $options option, can contain 'condition', 'dir', 'media'
* keys
* @return String: HTML fragment
*/
@@ -3468,12 +3551,16 @@ $templates
/**
* Transform "media" attribute based on request parameters
*
- * @param $media String: current value of the "media" attribute
- * @return String: modified value of the "media" attribute
+ * @param string $media current value of the "media" attribute
+ * @return String: modified value of the "media" attribute, or null to skip
+ * this stylesheet
*/
public static function transformCssMedia( $media ) {
global $wgRequest, $wgHandheldForIPhone;
+ // http://www.w3.org/TR/css3-mediaqueries/#syntax
+ $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
+
// Switch in on-screen display for media testing
$switches = array(
'printable' => 'print',
@@ -3483,8 +3570,20 @@ $templates
if( $wgRequest->getBool( $switch ) ) {
if( $media == $targetMedia ) {
$media = '';
- } elseif( $media == 'screen' ) {
- return null;
+ } elseif( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
+ // This regex will not attempt to understand a comma-separated media_query_list
+ //
+ // Example supported values for $media: 'screen', 'only screen', 'screen and (min-width: 982px)' ),
+ // Example NOT supported value for $media: '3d-glasses, screen, print and resolution > 90dpi'
+ //
+ // If it's a print request, we never want any kind of screen stylesheets
+ // If it's a handheld request (currently the only other choice with a switch),
+ // we don't want simple 'screen' but we might want screen queries that
+ // have a max-width or something, so we'll pass all others on and let the
+ // client do the query.
+ if( $targetMedia == 'print' || $media == 'screen' ) {
+ return null;
+ }
}
}
}
@@ -3557,7 +3656,6 @@ $templates
$msgSpecs = array_values( $msgSpecs );
$s = $wrap;
foreach ( $msgSpecs as $n => $spec ) {
- $options = array();
if ( is_array( $spec ) ) {
$args = $spec;
$name = array_shift( $args );
@@ -3568,7 +3666,7 @@ $templates
'1.20'
);
}
- } else {
+ } else {
$args = array();
$name = $spec;
}
@@ -3581,7 +3679,7 @@ $templates
* Include jQuery core. Use this to avoid loading it multiple times
* before we get a usable script loader.
*
- * @param $modules Array: list of jQuery modules which should be loaded
+ * @param array $modules list of jQuery modules which should be loaded
* @return Array: the list of modules which were not loaded.
* @since 1.16
* @deprecated since 1.17
diff --git a/includes/PHPVersionError.php b/includes/PHPVersionError.php
index dad71f82..7749bf1f 100644
--- a/includes/PHPVersionError.php
+++ b/includes/PHPVersionError.php
@@ -22,13 +22,13 @@
/**
* Display something vaguely comprehensible in the event of a totally unrecoverable error.
- * Does not assume access to *anything*; no globals, no autloader, no database, no localisation.
+ * Does not assume access to *anything*; no globals, no autoloader, no database, no localisation.
* Safe for PHP4 (and putting this here means that WebStart.php and GlobalSettings.php
* no longer need to be).
*
* Calling this function kills execution immediately.
*
- * @param $type String Which entry point we are protecting. One of:
+ * @param string $type Which entry point we are protecting. One of:
* - index.php
* - load.php
* - api.php
@@ -37,21 +37,28 @@
* @note Since we can't rely on anything, the minimum PHP versions and MW current
* version are hardcoded here
*/
-function wfPHPVersionError( $type ){
- $mwVersion = '1.20';
- $phpVersion = PHP_VERSION;
- $message = "MediaWiki $mwVersion requires at least PHP version 5.3.2, you are using PHP $phpVersion.";
- if( $type == 'index.php' ) {
+function wfPHPVersionError( $type ) {
+ $mwVersion = '1.21';
+ $minimumVersionPHP = '5.3.2';
+
+ $phpVersion = phpversion();
+ $protocol = isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0';
+ $message = "MediaWiki $mwVersion requires at least PHP version $minimumVersionPHP, you are using PHP $phpVersion.";
+ if ( $type == 'cli' ) {
+ $finalOutput = "You are using PHP version $phpVersion but MediaWiki $mwVersion needs PHP $minimumVersionPHP or higher. ABORTING.\n" .
+ "Check if you have a newer php executable with a different name, such as php5.\n";
+ } elseif ( $type == 'index.php' ) {
+ $pathinfo = pathinfo( $_SERVER['SCRIPT_NAME'] );
$encLogo = htmlspecialchars(
- str_replace( '//', '/', pathinfo( $_SERVER['SCRIPT_NAME'], PATHINFO_DIRNAME ) . '/'
- ) . 'skins/common/images/mediawiki.png'
+ str_replace( '//', '/', $pathinfo['dirname'] . '/' ) .
+ 'skins/common/images/mediawiki.png'
);
- header( $_SERVER['SERVER_PROTOCOL'] . ' 500 MediaWiki configuration Error', true, 500 );
+ header( "$protocol 500 MediaWiki configuration Error" );
header( 'Content-type: text/html; charset=UTF-8' );
// Don't cache error pages! They cause no end of trouble...
header( 'Cache-control: none' );
- header( 'Pragma: nocache' );
+ header( 'Pragma: no-cache' );
$finalOutput = <<<HTML
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@@ -103,9 +110,7 @@ HTML;
} else {
// So nothing thinks this is JS or CSS
$finalOutput = ( $type == 'load.php' ) ? "/* $message */" : $message;
- if( $type != 'cli' ) {
- header( $_SERVER['SERVER_PROTOCOL'] . ' 500 MediaWiki configuration Error', true, 500 );
- }
+ header( "$protocol 500 MediaWiki configuration Error" );
}
echo( "$finalOutput\n" );
die( 1 );
diff --git a/includes/Pager.php b/includes/Pager.php
index 96ba446e..9c514927 100644
--- a/includes/Pager.php
+++ b/includes/Pager.php
@@ -143,7 +143,7 @@ abstract class IndexPager extends ContextSource implements Pager {
$this->mOffset = $this->mRequest->getText( 'offset' );
# Use consistent behavior for the limit options
- $this->mDefaultLimit = intval( $this->getUser()->getOption( 'rclimit' ) );
+ $this->mDefaultLimit = $this->getUser()->getIntOption( 'rclimit' );
if ( !$this->mLimit ) {
// Don't override if a subclass calls $this->setLimit() in its constructor.
list( $this->mLimit, /* $offset */ ) = $this->mRequest->getLimitOffset();
@@ -206,13 +206,25 @@ abstract class IndexPager extends ContextSource implements Pager {
# Plus an extra row so that we can tell the "next" link should be shown
$queryLimit = $this->mLimit + 1;
+ if ( $this->mOffset == '' ) {
+ $isFirst = true;
+ } else {
+ // If there's an offset, we may or may not be at the first entry.
+ // The only way to tell is to run the query in the opposite
+ // direction see if we get a row.
+ $oldIncludeOffset = $this->mIncludeOffset;
+ $this->mIncludeOffset = !$this->mIncludeOffset;
+ $isFirst = !$this->reallyDoQuery( $this->mOffset, 1, !$descending )->numRows();
+ $this->mIncludeOffset = $oldIncludeOffset;
+ }
+
$this->mResult = $this->reallyDoQuery(
$this->mOffset,
$queryLimit,
$descending
);
- $this->extractResultInfo( $this->mOffset, $queryLimit, $this->mResult );
+ $this->extractResultInfo( $isFirst, $queryLimit, $this->mResult );
$this->mQueryDone = true;
$this->preprocessResults( $this->mResult );
@@ -269,11 +281,12 @@ abstract class IndexPager extends ContextSource implements Pager {
* Extract some useful data from the result object for use by
* the navigation bar, put it into $this
*
- * @param $offset String: index offset, inclusive
+ * @param $isFirst bool: False if there are rows before those fetched (i.e.
+ * if a "previous" link would make sense)
* @param $limit Integer: exact query limit
* @param $res ResultWrapper
*/
- function extractResultInfo( $offset, $limit, ResultWrapper $res ) {
+ function extractResultInfo( $isFirst, $limit, ResultWrapper $res ) {
$numRows = $res->numRows();
if ( $numRows ) {
# Remove any table prefix from index field
@@ -287,8 +300,7 @@ abstract class IndexPager extends ContextSource implements Pager {
if ( $numRows > $this->mLimit && $numRows > 1 ) {
$res->seek( $numRows - 1 );
$this->mPastTheEndRow = $res->fetchObject();
- $indexField = $this->mIndexField;
- $this->mPastTheEndIndex = $this->mPastTheEndRow->$indexField;
+ $this->mPastTheEndIndex = $this->mPastTheEndRow->$indexColumn;
$res->seek( $numRows - 2 );
$row = $res->fetchRow();
$lastIndex = $row[$indexColumn];
@@ -312,11 +324,11 @@ abstract class IndexPager extends ContextSource implements Pager {
if ( $this->mIsBackwards ) {
$this->mIsFirst = ( $numRows < $limit );
- $this->mIsLast = ( $offset == '' );
+ $this->mIsLast = $isFirst;
$this->mLastShown = $firstIndex;
$this->mFirstShown = $lastIndex;
} else {
- $this->mIsFirst = ( $offset == '' );
+ $this->mIsFirst = $isFirst;
$this->mIsLast = ( $numRows < $limit );
$this->mLastShown = $lastIndex;
$this->mFirstShown = $firstIndex;
@@ -336,7 +348,7 @@ abstract class IndexPager extends ContextSource implements Pager {
* Do a query with specified parameters, rather than using the object
* context
*
- * @param $offset String: index offset, inclusive
+ * @param string $offset index offset, inclusive
* @param $limit Integer: exact query limit
* @param $descending Boolean: query direction, false for ascending, true for descending
* @return ResultWrapper
@@ -349,7 +361,7 @@ abstract class IndexPager extends ContextSource implements Pager {
/**
* Build variables to use by the database wrapper.
*
- * @param $offset String: index offset, inclusive
+ * @param string $offset index offset, inclusive
* @param $limit Integer: exact query limit
* @param $descending Boolean: query direction, false for ascending, true for descending
* @return array
@@ -432,9 +444,9 @@ abstract class IndexPager extends ContextSource implements Pager {
/**
* Make a self-link
*
- * @param $text String: text displayed on the link
- * @param $query Array: associative array of paramter to be in the query string
- * @param $type String: value of the "rel" attribute
+ * @param string $text text displayed on the link
+ * @param array $query associative array of parameter to be in the query string
+ * @param string $type value of the "rel" attribute
*
* @return String: HTML fragment
*/
@@ -689,8 +701,8 @@ abstract class IndexPager extends ContextSource implements Pager {
protected function getExtraSortFields() { return array(); }
/**
- * Return the default sorting direction: false for ascending, true for de-
- * scending. You can also have an associative array of ordertype => dir,
+ * Return the default sorting direction: false for ascending, true for
+ * descending. You can also have an associative array of ordertype => dir,
* if multiple order types are supported. In this case getIndexField()
* must return an array, and the keys of that must exactly match the keys
* of this.
@@ -710,7 +722,6 @@ abstract class IndexPager extends ContextSource implements Pager {
protected function getDefaultDirections() { return false; }
}
-
/**
* IndexPager with an alphabetic list and a formatted navigation bar
* @ingroup Pager
@@ -786,8 +797,8 @@ abstract class AlphabeticPager extends IndexPager {
/**
* If this supports multiple order type messages, give the message key for
- * enabling each one in getNavigationBar. The return type is an associa-
- * tive array whose keys must exactly match the keys of the array returned
+ * enabling each one in getNavigationBar. The return type is an associative
+ * array whose keys must exactly match the keys of the array returned
* by getIndexField(), and whose values are message keys.
*
* @return Array
@@ -867,7 +878,7 @@ abstract class ReverseChronologicalPager extends IndexPager {
if ( $this->mMonth ) {
$month = $this->mMonth + 1;
// For December, we want January 1 of the next year
- if ($month > 12) {
+ if ( $month > 12 ) {
$month = 1;
$year++;
}
@@ -1049,8 +1060,8 @@ abstract class TablePager extends IndexPager {
*
* @protected
*
- * @param $field String The column
- * @param $value String The cell contents
+ * @param string $field The column
+ * @param string $value The cell contents
* @return Array of attr => value
*/
function getCellAttrs( $field, $value ) {
@@ -1161,7 +1172,7 @@ abstract class TablePager extends IndexPager {
# The pair is either $index => $limit, in which case the $value
# will be numeric, or $limit => $text, in which case the $value
# will be a string.
- if( is_int( $value ) ){
+ if( is_int( $value ) ) {
$limit = $value;
$text = $this->getLanguage()->formatNum( $limit );
} else {
@@ -1179,7 +1190,7 @@ abstract class TablePager extends IndexPager {
* Resubmits all defined elements of the query string, except for a
* blacklist, passed in the $blacklist parameter.
*
- * @param $blacklist Array parameters from the request query which should not be resubmitted
+ * @param array $blacklist parameters from the request query which should not be resubmitted
* @return String: HTML fragment
*/
function getHiddenFields( $blacklist = array() ) {
@@ -1245,8 +1256,8 @@ abstract class TablePager extends IndexPager {
*
* @protected
*
- * @param $name String: the database field name
- * @param $value String: the value retrieved from the database
+ * @param string $name the database field name
+ * @param string $value the value retrieved from the database
*/
abstract function formatValue( $name, $value );
diff --git a/includes/PathRouter.php b/includes/PathRouter.php
index 2dbc7ec0..fc891bb3 100644
--- a/includes/PathRouter.php
+++ b/includes/PathRouter.php
@@ -153,9 +153,9 @@ class PathRouter {
/**
* Add a new path pattern to the path router
*
- * @param $path string|array The path pattern to add
- * @param $params array The params for this path pattern
- * @param $options array The options for this path pattern
+ * @param string|array $path The path pattern to add
+ * @param array $params The params for this path pattern
+ * @param array $options The options for this path pattern
*/
public function add( $path, $params = array(), $options = array() ) {
if ( is_array( $path ) ) {
@@ -232,7 +232,7 @@ class PathRouter {
/**
* Parse a path and return the query matches for the path
*
- * @param $path string The path to parse
+ * @param string $path The path to parse
* @return Array The array of matches for the path
*/
public function parse( $path ) {
@@ -293,7 +293,7 @@ class PathRouter {
foreach ( $m as $matchKey => $matchValue ) {
if ( preg_match( '/^par\d+$/u', $matchKey ) ) {
$n = intval( substr( $matchKey, 3 ) );
- $data['$'.$n] = rawurldecode( $matchValue );
+ $data['$' . $n] = rawurldecode( $matchValue );
}
}
// If present give our $data array a $key as well
diff --git a/includes/PoolCounter.php b/includes/PoolCounter.php
index 452dbc54..2ebef04e 100644
--- a/includes/PoolCounter.php
+++ b/includes/PoolCounter.php
@@ -42,15 +42,15 @@
abstract class PoolCounter {
/* Return codes */
- const LOCKED = 1; /* Lock acquired */
+ const LOCKED = 1; /* Lock acquired */
const RELEASED = 2; /* Lock released */
- const DONE = 3; /* Another worker did the work for you */
+ const DONE = 3; /* Another worker did the work for you */
- const ERROR = -1; /* Indeterminate error */
+ const ERROR = -1; /* Indeterminate error */
const NOT_LOCKED = -2; /* Called release() with no lock held */
const QUEUE_FULL = -3; /* There are already maxqueue workers on this lock */
- const TIMEOUT = -4; /* Timeout exceeded */
- const LOCK_HELD = -5; /* Cannot acquire another lock while you have one lock held */
+ const TIMEOUT = -4; /* Timeout exceeded */
+ const LOCK_HELD = -5; /* Cannot acquire another lock while you have one lock held */
/**
* I want to do this task and I need to do it myself.
@@ -107,9 +107,9 @@ abstract class PoolCounter {
protected function __construct( $conf, $type, $key ) {
$this->key = $key;
- $this->workers = $conf['workers'];
+ $this->workers = $conf['workers'];
$this->maxqueue = $conf['maxqueue'];
- $this->timeout = $conf['timeout'];
+ $this->timeout = $conf['timeout'];
}
}
diff --git a/includes/Preferences.php b/includes/Preferences.php
index db231573..56dba05e 100644
--- a/includes/Preferences.php
+++ b/includes/Preferences.php
@@ -1,6 +1,6 @@
<?php
/**
- * Form to edit user perferences.
+ * Form to edit user preferences.
*
* 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
@@ -129,7 +129,7 @@ class Preferences {
static function getOptionFromUser( $name, $info, $user ) {
$val = $user->getOption( $name );
- // Handling for array-type preferences
+ // Handling for multiselect preferences
if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
( isset( $info['class'] ) && $info['class'] == 'HTMLMultiSelectField' ) ) {
$options = HTMLFormField::flattenOptions( $info['options'] );
@@ -143,6 +143,23 @@ class Preferences {
}
}
+ // Handling for checkmatrix preferences
+ if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
+ ( isset( $info['class'] ) && $info['class'] == 'HTMLCheckMatrix' ) ) {
+ $columns = HTMLFormField::flattenOptions( $info['columns'] );
+ $rows = HTMLFormField::flattenOptions( $info['rows'] );
+ $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
+ $val = array();
+
+ foreach ( $columns as $column ) {
+ foreach ( $rows as $row ) {
+ if ( $user->getOption( "$prefix-$column-$row" ) ) {
+ $val[] = "$column-$row";
+ }
+ }
+ }
+ }
+
return $val;
}
@@ -158,18 +175,21 @@ class Preferences {
$wgEnableEmail, $wgEmailConfirmToEdit, $wgEnableUserEmail, $wgEmailAuthentication,
$wgEnotifWatchlist, $wgEnotifUserTalk, $wgEnotifRevealEditorAddress;
+ // retrieving user name for GENDER and misc.
+ $userName = $user->getName();
+
## User info #####################################
// Information panel
$defaultPreferences['username'] = array(
'type' => 'info',
- 'label-message' => 'username',
- 'default' => $user->getName(),
+ 'label-message' => array( 'username', $userName ),
+ 'default' => $userName,
'section' => 'personal/info',
);
$defaultPreferences['userid'] = array(
'type' => 'info',
- 'label-message' => 'uid',
+ 'label-message' => array( 'uid', $userName ),
'default' => $user->getId(),
'section' => 'personal/info',
);
@@ -182,10 +202,10 @@ class Preferences {
// Skip the default * group, seems useless here
continue;
}
- $groupName = User::getGroupName( $ueg );
+ $groupName = User::getGroupName( $ueg );
$userGroups[] = User::makeGroupLinkHTML( $ueg, $groupName );
- $memberName = User::getGroupMember( $ueg, $user->getName() );
+ $memberName = User::getGroupMember( $ueg, $userName );
$userMembers[] = User::makeGroupLinkHTML( $ueg, $memberName );
}
asort( $userGroups );
@@ -196,7 +216,7 @@ class Preferences {
$defaultPreferences['usergroups'] = array(
'type' => 'info',
'label' => $context->msg( 'prefs-memberingroups' )->numParams(
- count( $userGroups ) )->parse(),
+ count( $userGroups ) )->params( $userName )->parse(),
'default' => $context->msg( 'prefs-memberingroups-type',
$lang->commaList( $userGroups ),
$lang->commaList( $userMembers )
@@ -252,7 +272,7 @@ class Preferences {
if ( $wgAuth->allowPasswordChange() ) {
$link = Linker::link( SpecialPage::getTitleFor( 'ChangePassword' ),
$context->msg( 'prefs-resetpass' )->escaped(), array(),
- array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' ) ) );
+ array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ) );
$defaultPreferences['password'] = array(
'type' => 'info',
@@ -356,7 +376,7 @@ class Preferences {
if ( $wgEnableEmail ) {
$helpMessages[] = $wgEmailConfirmToEdit
? 'prefs-help-email-required'
- : 'prefs-help-email' ;
+ : 'prefs-help-email';
if( $wgEnableUserEmail ) {
// additional messages when users can send email to each other
@@ -367,7 +387,7 @@ class Preferences {
SpecialPage::getTitleFor( 'ChangeEmail' ),
$context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->escaped(),
array(),
- array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' ) ) );
+ array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ) );
$emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : '';
if ( $wgAuth->allowPropChange( 'emailaddress' ) ) {
@@ -377,7 +397,6 @@ class Preferences {
);
}
-
$defaultPreferences['emailaddress'] = array(
'type' => 'info',
'raw' => true,
@@ -507,14 +526,15 @@ class Preferences {
# be nice to somehow merge this back in there to avoid redundancy.
if ( $wgAllowUserCss || $wgAllowUserJs ) {
$linkTools = array();
+ $userName = $user->getName();
if ( $wgAllowUserCss ) {
- $cssPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/common.css' );
+ $cssPage = Title::makeTitleSafe( NS_USER, $userName . '/common.css' );
$linkTools[] = Linker::link( $cssPage, $context->msg( 'prefs-custom-css' )->escaped() );
}
if ( $wgAllowUserJs ) {
- $jsPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/common.js' );
+ $jsPage = Title::makeTitleSafe( NS_USER, $userName . '/common.js' );
$linkTools[] = Linker::link( $jsPage, $context->msg( 'prefs-custom-js' )->escaped() );
}
@@ -805,7 +825,6 @@ class Preferences {
'label-message' => 'tog-forceeditsummary',
);
-
$defaultPreferences['uselivepreview'] = array(
'type' => 'toggle',
'section' => 'editing/advancedediting',
@@ -879,7 +898,7 @@ class Preferences {
global $wgUseRCPatrol, $wgEnableAPI, $wgRCMaxAge;
$watchlistdaysMax = ceil( $wgRCMaxAge / ( 3600 * 24 ) );
-
+
## Watchlist #####################################
$defaultPreferences['watchlistdays'] = array(
'type' => 'float',
@@ -887,7 +906,7 @@ class Preferences {
'max' => $watchlistdaysMax,
'section' => 'watchlist/displaywatchlist',
'help' => $context->msg( 'prefs-watchlist-days-max' )->numParams(
- $watchlistdaysMax )->text(),
+ $watchlistdaysMax )->text(),
'label-message' => 'prefs-watchlist-days',
);
$defaultPreferences['wllimit'] = array(
@@ -988,7 +1007,6 @@ class Preferences {
'min' => 0,
);
-
if ( $wgVectorUseSimpleSearch ) {
$defaultPreferences['vector-simplesearch'] = array(
'type' => 'toggle',
@@ -1009,27 +1027,17 @@ class Preferences {
'section' => 'searchoptions/advancedsearchoptions',
);
- $nsOptions = array();
-
- foreach ( $wgContLang->getNamespaces() as $ns => $name ) {
- if ( $ns < 0 ) {
- continue;
- }
-
- $displayNs = str_replace( '_', ' ', $name );
-
- if ( !$displayNs ) {
- $displayNs = $context->msg( 'blanknamespace' )->text();
- }
-
- $displayNs = htmlspecialchars( $displayNs );
- $nsOptions[$displayNs] = $ns;
+ $nsOptions = $wgContLang->getFormattedNamespaces();
+ $nsOptions[0] = $context->msg( 'blanknamespace' )->text();
+ foreach ( $nsOptions as $ns => $name ) {
+ if ( $ns < 0 )
+ unset( $nsOptions[$ns] );
}
$defaultPreferences['searchnamespaces'] = array(
'type' => 'multiselect',
'label-message' => 'defaultns',
- 'options' => $nsOptions,
+ 'options' => array_flip( $nsOptions ),
'section' => 'searchoptions/advancedsearchoptions',
'prefix' => 'searchNs',
);
@@ -1236,7 +1244,7 @@ class Preferences {
* @param $user User
* @param $context IContextSource
* @param $formClass string
- * @param $remove Array: array of items to remove
+ * @param array $remove array of items to remove
* @return HtmlForm
*/
static function getFormObject( $user, IContextSource $context, $formClass = 'PreferencesForm', array $remove = array() ) {
@@ -1339,7 +1347,7 @@ class Preferences {
* @param $alldata
* @return int
*/
- static function filterIntval( $value, $alldata ){
+ static function filterIntval( $value, $alldata ) {
return intval( $value );
}
@@ -1380,7 +1388,7 @@ class Preferences {
* @return bool|Status|string
*/
static function tryFormSubmit( $formData, $form, $entryPoint = 'internal' ) {
- global $wgHiddenPrefs;
+ global $wgHiddenPrefs, $wgAuth;
$user = $form->getModifiedUser();
$result = true;
@@ -1413,15 +1421,14 @@ class Preferences {
# via $wgHiddenPrefs, we don't want to destroy that setting in case the preference
# is subsequently re-enabled
# TODO: maintenance script to actually delete these
- foreach( $wgHiddenPrefs as $pref ){
+ foreach( $wgHiddenPrefs as $pref ) {
# If the user has not set a non-default value here, the default will be returned
# and subsequently discarded
$formData[$pref] = $user->getOption( $pref, null, true );
}
- // Keeps old preferences from interfering due to back-compat
- // code, etc.
- $user->resetOptions();
+ // Keep old preferences from interfering due to back-compat code, etc.
+ $user->resetOptions( 'unused', $form->getContext() );
foreach ( $formData as $key => $value ) {
$user->setOption( $key, $value );
@@ -1429,6 +1436,8 @@ class Preferences {
$user->saveSettings();
+ $wgAuth->updateExternalDB( $user );
+
return $result;
}
@@ -1464,7 +1473,7 @@ class Preferences {
*
* @deprecated in 1.20; use User::setEmailWithConfirmation() instead.
* @param $user User
- * @param $newaddr string New email address
+ * @param string $newaddr New email address
* @return Array (true on success or Status on failure, info string)
*/
public static function trySetUserEmail( User $user, $newaddr ) {
@@ -1565,10 +1574,11 @@ class PreferencesForm extends HTMLForm {
* @return array
*/
function filterDataForSubmit( $data ) {
- // Support for separating MultiSelect preferences into multiple preferences
+ // Support for separating multi-option preferences into multiple preferences
// Due to lack of array support.
foreach ( $this->mFlatFields as $fieldname => $field ) {
$info = $field->mParams;
+
if ( $field instanceof HTMLMultiSelectField ) {
$options = HTMLFormField::flattenOptions( $info['options'] );
$prefix = isset( $info['prefix'] ) ? $info['prefix'] : $fieldname;
@@ -1578,6 +1588,23 @@ class PreferencesForm extends HTMLForm {
}
unset( $data[$fieldname] );
+
+ } elseif ( $field instanceof HTMLCheckMatrix ) {
+ $columns = HTMLFormField::flattenOptions( $info['columns'] );
+ $rows = HTMLFormField::flattenOptions( $info['rows'] );
+ $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $fieldname;
+ foreach ( $columns as $column ) {
+ foreach ( $rows as $row ) {
+ // Make sure option hasn't been removed
+ if ( !isset( $info['remove-options'] )
+ || !in_array( "$column-$row", $info['remove-options'] ) )
+ {
+ $data["$prefix-$column-$row"] = in_array( "$column-$row", $data[$fieldname] );
+ }
+ }
+ }
+
+ unset( $data[$fieldname] );
}
}
diff --git a/includes/PrefixSearch.php b/includes/PrefixSearch.php
index 5d4b35c1..d37d9e80 100644
--- a/includes/PrefixSearch.php
+++ b/includes/PrefixSearch.php
@@ -32,7 +32,7 @@ class PrefixSearch {
*
* @param $search String
* @param $limit Integer
- * @param $namespaces Array: used if query is not explicitely prefixed
+ * @param array $namespaces used if query is not explicitly prefixed
* @return Array of strings
*/
public static function titleSearch( $search, $limit, $namespaces = array() ) {
@@ -45,7 +45,7 @@ class PrefixSearch {
// Find a Title which is not an interwiki and is in NS_MAIN
$title = Title::newFromText( $search );
if( $title && $title->getInterwiki() == '' ) {
- $ns = array($title->getNamespace());
+ $ns = array( $title->getNamespace() );
if( $ns[0] == NS_MAIN ) {
$ns = $namespaces; // no explicit prefix, use default namespaces
}
@@ -91,7 +91,7 @@ class PrefixSearch {
/**
* Prefix search special-case for Special: namespace.
*
- * @param $search String: term
+ * @param string $search term
* @param $limit Integer: max number of items to return
* @return Array
*/
@@ -147,8 +147,8 @@ class PrefixSearch {
* be automatically capitalized by Title::secureAndSpit()
* later on depending on $wgCapitalLinks)
*
- * @param $namespaces Array: namespaces to search in
- * @param $search String: term
+ * @param array $namespaces namespaces to search in
+ * @param string $search term
* @param $limit Integer: max number of items to return
* @return Array of title strings
*/
diff --git a/includes/ProtectionForm.php b/includes/ProtectionForm.php
index ce0e36b0..d7b88400 100644
--- a/includes/ProtectionForm.php
+++ b/includes/ProtectionForm.php
@@ -63,7 +63,7 @@ class ProtectionForm {
$this->mArticle = $article;
$this->mTitle = $article->getTitle();
$this->mApplicableTypes = $this->mTitle->getRestrictionTypes();
-
+
// Check if the form should be disabled.
// If it is, the form will be available in read-only to show levels.
$this->mPermErrors = $this->mTitle->getUserPermissionsErrors( 'protect', $wgUser );
@@ -139,7 +139,7 @@ class ProtectionForm {
if( !$wgUser->isAllowedAny( 'protect', 'editprotected' ) )
continue;
} else {
- if( !$wgUser->isAllowed($val) )
+ if( !$wgUser->isAllowed( $val ) )
continue;
}
$this->mRestrictions[$action] = $val;
@@ -201,12 +201,13 @@ class ProtectionForm {
/**
* Show the input form with optional error message
*
- * @param $err String: error message or null if there's no error
+ * @param string $err error message or null if there's no error
*/
function show( $err = null ) {
global $wgOut;
$wgOut->setRobotPolicy( 'noindex,nofollow' );
+ $wgOut->addBacklinkSubtitle( $this->mTitle );
if ( is_array( $err ) ) {
$wgOut->wrapWikiMsg( "<p class='error'>\n$1\n</p>\n", $err );
@@ -214,15 +215,27 @@ class ProtectionForm {
$wgOut->addHTML( "<p class='error'>{$err}</p>\n" );
}
+ if ( $this->mTitle->getRestrictionTypes() === array() ) {
+ // No restriction types available for the current title
+ // this might happen if an extension alters the available types
+ $wgOut->setPageTitle( wfMessage( 'protect-norestrictiontypes-title', $this->mTitle->getPrefixedText() ) );
+ $wgOut->addWikiText( wfMessage( 'protect-norestrictiontypes-text' )->text() );
+
+ // Show the log in case protection was possible once
+ $this->showLogExtract( $wgOut );
+ // return as there isn't anything else we can do
+ return;
+ }
+
list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
- if ( $cascadeSources && count($cascadeSources) > 0 ) {
+ if ( $cascadeSources && count( $cascadeSources ) > 0 ) {
$titles = '';
foreach ( $cascadeSources as $title ) {
$titles .= '* [[:' . $title->getPrefixedText() . "]]\n";
}
- $wgOut->wrapWikiMsg( "<div id=\"mw-protect-cascadeon\">\n$1\n" . $titles . "</div>", array( 'protect-cascadeon', count($cascadeSources) ) );
+ $wgOut->wrapWikiMsg( "<div id=\"mw-protect-cascadeon\">\n$1\n" . $titles . "</div>", array( 'protect-cascadeon', count( $cascadeSources ) ) );
}
# Show an appropriate message if the user isn't allowed or able to change
@@ -236,7 +249,6 @@ class ProtectionForm {
wfEscapeWikiText( $this->mTitle->getPrefixedText() ) );
}
- $wgOut->addBacklinkSubtitle( $this->mTitle );
$wgOut->addHTML( $this->buildForm() );
$this->showLogExtract( $wgOut );
}
@@ -272,7 +284,7 @@ class ProtectionForm {
$expiry = array();
foreach( $this->mApplicableTypes as $action ) {
$expiry[$action] = $this->getExpiry( $action );
- if( empty($this->mRestrictions[$action]) )
+ if( empty( $this->mRestrictions[$action] ) )
continue; // unprotected
if ( !$expiry[$action] ) {
$this->show( array( 'protect_expiry_invalid' ) );
@@ -286,12 +298,10 @@ class ProtectionForm {
# They shouldn't be able to do this anyway, but just to make sure, ensure that cascading restrictions aren't being applied
# to a semi-protected page.
- global $wgGroupPermissions;
-
$edit_restriction = isset( $this->mRestrictions['edit'] ) ? $this->mRestrictions['edit'] : '';
$this->mCascade = $wgRequest->getBool( 'mwProtect-cascade' );
- if ($this->mCascade && ($edit_restriction != 'protect') &&
- !(isset($wgGroupPermissions[$edit_restriction]['protect']) && $wgGroupPermissions[$edit_restriction]['protect'] ) )
+ if ( $this->mCascade && ($edit_restriction != 'protect') &&
+ !User::groupHasPermission( $edit_restriction, 'protect' ) )
$this->mCascade = false;
$status = $this->mArticle->doUpdateRestrictions( $this->mRestrictions, $expiry, $this->mCascade, $reasonstr, $wgUser );
@@ -402,14 +412,14 @@ class ProtectionForm {
wfMessage( 'protect-othertime-op' )->text(),
"othertime"
) . "\n";
- foreach( explode(',', $scExpiryOptions) as $option ) {
- if ( strpos($option, ":") === false ) {
+ foreach( explode( ',', $scExpiryOptions ) as $option ) {
+ if ( strpos( $option, ":" ) === false ) {
$show = $value = $option;
} else {
- list($show, $value) = explode(":", $option);
+ list( $show, $value ) = explode( ":", $option );
}
- $show = htmlspecialchars($show);
- $value = htmlspecialchars($value);
+ $show = htmlspecialchars( $show );
+ $value = htmlspecialchars( $value );
$expiryFormOptions .= Xml::option( $show, $value, $this->mExpirySelection[$action] === $value ) . "\n";
}
# Add expiry dropdown
@@ -448,7 +458,7 @@ class ProtectionForm {
"</td></tr>";
}
# Give extensions a chance to add items to the form
- wfRunHooks( 'ProtectionForm::buildForm', array($this->mArticle,&$out) );
+ wfRunHooks( 'ProtectionForm::buildForm', array( $this->mArticle, &$out ) );
$out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
@@ -472,7 +482,7 @@ class ProtectionForm {
# Add manual and custom reason field/selects as well as submit
if( !$this->disabled ) {
- $out .= Xml::openElement( 'table', array( 'id' => 'mw-protect-table3' ) ) .
+ $out .= Xml::openElement( 'table', array( 'id' => 'mw-protect-table3' ) ) .
Xml::openElement( 'tbody' );
$out .= "
<tr>
@@ -503,7 +513,7 @@ class ProtectionForm {
<td class='mw-input'>" .
Xml::checkLabel( wfMessage( 'watchthis' )->text(),
'mwProtectWatch', 'mwProtectWatch',
- $this->mTitle->userIsWatching() || $wgUser->getOption( 'watchdefault' ) ) .
+ $wgUser->isWatched( $this->mTitle ) || $wgUser->getOption( 'watchdefault' ) ) .
"</td>
</tr>";
}
@@ -544,8 +554,8 @@ class ProtectionForm {
/**
* Build protection level selector
*
- * @param $action String: action to protect
- * @param $selected String: current protection level
+ * @param string $action action to protect
+ * @param string $selected current protection level
* @return String: HTML fragment
*/
function buildSelector( $action, $selected ) {
@@ -559,7 +569,7 @@ class ProtectionForm {
if( !$wgUser->isAllowedAny( 'protect', 'editprotected' ) && !$this->disabled )
continue;
} else {
- if( !$wgUser->isAllowed($key) && !$this->disabled )
+ if( !$wgUser->isAllowed( $key ) && !$this->disabled )
continue;
}
$levels[] = $key;
@@ -584,7 +594,7 @@ class ProtectionForm {
/**
* Prepare the label for a protection selector option
*
- * @param $permission String: permission required
+ * @param string $permission permission required
* @return String
*/
private function getOptionLabel( $permission ) {
@@ -600,11 +610,11 @@ class ProtectionForm {
}
function buildCleanupScript() {
- global $wgRestrictionLevels, $wgGroupPermissions, $wgOut;
+ global $wgRestrictionLevels, $wgOut;
$cascadeableLevels = array();
foreach( $wgRestrictionLevels as $key ) {
- if ( ( isset( $wgGroupPermissions[$key]['protect'] ) && $wgGroupPermissions[$key]['protect'] )
+ if ( User::groupHasPermission( $key, 'protect' )
|| $key == 'protect'
) {
$cascadeableLevels[] = $key;
@@ -634,6 +644,6 @@ class ProtectionForm {
$out->addHTML( Xml::element( 'h2', null, $protectLogPage->getName()->text() ) );
LogEventsList::showLogExtract( $out, 'protect', $this->mTitle );
# Let extensions add other relevant log extracts
- wfRunHooks( 'ProtectionForm::showLogExtract', array($this->mArticle,$out) );
+ wfRunHooks( 'ProtectionForm::showLogExtract', array( $this->mArticle, $out ) );
}
}
diff --git a/includes/ProxyTools.php b/includes/ProxyTools.php
index 349789fe..b54a9a35 100644
--- a/includes/ProxyTools.php
+++ b/includes/ProxyTools.php
@@ -60,8 +60,8 @@ function wfGetIP() {
}
/**
- * Checks if an IP is a trusted proxy providor.
- * Useful to tell if X-Fowarded-For data is possibly bogus.
+ * Checks if an IP is a trusted proxy provider.
+ * Useful to tell if X-Forwarded-For data is possibly bogus.
* Squid cache servers for the site are whitelisted.
*
* @param $ip String
@@ -109,7 +109,7 @@ function wfProxyCheck() {
if ( !$skip ) {
$title = SpecialPage::getTitleFor( 'Blockme' );
$iphash = md5( $ip . $wgProxyKey );
- $url = wfExpandUrl( $title->getFullURL( 'ip='.$iphash ), PROTO_HTTP );
+ $url = wfExpandUrl( $title->getFullURL( 'ip=' . $iphash ), PROTO_HTTP );
foreach ( $wgProxyPorts as $port ) {
$params = implode( ' ', array(
diff --git a/includes/QueryPage.php b/includes/QueryPage.php
index ac559dc5..e1f24fa9 100644
--- a/includes/QueryPage.php
+++ b/includes/QueryPage.php
@@ -72,7 +72,6 @@ global $wgDisableCounters;
if ( !$wgDisableCounters )
$wgQueryPages[] = array( 'PopularPagesPage', 'Popularpages' );
-
/**
* This is a class for doing query pages; since they're almost all the same,
* we factor out some of the functionality into a superclass, and let
@@ -150,7 +149,8 @@ abstract class QueryPage extends SpecialPage {
/**
* For back-compat, subclasses may return a raw SQL query here, as a string.
- * This is stronly deprecated; getQueryInfo() should be overridden instead.
+ * This is strongly deprecated; getQueryInfo() should be overridden instead.
+ * @throws MWException
* @return string
*/
function getSQL() {
@@ -228,7 +228,7 @@ abstract class QueryPage extends SpecialPage {
}
/**
- * Sometime we dont want to build rss / atom feeds.
+ * Sometime we don't want to build rss / atom feeds.
*
* @return Boolean
*/
@@ -301,55 +301,51 @@ abstract class QueryPage extends SpecialPage {
return false;
}
- if ( $ignoreErrors ) {
- $ignoreW = $dbw->ignoreErrors( true );
- $ignoreR = $dbr->ignoreErrors( true );
- }
-
- # Clear out any old cached data
- $dbw->delete( 'querycache', array( 'qc_type' => $this->getName() ), $fname );
- # Do query
- $res = $this->reallyDoQuery( $limit, false );
- $num = false;
- if ( $res ) {
- $num = $res->numRows();
- # Fetch results
- $vals = array();
- while ( $res && $row = $dbr->fetchObject( $res ) ) {
- if ( isset( $row->value ) ) {
- if ( $this->usesTimestamps() ) {
- $value = wfTimestamp( TS_UNIX,
- $row->value );
+ try {
+ # Clear out any old cached data
+ $dbw->delete( 'querycache', array( 'qc_type' => $this->getName() ), $fname );
+ # Do query
+ $res = $this->reallyDoQuery( $limit, false );
+ $num = false;
+ if ( $res ) {
+ $num = $res->numRows();
+ # Fetch results
+ $vals = array();
+ while ( $res && $row = $dbr->fetchObject( $res ) ) {
+ if ( isset( $row->value ) ) {
+ if ( $this->usesTimestamps() ) {
+ $value = wfTimestamp( TS_UNIX,
+ $row->value );
+ } else {
+ $value = intval( $row->value ); // @bug 14414
+ }
} else {
- $value = intval( $row->value ); // @bug 14414
+ $value = 0;
}
- } else {
- $value = 0;
- }
- $vals[] = array( 'qc_type' => $this->getName(),
- 'qc_namespace' => $row->namespace,
- 'qc_title' => $row->title,
- 'qc_value' => $value );
- }
+ $vals[] = array( 'qc_type' => $this->getName(),
+ 'qc_namespace' => $row->namespace,
+ 'qc_title' => $row->title,
+ 'qc_value' => $value );
+ }
- # Save results into the querycache table on the master
- if ( count( $vals ) ) {
- if ( !$dbw->insert( 'querycache', $vals, __METHOD__ ) ) {
- // Set result to false to indicate error
- $num = false;
+ # Save results into the querycache table on the master
+ if ( count( $vals ) ) {
+ $dbw->insert( 'querycache', $vals, __METHOD__ );
}
+ # Update the querycache_info record for the page
+ $dbw->delete( 'querycache_info', array( 'qci_type' => $this->getName() ), $fname );
+ $dbw->insert( 'querycache_info',
+ array( 'qci_type' => $this->getName(), 'qci_timestamp' => $dbw->timestamp() ),
+ $fname );
}
- if ( $ignoreErrors ) {
- $dbw->ignoreErrors( $ignoreW );
- $dbr->ignoreErrors( $ignoreR );
+ } catch ( DBError $e ) {
+ if ( !$ignoreErrors ) {
+ throw $e; // report query error
}
-
- # Update the querycache_info record for the page
- $dbw->delete( 'querycache_info', array( 'qci_type' => $this->getName() ), $fname );
- $dbw->insert( 'querycache_info', array( 'qci_type' => $this->getName(), 'qci_timestamp' => $dbw->timestamp() ), $fname );
-
+ $num = false; // set result to false to indicate error
}
+
return $num;
}
@@ -651,7 +647,7 @@ abstract class QueryPage extends SpecialPage {
if ( !$wgFeed ) {
$this->getOutput()->addWikiMsg( 'feed-unavailable' );
- return;
+ return false;
}
global $wgFeedLimit;
diff --git a/includes/RecentChange.php b/includes/RecentChange.php
index 332d0390..d7cf995c 100644
--- a/includes/RecentChange.php
+++ b/includes/RecentChange.php
@@ -55,6 +55,7 @@
* lang the interwiki prefix, automatically set in save()
* oldSize text size before the change
* newSize text size after the change
+ * pageStatus status of the page: created, deleted, moved, restored, changed
*
* temporary: not stored in the database
* notificationtimestamp
@@ -79,7 +80,7 @@ class RecentChange {
* @var Title
*/
var $mMovedToTitle = false;
- var $numberofWatchingusers = 0 ; # Dummy to prevent error message in SpecialRecentchangeslinked
+ var $numberofWatchingusers = 0; # Dummy to prevent error message in SpecialRecentchangeslinked
var $notificationtimestamp;
# Factory methods
@@ -109,7 +110,7 @@ class RecentChange {
/**
* Obtain the recent change with a given rc_id value
*
- * @param $rcid Int rc_id value to retrieve
+ * @param int $rcid rc_id value to retrieve
* @return RecentChange
*/
public static function newFromId( $rcid ) {
@@ -119,13 +120,13 @@ class RecentChange {
/**
* Find the first recent change matching some specific conditions
*
- * @param $conds Array of conditions
+ * @param array $conds of conditions
* @param $fname Mixed: override the method name in profiling/logs
* @return RecentChange
*/
public static function newFromConds( $conds, $fname = __METHOD__ ) {
$dbr = wfGetDB( DB_SLAVE );
- $row = $dbr->selectRow( 'recentchanges', '*', $conds, $fname );
+ $row = $dbr->selectRow( 'recentchanges', self::selectFields(), $conds, $fname );
if ( $row !== false ) {
return self::newFromRow( $row );
} else {
@@ -133,6 +134,40 @@ class RecentChange {
}
}
+ /**
+ * Return the list of recentchanges fields that should be selected to create
+ * a new recentchanges object.
+ * @return array
+ */
+ public static function selectFields() {
+ return array(
+ 'rc_id',
+ 'rc_timestamp',
+ 'rc_cur_time',
+ 'rc_user',
+ 'rc_user_text',
+ 'rc_namespace',
+ 'rc_title',
+ 'rc_comment',
+ 'rc_minor',
+ 'rc_bot',
+ 'rc_new',
+ 'rc_cur_id',
+ 'rc_this_oldid',
+ 'rc_last_oldid',
+ 'rc_type',
+ 'rc_patrolled',
+ 'rc_ip',
+ 'rc_old_len',
+ 'rc_new_len',
+ 'rc_deleted',
+ 'rc_logid',
+ 'rc_log_type',
+ 'rc_log_action',
+ 'rc_params',
+ );
+ }
+
# Accessors
/**
@@ -154,26 +189,13 @@ class RecentChange {
* @return Title
*/
public function &getTitle() {
- if( $this->mTitle === false ) {
+ if ( $this->mTitle === false ) {
$this->mTitle = Title::makeTitle( $this->mAttribs['rc_namespace'], $this->mAttribs['rc_title'] );
- # Make sure the correct page ID is process cached
- $this->mTitle->resetArticleID( $this->mAttribs['rc_cur_id'] );
}
return $this->mTitle;
}
/**
- * @return bool|Title
- */
- public function getMovedToTitle() {
- if( $this->mMovedToTitle === false ) {
- $this->mMovedToTitle = Title::makeTitle( $this->mAttribs['rc_moved_to_ns'],
- $this->mAttribs['rc_moved_to_title'] );
- }
- return $this->mMovedToTitle;
- }
-
- /**
* Get the User object of the person who performed this change.
*
* @return User
@@ -197,30 +219,33 @@ class RecentChange {
global $wgLocalInterwiki, $wgPutIPinRC, $wgUseEnotif, $wgShowUpdatedMarker, $wgContLang;
$dbw = wfGetDB( DB_MASTER );
- if( !is_array($this->mExtra) ) {
+ if ( !is_array( $this->mExtra ) ) {
$this->mExtra = array();
}
$this->mExtra['lang'] = $wgLocalInterwiki;
- if( !$wgPutIPinRC ) {
+ if ( !$wgPutIPinRC ) {
$this->mAttribs['rc_ip'] = '';
}
# If our database is strict about IP addresses, use NULL instead of an empty string
- if( $dbw->strictIPs() and $this->mAttribs['rc_ip'] == '' ) {
+ if ( $dbw->strictIPs() and $this->mAttribs['rc_ip'] == '' ) {
unset( $this->mAttribs['rc_ip'] );
}
+ # Trim spaces on user supplied text
+ $this->mAttribs['rc_comment'] = trim( $this->mAttribs['rc_comment'] );
+
# Make sure summary is truncated (whole multibyte characters)
$this->mAttribs['rc_comment'] = $wgContLang->truncate( $this->mAttribs['rc_comment'], 255 );
# Fixup database timestamps
- $this->mAttribs['rc_timestamp'] = $dbw->timestamp($this->mAttribs['rc_timestamp']);
- $this->mAttribs['rc_cur_time'] = $dbw->timestamp($this->mAttribs['rc_cur_time']);
+ $this->mAttribs['rc_timestamp'] = $dbw->timestamp( $this->mAttribs['rc_timestamp'] );
+ $this->mAttribs['rc_cur_time'] = $dbw->timestamp( $this->mAttribs['rc_cur_time'] );
$this->mAttribs['rc_id'] = $dbw->nextSequenceValue( 'recentchanges_rc_id_seq' );
## If we are using foreign keys, an entry of 0 for the page_id will fail, so use NULL
- if( $dbw->cascadingDeletes() and $this->mAttribs['rc_cur_id']==0 ) {
+ if ( $dbw->cascadingDeletes() and $this->mAttribs['rc_cur_id'] == 0 ) {
unset( $this->mAttribs['rc_cur_id'] );
}
@@ -239,18 +264,19 @@ class RecentChange {
}
# E-mail notifications
- if( $wgUseEnotif || $wgShowUpdatedMarker ) {
+ if ( $wgUseEnotif || $wgShowUpdatedMarker ) {
$editor = $this->getPerformer();
$title = $this->getTitle();
- if ( wfRunHooks( 'AbortEmailNotification', array($editor, $title) ) ) {
+ if ( wfRunHooks( 'AbortEmailNotification', array( $editor, $title ) ) ) {
# @todo FIXME: This would be better as an extension hook
$enotif = new EmailNotification();
$enotif->notifyOnPageChange( $editor, $title,
$this->mAttribs['rc_timestamp'],
$this->mAttribs['rc_comment'],
$this->mAttribs['rc_minor'],
- $this->mAttribs['rc_last_oldid'] );
+ $this->mAttribs['rc_last_oldid'],
+ $this->mExtra['pageStatus'] );
}
}
}
@@ -258,7 +284,7 @@ class RecentChange {
public function notifyRC2UDP() {
global $wgRC2UDPAddress, $wgRC2UDPOmitBots;
# Notify external application via UDP
- if( $wgRC2UDPAddress && ( !$this->mAttribs['rc_bot'] || !$wgRC2UDPOmitBots ) ) {
+ if ( $wgRC2UDPAddress && ( !$this->mAttribs['rc_bot'] || !$wgRC2UDPOmitBots ) ) {
self::sendToUDP( $this->getIRCLine() );
}
}
@@ -266,10 +292,10 @@ class RecentChange {
/**
* Send some text to UDP.
* @see RecentChange::cleanupForIRC
- * @param $line String: text to send
- * @param $address String: defaults to $wgRC2UDPAddress.
- * @param $prefix String: defaults to $wgRC2UDPPrefix.
- * @param $port Int: defaults to $wgRC2UDPPort. (Since 1.17)
+ * @param string $line text to send
+ * @param string $address defaults to $wgRC2UDPAddress.
+ * @param string $prefix defaults to $wgRC2UDPPrefix.
+ * @param int $port defaults to $wgRC2UDPPort. (Since 1.17)
* @return Boolean: success
*/
public static function sendToUDP( $line, $address = '', $prefix = '', $port = '' ) {
@@ -279,12 +305,12 @@ class RecentChange {
$prefix = $prefix ? $prefix : $wgRC2UDPPrefix;
$port = $port ? $port : $wgRC2UDPPort;
# Notify external application via UDP
- if( $address ) {
+ if ( $address ) {
$conn = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
- if( $conn ) {
+ if ( $conn ) {
$line = $prefix . $line;
wfDebug( __METHOD__ . ": sending UDP line: $line\n" );
- socket_sendto( $conn, $line, strlen($line), 0, $address, $port );
+ socket_sendto( $conn, $line, strlen( $line ), 0, $address, $port );
socket_close( $conn );
return true;
} else {
@@ -295,7 +321,7 @@ class RecentChange {
}
/**
- * Remove newlines, carriage returns and decode html entites
+ * Remove newlines, carriage returns and decode html entities
* @param $text String
* @return String
*/
@@ -315,9 +341,9 @@ class RecentChange {
$change = $change instanceof RecentChange
? $change
- : RecentChange::newFromId($change);
+ : RecentChange::newFromId( $change );
- if( !$change instanceof RecentChange ) {
+ if ( !$change instanceof RecentChange ) {
return null;
}
return $change->doMarkPatrolled( $wgUser, $auto );
@@ -336,32 +362,32 @@ class RecentChange {
$errors = array();
// If recentchanges patrol is disabled, only new pages
// can be patrolled
- if( !$wgUseRCPatrol && ( !$wgUseNPPatrol || $this->getAttribute('rc_type') != RC_NEW ) ) {
- $errors[] = array('rcpatroldisabled');
+ if ( !$wgUseRCPatrol && ( !$wgUseNPPatrol || $this->getAttribute( 'rc_type' ) != RC_NEW ) ) {
+ $errors[] = array( 'rcpatroldisabled' );
}
// Automatic patrol needs "autopatrol", ordinary patrol needs "patrol"
$right = $auto ? 'autopatrol' : 'patrol';
$errors = array_merge( $errors, $this->getTitle()->getUserPermissionsErrors( $right, $user ) );
- if( !wfRunHooks('MarkPatrolled', array($this->getAttribute('rc_id'), &$user, false)) ) {
- $errors[] = array('hookaborted');
+ if ( !wfRunHooks( 'MarkPatrolled', array( $this->getAttribute( 'rc_id' ), &$user, false ) ) ) {
+ $errors[] = array( 'hookaborted' );
}
// Users without the 'autopatrol' right can't patrol their
// own revisions
- if( $user->getName() == $this->getAttribute('rc_user_text') && !$user->isAllowed('autopatrol') ) {
- $errors[] = array('markedaspatrollederror-noautopatrol');
+ if ( $user->getName() == $this->getAttribute( 'rc_user_text' ) && !$user->isAllowed( 'autopatrol' ) ) {
+ $errors[] = array( 'markedaspatrollederror-noautopatrol' );
}
- if( $errors ) {
+ if ( $errors ) {
return $errors;
}
// If the change was patrolled already, do nothing
- if( $this->getAttribute('rc_patrolled') ) {
+ if ( $this->getAttribute( 'rc_patrolled' ) ) {
return array();
}
// Actually set the 'patrolled' flag in RC
$this->reallyMarkPatrolled();
// Log this patrol event
PatrolLog::record( $this, $auto, $user );
- wfRunHooks( 'MarkPatrolledComplete', array($this->getAttribute('rc_id'), &$user, false) );
+ wfRunHooks( 'MarkPatrolledComplete', array( $this->getAttribute( 'rc_id' ), &$user, false ) );
return array();
}
@@ -377,7 +403,7 @@ class RecentChange {
'rc_patrolled' => 1
),
array(
- 'rc_id' => $this->getAttribute('rc_id')
+ 'rc_id' => $this->getAttribute( 'rc_id' )
),
__METHOD__
);
@@ -403,7 +429,7 @@ class RecentChange {
* @return RecentChange
*/
public static function notifyEdit( $timestamp, &$title, $minor, &$user, $comment, $oldId,
- $lastTimestamp, $bot, $ip='', $oldSize=0, $newSize=0, $newId=0, $patrol=0 ) {
+ $lastTimestamp, $bot, $ip = '', $oldSize = 0, $newSize = 0, $newId = 0, $patrol = 0 ) {
$rc = new RecentChange;
$rc->mTitle = $title;
$rc->mPerformer = $user;
@@ -421,10 +447,8 @@ class RecentChange {
'rc_this_oldid' => $newId,
'rc_last_oldid' => $oldId,
'rc_bot' => $bot ? 1 : 0,
- 'rc_moved_to_ns' => 0,
- 'rc_moved_to_title' => '',
'rc_ip' => self::checkIPAddress( $ip ),
- 'rc_patrolled' => intval($patrol),
+ 'rc_patrolled' => intval( $patrol ),
'rc_new' => 0, # obsolete
'rc_old_len' => $oldSize,
'rc_new_len' => $newSize,
@@ -435,11 +459,12 @@ class RecentChange {
'rc_params' => ''
);
- $rc->mExtra = array(
+ $rc->mExtra = array(
'prefixedDBkey' => $title->getPrefixedDBkey(),
'lastTimestamp' => $lastTimestamp,
'oldSize' => $oldSize,
'newSize' => $newSize,
+ 'pageStatus' => 'changed'
);
$rc->save();
return $rc;
@@ -463,7 +488,7 @@ class RecentChange {
* @return RecentChange
*/
public static function notifyNew( $timestamp, &$title, $minor, &$user, $comment, $bot,
- $ip='', $size=0, $newId=0, $patrol=0 ) {
+ $ip = '', $size = 0, $newId = 0, $patrol = 0 ) {
$rc = new RecentChange;
$rc->mTitle = $title;
$rc->mPerformer = $user;
@@ -481,10 +506,8 @@ class RecentChange {
'rc_this_oldid' => $newId,
'rc_last_oldid' => 0,
'rc_bot' => $bot ? 1 : 0,
- 'rc_moved_to_ns' => 0,
- 'rc_moved_to_title' => '',
'rc_ip' => self::checkIPAddress( $ip ),
- 'rc_patrolled' => intval($patrol),
+ 'rc_patrolled' => intval( $patrol ),
'rc_new' => 1, # obsolete
'rc_old_len' => 0,
'rc_new_len' => $size,
@@ -495,11 +518,12 @@ class RecentChange {
'rc_params' => ''
);
- $rc->mExtra = array(
+ $rc->mExtra = array(
'prefixedDBkey' => $title->getPrefixedDBkey(),
'lastTimestamp' => 0,
'oldSize' => 0,
- 'newSize' => $size
+ 'newSize' => $size,
+ 'pageStatus' => 'created'
);
$rc->save();
return $rc;
@@ -521,11 +545,11 @@ class RecentChange {
* @return bool
*/
public static function notifyLog( $timestamp, &$title, &$user, $actionComment, $ip, $type,
- $action, $target, $logComment, $params, $newId=0, $actionCommentIRC='' )
+ $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = '' )
{
global $wgLogRestrictions;
# Don't add private logs to RC!
- if( isset($wgLogRestrictions[$type]) && $wgLogRestrictions[$type] != '*' ) {
+ if ( isset( $wgLogRestrictions[$type] ) && $wgLogRestrictions[$type] != '*' ) {
return false;
}
$rc = self::newLogEntry( $timestamp, $title, $user, $actionComment, $ip, $type, $action,
@@ -550,9 +574,30 @@ class RecentChange {
* @return RecentChange
*/
public static function newLogEntry( $timestamp, &$title, &$user, $actionComment, $ip,
- $type, $action, $target, $logComment, $params, $newId=0, $actionCommentIRC='' ) {
+ $type, $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = '' ) {
global $wgRequest;
+ ## Get pageStatus for email notification
+ switch ( $type . '-' . $action ) {
+ case 'delete-delete':
+ $pageStatus = 'deleted';
+ break;
+ case 'move-move':
+ case 'move-move_redir':
+ $pageStatus = 'moved';
+ break;
+ case 'delete-restore':
+ $pageStatus = 'restored';
+ break;
+ case 'upload-upload':
+ $pageStatus = 'created';
+ break;
+ case 'upload-overwrite':
+ default:
+ $pageStatus = 'changed';
+ break;
+ }
+
$rc = new RecentChange;
$rc->mTitle = $target;
$rc->mPerformer = $user;
@@ -570,8 +615,6 @@ class RecentChange {
'rc_this_oldid' => 0,
'rc_last_oldid' => 0,
'rc_bot' => $user->isAllowed( 'bot' ) ? $wgRequest->getBool( 'bot', true ) : 0,
- 'rc_moved_to_ns' => 0,
- 'rc_moved_to_title' => '',
'rc_ip' => self::checkIPAddress( $ip ),
'rc_patrolled' => 1,
'rc_new' => 0, # obsolete
@@ -584,10 +627,11 @@ class RecentChange {
'rc_params' => $params
);
- $rc->mExtra = array(
+ $rc->mExtra = array(
'prefixedDBkey' => $title->getPrefixedDBkey(),
'lastTimestamp' => 0,
'actionComment' => $actionComment, // the comment appended to the action, passed from LogPage
+ 'pageStatus' => $pageStatus,
'actionCommentIRC' => $actionCommentIRC
);
return $rc;
@@ -600,7 +644,7 @@ class RecentChange {
*/
public function loadFromRow( $row ) {
$this->mAttribs = get_object_vars( $row );
- $this->mAttribs['rc_timestamp'] = wfTimestamp(TS_MW, $this->mAttribs['rc_timestamp']);
+ $this->mAttribs['rc_timestamp'] = wfTimestamp( TS_MW, $this->mAttribs['rc_timestamp'] );
$this->mAttribs['rc_deleted'] = $row->rc_deleted; // MUST be set
}
@@ -611,7 +655,7 @@ class RecentChange {
*/
public function loadFromCurRow( $row ) {
$this->mAttribs = array(
- 'rc_timestamp' => wfTimestamp(TS_MW, $row->rev_timestamp),
+ 'rc_timestamp' => wfTimestamp( TS_MW, $row->rev_timestamp ),
'rc_cur_time' => $row->rev_timestamp,
'rc_user' => $row->rev_user,
'rc_user_text' => $row->rev_user_text,
@@ -621,21 +665,19 @@ class RecentChange {
'rc_minor' => $row->rev_minor_edit ? 1 : 0,
'rc_type' => $row->page_is_new ? RC_NEW : RC_EDIT,
'rc_cur_id' => $row->page_id,
- 'rc_this_oldid' => $row->rev_id,
- 'rc_last_oldid' => isset($row->rc_last_oldid) ? $row->rc_last_oldid : 0,
- 'rc_bot' => 0,
- 'rc_moved_to_ns' => 0,
- 'rc_moved_to_title' => '',
+ 'rc_this_oldid' => $row->rev_id,
+ 'rc_last_oldid' => isset( $row->rc_last_oldid ) ? $row->rc_last_oldid : 0,
+ 'rc_bot' => 0,
'rc_ip' => '',
'rc_id' => $row->rc_id,
'rc_patrolled' => $row->rc_patrolled,
'rc_new' => $row->page_is_new, # obsolete
'rc_old_len' => $row->rc_old_len,
'rc_new_len' => $row->rc_new_len,
- 'rc_params' => isset($row->rc_params) ? $row->rc_params : '',
- 'rc_log_type' => isset($row->rc_log_type) ? $row->rc_log_type : null,
- 'rc_log_action' => isset($row->rc_log_action) ? $row->rc_log_action : null,
- 'rc_log_id' => isset($row->rc_log_id) ? $row->rc_log_id: 0,
+ 'rc_params' => isset( $row->rc_params ) ? $row->rc_params : '',
+ 'rc_log_type' => isset( $row->rc_log_type ) ? $row->rc_log_type : null,
+ 'rc_log_action' => isset( $row->rc_log_action ) ? $row->rc_log_action : null,
+ 'rc_logid' => isset( $row->rc_logid ) ? $row->rc_logid : 0,
'rc_deleted' => $row->rc_deleted // MUST be set
);
}
@@ -643,7 +685,7 @@ class RecentChange {
/**
* Get an attribute value
*
- * @param $name String Attribute name
+ * @param string $name Attribute name
* @return mixed
*/
public function getAttribute( $name ) {
@@ -664,13 +706,13 @@ class RecentChange {
* @return string
*/
public function diffLinkTrail( $forceCur ) {
- if( $this->mAttribs['rc_type'] == RC_EDIT ) {
- $trail = "curid=" . (int)($this->mAttribs['rc_cur_id']) .
- "&oldid=" . (int)($this->mAttribs['rc_last_oldid']);
- if( $forceCur ) {
- $trail .= '&diff=0' ;
+ if ( $this->mAttribs['rc_type'] == RC_EDIT ) {
+ $trail = "curid=" . (int)( $this->mAttribs['rc_cur_id'] ) .
+ "&oldid=" . (int)( $this->mAttribs['rc_last_oldid'] );
+ if ( $forceCur ) {
+ $trail .= '&diff=0';
} else {
- $trail .= '&diff=' . (int)($this->mAttribs['rc_this_oldid']);
+ $trail .= '&diff=' . (int)( $this->mAttribs['rc_this_oldid'] );
}
} else {
$trail = '';
@@ -685,7 +727,7 @@ class RecentChange {
global $wgUseRCPatrol, $wgUseNPPatrol, $wgRC2UDPInterwikiPrefix, $wgLocalInterwiki,
$wgCanonicalServer, $wgScript;
- if( $this->mAttribs['rc_type'] == RC_LOG ) {
+ if ( $this->mAttribs['rc_type'] == RC_LOG ) {
// Don't use SpecialPage::getTitleFor, backwards compatibility with
// IRC API which expects "Log".
$titleObj = Title::newFromText( 'Log/' . $this->mAttribs['rc_log_type'], NS_SPECIAL );
@@ -695,11 +737,11 @@ class RecentChange {
$title = $titleObj->getPrefixedText();
$title = self::cleanupForIRC( $title );
- if( $this->mAttribs['rc_type'] == RC_LOG ) {
+ if ( $this->mAttribs['rc_type'] == RC_LOG ) {
$url = '';
} else {
$url = $wgCanonicalServer . $wgScript;
- if( $this->mAttribs['rc_type'] == RC_NEW ) {
+ if ( $this->mAttribs['rc_type'] == RC_NEW ) {
$query = '?oldid=' . $this->mAttribs['rc_this_oldid'];
} else {
$query = '?diff=' . $this->mAttribs['rc_this_oldid'] . '&oldid=' . $this->mAttribs['rc_last_oldid'];
@@ -712,15 +754,15 @@ class RecentChange {
$url .= $query;
}
- if( $this->mAttribs['rc_old_len'] !== null && $this->mAttribs['rc_new_len'] !== null ) {
+ if ( $this->mAttribs['rc_old_len'] !== null && $this->mAttribs['rc_new_len'] !== null ) {
$szdiff = $this->mAttribs['rc_new_len'] - $this->mAttribs['rc_old_len'];
- if($szdiff < -500) {
+ if ( $szdiff < -500 ) {
$szdiff = "\002$szdiff\002";
- } elseif($szdiff >= 0) {
- $szdiff = '+' . $szdiff ;
+ } elseif ( $szdiff >= 0 ) {
+ $szdiff = '+' . $szdiff;
}
// @todo i18n with parentheses in content language?
- $szdiff = '(' . $szdiff . ')' ;
+ $szdiff = '(' . $szdiff . ')';
} else {
$szdiff = '';
}
@@ -756,7 +798,7 @@ class RecentChange {
# see http://www.irssi.org/documentation/formats for some colour codes. prefix is \003,
# no colour (\003) switches back to the term default
$fullString = "$titleString\0034 $flag\00310 " .
- "\00302$url\003 \0035*\003 \00303$user\003 \0035*\003 $szdiff \00310$comment\003\n";
+ "\00302$url\003 \0035*\003 \00303$user\003 \0035*\003 $szdiff \00310$comment\003\n";
return $fullString;
}
@@ -769,13 +811,13 @@ class RecentChange {
* @return string
*/
public function getCharacterDifference( $old = 0, $new = 0 ) {
- if( $old === 0 ) {
+ if ( $old === 0 ) {
$old = $this->mAttribs['rc_old_len'];
}
- if( $new === 0 ) {
+ if ( $new === 0 ) {
$new = $this->mAttribs['rc_new_len'];
}
- if( $old === null || $new === null ) {
+ if ( $old === null || $new === null ) {
return '';
}
return ChangesList::showCharacterDifference( $old, $new );
@@ -789,8 +831,9 @@ class RecentChange {
}
} else {
$ip = $wgRequest->getIP();
- if( !$ip )
+ if ( !$ip ) {
$ip = '';
+ }
}
return $ip;
}
diff --git a/includes/Revision.php b/includes/Revision.php
index 20cc8f58..2b34984b 100644
--- a/includes/Revision.php
+++ b/includes/Revision.php
@@ -25,6 +25,10 @@
*/
class Revision implements IDBAccessObject {
protected $mId;
+
+ /**
+ * @var int|null
+ */
protected $mPage;
protected $mUserText;
protected $mOrigUserText;
@@ -38,8 +42,24 @@ class Revision implements IDBAccessObject {
protected $mComment;
protected $mText;
protected $mTextRow;
+
+ /**
+ * @var null|Title
+ */
protected $mTitle;
protected $mCurrent;
+ protected $mContentModel;
+ protected $mContentFormat;
+
+ /**
+ * @var Content|null|bool
+ */
+ protected $mContent;
+
+ /**
+ * @var null|ContentHandler
+ */
+ protected $mContentHandler;
// Revision deletion constants
const DELETED_TEXT = 1;
@@ -83,7 +103,7 @@ class Revision implements IDBAccessObject {
* @param $flags Integer Bitfield (optional)
* @return Revision or null
*/
- public static function newFromTitle( $title, $id = 0, $flags = null ) {
+ public static function newFromTitle( $title, $id = 0, $flags = 0 ) {
$conds = array(
'page_namespace' => $title->getNamespace(),
'page_title' => $title->getDBkey()
@@ -94,8 +114,6 @@ class Revision implements IDBAccessObject {
} else {
// Use a join to get the latest revision
$conds[] = 'rev_id=page_latest';
- // Callers assume this will be up-to-date
- $flags = is_int( $flags ) ? $flags : self::READ_LATEST; // b/c
}
return self::newFromConds( $conds, (int)$flags );
}
@@ -106,7 +124,7 @@ class Revision implements IDBAccessObject {
* Returns null if no such revision can be found.
*
* $flags include:
- * Revision::READ_LATEST : Select the data from the master
+ * Revision::READ_LATEST : Select the data from the master (since 1.20)
* Revision::READ_LOCKING : Select & lock the data from the master
*
* @param $revId Integer
@@ -114,15 +132,13 @@ class Revision implements IDBAccessObject {
* @param $flags Integer Bitfield (optional)
* @return Revision or null
*/
- public static function newFromPageId( $pageId, $revId = 0, $flags = null ) {
+ public static function newFromPageId( $pageId, $revId = 0, $flags = 0 ) {
$conds = array( 'page_id' => $pageId );
if ( $revId ) {
$conds['rev_id'] = $revId;
} else {
// Use a join to get the latest revision
$conds[] = 'rev_id = page_latest';
- // Callers assume this will be up-to-date
- $flags = is_int( $flags ) ? $flags : self::READ_LATEST; // b/c
}
return self::newFromConds( $conds, (int)$flags );
}
@@ -135,9 +151,12 @@ class Revision implements IDBAccessObject {
* @param $row
* @param $overrides array
*
+ * @throws MWException
* @return Revision
*/
public static function newFromArchiveRow( $row, $overrides = array() ) {
+ global $wgContentHandlerUseDB;
+
$attribs = $overrides + array(
'page' => isset( $row->ar_page_id ) ? $row->ar_page_id : null,
'id' => isset( $row->ar_rev_id ) ? $row->ar_rev_id : null,
@@ -150,7 +169,22 @@ class Revision implements IDBAccessObject {
'deleted' => $row->ar_deleted,
'len' => $row->ar_len,
'sha1' => isset( $row->ar_sha1 ) ? $row->ar_sha1 : null,
+ 'content_model' => isset( $row->ar_content_model ) ? $row->ar_content_model : null,
+ 'content_format' => isset( $row->ar_content_format ) ? $row->ar_content_format : null,
);
+
+ if ( !$wgContentHandlerUseDB ) {
+ unset( $attribs['content_model'] );
+ unset( $attribs['content_format'] );
+ }
+
+ if ( !isset( $attribs['title'] )
+ && isset( $row->ar_namespace )
+ && isset( $row->ar_title ) ) {
+
+ $attribs['title'] = Title::makeTitle( $row->ar_namespace, $row->ar_title );
+ }
+
if ( isset( $row->ar_text ) && !$row->ar_text_id ) {
// Pre-1.5 ar_text row
$attribs['text'] = self::getRevisionText( $row, 'ar_' );
@@ -220,9 +254,11 @@ class Revision implements IDBAccessObject {
$matchId = 'page_latest';
}
return self::loadFromConds( $db,
- array( "rev_id=$matchId",
- 'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDBkey() )
+ array(
+ "rev_id=$matchId",
+ 'page_namespace' => $title->getNamespace(),
+ 'page_title' => $title->getDBkey()
+ )
);
}
@@ -238,9 +274,11 @@ class Revision implements IDBAccessObject {
*/
public static function loadFromTimestamp( $db, $title, $timestamp ) {
return self::loadFromConds( $db,
- array( 'rev_timestamp' => $db->timestamp( $timestamp ),
- 'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDBkey() )
+ array(
+ 'rev_timestamp' => $db->timestamp( $timestamp ),
+ 'page_namespace' => $title->getNamespace(),
+ 'page_title' => $title->getDBkey()
+ )
);
}
@@ -296,9 +334,11 @@ class Revision implements IDBAccessObject {
public static function fetchRevision( $title ) {
return self::fetchFromConds(
wfGetDB( DB_SLAVE ),
- array( 'rev_id=page_latest',
- 'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDBkey() )
+ array(
+ 'rev_id=page_latest',
+ 'page_namespace' => $title->getNamespace(),
+ 'page_title' => $title->getDBkey()
+ )
);
}
@@ -343,7 +383,7 @@ class Revision implements IDBAccessObject {
}
/**
- * Return the value of a select() page conds array for the paeg table.
+ * 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
* @return Array
@@ -358,7 +398,9 @@ class Revision implements IDBAccessObject {
* @return array
*/
public static function selectFields() {
- return array(
+ global $wgContentHandlerUseDB;
+
+ $fields = array(
'rev_id',
'rev_page',
'rev_text_id',
@@ -370,8 +412,15 @@ class Revision implements IDBAccessObject {
'rev_deleted',
'rev_len',
'rev_parent_id',
- 'rev_sha1'
+ 'rev_sha1',
);
+
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'rev_content_format';
+ $fields[] = 'rev_content_model';
+ }
+
+ return $fields;
}
/**
@@ -436,6 +485,7 @@ class Revision implements IDBAccessObject {
* Constructor
*
* @param $row Mixed: either a database row or an array
+ * @throws MWException
* @access private
*/
function __construct( $row ) {
@@ -449,13 +499,13 @@ class Revision implements IDBAccessObject {
$this->mTimestamp = $row->rev_timestamp;
$this->mDeleted = intval( $row->rev_deleted );
- if( !isset( $row->rev_parent_id ) ) {
- $this->mParentId = is_null( $row->rev_parent_id ) ? null : 0;
+ if ( !isset( $row->rev_parent_id ) ) {
+ $this->mParentId = null;
} else {
- $this->mParentId = intval( $row->rev_parent_id );
+ $this->mParentId = intval( $row->rev_parent_id );
}
- if( !isset( $row->rev_len ) || is_null( $row->rev_len ) ) {
+ if ( !isset( $row->rev_len ) ) {
$this->mSize = null;
} else {
$this->mSize = intval( $row->rev_len );
@@ -475,8 +525,20 @@ class Revision implements IDBAccessObject {
$this->mTitle = null;
}
+ if( !isset( $row->rev_content_model ) || is_null( $row->rev_content_model ) ) {
+ $this->mContentModel = null; # determine on demand if needed
+ } else {
+ $this->mContentModel = strval( $row->rev_content_model );
+ }
+
+ if( !isset( $row->rev_content_format ) || is_null( $row->rev_content_format ) ) {
+ $this->mContentFormat = null; # determine on demand if needed
+ } else {
+ $this->mContentFormat = strval( $row->rev_content_format );
+ }
+
// Lazy extraction...
- $this->mText = null;
+ $this->mText = null;
if( isset( $row->old_text ) ) {
$this->mTextRow = $row;
} else {
@@ -496,6 +558,20 @@ class Revision implements IDBAccessObject {
// Build a new revision to be saved...
global $wgUser; // ugh
+ # if we have a content object, use it to set the model and type
+ if ( !empty( $row['content'] ) ) {
+ //@todo: when is that set? test with external store setup! check out insertOn() [dk]
+ if ( !empty( $row['text_id'] ) ) {
+ throw new MWException( "Text already stored in external store (id {$row['text_id']}), " .
+ "can't serialize content object" );
+ }
+
+ $row['content_model'] = $row['content']->getModel();
+ # note: mContentFormat is initializes later accordingly
+ # note: content is serialized later in this method!
+ # also set text to null?
+ }
+
$this->mId = isset( $row['id'] ) ? intval( $row['id'] ) : null;
$this->mPage = isset( $row['page'] ) ? intval( $row['page'] ) : null;
$this->mTextId = isset( $row['text_id'] ) ? intval( $row['text_id'] ) : null;
@@ -508,21 +584,67 @@ class Revision implements IDBAccessObject {
$this->mParentId = isset( $row['parent_id'] ) ? intval( $row['parent_id'] ) : null;
$this->mSha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : null;
+ $this->mContentModel = isset( $row['content_model'] ) ? strval( $row['content_model'] ) : null;
+ $this->mContentFormat = isset( $row['content_format'] ) ? strval( $row['content_format'] ) : null;
+
// Enforce spacing trimming on supplied text
$this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null;
$this->mText = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null;
$this->mTextRow = null;
- $this->mTitle = null; # Load on demand if needed
- $this->mCurrent = false;
- # If we still have no length, see it we have the text to figure it out
+ $this->mTitle = isset( $row['title'] ) ? $row['title'] : null;
+
+ // if we have a Content object, override mText and mContentModel
+ if ( !empty( $row['content'] ) ) {
+ if ( !( $row['content'] instanceof Content ) ) {
+ throw new MWException( '`content` field must contain a Content object.' );
+ }
+
+ $handler = $this->getContentHandler();
+ $this->mContent = $row['content'];
+
+ $this->mContentModel = $this->mContent->getModel();
+ $this->mContentHandler = null;
+
+ $this->mText = $handler->serializeContent( $row['content'], $this->getContentFormat() );
+ } elseif ( !is_null( $this->mText ) ) {
+ $handler = $this->getContentHandler();
+ $this->mContent = $handler->unserializeContent( $this->mText );
+ }
+
+ // If we have a Title object, make sure it is consistent with mPage.
+ if ( $this->mTitle && $this->mTitle->exists() ) {
+ if ( $this->mPage === null ) {
+ // if the page ID wasn't known, set it now
+ $this->mPage = $this->mTitle->getArticleID();
+ } elseif ( $this->mTitle->getArticleID() !== $this->mPage ) {
+ // Got different page IDs. This may be legit (e.g. during undeletion),
+ // but it seems worth mentioning it in the log.
+ wfDebug( "Page ID " . $this->mPage . " mismatches the ID " .
+ $this->mTitle->getArticleID() . " provided by the Title object." );
+ }
+ }
+
+ $this->mCurrent = false;
+
+ // If we still have no length, see it we have the text to figure it out
if ( !$this->mSize ) {
- $this->mSize = is_null( $this->mText ) ? null : strlen( $this->mText );
+ if ( !is_null( $this->mContent ) ) {
+ $this->mSize = $this->mContent->getSize();
+ } else {
+ #NOTE: this should never happen if we have either text or content object!
+ $this->mSize = null;
+ }
}
- # Same for sha1
+
+ // Same for sha1
if ( $this->mSha1 === null ) {
$this->mSha1 = is_null( $this->mText ) ? null : self::base36Sha1( $this->mText );
}
+
+ // force lazy init
+ $this->getContentModel();
+ $this->getContentFormat();
} else {
throw new MWException( 'Revision constructor passed invalid row format.' );
}
@@ -595,18 +717,23 @@ class Revision implements IDBAccessObject {
if( isset( $this->mTitle ) ) {
return $this->mTitle;
}
- if( !is_null( $this->mId ) ) { //rev_id is defined as NOT NULL
+ if( !is_null( $this->mId ) ) { //rev_id is defined as NOT NULL, but this revision may not yet have been inserted.
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow(
array( 'page', 'revision' ),
self::selectPageFields(),
array( 'page_id=rev_page',
- 'rev_id' => $this->mId ),
+ 'rev_id' => $this->mId ),
__METHOD__ );
if ( $row ) {
$this->mTitle = Title::newFromRow( $row );
}
}
+
+ if ( !$this->mTitle && !is_null( $this->mPage ) && $this->mPage > 0 ) {
+ $this->mTitle = Title::newFromID( $this->mPage );
+ }
+
return $this->mTitle;
}
@@ -761,7 +888,7 @@ class Revision implements IDBAccessObject {
}
/**
- * @param $field int one of DELETED_* bitfield constants
+ * @param int $field one of DELETED_* bitfield constants
*
* @return Boolean
*/
@@ -789,15 +916,39 @@ class Revision implements IDBAccessObject {
* Revision::RAW get the text regardless of permissions
* @param $user User object to check for, only if FOR_THIS_USER is passed
* to the $audience parameter
+ *
+ * @deprecated in 1.21, use getContent() instead
+ * @todo: replace usage in core
* @return String
*/
public function getText( $audience = self::FOR_PUBLIC, User $user = null ) {
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
+ $content = $this->getContent( $audience, $user );
+ return ContentHandler::getContentText( $content ); # returns the raw content text, if applicable
+ }
+
+ /**
+ * Fetch revision content if it's available to the specified audience.
+ * If the specified audience does not have the ability to view this
+ * revision, null will be returned.
+ *
+ * @param $audience Integer: one of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::RAW get the text regardless of permissions
+ * @param $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
+ * @since 1.21
+ * @return Content|null
+ */
+ public function getContent( $audience = self::FOR_PUBLIC, User $user = null ) {
if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) {
- return '';
+ return null;
} elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT, $user ) ) {
- return '';
+ return null;
} else {
- return $this->getRawText();
+ return $this->getContentInternal();
}
}
@@ -816,16 +967,117 @@ class Revision implements IDBAccessObject {
* Fetch revision text without regard for view restrictions
*
* @return String
+ *
+ * @deprecated since 1.21. Instead, use Revision::getContent( Revision::RAW )
+ * or Revision::getSerializedData() as appropriate.
*/
public function getRawText() {
- if( is_null( $this->mText ) ) {
- // Revision text is immutable. Load on demand:
- $this->mText = $this->loadText();
- }
+ ContentHandler::deprecated( __METHOD__, "1.21" );
+ return $this->getText( self::RAW );
+ }
+
+ /**
+ * Fetch original serialized data without regard for view restrictions
+ *
+ * @since 1.21
+ * @return String
+ */
+ public function getSerializedData() {
return $this->mText;
}
/**
+ * Gets the content object for the revision (or null on failure).
+ *
+ * Note that for mutable Content objects, each call to this method will return a
+ * fresh clone.
+ *
+ * @since 1.21
+ * @return Content|null the Revision's content, or null on failure.
+ */
+ protected function getContentInternal() {
+ if( is_null( $this->mContent ) ) {
+ // Revision is immutable. Load on demand:
+ if( is_null( $this->mText ) ) {
+ $this->mText = $this->loadText();
+ }
+
+ if ( $this->mText !== null && $this->mText !== false ) {
+ // Unserialize content
+ $handler = $this->getContentHandler();
+ $format = $this->getContentFormat();
+
+ $this->mContent = $handler->unserializeContent( $this->mText, $format );
+ } else {
+ $this->mContent = false; // negative caching!
+ }
+ }
+
+ // NOTE: copy() will return $this for immutable content objects
+ return $this->mContent ? $this->mContent->copy() : null;
+ }
+
+ /**
+ * Returns the content model for this revision.
+ *
+ * If no content model was stored in the database, $this->getTitle()->getContentModel() is
+ * used to determine the content model to use. If no title is know, CONTENT_MODEL_WIKITEXT
+ * is used as a last resort.
+ *
+ * @return String the content model id associated with this revision, see the CONTENT_MODEL_XXX constants.
+ **/
+ public function getContentModel() {
+ if ( !$this->mContentModel ) {
+ $title = $this->getTitle();
+ $this->mContentModel = ( $title ? $title->getContentModel() : CONTENT_MODEL_WIKITEXT );
+
+ assert( !empty( $this->mContentModel ) );
+ }
+
+ return $this->mContentModel;
+ }
+
+ /**
+ * Returns the content format for this revision.
+ *
+ * If no content format was stored in the database, the default format for this
+ * revision's content model is returned.
+ *
+ * @return String the content format id associated with this revision, see the CONTENT_FORMAT_XXX constants.
+ **/
+ public function getContentFormat() {
+ if ( !$this->mContentFormat ) {
+ $handler = $this->getContentHandler();
+ $this->mContentFormat = $handler->getDefaultFormat();
+
+ assert( !empty( $this->mContentFormat ) );
+ }
+
+ return $this->mContentFormat;
+ }
+
+ /**
+ * Returns the content handler appropriate for this revision's content model.
+ *
+ * @throws MWException
+ * @return ContentHandler
+ */
+ public function getContentHandler() {
+ if ( !$this->mContentHandler ) {
+ $model = $this->getContentModel();
+ $this->mContentHandler = ContentHandler::getForModelID( $model );
+
+ $format = $this->getContentFormat();
+
+ if ( !$this->mContentHandler->isSupportedFormat( $format ) ) {
+ throw new MWException( "Oops, the content format $format is not supported for this content model, $model" );
+ }
+ }
+
+ return $this->mContentHandler;
+ }
+
+ /**
* @return String
*/
public function getTimestamp() {
@@ -842,7 +1094,7 @@ class Revision implements IDBAccessObject {
/**
* Get previous revision for this title
*
- * @return Revision or null
+ * @return Revision|null
*/
public function getPrevious() {
if( $this->getTitle() ) {
@@ -900,10 +1152,14 @@ class Revision implements IDBAccessObject {
* field must be included
*
* @param $row Object: the text data
- * @param $prefix String: table prefix (default 'old_')
+ * @param string $prefix table prefix (default 'old_')
+ * @param string|false $wiki the name of the wiki to load the revision text from
+ * (same as the the wiki $row was loaded from) or false to indicate the local
+ * wiki (this is the default). Otherwise, it must be a symbolic wiki database
+ * identifier as understood by the LoadBalancer class.
* @return String: text the text requested or false on failure
*/
- public static function getRevisionText( $row, $prefix = 'old_' ) {
+ public static function getRevisionText( $row, $prefix = 'old_', $wiki = false ) {
wfProfileIn( __METHOD__ );
# Get data
@@ -931,7 +1187,7 @@ class Revision implements IDBAccessObject {
wfProfileOut( __METHOD__ );
return false;
}
- $text = ExternalStore::fetchFromURL( $url );
+ $text = ExternalStore::fetchFromURL( $url, array( 'wiki' => $wiki ) );
}
// If the text was fetched without an error, convert it
@@ -1004,13 +1260,16 @@ class Revision implements IDBAccessObject {
* number on success and dies horribly on failure.
*
* @param $dbw DatabaseBase: (master connection)
+ * @throws MWException
* @return Integer
*/
public function insertOn( $dbw ) {
- global $wgDefaultExternalStore;
+ global $wgDefaultExternalStore, $wgContentHandlerUseDB;
wfProfileIn( __METHOD__ );
+ $this->checkContentModel();
+
$data = $this->mText;
$flags = self::compressRevisionText( $data );
@@ -1046,27 +1305,47 @@ class Revision implements IDBAccessObject {
$rev_id = isset( $this->mId )
? $this->mId
: $dbw->nextSequenceValue( 'revision_rev_id_seq' );
- $dbw->insert( 'revision',
- array(
- 'rev_id' => $rev_id,
- 'rev_page' => $this->mPage,
- 'rev_text_id' => $this->mTextId,
- 'rev_comment' => $this->mComment,
- 'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
- 'rev_user' => $this->mUser,
- 'rev_user_text' => $this->mUserText,
- 'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ),
- 'rev_deleted' => $this->mDeleted,
- 'rev_len' => $this->mSize,
- 'rev_parent_id' => is_null( $this->mParentId )
- ? $this->getPreviousRevisionId( $dbw )
- : $this->mParentId,
- 'rev_sha1' => is_null( $this->mSha1 )
- ? self::base36Sha1( $this->mText )
- : $this->mSha1
- ), __METHOD__
+ $row = array(
+ 'rev_id' => $rev_id,
+ 'rev_page' => $this->mPage,
+ 'rev_text_id' => $this->mTextId,
+ 'rev_comment' => $this->mComment,
+ 'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
+ 'rev_user' => $this->mUser,
+ 'rev_user_text' => $this->mUserText,
+ 'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ),
+ 'rev_deleted' => $this->mDeleted,
+ 'rev_len' => $this->mSize,
+ 'rev_parent_id' => is_null( $this->mParentId )
+ ? $this->getPreviousRevisionId( $dbw )
+ : $this->mParentId,
+ 'rev_sha1' => is_null( $this->mSha1 )
+ ? Revision::base36Sha1( $this->mText )
+ : $this->mSha1,
);
+ if ( $wgContentHandlerUseDB ) {
+ //NOTE: Store null for the default model and format, to save space.
+ //XXX: Makes the DB sensitive to changed defaults. Make this behavior optional? Only in miser mode?
+
+ $model = $this->getContentModel();
+ $format = $this->getContentFormat();
+
+ $title = $this->getTitle();
+
+ if ( $title === null ) {
+ throw new MWException( "Insufficient information to determine the title of the revision's page!" );
+ }
+
+ $defaultModel = ContentHandler::getDefaultModelFor( $title );
+ $defaultFormat = ContentHandler::getForModelID( $defaultModel )->getDefaultFormat();
+
+ $row[ 'rev_content_model' ] = ( $model === $defaultModel ) ? null : $model;
+ $row[ 'rev_content_format' ] = ( $format === $defaultFormat ) ? null : $format;
+ }
+
+ $dbw->insert( 'revision', $row, __METHOD__ );
+
$this->mId = !is_null( $rev_id ) ? $rev_id : $dbw->insertId();
wfRunHooks( 'RevisionInsertComplete', array( &$this, $data, $flags ) );
@@ -1075,6 +1354,52 @@ class Revision implements IDBAccessObject {
return $this->mId;
}
+ protected function checkContentModel() {
+ global $wgContentHandlerUseDB;
+
+ $title = $this->getTitle(); //note: may return null for revisions that have not yet been inserted.
+
+ $model = $this->getContentModel();
+ $format = $this->getContentFormat();
+ $handler = $this->getContentHandler();
+
+ if ( !$handler->isSupportedFormat( $format ) ) {
+ $t = $title->getPrefixedDBkey();
+
+ throw new MWException( "Can't use format $format with content model $model on $t" );
+ }
+
+ if ( !$wgContentHandlerUseDB && $title ) {
+ // if $wgContentHandlerUseDB is not set, all revisions must use the default content model and format.
+
+ $defaultModel = ContentHandler::getDefaultModelFor( $title );
+ $defaultHandler = ContentHandler::getForModelID( $defaultModel );
+ $defaultFormat = $defaultHandler->getDefaultFormat();
+
+ if ( $this->getContentModel() != $defaultModel ) {
+ $t = $title->getPrefixedDBkey();
+
+ throw new MWException( "Can't save non-default content model with \$wgContentHandlerUseDB disabled: "
+ . "model is $model , default for $t is $defaultModel" );
+ }
+
+ if ( $this->getContentFormat() != $defaultFormat ) {
+ $t = $title->getPrefixedDBkey();
+
+ throw new MWException( "Can't use non-default content format with \$wgContentHandlerUseDB disabled: "
+ . "format is $format, default for $t is $defaultFormat" );
+ }
+ }
+
+ $content = $this->getContent( Revision::RAW );
+
+ if ( !$content || !$content->isValid() ) {
+ $t = $title->getPrefixedDBkey();
+
+ throw new MWException( "Content of $t is not valid! Content model is $model" );
+ }
+ }
+
/**
* Get the base 36 SHA-1 value for a string of text
* @param $text String
@@ -1088,7 +1413,7 @@ class Revision implements IDBAccessObject {
* Lazy-load the revision's text.
* Currently hardcoded to the 'text' table storage engine.
*
- * @return String
+ * @return String|bool the revision's text, or false on failure
*/
protected function loadText() {
wfProfileIn( __METHOD__ );
@@ -1154,17 +1479,26 @@ class Revision implements IDBAccessObject {
*
* @param $dbw DatabaseBase
* @param $pageId Integer: ID number of the page to read from
- * @param $summary String: revision's summary
+ * @param string $summary revision's summary
* @param $minor Boolean: whether the revision should be considered as minor
* @return Revision|null on error
*/
public static function newNullRevision( $dbw, $pageId, $summary, $minor ) {
+ global $wgContentHandlerUseDB;
+
wfProfileIn( __METHOD__ );
+ $fields = array( 'page_latest', 'page_namespace', 'page_title',
+ 'rev_text_id', 'rev_len', 'rev_sha1' );
+
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'rev_content_model';
+ $fields[] = 'rev_content_format';
+ }
+
$current = $dbw->selectRow(
array( 'page', 'revision' ),
- array( 'page_latest', 'page_namespace', 'page_title',
- 'rev_text_id', 'rev_len', 'rev_sha1' ),
+ $fields,
array(
'page_id' => $pageId,
'page_latest=rev_id',
@@ -1172,7 +1506,7 @@ class Revision implements IDBAccessObject {
__METHOD__ );
if( $current ) {
- $revision = new Revision( array(
+ $row = array(
'page' => $pageId,
'comment' => $summary,
'minor_edit' => $minor,
@@ -1180,7 +1514,14 @@ class Revision implements IDBAccessObject {
'parent_id' => $current->page_latest,
'len' => $current->rev_len,
'sha1' => $current->rev_sha1
- ) );
+ );
+
+ if ( $wgContentHandlerUseDB ) {
+ $row[ 'content_model' ] = $current->rev_content_model;
+ $row[ 'content_format' ] = $current->rev_content_format;
+ }
+
+ $revision = new Revision( $row );
$revision->setTitle( Title::makeTitle( $current->page_namespace, $current->page_title ) );
} else {
$revision = null;
@@ -1245,7 +1586,7 @@ class Revision implements IDBAccessObject {
*/
static function getTimestampFromId( $title, $id ) {
$dbr = wfGetDB( DB_SLAVE );
- // Casting fix for DB2
+ // Casting fix for databases that can't take '' for rev_id
if ( $id == '' ) {
$id = 0;
}
@@ -1328,4 +1669,4 @@ class Revision implements IDBAccessObject {
}
return true;
}
-} \ No newline at end of file
+}
diff --git a/includes/RevisionList.php b/includes/RevisionList.php
index 3c5cfa8e..d87c540f 100644
--- a/includes/RevisionList.php
+++ b/includes/RevisionList.php
@@ -190,7 +190,7 @@ abstract class RevisionItemBase {
}
/**
- * Get the date, formatted in user's languae
+ * Get the date, formatted in user's language
* @return String
*/
public function formatDate() {
@@ -199,7 +199,7 @@ abstract class RevisionItemBase {
}
/**
- * Get the time, formatted in user's languae
+ * Get the time, formatted in user's language
* @return String
*/
public function formatTime() {
diff --git a/includes/Sanitizer.php b/includes/Sanitizer.php
index b443ce14..2dff081d 100644
--- a/includes/Sanitizer.php
+++ b/includes/Sanitizer.php
@@ -358,38 +358,48 @@ class Sanitizer {
* @private
* @param $text String
* @param $processCallback Callback to do any variable or parameter replacements in HTML attribute values
- * @param $args Array for the processing callback
- * @param $extratags Array for any extra tags to include
- * @param $removetags Array for any tags (default or extra) to exclude
+ * @param array $args for the processing callback
+ * @param array $extratags for any extra tags to include
+ * @param array $removetags for any tags (default or extra) to exclude
* @return string
*/
static function removeHTMLtags( $text, $processCallback = null, $args = array(), $extratags = array(), $removetags = array() ) {
- global $wgUseTidy;
+ global $wgUseTidy, $wgHtml5, $wgAllowMicrodataAttributes, $wgAllowImageTag;
static $htmlpairsStatic, $htmlsingle, $htmlsingleonly, $htmlnest, $tabletags,
$htmllist, $listtags, $htmlsingleallowed, $htmlelementsStatic, $staticInitialised;
wfProfileIn( __METHOD__ );
- if ( !$staticInitialised ) {
+ // Base our staticInitialised variable off of the global config state so that if the globals
+ // are changed (like in the screwed up test system) we will re-initialise the settings.
+ $globalContext = implode( '-', compact( 'wgHtml5', 'wgAllowMicrodataAttributes', 'wgAllowImageTag' ) );
+ if ( !$staticInitialised || $staticInitialised != $globalContext ) {
$htmlpairsStatic = array( # Tags that must be closed
'b', 'bdi', 'del', 'i', 'ins', 'u', 'font', 'big', 'small', 'sub', 'sup', 'h1',
'h2', 'h3', 'h4', 'h5', 'h6', 'cite', 'code', 'em', 's',
'strike', 'strong', 'tt', 'var', 'div', 'center',
'blockquote', 'ol', 'ul', 'dl', 'table', 'caption', 'pre',
- 'ruby', 'rt' , 'rb' , 'rp', 'p', 'span', 'abbr', 'dfn',
+ 'ruby', 'rt', 'rb', 'rp', 'p', 'span', 'abbr', 'dfn',
'kbd', 'samp'
);
+ if ( $wgHtml5 ) {
+ $htmlpairsStatic = array_merge( $htmlpairsStatic, array( 'data', 'time', 'mark' ) );
+ }
$htmlsingle = array(
'br', 'hr', 'li', 'dt', 'dd'
);
$htmlsingleonly = array( # Elements that cannot have close tags
'br', 'hr'
);
+ if ( $wgHtml5 && $wgAllowMicrodataAttributes ) {
+ $htmlsingle[] = $htmlsingleonly[] = 'meta';
+ $htmlsingle[] = $htmlsingleonly[] = 'link';
+ }
$htmlnest = array( # Tags that can be nested--??
'table', 'tr', 'td', 'th', 'div', 'blockquote', 'ol', 'ul',
- 'dl', 'font', 'big', 'small', 'sub', 'sup', 'span'
+ 'li', 'dl', 'dt', 'dd', 'font', 'big', 'small', 'sub', 'sup', 'span'
);
$tabletags = array( # Can only appear inside table, we will close them
'td', 'th', 'tr',
@@ -401,7 +411,6 @@ class Sanitizer {
'li',
);
- global $wgAllowImageTag;
if ( $wgAllowImageTag ) {
$htmlsingle[] = 'img';
$htmlsingleonly[] = 'img';
@@ -416,13 +425,13 @@ class Sanitizer {
foreach ( $vars as $var ) {
$$var = array_flip( $$var );
}
- $staticInitialised = true;
+ $staticInitialised = $globalContext;
}
# Populate $htmlpairs and $htmlelements with the $extratags and $removetags arrays
$extratags = array_flip( $extratags );
$removetags = array_flip( $removetags );
$htmlpairs = array_merge( $extratags, $htmlpairsStatic );
- $htmlelements = array_diff_key( array_merge( $extratags, $htmlelementsStatic ) , $removetags );
+ $htmlelements = array_diff_key( array_merge( $extratags, $htmlelementsStatic ), $removetags );
# Remove HTML comments
$text = Sanitizer::removeHTMLcomments( $text );
@@ -505,11 +514,15 @@ class Sanitizer {
isset( $htmlpairs[$t] ) ) {
$badtag = true;
} elseif ( isset( $htmlsingleonly[$t] ) ) {
- # Hack to force empty tag for uncloseable elements
+ # Hack to force empty tag for unclosable elements
$brace = '/>';
} elseif ( isset( $htmlsingle[$t] ) ) {
# Hack to not close $htmlsingle tags
$brace = null;
+ # Still need to push this optionally-closed tag to
+ # the tag stack so that we can match end tags
+ # instead of marking them as bad.
+ array_push( $tagstack, $t );
} elseif ( isset( $tabletags[$t] )
&& in_array( $t, $tagstack ) ) {
// New table tag but forgot to close the previous one
@@ -528,6 +541,10 @@ class Sanitizer {
call_user_func_array( $processCallback, array( &$params, $args ) );
}
+ if ( !Sanitizer::validateTag( $params, $t ) ) {
+ $badtag = true;
+ }
+
# Strip non-approved attributes from the tag
$newparams = Sanitizer::fixTagAttributes( $params, $t );
}
@@ -551,16 +568,24 @@ class Sanitizer {
preg_match( '/^(\\/?)(\\w+)([^>]*?)(\\/{0,1}>)([^<]*)$/',
$x, $regs );
@list( /* $qbar */, $slash, $t, $params, $brace, $rest ) = $regs;
+ $badtag = false;
if ( isset( $htmlelements[$t = strtolower( $t )] ) ) {
if( is_callable( $processCallback ) ) {
call_user_func_array( $processCallback, array( &$params, $args ) );
}
+
+ if ( !Sanitizer::validateTag( $params, $t ) ) {
+ $badtag = true;
+ }
+
$newparams = Sanitizer::fixTagAttributes( $params, $t );
- $rest = str_replace( '>', '&gt;', $rest );
- $text .= "<$slash$t$newparams$brace$rest";
- } else {
- $text .= '&lt;' . str_replace( '>', '&gt;', $x);
+ if ( !$badtag ) {
+ $rest = str_replace( '>', '&gt;', $rest );
+ $text .= "<$slash$t$newparams$brace$rest";
+ continue;
+ }
}
+ $text .= '&lt;' . str_replace( '>', '&gt;', $x);
}
}
wfProfileOut( __METHOD__ );
@@ -579,9 +604,9 @@ class Sanitizer {
*/
static function removeHTMLcomments( $text ) {
wfProfileIn( __METHOD__ );
- while (($start = strpos($text, '<!--')) !== false) {
- $end = strpos($text, '-->', $start + 4);
- if ($end === false) {
+ while ( ($start = strpos( $text, '<!--' ) ) !== false ) {
+ $end = strpos( $text, '-->', $start + 4 );
+ if ( $end === false ) {
# Unterminated comment; bail out
break;
}
@@ -590,22 +615,22 @@ class Sanitizer {
# Trim space and newline if the comment is both
# preceded and followed by a newline
- $spaceStart = max($start - 1, 0);
+ $spaceStart = max( $start - 1, 0 );
$spaceLen = $end - $spaceStart;
- while (substr($text, $spaceStart, 1) === ' ' && $spaceStart > 0) {
+ while ( substr( $text, $spaceStart, 1 ) === ' ' && $spaceStart > 0 ) {
$spaceStart--;
$spaceLen++;
}
- while (substr($text, $spaceStart + $spaceLen, 1) === ' ')
+ while ( substr( $text, $spaceStart + $spaceLen, 1 ) === ' ' )
$spaceLen++;
- if (substr($text, $spaceStart, 1) === "\n" and substr($text, $spaceStart + $spaceLen, 1) === "\n") {
+ if ( substr( $text, $spaceStart, 1 ) === "\n" and substr( $text, $spaceStart + $spaceLen, 1 ) === "\n" ) {
# Remove the comment, leading and trailing
# spaces, and leave only one newline.
- $text = substr_replace($text, "\n", $spaceStart, $spaceLen + 1);
+ $text = substr_replace( $text, "\n", $spaceStart, $spaceLen + 1 );
}
else {
# Remove just the comment.
- $text = substr_replace($text, '', $start, $end - $start);
+ $text = substr_replace( $text, '', $start, $end - $start );
}
}
wfProfileOut( __METHOD__ );
@@ -613,12 +638,45 @@ class Sanitizer {
}
/**
+ * Takes attribute names and values for a tag and the tag name and
+ * validates that the tag is allowed to be present.
+ * This DOES NOT validate the attributes, nor does it validate the
+ * tags themselves. This method only handles the special circumstances
+ * where we may want to allow a tag within content but ONLY when it has
+ * specific attributes set.
+ *
+ * @param $params
+ * @param $element
+ * @return bool
+ */
+ static function validateTag( $params, $element ) {
+ $params = Sanitizer::decodeTagAttributes( $params );
+
+ if ( $element == 'meta' || $element == 'link' ) {
+ if ( !isset( $params['itemprop'] ) ) {
+ // <meta> and <link> must have an itemprop="" otherwise they are not valid or safe in content
+ return false;
+ }
+ if ( $element == 'meta' && !isset( $params['content'] ) ) {
+ // <meta> must have a content="" for the itemprop
+ return false;
+ }
+ if ( $element == 'link' && !isset( $params['href'] ) ) {
+ // <link> must have an associated href=""
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
* Take an array of attribute names and values and normalize or discard
* illegal values for the given element type.
*
* - Discards attributes not on a whitelist for the given element
* - Unsafe style attributes are discarded
- * - Invalid id attributes are reencoded
+ * - Invalid id attributes are re-encoded
*
* @param $attribs Array
* @param $element String
@@ -638,10 +696,10 @@ class Sanitizer {
*
* - Discards attributes not the given whitelist
* - Unsafe style attributes are discarded
- * - Invalid id attributes are reencoded
+ * - Invalid id attributes are re-encoded
*
* @param $attribs Array
- * @param $whitelist Array: list of allowed attribute names
+ * @param array $whitelist list of allowed attribute names
* @return Array
*
* @todo Check for legal values where the DTD limits things.
@@ -679,6 +737,16 @@ class Sanitizer {
$value = Sanitizer::escapeId( $value, 'noninitial' );
}
+ # WAI-ARIA
+ # http://www.w3.org/TR/wai-aria/
+ # http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#wai-aria
+ # For now we only support role="presentation" until we work out what roles should be
+ # usable by content and we ensure that our code explicitly rejects patterns that
+ # violate HTML5's ARIA restrictions.
+ if ( $attribute === 'role' && $value !== 'presentation' ) {
+ continue;
+ }
+
//RDFa and microdata properties allow URLs, URIs and/or CURIs. check them for sanity
if ( $attribute === 'rel' || $attribute === 'rev' ||
$attribute === 'about' || $attribute === 'property' || $attribute === 'resource' || #RDFa
@@ -713,7 +781,7 @@ class Sanitizer {
unset( $out['itemid'] );
unset( $out['itemref'] );
}
- # TODO: Strip itemprop if we aren't descendants of an itemscope.
+ # TODO: Strip itemprop if we aren't descendants of an itemscope or pointed to by an itemref.
}
return $out;
}
@@ -804,7 +872,7 @@ class Sanitizer {
// Reject problematic keywords and control characters
if ( preg_match( '/[\000-\010\016-\037\177]/', $value ) ) {
return '/* invalid control char */';
- } elseif ( preg_match( '! expression | filter\s*: | accelerator\s*: | url\s*\( !ix', $value ) ) {
+ } elseif ( preg_match( '! expression | filter\s*: | accelerator\s*: | url\s*\( | image\s*\( | image-set\s*\( !ix', $value ) ) {
return '/* insecure input */';
}
return $value;
@@ -945,7 +1013,7 @@ class Sanitizer {
* @see http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-id-attribute
* HTML5 definition of id attribute
*
- * @param $id String: id to escape
+ * @param string $id id to escape
* @param $options Mixed: string or array of strings (default is array()):
* 'noninitial': This is a non-initial fragment of an id, not a full id,
* so don't pay attention if the first character isn't valid at the
@@ -982,7 +1050,7 @@ class Sanitizer {
$id = str_replace( array_keys( $replace ), array_values( $replace ), $id );
if ( !preg_match( '/^[a-zA-Z]/', $id )
- && !in_array( 'noninitial', $options ) ) {
+ && !in_array( 'noninitial', $options ) ) {
// Initial character must be a letter!
$id = "x$id";
}
@@ -1002,17 +1070,17 @@ class Sanitizer {
*/
static function escapeClass( $class ) {
// Convert ugly stuff to underscores and kill underscores in ugly places
- return rtrim(preg_replace(
- array('/(^[0-9\\-])|[\\x00-\\x20!"#$%&\'()*+,.\\/:;<=>?@[\\]^`{|}~]|\\xC2\\xA0/','/_+/'),
+ return rtrim( preg_replace(
+ array( '/(^[0-9\\-])|[\\x00-\\x20!"#$%&\'()*+,.\\/:;<=>?@[\\]^`{|}~]|\\xC2\\xA0/', '/_+/' ),
'_',
- $class ), '_');
+ $class ), '_' );
}
/**
- * Given HTML input, escape with htmlspecialchars but un-escape entites.
+ * Given HTML input, escape with htmlspecialchars but un-escape entities.
* This allows (generally harmless) entities like &#160; to survive.
*
- * @param $html String to escape
+ * @param string $html to escape
* @return String: escaped input
*/
static function escapeHtmlAllowEntities( $html ) {
@@ -1074,6 +1142,7 @@ class Sanitizer {
* attribs regex matches.
*
* @param $set Array
+ * @throws MWException
* @return String
*/
private static function getTagAttributeCallback( $set ) {
@@ -1169,7 +1238,7 @@ class Sanitizer {
$ret = Sanitizer::normalizeEntity( $matches[1] );
} elseif( $matches[2] != '' ) {
$ret = Sanitizer::decCharReference( $matches[2] );
- } elseif( $matches[3] != '' ) {
+ } elseif( $matches[3] != '' ) {
$ret = Sanitizer::hexCharReference( $matches[3] );
}
if( is_null( $ret ) ) {
@@ -1263,7 +1332,7 @@ class Sanitizer {
* This is useful for page titles, not for text to be displayed,
* MediaWiki allows HTML entities to escape normalization as a feature.
*
- * @param $text String (already normalized, containing entities)
+ * @param string $text (already normalized, containing entities)
* @return String (still normalized, without entities)
*/
public static function decodeCharReferencesAndNormalize( $text ) {
@@ -1289,7 +1358,7 @@ class Sanitizer {
return Sanitizer::decodeEntity( $matches[1] );
} elseif( $matches[2] != '' ) {
return Sanitizer::decodeChar( intval( $matches[2] ) );
- } elseif( $matches[3] != '' ) {
+ } elseif( $matches[3] != '' ) {
return Sanitizer::decodeChar( hexdec( $matches[3] ) );
}
# Last case should be an ampersand by itself
@@ -1337,10 +1406,7 @@ class Sanitizer {
* @return Array
*/
static function attributeWhitelist( $element ) {
- static $list;
- if( !isset( $list ) ) {
- $list = Sanitizer::setupAttributeWhitelist();
- }
+ $list = Sanitizer::setupAttributeWhitelist();
return isset( $list[$element] )
? $list[$element]
: array();
@@ -1354,7 +1420,25 @@ class Sanitizer {
static function setupAttributeWhitelist() {
global $wgAllowRdfaAttributes, $wgHtml5, $wgAllowMicrodataAttributes;
- $common = array( 'id', 'class', 'lang', 'dir', 'title', 'style' );
+ static $whitelist, $staticInitialised;
+ $globalContext = implode( '-', compact( 'wgAllowRdfaAttributes', 'wgHtml5', 'wgAllowMicrodataAttributes' ) );
+
+ if ( isset( $whitelist ) && $staticInitialised == $globalContext ) {
+ return $whitelist;
+ }
+
+ $common = array(
+ # HTML
+ 'id',
+ 'class',
+ 'style',
+ 'lang',
+ 'dir',
+ 'title',
+
+ # WAI-ARIA
+ 'role',
+ );
if ( $wgAllowRdfaAttributes ) {
#RDFa attributes as specified in section 9 of http://www.w3.org/TR/2008/REC-rdfa-syntax-20081014
@@ -1364,7 +1448,7 @@ class Sanitizer {
}
if ( $wgHtml5 && $wgAllowMicrodataAttributes ) {
- # add HTML5 microdata tages as pecified by http://www.whatwg.org/specs/web-apps/current-work/multipage/microdata.html#the-microdata-model
+ # add HTML5 microdata tags as specified by http://www.whatwg.org/specs/web-apps/current-work/multipage/microdata.html#the-microdata-model
$common = array_merge( $common, array(
'itemid', 'itemprop', 'itemref', 'itemscope', 'itemtype'
) );
@@ -1386,7 +1470,7 @@ class Sanitizer {
# Numbers refer to sections in HTML 4.01 standard describing the element.
# See: http://www.w3.org/TR/html4/
- $whitelist = array (
+ $whitelist = array(
# 7.5.4
'div' => $block,
'center' => $common, # deprecated
@@ -1518,7 +1602,28 @@ class Sanitizer {
# HTML 5 section 4.6
'bdi' => $common,
+ );
+
+ if ( $wgHtml5 ) {
+ # HTML5 elements, defined by:
+ # http://www.whatwg.org/specs/web-apps/current-work/multipage/
+ $whitelist += array(
+ 'data' => array_merge( $common, array( 'value' ) ),
+ 'time' => array_merge( $common, array( 'datetime' ) ),
+ 'mark' => $common,
+
+ // meta and link are only permitted by removeHTMLtags when Microdata
+ // is enabled so we don't bother adding a conditional to hide these
+ // Also meta and link are only valid in WikiText as Microdata elements
+ // (ie: validateTag rejects tags missing the attributes needed for Microdata)
+ // So we don't bother including $common attributes that have no purpose.
+ 'meta' => array( 'itemprop', 'content' ),
+ 'link' => array( 'itemprop', 'href' ),
);
+ }
+
+ $staticInitialised = $globalContext;
+
return $whitelist;
}
@@ -1529,7 +1634,7 @@ class Sanitizer {
* Warning: this return value must be further escaped for literal
* inclusion in HTML output as of 1.10!
*
- * @param $text String: HTML fragment
+ * @param string $text HTML fragment
* @return String
*/
static function stripAllTags( $text ) {
@@ -1641,7 +1746,7 @@ class Sanitizer {
*
* @since 1.18
*
- * @param $addr String E-mail address
+ * @param string $addr E-mail address
* @return Bool
*/
public static function validateEmail( $addr ) {
@@ -1653,8 +1758,8 @@ class Sanitizer {
// Please note strings below are enclosed in brackets [], this make the
// hyphen "-" a range indicator. Hence it is double backslashed below.
// See bug 26948
- $rfc5322_atext = "a-z0-9!#$%&'*+\\-\/=?^_`{|}~" ;
- $rfc1034_ldh_str = "a-z0-9\\-" ;
+ $rfc5322_atext = "a-z0-9!#$%&'*+\\-\/=?^_`{|}~";
+ $rfc1034_ldh_str = "a-z0-9\\-";
$HTML5_email_regexp = "/
^ # start of string
@@ -1663,7 +1768,7 @@ class Sanitizer {
[$rfc1034_ldh_str]+ # First domain part
(\\.[$rfc1034_ldh_str]+)* # Following part prefixed with a dot
$ # End of string
- /ix" ; // case Insensitive, eXtended
+ /ix"; // case Insensitive, eXtended
return (bool) preg_match( $HTML5_email_regexp, $addr );
}
diff --git a/includes/ScopedCallback.php b/includes/ScopedCallback.php
new file mode 100644
index 00000000..1d5b26bf
--- /dev/null
+++ b/includes/ScopedCallback.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * This file deals with RAII style scoped callbacks.
+ *
+ * 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
+ */
+
+/**
+ * Class for asserting that a callback happens when an dummy object leaves scope
+ */
+class ScopedCallback {
+ /** @var Closure */
+ protected $callback;
+
+ /**
+ * @param $callback Closure
+ */
+ public function __construct( Closure $callback ) {
+ $this->callback = $callback;
+ }
+
+ function __destruct() {
+ call_user_func( $this->callback );
+ }
+}
diff --git a/includes/SeleniumWebSettings.php b/includes/SeleniumWebSettings.php
index 7b98568d..7715484e 100644
--- a/includes/SeleniumWebSettings.php
+++ b/includes/SeleniumWebSettings.php
@@ -38,7 +38,7 @@ $cookieName = $cookiePrefix . 'Selenium';
// this is a fallback SQL file
$testSqlFile = false;
$testImageZip = false;
-
+
// if we find a request parameter containing the test name, set a cookie with the test name
if ( isset( $_GET['setupTestSuite'] ) ) {
$setupTestSuiteName = $_GET['setupTestSuite'];
@@ -62,9 +62,9 @@ if ( isset( $_GET['setupTestSuite'] ) ) {
true
);
}
-
+
$testIncludes = array(); // array containing all the includes needed for this test
- $testGlobalConfigs = array(); // an array containg all the global configs needed for this test
+ $testGlobalConfigs = array(); // an array containing all the global configs needed for this test
$testResourceFiles = array(); // an array containing all the resource files needed for this test
$callback = $wgSeleniumTestConfigs[$setupTestSuiteName];
call_user_func_array( $callback, array( &$testIncludes, &$testGlobalConfigs, &$testResourceFiles));
@@ -72,11 +72,11 @@ if ( isset( $_GET['setupTestSuite'] ) ) {
if ( isset( $testResourceFiles['images'] ) ) {
$testImageZip = $testResourceFiles['images'];
}
-
+
if ( isset( $testResourceFiles['db'] ) ) {
$testSqlFile = $testResourceFiles['db'];
$testResourceName = getTestResourceNameFromTestSuiteName( $setupTestSuiteName );
-
+
switchToTestResources( $testResourceName, false ); // false means do not switch database yet
setupTestResources( $testResourceName, $testSqlFile, $testImageZip );
}
@@ -86,7 +86,7 @@ if ( isset( $_GET['setupTestSuite'] ) ) {
if ( isset( $_GET['clearTestSuite'] ) ) {
$testSuiteName = getTestSuiteNameFromCookie( $cookieName );
- $expire = time() - 600;
+ $expire = time() - 600;
setcookie(
$cookieName,
'',
@@ -96,22 +96,22 @@ if ( isset( $_GET['clearTestSuite'] ) ) {
$wgCookieSecure,
true
);
-
+
$testResourceName = getTestResourceNameFromTestSuiteName( $testSuiteName );
teardownTestResources( $testResourceName );
}
// if a cookie is found, run the appropriate callback to get the config params.
-if ( isset( $_COOKIE[$cookieName] ) ) {
+if ( isset( $_COOKIE[$cookieName] ) ) {
$testSuiteName = getTestSuiteNameFromCookie( $cookieName );
if ( !isset( $wgSeleniumTestConfigs[$testSuiteName] ) ) {
return;
}
-
+
$testIncludes = array(); // array containing all the includes needed for this test
- $testGlobalConfigs = array(); // an array containg all the global configs needed for this test
+ $testGlobalConfigs = array(); // an array containing all the global configs needed for this test
$testResourceFiles = array(); // an array containing all the resource files needed for this test
- $callback = $wgSeleniumTestConfigs[$testSuiteName];
+ $callback = $wgSeleniumTestConfigs[$testSuiteName];
call_user_func_array( $callback, array( &$testIncludes, &$testGlobalConfigs, &$testResourceFiles));
if ( isset( $testResourceFiles['db'] ) ) {
@@ -123,9 +123,8 @@ if ( isset( $_COOKIE[$cookieName] ) ) {
require_once( $file );
}
foreach ( $testGlobalConfigs as $key => $value ) {
- if ( is_array( $value ) ) {
+ if ( is_array( $value ) ) {
$GLOBALS[$key] = array_merge( $GLOBALS[$key], $value );
-
} else {
$GLOBALS[$key] = $value;
}
@@ -166,7 +165,7 @@ function setupTestResources( $testResourceName, $testSqlFile, $testImageZip ) {
if ( $testResourceName == '' ) {
die( 'Cannot identify a test the resources should be installed for.' );
}
-
+
// create tables
$dbw = wfGetDB( DB_MASTER );
$dbw->query( 'DROP DATABASE IF EXISTS ' . $testResourceName );
diff --git a/includes/Setup.php b/includes/Setup.php
index 924c3c07..e87b200f 100644
--- a/includes/Setup.php
+++ b/includes/Setup.php
@@ -50,27 +50,27 @@ if ( $wgLoadScript === false ) $wgLoadScript = "$wgScriptPath/load$wgScriptExten
if ( $wgArticlePath === false ) {
if ( $wgUsePathInfo ) {
- $wgArticlePath = "$wgScript/$1";
+ $wgArticlePath = "$wgScript/$1";
} else {
- $wgArticlePath = "$wgScript?title=$1";
+ $wgArticlePath = "$wgScript?title=$1";
}
}
-if ( !empty($wgActionPaths) && !isset($wgActionPaths['view']) ) {
+if ( !empty( $wgActionPaths ) && !isset( $wgActionPaths['view'] ) ) {
# 'view' is assumed the default action path everywhere in the code
# but is rarely filled in $wgActionPaths
$wgActionPaths['view'] = $wgArticlePath;
}
-if ( !empty($wgActionPaths) && !isset($wgActionPaths['view']) ) {
+if ( !empty( $wgActionPaths ) && !isset( $wgActionPaths['view'] ) ) {
# 'view' is assumed the default action path everywhere in the code
# but is rarely filled in $wgActionPaths
- $wgActionPaths['view'] = $wgArticlePath ;
+ $wgActionPaths['view'] = $wgArticlePath;
}
if ( $wgStylePath === false ) $wgStylePath = "$wgScriptPath/skins";
if ( $wgLocalStylePath === false ) $wgLocalStylePath = "$wgScriptPath/skins";
-if ( $wgStyleDirectory === false ) $wgStyleDirectory = "$IP/skins";
+if ( $wgStyleDirectory === false ) $wgStyleDirectory = "$IP/skins";
if ( $wgExtensionAssetsPath === false ) $wgExtensionAssetsPath = "$wgScriptPath/extensions";
if ( $wgLogo === false ) $wgLogo = "$wgStylePath/common/images/wiki.png";
@@ -317,19 +317,13 @@ if ( $wgUseFileCache || $wgUseSquid ) {
$wgDebugToolbar = false;
}
-# $wgAllowRealName and $wgAllowUserSkin were removed in 1.16
-# in favor of $wgHiddenPrefs, handle b/c here
-if ( !$wgAllowRealName ) {
- $wgHiddenPrefs[] = 'realname';
-}
-
# Doesn't make sense to have if disabled.
if ( !$wgEnotifMinorEdits ) {
$wgHiddenPrefs[] = 'enotifminoredits';
}
# $wgDisabledActions is deprecated as of 1.18
-foreach( $wgDisabledActions as $action ){
+foreach( $wgDisabledActions as $action ) {
$wgActions[$action] = false;
}
@@ -343,7 +337,7 @@ if ( !$wgHtml5Version && $wgHtml5 && $wgAllowRdfaAttributes ) {
}
# Blacklisted file extensions shouldn't appear on the "allowed" list
-$wgFileExtensions = array_diff ( $wgFileExtensions, $wgFileBlacklist );
+$wgFileExtensions = array_values( array_diff ( $wgFileExtensions, $wgFileBlacklist ) );
if ( $wgArticleCountMethod === null ) {
$wgArticleCountMethod = $wgUseCommaCount ? 'comma' : 'link';
@@ -359,17 +353,18 @@ if ( $wgAjaxUploadDestCheck ) {
if ( $wgNewUserLog ) {
# Add a new log type
- $wgLogTypes[] = 'newusers';
- $wgLogNames['newusers'] = 'newuserlogpage';
- $wgLogHeaders['newusers'] = 'newuserlogpagetext';
+ $wgLogTypes[] = 'newusers';
+ $wgLogNames['newusers'] = 'newuserlogpage';
+ $wgLogHeaders['newusers'] = 'newuserlogpagetext';
$wgLogActionsHandlers['newusers/newusers'] = 'NewUsersLogFormatter';
$wgLogActionsHandlers['newusers/create'] = 'NewUsersLogFormatter';
$wgLogActionsHandlers['newusers/create2'] = 'NewUsersLogFormatter';
+ $wgLogActionsHandlers['newusers/byemail'] = 'NewUsersLogFormatter';
$wgLogActionsHandlers['newusers/autocreate'] = 'NewUsersLogFormatter';
}
if ( $wgCookieSecure === 'detect' ) {
- $wgCookieSecure = ( WebRequest::detectProtocol() === 'https:' );
+ $wgCookieSecure = ( WebRequest::detectProtocol() === 'https' );
}
// Disable MWDebug for command line mode, this prevents MWDebug from eating up
@@ -396,6 +391,11 @@ if ( !defined( 'MW_COMPILED' ) ) {
wfProfileOut( $fname . '-includes' );
}
+if ( $wgSecureLogin && substr( $wgServer, 0, 2 ) !== '//' ) {
+ $wgSecureLogin = false;
+ wfWarn( 'Secure login was enabled on a server that only supports HTTP or HTTPS. Disabling secure login.' );
+}
+
# Now that GlobalFunctions is loaded, set defaults that depend
# on it.
if ( $wgTmpDirectory === false ) {
diff --git a/includes/SiteConfiguration.php b/includes/SiteConfiguration.php
index 6a861d8e..bbc14a12 100644
--- a/includes/SiteConfiguration.php
+++ b/includes/SiteConfiguration.php
@@ -83,7 +83,7 @@
* $conf->settings = array(
* 'wgMergeSetting' = array(
* # Value that will be shared among all wikis:
- * 'default' => array( NS_USER => true ),
+ * 'default' => array( NS_USER => true ),
*
* # Leading '+' means merging the array of value with the defaults
* '+beta' => array( NS_HELP => true ),
@@ -162,12 +162,18 @@ class SiteConfiguration {
public $siteParamsCallback = null;
/**
+ * Configuration cache for getConfig()
+ * @var array
+ */
+ protected $cfgCache = array();
+
+ /**
* Retrieves a configuration setting for a given wiki.
- * @param $settingName String ID of the setting name to retrieve
- * @param $wiki String Wiki ID of the wiki in question.
- * @param $suffix String The suffix of the wiki in question.
- * @param $params Array List of parameters. $.'key' is replaced by $value in all returned data.
- * @param $wikiTags Array The tags assigned to the wiki.
+ * @param string $settingName ID of the setting name to retrieve
+ * @param string $wiki Wiki ID of the wiki in question.
+ * @param string $suffix The suffix of the wiki in question.
+ * @param array $params List of parameters. $.'key' is replaced by $value in all returned data.
+ * @param array $wikiTags The tags assigned to the wiki.
* @return Mixed the value of the setting requested.
*/
public function get( $settingName, $wiki, $suffix = null, $params = array(), $wikiTags = array() ) {
@@ -178,12 +184,12 @@ class SiteConfiguration {
/**
* Really retrieves a configuration setting for a given wiki.
*
- * @param $settingName String ID of the setting name to retrieve.
- * @param $wiki String Wiki ID of the wiki in question.
- * @param $params Array: array of parameters.
+ * @param string $settingName ID of the setting name to retrieve.
+ * @param string $wiki Wiki ID of the wiki in question.
+ * @param array $params array of parameters.
* @return Mixed the value of the setting requested.
*/
- protected function getSetting( $settingName, $wiki, /*array*/ $params ){
+ protected function getSetting( $settingName, $wiki, /*array*/ $params ) {
$retval = null;
if( array_key_exists( $settingName, $this->settings ) ) {
$thisSetting =& $this->settings[$settingName];
@@ -205,7 +211,7 @@ class SiteConfiguration {
$retval = $thisSetting[$tag];
}
break 2;
- } elseif( array_key_exists( "+$tag", $thisSetting ) && is_array($thisSetting["+$tag"]) ) {
+ } elseif( array_key_exists( "+$tag", $thisSetting ) && is_array( $thisSetting["+$tag"] ) ) {
if( !isset( $retval ) ) {
$retval = array();
}
@@ -216,7 +222,7 @@ class SiteConfiguration {
$suffix = $params['suffix'];
if( !is_null( $suffix ) ) {
if( array_key_exists( $suffix, $thisSetting ) ) {
- if ( isset($retval) && is_array($retval) && is_array($thisSetting[$suffix]) ) {
+ if ( isset( $retval ) && is_array( $retval ) && is_array( $thisSetting[$suffix] ) ) {
$retval = self::arrayMerge( $retval, $thisSetting[$suffix] );
} else {
$retval = $thisSetting[$suffix];
@@ -274,10 +280,10 @@ class SiteConfiguration {
/**
* Gets all settings for a wiki
- * @param $wiki String Wiki ID of the wiki in question.
- * @param $suffix String The suffix of the wiki in question.
- * @param $params Array List of parameters. $.'key' is replaced by $value in all returned data.
- * @param $wikiTags Array The tags assigned to the wiki.
+ * @param string $wiki Wiki ID of the wiki in question.
+ * @param string $suffix The suffix of the wiki in question.
+ * @param array $params List of parameters. $.'key' is replaced by $value in all returned data.
+ * @param array $wikiTags The tags assigned to the wiki.
* @return Array Array of settings requested.
*/
public function getAll( $wiki, $suffix = null, $params = array(), $wikiTags = array() ) {
@@ -304,10 +310,10 @@ class SiteConfiguration {
/**
* Retrieves a configuration setting for a given wiki, forced to a boolean.
- * @param $setting String ID of the setting name to retrieve
- * @param $wiki String Wiki ID of the wiki in question.
- * @param $suffix String The suffix of the wiki in question.
- * @param $wikiTags Array The tags assigned to the wiki.
+ * @param string $setting ID of the setting name to retrieve
+ * @param string $wiki Wiki ID of the wiki in question.
+ * @param string $suffix The suffix of the wiki in question.
+ * @param array $wikiTags The tags assigned to the wiki.
* @return bool The value of the setting requested.
*/
public function getBool( $setting, $wiki, $suffix = null, $wikiTags = array() ) {
@@ -325,12 +331,12 @@ class SiteConfiguration {
/**
* Retrieves the value of a given setting, and places it in a variable passed by reference.
- * @param $setting String ID of the setting name to retrieve
- * @param $wiki String Wiki ID of the wiki in question.
- * @param $suffix String The suffix of the wiki in question.
- * @param $var array Reference The variable to insert the value into.
- * @param $params Array List of parameters. $.'key' is replaced by $value in all returned data.
- * @param $wikiTags Array The tags assigned to the wiki.
+ * @param string $setting ID of the setting name to retrieve
+ * @param string $wiki Wiki ID of the wiki in question.
+ * @param string $suffix The suffix of the wiki in question.
+ * @param array $var Reference The variable to insert the value into.
+ * @param array $params List of parameters. $.'key' is replaced by $value in all returned data.
+ * @param array $wikiTags The tags assigned to the wiki.
*/
public function extractVar( $setting, $wiki, $suffix, &$var, $params = array(), $wikiTags = array() ) {
$value = $this->get( $setting, $wiki, $suffix, $params, $wikiTags );
@@ -341,11 +347,11 @@ class SiteConfiguration {
/**
* Retrieves the value of a given setting, and places it in its corresponding global variable.
- * @param $setting String ID of the setting name to retrieve
- * @param $wiki String Wiki ID of the wiki in question.
- * @param $suffix String The suffix of the wiki in question.
- * @param $params Array List of parameters. $.'key' is replaced by $value in all returned data.
- * @param $wikiTags Array The tags assigned to the wiki.
+ * @param string $setting ID of the setting name to retrieve
+ * @param string $wiki Wiki ID of the wiki in question.
+ * @param string $suffix The suffix of the wiki in question.
+ * @param array $params List of parameters. $.'key' is replaced by $value in all returned data.
+ * @param array $wikiTags The tags assigned to the wiki.
*/
public function extractGlobal( $setting, $wiki, $suffix = null, $params = array(), $wikiTags = array() ) {
$params = $this->mergeParams( $wiki, $suffix, $params, $wikiTags );
@@ -360,9 +366,9 @@ class SiteConfiguration {
public function extractGlobalSetting( $setting, $wiki, $params ) {
$value = $this->getSetting( $setting, $wiki, $params );
if ( !is_null( $value ) ) {
- if (substr($setting,0,1) == '+' && is_array($value)) {
- $setting = substr($setting,1);
- if ( is_array($GLOBALS[$setting]) ) {
+ if ( substr( $setting, 0, 1 ) == '+' && is_array( $value ) ) {
+ $setting = substr( $setting, 1 );
+ if ( is_array( $GLOBALS[$setting] ) ) {
$GLOBALS[$setting] = self::arrayMerge( $GLOBALS[$setting], $value );
} else {
$GLOBALS[$setting] = $value;
@@ -375,10 +381,10 @@ class SiteConfiguration {
/**
* Retrieves the values of all settings, and places them in their corresponding global variables.
- * @param $wiki String Wiki ID of the wiki in question.
- * @param $suffix String The suffix of the wiki in question.
- * @param $params Array List of parameters. $.'key' is replaced by $value in all returned data.
- * @param $wikiTags Array The tags assigned to the wiki.
+ * @param string $wiki Wiki ID of the wiki in question.
+ * @param string $suffix The suffix of the wiki in question.
+ * @param array $params List of parameters. $.'key' is replaced by $value in all returned data.
+ * @param array $wikiTags The tags assigned to the wiki.
*/
public function extractAllGlobals( $wiki, $suffix = null, $params = array(), $wikiTags = array() ) {
$params = $this->mergeParams( $wiki, $suffix, $params, $wikiTags );
@@ -395,7 +401,7 @@ class SiteConfiguration {
* @param $wiki String
* @return array
*/
- protected function getWikiParams( $wiki ){
+ protected function getWikiParams( $wiki ) {
static $default = array(
'suffix' => null,
'lang' => null,
@@ -413,7 +419,7 @@ class SiteConfiguration {
return $default;
}
- foreach( $default as $name => $def ){
+ foreach( $default as $name => $def ) {
if( !isset( $ret[$name] ) || ( is_array( $default[$name] ) && !is_array( $ret[$name] ) ) ) {
$ret[$name] = $default[$name];
}
@@ -427,14 +433,14 @@ class SiteConfiguration {
* by self::$siteParamsCallback for backward compatibility
* Values returned by self::getWikiParams() have the priority.
*
- * @param $wiki String Wiki ID of the wiki in question.
- * @param $suffix String The suffix of the wiki in question.
- * @param $params Array List of parameters. $.'key' is replaced by $value in
+ * @param string $wiki Wiki ID of the wiki in question.
+ * @param string $suffix The suffix of the wiki in question.
+ * @param array $params List of parameters. $.'key' is replaced by $value in
* all returned data.
- * @param $wikiTags Array The tags assigned to the wiki.
+ * @param array $wikiTags The tags assigned to the wiki.
* @return array
*/
- protected function mergeParams( $wiki, $suffix, /*array*/ $params, /*array*/ $wikiTags ){
+ protected function mergeParams( $wiki, $suffix, /*array*/ $params, /*array*/ $wikiTags ) {
$ret = $this->getWikiParams( $wiki );
if( is_null( $ret['suffix'] ) ) {
@@ -446,7 +452,7 @@ class SiteConfiguration {
$ret['params'] += $params;
// Automatically fill that ones if needed
- if( !isset( $ret['params']['lang'] ) && !is_null( $ret['lang'] ) ){
+ if( !isset( $ret['params']['lang'] ) && !is_null( $ret['lang'] ) ) {
$ret['params']['lang'] = $ret['lang'];
}
if( !isset( $ret['params']['site'] ) && !is_null( $ret['suffix'] ) ) {
@@ -487,6 +493,67 @@ class SiteConfiguration {
}
/**
+ * Get the resolved (post-setup) configuration of a potentially foreign wiki.
+ * For foreign wikis, this is expensive, and only works if maintenance
+ * scripts are setup to handle the --wiki parameter such as in wiki farms.
+ *
+ * @param string $wiki
+ * @param array|string $settings A setting name or array of setting names
+ * @return Array|mixed Array if $settings is an array, otherwise the value
+ * @throws MWException
+ * @since 1.21
+ */
+ public function getConfig( $wiki, $settings ) {
+ global $IP;
+
+ $multi = is_array( $settings );
+ $settings = (array)$settings;
+ if ( $wiki === wfWikiID() ) { // $wiki is this wiki
+ $res = array();
+ foreach ( $settings as $name ) {
+ if ( !preg_match( '/^wg[A-Z]/', $name ) ) {
+ throw new MWException( "Variable '$name' does start with 'wg'." );
+ } elseif ( !isset( $GLOBALS[$name] ) ) {
+ throw new MWException( "Variable '$name' is not set." );
+ }
+ $res[$name] = $GLOBALS[$name];
+ }
+ } else { // $wiki is a foreign wiki
+ if ( isset( $this->cfgCache[$wiki] ) ) {
+ $res = array_intersect_key( $this->cfgCache[$wiki], array_flip( $settings ) );
+ if ( count( $res ) == count( $settings ) ) {
+ return $res; // cache hit
+ }
+ } elseif ( !in_array( $wiki, $this->wikis ) ) {
+ throw new MWException( "No such wiki '$wiki'." );
+ } else {
+ $this->cfgCache[$wiki] = array();
+ }
+ $retVal = 1;
+ $cmd = wfShellWikiCmd(
+ "$IP/maintenance/getConfiguration.php",
+ array(
+ '--wiki', $wiki,
+ '--settings', implode( ' ', $settings ),
+ '--format', 'PHP'
+ )
+ );
+ // ulimit5.sh breaks this call
+ $data = trim( wfShellExec( $cmd, $retVal, array(), array( 'memory' => 0 ) ) );
+ if ( $retVal != 0 || !strlen( $data ) ) {
+ throw new MWException( "Failed to run getConfiguration.php." );
+ }
+ $res = unserialize( $data );
+ if ( !is_array( $res ) ) {
+ throw new MWException( "Failed to unserialize configuration array." );
+ }
+ $this->cfgCache[$wiki] = $this->cfgCache[$wiki] + $res;
+ }
+
+ return $multi ? $res : current( $res );
+ }
+
+ /**
* Returns true if the given vhost is handled locally.
* @param $vhost String
* @return bool
@@ -509,9 +576,9 @@ class SiteConfiguration {
$out = $array1;
for( $i = 1; $i < func_num_args(); $i++ ) {
foreach( func_get_arg( $i ) as $key => $value ) {
- if ( isset($out[$key]) && is_array($out[$key]) && is_array($value) ) {
+ if ( isset( $out[$key] ) && is_array( $out[$key] ) && is_array( $value ) ) {
$out[$key] = self::arrayMerge( $out[$key], $value );
- } elseif ( !isset($out[$key]) || !$out[$key] && !is_numeric($key) ) {
+ } elseif ( !isset( $out[$key] ) || !$out[$key] && !is_numeric( $key ) ) {
// Values that evaluate to true given precedence, for the primary purpose of merging permissions arrays.
$out[$key] = $value;
} elseif ( is_numeric( $key ) ) {
diff --git a/includes/SiteStats.php b/includes/SiteStats.php
index 1c2c454d..b7be29d6 100644
--- a/includes/SiteStats.php
+++ b/includes/SiteStats.php
@@ -48,8 +48,7 @@ class SiteStats {
# Update schema
$u = new SiteStatsUpdate( 0, 0, 0 );
$u->doUpdate();
- $dbr = wfGetDB( DB_SLAVE );
- self::$row = $dbr->selectRow( 'site_stats', '*', false, __METHOD__ );
+ self::$row = self::doLoad( wfGetDB( DB_SLAVE ) );
}
self::$loaded = true;
@@ -91,7 +90,16 @@ class SiteStats {
* @return Bool|ResultWrapper
*/
static function doLoad( $db ) {
- return $db->selectRow( 'site_stats', '*', false, __METHOD__ );
+ return $db->selectRow( 'site_stats', array(
+ 'ss_row_id',
+ 'ss_total_views',
+ 'ss_total_edits',
+ 'ss_good_articles',
+ 'ss_total_pages',
+ 'ss_users',
+ 'ss_active_users',
+ 'ss_images',
+ ), false, __METHOD__ );
}
/**
@@ -152,7 +160,7 @@ class SiteStats {
/**
* Find the number of users in a given user group.
- * @param $group String: name of group
+ * @param string $group name of group
* @return Integer
*/
static function numberingroup( $group ) {
@@ -390,7 +398,7 @@ class SiteStatsUpdate implements DeferrableUpdate {
/**
* @param $type string
- * @param $sign string ('+' or '-')
+ * @param string $sign ('+' or '-')
* @return string
*/
private function getTypeCacheKey( $type, $sign ) {
@@ -443,7 +451,7 @@ class SiteStatsUpdate implements DeferrableUpdate {
/**
* Reduce pending delta counters after updates have been applied
- * @param Array $pd Result of getPendingDeltas(), used for DB update
+ * @param array $pd Result of getPendingDeltas(), used for DB update
* @return void
*/
protected function removePendingDeltas( array $pd ) {
@@ -566,7 +574,7 @@ class SiteStatsInit {
* @param $database DatabaseBase|bool
* - Boolean: whether to use the master DB
* - DatabaseBase: database connection to use
- * @param $options Array of options, may contain the following values
+ * @param array $options of options, may contain the following values
* - update Boolean: whether to update the current stats (true) or write fresh (false) (default: false)
* - views Boolean: when true, do not update the number of page views (default: true)
* - activeUsers Boolean: whether to update the number of active users (default: false)
diff --git a/includes/Skin.php b/includes/Skin.php
index 968f215e..0cc1086c 100644
--- a/includes/Skin.php
+++ b/includes/Skin.php
@@ -73,7 +73,7 @@ abstract class Skin extends ContextSource {
return $wgValidSkinNames;
}
- /**
+ /**
* Fetch the skinname messages for available skins.
* @return array of strings
*/
@@ -107,7 +107,7 @@ abstract class Skin extends ContextSource {
* Normalize a skin preference value to a form that can be loaded.
* If a skin can't be found, it will fall back to the configured
* default (or the old 'Classic' skin if that's broken).
- * @param $key String: 'monobook', 'standard', etc.
+ * @param string $key 'monobook', 'standard', etc.
* @return string
*/
static function normalizeKey( $key ) {
@@ -148,7 +148,7 @@ abstract class Skin extends ContextSource {
/**
* Factory method for loading a skin of a given type
- * @param $key String: 'monobook', 'standard', etc.
+ * @param string $key 'monobook', 'standard', etc.
* @return Skin
*/
static function &newFromKey( $key ) {
@@ -167,7 +167,7 @@ abstract class Skin extends ContextSource {
require_once( "{$wgStyleDirectory}/{$skinName}.php" );
}
- # Check if we got if not failback to default skin
+ # Check if we got if not fallback to default skin
if ( !MWInit::classExists( $className ) ) {
# DO NOT die if the class isn't found. This breaks maintenance
# scripts and can cause a user account to be unrecoverable
@@ -262,7 +262,7 @@ abstract class Skin extends ContextSource {
* @return Title
*/
public function getRelevantTitle() {
- if ( isset($this->mRelevantTitle) ) {
+ if ( isset( $this->mRelevantTitle ) ) {
return $this->mRelevantTitle;
}
return $this->getTitle();
@@ -286,12 +286,12 @@ abstract class Skin extends ContextSource {
* @return User
*/
public function getRelevantUser() {
- if ( isset($this->mRelevantUser) ) {
+ if ( isset( $this->mRelevantUser ) ) {
return $this->mRelevantUser;
}
$title = $this->getRelevantTitle();
- if( $title->getNamespace() == NS_USER || $title->getNamespace() == NS_USER_TALK ) {
- $rootUser = strtok( $title->getText(), '/' );
+ if( $title->hasSubjectNamespace( NS_USER ) ) {
+ $rootUser = $title->getRootText();
if ( User::isIP( $rootUser ) ) {
$this->mRelevantUser = User::newFromName( $rootUser, false );
} else {
@@ -435,7 +435,7 @@ abstract class Skin extends ContextSource {
$colon = $this->msg( 'colon-separator' )->escaped();
if ( !empty( $allCats['normal'] ) ) {
- $t = $embed . implode( "{$pop}{$embed}" , $allCats['normal'] ) . $pop;
+ $t = $embed . implode( "{$pop}{$embed}", $allCats['normal'] ) . $pop;
$msg = $this->msg( 'pagecategories' )->numParams( count( $allCats['normal'] ) )->escaped();
$linkPage = wfMessage( 'pagecategorieslink' )->inContentLanguage()->text();
@@ -456,7 +456,7 @@ abstract class Skin extends ContextSource {
$s .= "<div id=\"mw-hidden-catlinks\" class=\"mw-hidden-catlinks$class\">" .
$this->msg( 'hidden-categories' )->numParams( count( $allCats['hidden'] ) )->escaped() .
- $colon . '<ul>' . $embed . implode( "{$pop}{$embed}" , $allCats['hidden'] ) . $pop . '</ul>' .
+ $colon . '<ul>' . $embed . implode( "{$pop}{$embed}", $allCats['hidden'] ) . $pop . '</ul>' .
'</div>';
}
@@ -481,8 +481,8 @@ abstract class Skin extends ContextSource {
}
/**
- * Render the array as a serie of links.
- * @param $tree Array: categories tree returned by Title::getParentCategoryTree
+ * Render the array as a series of links.
+ * @param array $tree categories tree returned by Title::getParentCategoryTree
* @return String separated by &gt;, terminate with "\n"
*/
function drawCategoryBrowser( $tree ) {
@@ -499,7 +499,7 @@ abstract class Skin extends ContextSource {
# add our current element to the list
$eltitle = Title::newFromText( $element );
- $return .= Linker::link( $eltitle, htmlspecialchars( $eltitle->getText() ) );
+ $return .= Linker::link( $eltitle, htmlspecialchars( $eltitle->getText() ) );
}
return $return;
@@ -613,7 +613,6 @@ abstract class Skin extends ContextSource {
( $this->getTitle()->getArticleID() == 0 || $action == 'history' ) ) {
$n = $this->getTitle()->isDeleted();
-
if ( $n ) {
if ( $this->getUser()->isAllowed( 'undelete' ) ) {
$msg = 'thisisdeleted';
@@ -851,16 +850,16 @@ abstract class Skin extends ContextSource {
}
/**
- * Renders a $wgFooterIcons icon acording to the method's arguments
- * @param $icon Array: The icon to build the html for, see $wgFooterIcons for the format of this array
- * @param $withImage Bool|String: Whether to use the icon's image or output a text-only footericon
+ * Renders a $wgFooterIcons icon according to the method's arguments
+ * @param array $icon The icon to build the html for, see $wgFooterIcons for the format of this array
+ * @param bool|String $withImage Whether to use the icon's image or output a text-only footericon
* @return String HTML
*/
function makeFooterIcon( $icon, $withImage = 'withImage' ) {
if ( is_string( $icon ) ) {
$html = $icon;
} else { // Assuming array
- $url = isset($icon["url"]) ? $icon["url"] : null;
+ $url = isset( $icon["url"] ) ? $icon["url"] : null;
unset( $icon["url"] );
if ( isset( $icon["src"] ) && $withImage === 'withImage' ) {
$html = Html::element( 'img', $icon ); // do this the lazy way, just pass icon data as an attribute array
@@ -969,7 +968,7 @@ abstract class Skin extends ContextSource {
* Return a fully resolved style path url to images or styles stored in the common folder.
* This method returns a url resolved using the configured skin style path
* and includes the style version inside of the url.
- * @param $name String: The name or path of a skin resource file
+ * @param string $name The name or path of a skin resource file
* @return String The fully resolved style path url including styleversion
*/
function getCommonStylePath( $name ) {
@@ -978,10 +977,10 @@ abstract class Skin extends ContextSource {
}
/**
- * Return a fully resolved style path url to images or styles stored in the curent skins's folder.
+ * Return a fully resolved style path url to images or styles stored in the current skins's folder.
* This method returns a url resolved using the configured skin style path
* and includes the style version inside of the url.
- * @param $name String: The name or path of a skin resource file
+ * @param string $name The name or path of a skin resource file
* @return String The fully resolved style path url including styleversion
*/
function getSkinStylePath( $name ) {
@@ -1008,8 +1007,8 @@ abstract class Skin extends ContextSource {
* If $proto is set to null, make a local URL. Otherwise, make a full
* URL with the protocol specified.
*
- * @param $name string Name of the Special page
- * @param $urlaction string Query to append
+ * @param string $name Name of the Special page
+ * @param string $urlaction Query to append
* @param $proto Protocol to use or null for a local URL
* @return String
*/
@@ -1102,7 +1101,7 @@ abstract class Skin extends ContextSource {
/**
* Make URL details where the article exists (or at least it's convenient to think so)
- * @param $name String Article name
+ * @param string $name Article name
* @param $urlaction String
* @return Array
*/
@@ -1132,7 +1131,23 @@ abstract class Skin extends ContextSource {
}
/**
- * Build an array that represents the sidebar(s), the navigation bar among them
+ * Build an array that represents the sidebar(s), the navigation bar among them.
+ *
+ * BaseTemplate::getSidebar can be used to simplify the format and id generation in new skins.
+ *
+ * The format of the returned array is array( heading => content, ... ), where:
+ * - heading is the heading of a navigation portlet. It is either:
+ * - magic string to be handled by the skins ('SEARCH' / 'LANGUAGES' / 'TOOLBOX' / ...)
+ * - a message name (e.g. 'navigation'), the message should be HTML-escaped by the skin
+ * - plain text, which should be HTML-escaped by the skin
+ * - content is the contents of the portlet. It is either:
+ * - HTML text (<ul><li>...</li>...</ul>)
+ * - array of link data in a format accepted by BaseTemplate::makeListItem()
+ * - (for a magic string as a key, any value)
+ *
+ * Note that extensions can control the sidebar contents using the SkinBuildSidebar hook
+ * and can technically insert anything in here; skin creators are expected to handle
+ * values described above.
*
* @return array
*/
@@ -1366,7 +1381,7 @@ abstract class Skin extends ContextSource {
/**
* Get a cached notice
*
- * @param $name String: message name, or 'default' for $wgSiteNotice
+ * @param string $name message name, or 'default' for $wgSiteNotice
* @return String: HTML fragment
*/
private function getCachedNotice( $name ) {
@@ -1474,9 +1489,9 @@ abstract class Skin extends ContextSource {
*
* @param $nt Title The title being linked to (may not be the same as
* $wgTitle, if the section is included from a template)
- * @param $section string The designation of the section being pointed to,
+ * @param string $section The designation of the section being pointed to,
* to be included in the link, like "&section=$section"
- * @param $tooltip string The tooltip to use for the link: will be escaped
+ * @param string $tooltip The tooltip to use for the link: will be escaped
* and wrapped in the 'editsectionhint' message
* @param $lang string Language code
* @return string HTML to use for edit link
@@ -1534,8 +1549,9 @@ abstract class Skin extends ContextSource {
* Use PHP's magic __call handler to intercept legacy calls to the linker
* for backwards compatibility.
*
- * @param $fname String Name of called method
- * @param $args Array Arguments to the method
+ * @param string $fname Name of called method
+ * @param array $args Arguments to the method
+ * @throws MWException
* @return mixed
*/
function __call( $fname, $args ) {
diff --git a/includes/SkinLegacy.php b/includes/SkinLegacy.php
index e695ba6c..b9766a9f 100644
--- a/includes/SkinLegacy.php
+++ b/includes/SkinLegacy.php
@@ -120,7 +120,7 @@ class LegacyTemplate extends BaseTemplate {
}
$s .= "\n<div id='content'>\n<div id='topbar'>\n" .
- "<table cellspacing='0' width='100%'>\n<tr>\n";
+ "<table cellspacing='0' style='width: 100%;'>\n<tr>\n";
if ( $this->getSkin()->qbSetting() == 0 ) {
$s .= "<td class='top' style='text-align: left; vertical-align: top;' rowspan='{$rows}'>\n" .
@@ -179,10 +179,10 @@ class LegacyTemplate extends BaseTemplate {
$search = $wgRequest->getText( 'search' );
$s = '<form id="searchform' . $this->searchboxes . '" name="search" class="inline" method="post" action="'
- . $this->getSkin()->escapeSearchLink() . "\">\n"
- . '<input type="text" id="searchInput' . $this->searchboxes . '" name="search" size="19" value="'
- . htmlspecialchars( substr( $search, 0, 256 ) ) . "\" />\n"
- . '<input type="submit" name="go" value="' . wfMessage( 'searcharticle' )->text() . '" />';
+ . $this->getSkin()->escapeSearchLink() . "\">\n"
+ . '<input type="text" id="searchInput' . $this->searchboxes . '" name="search" size="19" value="'
+ . htmlspecialchars( substr( $search, 0, 256 ) ) . "\" />\n"
+ . '<input type="submit" name="go" value="' . wfMessage( 'searcharticle' )->text() . '" />';
if ( $wgUseTwoButtonsSearchForm ) {
$s .= '&#160;<input type="submit" name="fulltext" value="' . wfMessage( 'searchbutton' )->text() . "\" />\n";
@@ -253,7 +253,7 @@ class LegacyTemplate extends BaseTemplate {
$lang = $title->getPageLanguage();
$variants = $lang->getVariants();
- if ( !$wgDisableLangConversion && sizeof( $variants ) > 1
+ if ( !$wgDisableLangConversion && count( $variants ) > 1
&& !$title->isSpecialPage() ) {
foreach ( $variants as $code ) {
$varname = $lang->getVariantname( $code );
@@ -263,7 +263,7 @@ class LegacyTemplate extends BaseTemplate {
}
$s = $wgLang->pipeList( array(
$s,
- '<a href="' . htmlspecialchars( $title->getLocalURL( 'variant=' . $code ) ) . '" lang="' . $code . '" hreflang="' . $code . '">' . htmlspecialchars( $varname ) . '</a>'
+ '<a href="' . htmlspecialchars( $title->getLocalURL( 'variant=' . $code ) ) . '" lang="' . $code . '" hreflang="' . $code . '">' . htmlspecialchars( $varname ) . '</a>'
) );
}
}
@@ -345,7 +345,7 @@ class LegacyTemplate extends BaseTemplate {
$s .= $this->deleteThisPage();
}
- if ( $wgUser->isAllowed( 'protect' ) ) {
+ if ( $wgUser->isAllowed( 'protect' ) && $title->getRestrictionTypes() ) {
$s .= $sep . $this->protectThisPage();
}
@@ -553,7 +553,7 @@ class LegacyTemplate extends BaseTemplate {
*/
function getQuickbarCompensator( $rows = 1 ) {
wfDeprecated( __METHOD__, '1.19' );
- return "<td width='152' rowspan='{$rows}'>&#160;</td>";
+ return "<td style='width: 152px;' rowspan='{$rows}'>&#160;</td>";
}
function editThisPage() {
@@ -610,7 +610,7 @@ class LegacyTemplate extends BaseTemplate {
$diff = $wgRequest->getVal( 'diff' );
$title = $this->getSkin()->getTitle();
- if ( $title->getArticleID() && ( ! $diff ) && $wgUser->isAllowed( 'protect' ) ) {
+ if ( $title->getArticleID() && ( ! $diff ) && $wgUser->isAllowed( 'protect' ) && $title->getRestrictionTypes() ) {
if ( $title->isProtected() ) {
$text = wfMessage( 'unprotectthispage' )->text();
$query = array( 'action' => 'unprotect' );
@@ -786,7 +786,7 @@ class LegacyTemplate extends BaseTemplate {
return '';
}
- # __NEWSECTIONLINK___ changes behaviour here
+ # __NEWSECTIONLINK___ changes behavior here
# If it is present, the link points to this page, otherwise
# it points to the talk page
if ( !$title->isTalkPage() && !$wgOut->showNewSectionLink() ) {
diff --git a/includes/SkinTemplate.php b/includes/SkinTemplate.php
index bda43957..59e1ccff 100644
--- a/includes/SkinTemplate.php
+++ b/includes/SkinTemplate.php
@@ -113,7 +113,7 @@ class SkinTemplate extends Skin {
* roughly equivalent to PHPTAL 0.7.
*
* @param $classname String
- * @param $repository string: subdirectory where we keep template files
+ * @param string $repository subdirectory where we keep template files
* @param $cache_dir string
* @return QuickTemplate
* @private
@@ -169,7 +169,7 @@ class SkinTemplate extends Skin {
unset( $query['returnto'] );
unset( $query['returntoquery'] );
}
- $this->thisquery = wfArrayToCGI( $query );
+ $this->thisquery = wfArrayToCgi( $query );
$this->loggedin = $user->isLoggedIn();
$this->username = $user->getName();
@@ -219,7 +219,7 @@ class SkinTemplate extends Skin {
if ( $subpagestr !== '' ) {
$subpagestr = '<span class="subpages">' . $subpagestr . '</span>';
}
- $tpl->set( 'subtitle', $subpagestr . $out->getSubtitle() );
+ $tpl->set( 'subtitle', $subpagestr . $out->getSubtitle() );
$undelete = $this->getUndeleteLink();
if ( $undelete === '' ) {
@@ -262,7 +262,7 @@ class SkinTemplate extends Skin {
$tpl->set( 'helppage', $this->msg( 'helppage' )->text() );
*/
$tpl->set( 'searchaction', $this->escapeSearchLink() );
- $tpl->set( 'searchtitle', SpecialPage::getTitleFor( 'Search' )->getPrefixedDBKey() );
+ $tpl->set( 'searchtitle', SpecialPage::getTitleFor( 'Search' )->getPrefixedDBkey() );
$tpl->set( 'search', trim( $request->getVal( 'search' ) ) );
$tpl->setRef( 'stylepath', $wgStylePath );
$tpl->setRef( 'articlepath', $wgArticlePath );
@@ -273,7 +273,7 @@ class SkinTemplate extends Skin {
$userLang = $this->getLanguage();
$userLangCode = $userLang->getHtmlCode();
- $userLangDir = $userLang->getDir();
+ $userLangDir = $userLang->getDir();
$tpl->set( 'lang', $userLangCode );
$tpl->set( 'dir', $userLangDir );
@@ -398,12 +398,12 @@ class SkinTemplate extends Skin {
# not for special pages or file pages AND only when viewing AND if the page exists
# (or is in MW namespace, because that has default content)
if ( !in_array( $title->getNamespace(), array( NS_SPECIAL, NS_FILE ) ) &&
- in_array( $request->getVal( 'action', 'view' ), array( 'view', 'historysubmit' ) ) &&
+ Action::getActionName( $this ) === 'view' &&
( $title->exists() || $title->getNamespace() == NS_MEDIAWIKI ) ) {
$pageLang = $title->getPageViewLanguage();
$realBodyAttribs['lang'] = $pageLang->getHtmlCode();
$realBodyAttribs['dir'] = $pageLang->getDir();
- $realBodyAttribs['class'] = 'mw-content-'.$pageLang->getDir();
+ $realBodyAttribs['class'] = 'mw-content-' . $pageLang->getDir();
}
$out->mBodytext = Html::rawElement( 'div', $realBodyAttribs, $out->mBodytext );
@@ -413,25 +413,28 @@ class SkinTemplate extends Skin {
$language_urls = array();
if ( !$wgHideInterlanguageLinks ) {
- foreach( $out->getLanguageLinks() as $l ) {
- $tmp = explode( ':', $l, 2 );
- $class = 'interwiki-' . $tmp[0];
- unset( $tmp );
- $nt = Title::newFromText( $l );
- if ( $nt ) {
- $ilLangName = Language::fetchLanguageName( $nt->getInterwiki() );
+ foreach( $out->getLanguageLinks() as $languageLinkText ) {
+ $languageLinkParts = explode( ':', $languageLinkText, 2 );
+ $class = 'interwiki-' . $languageLinkParts[0];
+ unset( $languageLinkParts );
+ $languageLinkTitle = Title::newFromText( $languageLinkText );
+ if ( $languageLinkTitle ) {
+ $ilInterwikiCode = $languageLinkTitle->getInterwiki();
+ $ilLangName = Language::fetchLanguageName( $ilInterwikiCode );
+
if ( strval( $ilLangName ) === '' ) {
- $ilLangName = $l;
+ $ilLangName = $languageLinkText;
} else {
- $ilLangName = $this->getLanguage()->ucfirst( $ilLangName );
+ $ilLangName = $this->formatLanguageName( $ilLangName );
}
+
$language_urls[] = array(
- 'href' => $nt->getFullURL(),
+ 'href' => $languageLinkTitle->getFullURL(),
'text' => $ilLangName,
- 'title' => $nt->getText(),
+ 'title' => $languageLinkTitle->getText(),
'class' => $class,
- 'lang' => $nt->getInterwiki(),
- 'hreflang' => $nt->getInterwiki(),
+ 'lang' => $ilInterwikiCode,
+ 'hreflang' => $ilInterwikiCode
);
}
}
@@ -499,6 +502,18 @@ class SkinTemplate extends Skin {
}
/**
+ * Format language name for use in sidebar interlanguage links list.
+ * By default it is capitalized.
+ *
+ * @param string $name Language name, e.g. "English" or "español"
+ * @return string
+ * @private
+ */
+ function formatLanguageName( $name ) {
+ return $this->getLanguage()->ucfirst( $name );
+ }
+
+ /**
* Output the string, or print error message if it's
* an error object of the appropriate type.
* For the base class, assume strings all around.
@@ -511,7 +526,7 @@ class SkinTemplate extends Skin {
}
/**
- * Output a boolean indiciating if buildPersonalUrls should output separate
+ * Output a boolean indicating if buildPersonalUrls should output separate
* login and create account links or output a combined link
* By default we simply return a global config setting that affects most skins
* This is setup as a method so that like with $wgLogo and getLogo() a skin
@@ -529,6 +544,8 @@ class SkinTemplate extends Skin {
* @return array
*/
protected function buildPersonalUrls() {
+ global $wgSecureLogin;
+
$title = $this->getTitle();
$request = $this->getRequest();
$pageurl = $title->getLocalURL();
@@ -541,7 +558,11 @@ class SkinTemplate extends Skin {
# $this->getTitle() will just give Special:Badtitle, which is
# not especially useful as a returnto parameter. Use the title
# from the request instead, if there was one.
- $page = Title::newFromURL( $request->getVal( 'title', '' ) );
+ if ( $this->getUser()->isAllowed( 'read' ) ) {
+ $page = $this->getTitle();
+ } else {
+ $page = Title::newFromText( $request->getVal( 'title', '' ) );
+ }
$page = $request->getVal( 'returnto', $page );
$a = array();
if ( strval( $page ) !== '' ) {
@@ -551,7 +572,12 @@ class SkinTemplate extends Skin {
$a['returntoquery'] = $query;
}
}
- $returnto = wfArrayToCGI( $a );
+
+ if ( $wgSecureLogin && $request->detectProtocol() === 'https' ) {
+ $a['wpStickHTTPS'] = true;
+ }
+
+ $returnto = wfArrayToCgi( $a );
if( $this->loggedin ) {
$personal_urls['userpage'] = array(
'text' => $this->username,
@@ -620,7 +646,6 @@ class SkinTemplate extends Skin {
$is_signup = $request->getText( 'type' ) == 'signup';
# anonlogin & login are the same
- global $wgSecureLogin;
$proto = $wgSecureLogin ? PROTO_HTTPS : null;
$login_id = $this->showIPinHeader() ? 'anonlogin' : 'login';
@@ -668,12 +693,14 @@ class SkinTemplate extends Skin {
}
/**
- * TODO document
- * @param $title Title
- * @param $message String message key
- * @param $selected Bool
- * @param $query String
- * @param $checkEdit Bool
+ * Builds an array with tab definition
+ *
+ * @param Title $title page where the tab links to
+ * @param string|array $message message key or an array of message keys (will fall back)
+ * @param boolean $selected display the tab as selected
+ * @param string $query query string attached to tab URL
+ * @param boolean $checkEdit check if $title exists and mark with .new if one doesn't
+ *
* @return array
*/
function tabAction( $title, $message, $selected, $query = '', $checkEdit = false ) {
@@ -683,7 +710,11 @@ class SkinTemplate extends Skin {
}
if( $checkEdit && !$title->isKnown() ) {
$classes[] = 'new';
- $query = 'action=edit&redlink=1';
+ if ( $query !== '' ) {
+ $query = 'action=edit&redlink=1&' . $query;
+ } else {
+ $query = 'action=edit&redlink=1';
+ }
}
// wfMessageFallback will nicely accept $message as an array of fallbacks
@@ -748,7 +779,7 @@ class SkinTemplate extends Skin {
* variants: Used to list the language variants for the page
*
* Each section's value is a key/value array of links for that section.
- * The links themseves have these common keys:
+ * The links themselves have these common keys:
* - class: The css classes to apply to the tab
* - text: The text to display on the tab
* - href: The href for the tab to point to
@@ -935,7 +966,7 @@ class SkinTemplate extends Skin {
}
}
- if ( $title->getNamespace() !== NS_MEDIAWIKI && $title->quickUserCan( 'protect', $user ) ) {
+ if ( $title->getNamespace() !== NS_MEDIAWIKI && $title->quickUserCan( 'protect', $user ) && $title->getRestrictionTypes() ) {
$mode = $title->isProtected() ? 'unprotect' : 'protect';
$content_navigation['actions'][$mode] = array(
'class' => ( $onPage && $action == $mode ) ? 'selected' : false,
@@ -980,6 +1011,12 @@ class SkinTemplate extends Skin {
// Gets preferred variant (note that user preference is
// only possible for wiki content language variant)
$preferred = $pageLang->getPreferredVariant();
+ if ( Action::getActionName( $this ) === 'view' ) {
+ $params = $request->getQueryValues();
+ unset( $params['title'] );
+ } else {
+ $params = array();
+ }
// Loops over each variant
foreach( $variants as $code ) {
// Gets variant name from language code
@@ -993,7 +1030,7 @@ class SkinTemplate extends Skin {
$content_navigation['variants'][] = array(
'class' => ( $code == $preferred ) ? 'selected' : false,
'text' => $varname,
- 'href' => $title->getLocalURL( array( 'variant' => $code ) ),
+ 'href' => $title->getLocalURL( array( 'variant' => $code ) + $params ),
'lang' => $code,
'hreflang' => $code
);
@@ -1014,7 +1051,7 @@ class SkinTemplate extends Skin {
}
// Equiv to SkinTemplateContentActions
- wfRunHooks( 'SkinTemplateNavigation::Universal', array( &$this, &$content_navigation ) );
+ wfRunHooks( 'SkinTemplateNavigation::Universal', array( &$this, &$content_navigation ) );
// Setup xml ids and tooltip info
foreach ( $content_navigation as $section => &$links ) {
@@ -1036,8 +1073,8 @@ class SkinTemplate extends Skin {
# We don't want to give the watch tab an accesskey if the
# page is being edited, because that conflicts with the
# accesskey on the watch checkbox. We also don't want to
- # give the edit tab an accesskey, because that's fairly su-
- # perfluous and conflicts with an accesskey (Ctrl-E) often
+ # give the edit tab an accesskey, because that's fairly
+ # superfluous and conflicts with an accesskey (Ctrl-E) often
# used for editing in Safari.
if ( in_array( $action, array( 'edit', 'submit' ) ) ) {
if ( isset( $content_navigation['views']['edit'] ) ) {
@@ -1130,12 +1167,14 @@ class SkinTemplate extends Skin {
$nav_urls['print'] = false;
$nav_urls['permalink'] = false;
+ $nav_urls['info'] = false;
$nav_urls['whatlinkshere'] = false;
$nav_urls['recentchangeslinked'] = false;
$nav_urls['contributions'] = false;
$nav_urls['log'] = false;
$nav_urls['blockip'] = false;
$nav_urls['emailuser'] = false;
+ $nav_urls['userrights'] = false;
// A print stylesheet is attached to all pages, but nobody ever
// figures that out. :) Add a link...
@@ -1166,6 +1205,12 @@ class SkinTemplate extends Skin {
$nav_urls['whatlinkshere'] = array(
'href' => SpecialPage::getTitleFor( 'Whatlinkshere', $this->thispage )->getLocalUrl()
);
+
+ $nav_urls['info'] = array(
+ 'text' => $this->msg( 'pageinfo-toolboxlink' )->text(),
+ 'href' => $this->getTitle()->getLocalURL( "action=info" )
+ );
+
if ( $this->getTitle()->getArticleID() ) {
$nav_urls['recentchangeslinked'] = array(
'href' => SpecialPage::getTitleFor( 'Recentchangeslinked', $this->thispage )->getLocalUrl()
@@ -1178,6 +1223,7 @@ class SkinTemplate extends Skin {
$rootUser = $user->getName();
$nav_urls['contributions'] = array(
+ 'text' => $this->msg( 'contributions', $rootUser )->text(),
'href' => self::makeSpecialUrlSubpage( 'Contributions', $rootUser )
);
@@ -1196,6 +1242,13 @@ class SkinTemplate extends Skin {
'href' => self::makeSpecialUrlSubpage( 'Emailuser', $rootUser )
);
}
+
+ $sur = new UserrightsPage;
+ if ( $sur->userCanExecute( $this->getUser() ) ) {
+ $nav_urls['userrights'] = array(
+ 'href' => self::makeSpecialUrlSubpage( 'Userrights', $rootUser )
+ );
+ }
}
wfProfileOut( __METHOD__ );
@@ -1225,7 +1278,7 @@ abstract class QuickTemplate {
/**
* Constructor
*/
- public function QuickTemplate() {
+ function __construct() {
$this->data = array();
$this->translator = new MediaWiki_I18N();
}
@@ -1344,7 +1397,7 @@ abstract class BaseTemplate extends QuickTemplate {
/**
* Get a Message object with its context set
*
- * @param $name string message name
+ * @param string $name message name
* @return Message
*/
public function getMsg( $name ) {
@@ -1366,7 +1419,7 @@ abstract class BaseTemplate extends QuickTemplate {
/**
* Create an array of common toolbox items from the data in the quicktemplate
* stored by SkinTemplate.
- * The resulting array is built acording to a format intended to be passed
+ * The resulting array is built according to a format intended to be passed
* through makeListItem to generate the html.
* @return array
*/
@@ -1394,7 +1447,7 @@ abstract class BaseTemplate extends QuickTemplate {
$toolbox['feeds']['links'][$key]['class'] = 'feedlink';
}
}
- foreach ( array( 'contributions', 'log', 'blockip', 'emailuser', 'upload', 'specialpages' ) as $special ) {
+ foreach ( array( 'contributions', 'log', 'blockip', 'emailuser', 'userrights', 'upload', 'specialpages' ) as $special ) {
if ( isset( $this->data['nav_urls'][$special] ) && $this->data['nav_urls'][$special] ) {
$toolbox[$special] = $this->data['nav_urls'][$special];
$toolbox[$special]['id'] = "t-$special";
@@ -1417,6 +1470,11 @@ abstract class BaseTemplate extends QuickTemplate {
$toolbox['permalink']['id'] = 't-permalink';
}
}
+ if ( isset( $this->data['nav_urls']['info'] ) && $this->data['nav_urls']['info'] ) {
+ $toolbox['info'] = $this->data['nav_urls']['info'];
+ $toolbox['info']['id'] = 't-info';
+ }
+
wfRunHooks( 'BaseTemplateToolbox', array( &$this, &$toolbox ) );
wfProfileOut( __METHOD__ );
return $toolbox;
@@ -1425,7 +1483,7 @@ abstract class BaseTemplate extends QuickTemplate {
/**
* Create an array of personal tools items from the data in the quicktemplate
* stored by SkinTemplate.
- * The resulting array is built acording to a format intended to be passed
+ * The resulting array is built according to a format intended to be passed
* through makeListItem to generate the html.
* This is in reality the same list as already stored in personal_urls
* however it is reformatted so that you can just pass the individual items
@@ -1588,9 +1646,9 @@ abstract class BaseTemplate extends QuickTemplate {
* Makes a link, usually used by makeListItem to generate a link for an item
* in a list used in navigation lists, portlets, portals, sidebars, etc...
*
- * @param $key string usually a key from the list you are generating this
+ * @param string $key usually a key from the list you are generating this
* link from.
- * @param $item array contains some of a specific set of keys.
+ * @param array $item contains some of a specific set of keys.
*
* The text of the link will be generated either from the contents of the
* "text" key in the $item array, if a "msg" key is present a message by
@@ -1607,7 +1665,7 @@ abstract class BaseTemplate extends QuickTemplate {
*
* If you don't want an accesskey, set $item['tooltiponly'] = true;
*
- * @param $options array can be used to affect the output of a link.
+ * @param array $options can be used to affect the output of a link.
* Possible options are:
* - 'text-wrapper' key to specify a list of elements to wrap the text of
* a link in. This should be an array of arrays containing a 'tag' and
@@ -1720,7 +1778,7 @@ abstract class BaseTemplate extends QuickTemplate {
foreach ( array( 'id', 'class', 'active', 'tag' ) as $k ) {
unset( $link[$k] );
}
- if ( isset( $item['id'] ) ) {
+ if ( isset( $item['id'] ) && !isset( $item['single-id'] ) ) {
// The id goes on the <li> not on the <a> for single links
// but makeSidebarLink still needs to know what id to use when
// generating tooltips and accesskeys.
@@ -1783,11 +1841,15 @@ abstract class BaseTemplate extends QuickTemplate {
);
unset( $buttonAttrs['src'] );
unset( $buttonAttrs['alt'] );
+ unset( $buttonAttrs['width'] );
+ unset( $buttonAttrs['height'] );
$imgAttrs = array(
'src' => $attrs['src'],
'alt' => isset( $attrs['alt'] )
? $attrs['alt']
: $this->translator->translate( 'searchbutton' ),
+ 'width' => isset( $attrs['width'] ) ? $attrs['width'] : null,
+ 'height' => isset( $attrs['height'] ) ? $attrs['height'] : null,
);
return Html::rawElement( 'button', $buttonAttrs, Html::element( 'img', $imgAttrs ) );
default:
diff --git a/includes/SpecialPage.php b/includes/SpecialPage.php
index 2e5e02b0..c32738f8 100644
--- a/includes/SpecialPage.php
+++ b/includes/SpecialPage.php
@@ -106,19 +106,6 @@ class SpecialPage {
}
/**
- * Add a page to the list of valid special pages. This used to be the preferred
- * method for adding special pages in extensions. It's now suggested that you add
- * an associative record to $wgSpecialPages. This avoids autoloading SpecialPage.
- *
- * @param $page SpecialPage
- * @deprecated since 1.7, warnings in 1.17, might be removed in 1.20
- */
- static function addPage( &$page ) {
- wfDeprecated( __METHOD__, '1.7' );
- SpecialPageFactory::getList()->{$page->mName} = $page;
- }
-
- /**
* Add a page to a certain display group for Special:SpecialPages
*
* @param $page Mixed: SpecialPage or string
@@ -148,7 +135,7 @@ class SpecialPage {
* preferred method is now to add a SpecialPage_initList hook.
* @deprecated since 1.18
*
- * @param $name String the page to remove
+ * @param string $name the page to remove
*/
static function removePage( $name ) {
wfDeprecated( __METHOD__, '1.18' );
@@ -158,7 +145,7 @@ class SpecialPage {
/**
* Check if a given name exist as a special page or as a special page alias
*
- * @param $name String: name of a special page
+ * @param string $name name of a special page
* @return Boolean: true if a special page exists with this name
* @deprecated since 1.18 call SpecialPageFactory method directly
*/
@@ -266,13 +253,15 @@ class SpecialPage {
* Get a localised Title object for a specified special page name
*
* @param $name String
- * @param $subpage String|Bool subpage string, or false to not use a subpage
+ * @param string|Bool $subpage subpage string, or false to not use a subpage
+ * @param string $fragment the link fragment (after the "#")
+ * @throws MWException
* @return Title object
*/
- public static function getTitleFor( $name, $subpage = false ) {
+ public static function getTitleFor( $name, $subpage = false, $fragment = '' ) {
$name = SpecialPageFactory::getLocalNameFor( $name, $subpage );
if ( $name ) {
- return Title::makeTitle( NS_SPECIAL, $name );
+ return Title::makeTitle( NS_SPECIAL, $name, $fragment );
} else {
throw new MWException( "Invalid special page name \"$name\"" );
}
@@ -282,7 +271,7 @@ class SpecialPage {
* Get a localised Title object for a page name with a possibly unvalidated subpage
*
* @param $name String
- * @param $subpage String|Bool subpage string, or false to not use a subpage
+ * @param string|Bool $subpage subpage string, or false to not use a subpage
* @return Title object or null if the page doesn't exist
*/
public static function getSafeTitleFor( $name, $subpage = false ) {
@@ -313,15 +302,15 @@ class SpecialPage {
* be displayed by the default execute() method, without the global function ever
* being called.
*
- * If you override execute(), you can recover the default behaviour with userCanExecute()
+ * If you override execute(), you can recover the default behavior with userCanExecute()
* and displayRestrictionError()
*
- * @param $name String: name of the special page, as seen in links and URLs
- * @param $restriction String: user right required, e.g. "block" or "delete"
- * @param $listed Bool: whether the page is listed in Special:Specialpages
+ * @param string $name name of the special page, as seen in links and URLs
+ * @param string $restriction user right required, e.g. "block" or "delete"
+ * @param bool $listed whether the page is listed in Special:Specialpages
* @param $function Callback|Bool: function called by execute(). By default it is constructed from $name
- * @param $file String: file which is included by execute(). It is also constructed from $name by default
- * @param $includable Bool: whether the page can be included in normal pages
+ * @param string $file file which is included by execute(). It is also constructed from $name by default
+ * @param bool $includable whether the page can be included in normal pages
*/
public function __construct(
$name = '', $restriction = '', $listed = true,
@@ -333,12 +322,12 @@ class SpecialPage {
/**
* Do the real work for the constructor, mainly so __call() can intercept
* calls to SpecialPage()
- * @param $name String: name of the special page, as seen in links and URLs
- * @param $restriction String: user right required, e.g. "block" or "delete"
- * @param $listed Bool: whether the page is listed in Special:Specialpages
+ * @param string $name name of the special page, as seen in links and URLs
+ * @param string $restriction user right required, e.g. "block" or "delete"
+ * @param bool $listed whether the page is listed in Special:Specialpages
* @param $function Callback|Bool: function called by execute(). By default it is constructed from $name
- * @param $file String: file which is included by execute(). It is also constructed from $name by default
- * @param $includable Bool: whether the page can be included in normal pages
+ * @param string $file file which is included by execute(). It is also constructed from $name by default
+ * @param bool $includable whether the page can be included in normal pages
*/
private function init( $name, $restriction, $listed, $function, $file, $includable ) {
$this->mName = $name;
@@ -361,8 +350,9 @@ class SpecialPage {
* Use PHP's magic __call handler to get calls to the old PHP4 constructor
* because PHP E_STRICT yells at you for having __construct() and SpecialPage()
*
- * @param $fName String Name of called method
- * @param $a Array Arguments to the method
+ * @param string $fName Name of called method
+ * @param array $a Arguments to the method
+ * @throws MWException
* @deprecated since 1.17, call parent::__construct()
*/
public function __call( $fName, $a ) {
@@ -525,6 +515,19 @@ class SpecialPage {
}
/**
+ * Is this page cached?
+ * Expensive pages are cached or disabled in miser mode.
+ * Used by QueryPage and subclasses, moved here so that
+ * Special:SpecialPages can safely call it for all special pages.
+ *
+ * @return Boolean
+ * @since 1.21
+ */
+ public function isCached() {
+ return false;
+ }
+
+ /**
* Can be overridden by subclasses with more complicated permissions
* schemes.
*
@@ -532,9 +535,8 @@ class SpecialPage {
* pages?
*/
public function isRestricted() {
- global $wgGroupPermissions;
// DWIM: If all anons can do something, then it is not restricted
- return $this->mRestriction != '' && empty( $wgGroupPermissions['*'][$this->mRestriction] );
+ return $this->mRestriction != '' && !User::groupHasPermission( '*', $this->mRestriction );
}
/**
@@ -596,7 +598,7 @@ class SpecialPage {
*
* @param $subPage string|null
*/
- public final function run( $subPage ) {
+ final public function run( $subPage ) {
/**
* Gets called before @see SpecialPage::execute.
*
@@ -668,10 +670,10 @@ class SpecialPage {
/**
* Outputs a summary message on top of special pages
* Per default the message key is the canonical name of the special page
- * May be overriden, i.e. by extensions to stick with the naming conventions
+ * May be overridden, i.e. by extensions to stick with the naming conventions
* for message keys: 'extensionname-xxx'
*
- * @param $summaryMessageKey String: message key of the summary
+ * @param string $summaryMessageKey message key of the summary
*/
function outputHeader( $summaryMessageKey = '' ) {
global $wgContLang;
@@ -693,7 +695,7 @@ class SpecialPage {
* also the name that will be listed in Special:Specialpages
*
* Derived classes can override this, but usually it is easier to keep the
- * default behaviour. Messages can be added at run-time, see
+ * default behavior. Messages can be added at run-time, see
* MessageCache.php.
*
* @return String
@@ -825,7 +827,7 @@ class SpecialPage {
// RequestContext passes context to wfMessage, and the language is set from
// the context, but setting the language for Message class removes the
// interface message status, which breaks for example usernameless gender
- // invokations. Restore the flag when not including special page in content.
+ // invocations. Restore the flag when not including special page in content.
if ( $this->including() ) {
$message->setInterfaceMessageFlag( false );
}
@@ -844,10 +846,61 @@ class SpecialPage {
foreach ( $wgFeedClasses as $format => $class ) {
$theseParams = $params + array( 'feedformat' => $format );
- $url = $feedTemplate . wfArrayToCGI( $theseParams );
+ $url = $feedTemplate . wfArrayToCgi( $theseParams );
$this->getOutput()->addFeedLink( $format, $url );
}
}
+
+ /**
+ * Get the group that the special page belongs in on Special:SpecialPage
+ * Use this method, instead of getGroupName to allow customization
+ * of the group name from the wiki side
+ *
+ * @return string Group of this special page
+ * @since 1.21
+ */
+ public function getFinalGroupName() {
+ global $wgSpecialPageGroups;
+ $name = $this->getName();
+ $group = '-';
+
+ // Allow overbidding the group from the wiki side
+ $msg = $this->msg( 'specialpages-specialpagegroup-' . strtolower( $name ) )->inContentLanguage();
+ if ( !$msg->isBlank() ) {
+ $group = $msg->text();
+ } else {
+ // Than use the group from this object
+ $group = $this->getGroupName();
+
+ // Group '-' is used as default to have the chance to determine,
+ // if the special pages overrides this method,
+ // if not overridden, $wgSpecialPageGroups is checked for b/c
+ if ( $group === '-' && isset( $wgSpecialPageGroups[$name] ) ) {
+ $group = $wgSpecialPageGroups[$name];
+ }
+ }
+
+ // never give '-' back, change to 'other'
+ if ( $group === '-' ) {
+ $group = 'other';
+ }
+
+ return $group;
+ }
+
+ /**
+ * Under which header this special page is listed in Special:SpecialPages
+ * See messages 'specialpages-group-*' for valid names
+ * This method defaults to group 'other'
+ *
+ * @return string
+ * @since 1.21
+ */
+ protected function getGroupName() {
+ // '-' used here to determine, if this group is overridden or has a hardcoded 'other'
+ // Needed for b/c in getFinalGroupName
+ return '-';
+ }
}
/**
@@ -861,7 +914,7 @@ abstract class FormSpecialPage extends SpecialPage {
* Get an HTMLForm descriptor array
* @return Array
*/
- protected abstract function getFormFields();
+ abstract protected function getFormFields();
/**
* Add pre- or post-text to the form
@@ -877,22 +930,32 @@ abstract class FormSpecialPage extends SpecialPage {
protected function alterForm( HTMLForm $form ) {}
/**
- * Get the HTMLForm to control behaviour
+ * Get message prefix for HTMLForm
+ *
+ * @since 1.21
+ * @return string
+ */
+ protected function getMessagePrefix() {
+ return strtolower( $this->getName() );
+ }
+
+ /**
+ * Get the HTMLForm to control behavior
* @return HTMLForm|null
*/
protected function getForm() {
$this->fields = $this->getFormFields();
- $form = new HTMLForm( $this->fields, $this->getContext() );
+ $form = new HTMLForm( $this->fields, $this->getContext(), $this->getMessagePrefix() );
$form->setSubmitCallback( array( $this, 'onSubmit' ) );
- $form->setWrapperLegend( $this->msg( strtolower( $this->getName() ) . '-legend' ) );
+ $form->setWrapperLegend( $this->msg( $this->getMessagePrefix() . '-legend' ) );
$form->addHeaderText(
- $this->msg( strtolower( $this->getName() ) . '-text' )->parseAsBlock() );
+ $this->msg( $this->getMessagePrefix() . '-text' )->parseAsBlock() );
// Retain query parameters (uselang etc)
$params = array_diff_key(
$this->getRequest()->getQueryValues(), array( 'title' => null ) );
- $form->addHiddenField( 'redirectparams', wfArrayToCGI( $params ) );
+ $form->addHiddenField( 'redirectparams', wfArrayToCgi( $params ) );
$form->addPreText( $this->preText() );
$form->addPostText( $this->postText() );
@@ -909,18 +972,18 @@ abstract class FormSpecialPage extends SpecialPage {
* @param $data Array
* @return Bool|Array true for success, false for didn't-try, array of errors on failure
*/
- public abstract function onSubmit( array $data );
+ abstract public function onSubmit( array $data );
/**
* Do something exciting on successful processing of the form, most likely to show a
* confirmation message
*/
- public abstract function onSuccess();
+ abstract public function onSuccess();
/**
* Basic SpecialPage workflow: get a form, send it to the user; get some data back,
*
- * @param $par String Subpage string if one was specified
+ * @param string $par Subpage string if one was specified
*/
public function execute( $par ) {
$this->setParameter( $par );
@@ -945,8 +1008,8 @@ abstract class FormSpecialPage extends SpecialPage {
* Called from execute() to check if the given user can perform this action.
* Failures here must throw subclasses of ErrorPageError.
* @param $user User
+ * @throws UserBlockedError
* @return Bool true
- * @throws ErrorPageError
*/
protected function checkExecutePermissions( User $user ) {
$this->checkPermissions();
@@ -1019,7 +1082,7 @@ abstract class RedirectSpecialPage extends UnlistedSpecialPage {
// Query parameters that can be passed through redirects
protected $mAllowedRedirectParams = array();
- // Query parameteres added by redirects
+ // Query parameters added by redirects
protected $mAddedRedirectParams = array();
public function execute( $par ) {
@@ -1029,14 +1092,12 @@ abstract class RedirectSpecialPage extends UnlistedSpecialPage {
if ( $redirect instanceof Title ) {
$url = $redirect->getFullUrl( $query );
$this->getOutput()->redirect( $url );
- wfProfileOut( __METHOD__ );
return $redirect;
// Redirect to index.php with query parameters
} elseif ( $redirect === true ) {
global $wgScript;
- $url = $wgScript . '?' . wfArrayToCGI( $query );
+ $url = $wgScript . '?' . wfArrayToCgi( $query );
$this->getOutput()->redirect( $url );
- wfProfileOut( __METHOD__ );
return $redirect;
} else {
$class = __CLASS__;
@@ -1048,7 +1109,7 @@ abstract class RedirectSpecialPage extends UnlistedSpecialPage {
* If the special page is a redirect, then get the Title object it redirects to.
* False otherwise.
*
- * @param $par String Subpage string
+ * @param string $par Subpage string
* @return Title|bool
*/
abstract public function getRedirect( $par );
@@ -1130,7 +1191,7 @@ class SpecialCreateAccount extends SpecialRedirectToSpecial {
}
/**
* SpecialMypage, SpecialMytalk and SpecialMycontributions special pages
- * are used to get user independant links pointing to the user page, talk
+ * are used to get user independent links pointing to the user page, talk
* page and list of contributions.
* This can let us cache a single copy of any generated content for all
* users.
@@ -1221,7 +1282,7 @@ abstract class RedirectSpecialArticle extends RedirectSpecialPage {
'ctype', 'maxage', 'smaxage',
);
- wfRunHooks( "RedirectSpecialArticleRedirectParams", array(&$redirectParams) );
+ wfRunHooks( "RedirectSpecialArticleRedirectParams", array( &$redirectParams ) );
$this->mAllowedRedirectParams = $redirectParams;
}
}
@@ -1268,7 +1329,7 @@ class SpecialMytalk extends RedirectSpecialArticle {
*/
class SpecialMycontributions extends RedirectSpecialPage {
function __construct() {
- parent::__construct( 'Mycontributions' );
+ parent::__construct( 'Mycontributions' );
$this->mAllowedRedirectParams = array( 'limit', 'namespace', 'tagfilter',
'offset', 'dir', 'year', 'month', 'feed' );
}
diff --git a/includes/SpecialPageFactory.php b/includes/SpecialPageFactory.php
index 95f75a8e..7368ab77 100644
--- a/includes/SpecialPageFactory.php
+++ b/includes/SpecialPageFactory.php
@@ -80,6 +80,7 @@ class SpecialPageFactory {
'Categories' => 'SpecialCategories',
'Disambiguations' => 'DisambiguationsPage',
'Listredirects' => 'ListredirectsPage',
+ 'PagesWithProp' => 'SpecialPagesWithProp',
// Login/create account
'Userlogin' => 'LoginForm',
@@ -95,7 +96,7 @@ class SpecialPageFactory {
'Preferences' => 'SpecialPreferences',
'Contributions' => 'SpecialContributions',
'Listgrouprights' => 'SpecialListGroupRights',
- 'Listusers' => 'SpecialListUsers' ,
+ 'Listusers' => 'SpecialListUsers',
'Listadmins' => 'SpecialListAdmins',
'Listbots' => 'SpecialListBots',
'Activeusers' => 'SpecialActiveUsers',
@@ -119,7 +120,7 @@ class SpecialPageFactory {
'Upload' => 'SpecialUpload',
'UploadStash' => 'SpecialUploadStash',
- // Wiki data and tools
+ // Data and tools
'Statistics' => 'SpecialStatistics',
'Allmessages' => 'SpecialAllmessages',
'Version' => 'SpecialVersion',
@@ -155,7 +156,6 @@ class SpecialPageFactory {
'Blankpage' => 'SpecialBlankpage',
'Blockme' => 'SpecialBlockme',
'Emailuser' => 'SpecialEmailUser',
- 'JavaScriptTest' => 'SpecialJavaScriptTest',
'Movepage' => 'MovePageForm',
'Mycontributions' => 'SpecialMycontributions',
'Mypage' => 'SpecialMypage',
@@ -178,7 +178,7 @@ class SpecialPageFactory {
static function getList() {
global $wgSpecialPages;
global $wgDisableCounters, $wgDisableInternalSearch, $wgEmailAuthentication;
- global $wgEnableEmail;
+ global $wgEnableEmail, $wgEnableJavaScriptTest;
if ( !is_object( self::$mList ) ) {
wfProfileIn( __METHOD__ );
@@ -200,6 +200,10 @@ class SpecialPageFactory {
self::$mList['ChangeEmail'] = 'SpecialChangeEmail';
}
+ if( $wgEnableJavaScriptTest ) {
+ self::$mList['JavaScriptTest'] = 'SpecialJavaScriptTest';
+ }
+
// Add extension special pages
self::$mList = array_merge( self::$mList, $wgSpecialPages );
@@ -218,7 +222,7 @@ class SpecialPageFactory {
/**
* Initialise and return the list of special page aliases. Returns an object with
* properties which can be accessed $obj->pagename - each property is an array of
- * aliases; the first in the array is the cannonical alias. All registered special
+ * aliases; the first in the array is the canonical alias. All registered special
* pages are guaranteed to have a property entry, and for that property array to
* contain at least one entry (English fallbacks will be added if necessary).
* @return Object
@@ -282,8 +286,11 @@ class SpecialPageFactory {
*
* @param $page Mixed: SpecialPage or string
* @param $group String
+ * @deprecated 1.21 Override SpecialPage::getGroupName
*/
public static function setGroup( $page, $group ) {
+ wfDeprecated( __METHOD__, '1.21' );
+
global $wgSpecialPageGroups;
$name = is_object( $page ) ? $page->getName() : $page;
$wgSpecialPageGroups[$name] = $group;
@@ -294,34 +301,18 @@ class SpecialPageFactory {
*
* @param $page SpecialPage
* @return String
+ * @deprecated 1.21 Use SpecialPage::getFinalGroupName
*/
public static function getGroup( &$page ) {
- $name = $page->getName();
+ wfDeprecated( __METHOD__, '1.21' );
- global $wgSpecialPageGroups;
- static $specialPageGroupsCache = array();
- if ( isset( $specialPageGroupsCache[$name] ) ) {
- return $specialPageGroupsCache[$name];
- }
- $msg = wfMessage( 'specialpages-specialpagegroup-' . strtolower( $name ) );
- if ( !$msg->isBlank() ) {
- $group = $msg->text();
- } else {
- $group = isset( $wgSpecialPageGroups[$name] )
- ? $wgSpecialPageGroups[$name]
- : '-';
- }
- if ( $group == '-' ) {
- $group = 'other';
- }
- $specialPageGroupsCache[$name] = $group;
- return $group;
+ return $page->getFinalGroupName();
}
/**
* Check if a given name exist as a special page or as a special page alias
*
- * @param $name String: name of a special page
+ * @param string $name name of a special page
* @return Boolean: true if a special page exists with this name
*/
public static function exists( $name ) {
@@ -332,8 +323,8 @@ class SpecialPageFactory {
/**
* Find the object with a given name and return it (or NULL)
*
- * @param $name String Special page name, may be localised and/or an alias
- * @return SpecialPage object or null if the page doesn't exist
+ * @param string $name Special page name, may be localised and/or an alias
+ * @return SpecialPage|null SpecialPage object or null if the page doesn't exist
*/
public static function getPage( $name ) {
list( $realName, /*...*/ ) = self::resolveAlias( $name );
@@ -370,11 +361,13 @@ class SpecialPageFactory {
}
foreach ( self::getList() as $name => $rec ) {
$page = self::getPage( $name );
- if ( $page // not null
- && $page->isListed()
- && ( !$page->isRestricted() || $page->userCanExecute( $user ) )
- ) {
- $pages[$name] = $page;
+ if ( $page ) { // not null
+ $page->setContext( RequestContext::getMain() );
+ if ( $page->isListed()
+ && ( !$page->isRestricted() || $page->userCanExecute( $user ) )
+ ) {
+ $pages[$name] = $page;
+ }
}
}
return $pages;
@@ -471,7 +464,7 @@ class SpecialPageFactory {
if ( $name != $page->getLocalName() && !$context->getRequest()->wasPosted() ) {
$query = $context->getRequest()->getQueryValues();
unset( $query['title'] );
- $query = wfArrayToCGI( $query );
+ $query = wfArrayToCgi( $query );
$title = $page->getTitle( $par );
$url = $title->getFullUrl( $query );
$context->getOutput()->redirect( $url );
diff --git a/includes/SqlDataUpdate.php b/includes/SqlDataUpdate.php
index 52c9be00..79dcdc59 100644
--- a/includes/SqlDataUpdate.php
+++ b/includes/SqlDataUpdate.php
@@ -48,7 +48,7 @@ abstract class SqlDataUpdate extends DataUpdate {
public function __construct( $withTransaction = true ) {
global $wgAntiLockFlags;
- parent::__construct( );
+ parent::__construct();
if ( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) {
$this->mOptions = array();
@@ -76,7 +76,7 @@ abstract class SqlDataUpdate extends DataUpdate {
// NOTE: nested transactions are not supported, only start a transaction if none is open
if ( $this->mDb->trxLevel() === 0 ) {
- $this->mDb->begin( get_class( $this ) . '::beginTransaction' );
+ $this->mDb->begin( get_class( $this ) . '::beginTransaction' );
$this->mHasTransaction = true;
}
}
@@ -95,7 +95,7 @@ abstract class SqlDataUpdate extends DataUpdate {
* Abort the database transaction started via beginTransaction (if any).
*/
public function abortTransaction() {
- if ( $this->mHasTransaction ) {
+ if ( $this->mHasTransaction ) { //XXX: actually... maybe always?
$this->mDb->rollback( get_class( $this ) . '::abortTransaction' );
$this->mHasTransaction = false;
}
@@ -108,8 +108,8 @@ abstract class SqlDataUpdate extends DataUpdate {
* @param $namespace Integer
* @param $dbkeys Array
*/
- protected function invalidatePages( $namespace, Array $dbkeys ) {
- if ( !count( $dbkeys ) ) {
+ protected function invalidatePages( $namespace, array $dbkeys ) {
+ if ( $dbkeys === array() ) {
return;
}
@@ -127,10 +127,12 @@ abstract class SqlDataUpdate extends DataUpdate {
'page_touched < ' . $this->mDb->addQuotes( $now )
), __METHOD__
);
+
foreach ( $res as $row ) {
$ids[] = $row->page_id;
}
- if ( !count( $ids ) ) {
+
+ if ( $ids === array() ) {
return;
}
diff --git a/includes/SquidPurgeClient.php b/includes/SquidPurgeClient.php
index 8eb0f6bf..f5fd1950 100644
--- a/includes/SquidPurgeClient.php
+++ b/includes/SquidPurgeClient.php
@@ -21,9 +21,9 @@
*/
/**
- * An HTTP 1.0 client built for the purposes of purging Squid and Varnish.
- * Uses asynchronous I/O, allowing purges to be done in a highly parallel
- * manner.
+ * An HTTP 1.0 client built for the purposes of purging Squid and Varnish.
+ * Uses asynchronous I/O, allowing purges to be done in a highly parallel
+ * manner.
*
* Could be replaced by curl_multi_exec() or some such.
*/
@@ -123,7 +123,7 @@ class SquidPurgeClient {
return array( $socket );
}
- /**
+ /**
* Get the host's IP address.
* Does not support IPv6 at present due to the lack of a convenient interface in PHP.
*/
@@ -176,11 +176,32 @@ class SquidPurgeClient {
* @param $url string
*/
public function queuePurge( $url ) {
+ global $wgSquidPurgeUseHostHeader;
$url = SquidUpdate::expand( str_replace( "\n", '', $url ) );
- $this->requests[] = "PURGE $url HTTP/1.0\r\n" .
- "Connection: Keep-Alive\r\n" .
- "Proxy-Connection: Keep-Alive\r\n" .
- "User-Agent: " . Http::userAgent() . ' ' . __CLASS__ . "\r\n\r\n";
+ $request = array();
+ if ( $wgSquidPurgeUseHostHeader ) {
+ $url = wfParseUrl( $url );
+ $host = $url['host'];
+ if ( isset( $url['port'] ) && strlen( $url['port'] ) > 0 ) {
+ $host .= ":" . $url['port'];
+ }
+ $path = $url['path'];
+ if ( isset( $url['query'] ) && is_string( $url['query'] ) ) {
+ $path = wfAppendQuery( $path, $url['query'] );
+ }
+ $request[] = "PURGE $path HTTP/1.1";
+ $request[] = "Host: $host";
+ } else {
+ $request[] = "PURGE $url HTTP/1.0";
+ }
+ $request[] = "Connection: Keep-Alive";
+ $request[] = "Proxy-Connection: Keep-Alive";
+ $request[] = "User-Agent: " . Http::userAgent() . ' ' . __CLASS__;
+ // Two ''s to create \r\n\r\n
+ $request[] = '';
+ $request[] = '';
+
+ $this->requests[] = implode( "\r\n", $request );
if ( $this->currentRequestIndex === null ) {
$this->nextRequest();
}
@@ -272,7 +293,7 @@ class SquidPurgeClient {
if ( count( $lines ) < 2 ) {
return 'done';
}
- if ( $this->readState == 'status' ) {
+ if ( $this->readState == 'status' ) {
$this->processStatusLine( $lines[0] );
} else { // header
$this->processHeaderLine( $lines[0] );
@@ -297,7 +318,7 @@ class SquidPurgeClient {
return 'done';
}
default:
- throw new MWException( __METHOD__.': unexpected state' );
+ throw new MWException( __METHOD__ . ': unexpected state' );
}
}
@@ -352,7 +373,7 @@ class SquidPurgeClient {
* @param $msg string
*/
protected function log( $msg ) {
- wfDebugLog( 'squid', __CLASS__." ($this->host): $msg\n" );
+ wfDebugLog( 'squid', __CLASS__ . " ($this->host): $msg\n" );
}
}
@@ -408,14 +429,14 @@ class SquidPurgeClientPool {
$numReady = socket_select( $readSockets, $writeSockets, $exceptSockets, $timeout );
wfRestoreWarnings();
if ( $numReady === false ) {
- wfDebugLog( 'squid', __METHOD__.': Error in stream_select: ' .
+ wfDebugLog( 'squid', __METHOD__ . ': Error in stream_select: ' .
socket_strerror( socket_last_error() ) . "\n" );
break;
}
// Check for timeout, use 1% tolerance since we aimed at having socket_select()
// exit at precisely the overall timeout
if ( microtime( true ) - $startTime > $this->timeout * 0.99 ) {
- wfDebugLog( 'squid', __CLASS__.": timeout ({$this->timeout}s)\n" );
+ wfDebugLog( 'squid', __CLASS__ . ": timeout ({$this->timeout}s)\n" );
break;
} elseif ( !$numReady ) {
continue;
diff --git a/includes/Status.php b/includes/Status.php
index 10dfb516..5d6236f7 100644
--- a/includes/Status.php
+++ b/includes/Status.php
@@ -47,7 +47,7 @@ class Status {
/**
* Factory function for fatal errors
*
- * @param $message String: message name
+ * @param string|Message $message message name or object
* @return Status
*/
static function newFatal( $message /*, parameters...*/ ) {
@@ -103,7 +103,7 @@ class Status {
/**
* Add a new warning
*
- * @param $message String: message name
+ * @param string|Message $message message name or object
*/
function warning( $message /*, parameters... */ ) {
$params = array_slice( func_get_args(), 1 );
@@ -117,7 +117,7 @@ class Status {
* Add an error, do not set fatal flag
* This can be used for non-fatal errors
*
- * @param $message String: message name
+ * @param string|Message $message message name or object
*/
function error( $message /*, parameters... */ ) {
$params = array_slice( func_get_args(), 1 );
@@ -131,7 +131,7 @@ class Status {
* Add an error and set OK to false, indicating that the operation
* as a whole was fatal
*
- * @param $message String: message name
+ * @param string|Message $message message name or object
*/
function fatal( $message /*, parameters... */ ) {
$params = array_slice( func_get_args(), 1 );
@@ -167,31 +167,31 @@ class Status {
/**
* Get the error list as a wikitext formatted list
*
- * @param $shortContext String: a short enclosing context message name, to
+ * @param string $shortContext a short enclosing context message name, to
* be used when there is a single error
- * @param $longContext String: a long enclosing context message name, for a list
+ * @param string $longContext a long enclosing context message name, for a list
* @return String
*/
function getWikiText( $shortContext = false, $longContext = false ) {
if ( count( $this->errors ) == 0 ) {
if ( $this->ok ) {
$this->fatal( 'internalerror_info',
- __METHOD__." called for a good result, this is incorrect\n" );
+ __METHOD__ . " called for a good result, this is incorrect\n" );
} else {
$this->fatal( 'internalerror_info',
- __METHOD__.": Invalid result object: no error text but not OK\n" );
+ __METHOD__ . ": Invalid result object: no error text but not OK\n" );
}
}
if ( count( $this->errors ) == 1 ) {
- $s = $this->getWikiTextForError( $this->errors[0], $this->errors[0] );
+ $s = $this->getErrorMessage( $this->errors[0] );
if ( $shortContext ) {
$s = wfMessage( $shortContext, $s )->plain();
} elseif ( $longContext ) {
$s = wfMessage( $longContext, "* $s\n" )->plain();
}
} else {
- $s = '* '. implode("\n* ",
- $this->getWikiTextArray( $this->errors ) ) . "\n";
+ $s = '* '. implode( "\n* ",
+ $this->getErrorMessageArray( $this->errors ) ) . "\n";
if ( $longContext ) {
$s = wfMessage( $longContext, $s )->plain();
} elseif ( $shortContext ) {
@@ -202,7 +202,7 @@ class Status {
}
/**
- * Return the wiki text for a single error.
+ * Return the message for a single error.
* @param $error Mixed With an array & two values keyed by
* 'message' and 'params', use those keys-value pairs.
* Otherwise, if its an array, just use the first value as the
@@ -210,19 +210,35 @@ class Status {
*
* @return String
*/
- protected function getWikiTextForError( $error ) {
+ protected function getErrorMessage( $error ) {
if ( is_array( $error ) ) {
- if ( isset( $error['message'] ) && isset( $error['params'] ) ) {
- return wfMessage( $error['message'],
- array_map( 'wfEscapeWikiText', $this->cleanParams( $error['params'] ) ) )->plain();
+ if( isset( $error['message'] ) && $error['message'] instanceof Message ) {
+ $msg = $error['message'];
+ } elseif ( isset( $error['message'] ) && isset( $error['params'] ) ) {
+ $msg = wfMessage( $error['message'],
+ array_map( 'wfEscapeWikiText', $this->cleanParams( $error['params'] ) ) );
} else {
- $message = array_shift($error);
- return wfMessage( $message,
- array_map( 'wfEscapeWikiText', $this->cleanParams( $error ) ) )->plain();
+ $msgName = array_shift( $error );
+ $msg = wfMessage( $msgName,
+ array_map( 'wfEscapeWikiText', $this->cleanParams( $error ) ) );
}
} else {
- return wfMessage( $error )->plain();
+ $msg = wfMessage( $error );
}
+ return $msg->plain();
+ }
+
+ /**
+ * Get the error message as HTML. This is done by parsing the wikitext error
+ * message.
+ *
+ * @note: this does not perform a full wikitext to HTML conversion, it merely applies
+ * a message transformation.
+ * @todo: figure out whether that is actually The Right Thing.
+ */
+ public function getHTML( $shortContext = false, $longContext = false ) {
+ $text = $this->getWikiText( $shortContext, $longContext );
+ return MessageCache::singleton()->transform( $text, true );
}
/**
@@ -230,8 +246,8 @@ class Status {
* @param $errors Array
* @return Array
*/
- function getWikiTextArray( $errors ) {
- return array_map( array( $this, 'getWikiTextForError' ), $errors );
+ protected function getErrorMessageArray( $errors ) {
+ return array_map( array( $this, 'getErrorMessage' ), $errors );
}
/**
@@ -278,7 +294,9 @@ class Status {
$result = array();
foreach ( $this->errors as $error ) {
if ( $error['type'] === $type ) {
- if( $error['params'] ) {
+ if( $error['message'] instanceof Message ) {
+ $result[] = $error['message'];
+ } elseif( $error['params'] ) {
$result[] = array_merge( array( $error['message'] ), $error['params'] );
} else {
$result[] = array( $error['message'] );
@@ -309,7 +327,10 @@ class Status {
/**
* Returns true if the specified message is present as a warning or error
*
- * @param $msg String: message name
+ * Note, due to the lack of tools for comparing Message objects, this
+ * function will not work when using a Message object as a parameter.
+ *
+ * @param string $msg message name
* @return Boolean
*/
function hasMessage( $msg ) {
@@ -325,9 +346,12 @@ class Status {
* If the specified source message exists, replace it with the specified
* destination message, but keep the same parameters as in the original error.
*
- * Return true if the replacement was done, false otherwise.
+ * Note, due to the lack of tools for comparing Message objects, this
+ * function will not work when using a Message object as the search parameter.
*
- * @return bool
+ * @param $source Message|String: Message key or object to search for
+ * @param $dest Message|String: Replacement message key or object
+ * @return bool Return true if the replacement was done, false otherwise.
*/
function replaceMessage( $source, $dest ) {
$replaced = false;
diff --git a/includes/StreamFile.php b/includes/StreamFile.php
index 95c69a20..f5e4acff 100644
--- a/includes/StreamFile.php
+++ b/includes/StreamFile.php
@@ -32,9 +32,10 @@ class StreamFile {
* Headers sent include: Content-type, Content-Length, Last-Modified,
* and Content-Disposition.
*
- * @param $fname string Full name and path of the file to stream
- * @param $headers array Any additional headers to send
- * @param $sendErrors bool Send error messages if errors occur (like 404)
+ * @param string $fname Full name and path of the file to stream
+ * @param array $headers Any additional headers to send
+ * @param bool $sendErrors Send error messages if errors occur (like 404)
+ * @throws MWException
* @return bool Success
*/
public static function stream( $fname, $headers = array(), $sendErrors = true ) {
@@ -70,10 +71,10 @@ class StreamFile {
* (b) cancels any PHP output buffering and automatic gzipping of output
* (c) sends Content-Length header based on HTTP_IF_MODIFIED_SINCE check
*
- * @param $path string Storage path or file system path
- * @param $info Array|bool File stat info with 'mtime' and 'size' fields
- * @param $headers Array Additional headers to send
- * @param $sendErrors bool Send error messages if errors occur (like 404)
+ * @param string $path Storage path or file system path
+ * @param array|bool $info File stat info with 'mtime' and 'size' fields
+ * @param array $headers Additional headers to send
+ * @param bool $sendErrors Send error messages if errors occur (like 404)
* @return int|bool READY_STREAM, NOT_MODIFIED, or false on failure
*/
public static function prepareForStream(
@@ -142,8 +143,8 @@ class StreamFile {
/**
* Determine the file type of a file based on the path
*
- * @param $filename string Storage path or file system path
- * @param $safe bool Whether to do retroactive upload blacklist checks
+ * @param string $filename Storage path or file system path
+ * @param bool $safe Whether to do retroactive upload blacklist checks
* @return null|string
*/
public static function contentTypeFromPath( $filename, $safe = true ) {
diff --git a/includes/StringUtils.php b/includes/StringUtils.php
index 43275a66..f4c98f1d 100644
--- a/includes/StringUtils.php
+++ b/includes/StringUtils.php
@@ -24,6 +24,51 @@
* A collection of static methods to play with strings.
*/
class StringUtils {
+
+ /**
+ * Test whether a string is valid UTF-8.
+ *
+ * The function check for invalid byte sequences, overlong encoding but
+ * not for different normalisations.
+ *
+ * This relies internally on the mbstring function mb_check_encoding()
+ * hardcoded to check against UTF-8. Whenever the function is not available
+ * we fallback to a pure PHP implementation. Setting $disableMbstring to
+ * true will skip the use of mb_check_encoding, this is mostly intended for
+ * unit testing our internal implementation.
+ *
+ * @since 1.21
+ *
+ * @param string $value String to check
+ * @param boolean $disableMbstring Whether to use the pure PHP
+ * implementation instead of trying mb_check_encoding. Intended for unit
+ * testing. Default: false
+ *
+ * @return boolean Whether the given $value is a valid UTF-8 encoded string
+ */
+ static function isUtf8( $value, $disableMbstring = false ) {
+
+ if ( preg_match( '/[\x80-\xff]/', $value ) === 0 ) {
+ # no high bit set, this is pure ASCII which is de facto
+ # valid UTF-8
+ return true;
+ }
+
+ if ( !$disableMbstring && function_exists( 'mb_check_encoding' ) ) {
+ return mb_check_encoding( $value, 'UTF-8' );
+ } else {
+ $hasUtf8 = preg_match( '/^(?>
+ [\x00-\x7f]
+ | [\xc0-\xdf][\x80-\xbf]
+ | [\xe0-\xef][\x80-\xbf]{2}
+ | [\xf0-\xf7][\x80-\xbf]{3}
+ | [\xf8-\xfb][\x80-\xbf]{4}
+ | \xfc[\x84-\xbf][\x80-\xbf]{4}
+ )+$/x', $value );
+ return ($hasUtf8 > 0 );
+ }
+ }
+
/**
* Perform an operation equivalent to
*
@@ -65,16 +110,16 @@ class StringUtils {
* memory. The delimiters are literal strings, not regular expressions.
*
* If the start delimiter ends with an initial substring of the end delimiter,
- * e.g. in the case of C-style comments, the behaviour differs from the model
+ * e.g. in the case of C-style comments, the behavior differs from the model
* regex. In this implementation, the end must share no characters with the
* start, so e.g. /*\/ is not considered to be both the start and end of a
* comment. /*\/xy/*\/ is considered to be a single comment with contents /xy/.
*
- * @param $startDelim String: start delimiter
- * @param $endDelim String: end delimiter
+ * @param string $startDelim start delimiter
+ * @param string $endDelim end delimiter
* @param $callback Callback: function to call on each match
* @param $subject String
- * @param $flags String: regular expression flags
+ * @param string $flags regular expression flags
* @throws MWException
* @return string
*/
@@ -90,12 +135,12 @@ class StringUtils {
$m = array();
while ( $inputPos < strlen( $subject ) &&
- preg_match( "!($encStart)|($encEnd)!S$flags", $subject, $m, PREG_OFFSET_CAPTURE, $inputPos ) )
+ preg_match( "!($encStart)|($encEnd)!S$flags", $subject, $m, PREG_OFFSET_CAPTURE, $inputPos ) )
{
$tokenOffset = $m[0][1];
if ( $m[1][0] != '' ) {
if ( $foundStart &&
- $strcmp( $endDelim, substr( $subject, $tokenOffset, $endLength ) ) == 0 )
+ $strcmp( $endDelim, substr( $subject, $tokenOffset, $endLength ) ) == 0 )
{
# An end match is present at the same location
$tokenType = 'end';
@@ -155,12 +200,12 @@ class StringUtils {
*
* preg_replace( "!$startDelim(.*)$endDelim!$flags", $replace, $subject )
*
- * @param $startDelim String: start delimiter regular expression
- * @param $endDelim String: end delimiter regular expression
- * @param $replace String: replacement string. May contain $1, which will be
+ * @param string $startDelim start delimiter regular expression
+ * @param string $endDelim end delimiter regular expression
+ * @param string $replace replacement string. May contain $1, which will be
* replaced by the text between the delimiters
- * @param $subject String to search
- * @param $flags String: regular expression flags
+ * @param string $subject to search
+ * @param string $flags regular expression flags
* @return String: The string with the matches replaced
*/
static function delimiterReplace( $startDelim, $endDelim, $replace, $subject, $flags = '' ) {
@@ -360,7 +405,7 @@ class ReplacementArray {
/**
* Set an element of the replacement array
* @param $from string
- * @param $to stromg
+ * @param $to string
*/
function setPair( $from, $to ) {
$this->data[$from] = $to;
@@ -387,7 +432,7 @@ class ReplacementArray {
* @param $from string
*/
function removePair( $from ) {
- unset($this->data[$from]);
+ unset( $this->data[$from] );
$this->fss = false;
}
@@ -424,7 +469,7 @@ class ReplacementArray {
/**
* An iterator which works exactly like:
- *
+ *
* foreach ( explode( $delim, $s ) as $element ) {
* ...
* }
diff --git a/includes/StubObject.php b/includes/StubObject.php
index 615bcb5f..f0a35740 100644
--- a/includes/StubObject.php
+++ b/includes/StubObject.php
@@ -41,9 +41,9 @@ class StubObject {
/**
* Constructor.
*
- * @param $global String: name of the global variable.
- * @param $class String: name of the class of the real object.
- * @param $params Array: parameters to pass to contructor of the real
+ * @param string $global name of the global variable.
+ * @param string $class name of the class of the real object.
+ * @param array $params parameters to pass to constructor of the real
* object.
*/
function __construct( $global = null, $class = null, $params = array() ) {
@@ -53,7 +53,7 @@ class StubObject {
}
/**
- * Returns a bool value whetever $obj is a stub object. Can be used to break
+ * Returns a bool value whenever $obj is a stub object. Can be used to break
* a infinite loop when unstubbing an object.
*
* @param $obj Object to check.
@@ -70,8 +70,8 @@ class StubObject {
* This function will also call the function with the same name in the real
* object.
*
- * @param $name String: name of the function called
- * @param $args Array: arguments
+ * @param string $name name of the function called
+ * @param array $args arguments
* @return mixed
*/
function _call( $name, $args ) {
@@ -91,8 +91,8 @@ class StubObject {
* Function called by PHP if no function with that name exists in this
* object.
*
- * @param $name String: name of the function called
- * @param $args Array: arguments
+ * @param string $name name of the function called
+ * @param array $args arguments
* @return mixed
*/
function __call( $name, $args ) {
@@ -105,9 +105,10 @@ class StubObject {
* This is public, for the convenience of external callers wishing to access
* properties, e.g. eval.php
*
- * @param $name String: name of the method called in this object.
- * @param $level Integer: level to go in the stact trace to get the function
+ * @param string $name name of the method called in this object.
+ * @param $level Integer: level to go in the stack trace to get the function
* who called this function.
+ * @throws MWException
*/
function _unstub( $name = '_unstub', $level = 2 ) {
static $recursionLevel = 0;
@@ -117,7 +118,7 @@ class StubObject {
}
if ( get_class( $GLOBALS[$this->mGlobal] ) != $this->mClass ) {
- $fname = __METHOD__.'-'.$this->mGlobal;
+ $fname = __METHOD__ . '-' . $this->mGlobal;
wfProfileIn( $fname );
$caller = wfGetCaller( $level );
if ( ++$recursionLevel > 2 ) {
diff --git a/includes/Timestamp.php b/includes/Timestamp.php
index c9ba8d91..1cf99a79 100644
--- a/includes/Timestamp.php
+++ b/includes/Timestamp.php
@@ -42,7 +42,6 @@ class MWTimestamp {
TS_RFC2822 => 'D, d M Y H:i:s',
TS_ORACLE => 'd-m-Y H:i:s.000000', // Was 'd-M-y h.i.s A' . ' +00:00' before r51500
TS_POSTGRES => 'Y-m-d H:i:s',
- TS_DB2 => 'Y-m-d H:i:s',
);
/**
@@ -54,7 +53,9 @@ class MWTimestamp {
"seconds" => 1000, // 1000 milliseconds per second
"minutes" => 60, // 60 seconds per minute
"hours" => 60, // 60 minutes per hour
- "days" => 24 // 24 hours per day
+ "days" => 24, // 24 hours per day
+ "months" => 30, // approximately 30 days per month
+ "years" => 12, // 12 months per year
);
/**
@@ -69,7 +70,9 @@ class MWTimestamp {
* Make a new timestamp and set it to the specified time,
* or the current time if unspecified.
*
- * @param $timestamp bool|string Timestamp to set, or false for current time
+ * @since 1.20
+ *
+ * @param bool|string $timestamp Timestamp to set, or false for current time
*/
public function __construct( $timestamp = false ) {
$this->setTimestamp( $timestamp );
@@ -81,7 +84,9 @@ class MWTimestamp {
* Parse the given timestamp into either a DateTime object or a Unix timestamp,
* and then store it.
*
- * @param $ts string|bool Timestamp to store, or false for now
+ * @since 1.20
+ *
+ * @param string|bool $ts Timestamp to store, or false for now
* @throws TimestampException
*/
public function setTimestamp( $ts = false ) {
@@ -112,8 +117,6 @@ class MWTimestamp {
# TS_POSTGRES
} elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d* GMT$/', $ts, $da ) ) {
# TS_POSTGRES
- } elseif (preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.\d\d\d$/', $ts, $da ) ) {
- # TS_DB2
} elseif ( preg_match( '/^[ \t\r\n]*([A-Z][a-z]{2},[ \t\r\n]*)?' . # Day of week
'\d\d?[ \t\r\n]*[A-Z][a-z]{2}[ \t\r\n]*\d{2}(?:\d{2})?' . # dd Mon yyyy
'[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d/S', $ts ) ) { # hh:mm:ss
@@ -136,14 +139,10 @@ class MWTimestamp {
$strtime = call_user_func_array( "sprintf", $da );
}
- if( function_exists( "date_create" ) ) {
- try {
- $final = new DateTime( $strtime, new DateTimeZone( 'GMT' ) );
- } catch(Exception $e) {
- throw new TimestampException( __METHOD__ . ' Invalid timestamp format.' );
- }
- } else {
- $final = strtotime( $strtime );
+ try {
+ $final = new DateTime( $strtime, new DateTimeZone( 'GMT' ) );
+ } catch(Exception $e) {
+ throw new TimestampException( __METHOD__ . ' Invalid timestamp format.' );
}
if( $final === false ) {
@@ -158,7 +157,9 @@ class MWTimestamp {
* Convert the internal timestamp to the specified format and then
* return it.
*
- * @param $style int Constant Output format for timestamp
+ * @since 1.20
+ *
+ * @param int $style Constant Output format for timestamp
* @throws TimestampException
* @return string The formatted timestamp
*/
@@ -192,7 +193,9 @@ class MWTimestamp {
* generate a readable timestamp by returning "<N> <units> ago", where the
* largest possible unit is used.
*
- * @return string Formatted timestamp
+ * @since 1.20
+ *
+ * @return Message Formatted timestamp
*/
public function getHumanTimestamp() {
$then = $this->getTimestamp( TS_UNIX );
@@ -212,13 +215,15 @@ class MWTimestamp {
if( $message ) {
$initial = call_user_func_array( 'wfMessage', $message );
- return wfMessage( 'ago', $initial );
+ return wfMessage( 'ago', $initial->parse() );
} else {
return wfMessage( 'just-now' );
}
}
/**
+ * @since 1.20
+ *
* @return string
*/
public function __toString() {
@@ -226,4 +231,7 @@ class MWTimestamp {
}
}
+/**
+ * @since 1.20
+ */
class TimestampException extends MWException {}
diff --git a/includes/Title.php b/includes/Title.php
index 1b5e21d2..ca66aaec 100644
--- a/includes/Title.php
+++ b/includes/Title.php
@@ -65,6 +65,7 @@ class Title {
var $mFragment; // /< Title fragment (i.e. the bit after the #)
var $mArticleID = -1; // /< Article ID, fetched from the link cache on demand
var $mLatestID = false; // /< ID of most recent revision
+ var $mContentModel = false; // /< ID of the page's content model, i.e. one of the CONTENT_MODEL_XXX constants
private $mEstimateRevisions; // /< Estimated number of revisions; null of not loaded
var $mRestrictions = array(); // /< Array of groups allowed to edit this article
var $mOldRestrictions = false;
@@ -87,7 +88,6 @@ class Title {
var $mHasSubpage; // /< Whether a page has any subpages
// @}
-
/**
* Constructor
*/
@@ -96,7 +96,7 @@ class Title {
/**
* Create a new Title from a prefixed DB key
*
- * @param $key String the database key, which has underscores
+ * @param string $key the database key, which has underscores
* instead of spaces, possibly including namespace and
* interwiki prefixes
* @return Title, or NULL on an error
@@ -115,10 +115,10 @@ class Title {
* Create a new Title from text, such as what one would find in a link. De-
* codes any HTML entities in the text.
*
- * @param $text String the link text; spaces, prefixes, and an
+ * @param string $text the link text; spaces, prefixes, and an
* initial ':' indicating the main namespace are accepted.
- * @param $defaultNamespace Int the namespace to use if none is speci-
- * fied by a prefix. If you want to force a specific namespace even if
+ * @param int $defaultNamespace the namespace to use if none is specified
+ * by a prefix. If you want to force a specific namespace even if
* $text might begin with a namespace prefix, use makeTitle() or
* makeTitleSafe().
* @throws MWException
@@ -148,7 +148,7 @@ class Title {
$t->mDbkeyform = str_replace( ' ', '_', $filteredText );
$t->mDefaultNamespace = $defaultNamespace;
- static $cachedcount = 0 ;
+ static $cachedcount = 0;
if ( $t->secureAndSplit() ) {
if ( $defaultNamespace == NS_MAIN ) {
if ( $cachedcount >= self::CACHE_MAX ) {
@@ -178,7 +178,7 @@ class Title {
* Create a new Title from URL-encoded text. Ensures that
* the given title's length does not exceed the maximum.
*
- * @param $url String the title, as might be taken from a URL
+ * @param string $url the title, as might be taken from a URL
* @return Title the new object, or NULL on an error
*/
public static function newFromURL( $url ) {
@@ -200,20 +200,38 @@ class Title {
}
/**
+ * Returns a list of fields that are to be selected for initializing Title objects or LinkCache entries.
+ * Uses $wgContentHandlerUseDB to determine whether to include page_content_model.
+ *
+ * @return array
+ */
+ protected static function getSelectFields() {
+ global $wgContentHandlerUseDB;
+
+ $fields = array(
+ 'page_namespace', 'page_title', 'page_id',
+ 'page_len', 'page_is_redirect', 'page_latest',
+ );
+
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'page_content_model';
+ }
+
+ return $fields;
+ }
+
+ /**
* Create a new Title from an article ID
*
- * @param $id Int the page_id corresponding to the Title to create
- * @param $flags Int use Title::GAID_FOR_UPDATE to use master
+ * @param int $id the page_id corresponding to the Title to create
+ * @param int $flags use Title::GAID_FOR_UPDATE to use master
* @return Title the new object, or NULL on an error
*/
public static function newFromID( $id, $flags = 0 ) {
$db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
$row = $db->selectRow(
'page',
- array(
- 'page_namespace', 'page_title', 'page_id',
- 'page_len', 'page_is_redirect', 'page_latest',
- ),
+ self::getSelectFields(),
array( 'page_id' => $id ),
__METHOD__
);
@@ -228,7 +246,7 @@ class Title {
/**
* Make an array of titles from an array of IDs
*
- * @param $ids Array of Int Array of IDs
+ * @param array $ids of Int Array of IDs
* @return Array of Titles
*/
public static function newFromIDs( $ids ) {
@@ -239,10 +257,7 @@ class Title {
$res = $dbr->select(
'page',
- array(
- 'page_namespace', 'page_title', 'page_id',
- 'page_len', 'page_is_redirect', 'page_latest',
- ),
+ self::getSelectFields(),
array( 'page_id' => $ids ),
__METHOD__
);
@@ -282,11 +297,16 @@ class Title {
$this->mRedirect = (bool)$row->page_is_redirect;
if ( isset( $row->page_latest ) )
$this->mLatestID = (int)$row->page_latest;
+ if ( isset( $row->page_content_model ) )
+ $this->mContentModel = strval( $row->page_content_model );
+ else
+ $this->mContentModel = false; # initialized lazily in getContentModel()
} else { // page not found
$this->mArticleID = 0;
$this->mLength = 0;
$this->mRedirect = false;
$this->mLatestID = 0;
+ $this->mContentModel = false; # initialized lazily in getContentModel()
}
}
@@ -297,10 +317,10 @@ class Title {
* For convenience, spaces are converted to underscores so that
* eg user_text fields can be used directly.
*
- * @param $ns Int the namespace of the article
- * @param $title String the unprefixed database key form
- * @param $fragment String the link fragment (after the "#")
- * @param $interwiki String the interwiki prefix
+ * @param int $ns the namespace of the article
+ * @param string $title the unprefixed database key form
+ * @param string $fragment the link fragment (after the "#")
+ * @param string $interwiki the interwiki prefix
* @return Title the new object
*/
public static function &makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
@@ -312,6 +332,7 @@ class Title {
$t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
$t->mUrlform = wfUrlencode( $t->mDbkeyform );
$t->mTextform = str_replace( '_', ' ', $title );
+ $t->mContentModel = false; # initialized lazily in getContentModel()
return $t;
}
@@ -320,10 +341,10 @@ class Title {
* The parameters will be checked for validity, which is a bit slower
* than makeTitle() but safer for user-provided data.
*
- * @param $ns Int the namespace of the article
- * @param $title String database key form
- * @param $fragment String the link fragment (after the "#")
- * @param $interwiki String interwiki prefix
+ * @param int $ns the namespace of the article
+ * @param string $title database key form
+ * @param string $fragment the link fragment (after the "#")
+ * @param string $interwiki interwiki prefix
* @return Title the new object, or NULL on an error
*/
public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
@@ -360,11 +381,15 @@ class Title {
* This will only return the very next target, useful for
* the redirect table and other checks that don't need full recursion
*
- * @param $text String: Text with possible redirect
+ * @param string $text Text with possible redirect
* @return Title: The corresponding Title
+ * @deprecated since 1.21, use Content::getRedirectTarget instead.
*/
public static function newFromRedirect( $text ) {
- return self::newFromRedirectInternal( $text );
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
+ $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
+ return $content->getRedirectTarget();
}
/**
@@ -373,12 +398,15 @@ class Title {
* This will recurse down $wgMaxRedirects times or until a non-redirect target is hit
* in order to provide (hopefully) the Title of the final destination instead of another redirect
*
- * @param $text String Text with possible redirect
+ * @param string $text Text with possible redirect
* @return Title
+ * @deprecated since 1.21, use Content::getUltimateRedirectTarget instead.
*/
public static function newFromRedirectRecurse( $text ) {
- $titles = self::newFromRedirectArray( $text );
- return $titles ? array_pop( $titles ) : null;
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
+ $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
+ return $content->getUltimateRedirectTarget();
}
/**
@@ -387,79 +415,21 @@ class Title {
* The last element in the array is the final destination after all redirects
* have been resolved (up to $wgMaxRedirects times)
*
- * @param $text String Text with possible redirect
+ * @param string $text Text with possible redirect
* @return Array of Titles, with the destination last
+ * @deprecated since 1.21, use Content::getRedirectChain instead.
*/
public static function newFromRedirectArray( $text ) {
- global $wgMaxRedirects;
- $title = self::newFromRedirectInternal( $text );
- if ( is_null( $title ) ) {
- return null;
- }
- // recursive check to follow double redirects
- $recurse = $wgMaxRedirects;
- $titles = array( $title );
- while ( --$recurse > 0 ) {
- if ( $title->isRedirect() ) {
- $page = WikiPage::factory( $title );
- $newtitle = $page->getRedirectTarget();
- } else {
- break;
- }
- // Redirects to some special pages are not permitted
- if ( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) {
- // the new title passes the checks, so make that our current title so that further recursion can be checked
- $title = $newtitle;
- $titles[] = $newtitle;
- } else {
- break;
- }
- }
- return $titles;
- }
+ ContentHandler::deprecated( __METHOD__, '1.21' );
- /**
- * Really extract the redirect destination
- * Do not call this function directly, use one of the newFromRedirect* functions above
- *
- * @param $text String Text with possible redirect
- * @return Title
- */
- protected static function newFromRedirectInternal( $text ) {
- global $wgMaxRedirects;
- if ( $wgMaxRedirects < 1 ) {
- //redirects are disabled, so quit early
- return null;
- }
- $redir = MagicWord::get( 'redirect' );
- $text = trim( $text );
- if ( $redir->matchStartAndRemove( $text ) ) {
- // Extract the first link and see if it's usable
- // Ensure that it really does come directly after #REDIRECT
- // Some older redirects included a colon, so don't freak about that!
- $m = array();
- if ( preg_match( '!^\s*:?\s*\[{2}(.*?)(?:\|.*?)?\]{2}!', $text, $m ) ) {
- // Strip preceding colon used to "escape" categories, etc.
- // and URL-decode links
- if ( strpos( $m[1], '%' ) !== false ) {
- // Match behavior of inline link parsing here;
- $m[1] = rawurldecode( ltrim( $m[1], ':' ) );
- }
- $title = Title::newFromText( $m[1] );
- // If the title is a redirect to bad special pages or is invalid, return null
- if ( !$title instanceof Title || !$title->isValidRedirectTarget() ) {
- return null;
- }
- return $title;
- }
- }
- return null;
+ $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
+ return $content->getRedirectChain();
}
/**
* Get the prefixed DB key associated with an ID
*
- * @param $id Int the page_id of the article
+ * @param int $id the page_id of the article
* @return Title an object representing the article, or NULL if no such article was found
*/
public static function nameOf( $id ) {
@@ -520,8 +490,8 @@ class Title {
* Get a string representation of a title suitable for
* including in a search index
*
- * @param $ns Int a namespace index
- * @param $title String text-form main part
+ * @param int $ns a namespace index
+ * @param string $title text-form main part
* @return String a stripped-down title string ready for the search index
*/
public static function indexTitle( $ns, $title ) {
@@ -547,10 +517,10 @@ class Title {
/**
* Make a prefixed DB key from a DB key and a namespace index
*
- * @param $ns Int numerical representation of the namespace
- * @param $title String the DB key form the title
- * @param $fragment String The link fragment (after the "#")
- * @param $interwiki String The interwiki prefix
+ * @param int $ns numerical representation of the namespace
+ * @param string $title the DB key form the title
+ * @param string $fragment The link fragment (after the "#")
+ * @param string $interwiki The interwiki prefix
* @return String the prefixed form of the title
*/
public static function makeName( $ns, $title, $fragment = '', $interwiki = '' ) {
@@ -570,7 +540,7 @@ class Title {
/**
* Escape a text fragment, say from a link, for a URL
*
- * @param $fragment string containing a URL or link fragment (after the "#")
+ * @param string $fragment containing a URL or link fragment (after the "#")
* @return String: escaped string
*/
static function escapeFragmentForURL( $fragment ) {
@@ -605,10 +575,12 @@ class Title {
*/
public function isLocal() {
if ( $this->mInterwiki != '' ) {
- return Interwiki::fetch( $this->mInterwiki )->isLocal();
- } else {
- return true;
+ $iw = Interwiki::fetch( $this->mInterwiki );
+ if ( $iw ) {
+ return $iw->isLocal();
+ }
}
+ return true;
}
/**
@@ -702,6 +674,39 @@ class Title {
}
/**
+ * Get the page's content model id, see the CONTENT_MODEL_XXX constants.
+ *
+ * @throws MWException
+ * @return String: Content model id
+ */
+ public function getContentModel() {
+ if ( !$this->mContentModel ) {
+ $linkCache = LinkCache::singleton();
+ $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
+ }
+
+ if ( !$this->mContentModel ) {
+ $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
+ }
+
+ if( !$this->mContentModel ) {
+ throw new MWException( 'Failed to determine content model!' );
+ }
+
+ return $this->mContentModel;
+ }
+
+ /**
+ * Convenience method for checking a title's content model name
+ *
+ * @param string $id The content model ID (use the CONTENT_MODEL_XXX constants).
+ * @return Boolean true if $this->getContentModel() == $id
+ */
+ public function hasContentModel( $id ) {
+ return $this->getContentModel() == $id;
+ }
+
+ /**
* Get the namespace text
*
* @return String: Namespace text
@@ -790,7 +795,7 @@ class Title {
/**
* Returns true if this title resolves to the named special page
*
- * @param $name String The special page name
+ * @param string $name The special page name
* @return boolean
*/
public function isSpecial( $name ) {
@@ -828,7 +833,7 @@ class Title {
* Please make use of this instead of comparing to getNamespace()
* This function is much more resistant to changes we may make
* to namespaces than code that makes direct comparisons.
- * @param $ns int The namespace
+ * @param int $ns The namespace
* @return bool
* @since 1.19
*/
@@ -865,7 +870,7 @@ class Title {
* is either NS_USER or NS_USER_TALK since both of them have NS_USER
* as their subject namespace.
*
- * This is MUCH simpler than individually testing for equivilance
+ * This is MUCH simpler than individually testing for equivalence
* against both NS_USER and NS_USER_TALK, and is also forward compatible.
* @since 1.19
* @param $ns int
@@ -905,9 +910,9 @@ class Title {
/**
* Is this the mainpage?
- * @note Title::newFromText seams to be sufficiently optimized by the title
+ * @note Title::newFromText seems to be sufficiently optimized by the title
* cache that we don't need to over-optimize by doing direct comparisons and
- * acidentally creating new bugs where $title->equals( Title::newFromText() )
+ * accidentally creating new bugs where $title->equals( Title::newFromText() )
* ends up reporting something differently than $title->isMainPage();
*
* @since 1.18
@@ -934,6 +939,8 @@ class Title {
* @return Bool
*/
public function isConversionTable() {
+ //@todo: ConversionTable should become a separate content model.
+
return $this->getNamespace() == NS_MEDIAWIKI &&
strpos( $this->getText(), 'Conversiontable/' ) === 0;
}
@@ -944,22 +951,31 @@ class Title {
* @return Bool
*/
public function isWikitextPage() {
- $retval = !$this->isCssOrJsPage() && !$this->isCssJsSubpage();
- wfRunHooks( 'TitleIsWikitextPage', array( $this, &$retval ) );
- return $retval;
+ return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
}
/**
- * Could this page contain custom CSS or JavaScript, based
- * on the title?
+ * Could this page contain custom CSS or JavaScript for the global UI.
+ * This is generally true for pages in the MediaWiki namespace having CONTENT_MODEL_CSS
+ * or CONTENT_MODEL_JAVASCRIPT.
+ *
+ * This method does *not* return true for per-user JS/CSS. Use isCssJsSubpage() for that!
+ *
+ * Note that this method should not return true for pages that contain and show "inactive" CSS or JS.
*
* @return Bool
*/
public function isCssOrJsPage() {
- $retval = $this->mNamespace == NS_MEDIAWIKI
- && preg_match( '!\.(?:css|js)$!u', $this->mTextform ) > 0;
- wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$retval ) );
- return $retval;
+ $isCssOrJsPage = NS_MEDIAWIKI == $this->mNamespace
+ && ( $this->hasContentModel( CONTENT_MODEL_CSS )
+ || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) );
+
+ #NOTE: this hook is also called in ContentHandler::getDefaultModel. It's called here again to make sure
+ # hook functions can force this method to return true even outside the mediawiki namespace.
+
+ wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$isCssOrJsPage ) );
+
+ return $isCssOrJsPage;
}
/**
@@ -967,7 +983,9 @@ class Title {
* @return Bool
*/
public function isCssJsSubpage() {
- return ( NS_USER == $this->mNamespace and preg_match( "/\\/.*\\.(?:css|js)$/", $this->mTextform ) );
+ return ( NS_USER == $this->mNamespace && $this->isSubpage()
+ && ( $this->hasContentModel( CONTENT_MODEL_CSS )
+ || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ) );
}
/**
@@ -990,7 +1008,8 @@ class Title {
* @return Bool
*/
public function isCssSubpage() {
- return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.css$/", $this->mTextform ) );
+ return ( NS_USER == $this->mNamespace && $this->isSubpage()
+ && $this->hasContentModel( CONTENT_MODEL_CSS ) );
}
/**
@@ -999,7 +1018,8 @@ class Title {
* @return Bool
*/
public function isJsSubpage() {
- return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.js$/", $this->mTextform ) );
+ return ( NS_USER == $this->mNamespace && $this->isSubpage()
+ && $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) );
}
/**
@@ -1083,7 +1103,7 @@ class Title {
* Deprecated for public use, use Title::makeTitle() with fragment parameter.
* Still in active use privately.
*
- * @param $fragment String text
+ * @param string $fragment text
*/
public function setFragment( $fragment ) {
$this->mFragment = str_replace( '_', ' ', substr( $fragment, 1 ) );
@@ -1093,7 +1113,7 @@ class Title {
* Prefix some arbitrary text with the namespace or interwiki prefix
* of this object
*
- * @param $name String the text
+ * @param string $name the text
* @return String the prefixed text
* @private
*/
@@ -1161,7 +1181,49 @@ class Title {
}
/**
- * Get the base page name, i.e. the leftmost part before any slashes
+ * Get the root page name text without a namespace, i.e. the leftmost part before any slashes
+ *
+ * @par Example:
+ * @code
+ * Title::newFromText('User:Foo/Bar/Baz')->getRootText();
+ * # returns: 'Foo'
+ * @endcode
+ *
+ * @return String Root name
+ * @since 1.20
+ */
+ public function getRootText() {
+ if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
+ return $this->getText();
+ }
+
+ return strtok( $this->getText(), '/' );
+ }
+
+ /**
+ * Get the root page name title, i.e. the leftmost part before any slashes
+ *
+ * @par Example:
+ * @code
+ * Title::newFromText('User:Foo/Bar/Baz')->getRootTitle();
+ * # returns: Title{User:Foo}
+ * @endcode
+ *
+ * @return Title Root title
+ * @since 1.20
+ */
+ public function getRootTitle() {
+ return Title::makeTitle( $this->getNamespace(), $this->getRootText() );
+ }
+
+ /**
+ * Get the base page name without a namespace, i.e. the part before the subpage name
+ *
+ * @par Example:
+ * @code
+ * Title::newFromText('User:Foo/Bar/Baz')->getBaseText();
+ * # returns: 'Foo/Bar'
+ * @endcode
*
* @return String Base name
*/
@@ -1179,8 +1241,30 @@ class Title {
}
/**
+ * Get the base page name title, i.e. the part before the subpage name
+ *
+ * @par Example:
+ * @code
+ * Title::newFromText('User:Foo/Bar/Baz')->getBaseTitle();
+ * # returns: Title{User:Foo/Bar}
+ * @endcode
+ *
+ * @return Title Base title
+ * @since 1.20
+ */
+ public function getBaseTitle() {
+ return Title::makeTitle( $this->getNamespace(), $this->getBaseText() );
+ }
+
+ /**
* Get the lowest-level subpage name, i.e. the rightmost part after any slashes
*
+ * @par Example:
+ * @code
+ * Title::newFromText('User:Foo/Bar/Baz')->getSubpageText();
+ * # returns: "Baz"
+ * @endcode
+ *
* @return String Subpage name
*/
public function getSubpageText() {
@@ -1192,10 +1276,28 @@ class Title {
}
/**
+ * Get the title for a subpage of the current page
+ *
+ * @par Example:
+ * @code
+ * Title::newFromText('User:Foo/Bar/Baz')->getSubpage("Asdf");
+ * # returns: Title{User:Foo/Bar/Baz/Asdf}
+ * @endcode
+ *
+ * @param string $text The subpage name to add to the title
+ * @return Title Subpage title
+ * @since 1.20
+ */
+ public function getSubpage( $text ) {
+ return Title::makeTitleSafe( $this->getNamespace(), $this->getText() . '/' . $text );
+ }
+
+ /**
* Get the HTML-escaped displayable text form.
* Used for the title field in <a> tags.
*
* @return String the text, including any prefixes
+ * @deprecated since 1.19
*/
public function getEscapedText() {
wfDeprecated( __METHOD__, '1.19' );
@@ -1230,7 +1332,7 @@ class Title {
* second argument named variant. This was deprecated in favor
* of passing an array of option with a "variant" key
* Once $query2 is removed for good, this helper can be dropped
- * andthe wfArrayToCGI moved to getLocalURL();
+ * and the wfArrayToCgi moved to getLocalURL();
*
* @since 1.19 (r105919)
* @param $query
@@ -1242,15 +1344,15 @@ class Title {
wfDeprecated( "Title::get{Canonical,Full,Link,Local} method called with a second parameter is deprecated. Add your parameter to an array passed as the first parameter.", "1.19" );
}
if ( is_array( $query ) ) {
- $query = wfArrayToCGI( $query );
+ $query = wfArrayToCgi( $query );
}
if ( $query2 ) {
if ( is_string( $query2 ) ) {
// $query2 is a string, we will consider this to be
// a deprecated $variant argument and add it to the query
- $query2 = wfArrayToCGI( array( 'variant' => $query2 ) );
+ $query2 = wfArrayToCgi( array( 'variant' => $query2 ) );
} else {
- $query2 = wfArrayToCGI( $query2 );
+ $query2 = wfArrayToCgi( $query2 );
}
// If we have $query content add a & to it first
if ( $query ) {
@@ -1270,6 +1372,8 @@ class Title {
*
* @see self::getLocalURL
* @see wfExpandUrl
+ * @param $query
+ * @param $query2 bool
* @param $proto Protocol type to use in URL
* @return String the URL
*/
@@ -1296,8 +1400,8 @@ class Title {
* with action=render, $wgServer is prepended.
*
- * @param $query string|array an optional query string,
- * not used for interwiki links. Can be specified as an associative array as well,
+ * @param string|array $query an optional query string,
+ * not used for interwiki links. Can be specified as an associative array as well,
* e.g., array( 'action' => 'edit' ) (keys and values will be URL-escaped).
* Some query patterns will trigger various shorturl path replacements.
* @param $query2 Mixed: An optional secondary query array. This one MUST
@@ -1396,13 +1500,16 @@ class Title {
*
* See getLocalURL for the arguments.
*
+ * @param $query
+ * @param $query2 bool
+ * @param $proto Protocol to use; setting this will cause a full URL to be used
* @see self::getLocalURL
* @return String the URL
*/
- public function getLinkURL( $query = '', $query2 = false ) {
+ public function getLinkURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
wfProfileIn( __METHOD__ );
- if ( $this->isExternal() ) {
- $ret = $this->getFullURL( $query, $query2 );
+ if ( $this->isExternal() || $proto !== PROTO_RELATIVE ) {
+ $ret = $this->getFullURL( $query, $query2, $proto );
} elseif ( $this->getPrefixedText() === '' && $this->getFragment() !== '' ) {
$ret = $this->getFragmentForURL();
} else {
@@ -1422,6 +1529,7 @@ class Title {
* @param $query string
* @param $query2 bool|string
* @return String the URL
+ * @deprecated since 1.19
*/
public function escapeLocalURL( $query = '', $query2 = false ) {
wfDeprecated( __METHOD__, '1.19' );
@@ -1436,6 +1544,7 @@ class Title {
*
* @see self::getLocalURL
* @return String the URL
+ * @deprecated since 1.19
*/
public function escapeFullURL( $query = '', $query2 = false ) {
wfDeprecated( __METHOD__, '1.19' );
@@ -1493,6 +1602,7 @@ class Title {
* @see self::getLocalURL
* @since 1.18
* @return string
+ * @deprecated since 1.19
*/
public function escapeCanonicalURL( $query = '', $query2 = false ) {
wfDeprecated( __METHOD__, '1.19' );
@@ -1555,7 +1665,7 @@ class Title {
*
* May provide false positives, but should never provide a false negative.
*
- * @param $action String action that permission needs to be checked for
+ * @param string $action action that permission needs to be checked for
* @param $user User to check (since 1.19); $wgUser will be used if not
* provided.
* @return Bool
@@ -1567,10 +1677,10 @@ class Title {
/**
* Can $user perform $action on this page?
*
- * @param $action String action that permission needs to be checked for
+ * @param string $action action that permission needs to be checked for
* @param $user User to check (since 1.19); $wgUser will be used if not
* provided.
- * @param $doExpensiveQueries Bool Set this to false to avoid doing
+ * @param bool $doExpensiveQueries Set this to false to avoid doing
* unnecessary queries.
* @return Bool
*/
@@ -1587,11 +1697,11 @@ class Title {
*
* @todo FIXME: This *does not* check throttles (User::pingLimiter()).
*
- * @param $action String action that permission needs to be checked for
+ * @param string $action action that permission needs to be checked for
* @param $user User to check
- * @param $doExpensiveQueries Bool Set this to false to avoid doing unnecessary
+ * @param bool $doExpensiveQueries Set this to false to avoid doing unnecessary
* queries by skipping checks for cascading protections and user blocks.
- * @param $ignoreErrors Array of Strings Set this to a list of message keys
+ * @param array $ignoreErrors of Strings Set this to a list of message keys
* whose corresponding errors may be ignored.
* @return Array of arguments to wfMessage to explain permissions problems.
*/
@@ -1613,9 +1723,9 @@ class Title {
/**
* Permissions checks that fail most often, and which are easiest to test.
*
- * @param $action String the action to check
+ * @param string $action the action to check
* @param $user User user to check
- * @param $errors Array list of current errors
+ * @param array $errors list of current errors
* @param $doExpensiveQueries Boolean whether or not to perform expensive queries
* @param $short Boolean short circuit on first error
*
@@ -1623,8 +1733,10 @@ class Title {
*/
private function checkQuickPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
if ( $action == 'create' ) {
- if ( ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
- ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) ) ) {
+ if (
+ ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
+ ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) )
+ ) {
$errors[] = $user->isAnon() ? array( 'nocreatetext' ) : array( 'nocreate-loggedin' );
}
} elseif ( $action == 'move' ) {
@@ -1641,15 +1753,8 @@ class Title {
if ( !$user->isAllowed( 'move' ) ) {
// User can't move anything
- global $wgGroupPermissions;
- $userCanMove = false;
- if ( isset( $wgGroupPermissions['user']['move'] ) ) {
- $userCanMove = $wgGroupPermissions['user']['move'];
- }
- $autoconfirmedCanMove = false;
- if ( isset( $wgGroupPermissions['autoconfirmed']['move'] ) ) {
- $autoconfirmedCanMove = $wgGroupPermissions['autoconfirmed']['move'];
- }
+ $userCanMove = User::groupHasPermission( 'user', 'move' );
+ $autoconfirmedCanMove = User::groupHasPermission( 'autoconfirmed', 'move' );
if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
// custom message if logged-in users without any special rights can move
$errors[] = array( 'movenologintext' );
@@ -1676,7 +1781,7 @@ class Title {
/**
* Add the resulting error code to the errors array
*
- * @param $errors Array list of current errors
+ * @param array $errors list of current errors
* @param $result Mixed result of errors
*
* @return Array list of errors
@@ -1701,9 +1806,9 @@ class Title {
/**
* Check various permission hooks
*
- * @param $action String the action to check
+ * @param string $action the action to check
* @param $user User user to check
- * @param $errors Array list of current errors
+ * @param array $errors list of current errors
* @param $doExpensiveQueries Boolean whether or not to perform expensive queries
* @param $short Boolean short circuit on first error
*
@@ -1720,8 +1825,11 @@ class Title {
$errors = $this->resultToError( $errors, $result );
}
// Check getUserPermissionsErrorsExpensive hook
- if ( $doExpensiveQueries && !( $short && count( $errors ) > 0 ) &&
- !wfRunHooks( 'getUserPermissionsErrorsExpensive', array( &$this, &$user, $action, &$result ) ) ) {
+ if (
+ $doExpensiveQueries
+ && !( $short && count( $errors ) > 0 )
+ && !wfRunHooks( 'getUserPermissionsErrorsExpensive', array( &$this, &$user, $action, &$result ) )
+ ) {
$errors = $this->resultToError( $errors, $result );
}
@@ -1731,19 +1839,18 @@ class Title {
/**
* Check permissions on special pages & namespaces
*
- * @param $action String the action to check
+ * @param string $action the action to check
* @param $user User user to check
- * @param $errors Array list of current errors
+ * @param array $errors list of current errors
* @param $doExpensiveQueries Boolean whether or not to perform expensive queries
* @param $short Boolean short circuit on first error
*
* @return Array list of errors
*/
private function checkSpecialsAndNSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
- # Only 'createaccount' and 'execute' can be performed on
- # special pages, which don't actually exist in the DB.
- $specialOKActions = array( 'createaccount', 'execute', 'read' );
- if ( NS_SPECIAL == $this->mNamespace && !in_array( $action, $specialOKActions ) ) {
+ # Only 'createaccount' can be performed on special pages,
+ # which don't actually exist in the DB.
+ if ( NS_SPECIAL == $this->mNamespace && $action !== 'createaccount' ) {
$errors[] = array( 'ns-specialprotected' );
}
@@ -1752,7 +1859,7 @@ class Title {
$ns = $this->mNamespace == NS_MAIN ?
wfMessage( 'nstab-main' )->text() : $this->getNsText();
$errors[] = $this->mNamespace == NS_MEDIAWIKI ?
- array( 'protectedinterface' ) : array( 'namespaceprotected', $ns );
+ array( 'protectedinterface' ) : array( 'namespaceprotected', $ns );
}
return $errors;
@@ -1761,9 +1868,9 @@ class Title {
/**
* Check CSS/JS sub-page permissions
*
- * @param $action String the action to check
+ * @param string $action the action to check
* @param $user User user to check
- * @param $errors Array list of current errors
+ * @param array $errors list of current errors
* @param $doExpensiveQueries Boolean whether or not to perform expensive queries
* @param $short Boolean short circuit on first error
*
@@ -1790,9 +1897,9 @@ class Title {
* page. The user must possess all required rights for this
* action.
*
- * @param $action String the action to check
+ * @param string $action the action to check
* @param $user User user to check
- * @param $errors Array list of current errors
+ * @param array $errors list of current errors
* @param $doExpensiveQueries Boolean whether or not to perform expensive queries
* @param $short Boolean short circuit on first error
*
@@ -1821,9 +1928,9 @@ class Title {
/**
* Check restrictions on cascading pages.
*
- * @param $action String the action to check
+ * @param string $action the action to check
* @param $user User to check
- * @param $errors Array list of current errors
+ * @param array $errors list of current errors
* @param $doExpensiveQueries Boolean whether or not to perform expensive queries
* @param $short Boolean short circuit on first error
*
@@ -1860,9 +1967,9 @@ class Title {
/**
* Check action permissions not already checked in checkQuickPermissions
*
- * @param $action String the action to check
+ * @param string $action the action to check
* @param $user User to check
- * @param $errors Array list of current errors
+ * @param array $errors list of current errors
* @param $doExpensiveQueries Boolean whether or not to perform expensive queries
* @param $short Boolean short circuit on first error
*
@@ -1914,11 +2021,11 @@ class Title {
}
/**
- * Check that the user isn't blocked from editting.
+ * Check that the user isn't blocked from editing.
*
- * @param $action String the action to check
+ * @param string $action the action to check
* @param $user User to check
- * @param $errors Array list of current errors
+ * @param array $errors list of current errors
* @param $doExpensiveQueries Boolean whether or not to perform expensive queries
* @param $short Boolean short circuit on first error
*
@@ -1981,22 +2088,22 @@ class Title {
/**
* Check that the user is allowed to read this page.
*
- * @param $action String the action to check
+ * @param string $action the action to check
* @param $user User to check
- * @param $errors Array list of current errors
+ * @param array $errors list of current errors
* @param $doExpensiveQueries Boolean whether or not to perform expensive queries
* @param $short Boolean short circuit on first error
*
* @return Array list of errors
*/
private function checkReadPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
- global $wgWhitelistRead, $wgGroupPermissions, $wgRevokePermissions;
+ global $wgWhitelistRead, $wgWhitelistReadRegexp, $wgRevokePermissions;
static $useShortcut = null;
# Initialize the $useShortcut boolean, to determine if we can skip quite a bit of code below
if ( is_null( $useShortcut ) ) {
$useShortcut = true;
- if ( empty( $wgGroupPermissions['*']['read'] ) ) {
+ if ( !User::groupHasPermission( '*', 'read' ) ) {
# Not a public wiki, so no shortcut
$useShortcut = false;
} elseif ( !empty( $wgRevokePermissions ) ) {
@@ -2034,7 +2141,7 @@ class Title {
# Time to check the whitelist
# Only do these checks is there's something to check against
$name = $this->getPrefixedText();
- $dbName = $this->getPrefixedDBKey();
+ $dbName = $this->getPrefixedDBkey();
// Check for explicit whitelisting with and without underscores
if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
@@ -2049,7 +2156,7 @@ class Title {
# If it's a special page, ditch the subpage bit and check again
$name = $this->getDBkey();
list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );
- if ( $name !== false ) {
+ if ( $name ) {
$pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
if ( in_array( $pure, $wgWhitelistRead, true ) ) {
$whitelisted = true;
@@ -2058,6 +2165,17 @@ class Title {
}
}
+ if( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) {
+ $name = $this->getPrefixedText();
+ // Check for regex whitelisting
+ foreach ( $wgWhitelistReadRegexp as $listItem ) {
+ if ( preg_match( $listItem, $name ) ) {
+ $whitelisted = true;
+ break;
+ }
+ }
+ }
+
if ( !$whitelisted ) {
# If the title is not whitelisted, give extensions a chance to do so...
wfRunHooks( 'TitleReadWhitelist', array( $this, $user, &$whitelisted ) );
@@ -2073,7 +2191,7 @@ class Title {
* Get a description array when the user doesn't have the right to perform
* $action (i.e. when User::isAllowed() returns false)
*
- * @param $action String the action to check
+ * @param string $action the action to check
* @param $short Boolean short circuit on first error
* @return Array list of errors
*/
@@ -2103,10 +2221,10 @@ class Title {
* which checks ONLY that previously checked by userCan (i.e. it leaves out
* checks on wfReadOnly() and blocks)
*
- * @param $action String action that permission needs to be checked for
+ * @param string $action action that permission needs to be checked for
* @param $user User to check
- * @param $doExpensiveQueries Bool Set this to false to avoid doing unnecessary queries.
- * @param $short Bool Set this to true to stop after the first permission error.
+ * @param bool $doExpensiveQueries Set this to false to avoid doing unnecessary queries.
+ * @param bool $short Set this to true to stop after the first permission error.
* @return Array of arrays of the arguments to wfMessage to explain permissions problems.
*/
protected function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries = true, $short = false ) {
@@ -2166,8 +2284,10 @@ class Title {
public function userCanEditJsSubpage() {
global $wgUser;
wfDeprecated( __METHOD__, '1.19' );
- return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'edituserjs' ) )
- || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
+ return (
+ ( $wgUser->isAllowedAll( 'editusercssjs', 'edituserjs' ) )
+ || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform )
+ );
}
/**
@@ -2235,9 +2355,12 @@ class Title {
if ( !isset( $this->mTitleProtection ) ) {
$dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( 'protected_titles', '*',
+ $res = $dbr->select(
+ 'protected_titles',
+ array( 'pt_user', 'pt_reason', 'pt_expiry', 'pt_create_perm' ),
array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
- __METHOD__ );
+ __METHOD__
+ );
// fetchRow returns false if there are no rows.
$this->mTitleProtection = $dbr->fetchRow( $res );
@@ -2250,8 +2373,8 @@ class Title {
*
* @deprecated in 1.19; will be removed in 1.20. Use WikiPage::doUpdateRestrictions() instead.
* @param $create_perm String Permission required for creation
- * @param $reason String Reason for protection
- * @param $expiry String Expiry timestamp
+ * @param string $reason Reason for protection
+ * @param string $expiry Expiry timestamp
* @return boolean true
*/
public function updateTitleProtection( $create_perm, $reason, $expiry ) {
@@ -2263,7 +2386,8 @@ class Title {
$expiry = array( 'create' => $expiry );
$page = WikiPage::factory( $this );
- $status = $page->doUpdateRestrictions( $limit, $expiry, false, $reason, $wgUser );
+ $cascade = false;
+ $status = $page->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $wgUser );
return $status->isOK();
}
@@ -2285,7 +2409,7 @@ class Title {
/**
* Is this page "semi-protected" - the *only* protection is autoconfirm?
*
- * @param $action String Action to check (default: edit)
+ * @param string $action Action to check (default: edit)
* @return Bool
*/
public function isSemiProtected( $action = 'edit' ) {
@@ -2311,7 +2435,7 @@ class Title {
/**
* Does the title correspond to a protected article?
*
- * @param $action String the action the page is protected from,
+ * @param string $action the action the page is protected from,
* by default checks all actions.
* @return Bool
*/
@@ -2373,7 +2497,7 @@ class Title {
/**
* Cascading protection: Get the source of any cascading restrictions on this page.
*
- * @param $getPages Bool Whether or not to retrieve the actual pages
+ * @param bool $getPages Whether or not to retrieve the actual pages
* that the restrictions have come from.
* @return Mixed Array of Title objects of the pages from which cascading restrictions
* have come, false for none, or true if such restrictions exist, but $getPages
@@ -2413,7 +2537,7 @@ class Title {
if ( $getPages ) {
$cols = array( 'pr_page', 'page_namespace', 'page_title',
- 'pr_expiry', 'pr_type', 'pr_level' );
+ 'pr_expiry', 'pr_type', 'pr_level' );
$where_clauses[] = 'page_id=pr_page';
$tables[] = 'page';
} else {
@@ -2441,8 +2565,10 @@ class Title {
$pagerestrictions[$row->pr_type] = array();
}
- if ( isset( $pagerestrictions[$row->pr_type] ) &&
- !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] ) ) {
+ if (
+ isset( $pagerestrictions[$row->pr_type] )
+ && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )
+ ) {
$pagerestrictions[$row->pr_type][] = $row->pr_level;
}
} else {
@@ -2471,7 +2597,7 @@ class Title {
/**
* Accessor/initialisation for mRestrictions
*
- * @param $action String action that permission needs to be checked for
+ * @param string $action action that permission needs to be checked for
* @return Array of Strings the array of groups allowed to edit this article
*/
public function getRestrictions( $action ) {
@@ -2514,7 +2640,7 @@ class Title {
* Loads a string into mRestrictions array
*
* @param $res Resource restrictions as an SQL result.
- * @param $oldFashionedRestrictions String comma-separated list of page
+ * @param string $oldFashionedRestrictions comma-separated list of page
* restrictions from page table (pre 1.10)
*/
private function loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions = null ) {
@@ -2532,8 +2658,8 @@ class Title {
* and page_restrictions table for this existing page.
* Public for usage by LiquidThreads.
*
- * @param $rows array of db result objects
- * @param $oldFashionedRestrictions string comma-separated list of page
+ * @param array $rows of db result objects
+ * @param string $oldFashionedRestrictions comma-separated list of page
* restrictions from page table (pre 1.10)
*/
public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
@@ -2615,7 +2741,7 @@ class Title {
/**
* Load restrictions from the page_restrictions table
*
- * @param $oldFashionedRestrictions String comma-separated list of page
+ * @param string $oldFashionedRestrictions comma-separated list of page
* restrictions from page table (pre 1.10)
*/
public function loadRestrictions( $oldFashionedRestrictions = null ) {
@@ -2626,7 +2752,7 @@ class Title {
$res = $dbr->select(
'page_restrictions',
- '*',
+ array( 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ),
array( 'pr_page' => $this->getArticleID() ),
__METHOD__
);
@@ -2668,6 +2794,10 @@ class Title {
* Purge expired restrictions from the page_restrictions table
*/
static function purgeExpiredRestrictions() {
+ if ( wfReadOnly() ) {
+ return;
+ }
+
$dbw = wfGetDB( DB_MASTER );
$dbw->delete(
'page_restrictions',
@@ -2711,7 +2841,7 @@ class Title {
/**
* Get all subpages of this page.
*
- * @param $limit Int maximum number of subpages to fetch; -1 for no limit
+ * @param int $limit maximum number of subpages to fetch; -1 for no limit
* @return mixed TitleArray, or empty array if this page's namespace
* doesn't allow subpages
*/
@@ -2789,7 +2919,7 @@ class Title {
* Get the article ID for this Title from the link cache,
* adding it if necessary
*
- * @param $flags Int a bit field; may be Title::GAID_FOR_UPDATE to select
+ * @param int $flags a bit field; may be Title::GAID_FOR_UPDATE to select
* for update
* @return Int the ID
*/
@@ -2815,7 +2945,7 @@ class Title {
* Is this an article that is a redirect page?
* Uses link cache, adding it if necessary
*
- * @param $flags Int a bit field; may be Title::GAID_FOR_UPDATE to select for update
+ * @param int $flags a bit field; may be Title::GAID_FOR_UPDATE to select for update
* @return Bool
*/
public function isRedirect( $flags = 0 ) {
@@ -2826,8 +2956,18 @@ class Title {
if ( !$this->getArticleID( $flags ) ) {
return $this->mRedirect = false;
}
+
$linkCache = LinkCache::singleton();
- $this->mRedirect = (bool)$linkCache->getGoodLinkFieldObj( $this, 'redirect' );
+ $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
+ if ( $cached === null ) {
+ // TODO: check the assumption that the cache actually knows about this title
+ // and handle this, such as get the title from the database.
+ // See https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
+ wfDebug( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
+ wfDebug( wfBacktrace() );
+ }
+
+ $this->mRedirect = (bool)$cached;
return $this->mRedirect;
}
@@ -2836,7 +2976,7 @@ class Title {
* What is the length of this page?
* Uses link cache, adding it if necessary
*
- * @param $flags Int a bit field; may be Title::GAID_FOR_UPDATE to select for update
+ * @param int $flags a bit field; may be Title::GAID_FOR_UPDATE to select for update
* @return Int
*/
public function getLength( $flags = 0 ) {
@@ -2848,7 +2988,15 @@ class Title {
return $this->mLength = 0;
}
$linkCache = LinkCache::singleton();
- $this->mLength = intval( $linkCache->getGoodLinkFieldObj( $this, 'length' ) );
+ $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
+ if ( $cached === null ) { # check the assumption that the cache actually knows about this title
+ # XXX: this does apparently happen, see https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
+ # as a stop gap, perhaps log this, but don't throw an exception?
+ wfDebug( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
+ wfDebug( wfBacktrace() );
+ }
+
+ $this->mLength = intval( $cached );
return $this->mLength;
}
@@ -2856,7 +3004,8 @@ class Title {
/**
* What is the page_latest field for this page?
*
- * @param $flags Int a bit field; may be Title::GAID_FOR_UPDATE to select for update
+ * @param int $flags a bit field; may be Title::GAID_FOR_UPDATE to select for update
+ * @throws MWException
* @return Int or 0 if the page doesn't exist
*/
public function getLatestRevID( $flags = 0 ) {
@@ -2868,7 +3017,15 @@ class Title {
return $this->mLatestID = 0;
}
$linkCache = LinkCache::singleton();
- $this->mLatestID = intval( $linkCache->getGoodLinkFieldObj( $this, 'revision' ) );
+ $linkCache->addLinkObj( $this );
+ $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
+ if ( $cached === null ) { # check the assumption that the cache actually knows about this title
+ # XXX: this does apparently happen, see https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
+ # as a stop gap, perhaps log this, but don't throw an exception?
+ throw new MWException( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
+ }
+
+ $this->mLatestID = intval( $cached );
return $this->mLatestID;
}
@@ -2881,7 +3038,7 @@ class Title {
* loading of the new page_id. It's also called from
* WikiPage::doDeleteArticleReal()
*
- * @param $newid Int the new Article ID
+ * @param int $newid the new Article ID
*/
public function resetArticleID( $newid ) {
$linkCache = LinkCache::singleton();
@@ -2897,14 +3054,15 @@ class Title {
$this->mRedirect = null;
$this->mLength = -1;
$this->mLatestID = false;
+ $this->mContentModel = false;
$this->mEstimateRevisions = null;
}
/**
* Capitalize a text string for a title if it belongs to a namespace that capitalizes
*
- * @param $text String containing title to capitalize
- * @param $ns int namespace index, defaults to NS_MAIN
+ * @param string $text containing title to capitalize
+ * @param int $ns namespace index, defaults to NS_MAIN
* @return String containing capitalized title
*/
public static function capitalize( $text, $ns = NS_MAIN ) {
@@ -3049,15 +3207,18 @@ class Title {
# Pages with "/./" or "/../" appearing in the URLs will often be un-
# reachable due to the way web browsers deal with 'relative' URLs.
# Also, they conflict with subpage syntax. Forbid them explicitly.
- if ( strpos( $dbkey, '.' ) !== false &&
- ( $dbkey === '.' || $dbkey === '..' ||
- strpos( $dbkey, './' ) === 0 ||
- strpos( $dbkey, '../' ) === 0 ||
- strpos( $dbkey, '/./' ) !== false ||
- strpos( $dbkey, '/../' ) !== false ||
- substr( $dbkey, -2 ) == '/.' ||
- substr( $dbkey, -3 ) == '/..' ) )
- {
+ if (
+ strpos( $dbkey, '.' ) !== false &&
+ (
+ $dbkey === '.' || $dbkey === '..' ||
+ strpos( $dbkey, './' ) === 0 ||
+ strpos( $dbkey, '../' ) === 0 ||
+ strpos( $dbkey, '/./' ) !== false ||
+ strpos( $dbkey, '/../' ) !== false ||
+ substr( $dbkey, -2 ) == '/.' ||
+ substr( $dbkey, -3 ) == '/..'
+ )
+ ) {
return false;
}
@@ -3070,9 +3231,10 @@ class Title {
# underlying database field. We make an exception for special pages, which
# don't need to be stored in the database, and may edge over 255 bytes due
# to subpage syntax for long titles, e.g. [[Special:Block/Long name]]
- if ( ( $this->mNamespace != NS_SPECIAL && strlen( $dbkey ) > 255 ) ||
- strlen( $dbkey ) > 512 )
- {
+ if (
+ ( $this->mNamespace != NS_SPECIAL && strlen( $dbkey ) > 255 )
+ || strlen( $dbkey ) > 512
+ ) {
return false;
}
@@ -3121,9 +3283,9 @@ class Title {
* WARNING: do not use this function on arbitrary user-supplied titles!
* On heavily-used templates it will max out the memory.
*
- * @param $options Array: may be FOR UPDATE
- * @param $table String: table name
- * @param $prefix String: fields prefix
+ * @param array $options may be FOR UPDATE
+ * @param string $table table name
+ * @param string $prefix fields prefix
* @return Array of Title objects linking here
*/
public function getLinksTo( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
@@ -3135,7 +3297,7 @@ class Title {
$res = $db->select(
array( 'page', $table ),
- array( 'page_namespace', 'page_title', 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
+ self::getSelectFields(),
array(
"{$prefix}_from=page_id",
"{$prefix}_namespace" => $this->getNamespace(),
@@ -3165,7 +3327,7 @@ class Title {
* WARNING: do not use this function on arbitrary user-supplied titles!
* On heavily-used templates it will max out the memory.
*
- * @param $options Array: may be FOR UPDATE
+ * @param array $options may be FOR UPDATE
* @return Array of Title the Title objects linking here
*/
public function getTemplateLinksTo( $options = array() ) {
@@ -3179,12 +3341,14 @@ class Title {
* WARNING: do not use this function on arbitrary user-supplied titles!
* On heavily-used templates it will max out the memory.
*
- * @param $options Array: may be FOR UPDATE
- * @param $table String: table name
- * @param $prefix String: fields prefix
+ * @param array $options may be FOR UPDATE
+ * @param string $table table name
+ * @param string $prefix fields prefix
* @return Array of Title objects linking here
*/
public function getLinksFrom( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
+ global $wgContentHandlerUseDB;
+
$id = $this->getArticleID();
# If the page doesn't exist; there can't be any link from this page
@@ -3201,9 +3365,12 @@ class Title {
$namespaceFiled = "{$prefix}_namespace";
$titleField = "{$prefix}_title";
+ $fields = array( $namespaceFiled, $titleField, 'page_id', 'page_len', 'page_is_redirect', 'page_latest' );
+ if ( $wgContentHandlerUseDB ) $fields[] = 'page_content_model';
+
$res = $db->select(
array( $table, 'page' ),
- array( $namespaceFiled, $titleField, 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
+ $fields,
array( "{$prefix}_from" => $id ),
__METHOD__,
$options,
@@ -3235,7 +3402,7 @@ class Title {
* WARNING: do not use this function on arbitrary user-supplied titles!
* On heavily-used templates it will max out the memory.
*
- * @param $options Array: may be FOR UPDATE
+ * @param array $options may be FOR UPDATE
* @return Array of Title the Title objects used here
*/
public function getTemplateLinksFrom( $options = array() ) {
@@ -3278,7 +3445,6 @@ class Title {
return $retVal;
}
-
/**
* Get a list of URLs to purge from the Squid cache when this
* page changes
@@ -3329,13 +3495,13 @@ class Title {
* Returns true if ok, or a getUserPermissionsErrors()-like array otherwise
*
* @param $nt Title the new title
- * @param $auth Bool indicates whether $wgUser's permissions
+ * @param bool $auth indicates whether $wgUser's permissions
* should be checked
- * @param $reason String is the log summary of the move, used for spam checking
+ * @param string $reason is the log summary of the move, used for spam checking
* @return Mixed True on success, getUserPermissionsErrors()-like array on failure
*/
public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
- global $wgUser;
+ global $wgUser, $wgContentHandlerUseDB;
$errors = array();
if ( !$nt ) {
@@ -3362,12 +3528,25 @@ class Title {
if ( strlen( $nt->getDBkey() ) < 1 ) {
$errors[] = array( 'articleexists' );
}
- if ( ( $this->getDBkey() == '' ) ||
- ( !$oldid ) ||
- ( $nt->getDBkey() == '' ) ) {
+ if (
+ ( $this->getDBkey() == '' ) ||
+ ( !$oldid ) ||
+ ( $nt->getDBkey() == '' )
+ ) {
$errors[] = array( 'badarticleerror' );
}
+ // Content model checks
+ if ( !$wgContentHandlerUseDB &&
+ $this->getContentModel() !== $nt->getContentModel() ) {
+ // can't move a page if that would change the page's content model
+ $errors[] = array(
+ 'bad-target-model',
+ ContentHandler::getLocalizedName( $this->getContentModel() ),
+ ContentHandler::getLocalizedName( $nt->getContentModel() )
+ );
+ }
+
// Image-specific checks
if ( $this->getNamespace() == NS_FILE ) {
$errors = array_merge( $errors, $this->validateFileMoveOperation( $nt ) );
@@ -3460,10 +3639,10 @@ class Title {
* Move a title to a new location
*
* @param $nt Title the new title
- * @param $auth Bool indicates whether $wgUser's permissions
+ * @param bool $auth indicates whether $wgUser's permissions
* should be checked
- * @param $reason String the reason for the move
- * @param $createRedirect Bool Whether to create a redirect from the old title to the new title.
+ * @param string $reason the reason for the move
+ * @param bool $createRedirect Whether to create a redirect from the old title to the new title.
* Ignored if the user doesn't have the suppressredirect right.
* @return Mixed true on success, getUserPermissionsErrors()-like array on failure
*/
@@ -3558,8 +3737,8 @@ class Title {
}
# Update watchlists
- $oldnamespace = $this->getNamespace() & ~1;
- $newnamespace = $nt->getNamespace() & ~1;
+ $oldnamespace = MWNamespace::getSubject( $this->getNamespace() );
+ $newnamespace = MWNamespace::getSubject( $nt->getNamespace() );
$oldtitle = $this->getDBkey();
$newtitle = $nt->getDBkey();
@@ -3578,8 +3757,8 @@ class Title {
* source page or nonexistent
*
* @param $nt Title the page to move to, which should be a redirect or nonexistent
- * @param $reason String The reason for the move
- * @param $createRedirect Bool Whether to leave a redirect at the old title. Does not check
+ * @param string $reason The reason for the move
+ * @param bool $createRedirect Whether to leave a redirect at the old title. Does not check
* if the user has the suppressredirect right
* @throws MWException
*/
@@ -3594,7 +3773,14 @@ class Title {
$logType = 'move';
}
- $redirectSuppressed = !$createRedirect;
+ if ( $createRedirect ) {
+ $contentHandler = ContentHandler::getForTitle( $this );
+ $redirectContent = $contentHandler->makeRedirectContent( $nt );
+
+ // NOTE: If this page's content model does not support redirects, $redirectContent will be null.
+ } else {
+ $redirectContent = null;
+ }
$logEntry = new ManualLogEntry( 'move', $logType );
$logEntry->setPerformer( $wgUser );
@@ -3602,7 +3788,7 @@ class Title {
$logEntry->setComment( $reason );
$logEntry->setParameters( array(
'4::target' => $nt->getPrefixedText(),
- '5::noredir' => $redirectSuppressed ? '1': '0',
+ '5::noredir' => $redirectContent ? '0': '1',
) );
$formatter = LogFormatter::newFromEntry( $logEntry );
@@ -3637,7 +3823,8 @@ class Title {
if ( !is_object( $nullRevision ) ) {
throw new MWException( 'No valid null revision produced in ' . __METHOD__ );
}
- $nullRevId = $nullRevision->insertOn( $dbw );
+
+ $nullRevision->insertOn( $dbw );
# Change the name of the target page:
$dbw->update( 'page',
@@ -3664,18 +3851,17 @@ class Title {
}
# Recreate the redirect, this time in the other direction.
- if ( $redirectSuppressed ) {
+ if ( !$redirectContent ) {
WikiPage::onArticleDelete( $this );
} else {
- $mwRedir = MagicWord::get( 'redirect' );
- $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n";
$redirectArticle = WikiPage::factory( $this );
$newid = $redirectArticle->insertOn( $dbw );
if ( $newid ) { // sanity
$redirectRevision = new Revision( array(
+ 'title' => $this, // for determining the default content model
'page' => $newid,
'comment' => $comment,
- 'text' => $redirectText ) );
+ 'content' => $redirectContent ) );
$redirectRevision->insertOn( $dbw );
$redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
@@ -3695,9 +3881,9 @@ class Title {
* Move this page's subpages to be subpages of $nt
*
* @param $nt Title Move target
- * @param $auth bool Whether $wgUser's permissions should be checked
- * @param $reason string The reason for the move
- * @param $createRedirect bool Whether to create redirects from the old subpages to
+ * @param bool $auth Whether $wgUser's permissions should be checked
+ * @param string $reason The reason for the move
+ * @param bool $createRedirect Whether to create redirects from the old subpages to
* the new ones Ignored if the user doesn't have the 'suppressredirect' right
* @return mixed array with old page titles as keys, and strings (new page titles) or
* arrays (errors) as values, or an error array with numeric indices if no pages
@@ -3771,10 +3957,16 @@ class Title {
* @return Bool
*/
public function isSingleRevRedirect() {
+ global $wgContentHandlerUseDB;
+
$dbw = wfGetDB( DB_MASTER );
+
# Is it a redirect?
+ $fields = array( 'page_is_redirect', 'page_latest', 'page_id' );
+ if ( $wgContentHandlerUseDB ) $fields[] = 'page_content_model';
+
$row = $dbw->selectRow( 'page',
- array( 'page_is_redirect', 'page_latest', 'page_id' ),
+ $fields,
$this->pageCond(),
__METHOD__,
array( 'FOR UPDATE' )
@@ -3783,6 +3975,7 @@ class Title {
$this->mArticleID = $row ? intval( $row->page_id ) : 0;
$this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
$this->mLatestID = $row ? intval( $row->page_latest ) : false;
+ $this->mContentModel = $row && isset( $row->page_content_model ) ? strval( $row->page_content_model ) : false;
if ( !$this->mRedirect ) {
return false;
}
@@ -3824,27 +4017,28 @@ class Title {
}
# Get the article text
$rev = Revision::newFromTitle( $nt, false, Revision::READ_LATEST );
- if( !is_object( $rev ) ){
+ if( !is_object( $rev ) ) {
return false;
}
- $text = $rev->getText();
+ $content = $rev->getContent();
# Does the redirect point to the source?
# Or is it a broken self-redirect, usually caused by namespace collisions?
- $m = array();
- if ( preg_match( "/\\[\\[\\s*([^\\]\\|]*)]]/", $text, $m ) ) {
- $redirTitle = Title::newFromText( $m[1] );
- if ( !is_object( $redirTitle ) ||
- ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
- $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) ) {
+ $redirTitle = $content ? $content->getRedirectTarget() : null;
+
+ if ( $redirTitle ) {
+ if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
+ $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
wfDebug( __METHOD__ . ": redirect points to other page\n" );
return false;
+ } else {
+ return true;
}
} else {
- # Fail safe
- wfDebug( __METHOD__ . ": failsafe\n" );
+ # Fail safe (not a redirect after all. strange.)
+ wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
+ " is a redirect, but it doesn't contain a valid redirect.\n" );
return false;
}
- return true;
}
/**
@@ -3867,15 +4061,14 @@ class Title {
$dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( 'categorylinks', '*',
- array(
- 'cl_from' => $titleKey,
- ),
- __METHOD__,
- array()
+ $res = $dbr->select(
+ 'categorylinks',
+ 'cl_to',
+ array( 'cl_from' => $titleKey ),
+ __METHOD__
);
- if ( $dbr->numRows( $res ) > 0 ) {
+ if ( $res->numRows() > 0 ) {
foreach ( $res as $row ) {
// $data[] = Title::newFromText($wgContLang->getNSText ( NS_CATEGORY ).':'.$row->cl_to);
$data[$wgContLang->getNSText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText();
@@ -3887,7 +4080,7 @@ class Title {
/**
* Get a tree of parent categories
*
- * @param $children Array with the children in the keys, to check for circular refs
+ * @param array $children with the children in the keys, to check for circular refs
* @return Array Tree of parent categories
*/
public function getParentCategoryTree( $children = array() ) {
@@ -3929,8 +4122,8 @@ class Title {
/**
* Get the revision ID of the previous revision
*
- * @param $revId Int Revision ID. Get the revision that was before this one.
- * @param $flags Int Title::GAID_FOR_UPDATE
+ * @param int $revId Revision ID. Get the revision that was before this one.
+ * @param int $flags Title::GAID_FOR_UPDATE
* @return Int|Bool Old revision ID, or FALSE if none exists
*/
public function getPreviousRevisionID( $revId, $flags = 0 ) {
@@ -3954,8 +4147,8 @@ class Title {
/**
* Get the revision ID of the next revision
*
- * @param $revId Int Revision ID. Get the revision that was after this one.
- * @param $flags Int Title::GAID_FOR_UPDATE
+ * @param int $revId Revision ID. Get the revision that was after this one.
+ * @param int $flags Title::GAID_FOR_UPDATE
* @return Int|Bool Next revision ID, or FALSE if none exists
*/
public function getNextRevisionID( $revId, $flags = 0 ) {
@@ -3979,7 +4172,7 @@ class Title {
/**
* Get the first revision of the page
*
- * @param $flags Int Title::GAID_FOR_UPDATE
+ * @param int $flags Title::GAID_FOR_UPDATE
* @return Revision|Null if page doesn't exist
*/
public function getFirstRevision( $flags = 0 ) {
@@ -4001,7 +4194,7 @@ class Title {
/**
* Get the oldest revision timestamp of this page
*
- * @param $flags Int Title::GAID_FOR_UPDATE
+ * @param int $flags Title::GAID_FOR_UPDATE
* @return String: MW timestamp
*/
public function getEarliestRevTime( $flags = 0 ) {
@@ -4058,8 +4251,8 @@ class Title {
* Get the number of revisions between the given revision.
* Used for diffs and other things that really need it.
*
- * @param $old int|Revision Old revision or rev ID (first before range)
- * @param $new int|Revision New revision or rev ID (first after range)
+ * @param int|Revision $old Old revision or rev ID (first before range)
+ * @param int|Revision $new New revision or rev ID (first after range)
* @return Int Number of revisions between these revisions.
*/
public function countRevisionsBetween( $old, $new ) {
@@ -4087,10 +4280,10 @@ class Title {
* Get the number of authors between the given revisions or revision IDs.
* Used for diffs and other things that really need it.
*
- * @param $old int|Revision Old revision or rev ID (first before range by default)
- * @param $new int|Revision New revision or rev ID (first after range by default)
- * @param $limit int Maximum number of authors
- * @param $options string|array (Optional): Single option, or an array of options:
+ * @param int|Revision $old Old revision or rev ID (first before range by default)
+ * @param int|Revision $new New revision or rev ID (first after range by default)
+ * @param int $limit Maximum number of authors
+ * @param string|array $options (Optional): Single option, or an array of options:
* 'include_old' Include $old in the range; $new is excluded.
* 'include_new' Include $new in the range; $old is excluded.
* 'include_both' Include both $old and $new in the range.
@@ -4112,7 +4305,7 @@ class Title {
}
$old_cmp = '>';
$new_cmp = '<';
- $options = (array) $options;
+ $options = (array)$options;
if ( in_array( 'include_old', $options ) ) {
$old_cmp = '>=';
}
@@ -4202,7 +4395,7 @@ class Title {
$isKnown = null;
/**
- * Allows overriding default behaviour for determining if a page exists.
+ * Allows overriding default behavior for determining if a page exists.
* If $isKnown is kept as null, regular checks happen. If it's
* a boolean, this value is returned by the isKnown method.
*
@@ -4271,7 +4464,7 @@ class Title {
// Use always content language to avoid loading hundreds of languages
// to get the link color.
global $wgContLang;
- list( $name, $lang ) = MessageCache::singleton()->figureMessage( $wgContLang->lcfirst( $this->getText() ) );
+ list( $name, ) = MessageCache::singleton()->figureMessage( $wgContLang->lcfirst( $this->getText() ) );
$message = wfMessage( $name )->inLanguage( $wgContLang )->useDatabase( false );
return $message->exists();
}
@@ -4304,9 +4497,10 @@ class Title {
/**
* Updates page_touched for this page; called from LinksUpdate.php
*
- * @return Bool true if the update succeded
+ * @return Bool true if the update succeeded
*/
public function invalidateCache() {
+ global $wgMemc;
if ( wfReadOnly() ) {
return false;
}
@@ -4318,12 +4512,20 @@ class Title {
__METHOD__
);
HTMLFileCache::clearFileCache( $this );
+
+ // Clear page info.
+ $revision = WikiPage::factory( $this )->getRevision();
+ if( $revision !== null ) {
+ $memcKey = wfMemcKey( 'infoaction', $this->getPrefixedText(), $revision->getId() );
+ $success = $success && $wgMemc->delete( $memcKey );
+ }
+
return $success;
}
/**
* Update page_touched timestamps and send squid purge messages for
- * pages linking to this title. May be sent to the job queue depending
+ * pages linking to this title. May be sent to the job queue depending
* on the number of links. Typically called on create and delete.
*/
public function touchLinks() {
@@ -4389,14 +4591,14 @@ class Title {
/**
* Generate strings used for xml 'id' names in monobook tabs
*
- * @param $prepend string defaults to 'nstab-'
+ * @param string $prepend defaults to 'nstab-'
* @return String XML 'id' name
*/
public function getNamespaceKey( $prepend = 'nstab-' ) {
global $wgContLang;
// Gets the subject namespace if this title
$namespace = MWNamespace::getSubject( $this->getNamespace() );
- // Checks if cononical namespace name exists for namespace
+ // Checks if canonical namespace name exists for namespace
if ( MWNamespace::exists( $this->getNamespace() ) ) {
// Uses canonical namespace name
$namespaceKey = MWNamespace::getCanonicalName( $namespace );
@@ -4420,7 +4622,7 @@ class Title {
/**
* Get all extant redirects to this Title
*
- * @param $ns Int|Null Single namespace to consider; NULL to consider all namespaces
+ * @param int|Null $ns Single namespace to consider; NULL to consider all namespaces
* @return Array of Title redirects to this title
*/
public function getRedirectsHere( $ns = null ) {
@@ -4462,7 +4664,7 @@ class Title {
public function isValidRedirectTarget() {
global $wgInvalidRedirectTargets;
- // invalid redirect targets are stored in a global array, but explicity disallow Userlogout here
+ // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here
if ( $this->isSpecial( 'Userlogout' ) ) {
return false;
}
@@ -4506,7 +4708,7 @@ class Title {
* prefix. This will be fed to Collation::getSortKey() to get a
* binary sortkey that can be used for actual sorting.
*
- * @param $prefix string The prefix to be used, specified using
+ * @param string $prefix The prefix to be used, specified using
* {{defaultsort:}} or like [[Category:Foo|prefix]]. Empty for no
* prefix.
* @return string
@@ -4543,19 +4745,13 @@ class Title {
if ( $this->isSpecialPage() ) {
// special pages are in the user language
return $wgLang;
- } elseif ( $this->isCssOrJsPage() || $this->isCssJsSubpage() ) {
- // css/js should always be LTR and is, in fact, English
- return wfGetLangObj( 'en' );
- } elseif ( $this->getNamespace() == NS_MEDIAWIKI ) {
- // Parse mediawiki messages with correct target language
- list( /* $unused */, $lang ) = MessageCache::singleton()->figureMessage( $this->getText() );
- return wfGetLangObj( $lang );
}
- global $wgContLang;
- // If nothing special, it should be in the wiki content language
- $pageLang = $wgContLang;
- // Hook at the end because we don't want to override the above stuff
- wfRunHooks( 'PageContentLanguage', array( $this, &$pageLang, $wgLang ) );
+
+ //TODO: use the LinkCache to cache this! Note that this may depend on user settings, so the cache should be only per-request.
+ //NOTE: ContentHandler::getPageLanguage() may need to load the content to determine the page language!
+ $contentHandler = ContentHandler::getForTitle( $this );
+ $pageLang = $contentHandler->getPageLanguage( $this );
+
return wfGetLangObj( $pageLang );
}
@@ -4568,19 +4764,62 @@ class Title {
* @return Language
*/
public function getPageViewLanguage() {
- $pageLang = $this->getPageLanguage();
- // If this is nothing special (so the content is converted when viewed)
- if ( !$this->isSpecialPage()
- && !$this->isCssOrJsPage() && !$this->isCssJsSubpage()
- && $this->getNamespace() !== NS_MEDIAWIKI
- ) {
+ global $wgLang;
+
+ if ( $this->isSpecialPage() ) {
// If the user chooses a variant, the content is actually
// in a language whose code is the variant code.
- $variant = $pageLang->getPreferredVariant();
- if ( $pageLang->getCode() !== $variant ) {
- $pageLang = Language::factory( $variant );
+ $variant = $wgLang->getPreferredVariant();
+ if ( $wgLang->getCode() !== $variant ) {
+ return Language::factory( $variant );
}
+
+ return $wgLang;
}
+
+ //NOTE: can't be cached persistently, depends on user settings
+ //NOTE: ContentHandler::getPageViewLanguage() may need to load the content to determine the page language!
+ $contentHandler = ContentHandler::getForTitle( $this );
+ $pageLang = $contentHandler->getPageViewLanguage( $this );
return $pageLang;
}
+
+ /**
+ * Get a list of rendered edit notices for this page.
+ *
+ * Array is keyed by the original message key, and values are rendered using parseAsBlock, so
+ * they will already be wrapped in paragraphs.
+ *
+ * @since 1.21
+ * @return Array
+ */
+ public function getEditNotices() {
+ $notices = array();
+
+ # Optional notices on a per-namespace and per-page basis
+ $editnotice_ns = 'editnotice-' . $this->getNamespace();
+ $editnotice_ns_message = wfMessage( $editnotice_ns );
+ if ( $editnotice_ns_message->exists() ) {
+ $notices[$editnotice_ns] = $editnotice_ns_message->parseAsBlock();
+ }
+ if ( MWNamespace::hasSubpages( $this->getNamespace() ) ) {
+ $parts = explode( '/', $this->getDBkey() );
+ $editnotice_base = $editnotice_ns;
+ while ( count( $parts ) > 0 ) {
+ $editnotice_base .= '-' . array_shift( $parts );
+ $editnotice_base_msg = wfMessage( $editnotice_base );
+ if ( $editnotice_base_msg->exists() ) {
+ $notices[$editnotice_base] = $editnotice_base_msg->parseAsBlock();
+ }
+ }
+ } else {
+ # Even if there are no subpages in namespace, we still don't want / in MW ns.
+ $editnoticeText = $editnotice_ns . '-' . str_replace( '/', '-', $this->getDBkey() );
+ $editnoticeMsg = wfMessage( $editnoticeText );
+ if ( $editnoticeMsg->exists() ) {
+ $notices[$editnoticeText] = $editnoticeMsg->parseAsBlock();
+ }
+ }
+ return $notices;
+ }
}
diff --git a/includes/TitleArray.php b/includes/TitleArray.php
index 5cdec16d..90fb861a 100644
--- a/includes/TitleArray.php
+++ b/includes/TitleArray.php
@@ -3,8 +3,8 @@
* Classes to walk into a list of Title objects.
*
* Note: this entire file is a byte-for-byte copy of UserArray.php with
- * s/User/Title/. If anyone can figure out how to do this nicely with inheri-
- * tance or something, please do so.
+ * s/User/Title/. If anyone can figure out how to do this nicely with
+ * inheritance or something, please do so.
*
* 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
diff --git a/includes/UIDGenerator.php b/includes/UIDGenerator.php
new file mode 100644
index 00000000..b042d8c7
--- /dev/null
+++ b/includes/UIDGenerator.php
@@ -0,0 +1,350 @@
+<?php
+/**
+ * This file deals with UID generation.
+ *
+ * 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
+ * @author Aaron Schulz
+ */
+
+/**
+ * Class for getting statistically unique IDs
+ *
+ * @since 1.21
+ */
+class UIDGenerator {
+ /** @var UIDGenerator */
+ protected static $instance = null;
+
+ protected $nodeId32; // string; node ID in binary (32 bits)
+ protected $nodeId48; // string; node ID in binary (48 bits)
+
+ protected $lockFile88; // string; local file path
+ protected $lockFile128; // string; local file path
+
+ /** @var Array */
+ protected $fileHandles = array(); // cache file handles
+
+ const QUICK_RAND = 1; // get randomness from fast and insecure sources
+
+ protected function __construct() {
+ $idFile = wfTempDir() . '/mw-' . __CLASS__ . '-UID-nodeid';
+ $nodeId = is_file( $idFile ) ? file_get_contents( $idFile ) : '';
+ // Try to get some ID that uniquely identifies this machine (RFC 4122)...
+ if ( !preg_match( '/^[0-9a-f]{12}$/i', $nodeId ) ) {
+ wfSuppressWarnings();
+ if ( wfIsWindows() ) {
+ // http://technet.microsoft.com/en-us/library/bb490913.aspx
+ $csv = trim( wfShellExec( 'getmac /NH /FO CSV' ) );
+ $line = substr( $csv, 0, strcspn( $csv, "\n" ) );
+ $info = str_getcsv( $line );
+ $nodeId = isset( $info[0] ) ? str_replace( '-', '', $info[0] ) : '';
+ } elseif ( is_executable( '/sbin/ifconfig' ) ) { // Linux/BSD/Solaris/OS X
+ // See http://linux.die.net/man/8/ifconfig
+ $m = array();
+ preg_match( '/\s([0-9a-f]{2}(:[0-9a-f]{2}){5})\s/',
+ wfShellExec( '/sbin/ifconfig -a' ), $m );
+ $nodeId = isset( $m[1] ) ? str_replace( ':', '', $m[1] ) : '';
+ }
+ wfRestoreWarnings();
+ if ( !preg_match( '/^[0-9a-f]{12}$/i', $nodeId ) ) {
+ $nodeId = MWCryptRand::generateHex( 12, true );
+ $nodeId[1] = dechex( hexdec( $nodeId[1] ) | 0x1 ); // set multicast bit
+ }
+ file_put_contents( $idFile, $nodeId ); // cache
+ }
+ $this->nodeId32 = wfBaseConvert( substr( sha1( $nodeId ), 0, 8 ), 16, 2, 32 );
+ $this->nodeId48 = wfBaseConvert( $nodeId, 16, 2, 48 );
+ // If different processes run as different users, they may have different temp dirs.
+ // This is dealt with by initializing the clock sequence number and counters randomly.
+ $this->lockFile88 = wfTempDir() . '/mw-' . __CLASS__ . '-UID-88';
+ $this->lockFile128 = wfTempDir() . '/mw-' . __CLASS__ . '-UID-128';
+ }
+
+ /**
+ * @return UIDGenerator
+ */
+ protected static function singleton() {
+ if ( self::$instance === null ) {
+ self::$instance = new self();
+ }
+ return self::$instance;
+ }
+
+ /**
+ * Get a statistically unique 88-bit unsigned integer ID string.
+ * The bits of the UID are prefixed with the time (down to the millisecond).
+ *
+ * These IDs are suitable as values for the shard key of distributed data.
+ * If a column uses these as values, it should be declared UNIQUE to handle collisions.
+ * New rows almost always have higher UIDs, which makes B-TREE updates on INSERT fast.
+ * They can also be stored "DECIMAL(27) UNSIGNED" or BINARY(11) in MySQL.
+ *
+ * UID generation is serialized on each server (as the node ID is for the whole machine).
+ *
+ * @param $base integer Specifies a base other than 10
+ * @return string Number
+ * @throws MWException
+ */
+ public static function newTimestampedUID88( $base = 10 ) {
+ if ( !is_integer( $base ) || $base > 36 || $base < 2 ) {
+ throw new MWException( "Base must an integer be between 2 and 36" );
+ }
+ $gen = self::singleton();
+ $time = $gen->getTimestampAndDelay( 'lockFile88', 1, 1024 );
+ return wfBaseConvert( $gen->getTimestampedID88( $time ), 2, $base );
+ }
+
+ /**
+ * @param array $time (UIDGenerator::millitime(), clock sequence)
+ * @return string 88 bits
+ */
+ protected function getTimestampedID88( array $info ) {
+ list( $time, $counter ) = $info;
+ // Take the 46 MSBs of "milliseconds since epoch"
+ $id_bin = $this->millisecondsSinceEpochBinary( $time );
+ // Add a 10 bit counter resulting in 56 bits total
+ $id_bin .= str_pad( decbin( $counter ), 10, '0', STR_PAD_LEFT );
+ // Add the 32 bit node ID resulting in 88 bits total
+ $id_bin .= $this->nodeId32;
+ // Convert to a 1-27 digit integer string
+ if ( strlen( $id_bin ) !== 88 ) {
+ throw new MWException( "Detected overflow for millisecond timestamp." );
+ }
+ return $id_bin;
+ }
+
+ /**
+ * Get a statistically unique 128-bit unsigned integer ID string.
+ * The bits of the UID are prefixed with the time (down to the millisecond).
+ *
+ * These IDs are suitable as globally unique IDs, without any enforced uniqueness.
+ * New rows almost always have higher UIDs, which makes B-TREE updates on INSERT fast.
+ * They can also be stored as "DECIMAL(39) UNSIGNED" or BINARY(16) in MySQL.
+ *
+ * UID generation is serialized on each server (as the node ID is for the whole machine).
+ *
+ * @param $base integer Specifies a base other than 10
+ * @return string Number
+ * @throws MWException
+ */
+ public static function newTimestampedUID128( $base = 10 ) {
+ if ( !is_integer( $base ) || $base > 36 || $base < 2 ) {
+ throw new MWException( "Base must be an integer between 2 and 36" );
+ }
+ $gen = self::singleton();
+ $time = $gen->getTimestampAndDelay( 'lockFile128', 16384, 1048576 );
+ return wfBaseConvert( $gen->getTimestampedID128( $time ), 2, $base );
+ }
+
+ /**
+ * @param array $info (UIDGenerator::millitime(), counter, clock sequence)
+ * @return string 128 bits
+ */
+ protected function getTimestampedID128( array $info ) {
+ list( $time, $counter, $clkSeq ) = $info;
+ // Take the 46 MSBs of "milliseconds since epoch"
+ $id_bin = $this->millisecondsSinceEpochBinary( $time );
+ // Add a 20 bit counter resulting in 66 bits total
+ $id_bin .= str_pad( decbin( $counter ), 20, '0', STR_PAD_LEFT );
+ // Add a 14 bit clock sequence number resulting in 80 bits total
+ $id_bin .= str_pad( decbin( $clkSeq ), 14, '0', STR_PAD_LEFT );
+ // Add the 48 bit node ID resulting in 128 bits total
+ $id_bin .= $this->nodeId48;
+ // Convert to a 1-39 digit integer string
+ if ( strlen( $id_bin ) !== 128 ) {
+ throw new MWException( "Detected overflow for millisecond timestamp." );
+ }
+ return $id_bin;
+ }
+
+ /**
+ * Return an RFC4122 compliant v4 UUID
+ *
+ * @param $flags integer Bitfield (supports UIDGenerator::QUICK_RAND)
+ * @return string
+ * @throws MWException
+ */
+ public static function newUUIDv4( $flags = 0 ) {
+ $hex = ( $flags & self::QUICK_RAND )
+ ? wfRandomString( 31 )
+ : MWCryptRand::generateHex( 31 );
+
+ return sprintf( '%s-%s-%s-%s-%s',
+ // "time_low" (32 bits)
+ substr( $hex, 0, 8 ),
+ // "time_mid" (16 bits)
+ substr( $hex, 8, 4 ),
+ // "time_hi_and_version" (16 bits)
+ '4' . substr( $hex, 12, 3 ),
+ // "clk_seq_hi_res (8 bits, variant is binary 10x) and "clk_seq_low" (8 bits)
+ dechex( 0x8 | ( hexdec( $hex[15] ) & 0x3 ) ) . $hex[16] . substr( $hex, 17, 2 ),
+ // "node" (48 bits)
+ substr( $hex, 19, 12 )
+ );
+ }
+
+ /**
+ * Return an RFC4122 compliant v4 UUID
+ *
+ * @param $flags integer Bitfield (supports UIDGenerator::QUICK_RAND)
+ * @return string 32 hex characters with no hyphens
+ * @throws MWException
+ */
+ public static function newRawUUIDv4( $flags = 0 ) {
+ return str_replace( '-', '', self::newUUIDv4( $flags ) );
+ }
+
+ /**
+ * Get a (time,counter,clock sequence) where (time,counter) is higher
+ * than any previous (time,counter) value for the given clock sequence.
+ * This is useful for making UIDs sequential on a per-node bases.
+ *
+ * @param string $lockFile Name of a local lock file
+ * @param $clockSeqSize integer The number of possible clock sequence values
+ * @param $counterSize integer The number of possible counter values
+ * @return Array (result of UIDGenerator::millitime(), counter, clock sequence)
+ * @throws MWException
+ */
+ protected function getTimestampAndDelay( $lockFile, $clockSeqSize, $counterSize ) {
+ // Get the UID lock file handle
+ if ( isset( $this->fileHandles[$lockFile] ) ) {
+ $handle = $this->fileHandles[$lockFile];
+ } else {
+ $handle = fopen( $this->$lockFile, 'cb+' );
+ $this->fileHandles[$lockFile] = $handle ?: null; // cache
+ }
+ // Acquire the UID lock file
+ if ( $handle === false ) {
+ throw new MWException( "Could not open '{$this->$lockFile}'." );
+ } elseif ( !flock( $handle, LOCK_EX ) ) {
+ throw new MWException( "Could not acquire '{$this->$lockFile}'." );
+ }
+ // Get the current timestamp, clock sequence number, last time, and counter
+ rewind( $handle );
+ $data = explode( ' ', fgets( $handle ) ); // "<clk seq> <sec> <msec> <counter> <offset>"
+ $clockChanged = false; // clock set back significantly?
+ if ( count( $data ) == 5 ) { // last UID info already initialized
+ $clkSeq = (int) $data[0] % $clockSeqSize;
+ $prevTime = array( (int) $data[1], (int) $data[2] );
+ $offset = (int) $data[4] % $counterSize; // random counter offset
+ $counter = 0; // counter for UIDs with the same timestamp
+ // Delay until the clock reaches the time of the last ID.
+ // This detects any microtime() drift among processes.
+ $time = $this->timeWaitUntil( $prevTime );
+ if ( !$time ) { // too long to delay?
+ $clockChanged = true; // bump clock sequence number
+ $time = self::millitime();
+ } elseif ( $time == $prevTime ) {
+ // Bump the counter if there are timestamp collisions
+ $counter = (int) $data[3] % $counterSize;
+ if ( ++$counter >= $counterSize ) { // sanity (starts at 0)
+ flock( $handle, LOCK_UN ); // abort
+ throw new MWException( "Counter overflow for timestamp value." );
+ }
+ }
+ } else { // last UID info not initialized
+ $clkSeq = mt_rand( 0, $clockSeqSize - 1 );
+ $counter = 0;
+ $offset = mt_rand( 0, $counterSize - 1 );
+ $time = self::millitime();
+ }
+ // microtime() and gettimeofday() can drift from time() at least on Windows.
+ // The drift is immediate for processes running while the system clock changes.
+ // time() does not have this problem. See https://bugs.php.net/bug.php?id=42659.
+ if ( abs( time() - $time[0] ) >= 2 ) {
+ // We don't want processes using too high or low timestamps to avoid duplicate
+ // UIDs and clock sequence number churn. This process should just be restarted.
+ flock( $handle, LOCK_UN ); // abort
+ throw new MWException( "Process clock is outdated or drifted." );
+ }
+ // If microtime() is synced and a clock change was detected, then the clock went back
+ if ( $clockChanged ) {
+ // Bump the clock sequence number and also randomize the counter offset,
+ // which is useful for UIDs that do not include the clock sequence number.
+ $clkSeq = ( $clkSeq + 1 ) % $clockSeqSize;
+ $offset = mt_rand( 0, $counterSize - 1 );
+ trigger_error( "Clock was set back; sequence number incremented." );
+ }
+ // Update the (clock sequence number, timestamp, counter)
+ ftruncate( $handle, 0 );
+ rewind( $handle );
+ fwrite( $handle, "{$clkSeq} {$time[0]} {$time[1]} {$counter} {$offset}" );
+ fflush( $handle );
+ // Release the UID lock file
+ flock( $handle, LOCK_UN );
+
+ return array( $time, ( $counter + $offset ) % $counterSize, $clkSeq );
+ }
+
+ /**
+ * Wait till the current timestamp reaches $time and return the current
+ * timestamp. This returns false if it would have to wait more than 10ms.
+ *
+ * @param array $time Result of UIDGenerator::millitime()
+ * @return Array|bool UIDGenerator::millitime() result or false
+ */
+ protected function timeWaitUntil( array $time ) {
+ do {
+ $ct = self::millitime();
+ if ( $ct >= $time ) { // http://php.net/manual/en/language.operators.comparison.php
+ return $ct; // current timestamp is higher than $time
+ }
+ } while ( ( ( $time[0] - $ct[0] )*1000 + ( $time[1] - $ct[1] ) ) <= 10 );
+
+ return false;
+ }
+
+ /**
+ * @param array $time Result of UIDGenerator::millitime()
+ * @return string 46 MSBs of "milliseconds since epoch" in binary (rolls over in 4201)
+ */
+ protected function millisecondsSinceEpochBinary( array $time ) {
+ list( $sec, $msec ) = $time;
+ if ( PHP_INT_SIZE >= 8 ) { // 64 bit integers
+ $ts = ( 1000 * $sec + $msec );
+ $id_bin = str_pad( decbin( $ts % pow( 2, 46 ) ), 46, '0', STR_PAD_LEFT );
+ } elseif ( extension_loaded( 'gmp' ) ) {
+ $ts = gmp_mod( // wrap around
+ gmp_add( gmp_mul( (string) $sec, (string) 1000 ), (string) $msec ),
+ gmp_pow( '2', '46' )
+ );
+ $id_bin = str_pad( gmp_strval( $ts, 2 ), 46, '0', STR_PAD_LEFT );
+ } elseif ( extension_loaded( 'bcmath' ) ) {
+ $ts = bcmod( // wrap around
+ bcadd( bcmul( $sec, 1000 ), $msec ),
+ bcpow( 2, 46 )
+ );
+ $id_bin = wfBaseConvert( $ts, 10, 2, 46 );
+ } else {
+ throw new MWException( 'bcmath or gmp extension required for 32 bit machines.' );
+ }
+ return $id_bin;
+ }
+
+ /**
+ * @return Array (current time in seconds, milliseconds since then)
+ */
+ protected static function millitime() {
+ list( $msec, $sec ) = explode( ' ', microtime() );
+ return array( (int) $sec, (int) ( $msec * 1000 ) );
+ }
+
+ function __destruct() {
+ array_map( 'fclose', $this->fileHandles );
+ }
+}
diff --git a/includes/User.php b/includes/User.php
index 0a3db4c0..6b7348a8 100644
--- a/includes/User.php
+++ b/includes/User.php
@@ -253,7 +253,7 @@ class User {
/**
* @return String
*/
- function __toString(){
+ function __toString() {
return $this->getName();
}
@@ -286,10 +286,14 @@ class User {
$this->loadFromId();
break;
case 'session':
- $this->loadFromSession();
+ if( !$this->loadFromSession() ) {
+ // Loading from session failed. Load defaults.
+ $this->loadDefaults();
+ }
wfRunHooks( 'UserLoadAfterLoadFromSession', array( $this ) );
break;
default:
+ wfProfileOut( __METHOD__ );
throw new MWException( "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
}
wfProfileOut( __METHOD__ );
@@ -329,6 +333,9 @@ class User {
$this->$name = $data[$name];
}
}
+
+ $this->mLoadedItems = true;
+
return true;
}
@@ -362,12 +369,12 @@ class User {
* This is slightly less efficient than newFromId(), so use newFromId() if
* you have both an ID and a name handy.
*
- * @param $name String Username, validated by Title::newFromText()
- * @param $validate String|Bool Validate username. Takes the same parameters as
+ * @param string $name Username, validated by Title::newFromText()
+ * @param string|Bool $validate Validate username. Takes the same parameters as
* User::getCanonicalName(), except that true is accepted as an alias
* for 'valid', for BC.
*
- * @return User object, or false if the username is invalid
+ * @return User|bool User object, or false if the username is invalid
* (e.g. if it contains illegal characters or is an IP address). If the
* username is not present in the database, the result will be a user object
* with a name, zero user ID and default settings.
@@ -392,7 +399,7 @@ class User {
/**
* Static factory method for creation from a given user ID.
*
- * @param $id Int Valid user ID
+ * @param int $id Valid user ID
* @return User The corresponding User object
*/
public static function newFromId( $id ) {
@@ -410,7 +417,7 @@ class User {
*
* If the code is invalid or has expired, returns NULL.
*
- * @param $code String Confirmation code
+ * @param string $code Confirmation code
* @return User object, or null
*/
public static function newFromConfirmationCode( $code ) {
@@ -430,8 +437,7 @@ class User {
* Create a new user object using data from session or cookies. If the
* login credentials are invalid, the result is an anonymous user.
*
- * @param $request WebRequest object to use; $wgRequest will be used if
- * ommited.
+ * @param $request WebRequest object to use; $wgRequest will be used if omitted.
* @return User object
*/
public static function newFromSession( WebRequest $request = null ) {
@@ -451,12 +457,13 @@ class User {
* user_name and user_real_name are not provided because the whole row
* will be loaded once more from the database when accessing them.
*
- * @param $row Array A row from the user table
+ * @param array $row A row from the user table
+ * @param array $data Further data to load into the object (see User::loadFromRow for valid keys)
* @return User
*/
- public static function newFromRow( $row ) {
+ public static function newFromRow( $row, $data = null ) {
$user = new User;
- $user->loadFromRow( $row );
+ $user->loadFromRow( $row, $data );
return $user;
}
@@ -464,7 +471,7 @@ class User {
/**
* Get the username corresponding to a given user ID
- * @param $id Int User ID
+ * @param int $id User ID
* @return String|bool The corresponding username
*/
public static function whoIs( $id ) {
@@ -474,7 +481,7 @@ class User {
/**
* Get the real name of a user given their user ID
*
- * @param $id Int User ID
+ * @param int $id User ID
* @return String|bool The corresponding user's real name
*/
public static function whoIsReal( $id ) {
@@ -483,7 +490,7 @@ class User {
/**
* Get database id given a user name
- * @param $name String Username
+ * @param string $name Username
* @return Int|Null The corresponding user's ID, or null if user is nonexistent
*/
public static function idFromName( $name ) {
@@ -535,22 +542,22 @@ class User {
* addresses like this, if we allowed accounts like this to be created
* new users could get the old edits of these anonymous users.
*
- * @param $name String to match
+ * @param string $name to match
* @return Bool
*/
public static function isIP( $name ) {
- return preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/',$name) || IP::isIPv6($name);
+ return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name ) || IP::isIPv6( $name );
}
/**
* Is the input a valid username?
*
* Checks if the input is a valid username, we don't want an empty string,
- * an IP address, anything that containins slashes (would mess up subpages),
+ * an IP address, anything that contains slashes (would mess up subpages),
* is longer than the maximum allowed username size or doesn't begin with
* a capital letter.
*
- * @param $name String to match
+ * @param string $name to match
* @return Bool
*/
public static function isValidUserName( $name ) {
@@ -566,7 +573,6 @@ class User {
return false;
}
-
// Ensure that the name can't be misresolved as a different title,
// such as with extra namespace keys at the start.
$parsed = Title::newFromText( $name );
@@ -605,7 +611,7 @@ class User {
* If an account already exists in this form, login will be blocked
* by a failure to pass this function.
*
- * @param $name String to match
+ * @param string $name to match
* @return Bool
*/
public static function isUsableName( $name ) {
@@ -642,7 +648,7 @@ class User {
* Additional blacklisting may be added here rather than in
* isValidUserName() to avoid disrupting existing accounts.
*
- * @param $name String to match
+ * @param string $name to match
* @return Bool
*/
public static function isCreatableName( $name ) {
@@ -672,7 +678,7 @@ class User {
/**
* Is the input a valid password for this user?
*
- * @param $password String Desired password
+ * @param string $password Desired password
* @return Bool
*/
public function isValidPassword( $password ) {
@@ -683,7 +689,7 @@ class User {
/**
* Given unvalidated password input, return error message on failure.
*
- * @param $password String Desired password
+ * @param string $password Desired password
* @return mixed: true on success, string or array of error message on failure
*/
public function getPasswordValidity( $password ) {
@@ -743,7 +749,7 @@ class User {
* to be liberal enough for wide use. Some invalid addresses will still
* pass validation here.
*
- * @param $addr String E-mail address
+ * @param string $addr E-mail address
* @return Bool
* @deprecated since 1.18 call Sanitizer::isValidEmail() directly
*/
@@ -755,13 +761,14 @@ class User {
/**
* Given unvalidated user input, return a canonical username, or false if
* the username is invalid.
- * @param $name String User input
- * @param $validate String|Bool type of validation to use:
+ * @param string $name User input
+ * @param string|Bool $validate type of validation to use:
* - false No validation
* - 'valid' Valid for batch processes
* - 'usable' Valid for batch processes and login
* - 'creatable' Valid for batch processes, login and account creation
*
+ * @throws MWException
* @return bool|string
*/
public static function getCanonicalName( $name, $validate = 'valid' ) {
@@ -813,39 +820,16 @@ class User {
/**
* Count the number of edits of a user
- * @todo It should not be static and some day should be merged as proper member function / deprecated -- domas
*
- * @param $uid Int User ID to check
+ * @param int $uid User ID to check
* @return Int the user's edit count
+ *
+ * @deprecated since 1.21 in favour of User::getEditCount
*/
public static function edits( $uid ) {
- wfProfileIn( __METHOD__ );
- $dbr = wfGetDB( DB_SLAVE );
- // check if the user_editcount field has been initialized
- $field = $dbr->selectField(
- 'user', 'user_editcount',
- array( 'user_id' => $uid ),
- __METHOD__
- );
-
- if( $field === null ) { // it has not been initialized. do so.
- $dbw = wfGetDB( DB_MASTER );
- $count = $dbr->selectField(
- 'revision', 'count(*)',
- array( 'rev_user' => $uid ),
- __METHOD__
- );
- $dbw->update(
- 'user',
- array( 'user_editcount' => $count ),
- array( 'user_id' => $uid ),
- __METHOD__
- );
- } else {
- $count = $field;
- }
- wfProfileOut( __METHOD__ );
- return $count;
+ wfDeprecated( __METHOD__, '1.21' );
+ $user = self::newFromId( $uid );
+ return $user->getEditCount();
}
/**
@@ -871,7 +855,7 @@ class User {
* @note This no longer clears uncached lazy-initialised properties;
* the constructor does that instead.
*
- * @param $name string
+ * @param $name string|bool
*/
public function loadDefaults( $name = false ) {
wfProfileIn( __METHOD__ );
@@ -889,7 +873,7 @@ class User {
if( $loggedOut !== null ) {
$this->mTouched = wfTimestamp( TS_MW, $loggedOut );
} else {
- $this->mTouched = '0'; # Allow any pages to be cached
+ $this->mTouched = '1'; # Allow any pages to be cached
}
$this->mToken = null; // Don't run cryptographic functions till we need a token
@@ -907,11 +891,11 @@ class User {
/**
* Return whether an item has been loaded.
*
- * @param $item String: item to check. Current possibilities:
+ * @param string $item item to check. Current possibilities:
* - id
* - name
* - realname
- * @param $all String: 'all' to check if the whole object has been loaded
+ * @param string $all 'all' to check if the whole object has been loaded
* or any other string to check if only the item is available (e.g.
* for optimisation)
* @return Boolean
@@ -933,8 +917,7 @@ class User {
}
/**
- * Load user data from the session or login cookie. If there are no valid
- * credentials, initialises the user as an anonymous user.
+ * Load user data from the session or login cookie.
* @return Bool True if the user is logged in, false otherwise.
*/
private function loadFromSession() {
@@ -962,7 +945,6 @@ class User {
if ( $cookieId !== null ) {
$sId = intval( $cookieId );
if( $sessId !== null && $cookieId != $sessId ) {
- $this->loadDefaults(); // Possible collision!
wfDebugLog( 'loginSessions', "Session user ID ($sessId) and
cookie user ID ($sId) don't match!" );
return false;
@@ -971,7 +953,6 @@ class User {
} elseif ( $sessId !== null && $sessId != 0 ) {
$sId = $sessId;
} else {
- $this->loadDefaults();
return false;
}
@@ -981,33 +962,32 @@ class User {
$sName = $request->getCookie( 'UserName' );
$request->setSessionData( 'wsUserName', $sName );
} else {
- $this->loadDefaults();
return false;
}
$proposedUser = User::newFromId( $sId );
if ( !$proposedUser->isLoggedIn() ) {
# Not a valid ID
- $this->loadDefaults();
return false;
}
global $wgBlockDisablesLogin;
if( $wgBlockDisablesLogin && $proposedUser->isBlocked() ) {
# User blocked and we've disabled blocked user logins
- $this->loadDefaults();
return false;
}
if ( $request->getSessionData( 'wsToken' ) ) {
- $passwordCorrect = $proposedUser->getToken( false ) === $request->getSessionData( 'wsToken' );
+ $passwordCorrect = ( $proposedUser->getToken( false ) === $request->getSessionData( 'wsToken' ) );
$from = 'session';
} elseif ( $request->getCookie( 'Token' ) ) {
- $passwordCorrect = $proposedUser->getToken( false ) === $request->getCookie( 'Token' );
+ # Get the token from DB/cache and clean it up to remove garbage padding.
+ # This deals with historical problems with bugs and the default column value.
+ $token = rtrim( $proposedUser->getToken( false ) ); // correct token
+ $passwordCorrect = ( strlen( $token ) && $token === $request->getCookie( 'Token' ) );
$from = 'cookie';
} else {
# No session or persistent login cookie
- $this->loadDefaults();
return false;
}
@@ -1019,7 +999,6 @@ class User {
} else {
# Invalid credentials
wfDebug( "User: can't log in from $from, invalid credentials\n" );
- $this->loadDefaults();
return false;
}
}
@@ -1062,9 +1041,13 @@ class User {
/**
* Initialize this object from a row from the user table.
*
- * @param $row Array Row from the user table to load.
+ * @param array $row Row from the user table to load.
+ * @param array $data Further user data to load into the object
+ *
+ * user_groups Array with groups out of the user_groups table
+ * user_properties Array with properties out of the user_properties table
*/
- public function loadFromRow( $row ) {
+ public function loadFromRow( $row, $data = null ) {
$all = true;
$this->mGroups = null; // deferred
@@ -1122,6 +1105,15 @@ class User {
if ( $all ) {
$this->mLoadedItems = true;
}
+
+ if ( is_array( $data ) ) {
+ if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
+ $this->mGroups = $data['user_groups'];
+ }
+ if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
+ $this->loadOptions( $data['user_properties'] );
+ }
+ }
}
/**
@@ -1163,7 +1155,7 @@ class User {
* will not be re-added automatically. The user will also not lose the
* group if they no longer meet the criteria.
*
- * @param $event String key in $wgAutopromoteOnce (each one has groups/criteria)
+ * @param string $event key in $wgAutopromoteOnce (each one has groups/criteria)
*
* @return array Array of groups the user has been promoted to.
*
@@ -1182,21 +1174,27 @@ class User {
}
$newGroups = array_merge( $oldGroups, $toPromote ); // all groups
- $log = new LogPage( 'rights', $wgAutopromoteOnceLogInRC /* in RC? */ );
- $log->addEntry( 'autopromote',
- $this->getUserPage(),
- '', // no comment
- // These group names are "list to texted"-ed in class LogPage.
- array( implode( ', ', $oldGroups ), implode( ', ', $newGroups ) )
- );
+ $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
+ $logEntry->setPerformer( $this );
+ $logEntry->setTarget( $this->getUserPage() );
+ $logEntry->setParameters( array(
+ '4::oldgroups' => $oldGroups,
+ '5::newgroups' => $newGroups,
+ ) );
+ $logid = $logEntry->insert();
+ if ( $wgAutopromoteOnceLogInRC ) {
+ $logEntry->publish( $logid );
+ }
}
}
return $toPromote;
}
/**
- * Clear various cached data stored in this object.
- * @param $reloadFrom bool|String Reload user and user_groups table data from a
+ * Clear various cached data stored in this object. The cache of the user table
+ * data (i.e. self::$mCacheVars) is not cleared unless $reloadFrom is given.
+ *
+ * @param bool|String $reloadFrom Reload user and user_groups table data from a
* given source. May be "name", "id", "defaults", "session", or false for
* no reload.
*/
@@ -1208,7 +1206,10 @@ class User {
$this->mRights = null;
$this->mEffectiveGroups = null;
$this->mImplicitGroups = null;
+ $this->mGroups = null;
$this->mOptions = null;
+ $this->mOptionsLoaded = false;
+ $this->mEditCount = null;
if ( $reloadFrom ) {
$this->mLoadedItems = array();
@@ -1225,22 +1226,23 @@ class User {
public static function getDefaultOptions() {
global $wgNamespacesToBeSearchedDefault, $wgDefaultUserOptions, $wgContLang, $wgDefaultSkin;
+ static $defOpt = null;
+ if ( !defined( 'MW_PHPUNIT_TEST' ) && $defOpt !== null ) {
+ // Disabling this for the unit tests, as they rely on being able to change $wgContLang
+ // mid-request and see that change reflected in the return value of this function.
+ // Which is insane and would never happen during normal MW operation
+ return $defOpt;
+ }
+
$defOpt = $wgDefaultUserOptions;
# default language setting
- $variant = $wgContLang->getDefaultVariant();
- $defOpt['variant'] = $variant;
- $defOpt['language'] = $variant;
+ $defOpt['variant'] = $wgContLang->getCode();
+ $defOpt['language'] = $wgContLang->getCode();
foreach( SearchEngine::searchableNamespaces() as $nsnum => $nsname ) {
$defOpt['searchNs'.$nsnum] = !empty( $wgNamespacesToBeSearchedDefault[$nsnum] );
}
$defOpt['skin'] = $wgDefaultSkin;
- // FIXME: Ideally we'd cache the results of this function so the hook is only run once,
- // but that breaks the parser tests because they rely on being able to change $wgContLang
- // mid-request and see that change reflected in the return value of this function.
- // Which is insane and would never happen during normal MW operation, but is also not
- // likely to get fixed unless and until we context-ify everything.
- // See also https://www.mediawiki.org/wiki/Special:Code/MediaWiki/101488#c25275
wfRunHooks( 'UserGetDefaultOptions', array( &$defOpt ) );
return $defOpt;
@@ -1249,7 +1251,7 @@ class User {
/**
* Get a given default option value.
*
- * @param $opt String Name of option to retrieve
+ * @param string $opt Name of option to retrieve
* @return String Default option value
*/
public static function getDefaultOption( $opt ) {
@@ -1261,10 +1263,9 @@ class User {
}
}
-
/**
* Get blocking information
- * @param $bFromSlave Bool Whether to check the slave database first. To
+ * @param bool $bFromSlave Whether to check the slave database first. To
* improve performance, non-critical checks are done
* against slaves. Check when actually saving should be
* done against master.
@@ -1338,8 +1339,8 @@ class User {
/**
* Whether the given IP is in a DNS blacklist.
*
- * @param $ip String IP to check
- * @param $checkWhitelist Bool: whether to check the whitelist first
+ * @param string $ip IP to check
+ * @param bool $checkWhitelist whether to check the whitelist first
* @return Bool True if blacklisted.
*/
public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
@@ -1359,8 +1360,8 @@ class User {
/**
* Whether the given IP is in a given DNS blacklist.
*
- * @param $ip String IP to check
- * @param $bases String|Array of Strings: URL of the DNS blacklist
+ * @param string $ip IP to check
+ * @param string|array $bases of Strings: URL of the DNS blacklist
* @return Bool True if blacklisted.
*/
public function inDnsBlacklist( $ip, $bases ) {
@@ -1450,7 +1451,7 @@ class User {
// But this is a crappy hack and should die.
return false;
}
- return !$this->isAllowed('noratelimit');
+ return !$this->isAllowed( 'noratelimit' );
}
/**
@@ -1460,13 +1461,13 @@ class User {
* @note When using a shared cache like memcached, IP-address
* last-hit counters will be shared across wikis.
*
- * @param $action String Action to enforce; 'edit' if unspecified
+ * @param string $action Action to enforce; 'edit' if unspecified
* @return Bool True if a rate limiter was tripped
*/
public function pingLimiter( $action = 'edit' ) {
# Call the 'PingLimiter' hook
$result = false;
- if( !wfRunHooks( 'PingLimiter', array( &$this, $action, $result ) ) ) {
+ if( !wfRunHooks( 'PingLimiter', array( &$this, $action, &$result ) ) ) {
return $result;
}
@@ -1555,7 +1556,7 @@ class User {
/**
* Check if user is blocked
*
- * @param $bFromSlave Bool Whether to check the slave database instead of the master
+ * @param bool $bFromSlave Whether to check the slave database instead of the master
* @return Bool True if blocked, false otherwise
*/
public function isBlocked( $bFromSlave = true ) { // hacked from false due to horrible probs on site
@@ -1565,10 +1566,10 @@ class User {
/**
* Get the block affecting the user, or null if the user is not blocked
*
- * @param $bFromSlave Bool Whether to check the slave database instead of the master
+ * @param bool $bFromSlave Whether to check the slave database instead of the master
* @return Block|null
*/
- public function getBlock( $bFromSlave = true ){
+ public function getBlock( $bFromSlave = true ) {
$this->getBlockedStatus( $bFromSlave );
return $this->mBlock instanceof Block ? $this->mBlock : null;
}
@@ -1577,7 +1578,7 @@ class User {
* Check if user is blocked from editing a particular article
*
* @param $title Title to check
- * @param $bFromSlave Bool whether to check the slave database instead of the master
+ * @param bool $bFromSlave whether to check the slave database instead of the master
* @return Bool
*/
function isBlockedFrom( $title, $bFromSlave = false ) {
@@ -1629,9 +1630,9 @@ class User {
/**
* Check if user is blocked on all wikis.
* Do not use for actual edit permission checks!
- * This is intented for quick UI checks.
+ * This is intended for quick UI checks.
*
- * @param $ip String IP address, uses current client if none given
+ * @param string $ip IP address, uses current client if none given
* @return Bool True if blocked, false otherwise
*/
public function isBlockedGlobally( $ip = '' ) {
@@ -1701,7 +1702,7 @@ class User {
/**
* Set the user and reload all fields according to a given ID
- * @param $v Int User ID to reload
+ * @param int $v User ID to reload
*/
public function setId( $v ) {
$this->mId = $v;
@@ -1735,9 +1736,9 @@ class User {
* address for an anonymous user to something other than the current
* remote IP.
*
- * @note User::newFromName() has rougly the same function, when the named user
+ * @note User::newFromName() has roughly the same function, when the named user
* does not exist.
- * @param $str String New user name to set
+ * @param string $str New user name to set
*/
public function setName( $str ) {
$this->load();
@@ -1817,9 +1818,9 @@ class User {
* Internal uncached check for new messages
*
* @see getNewtalk()
- * @param $field String 'user_ip' for anonymous users, 'user_id' otherwise
- * @param $id String|Int User's IP address for anonymous users, User ID otherwise
- * @param $fromMaster Bool true to fetch from the master, false for a slave
+ * @param string $field 'user_ip' for anonymous users, 'user_id' otherwise
+ * @param string|Int $id User's IP address for anonymous users, User ID otherwise
+ * @param bool $fromMaster true to fetch from the master, false for a slave
* @return Bool True if the user has new messages
*/
protected function checkNewtalk( $field, $id, $fromMaster = false ) {
@@ -1835,8 +1836,8 @@ class User {
/**
* Add or update the new messages flag
- * @param $field String 'user_ip' for anonymous users, 'user_id' otherwise
- * @param $id String|Int User's IP address for anonymous users, User ID otherwise
+ * @param string $field 'user_ip' for anonymous users, 'user_id' otherwise
+ * @param string|Int $id User's IP address for anonymous users, User ID otherwise
* @param $curRev Revision new, as yet unseen revision of the user talk page. Ignored if null.
* @return Bool True if successful, false otherwise
*/
@@ -1861,8 +1862,8 @@ class User {
/**
* Clear the new messages flag for the given user
- * @param $field String 'user_ip' for anonymous users, 'user_id' otherwise
- * @param $id String|Int User's IP address for anonymous users, User ID otherwise
+ * @param string $field 'user_ip' for anonymous users, 'user_id' otherwise
+ * @param string|Int $id User's IP address for anonymous users, User ID otherwise
* @return Bool True if successful, false otherwise
*/
protected function deleteNewtalk( $field, $id ) {
@@ -1881,7 +1882,7 @@ class User {
/**
* Update the 'You have new messages!' status.
- * @param $val Bool Whether the user has new messages
+ * @param bool $val Whether the user has new messages
* @param $curRev Revision new, as yet unseen revision of the user talk page. Ignored if null or !$val.
*/
public function setNewtalk( $val, $curRev = null ) {
@@ -1977,7 +1978,7 @@ class User {
/**
* Validate the cache for this account.
- * @param $timestamp String A timestamp in TS_MW format
+ * @param string $timestamp A timestamp in TS_MW format
*
* @return bool
*/
@@ -2006,7 +2007,7 @@ class User {
* wipes it, so the account cannot be logged in until
* a new password is set, for instance via e-mail.
*
- * @param $str String New password to set
+ * @param string $str New password to set
* @throws PasswordError on failure
*
* @return bool
@@ -2045,7 +2046,9 @@ class User {
/**
* Set the password and reset the random token unconditionally.
*
- * @param $str String New password to set
+ * @param string|null $str New password to set or null to set an invalid
+ * password hash meaning that the user will not be able to log in
+ * through the web interface.
*/
public function setInternalPassword( $str ) {
$this->load();
@@ -2063,7 +2066,7 @@ class User {
/**
* Get the user's current token.
- * @param $forceCreation Force the generation of a new token if the user doesn't have one (default=true for backwards compatibility)
+ * @param bool $forceCreation Force the generation of a new token if the user doesn't have one (default=true for backwards compatibility)
* @return String Token
*/
public function getToken( $forceCreation = true ) {
@@ -2078,7 +2081,7 @@ class User {
* Set the random token (used for persistent authentication)
* Called from loadDefaults() among other places.
*
- * @param $token String|bool If specified, set the token to this value
+ * @param string|bool $token If specified, set the token to this value
*/
public function setToken( $token = false ) {
$this->load();
@@ -2092,8 +2095,8 @@ class User {
/**
* Set the password for a password reminder or new account email
*
- * @param $str String New password to set
- * @param $throttle Bool If true, reset the throttle timestamp to the present
+ * @param string $str New password to set
+ * @param bool $throttle If true, reset the throttle timestamp to the present
*/
public function setNewpassword( $str, $throttle = true ) {
$this->load();
@@ -2140,7 +2143,7 @@ class User {
/**
* Set the user's e-mail address
- * @param $str String New e-mail address
+ * @param string $str New e-mail address
*/
public function setEmail( $str ) {
$this->load();
@@ -2156,7 +2159,7 @@ class User {
* Set the user's e-mail address and a confirmation mail if needed.
*
* @since 1.20
- * @param $str String New e-mail address
+ * @param string $str New e-mail address
* @return Status
*/
public function setEmailWithConfirmation( $str ) {
@@ -2202,7 +2205,7 @@ class User {
/**
* Set the user's real name
- * @param $str String New real name
+ * @param string $str New real name
*/
public function setRealName( $str ) {
$this->load();
@@ -2212,9 +2215,9 @@ class User {
/**
* Get the user's current setting for a given option.
*
- * @param $oname String The option to check
- * @param $defaultOverride String A default value returned if the option does not exist
- * @param $ignoreHidden Bool = whether to ignore the effects of $wgHiddenPrefs
+ * @param string $oname The option to check
+ * @param string $defaultOverride A default value returned if the option does not exist
+ * @param bool $ignoreHidden = whether to ignore the effects of $wgHiddenPrefs
* @return String User's current value for the option
* @see getBoolOption()
* @see getIntOption()
@@ -2223,19 +2226,12 @@ class User {
global $wgHiddenPrefs;
$this->loadOptions();
- if ( is_null( $this->mOptions ) ) {
- if($defaultOverride != '') {
- return $defaultOverride;
- }
- $this->mOptions = User::getDefaultOptions();
- }
-
# We want 'disabled' preferences to always behave as the default value for
# users, even if they have set the option explicitly in their settings (ie they
# set it, and then it was disabled removing their ability to change it). But
# we don't want to erase the preferences in the database in case the preference
# is re-enabled again. So don't touch $mOptions, just override the returned value
- if( in_array( $oname, $wgHiddenPrefs ) && !$ignoreHidden ){
+ if( in_array( $oname, $wgHiddenPrefs ) && !$ignoreHidden ) {
return self::getDefaultOption( $oname );
}
@@ -2261,9 +2257,9 @@ class User {
# set it, and then it was disabled removing their ability to change it). But
# we don't want to erase the preferences in the database in case the preference
# is re-enabled again. So don't touch $mOptions, just override the returned value
- foreach( $wgHiddenPrefs as $pref ){
+ foreach( $wgHiddenPrefs as $pref ) {
$default = self::getDefaultOption( $pref );
- if( $default !== null ){
+ if( $default !== null ) {
$options[$pref] = $default;
}
}
@@ -2274,7 +2270,7 @@ class User {
/**
* Get the user's current setting for a given option, as a boolean value.
*
- * @param $oname String The option to check
+ * @param string $oname The option to check
* @return Bool User's current value for the option
* @see getOption()
*/
@@ -2285,12 +2281,12 @@ class User {
/**
* Get the user's current setting for a given option, as a boolean value.
*
- * @param $oname String The option to check
- * @param $defaultOverride Int A default value returned if the option does not exist
+ * @param string $oname The option to check
+ * @param int $defaultOverride A default value returned if the option does not exist
* @return Int User's current value for the option
* @see getOption()
*/
- public function getIntOption( $oname, $defaultOverride=0 ) {
+ public function getIntOption( $oname, $defaultOverride = 0 ) {
$val = $this->getOption( $oname );
if( $val == '' ) {
$val = $defaultOverride;
@@ -2301,31 +2297,173 @@ class User {
/**
* Set the given option for a user.
*
- * @param $oname String The option to set
+ * @param string $oname The option to set
* @param $val mixed New value to set
*/
public function setOption( $oname, $val ) {
- $this->load();
$this->loadOptions();
// Explicitly NULL values should refer to defaults
if( is_null( $val ) ) {
- $defaultOption = self::getDefaultOption( $oname );
- if( !is_null( $defaultOption ) ) {
- $val = $defaultOption;
- }
+ $val = self::getDefaultOption( $oname );
}
$this->mOptions[$oname] = $val;
}
/**
- * Reset all options to the site defaults
+ * Return a list of the types of user options currently returned by
+ * User::getOptionKinds().
+ *
+ * Currently, the option kinds are:
+ * - 'registered' - preferences which are registered in core MediaWiki or
+ * by extensions using the UserGetDefaultOptions hook.
+ * - 'registered-multiselect' - as above, using the 'multiselect' type.
+ * - 'registered-checkmatrix' - as above, using the 'checkmatrix' type.
+ * - 'userjs' - preferences with names starting with 'userjs-', intended to
+ * be used by user scripts.
+ * - 'unused' - preferences about which MediaWiki doesn't know anything.
+ * These are usually legacy options, removed in newer versions.
+ *
+ * The API (and possibly others) use this function to determine the possible
+ * option types for validation purposes, so make sure to update this when a
+ * new option kind is added.
+ *
+ * @see User::getOptionKinds
+ * @return array Option kinds
+ */
+ public static function listOptionKinds() {
+ return array(
+ 'registered',
+ 'registered-multiselect',
+ 'registered-checkmatrix',
+ 'userjs',
+ 'unused'
+ );
+ }
+
+ /**
+ * Return an associative array mapping preferences keys to the kind of a preference they're
+ * used for. Different kinds are handled differently when setting or reading preferences.
+ *
+ * See User::listOptionKinds for the list of valid option types that can be provided.
+ *
+ * @see User::listOptionKinds
+ * @param $context IContextSource
+ * @param array $options assoc. array with options keys to check as keys. Defaults to $this->mOptions.
+ * @return array the key => kind mapping data
*/
- public function resetOptions() {
+ public function getOptionKinds( IContextSource $context, $options = null ) {
+ $this->loadOptions();
+ if ( $options === null ) {
+ $options = $this->mOptions;
+ }
+
+ $prefs = Preferences::getPreferences( $this, $context );
+ $mapping = array();
+
+ // Multiselect and checkmatrix options are stored in the database with
+ // one key per option, each having a boolean value. Extract those keys.
+ $multiselectOptions = array();
+ foreach ( $prefs as $name => $info ) {
+ if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
+ ( isset( $info['class'] ) && $info['class'] == 'HTMLMultiSelectField' ) ) {
+ $opts = HTMLFormField::flattenOptions( $info['options'] );
+ $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
+
+ foreach ( $opts as $value ) {
+ $multiselectOptions["$prefix$value"] = true;
+ }
+
+ unset( $prefs[$name] );
+ }
+ }
+ $checkmatrixOptions = array();
+ foreach ( $prefs as $name => $info ) {
+ if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
+ ( isset( $info['class'] ) && $info['class'] == 'HTMLCheckMatrix' ) ) {
+ $columns = HTMLFormField::flattenOptions( $info['columns'] );
+ $rows = HTMLFormField::flattenOptions( $info['rows'] );
+ $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
+
+ foreach ( $columns as $column ) {
+ foreach ( $rows as $row ) {
+ $checkmatrixOptions["$prefix-$column-$row"] = true;
+ }
+ }
+
+ unset( $prefs[$name] );
+ }
+ }
+
+ // $value is ignored
+ foreach ( $options as $key => $value ) {
+ if ( isset( $prefs[$key] ) ) {
+ $mapping[$key] = 'registered';
+ } elseif( isset( $multiselectOptions[$key] ) ) {
+ $mapping[$key] = 'registered-multiselect';
+ } elseif( isset( $checkmatrixOptions[$key] ) ) {
+ $mapping[$key] = 'registered-checkmatrix';
+ } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
+ $mapping[$key] = 'userjs';
+ } else {
+ $mapping[$key] = 'unused';
+ }
+ }
+
+ return $mapping;
+ }
+
+ /**
+ * Reset certain (or all) options to the site defaults
+ *
+ * The optional parameter determines which kinds of preferences will be reset.
+ * Supported values are everything that can be reported by getOptionKinds()
+ * and 'all', which forces a reset of *all* preferences and overrides everything else.
+ *
+ * @param array|string $resetKinds which kinds of preferences to reset. Defaults to
+ * array( 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' )
+ * for backwards-compatibility.
+ * @param $context IContextSource|null context source used when $resetKinds
+ * does not contain 'all', passed to getOptionKinds().
+ * Defaults to RequestContext::getMain() when null.
+ */
+ public function resetOptions(
+ $resetKinds = array( 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ),
+ IContextSource $context = null
+ ) {
$this->load();
+ $defaultOptions = self::getDefaultOptions();
- $this->mOptions = self::getDefaultOptions();
+ if ( !is_array( $resetKinds ) ) {
+ $resetKinds = array( $resetKinds );
+ }
+
+ if ( in_array( 'all', $resetKinds ) ) {
+ $newOptions = $defaultOptions;
+ } else {
+ if ( $context === null ) {
+ $context = RequestContext::getMain();
+ }
+
+ $optionKinds = $this->getOptionKinds( $context );
+ $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
+ $newOptions = array();
+
+ // Use default values for the options that should be deleted, and
+ // copy old values for the ones that shouldn't.
+ foreach ( $this->mOptions as $key => $value ) {
+ if ( in_array( $optionKinds[$key], $resetKinds ) ) {
+ if ( array_key_exists( $key, $defaultOptions ) ) {
+ $newOptions[$key] = $defaultOptions[$key];
+ }
+ } else {
+ $newOptions[$key] = $value;
+ }
+ }
+ }
+
+ $this->mOptions = $newOptions;
$this->mOptionsLoaded = true;
}
@@ -2354,7 +2492,7 @@ class User {
*/
public function getStubThreshold() {
global $wgMaxArticleSize; # Maximum article size, in Kb
- $threshold = intval( $this->getOption( 'stubthreshold' ) );
+ $threshold = $this->getIntOption( 'stubthreshold' );
if ( $threshold > $wgMaxArticleSize * 1024 ) {
# If they have set an impossible value, disable the preference
# so we can use the parser cache again.
@@ -2372,7 +2510,7 @@ class User {
$this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
wfRunHooks( 'UserGetRights', array( $this, &$this->mRights ) );
// Force reindexation of rights when a hook has unset one of them
- $this->mRights = array_values( $this->mRights );
+ $this->mRights = array_values( array_unique( $this->mRights ) );
}
return $this->mRights;
}
@@ -2392,7 +2530,7 @@ class User {
* Get the list of implicit group memberships this user has.
* This includes all explicit groups, plus 'user' if logged in,
* '*' for all accounts, and autopromoted groups
- * @param $recache Bool Whether to avoid the cache
+ * @param bool $recache Whether to avoid the cache
* @return Array of String internal group names
*/
public function getEffectiveGroups( $recache = false ) {
@@ -2404,6 +2542,8 @@ class User {
) );
# Hook for additional groups
wfRunHooks( 'UserEffectiveGroups', array( &$this, &$this->mEffectiveGroups ) );
+ // Force reindexation of groups when a hook has unset one of them
+ $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
wfProfileOut( __METHOD__ );
}
return $this->mEffectiveGroups;
@@ -2413,7 +2553,7 @@ class User {
* Get the list of implicit group memberships this user has.
* This includes 'user' if logged in, '*' for all accounts,
* and autopromoted groups
- * @param $recache Bool Whether to avoid the cache
+ * @param bool $recache Whether to avoid the cache
* @return Array of String internal group names
*/
public function getAutomaticGroups( $recache = false ) {
@@ -2467,22 +2607,35 @@ class User {
* @return Int
*/
public function getEditCount() {
- if( $this->getId() ) {
- if ( !isset( $this->mEditCount ) ) {
- /* Populate the count, if it has not been populated yet */
- $this->mEditCount = User::edits( $this->mId );
- }
- return $this->mEditCount;
- } else {
- /* nil */
+ if ( !$this->getId() ) {
return null;
}
+
+ if ( !isset( $this->mEditCount ) ) {
+ /* Populate the count, if it has not been populated yet */
+ wfProfileIn( __METHOD__ );
+ $dbr = wfGetDB( DB_SLAVE );
+ // check if the user_editcount field has been initialized
+ $count = $dbr->selectField(
+ 'user', 'user_editcount',
+ array( 'user_id' => $this->mId ),
+ __METHOD__
+ );
+
+ if( $count === null ) {
+ // it has not been initialized. do so.
+ $count = $this->initEditCount();
+ }
+ $this->mEditCount = intval( $count );
+ wfProfileOut( __METHOD__ );
+ }
+ return $this->mEditCount;
}
/**
* Add the user to the given group.
* This takes immediate effect.
- * @param $group String Name of the group to add
+ * @param string $group Name of the group to add
*/
public function addGroup( $group ) {
if( wfRunHooks( 'UserAddGroup', array( $this, &$group ) ) ) {
@@ -2490,7 +2643,7 @@ class User {
if( $this->getId() ) {
$dbw->insert( 'user_groups',
array(
- 'ug_user' => $this->getID(),
+ 'ug_user' => $this->getID(),
'ug_group' => $group,
),
__METHOD__,
@@ -2507,7 +2660,7 @@ class User {
/**
* Remove the user from the given group.
* This takes immediate effect.
- * @param $group String Name of the group to remove
+ * @param string $group Name of the group to remove
*/
public function removeGroup( $group ) {
$this->load();
@@ -2515,13 +2668,13 @@ class User {
$dbw = wfGetDB( DB_MASTER );
$dbw->delete( 'user_groups',
array(
- 'ug_user' => $this->getID(),
+ 'ug_user' => $this->getID(),
'ug_group' => $group,
), __METHOD__ );
// Remember that the user was in this group
$dbw->insert( 'user_former_groups',
array(
- 'ufg_user' => $this->getID(),
+ 'ufg_user' => $this->getID(),
'ufg_group' => $group,
),
__METHOD__,
@@ -2558,10 +2711,10 @@ class User {
*
* @return bool
*/
- public function isAllowedAny( /*...*/ ){
+ public function isAllowedAny( /*...*/ ) {
$permissions = func_get_args();
- foreach( $permissions as $permission ){
- if( $this->isAllowed( $permission ) ){
+ foreach( $permissions as $permission ) {
+ if( $this->isAllowed( $permission ) ) {
return true;
}
}
@@ -2573,10 +2726,10 @@ class User {
* @internal param $varargs string
* @return bool True if the user is allowed to perform *all* of the given actions
*/
- public function isAllowedAll( /*...*/ ){
+ public function isAllowedAll( /*...*/ ) {
$permissions = func_get_args();
- foreach( $permissions as $permission ){
- if( !$this->isAllowed( $permission ) ){
+ foreach( $permissions as $permission ) {
+ if( !$this->isAllowed( $permission ) ) {
return false;
}
}
@@ -2744,13 +2897,17 @@ class User {
* the next change of any watched page.
*/
public function clearAllNotifications() {
+ if ( wfReadOnly() ) {
+ return;
+ }
+
global $wgUseEnotif, $wgShowUpdatedMarker;
if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
$this->setNewtalk( false );
return;
}
$id = $this->getId();
- if( $id != 0 ) {
+ if( $id != 0 ) {
$dbw = wfGetDB( DB_MASTER );
$dbw->update( 'watchlist',
array( /* SET */
@@ -2766,7 +2923,7 @@ class User {
/**
* Set this user's options from an encoded string
- * @param $str String Encoded options to import
+ * @param string $str Encoded options to import
*
* @deprecated in 1.19 due to removal of user_options from the user table
*/
@@ -2794,18 +2951,22 @@ class User {
/**
* Set a cookie on the user's client. Wrapper for
* WebResponse::setCookie
- * @param $name String Name of the cookie to set
- * @param $value String Value to set
- * @param $exp Int Expiration time, as a UNIX time value;
+ * @param string $name Name of the cookie to set
+ * @param string $value Value to set
+ * @param int $exp Expiration time, as a UNIX time value;
* if 0 or not specified, use the default $wgCookieExpiration
+ * @param $secure Bool
+ * true: Force setting the secure attribute when setting the cookie
+ * false: Force NOT setting the secure attribute when setting the cookie
+ * null (default): Use the default ($wgCookieSecure) to set the secure attribute
*/
- protected function setCookie( $name, $value, $exp = 0 ) {
- $this->getRequest()->response()->setcookie( $name, $value, $exp );
+ protected function setCookie( $name, $value, $exp = 0, $secure = null ) {
+ $this->getRequest()->response()->setcookie( $name, $value, $exp, null, null, $secure );
}
/**
* Clear a cookie on the user's client
- * @param $name String Name of the cookie to clear
+ * @param string $name Name of the cookie to clear
*/
protected function clearCookie( $name ) {
$this->setCookie( $name, '', time() - 86400 );
@@ -2816,14 +2977,17 @@ class User {
*
* @param $request WebRequest object to use; $wgRequest will be used if null
* is passed.
+ * @param bool $secure Whether to force secure/insecure cookies or use default
*/
- public function setCookies( $request = null ) {
+ public function setCookies( $request = null, $secure = null ) {
if ( $request === null ) {
$request = $this->getRequest();
}
$this->load();
- if ( 0 == $this->mId ) return;
+ if ( 0 == $this->mId ) {
+ return;
+ }
if ( !$this->mToken ) {
// When token is empty or NULL generate a new one and then save it to the database
// This allows a wiki to re-secure itself after a leak of it's user table or $wgSecretKey
@@ -2856,9 +3020,18 @@ class User {
if ( $value === false ) {
$this->clearCookie( $name );
} else {
- $this->setCookie( $name, $value );
+ $this->setCookie( $name, $value, 0, $secure );
}
}
+
+ /**
+ * If wpStickHTTPS was selected, also set an insecure cookie that
+ * will cause the site to redirect the user to HTTPS, if they access
+ * it over HTTP. Bug 29898.
+ */
+ if ( $request->getCheck( 'wpStickHTTPS' ) ) {
+ $this->setCookie( 'forceHTTPS', 'true', time() + 2592000, false ); //30 days
+ }
}
/**
@@ -2881,6 +3054,7 @@ class User {
$this->clearCookie( 'UserID' );
$this->clearCookie( 'Token' );
+ $this->clearCookie( 'forceHTTPS' );
# Remember when user logged out, to prevent seeing cached pages
$this->setCookie( 'LoggedOut', wfTimestampNow(), time() + 86400 );
@@ -2947,8 +3121,8 @@ class User {
/**
* Add a user to the database, return the user object
*
- * @param $name String Username to add
- * @param $params Array of Strings Non-default parameters to save to the database as user_* fields:
+ * @param string $name Username to add
+ * @param array $params of Strings Non-default parameters to save to the database as user_* fields:
* - password The user's password hash. Password logins will be disabled if this is omitted.
* - newpassword Hash for a temporary password that has been mailed to the user
* - email The user's email address
@@ -2963,6 +3137,7 @@ class User {
public static function createNew( $name, $params = array() ) {
$user = new User;
$user->load();
+ $user->setToken(); // init token
if ( isset( $params['options'] ) ) {
$user->mOptions = $params['options'] + (array)$user->mOptions;
unset( $params['options'] );
@@ -2997,10 +3172,36 @@ class User {
}
/**
- * Add this existing user object to the database
+ * Add this existing user object to the database. If the user already
+ * exists, a fatal status object is returned, and the user object is
+ * initialised with the data from the database.
+ *
+ * Previously, this function generated a DB error due to a key conflict
+ * if the user already existed. Many extension callers use this function
+ * in code along the lines of:
+ *
+ * $user = User::newFromName( $name );
+ * if ( !$user->isLoggedIn() ) {
+ * $user->addToDatabase();
+ * }
+ * // do something with $user...
+ *
+ * However, this was vulnerable to a race condition (bug 16020). By
+ * initialising the user object if the user exists, we aim to support this
+ * calling sequence as far as possible.
+ *
+ * Note that if the user exists, this function will acquire a write lock,
+ * so it is still advisable to make the call conditional on isLoggedIn(),
+ * and to commit the transaction after calling.
+ *
+ * @throws MWException
+ * @return Status
*/
public function addToDatabase() {
$this->load();
+ if ( !$this->mToken ) {
+ $this->setToken(); // init token
+ }
$this->mTouched = self::newTouchedTimestamp();
@@ -3020,14 +3221,31 @@ class User {
'user_registration' => $dbw->timestamp( $this->mRegistration ),
'user_editcount' => 0,
'user_touched' => $dbw->timestamp( $this->mTouched ),
- ), __METHOD__
+ ), __METHOD__,
+ array( 'IGNORE' )
);
+ if ( !$dbw->affectedRows() ) {
+ $this->mId = $dbw->selectField( 'user', 'user_id',
+ array( 'user_name' => $this->mName ), __METHOD__ );
+ $loaded = false;
+ if ( $this->mId ) {
+ if ( $this->loadFromDatabase() ) {
+ $loaded = true;
+ }
+ }
+ if ( !$loaded ) {
+ throw new MWException( __METHOD__. ": hit a key conflict attempting " .
+ "to insert a user row, but then it doesn't exist when we select it!" );
+ }
+ return Status::newFatal( 'userexists' );
+ }
$this->mId = $dbw->insertId();
// Clear instance cache other than user table data, which is already accurate
$this->clearInstanceCache();
$this->saveOptions();
+ return Status::newGood();
}
/**
@@ -3079,8 +3297,8 @@ class User {
public function getPageRenderingHash() {
wfDeprecated( __METHOD__, '1.17' );
- global $wgUseDynamicDates, $wgRenderHashAppend, $wgLang, $wgContLang;
- if( $this->mHash ){
+ global $wgRenderHashAppend, $wgLang, $wgContLang;
+ if( $this->mHash ) {
return $this->mHash;
}
@@ -3088,11 +3306,8 @@ class User {
// since it disables the parser cache, its value will always
// be 0 when this function is called by parsercache.
- $confstr = $this->getOption( 'math' );
+ $confstr = $this->getOption( 'math' );
$confstr .= '!' . $this->getStubThreshold();
- if ( $wgUseDynamicDates ) { # This is wrong (bug 24714)
- $confstr .= '!' . $this->getDatePreference();
- }
$confstr .= '!' . ( $this->getOption( 'numberheadings' ) ? '1' : '' );
$confstr .= '!' . $wgLang->getCode();
$confstr .= '!' . $this->getOption( 'thumbsize' );
@@ -3121,14 +3336,14 @@ class User {
*/
public function isBlockedFromCreateAccount() {
$this->getBlockedStatus();
- if( $this->mBlock && $this->mBlock->prevents( 'createaccount' ) ){
+ if( $this->mBlock && $this->mBlock->prevents( 'createaccount' ) ) {
return $this->mBlock;
}
# bug 13611: if the IP address the user is trying to create an account from is
# blocked with createaccount disabled, prevent new account creation there even
# when the user is logged in
- if( $this->mBlockedFromCreateAccount === false ){
+ if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
$this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
}
return $this->mBlockedFromCreateAccount instanceof Block && $this->mBlockedFromCreateAccount->prevents( 'createaccount' )
@@ -3183,7 +3398,7 @@ class User {
/**
* Check to see if the given clear-text password is one of the accepted passwords
- * @param $password String: user password.
+ * @param string $password user password.
* @return Boolean: True if the given password is correct, otherwise False.
*/
public function checkPassword( $password ) {
@@ -3250,7 +3465,7 @@ class User {
* Alias for getEditToken.
* @deprecated since 1.19, use getEditToken instead.
*
- * @param $salt String|Array of Strings Optional function-specific data for hashing
+ * @param string|array $salt of Strings Optional function-specific data for hashing
* @param $request WebRequest object to use or null to use $wgRequest
* @return String The new edit token
*/
@@ -3267,7 +3482,7 @@ class User {
*
* @since 1.19
*
- * @param $salt String|Array of Strings Optional function-specific data for hashing
+ * @param string|array $salt of Strings Optional function-specific data for hashing
* @param $request WebRequest object to use or null to use $wgRequest
* @return String The new edit token
*/
@@ -3294,11 +3509,10 @@ class User {
/**
* Generate a looking random token for various uses.
*
- * @param $salt String Optional salt value
* @return String The new random token
- * @deprecated since 1.20; Use MWCryptRand for secure purposes or wfRandomString for pesudo-randomness
+ * @deprecated since 1.20; Use MWCryptRand for secure purposes or wfRandomString for pseudo-randomness
*/
- public static function generateToken( $salt = '' ) {
+ public static function generateToken() {
return MWCryptRand::generateHex( 32 );
}
@@ -3308,8 +3522,8 @@ class User {
* user's own login session, not a form submission from a third-party
* site.
*
- * @param $val String Input value to compare
- * @param $salt String Optional function-specific data for hashing
+ * @param string $val Input value to compare
+ * @param string $salt Optional function-specific data for hashing
* @param $request WebRequest object to use or null to use $wgRequest
* @return Boolean: Whether the token matches
*/
@@ -3325,8 +3539,8 @@ class User {
* Check given value against the token value stored in the session,
* ignoring the suffix.
*
- * @param $val String Input value to compare
- * @param $salt String Optional function-specific data for hashing
+ * @param string $val Input value to compare
+ * @param string $salt Optional function-specific data for hashing
* @param $request WebRequest object to use or null to use $wgRequest
* @return Boolean: Whether the token matches
*/
@@ -3339,7 +3553,7 @@ class User {
* Generate a new e-mail confirmation token and send a confirmation/invalidation
* mail to the user's given address.
*
- * @param $type String: message to send, either "created", "changed" or "set"
+ * @param string $type message to send, either "created", "changed" or "set"
* @return Status object
*/
public function sendConfirmationMail( $type = 'created' ) {
@@ -3373,10 +3587,10 @@ class User {
* Send an e-mail to this user's account. Does not check for
* confirmed status or validity.
*
- * @param $subject String Message subject
- * @param $body String Message body
- * @param $from String Optional From address; if unspecified, default $wgPasswordSender will be used
- * @param $replyto String Reply-To address
+ * @param string $subject Message subject
+ * @param string $body Message body
+ * @param string $from Optional From address; if unspecified, default $wgPasswordSender will be used
+ * @param string $replyto Reply-To address
* @return Status
*/
public function sendMail( $subject, $body, $from = null, $replyto = null ) {
@@ -3415,8 +3629,8 @@ class User {
}
/**
- * Return a URL the user can use to confirm their email address.
- * @param $token String Accepts the email confirmation token
+ * Return a URL the user can use to confirm their email address.
+ * @param string $token Accepts the email confirmation token
* @return String New token URL
*/
private function confirmationTokenUrl( $token ) {
@@ -3425,7 +3639,7 @@ class User {
/**
* Return a URL the user can use to invalidate their email address.
- * @param $token String Accepts the email confirmation token
+ * @param string $token Accepts the email confirmation token
* @return String New token URL
*/
private function invalidationTokenUrl( $token ) {
@@ -3442,8 +3656,8 @@ class User {
* also sometimes can get corrupted in some browsers/mailers
* (bug 6957 with Gmail and Internet Explorer).
*
- * @param $page String Special page
- * @param $token String Token
+ * @param string $page Special page
+ * @param string $token Token
* @return String Formatted URL
*/
protected function getTokenUrl( $page, $token ) {
@@ -3483,7 +3697,7 @@ class User {
/**
* Set the e-mail authentication timestamp.
- * @param $timestamp String TS_MW timestamp
+ * @param string $timestamp TS_MW timestamp
*/
function setEmailAuthenticationTimestamp( $timestamp ) {
$this->load();
@@ -3560,8 +3774,9 @@ class User {
/**
* Get the timestamp of account creation.
*
- * @return String|Bool Timestamp of account creation, or false for
- * non-existent/anonymous user accounts.
+ * @return String|Bool|Null Timestamp of account creation, false for
+ * non-existent/anonymous user accounts, or null if existing account
+ * but information is not in database.
*/
public function getRegistration() {
if ( $this->isAnon() ) {
@@ -3596,7 +3811,7 @@ class User {
/**
* Get the permissions associated with a given list of groups
*
- * @param $groups Array of Strings List of internal group names
+ * @param array $groups of Strings List of internal group names
* @return Array of Strings List of permission key names for given groups combined
*/
public static function getGroupPermissions( $groups ) {
@@ -3623,14 +3838,14 @@ class User {
/**
* Get all the groups who have a given permission
*
- * @param $role String Role to check
+ * @param string $role Role to check
* @return Array of Strings List of internal group names with the given permission
*/
public static function getGroupsWithPermission( $role ) {
global $wgGroupPermissions;
$allowedGroups = array();
- foreach ( $wgGroupPermissions as $group => $rights ) {
- if ( isset( $rights[$role] ) && $rights[$role] ) {
+ foreach ( array_keys( $wgGroupPermissions ) as $group ) {
+ if ( self::groupHasPermission( $group, $role ) ) {
$allowedGroups[] = $group;
}
}
@@ -3638,9 +3853,22 @@ class User {
}
/**
+ * Check, if the given group has the given permission
+ *
+ * @param string $group Group to check
+ * @param string $role Role to check
+ * @return bool
+ */
+ public static function groupHasPermission( $group, $role ) {
+ global $wgGroupPermissions, $wgRevokePermissions;
+ return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
+ && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
+ }
+
+ /**
* Get the localized descriptive name for a group, if it exists
*
- * @param $group String Internal group name
+ * @param string $group Internal group name
* @return String Localized descriptive group name
*/
public static function getGroupName( $group ) {
@@ -3651,8 +3879,8 @@ class User {
/**
* Get the localized descriptive name for a member of a group, if it exists
*
- * @param $group String Internal group name
- * @param $username String Username for gender (since 1.19)
+ * @param string $group Internal group name
+ * @param string $username Username for gender (since 1.19)
* @return String Localized name for group member
*/
public static function getGroupMember( $group, $username = '#' ) {
@@ -3705,7 +3933,7 @@ class User {
/**
* Get the title of a page describing a particular group
*
- * @param $group String Internal group name
+ * @param string $group Internal group name
* @return Title|Bool Title of the page if it exists, false otherwise
*/
public static function getGroupPage( $group ) {
@@ -3722,8 +3950,8 @@ class User {
* Create a link to the group in HTML, if available;
* else return the group name.
*
- * @param $group String Internal name of the group
- * @param $text String The text of the link
+ * @param string $group Internal name of the group
+ * @param string $text The text of the link
* @return String HTML link to the group
*/
public static function makeGroupLinkHTML( $group, $text = '' ) {
@@ -3742,8 +3970,8 @@ class User {
* Create a link to the group in Wikitext, if available;
* else return the group name.
*
- * @param $group String Internal name of the group
- * @param $text String The text of the link
+ * @param string $group Internal name of the group
+ * @param string $text The text of the link
* @return String Wikilink to the group
*/
public static function makeGroupLinkWiki( $group, $text = '' ) {
@@ -3762,7 +3990,7 @@ class User {
/**
* Returns an array of the groups that a particular group can add/remove.
*
- * @param $group String: the group to check for whether it can add/remove
+ * @param string $group the group to check for whether it can add/remove
* @return Array array( 'add' => array( addablegroups ),
* 'remove' => array( removablegroups ),
* 'add-self' => array( addablegroups to self),
@@ -3860,7 +4088,7 @@ class User {
$groups = array_merge_recursive(
$groups, $this->changeableByGroup( $addergroup )
);
- $groups['add'] = array_unique( $groups['add'] );
+ $groups['add'] = array_unique( $groups['add'] );
$groups['remove'] = array_unique( $groups['remove'] );
$groups['add-self'] = array_unique( $groups['add-self'] );
$groups['remove-self'] = array_unique( $groups['remove-self'] );
@@ -3875,37 +4103,28 @@ class User {
public function incEditCount() {
if( !$this->isAnon() ) {
$dbw = wfGetDB( DB_MASTER );
- $dbw->update( 'user',
+ $dbw->update(
+ 'user',
array( 'user_editcount=user_editcount+1' ),
array( 'user_id' => $this->getId() ),
- __METHOD__ );
+ __METHOD__
+ );
// Lazy initialization check...
if( $dbw->affectedRows() == 0 ) {
- // Pull from a slave to be less cruel to servers
- // Accuracy isn't the point anyway here
- $dbr = wfGetDB( DB_SLAVE );
- $count = $dbr->selectField( 'revision',
- 'COUNT(rev_user)',
- array( 'rev_user' => $this->getId() ),
- __METHOD__ );
-
// Now here's a goddamn hack...
+ $dbr = wfGetDB( DB_SLAVE );
if( $dbr !== $dbw ) {
// If we actually have a slave server, the count is
// at least one behind because the current transaction
// has not been committed and replicated.
- $count++;
+ $this->initEditCount( 1 );
} else {
// But if DB_SLAVE is selecting the master, then the
// count we just read includes the revision that was
// just added in the working transaction.
+ $this->initEditCount();
}
-
- $dbw->update( 'user',
- array( 'user_editcount' => $count ),
- array( 'user_id' => $this->getId() ),
- __METHOD__ );
}
}
// edit count in user cache too
@@ -3913,9 +4132,38 @@ class User {
}
/**
+ * Initialize user_editcount from data out of the revision table
+ *
+ * @param $add Integer Edits to add to the count from the revision table
+ * @return Integer Number of edits
+ */
+ protected function initEditCount( $add = 0 ) {
+ // Pull from a slave to be less cruel to servers
+ // Accuracy isn't the point anyway here
+ $dbr = wfGetDB( DB_SLAVE );
+ $count = (int) $dbr->selectField(
+ 'revision',
+ 'COUNT(rev_user)',
+ array( 'rev_user' => $this->getId() ),
+ __METHOD__
+ );
+ $count = $count + $add;
+
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update(
+ 'user',
+ array( 'user_editcount' => $count ),
+ array( 'user_id' => $this->getId() ),
+ __METHOD__
+ );
+
+ return $count;
+ }
+
+ /**
* Get the description of a given right
*
- * @param $right String Right to query
+ * @param string $right Right to query
* @return String Localized description of the right
*/
public static function getRightDescription( $right ) {
@@ -3927,8 +4175,8 @@ class User {
/**
* Make an old-style password hash
*
- * @param $password String Plain-text password
- * @param $userId String User ID
+ * @param string $password Plain-text password
+ * @param string $userId User ID
* @return String Password hash
*/
public static function oldCrypt( $password, $userId ) {
@@ -3943,7 +4191,7 @@ class User {
/**
* Make a new-style password hash
*
- * @param $password String Plain-text password
+ * @param string $password Plain-text password
* @param bool|string $salt Optional salt, may be random or the user ID.
* If unspecified or false, will generate one automatically
@@ -3971,9 +4219,9 @@ class User {
* Compare a password hash with a plain-text password. Requires the user
* ID if there's a chance that the hash is an old-style hash.
*
- * @param $hash String Password hash
- * @param $password String Plain-text password to compare
- * @param $userId String|bool User ID for old-style password salt
+ * @param string $hash Password hash
+ * @param string $password Plain-text password to compare
+ * @param string|bool $userId User ID for old-style password salt
*
* @return Boolean
*/
@@ -3999,67 +4247,104 @@ class User {
}
/**
- * Add a newuser log entry for this user. Before 1.19 the return value was always true.
+ * Add a newuser log entry for this user.
+ * Before 1.19 the return value was always true.
+ *
+ * @param string|bool $action account creation type.
+ * - String, one of the following values:
+ * - 'create' for an anonymous user creating an account for himself.
+ * This will force the action's performer to be the created user itself,
+ * no matter the value of $wgUser
+ * - 'create2' for a logged in user creating an account for someone else
+ * - 'byemail' when the created user will receive its password by e-mail
+ * - Boolean means whether the account was created by e-mail (deprecated):
+ * - true will be converted to 'byemail'
+ * - false will be converted to 'create' if this object is the same as
+ * $wgUser and to 'create2' otherwise
*
- * @param $byEmail Boolean: account made by email?
- * @param $reason String: user supplied reason
+ * @param string $reason user supplied reason
*
* @return int|bool True if not $wgNewUserLog; otherwise ID of log item or 0 on failure
*/
- public function addNewUserLogEntry( $byEmail = false, $reason = '' ) {
- global $wgUser, $wgContLang, $wgNewUserLog;
+ public function addNewUserLogEntry( $action = false, $reason = '' ) {
+ global $wgUser, $wgNewUserLog;
if( empty( $wgNewUserLog ) ) {
return true; // disabled
}
- if( $this->getName() == $wgUser->getName() ) {
- $action = 'create';
- } else {
- $action = 'create2';
- if ( $byEmail ) {
- if ( $reason === '' ) {
- $reason = wfMessage( 'newuserlog-byemail' )->inContentLanguage()->text();
- } else {
- $reason = $wgContLang->commaList( array(
- $reason, wfMessage( 'newuserlog-byemail' )->inContentLanguage()->text() ) );
- }
+ if ( $action === true ) {
+ $action = 'byemail';
+ } elseif ( $action === false ) {
+ if ( $this->getName() == $wgUser->getName() ) {
+ $action = 'create';
+ } else {
+ $action = 'create2';
}
}
- $log = new LogPage( 'newusers' );
- return (int)$log->addEntry(
- $action,
- $this->getUserPage(),
- $reason,
- array( $this->getId() )
- );
+
+ if ( $action === 'create' || $action === 'autocreate' ) {
+ $performer = $this;
+ } else {
+ $performer = $wgUser;
+ }
+
+ $logEntry = new ManualLogEntry( 'newusers', $action );
+ $logEntry->setPerformer( $performer );
+ $logEntry->setTarget( $this->getUserPage() );
+ $logEntry->setComment( $reason );
+ $logEntry->setParameters( array(
+ '4::userid' => $this->getId(),
+ ) );
+ $logid = $logEntry->insert();
+
+ if ( $action !== 'autocreate' ) {
+ $logEntry->publish( $logid );
+ }
+
+ return (int)$logid;
}
/**
* Add an autocreate newuser log entry for this user
* Used by things like CentralAuth and perhaps other authplugins.
+ * Consider calling addNewUserLogEntry() directly instead.
*
* @return bool
*/
public function addNewUserLogEntryAutoCreate() {
- global $wgNewUserLog;
- if( !$wgNewUserLog ) {
- return true; // disabled
- }
- $log = new LogPage( 'newusers', false );
- $log->addEntry( 'autocreate', $this->getUserPage(), '', array( $this->getId() ), $this );
+ $this->addNewUserLogEntry( 'autocreate' );
+
return true;
}
/**
- * @todo document
+ * Load the user options either from cache, the database or an array
+ *
+ * @param array $data Rows for the current user out of the user_properties table
*/
- protected function loadOptions() {
+ protected function loadOptions( $data = null ) {
+ global $wgContLang;
+
$this->load();
- if ( $this->mOptionsLoaded || !$this->getId() )
+
+ if ( $this->mOptionsLoaded ) {
return;
+ }
$this->mOptions = self::getDefaultOptions();
+ if ( !$this->getId() ) {
+ // For unlogged-in users, load language/variant options from request.
+ // There's no need to do it for logged-in users: they can set preferences,
+ // and handling of page content is done by $pageLang->getPreferredVariant() and such,
+ // so don't override user's choice (especially when the user chooses site default).
+ $variant = $wgContLang->getDefaultVariant();
+ $this->mOptions['variant'] = $variant;
+ $this->mOptions['language'] = $variant;
+ $this->mOptionsLoaded = true;
+ return;
+ }
+
// Maybe load from the object
if ( !is_null( $this->mOptionOverrides ) ) {
wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
@@ -4067,21 +4352,27 @@ class User {
$this->mOptions[$key] = $value;
}
} else {
- wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
- // Load from database
- $dbr = wfGetDB( DB_SLAVE );
+ if( !is_array( $data ) ) {
+ wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
+ // Load from database
+ $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select(
- 'user_properties',
- array( 'up_property', 'up_value' ),
- array( 'up_user' => $this->getId() ),
- __METHOD__
- );
+ $res = $dbr->select(
+ 'user_properties',
+ array( 'up_property', 'up_value' ),
+ array( 'up_user' => $this->getId() ),
+ __METHOD__
+ );
- $this->mOptionOverrides = array();
- foreach ( $res as $row ) {
- $this->mOptionOverrides[$row->up_property] = $row->up_value;
- $this->mOptions[$row->up_property] = $row->up_value;
+ $this->mOptionOverrides = array();
+ $data = array();
+ foreach ( $res as $row ) {
+ $data[$row->up_property] = $row->up_value;
+ }
+ }
+ foreach ( $data as $property => $value ) {
+ $this->mOptionOverrides[$property] = $value;
+ $this->mOptions[$property] = $value;
}
}
diff --git a/includes/UserMailer.php b/includes/UserMailer.php
index 01e7132d..6eb99172 100644
--- a/includes/UserMailer.php
+++ b/includes/UserMailer.php
@@ -21,9 +21,9 @@
* @author <brion@pobox.com>
* @author <mail@tgries.de>
* @author Tim Starling
+ * @author Luke Welling lwelling@wikimedia.org
*/
-
/**
* Stores a single person's name and email address.
* These are passed in via the constructor, and will be returned in SMTP
@@ -31,9 +31,9 @@
*/
class MailAddress {
/**
- * @param $address string|User string with an email address, or a User object
- * @param $name String: human-readable name if a string address is given
- * @param $realName String: human-readable real name if a string address is given
+ * @param string|User $address string with an email address, or a User object
+ * @param string $name human-readable name if a string address is given
+ * @param string $realName human-readable real name if a string address is given
*/
function __construct( $address, $name = null, $realName = null ) {
if ( is_object( $address ) && $address instanceof User ) {
@@ -77,7 +77,6 @@ class MailAddress {
}
}
-
/**
* Collection of static functions for sending mail
*/
@@ -109,9 +108,13 @@ class UserMailer {
/**
* Creates a single string from an associative array
*
- * @param $headers array Associative Array: keys are header field names,
+ * @param array $headers Associative Array: keys are header field names,
* values are ... values.
- * @param $endl String: The end of line character. Defaults to "\n"
+ * @param string $endl The end of line character. Defaults to "\n"
+ *
+ * Note RFC2822 says newlines must be CRLF (\r\n)
+ * but php mail naively "corrects" it and requires \n for the "correction" to work
+ *
* @return String
*/
static function arrayToHeaderString( $headers, $endl = "\n" ) {
@@ -131,10 +134,10 @@ class UserMailer {
global $wgSMTP, $wgServer;
$msgid = uniqid( wfWikiID() . ".", true ); /* true required for cygwin */
- if ( is_array($wgSMTP) && isset($wgSMTP['IDHost']) && $wgSMTP['IDHost'] ) {
+ if ( is_array( $wgSMTP ) && isset( $wgSMTP['IDHost'] ) && $wgSMTP['IDHost'] ) {
$domain = $wgSMTP['IDHost'];
} else {
- $url = wfParseUrl($wgServer);
+ $url = wfParseUrl( $wgServer );
$domain = $url['host'];
}
return "<$msgid@$domain>";
@@ -148,19 +151,48 @@ class UserMailer {
*
* @param $to MailAddress: recipient's email (or an array of them)
* @param $from MailAddress: sender's email
- * @param $subject String: email's subject.
- * @param $body String: email's text.
+ * @param string $subject email's subject.
+ * @param string $body email's text or Array of two strings to be the text and html bodies
* @param $replyto MailAddress: optional reply-to email (default: null).
- * @param $contentType String: optional custom Content-Type (default: text/plain; charset=UTF-8)
+ * @param string $contentType optional custom Content-Type (default: text/plain; charset=UTF-8)
+ * @throws MWException
* @return Status object
*/
public static function send( $to, $from, $subject, $body, $replyto = null, $contentType = 'text/plain; charset=UTF-8' ) {
- global $wgSMTP, $wgEnotifMaxRecips, $wgAdditionalMailParams;
-
+ global $wgSMTP, $wgEnotifMaxRecips, $wgAdditionalMailParams, $wgAllowHTMLEmail;
+ $mime = null;
if ( !is_array( $to ) ) {
$to = array( $to );
}
+ // mail body must have some content
+ $minBodyLen = 10;
+ // arbitrary but longer than Array or Object to detect casting error
+
+ // body must either be a string or an array with text and body
+ if (
+ !(
+ !is_array( $body ) &&
+ strlen( $body ) >= $minBodyLen
+ )
+ &&
+ !(
+ is_array( $body ) &&
+ isset( $body['text'] ) &&
+ isset( $body['html'] ) &&
+ strlen( $body['text'] ) >= $minBodyLen &&
+ strlen( $body['html'] ) >= $minBodyLen
+ )
+ ) {
+ // if it is neither we have a problem
+ return Status::newFatal( 'user-mail-no-body' );
+ }
+
+ if ( !$wgAllowHTMLEmail && is_array( $body ) ) {
+ // HTML not wanted. Dump it.
+ $body = $body['text'];
+ }
+
wfDebug( __METHOD__ . ': sending mail to ' . implode( ', ', $to ) . "\n" );
# Make sure we have at least one address
@@ -194,7 +226,7 @@ class UserMailer {
# NOTE: To: is for presentation, the actual recipient is specified
# by the mailer using the Rcpt-To: header.
#
- # Subject:
+ # Subject:
# PHP mail() second argument to pass the subject, passing a Subject
# as an additional header will result in a duplicate header.
#
@@ -210,32 +242,62 @@ class UserMailer {
}
$headers['Date'] = date( 'r' );
- $headers['MIME-Version'] = '1.0';
- $headers['Content-type'] = ( is_null( $contentType ) ?
- 'text/plain; charset=UTF-8' : $contentType );
- $headers['Content-transfer-encoding'] = '8bit';
-
$headers['Message-ID'] = self::makeMsgId();
$headers['X-Mailer'] = 'MediaWiki mailer';
+ # Line endings need to be different on Unix and Windows due to
+ # the bug described at http://trac.wordpress.org/ticket/2603
+ if ( wfIsWindows() ) {
+ $endl = "\r\n";
+ } else {
+ $endl = "\n";
+ }
+
+ if ( is_array( $body ) ) {
+ // we are sending a multipart message
+ wfDebug( "Assembling multipart mime email\n" );
+ if ( !stream_resolve_include_path( 'Mail/mime.php' ) ) {
+ wfDebug( "PEAR Mail_Mime package is not installed. Falling back to text email.\n" );
+ }
+ else {
+ require_once( 'Mail/mime.php' );
+ if ( wfIsWindows() ) {
+ $body['text'] = str_replace( "\n", "\r\n", $body['text'] );
+ $body['html'] = str_replace( "\n", "\r\n", $body['html'] );
+ }
+ $mime = new Mail_mime( array( 'eol' => $endl ) );
+ $mime->setTXTBody( $body['text'] );
+ $mime->setHTMLBody( $body['html'] );
+ $body = $mime->get(); // must call get() before headers()
+ $headers = $mime->headers( $headers );
+ }
+ }
+ if ( !isset( $mime ) ) {
+ // sending text only, either deliberately or as a fallback
+ if ( wfIsWindows() ) {
+ $body = str_replace( "\n", "\r\n", $body );
+ }
+ $headers['MIME-Version'] = '1.0';
+ $headers['Content-type'] = ( is_null( $contentType ) ?
+ 'text/plain; charset=UTF-8' : $contentType );
+ $headers['Content-transfer-encoding'] = '8bit';
+ }
+
$ret = wfRunHooks( 'AlternateUserMailer', array( $headers, $to, $from, $subject, $body ) );
if ( $ret === false ) {
+ // the hook implementation will return false to skip regular mail sending
return Status::newGood();
} elseif ( $ret !== true ) {
+ // the hook implementation will return a string to pass an error message
return Status::newFatal( 'php-mail-error', $ret );
}
if ( is_array( $wgSMTP ) ) {
#
# PEAR MAILER
- #
+ #
- if ( function_exists( 'stream_resolve_include_path' ) ) {
- $found = stream_resolve_include_path( 'Mail.php' );
- } else {
- $found = Fallback::stream_resolve_include_path( 'Mail.php' );
- }
- if ( !$found ) {
+ if ( !stream_resolve_include_path( 'Mail.php' ) ) {
throw new MWException( 'PEAR mail package is not installed' );
}
require_once( 'Mail.php' );
@@ -260,7 +322,7 @@ class UserMailer {
}
# Split jobs since SMTP servers tends to limit the maximum
- # number of possible recipients.
+ # number of possible recipients.
$chunks = array_chunk( $to, $wgEnotifMaxRecips );
foreach ( $chunks as $chunk ) {
$status = self::sendWithPear( $mail_object, $chunk, $headers, $body );
@@ -272,21 +334,11 @@ class UserMailer {
}
wfRestoreWarnings();
return Status::newGood();
- } else {
- #
+ } else {
+ #
# PHP mail()
#
-
- # Line endings need to be different on Unix and Windows due to
- # the bug described at http://trac.wordpress.org/ticket/2603
- if ( wfIsWindows() ) {
- $body = str_replace( "\n", "\r\n", $body );
- $endl = "\r\n";
- } else {
- $endl = "\n";
- }
-
- if( count($to) > 1 ) {
+ if( count( $to ) > 1 ) {
$headers['To'] = 'undisclosed-recipients:;';
}
$headers = self::arrayToHeaderString( $headers, $endl );
@@ -299,6 +351,7 @@ class UserMailer {
set_error_handler( 'UserMailer::errorHandler' );
$safeMode = wfIniGetBool( 'safe_mode' );
+
foreach ( $to as $recip ) {
if ( $safeMode ) {
$sent = mail( $recip, self::quotedPrintable( $subject ), $body, $headers );
@@ -327,7 +380,7 @@ class UserMailer {
* Set the mail error message in self::$mErrorString
*
* @param $code Integer: error number
- * @param $string String: error message
+ * @param string $string error message
*/
static function errorHandler( $code, $string ) {
self::$mErrorString = preg_replace( '/^mail\(\)(\s*\[.*?\])?: /', '', $string );
@@ -346,6 +399,12 @@ class UserMailer {
/**
* Converts a string into quoted-printable format
* @since 1.17
+ *
+ * From PHP5.3 there is a built in function quoted_printable_encode()
+ * This method does not duplicate that.
+ * This method is doing Q encoding inside encoded-words as defined by RFC 2047
+ * This is for email headers.
+ * The built in quoted_printable_encode() is for email bodies
* @return string
*/
public static function quotedPrintable( $string, $charset = '' ) {
@@ -395,7 +454,7 @@ class UserMailer {
*/
class EmailNotification {
protected $subject, $body, $replyto, $from;
- protected $timestamp, $summary, $minorEdit, $oldid, $composed_common;
+ protected $timestamp, $summary, $minorEdit, $oldid, $composed_common, $pageStatus;
protected $mailTargets = array();
/**
@@ -420,8 +479,9 @@ class EmailNotification {
* @param $summary
* @param $minorEdit
* @param $oldid (default: false)
+ * @param $pageStatus (default: 'changed')
*/
- public function notifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid = false ) {
+ public function notifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid = false, $pageStatus = 'changed' ) {
global $wgEnotifUseJobQ, $wgEnotifWatchlist, $wgShowUpdatedMarker, $wgEnotifMinorEdits,
$wgUsersNotifiedOnAllChanges, $wgEnotifUserTalk;
@@ -429,7 +489,7 @@ class EmailNotification {
return;
}
- // Build a list of users to notfiy
+ // Build a list of users to notify
$watchers = array();
if ( $wgEnotifWatchlist || $wgShowUpdatedMarker ) {
$dbw = wfGetDB( DB_MASTER );
@@ -446,19 +506,23 @@ class EmailNotification {
$watchers[] = intval( $row->wl_user );
}
if ( $watchers ) {
- // Update wl_notificationtimestamp for all watching users except
- // the editor
- $dbw->begin( __METHOD__ );
- $dbw->update( 'watchlist',
- array( /* SET */
- 'wl_notificationtimestamp' => $dbw->timestamp( $timestamp )
- ), array( /* WHERE */
- 'wl_user' => $watchers,
- 'wl_namespace' => $title->getNamespace(),
- 'wl_title' => $title->getDBkey(),
- ), __METHOD__
+ // Update wl_notificationtimestamp for all watching users except the editor
+ $fname = __METHOD__;
+ $dbw->onTransactionIdle(
+ function() use ( $dbw, $timestamp, $watchers, $title, $fname ) {
+ $dbw->begin( $fname );
+ $dbw->update( 'watchlist',
+ array( /* SET */
+ 'wl_notificationtimestamp' => $dbw->timestamp( $timestamp )
+ ), array( /* WHERE */
+ 'wl_user' => $watchers,
+ 'wl_namespace' => $title->getNamespace(),
+ 'wl_title' => $title->getDBkey(),
+ ), $fname
+ );
+ $dbw->commit( $fname );
+ }
);
- $dbw->commit( __METHOD__ );
}
}
@@ -488,12 +552,13 @@ class EmailNotification {
'summary' => $summary,
'minorEdit' => $minorEdit,
'oldid' => $oldid,
- 'watchers' => $watchers
+ 'watchers' => $watchers,
+ 'pageStatus' => $pageStatus
);
$job = new EnotifNotifyJob( $title, $params );
- $job->insert();
+ JobQueueGroup::singleton()->push( $job );
} else {
- $this->actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid, $watchers );
+ $this->actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid, $watchers, $pageStatus );
}
}
@@ -505,13 +570,16 @@ class EmailNotification {
*
* @param $editor User object
* @param $title Title object
- * @param $timestamp string Edit timestamp
- * @param $summary string Edit summary
+ * @param string $timestamp Edit timestamp
+ * @param string $summary Edit summary
* @param $minorEdit bool
- * @param $oldid int Revision ID
- * @param $watchers array of user IDs
+ * @param int $oldid Revision ID
+ * @param array $watchers of user IDs
+ * @param string $pageStatus
+ * @throws MWException
*/
- public function actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid, $watchers ) {
+ public function actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit,
+ $oldid, $watchers, $pageStatus = 'changed' ) {
# we use $wgPasswordSender as sender's address
global $wgEnotifWatchlist;
global $wgEnotifMinorEdits, $wgEnotifUserTalk;
@@ -531,6 +599,14 @@ class EmailNotification {
$this->oldid = $oldid;
$this->editor = $editor;
$this->composed_common = false;
+ $this->pageStatus = $pageStatus;
+
+ $formattedPageStatus = array( 'deleted', 'created', 'moved', 'restored', 'changed' );
+
+ wfRunHooks( 'UpdateUserMailerFormattedPageStatus', array( &$formattedPageStatus ) );
+ if ( !in_array( $this->pageStatus, $formattedPageStatus ) ) {
+ throw new MWException( 'Not a valid page status!' );
+ }
$userTalkId = false;
@@ -620,25 +696,30 @@ class EmailNotification {
$keys = array();
$postTransformKeys = array();
+ $pageTitleUrl = $this->title->getCanonicalUrl();
+ $pageTitle = $this->title->getPrefixedText();
if ( $this->oldid ) {
// Always show a link to the diff which triggered the mail. See bug 32210.
- $keys['$NEWPAGE'] = wfMessage( 'enotif_lastdiff',
+ $keys['$NEWPAGE'] = "\n\n" . wfMessage( 'enotif_lastdiff',
$this->title->getCanonicalUrl( 'diff=next&oldid=' . $this->oldid ) )
->inContentLanguage()->text();
+
if ( !$wgEnotifImpersonal ) {
// For personal mail, also show a link to the diff of all changes
// since last visited.
- $keys['$NEWPAGE'] .= " \n" . wfMessage( 'enotif_lastvisited',
+ $keys['$NEWPAGE'] .= "\n\n" . wfMessage( 'enotif_lastvisited',
$this->title->getCanonicalUrl( 'diff=0&oldid=' . $this->oldid ) )
->inContentLanguage()->text();
}
- $keys['$OLDID'] = $this->oldid;
+ $keys['$OLDID'] = $this->oldid;
+ // @deprecated Remove in MediaWiki 1.23.
$keys['$CHANGEDORCREATED'] = wfMessage( 'changed' )->inContentLanguage()->text();
} else {
- $keys['$NEWPAGE'] = wfMessage( 'enotif_newpagetext' )->inContentLanguage()->text();
# clear $OLDID placeholder in the message template
- $keys['$OLDID'] = '';
+ $keys['$OLDID'] = '';
+ $keys['$NEWPAGE'] = '';
+ // @deprecated Remove in MediaWiki 1.23.
$keys['$CHANGEDORCREATED'] = wfMessage( 'created' )->inContentLanguage()->text();
}
@@ -653,6 +734,7 @@ class EmailNotification {
$keys['$PAGEEDITOR'] = wfMessage( 'enotif_anon_editor', $this->editor->getName() )
->inContentLanguage()->text();
$keys['$PAGEEDITOR_EMAIL'] = wfMessage( 'noemailtitle' )->inContentLanguage()->text();
+
} else {
$keys['$PAGEEDITOR'] = $wgEnotifUseRealName ? $this->editor->getRealName() : $this->editor->getName();
$emailPage = SpecialPage::getSafeTitleFor( 'Emailuser', $this->editor->getName() );
@@ -665,11 +747,12 @@ class EmailNotification {
$postTransformKeys['$PAGESUMMARY'] = $this->summary == '' ? ' - ' : $this->summary;
# Now build message's subject and body
+ $this->subject = wfMessage( 'enotif_subject_' . $this->pageStatus )->inContentLanguage()
+ ->params( $pageTitle, $keys['$PAGEEDITOR'] )->text();
- $subject = wfMessage( 'enotif_subject' )->inContentLanguage()->plain();
- $subject = strtr( $subject, $keys );
- $subject = MessageCache::singleton()->transform( $subject, false, null, $this->title );
- $this->subject = strtr( $subject, $postTransformKeys );
+ $keys['$PAGEINTRO'] = wfMessage( 'enotif_body_intro_' . $this->pageStatus )
+ ->inContentLanguage()->params( $pageTitle, $keys['$PAGEEDITOR'], $pageTitleUrl )
+ ->text();
$body = wfMessage( 'enotif_body' )->inContentLanguage()->plain();
$body = strtr( $body, $keys );
@@ -686,13 +769,13 @@ class EmailNotification {
{
$editorAddress = new MailAddress( $this->editor );
if ( $wgEnotifFromEditor ) {
- $this->from = $editorAddress;
+ $this->from = $editorAddress;
} else {
- $this->from = $adminAddress;
+ $this->from = $adminAddress;
$this->replyto = $editorAddress;
}
} else {
- $this->from = $adminAddress;
+ $this->from = $adminAddress;
$this->replyto = new MailAddress( $wgNoReplyAddress );
}
}
@@ -761,13 +844,15 @@ class EmailNotification {
/**
* Same as sendPersonalised but does impersonal mail suitable for bulk
* mailing. Takes an array of MailAddress objects.
- * @return Status
+ * @param $addresses array
+ * @return Status|null
*/
function sendImpersonal( $addresses ) {
global $wgContLang;
- if ( empty( $addresses ) )
- return;
+ if ( empty( $addresses ) ) {
+ return null;
+ }
$body = str_replace(
array( '$WATCHINGUSERNAME',
diff --git a/includes/UserRightsProxy.php b/includes/UserRightsProxy.php
index 26ac3dcd..cd5dff84 100644
--- a/includes/UserRightsProxy.php
+++ b/includes/UserRightsProxy.php
@@ -32,8 +32,8 @@ class UserRightsProxy {
* @see newFromId()
* @see newFromName()
* @param $db DatabaseBase: db connection
- * @param $database String: database name
- * @param $name String: user name
+ * @param string $database database name
+ * @param string $name user name
* @param $id Integer: user ID
*/
private function __construct( $db, $database, $name, $id ) {
@@ -56,7 +56,7 @@ class UserRightsProxy {
/**
* Confirm the selected database name is a valid local interwiki database name.
*
- * @param $database String: database name
+ * @param string $database database name
* @return Boolean
*/
public static function validDatabase( $database ) {
@@ -67,7 +67,7 @@ class UserRightsProxy {
/**
* Same as User::whoIs()
*
- * @param $database String: database name
+ * @param string $database database name
* @param $id Integer: user ID
* @param $ignoreInvalidDB Boolean: if true, don't check if $database is in $wgLocalDatabases
* @return String: user name or false if the user doesn't exist
@@ -84,7 +84,7 @@ class UserRightsProxy {
/**
* Factory function; get a remote user entry by ID number.
*
- * @param $database String: database name
+ * @param string $database database name
* @param $id Integer: user ID
* @param $ignoreInvalidDB Boolean: if true, don't check if $database is in $wgLocalDatabases
* @return UserRightsProxy or null if doesn't exist
@@ -96,8 +96,8 @@ class UserRightsProxy {
/**
* Factory function; get a remote user entry by name.
*
- * @param $database String: database name
- * @param $name String: user name
+ * @param string $database database name
+ * @param string $name user name
* @param $ignoreInvalidDB Boolean: if true, don't check if $database is in $wgLocalDatabases
* @return UserRightsProxy or null if doesn't exist
*/
diff --git a/includes/ViewCountUpdate.php b/includes/ViewCountUpdate.php
index 28ba3414..22a46493 100644
--- a/includes/ViewCountUpdate.php
+++ b/includes/ViewCountUpdate.php
@@ -53,16 +53,13 @@ class ViewCountUpdate implements DeferrableUpdate {
}
# Not important enough to warrant an error page in case of failure
- $oldignore = $dbw->ignoreErrors( true );
-
- $dbw->insert( 'hitcounter', array( 'hc_id' => $this->id ), __METHOD__ );
-
- $checkfreq = intval( $wgHitcounterUpdateFreq / 25 + 1 );
- if ( rand() % $checkfreq == 0 && $dbw->lastErrno() == 0 ) {
- $this->collect();
- }
-
- $dbw->ignoreErrors( $oldignore );
+ try {
+ $dbw->insert( 'hitcounter', array( 'hc_id' => $this->id ), __METHOD__ );
+ $checkfreq = intval( $wgHitcounterUpdateFreq / 25 + 1 );
+ if ( rand() % $checkfreq == 0 && $dbw->lastErrno() == 0 ) {
+ $this->collect();
+ }
+ } catch ( DBError $e ) {}
}
protected function collect() {
diff --git a/includes/WatchedItem.php b/includes/WatchedItem.php
index 932af169..5ac92f73 100644
--- a/includes/WatchedItem.php
+++ b/includes/WatchedItem.php
@@ -27,7 +27,7 @@
* @ingroup Watchlist
*/
class WatchedItem {
- var $mTitle, $mUser, $id, $ns, $ti;
+ var $mTitle, $mUser;
private $loaded = false, $watched, $timestamp;
/**
@@ -40,25 +40,44 @@ class WatchedItem {
$wl = new WatchedItem;
$wl->mUser = $user;
$wl->mTitle = $title;
- $wl->id = $user->getId();
- # Patch (also) for email notification on page changes T.Gries/M.Arndt 11.09.2004
- # TG patch: here we do not consider pages and their talk pages equivalent - why should we ?
- # The change results in talk-pages not automatically included in watchlists, when their parent page is included
- # $wl->ns = $title->getNamespace() & ~1;
- $wl->ns = $title->getNamespace();
- $wl->ti = $title->getDBkey();
return $wl;
}
/**
+ * Title being watched
+ * @return Title
+ */
+ protected function getTitle() {
+ return $this->mTitle;
+ }
+
+ /** Helper to retrieve the title namespace */
+ protected function getTitleNs() {
+ return $this->getTitle()->getNamespace();
+ }
+
+ /** Helper to retrieve the title DBkey */
+ protected function getTitleDBkey() {
+ return $this->getTitle()->getDBkey();
+ }
+ /** Helper to retrieve the user id */
+ protected function getUserId() {
+ return $this->mUser->getId();
+ }
+
+ /**
* Return an array of conditions to select or update the appropriate database
* row.
*
* @return array
*/
private function dbCond() {
- return array( 'wl_user' => $this->id, 'wl_namespace' => $this->ns, 'wl_title' => $this->ti );
+ return array(
+ 'wl_user' => $this->getUserId(),
+ 'wl_namespace' => $this->getTitleNs(),
+ 'wl_title' => $this->getTitleDBkey(),
+ );
}
/**
@@ -143,22 +162,22 @@ class WatchedItem {
// if there's already an entry for this page
$dbw = wfGetDB( DB_MASTER );
$dbw->insert( 'watchlist',
- array(
- 'wl_user' => $this->id,
- 'wl_namespace' => MWNamespace::getSubject($this->ns),
- 'wl_title' => $this->ti,
- 'wl_notificationtimestamp' => null
- ), __METHOD__, 'IGNORE' );
+ array(
+ 'wl_user' => $this->getUserId(),
+ 'wl_namespace' => MWNamespace::getSubject( $this->getTitleNs() ),
+ 'wl_title' => $this->getTitleDBkey(),
+ 'wl_notificationtimestamp' => null
+ ), __METHOD__, 'IGNORE' );
// Every single watched page needs now to be listed in watchlist;
// namespace:page and namespace_talk:page need separate entries:
$dbw->insert( 'watchlist',
- array(
- 'wl_user' => $this->id,
- 'wl_namespace' => MWNamespace::getTalk($this->ns),
- 'wl_title' => $this->ti,
- 'wl_notificationtimestamp' => null
- ), __METHOD__, 'IGNORE' );
+ array(
+ 'wl_user' => $this->getUserId(),
+ 'wl_namespace' => MWNamespace::getTalk( $this->getTitleNs() ),
+ 'wl_title' => $this->getTitleDBkey(),
+ 'wl_notificationtimestamp' => null
+ ), __METHOD__, 'IGNORE' );
$this->watched = true;
@@ -177,24 +196,24 @@ class WatchedItem {
$dbw = wfGetDB( DB_MASTER );
$dbw->delete( 'watchlist',
array(
- 'wl_user' => $this->id,
- 'wl_namespace' => MWNamespace::getSubject($this->ns),
- 'wl_title' => $this->ti
+ 'wl_user' => $this->getUserId(),
+ 'wl_namespace' => MWNamespace::getSubject( $this->getTitleNs() ),
+ 'wl_title' => $this->getTitleDBkey(),
), __METHOD__
);
if ( $dbw->affectedRows() ) {
$success = true;
}
- # the following code compensates the new behaviour, introduced by the
+ # the following code compensates the new behavior, introduced by the
# enotif patch, that every single watched page needs now to be listed
# in watchlist namespace:page and namespace_talk:page had separate
# entries: clear them
$dbw->delete( 'watchlist',
array(
- 'wl_user' => $this->id,
- 'wl_namespace' => MWNamespace::getTalk($this->ns),
- 'wl_title' => $this->ti
+ 'wl_user' => $this->getUserId(),
+ 'wl_namespace' => MWNamespace::getTalk( $this->getTitleNs() ),
+ 'wl_title' => $this->getTitleDBkey(),
), __METHOD__
);
diff --git a/includes/WebRequest.php b/includes/WebRequest.php
index 96279fb2..98007ef8 100644
--- a/includes/WebRequest.php
+++ b/includes/WebRequest.php
@@ -1,6 +1,6 @@
<?php
/**
- * Deal with importing all those nasssty globals and things
+ * Deal with importing all those nasty globals and things
*
* Copyright © 2003 Brion Vibber <brion@pobox.com>
* http://www.mediawiki.org/
@@ -70,13 +70,13 @@ class WebRequest {
* If the REQUEST_URI is not provided we'll fall back on the PATH_INFO
* provided by the server if any and use that to set a 'title' parameter.
*
- * @param $want string: If this is not 'all', then the function
+ * @param string $want If this is not 'all', then the function
* will return an empty array if it determines that the URL is
* inside a rewrite path.
*
* @return Array: Any query arguments found in path matches.
*/
- static public function getPathInfo( $want = 'all' ) {
+ public static function getPathInfo( $want = 'all' ) {
global $wgUsePathInfo;
// PATH_INFO is mangled due to http://bugs.php.net/bug.php?id=31892
// And also by Apache 2.x, double slashes are converted to single slashes.
@@ -128,7 +128,7 @@ class WebRequest {
global $wgVariantArticlePath, $wgContLang;
if( $wgVariantArticlePath ) {
$router->add( $wgVariantArticlePath,
- array( 'variant' => '$2'),
+ array( 'variant' => '$2' ),
array( '$2' => $wgContLang->getVariants() )
);
}
@@ -144,7 +144,7 @@ class WebRequest {
// Also reported when ini_get('cgi.fix_pathinfo')==false
$matches['title'] = substr( $_SERVER['ORIG_PATH_INFO'], 1 );
- } elseif ( isset( $_SERVER['PATH_INFO'] ) && ($_SERVER['PATH_INFO'] != '') ) {
+ } elseif ( isset( $_SERVER['PATH_INFO'] ) && $_SERVER['PATH_INFO'] != '' ) {
// Regular old PATH_INFO yay
$matches['title'] = substr( $_SERVER['PATH_INFO'], 1 );
}
@@ -192,14 +192,21 @@ class WebRequest {
* @return array
*/
public static function detectProtocolAndStdPort() {
- return ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] == 'on' ) ? array( 'https', 443 ) : array( 'http', 80 );
+ if ( ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] == 'on' ) ||
+ ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) &&
+ $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' ) ) {
+ $arr = array( 'https', 443 );
+ } else {
+ $arr = array( 'http', 80 );
+ }
+ return $arr;
}
/**
* @return string
*/
public static function detectProtocol() {
- list( $proto, $stdPort ) = self::detectProtocolAndStdPort();
+ list( $proto, ) = self::detectProtocolAndStdPort();
return $proto;
}
@@ -226,9 +233,9 @@ class WebRequest {
* URL rewriting function; tries to extract page title and,
* optionally, one other fixed parameter value from a URL path.
*
- * @param $path string: the URL path given from the client
- * @param $bases array: one or more URLs, optionally with $1 at the end
- * @param $key string: if provided, the matching key in $bases will be
+ * @param string $path the URL path given from the client
+ * @param array $bases one or more URLs, optionally with $1 at the end
+ * @param string $key if provided, the matching key in $bases will be
* passed on as the value of this URL parameter
* @return array of URL variables to interpolate; empty if no match
*/
@@ -255,8 +262,8 @@ class WebRequest {
* Recursively strips slashes from the given array;
* used for undoing the evil that is magic_quotes_gpc.
*
- * @param $arr array: will be modified
- * @param $topLevel bool Specifies if the array passed is from the top
+ * @param array $arr will be modified
+ * @param bool $topLevel Specifies if the array passed is from the top
* level of the source. In PHP5 magic_quotes only escapes the first level
* of keys that belong to an array.
* @return array the original array
@@ -352,7 +359,7 @@ class WebRequest {
* selected by a drop-down menu). For freeform input, see getText().
*
* @param $name String
- * @param $default String: optional default (or NULL)
+ * @param string $default optional default (or NULL)
* @return String
*/
public function getVal( $name, $default = null ) {
@@ -370,7 +377,7 @@ class WebRequest {
/**
* Set an arbitrary value into our get/post data.
*
- * @param $key String: key name to use
+ * @param string $key key name to use
* @param $value Mixed: value to set
* @return Mixed: old value if one was present, null otherwise
*/
@@ -380,11 +387,10 @@ class WebRequest {
return $ret;
}
-
/**
* Unset an arbitrary value from our get/post data.
- *
- * @param $key String: key name to use
+ *
+ * @param string $key key name to use
* @return Mixed: old value if one was present, null otherwise
*/
public function unsetVal( $key ) {
@@ -403,7 +409,7 @@ class WebRequest {
* If no source and no default, returns NULL.
*
* @param $name String
- * @param $default Array: optional default (or NULL)
+ * @param array $default optional default (or NULL)
* @return Array
*/
public function getArray( $name, $default = null ) {
@@ -422,7 +428,7 @@ class WebRequest {
* If an array is returned, contents are guaranteed to be integers.
*
* @param $name String
- * @param $default Array: option default (or NULL)
+ * @param array $default option default (or NULL)
* @return Array of ints
*/
public function getIntArray( $name, $default = null ) {
@@ -497,7 +503,7 @@ class WebRequest {
*/
public function getCheck( $name ) {
# Checkboxes and buttons are only present when clicked
- # Presence connotes truth, abscense false
+ # Presence connotes truth, absence false
return $this->getVal( $name, null ) !== null;
}
@@ -510,7 +516,7 @@ class WebRequest {
* be required - e.g. Esperanto x-coding).
*
* @param $name String
- * @param $default String: optional
+ * @param string $default optional
* @return String
*/
public function getText( $name, $default = '' ) {
@@ -559,9 +565,9 @@ class WebRequest {
*
* @return Array
*/
- public function getQueryValues() {
+ public function getQueryValues() {
return $_GET;
- }
+ }
/**
* Get the HTTP method used for this request.
@@ -603,8 +609,8 @@ class WebRequest {
/**
* Get a cookie from the $_COOKIE jar
*
- * @param $key String: the name of the cookie
- * @param $prefix String: a prefix to use for the cookie name, if not $wgCookiePrefix
+ * @param string $key the name of the cookie
+ * @param string $prefix a prefix to use for the cookie name, if not $wgCookiePrefix
* @param $default Mixed: what to return if the value isn't found
* @return Mixed: cookie value or $default if the cookie not set
*/
@@ -613,13 +619,14 @@ class WebRequest {
global $wgCookiePrefix;
$prefix = $wgCookiePrefix;
}
- return $this->getGPCVal( $_COOKIE, $prefix . $key , $default );
+ return $this->getGPCVal( $_COOKIE, $prefix . $key, $default );
}
/**
* Return the path and query string portion of the request URI.
* This will be suitable for use as a relative link in HTML output.
*
+ * @throws MWException
* @return String
*/
public function getRequestURL() {
@@ -671,7 +678,7 @@ class WebRequest {
/**
* Take an arbitrary query and rewrite the present URL to include it
- * @param $query String: query string fragment; do not include initial '?'
+ * @param string $query query string fragment; do not include initial '?'
*
* @return String
*/
@@ -683,7 +690,7 @@ class WebRequest {
* HTML-safe version of appendQuery().
* @deprecated: Deprecated in 1.20, warnings in 1.21, remove in 1.22.
*
- * @param $query String: query string fragment; do not include initial '?'
+ * @param string $query query string fragment; do not include initial '?'
* @return String
*/
public function escapeAppendQuery( $query ) {
@@ -703,8 +710,8 @@ class WebRequest {
/**
* Appends or replaces value of query variables.
*
- * @param $array Array of values to replace/add to query
- * @param $onlyquery Bool: whether to only return the query string and not
+ * @param array $array of values to replace/add to query
+ * @param bool $onlyquery whether to only return the query string and not
* the complete URL
* @return String
*/
@@ -713,7 +720,7 @@ class WebRequest {
$newquery = $this->getQueryValues();
unset( $newquery['title'] );
$newquery = array_merge( $newquery, $array );
- $query = wfArrayToCGI( $newquery );
+ $query = wfArrayToCgi( $newquery );
return $onlyquery ? $query : $wgTitle->getLocalURL( $query );
}
@@ -723,7 +730,7 @@ class WebRequest {
* Offset must be positive but is not capped.
*
* @param $deflimit Integer: limit to use if no input and the user hasn't set the option.
- * @param $optionname String: to specify an option other than rclimit to pull from.
+ * @param string $optionname to specify an option other than rclimit to pull from.
* @return array first element is limit, second is offset
*/
public function getLimitOffset( $deflimit = 50, $optionname = 'rclimit' ) {
@@ -734,7 +741,7 @@ class WebRequest {
$limit = 0;
}
if( ( $limit == 0 ) && ( $optionname != '' ) ) {
- $limit = (int)$wgUser->getOption( $optionname );
+ $limit = $wgUser->getIntOption( $optionname );
}
if( $limit <= 0 ) {
$limit = $deflimit;
@@ -842,7 +849,7 @@ class WebRequest {
} else {
foreach ( $_SERVER as $name => $value ) {
if ( substr( $name, 0, 5 ) === 'HTTP_' ) {
- $name = str_replace( '_', '-', substr( $name, 5 ) );
+ $name = str_replace( '_', '-', substr( $name, 5 ) );
$this->headers[$name] = $value;
} elseif ( $name === 'CONTENT_LENGTH' ) {
$this->headers['CONTENT-LENGTH'] = $value;
@@ -863,7 +870,7 @@ class WebRequest {
/**
* Get a request header, or false if it isn't set
- * @param $name String: case-insensitive header name
+ * @param string $name case-insensitive header name
*
* @return string|bool False on failure
*/
@@ -880,7 +887,7 @@ class WebRequest {
/**
* Get data from $_SESSION
*
- * @param $key String: name of key in $_SESSION
+ * @param string $key name of key in $_SESSION
* @return Mixed
*/
public function getSessionData( $key ) {
@@ -893,7 +900,7 @@ class WebRequest {
/**
* Set session data
*
- * @param $key String: name of key in $_SESSION
+ * @param string $key name of key in $_SESSION
* @param $data Mixed
*/
public function setSessionData( $key, $data ) {
@@ -907,6 +914,7 @@ class WebRequest {
* false if an error message has been shown and the request should be aborted.
*
* @param $extWhitelist array
+ * @throws HttpError
* @return bool
*/
public function checkUrlExtension( $extWhitelist = array() ) {
@@ -1043,22 +1051,30 @@ HTML;
*
* @since 1.19
*
+ * @throws MWException
* @return String
*/
protected function getRawIP() {
- if ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
- return IP::canonicalize( $_SERVER['REMOTE_ADDR'] );
- } else {
+ if ( !isset( $_SERVER['REMOTE_ADDR'] ) ) {
return null;
}
+
+ if ( is_array( $_SERVER['REMOTE_ADDR'] ) || strpos( $_SERVER['REMOTE_ADDR'], ',' ) !== false ) {
+ throw new MWException( __METHOD__ . " : Could not determine the remote IP address due to multiple values." );
+ } else {
+ $ipchain = $_SERVER['REMOTE_ADDR'];
+ }
+
+ return IP::canonicalize( $ipchain );
}
/**
* Work out the IP address based on various globals
* For trusted proxies, use the XFF client IP (first of the chain)
- *
+ *
* @since 1.19
*
+ * @throws MWException
* @return string
*/
public function getIP() {
@@ -1108,6 +1124,15 @@ HTML;
$this->ip = $ip;
return $ip;
}
+
+ /**
+ * @param string $ip
+ * @return void
+ * @since 1.21
+ */
+ public function setIP( $ip ) {
+ $this->ip = $ip;
+ }
}
/**
@@ -1122,7 +1147,7 @@ class WebRequestUpload {
* Constructor. Should only be called by WebRequest
*
* @param $request WebRequest The associated request
- * @param $key string Key in $_FILES array (name of form field)
+ * @param string $key Key in $_FILES array (name of form field)
*/
public function __construct( $request, $key ) {
$this->request = $request;
@@ -1234,10 +1259,11 @@ class FauxRequest extends WebRequest {
private $session = array();
/**
- * @param $data Array of *non*-urlencoded key => value pairs, the
+ * @param array $data of *non*-urlencoded key => value pairs, the
* fake GET/POST values
- * @param $wasPosted Bool: whether to treat the data as POST
+ * @param bool $wasPosted whether to treat the data as POST
* @param $session Mixed: session array or null
+ * @throws MWException
*/
public function __construct( $data = array(), $wasPosted = false, $session = null ) {
if( is_array( $data ) ) {
@@ -1246,8 +1272,9 @@ class FauxRequest extends WebRequest {
throw new MWException( "FauxRequest() got bogus data" );
}
$this->wasPosted = $wasPosted;
- if( $session )
+ if( $session ) {
$this->session = $session;
+ }
}
/**
@@ -1330,8 +1357,10 @@ class FauxRequest extends WebRequest {
* @return mixed
*/
public function getSessionData( $key ) {
- if( isset( $this->session[$key] ) )
+ if( isset( $this->session[$key] ) ) {
return $this->session[$key];
+ }
+ return null;
}
/**
diff --git a/includes/WebResponse.php b/includes/WebResponse.php
index 193101b1..8e15d712 100644
--- a/includes/WebResponse.php
+++ b/includes/WebResponse.php
@@ -30,8 +30,8 @@ class WebResponse {
/**
* Output a HTTP header, wrapper for PHP's
* header()
- * @param $string String: header to output
- * @param $replace Bool: replace current similar header
+ * @param string $string header to output
+ * @param bool $replace replace current similar header
* @param $http_response_code null|int Forces the HTTP response code to the specified value.
*/
public function header( $string, $replace = true, $http_response_code = null ) {
@@ -40,15 +40,20 @@ class WebResponse {
/**
* Set the browser cookie
- * @param $name String: name of cookie
- * @param $value String: value to give cookie
- * @param $expire Int: number of seconds til cookie expires
- * @param $prefix String: Prefix to use, if not $wgCookiePrefix (use '' for no prefix)
- * @param @domain String: Cookie domain to use, if not $wgCookieDomain
+ * @param string $name name of cookie
+ * @param string $value value to give cookie
+ * @param int $expire Unix timestamp (in seconds) when the cookie should expire.
+ * 0 (the default) causes it to expire $wgCookieExpiration seconds from now.
+ * @param string $prefix Prefix to use, if not $wgCookiePrefix (use '' for no prefix)
+ * @param string $domain Cookie domain to use, if not $wgCookieDomain
+ * @param $forceSecure Bool:
+ * true: force the cookie to be set with the secure attribute
+ * false: force the cookie to be set without the secure attribute
+ * null: use the value from $wgCookieSecure
*/
- public function setcookie( $name, $value, $expire = 0, $prefix = null, $domain = null ) {
+ public function setcookie( $name, $value, $expire = 0, $prefix = null, $domain = null, $forceSecure = null ) {
global $wgCookiePath, $wgCookiePrefix, $wgCookieDomain;
- global $wgCookieSecure,$wgCookieExpiration, $wgCookieHttpOnly;
+ global $wgCookieSecure, $wgCookieExpiration, $wgCookieHttpOnly;
if ( $expire == 0 ) {
$expire = time() + $wgCookieExpiration;
}
@@ -58,7 +63,18 @@ class WebResponse {
if( $domain === null ) {
$domain = $wgCookieDomain;
}
- $httpOnlySafe = wfHttpOnlySafe() && $wgCookieHttpOnly;
+
+ if ( is_null( $forceSecure ) ) {
+ $secureCookie = $wgCookieSecure;
+ } else {
+ $secureCookie = $forceSecure;
+ }
+
+ // Mark the cookie as httpOnly if $wgCookieHttpOnly is true,
+ // unless the requesting user-agent is known to have trouble with
+ // httpOnly cookies.
+ $httpOnlySafe = $wgCookieHttpOnly && wfHttpOnlySafe();
+
wfDebugLog( 'cookie',
'setcookie: "' . implode( '", "',
array(
@@ -67,14 +83,14 @@ class WebResponse {
$expire,
$wgCookiePath,
$domain,
- $wgCookieSecure,
+ $secureCookie,
$httpOnlySafe ) ) . '"' );
setcookie( $prefix . $name,
$value,
$expire,
$wgCookiePath,
$domain,
- $wgCookieSecure,
+ $secureCookie,
$httpOnlySafe );
}
}
@@ -89,8 +105,8 @@ class FauxResponse extends WebResponse {
/**
* Stores a HTTP header
- * @param $string String: header to output
- * @param $replace Bool: replace current similar header
+ * @param string $string header to output
+ * @param bool $replace replace current similar header
* @param $http_response_code null|int Forces the HTTP response code to the specified value.
*/
public function header( $string, $replace = true, $http_response_code = null ) {
@@ -133,14 +149,14 @@ class FauxResponse extends WebResponse {
/**
* @todo document. It just ignore optional parameters.
*
- * @param $name String: name of cookie
- * @param $value String: value to give cookie
- * @param $expire Int: number of seconds til cookie expires (Default: 0)
+ * @param string $name name of cookie
+ * @param string $value value to give cookie
+ * @param int $expire number of seconds til cookie expires (Default: 0)
* @param $prefix TODO DOCUMENT (Default: null)
* @param $domain TODO DOCUMENT (Default: null)
- *
+ * @param $forceSecure TODO DOCUMENT (Default: null)
*/
- public function setcookie( $name, $value, $expire = 0, $prefix = null, $domain = null ) {
+ public function setcookie( $name, $value, $expire = 0, $prefix = null, $domain = null, $forceSecure = null ) {
$this->cookies[$name] = $value;
}
@@ -148,7 +164,7 @@ class FauxResponse extends WebResponse {
* @param $name string
* @return string
*/
- public function getcookie( $name ) {
+ public function getcookie( $name ) {
if ( isset( $this->cookies[$name] ) ) {
return $this->cookies[$name];
}
diff --git a/includes/WebStart.php b/includes/WebStart.php
index 01c5eea8..e6f31355 100644
--- a/includes/WebStart.php
+++ b/includes/WebStart.php
@@ -27,7 +27,7 @@
# This must be done before any globals are set by the code
if ( ini_get( 'register_globals' ) ) {
if ( isset( $_REQUEST['GLOBALS'] ) || isset( $_FILES['GLOBALS'] ) ) {
- die( '<a href="http://www.hardened-php.net/globals-problem">$GLOBALS overwrite vulnerability</a>');
+ die( '<a href="http://www.hardened-php.net/globals-problem">$GLOBALS overwrite vulnerability</a>' );
}
$verboten = array(
'GLOBALS',
@@ -57,12 +57,12 @@ if ( ini_get( 'register_globals' ) ) {
}
}
-# bug 15461: Make IE8 turn off content sniffing. Everbody else should ignore this
+# bug 15461: Make IE8 turn off content sniffing. Everybody else should ignore this
# We're adding it here so that it's *always* set, even for alternate entry
# points and when $wgOut gets disabled or overridden.
header( 'X-Content-Type-Options: nosniff' );
-$wgRequestTime = microtime(true);
+$wgRequestTime = microtime( true );
# getrusage() does not exist on the Microsoft Windows platforms, catching this
if ( function_exists ( 'getrusage' ) ) {
$wgRUstart = getrusage();
@@ -80,11 +80,15 @@ define( 'MEDIAWIKI', true );
# Full path to working directory.
# Makes it possible to for example to have effective exclude path in apc.
-# Also doesn't break installations using symlinked includes, like
-# __DIR__ would do.
+# __DIR__ breaks symlinked includes, but realpath() returns false
+# if we don't have permissions on parent directories.
$IP = getenv( 'MW_INSTALL_PATH' );
if ( $IP === false ) {
- $IP = realpath( '.' );
+ if( realpath( '.' ) ) {
+ $IP = realpath( '.' );
+ } else {
+ $IP = dirname( __DIR__ );
+ }
}
if ( isset( $_SERVER['MW_COMPILED'] ) ) {
@@ -119,7 +123,7 @@ if ( defined( 'MW_CONFIG_CALLBACK' ) ) {
MWFunction::call( MW_CONFIG_CALLBACK );
} else {
if ( !defined( 'MW_CONFIG_FILE' ) ) {
- define('MW_CONFIG_FILE', MWInit::interpretedPath( 'LocalSettings.php' ) );
+ define( 'MW_CONFIG_FILE', MWInit::interpretedPath( 'LocalSettings.php' ) );
}
# LocalSettings.php is the per site customization file. If it does not exist
@@ -156,4 +160,3 @@ wfProfileOut( 'WebStart.php-ob_start' );
if ( !defined( 'MW_NO_SETUP' ) ) {
require_once( MWInit::compiledPath( "includes/Setup.php" ) );
}
-
diff --git a/includes/Wiki.php b/includes/Wiki.php
index a4a89032..f8f699c9 100644
--- a/includes/Wiki.php
+++ b/includes/Wiki.php
@@ -126,7 +126,7 @@ class MediaWiki {
* @return Title
*/
public function getTitle() {
- if( $this->context->getTitle() === null ){
+ if( $this->context->getTitle() === null ) {
$this->context->setTitle( $this->parseTitle() );
}
return $this->context->getTitle();
@@ -169,6 +169,7 @@ class MediaWiki {
* - special pages
* - normal pages
*
+ * @throws MWException|PermissionsError|BadTitleError|HttpError
* @return void
*/
private function performRequest() {
@@ -177,7 +178,7 @@ class MediaWiki {
wfProfileIn( __METHOD__ );
$request = $this->context->getRequest();
- $title = $this->context->getTitle();
+ $requestTitle = $title = $this->context->getTitle();
$output = $this->context->getOutput();
$user = $this->context->getUser();
@@ -246,7 +247,7 @@ class MediaWiki {
// Redirect loops, no title in URL, $wgUsePathInfo URLs, and URLs with a variant
} elseif ( $request->getVal( 'action', 'view' ) == 'view' && !$request->wasPosted()
&& ( $request->getVal( 'title' ) === null ||
- $title->getPrefixedDBKey() != $request->getVal( 'title' ) )
+ $title->getPrefixedDBkey() != $request->getVal( 'title' ) )
&& !count( $request->getValueNames( array( 'action', 'title' ) ) )
&& wfRunHooks( 'TestCanonicalRedirect', array( $request, $title, $output ) ) )
{
@@ -301,7 +302,7 @@ class MediaWiki {
global $wgArticle;
$wgArticle = new DeprecatedGlobal( 'wgArticle', $article, '1.18' );
- $this->performAction( $article );
+ $this->performAction( $article, $requestTitle );
} elseif ( is_string( $article ) ) {
$output->redirect( $article );
} else {
@@ -330,8 +331,18 @@ class MediaWiki {
wfProfileIn( __METHOD__ );
$title = $this->context->getTitle();
- $article = Article::newFromTitle( $title, $this->context );
- $this->context->setWikiPage( $article->getPage() );
+ if ( $this->context->canUseWikiPage() ) {
+ // Try to use request context wiki page, as there
+ // is already data from db saved in per process
+ // cache there from this->getAction() call.
+ $page = $this->context->getWikiPage();
+ $article = Article::newFromWikiPage( $page, $this->context );
+ } else {
+ // This case should not happen, but just in case.
+ $article = Article::newFromTitle( $title, $this->context );
+ $this->context->setWikiPage( $article->getPage() );
+ }
+
// NS_MEDIAWIKI has no redirects.
// It is also used for CSS/JS, so performance matters here...
if ( $title->getNamespace() == NS_MEDIAWIKI ) {
@@ -345,10 +356,10 @@ class MediaWiki {
// Check for redirects ...
$action = $request->getVal( 'action', 'view' );
$file = ( $title->getNamespace() == NS_FILE ) ? $article->getFile() : null;
- if ( ( $action == 'view' || $action == 'render' ) // ... for actions that show content
- && !$request->getVal( 'oldid' ) && // ... and are not old revisions
- !$request->getVal( 'diff' ) && // ... and not when showing diff
- $request->getVal( 'redirect' ) != 'no' && // ... unless explicitly told not to
+ if ( ( $action == 'view' || $action == 'render' ) // ... for actions that show content
+ && !$request->getVal( 'oldid' ) && // ... and are not old revisions
+ !$request->getVal( 'diff' ) && // ... and not when showing diff
+ $request->getVal( 'redirect' ) != 'no' && // ... unless explicitly told not to
// ... and the article is not a non-redirect image page with associated file
!( is_object( $file ) && $file->exists() && !$file->getRedirected() ) )
{
@@ -395,8 +406,9 @@ class MediaWiki {
* Perform one of the "standard" actions
*
* @param $page Page
+ * @param $requestTitle The original title, before any redirects were applied
*/
- private function performAction( Page $page ) {
+ private function performAction( Page $page, Title $requestTitle ) {
global $wgUseSquid, $wgSquidMaxage;
wfProfileIn( __METHOD__ );
@@ -419,7 +431,7 @@ class MediaWiki {
if ( $action instanceof Action ) {
# Let Squid cache things if we can purge them.
if ( $wgUseSquid &&
- in_array( $request->getFullRequestURL(), $title->getSquidURLs() )
+ in_array( $request->getFullRequestURL(), $requestTitle->getSquidURLs() )
) {
$output->setSquidMaxage( $wgSquidMaxage );
}
@@ -490,6 +502,23 @@ class MediaWiki {
$request = $this->context->getRequest();
+ if ( $request->getCookie( 'forceHTTPS' )
+ && $request->detectProtocol() == 'http'
+ && $request->getMethod() == 'GET'
+ ) {
+ $redirUrl = $request->getFullRequestURL();
+ $redirUrl = str_replace( 'http://', 'https://', $redirUrl );
+
+ // Setup dummy Title, otherwise OutputPage::redirect will fail
+ $title = Title::newFromText( NS_MAIN, 'REDIR' );
+ $this->context->setTitle( $title );
+ $output = $this->context->getOutput();
+ $output->redirect( $redirUrl );
+ $output->output();
+ wfProfileOut( __METHOD__ );
+ return;
+ }
+
// Send Ajax requests to the Ajax dispatcher.
if ( $wgUseAjax && $request->getVal( 'action', 'view' ) == 'ajax' ) {
@@ -555,9 +584,6 @@ class MediaWiki {
// Execute a job from the queue
$this->doJobs();
- // Log message usage, if $wgAdaptiveMessageCache is set to true
- MessageCache::logMessages();
-
// Log profiling data, e.g. in the database or UDP
wfLogProfilingData();
@@ -578,28 +604,34 @@ class MediaWiki {
if ( $wgJobRunRate <= 0 || wfReadOnly() ) {
return;
}
+
if ( $wgJobRunRate < 1 ) {
$max = mt_getrandmax();
if ( mt_rand( 0, $max ) > $max * $wgJobRunRate ) {
- return;
+ return; // the higher $wgJobRunRate, the less likely we return here
}
$n = 1;
} else {
$n = intval( $wgJobRunRate );
}
- while ( $n-- && false != ( $job = Job::pop() ) ) {
- $output = $job->toString() . "\n";
- $t = - microtime( true );
- $success = $job->run();
- $t += microtime( true );
- $t = round( $t * 1000 );
- if ( !$success ) {
- $output .= "Error: " . $job->getLastError() . ", Time: $t ms\n";
- } else {
- $output .= "Success, Time: $t ms\n";
+ $group = JobQueueGroup::singleton();
+ do {
+ $job = $group->pop( JobQueueGroup::USE_CACHE ); // job from any queue
+ if ( $job ) {
+ $output = $job->toString() . "\n";
+ $t = - microtime( true );
+ $success = $job->run();
+ $group->ack( $job ); // done
+ $t += microtime( true );
+ $t = round( $t * 1000 );
+ if ( !$success ) {
+ $output .= "Error: " . $job->getLastError() . ", Time: $t ms\n";
+ } else {
+ $output .= "Success, Time: $t ms\n";
+ }
+ wfDebugLog( 'jobqueue', $output );
}
- wfDebugLog( 'jobqueue', $output );
- }
+ } while ( --$n && $job );
}
}
diff --git a/includes/WikiError.php b/includes/WikiError.php
index 45ee20c9..62781213 100644
--- a/includes/WikiError.php
+++ b/includes/WikiError.php
@@ -82,7 +82,7 @@ class WikiError {
*/
class WikiErrorMsg extends WikiError {
/**
- * @param $message String: wiki message name
+ * @param string $message wiki message name
* @param ... parameters to pass to wfMsg()
*
* @deprecated since 1.17
diff --git a/includes/WikiFilePage.php b/includes/WikiFilePage.php
index 9fb1522d..5e603d37 100644
--- a/includes/WikiFilePage.php
+++ b/includes/WikiFilePage.php
@@ -41,7 +41,9 @@ class WikiFilePage extends WikiPage {
}
public function getActionOverrides() {
- return array( 'revert' => 'RevertFileAction' );
+ $overrides = parent::getActionOverrides();
+ $overrides['revert'] = 'RevertFileAction';
+ return $overrides;
}
/**
@@ -103,13 +105,12 @@ class WikiFilePage extends WikiPage {
}
/**
- * @param bool $text
* @return bool
*/
- public function isRedirect( $text = false ) {
+ public function isRedirect() {
$this->loadFile();
if ( $this->mFile->isLocal() ) {
- return parent::isRedirect( $text );
+ return parent::isRedirect();
}
return (bool)$this->mFile->getRedirected();
diff --git a/includes/WikiMap.php b/includes/WikiMap.php
index 4a5e2bcf..b04a7842 100644
--- a/includes/WikiMap.php
+++ b/includes/WikiMap.php
@@ -28,7 +28,7 @@ class WikiMap {
/**
* Get a WikiReference object for $wikiID
*
- * @param $wikiID String: wiki'd id (generally database name)
+ * @param string $wikiID wiki'd id (generally database name)
* @return WikiReference object or null if the wiki was not found
*/
public static function getWiki( $wikiID ) {
@@ -53,7 +53,7 @@ class WikiMap {
* Convenience to get the wiki's display name
*
* @todo We can give more info than just the wiki id!
- * @param $wikiID String: wiki'd id (generally database name)
+ * @param string $wikiID wiki'd id (generally database name)
* @return string|int Wiki's name or $wiki_id if the wiki was not found
*/
public static function getWikiName( $wikiID ) {
@@ -68,9 +68,9 @@ class WikiMap {
/**
* Convenience to get a link to a user page on a foreign wiki
*
- * @param $wikiID String: wiki'd id (generally database name)
- * @param $user String: user name (must be normalised before calling this function!)
- * @param $text String: link's text; optional, default to "User:$user"
+ * @param string $wikiID wiki'd id (generally database name)
+ * @param string $user user name (must be normalised before calling this function!)
+ * @param string $text link's text; optional, default to "User:$user"
* @return String: HTML link or false if the wiki was not found
*/
public static function foreignUserLink( $wikiID, $user, $text=null ) {
@@ -80,9 +80,9 @@ class WikiMap {
/**
* Convenience to get a link to a page on a foreign wiki
*
- * @param $wikiID String: wiki'd id (generally database name)
- * @param $page String: page name (must be normalised before calling this function!)
- * @param $text String: link's text; optional, default to $page
+ * @param string $wikiID wiki'd id (generally database name)
+ * @param string $page page name (must be normalised before calling this function!)
+ * @param string $text link's text; optional, default to $page
* @return String: HTML link or false if the wiki was not found
*/
public static function makeForeignLink( $wikiID, $page, $text=null ) {
@@ -101,8 +101,8 @@ class WikiMap {
/**
* Convenience to get a url to a page on a foreign wiki
*
- * @param $wikiID String: wiki'd id (generally database name)
- * @param $page String: page name (must be normalised before calling this function!)
+ * @param string $wikiID wiki'd id (generally database name)
+ * @param string $page page name (must be normalised before calling this function!)
* @return String: URL or false if the wiki was not found
*/
public static function getForeignURL( $wikiID, $page ) {
@@ -176,7 +176,7 @@ class WikiReference {
* Helper function for getUrl()
*
* @todo FIXME: This may be generalized...
- * @param $page String: page name (must be normalised before calling this function!)
+ * @param string $page page name (must be normalised before calling this function!)
* @return String: Url fragment
*/
private function getLocalUrl( $page ) {
@@ -186,7 +186,7 @@ class WikiReference {
/**
* Get a canonical (i.e. based on $wgCanonicalServer) URL to a page on this foreign wiki
*
- * @param $page String: page name (must be normalised before calling this function!)
+ * @param string $page page name (must be normalised before calling this function!)
* @return String: Url
*/
public function getCanonicalUrl( $page ) {
@@ -214,7 +214,7 @@ class WikiReference {
* Get a URL based on $wgServer, like Title::getFullUrl() would produce
* when called locally on the wiki.
*
- * @param $page String: page name (must be normalized before calling this function!)
+ * @param string $page page name (must be normalized before calling this function!)
* @return String: URL
*/
public function getFullUrl( $page ) {
diff --git a/includes/WikiPage.php b/includes/WikiPage.php
index bc8766de..de881ef6 100644
--- a/includes/WikiPage.php
+++ b/includes/WikiPage.php
@@ -23,7 +23,7 @@
/**
* Abstract class for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
*/
-abstract class Page {}
+interface Page {}
/**
* Class representing a MediaWiki article and history.
@@ -33,7 +33,7 @@ abstract class Page {}
*
* @internal documentation reviewed 15 Mar 2010
*/
-class WikiPage extends Page implements IDBAccessObject {
+class WikiPage implements Page, IDBAccessObject {
// Constants for $mDataLoadedFrom and related
/**
@@ -121,8 +121,8 @@ class WikiPage extends Page implements IDBAccessObject {
/**
* Constructor from a page id
*
- * @param $id Int article ID to load
- * @param $from string|int one of the following values:
+ * @param int $id article ID to load
+ * @param string|int $from one of the following values:
* - "fromdb" or WikiPage::READ_NORMAL to select from a slave database
* - "fromdbmaster" or WikiPage::READ_LATEST to select from the master database
*
@@ -144,7 +144,7 @@ class WikiPage extends Page implements IDBAccessObject {
* @since 1.20
* @param $row object: database row containing at least fields returned
* by selectFields().
- * @param $from string|int: source of $data:
+ * @param string|int $from source of $data:
* - "fromdb" or WikiPage::READ_NORMAL: from a slave DB
* - "fromdbmaster" or WikiPage::READ_LATEST: from the master DB
* - "forupdate" or WikiPage::READ_LOCKING: from the master DB using SELECT FOR UPDATE
@@ -187,7 +187,21 @@ class WikiPage extends Page implements IDBAccessObject {
* @return Array
*/
public function getActionOverrides() {
- return array();
+ $content_handler = $this->getContentHandler();
+ return $content_handler->getActionOverrides();
+ }
+
+ /**
+ * Returns the ContentHandler instance to be used to deal with the content of this WikiPage.
+ *
+ * Shorthand for ContentHandler::getForModelID( $this->getContentModel() );
+ *
+ * @return ContentHandler
+ *
+ * @since 1.21
+ */
+ public function getContentHandler() {
+ return ContentHandler::getForModelID( $this->getContentModel() );
}
/**
@@ -215,8 +229,8 @@ class WikiPage extends Page implements IDBAccessObject {
*/
protected function clearCacheFields() {
$this->mCounter = null;
- $this->mRedirectTarget = null; # Title object if set
- $this->mLastRevision = null; # Latest revision
+ $this->mRedirectTarget = null; // Title object if set
+ $this->mLastRevision = null; // Latest revision
$this->mTouched = '19700101000000';
$this->mTimestamp = '';
$this->mIsRedirect = false;
@@ -231,7 +245,9 @@ class WikiPage extends Page implements IDBAccessObject {
* @return array
*/
public static function selectFields() {
- return array(
+ global $wgContentHandlerUseDB;
+
+ $fields = array(
'page_id',
'page_namespace',
'page_title',
@@ -244,6 +260,12 @@ class WikiPage extends Page implements IDBAccessObject {
'page_latest',
'page_len',
);
+
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'page_content_model';
+ }
+
+ return $fields;
}
/**
@@ -317,8 +339,8 @@ class WikiPage extends Page implements IDBAccessObject {
$data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
} elseif ( $from === self::READ_NORMAL ) {
$data = $this->pageDataFromTitle( wfGetDB( DB_SLAVE ), $this->mTitle );
- # Use a "last rev inserted" timestamp key to dimish the issue of slave lag.
- # Note that DB also stores the master position in the session and checks it.
+ // Use a "last rev inserted" timestamp key to diminish the issue of slave lag.
+ // Note that DB also stores the master position in the session and checks it.
$touched = $this->getCachedLastEditTime();
if ( $touched ) { // key set
if ( !$data || $touched > wfTimestamp( TS_MW, $data->page_touched ) ) {
@@ -341,7 +363,7 @@ class WikiPage extends Page implements IDBAccessObject {
* @since 1.20
* @param $data object: database row containing at least fields returned
* by selectFields()
- * @param $from string|int One of the following:
+ * @param string|int $from One of the following:
* - "fromdb" or WikiPage::READ_NORMAL if the data comes from a slave DB
* - "fromdbmaster" or WikiPage::READ_LATEST if the data comes from the master DB
* - "forupdate" or WikiPage::READ_LOCKING if the data comes from from
@@ -349,13 +371,14 @@ class WikiPage extends Page implements IDBAccessObject {
*/
public function loadFromRow( $data, $from ) {
$lc = LinkCache::singleton();
+ $lc->clearLink( $this->mTitle );
if ( $data ) {
$lc->addGoodLinkObjFromRow( $this->mTitle, $data );
$this->mTitle->loadFromRow( $data );
- # Old-fashioned restrictions
+ // Old-fashioned restrictions
$this->mTitle->loadRestrictions( $data->page_restrictions );
$this->mCounter = intval( $data->page_counter );
@@ -418,21 +441,42 @@ class WikiPage extends Page implements IDBAccessObject {
}
/**
- * Tests if the article text represents a redirect
+ * Tests if the article content represents a redirect
*
- * @param $text mixed string containing article contents, or boolean
* @return bool
*/
- public function isRedirect( $text = false ) {
- if ( $text === false ) {
- if ( !$this->mDataLoaded ) {
- $this->loadPageData();
- }
+ public function isRedirect() {
+ $content = $this->getContent();
+ if ( !$content ) return false;
- return (bool)$this->mIsRedirect;
- } else {
- return Title::newFromRedirect( $text ) !== null;
+ return $content->isRedirect();
+ }
+
+ /**
+ * Returns the page's content model id (see the CONTENT_MODEL_XXX constants).
+ *
+ * Will use the revisions actual content model if the page exists,
+ * and the page's default if the page doesn't exist yet.
+ *
+ * @return String
+ *
+ * @since 1.21
+ */
+ public function getContentModel() {
+ if ( $this->exists() ) {
+ // look at the revision's actual content model
+ $rev = $this->getRevision();
+
+ if ( $rev !== null ) {
+ return $rev->getContentModel();
+ } else {
+ $title = $this->mTitle->getPrefixedDBkey();
+ wfWarn( "Page $title exists but has no (visible) revisions!" );
+ }
}
+
+ // use the default model for this page
+ return $this->mTitle->getContentModel();
}
/**
@@ -555,36 +599,61 @@ class WikiPage extends Page implements IDBAccessObject {
}
/**
- * Get the text of the current revision. No side-effects...
+ * Get the content of the current revision. No side-effects...
*
* @param $audience Integer: one of:
* Revision::FOR_PUBLIC to be displayed to all users
* Revision::FOR_THIS_USER to be displayed to $wgUser
* Revision::RAW get the text regardless of permissions
- * @return String|bool The text of the current revision. False on failure
+ * @param $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
+ * @return Content|null The content of the current revision
+ *
+ * @since 1.21
*/
- public function getText( $audience = Revision::FOR_PUBLIC ) {
+ public function getContent( $audience = Revision::FOR_PUBLIC, User $user = null ) {
$this->loadLastEdit();
if ( $this->mLastRevision ) {
- return $this->mLastRevision->getText( $audience );
+ return $this->mLastRevision->getContent( $audience, $user );
}
- return false;
+ return null;
}
/**
* Get the text of the current revision. No side-effects...
*
- * @return String|bool The text of the current revision. False on failure
+ * @param $audience Integer: one of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to the given user
+ * Revision::RAW get the text regardless of permissions
+ * @param $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
+ * @return String|false The text of the current revision
+ * @deprecated as of 1.21, getContent() should be used instead.
*/
- public function getRawText() {
+ public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) { // @todo: deprecated, replace usage!
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
$this->loadLastEdit();
if ( $this->mLastRevision ) {
- return $this->mLastRevision->getRawText();
+ return $this->mLastRevision->getText( $audience, $user );
}
return false;
}
/**
+ * Get the text of the current revision. No side-effects...
+ *
+ * @return String|bool The text of the current revision. False on failure
+ * @deprecated as of 1.21, getContent() should be used instead.
+ */
+ public function getRawText() {
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
+ return $this->getText( Revision::RAW );
+ }
+
+ /**
* @return string MW timestamp of last article revision
*/
public function getTimestamp() {
@@ -598,7 +667,7 @@ class WikiPage extends Page implements IDBAccessObject {
/**
* Set the page timestamp (use only to avoid DB queries)
- * @param $ts string MW timestamp of last article revision
+ * @param string $ts MW timestamp of last article revision
* @return void
*/
public function setTimestamp( $ts ) {
@@ -608,14 +677,16 @@ class WikiPage extends Page implements IDBAccessObject {
/**
* @param $audience Integer: one of:
* Revision::FOR_PUBLIC to be displayed to all users
- * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::FOR_THIS_USER to be displayed to the given user
* Revision::RAW get the text regardless of permissions
+ * @param $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
* @return int user ID for the user that made the last article revision
*/
- public function getUser( $audience = Revision::FOR_PUBLIC ) {
+ public function getUser( $audience = Revision::FOR_PUBLIC, User $user = null ) {
$this->loadLastEdit();
if ( $this->mLastRevision ) {
- return $this->mLastRevision->getUser( $audience );
+ return $this->mLastRevision->getUser( $audience, $user );
} else {
return -1;
}
@@ -625,14 +696,16 @@ class WikiPage extends Page implements IDBAccessObject {
* Get the User object of the user who created the page
* @param $audience Integer: one of:
* Revision::FOR_PUBLIC to be displayed to all users
- * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::FOR_THIS_USER to be displayed to the given user
* Revision::RAW get the text regardless of permissions
+ * @param $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
* @return User|null
*/
- public function getCreator( $audience = Revision::FOR_PUBLIC ) {
+ public function getCreator( $audience = Revision::FOR_PUBLIC, User $user = null ) {
$revision = $this->getOldestRevision();
if ( $revision ) {
- $userName = $revision->getUserText( $audience );
+ $userName = $revision->getUserText( $audience, $user );
return User::newFromName( $userName, false );
} else {
return null;
@@ -642,14 +715,16 @@ class WikiPage extends Page implements IDBAccessObject {
/**
* @param $audience Integer: one of:
* Revision::FOR_PUBLIC to be displayed to all users
- * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::FOR_THIS_USER to be displayed to the given user
* Revision::RAW get the text regardless of permissions
+ * @param $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
* @return string username of the user that made the last article revision
*/
- public function getUserText( $audience = Revision::FOR_PUBLIC ) {
+ public function getUserText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
$this->loadLastEdit();
if ( $this->mLastRevision ) {
- return $this->mLastRevision->getUserText( $audience );
+ return $this->mLastRevision->getUserText( $audience, $user );
} else {
return '';
}
@@ -658,14 +733,16 @@ class WikiPage extends Page implements IDBAccessObject {
/**
* @param $audience Integer: one of:
* Revision::FOR_PUBLIC to be displayed to all users
- * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::FOR_THIS_USER to be displayed to the given user
* Revision::RAW get the text regardless of permissions
+ * @param $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
* @return string Comment stored for the last article revision
*/
- public function getComment( $audience = Revision::FOR_PUBLIC ) {
+ public function getComment( $audience = Revision::FOR_PUBLIC, User $user = null ) {
$this->loadLastEdit();
if ( $this->mLastRevision ) {
- return $this->mLastRevision->getComment( $audience );
+ return $this->mLastRevision->getComment( $audience, $user );
} else {
return '';
}
@@ -723,32 +800,34 @@ class WikiPage extends Page implements IDBAccessObject {
return false;
}
- $text = $editInfo ? $editInfo->pst : false;
+ if ( $editInfo ) {
+ $content = $editInfo->pstContent;
+ } else {
+ $content = $this->getContent();
+ }
- if ( $this->isRedirect( $text ) ) {
+ if ( !$content || $content->isRedirect() ) {
return false;
}
- switch ( $wgArticleCountMethod ) {
- case 'any':
- return true;
- case 'comma':
- if ( $text === false ) {
- $text = $this->getRawText();
- }
- return strpos( $text, ',' ) !== false;
- case 'link':
+ $hasLinks = null;
+
+ if ( $wgArticleCountMethod === 'link' ) {
+ // nasty special case to avoid re-parsing to detect links
+
if ( $editInfo ) {
// ParserOutput::getLinks() is a 2D array of page links, so
// to be really correct we would need to recurse in the array
// but the main array should only have items in it if there are
// links.
- return (bool)count( $editInfo->output->getLinks() );
+ $hasLinks = (bool)count( $editInfo->output->getLinks() );
} else {
- return (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
+ $hasLinks = (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
array( 'pl_from' => $this->getId() ), __METHOD__ );
}
}
+
+ return $content->isCountable( $hasLinks );
}
/**
@@ -767,7 +846,7 @@ class WikiPage extends Page implements IDBAccessObject {
return $this->mRedirectTarget;
}
- # Query the redirect table
+ // Query the redirect table
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow( 'redirect',
array( 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ),
@@ -782,7 +861,7 @@ class WikiPage extends Page implements IDBAccessObject {
$row->rd_fragment, $row->rd_interwiki );
}
- # This page doesn't have an entry in the redirect table
+ // This page doesn't have an entry in the redirect table
return $this->mRedirectTarget = $this->insertRedirect();
}
@@ -794,7 +873,8 @@ class WikiPage extends Page implements IDBAccessObject {
*/
public function insertRedirect() {
// recurse through to only get the final target
- $retval = Title::newFromRedirectRecurse( $this->getRawText() );
+ $content = $this->getContent();
+ $retval = $content ? $content->getUltimateRedirectTarget() : null;
if ( !$retval ) {
return null;
}
@@ -879,7 +959,7 @@ class WikiPage extends Page implements IDBAccessObject {
* @return UserArrayFromResult
*/
public function getContributors() {
- # @todo FIXME: This is expensive; cache this info somewhere.
+ // @todo FIXME: This is expensive; cache this info somewhere.
$dbr = wfGetDB( DB_SLAVE );
@@ -927,7 +1007,7 @@ class WikiPage extends Page implements IDBAccessObject {
/**
* Get the last N authors
* @param $num Integer: number of revisions to get
- * @param $revLatest String: the latest rev_id, selected from the master (optional)
+ * @param string $revLatest the latest rev_id, selected from the master (optional)
* @return array Array of authors, duplicates not removed
*/
public function getLastNAuthors( $num, $revLatest = 0 ) {
@@ -990,7 +1070,7 @@ class WikiPage extends Page implements IDBAccessObject {
&& $parserOptions->getStubThreshold() == 0
&& $this->mTitle->exists()
&& ( $oldid === null || $oldid === 0 || $oldid === $this->getLatest() )
- && $this->mTitle->isWikitextPage();
+ && $this->getContentHandler()->isParserCacheSupported();
}
/**
@@ -1001,6 +1081,7 @@ class WikiPage extends Page implements IDBAccessObject {
* @param $parserOptions ParserOptions to use for the parse operation
* @param $oldid Revision ID to get the text from, passing null or 0 will
* get the current revision (default value)
+ *
* @return ParserOutput or false if the revision was not found
*/
public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
@@ -1042,13 +1123,13 @@ class WikiPage extends Page implements IDBAccessObject {
return;
}
- # Don't update page view counters on views from bot users (bug 14044)
+ // Don't update page view counters on views from bot users (bug 14044)
if ( !$wgDisableCounters && !$user->isAllowed( 'bot' ) && $this->mTitle->exists() ) {
DeferredUpdates::addUpdate( new ViewCountUpdate( $this->getId() ) );
DeferredUpdates::addUpdate( new SiteStatsUpdate( 1, 0, 0 ) );
}
- # Update newtalk / watchlist notification status
+ // Update newtalk / watchlist notification status
$user->clearNotification( $this->mTitle );
}
@@ -1059,7 +1140,7 @@ class WikiPage extends Page implements IDBAccessObject {
public function doPurge() {
global $wgUseSquid;
- if( !wfRunHooks( 'ArticlePurge', array( &$this ) ) ){
+ if( !wfRunHooks( 'ArticlePurge', array( &$this ) ) ) {
return false;
}
@@ -1078,8 +1159,16 @@ class WikiPage extends Page implements IDBAccessObject {
}
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+ // @todo: move this logic to MessageCache
+
if ( $this->mTitle->exists() ) {
- $text = $this->getRawText();
+ // NOTE: use transclusion text for messages.
+ // This is consistent with MessageCache::getMsgFromNamespace()
+
+ $content = $this->getContent();
+ $text = $content === null ? null : $content->getWikitextForTransclusion();
+
+ if ( $text === null ) $text = false;
} else {
$text = false;
}
@@ -1109,12 +1198,12 @@ class WikiPage extends Page implements IDBAccessObject {
'page_title' => $this->mTitle->getDBkey(),
'page_counter' => 0,
'page_restrictions' => '',
- 'page_is_redirect' => 0, # Will set this shortly...
+ 'page_is_redirect' => 0, // Will set this shortly...
'page_is_new' => 1,
'page_random' => wfRandom(),
'page_touched' => $dbw->timestamp(),
- 'page_latest' => 0, # Fill this in shortly...
- 'page_len' => 0, # Fill this in shortly...
+ 'page_latest' => 0, // Fill this in shortly...
+ 'page_len' => 0, // Fill this in shortly...
), __METHOD__, 'IGNORE' );
$affected = $dbw->affectedRows();
@@ -1144,28 +1233,36 @@ class WikiPage extends Page implements IDBAccessObject {
* @private
*/
public function updateRevisionOn( $dbw, $revision, $lastRevision = null, $lastRevIsRedirect = null ) {
+ global $wgContentHandlerUseDB;
+
wfProfileIn( __METHOD__ );
- $text = $revision->getText();
- $len = strlen( $text );
- $rt = Title::newFromRedirectRecurse( $text );
+ $content = $revision->getContent();
+ $len = $content ? $content->getSize() : 0;
+ $rt = $content ? $content->getUltimateRedirectTarget() : null;
$conditions = array( 'page_id' => $this->getId() );
if ( !is_null( $lastRevision ) ) {
- # An extra check against threads stepping on each other
+ // An extra check against threads stepping on each other
$conditions['page_latest'] = $lastRevision;
}
$now = wfTimestampNow();
+ $row = array( /* SET */
+ 'page_latest' => $revision->getId(),
+ 'page_touched' => $dbw->timestamp( $now ),
+ 'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0,
+ 'page_is_redirect' => $rt !== null ? 1 : 0,
+ 'page_len' => $len,
+ );
+
+ if ( $wgContentHandlerUseDB ) {
+ $row[ 'page_content_model' ] = $revision->getContentModel();
+ }
+
$dbw->update( 'page',
- array( /* SET */
- 'page_latest' => $revision->getId(),
- 'page_touched' => $dbw->timestamp( $now ),
- 'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0,
- 'page_is_redirect' => $rt !== null ? 1 : 0,
- 'page_len' => $len,
- ),
+ $row,
$conditions,
__METHOD__ );
@@ -1176,8 +1273,9 @@ class WikiPage extends Page implements IDBAccessObject {
$this->setCachedLastEditTime( $now );
$this->mLatest = $revision->getId();
$this->mIsRedirect = (bool)$rt;
- # Update the LinkCache.
- LinkCache::singleton()->addGoodLinkObj( $this->getId(), $this->mTitle, $len, $this->mIsRedirect, $this->mLatest );
+ // Update the LinkCache.
+ LinkCache::singleton()->addGoodLinkObj( $this->getId(), $this->mTitle, $len, $this->mIsRedirect,
+ $this->mLatest, $revision->getContentModel() );
}
wfProfileOut( __METHOD__ );
@@ -1249,7 +1347,7 @@ class WikiPage extends Page implements IDBAccessObject {
$prev = $row->rev_id;
$lastRevIsRedirect = (bool)$row->page_is_redirect;
} else {
- # No or missing previous revision; mark the page as new
+ // No or missing previous revision; mark the page as new
$prev = 0;
$lastRevIsRedirect = null;
}
@@ -1261,56 +1359,119 @@ class WikiPage extends Page implements IDBAccessObject {
}
/**
+ * Get the content that needs to be saved in order to undo all revisions
+ * between $undo and $undoafter. Revisions must belong to the same page,
+ * must exist and must not be deleted
+ * @param $undo Revision
+ * @param $undoafter Revision Must be an earlier revision than $undo
+ * @return mixed string on success, false on failure
+ * @since 1.21
+ * Before we had the Content object, this was done in getUndoText
+ */
+ public function getUndoContent( Revision $undo, Revision $undoafter = null ) {
+ $handler = $undo->getContentHandler();
+ return $handler->getUndoContent( $this->getRevision(), $undo, $undoafter );
+ }
+
+ /**
* Get the text that needs to be saved in order to undo all revisions
* between $undo and $undoafter. Revisions must belong to the same page,
* must exist and must not be deleted
* @param $undo Revision
* @param $undoafter Revision Must be an earlier revision than $undo
* @return mixed string on success, false on failure
+ * @deprecated since 1.21: use ContentHandler::getUndoContent() instead.
*/
public function getUndoText( Revision $undo, Revision $undoafter = null ) {
- $cur_text = $this->getRawText();
- if ( $cur_text === false ) {
- return false; // no page
- }
- $undo_text = $undo->getText();
- $undoafter_text = $undoafter->getText();
+ ContentHandler::deprecated( __METHOD__, '1.21' );
- if ( $cur_text == $undo_text ) {
- # No use doing a merge if it's just a straight revert.
- return $undoafter_text;
- }
+ $this->loadLastEdit();
- $undone_text = '';
+ if ( $this->mLastRevision ) {
+ if ( is_null( $undoafter ) ) {
+ $undoafter = $undo->getPrevious();
+ }
- if ( !wfMerge( $undo_text, $undoafter_text, $cur_text, $undone_text ) ) {
- return false;
+ $handler = $this->getContentHandler();
+ $undone = $handler->getUndoContent( $this->mLastRevision, $undo, $undoafter );
+
+ if ( !$undone ) {
+ return false;
+ } else {
+ return ContentHandler::getContentText( $undone );
+ }
}
- return $undone_text;
+ return false;
}
/**
* @param $section null|bool|int or a section number (0, 1, 2, T1, T2...)
- * @param $text String: new text of the section
- * @param $sectionTitle String: new section's subject, only if $section is 'new'
- * @param $edittime String: revision timestamp or null to use the current revision
- * @return string Complete article text, or null if error
+ * @param string $text new text of the section
+ * @param string $sectionTitle new section's subject, only if $section is 'new'
+ * @param string $edittime revision timestamp or null to use the current revision
+ * @throws MWException
+ * @return String new complete article text, or null if error
+ *
+ * @deprecated since 1.21, use replaceSectionContent() instead
*/
public function replaceSection( $section, $text, $sectionTitle = '', $edittime = null ) {
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
+ if ( strval( $section ) == '' ) { //NOTE: keep condition in sync with condition in replaceSectionContent!
+ // Whole-page edit; let the whole text through
+ return $text;
+ }
+
+ if ( !$this->supportsSections() ) {
+ throw new MWException( "sections not supported for content model " . $this->getContentHandler()->getModelID() );
+ }
+
+ // could even make section title, but that's not required.
+ $sectionContent = ContentHandler::makeContent( $text, $this->getTitle() );
+
+ $newContent = $this->replaceSectionContent( $section, $sectionContent, $sectionTitle, $edittime );
+
+ return ContentHandler::getContentText( $newContent );
+ }
+
+ /**
+ * Returns true iff this page's content model supports sections.
+ *
+ * @return boolean whether sections are supported.
+ *
+ * @todo: the skin should check this and not offer section functionality if sections are not supported.
+ * @todo: the EditPage should check this and not offer section functionality if sections are not supported.
+ */
+ public function supportsSections() {
+ return $this->getContentHandler()->supportsSections();
+ }
+
+ /**
+ * @param $section null|bool|int or a section number (0, 1, 2, T1, T2...)
+ * @param $sectionContent Content: new content of the section
+ * @param string $sectionTitle new section's subject, only if $section is 'new'
+ * @param string $edittime revision timestamp or null to use the current revision
+ *
+ * @throws MWException
+ * @return Content new complete article content, or null if error
+ *
+ * @since 1.21
+ */
+ public function replaceSectionContent( $section, Content $sectionContent, $sectionTitle = '', $edittime = null ) {
wfProfileIn( __METHOD__ );
if ( strval( $section ) == '' ) {
// Whole-page edit; let the whole text through
+ $newContent = $sectionContent;
} else {
+ if ( !$this->supportsSections() ) {
+ throw new MWException( "sections not supported for content model " . $this->getContentHandler()->getModelID() );
+ }
+
// Bug 30711: always use current version when adding a new section
if ( is_null( $edittime ) || $section == 'new' ) {
- $oldtext = $this->getRawText();
- if ( $oldtext === false ) {
- wfDebug( __METHOD__ . ": no page text\n" );
- wfProfileOut( __METHOD__ );
- return null;
- }
+ $oldContent = $this->getContent();
} else {
$dbw = wfGetDB( DB_MASTER );
$rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
@@ -1322,28 +1483,21 @@ class WikiPage extends Page implements IDBAccessObject {
return null;
}
- $oldtext = $rev->getText();
+ $oldContent = $rev->getContent();
}
- if ( $section == 'new' ) {
- # Inserting a new section
- $subject = $sectionTitle ? wfMessage( 'newsectionheaderdefaultlevel' )
- ->rawParams( $sectionTitle )->inContentLanguage()->text() . "\n\n" : '';
- if ( wfRunHooks( 'PlaceNewSection', array( $this, $oldtext, $subject, &$text ) ) ) {
- $text = strlen( trim( $oldtext ) ) > 0
- ? "{$oldtext}\n\n{$subject}{$text}"
- : "{$subject}{$text}";
- }
- } else {
- # Replacing an existing section; roll out the big guns
- global $wgParser;
-
- $text = $wgParser->replaceSection( $oldtext, $section, $text );
+ if ( ! $oldContent ) {
+ wfDebug( __METHOD__ . ": no page text\n" );
+ wfProfileOut( __METHOD__ );
+ return null;
}
+
+ // FIXME: $oldContent might be null?
+ $newContent = $oldContent->replaceSection( $section, $sectionContent, $sectionTitle );
}
wfProfileOut( __METHOD__ );
- return $text;
+ return $newContent;
}
/**
@@ -1367,8 +1521,8 @@ class WikiPage extends Page implements IDBAccessObject {
* Change an existing article or create a new article. Updates RC and all necessary caches,
* optionally via the deferred update array.
*
- * @param $text String: new text
- * @param $summary String: edit summary
+ * @param string $text new text
+ * @param string $summary edit summary
* @param $flags Integer bitfield:
* EDIT_NEW
* Article is known or assumed to be non-existent, create a new one
@@ -1409,17 +1563,83 @@ class WikiPage extends Page implements IDBAccessObject {
* revision: The revision object for the inserted revision, or null
*
* Compatibility note: this function previously returned a boolean value indicating success/failure
+ *
+ * @deprecated since 1.21: use doEditContent() instead.
*/
public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
+ $content = ContentHandler::makeContent( $text, $this->getTitle() );
+
+ return $this->doEditContent( $content, $summary, $flags, $baseRevId, $user );
+ }
+
+ /**
+ * Change an existing article or create a new article. Updates RC and all necessary caches,
+ * optionally via the deferred update array.
+ *
+ * @param $content Content: new content
+ * @param string $summary edit summary
+ * @param $flags Integer bitfield:
+ * EDIT_NEW
+ * Article is known or assumed to be non-existent, create a new one
+ * EDIT_UPDATE
+ * Article is known or assumed to be pre-existing, update it
+ * EDIT_MINOR
+ * Mark this edit minor, if the user is allowed to do so
+ * EDIT_SUPPRESS_RC
+ * Do not log the change in recentchanges
+ * EDIT_FORCE_BOT
+ * Mark the edit a "bot" edit regardless of user rights
+ * EDIT_DEFER_UPDATES
+ * Defer some of the updates until the end of index.php
+ * EDIT_AUTOSUMMARY
+ * Fill in blank summaries with generated text where possible
+ *
+ * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected.
+ * If EDIT_UPDATE is specified and the article doesn't exist, the function will return an
+ * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an
+ * edit-already-exists error will be returned. These two conditions are also possible with
+ * auto-detection due to MediaWiki's performance-optimised locking strategy.
+ *
+ * @param bool|\the $baseRevId the revision ID this edit was based off, if any
+ * @param $user User the user doing the edit
+ * @param $serialisation_format String: format for storing the content in the database
+ *
+ * @throws MWException
+ * @return Status object. Possible errors:
+ * edit-hook-aborted: The ArticleSave hook aborted the edit but didn't set the fatal flag of $status
+ * edit-gone-missing: In update mode, but the article didn't exist
+ * edit-conflict: In update mode, the article changed unexpectedly
+ * edit-no-change: Warning that the text was the same as before
+ * edit-already-exists: In creation mode, but the article already exists
+ *
+ * Extensions may define additional errors.
+ *
+ * $return->value will contain an associative array with members as follows:
+ * new: Boolean indicating if the function attempted to create a new article
+ * revision: The revision object for the inserted revision, or null
+ *
+ * @since 1.21
+ */
+ public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
+ User $user = null, $serialisation_format = null ) {
global $wgUser, $wgUseAutomaticEditSummaries, $wgUseRCPatrol, $wgUseNPPatrol;
- # Low-level sanity check
+ // Low-level sanity check
if ( $this->mTitle->getText() === '' ) {
throw new MWException( 'Something is trying to edit an article with an empty title' );
}
wfProfileIn( __METHOD__ );
+ if ( !$content->getContentHandler()->canBeUsedOn( $this->getTitle() ) ) {
+ wfProfileOut( __METHOD__ );
+ return Status::newFatal( 'content-not-allowed-here',
+ ContentHandler::getLocalizedName( $content->getModel() ),
+ $this->getTitle()->getPrefixedText() );
+ }
+
$user = is_null( $user ) ? $wgUser : $user;
$status = Status::newGood( array() );
@@ -1430,10 +1650,14 @@ class WikiPage extends Page implements IDBAccessObject {
$flags = $this->checkFlags( $flags );
- if ( !wfRunHooks( 'ArticleSave', array( &$this, &$user, &$text, &$summary,
- $flags & EDIT_MINOR, null, null, &$flags, &$status ) ) )
- {
- wfDebug( __METHOD__ . ": ArticleSave hook aborted save!\n" );
+ // handle hook
+ $hook_args = array( &$this, &$user, &$content, &$summary,
+ $flags & EDIT_MINOR, null, null, &$flags, &$status );
+
+ if ( !wfRunHooks( 'PageContentSave', $hook_args )
+ || !ContentHandler::runLegacyHooks( 'ArticleSave', $hook_args ) ) {
+
+ wfDebug( __METHOD__ . ": ArticleSave or ArticleSaveContent hook aborted save!\n" );
if ( $status->isOK() ) {
$status->fatal( 'edit-hook-aborted' );
@@ -1443,77 +1667,98 @@ class WikiPage extends Page implements IDBAccessObject {
return $status;
}
- # Silently ignore EDIT_MINOR if not allowed
+ // Silently ignore EDIT_MINOR if not allowed
$isminor = ( $flags & EDIT_MINOR ) && $user->isAllowed( 'minoredit' );
$bot = $flags & EDIT_FORCE_BOT;
- $oldtext = $this->getRawText(); // current revision
- $oldsize = strlen( $oldtext );
+ $old_content = $this->getContent( Revision::RAW ); // current revision's content
+
+ $oldsize = $old_content ? $old_content->getSize() : 0;
$oldid = $this->getLatest();
$oldIsRedirect = $this->isRedirect();
$oldcountable = $this->isCountable();
- # Provide autosummaries if one is not provided and autosummaries are enabled.
+ $handler = $content->getContentHandler();
+
+ // Provide autosummaries if one is not provided and autosummaries are enabled.
if ( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) {
- $summary = self::getAutosummary( $oldtext, $text, $flags );
+ if ( !$old_content ) $old_content = null;
+ $summary = $handler->getAutosummary( $old_content, $content, $flags );
}
- $editInfo = $this->prepareTextForEdit( $text, null, $user );
- $text = $editInfo->pst;
- $newsize = strlen( $text );
+ $editInfo = $this->prepareContentForEdit( $content, null, $user, $serialisation_format );
+ $serialized = $editInfo->pst;
+ $content = $editInfo->pstContent;
+ $newsize = $content->getSize();
$dbw = wfGetDB( DB_MASTER );
$now = wfTimestampNow();
$this->mTimestamp = $now;
if ( $flags & EDIT_UPDATE ) {
- # Update article, but only if changed.
+ // Update article, but only if changed.
$status->value['new'] = false;
if ( !$oldid ) {
- # Article gone missing
+ // Article gone missing
wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" );
$status->fatal( 'edit-gone-missing' );
wfProfileOut( __METHOD__ );
return $status;
- } elseif ( $oldtext === false ) {
- # Sanity check for bug 37225
+ } elseif ( !$old_content ) {
+ // Sanity check for bug 37225
wfProfileOut( __METHOD__ );
throw new MWException( "Could not find text for current revision {$oldid}." );
}
$revision = new Revision( array(
'page' => $this->getId(),
+ 'title' => $this->getTitle(), // for determining the default content model
'comment' => $summary,
'minor_edit' => $isminor,
- 'text' => $text,
+ 'text' => $serialized,
+ 'len' => $newsize,
'parent_id' => $oldid,
'user' => $user->getId(),
'user_text' => $user->getName(),
- 'timestamp' => $now
- ) );
- # Bug 37225: use accessor to get the text as Revision may trim it.
- # After trimming, the text may be a duplicate of the current text.
- $text = $revision->getText(); // sanity; EditPage should trim already
+ 'timestamp' => $now,
+ 'content_model' => $content->getModel(),
+ 'content_format' => $serialisation_format,
+ ) ); // XXX: pass content object?!
- $changed = ( strcmp( $text, $oldtext ) != 0 );
+ $changed = !$content->equals( $old_content );
if ( $changed ) {
+ if ( !$content->isValid() ) {
+ throw new MWException( "New content failed validity check!" );
+ }
+
$dbw->begin( __METHOD__ );
+
+ $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
+ $status->merge( $prepStatus );
+
+ if ( !$status->isOK() ) {
+ $dbw->rollback( __METHOD__ );
+
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
$revisionId = $revision->insertOn( $dbw );
- # Update page
- #
- # Note that we use $this->mLatest instead of fetching a value from the master DB
- # during the course of this function. This makes sure that EditPage can detect
- # edit conflicts reliably, either by $ok here, or by $article->getTimestamp()
- # before this function is called. A previous function used a separate query, this
- # creates a window where concurrent edits can cause an ignored edit conflict.
+ // Update page
+ //
+ // Note that we use $this->mLatest instead of fetching a value from the master DB
+ // during the course of this function. This makes sure that EditPage can detect
+ // edit conflicts reliably, either by $ok here, or by $article->getTimestamp()
+ // before this function is called. A previous function used a separate query, this
+ // creates a window where concurrent edits can cause an ignored edit conflict.
$ok = $this->updateRevisionOn( $dbw, $revision, $oldid, $oldIsRedirect );
if ( !$ok ) {
- # Belated edit conflict! Run away!!
+ // Belated edit conflict! Run away!!
$status->fatal( 'edit-conflict' );
$dbw->rollback( __METHOD__ );
@@ -1523,18 +1768,18 @@ class WikiPage extends Page implements IDBAccessObject {
}
wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
- # Update recentchanges
+ // Update recentchanges
if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
- # Mark as patrolled if the user can do so
+ // Mark as patrolled if the user can do so
$patrolled = $wgUseRCPatrol && !count(
$this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
- # Add RC row to the DB
+ // Add RC row to the DB
$rc = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $user, $summary,
$oldid, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
$revisionId, $patrolled
);
- # Log auto-patrolled edits
+ // Log auto-patrolled edits
if ( $patrolled ) {
PatrolLog::record( $rc, true, $user );
}
@@ -1547,9 +1792,15 @@ class WikiPage extends Page implements IDBAccessObject {
$revision->setId( $this->getLatest() );
}
- # Update links tables, site stats, etc.
- $this->doEditUpdates( $revision, $user, array( 'changed' => $changed,
- 'oldcountable' => $oldcountable ) );
+ // Update links tables, site stats, etc.
+ $this->doEditUpdates(
+ $revision,
+ $user,
+ array(
+ 'changed' => $changed,
+ 'oldcountable' => $oldcountable
+ )
+ );
if ( !$changed ) {
$status->warning( 'edit-no-change' );
@@ -1559,13 +1810,25 @@ class WikiPage extends Page implements IDBAccessObject {
$this->mTitle->invalidateCache();
}
} else {
- # Create new article
+ // Create new article
$status->value['new'] = true;
$dbw->begin( __METHOD__ );
- # Add the page record; stake our claim on this title!
- # This will return false if the article already exists
+ $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
+ $status->merge( $prepStatus );
+
+ if ( !$status->isOK() ) {
+ $dbw->rollback( __METHOD__ );
+
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ $status->merge( $prepStatus );
+
+ // Add the page record; stake our claim on this title!
+ // This will return false if the article already exists
$newid = $this->insertOn( $dbw );
if ( $newid === false ) {
@@ -1576,36 +1839,44 @@ class WikiPage extends Page implements IDBAccessObject {
return $status;
}
- # Save the revision text...
+ // Save the revision text...
$revision = new Revision( array(
'page' => $newid,
+ 'title' => $this->getTitle(), // for determining the default content model
'comment' => $summary,
'minor_edit' => $isminor,
- 'text' => $text,
+ 'text' => $serialized,
+ 'len' => $newsize,
'user' => $user->getId(),
'user_text' => $user->getName(),
- 'timestamp' => $now
+ 'timestamp' => $now,
+ 'content_model' => $content->getModel(),
+ 'content_format' => $serialisation_format,
) );
$revisionId = $revision->insertOn( $dbw );
- # Bug 37225: use accessor to get the text as Revision may trim it
- $text = $revision->getText(); // sanity; EditPage should trim already
+ // Bug 37225: use accessor to get the text as Revision may trim it
+ $content = $revision->getContent(); // sanity; get normalized version
- # Update the page record with revision data
+ if ( $content ) {
+ $newsize = $content->getSize();
+ }
+
+ // Update the page record with revision data
$this->updateRevisionOn( $dbw, $revision, 0 );
wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
- # Update recentchanges
+ // Update recentchanges
if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
- # Mark as patrolled if the user can do so
+ // Mark as patrolled if the user can do so
$patrolled = ( $wgUseRCPatrol || $wgUseNPPatrol ) && !count(
$this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
- # Add RC row to the DB
+ // Add RC row to the DB
$rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot,
- '', strlen( $text ), $revisionId, $patrolled );
+ '', $newsize, $revisionId, $patrolled );
- # Log auto-patrolled edits
+ // Log auto-patrolled edits
if ( $patrolled ) {
PatrolLog::record( $rc, true, $user );
}
@@ -1613,14 +1884,17 @@ class WikiPage extends Page implements IDBAccessObject {
$user->incEditCount();
$dbw->commit( __METHOD__ );
- # Update links, etc.
+ // Update links, etc.
$this->doEditUpdates( $revision, $user, array( 'created' => true ) );
- wfRunHooks( 'ArticleInsertComplete', array( &$this, &$user, $text, $summary,
- $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
+ $hook_args = array( &$this, &$user, $content, $summary,
+ $flags & EDIT_MINOR, null, null, &$flags, $revision );
+
+ ContentHandler::runLegacyHooks( 'ArticleInsertComplete', $hook_args );
+ wfRunHooks( 'PageContentInsertComplete', $hook_args );
}
- # Do updates right now unless deferral was requested
+ // Do updates right now unless deferral was requested
if ( !( $flags & EDIT_DEFER_UPDATES ) ) {
DeferredUpdates::doUpdates();
}
@@ -1628,10 +1902,13 @@ class WikiPage extends Page implements IDBAccessObject {
// Return the new revision (or null) to the caller
$status->value['revision'] = $revision;
- wfRunHooks( 'ArticleSaveComplete', array( &$this, &$user, $text, $summary,
- $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) );
+ $hook_args = array( &$this, &$user, $content, $summary,
+ $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId );
- # Promote user to any groups they meet the criteria for
+ ContentHandler::runLegacyHooks( 'ArticleSaveComplete', $hook_args );
+ wfRunHooks( 'PageContentSaveComplete', $hook_args );
+
+ // Promote user to any groups they meet the criteria for
$user->addAutopromoteOnceGroups( 'onEdit' );
wfProfileOut( __METHOD__ );
@@ -1641,6 +1918,8 @@ class WikiPage extends Page implements IDBAccessObject {
/**
* Get parser options suitable for rendering the primary article wikitext
*
+ * @see ContentHandler::makeParserOptions
+ *
* @param IContextSource|User|string $context One of the following:
* - IContextSource: Use the User and the Language of the provided
* context
@@ -1651,38 +1930,52 @@ class WikiPage extends Page implements IDBAccessObject {
* @return ParserOptions
*/
public function makeParserOptions( $context ) {
- global $wgContLang;
-
- if ( $context instanceof IContextSource ) {
- $options = ParserOptions::newFromContext( $context );
- } elseif ( $context instanceof User ) { // settings per user (even anons)
- $options = ParserOptions::newFromUser( $context );
- } else { // canonical settings
- $options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
- }
+ $options = $this->getContentHandler()->makeParserOptions( $context );
if ( $this->getTitle()->isConversionTable() ) {
+ //@todo: ConversionTable should become a separate content model, so we don't need special cases like this one.
$options->disableContentConversion();
}
- $options->enableLimitReport(); // show inclusion/loop reports
- $options->setTidy( true ); // fix bad HTML
-
return $options;
}
/**
* Prepare text which is about to be saved.
* Returns a stdclass with source, pst and output members
- * @return bool|object
+ *
+ * @deprecated in 1.21: use prepareContentForEdit instead.
*/
public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
- global $wgParser, $wgContLang, $wgUser;
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+ $content = ContentHandler::makeContent( $text, $this->getTitle() );
+ return $this->prepareContentForEdit( $content, $revid, $user );
+ }
+
+ /**
+ * Prepare content which is about to be saved.
+ * Returns a stdclass with source, pst and output members
+ *
+ * @param \Content $content
+ * @param null $revid
+ * @param null|\User $user
+ * @param null $serialization_format
+ *
+ * @return bool|object
+ *
+ * @since 1.21
+ */
+ public function prepareContentForEdit( Content $content, $revid = null, User $user = null, $serialization_format = null ) {
+ global $wgContLang, $wgUser;
$user = is_null( $user ) ? $wgUser : $user;
- // @TODO fixme: check $user->getId() here???
+ //XXX: check $user->getId() here???
+
if ( $this->mPreparedEdit
- && $this->mPreparedEdit->newText == $text
+ && $this->mPreparedEdit->newContent
+ && $this->mPreparedEdit->newContent->equals( $content )
&& $this->mPreparedEdit->revid == $revid
+ && $this->mPreparedEdit->format == $serialization_format
+ // XXX: also check $user here?
) {
// Already prepared
return $this->mPreparedEdit;
@@ -1693,14 +1986,22 @@ class WikiPage extends Page implements IDBAccessObject {
$edit = (object)array();
$edit->revid = $revid;
- $edit->newText = $text;
- $edit->pst = $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
+
+ $edit->pstContent = $content ? $content->preSaveTransform( $this->mTitle, $user, $popts ) : null;
+
+ $edit->format = $serialization_format;
$edit->popts = $this->makeParserOptions( 'canonical' );
- $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $edit->popts, true, true, $revid );
- $edit->oldText = $this->getRawText();
+ $edit->output = $edit->pstContent ? $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts ) : null;
- $this->mPreparedEdit = $edit;
+ $edit->newContent = $content;
+ $edit->oldContent = $this->getContent( Revision::RAW );
+ // NOTE: B/C for hooks! don't use these fields!
+ $edit->newText = $edit->newContent ? ContentHandler::getContentText( $edit->newContent ) : '';
+ $edit->oldText = $edit->oldContent ? ContentHandler::getContentText( $edit->oldContent ) : '';
+ $edit->pst = $edit->pstContent ? $edit->pstContent->serialize( $serialization_format ) : '';
+
+ $this->mPreparedEdit = $edit;
return $edit;
}
@@ -1710,10 +2011,9 @@ class WikiPage extends Page implements IDBAccessObject {
* Purges pages that include this page if the text was changed here.
* Every 100th edit, prune the recent changes table.
*
- * @private
* @param $revision Revision object
* @param $user User object that did the revision
- * @param $options Array of options, following indexes are used:
+ * @param array $options of options, following indexes are used:
* - changed: boolean, whether the revision changed the content (default true)
* - created: boolean, whether the revision created the page (default false)
* - oldcountable: boolean or null (default null):
@@ -1727,27 +2027,29 @@ class WikiPage extends Page implements IDBAccessObject {
wfProfileIn( __METHOD__ );
$options += array( 'changed' => true, 'created' => false, 'oldcountable' => null );
- $text = $revision->getText();
+ $content = $revision->getContent();
- # Parse the text
- # Be careful not to double-PST: $text is usually already PST-ed once
+ // Parse the text
+ // Be careful not to double-PST: $text is usually already PST-ed once
if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
- $editInfo = $this->prepareTextForEdit( $text, $revision->getId(), $user );
+ $editInfo = $this->prepareContentForEdit( $content, $revision->getId(), $user );
} else {
wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
$editInfo = $this->mPreparedEdit;
}
- # Save it to the parser cache
+ // Save it to the parser cache
if ( $wgEnableParserCache ) {
$parserCache = ParserCache::singleton();
$parserCache->save( $editInfo->output, $this, $editInfo->popts );
}
- # Update the links tables and other secondary data
- $updates = $editInfo->output->getSecondaryDataUpdates( $this->mTitle );
- DataUpdate::runUpdates( $updates );
+ // Update the links tables and other secondary data
+ if ( $content ) {
+ $updates = $content->getSecondaryDataUpdates( $this->getTitle(), null, true, $editInfo->output );
+ DataUpdate::runUpdates( $updates );
+ }
wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
@@ -1761,7 +2063,7 @@ class WikiPage extends Page implements IDBAccessObject {
$cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
$dbw->delete(
'recentchanges',
- array( "rc_timestamp < '$cutoff'" ),
+ array( 'rc_timestamp < ' . $dbw->addQuotes( $cutoff ) ),
__METHOD__
);
}
@@ -1791,11 +2093,12 @@ class WikiPage extends Page implements IDBAccessObject {
}
DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, $good, $total ) );
- DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $text ) );
+ DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $content->getTextForSearchIndex() ) );
+ // @TODO: let the search engine decide what to do with the content object
- # If this is another user's talk page, update newtalk.
- # Don't do this if $options['changed'] = false (null-edits) nor if
- # it's a minor edit and the user doesn't want notifications for those.
+ // If this is another user's talk page, update newtalk.
+ // Don't do this if $options['changed'] = false (null-edits) nor if
+ // it's a minor edit and the user doesn't want notifications for those.
if ( $options['changed']
&& $this->mTitle->getNamespace() == NS_USER_TALK
&& $shortTitle != $user->getTitleKey()
@@ -1817,7 +2120,11 @@ class WikiPage extends Page implements IDBAccessObject {
}
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
- MessageCache::singleton()->replace( $shortTitle, $text );
+ // XXX: could skip pseudo-messages like js/css here, based on content model.
+ $msgtext = $content ? $content->getWikitextForTransclusion() : null;
+ if ( $msgtext === false || $msgtext === null ) $msgtext = '';
+
+ MessageCache::singleton()->replace( $shortTitle, $msgtext );
}
if( $options['created'] ) {
@@ -1834,21 +2141,45 @@ class WikiPage extends Page implements IDBAccessObject {
* The article must already exist; link tables etc
* are not updated, caches are not flushed.
*
- * @param $text String: text submitted
+ * @param string $text text submitted
* @param $user User The relevant user
- * @param $comment String: comment submitted
+ * @param string $comment comment submitted
* @param $minor Boolean: whereas it's a minor modification
+ *
+ * @deprecated since 1.21, use doEditContent() instead.
*/
public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) {
+ ContentHandler::deprecated( __METHOD__, "1.21" );
+
+ $content = ContentHandler::makeContent( $text, $this->getTitle() );
+ return $this->doQuickEditContent( $content, $user, $comment, $minor );
+ }
+
+ /**
+ * Edit an article without doing all that other stuff
+ * The article must already exist; link tables etc
+ * are not updated, caches are not flushed.
+ *
+ * @param $content Content: content submitted
+ * @param $user User The relevant user
+ * @param string $comment comment submitted
+ * @param $serialisation_format String: format for storing the content in the database
+ * @param $minor Boolean: whereas it's a minor modification
+ */
+ public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = 0, $serialisation_format = null ) {
wfProfileIn( __METHOD__ );
+ $serialized = $content->serialize( $serialisation_format );
+
$dbw = wfGetDB( DB_MASTER );
$revision = new Revision( array(
+ 'title' => $this->getTitle(), // for determining the default content model
'page' => $this->getId(),
- 'text' => $text,
+ 'text' => $serialized,
+ 'length' => $content->getSize(),
'comment' => $comment,
'minor_edit' => $minor ? 1 : 0,
- ) );
+ ) ); // XXX: set the content object?
$revision->insertOn( $dbw );
$this->updateRevisionOn( $dbw, $revision );
@@ -1861,10 +2192,10 @@ class WikiPage extends Page implements IDBAccessObject {
* Update the article's restriction field, and leave a log entry.
* This works for protection both existing and non-existing pages.
*
- * @param $limit Array: set of restriction keys
+ * @param array $limit set of restriction keys
* @param $reason String
* @param &$cascade Integer. Set to false if cascading protection isn't allowed.
- * @param $expiry Array: per restriction type expiration
+ * @param array $expiry per restriction type expiration
* @param $user User The user updating the restrictions
* @return Status
*/
@@ -1886,8 +2217,8 @@ class WikiPage extends Page implements IDBAccessObject {
// Take this opportunity to purge out expired restrictions
Title::purgeExpiredRestrictions();
- # @todo FIXME: Same limitations as described in ProtectionForm.php (line 37);
- # we expect a single selection, but the schema allows otherwise.
+ // @todo FIXME: Same limitations as described in ProtectionForm.php (line 37);
+ // we expect a single selection, but the schema allows otherwise.
$isProtected = false;
$protect = false;
$changed = false;
@@ -1904,7 +2235,7 @@ class WikiPage extends Page implements IDBAccessObject {
$protect = true;
}
- # Get current restrictions on $action
+ // Get current restrictions on $action
$current = implode( '', $this->mTitle->getRestrictions( $action ) );
if ( $current != '' ) {
$isProtected = true;
@@ -1913,9 +2244,9 @@ class WikiPage extends Page implements IDBAccessObject {
if ( $limit[$action] != $current ) {
$changed = true;
} elseif ( $limit[$action] != '' ) {
- # Only check expiry change if the action is actually being
- # protected, since expiry does nothing on an not-protected
- # action.
+ // Only check expiry change if the action is actually being
+ // protected, since expiry does nothing on an not-protected
+ // action.
if ( $this->mTitle->getRestrictionExpiry( $action ) != $expiry[$action] ) {
$changed = true;
}
@@ -1926,12 +2257,12 @@ class WikiPage extends Page implements IDBAccessObject {
$changed = true;
}
- # If nothing's changed, do nothing
+ // If nothing has changed, do nothing
if ( !$changed ) {
return Status::newGood();
}
- if ( !$protect ) { # No protection at all means unprotection
+ if ( !$protect ) { // No protection at all means unprotection
$revCommentMsg = 'unprotectedarticle';
$logAction = 'unprotect';
} elseif ( $isProtected ) {
@@ -1944,42 +2275,64 @@ class WikiPage extends Page implements IDBAccessObject {
$encodedExpiry = array();
$protectDescription = '';
+ # Some bots may parse IRC lines, which are generated from log entries which contain plain
+ # protect description text. Keep them in old format to avoid breaking compatibility.
+ # TODO: Fix protection log to store structured description and format it on-the-fly.
+ $protectDescriptionLog = '';
foreach ( $limit as $action => $restrictions ) {
$encodedExpiry[$action] = $dbw->encodeExpiry( $expiry[$action] );
if ( $restrictions != '' ) {
- $protectDescription .= $wgContLang->getDirMark() . "[$action=$restrictions] (";
+ $protectDescriptionLog .= $wgContLang->getDirMark() . "[$action=$restrictions] (";
+ # $action is one of $wgRestrictionTypes = array( 'create', 'edit', 'move', 'upload' ).
+ # All possible message keys are listed here for easier grepping:
+ # * restriction-create
+ # * restriction-edit
+ # * restriction-move
+ # * restriction-upload
+ $actionText = wfMessage( 'restriction-' . $action )->inContentLanguage()->text();
+ # $restrictions is one of $wgRestrictionLevels = array( '', 'autoconfirmed', 'sysop' ),
+ # with '' filtered out. All possible message keys are listed below:
+ # * protect-level-autoconfirmed
+ # * protect-level-sysop
+ $restrictionsText = wfMessage( 'protect-level-' . $restrictions )->inContentLanguage()->text();
if ( $encodedExpiry[$action] != 'infinity' ) {
- $protectDescription .= wfMessage(
+ $expiryText = wfMessage(
'protect-expiring',
- $wgContLang->timeanddate( $expiry[$action], false, false ) ,
- $wgContLang->date( $expiry[$action], false, false ) ,
+ $wgContLang->timeanddate( $expiry[$action], false, false ),
+ $wgContLang->date( $expiry[$action], false, false ),
$wgContLang->time( $expiry[$action], false, false )
)->inContentLanguage()->text();
} else {
- $protectDescription .= wfMessage( 'protect-expiry-indefinite' )
+ $expiryText = wfMessage( 'protect-expiry-indefinite' )
->inContentLanguage()->text();
}
- $protectDescription .= ') ';
+ if ( $protectDescription !== '' ) {
+ $protectDescription .= wfMessage( 'word-separator' )->inContentLanguage()->text();
+ }
+ $protectDescription .= wfMessage( 'protect-summary-desc' )
+ ->params( $actionText, $restrictionsText, $expiryText )
+ ->inContentLanguage()->text();
+ $protectDescriptionLog .= $expiryText . ') ';
}
}
- $protectDescription = trim( $protectDescription );
+ $protectDescriptionLog = trim( $protectDescriptionLog );
- if ( $id ) { # Protection of existing page
+ if ( $id ) { // Protection of existing page
if ( !wfRunHooks( 'ArticleProtect', array( &$this, &$user, $limit, $reason ) ) ) {
return Status::newGood();
}
- # Only restrictions with the 'protect' right can cascade...
- # Otherwise, people who cannot normally protect can "protect" pages via transclusion
+ // Only restrictions with the 'protect' right can cascade...
+ // Otherwise, people who cannot normally protect can "protect" pages via transclusion
$editrestriction = isset( $limit['edit'] ) ? array( $limit['edit'] ) : $this->mTitle->getRestrictions( 'edit' );
- # The schema allows multiple restrictions
+ // The schema allows multiple restrictions
if ( !in_array( 'protect', $editrestriction ) && !in_array( 'sysop', $editrestriction ) ) {
$cascade = false;
}
- # Update restrictions table
+ // Update restrictions table
foreach ( $limit as $action => $restrictions ) {
if ( $restrictions != '' ) {
$dbw->replace( 'page_restrictions', array( array( 'pr_page', 'pr_type' ) ),
@@ -1997,7 +2350,7 @@ class WikiPage extends Page implements IDBAccessObject {
}
}
- # Prepare a null revision to be added to the history
+ // Prepare a null revision to be added to the history
$editComment = $wgContLang->ucfirst(
wfMessage(
$revCommentMsg,
@@ -2005,23 +2358,25 @@ class WikiPage extends Page implements IDBAccessObject {
)->inContentLanguage()->text()
);
if ( $reason ) {
- $editComment .= ": $reason";
+ $editComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
}
if ( $protectDescription ) {
- $editComment .= " ($protectDescription)";
+ $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
+ $editComment .= wfMessage( 'parentheses' )->params( $protectDescription )->inContentLanguage()->text();
}
if ( $cascade ) {
- // FIXME: Should use 'brackets' message.
- $editComment .= ' [' . wfMessage( 'protect-summary-cascade' )
- ->inContentLanguage()->text() . ']';
+ $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
+ $editComment .= wfMessage( 'brackets' )->params(
+ wfMessage( 'protect-summary-cascade' )->inContentLanguage()->text()
+ )->inContentLanguage()->text();
}
- # Insert a null revision
+ // Insert a null revision
$nullRevision = Revision::newNullRevision( $dbw, $id, $editComment, true );
$nullRevId = $nullRevision->insertOn( $dbw );
$latest = $this->getLatest();
- # Update page record
+ // Update page record
$dbw->update( 'page',
array( /* SET */
'page_touched' => $dbw->timestamp(),
@@ -2034,8 +2389,8 @@ class WikiPage extends Page implements IDBAccessObject {
wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $user ) );
wfRunHooks( 'ArticleProtectComplete', array( &$this, &$user, $limit, $reason ) );
- } else { # Protection of non-existing page (also known as "title protection")
- # Cascade protection is meaningless in this case
+ } else { // Protection of non-existing page (also known as "title protection")
+ // Cascade protection is meaningless in this case
$cascade = false;
if ( $limit['create'] != '' ) {
@@ -2066,10 +2421,10 @@ class WikiPage extends Page implements IDBAccessObject {
if ( $logAction == 'unprotect' ) {
$logParams = array();
} else {
- $logParams = array( $protectDescription, $cascade ? 'cascade' : '' );
+ $logParams = array( $protectDescriptionLog, $cascade ? 'cascade' : '' );
}
- # Update the protection log
+ // Update the protection log
$log = new LogPage( 'protect' );
$log->addEntry( $logAction, $this->mTitle, trim( $reason ), $logParams, $user );
@@ -2107,10 +2462,10 @@ class WikiPage extends Page implements IDBAccessObject {
*
* Deletes the article with database consistency, writes logs, purges caches
*
- * @param $reason string delete reason for deletion log
+ * @param string $reason delete reason for deletion log
* @param $suppress boolean suppress all revisions and log the deletion in
* the suppression log instead of the deletion log
- * @param $id int article ID
+ * @param int $id article ID
* @param $commit boolean defaults to true, triggers transaction end
* @param &$error Array of errors to append to
* @param $user User The deleting user
@@ -2129,9 +2484,10 @@ class WikiPage extends Page implements IDBAccessObject {
*
* @since 1.19
*
- * @param $reason string delete reason for deletion log
+ * @param string $reason delete reason for deletion log
* @param $suppress boolean suppress all revisions and log the deletion in
* the suppression log instead of the deletion log
+ * @param int $id article ID
* @param $commit boolean defaults to true, triggers transaction end
* @param &$error Array of errors to append to
* @param $user User The deleting user
@@ -2142,7 +2498,7 @@ class WikiPage extends Page implements IDBAccessObject {
public function doDeleteArticleReal(
$reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
) {
- global $wgUser;
+ global $wgUser, $wgContentHandlerUseDB;
wfDebug( __METHOD__ . "\n" );
@@ -2183,6 +2539,9 @@ class WikiPage extends Page implements IDBAccessObject {
$bitfield = 'rev_deleted';
}
+ // we need to remember the old content so we can use it to generate all deletion updates.
+ $content = $this->getContent( Revision::RAW );
+
$dbw = wfGetDB( DB_MASTER );
$dbw->begin( __METHOD__ );
// For now, shunt the revision data into the archive table.
@@ -2195,31 +2554,40 @@ class WikiPage extends Page implements IDBAccessObject {
//
// In the future, we may keep revisions and mark them with
// the rev_deleted field, which is reserved for this purpose.
+
+ $row = array(
+ 'ar_namespace' => 'page_namespace',
+ 'ar_title' => 'page_title',
+ 'ar_comment' => 'rev_comment',
+ 'ar_user' => 'rev_user',
+ 'ar_user_text' => 'rev_user_text',
+ 'ar_timestamp' => 'rev_timestamp',
+ 'ar_minor_edit' => 'rev_minor_edit',
+ 'ar_rev_id' => 'rev_id',
+ 'ar_parent_id' => 'rev_parent_id',
+ 'ar_text_id' => 'rev_text_id',
+ 'ar_text' => '\'\'', // Be explicit to appease
+ 'ar_flags' => '\'\'', // MySQL's "strict mode"...
+ 'ar_len' => 'rev_len',
+ 'ar_page_id' => 'page_id',
+ 'ar_deleted' => $bitfield,
+ 'ar_sha1' => 'rev_sha1',
+ );
+
+ if ( $wgContentHandlerUseDB ) {
+ $row[ 'ar_content_model' ] = 'rev_content_model';
+ $row[ 'ar_content_format' ] = 'rev_content_format';
+ }
+
$dbw->insertSelect( 'archive', array( 'page', 'revision' ),
+ $row,
array(
- 'ar_namespace' => 'page_namespace',
- 'ar_title' => 'page_title',
- 'ar_comment' => 'rev_comment',
- 'ar_user' => 'rev_user',
- 'ar_user_text' => 'rev_user_text',
- 'ar_timestamp' => 'rev_timestamp',
- 'ar_minor_edit' => 'rev_minor_edit',
- 'ar_rev_id' => 'rev_id',
- 'ar_parent_id' => 'rev_parent_id',
- 'ar_text_id' => 'rev_text_id',
- 'ar_text' => '\'\'', // Be explicit to appease
- 'ar_flags' => '\'\'', // MySQL's "strict mode"...
- 'ar_len' => 'rev_len',
- 'ar_page_id' => 'page_id',
- 'ar_deleted' => $bitfield,
- 'ar_sha1' => 'rev_sha1'
- ), array(
'page_id' => $id,
'page_id = rev_page'
), __METHOD__
);
- # Now that it's safely backed up, delete it
+ // Now that it's safely backed up, delete it
$dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__ );
$ok = ( $dbw->affectedRows() > 0 ); // getArticleID() uses slave, could be laggy
@@ -2229,9 +2597,9 @@ class WikiPage extends Page implements IDBAccessObject {
return $status;
}
- $this->doDeleteUpdates( $id );
+ $this->doDeleteUpdates( $id, $content );
- # Log the deletion, if the page was suppressed, log it at Oversight instead
+ // Log the deletion, if the page was suppressed, log it at Oversight instead
$logtype = $suppress ? 'suppress' : 'delete';
$logEntry = new ManualLogEntry( $logtype, 'delete' );
@@ -2245,7 +2613,7 @@ class WikiPage extends Page implements IDBAccessObject {
$dbw->commit( __METHOD__ );
}
- wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id ) );
+ wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id, $content, $logEntry ) );
$status->value = $logid;
return $status;
}
@@ -2253,36 +2621,28 @@ class WikiPage extends Page implements IDBAccessObject {
/**
* Do some database updates after deletion
*
- * @param $id Int: page_id value of the page being deleted (B/C, currently unused)
+ * @param int $id page_id value of the page being deleted (B/C, currently unused)
+ * @param $content Content: optional page content to be used when determining the required updates.
+ * This may be needed because $this->getContent() may already return null when the page proper was deleted.
*/
- public function doDeleteUpdates( $id ) {
- # update site status
+ public function doDeleteUpdates( $id, Content $content = null ) {
+ // update site status
DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 ) );
- # remove secondary indexes, etc
- $updates = $this->getDeletionUpdates( );
+ // remove secondary indexes, etc
+ $updates = $this->getDeletionUpdates( $content );
DataUpdate::runUpdates( $updates );
- # Clear caches
+ // Clear caches
WikiPage::onArticleDelete( $this->mTitle );
- # Reset this object
+ // Reset this object
$this->clear();
- # Clear the cached article id so the interface doesn't act like we exist
+ // Clear the cached article id so the interface doesn't act like we exist
$this->mTitle->resetArticleID( 0 );
}
- public function getDeletionUpdates() {
- $updates = array(
- new LinksDeletionUpdate( $this ),
- );
-
- //@todo: make a hook to add update objects
- //NOTE: deletion updates will be determined by the ContentHandler in the future
- return $updates;
- }
-
/**
* Roll back the most recent consecutive set of edits to a page
* from the same user; fails if there are no eligible edits to
@@ -2290,14 +2650,14 @@ class WikiPage extends Page implements IDBAccessObject {
* performs permissions checks on $user, then calls commitRollback()
* to do the dirty work
*
- * @todo: seperate the business/permission stuff out from backend code
+ * @todo: separate the business/permission stuff out from backend code
*
- * @param $fromP String: Name of the user whose edits to rollback.
- * @param $summary String: Custom summary. Set to default summary if empty.
- * @param $token String: Rollback token.
+ * @param string $fromP Name of the user whose edits to rollback.
+ * @param string $summary Custom summary. Set to default summary if empty.
+ * @param string $token Rollback token.
* @param $bot Boolean: If true, mark all reverted edits as bot.
*
- * @param $resultDetails Array: contains result-specific array of additional values
+ * @param array $resultDetails contains result-specific array of additional values
* 'alreadyrolled' : 'current' (rev)
* success : 'summary' (str), 'current' (rev), 'target' (rev)
*
@@ -2312,7 +2672,7 @@ class WikiPage extends Page implements IDBAccessObject {
) {
$resultDetails = null;
- # Check permissions
+ // Check permissions
$editErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $user );
$rollbackErrors = $this->mTitle->getUserPermissionsErrors( 'rollback', $user );
$errors = array_merge( $editErrors, wfArrayDiff2( $rollbackErrors, $editErrors ) );
@@ -2325,7 +2685,7 @@ class WikiPage extends Page implements IDBAccessObject {
$errors[] = array( 'actionthrottledtext' );
}
- # If there were errors, bail out now
+ // If there were errors, bail out now
if ( !empty( $errors ) ) {
return $errors;
}
@@ -2341,11 +2701,11 @@ class WikiPage extends Page implements IDBAccessObject {
* rollback to the DB. Therefore, you should only call this function direct-
* ly if you want to use custom permissions checks. If you don't, use
* doRollback() instead.
- * @param $fromP String: Name of the user whose edits to rollback.
- * @param $summary String: Custom summary. Set to default summary if empty.
+ * @param string $fromP Name of the user whose edits to rollback.
+ * @param string $summary Custom summary. Set to default summary if empty.
* @param $bot Boolean: If true, mark all reverted edits as bot.
*
- * @param $resultDetails Array: contains result-specific array of additional values
+ * @param array $resultDetails contains result-specific array of additional values
* @param $guser User The user performing the rollback
* @return array
*/
@@ -2358,16 +2718,16 @@ class WikiPage extends Page implements IDBAccessObject {
return array( array( 'readonlytext' ) );
}
- # Get the last editor
+ // Get the last editor
$current = $this->getRevision();
if ( is_null( $current ) ) {
- # Something wrong... no page?
+ // Something wrong... no page?
return array( array( 'notanarticle' ) );
}
$from = str_replace( '_', ' ', $fromP );
- # User name given should match up with the top revision.
- # If the user was deleted then $from should be empty.
+ // User name given should match up with the top revision.
+ // If the user was deleted then $from should be empty.
if ( $from != $current->getUserText() ) {
$resultDetails = array( 'current' => $current );
return array( array( 'alreadyrolled',
@@ -2377,8 +2737,8 @@ class WikiPage extends Page implements IDBAccessObject {
) );
}
- # Get the last edit not by this guy...
- # Note: these may not be public values
+ // Get the last edit not by this guy...
+ // Note: these may not be public values
$user = intval( $current->getRawUser() );
$user_text = $dbw->addQuotes( $current->getRawUserText() );
$s = $dbw->selectRow( 'revision',
@@ -2390,21 +2750,21 @@ class WikiPage extends Page implements IDBAccessObject {
'ORDER BY' => 'rev_timestamp DESC' )
);
if ( $s === false ) {
- # No one else ever edited this page
+ // No one else ever edited this page
return array( array( 'cantrollback' ) );
} elseif ( $s->rev_deleted & Revision::DELETED_TEXT || $s->rev_deleted & Revision::DELETED_USER ) {
- # Only admins can see this text
+ // Only admins can see this text
return array( array( 'notvisiblerev' ) );
}
$set = array();
if ( $bot && $guser->isAllowed( 'markbotedits' ) ) {
- # Mark all reverted edits as bot
+ // Mark all reverted edits as bot
$set['rc_bot'] = 1;
}
if ( $wgUseRCPatrol ) {
- # Mark all reverted edits as patrolled
+ // Mark all reverted edits as patrolled
$set['rc_patrolled'] = 1;
}
@@ -2418,7 +2778,7 @@ class WikiPage extends Page implements IDBAccessObject {
);
}
- # Generate the edit summary if necessary
+ // Generate the edit summary if necessary
$target = Revision::newFromId( $s->rev_id );
if ( empty( $summary ) ) {
if ( $from == '' ) { // no public user name
@@ -2428,7 +2788,7 @@ class WikiPage extends Page implements IDBAccessObject {
}
}
- # Allow the custom summary to use the same args as the default message
+ // Allow the custom summary to use the same args as the default message
$args = array(
$target->getUserText(), $from, $s->rev_id,
$wgContLang->timeanddate( wfTimestamp( TS_MW, $s->rev_timestamp ) ),
@@ -2440,10 +2800,13 @@ class WikiPage extends Page implements IDBAccessObject {
$summary = wfMsgReplaceArgs( $summary, $args );
}
- # Truncate for whole multibyte characters.
+ // Trim spaces on user supplied text
+ $summary = trim( $summary );
+
+ // Truncate for whole multibyte characters.
$summary = $wgContLang->truncate( $summary, 255 );
- # Save
+ // Save
$flags = EDIT_UPDATE;
if ( $guser->isAllowed( 'minoredit' ) ) {
@@ -2454,8 +2817,13 @@ class WikiPage extends Page implements IDBAccessObject {
$flags |= EDIT_FORCE_BOT;
}
- # Actually store the edit
- $status = $this->doEdit( $target->getText(), $summary, $flags, $target->getId(), $guser );
+ // Actually store the edit
+ $status = $this->doEditContent( $target->getContent(), $summary, $flags, $target->getId(), $guser );
+
+ if ( !$status->isOK() ) {
+ return $status->getErrorsArray();
+ }
+
if ( !empty( $status->value['revision'] ) ) {
$revId = $status->value['revision']->getId();
} else {
@@ -2486,7 +2854,7 @@ class WikiPage extends Page implements IDBAccessObject {
* @param $title Title object
*/
public static function onArticleCreate( $title ) {
- # Update existence markers on article/talk tabs...
+ // Update existence markers on article/talk tabs...
if ( $title->isTalkPage() ) {
$other = $title->getSubjectPage();
} else {
@@ -2507,7 +2875,7 @@ class WikiPage extends Page implements IDBAccessObject {
* @param $title Title
*/
public static function onArticleDelete( $title ) {
- # Update existence markers on article/talk tabs...
+ // Update existence markers on article/talk tabs...
if ( $title->isTalkPage() ) {
$other = $title->getSubjectPage();
} else {
@@ -2520,21 +2888,21 @@ class WikiPage extends Page implements IDBAccessObject {
$title->touchLinks();
$title->purgeSquid();
- # File cache
+ // File cache
HTMLFileCache::clearFileCache( $title );
- # Messages
+ // Messages
if ( $title->getNamespace() == NS_MEDIAWIKI ) {
MessageCache::singleton()->replace( $title->getDBkey(), false );
}
- # Images
+ // Images
if ( $title->getNamespace() == NS_FILE ) {
$update = new HTMLCacheUpdate( $title, 'imagelinks' );
$update->doUpdate();
}
- # User talk pages
+ // User talk pages
if ( $title->getNamespace() == NS_USER_TALK ) {
$user = User::newFromName( $title->getText(), false );
if ( $user ) {
@@ -2542,7 +2910,7 @@ class WikiPage extends Page implements IDBAccessObject {
}
}
- # Image redirects
+ // Image redirects
RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $title );
}
@@ -2556,14 +2924,13 @@ class WikiPage extends Page implements IDBAccessObject {
// Invalidate caches of articles which include this page
DeferredUpdates::addHTMLCacheUpdate( $title, 'templatelinks' );
-
// Invalidate the caches of all pages which redirect here
DeferredUpdates::addHTMLCacheUpdate( $title, 'redirect' );
- # Purge squid for this page only
+ // Purge squid for this page only
$title->purgeSquid();
- # Clear file cache for this page only
+ // Clear file cache for this page only
HTMLFileCache::clearFileCache( $title );
}
@@ -2600,61 +2967,24 @@ class WikiPage extends Page implements IDBAccessObject {
}
/**
- * Return an applicable autosummary if one exists for the given edit.
- * @param $oldtext String: the previous text of the page.
- * @param $newtext String: The submitted text of the page.
- * @param $flags Int bitmask: a bitmask of flags submitted for the edit.
- * @return string An appropriate autosummary, or an empty string.
- */
+ * Return an applicable autosummary if one exists for the given edit.
+ * @param string|null $oldtext the previous text of the page.
+ * @param string|null $newtext The submitted text of the page.
+ * @param int $flags bitmask: a bitmask of flags submitted for the edit.
+ * @return string An appropriate autosummary, or an empty string.
+ *
+ * @deprecated since 1.21, use ContentHandler::getAutosummary() instead
+ */
public static function getAutosummary( $oldtext, $newtext, $flags ) {
- global $wgContLang;
-
- # Decide what kind of autosummary is needed.
-
- # Redirect autosummaries
- $ot = Title::newFromRedirect( $oldtext );
- $rt = Title::newFromRedirect( $newtext );
-
- if ( is_object( $rt ) && ( !is_object( $ot ) || !$rt->equals( $ot ) || $ot->getFragment() != $rt->getFragment() ) ) {
- $truncatedtext = $wgContLang->truncate(
- str_replace( "\n", ' ', $newtext ),
- max( 0, 255
- - strlen( wfMessage( 'autoredircomment' )->inContentLanguage()->text() )
- - strlen( $rt->getFullText() )
- ) );
- return wfMessage( 'autoredircomment', $rt->getFullText() )
- ->rawParams( $truncatedtext )->inContentLanguage()->text();
- }
-
- # New page autosummaries
- if ( $flags & EDIT_NEW && strlen( $newtext ) ) {
- # If they're making a new article, give its text, truncated, in the summary.
-
- $truncatedtext = $wgContLang->truncate(
- str_replace( "\n", ' ', $newtext ),
- max( 0, 200 - strlen( wfMessage( 'autosumm-new' )->inContentLanguage()->text() ) ) );
-
- return wfMessage( 'autosumm-new' )->rawParams( $truncatedtext )
- ->inContentLanguage()->text();
- }
+ // NOTE: stub for backwards-compatibility. assumes the given text is wikitext. will break horribly if it isn't.
- # Blanking autosummaries
- if ( $oldtext != '' && $newtext == '' ) {
- return wfMessage( 'autosumm-blank' )->inContentLanguage()->text();
- } elseif ( strlen( $oldtext ) > 10 * strlen( $newtext ) && strlen( $newtext ) < 500 ) {
- # Removing more than 90% of the article
+ ContentHandler::deprecated( __METHOD__, '1.21' );
- $truncatedtext = $wgContLang->truncate(
- $newtext,
- max( 0, 200 - strlen( wfMessage( 'autosumm-replace' )->inContentLanguage()->text() ) ) );
+ $handler = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT );
+ $oldContent = is_null( $oldtext ) ? null : $handler->unserializeContent( $oldtext );
+ $newContent = is_null( $newtext ) ? null : $handler->unserializeContent( $newtext );
- return wfMessage( 'autosumm-replace' )->rawParams( $truncatedtext )
- ->inContentLanguage()->text();
- }
-
- # If we reach this point, there's no applicable autosummary for our case, so our
- # autosummary is empty.
- return '';
+ return $handler->getAutosummary( $oldContent, $newContent, $flags );
}
/**
@@ -2665,117 +2995,29 @@ class WikiPage extends Page implements IDBAccessObject {
* if no revision occurred
*/
public function getAutoDeleteReason( &$hasHistory ) {
- global $wgContLang;
-
- // Get the last revision
- $rev = $this->getRevision();
-
- if ( is_null( $rev ) ) {
- return false;
- }
-
- // Get the article's contents
- $contents = $rev->getText();
- $blank = false;
-
- // If the page is blank, use the text from the previous revision,
- // which can only be blank if there's a move/import/protect dummy revision involved
- if ( $contents == '' ) {
- $prev = $rev->getPrevious();
-
- if ( $prev ) {
- $contents = $prev->getText();
- $blank = true;
- }
- }
-
- $dbw = wfGetDB( DB_MASTER );
-
- // Find out if there was only one contributor
- // Only scan the last 20 revisions
- $res = $dbw->select( 'revision', 'rev_user_text',
- array( 'rev_page' => $this->getID(), $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' ),
- __METHOD__,
- array( 'LIMIT' => 20 )
- );
-
- if ( $res === false ) {
- // This page has no revisions, which is very weird
- return false;
- }
-
- $hasHistory = ( $res->numRows() > 1 );
- $row = $dbw->fetchObject( $res );
-
- if ( $row ) { // $row is false if the only contributor is hidden
- $onlyAuthor = $row->rev_user_text;
- // Try to find a second contributor
- foreach ( $res as $row ) {
- if ( $row->rev_user_text != $onlyAuthor ) { // Bug 22999
- $onlyAuthor = false;
- break;
- }
- }
- } else {
- $onlyAuthor = false;
- }
-
- // Generate the summary with a '$1' placeholder
- if ( $blank ) {
- // The current revision is blank and the one before is also
- // blank. It's just not our lucky day
- $reason = wfMessage( 'exbeforeblank', '$1' )->inContentLanguage()->text();
- } else {
- if ( $onlyAuthor ) {
- $reason = wfMessage(
- 'excontentauthor',
- '$1',
- $onlyAuthor
- )->inContentLanguage()->text();
- } else {
- $reason = wfMessage( 'excontent', '$1' )->inContentLanguage()->text();
- }
- }
-
- if ( $reason == '-' ) {
- // Allow these UI messages to be blanked out cleanly
- return '';
- }
-
- // Replace newlines with spaces to prevent uglyness
- $contents = preg_replace( "/[\n\r]/", ' ', $contents );
- // Calculate the maximum amount of chars to get
- // Max content length = max comment length - length of the comment (excl. $1)
- $maxLength = 255 - ( strlen( $reason ) - 2 );
- $contents = $wgContLang->truncate( $contents, $maxLength );
- // Remove possible unfinished links
- $contents = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $contents );
- // Now replace the '$1' placeholder
- $reason = str_replace( '$1', $contents, $reason );
-
- return $reason;
+ return $this->getContentHandler()->getAutoDeleteReason( $this->getTitle(), $hasHistory );
}
/**
* Update all the appropriate counts in the category table, given that
* we've added the categories $added and deleted the categories $deleted.
*
- * @param $added array The names of categories that were added
- * @param $deleted array The names of categories that were deleted
+ * @param array $added The names of categories that were added
+ * @param array $deleted The names of categories that were deleted
*/
public function updateCategoryCounts( $added, $deleted ) {
$ns = $this->mTitle->getNamespace();
$dbw = wfGetDB( DB_MASTER );
- # First make sure the rows exist. If one of the "deleted" ones didn't
- # exist, we might legitimately not create it, but it's simpler to just
- # create it and then give it a negative value, since the value is bogus
- # anyway.
- #
- # Sometimes I wish we had INSERT ... ON DUPLICATE KEY UPDATE.
+ // First make sure the rows exist. If one of the "deleted" ones didn't
+ // exist, we might legitimately not create it, but it's simpler to just
+ // create it and then give it a negative value, since the value is bogus
+ // anyway.
+ //
+ // Sometimes I wish we had INSERT ... ON DUPLICATE KEY UPDATE.
$insertCats = array_merge( $added, $deleted );
if ( !$insertCats ) {
- # Okay, nothing to do
+ // Okay, nothing to do
return;
}
@@ -2789,14 +3031,14 @@ class WikiPage extends Page implements IDBAccessObject {
}
$dbw->insert( 'category', $insertRows, __METHOD__, 'IGNORE' );
- $addFields = array( 'cat_pages = cat_pages + 1' );
+ $addFields = array( 'cat_pages = cat_pages + 1' );
$removeFields = array( 'cat_pages = cat_pages - 1' );
if ( $ns == NS_CATEGORY ) {
- $addFields[] = 'cat_subcats = cat_subcats + 1';
+ $addFields[] = 'cat_subcats = cat_subcats + 1';
$removeFields[] = 'cat_subcats = cat_subcats - 1';
} elseif ( $ns == NS_FILE ) {
- $addFields[] = 'cat_files = cat_files + 1';
+ $addFields[] = 'cat_files = cat_files + 1';
$removeFields[] = 'cat_files = cat_files - 1';
}
@@ -2817,6 +3059,15 @@ class WikiPage extends Page implements IDBAccessObject {
__METHOD__
);
}
+
+ foreach( $added as $catName ) {
+ $cat = Category::newFromName( $catName );
+ wfRunHooks( 'CategoryAfterPageAdded', array( $cat, $this ) );
+ }
+ foreach( $deleted as $catName ) {
+ $cat = Category::newFromName( $catName );
+ wfRunHooks( 'CategoryAfterPageRemoved', array( $cat, $this ) );
+ }
}
/**
@@ -2836,7 +3087,7 @@ class WikiPage extends Page implements IDBAccessObject {
// that cascaded protections apply as soon as the changes
// are visible.
- # Get templates from templatelinks
+ // Get templates from templatelinks
$id = $this->mTitle->getArticleID();
$tlTemplates = array();
@@ -2852,7 +3103,7 @@ class WikiPage extends Page implements IDBAccessObject {
$tlTemplates["{$row->tl_namespace}:{$row->tl_title}"] = true;
}
- # Get templates from parser output.
+ // Get templates from parser output.
$poTemplates = array();
foreach ( $parserOutput->getTemplates() as $ns => $templates ) {
foreach ( $templates as $dbk => $id ) {
@@ -2860,12 +3111,12 @@ class WikiPage extends Page implements IDBAccessObject {
}
}
- # Get the diff
+ // Get the diff
$templates_diff = array_diff_key( $poTemplates, $tlTemplates );
if ( count( $templates_diff ) > 0 ) {
- # Whee, link updates time.
- # Note: we are only interested in links here. We don't need to get other DataUpdate items from the parser output.
+ // Whee, link updates time.
+ // Note: we are only interested in links here. We don't need to get other DataUpdate items from the parser output.
$u = new LinksUpdate( $this->mTitle, $parserOutput, false );
$u->doUpdate();
}
@@ -2903,7 +3154,7 @@ class WikiPage extends Page implements IDBAccessObject {
* so we can do things like signatures and links-in-context.
*
* @deprecated in 1.19; use Parser::preSaveTransform() instead
- * @param $text String article contents
+ * @param string $text article contents
* @param $user User object: user doing the edit
* @param $popts ParserOptions object: parser options, default options for
* the user loaded if null given
@@ -2950,10 +3201,10 @@ class WikiPage extends Page implements IDBAccessObject {
* Update the article's restriction field, and leave a log entry.
*
* @deprecated since 1.19
- * @param $limit Array: set of restriction keys
+ * @param array $limit set of restriction keys
* @param $reason String
* @param &$cascade Integer. Set to false if cascading protection isn't allowed.
- * @param $expiry Array: per restriction type expiration
+ * @param array $expiry per restriction type expiration
* @param $user User The user updating the restrictions
* @return bool true on success
*/
@@ -2995,6 +3246,31 @@ class WikiPage extends Page implements IDBAccessObject {
global $wgUser;
return $this->isParserCacheUsed( ParserOptions::newFromUser( $wgUser ), $oldid );
}
+
+ /**
+ * Returns a list of updates to be performed when this page is deleted. The updates should remove any information
+ * about this page from secondary data stores such as links tables.
+ *
+ * @param Content|null $content optional Content object for determining the necessary updates
+ * @return Array an array of DataUpdates objects
+ */
+ public function getDeletionUpdates( Content $content = null ) {
+ if ( !$content ) {
+ // load content object, which may be used to determine the necessary updates
+ // XXX: the content may not be needed to determine the updates, then this would be overhead.
+ $content = $this->getContent( Revision::RAW );
+ }
+
+ if ( !$content ) {
+ $updates = array();
+ } else {
+ $updates = $content->getDeletionUpdates( $this );
+ }
+
+ wfRunHooks( 'WikiPageDeletionUpdates', array( $this, $content, &$updates ) );
+ return $updates;
+ }
+
}
class PoolWorkArticleView extends PoolCounterWork {
@@ -3020,9 +3296,9 @@ class PoolWorkArticleView extends PoolCounterWork {
private $parserOptions;
/**
- * @var string|null
+ * @var Content|null
*/
- private $text;
+ private $content = null;
/**
* @var ParserOutput|bool
@@ -3046,14 +3322,20 @@ class PoolWorkArticleView extends PoolCounterWork {
* @param $revid Integer: ID of the revision being parsed
* @param $useParserCache Boolean: whether to use the parser cache
* @param $parserOptions parserOptions to use for the parse operation
- * @param $text String: text to parse or null to load it
+ * @param $content Content|String: content to parse or null to load it; may also be given as a wikitext string, for BC
*/
- function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $text = null ) {
+ function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $content = null ) {
+ if ( is_string( $content ) ) { // BC: old style call
+ $modelId = $page->getRevision()->getContentModel();
+ $format = $page->getRevision()->getContentFormat();
+ $content = ContentHandler::makeContent( $content, $page->getTitle(), $modelId, $format );
+ }
+
$this->page = $page;
$this->revid = $revid;
$this->cacheable = $useParserCache;
$this->parserOptions = $parserOptions;
- $this->text = $text;
+ $this->content = $content;
$this->cacheKey = ParserCache::singleton()->getKey( $page, $parserOptions );
parent::__construct( 'ArticleView', $this->cacheKey . ':revid:' . $revid );
}
@@ -3089,28 +3371,38 @@ class PoolWorkArticleView extends PoolCounterWork {
* @return bool
*/
function doWork() {
- global $wgParser, $wgUseFileCache;
+ global $wgUseFileCache;
+
+ // @todo: several of the methods called on $this->page are not declared in Page, but present
+ // in WikiPage and delegated by Article.
$isCurrent = $this->revid === $this->page->getLatest();
- if ( $this->text !== null ) {
- $text = $this->text;
+ if ( $this->content !== null ) {
+ $content = $this->content;
} elseif ( $isCurrent ) {
- $text = $this->page->getRawText();
+ // XXX: why use RAW audience here, and PUBLIC (default) below?
+ $content = $this->page->getContent( Revision::RAW );
} else {
$rev = Revision::newFromTitle( $this->page->getTitle(), $this->revid );
+
if ( $rev === null ) {
- return false;
+ $content = null;
+ } else {
+ // XXX: why use PUBLIC audience here (default), and RAW above?
+ $content = $rev->getContent();
}
- $text = $rev->getText();
+ }
+
+ if ( $content === null ) {
+ return false;
}
$time = - microtime( true );
- $this->parserOutput = $wgParser->parse( $text, $this->page->getTitle(),
- $this->parserOptions, true, true, $this->revid );
+ $this->parserOutput = $content->getParserOutput( $this->page->getTitle(), $this->revid, $this->parserOptions );
$time += microtime( true );
- # Timing hack
+ // Timing hack
if ( $time > 3 ) {
wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time,
$this->page->getTitle()->getPrefixedDBkey() ) );
diff --git a/includes/Xml.php b/includes/Xml.php
index 120312dd..57a4fcf6 100644
--- a/includes/Xml.php
+++ b/includes/Xml.php
@@ -30,16 +30,16 @@ class Xml {
* Strings are assumed to not contain XML-illegal characters; special
* characters (<, >, &) are escaped but illegals are not touched.
*
- * @param $element String: element name
- * @param $attribs Array: Name=>value pairs. Values will be escaped.
- * @param $contents String: NULL to make an open tag only; '' for a contentless closed tag (default)
- * @param $allowShortTag Bool: whether '' in $contents will result in a contentless closed tag
+ * @param string $element element name
+ * @param array $attribs Name=>value pairs. Values will be escaped.
+ * @param string $contents NULL to make an open tag only; '' for a contentless closed tag (default)
+ * @param bool $allowShortTag whether '' in $contents will result in a contentless closed tag
* @return string
*/
public static function element( $element, $attribs = null, $contents = '', $allowShortTag = true ) {
$out = '<' . $element;
if( !is_null( $attribs ) ) {
- $out .= self::expandAttributes( $attribs );
+ $out .= self::expandAttributes( $attribs );
}
if( is_null( $contents ) ) {
$out .= '>';
@@ -58,7 +58,8 @@ class Xml {
* to set the XML attributes : attributename="value".
* The values are passed to Sanitizer::encodeAttribute.
* Return null if no attributes given.
- * @param $attribs Array of attributes for an XML element
+ * @param array $attribs of attributes for an XML element
+ * @throws MWException
* @return null|string
*/
public static function expandAttributes( $attribs ) {
@@ -81,11 +82,11 @@ class Xml {
* is passed.
*
* @param $element String:
- * @param $attribs Array: Name=>value pairs. Values will be escaped.
- * @param $contents String: NULL to make an open tag only; '' for a contentless closed tag (default)
+ * @param array $attribs Name=>value pairs. Values will be escaped.
+ * @param string $contents NULL to make an open tag only; '' for a contentless closed tag (default)
* @return string
*/
- public static function elementClean( $element, $attribs = array(), $contents = '') {
+ public static function elementClean( $element, $attribs = array(), $contents = '' ) {
global $wgContLang;
if( $attribs ) {
$attribs = array_map( array( 'UtfNormal', 'cleanUp' ), $attribs );
@@ -101,8 +102,8 @@ class Xml {
/**
* This opens an XML element
*
- * @param $element String name of the element
- * @param $attribs array of attributes, see Xml::expandAttributes()
+ * @param string $element name of the element
+ * @param array $attribs of attributes, see Xml::expandAttributes()
* @return string
*/
public static function openElement( $element, $attribs = null ) {
@@ -111,7 +112,7 @@ class Xml {
/**
* Shortcut to close an XML element
- * @param $element String element name
+ * @param string $element element name
* @return string
*/
public static function closeElement( $element ) { return "</$element>"; }
@@ -120,9 +121,9 @@ class Xml {
* Same as Xml::element(), but does not escape contents. Handy when the
* content you have is already valid xml.
*
- * @param $element String element name
- * @param $attribs array of attributes
- * @param $contents String content of the element
+ * @param string $element element name
+ * @param array $attribs of attributes
+ * @param string $contents content of the element
* @return string
*/
public static function tags( $element, $attribs = null, $contents ) {
@@ -135,7 +136,7 @@ class Xml {
* @param $selected Mixed: Namespace which should be pre-selected
* @param $all Mixed: Value of an item denoting all namespaces, or null to omit
* @param $element_name String: value of the "name" attribute of the select tag
- * @param $label String: optional label to add to the field
+ * @param string $label optional label to add to the field
* @return string
* @deprecated since 1.19
*/
@@ -156,8 +157,8 @@ class Xml {
* Create a date selector
*
* @param $selected Mixed: the month which should be selected, default ''
- * @param $allmonths String: value of a special item denoting all month. Null to not include (default)
- * @param $id String: Element identifier
+ * @param string $allmonths value of a special item denoting all month. Null to not include (default)
+ * @param string $id Element identifier
* @return String: Html string containing the month selector
*/
public static function monthSelector( $selected = '', $allmonths = null, $id = 'month' ) {
@@ -191,22 +192,23 @@ class Xml {
} elseif( $encMonth ) {
$thisMonth = intval( gmdate( 'n' ) );
$thisYear = intval( gmdate( 'Y' ) );
- if( intval($encMonth) > $thisMonth ) {
+ if( intval( $encMonth ) > $thisMonth ) {
$thisYear--;
}
$encYear = $thisYear;
} else {
$encYear = '';
}
- return Xml::label( wfMessage( 'year' )->text(), 'year' ) . ' '.
- Xml::input( 'year', 4, $encYear, array('id' => 'year', 'maxlength' => 4) ) . ' '.
- Xml::label( wfMessage( 'month' )->text(), 'month' ) . ' '.
- Xml::monthSelector( $encMonth, -1 );
+ $inputAttribs = array( 'id' => 'year', 'maxlength' => 4, 'size' => 7 );
+ return self::label( wfMessage( 'year' )->text(), 'year' ) . ' '.
+ Html::input( 'year', $encYear, 'number', $inputAttribs ) . ' '.
+ self::label( wfMessage( 'month' )->text(), 'month' ) . ' '.
+ self::monthSelector( $encMonth, -1 );
}
/**
* Construct a language selector appropriate for use in a form or preferences
- *
+ *
* @param string $selected The language code of the selected language
* @param boolean $customisedOnly If true only languages which have some content are listed
* @param string $inLanguage The ISO code of the language to display the select list in (optional)
@@ -254,9 +256,9 @@ class Xml {
/**
* Shortcut to make a span element
- * @param $text String content of the element, will be escaped
- * @param $class String class name of the span element
- * @param $attribs array other attributes
+ * @param string $text content of the element, will be escaped
+ * @param string $class class name of the span element
+ * @param array $attribs other attributes
* @return string
*/
public static function span( $text, $class, $attribs = array() ) {
@@ -265,10 +267,10 @@ class Xml {
/**
* Shortcut to make a specific element with a class attribute
- * @param $text string content of the element, will be escaped
- * @param $class string class name of the span element
- * @param $tag string element name
- * @param $attribs array other attributes
+ * @param string $text content of the element, will be escaped
+ * @param string $class class name of the span element
+ * @param string $tag element name
+ * @param array $attribs other attributes
* @return string
*/
public static function wrapClass( $text, $class, $tag = 'span', $attribs = array() ) {
@@ -277,10 +279,10 @@ class Xml {
/**
* Convenience function to build an HTML text input field
- * @param $name String value of the name attribute
- * @param $size int value of the size attribute
+ * @param string $name value of the name attribute
+ * @param int $size value of the size attribute
* @param $value mixed value of the value attribute
- * @param $attribs array other attributes
+ * @param array $attribs other attributes
* @return string HTML
*/
public static function input( $name, $size = false, $value = false, $attribs = array() ) {
@@ -299,10 +301,10 @@ class Xml {
/**
* Convenience function to build an HTML password input field
- * @param $name string value of the name attribute
- * @param $size int value of the size attribute
+ * @param string $name value of the name attribute
+ * @param int $size value of the size attribute
* @param $value mixed value of the value attribute
- * @param $attribs array other attributes
+ * @param array $attribs other attributes
* @return string HTML
*/
public static function password( $name, $size = false, $value = false, $attribs = array() ) {
@@ -323,9 +325,9 @@ class Xml {
/**
* Convenience function to build an HTML checkbox
- * @param $name String value of the name attribute
- * @param $checked Bool Whether the checkbox is checked or not
- * @param $attribs Array other attributes
+ * @param string $name value of the name attribute
+ * @param bool $checked Whether the checkbox is checked or not
+ * @param array $attribs other attributes
* @return string HTML
*/
public static function check( $name, $checked = false, $attribs=array() ) {
@@ -340,10 +342,10 @@ class Xml {
/**
* Convenience function to build an HTML radio button
- * @param $name String value of the name attribute
- * @param $value String value of the value attribute
- * @param $checked Bool Whether the checkbox is checked or not
- * @param $attribs Array other attributes
+ * @param string $name value of the name attribute
+ * @param string $value value of the value attribute
+ * @param bool $checked Whether the checkbox is checked or not
+ * @param array $attribs other attributes
* @return string HTML
*/
public static function radio( $name, $value, $checked = false, $attribs = array() ) {
@@ -355,9 +357,9 @@ class Xml {
/**
* Convenience function to build an HTML form label
- * @param $label String text of the label
+ * @param string $label text of the label
* @param $id
- * @param $attribs Array an attribute array. This will usuall be
+ * @param array $attribs an attribute array. This will usually be
* the same array as is passed to the corresponding input element,
* so this function will cherry-pick appropriate attributes to
* apply to the label as well; only class and title are applied.
@@ -367,10 +369,10 @@ class Xml {
$a = array( 'for' => $id );
# FIXME avoid copy pasting below:
- if( isset( $attribs['class'] ) ){
+ if( isset( $attribs['class'] ) ) {
$a['class'] = $attribs['class'];
}
- if( isset( $attribs['title'] ) ){
+ if( isset( $attribs['title'] ) ) {
$a['title'] = $attribs['title'];
}
@@ -379,12 +381,12 @@ class Xml {
/**
* Convenience function to build an HTML text input field with a label
- * @param $label String text of the label
- * @param $name String value of the name attribute
- * @param $id String id of the input
- * @param $size Int|Bool value of the size attribute
- * @param $value String|Bool value of the value attribute
- * @param $attribs array other attributes
+ * @param string $label text of the label
+ * @param string $name value of the name attribute
+ * @param string $id id of the input
+ * @param int|Bool $size value of the size attribute
+ * @param string|Bool $value value of the value attribute
+ * @param array $attribs other attributes
* @return string HTML
*/
public static function inputLabel( $label, $name, $id, $size=false, $value=false, $attribs = array() ) {
@@ -448,8 +450,8 @@ class Xml {
/**
* Convenience function to build an HTML submit button
- * @param $value String: label text for the button
- * @param $attribs Array: optional custom attributes
+ * @param string $value label text for the button
+ * @param array $attribs optional custom attributes
* @return string HTML
*/
public static function submitButton( $value, $attribs = array() ) {
@@ -458,10 +460,10 @@ class Xml {
/**
* Convenience function to build an HTML drop-down list item.
- * @param $text String: text for this item
- * @param $value String: form submission value; if empty, use text
+ * @param string $text text for this item
+ * @param string $value form submission value; if empty, use text
* @param $selected boolean: if true, will be the default selected item
- * @param $attribs array: optional additional HTML attributes
+ * @param array $attribs optional additional HTML attributes
* @return string HTML
*/
public static function option( $text, $value=null, $selected = false,
@@ -498,7 +500,7 @@ class Xml {
} elseif ( substr( $value, 0, 1) == '*' && substr( $value, 1, 1) != '*' ) {
// A new group is starting ...
$value = trim( substr( $value, 1 ) );
- if( $optgroup ) $options .= self::closeElement('optgroup');
+ if( $optgroup ) $options .= self::closeElement( 'optgroup' );
$options .= self::openElement( 'optgroup', array( 'label' => $value ) );
$optgroup = true;
} elseif ( substr( $value, 0, 2) == '**' ) {
@@ -507,13 +509,13 @@ class Xml {
$options .= self::option( $value, $value, $selected === $value );
} else {
// groupless reason list
- if( $optgroup ) $options .= self::closeElement('optgroup');
+ if( $optgroup ) $options .= self::closeElement( 'optgroup' );
$options .= self::option( $value, $value, $selected === $value );
$optgroup = false;
}
}
- if( $optgroup ) $options .= self::closeElement('optgroup');
+ if( $optgroup ) $options .= self::closeElement( 'optgroup' );
$attribs = array();
@@ -540,9 +542,9 @@ class Xml {
/**
* Shortcut for creating fieldsets.
*
- * @param $legend string|bool Legend of the fieldset. If evaluates to false, legend is not added.
- * @param $content string Pre-escaped content for the fieldset. If false, only open fieldset is returned.
- * @param $attribs array Any attributes to fieldset-element.
+ * @param string|bool $legend Legend of the fieldset. If evaluates to false, legend is not added.
+ * @param string $content Pre-escaped content for the fieldset. If false, only open fieldset is returned.
+ * @param array $attribs Any attributes to fieldset-element.
*
* @return string
*/
@@ -564,17 +566,18 @@ class Xml {
/**
* Shortcut for creating textareas.
*
- * @param $name string The 'name' for the textarea
- * @param $content string Content for the textarea
- * @param $cols int The number of columns for the textarea
- * @param $rows int The number of rows for the textarea
- * @param $attribs array Any other attributes for the textarea
+ * @param string $name The 'name' for the textarea
+ * @param string $content Content for the textarea
+ * @param int $cols The number of columns for the textarea
+ * @param int $rows The number of rows for the textarea
+ * @param array $attribs Any other attributes for the textarea
*
* @return string
*/
public static function textarea( $name, $content, $cols = 40, $rows = 5, $attribs = array() ) {
return self::element( 'textarea',
- array( 'name' => $name,
+ array(
+ 'name' => $name,
'id' => $name,
'cols' => $cols,
'rows' => $rows
@@ -587,7 +590,7 @@ class Xml {
* for JavaScript source code.
* Illegal control characters are assumed not to be present.
*
- * @param $string String to escape
+ * @param string $string to escape
* @return String
*/
public static function escapeJsString( $string ) {
@@ -633,10 +636,10 @@ class Xml {
} elseif ( is_null( $value ) ) {
$s = 'null';
} elseif ( is_int( $value ) || is_float( $value ) ) {
- $s = strval($value);
+ $s = strval( $value );
} elseif ( is_array( $value ) && // Make sure it's not associative.
- array_keys($value) === range( 0, count($value) - 1 ) ||
- count($value) == 0
+ array_keys( $value ) === range( 0, count( $value ) - 1 ) ||
+ count( $value ) == 0
) {
$s = '[';
foreach ( $value as $elt ) {
@@ -670,38 +673,27 @@ class Xml {
* Create a call to a JavaScript function. The supplied arguments will be
* encoded using Xml::encodeJsVar().
*
- * @param $name String The name of the function to call, or a JavaScript expression
+ * @param string $name The name of the function to call, or a JavaScript expression
* which evaluates to a function object which is called.
- * @param $args Array of arguments to pass to the function.
+ * @param array $args of arguments to pass to the function.
*
* @since 1.17
*
* @return string
*/
public static function encodeJsCall( $name, $args ) {
- $s = "$name(";
- $first = true;
-
- foreach ( $args as $arg ) {
- if ( $first ) {
- $first = false;
- } else {
- $s .= ', ';
- }
-
- $s .= Xml::encodeJsVar( $arg );
+ foreach ( $args as &$arg ) {
+ $arg = Xml::encodeJsVar( $arg );
}
- $s .= ");\n";
-
- return $s;
+ return "$name(" . implode( ', ', $args ) . ");\n";
}
/**
* Check if a string is well-formed XML.
* Must include the surrounding tag.
*
- * @param $text String: string to test.
+ * @param string $text string to test.
* @return bool
*
* @todo Error position reporting return
@@ -748,7 +740,7 @@ class Xml {
* Replace " > and < with their respective HTML entities ( &quot;,
* &gt;, &lt;)
*
- * @param $in String: text that might contain HTML tags.
+ * @param string $in text that might contain HTML tags.
* @return string Escaped string
*/
public static function escapeTagsOnly( $in ) {
@@ -759,12 +751,12 @@ class Xml {
}
/**
- * Generate a form (without the opening form element).
- * Output optionally includes a submit button.
- * @param $fields Array Associative array, key is message corresponding to a description for the field (colon is in the message), value is appropriate input.
- * @param $submitLabel String A message containing a label for the submit button.
- * @return string HTML form.
- */
+ * Generate a form (without the opening form element).
+ * Output optionally includes a submit button.
+ * @param array $fields Associative array, key is message corresponding to a description for the field (colon is in the message), value is appropriate input.
+ * @param string $submitLabel A message containing a label for the submit button.
+ * @return string HTML form.
+ */
public static function buildForm( $fields, $submitLabel = null ) {
$form = '';
$form .= "<table><tbody>";
@@ -772,7 +764,7 @@ class Xml {
foreach( $fields as $labelmsg => $input ) {
$id = "mw-$labelmsg";
$form .= Xml::openElement( 'tr', array( 'id' => $id ) );
- $form .= Xml::tags( 'td', array('class' => 'mw-label'), wfMessage( $labelmsg )->parse() );
+ $form .= Xml::tags( 'td', array( 'class' => 'mw-label' ), wfMessage( $labelmsg )->parse() );
$form .= Xml::openElement( 'td', array( 'class' => 'mw-input' ) ) . $input . Xml::closeElement( 'td' );
$form .= Xml::closeElement( 'tr' );
}
@@ -791,9 +783,9 @@ class Xml {
/**
* Build a table of data
- * @param $rows array An array of arrays of strings, each to be a row in a table
- * @param $attribs array An array of attributes to apply to the table tag [optional]
- * @param $headers array An array of strings to use as table headers [optional]
+ * @param array $rows An array of arrays of strings, each to be a row in a table
+ * @param array $attribs An array of attributes to apply to the table tag [optional]
+ * @param array $headers An array of strings to use as table headers [optional]
* @return string
*/
public static function buildTable( $rows, $attribs = array(), $headers = null ) {
@@ -831,8 +823,8 @@ class Xml {
/**
* Build a row for a table
- * @param $attribs array An array of attributes to apply to the tr tag
- * @param $cells array An array of strings to put in <td>
+ * @param array $attribs An array of attributes to apply to the tr tag
+ * @param array $cells An array of strings to put in <td>
* @return string
*/
public static function buildTableRow( $attribs, $cells ) {
diff --git a/includes/XmlTypeCheck.php b/includes/XmlTypeCheck.php
index b95dd6a5..2e184606 100644
--- a/includes/XmlTypeCheck.php
+++ b/includes/XmlTypeCheck.php
@@ -40,7 +40,7 @@ class XmlTypeCheck {
public $rootElement = '';
/**
- * @param $file string filename
+ * @param string $file filename
* @param $filterCallback callable (optional)
* Function to call to do additional custom validity checks from the
* SAX element handler event. This gives you access to the element
diff --git a/includes/ZhClient.php b/includes/ZhClient.php
index 4299841b..fd03ec45 100644
--- a/includes/ZhClient.php
+++ b/includes/ZhClient.php
@@ -50,7 +50,7 @@ class ZhClient {
}
/**
- * Establish conncetion
+ * Establish connection
*
* @access private
*
@@ -100,8 +100,8 @@ class ZhClient {
/**
* Convert the input to a different language variant
*
- * @param $text String: input text
- * @param $tolang String: language variant
+ * @param string $text input text
+ * @param string $tolang language variant
* @return string the converted text
*/
function convert( $text, $tolang ) {
@@ -117,7 +117,7 @@ class ZhClient {
/**
* Convert the input to all possible variants
*
- * @param $text String: input text
+ * @param string $text input text
* @return array langcode => converted_string
*/
function convertToAllVariants( $text ) {
@@ -142,7 +142,7 @@ class ZhClient {
/**
* Perform word segmentation
*
- * @param $text String: input text
+ * @param string $text input text
* @return string segmented text
*/
function segment( $text ) {
diff --git a/includes/ZhConversion.php b/includes/ZhConversion.php
index 247b1939..df98836f 100644
--- a/includes/ZhConversion.php
+++ b/includes/ZhConversion.php
@@ -9,36 +9,66 @@
*/
$zh2Hant = array(
+'㐽' => '偑',
+'㑇' => '㑳',
+'㑈' => '倲',
+'㑔' => '㑯',
'㑩' => '儸',
'㓥' => '劏',
'㔉' => '劚',
'㖊' => '噚',
'㖞' => '喎',
+'㘎' => '㘚',
+'㚯' => '㜄',
+'㛀' => '媰',
'㛟' => '𡞵',
'㛠' => '𡢃',
+'㛣' => '㜏',
+'㛤' => '孋',
'㛿' => '𡠹',
'㟆' => '㠏',
+'㟜' => '𡾱',
+'㤘' => '㥮',
+'㧏' => '掆',
+'㧐' => '㩳',
'㧑' => '撝',
'㧟' => '擓',
+'㧰' => '擽',
'㨫' => '㩜',
+'㭎' => '棡',
+'㭏' => '椲',
+'㭣' => '𣙎',
+'㭤' => '樢',
+'㭴' => '樫',
'㱩' => '殰',
'㱮' => '殨',
'㲿' => '瀇',
+'㳔' => '濧',
+'㳕' => '灡',
'㳠' => '澾',
+'㳡' => '濄',
+'㳢' => '𣾷',
+'㳽' => '瀰',
'㶉' => '鸂',
'㶶' => '燶',
'㶽' => '煱',
'㺍' => '獱',
+'㻅' => '璯',
'㻏' => '𤫩',
'㻘' => '𤪺',
+'䀥' => '䁻',
'䁖' => '瞜',
+'䂵' => '碽',
'䅉' => '稏',
+'䅪' => '𥢢',
'䇲' => '筴',
+'䉤' => '籔',
'䌶' => '䊷',
'䌷' => '紬',
'䌸' => '縳',
'䌹' => '絅',
'䌺' => '䋙',
+'䌻' => '䋚',
'䌼' => '綐',
'䌽' => '綵',
'䌾' => '䋻',
@@ -48,24 +78,34 @@ $zh2Hant = array(
'䓕' => '薳',
'䗖' => '螮',
'䘛' => '𧝞',
+'䘞' => '𧜗',
'䙊' => '𧜵',
+'䙌' => '䙡',
'䙓' => '襬',
'䜣' => '訢',
'䜥' => '𧩙',
-'䜧' => '譅',
+'䜧' => '䜀',
+'䜩' => '讌',
'䝙' => '貙',
'䞌' => '𧵳',
'䞍' => '䝼',
+'䞎' => '𧶧',
'䞐' => '賰',
+'䟢' => '躎',
+'䢀' => '𨊰',
+'䢁' => '𨊸',
'䢂' => '𨋢',
'䥺' => '釾',
'䥽' => '鏺',
+'䥾' => '䥱',
'䥿' => '𨯅',
'䦀' => '𨦫',
'䦁' => '𨧜',
+'䦂' => '䥇',
'䦃' => '鐯',
'䦅' => '鐥',
-'䩄' => '靦',
+'䦶' => '䦛',
+'䦷' => '䦟',
'䭪' => '𩞯',
'䯃' => '𩣑',
'䯄' => '騧',
@@ -136,6 +176,7 @@ $zh2Hant = array(
'伞' => '傘',
'伟' => '偉',
'传' => '傳',
+'伡' => '俥',
'伣' => '俔',
'伤' => '傷',
'伥' => '倀',
@@ -217,6 +258,7 @@ $zh2Hant = array(
'刭' => '剄',
'刹' => '剎',
'刽' => '劊',
+'刾' => '㓨',
'刿' => '劌',
'剀' => '剴',
'剂' => '劑',
@@ -321,6 +363,8 @@ $zh2Hant = array(
'啬' => '嗇',
'啭' => '囀',
'啮' => '嚙',
+'啯' => '嘓',
+'啰' => '囉',
'啴' => '嘽',
'啸' => '嘯',
'喷' => '噴',
@@ -410,6 +454,7 @@ $zh2Hant = array(
'婵' => '嬋',
'婶' => '嬸',
'媪' => '媼',
+'媭' => '嬃',
'嫒' => '嬡',
'嫔' => '嬪',
'嫱' => '嬙',
@@ -513,7 +558,9 @@ $zh2Hant = array(
'归' => '歸',
'当' => '當',
'录' => '錄',
+'彟' => '彠',
'彦' => '彥',
+'彨' => '彲',
'彻' => '徹',
'径' => '徑',
'徕' => '徠',
@@ -648,6 +695,7 @@ $zh2Hant = array(
'攒' => '攢',
'敌' => '敵',
'敛' => '斂',
+'敩' => '斆',
'数' => '數',
'斋' => '齋',
'斓' => '斕',
@@ -719,6 +767,7 @@ $zh2Hant = array(
'桧' => '檜',
'桨' => '槳',
'桩' => '樁',
+'桪' => '樳',
'梦' => '夢',
'梼' => '檮',
'梾' => '棶',
@@ -727,9 +776,12 @@ $zh2Hant = array(
'棁' => '梲',
'棂' => '欞',
'椁' => '槨',
+'椝' => '槼',
'椟' => '櫝',
'椠' => '槧',
+'椢' => '槶',
'椤' => '欏',
+'椫' => '樿',
'椭' => '橢',
'楼' => '樓',
'榄' => '欖',
@@ -783,6 +835,7 @@ $zh2Hant = array(
'沥' => '瀝',
'沦' => '淪',
'沧' => '滄',
+'沨' => '渢',
'沩' => '溈',
'沪' => '滬',
'泞' => '濘',
@@ -813,6 +866,7 @@ $zh2Hant = array(
'浒' => '滸',
'浓' => '濃',
'浔' => '潯',
+'浕' => '濜',
'涂' => '塗',
'涛' => '濤',
'涝' => '澇',
@@ -820,6 +874,7 @@ $zh2Hant = array(
'涟' => '漣',
'涠' => '潿',
'涡' => '渦',
+'涢' => '溳',
'涣' => '渙',
'涤' => '滌',
'润' => '潤',
@@ -833,7 +888,6 @@ $zh2Hant = array(
'渐' => '漸',
'渑' => '澠',
'渔' => '漁',
-'渖' => '瀋',
'渗' => '滲',
'温' => '溫',
'湾' => '灣',
@@ -841,6 +895,7 @@ $zh2Hant = array(
'溃' => '潰',
'溅' => '濺',
'溆' => '漵',
+'溇' => '漊',
'滗' => '潷',
'滚' => '滾',
'滞' => '滯',
@@ -861,6 +916,7 @@ $zh2Hant = array(
'潍' => '濰',
'潜' => '潛',
'潴' => '瀦',
+'澛' => '瀂',
'澜' => '瀾',
'濑' => '瀨',
'濒' => '瀕',
@@ -872,7 +928,6 @@ $zh2Hant = array(
'灿' => '燦',
'炀' => '煬',
'炉' => '爐',
-'炖' => '燉',
'炜' => '煒',
'炝' => '熗',
'点' => '點',
@@ -1014,12 +1069,13 @@ $zh2Hant = array(
'硖' => '硤',
'硗' => '磽',
'硙' => '磑',
+'硚' => '礄',
'确' => '確',
+'硵' => '磠',
'硷' => '礆',
'碍' => '礙',
'碛' => '磧',
'碜' => '磣',
-'碱' => '鹼',
'礼' => '禮',
'祃' => '禡',
'祎' => '禕',
@@ -1081,6 +1137,7 @@ $zh2Hant = array(
'篑' => '簣',
'篓' => '簍',
'篮' => '籃',
+'篯' => '籛',
'篱' => '籬',
'簖' => '籪',
'籁' => '籟',
@@ -1256,6 +1313,8 @@ $zh2Hant = array(
'羟' => '羥',
'羡' => '羨',
'翘' => '翹',
+'翙' => '翽',
+'翚' => '翬',
'耢' => '耮',
'耧' => '耬',
'耸' => '聳',
@@ -1295,6 +1354,7 @@ $zh2Hant = array(
'脶' => '腡',
'脸' => '臉',
'腊' => '臘',
+'腘' => '膕',
'腭' => '齶',
'腻' => '膩',
'腼' => '靦',
@@ -1337,6 +1397,7 @@ $zh2Hant = array(
'荚' => '莢',
'荛' => '蕘',
'荜' => '蓽',
+'荝' => '萴',
'荞' => '蕎',
'荟' => '薈',
'荠' => '薺',
@@ -1405,6 +1466,7 @@ $zh2Hant = array(
'蚀' => '蝕',
'蚁' => '蟻',
'蚂' => '螞',
+'蚃' => '蠁',
'蚕' => '蠶',
'蚬' => '蜆',
'蛊' => '蠱',
@@ -1445,9 +1507,10 @@ $zh2Hant = array(
'裢' => '褳',
'裣' => '襝',
'裤' => '褲',
-'裥' => '襇',
+'裥' => '襉',
'褛' => '褸',
'褴' => '襤',
+'襕' => '襴',
'见' => '見',
'观' => '觀',
'觃' => '覎',
@@ -1469,6 +1532,7 @@ $zh2Hant = array(
'触' => '觸',
'觯' => '觶',
'訚' => '誾',
+'詟' => '讋',
'誉' => '譽',
'誊' => '謄',
'讠' => '訁',
@@ -2209,6 +2273,7 @@ $zh2Hant = array(
'颠' => '顛',
'颡' => '顙',
'颢' => '顥',
+'颣' => '纇',
'颤' => '顫',
'颥' => '顬',
'颦' => '顰',
@@ -2344,6 +2409,7 @@ $zh2Hant = array(
'髋' => '髖',
'髌' => '髕',
'鬓' => '鬢',
+'鬶' => '鬹',
'魇' => '魘',
'魉' => '魎',
'鱼' => '魚',
@@ -2404,7 +2470,6 @@ $zh2Hant = array(
'鲳' => '鯧',
'鲴' => '鯝',
'鲵' => '鯢',
-'鲶' => '鯰',
'鲷' => '鯛',
'鲸' => '鯨',
'鲹' => '鰺',
@@ -2570,9 +2635,144 @@ $zh2Hant = array(
'龚' => '龔',
'龛' => '龕',
'龟' => '龜',
+'𠆲' => '儣',
+'𠆿' => '𠌥',
+'𠉂' => '㒓',
+'𠉗' => '𠏢',
+'𠚳' => '𠠎',
+'𠛅' => '剾',
+'𠛆' => '𠞆',
'𠮶' => '嗰',
+'𠯟' => '哯',
+'𠯠' => '噅',
+'𠲥' => '𡅏',
+'𠴢' => '𡄔',
+'𠵸' => '𡄣',
+'𠵾' => '㗲',
+'𡋀' => '𡓾',
+'𡋗' => '𡑭',
'𡒄' => '壈',
+'𡝠' => '㜷',
+'𡞱' => '㜢',
+'𡭜' => '𡮉',
+'𡭬' => '𡮣',
+'𡶴' => '嵼',
+'𢋈' => '㢝',
+'𢘝' => '𢣚',
+'𢘞' => '𢣭',
+'𢙓' => '懀',
+'𢛯' => '㦎',
+'𢫊' => '𢷮',
+'𢫞' => '𢶫',
+'𢫬' => '摋',
+'𢬦' => '𢹿',
+'𢭏' => '擣',
+'𢽾' => '斅',
+'𣆐' => '曥',
+'𣍨' => '𦢈',
+'𣍯' => '腪',
+'𣍰' => '脥',
+'𣎑' => '臗',
+'𣐤' => '欍',
+'𣑶' => '𣠲',
+'𣗋' => '欓',
+'𣘓' => '𣞻',
+'𣘴' => '檭',
+'𣘷' => '𣝕',
+'𣭤' => '𣯴',
+'𣶩' => '澅',
+'𣶫' => '𣿉',
+'𣸣' => '濆',
+'𣺼' => '灙',
+'𣺽' => '𤁣',
+'𣽷' => '瀃',
+'𤆡' => '熓',
+'𤇃' => '爄',
+'𤇄' => '熌',
+'𤈶' => '熉',
+'𤈷' => '㷿',
+'𤊀' => '𤒎',
+'𤋏' => '熡',
+'𤞤' => '玁',
+'𤠋' => '㺏',
+'𤦀' => '瓕',
+'𤳄' => '𤳸',
+'𤶧' => '𤸫',
+'𤽯' => '㿧',
+'𤾀' => '皟',
+'𥅘' => '𥌃',
+'𥅴' => '䀹',
+'𥆧' => '瞤',
+'𥇢' => '䁪',
+'𥐟' => '礒',
+'𥐯' => '𥖅',
+'𥐰' => '𥕥',
+'𥐻' => '碙',
+'𥧂' => '𥨐',
+'𥬀' => '䉙',
+'𥬞' => '籋',
+'𥬠' => '篘',
+'𥭉' => '𥵊',
+'𥮋' => '𥸠',
+'𥮜' => '䉲',
+'𥱔' => '𥵃',
+'𥹥' => '𥼽',
+'𥺅' => '䊭',
+'𥺇' => '𥽖',
+'𦈈' => '𥿊',
+'𦈉' => '緷',
+'𦈋' => '綇',
+'𦈌' => '綀',
+'𦈎' => '繟',
+'𦈏' => '緍',
+'𦈐' => '縺',
+'𦈑' => '緸',
+'𦈒' => '𦂅',
+'𦈓' => '䋿',
+'𦈔' => '縎',
+'𦈕' => '緰',
'𦈖' => '䌈',
+'𦈗' => '𦃄',
+'𦈘' => '䌋',
+'𦈙' => '䌰',
+'𦈚' => '縬',
+'𦈛' => '繓',
+'𦈜' => '䌖',
+'𦈝' => '繏',
+'𦈞' => '䌟',
+'𦈟' => '䌝',
+'𦈠' => '䌥',
+'𦈡' => '繻',
+'𦛨' => '朥',
+'𦝼' => '膢',
+'𦟗' => '𦣎',
+'𦨩' => '𦪽',
+'𦰴' => '䕳',
+'𧉞' => '䗿',
+'𧒭' => '𧔥',
+'𧮪' => '詀',
+'𧳕' => '𧳟',
+'𧹑' => '䞈',
+'𧹓' => '𧶔',
+'𧹕' => '䝻',
+'𧹖' => '賟',
+'𧹗' => '贃',
+'𧿈' => '𨇁',
+'𨀱' => '𨄣',
+'𨁴' => '𨅍',
+'𨂺' => '𨈊',
+'𨄄' => '𨈌',
+'𨅫' => '𨇞',
+'𨅬' => '躝',
+'𨉗' => '軉',
+'𨐅' => '軗',
+'𨐆' => '𨊻',
+'𨐇' => '𨏠',
+'𨐈' => '輄',
+'𨐉' => '𨎮',
+'𨐊' => '𨏥',
+'𨑹' => '䢨',
+'𨤰' => '𨤻',
'𨰾' => '鎷',
'𨰿' => '釳',
'𨱀' => '𨥛',
@@ -2581,6 +2781,7 @@ $zh2Hant = array(
'𨱃' => '鈲',
'𨱄' => '鈯',
'𨱅' => '鉁',
+'𨱆' => '龯',
'𨱇' => '銶',
'𨱈' => '鋉',
'𨱉' => '鍄',
@@ -2591,12 +2792,28 @@ $zh2Hant = array(
'𨱎' => '鍮',
'𨱏' => '鎝',
'𨱐' => '𨫒',
+'𨱑' => '鐄',
'𨱒' => '鏉',
'𨱓' => '鐎',
'𨱔' => '鐏',
'𨱕' => '𨮂',
+'𨱖' => '䥩',
+'𨷿' => '䦳',
+'𨸀' => '𨳕',
+'𨸁' => '𨳑',
'𨸂' => '閍',
'𨸃' => '閐',
+'𨸄' => '䦘',
+'𨸅' => '𨴗',
+'𨸆' => '𨵩',
+'𨸇' => '𨵸',
+'𨸉' => '𨶀',
+'𨸊' => '𨶏',
+'𨸋' => '𨶲',
+'𨸌' => '𨶮',
+'𨸎' => '𨷲',
+'𨸘' => '𨽏',
+'𨸟' => '䧢',
'𩏼' => '䪏',
'𩏽' => '𩏪',
'𩏾' => '𩎢',
@@ -2617,12 +2834,22 @@ $zh2Hant = array(
'𩙮' => '䬘',
'𩙯' => '䬝',
'𩙰' => '𩙈',
+'𩟿' => '𩚛',
+'𩠀' => '𩚥',
+'𩠁' => '𩚵',
+'𩠂' => '𩛆',
+'𩠃' => '𩛩',
'𩠅' => '𩟐',
'𩠆' => '𩜦',
'𩠇' => '䭀',
'𩠈' => '䭃',
+'𩠉' => '𩜇',
+'𩠊' => '𩜵',
'𩠋' => '𩝔',
'𩠌' => '餸',
+'𩠎' => '𩞄',
+'𩠏' => '𩞦',
+'𩠠' => '𩠴',
'𩧦' => '𩡺',
'𩧨' => '駎',
'𩧩' => '𩤊',
@@ -2649,14 +2876,20 @@ $zh2Hant = array(
'𩨄' => '騪',
'𩨅' => '𩤸',
'𩨆' => '𩤙',
+'𩨇' => '䮫',
'𩨈' => '騟',
'𩨉' => '𩤲',
'𩨊' => '騚',
'𩨋' => '𩥄',
'𩨌' => '𩥑',
'𩨍' => '𩥇',
+'𩨎' => '龭',
'𩨏' => '䮳',
'𩨐' => '𩧆',
+'𩬣' => '𩭙',
+'𩬤' => '𩰀',
+'𩯒' => '𩯳',
+'𩲒' => '𩳤',
'𩽹' => '魥',
'𩽺' => '𩵩',
'𩽻' => '𩵹',
@@ -2666,6 +2899,7 @@ $zh2Hant = array(
'𩽿' => '𩶰',
'𩾀' => '鮕',
'𩾁' => '鯄',
+'𩾂' => '䲖',
'𩾃' => '鮸',
'𩾄' => '𩷰',
'𩾅' => '𩸃',
@@ -2699,6 +2933,8 @@ $zh2Hant = array(
'𪎊' => '麨',
'𪎋' => '䴴',
'𪎌' => '麳',
+'𪎍' => '𪋿',
+'𪔭' => '𪔵',
'𪚏' => '𪘀',
'𪚐' => '𪘯',
'𪞝' => '凙',
@@ -2715,7 +2951,7 @@ $zh2Hant = array(
'𫌀' => '襀',
'𫌨' => '覼',
'𫍙' => '訑',
-'𫍟' => '𧦧',
+'𫍟' => '詑',
'𫍢' => '譊',
'𫍰' => '諰',
'𫍲' => '謏',
@@ -2728,6 +2964,7 @@ $zh2Hant = array(
'𫓧' => '鈇',
'𫓩' => '鏦',
'𫔎' => '鐍',
+'𫖸' => '願',
'𫗠' => '餦',
'𫗦' => '餔',
'𫗧' => '餗',
@@ -3993,7 +4230,6 @@ $zh2Hant = array(
'优游' => '優遊',
'兀术' => '兀朮',
'元凶' => '元兇',
-'充饥' => '充饑',
'兆个' => '兆個',
'兆余' => '兆餘',
'凶刀' => '兇刀',
@@ -4144,7 +4380,6 @@ $zh2Hant = array(
'出于' => '出於',
'出游' => '出遊',
'出丑' => '出醜',
-'出锤' => '出鎚',
'分占' => '分佔',
'分别致' => '分别致',
'分半钟' => '分半鐘',
@@ -4393,6 +4628,7 @@ $zh2Hant = array(
'古书云' => '古書云',
'古書云' => '古書云',
'古柯咸' => '古柯鹹',
+'古柯碱' => '古柯鹼',
'古朴' => '古樸',
'古语云' => '古語云',
'古語云' => '古語云',
@@ -4617,8 +4853,8 @@ $zh2Hant = array(
'严于' => '嚴於',
'严丝合缝' => '嚴絲合縫',
'嚼谷' => '嚼穀',
-'囉囉苏苏' => '囉囉囌囌',
-'囉苏' => '囉囌',
+'啰啰苏苏' => '囉囉囌囌',
+'啰苏' => '囉囌',
'嘱托' => '囑託',
'四个' => '四個',
'四出刊' => '四出刊',
@@ -4815,7 +5051,6 @@ $zh2Hant = array(
'大赞' => '大讚',
'大周折' => '大週摺',
'大金发苔' => '大金髮苔',
-'大锤' => '大鎚',
'大钟' => '大鐘',
'大只' => '大隻',
'大风后' => '大風後',
@@ -4836,6 +5071,7 @@ $zh2Hant = array(
'天文钟' => '天文鐘',
'天历' => '天曆',
'天历史' => '天歷史',
+'天然碱' => '天然鹼',
'天翻地覆' => '天翻地覆',
'天覆地载' => '天覆地載',
'太仆' => '太僕',
@@ -4885,7 +5121,6 @@ $zh2Hant = array(
'好丑' => '好醜',
'好斗' => '好鬥',
'如果干' => '如果幹',
-'如饥似渴' => '如饑似渴',
'妖后' => '妖后',
'妙药' => '妙藥',
'始于' => '始於',
@@ -5279,6 +5514,7 @@ $zh2Hant = array(
'弘历史' => '弘歷史',
'弱于' => '弱於',
'弱水三千只取一瓢' => '弱水三千只取一瓢',
+'弱碱' => '弱鹼',
'张三丰' => '張三丰',
'張三丰' => '張三丰',
'张勋' => '張勳',
@@ -5288,6 +5524,7 @@ $zh2Hant = array(
'强奸' => '強姦',
'强干' => '強幹',
'强于' => '強於',
+'强碱' => '強鹼',
'别口气' => '彆口氣',
'别强' => '彆強',
'别扭' => '彆扭',
@@ -5686,6 +5923,7 @@ $zh2Hant = array(
'抗癌药' => '抗癌藥',
'抗御' => '抗禦',
'抗药' => '抗藥',
+'抗碱' => '抗鹼',
'折向往' => '折向往',
'折子戏' => '折子戲',
'折戟沈河' => '折戟沈河',
@@ -5824,7 +6062,7 @@ $zh2Hant = array(
'挂名' => '掛名',
'挂帘' => '掛帘',
'挂历' => '掛曆',
-'挂钩' => '掛鈎',
+'挂鈎' => '掛鈎',
'挂钟' => '掛鐘',
'采下' => '採下',
'采伐' => '採伐',
@@ -6528,12 +6766,14 @@ $zh2Hant = array(
'水里浊水溪' => '水里濁水溪',
'水里鄉' => '水里鄉',
'水里乡' => '水里鄉',
+'水碱' => '水鹼',
'永历' => '永曆',
'永历史' => '永歷史',
'永志不忘' => '永誌不忘',
'求知欲' => '求知慾',
'求签' => '求籤',
'求道于盲' => '求道於盲',
+'汗碱' => '汗鹼',
'池里' => '池裡',
'污蔑' => '污衊',
'汲于' => '汲於',
@@ -6716,6 +6956,7 @@ $zh2Hant = array(
'准话' => '準話',
'准谱' => '準譜',
'准货币' => '準貨幣',
+'准军事' => '準軍事',
'准头' => '準頭',
'准点' => '準點',
'溟蒙' => '溟濛',
@@ -6725,16 +6966,8 @@ $zh2Hant = array(
'滃郁' => '滃鬱',
'滑借' => '滑藉',
'汇丰' => '滙豐',
-'卤味' => '滷味',
-'卤水' => '滷水',
-'卤汁' => '滷汁',
-'卤湖' => '滷湖',
-'卤肉' => '滷肉',
-'卤菜' => '滷菜',
-'卤蛋' => '滷蛋',
-'卤制' => '滷製',
-'卤鸡' => '滷雞',
-'卤面' => '滷麵',
+'滷制' => '滷製',
+'滷面' => '滷麵',
'满拼自尽' => '滿拚自盡',
'满满当当' => '滿滿當當',
'满头洋发' => '滿頭洋髮',
@@ -6830,6 +7063,7 @@ $zh2Hant = array(
'煎面' => '煎麵',
'烟卷' => '煙捲',
'烟斗丝' => '煙斗絲',
+'烟碱' => '煙鹼',
'照占' => '照佔',
'照入签' => '照入籤',
'照准' => '照準',
@@ -6839,9 +7073,10 @@ $zh2Hant = array(
'熊杰' => '熊杰',
'荧郁' => '熒鬱',
'熬药' => '熬藥',
-'炖药' => '燉藥',
+'燉药' => '燉藥',
'燎发' => '燎髮',
'烧干' => '燒乾',
+'烧碱' => '燒鹼',
'燕几' => '燕几',
'燕巢于幕' => '燕巢於幕',
'燕燕于飞' => '燕燕于飛',
@@ -7014,7 +7249,7 @@ $zh2Hant = array(
'发松' => '發鬆',
'发面' => '發麵',
'白干' => '白乾',
-'白兔擣药' => '白兔擣藥',
+'白兔𢭏药' => '白兔擣藥',
'白干儿' => '白干兒',
'白术' => '白朮',
'白朴' => '白樸',
@@ -7148,6 +7383,7 @@ $zh2Hant = array(
'石英钟表' => '石英鐘錶',
'石莼' => '石蓴',
'石钟乳' => '石鐘乳',
+'石碱' => '石鹼',
'矽谷' => '矽谷',
'研制' => '研製',
'砰当' => '砰噹',
@@ -7158,6 +7394,7 @@ $zh2Hant = array(
'朱红色' => '硃紅色',
'朱色' => '硃色',
'朱谕' => '硃諭',
+'硫化碱' => '硫化鹼',
'硬干' => '硬幹',
'确瘠' => '确瘠',
'碑志' => '碑誌',
@@ -7266,7 +7503,6 @@ $zh2Hant = array(
'积极参加' => '積极參加',
'积淀' => '積澱',
'积谷' => '積穀',
-'积谷防饥' => '積穀防饑',
'积郁' => '積鬱',
'稳占' => '穩佔',
'稳扎' => '穩紮',
@@ -7403,6 +7639,7 @@ $zh2Hant = array(
'纡郁' => '紆鬱',
'纳征' => '納徵',
'纯朴' => '純樸',
+'纯碱' => '純鹼',
'纸扎' => '紙紮',
'素朴' => '素樸',
'素发' => '素髮',
@@ -7598,6 +7835,7 @@ $zh2Hant = array(
'考试' => '考試',
'而克制' => '而剋制',
'耍斗' => '耍鬥',
+'耐碱' => '耐鹼',
'耕佣' => '耕傭',
'耕获' => '耕穫',
'耳余' => '耳餘',
@@ -7731,6 +7969,7 @@ $zh2Hant = array(
'花马吊嘴' => '花馬弔嘴',
'花哄' => '花鬨',
'苑里' => '苑裡',
+'苛性碱' => '苛性鹼',
'若干' => '若干',
'苦干' => '苦幹',
'苦药' => '苦藥',
@@ -7761,7 +8000,7 @@ $zh2Hant = array(
'草药' => '草藥',
'荐居' => '荐居',
'荐臻' => '荐臻',
-'荐饥' => '荐饑',
+'荐饑' => '荐饑',
'荷花淀' => '荷花澱',
'庄上' => '莊上',
'庄主' => '莊主',
@@ -7795,6 +8034,7 @@ $zh2Hant = array(
'菠萝干' => '菠蘿乾',
'华严钟' => '華嚴鐘',
'华发' => '華髮',
+'菸碱' => '菸鹼',
'萬一只' => '萬一只',
'万一只' => '萬一只',
'万个' => '萬個',
@@ -7874,7 +8114,7 @@ $zh2Hant = array(
'姜饼' => '薑餅',
'姜黄' => '薑黃',
'薙发' => '薙髮',
-'薝卜' => '薝蔔',
+'薝蔔' => '薝蔔',
'苧悴' => '薴悴',
'薴烯' => '薴烯',
'苧烯' => '薴烯',
@@ -7961,8 +8201,7 @@ $zh2Hant = array(
'蕴含着' => '蘊含著',
'蕴涵着' => '蘊涵著',
'苹果干' => '蘋果乾',
-'萝卜' => '蘿蔔',
-'萝卜干' => '蘿蔔乾',
+'萝蔔干' => '蘿蔔乾',
'虎须' => '虎鬚',
'虎斗' => '虎鬥',
'号志' => '號誌',
@@ -7982,7 +8221,7 @@ $zh2Hant = array(
'蛏干' => '蟶乾',
'蚁后' => '蟻后',
'蟻后' => '蟻后',
-'蠁干' => '蠁幹',
+'蚃干' => '蠁幹',
'蛮干' => '蠻幹',
'血拼' => '血拚',
'血余' => '血餘',
@@ -8378,7 +8617,6 @@ $zh2Hant = array(
'资金占用' => '資金占用',
'贾后' => '賈后',
'賈后' => '賈后',
-'赈饥' => '賑饑',
'赏赞' => '賞讚',
'贤后' => '賢后',
'賢后' => '賢后',
@@ -8654,6 +8892,7 @@ $zh2Hant = array(
'酒醴曲蘖' => '酒醴麴櫱',
'酒曲' => '酒麴',
'酥松' => '酥鬆',
+'酸碱' => '酸鹼',
'醇朴' => '醇樸',
'醉于' => '醉於',
'醋坛' => '醋罈',
@@ -8728,7 +8967,6 @@ $zh2Hant = array(
'重复' => '重複',
'重托' => '重託',
'重游' => '重遊',
-'重锤' => '重鎚',
'野姜' => '野薑',
'野游' => '野遊',
'厘出' => '釐出',
@@ -8755,10 +8993,10 @@ $zh2Hant = array(
'金装玉里' => '金裝玉裡',
'金表' => '金錶',
'金钟' => '金鐘',
+'金鸡纳碱' => '金雞納鹼',
'金马仑道' => '金馬崙道',
'金发' => '金髮',
-'钉锤' => '釘鎚',
-'钩心斗角' => '鈎心鬥角',
+'鈎心斗角' => '鈎心鬥角',
'银朱' => '銀硃',
'银发' => '銀髮',
'铜范' => '銅範',
@@ -8776,7 +9014,7 @@ $zh2Hant = array(
'钱谷' => '錢穀',
'钱范' => '錢範',
'钱庄' => '錢莊',
-'锦绣花园' => '錦綉花園',
+'锦綉花园' => '錦綉花園',
'锦绣' => '錦繡',
'表停' => '錶停',
'表冠' => '錶冠',
@@ -8813,9 +9051,6 @@ $zh2Hant = array(
'锻炼出' => '鍛鍊出',
'锲而不舍' => '鍥而不捨',
'镰仓' => '鎌倉',
-'锤儿' => '鎚兒',
-'锤子' => '鎚子',
-'锤头' => '鎚頭',
'锈病' => '鏽病',
'锈菌' => '鏽菌',
'锈蚀' => '鏽蝕',
@@ -8895,7 +9130,6 @@ $zh2Hant = array(
'钟鼓' => '鐘鼓',
'铁杆' => '鐵杆',
'铁栏杆' => '鐵欄杆',
-'铁锤' => '鐵鎚',
'铁锈' => '鐵鏽',
'铁钟' => '鐵鐘',
'铸钟' => '鑄鐘',
@@ -9297,13 +9531,8 @@ $zh2Hant = array(
'喂鱼' => '餵魚',
'喂鸭' => '餵鴨',
'喂鹅' => '餵鵝',
-'饥寒' => '饑寒',
-'饥民' => '饑民',
-'饥渴' => '饑渴',
-'饥溺' => '饑溺',
-'饥荒' => '饑荒',
-'饥饱' => '饑飽',
-'饥馑' => '饑饉',
+'饑荒' => '饑荒',
+'饑馑' => '饑饉',
'首当其冲' => '首當其衝',
'首发' => '首發',
'首只' => '首隻',
@@ -9580,7 +9809,7 @@ $zh2Hant = array(
'鱼干' => '魚乾',
'鱼松' => '魚鬆',
'鲸须' => '鯨鬚',
-'鲇鱼' => '鯰魚',
+'鯰鱼' => '鯰魚',
'鸠占鹊巢' => '鳩佔鵲巢',
'凤凰于飞' => '鳳凰于飛',
'凤梨干' => '鳳梨乾',
@@ -9623,9 +9852,22 @@ $zh2Hant = array(
'咸鸭蛋' => '鹹鴨蛋',
'咸卤' => '鹹鹵',
'咸咸' => '鹹鹹',
+'碱化' => '鹼化',
+'碱土金属' => '鹼土金屬',
+'碱地' => '鹼地',
+'碱度' => '鹼度',
+'碱性' => '鹼性',
+'碱水' => '鹼水',
+'碱液' => '鹼液',
+'碱熔' => '鹼熔',
+'碱石灰' => '鹼石灰',
+'碱纤维素' => '鹼纖維素',
+'碱金属' => '鹼金屬',
+'碱类' => '鹼類',
'盐打怎么咸' => '鹽打怎麼鹹',
-'盐卤' => '鹽滷',
'盐余' => '鹽餘',
+'盐碱土' => '鹽鹼土',
+'盐碱滩' => '鹽鹼灘',
'丽于' => '麗於',
'曲尘' => '麴塵',
'曲蘖' => '麴櫱',
@@ -9778,18 +10020,90 @@ $zh2Hant = array(
);
$zh2Hans = array(
+'㑯' => '㑔',
+'㑳' => '㑇',
+'㒓' => '𠉂',
+'㓨' => '刾',
+'㗲' => '𠵾',
+'㘚' => '㘎',
+'㜄' => '㚯',
+'㜏' => '㛣',
+'㜢' => '𡞱',
+'㜷' => '𡝠',
'㞞' => '𪨊',
'㠏' => '㟆',
+'㢝' => '𢋈',
+'㥮' => '㤘',
+'㦎' => '𢛯',
'㩜' => '㨫',
+'㩳' => '㧐',
+'㷿' => '𤈷',
+'㺏' => '𤠋',
+'㿧' => '𤽯',
+'䀹' => '𥅴',
+'䁪' => '𥇢',
+'䁻' => '䀥',
+'䉙' => '𥬀',
'䉬' => '𫂈',
+'䉲' => '𥮜',
+'䊭' => '𥺅',
'䊷' => '䌶',
'䋙' => '䌺',
+'䋚' => '䌻',
'䋻' => '䌾',
+'䋿' => '𦈓',
+'䌈' => '𦈖',
+'䌋' => '𦈘',
+'䌖' => '𦈜',
+'䌝' => '𦈟',
+'䌟' => '𦈞',
+'䌥' => '𦈠',
+'䌰' => '𦈙',
+'䕳' => '𦰴',
+'䗿' => '𧉞',
+'䙡' => '䙌',
+'䜀' => '䜧',
+'䝻' => '𧹕',
'䝼' => '䞍',
+'䞈' => '𧹑',
+'䢨' => '𨑹',
+'䥇' => '䦂',
+'䥩' => '𨱖',
+'䥱' => '䥾',
+'䦘' => '𨸄',
+'䦛' => '䦶',
+'䦟' => '䦷',
+'䦳' => '𨷿',
+'䧢' => '𨸟',
+'䪏' => '𩏼',
+'䪗' => '𩐀',
+'䪘' => '𩏿',
+'䫴' => '𩖗',
+'䬘' => '𩙮',
+'䬝' => '𩙯',
+'䬞' => '𩙧',
+'䭀' => '𩠇',
+'䭃' => '𩠈',
+'䭿' => '𩧭',
+'䮝' => '𩧰',
+'䮞' => '𩨁',
+'䮠' => '𩧿',
+'䮫' => '𩨇',
+'䮳' => '𩨏',
+'䮾' => '𩧪',
'䯀' => '䯅',
'䰾' => '鲃',
+'䱙' => '𩾈',
+'䱬' => '𩾊',
+'䱰' => '𩾋',
+'䱷' => '䲣',
'䱽' => '䲝',
'䲁' => '鳚',
+'䲖' => '𩾂',
+'䲰' => '𪉂',
+'䴉' => '鹮',
+'䴬' => '𪎈',
+'䴴' => '𪎋',
'丟' => '丢',
'並' => '并',
'乾' => '干',
@@ -9807,6 +10121,7 @@ $zh2Hans = array(
'係' => '系',
'俔' => '伣',
'俠' => '侠',
+'俥' => '伡',
'倀' => '伥',
'倆' => '俩',
'倈' => '俫',
@@ -9815,7 +10130,9 @@ $zh2Hans = array(
'們' => '们',
'倖' => '幸',
'倫' => '伦',
+'倲' => '㑈',
'偉' => '伟',
+'偑' => '㐽',
'側' => '侧',
'偵' => '侦',
'偽' => '伪',
@@ -9851,6 +10168,7 @@ $zh2Hans = array(
'儕' => '侪',
'儘' => '尽',
'償' => '偿',
+'儣' => '𠆲',
'優' => '优',
'儲' => '储',
'儷' => '俪',
@@ -9884,6 +10202,7 @@ $zh2Hans = array(
'剴' => '剀',
'創' => '创',
'剷' => '铲',
+'剾' => '𠛅',
'劃' => '划',
'劇' => '剧',
'劉' => '刘',
@@ -9914,7 +10233,6 @@ $zh2Hans = array(
'卻' => '却',
'卽' => '即',
'厙' => '厍',
-'厠' => '厕',
'厤' => '历',
'厭' => '厌',
'厲' => '厉',
@@ -9928,10 +10246,10 @@ $zh2Hans = array(
'呂' => '吕',
'咼' => '呙',
'員' => '员',
+'哯' => '𠯟',
'唄' => '呗',
'唚' => '吣',
'問' => '问',
-'啓' => '启',
'啞' => '哑',
'啟' => '启',
'啢' => '唡',
@@ -9952,6 +10270,7 @@ $zh2Hans = array(
'嗹' => '𪡏',
'嘆' => '叹',
'嘍' => '喽',
+'嘓' => '啯',
'嘔' => '呕',
'嘖' => '啧',
'嘗' => '尝',
@@ -9964,6 +10283,7 @@ $zh2Hans = array(
'嘸' => '呒',
'嘽' => '啴',
'噁' => '恶',
+'噅' => '𠯠',
'噓' => '嘘',
'噚' => '㖊',
'噝' => '咝',
@@ -9994,6 +10314,7 @@ $zh2Hans = array(
'囂' => '嚣',
'囅' => '冁',
'囈' => '呓',
+'囉' => '啰',
'囌' => '苏',
'囑' => '嘱',
'囪' => '囱',
@@ -10031,7 +10352,6 @@ $zh2Hans = array(
'墮' => '堕',
'墰' => '坛',
'墳' => '坟',
-'墻' => '墙',
'墾' => '垦',
'壇' => '坛',
'壈' => '𡒄',
@@ -10058,7 +10378,6 @@ $zh2Hans = array(
'奧' => '奥',
'奩' => '奁',
'奪' => '夺',
-'奬' => '奖',
'奮' => '奋',
'奼' => '姹',
'妝' => '妆',
@@ -10070,13 +10389,14 @@ $zh2Hans = array(
'婭' => '娅',
'媧' => '娲',
'媯' => '妫',
+'媰' => '㛀',
'媼' => '媪',
'媽' => '妈',
'嫗' => '妪',
'嫵' => '妩',
'嫻' => '娴',
'嫿' => '婳',
-'嬀' => '妫',
+'嬃' => '媭',
'嬈' => '娆',
'嬋' => '婵',
'嬌' => '娇',
@@ -10086,6 +10406,7 @@ $zh2Hans = array(
'嬪' => '嫔',
'嬰' => '婴',
'嬸' => '婶',
+'孋' => '㛤',
'孌' => '娈',
'孫' => '孙',
'學' => '学',
@@ -10127,6 +10448,7 @@ $zh2Hans = array(
'崬' => '岽',
'嵐' => '岚',
'嵗' => '岁',
+'嵼' => '𡶴',
'嶁' => '嵝',
'嶄' => '崭',
'嶇' => '岖',
@@ -10188,7 +10510,9 @@ $zh2Hans = array(
'彎' => '弯',
'彙' => '汇',
'彞' => '彝',
+'彠' => '彟',
'彥' => '彦',
+'彲' => '彨',
'後' => '后',
'徑' => '径',
'從' => '从',
@@ -10219,7 +10543,6 @@ $zh2Hans = array(
'慚' => '惭',
'慟' => '恸',
'慣' => '惯',
-'慤' => '悫',
'慪' => '怄',
'慫' => '怂',
'慮' => '虑',
@@ -10238,6 +10561,7 @@ $zh2Hans = array(
'憮' => '怃',
'憲' => '宪',
'憶' => '忆',
+'懀' => '𢙓',
'懇' => '恳',
'應' => '应',
'懌' => '怿',
@@ -10273,6 +10597,7 @@ $zh2Hans = array(
'捲' => '卷',
'掃' => '扫',
'掄' => '抡',
+'掆' => '㧏',
'掗' => '挜',
'掙' => '挣',
'掛' => '挂',
@@ -10286,6 +10611,7 @@ $zh2Hans = array(
'搗' => '捣',
'搵' => '揾',
'搶' => '抢',
+'摋' => '𢫬',
'摑' => '掴',
'摜' => '掼',
'摟' => '搂',
@@ -10317,6 +10643,7 @@ $zh2Hans = array(
'擔' => '担',
'據' => '据',
'擠' => '挤',
+'擣' => '𢭏',
'擬' => '拟',
'擯' => '摈',
'擰' => '拧',
@@ -10327,6 +10654,7 @@ $zh2Hans = array(
'擺' => '摆',
'擻' => '擞',
'擼' => '撸',
+'擽' => '㧰',
'擾' => '扰',
'攄' => '摅',
'攆' => '撵',
@@ -10348,6 +10676,8 @@ $zh2Hans = array(
'數' => '数',
'斂' => '敛',
'斃' => '毙',
+'斅' => '𢽾',
+'斆' => '敩',
'斕' => '斓',
'斬' => '斩',
'斷' => '断',
@@ -10370,10 +10700,12 @@ $zh2Hans = array(
'曏' => '向',
'曖' => '暧',
'曠' => '旷',
+'曥' => '𣆐',
'曨' => '昽',
'曬' => '晒',
'書' => '书',
'會' => '会',
+'朥' => '𦛨',
'朧' => '胧',
'朮' => '术',
'東' => '东',
@@ -10390,10 +10722,12 @@ $zh2Hans = array(
'棖' => '枨',
'棗' => '枣',
'棟' => '栋',
+'棡' => '㭎',
'棧' => '栈',
'棲' => '栖',
'棶' => '梾',
'椏' => '桠',
+'椲' => '㭏',
'楊' => '杨',
'楓' => '枫',
'楨' => '桢',
@@ -10411,6 +10745,8 @@ $zh2Hans = array(
'槧' => '椠',
'槨' => '椁',
'槳' => '桨',
+'槶' => '椢',
+'槼' => '椝',
'樁' => '桩',
'樂' => '乐',
'樅' => '枞',
@@ -10418,10 +10754,14 @@ $zh2Hans = array(
'樓' => '楼',
'標' => '标',
'樞' => '枢',
+'樢' => '㭤',
'樣' => '样',
+'樫' => '㭴',
+'樳' => '桪',
'樸' => '朴',
'樹' => '树',
'樺' => '桦',
+'樿' => '椫',
'橈' => '桡',
'橋' => '桥',
'機' => '机',
@@ -10434,6 +10774,7 @@ $zh2Hans = array(
'檟' => '槚',
'檢' => '检',
'檣' => '樯',
+'檭' => '𣘴',
'檮' => '梼',
'檯' => '台',
'檳' => '槟',
@@ -10459,8 +10800,10 @@ $zh2Hans = array(
'欄' => '栏',
'欅' => '榉',
'權' => '权',
+'欍' => '𣐤',
'欏' => '椤',
'欒' => '栾',
+'欓' => '𣗋',
'欖' => '榄',
'欞' => '棂',
'欽' => '钦',
@@ -10483,7 +10826,6 @@ $zh2Hans = array(
'殰' => '㱩',
'殲' => '歼',
'殺' => '杀',
-'殻' => '壳',
'殼' => '壳',
'毀' => '毁',
'毆' => '殴',
@@ -10517,6 +10859,7 @@ $zh2Hans = array(
'淺' => '浅',
'渙' => '涣',
'減' => '减',
+'渢' => '沨',
'渦' => '涡',
'測' => '测',
'渾' => '浑',
@@ -10528,6 +10871,7 @@ $zh2Hans = array(
'準' => '准',
'溝' => '沟',
'溫' => '温',
+'溳' => '涢',
'滄' => '沧',
'滅' => '灭',
'滌' => '涤',
@@ -10536,12 +10880,12 @@ $zh2Hans = array(
'滬' => '沪',
'滯' => '滞',
'滲' => '渗',
-'滷' => '卤',
'滸' => '浒',
'滻' => '浐',
'滾' => '滚',
'滿' => '满',
'漁' => '渔',
+'漊' => '溇',
'漚' => '沤',
'漢' => '汉',
'漣' => '涟',
@@ -10553,7 +10897,6 @@ $zh2Hans = array(
'潁' => '颍',
'潑' => '泼',
'潔' => '洁',
-'潙' => '沩',
'潛' => '潜',
'潤' => '润',
'潯' => '浔',
@@ -10561,6 +10904,7 @@ $zh2Hans = array(
'潷' => '滗',
'潿' => '涠',
'澀' => '涩',
+'澅' => '𣶩',
'澆' => '浇',
'澇' => '涝',
'澐' => '沄',
@@ -10574,16 +10918,22 @@ $zh2Hans = array(
'澾' => '㳠',
'濁' => '浊',
'濃' => '浓',
+'濄' => '㳡',
+'濆' => '𣸣',
'濕' => '湿',
'濘' => '泞',
+'濜' => '浕',
'濟' => '济',
'濤' => '涛',
+'濧' => '㳔',
'濫' => '滥',
'濰' => '潍',
'濱' => '滨',
'濺' => '溅',
'濼' => '泺',
'濾' => '滤',
+'瀂' => '澛',
+'瀃' => '𣽷',
'瀅' => '滢',
'瀆' => '渎',
'瀇' => '㲿',
@@ -10606,8 +10956,10 @@ $zh2Hans = array(
'灑' => '洒',
'灕' => '漓',
'灘' => '滩',
+'灙' => '𣺼',
'灝' => '灏',
'灠' => '漤',
+'灡' => '㳕',
'灣' => '湾',
'灤' => '滦',
'灧' => '滟',
@@ -10625,14 +10977,17 @@ $zh2Hans = array(
'煬' => '炀',
'煱' => '㶽',
'熅' => '煴',
+'熉' => '𤈶',
+'熌' => '𤇄',
'熒' => '荧',
+'熓' => '𤆡',
'熗' => '炝',
+'熡' => '𤋏',
'熱' => '热',
'熲' => '颎',
'熾' => '炽',
'燁' => '烨',
'燈' => '灯',
-'燉' => '炖',
'燒' => '烧',
'燙' => '烫',
'燜' => '焖',
@@ -10644,6 +10999,7 @@ $zh2Hans = array(
'燶' => '㶶',
'燼' => '烬',
'燾' => '焘',
+'爄' => '𤇃',
'爍' => '烁',
'爐' => '炉',
'爛' => '烂',
@@ -10682,6 +11038,7 @@ $zh2Hans = array(
'獻' => '献',
'獼' => '猕',
'玀' => '猡',
+'玁' => '𤞤',
'現' => '现',
'琺' => '珐',
'琿' => '珲',
@@ -10697,16 +11054,17 @@ $zh2Hans = array(
'璣' => '玑',
'璦' => '瑷',
'璫' => '珰',
+'璯' => '㻅',
'環' => '环',
'璽' => '玺',
'瓊' => '琼',
'瓏' => '珑',
'瓔' => '璎',
+'瓕' => '𤦀',
'瓚' => '瓒',
'甌' => '瓯',
'甕' => '瓮',
'產' => '产',
-'産' => '产',
'甦' => '苏',
'甯' => '宁',
'畝' => '亩',
@@ -10730,7 +11088,6 @@ $zh2Hans = array(
'瘮' => '瘆',
'瘲' => '疭',
'瘺' => '瘘',
-'瘻' => '瘘',
'療' => '疗',
'癆' => '痨',
'癇' => '痫',
@@ -10752,6 +11109,7 @@ $zh2Hans = array(
'癲' => '癫',
'發' => '发',
'皚' => '皑',
+'皟' => '𤾀',
'皰' => '疱',
'皸' => '皲',
'皺' => '皱',
@@ -10773,6 +11131,7 @@ $zh2Hans = array(
'瞘' => '眍',
'瞜' => '䁖',
'瞞' => '瞒',
+'瞤' => '𥆧',
'瞭' => '了',
'瞶' => '瞆',
'瞼' => '睑',
@@ -10786,19 +11145,24 @@ $zh2Hans = array(
'硨' => '砗',
'硯' => '砚',
'碕' => '埼',
+'碙' => '𥐻',
'碩' => '硕',
'碭' => '砀',
'碸' => '砜',
'確' => '确',
'碼' => '码',
+'碽' => '䂵',
'磑' => '硙',
'磚' => '砖',
+'磠' => '硵',
'磣' => '碜',
'磧' => '碛',
'磯' => '矶',
'磽' => '硗',
+'礄' => '硚',
'礆' => '硷',
'礎' => '础',
+'礒' => '𥐟',
'礙' => '碍',
'礦' => '矿',
'礪' => '砺',
@@ -10845,9 +11209,7 @@ $zh2Hans = array(
'竄' => '窜',
'竅' => '窍',
'竇' => '窦',
-'竈' => '灶',
'竊' => '窃',
-'竪' => '竖',
'競' => '竞',
'筆' => '笔',
'筍' => '笋',
@@ -10861,6 +11223,7 @@ $zh2Hans = array(
'築' => '筑',
'篋' => '箧',
'篔' => '筼',
+'篘' => '𥬠',
'篤' => '笃',
'篩' => '筛',
'篳' => '筚',
@@ -10875,8 +11238,11 @@ $zh2Hans = array(
'簽' => '签',
'簾' => '帘',
'籃' => '篮',
+'籋' => '𥬞',
'籌' => '筹',
+'籔' => '䉤',
'籙' => '箓',
+'籛' => '篯',
'籜' => '箨',
'籟' => '籁',
'籠' => '笼',
@@ -10949,14 +11315,14 @@ $zh2Hans = array(
'統' => '统',
'絲' => '丝',
'絳' => '绛',
-'絶' => '绝',
'絹' => '绢',
'絺' => '𫄨',
+'綀' => '𦈌',
'綁' => '绑',
'綃' => '绡',
'綆' => '绠',
+'綇' => '𦈋',
'綈' => '绨',
-'綉' => '绣',
'綌' => '绤',
'綏' => '绥',
'綐' => '䌼',
@@ -10973,7 +11339,6 @@ $zh2Hans = array(
'綰' => '绾',
'綱' => '纲',
'網' => '网',
-'綳' => '绷',
'綴' => '缀',
'綵' => '彩',
'綸' => '纶',
@@ -10987,10 +11352,9 @@ $zh2Hans = array(
'緇' => '缁',
'緊' => '紧',
'緋' => '绯',
-'緑' => '绿',
+'緍' => '𦈏',
'緒' => '绪',
'緓' => '绬',
-'緔' => '绱',
'緗' => '缃',
'緘' => '缄',
'緙' => '缂',
@@ -11005,16 +11369,20 @@ $zh2Hans = array(
'緩' => '缓',
'緬' => '缅',
'緯' => '纬',
+'緰' => '𦈕',
'緱' => '缑',
'緲' => '缈',
'練' => '练',
'緶' => '缏',
+'緷' => '𦈉',
+'緸' => '𦈑',
'緹' => '缇',
'緻' => '致',
'縈' => '萦',
'縉' => '缙',
'縊' => '缢',
'縋' => '缒',
+'縎' => '𦈔',
'縐' => '绉',
'縑' => '缣',
'縕' => '缊',
@@ -11024,8 +11392,8 @@ $zh2Hans = array(
'縞' => '缟',
'縟' => '缛',
'縣' => '县',
-'縧' => '绦',
'縫' => '缝',
+'縬' => '𦈚',
'縭' => '缡',
'縮' => '缩',
'縱' => '纵',
@@ -11036,34 +11404,39 @@ $zh2Hans = array(
'縶' => '絷',
'縷' => '缕',
'縹' => '缥',
+'縺' => '𦈐',
'總' => '总',
'績' => '绩',
'繃' => '绷',
'繅' => '缫',
'繆' => '缪',
+'繏' => '𦈝',
'繐' => '穗',
'繒' => '缯',
+'繓' => '𦈛',
'織' => '织',
'繕' => '缮',
'繚' => '缭',
'繞' => '绕',
+'繟' => '𦈎',
'繡' => '绣',
'繢' => '缋',
'繩' => '绳',
'繪' => '绘',
'繫' => '系',
'繭' => '茧',
-'繮' => '缰',
'繯' => '缳',
'繰' => '缲',
'繳' => '缴',
'繸' => '䍁',
'繹' => '绎',
+'繻' => '𦈡',
'繼' => '继',
'繽' => '缤',
'繾' => '缱',
'繿' => '䍀',
'纁' => '𫄸',
+'纇' => '颣',
'纈' => '缬',
'纊' => '纩',
'續' => '续',
@@ -11089,7 +11462,9 @@ $zh2Hans = array(
'羨' => '羡',
'義' => '义',
'習' => '习',
+'翬' => '翚',
'翹' => '翘',
+'翽' => '翙',
'耬' => '耧',
'耮' => '耢',
'聖' => '圣',
@@ -11109,18 +11484,22 @@ $zh2Hans = array(
'脈' => '脉',
'脛' => '胫',
'脣' => '唇',
+'脥' => '𣍰',
'脫' => '脱',
'脹' => '胀',
'腎' => '肾',
'腖' => '胨',
'腡' => '脶',
'腦' => '脑',
+'腪' => '𣍯',
'腫' => '肿',
'腳' => '脚',
'腸' => '肠',
'膃' => '腽',
+'膕' => '腘',
'膚' => '肤',
'膠' => '胶',
+'膢' => '𦝼',
'膩' => '腻',
'膽' => '胆',
'膾' => '脍',
@@ -11128,6 +11507,7 @@ $zh2Hans = array(
'臉' => '脸',
'臍' => '脐',
'臏' => '膑',
+'臗' => '𣎑',
'臘' => '腊',
'臚' => '胪',
'臟' => '脏',
@@ -11160,14 +11540,13 @@ $zh2Hans = array(
'萇' => '苌',
'萊' => '莱',
'萬' => '万',
+'萴' => '荝',
'萵' => '莴',
'葉' => '叶',
'葒' => '荭',
'葤' => '荮',
'葦' => '苇',
-'葯' => '药',
'葷' => '荤',
-'蒓' => '莼',
'蒔' => '莳',
'蒞' => '莅',
'蒼' => '苍',
@@ -11177,7 +11556,6 @@ $zh2Hans = array(
'蓯' => '苁',
'蓴' => '莼',
'蓽' => '荜',
-'蔔' => '卜',
'蔘' => '参',
'蔞' => '蒌',
'蔣' => '蒋',
@@ -11214,7 +11592,6 @@ $zh2Hans = array(
'藝' => '艺',
'藥' => '药',
'藪' => '薮',
-'藴' => '蕴',
'藶' => '苈',
'藹' => '蔼',
'藺' => '蔺',
@@ -11258,6 +11635,7 @@ $zh2Hans = array(
'蟲' => '虫',
'蟶' => '蛏',
'蟻' => '蚁',
+'蠁' => '蚃',
'蠅' => '蝇',
'蠆' => '虿',
'蠍' => '蝎',
@@ -11269,14 +11647,12 @@ $zh2Hans = array(
'蠱' => '蛊',
'蠶' => '蚕',
'蠻' => '蛮',
-'衆' => '众',
'衊' => '蔑',
'術' => '术',
'衕' => '同',
'衚' => '胡',
'衛' => '卫',
'衝' => '冲',
-'衹' => '只',
'袞' => '衮',
'裊' => '袅',
'裏' => '里',
@@ -11293,7 +11669,7 @@ $zh2Hans = array(
'褻' => '亵',
'襀' => '𫌀',
'襆' => '幞',
-'襇' => '裥',
+'襉' => '裥',
'襏' => '袯',
'襖' => '袄',
'襝' => '裣',
@@ -11303,6 +11679,7 @@ $zh2Hans = array(
'襬' => '䙓',
'襯' => '衬',
'襲' => '袭',
+'襴' => '襕',
'見' => '见',
'覎' => '觃',
'規' => '规',
@@ -11354,10 +11731,12 @@ $zh2Hans = array(
'訶' => '诃',
'診' => '诊',
'註' => '注',
+'詀' => '𧮪',
'詁' => '诂',
'詆' => '诋',
'詎' => '讵',
'詐' => '诈',
+'詑' => '𫍟',
'詒' => '诒',
'詔' => '诏',
'評' => '评',
@@ -11403,7 +11782,6 @@ $zh2Hans = array(
'誦' => '诵',
'誨' => '诲',
'說' => '说',
-'説' => '说',
'誰' => '谁',
'課' => '课',
'誶' => '谇',
@@ -11459,15 +11837,12 @@ $zh2Hans = array(
'講' => '讲',
'謝' => '谢',
'謠' => '谣',
-'謡' => '谣',
'謨' => '谟',
'謫' => '谪',
'謬' => '谬',
-'謭' => '谫',
'謳' => '讴',
'謹' => '谨',
'謾' => '谩',
-'譅' => '䜧',
'證' => '证',
'譊' => '𫍢',
'譎' => '谲',
@@ -11488,6 +11863,8 @@ $zh2Hans = array(
'譾' => '谫',
'讀' => '读',
'變' => '变',
+'讋' => '詟',
+'讌' => '䜩',
'讎' => '仇',
'讒' => '谗',
'讓' => '让',
@@ -11546,6 +11923,7 @@ $zh2Hans = array(
'賚' => '赉',
'賜' => '赐',
'賞' => '赏',
+'賟' => '𧹖',
'賠' => '赔',
'賡' => '赓',
'賢' => '贤',
@@ -11554,7 +11932,6 @@ $zh2Hans = array(
'賦' => '赋',
'賧' => '赕',
'質' => '质',
-'賫' => '赍',
'賬' => '账',
'賭' => '赌',
'賰' => '䞐',
@@ -11565,12 +11942,12 @@ $zh2Hans = array(
'購' => '购',
'賽' => '赛',
'賾' => '赜',
+'贃' => '𧹗',
'贄' => '贽',
'贅' => '赘',
'贇' => '赟',
'贈' => '赠',
'贊' => '赞',
-'贋' => '赝',
'贍' => '赡',
'贏' => '赢',
'贐' => '赆',
@@ -11579,7 +11956,6 @@ $zh2Hans = array(
'贖' => '赎',
'贗' => '赝',
'贛' => '赣',
-'贜' => '赃',
'赬' => '赪',
'趕' => '赶',
'趙' => '赵',
@@ -11599,16 +11975,19 @@ $zh2Hans = array(
'躊' => '踌',
'躋' => '跻',
'躍' => '跃',
+'躎' => '䟢',
'躑' => '踯',
'躒' => '跞',
'躓' => '踬',
'躕' => '蹰',
'躚' => '跹',
+'躝' => '𨅬',
'躡' => '蹑',
'躥' => '蹿',
'躦' => '躜',
'躪' => '躏',
'軀' => '躯',
+'軉' => '𨉗',
'車' => '车',
'軋' => '轧',
'軌' => '轨',
@@ -11617,6 +11996,7 @@ $zh2Hans = array(
'軑' => '轪',
'軒' => '轩',
'軔' => '轫',
+'軗' => '𨐅',
'軛' => '轭',
'軟' => '软',
'軤' => '轷',
@@ -11630,6 +12010,7 @@ $zh2Hans = array(
'軼' => '轶',
'軾' => '轼',
'較' => '较',
+'輄' => '𨐈',
'輅' => '辂',
'輇' => '辁',
'輈' => '辀',
@@ -11720,7 +12101,6 @@ $zh2Hans = array(
'鄺' => '邝',
'酇' => '酂',
'酈' => '郦',
-'醖' => '酝',
'醜' => '丑',
'醞' => '酝',
'醣' => '糖',
@@ -11746,10 +12126,12 @@ $zh2Hans = array(
'釤' => '钐',
'釧' => '钏',
'釩' => '钒',
+'釳' => '𨰿',
'釵' => '钗',
'釷' => '钍',
'釹' => '钕',
'釺' => '钎',
+'釾' => '䥺',
'鈀' => '钯',
'鈁' => '钫',
'鈃' => '钘',
@@ -11757,20 +12139,23 @@ $zh2Hans = array(
'鈇' => '𫓧',
'鈈' => '钚',
'鈉' => '钠',
+'鈋' => '𨱂',
'鈍' => '钝',
-'鈎' => '钩',
'鈐' => '钤',
'鈑' => '钣',
'鈒' => '钑',
'鈔' => '钞',
'鈕' => '钮',
'鈞' => '钧',
+'鈠' => '𨱁',
'鈣' => '钙',
'鈥' => '钬',
'鈦' => '钛',
'鈧' => '钪',
'鈮' => '铌',
+'鈯' => '𨱄',
'鈰' => '铈',
+'鈲' => '𨱃',
'鈳' => '钶',
'鈴' => '铃',
'鈷' => '钴',
@@ -11781,6 +12166,7 @@ $zh2Hans = array(
'鈾' => '铀',
'鈿' => '钿',
'鉀' => '钾',
+'鉁' => '𨱅',
'鉅' => '钜',
'鉈' => '铊',
'鉉' => '铉',
@@ -11792,7 +12178,6 @@ $zh2Hans = array(
'鉚' => '铆',
'鉛' => '铅',
'鉞' => '钺',
-'鉢' => '钵',
'鉤' => '钩',
'鉦' => '钲',
'鉬' => '钼',
@@ -11824,14 +12209,15 @@ $zh2Hans = array(
'銬' => '铐',
'銱' => '铞',
'銳' => '锐',
+'銶' => '𨱇',
'銷' => '销',
-'銹' => '锈',
'銻' => '锑',
'銼' => '锉',
'鋁' => '铝',
'鋃' => '锒',
'鋅' => '锌',
'鋇' => '钡',
+'鋉' => '𨱈',
'鋌' => '铤',
'鋏' => '铗',
'鋒' => '锋',
@@ -11845,7 +12231,6 @@ $zh2Hans = array(
'鋨' => '锇',
'鋩' => '铓',
'鋪' => '铺',
-'鋭' => '锐',
'鋮' => '铖',
'鋯' => '锆',
'鋰' => '锂',
@@ -11854,6 +12239,7 @@ $zh2Hans = array(
'鋸' => '锯',
'鋼' => '钢',
'錁' => '锞',
+'錂' => '𨱋',
'錄' => '录',
'錆' => '锖',
'錇' => '锫',
@@ -11876,13 +12262,12 @@ $zh2Hans = array(
'錫' => '锡',
'錮' => '锢',
'錯' => '错',
-'録' => '录',
'錳' => '锰',
'錶' => '表',
'錸' => '铼',
'鍀' => '锝',
-'鍁' => '锨',
'鍃' => '锪',
+'鍄' => '𨱉',
'鍆' => '钔',
'鍇' => '锴',
'鍈' => '锳',
@@ -11898,6 +12283,7 @@ $zh2Hans = array(
'鍥' => '锲',
'鍩' => '锘',
'鍬' => '锹',
+'鍮' => '𨱎',
'鍰' => '锾',
'鍵' => '键',
'鍶' => '锶',
@@ -11911,7 +12297,6 @@ $zh2Hans = array(
'鎔' => '镕',
'鎖' => '锁',
'鎘' => '镉',
-'鎚' => '锤',
'鎛' => '镈',
'鎝' => '𨱏',
'鎡' => '镃',
@@ -11924,15 +12309,18 @@ $zh2Hans = array(
'鎬' => '镐',
'鎭' => '鎮',
'鎮' => '镇',
+'鎯' => '𨱍',
'鎰' => '镒',
'鎲' => '镋',
'鎳' => '镍',
'鎵' => '镓',
-'鎸' => '镌',
+'鎷' => '𨰾',
'鎿' => '镎',
'鏃' => '镞',
+'鏆' => '𨱌',
'鏇' => '镟',
'鏈' => '链',
+'鏉' => '𨱒',
'鏌' => '镆',
'鏍' => '镙',
'鏐' => '镠',
@@ -11953,23 +12341,28 @@ $zh2Hans = array(
'鏵' => '铧',
'鏷' => '镤',
'鏹' => '镪',
+'鏺' => '䥽',
'鏽' => '锈',
'鐃' => '铙',
+'鐄' => '𨱑',
'鐋' => '铴',
'鐍' => '𫔎',
+'鐎' => '𨱓',
+'鐏' => '𨱔',
'鐐' => '镣',
'鐒' => '铹',
'鐓' => '镦',
'鐔' => '镡',
'鐘' => '钟',
'鐙' => '镫',
-'鐝' => '镢',
'鐠' => '镨',
+'鐥' => '䦅',
'鐦' => '锎',
'鐧' => '锏',
'鐨' => '镄',
'鐫' => '镌',
'鐮' => '镰',
+'鐯' => '䦃',
'鐲' => '镯',
'鐳' => '镭',
'鐵' => '铁',
@@ -12009,8 +12402,10 @@ $zh2Hans = array(
'閉' => '闭',
'開' => '开',
'閌' => '闶',
+'閍' => '𨸂',
'閎' => '闳',
'閏' => '闰',
+'閐' => '𨸃',
'閑' => '闲',
'閒' => '闲',
'間' => '间',
@@ -12026,7 +12421,6 @@ $zh2Hans = array(
'閬' => '阆',
'閭' => '闾',
'閱' => '阅',
-'閲' => '阅',
'閶' => '阊',
'閹' => '阉',
'閻' => '阎',
@@ -12142,7 +12536,7 @@ $zh2Hans = array(
'頸' => '颈',
'頹' => '颓',
'頻' => '频',
-'頽' => '颓',
+'顃' => '𩖖',
'顆' => '颗',
'題' => '题',
'額' => '额',
@@ -12150,7 +12544,6 @@ $zh2Hans = array(
'顏' => '颜',
'顒' => '颙',
'顓' => '颛',
-'顔' => '颜',
'願' => '愿',
'顙' => '颡',
'顛' => '颠',
@@ -12169,13 +12562,16 @@ $zh2Hans = array(
'颭' => '飐',
'颮' => '飑',
'颯' => '飒',
+'颰' => '𩙥',
'颱' => '台',
'颳' => '刮',
'颶' => '飓',
+'颷' => '𩙪',
'颸' => '飔',
'颺' => '飏',
'颻' => '飖',
'颼' => '飕',
+'颾' => '𩙫',
'飀' => '飗',
'飄' => '飘',
'飆' => '飙',
@@ -12226,6 +12622,7 @@ $zh2Hans = array(
'餵' => '喂',
'餶' => '馉',
'餷' => '馇',
+'餸' => '𩠌',
'餺' => '馎',
'餼' => '饩',
'餾' => '馏',
@@ -12238,7 +12635,6 @@ $zh2Hans = array(
'饊' => '馓',
'饋' => '馈',
'饌' => '馔',
-'饑' => '饥',
'饒' => '饶',
'饗' => '飨',
'饘' => '𫗴',
@@ -12254,6 +12650,7 @@ $zh2Hans = array(
'馹' => '驲',
'駁' => '驳',
'駃' => '𫘝',
+'駎' => '𩧨',
'駐' => '驻',
'駑' => '驽',
'駒' => '驹',
@@ -12261,14 +12658,17 @@ $zh2Hans = array(
'駕' => '驾',
'駘' => '骀',
'駙' => '驸',
+'駚' => '𩧫',
'駛' => '驶',
'駝' => '驼',
'駟' => '驷',
-'駡' => '骂',
'駢' => '骈',
+'駧' => '𩧲',
+'駩' => '𩧴',
'駭' => '骇',
'駰' => '骃',
'駱' => '骆',
+'駶' => '𩧺',
'駸' => '骎',
'駻' => '𫘣',
'駿' => '骏',
@@ -12280,11 +12680,16 @@ $zh2Hans = array(
'騍' => '骒',
'騎' => '骑',
'騏' => '骐',
+'騔' => '𩨀',
'騖' => '骛',
'騙' => '骗',
+'騚' => '𩨊',
+'騝' => '𩨃',
+'騟' => '𩨈',
'騠' => '𫘨',
'騤' => '骙',
'騧' => '䯄',
+'騪' => '𩨄',
'騫' => '骞',
'騭' => '骘',
'騮' => '骝',
@@ -12300,6 +12705,7 @@ $zh2Hans = array(
'驄' => '骢',
'驅' => '驱',
'驊' => '骅',
+'驋' => '𩧯',
'驌' => '骕',
'驍' => '骁',
'驏' => '骣',
@@ -12331,12 +12737,14 @@ $zh2Hans = array(
'鬩' => '阋',
'鬮' => '阄',
'鬱' => '郁',
+'鬹' => '鬶',
'魎' => '魉',
'魘' => '魇',
'魚' => '鱼',
'魛' => '鱽',
'魟' => '𫚉',
'魢' => '鱾',
+'魥' => '𩽹',
'魨' => '鲀',
'魯' => '鲁',
'魴' => '鲂',
@@ -12348,15 +12756,15 @@ $zh2Hans = array(
'鮊' => '鲌',
'鮋' => '鲉',
'鮍' => '鲏',
-'鮎' => '鲇',
'鮐' => '鲐',
'鮑' => '鲍',
'鮒' => '鲋',
'鮓' => '鲊',
'鮚' => '鲒',
'鮜' => '鲘',
-'鮝' => '鲞',
'鮞' => '鲕',
+'鮟' => '𩽾',
+'鮣' => '䲟',
'鮦' => '鲖',
'鮪' => '鲔',
'鮫' => '鲛',
@@ -12365,9 +12773,11 @@ $zh2Hans = array(
'鮰' => '𫚔',
'鮳' => '鲓',
'鮶' => '鲪',
+'鮸' => '𩾃',
'鮺' => '鲝',
'鯀' => '鲧',
'鯁' => '鲠',
+'鯄' => '𩾁',
'鯆' => '𫚙',
'鯇' => '鲩',
'鯉' => '鲤',
@@ -12386,19 +12796,21 @@ $zh2Hans = array(
'鯨' => '鲸',
'鯪' => '鲮',
'鯫' => '鲰',
-'鯰' => '鲇',
+'鯱' => '𩾇',
'鯴' => '鲺',
+'鯶' => '𩽼',
'鯷' => '鳀',
'鯽' => '鲫',
'鯿' => '鳊',
'鰁' => '鳈',
'鰂' => '鲗',
'鰃' => '鳂',
+'鰆' => '䲠',
'鰈' => '鲽',
'鰉' => '鳇',
+'鰌' => '䲡',
'鰍' => '鳅',
'鰏' => '鲾',
-'鰐' => '鳄',
'鰒' => '鳆',
'鰓' => '鳃',
'鰜' => '鳒',
@@ -12407,6 +12819,7 @@ $zh2Hans = array(
'鰣' => '鲥',
'鰤' => '𫚕',
'鰥' => '鳏',
+'鰧' => '䲢',
'鰨' => '鳎',
'鰩' => '鳐',
'鰭' => '鳍',
@@ -12423,6 +12836,7 @@ $zh2Hans = array(
'鰾' => '鳔',
'鱂' => '鳉',
'鱅' => '鳙',
+'鱇' => '𩾌',
'鱈' => '鳕',
'鱉' => '鳖',
'鱒' => '鳟',
@@ -12446,12 +12860,12 @@ $zh2Hans = array(
'鳥' => '鸟',
'鳧' => '凫',
'鳩' => '鸠',
-'鳬' => '凫',
'鳲' => '鸤',
'鳳' => '凤',
'鳴' => '鸣',
'鳶' => '鸢',
'鳷' => '𫛛',
+'鳼' => '𪉃',
'鳾' => '䴓',
'鴃' => '𫛞',
'鴆' => '鸩',
@@ -12461,6 +12875,7 @@ $zh2Hans = array(
'鴕' => '鸵',
'鴗' => '𫁡',
'鴛' => '鸳',
+'鴜' => '𪉈',
'鴝' => '鸲',
'鴞' => '鸮',
'鴟' => '鸱',
@@ -12469,6 +12884,7 @@ $zh2Hans = array(
'鴨' => '鸭',
'鴯' => '鸸',
'鴰' => '鸹',
+'鴲' => '𪉆',
'鴴' => '鸻',
'鴷' => '䴕',
'鴻' => '鸿',
@@ -12480,6 +12896,7 @@ $zh2Hans = array(
'鵑' => '鹃',
'鵒' => '鹆',
'鵓' => '鹁',
+'鵚' => '𪉍',
'鵜' => '鹈',
'鵝' => '鹅',
'鵠' => '鹄',
@@ -12515,19 +12932,19 @@ $zh2Hans = array(
'鶻' => '鹘',
'鶼' => '鹣',
'鶿' => '鹚',
-'鷀' => '鹚',
'鷁' => '鹢',
'鷂' => '鹞',
-'鷄' => '鸡',
'鷈' => '䴘',
'鷊' => '鹝',
'鷓' => '鹧',
+'鷔' => '𪉑',
'鷖' => '鹥',
'鷗' => '鸥',
'鷙' => '鸷',
'鷚' => '鹨',
'鷥' => '鸶',
'鷦' => '鹪',
+'鷨' => '𪉊',
'鷫' => '鹔',
'鷯' => '鹩',
'鷲' => '鹫',
@@ -12555,10 +12972,12 @@ $zh2Hans = array(
'鹽' => '盐',
'麗' => '丽',
'麥' => '麦',
+'麨' => '𪎊',
'麩' => '麸',
'麪' => '面',
'麫' => '面',
'麯' => '曲',
+'麲' => '𪎉',
'麴' => '曲',
'麵' => '面',
'麼' => '么',
@@ -12604,25 +13023,167 @@ $zh2Hans = array(
'龔' => '龚',
'龕' => '龛',
'龜' => '龟',
+'龭' => '𩨎',
+'龯' => '𨱆',
+'𠌥' => '𠆿',
+'𠏢' => '𠉗',
+'𠞆' => '𠛆',
+'𠠎' => '𠚳',
+'𡄔' => '𠴢',
+'𡄣' => '𠵸',
+'𡅏' => '𠲥',
+'𡑭' => '𡋗',
+'𡓾' => '𡋀',
'𡞵' => '㛟',
'𡠹' => '㛿',
'𡢃' => '㛠',
+'𡮉' => '𡭜',
+'𡮣' => '𡭬',
'𡻕' => '岁',
+'𡾱' => '㟜',
+'𢣚' => '𢘝',
+'𢣭' => '𢘞',
+'𢶫' => '𢫞',
+'𢷮' => '𢫊',
+'𢹿' => '𢬦',
+'𣙎' => '㭣',
+'𣝕' => '𣘷',
+'𣞻' => '𣘓',
+'𣠲' => '𣑶',
+'𣯴' => '𣭤',
+'𣾷' => '㳢',
+'𣿉' => '𣶫',
+'𤁣' => '𣺽',
+'𤒎' => '𤊀',
'𤪺' => '㻘',
'𤫩' => '㻏',
+'𤳸' => '𤳄',
+'𤸫' => '𤶧',
+'𥌃' => '𥅘',
+'𥕥' => '𥐰',
+'𥖅' => '𥐯',
+'𥢢' => '䅪',
+'𥨐' => '𥧂',
+'𥵃' => '𥱔',
+'𥵊' => '𥭉',
+'𥸠' => '𥮋',
+'𥼽' => '𥹥',
+'𥽖' => '𥺇',
+'𥿊' => '𦈈',
+'𦂅' => '𦈒',
+'𦃄' => '𦈗',
+'𦢈' => '𣍨',
+'𦣎' => '𦟗',
+'𦪽' => '𦨩',
+'𧔥' => '𧒭',
+'𧜗' => '䘞',
'𧜵' => '䙊',
'𧝞' => '䘛',
-'𧦧' => '𫍟',
'𧩙' => '䜥',
+'𧳟' => '𧳕',
'𧵳' => '䞌',
+'𧶔' => '𧹓',
+'𧶧' => '䞎',
+'𨄣' => '𨀱',
+'𨅍' => '𨁴',
+'𨇁' => '𧿈',
+'𨇞' => '𨅫',
+'𨈊' => '𨂺',
+'𨈌' => '𨄄',
+'𨊰' => '䢀',
+'𨊸' => '䢁',
+'𨊻' => '𨐆',
'𨋢' => '䢂',
+'𨎮' => '𨐉',
+'𨏠' => '𨐇',
+'𨏥' => '𨐊',
+'𨤻' => '𨤰',
+'𨥛' => '𨱀',
'𨦫' => '䦀',
'𨧜' => '䦁',
+'𨧱' => '𨱊',
+'𨫒' => '𨱐',
+'𨮂' => '𨱕',
'𨯅' => '䥿',
+'𨳑' => '𨸁',
+'𨳕' => '𨸀',
+'𨴗' => '𨸅',
+'𨵩' => '𨸆',
+'𨵸' => '𨸇',
+'𨶀' => '𨸉',
+'𨶏' => '𨸊',
+'𨶮' => '𨸌',
+'𨶲' => '𨸋',
+'𨷲' => '𨸎',
+'𨽏' => '𨸘',
+'𩎢' => '𩏾',
+'𩏪' => '𩏽',
+'𩓣' => '𩖕',
+'𩗀' => '𩙦',
+'𩘀' => '𩙩',
+'𩘝' => '𩙭',
+'𩘹' => '𩙨',
+'𩘺' => '𩙬',
+'𩙈' => '𩙰',
+'𩚛' => '𩟿',
+'𩚥' => '𩠀',
+'𩚵' => '𩠁',
+'𩛆' => '𩠂',
+'𩛩' => '𩠃',
+'𩜇' => '𩠉',
+'𩜦' => '𩠆',
+'𩜵' => '𩠊',
+'𩝔' => '𩠋',
+'𩞄' => '𩠎',
+'𩞦' => '𩠏',
+'𩞯' => '䭪',
+'𩟐' => '𩠅',
+'𩠴' => '𩠠',
+'𩡺' => '𩧦',
+'𩢡' => '𩧬',
+'𩢴' => '𩧵',
+'𩢸' => '𩧳',
+'𩢾' => '𩧮',
+'𩣏' => '𩧶',
'𩣑' => '䯃',
'𩣵' => '𩧻',
+'𩣺' => '𩧼',
+'𩤊' => '𩧩',
+'𩤙' => '𩨆',
+'𩤲' => '𩨉',
+'𩤸' => '𩨅',
+'𩥄' => '𩨋',
+'𩥇' => '𩨍',
+'𩥉' => '𩧱',
+'𩥑' => '𩨌',
+'𩧆' => '𩨐',
+'𩭙' => '𩬣',
+'𩯳' => '𩯒',
+'𩰀' => '𩬤',
+'𩳤' => '𩲒',
+'𩵩' => '𩽺',
+'𩵹' => '𩽻',
'𩶘' => '䲞',
-'𫚒' => '軿',
+'𩶰' => '𩽿',
+'𩶱' => '𩽽',
+'𩷰' => '𩾄',
+'𩸃' => '𩾅',
+'𩸦' => '𩾆',
+'𩿪' => '𪉄',
+'𪀦' => '𪉅',
+'𪀾' => '𪉋',
+'𪁈' => '𪉉',
+'𪁖' => '𪉌',
+'𪂆' => '𪉎',
+'𪃍' => '𪉐',
+'𪃏' => '𪉏',
+'𪄆' => '𪉔',
+'𪄕' => '𪉒',
+'𪇳' => '𪉕',
+'𪋿' => '𪎍',
+'𪔵' => '𪔭',
+'𪘀' => '𪚏',
+'𪘯' => '𪚐',
'《易乾' => '《易乾',
'不著痕跡' => '不着痕迹',
'不著邊際' => '不着边际',
@@ -12761,8 +13322,8 @@ $zh2Hans = array(
'乾曜' => '乾曜',
'乾构' => '乾构',
'乾構' => '乾构',
-'乾枢' => '乾枢',
'乾樞' => '乾枢',
+'乾枢' => '乾枢',
'乾栋' => '乾栋',
'乾棟' => '乾栋',
'乾步' => '乾步',
@@ -13472,6 +14033,7 @@ $zh2Hans = array(
'尋著稱' => '寻著称',
'尋著者' => '寻著者',
'尋著述' => '寻著述',
+'將軍抽俥' => '将军抽俥',
'將軍抽車' => '将军抽車',
'尼乾陀' => '尼乾陀',
'展著' => '展着',
@@ -14789,6 +15351,8 @@ $zh2Hans = array(
'繞著稱' => '绕著称',
'繞著者' => '绕著者',
'繞著述' => '绕著述',
+'綳著勁' => '绷着劲',
+'綳著臉' => '绷着脸',
'編著' => '编著',
'纏著' => '缠着',
'纏著書' => '缠著书',
@@ -14917,7 +15481,9 @@ $zh2Hans = array(
'著者' => '著者',
'著身' => '著身',
'著述' => '著述',
+'蒙汗葯' => '蒙汗药',
'蒙著' => '蒙着',
+'蒙葯' => '蒙药',
'蒙著書' => '蒙著书',
'蒙著书' => '蒙著书',
'蒙著作' => '蒙著作',
@@ -15203,6 +15769,7 @@ $zh2Hans = array(
'達著述' => '达著述',
'近角聪信' => '近角聪信',
'近角聰信' => '近角聪信',
+'这么' => '这么',
'遠著' => '远着',
'遠著書' => '远著书',
'遠著作' => '远著作',
@@ -15511,7 +16078,6 @@ $zh2Hans = array(
'鬱氏' => '鬱氏',
'魏徵' => '魏徵',
'魚乾乾' => '鱼干干',
-'鯰魚' => '鲶鱼',
'麯崇裕' => '麯崇裕',
'麴義' => '麴义',
'麴义' => '麴义',
@@ -18515,4 +19081,4 @@ $zh2SG = array(
'笨豬跳' => '绑紧跳',
'蹦极跳' => '绑紧跳',
'笑星' => '谐星',
-); \ No newline at end of file
+);
diff --git a/includes/ZipDirectoryReader.php b/includes/ZipDirectoryReader.php
index 0e84583f..646180d2 100644
--- a/includes/ZipDirectoryReader.php
+++ b/includes/ZipDirectoryReader.php
@@ -34,10 +34,10 @@ class ZipDirectoryReader {
*
* Because this class is aimed at verification, an error is raised on
* suspicious or ambiguous input, instead of emulating some standard
- * behaviour.
+ * behavior.
*
- * @param $fileName string The archive file name
- * @param $callback Array The callback function. It will be called for each file
+ * @param string $fileName The archive file name
+ * @param array $callback The callback function. It will be called for each file
* with a single associative array each time, with members:
*
* - name: The file name. Directories conventionally have a trailing
@@ -47,7 +47,7 @@ class ZipDirectoryReader {
*
* - size: The uncompressed file size
*
- * @param $options Array An associative array of read options, with the option
+ * @param array $options An associative array of read options, with the option
* name in the key. This may currently contain:
*
* - zip64: If this is set to true, then we will emulate a
@@ -181,7 +181,7 @@ class ZipDirectoryReader {
* Throw an error, and log a debug message
*/
function error( $code, $debugMessage ) {
- wfDebug( __CLASS__.": Fatal error: $debugMessage\n" );
+ wfDebug( __CLASS__ . ": Fatal error: $debugMessage\n" );
throw new ZipDirectoryReaderError( $code );
}
@@ -220,7 +220,7 @@ class ZipDirectoryReader {
if ( $structSize + $this->eocdr['file comment length'] != strlen( $block ) - $sigPos ) {
$this->error( 'zip-bad', 'trailing bytes after the end of the file comment' );
}
- if ( $this->eocdr['disk'] !== 0
+ if ( $this->eocdr['disk'] !== 0
|| $this->eocdr['CD start disk'] !== 0 )
{
$this->error( 'zip-unsupported', 'more than one disk (in EOCDR)' );
@@ -262,7 +262,7 @@ class ZipDirectoryReader {
* may replace the regular "end of central directory record" in ZIP64 files.
*/
function readZip64EndOfCentralDirectoryRecord() {
- if ( $this->eocdr64Locator['eocdr64 start disk'] != 0
+ if ( $this->eocdr64Locator['eocdr64 start disk'] != 0
|| $this->eocdr64Locator['number of disks'] != 0 )
{
$this->error( 'zip-unsupported', 'more than one disk (in EOCDR64 locator)' );
@@ -286,7 +286,7 @@ class ZipDirectoryReader {
if ( $data['signature'] !== "PK\x06\x06" ) {
$this->error( 'zip-bad', 'wrong signature on Zip64 end of central directory record' );
}
- if ( $data['disk'] !== 0
+ if ( $data['disk'] !== 0
|| $data['CD start disk'] !== 0 )
{
$this->error( 'zip-unsupported', 'more than one disk (in EOCDR64)' );
@@ -327,7 +327,7 @@ class ZipDirectoryReader {
$offset = $this->eocdr['CD offset'];
$numEntries = $this->eocdr['CD entries total'];
$endPos = $this->eocdr['position'];
- if ( $size == 0xffffffff
+ if ( $size == 0xffffffff
|| $offset == 0xffffffff
|| $numEntries == 0xffff )
{
@@ -395,7 +395,7 @@ class ZipDirectoryReader {
$data += $this->unpack( $block, $variableInfo, $pos );
$pos += $this->getStructSize( $variableInfo );
- if ( $this->zip64 && (
+ if ( $this->zip64 && (
$data['compressed size'] == 0xffffffff
|| $data['uncompressed size'] == 0xffffffff
|| $data['local header offset'] == 0xffffffff ) )
@@ -494,8 +494,8 @@ class ZipDirectoryReader {
* Get the file contents from a given offset. If there are not enough bytes
* in the file to satisfy the request, an exception will be thrown.
*
- * @param $start int The byte offset of the start of the block.
- * @param $length int The number of bytes to return. If omitted, the remainder
+ * @param int $start The byte offset of the start of the block.
+ * @param int $length The number of bytes to return. If omitted, the remainder
* of the file will be returned.
*
* @return string
@@ -538,7 +538,7 @@ class ZipDirectoryReader {
* of length self::SEGSIZE. The result is cached. This is a helper function
* for getBlock().
*
- * If there are not enough bytes in the file to satsify the request, the
+ * If there are not enough bytes in the file to satisfy the request, the
* return value will be truncated. If a request is made for a segment beyond
* the end of the file, an empty string will be returned.
* @return string
@@ -570,7 +570,7 @@ class ZipDirectoryReader {
$size = 0;
foreach ( $struct as $type ) {
if ( is_array( $type ) ) {
- list( $typeName, $fieldSize ) = $type;
+ list( , $fieldSize ) = $type;
$size += $fieldSize;
} else {
$size += $type;
@@ -583,9 +583,9 @@ class ZipDirectoryReader {
* Unpack a binary structure. This is like the built-in unpack() function
* except nicer.
*
- * @param $string string The binary data input
+ * @param string $string The binary data input
*
- * @param $struct array An associative array giving structure members and their
+ * @param array $struct An associative array giving structure members and their
* types. In the key is the field name. The value may be either an
* integer, in which case the field is a little-endian unsigned integer
* encoded in the given number of bytes, or an array, in which case the
@@ -594,8 +594,9 @@ class ZipDirectoryReader {
* - "string": The second array element gives the length of string.
* Not null terminated.
*
- * @param $offset int The offset into the string at which to start unpacking.
+ * @param int $offset The offset into the string at which to start unpacking.
*
+ * @throws MWException
* @return array Unpacked associative array. Note that large integers in the input
* may be represented as floating point numbers in the return value, so
* the use of weak comparison is advised.
@@ -617,12 +618,11 @@ class ZipDirectoryReader {
$pos += $fieldSize;
break;
default:
- throw new MWException( __METHOD__.": invalid type \"$typeName\"" );
+ throw new MWException( __METHOD__ . ": invalid type \"$typeName\"" );
}
} else {
// Unsigned little-endian integer
$length = intval( $type );
- $bytes = substr( $string, $pos, $length );
// Calculate the value. Use an algorithm which automatically
// upgrades the value to floating point if necessary.
@@ -651,7 +651,7 @@ class ZipDirectoryReader {
* boolean.
*
* @param $value integer
- * @param $bitIndex int The index of the bit, where 0 is the LSB.
+ * @param int $bitIndex The index of the bit, where 0 is the LSB.
* @return bool
*/
function testBit( $value, $bitIndex ) {
diff --git a/includes/actions/CachedAction.php b/includes/actions/CachedAction.php
index d21f9aeb..bfdda7b9 100644
--- a/includes/actions/CachedAction.php
+++ b/includes/actions/CachedAction.php
@@ -1,22 +1,8 @@
<?php
-
/**
* Abstract action class with scaffolding for caching HTML and other values
* in a single blob.
*
- * Before using any of the caching functionality, call startCache.
- * After the last call to either getCachedValue or addCachedHTML, call saveCache.
- *
- * To get a cached value or compute it, use getCachedValue like this:
- * $this->getCachedValue( $callback );
- *
- * To add HTML that should be cached, use addCachedHTML like this:
- * $this->addCachedHTML( $callback );
- *
- * The callback function is only called when needed, so do all your expensive
- * computations here. This function should returns the HTML to be cached.
- * It should not add anything to the PageOutput object!
- *
* 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
@@ -33,10 +19,30 @@
* http://www.gnu.org/copyleft/gpl.html
*
* @file
- * @ingroup Action
+ * @ingroup Actions
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
* @since 1.20
*/
+
+/**
+ * Abstract action class with scaffolding for caching HTML and other values
+ * in a single blob.
+ *
+ * Before using any of the caching functionality, call startCache.
+ * After the last call to either getCachedValue or addCachedHTML, call saveCache.
+ *
+ * To get a cached value or compute it, use getCachedValue like this:
+ * $this->getCachedValue( $callback );
+ *
+ * To add HTML that should be cached, use addCachedHTML like this:
+ * $this->addCachedHTML( $callback );
+ *
+ * The callback function is only called when needed, so do all your expensive
+ * computations here. This function should returns the HTML to be cached.
+ * It should not add anything to the PageOutput object!
+ *
+ * @ingroup Actions
+ */
abstract class CachedAction extends FormlessAction implements ICacheHelper {
/**
diff --git a/includes/actions/CreditsAction.php b/includes/actions/CreditsAction.php
index f7152297..4d3c41be 100644
--- a/includes/actions/CreditsAction.php
+++ b/includes/actions/CreditsAction.php
@@ -23,6 +23,9 @@
* @author <evan@wikitravel.org>
*/
+/**
+ * @ingroup Actions
+ */
class CreditsAction extends FormlessAction {
public function getName() {
@@ -55,8 +58,8 @@ class CreditsAction extends FormlessAction {
/**
* Get a list of contributors
*
- * @param $cnt Int: maximum list of contributors to show
- * @param $showIfMax Bool: whether to contributors if there more than $cnt
+ * @param int $cnt maximum list of contributors to show
+ * @param bool $showIfMax whether to contributors if there more than $cnt
* @return String: html
*/
public function getCredits( $cnt, $showIfMax = true ) {
@@ -97,8 +100,8 @@ class CreditsAction extends FormlessAction {
/**
* Get a list of contributors of $article
- * @param $cnt Int: maximum list of contributors to show
- * @param $showIfMax Bool: whether to contributors if there more than $cnt
+ * @param int $cnt maximum list of contributors to show
+ * @param bool $showIfMax whether to contributors if there more than $cnt
* @return String: html
*/
protected function getContributors( $cnt, $showIfMax ) {
@@ -122,7 +125,7 @@ class CreditsAction extends FormlessAction {
# Sift for real versus user names
foreach ( $contributors as $user ) {
- $cnt--;
+ $cnt--;
if ( $user->isLoggedIn() ) {
$link = $this->link( $user );
if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() ) {
diff --git a/includes/actions/DeleteAction.php b/includes/actions/DeleteAction.php
index 5a5a382b..db7123db 100644
--- a/includes/actions/DeleteAction.php
+++ b/includes/actions/DeleteAction.php
@@ -23,17 +23,24 @@
* @author Timo Tijhof
*/
+/**
+ * Handle page deletion
+ *
+ * This is a wrapper that will call Article::delete().
+ *
+ * @ingroup Actions
+ */
class DeleteAction extends FormlessAction {
public function getName() {
return 'delete';
}
- public function onView(){
+ public function onView() {
return null;
}
- public function show(){
+ public function show() {
$this->page->delete();
diff --git a/includes/actions/EditAction.php b/includes/actions/EditAction.php
index 08a33f4c..dec3d841 100644
--- a/includes/actions/EditAction.php
+++ b/includes/actions/EditAction.php
@@ -23,17 +23,25 @@
* @author Timo Tijhof
*/
+/**
+ * Page edition handler
+ *
+ * This is a wrapper that will call the EditPage class, or ExternalEdit
+ * if $wgUseExternalEditor is set to true and requested by the user.
+ *
+ * @ingroup Actions
+ */
class EditAction extends FormlessAction {
public function getName() {
return 'edit';
}
- public function onView(){
+ public function onView() {
return null;
}
- public function show(){
+ public function show() {
$page = $this->page;
$request = $this->getRequest();
$user = $this->getUser();
@@ -56,13 +64,20 @@ class EditAction extends FormlessAction {
}
+/**
+ * Edit submission handler
+ *
+ * This is the same as EditAction; except that it sets the session cookie.
+ *
+ * @ingroup Actions
+ */
class SubmitAction extends EditAction {
public function getName() {
return 'submit';
}
- public function show(){
+ public function show() {
if ( session_id() == '' ) {
// Send a cookie so anons get talk message notifications
wfSetupSession();
diff --git a/includes/actions/HistoryAction.php b/includes/actions/HistoryAction.php
index dcd6fe55..245a5bdc 100644
--- a/includes/actions/HistoryAction.php
+++ b/includes/actions/HistoryAction.php
@@ -20,16 +20,18 @@
* http://www.gnu.org/copyleft/gpl.html
*
* @file
+ * @ingroup Actions
*/
/**
- * This class handles printing the history page for an article. In order to
+ * This class handles printing the history page for an article. In order to
* be efficient, it uses timestamps rather than offsets for paging, to avoid
* costly LIMIT,offset queries.
*
* Construct it by passing in an Article, and call $h->history() to print the
* history.
*
+ * @ingroup Actions
*/
class HistoryAction extends FormlessAction {
const DIR_PREV = 0;
@@ -132,7 +134,7 @@ class HistoryAction extends FormlessAction {
array( 'delete', 'move' ),
$this->getTitle(),
'',
- array( 'lim' => 10,
+ array( 'lim' => 10,
'conds' => array( "log_action != 'revision'" ),
'showIfEmpty' => false,
'msgKey' => array( 'moveddeleted-notice' )
@@ -145,21 +147,25 @@ class HistoryAction extends FormlessAction {
/**
* Add date selector to quickly get to a certain time
*/
- $year = $request->getInt( 'year' );
- $month = $request->getInt( 'month' );
- $tagFilter = $request->getVal( 'tagfilter' );
+ $year = $request->getInt( 'year' );
+ $month = $request->getInt( 'month' );
+ $tagFilter = $request->getVal( 'tagfilter' );
$tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter );
/**
* Option to show only revisions that have been (partially) hidden via RevisionDelete
*/
if ( $request->getBool( 'deleted' ) ) {
- $conds = array( "rev_deleted != '0'" );
+ $conds = array( 'rev_deleted != 0' );
} else {
$conds = array();
}
- $checkDeleted = Xml::checkLabel( $this->msg( 'history-show-deleted' )->text(),
+ if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) {
+ $checkDeleted = Xml::checkLabel( $this->msg( 'history-show-deleted' )->text(),
'deleted', 'mw-show-deleted-only', $request->getBool( 'deleted' ) ) . "\n";
+ } else {
+ $checkDeleted = '';
+ }
// Add the general form
$action = htmlspecialchars( $wgScript );
@@ -170,16 +176,16 @@ class HistoryAction extends FormlessAction {
false,
array( 'id' => 'mw-history-search' )
) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedDBKey() ) . "\n" .
+ Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . "\n" .
Html::hidden( 'action', 'history' ) . "\n" .
- Xml::dateMenu( $year, $month ) . '&#160;' .
+ Xml::dateMenu( ( $year == null ? date( "Y" ) : $year ), $month ) . '&#160;' .
( $tagSelector ? ( implode( '&#160;', $tagSelector ) . '&#160;' ) : '' ) .
$checkDeleted .
Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . "\n" .
'</fieldset></form>'
);
- wfRunHooks( 'PageHistoryBeforeList', array( &$this->page ) );
+ wfRunHooks( 'PageHistoryBeforeList', array( &$this->page, $this->getContext() ) );
// Create and output the list.
$pager = new HistoryPager( $this, $year, $month, $tagFilter, $conds );
@@ -218,7 +224,7 @@ class HistoryAction extends FormlessAction {
}
if ( $offset ) {
- $offsets = array( "rev_timestamp $oper '$offset'" );
+ $offsets = array( "rev_timestamp $oper " . $dbr->addQuotes( $dbr->timestamp( $offset ) ) );
} else {
$offsets = array();
}
@@ -227,7 +233,7 @@ class HistoryAction extends FormlessAction {
return $dbr->select( 'revision',
Revision::selectFields(),
- array_merge( array( "rev_page=$page_id" ), $offsets ),
+ array_merge( array( 'rev_page' => $page_id ), $offsets ),
__METHOD__,
array( 'ORDER BY' => "rev_timestamp $dirs",
'USE INDEX' => 'page_timestamp', 'LIMIT' => $limit )
@@ -237,7 +243,7 @@ class HistoryAction extends FormlessAction {
/**
* Output a subscription feed listing recent edits to this page.
*
- * @param $type String: feed type
+ * @param string $type feed type
*/
function feed( $type ) {
global $wgFeedClasses, $wgFeedLimit;
@@ -327,6 +333,7 @@ class HistoryAction extends FormlessAction {
/**
* @ingroup Pager
+ * @ingroup Actions
*/
class HistoryPager extends ReverseChronologicalPager {
public $lastRow = false, $counter, $historyPage, $buttons, $conds;
@@ -436,7 +443,7 @@ class HistoryPager extends ReverseChronologicalPager {
$this->getOutput()->wrapWikiMsg( "<div class='mw-history-legend'>\n$1\n</div>", 'histlegend' );
$s = Html::openElement( 'form', array( 'action' => $wgScript,
'id' => 'mw-history-compare' ) ) . "\n";
- $s .= Html::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) . "\n";
+ $s .= Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . "\n";
$s .= Html::hidden( 'action', 'historysubmit' ) . "\n";
// Button container stored in $this->buttons for re-use in getEndBody()
@@ -504,14 +511,14 @@ class HistoryPager extends ReverseChronologicalPager {
/**
* Creates a submit button
*
- * @param $message String: text of the submit button, will be escaped
- * @param $attributes Array: attributes
+ * @param string $message text of the submit button, will be escaped
+ * @param array $attributes attributes
* @return String: HTML output for the submit button
*/
function submitButton( $message, $attributes = array() ) {
# Disable submit button if history has 1 revision only
if ( $this->getNumRows() > 1 ) {
- return Xml::submitButton( $message , $attributes );
+ return Xml::submitButton( $message, $attributes );
} else {
return '';
}
@@ -572,11 +579,11 @@ class HistoryPager extends ReverseChronologicalPager {
} elseif ( $rev->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) {
// If revision was hidden from sysops, disable the link
if ( !$rev->userCan( Revision::DELETED_RESTRICTED, $user ) ) {
- $cdel = Linker::revDeleteLinkDisabled( false );
+ $del = Linker::revDeleteLinkDisabled( false );
// Otherwise, show the link...
} else {
$query = array( 'type' => 'revision',
- 'target' => $this->getTitle()->getPrefixedDbkey(), 'ids' => $rev->getId() );
+ 'target' => $this->getTitle()->getPrefixedDBkey(), 'ids' => $rev->getId() );
$del .= Linker::revDeleteLink( $query,
$rev->isDeleted( Revision::DELETED_RESTRICTED ), false );
}
@@ -598,19 +605,22 @@ class HistoryPager extends ReverseChronologicalPager {
$s .= ' ' . ChangesList::flag( 'minor' );
}
- # Size is always public data
- $prevSize = isset( $this->parentLens[$row->rev_parent_id] )
- ? $this->parentLens[$row->rev_parent_id]
- : 0;
- $sDiff = ChangesList::showCharacterDifference( $prevSize, $rev->getSize() );
- $fSize = Linker::formatRevisionSize($rev->getSize());
- $s .= ' <span class="mw-changeslist-separator">. .</span> ' . "$fSize $sDiff";
+ # Sometimes rev_len isn't populated
+ if ( $rev->getSize() !== null ) {
+ # Size is always public data
+ $prevSize = isset( $this->parentLens[$row->rev_parent_id] )
+ ? $this->parentLens[$row->rev_parent_id]
+ : 0;
+ $sDiff = ChangesList::showCharacterDifference( $prevSize, $rev->getSize() );
+ $fSize = Linker::formatRevisionSize( $rev->getSize() );
+ $s .= ' <span class="mw-changeslist-separator">. .</span> ' . "$fSize $sDiff";
+ }
# Text following the character difference is added just before running hooks
$s2 = Linker::revComment( $rev, false, true );
if ( $notificationtimestamp && ( $row->rev_timestamp >= $notificationtimestamp ) ) {
- $s2 .= ' <span class="updatedmarker">' . $this->msg( 'updatedmarker' )->escaped() . '</span>';
+ $s2 .= ' <span class="updatedmarker">' . $this->msg( 'updatedmarker' )->escaped() . '</span>';
$classes[] = 'mw-history-line-updated';
}
@@ -619,9 +629,12 @@ class HistoryPager extends ReverseChronologicalPager {
# Rollback and undo links
if ( $prevRev && $this->getTitle()->quickUserCan( 'edit', $user ) ) {
if ( $latest && $this->getTitle()->quickUserCan( 'rollback', $user ) ) {
- $this->preventClickjacking();
- $tools[] = '<span class="mw-rollback-link">' .
- Linker::buildRollbackLink( $rev, $this->getContext() ) . '</span>';
+ // Get a rollback link without the brackets
+ $rollbackLink = Linker::generateRollback( $rev, $this->getContext(), array( 'verify', 'noBrackets' ) );
+ if ( $rollbackLink ) {
+ $this->preventClickjacking();
+ $tools[] = $rollbackLink;
+ }
}
if ( !$rev->isDeleted( Revision::DELETED_TEXT )
@@ -644,6 +657,8 @@ class HistoryPager extends ReverseChronologicalPager {
$tools[] = "<span class=\"mw-history-undo\">{$undolink}</span>";
}
}
+ // Allow extension to add their own links here
+ wfRunHooks( 'HistoryRevisionTools', array( $rev, &$tools ) );
if ( $tools ) {
$s2 .= ' '. $this->msg( 'parentheses' )->rawParams( $lang->pipeList( $tools ) )->escaped();
@@ -661,7 +676,7 @@ class HistoryPager extends ReverseChronologicalPager {
$s .= ' <span class="mw-changeslist-separator">. .</span> ' . $s2;
}
- wfRunHooks( 'PageHistoryLineEnding', array( $this, &$row , &$s, &$classes ) );
+ wfRunHooks( 'PageHistoryLineEnding', array( $this, &$row, &$s, &$classes ) );
$attribs = array();
if ( $classes ) {
diff --git a/includes/actions/InfoAction.php b/includes/actions/InfoAction.php
index ae550391..1e312d7a 100644
--- a/includes/actions/InfoAction.php
+++ b/includes/actions/InfoAction.php
@@ -22,6 +22,11 @@
* @ingroup Actions
*/
+/**
+ * Displays information about a page.
+ *
+ * @ingroup Actions
+ */
class InfoAction extends FormlessAction {
/**
* Returns the name of the action this object responds to.
@@ -81,11 +86,11 @@ class InfoAction extends FormlessAction {
// Hide "This page is a member of # hidden categories" explanation
$content .= Html::element( 'style', array(),
- '.mw-hiddenCategoriesExplanation { display: none; }' );
+ '.mw-hiddenCategoriesExplanation { display: none; }' ) . "\n";
// Hide "Templates used on this page" explanation
$content .= Html::element( 'style', array(),
- '.mw-templatesUsedExplanation { display: none; }' );
+ '.mw-templatesUsedExplanation { display: none; }' ) . "\n";
// Get page information
$pageInfo = $this->pageInfo();
@@ -95,14 +100,14 @@ class InfoAction extends FormlessAction {
// Render page information
foreach ( $pageInfo as $header => $infoTable ) {
- $content .= $this->makeHeader( $this->msg( "pageinfo-${header}" )->escaped() );
- $table = '';
+ $content .= $this->makeHeader( $this->msg( "pageinfo-${header}" )->escaped() ) . "\n";
+ $table = "\n";
foreach ( $infoTable as $infoRow ) {
$name = ( $infoRow[0] instanceof Message ) ? $infoRow[0]->escaped() : $infoRow[0];
$value = ( $infoRow[1] instanceof Message ) ? $infoRow[1]->escaped() : $infoRow[1];
- $table = $this->addRow( $table, $name, $value );
+ $table = $this->addRow( $table, $name, $value ) . "\n";
}
- $content = $this->addTable( $content, $table );
+ $content = $this->addTable( $content, $table ) . "\n";
}
// Page footer
@@ -125,17 +130,16 @@ class InfoAction extends FormlessAction {
* @return string The HTML.
*/
protected function makeHeader( $header ) {
- global $wgParser;
- $spanAttribs = array( 'class' => 'mw-headline', 'id' => $wgParser->guessSectionNameFromWikiText( $header ) );
+ $spanAttribs = array( 'class' => 'mw-headline', 'id' => Sanitizer::escapeId( $header ) );
return Html::rawElement( 'h2', array(), Html::element( 'span', $spanAttribs, $header ) );
}
/**
* Adds a row to a table that will be added to the content.
*
- * @param $table string The table that will be added to the content
- * @param $name string The name of the row
- * @param $value string The value of the row
+ * @param string $table The table that will be added to the content
+ * @param string $name The name of the row
+ * @param string $value The value of the row
* @return string The table with the row added
*/
protected function addRow( $table, $name, $value ) {
@@ -148,8 +152,8 @@ class InfoAction extends FormlessAction {
/**
* Adds a table to the content that will be added to the output.
*
- * @param $content string The content that will be added to the output
- * @param $table string The table
+ * @param string $content The content that will be added to the output
+ * @param string $table The table
* @return string The content with the table added
*/
protected function addTable( $content, $table ) {
@@ -161,17 +165,25 @@ class InfoAction extends FormlessAction {
* Returns page information in an easily-manipulated format. Array keys are used so extensions
* may add additional information in arbitrary positions. Array values are arrays with one
* element to be rendered as a header, arrays with two elements to be rendered as a table row.
+ *
+ * @return array
*/
protected function pageInfo() {
- global $wgContLang, $wgRCMaxAge;
+ global $wgContLang, $wgRCMaxAge, $wgMemc, $wgUnwatchedPageThreshold, $wgPageInfoTransclusionLimit;
$user = $this->getUser();
$lang = $this->getLanguage();
$title = $this->getTitle();
$id = $title->getArticleID();
- // Get page information that would be too "expensive" to retrieve by normal means
- $pageCounts = self::pageCounts( $title, $user );
+ $memcKey = wfMemcKey( 'infoaction', sha1( $title->getPrefixedText() ), $this->page->getLatest() );
+ $pageCounts = $wgMemc->get( $memcKey );
+ if ( $pageCounts === false ) {
+ // Get page information that would be too "expensive" to retrieve by normal means
+ $pageCounts = self::pageCounts( $title );
+
+ $wgMemc->set( $memcKey, $pageCounts );
+ }
// Get page properties
$dbr = wfGetDB( DB_SLAVE );
@@ -201,6 +213,21 @@ class InfoAction extends FormlessAction {
$this->msg( 'pageinfo-display-title' ), $displayTitle
);
+ // Is it a redirect? If so, where to?
+ if ( $title->isRedirect() ) {
+ $pageInfo['header-basic'][] = array(
+ $this->msg( 'pageinfo-redirectsto' ),
+ Linker::link( $this->page->getRedirectTarget() ) .
+ $this->msg( 'word-separator' )->text() .
+ $this->msg( 'parentheses', Linker::link(
+ $this->page->getRedirectTarget(),
+ $this->msg( 'pageinfo-redirectsto-info' )->escaped(),
+ array(),
+ array( 'action' => 'info' )
+ ) )->text()
+ );
+ }
+
// Default sort key
$sortKey = $title->getCategorySortKey();
if ( !empty( $pageProperties['defaultsort'] ) ) {
@@ -217,6 +244,12 @@ class InfoAction extends FormlessAction {
// Page ID (number not localised, as it's a database ID)
$pageInfo['header-basic'][] = array( $this->msg( 'pageinfo-article-id' ), $id );
+ // Language in which the page content is (supposed to be) written
+ $pageLang = $title->getPageLanguage()->getCode();
+ $pageInfo['header-basic'][] = array( $this->msg( 'pageinfo-language' ),
+ Language::fetchLanguageName( $pageLang, $lang->getCode() )
+ . ' ' . $this->msg( 'parentheses', $pageLang ) );
+
// Search engine status
$pOutput = new ParserOutput();
if ( isset( $pageProperties['noindex'] ) ) {
@@ -236,11 +269,20 @@ class InfoAction extends FormlessAction {
);
}
- if ( isset( $pageCounts['watchers'] ) ) {
+ if (
+ $user->isAllowed( 'unwatchedpages' ) ||
+ ( $wgUnwatchedPageThreshold !== false &&
+ $pageCounts['watchers'] >= $wgUnwatchedPageThreshold )
+ ) {
// Number of page watchers
$pageInfo['header-basic'][] = array(
$this->msg( 'pageinfo-watchers' ), $lang->formatNum( $pageCounts['watchers'] )
);
+ } elseif ( $wgUnwatchedPageThreshold !== false ) {
+ $pageInfo['header-basic'][] = array(
+ $this->msg( 'pageinfo-watchers' ),
+ $this->msg( 'pageinfo-few-watchers' )->numParams( $wgUnwatchedPageThreshold )
+ );
}
// Redirects to this page
@@ -256,6 +298,14 @@ class InfoAction extends FormlessAction {
->numParams( count( $title->getRedirectsHere() ) )
);
+ // Is it counted as a content page?
+ if ( $this->page->isCountable() ) {
+ $pageInfo['header-basic'][] = array(
+ $this->msg( 'pageinfo-contentpage' ),
+ $this->msg( 'pageinfo-contentpage-yes' )
+ );
+ }
+
// Subpages of this page, if subpages are enabled for the current NS
if ( MWNamespace::hasSubpages( $title->getNamespace() ) ) {
$prefixIndex = SpecialPage::getTitleFor( 'Prefixindex', $title->getPrefixedText() . '/' );
@@ -269,9 +319,51 @@ class InfoAction extends FormlessAction {
);
}
+ if ( $title->inNamespace( NS_CATEGORY ) ) {
+ $category = Category::newFromTitle( $title );
+ $pageInfo['category-info'] = array(
+ array(
+ $this->msg( 'pageinfo-category-pages' ),
+ $lang->formatNum( $category->getPageCount() )
+ ),
+ array(
+ $this->msg( 'pageinfo-category-subcats' ),
+ $lang->formatNum( $category->getSubcatCount() )
+ ),
+ array(
+ $this->msg( 'pageinfo-category-files' ),
+ $lang->formatNum( $category->getFileCount() )
+ )
+ );
+ }
+
// Page protection
$pageInfo['header-restrictions'] = array();
+ // Is this page effected by the cascading protection of something which includes it?
+ if ( $title->isCascadeProtected() ) {
+ $cascadingFrom = '';
+ $sources = $title->getCascadeProtectionSources(); // Array deferencing is in PHP 5.4 :(
+
+ foreach ( $sources[0] as $sourceTitle ) {
+ $cascadingFrom .= Html::rawElement( 'li', array(), Linker::linkKnown( $sourceTitle ) );
+ }
+
+ $cascadingFrom = Html::rawElement( 'ul', array(), $cascadingFrom );
+ $pageInfo['header-restrictions'][] = array(
+ $this->msg( 'pageinfo-protect-cascading-from' ),
+ $cascadingFrom
+ );
+ }
+
+ // Is out protection set to cascade to other pages?
+ if ( $title->areRestrictionsCascading() ) {
+ $pageInfo['header-restrictions'][] = array(
+ $this->msg( 'pageinfo-protect-cascading' ),
+ $this->msg( 'pageinfo-protect-cascading-yes' )
+ );
+ }
+
// Page protection
foreach ( $title->getRestrictionTypes() as $restrictionType ) {
$protectionLevel = implode( ', ', $title->getRestrictions( $restrictionType ) );
@@ -303,40 +395,64 @@ class InfoAction extends FormlessAction {
$pageInfo['header-edits'] = array();
$firstRev = $this->page->getOldestRevision();
+ $lastRev = $this->page->getRevision();
+ $batch = new LinkBatch;
+
+ if ( $firstRev ) {
+ $firstRevUser = $firstRev->getUserText( Revision::FOR_THIS_USER );
+ if ( $firstRevUser !== '' ) {
+ $batch->add( NS_USER, $firstRevUser );
+ $batch->add( NS_USER_TALK, $firstRevUser );
+ }
+ }
- // Page creator
- $pageInfo['header-edits'][] = array(
- $this->msg( 'pageinfo-firstuser' ),
- Linker::revUserTools( $firstRev )
- );
+ if ( $lastRev ) {
+ $lastRevUser = $lastRev->getUserText( Revision::FOR_THIS_USER );
+ if ( $lastRevUser !== '' ) {
+ $batch->add( NS_USER, $lastRevUser );
+ $batch->add( NS_USER_TALK, $lastRevUser );
+ }
+ }
- // Date of page creation
- $pageInfo['header-edits'][] = array(
- $this->msg( 'pageinfo-firsttime' ),
- Linker::linkKnown(
- $title,
- $lang->userTimeAndDate( $firstRev->getTimestamp(), $user ),
- array(),
- array( 'oldid' => $firstRev->getId() )
- )
- );
+ $batch->execute();
- // Latest editor
- $pageInfo['header-edits'][] = array(
- $this->msg( 'pageinfo-lastuser' ),
- Linker::revUserTools( $this->page->getRevision() )
- );
+ if ( $firstRev ) {
+ // Page creator
+ $pageInfo['header-edits'][] = array(
+ $this->msg( 'pageinfo-firstuser' ),
+ Linker::revUserTools( $firstRev )
+ );
- // Date of latest edit
- $pageInfo['header-edits'][] = array(
- $this->msg( 'pageinfo-lasttime' ),
- Linker::linkKnown(
- $title,
- $lang->userTimeAndDate( $this->page->getTimestamp(), $user ),
- array(),
- array( 'oldid' => $this->page->getLatest() )
- )
- );
+ // Date of page creation
+ $pageInfo['header-edits'][] = array(
+ $this->msg( 'pageinfo-firsttime' ),
+ Linker::linkKnown(
+ $title,
+ $lang->userTimeAndDate( $firstRev->getTimestamp(), $user ),
+ array(),
+ array( 'oldid' => $firstRev->getId() )
+ )
+ );
+ }
+
+ if ( $lastRev ) {
+ // Latest editor
+ $pageInfo['header-edits'][] = array(
+ $this->msg( 'pageinfo-lastuser' ),
+ Linker::revUserTools( $lastRev )
+ );
+
+ // Date of latest edit
+ $pageInfo['header-edits'][] = array(
+ $this->msg( 'pageinfo-lasttime' ),
+ Linker::linkKnown(
+ $title,
+ $lang->userTimeAndDate( $this->page->getTimestamp(), $user ),
+ array(),
+ array( 'oldid' => $this->page->getLatest() )
+ )
+ );
+ }
// Total number of edits
$pageInfo['header-edits'][] = array(
@@ -377,11 +493,17 @@ class InfoAction extends FormlessAction {
$localizedList = Html::rawElement( 'ul', array(), implode( '', $listItems ) );
$hiddenCategories = $this->page->getHiddenCategories();
- $transcludedTemplates = $title->getTemplateLinksFrom();
- if ( count( $listItems ) > 0
- || count( $hiddenCategories ) > 0
- || count( $transcludedTemplates ) > 0 ) {
+ if (
+ count( $listItems ) > 0 ||
+ count( $hiddenCategories ) > 0 ||
+ $pageCounts['transclusion']['from'] > 0 ||
+ $pageCounts['transclusion']['to'] > 0
+ ) {
+ $options = array( 'LIMIT' => $wgPageInfoTransclusionLimit );
+ $transcludedTemplates = $title->getTemplateLinksFrom( $options );
+ $transcludedTargets = $title->getTemplateLinksTo( $options );
+
// Page properties
$pageInfo['header-properties'] = array();
@@ -403,11 +525,44 @@ class InfoAction extends FormlessAction {
}
// Transcluded templates
- if ( count( $transcludedTemplates ) > 0 ) {
+ if ( $pageCounts['transclusion']['from'] > 0 ) {
+ if ( $pageCounts['transclusion']['from'] > count( $transcludedTemplates ) ) {
+ $more = $this->msg( 'morenotlisted' )->escaped();
+ } else {
+ $more = null;
+ }
+
$pageInfo['header-properties'][] = array(
$this->msg( 'pageinfo-templates' )
- ->numParams( count( $transcludedTemplates ) ),
- Linker::formatTemplates( $transcludedTemplates )
+ ->numParams( $pageCounts['transclusion']['from'] ),
+ Linker::formatTemplates(
+ $transcludedTemplates,
+ false,
+ false,
+ $more )
+ );
+ }
+
+ if ( $pageCounts['transclusion']['to'] > 0 ) {
+ if ( $pageCounts['transclusion']['to'] > count( $transcludedTargets ) ) {
+ $more = Linker::link(
+ $whatLinksHere,
+ $this->msg( 'moredotdotdot' )->escaped(),
+ array(),
+ array( 'hidelinks' => 1, 'hideredirs' => 1 )
+ );
+ } else {
+ $more = null;
+ }
+
+ $pageInfo['header-properties'][] = array(
+ $this->msg( 'pageinfo-transclusions' )
+ ->numParams( $pageCounts['transclusion']['to'] ),
+ Linker::formatTemplates(
+ $transcludedTargets,
+ false,
+ false,
+ $more )
);
}
}
@@ -418,11 +573,10 @@ class InfoAction extends FormlessAction {
/**
* Returns page counts that would be too "expensive" to retrieve by normal means.
*
- * @param $title Title object
- * @param $user User object
+ * @param Title $title Title to get counts for
* @return array
*/
- protected static function pageCounts( $title, $user ) {
+ protected static function pageCounts( Title $title ) {
global $wgRCMaxAge, $wgDisableCounters;
wfProfileIn( __METHOD__ );
@@ -442,19 +596,17 @@ class InfoAction extends FormlessAction {
$result['views'] = $views;
}
- if ( $user->isAllowed( 'unwatchedpages' ) ) {
- // Number of page watchers
- $watchers = (int) $dbr->selectField(
- 'watchlist',
- 'COUNT(*)',
- array(
- 'wl_namespace' => $title->getNamespace(),
- 'wl_title' => $title->getDBkey(),
- ),
- __METHOD__
- );
- $result['watchers'] = $watchers;
- }
+ // Number of page watchers
+ $watchers = (int) $dbr->selectField(
+ 'watchlist',
+ 'COUNT(*)',
+ array(
+ 'wl_namespace' => $title->getNamespace(),
+ 'wl_title' => $title->getDBkey(),
+ ),
+ __METHOD__
+ );
+ $result['watchers'] = $watchers;
// Total number of edits
$edits = (int) $dbr->selectField(
@@ -482,8 +634,8 @@ class InfoAction extends FormlessAction {
'revision',
'COUNT(rev_page)',
array(
- 'rev_page' => $id ,
- "rev_timestamp >= $threshold"
+ 'rev_page' => $id,
+ "rev_timestamp >= " . $dbr->addQuotes( $threshold )
),
__METHOD__
);
@@ -495,7 +647,7 @@ class InfoAction extends FormlessAction {
'COUNT(DISTINCT rev_user_text)',
array(
'rev_page' => $id,
- "rev_timestamp >= $threshold"
+ "rev_timestamp >= " . $dbr->addQuotes( $threshold )
),
__METHOD__
);
@@ -528,12 +680,30 @@ class InfoAction extends FormlessAction {
+ $result['subpages']['nonredirects'];
}
+ // Counts for the number of transclusion links (to/from)
+ $result['transclusion']['to'] = (int) $dbr->selectField(
+ 'templatelinks',
+ 'COUNT(tl_from)',
+ array(
+ 'tl_namespace' => $title->getNamespace(),
+ 'tl_title' => $title->getDBkey()
+ ),
+ __METHOD__
+ );
+
+ $result['transclusion']['from'] = (int) $dbr->selectField(
+ 'templatelinks',
+ 'COUNT(*)',
+ array( 'tl_from' => $title->getArticleID() ),
+ __METHOD__
+ );
+
wfProfileOut( __METHOD__ );
return $result;
}
/**
- * Returns the name that goes in the <h1> page title.
+ * Returns the name that goes in the "<h1>" page title.
*
* @return string
*/
@@ -604,7 +774,7 @@ class InfoAction extends FormlessAction {
}
/**
- * Returns the description that goes below the <h1> tag.
+ * Returns the description that goes below the "<h1>" tag.
*
* @return string
*/
diff --git a/includes/actions/MarkpatrolledAction.php b/includes/actions/MarkpatrolledAction.php
index ae9223f4..ff6cf13a 100644
--- a/includes/actions/MarkpatrolledAction.php
+++ b/includes/actions/MarkpatrolledAction.php
@@ -22,6 +22,11 @@
* @ingroup Actions
*/
+/**
+ * Mark a revision as patrolled on a page
+ *
+ * @ingroup Actions
+ */
class MarkpatrolledAction extends FormlessAction {
public function getName() {
diff --git a/includes/actions/ProtectAction.php b/includes/actions/ProtectAction.php
index f053ede7..ec6648e2 100644
--- a/includes/actions/ProtectAction.php
+++ b/includes/actions/ProtectAction.php
@@ -23,17 +23,24 @@
* @author Timo Tijhof
*/
+/**
+ * Handle page protection
+ *
+ * This is a wrapper that will call Article::protect().
+ *
+ * @ingroup Actions
+ */
class ProtectAction extends FormlessAction {
public function getName() {
return 'protect';
}
- public function onView(){
+ public function onView() {
return null;
}
- public function show(){
+ public function show() {
$this->page->protect();
@@ -41,13 +48,20 @@ class ProtectAction extends FormlessAction {
}
+/**
+ * Handle page unprotection
+ *
+ * This is a wrapper that will call Article::unprotect().
+ *
+ * @ingroup Actions
+ */
class UnprotectAction extends ProtectAction {
public function getName() {
return 'unprotect';
}
- public function show(){
+ public function show() {
$this->page->unprotect();
diff --git a/includes/actions/PurgeAction.php b/includes/actions/PurgeAction.php
index cd58889d..00bb961d 100644
--- a/includes/actions/PurgeAction.php
+++ b/includes/actions/PurgeAction.php
@@ -1,8 +1,6 @@
<?php
/**
- * Formats credits for articles
- *
- * Copyright 2004, Evan Prodromou <evan@wikitravel.org>.
+ * User-requested page cache purging.
*
* 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
@@ -20,9 +18,16 @@
*
* @file
* @ingroup Actions
- * @author <evan@wikitravel.org>
*/
+/**
+ * User-requested page cache purging.
+ *
+ * For users with 'purge', this will directly trigger the cache purging and
+ * for users without that right, it will show a confirmation form.
+ *
+ * @ingroup Actions
+ */
class PurgeAction extends FormAction {
private $redirectParams;
@@ -62,7 +67,7 @@ class PurgeAction extends FormAction {
$this->checkCanExecute( $this->getUser() );
if ( $this->getUser()->isAllowed( 'purge' ) ) {
- $this->redirectParams = wfArrayToCGI( array_diff_key(
+ $this->redirectParams = wfArrayToCgi( array_diff_key(
$this->getRequest()->getQueryValues(),
array( 'title' => null, 'action' => null )
) );
diff --git a/includes/actions/RawAction.php b/includes/actions/RawAction.php
index 174ca3f8..d1d457c0 100644
--- a/includes/actions/RawAction.php
+++ b/includes/actions/RawAction.php
@@ -29,6 +29,8 @@
/**
* A simple method to retrieve the plain source of an article,
* using "action=raw" in the GET request string.
+ *
+ * @ingroup Actions
*/
class RawAction extends FormlessAction {
private $mGen;
@@ -46,7 +48,7 @@ class RawAction extends FormlessAction {
}
function onView() {
- global $wgGroupPermissions, $wgSquidMaxage, $wgForcedRawSMaxage, $wgJsMimeType;
+ global $wgSquidMaxage, $wgForcedRawSMaxage, $wgJsMimeType;
$this->getOutput()->disable();
$request = $this->getRequest();
@@ -91,7 +93,7 @@ class RawAction extends FormlessAction {
$response->header( 'Content-type: ' . $contentType . '; charset=UTF-8' );
# Output may contain user-specific data;
# vary generated content for open sessions on private wikis
- $privateCache = !$wgGroupPermissions['*']['read'] && ( $smaxage == 0 || session_id() != '' );
+ $privateCache = !User::groupHasPermission( '*', 'read' ) && ( $smaxage == 0 || session_id() != '' );
# allow the client to cache this for 24 hours
$mode = $privateCache ? 'private' : 'public';
$response->header( 'Cache-Control: ' . $mode . ', s-maxage=' . $smaxage . ', max-age=' . $maxage );
@@ -148,10 +150,29 @@ class RawAction extends FormlessAction {
$request->response()->header( "Last-modified: $lastmod" );
// Public-only due to cache headers
- $text = $rev->getText();
- $section = $request->getIntOrNull( 'section' );
- if ( $section !== null ) {
- $text = $wgParser->getSection( $text, $section );
+ $content = $rev->getContent();
+
+ if ( $content === null ) {
+ // revision not found (or suppressed)
+ $text = false;
+ } elseif ( !$content instanceof TextContent ) {
+ // non-text content
+ wfHttpError( 415, "Unsupported Media Type", "The requested page uses the content model `"
+ . $content->getModel() . "` which is not supported via this interface." );
+ die();
+ } else {
+ // want a section?
+ $section = $request->getIntOrNull( 'section' );
+ if ( $section !== null ) {
+ $content = $content->getSection( $section );
+ }
+
+ if ( $content === null || $content === false ) {
+ // section not found (or section not supported, e.g. for JS and CSS)
+ $text = false;
+ } else {
+ $text = $content->getNativeData();
+ }
}
}
}
@@ -185,7 +206,7 @@ class RawAction extends FormlessAction {
$oldid = $this->page->getLatest();
}
$prev = $this->getTitle()->getPreviousRevisionId( $oldid );
- $oldid = $prev ? $prev : -1 ;
+ $oldid = $prev ? $prev : -1;
break;
case 'cur':
$oldid = 0;
diff --git a/includes/actions/RenderAction.php b/includes/actions/RenderAction.php
index 80af79cc..3d244fb3 100644
--- a/includes/actions/RenderAction.php
+++ b/includes/actions/RenderAction.php
@@ -23,17 +23,24 @@
* @author Timo Tijhof
*/
+/**
+ * Handle action=render
+ *
+ * This is a wrapper that will call Article::render().
+ *
+ * @ingroup Actions
+ */
class RenderAction extends FormlessAction {
public function getName() {
return 'render';
}
- public function onView(){
+ public function onView() {
return null;
}
- public function show(){
+ public function show() {
$this->page->render();
diff --git a/includes/actions/RevertAction.php b/includes/actions/RevertAction.php
index 77434384..a5fc4e17 100644
--- a/includes/actions/RevertAction.php
+++ b/includes/actions/RevertAction.php
@@ -115,7 +115,7 @@ class RevertFileAction extends FormAction {
$source = $this->page->getFile()->getArchiveVirtualUrl( $this->getRequest()->getText( 'oldimage' ) );
$comment = $data['comment'];
// TODO: Preserve file properties from database instead of reloading from file
- return $this->page->getFile()->upload( $source, $comment, $comment );
+ return $this->page->getFile()->upload( $source, $comment, $comment, 0, false, false, $this->getUser() );
}
public function onSuccess() {
@@ -124,7 +124,7 @@ class RevertFileAction extends FormAction {
$lang = $this->getLanguage();
$userDate = $lang->userDate( $timestamp, $user );
$userTime = $lang->userTime( $timestamp, $user );
-
+
$this->getOutput()->addWikiMsg( 'filerevert-success', $this->getTitle()->getText(),
$userDate, $userTime,
wfExpandUrl( $this->page->getFile()->getArchiveUrl( $this->getRequest()->getText( 'oldimage' ) ),
@@ -136,7 +136,7 @@ class RevertFileAction extends FormAction {
protected function getPageTitle() {
return $this->msg( 'filerevert', $this->getTitle()->getText() );
}
-
+
protected function getDescription() {
$this->getOutput()->addBacklinkSubtitle( $this->getTitle() );
return '';
diff --git a/includes/actions/RevisiondeleteAction.php b/includes/actions/RevisiondeleteAction.php
index 14da2fcf..2949fa95 100644
--- a/includes/actions/RevisiondeleteAction.php
+++ b/includes/actions/RevisiondeleteAction.php
@@ -23,6 +23,11 @@
* @author Alexandre Emsenhuber
*/
+/**
+ * An action that just pass the request to Special:RevisionDelete
+ *
+ * @ingroup Actions
+ */
class RevisiondeleteAction extends FormlessAction {
public function getName() {
diff --git a/includes/actions/RollbackAction.php b/includes/actions/RollbackAction.php
index 0d9a9027..81bad9da 100644
--- a/includes/actions/RollbackAction.php
+++ b/includes/actions/RollbackAction.php
@@ -71,45 +71,32 @@ class RollbackAction extends FormlessAction {
return;
}
- # Display permissions errors before read-only message -- there's no
- # point in misleading the user into thinking the inability to rollback
- # is only temporary.
- if ( !empty( $result ) && $result !== array( array( 'readonlytext' ) ) ) {
- # array_diff is completely broken for arrays of arrays, sigh.
- # Remove any 'readonlytext' error manually.
- $out = array();
- foreach ( $result as $error ) {
- if ( $error != array( 'readonlytext' ) ) {
- $out [] = $error;
- }
- }
- throw new PermissionsError( 'rollback', $out );
- }
+ #NOTE: Permission errors already handled by Action::checkExecute.
if ( $result == array( array( 'readonlytext' ) ) ) {
throw new ReadOnlyError;
}
+ #XXX: Would be nice if ErrorPageError could take multiple errors, and/or a status object.
+ # Right now, we only show the first error
+ foreach ( $result as $error ) {
+ throw new ErrorPageError( 'rollbackfailed', $error[0], array_slice( $error, 1 ) );
+ }
+
$current = $details['current'];
$target = $details['target'];
$newId = $details['newid'];
$this->getOutput()->setPageTitle( $this->msg( 'actioncomplete' ) );
$this->getOutput()->setRobotPolicy( 'noindex,nofollow' );
- if ( $current->getUserText() === '' ) {
- $old = $this->msg( 'rev-deleted-user' )->escaped();
- } else {
- $old = Linker::userLink( $current->getUser(), $current->getUserText() )
- . Linker::userToolLinks( $current->getUser(), $current->getUserText() );
- }
-
- $new = Linker::userLink( $target->getUser(), $target->getUserText() )
- . Linker::userToolLinks( $target->getUser(), $target->getUserText() );
+ $old = Linker::revUserTools( $current );
+ $new = Linker::revUserTools( $target );
$this->getOutput()->addHTML( $this->msg( 'rollback-success' )->rawParams( $old, $new )->parseAsBlock() );
$this->getOutput()->returnToMain( false, $this->getTitle() );
if ( !$request->getBool( 'hidediff', false ) && !$this->getUser()->getBoolOption( 'norollbackdiff', false ) ) {
- $de = new DifferenceEngine( $this->getContext(), $current->getId(), $newId, false, true );
+ $contentHandler = $current->getContentHandler();
+ $de = $contentHandler->createDifferenceEngine( $this->getContext(), $current->getId(), $newId, false, true );
$de->showDiff( '', '' );
}
}
diff --git a/includes/actions/ViewAction.php b/includes/actions/ViewAction.php
index d57585ee..e227197d 100644
--- a/includes/actions/ViewAction.php
+++ b/includes/actions/ViewAction.php
@@ -23,17 +23,24 @@
* @author Timo Tijhof
*/
+/**
+ * An action that views article content
+ *
+ * This is a wrapper that will call Article::render().
+ *
+ * @ingroup Actions
+ */
class ViewAction extends FormlessAction {
public function getName() {
return 'view';
}
- public function onView(){
+ public function onView() {
return null;
}
- public function show(){
+ public function show() {
$this->page->view();
}
diff --git a/includes/actions/WatchAction.php b/includes/actions/WatchAction.php
index e2636452..ae5f76c6 100644
--- a/includes/actions/WatchAction.php
+++ b/includes/actions/WatchAction.php
@@ -20,6 +20,11 @@
* @ingroup Actions
*/
+/**
+ * Page addition to a user's watchlist
+ *
+ * @ingroup Actions
+ */
class WatchAction extends FormAction {
public function getName() {
@@ -148,6 +153,11 @@ class WatchAction extends FormAction {
}
}
+/**
+ * Page removal from a user's watchlist
+ *
+ * @ingroup Actions
+ */
class UnwatchAction extends WatchAction {
public function getName() {
diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php
index 875a3814..9351a8d8 100644
--- a/includes/api/ApiBase.php
+++ b/includes/api/ApiBase.php
@@ -66,14 +66,22 @@ abstract class ApiBase extends ContextSource {
const LIMIT_SML1 = 50; // Slow query, std user limit
const LIMIT_SML2 = 500; // Slow query, bot/sysop limit
+ /**
+ * getAllowedParams() flag: When set, the result could take longer to generate,
+ * but should be more thorough. E.g. get the list of generators for ApiSandBox extension
+ * @since 1.21
+ */
+ const GET_VALUES_FOR_HELP = 1;
+
private $mMainModule, $mModuleName, $mModulePrefix;
+ private $mSlaveDB = null;
private $mParamCache = array();
/**
* Constructor
* @param $mainModule ApiMain object
- * @param $moduleName string Name of this module
- * @param $modulePrefix string Prefix to use for parameter names
+ * @param string $moduleName Name of this module
+ * @param string $modulePrefix Prefix to use for parameter names
*/
public function __construct( $mainModule, $moduleName, $modulePrefix = '' ) {
$this->mMainModule = $mainModule;
@@ -105,15 +113,19 @@ abstract class ApiBase extends ContextSource {
* The result data should be stored in the ApiResult object available
* through getResult().
*/
- public abstract function execute();
+ abstract public function execute();
/**
* Returns a string that identifies the version of the extending class.
* Typically includes the class name, the svn revision, timestamp, and
* last author. Usually done with SVN's Id keyword
* @return string
+ * @deprecated since 1.21, version string is no longer supported
*/
- public abstract function getVersion();
+ public function getVersion() {
+ wfDeprecated( __METHOD__, '1.21' );
+ return '';
+ }
/**
* Get the name of the module being executed by this instance
@@ -124,6 +136,15 @@ abstract class ApiBase extends ContextSource {
}
/**
+ * Get the module manager, or null if this module has no sub-modules
+ * @since 1.21
+ * @return ApiModuleManager
+ */
+ public function getModuleManager() {
+ return null;
+ }
+
+ /**
* Get parameter prefix (usually two letters or an empty string).
* @return string
*/
@@ -168,7 +189,7 @@ abstract class ApiBase extends ContextSource {
* @return ApiResult
*/
public function getResult() {
- // Main module has getResult() method overriden
+ // Main module has getResult() method overridden
// Safety - avoid infinite loop:
if ( $this->isMain() ) {
ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
@@ -203,26 +224,32 @@ abstract class ApiBase extends ContextSource {
* section to notice any changes in API. Multiple calls to this
* function will result in the warning messages being separated by
* newlines
- * @param $warning string Warning message
+ * @param string $warning Warning message
*/
public function setWarning( $warning ) {
$result = $this->getResult();
$data = $result->getData();
- if ( isset( $data['warnings'][$this->getModuleName()] ) ) {
+ $moduleName = $this->getModuleName();
+ if ( isset( $data['warnings'][$moduleName] ) ) {
// Don't add duplicate warnings
- $warn_regex = preg_quote( $warning, '/' );
- if ( preg_match( "/{$warn_regex}(\\n|$)/", $data['warnings'][$this->getModuleName()]['*'] ) ) {
- return;
+ $oldWarning = $data['warnings'][$moduleName]['*'];
+ $warnPos = strpos( $oldWarning, $warning );
+ // If $warning was found in $oldWarning, check if it starts at 0 or after "\n"
+ if ( $warnPos !== false && ( $warnPos === 0 || $oldWarning[$warnPos - 1] === "\n" ) ) {
+ // Check if $warning is followed by "\n" or the end of the $oldWarning
+ $warnPos += strlen( $warning );
+ if ( strlen( $oldWarning ) <= $warnPos || $oldWarning[$warnPos] === "\n" ) {
+ return;
+ }
}
- $oldwarning = $data['warnings'][$this->getModuleName()]['*'];
// If there is a warning already, append it to the existing one
- $warning = "$oldwarning\n$warning";
- $result->unsetValue( 'warnings', $this->getModuleName() );
+ $warning = "$oldWarning\n$warning";
}
$msg = array();
ApiResult::setContent( $msg, $warning );
$result->disableSizeCheck();
- $result->addValue( 'warnings', $this->getModuleName(), $msg );
+ $result->addValue( 'warnings', $moduleName,
+ $msg, ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP );
$result->enableSizeCheck();
}
@@ -254,6 +281,8 @@ abstract class ApiBase extends ContextSource {
}
$msg = $lnPrfx . implode( $lnPrfx, $msg ) . "\n";
+ $msg .= $this->makeHelpArrayToString( $lnPrfx, false, $this->getHelpUrls() );
+
if ( $this->isReadMode() ) {
$msg .= "\nThis module requires read rights";
}
@@ -297,25 +326,6 @@ abstract class ApiBase extends ContextSource {
}
}
}
-
- $msg .= $this->makeHelpArrayToString( $lnPrfx, "Help page", $this->getHelpUrls() );
-
- if ( $this->getMain()->getShowVersions() ) {
- $versions = $this->getVersion();
- $pattern = '/(\$.*) ([0-9a-z_]+\.php) (.*\$)/i';
- $callback = array( $this, 'makeHelpMsg_callback' );
-
- if ( is_array( $versions ) ) {
- foreach ( $versions as &$v ) {
- $v = preg_replace_callback( $pattern, $callback, $v );
- }
- $versions = implode( "\n ", $versions );
- } else {
- $versions = preg_replace_callback( $pattern, $callback, $versions );
- }
-
- $msg .= "Version:\n $versions\n";
- }
}
return $msg;
@@ -330,8 +340,8 @@ abstract class ApiBase extends ContextSource {
}
/**
- * @param $prefix string Text to split output items
- * @param $title string What is being output
+ * @param string $prefix Text to split output items
+ * @param string $title What is being output
* @param $input string|array
* @return string
*/
@@ -340,13 +350,15 @@ abstract class ApiBase extends ContextSource {
return '';
}
if ( !is_array( $input ) ) {
- $input = array(
- $input
- );
+ $input = array( $input );
}
if ( count( $input ) > 0 ) {
- $msg = $title . ( count( $input ) > 1 ? 's' : '' ) . ":\n ";
+ if ( $title ) {
+ $msg = $title . ( count( $input ) > 1 ? 's' : '' ) . ":\n ";
+ } else {
+ $msg = ' ';
+ }
$msg .= implode( $prefix, $input ) . "\n";
return $msg;
}
@@ -359,7 +371,7 @@ abstract class ApiBase extends ContextSource {
* @return string or false
*/
public function makeHelpMsgParameters() {
- $params = $this->getFinalParams();
+ $params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
if ( $params ) {
$paramsDescription = $this->getFinalParamDescription();
@@ -416,7 +428,7 @@ abstract class ApiBase extends ContextSource {
if ( $t === '' ) {
$nothingPrompt = 'Can be empty, or ';
} else {
- $choices[] = $t;
+ $choices[] = $t;
}
}
$desc .= $paramPrefix . $nothingPrompt . $prompt;
@@ -455,6 +467,9 @@ abstract class ApiBase extends ContextSource {
$desc .= $paramPrefix . $intRangeStr;
}
break;
+ case 'upload':
+ $desc .= $paramPrefix . "Must be posted as a file upload using multipart/form-data";
+ break;
}
}
@@ -487,44 +502,6 @@ abstract class ApiBase extends ContextSource {
}
/**
- * Callback for preg_replace_callback() call in makeHelpMsg().
- * Replaces a source file name with a link to ViewVC
- *
- * @param $matches array
- * @return string
- */
- public function makeHelpMsg_callback( $matches ) {
- global $wgAutoloadClasses, $wgAutoloadLocalClasses;
-
- $file = '';
- if ( isset( $wgAutoloadLocalClasses[get_class( $this )] ) ) {
- $file = $wgAutoloadLocalClasses[get_class( $this )];
- } elseif ( isset( $wgAutoloadClasses[get_class( $this )] ) ) {
- $file = $wgAutoloadClasses[get_class( $this )];
- }
-
- // Do some guesswork here
- $path = strstr( $file, 'includes/api/' );
- if ( $path === false ) {
- $path = strstr( $file, 'extensions/' );
- } else {
- $path = 'phase3/' . $path;
- }
-
- // Get the filename from $matches[2] instead of $file
- // If they're not the same file, they're assumed to be in the
- // same directory
- // This is necessary to make stuff like ApiMain::getVersion()
- // returning the version string for ApiBase work
- if ( $path ) {
- return "{$matches[0]}\n https://svn.wikimedia.org/" .
- "viewvc/mediawiki/trunk/" . dirname( $path ) .
- "/{$matches[2]}";
- }
- return $matches[0];
- }
-
- /**
* Returns the description string for this module
* @return mixed string or array of strings
*/
@@ -545,15 +522,22 @@ abstract class ApiBase extends ContextSource {
* value) or (parameter name) => (array with PARAM_* constants as keys)
* Don't call this function directly: use getFinalParams() to allow
* hooks to modify parameters as needed.
+ *
+ * Some derived classes may choose to handle an integer $flags parameter
+ * in the overriding methods. Callers of this method can pass zero or
+ * more OR-ed flags like GET_VALUES_FOR_HELP.
+ *
* @return array|bool
*/
- protected function getAllowedParams() {
+ protected function getAllowedParams( /* $flags = 0 */ ) {
+ // int $flags is not declared because it causes "Strict standards"
+ // warning. Most derived classes do not implement it.
return false;
}
/**
* Returns an array of parameter descriptions.
- * Don't call this functon directly: use getFinalParamDescription() to
+ * Don't call this function directly: use getFinalParamDescription() to
* allow hooks to modify descriptions as needed.
* @return array|bool False on no parameter descriptions
*/
@@ -565,11 +549,13 @@ abstract class ApiBase extends ContextSource {
* Get final list of parameters, after hooks have had a chance to
* tweak it as needed.
*
+ * @param int $flags Zero or more flags like GET_VALUES_FOR_HELP
* @return array|Bool False on no parameters
+ * @since 1.21 $flags param added
*/
- public function getFinalParams() {
- $params = $this->getAllowedParams();
- wfRunHooks( 'APIGetAllowedParams', array( &$this, &$params ) );
+ public function getFinalParams( $flags = 0 ) {
+ $params = $this->getAllowedParams( $flags );
+ wfRunHooks( 'APIGetAllowedParams', array( &$this, &$params, $flags ) );
return $params;
}
@@ -596,7 +582,7 @@ abstract class ApiBase extends ContextSource {
* The array can also contain a boolean under the key PROP_LIST,
* indicating whether the result is a list.
*
- * Don't call this functon directly: use getFinalResultProperties() to
+ * Don't call this function directly: use getFinalResultProperties() to
* allow hooks to modify descriptions as needed.
*
* @return array|bool False on no properties
@@ -645,7 +631,7 @@ abstract class ApiBase extends ContextSource {
/**
* This method mangles parameter name based on the prefix supplied to the constructor.
* Override this method to change parameter name during runtime
- * @param $paramName string Parameter name
+ * @param string $paramName Parameter name
* @return string Prefixed parameter name
*/
public function encodeParamName( $paramName ) {
@@ -680,8 +666,8 @@ abstract class ApiBase extends ContextSource {
/**
* Get a value for the given parameter
- * @param $paramName string Parameter name
- * @param $parseLimit bool see extractRequestParams()
+ * @param string $paramName Parameter name
+ * @param bool $parseLimit see extractRequestParams()
* @return mixed Parameter value
*/
protected function getParameter( $paramName, $parseLimit = true ) {
@@ -692,7 +678,7 @@ abstract class ApiBase extends ContextSource {
/**
* Die if none or more than one of a certain set of parameters is set and not false.
- * @param $params array of parameter names
+ * @param array $params of parameter names
*/
public function requireOnlyOneParameter( $params ) {
$required = func_get_args();
@@ -703,7 +689,7 @@ abstract class ApiBase extends ContextSource {
array( $this, "parameterNotEmpty" ) ) ), $required );
if ( count( $intersection ) > 1 ) {
- $this->dieUsage( "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together', "{$p}invalidparammix" );
+ $this->dieUsage( "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together', "{$p}invalidparammix" );
} elseif ( count( $intersection ) == 0 ) {
$this->dieUsage( "One of the parameters {$p}" . implode( ", {$p}", $required ) . ' is required', "{$p}missingparam" );
}
@@ -760,7 +746,7 @@ abstract class ApiBase extends ContextSource {
/**
* @param $params array
- * @param $load bool|string Whether load the object's state from the database:
+ * @param bool|string $load Whether load the object's state from the database:
* - false: don't load (if the pageid is given, it will still be loaded)
* - 'fromdb': load from a slave database
* - 'fromdbmaster': load from the master database
@@ -772,9 +758,12 @@ abstract class ApiBase extends ContextSource {
$pageObj = null;
if ( isset( $params['title'] ) ) {
$titleObj = Title::newFromText( $params['title'] );
- if ( !$titleObj ) {
+ if ( !$titleObj || $titleObj->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
+ if ( !$titleObj->canExist() ) {
+ $this->dieUsage( "Namespace doesn't allow actual pages", 'pagecannotexist' );
+ }
$pageObj = WikiPage::factory( $titleObj );
if ( $load !== false ) {
$pageObj->loadPageData( $load );
@@ -806,7 +795,7 @@ abstract class ApiBase extends ContextSource {
}
/**
- * Callback function used in requireOnlyOneParameter to check whether reequired parameters are set
+ * Callback function used in requireOnlyOneParameter to check whether required parameters are set
*
* @param $x object Parameter to check is not null/false
* @return bool
@@ -827,9 +816,9 @@ abstract class ApiBase extends ContextSource {
/**
* Return true if we're to watch the page, false if not, null if no change.
- * @param $watchlist String Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
+ * @param string $watchlist Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
* @param $titleObj Title the page under consideration
- * @param $userOption String The user option to consider when $watchlist=preferences.
+ * @param string $userOption The user option to consider when $watchlist=preferences.
* If not set will magically default to either watchdefault or watchcreations
* @return bool
*/
@@ -849,13 +838,13 @@ abstract class ApiBase extends ContextSource {
if ( $userWatching ) {
return true;
}
- # If no user option was passed, use watchdefault or watchcreation
+ # If no user option was passed, use watchdefault or watchcreations
if ( is_null( $userOption ) ) {
$userOption = $titleObj->exists()
? 'watchdefault' : 'watchcreations';
}
# Watch the article based on the user preference
- return (bool)$this->getUser()->getOption( $userOption );
+ return $this->getUser()->getBoolOption( $userOption );
case 'nochange':
return $userWatching;
@@ -867,9 +856,9 @@ abstract class ApiBase extends ContextSource {
/**
* Set a watch (or unwatch) based the based on a watchlist parameter.
- * @param $watch String Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
+ * @param string $watch Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
* @param $titleObj Title the article's title to change
- * @param $userOption String The user option to consider when $watch=preferences
+ * @param string $userOption The user option to consider when $watch=preferences
*/
protected function setWatch( $watch, $titleObj, $userOption = null ) {
$value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
@@ -888,8 +877,8 @@ abstract class ApiBase extends ContextSource {
/**
* Using the settings determine the value for the given parameter
*
- * @param $paramName String: parameter name
- * @param $paramSettings array|mixed default value or an array of settings
+ * @param string $paramName parameter name
+ * @param array|mixed $paramSettings default value or an array of settings
* using PARAM_* constants.
* @param $parseLimit Boolean: parse limit?
* @return mixed Parameter value
@@ -929,9 +918,32 @@ abstract class ApiBase extends ContextSource {
ApiBase::dieDebug( __METHOD__, "Boolean param $encParamName's default is set to '$default'. Boolean parameters must default to false." );
}
- $value = $this->getRequest()->getCheck( $encParamName );
+ $value = $this->getMain()->getCheck( $encParamName );
+ } elseif ( $type == 'upload' ) {
+ if ( isset( $default ) ) {
+ // Having a default value is not allowed
+ ApiBase::dieDebug( __METHOD__, "File upload param $encParamName's default is set to '$default'. File upload parameters may not have a default." );
+ }
+ if ( $multi ) {
+ ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
+ }
+ $value = $this->getMain()->getUpload( $encParamName );
+ if ( !$value->exists() ) {
+ // This will get the value without trying to normalize it
+ // (because trying to normalize a large binary file
+ // accidentally uploaded as a field fails spectacularly)
+ $value = $this->getMain()->getRequest()->unsetVal( $encParamName );
+ if ( $value !== null ) {
+ $this->dieUsage(
+ "File upload param $encParamName is not a file upload; " .
+ "be sure to use multipart/form-data for your POST and include " .
+ "a filename in the Content-Disposition header.",
+ "badupload_{$encParamName}"
+ );
+ }
+ }
} else {
- $value = $this->getRequest()->getVal( $encParamName, $default );
+ $value = $this->getMain()->getVal( $encParamName, $default );
if ( isset( $value ) && $type == 'namespace' ) {
$type = MWNamespace::getValidNamespaces();
@@ -953,7 +965,6 @@ abstract class ApiBase extends ContextSource {
if ( $required && $value === '' ) {
$this->dieUsageMsg( array( 'missingparam', $paramName ) );
}
-
break;
case 'integer': // Force everything using intval() and optionally validate limits
$min = isset ( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : null;
@@ -1010,29 +1021,23 @@ abstract class ApiBase extends ContextSource {
}
break;
case 'user':
- if ( !is_array( $value ) ) {
- $value = array( $value );
- }
-
- foreach ( $value as $key => $val ) {
- $title = Title::makeTitleSafe( NS_USER, $val );
- if ( is_null( $title ) ) {
- $this->dieUsage( "Invalid value for user parameter $encParamName", "baduser_{$encParamName}" );
+ if ( is_array( $value ) ) {
+ foreach ( $value as $key => $val ) {
+ $value[$key] = $this->validateUser( $val, $encParamName );
}
- $value[$key] = $title->getText();
- }
-
- if ( !$multi ) {
- $value = $value[0];
+ } else {
+ $value = $this->validateUser( $value, $encParamName );
}
break;
+ case 'upload': // nothing to do
+ break;
default:
ApiBase::dieDebug( __METHOD__, "Param $encParamName's type is unknown - $type" );
}
}
// Throw out duplicates if requested
- if ( is_array( $value ) && !$dupes ) {
+ if ( !$dupes && is_array( $value ) ) {
$value = array_unique( $value );
}
@@ -1051,10 +1056,10 @@ abstract class ApiBase extends ContextSource {
* Return an array of values that were given in a 'a|b|c' notation,
* after it optionally validates them against the list allowed values.
*
- * @param $valueName string The name of the parameter (for error
+ * @param string $valueName The name of the parameter (for error
* reporting)
* @param $value mixed The value being parsed
- * @param $allowMultiple bool Can $value contain more than one value
+ * @param bool $allowMultiple Can $value contain more than one value
* separated by '|'?
* @param $allowedValues mixed An array of values to check against. If
* null, all values are accepted.
@@ -1106,11 +1111,11 @@ abstract class ApiBase extends ContextSource {
/**
* Validate the value against the minimum and user/bot maximum limits.
* Prints usage info on failure.
- * @param $paramName string Parameter name
- * @param $value int Parameter value
- * @param $min int|null Minimum value
- * @param $max int|null Maximum value for users
- * @param $botMax int Maximum value for sysops/bots
+ * @param string $paramName Parameter name
+ * @param int $value Parameter value
+ * @param int|null $min Minimum value
+ * @param int|null $max Maximum value for users
+ * @param int $botMax Maximum value for sysops/bots
* @param $enforceLimits Boolean Whether to enforce (die) if value is outside limits
*/
function validateLimit( $paramName, &$value, $min, $max, $botMax = null, $enforceLimits = false ) {
@@ -1144,16 +1149,31 @@ abstract class ApiBase extends ContextSource {
}
/**
- * @param $value string
- * @param $paramName string
- * @return string
+ * Validate and normalize of parameters of type 'timestamp'
+ * @param string $value Parameter value
+ * @param string $encParamName Parameter name
+ * @return string Validated and normalized parameter
+ */
+ function validateTimestamp( $value, $encParamName ) {
+ $unixTimestamp = wfTimestamp( TS_UNIX, $value );
+ if ( $unixTimestamp === false ) {
+ $this->dieUsage( "Invalid value '$value' for timestamp parameter $encParamName", "badtimestamp_{$encParamName}" );
+ }
+ return wfTimestamp( TS_MW, $unixTimestamp );
+ }
+
+ /**
+ * Validate and normalize of parameters of type 'user'
+ * @param string $value Parameter value
+ * @param string $encParamName Parameter value
+ * @return string Validated and normalized parameter
*/
- function validateTimestamp( $value, $paramName ) {
- $value = wfTimestamp( TS_UNIX, $value );
- if ( $value === 0 ) {
- $this->dieUsage( "Invalid value '$value' for timestamp parameter $paramName", "badtimestamp_{$paramName}" );
+ private function validateUser( $value, $encParamName ) {
+ $title = Title::makeTitleSafe( NS_USER, $value );
+ if ( $title === null ) {
+ $this->dieUsage( "Invalid value '$value' for user parameter $encParamName", "baduser_{$encParamName}" );
}
- return wfTimestamp( TS_MW, $value );
+ return $title->getText();
}
/**
@@ -1172,8 +1192,8 @@ abstract class ApiBase extends ContextSource {
/**
* Truncate an array to a certain length.
- * @param $arr array Array to truncate
- * @param $limit int Maximum length
+ * @param array $arr Array to truncate
+ * @param int $limit Maximum length
* @return bool True if the array was truncated, false otherwise
*/
public static function truncateArray( &$arr, $limit ) {
@@ -1189,12 +1209,12 @@ abstract class ApiBase extends ContextSource {
* Throw a UsageException, which will (if uncaught) call the main module's
* error handler and die with an error message.
*
- * @param $description string One-line human-readable description of the
+ * @param string $description One-line human-readable description of the
* error condition, e.g., "The API requires a valid action parameter"
- * @param $errorCode string Brief, arbitrary, stable string to allow easy
+ * @param string $errorCode Brief, arbitrary, stable string to allow easy
* automated identification of the error, e.g., 'unknown_action'
- * @param $httpRespCode int HTTP response code
- * @param $extradata array Data to add to the "<error>" element; array in ApiResult format
+ * @param int $httpRespCode HTTP response code
+ * @param array $extradata Data to add to the "<error>" element; array in ApiResult format
* @throws UsageException
*/
public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
@@ -1226,7 +1246,7 @@ abstract class ApiBase extends ContextSource {
'nocreatetext' => array( 'code' => 'cantcreate-anon', 'info' => "Anonymous users can't create new pages" ),
'movenologintext' => array( 'code' => 'cantmove-anon', 'info' => "Anonymous users can't move pages" ),
'movenotallowed' => array( 'code' => 'cantmove', 'info' => "You don't have permission to move pages" ),
- 'confirmedittext' => array( 'code' => 'confirmemail', 'info' => "You must confirm your e-mail address before you can edit" ),
+ 'confirmedittext' => array( 'code' => 'confirmemail', 'info' => "You must confirm your email address before you can edit" ),
'blockedtext' => array( 'code' => 'blocked', 'info' => "You have been blocked from editing" ),
'autoblockedtext' => array( 'code' => 'autoblocked', 'info' => "Your IP address has been blocked automatically, because it was used by a blocked user" ),
@@ -1254,15 +1274,15 @@ abstract class ApiBase extends ContextSource {
'badipaddress' => array( 'code' => 'invalidip', 'info' => "Invalid IP address specified" ),
'ipb_expiry_invalid' => array( 'code' => 'invalidexpiry', 'info' => "Invalid expiry time" ),
'ipb_already_blocked' => array( 'code' => 'alreadyblocked', 'info' => "The user you tried to block was already blocked" ),
- 'ipb_blocked_as_range' => array( 'code' => 'blockedasrange', 'info' => "IP address \"\$1\" was blocked as part of range \"\$2\". You can't unblock the IP invidually, but you can unblock the range as a whole." ),
+ 'ipb_blocked_as_range' => array( 'code' => 'blockedasrange', 'info' => "IP address \"\$1\" was blocked as part of range \"\$2\". You can't unblock the IP individually, but you can unblock the range as a whole." ),
'ipb_cant_unblock' => array( 'code' => 'cantunblock', 'info' => "The block you specified was not found. It may have been unblocked already" ),
- 'mailnologin' => array( 'code' => 'cantsend', 'info' => "You are not logged in, you do not have a confirmed e-mail address, or you are not allowed to send e-mail to other users, so you cannot send e-mail" ),
+ 'mailnologin' => array( 'code' => 'cantsend', 'info' => "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" ),
'ipbblocked' => array( 'code' => 'ipbblocked', 'info' => 'You cannot block or unblock users while you are yourself blocked' ),
'ipbnounblockself' => array( 'code' => 'ipbnounblockself', 'info' => 'You are not allowed to unblock yourself' ),
'usermaildisabled' => array( 'code' => 'usermaildisabled', 'info' => "User email has been disabled" ),
- 'blockedemailuser' => array( 'code' => 'blockedfrommail', 'info' => "You have been blocked from sending e-mail" ),
+ 'blockedemailuser' => array( 'code' => 'blockedfrommail', 'info' => "You have been blocked from sending email" ),
'notarget' => array( 'code' => 'notarget', 'info' => "You have not specified a valid target for this action" ),
- 'noemail' => array( 'code' => 'noemail', 'info' => "The user has not specified a valid e-mail address, or has chosen not to receive e-mail from other users" ),
+ 'noemail' => array( 'code' => 'noemail', 'info' => "The user has not specified a valid email address, or has chosen not to receive email from other users" ),
'rcpatroldisabled' => array( 'code' => 'patroldisabled', 'info' => "Patrolling is disabled on this wiki" ),
'markedaspatrollederror-noautopatrol' => array( 'code' => 'noautopatrol', 'info' => "You don't have permission to patrol your own changes" ),
'delete-toobig' => array( 'code' => 'bigdelete', 'info' => "You can't delete this page because it has more than \$1 revisions" ),
@@ -1291,7 +1311,7 @@ abstract class ApiBase extends ContextSource {
'missingtitle-createonly' => array( 'code' => 'missingtitle-createonly', 'info' => "Missing titles can only be protected with 'create'" ),
'cantblock' => array( 'code' => 'cantblock', 'info' => "You don't have permission to block users" ),
'canthide' => array( 'code' => 'canthide', 'info' => "You don't have permission to hide user names from the block log" ),
- 'cantblock-email' => array( 'code' => 'cantblock-email', 'info' => "You don't have permission to block users from sending e-mail through the wiki" ),
+ 'cantblock-email' => array( 'code' => 'cantblock-email', 'info' => "You don't have permission to block users from sending email through the wiki" ),
'unblock-notarget' => array( 'code' => 'notarget', 'info' => "Either the id or the user parameter must be set" ),
'unblock-idanduser' => array( 'code' => 'idanduser', 'info' => "The id and user parameters can't be used together" ),
'cantunblock' => array( 'code' => 'permissiondenied', 'info' => "You don't have permission to unblock users" ),
@@ -1349,8 +1369,8 @@ abstract class ApiBase extends ContextSource {
// uploadMsgs
'invalid-file-key' => array( 'code' => 'invalid-file-key', 'info' => 'Not a valid file key' ),
'nouploadmodule' => array( 'code' => 'nouploadmodule', 'info' => 'No upload module set' ),
- 'uploaddisabled' => array( 'code' => 'uploaddisabled', 'info' => 'Uploads are not enabled. Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true' ),
- 'copyuploaddisabled' => array( 'code' => 'copyuploaddisabled', 'info' => 'Uploads by URL is not enabled. Make sure $wgAllowCopyUploads is set to true in LocalSettings.php.' ),
+ 'uploaddisabled' => array( 'code' => 'uploaddisabled', 'info' => 'Uploads are not enabled. Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true' ),
+ 'copyuploaddisabled' => array( 'code' => 'copyuploaddisabled', 'info' => 'Uploads by URL is not enabled. Make sure $wgAllowCopyUploads is set to true in LocalSettings.php.' ),
'copyuploadbaddomain' => array( 'code' => 'copyuploadbaddomain', 'info' => 'Uploads by URL are not allowed from this domain.' ),
'filename-tooshort' => array( 'code' => 'filename-tooshort', 'info' => 'The filename is too short' ),
@@ -1385,8 +1405,41 @@ abstract class ApiBase extends ContextSource {
}
/**
+ * Will only set a warning instead of failing if the global $wgDebugAPI
+ * is set to true. Otherwise behaves exactly as dieUsageMsg().
+ * @param $error (array|string) Element of a getUserPermissionsErrors()-style array
+ * @since 1.21
+ */
+ public function dieUsageMsgOrDebug( $error ) {
+ global $wgDebugAPI;
+ if( $wgDebugAPI !== true ) {
+ $this->dieUsageMsg( $error );
+ } else {
+ if( is_string( $error ) ) {
+ $error = array( $error );
+ }
+ $parsed = $this->parseMsg( $error );
+ $this->setWarning( '$wgDebugAPI: ' . $parsed['code']
+ . ' - ' . $parsed['info'] );
+ }
+ }
+
+ /**
+ * Die with the $prefix.'badcontinue' error. This call is common enough to make it into the base method.
+ * @param $condition boolean will only die if this value is true
+ * @since 1.21
+ */
+ protected function dieContinueUsageIf( $condition ) {
+ if ( $condition ) {
+ $this->dieUsage(
+ 'Invalid continue param. You should pass the original value returned by the previous query',
+ 'badcontinue' );
+ }
+ }
+
+ /**
* Return the error message related to a certain array
- * @param $error array Element of a getUserPermissionsErrors()-style array
+ * @param array $error Element of a getUserPermissionsErrors()-style array
* @return array('code' => code, 'info' => info)
*/
public function parseMsg( $error ) {
@@ -1395,7 +1448,7 @@ abstract class ApiBase extends ContextSource {
// Check whether the error array was nested
// array( array( <code>, <params> ), array( <another_code>, <params> ) )
- if( is_array( $key ) ){
+ if( is_array( $key ) ) {
$error = $key;
$key = array_shift( $error );
}
@@ -1413,8 +1466,8 @@ abstract class ApiBase extends ContextSource {
/**
* Internal code errors should be reported with this method
- * @param $method string Method or function name
- * @param $message string Error message
+ * @param string $method Method or function name
+ * @param string $message Error message
*/
protected static function dieDebug( $method, $message ) {
wfDebugDieBacktrace( "Internal error in $method: $message" );
@@ -1515,10 +1568,17 @@ abstract class ApiBase extends ContextSource {
$params = $this->getFinalParams();
if ( $params ) {
foreach ( $params as $paramName => $paramSettings ) {
- if ( isset( $paramSettings[ApiBase::PARAM_REQUIRED] ) ) {
+ if ( isset( $paramSettings[ApiBase::PARAM_REQUIRED] ) && $paramSettings[ApiBase::PARAM_REQUIRED] ) {
$ret[] = array( 'missingparam', $paramName );
}
}
+ if ( array_key_exists( 'continue', $params ) ) {
+ $ret[] = array(
+ array(
+ 'code' => 'badcontinue',
+ 'info' => 'Invalid continue param. You should pass the original value returned by the previous query'
+ ) );
+ }
}
if ( $this->mustBePosted() ) {
@@ -1544,7 +1604,7 @@ abstract class ApiBase extends ContextSource {
/**
* Parses a list of errors into a standardised format
- * @param $errors array List of errors. Items can be in the for array( key, param1, param2, ... ) or array( 'code' => ..., 'info' => ... )
+ * @param array $errors List of errors. Items can be in the for array( key, param1, param2, ... ) or array( 'code' => ..., 'info' => ... )
* @return array Parsed list of errors with items in the form array( 'code' => ..., 'info' => ... )
*/
public function parseErrors( $errors ) {
@@ -1666,17 +1726,23 @@ abstract class ApiBase extends ContextSource {
}
/**
+ * Gets a default slave database connection object
* @return DatabaseBase
*/
protected function getDB() {
- return wfGetDB( DB_SLAVE, 'api' );
+ if ( !isset( $this->mSlaveDB ) ) {
+ $this->profileDBIn();
+ $this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' );
+ $this->profileDBOut();
+ }
+ return $this->mSlaveDB;
}
/**
* Debugging function that prints a value and an optional backtrace
* @param $value mixed Value to print
- * @param $name string Description of the printed value
- * @param $backtrace bool If true, print a backtrace
+ * @param string $name Description of the printed value
+ * @param bool $backtrace If true, print a backtrace
*/
public static function debugPrint( $value, $name = 'unknown', $backtrace = false ) {
print "\n\n<pre><b>Debugging value '$name':</b>\n\n";
@@ -1686,12 +1752,4 @@ abstract class ApiBase extends ContextSource {
}
print "\n</pre>\n";
}
-
- /**
- * Returns a string that identifies the version of this class.
- * @return string
- */
- public static function getBaseVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiBlock.php b/includes/api/ApiBlock.php
index c879b35d..90432b95 100644
--- a/includes/api/ApiBlock.php
+++ b/includes/api/ApiBlock.php
@@ -25,17 +25,13 @@
*/
/**
-* API module that facilitates the blocking of users. Requires API write mode
-* to be enabled.
-*
+ * API module that facilitates the blocking of users. Requires API write mode
+ * to be enabled.
+ *
* @ingroup API
*/
class ApiBlock extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
/**
* Blocks the user specified in the parameters for the given expiry, with the
* given reason, and with all other settings provided in the params. If the block
@@ -55,6 +51,7 @@ class ApiBlock extends ApiBase {
if ( !$user->isAllowed( 'block' ) ) {
$this->dieUsageMsg( 'cantblock' );
}
+
# bug 15810: blocked admins should have limited access here
if ( $user->isBlocked() ) {
$status = SpecialBlock::checkUnblockSelf( $params['user'], $user );
@@ -62,6 +59,13 @@ class ApiBlock extends ApiBase {
$this->dieUsageMsg( array( $status ) );
}
}
+
+ $target = User::newFromName( $params['user'] );
+ // Bug 38633 - if the target is a user (not an IP address), but it doesn't exist or is unusable, error.
+ if ( $target instanceof User && ( $target->isAnon() /* doesn't exist */ || !User::isUsableName( $target->getName() ) ) ) {
+ $this->dieUsageMsg( array( 'nosuchuser', $params['user'] ) );
+ }
+
if ( $params['hidename'] && !$user->isAllowed( 'hideuser' ) ) {
$this->dieUsageMsg( 'canthide' );
}
@@ -70,6 +74,7 @@ class ApiBlock extends ApiBase {
}
$data = array(
+ 'PreviousTarget' => $params['user'],
'Target' => $params['user'],
'Reason' => array(
$params['reason'],
@@ -83,7 +88,7 @@ class ApiBlock extends ApiBase {
'DisableEmail' => $params['noemail'],
'HideUser' => $params['hidename'],
'DisableUTEdit' => !$params['allowusertalk'],
- 'AlreadyBlocked' => $params['reblock'],
+ 'Reblock' => $params['reblock'],
'Watch' => $params['watchuser'],
'Confirm' => true,
);
@@ -99,7 +104,7 @@ class ApiBlock extends ApiBase {
$res['userID'] = $target instanceof User ? $target->getId() : 0;
$block = Block::newFromTarget( $target );
- if( $block instanceof Block ){
+ if( $block instanceof Block ) {
$res['expiry'] = $block->mExpiry == $this->getDB()->getInfinity()
? 'infinite'
: wfTimestamp( TS_ISO_8601, $block->mExpiry );
@@ -178,7 +183,7 @@ class ApiBlock extends ApiBase {
'anononly' => 'Block anonymous users only (i.e. disable anonymous edits for this IP)',
'nocreate' => 'Prevent account creation',
'autoblock' => 'Automatically block the last used IP address, and any subsequent IP addresses they try to login from',
- 'noemail' => 'Prevent user from sending e-mail through the wiki. (Requires the "blockemail" right.)',
+ 'noemail' => 'Prevent user from sending email through the wiki. (Requires the "blockemail" right.)',
'hidename' => 'Hide the username from the block log. (Requires the "hideuser" right.)',
'allowusertalk' => 'Allow the user to edit their own talk page (depends on $wgBlockAllowsUTEdit)',
'reblock' => 'If the user is already blocked, overwrite the existing block',
@@ -256,8 +261,4 @@ class ApiBlock extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Block';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiComparePages.php b/includes/api/ApiComparePages.php
index ed72b29b..79ffcb0a 100644
--- a/includes/api/ApiComparePages.php
+++ b/includes/api/ApiComparePages.php
@@ -25,17 +25,21 @@
class ApiComparePages extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$params = $this->extractRequestParams();
$rev1 = $this->revisionOrTitleOrId( $params['fromrev'], $params['fromtitle'], $params['fromid'] );
$rev2 = $this->revisionOrTitleOrId( $params['torev'], $params['totitle'], $params['toid'] );
- $de = new DifferenceEngine( $this->getContext(),
+ $revision = Revision::newFromId( $rev1 );
+
+ if ( !$revision ) {
+ $this->dieUsage( 'The diff cannot be retrieved, ' .
+ 'one revision does not exist or you do not have permission to view it.', 'baddiff' );
+ }
+
+ $contentHandler = $revision->getContentHandler();
+ $de = $contentHandler->createDifferenceEngine( $this->getContext(),
$rev1,
$rev2,
null, // rcid
@@ -77,11 +81,11 @@ class ApiComparePages extends ApiBase {
* @return int
*/
private function revisionOrTitleOrId( $revision, $titleText, $titleId ) {
- if( $revision ){
+ if( $revision ) {
return $revision;
} elseif( $titleText ) {
$title = Title::newFromText( $titleText );
- if( !$title ){
+ if( !$title || $title->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $titleText ) );
}
return $title->getLatestRevID();
@@ -164,8 +168,4 @@ class ApiComparePages extends ApiBase {
'api.php?action=compare&fromrev=1&torev=2' => 'Create a diff between revision 1 and 2',
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiCreateAccount.php b/includes/api/ApiCreateAccount.php
new file mode 100644
index 00000000..55c60cce
--- /dev/null
+++ b/includes/api/ApiCreateAccount.php
@@ -0,0 +1,298 @@
+<?php
+/**
+ * Created on August 7, 2012
+ *
+ * Copyright © 2012 Tyler Romeo <tylerromeo@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
+ */
+
+/**
+ * Unit to authenticate account registration attempts to the current wiki.
+ *
+ * @ingroup API
+ */
+class ApiCreateAccount extends ApiBase {
+ public function execute() {
+
+ // $loginForm->addNewaccountInternal will throw exceptions
+ // if wiki is read only (already handled by api), user is blocked or does not have rights.
+ // Use userCan in order to hit GlobalBlock checks (according to Special:userlogin)
+ $loginTitle = SpecialPage::getTitleFor( 'Userlogin' );
+ if ( !$loginTitle->userCan( 'createaccount', $this->getUser() ) ) {
+ $this->dieUsage( 'You do not have the right to create a new account', 'permdenied-createaccount' );
+ }
+ if ( $this->getUser()->isBlockedFromCreateAccount() ) {
+ $this->dieUsage( 'You cannot create a new account because you are blocked', 'blocked' );
+ }
+
+ $params = $this->extractRequestParams();
+
+ $result = array();
+
+ // Init session if necessary
+ if ( session_id() == '' ) {
+ wfSetupSession();
+ }
+
+ if( $params['mailpassword'] && !$params['email'] ) {
+ $this->dieUsageMsg( 'noemail' );
+ }
+
+ $context = new DerivativeContext( $this->getContext() );
+ $context->setRequest( new DerivativeRequest(
+ $this->getContext()->getRequest(),
+ array(
+ 'type' => 'signup',
+ 'uselang' => $params['language'],
+ 'wpName' => $params['name'],
+ 'wpPassword' => $params['password'],
+ 'wpRetype' => $params['password'],
+ 'wpDomain' => $params['domain'],
+ 'wpEmail' => $params['email'],
+ 'wpRealName' => $params['realname'],
+ 'wpCreateaccountToken' => $params['token'],
+ 'wpCreateaccount' => $params['mailpassword'] ? null : '1',
+ 'wpCreateaccountMail' => $params['mailpassword'] ? '1' : null
+ )
+ ) );
+
+ $loginForm = new LoginForm();
+ $loginForm->setContext( $context );
+ $loginForm->load();
+
+ $status = $loginForm->addNewaccountInternal();
+ $result = array();
+ if( $status->isGood() ) {
+ // Success!
+ $user = $status->getValue();
+
+ // If we showed up language selection links, and one was in use, be
+ // smart (and sensible) and save that language as the user's preference
+ global $wgLoginLanguageSelector, $wgEmailAuthentication;
+ if( $wgLoginLanguageSelector && $params['language'] ) {
+ $user->setOption( 'language', $params['language'] );
+ }
+
+ if( $params['mailpassword'] ) {
+ // If mailpassword was set, disable the password and send an email.
+ $user->setPassword( null );
+ $status->merge( $loginForm->mailPasswordInternal( $user, false, 'createaccount-title', 'createaccount-text' ) );
+ } elseif( $wgEmailAuthentication && Sanitizer::validateEmail( $user->getEmail() ) ) {
+ // Send out an email authentication message if needed
+ $status->merge( $user->sendConfirmationMail() );
+ }
+
+ // Save settings (including confirmation token)
+ $user->saveSettings();
+
+ wfRunHooks( 'AddNewAccount', array( $user, $params['mailpassword'] ) );
+
+ if ( $params['mailpassword'] ) {
+ $logAction = 'byemail';
+ } elseif ( $this->getUser()->isLoggedIn() ) {
+ $logAction = 'create2';
+ } else {
+ $logAction = 'create';
+ }
+ $user->addNewUserLogEntry( $logAction, (string)$params['reason'] );
+
+ // Add username, id, and token to result.
+ $result['username'] = $user->getName();
+ $result['userid'] = $user->getId();
+ $result['token'] = $user->getToken();
+ }
+
+ $apiResult = $this->getResult();
+
+ if( $status->hasMessage( 'sessionfailure' ) || $status->hasMessage( 'nocookiesfornew' ) ) {
+ // Token was incorrect, so add it to result, but don't throw an exception
+ // since not having the correct token is part of the normal
+ // flow of events.
+ $result['token'] = LoginForm::getCreateaccountToken();
+ $result['result'] = 'needtoken';
+ } elseif( !$status->isOK() ) {
+ // There was an error. Die now.
+ // Cannot use dieUsageMsg() directly because extensions
+ // might return custom error messages.
+ $errors = $status->getErrorsArray();
+ if( $errors[0] instanceof Message ) {
+ $code = 'aborted';
+ $desc = $errors[0];
+ } else {
+ $code = array_shift( $errors[0] );
+ $desc = wfMessage( $code, $errors[0] );
+ }
+ $this->dieUsage( $desc, $code );
+ } elseif( !$status->isGood() ) {
+ // Status is not good, but OK. This means warnings.
+ $result['result'] = 'warning';
+
+ // Add any warnings to the result
+ $warnings = $status->getErrorsByType( 'warning' );
+ if( $warnings ) {
+ foreach( $warnings as &$warning ) {
+ $apiResult->setIndexedTagName( $warning['params'], 'param' );
+ }
+ $apiResult->setIndexedTagName( $warnings, 'warning' );
+ $result['warnings'] = $warnings;
+ }
+ } else {
+ // Everything was fine.
+ $result['result'] = 'success';
+ }
+
+ $apiResult->addValue( null, 'createaccount', $result );
+ }
+
+ public function getDescription() {
+ return 'Create a new user account.';
+ }
+
+ public function mustBePosted() {
+ return true;
+ }
+
+ public function isReadMode() {
+ return false;
+ }
+
+ public function isWriteMode() {
+ return true;
+ }
+
+ public function getAllowedParams() {
+ global $wgEmailConfirmToEdit;
+ return array(
+ 'name' => array(
+ ApiBase::PARAM_TYPE => 'user',
+ ApiBase::PARAM_REQUIRED => true
+ ),
+ 'password' => null,
+ 'domain' => null,
+ 'token' => null,
+ 'email' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => $wgEmailConfirmToEdit
+ ),
+ 'realname' => null,
+ 'mailpassword' => array(
+ ApiBase::PARAM_TYPE => 'boolean',
+ ApiBase::PARAM_DFLT => false
+ ),
+ 'reason' => null,
+ 'language' => null
+ );
+ }
+
+ public function getParamDescription() {
+ $p = $this->getModulePrefix();
+ return array(
+ 'name' => 'Username',
+ 'password' => "Password (ignored if {$p}mailpassword is set)",
+ 'domain' => 'Domain for external authentication (optional)',
+ 'token' => 'Account creation token obtained in first request',
+ 'email' => 'Email address of user (optional)',
+ 'realname' => 'Real name of user (optional)',
+ 'mailpassword' => 'If set to any value, a random password will be emailed to the user',
+ 'reason' => 'Optional reason for creating the account to be put in the logs',
+ 'language' => 'Language code to set as default for the user (optional, defaults to content language)'
+ );
+ }
+
+ public function getResultProperties() {
+ return array(
+ 'createaccount' => array(
+ 'result' => array(
+ ApiBase::PROP_TYPE => array(
+ 'success',
+ 'warning',
+ 'needtoken'
+ )
+ ),
+ 'username' => array(
+ ApiBase::PROP_TYPE => 'string',
+ ApiBase::PROP_NULLABLE => true
+ ),
+ 'userid' => array(
+ ApiBase::PROP_TYPE => 'int',
+ ApiBase::PROP_NULLABLE => true
+ ),
+ 'token' => array(
+ ApiBase::PROP_TYPE => 'string',
+ ApiBase::PROP_NULLABLE => true
+ ),
+ )
+ );
+ }
+
+ public function getPossibleErrors() {
+ // Note the following errors aren't possible and don't need to be listed:
+ // sessionfailure, nocookiesfornew, badretype
+ $localErrors = array(
+ 'wrongpassword', // Actually caused by wrong domain field. Riddle me that...
+ 'sorbs_create_account_reason',
+ 'noname',
+ 'userexists',
+ 'password-name-match', // from User::getPasswordValidity
+ 'password-login-forbidden', // from User::getPasswordValidity
+ 'noemailtitle',
+ 'invalidemailaddress',
+ 'externaldberror',
+ 'acct_creation_throttle_hit',
+ );
+
+ $errors = parent::getPossibleErrors();
+ // All local errors are from LoginForm, which means they're actually message keys.
+ foreach( $localErrors as $error ) {
+ $errors[] = array( 'code' => $error, 'info' => wfMessage( $error )->parse() );
+ }
+
+ $errors[] = array(
+ 'code' => 'permdenied-createaccount',
+ 'info' => 'You do not have the right to create a new account'
+ );
+ $errors[] = array(
+ 'code' => 'blocked',
+ 'info' => 'You cannot create a new account because you are blocked'
+ );
+ $errors[] = array(
+ 'code' => 'aborted',
+ 'info' => 'Account creation aborted by hook (info may vary)'
+ );
+
+ // 'passwordtooshort' has parameters. :(
+ global $wgMinimalPasswordLength;
+ $errors[] = array(
+ 'code' => 'passwordtooshort',
+ 'info' => wfMessage( 'passwordtooshort', $wgMinimalPasswordLength )->parse()
+ );
+ return $errors;
+ }
+
+ public function getExamples() {
+ return array(
+ 'api.php?action=createaccount&name=testuser&password=test123',
+ 'api.php?action=createaccount&name=testmailuser&mailpassword=true&reason=MyReason',
+ );
+ }
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Account_creation';
+ }
+}
diff --git a/includes/api/ApiDelete.php b/includes/api/ApiDelete.php
index 2d36f19a..d1f0806e 100644
--- a/includes/api/ApiDelete.php
+++ b/includes/api/ApiDelete.php
@@ -32,10 +32,6 @@
*/
class ApiDelete extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
/**
* Extracts the title, token, and reason from the request parameters and invokes
* the local delete() function with these as arguments. It does not make use of
@@ -61,6 +57,9 @@ class ApiDelete extends ApiBase {
$status = self::delete( $pageObj, $user, $params['token'], $reason );
}
+ if ( is_array( $status ) ) {
+ $this->dieUsageMsg( $status[0] );
+ }
if ( !$status->isGood() ) {
$errors = $status->getErrorsArray();
$this->dieUsageMsg( $errors[0] ); // We don't care about multiple errors, just report one of them
@@ -98,11 +97,11 @@ class ApiDelete extends ApiBase {
/**
* We have our own delete() function, since Article.php's implementation is split in two phases
*
- * @param $page WikiPage object to work on
+ * @param $page Page|WikiPage object to work on
* @param $user User doing the action
- * @param $token String: delete token (same as edit token)
- * @param $reason String: reason for the deletion. Autogenerated if NULL
- * @return Status
+ * @param string $token delete token (same as edit token)
+ * @param string|null $reason reason for the deletion. Autogenerated if NULL
+ * @return Status|array
*/
public static function delete( Page $page, User $user, $token, &$reason = null ) {
$title = $page->getTitle();
@@ -128,13 +127,13 @@ class ApiDelete extends ApiBase {
}
/**
- * @param $page WikiPage object to work on
+ * @param $page WikiPage|Page object to work on
* @param $user User doing the action
* @param $token
* @param $oldimage
* @param $reason
* @param $suppress bool
- * @return Status
+ * @return Status|array
*/
public static function deleteFile( Page $page, User $user, $token, $oldimage, &$reason = null, $suppress = false ) {
$title = $page->getTitle();
@@ -161,7 +160,7 @@ class ApiDelete extends ApiBase {
if ( is_null( $reason ) ) { // Log and RC don't like null reasons
$reason = '';
}
- return FileDeleteForm::doDelete( $title, $file, $oldimage, $reason, $suppress );
+ return FileDeleteForm::doDelete( $title, $file, $oldimage, $reason, $suppress, $user );
}
public function mustBePosted() {
@@ -264,8 +263,4 @@ class ApiDelete extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Delete';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiDisabled.php b/includes/api/ApiDisabled.php
index 13975aec..e5ef3b7e 100644
--- a/includes/api/ApiDisabled.php
+++ b/includes/api/ApiDisabled.php
@@ -36,10 +36,6 @@
*/
class ApiDisabled extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$this->dieUsage( "The \"{$this->getModuleName()}\" module has been disabled.", 'moduledisabled' );
}
@@ -63,8 +59,4 @@ class ApiDisabled extends ApiBase {
public function getExamples() {
return array();
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiEditPage.php b/includes/api/ApiEditPage.php
index 0963fe7c..4916145b 100644
--- a/includes/api/ApiEditPage.php
+++ b/includes/api/ApiEditPage.php
@@ -33,10 +33,6 @@
*/
class ApiEditPage extends ApiBase {
- public function __construct( $query, $moduleName ) {
- parent::__construct( $query, $moduleName );
- }
-
public function execute() {
$user = $this->getUser();
$params = $this->extractRequestParams();
@@ -50,32 +46,28 @@ class ApiEditPage extends ApiBase {
$pageObj = $this->getTitleOrPageId( $params );
$titleObj = $pageObj->getTitle();
- if ( $titleObj->isExternal() ) {
- $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
- }
-
$apiResult = $this->getResult();
if ( $params['redirect'] ) {
if ( $titleObj->isRedirect() ) {
$oldTitle = $titleObj;
- $titles = Title::newFromRedirectArray(
- Revision::newFromTitle(
- $oldTitle, false, Revision::READ_LATEST
- )->getText( Revision::FOR_THIS_USER )
- );
+ $titles = Revision::newFromTitle( $oldTitle, false, Revision::READ_LATEST )
+ ->getContent( Revision::FOR_THIS_USER, $user )
+ ->getRedirectChain();
// array_shift( $titles );
$redirValues = array();
+
+ /** @var $newTitle Title */
foreach ( $titles as $id => $newTitle ) {
- if ( !isset( $titles[ $id - 1 ] ) ) {
- $titles[ $id - 1 ] = $oldTitle;
+ if ( !isset( $titles[$id - 1] ) ) {
+ $titles[$id - 1] = $oldTitle;
}
$redirValues[] = array(
- 'from' => $titles[ $id - 1 ]->getPrefixedText(),
+ 'from' => $titles[$id - 1]->getPrefixedText(),
'to' => $newTitle->getPrefixedText()
);
@@ -84,9 +76,34 @@ class ApiEditPage extends ApiBase {
$apiResult->setIndexedTagName( $redirValues, 'r' );
$apiResult->addValue( null, 'redirects', $redirValues );
+
+ // Since the page changed, update $pageObj
+ $pageObj = WikiPage::factory( $titleObj );
}
}
+ if ( !isset( $params['contentmodel'] ) || $params['contentmodel'] == '' ) {
+ $contentHandler = $pageObj->getContentHandler();
+ } else {
+ $contentHandler = ContentHandler::getForModelID( $params['contentmodel'] );
+ }
+
+ // @todo ask handler whether direct editing is supported at all! make allowFlatEdit() method or some such
+
+ if ( !isset( $params['contentformat'] ) || $params['contentformat'] == '' ) {
+ $params['contentformat'] = $contentHandler->getDefaultFormat();
+ }
+
+ $contentFormat = $params['contentformat'];
+
+ if ( !$contentHandler->isSupportedFormat( $contentFormat ) ) {
+ $name = $titleObj->getPrefixedDBkey();
+ $model = $contentHandler->getModelID();
+
+ $this->dieUsage( "The requested format $contentFormat is not supported for content model ".
+ " $model used by $name", 'badformat' );
+ }
+
if ( $params['createonly'] && $titleObj->exists() ) {
$this->dieUsageMsg( 'createonly-exists' );
}
@@ -103,31 +120,61 @@ class ApiEditPage extends ApiBase {
$this->dieUsageMsg( $errors[0] );
}
- $articleObj = Article::newFromTitle( $titleObj, $this->getContext() );
-
$toMD5 = $params['text'];
if ( !is_null( $params['appendtext'] ) || !is_null( $params['prependtext'] ) )
{
- // For non-existent pages, Article::getContent()
- // returns an interface message rather than ''
- // We do want getContent()'s behavior for non-existent
- // MediaWiki: pages, though
- if ( $articleObj->getID() == 0 && $titleObj->getNamespace() != NS_MEDIAWIKI ) {
- $content = '';
- } else {
- $content = $articleObj->getContent();
+ $content = $pageObj->getContent();
+
+ if ( !$content ) {
+ if ( $titleObj->getNamespace() == NS_MEDIAWIKI ) {
+ # If this is a MediaWiki:x message, then load the messages
+ # and return the message value for x.
+ $text = $titleObj->getDefaultMessageText();
+ if ( $text === false ) {
+ $text = '';
+ }
+
+ try {
+ $content = ContentHandler::makeContent( $text, $this->getTitle() );
+ } catch ( MWContentSerializationException $ex ) {
+ $this->dieUsage( $ex->getMessage(), 'parseerror' );
+ return;
+ }
+ } else {
+ # Otherwise, make a new empty content.
+ $content = $contentHandler->makeEmptyContent();
+ }
+ }
+
+ // @todo: Add support for appending/prepending to the Content interface
+
+ if ( !( $content instanceof TextContent ) ) {
+ $mode = $contentHandler->getModelID();
+ $this->dieUsage( "Can't append to pages using content model $mode", 'appendnotsupported' );
}
if ( !is_null( $params['section'] ) ) {
+ if ( !$contentHandler->supportsSections() ) {
+ $modelName = $contentHandler->getModelID();
+ $this->dieUsage( "Sections are not supported for this content model: $modelName.", 'sectionsnotsupported' );
+ }
+
// Process the content for section edits
- global $wgParser;
$section = intval( $params['section'] );
- $content = $wgParser->getSection( $content, $section, false );
- if ( $content === false ) {
+ $content = $content->getSection( $section );
+
+ if ( !$content ) {
$this->dieUsage( "There is no section {$section}.", 'nosuchsection' );
}
}
- $params['text'] = $params['prependtext'] . $content . $params['appendtext'];
+
+ if ( !$content ) {
+ $text = '';
+ } else {
+ $text = $content->serialize( $contentFormat );
+ }
+
+ $params['text'] = $params['prependtext'] . $text . $params['appendtext'];
$toMD5 = $params['prependtext'] . $params['appendtext'];
}
@@ -151,18 +198,21 @@ class ApiEditPage extends ApiBase {
$this->dieUsageMsg( array( 'nosuchrevid', $params['undoafter'] ) );
}
- if ( $undoRev->getPage() != $articleObj->getID() ) {
+ if ( $undoRev->getPage() != $pageObj->getID() ) {
$this->dieUsageMsg( array( 'revwrongpage', $undoRev->getID(), $titleObj->getPrefixedText() ) );
}
- if ( $undoafterRev->getPage() != $articleObj->getID() ) {
+ if ( $undoafterRev->getPage() != $pageObj->getID() ) {
$this->dieUsageMsg( array( 'revwrongpage', $undoafterRev->getID(), $titleObj->getPrefixedText() ) );
}
- $newtext = $articleObj->getUndoText( $undoRev, $undoafterRev );
- if ( $newtext === false ) {
+ $newContent = $contentHandler->getUndoContent( $pageObj->getRevision(), $undoRev, $undoafterRev );
+
+ if ( !$newContent ) {
$this->dieUsageMsg( 'undo-failure' );
}
- $params['text'] = $newtext;
+
+ $params['text'] = $newContent->serialize( $params['contentformat'] );
+
// If no summary was given and we only undid one rev,
// use an autosummary
if ( is_null( $params['summary'] ) && $titleObj->getNextRevisionID( $undoafterRev->getID() ) == $params['undo'] ) {
@@ -179,6 +229,8 @@ class ApiEditPage extends ApiBase {
// That interface kind of sucks, but it's workable
$requestArray = array(
'wpTextbox1' => $params['text'],
+ 'format' => $contentFormat,
+ 'model' => $contentHandler->getModelID(),
'wpEditToken' => $params['token'],
'wpIgnoreBlankSummary' => ''
);
@@ -191,18 +243,23 @@ class ApiEditPage extends ApiBase {
$requestArray['wpSectionTitle'] = $params['sectiontitle'];
}
+ // TODO: Pass along information from 'undoafter' as well
+ if ( $params['undo'] > 0 ) {
+ $requestArray['wpUndidRevision'] = $params['undo'];
+ }
+
// Watch out for basetimestamp == ''
// wfTimestamp() treats it as NOW, almost certainly causing an edit conflict
if ( !is_null( $params['basetimestamp'] ) && $params['basetimestamp'] != '' ) {
$requestArray['wpEdittime'] = wfTimestamp( TS_MW, $params['basetimestamp'] );
} else {
- $requestArray['wpEdittime'] = $articleObj->getTimestamp();
+ $requestArray['wpEdittime'] = $pageObj->getTimestamp();
}
if ( !is_null( $params['starttimestamp'] ) && $params['starttimestamp'] != '' ) {
$requestArray['wpStarttime'] = wfTimestamp( TS_MW, $params['starttimestamp'] );
} else {
- $requestArray['wpStarttime'] = wfTimestampNow(); // Fake wpStartime
+ $requestArray['wpStarttime'] = wfTimestampNow(); // Fake wpStartime
}
if ( $params['minor'] || ( !$params['notminor'] && $user->getOption( 'minordefault' ) ) ) {
@@ -244,7 +301,19 @@ class ApiEditPage extends ApiBase {
// TODO: Make them not or check if they still do
$wgTitle = $titleObj;
- $ep = new EditPage( $articleObj );
+ $articleContext = new RequestContext;
+ $articleContext->setRequest( $req );
+ $articleContext->setWikiPage( $pageObj );
+ $articleContext->setUser( $this->getUser() );
+
+ /** @var $articleObject Article */
+ $articleObject = Article::newFromWikiPage( $pageObj, $articleContext );
+
+ $ep = new EditPage( $articleObject );
+
+ // allow editing of non-textual content.
+ $ep->allowNonTextContent = true;
+
$ep->setContextTitle( $titleObj );
$ep->importFormData( $req );
@@ -262,7 +331,7 @@ class ApiEditPage extends ApiBase {
}
// Do the actual save
- $oldRevId = $articleObj->getRevIdFetched();
+ $oldRevId = $articleObject->getRevIdFetched();
$result = null;
// Fake $wgRequest for some hooks inside EditPage
// @todo FIXME: This interface SUCKS
@@ -278,6 +347,9 @@ class ApiEditPage extends ApiBase {
case EditPage::AS_HOOK_ERROR_EXPECTED:
$this->dieUsageMsg( 'hookaborted' );
+ case EditPage::AS_PARSE_ERROR:
+ $this->dieUsage( $status->getMessage(), 'parseerror' );
+
case EditPage::AS_IMAGE_REDIRECT_ANON:
$this->dieUsageMsg( 'noimageredirect-anon' );
@@ -324,19 +396,21 @@ class ApiEditPage extends ApiBase {
case EditPage::AS_SUCCESS_NEW_ARTICLE:
$r['new'] = '';
+ // fall-through
case EditPage::AS_SUCCESS_UPDATE:
$r['result'] = 'Success';
$r['pageid'] = intval( $titleObj->getArticleID() );
$r['title'] = $titleObj->getPrefixedText();
- $newRevId = $articleObj->getLatest();
+ $r['contentmodel'] = $titleObj->getContentModel();
+ $newRevId = $articleObject->getLatest();
if ( $newRevId == $oldRevId ) {
$r['nochange'] = '';
} else {
$r['oldrevid'] = intval( $oldRevId );
$r['newrevid'] = intval( $newRevId );
$r['newtimestamp'] = wfTimestamp( TS_ISO_8601,
- $articleObj->getTimestamp() );
+ $pageObj->getTimestamp() );
}
break;
@@ -380,6 +454,7 @@ class ApiEditPage extends ApiBase {
array( 'undo-failure' ),
array( 'hashcheckfailed' ),
array( 'hookaborted' ),
+ array( 'code' => 'parseerror', 'info' => 'Failed to parse the given text.' ),
array( 'noimageredirect-anon' ),
array( 'noimageredirect-logged' ),
array( 'spamdetected', 'spam' ),
@@ -397,6 +472,13 @@ class ApiEditPage extends ApiBase {
array( 'unknownerror', 'retval' ),
array( 'code' => 'nosuchsection', 'info' => 'There is no section section.' ),
array( 'code' => 'invalidsection', 'info' => 'The section parameter must be set to an integer or \'new\'' ),
+ array( 'code' => 'sectionsnotsupported', 'info' => 'Sections are not supported for this type of page.' ),
+ array( 'code' => 'editnotsupported', 'info' => 'Editing of this type of page is not supported using '
+ . 'the text based edit API.' ),
+ array( 'code' => 'appendnotsupported', 'info' => 'This type of page can not be edited by appending '
+ . 'or prepending text.' ),
+ array( 'code' => 'badformat', 'info' => 'The requested serialization format can not be applied to '
+ . 'the page\'s content model' ),
array( 'customcssprotected' ),
array( 'customjsprotected' ),
)
@@ -414,7 +496,6 @@ class ApiEditPage extends ApiBase {
'section' => null,
'sectiontitle' => array(
ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => false,
),
'text' => null,
'token' => array(
@@ -460,6 +541,12 @@ class ApiEditPage extends ApiBase {
ApiBase::PARAM_TYPE => 'boolean',
ApiBase::PARAM_DFLT => false,
),
+ 'contentformat' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+ ),
+ 'contentmodel' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
+ )
);
}
@@ -490,7 +577,7 @@ class ApiEditPage extends ApiBase {
'watch' => 'Add the page to your watchlist',
'unwatch' => 'Remove the page from your watchlist',
'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch',
- 'md5' => array( "The MD5 hash of the {$p}text parameter, or the {$p}prependtext and {$p}appendtext parameters concatenated.",
+ 'md5' => array( "The MD5 hash of the {$p}text parameter, or the {$p}prependtext and {$p}appendtext parameters concatenated.",
'If set, the edit won\'t be done unless the hash is correct' ),
'prependtext' => "Add this text to the beginning of the page. Overrides {$p}text",
'appendtext' => array( "Add this text to the end of the page. Overrides {$p}text.",
@@ -498,6 +585,8 @@ class ApiEditPage extends ApiBase {
'undo' => "Undo this revision. Overrides {$p}text, {$p}prependtext and {$p}appendtext",
'undoafter' => 'Undo all revisions from undo to this one. If not set, just undo one revision',
'redirect' => 'Automatically resolve redirects',
+ 'contentformat' => 'Content serialization format used for the input text',
+ 'contentmodel' => 'Content model of the new content',
);
}
@@ -546,10 +635,8 @@ class ApiEditPage extends ApiBase {
public function getExamples() {
return array(
-
'api.php?action=edit&title=Test&summary=test%20summary&text=article%20content&basetimestamp=20070824123454&token=%2B\\'
=> 'Edit a page (anonymous user)',
-
'api.php?action=edit&title=Test&summary=NOTOC&minor=&prependtext=__NOTOC__%0A&basetimestamp=20070824123454&token=%2B\\'
=> 'Prepend __NOTOC__ to a page (anonymous user)',
'api.php?action=edit&title=Test&undo=13585&undoafter=13579&basetimestamp=20070824123454&token=%2B\\'
@@ -560,8 +647,4 @@ class ApiEditPage extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Edit';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiEmailUser.php b/includes/api/ApiEmailUser.php
index 4fa03434..cd0d0cba 100644
--- a/includes/api/ApiEmailUser.php
+++ b/includes/api/ApiEmailUser.php
@@ -30,10 +30,6 @@
*/
class ApiEmailUser extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$params = $this->extractRequestParams();
@@ -158,10 +154,6 @@ class ApiEmailUser extends ApiBase {
}
public function getHelpUrls() {
- return 'https://www.mediawiki.org/wiki/API:E-mail';
- }
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
+ return 'https://www.mediawiki.org/wiki/API:Email';
}
}
diff --git a/includes/api/ApiExpandTemplates.php b/includes/api/ApiExpandTemplates.php
index 160f5b91..f5898fb3 100644
--- a/includes/api/ApiExpandTemplates.php
+++ b/includes/api/ApiExpandTemplates.php
@@ -33,10 +33,6 @@
*/
class ApiExpandTemplates extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
// Cache may vary on $wgUser because ParserOptions gets data from it
$this->getMain()->setCacheMode( 'anon-public-user-private' );
@@ -46,7 +42,7 @@ class ApiExpandTemplates extends ApiBase {
// Create title for parser
$title_obj = Title::newFromText( $params['title'] );
- if ( !$title_obj ) {
+ if ( !$title_obj || $title_obj->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
@@ -130,8 +126,4 @@ class ApiExpandTemplates extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Parsing_wikitext#expandtemplates';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFeedContributions.php b/includes/api/ApiFeedContributions.php
index 1cf760ae..015a9922 100644
--- a/includes/api/ApiFeedContributions.php
+++ b/includes/api/ApiFeedContributions.php
@@ -29,10 +29,6 @@
*/
class ApiFeedContributions extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
/**
* This module uses a custom feed wrapper printer.
*
@@ -51,7 +47,7 @@ class ApiFeedContributions extends ApiBase {
$this->dieUsage( 'Syndication feeds are not available', 'feed-unavailable' );
}
- if( !isset( $wgFeedClasses[ $params['feedformat'] ] ) ) {
+ if( !isset( $wgFeedClasses[$params['feedformat']] ) ) {
$this->dieUsage( 'Invalid subscription feed type', 'feed-invalid' );
}
@@ -130,10 +126,22 @@ class ApiFeedContributions extends ApiBase {
protected function feedItemDesc( $revision ) {
if( $revision ) {
$msg = wfMessage( 'colon-separator' )->inContentLanguage()->text();
+ $content = $revision->getContent();
+
+ if ( $content instanceof TextContent ) {
+ // only textual content has a "source view".
+ $html = nl2br( htmlspecialchars( $content->getNativeData() ) );
+ } else {
+ //XXX: we could get an HTML representation of the content via getParserOutput, but that may
+ // contain JS magic and generally may not be suitable for inclusion in a feed.
+ // Perhaps Content should have a getDescriptiveHtml method and/or a getSourceText method.
+ //Compare also FeedUtils::formatDiffRow.
+ $html = '';
+ }
+
return '<p>' . htmlspecialchars( $revision->getUserText() ) . $msg .
htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) .
- "</p>\n<hr />\n<div>" .
- nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>";
+ "</p>\n<hr />\n<div>" . $html . "</div>";
}
return '';
}
@@ -201,8 +209,4 @@ class ApiFeedContributions extends ApiBase {
'api.php?action=feedcontributions&user=Reedy',
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFeedWatchlist.php b/includes/api/ApiFeedWatchlist.php
index 6ccb02fe..6c793b36 100644
--- a/includes/api/ApiFeedWatchlist.php
+++ b/includes/api/ApiFeedWatchlist.php
@@ -33,10 +33,6 @@
*/
class ApiFeedWatchlist extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
/**
* This module uses a custom feed wrapper printer.
*
@@ -62,12 +58,9 @@ class ApiFeedWatchlist extends ApiBase {
$this->dieUsage( 'Syndication feeds are not available', 'feed-unavailable' );
}
- if( !isset( $wgFeedClasses[ $params['feedformat'] ] ) ) {
+ if( !isset( $wgFeedClasses[$params['feedformat']] ) ) {
$this->dieUsage( 'Invalid subscription feed type', 'feed-invalid' );
}
- if ( !is_null( $params['wlexcludeuser'] ) ) {
- $fauxReqArr['wlexcludeuser'] = $params['wlexcludeuser'];
- }
// limit to the number of hours going from now back
$endTime = wfTimestamp( TS_MW, time() - intval( $params['hours'] * 60 * 60 ) );
@@ -84,12 +77,15 @@ class ApiFeedWatchlist extends ApiBase {
'wllimit' => ( 50 > $wgFeedLimit ) ? $wgFeedLimit : 50
);
- if ( !is_null( $params['wlowner'] ) ) {
+ if ( $params['wlowner'] !== null ) {
$fauxReqArr['wlowner'] = $params['wlowner'];
}
- if ( !is_null( $params['wltoken'] ) ) {
+ if ( $params['wltoken'] !== null ) {
$fauxReqArr['wltoken'] = $params['wltoken'];
}
+ if ( $params['wlexcludeuser'] !== null ) {
+ $fauxReqArr['wlexcludeuser'] = $params['wlexcludeuser'];
+ }
// Support linking to diffs instead of article
if ( $params['linktodiffs'] ) {
@@ -233,8 +229,4 @@ class ApiFeedWatchlist extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Watchlist_feed';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFileRevert.php b/includes/api/ApiFileRevert.php
index 83d078d2..cbb2ba6a 100644
--- a/includes/api/ApiFileRevert.php
+++ b/includes/api/ApiFileRevert.php
@@ -37,10 +37,6 @@ class ApiFileRevert extends ApiBase {
protected $params;
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$this->params = $this->extractRequestParams();
// Extract the file and archiveName from the request parameters
@@ -50,7 +46,7 @@ class ApiFileRevert extends ApiBase {
$this->checkPermissions( $this->getUser() );
$sourceUrl = $this->file->getArchiveVirtualUrl( $this->archiveName );
- $status = $this->file->upload( $sourceUrl, $this->params['comment'], $this->params['comment'] );
+ $status = $this->file->upload( $sourceUrl, $this->params['comment'], $this->params['comment'], 0, false, false, $this->getUser() );
if ( $status->isGood() ) {
$result = array( 'result' => 'Success' );
@@ -73,8 +69,8 @@ class ApiFileRevert extends ApiBase {
protected function checkPermissions( $user ) {
$title = $this->file->getTitle();
$permissionErrors = array_merge(
- $title->getUserPermissionsErrors( 'edit' , $user ),
- $title->getUserPermissionsErrors( 'upload' , $user )
+ $title->getUserPermissionsErrors( 'edit', $user ),
+ $title->getUserPermissionsErrors( 'upload', $user )
);
if ( $permissionErrors ) {
@@ -191,12 +187,8 @@ class ApiFileRevert extends ApiBase {
public function getExamples() {
return array(
- 'api.php?action=filerevert&filename=Wiki.png&comment=Revert&archivename=20110305152740!Wiki.png&token=+\\'
+ 'api.php?action=filerevert&filename=Wiki.png&comment=Revert&archivename=20110305152740!Wiki.png&token=123ABC'
=> 'Revert Wiki.png to the version of 20110305152740',
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php
index 8ad9b8ca..d8aa1634 100644
--- a/includes/api/ApiFormatBase.php
+++ b/includes/api/ApiFormatBase.php
@@ -38,7 +38,7 @@ abstract class ApiFormatBase extends ApiBase {
* Constructor
* If $format ends with 'fm', pretty-print the output in HTML.
* @param $main ApiMain
- * @param $format string Format name
+ * @param string $format Format name
*/
public function __construct( $main, $format ) {
parent::__construct( $main, $format );
@@ -58,7 +58,7 @@ abstract class ApiFormatBase extends ApiBase {
* This method is not called if getIsHtml() returns true.
* @return string
*/
- public abstract function getMimeType();
+ abstract public function getMimeType();
/**
* Whether this formatter needs raw data such as _element tags
@@ -83,7 +83,7 @@ abstract class ApiFormatBase extends ApiBase {
* special-case fix that should be removed once the help has been
* reworked to use a fully HTML interface.
*
- * @param $b bool Whether or not ampersands should be escaped.
+ * @param bool $b Whether or not ampersands should be escaped.
*/
public function setUnescapeAmps ( $b ) {
$this->mUnescapeAmps = $b;
@@ -123,11 +123,13 @@ abstract class ApiFormatBase extends ApiBase {
/**
* Initialize the printer function and prepare the output headers, etc.
- * This method must be the first outputing method during execution.
- * A help screen's header is printed for the HTML-based output
- * @param $isError bool Whether an error message is printed
+ * This method must be the first outputting method during execution.
+ * A human-targeted notice about available formats is printed for the HTML-based output,
+ * except for help screens (caused by either an error in the API parameters,
+ * the calling of action=help, or requesting the root script api.php).
+ * @param bool $isHelpScreen Whether a help screen is going to be shown
*/
- function initPrinter( $isError ) {
+ function initPrinter( $isHelpScreen ) {
if ( $this->mDisabled ) {
return;
}
@@ -164,7 +166,7 @@ abstract class ApiFormatBase extends ApiBase {
<?php
- if ( !$isError ) {
+ if ( !$isHelpScreen ) {
?>
<br />
<small>
@@ -175,15 +177,18 @@ To see the non HTML representation of the <?php echo( $this->mFormat ); ?> forma
See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>, or
<a href='<?php echo( $script ); ?>'>API help</a> for more information.
</small>
+<pre style='white-space: pre-wrap;'>
<?php
- }
+ } else { // don't wrap the contents of the <pre> for help screens
+ // because these are actually formatted to rely on
+ // the monospaced font for layout purposes
?>
<pre>
<?php
-
+ }
}
}
@@ -248,7 +253,7 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
}
/**
- * Sets whether the pretty-printer should format *bold* and $italics$
+ * Sets whether the pretty-printer should format *bold*
* @param $help bool
*/
public function setHelp( $help = true ) {
@@ -264,22 +269,19 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
protected function formatHTML( $text ) {
// Escape everything first for full coverage
$text = htmlspecialchars( $text );
-
// encode all comments or tags as safe blue strings
$text = str_replace( '&lt;', '<span style="color:blue;">&lt;', $text );
$text = str_replace( '&gt;', '&gt;</span>', $text );
- // identify URLs
- $protos = wfUrlProtocolsWithoutProtRel();
- // This regex hacks around bug 13218 (&quot; included in the URL)
- $text = preg_replace( "#(((?i)$protos).*?)(&quot;)?([ \\'\"<>\n]|&lt;|&gt;|&quot;)#", '<a href="\\1">\\1</a>\\3\\4', $text );
// identify requests to api.php
$text = preg_replace( "#api\\.php\\?[^ <\n\t]+#", '<a href="\\0">\\0</a>', $text );
if ( $this->mHelp ) {
// make strings inside * bold
$text = preg_replace( "#\\*[^<>\n]+\\*#", '<b>\\0</b>', $text );
- // make strings inside $ italic
- $text = preg_replace( "#\\$[^<>\n]+\\$#", '<b><i>\\0</i></b>', $text );
}
+ // identify URLs
+ $protos = wfUrlProtocolsWithoutProtRel();
+ // This regex hacks around bug 13218 (&quot; included in the URL)
+ $text = preg_replace( "#(((?i)$protos).*?)(&quot;)?([ \\'\"<>\n]|&lt;|&gt;|&quot;)#", '<a href="\\1">\\1</a>\\3\\4', $text );
/**
* Temporary fix for bad links in help messages. As a special case,
@@ -308,10 +310,6 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
public function getDescription() {
return $this->getIsHtml() ? ' (pretty-print in HTML)' : '';
}
-
- public static function getBaseVersion() {
- return __CLASS__ . ': $Id$';
- }
}
/**
@@ -328,7 +326,7 @@ class ApiFormatFeedWrapper extends ApiFormatBase {
* Call this method to initialize output data. See execute()
* @param $result ApiResult
* @param $feed object an instance of one of the $wgFeedClasses classes
- * @param $feedItems array of FeedItem objects
+ * @param array $feedItems of FeedItem objects
*/
public static function setResult( $result, $feed, $feedItems ) {
// Store output in the Result data.
@@ -381,8 +379,4 @@ class ApiFormatFeedWrapper extends ApiFormatBase {
ApiBase::dieDebug( __METHOD__, 'Invalid feed class/item' );
}
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFormatDbg.php b/includes/api/ApiFormatDbg.php
index 3d2a39ca..1b2e02c9 100644
--- a/includes/api/ApiFormatDbg.php
+++ b/includes/api/ApiFormatDbg.php
@@ -30,10 +30,6 @@
*/
class ApiFormatDbg extends ApiFormatBase {
- public function __construct( $main, $format ) {
- parent::__construct( $main, $format );
- }
-
public function getMimeType() {
// This looks like it should be text/plain, but IE7 is so
// brain-damaged it tries to parse text/plain as HTML if it
@@ -48,8 +44,4 @@ class ApiFormatDbg extends ApiFormatBase {
public function getDescription() {
return 'Output data in PHP\'s var_export() format' . parent::getDescription();
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFormatDump.php b/includes/api/ApiFormatDump.php
index 0f055e13..62253e14 100644
--- a/includes/api/ApiFormatDump.php
+++ b/includes/api/ApiFormatDump.php
@@ -30,10 +30,6 @@
*/
class ApiFormatDump extends ApiFormatBase {
- public function __construct( $main, $format ) {
- parent::__construct( $main, $format );
- }
-
public function getMimeType() {
// This looks like it should be text/plain, but IE7 is so
// brain-damaged it tries to parse text/plain as HTML if it
@@ -52,8 +48,4 @@ class ApiFormatDump extends ApiFormatBase {
public function getDescription() {
return 'Output data in PHP\'s var_dump() format' . parent::getDescription();
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFormatJson.php b/includes/api/ApiFormatJson.php
index acbc7d3b..abb63480 100644
--- a/includes/api/ApiFormatJson.php
+++ b/includes/api/ApiFormatJson.php
@@ -85,13 +85,9 @@ class ApiFormatJson extends ApiFormatBase {
public function getDescription() {
if ( $this->mIsRaw ) {
- return 'Output data with the debuging elements in JSON format' . parent::getDescription();
+ return 'Output data with the debugging elements in JSON format' . parent::getDescription();
} else {
return 'Output data in JSON format' . parent::getDescription();
}
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/HttpFunctions.old.php b/includes/api/ApiFormatNone.php
index feb9b93c..78023af3 100644
--- a/includes/HttpFunctions.old.php
+++ b/includes/api/ApiFormatNone.php
@@ -1,6 +1,10 @@
<?php
/**
- * Class alias kept for backward compatibility.
+ *
+ *
+ * Created on Oct 22, 2006
+ *
+ * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@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
@@ -18,16 +22,22 @@
* http://www.gnu.org/copyleft/gpl.html
*
* @file
- * @ingroup HTTP
*/
/**
- * HttpRequest was renamed to MWHttpRequest in order
- * to prevent conflicts with PHP's HTTP extension
- * which also defines an HttpRequest class.
- * http://www.php.net/manual/en/class.httprequest.php
- *
- * This is for backwards compatibility.
- * @since 1.17
+ * API Serialized PHP output formatter
+ * @ingroup API
*/
-class HttpRequest extends MWHttpRequest { }
+class ApiFormatNone extends ApiFormatBase {
+
+ public function getMimeType() {
+ return 'text/plain';
+ }
+
+ public function execute() {
+ }
+
+ public function getDescription() {
+ return 'Output nothing' . parent::getDescription();
+ }
+}
diff --git a/includes/api/ApiFormatPhp.php b/includes/api/ApiFormatPhp.php
index fac2ca58..b2d1f044 100644
--- a/includes/api/ApiFormatPhp.php
+++ b/includes/api/ApiFormatPhp.php
@@ -30,10 +30,6 @@
*/
class ApiFormatPhp extends ApiFormatBase {
- public function __construct( $main, $format ) {
- parent::__construct( $main, $format );
- }
-
public function getMimeType() {
return 'application/vnd.php.serialized';
}
@@ -45,8 +41,4 @@ class ApiFormatPhp extends ApiFormatBase {
public function getDescription() {
return 'Output data in serialized PHP format' . parent::getDescription();
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFormatRaw.php b/includes/api/ApiFormatRaw.php
index 184f0a34..d278efa0 100644
--- a/includes/api/ApiFormatRaw.php
+++ b/includes/api/ApiFormatRaw.php
@@ -66,8 +66,4 @@ class ApiFormatRaw extends ApiFormatBase {
}
$this->printText( $data['text'] );
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFormatTxt.php b/includes/api/ApiFormatTxt.php
index 71414593..4130e70c 100644
--- a/includes/api/ApiFormatTxt.php
+++ b/includes/api/ApiFormatTxt.php
@@ -30,10 +30,6 @@
*/
class ApiFormatTxt extends ApiFormatBase {
- public function __construct( $main, $format ) {
- parent::__construct( $main, $format );
- }
-
public function getMimeType() {
// This looks like it should be text/plain, but IE7 is so
// brain-damaged it tries to parse text/plain as HTML if it
@@ -48,8 +44,4 @@ class ApiFormatTxt extends ApiFormatBase {
public function getDescription() {
return 'Output data in PHP\'s print_r() format' . parent::getDescription();
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFormatWddx.php b/includes/api/ApiFormatWddx.php
index 65056e44..62b69bb6 100644
--- a/includes/api/ApiFormatWddx.php
+++ b/includes/api/ApiFormatWddx.php
@@ -30,10 +30,6 @@
*/
class ApiFormatWddx extends ApiFormatBase {
- public function __construct( $main, $format ) {
- parent::__construct( $main, $format );
- }
-
public function getMimeType() {
return 'text/xml';
}
@@ -112,8 +108,4 @@ class ApiFormatWddx extends ApiFormatBase {
public function getDescription() {
return 'Output data in WDDX format' . parent::getDescription();
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFormatXml.php b/includes/api/ApiFormatXml.php
index 5ccf1859..b4e8e330 100644
--- a/includes/api/ApiFormatXml.php
+++ b/includes/api/ApiFormatXml.php
@@ -36,10 +36,6 @@ class ApiFormatXml extends ApiFormatBase {
private $mIncludeNamespace = false;
private $mXslt = null;
- public function __construct( $main, $format ) {
- parent::__construct( $main, $format );
- }
-
public function getMimeType() {
return 'text/xml';
}
@@ -92,7 +88,7 @@ class ApiFormatXml extends ApiFormatBase {
*
* @par Example:
* @verbatim
- * name='root', value = array( '_element'=>'page', 'x', 'y', 'z')
+ * name='root', value = array( '_element'=>'page', 'x', 'y', 'z')
* @endverbatim
* creates:
* @verbatim
@@ -105,7 +101,7 @@ class ApiFormatXml extends ApiFormatBase {
*
* @par Example:
* @verbatim
- * name='root', value = array( '*'=>'text', 'lang'=>'en', 'id'=>10)
+ * name='root', value = array( '*'=>'text', 'lang'=>'en', 'id'=>10)
* @endverbatim
* creates:
* @verbatim
@@ -205,7 +201,13 @@ class ApiFormatXml extends ApiFormatBase {
// ignore
break;
default:
- $retval .= $indstr . Xml::element( $elemName, null, $elemValue );
+ // to make sure null value doesn't produce unclosed element,
+ // which is what Xml::element( $elemName, null, null ) returns
+ if ( $elemValue === null ) {
+ $retval .= $indstr . Xml::element( $elemName );
+ } else {
+ $retval .= $indstr . Xml::element( $elemName, null, $elemValue );
+ }
break;
}
return $retval;
@@ -248,8 +250,4 @@ class ApiFormatXml extends ApiFormatBase {
public function getDescription() {
return 'Output data in XML format' . parent::getDescription();
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFormatYaml.php b/includes/api/ApiFormatYaml.php
index 730ad8ea..700d4a5e 100644
--- a/includes/api/ApiFormatYaml.php
+++ b/includes/api/ApiFormatYaml.php
@@ -35,10 +35,6 @@ class ApiFormatYaml extends ApiFormatJson {
}
public function getDescription() {
- return 'Output data in YAML format' . parent::getDescription();
- }
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
+ return 'Output data in YAML format' . ApiFormatBase::getDescription();
}
}
diff --git a/includes/api/ApiHelp.php b/includes/api/ApiHelp.php
index 2b5de21a..9cafc5bb 100644
--- a/includes/api/ApiHelp.php
+++ b/includes/api/ApiHelp.php
@@ -31,10 +31,6 @@
*/
class ApiHelp extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
/**
* Module for displaying help
*/
@@ -47,43 +43,62 @@ class ApiHelp extends ApiBase {
}
$this->getMain()->setHelp();
-
$result = $this->getResult();
- $queryObj = new ApiQuery( $this->getMain(), 'query' );
- $r = array();
- if ( is_array( $params['modules'] ) ) {
- $modArr = $this->getMain()->getModules();
- foreach ( $params['modules'] as $m ) {
- if ( !isset( $modArr[$m] ) ) {
- $r[] = array( 'name' => $m, 'missing' => '' );
- continue;
- }
- $module = new $modArr[$m]( $this->getMain(), $m );
-
- $r[] = $this->buildModuleHelp( $module, 'action' );
- }
+ if ( is_array( $params['modules'] ) ) {
+ $modules = $params['modules'];
+ } else {
+ $modules = array();
}
if ( is_array( $params['querymodules'] ) ) {
- $qmodArr = $queryObj->getModules();
+ $queryModules = $params['querymodules'];
+ foreach ( $queryModules as $m ) {
+ $modules[] = 'query+' . $m;
+ }
+ } else {
+ $queryModules = array();
+ }
- foreach ( $params['querymodules'] as $qm ) {
- if ( !isset( $qmodArr[$qm] ) ) {
- $r[] = array( 'name' => $qm, 'missing' => '' );
- continue;
+ $r = array();
+ foreach ( $modules as $m ) {
+ // sub-modules could be given in the form of "name[+name[+name...]]"
+ $subNames = explode( '+', $m );
+ if ( count( $subNames ) === 1 ) {
+ // In case the '+' was typed into URL, it resolves as a space
+ $subNames = explode( ' ', $m );
+ }
+ $module = $this->getMain();
+ for ( $i = 0; $i < count( $subNames ); $i++ ) {
+ $subs = $module->getModuleManager();
+ if ( $subs === null ) {
+ $module = null;
+ } else {
+ $module = $subs->getModule( $subNames[$i] );
}
- $module = new $qmodArr[$qm]( $this, $qm );
- $type = $queryObj->getModuleType( $qm );
-
- if ( $type === null ) {
- $r[] = array( 'name' => $qm, 'missing' => '' );
- continue;
+ if ( $module === null ) {
+ if ( count( $subNames ) === 2
+ && $i === 1
+ && $subNames[0] === 'query'
+ && in_array( $subNames[1], $queryModules )
+ ) {
+ // Legacy: This is one of the renamed 'querymodule=...' parameters,
+ // do not use '+' notation in the output, use submodule's name instead.
+ $name = $subNames[1];
+ } else {
+ $name = implode( '+', array_slice( $subNames, 0, $i + 1 ) );
+ }
+ $r[] = array( 'name' => $name, 'missing' => '' );
+ break;
+ } else {
+ $type = $subs->getModuleGroup( $subNames[$i] );
}
-
+ }
+ if ( $module !== null ) {
$r[] = $this->buildModuleHelp( $module, $type );
}
}
+
$result->setIndexedTagName( $r, 'module' );
$result->addValue( null, $this->getModuleName(), $r );
}
@@ -118,15 +133,16 @@ class ApiHelp extends ApiBase {
ApiBase::PARAM_ISMULTI => true
),
'querymodules' => array(
- ApiBase::PARAM_ISMULTI => true
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_DEPRECATED => true
),
);
}
public function getParamDescription() {
return array(
- 'modules' => 'List of module names (value of the action= parameter)',
- 'querymodules' => 'List of query module names (value of prop=, meta= or list= parameter)',
+ 'modules' => 'List of module names (value of the action= parameter). Can specify submodules with a \'+\'',
+ 'querymodules' => 'Use modules=query+value instead. List of query module names (value of prop=, meta= or list= parameter)',
);
}
@@ -138,9 +154,8 @@ class ApiHelp extends ApiBase {
return array(
'api.php?action=help' => 'Whole help page',
'api.php?action=help&modules=protect' => 'Module (action) help page',
- 'api.php?action=help&querymodules=categorymembers' => 'Query (list) modules help page',
- 'api.php?action=help&querymodules=info' => 'Query (prop) modules help page',
- 'api.php?action=help&querymodules=siteinfo' => 'Query (meta) modules help page',
+ 'api.php?action=help&modules=query+categorymembers' => 'Help for the query/categorymembers module',
+ 'api.php?action=help&modules=login|query+info' => 'Help for the login and query/info modules',
);
}
@@ -151,8 +166,4 @@ class ApiHelp extends ApiBase {
'https://www.mediawiki.org/wiki/API:Quick_start_guide',
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiImageRotate.php b/includes/api/ApiImageRotate.php
new file mode 100644
index 00000000..b2d75825
--- /dev/null
+++ b/includes/api/ApiImageRotate.php
@@ -0,0 +1,232 @@
+<?php
+/**
+ *
+ * Created on January 3rd, 2013
+ *
+ * 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
+ */
+
+class ApiImageRotate extends ApiBase {
+
+ private $mPageSet = null;
+
+ public function __construct( $main, $action ) {
+ parent::__construct( $main, $action );
+ }
+
+ /**
+ * Add all items from $values into the result
+ * @param array $result output
+ * @param array $values values to add
+ * @param string $flag the name of the boolean flag to mark this element
+ * @param string $name if given, name of the value
+ */
+ private static function addValues( array &$result, $values, $flag = null, $name = null ) {
+ foreach ( $values as $val ) {
+ if( $val instanceof Title ) {
+ $v = array();
+ ApiQueryBase::addTitleInfo( $v, $val );
+ } elseif( $name !== null ) {
+ $v = array( $name => $val );
+ } else {
+ $v = $val;
+ }
+ if( $flag !== null ) {
+ $v[$flag] = '';
+ }
+ $result[] = $v;
+ }
+ }
+
+
+ public function execute() {
+ $params = $this->extractRequestParams();
+ $rotation = $params[ 'rotation' ];
+ $user = $this->getUser();
+
+ $pageSet = $this->getPageSet();
+ $pageSet->execute();
+
+ $result = array();
+ $result = array();
+
+ self::addValues( $result, $pageSet->getInvalidTitles(), 'invalid', 'title' );
+ self::addValues( $result, $pageSet->getSpecialTitles(), 'special', 'title' );
+ self::addValues( $result, $pageSet->getMissingPageIDs(), 'missing', 'pageid' );
+ self::addValues( $result, $pageSet->getMissingRevisionIDs(), 'missing', 'revid' );
+ self::addValues( $result, $pageSet->getInterwikiTitlesAsResult() );
+
+ foreach ( $pageSet->getTitles() as $title ) {
+ $r = array();
+ $r['id'] = $title->getArticleID();
+ ApiQueryBase::addTitleInfo( $r, $title );
+ if ( !$title->exists() ) {
+ $r['missing'] = '';
+ }
+
+ $file = wfFindFile( $title );
+ if ( !$file ) {
+ $r['result'] = 'Failure';
+ $r['errormessage'] = 'File does not exist';
+ $result[] = $r;
+ continue;
+ }
+ $handler = $file->getHandler();
+ if ( !$handler || !$handler->canRotate() ) {
+ $r['result'] = 'Failure';
+ $r['errormessage'] = 'File type cannot be rotated';
+ $result[] = $r;
+ continue;
+ }
+
+ // Check whether we're allowed to rotate this file
+ $permError = $this->checkPermissions( $this->getUser(), $file->getTitle() );
+ if ( $permError !== null ) {
+ $r['result'] = 'Failure';
+ $r['errormessage'] = $permError;
+ $result[] = $r;
+ continue;
+ }
+
+ $srcPath = $file->getLocalRefPath();
+ if ( $srcPath === false ) {
+ $r['result'] = 'Failure';
+ $r['errormessage'] = 'Cannot get local file path';
+ $result[] = $r;
+ continue;
+ }
+ $ext = strtolower( pathinfo( "$srcPath", PATHINFO_EXTENSION ) );
+ $tmpFile = TempFSFile::factory( 'rotate_', $ext);
+ $dstPath = $tmpFile->getPath();
+ $err = $handler->rotate( $file, array(
+ "srcPath" => $srcPath,
+ "dstPath" => $dstPath,
+ "rotation"=> $rotation
+ ) );
+ if ( !$err ) {
+ $comment = wfMessage( 'rotate-comment' )->numParams( $rotation )->text();
+ $status = $file->upload( $dstPath,
+ $comment, $comment, 0, false, false, $this->getUser() );
+ if ( $status->isGood() ) {
+ $r['result'] = 'Success';
+ } else {
+ $r['result'] = 'Failure';
+ $r['errormessage'] = $this->getResult()->convertStatusToArray( $status );
+ }
+ } else {
+ $r['result'] = 'Failure';
+ $r['errormessage'] = $err->toText();
+ }
+ $result[] = $r;
+ }
+ $apiResult = $this->getResult();
+ $apiResult->setIndexedTagName( $result, 'page' );
+ $apiResult->addValue( null, $this->getModuleName(), $result );
+ }
+
+ /**
+ * Get a cached instance of an ApiPageSet object
+ * @return ApiPageSet
+ */
+ private function getPageSet() {
+ if ( $this->mPageSet === null ) {
+ $this->mPageSet = new ApiPageSet( $this, 0, NS_FILE );
+ }
+ return $this->mPageSet;
+ }
+
+ /**
+ * Checks that the user has permissions to perform rotations.
+ * @param $user User The user to check.
+ * @return string|null Permission error message, or null if there is no error
+ */
+ protected function checkPermissions( $user, $title ) {
+ $permissionErrors = array_merge(
+ $title->getUserPermissionsErrors( 'edit' , $user ),
+ $title->getUserPermissionsErrors( 'upload' , $user )
+ );
+
+ if ( $permissionErrors ) {
+ // Just return the first error
+ $msg = $this->parseMsg( $permissionErrors[0] );
+ return $msg['info'];
+ }
+
+ return null;
+ }
+
+ public function mustBePosted() {
+ return true;
+ }
+
+ public function isWriteMode() {
+ return true;
+ }
+
+ public function getAllowedParams( $flags = 0 ) {
+ $pageSet = $this->getPageSet();
+ $result = array(
+ 'rotation' => array(
+ ApiBase::PARAM_TYPE => array( '90', '180', '270' ),
+ ApiBase::PARAM_REQUIRED => true
+ ),
+ 'token' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true
+ ),
+ );
+ if ( $flags ) {
+ $result += $this->getPageSet()->getFinalParams( $flags );
+ }
+ return $result;
+ }
+
+ public function getParamDescription() {
+ $pageSet = $this->getPageSet();
+ return $pageSet->getParamDescription() + array(
+ 'rotation' => 'Degrees to rotate image clockwise',
+ 'token' => 'Edit token. You can get one of these through action=tokens',
+ );
+ }
+
+ public function getDescription() {
+ return 'Rotate one or more images';
+ }
+
+ public function needsToken() {
+ return true;
+ }
+
+ public function getTokenSalt() {
+ return '';
+ }
+
+ public function getPossibleErrors() {
+ $pageSet = $this->getPageSet();
+ return array_merge(
+ parent::getPossibleErrors(),
+ $pageSet->getPossibleErrors()
+ );
+ }
+
+ public function getExamples() {
+ return array(
+ 'api.php?action=imagerotate&titles=Example.jpg&rotation=90&token=123ABC',
+ );
+ }
+}
diff --git a/includes/api/ApiImport.php b/includes/api/ApiImport.php
index 637c1fff..1f0a5fab 100644
--- a/includes/api/ApiImport.php
+++ b/includes/api/ApiImport.php
@@ -31,10 +31,6 @@
*/
class ApiImport extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$user = $this->getUser();
$params = $this->extractRequestParams();
@@ -109,7 +105,9 @@ class ApiImport extends ApiBase {
ApiBase::PARAM_REQUIRED => true
),
'summary' => null,
- 'xml' => null,
+ 'xml' => array(
+ ApiBase::PARAM_TYPE => 'upload',
+ ),
'interwikisource' => array(
ApiBase::PARAM_TYPE => $wgImportSources
),
@@ -150,7 +148,7 @@ class ApiImport extends ApiBase {
public function getDescription() {
return array(
- 'Import a page from another wiki, or an XML file.' ,
+ 'Import a page from another wiki, or an XML file.',
'Note that the HTTP POST must be done as a file upload (i.e. using multipart/form-data) when',
'sending a file for the "xml" parameter.'
);
@@ -186,10 +184,6 @@ class ApiImport extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Import';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
/**
diff --git a/includes/api/ApiLogin.php b/includes/api/ApiLogin.php
index 1f91fe92..b936d3be 100644
--- a/includes/api/ApiLogin.php
+++ b/includes/api/ApiLogin.php
@@ -38,7 +38,7 @@ class ApiLogin extends ApiBase {
/**
* Executes the log-in attempt using the parameters passed. If
- * the log-in succeeeds, it attaches a cookie to the session
+ * the log-in succeeds, it attaches a cookie to the session
* and outputs the user id, username, and session token. If a
* log-in fails, as the result of a bad password, a nonexistent
* user, or any other reason, the host is cached with an expiry
@@ -147,7 +147,7 @@ class ApiLogin extends ApiBase {
case LoginForm::ABORTED:
$result['result'] = 'Aborted';
- $result['reason'] = $loginForm->mAbortLoginErrorMsg;
+ $result['reason'] = $loginForm->mAbortLoginErrorMsg;
break;
default:
@@ -278,8 +278,4 @@ class ApiLogin extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Login';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiLogout.php b/includes/api/ApiLogout.php
index b2f634d0..2ba92a63 100644
--- a/includes/api/ApiLogout.php
+++ b/includes/api/ApiLogout.php
@@ -32,10 +32,6 @@
*/
class ApiLogout extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$user = $this->getUser();
$oldName = $user->getName();
@@ -75,8 +71,4 @@ class ApiLogout extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Logout';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php
index 35febd95..80bca2f6 100644
--- a/includes/api/ApiMain.php
+++ b/includes/api/ApiMain.php
@@ -51,6 +51,7 @@ class ApiMain extends ApiBase {
private static $Modules = array(
'login' => 'ApiLogin',
'logout' => 'ApiLogout',
+ 'createaccount' => 'ApiCreateAccount',
'query' => 'ApiQuery',
'expandtemplates' => 'ApiExpandTemplates',
'parse' => 'ApiParse',
@@ -82,6 +83,7 @@ class ApiMain extends ApiBase {
'import' => 'ApiImport',
'userrights' => 'ApiUserrights',
'options' => 'ApiOptions',
+ 'imagerotate' =>'ApiImageRotate',
);
/**
@@ -105,6 +107,7 @@ class ApiMain extends ApiBase {
'dbgfm' => 'ApiFormatDbg',
'dump' => 'ApiFormatDump',
'dumpfm' => 'ApiFormatDump',
+ 'none' => 'ApiFormatNone',
);
/**
@@ -118,7 +121,7 @@ class ApiMain extends ApiBase {
'msg' => 'Use of the write API',
'params' => array()
),
- 'apihighlimits' => array(
+ 'apihighlimits' => array(
'msg' => 'Use higher limits in API queries (Slow queries: $1 results; Fast queries: $2 results). The limits for slow queries also apply to multivalue parameters.',
'params' => array( ApiBase::LIMIT_SML2, ApiBase::LIMIT_BIG2 )
)
@@ -129,18 +132,20 @@ class ApiMain extends ApiBase {
*/
private $mPrinter;
- private $mModules, $mModuleNames, $mFormats, $mFormatNames;
- private $mResult, $mAction, $mShowVersions, $mEnableWrite;
+ private $mModuleMgr, $mResult;
+ private $mAction;
+ private $mEnableWrite;
private $mInternalMode, $mSquidMaxage, $mModule;
private $mCacheMode = 'private';
private $mCacheControl = array();
+ private $mParamsUsed = array();
/**
* Constructs an instance of ApiMain that utilizes the module and format specified by $request.
*
* @param $context IContextSource|WebRequest - if this is an instance of FauxRequest, errors are thrown and no printing occurs
- * @param $enableWrite bool should be set to true if the api may modify data
+ * @param bool $enableWrite should be set to true if the api may modify data
*/
public function __construct( $context = null, $enableWrite = false ) {
if ( $context === null ) {
@@ -168,7 +173,7 @@ class ApiMain extends ApiBase {
// Remove all modules other than login
global $wgUser;
- if ( $this->getRequest()->getVal( 'callback' ) !== null ) {
+ if ( $this->getVal( 'callback' ) !== null ) {
// JSON callback allows cross-site reads.
// For safety, strip user credentials.
wfDebug( "API: stripping user credentials for JSON callback\n" );
@@ -177,15 +182,13 @@ class ApiMain extends ApiBase {
}
}
- global $wgAPIModules; // extension modules
- $this->mModules = $wgAPIModules + self::$Modules;
-
- $this->mModuleNames = array_keys( $this->mModules );
- $this->mFormats = self::$Formats;
- $this->mFormatNames = array_keys( $this->mFormats );
+ global $wgAPIModules;
+ $this->mModuleMgr = new ApiModuleManager( $this );
+ $this->mModuleMgr->addModules( self::$Modules, 'action' );
+ $this->mModuleMgr->addModules( $wgAPIModules, 'action' );
+ $this->mModuleMgr->addModules( self::$Formats, 'format' );
$this->mResult = new ApiResult( $this );
- $this->mShowVersions = false;
$this->mEnableWrite = $enableWrite;
$this->mSquidMaxage = - 1; // flag for executeActionWithErrorHandling()
@@ -242,7 +245,7 @@ class ApiMain extends ApiBase {
/**
* Set the type of caching headers which will be sent.
*
- * @param $mode String One of:
+ * @param string $mode One of:
* - 'public': Cache this object in public caches, if the maxage or smaxage
* parameter is set, or if setCacheMaxAge() was called. If a maximum age is
* not provided by any of these means, the object will be private.
@@ -271,7 +274,7 @@ class ApiMain extends ApiBase {
return;
}
- if ( !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) ) {
+ if ( !User::groupHasPermission( '*', 'read' ) ) {
// Private wiki, only private headers
if ( $mode !== 'private' ) {
wfDebug( __METHOD__ . ": ignoring request for $mode cache mode, private wiki\n" );
@@ -330,10 +333,11 @@ class ApiMain extends ApiBase {
* @return ApiFormatBase
*/
public function createPrinterByName( $format ) {
- if ( !isset( $this->mFormats[$format] ) ) {
+ $printer = $this->mModuleMgr->getModule( $format, 'format' );
+ if ( $printer === null ) {
$this->dieUsage( "Unrecognized format: {$format}", 'unknown_format' );
}
- return new $this->mFormats[$format] ( $this, $format );
+ return $printer;
}
/**
@@ -361,10 +365,17 @@ class ApiMain extends ApiBase {
return;
}
+ // Exit here if the request method was OPTIONS
+ // (assume there will be a followup GET or POST)
+ if ( $this->getRequest()->getMethod() === 'OPTIONS' ) {
+ return;
+ }
+
// In case an error occurs during data output,
// clear the output buffer and print just the error information
ob_start();
+ $t = microtime( true );
try {
$this->executeAction();
} catch ( Exception $e ) {
@@ -372,11 +383,16 @@ class ApiMain extends ApiBase {
wfRunHooks( 'ApiMain::onException', array( $this, $e ) );
// Log it
- if ( !( $e instanceof UsageException ) ) {
- wfDebugLog( 'exception', $e->getLogMessage() );
+ if ( $e instanceof MWException && !( $e instanceof UsageException ) ) {
+ global $wgLogExceptionBacktrace;
+ if ( $wgLogExceptionBacktrace ) {
+ wfDebugLog( 'exception', $e->getLogMessage() . "\n" . $e->getTraceAsString() . "\n" );
+ } else {
+ wfDebugLog( 'exception', $e->getLogMessage() );
+ }
}
- // Handle any kind of exception by outputing properly formatted error message.
+ // Handle any kind of exception by outputting properly formatted error message.
// If this fails, an unhandled exception should be thrown so that global error
// handler will process and log it.
@@ -401,6 +417,9 @@ class ApiMain extends ApiBase {
$this->printResult( true );
}
+ // Log the request whether or not there was an error
+ $this->logRequest( microtime( true ) - $t);
+
// Send cache headers after any code which might generate an error, to
// avoid sending public cache headers for errors.
$this->sendCacheHeaders();
@@ -461,9 +480,9 @@ class ApiMain extends ApiBase {
/**
* Attempt to match an Origin header against a set of rules and a set of exceptions
- * @param $value string Origin header
- * @param $rules array Set of wildcard rules
- * @param $exceptions array Set of wildcard rules
+ * @param string $value Origin header
+ * @param array $rules Set of wildcard rules
+ * @param array $exceptions Set of wildcard rules
* @return bool True if $value matches a rule in $rules and doesn't match any rules in $exceptions, false otherwise
*/
protected static function matchOrigin( $value, $rules, $exceptions ) {
@@ -486,7 +505,7 @@ class ApiMain extends ApiBase {
* '*' => '.*?'
* '?' => '.'
*
- * @param $wildcard string String with wildcards
+ * @param string $wildcard String with wildcards
* @return string Regular expression
*/
protected static function wildcardToRegex( $wildcard ) {
@@ -593,7 +612,7 @@ class ApiMain extends ApiBase {
if ( !isset ( $this->mPrinter ) ) {
// The printer has not been created yet. Try to manually get formatter value.
$value = $this->getRequest()->getVal( 'format', self::API_DEFAULT_FORMAT );
- if ( !in_array( $value, $this->mFormatNames ) ) {
+ if ( !$this->mModuleMgr->isDefined( $value, 'format' ) ) {
$value = self::API_DEFAULT_FORMAT;
}
@@ -611,7 +630,6 @@ class ApiMain extends ApiBase {
if ( $this->mPrinter->getWantsHelp() || $this->mAction == 'help' ) {
ApiResult::setContent( $errMessage, $this->makeHelpMsg() );
}
-
} else {
global $wgShowSQLErrors, $wgShowExceptionDetails;
// Something is seriously wrong
@@ -628,6 +646,10 @@ class ApiMain extends ApiBase {
ApiResult::setContent( $errMessage, $wgShowExceptionDetails ? "\n\n{$e->getTraceAsString()}\n\n" : '' );
}
+ // Remember all the warnings to re-add them later
+ $oldResult = $result->getData();
+ $warnings = isset( $oldResult['warnings'] ) ? $oldResult['warnings'] : null;
+
$result->reset();
$result->disableSizeCheck();
// Re-add the id
@@ -635,11 +657,13 @@ class ApiMain extends ApiBase {
if ( !is_null( $requestid ) ) {
$result->addValue( null, 'requestid', $requestid );
}
-
if ( $wgShowHostnames ) {
// servedby is especially useful when debugging errors
$result->addValue( null, 'servedby', wfHostName() );
}
+ if ( $warnings !== null ) {
+ $result->addValue( null, 'warnings', $warnings );
+ }
$result->addValue( null, 'error', $errMessage );
@@ -669,7 +693,6 @@ class ApiMain extends ApiBase {
$params = $this->extractRequestParams();
- $this->mShowVersions = $params['version'];
$this->mAction = $params['action'];
if ( !is_string( $this->mAction ) ) {
@@ -685,9 +708,10 @@ class ApiMain extends ApiBase {
*/
protected function setupModule() {
// Instantiate the module requested by the user
- $module = new $this->mModules[$this->mAction] ( $this, $this->mAction );
- $this->mModule = $module;
-
+ $module = $this->mModuleMgr->getModule( $this->mAction, 'action' );
+ if ( $module === null ) {
+ $this->dieUsage( 'The API requires a valid action parameter', 'unknown_action' );
+ }
$moduleParams = $module->extractRequestParams();
// Die if token required, but not provided (unless there is a gettoken parameter)
@@ -713,7 +737,7 @@ class ApiMain extends ApiBase {
/**
* Check the max lag if necessary
* @param $module ApiBase object: Api module being used
- * @param $params Array an array containing the request parameters.
+ * @param array $params an array containing the request parameters.
* @return boolean True on success, false should exit immediately
*/
protected function checkMaxLag( $module, $params ) {
@@ -745,7 +769,7 @@ class ApiMain extends ApiBase {
*/
protected function checkExecutePermissions( $module ) {
$user = $this->getUser();
- if ( $module->isReadMode() && !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) &&
+ if ( $module->isReadMode() && !User::groupHasPermission( '*', 'read' ) &&
!$user->isAllowed( 'read' ) )
{
$this->dieUsageMsg( 'readrequired' );
@@ -772,12 +796,13 @@ class ApiMain extends ApiBase {
/**
* Check POST for external response and setup result printer
* @param $module ApiBase An Api module
- * @param $params Array an array with the request parameters
+ * @param array $params an array with the request parameters
*/
protected function setupExternalResponse( $module, $params ) {
- // Ignore mustBePosted() for internal calls
- if ( $module->mustBePosted() && !$this->getRequest()->wasPosted() ) {
- $this->dieUsageMsg( array( 'mustbeposted', $this->mAction ) );
+ if ( !$this->getRequest()->wasPosted() && $module->mustBePosted() ) {
+ // Module requires POST. GET request might still be allowed
+ // if $wgDebugApi is true, otherwise fail.
+ $this->dieUsageMsgOrDebug( array( 'mustbeposted', $this->mAction ) );
}
// See if custom printer is used
@@ -798,6 +823,7 @@ class ApiMain extends ApiBase {
protected function executeAction() {
$params = $this->setupExecuteAction();
$module = $this->setupModule();
+ $this->mModule = $module;
$this->checkExecutePermissions( $module );
@@ -815,6 +841,8 @@ class ApiMain extends ApiBase {
wfRunHooks( 'APIAfterExecute', array( &$module ) );
$module->profileOut();
+ $this->reportUnusedParams();
+
if ( !$this->mInternalMode ) {
//append Debug information
MWDebug::appendDebugInfoToApiResult( $this->getContext(), $this->getResult() );
@@ -825,11 +853,120 @@ class ApiMain extends ApiBase {
}
/**
+ * Log the preceding request
+ * @param $time Time in seconds
+ */
+ protected function logRequest( $time ) {
+ $request = $this->getRequest();
+ $milliseconds = $time === null ? '?' : round( $time * 1000 );
+ $s = 'API' .
+ ' ' . $request->getMethod() .
+ ' ' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) .
+ ' ' . $request->getIP() .
+ ' T=' . $milliseconds .'ms';
+ foreach ( $this->getParamsUsed() as $name ) {
+ $value = $request->getVal( $name );
+ if ( $value === null ) {
+ continue;
+ }
+ $s .= ' ' . $name . '=';
+ if ( strlen( $value ) > 256 ) {
+ $encValue = $this->encodeRequestLogValue( substr( $value, 0, 256 ) );
+ $s .= $encValue . '[...]';
+ } else {
+ $s .= $this->encodeRequestLogValue( $value );
+ }
+ }
+ $s .= "\n";
+ wfDebugLog( 'api', $s, false );
+ }
+
+ /**
+ * Encode a value in a format suitable for a space-separated log line.
+ */
+ protected function encodeRequestLogValue( $s ) {
+ static $table;
+ if ( !$table ) {
+ $chars = ';@$!*(),/:';
+ for ( $i = 0; $i < strlen( $chars ); $i++ ) {
+ $table[rawurlencode( $chars[$i] )] = $chars[$i];
+ }
+ }
+ return strtr( rawurlencode( $s ), $table );
+ }
+
+ /**
+ * Get the request parameters used in the course of the preceding execute() request
+ */
+ protected function getParamsUsed() {
+ return array_keys( $this->mParamsUsed );
+ }
+
+ /**
+ * Get a request value, and register the fact that it was used, for logging.
+ */
+ public function getVal( $name, $default = null ) {
+ $this->mParamsUsed[$name] = true;
+ return $this->getRequest()->getVal( $name, $default );
+ }
+
+ /**
+ * Get a boolean request value, and register the fact that the parameter
+ * was used, for logging.
+ */
+ public function getCheck( $name ) {
+ $this->mParamsUsed[$name] = true;
+ return $this->getRequest()->getCheck( $name );
+ }
+
+ /**
+ * Get a request upload, and register the fact that it was used, for logging.
+ *
+ * @since 1.21
+ * @param string $name Parameter name
+ * @return WebRequestUpload
+ */
+ public function getUpload( $name ) {
+ $this->mParamsUsed[$name] = true;
+ return $this->getRequest()->getUpload( $name );
+ }
+
+ /**
+ * Report unused parameters, so the client gets a hint in case it gave us parameters we don't know,
+ * for example in case of spelling mistakes or a missing 'g' prefix for generators.
+ */
+ protected function reportUnusedParams() {
+ $paramsUsed = $this->getParamsUsed();
+ $allParams = $this->getRequest()->getValueNames();
+
+ if ( !$this->mInternalMode ) {
+ // Printer has not yet executed; don't warn that its parameters are unused
+ $printerParams = array_map(
+ array( $this->mPrinter, 'encodeParamName' ),
+ array_keys( $this->mPrinter->getFinalParams() ?: array() )
+ );
+ $unusedParams = array_diff( $allParams, $paramsUsed, $printerParams );
+ } else {
+ $unusedParams = array_diff( $allParams, $paramsUsed );
+ }
+
+ if( count( $unusedParams ) ) {
+ $s = count( $unusedParams ) > 1 ? 's' : '';
+ $this->setWarning( "Unrecognized parameter$s: '" . implode( $unusedParams, "', '" ) . "'" );
+ }
+ }
+
+ /**
* Print results using the current printer
*
* @param $isError bool
*/
protected function printResult( $isError ) {
+ global $wgDebugAPI;
+ if( $wgDebugAPI !== false ) {
+ $this->setWarning( 'SECURITY WARNING: $wgDebugAPI is enabled' );
+ }
+
$this->getResult()->cleanUpUTF8();
$printer = $this->mPrinter;
$printer->profileIn();
@@ -839,10 +976,10 @@ class ApiMain extends ApiBase {
* tell the printer not to escape ampersands so that our links do
* not break.
*/
- $printer->setUnescapeAmps( ( $this->mAction == 'help' || $isError )
- && $printer->getFormat() == 'XML' && $printer->getIsHtml() );
+ $isHelp = $isError || $this->mAction == 'help';
+ $printer->setUnescapeAmps( $isHelp && $printer->getFormat() == 'XML' && $printer->getIsHtml() );
- $printer->initPrinter( $isError );
+ $printer->initPrinter( $isHelp );
$printer->execute();
$printer->closePrinter();
@@ -865,13 +1002,12 @@ class ApiMain extends ApiBase {
return array(
'format' => array(
ApiBase::PARAM_DFLT => ApiMain::API_DEFAULT_FORMAT,
- ApiBase::PARAM_TYPE => $this->mFormatNames
+ ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'format' )
),
'action' => array(
ApiBase::PARAM_DFLT => 'help',
- ApiBase::PARAM_TYPE => $this->mModuleNames
+ ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'action' )
),
- 'version' => false,
'maxlag' => array(
ApiBase::PARAM_TYPE => 'integer'
),
@@ -898,12 +1034,11 @@ class ApiMain extends ApiBase {
return array(
'format' => 'The format of the output',
'action' => 'What action you would like to perform. See below for module help',
- 'version' => 'When showing help, include version for each module',
'maxlag' => array(
'Maximum lag can be used when MediaWiki is installed on a database replicated cluster.',
'To save actions causing any more site replication lag, this parameter can make the client',
'wait until the replication lag is less than the specified value.',
- 'In case of a replag error, a HTTP 503 error is returned, with the message like',
+ 'In case of a replag error, error code "maxlag" is returned, with the message like',
'"Waiting for $host: $lag seconds lagged\n".',
'See https://www.mediawiki.org/wiki/Manual:Maxlag_parameter for more information',
),
@@ -984,11 +1119,11 @@ class ApiMain extends ApiBase {
protected function getCredits() {
return array(
'API developers:',
- ' Roan Kattouw "<Firstname>.<Lastname>@gmail.com" (lead developer Sep 2007-present)',
+ ' Roan Kattouw "<Firstname>.<Lastname>@gmail.com" (lead developer Sep 2007-2009)',
' Victor Vasiliev - vasilvv at gee mail dot com',
' Bryan Tong Minh - bryan . tongminh @ gmail . com',
' Sam Reed - sam @ reedyboy . net',
- ' Yuri Astrakhan "<Firstname><Lastname>@gmail.com" (creator, lead developer Sep 2006-Sep 2007)',
+ ' Yuri Astrakhan "<Firstname><Lastname>@gmail.com" (creator, lead developer Sep 2006-Sep 2007, 2012-present)',
'',
'Please send your comments, suggestions and questions to mediawiki-api@lists.wikimedia.org',
'or file a bug report at https://bugzilla.wikimedia.org/'
@@ -1014,8 +1149,7 @@ class ApiMain extends ApiBase {
$this->setHelp();
// Get help text from cache if present
$key = wfMemcKey( 'apihelp', $this->getModuleName(),
- SpecialVersion::getVersion( 'nodb' ) .
- $this->getShowVersions() );
+ SpecialVersion::getVersion( 'nodb' ) );
if ( $wgAPICacheHelpTimeout > 0 ) {
$cached = $wgMemc->get( $key );
if ( $cached ) {
@@ -1040,9 +1174,11 @@ class ApiMain extends ApiBase {
$astriks = str_repeat( '*** ', 14 );
$msg .= "\n\n$astriks Modules $astriks\n\n";
- foreach ( array_keys( $this->mModules ) as $moduleName ) {
- $module = new $this->mModules[$moduleName] ( $this, $moduleName );
+
+ foreach ( $this->mModuleMgr->getNames( 'action' ) as $name ) {
+ $module = $this->mModuleMgr->getModule( $name );
$msg .= self::makeHelpMsgHeader( $module, 'action' );
+
$msg2 = $module->makeHelpMsg();
if ( $msg2 !== false ) {
$msg .= $msg2;
@@ -1053,14 +1189,13 @@ class ApiMain extends ApiBase {
$msg .= "\n$astriks Permissions $astriks\n\n";
foreach ( self::$mRights as $right => $rightMsg ) {
$groups = User::getGroupsWithPermission( $right );
- $msg .= "* " . $right . " *\n " . wfMsgReplaceArgs( $rightMsg[ 'msg' ], $rightMsg[ 'params' ] ) .
+ $msg .= "* " . $right . " *\n " . wfMsgReplaceArgs( $rightMsg['msg'], $rightMsg['params'] ) .
"\nGranted to:\n " . str_replace( '*', 'all', implode( ', ', $groups ) ) . "\n\n";
-
}
$msg .= "\n$astriks Formats $astriks\n\n";
- foreach ( array_keys( $this->mFormats ) as $formatName ) {
- $module = $this->createPrinterByName( $formatName );
+ foreach ( $this->mModuleMgr->getNames( 'format' ) as $name ) {
+ $module = $this->mModuleMgr->getModule( $name );
$msg .= self::makeHelpMsgHeader( $module, 'format' );
$msg2 = $module->makeHelpMsg();
if ( $msg2 !== false ) {
@@ -1076,7 +1211,7 @@ class ApiMain extends ApiBase {
/**
* @param $module ApiBase
- * @param $paramName String What type of request is this? e.g. action, query, list, prop, meta, format
+ * @param string $paramName What type of request is this? e.g. action, query, list, prop, meta, format
* @return string
*/
public static function makeHelpMsgHeader( $module, $paramName ) {
@@ -1105,25 +1240,19 @@ class ApiMain extends ApiBase {
/**
* Check whether the user wants us to show version information in the API help
* @return bool
+ * @deprecated since 1.21, always returns false
*/
public function getShowVersions() {
- return $this->mShowVersions;
+ wfDeprecated( __METHOD__, '1.21' );
+ return false;
}
/**
- * Returns the version information of this file, plus it includes
- * the versions for all files that are not callable proper API modules
- *
- * @return array
+ * Overrides to return this instance's module manager.
+ * @return ApiModuleManager
*/
- public function getVersion() {
- $vers = array();
- $vers[] = 'MediaWiki: ' . SpecialVersion::getVersion() . "\n https://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/";
- $vers[] = __CLASS__ . ': $Id$';
- $vers[] = ApiBase::getBaseVersion();
- $vers[] = ApiFormatBase::getBaseVersion();
- $vers[] = ApiQueryBase::getBaseVersion();
- return $vers;
+ public function getModuleManager() {
+ return $this->mModuleMgr;
}
/**
@@ -1131,40 +1260,44 @@ class ApiMain extends ApiBase {
* classes who wish to add their own modules to their lexicon or override the
* behavior of inherent ones.
*
- * @param $mdlName String The identifier for this module.
- * @param $mdlClass String The class where this module is implemented.
+ * @deprecated since 1.21, Use getModuleManager()->addModule() instead.
+ * @param string $name The identifier for this module.
+ * @param $class ApiBase The class where this module is implemented.
*/
- protected function addModule( $mdlName, $mdlClass ) {
- $this->mModules[$mdlName] = $mdlClass;
+ protected function addModule( $name, $class ) {
+ $this->getModuleManager()->addModule( $name, 'action', $class );
}
/**
* Add or overwrite an output format for this ApiMain. Intended for use by extending
* classes who wish to add to or modify current formatters.
*
- * @param $fmtName string The identifier for this format.
- * @param $fmtClass ApiFormatBase The class implementing this format.
+ * @deprecated since 1.21, Use getModuleManager()->addModule() instead.
+ * @param string $name The identifier for this format.
+ * @param $class ApiFormatBase The class implementing this format.
*/
- protected function addFormat( $fmtName, $fmtClass ) {
- $this->mFormats[$fmtName] = $fmtClass;
+ protected function addFormat( $name, $class ) {
+ $this->getModuleManager()->addModule( $name, 'format', $class );
}
/**
* Get the array mapping module names to class names
+ * @deprecated since 1.21, Use getModuleManager()'s methods instead.
* @return array
*/
function getModules() {
- return $this->mModules;
+ return $this->getModuleManager()->getNamesWithClasses( 'action' );
}
/**
* Returns the list of supported formats in form ( 'format' => 'ClassName' )
*
* @since 1.18
+ * @deprecated since 1.21, Use getModuleManager()'s methods instead.
* @return array
*/
public function getFormats() {
- return $this->mFormats;
+ return $this->getModuleManager()->getNamesWithClasses( 'format' );
}
}
diff --git a/includes/api/ApiModuleManager.php b/includes/api/ApiModuleManager.php
new file mode 100644
index 00000000..100392bf
--- /dev/null
+++ b/includes/api/ApiModuleManager.php
@@ -0,0 +1,171 @@
+<?php
+/**
+ *
+ *
+ * Created on Dec 27, 2012
+ *
+ * Copyright © 2012 Yuri Astrakhan "<Firstname><Lastname>@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
+ * @since 1.21
+ */
+
+/**
+ * This class holds a list of modules and handles instantiation
+ *
+ * @since 1.21
+ * @ingroup API
+ */
+class ApiModuleManager extends ContextSource {
+
+ private $mParent;
+ private $mInstances = array();
+ private $mGroups = array();
+ private $mModules = array();
+
+ /**
+ * Construct new module manager
+ * @param ApiBase $parentModule Parent module instance will be used during instantiation
+ */
+ public function __construct( ApiBase $parentModule ) {
+ $this->mParent = $parentModule;
+ }
+
+ /**
+ * Add a list of modules to the manager
+ * @param array $modules A map of ModuleName => ModuleClass
+ * @param string $group Which group modules belong to (action,format,...)
+ */
+ public function addModules( array $modules, $group ) {
+ foreach ( $modules as $name => $class ) {
+ $this->addModule( $name, $group, $class );
+ }
+ }
+
+ /**
+ * Add or overwrite a module in this ApiMain instance. Intended for use by extending
+ * classes who wish to add their own modules to their lexicon or override the
+ * behavior of inherent ones.
+ *
+ * @param string $group Name of the module group
+ * @param string $name The identifier for this module.
+ * @param string $class The class where this module is implemented.
+ */
+ public function addModule( $name, $group, $class ) {
+ $this->mGroups[$group] = null;
+ $this->mModules[$name] = array( $group, $class );
+ }
+
+ /**
+ * Get module instance by name, or instantiate it if it does not exist
+ * @param string $moduleName module name
+ * @param string $group optionally validate that the module is in a specific group
+ * @param bool $ignoreCache if true, force-creates a new instance and does not cache it
+ * @return mixed the new module instance, or null if failed
+ */
+ public function getModule( $moduleName, $group = null, $ignoreCache = false ) {
+ if ( !isset( $this->mModules[$moduleName] ) ) {
+ return null;
+ }
+ $grpCls = $this->mModules[$moduleName];
+ if ( $group !== null && $grpCls[0] !== $group ) {
+ return null;
+ }
+ if ( !$ignoreCache && isset( $this->mInstances[$moduleName] ) ) {
+ // already exists
+ return $this->mInstances[$moduleName];
+ } else {
+ // new instance
+ $class = $grpCls[1];
+ $instance = new $class ( $this->mParent, $moduleName );
+ if ( !$ignoreCache ) {
+ // cache this instance in case it is needed later
+ $this->mInstances[$moduleName] = $instance;
+ }
+ return $instance;
+ }
+ }
+
+ /**
+ * Get an array of modules in a specific group or all if no group is set.
+ * @param string $group optional group filter
+ * @return array list of module names
+ */
+ public function getNames( $group = null ) {
+ if ( $group === null ) {
+ return array_keys( $this->mModules );
+ }
+ $result = array();
+ foreach ( $this->mModules as $name => $grpCls ) {
+ if ( $grpCls[0] === $group ) {
+ $result[] = $name;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Create an array of (moduleName => moduleClass) for a specific group or for all.
+ * @param string $group name of the group to get or null for all
+ * @return array name=>class map
+ */
+ public function getNamesWithClasses( $group = null ) {
+ $result = array();
+ foreach ( $this->mModules as $name => $grpCls ) {
+ if ( $group === null || $grpCls[0] === $group ) {
+ $result[$name] = $grpCls[1];
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Returns true if the specific module is defined at all or in a specific group.
+ * @param string $moduleName module name
+ * @param string $group group name to check against, or null to check all groups,
+ * @return boolean true if defined
+ */
+ public function isDefined( $moduleName, $group = null ) {
+ if ( isset( $this->mModules[$moduleName] ) ) {
+ return $group === null || $this->mModules[$moduleName][0] === $group;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the group name for the given module
+ * @param string $moduleName
+ * @return string group name or null if missing
+ */
+ public function getModuleGroup( $moduleName ) {
+ if ( isset( $this->mModules[$moduleName] ) ) {
+ return $this->mModules[$moduleName][0];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get a list of groups this manager contains.
+ * @return array
+ */
+ public function getGroups() {
+ return array_keys( $this->mGroups );
+ }
+}
diff --git a/includes/api/ApiMove.php b/includes/api/ApiMove.php
index 9d73562b..3e846e3b 100644
--- a/includes/api/ApiMove.php
+++ b/includes/api/ApiMove.php
@@ -30,10 +30,6 @@
*/
class ApiMove extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$user = $this->getUser();
$params = $this->extractRequestParams();
@@ -42,7 +38,7 @@ class ApiMove extends ApiBase {
if ( isset( $params['from'] ) ) {
$fromTitle = Title::newFromText( $params['from'] );
- if ( !$fromTitle ) {
+ if ( !$fromTitle || $fromTitle->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['from'] ) );
}
} elseif ( isset( $params['fromid'] ) ) {
@@ -58,7 +54,7 @@ class ApiMove extends ApiBase {
$fromTalk = $fromTitle->getTalkPage();
$toTitle = Title::newFromText( $params['to'] );
- if ( !$toTitle ) {
+ if ( !$toTitle || $toTitle->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['to'] ) );
}
$toTalk = $toTitle->getTalkPage();
@@ -82,9 +78,16 @@ class ApiMove extends ApiBase {
}
$r = array( 'from' => $fromTitle->getPrefixedText(), 'to' => $toTitle->getPrefixedText(), 'reason' => $params['reason'] );
- if ( !$params['noredirect'] || !$user->isAllowed( 'suppressredirect' ) ) {
+
+ if ( $fromTitle->exists() ) {
+ //NOTE: we assume that if the old title exists, it's because it was re-created as
+ // a redirect to the new title. This is not safe, but what we did before was
+ // even worse: we just determined whether a redirect should have been created,
+ // and reported that it was created if it should have, without any checks.
+ // Also note that isRedirect() is unreliable because of bug 37209.
$r['redirectcreated'] = '';
}
+
if( $toTitleExists ) {
$r['moveoverredirect'] = '';
}
@@ -122,7 +125,7 @@ class ApiMove extends ApiBase {
}
}
- $watch = "preferences";
+ $watch = 'preferences';
if ( isset( $params['watchlist'] ) ) {
$watch = $params['watchlist'];
} elseif ( $params['watch'] ) {
@@ -288,15 +291,11 @@ class ApiMove extends ApiBase {
public function getExamples() {
return array(
- 'api.php?action=move&from=Exampel&to=Example&token=123ABC&reason=Misspelled%20title&movetalk=&noredirect='
+ 'api.php?action=move&from=Badtitle&to=Goodtitle&token=123ABC&reason=Misspelled%20title&movetalk=&noredirect='
);
}
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Move';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiOpenSearch.php b/includes/api/ApiOpenSearch.php
index ef562741..caf361ac 100644
--- a/includes/api/ApiOpenSearch.php
+++ b/includes/api/ApiOpenSearch.php
@@ -29,10 +29,6 @@
*/
class ApiOpenSearch extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function getCustomPrinter() {
return $this->getMain()->createPrinterByName( 'json' );
}
@@ -123,8 +119,4 @@ class ApiOpenSearch extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Opensearch';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiOptions.php b/includes/api/ApiOptions.php
index 265c2ccb..8c996a26 100644
--- a/includes/api/ApiOptions.php
+++ b/includes/api/ApiOptions.php
@@ -25,17 +25,13 @@
*/
/**
-* API module that facilitates the changing of user's preferences.
-* Requires API write mode to be enabled.
-*
+ * API module that facilitates the changing of user's preferences.
+ * Requires API write mode to be enabled.
+ *
* @ingroup API
*/
class ApiOptions extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
/**
* Changes preferences of the current user.
*/
@@ -54,7 +50,7 @@ class ApiOptions extends ApiBase {
}
if ( $params['reset'] ) {
- $user->resetOptions();
+ $user->resetOptions( $params['resetkinds'] );
$changed = true;
}
@@ -74,13 +70,36 @@ class ApiOptions extends ApiBase {
}
$prefs = Preferences::getPreferences( $user, $this->getContext() );
+ $prefsKinds = $user->getOptionKinds( $this->getContext(), $changes );
+
foreach ( $changes as $key => $value ) {
- if ( !isset( $prefs[$key] ) ) {
- $this->setWarning( "Not a valid preference: $key" );
- continue;
+ switch ( $prefsKinds[$key] ) {
+ case 'registered':
+ // Regular option.
+ $field = HTMLForm::loadInputFromParameters( $key, $prefs[$key] );
+ $validation = $field->validate( $value, $user->getOptions() );
+ break;
+ case 'registered-multiselect':
+ case 'registered-checkmatrix':
+ // A key for a multiselect or checkmatrix option.
+ $validation = true;
+ $value = $value !== null ? (bool) $value : null;
+ break;
+ case 'userjs':
+ // Allow non-default preferences prefixed with 'userjs-', to be set by user scripts
+ if ( strlen( $key ) > 255 ) {
+ $validation = "key too long (no more than 255 bytes allowed)";
+ } elseif ( preg_match( "/[^a-zA-Z0-9_-]/", $key ) !== 0 ) {
+ $validation = "invalid key (only a-z, A-Z, 0-9, _, - allowed)";
+ } else {
+ $validation = true;
+ }
+ break;
+ case 'unused':
+ default:
+ $validation = "not a valid preference";
+ break;
}
- $field = HTMLForm::loadInputFromParameters( $key, $prefs[$key] );
- $validation = $field->validate( $value, $user->getOptions() );
if ( $validation === true ) {
$user->setOption( $key, $value );
$changed = true;
@@ -106,12 +125,20 @@ class ApiOptions extends ApiBase {
}
public function getAllowedParams() {
+ $optionKinds = User::listOptionKinds();
+ $optionKinds[] = 'all';
+
return array(
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'reset' => false,
+ 'resetkinds' => array(
+ ApiBase::PARAM_TYPE => $optionKinds,
+ ApiBase::PARAM_DFLT => 'all',
+ ApiBase::PARAM_ISMULTI => true
+ ),
'change' => array(
ApiBase::PARAM_ISMULTI => true,
),
@@ -139,15 +166,20 @@ class ApiOptions extends ApiBase {
public function getParamDescription() {
return array(
'token' => 'An options token previously obtained through the action=tokens',
- 'reset' => 'Resets all preferences to the site defaults',
- 'change' => 'List of changes, formatted name=value (e.g. skin=vector), value cannot contain pipe characters',
+ 'reset' => 'Resets preferences to the site defaults',
+ 'resetkinds' => 'List of types of options to reset when the "reset" option is set',
+ 'change' => 'List of changes, formatted name=value (e.g. skin=vector), value cannot contain pipe characters. If no value is given (not even an equals sign), e.g., optionname|otheroption|..., the option will be reset to its default value',
'optionname' => 'A name of a option which should have an optionvalue set',
'optionvalue' => 'A value of the option specified by the optionname, can contain pipe characters',
);
}
public function getDescription() {
- return 'Change preferences of the current user';
+ return array(
+ 'Change preferences of the current user',
+ 'Only options which are registered in core or in one of installed extensions,',
+ 'or as options with keys prefixed with \'userjs-\' (intended to be used by user scripts), can be set.'
+ );
}
public function getPossibleErrors() {
@@ -176,8 +208,4 @@ class ApiOptions extends ApiBase {
'api.php?action=options&reset=&change=skin=monobook&optionname=nickname&optionvalue=[[User:Beau|Beau]]%20([[User_talk:Beau|talk]])&token=123ABC',
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php
index 0f5be6b2..074efe4b 100644
--- a/includes/api/ApiPageSet.php
+++ b/includes/api/ApiPageSet.php
@@ -4,7 +4,7 @@
*
* Created on Sep 24, 2006
*
- * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
+ * Copyright © 2006, 2013 Yuri Astrakhan "<Firstname><Lastname>@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
@@ -36,52 +36,177 @@
* the second instance for all their work.
*
* @ingroup API
+ * @since 1.21 derives from ApiBase instead of ApiQueryBase
*/
-class ApiPageSet extends ApiQueryBase {
+class ApiPageSet extends ApiBase {
- private $mAllPages; // [ns][dbkey] => page_id or negative when missing
- private $mTitles, $mGoodTitles, $mMissingTitles, $mInvalidTitles;
- private $mMissingPageIDs, $mRedirectTitles, $mSpecialTitles;
- private $mNormalizedTitles, $mInterwikiTitles;
- private $mResolveRedirects, $mPendingRedirectIDs;
- private $mConvertTitles, $mConvertedTitles;
- private $mGoodRevIDs, $mMissingRevIDs;
- private $mFakePageId;
-
- private $mRequestedPageFields;
+ /**
+ * Constructor flag: The new instance of ApiPageSet will ignore the 'generator=' parameter
+ * @since 1.21
+ */
+ const DISABLE_GENERATORS = 1;
+
+ private $mDbSource;
+ private $mParams;
+ private $mResolveRedirects;
+ private $mConvertTitles;
+ private $mAllowGenerator;
+
+ private $mAllPages = array(); // [ns][dbkey] => page_id or negative when missing
+ private $mTitles = array();
+ private $mGoodTitles = array();
+ private $mMissingTitles = array();
+ private $mInvalidTitles = array();
+ private $mMissingPageIDs = array();
+ private $mRedirectTitles = array();
+ private $mSpecialTitles = array();
+ private $mNormalizedTitles = array();
+ private $mInterwikiTitles = array();
+ private $mPendingRedirectIDs = array();
+ private $mConvertedTitles = array();
+ private $mGoodRevIDs = array();
+ private $mMissingRevIDs = array();
+ private $mFakePageId = -1;
+ private $mCacheMode = 'public';
+ private $mRequestedPageFields = array();
+ private $mDefaultNamespace = NS_MAIN;
/**
* Constructor
- * @param $query ApiBase
- * @param $resolveRedirects bool Whether redirects should be resolved
- * @param $convertTitles bool
+ * @param $dbSource ApiBase Module implementing getDB().
+ * Allows PageSet to reuse existing db connection from the shared state like ApiQuery.
+ * @param int $flags Zero or more flags like DISABLE_GENERATORS
+ * @param int $defaultNamespace the namespace to use if none is specified by a prefix.
+ * @since 1.21 accepts $flags instead of two boolean values
*/
- public function __construct( $query, $resolveRedirects = false, $convertTitles = false ) {
- parent::__construct( $query, 'query' );
+ public function __construct( ApiBase $dbSource, $flags = 0, $defaultNamespace = NS_MAIN ) {
+ parent::__construct( $dbSource->getMain(), $dbSource->getModuleName() );
+ $this->mDbSource = $dbSource;
+ $this->mAllowGenerator = ( $flags & ApiPageSet::DISABLE_GENERATORS ) == 0;
+ $this->mDefaultNamespace = $defaultNamespace;
- $this->mAllPages = array();
- $this->mTitles = array();
- $this->mGoodTitles = array();
- $this->mMissingTitles = array();
- $this->mInvalidTitles = array();
- $this->mMissingPageIDs = array();
- $this->mRedirectTitles = array();
- $this->mNormalizedTitles = array();
- $this->mInterwikiTitles = array();
- $this->mGoodRevIDs = array();
- $this->mMissingRevIDs = array();
- $this->mSpecialTitles = array();
+ $this->profileIn();
+ $this->mParams = $this->extractRequestParams();
+ $this->mResolveRedirects = $this->mParams['redirects'];
+ $this->mConvertTitles = $this->mParams['converttitles'];
+ $this->profileOut();
+ }
- $this->mRequestedPageFields = array();
- $this->mResolveRedirects = $resolveRedirects;
- if ( $resolveRedirects ) {
- $this->mPendingRedirectIDs = array();
- }
+ /**
+ * In case execute() is not called, call this method to mark all relevant parameters as used
+ * This prevents unused parameters from being reported as warnings
+ */
+ public function executeDryRun() {
+ $this->executeInternal( true );
+ }
- $this->mConvertTitles = $convertTitles;
- $this->mConvertedTitles = array();
+ /**
+ * Populate the PageSet from the request parameters.
+ */
+ public function execute() {
+ $this->executeInternal( false );
+ }
+
+ /**
+ * Populate the PageSet from the request parameters.
+ * @param bool $isDryRun If true, instantiates generator, but only to mark relevant parameters as used
+ */
+ private function executeInternal( $isDryRun ) {
+ $this->profileIn();
- $this->mFakePageId = - 1;
+ $generatorName = $this->mAllowGenerator ? $this->mParams['generator'] : null;
+ if ( isset( $generatorName ) ) {
+ $dbSource = $this->mDbSource;
+ $isQuery = $dbSource instanceof ApiQuery;
+ if ( !$isQuery ) {
+ // If the parent container of this pageset is not ApiQuery, we must create it to run generator
+ $dbSource = $this->getMain()->getModuleManager()->getModule( 'query' );
+ // Enable profiling for query module because it will be used for db sql profiling
+ $dbSource->profileIn();
+ }
+ $generator = $dbSource->getModuleManager()->getModule( $generatorName, null, true );
+ if ( $generator === null ) {
+ $this->dieUsage( 'Unknown generator=' . $generatorName, 'badgenerator' );
+ }
+ if ( !$generator instanceof ApiQueryGeneratorBase ) {
+ $this->dieUsage( "Module $generatorName cannot be used as a generator", 'badgenerator' );
+ }
+ // Create a temporary pageset to store generator's output,
+ // add any additional fields generator may need, and execute pageset to populate titles/pageids
+ $tmpPageSet = new ApiPageSet( $dbSource, ApiPageSet::DISABLE_GENERATORS );
+ $generator->setGeneratorMode( $tmpPageSet );
+ $this->mCacheMode = $generator->getCacheMode( $generator->extractRequestParams() );
+
+ if ( !$isDryRun ) {
+ $generator->requestExtraData( $tmpPageSet );
+ }
+ $tmpPageSet->executeInternal( $isDryRun );
+
+ // populate this pageset with the generator output
+ $this->profileOut();
+ $generator->profileIn();
+
+ if ( !$isDryRun ) {
+ $generator->executeGenerator( $this );
+ wfRunHooks( 'APIQueryGeneratorAfterExecute', array( &$generator, &$this ) );
+ $this->resolvePendingRedirects();
+ } else {
+ // Prevent warnings from being reported on these parameters
+ $main = $this->getMain();
+ foreach ( $generator->extractRequestParams() as $paramName => $param ) {
+ $main->getVal( $generator->encodeParamName( $paramName ) );
+ }
+ }
+ $generator->profileOut();
+ $this->profileIn();
+
+ if ( !$isQuery ) {
+ // If this pageset is not part of the query, we called profileIn() above
+ $dbSource->profileOut();
+ }
+ } else {
+ // Only one of the titles/pageids/revids is allowed at the same time
+ $dataSource = null;
+ if ( isset( $this->mParams['titles'] ) ) {
+ $dataSource = 'titles';
+ }
+ if ( isset( $this->mParams['pageids'] ) ) {
+ if ( isset( $dataSource ) ) {
+ $this->dieUsage( "Cannot use 'pageids' at the same time as '$dataSource'", 'multisource' );
+ }
+ $dataSource = 'pageids';
+ }
+ if ( isset( $this->mParams['revids'] ) ) {
+ if ( isset( $dataSource ) ) {
+ $this->dieUsage( "Cannot use 'revids' at the same time as '$dataSource'", 'multisource' );
+ }
+ $dataSource = 'revids';
+ }
+
+ if ( !$isDryRun ) {
+ // Populate page information with the original user input
+ switch( $dataSource ) {
+ case 'titles':
+ $this->initFromTitles( $this->mParams['titles'] );
+ break;
+ case 'pageids':
+ $this->initFromPageIds( $this->mParams['pageids'] );
+ break;
+ case 'revids':
+ if ( $this->mResolveRedirects ) {
+ $this->setWarning( 'Redirect resolution cannot be used together with the revids= parameter. ' .
+ 'Any redirects the revids= point to have not been resolved.' );
+ }
+ $this->mResolveRedirects = false;
+ $this->initFromRevIDs( $this->mParams['revids'] );
+ break;
+ default:
+ // Do nothing - some queries do not need any of the data sources.
+ break;
+ }
+ }
+ }
+ $this->profileOut();
}
/**
@@ -93,9 +218,33 @@ class ApiPageSet extends ApiQueryBase {
}
/**
- * Request an additional field from the page table. Must be called
- * before execute()
- * @param $fieldName string Field name
+ * Return the parameter name that is the source of data for this PageSet
+ *
+ * If multiple source parameters are specified (e.g. titles and pageids),
+ * one will be named arbitrarily.
+ *
+ * @return string|null
+ */
+ public function getDataSource() {
+ if ( $this->mAllowGenerator && isset( $this->mParams['generator'] ) ) {
+ return 'generator';
+ }
+ if ( isset( $this->mParams['titles'] ) ) {
+ return 'titles';
+ }
+ if ( isset( $this->mParams['pageids'] ) ) {
+ return 'pageids';
+ }
+ if ( isset( $this->mParams['revids'] ) ) {
+ return 'revids';
+ }
+ return null;
+ }
+
+ /**
+ * Request an additional field from the page table.
+ * Must be called before execute()
+ * @param string $fieldName Field name
*/
public function requestField( $fieldName ) {
$this->mRequestedPageFields[$fieldName] = null;
@@ -104,7 +253,7 @@ class ApiPageSet extends ApiQueryBase {
/**
* Get the value of a custom field previously requested through
* requestField()
- * @param $fieldName string Field name
+ * @param string $fieldName Field name
* @return mixed Field value
*/
public function getCustomField( $fieldName ) {
@@ -207,14 +356,39 @@ class ApiPageSet extends ApiQueryBase {
/**
* Get a list of redirect resolutions - maps a title to its redirect
- * target.
- * @return array prefixed_title (string) => Title object
+ * target, as an array of output-ready arrays
+ * @return array
*/
public function getRedirectTitles() {
return $this->mRedirectTitles;
}
/**
+ * Get a list of redirect resolutions - maps a title to its redirect
+ * target.
+ * @param $result ApiResult
+ * @return array of prefixed_title (string) => Title object
+ * @since 1.21
+ */
+ public function getRedirectTitlesAsResult( $result = null ) {
+ $values = array();
+ foreach ( $this->getRedirectTitles() as $titleStrFrom => $titleTo ) {
+ $r = array(
+ 'from' => strval( $titleStrFrom ),
+ 'to' => $titleTo->getPrefixedText(),
+ );
+ if ( $titleTo->getFragment() !== '' ) {
+ $r['tofragment'] = $titleTo->getFragment();
+ }
+ $values[] = $r;
+ }
+ if ( !empty( $values ) && $result ) {
+ $result->setIndexedTagName( $values, 'r' );
+ }
+ return $values;
+ }
+
+ /**
* Get a list of title normalizations - maps a title to its normalized
* version.
* @return array raw_prefixed_title (string) => prefixed_title (string)
@@ -224,6 +398,27 @@ class ApiPageSet extends ApiQueryBase {
}
/**
+ * Get a list of title normalizations - maps a title to its normalized
+ * version in the form of result array.
+ * @param $result ApiResult
+ * @return array of raw_prefixed_title (string) => prefixed_title (string)
+ * @since 1.21
+ */
+ public function getNormalizedTitlesAsResult( $result = null ) {
+ $values = array();
+ foreach ( $this->getNormalizedTitles() as $rawTitleStr => $titleStr ) {
+ $values[] = array(
+ 'from' => $rawTitleStr,
+ 'to' => $titleStr
+ );
+ }
+ if ( !empty( $values ) && $result ) {
+ $result->setIndexedTagName( $values, 'n' );
+ }
+ return $values;
+ }
+
+ /**
* Get a list of title conversions - maps a title to its converted
* version.
* @return array raw_prefixed_title (string) => prefixed_title (string)
@@ -233,6 +428,27 @@ class ApiPageSet extends ApiQueryBase {
}
/**
+ * Get a list of title conversions - maps a title to its converted
+ * version as a result array.
+ * @param $result ApiResult
+ * @return array of (from, to) strings
+ * @since 1.21
+ */
+ public function getConvertedTitlesAsResult( $result = null ) {
+ $values = array();
+ foreach ( $this->getConvertedTitles() as $rawTitleStr => $titleStr ) {
+ $values[] = array(
+ 'from' => $rawTitleStr,
+ 'to' => $titleStr
+ );
+ }
+ if ( !empty( $values ) && $result ) {
+ $result->setIndexedTagName( $values, 'c' );
+ }
+ return $values;
+ }
+
+ /**
* Get a list of interwiki titles - maps a title to its interwiki
* prefix.
* @return array raw_prefixed_title (string) => interwiki_prefix (string)
@@ -242,6 +458,33 @@ class ApiPageSet extends ApiQueryBase {
}
/**
+ * Get a list of interwiki titles - maps a title to its interwiki
+ * prefix as result.
+ * @param $result ApiResult
+ * @param $iwUrl boolean
+ * @return array raw_prefixed_title (string) => interwiki_prefix (string)
+ * @since 1.21
+ */
+ public function getInterwikiTitlesAsResult( $result = null, $iwUrl = false ) {
+ $values = array();
+ foreach ( $this->getInterwikiTitles() as $rawTitleStr => $interwikiStr ) {
+ $item = array(
+ 'title' => $rawTitleStr,
+ 'iw' => $interwikiStr,
+ );
+ if ( $iwUrl ) {
+ $title = Title::newFromText( $rawTitleStr );
+ $item['url'] = $title->getFullURL( '', false, PROTO_CURRENT );
+ }
+ $values[] = $item;
+ }
+ if ( !empty( $values ) && $result ) {
+ $result->setIndexedTagName( $values, 'i' );
+ }
+ return $values;
+ }
+
+ /**
* Get the list of revision IDs (requested with the revids= parameter)
* @return array revID (int) => pageID (int)
*/
@@ -258,6 +501,25 @@ class ApiPageSet extends ApiQueryBase {
}
/**
+ * Revision IDs that were not found in the database as result array.
+ * @param $result ApiResult
+ * @return array of revision IDs
+ * @since 1.21
+ */
+ public function getMissingRevisionIDsAsResult( $result = null ) {
+ $values = array();
+ foreach ( $this->getMissingRevisionIDs() as $revid ) {
+ $values[$revid] = array(
+ 'revid' => $revid
+ );
+ }
+ if ( !empty( $values ) && $result ) {
+ $result->setIndexedTagName( $values, 'rev' );
+ }
+ return $values;
+ }
+
+ /**
* Get the list of titles with negative namespace
* @return array Title
*/
@@ -274,55 +536,8 @@ class ApiPageSet extends ApiQueryBase {
}
/**
- * Populate the PageSet from the request parameters.
- */
- public function execute() {
- $this->profileIn();
- $params = $this->extractRequestParams();
-
- // Only one of the titles/pageids/revids is allowed at the same time
- $dataSource = null;
- if ( isset( $params['titles'] ) ) {
- $dataSource = 'titles';
- }
- if ( isset( $params['pageids'] ) ) {
- if ( isset( $dataSource ) ) {
- $this->dieUsage( "Cannot use 'pageids' at the same time as '$dataSource'", 'multisource' );
- }
- $dataSource = 'pageids';
- }
- if ( isset( $params['revids'] ) ) {
- if ( isset( $dataSource ) ) {
- $this->dieUsage( "Cannot use 'revids' at the same time as '$dataSource'", 'multisource' );
- }
- $dataSource = 'revids';
- }
-
- switch ( $dataSource ) {
- case 'titles':
- $this->initFromTitles( $params['titles'] );
- break;
- case 'pageids':
- $this->initFromPageIds( $params['pageids'] );
- break;
- case 'revids':
- if ( $this->mResolveRedirects ) {
- $this->setWarning( 'Redirect resolution cannot be used together with the revids= parameter. ' .
- 'Any redirects the revids= point to have not been resolved.' );
- }
- $this->mResolveRedirects = false;
- $this->initFromRevIDs( $params['revids'] );
- break;
- default:
- // Do nothing - some queries do not need any of the data sources.
- break;
- }
- $this->profileOut();
- }
-
- /**
* Populate this PageSet from a list of Titles
- * @param $titles array of Title objects
+ * @param array $titles of Title objects
*/
public function populateFromTitles( $titles ) {
$this->profileIn();
@@ -332,7 +547,7 @@ class ApiPageSet extends ApiQueryBase {
/**
* Populate this PageSet from a list of page IDs
- * @param $pageIDs array of page IDs
+ * @param array $pageIDs of page IDs
*/
public function populateFromPageIDs( $pageIDs ) {
$this->profileIn();
@@ -353,7 +568,7 @@ class ApiPageSet extends ApiQueryBase {
/**
* Populate this PageSet from a list of revision IDs
- * @param $revIDs array of revision IDs
+ * @param array $revIDs of revision IDs
*/
public function populateFromRevisionIDs( $revIDs ) {
$this->profileIn();
@@ -385,12 +600,11 @@ class ApiPageSet extends ApiQueryBase {
}
/**
- * Resolve redirects, if applicable
+ * Do not use, does nothing, will be removed
+ * @deprecated 1.21
*/
public function finishPageSetGeneration() {
- $this->profileIn();
- $this->resolvePendingRedirects();
- $this->profileOut();
+ wfDeprecated( __METHOD__, '1.21' );
}
/**
@@ -407,7 +621,7 @@ class ApiPageSet extends ApiQueryBase {
* #5 Substitute the original LinkBatch object with the new list
* #6 Repeat from step #1
*
- * @param $titles array of Title objects or strings
+ * @param array $titles of Title objects or strings
*/
private function initFromTitles( $titles ) {
// Get validated and normalized title objects
@@ -434,10 +648,10 @@ class ApiPageSet extends ApiQueryBase {
/**
* Does the same as initFromTitles(), but is based on page IDs instead
- * @param $pageids array of page IDs
+ * @param array $pageids of page IDs
*/
private function initFromPageIds( $pageids ) {
- if ( !count( $pageids ) ) {
+ if ( !$pageids ) {
return;
}
@@ -447,7 +661,7 @@ class ApiPageSet extends ApiQueryBase {
$pageids = self::getPositiveIntegers( $pageids );
$res = null;
- if ( count( $pageids ) ) {
+ if ( !empty( $pageids ) ) {
$set = array(
'page_id' => $pageids
);
@@ -460,7 +674,7 @@ class ApiPageSet extends ApiQueryBase {
$this->profileDBOut();
}
- $this->initFromQueryResult( $res, $remaining, false ); // process PageIDs
+ $this->initFromQueryResult( $res, $remaining, false ); // process PageIDs
// Resolve any found redirects
$this->resolvePendingRedirects();
@@ -470,9 +684,9 @@ class ApiPageSet extends ApiQueryBase {
* Iterate through the result of the query on 'page' table,
* and for each row create and store title object and save any extra fields requested.
* @param $res ResultWrapper DB Query result
- * @param $remaining array of either pageID or ns/title elements (optional).
+ * @param array $remaining of either pageID or ns/title elements (optional).
* If given, any missing items will go to $mMissingPageIDs and $mMissingTitles
- * @param $processTitles bool Must be provided together with $remaining.
+ * @param bool $processTitles Must be provided together with $remaining.
* If true, treat $remaining as an array of [ns][title]
* If false, treat it as an array of [pageIDs]
*/
@@ -499,7 +713,7 @@ class ApiPageSet extends ApiQueryBase {
$this->processDbRow( $row );
// Need gender information
- if( MWNamespace::hasGenderDistinction( $row->page_namespace ) ) {
+ if ( MWNamespace::hasGenderDistinction( $row->page_namespace ) ) {
$usernames[] = $row->page_title;
}
}
@@ -518,7 +732,7 @@ class ApiPageSet extends ApiQueryBase {
$this->mTitles[] = $title;
// need gender information
- if( MWNamespace::hasGenderDistinction( $ns ) ) {
+ if ( MWNamespace::hasGenderDistinction( $ns ) ) {
$usernames[] = $dbkey;
}
}
@@ -541,10 +755,10 @@ class ApiPageSet extends ApiQueryBase {
/**
* Does the same as initFromTitles(), but is based on revision IDs
* instead
- * @param $revids array of revision IDs
+ * @param array $revids of revision IDs
*/
private function initFromRevIDs( $revids ) {
- if ( !count( $revids ) ) {
+ if ( !$revids ) {
return;
}
@@ -555,14 +769,14 @@ class ApiPageSet extends ApiQueryBase {
$revids = self::getPositiveIntegers( $revids );
- if ( count( $revids ) ) {
+ if ( !empty( $revids ) ) {
$tables = array( 'revision', 'page' );
$fields = array( 'rev_id', 'rev_page' );
$where = array( 'rev_id' => $revids, 'rev_page = page_id' );
// Get pageIDs data from the `page` table
$this->profileDBIn();
- $res = $db->select( $tables, $fields, $where, __METHOD__ );
+ $res = $db->select( $tables, $fields, $where, __METHOD__ );
foreach ( $res as $row ) {
$revid = intval( $row->rev_id );
$pageid = intval( $row->rev_page );
@@ -670,44 +884,63 @@ class ApiPageSet extends ApiQueryBase {
}
/**
+ * Get the cache mode for the data generated by this module.
+ * All PageSet users should take into account whether this returns a more-restrictive
+ * cache mode than the using module itself. For possible return values and other
+ * details about cache modes, see ApiMain::setCacheMode()
+ *
+ * Public caching will only be allowed if *all* the modules that supply
+ * data for a given request return a cache mode of public.
+ *
+ * @param $params
+ * @return string
+ * @since 1.21
+ */
+ public function getCacheMode( $params = null ) {
+ return $this->mCacheMode;
+ }
+
+ /**
* Given an array of title strings, convert them into Title objects.
- * Alternativelly, an array of Title objects may be given.
+ * Alternatively, an array of Title objects may be given.
* This method validates access rights for the title,
* and appends normalization values to the output.
*
- * @param $titles array of Title objects or strings
+ * @param array $titles of Title objects or strings
* @return LinkBatch
*/
private function processTitlesArray( $titles ) {
- $genderCache = GenderCache::singleton();
- $genderCache->doTitlesArray( $titles, __METHOD__ );
-
+ $usernames = array();
$linkBatch = new LinkBatch();
foreach ( $titles as $title ) {
- $titleObj = is_string( $title ) ? Title::newFromText( $title ) : $title;
+ if ( is_string( $title ) ) {
+ $titleObj = Title::newFromText( $title, $this->mDefaultNamespace );
+ } else {
+ $titleObj = $title;
+ }
if ( !$titleObj ) {
// Handle invalid titles gracefully
- $this->mAllpages[0][$title] = $this->mFakePageId;
+ $this->mAllPages[0][$title] = $this->mFakePageId;
$this->mInvalidTitles[$this->mFakePageId] = $title;
$this->mFakePageId--;
continue; // There's nothing else we can do
}
$unconvertedTitle = $titleObj->getPrefixedText();
$titleWasConverted = false;
- $iw = $titleObj->getInterwiki();
- if ( strval( $iw ) !== '' ) {
+ if ( $titleObj->isExternal() ) {
// This title is an interwiki link.
- $this->mInterwikiTitles[$titleObj->getPrefixedText()] = $iw;
+ $this->mInterwikiTitles[$unconvertedTitle] = $titleObj->getInterwiki();
} else {
// Variants checking
global $wgContLang;
if ( $this->mConvertTitles &&
- count( $wgContLang->getVariants() ) > 1 &&
+ count( $wgContLang->getVariants() ) > 1 &&
!$titleObj->exists() ) {
- // Language::findVariantLink will modify titleObj into
+ // Language::findVariantLink will modify titleText and titleObj into
// the canonical variant if possible
- $wgContLang->findVariantLink( $title, $titleObj );
+ $titleText = is_string( $title ) ? $title : $titleObj->getPrefixedText();
+ $wgContLang->findVariantLink( $titleText, $titleObj );
$titleWasConverted = $unconvertedTitle !== $titleObj->getPrefixedText();
}
@@ -728,16 +961,36 @@ class ApiPageSet extends ApiQueryBase {
// namespace is localized or the capitalization is
// different
if ( $titleWasConverted ) {
- $this->mConvertedTitles[$title] = $titleObj->getPrefixedText();
+ $this->mConvertedTitles[$unconvertedTitle] = $titleObj->getPrefixedText();
+ // In this case the page can't be Special.
+ if ( is_string( $title ) && $title !== $unconvertedTitle ) {
+ $this->mNormalizedTitles[$title] = $unconvertedTitle;
+ }
} elseif ( is_string( $title ) && $title !== $titleObj->getPrefixedText() ) {
$this->mNormalizedTitles[$title] = $titleObj->getPrefixedText();
}
+
+ // Need gender information
+ if ( MWNamespace::hasGenderDistinction( $titleObj->getNamespace() ) ) {
+ $usernames[] = $titleObj->getText();
+ }
}
+ // Get gender information
+ $genderCache = GenderCache::singleton();
+ $genderCache->doQuery( $usernames, __METHOD__ );
return $linkBatch;
}
/**
+ * Get the database connection (read-only)
+ * @return DatabaseBase
+ */
+ protected function getDB() {
+ return $this->mDbSource->getDB();
+ }
+
+ /**
* Returns the input array of integers with all values < 0 removed
*
* @param $array array
@@ -747,7 +1000,7 @@ class ApiPageSet extends ApiQueryBase {
// bug 25734 API: possible issue with revids validation
// It seems with a load of revision rows, MySQL gets upset
// Remove any < 0 integers, as they can't be valid
- foreach( $array as $i => $int ) {
+ foreach ( $array as $i => $int ) {
if ( $int < 0 ) {
unset( $array[$i] );
}
@@ -756,8 +1009,8 @@ class ApiPageSet extends ApiQueryBase {
return $array;
}
- public function getAllowedParams() {
- return array(
+ public function getAllowedParams( $flags = 0 ) {
+ $result = array(
'titles' => array(
ApiBase::PARAM_ISMULTI => true
),
@@ -768,15 +1021,59 @@ class ApiPageSet extends ApiQueryBase {
'revids' => array(
ApiBase::PARAM_TYPE => 'integer',
ApiBase::PARAM_ISMULTI => true
- )
+ ),
+ 'redirects' => false,
+ 'converttitles' => false,
);
+ if ( $this->mAllowGenerator ) {
+ if ( $flags & ApiBase::GET_VALUES_FOR_HELP ) {
+ $result['generator'] = array(
+ ApiBase::PARAM_TYPE => $this->getGenerators()
+ );
+ } else {
+ $result['generator'] = null;
+ }
+ }
+ return $result;
+ }
+
+ private static $generators = null;
+
+ /**
+ * Get an array of all available generators
+ * @return array
+ */
+ private function getGenerators() {
+ if ( self::$generators === null ) {
+ $query = $this->mDbSource;
+ if ( !( $query instanceof ApiQuery ) ) {
+ // If the parent container of this pageset is not ApiQuery,
+ // we must create it to get module manager
+ $query = $this->getMain()->getModuleManager()->getModule( 'query' );
+ }
+ $gens = array();
+ $mgr = $query->getModuleManager();
+ foreach ( $mgr->getNamesWithClasses() as $name => $class ) {
+ if ( is_subclass_of( $class, 'ApiQueryGeneratorBase' ) ) {
+ $gens[] = $name;
+ }
+ }
+ sort( $gens );
+ self::$generators = $gens;
+ }
+ return self::$generators;
}
public function getParamDescription() {
return array(
'titles' => 'A list of titles to work on',
'pageids' => 'A list of page IDs to work on',
- 'revids' => 'A list of revision IDs to work on'
+ 'revids' => 'A list of revision IDs to work on',
+ 'generator' => array( 'Get the list of pages to work on by executing the specified query module.',
+ 'NOTE: generator parameter names must be prefixed with a \'g\', see examples' ),
+ 'redirects' => 'Automatically resolve redirects',
+ 'converttitles' => array( 'Convert titles to other variants if necessary. Only works if the wiki\'s content language supports variant conversion.',
+ 'Languages that support variant conversion include ' . implode( ', ', LanguageConverter::$languagesWithVariants ) ),
);
}
@@ -784,10 +1081,7 @@ class ApiPageSet extends ApiQueryBase {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'multisource', 'info' => "Cannot use 'pageids' at the same time as 'dataSource'" ),
array( 'code' => 'multisource', 'info' => "Cannot use 'revids' at the same time as 'dataSource'" ),
+ array( 'code' => 'badgenerator', 'info' => 'Module $generatorName cannot be used as a generator' ),
) );
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiParamInfo.php b/includes/api/ApiParamInfo.php
index 343a2625..27f8cefd 100644
--- a/includes/api/ApiParamInfo.php
+++ b/includes/api/ApiParamInfo.php
@@ -42,42 +42,13 @@ class ApiParamInfo extends ApiBase {
public function execute() {
// Get parameters
$params = $this->extractRequestParams();
- $result = $this->getResult();
+ $resultObj = $this->getResult();
$res = array();
- if ( is_array( $params['modules'] ) ) {
- $modules = $this->getMain()->getModules();
- $res['modules'] = array();
- foreach ( $params['modules'] as $mod ) {
- if ( !isset( $modules[$mod] ) ) {
- $res['modules'][] = array( 'name' => $mod, 'missing' => '' );
- continue;
- }
- $obj = new $modules[$mod]( $this->getMain(), $mod );
- $item = $this->getClassInfo( $obj );
- $item['name'] = $mod;
- $res['modules'][] = $item;
- }
- $result->setIndexedTagName( $res['modules'], 'module' );
- }
+ $this->addModulesInfo( $params, 'modules', $res, $resultObj );
- if ( is_array( $params['querymodules'] ) ) {
- $queryModules = $this->queryObj->getModules();
- $res['querymodules'] = array();
- foreach ( $params['querymodules'] as $qm ) {
- if ( !isset( $queryModules[$qm] ) ) {
- $res['querymodules'][] = array( 'name' => $qm, 'missing' => '' );
- continue;
- }
- $obj = new $queryModules[$qm]( $this, $qm );
- $item = $this->getClassInfo( $obj );
- $item['name'] = $qm;
- $item['querytype'] = $this->queryObj->getModuleType( $qm );
- $res['querymodules'][] = $item;
- }
- $result->setIndexedTagName( $res['querymodules'], 'module' );
- }
+ $this->addModulesInfo( $params, 'querymodules', $res, $resultObj );
if ( $params['mainmodule'] ) {
$res['mainmodule'] = $this->getClassInfo( $this->getMain() );
@@ -88,36 +59,57 @@ class ApiParamInfo extends ApiBase {
$res['pagesetmodule'] = $this->getClassInfo( $pageSet );
}
- if ( is_array( $params['formatmodules'] ) ) {
- $formats = $this->getMain()->getFormats();
- $res['formatmodules'] = array();
- foreach ( $params['formatmodules'] as $f ) {
- if ( !isset( $formats[$f] ) ) {
- $res['formatmodules'][] = array( 'name' => $f, 'missing' => '' );
- continue;
- }
- $obj = new $formats[$f]( $this, $f );
- $item = $this->getClassInfo( $obj );
- $item['name'] = $f;
- $res['formatmodules'][] = $item;
+ $this->addModulesInfo( $params, 'formatmodules', $res, $resultObj );
+
+ $resultObj->addValue( null, $this->getModuleName(), $res );
+ }
+
+ /**
+ * If the type is requested in parameters, adds a section to res with module info.
+ * @param array $params user parameters array
+ * @param string $type parameter name
+ * @param array $res store results in this array
+ * @param ApiResult $resultObj results object to set indexed tag.
+ */
+ private function addModulesInfo( $params, $type, &$res, $resultObj ) {
+ if ( !is_array( $params[$type] ) ) {
+ return;
+ }
+ $isQuery = ( $type === 'querymodules' );
+ if ( $isQuery ) {
+ $mgr = $this->queryObj->getModuleManager();
+ } else {
+ $mgr = $this->getMain()->getModuleManager();
+ }
+ $res[$type] = array();
+ foreach ( $params[$type] as $mod ) {
+ if ( !$mgr->isDefined( $mod ) ) {
+ $res[$type][] = array( 'name' => $mod, 'missing' => '' );
+ continue;
+ }
+ $obj = $mgr->getModule( $mod );
+ $item = $this->getClassInfo( $obj );
+ $item['name'] = $mod;
+ if ( $isQuery ) {
+ $item['querytype'] = $mgr->getModuleGroup( $mod );
}
- $result->setIndexedTagName( $res['formatmodules'], 'module' );
+ $res[$type][] = $item;
}
- $result->addValue( null, $this->getModuleName(), $res );
+ $resultObj->setIndexedTagName( $res[$type], 'module' );
}
/**
* @param $obj ApiBase
* @return ApiResult
*/
- function getClassInfo( $obj ) {
+ private function getClassInfo( $obj ) {
$result = $this->getResult();
$retval['classname'] = get_class( $obj );
$retval['description'] = implode( "\n", (array)$obj->getFinalDescription() );
-
$retval['examples'] = '';
- $retval['version'] = implode( "\n", (array)$obj->getVersion() );
+ // version is deprecated since 1.21, but needs to be returned for v1
+ $retval['version'] = '';
$retval['prefix'] = $obj->getModulePrefix();
if ( $obj->isReadMode() ) {
@@ -133,7 +125,7 @@ class ApiParamInfo extends ApiBase {
$retval['generator'] = '';
}
- $allowedParams = $obj->getFinalParams();
+ $allowedParams = $obj->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
if ( !is_array( $allowedParams ) ) {
return $retval;
}
@@ -150,7 +142,7 @@ class ApiParamInfo extends ApiBase {
if ( is_string( $examples ) ) {
$examples = array( $examples );
}
- foreach( $examples as $k => $v ) {
+ foreach ( $examples as $k => $v ) {
if ( strlen( $retval['examples'] ) ) {
$retval['examples'] .= ' ';
}
@@ -181,7 +173,7 @@ class ApiParamInfo extends ApiBase {
}
//handle shorthand
- if( !is_array( $p ) ) {
+ if ( !is_array( $p ) ) {
$p = array(
ApiBase::PARAM_DFLT => $p,
);
@@ -208,11 +200,11 @@ class ApiParamInfo extends ApiBase {
if ( isset( $p[ApiBase::PARAM_DFLT] ) ) {
$type = $p[ApiBase::PARAM_TYPE];
- if( $type === 'boolean' ) {
+ if ( $type === 'boolean' ) {
$a['default'] = ( $p[ApiBase::PARAM_DFLT] ? 'true' : 'false' );
- } elseif( $type === 'string' ) {
+ } elseif ( $type === 'string' ) {
$a['default'] = strval( $p[ApiBase::PARAM_DFLT] );
- } elseif( $type === 'integer' ) {
+ } elseif ( $type === 'integer' ) {
$a['default'] = intval( $p[ApiBase::PARAM_DFLT] );
} else {
$a['default'] = $p[ApiBase::PARAM_DFLT];
@@ -299,7 +291,7 @@ class ApiParamInfo extends ApiBase {
$retval['props'][] = $propResult;
}
- // default is true for query modules, false for other modules, overriden by ApiBase::PROP_LIST
+ // default is true for query modules, false for other modules, overridden by ApiBase::PROP_LIST
if ( $listResult === true || ( $listResult !== false && $obj instanceof ApiQueryBase ) ) {
$retval['listresult'] = '';
}
@@ -319,11 +311,11 @@ class ApiParamInfo extends ApiBase {
}
public function getAllowedParams() {
- $modules = array_keys( $this->getMain()->getModules() );
+ $modules = $this->getMain()->getModuleManager()->getNames( 'action' );
sort( $modules );
- $querymodules = array_keys( $this->queryObj->getModules() );
+ $querymodules = $this->queryObj->getModuleManager()->getNames();
sort( $querymodules );
- $formatmodules = array_keys( $this->getMain()->getFormats() );
+ $formatmodules = $this->getMain()->getModuleManager()->getNames( 'format' );
sort( $formatmodules );
return array(
'modules' => array(
@@ -366,8 +358,4 @@ class ApiParamInfo extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Parameter_information';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php
index db6e2bb8..09b7a882 100644
--- a/includes/api/ApiParse.php
+++ b/includes/api/ApiParse.php
@@ -26,11 +26,15 @@
* @ingroup API
*/
class ApiParse extends ApiBase {
- private $section, $text, $pstText = null;
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
+ /** @var String $section */
+ private $section = null;
+
+ /** @var Content $content */
+ private $content = null;
+
+ /** @var Content $pstContent */
+ private $pstContent = null;
public function execute() {
// The data is hot but user-dependent, like page views, so we set vary cookies
@@ -44,6 +48,9 @@ class ApiParse extends ApiBase {
$pageid = $params['pageid'];
$oldid = $params['oldid'];
+ $model = $params['contentmodel'];
+ $format = $params['contentformat'];
+
if ( !is_null( $page ) && ( !is_null( $text ) || $title != 'API' ) ) {
$this->dieUsage( 'The page parameter cannot be used together with the text and title parameters', 'params' );
}
@@ -61,7 +68,7 @@ class ApiParse extends ApiBase {
// TODO: Does this still need $wgTitle?
global $wgParser, $wgTitle;
- // Currently unnecessary, code to act as a safeguard against any change in current behaviour of uselang breaks
+ // Currently unnecessary, code to act as a safeguard against any change in current behavior of uselang
$oldLang = null;
if ( isset( $params['uselang'] ) && $params['uselang'] != $this->getContext()->getLanguage()->getCode() ) {
$oldLang = $this->getContext()->getLanguage(); // Backup language
@@ -91,19 +98,19 @@ class ApiParse extends ApiBase {
$popts->enableLimitReport( !$params['disablepp'] );
// If for some reason the "oldid" is actually the current revision, it may be cached
- if ( $titleObj->getLatestRevID() === intval( $oldid ) ) {
+ if ( $rev->isCurrent() ) {
// May get from/save to parser cache
- $p_result = $this->getParsedSectionOrText( $pageObj, $popts, $pageid,
- isset( $prop['wikitext'] ) ) ;
+ $p_result = $this->getParsedContent( $pageObj, $popts,
+ $pageid, isset( $prop['wikitext'] ) );
} else { // This is an old revision, so get the text differently
- $this->text = $rev->getText( Revision::FOR_THIS_USER, $this->getUser() );
+ $this->content = $rev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
if ( $this->section !== false ) {
- $this->text = $this->getSectionText( $this->text, 'r' . $rev->getId() );
+ $this->content = $this->getSectionContent( $this->content, 'r' . $rev->getId() );
}
// Should we save old revision parses to the parser cache?
- $p_result = $wgParser->parse( $this->text, $titleObj, $popts );
+ $p_result = $this->content->getParserOutput( $titleObj, $rev->getId(), $popts );
}
} else { // Not $oldid, but $pageid or $page
if ( $params['redirects'] ) {
@@ -136,6 +143,9 @@ class ApiParse extends ApiBase {
$pageObj = $this->getTitleOrPageId( $pageParams, 'fromdb' );
$titleObj = $pageObj->getTitle();
+ if ( !$titleObj || !$titleObj->exists() ) {
+ $this->dieUsage( "The page you specified doesn't exist", 'missingtitle' );
+ }
$wgTitle = $titleObj;
if ( isset( $prop['revid'] ) ) {
@@ -146,46 +156,59 @@ class ApiParse extends ApiBase {
$popts->enableLimitReport( !$params['disablepp'] );
// Potentially cached
- $p_result = $this->getParsedSectionOrText( $pageObj, $popts, $pageid,
- isset( $prop['wikitext'] ) ) ;
+ $p_result = $this->getParsedContent( $pageObj, $popts, $pageid,
+ isset( $prop['wikitext'] ) );
}
} else { // Not $oldid, $pageid, $page. Hence based on $text
-
- if ( is_null( $text ) ) {
- $this->dieUsage( 'The text parameter should be passed with the title parameter. Should you be using the "page" parameter instead?', 'params' );
- }
- $this->text = $text;
$titleObj = Title::newFromText( $title );
- if ( !$titleObj ) {
+ if ( !$titleObj || $titleObj->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $title ) );
}
+ if ( !$titleObj->canExist() ) {
+ $this->dieUsage( "Namespace doesn't allow actual pages", 'pagecannotexist' );
+ }
$wgTitle = $titleObj;
$pageObj = WikiPage::factory( $titleObj );
$popts = $pageObj->makeParserOptions( $this->getContext() );
$popts->enableLimitReport( !$params['disablepp'] );
+ if ( is_null( $text ) ) {
+ $this->dieUsage( 'The text parameter should be passed with the title parameter. Should you be using the "page" parameter instead?', 'params' );
+ }
+
+ try {
+ $this->content = ContentHandler::makeContent( $text, $titleObj, $model, $format );
+ } catch ( MWContentSerializationException $ex ) {
+ $this->dieUsage( $ex->getMessage(), 'parseerror' );
+ }
+
if ( $this->section !== false ) {
- $this->text = $this->getSectionText( $this->text, $titleObj->getText() );
+ $this->content = $this->getSectionContent( $this->content, $titleObj->getText() );
}
if ( $params['pst'] || $params['onlypst'] ) {
- $this->pstText = $wgParser->preSaveTransform( $this->text, $titleObj, $this->getUser(), $popts );
+ $this->pstContent = $this->content->preSaveTransform( $titleObj, $this->getUser(), $popts );
}
if ( $params['onlypst'] ) {
// Build a result and bail out
$result_array = array();
$result_array['text'] = array();
- $result->setContent( $result_array['text'], $this->pstText );
+ $result->setContent( $result_array['text'], $this->pstContent->serialize( $format ) );
if ( isset( $prop['wikitext'] ) ) {
$result_array['wikitext'] = array();
- $result->setContent( $result_array['wikitext'], $this->text );
+ $result->setContent( $result_array['wikitext'], $this->content->serialize( $format ) );
}
$result->addValue( null, $this->getModuleName(), $result_array );
return;
}
+
// Not cached (save or load)
- $p_result = $wgParser->parse( $params['pst'] ? $this->pstText : $this->text, $titleObj, $popts );
+ if ( $params['pst'] ) {
+ $p_result = $this->pstContent->getParserOutput( $titleObj, null, $popts );
+ } else {
+ $p_result = $this->content->getParserOutput( $titleObj, null, $popts );
+ }
}
$result_array = array();
@@ -275,10 +298,10 @@ class ApiParse extends ApiBase {
if ( isset( $prop['wikitext'] ) ) {
$result_array['wikitext'] = array();
- $result->setContent( $result_array['wikitext'], $this->text );
- if ( !is_null( $this->pstText ) ) {
+ $result->setContent( $result_array['wikitext'], $this->content->serialize( $format ) );
+ if ( !is_null( $this->pstContent ) ) {
$result_array['psttext'] = array();
- $result->setContent( $result_array['psttext'], $this->pstText );
+ $result->setContent( $result_array['psttext'], $this->pstContent->serialize( $format ) );
}
}
if ( isset( $prop['properties'] ) ) {
@@ -286,8 +309,12 @@ class ApiParse extends ApiBase {
}
if ( $params['generatexml'] ) {
+ if ( $this->content->getModel() != CONTENT_MODEL_WIKITEXT ) {
+ $this->dieUsage( "generatexml is only supported for wikitext content", "notwikitext" );
+ }
+
$wgParser->startExternalParse( $titleObj, $popts, OT_PREPROCESS );
- $dom = $wgParser->preprocessToDom( $this->text );
+ $dom = $wgParser->preprocessToDom( $this->content->getNativeData() );
if ( is_callable( array( $dom, 'saveXML' ) ) ) {
$xml = $dom->saveXML();
} else {
@@ -325,15 +352,16 @@ class ApiParse extends ApiBase {
* @param $getWikitext Bool
* @return ParserOutput
*/
- private function getParsedSectionOrText( $page, $popts, $pageId = null, $getWikitext = false ) {
- global $wgParser;
+ private function getParsedContent( WikiPage $page, $popts, $pageId = null, $getWikitext = false ) {
+ $this->content = $page->getContent( Revision::RAW ); //XXX: really raw?
- if ( $this->section !== false ) {
- $this->text = $this->getSectionText( $page->getRawText(), !is_null( $pageId )
- ? 'page id ' . $pageId : $page->getTitle()->getPrefixedText() );
+ if ( $this->section !== false && $this->content !== null ) {
+ $this->content = $this->getSectionContent(
+ $this->content,
+ !is_null( $pageId ) ? 'page id ' . $pageId : $page->getTitle()->getText() );
// Not cached (save or load)
- return $wgParser->parse( $this->text, $page->getTitle(), $popts );
+ return $this->content->getParserOutput( $page->getTitle(), null, $popts );
} else {
// Try the parser cache first
// getParserOutput will save to Parser cache if able
@@ -342,20 +370,23 @@ class ApiParse extends ApiBase {
$this->dieUsage( "There is no revision ID {$page->getLatest()}", 'missingrev' );
}
if ( $getWikitext ) {
- $this->text = $page->getRawText();
+ $this->content = $page->getContent( Revision::RAW );
}
return $pout;
}
}
- private function getSectionText( $text, $what ) {
- global $wgParser;
+ private function getSectionContent( Content $content, $what ) {
// Not cached (save or load)
- $text = $wgParser->getSection( $text, $this->section, false );
- if ( $text === false ) {
+ $section = $content->getSection( $this->section );
+ if ( $section === false ) {
$this->dieUsage( "There is no section {$this->section} in " . $what, 'nosuchsection' );
}
- return $text;
+ if ( $section === null ) {
+ $this->dieUsage( "Sections are not supported by " . $what, 'nosuchsection' );
+ $section = false;
+ }
+ return $section;
}
private function formatLangLinks( $links ) {
@@ -415,14 +446,14 @@ class ApiParse extends ApiBase {
$text = Language::fetchLanguageName( $nt->getInterwiki() );
$langs[] = Html::element( 'a',
- array( 'href' => $nt->getFullURL(), 'title' => $nt->getText(), 'class' => "external" ),
+ array( 'href' => $nt->getFullURL(), 'title' => $nt->getText(), 'class' => 'external' ),
$text == '' ? $l : $text );
}
$s .= implode( wfMessage( 'pipe-separator' )->escaped(), $langs );
if ( $wgContLang->isRTL() ) {
- $s = Html::rawElement( 'span', array( 'dir' => "LTR" ), $s );
+ $s = Html::rawElement( 'span', array( 'dir' => 'LTR' ), $s );
}
return $s;
@@ -548,6 +579,12 @@ class ApiParse extends ApiBase {
'section' => null,
'disablepp' => false,
'generatexml' => false,
+ 'contentformat' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+ ),
+ 'contentmodel' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
+ )
);
}
@@ -592,7 +629,9 @@ class ApiParse extends ApiBase {
'uselang' => 'Which language to parse the request in',
'section' => 'Only retrieve the content of this section number',
'disablepp' => 'Disable the PP Report from the parser output',
- 'generatexml' => 'Generate XML parse tree',
+ 'generatexml' => 'Generate XML parse tree (requires prop=wikitext)',
+ 'contentformat' => 'Content serialization format used for the input text',
+ 'contentmodel' => 'Content model of the new content',
);
}
@@ -613,6 +652,9 @@ class ApiParse extends ApiBase {
array( 'code' => 'nosuchsection', 'info' => 'There is no section sectionnumber in page' ),
array( 'nosuchpageid' ),
array( 'invalidtitle', 'title' ),
+ array( 'code' => 'parseerror', 'info' => 'Failed to parse the given text.' ),
+ array( 'code' => 'notwikitext', 'info' => 'The requested operation is only supported on wikitext content.' ),
+ array( 'code' => 'pagecannotexist', 'info' => "Namespace doesn't allow actual pages" ),
) );
}
@@ -625,8 +667,4 @@ class ApiParse extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Parsing_wikitext#parse';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiPatrol.php b/includes/api/ApiPatrol.php
index cb5e081a..4d4fbba9 100644
--- a/includes/api/ApiPatrol.php
+++ b/includes/api/ApiPatrol.php
@@ -30,10 +30,6 @@
*/
class ApiPatrol extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
/**
* Patrols the article or provides the reason the patrol failed.
*/
@@ -120,8 +116,4 @@ class ApiPatrol extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Patrol';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiProtect.php b/includes/api/ApiProtect.php
index b3ca67e6..503c6920 100644
--- a/includes/api/ApiProtect.php
+++ b/includes/api/ApiProtect.php
@@ -29,10 +29,6 @@
*/
class ApiProtect extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
global $wgRestrictionLevels;
$params = $this->extractRequestParams();
@@ -178,7 +174,7 @@ class ApiProtect extends ApiBase {
'token' => 'A protect token previously retrieved through prop=info',
'protections' => 'List of protection levels, formatted action=group (e.g. edit=sysop)',
'expiry' => array( 'Expiry timestamps. If only one timestamp is set, it\'ll be used for all protections.',
- 'Use \'infinite\', \'indefinite\' or \'never\', for a neverexpiring protection.' ),
+ 'Use \'infinite\', \'indefinite\' or \'never\', for a never-expiring protection.' ),
'reason' => 'Reason for (un)protecting',
'cascade' => array( 'Enable cascading protection (i.e. protect pages included in this page)',
'Ignored if not all protection levels are \'sysop\' or \'protect\'' ),
@@ -234,8 +230,4 @@ class ApiProtect extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Protect';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiPurge.php b/includes/api/ApiPurge.php
index 9fedaf1b..134f4a0d 100644
--- a/includes/api/ApiPurge.php
+++ b/includes/api/ApiPurge.php
@@ -31,69 +31,69 @@
*/
class ApiPurge extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
+ private $mPageSet;
+
+ /**
+ * Add all items from $values into the result
+ * @param array $result output
+ * @param array $values values to add
+ * @param string $flag the name of the boolean flag to mark this element
+ * @param string $name if given, name of the value
+ */
+ private static function addValues( array &$result, $values, $flag = null, $name = null ) {
+ foreach ( $values as $val ) {
+ if( $val instanceof Title ) {
+ $v = array();
+ ApiQueryBase::addTitleInfo( $v, $val );
+ } elseif( $name !== null ) {
+ $v = array( $name => $val );
+ } else {
+ $v = $val;
+ }
+ if( $flag !== null ) {
+ $v[$flag] = '';
+ }
+ $result[] = $v;
+ }
}
/**
* Purges the cache of a page
*/
public function execute() {
- $user = $this->getUser();
$params = $this->extractRequestParams();
- if ( !$user->isAllowed( 'purge' ) && !$this->getMain()->isInternalMode() &&
- !$this->getRequest()->wasPosted() ) {
- $this->dieUsageMsg( array( 'mustbeposted', $this->getModuleName() ) );
- }
$forceLinkUpdate = $params['forcelinkupdate'];
- $pageSet = new ApiPageSet( $this );
+ $pageSet = $this->getPageSet();
$pageSet->execute();
$result = array();
- foreach( $pageSet->getInvalidTitles() as $title ) {
- $r = array();
- $r['title'] = $title;
- $r['invalid'] = '';
- $result[] = $r;
- }
- foreach( $pageSet->getMissingPageIDs() as $p ) {
- $page = array();
- $page['pageid'] = $p;
- $page['missing'] = '';
- $result[] = $page;
- }
- foreach( $pageSet->getMissingRevisionIDs() as $r ) {
- $rev = array();
- $rev['revid'] = $r;
- $rev['missing'] = '';
- $result[] = $rev;
- }
-
- foreach ( $pageSet->getTitles() as $title ) {
+ self::addValues( $result, $pageSet->getInvalidTitles(), 'invalid', 'title' );
+ self::addValues( $result, $pageSet->getSpecialTitles(), 'special', 'title' );
+ self::addValues( $result, $pageSet->getMissingPageIDs(), 'missing', 'pageid' );
+ self::addValues( $result, $pageSet->getMissingRevisionIDs(), 'missing', 'revid' );
+ self::addValues( $result, $pageSet->getMissingTitles(), 'missing' );
+ self::addValues( $result, $pageSet->getInterwikiTitlesAsResult() );
+
+ foreach ( $pageSet->getGoodTitles() as $title ) {
$r = array();
-
ApiQueryBase::addTitleInfo( $r, $title );
- if ( !$title->exists() ) {
- $r['missing'] = '';
- $result[] = $r;
- continue;
- }
-
$page = WikiPage::factory( $title );
$page->doPurge(); // Directly purge and skip the UI part of purge().
$r['purged'] = '';
- if( $forceLinkUpdate ) {
- if ( !$user->pingLimiter() ) {
- global $wgParser, $wgEnableParserCache;
+ if ( $forceLinkUpdate ) {
+ if ( !$this->getUser()->pingLimiter() ) {
+ global $wgEnableParserCache;
$popts = $page->makeParserOptions( 'canonical' );
- $p_result = $wgParser->parse( $page->getRawText(), $title, $popts,
- true, true, $page->getLatest() );
+
+ # Parse content; note that HTML generation is only needed if we want to cache the result.
+ $content = $page->getContent( Revision::RAW );
+ $p_result = $content->getParserOutput( $title, $page->getLatest(), $popts, $wgEnableParserCache );
# Update the links tables
- $updates = $p_result->getSecondaryDataUpdates( $title );
+ $updates = $content->getSecondaryDataUpdates( $title, null, true, $p_result );
DataUpdate::runUpdates( $updates );
$r['linkupdate'] = '';
@@ -114,24 +114,52 @@ class ApiPurge extends ApiBase {
$apiResult = $this->getResult();
$apiResult->setIndexedTagName( $result, 'page' );
$apiResult->addValue( null, $this->getModuleName(), $result );
+
+ $values = $pageSet->getNormalizedTitlesAsResult( $apiResult );
+ if ( $values ) {
+ $apiResult->addValue( null, 'normalized', $values );
+ }
+ $values = $pageSet->getConvertedTitlesAsResult( $apiResult );
+ if ( $values ) {
+ $apiResult->addValue( null, 'converted', $values );
+ }
+ $values = $pageSet->getRedirectTitlesAsResult( $apiResult );
+ if ( $values ) {
+ $apiResult->addValue( null, 'redirects', $values );
+ }
+ }
+
+ /**
+ * Get a cached instance of an ApiPageSet object
+ * @return ApiPageSet
+ */
+ private function getPageSet() {
+ if ( !isset( $this->mPageSet ) ) {
+ $this->mPageSet = new ApiPageSet( $this );
+ }
+ return $this->mPageSet;
}
public function isWriteMode() {
return true;
}
- public function getAllowedParams() {
- $psModule = new ApiPageSet( $this );
- return $psModule->getAllowedParams() + array(
- 'forcelinkupdate' => false,
- );
+ public function mustBePosted() {
+ // Anonymous users are not allowed a non-POST request
+ return !$this->getUser()->isAllowed( 'purge' );
+ }
+
+ public function getAllowedParams( $flags = 0 ) {
+ $result = array( 'forcelinkupdate' => false );
+ if ( $flags ) {
+ $result += $this->getPageSet()->getFinalParams( $flags );
+ }
+ return $result;
}
public function getParamDescription() {
- $psModule = new ApiPageSet( $this );
- return $psModule->getParamDescription() + array(
- 'forcelinkupdate' => 'Update the links tables',
- );
+ return $this->getPageSet()->getParamDescription()
+ + array( 'forcelinkupdate' => 'Update the links tables' );
}
public function getResultProperties() {
@@ -155,9 +183,14 @@ class ApiPurge extends ApiBase {
ApiBase::PROP_NULLABLE => true
),
'invalid' => 'boolean',
+ 'special' => 'boolean',
'missing' => 'boolean',
'purged' => 'boolean',
- 'linkupdate' => 'boolean'
+ 'linkupdate' => 'boolean',
+ 'iw' => array(
+ ApiBase::PROP_TYPE => 'string',
+ ApiBase::PROP_NULLABLE => true
+ ),
)
);
}
@@ -169,10 +202,9 @@ class ApiPurge extends ApiBase {
}
public function getPossibleErrors() {
- $psModule = new ApiPageSet( $this );
return array_merge(
parent::getPossibleErrors(),
- $psModule->getPossibleErrors()
+ $this->getPageSet()->getPossibleErrors()
);
}
@@ -185,8 +217,4 @@ class ApiPurge extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Purge';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php
index 554aae5a..f69ad234 100644
--- a/includes/api/ApiQuery.php
+++ b/includes/api/ApiQuery.php
@@ -37,16 +37,11 @@
*/
class ApiQuery extends ApiBase {
- private $mPropModuleNames, $mListModuleNames, $mMetaModuleNames;
-
/**
- * @var ApiPageSet
+ * List of Api Query prop modules
+ * @var array
*/
- private $mPageSet;
-
- private $params, $redirects, $convertTitles, $iwUrl;
-
- private $mQueryPropModules = array(
+ private static $QueryPropModules = array(
'categories' => 'ApiQueryCategories',
'categoryinfo' => 'ApiQueryCategoryInfo',
'duplicatefiles' => 'ApiQueryDuplicateFiles',
@@ -63,11 +58,16 @@ class ApiQuery extends ApiBase {
'templates' => 'ApiQueryLinks',
);
- private $mQueryListModules = array(
+ /**
+ * List of Api Query list modules
+ * @var array
+ */
+ private static $QueryListModules = array(
'allcategories' => 'ApiQueryAllCategories',
'allimages' => 'ApiQueryAllImages',
'alllinks' => 'ApiQueryAllLinks',
'allpages' => 'ApiQueryAllPages',
+ 'alltransclusions' => 'ApiQueryAllLinks',
'allusers' => 'ApiQueryAllUsers',
'backlinks' => 'ApiQueryBacklinks',
'blocks' => 'ApiQueryBlocks',
@@ -80,6 +80,8 @@ class ApiQuery extends ApiBase {
'iwbacklinks' => 'ApiQueryIWBacklinks',
'langbacklinks' => 'ApiQueryLangBacklinks',
'logevents' => 'ApiQueryLogEvents',
+ 'pageswithprop' => 'ApiQueryPagesWithProp',
+ 'pagepropnames' => 'ApiQueryPagePropNames',
'protectedtitles' => 'ApiQueryProtectedTitles',
'querypage' => 'ApiQueryQueryPage',
'random' => 'ApiQueryRandom',
@@ -92,16 +94,26 @@ class ApiQuery extends ApiBase {
'watchlistraw' => 'ApiQueryWatchlistRaw',
);
- private $mQueryMetaModules = array(
+ /**
+ * List of Api Query meta modules
+ * @var array
+ */
+ private static $QueryMetaModules = array(
'allmessages' => 'ApiQueryAllMessages',
'siteinfo' => 'ApiQuerySiteinfo',
'userinfo' => 'ApiQueryUserInfo',
);
- private $mSlaveDB = null;
- private $mNamedDB = array();
+ /**
+ * @var ApiPageSet
+ */
+ private $mPageSet;
- protected $mAllowedGenerators = array();
+ private $mParams;
+ private $mNamedDB = array();
+ private $mModuleMgr;
+ private $mGeneratorContinue;
+ private $mUseLegacyContinue;
/**
* @param $main ApiMain
@@ -110,59 +122,27 @@ class ApiQuery extends ApiBase {
public function __construct( $main, $action ) {
parent::__construct( $main, $action );
- // Allow custom modules to be added in LocalSettings.php
- global $wgAPIPropModules, $wgAPIListModules, $wgAPIMetaModules,
- $wgMemc, $wgAPICacheHelpTimeout;
- self::appendUserModules( $this->mQueryPropModules, $wgAPIPropModules );
- self::appendUserModules( $this->mQueryListModules, $wgAPIListModules );
- self::appendUserModules( $this->mQueryMetaModules, $wgAPIMetaModules );
-
- $this->mPropModuleNames = array_keys( $this->mQueryPropModules );
- $this->mListModuleNames = array_keys( $this->mQueryListModules );
- $this->mMetaModuleNames = array_keys( $this->mQueryMetaModules );
-
- // Get array of query generators from cache if present
- $key = wfMemcKey( 'apiquerygenerators', SpecialVersion::getVersion( 'nodb' ) );
-
- if ( $wgAPICacheHelpTimeout > 0 ) {
- $cached = $wgMemc->get( $key );
- if ( $cached ) {
- $this->mAllowedGenerators = $cached;
- return;
- }
- }
- $this->makeGeneratorList( $this->mQueryPropModules );
- $this->makeGeneratorList( $this->mQueryListModules );
+ $this->mModuleMgr = new ApiModuleManager( $this );
- if ( $wgAPICacheHelpTimeout > 0 ) {
- $wgMemc->set( $key, $this->mAllowedGenerators, $wgAPICacheHelpTimeout );
- }
+ // Allow custom modules to be added in LocalSettings.php
+ global $wgAPIPropModules, $wgAPIListModules, $wgAPIMetaModules;
+ $this->mModuleMgr->addModules( self::$QueryPropModules, 'prop' );
+ $this->mModuleMgr->addModules( $wgAPIPropModules, 'prop' );
+ $this->mModuleMgr->addModules( self::$QueryListModules, 'list' );
+ $this->mModuleMgr->addModules( $wgAPIListModules, 'list' );
+ $this->mModuleMgr->addModules( self::$QueryMetaModules, 'meta' );
+ $this->mModuleMgr->addModules( $wgAPIMetaModules, 'meta' );
+
+ // Create PageSet that will process titles/pageids/revids/generator
+ $this->mPageSet = new ApiPageSet( $this );
}
/**
- * Helper function to append any add-in modules to the list
- * @param $modules array Module array
- * @param $newModules array Module array to add to $modules
+ * Overrides to return this instance's module manager.
+ * @return ApiModuleManager
*/
- private static function appendUserModules( &$modules, $newModules ) {
- if ( is_array( $newModules ) ) {
- foreach ( $newModules as $moduleName => $moduleClass ) {
- $modules[$moduleName] = $moduleClass;
- }
- }
- }
-
- /**
- * Gets a default slave database connection object
- * @return DatabaseBase
- */
- public function getDB() {
- if ( !isset( $this->mSlaveDB ) ) {
- $this->profileDBIn();
- $this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' );
- $this->profileDBOut();
- }
- return $this->mSlaveDB;
+ public function getModuleManager() {
+ return $this->mModuleMgr;
}
/**
@@ -170,9 +150,9 @@ class ApiQuery extends ApiBase {
* If no such connection has been requested before, it will be created.
* Subsequent calls with the same $name will return the same connection
* as the first, regardless of the values of $db and $groups
- * @param $name string Name to assign to the database connection
- * @param $db int One of the DB_* constants
- * @param $groups array Query groups
+ * @param string $name Name to assign to the database connection
+ * @param int $db One of the DB_* constants
+ * @param array $groups Query groups
* @return DatabaseBase
*/
public function getNamedDB( $name, $db, $groups ) {
@@ -194,31 +174,38 @@ class ApiQuery extends ApiBase {
/**
* Get the array mapping module names to class names
+ * @deprecated since 1.21, use getModuleManager()'s methods instead
+ * @return array array(modulename => classname)
+ */
+ public function getModules() {
+ wfDeprecated( __METHOD__, '1.21' );
+ return $this->getModuleManager()->getNamesWithClasses();
+ }
+
+ /**
+ * Get the generators array mapping module names to class names
+ * @deprecated since 1.21, list of generators is maintained by ApiPageSet
* @return array array(modulename => classname)
*/
- function getModules() {
- return array_merge( $this->mQueryPropModules, $this->mQueryListModules, $this->mQueryMetaModules );
+ public function getGenerators() {
+ wfDeprecated( __METHOD__, '1.21' );
+ $gens = array();
+ foreach ( $this->mModuleMgr->getNamesWithClasses() as $name => $class ) {
+ if ( is_subclass_of( $class, 'ApiQueryGeneratorBase' ) ) {
+ $gens[$name] = $class;
+ }
+ }
+ return $gens;
}
/**
* Get whether the specified module is a prop, list or a meta query module
- * @param $moduleName string Name of the module to find type for
+ * @deprecated since 1.21, use getModuleManager()->getModuleGroup()
+ * @param string $moduleName Name of the module to find type for
* @return mixed string or null
*/
function getModuleType( $moduleName ) {
- if ( isset( $this->mQueryPropModules[$moduleName] ) ) {
- return 'prop';
- }
-
- if ( isset( $this->mQueryListModules[$moduleName] ) ) {
- return 'list';
- }
-
- if ( isset( $this->mQueryMetaModules[$moduleName] ) ) {
- return 'meta';
- }
-
- return null;
+ return $this->getModuleManager()->getModuleGroup( $moduleName );
}
/**
@@ -247,42 +234,37 @@ class ApiQuery extends ApiBase {
* #5 Execute all requested modules
*/
public function execute() {
- $this->params = $this->extractRequestParams();
- $this->redirects = $this->params['redirects'];
- $this->convertTitles = $this->params['converttitles'];
- $this->iwUrl = $this->params['iwurl'];
+ $this->mParams = $this->extractRequestParams();
- // Create PageSet
- $this->mPageSet = new ApiPageSet( $this, $this->redirects, $this->convertTitles );
+ // $pagesetParams is a array of parameter names used by the pageset generator
+ // or null if pageset has already finished and is no longer needed
+ // $completeModules is a set of complete modules with the name as key
+ $this->initContinue( $pagesetParams, $completeModules );
// Instantiate requested modules
- $modules = array();
- $this->instantiateModules( $modules, 'prop', $this->mQueryPropModules );
- $this->instantiateModules( $modules, 'list', $this->mQueryListModules );
- $this->instantiateModules( $modules, 'meta', $this->mQueryMetaModules );
-
- $cacheMode = 'public';
-
- // If given, execute generator to substitute user supplied data with generated data.
- if ( isset( $this->params['generator'] ) ) {
- $generator = $this->newGenerator( $this->params['generator'] );
- $params = $generator->extractRequestParams();
- $cacheMode = $this->mergeCacheMode( $cacheMode,
- $generator->getCacheMode( $params ) );
- $this->executeGeneratorModule( $generator, $modules );
- } else {
- // Append custom fields and populate page/revision information
- $this->addCustomFldsToPageSet( $modules, $this->mPageSet );
+ $allModules = array();
+ $this->instantiateModules( $allModules, 'prop' );
+ $propModules = $allModules; // Keep a copy
+ $this->instantiateModules( $allModules, 'list' );
+ $this->instantiateModules( $allModules, 'meta' );
+
+ // Filter modules based on continue parameter
+ $modules = $this->initModules( $allModules, $completeModules, $pagesetParams !== null );
+
+ // Execute pageset if in legacy mode or if pageset is not done
+ if ( $completeModules === null || $pagesetParams !== null ) {
+ // Populate page/revision information
$this->mPageSet->execute();
+ // Record page information (title, namespace, if exists, etc)
+ $this->outputGeneralPageInfo();
+ } else {
+ $this->mPageSet->executeDryRun();
}
- // Record page information (title, namespace, if exists, etc)
- $this->outputGeneralPageInfo();
+ $cacheMode = $this->mPageSet->getCacheMode();
- // Execute all requested modules.
- /**
- * @var $module ApiQueryBase
- */
+ // Execute all unfinished modules
+ /** @var $module ApiQueryBase */
foreach ( $modules as $module ) {
$params = $module->extractRequestParams();
$cacheMode = $this->mergeCacheMode(
@@ -295,6 +277,136 @@ class ApiQuery extends ApiBase {
// Set the cache mode
$this->getMain()->setCacheMode( $cacheMode );
+
+ if ( $completeModules === null ) {
+ return; // Legacy continue, we are done
+ }
+
+ // Reformat query-continue result section
+ $result = $this->getResult();
+ $qc = $result->getData();
+ if ( isset( $qc['query-continue'] ) ) {
+ $qc = $qc['query-continue'];
+ $result->unsetValue( null, 'query-continue' );
+ } elseif ( $this->mGeneratorContinue !== null ) {
+ $qc = array();
+ } else {
+ // no more "continue"s, we are done!
+ return;
+ }
+
+ // we are done with all the modules that do not have result in query-continue
+ $completeModules = array_merge( $completeModules, array_diff_key( $modules, $qc ) );
+ if ( $pagesetParams !== null ) {
+ // The pageset is still in use, check if all props have finished
+ $incompleteProps = array_intersect_key( $propModules, $qc );
+ if ( count( $incompleteProps ) > 0 ) {
+ // Properties are not done, continue with the same pageset state - copy current parameters
+ $main = $this->getMain();
+ $contValues = array();
+ foreach ( $pagesetParams as $param ) {
+ // The param name is already prefix-encoded
+ $contValues[$param] = $main->getVal( $param );
+ }
+ } elseif ( $this->mGeneratorContinue !== null ) {
+ // Move to the next set of pages produced by pageset, properties need to be restarted
+ $contValues = $this->mGeneratorContinue;
+ $pagesetParams = array_keys( $contValues );
+ $completeModules = array_diff_key( $completeModules, $propModules );
+ } else {
+ // Done with the pageset, finish up with the the lists and meta modules
+ $pagesetParams = null;
+ }
+ }
+
+ $continue = '||' . implode( '|', array_keys( $completeModules ) );
+ if ( $pagesetParams !== null ) {
+ // list of all pageset parameters to use in the next request
+ $continue = implode( '|', $pagesetParams ) . $continue;
+ } else {
+ // we are done with the pageset
+ $contValues = array();
+ $continue = '-' . $continue;
+ }
+ $contValues['continue'] = $continue;
+ foreach ( $qc as $qcModule ) {
+ foreach ( $qcModule as $qcKey => $qcValue ) {
+ $contValues[$qcKey] = $qcValue;
+ }
+ }
+ $this->getResult()->addValue( null, 'continue', $contValues );
+ }
+
+ /**
+ * Parse 'continue' parameter into the list of complete modules and a list of generator parameters
+ * @param array|null $pagesetParams returns list of generator params or null if pageset is done
+ * @param array|null $completeModules returns list of finished modules (as keys), or null if legacy
+ */
+ private function initContinue( &$pagesetParams, &$completeModules ) {
+ $pagesetParams = array();
+ $continue = $this->mParams['continue'];
+ if ( $continue !== null ) {
+ $this->mUseLegacyContinue = false;
+ if ( $continue !== '' ) {
+ // Format: ' pagesetParam1 | pagesetParam2 || module1 | module2 | module3 | ...
+ // If pageset is done, use '-'
+ $continue = explode( '||', $continue );
+ $this->dieContinueUsageIf( count( $continue ) !== 2 );
+ if ( $continue[0] === '-' ) {
+ $pagesetParams = null; // No need to execute pageset
+ } elseif ( $continue[0] !== '' ) {
+ // list of pageset params that might need to be repeated
+ $pagesetParams = explode( '|', $continue[0] );
+ }
+ $continue = $continue[1];
+ }
+ if ( $continue !== '' ) {
+ $completeModules = array_flip( explode( '|', $continue ) );
+ } else {
+ $completeModules = array();
+ }
+ } else {
+ $this->mUseLegacyContinue = true;
+ $completeModules = null;
+ }
+ }
+
+ /**
+ * Validate sub-modules, filter out completed ones, and do requestExtraData()
+ * @param array $allModules An dict of name=>instance of all modules requested by the client
+ * @param array|null $completeModules list of finished modules, or null if legacy continue
+ * @param bool $usePageset True if pageset will be executed
+ * @return array of modules to be processed during this execution
+ */
+ private function initModules( $allModules, $completeModules, $usePageset ) {
+ $modules = $allModules;
+ $tmp = $completeModules;
+ $wasPosted = $this->getRequest()->wasPosted();
+ $main = $this->getMain();
+
+ /** @var $module ApiQueryBase */
+ foreach ( $allModules as $moduleName => $module ) {
+ if ( !$wasPosted && $module->mustBePosted() ) {
+ $this->dieUsageMsgOrDebug( array( 'mustbeposted', $moduleName ) );
+ }
+ if ( $completeModules !== null && array_key_exists( $moduleName, $completeModules ) ) {
+ // If this module is done, mark all its params as used
+ $module->extractRequestParams();
+ // Make sure this module is not used during execution
+ unset( $modules[$moduleName] );
+ unset( $tmp[$moduleName] );
+ } elseif ( $completeModules === null || $usePageset ) {
+ // Query modules may optimize data requests through the $this->getPageSet()
+ // object by adding extra fields from the page table.
+ // This function will gather all the extra request fields from the modules.
+ $module->requestExtraData( $this->mPageSet );
+ } else {
+ // Error - this prop module must have finished before generator is done
+ $this->dieContinueUsageIf( $this->mModuleMgr->getModuleGroup( $moduleName ) === 'prop' );
+ }
+ }
+ $this->dieContinueUsageIf( $completeModules !== null && count( $tmp ) !== 0 );
+ return $modules;
}
/**
@@ -320,32 +432,21 @@ class ApiQuery extends ApiBase {
}
/**
- * Query modules may optimize data requests through the $this->getPageSet() object
- * by adding extra fields from the page table.
- * This function will gather all the extra request fields from the modules.
- * @param $modules array of module objects
- * @param $pageSet ApiPageSet
- */
- private function addCustomFldsToPageSet( $modules, $pageSet ) {
- // Query all requested modules.
- /**
- * @var $module ApiQueryBase
- */
- foreach ( $modules as $module ) {
- $module->requestExtraData( $pageSet );
- }
- }
-
- /**
* Create instances of all modules requested by the client
- * @param $modules Array to append instantiated modules to
- * @param $param string Parameter name to read modules from
- * @param $moduleList Array array(modulename => classname)
+ * @param array $modules to append instantiated modules to
+ * @param string $param Parameter name to read modules from
*/
- private function instantiateModules( &$modules, $param, $moduleList ) {
- if ( isset( $this->params[$param] ) ) {
- foreach ( $this->params[$param] as $moduleName ) {
- $modules[] = new $moduleList[$moduleName] ( $this, $moduleName );
+ private function instantiateModules( &$modules, $param ) {
+ if ( isset( $this->mParams[$param] ) ) {
+ foreach ( $this->mParams[$param] as $moduleName ) {
+ $instance = $this->mModuleMgr->getModule( $moduleName, $param );
+ if ( $instance === null ) {
+ ApiBase::dieDebug( __METHOD__, 'Error instantiating module' );
+ }
+ // Ignore duplicates. TODO 2.0: die()?
+ if ( !array_key_exists( $moduleName, $modules ) ) {
+ $modules[$moduleName] = $instance;
+ }
}
}
}
@@ -363,85 +464,25 @@ class ApiQuery extends ApiBase {
// more than 380K. The maximum revision size is in the megabyte range,
// and the maximum result size must be even higher than that.
- // Title normalizations
- $normValues = array();
- foreach ( $pageSet->getNormalizedTitles() as $rawTitleStr => $titleStr ) {
- $normValues[] = array(
- 'from' => $rawTitleStr,
- 'to' => $titleStr
- );
+ $values = $pageSet->getNormalizedTitlesAsResult( $result );
+ if ( $values ) {
+ $result->addValue( 'query', 'normalized', $values );
}
-
- if ( count( $normValues ) ) {
- $result->setIndexedTagName( $normValues, 'n' );
- $result->addValue( 'query', 'normalized', $normValues );
+ $values = $pageSet->getConvertedTitlesAsResult( $result );
+ if ( $values ) {
+ $result->addValue( 'query', 'converted', $values );
}
-
- // Title conversions
- $convValues = array();
- foreach ( $pageSet->getConvertedTitles() as $rawTitleStr => $titleStr ) {
- $convValues[] = array(
- 'from' => $rawTitleStr,
- 'to' => $titleStr
- );
+ $values = $pageSet->getInterwikiTitlesAsResult( $result, $this->mParams['iwurl'] );
+ if ( $values ) {
+ $result->addValue( 'query', 'interwiki', $values );
}
-
- if ( count( $convValues ) ) {
- $result->setIndexedTagName( $convValues, 'c' );
- $result->addValue( 'query', 'converted', $convValues );
+ $values = $pageSet->getRedirectTitlesAsResult( $result );
+ if ( $values ) {
+ $result->addValue( 'query', 'redirects', $values );
}
-
- // Interwiki titles
- $intrwValues = array();
- foreach ( $pageSet->getInterwikiTitles() as $rawTitleStr => $interwikiStr ) {
- $item = array(
- 'title' => $rawTitleStr,
- 'iw' => $interwikiStr,
- );
- if ( $this->iwUrl ) {
- $title = Title::newFromText( $rawTitleStr );
- $item['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
- }
- $intrwValues[] = $item;
- }
-
- if ( count( $intrwValues ) ) {
- $result->setIndexedTagName( $intrwValues, 'i' );
- $result->addValue( 'query', 'interwiki', $intrwValues );
- }
-
- // Show redirect information
- $redirValues = array();
- /**
- * @var $titleTo Title
- */
- foreach ( $pageSet->getRedirectTitles() as $titleStrFrom => $titleTo ) {
- $r = array(
- 'from' => strval( $titleStrFrom ),
- 'to' => $titleTo->getPrefixedText(),
- );
- if ( $titleTo->getFragment() !== '' ) {
- $r['tofragment'] = $titleTo->getFragment();
- }
- $redirValues[] = $r;
- }
-
- if ( count( $redirValues ) ) {
- $result->setIndexedTagName( $redirValues, 'r' );
- $result->addValue( 'query', 'redirects', $redirValues );
- }
-
- // Missing revision elements
- $missingRevIDs = $pageSet->getMissingRevisionIDs();
- if ( count( $missingRevIDs ) ) {
- $revids = array();
- foreach ( $missingRevIDs as $revid ) {
- $revids[$revid] = array(
- 'revid' => $revid
- );
- }
- $result->setIndexedTagName( $revids, 'rev' );
- $result->addValue( 'query', 'badrevids', $revids );
+ $values = $pageSet->getMissingRevisionIDsAsResult( $result );
+ if ( $values ) {
+ $result->addValue( 'query', 'badrevids', $values );
}
// Page elements
@@ -466,6 +507,7 @@ class ApiQuery extends ApiBase {
);
}
// Report special pages
+ /** @var $title Title */
foreach ( $pageSet->getSpecialTitles() as $fakeId => $title ) {
$vals = array();
ApiQueryBase::addTitleInfo( $vals, $title );
@@ -489,7 +531,7 @@ class ApiQuery extends ApiBase {
}
if ( count( $pages ) ) {
- if ( $this->params['indexpageids'] ) {
+ if ( $this->mParams['indexpageids'] ) {
$pageIDs = array_keys( $pages );
// json treats all map keys as strings - converting to match
$pageIDs = array_map( 'strval', $pageIDs );
@@ -500,21 +542,44 @@ class ApiQuery extends ApiBase {
$result->setIndexedTagName( $pages, 'page' );
$result->addValue( 'query', 'pages', $pages );
}
- if ( $this->params['export'] ) {
+ if ( $this->mParams['export'] ) {
$this->doExport( $pageSet, $result );
}
}
/**
- * @param $pageSet ApiPageSet Pages to be exported
- * @param $result ApiResult Result to output to
+ * This method is called by the generator base when generator in the smart-continue
+ * mode tries to set 'query-continue' value. ApiQuery stores those values separately
+ * until the post-processing when it is known if the generation should continue or repeat.
+ * @param ApiQueryGeneratorBase $module generator module
+ * @param string $paramName
+ * @param mixed $paramValue
+ * @return bool true if processed, false if this is a legacy continue
+ */
+ public function setGeneratorContinue( $module, $paramName, $paramValue ) {
+ if ( $this->mUseLegacyContinue ) {
+ return false;
+ }
+ $paramName = $module->encodeParamName( $paramName );
+ if ( $this->mGeneratorContinue === null ) {
+ $this->mGeneratorContinue = array();
+ }
+ $this->mGeneratorContinue[$paramName] = $paramValue;
+ return true;
+ }
+
+ /**
+ * @param $pageSet ApiPageSet Pages to be exported
+ * @param $result ApiResult Result to output to
*/
- private function doExport( $pageSet, $result ) {
+ private function doExport( $pageSet, $result ) {
$exportTitles = array();
$titles = $pageSet->getGoodTitles();
if ( count( $titles ) ) {
+ $user = $this->getUser();
+ /** @var $title Title */
foreach ( $titles as $title ) {
- if ( $title->userCan( 'read' ) ) {
+ if ( $title->userCan( 'read', $user ) ) {
$exportTitles[] = $title;
}
}
@@ -536,7 +601,7 @@ class ApiQuery extends ApiBase {
// It's not continuable, so it would cause more
// problems than it'd solve
$result->disableSizeCheck();
- if ( $this->params['exportnowrap'] ) {
+ if ( $this->mParams['exportnowrap'] ) {
$result->reset();
// Raw formatter will handle this
$result->addValue( null, 'text', $exportxml );
@@ -549,80 +614,30 @@ class ApiQuery extends ApiBase {
$result->enableSizeCheck();
}
- /**
- * Create a generator object of the given type and return it
- * @param $generatorName string Module name
- * @return ApiQueryGeneratorBase
- */
- public function newGenerator( $generatorName ) {
- // Find class that implements requested generator
- if ( isset( $this->mQueryListModules[$generatorName] ) ) {
- $className = $this->mQueryListModules[$generatorName];
- } elseif ( isset( $this->mQueryPropModules[$generatorName] ) ) {
- $className = $this->mQueryPropModules[$generatorName];
- } else {
- ApiBase::dieDebug( __METHOD__, "Unknown generator=$generatorName" );
- }
- $generator = new $className ( $this, $generatorName );
- if ( !$generator instanceof ApiQueryGeneratorBase ) {
- $this->dieUsage( "Module $generatorName cannot be used as a generator", 'badgenerator' );
- }
- $generator->setGeneratorMode();
- return $generator;
- }
-
- /**
- * For generator mode, execute generator, and use its output as new
- * ApiPageSet
- * @param $generator ApiQueryGeneratorBase Generator Module
- * @param $modules array of module objects
- */
- protected function executeGeneratorModule( $generator, $modules ) {
- // Generator results
- $resultPageSet = new ApiPageSet( $this, $this->redirects, $this->convertTitles );
-
- // Add any additional fields modules may need
- $generator->requestExtraData( $this->mPageSet );
- $this->addCustomFldsToPageSet( $modules, $resultPageSet );
-
- // Populate page information with the original user input
- $this->mPageSet->execute();
-
- // populate resultPageSet with the generator output
- $generator->profileIn();
- $generator->executeGenerator( $resultPageSet );
- wfRunHooks( 'APIQueryGeneratorAfterExecute', array( &$generator, &$resultPageSet ) );
- $resultPageSet->finishPageSetGeneration();
- $generator->profileOut();
-
- // Swap the resulting pageset back in
- $this->mPageSet = $resultPageSet;
- }
-
- public function getAllowedParams() {
- return array(
+ public function getAllowedParams( $flags = 0 ) {
+ $result = array(
'prop' => array(
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => $this->mPropModuleNames
+ ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'prop' )
),
'list' => array(
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => $this->mListModuleNames
+ ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'list' )
),
'meta' => array(
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => $this->mMetaModuleNames
+ ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'meta' )
),
- 'generator' => array(
- ApiBase::PARAM_TYPE => $this->mAllowedGenerators
- ),
- 'redirects' => false,
- 'converttitles' => false,
'indexpageids' => false,
'export' => false,
'exportnowrap' => false,
'iwurl' => false,
+ 'continue' => null,
);
+ if ( $flags ) {
+ $result += $this->getPageSet()->getFinalParams( $flags );
+ }
+ return $result;
}
/**
@@ -630,42 +645,40 @@ class ApiQuery extends ApiBase {
* @return string
*/
public function makeHelpMsg() {
- // Make sure the internal object is empty
- // (just in case a sub-module decides to optimize during instantiation)
- $this->mPageSet = null;
+
+ // Use parent to make default message for the query module
+ $msg = parent::makeHelpMsg();
$querySeparator = str_repeat( '--- ', 12 );
$moduleSeparator = str_repeat( '*** ', 14 );
- $msg = "\n$querySeparator Query: Prop $querySeparator\n\n";
- $msg .= $this->makeHelpMsgHelper( $this->mQueryPropModules, 'prop' );
+ $msg .= "\n$querySeparator Query: Prop $querySeparator\n\n";
+ $msg .= $this->makeHelpMsgHelper( 'prop' );
$msg .= "\n$querySeparator Query: List $querySeparator\n\n";
- $msg .= $this->makeHelpMsgHelper( $this->mQueryListModules, 'list' );
+ $msg .= $this->makeHelpMsgHelper( 'list' );
$msg .= "\n$querySeparator Query: Meta $querySeparator\n\n";
- $msg .= $this->makeHelpMsgHelper( $this->mQueryMetaModules, 'meta' );
+ $msg .= $this->makeHelpMsgHelper( 'meta' );
$msg .= "\n\n$moduleSeparator Modules: continuation $moduleSeparator\n\n";
- // Use parent to make default message for the query module
- $msg = parent::makeHelpMsg() . $msg;
-
return $msg;
}
/**
- * For all modules in $moduleList, generate help messages and join them together
- * @param $moduleList Array array(modulename => classname)
- * @param $paramName string Parameter name
+ * For all modules of a given group, generate help messages and join them together
+ * @param string $group Module group
* @return string
*/
- private function makeHelpMsgHelper( $moduleList, $paramName ) {
+ private function makeHelpMsgHelper( $group ) {
$moduleDescriptions = array();
- foreach ( $moduleList as $moduleName => $moduleClass ) {
+ $moduleNames = $this->mModuleMgr->getNames( $group );
+ sort( $moduleNames );
+ foreach ( $moduleNames as $name ) {
/**
* @var $module ApiQueryBase
*/
- $module = new $moduleClass( $this, $moduleName, null );
+ $module = $this->mModuleMgr->getModule( $name );
- $msg = ApiMain::makeHelpMsgHeader( $module, $paramName );
+ $msg = ApiMain::makeHelpMsgHeader( $module, $group );
$msg2 = $module->makeHelpMsg();
if ( $msg2 !== false ) {
$msg .= $msg2;
@@ -679,46 +692,23 @@ class ApiQuery extends ApiBase {
return implode( "\n", $moduleDescriptions );
}
- /**
- * Adds any classes that are a subclass of ApiQueryGeneratorBase
- * to the allowed generator list
- * @param $moduleList array()
- */
- private function makeGeneratorList( $moduleList ) {
- foreach( $moduleList as $moduleName => $moduleClass ) {
- if ( is_subclass_of( $moduleClass, 'ApiQueryGeneratorBase' ) ) {
- $this->mAllowedGenerators[] = $moduleName;
- }
- }
- }
-
- /**
- * Override to add extra parameters from PageSet
- * @return string
- */
- public function makeHelpMsgParameters() {
- $psModule = new ApiPageSet( $this );
- return $psModule->makeHelpMsgParameters() . parent::makeHelpMsgParameters();
- }
-
public function shouldCheckMaxlag() {
return true;
}
public function getParamDescription() {
- return array(
+ return $this->getPageSet()->getParamDescription() + array(
'prop' => 'Which properties to get for the titles/revisions/pageids. Module help is available below',
'list' => 'Which lists to get. Module help is available below',
'meta' => 'Which metadata to get about the site. Module help is available below',
- 'generator' => array( 'Use the output of a list as the input for other prop/list/meta items',
- 'NOTE: generator parameter names must be prefixed with a \'g\', see examples' ),
- 'redirects' => 'Automatically resolve redirects',
- 'converttitles' => array( "Convert titles to other variants if necessary. Only works if the wiki's content language supports variant conversion.",
- 'Languages that support variant conversion include ' . implode( ', ', LanguageConverter::$languagesWithVariants ) ),
'indexpageids' => 'Include an additional pageids section listing all returned page IDs',
'export' => 'Export the current revisions of all given or generated pages',
'exportnowrap' => 'Return the export XML without wrapping it in an XML result (same format as Special:Export). Can only be used with export',
'iwurl' => 'Whether to get the full URL if the title is an interwiki link',
+ 'continue' => array(
+ 'When present, formats query-continue as key-value pairs that should simply be merged into the original request.',
+ 'This parameter must be set to an empty string in the initial query.',
+ 'This parameter is recommended for all new development, and will be made default in the next API version.' ),
);
}
@@ -731,15 +721,16 @@ class ApiQuery extends ApiBase {
}
public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'badgenerator', 'info' => 'Module $generatorName cannot be used as a generator' ),
- ) );
+ return array_merge(
+ parent::getPossibleErrors(),
+ $this->getPageSet()->getPossibleErrors()
+ );
}
public function getExamples() {
return array(
- 'api.php?action=query&prop=revisions&meta=siteinfo&titles=Main%20Page&rvprop=user|comment',
- 'api.php?action=query&generator=allpages&gapprefix=API/&prop=revisions',
+ 'api.php?action=query&prop=revisions&meta=siteinfo&titles=Main%20Page&rvprop=user|comment&continue=',
+ 'api.php?action=query&generator=allpages&gapprefix=API/&prop=revisions&continue=',
);
}
@@ -750,12 +741,4 @@ class ApiQuery extends ApiBase {
'https://www.mediawiki.org/wiki/API:Lists',
);
}
-
- public function getVersion() {
- $psModule = new ApiPageSet( $this );
- $vers = array();
- $vers[] = __CLASS__ . ': $Id$';
- $vers[] = $psModule->getVersion();
- return $vers;
- }
}
diff --git a/includes/api/ApiQueryAllCategories.php b/includes/api/ApiQueryAllCategories.php
index 4f4c77f0..496a0eb8 100644
--- a/includes/api/ApiQueryAllCategories.php
+++ b/includes/api/ApiQueryAllCategories.php
@@ -60,10 +60,7 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 1 ) {
- $this->dieUsage( "Invalid continue param. You should pass the " .
- "original value returned by the previous query", "_badcontinue" );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 1 );
$op = $params['dir'] == 'descending' ? '<' : '>';
$cont_from = $db->addQuotes( $cont[0] );
$this->addWhere( "cat_title $op= $cont_from" );
@@ -81,7 +78,6 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
} else {
$this->addWhereRange( 'cat_pages', 'older', $max, $min);
}
-
if ( isset( $params['prefix'] ) ) {
$this->addWhere( 'cat_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
@@ -225,12 +221,6 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
return 'Enumerate all categories';
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
- ) );
- }
-
public function getExamples() {
return array(
'api.php?action=query&list=allcategories&acprop=size',
@@ -241,8 +231,4 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Allcategories';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryAllImages.php b/includes/api/ApiQueryAllImages.php
index b562da8e..e24b162c 100644
--- a/includes/api/ApiQueryAllImages.php
+++ b/includes/api/ApiQueryAllImages.php
@@ -41,8 +41,8 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
}
/**
- * Override parent method to make sure to make sure the repo's DB is used
- * which may not necesarilly be the same as the local DB.
+ * Override parent method to make sure the repo's DB is used
+ * which may not necessarily be the same as the local DB.
*
* TODO: allow querying non-local repos.
* @return DatabaseBase
@@ -93,7 +93,10 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
$prop = array_flip( $params['prop'] );
$this->addFields( LocalFile::selectFields() );
- $dir = ( in_array( $params['dir'], array( 'descending', 'older' ) ) ? 'older' : 'newer' );
+ $ascendingOrder = true;
+ if ( $params['dir'] == 'descending' || $params['dir'] == 'older' ) {
+ $ascendingOrder = false;
+ }
if ( $params['sort'] == 'name' ) {
// Check mutually exclusive params
@@ -110,19 +113,16 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
// Pagination
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 1 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the ' .
- 'original value returned by the previous query', '_badcontinue' );
- }
- $op = ( $dir == 'older' ? '<' : '>' );
- $cont_from = $db->addQuotes( $cont[0] );
- $this->addWhere( "img_name $op= $cont_from" );
+ $this->dieContinueUsageIf( count( $cont ) != 1 );
+ $op = ( $ascendingOrder ? '>' : '<' );
+ $continueFrom = $db->addQuotes( $cont[0] );
+ $this->addWhere( "img_name $op= $continueFrom" );
}
// Image filters
$from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
$to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
- $this->addWhereRange( 'img_name', $dir, $from, $to );
+ $this->addWhereRange( 'img_name', ( $ascendingOrder ? 'newer' : 'older' ), $from, $to );
if ( isset( $params['prefix'] ) ) {
$this->addWhere( 'img_name' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
@@ -135,13 +135,13 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
$this->dieUsage( "Parameter '{$prefix}{$pname}' can only be used with {$prefix}sort=name", 'badparams' );
}
}
- if (!is_null( $params['user'] ) && $params['filterbots'] != 'all') {
+ if ( !is_null( $params['user'] ) && $params['filterbots'] != 'all' ) {
// Since filterbots checks if each user has the bot right, it doesn't make sense to use it with user
- $this->dieUsage( "Parameters 'user' and 'filterbots' cannot be used together", 'badparams' );
+ $this->dieUsage( "Parameters '{$prefix}user' and '{$prefix}filterbots' cannot be used together", 'badparams' );
}
// Pagination
- $this->addTimestampWhereRange( 'img_timestamp', $dir, $params['start'], $params['end'] );
+ $this->addTimestampWhereRange( 'img_timestamp', ( $ascendingOrder ? 'newer' : 'older' ), $params['start'], $params['end'] );
// Image filters
if ( !is_null( $params['user'] ) ) {
@@ -149,8 +149,6 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
}
if ( $params['filterbots'] != 'all' ) {
$this->addTables( 'user_groups' );
- $groupCond = ( $params['filterbots'] == 'nobots' ? 'NULL': 'NOT NULL' );
- $this->addWhere( "ug_group IS $groupCond" );
$this->addJoinConds( array( 'user_groups' => array(
'LEFT JOIN',
array(
@@ -158,6 +156,8 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
'ug_user = img_user'
)
) ) );
+ $groupCond = ( $params['filterbots'] == 'nobots' ? 'NULL': 'NOT NULL' );
+ $this->addWhere( "ug_group IS $groupCond" );
}
}
@@ -172,12 +172,13 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
$sha1 = false;
if ( isset( $params['sha1'] ) ) {
- if ( !$this->validateSha1Hash( $params['sha1'] ) ) {
+ $sha1 = strtolower( $params['sha1'] );
+ if ( !$this->validateSha1Hash( $sha1 ) ) {
$this->dieUsage( 'The SHA1 hash provided is not valid', 'invalidsha1hash' );
}
- $sha1 = wfBaseConvert( $params['sha1'], 16, 36, 31 );
+ $sha1 = wfBaseConvert( $sha1, 16, 36, 31 );
} elseif ( isset( $params['sha1base36'] ) ) {
- $sha1 = $params['sha1base36'];
+ $sha1 = strtolower( $params['sha1base36'] );
if ( !$this->validateSha1Base36Hash( $sha1 ) ) {
$this->dieUsage( 'The SHA1Base36 hash provided is not valid', 'invalidsha1base36hash' );
}
@@ -200,16 +201,19 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
$limit = $params['limit'];
$this->addOption( 'LIMIT', $limit + 1 );
- $sort = ( $dir == 'older' ? ' DESC' : '' );
+ $sortFlag = '';
+ if ( !$ascendingOrder ) {
+ $sortFlag = ' DESC';
+ }
if ( $params['sort'] == 'timestamp' ) {
- $this->addOption( 'ORDER BY', 'img_timestamp' . $sort );
- if ( $params['filterbots'] == 'all' ) {
- $this->addOption( 'USE INDEX', array( 'image' => 'img_timestamp' ) );
- } else {
+ $this->addOption( 'ORDER BY', 'img_timestamp' . $sortFlag );
+ if ( !is_null( $params['user'] ) ) {
$this->addOption( 'USE INDEX', array( 'image' => 'img_usertext_timestamp' ) );
+ } else {
+ $this->addOption( 'USE INDEX', array( 'image' => 'img_timestamp' ) );
}
} else {
- $this->addOption( 'ORDER BY', 'img_name' . $sort );
+ $this->addOption( 'ORDER BY', 'img_name' . $sortFlag );
}
$res = $this->select( __METHOD__ );
@@ -272,7 +276,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
'descending',
// sort=timestamp
'newer',
- 'older',
+ 'older'
)
),
'from' => null,
@@ -373,12 +377,11 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
array( 'code' => 'badparams', 'info' => "Parameter'{$p}from' can only be used with {$p}sort=name" ),
array( 'code' => 'badparams', 'info' => "Parameter'{$p}to' can only be used with {$p}sort=name" ),
array( 'code' => 'badparams', 'info' => "Parameter'{$p}prefix' can only be used with {$p}sort=name" ),
- array( 'code' => 'badparams', 'info' => "Parameters 'user' and 'filterbots' cannot be used together" ),
+ array( 'code' => 'badparams', 'info' => "Parameters '{$p}user' and '{$p}filterbots' cannot be used together" ),
array( 'code' => 'unsupportedrepo', 'info' => 'Local file repository does not support querying all images' ),
array( 'code' => 'mimesearchdisabled', 'info' => 'MIME search disabled in Miser Mode' ),
array( 'code' => 'invalidsha1hash', 'info' => 'The SHA1 hash provided is not valid' ),
array( 'code' => 'invalidsha1base36hash', 'info' => 'The SHA1Base36 hash provided is not valid' ),
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
) );
}
@@ -402,8 +405,4 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Allimages';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryAllLinks.php b/includes/api/ApiQueryAllLinks.php
index da4840f0..e355f8b0 100644
--- a/includes/api/ApiQueryAllLinks.php
+++ b/includes/api/ApiQueryAllLinks.php
@@ -32,7 +32,34 @@
class ApiQueryAllLinks extends ApiQueryGeneratorBase {
public function __construct( $query, $moduleName ) {
- parent::__construct( $query, $moduleName, 'al' );
+ switch ( $moduleName ) {
+ case 'alllinks':
+ $prefix = 'al';
+ $this->table = 'pagelinks';
+ $this->tablePrefix = 'pl_';
+ $this->dfltNamespace = NS_MAIN;
+ $this->indexTag = 'l';
+ $this->description = 'Enumerate all links that point to a given namespace';
+ $this->descriptionLink = 'link';
+ $this->descriptionLinked = 'linked';
+ $this->descriptionLinking = 'linking';
+ break;
+ case 'alltransclusions':
+ $prefix = 'at';
+ $this->table = 'templatelinks';
+ $this->tablePrefix = 'tl_';
+ $this->dfltNamespace = NS_TEMPLATE;
+ $this->indexTag = 't';
+ $this->description = 'List all transclusions (pages embedded using {{x}}), including non-existing';
+ $this->descriptionLink = 'transclusion';
+ $this->descriptionLinked = 'transcluded';
+ $this->descriptionLinking = 'transcluding';
+ break;
+ default:
+ ApiBase::dieDebug( __METHOD__, 'Unknown module name' );
+ }
+
+ parent::__construct( $query, $moduleName, $prefix );
}
public function execute() {
@@ -55,75 +82,71 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$db = $this->getDB();
$params = $this->extractRequestParams();
+ $pfx = $this->tablePrefix;
$prop = array_flip( $params['prop'] );
$fld_ids = isset( $prop['ids'] );
$fld_title = isset( $prop['title'] );
if ( $params['unique'] ) {
- if ( !is_null( $resultPageSet ) ) {
- $this->dieUsage( $this->getModuleName() . ' cannot be used as a generator in unique links mode', 'params' );
- }
if ( $fld_ids ) {
- $this->dieUsage( $this->getModuleName() . ' cannot return corresponding page ids in unique links mode', 'params' );
+ $this->dieUsage(
+ "{$this->getModuleName()} cannot return corresponding page ids in unique {$this->descriptionLink}s mode",
+ 'params' );
}
$this->addOption( 'DISTINCT' );
}
- $this->addTables( 'pagelinks' );
- $this->addWhereFld( 'pl_namespace', $params['namespace'] );
+ $this->addTables( $this->table );
+ $this->addWhereFld( $pfx . 'namespace', $params['namespace'] );
- if ( !is_null( $params['from'] ) && !is_null( $params['continue'] ) ) {
- $this->dieUsage( 'alcontinue and alfrom cannot be used together', 'params' );
- }
- if ( !is_null( $params['continue'] ) ) {
+ $continue = !is_null( $params['continue'] );
+ if ( $continue ) {
$continueArr = explode( '|', $params['continue'] );
$op = $params['dir'] == 'descending' ? '<' : '>';
if ( $params['unique'] ) {
- if ( count( $continueArr ) != 1 ) {
- $this->dieUsage( 'Invalid continue parameter', 'badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $continueArr ) != 1 );
$continueTitle = $db->addQuotes( $continueArr[0] );
- $this->addWhere( "pl_title $op= $continueTitle" );
+ $this->addWhere( "{$pfx}title $op= $continueTitle" );
} else {
- if ( count( $continueArr ) != 2 ) {
- $this->dieUsage( 'Invalid continue parameter', 'badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $continueArr ) != 2 );
$continueTitle = $db->addQuotes( $continueArr[0] );
$continueFrom = intval( $continueArr[1] );
$this->addWhere(
- "pl_title $op $continueTitle OR " .
- "(pl_title = $continueTitle AND " .
- "pl_from $op= $continueFrom)"
+ "{$pfx}title $op $continueTitle OR " .
+ "({$pfx}title = $continueTitle AND " .
+ "{$pfx}from $op= $continueFrom)"
);
}
}
- $from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
+ // 'continue' always overrides 'from'
+ $from = ( $continue || is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
$to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
- $this->addWhereRange( 'pl_title', 'newer', $from, $to );
+ $this->addWhereRange( $pfx . 'title', 'newer', $from, $to );
if ( isset( $params['prefix'] ) ) {
- $this->addWhere( 'pl_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
+ $this->addWhere( $pfx . 'title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
}
- $this->addFields( 'pl_title' );
- $this->addFieldsIf( 'pl_from', !$params['unique'] );
+ $this->addFields( array( 'pl_title' => $pfx . 'title' ) );
+ $this->addFieldsIf( array( 'pl_from' => $pfx . 'from' ), !$params['unique'] );
- $this->addOption( 'USE INDEX', 'pl_namespace' );
+ $this->addOption( 'USE INDEX', $pfx . 'namespace' );
$limit = $params['limit'];
$this->addOption( 'LIMIT', $limit + 1 );
$sort = ( $params['dir'] == 'descending' ? ' DESC' : '' );
$orderBy = array();
- $orderBy[] = 'pl_title' . $sort;
+ $orderBy[] = $pfx . 'title' . $sort;
if ( !$params['unique'] ) {
- $orderBy[] = 'pl_from' . $sort;
+ $orderBy[] = $pfx . 'from' . $sort;
}
$this->addOption( 'ORDER BY', $orderBy );
$res = $this->select( __METHOD__ );
$pageids = array();
+ $titles = array();
$count = 0;
$result = $this->getResult();
foreach ( $res as $row ) {
@@ -132,7 +155,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
if ( $params['unique'] ) {
$this->setContinueEnumParameter( 'continue', $row->pl_title );
} else {
- $this->setContinueEnumParameter( 'continue', $row->pl_title . "|" . $row->pl_from );
+ $this->setContinueEnumParameter( 'continue', $row->pl_title . '|' . $row->pl_from );
}
break;
}
@@ -151,17 +174,21 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
if ( $params['unique'] ) {
$this->setContinueEnumParameter( 'continue', $row->pl_title );
} else {
- $this->setContinueEnumParameter( 'continue', $row->pl_title . "|" . $row->pl_from );
+ $this->setContinueEnumParameter( 'continue', $row->pl_title . '|' . $row->pl_from );
}
break;
}
+ } elseif ( $params['unique'] ) {
+ $titles[] = Title::makeTitle( $params['namespace'], $row->pl_title );
} else {
$pageids[] = $row->pl_from;
}
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'l' );
+ $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), $this->indexTag );
+ } elseif ( $params['unique'] ) {
+ $resultPageSet->populateFromTitles( $titles );
} else {
$resultPageSet->populateFromPageIDs( $pageids );
}
@@ -183,7 +210,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
)
),
'namespace' => array(
- ApiBase::PARAM_DFLT => 0,
+ ApiBase::PARAM_DFLT => $this->dfltNamespace,
ApiBase::PARAM_TYPE => 'namespace'
),
'limit' => array(
@@ -205,18 +232,23 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+ $link = $this->descriptionLink;
+ $linking = $this->descriptionLinking;
return array(
- 'from' => 'The page title to start enumerating from',
- 'to' => 'The page title to stop enumerating at',
- 'prefix' => 'Search for all page titles that begin with this value',
- 'unique' => "Only show unique links. Cannot be used with generator or {$p}prop=ids",
+ 'from' => "The title of the $link to start enumerating from",
+ 'to' => "The title of the $link to stop enumerating at",
+ 'prefix' => "Search for all $link titles that begin with this value",
+ 'unique' => array(
+ "Only show distinct $link titles. Cannot be used with {$p}prop=ids.",
+ 'When used as a generator, yields target pages instead of source pages.',
+ ),
'prop' => array(
'What pieces of information to include',
- " ids - Adds pageid of where the link is from (Cannot be used with {$p}unique)",
- ' title - Adds the title of the link',
+ " ids - Adds the pageid of the $linking page (Cannot be used with {$p}unique)",
+ " title - Adds the title of the $link",
),
'namespace' => 'The namespace to enumerate',
- 'limit' => 'How many total links to return',
+ 'limit' => "How many total items to return",
'continue' => 'When more results are available, use this to continue',
'dir' => 'The direction in which to list',
);
@@ -235,30 +267,34 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
}
public function getDescription() {
- return 'Enumerate all links that point to a given namespace';
+ return $this->description;
}
public function getPossibleErrors() {
$m = $this->getModuleName();
+ $link = $this->descriptionLink;
return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'params', 'info' => "{$m} cannot be used as a generator in unique links mode" ),
- array( 'code' => 'params', 'info' => "{$m} cannot return corresponding page ids in unique links mode" ),
- array( 'code' => 'params', 'info' => 'alcontinue and alfrom cannot be used together' ),
- array( 'code' => 'badcontinue', 'info' => 'Invalid continue parameter' ),
+ array( 'code' => 'params', 'info' => "{$m} cannot return corresponding page ids in unique {$link}s mode" ),
) );
}
public function getExamples() {
+ $p = $this->getModulePrefix();
+ $link = $this->descriptionLink;
+ $linked = $this->descriptionLinked;
return array(
- 'api.php?action=query&list=alllinks&alunique=&alfrom=B',
+ "api.php?action=query&list=all{$link}s&{$p}from=B&{$p}prop=ids|title"
+ => "List $linked titles with page ids they are from, including missing ones. Start at B",
+ "api.php?action=query&list=all{$link}s&{$p}unique=&{$p}from=B"
+ => "List unique $linked titles",
+ "api.php?action=query&generator=all{$link}s&g{$p}unique=&g{$p}from=B"
+ => "Gets all $link targets, marking the missing ones",
+ "api.php?action=query&generator=all{$link}s&g{$p}from=B"
+ => "Gets pages containing the {$link}s",
);
}
public function getHelpUrls() {
- return 'https://www.mediawiki.org/wiki/API:Alllinks';
- }
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
+ return "https://www.mediawiki.org/wiki/API:All{$this->descriptionLink}s";
}
}
diff --git a/includes/api/ApiQueryAllMessages.php b/includes/api/ApiQueryAllMessages.php
index f5e1146b..c9811b0d 100644
--- a/includes/api/ApiQueryAllMessages.php
+++ b/includes/api/ApiQueryAllMessages.php
@@ -39,8 +39,9 @@ class ApiQueryAllMessages extends ApiQueryBase {
$params = $this->extractRequestParams();
if ( is_null( $params['lang'] ) ) {
- global $wgLang;
- $langObj = $wgLang;
+ $langObj = $this->getLanguage();
+ } elseif ( !Language::isValidCode( $params['lang'] ) ) {
+ $this->dieUsage( 'Invalid language code for parameter lang', 'invalidlang' );
} else {
$langObj = Language::factory( $params['lang'] );
}
@@ -48,7 +49,7 @@ class ApiQueryAllMessages extends ApiQueryBase {
if ( $params['enableparser'] ) {
if ( !is_null( $params['title'] ) ) {
$title = Title::newFromText( $params['title'] );
- if ( !$title ) {
+ if ( !$title || $title->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
} else {
@@ -116,7 +117,7 @@ class ApiQueryAllMessages extends ApiQueryBase {
$lang = $langObj->getCode();
$customisedMessages = AllmessagesTablePager::getCustomisedStatuses(
- array_map( array( $langObj, 'ucfirst'), $messages_target ), $lang, $lang != $wgContLang->getCode() );
+ array_map( array( $langObj, 'ucfirst' ), $messages_target ), $lang, $lang != $wgContLang->getCode() );
$customised = $params['customised'] === 'modified';
}
@@ -143,7 +144,7 @@ class ApiQueryAllMessages extends ApiQueryBase {
}
if ( $customiseFilterEnabled ) {
- $messageIsCustomised = isset( $customisedMessages['pages'][ $langObj->ucfirst( $message ) ] );
+ $messageIsCustomised = isset( $customisedMessages['pages'][$langObj->ucfirst( $message )] );
if ( $customised === $messageIsCustomised ) {
if ( $customised ) {
$a['customised'] = '';
@@ -256,6 +257,12 @@ class ApiQueryAllMessages extends ApiQueryBase {
);
}
+ public function getPossibleErrors() {
+ return array_merge( parent::getPossibleErrors(), array(
+ array( 'code' => 'invalidlang', 'info' => 'Invalid language code for parameter lang' ),
+ ) );
+ }
+
public function getResultProperties() {
return array(
'' => array(
@@ -291,8 +298,4 @@ class ApiQueryAllMessages extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Meta#allmessages_.2F_am';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryAllPages.php b/includes/api/ApiQueryAllPages.php
index 16cc31d2..d718b967 100644
--- a/includes/api/ApiQueryAllPages.php
+++ b/includes/api/ApiQueryAllPages.php
@@ -69,10 +69,7 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 1 ) {
- $this->dieUsage( "Invalid continue param. You should pass the " .
- "original value returned by the previous query", "_badcontinue" );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 1 );
$op = $params['dir'] == 'descending' ? '<' : '>';
$cont_from = $db->addQuotes( $cont[0] );
$this->addWhere( "page_title $op= $cont_from" );
@@ -120,7 +117,7 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
if ( count( $params['prtype'] ) || $params['prexpiry'] != 'all' ) {
$this->addTables( 'page_restrictions' );
$this->addWhere( 'page_id=pr_page' );
- $this->addWhere( 'pr_expiry>' . $db->addQuotes( $db->timestamp() ) );
+ $this->addWhere( "pr_expiry > {$db->addQuotes( $db->timestamp() )} OR pr_expiry IS NULL" );
if ( count( $params['prtype'] ) ) {
$this->addWhereFld( 'pr_type', $params['prtype'] );
@@ -138,8 +135,6 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
} elseif ( $params['prfiltercascade'] == 'noncascading' ) {
$this->addWhereFld( 'pr_cascade', 0 );
}
-
- $this->addOption( 'DISTINCT' );
}
$forceNameTitleIndex = false;
@@ -149,6 +144,8 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
$this->addWhere( "pr_expiry != {$db->addQuotes( $db->getInfinity() )}" );
}
+ $this->addOption( 'DISTINCT' );
+
} elseif ( isset( $params['prlevel'] ) ) {
$this->dieUsage( 'prlevel may not be used without prtype', 'params' );
}
@@ -226,7 +223,7 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
'to' => null,
'prefix' => null,
'namespace' => array(
- ApiBase::PARAM_DFLT => 0,
+ ApiBase::PARAM_DFLT => NS_MAIN,
ApiBase::PARAM_TYPE => 'namespace',
),
'filterredir' => array(
@@ -336,7 +333,6 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'params', 'info' => 'Use "gapfilterredir=nonredirects" option instead of "redirects" when using allpages as a generator' ),
array( 'code' => 'params', 'info' => 'prlevel may not be used without prtype' ),
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
) );
}
@@ -351,7 +347,7 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
'Show info about 4 pages starting at the letter "T"',
),
'api.php?action=query&generator=allpages&gaplimit=2&gapfilterredir=nonredirects&gapfrom=Re&prop=revisions&rvprop=content' => array(
- 'Show content of first 2 non-redirect pages begining at "Re"',
+ 'Show content of first 2 non-redirect pages beginning at "Re"',
)
);
}
@@ -359,8 +355,4 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Allpages';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryAllUsers.php b/includes/api/ApiQueryAllUsers.php
index 7f50cbad..7283aa00 100644
--- a/includes/api/ApiQueryAllUsers.php
+++ b/includes/api/ApiQueryAllUsers.php
@@ -37,7 +37,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
/**
* This function converts the user name to a canonical form
* which is stored in the database.
- * @param String $name
+ * @param string $name
* @return String
*/
private function getCanonicalUserName( $name ) {
@@ -81,12 +81,18 @@ class ApiQueryAllUsers extends ApiQueryBase {
$db->buildLike( $this->getCanonicalUserName( $params['prefix'] ), $db->anyString() ) );
}
- if ( !is_null( $params['rights'] ) ) {
+ if ( !is_null( $params['rights'] ) && count( $params['rights'] ) ) {
$groups = array();
foreach( $params['rights'] as $r ) {
$groups = array_merge( $groups, User::getGroupsWithPermission( $r ) );
}
+ // no group with the given right(s) exists, no need for a query
+ if( !count( $groups ) ) {
+ $this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), '' );
+ return;
+ }
+
$groups = array_unique( $groups );
if ( is_null( $params['group'] ) ) {
@@ -155,7 +161,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
$this->addFields( array( 'recentedits' => 'COUNT(*)' ) );
$this->addWhere( 'rc_log_type IS NULL OR rc_log_type != ' . $db->addQuotes( 'newusers' ) );
- $timestamp = $db->timestamp( wfTimestamp( TS_UNIX ) - $wgActiveUserDays*24*3600 );
+ $timestamp = $db->timestamp( wfTimestamp( TS_UNIX ) - $wgActiveUserDays * 24 * 3600 );
$this->addWhere( 'rc_timestamp >= ' . $db->addQuotes( $timestamp ) );
$this->addOption( 'GROUP BY', $userFieldToSort );
@@ -273,7 +279,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
if ( $fld_rights ) {
if ( !isset( $lastUserData['rights'] ) ) {
if ( $lastUserObj ) {
- $lastUserData['rights'] = User::getGroupPermissions( $lastUserObj->getAutomaticGroups() );
+ $lastUserData['rights'] = User::getGroupPermissions( $lastUserObj->getAutomaticGroups() );
} else {
// This should not normally happen
$lastUserData['rights'] = array();
@@ -438,8 +444,4 @@ class ApiQueryAllUsers extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Allusers';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryBacklinks.php b/includes/api/ApiQueryBacklinks.php
index 06db87bf..3ef6b840 100644
--- a/includes/api/ApiQueryBacklinks.php
+++ b/includes/api/ApiQueryBacklinks.php
@@ -188,6 +188,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$titleWhere = array();
$allRedirNs = array();
$allRedirDBkey = array();
+ /** @var $t Title */
foreach ( $this->redirTitles as $t ) {
$redirNs = $t->getNamespace();
$redirDBkey = $t->getDBkey();
@@ -201,6 +202,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
if ( !is_null( $this->redirID ) ) {
$op = $this->params['dir'] == 'descending' ? '<' : '>';
+ /** @var $first Title */
$first = $this->redirTitles[0];
$title = $db->addQuotes( $first->getDBkey() );
$ns = $first->getNamespace();
@@ -246,7 +248,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$this->params = $this->extractRequestParams( false );
$this->redirect = isset( $this->params['redirect'] ) && $this->params['redirect'];
$userMax = ( $this->redirect ? ApiBase::LIMIT_BIG1 / 2 : ApiBase::LIMIT_BIG1 );
- $botMax = ( $this->redirect ? ApiBase::LIMIT_BIG2 / 2 : ApiBase::LIMIT_BIG2 );
+ $botMax = ( $this->redirect ? ApiBase::LIMIT_BIG2 / 2 : ApiBase::LIMIT_BIG2 );
$result = $this->getResult();
@@ -406,20 +408,14 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
// null stuff out now so we know what's set and what isn't
$this->rootTitle = $this->contID = $this->redirID = null;
$rootNs = intval( $continueList[0] );
- if ( $rootNs === 0 && $continueList[0] !== '0' ) {
- // Illegal continue parameter
- $this->dieUsage( 'Invalid continue param. You should pass the original value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( $rootNs === 0 && $continueList[0] !== '0' );
+
$this->rootTitle = Title::makeTitleSafe( $rootNs, $continueList[1] );
+ $this->dieContinueUsageIf( !$this->rootTitle );
- if ( !$this->rootTitle ) {
- $this->dieUsage( 'Invalid continue param. You should pass the original value returned by the previous query', '_badcontinue' );
- }
$contID = intval( $continueList[2] );
+ $this->dieContinueUsageIf( $contID === 0 && $continueList[2] !== '0' );
- if ( $contID === 0 && $continueList[2] !== '0' ) {
- $this->dieUsage( 'Invalid continue param. You should pass the original value returned by the previous query', '_badcontinue' );
- }
$this->contID = $contID;
$id2 = isset( $continueList[3] ) ? $continueList[3] : null;
$redirID = intval( $id2 );
@@ -455,12 +451,12 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => 'namespace'
),
- 'dir' => array(
- ApiBase::PARAM_DFLT => 'ascending',
- ApiBase::PARAM_TYPE => array(
- 'ascending',
- 'descending'
- )
+ 'dir' => array(
+ ApiBase::PARAM_DFLT => 'ascending',
+ ApiBase::PARAM_TYPE => array(
+ 'ascending',
+ 'descending'
+ )
),
'filterredir' => array(
ApiBase::PARAM_DFLT => 'all',
@@ -535,7 +531,6 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$this->getTitleOrPageIdErrorMessage(),
array(
array( 'code' => 'bad_image_title', 'info' => "The title for {$this->getModuleName()} query must be an image" ),
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
)
);
}
@@ -562,8 +557,4 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return $this->helpUrl;
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php
index 2c48aca0..7819ead4 100644
--- a/includes/api/ApiQueryBase.php
+++ b/includes/api/ApiQueryBase.php
@@ -112,7 +112,7 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Add a set of fields to select to the internal array
- * @param $value array|string Field name or array of field names
+ * @param array|string $value Field name or array of field names
*/
protected function addFields( $value ) {
if ( is_array( $value ) ) {
@@ -124,8 +124,8 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Same as addFields(), but add the fields only if a condition is met
- * @param $value array|string See addFields()
- * @param $condition bool If false, do nothing
+ * @param array|string $value See addFields()
+ * @param bool $condition If false, do nothing
* @return bool $condition
*/
protected function addFieldsIf( $value, $condition ) {
@@ -162,7 +162,7 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Same as addWhere(), but add the WHERE clauses only if a condition is met
* @param $value mixed See addWhere()
- * @param $condition bool If false, do nothing
+ * @param bool $condition If false, do nothing
* @return bool $condition
*/
protected function addWhereIf( $value, $condition ) {
@@ -175,8 +175,8 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Equivalent to addWhere(array($field => $value))
- * @param $field string Field name
- * @param $value string Value; ignored if null or empty array;
+ * @param string $field Field name
+ * @param string $value Value; ignored if null or empty array;
*/
protected function addWhereFld( $field, $value ) {
// Use count() to its full documented capabilities to simultaneously
@@ -189,14 +189,14 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Add a WHERE clause corresponding to a range, and an ORDER BY
* clause to sort in the right direction
- * @param $field string Field name
- * @param $dir string If 'newer', sort in ascending order, otherwise
+ * @param string $field Field name
+ * @param string $dir If 'newer', sort in ascending order, otherwise
* sort in descending order
- * @param $start string Value to start the list at. If $dir == 'newer'
+ * @param string $start Value to start the list at. If $dir == 'newer'
* this is the lower boundary, otherwise it's the upper boundary
- * @param $end string Value to end the list at. If $dir == 'newer' this
+ * @param string $end Value to end the list at. If $dir == 'newer' this
* is the upper boundary, otherwise it's the lower boundary
- * @param $sort bool If false, don't add an ORDER BY clause
+ * @param bool $sort If false, don't add an ORDER BY clause
*/
protected function addWhereRange( $field, $dir, $start, $end, $sort = true ) {
$isDirNewer = ( $dir === 'newer' );
@@ -240,8 +240,8 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Add an option such as LIMIT or USE INDEX. If an option was set
* before, the old value will be overwritten
- * @param $name string Option name
- * @param $value string Option value
+ * @param string $name Option name
+ * @param string $value Option value
*/
protected function addOption( $name, $value = null ) {
if ( is_null( $value ) ) {
@@ -253,9 +253,9 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Execute a SELECT query based on the values in the internal arrays
- * @param $method string Function the query should be attributed to.
+ * @param string $method Function the query should be attributed to.
* You should usually use __METHOD__ here
- * @param $extraQuery array Query data to add but not store in the object
+ * @param array $extraQuery Query data to add but not store in the object
* Format is array( 'tables' => ..., 'fields' => ..., 'where' => ..., 'options' => ..., 'join_conds' => ... )
* @return ResultWrapper
*/
@@ -298,9 +298,9 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Add information (title and namespace) about a Title object to a
* result array
- * @param $arr array Result array à la ApiResult
+ * @param array $arr Result array à la ApiResult
* @param $title Title
- * @param $prefix string Module prefix
+ * @param string $prefix Module prefix
*/
public static function addTitleInfo( &$arr, $title, $prefix = '' ) {
$arr[$prefix . 'ns'] = intval( $title->getNamespace() );
@@ -325,8 +325,8 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Add a sub-element under the page element with the given page ID
- * @param $pageId int Page ID
- * @param $data array Data array à la ApiResult
+ * @param int $pageId Page ID
+ * @param array $data Data array à la ApiResult
* @return bool Whether the element fit in the result
*/
protected function addPageSubItems( $pageId, $data ) {
@@ -339,9 +339,9 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Same as addPageSubItems(), but one element of $data at a time
- * @param $pageId int Page ID
- * @param $item array Data array à la ApiResult
- * @param $elemname string XML element name. If null, getModuleName()
+ * @param int $pageId Page ID
+ * @param array $item Data array à la ApiResult
+ * @param string $elemname XML element name. If null, getModuleName()
* is used
* @return bool Whether the element fit in the result
*/
@@ -351,7 +351,7 @@ abstract class ApiQueryBase extends ApiBase {
}
$result = $this->getResult();
$fit = $result->addValue( array( 'query', 'pages', $pageId,
- $this->getModuleName() ), null, $item );
+ $this->getModuleName() ), null, $item );
if ( !$fit ) {
return false;
}
@@ -362,15 +362,15 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Set a query-continue value
- * @param $paramName string Parameter name
- * @param $paramValue string Parameter value
+ * @param string $paramName Parameter name
+ * @param string $paramValue Parameter value
*/
protected function setContinueEnumParameter( $paramName, $paramValue ) {
$paramName = $this->encodeParamName( $paramName );
$msg = array( $paramName => $paramValue );
$result = $this->getResult();
$result->disableSizeCheck();
- $result->addValue( 'query-continue', $this->getModuleName(), $msg );
+ $result->addValue( 'query-continue', $this->getModuleName(), $msg, ApiResult::ADD_ON_TOP );
$result->enableSizeCheck();
}
@@ -380,8 +380,7 @@ abstract class ApiQueryBase extends ApiBase {
*/
protected function getDB() {
if ( is_null( $this->mDb ) ) {
- $apiQuery = $this->getQuery();
- $this->mDb = $apiQuery->getDB();
+ $this->mDb = $this->getQuery()->getDB();
}
return $this->mDb;
}
@@ -389,9 +388,9 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Selects the query database connection with the given name.
* See ApiQuery::getNamedDB() for more information
- * @param $name string Name to assign to the database connection
- * @param $db int One of the DB_* constants
- * @param $groups array Query groups
+ * @param string $name Name to assign to the database connection
+ * @param int $db One of the DB_* constants
+ * @param array $groups Query groups
* @return DatabaseBase
*/
public function selectNamedDB( $name, $db, $groups ) {
@@ -408,7 +407,7 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Convert a title to a DB key
- * @param $title string Page title with spaces
+ * @param string $title Page title with spaces
* @return string Page title with underscores
*/
public function titleToKey( $title ) {
@@ -420,12 +419,12 @@ abstract class ApiQueryBase extends ApiBase {
if ( !$t ) {
$this->dieUsageMsg( array( 'invalidtitle', $title ) );
}
- return $t->getPrefixedDbKey();
+ return $t->getPrefixedDBkey();
}
/**
* The inverse of titleToKey()
- * @param $key string Page title with underscores
+ * @param string $key Page title with underscores
* @return string Page title with spaces
*/
public function keyToTitle( $key ) {
@@ -443,7 +442,7 @@ abstract class ApiQueryBase extends ApiBase {
/**
* An alternative to titleToKey() that doesn't trim trailing spaces
- * @param $titlePart string Title part with spaces
+ * @param string $titlePart Title part with spaces
* @return string Title part with underscores
*/
public function titlePartToKey( $titlePart ) {
@@ -452,7 +451,7 @@ abstract class ApiQueryBase extends ApiBase {
/**
* An alternative to keyToTitle() that doesn't trim trailing spaces
- * @param $keyPart string Key part with spaces
+ * @param string $keyPart Key part with spaces
* @return string Key part with underscores
*/
public function keyPartToTitle( $keyPart ) {
@@ -534,7 +533,7 @@ abstract class ApiQueryBase extends ApiBase {
* @return bool
*/
public function validateSha1Hash( $hash ) {
- return preg_match( '/[a-fA-F0-9]{40}/', $hash );
+ return preg_match( '/^[a-f0-9]{40}$/', $hash );
}
/**
@@ -542,25 +541,19 @@ abstract class ApiQueryBase extends ApiBase {
* @return bool
*/
public function validateSha1Base36Hash( $hash ) {
- return preg_match( '/[a-zA-Z0-9]{31}/', $hash );
+ return preg_match( '/^[a-z0-9]{31}$/', $hash );
}
/**
* @return array
*/
public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
+ $errors = parent::getPossibleErrors();
+ $errors = array_merge( $errors, array(
array( 'invalidtitle', 'title' ),
array( 'invalidtitle', 'key' ),
) );
- }
-
- /**
- * Get version string for use in the API help output
- * @return string
- */
- public static function getBaseVersion() {
- return __CLASS__ . ': $Id$';
+ return $errors;
}
}
@@ -569,33 +562,41 @@ abstract class ApiQueryBase extends ApiBase {
*/
abstract class ApiQueryGeneratorBase extends ApiQueryBase {
- private $mIsGenerator;
+ private $mGeneratorPageSet = null;
/**
- * @param $query ApiBase
- * @param $moduleName string
- * @param $paramPrefix string
+ * Switch this module to generator mode. By default, generator mode is
+ * switched off and the module acts like a normal query module.
+ * @since 1.21 requires pageset parameter
+ * @param $generatorPageSet ApiPageSet object that the module will get
+ * by calling getPageSet() when in generator mode.
*/
- public function __construct( $query, $moduleName, $paramPrefix = '' ) {
- parent::__construct( $query, $moduleName, $paramPrefix );
- $this->mIsGenerator = false;
+ public function setGeneratorMode( ApiPageSet $generatorPageSet ) {
+ if ( $generatorPageSet === null ) {
+ ApiBase::dieDebug( __METHOD__, 'Required parameter missing - $generatorPageSet' );
+ }
+ $this->mGeneratorPageSet = $generatorPageSet;
}
/**
- * Switch this module to generator mode. By default, generator mode is
- * switched off and the module acts like a normal query module.
+ * Get the PageSet object to work on.
+ * If this module is generator, the pageSet object is different from other module's
+ * @return ApiPageSet
*/
- public function setGeneratorMode() {
- $this->mIsGenerator = true;
+ protected function getPageSet() {
+ if ( $this->mGeneratorPageSet !== null ) {
+ return $this->mGeneratorPageSet;
+ }
+ return parent::getPageSet();
}
/**
* Overrides base class to prepend 'g' to every generator parameter
- * @param $paramName string Parameter name
+ * @param string $paramName Parameter name
* @return string Prefixed parameter name
*/
public function encodeParamName( $paramName ) {
- if ( $this->mIsGenerator ) {
+ if ( $this->mGeneratorPageSet !== null ) {
return 'g' . parent::encodeParamName( $paramName );
} else {
return parent::encodeParamName( $paramName );
@@ -603,9 +604,24 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase {
}
/**
+ * Overrides base in case of generator & smart continue to
+ * notify ApiQueryMain instead of adding them to the result right away.
+ * @param string $paramName Parameter name
+ * @param string $paramValue Parameter value
+ */
+ protected function setContinueEnumParameter( $paramName, $paramValue ) {
+ // If this is a generator and query->setGeneratorContinue() returns false, treat as before
+ if ( $this->mGeneratorPageSet === null
+ || !$this->getQuery()->setGeneratorContinue( $this, $paramName, $paramValue )
+ ) {
+ parent::setContinueEnumParameter( $paramName, $paramValue );
+ }
+ }
+
+ /**
* Execute this module as a generator
* @param $resultPageSet ApiPageSet: All output should be appended to
* this object
*/
- public abstract function executeGenerator( $resultPageSet );
+ abstract public function executeGenerator( $resultPageSet );
}
diff --git a/includes/api/ApiQueryBlocks.php b/includes/api/ApiQueryBlocks.php
index 96b86962..d9be9f28 100644
--- a/includes/api/ApiQueryBlocks.php
+++ b/includes/api/ApiQueryBlocks.php
@@ -132,10 +132,10 @@ class ApiQueryBlocks extends ApiQueryBase {
$this->addWhereIf( 'ipb_user != 0', isset( $show['account'] ) );
$this->addWhereIf( 'ipb_user != 0 OR ipb_range_end > ipb_range_start', isset( $show['!ip'] ) );
$this->addWhereIf( 'ipb_user = 0 AND ipb_range_end = ipb_range_start', isset( $show['ip'] ) );
- $this->addWhereIf( 'ipb_expiry = '.$db->addQuotes($db->getInfinity()), isset( $show['!temp'] ) );
- $this->addWhereIf( 'ipb_expiry != '.$db->addQuotes($db->getInfinity()), isset( $show['temp'] ) );
- $this->addWhereIf( "ipb_range_end = ipb_range_start", isset( $show['!range'] ) );
- $this->addWhereIf( "ipb_range_end > ipb_range_start", isset( $show['range'] ) );
+ $this->addWhereIf( 'ipb_expiry = ' . $db->addQuotes( $db->getInfinity() ), isset( $show['!temp'] ) );
+ $this->addWhereIf( 'ipb_expiry != ' . $db->addQuotes( $db->getInfinity() ), isset( $show['temp'] ) );
+ $this->addWhereIf( 'ipb_range_end = ipb_range_start', isset( $show['!range'] ) );
+ $this->addWhereIf( 'ipb_range_end > ipb_range_start', isset( $show['range'] ) );
}
if ( !$this->getUser()->isAllowed( 'hideuser' ) ) {
@@ -182,8 +182,8 @@ class ApiQueryBlocks extends ApiQueryBase {
$block['reason'] = $row->ipb_reason;
}
if ( $fld_range && !$row->ipb_auto ) {
- $block['rangestart'] = IP::hexToQuad( $row->ipb_range_start );
- $block['rangeend'] = IP::hexToQuad( $row->ipb_range_end );
+ $block['rangestart'] = IP::formatHex( $row->ipb_range_start );
+ $block['rangeend'] = IP::formatHex( $row->ipb_range_end );
}
if ( $fld_flags ) {
// For clarity, these flags use the same names as their action=block counterparts
@@ -301,7 +301,7 @@ class ApiQueryBlocks extends ApiQueryBase {
'dir' => $this->getDirectionDescription( $p ),
'ids' => 'List of block IDs to list (optional)',
'users' => 'List of users to search for (optional)',
- 'ip' => array( 'Get all blocks applying to this IP or CIDR range, including range blocks.',
+ 'ip' => array( 'Get all blocks applying to this IP or CIDR range, including range blocks.',
'Cannot be used together with bkusers. CIDR ranges broader than /16 are not accepted' ),
'limit' => 'The maximum amount of blocks to list',
'prop' => array(
@@ -404,8 +404,4 @@ class ApiQueryBlocks extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Blocks';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryCategories.php b/includes/api/ApiQueryCategories.php
index 309c2ce9..69a64415 100644
--- a/includes/api/ApiQueryCategories.php
+++ b/includes/api/ApiQueryCategories.php
@@ -53,7 +53,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
*/
private function run( $resultPageSet = null ) {
if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
- return; // nothing to do
+ return; // nothing to do
}
$params = $this->extractRequestParams();
@@ -85,10 +85,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 ) {
- $this->dieUsage( "Invalid continue param. You should pass the " .
- "original value returned by the previous query", "_badcontinue" );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
$op = $params['dir'] == 'descending' ? '<' : '>';
$clfrom = intval( $cont[0] );
$clto = $this->getDB()->addQuotes( $cont[1] );
@@ -276,8 +273,4 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Properties#categories_.2F_cl';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryCategoryInfo.php b/includes/api/ApiQueryCategoryInfo.php
index 31517fab..a889272e 100644
--- a/includes/api/ApiQueryCategoryInfo.php
+++ b/includes/api/ApiQueryCategoryInfo.php
@@ -48,6 +48,7 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
$this->getPageSet()->getMissingTitles();
$cattitles = array();
foreach ( $categories as $c ) {
+ /** @var $t Title */
$t = $titles[$c];
$cattitles[$c] = $t->getDBkey();
}
@@ -146,8 +147,4 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Properties#categoryinfo_.2F_ci';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryCategoryMembers.php b/includes/api/ApiQueryCategoryMembers.php
index 55ce0234..9dbd8593 100644
--- a/includes/api/ApiQueryCategoryMembers.php
+++ b/includes/api/ApiQueryCategoryMembers.php
@@ -78,7 +78,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
$this->addFieldsIf( 'cl_timestamp', $fld_timestamp || $params['sort'] == 'timestamp' );
- $this->addTables( array( 'page', 'categorylinks' ) ); // must be in this order for 'USE INDEX'
+ $this->addTables( array( 'page', 'categorylinks' ) ); // must be in this order for 'USE INDEX'
$this->addWhereFld( 'cl_to', $categoryTitle->getDBkey() );
$queryTypes = $params['type'];
@@ -106,11 +106,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
} else {
if ( $params['continue'] ) {
$cont = explode( '|', $params['continue'], 3 );
- if ( count( $cont ) != 3 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the original value returned '.
- 'by the previous query', '_badcontinue'
- );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 3 );
// Remove the types to skip from $queryTypes
$contTypeIndex = array_search( $cont[0], $queryTypes );
@@ -118,7 +114,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
// Add a WHERE clause for sortkey and from
// pack( "H*", $foo ) is used to convert hex back to binary
- $escSortkey = $this->getDB()->addQuotes( pack( "H*", $cont[1] ) );
+ $escSortkey = $this->getDB()->addQuotes( pack( 'H*', $cont[1] ) );
$from = intval( $cont[2] );
$op = $dir == 'newer' ? '>' : '<';
// $contWhere is used further down
@@ -247,7 +243,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
if ( is_null( $resultPageSet ) ) {
$result->setIndexedTagName_internal(
- array( 'query', $this->getModuleName() ), 'cm' );
+ array( 'query', $this->getModuleName() ), 'cm' );
}
}
@@ -403,7 +399,6 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
$this->getTitleOrPageIdErrorMessage(),
array(
array( 'code' => 'invalidcategory', 'info' => 'The category name you entered is not valid' ),
- array( 'code' => 'badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
)
);
}
@@ -418,8 +413,4 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Categorymembers';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryDeletedrevs.php b/includes/api/ApiQueryDeletedrevs.php
index e69ccbd6..31ca1ef5 100644
--- a/includes/api/ApiQueryDeletedrevs.php
+++ b/includes/api/ApiQueryDeletedrevs.php
@@ -74,15 +74,15 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
if ( $mode == 'revs' || $mode == 'user' ) {
// Ignore namespace and unique due to inability to know whether they were purposely set
- foreach( array( 'from', 'to', 'prefix', /*'namespace',*/ 'continue', /*'unique'*/ ) as $p ) {
+ foreach( array( 'from', 'to', 'prefix', /*'namespace', 'unique'*/ ) as $p ) {
if ( !is_null( $params[$p] ) ) {
- $this->dieUsage( "The '{$p}' parameter cannot be used in modes 1 or 2", 'badparams');
+ $this->dieUsage( "The '{$p}' parameter cannot be used in modes 1 or 2", 'badparams' );
}
}
} else {
foreach( array( 'start', 'end' ) as $p ) {
if ( !is_null( $params[$p] ) ) {
- $this->dieUsage( "The {$p} parameter cannot be used in mode 3", 'badparams');
+ $this->dieUsage( "The {$p} parameter cannot be used in mode 3", 'badparams' );
}
}
}
@@ -116,7 +116,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
}
// Check limits
$userMax = $fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1;
- $botMax = $fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2;
+ $botMax = $fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2;
$limit = $params['limit'];
@@ -160,10 +160,9 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
if ( !is_null( $params['continue'] ) && ( $mode == 'all' || $mode == 'revs' ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 3 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the original value returned by the previous query', 'badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 3 );
$ns = intval( $cont[0] );
+ $this->dieContinueUsageIf( strval( $ns ) !== $cont[0] );
$title = $db->addQuotes( $cont[1] );
$ts = $db->addQuotes( $db->timestamp( $cont[2] ) );
$op = ( $dir == 'newer' ? '>' : '<' );
@@ -307,7 +306,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
),
'namespace' => array(
ApiBase::PARAM_TYPE => 'namespace',
- ApiBase::PARAM_DFLT => 0,
+ ApiBase::PARAM_DFLT => NS_MAIN,
),
'limit' => array(
ApiBase::PARAM_DFLT => 10,
@@ -362,7 +361,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
'namespace' => 'Only list pages in this namespace (3)',
'user' => 'Only list revisions by this user',
'excludeuser' => 'Don\'t list revisions by this user',
- 'continue' => 'When more results are available, use this to continue (3)',
+ 'continue' => 'When more results are available, use this to continue (1, 3)',
'unique' => 'List only one revision for each page (3)',
);
}
@@ -397,11 +396,9 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted revision information' ),
array( 'code' => 'badparams', 'info' => 'user and excludeuser cannot be used together' ),
array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted revision content' ),
- array( 'code' => 'badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
array( 'code' => 'badparams', 'info' => "The 'from' parameter cannot be used in modes 1 or 2" ),
array( 'code' => 'badparams', 'info' => "The 'to' parameter cannot be used in modes 1 or 2" ),
array( 'code' => 'badparams', 'info' => "The 'prefix' parameter cannot be used in modes 1 or 2" ),
- array( 'code' => 'badparams', 'info' => "The 'continue' parameter cannot be used in modes 1 or 2" ),
array( 'code' => 'badparams', 'info' => "The 'start' parameter cannot be used in mode 3" ),
array( 'code' => 'badparams', 'info' => "The 'end' parameter cannot be used in mode 3" ),
) );
@@ -423,8 +420,4 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Deletedrevs';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryDisabled.php b/includes/api/ApiQueryDisabled.php
index 6715969a..cf0d841e 100644
--- a/includes/api/ApiQueryDisabled.php
+++ b/includes/api/ApiQueryDisabled.php
@@ -36,10 +36,6 @@
*/
class ApiQueryDisabled extends ApiQueryBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$this->setWarning( "The \"{$this->getModuleName()}\" module has been disabled." );
}
@@ -61,8 +57,4 @@ class ApiQueryDisabled extends ApiQueryBase {
public function getExamples() {
return array();
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryDuplicateFiles.php b/includes/api/ApiQueryDuplicateFiles.php
index 8f0fd3be..18dcba85 100644
--- a/includes/api/ApiQueryDuplicateFiles.php
+++ b/includes/api/ApiQueryDuplicateFiles.php
@@ -66,10 +66,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
$skipUntilThisDup = false;
if ( isset( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the ' .
- 'original value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
$fromImage = $cont[0];
$skipUntilThisDup = $cont[1];
// Filter out any images before $fromImage
@@ -95,6 +92,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
$sha1s = array();
foreach ( $files as $file ) {
+ /** @var $file File */
$sha1s[$file->getName()] = $file->getSha1();
}
@@ -116,6 +114,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
if( $params['dir'] == 'descending' ) {
$dupFiles = array_reverse( $dupFiles );
}
+ /** @var $dupFile File */
foreach ( $dupFiles as $dupFile ) {
$dupName = $dupFile->getName();
if( $image == $dupName && $dupFile->isLocal() ) {
@@ -133,7 +132,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
break;
}
if ( !is_null( $resultPageSet ) ) {
- $titles[] = $file->getTitle();
+ $titles[] = $dupFile->getTitle();
} else {
$r = array(
'name' => $dupName,
@@ -204,12 +203,6 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
return 'List all files that are duplicates of the given file(s) based on hash values';
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
- ) );
- }
-
public function getExamples() {
return array(
'api.php?action=query&titles=File:Albert_Einstein_Head.jpg&prop=duplicatefiles',
@@ -220,8 +213,4 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Properties#duplicatefiles_.2F_df';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryExtLinksUsage.php b/includes/api/ApiQueryExtLinksUsage.php
index 42b398ba..eb9cdf9e 100644
--- a/includes/api/ApiQueryExtLinksUsage.php
+++ b/includes/api/ApiQueryExtLinksUsage.php
@@ -55,7 +55,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
$query = $params['query'];
$protocol = self::getProtocolPrefix( $params['protocol'] );
- $this->addTables( array( 'page', 'externallinks' ) ); // must be in this order for 'USE INDEX'
+ $this->addTables( array( 'page', 'externallinks' ) ); // must be in this order for 'USE INDEX'
$this->addOption( 'USE INDEX', 'el_index' );
$this->addWhere( 'page_id=el_from' );
@@ -121,8 +121,12 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
ApiQueryBase::addTitleInfo( $vals, $title );
}
if ( $fld_url ) {
- // We *could* run this through wfExpandUrl() but I think it's better to output the link verbatim, even if it's protocol-relative --Roan
- $vals['url'] = $row->el_to;
+ $to = $row->el_to;
+ // expand protocol-relative urls
+ if( $params['expandurl'] ) {
+ $to = wfExpandUrl( $to, PROTO_CANONICAL );
+ }
+ $vals['url'] = $to;
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
if ( !$fit ) {
@@ -169,7 +173,8 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
ApiBase::PARAM_MIN => 1,
ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
- )
+ ),
+ 'expandurl' => false,
);
}
@@ -218,7 +223,8 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
),
'query' => 'Search string without protocol. See [[Special:LinkSearch]]. Leave empty to list all external links',
'namespace' => 'The page namespace(s) to enumerate.',
- 'limit' => 'How many pages to return.'
+ 'limit' => 'How many pages to return.',
+ 'expandurl' => 'Expand protocol-relative urls with the canonical protocol',
);
if ( $wgMiserMode ) {
@@ -266,8 +272,4 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Exturlusage';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryExternalLinks.php b/includes/api/ApiQueryExternalLinks.php
index 9365a9b8..761b49ea 100644
--- a/includes/api/ApiQueryExternalLinks.php
+++ b/includes/api/ApiQueryExternalLinks.php
@@ -86,8 +86,12 @@ class ApiQueryExternalLinks extends ApiQueryBase {
break;
}
$entry = array();
- // We *could* run this through wfExpandUrl() but I think it's better to output the link verbatim, even if it's protocol-relative --Roan
- ApiResult::setContent( $entry, $row->el_to );
+ $to = $row->el_to;
+ // expand protocol-relative urls
+ if( $params['expandurl'] ) {
+ $to = wfExpandUrl( $to, PROTO_CANONICAL );
+ }
+ ApiResult::setContent( $entry, $to );
$fit = $this->addPageSubItem( $row->el_from, $entry );
if ( !$fit ) {
$this->setContinueEnumParameter( 'offset', $offset + $count - 1 );
@@ -117,6 +121,7 @@ class ApiQueryExternalLinks extends ApiQueryBase {
ApiBase::PARAM_DFLT => '',
),
'query' => null,
+ 'expandurl' => false,
);
}
@@ -130,6 +135,7 @@ class ApiQueryExternalLinks extends ApiQueryBase {
"Leave both this and {$p}query empty to list all external links"
),
'query' => 'Search string without protocol. Useful for checking whether a certain page contains a certain external url',
+ 'expandurl' => 'Expand protocol-relative urls with the canonical protocol',
);
}
@@ -142,7 +148,7 @@ class ApiQueryExternalLinks extends ApiQueryBase {
}
public function getDescription() {
- return 'Returns all external urls (not interwikies) from the given page(s)';
+ return 'Returns all external urls (not interwikis) from the given page(s)';
}
public function getPossibleErrors() {
@@ -160,8 +166,4 @@ class ApiQueryExternalLinks extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Properties#extlinks_.2F_el';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryFilearchive.php b/includes/api/ApiQueryFilearchive.php
index a5486ef4..021074a9 100644
--- a/includes/api/ApiQueryFilearchive.php
+++ b/includes/api/ApiQueryFilearchive.php
@@ -64,7 +64,7 @@ class ApiQueryFilearchive extends ApiQueryBase {
$this->addTables( 'filearchive' );
$this->addFields( array( 'fa_name', 'fa_deleted' ) );
- $this->addFieldsIf( 'fa_storage_key', $fld_sha1 );
+ $this->addFieldsIf( 'fa_sha1', $fld_sha1 );
$this->addFieldsIf( 'fa_timestamp', $fld_timestamp );
$this->addFieldsIf( array( 'fa_user', 'fa_user_text' ), $fld_user );
$this->addFieldsIf( array( 'fa_height', 'fa_width', 'fa_size' ), $fld_dimensions || $fld_size );
@@ -77,10 +77,7 @@ class ApiQueryFilearchive extends ApiQueryBase {
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 1 ) {
- $this->dieUsage( "Invalid continue param. You should pass the " .
- "original value returned by the previous query", "_badcontinue" );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 1 );
$op = $params['dir'] == 'descending' ? '<' : '>';
$cont_from = $db->addQuotes( $cont[0] );
$this->addWhere( "fa_name $op= $cont_from" );
@@ -101,25 +98,21 @@ class ApiQueryFilearchive extends ApiQueryBase {
$sha1Set = isset( $params['sha1'] );
$sha1base36Set = isset( $params['sha1base36'] );
if ( $sha1Set || $sha1base36Set ) {
- global $wgMiserMode;
- if ( $wgMiserMode ) {
- $this->dieUsage( 'Search by hash disabled in Miser Mode', 'hashsearchdisabled' );
- }
-
$sha1 = false;
if ( $sha1Set ) {
- if ( !$this->validateSha1Hash( $params['sha1'] ) ) {
+ $sha1 = strtolower( $params['sha1'] );
+ if ( !$this->validateSha1Hash( $sha1 ) ) {
$this->dieUsage( 'The SHA1 hash provided is not valid', 'invalidsha1hash' );
}
- $sha1 = wfBaseConvert( $params['sha1'], 16, 36, 31 );
+ $sha1 = wfBaseConvert( $sha1, 16, 36, 31 );
} elseif ( $sha1base36Set ) {
- if ( !$this->validateSha1Base36Hash( $params['sha1base36'] ) ) {
+ $sha1 = strtolower( $params['sha1base36'] );
+ if ( !$this->validateSha1Base36Hash( $sha1 ) ) {
$this->dieUsage( 'The SHA1Base36 hash provided is not valid', 'invalidsha1base36hash' );
}
- $sha1 = $params['sha1base36'];
}
if ( $sha1 ) {
- $this->addWhere( 'fa_storage_key ' . $db->buildLike( "{$sha1}.", $db->anyString() ) );
+ $this->addWhereFld( 'fa_sha1', $sha1 );
}
}
@@ -155,7 +148,7 @@ class ApiQueryFilearchive extends ApiQueryBase {
self::addTitleInfo( $file, $title );
if ( $fld_sha1 ) {
- $file['sha1'] = wfBaseConvert( LocalRepo::getHashFromKey( $row->fa_storage_key ), 36, 16, 40 );
+ $file['sha1'] = wfBaseConvert( $row->fa_sha1, 36, 16, 40 );
}
if ( $fld_timestamp ) {
$file['timestamp'] = wfTimestamp( TS_ISO_8601, $row->fa_timestamp );
@@ -214,7 +207,6 @@ class ApiQueryFilearchive extends ApiQueryBase {
$file['suppressed'] = '';
}
-
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $file );
if ( !$fit ) {
$this->setContinueEnumParameter( 'continue', $row->fa_name );
@@ -276,8 +268,8 @@ class ApiQueryFilearchive extends ApiQueryBase {
'prefix' => 'Search for all image titles that begin with this value',
'dir' => 'The direction in which to list',
'limit' => 'How many images to return in total',
- 'sha1' => "SHA1 hash of image. Overrides {$this->getModulePrefix()}sha1base36. Disabled in Miser Mode",
- 'sha1base36' => 'SHA1 hash of image in base 36 (used in MediaWiki). Disabled in Miser Mode',
+ 'sha1' => "SHA1 hash of image. Overrides {$this->getModulePrefix()}sha1base36",
+ 'sha1base36' => 'SHA1 hash of image in base 36 (used in MediaWiki)',
'prop' => array(
'What image information to get:',
' sha1 - Adds SHA-1 hash for the image',
@@ -370,7 +362,6 @@ class ApiQueryFilearchive extends ApiQueryBase {
array( 'code' => 'hashsearchdisabled', 'info' => 'Search by hash disabled in Miser Mode' ),
array( 'code' => 'invalidsha1hash', 'info' => 'The SHA1 hash provided is not valid' ),
array( 'code' => 'invalidsha1base36hash', 'info' => 'The SHA1Base36 hash provided is not valid' ),
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
) );
}
@@ -382,8 +373,4 @@ class ApiQueryFilearchive extends ApiQueryBase {
),
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryIWBacklinks.php b/includes/api/ApiQueryIWBacklinks.php
index c5012f08..b47d31f2 100644
--- a/includes/api/ApiQueryIWBacklinks.php
+++ b/includes/api/ApiQueryIWBacklinks.php
@@ -56,10 +56,7 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 3 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the ' .
- 'original value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 3 );
$db = $this->getDB();
$op = $params['dir'] == 'descending' ? '<' : '>';
@@ -233,7 +230,6 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'missingparam', 'prefix' ),
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
) );
}
@@ -243,8 +239,4 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
'api.php?action=query&generator=iwbacklinks&giwbltitle=Test&giwblprefix=wikibooks&prop=info'
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryIWLinks.php b/includes/api/ApiQueryIWLinks.php
index 30c7f5a8..fc77b4e6 100644
--- a/includes/api/ApiQueryIWLinks.php
+++ b/includes/api/ApiQueryIWLinks.php
@@ -58,10 +58,7 @@ class ApiQueryIWLinks extends ApiQueryBase {
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 3 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the ' .
- 'original value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 3 );
$op = $params['dir'] == 'descending' ? '<' : '>';
$db = $this->getDB();
$iwlfrom = intval( $cont[0] );
@@ -187,7 +184,6 @@ class ApiQueryIWLinks extends ApiQueryBase {
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'missingparam', 'prefix' ),
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
) );
}
@@ -196,8 +192,4 @@ class ApiQueryIWLinks extends ApiQueryBase {
'api.php?action=query&prop=iwlinks&titles=Main%20Page' => 'Get interwiki links from the [[Main Page]]',
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryImageInfo.php b/includes/api/ApiQueryImageInfo.php
index d822eed5..95c2745a 100644
--- a/includes/api/ApiQueryImageInfo.php
+++ b/includes/api/ApiQueryImageInfo.php
@@ -30,6 +30,8 @@
* @ingroup API
*/
class ApiQueryImageInfo extends ApiQueryBase {
+ const TRANSFORM_LIMIT = 50;
+ private static $transformCount = 0;
public function __construct( $query, $moduleName, $prefix = 'ii' ) {
// We allow a subclass to override the prefix, to create a related API module.
@@ -52,14 +54,10 @@ class ApiQueryImageInfo extends ApiQueryBase {
$titles = array_keys( $pageIds[NS_FILE] );
asort( $titles ); // Ensure the order is always the same
- $skip = false;
+ $fromTitle = null;
if ( !is_null( $params['continue'] ) ) {
- $skip = true;
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the original ' .
- 'value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
$fromTitle = strval( $cont[0] );
$fromTimestamp = $cont[1];
// Filter out any titles before $fromTitle
@@ -79,14 +77,34 @@ class ApiQueryImageInfo extends ApiQueryBase {
} else {
$images = RepoGroup::singleton()->findFiles( $titles );
}
- foreach ( $images as $img ) {
- // Skip redirects
- if ( $img->getOriginalTitle()->isRedirect() ) {
+ foreach ( $titles as $title ) {
+ $pageId = $pageIds[NS_FILE][$title];
+ $start = $title === $fromTitle ? $fromTimestamp : $params['start'];
+
+ if ( !isset( $images[$title] ) ) {
+ $result->addValue(
+ array( 'query', 'pages', intval( $pageId ) ),
+ 'imagerepository', ''
+ );
+ // The above can't fail because it doesn't increase the result size
continue;
}
- $start = $skip ? $fromTimestamp : $params['start'];
- $pageId = $pageIds[NS_FILE][ $img->getOriginalTitle()->getDBkey() ];
+ /** @var $img File */
+ $img = $images[$title];
+
+ if ( self::getTransformCount() >= self::TRANSFORM_LIMIT ) {
+ if ( count( $pageIds[NS_FILE] ) == 1 ) {
+ // See the 'the user is screwed' comment below
+ $this->setContinueEnumParameter( 'start',
+ $start !== null ? $start : wfTimestamp( TS_ISO_8601, $img->getTimestamp() )
+ );
+ } else {
+ $this->setContinueEnumParameter( 'continue',
+ $this->getContinueStr( $img, $start ) );
+ }
+ break;
+ }
$fit = $result->addValue(
array( 'query', 'pages', intval( $pageId ) ),
@@ -100,10 +118,11 @@ class ApiQueryImageInfo extends ApiQueryBase {
// thing again. When the violating queries have been
// out-continued, the result will get through
$this->setContinueEnumParameter( 'start',
- wfTimestamp( TS_ISO_8601, $img->getTimestamp() ) );
+ $start !== null ? $start : wfTimestamp( TS_ISO_8601, $img->getTimestamp() )
+ );
} else {
$this->setContinueEnumParameter( 'continue',
- $this->getContinueStr( $img ) );
+ $this->getContinueStr( $img, $start ) );
}
break;
}
@@ -140,6 +159,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
// Get one more to facilitate query-continue functionality
$count = ( $gotOne ? 1 : 0 );
$oldies = $img->getHistory( $params['limit'] - $count + 1, $start, $params['end'] );
+ /** @var $oldie File */
foreach ( $oldies as $oldie ) {
if ( ++$count > $params['limit'] ) {
// We've reached the extra one which shows that there are additional pages to be had. Stop here...
@@ -167,25 +187,13 @@ class ApiQueryImageInfo extends ApiQueryBase {
if ( !$fit ) {
break;
}
- $skip = false;
- }
-
- $data = $this->getResultData();
- foreach ( $data['query']['pages'] as $pageid => $arr ) {
- if ( !isset( $arr['imagerepository'] ) ) {
- $result->addValue(
- array( 'query', 'pages', $pageid ),
- 'imagerepository', ''
- );
- }
- // The above can't fail because it doesn't increase the result size
}
}
}
/**
* From parameters, construct a 'scale' array
- * @param $params Array: Parameters passed to api.
+ * @param array $params Parameters passed to api.
* @return Array or Null: key-val array of 'width' and 'height', or null
*/
public function getScale( $params ) {
@@ -216,8 +224,8 @@ class ApiQueryImageInfo extends ApiQueryBase {
* We do this later than getScale, since we need the image
* to know which handler, since handlers can make their own parameters.
* @param File $image Image that params are for.
- * @param Array $thumbParams thumbnail parameters from getScale
- * @param String $otherParams of otherParams (iiurlparam).
+ * @param array $thumbParams thumbnail parameters from getScale
+ * @param string $otherParams of otherParams (iiurlparam).
* @return Array of parameters for transform.
*/
protected function mergeThumbParams ( $image, $thumbParams, $otherParams ) {
@@ -264,10 +272,10 @@ class ApiQueryImageInfo extends ApiQueryBase {
* Get result information for an image revision
*
* @param $file File object
- * @param $prop Array of properties to get (in the keys)
+ * @param array $prop of properties to get (in the keys)
* @param $result ApiResult object
- * @param $thumbParams Array containing 'width' and 'height' items, or null
- * @param $version string Version of image metadata (for things like jpeg which have different versions).
+ * @param array $thumbParams containing 'width' and 'height' items, or null
+ * @param string $version Version of image metadata (for things like jpeg which have different versions).
* @return Array: result array
*/
static function getInfo( $file, $prop, $result, $thumbParams = null, $version = 'latest' ) {
@@ -346,6 +354,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
if ( $url ) {
if ( !is_null( $thumbParams ) ) {
$mto = $file->transform( $thumbParams );
+ self::$transformCount++;
if ( $mto && !$mto->isError() ) {
$vals['thumburl'] = wfExpandUrl( $mto->getUrl(), PROTO_CURRENT );
@@ -360,7 +369,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
if ( isset( $prop['thumbmime'] ) && $file->getHandler() ) {
- list( $ext, $mime ) = $file->getHandler()->getThumbType(
+ list( , $mime ) = $file->getHandler()->getThumbType(
$mto->getExtension(), $file->getMimeType(), $thumbParams );
$vals['thumbmime'] = $mime;
}
@@ -377,8 +386,10 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
if ( $meta ) {
+ wfSuppressWarnings();
$metadata = unserialize( $file->getMetadata() );
- if ( $version !== 'latest' ) {
+ wfRestoreWarnings();
+ if ( $metadata && $version !== 'latest' ) {
$metadata = $file->convertMetadataVersion( $metadata, $version );
}
$vals['metadata'] = $metadata ? self::processMetaData( $metadata, $result ) : null;
@@ -404,6 +415,17 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
/**
+ * Get the count of image transformations performed
+ *
+ * If this is >= TRANSFORM_LIMIT, you should probably stop processing images.
+ *
+ * @return integer count
+ */
+ static function getTransformCount() {
+ return self::$transformCount;
+ }
+
+ /**
*
* @param $metadata Array
* @param $result ApiResult
@@ -432,11 +454,14 @@ class ApiQueryImageInfo extends ApiQueryBase {
/**
* @param $img File
+ * @param null|string $start
* @return string
*/
- protected function getContinueStr( $img ) {
- return $img->getOriginalTitle()->getText() .
- '|' . $img->getTimestamp();
+ protected function getContinueStr( $img, $start = null ) {
+ if ( $start === null ) {
+ $start = $img->getTimestamp();
+ }
+ return $img->getOriginalTitle()->getText() . '|' . $start;
}
public function getAllowedParams() {
@@ -494,6 +519,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
/**
* Returns array key value pairs of properties and their descriptions
*
+ * @param string $modulePrefix
* @return array
*/
private static function getProperties( $modulePrefix = '' ) {
@@ -540,7 +566,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
return array(
'prop' => self::getPropertyDescriptions( array(), $p ),
'urlwidth' => array( "If {$p}prop=url is set, a URL to an image scaled to this width will be returned.",
- 'Only the current version of the image can be scaled' ),
+ 'Only the current version of the image can be scaled' ),
'urlheight' => "Similar to {$p}urlwidth. Cannot be used without {$p}urlwidth",
'urlparam' => array( "A handler specific parameter string. For example, pdf's ",
"might use 'page15-100px'. {$p}urlwidth must be used and be consistent with {$p}urlparam" ),
@@ -578,6 +604,15 @@ class ApiQueryImageInfo extends ApiQueryBase {
ApiBase::PROP_NULLABLE => true
)
),
+ 'dimensions' => array(
+ 'size' => 'integer',
+ 'width' => 'integer',
+ 'height' => 'integer',
+ 'pagecount' => array(
+ ApiBase::PROP_TYPE => 'integer',
+ ApiBase::PROP_NULLABLE => true
+ )
+ ),
'comment' => array(
'commenthidden' => 'boolean',
'comment' => array(
@@ -633,6 +668,13 @@ class ApiQueryImageInfo extends ApiQueryBase {
ApiBase::PROP_NULLABLE => true
)
),
+ 'thumbmime' => array(
+ 'filehidden' => 'boolean',
+ 'thumbmime' => array(
+ ApiBase::PROP_TYPE => 'string',
+ ApiBase::PROP_NULLABLE => true
+ )
+ ),
'mediatype' => array(
'filehidden' => 'boolean',
'mediatype' => array(
@@ -672,7 +714,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
array( 'code' => "{$p}urlwidth", 'info' => "{$p}urlheight cannot be used without {$p}urlwidth" ),
array( 'code' => 'urlparam', 'info' => "Invalid value for {$p}urlparam" ),
array( 'code' => 'urlparam_no_width', 'info' => "{$p}urlparam requires {$p}urlwidth" ),
- array( 'code' => 'urlparam_urlwidth_mismatch', 'info' => "The width set in {$p}urlparm doesnt't " .
+ array( 'code' => 'urlparam_urlwidth_mismatch', 'info' => "The width set in {$p}urlparm doesn't " .
"match the one in {$p}urlwidth" ),
) );
}
@@ -687,8 +729,4 @@ class ApiQueryImageInfo extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Properties#imageinfo_.2F_ii';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryImages.php b/includes/api/ApiQueryImages.php
index 6052a75f..f2bf0a7b 100644
--- a/includes/api/ApiQueryImages.php
+++ b/includes/api/ApiQueryImages.php
@@ -49,7 +49,7 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
*/
private function run( $resultPageSet = null ) {
if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
- return; // nothing to do
+ return; // nothing to do
}
$params = $this->extractRequestParams();
@@ -62,10 +62,7 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
$this->addWhereFld( 'il_from', array_keys( $this->getPageSet()->getGoodTitles() ) );
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the ' .
- 'original value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
$op = $params['dir'] == 'descending' ? '<' : '>';
$ilfrom = intval( $cont[0] );
$ilto = $this->getDB()->addQuotes( $cont[1] );
@@ -185,12 +182,6 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
return 'Returns all images contained on the given page(s)';
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
- ) );
- }
-
public function getExamples() {
return array(
'api.php?action=query&prop=images&titles=Main%20Page' => 'Get a list of images used in the [[Main Page]]',
@@ -201,8 +192,4 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Properties#images_.2F_im';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php
index 5d4f0346..37cd9159 100644
--- a/includes/api/ApiQueryInfo.php
+++ b/includes/api/ApiQueryInfo.php
@@ -33,7 +33,8 @@ class ApiQueryInfo extends ApiQueryBase {
private $fld_protection = false, $fld_talkid = false,
$fld_subjectid = false, $fld_url = false,
- $fld_readable = false, $fld_watched = false, $fld_notificationtimestamp = false,
+ $fld_readable = false, $fld_watched = false, $fld_watchers = false,
+ $fld_notificationtimestamp = false,
$fld_preload = false, $fld_displaytitle = false;
private $params, $titles, $missing, $everything, $pageCounter;
@@ -41,7 +42,8 @@ class ApiQueryInfo extends ApiQueryBase {
private $pageRestrictions, $pageIsRedir, $pageIsNew, $pageTouched,
$pageLatest, $pageLength;
- private $protections, $watched, $notificationtimestamps, $talkids, $subjectids, $displaytitles;
+ private $protections, $watched, $watchers, $notificationtimestamps, $talkids, $subjectids, $displaytitles;
+ private $showZeroWatchers = false;
private $tokenFunctions;
@@ -96,7 +98,7 @@ class ApiQueryInfo extends ApiQueryBase {
'unblock' => array( 'ApiQueryInfo', 'getUnblockToken' ),
'email' => array( 'ApiQueryInfo', 'getEmailToken' ),
'import' => array( 'ApiQueryInfo', 'getImportToken' ),
- 'watch' => array( 'ApiQueryInfo', 'getWatchToken'),
+ 'watch' => array( 'ApiQueryInfo', 'getWatchToken' ),
);
wfRunHooks( 'APIQueryInfoTokens', array( &$this->tokenFunctions ) );
return $this->tokenFunctions;
@@ -118,11 +120,11 @@ class ApiQueryInfo extends ApiQueryBase {
}
// The token is always the same, let's exploit that
- if ( !isset( ApiQueryInfo::$cachedTokens[ 'edit' ] ) ) {
- ApiQueryInfo::$cachedTokens[ 'edit' ] = $wgUser->getEditToken();
+ if ( !isset( ApiQueryInfo::$cachedTokens['edit'] ) ) {
+ ApiQueryInfo::$cachedTokens['edit'] = $wgUser->getEditToken();
}
- return ApiQueryInfo::$cachedTokens[ 'edit' ];
+ return ApiQueryInfo::$cachedTokens['edit'];
}
public static function getDeleteToken( $pageid, $title ) {
@@ -132,11 +134,11 @@ class ApiQueryInfo extends ApiQueryBase {
}
// The token is always the same, let's exploit that
- if ( !isset( ApiQueryInfo::$cachedTokens[ 'delete' ] ) ) {
- ApiQueryInfo::$cachedTokens[ 'delete' ] = $wgUser->getEditToken();
+ if ( !isset( ApiQueryInfo::$cachedTokens['delete'] ) ) {
+ ApiQueryInfo::$cachedTokens['delete'] = $wgUser->getEditToken();
}
- return ApiQueryInfo::$cachedTokens[ 'delete' ];
+ return ApiQueryInfo::$cachedTokens['delete'];
}
public static function getProtectToken( $pageid, $title ) {
@@ -146,11 +148,11 @@ class ApiQueryInfo extends ApiQueryBase {
}
// The token is always the same, let's exploit that
- if ( !isset( ApiQueryInfo::$cachedTokens[ 'protect' ] ) ) {
- ApiQueryInfo::$cachedTokens[ 'protect' ] = $wgUser->getEditToken();
+ if ( !isset( ApiQueryInfo::$cachedTokens['protect'] ) ) {
+ ApiQueryInfo::$cachedTokens['protect'] = $wgUser->getEditToken();
}
- return ApiQueryInfo::$cachedTokens[ 'protect' ];
+ return ApiQueryInfo::$cachedTokens['protect'];
}
public static function getMoveToken( $pageid, $title ) {
@@ -160,11 +162,11 @@ class ApiQueryInfo extends ApiQueryBase {
}
// The token is always the same, let's exploit that
- if ( !isset( ApiQueryInfo::$cachedTokens[ 'move' ] ) ) {
- ApiQueryInfo::$cachedTokens[ 'move' ] = $wgUser->getEditToken();
+ if ( !isset( ApiQueryInfo::$cachedTokens['move'] ) ) {
+ ApiQueryInfo::$cachedTokens['move'] = $wgUser->getEditToken();
}
- return ApiQueryInfo::$cachedTokens[ 'move' ];
+ return ApiQueryInfo::$cachedTokens['move'];
}
public static function getBlockToken( $pageid, $title ) {
@@ -174,11 +176,11 @@ class ApiQueryInfo extends ApiQueryBase {
}
// The token is always the same, let's exploit that
- if ( !isset( ApiQueryInfo::$cachedTokens[ 'block' ] ) ) {
- ApiQueryInfo::$cachedTokens[ 'block' ] = $wgUser->getEditToken();
+ if ( !isset( ApiQueryInfo::$cachedTokens['block'] ) ) {
+ ApiQueryInfo::$cachedTokens['block'] = $wgUser->getEditToken();
}
- return ApiQueryInfo::$cachedTokens[ 'block' ];
+ return ApiQueryInfo::$cachedTokens['block'];
}
public static function getUnblockToken( $pageid, $title ) {
@@ -193,11 +195,11 @@ class ApiQueryInfo extends ApiQueryBase {
}
// The token is always the same, let's exploit that
- if ( !isset( ApiQueryInfo::$cachedTokens[ 'email' ] ) ) {
- ApiQueryInfo::$cachedTokens[ 'email' ] = $wgUser->getEditToken();
+ if ( !isset( ApiQueryInfo::$cachedTokens['email'] ) ) {
+ ApiQueryInfo::$cachedTokens['email'] = $wgUser->getEditToken();
}
- return ApiQueryInfo::$cachedTokens[ 'email' ];
+ return ApiQueryInfo::$cachedTokens['email'];
}
public static function getImportToken( $pageid, $title ) {
@@ -207,11 +209,11 @@ class ApiQueryInfo extends ApiQueryBase {
}
// The token is always the same, let's exploit that
- if ( !isset( ApiQueryInfo::$cachedTokens[ 'import' ] ) ) {
- ApiQueryInfo::$cachedTokens[ 'import' ] = $wgUser->getEditToken();
+ if ( !isset( ApiQueryInfo::$cachedTokens['import'] ) ) {
+ ApiQueryInfo::$cachedTokens['import'] = $wgUser->getEditToken();
}
- return ApiQueryInfo::$cachedTokens[ 'import' ];
+ return ApiQueryInfo::$cachedTokens['import'];
}
public static function getWatchToken( $pageid, $title ) {
@@ -221,11 +223,11 @@ class ApiQueryInfo extends ApiQueryBase {
}
// The token is always the same, let's exploit that
- if ( !isset( ApiQueryInfo::$cachedTokens[ 'watch' ] ) ) {
- ApiQueryInfo::$cachedTokens[ 'watch' ] = $wgUser->getEditToken( 'watch' );
+ if ( !isset( ApiQueryInfo::$cachedTokens['watch'] ) ) {
+ ApiQueryInfo::$cachedTokens['watch'] = $wgUser->getEditToken( 'watch' );
}
- return ApiQueryInfo::$cachedTokens[ 'watch' ];
+ return ApiQueryInfo::$cachedTokens['watch'];
}
public static function getOptionsToken( $pageid, $title ) {
@@ -235,11 +237,11 @@ class ApiQueryInfo extends ApiQueryBase {
}
// The token is always the same, let's exploit that
- if ( !isset( ApiQueryInfo::$cachedTokens[ 'options' ] ) ) {
- ApiQueryInfo::$cachedTokens[ 'options' ] = $wgUser->getEditToken();
+ if ( !isset( ApiQueryInfo::$cachedTokens['options'] ) ) {
+ ApiQueryInfo::$cachedTokens['options'] = $wgUser->getEditToken();
}
- return ApiQueryInfo::$cachedTokens[ 'options' ];
+ return ApiQueryInfo::$cachedTokens['options'];
}
public function execute() {
@@ -248,6 +250,7 @@ class ApiQueryInfo extends ApiQueryBase {
$prop = array_flip( $this->params['prop'] );
$this->fld_protection = isset( $prop['protection'] );
$this->fld_watched = isset( $prop['watched'] );
+ $this->fld_watchers = isset( $prop['watchers'] );
$this->fld_notificationtimestamp = isset( $prop['notificationtimestamp'] );
$this->fld_talkid = isset( $prop['talkid'] );
$this->fld_subjectid = isset( $prop['subjectid'] );
@@ -268,10 +271,7 @@ class ApiQueryInfo extends ApiQueryBase {
// Throw away any titles we're gonna skip so they don't
// clutter queries
$cont = explode( '|', $this->params['continue'] );
- if ( count( $cont ) != 2 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the original ' .
- 'value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
$conttitle = Title::makeTitleSafe( $cont[0], $cont[1] );
foreach ( $this->everything as $pageid => $title ) {
if ( Title::compare( $title, $conttitle ) >= 0 ) {
@@ -308,6 +308,10 @@ class ApiQueryInfo extends ApiQueryBase {
$this->getWatchedInfo();
}
+ if ( $this->fld_watchers ) {
+ $this->getWatcherInfo();
+ }
+
// Run the talkid/subjectid query if requested
if ( $this->fld_talkid || $this->fld_subjectid ) {
$this->getTSIDs();
@@ -317,6 +321,7 @@ class ApiQueryInfo extends ApiQueryBase {
$this->getDisplayTitle();
}
+ /** @var $title Title */
foreach ( $this->everything as $pageid => $title ) {
$pageInfo = $this->extractPageInfo( $pageid, $title );
$fit = $result->addValue( array(
@@ -334,7 +339,7 @@ class ApiQueryInfo extends ApiQueryBase {
/**
* Get a result array with information about a title
- * @param $pageid int Page ID (negative for missing titles)
+ * @param int $pageid Page ID (negative for missing titles)
* @param $title Title object
* @return array
*/
@@ -349,7 +354,7 @@ class ApiQueryInfo extends ApiQueryBase {
$pageInfo['touched'] = wfTimestamp( TS_ISO_8601, $this->pageTouched[$pageid] );
$pageInfo['lastrevid'] = intval( $this->pageLatest[$pageid] );
$pageInfo['counter'] = $wgDisableCounters
- ? ""
+ ? ''
: intval( $this->pageCounter[$pageid] );
$pageInfo['length'] = intval( $this->pageLength[$pageid] );
@@ -387,6 +392,14 @@ class ApiQueryInfo extends ApiQueryBase {
$pageInfo['watched'] = '';
}
+ if ( $this->fld_watchers ) {
+ if ( isset( $this->watchers[$ns][$dbkey] ) ) {
+ $pageInfo['watchers'] = $this->watchers[$ns][$dbkey];
+ } elseif ( $this->showZeroWatchers ) {
+ $pageInfo['watchers'] = 0;
+ }
+ }
+
if ( $this->fld_notificationtimestamp ) {
$pageInfo['notificationtimestamp'] = '';
if ( isset( $this->notificationtimestamps[$ns][$dbkey] ) ) {
@@ -394,7 +407,7 @@ class ApiQueryInfo extends ApiQueryBase {
}
}
- if ( $this->fld_talkid && isset( $this->talkids[$ns][$dbkey] ) ) {
+ if ( $this->fld_talkid && isset( $this->talkids[$ns][$dbkey] ) ) {
$pageInfo['talkid'] = $this->talkids[$ns][$dbkey];
}
@@ -406,7 +419,7 @@ class ApiQueryInfo extends ApiQueryBase {
$pageInfo['fullurl'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
$pageInfo['editurl'] = wfExpandUrl( $title->getFullURL( 'action=edit' ), PROTO_CURRENT );
}
- if ( $this->fld_readable && $title->userCan( 'read' ) ) {
+ if ( $this->fld_readable && $title->userCan( 'read', $this->getUser() ) ) {
$pageInfo['readable'] = '';
}
@@ -450,6 +463,7 @@ class ApiQueryInfo extends ApiQueryBase {
$res = $this->select( __METHOD__ );
foreach ( $res as $row ) {
+ /** @var $title Title */
$title = $this->titles[$row->pr_page];
$a = array(
'type' => $row->pr_type,
@@ -585,6 +599,7 @@ class ApiQueryInfo extends ApiQueryBase {
private function getTSIDs() {
$getTitles = $this->talkids = $this->subjectids = array();
+ /** @var $t Title */
foreach ( $this->everything as $t ) {
if ( MWNamespace::isTalk( $t->getNamespace() ) ) {
if ( $this->fld_subjectid ) {
@@ -678,6 +693,46 @@ class ApiQueryInfo extends ApiQueryBase {
}
}
+ /**
+ * Get the count of watchers and put it in $this->watchers
+ */
+ private function getWatcherInfo() {
+ global $wgUnwatchedPageThreshold;
+
+ if ( count( $this->everything ) == 0 ) {
+ return;
+ }
+
+ $user = $this->getUser();
+ $canUnwatchedpages = $user->isAllowed( 'unwatchedpages' );
+ if ( !$canUnwatchedpages && !is_int( $wgUnwatchedPageThreshold ) ) {
+ return;
+ }
+
+ $this->watchers = array();
+ $this->showZeroWatchers = $canUnwatchedpages;
+ $db = $this->getDB();
+
+ $lb = new LinkBatch( $this->everything );
+
+ $this->resetQueryParams();
+ $this->addTables( array( 'watchlist' ) );
+ $this->addFields( array( 'wl_title', 'wl_namespace', 'count' => 'COUNT(*)' ) );
+ $this->addWhere( array(
+ $lb->constructSet( 'wl', $db )
+ ) );
+ $this->addOption( 'GROUP BY', array( 'wl_namespace', 'wl_title' ) );
+ if ( !$canUnwatchedpages ) {
+ $this->addOption( 'HAVING', "COUNT(*) >= $wgUnwatchedPageThreshold" );
+ }
+
+ $res = $this->select( __METHOD__ );
+
+ foreach ( $res as $row ) {
+ $this->watchers[$row->wl_namespace][$row->wl_title] = (int)$row->count;
+ }
+ }
+
public function getCacheMode( $params ) {
$publicProps = array(
'protection',
@@ -709,6 +764,7 @@ class ApiQueryInfo extends ApiQueryBase {
'protection',
'talkid',
'watched', # private
+ 'watchers', # private
'notificationtimestamp', # private
'subjectid',
'url',
@@ -734,6 +790,7 @@ class ApiQueryInfo extends ApiQueryBase {
' protection - List the protection level of each page',
' talkid - The page ID of the talk page for each non-talk page',
' watched - List the watched status of each page',
+ ' watchers - The number of watchers, if allowed',
' notificationtimestamp - The watchlist notification timestamp of each page',
' subjectid - The page ID of the parent page for each talk page',
' url - Gives a full URL to the page, and also an edit URL',
@@ -767,6 +824,12 @@ class ApiQueryInfo extends ApiQueryBase {
'watched' => array(
'watched' => 'boolean'
),
+ 'watchers' => array(
+ 'watchers' => array(
+ ApiBase::PROP_TYPE => 'integer',
+ ApiBase::PROP_NULLABLE => true
+ )
+ ),
'notificationtimestamp' => array(
'notificationtimestamp' => array(
ApiBase::PROP_TYPE => 'timestamp',
@@ -809,12 +872,6 @@ class ApiQueryInfo extends ApiQueryBase {
return 'Get basic page information such as namespace, title, last touched date, ...';
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
- ) );
- }
-
public function getExamples() {
return array(
'api.php?action=query&prop=info&titles=Main%20Page',
@@ -825,8 +882,4 @@ class ApiQueryInfo extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Properties#info_.2F_in';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryLangBacklinks.php b/includes/api/ApiQueryLangBacklinks.php
index 3920407b..7a4880a4 100644
--- a/includes/api/ApiQueryLangBacklinks.php
+++ b/includes/api/ApiQueryLangBacklinks.php
@@ -56,10 +56,7 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 3 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the ' .
- 'original value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 3 );
$db = $this->getDB();
$op = $params['dir'] == 'descending' ? '<' : '>';
@@ -233,7 +230,6 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'missingparam', 'lang' ),
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
) );
}
@@ -243,8 +239,4 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
'api.php?action=query&generator=langbacklinks&glbltitle=Test&glbllang=fr&prop=info'
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryLangLinks.php b/includes/api/ApiQueryLangLinks.php
index 3109a090..ac65d2d2 100644
--- a/includes/api/ApiQueryLangLinks.php
+++ b/includes/api/ApiQueryLangLinks.php
@@ -25,7 +25,7 @@
*/
/**
- * A query module to list all langlinks (links to correspanding foreign language pages).
+ * A query module to list all langlinks (links to corresponding foreign language pages).
*
* @ingroup API
*/
@@ -56,10 +56,7 @@ class ApiQueryLangLinks extends ApiQueryBase {
$this->addWhereFld( 'll_from', array_keys( $this->getPageSet()->getGoodTitles() ) );
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the ' .
- 'original value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
$op = $params['dir'] == 'descending' ? '<' : '>';
$llfrom = intval( $cont[0] );
$lllang = $this->getDB()->addQuotes( $cont[1] );
@@ -179,7 +176,6 @@ class ApiQueryLangLinks extends ApiQueryBase {
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'missingparam', 'lang' ),
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
) );
}
@@ -192,8 +188,4 @@ class ApiQueryLangLinks extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Properties#langlinks_.2F_ll';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryLinks.php b/includes/api/ApiQueryLinks.php
index 9e4b7ebb..937f4f13 100644
--- a/includes/api/ApiQueryLinks.php
+++ b/includes/api/ApiQueryLinks.php
@@ -79,7 +79,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
*/
private function run( $resultPageSet = null ) {
if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
- return; // nothing to do
+ return; // nothing to do
}
$params = $this->extractRequestParams();
@@ -112,10 +112,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 3 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the ' .
- 'original value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 3 );
$op = $params['dir'] == 'descending' ? '<' : '>';
$plfrom = intval( $cont[0] );
$plns = intval( $cont[1] );
@@ -241,17 +238,13 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
$desc = $this->description;
$name = $this->getModuleName();
return array(
- "api.php?action=query&prop={$name}&titles=Main%20Page" => "Get {$desc}s from the [[Main Page]]:",
- "api.php?action=query&generator={$name}&titles=Main%20Page&prop=info" => "Get information about the {$desc} pages in the [[Main Page]]:",
- "api.php?action=query&prop={$name}&titles=Main%20Page&{$this->prefix}namespace=2|10" => "Get {$desc}s from the Main Page in the User and Template namespaces:",
+ "api.php?action=query&prop={$name}&titles=Main%20Page" => "Get {$desc}s from the [[Main Page]]",
+ "api.php?action=query&generator={$name}&titles=Main%20Page&prop=info" => "Get information about the {$desc} pages in the [[Main Page]]",
+ "api.php?action=query&prop={$name}&titles=Main%20Page&{$this->prefix}namespace=2|10" => "Get {$desc}s from the Main Page in the User and Template namespaces",
);
}
public function getHelpUrls() {
return $this->helpUrl;
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php
index 5d85c221..73dcea49 100644
--- a/includes/api/ApiQueryLogEvents.php
+++ b/includes/api/ApiQueryLogEvents.php
@@ -58,7 +58,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
$this->fld_details = isset( $prop['details'] );
$this->fld_tags = isset( $prop['tags'] );
- $hideLogs = LogEventsList::getExcludeClause( $db );
+ $hideLogs = LogEventsList::getExcludeClause( $db, 'user', $this->getUser() );
if ( $hideLogs !== false ) {
$this->addWhere( $hideLogs );
}
@@ -70,7 +70,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
'user' => array( 'JOIN',
'user_id=log_user' ),
'page' => array( 'LEFT JOIN',
- array( 'log_namespace=page_namespace',
+ array( 'log_namespace=page_namespace',
'log_title=page_title' ) ) ) );
$index = array( 'logging' => 'times' ); // default, may change
@@ -151,7 +151,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
if ( is_null( $title ) ) {
$this->dieUsage( "Bad title value '$prefix'", 'param_prefix' );
}
- $this->addWhereFld( 'log_namespace', $title->getNamespace() );
+ $this->addWhereFld( 'log_namespace', $title->getNamespace() );
$this->addWhere( 'log_title ' . $db->buildLike( $title->getDBkey(), $db->anyString() ) );
}
@@ -201,7 +201,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
public static function addLogParams( $result, &$vals, $params, $type, $action, $ts, $legacy = false ) {
switch ( $type ) {
case 'move':
- if ( $legacy ){
+ if ( $legacy ) {
$targetKey = 0;
$noredirKey = 1;
} else {
@@ -209,21 +209,21 @@ class ApiQueryLogEvents extends ApiQueryBase {
$noredirKey = '5::noredir';
}
- if ( isset( $params[ $targetKey ] ) ) {
- $title = Title::newFromText( $params[ $targetKey ] );
+ if ( isset( $params[$targetKey] ) ) {
+ $title = Title::newFromText( $params[$targetKey] );
if ( $title ) {
$vals2 = array();
ApiQueryBase::addTitleInfo( $vals2, $title, 'new_' );
$vals[$type] = $vals2;
}
}
- if ( isset( $params[ $noredirKey ] ) && $params[ $noredirKey ] ) {
+ if ( isset( $params[$noredirKey] ) && $params[$noredirKey] ) {
$vals[$type]['suppressedredirect'] = '';
}
$params = null;
break;
case 'patrol':
- if ( $legacy ){
+ if ( $legacy ) {
$cur = 0;
$prev = 1;
$auto = 2;
@@ -241,7 +241,12 @@ class ApiQueryLogEvents extends ApiQueryBase {
break;
case 'rights':
$vals2 = array();
- list( $vals2['old'], $vals2['new'] ) = $params;
+ if( $legacy ) {
+ list( $vals2['old'], $vals2['new'] ) = $params;
+ } else {
+ $vals2['new'] = implode( ', ', $params['5::newgroups'] );
+ $vals2['old'] = implode( ', ', $params['4::oldgroups'] );
+ }
$vals[$type] = $vals2;
$params = null;
break;
@@ -262,9 +267,19 @@ class ApiQueryLogEvents extends ApiQueryBase {
break;
}
if ( !is_null( $params ) ) {
- $result->setIndexedTagName( $params, 'param' );
- $result->setIndexedTagName_recursive( $params, 'param' );
- $vals = array_merge( $vals, $params );
+ $logParams = array();
+ // Keys like "4::paramname" can't be used for output so we change them to "paramname"
+ foreach ( $params as $key => $value ) {
+ if ( strpos( $key, ':' ) === false ) {
+ $logParams[$key] = $value;
+ continue;
+ }
+ $logParam = explode( ':', $key, 3 );
+ $logParams[$logParam[2]] = $value;
+ }
+ $result->setIndexedTagName( $logParams, 'param' );
+ $result->setIndexedTagName_recursive( $logParams, 'param' );
+ $vals = array_merge( $vals, $logParams );
}
return $vals;
}
@@ -362,8 +377,12 @@ class ApiQueryLogEvents extends ApiQueryBase {
if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
// formatComment() calls wfMessage() among other things
return 'anon-public-user-private';
- } else {
+ } elseif ( LogEventsList::getExcludeClause( $this->getDB(), 'user', $this->getUser() )
+ === LogEventsList::getExcludeClause( $this->getDB(), 'public' )
+ ) { // Output can only contain public data.
return 'public';
+ } else {
+ return 'anon-public-user-private';
}
}
@@ -432,7 +451,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
' timestamp - Adds the timestamp for the event',
' comment - Adds the comment of the event',
' parsedcomment - Adds the parsed comment of the event',
- ' details - Lists addtional details about the event',
+ ' details - Lists additional details about the event',
' tags - Lists tags for the event',
),
'type' => 'Filter log entries to only this type',
@@ -526,8 +545,4 @@ class ApiQueryLogEvents extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Logevents';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryORM.php b/includes/api/ApiQueryORM.php
new file mode 100644
index 00000000..41d8f11c
--- /dev/null
+++ b/includes/api/ApiQueryORM.php
@@ -0,0 +1,264 @@
+<?php
+
+/**
+ * Base query module for querying results from ORMTables.
+ *
+ * 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
+ *
+ * @since 1.21
+ *
+ * @file
+ * @ingroup API
+ *
+ * @license GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+abstract class ApiQueryORM extends ApiQueryBase {
+
+ /**
+ * Returns an instance of the IORMTable table being queried.
+ *
+ * @since 1.21
+ *
+ * @return IORMTable
+ */
+ abstract protected function getTable();
+
+ /**
+ * Returns the name of the individual rows.
+ * For example: page, user, contest, campaign, etc.
+ * This is used to appropriately name elements in XML.
+ * Deriving classes typically override this method.
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ protected function getRowName() {
+ return 'item';
+ }
+
+ /**
+ * Returns the name of the list of rows.
+ * For example: pages, users, contests, campaigns, etc.
+ * This is used to appropriately name nodes in the output.
+ * Deriving classes typically override this method.
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ protected function getListName() {
+ return 'items';
+ }
+
+ /**
+ * Returns the path to where the items results should be added in the result.
+ *
+ * @since 1.21
+ *
+ * @return null|string|array
+ */
+ protected function getResultPath() {
+ return null;
+ }
+
+ /**
+ * Get the parameters, find out what the conditions for the query are,
+ * run it, and add the results.
+ *
+ * @since 1.21
+ */
+ public function execute() {
+ $params = $this->getParams();
+
+ if ( !in_array( 'id', $params['props'] ) ) {
+ $params['props'][] = 'id';
+ }
+
+ $results = $this->getResults( $params, $this->getConditions( $params ) );
+ $this->addResults( $params, $results );
+ }
+
+ /**
+ * Get the request parameters and remove all params set
+ * to null (ie those that are not actually provided).
+ *
+ * @since 1.21
+ *
+ * @return array
+ */
+ protected function getParams() {
+ return array_filter(
+ $this->extractRequestParams(),
+ function( $prop ) {
+ return isset( $prop );
+ }
+ );
+ }
+
+ /**
+ * Get the conditions for the query. These will be provided as
+ * regular parameters, together with limit, props, continue,
+ * and possibly others which we need to get rid off.
+ *
+ * @since 1.21
+ *
+ * @param array $params
+ *
+ * @return array
+ */
+ protected function getConditions( array $params ) {
+ $conditions = array();
+ $fields = $this->getTable()->getFields();
+
+ foreach ( $params as $name => $value ) {
+ if ( array_key_exists( $name, $fields ) ) {
+ $conditions[$name] = $value;
+ }
+ }
+
+ return $conditions;
+ }
+
+ /**
+ * Get the actual results.
+ *
+ * @since 1.21
+ *
+ * @param array $params
+ * @param array $conditions
+ *
+ * @return ORMResult
+ */
+ protected function getResults( array $params, array $conditions ) {
+ return $this->getTable()->select(
+ $params['props'],
+ $conditions,
+ array(
+ 'LIMIT' => $params['limit'] + 1,
+ 'ORDER BY' => $this->getTable()->getPrefixedField( 'id' ) . ' ASC',
+ ),
+ __METHOD__
+ );
+ }
+
+ /**
+ * Serialize the results and add them to the result object.
+ *
+ * @since 1.21
+ *
+ * @param array $params
+ * @param ORMResult $results
+ */
+ protected function addResults( array $params, ORMResult $results ) {
+ $serializedResults = array();
+ $count = 0;
+
+ foreach ( $results as /* IORMRow */ $result ) {
+ if ( ++$count > $params['limit'] ) {
+ // We've reached the one extra which shows that
+ // there are additional pages to be had. Stop here...
+ $this->setContinueEnumParameter( 'continue', $result->getId() );
+ break;
+ }
+
+ $serializedResults[] = $this->formatRow( $result, $params );
+ }
+
+ $this->setIndexedTagNames( $serializedResults );
+ $this->addSerializedResults( $serializedResults );
+ }
+
+ /**
+ * Formats a row to it's desired output format.
+ *
+ * @since 1.21
+ *
+ * @param IORMRow $result
+ * @param array $params
+ *
+ * @return mixed
+ */
+ protected function formatRow( IORMRow $result, array $params ) {
+ return $result->toArray( $params['props'] );
+ }
+
+ /**
+ * Set the tag names for formats such as XML.
+ *
+ * @since 1.21
+ *
+ * @param array $serializedResults
+ */
+ protected function setIndexedTagNames( array &$serializedResults ) {
+ $this->getResult()->setIndexedTagName( $serializedResults, $this->getRowName() );
+ }
+
+ /**
+ * Add the serialized results to the result object.
+ *
+ * @since 1.21
+ *
+ * @param array $serializedResults
+ */
+ protected function addSerializedResults( array $serializedResults ) {
+ $this->getResult()->addValue(
+ $this->getResultPath(),
+ $this->getListName(),
+ $serializedResults
+ );
+ }
+
+ /**
+ * @see ApiBase::getAllowedParams()
+ * @return array
+ */
+ public function getAllowedParams() {
+ $params = array (
+ 'props' => array(
+ ApiBase::PARAM_TYPE => $this->getTable()->getFieldNames(),
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_REQUIRED => true,
+ ),
+ 'limit' => array(
+ ApiBase::PARAM_DFLT => 20,
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
+ ),
+ 'continue' => null,
+ );
+
+ return array_merge( $this->getTable()->getAPIParams(), $params );
+ }
+
+ /**
+ * @see ApiBase::getParamDescription()
+ * @return array
+ */
+ public function getParamDescription() {
+ $descriptions = array (
+ 'props' => 'Fields to query',
+ 'continue' => 'Offset number from where to continue the query',
+ 'limit' => 'Max amount of rows to return',
+ );
+
+ return array_merge( $this->getTable()->getFieldDescriptions(), $descriptions );
+ }
+
+}
diff --git a/includes/api/ApiQueryPagePropNames.php b/includes/api/ApiQueryPagePropNames.php
new file mode 100644
index 00000000..08c883d8
--- /dev/null
+++ b/includes/api/ApiQueryPagePropNames.php
@@ -0,0 +1,116 @@
+<?php
+/**
+ * Created on January 21, 2013
+ *
+ * Copyright © 2013 Brad Jorsch <bjorsch@wikimedia.org>
+ *
+ * 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
+ * @since 1.21
+ * @author Brad Jorsch
+ */
+
+/**
+ * A query module to list used page props
+ *
+ * @ingroup API
+ * @since 1.21
+ */
+class ApiQueryPagePropNames extends ApiQueryBase {
+
+ public function __construct( $query, $moduleName ) {
+ parent::__construct( $query, $moduleName, 'ppn' );
+ }
+
+ public function getCacheMode( $params ) {
+ return 'public';
+ }
+
+ public function execute() {
+ $params = $this->extractRequestParams();
+
+ $this->addTables( 'page_props' );
+ $this->addFields( 'pp_propname' );
+ $this->addOption( 'DISTINCT' );
+ $this->addOption( 'ORDER BY', 'pp_propname' );
+
+ if ( $params['continue'] ) {
+ $cont = explode( '|', $params['continue'] );
+ $this->dieContinueUsageIf( count( $cont ) != 1 );
+
+ // Add a WHERE clause
+ $this->addWhereRange( 'pp_propname', 'newer', $cont[0], null );
+ }
+
+ $limit = $params['limit'];
+ $this->addOption( 'LIMIT', $limit + 1 );
+
+ $result = $this->getResult();
+ $count = 0;
+ foreach ( $this->select( __METHOD__ ) as $row ) {
+ if ( ++$count > $limit ) {
+ // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ $this->setContinueEnumParameter( 'continue', $row->pp_propname );
+ break;
+ }
+
+ $vals = array();
+ $vals['propname'] = $row->pp_propname;
+ $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
+ if ( !$fit ) {
+ $this->setContinueEnumParameter( 'continue', $row->pp_propname );
+ break;
+ }
+ }
+
+ $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'p' );
+ }
+
+ public function getAllowedParams() {
+ return array(
+ 'continue' => null,
+ 'limit' => array(
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_DFLT => 10,
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
+ ),
+ );
+ }
+
+ public function getParamDescription() {
+ return array(
+ 'continue' => 'When more results are available, use this to continue',
+ 'limit' => 'The maximum number of pages to return',
+ );
+ }
+
+ public function getDescription() {
+ return 'List all page prop names in use on the wiki';
+ }
+
+ public function getExamples() {
+ return array(
+ 'api.php?action=query&list=pagepropnames' => 'Get first 10 prop names',
+ );
+ }
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Pagepropnames';
+ }
+}
diff --git a/includes/api/ApiQueryPageProps.php b/includes/api/ApiQueryPageProps.php
index 1eef67e6..2de57106 100644
--- a/includes/api/ApiQueryPageProps.php
+++ b/includes/api/ApiQueryPageProps.php
@@ -49,7 +49,7 @@ class ApiQueryPageProps extends ApiQueryBase {
$this->addTables( 'page_props' );
$this->addFields( array( 'pp_page', 'pp_propname', 'pp_value' ) );
- $this->addWhereFld( 'pp_page', array_keys( $pages ) );
+ $this->addWhereFld( 'pp_page', array_keys( $pages ) );
if ( $this->params['continue'] ) {
$this->addWhere( 'pp_page >=' . intval( $this->params['continue'] ) );
@@ -60,7 +60,10 @@ class ApiQueryPageProps extends ApiQueryBase {
}
# Force a sort order to ensure that properties are grouped by page
- $this->addOption( 'ORDER BY', 'pp_page' );
+ # But only if pp_page is not constant in the WHERE clause.
+ if ( count( $pages ) > 1 ) {
+ $this->addOption( 'ORDER BY', 'pp_page' );
+ }
$res = $this->select( __METHOD__ );
$currentPage = 0; # Id of the page currently processed
@@ -122,14 +125,16 @@ class ApiQueryPageProps extends ApiQueryBase {
public function getAllowedParams() {
return array(
'continue' => null,
- 'prop' => null,
+ 'prop' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ),
);
}
public function getParamDescription() {
return array(
'continue' => 'When more results are available, use this to continue',
- 'prop' => 'Page prop to look on the page for. Useful for checking whether a certain page uses a certain page prop.'
+ 'prop' => 'Only list these props. Useful for checking whether a certain page uses a certain page prop',
);
}
@@ -146,8 +151,4 @@ class ApiQueryPageProps extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Properties#pageprops_.2F_pp';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryPagesWithProp.php b/includes/api/ApiQueryPagesWithProp.php
new file mode 100644
index 00000000..0132fc3e
--- /dev/null
+++ b/includes/api/ApiQueryPagesWithProp.php
@@ -0,0 +1,189 @@
+<?php
+/**
+ * Created on December 31, 2012
+ *
+ * Copyright © 2012 Brad Jorsch <bjorsch@wikimedia.org>
+ *
+ * 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
+ * @since 1.21
+ * @author Brad Jorsch
+ */
+
+/**
+ * A query module to enumerate pages that use a particular prop
+ *
+ * @ingroup API
+ * @since 1.21
+ */
+class ApiQueryPagesWithProp extends ApiQueryGeneratorBase {
+
+ public function __construct( $query, $moduleName ) {
+ parent::__construct( $query, $moduleName, 'pwp' );
+ }
+
+ public function execute() {
+ $this->run();
+ }
+
+ public function getCacheMode( $params ) {
+ return 'public';
+ }
+
+ public function executeGenerator( $resultPageSet ) {
+ $this->run( $resultPageSet );
+ }
+
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return void
+ */
+ private function run( $resultPageSet = null ) {
+ $params = $this->extractRequestParams();
+
+ $prop = array_flip( $params['prop'] );
+ $fld_ids = isset( $prop['ids'] );
+ $fld_title = isset( $prop['title'] );
+ $fld_value = isset( $prop['value'] );
+
+ if ( $resultPageSet === null ) {
+ $this->addFields( array( 'page_id' ) );
+ $this->addFieldsIf( array( 'page_title', 'page_namespace' ), $fld_title );
+ $this->addFieldsIf( 'pp_value', $fld_value );
+ } else {
+ $this->addFields( $resultPageSet->getPageTableFields() );
+ }
+ $this->addTables( array( 'page_props', 'page' ) );
+ $this->addWhere( 'pp_page=page_id' );
+ $this->addWhereFld( 'pp_propname', $params['propname'] );
+
+ $dir = ( $params['dir'] == 'ascending' ) ? 'newer' : 'older';
+
+ if ( $params['continue'] ) {
+ $cont = explode( '|', $params['continue'] );
+ $this->dieContinueUsageIf( count( $cont ) != 1 );
+
+ // Add a WHERE clause
+ $from = (int)$cont[0];
+ $this->addWhereRange( 'pp_page', $dir, $from, null );
+ }
+
+ $sort = ( $params['dir'] === 'descending' ? ' DESC' : '' );
+ $this->addOption( 'ORDER BY', 'pp_page' . $sort );
+
+ $limit = $params['limit'];
+ $this->addOption( 'LIMIT', $limit + 1 );
+
+ $result = $this->getResult();
+ $count = 0;
+ foreach ( $this->select( __METHOD__ ) as $row ) {
+ if ( ++$count > $limit ) {
+ // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ $this->setContinueEnumParameter( 'continue', $row->page_id );
+ break;
+ }
+
+ if ( $resultPageSet === null ) {
+ $vals = array();
+ if ( $fld_ids ) {
+ $vals['pageid'] = (int)$row->page_id;
+ }
+ if ( $fld_title ) {
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+ ApiQueryBase::addTitleInfo( $vals, $title );
+ }
+ if ( $fld_value ) {
+ $vals['value'] = $row->pp_value;
+ }
+ $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
+ if ( !$fit ) {
+ $this->setContinueEnumParameter( 'continue', $row->page_id );
+ break;
+ }
+ } else {
+ $resultPageSet->processDbRow( $row );
+ }
+ }
+
+ if ( $resultPageSet === null ) {
+ $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'page' );
+ }
+ }
+
+ public function getAllowedParams() {
+ return array(
+ 'propname' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true,
+ ),
+ 'prop' => array(
+ ApiBase::PARAM_DFLT => 'ids|title',
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array (
+ 'ids',
+ 'title',
+ 'value',
+ )
+ ),
+ 'continue' => null,
+ 'limit' => array(
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_DFLT => 10,
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
+ ),
+ 'dir' => array(
+ ApiBase::PARAM_DFLT => 'ascending',
+ ApiBase::PARAM_TYPE => array(
+ 'ascending',
+ 'descending',
+ )
+ ),
+ );
+ }
+
+ public function getParamDescription() {
+ return array(
+ 'propname' => 'Page prop for which to enumerate pages',
+ 'prop' => array(
+ 'What pieces of information to include',
+ ' ids - Adds the page ID',
+ ' title - Adds the title and namespace ID of the page',
+ ' value - Adds the value of the page prop',
+ ),
+ 'dir' => 'In which direction to sort',
+ 'continue' => 'When more results are available, use this to continue',
+ 'limit' => 'The maximum number of pages to return',
+ );
+ }
+
+ public function getDescription() {
+ return 'List all pages using a given page prop';
+ }
+
+ public function getExamples() {
+ return array(
+ 'api.php?action=query&list=pageswithprop&pwppropname=displaytitle&pwpprop=ids|title|value' => 'Get first 10 pages using {{DISPLAYTITLE:}}',
+ 'api.php?action=query&generator=pageswithprop&gpwppropname=notoc&prop=info' => 'Get page info about first 10 pages using __NOTOC__',
+ );
+ }
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Pageswithprop';
+ }
+}
diff --git a/includes/api/ApiQueryProtectedTitles.php b/includes/api/ApiQueryProtectedTitles.php
index 14aed28d..4aa00007 100644
--- a/includes/api/ApiQueryProtectedTitles.php
+++ b/includes/api/ApiQueryProtectedTitles.php
@@ -98,7 +98,7 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
$vals['user'] = $row->user_name;
}
- if ( isset( $prop['user'] ) ) {
+ if ( isset( $prop['userid'] ) || /*B/C*/isset( $prop['user'] ) ) {
$vals['userid'] = $row->pt_user;
}
@@ -231,6 +231,9 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
),
'userid' => 'integer'
),
+ 'userid' => array(
+ 'userid' => 'integer'
+ ),
'comment' => array(
'comment' => 'string'
),
@@ -261,8 +264,4 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Protectedtitles';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryQueryPage.php b/includes/api/ApiQueryQueryPage.php
index a8be26d3..b03bdfb8 100644
--- a/includes/api/ApiQueryQueryPage.php
+++ b/includes/api/ApiQueryQueryPage.php
@@ -75,6 +75,7 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
$params = $this->extractRequestParams();
$result = $this->getResult();
+ /** @var $qp QueryPage */
$qp = new $this->qpMap[$params['page']]();
if ( !$qp->userCanExecute( $this->getUser() ) ) {
$this->dieUsageMsg( 'specialpage-cantexecute' );
@@ -141,6 +142,7 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
}
public function getCacheMode( $params ) {
+ /** @var $qp QueryPage */
$qp = new $this->qpMap[$params['page']]();
if ( $qp->getRestriction() != '' ) {
return 'private';
@@ -211,7 +213,7 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
- array( 'specialpage-cantexecute' )
+ array( 'specialpage-cantexecute' )
) );
}
@@ -220,8 +222,4 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
'api.php?action=query&list=querypage&qppage=Ancientpages'
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryRandom.php b/includes/api/ApiQueryRandom.php
index ddf5841b..ae3bb893 100644
--- a/includes/api/ApiQueryRandom.php
+++ b/includes/api/ApiQueryRandom.php
@@ -33,6 +33,8 @@
class ApiQueryRandom extends ApiQueryGeneratorBase {
+ private $pageIDs;
+
public function __construct( $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'rn' );
}
@@ -183,8 +185,4 @@ class ApiQueryRandom extends ApiQueryGeneratorBase {
public function getExamples() {
return 'api.php?action=query&list=random&rnnamespace=0&rnlimit=2';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryRandom.php overlordq$';
- }
}
diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php
index 7ae4f371..8aceab22 100644
--- a/includes/api/ApiQueryRecentChanges.php
+++ b/includes/api/ApiQueryRecentChanges.php
@@ -105,7 +105,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
/**
* Sets internal state to include the desired properties in the output.
- * @param $prop Array associative array of properties, only keys are used here
+ * @param array $prop associative array of properties, only keys are used here
*/
public function initProperties( $prop ) {
$this->fld_comment = isset( $prop['comment'] );
@@ -149,6 +149,31 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
$this->addTables( 'recentchanges' );
$index = array( 'recentchanges' => 'rc_timestamp' ); // May change
$this->addTimestampWhereRange( 'rc_timestamp', $params['dir'], $params['start'], $params['end'] );
+
+ if ( !is_null( $params['continue'] ) ) {
+ $cont = explode( '|', $params['continue'] );
+ if ( count( $cont ) != 2 ) {
+ $this->dieUsage( 'Invalid continue param. You should pass the ' .
+ 'original value returned by the previous query', '_badcontinue' );
+ }
+
+ $timestamp = $this->getDB()->addQuotes( wfTimestamp( TS_MW, $cont[0] ) );
+ $id = intval( $cont[1] );
+ $op = $params['dir'] === 'older' ? '<' : '>';
+
+ $this->addWhere(
+ "rc_timestamp $op $timestamp OR " .
+ "(rc_timestamp = $timestamp AND " .
+ "rc_id $op= $id)"
+ );
+ }
+
+ $order = $params['dir'] === 'older' ? 'DESC' : 'ASC';
+ $this->addOption( 'ORDER BY', array(
+ "rc_timestamp $order",
+ "rc_id $order",
+ ) );
+
$this->addWhereFld( 'rc_namespace', $params['namespace'] );
$this->addWhereFld( 'rc_deleted', 0 );
@@ -214,8 +239,6 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
'rc_title',
'rc_cur_id',
'rc_type',
- 'rc_moved_to_ns',
- 'rc_moved_to_title',
'rc_deleted'
) );
@@ -231,12 +254,13 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
$this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' );
}
+ $this->addFields( 'rc_id' );
/* Add fields to our query if they are specified as a needed parameter. */
- $this->addFieldsIf( array( 'rc_id', 'rc_this_oldid', 'rc_last_oldid' ), $this->fld_ids );
+ $this->addFieldsIf( array( 'rc_this_oldid', 'rc_last_oldid' ), $this->fld_ids );
$this->addFieldsIf( 'rc_comment', $this->fld_comment || $this->fld_parsedcomment );
$this->addFieldsIf( 'rc_user', $this->fld_user );
$this->addFieldsIf( 'rc_user_text', $this->fld_user || $this->fld_userid );
- $this->addFieldsIf( array( 'rc_minor', 'rc_type', 'rc_bot' ) , $this->fld_flags );
+ $this->addFieldsIf( array( 'rc_minor', 'rc_type', 'rc_bot' ), $this->fld_flags );
$this->addFieldsIf( array( 'rc_old_len', 'rc_new_len' ), $this->fld_sizes );
$this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled );
$this->addFieldsIf( array( 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ), $this->fld_loginfo );
@@ -262,7 +286,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
if ( !is_null( $params['tag'] ) ) {
$this->addTables( 'change_tag' );
$this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rc_id=ct_rc_id' ) ) ) );
- $this->addWhereFld( 'ct_tag' , $params['tag'] );
+ $this->addWhereFld( 'ct_tag', $params['tag'] );
global $wgOldChangeTagsIndex;
$index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id';
}
@@ -283,7 +307,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
foreach ( $res as $row ) {
if ( ++ $count > $params['limit'] ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) . '|' . $row->rc_id );
break;
}
@@ -297,7 +321,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) . '|' . $row->rc_id );
break;
}
} else {
@@ -316,17 +340,11 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
/**
* Extracts from a single sql row the data needed to describe one recent change.
*
- * @param $row The row from which to extract the data.
+ * @param mixed $row The row from which to extract the data.
* @return array An array mapping strings (descriptors) to their respective string values.
* @access public
*/
public function extractRowInfo( $row ) {
- /* If page was moved somewhere, get the title of the move target. */
- $movedToTitle = false;
- if ( isset( $row->rc_moved_to_title ) && $row->rc_moved_to_title !== '' ) {
- $movedToTitle = Title::makeTitle( $row->rc_moved_to_ns, $row->rc_moved_to_title );
- }
-
/* Determine the title of the page that has been changed. */
$title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
@@ -349,6 +367,9 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
case RC_LOG:
$vals['type'] = 'log';
break;
+ case RC_EXTERNAL:
+ $vals['type'] = 'external';
+ break;
case RC_MOVE_OVER_REDIRECT:
$vals['type'] = 'move over redirect';
break;
@@ -359,9 +380,6 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
/* Create a new entry in the result for the title. */
if ( $this->fld_title ) {
ApiQueryBase::addTitleInfo( $vals, $title );
- if ( $movedToTitle ) {
- ApiQueryBase::addTitleInfo( $vals, $movedToTitle, 'new_' );
- }
}
/* Add ids, such as rcid, pageid, revid, and oldid to the change's info. */
@@ -488,6 +506,8 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
return RC_NEW;
case 'log':
return RC_LOG;
+ case 'external':
+ return RC_EXTERNAL;
}
}
@@ -584,11 +604,13 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => array(
'edit',
+ 'external',
'new',
'log'
)
),
'toponly' => false,
+ 'continue' => null,
);
}
@@ -626,6 +648,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
'limit' => 'How many total changes to return',
'tag' => 'Only list changes tagged with this tag',
'toponly' => 'Only list changes which are the latest revision',
+ 'continue' => 'When more results are available, use this to continue',
);
}
@@ -741,8 +764,4 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Recentchanges';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php
index b89a8ea9..192fe873 100644
--- a/includes/api/ApiQueryRevisions.php
+++ b/includes/api/ApiQueryRevisions.php
@@ -34,15 +34,15 @@
class ApiQueryRevisions extends ApiQueryBase {
private $diffto, $difftotext, $expandTemplates, $generateXML, $section,
- $token, $parseContent;
+ $token, $parseContent, $contentFormat;
public function __construct( $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'rv' );
}
- private $fld_ids = false, $fld_flags = false, $fld_timestamp = false, $fld_size = false,
+ private $fld_ids = false, $fld_flags = false, $fld_timestamp = false, $fld_size = false, $fld_sha1 = false,
$fld_comment = false, $fld_parsedcomment = false, $fld_user = false, $fld_userid = false,
- $fld_content = false, $fld_tags = false;
+ $fld_content = false, $fld_tags = false, $fld_contentmodel = false;
private $tokenFunctions;
@@ -95,7 +95,6 @@ class ApiQueryRevisions extends ApiQueryBase {
!is_null( $params['endid'] ) || $params['dir'] === 'newer' ||
!is_null( $params['start'] ) || !is_null( $params['end'] ) );
-
$pageSet = $this->getPageSet();
$pageCount = $pageSet->getGoodTitleCount();
$revCount = $pageSet->getRevisionCount();
@@ -155,15 +154,20 @@ class ApiQueryRevisions extends ApiQueryBase {
$this->fld_parsedcomment = isset ( $prop['parsedcomment'] );
$this->fld_size = isset ( $prop['size'] );
$this->fld_sha1 = isset ( $prop['sha1'] );
+ $this->fld_contentmodel = isset ( $prop['contentmodel'] );
$this->fld_userid = isset( $prop['userid'] );
$this->fld_user = isset ( $prop['user'] );
$this->token = $params['token'];
+ if ( !empty( $params['contentformat'] ) ) {
+ $this->contentFormat = $params['contentformat'];
+ }
+
// Possible indexes used
$index = array();
$userMax = ( $this->fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1 );
- $botMax = ( $this->fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2 );
+ $botMax = ( $this->fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2 );
$limit = $params['limit'];
if ( $limit == 'max' ) {
$limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
@@ -184,15 +188,17 @@ class ApiQueryRevisions extends ApiQueryBase {
if ( !is_null( $params['tag'] ) ) {
$this->addTables( 'change_tag' );
$this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rev_id=ct_rev_id' ) ) ) );
- $this->addWhereFld( 'ct_tag' , $params['tag'] );
+ $this->addWhereFld( 'ct_tag', $params['tag'] );
global $wgOldChangeTagsIndex;
$index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id';
}
if ( isset( $prop['content'] ) || !is_null( $this->difftotext ) ) {
// For each page we will request, the user must have read rights for that page
+ $user = $this->getUser();
+ /** @var $title Title */
foreach ( $pageSet->getGoodTitles() as $title ) {
- if ( !$title->userCan( 'read' ) ) {
+ if ( !$title->userCan( 'read', $user ) ) {
$this->dieUsage(
'The current user is not allowed to read ' . $title->getPrefixedText(),
'accessdenied' );
@@ -255,7 +261,7 @@ class ApiQueryRevisions extends ApiQueryBase {
// rvstart and rvstartid when that is supplied.
if ( !is_null( $params['continue'] ) ) {
$params['startid'] = $params['continue'];
- unset( $params['start'] );
+ $params['start'] = null;
}
// This code makes an assumption that sorting by rev_id and rev_timestamp produces
@@ -332,10 +338,7 @@ class ApiQueryRevisions extends ApiQueryBase {
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the original ' .
- 'value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
$pageid = intval( $cont[0] );
$revid = intval( $cont[1] );
$this->addWhere(
@@ -433,12 +436,18 @@ class ApiQueryRevisions extends ApiQueryBase {
}
}
- if ( $this->fld_sha1 ) {
+ if ( $this->fld_sha1 && !$revision->isDeleted( Revision::DELETED_TEXT ) ) {
if ( $revision->getSha1() != '' ) {
$vals['sha1'] = wfBaseConvert( $revision->getSha1(), 36, 16, 40 );
} else {
$vals['sha1'] = '';
}
+ } elseif ( $this->fld_sha1 ) {
+ $vals['sha1hidden'] = '';
+ }
+
+ if ( $this->fld_contentmodel ) {
+ $vals['contentmodel'] = $revision->getContentModel();
}
if ( $this->fld_comment || $this->fld_parsedcomment ) {
@@ -479,55 +488,121 @@ class ApiQueryRevisions extends ApiQueryBase {
}
}
- $text = null;
+ $content = null;
global $wgParser;
- if ( $this->fld_content || !is_null( $this->difftotext ) ) {
- $text = $revision->getText();
+ if ( $this->fld_content || !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
+ $content = $revision->getContent();
// Expand templates after getting section content because
// template-added sections don't count and Parser::preprocess()
// will have less input
- if ( $this->section !== false ) {
- $text = $wgParser->getSection( $text, $this->section, false );
- if ( $text === false ) {
+ if ( $content && $this->section !== false ) {
+ $content = $content->getSection( $this->section, false );
+ if ( !$content ) {
$this->dieUsage( "There is no section {$this->section} in r" . $revision->getId(), 'nosuchsection' );
}
}
}
- if ( $this->fld_content && !$revision->isDeleted( Revision::DELETED_TEXT ) ) {
+ if ( $this->fld_content && $content && !$revision->isDeleted( Revision::DELETED_TEXT ) ) {
+ $text = null;
+
if ( $this->generateXML ) {
- $wgParser->startExternalParse( $title, ParserOptions::newFromContext( $this->getContext() ), OT_PREPROCESS );
- $dom = $wgParser->preprocessToDom( $text );
- if ( is_callable( array( $dom, 'saveXML' ) ) ) {
- $xml = $dom->saveXML();
+ if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
+ $t = $content->getNativeData(); # note: don't set $text
+
+ $wgParser->startExternalParse( $title, ParserOptions::newFromContext( $this->getContext() ), OT_PREPROCESS );
+ $dom = $wgParser->preprocessToDom( $t );
+ if ( is_callable( array( $dom, 'saveXML' ) ) ) {
+ $xml = $dom->saveXML();
+ } else {
+ $xml = $dom->__toString();
+ }
+ $vals['parsetree'] = $xml;
} else {
- $xml = $dom->__toString();
+ $this->setWarning( "Conversion to XML is supported for wikitext only, " .
+ $title->getPrefixedDBkey() .
+ " uses content model " . $content->getModel() . ")" );
}
- $vals['parsetree'] = $xml;
-
}
+
if ( $this->expandTemplates && !$this->parseContent ) {
- $text = $wgParser->preprocess( $text, $title, ParserOptions::newFromContext( $this->getContext() ) );
+ #XXX: implement template expansion for all content types in ContentHandler?
+ if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
+ $text = $content->getNativeData();
+
+ $text = $wgParser->preprocess( $text, $title, ParserOptions::newFromContext( $this->getContext() ) );
+ } else {
+ $this->setWarning( "Template expansion is supported for wikitext only, " .
+ $title->getPrefixedDBkey() .
+ " uses content model " . $content->getModel() . ")" );
+
+ $text = false;
+ }
}
if ( $this->parseContent ) {
- $text = $wgParser->parse( $text, $title, ParserOptions::newFromContext( $this->getContext() ) )->getText();
+ $po = $content->getParserOutput( $title, $revision->getId(), ParserOptions::newFromContext( $this->getContext() ) );
+ $text = $po->getText();
+ }
+
+ if ( $text === null ) {
+ $format = $this->contentFormat ? $this->contentFormat : $content->getDefaultFormat();
+ $model = $content->getModel();
+
+ if ( !$content->isSupportedFormat( $format ) ) {
+ $name = $title->getPrefixedDBkey();
+
+ $this->dieUsage( "The requested format {$this->contentFormat} is not supported ".
+ "for content model $model used by $name", 'badformat' );
+ }
+
+ $text = $content->serialize( $format );
+
+ // always include format and model.
+ // Format is needed to deserialize, model is needed to interpret.
+ $vals['contentformat'] = $format;
+ $vals['contentmodel'] = $model;
+ }
+
+ if ( $text !== false ) {
+ ApiResult::setContent( $vals, $text );
}
- ApiResult::setContent( $vals, $text );
} elseif ( $this->fld_content ) {
- $vals['texthidden'] = '';
+ if ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
+ $vals['texthidden'] = '';
+ } else {
+ $vals['textmissing'] = '';
+ }
}
if ( !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
global $wgAPIMaxUncachedDiffs;
static $n = 0; // Number of uncached diffs we've had
- if ( $n < $wgAPIMaxUncachedDiffs ) {
+
+ if ( is_null( $content ) ) {
+ $vals['textmissing'] = '';
+ } elseif ( $n < $wgAPIMaxUncachedDiffs ) {
$vals['diff'] = array();
$context = new DerivativeContext( $this->getContext() );
$context->setTitle( $title );
+ $handler = $revision->getContentHandler();
+
if ( !is_null( $this->difftotext ) ) {
- $engine = new DifferenceEngine( $context );
- $engine->setText( $text, $this->difftotext );
+ $model = $title->getContentModel();
+
+ if ( $this->contentFormat
+ && !ContentHandler::getForModelID( $model )->isSupportedFormat( $this->contentFormat ) ) {
+
+ $name = $title->getPrefixedDBkey();
+
+ $this->dieUsage( "The requested format {$this->contentFormat} is not supported for ".
+ "content model $model used by $name", 'badformat' );
+ }
+
+ $difftocontent = ContentHandler::makeContent( $this->difftotext, $title, $model, $this->contentFormat );
+
+ $engine = $handler->createDifferenceEngine( $context );
+ $engine->setContent( $content, $difftocontent );
} else {
- $engine = new DifferenceEngine( $context, $revision->getID(), $this->diffto );
+ $engine = $handler->createDifferenceEngine( $context, $revision->getID(), $this->diffto );
$vals['diff']['from'] = $engine->getOldid();
$vals['diff']['to'] = $engine->getNewid();
}
@@ -567,6 +642,7 @@ class ApiQueryRevisions extends ApiQueryBase {
'userid',
'size',
'sha1',
+ 'contentmodel',
'comment',
'parsedcomment',
'content',
@@ -616,6 +692,10 @@ class ApiQueryRevisions extends ApiQueryBase {
'continue' => null,
'diffto' => null,
'difftotext' => null,
+ 'contentformat' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+ ApiBase::PARAM_DFLT => null
+ ),
);
}
@@ -631,6 +711,7 @@ class ApiQueryRevisions extends ApiQueryBase {
' userid - User id of revision creator',
' size - Length (bytes) of the revision',
' sha1 - SHA-1 (base 16) of the revision',
+ ' contentmodel - Content model id',
' comment - Comment by the user for revision',
' parsedcomment - Parsed comment by the user for the revision',
' content - Text of the revision',
@@ -644,9 +725,10 @@ class ApiQueryRevisions extends ApiQueryBase {
'dir' => $this->getDirectionDescription( $p, ' (enum)' ),
'user' => 'Only include revisions made by user (enum)',
'excludeuser' => 'Exclude revisions made by user (enum)',
- 'expandtemplates' => 'Expand templates in revision content',
- 'generatexml' => 'Generate XML parse tree for revision content',
- 'parse' => 'Parse revision content. For performance reasons if this option is used, rvlimit is enforced to 1.',
+ 'expandtemplates' => "Expand templates in revision content (requires {$p}prop=content)",
+ 'generatexml' => "Generate XML parse tree for revision content (requires {$p}prop=content)",
+ 'parse' => array( "Parse revision content (requires {$p}prop=content).",
+ 'For performance reasons if this option is used, rvlimit is enforced to 1.' ),
'section' => 'Only retrieve the content of this section number',
'token' => 'Which tokens to obtain for each revision',
'continue' => 'When more results are available, use this to continue',
@@ -655,6 +737,7 @@ class ApiQueryRevisions extends ApiQueryBase {
'difftotext' => array( 'Text to diff each revision to. Only diffs a limited number of revisions.',
"Overrides {$p}diffto. If {$p}section is set, only that section will be diffed against this text" ),
'tag' => 'Only list revisions tagged with this tag',
+ 'contentformat' => 'Serialization format used for difftotext and expected for output of content',
);
}
@@ -709,8 +792,12 @@ class ApiQueryRevisions extends ApiQueryBase {
ApiBase::PROP_TYPE => 'string',
ApiBase::PROP_NULLABLE => true
),
- 'texthidden' => 'boolean'
- )
+ 'texthidden' => 'boolean',
+ 'textmissing' => 'boolean',
+ ),
+ 'contentmodel' => array(
+ 'contentmodel' => 'string'
+ ),
);
self::addTokenProperties( $props, $this->getTokenFunctions() );
@@ -732,13 +819,18 @@ class ApiQueryRevisions extends ApiQueryBase {
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'nosuchrevid', 'diffto' ),
- array( 'code' => 'revids', 'info' => 'The revids= parameter may not be used with the list options (limit, startid, endid, dirNewer, start, end).' ),
- array( 'code' => 'multpages', 'info' => 'titles, pageids or a generator was used to supply multiple pages, but the limit, startid, endid, dirNewer, user, excludeuser, start and end parameters may only be used on a single page.' ),
+ array( 'code' => 'revids', 'info' => 'The revids= parameter may not be used with the list options '
+ . '(limit, startid, endid, dirNewer, start, end).' ),
+ array( 'code' => 'multpages', 'info' => 'titles, pageids or a generator was used to supply multiple pages, '
+ . ' but the limit, startid, endid, dirNewer, user, excludeuser, '
+ . 'start and end parameters may only be used on a single page.' ),
array( 'code' => 'diffto', 'info' => 'rvdiffto must be set to a non-negative number, "prev", "next" or "cur"' ),
array( 'code' => 'badparams', 'info' => 'start and startid cannot be used together' ),
array( 'code' => 'badparams', 'info' => 'end and endid cannot be used together' ),
array( 'code' => 'badparams', 'info' => 'user and excludeuser cannot be used together' ),
array( 'code' => 'nosuchsection', 'info' => 'There is no section section in rID' ),
+ array( 'code' => 'badformat', 'info' => 'The requested serialization format can not be applied '
+ . ' to the page\'s content model' ),
) );
}
@@ -762,8 +854,4 @@ class ApiQueryRevisions extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Properties#revisions_.2F_rv';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQuerySearch.php b/includes/api/ApiQuerySearch.php
index 364433d5..86183391 100644
--- a/includes/api/ApiQuerySearch.php
+++ b/includes/api/ApiQuerySearch.php
@@ -168,7 +168,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
}
}
if ( isset( $prop['hasrelated'] ) && $result->hasRelated() ) {
- $vals['hasrelated'] = "";
+ $vals['hasrelated'] = '';
}
// Add item to results and see whether it fits
@@ -205,7 +205,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
ApiBase::PARAM_REQUIRED => true
),
'namespace' => array(
- ApiBase::PARAM_DFLT => 0,
+ ApiBase::PARAM_DFLT => NS_MAIN,
ApiBase::PARAM_TYPE => 'namespace',
ApiBase::PARAM_ISMULTI => true,
),
@@ -359,8 +359,4 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Search';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php
index ec503d64..810e1d6b 100644
--- a/includes/api/ApiQuerySiteinfo.php
+++ b/includes/api/ApiQuerySiteinfo.php
@@ -96,6 +96,9 @@ class ApiQuerySiteinfo extends ApiQueryBase {
case 'variables':
$fit = $this->appendVariables( $p );
break;
+ case 'protocols':
+ $fit = $this->appendProtocols( $p );
+ break;
default:
ApiBase::dieDebug( __METHOD__, "Unknown prop=$p" );
}
@@ -111,7 +114,9 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
protected function appendGeneralInfo( $property ) {
- global $wgContLang;
+ global $wgContLang,
+ $wgDisableLangConversion,
+ $wgDisableTitleConversion;
$data = array();
$mainPage = Title::newMainPage();
@@ -120,10 +125,31 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data['sitename'] = $GLOBALS['wgSitename'];
$data['generator'] = "MediaWiki {$GLOBALS['wgVersion']}";
$data['phpversion'] = phpversion();
- $data['phpsapi'] = php_sapi_name();
+ $data['phpsapi'] = PHP_SAPI;
$data['dbtype'] = $GLOBALS['wgDBtype'];
$data['dbversion'] = $this->getDB()->getServerVersion();
+ if ( !$wgDisableLangConversion ) {
+ $data['langconversion'] = '';
+ }
+
+ if ( !$wgDisableTitleConversion ) {
+ $data['titleconversion'] = '';
+ }
+
+ if ( $wgContLang->linkPrefixExtension() ) {
+ $data['linkprefix'] = wfMessage( 'linkprefix' )->inContentLanguage()->text();
+ } else {
+ $data['linkprefix'] = '';
+ }
+
+ $linktrail = $wgContLang->linkTrail();
+ if ( $linktrail ) {
+ $data['linktrail'] = $linktrail;
+ } else {
+ $data['linktrail'] = '';
+ }
+
$git = SpecialVersion::getGitHeadSha1( $GLOBALS['IP'] );
if ( $git ) {
$data['git-hash'] = $git;
@@ -227,6 +253,11 @@ class ApiQuerySiteinfo extends ApiQueryBase {
if ( MWNamespace::isNonincludable( $ns ) ) {
$data[$ns]['nonincludable'] = '';
}
+
+ $contentmodel = MWNamespace::getNamespaceContentModel( $ns );
+ if ( $contentmodel ) {
+ $data[$ns]['defaultcontentmodel'] = $contentmodel;
+ }
}
$this->getResult()->setIndexedTagName( $data, 'ns' );
@@ -345,7 +376,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
);
}
} else {
- list( $host, $lag, $index ) = $lb->getMaxLag();
+ list( , $lag, $index ) = $lb->getMaxLag();
$data[] = array(
'host' => $wgShowHostnames
? $lb->getServerName( $index )
@@ -457,7 +488,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
if ( isset( $ext['author'] ) ) {
$ret['author'] = is_array( $ext['author'] ) ?
- implode( ', ', $ext['author' ] ) : $ext['author'];
+ implode( ', ', $ext['author'] ) : $ext['author'];
}
if ( isset( $ext['url'] ) ) {
$ret['url'] = $ext['url'];
@@ -525,7 +556,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
public function appendExtensionTags( $property ) {
global $wgParser;
$wgParser->firstCallInit();
- $tags = array_map( array( $this, 'formatParserTags'), $wgParser->getTags() );
+ $tags = array_map( array( $this, 'formatParserTags' ), $wgParser->getTags() );
$this->getResult()->setIndexedTagName( $tags, 't' );
return $this->getResult()->addValue( 'query', $property, $tags );
}
@@ -544,6 +575,14 @@ class ApiQuerySiteinfo extends ApiQueryBase {
return $this->getResult()->addValue( 'query', $property, $variables );
}
+ public function appendProtocols( $property ) {
+ global $wgUrlProtocols;
+ // Make a copy of the global so we don't try to set the _element key of it - bug 45130
+ $protocols = array_values( $wgUrlProtocols );
+ $this->getResult()->setIndexedTagName( $protocols, 'p' );
+ return $this->getResult()->addValue( 'query', $property, $protocols );
+ }
+
private function formatParserTags( $item ) {
return "<{$item}>";
}
@@ -554,7 +593,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
ksort( $myWgHooks );
$data = array();
- foreach ( $myWgHooks as $hook => $hooks ) {
+ foreach ( $myWgHooks as $hook => $hooks ) {
$arr = array(
'name' => $hook,
'subscribers' => array_map( array( 'SpecialVersion', 'arrayToString' ), $hooks ),
@@ -596,6 +635,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
'functionhooks',
'showhooks',
'variables',
+ 'protocols',
)
),
'filteriw' => array(
@@ -633,6 +673,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
' functionhooks - Returns a list of parser function hooks',
' showhooks - Returns a list of all subscribed hooks (contents of $wgHooks)',
' variables - Returns a list of variable IDs',
+ ' protocols - Returns a list of protocols that are allowed in external links.',
),
'filteriw' => 'Return only local or only nonlocal entries of the interwiki map',
'showalldb' => 'List all database servers, not just the one lagging the most',
@@ -662,8 +703,4 @@ class ApiQuerySiteinfo extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Meta#siteinfo_.2F_si';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryStashImageInfo.php b/includes/api/ApiQueryStashImageInfo.php
index a310d109..6899375a 100644
--- a/includes/api/ApiQueryStashImageInfo.php
+++ b/includes/api/ApiQueryStashImageInfo.php
@@ -42,7 +42,7 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
$result = $this->getResult();
if ( !$params['filekey'] && !$params['sessionkey'] ) {
- $this->dieUsage( "One of filekey or sessionkey must be supplied", 'nofilekey');
+ $this->dieUsage( "One of filekey or sessionkey must be supplied", 'nofilekey' );
}
// Alias sessionkey to filekey, but give an existing filekey precedence.
@@ -138,9 +138,4 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
);
}
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
-
}
-
diff --git a/includes/api/ApiQueryTags.php b/includes/api/ApiQueryTags.php
index f97c1b2a..e0637ff7 100644
--- a/includes/api/ApiQueryTags.php
+++ b/includes/api/ApiQueryTags.php
@@ -162,7 +162,7 @@ class ApiQueryTags extends ApiQueryBase {
'prop' => array(
'Which properties to get',
' name - Adds name of tag',
- ' displayname - Adds system messsage for the tag',
+ ' displayname - Adds system message for the tag',
' description - Adds description of the tag',
' hitcount - Adds the amount of revisions that have this tag',
),
@@ -195,8 +195,4 @@ class ApiQueryTags extends ApiQueryBase {
'api.php?action=query&list=tags&tgprop=displayname|description|hitcount'
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryUserContributions.php b/includes/api/ApiQueryUserContributions.php
index f30b1325..597c412d 100644
--- a/includes/api/ApiQueryUserContributions.php
+++ b/includes/api/ApiQueryUserContributions.php
@@ -160,10 +160,7 @@ class ApiQueryContributions extends ApiQueryBase {
// Handle continue parameter
if ( $this->multiUserMode && !is_null( $this->params['continue'] ) ) {
$continue = explode( '|', $this->params['continue'] );
- if ( count( $continue ) != 2 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the original ' .
- 'value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $continue ) != 2 );
$db = $this->getDB();
$encUser = $db->addQuotes( $continue[0] );
$encTS = $db->addQuotes( $db->timestamp( $continue[1] ) );
@@ -223,7 +220,7 @@ class ApiQueryContributions extends ApiQueryBase {
) );
if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ||
- $this->fld_patrolled ) {
+ $this->fld_patrolled ) {
if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
$this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' );
}
@@ -445,7 +442,7 @@ class ApiQueryContributions extends ApiQueryBase {
'end' => 'The end timestamp to return to',
'continue' => 'When more results are available, use this to continue',
'user' => 'The users to retrieve contributions for',
- 'userprefix' => "Retrieve contibutions for all users whose names begin with this value. Overrides {$p}user",
+ 'userprefix' => "Retrieve contributions for all users whose names begin with this value. Overrides {$p}user",
'dir' => $this->getDirectionDescription( $p ),
'namespace' => 'Only list contributions in these namespaces',
'prop' => array(
@@ -546,8 +543,4 @@ class ApiQueryContributions extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Usercontribs';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryUserInfo.php b/includes/api/ApiQueryUserInfo.php
index 66906659..1a491eca 100644
--- a/includes/api/ApiQueryUserInfo.php
+++ b/includes/api/ApiQueryUserInfo.php
@@ -77,18 +77,18 @@ class ApiQueryUserInfo extends ApiQueryBase {
if ( isset( $this->prop['groups'] ) ) {
$vals['groups'] = $user->getEffectiveGroups();
- $result->setIndexedTagName( $vals['groups'], 'g' ); // even if empty
+ $result->setIndexedTagName( $vals['groups'], 'g' ); // even if empty
}
if ( isset( $this->prop['implicitgroups'] ) ) {
$vals['implicitgroups'] = $user->getAutomaticGroups();
- $result->setIndexedTagName( $vals['implicitgroups'], 'g' ); // even if empty
+ $result->setIndexedTagName( $vals['implicitgroups'], 'g' ); // even if empty
}
if ( isset( $this->prop['rights'] ) ) {
// User::getRights() may return duplicate values, strip them
$vals['rights'] = array_values( array_unique( $user->getRights() ) );
- $result->setIndexedTagName( $vals['rights'], 'r' ); // even if empty
+ $result->setIndexedTagName( $vals['rights'], 'r' ); // even if empty
}
if ( isset( $this->prop['changeablegroups'] ) ) {
@@ -303,8 +303,4 @@ class ApiQueryUserInfo extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Meta#userinfo_.2F_ui';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryUsers.php b/includes/api/ApiQueryUsers.php
index bf438d1d..72ab7866 100644
--- a/includes/api/ApiQueryUsers.php
+++ b/includes/api/ApiQueryUsers.php
@@ -110,19 +110,39 @@ class ApiQueryUsers extends ApiQueryBase {
$this->addFields( User::selectFields() );
$this->addWhereFld( 'user_name', $goodNames );
- if ( isset( $this->prop['groups'] ) || isset( $this->prop['rights'] ) ) {
- $this->addTables( 'user_groups' );
- $this->addJoinConds( array( 'user_groups' => array( 'LEFT JOIN', 'ug_user=user_id' ) ) );
- $this->addFields( 'ug_group' );
- }
-
$this->showHiddenUsersAddBlockInfo( isset( $this->prop['blockinfo'] ) );
$data = array();
$res = $this->select( __METHOD__ );
+ $this->resetQueryParams();
+
+ // get user groups if needed
+ if ( isset( $this->prop['groups'] ) || isset( $this->prop['rights'] ) ) {
+ $userGroups = array();
+
+ $this->addTables( 'user' );
+ $this->addWhereFld( 'user_name', $goodNames );
+ $this->addTables( 'user_groups' );
+ $this->addJoinConds( array( 'user_groups' => array( 'INNER JOIN', 'ug_user=user_id' ) ) );
+ $this->addFields( array( 'user_name', 'ug_group' ) );
+ $userGroupsRes = $this->select( __METHOD__ );
+
+ foreach( $userGroupsRes as $row ) {
+ $userGroups[$row->user_name][] = $row->ug_group;
+ }
+ }
foreach ( $res as $row ) {
- $user = User::newFromRow( $row );
+ // create user object and pass along $userGroups if set
+ // that reduces the number of database queries needed in User dramatically
+ if ( !isset( $userGroups ) ) {
+ $user = User::newFromRow( $row );
+ } else {
+ if ( !isset( $userGroups[$row->user_name] ) || !is_array( $userGroups[$row->user_name] ) ) {
+ $userGroups[$row->user_name] = array();
+ }
+ $user = User::newFromRow( $row, array( 'user_groups' => $userGroups[$row->user_name] ) );
+ }
$name = $user->getName();
$data[$name]['userid'] = $user->getId();
@@ -137,29 +157,15 @@ class ApiQueryUsers extends ApiQueryBase {
}
if ( isset( $this->prop['groups'] ) ) {
- if ( !isset( $data[$name]['groups'] ) ) {
- $data[$name]['groups'] = $user->getAutomaticGroups();
- }
-
- if ( !is_null( $row->ug_group ) ) {
- // This row contains only one group, others will be added from other rows
- $data[$name]['groups'][] = $row->ug_group;
- }
+ $data[$name]['groups'] = $user->getEffectiveGroups();
}
- if ( isset( $this->prop['implicitgroups'] ) && !isset( $data[$name]['implicitgroups'] ) ) {
- $data[$name]['implicitgroups'] = $user->getAutomaticGroups();
+ if ( isset( $this->prop['implicitgroups'] ) ) {
+ $data[$name]['implicitgroups'] = $user->getAutomaticGroups();
}
if ( isset( $this->prop['rights'] ) ) {
- if ( !isset( $data[$name]['rights'] ) ) {
- $data[$name]['rights'] = User::getGroupPermissions( $user->getAutomaticGroups() );
- }
-
- if ( !is_null( $row->ug_group ) ) {
- $data[$name]['rights'] = array_unique( array_merge( $data[$name]['rights'],
- User::getGroupPermissions( array( $row->ug_group ) ) ) );
- }
+ $data[$name]['rights'] = $user->getRights();
}
if ( $row->ipb_deleted ) {
$data[$name]['hidden'] = '';
@@ -244,16 +250,16 @@ class ApiQueryUsers extends ApiQueryBase {
}
$done[] = $u;
}
- return $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'user' );
+ $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'user' );
}
/**
- * Gets all the groups that a user is automatically a member of (implicit groups)
- *
- * @deprecated since 1.20; call User::getAutomaticGroups() directly.
- * @param $user User
- * @return array
- */
+ * Gets all the groups that a user is automatically a member of (implicit groups)
+ *
+ * @deprecated since 1.20; call User::getAutomaticGroups() directly.
+ * @param $user User
+ * @return array
+ */
public static function getAutoGroups( $user ) {
wfDeprecated( __METHOD__, '1.20' );
@@ -304,7 +310,7 @@ class ApiQueryUsers extends ApiQueryBase {
' rights - Lists all the rights the user(s) has',
' editcount - Adds the user\'s edit count',
' registration - Adds the user\'s registration timestamp',
- ' emailable - Tags if the user can and wants to receive e-mail through [[Special:Emailuser]]',
+ ' emailable - Tags if the user can and wants to receive email through [[Special:Emailuser]]',
' gender - Tags the gender of the user. Returns "male", "female", or "unknown"',
),
'users' => 'A list of users to obtain the same information for',
@@ -390,8 +396,4 @@ class ApiQueryUsers extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Users';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryWatchlist.php b/includes/api/ApiQueryWatchlist.php
index a1a33728..90b12c14 100644
--- a/includes/api/ApiQueryWatchlist.php
+++ b/includes/api/ApiQueryWatchlist.php
@@ -116,7 +116,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
) );
$userId = $user->getId();
- $this->addJoinConds( array( 'watchlist' => array('INNER JOIN',
+ $this->addJoinConds( array( 'watchlist' => array( 'INNER JOIN',
array(
'wl_user' => $userId,
'wl_namespace=rc_namespace',
@@ -240,14 +240,16 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
if ( $this->fld_user || $this->fld_userid ) {
- if ( $this->fld_user ) {
- $vals['user'] = $row->rc_user_text;
- }
-
if ( $this->fld_userid ) {
+ $vals['userid'] = $row->rc_user;
+ // for backwards compatibility
$vals['user'] = $row->rc_user;
}
+ if ( $this->fld_user ) {
+ $vals['user'] = $row->rc_user_text;
+ }
+
if ( !$row->rc_user ) {
$vals['anon'] = '';
}
@@ -511,15 +513,11 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
'api.php?action=query&list=watchlist&wlallrev=&wlprop=ids|title|timestamp|user|comment',
'api.php?action=query&generator=watchlist&prop=info',
'api.php?action=query&generator=watchlist&gwlallrev=&prop=revisions&rvprop=timestamp|user',
- 'api.php?action=query&list=watchlist&wlowner=Bob_Smith&wltoken=d8d562e9725ea1512894cdab28e5ceebc7f20237'
+ 'api.php?action=query&list=watchlist&wlowner=Bob_Smith&wltoken=123ABC'
);
}
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Watchlist';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryWatchlistRaw.php b/includes/api/ApiQueryWatchlistRaw.php
index 6b24aef3..2cb4d9eb 100644
--- a/includes/api/ApiQueryWatchlistRaw.php
+++ b/includes/api/ApiQueryWatchlistRaw.php
@@ -71,11 +71,9 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
if ( isset( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 ) {
- $this->dieUsage( "Invalid continue param. You should pass the " .
- "original value returned by the previous query", "_badcontinue" );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
$ns = intval( $cont[0] );
+ $this->dieContinueUsageIf( strval( $ns ) !== $cont[0] );
$title = $this->getDB()->addQuotes( $cont[1] );
$op = $params['dir'] == 'ascending' ? '>' : '<';
$this->addWhere(
@@ -224,8 +222,4 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
'api.php?action=query&generator=watchlistraw&gwrshow=changed&prop=revisions',
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php
index 91e20812..39c114b8 100644
--- a/includes/api/ApiResult.php
+++ b/includes/api/ApiResult.php
@@ -36,13 +36,26 @@
* There are two special key values that change how XML output is generated:
* '_element' This key sets the tag name for the rest of the elements in the current array.
* It is only inserted if the formatter returned true for getNeedsRawData()
- * '*' This key has special meaning only to the XML formatter, and is outputed as is
- * for all others. In XML it becomes the content of the current element.
+ * '*' This key has special meaning only to the XML formatter, and is outputted as is
+ * for all others. In XML it becomes the content of the current element.
*
* @ingroup API
*/
class ApiResult extends ApiBase {
+ /**
+ * override existing value in addValue() and setElement()
+ * @since 1.21
+ */
+ const OVERRIDE = 1;
+
+ /**
+ * For addValue() and setElement(), if the value does not exist, add it as the first element.
+ * In case the new value has no name (numerical index), all indexes will be renumbered.
+ * @since 1.21
+ */
+ const ADD_ON_TOP = 2;
+
private $mData, $mIsRawMode, $mSize, $mCheckingSize;
/**
@@ -134,18 +147,27 @@ class ApiResult extends ApiBase {
/**
* Add an output value to the array by name.
* Verifies that value with the same name has not been added before.
- * @param $arr array to add $value to
- * @param $name string Index of $arr to add $value at
+ * @param array $arr to add $value to
+ * @param string $name Index of $arr to add $value at
* @param $value mixed
- * @param $overwrite bool Whether overwriting an existing element is allowed
+ * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP. This parameter used to be
+ * boolean, and the value of OVERRIDE=1 was specifically chosen so that it would be backwards
+ * compatible with the new method signature.
+ *
+ * @since 1.21 int $flags replaced boolean $override
*/
- public static function setElement( &$arr, $name, $value, $overwrite = false ) {
+ public static function setElement( &$arr, $name, $value, $flags = 0 ) {
if ( $arr === null || $name === null || $value === null || !is_array( $arr ) || is_array( $name ) ) {
ApiBase::dieDebug( __METHOD__, 'Bad parameter' );
}
- if ( !isset ( $arr[$name] ) || $overwrite ) {
- $arr[$name] = $value;
+ $exists = isset( $arr[$name] );
+ if ( !$exists || ( $flags & ApiResult::OVERRIDE ) ) {
+ if ( !$exists && ( $flags & ApiResult::ADD_ON_TOP ) ) {
+ $arr = array( $name => $value ) + $arr;
+ } else {
+ $arr[$name] = $value;
+ }
} elseif ( is_array( $arr[$name] ) && is_array( $value ) ) {
$merged = array_intersect_key( $arr[$name], $value );
if ( !count( $merged ) ) {
@@ -161,9 +183,9 @@ class ApiResult extends ApiBase {
/**
* Adds a content element to an array.
* Use this function instead of hardcoding the '*' element.
- * @param $arr array to add the content element to
+ * @param array $arr to add the content element to
* @param $value Mixed
- * @param $subElemName string when present, content element is created
+ * @param string $subElemName when present, content element is created
* as a sub item of $arr. Use this parameter to create elements in
* format "<elem>text</elem>" without attributes.
*/
@@ -186,7 +208,7 @@ class ApiResult extends ApiBase {
* give all indexed values the given tag name. This function MUST be
* called on every array that has numerical indexes.
* @param $arr array
- * @param $tag string Tag name
+ * @param string $tag Tag name
*/
public function setIndexedTagName( &$arr, $tag ) {
// In raw mode, add the '_element', otherwise just ignore
@@ -203,7 +225,7 @@ class ApiResult extends ApiBase {
/**
* Calls setIndexedTagName() on each sub-array of $arr
* @param $arr array
- * @param $tag string Tag name
+ * @param string $tag Tag name
*/
public function setIndexedTagName_recursive( &$arr, $tag ) {
if ( !is_array( $arr ) ) {
@@ -222,7 +244,7 @@ class ApiResult extends ApiBase {
* Calls setIndexedTagName() on an array already in the result.
* Don't specify a path to a value that's not in the result, or
* you'll get nasty errors.
- * @param $path array Path to the array, like addValue()'s $path
+ * @param array $path Path to the array, like addValue()'s $path
* @param $tag string
*/
public function setIndexedTagName_internal( $path, $tag ) {
@@ -249,11 +271,14 @@ class ApiResult extends ApiBase {
* @param $path array|string|null
* @param $name string
* @param $value mixed
- * @param $overwrite bool
- *
+ * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP. This parameter used to be
+ * boolean, and the value of OVERRIDE=1 was specifically chosen so that it would be backwards
+ * compatible with the new method signature.
* @return bool True if $value fits in the result, false if not
+ *
+ * @since 1.21 int $flags replaced boolean $override
*/
- public function addValue( $path, $name, $value, $overwrite = false ) {
+ public function addValue( $path, $name, $value, $flags = 0 ) {
global $wgAPIMaxResultSize;
$data = &$this->mData;
@@ -268,26 +293,34 @@ class ApiResult extends ApiBase {
$this->mSize = $newsize;
}
- if ( !is_null( $path ) ) {
- if ( is_array( $path ) ) {
- foreach ( $path as $p ) {
- if ( !isset( $data[$p] ) ) {
+ $addOnTop = $flags & ApiResult::ADD_ON_TOP;
+ if ( $path !== null ) {
+ foreach ( (array)$path as $p ) {
+ if ( !isset( $data[$p] ) ) {
+ if ( $addOnTop ) {
+ $data = array( $p => array() ) + $data;
+ $addOnTop = false;
+ } else {
$data[$p] = array();
}
- $data = &$data[$p];
- }
- } else {
- if ( !isset( $data[$path] ) ) {
- $data[$path] = array();
}
- $data = &$data[$path];
+ $data = &$data[$p];
}
}
if ( !$name ) {
- $data[] = $value; // Add list element
+ // Add list element
+ if ( $addOnTop ) {
+ // This element needs to be inserted in the beginning
+ // Numerical indexes will be renumbered
+ array_unshift( $data, $value );
+ } else {
+ // Add new value at the end
+ $data[] = $value;
+ }
} else {
- self::setElement( $data, $name, $value, $overwrite ); // Add named element
+ // Add named element
+ self::setElement( $data, $name, $value, $flags );
}
return true;
}
@@ -300,19 +333,19 @@ class ApiResult extends ApiBase {
*/
public function setParsedLimit( $moduleName, $limit ) {
// Add value, allowing overwriting
- $this->addValue( 'limits', $moduleName, $limit, true );
+ $this->addValue( 'limits', $moduleName, $limit, ApiResult::OVERRIDE );
}
/**
* Unset a value previously added to the result set.
* Fails silently if the value isn't found.
* For parameters, see addValue()
- * @param $path array
+ * @param $path array|null
* @param $name string
*/
public function unsetValue( $path, $name ) {
$data = &$this->mData;
- if ( !is_null( $path ) ) {
+ if ( $path !== null ) {
foreach ( (array)$path as $p ) {
if ( !isset( $data[$p] ) ) {
return;
@@ -367,8 +400,4 @@ class ApiResult extends ApiBase {
public function execute() {
ApiBase::dieDebug( __METHOD__, 'execute() is not supported on Result object' );
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiRollback.php b/includes/api/ApiRollback.php
index 677df16a..b9873f49 100644
--- a/includes/api/ApiRollback.php
+++ b/includes/api/ApiRollback.php
@@ -29,10 +29,6 @@
*/
class ApiRollback extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
/**
* @var Title
*/
@@ -185,7 +181,7 @@ class ApiRollback extends ApiBase {
$this->mTitleObj = Title::newFromText( $params['title'] );
- if ( !$this->mTitleObj ) {
+ if ( !$this->mTitleObj || $this->mTitleObj->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
if ( !$this->mTitleObj->exists() ) {
@@ -205,8 +201,4 @@ class ApiRollback extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Rollback';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiRsd.php b/includes/api/ApiRsd.php
index f0e1fad6..c4a1328c 100644
--- a/includes/api/ApiRsd.php
+++ b/includes/api/ApiRsd.php
@@ -31,10 +31,6 @@
*/
class ApiRsd extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$result = $this->getResult();
@@ -155,10 +151,6 @@ class ApiRsd extends ApiBase {
}
return $outputData;
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
class ApiFormatXmlRsd extends ApiFormatXml {
@@ -170,8 +162,4 @@ class ApiFormatXmlRsd extends ApiFormatXml {
public function getMimeType() {
return 'application/rsd+xml';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiSetNotificationTimestamp.php b/includes/api/ApiSetNotificationTimestamp.php
index 098b1a66..58d5d9ab 100644
--- a/includes/api/ApiSetNotificationTimestamp.php
+++ b/includes/api/ApiSetNotificationTimestamp.php
@@ -31,9 +31,7 @@
*/
class ApiSetNotificationTimestamp extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
+ private $mPageSet;
public function execute() {
$user = $this->getUser();
@@ -45,11 +43,12 @@ class ApiSetNotificationTimestamp extends ApiBase {
$params = $this->extractRequestParams();
$this->requireMaxOneParameter( $params, 'timestamp', 'torevid', 'newerthanrevid' );
- $pageSet = new ApiPageSet( $this );
- $args = array_merge( array( $params, 'entirewatchlist' ), array_keys( $pageSet->getAllowedParams() ) );
- call_user_func_array( array( $this, 'requireOnlyOneParameter' ), $args );
+ $pageSet = $this->getPageSet();
+ if ( $params['entirewatchlist'] && $pageSet->getDataSource() !== null ) {
+ $this->dieUsage( "Cannot use 'entirewatchlist' at the same time as '{$pageSet->getDataSource()}'", 'multisource' );
+ }
- $dbw = $this->getDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER, 'api' );
$timestamp = null;
if ( isset( $params['timestamp'] ) ) {
@@ -96,20 +95,20 @@ class ApiSetNotificationTimestamp extends ApiBase {
$result['notificationtimestamp'] = ( is_null( $timestamp ) ? '' : wfTimestamp( TS_ISO_8601, $timestamp ) );
} else {
// First, log the invalid titles
- foreach( $pageSet->getInvalidTitles() as $title ) {
+ foreach ( $pageSet->getInvalidTitles() as $title ) {
$r = array();
$r['title'] = $title;
$r['invalid'] = '';
$result[] = $r;
}
- foreach( $pageSet->getMissingPageIDs() as $p ) {
+ foreach ( $pageSet->getMissingPageIDs() as $p ) {
$page = array();
$page['pageid'] = $p;
$page['missing'] = '';
$page['notwatched'] = '';
$result[] = $page;
}
- foreach( $pageSet->getMissingRevisionIDs() as $r ) {
+ foreach ( $pageSet->getMissingRevisionIDs() as $r ) {
$rev = array();
$rev['revid'] = $r;
$rev['missing'] = '';
@@ -135,6 +134,7 @@ class ApiSetNotificationTimestamp extends ApiBase {
}
// Now, put the valid titles into the result
+ /** @var $title Title */
foreach ( $pageSet->getTitles() as $title ) {
$ns = $title->getNamespace();
$dbkey = $title->getDBkey();
@@ -161,6 +161,17 @@ class ApiSetNotificationTimestamp extends ApiBase {
$apiResult->addValue( null, $this->getModuleName(), $result );
}
+ /**
+ * Get a cached instance of an ApiPageSet object
+ * @return ApiPageSet
+ */
+ private function getPageSet() {
+ if ( !isset( $this->mPageSet ) ) {
+ $this->mPageSet = new ApiPageSet( $this );
+ }
+ return $this->mPageSet;
+ }
+
public function mustBePosted() {
return true;
}
@@ -177,9 +188,8 @@ class ApiSetNotificationTimestamp extends ApiBase {
return '';
}
- public function getAllowedParams() {
- $psModule = new ApiPageSet( $this );
- return $psModule->getAllowedParams() + array(
+ public function getAllowedParams( $flags = 0 ) {
+ $result = array(
'entirewatchlist' => array(
ApiBase::PARAM_TYPE => 'boolean'
),
@@ -194,11 +204,15 @@ class ApiSetNotificationTimestamp extends ApiBase {
ApiBase::PARAM_TYPE => 'integer'
),
);
+ if ( $flags ) {
+ $result += $this->getPageSet()->getFinalParams( $flags );
+ }
+ return $result;
+
}
public function getParamDescription() {
- $psModule = new ApiPageSet( $this );
- return $psModule->getParamDescription() + array(
+ return $this->getPageSet()->getParamDescription() + array(
'entirewatchlist' => 'Work on all watched pages',
'timestamp' => 'Timestamp to which to set the notification timestamp',
'torevid' => 'Revision to set the notification timestamp to (one page only)',
@@ -247,18 +261,20 @@ class ApiSetNotificationTimestamp extends ApiBase {
public function getDescription() {
return array( 'Update the notification timestamp for watched pages.',
'This affects the highlighting of changed pages in the watchlist and history,',
- 'and the sending of email when the "E-mail me when a page on my watchlist is',
+ 'and the sending of email when the "Email me when a page on my watchlist is',
'changed" preference is enabled.'
);
}
public function getPossibleErrors() {
- $psModule = new ApiPageSet( $this );
+ $ps = $this->getPageSet();
return array_merge(
parent::getPossibleErrors(),
- $psModule->getPossibleErrors(),
- $this->getRequireMaxOneParameterErrorMessages( array( 'timestamp', 'torevid', 'newerthanrevid' ) ),
- $this->getRequireOnlyOneParameterErrorMessages( array_merge( array( 'entirewatchlist' ), array_keys( $psModule->getAllowedParams() ) ) ),
+ $ps->getPossibleErrors(),
+ $this->getRequireMaxOneParameterErrorMessages(
+ array( 'timestamp', 'torevid', 'newerthanrevid' ) ),
+ $this->getRequireOnlyOneParameterErrorMessages(
+ array_merge( array( 'entirewatchlist' ), array_keys( $ps->getFinalParams() ) ) ),
array(
array( 'code' => 'notloggedin', 'info' => 'Anonymous users cannot use watchlist change notifications' ),
array( 'code' => 'multpages', 'info' => 'torevid may only be used with a single page' ),
@@ -269,17 +285,13 @@ class ApiSetNotificationTimestamp extends ApiBase {
public function getExamples() {
return array(
- 'api.php?action=setnotificationtimestamp&entirewatchlist=&token=ABC123' => 'Reset the notification status for the entire watchlist',
- 'api.php?action=setnotificationtimestamp&titles=Main_page&token=ABC123' => 'Reset the notification status for "Main page"',
- 'api.php?action=setnotificationtimestamp&titles=Main_page&timestamp=2012-01-01T00:00:00Z&token=ABC123' => 'Set the notification timestamp for "Main page" so all edits since 1 January 2012 are unviewed',
+ 'api.php?action=setnotificationtimestamp&entirewatchlist=&token=123ABC' => 'Reset the notification status for the entire watchlist',
+ 'api.php?action=setnotificationtimestamp&titles=Main_page&token=123ABC' => 'Reset the notification status for "Main page"',
+ 'api.php?action=setnotificationtimestamp&titles=Main_page&timestamp=2012-01-01T00:00:00Z&token=123ABC' => 'Set the notification timestamp for "Main page" so all edits since 1 January 2012 are unviewed',
);
}
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:SetNotificationTimestamp';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiTokens.php b/includes/api/ApiTokens.php
index 2c9b482c..7080f547 100644
--- a/includes/api/ApiTokens.php
+++ b/includes/api/ApiTokens.php
@@ -24,25 +24,17 @@
* @file
*/
-
/**
* @ingroup API
*/
class ApiTokens extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
- wfProfileIn( __METHOD__ );
$params = $this->extractRequestParams();
$res = array();
$types = $this->getTokenTypes();
foreach ( $params['type'] as $type ) {
- $type = strtolower( $type );
-
$val = call_user_func( $types[$type], null, null );
if ( $val === false ) {
@@ -53,7 +45,6 @@ class ApiTokens extends ApiBase {
}
$this->getResult()->addValue( null, $this->getModuleName(), $res );
- wfProfileOut( __METHOD__ );
}
private function getTokenTypes() {
@@ -62,11 +53,11 @@ class ApiTokens extends ApiBase {
return $types;
}
wfProfileIn( __METHOD__ );
- $types = array( 'patrol' => 'ApiQueryRecentChanges::getPatrolToken' );
+ $types = array( 'patrol' => array( 'ApiQueryRecentChanges', 'getPatrolToken' ) );
$names = array( 'edit', 'delete', 'protect', 'move', 'block', 'unblock',
'email', 'import', 'watch', 'options' );
foreach ( $names as $name ) {
- $types[$name] = 'ApiQueryInfo::get' . ucfirst( $name ) . 'Token';
+ $types[$name] = array( 'ApiQueryInfo', 'get' . ucfirst( $name ) . 'Token' );
}
wfRunHooks( 'ApiTokensGetTokenTypes', array( &$types ) );
ksort( $types );
@@ -85,54 +76,13 @@ class ApiTokens extends ApiBase {
}
public function getResultProperties() {
- return array(
- '' => array(
- 'patroltoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'edittoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'deletetoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'protecttoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'movetoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'blocktoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'unblocktoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'emailtoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'importtoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'watchtoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'optionstoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- )
+ $props = array(
+ '' => array(),
);
+
+ self::addTokenProperties( $props, $this->getTokenTypes() );
+
+ return $props;
}
public function getParamDescription() {
@@ -151,8 +101,4 @@ class ApiTokens extends ApiBase {
'api.php?action=tokens&type=email|move' => 'Retrieve an email token and a move token'
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiUnblock.php b/includes/api/ApiUnblock.php
index ff9ac474..55e7331d 100644
--- a/includes/api/ApiUnblock.php
+++ b/includes/api/ApiUnblock.php
@@ -32,10 +32,6 @@
*/
class ApiUnblock extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
/**
* Unblocks the specified user or provides the reason the unblock failed.
*/
@@ -178,8 +174,4 @@ class ApiUnblock extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Block';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiUndelete.php b/includes/api/ApiUndelete.php
index c9962517..4bbe568d 100644
--- a/includes/api/ApiUndelete.php
+++ b/includes/api/ApiUndelete.php
@@ -29,10 +29,6 @@
*/
class ApiUndelete extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$params = $this->extractRequestParams();
@@ -45,7 +41,7 @@ class ApiUndelete extends ApiBase {
}
$titleObj = Title::newFromText( $params['title'] );
- if ( !$titleObj ) {
+ if ( !$titleObj || $titleObj->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
@@ -61,7 +57,13 @@ class ApiUndelete extends ApiBase {
}
$pa = new PageArchive( $titleObj );
- $retval = $pa->undelete( ( isset( $params['timestamps'] ) ? $params['timestamps'] : array() ), $params['reason'] );
+ $retval = $pa->undelete(
+ ( isset( $params['timestamps'] ) ? $params['timestamps'] : array() ),
+ $params['reason'],
+ array(),
+ false,
+ $this->getUser()
+ );
if ( !is_array( $retval ) ) {
$this->dieUsageMsg( 'cannotundelete' );
}
@@ -170,8 +172,4 @@ class ApiUndelete extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Undelete';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiUpload.php b/includes/api/ApiUpload.php
index e7a7849b..7d67aa6e 100644
--- a/includes/api/ApiUpload.php
+++ b/includes/api/ApiUpload.php
@@ -36,11 +36,9 @@ class ApiUpload extends ApiBase {
protected $mParams;
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
+ global $wgEnableAsyncUploads;
+
// Check whether upload is enabled
if ( !UploadBase::isEnabled() ) {
$this->dieUsageMsg( 'uploaddisabled' );
@@ -51,6 +49,8 @@ class ApiUpload extends ApiBase {
// Parameter handling
$this->mParams = $this->extractRequestParams();
$request = $this->getMain()->getRequest();
+ // Check if async mode is actually supported (jobs done in cli mode)
+ $this->mParams['async'] = ( $this->mParams['async'] && $wgEnableAsyncUploads );
// Add the uploaded file to the params array
$this->mParams['file'] = $request->getFileName( 'file' );
$this->mParams['chunk'] = $request->getFileName( 'chunk' );
@@ -62,17 +62,16 @@ class ApiUpload extends ApiBase {
// Select an upload module
if ( !$this->selectUploadModule() ) {
- // This is not a true upload, but a status request or similar
- return;
- }
- if ( !isset( $this->mUpload ) ) {
+ return; // not a true upload, but a status request or similar
+ } elseif ( !isset( $this->mUpload ) ) {
$this->dieUsage( 'No upload module set', 'nomodule' );
}
// First check permission to upload
$this->checkPermissions( $user );
- // Fetch the file
+ // Fetch the file (usually a no-op)
+ /** @var $status Status */
$status = $this->mUpload->fetchFile();
if ( !$status->isGood() ) {
$errors = $status->getErrorsArray();
@@ -82,26 +81,32 @@ class ApiUpload extends ApiBase {
// Check if the uploaded file is sane
if ( $this->mParams['chunk'] ) {
- $maxSize = $this->mUpload->getMaxUploadSize( );
+ $maxSize = $this->mUpload->getMaxUploadSize();
if( $this->mParams['filesize'] > $maxSize ) {
$this->dieUsage( 'The file you submitted was too large', 'file-too-large' );
}
+ if ( !$this->mUpload->getTitle() ) {
+ $this->dieUsage( 'Invalid file title supplied', 'internal-error' );
+ }
+ } elseif ( $this->mParams['async'] && $this->mParams['filekey'] ) {
+ // defer verification to background process
} else {
+ wfDebug( __METHOD__ . 'about to verify' );
$this->verifyUpload();
}
-
+
// Check if the user has the rights to modify or overwrite the requested title
// (This check is irrelevant if stashing is already requested, since the errors
// can always be fixed by changing the title)
- if ( ! $this->mParams['stash'] ) {
+ if ( !$this->mParams['stash'] ) {
$permErrors = $this->mUpload->verifyTitlePermissions( $user );
if ( $permErrors !== true ) {
$this->dieRecoverableError( $permErrors[0], 'filename' );
}
}
- // Get the result based on the current upload context:
- $result = $this->getContextResult();
+ // Get the result based on the current upload context:
+ $result = $this->getContextResult();
if ( $result['result'] === 'Success' ) {
$result['imageinfo'] = $this->mUpload->getImageInfo( $this->getResult() );
}
@@ -111,14 +116,15 @@ class ApiUpload extends ApiBase {
// Cleanup any temporary mess
$this->mUpload->cleanupTempFile();
}
+
/**
- * Get an uplaod result based on upload context
+ * Get an upload result based on upload context
* @return array
*/
- private function getContextResult(){
+ private function getContextResult() {
$warnings = $this->getApiWarnings();
if ( $warnings && !$this->mParams['ignorewarnings'] ) {
- // Get warnings formated in result array format
+ // Get warnings formatted in result array format
return $this->getWarningsResult( $warnings );
} elseif ( $this->mParams['chunk'] ) {
// Add chunk, and get result
@@ -131,12 +137,13 @@ class ApiUpload extends ApiBase {
// performUpload will return a formatted properly for the API with status
return $this->performUpload( $warnings );
}
+
/**
- * Get Stash Result, throws an expetion if the file could not be stashed.
- * @param $warnings array Array of Api upload warnings
+ * Get Stash Result, throws an exception if the file could not be stashed.
+ * @param array $warnings Array of Api upload warnings
* @return array
*/
- private function getStashResult( $warnings ){
+ private function getStashResult( $warnings ) {
$result = array ();
// Some uploads can request they be stashed, so as not to publish them immediately.
// In this case, a failure to stash ought to be fatal
@@ -152,12 +159,13 @@ class ApiUpload extends ApiBase {
}
return $result;
}
+
/**
* Get Warnings Result
- * @param $warnings array Array of Api upload warnings
+ * @param array $warnings Array of Api upload warnings
* @return array
*/
- private function getWarningsResult( $warnings ){
+ private function getWarningsResult( $warnings ) {
$result = array();
$result['result'] = 'Warning';
$result['warnings'] = $warnings;
@@ -171,12 +179,13 @@ class ApiUpload extends ApiBase {
}
return $result;
}
+
/**
* Get the result of a chunk upload.
- * @param $warnings array Array of Api upload warnings
+ * @param array $warnings Array of Api upload warnings
* @return array
*/
- private function getChunkResult( $warnings ){
+ private function getChunkResult( $warnings ) {
$result = array();
$result['result'] = 'Continue';
@@ -186,55 +195,78 @@ class ApiUpload extends ApiBase {
$request = $this->getMain()->getRequest();
$chunkPath = $request->getFileTempname( 'chunk' );
$chunkSize = $request->getUpload( 'chunk' )->getSize();
- if ($this->mParams['offset'] == 0) {
+ if ( $this->mParams['offset'] == 0 ) {
try {
- $result['filekey'] = $this->performStash();
+ $filekey = $this->performStash();
} catch ( MWException $e ) {
// FIXME: Error handling here is wrong/different from rest of this
$this->dieUsage( $e->getMessage(), 'stashfailed' );
}
} else {
- $status = $this->mUpload->addChunk($chunkPath, $chunkSize,
- $this->mParams['offset']);
+ $filekey = $this->mParams['filekey'];
+ /** @var $status Status */
+ $status = $this->mUpload->addChunk(
+ $chunkPath, $chunkSize, $this->mParams['offset'] );
if ( !$status->isGood() ) {
$this->dieUsage( $status->getWikiText(), 'stashfailed' );
return array();
}
+ }
- // Check we added the last chunk:
- if( $this->mParams['offset'] + $chunkSize == $this->mParams['filesize'] ) {
+ // Check we added the last chunk:
+ if ( $this->mParams['offset'] + $chunkSize == $this->mParams['filesize'] ) {
+ if ( $this->mParams['async'] ) {
+ $progress = UploadBase::getSessionStatus( $this->mParams['filekey'] );
+ if ( $progress && $progress['result'] === 'Poll' ) {
+ $this->dieUsage( "Chunk assembly already in progress.", 'stashfailed' );
+ }
+ UploadBase::setSessionStatus(
+ $this->mParams['filekey'],
+ array( 'result' => 'Poll',
+ 'stage' => 'queued', 'status' => Status::newGood() )
+ );
+ $ok = JobQueueGroup::singleton()->push( new AssembleUploadChunksJob(
+ Title::makeTitle( NS_FILE, $this->mParams['filekey'] ),
+ array(
+ 'filename' => $this->mParams['filename'],
+ 'filekey' => $this->mParams['filekey'],
+ 'session' => $this->getContext()->exportSession()
+ )
+ ) );
+ if ( $ok ) {
+ $result['result'] = 'Poll';
+ } else {
+ UploadBase::setSessionStatus( $this->mParams['filekey'], false );
+ $this->dieUsage(
+ "Failed to start AssembleUploadChunks.php", 'stashfailed' );
+ }
+ } else {
$status = $this->mUpload->concatenateChunks();
-
if ( !$status->isGood() ) {
$this->dieUsage( $status->getWikiText(), 'stashfailed' );
return array();
}
- // We have a new filekey for the fully concatenated file.
- $result['filekey'] = $this->mUpload->getLocalFile()->getFileKey();
-
- // Remove chunk from stash. (Checks against user ownership of chunks.)
- $this->mUpload->stash->removeFile( $this->mParams['filekey'] );
+ // The fully concatenated file has a new filekey. So remove
+ // the old filekey and fetch the new one.
+ $this->mUpload->stash->removeFile( $filekey );
+ $filekey = $this->mUpload->getLocalFile()->getFileKey();
$result['result'] = 'Success';
-
- } else {
-
- // Continue passing through the filekey for adding further chunks.
- $result['filekey'] = $this->mParams['filekey'];
}
}
+ $result['filekey'] = $filekey;
$result['offset'] = $this->mParams['offset'] + $chunkSize;
return $result;
}
-
+
/**
* Stash the file and return the file key
* Also re-raises exceptions with slightly more informative message strings (useful for API)
* @throws MWException
* @return String file key
*/
- function performStash() {
+ private function performStash() {
try {
$stashFile = $this->mUpload->stashFile();
@@ -244,7 +276,7 @@ class ApiUpload extends ApiBase {
$fileKey = $stashFile->getFileKey();
} catch ( MWException $e ) {
$message = 'Stashing temporary file failed: ' . get_class( $e ) . ' ' . $e->getMessage();
- wfDebug( __METHOD__ . ' ' . $message . "\n");
+ wfDebug( __METHOD__ . ' ' . $message . "\n" );
throw new MWException( $message );
}
return $fileKey;
@@ -254,12 +286,12 @@ class ApiUpload extends ApiBase {
* Throw an error that the user can recover from by providing a better
* value for $parameter
*
- * @param $error array Error array suitable for passing to dieUsageMsg()
- * @param $parameter string Parameter that needs revising
- * @param $data array Optional extra data to pass to the user
+ * @param array $error Error array suitable for passing to dieUsageMsg()
+ * @param string $parameter Parameter that needs revising
+ * @param array $data Optional extra data to pass to the user
* @throws UsageException
*/
- function dieRecoverableError( $error, $parameter, $data = array() ) {
+ private function dieRecoverableError( $error, $parameter, $data = array() ) {
try {
$data['filekey'] = $this->performStash();
$data['sessionkey'] = $data['filekey'];
@@ -283,11 +315,27 @@ class ApiUpload extends ApiBase {
$request = $this->getMain()->getRequest();
// chunk or one and only one of the following parameters is needed
- if( !$this->mParams['chunk'] ) {
+ if ( !$this->mParams['chunk'] ) {
$this->requireOnlyOneParameter( $this->mParams,
'filekey', 'file', 'url', 'statuskey' );
}
+ // Status report for "upload to stash"/"upload from stash"
+ if ( $this->mParams['filekey'] && $this->mParams['checkstatus'] ) {
+ $progress = UploadBase::getSessionStatus( $this->mParams['filekey'] );
+ if ( !$progress ) {
+ $this->dieUsage( 'No result in status data', 'missingresult' );
+ } elseif ( !$progress['status']->isGood() ) {
+ $this->dieUsage( $progress['status']->getWikiText(), 'stashfailed' );
+ }
+ if ( isset( $progress['status']->value['verification'] ) ) {
+ $this->checkVerification( $progress['status']->value['verification'] );
+ }
+ unset( $progress['status'] ); // remove Status object
+ $this->getResult()->addValue( null, $this->getModuleName(), $progress );
+ return false;
+ }
+
if ( $this->mParams['statuskey'] ) {
$this->checkAsyncDownloadEnabled();
@@ -302,7 +350,6 @@ class ApiUpload extends ApiBase {
}
$this->getResult()->addValue( null, $this->getModuleName(), $sessionData );
return false;
-
}
// The following modules all require the filename parameter to be set
@@ -311,9 +358,11 @@ class ApiUpload extends ApiBase {
}
if ( $this->mParams['chunk'] ) {
+ $this->checkChunkedEnabled();
+
// Chunk upload
$this->mUpload = new UploadFromChunks();
- if( isset( $this->mParams['filekey'] ) ){
+ if( isset( $this->mParams['filekey'] ) ) {
// handle new chunk
$this->mUpload->continueChunks(
$this->mParams['filename'],
@@ -334,8 +383,11 @@ class ApiUpload extends ApiBase {
}
$this->mUpload = new UploadFromStash( $this->getUser() );
-
- $this->mUpload->initialize( $this->mParams['filekey'], $this->mParams['filename'] );
+ // This will not download the temp file in initialize() in async mode.
+ // We still have enough information to call checkWarnings() and such.
+ $this->mUpload->initialize(
+ $this->mParams['filekey'], $this->mParams['filename'], !$this->mParams['async']
+ );
} elseif ( isset( $this->mParams['file'] ) ) {
$this->mUpload = new UploadFromFile();
$this->mUpload->initialize(
@@ -396,13 +448,20 @@ class ApiUpload extends ApiBase {
/**
* Performs file verification, dies on error.
*/
- protected function verifyUpload( ) {
- global $wgFileExtensions;
-
- $verification = $this->mUpload->verifyUpload( );
+ protected function verifyUpload() {
+ $verification = $this->mUpload->verifyUpload();
if ( $verification['status'] === UploadBase::OK ) {
return;
+ } else {
+ return $this->checkVerification( $verification );
}
+ }
+
+ /**
+ * Performs file verification, dies on error.
+ */
+ protected function checkVerification( array $verification ) {
+ global $wgFileExtensions;
// TODO: Move them to ApiBase's message map
switch( $verification['status'] ) {
@@ -460,12 +519,11 @@ class ApiUpload extends ApiBase {
break;
default:
$this->dieUsage( 'An unknown error occurred', 'unknown-error',
- 0, array( 'code' => $verification['status'] ) );
+ 0, array( 'code' => $verification['status'] ) );
break;
}
}
-
/**
* Check warnings.
* Returns a suitable array for inclusion into API results if there were warnings
@@ -503,12 +561,11 @@ class ApiUpload extends ApiBase {
return $warnings;
}
-
/**
* Perform the actual upload. Returns a suitable result array on success;
* dies on failure.
*
- * @param $warnings array Array of Api upload warnings
+ * @param array $warnings Array of Api upload warnings
* @return array
*/
protected function performUpload( $warnings ) {
@@ -517,6 +574,7 @@ class ApiUpload extends ApiBase {
$this->mParams['text'] = $this->mParams['comment'];
}
+ /** @var $file File */
$file = $this->mUpload->getLocalFile();
$watch = $this->getWatchlistValue( $this->mParams['watchlist'], $file->getTitle() );
@@ -526,29 +584,57 @@ class ApiUpload extends ApiBase {
}
// No errors, no warnings: do the upload
- $status = $this->mUpload->performUpload( $this->mParams['comment'],
- $this->mParams['text'], $watch, $this->getUser() );
-
- if ( !$status->isGood() ) {
- $error = $status->getErrorsArray();
-
- if ( count( $error ) == 1 && $error[0][0] == 'async' ) {
- // The upload can not be performed right now, because the user
- // requested so
- return array(
- 'result' => 'Queued',
- 'statuskey' => $error[0][1],
- );
+ if ( $this->mParams['async'] ) {
+ $progress = UploadBase::getSessionStatus( $this->mParams['filekey'] );
+ if ( $progress && $progress['result'] === 'Poll' ) {
+ $this->dieUsage( "Upload from stash already in progress.", 'publishfailed' );
+ }
+ UploadBase::setSessionStatus(
+ $this->mParams['filekey'],
+ array( 'result' => 'Poll', 'stage' => 'queued', 'status' => Status::newGood() )
+ );
+ $ok = JobQueueGroup::singleton()->push( new PublishStashedFileJob(
+ Title::makeTitle( NS_FILE, $this->mParams['filename'] ),
+ array(
+ 'filename' => $this->mParams['filename'],
+ 'filekey' => $this->mParams['filekey'],
+ 'comment' => $this->mParams['comment'],
+ 'text' => $this->mParams['text'],
+ 'watch' => $watch,
+ 'session' => $this->getContext()->exportSession()
+ )
+ ) );
+ if ( $ok ) {
+ $result['result'] = 'Poll';
} else {
- $this->getResult()->setIndexedTagName( $error, 'error' );
+ UploadBase::setSessionStatus( $this->mParams['filekey'], false );
+ $this->dieUsage(
+ "Failed to start PublishStashedFile.php", 'publishfailed' );
+ }
+ } else {
+ /** @var $status Status */
+ $status = $this->mUpload->performUpload( $this->mParams['comment'],
+ $this->mParams['text'], $watch, $this->getUser() );
- $this->dieUsage( 'An internal error occurred', 'internal-error', 0, $error );
+ if ( !$status->isGood() ) {
+ $error = $status->getErrorsArray();
+
+ if ( count( $error ) == 1 && $error[0][0] == 'async' ) {
+ // The upload can not be performed right now, because the user
+ // requested so
+ return array(
+ 'result' => 'Queued',
+ 'statuskey' => $error[0][1],
+ );
+ } else {
+ $this->getResult()->setIndexedTagName( $error, 'error' );
+
+ $this->dieUsage( 'An internal error occurred', 'internal-error', 0, $error );
+ }
}
+ $result['result'] = 'Success';
}
- $file = $this->mUpload->getLocalFile();
-
- $result['result'] = 'Success';
$result['filename'] = $file->getName();
if ( $warnings && count( $warnings ) > 0 ) {
$result['warnings'] = $warnings;
@@ -563,7 +649,14 @@ class ApiUpload extends ApiBase {
protected function checkAsyncDownloadEnabled() {
global $wgAllowAsyncCopyUploads;
if ( !$wgAllowAsyncCopyUploads ) {
- $this->dieUsage( 'Asynchronous copy uploads disabled', 'asynccopyuploaddisabled');
+ $this->dieUsage( 'Asynchronous copy uploads disabled', 'asynccopyuploaddisabled' );
+ }
+ }
+
+ protected function checkChunkedEnabled() {
+ global $wgAllowChunkedUploads;
+ if ( !$wgAllowChunkedUploads ) {
+ $this->dieUsage( 'Chunked uploads disabled', 'chunkeduploaddisabled' );
}
}
@@ -601,7 +694,9 @@ class ApiUpload extends ApiBase {
),
),
'ignorewarnings' => false,
- 'file' => null,
+ 'file' => array(
+ ApiBase::PARAM_TYPE => 'upload',
+ ),
'url' => null,
'filekey' => null,
'sessionkey' => array(
@@ -612,11 +707,15 @@ class ApiUpload extends ApiBase {
'filesize' => null,
'offset' => null,
- 'chunk' => null,
+ 'chunk' => array(
+ ApiBase::PARAM_TYPE => 'upload',
+ ),
+ 'async' => false,
'asyncdownload' => false,
'leavemessage' => false,
'statuskey' => null,
+ 'checkstatus' => false,
);
return $params;
@@ -641,9 +740,11 @@ class ApiUpload extends ApiBase {
'offset' => 'Offset of chunk in bytes',
'filesize' => 'Filesize of entire upload',
+ 'async' => 'Make potentially large file operations asynchronous when possible',
'asyncdownload' => 'Make fetching a URL asynchronous',
'leavemessage' => 'If asyncdownload is used, leave a message on the user talk page if finished',
- 'statuskey' => 'Fetch the upload status for this file key',
+ 'statuskey' => 'Fetch the upload status for this file key (upload by URL)',
+ 'checkstatus' => 'Only fetch the upload status for the given file key',
);
return $params;
@@ -692,7 +793,7 @@ class ApiUpload extends ApiBase {
' * Have the MediaWiki server fetch a file from a URL, using the "url" parameter',
' * Complete an earlier upload that failed due to warnings, using the "filekey" parameter',
'Note that the HTTP POST must be done as a file upload (i.e. using multipart/form-data) when',
- 'sending the "file". Also you must get and send an edit token before doing any upload stuff'
+ 'sending the "file". Also you must get and send an edit token before doing any upload stuff'
);
}
@@ -712,8 +813,10 @@ class ApiUpload extends ApiBase {
array( 'code' => 'filename-tooshort', 'info' => 'The filename is too short' ),
array( 'code' => 'overwrite', 'info' => 'Overwriting an existing file is not allowed' ),
array( 'code' => 'stashfailed', 'info' => 'Stashing temporary file failed' ),
+ array( 'code' => 'publishfailed', 'info' => 'Publishing of stashed file failed' ),
array( 'code' => 'internal-error', 'info' => 'An internal error occurred' ),
array( 'code' => 'asynccopyuploaddisabled', 'info' => 'Asynchronous copy uploads disabled' ),
+ array( 'code' => 'chunkeduploaddisabled', 'info' => 'Chunked uploads disabled' ),
array( 'fileexists-forbidden' ),
array( 'fileexists-shared-forbidden' ),
)
@@ -740,8 +843,4 @@ class ApiUpload extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Upload';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiUserrights.php b/includes/api/ApiUserrights.php
index cbb66a41..b9b1eeda 100644
--- a/includes/api/ApiUserrights.php
+++ b/includes/api/ApiUserrights.php
@@ -30,10 +30,6 @@
*/
class ApiUserrights extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
private $mUser = null;
public function execute() {
@@ -141,8 +137,4 @@ class ApiUserrights extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:User_group_membership';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiWatch.php b/includes/api/ApiWatch.php
index 0509f1f8..3e51299f 100644
--- a/includes/api/ApiWatch.php
+++ b/includes/api/ApiWatch.php
@@ -31,10 +31,6 @@
*/
class ApiWatch extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$user = $this->getUser();
if ( !$user->isLoggedIn() ) {
@@ -44,12 +40,20 @@ class ApiWatch extends ApiBase {
$params = $this->extractRequestParams();
$title = Title::newFromText( $params['title'] );
- if ( !$title || $title->getNamespace() < 0 ) {
+ if ( !$title || $title->isExternal() || !$title->canExist() ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
$res = array( 'title' => $title->getPrefixedText() );
+ // Currently unnecessary, code to act as a safeguard against any change in current behavior of uselang
+ // Copy from ApiParse
+ $oldLang = null;
+ if ( isset( $params['uselang'] ) && $params['uselang'] != $this->getContext()->getLanguage()->getCode() ) {
+ $oldLang = $this->getContext()->getLanguage(); // Backup language
+ $this->getContext()->setLanguage( Language::factory( $params['uselang'] ) );
+ }
+
if ( $params['unwatch'] ) {
$res['unwatched'] = '';
$res['message'] = $this->msg( 'removedwatchtext', $title->getPrefixedText() )->title( $title )->parseAsBlock();
@@ -59,6 +63,11 @@ class ApiWatch extends ApiBase {
$res['message'] = $this->msg( 'addedwatchtext', $title->getPrefixedText() )->title( $title )->parseAsBlock();
$success = WatchAction::doWatch( $title, $user );
}
+
+ if ( !is_null( $oldLang ) ) {
+ $this->getContext()->setLanguage( $oldLang ); // Reset language to $oldLang
+ }
+
if ( !$success ) {
$this->dieUsageMsg( 'hookaborted' );
}
@@ -88,6 +97,7 @@ class ApiWatch extends ApiBase {
ApiBase::PARAM_REQUIRED => true
),
'unwatch' => false,
+ 'uselang' => null,
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
@@ -99,6 +109,7 @@ class ApiWatch extends ApiBase {
return array(
'title' => 'The page to (un)watch',
'unwatch' => 'If set the page will be unwatched rather than watched',
+ 'uselang' => 'Language to show the message in',
'token' => 'A token previously acquired via prop=info',
);
}
@@ -136,8 +147,4 @@ class ApiWatch extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Watch';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/BacklinkCache.php b/includes/cache/BacklinkCache.php
index 05bf3186..a59cc9a2 100644
--- a/includes/BacklinkCache.php
+++ b/includes/cache/BacklinkCache.php
@@ -217,15 +217,16 @@ class BacklinkCache {
/**
* Get the field name prefix for a given table
* @param $table String
+ * @throws MWException
* @return null|string
*/
protected function getPrefix( $table ) {
static $prefixes = array(
- 'pagelinks' => 'pl',
- 'imagelinks' => 'il',
+ 'pagelinks' => 'pl',
+ 'imagelinks' => 'il',
'categorylinks' => 'cl',
'templatelinks' => 'tl',
- 'redirect' => 'rd',
+ 'redirect' => 'rd',
);
if ( isset( $prefixes[$table] ) ) {
@@ -245,6 +246,7 @@ class BacklinkCache {
* Get the SQL condition array for selecting backlinks, with a join
* on the page table.
* @param $table String
+ * @throws MWException
* @return array|null
*/
protected function getConditions( $table ) {
@@ -257,17 +259,17 @@ class BacklinkCache {
case 'templatelinks':
$conds = array(
"{$prefix}_namespace" => $this->title->getNamespace(),
- "{$prefix}_title" => $this->title->getDBkey(),
+ "{$prefix}_title" => $this->title->getDBkey(),
"page_id={$prefix}_from"
);
break;
case 'redirect':
$conds = array(
"{$prefix}_namespace" => $this->title->getNamespace(),
- "{$prefix}_title" => $this->title->getDBkey(),
+ "{$prefix}_title" => $this->title->getDBkey(),
$this->getDb()->makeList( array(
- "{$prefix}_interwiki = ''",
- "{$prefix}_interwiki is null",
+ "{$prefix}_interwiki" => '',
+ "{$prefix}_interwiki IS NULL",
), LIST_OR ),
"page_id={$prefix}_from"
);
@@ -287,31 +289,66 @@ class BacklinkCache {
default:
$conds = null;
wfRunHooks( 'BacklinkCacheGetConditions', array( $table, $this->title, &$conds ) );
- if( !$conds )
+ if( !$conds ) {
throw new MWException( "Invalid table \"$table\" in " . __CLASS__ );
+ }
}
return $conds;
}
/**
+ * Check if there are any backlinks
+ * @param $table String
+ * @return bool
+ */
+ public function hasLinks( $table ) {
+ return ( $this->getNumLinks( $table, 1 ) > 0 );
+ }
+
+ /**
* Get the approximate number of backlinks
* @param $table String
+ * @param $max integer Only count up to this many backlinks
* @return integer
*/
- public function getNumLinks( $table ) {
- if ( isset( $this->fullResultCache[$table] ) ) {
- return $this->fullResultCache[$table]->numRows();
- }
+ public function getNumLinks( $table, $max = INF ) {
+ global $wgMemc;
+ // 1) try partition cache ...
if ( isset( $this->partitionCache[$table] ) ) {
$entry = reset( $this->partitionCache[$table] );
- return $entry['numRows'];
+ return min( $max, $entry['numRows'] );
}
- $titleArray = $this->getLinks( $table );
+ // 2) ... then try full result cache ...
+ if ( isset( $this->fullResultCache[$table] ) ) {
+ return min( $max, $this->fullResultCache[$table]->numRows() );
+ }
- return $titleArray->count();
+ $memcKey = wfMemcKey( 'numbacklinks', md5( $this->title->getPrefixedDBkey() ), $table );
+
+ // 3) ... fallback to memcached ...
+ $count = $wgMemc->get( $memcKey );
+ if ( $count ) {
+ return min( $max, $count );
+ }
+
+ // 4) fetch from the database ...
+ if ( is_infinite( $max ) ) { // full count
+ $count = $this->getLinks( $table )->count();
+ $wgMemc->set( $memcKey, $count, self::CACHE_EXPIRY );
+ } else { // with limit
+ $count = $this->getDB()->select(
+ array( $table, 'page' ),
+ '1',
+ $this->getConditions( $table ),
+ __METHOD__,
+ array( 'LIMIT' => $max )
+ )->numRows();
+ }
+
+ return $count;
}
/**
@@ -319,14 +356,14 @@ class BacklinkCache {
* Returns an array giving the start and end of each range. The first
* batch has a start of false, and the last batch has an end of false.
*
- * @param $table String: the links table name
+ * @param string $table the links table name
* @param $batchSize Integer
* @return Array
*/
public function partition( $table, $batchSize ) {
+ global $wgMemc;
// 1) try partition cache ...
-
if ( isset( $this->partitionCache[$table][$batchSize] ) ) {
wfDebug( __METHOD__ . ": got from partition cache\n" );
return $this->partitionCache[$table][$batchSize]['batches'];
@@ -336,18 +373,12 @@ class BacklinkCache {
$cacheEntry =& $this->partitionCache[$table][$batchSize];
// 2) ... then try full result cache ...
-
if ( isset( $this->fullResultCache[$table] ) ) {
$cacheEntry = $this->partitionResult( $this->fullResultCache[$table], $batchSize );
wfDebug( __METHOD__ . ": got from full result cache\n" );
-
return $cacheEntry['batches'];
}
- // 3) ... fallback to memcached ...
-
- global $wgMemc;
-
$memcKey = wfMemcKey(
'backlinks',
md5( $this->title->getPrefixedDBkey() ),
@@ -355,23 +386,24 @@ class BacklinkCache {
$batchSize
);
+ // 3) ... fallback to memcached ...
$memcValue = $wgMemc->get( $memcKey );
-
if ( is_array( $memcValue ) ) {
$cacheEntry = $memcValue;
wfDebug( __METHOD__ . ": got from memcached $memcKey\n" );
-
return $cacheEntry['batches'];
}
-
// 4) ... finally fetch from the slow database :(
-
$this->getLinks( $table );
$cacheEntry = $this->partitionResult( $this->fullResultCache[$table], $batchSize );
- // Save to memcached
+ // Save partitions to memcached
$wgMemc->set( $memcKey, $cacheEntry, self::CACHE_EXPIRY );
+ // Save backlink count to memcached
+ $memcKey = wfMemcKey( 'numbacklinks', md5( $this->title->getPrefixedDBkey() ), $table );
+ $wgMemc->set( $memcKey, $cacheEntry['numRows'], self::CACHE_EXPIRY );
+
wfDebug( __METHOD__ . ": got from database\n" );
return $cacheEntry['batches'];
}
@@ -380,6 +412,7 @@ class BacklinkCache {
* Partition a DB result with backlinks in it into batches
* @param $res ResultWrapper database result
* @param $batchSize integer
+ * @throws MWException
* @return array @see
*/
protected function partitionResult( $res, $batchSize ) {
diff --git a/includes/cache/CacheDependency.php b/includes/cache/CacheDependency.php
index a3c2b52a..0f047e80 100644
--- a/includes/cache/CacheDependency.php
+++ b/includes/cache/CacheDependency.php
@@ -74,7 +74,7 @@ class DependencyWrapper {
/**
* Get the user-defined value
- * @return bool|\Mixed
+ * @return bool|Mixed
*/
function getValue() {
return $this->value;
@@ -98,11 +98,11 @@ class DependencyWrapper {
* calculated value will be stored to the cache in a wrapper.
*
* @param $cache BagOStuff a cache object such as $wgMemc
- * @param $key String: the cache key
+ * @param string $key the cache key
* @param $expiry Integer: the expiry timestamp or interval in seconds
* @param $callback Mixed: the callback for generating the value, or false
- * @param $callbackParams Array: the function parameters for the callback
- * @param $deps Array: the dependencies to store on a cache miss. Note: these
+ * @param array $callbackParams the function parameters for the callback
+ * @param array $deps the dependencies to store on a cache miss. Note: these
* are not the dependencies used on a cache hit! Cache hits use the stored
* dependency array.
*
@@ -153,7 +153,7 @@ class FileDependency extends CacheDependency {
/**
* Create a file dependency
*
- * @param $filename String: the name of the file, preferably fully qualified
+ * @param string $filename the name of the file, preferably fully qualified
* @param $timestamp Mixed: the unix last modified timestamp, or false if the
* file does not exist. If omitted, the timestamp will be loaded from
* the file.
@@ -404,7 +404,7 @@ class GlobalDependency extends CacheDependency {
* @return bool
*/
function isExpired() {
- if( !isset($GLOBALS[$this->name]) ) {
+ if( !isset( $GLOBALS[$this->name] ) ) {
return true;
}
return $GLOBALS[$this->name] != $this->value;
diff --git a/includes/cache/FileCacheBase.php b/includes/cache/FileCacheBase.php
index c0c5609c..30a72174 100644
--- a/includes/cache/FileCacheBase.php
+++ b/includes/cache/FileCacheBase.php
@@ -107,7 +107,7 @@ abstract class FileCacheBase {
/**
* Check if up to date cache file exists
- * @param $timestamp string MW_TS timestamp
+ * @param string $timestamp MW_TS timestamp
*
* @return bool
*/
@@ -163,7 +163,7 @@ abstract class FileCacheBase {
$this->checkCacheDirs(); // build parent dir
if ( !file_put_contents( $this->cachePath(), $text, LOCK_EX ) ) {
- wfDebug( __METHOD__ . "() failed saving ". $this->cachePath() . "\n");
+ wfDebug( __METHOD__ . "() failed saving ". $this->cachePath() . "\n" );
$this->mCached = null;
return false;
}
@@ -229,7 +229,7 @@ abstract class FileCacheBase {
public function incrMissesRecent( WebRequest $request ) {
global $wgMemc;
if ( mt_rand( 0, self::MISS_FACTOR - 1 ) == 0 ) {
- # Get a large IP range that should include the user even if that
+ # Get a large IP range that should include the user even if that
# person's IP address changes
$ip = $request->getIP();
if ( !IP::isValid( $ip ) ) {
diff --git a/includes/cache/GenderCache.php b/includes/cache/GenderCache.php
index 2a169bb3..63e4226d 100644
--- a/includes/cache/GenderCache.php
+++ b/includes/cache/GenderCache.php
@@ -59,8 +59,8 @@ class GenderCache {
/**
* Returns the gender for given username.
- * @param $username String or User: username
- * @param $caller String: the calling method
+ * @param string $username or User: username
+ * @param string $caller the calling method
* @return String
*/
public function getGenderOf( $username, $caller = '' ) {
@@ -116,7 +116,7 @@ class GenderCache {
*
* @since 1.20
* @param $titles List: array of Title objects or strings
- * @param $caller String: the calling method
+ * @param string $caller the calling method
*/
public function doTitlesArray( $titles, $caller = '' ) {
$users = array();
@@ -137,7 +137,7 @@ class GenderCache {
/**
* Preloads genders for given list of users.
* @param $users List|String: usernames
- * @param $caller String: the calling method
+ * @param string $caller the calling method
*/
public function doQuery( $users, $caller = '' ) {
$default = $this->getDefault();
diff --git a/includes/cache/HTMLCacheUpdate.php b/includes/cache/HTMLCacheUpdate.php
index 0a3c0023..88e79281 100644
--- a/includes/cache/HTMLCacheUpdate.php
+++ b/includes/cache/HTMLCacheUpdate.php
@@ -23,24 +23,6 @@
/**
* Class to invalidate the HTML cache of all the pages linking to a given title.
- * Small numbers of links will be done immediately, large numbers are pushed onto
- * the job queue.
- *
- * This class is designed to work efficiently with small numbers of links, and
- * to work reasonably well with up to ~10^5 links. Above ~10^6 links, the memory
- * and time requirements of loading all backlinked IDs in doUpdate() might become
- * prohibitive. The requirements measured at Wikimedia are approximately:
- *
- * memory: 48 bytes per row
- * time: 16us per row for the query plus processing
- *
- * The reason this query is done is to support partitioning of the job
- * by backlinked ID. The memory issue could be allieviated by doing this query in
- * batches, but of course LIMIT with an offset is inefficient on the DB side.
- *
- * The class is nevertheless a vast improvement on the previous method of using
- * File::getLinksTo() and Title::touchArray(), which uses about 2KB of memory per
- * link.
*
* @ingroup Cache
*/
@@ -50,8 +32,7 @@ class HTMLCacheUpdate implements DeferrableUpdate {
*/
public $mTitle;
- public $mTable, $mPrefix, $mStart, $mEnd;
- public $mRowsPerJob, $mRowsPerQuery;
+ public $mTable;
/**
* @param $titleTo
@@ -59,202 +40,35 @@ class HTMLCacheUpdate implements DeferrableUpdate {
* @param $start bool
* @param $end bool
*/
- function __construct( $titleTo, $table, $start = false, $end = false ) {
- global $wgUpdateRowsPerJob, $wgUpdateRowsPerQuery;
-
+ function __construct( Title $titleTo, $table ) {
$this->mTitle = $titleTo;
$this->mTable = $table;
- $this->mStart = $start;
- $this->mEnd = $end;
- $this->mRowsPerJob = $wgUpdateRowsPerJob;
- $this->mRowsPerQuery = $wgUpdateRowsPerQuery;
- $this->mCache = $this->mTitle->getBacklinkCache();
}
public function doUpdate() {
- if ( $this->mStart || $this->mEnd ) {
- $this->doPartialUpdate();
- return;
- }
-
- # Get an estimate of the number of rows from the BacklinkCache
- $numRows = $this->mCache->getNumLinks( $this->mTable );
- if ( $numRows > $this->mRowsPerJob * 2 ) {
- # Do fast cached partition
- $this->insertJobs();
- } else {
- # Get the links from the DB
- $titleArray = $this->mCache->getLinks( $this->mTable );
- # Check if the row count estimate was correct
- if ( $titleArray->count() > $this->mRowsPerJob * 2 ) {
- # Not correct, do accurate partition
- wfDebug( __METHOD__.": row count estimate was incorrect, repartitioning\n" );
- $this->insertJobsFromTitles( $titleArray );
- } else {
- $this->invalidateTitles( $titleArray );
- }
- }
- }
-
- /**
- * Update some of the backlinks, defined by a page ID range
- */
- protected function doPartialUpdate() {
- $titleArray = $this->mCache->getLinks( $this->mTable, $this->mStart, $this->mEnd );
- if ( $titleArray->count() <= $this->mRowsPerJob * 2 ) {
- # This partition is small enough, do the update
- $this->invalidateTitles( $titleArray );
- } else {
- # Partitioning was excessively inaccurate. Divide the job further.
- # This can occur when a large number of links are added in a short
- # period of time, say by updating a heavily-used template.
- $this->insertJobsFromTitles( $titleArray );
- }
- }
+ global $wgMaxBacklinksInvalidate;
- /**
- * Partition the current range given by $this->mStart and $this->mEnd,
- * using a pre-calculated title array which gives the links in that range.
- * Queue the resulting jobs.
- *
- * @param $titleArray array
- */
- protected function insertJobsFromTitles( $titleArray ) {
- # We make subpartitions in the sense that the start of the first job
- # will be the start of the parent partition, and the end of the last
- # job will be the end of the parent partition.
- $jobs = array();
- $start = $this->mStart; # start of the current job
- $numTitles = 0;
- foreach ( $titleArray as $title ) {
- $id = $title->getArticleID();
- # $numTitles is now the number of titles in the current job not
- # including the current ID
- if ( $numTitles >= $this->mRowsPerJob ) {
- # Add a job up to but not including the current ID
- $params = array(
- 'table' => $this->mTable,
- 'start' => $start,
- 'end' => $id - 1
- );
- $jobs[] = new HTMLCacheUpdateJob( $this->mTitle, $params );
- $start = $id;
- $numTitles = 0;
- }
- $numTitles++;
- }
- # Last job
- $params = array(
- 'table' => $this->mTable,
- 'start' => $start,
- 'end' => $this->mEnd
- );
- $jobs[] = new HTMLCacheUpdateJob( $this->mTitle, $params );
- wfDebug( __METHOD__.": repartitioning into " . count( $jobs ) . " jobs\n" );
-
- if ( count( $jobs ) < 2 ) {
- # I don't think this is possible at present, but handling this case
- # makes the code a bit more robust against future code updates and
- # avoids a potential infinite loop of repartitioning
- wfDebug( __METHOD__.": repartitioning failed!\n" );
- $this->invalidateTitles( $titleArray );
- return;
- }
+ wfProfileIn( __METHOD__ );
- Job::batchInsert( $jobs );
- }
-
- /**
- * @return mixed
- */
- protected function insertJobs() {
- $batches = $this->mCache->partition( $this->mTable, $this->mRowsPerJob );
- if ( !$batches ) {
- return;
- }
- $jobs = array();
- foreach ( $batches as $batch ) {
- $params = array(
+ $job = new HTMLCacheUpdateJob(
+ $this->mTitle,
+ array(
'table' => $this->mTable,
- 'start' => $batch[0],
- 'end' => $batch[1],
- );
- $jobs[] = new HTMLCacheUpdateJob( $this->mTitle, $params );
- }
- Job::batchInsert( $jobs );
- }
-
- /**
- * Invalidate an array (or iterator) of Title objects, right now
- * @param $titleArray array
- */
- protected function invalidateTitles( $titleArray ) {
- global $wgUseFileCache, $wgUseSquid;
-
- $dbw = wfGetDB( DB_MASTER );
- $timestamp = $dbw->timestamp();
-
- # Get all IDs in this query into an array
- $ids = array();
- foreach ( $titleArray as $title ) {
- $ids[] = $title->getArticleID();
- }
-
- if ( !$ids ) {
- return;
- }
-
- # Update page_touched
- $batches = array_chunk( $ids, $this->mRowsPerQuery );
- foreach ( $batches as $batch ) {
- $dbw->update( 'page',
- array( 'page_touched' => $timestamp ),
- array( 'page_id' => $batch ),
- __METHOD__
- );
- }
-
- # Update squid
- if ( $wgUseSquid ) {
- $u = SquidUpdate::newFromTitles( $titleArray );
- $u->doUpdate();
- }
+ ) + Job::newRootJobParams( // "overall" refresh links job info
+ "htmlCacheUpdate:{$this->mTable}:{$this->mTitle->getPrefixedText()}"
+ )
+ );
- # Update file cache
- if ( $wgUseFileCache ) {
- foreach ( $titleArray as $title ) {
- HTMLFileCache::clearFileCache( $title );
- }
+ $count = $this->mTitle->getBacklinkCache()->getNumLinks( $this->mTable, 200 );
+ if ( $wgMaxBacklinksInvalidate !== false && $count > $wgMaxBacklinksInvalidate ) {
+ wfDebug( "Skipped HTML cache invalidation of {$this->mTitle->getPrefixedText()}." );
+ } elseif ( $count >= 200 ) { // many backlinks
+ JobQueueGroup::singleton()->push( $job );
+ JobQueueGroup::singleton()->deduplicateRootJob( $job );
+ } else { // few backlinks ($count might be off even if 0)
+ $job->run(); // just do the purge query now
}
- }
-}
-
-
-/**
- * Job wrapper for HTMLCacheUpdate. Gets run whenever a related
- * job gets called from the queue.
- *
- * @ingroup JobQueue
- */
-class HTMLCacheUpdateJob extends Job {
- var $table, $start, $end;
-
- /**
- * Construct a job
- * @param $title Title: the title linked to
- * @param $params Array: job parameters (table, start and end page_ids)
- * @param $id Integer: job id
- */
- function __construct( $title, $params, $id = 0 ) {
- parent::__construct( 'htmlCacheUpdate', $title, $params, $id );
- $this->table = $params['table'];
- $this->start = $params['start'];
- $this->end = $params['end'];
- }
- public function run() {
- $update = new HTMLCacheUpdate( $this->title, $this->table, $this->start, $this->end );
- $update->doUpdate();
- return true;
+ wfProfileOut( __METHOD__ );
}
}
diff --git a/includes/cache/HTMLFileCache.php b/includes/cache/HTMLFileCache.php
index 6bfeed32..055fd685 100644
--- a/includes/cache/HTMLFileCache.php
+++ b/includes/cache/HTMLFileCache.php
@@ -33,6 +33,7 @@ class HTMLFileCache extends FileCacheBase {
* Construct an ObjectFileCache from a Title and an action
* @param $title Title|string Title object or prefixed DB key string
* @param $action string
+ * @throws MWException
* @return HTMLFileCache
*/
public static function newFromTitle( $title, $action ) {
@@ -127,7 +128,7 @@ class HTMLFileCache extends FileCacheBase {
public function loadFromFileCache( IContextSource $context ) {
global $wgMimeType, $wgLanguageCode;
- wfDebug( __METHOD__ . "()\n");
+ wfDebug( __METHOD__ . "()\n" );
$filename = $this->cachePath();
$context->getOutput()->sendCacheControl();
@@ -167,10 +168,10 @@ class HTMLFileCache extends FileCacheBase {
$now = wfTimestampNow();
if ( $this->useGzip() ) {
$text = str_replace(
- '</html>', '<!-- Cached/compressed '.$now." -->\n</html>", $text );
+ '</html>', '<!-- Cached/compressed ' . $now . " -->\n</html>", $text );
} else {
$text = str_replace(
- '</html>', '<!-- Cached '.$now." -->\n</html>", $text );
+ '</html>', '<!-- Cached ' . $now . " -->\n</html>", $text );
}
// Store text to FS...
diff --git a/includes/cache/LinkBatch.php b/includes/cache/LinkBatch.php
index 372f983b..72a2e8e5 100644
--- a/includes/cache/LinkBatch.php
+++ b/includes/cache/LinkBatch.php
@@ -223,7 +223,7 @@ class LinkBatch {
/**
* Construct a WHERE clause which will match all the given titles.
*
- * @param $prefix String: the appropriate table's field name prefix ('page', 'pl', etc)
+ * @param string $prefix the appropriate table's field name prefix ('page', 'pl', etc)
* @param $db DatabaseBase object to use
* @return mixed string with SQL where clause fragment, or false if no items.
*/
diff --git a/includes/cache/LinkCache.php b/includes/cache/LinkCache.php
index f759c020..0e41e265 100644
--- a/includes/cache/LinkCache.php
+++ b/includes/cache/LinkCache.php
@@ -74,11 +74,11 @@ class LinkCache {
* Get a field of a title object from cache.
* If this link is not good, it will return NULL.
* @param $title Title
- * @param $field String: ('length','redirect','revision')
+ * @param string $field ('length','redirect','revision','model')
* @return mixed
*/
public function getGoodLinkFieldObj( $title, $field ) {
- $dbkey = $title->getPrefixedDbKey();
+ $dbkey = $title->getPrefixedDBkey();
if ( array_key_exists( $dbkey, $this->mGoodLinkFields ) ) {
return $this->mGoodLinkFields[$dbkey][$field];
} else {
@@ -102,14 +102,16 @@ class LinkCache {
* @param $len Integer: text's length
* @param $redir Integer: whether the page is a redirect
* @param $revision Integer: latest revision's ID
+ * @param $model Integer: latest revision's content model ID
*/
- public function addGoodLinkObj( $id, $title, $len = -1, $redir = null, $revision = false ) {
- $dbkey = $title->getPrefixedDbKey();
+ public function addGoodLinkObj( $id, $title, $len = -1, $redir = null, $revision = false, $model = false ) {
+ $dbkey = $title->getPrefixedDBkey();
$this->mGoodLinks[$dbkey] = intval( $id );
$this->mGoodLinkFields[$dbkey] = array(
'length' => intval( $len ),
'redirect' => intval( $redir ),
- 'revision' => intval( $revision ) );
+ 'revision' => intval( $revision ),
+ 'model' => intval( $model ) );
}
/**
@@ -117,15 +119,16 @@ class LinkCache {
* @since 1.19
* @param $title Title
* @param $row object which has the fields page_id, page_is_redirect,
- * page_latest
+ * page_latest and page_content_model
*/
public function addGoodLinkObjFromRow( $title, $row ) {
- $dbkey = $title->getPrefixedDbKey();
+ $dbkey = $title->getPrefixedDBkey();
$this->mGoodLinks[$dbkey] = intval( $row->page_id );
$this->mGoodLinkFields[$dbkey] = array(
'length' => intval( $row->page_len ),
'redirect' => intval( $row->page_is_redirect ),
'revision' => intval( $row->page_latest ),
+ 'model' => !empty( $row->page_content_model ) ? strval( $row->page_content_model ) : null,
);
}
@@ -133,7 +136,7 @@ class LinkCache {
* @param $title Title
*/
public function addBadLinkObj( $title ) {
- $dbkey = $title->getPrefixedDbKey();
+ $dbkey = $title->getPrefixedDBkey();
if ( !$this->isBadLink( $dbkey ) ) {
$this->mBadLinks[$dbkey] = 1;
}
@@ -147,7 +150,7 @@ class LinkCache {
* @param $title Title
*/
public function clearLink( $title ) {
- $dbkey = $title->getPrefixedDbKey();
+ $dbkey = $title->getPrefixedDBkey();
unset( $this->mBadLinks[$dbkey] );
unset( $this->mGoodLinks[$dbkey] );
unset( $this->mGoodLinkFields[$dbkey] );
@@ -159,7 +162,7 @@ class LinkCache {
/**
* Add a title to the link cache, return the page_id or zero if non-existent
*
- * @param $title String: title to add
+ * @param string $title title to add
* @return Integer
*/
public function addLink( $title ) {
@@ -178,7 +181,8 @@ class LinkCache {
* @return Integer
*/
public function addLinkObj( $nt ) {
- global $wgAntiLockFlags;
+ global $wgAntiLockFlags, $wgContentHandlerUseDB;
+
wfProfileIn( __METHOD__ );
$key = $nt->getPrefixedDBkey();
@@ -210,8 +214,10 @@ class LinkCache {
$options = array();
}
- $s = $db->selectRow( 'page',
- array( 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
+ $f = array( 'page_id', 'page_len', 'page_is_redirect', 'page_latest' );
+ if ( $wgContentHandlerUseDB ) $f[] = 'page_content_model';
+
+ $s = $db->selectRow( 'page', $f,
array( 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey() ),
__METHOD__, $options );
# Set fields...
diff --git a/includes/LocalisationCache.php b/includes/cache/LocalisationCache.php
index d8e5d3a3..009b9507 100644
--- a/includes/LocalisationCache.php
+++ b/includes/cache/LocalisationCache.php
@@ -168,6 +168,7 @@ class LocalisationCache {
* for $wgLocalisationCacheConf.
*
* @param $conf Array
+ * @throws MWException
*/
function __construct( $conf ) {
global $wgCacheDirectory;
@@ -378,7 +379,7 @@ class LocalisationCache {
}
$deps = $this->store->get( $code, 'deps' );
- $keys = $this->store->get( $code, 'list', 'messages' );
+ $keys = $this->store->get( $code, 'list' );
$preload = $this->store->get( $code, 'preload' );
// Different keys may expire separately, at least in LCStore_Accel
if ( $deps === null || $keys === null || $preload === null ) {
@@ -404,6 +405,7 @@ class LocalisationCache {
/**
* Initialise a language in this object. Rebuild the cache if necessary.
* @param $code
+ * @throws MWException
*/
protected function initLanguage( $code ) {
if ( isset( $this->initialisedLangs[$code] ) ) {
@@ -474,6 +476,7 @@ class LocalisationCache {
* Read a PHP file containing localisation data.
* @param $_fileName
* @param $_fileType
+ * @throws MWException
* @return array
*/
protected function readPHPFile( $_fileName, $_fileType ) {
@@ -516,9 +519,11 @@ class LocalisationCache {
* @since 1.20
*/
public function getPluralRules( $code ) {
+ global $IP;
+
if ( $this->pluralRules === null ) {
- $cldrPlural = __DIR__ . "/../languages/data/plurals.xml";
- $mwPlural = __DIR__ . "/../languages/data/plurals-mediawiki.xml";
+ $cldrPlural = "$IP/languages/data/plurals.xml";
+ $mwPlural = "$IP/languages/data/plurals-mediawiki.xml";
// Load CLDR plural rules
$this->loadPluralFile( $cldrPlural );
if ( file_exists( $mwPlural ) ) {
@@ -533,7 +538,6 @@ class LocalisationCache {
}
}
-
/**
* Load a plural XML file with the given filename, compile the relevant
* rules, and save the compiled rules in a process-local cache.
@@ -561,6 +565,8 @@ class LocalisationCache {
* exists, the data array is returned, otherwise false is returned.
*/
protected function readSourceFilesAndRegisterDeps( $code, &$deps ) {
+ global $IP;
+
$fileName = Language::getMessagesFileName( $code );
if ( !file_exists( $fileName ) ) {
return false;
@@ -574,8 +580,9 @@ class LocalisationCache {
# And for PHP
$data['compiledPluralRules'] = $this->getCompiledPluralRules( $code );
- $deps['plurals'] = new FileDependency( __DIR__ . "/../languages/data/plurals.xml" );
- $deps['plurals-mw'] = new FileDependency( __DIR__ . "/../languages/data/plurals-mediawiki.xml" );
+ $deps['plurals'] = new FileDependency( "$IP/languages/data/plurals.xml" );
+ $deps['plurals-mw'] = new FileDependency( "$IP/languages/data/plurals-mediawiki.xml" );
+
return $data;
}
@@ -596,7 +603,7 @@ class LocalisationCache {
} elseif ( in_array( $key, self::$mergeableAliasListKeys ) ) {
$value = array_merge_recursive( $value, $fallbackValue );
} elseif ( in_array( $key, self::$optionalMergeKeys ) ) {
- if ( !empty( $value['inherit'] ) ) {
+ if ( !empty( $value['inherit'] ) ) {
$value = array_merge( $fallbackValue, $value );
}
@@ -659,6 +666,7 @@ class LocalisationCache {
* Load localisation data for a given language for both core and extensions
* and save it to the persistent cache store and the process cache
* @param $code
+ * @throws MWException
*/
public function recache( $code ) {
global $wgExtensionMessagesFiles;
@@ -906,8 +914,8 @@ class LocalisationCache {
interface LCStore {
/**
* Get a value.
- * @param $code string Language code
- * @param $key string Cache key
+ * @param string $code Language code
+ * @param string $key Cache key
*/
function get( $code, $key );
@@ -1027,7 +1035,6 @@ class LCStore_DB implements LCStore {
if ( $this->dbw->wasReadOnlyError() ) {
$this->readOnly = true;
$this->dbw->rollback( __METHOD__ );
- $this->dbw->ignoreErrors( false );
return;
} else {
throw $e;
@@ -1156,7 +1163,7 @@ class LCStore_CDB implements LCStore {
}
protected function getFileName( $code ) {
- if ( !$code || strpos( $code, '/' ) !== false ) {
+ if ( strval( $code ) === '' || strpos( $code, '/' ) !== false ) {
throw new MWException( __METHOD__ . ": Invalid language \"$code\"" );
}
return "{$this->directory}/l10n_cache-$code.cdb";
diff --git a/includes/cache/MessageCache.php b/includes/cache/MessageCache.php
index b854a2ec..c406b5c3 100644
--- a/includes/cache/MessageCache.php
+++ b/includes/cache/MessageCache.php
@@ -60,26 +60,6 @@ class MessageCache {
protected $mLoadedLanguages = array();
/**
- * Used for automatic detection of most used messages.
- */
- protected $mRequestedMessages = array();
-
- /**
- * How long the message request counts are stored. Longer period gives
- * better sample, but also takes longer to adapt changes. The counts
- * are aggregrated per day, regardless of the value of this variable.
- */
- protected static $mAdaptiveDataAge = 604800; // Is 7*24*3600
-
- /**
- * Filter the tail of less used messages that are requested more seldom
- * than this factor times the number of request of most requested message.
- * These messages are not loaded in the default set, but are still cached
- * individually on demand with the normal cache expiry time.
- */
- protected static $mAdaptiveInclusionThreshold = 0.05;
-
- /**
* Singleton instance
*
* @var MessageCache
@@ -142,7 +122,7 @@ class MessageCache {
* Actual format of the file depends on the $wgLocalMessageCacheSerialized
* setting.
*
- * @param $hash String: the hash of contents, to check validity.
+ * @param string $hash the hash of contents, to check validity.
* @param $code Mixed: Optional language code, see documenation of load().
* @return bool on failure.
*/
@@ -277,12 +257,15 @@ class MessageCache {
* or false if populating empty cache fails. Also returns true if MessageCache
* is disabled.
*
- * @param $code String: language to which load messages
+ * @param bool|String $code String: language to which load messages
+ * @throws MWException
* @return bool
*/
function load( $code = false ) {
global $wgUseLocalMessageCache;
+ $exception = null; // deferred error
+
if( !is_string( $code ) ) {
# This isn't really nice, so at least make a note about it and try to
# fall back
@@ -344,35 +327,52 @@ class MessageCache {
$where[] = 'cache is empty';
$where[] = 'loading from database';
- $this->lock( $cacheKey );
-
+ if ( $this->lock( $cacheKey ) ) {
+ $that = $this;
+ $osc = new ScopedCallback( function() use ( $that, $cacheKey ) {
+ $that->unlock( $cacheKey );
+ } );
+ }
# Limit the concurrency of loadFromDB to a single process
# This prevents the site from going down when the cache expires
$statusKey = wfMemcKey( 'messages', $code, 'status' );
$success = $this->mMemc->add( $statusKey, 'loading', MSG_LOAD_TIMEOUT );
- if ( $success ) {
+ if ( $success ) { // acquired lock
+ $cache = $this->mMemc;
+ $isc = new ScopedCallback( function() use ( $cache, $statusKey ) {
+ $cache->delete( $statusKey );
+ } );
$cache = $this->loadFromDB( $code );
$success = $this->setCache( $cache, $code );
- }
- if ( $success ) {
- $success = $this->saveToCaches( $cache, true, $code );
- if ( $success ) {
- $this->mMemc->delete( $statusKey );
+ if ( $success ) { // messages loaded
+ $success = $this->saveToCaches( $cache, true, $code );
+ $isc = null; // unlock
+ if ( !$success ) {
+ $this->mMemc->set( $statusKey, 'error', 60 * 5 );
+ wfDebug( __METHOD__ . ": set() error: restart memcached server!\n" );
+ $exception = new MWException( "Could not save cache for '$code'." );
+ }
} else {
- $this->mMemc->set( $statusKey, 'error', 60 * 5 );
- wfDebug( "MemCached set error in MessageCache: restart memcached server!\n" );
+ $isc = null; // unlock
+ $exception = new MWException( "Could not load cache from DB for '$code'." );
}
+ } else {
+ $exception = new MWException( "Could not acquire '$statusKey' lock." );
}
- $this->unlock($cacheKey);
+ $osc = null; // unlock
}
if ( !$success ) {
- # Bad luck... this should not happen
- $where[] = 'loading FAILED - cache is disabled';
- $info = implode( ', ', $where );
- wfDebug( __METHOD__ . ": Loading $code... $info\n" );
$this->mDisable = true;
$this->mCache = false;
+ // This used to go on, but that led to lots of nasty side
+ // effects like gadgets and sidebar getting cached with their
+ // default content
+ if ( $exception instanceof Exception ) {
+ throw $exception;
+ } else {
+ throw new MWException( "MessageCache failed to load messages" );
+ }
} else {
# All good, just record the success
$info = implode( ', ', $where );
@@ -388,7 +388,7 @@ class MessageCache {
* $wgMaxMsgCacheEntrySize are assigned a special value, and are loaded
* on-demand from the database later.
*
- * @param $code String: language code.
+ * @param string $code language code.
* @return Array: loaded messages for storing in caches.
*/
function loadFromDB( $code ) {
@@ -404,19 +404,20 @@ class MessageCache {
);
$mostused = array();
- if ( $wgAdaptiveMessageCache ) {
- $mostused = $this->getMostUsedMessages();
- if ( $code !== $wgLanguageCode ) {
- foreach ( $mostused as $key => $value ) {
- $mostused[$key] = "$value/$code";
- }
+ if ( $wgAdaptiveMessageCache && $code !== $wgLanguageCode ) {
+ if ( !isset( $this->mCache[$wgLanguageCode] ) ) {
+ $this->load( $wgLanguageCode );
+ }
+ $mostused = array_keys( $this->mCache[$wgLanguageCode] );
+ foreach ( $mostused as $key => $value ) {
+ $mostused[$key] = "$value/$code";
}
}
if ( count( $mostused ) ) {
$conds['page_title'] = $mostused;
} elseif ( $code !== $wgLanguageCode ) {
- $conds[] = 'page_title' . $dbr->buildLike( $dbr->anyString(), "/$code" );
+ $conds[] = 'page_title' . $dbr->buildLike( $dbr->anyString(), '/', $code );
} else {
# Effectively disallows use of '/' character in NS_MEDIAWIKI for uses
# other than language code.
@@ -459,12 +460,6 @@ class MessageCache {
$cache[$row->page_title] = $entry;
}
- foreach ( $mostused as $key ) {
- if ( !isset( $cache[$key] ) ) {
- $cache[$key] = '!NONEXISTENT';
- }
- }
-
$cache['VERSION'] = MSG_CACHE_VERSION;
wfProfileOut( __METHOD__ );
return $cache;
@@ -473,7 +468,7 @@ class MessageCache {
/**
* Updates cache as necessary when message page is changed
*
- * @param $title String: name of the page changed.
+ * @param string $title name of the page changed.
* @param $text Mixed: new contents of the page.
*/
public function replace( $title, $text ) {
@@ -512,7 +507,7 @@ class MessageCache {
// Also delete cached sidebar... just in case it is affected
$codes = array( $code );
- if ( $code === 'en' ) {
+ if ( $code === 'en' ) {
// Delete all sidebars, like for example on action=purge on the
// sidebar messages
$codes = array_keys( Language::fetchLanguageNames() );
@@ -536,9 +531,9 @@ class MessageCache {
/**
* Shortcut to update caches.
*
- * @param $cache Array: cached messages with a version.
- * @param $memc Bool: Wether to update or not memcache.
- * @param $code String: Language code.
+ * @param array $cache cached messages with a version.
+ * @param bool $memc Wether to update or not memcache.
+ * @param string $code Language code.
* @return bool on somekind of error.
*/
protected function saveToCaches( $cache, $memc = true, $code = false ) {
@@ -558,7 +553,7 @@ class MessageCache {
$serialized = serialize( $cache );
$hash = md5( $serialized );
$this->mMemc->set( wfMemcKey( 'messages', $code, 'hash' ), $hash, $this->mExpiry );
- if ($wgLocalMessageCacheSerialized) {
+ if ( $wgLocalMessageCacheSerialized ) {
$this->saveToLocal( $serialized, $hash, $code );
} else {
$this->saveToScript( $cache, $hash, $code );
@@ -593,10 +588,10 @@ class MessageCache {
/**
* Get a message from either the content language or the user language.
*
- * @param $key String: the message cache key
+ * @param string $key the message cache key
* @param $useDB Boolean: get the message from the DB, false to use only
* the localisation
- * @param $langcode String: code of the language to get the message for, if
+ * @param bool|string $langcode Code of the language to get the message for, if
* it is a valid code create a language for that language,
* if it is a string but not a valid code then make a basic
* language object, if it is a false boolean then use the
@@ -607,6 +602,7 @@ class MessageCache {
* @param $isFullKey Boolean: specifies whether $key is a two part key
* "msg/lang".
*
+ * @throws MWException
* @return string|bool
*/
function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) {
@@ -645,13 +641,6 @@ class MessageCache {
$uckey = $wgContLang->ucfirst( $lckey );
}
- /**
- * Record each message request, but only once per request.
- * This information is not used unless $wgAdaptiveMessageCache
- * is enabled.
- */
- $this->mRequestedMessages[$uckey] = true;
-
# Try the MediaWiki namespace
if( !$this->mDisable && $useDB ) {
$title = $uckey;
@@ -712,14 +701,12 @@ class MessageCache {
* Get a message from the MediaWiki namespace, with caching. The key must
* first be converted to two-part lang/msg form if necessary.
*
- * @param $title String: Message cache key with initial uppercase letter.
- * @param $code String: code denoting the language to try.
+ * @param string $title Message cache key with initial uppercase letter.
+ * @param string $code code denoting the language to try.
*
* @return string|bool False on failure
*/
function getMsgFromNamespace( $title, $code ) {
- global $wgAdaptiveMessageCache;
-
$this->load( $code );
if ( isset( $this->mCache[$code][$title] ) ) {
$entry = $this->mCache[$code][$title];
@@ -738,15 +725,7 @@ class MessageCache {
return $message;
}
- /**
- * If message cache is in normal mode, it is guaranteed
- * (except bugs) that there is always entry (or placeholder)
- * in the cache if message exists. Thus we can do minor
- * performance improvement and return false early.
- */
- if ( !$wgAdaptiveMessageCache ) {
- return false;
- }
+ return false;
}
# Try the individual message cache
@@ -770,16 +749,32 @@ class MessageCache {
Title::makeTitle( NS_MEDIAWIKI, $title ), false, Revision::READ_LATEST
);
if ( $revision ) {
- $message = $revision->getText();
- if ($message === false) {
+ $content = $revision->getContent();
+ if ( !$content ) {
// A possibly temporary loading failure.
wfDebugLog( 'MessageCache', __METHOD__ . ": failed to load message page text for {$title} ($code)" );
+ $message = null; // no negative caching
} else {
- $this->mCache[$code][$title] = ' ' . $message;
- $this->mMemc->set( $titleKey, ' ' . $message, $this->mExpiry );
+ // XXX: Is this the right way to turn a Content object into a message?
+ // NOTE: $content is typically either WikitextContent, JavaScriptContent or CssContent.
+ // MessageContent is *not* used for storing messages, it's only used for wrapping them when needed.
+ $message = $content->getWikitextForTransclusion();
+
+ if ( $message === false || $message === null ) {
+ wfDebugLog( 'MessageCache', __METHOD__ . ": message content doesn't provide wikitext "
+ . "(content model: " . $content->getContentHandler() . ")" );
+
+ $message = false; // negative caching
+ } else {
+ $this->mCache[$code][$title] = ' ' . $message;
+ $this->mMemc->set( $titleKey, ' ' . $message, $this->mExpiry );
+ }
}
} else {
- $message = false;
+ $message = false; // negative caching
+ }
+
+ if ( $message === false ) { // negative caching
$this->mCache[$code][$title] = '!NONEXISTENT';
$this->mMemc->set( $titleKey, '!NONEXISTENT', $this->mExpiry );
}
@@ -845,7 +840,7 @@ class MessageCache {
* @param $linestart bool
* @param $interface bool
* @param $language
- * @return ParserOutput
+ * @return ParserOutput|string
*/
public function parse( $text, $title = null, $linestart = true, $interface = false, $language = null ) {
if ( $this->mInParser ) {
@@ -890,7 +885,7 @@ class MessageCache {
*/
function clear() {
$langs = Language::fetchLanguageNames( null, 'mw' );
- foreach ( array_keys($langs) as $code ) {
+ foreach ( array_keys( $langs ) as $code ) {
# Global cache
$this->mMemc->delete( wfMemcKey( 'messages', $code ) );
# Invalidate all local caches
@@ -919,82 +914,6 @@ class MessageCache {
return array( $message, $lang );
}
- public static function logMessages() {
- wfProfileIn( __METHOD__ );
- global $wgAdaptiveMessageCache;
- if ( !$wgAdaptiveMessageCache || !self::$instance instanceof MessageCache ) {
- wfProfileOut( __METHOD__ );
- return;
- }
-
- $cachekey = wfMemckey( 'message-profiling' );
- $cache = wfGetCache( CACHE_DB );
- $data = $cache->get( $cachekey );
-
- if ( !$data ) {
- $data = array();
- }
-
- $age = self::$mAdaptiveDataAge;
- $filterDate = substr( wfTimestamp( TS_MW, time() - $age ), 0, 8 );
- foreach ( array_keys( $data ) as $key ) {
- if ( $key < $filterDate ) {
- unset( $data[$key] );
- }
- }
-
- $index = substr( wfTimestampNow(), 0, 8 );
- if ( !isset( $data[$index] ) ) {
- $data[$index] = array();
- }
-
- foreach ( self::$instance->mRequestedMessages as $message => $_ ) {
- if ( !isset( $data[$index][$message] ) ) {
- $data[$index][$message] = 0;
- }
- $data[$index][$message]++;
- }
-
- $cache->set( $cachekey, $data );
- wfProfileOut( __METHOD__ );
- }
-
- /**
- * @return array
- */
- public function getMostUsedMessages() {
- wfProfileIn( __METHOD__ );
- $cachekey = wfMemcKey( 'message-profiling' );
- $cache = wfGetCache( CACHE_DB );
- $data = $cache->get( $cachekey );
- if ( !$data ) {
- wfProfileOut( __METHOD__ );
- return array();
- }
-
- $list = array();
-
- foreach( $data as $messages ) {
- foreach( $messages as $message => $count ) {
- $key = $message;
- if ( !isset( $list[$key] ) ) {
- $list[$key] = 0;
- }
- $list[$key] += $count;
- }
- }
-
- $max = max( $list );
- foreach ( $list as $message => $count ) {
- if ( $count < intval( $max * self::$mAdaptiveInclusionThreshold ) ) {
- unset( $list[$message] );
- }
- }
-
- wfProfileOut( __METHOD__ );
- return array_keys( $list );
- }
-
/**
* Get all message keys stored in the message cache for a given language.
* If $code is the content language code, this will return all message keys
diff --git a/includes/cache/ProcessCacheLRU.php b/includes/cache/ProcessCacheLRU.php
index f215ebd8..76c76f37 100644
--- a/includes/cache/ProcessCacheLRU.php
+++ b/includes/cache/ProcessCacheLRU.php
@@ -28,6 +28,8 @@
class ProcessCacheLRU {
/** @var Array */
protected $cache = array(); // (key => prop => value)
+ /** @var Array */
+ protected $cacheTimes = array(); // (key => prop => UNIX timestamp)
protected $maxCacheKeys; // integer; max entries
@@ -44,7 +46,7 @@ class ProcessCacheLRU {
/**
* Set a property field for a cache entry.
- * This will prune the cache if it gets too large.
+ * This will prune the cache if it gets too large based on LRU.
* If the item is already set, it will be pushed to the top of the cache.
*
* @param $key string
@@ -57,9 +59,12 @@ class ProcessCacheLRU {
$this->ping( $key ); // push to top
} elseif ( count( $this->cache ) >= $this->maxCacheKeys ) {
reset( $this->cache );
- unset( $this->cache[key( $this->cache )] );
+ $evictKey = key( $this->cache );
+ unset( $this->cache[$evictKey] );
+ unset( $this->cacheTimes[$evictKey] );
}
$this->cache[$key][$prop] = $value;
+ $this->cacheTimes[$key][$prop] = time();
}
/**
@@ -67,10 +72,14 @@ class ProcessCacheLRU {
*
* @param $key string
* @param $prop string
+ * @param $maxAge integer Ignore items older than this many seconds (since 1.21)
* @return bool
*/
- public function has( $key, $prop ) {
- return isset( $this->cache[$key][$prop] );
+ public function has( $key, $prop, $maxAge = 0 ) {
+ if ( isset( $this->cache[$key][$prop] ) ) {
+ return ( $maxAge <= 0 || ( time() - $this->cacheTimes[$key][$prop] ) <= $maxAge );
+ }
+ return false;
}
/**
@@ -100,9 +109,11 @@ class ProcessCacheLRU {
public function clear( $keys = null ) {
if ( $keys === null ) {
$this->cache = array();
+ $this->cacheTimes = array();
} else {
foreach ( (array)$keys as $key ) {
unset( $this->cache[$key] );
+ unset( $this->cacheTimes[$key] );
}
}
}
diff --git a/includes/cache/SquidUpdate.php b/includes/cache/SquidUpdate.php
index 423e3884..39bf4c9f 100644
--- a/includes/cache/SquidUpdate.php
+++ b/includes/cache/SquidUpdate.php
@@ -61,13 +61,13 @@ class SquidUpdate {
array( 'page_namespace', 'page_title' ),
array(
'pl_namespace' => $title->getNamespace(),
- 'pl_title' => $title->getDBkey(),
+ 'pl_title' => $title->getDBkey(),
'pl_from=page_id' ),
__METHOD__ );
$blurlArr = $title->getSquidURLs();
- if ( $dbr->numRows( $res ) <= $wgMaxSquidPurgeTitles ) {
+ if ( $res->numRows() <= $wgMaxSquidPurgeTitles ) {
foreach ( $res as $BL ) {
- $tobj = Title::makeTitle( $BL->page_namespace, $BL->page_title ) ;
+ $tobj = Title::makeTitle( $BL->page_namespace, $BL->page_title );
$blurlArr[] = $tobj->getInternalURL();
}
}
@@ -129,6 +129,8 @@ class SquidUpdate {
return;
}
+ wfDebug( "Squid purge: " . implode( ' ', $urlArr ) . "\n" );
+
if ( $wgHTCPMulticastRouting ) {
SquidUpdate::HTCPPurge( $urlArr );
}
@@ -249,11 +251,11 @@ class SquidUpdate {
static function expand( $url ) {
return wfExpandUrl( $url, PROTO_INTERNAL );
}
-
+
/**
* Find the HTCP routing rule to use for a given URL.
- * @param $url string URL to match
- * @param $rules array Array of rules, see $wgHTCPMulticastRouting for format and behavior
+ * @param string $url URL to match
+ * @param array $rules Array of rules, see $wgHTCPMulticastRouting for format and behavior
* @return mixed Element of $rules that matched, or false if nothing matched
*/
static function getRuleForURL( $url, $rules ) {
@@ -264,5 +266,4 @@ class SquidUpdate {
}
return false;
}
-
}
diff --git a/includes/cache/UserCache.php b/includes/cache/UserCache.php
index 6ec23669..bfbacfaa 100644
--- a/includes/cache/UserCache.php
+++ b/includes/cache/UserCache.php
@@ -45,7 +45,7 @@ class UserCache {
* Get a property of a user based on their user ID
*
* @param $userId integer User ID
- * @param $prop string User property
+ * @param string $prop User property
* @return mixed The property or false if the user does not exist
*/
public function getProp( $userId, $prop ) {
@@ -60,9 +60,9 @@ class UserCache {
/**
* Preloads user names for given list of users.
- * @param $userIds Array List of user IDs
- * @param $options Array Option flags; include 'userpage' and 'usertalk'
- * @param $caller String: the calling method
+ * @param array $userIds List of user IDs
+ * @param array $options Option flags; include 'userpage' and 'usertalk'
+ * @param string $caller the calling method
*/
public function doQuery( array $userIds, $options = array(), $caller = '' ) {
wfProfileIn( __METHOD__ );
@@ -124,8 +124,8 @@ class UserCache {
* Check if a cache type is in $options and was not loaded for this user
*
* @param $uid integer user ID
- * @param $type string Cache type
- * @param $options Array Requested cache types
+ * @param string $type Cache type
+ * @param array $options Requested cache types
* @return bool
*/
protected function queryNeeded( $uid, $type, array $options ) {
diff --git a/includes/clientpool/RedisConnectionPool.php b/includes/clientpool/RedisConnectionPool.php
new file mode 100644
index 00000000..5c7c4f20
--- /dev/null
+++ b/includes/clientpool/RedisConnectionPool.php
@@ -0,0 +1,312 @@
+<?php
+/**
+ * PhpRedis client connection pooling manager.
+ *
+ * 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
+ * @defgroup Redis Redis
+ * @author Aaron Schulz
+ */
+
+/**
+ * Helper class to manage redis connections using PhpRedis.
+ *
+ * This can be used to get handle wrappers that free the handle when the wrapper
+ * leaves scope. The maximum number of free handles (connections) is configurable.
+ * This provides an easy way to cache connection handles that may also have state,
+ * such as a handle does between multi() and exec(), and without hoarding connections.
+ * The wrappers use PHP magic methods so that calling functions on them calls the
+ * function of the actual Redis object handle.
+ *
+ * @ingroup Redis
+ * @since 1.21
+ */
+class RedisConnectionPool {
+ // Settings for all connections in this pool
+ protected $connectTimeout; // string; connection timeout
+ protected $persistent; // bool; whether connections persist
+ protected $password; // string; plaintext auth password
+ protected $serializer; // integer; the serializer to use (Redis::SERIALIZER_*)
+
+ protected $idlePoolSize = 0; // integer; current idle pool size
+
+ /** @var Array (server name => ((connection info array),...) */
+ protected $connections = array();
+ /** @var Array (server name => UNIX timestamp) */
+ protected $downServers = array();
+
+ /** @var Array */
+ protected static $instances = array(); // (pool ID => RedisConnectionPool)
+
+ const SERVER_DOWN_TTL = 30; // integer; seconds to cache servers as "down"
+
+ /**
+ * $options include:
+ * - connectTimeout : The timeout for new connections, in seconds.
+ * Optional, default is 1 second.
+ * - persistent : Set this to true to allow connections to persist across
+ * multiple web requests. False by default.
+ * - password : The authentication password, will be sent to Redis in clear text.
+ * Optional, if it is unspecified, no AUTH command will be sent.
+ * - serializer : Set to "php", "igbinary", or "none". Default is "php".
+ * @param array $options
+ */
+ protected function __construct( array $options ) {
+ if ( !extension_loaded( 'redis' ) ) {
+ throw new MWException( __CLASS__. ' requires the phpredis extension: ' .
+ 'https://github.com/nicolasff/phpredis' );
+ }
+ $this->connectTimeout = $options['connectTimeout'];
+ $this->persistent = $options['persistent'];
+ $this->password = $options['password'];
+ if ( !isset( $options['serializer'] ) || $options['serializer'] === 'php' ) {
+ $this->serializer = Redis::SERIALIZER_PHP;
+ } elseif ( $options['serializer'] === 'igbinary' ) {
+ $this->serializer = Redis::SERIALIZER_IGBINARY;
+ } elseif ( $options['serializer'] === 'none' ) {
+ $this->serializer = Redis::SERIALIZER_NONE;
+ } else {
+ throw new MWException( "Invalid serializer specified." );
+ }
+ }
+
+ /**
+ * @param $options Array
+ * @return Array
+ */
+ protected static function applyDefaultConfig( array $options ) {
+ if ( !isset( $options['connectTimeout'] ) ) {
+ $options['connectTimeout'] = 1;
+ }
+ if ( !isset( $options['persistent'] ) ) {
+ $options['persistent'] = false;
+ }
+ if ( !isset( $options['password'] ) ) {
+ $options['password'] = null;
+ }
+ return $options;
+ }
+
+ /**
+ * @param $options Array
+ * @return RedisConnectionPool
+ */
+ public static function singleton( array $options ) {
+ $options = self::applyDefaultConfig( $options );
+ // Map the options to a unique hash...
+ ksort( $options ); // normalize to avoid pool fragmentation
+ $id = sha1( serialize( $options ) );
+ // Initialize the object at the hash as needed...
+ if ( !isset( self::$instances[$id] ) ) {
+ self::$instances[$id] = new self( $options );
+ wfDebug( "Creating a new " . __CLASS__ . " instance with id $id." );
+ }
+ return self::$instances[$id];
+ }
+
+ /**
+ * Get a connection to a redis server. Based on code in RedisBagOStuff.php.
+ *
+ * @param string $server A hostname/port combination or the absolute path of a UNIX socket.
+ * If a hostname is specified but no port, port 6379 will be used.
+ * @return RedisConnRef|bool Returns false on failure
+ * @throws MWException
+ */
+ public function getConnection( $server ) {
+ // Check the listing "dead" servers which have had a connection errors.
+ // Servers are marked dead for a limited period of time, to
+ // avoid excessive overhead from repeated connection timeouts.
+ if ( isset( $this->downServers[$server] ) ) {
+ $now = time();
+ if ( $now > $this->downServers[$server] ) {
+ // Dead time expired
+ unset( $this->downServers[$server] );
+ } else {
+ // Server is dead
+ wfDebug( "server $server is marked down for another " .
+ ( $this->downServers[$server] - $now ) . " seconds, can't get connection" );
+ return false;
+ }
+ }
+
+ // Check if a connection is already free for use
+ if ( isset( $this->connections[$server] ) ) {
+ foreach ( $this->connections[$server] as &$connection ) {
+ if ( $connection['free'] ) {
+ $connection['free'] = false;
+ --$this->idlePoolSize;
+ return new RedisConnRef( $this, $server, $connection['conn'] );
+ }
+ }
+ }
+
+ if ( substr( $server, 0, 1 ) === '/' ) {
+ // UNIX domain socket
+ // These are required by the redis extension to start with a slash, but
+ // we still need to set the port to a special value to make it work.
+ $host = $server;
+ $port = 0;
+ } else {
+ // TCP connection
+ $hostPort = IP::splitHostAndPort( $server );
+ if ( !$hostPort ) {
+ throw new MWException( __CLASS__.": invalid configured server \"$server\"" );
+ }
+ list( $host, $port ) = $hostPort;
+ if ( $port === false ) {
+ $port = 6379;
+ }
+ }
+
+ $conn = new Redis();
+ try {
+ if ( $this->persistent ) {
+ $result = $conn->pconnect( $host, $port, $this->connectTimeout );
+ } else {
+ $result = $conn->connect( $host, $port, $this->connectTimeout );
+ }
+ if ( !$result ) {
+ wfDebugLog( 'redis', "Could not connect to server $server" );
+ // Mark server down for some time to avoid further timeouts
+ $this->downServers[$server] = time() + self::SERVER_DOWN_TTL;
+ return false;
+ }
+ if ( $this->password !== null ) {
+ if ( !$conn->auth( $this->password ) ) {
+ wfDebugLog( 'redis', "Authentication error connecting to $server" );
+ }
+ }
+ } catch ( RedisException $e ) {
+ $this->downServers[$server] = time() + self::SERVER_DOWN_TTL;
+ wfDebugLog( 'redis', "Redis exception: " . $e->getMessage() . "\n" );
+ return false;
+ }
+
+ if ( $conn ) {
+ $conn->setOption( Redis::OPT_SERIALIZER, $this->serializer );
+ $this->connections[$server][] = array( 'conn' => $conn, 'free' => false );
+ return new RedisConnRef( $this, $server, $conn );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Mark a connection to a server as free to return to the pool
+ *
+ * @param $server string
+ * @param $conn Redis
+ * @return boolean
+ */
+ public function freeConnection( $server, Redis $conn ) {
+ $found = false;
+
+ foreach ( $this->connections[$server] as &$connection ) {
+ if ( $connection['conn'] === $conn && !$connection['free'] ) {
+ $connection['free'] = true;
+ ++$this->idlePoolSize;
+ break;
+ }
+ }
+
+ $this->closeExcessIdleConections();
+
+ return $found;
+ }
+
+ /**
+ * Close any extra idle connections if there are more than the limit
+ *
+ * @return void
+ */
+ protected function closeExcessIdleConections() {
+ if ( $this->idlePoolSize <= count( $this->connections ) ) {
+ return; // nothing to do (no more connections than servers)
+ }
+
+ foreach ( $this->connections as $server => &$serverConnections ) {
+ foreach ( $serverConnections as $key => &$connection ) {
+ if ( $connection['free'] ) {
+ unset( $serverConnections[$key] );
+ if ( --$this->idlePoolSize <= count( $this->connections ) ) {
+ return; // done (no more connections than servers)
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * The redis extension throws an exception in response to various read, write
+ * and protocol errors. Sometimes it also closes the connection, sometimes
+ * not. The safest response for us is to explicitly destroy the connection
+ * object and let it be reopened during the next request.
+ *
+ * @param $server string
+ * @param $cref RedisConnRef
+ * @param $e RedisException
+ * @return void
+ */
+ public function handleException( $server, RedisConnRef $cref, RedisException $e ) {
+ wfDebugLog( 'redis', "Redis exception on server $server: " . $e->getMessage() . "\n" );
+ foreach ( $this->connections[$server] as $key => $connection ) {
+ if ( $cref->isConnIdentical( $connection['conn'] ) ) {
+ $this->idlePoolSize -= $connection['free'] ? 1 : 0;
+ unset( $this->connections[$server][$key] );
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * Helper class to handle automatically marking connectons as reusable (via RAII pattern)
+ *
+ * @ingroup Redis
+ * @since 1.21
+ */
+class RedisConnRef {
+ /** @var RedisConnectionPool */
+ protected $pool;
+ /** @var Redis */
+ protected $conn;
+
+ protected $server; // string
+
+ /**
+ * @param $pool RedisConnectionPool
+ * @param $server string
+ * @param $conn Redis
+ */
+ public function __construct( RedisConnectionPool $pool, $server, Redis $conn ) {
+ $this->pool = $pool;
+ $this->server = $server;
+ $this->conn = $conn;
+ }
+
+ public function __call( $name, $arguments ) {
+ return call_user_func_array( array( $this->conn, $name ), $arguments );
+ }
+
+ public function isConnIdentical( Redis $conn ) {
+ return $this->conn === $conn;
+ }
+
+ function __destruct() {
+ $this->pool->freeConnection( $this->server, $this->conn );
+ }
+}
diff --git a/includes/content/AbstractContent.php b/includes/content/AbstractContent.php
new file mode 100644
index 00000000..137efb8a
--- /dev/null
+++ b/includes/content/AbstractContent.php
@@ -0,0 +1,444 @@
+<?php
+/**
+ * A content object represents page content, e.g. the text to show on a page.
+ * Content objects have no knowledge about how they relate to Wiki pages.
+ *
+ * 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
+ *
+ * @since 1.21
+ *
+ * @file
+ * @ingroup Content
+ *
+ * @author Daniel Kinzler
+ */
+
+/**
+ * Base implementation for content objects.
+ *
+ * @ingroup Content
+ */
+abstract class AbstractContent implements Content {
+
+ /**
+ * Name of the content model this Content object represents.
+ * Use with CONTENT_MODEL_XXX constants
+ *
+ * @since 1.21
+ *
+ * @var string $model_id
+ */
+ protected $model_id;
+
+ /**
+ * @param string|null $modelId
+ *
+ * @since 1.21
+ */
+ public function __construct( $modelId = null ) {
+ $this->model_id = $modelId;
+ }
+
+ /**
+ * @see Content::getModel
+ *
+ * @since 1.21
+ */
+ public function getModel() {
+ return $this->model_id;
+ }
+
+ /**
+ * Throws an MWException if $model_id is not the id of the content model
+ * supported by this Content object.
+ *
+ * @since 1.21
+ *
+ * @param string $modelId The model to check
+ *
+ * @throws MWException
+ */
+ protected function checkModelID( $modelId ) {
+ if ( $modelId !== $this->model_id ) {
+ throw new MWException(
+ "Bad content model: " .
+ "expected {$this->model_id} " .
+ "but got $modelId."
+ );
+ }
+ }
+
+ /**
+ * @see Content::getContentHandler
+ *
+ * @since 1.21
+ */
+ public function getContentHandler() {
+ return ContentHandler::getForContent( $this );
+ }
+
+ /**
+ * @see Content::getDefaultFormat
+ *
+ * @since 1.21
+ */
+ public function getDefaultFormat() {
+ return $this->getContentHandler()->getDefaultFormat();
+ }
+
+ /**
+ * @see Content::getSupportedFormats
+ *
+ * @since 1.21
+ */
+ public function getSupportedFormats() {
+ return $this->getContentHandler()->getSupportedFormats();
+ }
+
+ /**
+ * @see Content::isSupportedFormat
+ *
+ * @param string $format
+ *
+ * @since 1.21
+ *
+ * @return boolean
+ */
+ public function isSupportedFormat( $format ) {
+ if ( !$format ) {
+ return true; // this means "use the default"
+ }
+
+ return $this->getContentHandler()->isSupportedFormat( $format );
+ }
+
+ /**
+ * Throws an MWException if $this->isSupportedFormat( $format ) does not
+ * return true.
+ *
+ * @since 1.21
+ *
+ * @param string $format
+ * @throws MWException
+ */
+ protected function checkFormat( $format ) {
+ if ( !$this->isSupportedFormat( $format ) ) {
+ throw new MWException(
+ "Format $format is not supported for content model " .
+ $this->getModel()
+ );
+ }
+ }
+
+ /**
+ * @see Content::serialize
+ *
+ * @param string|null $format
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function serialize( $format = null ) {
+ return $this->getContentHandler()->serializeContent( $this, $format );
+ }
+
+ /**
+ * @see Content::isEmpty
+ *
+ * @since 1.21
+ *
+ * @return boolean
+ */
+ public function isEmpty() {
+ return $this->getSize() === 0;
+ }
+
+ /**
+ * @see Content::isValid
+ *
+ * @since 1.21
+ *
+ * @return boolean
+ */
+ public function isValid() {
+ return true;
+ }
+
+ /**
+ * @see Content::equals
+ *
+ * @since 1.21
+ *
+ * @param Content|null $that
+ *
+ * @return boolean
+ */
+ public function equals( Content $that = null ) {
+ if ( is_null( $that ) ) {
+ return false;
+ }
+
+ if ( $that === $this ) {
+ return true;
+ }
+
+ if ( $that->getModel() !== $this->getModel() ) {
+ return false;
+ }
+
+ return $this->getNativeData() === $that->getNativeData();
+ }
+
+ /**
+ * Returns a list of DataUpdate objects for recording information about this
+ * Content in some secondary data store.
+ *
+ * This default implementation calls
+ * $this->getParserOutput( $content, $title, null, null, false ),
+ * and then calls getSecondaryDataUpdates( $title, $recursive ) on the
+ * resulting ParserOutput object.
+ *
+ * Subclasses may override this to determine the secondary data updates more
+ * efficiently, preferably without the need to generate a parser output object.
+ *
+ * @see Content::getSecondaryDataUpdates()
+ *
+ * @param $title Title The context for determining the necessary updates
+ * @param $old Content|null An optional Content object representing the
+ * previous content, i.e. the content being replaced by this Content
+ * object.
+ * @param $recursive boolean Whether to include recursive updates (default:
+ * false).
+ * @param $parserOutput ParserOutput|null Optional ParserOutput object.
+ * Provide if you have one handy, to avoid re-parsing of the content.
+ *
+ * @return Array. A list of DataUpdate objects for putting information
+ * about this content object somewhere.
+ *
+ * @since 1.21
+ */
+ public function getSecondaryDataUpdates( Title $title,
+ Content $old = null,
+ $recursive = true, ParserOutput $parserOutput = null
+ ) {
+ if ( $parserOutput === null ) {
+ $parserOutput = $this->getParserOutput( $title, null, null, false );
+ }
+
+ return $parserOutput->getSecondaryDataUpdates( $title, $recursive );
+ }
+
+ /**
+ * @see Content::getRedirectChain
+ *
+ * @since 1.21
+ */
+ public function getRedirectChain() {
+ global $wgMaxRedirects;
+ $title = $this->getRedirectTarget();
+ if ( is_null( $title ) ) {
+ return null;
+ }
+ // recursive check to follow double redirects
+ $recurse = $wgMaxRedirects;
+ $titles = array( $title );
+ while ( --$recurse > 0 ) {
+ if ( $title->isRedirect() ) {
+ $page = WikiPage::factory( $title );
+ $newtitle = $page->getRedirectTarget();
+ } else {
+ break;
+ }
+ // Redirects to some special pages are not permitted
+ if ( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) {
+ // The new title passes the checks, so make that our current
+ // title so that further recursion can be checked
+ $title = $newtitle;
+ $titles[] = $newtitle;
+ } else {
+ break;
+ }
+ }
+ return $titles;
+ }
+
+ /**
+ * @see Content::getRedirectTarget
+ *
+ * @since 1.21
+ */
+ public function getRedirectTarget() {
+ return null;
+ }
+
+ /**
+ * @see Content::getUltimateRedirectTarget
+ * @note: migrated here from Title::newFromRedirectRecurse
+ *
+ * @since 1.21
+ */
+ public function getUltimateRedirectTarget() {
+ $titles = $this->getRedirectChain();
+ return $titles ? array_pop( $titles ) : null;
+ }
+
+ /**
+ * @see Content::isRedirect
+ *
+ * @since 1.21
+ *
+ * @return bool
+ */
+ public function isRedirect() {
+ return $this->getRedirectTarget() !== null;
+ }
+
+ /**
+ * @see Content::updateRedirect
+ *
+ * This default implementation always returns $this.
+ *
+ * @param Title $target
+ *
+ * @since 1.21
+ *
+ * @return Content $this
+ */
+ public function updateRedirect( Title $target ) {
+ return $this;
+ }
+
+ /**
+ * @see Content::getSection
+ *
+ * @since 1.21
+ */
+ public function getSection( $sectionId ) {
+ return null;
+ }
+
+ /**
+ * @see Content::replaceSection
+ *
+ * @since 1.21
+ */
+ public function replaceSection( $section, Content $with, $sectionTitle = '' ) {
+ return null;
+ }
+
+ /**
+ * @see Content::preSaveTransform
+ *
+ * @since 1.21
+ */
+ public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
+ return $this;
+ }
+
+ /**
+ * @see Content::addSectionHeader
+ *
+ * @since 1.21
+ */
+ public function addSectionHeader( $header ) {
+ return $this;
+ }
+
+ /**
+ * @see Content::preloadTransform
+ *
+ * @since 1.21
+ */
+ public function preloadTransform( Title $title, ParserOptions $popts ) {
+ return $this;
+ }
+
+ /**
+ * @see Content::prepareSave
+ *
+ * @since 1.21
+ */
+ public function prepareSave( WikiPage $page, $flags, $baseRevId, User $user ) {
+ if ( $this->isValid() ) {
+ return Status::newGood();
+ } else {
+ return Status::newFatal( "invalid-content-data" );
+ }
+ }
+
+ /**
+ * @see Content::getDeletionUpdates
+ *
+ * @since 1.21
+ *
+ * @param $page WikiPage the deleted page
+ * @param $parserOutput null|ParserOutput optional parser output object
+ * for efficient access to meta-information about the content object.
+ * Provide if you have one handy.
+ *
+ * @return array A list of DataUpdate instances that will clean up the
+ * database after deletion.
+ */
+ public function getDeletionUpdates( WikiPage $page,
+ ParserOutput $parserOutput = null )
+ {
+ return array(
+ new LinksDeletionUpdate( $page ),
+ );
+ }
+
+ /**
+ * This default implementation always returns false. Subclasses may override this to supply matching logic.
+ *
+ * @see Content::matchMagicWord
+ *
+ * @since 1.21
+ *
+ * @param MagicWord $word
+ *
+ * @return bool
+ */
+ public function matchMagicWord( MagicWord $word ) {
+ return false;
+ }
+
+ /**
+ * @see Content::convert()
+ *
+ * This base implementation calls the hook ConvertContent to enable custom conversions.
+ * Subclasses may override this to implement conversion for "their" content model.
+ *
+ * @param string $toModel the desired content model, use the CONTENT_MODEL_XXX flags.
+ * @param string $lossy flag, set to "lossy" to allow lossy conversion. If lossy conversion is
+ * not allowed, full round-trip conversion is expected to work without losing information.
+ *
+ * @return Content|bool A content object with the content model $toModel, or false if
+ * that conversion is not supported.
+ */
+ public function convert( $toModel, $lossy = '' ) {
+ if ( $this->getModel() === $toModel ) {
+ //nothing to do, shorten out.
+ return $this;
+ }
+
+ $lossy = ( $lossy === 'lossy' ); // string flag, convert to boolean for convenience
+ $result = false;
+
+ wfRunHooks( 'ConvertContent', array( $this, $toModel, $lossy, &$result ) );
+ return $result;
+ }
+}
diff --git a/includes/content/Content.php b/includes/content/Content.php
new file mode 100644
index 00000000..72729b09
--- /dev/null
+++ b/includes/content/Content.php
@@ -0,0 +1,508 @@
+<?php
+/**
+ * A content object represents page content, e.g. the text to show on a page.
+ * Content objects have no knowledge about how they relate to wiki pages.
+ *
+ * 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
+ *
+ * @since 1.21
+ *
+ * @file
+ * @ingroup Content
+ *
+ * @author Daniel Kinzler
+ */
+
+/**
+ * Base interface for content objects.
+ *
+ * @ingroup Content
+ */
+interface Content {
+
+ /**
+ * @since 1.21
+ *
+ * @return string A string representing the content in a way useful for
+ * building a full text search index. If no useful representation exists,
+ * this method returns an empty string.
+ *
+ * @todo: test that this actually works
+ * @todo: make sure this also works with LuceneSearch / WikiSearch
+ */
+ public function getTextForSearchIndex();
+
+ /**
+ * @since 1.21
+ *
+ * @return string|false The wikitext to include when another page includes this
+ * content, or false if the content is not includable in a wikitext page.
+ *
+ * @todo allow native handling, bypassing wikitext representation, like
+ * for includable special pages.
+ * @todo allow transclusion into other content models than Wikitext!
+ * @todo used in WikiPage and MessageCache to get message text. Not so
+ * nice. What should we use instead?!
+ */
+ public function getWikitextForTransclusion();
+
+ /**
+ * Returns a textual representation of the content suitable for use in edit
+ * summaries and log messages.
+ *
+ * @since 1.21
+ *
+ * @param int $maxLength Maximum length of the summary text
+ * @return string The summary text
+ */
+ public function getTextForSummary( $maxLength = 250 );
+
+ /**
+ * Returns native representation of the data. Interpretation depends on
+ * the data model used, as given by getDataModel().
+ *
+ * @since 1.21
+ *
+ * @return mixed The native representation of the content. Could be a
+ * string, a nested array structure, an object, a binary blob...
+ * anything, really.
+ *
+ * @note Caller must be aware of content model!
+ */
+ public function getNativeData();
+
+ /**
+ * Returns the content's nominal size in bogo-bytes.
+ *
+ * @return int
+ */
+ public function getSize();
+
+ /**
+ * Returns the ID of the content model used by this Content object.
+ * Corresponds to the CONTENT_MODEL_XXX constants.
+ *
+ * @since 1.21
+ *
+ * @return String The model id
+ */
+ public function getModel();
+
+ /**
+ * Convenience method that returns the ContentHandler singleton for handling
+ * the content model that this Content object uses.
+ *
+ * Shorthand for ContentHandler::getForContent( $this )
+ *
+ * @since 1.21
+ *
+ * @return ContentHandler
+ */
+ public function getContentHandler();
+
+ /**
+ * Convenience method that returns the default serialization format for the
+ * content model that this Content object uses.
+ *
+ * Shorthand for $this->getContentHandler()->getDefaultFormat()
+ *
+ * @since 1.21
+ *
+ * @return String
+ */
+ public function getDefaultFormat();
+
+ /**
+ * Convenience method that returns the list of serialization formats
+ * supported for the content model that this Content object uses.
+ *
+ * Shorthand for $this->getContentHandler()->getSupportedFormats()
+ *
+ * @since 1.21
+ *
+ * @return Array of supported serialization formats
+ */
+ public function getSupportedFormats();
+
+ /**
+ * Returns true if $format is a supported serialization format for this
+ * Content object, false if it isn't.
+ *
+ * Note that this should always return true if $format is null, because null
+ * stands for the default serialization.
+ *
+ * Shorthand for $this->getContentHandler()->isSupportedFormat( $format )
+ *
+ * @since 1.21
+ *
+ * @param string $format The format to check
+ * @return bool Whether the format is supported
+ */
+ public function isSupportedFormat( $format );
+
+ /**
+ * Convenience method for serializing this Content object.
+ *
+ * Shorthand for $this->getContentHandler()->serializeContent( $this, $format )
+ *
+ * @since 1.21
+ *
+ * @param $format null|string The desired serialization format (or null for
+ * the default format).
+ * @return string Serialized form of this Content object
+ */
+ public function serialize( $format = null );
+
+ /**
+ * Returns true if this Content object represents empty content.
+ *
+ * @since 1.21
+ *
+ * @return bool Whether this Content object is empty
+ */
+ public function isEmpty();
+
+ /**
+ * Returns whether the content is valid. This is intended for local validity
+ * checks, not considering global consistency.
+ *
+ * Content needs to be valid before it can be saved.
+ *
+ * This default implementation always returns true.
+ *
+ * @since 1.21
+ *
+ * @return boolean
+ */
+ public function isValid();
+
+ /**
+ * Returns true if this Content objects is conceptually equivalent to the
+ * given Content object.
+ *
+ * Contract:
+ *
+ * - Will return false if $that is null.
+ * - Will return true if $that === $this.
+ * - Will return false if $that->getModel() != $this->getModel().
+ * - Will return false if $that->getNativeData() is not equal to $this->getNativeData(),
+ * where the meaning of "equal" depends on the actual data model.
+ *
+ * Implementations should be careful to make equals() transitive and reflexive:
+ *
+ * - $a->equals( $b ) <=> $b->equals( $a )
+ * - $a->equals( $b ) && $b->equals( $c ) ==> $a->equals( $c )
+ *
+ * @since 1.21
+ *
+ * @param $that Content The Content object to compare to
+ * @return bool True if this Content object is equal to $that, false otherwise.
+ */
+ public function equals( Content $that = null );
+
+ /**
+ * Return a copy of this Content object. The following must be true for the
+ * object returned:
+ *
+ * if $copy = $original->copy()
+ *
+ * - get_class($original) === get_class($copy)
+ * - $original->getModel() === $copy->getModel()
+ * - $original->equals( $copy )
+ *
+ * If and only if the Content object is immutable, the copy() method can and
+ * should return $this. That is, $copy === $original may be true, but only
+ * for immutable content objects.
+ *
+ * @since 1.21
+ *
+ * @return Content. A copy of this object
+ */
+ public function copy();
+
+ /**
+ * Returns true if this content is countable as a "real" wiki page, provided
+ * that it's also in a countable location (e.g. a current revision in the
+ * main namespace).
+ *
+ * @since 1.21
+ *
+ * @param bool $hasLinks If it is known whether this content contains
+ * links, provide this information here, to avoid redundant parsing to
+ * find out.
+ * @return boolean
+ */
+ public function isCountable( $hasLinks = null );
+
+ /**
+ * Parse the Content object and generate a ParserOutput from the result.
+ * $result->getText() can be used to obtain the generated HTML. If no HTML
+ * is needed, $generateHtml can be set to false; in that case,
+ * $result->getText() may return null.
+ *
+ * @param $title Title The page title to use as a context for rendering
+ * @param $revId null|int The revision being rendered (optional)
+ * @param $options null|ParserOptions Any parser options
+ * @param $generateHtml Boolean Whether to generate HTML (default: true). If false,
+ * the result of calling getText() on the ParserOutput object returned by
+ * this method is undefined.
+ *
+ * @since 1.21
+ *
+ * @return ParserOutput
+ */
+ public function getParserOutput( Title $title,
+ $revId = null,
+ ParserOptions $options = null, $generateHtml = true );
+ // TODO: make RenderOutput and RenderOptions base classes
+
+ /**
+ * Returns a list of DataUpdate objects for recording information about this
+ * Content in some secondary data store. If the optional second argument,
+ * $old, is given, the updates may model only the changes that need to be
+ * made to replace information about the old content with information about
+ * the new content.
+ *
+ * This default implementation calls
+ * $this->getParserOutput( $content, $title, null, null, false ),
+ * and then calls getSecondaryDataUpdates( $title, $recursive ) on the
+ * resulting ParserOutput object.
+ *
+ * Subclasses may implement this to determine the necessary updates more
+ * efficiently, or make use of information about the old content.
+ *
+ * @param $title Title The context for determining the necessary updates
+ * @param $old Content|null An optional Content object representing the
+ * previous content, i.e. the content being replaced by this Content
+ * object.
+ * @param $recursive boolean Whether to include recursive updates (default:
+ * false).
+ * @param $parserOutput ParserOutput|null Optional ParserOutput object.
+ * Provide if you have one handy, to avoid re-parsing of the content.
+ *
+ * @return Array. A list of DataUpdate objects for putting information
+ * about this content object somewhere.
+ *
+ * @since 1.21
+ */
+ public function getSecondaryDataUpdates( Title $title,
+ Content $old = null,
+ $recursive = true, ParserOutput $parserOutput = null
+ );
+
+ /**
+ * Construct the redirect destination from this content and return an
+ * array of Titles, or null if this content doesn't represent a redirect.
+ * The last element in the array is the final destination after all redirects
+ * have been resolved (up to $wgMaxRedirects times).
+ *
+ * @since 1.21
+ *
+ * @return Array of Titles, with the destination last
+ */
+ public function getRedirectChain();
+
+ /**
+ * Construct the redirect destination from this content and return a Title,
+ * or null if this content doesn't represent a redirect.
+ * This will only return the immediate redirect target, useful for
+ * the redirect table and other checks that don't need full recursion.
+ *
+ * @since 1.21
+ *
+ * @return Title: The corresponding Title
+ */
+ public function getRedirectTarget();
+
+ /**
+ * Construct the redirect destination from this content and return the
+ * Title, or null if this content doesn't represent a redirect.
+ *
+ * This will recurse down $wgMaxRedirects times or until a non-redirect
+ * target is hit in order to provide (hopefully) the Title of the final
+ * destination instead of another redirect.
+ *
+ * There is usually no need to override the default behavior, subclasses that
+ * want to implement redirects should override getRedirectTarget().
+ *
+ * @since 1.21
+ *
+ * @return Title
+ */
+ public function getUltimateRedirectTarget();
+
+ /**
+ * Returns whether this Content represents a redirect.
+ * Shorthand for getRedirectTarget() !== null.
+ *
+ * @since 1.21
+ *
+ * @return bool
+ */
+ public function isRedirect();
+
+ /**
+ * If this Content object is a redirect, this method updates the redirect target.
+ * Otherwise, it does nothing.
+ *
+ * @since 1.21
+ *
+ * @param Title $target the new redirect target
+ *
+ * @return Content a new Content object with the updated redirect (or $this if this Content object isn't a redirect)
+ */
+ public function updateRedirect( Title $target );
+
+ /**
+ * Returns the section with the given ID.
+ *
+ * @since 1.21
+ *
+ * @param string $sectionId The section's ID, given as a numeric string.
+ * The ID "0" retrieves the section before the first heading, "1" the
+ * text between the first heading (included) and the second heading
+ * (excluded), etc.
+ * @return Content|Boolean|null The section, or false if no such section
+ * exist, or null if sections are not supported.
+ */
+ public function getSection( $sectionId );
+
+ /**
+ * Replaces a section of the content and returns a Content object with the
+ * section replaced.
+ *
+ * @since 1.21
+ *
+ * @param $section null/false or a section number (0, 1, 2, T1, T2...), or "new"
+ * @param $with Content: new content of the section
+ * @param string $sectionTitle new section's subject, only if $section is 'new'
+ * @return string Complete article text, or null if error
+ */
+ public function replaceSection( $section, Content $with, $sectionTitle = '' );
+
+ /**
+ * Returns a Content object with pre-save transformations applied (or this
+ * object if no transformations apply).
+ *
+ * @since 1.21
+ *
+ * @param $title Title
+ * @param $user User
+ * @param $parserOptions null|ParserOptions
+ * @return Content
+ */
+ public function preSaveTransform( Title $title, User $user, ParserOptions $parserOptions );
+
+ /**
+ * Returns a new WikitextContent object with the given section heading
+ * prepended, if supported. The default implementation just returns this
+ * Content object unmodified, ignoring the section header.
+ *
+ * @since 1.21
+ *
+ * @param $header string
+ * @return Content
+ */
+ public function addSectionHeader( $header );
+
+ /**
+ * Returns a Content object with preload transformations applied (or this
+ * object if no transformations apply).
+ *
+ * @since 1.21
+ *
+ * @param $title Title
+ * @param $parserOptions null|ParserOptions
+ * @return Content
+ */
+ public function preloadTransform( Title $title, ParserOptions $parserOptions );
+
+ /**
+ * Prepare Content for saving. Called before Content is saved by WikiPage::doEditContent() and in
+ * similar places.
+ *
+ * This may be used to check the content's consistency with global state. This function should
+ * NOT write any information to the database.
+ *
+ * Note that this method will usually be called inside the same transaction bracket that will be used
+ * to save the new revision.
+ *
+ * Note that this method is called before any update to the page table is performed. This means that
+ * $page may not yet know a page ID.
+ *
+ * @since 1.21
+ *
+ * @param WikiPage $page The page to be saved.
+ * @param int $flags bitfield for use with EDIT_XXX constants, see WikiPage::doEditContent()
+ * @param int $baseRevId the ID of the current revision
+ * @param User $user
+ *
+ * @return Status A status object indicating whether the content was successfully prepared for saving.
+ * If the returned status indicates an error, a rollback will be performed and the
+ * transaction aborted.
+ *
+ * @see see WikiPage::doEditContent()
+ */
+ public function prepareSave( WikiPage $page, $flags, $baseRevId, User $user );
+
+ /**
+ * Returns a list of updates to perform when this content is deleted.
+ * The necessary updates may be taken from the Content object, or depend on
+ * the current state of the database.
+ *
+ * @since 1.21
+ *
+ * @param $page WikiPage the deleted page
+ * @param $parserOutput null|ParserOutput optional parser output object
+ * for efficient access to meta-information about the content object.
+ * Provide if you have one handy.
+ *
+ * @return array A list of DataUpdate instances that will clean up the
+ * database after deletion.
+ */
+ public function getDeletionUpdates( WikiPage $page,
+ ParserOutput $parserOutput = null );
+
+ /**
+ * Returns true if this Content object matches the given magic word.
+ *
+ * @since 1.21
+ *
+ * @param MagicWord $word the magic word to match
+ *
+ * @return bool whether this Content object matches the given magic word.
+ */
+ public function matchMagicWord( MagicWord $word );
+
+ /**
+ * Converts this content object into another content object with the given content model,
+ * if that is possible.
+ *
+ * @param string $toModel the desired content model, use the CONTENT_MODEL_XXX flags.
+ * @param string $lossy flag, set to "lossy" to allow lossy conversion. If lossy conversion is
+ * not allowed, full round-trip conversion is expected to work without losing information.
+ *
+ * @return Content|bool A content object with the content model $toModel, or false if
+ * that conversion is not supported.
+ */
+ public function convert( $toModel, $lossy = '' );
+
+ // TODO: ImagePage and CategoryPage interfere with per-content action handlers
+ // TODO: nice&sane integration of GeSHi syntax highlighting
+ // [11:59] <vvv> Hooks are ugly; make CodeHighlighter interface and a
+ // config to set the class which handles syntax highlighting
+ // [12:00] <vvv> And default it to a DummyHighlighter
+}
diff --git a/includes/content/ContentHandler.php b/includes/content/ContentHandler.php
new file mode 100644
index 00000000..9c201955
--- /dev/null
+++ b/includes/content/ContentHandler.php
@@ -0,0 +1,1114 @@
+<?php
+/**
+ * Base class for content handling.
+ *
+ * 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
+ *
+ * @since 1.21
+ *
+ * @file
+ * @ingroup Content
+ *
+ * @author Daniel Kinzler
+ */
+
+/**
+ * Exception representing a failure to serialize or unserialize a content object.
+ *
+ * @ingroup Content
+ */
+class MWContentSerializationException extends MWException {
+
+}
+
+/**
+ * A content handler knows how do deal with a specific type of content on a wiki
+ * page. Content is stored in the database in a serialized form (using a
+ * serialization format a.k.a. MIME type) and is unserialized into its native
+ * PHP representation (the content model), which is wrapped in an instance of
+ * the appropriate subclass of Content.
+ *
+ * ContentHandler instances are stateless singletons that serve, among other
+ * things, as a factory for Content objects. Generally, there is one subclass
+ * of ContentHandler and one subclass of Content for every type of content model.
+ *
+ * Some content types have a flat model, that is, their native representation
+ * is the same as their serialized form. Examples would be JavaScript and CSS
+ * code. As of now, this also applies to wikitext (MediaWiki's default content
+ * type), but wikitext content may be represented by a DOM or AST structure in
+ * the future.
+ *
+ * @ingroup Content
+ */
+abstract class ContentHandler {
+
+ /**
+ * Switch for enabling deprecation warnings. Used by ContentHandler::deprecated()
+ * and ContentHandler::runLegacyHooks().
+ *
+ * Once the ContentHandler code has settled in a bit, this should be set to true to
+ * make extensions etc. show warnings when using deprecated functions and hooks.
+ */
+ protected static $enableDeprecationWarnings = false;
+
+ /**
+ * Convenience function for getting flat text from a Content object. This
+ * should only be used in the context of backwards compatibility with code
+ * that is not yet able to handle Content objects!
+ *
+ * If $content is null, this method returns the empty string.
+ *
+ * If $content is an instance of TextContent, this method returns the flat
+ * text as returned by $content->getNativeData().
+ *
+ * If $content is not a TextContent object, the behavior of this method
+ * depends on the global $wgContentHandlerTextFallback:
+ * - If $wgContentHandlerTextFallback is 'fail' and $content is not a
+ * TextContent object, an MWException is thrown.
+ * - If $wgContentHandlerTextFallback is 'serialize' and $content is not a
+ * TextContent object, $content->serialize() is called to get a string
+ * form of the content.
+ * - If $wgContentHandlerTextFallback is 'ignore' and $content is not a
+ * TextContent object, this method returns null.
+ * - otherwise, the behavior is undefined.
+ *
+ * @since 1.21
+ *
+ * @param $content Content|null
+ * @return null|string the textual form of $content, if available
+ * @throws MWException if $content is not an instance of TextContent and
+ * $wgContentHandlerTextFallback was set to 'fail'.
+ */
+ public static function getContentText( Content $content = null ) {
+ global $wgContentHandlerTextFallback;
+
+ if ( is_null( $content ) ) {
+ return '';
+ }
+
+ if ( $content instanceof TextContent ) {
+ return $content->getNativeData();
+ }
+
+ wfDebugLog( 'ContentHandler', 'Accessing ' . $content->getModel() . ' content as text!' );
+
+ if ( $wgContentHandlerTextFallback == 'fail' ) {
+ throw new MWException(
+ "Attempt to get text from Content with model " .
+ $content->getModel()
+ );
+ }
+
+ if ( $wgContentHandlerTextFallback == 'serialize' ) {
+ return $content->serialize();
+ }
+
+ return null;
+ }
+
+ /**
+ * Convenience function for creating a Content object from a given textual
+ * representation.
+ *
+ * $text will be deserialized into a Content object of the model specified
+ * by $modelId (or, if that is not given, $title->getContentModel()) using
+ * the given format.
+ *
+ * @since 1.21
+ *
+ * @param string $text the textual representation, will be
+ * unserialized to create the Content object
+ * @param $title null|Title the title of the page this text belongs to.
+ * Required if $modelId is not provided.
+ * @param $modelId null|string the model to deserialize to. If not provided,
+ * $title->getContentModel() is used.
+ * @param $format null|string the format to use for deserialization. If not
+ * given, the model's default format is used.
+ *
+ * @throws MWException
+ * @return Content a Content object representing $text
+ *
+ * @throws MWException if $model or $format is not supported or if $text can
+ * not be unserialized using $format.
+ */
+ public static function makeContent( $text, Title $title = null,
+ $modelId = null, $format = null )
+ {
+ if ( is_null( $modelId ) ) {
+ if ( is_null( $title ) ) {
+ throw new MWException( "Must provide a Title object or a content model ID." );
+ }
+
+ $modelId = $title->getContentModel();
+ }
+
+ $handler = ContentHandler::getForModelID( $modelId );
+ return $handler->unserializeContent( $text, $format );
+ }
+
+ /**
+ * Returns the name of the default content model to be used for the page
+ * with the given title.
+ *
+ * Note: There should rarely be need to call this method directly.
+ * To determine the actual content model for a given page, use
+ * Title::getContentModel().
+ *
+ * Which model is to be used by default for the page is determined based
+ * on several factors:
+ * - The global setting $wgNamespaceContentModels specifies a content model
+ * per namespace.
+ * - The hook ContentHandlerDefaultModelFor may be used to override the page's default
+ * model.
+ * - Pages in NS_MEDIAWIKI and NS_USER default to the CSS or JavaScript
+ * model if they end in .js or .css, respectively.
+ * - Pages in NS_MEDIAWIKI default to the wikitext model otherwise.
+ * - The hook TitleIsCssOrJsPage may be used to force a page to use the CSS
+ * or JavaScript model. This is a compatibility feature. The ContentHandlerDefaultModelFor
+ * hook should be used instead if possible.
+ * - The hook TitleIsWikitextPage may be used to force a page to use the
+ * wikitext model. This is a compatibility feature. The ContentHandlerDefaultModelFor
+ * hook should be used instead if possible.
+ *
+ * If none of the above applies, the wikitext model is used.
+ *
+ * Note: this is used by, and may thus not use, Title::getContentModel()
+ *
+ * @since 1.21
+ *
+ * @param $title Title
+ * @return null|string default model name for the page given by $title
+ */
+ public static function getDefaultModelFor( Title $title ) {
+ // NOTE: this method must not rely on $title->getContentModel() directly or indirectly,
+ // because it is used to initialize the mContentModel member.
+
+ $ns = $title->getNamespace();
+
+ $ext = false;
+ $m = null;
+ $model = MWNamespace::getNamespaceContentModel( $ns );
+
+ // Hook can determine default model
+ if ( !wfRunHooks( 'ContentHandlerDefaultModelFor', array( $title, &$model ) ) ) {
+ if ( !is_null( $model ) ) {
+ return $model;
+ }
+ }
+
+ // Could this page contain custom CSS or JavaScript, based on the title?
+ $isCssOrJsPage = NS_MEDIAWIKI == $ns && preg_match( '!\.(css|js)$!u', $title->getText(), $m );
+ if ( $isCssOrJsPage ) {
+ $ext = $m[1];
+ }
+
+ // Hook can force JS/CSS
+ wfRunHooks( 'TitleIsCssOrJsPage', array( $title, &$isCssOrJsPage ) );
+
+ // Is this a .css subpage of a user page?
+ $isJsCssSubpage = NS_USER == $ns
+ && !$isCssOrJsPage
+ && preg_match( "/\\/.*\\.(js|css)$/", $title->getText(), $m );
+ if ( $isJsCssSubpage ) {
+ $ext = $m[1];
+ }
+
+ // Is this wikitext, according to $wgNamespaceContentModels or the DefaultModelFor hook?
+ $isWikitext = is_null( $model ) || $model == CONTENT_MODEL_WIKITEXT;
+ $isWikitext = $isWikitext && !$isCssOrJsPage && !$isJsCssSubpage;
+
+ // Hook can override $isWikitext
+ wfRunHooks( 'TitleIsWikitextPage', array( $title, &$isWikitext ) );
+
+ if ( !$isWikitext ) {
+ switch ( $ext ) {
+ case 'js':
+ return CONTENT_MODEL_JAVASCRIPT;
+ case 'css':
+ return CONTENT_MODEL_CSS;
+ default:
+ return is_null( $model ) ? CONTENT_MODEL_TEXT : $model;
+ }
+ }
+
+ // We established that it must be wikitext
+
+ return CONTENT_MODEL_WIKITEXT;
+ }
+
+ /**
+ * Returns the appropriate ContentHandler singleton for the given title.
+ *
+ * @since 1.21
+ *
+ * @param $title Title
+ * @return ContentHandler
+ */
+ public static function getForTitle( Title $title ) {
+ $modelId = $title->getContentModel();
+ return ContentHandler::getForModelID( $modelId );
+ }
+
+ /**
+ * Returns the appropriate ContentHandler singleton for the given Content
+ * object.
+ *
+ * @since 1.21
+ *
+ * @param $content Content
+ * @return ContentHandler
+ */
+ public static function getForContent( Content $content ) {
+ $modelId = $content->getModel();
+ return ContentHandler::getForModelID( $modelId );
+ }
+
+ /**
+ * @var Array A Cache of ContentHandler instances by model id
+ */
+ static $handlers;
+
+ /**
+ * Returns the ContentHandler singleton for the given model ID. Use the
+ * CONTENT_MODEL_XXX constants to identify the desired content model.
+ *
+ * ContentHandler singletons are taken from the global $wgContentHandlers
+ * array. Keys in that array are model names, the values are either
+ * ContentHandler singleton objects, or strings specifying the appropriate
+ * subclass of ContentHandler.
+ *
+ * If a class name is encountered when looking up the singleton for a given
+ * model name, the class is instantiated and the class name is replaced by
+ * the resulting singleton in $wgContentHandlers.
+ *
+ * If no ContentHandler is defined for the desired $modelId, the
+ * ContentHandler may be provided by the ContentHandlerForModelID hook.
+ * If no ContentHandler can be determined, an MWException is raised.
+ *
+ * @since 1.21
+ *
+ * @param string $modelId The ID of the content model for which to get a
+ * handler. Use CONTENT_MODEL_XXX constants.
+ * @return ContentHandler The ContentHandler singleton for handling the
+ * model given by $modelId
+ * @throws MWException if no handler is known for $modelId.
+ */
+ public static function getForModelID( $modelId ) {
+ global $wgContentHandlers;
+
+ if ( isset( ContentHandler::$handlers[$modelId] ) ) {
+ return ContentHandler::$handlers[$modelId];
+ }
+
+ if ( empty( $wgContentHandlers[$modelId] ) ) {
+ $handler = null;
+
+ wfRunHooks( 'ContentHandlerForModelID', array( $modelId, &$handler ) );
+
+ if ( $handler === null ) {
+ throw new MWException( "No handler for model '$modelId'' registered in \$wgContentHandlers" );
+ }
+
+ if ( !( $handler instanceof ContentHandler ) ) {
+ throw new MWException( "ContentHandlerForModelID must supply a ContentHandler instance" );
+ }
+ } else {
+ $class = $wgContentHandlers[$modelId];
+ $handler = new $class( $modelId );
+
+ if ( !( $handler instanceof ContentHandler ) ) {
+ throw new MWException( "$class from \$wgContentHandlers is not compatible with ContentHandler" );
+ }
+ }
+
+ wfDebugLog( 'ContentHandler', 'Created handler for ' . $modelId
+ . ': ' . get_class( $handler ) );
+
+ ContentHandler::$handlers[$modelId] = $handler;
+ return ContentHandler::$handlers[$modelId];
+ }
+
+ /**
+ * Returns the localized name for a given content model.
+ *
+ * Model names are localized using system messages. Message keys
+ * have the form content-model-$name, where $name is getContentModelName( $id ).
+ *
+ * @param string $name The content model ID, as given by a CONTENT_MODEL_XXX
+ * constant or returned by Revision::getContentModel().
+ *
+ * @return string The content format's localized name.
+ * @throws MWException if the model id isn't known.
+ */
+ public static function getLocalizedName( $name ) {
+ $key = "content-model-$name";
+
+ $msg = wfMessage( $key );
+
+ return $msg->exists() ? $msg->plain() : $name;
+ }
+
+ public static function getContentModels() {
+ global $wgContentHandlers;
+
+ return array_keys( $wgContentHandlers );
+ }
+
+ public static function getAllContentFormats() {
+ global $wgContentHandlers;
+
+ $formats = array();
+
+ foreach ( $wgContentHandlers as $model => $class ) {
+ $handler = ContentHandler::getForModelID( $model );
+ $formats = array_merge( $formats, $handler->getSupportedFormats() );
+ }
+
+ $formats = array_unique( $formats );
+ return $formats;
+ }
+
+ // ------------------------------------------------------------------------
+
+ protected $mModelID;
+ protected $mSupportedFormats;
+
+ /**
+ * Constructor, initializing the ContentHandler instance with its model ID
+ * and a list of supported formats. Values for the parameters are typically
+ * provided as literals by subclass's constructors.
+ *
+ * @param string $modelId (use CONTENT_MODEL_XXX constants).
+ * @param array $formats List for supported serialization formats
+ * (typically as MIME types)
+ */
+ public function __construct( $modelId, $formats ) {
+ $this->mModelID = $modelId;
+ $this->mSupportedFormats = $formats;
+
+ $this->mModelName = preg_replace( '/(Content)?Handler$/', '', get_class( $this ) );
+ $this->mModelName = preg_replace( '/[_\\\\]/', '', $this->mModelName );
+ $this->mModelName = strtolower( $this->mModelName );
+ }
+
+ /**
+ * Serializes a Content object of the type supported by this ContentHandler.
+ *
+ * @since 1.21
+ *
+ * @param $content Content The Content object to serialize
+ * @param $format null|String The desired serialization format
+ * @return string Serialized form of the content
+ */
+ abstract public function serializeContent( Content $content, $format = null );
+
+ /**
+ * Unserializes a Content object of the type supported by this ContentHandler.
+ *
+ * @since 1.21
+ *
+ * @param string $blob serialized form of the content
+ * @param $format null|String the format used for serialization
+ * @return Content the Content object created by deserializing $blob
+ */
+ abstract public function unserializeContent( $blob, $format = null );
+
+ /**
+ * Creates an empty Content object of the type supported by this
+ * ContentHandler.
+ *
+ * @since 1.21
+ *
+ * @return Content
+ */
+ abstract public function makeEmptyContent();
+
+ /**
+ * Creates a new Content object that acts as a redirect to the given page,
+ * or null of redirects are not supported by this content model.
+ *
+ * This default implementation always returns null. Subclasses supporting redirects
+ * must override this method.
+ *
+ * Note that subclasses that override this method to return a Content object
+ * should also override supportsRedirects() to return true.
+ *
+ * @since 1.21
+ *
+ * @param Title $destination the page to redirect to.
+ *
+ * @return Content
+ */
+ public function makeRedirectContent( Title $destination ) {
+ return null;
+ }
+
+ /**
+ * Returns the model id that identifies the content model this
+ * ContentHandler can handle. Use with the CONTENT_MODEL_XXX constants.
+ *
+ * @since 1.21
+ *
+ * @return String The model ID
+ */
+ public function getModelID() {
+ return $this->mModelID;
+ }
+
+ /**
+ * Throws an MWException if $model_id is not the ID of the content model
+ * supported by this ContentHandler.
+ *
+ * @since 1.21
+ *
+ * @param string $model_id The model to check
+ *
+ * @throws MWException
+ */
+ protected function checkModelID( $model_id ) {
+ if ( $model_id !== $this->mModelID ) {
+ throw new MWException( "Bad content model: " .
+ "expected {$this->mModelID} " .
+ "but got $model_id." );
+ }
+ }
+
+ /**
+ * Returns a list of serialization formats supported by the
+ * serializeContent() and unserializeContent() methods of this
+ * ContentHandler.
+ *
+ * @since 1.21
+ *
+ * @return array of serialization formats as MIME type like strings
+ */
+ public function getSupportedFormats() {
+ return $this->mSupportedFormats;
+ }
+
+ /**
+ * The format used for serialization/deserialization by default by this
+ * ContentHandler.
+ *
+ * This default implementation will return the first element of the array
+ * of formats that was passed to the constructor.
+ *
+ * @since 1.21
+ *
+ * @return string the name of the default serialization format as a MIME type
+ */
+ public function getDefaultFormat() {
+ return $this->mSupportedFormats[0];
+ }
+
+ /**
+ * Returns true if $format is a serialization format supported by this
+ * ContentHandler, and false otherwise.
+ *
+ * Note that if $format is null, this method always returns true, because
+ * null means "use the default format".
+ *
+ * @since 1.21
+ *
+ * @param string $format the serialization format to check
+ * @return bool
+ */
+ public function isSupportedFormat( $format ) {
+
+ if ( !$format ) {
+ return true; // this means "use the default"
+ }
+
+ return in_array( $format, $this->mSupportedFormats );
+ }
+
+ /**
+ * Throws an MWException if isSupportedFormat( $format ) is not true.
+ * Convenient for checking whether a format provided as a parameter is
+ * actually supported.
+ *
+ * @param string $format the serialization format to check
+ *
+ * @throws MWException
+ */
+ protected function checkFormat( $format ) {
+ if ( !$this->isSupportedFormat( $format ) ) {
+ throw new MWException(
+ "Format $format is not supported for content model "
+ . $this->getModelID()
+ );
+ }
+ }
+
+ /**
+ * Returns overrides for action handlers.
+ * Classes listed here will be used instead of the default one when
+ * (and only when) $wgActions[$action] === true. This allows subclasses
+ * to override the default action handlers.
+ *
+ * @since 1.21
+ *
+ * @return Array
+ */
+ public function getActionOverrides() {
+ return array();
+ }
+
+ /**
+ * Factory for creating an appropriate DifferenceEngine for this content model.
+ *
+ * @since 1.21
+ *
+ * @param $context IContextSource context to use, anything else will be
+ * ignored
+ * @param $old Integer Old ID we want to show and diff with.
+ * @param int|string $new String either 'prev' or 'next'.
+ * @param $rcid Integer ??? FIXME (default 0)
+ * @param $refreshCache boolean If set, refreshes the diff cache
+ * @param $unhide boolean If set, allow viewing deleted revs
+ *
+ * @return DifferenceEngine
+ */
+ public function createDifferenceEngine( IContextSource $context,
+ $old = 0, $new = 0,
+ $rcid = 0, # FIXME: use everywhere!
+ $refreshCache = false, $unhide = false
+ ) {
+ $diffEngineClass = $this->getDiffEngineClass();
+
+ return new $diffEngineClass( $context, $old, $new, $rcid, $refreshCache, $unhide );
+ }
+
+ /**
+ * Get the language in which the content of the given page is written.
+ *
+ * This default implementation just returns $wgContLang (except for pages in the MediaWiki namespace)
+ *
+ * Note that the pages language is not cacheable, since it may in some cases depend on user settings.
+ *
+ * Also note that the page language may or may not depend on the actual content of the page,
+ * that is, this method may load the content in order to determine the language.
+ *
+ * @since 1.21
+ *
+ * @param Title $title the page to determine the language for.
+ * @param Content|null $content the page's content, if you have it handy, to avoid reloading it.
+ *
+ * @return Language the page's language
+ */
+ public function getPageLanguage( Title $title, Content $content = null ) {
+ global $wgContLang, $wgLang;
+ $pageLang = $wgContLang;
+
+ if ( $title->getNamespace() == NS_MEDIAWIKI ) {
+ // Parse mediawiki messages with correct target language
+ list( /* $unused */, $lang ) = MessageCache::singleton()->figureMessage( $title->getText() );
+ $pageLang = wfGetLangObj( $lang );
+ }
+
+ wfRunHooks( 'PageContentLanguage', array( $title, &$pageLang, $wgLang ) );
+ return wfGetLangObj( $pageLang );
+ }
+
+ /**
+ * Get the language in which the content of this page is written when
+ * viewed by user. Defaults to $this->getPageLanguage(), but if the user
+ * specified a preferred variant, the variant will be used.
+ *
+ * This default implementation just returns $this->getPageLanguage( $title, $content ) unless
+ * the user specified a preferred variant.
+ *
+ * Note that the pages view language is not cacheable, since it depends on user settings.
+ *
+ * Also note that the page language may or may not depend on the actual content of the page,
+ * that is, this method may load the content in order to determine the language.
+ *
+ * @since 1.21
+ *
+ * @param Title $title the page to determine the language for.
+ * @param Content|null $content the page's content, if you have it handy, to avoid reloading it.
+ *
+ * @return Language the page's language for viewing
+ */
+ public function getPageViewLanguage( Title $title, Content $content = null ) {
+ $pageLang = $this->getPageLanguage( $title, $content );
+
+ if ( $title->getNamespace() !== NS_MEDIAWIKI ) {
+ // If the user chooses a variant, the content is actually
+ // in a language whose code is the variant code.
+ $variant = $pageLang->getPreferredVariant();
+ if ( $pageLang->getCode() !== $variant ) {
+ $pageLang = Language::factory( $variant );
+ }
+ }
+
+ return $pageLang;
+ }
+
+ /**
+ * Determines whether the content type handled by this ContentHandler
+ * can be used on the given page.
+ *
+ * This default implementation always returns true.
+ * Subclasses may override this to restrict the use of this content model to specific locations,
+ * typically based on the namespace or some other aspect of the title, such as a special suffix
+ * (e.g. ".svg" for SVG content).
+ *
+ * @param Title $title the page's title.
+ *
+ * @return bool true if content of this kind can be used on the given page, false otherwise.
+ */
+ public function canBeUsedOn( Title $title ) {
+ return true;
+ }
+
+ /**
+ * Returns the name of the diff engine to use.
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ protected function getDiffEngineClass() {
+ return 'DifferenceEngine';
+ }
+
+ /**
+ * Attempts to merge differences between three versions.
+ * Returns a new Content object for a clean merge and false for failure or
+ * a conflict.
+ *
+ * This default implementation always returns false.
+ *
+ * @since 1.21
+ *
+ * @param $oldContent Content|string String
+ * @param $myContent Content|string String
+ * @param $yourContent Content|string String
+ *
+ * @return Content|Bool
+ */
+ public function merge3( Content $oldContent, Content $myContent, Content $yourContent ) {
+ return false;
+ }
+
+ /**
+ * Return an applicable auto-summary if one exists for the given edit.
+ *
+ * @since 1.21
+ *
+ * @param $oldContent Content|null: the previous text of the page.
+ * @param $newContent Content|null: The submitted text of the page.
+ * @param int $flags Bit mask: a bit mask of flags submitted for the edit.
+ *
+ * @return string An appropriate auto-summary, or an empty string.
+ */
+ public function getAutosummary( Content $oldContent = null, Content $newContent = null, $flags ) {
+ // Decide what kind of auto-summary is needed.
+
+ // Redirect auto-summaries
+
+ /**
+ * @var $ot Title
+ * @var $rt Title
+ */
+
+ $ot = !is_null( $oldContent ) ? $oldContent->getRedirectTarget() : null;
+ $rt = !is_null( $newContent ) ? $newContent->getRedirectTarget() : null;
+
+ if ( is_object( $rt ) ) {
+ if ( !is_object( $ot )
+ || !$rt->equals( $ot )
+ || $ot->getFragment() != $rt->getFragment() )
+ {
+ $truncatedtext = $newContent->getTextForSummary(
+ 250
+ - strlen( wfMessage( 'autoredircomment' )->inContentLanguage()->text() )
+ - strlen( $rt->getFullText() ) );
+
+ return wfMessage( 'autoredircomment', $rt->getFullText() )
+ ->rawParams( $truncatedtext )->inContentLanguage()->text();
+ }
+ }
+
+ // New page auto-summaries
+ if ( $flags & EDIT_NEW && $newContent->getSize() > 0 ) {
+ // If they're making a new article, give its text, truncated, in
+ // the summary.
+
+ $truncatedtext = $newContent->getTextForSummary(
+ 200 - strlen( wfMessage( 'autosumm-new' )->inContentLanguage()->text() ) );
+
+ return wfMessage( 'autosumm-new' )->rawParams( $truncatedtext )
+ ->inContentLanguage()->text();
+ }
+
+ // Blanking auto-summaries
+ if ( !empty( $oldContent ) && $oldContent->getSize() > 0 && $newContent->getSize() == 0 ) {
+ return wfMessage( 'autosumm-blank' )->inContentLanguage()->text();
+ } elseif ( !empty( $oldContent )
+ && $oldContent->getSize() > 10 * $newContent->getSize()
+ && $newContent->getSize() < 500 )
+ {
+ // Removing more than 90% of the article
+
+ $truncatedtext = $newContent->getTextForSummary(
+ 200 - strlen( wfMessage( 'autosumm-replace' )->inContentLanguage()->text() ) );
+
+ return wfMessage( 'autosumm-replace' )->rawParams( $truncatedtext )
+ ->inContentLanguage()->text();
+ }
+
+ // If we reach this point, there's no applicable auto-summary for our
+ // case, so our auto-summary is empty.
+ return '';
+ }
+
+ /**
+ * Auto-generates a deletion reason
+ *
+ * @since 1.21
+ *
+ * @param $title Title: the page's title
+ * @param &$hasHistory Boolean: whether the page has a history
+ * @return mixed String containing deletion reason or empty string, or
+ * boolean false if no revision occurred
+ *
+ * @XXX &$hasHistory is extremely ugly, it's here because
+ * WikiPage::getAutoDeleteReason() and Article::generateReason()
+ * have it / want it.
+ */
+ public function getAutoDeleteReason( Title $title, &$hasHistory ) {
+ $dbw = wfGetDB( DB_MASTER );
+
+ // Get the last revision
+ $rev = Revision::newFromTitle( $title );
+
+ if ( is_null( $rev ) ) {
+ return false;
+ }
+
+ // Get the article's contents
+ $content = $rev->getContent();
+ $blank = false;
+
+ // If the page is blank, use the text from the previous revision,
+ // which can only be blank if there's a move/import/protect dummy
+ // revision involved
+ if ( !$content || $content->isEmpty() ) {
+ $prev = $rev->getPrevious();
+
+ if ( $prev ) {
+ $rev = $prev;
+ $content = $rev->getContent();
+ $blank = true;
+ }
+ }
+
+ $this->checkModelID( $rev->getContentModel() );
+
+ // Find out if there was only one contributor
+ // Only scan the last 20 revisions
+ $res = $dbw->select( 'revision', 'rev_user_text',
+ array(
+ 'rev_page' => $title->getArticleID(),
+ $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0'
+ ),
+ __METHOD__,
+ array( 'LIMIT' => 20 )
+ );
+
+ if ( $res === false ) {
+ // This page has no revisions, which is very weird
+ return false;
+ }
+
+ $hasHistory = ( $res->numRows() > 1 );
+ $row = $dbw->fetchObject( $res );
+
+ if ( $row ) { // $row is false if the only contributor is hidden
+ $onlyAuthor = $row->rev_user_text;
+ // Try to find a second contributor
+ foreach ( $res as $row ) {
+ if ( $row->rev_user_text != $onlyAuthor ) { // Bug 22999
+ $onlyAuthor = false;
+ break;
+ }
+ }
+ } else {
+ $onlyAuthor = false;
+ }
+
+ // Generate the summary with a '$1' placeholder
+ if ( $blank ) {
+ // The current revision is blank and the one before is also
+ // blank. It's just not our lucky day
+ $reason = wfMessage( 'exbeforeblank', '$1' )->inContentLanguage()->text();
+ } else {
+ if ( $onlyAuthor ) {
+ $reason = wfMessage(
+ 'excontentauthor',
+ '$1',
+ $onlyAuthor
+ )->inContentLanguage()->text();
+ } else {
+ $reason = wfMessage( 'excontent', '$1' )->inContentLanguage()->text();
+ }
+ }
+
+ if ( $reason == '-' ) {
+ // Allow these UI messages to be blanked out cleanly
+ return '';
+ }
+
+ // Max content length = max comment length - length of the comment (excl. $1)
+ $text = $content ? $content->getTextForSummary( 255 - ( strlen( $reason ) - 2 ) ) : '';
+
+ // Now replace the '$1' placeholder
+ $reason = str_replace( '$1', $text, $reason );
+
+ return $reason;
+ }
+
+ /**
+ * Get the Content object that needs to be saved in order to undo all revisions
+ * between $undo and $undoafter. Revisions must belong to the same page,
+ * must exist and must not be deleted.
+ *
+ * @since 1.21
+ *
+ * @param $current Revision The current text
+ * @param $undo Revision The revision to undo
+ * @param $undoafter Revision Must be an earlier revision than $undo
+ *
+ * @return mixed String on success, false on failure
+ */
+ public function getUndoContent( Revision $current, Revision $undo, Revision $undoafter ) {
+ $cur_content = $current->getContent();
+
+ if ( empty( $cur_content ) ) {
+ return false; // no page
+ }
+
+ $undo_content = $undo->getContent();
+ $undoafter_content = $undoafter->getContent();
+
+ $this->checkModelID( $cur_content->getModel() );
+ $this->checkModelID( $undo_content->getModel() );
+ $this->checkModelID( $undoafter_content->getModel() );
+
+ if ( $cur_content->equals( $undo_content ) ) {
+ // No use doing a merge if it's just a straight revert.
+ return $undoafter_content;
+ }
+
+ $undone_content = $this->merge3( $undo_content, $undoafter_content, $cur_content );
+
+ return $undone_content;
+ }
+
+ /**
+ * Get parser options suitable for rendering the primary article wikitext
+ *
+ * @param IContextSource|User|string $context One of the following:
+ * - IContextSource: Use the User and the Language of the provided
+ * context
+ * - User: Use the provided User object and $wgLang for the language,
+ * so use an IContextSource object if possible.
+ * - 'canonical': Canonical options (anonymous user with default
+ * preferences and content language).
+ *
+ * @param IContextSource|User|string $context
+ *
+ * @throws MWException
+ * @return ParserOptions
+ */
+ public function makeParserOptions( $context ) {
+ global $wgContLang;
+
+ if ( $context instanceof IContextSource ) {
+ $options = ParserOptions::newFromContext( $context );
+ } elseif ( $context instanceof User ) { // settings per user (even anons)
+ $options = ParserOptions::newFromUser( $context );
+ } elseif ( $context === 'canonical' ) { // canonical settings
+ $options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
+ } else {
+ throw new MWException( "Bad context for parser options: $context" );
+ }
+
+ $options->enableLimitReport(); // show inclusion/loop reports
+ $options->setTidy( true ); // fix bad HTML
+
+ return $options;
+ }
+
+ /**
+ * Returns true for content models that support caching using the
+ * ParserCache mechanism. See WikiPage::isParserCacheUsed().
+ *
+ * @since 1.21
+ *
+ * @return bool
+ */
+ public function isParserCacheSupported() {
+ return false;
+ }
+
+ /**
+ * Returns true if this content model supports sections.
+ * This default implementation returns false.
+ *
+ * Content models that return true here should also implement
+ * Content::getSection, Content::replaceSection, etc. to handle sections..
+ *
+ * @return boolean whether sections are supported.
+ */
+ public function supportsSections() {
+ return false;
+ }
+
+ /**
+ * Returns true if this content model supports redirects.
+ * This default implementation returns false.
+ *
+ * Content models that return true here should also implement
+ * ContentHandler::makeRedirectContent to return a Content object.
+ *
+ * @return boolean whether redirects are supported.
+ */
+ public function supportsRedirects() {
+ return false;
+ }
+
+ /**
+ * Logs a deprecation warning, visible if $wgDevelopmentWarnings, but only if
+ * self::$enableDeprecationWarnings is set to true.
+ *
+ * @param string $func The name of the deprecated function
+ * @param string $version The version since the method is deprecated. Usually 1.21
+ * for ContentHandler related stuff.
+ * @param string|bool $component: Component to which the function belongs.
+ * If false, it is assumed the function is in MediaWiki core.
+ *
+ * @see ContentHandler::$enableDeprecationWarnings
+ * @see wfDeprecated
+ */
+ public static function deprecated( $func, $version, $component = false ) {
+ if ( self::$enableDeprecationWarnings ) {
+ wfDeprecated( $func, $version, $component, 3 );
+ }
+ }
+
+ /**
+ * Call a legacy hook that uses text instead of Content objects.
+ * Will log a warning when a matching hook function is registered.
+ * If the textual representation of the content is changed by the
+ * hook function, a new Content object is constructed from the new
+ * text.
+ *
+ * @param string $event event name
+ * @param array $args parameters passed to hook functions
+ * @param bool $warn whether to log a warning.
+ * Default to self::$enableDeprecationWarnings.
+ * May be set to false for testing.
+ *
+ * @return Boolean True if no handler aborted the hook
+ *
+ * @see ContentHandler::$enableDeprecationWarnings
+ */
+ public static function runLegacyHooks( $event, $args = array(),
+ $warn = null ) {
+
+ if ( $warn === null ) {
+ $warn = self::$enableDeprecationWarnings;
+ }
+
+ if ( !Hooks::isRegistered( $event ) ) {
+ return true; // nothing to do here
+ }
+
+ if ( $warn ) {
+ // Log information about which handlers are registered for the legacy hook,
+ // so we can find and fix them.
+
+ $handlers = Hooks::getHandlers( $event );
+ $handlerInfo = array();
+
+ wfSuppressWarnings();
+
+ foreach ( $handlers as $handler ) {
+ if ( is_array( $handler ) ) {
+ if ( is_object( $handler[0] ) ) {
+ $info = get_class( $handler[0] );
+ } else {
+ $info = $handler[0];
+ }
+
+ if ( isset( $handler[1] ) ) {
+ $info .= '::' . $handler[1];
+ }
+ } else if ( is_object( $handler ) ) {
+ $info = get_class( $handler[0] );
+ $info .= '::on' . $event;
+ } else {
+ $info = $handler;
+ }
+
+ $handlerInfo[] = $info;
+ }
+
+ wfRestoreWarnings();
+
+ wfWarn( "Using obsolete hook $event via ContentHandler::runLegacyHooks()! Handlers: " . implode( ', ', $handlerInfo ), 2 );
+ }
+
+ // convert Content objects to text
+ $contentObjects = array();
+ $contentTexts = array();
+
+ foreach ( $args as $k => $v ) {
+ if ( $v instanceof Content ) {
+ /* @var Content $v */
+
+ $contentObjects[$k] = $v;
+
+ $v = $v->serialize();
+ $contentTexts[ $k ] = $v;
+ $args[ $k ] = $v;
+ }
+ }
+
+ // call the hook functions
+ $ok = wfRunHooks( $event, $args );
+
+ // see if the hook changed the text
+ foreach ( $contentTexts as $k => $orig ) {
+ /* @var Content $content */
+
+ $modified = $args[ $k ];
+ $content = $contentObjects[$k];
+
+ if ( $modified !== $orig ) {
+ // text was changed, create updated Content object
+ $content = $content->getContentHandler()->unserializeContent( $modified );
+ }
+
+ $args[ $k ] = $content;
+ }
+
+ return $ok;
+ }
+}
diff --git a/includes/content/CssContent.php b/includes/content/CssContent.php
new file mode 100644
index 00000000..569d122d
--- /dev/null
+++ b/includes/content/CssContent.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * Content object for CSS pages.
+ *
+ * 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
+ *
+ * @since 1.21
+ *
+ * @file
+ * @ingroup Content
+ *
+ * @author Daniel Kinzler
+ */
+
+/**
+ * Content object for CSS pages.
+ *
+ * @ingroup Content
+ */
+class CssContent extends TextContent {
+ public function __construct( $text ) {
+ parent::__construct( $text, CONTENT_MODEL_CSS );
+ }
+
+ /**
+ * Returns a Content object with pre-save transformations applied using
+ * Parser::preSaveTransform().
+ *
+ * @param $title Title
+ * @param $user User
+ * @param $popts ParserOptions
+ * @return Content
+ */
+ public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
+ global $wgParser;
+ // @todo: make pre-save transformation optional for script pages
+
+ $text = $this->getNativeData();
+ $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts );
+
+ return new CssContent( $pst );
+ }
+
+ protected function getHtml() {
+ $html = "";
+ $html .= "<pre class=\"mw-code mw-css\" dir=\"ltr\">\n";
+ $html .= $this->getHighlightHtml();
+ $html .= "\n</pre>\n";
+
+ return $html;
+ }
+}
diff --git a/includes/content/CssContentHandler.php b/includes/content/CssContentHandler.php
new file mode 100644
index 00000000..cb5a349d
--- /dev/null
+++ b/includes/content/CssContentHandler.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Content handler for CSS pages.
+ *
+ * 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 Content
+ */
+
+/**
+ * Content handler for CSS pages.
+ *
+ * @since 1.21
+ * @ingroup Content
+ */
+class CssContentHandler extends TextContentHandler {
+
+ public function __construct( $modelId = CONTENT_MODEL_CSS ) {
+ parent::__construct( $modelId, array( CONTENT_FORMAT_CSS ) );
+ }
+
+ public function unserializeContent( $text, $format = null ) {
+ $this->checkFormat( $format );
+
+ return new CssContent( $text );
+ }
+
+ public function makeEmptyContent() {
+ return new CssContent( '' );
+ }
+
+ /**
+ * Returns the english language, because CSS is english, and should be handled as such.
+ *
+ * @return Language wfGetLangObj( 'en' )
+ *
+ * @see ContentHandler::getPageLanguage()
+ */
+ public function getPageLanguage( Title $title, Content $content = null ) {
+ return wfGetLangObj( 'en' );
+ }
+
+ /**
+ * Returns the english language, because CSS is english, and should be handled as such.
+ *
+ * @return Language wfGetLangObj( 'en' )
+ *
+ * @see ContentHandler::getPageViewLanguage()
+ */
+ public function getPageViewLanguage( Title $title, Content $content = null ) {
+ return wfGetLangObj( 'en' );
+ }
+}
diff --git a/includes/content/JavaScriptContent.php b/includes/content/JavaScriptContent.php
new file mode 100644
index 00000000..9cd947f9
--- /dev/null
+++ b/includes/content/JavaScriptContent.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * Content for JavaScript pages.
+ *
+ * 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
+ *
+ * @since 1.21
+ *
+ * @file
+ * @ingroup Content
+ *
+ * @author Daniel Kinzler
+ */
+
+/**
+ * Content for JavaScript pages.
+ *
+ * @ingroup Content
+ */
+class JavaScriptContent extends TextContent {
+ public function __construct( $text ) {
+ parent::__construct( $text, CONTENT_MODEL_JAVASCRIPT );
+ }
+
+ /**
+ * Returns a Content object with pre-save transformations applied using
+ * Parser::preSaveTransform().
+ *
+ * @param Title $title
+ * @param User $user
+ * @param ParserOptions $popts
+ * @return Content
+ */
+ public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
+ global $wgParser;
+ // @todo: make pre-save transformation optional for script pages
+ // See bug #32858
+
+ $text = $this->getNativeData();
+ $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts );
+
+ return new JavaScriptContent( $pst );
+ }
+
+ protected function getHtml() {
+ $html = "";
+ $html .= "<pre class=\"mw-code mw-js\" dir=\"ltr\">\n";
+ $html .= $this->getHighlightHtml();
+ $html .= "\n</pre>\n";
+
+ return $html;
+ }
+}
diff --git a/includes/content/JavaScriptContentHandler.php b/includes/content/JavaScriptContentHandler.php
new file mode 100644
index 00000000..33fa9172
--- /dev/null
+++ b/includes/content/JavaScriptContentHandler.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Content handler for JavaScript pages.
+ *
+ * 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
+ */
+
+/**
+ * Content handler for JavaScript pages.
+ *
+ * @since 1.21
+ * @ingroup Content
+ * @todo make ScriptContentHandler base class, do highlighting stuff there?
+ */
+class JavaScriptContentHandler extends TextContentHandler {
+
+ public function __construct( $modelId = CONTENT_MODEL_JAVASCRIPT ) {
+ parent::__construct( $modelId, array( CONTENT_FORMAT_JAVASCRIPT ) );
+ }
+
+ public function unserializeContent( $text, $format = null ) {
+ $this->checkFormat( $format );
+
+ return new JavaScriptContent( $text );
+ }
+
+ public function makeEmptyContent() {
+ return new JavaScriptContent( '' );
+ }
+
+ /**
+ * Returns the english language, because JS is english, and should be handled as such.
+ *
+ * @return Language wfGetLangObj( 'en' )
+ *
+ * @see ContentHandler::getPageLanguage()
+ */
+ public function getPageLanguage( Title $title, Content $content = null ) {
+ return wfGetLangObj( 'en' );
+ }
+
+ /**
+ * Returns the english language, because JS is english, and should be handled as such.
+ *
+ * @return Language wfGetLangObj( 'en' )
+ *
+ * @see ContentHandler::getPageViewLanguage()
+ */
+ public function getPageViewLanguage( Title $title, Content $content = null ) {
+ return wfGetLangObj( 'en' );
+ }
+}
diff --git a/includes/content/MessageContent.php b/includes/content/MessageContent.php
new file mode 100644
index 00000000..b36b670c
--- /dev/null
+++ b/includes/content/MessageContent.php
@@ -0,0 +1,158 @@
+<?php
+/**
+ * Wrapper content object allowing to handle a system message as a Content object.
+ *
+ * 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
+ *
+ * @since 1.21
+ *
+ * @file
+ * @ingroup Content
+ *
+ * @author Daniel Kinzler
+ */
+
+/**
+ * Wrapper allowing us to handle a system message as a Content object.
+ * Note that this is generally *not* used to represent content from the
+ * MediaWiki namespace, and that there is no MessageContentHandler.
+ * MessageContent is just intended as glue for wrapping a message programatically.
+ *
+ * @ingroup Content
+ */
+class MessageContent extends AbstractContent {
+
+ /**
+ * @var Message
+ */
+ protected $mMessage;
+
+ /**
+ * @param Message|String $msg A Message object, or a message key
+ * @param array|null $params An optional array of message parameters
+ */
+ public function __construct( $msg, $params = null ) {
+ # XXX: messages may be wikitext, html or plain text! and maybe even something else entirely.
+ parent::__construct( CONTENT_MODEL_WIKITEXT );
+
+ if ( is_string( $msg ) ) {
+ $this->mMessage = wfMessage( $msg );
+ } else {
+ $this->mMessage = clone $msg;
+ }
+
+ if ( $params ) {
+ $this->mMessage = $this->mMessage->params( $params );
+ }
+ }
+
+ /**
+ * Returns the message as rendered HTML
+ *
+ * @return string The message text, parsed into html
+ */
+ public function getHtml() {
+ return $this->mMessage->parse();
+ }
+
+ /**
+ * Returns the message as rendered HTML
+ *
+ * @return string The message text, parsed into html
+ */
+ public function getWikitext() {
+ return $this->mMessage->text();
+ }
+
+ /**
+ * Returns the message object, with any parameters already substituted.
+ *
+ * @return Message The message object.
+ */
+ public function getNativeData() {
+ //NOTE: Message objects are mutable. Cloning here makes MessageContent immutable.
+ return clone $this->mMessage;
+ }
+
+ /**
+ * @see Content::getTextForSearchIndex
+ */
+ public function getTextForSearchIndex() {
+ return $this->mMessage->plain();
+ }
+
+ /**
+ * @see Content::getWikitextForTransclusion
+ */
+ public function getWikitextForTransclusion() {
+ return $this->getWikitext();
+ }
+
+ /**
+ * @see Content::getTextForSummary
+ */
+ public function getTextForSummary( $maxlength = 250 ) {
+ return substr( $this->mMessage->plain(), 0, $maxlength );
+ }
+
+ /**
+ * @see Content::getSize
+ *
+ * @return int
+ */
+ public function getSize() {
+ return strlen( $this->mMessage->plain() );
+ }
+
+ /**
+ * @see Content::copy
+ *
+ * @return Content. A copy of this object
+ */
+ public function copy() {
+ // MessageContent is immutable (because getNativeData() returns a clone of the Message object)
+ return $this;
+ }
+
+ /**
+ * @see Content::isCountable
+ *
+ * @return bool false
+ */
+ public function isCountable( $hasLinks = null ) {
+ return false;
+ }
+
+ /**
+ * @see Content::getParserOutput
+ *
+ * @return ParserOutput
+ */
+ public function getParserOutput(
+ Title $title, $revId = null,
+ ParserOptions $options = null, $generateHtml = true
+ ) {
+
+ if ( $generateHtml ) {
+ $html = $this->getHtml();
+ } else {
+ $html = '';
+ }
+
+ $po = new ParserOutput( $html );
+ return $po;
+ }
+}
diff --git a/includes/content/TextContent.php b/includes/content/TextContent.php
new file mode 100644
index 00000000..8fafcb63
--- /dev/null
+++ b/includes/content/TextContent.php
@@ -0,0 +1,286 @@
+<?php
+/**
+ * Content object implementation for representing flat text.
+ *
+ * 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
+ *
+ * @since 1.21
+ *
+ * @file
+ * @ingroup Content
+ *
+ * @author Daniel Kinzler
+ */
+
+/**
+ * Content object implementation for representing flat text.
+ *
+ * TextContent instances are immutable
+ *
+ * @ingroup Content
+ */
+class TextContent extends AbstractContent {
+
+ public function __construct( $text, $model_id = CONTENT_MODEL_TEXT ) {
+ parent::__construct( $model_id );
+
+ if ( $text === null || $text === false ) {
+ wfWarn( "TextContent constructed with \$text = " . var_export( $text, true ) . "! "
+ . "This may indicate an error in the caller's scope." );
+
+ $text = '';
+ }
+
+ if ( !is_string( $text ) ) {
+ throw new MWException( "TextContent expects a string in the constructor." );
+ }
+
+ $this->mText = $text;
+ }
+
+ public function copy() {
+ return $this; # NOTE: this is ok since TextContent are immutable.
+ }
+
+ public function getTextForSummary( $maxlength = 250 ) {
+ global $wgContLang;
+
+ $text = $this->getNativeData();
+
+ $truncatedtext = $wgContLang->truncate(
+ preg_replace( "/[\n\r]/", ' ', $text ),
+ max( 0, $maxlength ) );
+
+ return $truncatedtext;
+ }
+
+ /**
+ * returns the text's size in bytes.
+ *
+ * @return int The size
+ */
+ public function getSize() {
+ $text = $this->getNativeData();
+ return strlen( $text );
+ }
+
+ /**
+ * Returns true if this content is not a redirect, and $wgArticleCountMethod
+ * is "any".
+ *
+ * @param bool $hasLinks if it is known whether this content contains links,
+ * provide this information here, to avoid redundant parsing to find out.
+ *
+ * @return bool True if the content is countable
+ */
+ public function isCountable( $hasLinks = null ) {
+ global $wgArticleCountMethod;
+
+ if ( $this->isRedirect() ) {
+ return false;
+ }
+
+ if ( $wgArticleCountMethod === 'any' ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the text represented by this Content object, as a string.
+ *
+ * @return string: the raw text
+ */
+ public function getNativeData() {
+ $text = $this->mText;
+ return $text;
+ }
+
+ /**
+ * Returns the text represented by this Content object, as a string.
+ *
+ * @return string: the raw text
+ */
+ public function getTextForSearchIndex() {
+ return $this->getNativeData();
+ }
+
+ /**
+ * Returns attempts to convert this content object to wikitext,
+ * and then returns the text string. The conversion may be lossy.
+ *
+ * @note: this allows any text-based content to be transcluded as if it was wikitext.
+ *
+ * @return string|false: the raw text, or null if the conversion failed
+ */
+ public function getWikitextForTransclusion() {
+ $wikitext = $this->convert( CONTENT_MODEL_WIKITEXT, 'lossy' );
+
+ if ( $wikitext ) {
+ return $wikitext->getNativeData();
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns a Content object with pre-save transformations applied.
+ * This implementation just trims trailing whitespace.
+ *
+ * @param $title Title
+ * @param $user User
+ * @param $popts ParserOptions
+ * @return Content
+ */
+ public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
+ $text = $this->getNativeData();
+ $pst = rtrim( $text );
+
+ return ( $text === $pst ) ? $this : new WikitextContent( $pst );
+ }
+
+ /**
+ * Diff this content object with another content object.
+ *
+ * @since 1.21diff
+ *
+ * @param $that Content: The other content object to compare this content
+ * object to.
+ * @param $lang Language: The language object to use for text segmentation.
+ * If not given, $wgContentLang is used.
+ *
+ * @return DiffResult: A diff representing the changes that would have to be
+ * made to this content object to make it equal to $that.
+ */
+ public function diff( Content $that, Language $lang = null ) {
+ global $wgContLang;
+
+ $this->checkModelID( $that->getModel() );
+
+ # @todo: could implement this in DifferenceEngine and just delegate here?
+
+ if ( !$lang ) {
+ $lang = $wgContLang;
+ }
+
+ $otext = $this->getNativeData();
+ $ntext = $this->getNativeData();
+
+ # Note: Use native PHP diff, external engines don't give us abstract output
+ $ota = explode( "\n", $lang->segmentForDiff( $otext ) );
+ $nta = explode( "\n", $lang->segmentForDiff( $ntext ) );
+
+ $diff = new Diff( $ota, $nta );
+ return $diff;
+ }
+
+ /**
+ * Returns a generic ParserOutput object, wrapping the HTML returned by
+ * getHtml().
+ *
+ * @param $title Title Context title for parsing
+ * @param int|null $revId Revision ID (for {{REVISIONID}})
+ * @param $options ParserOptions|null Parser options
+ * @param bool $generateHtml Whether or not to generate HTML
+ *
+ * @return ParserOutput representing the HTML form of the text
+ */
+ public function getParserOutput( Title $title,
+ $revId = null,
+ ParserOptions $options = null, $generateHtml = true
+ ) {
+ global $wgParser, $wgTextModelsToParse;
+
+ if ( !$options ) {
+ //NOTE: use canonical options per default to produce cacheable output
+ $options = $this->getContentHandler()->makeParserOptions( 'canonical' );
+ }
+
+ if ( in_array( $this->getModel(), $wgTextModelsToParse ) ) {
+ // parse just to get links etc into the database
+ $po = $wgParser->parse( $this->getNativeData(), $title, $options, true, true, $revId );
+ } else {
+ $po = new ParserOutput();
+ }
+
+ if ( $generateHtml ) {
+ $html = $this->getHtml();
+ } else {
+ $html = '';
+ }
+
+ $po->setText( $html );
+ return $po;
+ }
+
+ /**
+ * Generates an HTML version of the content, for display. Used by
+ * getParserOutput() to construct a ParserOutput object.
+ *
+ * This default implementation just calls getHighlightHtml(). Content
+ * models that have another mapping to HTML (as is the case for markup
+ * languages like wikitext) should override this method to generate the
+ * appropriate HTML.
+ *
+ * @return string An HTML representation of the content
+ */
+ protected function getHtml() {
+ return $this->getHighlightHtml();
+ }
+
+ /**
+ * Generates a syntax-highlighted version of the content, as HTML.
+ * Used by the default implementation of getHtml().
+ *
+ * @return string an HTML representation of the content's markup
+ */
+ protected function getHighlightHtml() {
+ # TODO: make Highlighter interface, use highlighter here, if available
+ return htmlspecialchars( $this->getNativeData() );
+ }
+
+ /**
+ * @see Content::convert()
+ *
+ * This implementation provides lossless conversion between content models based
+ * on TextContent.
+ *
+ * @param string $toModel the desired content model, use the CONTENT_MODEL_XXX flags.
+ * @param string $lossy flag, set to "lossy" to allow lossy conversion. If lossy conversion is
+ * not allowed, full round-trip conversion is expected to work without losing information.
+ *
+ * @return Content|bool A content object with the content model $toModel, or false if
+ * that conversion is not supported.
+ */
+ public function convert( $toModel, $lossy = '' ) {
+ $converted = parent::convert( $toModel, $lossy );
+
+ if ( $converted !== false ) {
+ return $converted;
+ }
+
+ $toHandler = ContentHandler::getForModelID( $toModel );
+
+ if ( $toHandler instanceof TextContentHandler ) {
+ //NOTE: ignore content serialization format - it's just text anyway.
+ $text = $this->getNativeData();
+ $converted = $toHandler->unserializeContent( $text );
+ }
+
+ return $converted;
+ }
+}
diff --git a/includes/content/TextContentHandler.php b/includes/content/TextContentHandler.php
new file mode 100644
index 00000000..e7f41e18
--- /dev/null
+++ b/includes/content/TextContentHandler.php
@@ -0,0 +1,115 @@
+<?php
+/**
+ * Base content handler class for flat text contents.
+ *
+ * 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
+ *
+ * @since 1.21
+ *
+ * @file
+ * @ingroup Content
+ */
+
+/**
+ * Base content handler implementation for flat text contents.
+ *
+ * @ingroup Content
+ */
+class TextContentHandler extends ContentHandler {
+
+ public function __construct( $modelId = CONTENT_MODEL_TEXT, $formats = array( CONTENT_FORMAT_TEXT ) ) {
+ parent::__construct( $modelId, $formats );
+ }
+
+ /**
+ * Returns the content's text as-is.
+ *
+ * @param $content Content
+ * @param $format string|null
+ * @return mixed
+ */
+ public function serializeContent( Content $content, $format = null ) {
+ $this->checkFormat( $format );
+ return $content->getNativeData();
+ }
+
+ /**
+ * Attempts to merge differences between three versions. Returns a new
+ * Content object for a clean merge and false for failure or a conflict.
+ *
+ * All three Content objects passed as parameters must have the same
+ * content model.
+ *
+ * This text-based implementation uses wfMerge().
+ *
+ * @param $oldContent Content|string String
+ * @param $myContent Content|string String
+ * @param $yourContent Content|string String
+ *
+ * @return Content|Bool
+ */
+ public function merge3( Content $oldContent, Content $myContent, Content $yourContent ) {
+ $this->checkModelID( $oldContent->getModel() );
+ $this->checkModelID( $myContent->getModel() );
+ $this->checkModelID( $yourContent->getModel() );
+
+ $format = $this->getDefaultFormat();
+
+ $old = $this->serializeContent( $oldContent, $format );
+ $mine = $this->serializeContent( $myContent, $format );
+ $yours = $this->serializeContent( $yourContent, $format );
+
+ $ok = wfMerge( $old, $mine, $yours, $result );
+
+ if ( !$ok ) {
+ return false;
+ }
+
+ if ( !$result ) {
+ return $this->makeEmptyContent();
+ }
+
+ $mergedContent = $this->unserializeContent( $result, $format );
+ return $mergedContent;
+ }
+
+ /**
+ * Unserializes a Content object of the type supported by this ContentHandler.
+ *
+ * @since 1.21
+ *
+ * @param $text string serialized form of the content
+ * @param $format null|String the format used for serialization
+ *
+ * @return Content the TextContent object wrapping $text
+ */
+ public function unserializeContent( $text, $format = null ) {
+ $this->checkFormat( $format );
+
+ return new TextContent( $text );
+ }
+
+ /**
+ * Creates an empty TextContent object.
+ *
+ * @since 1.21
+ *
+ * @return Content
+ */
+ public function makeEmptyContent() {
+ return new TextContent( '' );
+ }
+}
diff --git a/includes/content/WikitextContent.php b/includes/content/WikitextContent.php
new file mode 100644
index 00000000..8be4ebab
--- /dev/null
+++ b/includes/content/WikitextContent.php
@@ -0,0 +1,322 @@
+<?php
+/**
+ * Content object for wiki text pages.
+ *
+ * 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
+ *
+ * @since 1.21
+ *
+ * @file
+ * @ingroup Content
+ *
+ * @author Daniel Kinzler
+ */
+
+/**
+ * Content object for wiki text pages.
+ *
+ * @ingroup Content
+ */
+class WikitextContent extends TextContent {
+
+ public function __construct( $text ) {
+ parent::__construct( $text, CONTENT_MODEL_WIKITEXT );
+ }
+
+ /**
+ * @see Content::getSection()
+ */
+ public function getSection( $section ) {
+ global $wgParser;
+
+ $text = $this->getNativeData();
+ $sect = $wgParser->getSection( $text, $section, false );
+
+ if ( $sect === false ) {
+ return false;
+ } else {
+ return new WikitextContent( $sect );
+ }
+ }
+
+ /**
+ * @see Content::replaceSection()
+ */
+ public function replaceSection( $section, Content $with, $sectionTitle = '' ) {
+ wfProfileIn( __METHOD__ );
+
+ $myModelId = $this->getModel();
+ $sectionModelId = $with->getModel();
+
+ if ( $sectionModelId != $myModelId ) {
+ throw new MWException( "Incompatible content model for section: " .
+ "document uses $myModelId but " .
+ "section uses $sectionModelId." );
+ }
+
+ $oldtext = $this->getNativeData();
+ $text = $with->getNativeData();
+
+ if ( $section === '' ) {
+ wfProfileOut( __METHOD__ );
+ return $with; # XXX: copy first?
+ } if ( $section == 'new' ) {
+ # Inserting a new section
+ $subject = $sectionTitle ? wfMessage( 'newsectionheaderdefaultlevel' )
+ ->rawParams( $sectionTitle )->inContentLanguage()->text() . "\n\n" : '';
+ if ( wfRunHooks( 'PlaceNewSection', array( $this, $oldtext, $subject, &$text ) ) ) {
+ $text = strlen( trim( $oldtext ) ) > 0
+ ? "{$oldtext}\n\n{$subject}{$text}"
+ : "{$subject}{$text}";
+ }
+ } else {
+ # Replacing an existing section; roll out the big guns
+ global $wgParser;
+
+ $text = $wgParser->replaceSection( $oldtext, $section, $text );
+ }
+
+ $newContent = new WikitextContent( $text );
+
+ wfProfileOut( __METHOD__ );
+ return $newContent;
+ }
+
+ /**
+ * Returns a new WikitextContent object with the given section heading
+ * prepended.
+ *
+ * @param $header string
+ * @return Content
+ */
+ public function addSectionHeader( $header ) {
+ $text = wfMessage( 'newsectionheaderdefaultlevel' )
+ ->rawParams( $header )->inContentLanguage()->text();
+ $text .= "\n\n";
+ $text .= $this->getNativeData();
+
+ return new WikitextContent( $text );
+ }
+
+ /**
+ * Returns a Content object with pre-save transformations applied using
+ * Parser::preSaveTransform().
+ *
+ * @param $title Title
+ * @param $user User
+ * @param $popts ParserOptions
+ * @return Content
+ */
+ public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
+ global $wgParser;
+
+ $text = $this->getNativeData();
+ $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts );
+ rtrim( $pst );
+
+ return ( $text === $pst ) ? $this : new WikitextContent( $pst );
+ }
+
+ /**
+ * Returns a Content object with preload transformations applied (or this
+ * object if no transformations apply).
+ *
+ * @param $title Title
+ * @param $popts ParserOptions
+ * @return Content
+ */
+ public function preloadTransform( Title $title, ParserOptions $popts ) {
+ global $wgParser;
+
+ $text = $this->getNativeData();
+ $plt = $wgParser->getPreloadText( $text, $title, $popts );
+
+ return new WikitextContent( $plt );
+ }
+
+ /**
+ * Implement redirect extraction for wikitext.
+ *
+ * @return null|Title
+ *
+ * @note: migrated here from Title::newFromRedirectInternal()
+ *
+ * @see Content::getRedirectTarget
+ * @see AbstractContent::getRedirectTarget
+ */
+ public function getRedirectTarget() {
+ global $wgMaxRedirects;
+ if ( $wgMaxRedirects < 1 ) {
+ // redirects are disabled, so quit early
+ return null;
+ }
+ $redir = MagicWord::get( 'redirect' );
+ $text = trim( $this->getNativeData() );
+ if ( $redir->matchStartAndRemove( $text ) ) {
+ // Extract the first link and see if it's usable
+ // Ensure that it really does come directly after #REDIRECT
+ // Some older redirects included a colon, so don't freak about that!
+ $m = array();
+ if ( preg_match( '!^\s*:?\s*\[{2}(.*?)(?:\|.*?)?\]{2}!', $text, $m ) ) {
+ // Strip preceding colon used to "escape" categories, etc.
+ // and URL-decode links
+ if ( strpos( $m[1], '%' ) !== false ) {
+ // Match behavior of inline link parsing here;
+ $m[1] = rawurldecode( ltrim( $m[1], ':' ) );
+ }
+ $title = Title::newFromText( $m[1] );
+ // If the title is a redirect to bad special pages or is invalid, return null
+ if ( !$title instanceof Title || !$title->isValidRedirectTarget() ) {
+ return null;
+ }
+ return $title;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @see Content::updateRedirect()
+ *
+ * This implementation replaces the first link on the page with the given new target
+ * if this Content object is a redirect. Otherwise, this method returns $this.
+ *
+ * @since 1.21
+ *
+ * @param Title $target
+ *
+ * @return Content a new Content object with the updated redirect (or $this if this Content object isn't a redirect)
+ */
+ public function updateRedirect( Title $target ) {
+ if ( !$this->isRedirect() ) {
+ return $this;
+ }
+
+ # Fix the text
+ # Remember that redirect pages can have categories, templates, etc.,
+ # so the regex has to be fairly general
+ $newText = preg_replace( '/ \[ \[ [^\]]* \] \] /x',
+ '[[' . $target->getFullText() . ']]',
+ $this->getNativeData(), 1 );
+
+ return new WikitextContent( $newText );
+ }
+
+ /**
+ * Returns true if this content is not a redirect, and this content's text
+ * is countable according to the criteria defined by $wgArticleCountMethod.
+ *
+ * @param bool $hasLinks if it is known whether this content contains
+ * links, provide this information here, to avoid redundant parsing to
+ * find out (default: null).
+ * @param $title Title: (default: null)
+ *
+ * @internal param \IContextSource $context context for parsing if necessary
+ *
+ * @return bool True if the content is countable
+ */
+ public function isCountable( $hasLinks = null, Title $title = null ) {
+ global $wgArticleCountMethod;
+
+ if ( $this->isRedirect() ) {
+ return false;
+ }
+
+ $text = $this->getNativeData();
+
+ switch ( $wgArticleCountMethod ) {
+ case 'any':
+ return true;
+ case 'comma':
+ return strpos( $text, ',' ) !== false;
+ case 'link':
+ if ( $hasLinks === null ) { # not known, find out
+ if ( !$title ) {
+ $context = RequestContext::getMain();
+ $title = $context->getTitle();
+ }
+
+ $po = $this->getParserOutput( $title, null, null, false );
+ $links = $po->getLinks();
+ $hasLinks = !empty( $links );
+ }
+
+ return $hasLinks;
+ }
+
+ return false;
+ }
+
+ public function getTextForSummary( $maxlength = 250 ) {
+ $truncatedtext = parent::getTextForSummary( $maxlength );
+
+ # clean up unfinished links
+ # XXX: make this optional? wasn't there in autosummary, but required for
+ # deletion summary.
+ $truncatedtext = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $truncatedtext );
+
+ return $truncatedtext;
+ }
+
+ /**
+ * Returns a ParserOutput object resulting from parsing the content's text
+ * using $wgParser.
+ *
+ * @since 1.21
+ *
+ * @param $title Title
+ * @param int $revId Revision to pass to the parser (default: null)
+ * @param $options ParserOptions (default: null)
+ * @param bool $generateHtml (default: false)
+ *
+ * @internal param \IContextSource|null $context
+ * @return ParserOutput representing the HTML form of the text
+ */
+ public function getParserOutput( Title $title,
+ $revId = null,
+ ParserOptions $options = null, $generateHtml = true
+ ) {
+ global $wgParser;
+
+ if ( !$options ) {
+ //NOTE: use canonical options per default to produce cacheable output
+ $options = $this->getContentHandler()->makeParserOptions( 'canonical' );
+ }
+
+ $po = $wgParser->parse( $this->getNativeData(), $title, $options, true, true, $revId );
+ return $po;
+ }
+
+ protected function getHtml() {
+ throw new MWException(
+ "getHtml() not implemented for wikitext. "
+ . "Use getParserOutput()->getText()."
+ );
+ }
+
+ /**
+ * @see Content::matchMagicWord()
+ *
+ * This implementation calls $word->match() on the this TextContent object's text.
+ *
+ * @param MagicWord $word
+ *
+ * @return bool whether this Content object matches the given magic word.
+ */
+ public function matchMagicWord( MagicWord $word ) {
+ return $word->match( $this->getNativeData() );
+ }
+}
diff --git a/includes/content/WikitextContentHandler.php b/includes/content/WikitextContentHandler.php
new file mode 100644
index 00000000..e1dcc668
--- /dev/null
+++ b/includes/content/WikitextContentHandler.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * Content handler for wiki text pages.
+ *
+ * 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
+ *
+ * @since 1.21
+ *
+ * @file
+ * @ingroup Content
+ */
+
+/**
+ * Content handler for wiki text pages.
+ *
+ * @ingroup Content
+ */
+class WikitextContentHandler extends TextContentHandler {
+
+ public function __construct( $modelId = CONTENT_MODEL_WIKITEXT ) {
+ parent::__construct( $modelId, array( CONTENT_FORMAT_WIKITEXT ) );
+ }
+
+ public function unserializeContent( $text, $format = null ) {
+ $this->checkFormat( $format );
+
+ return new WikitextContent( $text );
+ }
+
+ /**
+ * @see ContentHandler::makeEmptyContent
+ *
+ * @return Content
+ */
+ public function makeEmptyContent() {
+ return new WikitextContent( '' );
+ }
+
+ /**
+ * Returns a WikitextContent object representing a redirect to the given destination page.
+ *
+ * @see ContentHandler::makeRedirectContent
+ *
+ * @param Title $destination the page to redirect to.
+ *
+ * @return Content
+ */
+ public function makeRedirectContent( Title $destination ) {
+ $mwRedir = MagicWord::get( 'redirect' );
+ $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $destination->getPrefixedText() . ']]';
+
+ return new WikitextContent( $redirectText );
+ }
+
+ /**
+ * Returns true because wikitext supports redirects.
+ *
+ * @see ContentHandler::supportsRedirects
+ *
+ * @return boolean whether redirects are supported.
+ */
+ public function supportsRedirects() {
+ return true;
+ }
+
+ /**
+ * Returns true because wikitext supports sections.
+ *
+ * @return boolean whether sections are supported.
+ */
+ public function supportsSections() {
+ return true;
+ }
+
+ /**
+ * Returns true, because wikitext supports caching using the
+ * ParserCache mechanism.
+ *
+ * @since 1.21
+ * @return bool
+ */
+ public function isParserCacheSupported() {
+ return true;
+ }
+}
diff --git a/includes/context/ContextSource.php b/includes/context/ContextSource.php
index 45bd6fff..33f51cb9 100644
--- a/includes/context/ContextSource.php
+++ b/includes/context/ContextSource.php
@@ -28,7 +28,6 @@
* member variable and provide accessors to it.
*/
abstract class ContextSource implements IContextSource {
-
/**
* @var IContextSource
*/
@@ -42,7 +41,7 @@ abstract class ContextSource implements IContextSource {
public function getContext() {
if ( $this->context === null ) {
$class = get_class( $this );
- wfDebug( __METHOD__ . " ($class): called and \$context is null. Using RequestContext::getMain() for sanity\n" );
+ wfDebug( __METHOD__ . " ($class): called and \$context is null. Using RequestContext::getMain() for sanity\n" );
$this->context = RequestContext::getMain();
}
return $this->context;
@@ -52,7 +51,7 @@ abstract class ContextSource implements IContextSource {
* Set the IContextSource object
*
* @since 1.18
- * @param $context IContextSource
+ * @param IContextSource $context
*/
public function setContext( IContextSource $context ) {
$this->context = $context;
@@ -107,7 +106,7 @@ abstract class ContextSource implements IContextSource {
* Get the OutputPage object
*
* @since 1.18
- * @return OutputPage object
+ * @return OutputPage
*/
public function getOutput() {
return $this->getContext()->getOutput();
@@ -159,12 +158,21 @@ abstract class ContextSource implements IContextSource {
* Parameters are the same as wfMessage()
*
* @since 1.18
- * @return Message object
+ * @return Message
*/
public function msg( /* $args */ ) {
$args = func_get_args();
return call_user_func_array( array( $this->getContext(), 'msg' ), $args );
}
-
-}
+ /**
+ * Export the resolved user IP, HTTP headers, user ID, and session ID.
+ * The result will be reasonably sized to allow for serialization.
+ *
+ * @return Array
+ * @since 1.21
+ */
+ public function exportSession() {
+ return $this->getContext()->exportSession();
+ }
+}
diff --git a/includes/context/DerivativeContext.php b/includes/context/DerivativeContext.php
index 5adf3621..b9a70068 100644
--- a/includes/context/DerivativeContext.php
+++ b/includes/context/DerivativeContext.php
@@ -30,7 +30,6 @@
* a different Title instance set on it.
*/
class DerivativeContext extends ContextSource {
-
/**
* @var WebRequest
*/
@@ -68,7 +67,7 @@ class DerivativeContext extends ContextSource {
/**
* Constructor
- * @param $context IContextSource Context to inherit from
+ * @param IContextSource $context Context to inherit from
*/
public function __construct( IContextSource $context ) {
$this->setContext( $context );
@@ -77,7 +76,7 @@ class DerivativeContext extends ContextSource {
/**
* Set the WebRequest object
*
- * @param $r WebRequest object
+ * @param WebRequest $r
*/
public function setRequest( WebRequest $r ) {
$this->request = $r;
@@ -99,7 +98,7 @@ class DerivativeContext extends ContextSource {
/**
* Set the Title object
*
- * @param $t Title object
+ * @param Title $t
*/
public function setTitle( Title $t ) {
$this->title = $t;
@@ -140,7 +139,7 @@ class DerivativeContext extends ContextSource {
* Set the WikiPage object
*
* @since 1.19
- * @param $p WikiPage object
+ * @param WikiPage $p
*/
public function setWikiPage( WikiPage $p ) {
$this->wikipage = $p;
@@ -166,7 +165,7 @@ class DerivativeContext extends ContextSource {
/**
* Set the OutputPage object
*
- * @param $o OutputPage
+ * @param OutputPage $o
*/
public function setOutput( OutputPage $o ) {
$this->output = $o;
@@ -175,7 +174,7 @@ class DerivativeContext extends ContextSource {
/**
* Get the OutputPage object
*
- * @return OutputPage object
+ * @return OutputPage
*/
public function getOutput() {
if ( !is_null( $this->output ) ) {
@@ -188,7 +187,7 @@ class DerivativeContext extends ContextSource {
/**
* Set the User object
*
- * @param $u User
+ * @param User $u
*/
public function setUser( User $u ) {
$this->user = $u;
@@ -211,7 +210,7 @@ class DerivativeContext extends ContextSource {
* Set the Language object
*
* @deprecated 1.19 Use setLanguage instead
- * @param $l Mixed Language instance or language code
+ * @param Language|string $l Language instance or language code
*/
public function setLang( $l ) {
wfDeprecated( __METHOD__, '1.19' );
@@ -221,7 +220,8 @@ class DerivativeContext extends ContextSource {
/**
* Set the Language object
*
- * @param $l Mixed Language instance or language code
+ * @param Language|string $l Language instance or language code
+ * @throws MWException
* @since 1.19
*/
public function setLanguage( $l ) {
@@ -262,7 +262,7 @@ class DerivativeContext extends ContextSource {
/**
* Set the Skin object
*
- * @param $s Skin
+ * @param Skin $s
*/
public function setSkin( Skin $s ) {
$this->skin = clone $s;
@@ -281,6 +281,4 @@ class DerivativeContext extends ContextSource {
return $this->getContext()->getSkin();
}
}
-
}
-
diff --git a/includes/context/IContextSource.php b/includes/context/IContextSource.php
index 476035b5..c7b221b9 100644
--- a/includes/context/IContextSource.php
+++ b/includes/context/IContextSource.php
@@ -27,7 +27,6 @@
* Interface for objects which can provide a context on request.
*/
interface IContextSource {
-
/**
* Get the WebRequest object
*
@@ -66,7 +65,7 @@ interface IContextSource {
/**
* Get the OutputPage object
*
- * @return OutputPage object
+ * @return OutputPage
*/
public function getOutput();
@@ -103,8 +102,16 @@ interface IContextSource {
/**
* Get a Message object with context set
*
- * @return Message object
+ * @return Message
*/
public function msg();
-}
+ /**
+ * Export the resolved user IP, HTTP headers, user ID, and session ID.
+ * The result will be reasonably sized to allow for serialization.
+ *
+ * @return Array
+ * @since 1.21
+ */
+ public function exportSession();
+}
diff --git a/includes/context/RequestContext.php b/includes/context/RequestContext.php
index 9e7837d9..6aefc98e 100644
--- a/includes/context/RequestContext.php
+++ b/includes/context/RequestContext.php
@@ -28,7 +28,6 @@
* Group all the pieces relevant to the context of a request into one instance
*/
class RequestContext implements IContextSource {
-
/**
* @var WebRequest
*/
@@ -67,7 +66,7 @@ class RequestContext implements IContextSource {
/**
* Set the WebRequest object
*
- * @param $r WebRequest object
+ * @param WebRequest $r
*/
public function setRequest( WebRequest $r ) {
$this->request = $r;
@@ -89,10 +88,12 @@ class RequestContext implements IContextSource {
/**
* Set the Title object
*
- * @param $t Title object
+ * @param Title $t
*/
public function setTitle( Title $t ) {
$this->title = $t;
+ // Erase the WikiPage so a new one with the new title gets created.
+ $this->wikipage = null;
}
/**
@@ -135,9 +136,15 @@ class RequestContext implements IContextSource {
* Set the WikiPage object
*
* @since 1.19
- * @param $p WikiPage object
+ * @param WikiPage $p
*/
public function setWikiPage( WikiPage $p ) {
+ $contextTitle = $this->getTitle();
+ $pageTitle = $p->getTitle();
+ if ( !$contextTitle || !$pageTitle->equals( $contextTitle ) ) {
+ $this->setTitle( $pageTitle );
+ }
+ // Defer this to the end since setTitle sets it to null.
$this->wikipage = $p;
}
@@ -148,6 +155,7 @@ class RequestContext implements IContextSource {
* canUseWikiPage() to check whether this method can be called safely.
*
* @since 1.19
+ * @throws MWException
* @return WikiPage
*/
public function getWikiPage() {
@@ -171,7 +179,7 @@ class RequestContext implements IContextSource {
/**
* Get the OutputPage object
*
- * @return OutputPage object
+ * @return OutputPage
*/
public function getOutput() {
if ( $this->output === null ) {
@@ -183,7 +191,7 @@ class RequestContext implements IContextSource {
/**
* Set the User object
*
- * @param $u User
+ * @param User $u
*/
public function setUser( User $u ) {
$this->user = $u;
@@ -204,7 +212,7 @@ class RequestContext implements IContextSource {
/**
* Accepts a language code and ensures it's sane. Outputs a cleaned up language
* code and replaces with $wgLanguageCode if not sane.
- * @param $code string
+ * @param string $code Language code
* @return string
*/
public static function sanitizeLangCode( $code ) {
@@ -214,7 +222,7 @@ class RequestContext implements IContextSource {
$code = strtolower( $code );
# Validate $code
- if( empty( $code ) || !Language::isValidCode( $code ) || ( $code === 'qqq' ) ) {
+ if ( empty( $code ) || !Language::isValidCode( $code ) || ( $code === 'qqq' ) ) {
wfDebug( "Invalid user language code\n" );
$code = $wgLanguageCode;
}
@@ -226,7 +234,7 @@ class RequestContext implements IContextSource {
* Set the Language object
*
* @deprecated 1.19 Use setLanguage instead
- * @param $l Mixed Language instance or language code
+ * @param Language|string $l Language instance or language code
*/
public function setLang( $l ) {
wfDeprecated( __METHOD__, '1.19' );
@@ -236,7 +244,8 @@ class RequestContext implements IContextSource {
/**
* Set the Language object
*
- * @param $l Mixed Language instance or language code
+ * @param Language|string $l Language instance or language code
+ * @throws MWException
* @since 1.19
*/
public function setLanguage( $l ) {
@@ -289,7 +298,7 @@ class RequestContext implements IContextSource {
wfRunHooks( 'UserGetLanguageObject', array( $user, &$code, $this ) );
- if( $code === $wgLanguageCode ) {
+ if ( $code === $wgLanguageCode ) {
$this->lang = $wgContLang;
} else {
$obj = Language::factory( $code );
@@ -305,7 +314,7 @@ class RequestContext implements IContextSource {
/**
* Set the Skin object
*
- * @param $s Skin
+ * @param Skin $s
*/
public function setSkin( Skin $s ) {
$this->skin = clone $s;
@@ -320,14 +329,14 @@ class RequestContext implements IContextSource {
public function getSkin() {
if ( $this->skin === null ) {
wfProfileIn( __METHOD__ . '-createskin' );
-
+
$skin = null;
wfRunHooks( 'RequestContextCreateSkin', array( $this, &$skin ) );
// If the hook worked try to set a skin from it
if ( $skin instanceof Skin ) {
$this->skin = $skin;
- } elseif ( is_string($skin) ) {
+ } elseif ( is_string( $skin ) ) {
$this->skin = Skin::newFromKey( $skin );
}
@@ -335,7 +344,7 @@ class RequestContext implements IContextSource {
// then go through the normal processing to load a skin
if ( $this->skin === null ) {
global $wgHiddenPrefs;
- if( !in_array( 'skin', $wgHiddenPrefs ) ) {
+ if ( !in_array( 'skin', $wgHiddenPrefs ) ) {
# get the user skin
$userSkin = $this->getUser()->getOption( 'skin' );
$userSkin = $this->getRequest()->getVal( 'useskin', $userSkin );
@@ -361,7 +370,7 @@ class RequestContext implements IContextSource {
* Get a Message object with context set
* Parameters are the same as wfMessage()
*
- * @return Message object
+ * @return Message
*/
public function msg() {
$args = func_get_args();
@@ -373,7 +382,7 @@ class RequestContext implements IContextSource {
/**
* Get the RequestContext object associated with the main request
*
- * @return RequestContext object
+ * @return RequestContext
*/
public static function getMain() {
static $instance = null;
@@ -384,6 +393,96 @@ class RequestContext implements IContextSource {
}
/**
+ * Export the resolved user IP, HTTP headers, user ID, and session ID.
+ * The result will be reasonably sized to allow for serialization.
+ *
+ * @return Array
+ * @since 1.21
+ */
+ public function exportSession() {
+ return array(
+ 'ip' => $this->getRequest()->getIP(),
+ 'headers' => $this->getRequest()->getAllHeaders(),
+ 'sessionId' => session_id(),
+ 'userId' => $this->getUser()->getId()
+ );
+ }
+
+ /**
+ * Import the resolved user IP, HTTP headers, user ID, and session ID.
+ * This sets the current session and sets $wgUser and $wgRequest.
+ * Once the return value falls out of scope, the old context is restored.
+ * This function can only be called within CLI mode scripts.
+ *
+ * This will setup the session from the given ID. This is useful when
+ * background scripts inherit context when acting on behalf of a user.
+ *
+ * $param array $params Result of RequestContext::exportSession()
+ * @return ScopedCallback
+ * @throws MWException
+ * @since 1.21
+ */
+ public static function importScopedSession( array $params ) {
+ if ( PHP_SAPI !== 'cli' ) {
+ // Don't send random private cookies or turn $wgRequest into FauxRequest
+ throw new MWException( "Sessions can only be imported in cli mode." );
+ } elseif ( !strlen( $params['sessionId'] ) ) {
+ throw new MWException( "No session ID was specified." );
+ }
+
+ if ( $params['userId'] ) { // logged-in user
+ $user = User::newFromId( $params['userId'] );
+ if ( !$user ) {
+ throw new MWException( "No user with ID '{$params['userId']}'." );
+ }
+ } elseif ( !IP::isValid( $params['ip'] ) ) {
+ throw new MWException( "Could not load user '{$params['ip']}'." );
+ } else { // anon user
+ $user = User::newFromName( $params['ip'], false );
+ }
+
+ $importSessionFunction = function( User $user, array $params ) {
+ global $wgRequest, $wgUser;
+
+ $context = RequestContext::getMain();
+ // Commit and close any current session
+ session_write_close(); // persist
+ session_id( '' ); // detach
+ $_SESSION = array(); // clear in-memory array
+ // Remove any user IP or agent information
+ $context->setRequest( new FauxRequest() );
+ $wgRequest = $context->getRequest(); // b/c
+ // Now that all private information is detached from the user, it should
+ // be safe to load the new user. If errors occur or an exception is thrown
+ // and caught (leaving the main context in a mixed state), there is no risk
+ // of the User object being attached to the wrong IP, headers, or session.
+ $context->setUser( $user );
+ $wgUser = $context->getUser(); // b/c
+ if ( strlen( $params['sessionId'] ) ) { // don't make a new random ID
+ wfSetupSession( $params['sessionId'] ); // sets $_SESSION
+ }
+ $request = new FauxRequest( array(), false, $_SESSION );
+ $request->setIP( $params['ip'] );
+ foreach ( $params['headers'] as $name => $value ) {
+ $request->setHeader( $name, $value );
+ }
+ // Set the current context to use the new WebRequest
+ $context->setRequest( $request );
+ $wgRequest = $context->getRequest(); // b/c
+ };
+
+ // Stash the old session and load in the new one
+ $oUser = self::getMain()->getUser();
+ $oParams = self::getMain()->exportSession();
+ $importSessionFunction( $user, $params );
+
+ // Set callback to save and close the new session and reload the old one
+ return new ScopedCallback( function() use ( $importSessionFunction, $oUser, $oParams ) {
+ $importSessionFunction( $oUser, $oParams );
+ } );
+ }
+
+ /**
* Create a new extraneous context. The context is filled with information
* external to the current session.
* - Title is specified by argument
@@ -397,7 +496,7 @@ class RequestContext implements IContextSource {
* @param WebRequest|array $request A WebRequest or data to use for a FauxRequest
* @return RequestContext
*/
- public static function newExtraneousContext( Title $title, $request=array() ) {
+ public static function newExtraneousContext( Title $title, $request = array() ) {
$context = new self;
$context->setTitle( $title );
if ( $request instanceof WebRequest ) {
@@ -408,6 +507,4 @@ class RequestContext implements IContextSource {
$context->user = User::newFromName( '127.0.0.1', false );
return $context;
}
-
}
-
diff --git a/includes/dao/DBAccessBase.php b/includes/dao/DBAccessBase.php
new file mode 100644
index 00000000..6c009dee
--- /dev/null
+++ b/includes/dao/DBAccessBase.php
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * Base class for objects that allow access to other wiki's databases using
+ * the foreign database access mechanism implemented by LBFactory_multi.
+ *
+ * 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
+ *
+ * @since 1.21
+ *
+ * @file
+ * @ingroup Database
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+abstract class DBAccessBase implements IDBAccessObject {
+
+ /**
+ * @var String|bool $wiki The target wiki's name. This must be an ID
+ * that LBFactory can understand.
+ */
+ protected $wiki = false;
+
+ /**
+ * @param string|bool $wiki The target wiki's name. This must be an ID
+ * that LBFactory can understand.
+ */
+ public function __construct( $wiki = false ) {
+ $this->wiki = $wiki;
+ }
+
+ /**
+ * Returns a database connection.
+ *
+ * @see wfGetDB()
+ * @see LoadBalancer::getConnection()
+ *
+ * @since 1.21
+ *
+ * @param int $id Which connection to use
+ * @param array $groups Query groups
+ *
+ * @return DatabaseBase
+ */
+ protected function getConnection( $id, $groups = array() ) {
+ $loadBalancer = wfGetLB( $this->wiki );
+ return $loadBalancer->getConnection( $id, $groups, $this->wiki );
+ }
+
+ /**
+ * Releases a database connection and makes it available for recycling.
+ *
+ * @see LoadBalancer::reuseConnection()
+ *
+ * @since 1.21
+ *
+ * @param DatabaseBase $db the database connection to release.
+ */
+ protected function releaseConnection( DatabaseBase $db ) {
+ if ( $this->wiki !== false ) {
+ $loadBalancer = $this->getLoadBalancer();
+ $loadBalancer->reuseConnection( $db );
+ }
+ }
+
+ /**
+ * Get the database type used for read operations.
+ *
+ * @see wfGetLB
+ *
+ * @since 1.21
+ *
+ * @return LoadBalancer The database load balancer object
+ */
+ public function getLoadBalancer() {
+ return wfGetLB( $this->wiki );
+ }
+}
diff --git a/includes/dao/IDBAccessObject.php b/includes/dao/IDBAccessObject.php
index e30522a5..4eb6ff3e 100644
--- a/includes/dao/IDBAccessObject.php
+++ b/includes/dao/IDBAccessObject.php
@@ -41,10 +41,12 @@
* - b) Determine the new row (expensive, so we don't want to hold locks now)
* - c) Re-read the current row with READ_LOCKING; if it changed then bail out
* - d) otherwise, do the updates
+ *
+ * @since 1.20
*/
interface IDBAccessObject {
// Constants for object loading bitfield flags (higher => higher QoS)
- const READ_LATEST = 1; // read from the master
+ const READ_LATEST = 1; // read from the master
const READ_LOCKING = 3; // READ_LATEST and "FOR UPDATE"
// Convenience constant for callers to explicitly request slave data
diff --git a/includes/db/CloneDatabase.php b/includes/db/CloneDatabase.php
index 4e43642f..4e443741 100644
--- a/includes/db/CloneDatabase.php
+++ b/includes/db/CloneDatabase.php
@@ -60,9 +60,9 @@ class CloneDatabase {
* Constructor
*
* @param $db DatabaseBase A database subclass
- * @param $tablesToClone Array An array of tables to clone, unprefixed
- * @param $newTablePrefix String Prefix to assign to the tables
- * @param $oldTablePrefix String Prefix on current tables, if not $wgDBprefix
+ * @param array $tablesToClone An array of tables to clone, unprefixed
+ * @param string $newTablePrefix Prefix to assign to the tables
+ * @param string $oldTablePrefix Prefix on current tables, if not $wgDBprefix
* @param $dropCurrentTables bool
*/
public function __construct( DatabaseBase $db, array $tablesToClone,
@@ -77,7 +77,7 @@ class CloneDatabase {
/**
* Set whether to use temporary tables or not
- * @param $u Bool Use temporary tables when cloning the structure
+ * @param bool $u Use temporary tables when cloning the structure
*/
public function useTemporaryTables( $u = true ) {
$this->useTemporaryTables = $u;
@@ -87,35 +87,32 @@ class CloneDatabase {
* Clone the table structure
*/
public function cloneTableStructure() {
-
foreach( $this->tablesToClone as $tbl ) {
# Clean up from previous aborted run. So that table escaping
# works correctly across DB engines, we need to change the pre-
# fix back and forth so tableName() works right.
-
+
self::changePrefix( $this->oldTablePrefix );
$oldTableName = $this->db->tableName( $tbl, 'raw' );
-
+
self::changePrefix( $this->newTablePrefix );
$newTableName = $this->db->tableName( $tbl, 'raw' );
-
+
if( $this->dropCurrentTables && !in_array( $this->db->getType(), array( 'postgres', 'oracle' ) ) ) {
$this->db->dropTable( $tbl, __METHOD__ );
- wfDebug( __METHOD__." dropping {$newTableName}\n", true);
+ wfDebug( __METHOD__ . " dropping {$newTableName}\n", true );
//Dropping the oldTable because the prefix was changed
}
# Create new table
- wfDebug( __METHOD__." duplicating $oldTableName to $newTableName\n", true );
+ wfDebug( __METHOD__ . " duplicating $oldTableName to $newTableName\n", true );
$this->db->duplicateTableStructure( $oldTableName, $newTableName, $this->useTemporaryTables );
-
}
-
}
/**
* Change the prefix back to the original.
- * @param $dropTables bool Optionally drop the tables we created
+ * @param bool $dropTables Optionally drop the tables we created
*/
public function destroy( $dropTables = false ) {
if( $dropTables ) {
diff --git a/includes/db/Database.php b/includes/db/Database.php
index 5f10b97d..65a74abf 100644
--- a/includes/db/Database.php
+++ b/includes/db/Database.php
@@ -49,10 +49,10 @@ interface DatabaseType {
/**
* Open a connection to the database. Usually aborts on failure
*
- * @param $server String: database server host
- * @param $user String: database user name
- * @param $password String: database user password
- * @param $dbName String: database name
+ * @param string $server database server host
+ * @param string $user database user name
+ * @param string $password database user password
+ * @param string $dbName database name
* @return bool
* @throws DBConnectionError
*/
@@ -62,9 +62,10 @@ interface DatabaseType {
* Fetch the next row from the given result object, in object form.
* Fields can be retrieved with $row->fieldname, with fields acting like
* member variables.
+ * If no more rows are available, false is returned.
*
* @param $res ResultWrapper|object as returned from DatabaseBase::query(), etc.
- * @return Row object
+ * @return object|bool
* @throws DBUnexpectedError Thrown if the database returns an error
*/
function fetchObject( $res );
@@ -72,9 +73,10 @@ interface DatabaseType {
/**
* Fetch the next row from the given result object, in associative array
* form. Fields are retrieved with $row['fieldname'].
+ * If no more rows are available, false is returned.
*
* @param $res ResultWrapper result object as returned from DatabaseBase::query(), etc.
- * @return Row object
+ * @return array|bool
* @throws DBUnexpectedError Thrown if the database returns an error
*/
function fetchRow( $res );
@@ -112,8 +114,8 @@ interface DatabaseType {
* The value inserted should be fetched from nextSequenceValue()
*
* Example:
- * $id = $dbw->nextSequenceValue('page_page_id_seq');
- * $dbw->insert('page',array('page_id' => $id));
+ * $id = $dbw->nextSequenceValue( 'page_page_id_seq' );
+ * $dbw->insert( 'page', array( 'page_id' => $id ) );
* $id = $dbw->insertId();
*
* @return int
@@ -149,8 +151,8 @@ interface DatabaseType {
* mysql_fetch_field() wrapper
* Returns false if the field doesn't exist
*
- * @param $table string: table name
- * @param $field string: field name
+ * @param string $table table name
+ * @param string $field field name
*
* @return Field
*/
@@ -158,9 +160,9 @@ interface DatabaseType {
/**
* Get information about an index into an object
- * @param $table string: Table name
- * @param $index string: Index name
- * @param $fname string: Calling function name
+ * @param string $table Table name
+ * @param string $index Index name
+ * @param string $fname Calling function name
* @return Mixed: Database-specific index description class or false if the index does not exist
*/
function indexInfo( $table, $index, $fname = 'Database::indexInfo' );
@@ -176,7 +178,7 @@ interface DatabaseType {
/**
* Wrapper for addslashes()
*
- * @param $s string: to be slashed.
+ * @param string $s to be slashed.
* @return string: slashed string.
*/
function strencode( $s );
@@ -249,6 +251,37 @@ abstract class DatabaseBase implements DatabaseType {
protected $delimiter = ';';
+ /**
+ * Remembers the function name given for starting the most recent transaction via begin().
+ * Used to provide additional context for error reporting.
+ *
+ * @var String
+ * @see DatabaseBase::mTrxLevel
+ */
+ private $mTrxFname = null;
+
+ /**
+ * Record if possible write queries were done in the last transaction started
+ *
+ * @var Bool
+ * @see DatabaseBase::mTrxLevel
+ */
+ private $mTrxDoneWrites = false;
+
+ /**
+ * Record if the current transaction was started implicitly due to DBO_TRX being set.
+ *
+ * @var Bool
+ * @see DatabaseBase::mTrxLevel
+ */
+ private $mTrxAutomatic = false;
+
+ /**
+ * @since 1.21
+ * @var file handle for upgrade
+ */
+ protected $fileHandle = null;
+
# ------------------------------------------------------------------------------
# Accessors
# ------------------------------------------------------------------------------
@@ -266,6 +299,13 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
+ * @return string: command delimiter used by this database engine
+ */
+ public function getDelimiter() {
+ return $this->delimiter;
+ }
+
+ /**
* Boolean, controls output of large amounts of debug information.
* @param $debug bool|null
* - true to enable debugging
@@ -329,7 +369,7 @@ abstract class DatabaseBase implements DatabaseType {
* Historically, transactions were allowed to be "nested". This is no
* longer supported, so this function really only returns a boolean.
*
- * @param $level int An integer (0 or 1), or omitted to leave it unchanged.
+ * @param int $level An integer (0 or 1), or omitted to leave it unchanged.
* @return int The previous value
*/
public function trxLevel( $level = null ) {
@@ -338,7 +378,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Get/set the number of errors logged. Only useful when errors are ignored
- * @param $count int The count to set, or omitted to leave it unchanged.
+ * @param int $count The count to set, or omitted to leave it unchanged.
* @return int The error count
*/
public function errorCount( $count = null ) {
@@ -347,7 +387,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Get/set the table prefix.
- * @param $prefix string The table prefix to set, or omitted to leave it unchanged.
+ * @param string $prefix The table prefix to set, or omitted to leave it unchanged.
* @return string The previous table prefix.
*/
public function tablePrefix( $prefix = null ) {
@@ -355,10 +395,19 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
+ * Set the filehandle to copy write statements to.
+ *
+ * @param $fh filehandle
+ */
+ public function setFileHandle( $fh ) {
+ $this->fileHandle = $fh;
+ }
+
+ /**
* Get properties passed down from the server info array of the load
* balancer.
*
- * @param $name string The entry of the info array to get, or null to get the
+ * @param string $name The entry of the info array to get, or null to get the
* whole array
*
* @return LoadBalancer|null
@@ -441,7 +490,7 @@ abstract class DatabaseBase implements DatabaseType {
* Returns true if this database uses timestamps rather than integers
*
* @return bool
- */
+ */
public function realTimestamps() {
return false;
}
@@ -509,7 +558,7 @@ abstract class DatabaseBase implements DatabaseType {
* @return bool
*/
public function writesOrCallbacksPending() {
- return $this->mTrxLevel && ( $this->mDoneWrites || $this->mTrxIdleCallbacks );
+ return $this->mTrxLevel && ( $this->mTrxDoneWrites || $this->mTrxIdleCallbacks );
}
/**
@@ -536,7 +585,7 @@ abstract class DatabaseBase implements DatabaseType {
global $wgDebugDBTransactions;
$this->mFlags |= $flag;
if ( ( $flag & DBO_TRX) & $wgDebugDBTransactions ) {
- wfDebug("Implicit transactions are now disabled.\n");
+ wfDebug( "Implicit transactions are now disabled.\n" );
}
}
@@ -549,7 +598,7 @@ abstract class DatabaseBase implements DatabaseType {
global $wgDebugDBTransactions;
$this->mFlags &= ~$flag;
if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) {
- wfDebug("Implicit transactions are now disabled.\n");
+ wfDebug( "Implicit transactions are now disabled.\n" );
}
}
@@ -605,12 +654,12 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Constructor.
- * @param $server String: database server host
- * @param $user String: database user name
- * @param $password String: database user password
- * @param $dbName String: database name
+ * @param string $server database server host
+ * @param string $user database user name
+ * @param string $password database user password
+ * @param string $dbName database name
* @param $flags
- * @param $tablePrefix String: database table prefixes. By default use the prefix gave in LocalSettings.php
+ * @param string $tablePrefix database table prefixes. By default use the prefix gave in LocalSettings.php
*/
function __construct( $server = false, $user = false, $password = false, $dbName = false,
$flags = 0, $tablePrefix = 'get from global'
@@ -623,12 +672,12 @@ abstract class DatabaseBase implements DatabaseType {
if ( $wgCommandLineMode ) {
$this->mFlags &= ~DBO_TRX;
if ( $wgDebugDBTransactions ) {
- wfDebug("Implicit transaction open disabled.\n");
+ wfDebug( "Implicit transaction open disabled.\n" );
}
} else {
$this->mFlags |= DBO_TRX;
if ( $wgDebugDBTransactions ) {
- wfDebug("Implicit transaction open enabled.\n");
+ wfDebug( "Implicit transaction open enabled.\n" );
}
}
}
@@ -671,14 +720,14 @@ abstract class DatabaseBase implements DatabaseType {
*
* @since 1.18
*
- * @param $dbType String A possible DB type
- * @param $p Array An array of options to pass to the constructor.
+ * @param string $dbType A possible DB type
+ * @param array $p An array of options to pass to the constructor.
* Valid options are: host, user, password, dbname, flags, tablePrefix
* @return DatabaseBase subclass or null
*/
- public final static function factory( $dbType, $p = array() ) {
+ final public static function factory( $dbType, $p = array() ) {
$canonicalDBTypes = array(
- 'mysql', 'postgres', 'sqlite', 'oracle', 'mssql', 'ibm_db2'
+ 'mysql', 'postgres', 'sqlite', 'oracle', 'mssql'
);
$dbType = strtolower( $dbType );
$class = 'Database' . ucfirst( $dbType );
@@ -724,7 +773,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $errno
* @param $errstr
*/
- protected function connectionErrorHandler( $errno, $errstr ) {
+ protected function connectionErrorHandler( $errno, $errstr ) {
$this->mPHPError = $errstr;
}
@@ -732,6 +781,7 @@ abstract class DatabaseBase implements DatabaseType {
* Closes a database connection.
* if it is open : commits any open transactions
*
+ * @throws MWException
* @return Bool operation success. true if already closed.
*/
public function close() {
@@ -741,8 +791,14 @@ abstract class DatabaseBase implements DatabaseType {
$this->mOpened = false;
if ( $this->mConn ) {
if ( $this->trxLevel() ) {
- $this->commit( __METHOD__ );
+ if ( !$this->mTrxAutomatic ) {
+ wfWarn( "Transaction still in progress (from {$this->mTrxFname}), " .
+ " performing implicit commit before closing connection!" );
+ }
+
+ $this->commit( __METHOD__, 'flush' );
}
+
$ret = $this->closeConnection();
$this->mConn = false;
return $ret;
@@ -756,10 +812,11 @@ abstract class DatabaseBase implements DatabaseType {
* @since 1.20
* @return bool: Whether connection was closed successfully
*/
- protected abstract function closeConnection();
+ abstract protected function closeConnection();
/**
- * @param $error String: fallback error message, used if none is given by DB
+ * @param string $error fallback error message, used if none is given by DB
+ * @throws DBConnectionError
*/
function reportConnectionError( $error = 'Unknown error' ) {
$myError = $this->lastError();
@@ -777,7 +834,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $sql String: SQL query.
* @return ResultWrapper Result object to feed to fetchObject, fetchRow, ...; or false on failure
*/
- protected abstract function doQuery( $sql );
+ abstract protected function doQuery( $sql );
/**
* Determine whether a query writes to the DB.
@@ -809,9 +866,9 @@ abstract class DatabaseBase implements DatabaseType {
* comment (you can use __METHOD__ or add some extra info)
* @param $tempIgnore Boolean: Whether to avoid throwing an exception on errors...
* maybe best to catch the exception instead?
+ * @throws MWException
* @return boolean|ResultWrapper. true for a successful write query, ResultWrapper object
* for a successful read query, or false on failure if $tempIgnore set
- * @throws DBQueryError Thrown when the database returns an error of any kind
*/
public function query( $sql, $fname = '', $tempIgnore = false ) {
$isMaster = !is_null( $this->getLBInfo( 'master' ) );
@@ -849,24 +906,34 @@ abstract class DatabaseBase implements DatabaseType {
} else {
$userName = '';
}
- $commentedSql = preg_replace( '/\s/', " /* $fname $userName */ ", $sql, 1 );
+
+ // Add trace comment to the begin of the sql string, right after the operator.
+ // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598)
+ $commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 );
# If DBO_TRX is set, start a transaction
- if ( ( $this->mFlags & DBO_TRX ) && !$this->trxLevel() &&
- $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' ) {
- # avoid establishing transactions for SHOW and SET statements too -
+ if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel &&
+ $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' )
+ {
+ # Avoid establishing transactions for SHOW and SET statements too -
# that would delay transaction initializations to once connection
# is really used by application
$sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm)
if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) {
global $wgDebugDBTransactions;
if ( $wgDebugDBTransactions ) {
- wfDebug("Implicit transaction start.\n");
+ wfDebug( "Implicit transaction start.\n" );
}
$this->begin( __METHOD__ . " ($fname)" );
+ $this->mTrxAutomatic = true;
}
}
+ # Keep track of whether the transaction has write queries pending
+ if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) {
+ $this->mTrxDoneWrites = true;
+ }
+
if ( $this->debug() ) {
static $cnt = 0;
@@ -933,6 +1000,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $sql String
* @param $fname String
* @param $tempIgnore Boolean
+ * @throws DBQueryError
*/
public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
# Ignore errors during error handling to avoid infinite recursion
@@ -981,7 +1049,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Execute a prepared query with the various arguments
- * @param $prepared String: the prepared sql
+ * @param string $prepared the prepared sql
* @param $args Mixed: Either an array here, or put scalars as varargs
*
* @return ResultWrapper
@@ -1001,8 +1069,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* For faking prepared SQL statements on DBs that don't support it directly.
*
- * @param $preparedQuery String: a 'preparable' SQL statement
- * @param $args Array of arguments to fill it with
+ * @param string $preparedQuery a 'preparable' SQL statement
+ * @param array $args of arguments to fill it with
* @return string executable SQL
*/
public function fillPrepared( $preparedQuery, $args ) {
@@ -1019,6 +1087,7 @@ abstract class DatabaseBase implements DatabaseType {
* while we're doing this.
*
* @param $matches Array
+ * @throws DBUnexpectedError
* @return String
*/
protected function fillPreparedArg( $matches ) {
@@ -1028,7 +1097,7 @@ abstract class DatabaseBase implements DatabaseType {
case '\\&': return '&';
}
- list( /* $n */ , $arg ) = each( $this->preparedArgs );
+ list( /* $n */, $arg ) = each( $this->preparedArgs );
switch( $matches[1] ) {
case '?': return $this->addQuotes( $arg );
@@ -1058,12 +1127,12 @@ abstract class DatabaseBase implements DatabaseType {
*
* If no result rows are returned from the query, false is returned.
*
- * @param $table string|array Table name. See DatabaseBase::select() for details.
- * @param $var string The field name to select. This must be a valid SQL
+ * @param string|array $table Table name. See DatabaseBase::select() for details.
+ * @param string $var The field name to select. This must be a valid SQL
* fragment: do not use unvalidated user input.
- * @param $cond string|array The condition array. See DatabaseBase::select() for details.
- * @param $fname string The function name of the caller.
- * @param $options string|array The query options. See DatabaseBase::select() for details.
+ * @param string|array $cond The condition array. See DatabaseBase::select() for details.
+ * @param string $fname The function name of the caller.
+ * @param string|array $options The query options. See DatabaseBase::select() for details.
*
* @return bool|mixed The value from the field, or false on failure.
*/
@@ -1095,7 +1164,7 @@ abstract class DatabaseBase implements DatabaseType {
* Returns an optional USE INDEX clause to go after the table, and a
* string to go at the end of the query.
*
- * @param $options Array: associative array of options to be turned into
+ * @param array $options associative array of options to be turned into
* an SQL query, valid keys are listed in the function.
* @return Array
* @see DatabaseBase::select()
@@ -1112,26 +1181,9 @@ abstract class DatabaseBase implements DatabaseType {
}
}
- if ( isset( $options['GROUP BY'] ) ) {
- $gb = is_array( $options['GROUP BY'] )
- ? implode( ',', $options['GROUP BY'] )
- : $options['GROUP BY'];
- $preLimitTail .= " GROUP BY {$gb}";
- }
+ $preLimitTail .= $this->makeGroupByWithHaving( $options );
- if ( isset( $options['HAVING'] ) ) {
- $having = is_array( $options['HAVING'] )
- ? $this->makeList( $options['HAVING'], LIST_AND )
- : $options['HAVING'];
- $preLimitTail .= " HAVING {$having}";
- }
-
- if ( isset( $options['ORDER BY'] ) ) {
- $ob = is_array( $options['ORDER BY'] )
- ? implode( ',', $options['ORDER BY'] )
- : $options['ORDER BY'];
- $preLimitTail .= " ORDER BY {$ob}";
- }
+ $preLimitTail .= $this->makeOrderBy( $options );
// if (isset($options['LIMIT'])) {
// $tailOpts .= $this->limitResult('', $options['LIMIT'],
@@ -1194,14 +1246,57 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
+ * Returns an optional GROUP BY with an optional HAVING
+ *
+ * @param array $options associative array of options
+ * @return string
+ * @see DatabaseBase::select()
+ * @since 1.21
+ */
+ public function makeGroupByWithHaving( $options ) {
+ $sql = '';
+ if ( isset( $options['GROUP BY'] ) ) {
+ $gb = is_array( $options['GROUP BY'] )
+ ? implode( ',', $options['GROUP BY'] )
+ : $options['GROUP BY'];
+ $sql .= ' GROUP BY ' . $gb;
+ }
+ if ( isset( $options['HAVING'] ) ) {
+ $having = is_array( $options['HAVING'] )
+ ? $this->makeList( $options['HAVING'], LIST_AND )
+ : $options['HAVING'];
+ $sql .= ' HAVING ' . $having;
+ }
+ return $sql;
+ }
+
+ /**
+ * Returns an optional ORDER BY
+ *
+ * @param array $options associative array of options
+ * @return string
+ * @see DatabaseBase::select()
+ * @since 1.21
+ */
+ public function makeOrderBy( $options ) {
+ if ( isset( $options['ORDER BY'] ) ) {
+ $ob = is_array( $options['ORDER BY'] )
+ ? implode( ',', $options['ORDER BY'] )
+ : $options['ORDER BY'];
+ return ' ORDER BY ' . $ob;
+ }
+ return '';
+ }
+
+ /**
* Execute a SELECT query constructed using the various parameters provided.
* See below for full details of the parameters.
*
- * @param $table String|Array Table name
- * @param $vars String|Array Field names
- * @param $conds String|Array Conditions
- * @param $fname String Caller function name
- * @param $options Array Query options
+ * @param string|array $table Table name
+ * @param string|array $vars Field names
+ * @param string|array $conds Conditions
+ * @param string $fname Caller function name
+ * @param array $options Query options
* @param $join_conds Array Join conditions
*
* @param $table string|array
@@ -1325,7 +1420,7 @@ abstract class DatabaseBase implements DatabaseType {
* join, the second is an SQL fragment giving the join condition for that
* table. For example:
*
- * array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
+ * array( 'page' => array( 'LEFT JOIN', 'page_latest=rev_id' ) )
*
* @return ResultWrapper. If the query returned no rows, a ResultWrapper
* with no rows in it will be returned. If there was a query error, a
@@ -1345,11 +1440,11 @@ abstract class DatabaseBase implements DatabaseType {
* doing UNION queries, where the SQL text of each query is needed. In general,
* however, callers outside of Database classes should just use select().
*
- * @param $table string|array Table name
- * @param $vars string|array Field names
- * @param $conds string|array Conditions
- * @param $fname string Caller function name
- * @param $options string|array Query options
+ * @param string|array $table Table name
+ * @param string|array $vars Field names
+ * @param string|array $conds Conditions
+ * @param string $fname Caller function name
+ * @param string|array $options Query options
* @param $join_conds string|array Join conditions
*
* @return string SQL query string.
@@ -1413,11 +1508,11 @@ abstract class DatabaseBase implements DatabaseType {
* that a single row object is returned. If the query returns no rows,
* false is returned.
*
- * @param $table string|array Table name
- * @param $vars string|array Field names
- * @param $conds array Conditions
- * @param $fname string Caller function name
- * @param $options string|array Query options
+ * @param string|array $table Table name
+ * @param string|array $vars Field names
+ * @param array $conds Conditions
+ * @param string $fname Caller function name
+ * @param string|array $options Query options
* @param $join_conds array|string Join conditions
*
* @return object|bool
@@ -1455,11 +1550,11 @@ abstract class DatabaseBase implements DatabaseType {
*
* Takes the same arguments as DatabaseBase::select().
*
- * @param $table String: table name
- * @param Array|string $vars : unused
- * @param Array|string $conds : filters on the table
- * @param $fname String: function name for profiling
- * @param $options Array: options for select
+ * @param string $table table name
+ * @param array|string $vars : unused
+ * @param array|string $conds : filters on the table
+ * @param string $fname function name for profiling
+ * @param array $options options for select
* @return Integer: row count
*/
public function estimateRowCount( $table, $vars = '*', $conds = '',
@@ -1480,7 +1575,7 @@ abstract class DatabaseBase implements DatabaseType {
* Removes most variables from an SQL query and replaces them with X or N for numbers.
* It's only slightly flawed. Don't use for anything important.
*
- * @param $sql String A SQL Query
+ * @param string $sql A SQL Query
*
* @return string
*/
@@ -1507,9 +1602,9 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Determines whether a field exists in a table
*
- * @param $table String: table name
- * @param $field String: filed to check on that table
- * @param $fname String: calling function name (optional)
+ * @param string $table table name
+ * @param string $field filed to check on that table
+ * @param string $fname calling function name (optional)
* @return Boolean: whether $table has filed $field
*/
public function fieldExists( $table, $field, $fname = 'DatabaseBase::fieldExists' ) {
@@ -1530,6 +1625,10 @@ abstract class DatabaseBase implements DatabaseType {
* @return bool|null
*/
public function indexExists( $table, $index, $fname = 'DatabaseBase::indexExists' ) {
+ if( !$this->tableExists( $table ) ) {
+ return null;
+ }
+
$info = $this->indexInfo( $table, $index, $fname );
if ( is_null( $info ) ) {
return null;
@@ -1626,7 +1725,7 @@ abstract class DatabaseBase implements DatabaseType {
* DatabaseBase::tableName().
* @param $a Array of rows to insert
* @param $fname String Calling function name (use __METHOD__) for logs/profiling
- * @param $options Array of options
+ * @param array $options of options
*
* @return bool
*/
@@ -1642,6 +1741,10 @@ abstract class DatabaseBase implements DatabaseType {
$options = array( $options );
}
+ $fh = null;
+ if ( isset( $options['fileHandle'] ) ) {
+ $fh = $options['fileHandle'];
+ }
$options = $this->makeInsertOptions( $options );
if ( isset( $a[0] ) && is_array( $a[0] ) ) {
@@ -1669,13 +1772,19 @@ abstract class DatabaseBase implements DatabaseType {
$sql .= '(' . $this->makeList( $a ) . ')';
}
+ if ( $fh !== null && false === fwrite( $fh, $sql ) ) {
+ return false;
+ } elseif ( $fh !== null ) {
+ return true;
+ }
+
return (bool)$this->query( $sql, $fname );
}
/**
* Make UPDATE options for the DatabaseBase::update function
*
- * @param $options Array: The options passed to DatabaseBase::update
+ * @param array $options The options passed to DatabaseBase::update
* @return string
*/
protected function makeUpdateOptions( $options ) {
@@ -1702,7 +1811,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $table String name of the table to UPDATE. This will be passed through
* DatabaseBase::tableName().
*
- * @param $values Array: An array of values to SET. For each array element,
+ * @param array $values An array of values to SET. For each array element,
* the key gives the field name, and the value gives the data
* to set that field to. The data will be quoted by
* DatabaseBase::addQuotes().
@@ -1714,7 +1823,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param $fname String: The function name of the caller (from __METHOD__),
* for logging and profiling.
*
- * @param $options Array: An array of UPDATE options, can be:
+ * @param array $options An array of UPDATE options, can be:
* - IGNORE: Ignore unique key conflicts
* - LOW_PRIORITY: MySQL-specific, see MySQL manual.
* @return Boolean
@@ -1733,8 +1842,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Makes an encoded list of strings from an array
- * @param $a Array containing the data
- * @param $mode int Constant
+ * @param array $a containing the data
+ * @param int $mode Constant
* - LIST_COMMA: comma separated, no field names
* - LIST_AND: ANDed WHERE clause (without the WHERE). See
* the documentation for $conds in DatabaseBase::select().
@@ -1742,6 +1851,7 @@ abstract class DatabaseBase implements DatabaseType {
* - LIST_SET: comma separated with field names, like a SET clause
* - LIST_NAMES: comma separated field names
*
+ * @throws MWException|DBUnexpectedError
* @return string
*/
public function makeList( $a, $mode = LIST_COMMA ) {
@@ -1771,7 +1881,7 @@ abstract class DatabaseBase implements DatabaseType {
$list .= "$value";
} elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
if ( count( $value ) == 0 ) {
- throw new MWException( __METHOD__ . ': empty input' );
+ throw new MWException( __METHOD__ . ": empty input for field $field" );
} elseif ( count( $value ) == 1 ) {
// Special-case single values, as IN isn't terribly efficient
// Don't necessarily assume the single key is 0; we don't
@@ -1803,10 +1913,10 @@ abstract class DatabaseBase implements DatabaseType {
* Build a partial where clause from a 2-d array such as used for LinkBatch.
* The keys on each level may be either integers or strings.
*
- * @param $data Array: organized as 2-d
+ * @param array $data organized as 2-d
* array(baseKeyVal => array(subKeyVal => [ignored], ...), ...)
- * @param $baseKey String: field name to match the base-level keys to (eg 'pl_namespace')
- * @param $subKey String: field name to match the sub-level keys to (eg 'pl_title')
+ * @param string $baseKey field name to match the base-level keys to (eg 'pl_namespace')
+ * @param string $subKey field name to match the sub-level keys to (eg 'pl_title')
* @return Mixed: string SQL fragment, or false if no items in array.
*/
public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
@@ -1868,7 +1978,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Build a concatenation list to feed into a SQL query
- * @param $stringList Array: list of raw SQL expressions; caller is responsible for any quoting
+ * @param array $stringList list of raw SQL expressions; caller is responsible for any quoting
* @return String
*/
public function buildConcat( $stringList ) {
@@ -1916,8 +2026,8 @@ abstract class DatabaseBase implements DatabaseType {
* themselves. Pass the canonical name to such functions. This is only needed
* when calling query() directly.
*
- * @param $name String: database table name
- * @param $format String One of:
+ * @param string $name database table name
+ * @param string $format One of:
* quoted - Automatically pass the table name through addIdentifierQuotes()
* so that it can be used in a query.
* raw - Do not add identifier quotes to the table name
@@ -1947,47 +2057,39 @@ abstract class DatabaseBase implements DatabaseType {
# Split database and table into proper variables.
# We reverse the explode so that database.table and table both output
# the correct table.
- $dbDetails = array_reverse( explode( '.', $name, 2 ) );
- if ( isset( $dbDetails[1] ) ) {
- list( $table, $database ) = $dbDetails;
+ $dbDetails = explode( '.', $name, 2 );
+ if ( count( $dbDetails ) == 2 ) {
+ list( $database, $table ) = $dbDetails;
+ # We don't want any prefix added in this case
+ $prefix = '';
} else {
list( $table ) = $dbDetails;
- }
- $prefix = $this->mTablePrefix; # Default prefix
-
- # A database name has been specified in input. We don't want any
- # prefixes added.
- if ( isset( $database ) ) {
- $prefix = '';
+ if ( $wgSharedDB !== null # We have a shared database
+ && !$this->isQuotedIdentifier( $table ) # Paranoia check to prevent shared tables listing '`table`'
+ && in_array( $table, $wgSharedTables ) # A shared table is selected
+ ) {
+ $database = $wgSharedDB;
+ $prefix = $wgSharedPrefix === null ? $this->mTablePrefix : $wgSharedPrefix;
+ } else {
+ $database = null;
+ $prefix = $this->mTablePrefix; # Default prefix
+ }
}
- # Note that we use the long format because php will complain in in_array if
- # the input is not an array, and will complain in is_array if it is not set.
- if ( !isset( $database ) # Don't use shared database if pre selected.
- && isset( $wgSharedDB ) # We have a shared database
- && !$this->isQuotedIdentifier( $table ) # Paranoia check to prevent shared tables listing '`table`'
- && isset( $wgSharedTables )
- && is_array( $wgSharedTables )
- && in_array( $table, $wgSharedTables ) ) { # A shared table is selected
- $database = $wgSharedDB;
- $prefix = isset( $wgSharedPrefix ) ? $wgSharedPrefix : $prefix;
+ # Quote $table and apply the prefix if not quoted.
+ $tableName = "{$prefix}{$table}";
+ if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) ) {
+ $tableName = $this->addIdentifierQuotes( $tableName );
}
- # Quote the $database and $table and apply the prefix if not quoted.
- if ( isset( $database ) ) {
+ # Quote $database and merge it with the table name if needed
+ if ( $database !== null ) {
if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) {
$database = $this->addIdentifierQuotes( $database );
}
+ $tableName = $database . '.' . $tableName;
}
- $table = "{$prefix}{$table}";
- if ( $format == 'quoted' && !$this->isQuotedIdentifier( $table ) ) {
- $table = $this->addIdentifierQuotes( "{$table}" );
- }
-
- # Merge our database and table into our final table name.
- $tableName = ( isset( $database ) ? "{$database}.{$table}" : "{$table}" );
-
return $tableName;
}
@@ -1996,7 +2098,7 @@ abstract class DatabaseBase implements DatabaseType {
* This is handy when you need to construct SQL for joins
*
* Example:
- * extract($dbr->tableNames('user','watchlist'));
+ * extract( $dbr->tableNames( 'user', 'watchlist' ) );
* $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user
* WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
*
@@ -2018,7 +2120,7 @@ abstract class DatabaseBase implements DatabaseType {
* This is handy when you need to construct SQL for joins
*
* Example:
- * list( $user, $watchlist ) = $dbr->tableNamesN('user','watchlist');
+ * list( $user, $watchlist ) = $dbr->tableNamesN( 'user', 'watchlist' );
* $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user
* WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
*
@@ -2039,8 +2141,8 @@ abstract class DatabaseBase implements DatabaseType {
* Get an aliased table name
* e.g. tableName AS newTableName
*
- * @param $name string Table name, see tableName()
- * @param $alias string|bool Alias (optional)
+ * @param string $name Table name, see tableName()
+ * @param string|bool $alias Alias (optional)
* @return string SQL name for aliased table. Will not alias a table to its own name
*/
public function tableNameWithAlias( $name, $alias = false ) {
@@ -2072,8 +2174,8 @@ abstract class DatabaseBase implements DatabaseType {
* Get an aliased field name
* e.g. fieldName AS newFieldName
*
- * @param $name string Field name
- * @param $alias string|bool Alias (optional)
+ * @param string $name Field name
+ * @param string|bool $alias Alias (optional)
* @return string SQL name for aliased field. Will not alias a field to its own name
*/
public function fieldNameWithAlias( $name, $alias = false ) {
@@ -2105,7 +2207,7 @@ abstract class DatabaseBase implements DatabaseType {
* Get the aliased table name clause for a FROM clause
* which might have a JOIN and/or USE INDEX clause
*
- * @param $tables array ( [alias] => table )
+ * @param array $tables ( [alias] => table )
* @param $use_index array Same as for select()
* @param $join_conds array Same as for select()
* @return string
@@ -2336,12 +2438,12 @@ abstract class DatabaseBase implements DatabaseType {
* to collide. However if you do this, you run the risk of encountering
* errors which wouldn't have occurred in MySQL.
*
- * @param $table String: The table to replace the row(s) in.
- * @param $rows array Can be either a single row to insert, or multiple rows,
+ * @param string $table The table to replace the row(s) in.
+ * @param array $rows Can be either a single row to insert, or multiple rows,
* in the same format as for DatabaseBase::insert()
- * @param $uniqueIndexes array is an array of indexes. Each element may be either
+ * @param array $uniqueIndexes is an array of indexes. Each element may be either
* a field name or an array of field names
- * @param $fname String: Calling function name (use __METHOD__) for logs/profiling
+ * @param string $fname Calling function name (use __METHOD__) for logs/profiling
*/
public function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) {
$quotedTable = $this->tableName( $table );
@@ -2394,9 +2496,9 @@ abstract class DatabaseBase implements DatabaseType {
* REPLACE query wrapper for MySQL and SQLite, which have a native REPLACE
* statement.
*
- * @param $table string Table name
- * @param $rows array Rows to insert
- * @param $fname string Caller function name
+ * @param string $table Table name
+ * @param array $rows Rows to insert
+ * @param string $fname Caller function name
*
* @return ResultWrapper
*/
@@ -2443,6 +2545,7 @@ abstract class DatabaseBase implements DatabaseType {
* ANDed together in the WHERE clause
* @param $fname String: Calling function name (use __METHOD__) for
* logs/profiling
+ * @throws DBUnexpectedError
*/
public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
$fname = 'DatabaseBase::deleteJoin' )
@@ -2503,12 +2606,13 @@ abstract class DatabaseBase implements DatabaseType {
/**
* DELETE query wrapper.
*
- * @param $table Array Table name
- * @param $conds String|Array of conditions. See $conds in DatabaseBase::select() for
+ * @param array $table Table name
+ * @param string|array $conds of conditions. See $conds in DatabaseBase::select() for
* the format. Use $conds == "*" to delete all rows
- * @param $fname String name of the calling function
+ * @param string $fname name of the calling function
*
- * @return bool
+ * @throws DBUnexpectedError
+ * @return bool|ResultWrapper
*/
public function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) {
if ( !$conds ) {
@@ -2529,24 +2633,24 @@ abstract class DatabaseBase implements DatabaseType {
* INSERT SELECT wrapper. Takes data from a SELECT query and inserts it
* into another table.
*
- * @param $destTable string The table name to insert into
- * @param $srcTable string|array May be either a table name, or an array of table names
+ * @param string $destTable The table name to insert into
+ * @param string|array $srcTable May be either a table name, or an array of table names
* to include in a join.
*
- * @param $varMap array must be an associative array of the form
+ * @param array $varMap must be an associative array of the form
* array( 'dest1' => 'source1', ...). Source items may be literals
* rather than field names, but strings should be quoted with
* DatabaseBase::addQuotes()
*
- * @param $conds array Condition array. See $conds in DatabaseBase::select() for
+ * @param array $conds Condition array. See $conds in DatabaseBase::select() for
* the details of the format of condition arrays. May be "*" to copy the
* whole table.
*
- * @param $fname string The function name of the caller, from __METHOD__
+ * @param string $fname The function name of the caller, from __METHOD__
*
- * @param $insertOptions array Options for the INSERT part of the query, see
+ * @param array $insertOptions Options for the INSERT part of the query, see
* DatabaseBase::insert() for details.
- * @param $selectOptions array Options for the SELECT part of the query, see
+ * @param array $selectOptions Options for the SELECT part of the query, see
* DatabaseBase::select() for details.
*
* @return ResultWrapper
@@ -2568,7 +2672,7 @@ abstract class DatabaseBase implements DatabaseType {
list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
if ( is_array( $srcTable ) ) {
- $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
+ $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
} else {
$srcTable = $this->tableName( $srcTable );
}
@@ -2602,10 +2706,11 @@ abstract class DatabaseBase implements DatabaseType {
* The version provided by default works in MySQL and SQLite. It will very
* likely need to be overridden for most other DBMSes.
*
- * @param $sql String SQL query we will append the limit too
+ * @param string $sql SQL query we will append the limit too
* @param $limit Integer the SQL limit
* @param $offset Integer|bool the SQL offset (default false)
*
+ * @throws DBUnexpectedError
* @return string
*/
public function limitResult( $sql, $limit, $offset = false ) {
@@ -2630,7 +2735,7 @@ abstract class DatabaseBase implements DatabaseType {
* Construct a UNION query
* This is used for providing overload point for other DB abstractions
* not compatible with the MySQL syntax.
- * @param $sqls Array: SQL statements to combine
+ * @param array $sqls SQL statements to combine
* @param $all Boolean: use UNION ALL
* @return String: SQL fragment
*/
@@ -2643,9 +2748,9 @@ abstract class DatabaseBase implements DatabaseType {
* Returns an SQL expression for a simple conditional. This doesn't need
* to be overridden unless CASE isn't supported in your DBMS.
*
- * @param $cond string|array SQL expression which will result in a boolean value
- * @param $trueVal String: SQL expression to return if true
- * @param $falseVal String: SQL expression to return if false
+ * @param string|array $cond SQL expression which will result in a boolean value
+ * @param string $trueVal SQL expression to return if true
+ * @param string $falseVal SQL expression to return if false
* @return String: SQL fragment
*/
public function conditional( $cond, $trueVal, $falseVal ) {
@@ -2659,9 +2764,9 @@ abstract class DatabaseBase implements DatabaseType {
* Returns a comand for str_replace function in SQL query.
* Uses REPLACE() in MySQL
*
- * @param $orig String: column to modify
- * @param $old String: column to seek
- * @param $new String: column to replace with
+ * @param string $orig column to modify
+ * @param string $old column to seek
+ * @param string $new column to replace with
*
* @return string
*/
@@ -2854,8 +2959,9 @@ abstract class DatabaseBase implements DatabaseType {
*
* This is useful for updates to different systems or separate transactions are needed.
*
+ * @since 1.20
+ *
* @param Closure $callback
- * @return void
*/
final public function onTransactionIdle( Closure $callback ) {
if ( $this->mTrxLevel ) {
@@ -2866,7 +2972,9 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Actually run the "on transaction idle" callbacks
+ * Actually run the "on transaction idle" callbacks.
+ *
+ * @since 1.20
*/
protected function runOnTransactionIdleCallbacks() {
$autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled?
@@ -2890,19 +2998,50 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Begin a transaction
+ * Begin a transaction. If a transaction is already in progress, that transaction will be committed before the
+ * new transaction is started.
+ *
+ * Note that when the DBO_TRX flag is set (which is usually the case for web requests, but not for maintenance scripts),
+ * any previous database query will have started a transaction automatically.
+ *
+ * Nesting of transactions is not supported. Attempts to nest transactions will cause a warning, unless the current
+ * transaction was started automatically because of the DBO_TRX flag.
*
* @param $fname string
*/
final public function begin( $fname = 'DatabaseBase::begin' ) {
+ global $wgDebugDBTransactions;
+
if ( $this->mTrxLevel ) { // implicit commit
+ if ( !$this->mTrxAutomatic ) {
+ // We want to warn about inadvertently nested begin/commit pairs, but not about
+ // auto-committing implicit transactions that were started by query() via DBO_TRX
+ $msg = "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
+ " performing implicit commit!";
+ wfWarn( $msg );
+ wfLogDBError( $msg );
+ } else {
+ // if the transaction was automatic and has done write operations,
+ // log it if $wgDebugDBTransactions is enabled.
+ if ( $this->mTrxDoneWrites && $wgDebugDBTransactions ) {
+ wfDebug( "$fname: Automatic transaction with writes in progress" .
+ " (from {$this->mTrxFname}), performing implicit commit!\n" );
+ }
+ }
+
$this->doCommit( $fname );
$this->runOnTransactionIdleCallbacks();
}
+
$this->doBegin( $fname );
+ $this->mTrxFname = $fname;
+ $this->mTrxDoneWrites = false;
+ $this->mTrxAutomatic = false;
}
/**
+ * Issues the BEGIN command to the database server.
+ *
* @see DatabaseBase::begin()
* @param type $fname
*/
@@ -2912,16 +3051,39 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * End a transaction
+ * Commits a transaction previously started using begin().
+ * If no transaction is in progress, a warning is issued.
+ *
+ * Nesting of transactions is not supported.
*
* @param $fname string
- */
- final public function commit( $fname = 'DatabaseBase::commit' ) {
+ * @param string $flush Flush flag, set to 'flush' to disable warnings about explicitly committing implicit
+ * transactions, or calling commit when no transaction is in progress.
+ * This will silently break any ongoing explicit transaction. Only set the flush flag if you are sure
+ * that it is safe to ignore these warnings in your context.
+ */
+ final public function commit( $fname = 'DatabaseBase::commit', $flush = '' ) {
+ if ( $flush != 'flush' ) {
+ if ( !$this->mTrxLevel ) {
+ wfWarn( "$fname: No transaction to commit, something got out of sync!" );
+ } elseif( $this->mTrxAutomatic ) {
+ wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" );
+ }
+ } else {
+ if ( !$this->mTrxLevel ) {
+ return; // nothing to do
+ } elseif( !$this->mTrxAutomatic ) {
+ wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" );
+ }
+ }
+
$this->doCommit( $fname );
$this->runOnTransactionIdleCallbacks();
}
/**
+ * Issues the COMMIT command to the database server.
+ *
* @see DatabaseBase::commit()
* @param type $fname
*/
@@ -2933,17 +3095,24 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Rollback a transaction.
+ * Rollback a transaction previously started using begin().
+ * If no transaction is in progress, a warning is issued.
+ *
* No-op on non-transactional databases.
*
* @param $fname string
*/
final public function rollback( $fname = 'DatabaseBase::rollback' ) {
+ if ( !$this->mTrxLevel ) {
+ wfWarn( "$fname: No transaction to rollback, something got out of sync!" );
+ }
$this->doRollback( $fname );
$this->mTrxIdleCallbacks = array(); // cancel
}
/**
+ * Issues the ROLLBACK command to the database server.
+ *
* @see DatabaseBase::rollback()
* @param type $fname
*/
@@ -2962,10 +3131,11 @@ abstract class DatabaseBase implements DatabaseType {
* The table names passed to this function shall not be quoted (this
* function calls addIdentifierQuotes when needed).
*
- * @param $oldName String: name of table whose structure should be copied
- * @param $newName String: name of table to be created
+ * @param string $oldName name of table whose structure should be copied
+ * @param string $newName name of table to be created
* @param $temporary Boolean: whether the new table should be temporary
- * @param $fname String: calling function name
+ * @param string $fname calling function name
+ * @throws MWException
* @return Boolean: true if operation was successful
*/
public function duplicateTableStructure( $oldName, $newName, $temporary = false,
@@ -2978,8 +3148,9 @@ abstract class DatabaseBase implements DatabaseType {
/**
* List all tables on the database
*
- * @param $prefix string Only show tables with this prefix, e.g. mw_
- * @param $fname String: calling function name
+ * @param string $prefix Only show tables with this prefix, e.g. mw_
+ * @param string $fname calling function name
+ * @throws MWException
*/
function listTables( $prefix = null, $fname = 'DatabaseBase::listTables' ) {
throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' );
@@ -3122,15 +3293,18 @@ abstract class DatabaseBase implements DatabaseType {
* Returns true on success, error string or exception on failure (depending
* on object's error ignore settings).
*
- * @param $filename String: File name to open
- * @param $lineCallback Callback: Optional function called before reading each line
- * @param $resultCallback Callback: Optional function called for each MySQL result
- * @param $fname String: Calling function name or false if name should be
+ * @param string $filename File name to open
+ * @param bool|callable $lineCallback Optional function called before reading each line
+ * @param bool|callable $resultCallback Optional function called for each MySQL result
+ * @param bool|string $fname Calling function name or false if name should be
* generated dynamically using $filename
+ * @param bool|callable $inputCallback Callback: Optional function called for each complete line sent
+ * @throws MWException
+ * @throws Exception|MWException
* @return bool|string
*/
public function sourceFile(
- $filename, $lineCallback = false, $resultCallback = false, $fname = false
+ $filename, $lineCallback = false, $resultCallback = false, $fname = false, $inputCallback = false
) {
wfSuppressWarnings();
$fp = fopen( $filename, 'r' );
@@ -3145,7 +3319,7 @@ abstract class DatabaseBase implements DatabaseType {
}
try {
- $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname );
+ $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
}
catch ( MWException $e ) {
fclose( $fp );
@@ -3162,7 +3336,7 @@ abstract class DatabaseBase implements DatabaseType {
* from updaters.inc. Keep in mind this always returns a patch, as
* it fails back to MySQL if no DB-specific patch can be found
*
- * @param $patch String The name of the patch, like patch-something.sql
+ * @param string $patch The name of the patch, like patch-something.sql
* @return String Full path to patch file
*/
public function patchPath( $patch ) {
@@ -3181,7 +3355,7 @@ abstract class DatabaseBase implements DatabaseType {
* ones in $GLOBALS. If an array is set here, $GLOBALS will not be used at
* all. If it's set to false, $GLOBALS will be used.
*
- * @param $vars bool|array mapping variable name to value.
+ * @param bool|array $vars mapping variable name to value.
*/
public function setSchemaVars( $vars ) {
$this->mSchemaVars = $vars;
@@ -3194,10 +3368,10 @@ abstract class DatabaseBase implements DatabaseType {
* on object's error ignore settings).
*
* @param $fp Resource: File handle
- * @param $lineCallback Callback: Optional function called before reading each line
+ * @param $lineCallback Callback: Optional function called before reading each query
* @param $resultCallback Callback: Optional function called for each MySQL result
- * @param $fname String: Calling function name
- * @param $inputCallback Callback: Optional function called for each complete line (ended with ;) sent
+ * @param string $fname Calling function name
+ * @param $inputCallback Callback: Optional function called for each complete query sent
* @return bool|string
*/
public function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
@@ -3230,20 +3404,19 @@ abstract class DatabaseBase implements DatabaseType {
if ( $done || feof( $fp ) ) {
$cmd = $this->replaceVars( $cmd );
- if ( $inputCallback ) {
- call_user_func( $inputCallback, $cmd );
- }
- $res = $this->query( $cmd, $fname );
- if ( $resultCallback ) {
- call_user_func( $resultCallback, $res, $this );
- }
+ if ( ( $inputCallback && call_user_func( $inputCallback, $cmd ) ) || !$inputCallback ) {
+ $res = $this->query( $cmd, $fname );
- if ( false === $res ) {
- $err = $this->lastError();
- return "Query \"{$cmd}\" failed with error code \"$err\".\n";
- }
+ if ( $resultCallback ) {
+ call_user_func( $resultCallback, $res, $this );
+ }
+ if ( false === $res ) {
+ $err = $this->lastError();
+ return "Query \"{$cmd}\" failed with error code \"$err\".\n";
+ }
+ }
$cmd = '';
}
}
@@ -3254,8 +3427,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Called by sourceStream() to check if we've reached a statement end
*
- * @param $sql String SQL assembled so far
- * @param $newLine String New line about to be added to $sql
+ * @param string $sql SQL assembled so far
+ * @param string $newLine New line about to be added to $sql
* @return Bool Whether $newLine contains end of the statement
*/
public function streamStatementEnd( &$sql, &$newLine ) {
@@ -3283,7 +3456,7 @@ abstract class DatabaseBase implements DatabaseType {
* - / *$var* / is just encoded, besides traditional table prefix and
* table options its use should be avoided.
*
- * @param $ins String: SQL statement to replace variables in
+ * @param string $ins SQL statement to replace variables in
* @return String The new SQL statement with variables replaced
*/
protected function replaceSchemaVars( $ins ) {
@@ -3294,7 +3467,7 @@ abstract class DatabaseBase implements DatabaseType {
// replace `{$var}`
$ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins );
// replace /*$var*/
- $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ) , $ins );
+ $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ), $ins );
}
return $ins;
}
@@ -3371,8 +3544,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Check to see if a named lock is available. This is non-blocking.
*
- * @param $lockName String: name of lock to poll
- * @param $method String: name of method calling us
+ * @param string $lockName name of lock to poll
+ * @param string $method name of method calling us
* @return Boolean
* @since 1.20
*/
@@ -3386,8 +3559,8 @@ abstract class DatabaseBase implements DatabaseType {
* Abstracted from Filestore::lock() so child classes can implement for
* their own needs.
*
- * @param $lockName String: name of lock to aquire
- * @param $method String: name of method calling us
+ * @param string $lockName name of lock to aquire
+ * @param string $method name of method calling us
* @param $timeout Integer: timeout
* @return Boolean
*/
@@ -3398,8 +3571,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Release a lock.
*
- * @param $lockName String: Name of lock to release
- * @param $method String: Name of method calling us
+ * @param string $lockName Name of lock to release
+ * @param string $method Name of method calling us
*
* @return int Returns 1 if the lock was released, 0 if the lock was not established
* by this thread (in which case the lock is not released), and NULL if the named
@@ -3412,10 +3585,10 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Lock specific tables
*
- * @param $read Array of tables to lock for read access
- * @param $write Array of tables to lock for write access
- * @param $method String name of caller
- * @param $lowPriority bool Whether to indicate writes to be LOW PRIORITY
+ * @param array $read of tables to lock for read access
+ * @param array $write of tables to lock for write access
+ * @param string $method name of caller
+ * @param bool $lowPriority Whether to indicate writes to be LOW PRIORITY
*
* @return bool
*/
@@ -3426,7 +3599,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Unlock specific tables
*
- * @param $method String the caller
+ * @param string $method the caller
*
* @return bool
*/
@@ -3476,7 +3649,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Encode an expiry time into the DBMS dependent format
*
- * @param $expiry String: timestamp for expiry, or the 'infinity' string
+ * @param string $expiry timestamp for expiry, or the 'infinity' string
* @return String
*/
public function encodeExpiry( $expiry ) {
@@ -3488,7 +3661,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Decode an expiry time into a DBMS independent format
*
- * @param $expiry String: DB timestamp field value for expiry
+ * @param string $expiry DB timestamp field value for expiry
* @param $format integer: TS_* constant, defaults to TS_MW
* @return String
*/
diff --git a/includes/db/DatabaseError.php b/includes/db/DatabaseError.php
index a53a6747..628a2afc 100644
--- a/includes/db/DatabaseError.php
+++ b/includes/db/DatabaseError.php
@@ -35,9 +35,9 @@ class DBError extends MWException {
/**
* Construct a database error
* @param $db DatabaseBase object which threw the error
- * @param $error String A simple error message to be used for debugging
+ * @param string $error A simple error message to be used for debugging
*/
- function __construct( DatabaseBase &$db, $error ) {
+ function __construct( DatabaseBase $db = null, $error ) {
$this->db = $db;
parent::__construct( $error );
}
@@ -91,7 +91,7 @@ class DBError extends MWException {
class DBConnectionError extends DBError {
public $error;
- function __construct( DatabaseBase &$db, $error = 'unknown error' ) {
+ function __construct( DatabaseBase $db = null, $error = 'unknown error' ) {
$msg = 'DB connection error';
if ( trim( $error ) != '' ) {
@@ -153,12 +153,12 @@ class DBConnectionError extends DBError {
$sorry = htmlspecialchars( $this->msg( 'dberr-problems', 'Sorry! This site is experiencing technical difficulties.' ) );
$again = htmlspecialchars( $this->msg( 'dberr-again', 'Try waiting a few minutes and reloading.' ) );
- $info = htmlspecialchars( $this->msg( 'dberr-info', '(Can\'t contact the database server: $1)' ) );
+ $info = htmlspecialchars( $this->msg( 'dberr-info', '(Can\'t contact the database server: $1)' ) );
# No database access
MessageCache::singleton()->disable();
- if ( trim( $this->error ) == '' ) {
+ if ( trim( $this->error ) == '' && $this->db ) {
$this->error = $this->db->getProperty( 'mServer' );
}
@@ -176,7 +176,7 @@ class DBConnectionError extends DBError {
return "$text<hr />$extra";
}
- public function reportHTML(){
+ public function reportHTML() {
global $wgUseFileCache;
# Check whether we can serve a file-cached copy of the page with the error underneath
@@ -288,11 +288,11 @@ class DBQueryError extends DBError {
* @param $sql string
* @param $fname string
*/
- function __construct( DatabaseBase &$db, $error, $errno, $sql, $fname ) {
- $message = "A database error has occurred. Did you forget to run maintenance/update.php after upgrading? See: https://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" .
- "Query: $sql\n" .
- "Function: $fname\n" .
- "Error: $errno $error\n";
+ function __construct( DatabaseBase $db, $error, $errno, $sql, $fname ) {
+ $message = "A database error has occurred. Did you forget to run maintenance/update.php after upgrading? See: https://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" .
+ "Query: $sql\n" .
+ "Function: $fname\n" .
+ "Error: $errno $error\n";
parent::__construct( $db, $message );
$this->error = $error;
diff --git a/includes/db/DatabaseIbm_db2.php b/includes/db/DatabaseIbm_db2.php
deleted file mode 100644
index f1f6dfca..00000000
--- a/includes/db/DatabaseIbm_db2.php
+++ /dev/null
@@ -1,1721 +0,0 @@
-<?php
-/**
- * This is the IBM DB2 database abstraction layer.
- * See maintenance/ibm_db2/README for development notes
- * and other specific information.
- *
- * 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 Database
- * @author leo.petr+mediawiki@gmail.com
- */
-
-/**
- * This represents a column in a DB2 database
- * @ingroup Database
- */
-class IBM_DB2Field implements Field {
- private $name = '';
- private $tablename = '';
- private $type = '';
- private $nullable = false;
- private $max_length = 0;
-
- /**
- * Builder method for the class
- * @param $db DatabaseIbm_db2: Database interface
- * @param $table String: table name
- * @param $field String: column name
- * @return IBM_DB2Field
- */
- static function fromText( $db, $table, $field ) {
- global $wgDBmwschema;
-
- $q = <<<SQL
-SELECT
-lcase( coltype ) AS typname,
-nulls AS attnotnull, length AS attlen
-FROM sysibm.syscolumns
-WHERE tbcreator=%s AND tbname=%s AND name=%s;
-SQL;
- $res = $db->query(
- sprintf( $q,
- $db->addQuotes( $wgDBmwschema ),
- $db->addQuotes( $table ),
- $db->addQuotes( $field )
- )
- );
- $row = $db->fetchObject( $res );
- if ( !$row ) {
- return null;
- }
- $n = new IBM_DB2Field;
- $n->type = $row->typname;
- $n->nullable = ( $row->attnotnull == 'N' );
- $n->name = $field;
- $n->tablename = $table;
- $n->max_length = $row->attlen;
- return $n;
- }
- /**
- * Get column name
- * @return string column name
- */
- function name() { return $this->name; }
- /**
- * Get table name
- * @return string table name
- */
- function tableName() { return $this->tablename; }
- /**
- * Get column type
- * @return string column type
- */
- function type() { return $this->type; }
- /**
- * Can column be null?
- * @return bool true or false
- */
- function isNullable() { return $this->nullable; }
- /**
- * How much can you fit in the column per row?
- * @return int length
- */
- function maxLength() { return $this->max_length; }
-}
-
-/**
- * Wrapper around binary large objects
- * @ingroup Database
- */
-class IBM_DB2Blob {
- private $mData;
-
- public function __construct( $data ) {
- $this->mData = $data;
- }
-
- public function getData() {
- return $this->mData;
- }
-
- public function __toString() {
- return $this->mData;
- }
-}
-
-/**
- * Wrapper to address lack of certain operations in the DB2 driver
- * ( seek, num_rows )
- * @ingroup Database
- * @since 1.19
- */
-class IBM_DB2Result{
- private $db;
- private $result;
- private $num_rows;
- private $current_pos;
- private $columns = array();
- private $sql;
-
- private $resultSet = array();
- private $loadedLines = 0;
-
- /**
- * Construct and initialize a wrapper for DB2 query results
- * @param $db DatabaseBase
- * @param $result Object
- * @param $num_rows Integer
- * @param $sql String
- * @param $columns Array
- */
- public function __construct( $db, $result, $num_rows, $sql, $columns ){
- $this->db = $db;
-
- if( $result instanceof ResultWrapper ){
- $this->result = $result->result;
- }
- else{
- $this->result = $result;
- }
-
- $this->num_rows = $num_rows;
- $this->current_pos = 0;
- if ( $this->num_rows > 0 ) {
- // Make a lower-case list of the column names
- // By default, DB2 column names are capitalized
- // while MySQL column names are lowercase
-
- // Is there a reasonable maximum value for $i?
- // Setting to 2048 to prevent an infinite loop
- for( $i = 0; $i < 2048; $i++ ) {
- $name = db2_field_name( $this->result, $i );
- if ( $name != false ) {
- continue;
- }
- else {
- return false;
- }
-
- $this->columns[$i] = strtolower( $name );
- }
- }
-
- $this->sql = $sql;
- }
-
- /**
- * Unwrap the DB2 query results
- * @return mixed Object on success, false on failure
- */
- public function getResult() {
- if ( $this->result ) {
- return $this->result;
- }
- else return false;
- }
-
- /**
- * Get the number of rows in the result set
- * @return integer
- */
- public function getNum_rows() {
- return $this->num_rows;
- }
-
- /**
- * Return a row from the result set in object format
- * @return mixed Object on success, false on failure.
- */
- public function fetchObject() {
- if ( $this->result
- && $this->num_rows > 0
- && $this->current_pos >= 0
- && $this->current_pos < $this->num_rows )
- {
- $row = $this->fetchRow();
- $ret = new stdClass();
-
- foreach ( $row as $k => $v ) {
- $lc = $this->columns[$k];
- $ret->$lc = $v;
- }
- return $ret;
- }
- return false;
- }
-
- /**
- * Return a row form the result set in array format
- * @return mixed Array on success, false on failure
- * @throws DBUnexpectedError
- */
- public function fetchRow(){
- if ( $this->result
- && $this->num_rows > 0
- && $this->current_pos >= 0
- && $this->current_pos < $this->num_rows )
- {
- if ( $this->loadedLines <= $this->current_pos ) {
- $row = db2_fetch_array( $this->result );
- $this->resultSet[$this->loadedLines++] = $row;
- if ( $this->db->lastErrno() ) {
- throw new DBUnexpectedError( $this->db, 'Error in fetchRow(): '
- . htmlspecialchars( $this->db->lastError() ) );
- }
- }
-
- if ( $this->loadedLines > $this->current_pos ){
- return $this->resultSet[$this->current_pos++];
- }
-
- }
- return false;
- }
-
- /**
- * Free a DB2 result object
- * @throws DBUnexpectedError
- */
- public function freeResult(){
- unset( $this->resultSet );
- if ( !@db2_free_result( $this->result ) ) {
- throw new DBUnexpectedError( $this, "Unable to free DB2 result\n" );
- }
- }
-}
-
-/**
- * Primary database interface
- * @ingroup Database
- */
-class DatabaseIbm_db2 extends DatabaseBase {
- /*
- * Inherited members
- protected $mLastQuery = '';
- protected $mPHPError = false;
-
- protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname;
- protected $mOpened = false;
-
- protected $mTablePrefix;
- protected $mFlags;
- protected $mTrxLevel = 0;
- protected $mErrorCount = 0;
- protected $mLBInfo = array();
- protected $mFakeSlaveLag = null, $mFakeMaster = false;
- *
- */
-
- /** Database server port */
- protected $mPort = null;
- /** Schema for tables, stored procedures, triggers */
- protected $mSchema = null;
- /** Whether the schema has been applied in this session */
- protected $mSchemaSet = false;
- /** Result of last query */
- protected $mLastResult = null;
- /** Number of rows affected by last INSERT/UPDATE/DELETE */
- protected $mAffectedRows = null;
- /** Number of rows returned by last SELECT */
- protected $mNumRows = null;
- /** Current row number on the cursor of the last SELECT */
- protected $currentRow = 0;
-
- /** Connection config options - see constructor */
- public $mConnOptions = array();
- /** Statement config options -- see constructor */
- public $mStmtOptions = array();
-
- /** Default schema */
- const USE_GLOBAL = 'get from global';
-
- /** Option that applies to nothing */
- const NONE_OPTION = 0x00;
- /** Option that applies to connection objects */
- const CONN_OPTION = 0x01;
- /** Option that applies to statement objects */
- const STMT_OPTION = 0x02;
-
- /** Regular operation mode -- minimal debug messages */
- const REGULAR_MODE = 'regular';
- /** Installation mode -- lots of debug messages */
- const INSTALL_MODE = 'install';
-
- /** Controls the level of debug message output */
- protected $mMode = self::REGULAR_MODE;
-
- /** Last sequence value used for a primary key */
- protected $mInsertId = null;
-
- ######################################
- # Getters and Setters
- ######################################
-
- /**
- * Returns true if this database supports (and uses) cascading deletes
- * @return bool
- */
- function cascadingDeletes() {
- return true;
- }
-
- /**
- * Returns true if this database supports (and uses) triggers (e.g. on the
- * page table)
- * @return bool
- */
- function cleanupTriggers() {
- return true;
- }
-
- /**
- * Returns true if this database is strict about what can be put into an
- * IP field.
- * Specifically, it uses a NULL value instead of an empty string.
- * @return bool
- */
- function strictIPs() {
- return true;
- }
-
- /**
- * Returns true if this database uses timestamps rather than integers
- * @return bool
- */
- function realTimestamps() {
- return true;
- }
-
- /**
- * Returns true if this database does an implicit sort when doing GROUP BY
- * @return bool
- */
- function implicitGroupby() {
- return false;
- }
-
- /**
- * Returns true if this database does an implicit order by when the column
- * has an index
- * For example: SELECT page_title FROM page LIMIT 1
- * @return bool
- */
- function implicitOrderby() {
- return false;
- }
-
- /**
- * Returns true if this database can do a native search on IP columns
- * e.g. this works as expected: .. WHERE rc_ip = '127.42.12.102/32';
- * @return bool
- */
- function searchableIPs() {
- return true;
- }
-
- /**
- * Returns true if this database can use functional indexes
- * @return bool
- */
- function functionalIndexes() {
- return true;
- }
-
- /**
- * Returns a unique string representing the wiki on the server
- * @return string
- */
- public function getWikiID() {
- if( $this->mSchema ) {
- return "{$this->mDBname}-{$this->mSchema}";
- } else {
- return $this->mDBname;
- }
- }
-
- /**
- * Returns the database software identifieir
- * @return string
- */
- public function getType() {
- return 'ibm_db2';
- }
-
- /**
- * Returns the database connection object
- * @return Object
- */
- public function getDb(){
- return $this->mConn;
- }
-
- /**
- *
- * @param $server String: hostname of database server
- * @param $user String: username
- * @param $password String: password
- * @param $dbName String: database name on the server
- * @param $flags Integer: database behaviour flags (optional, unused)
- * @param $schema String
- */
- public function __construct( $server = false, $user = false,
- $password = false,
- $dbName = false, $flags = 0,
- $schema = self::USE_GLOBAL )
- {
- global $wgDBmwschema;
-
- if ( $schema == self::USE_GLOBAL ) {
- $this->mSchema = $wgDBmwschema;
- } else {
- $this->mSchema = $schema;
- }
-
- // configure the connection and statement objects
- $this->setDB2Option( 'db2_attr_case', 'DB2_CASE_LOWER',
- self::CONN_OPTION | self::STMT_OPTION );
- $this->setDB2Option( 'deferred_prepare', 'DB2_DEFERRED_PREPARE_ON',
- self::STMT_OPTION );
- $this->setDB2Option( 'rowcount', 'DB2_ROWCOUNT_PREFETCH_ON',
- self::STMT_OPTION );
- parent::__construct( $server, $user, $password, $dbName, DBO_TRX | $flags );
- }
-
- /**
- * Enables options only if the ibm_db2 extension version supports them
- * @param $name String: name of the option in the options array
- * @param $const String: name of the constant holding the right option value
- * @param $type Integer: whether this is a Connection or Statement otion
- */
- private function setDB2Option( $name, $const, $type ) {
- if ( defined( $const ) ) {
- if ( $type & self::CONN_OPTION ) {
- $this->mConnOptions[$name] = constant( $const );
- }
- if ( $type & self::STMT_OPTION ) {
- $this->mStmtOptions[$name] = constant( $const );
- }
- } else {
- $this->installPrint(
- "$const is not defined. ibm_db2 version is likely too low." );
- }
- }
-
- /**
- * Outputs debug information in the appropriate place
- * @param $string String: the relevant debug message
- */
- private function installPrint( $string ) {
- wfDebug( "$string\n" );
- if ( $this->mMode == self::INSTALL_MODE ) {
- print "<li><pre>$string</pre></li>";
- flush();
- }
- }
-
- /**
- * Opens a database connection and returns it
- * Closes any existing connection
- *
- * @param $server String: hostname
- * @param $user String
- * @param $password String
- * @param $dbName String: database name
- * @return DatabaseBase a fresh connection
- */
- public function open( $server, $user, $password, $dbName ) {
- wfProfileIn( __METHOD__ );
-
- # Load IBM DB2 driver if missing
- wfDl( 'ibm_db2' );
-
- # Test for IBM DB2 support, to avoid suppressed fatal error
- if ( !function_exists( 'db2_connect' ) ) {
- throw new DBConnectionError( $this, "DB2 functions missing, have you enabled the ibm_db2 extension for PHP?" );
- }
-
- global $wgDBport;
-
- // Close existing connection
- $this->close();
- // Cache conn info
- $this->mServer = $server;
- $this->mPort = $port = $wgDBport;
- $this->mUser = $user;
- $this->mPassword = $password;
- $this->mDBname = $dbName;
-
- $this->openUncataloged( $dbName, $user, $password, $server, $port );
-
- if ( !$this->mConn ) {
- $this->installPrint( "DB connection error\n" );
- $this->installPrint(
- "Server: $server, Database: $dbName, User: $user, Password: "
- . substr( $password, 0, 3 ) . "...\n" );
- $this->installPrint( $this->lastError() . "\n" );
- wfProfileOut( __METHOD__ );
- wfDebug( "DB connection error\n" );
- wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
- wfDebug( $this->lastError() . "\n" );
- throw new DBConnectionError( $this, $this->lastError() );
- }
-
- // Some MediaWiki code is still transaction-less (?).
- // The strategy is to keep AutoCommit on for that code
- // but switch it off whenever a transaction is begun.
- db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
-
- $this->mOpened = true;
- $this->applySchema();
-
- wfProfileOut( __METHOD__ );
- return $this->mConn;
- }
-
- /**
- * Opens a cataloged database connection, sets mConn
- */
- protected function openCataloged( $dbName, $user, $password ) {
- wfSuppressWarnings();
- $this->mConn = db2_pconnect( $dbName, $user, $password );
- wfRestoreWarnings();
- }
-
- /**
- * Opens an uncataloged database connection, sets mConn
- */
- protected function openUncataloged( $dbName, $user, $password, $server, $port )
- {
- $dsn = "DRIVER={IBM DB2 ODBC DRIVER};DATABASE=$dbName;CHARSET=UTF-8;HOSTNAME=$server;PORT=$port;PROTOCOL=TCPIP;UID=$user;PWD=$password;";
- wfSuppressWarnings();
- $this->mConn = db2_pconnect( $dsn, "", "", array() );
- wfRestoreWarnings();
- }
-
- /**
- * Closes a database connection, if it is open
- * Returns success, true if already closed
- * @return bool
- */
- protected function closeConnection() {
- return db2_close( $this->mConn );
- }
-
- /**
- * Retrieves the most current database error
- * Forces a database rollback
- * @return bool|string
- */
- public function lastError() {
- $connerr = db2_conn_errormsg();
- if ( $connerr ) {
- //$this->rollback( __METHOD__ );
- return $connerr;
- }
- $stmterr = db2_stmt_errormsg();
- if ( $stmterr ) {
- //$this->rollback( __METHOD__ );
- return $stmterr;
- }
-
- return false;
- }
-
- /**
- * Get the last error number
- * Return 0 if no error
- * @return integer
- */
- public function lastErrno() {
- $connerr = db2_conn_error();
- if ( $connerr ) {
- return $connerr;
- }
- $stmterr = db2_stmt_error();
- if ( $stmterr ) {
- return $stmterr;
- }
- return 0;
- }
-
- /**
- * Is a database connection open?
- * @return
- */
- public function isOpen() { return $this->mOpened; }
-
- /**
- * The DBMS-dependent part of query()
- * @param $sql String: SQL query.
- * @return object Result object for fetch functions or false on failure
- */
- protected function doQuery( $sql ) {
- $this->applySchema();
-
- // Needed to handle any UTF-8 encoding issues in the raw sql
- // Note that we fully support prepared statements for DB2
- // prepare() and execute() should be used instead of doQuery() whenever possible
- $sql = utf8_decode( $sql );
-
- $ret = db2_exec( $this->mConn, $sql, $this->mStmtOptions );
- if( $ret == false ) {
- $error = db2_stmt_errormsg();
-
- $this->installPrint( "<pre>$sql</pre>" );
- $this->installPrint( $error );
- throw new DBUnexpectedError( $this, 'SQL error: '
- . htmlspecialchars( $error ) );
- }
- $this->mLastResult = $ret;
- $this->mAffectedRows = null; // Not calculated until asked for
- return $ret;
- }
-
- /**
- * @return string Version information from the database
- */
- public function getServerVersion() {
- $info = db2_server_info( $this->mConn );
- return $info->DBMS_VER;
- }
-
- /**
- * Queries whether a given table exists
- * @return boolean
- */
- public function tableExists( $table, $fname = __METHOD__ ) {
- $schema = $this->mSchema;
-
- $sql = "SELECT COUNT( * ) FROM SYSIBM.SYSTABLES ST WHERE ST.NAME = '" .
- strtoupper( $table ) .
- "' AND ST.CREATOR = '" .
- strtoupper( $schema ) . "'";
- $res = $this->query( $sql );
- if ( !$res ) {
- return false;
- }
-
- // If the table exists, there should be one of it
- $row = $this->fetchRow( $res );
- $count = $row[0];
- if ( $count == '1' || $count == 1 ) {
- return true;
- }
-
- return false;
- }
-
- /**
- * Fetch the next row from the given result object, in object form.
- * Fields can be retrieved with $row->fieldname, with fields acting like
- * member variables.
- *
- * @param $res array|ResultWrapper SQL result object as returned from Database::query(), etc.
- * @return DB2 row object
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- public function fetchObject( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- wfSuppressWarnings();
- $row = db2_fetch_object( $res );
- wfRestoreWarnings();
- if( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in fetchObject(): '
- . htmlspecialchars( $this->lastError() ) );
- }
- return $row;
- }
-
- /**
- * Fetch the next row from the given result object, in associative array
- * form. Fields are retrieved with $row['fieldname'].
- *
- * @param $res array|ResultWrapper SQL result object as returned from Database::query(), etc.
- * @return ResultWrapper row object
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- public function fetchRow( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- if ( db2_num_rows( $res ) > 0) {
- wfSuppressWarnings();
- $row = db2_fetch_array( $res );
- wfRestoreWarnings();
- if ( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in fetchRow(): '
- . htmlspecialchars( $this->lastError() ) );
- }
- return $row;
- }
- return false;
- }
-
- /**
- * Escapes strings
- * Doesn't escape numbers
- *
- * @param $s String: string to escape
- * @return string escaped string
- */
- public function addQuotes( $s ) {
- //$this->installPrint( "DB2::addQuotes( $s )\n" );
- if ( is_null( $s ) ) {
- return 'NULL';
- } elseif ( $s instanceof Blob ) {
- return "'" . $s->fetch( $s ) . "'";
- } elseif ( $s instanceof IBM_DB2Blob ) {
- return "'" . $this->decodeBlob( $s ) . "'";
- }
- $s = $this->strencode( $s );
- if ( is_numeric( $s ) ) {
- return $s;
- } else {
- return "'$s'";
- }
- }
-
- /**
- * Verifies that a DB2 column/field type is numeric
- *
- * @param $type String: DB2 column type
- * @return Boolean: true if numeric
- */
- public function is_numeric_type( $type ) {
- switch ( strtoupper( $type ) ) {
- case 'SMALLINT':
- case 'INTEGER':
- case 'INT':
- case 'BIGINT':
- case 'DECIMAL':
- case 'REAL':
- case 'DOUBLE':
- case 'DECFLOAT':
- return true;
- }
- return false;
- }
-
- /**
- * Alias for addQuotes()
- * @param $s String: string to escape
- * @return string escaped string
- */
- public function strencode( $s ) {
- // Bloody useless function
- // Prepends backslashes to \x00, \n, \r, \, ', " and \x1a.
- // But also necessary
- $s = db2_escape_string( $s );
- // Wide characters are evil -- some of them look like '
- $s = utf8_encode( $s );
- // Fix its stupidity
- $from = array( "\\\\", "\\'", '\\n', '\\t', '\\"', '\\r' );
- $to = array( "\\", "''", "\n", "\t", '"', "\r" );
- $s = str_replace( $from, $to, $s ); // DB2 expects '', not \' escaping
- return $s;
- }
-
- /**
- * Switch into the database schema
- */
- protected function applySchema() {
- if ( !( $this->mSchemaSet ) ) {
- $this->mSchemaSet = true;
- $this->begin( __METHOD__ );
- $this->doQuery( "SET SCHEMA = $this->mSchema" );
- $this->commit( __METHOD__ );
- }
- }
-
- /**
- * Start a transaction (mandatory)
- */
- protected function doBegin( $fname = 'DatabaseIbm_db2::begin' ) {
- // BEGIN is implicit for DB2
- // However, it requires that AutoCommit be off.
-
- // Some MediaWiki code is still transaction-less (?).
- // The strategy is to keep AutoCommit on for that code
- // but switch it off whenever a transaction is begun.
- db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_OFF );
-
- $this->mTrxLevel = 1;
- }
-
- /**
- * End a transaction
- * Must have a preceding begin()
- */
- protected function doCommit( $fname = 'DatabaseIbm_db2::commit' ) {
- db2_commit( $this->mConn );
-
- // Some MediaWiki code is still transaction-less (?).
- // The strategy is to keep AutoCommit on for that code
- // but switch it off whenever a transaction is begun.
- db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
-
- $this->mTrxLevel = 0;
- }
-
- /**
- * Cancel a transaction
- */
- protected function doRollback( $fname = 'DatabaseIbm_db2::rollback' ) {
- db2_rollback( $this->mConn );
- // turn auto-commit back on
- // not sure if this is appropriate
- db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
- $this->mTrxLevel = 0;
- }
-
- /**
- * Makes an encoded list of strings from an array
- * $mode:
- * LIST_COMMA - comma separated, no field names
- * LIST_AND - ANDed WHERE clause (without the WHERE)
- * LIST_OR - ORed WHERE clause (without the WHERE)
- * LIST_SET - comma separated with field names, like a SET clause
- * LIST_NAMES - comma separated field names
- * LIST_SET_PREPARED - like LIST_SET, except with ? tokens as values
- * @return string
- */
- function makeList( $a, $mode = LIST_COMMA ) {
- if ( !is_array( $a ) ) {
- throw new DBUnexpectedError( $this,
- 'DatabaseIbm_db2::makeList called with incorrect parameters' );
- }
-
- // if this is for a prepared UPDATE statement
- // (this should be promoted to the parent class
- // once other databases use prepared statements)
- if ( $mode == LIST_SET_PREPARED ) {
- $first = true;
- $list = '';
- foreach ( $a as $field => $value ) {
- if ( !$first ) {
- $list .= ", $field = ?";
- } else {
- $list .= "$field = ?";
- $first = false;
- }
- }
- $list .= '';
-
- return $list;
- }
-
- // otherwise, call the usual function
- return parent::makeList( $a, $mode );
- }
-
- /**
- * Construct a LIMIT query with optional offset
- * This is used for query pages
- *
- * @param $sql string SQL query we will append the limit too
- * @param $limit integer the SQL limit
- * @param $offset integer the SQL offset (default false)
- * @return string
- */
- public function limitResult( $sql, $limit, $offset=false ) {
- if( !is_numeric( $limit ) ) {
- throw new DBUnexpectedError( $this,
- "Invalid non-numeric limit passed to limitResult()\n" );
- }
- if( $offset ) {
- if ( stripos( $sql, 'where' ) === false ) {
- return "$sql AND ( ROWNUM BETWEEN $offset AND $offset+$limit )";
- } else {
- return "$sql WHERE ( ROWNUM BETWEEN $offset AND $offset+$limit )";
- }
- }
- return "$sql FETCH FIRST $limit ROWS ONLY ";
- }
-
- /**
- * Handle reserved keyword replacement in table names
- *
- * @param $name Object
- * @param $format String Ignored parameter Default 'quoted'Boolean
- * @return String
- */
- public function tableName( $name, $format = 'quoted' ) {
- // we want maximum compatibility with MySQL schema
- return $name;
- }
-
- /**
- * Generates a timestamp in an insertable format
- *
- * @param $ts string timestamp
- * @return String: timestamp value
- */
- public function timestamp( $ts = 0 ) {
- // TS_MW cannot be easily distinguished from an integer
- return wfTimestamp( TS_DB2, $ts );
- }
-
- /**
- * Return the next in a sequence, save the value for retrieval via insertId()
- * @param $seqName String: name of a defined sequence in the database
- * @return int next value in that sequence
- */
- public function nextSequenceValue( $seqName ) {
- // Not using sequences in the primary schema to allow for easier migration
- // from MySQL
- // Emulating MySQL behaviour of using NULL to signal that sequences
- // aren't used
- /*
- $safeseq = preg_replace( "/'/", "''", $seqName );
- $res = $this->query( "VALUES NEXTVAL FOR $safeseq" );
- $row = $this->fetchRow( $res );
- $this->mInsertId = $row[0];
- return $this->mInsertId;
- */
- return null;
- }
-
- /**
- * This must be called after nextSequenceVal
- * @return int Last sequence value used as a primary key
- */
- public function insertId() {
- return $this->mInsertId;
- }
-
- /**
- * Updates the mInsertId property with the value of the last insert
- * into a generated column
- *
- * @param $table String: sanitized table name
- * @param $primaryKey Mixed: string name of the primary key
- * @param $stmt Resource: prepared statement resource
- * of the SELECT primary_key FROM FINAL TABLE ( INSERT ... ) form
- */
- private function calcInsertId( $table, $primaryKey, $stmt ) {
- if ( $primaryKey ) {
- $this->mInsertId = db2_last_insert_id( $this->mConn );
- }
- }
-
- /**
- * INSERT wrapper, inserts an array into a table
- *
- * $args may be a single associative array, or an array of arrays
- * with numeric keys, for multi-row insert
- *
- * @param $table String: Name of the table to insert to.
- * @param $args Array: Items to insert into the table.
- * @param $fname String: Name of the function, for profiling
- * @param $options String or Array. Valid options: IGNORE
- *
- * @return bool Success of insert operation. IGNORE always returns true.
- */
- public function insert( $table, $args, $fname = 'DatabaseIbm_db2::insert',
- $options = array() )
- {
- if ( !count( $args ) ) {
- return true;
- }
- // get database-specific table name (not used)
- $table = $this->tableName( $table );
- // format options as an array
- $options = IBM_DB2Helper::makeArray( $options );
- // format args as an array of arrays
- if ( !( isset( $args[0] ) && is_array( $args[0] ) ) ) {
- $args = array( $args );
- }
-
- // prevent insertion of NULL into primary key columns
- list( $args, $primaryKeys ) = $this->removeNullPrimaryKeys( $table, $args );
- // if there's only one primary key
- // we'll be able to read its value after insertion
- $primaryKey = false;
- if ( count( $primaryKeys ) == 1 ) {
- $primaryKey = $primaryKeys[0];
- }
-
- // get column names
- $keys = array_keys( $args[0] );
- $key_count = count( $keys );
-
- // If IGNORE is set, we use savepoints to emulate mysql's behavior
- $ignore = in_array( 'IGNORE', $options ) ? 'mw' : '';
-
- // assume success
- $res = true;
- // If we are not in a transaction, we need to be for savepoint trickery
- if ( !$this->mTrxLevel ) {
- $this->begin( __METHOD__ );
- }
-
- $sql = "INSERT INTO $table ( " . implode( ',', $keys ) . ' ) VALUES ';
- if ( $key_count == 1 ) {
- $sql .= '( ? )';
- } else {
- $sql .= '( ?' . str_repeat( ',?', $key_count-1 ) . ' )';
- }
- $this->installPrint( "Preparing the following SQL:" );
- $this->installPrint( "$sql" );
- $this->installPrint( print_r( $args, true ));
- $stmt = $this->prepare( $sql );
-
- // start a transaction/enter transaction mode
- $this->begin( __METHOD__ );
-
- if ( !$ignore ) {
- //$first = true;
- foreach ( $args as $row ) {
- //$this->installPrint( "Inserting " . print_r( $row, true ));
- // insert each row into the database
- $res = $res & $this->execute( $stmt, $row );
- if ( !$res ) {
- $this->installPrint( 'Last error:' );
- $this->installPrint( $this->lastError() );
- }
- // get the last inserted value into a generated column
- $this->calcInsertId( $table, $primaryKey, $stmt );
- }
- } else {
- $olde = error_reporting( 0 );
- // For future use, we may want to track the number of actual inserts
- // Right now, insert (all writes) simply return true/false
- $numrowsinserted = 0;
-
- // always return true
- $res = true;
-
- foreach ( $args as $row ) {
- $overhead = "SAVEPOINT $ignore ON ROLLBACK RETAIN CURSORS";
- db2_exec( $this->mConn, $overhead, $this->mStmtOptions );
-
- $res2 = $this->execute( $stmt, $row );
-
- if ( !$res2 ) {
- $this->installPrint( 'Last error:' );
- $this->installPrint( $this->lastError() );
- }
- // get the last inserted value into a generated column
- $this->calcInsertId( $table, $primaryKey, $stmt );
-
- $errNum = $this->lastErrno();
- if ( $errNum ) {
- db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore",
- $this->mStmtOptions );
- } else {
- db2_exec( $this->mConn, "RELEASE SAVEPOINT $ignore",
- $this->mStmtOptions );
- $numrowsinserted++;
- }
- }
-
- $olde = error_reporting( $olde );
- // Set the affected row count for the whole operation
- $this->mAffectedRows = $numrowsinserted;
- }
- // commit either way
- $this->commit( __METHOD__ );
- $this->freePrepared( $stmt );
-
- return $res;
- }
-
- /**
- * Given a table name and a hash of columns with values
- * Removes primary key columns from the hash where the value is NULL
- *
- * @param $table String: name of the table
- * @param $args Array of hashes of column names with values
- * @return Array: tuple( filtered array of columns, array of primary keys )
- */
- private function removeNullPrimaryKeys( $table, $args ) {
- $schema = $this->mSchema;
-
- // find out the primary keys
- $keyres = $this->doQuery( "SELECT NAME FROM SYSIBM.SYSCOLUMNS WHERE TBNAME = '"
- . strtoupper( $table )
- . "' AND TBCREATOR = '"
- . strtoupper( $schema )
- . "' AND KEYSEQ > 0" );
-
- $keys = array();
- for (
- $row = $this->fetchRow( $keyres );
- $row != null;
- $row = $this->fetchRow( $keyres )
- )
- {
- $keys[] = strtolower( $row[0] );
- }
- // remove primary keys
- foreach ( $args as $ai => $row ) {
- foreach ( $keys as $key ) {
- if ( $row[$key] == null ) {
- unset( $row[$key] );
- }
- }
- $args[$ai] = $row;
- }
- // return modified hash
- return array( $args, $keys );
- }
-
- /**
- * UPDATE wrapper, takes a condition array and a SET array
- *
- * @param $table String: The table to UPDATE
- * @param $values array An array of values to SET
- * @param $conds array An array of conditions ( WHERE ). Use '*' to update all rows.
- * @param $fname String: The Class::Function calling this function
- * ( for the log )
- * @param $options array An array of UPDATE options, can be one or
- * more of IGNORE, LOW_PRIORITY
- * @return Boolean
- */
- public function update( $table, $values, $conds, $fname = 'DatabaseIbm_db2::update',
- $options = array() )
- {
- $table = $this->tableName( $table );
- $opts = $this->makeUpdateOptions( $options );
- $sql = "UPDATE $opts $table SET "
- . $this->makeList( $values, LIST_SET_PREPARED );
- if ( $conds != '*' ) {
- $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
- }
- $stmt = $this->prepare( $sql );
- $this->installPrint( 'UPDATE: ' . print_r( $values, true ) );
- // assuming for now that an array with string keys will work
- // if not, convert to simple array first
- $result = $this->execute( $stmt, $values );
- $this->freePrepared( $stmt );
-
- return $result;
- }
-
- /**
- * DELETE query wrapper
- *
- * Use $conds == "*" to delete all rows
- * @return bool|\ResultWrapper
- */
- public function delete( $table, $conds, $fname = 'DatabaseIbm_db2::delete' ) {
- if ( !$conds ) {
- throw new DBUnexpectedError( $this,
- 'DatabaseIbm_db2::delete() called with no conditions' );
- }
- $table = $this->tableName( $table );
- $sql = "DELETE FROM $table";
- if ( $conds != '*' ) {
- $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
- }
- $result = $this->query( $sql, $fname );
-
- return $result;
- }
-
- /**
- * Returns the number of rows affected by the last query or 0
- * @return Integer: the number of rows affected by the last query
- */
- public function affectedRows() {
- if ( !is_null( $this->mAffectedRows ) ) {
- // Forced result for simulated queries
- return $this->mAffectedRows;
- }
- if( empty( $this->mLastResult ) ) {
- return 0;
- }
- return db2_num_rows( $this->mLastResult );
- }
-
- /**
- * Returns the number of rows in the result set
- * Has to be called right after the corresponding select query
- * @param $res Object result set
- * @return Integer: number of rows
- */
- public function numRows( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- if ( $this->mNumRows ) {
- return $this->mNumRows;
- } else {
- return 0;
- }
- }
-
- /**
- * Moves the row pointer of the result set
- * @param $res Object: result set
- * @param $row Integer: row number
- * @return bool success or failure
- */
- public function dataSeek( $res, $row ) {
- if ( $res instanceof ResultWrapper ) {
- return $res = $res->result;
- }
- if ( $res instanceof IBM_DB2Result ) {
- return $res->dataSeek( $row );
- }
- wfDebug( "dataSeek operation in DB2 database\n" );
- return false;
- }
-
- ###
- # Fix notices in Block.php
- ###
-
- /**
- * Frees memory associated with a statement resource
- * @param $res Object: statement resource to free
- * @return Boolean success or failure
- */
- public function freeResult( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- wfSuppressWarnings();
- $ok = db2_free_result( $res );
- wfRestoreWarnings();
- if ( !$ok ) {
- throw new DBUnexpectedError( $this, "Unable to free DB2 result\n" );
- }
- }
-
- /**
- * Returns the number of columns in a resource
- * @param $res Object: statement resource
- * @return Number of fields/columns in resource
- */
- public function numFields( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- if ( $res instanceof IBM_DB2Result ) {
- $res = $res->getResult();
- }
- return db2_num_fields( $res );
- }
-
- /**
- * Returns the nth column name
- * @param $res Object: statement resource
- * @param $n Integer: Index of field or column
- * @return String name of nth column
- */
- public function fieldName( $res, $n ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- if ( $res instanceof IBM_DB2Result ) {
- $res = $res->getResult();
- }
- return db2_field_name( $res, $n );
- }
-
- /**
- * SELECT wrapper
- *
- * @param $table Array or string, table name(s) (prefix auto-added)
- * @param $vars Array or string, field name(s) to be retrieved
- * @param $conds Array or string, condition(s) for WHERE
- * @param $fname String: calling function name (use __METHOD__)
- * for logs/profiling
- * @param $options array Associative array of options
- * (e.g. array( 'GROUP BY' => 'page_title' )),
- * see Database::makeSelectOptions code for list of
- * supported stuff
- * @param $join_conds array Associative array of table join conditions (optional)
- * (e.g. array( 'page' => array('LEFT JOIN',
- * 'page_latest=rev_id') )
- * @return Mixed: database result resource for fetch functions or false
- * on failure
- */
- public function select( $table, $vars, $conds = '', $fname = 'DatabaseIbm_db2::select', $options = array(), $join_conds = array() )
- {
- $res = parent::select( $table, $vars, $conds, $fname, $options,
- $join_conds );
- $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
-
- // We must adjust for offset
- if ( isset( $options['LIMIT'] ) && isset ( $options['OFFSET'] ) ) {
- $limit = $options['LIMIT'];
- $offset = $options['OFFSET'];
- }
-
- // DB2 does not have a proper num_rows() function yet, so we must emulate
- // DB2 9.5.4 and the corresponding ibm_db2 driver will introduce
- // a working one
- // TODO: Yay!
-
- // we want the count
- $vars2 = array( 'count( * ) as num_rows' );
- // respecting just the limit option
- $options2 = array();
- if ( isset( $options['LIMIT'] ) ) {
- $options2['LIMIT'] = $options['LIMIT'];
- }
- // but don't try to emulate for GROUP BY
- if ( isset( $options['GROUP BY'] ) ) {
- return $res;
- }
-
- $res2 = parent::select( $table, $vars2, $conds, $fname, $options2,
- $join_conds );
-
- $obj = $this->fetchObject( $res2 );
- $this->mNumRows = $obj->num_rows;
-
- return new ResultWrapper( $this, new IBM_DB2Result( $this, $res, $obj->num_rows, $vars, $sql ) );
- }
-
- /**
- * Handles ordering, grouping, and having options ('GROUP BY' => colname)
- * Has limited support for per-column options (colnum => 'DISTINCT')
- *
- * @private
- *
- * @param $options array Associative array of options to be turned into
- * an SQL query, valid keys are listed in the function.
- * @return Array
- */
- function makeSelectOptions( $options ) {
- $preLimitTail = $postLimitTail = '';
- $startOpts = '';
-
- $noKeyOptions = array();
- foreach ( $options as $key => $option ) {
- if ( is_numeric( $key ) ) {
- $noKeyOptions[$option] = true;
- }
- }
-
- if ( isset( $options['GROUP BY'] ) ) {
- $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
- }
- if ( isset( $options['HAVING'] ) ) {
- $preLimitTail .= " HAVING {$options['HAVING']}";
- }
- if ( isset( $options['ORDER BY'] ) ) {
- $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
- }
-
- if ( isset( $noKeyOptions['DISTINCT'] )
- || isset( $noKeyOptions['DISTINCTROW'] ) )
- {
- $startOpts .= 'DISTINCT';
- }
-
- return array( $startOpts, '', $preLimitTail, $postLimitTail );
- }
-
- /**
- * Returns link to IBM DB2 free download
- * @return String: wikitext of a link to the server software's web site
- */
- public static function getSoftwareLink() {
- return '[http://www.ibm.com/db2/express/ IBM DB2]';
- }
-
- /**
- * Get search engine class. All subclasses of this
- * need to implement this if they wish to use searching.
- *
- * @return String
- */
- public function getSearchEngine() {
- return 'SearchIBM_DB2';
- }
-
- /**
- * Did the last database access fail because of deadlock?
- * @return Boolean
- */
- public function wasDeadlock() {
- // get SQLSTATE
- $err = $this->lastErrno();
- switch( $err ) {
- // This is literal port of the MySQL logic and may be wrong for DB2
- case '40001': // sql0911n, Deadlock or timeout, rollback
- case '57011': // sql0904n, Resource unavailable, no rollback
- case '57033': // sql0913n, Deadlock or timeout, no rollback
- $this->installPrint( "In a deadlock because of SQLSTATE $err" );
- return true;
- }
- return false;
- }
-
- /**
- * Ping the server and try to reconnect if it there is no connection
- * The connection may be closed and reopened while this happens
- * @return Boolean: whether the connection exists
- */
- public function ping() {
- // db2_ping() doesn't exist
- // Emulate
- $this->close();
- $this->openUncataloged( $this->mDBName, $this->mUser,
- $this->mPassword, $this->mServer, $this->mPort );
-
- return false;
- }
- ######################################
- # Unimplemented and not applicable
- ######################################
-
- /**
- * Only useful with fake prepare like in base Database class
- * @return string
- */
- public function fillPreparedArg( $matches ) {
- $this->installPrint( 'Not useful for DB2: fillPreparedArg()' );
- return '';
- }
-
- ######################################
- # Reflection
- ######################################
-
- /**
- * Returns information about an index
- * If errors are explicitly ignored, returns NULL on failure
- * @param $table String: table name
- * @param $index String: index name
- * @param $fname String: function name for logging and profiling
- * @return Object query row in object form
- */
- public function indexInfo( $table, $index,
- $fname = 'DatabaseIbm_db2::indexExists' )
- {
- $table = $this->tableName( $table );
- $sql = <<<SQL
-SELECT name as indexname
-FROM sysibm.sysindexes si
-WHERE si.name='$index' AND si.tbname='$table'
-AND sc.tbcreator='$this->mSchema'
-SQL;
- $res = $this->query( $sql, $fname );
- if ( !$res ) {
- return null;
- }
- $row = $this->fetchObject( $res );
- if ( $row != null ) {
- return $row;
- } else {
- return false;
- }
- }
-
- /**
- * Returns an information object on a table column
- * @param $table String: table name
- * @param $field String: column name
- * @return IBM_DB2Field
- */
- public function fieldInfo( $table, $field ) {
- return IBM_DB2Field::fromText( $this, $table, $field );
- }
-
- /**
- * db2_field_type() wrapper
- * @param $res Object: result of executed statement
- * @param $index Mixed: number or name of the column
- * @return String column type
- */
- public function fieldType( $res, $index ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- if ( $res instanceof IBM_DB2Result ) {
- $res = $res->getResult();
- }
- return db2_field_type( $res, $index );
- }
-
- /**
- * Verifies that an index was created as unique
- * @param $table String: table name
- * @param $index String: index name
- * @param $fname string function name for profiling
- * @return Bool
- */
- public function indexUnique ( $table, $index,
- $fname = 'DatabaseIbm_db2::indexUnique' )
- {
- $table = $this->tableName( $table );
- $sql = <<<SQL
-SELECT si.name as indexname
-FROM sysibm.sysindexes si
-WHERE si.name='$index' AND si.tbname='$table'
-AND sc.tbcreator='$this->mSchema'
-AND si.uniquerule IN ( 'U', 'P' )
-SQL;
- $res = $this->query( $sql, $fname );
- if ( !$res ) {
- return null;
- }
- if ( $this->fetchObject( $res ) ) {
- return true;
- }
- return false;
-
- }
-
- /**
- * Returns the size of a text field, or -1 for "unlimited"
- * @param $table String: table name
- * @param $field String: column name
- * @return Integer: length or -1 for unlimited
- */
- public function textFieldSize( $table, $field ) {
- $table = $this->tableName( $table );
- $sql = <<<SQL
-SELECT length as size
-FROM sysibm.syscolumns sc
-WHERE sc.name='$field' AND sc.tbname='$table'
-AND sc.tbcreator='$this->mSchema'
-SQL;
- $res = $this->query( $sql );
- $row = $this->fetchObject( $res );
- $size = $row->size;
- return $size;
- }
-
- /**
- * Description is left as an exercise for the reader
- * @param $b Mixed: data to be encoded
- * @return IBM_DB2Blob
- */
- public function encodeBlob( $b ) {
- return new IBM_DB2Blob( $b );
- }
-
- /**
- * Description is left as an exercise for the reader
- * @param $b IBM_DB2Blob: data to be decoded
- * @return mixed
- */
- public function decodeBlob( $b ) {
- return "$b";
- }
-
- /**
- * Convert into a list of string being concatenated
- * @param $stringList Array: strings that need to be joined together
- * by the SQL engine
- * @return String: joined by the concatenation operator
- */
- public function buildConcat( $stringList ) {
- // || is equivalent to CONCAT
- // Sample query: VALUES 'foo' CONCAT 'bar' CONCAT 'baz'
- return implode( ' || ', $stringList );
- }
-
- /**
- * Generates the SQL required to convert a DB2 timestamp into a Unix epoch
- * @param $column String: name of timestamp column
- * @return String: SQL code
- */
- public function extractUnixEpoch( $column ) {
- // TODO
- // see SpecialAncientpages
- }
-
- ######################################
- # Prepared statements
- ######################################
-
- /**
- * Intended to be compatible with the PEAR::DB wrapper functions.
- * http://pear.php.net/manual/en/package.database.db.intro-execute.php
- *
- * ? = scalar value, quoted as necessary
- * ! = raw SQL bit (a function for instance)
- * & = filename; reads the file and inserts as a blob
- * (we don't use this though...)
- * @param $sql String: SQL statement with appropriate markers
- * @param $func String: Name of the function, for profiling
- * @return resource a prepared DB2 SQL statement
- */
- public function prepare( $sql, $func = 'DB2::prepare' ) {
- $stmt = db2_prepare( $this->mConn, $sql, $this->mStmtOptions );
- return $stmt;
- }
-
- /**
- * Frees resources associated with a prepared statement
- * @return Boolean success or failure
- */
- public function freePrepared( $prepared ) {
- return db2_free_stmt( $prepared );
- }
-
- /**
- * Execute a prepared query with the various arguments
- * @param $prepared String: the prepared sql
- * @param $args Mixed: either an array here, or put scalars as varargs
- * @return Resource: results object
- */
- public function execute( $prepared, $args = null ) {
- if( !is_array( $args ) ) {
- # Pull the var args
- $args = func_get_args();
- array_shift( $args );
- }
- $res = db2_execute( $prepared, $args );
- if ( !$res ) {
- $this->installPrint( db2_stmt_errormsg() );
- }
- return $res;
- }
-
- /**
- * For faking prepared SQL statements on DBs that don't support
- * it directly.
- * @param $preparedQuery String: a 'preparable' SQL statement
- * @param $args Array of arguments to fill it with
- * @return String: executable statement
- */
- public function fillPrepared( $preparedQuery, $args ) {
- reset( $args );
- $this->preparedArgs =& $args;
-
- foreach ( $args as $i => $arg ) {
- db2_bind_param( $preparedQuery, $i+1, $args[$i] );
- }
-
- return $preparedQuery;
- }
-
- /**
- * Switches module between regular and install modes
- * @return string
- */
- public function setMode( $mode ) {
- $old = $this->mMode;
- $this->mMode = $mode;
- return $old;
- }
-
- /**
- * Bitwise negation of a column or value in SQL
- * Same as (~field) in C
- * @param $field String
- * @return String
- */
- function bitNot( $field ) {
- // expecting bit-fields smaller than 4bytes
- return "BITNOT( $field )";
- }
-
- /**
- * Bitwise AND of two columns or values in SQL
- * Same as (fieldLeft & fieldRight) in C
- * @param $fieldLeft String
- * @param $fieldRight String
- * @return String
- */
- function bitAnd( $fieldLeft, $fieldRight ) {
- return "BITAND( $fieldLeft, $fieldRight )";
- }
-
- /**
- * Bitwise OR of two columns or values in SQL
- * Same as (fieldLeft | fieldRight) in C
- * @param $fieldLeft String
- * @param $fieldRight String
- * @return String
- */
- function bitOr( $fieldLeft, $fieldRight ) {
- return "BITOR( $fieldLeft, $fieldRight )";
- }
-}
-
-class IBM_DB2Helper {
- public static function makeArray( $maybeArray ) {
- if ( !is_array( $maybeArray ) ) {
- return array( $maybeArray );
- }
-
- return $maybeArray;
- }
-}
diff --git a/includes/db/DatabaseMssql.php b/includes/db/DatabaseMssql.php
index 914ab408..6c45ffaf 100644
--- a/includes/db/DatabaseMssql.php
+++ b/includes/db/DatabaseMssql.php
@@ -28,9 +28,9 @@
* @ingroup Database
*/
class DatabaseMssql extends DatabaseBase {
- var $mInsertId = NULL;
- var $mLastResult = NULL;
- var $mAffectedRows = NULL;
+ var $mInsertId = null;
+ var $mLastResult = null;
+ var $mAffectedRows = null;
var $mPort;
@@ -61,6 +61,11 @@ class DatabaseMssql extends DatabaseBase {
/**
* Usually aborts on failure
+ * @param string $server
+ * @param string $user
+ * @param string $password
+ * @param string $dbName
+ * @throws DBConnectionError
* @return bool|DatabaseBase|null
*/
function open( $server, $user, $password, $dbName ) {
@@ -97,7 +102,7 @@ class DatabaseMssql extends DatabaseBase {
$ntAuthPassTest = strtolower( $password );
// Decide which auth scenerio to use
- if( $ntAuthPassTest == 'ntauth' && $ntAuthUserTest == 'ntauth' ){
+ if( $ntAuthPassTest == 'ntauth' && $ntAuthUserTest == 'ntauth' ) {
// Don't add credentials to $connectionInfo
} else {
$connectionInfo['UID'] = $user;
@@ -139,7 +144,7 @@ class DatabaseMssql extends DatabaseBase {
// $this->limitResult();
if ( preg_match( '/\bLIMIT\s*/i', $sql ) ) {
// massage LIMIT -> TopN
- $sql = $this->LimitToTopN( $sql ) ;
+ $sql = $this->LimitToTopN( $sql );
}
// MSSQL doesn't have EXTRACT(epoch FROM XXX)
@@ -151,7 +156,7 @@ class DatabaseMssql extends DatabaseBase {
// perform query
$stmt = sqlsrv_query( $this->mConn, $sql );
if ( $stmt == false ) {
- $message = "A database error has occurred. Did you forget to run maintenance/update.php after upgrading? See: http://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" .
+ $message = "A database error has occurred. Did you forget to run maintenance/update.php after upgrading? See: http://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" .
"Query: " . htmlentities( $sql ) . "\n" .
"Function: " . __METHOD__ . "\n";
// process each error (our driver will give us an array of errors unlike other providers)
@@ -279,7 +284,7 @@ class DatabaseMssql extends DatabaseBase {
* @param $vars Mixed: array or string, field name(s) to be retrieved
* @param $conds Mixed: array or string, condition(s) for WHERE
* @param $fname String: calling function name (use __METHOD__) for logs/profiling
- * @param $options Array: associative array of options (e.g. array('GROUP BY' => 'page_title')),
+ * @param array $options associative array of options (e.g. array('GROUP BY' => 'page_title')),
* see Database::makeSelectOptions code for list of supported stuff
* @param $join_conds Array: Associative array of table join conditions (optional)
* (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
@@ -304,7 +309,7 @@ class DatabaseMssql extends DatabaseBase {
* @param $vars Mixed: Array or string, field name(s) to be retrieved
* @param $conds Mixed: Array or string, condition(s) for WHERE
* @param $fname String: Calling function name (use __METHOD__) for logs/profiling
- * @param $options Array: Associative array of options (e.g. array('GROUP BY' => 'page_title')),
+ * @param array $options Associative array of options (e.g. array('GROUP BY' => 'page_title')),
* see Database::makeSelectOptions code for list of supported stuff
* @param $join_conds Array: Associative array of table join conditions (optional)
* (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
@@ -314,7 +319,7 @@ class DatabaseMssql extends DatabaseBase {
if ( isset( $options['EXPLAIN'] ) ) {
unset( $options['EXPLAIN'] );
}
- return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
+ return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
}
/**
@@ -348,7 +353,7 @@ class DatabaseMssql extends DatabaseBase {
$sql = "sp_helpindex '" . $table . "'";
$res = $this->query( $sql, $fname );
if ( !$res ) {
- return NULL;
+ return null;
}
$result = array();
@@ -380,6 +385,11 @@ class DatabaseMssql extends DatabaseBase {
*
* Usually aborts on failure
* If errors are explicitly ignored, returns success
+ * @param string $table
+ * @param array $arrToInsert
+ * @param string $fname
+ * @param array $options
+ * @throws DBQueryError
* @return bool
*/
function insert( $table, $arrToInsert, $fname = 'DatabaseMssql::insert', $options = array() ) {
@@ -404,7 +414,7 @@ class DatabaseMssql extends DatabaseBase {
$identity = null;
$tableRaw = preg_replace( '#\[([^\]]*)\]#', '$1', $table ); // strip matching square brackets from table name
$res = $this->doQuery( "SELECT NAME AS idColumn FROM SYS.IDENTITY_COLUMNS WHERE OBJECT_NAME(OBJECT_ID)='{$tableRaw}'" );
- if( $res && $res->numrows() ){
+ if( $res && $res->numrows() ) {
// There is an identity for this table.
$identity = array_pop( $res->fetch( SQLSRV_FETCH_ASSOC ) );
}
@@ -421,9 +431,9 @@ class DatabaseMssql extends DatabaseBase {
// iterate through
foreach ($a as $k => $v ) {
if ( $k == $identity ) {
- if( !is_null($v) ){
+ if( !is_null($v) ) {
// there is a value being passed to us, we need to turn on and off inserted identity
- $sqlPre = "SET IDENTITY_INSERT $table ON;" ;
+ $sqlPre = "SET IDENTITY_INSERT $table ON;";
$sqlPost = ";SET IDENTITY_INSERT $table OFF;";
} else {
@@ -474,7 +484,7 @@ class DatabaseMssql extends DatabaseBase {
} elseif ( is_array( $value ) || is_object( $value ) ) {
if ( is_object( $value ) && strtolower( get_class( $value ) ) == 'blob' ) {
$sql .= $this->addQuotes( $value );
- } else {
+ } else {
$sql .= $this->addQuotes( serialize( $value ) );
}
} else {
@@ -488,7 +498,7 @@ class DatabaseMssql extends DatabaseBase {
if ( $ret === false ) {
throw new DBQueryError( $this, $this->getErrors(), $this->lastErrno(), $sql, $fname );
- } elseif ( $ret != NULL ) {
+ } elseif ( $ret != null ) {
// remember number of rows affected
$this->mAffectedRows = sqlsrv_rows_affected( $ret );
if ( !is_null($identity) ) {
@@ -510,7 +520,15 @@ class DatabaseMssql extends DatabaseBase {
* Source items may be literals rather than field names, but strings should be quoted with Database::addQuotes()
* $conds may be "*" to copy the whole table
* srcTable may be an array of tables.
- * @return null|\ResultWrapper
+ * @param string $destTable
+ * @param array|string $srcTable
+ * @param array $varMap
+ * @param array $conds
+ * @param string $fname
+ * @param array $insertOptions
+ * @param array $selectOptions
+ * @throws DBQueryError
+ * @return null|ResultWrapper
*/
function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabaseMssql::insertSelect',
$insertOptions = array(), $selectOptions = array() ) {
@@ -518,12 +536,12 @@ class DatabaseMssql extends DatabaseBase {
if ( $ret === false ) {
throw new DBQueryError( $this, $this->getErrors(), $this->lastErrno(), /*$sql*/ '', $fname );
- } elseif ( $ret != NULL ) {
+ } elseif ( $ret != null ) {
// remember number of rows affected
$this->mAffectedRows = sqlsrv_rows_affected( $ret );
return $ret;
}
- return NULL;
+ return null;
}
/**
@@ -590,9 +608,9 @@ class DatabaseMssql extends DatabaseBase {
} else {
$sql = '
SELECT * FROM (
- SELECT sub2.*, ROW_NUMBER() OVER(ORDER BY sub2.line2) AS line3 FROM (
- SELECT 1 AS line2, sub1.* FROM (' . $sql . ') AS sub1
- ) as sub2
+ SELECT sub2.*, ROW_NUMBER() OVER(ORDER BY sub2.line2) AS line3 FROM (
+ SELECT 1 AS line2, sub1.* FROM (' . $sql . ') AS sub1
+ ) as sub2
) AS sub3
WHERE line3 BETWEEN ' . ( $offset + 1 ) . ' AND ' . ( $offset + $limit );
return $sql;
@@ -720,6 +738,8 @@ class DatabaseMssql extends DatabaseBase {
* Escapes a identifier for use inm SQL.
* Throws an exception if it is invalid.
* Reference: http://msdn.microsoft.com/en-us/library/aa224033%28v=SQL.80%29.aspx
+ * @param $identifier
+ * @throws MWException
* @return string
*/
private function escapeIdentifier( $identifier ) {
@@ -750,17 +770,17 @@ class DatabaseMssql extends DatabaseBase {
$newUser = $this->escapeIdentifier( $newUser );
$loginPassword = $this->addQuotes( $loginPassword );
- $this->doQuery("CREATE DATABASE $dbName;");
- $this->doQuery("USE $dbName;");
- $this->doQuery("CREATE SCHEMA $dbName;");
- $this->doQuery("
+ $this->doQuery( "CREATE DATABASE $dbName;" );
+ $this->doQuery( "USE $dbName;" );
+ $this->doQuery( "CREATE SCHEMA $dbName;" );
+ $this->doQuery( "
CREATE
LOGIN $newUser
WITH
PASSWORD=$loginPassword
;
- ");
- $this->doQuery("
+ " );
+ $this->doQuery( "
CREATE
USER $newUser
FOR
@@ -768,8 +788,8 @@ class DatabaseMssql extends DatabaseBase {
WITH
DEFAULT_SCHEMA=$dbName
;
- ");
- $this->doQuery("
+ " );
+ $this->doQuery( "
GRANT
BACKUP DATABASE,
BACKUP LOG,
@@ -784,17 +804,15 @@ class DatabaseMssql extends DatabaseBase {
DATABASE::$dbName
TO $newUser
;
- ");
- $this->doQuery("
+ " );
+ $this->doQuery( "
GRANT
CONTROL
ON
SCHEMA::$dbName
TO $newUser
;
- ");
-
-
+ " );
}
function encodeBlob( $b ) {
@@ -873,7 +891,7 @@ class DatabaseMssql extends DatabaseBase {
/**
* @private
*
- * @param $options Array: an associative array of options to be turned into
+ * @param array $options an associative array of options to be turned into
* an SQL query, valid keys are listed in the function.
* @return Array
*/
@@ -888,29 +906,23 @@ class DatabaseMssql extends DatabaseBase {
}
}
- if ( isset( $options['GROUP BY'] ) ) {
- $tailOpts .= " GROUP BY {$options['GROUP BY']}";
- }
- if ( isset( $options['HAVING'] ) ) {
- $tailOpts .= " HAVING {$options['GROUP BY']}";
- }
- if ( isset( $options['ORDER BY'] ) ) {
- $tailOpts .= " ORDER BY {$options['ORDER BY']}";
- }
+ $tailOpts .= $this->makeGroupByWithHaving( $options );
+
+ $tailOpts .= $this->makeOrderBy( $options );
if ( isset( $noKeyOptions['DISTINCT'] ) && isset( $noKeyOptions['DISTINCTROW'] ) ) {
$startOpts .= 'DISTINCT';
}
// we want this to be compatible with the output of parent::makeSelectOptions()
- return array( $startOpts, '' , $tailOpts, '' );
+ return array( $startOpts, '', $tailOpts, '' );
}
/**
* Get the type of the DBMS, as it appears in $wgDBtype.
* @return string
*/
- function getType(){
+ function getType() {
return 'mssql';
}
@@ -1118,6 +1130,5 @@ class MssqlResult {
public function free() {
unset( $this->mRows );
- return;
}
}
diff --git a/includes/db/DatabaseMysql.php b/includes/db/DatabaseMysql.php
index 7f389da9..27aae188 100644
--- a/includes/db/DatabaseMysql.php
+++ b/includes/db/DatabaseMysql.php
@@ -133,7 +133,7 @@ class DatabaseMysql extends DatabaseBase {
substr( $password, 0, 3 ) . "..., error: " . $error . "\n" );
wfProfileOut( __METHOD__ );
- $this->reportConnectionError( $error );
+ return $this->reportConnectionError( $error );
}
if ( $dbName != '' ) {
@@ -146,7 +146,7 @@ class DatabaseMysql extends DatabaseBase {
"from client host " . wfHostname() . "\n" );
wfProfileOut( __METHOD__ );
- $this->reportConnectionError( "Error selecting database $dbName" );
+ return $this->reportConnectionError( "Error selecting database $dbName" );
}
}
@@ -193,7 +193,7 @@ class DatabaseMysql extends DatabaseBase {
/**
* @param $res ResultWrapper
- * @return object|stdClass
+ * @return object|bool
* @throws DBUnexpectedError
*/
function fetchObject( $res ) {
@@ -208,7 +208,7 @@ class DatabaseMysql extends DatabaseBase {
// Unfortunately, mysql_fetch_object does not reset the last errno.
// Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as
// these are the only errors mysql_fetch_object can cause.
- // See http://dev.mysql.com/doc/refman/5.0/es/mysql-fetch-row.html.
+ // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
if( $errno == 2000 || $errno == 2013 ) {
throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
}
@@ -217,7 +217,7 @@ class DatabaseMysql extends DatabaseBase {
/**
* @param $res ResultWrapper
- * @return array
+ * @return array|bool
* @throws DBUnexpectedError
*/
function fetchRow( $res ) {
@@ -232,7 +232,7 @@ class DatabaseMysql extends DatabaseBase {
// Unfortunately, mysql_fetch_array does not reset the last errno.
// Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as
// these are the only errors mysql_fetch_object can cause.
- // See http://dev.mysql.com/doc/refman/5.0/es/mysql-fetch-row.html.
+ // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
if( $errno == 2000 || $errno == 2013 ) {
throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) );
}
@@ -251,9 +251,11 @@ class DatabaseMysql extends DatabaseBase {
wfSuppressWarnings();
$n = mysql_num_rows( $res );
wfRestoreWarnings();
- if( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in numRows(): ' . htmlspecialchars( $this->lastError() ) );
- }
+ // Unfortunately, mysql_num_rows does not reset the last errno.
+ // We are not checking for any errors here, since
+ // these are no errors mysql_num_rows can cause.
+ // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
+ // See https://bugzilla.wikimedia.org/42430
return $n;
}
@@ -361,7 +363,7 @@ class DatabaseMysql extends DatabaseBase {
* @param $options string|array
* @return int
*/
- public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'DatabaseMysql::estimateRowCount', $options = array() ) {
+ public function estimateRowCount( $table, $vars = '*', $conds = '', $fname = 'DatabaseMysql::estimateRowCount', $options = array() ) {
$options['EXPLAIN'] = true;
$res = $this->select( $table, $vars, $conds, $fname, $options );
if ( $res === false ) {
@@ -393,7 +395,7 @@ class DatabaseMysql extends DatabaseBase {
for( $i = 0; $i < $n; $i++ ) {
$meta = mysql_fetch_field( $res->result, $i );
if( $field == $meta->name ) {
- return new MySQLField($meta);
+ return new MySQLField( $meta );
}
}
return false;
@@ -414,6 +416,7 @@ class DatabaseMysql extends DatabaseBase {
# http://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html
$table = $this->tableName( $table );
$index = $this->indexName( $index );
+
$sql = 'SHOW INDEX FROM ' . $table;
$res = $this->query( $sql, $fname );
@@ -428,7 +431,6 @@ class DatabaseMysql extends DatabaseBase {
$result[] = $row;
}
}
-
return empty( $result ) ? false : $result;
}
@@ -449,7 +451,7 @@ class DatabaseMysql extends DatabaseBase {
function strencode( $s ) {
$sQuoted = mysql_real_escape_string( $s, $this->mConn );
- if($sQuoted === false) {
+ if( $sQuoted === false ) {
$this->ping();
$sQuoted = mysql_real_escape_string( $s, $this->mConn );
}
@@ -597,10 +599,9 @@ class DatabaseMysql extends DatabaseBase {
if ( $res && $row = $this->fetchRow( $res ) ) {
wfProfileOut( $fname );
return $row[0];
- } else {
- wfProfileOut( $fname );
- return false;
}
+ wfProfileOut( $fname );
+ return false;
}
/**
@@ -686,7 +687,7 @@ class DatabaseMysql extends DatabaseBase {
public function streamStatementEnd( &$sql, &$newLine ) {
if ( strtoupper( substr( $newLine, 0, 9 ) ) == 'DELIMITER' ) {
- preg_match( '/^DELIMITER\s+(\S+)/' , $newLine, $m );
+ preg_match( '/^DELIMITER\s+(\S+)/', $newLine, $m );
$this->delimiter = $m[1];
$newLine = '';
}
@@ -696,8 +697,8 @@ class DatabaseMysql extends DatabaseBase {
/**
* Check to see if a named lock is available. This is non-blocking.
*
- * @param $lockName String: name of lock to poll
- * @param $method String: name of method calling us
+ * @param string $lockName name of lock to poll
+ * @param string $method name of method calling us
* @return Boolean
* @since 1.20
*/
@@ -722,7 +723,7 @@ class DatabaseMysql extends DatabaseBase {
if( $row->lockstatus == 1 ) {
return true;
} else {
- wfDebug( __METHOD__." failed to acquire lock\n" );
+ wfDebug( __METHOD__ . " failed to acquire lock\n" );
return false;
}
}
@@ -745,6 +746,7 @@ class DatabaseMysql extends DatabaseBase {
* @param $write array
* @param $method string
* @param $lowPriority bool
+ * @return bool
*/
public function lockTables( $read, $write, $method, $lowPriority = true ) {
$items = array();
@@ -760,13 +762,16 @@ class DatabaseMysql extends DatabaseBase {
}
$sql = "LOCK TABLES " . implode( ',', $items );
$this->query( $sql, $method );
+ return true;
}
/**
* @param $method string
+ * @return bool
*/
public function unlockTables( $method ) {
$this->query( "UNLOCK TABLES", $method );
+ return true;
}
/**
@@ -805,7 +810,8 @@ class DatabaseMysql extends DatabaseBase {
* @param $delVar string
* @param $joinVar string
* @param $conds array|string
- * @param $fname bool
+ * @param bool|string $fname bool
+ * @throws DBUnexpectedError
* @return bool|ResultWrapper
*/
function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabaseBase::deleteJoin' ) {
@@ -889,8 +895,8 @@ class DatabaseMysql extends DatabaseBase {
/**
* List all tables on the database
*
- * @param $prefix string Only show tables with this prefix, e.g. mw_
- * @param $fname String: calling function name
+ * @param string $prefix Only show tables with this prefix, e.g. mw_
+ * @param string $fname calling function name
* @return array
*/
function listTables( $prefix = null, $fname = 'DatabaseMysql::listTables' ) {
@@ -899,7 +905,7 @@ class DatabaseMysql extends DatabaseBase {
$endArray = array();
foreach( $result as $table ) {
- $vars = get_object_vars($table);
+ $vars = get_object_vars( $table );
$table = array_pop( $vars );
if( !$prefix || strpos( $table, $prefix ) === 0 ) {
@@ -952,13 +958,6 @@ class DatabaseMysql extends DatabaseBase {
}
/**
- * Legacy support: Database == DatabaseMysql
- *
- * @deprecated in 1.16
- */
-class Database extends DatabaseMysql {}
-
-/**
* Utility class.
* @ingroup Database
*/
diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php
index 7d8884fb..75b3550a 100644
--- a/includes/db/DatabaseOracle.php
+++ b/includes/db/DatabaseOracle.php
@@ -23,7 +23,7 @@
/**
* The oci8 extension is fairly weak and doesn't support oci_num_rows, among
- * other things. We use a wrapper class to handle that and other
+ * other things. We use a wrapper class to handle that and other
* Oracle-specific bits, like converting column names back to lowercase.
* @ingroup Database
*/
@@ -69,7 +69,7 @@ class ORAResult {
$this->nrows = count( $this->rows );
}
- if ($this->nrows > 0) {
+ if ( $this->nrows > 0 ) {
foreach ( $this->rows[0] as $k => $v ) {
$this->columns[$k] = strtolower( oci_field_name( $stmt, $k + 1 ) );
}
@@ -80,7 +80,7 @@ class ORAResult {
}
public function free() {
- unset($this->db);
+ unset( $this->db );
}
public function seek( $row ) {
@@ -92,7 +92,7 @@ class ORAResult {
}
public function numFields() {
- return count($this->columns);
+ return count( $this->columns );
}
public function fetchObject() {
@@ -241,6 +241,11 @@ class DatabaseOracle extends DatabaseBase {
/**
* Usually aborts on failure
+ * @param string $server
+ * @param string $user
+ * @param string $password
+ * @param string $dbName
+ * @throws DBConnectionError
* @return DatabaseBase|null
*/
function open( $server, $user, $password, $dbName ) {
@@ -313,7 +318,7 @@ class DatabaseOracle extends DatabaseBase {
protected function doQuery( $sql ) {
wfDebug( "SQL: [$sql]\n" );
- if ( !mb_check_encoding( $sql ) ) {
+ if ( !StringUtils::isUtf8( $sql ) ) {
throw new MWException( "SQL encoding is invalid\n$sql" );
}
@@ -628,7 +633,7 @@ class DatabaseOracle extends DatabaseBase {
}
list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
if ( is_array( $srcTable ) ) {
- $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
+ $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
} else {
$srcTable = $this->tableName( $srcTable );
}
@@ -751,7 +756,7 @@ class DatabaseOracle extends DatabaseBase {
function unionQueries( $sqls, $all ) {
$glue = ' UNION ALL ';
- return 'SELECT * ' . ( $all ? '':'/* UNION_UNIQUE */ ' ) . 'FROM (' . implode( $glue, $sqls ) . ')' ;
+ return 'SELECT * ' . ( $all ? '':'/* UNION_UNIQUE */ ' ) . 'FROM (' . implode( $glue, $sqls ) . ')';
}
function wasDeadlock() {
@@ -773,8 +778,8 @@ class DatabaseOracle extends DatabaseBase {
function listTables( $prefix = null, $fname = 'DatabaseOracle::listTables' ) {
$listWhere = '';
- if (!empty($prefix)) {
- $listWhere = ' AND table_name LIKE \''.strtoupper($prefix).'%\'';
+ if ( !empty( $prefix ) ) {
+ $listWhere = ' AND table_name LIKE \'' . strtoupper( $prefix ) . '%\'';
}
$owner = strtoupper( $this->mDBname );
@@ -782,12 +787,12 @@ class DatabaseOracle extends DatabaseBase {
// dirty code ... i know
$endArray = array();
- $endArray[] = strtoupper($prefix.'MWUSER');
- $endArray[] = strtoupper($prefix.'PAGE');
- $endArray[] = strtoupper($prefix.'IMAGE');
+ $endArray[] = strtoupper( $prefix . 'MWUSER' );
+ $endArray[] = strtoupper( $prefix . 'PAGE' );
+ $endArray[] = strtoupper( $prefix . 'IMAGE' );
$fixedOrderTabs = $endArray;
- while (($row = $result->fetchRow()) !== false) {
- if (!in_array($row['table_name'], $fixedOrderTabs))
+ while ( ($row = $result->fetchRow()) !== false ) {
+ if ( !in_array( $row['table_name'], $fixedOrderTabs ) )
$endArray[] = $row['table_name'];
}
@@ -795,7 +800,7 @@ class DatabaseOracle extends DatabaseBase {
}
public function dropTable( $tableName, $fName = 'DatabaseOracle::dropTable' ) {
- $tableName = $this->tableName($tableName);
+ $tableName = $this->tableName( $tableName );
if( !$this->tableExists( $tableName ) ) {
return false;
}
@@ -841,7 +846,7 @@ class DatabaseOracle extends DatabaseBase {
function getServerVersion() {
//better version number, fallback on driver
$rset = $this->doQuery( 'SELECT version FROM product_component_version WHERE UPPER(product) LIKE \'ORACLE DATABASE%\'' );
- if ( !( $row = $rset->fetchRow() ) ) {
+ if ( !( $row = $rset->fetchRow() ) ) {
return oci_server_version( $this->mConn );
}
return $row['version'];
@@ -902,7 +907,7 @@ class DatabaseOracle extends DatabaseBase {
$table = array_map( array( &$this, 'tableNameInternal' ), $table );
$tableWhere = 'IN (';
foreach( $table as &$singleTable ) {
- $singleTable = $this->removeIdentifierQuotes($singleTable);
+ $singleTable = $this->removeIdentifierQuotes( $singleTable );
if ( isset( $this->mFieldInfoCache["$singleTable.$field"] ) ) {
return $this->mFieldInfoCache["$singleTable.$field"];
}
@@ -910,14 +915,14 @@ class DatabaseOracle extends DatabaseBase {
}
$tableWhere = rtrim( $tableWhere, ',' ) . ')';
} else {
- $table = $this->removeIdentifierQuotes( $this->tableNameInternal( $table ) );
+ $table = $this->removeIdentifierQuotes( $this->tableNameInternal( $table ) );
if ( isset( $this->mFieldInfoCache["$table.$field"] ) ) {
return $this->mFieldInfoCache["$table.$field"];
}
$tableWhere = '= \''.$table.'\'';
}
- $fieldInfoStmt = oci_parse( $this->mConn, 'SELECT * FROM wiki_field_info_full WHERE table_name '.$tableWhere.' and column_name = \''.$field.'\'' );
+ $fieldInfoStmt = oci_parse( $this->mConn, 'SELECT * FROM wiki_field_info_full WHERE table_name ' . $tableWhere . ' and column_name = \'' . $field . '\'' );
if ( oci_execute( $fieldInfoStmt, $this->execFlags() ) === false ) {
$e = oci_error( $fieldInfoStmt );
$this->reportQueryError( $e['message'], $e['code'], 'fieldInfo QUERY', __METHOD__ );
@@ -952,7 +957,7 @@ class DatabaseOracle extends DatabaseBase {
if ( is_array( $table ) ) {
throw new DBUnexpectedError( $this, 'DatabaseOracle::fieldInfo called with table array!' );
}
- return $this->fieldInfoMulti ($table, $field);
+ return $this->fieldInfoMulti( $table, $field );
}
protected function doBegin( $fname = 'DatabaseOracle::begin' ) {
@@ -1061,7 +1066,7 @@ class DatabaseOracle extends DatabaseBase {
if ( $db == null || $db == $this->mUser ) {
return true;
}
- $sql = 'ALTER SESSION SET CURRENT_SCHEMA=' . strtoupper($db);
+ $sql = 'ALTER SESSION SET CURRENT_SCHEMA=' . strtoupper( $db );
$stmt = oci_parse( $this->mConn, $sql );
wfSuppressWarnings();
$success = oci_execute( $stmt );
@@ -1096,11 +1101,11 @@ class DatabaseOracle extends DatabaseBase {
}
public function removeIdentifierQuotes( $s ) {
- return strpos($s, '/*Q*/') === FALSE ? $s : substr($s, 5);
+ return strpos( $s, '/*Q*/' ) === false ? $s : substr( $s, 5 );
}
public function isQuotedIdentifier( $s ) {
- return strpos($s, '/*Q*/') !== FALSE;
+ return strpos( $s, '/*Q*/' ) !== false;
}
private function wrapFieldForWhere( $table, &$col, &$val ) {
@@ -1111,7 +1116,7 @@ class DatabaseOracle extends DatabaseBase {
if ( $col_type == 'CLOB' ) {
$col = 'TO_CHAR(' . $col . ')';
$val = $wgContLang->checkTitleEncoding( $val );
- } elseif ( $col_type == 'VARCHAR2' && !mb_check_encoding( $val ) ) {
+ } elseif ( $col_type == 'VARCHAR2' ) {
$val = $wgContLang->checkTitleEncoding( $val );
}
}
@@ -1134,7 +1139,7 @@ class DatabaseOracle extends DatabaseBase {
}
function selectRow( $table, $vars, $conds, $fname = 'DatabaseOracle::selectRow', $options = array(), $join_conds = array() ) {
- if ( is_array($conds) ) {
+ if ( is_array( $conds ) ) {
$conds = $this->wrapConditionsForWhere( $table, $conds );
}
return parent::selectRow( $table, $vars, $conds, $fname, $options, $join_conds );
@@ -1146,7 +1151,7 @@ class DatabaseOracle extends DatabaseBase {
*
* @private
*
- * @param $options Array: an associative array of options to be turned into
+ * @param array $options an associative array of options to be turned into
* an SQL query, valid keys are listed in the function.
* @return array
*/
@@ -1161,15 +1166,14 @@ class DatabaseOracle extends DatabaseBase {
}
}
- if ( isset( $options['GROUP BY'] ) ) {
- $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
- }
- if ( isset( $options['ORDER BY'] ) ) {
- $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
+ $preLimitTail .= $this->makeGroupByWithHaving( $options );
+
+ $preLimitTail .= $this->makeOrderBy( $options );
+
+ if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
+ $postLimitTail .= ' FOR UPDATE';
}
- # if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $tailOpts .= ' FOR UPDATE';
- # if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $tailOpts .= ' LOCK IN SHARE MODE';
if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
$startOpts .= 'DISTINCT';
}
@@ -1184,13 +1188,13 @@ class DatabaseOracle extends DatabaseBase {
}
public function delete( $table, $conds, $fname = 'DatabaseOracle::delete' ) {
- if ( is_array($conds) ) {
+ if ( is_array( $conds ) ) {
$conds = $this->wrapConditionsForWhere( $table, $conds );
}
// a hack for deleting pages, users and images (which have non-nullable FKs)
// all deletions on these tables have transactions so final failure rollbacks these updates
$table = $this->tableName( $table );
- if ( $table == $this->tableName( 'user' ) ) {
+ if ( $table == $this->tableName( 'user' ) ) {
$this->update( 'archive', array( 'ar_user' => 0 ), array( 'ar_user' => $conds['user_id'] ), $fname );
$this->update( 'ipblocks', array( 'ipb_user' => 0 ), array( 'ipb_user' => $conds['user_id'] ), $fname );
$this->update( 'image', array( 'img_user' => 0 ), array( 'img_user' => $conds['user_id'] ), $fname );
@@ -1200,7 +1204,7 @@ class DatabaseOracle extends DatabaseBase {
$this->update( 'uploadstash', array( 'us_user' => 0 ), array( 'us_user' => $conds['user_id'] ), $fname );
$this->update( 'recentchanges', array( 'rc_user' => 0 ), array( 'rc_user' => $conds['user_id'] ), $fname );
$this->update( 'logging', array( 'log_user' => 0 ), array( 'log_user' => $conds['user_id'] ), $fname );
- } elseif ( $table == $this->tableName( 'image' ) ) {
+ } elseif ( $table == $this->tableName( 'image' ) ) {
$this->update( 'oldimage', array( 'oi_name' => 0 ), array( 'oi_name' => $conds['img_name'] ), $fname );
}
return parent::delete( $table, $conds, $fname );
diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php
index 457bf384..f32d7758 100644
--- a/includes/db/DatabasePostgres.php
+++ b/includes/db/DatabasePostgres.php
@@ -176,8 +176,8 @@ class PostgresTransactionState {
$old = reset( $this->mCurrentState );
$new = reset( $this->mNewState );
foreach ( self::$WATCHED as $watched ) {
- if ($old !== $new) {
- $this->log_changed($old, $new, $watched);
+ if ( $old !== $new ) {
+ $this->log_changed( $old, $new, $watched );
}
$old = next( $this->mCurrentState );
$new = next( $this->mNewState );
@@ -197,11 +197,11 @@ class PostgresTransactionState {
}
protected function log_changed( $old, $new, $watched ) {
- wfDebug(sprintf($watched["desc"],
+ wfDebug( sprintf( $watched["desc"],
$this->mConn,
$this->describe_changed( $old, $watched["states"] ),
- $this->describe_changed( $new, $watched["states"] ))
- );
+ $this->describe_changed( $new, $watched["states"] )
+ ) );
}
}
@@ -218,7 +218,7 @@ class SavepointPostgres {
protected $id;
protected $didbegin;
- public function __construct ($dbw, $id) {
+ public function __construct ( $dbw, $id ) {
$this->dbw = $dbw;
$this->id = $id;
$this->didbegin = false;
@@ -232,12 +232,14 @@ class SavepointPostgres {
public function __destruct() {
if ( $this->didbegin ) {
$this->dbw->rollback();
+ $this->didbegin = false;
}
}
public function commit() {
if ( $this->didbegin ) {
$this->dbw->commit();
+ $this->didbegin = false;
}
}
@@ -245,29 +247,29 @@ class SavepointPostgres {
global $wgDebugDBTransactions;
if ( $this->dbw->doQuery( $keyword . " " . $this->id ) !== false ) {
if ( $wgDebugDBTransactions ) {
- wfDebug( sprintf ($msg_ok, $this->id ) );
+ wfDebug( sprintf ( $msg_ok, $this->id ) );
}
} else {
- wfDebug( sprintf ($msg_failed, $this->id ) );
+ wfDebug( sprintf ( $msg_failed, $this->id ) );
}
}
public function savepoint() {
- $this->query("SAVEPOINT",
+ $this->query( "SAVEPOINT",
"Transaction state: savepoint \"%s\" established.\n",
"Transaction state: establishment of savepoint \"%s\" FAILED.\n"
);
}
public function release() {
- $this->query("RELEASE",
+ $this->query( "RELEASE",
"Transaction state: savepoint \"%s\" released.\n",
"Transaction state: release of savepoint \"%s\" FAILED.\n"
);
}
public function rollback() {
- $this->query("ROLLBACK TO",
+ $this->query( "ROLLBACK TO",
"Transaction state: savepoint \"%s\" rolled back.\n",
"Transaction state: rollback of savepoint \"%s\" FAILED.\n"
);
@@ -325,6 +327,11 @@ class DatabasePostgres extends DatabaseBase {
/**
* Usually aborts on failure
+ * @param string $server
+ * @param string $user
+ * @param string $password
+ * @param string $dbName
+ * @throws DBConnectionError
* @return DatabaseBase|null
*/
function open( $server, $user, $password, $dbName ) {
@@ -386,6 +393,9 @@ class DatabasePostgres extends DatabaseBase {
$this->query( "SET datestyle = 'ISO, YMD'", __METHOD__ );
$this->query( "SET timezone = 'GMT'", __METHOD__ );
$this->query( "SET standard_conforming_strings = on", __METHOD__ );
+ if ( $this->getServerVersion() >= 9.0 ) {
+ $this->query( "SET bytea_output = 'escape'", __METHOD__ ); // PHP bug 53127
+ }
global $wgDBmwschema;
$this->determineCoreSchema( $wgDBmwschema );
@@ -472,7 +482,6 @@ class DatabasePostgres extends DatabaseBase {
parent::reportQueryError( $error, $errno, $sql, $fname, false );
}
-
function queryIgnore( $sql, $fname = 'DatabasePostgres::queryIgnore' ) {
return $this->query( $sql, $fname, true );
}
@@ -599,7 +608,7 @@ class DatabasePostgres extends DatabaseBase {
* Takes same arguments as Database::select()
* @return int
*/
- function estimateRowCount( $table, $vars = '*', $conds='', $fname = 'DatabasePostgres::estimateRowCount', $options = array() ) {
+ function estimateRowCount( $table, $vars = '*', $conds = '', $fname = 'DatabasePostgres::estimateRowCount', $options = array() ) {
$options['EXPLAIN'] = true;
$res = $this->select( $table, $vars, $conds, $fname, $options );
$rows = -1;
@@ -677,7 +686,7 @@ class DatabasePostgres extends DatabaseBase {
AND i.indclass[s.g] = opcls.oid
AND pg_am.oid = opcls.opcmethod
__INDEXATTR__;
- $res = $this->query($sql, __METHOD__);
+ $res = $this->query( $sql, __METHOD__ );
$a = array();
if ( $res ) {
foreach ( $res as $row ) {
@@ -685,7 +694,7 @@ __INDEXATTR__;
$row->attname,
$row->opcname,
$row->amname,
- $row->option);
+ $row->option );
}
} else {
return null;
@@ -693,7 +702,6 @@ __INDEXATTR__;
return $a;
}
-
function indexUnique( $table, $index, $fname = 'DatabasePostgres::indexUnique' ) {
$sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'".
" AND indexdef LIKE 'CREATE UNIQUE%(" .
@@ -718,7 +726,7 @@ __INDEXATTR__;
* @param $table String: Name of the table to insert to.
* @param $args Array: Items to insert into the table.
* @param $fname String: Name of the function, for profiling
- * @param $options String or Array. Valid options: IGNORE
+ * @param string $options or Array. Valid options: IGNORE
*
* @return bool Success of insert operation. IGNORE always returns true.
*/
@@ -728,7 +736,7 @@ __INDEXATTR__;
}
$table = $this->tableName( $table );
- if (! isset( $this->numeric_version ) ) {
+ if ( !isset( $this->numeric_version ) ) {
$this->getServerVersion();
}
@@ -980,7 +988,7 @@ __INDEXATTR__;
$endArray = array();
foreach( $result as $table ) {
- $vars = get_object_vars($table);
+ $vars = get_object_vars( $table );
$table = array_pop( $vars );
if( !$prefix || strpos( $table, $prefix ) === 0 ) {
$endArray[] = $table;
@@ -1052,7 +1060,6 @@ __INDEXATTR__;
return '[http://www.postgresql.org/ PostgreSQL]';
}
-
/**
* Return current schema (executes SELECT current_schema())
* Needs transaction
@@ -1061,7 +1068,7 @@ __INDEXATTR__;
* @return string return default schema for the current session
*/
function getCurrentSchema() {
- $res = $this->query( "SELECT current_schema()", __METHOD__);
+ $res = $this->query( "SELECT current_schema()", __METHOD__ );
$row = $this->fetchRow( $res );
return $row[0];
}
@@ -1071,17 +1078,17 @@ __INDEXATTR__;
* This is list does not contain magic keywords like "$user"
* Needs transaction
*
- * @seealso getSearchPath()
- * @seealso setSearchPath()
+ * @see getSearchPath()
+ * @see setSearchPath()
* @since 1.19
* @return array list of actual schemas for the current sesson
*/
function getSchemas() {
- $res = $this->query( "SELECT current_schemas(false)", __METHOD__);
+ $res = $this->query( "SELECT current_schemas(false)", __METHOD__ );
$row = $this->fetchRow( $res );
$schemas = array();
/* PHP pgsql support does not support array type, "{a,b}" string is returned */
- return $this->pg_array_parse($row[0], $schemas);
+ return $this->pg_array_parse( $row[0], $schemas );
}
/**
@@ -1094,10 +1101,10 @@ __INDEXATTR__;
* @return array how to search for table names schemas for the current user
*/
function getSearchPath() {
- $res = $this->query( "SHOW search_path", __METHOD__);
+ $res = $this->query( "SHOW search_path", __METHOD__ );
$row = $this->fetchRow( $res );
/* PostgreSQL returns SHOW values as strings */
- return explode(",", $row[0]);
+ return explode( ",", $row[0] );
}
/**
@@ -1108,7 +1115,7 @@ __INDEXATTR__;
* @param $search_path array list of schemas to be searched by default
*/
function setSearchPath( $search_path ) {
- $this->query( "SET search_path = " . implode(", ", $search_path) );
+ $this->query( "SET search_path = " . implode( ", ", $search_path ) );
}
/**
@@ -1129,7 +1136,7 @@ __INDEXATTR__;
if ( $this->schemaExists( $desired_schema ) ) {
if ( in_array( $desired_schema, $this->getSchemas() ) ) {
$this->mCoreSchema = $desired_schema;
- wfDebug("Schema \"" . $desired_schema . "\" already in the search path\n");
+ wfDebug( "Schema \"" . $desired_schema . "\" already in the search path\n" );
} else {
/**
* Prepend our schema (e.g. 'mediawiki') in front
@@ -1141,11 +1148,11 @@ __INDEXATTR__;
$this->addIdentifierQuotes( $desired_schema ));
$this->setSearchPath( $search_path );
$this->mCoreSchema = $desired_schema;
- wfDebug("Schema \"" . $desired_schema . "\" added to the search path\n");
+ wfDebug( "Schema \"" . $desired_schema . "\" added to the search path\n" );
}
} else {
$this->mCoreSchema = $this->getCurrentSchema();
- wfDebug("Schema \"" . $desired_schema . "\" not found, using current \"". $this->mCoreSchema ."\"\n");
+ wfDebug( "Schema \"" . $desired_schema . "\" not found, using current \"" . $this->mCoreSchema . "\"\n" );
}
/* Commit SET otherwise it will be rollbacked on error or IGNORE SELECT */
$this->commit( __METHOD__ );
@@ -1251,8 +1258,8 @@ SQL;
}
function constraintExists( $table, $constraint ) {
- $SQL = sprintf( "SELECT 1 FROM information_schema.table_constraints ".
- "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
+ $SQL = sprintf( "SELECT 1 FROM information_schema.table_constraints " .
+ "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
$this->addQuotes( $this->getCoreSchema() ),
$this->addQuotes( $table ),
$this->addQuotes( $constraint )
@@ -1340,7 +1347,7 @@ SQL;
*
* @private
*
- * @param $ins String: SQL string, read from a stream (usually tables.sql)
+ * @param string $ins SQL string, read from a stream (usually tables.sql)
*
* @return string SQL string
*/
@@ -1364,7 +1371,7 @@ SQL;
*
* @private
*
- * @param $options Array: an associative array of options to be turned into
+ * @param array $options an associative array of options to be turned into
* an SQL query, valid keys are listed in the function.
* @return array
*/
@@ -1379,23 +1386,9 @@ SQL;
}
}
- if ( isset( $options['GROUP BY'] ) ) {
- $gb = is_array( $options['GROUP BY'] )
- ? implode( ',', $options['GROUP BY'] )
- : $options['GROUP BY'];
- $preLimitTail .= " GROUP BY {$gb}";
- }
+ $preLimitTail .= $this->makeGroupByWithHaving( $options );
- if ( isset( $options['HAVING'] ) ) {
- $preLimitTail .= " HAVING {$options['HAVING']}";
- }
-
- if ( isset( $options['ORDER BY'] ) ) {
- $ob = is_array( $options['ORDER BY'] )
- ? implode( ',', $options['ORDER BY'] )
- : $options['ORDER BY'];
- $preLimitTail .= " ORDER BY {$ob}";
- }
+ $preLimitTail .= $this->makeOrderBy( $options );
//if ( isset( $options['LIMIT'] ) ) {
// $tailOpts .= $this->limitResult( '', $options['LIMIT'],
@@ -1443,4 +1436,65 @@ SQL;
}
return parent::streamStatementEnd( $sql, $newLine );
}
+
+ /**
+ * Check to see if a named lock is available. This is non-blocking.
+ * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
+ *
+ * @param string $lockName name of lock to poll
+ * @param string $method name of method calling us
+ * @return Boolean
+ * @since 1.20
+ */
+ public function lockIsFree( $lockName, $method ) {
+ $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
+ $result = $this->query( "SELECT (CASE(pg_try_advisory_lock($key))
+ WHEN 'f' THEN 'f' ELSE pg_advisory_unlock($key) END) AS lockstatus", $method );
+ $row = $this->fetchObject( $result );
+ return ( $row->lockstatus === 't' );
+ }
+
+ /**
+ * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
+ * @param $lockName string
+ * @param $method string
+ * @param $timeout int
+ * @return bool
+ */
+ public function lock( $lockName, $method, $timeout = 5 ) {
+ $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
+ for ( $attempts = 1; $attempts <= $timeout; ++$attempts ) {
+ $result = $this->query(
+ "SELECT pg_try_advisory_lock($key) AS lockstatus", $method );
+ $row = $this->fetchObject( $result );
+ if ( $row->lockstatus === 't' ) {
+ return true;
+ } else {
+ sleep( 1 );
+ }
+ }
+ wfDebug( __METHOD__." failed to acquire lock\n" );
+ return false;
+ }
+
+ /**
+ * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKSFROM PG DOCS: http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
+ * @param $lockName string
+ * @param $method string
+ * @return bool
+ */
+ public function unlock( $lockName, $method ) {
+ $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
+ $result = $this->query( "SELECT pg_advisory_unlock($key) as lockstatus", $method );
+ $row = $this->fetchObject( $result );
+ return ( $row->lockstatus === 't' );
+ }
+
+ /**
+ * @param string $lockName
+ * @return string Integer
+ */
+ private function bigintFromLockName( $lockName ) {
+ return wfBaseConvert( substr( sha1( $lockName ), 0, 15 ), 16, 10 );
+ }
} // end DatabasePostgres class
diff --git a/includes/db/DatabaseSqlite.php b/includes/db/DatabaseSqlite.php
index f1e553d7..0789e1b1 100644
--- a/includes/db/DatabaseSqlite.php
+++ b/includes/db/DatabaseSqlite.php
@@ -79,11 +79,12 @@ class DatabaseSqlite extends DatabaseBase {
/** Open an SQLite database and return a resource handle to it
* NOTE: only $dbName is used, the other parameters are irrelevant for SQLite databases
*
- * @param $server
- * @param $user
- * @param $pass
- * @param $dbName
+ * @param string $server
+ * @param string $user
+ * @param string $pass
+ * @param string $dbName
*
+ * @throws DBConnectionError
* @return PDO
*/
function open( $server, $user, $pass, $dbName ) {
@@ -103,6 +104,7 @@ class DatabaseSqlite extends DatabaseBase {
*
* @param $fileName string
*
+ * @throws DBConnectionError
* @return PDO|bool SQL connection or false if failed
*/
function openFile( $fileName ) {
@@ -125,6 +127,8 @@ class DatabaseSqlite extends DatabaseBase {
# set error codes only, don't raise exceptions
if ( $this->mOpened ) {
$this->mConn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
+ # Enforce LIKE to be case sensitive, just like MySQL
+ $this->query( 'PRAGMA case_sensitive_like = 1' );
return true;
}
}
@@ -140,8 +144,8 @@ class DatabaseSqlite extends DatabaseBase {
/**
* Generates a database file name. Explicitly public for installer.
- * @param $dir String: Directory where database resides
- * @param $dbName String: Database name
+ * @param string $dir Directory where database resides
+ * @param string $dbName Database name
* @return String
*/
public static function generateFileName( $dir, $dbName ) {
@@ -159,7 +163,7 @@ class DatabaseSqlite extends DatabaseBase {
$res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name = '$table'", __METHOD__ );
if ( $res ) {
$row = $res->fetchRow();
- self::$fulltextEnabled = stristr($row['sql'], 'fts' ) !== false;
+ self::$fulltextEnabled = stristr( $row['sql'], 'fts' ) !== false;
}
}
return self::$fulltextEnabled;
@@ -190,9 +194,9 @@ class DatabaseSqlite extends DatabaseBase {
* Attaches external database to our connection, see http://sqlite.org/lang_attach.html
* for details.
*
- * @param $name String: database name to be used in queries like SELECT foo FROM dbname.table
- * @param $file String: database file name. If omitted, will be generated using $name and $wgSQLiteDataDir
- * @param $fname String: calling function name
+ * @param string $name database name to be used in queries like SELECT foo FROM dbname.table
+ * @param string $file database file name. If omitted, will be generated using $name and $wgSQLiteDataDir
+ * @param string $fname calling function name
*
* @return ResultWrapper
*/
@@ -248,7 +252,7 @@ class DatabaseSqlite extends DatabaseBase {
/**
* @param $res ResultWrapper
- * @return
+ * @return object|bool
*/
function fetchObject( $res ) {
if ( $res instanceof ResultWrapper ) {
@@ -274,7 +278,7 @@ class DatabaseSqlite extends DatabaseBase {
/**
* @param $res ResultWrapper
- * @return bool|mixed
+ * @return array|bool
*/
function fetchRow( $res ) {
if ( $res instanceof ResultWrapper ) {
@@ -467,7 +471,7 @@ class DatabaseSqlite extends DatabaseBase {
*/
function makeSelectOptions( $options ) {
foreach ( $options as $k => $v ) {
- if ( is_numeric( $k ) && $v == 'FOR UPDATE' ) {
+ if ( is_numeric( $k ) && ($v == 'FOR UPDATE' || $v == 'LOCK IN SHARE MODE') ) {
$options[$k] = '';
}
}
@@ -593,7 +597,7 @@ class DatabaseSqlite extends DatabaseBase {
* @return bool
*/
function wasErrorReissuable() {
- return $this->lastErrno() == 17; // SQLITE_SCHEMA;
+ return $this->lastErrno() == 17; // SQLITE_SCHEMA;
}
/**
@@ -703,6 +707,14 @@ class DatabaseSqlite extends DatabaseBase {
function addQuotes( $s ) {
if ( $s instanceof Blob ) {
return "x'" . bin2hex( $s->fetch() ) . "'";
+ } else if ( strpos( $s, "\0" ) !== false ) {
+ // SQLite doesn't support \0 in strings, so use the hex representation as a workaround.
+ // This is a known limitation of SQLite's mprintf function which PDO should work around,
+ // but doesn't. I have reported this to php.net as bug #63419:
+ // https://bugs.php.net/bug.php?id=63419
+ // There was already a similar report for SQLite3::escapeString, bug #62361:
+ // https://bugs.php.net/bug.php?id=62361
+ return "x'" . bin2hex( $s ) . "'";
} else {
return $this->mConn->quote( $s );
}
@@ -819,12 +831,11 @@ class DatabaseSqlite extends DatabaseBase {
return $this->query( $sql, $fname );
}
-
/**
* List all tables on the database
*
- * @param $prefix string Only show tables with this prefix, e.g. mw_
- * @param $fname String: calling function name
+ * @param string $prefix Only show tables with this prefix, e.g. mw_
+ * @param string $fname calling function name
*
* @return array
*/
@@ -838,7 +849,7 @@ class DatabaseSqlite extends DatabaseBase {
$endArray = array();
foreach( $result as $table ) {
- $vars = get_object_vars($table);
+ $vars = get_object_vars( $table );
$table = array_pop( $vars );
if( !$prefix || strpos( $table, $prefix ) === 0 ) {
diff --git a/includes/db/DatabaseUtility.php b/includes/db/DatabaseUtility.php
index c846788d..9a1c8bde 100644
--- a/includes/db/DatabaseUtility.php
+++ b/includes/db/DatabaseUtility.php
@@ -1,6 +1,6 @@
<?php
/**
- * This file contains database-related utiliy classes.
+ * This file contains database-related utility classes.
*
* 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
@@ -138,7 +138,7 @@ class ResultWrapper implements Iterator {
/**
* Fetch the next row from the given result object, in associative array
- * form. Fields are retrieved with $row['fieldname'].
+ * form. Fields are retrieved with $row['fieldname'].
*
* @return Array
* @throws DBUnexpectedError Thrown if the database returns an error
@@ -219,9 +219,9 @@ class ResultWrapper implements Iterator {
* doesn't go anywhere near an actual database.
*/
class FakeResultWrapper extends ResultWrapper {
- var $result = array();
- var $db = null; // And it's going to stay that way :D
- var $pos = 0;
+ var $result = array();
+ var $db = null; // And it's going to stay that way :D
+ var $pos = 0;
var $currentRow = null;
function __construct( $array ) {
@@ -285,7 +285,7 @@ class LikeMatch {
/**
* Store a string into a LikeMatch marker object.
*
- * @param String $s
+ * @param string $s
*/
public function __construct( $s ) {
$this->str = $s;
@@ -306,4 +306,3 @@ class LikeMatch {
*/
interface DBMasterPos {
}
-
diff --git a/includes/db/IORMRow.php b/includes/db/IORMRow.php
index e99ba6cc..6bc0cdd2 100644
--- a/includes/db/IORMRow.php
+++ b/includes/db/IORMRow.php
@@ -27,13 +27,12 @@
* @file
* @ingroup ORM
*
- * @licence GNU GPL v2 or later
+ * @license GNU GPL v2 or later
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
interface IORMRow {
-
/**
* Constructor.
*
@@ -272,4 +271,4 @@ interface IORMRow {
*/
public function getTable();
-} \ No newline at end of file
+}
diff --git a/includes/db/IORMTable.php b/includes/db/IORMTable.php
index 99413f99..36865655 100644
--- a/includes/db/IORMTable.php
+++ b/includes/db/IORMTable.php
@@ -23,7 +23,7 @@
* @file
* @ingroup ORM
*
- * @licence GNU GPL v2 or later
+ * @license GNU GPL v2 or later
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
@@ -97,6 +97,8 @@ interface IORMTable {
* Selects the the specified fields of the records matching the provided
* conditions and returns them as DBDataObject. Field names get prefixed.
*
+ * @see DatabaseBase::select()
+ *
* @since 1.20
*
* @param array|string|null $fields
@@ -104,7 +106,8 @@ interface IORMTable {
* @param array $options
* @param string|null $functionName
*
- * @return ORMResult
+ * @return ORMResult The result set
+ * @throws DBQueryError if the query failed (even if the database was in ignoreErrors mode)
*/
public function select( $fields = null, array $conditions = array(),
array $options = array(), $functionName = null );
@@ -136,6 +139,7 @@ interface IORMTable {
* @param null|string $functionName
*
* @return ResultWrapper
+ * @throws DBQueryError if the query failed (even if the database was in ignoreErrors mode)
*/
public function rawSelect( $fields = null, array $conditions = array(),
array $options = array(), $functionName = null );
@@ -230,6 +234,15 @@ interface IORMTable {
public function has( array $conditions = array() );
/**
+ * Checks if the table exists
+ *
+ * @since 1.21
+ *
+ * @return boolean
+ */
+ public function exists();
+
+ /**
* Returns the amount of matching records.
* Condition field names get prefixed.
*
@@ -299,6 +312,71 @@ interface IORMTable {
public function setReadDb( $db );
/**
+ * Get the ID of the any foreign wiki to use as a target for database operations
+ *
+ * @since 1.20
+ *
+ * @return String|bool The target wiki, in a form that LBFactory understands (or false if the local wiki is used)
+ */
+ public function getTargetWiki();
+
+ /**
+ * Set the ID of the any foreign wiki to use as a target for database operations
+ *
+ * @param string|bool $wiki The target wiki, in a form that LBFactory understands (or false if the local wiki shall be used)
+ *
+ * @since 1.20
+ */
+ public function setTargetWiki( $wiki );
+
+ /**
+ * Get the database type used for read operations.
+ * This is to be used instead of wfGetDB.
+ *
+ * @see LoadBalancer::getConnection
+ *
+ * @since 1.20
+ *
+ * @return DatabaseBase The database object
+ */
+ public function getReadDbConnection();
+
+ /**
+ * Get the database type used for read operations.
+ * This is to be used instead of wfGetDB.
+ *
+ * @see LoadBalancer::getConnection
+ *
+ * @since 1.20
+ *
+ * @return DatabaseBase The database object
+ */
+ public function getWriteDbConnection();
+
+ /**
+ * Get the database type used for read operations.
+ *
+ * @see wfGetLB
+ *
+ * @since 1.20
+ *
+ * @return LoadBalancer The database load balancer object
+ */
+ public function getLoadBalancer();
+
+ /**
+ * Releases the lease on the given database connection. This is useful mainly
+ * for connections to a foreign wiki. It does nothing for connections to the local wiki.
+ *
+ * @see LoadBalancer::reuseConnection
+ *
+ * @param DatabaseBase $db the database
+ *
+ * @since 1.20
+ */
+ public function releaseConnection( DatabaseBase $db );
+
+ /**
* Update the records matching the provided conditions by
* setting the fields that are keys in the $values param to
* their corresponding values.
@@ -381,15 +459,6 @@ interface IORMTable {
public function unprefixFieldName( $fieldName );
/**
- * Get an instance of this class.
- *
- * @since 1.20
- *
- * @return IORMTable
- */
- public static function singleton();
-
- /**
* Get an array with fields from a database result,
* that can be fed directly to the constructor or
* to setFields.
diff --git a/includes/db/LBFactory.php b/includes/db/LBFactory.php
index e82c54ba..d469e867 100644
--- a/includes/db/LBFactory.php
+++ b/includes/db/LBFactory.php
@@ -86,7 +86,7 @@ abstract class LBFactory {
* Create a new load balancer object. The resulting object will be untracked,
* not chronology-protected, and the caller is responsible for cleaning it up.
*
- * @param $wiki String: wiki ID, or false for the current wiki
+ * @param string $wiki wiki ID, or false for the current wiki
* @return LoadBalancer
*/
abstract function newMainLB( $wiki = false );
@@ -94,7 +94,7 @@ abstract class LBFactory {
/**
* Get a cached (tracked) load balancer object.
*
- * @param $wiki String: wiki ID, or false for the current wiki
+ * @param string $wiki wiki ID, or false for the current wiki
* @return LoadBalancer
*/
abstract function getMainLB( $wiki = false );
@@ -104,8 +104,8 @@ abstract class LBFactory {
* untracked, not chronology-protected, and the caller is responsible for
* cleaning it up.
*
- * @param $cluster String: external storage cluster, or false for core
- * @param $wiki String: wiki ID, or false for the current wiki
+ * @param string $cluster external storage cluster, or false for core
+ * @param string $wiki wiki ID, or false for the current wiki
*
* @return LoadBalancer
*/
@@ -114,8 +114,8 @@ abstract class LBFactory {
/**
* Get a cached (tracked) load balancer for external storage
*
- * @param $cluster String: external storage cluster, or false for core
- * @param $wiki String: wiki ID, or false for the current wiki
+ * @param string $cluster external storage cluster, or false for core
+ * @param string $wiki wiki ID, or false for the current wiki
*
* @return LoadBalancer
*/
@@ -240,7 +240,7 @@ class LBFactory_Simple extends LBFactory {
function newExternalLB( $cluster, $wiki = false ) {
global $wgExternalServers;
if ( !isset( $wgExternalServers[$cluster] ) ) {
- throw new MWException( __METHOD__.": Unknown cluster \"$cluster\"" );
+ throw new MWException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
}
return new LoadBalancer( array(
'servers' => $wgExternalServers[$cluster]
@@ -345,7 +345,7 @@ class ChronologyProtector {
if ( $lb->getServerCount() > 1 && !empty( $this->startupPos[$masterName] ) ) {
$info = $lb->parentInfo();
$pos = $this->startupPos[$masterName];
- wfDebug( __METHOD__.": LB " . $info['id'] . " waiting for master pos $pos\n" );
+ wfDebug( __METHOD__ . ": LB " . $info['id'] . " waiting for master pos $pos\n" );
$lb->waitFor( $this->startupPos[$masterName] );
}
}
@@ -370,11 +370,11 @@ class ChronologyProtector {
$db = $lb->getAnyOpenConnection( 0 );
$info = $lb->parentInfo();
if ( !$db || !$db->doneWrites() ) {
- wfDebug( __METHOD__.": LB {$info['id']}, no writes done\n" );
+ wfDebug( __METHOD__ . ": LB {$info['id']}, no writes done\n" );
return;
}
$pos = $db->getMasterPos();
- wfDebug( __METHOD__.": LB {$info['id']} has master pos $pos\n" );
+ wfDebug( __METHOD__ . ": LB {$info['id']} has master pos $pos\n" );
$this->shutdownPos[$masterName] = $pos;
}
@@ -384,7 +384,7 @@ class ChronologyProtector {
*/
function shutdown() {
if ( session_id() != '' && count( $this->shutdownPos ) ) {
- wfDebug( __METHOD__.": saving master pos for " .
+ wfDebug( __METHOD__ . ": saving master pos for " .
count( $this->shutdownPos ) . " master(s)\n" );
$_SESSION[__CLASS__] = $this->shutdownPos;
}
diff --git a/includes/db/LBFactory_Multi.php b/includes/db/LBFactory_Multi.php
index 6008813b..2e4963d4 100644
--- a/includes/db/LBFactory_Multi.php
+++ b/includes/db/LBFactory_Multi.php
@@ -21,7 +21,6 @@
* @ingroup Database
*/
-
/**
* A multi-wiki, multi-master factory for Wikimedia and similar installations.
* Ignores the old configuration globals
@@ -70,6 +69,7 @@ class LBFactory_Multi extends LBFactory {
/**
* @param $conf array
+ * @throws MWException
*/
function __construct( $conf ) {
$this->chronProt = new ChronologyProtector;
@@ -82,7 +82,7 @@ class LBFactory_Multi extends LBFactory {
foreach ( $required as $key ) {
if ( !isset( $conf[$key] ) ) {
- throw new MWException( __CLASS__.": $key is required in configuration" );
+ throw new MWException( __CLASS__ . ": $key is required in configuration" );
}
$this->$key = $conf[$key];
}
@@ -153,13 +153,14 @@ class LBFactory_Multi extends LBFactory {
}
/**
- * @param $cluster
- * @param $wiki
+ * @param string $cluster
+ * @param bool $wiki
+ * @throws MWException
* @return LoadBalancer
*/
function newExternalLB( $cluster, $wiki = false ) {
if ( !isset( $this->externalLoads[$cluster] ) ) {
- throw new MWException( __METHOD__.": Unknown cluster \"$cluster\"" );
+ throw new MWException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
}
$template = $this->serverTemplate;
if ( isset( $this->externalTemplateOverrides ) ) {
diff --git a/includes/db/LBFactory_Single.php b/includes/db/LBFactory_Single.php
index 4b165b2a..7dca06d7 100644
--- a/includes/db/LBFactory_Single.php
+++ b/includes/db/LBFactory_Single.php
@@ -28,7 +28,7 @@ class LBFactory_Single extends LBFactory {
protected $lb;
/**
- * @param $conf array An associative array with one member:
+ * @param array $conf An associative array with one member:
* - connection: The DatabaseBase connection object
*/
function __construct( $conf ) {
diff --git a/includes/db/LoadBalancer.php b/includes/db/LoadBalancer.php
index 0e455e0c..1e859278 100644
--- a/includes/db/LoadBalancer.php
+++ b/includes/db/LoadBalancer.php
@@ -37,14 +37,15 @@ class LoadBalancer {
private $mLoadMonitorClass, $mLoadMonitor;
/**
- * @param $params Array with keys:
+ * @param array $params with keys:
* servers Required. Array of server info structures.
* masterWaitTimeout Replication lag wait timeout
* loadMonitor Name of a class used to fetch server lag and load.
+ * @throws MWException
*/
function __construct( $params ) {
if ( !isset( $params['servers'] ) ) {
- throw new MWException( __CLASS__.': missing servers parameter' );
+ throw new MWException( __CLASS__ . ': missing servers parameter' );
}
$this->mServers = $params['servers'];
@@ -116,34 +117,14 @@ class LoadBalancer {
* Given an array of non-normalised probabilities, this function will select
* an element and return the appropriate key
*
+ * @deprecated 1.21, use ArrayUtils::pickRandom()
+ *
* @param $weights array
*
- * @return int
+ * @return bool|int|string
*/
function pickRandom( $weights ) {
- if ( !is_array( $weights ) || count( $weights ) == 0 ) {
- return false;
- }
-
- $sum = array_sum( $weights );
- if ( $sum == 0 ) {
- # No loads on any of them
- # In previous versions, this triggered an unweighted random selection,
- # but this feature has been removed as of April 2006 to allow for strict
- # separation of query groups.
- return false;
- }
- $max = mt_getrandmax();
- $rand = mt_rand( 0, $max ) / $max * $sum;
-
- $sum = 0;
- foreach ( $weights as $i => $w ) {
- $sum += $w;
- if ( $sum >= $rand ) {
- break;
- }
- }
- return $i;
+ return ArrayUtils::pickRandom( $weights );
}
/**
@@ -197,17 +178,18 @@ class LoadBalancer {
* Side effect: opens connections to databases
* @param $group bool
* @param $wiki bool
+ * @throws MWException
* @return bool|int|string
*/
function getReaderIndex( $group = false, $wiki = false ) {
global $wgReadOnly, $wgDBClusterTimeout, $wgDBAvgStatusPoll, $wgDBtype;
# @todo FIXME: For now, only go through all this for mysql databases
- if ($wgDBtype != 'mysql') {
+ if ( $wgDBtype != 'mysql' ) {
return $this->getWriterIndex();
}
- if ( count( $this->mServers ) == 1 ) {
+ if ( count( $this->mServers ) == 1 ) {
# Skip the load balancing if there's only one server
return 0;
} elseif ( $group === false and $this->mReadIndex >= 0 ) {
@@ -228,7 +210,7 @@ class LoadBalancer {
$nonErrorLoads = $this->mGroupLoads[$group];
} else {
# No loads for this group, return false and the caller can use some other group
- wfDebug( __METHOD__.": no loads for group $group\n" );
+ wfDebug( __METHOD__ . ": no loads for group $group\n" );
wfProfileOut( __METHOD__ );
return false;
}
@@ -256,7 +238,7 @@ class LoadBalancer {
$i = $this->pickRandom( $currentLoads );
} else {
$i = $this->getRandomNonLagged( $currentLoads, $wiki );
- if ( $i === false && count( $currentLoads ) != 0 ) {
+ if ( $i === false && count( $currentLoads ) != 0 ) {
# All slaves lagged. Switch to read-only mode
wfDebugLog( 'replication', "All slaves lagged. Switch to read-only mode\n" );
$wgReadOnly = 'The database has been automatically locked ' .
@@ -270,16 +252,16 @@ class LoadBalancer {
# pickRandom() returned false
# This is permanent and means the configuration or the load monitor
# wants us to return false.
- wfDebugLog( 'connect', __METHOD__.": pickRandom() returned false\n" );
+ wfDebugLog( 'connect', __METHOD__ . ": pickRandom() returned false\n" );
wfProfileOut( __METHOD__ );
return false;
}
- wfDebugLog( 'connect', __METHOD__.": Using reader #$i: {$this->mServers[$i]['host']}...\n" );
+ wfDebugLog( 'connect', __METHOD__ . ": Using reader #$i: {$this->mServers[$i]['host']}...\n" );
$conn = $this->openConnection( $i, $wiki );
if ( !$conn ) {
- wfDebugLog( 'connect', __METHOD__.": Failed connecting to $i/$wiki\n" );
+ wfDebugLog( 'connect', __METHOD__ . ": Failed connecting to $i/$wiki\n" );
unset( $nonErrorLoads[$i] );
unset( $currentLoads[$i] );
continue;
@@ -318,7 +300,7 @@ class LoadBalancer {
# Some servers must have been overloaded
if ( $overloadedServers == 0 ) {
- throw new MWException( __METHOD__.": unexpectedly found no overloaded servers" );
+ throw new MWException( __METHOD__ . ": unexpectedly found no overloaded servers" );
}
# Back off for a while
# Scale the sleep time by the number of connected threads, to produce a
@@ -341,7 +323,7 @@ class LoadBalancer {
$this->mServers[$i]['slave pos'] = $conn->getSlavePos();
}
}
- if ( $this->mReadIndex <=0 && $this->mLoads[$i]>0 && $i !== false ) {
+ if ( $this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $i !== false ) {
$this->mReadIndex = $i;
}
}
@@ -356,7 +338,7 @@ class LoadBalancer {
*/
function sleep( $t ) {
wfProfileIn( __METHOD__ );
- wfDebug( __METHOD__.": waiting $t us\n" );
+ wfDebug( __METHOD__ . ": waiting $t us\n" );
usleep( $t );
wfProfileOut( __METHOD__ );
return $t;
@@ -390,7 +372,7 @@ class LoadBalancer {
wfProfileIn( __METHOD__ );
$this->mWaitForPos = $pos;
for ( $i = 1; $i < count( $this->mServers ); $i++ ) {
- $this->doWait( $i , true );
+ $this->doWait( $i, true );
}
wfProfileOut( __METHOD__ );
}
@@ -433,15 +415,15 @@ class LoadBalancer {
}
}
- wfDebug( __METHOD__.": Waiting for slave #$index to catch up...\n" );
+ wfDebug( __METHOD__ . ": Waiting for slave #$index to catch up...\n" );
$result = $conn->masterPosWait( $this->mWaitForPos, $this->mWaitTimeout );
if ( $result == -1 || is_null( $result ) ) {
# Timed out waiting for slave, use master instead
- wfDebug( __METHOD__.": Timed out waiting for slave #$index pos {$this->mWaitForPos}\n" );
+ wfDebug( __METHOD__ . ": Timed out waiting for slave #$index pos {$this->mWaitForPos}\n" );
return false;
} else {
- wfDebug( __METHOD__.": Done\n" );
+ wfDebug( __METHOD__ . ": Done\n" );
return true;
}
}
@@ -451,9 +433,10 @@ class LoadBalancer {
* This is the main entry point for this class.
*
* @param $i Integer: server index
- * @param $groups Array: query groups
- * @param $wiki String: wiki ID
+ * @param array $groups query groups
+ * @param bool|string $wiki Wiki ID
*
+ * @throws MWException
* @return DatabaseBase
*/
public function &getConnection( $i, $groups = array(), $wiki = false ) {
@@ -476,7 +459,7 @@ class LoadBalancer {
$groupIndex = $this->getReaderIndex( $groups, $wiki );
if ( $groupIndex !== false ) {
$serverName = $this->getServerName( $groupIndex );
- wfDebug( __METHOD__.": using server $serverName for group $groups\n" );
+ wfDebug( __METHOD__ . ": using server $serverName for group $groups\n" );
$i = $groupIndex;
}
} else {
@@ -484,7 +467,7 @@ class LoadBalancer {
$groupIndex = $this->getReaderIndex( $group, $wiki );
if ( $groupIndex !== false ) {
$serverName = $this->getServerName( $groupIndex );
- wfDebug( __METHOD__.": using server $serverName for group $group\n" );
+ wfDebug( __METHOD__ . ": using server $serverName for group $group\n" );
$i = $groupIndex;
break;
}
@@ -497,16 +480,16 @@ class LoadBalancer {
# Couldn't find a working server in getReaderIndex()?
if ( $i === false ) {
$this->mLastError = 'No working slave server: ' . $this->mLastError;
- $this->reportConnectionError( $this->mErrorConnection );
wfProfileOut( __METHOD__ );
- return false;
+ return $this->reportConnectionError();
}
}
# Now we have an explicit index into the servers array
$conn = $this->openConnection( $i, $wiki );
if ( !$conn ) {
- $this->reportConnectionError( $this->mErrorConnection );
+ wfProfileOut( __METHOD__ );
+ return $this->reportConnectionError();
}
wfProfileOut( __METHOD__ );
@@ -519,10 +502,11 @@ class LoadBalancer {
* the same number of times as getConnection() to work.
*
* @param DatabaseBase $conn
+ * @throws MWException
*/
public function reuseConnection( $conn ) {
- $serverIndex = $conn->getLBInfo('serverIndex');
- $refCount = $conn->getLBInfo('foreignPoolRefCount');
+ $serverIndex = $conn->getLBInfo( 'serverIndex' );
+ $refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
$dbName = $conn->getDBname();
$prefix = $conn->tablePrefix();
if ( strval( $prefix ) !== '' ) {
@@ -531,7 +515,7 @@ class LoadBalancer {
$wiki = $dbName;
}
if ( $serverIndex === null || $refCount === null ) {
- wfDebug( __METHOD__.": this connection was not opened as a foreign connection\n" );
+ wfDebug( __METHOD__ . ": this connection was not opened as a foreign connection\n" );
/**
* This can happen in code like:
* foreach ( $dbs as $db ) {
@@ -545,15 +529,15 @@ class LoadBalancer {
return;
}
if ( $this->mConns['foreignUsed'][$serverIndex][$wiki] !== $conn ) {
- throw new MWException( __METHOD__.": connection not found, has the connection been freed already?" );
+ throw new MWException( __METHOD__ . ": connection not found, has the connection been freed already?" );
}
$conn->setLBInfo( 'foreignPoolRefCount', --$refCount );
if ( $refCount <= 0 ) {
$this->mConns['foreignFree'][$serverIndex][$wiki] = $conn;
unset( $this->mConns['foreignUsed'][$serverIndex][$wiki] );
- wfDebug( __METHOD__.": freed connection $serverIndex/$wiki\n" );
+ wfDebug( __METHOD__ . ": freed connection $serverIndex/$wiki\n" );
} else {
- wfDebug( __METHOD__.": reference count for $serverIndex/$wiki reduced to $refCount\n" );
+ wfDebug( __METHOD__ . ": reference count for $serverIndex/$wiki reduced to $refCount\n" );
}
}
@@ -566,7 +550,7 @@ class LoadBalancer {
* error will be available via $this->mErrorConnection.
*
* @param $i Integer server index
- * @param $wiki String wiki ID to open
+ * @param string $wiki wiki ID to open
* @return DatabaseBase
*
* @access private
@@ -575,7 +559,7 @@ class LoadBalancer {
wfProfileIn( __METHOD__ );
if ( $wiki !== false ) {
$conn = $this->openForeignConnection( $i, $wiki );
- wfProfileOut( __METHOD__);
+ wfProfileOut( __METHOD__ );
return $conn;
}
if ( isset( $this->mConns['local'][$i][0] ) ) {
@@ -585,6 +569,7 @@ class LoadBalancer {
$server['serverIndex'] = $i;
$conn = $this->reallyOpenConnection( $server, false );
if ( $conn->isOpen() ) {
+ wfDebug( "Connected to database $i at {$this->mServers[$i]['host']}\n" );
$this->mConns['local'][$i][0] = $conn;
} else {
wfDebug( "Failed to connect to database $i at {$this->mServers[$i]['host']}\n" );
@@ -611,22 +596,22 @@ class LoadBalancer {
* error will be available via $this->mErrorConnection.
*
* @param $i Integer: server index
- * @param $wiki String: wiki ID to open
+ * @param string $wiki wiki ID to open
* @return DatabaseBase
*/
function openForeignConnection( $i, $wiki ) {
- wfProfileIn(__METHOD__);
+ wfProfileIn( __METHOD__ );
list( $dbName, $prefix ) = wfSplitWikiID( $wiki );
if ( isset( $this->mConns['foreignUsed'][$i][$wiki] ) ) {
// Reuse an already-used connection
$conn = $this->mConns['foreignUsed'][$i][$wiki];
- wfDebug( __METHOD__.": reusing connection $i/$wiki\n" );
+ wfDebug( __METHOD__ . ": reusing connection $i/$wiki\n" );
} elseif ( isset( $this->mConns['foreignFree'][$i][$wiki] ) ) {
// Reuse a free connection for the same wiki
$conn = $this->mConns['foreignFree'][$i][$wiki];
unset( $this->mConns['foreignFree'][$i][$wiki] );
$this->mConns['foreignUsed'][$i][$wiki] = $conn;
- wfDebug( __METHOD__.": reusing free connection $i/$wiki\n" );
+ wfDebug( __METHOD__ . ": reusing free connection $i/$wiki\n" );
} elseif ( !empty( $this->mConns['foreignFree'][$i] ) ) {
// Reuse a connection from another wiki
$conn = reset( $this->mConns['foreignFree'][$i] );
@@ -641,7 +626,7 @@ class LoadBalancer {
$conn->tablePrefix( $prefix );
unset( $this->mConns['foreignFree'][$i][$oldWiki] );
$this->mConns['foreignUsed'][$i][$wiki] = $conn;
- wfDebug( __METHOD__.": reusing free connection from $oldWiki for $wiki\n" );
+ wfDebug( __METHOD__ . ": reusing free connection from $oldWiki for $wiki\n" );
}
} else {
// Open a new connection
@@ -650,13 +635,13 @@ class LoadBalancer {
$server['foreignPoolRefCount'] = 0;
$conn = $this->reallyOpenConnection( $server, $dbName );
if ( !$conn->isOpen() ) {
- wfDebug( __METHOD__.": error opening connection for $i/$wiki\n" );
+ wfDebug( __METHOD__ . ": error opening connection for $i/$wiki\n" );
$this->mErrorConnection = $conn;
$conn = false;
} else {
$conn->tablePrefix( $prefix );
$this->mConns['foreignUsed'][$i][$wiki] = $conn;
- wfDebug( __METHOD__.": opened new connection for $i/$wiki\n" );
+ wfDebug( __METHOD__ . ": opened new connection for $i/$wiki\n" );
}
}
@@ -665,7 +650,7 @@ class LoadBalancer {
$refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
$conn->setLBInfo( 'foreignPoolRefCount', $refCount + 1 );
}
- wfProfileOut(__METHOD__);
+ wfProfileOut( __METHOD__ );
return $conn;
}
@@ -690,6 +675,7 @@ class LoadBalancer {
*
* @param $server
* @param $dbNameOverride bool
+ * @throws MWException
* @return DatabaseBase
*/
function reallyOpenConnection( $server, $dbNameOverride = false ) {
@@ -698,15 +684,11 @@ class LoadBalancer {
'See DefaultSettings.php entry for $wgDBservers.' );
}
- $host = $server['host'];
- $dbname = $server['dbname'];
-
if ( $dbNameOverride !== false ) {
- $server['dbname'] = $dbname = $dbNameOverride;
+ $server['dbname'] = $dbNameOverride;
}
# Create object
- wfDebug( "Connecting to $host $dbname...\n" );
try {
$db = DatabaseBase::factory( $server['type'], $server );
} catch ( DBConnectionError $e ) {
@@ -715,11 +697,6 @@ class LoadBalancer {
$db = $e->db;
}
- if ( $db->isOpen() ) {
- wfDebug( "Connected to $host $dbname.\n" );
- } else {
- wfDebug( "Connection failed to $host $dbname.\n" );
- }
$db->setLBInfo( $server );
if ( isset( $server['fakeSlaveLag'] ) ) {
$db->setFakeSlaveLag( $server['fakeSlaveLag'] );
@@ -731,24 +708,24 @@ class LoadBalancer {
}
/**
- * @param $conn
* @throws DBConnectionError
+ * @return bool
*/
- function reportConnectionError( &$conn ) {
- wfProfileIn( __METHOD__ );
+ private function reportConnectionError() {
+ $conn = $this->mErrorConnection; // The connection which caused the error
if ( !is_object( $conn ) ) {
// No last connection, probably due to all servers being too busy
wfLogDBError( "LB failure with no last connection. Connection error: {$this->mLastError}\n" );
- $conn = new Database;
+
// If all servers were busy, mLastError will contain something sensible
- throw new DBConnectionError( $conn, $this->mLastError );
+ throw new DBConnectionError( null, $this->mLastError );
} else {
$server = $conn->getProperty( 'mServer' );
wfLogDBError( "Connection error: {$this->mLastError} ({$server})\n" );
- $conn->reportConnectionError( "{$this->mLastError} ({$server})" );
+ $conn->reportConnectionError( "{$this->mLastError} ({$server})" ); // throws DBConnectionError
}
- wfProfileOut( __METHOD__ );
+ return false; /* not reached */
}
/**
@@ -909,7 +886,9 @@ class LoadBalancer {
foreach ( $this->mConns as $conns2 ) {
foreach ( $conns2 as $conns3 ) {
foreach ( $conns3 as $conn ) {
- $conn->commit( __METHOD__ );
+ if ( $conn->trxLevel() ) {
+ $conn->commit( __METHOD__, 'flush' );
+ }
}
}
}
@@ -926,8 +905,8 @@ class LoadBalancer {
continue;
}
foreach ( $conns2[$masterIndex] as $conn ) {
- if ( $conn->writesOrCallbacksPending() ) {
- $conn->commit( __METHOD__ );
+ if ( $conn->trxLevel() && $conn->writesOrCallbacksPending() ) {
+ $conn->commit( __METHOD__, 'flush' );
}
}
}
@@ -954,10 +933,11 @@ class LoadBalancer {
* @return bool
*/
function allowLagged( $mode = null ) {
- if ( $mode === null) {
+ if ( $mode === null ) {
return $this->mAllowLagged;
}
$this->mAllowLagged = $mode;
+ return $this->mAllowLagged;
}
/**
@@ -999,7 +979,7 @@ class LoadBalancer {
* May attempt to open connections to slaves on the default DB. If there is
* no lag, the maximum lag will be reported as -1.
*
- * @param $wiki string Wiki ID, or false for the default database
+ * @param string $wiki Wiki ID, or false for the default database
*
* @return array ( host, max lag, index of max lagged host )
*/
diff --git a/includes/db/LoadMonitor.php b/includes/db/LoadMonitor.php
index 146ac61e..ad7b3b2f 100644
--- a/includes/db/LoadMonitor.php
+++ b/includes/db/LoadMonitor.php
@@ -37,7 +37,7 @@ interface LoadMonitor {
/**
* Perform pre-connection load ratio adjustment.
* @param $loads array
- * @param $group String: the selected query group
+ * @param string $group the selected query group
* @param $wiki String
*/
function scaleLoads( &$loads, $group = false, $wiki = false );
@@ -159,7 +159,7 @@ class LoadMonitor_MySQL implements LoadMonitor {
$times = array();
foreach ( $serverIndexes as $i ) {
- if ($i == 0) { # Master
+ if ( $i == 0 ) { # Master
$times[$i] = 0;
} elseif ( false !== ( $conn = $this->parent->getAnyOpenConnection( $i ) ) ) {
$times[$i] = $conn->getLag();
@@ -173,7 +173,7 @@ class LoadMonitor_MySQL implements LoadMonitor {
$wgMemc->set( $memcKey, $times, $expiry );
# But don't give the timestamp to the caller
- unset($times['timestamp']);
+ unset( $times['timestamp'] );
$lagTimes = $times;
wfProfileOut( __METHOD__ );
@@ -189,7 +189,7 @@ class LoadMonitor_MySQL implements LoadMonitor {
if ( !$threshold ) {
return 0;
}
- $status = $conn->getMysqlStatus("Thread%");
+ $status = $conn->getMysqlStatus( "Thread%" );
if ( $status['Threads_running'] > $threshold ) {
$server = $conn->getProperty( 'mServer' );
wfLogDBError( "LB backoff from $server - Threads_running = {$status['Threads_running']}\n" );
@@ -199,4 +199,3 @@ class LoadMonitor_MySQL implements LoadMonitor {
}
}
}
-
diff --git a/includes/db/ORMIterator.php b/includes/db/ORMIterator.php
index 090b8932..077eab0f 100644
--- a/includes/db/ORMIterator.php
+++ b/includes/db/ORMIterator.php
@@ -23,9 +23,9 @@
* @file
* @ingroup ORM
*
- * @licence GNU GPL v2 or later
+ * @license GNU GPL v2 or later
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
interface ORMIterator extends Iterator {
-} \ No newline at end of file
+}
diff --git a/includes/db/ORMResult.php b/includes/db/ORMResult.php
index 2a5837a1..160033c4 100644
--- a/includes/db/ORMResult.php
+++ b/includes/db/ORMResult.php
@@ -25,7 +25,7 @@
* @file ORMResult.php
* @ingroup ORM
*
- * @licence GNU GPL v2 or later
+ * @license GNU GPL v2 or later
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
diff --git a/includes/db/ORMRow.php b/includes/db/ORMRow.php
index 303f3a20..6c1f27ff 100644
--- a/includes/db/ORMRow.php
+++ b/includes/db/ORMRow.php
@@ -27,11 +27,11 @@
* @file ORMRow.php
* @ingroup ORM
*
- * @licence GNU GPL v2 or later
+ * @license GNU GPL v2 or later
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
-abstract class ORMRow implements IORMRow {
+class ORMRow implements IORMRow {
/**
* The fields of the object.
@@ -120,7 +120,8 @@ abstract class ORMRow implements IORMRow {
$result = $this->table->rawSelectRow(
$this->table->getPrefixedFields( $fields ),
array( $this->table->getPrefixedField( 'id' ) => $this->getId() ),
- array( 'LIMIT' => 1 )
+ array( 'LIMIT' => 1 ),
+ __METHOD__
);
if ( $result !== false ) {
@@ -138,8 +139,9 @@ abstract class ORMRow implements IORMRow {
*
* @since 1.20
*
- * @param string $name
- * @param mixed $default
+ * @param string $name Field name
+ * @param $default mixed: Default value to return when none is found
+ * (default: null)
*
* @throws MWException
* @return mixed
@@ -159,7 +161,7 @@ abstract class ORMRow implements IORMRow {
*
* @since 1.20
*
- * @param string$name
+ * @param $name string
*
* @return mixed
*/
@@ -259,11 +261,18 @@ abstract class ORMRow implements IORMRow {
if ( array_key_exists( $name, $this->fields ) ) {
$value = $this->fields[$name];
+ // Skip null id fields so that the DBMS can set the default.
+ if ( $name === 'id' && is_null ( $value ) ) {
+ continue;
+ }
+
switch ( $type ) {
case 'array':
$value = (array)$value;
+ // fall-through!
case 'blob':
$value = serialize( $value );
+ // fall-through!
}
$values[$this->table->getPrefixedField( $name )] = $value;
@@ -346,7 +355,7 @@ abstract class ORMRow implements IORMRow {
* @return boolean Success indicator
*/
protected function saveExisting( $functionName = null ) {
- $dbw = wfGetDB( DB_MASTER );
+ $dbw = $this->table->getWriteDbConnection();
$success = $dbw->update(
$this->table->getName(),
@@ -355,6 +364,8 @@ abstract class ORMRow implements IORMRow {
is_null( $functionName ) ? __METHOD__ : $functionName
);
+ $this->table->releaseConnection( $dbw );
+
// DatabaseBase::update does not always return true for success as documented...
return $success !== false;
}
@@ -382,13 +393,13 @@ abstract class ORMRow implements IORMRow {
* @return boolean Success indicator
*/
protected function insert( $functionName = null, array $options = null ) {
- $dbw = wfGetDB( DB_MASTER );
+ $dbw = $this->table->getWriteDbConnection();
$success = $dbw->insert(
$this->table->getName(),
$this->getWriteValues(),
is_null( $functionName ) ? __METHOD__ : $functionName,
- is_null( $options ) ? array( 'IGNORE' ) : $options
+ $options
);
// DatabaseBase::insert does not always return true for success as documented...
@@ -398,6 +409,8 @@ abstract class ORMRow implements IORMRow {
$this->setField( 'id', $dbw->insertId() );
}
+ $this->table->releaseConnection( $dbw );
+
return $success;
}
@@ -411,7 +424,7 @@ abstract class ORMRow implements IORMRow {
public function remove() {
$this->beforeRemove();
- $success = $this->table->delete( array( 'id' => $this->getId() ) );
+ $success = $this->table->delete( array( 'id' => $this->getId() ), __METHOD__ );
// DatabaseBase::delete does not always return true for success as documented...
$success = $success !== false;
@@ -446,8 +459,8 @@ abstract class ORMRow implements IORMRow {
}
/**
- * Gets called after successfull removal.
- * Can be overriden to get rid of linked data.
+ * Gets called after successful removal.
+ * Can be overridden to get rid of linked data.
*
* @since 1.20
*/
@@ -501,11 +514,7 @@ abstract class ORMRow implements IORMRow {
$value = (float)$value;
break;
case 'bool':
- if ( is_string( $value ) ) {
- $value = $value !== '0';
- } elseif ( is_int( $value ) ) {
- $value = $value !== 0;
- }
+ $value = (bool)$value;
break;
case 'array':
if ( is_string( $value ) ) {
@@ -557,7 +566,7 @@ abstract class ORMRow implements IORMRow {
$absoluteAmount = abs( $amount );
$isNegative = $amount < 0;
- $dbw = wfGetDB( DB_MASTER );
+ $dbw = $this->table->getWriteDbConnection();
$fullField = $this->table->getPrefixedField( $field );
@@ -572,6 +581,8 @@ abstract class ORMRow implements IORMRow {
$this->setField( $field, $this->getField( $field ) + $amount );
}
+ $this->table->releaseConnection( $dbw );
+
return $success;
}
diff --git a/includes/db/ORMTable.php b/includes/db/ORMTable.php
index a77074ff..bcbe94a3 100644
--- a/includes/db/ORMTable.php
+++ b/includes/db/ORMTable.php
@@ -19,43 +19,150 @@
* http://www.gnu.org/copyleft/gpl.html
*
* @since 1.20
+ * Non-abstract since 1.21
*
* @file ORMTable.php
* @ingroup ORM
*
- * @licence GNU GPL v2 or later
+ * @license GNU GPL v2 or later
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
-abstract class ORMTable implements IORMTable {
+class ORMTable extends DBAccessBase implements IORMTable {
/**
- * Gets the db field prefix.
+ * Cache for instances, used by the singleton method.
*
* @since 1.20
+ * @deprecated since 1.21
*
- * @return string
+ * @var ORMTable[]
*/
- protected abstract function getFieldPrefix();
+ protected static $instanceCache = array();
/**
- * Cache for instances, used by the singleton method.
+ * @since 1.21
*
- * @since 1.20
- * @var array of DBTable
+ * @var string
*/
- protected static $instanceCache = array();
+ protected $tableName;
+
+ /**
+ * @since 1.21
+ *
+ * @var string[]
+ */
+ protected $fields = array();
+
+ /**
+ * @since 1.21
+ *
+ * @var string
+ */
+ protected $fieldPrefix = '';
+
+ /**
+ * @since 1.21
+ *
+ * @var string
+ */
+ protected $rowClass = 'ORMRow';
/**
- * The database connection to use for read operations.
+ * @since 1.21
+ *
+ * @var array
+ */
+ protected $defaults = array();
+
+ /**
+ * ID of the database connection to use for read operations.
* Can be changed via @see setReadDb.
*
* @since 1.20
+ *
* @var integer DB_ enum
*/
protected $readDb = DB_SLAVE;
/**
+ * Constructor.
+ *
+ * @since 1.21
+ *
+ * @param string $tableName
+ * @param string[] $fields
+ * @param array $defaults
+ * @param string|null $rowClass
+ * @param string $fieldPrefix
+ */
+ public function __construct( $tableName = '', array $fields = array(), array $defaults = array(), $rowClass = null, $fieldPrefix = '' ) {
+ $this->tableName = $tableName;
+ $this->fields = $fields;
+ $this->defaults = $defaults;
+
+ if ( is_string( $rowClass ) ) {
+ $this->rowClass = $rowClass;
+ }
+
+ $this->fieldPrefix = $fieldPrefix;
+ }
+
+ /**
+ * @see IORMTable::getName
+ *
+ * @since 1.21
+ *
+ * @return string
+ * @throws MWException
+ */
+ public function getName() {
+ if ( $this->tableName === '' ) {
+ throw new MWException( 'The table name needs to be set' );
+ }
+
+ return $this->tableName;
+ }
+
+ /**
+ * Gets the db field prefix.
+ *
+ * @since 1.20
+ *
+ * @return string
+ */
+ protected function getFieldPrefix() {
+ return $this->fieldPrefix;
+ }
+
+ /**
+ * @see IORMTable::getRowClass
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getRowClass() {
+ return $this->rowClass;
+ }
+
+ /**
+ * @see ORMTable::getFields
+ *
+ * @since 1.21
+ *
+ * @return array
+ * @throws MWException
+ */
+ public function getFields() {
+ if ( $this->fields === array() ) {
+ throw new MWException( 'The table needs to have one or more fields' );
+ }
+
+ return $this->fields;
+ }
+
+ /**
* Returns a list of default field values.
* field name => field value
*
@@ -64,7 +171,7 @@ abstract class ORMTable implements IORMTable {
* @return array
*/
public function getDefaults() {
- return array();
+ return $this->defaults;
}
/**
@@ -94,8 +201,9 @@ abstract class ORMTable implements IORMTable {
* @return ORMResult
*/
public function select( $fields = null, array $conditions = array(),
- array $options = array(), $functionName = null ) {
- return new ORMResult( $this, $this->rawSelect( $fields, $conditions, $options, $functionName ) );
+ array $options = array(), $functionName = null ) {
+ $res = $this->rawSelect( $fields, $conditions, $options, $functionName );
+ return new ORMResult( $this, $res );
}
/**
@@ -109,10 +217,11 @@ abstract class ORMTable implements IORMTable {
* @param array $options
* @param string|null $functionName
*
- * @return array of self
+ * @return array of row objects
+ * @throws DBQueryError if the query failed (even if the database was in ignoreErrors mode).
*/
public function selectObjects( $fields = null, array $conditions = array(),
- array $options = array(), $functionName = null ) {
+ array $options = array(), $functionName = null ) {
$result = $this->selectFields( $fields, $conditions, $options, false, $functionName );
$objects = array();
@@ -130,14 +239,15 @@ abstract class ORMTable implements IORMTable {
* @since 1.20
*
* @param null|string|array $fields
- * @param array $conditions
- * @param array $options
- * @param null|string $functionName
+ * @param array $conditions
+ * @param array $options
+ * @param null|string $functionName
*
* @return ResultWrapper
+ * @throws DBQueryError if the quey failed (even if the database was in ignoreErrors mode).
*/
public function rawSelect( $fields = null, array $conditions = array(),
- array $options = array(), $functionName = null ) {
+ array $options = array(), $functionName = null ) {
if ( is_null( $fields ) ) {
$fields = array_keys( $this->getFields() );
}
@@ -145,13 +255,39 @@ abstract class ORMTable implements IORMTable {
$fields = (array)$fields;
}
- return wfGetDB( $this->getReadDb() )->select(
+ $dbr = $this->getReadDbConnection();
+ $result = $dbr->select(
$this->getName(),
$this->getPrefixedFields( $fields ),
$this->getPrefixedValues( $conditions ),
is_null( $functionName ) ? __METHOD__ : $functionName,
$options
);
+
+ /* @var Exception $error */
+ $error = null;
+
+ if ( $result === false ) {
+ // Database connection was in "ignoreErrors" mode. We don't like that.
+ // So, we emulate the DBQueryError that should have been thrown.
+ $error = new DBQueryError(
+ $dbr,
+ $dbr->lastError(),
+ $dbr->lastErrno(),
+ $dbr->lastQuery(),
+ is_null( $functionName ) ? __METHOD__ : $functionName
+ );
+ }
+
+ $this->releaseConnection( $dbr );
+
+ if ( $error ) {
+ // Note: construct the error before releasing the connection,
+ // but throw it after.
+ throw $error;
+ }
+
+ return $result;
}
/**
@@ -177,7 +313,7 @@ abstract class ORMTable implements IORMTable {
* @return array of array
*/
public function selectFields( $fields = null, array $conditions = array(),
- array $options = array(), $collapse = true, $functionName = null ) {
+ array $options = array(), $collapse = true, $functionName = null ) {
$objects = array();
$result = $this->rawSelect( $fields, $conditions, $options, $functionName );
@@ -223,7 +359,7 @@ abstract class ORMTable implements IORMTable {
$objects = $this->select( $fields, $conditions, $options, $functionName );
- return $objects->isEmpty() ? false : $objects->current();
+ return ( !$objects || $objects->isEmpty() ) ? false : $objects->current();
}
/**
@@ -241,15 +377,18 @@ abstract class ORMTable implements IORMTable {
*/
public function rawSelectRow( array $fields, array $conditions = array(),
array $options = array(), $functionName = null ) {
- $dbr = wfGetDB( $this->getReadDb() );
+ $dbr = $this->getReadDbConnection();
- return $dbr->selectRow(
+ $result = $dbr->selectRow(
$this->getName(),
$fields,
$conditions,
is_null( $functionName ) ? __METHOD__ : $functionName,
$options
);
+
+ $this->releaseConnection( $dbr );
+ return $result;
}
/**
@@ -293,6 +432,21 @@ abstract class ORMTable implements IORMTable {
}
/**
+ * Checks if the table exists
+ *
+ * @since 1.21
+ *
+ * @return boolean
+ */
+ public function exists() {
+ $dbr = $this->getReadDbConnection();
+ $exists = $dbr->tableExists( $this->getName() );
+ $this->releaseConnection( $dbr );
+
+ return $exists;
+ }
+
+ /**
* Returns the amount of matching records.
* Condition field names get prefixed.
*
@@ -310,7 +464,8 @@ abstract class ORMTable implements IORMTable {
$res = $this->rawSelectRow(
array( 'rowcount' => 'COUNT(*)' ),
$this->getPrefixedValues( $conditions ),
- $options
+ $options,
+ __METHOD__
);
return $res->rowcount;
@@ -327,13 +482,18 @@ abstract class ORMTable implements IORMTable {
* @return boolean Success indicator
*/
public function delete( array $conditions, $functionName = null ) {
- return wfGetDB( DB_MASTER )->delete(
+ $dbw = $this->getWriteDbConnection();
+
+ $result = $dbw->delete(
$this->getName(),
$conditions === array() ? '*' : $this->getPrefixedValues( $conditions ),
- $functionName
+ is_null( $functionName ) ? __METHOD__ : $functionName
) !== false; // DatabaseBase::delete does not always return true for success as documented...
+
+ $this->releaseConnection( $dbw );
+ return $result;
}
-
+
/**
* Get API parameters for the fields supported by this object.
*
@@ -397,7 +557,7 @@ abstract class ORMTable implements IORMTable {
}
/**
- * Get the database type used for read operations.
+ * Get the database ID used for read operations.
*
* @since 1.20
*
@@ -408,7 +568,7 @@ abstract class ORMTable implements IORMTable {
}
/**
- * Set the database type to use for read operations.
+ * Set the database ID to use for read operations, use DB_XXX constants or an index to the load balancer setup.
*
* @param integer $db
*
@@ -419,6 +579,70 @@ abstract class ORMTable implements IORMTable {
}
/**
+ * Get the ID of the any foreign wiki to use as a target for database operations
+ *
+ * @since 1.20
+ *
+ * @return String|bool The target wiki, in a form that LBFactory understands (or false if the local wiki is used)
+ */
+ public function getTargetWiki() {
+ return $this->wiki;
+ }
+
+ /**
+ * Set the ID of the any foreign wiki to use as a target for database operations
+ *
+ * @param string|bool $wiki The target wiki, in a form that LBFactory understands (or false if the local wiki shall be used)
+ *
+ * @since 1.20
+ */
+ public function setTargetWiki( $wiki ) {
+ $this->wiki = $wiki;
+ }
+
+ /**
+ * Get the database type used for read operations.
+ * This is to be used instead of wfGetDB.
+ *
+ * @see LoadBalancer::getConnection
+ *
+ * @since 1.20
+ *
+ * @return DatabaseBase The database object
+ */
+ public function getReadDbConnection() {
+ return $this->getConnection( $this->getReadDb(), array() );
+ }
+
+ /**
+ * Get the database type used for read operations.
+ * This is to be used instead of wfGetDB.
+ *
+ * @see LoadBalancer::getConnection
+ *
+ * @since 1.20
+ *
+ * @return DatabaseBase The database object
+ */
+ public function getWriteDbConnection() {
+ return $this->getConnection( DB_MASTER, array() );
+ }
+
+ /**
+ * Releases the lease on the given database connection. This is useful mainly
+ * for connections to a foreign wiki. It does nothing for connections to the local wiki.
+ *
+ * @see LoadBalancer::reuseConnection
+ *
+ * @param DatabaseBase $db the database
+ *
+ * @since 1.20
+ */
+ public function releaseConnection( DatabaseBase $db ) {
+ parent::releaseConnection( $db ); // just make it public
+ }
+
+ /**
* Update the records matching the provided conditions by
* setting the fields that are keys in the $values param to
* their corresponding values.
@@ -431,14 +655,17 @@ abstract class ORMTable implements IORMTable {
* @return boolean Success indicator
*/
public function update( array $values, array $conditions = array() ) {
- $dbw = wfGetDB( DB_MASTER );
+ $dbw = $this->getWriteDbConnection();
- return $dbw->update(
+ $result = $dbw->update(
$this->getName(),
$this->getPrefixedValues( $values ),
$this->getPrefixedValues( $conditions ),
__METHOD__
) !== false; // DatabaseBase::update does not always return true for success as documented...
+
+ $this->releaseConnection( $dbw );
+ return $result;
}
/**
@@ -450,6 +677,7 @@ abstract class ORMTable implements IORMTable {
* @param array $conditions
*/
public function updateSummaryFields( $summaryFields = null, array $conditions = array() ) {
+ $slave = $this->getReadDb();
$this->setReadDb( DB_MASTER );
/**
@@ -461,7 +689,7 @@ abstract class ORMTable implements IORMTable {
$item->save();
}
- $this->setReadDb( DB_SLAVE );
+ $this->setReadDb( $slave );
}
/**
@@ -559,6 +787,7 @@ abstract class ORMTable implements IORMTable {
* Get an instance of this class.
*
* @since 1.20
+ * @deprecated since 1.21
*
* @return IORMTable
*/
diff --git a/includes/debug/Debug.php b/includes/debug/Debug.php
index d02bcf53..8c39e1a1 100644
--- a/includes/debug/Debug.php
+++ b/includes/debug/Debug.php
@@ -135,6 +135,7 @@ class MWDebug {
* @since 1.19
* @param $msg string
* @param $callerOffset int
+ * @param int $level A PHP error level. See sendWarning()
* @return mixed
*/
public static function warning( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
@@ -161,9 +162,9 @@ class MWDebug {
* - MediaWiki's debug log, if $wgDevelopmentWarnings is set to false.
*
* @since 1.19
- * @param $function string: Function that is deprecated.
- * @param $version string|bool: Version in which the function was deprecated.
- * @param $component string|bool: Component to which the function belongs.
+ * @param string $function Function that is deprecated.
+ * @param string|bool $version Version in which the function was deprecated.
+ * @param string|bool $component Component to which the function belongs.
* If false, it is assumbed the function is in MediaWiki core.
* @param $callerOffset integer: How far up the callstack is the original
* caller. 2 = function that called the function that called
@@ -269,8 +270,8 @@ class MWDebug {
* Send a warning either to the debug log or by triggering an user PHP
* error depending on $wgDevelopmentWarnings.
*
- * @param $msg string Message to send
- * @param $caller array caller description get from getCallerDescription()
+ * @param string $msg Message to send
+ * @param array $caller caller description get from getCallerDescription()
* @param $level error level to use if $wgDevelopmentWarnings is true
*/
private static function sendWarning( $msg, $caller, $level ) {
diff --git a/includes/diff/DairikiDiff.php b/includes/diff/DairikiDiff.php
index 72eb5d3c..94ffc066 100644
--- a/includes/diff/DairikiDiff.php
+++ b/includes/diff/DairikiDiff.php
@@ -5,6 +5,21 @@
* Copyright © 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org>
* You may copy this code freely under the conditions of the GPL.
*
+ * 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 DifferenceEngine
* @defgroup DifferenceEngine DifferenceEngine
@@ -28,14 +43,14 @@ class _DiffOp {
* @return int
*/
function norig() {
- return $this->orig ? sizeof( $this->orig ) : 0;
+ return $this->orig ? count( $this->orig ) : 0;
}
/**
* @return int
*/
function nclosing() {
- return $this->closing ? sizeof( $this->closing ) : 0;
+ return $this->closing ? count( $this->closing ) : 0;
}
}
@@ -152,7 +167,7 @@ class _DiffOp_Change extends _DiffOp {
*/
class _DiffEngine {
- const MAX_XREF_LENGTH = 10000;
+ const MAX_XREF_LENGTH = 10000;
protected $xchanged, $ychanged;
@@ -179,8 +194,8 @@ class _DiffEngine {
$this->_shift_boundaries( $to_lines, $this->ychanged, $this->xchanged );
// Compute the edit operations.
- $n_from = sizeof( $from_lines );
- $n_to = sizeof( $to_lines );
+ $n_from = count( $from_lines );
+ $n_to = count( $to_lines );
$edits = array();
$xi = $yi = 0;
@@ -206,7 +221,7 @@ class _DiffEngine {
}
$add = array();
- while ( $yi < $n_to && $this->ychanged[$yi] ) {
+ while ( $yi < $n_to && $this->ychanged[$yi] ) {
$add[] = $to_lines[$yi++];
}
@@ -239,8 +254,8 @@ class _DiffEngine {
unset( $wikidiff3 );
} else {
// old diff
- $n_from = sizeof( $from_lines );
- $n_to = sizeof( $to_lines );
+ $n_from = count( $from_lines );
+ $n_to = count( $to_lines );
$this->xchanged = $this->ychanged = array();
$this->xv = $this->yv = array();
$this->xind = $this->yind = array();
@@ -288,7 +303,7 @@ class _DiffEngine {
}
// Find the LCS.
- $this->_compareseq( 0, sizeof( $this->xv ), 0, sizeof( $this->yv ) );
+ $this->_compareseq( 0, count( $this->xv ), 0, count( $this->yv ) );
}
wfProfileOut( __METHOD__ );
}
@@ -311,7 +326,7 @@ class _DiffEngine {
* [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally
* sized segments.
*
- * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an
+ * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an
* array of NCHUNKS+1 (X, Y) indexes giving the diving points between
* sub sequences. The first sub-sequence is contained in [X0, X1),
* [Y0, Y1), the second in [X1, X2), [Y1, Y2) and so on. Note
@@ -476,8 +491,7 @@ class _DiffEngine {
// $nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5);
// $nchunks = max(2,min(8,(int)$nchunks));
$nchunks = min( 7, $xlim - $xoff, $ylim - $yoff ) + 1;
- list ( $lcs, $seps )
- = $this->_diag( $xoff, $xlim, $yoff, $ylim, $nchunks );
+ list ( $lcs, $seps ) = $this->_diag( $xoff, $xlim, $yoff, $ylim, $nchunks );
}
if ( $lcs == 0 ) {
@@ -518,9 +532,9 @@ class _DiffEngine {
$i = 0;
$j = 0;
- assert( 'sizeof($lines) == sizeof($changed)' );
- $len = sizeof( $lines );
- $other_len = sizeof( $other_changed );
+ assert( 'count($lines) == count($changed)' );
+ $len = count( $lines );
+ $other_len = count( $other_changed );
while ( 1 ) {
/*
@@ -698,7 +712,7 @@ class Diff {
$lcs = 0;
foreach ( $this->edits as $edit ) {
if ( $edit->type == 'copy' ) {
- $lcs += sizeof( $edit->orig );
+ $lcs += count( $edit->orig );
}
}
return $lcs;
@@ -717,7 +731,7 @@ class Diff {
foreach ( $this->edits as $edit ) {
if ( $edit->orig ) {
- array_splice( $lines, sizeof( $lines ), 0, $edit->orig );
+ array_splice( $lines, count( $lines ), 0, $edit->orig );
}
}
return $lines;
@@ -736,7 +750,7 @@ class Diff {
foreach ( $this->edits as $edit ) {
if ( $edit->closing ) {
- array_splice( $lines, sizeof( $lines ), 0, $edit->closing );
+ array_splice( $lines, count( $lines ), 0, $edit->closing );
}
}
return $lines;
@@ -766,7 +780,6 @@ class Diff {
trigger_error( "Reversed closing doesn't match", E_USER_ERROR );
}
-
$prevtype = 'none';
foreach ( $this->edits as $edit ) {
if ( $prevtype == $edit->type ) {
@@ -814,23 +827,23 @@ class MappedDiff extends Diff {
$mapped_from_lines, $mapped_to_lines ) {
wfProfileIn( __METHOD__ );
- assert( 'sizeof( $from_lines ) == sizeof( $mapped_from_lines )' );
- assert( 'sizeof( $to_lines ) == sizeof( $mapped_to_lines )' );
+ assert( 'count( $from_lines ) == count( $mapped_from_lines )' );
+ assert( 'count( $to_lines ) == count( $mapped_to_lines )' );
parent::__construct( $mapped_from_lines, $mapped_to_lines );
$xi = $yi = 0;
- for ( $i = 0; $i < sizeof( $this->edits ); $i++ ) {
+ for ( $i = 0; $i < count( $this->edits ); $i++ ) {
$orig = &$this->edits[$i]->orig;
if ( is_array( $orig ) ) {
- $orig = array_slice( $from_lines, $xi, sizeof( $orig ) );
- $xi += sizeof( $orig );
+ $orig = array_slice( $from_lines, $xi, count( $orig ) );
+ $xi += count( $orig );
}
$closing = &$this->edits[$i]->closing;
if ( is_array( $closing ) ) {
- $closing = array_slice( $to_lines, $yi, sizeof( $closing ) );
- $yi += sizeof( $closing );
+ $closing = array_slice( $to_lines, $yi, count( $closing ) );
+ $yi += count( $closing );
}
}
wfProfileOut( __METHOD__ );
@@ -885,7 +898,7 @@ class DiffFormatter {
foreach ( $diff->edits as $edit ) {
if ( $edit->type == 'copy' ) {
if ( is_array( $block ) ) {
- if ( sizeof( $edit->orig ) <= $nlead + $ntrail ) {
+ if ( count( $edit->orig ) <= $nlead + $ntrail ) {
$block[] = $edit;
} else {
if ( $ntrail ) {
@@ -901,9 +914,9 @@ class DiffFormatter {
$context = $edit->orig;
} else {
if ( !is_array( $block ) ) {
- $context = array_slice( $context, sizeof( $context ) - $nlead );
- $x0 = $xi - sizeof( $context );
- $y0 = $yi - sizeof( $context );
+ $context = array_slice( $context, count( $context ) - $nlead );
+ $x0 = $xi - count( $context );
+ $y0 = $yi - count( $context );
$block = array();
if ( $context ) {
$block[] = new _DiffOp_Copy( $context );
@@ -913,10 +926,10 @@ class DiffFormatter {
}
if ( $edit->orig ) {
- $xi += sizeof( $edit->orig );
+ $xi += count( $edit->orig );
}
if ( $edit->closing ) {
- $yi += sizeof( $edit->closing );
+ $yi += count( $edit->closing );
}
}
@@ -1350,7 +1363,7 @@ class TableDiffFormatter extends DiffFormatter {
*/
function _block_header( $xbeg, $xlen, $ybeg, $ylen ) {
$r = '<tr><td colspan="2" class="diff-lineno"><!--LINE ' . $xbeg . "--></td>\n" .
- '<td colspan="2" class="diff-lineno"><!--LINE ' . $ybeg . "--></td></tr>\n";
+ '<td colspan="2" class="diff-lineno"><!--LINE ' . $ybeg . "--></td></tr>\n";
return $r;
}
diff --git a/includes/diff/DifferenceEngine.php b/includes/diff/DifferenceEngine.php
index c7156fb2..0f3c77ff 100644
--- a/includes/diff/DifferenceEngine.php
+++ b/includes/diff/DifferenceEngine.php
@@ -38,7 +38,10 @@ class DifferenceEngine extends ContextSource {
* @private
*/
var $mOldid, $mNewid;
- var $mOldtext, $mNewtext;
+ /**
+ * @var Content
+ */
+ var $mOldContent, $mNewContent;
protected $mDiffLang;
/**
@@ -77,7 +80,7 @@ class DifferenceEngine extends ContextSource {
* Constructor
* @param $context IContextSource context to use, anything else will be ignored
* @param $old Integer old ID we want to show and diff with.
- * @param $new String either 'prev' or 'next'.
+ * @param string $new either 'prev' or 'next'.
* @param $rcid Integer ??? FIXME (default 0)
* @param $refreshCache boolean If set, refreshes the diff cache
* @param $unhide boolean If set, allow viewing deleted revs
@@ -149,7 +152,7 @@ class DifferenceEngine extends ContextSource {
function deletedLink( $id ) {
if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) {
$dbr = wfGetDB( DB_SLAVE );
- $row = $dbr->selectRow('archive', '*',
+ $row = $dbr->selectRow( 'archive', '*',
array( 'ar_rev_id' => $id ),
__METHOD__ );
if ( $row ) {
@@ -224,6 +227,10 @@ class DifferenceEngine extends ContextSource {
# we'll use the application/x-external-editor interface to call
# an external diff tool like kompare, kdiff3, etc.
if ( ExternalEdit::useExternalEngine( $this->getContext(), 'diff' ) ) {
+ //TODO: come up with a good solution for non-text content here.
+ // at least, the content format needs to be passed to the client somehow.
+ // Currently, action=raw will just fail for non-text content.
+
$urls = array(
'File' => array( 'Extension' => 'wiki', 'URL' =>
# This should be mOldPage, but it may not be set, see below.
@@ -260,6 +267,8 @@ class DifferenceEngine extends ContextSource {
$deleted = $suppressed = false;
$allowed = $this->mNewRev->userCan( Revision::DELETED_TEXT, $user );
+ $revisionTools = array();
+
# mOldRev is false if the difference engine is called with a "vague" query for
# a diff between a version V and its previous version V' AND the version V
# is the first version of that article. In that case, V' does not exist.
@@ -287,12 +296,14 @@ class DifferenceEngine extends ContextSource {
if ( $samePage && $this->mNewPage->quickUserCan( 'edit', $user ) ) {
if ( $this->mNewRev->isCurrent() && $this->mNewPage->userCan( 'rollback', $user ) ) {
- $out->preventClickjacking();
- $rollback = '&#160;&#160;&#160;' . Linker::generateRollback( $this->mNewRev, $this->getContext() );
+ $rollbackLink = Linker::generateRollback( $this->mNewRev, $this->getContext() );
+ if ( $rollbackLink ) {
+ $out->preventClickjacking();
+ $rollback = '&#160;&#160;&#160;' . $rollbackLink;
+ }
}
if ( !$this->mOldRev->isDeleted( Revision::DELETED_TEXT ) && !$this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
- $undoLink = ' ' . $this->msg( 'parentheses' )->rawParams(
- Html::element( 'a', array(
+ $undoLink = Html::element( 'a', array(
'href' => $this->mNewPage->getLocalUrl( array(
'action' => 'edit',
'undoafter' => $this->mOldid,
@@ -300,7 +311,8 @@ class DifferenceEngine extends ContextSource {
'title' => Linker::titleAttrib( 'undo' )
),
$this->msg( 'editundo' )->text()
- ) )->escaped();
+ );
+ $revisionTools[] = $undoLink;
}
}
@@ -366,7 +378,15 @@ class DifferenceEngine extends ContextSource {
# Handle RevisionDelete links...
$rdel = $this->revisionDeleteLink( $this->mNewRev );
- $newRevisionHeader = $this->getRevisionHeader( $this->mNewRev, 'complete' ) . $undoLink;
+
+ # Allow extensions to define their own revision tools
+ wfRunHooks( 'DiffRevisionTools', array( $this->mNewRev, &$revisionTools ) );
+ $formattedRevisionTools = array();
+ // Put each one in parentheses (poor man's button)
+ foreach ( $revisionTools as $tool ) {
+ $formattedRevisionTools[] = $this->msg( 'parentheses' )->rawParams( $tool )->escaped();
+ }
+ $newRevisionHeader = $this->getRevisionHeader( $this->mNewRev, 'complete' ) . ' ' . implode( ' ', $formattedRevisionTools );
$newHeader = '<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader . '</strong></div>' .
'<div id="mw-diff-ntitle2">' . Linker::revUserTools( $this->mNewRev, !$this->unhide ) .
@@ -417,8 +437,8 @@ class DifferenceEngine extends ContextSource {
/**
* Get a link to mark the change as patrolled, or '' if there's either no
* revision to patrol or the user is not allowed to to it.
- * Side effect: this method will call OutputPage::preventClickjacking()
- * when a link is builded.
+ * Side effect: When the patrol link is build, this method will call
+ * OutputPage::preventClickjacking() and load mediawiki.page.patrol.ajax.
*
* @return String
*/
@@ -459,6 +479,8 @@ class DifferenceEngine extends ContextSource {
// Build the link
if ( $rcid ) {
$this->getOutput()->preventClickjacking();
+ $this->getOutput()->addModules( 'mediawiki.page.patrol.ajax' );
+
$token = $this->getUser()->getEditToken( $rcid );
$this->mMarkPatrolledLink = ' <span class="patrollink">[' . Linker::linkKnown(
$this->mNewPage,
@@ -510,19 +532,23 @@ class DifferenceEngine extends ContextSource {
$out->setRevisionTimestamp( $this->mNewRev->getTimestamp() );
$out->setArticleFlag( true );
+ // NOTE: only needed for B/C: custom rendering of JS/CSS via hook
if ( $this->mNewPage->isCssJsSubpage() || $this->mNewPage->isCssOrJsPage() ) {
// Stolen from Article::view --AG 2007-10-11
// Give hooks a chance to customise the output
// @TODO: standardize this crap into one function
- if ( wfRunHooks( 'ShowRawCssJs', array( $this->mNewtext, $this->mNewPage, $out ) ) ) {
- // Wrap the whole lot in a <pre> and don't parse
- $m = array();
- preg_match( '!\.(css|js)$!u', $this->mNewPage->getText(), $m );
- $out->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
- $out->addHTML( htmlspecialchars( $this->mNewtext ) );
- $out->addHTML( "\n</pre>\n" );
+ if ( ContentHandler::runLegacyHooks( 'ShowRawCssJs', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
+ // NOTE: deprecated hook, B/C only
+ // use the content object's own rendering
+ $cnt = $this->mNewRev->getContent();
+ $po = $cnt ? $cnt->getParserOutput( $this->mNewRev->getTitle(), $this->mNewRev->getId() ) : null;
+ $txt = $po ? $po->getText() : '';
+ $out->addHTML( $txt );
}
- } elseif ( !wfRunHooks( 'ArticleViewCustom', array( $this->mNewtext, $this->mNewPage, $out ) ) ) {
+ } elseif( !wfRunHooks( 'ArticleContentViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
+ // Handled by extension
+ } elseif( !ContentHandler::runLegacyHooks( 'ArticleViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
+ // NOTE: deprecated hook, B/C only
// Handled by extension
} else {
// Normal page
@@ -536,16 +562,21 @@ class DifferenceEngine extends ContextSource {
$wikiPage = WikiPage::factory( $this->mNewPage );
}
- $parserOptions = $wikiPage->makeParserOptions( $this->getContext() );
+ $parserOutput = $this->getParserOutput( $wikiPage, $this->mNewRev );
- if ( !$this->mNewRev->isCurrent() ) {
- $parserOptions->setEditSection( false );
- }
+ # Also try to load it as a redirect
+ $rt = $this->mNewContent ? $this->mNewContent->getRedirectTarget() : null;
- $parserOutput = $wikiPage->getParserOutput( $parserOptions, $this->mNewid );
+ if ( $rt ) {
+ $article = Article::newFromTitle( $this->mNewPage, $this->getContext() );
+ $out->addHTML( $article->viewRedirect( $rt ) );
- # WikiPage::getParserOutput() should not return false, but just in case
- if( $parserOutput ) {
+ # WikiPage::getParserOutput() should not return false, but just in case
+ if ( $parserOutput ) {
+ # Show categories etc.
+ $out->addParserOutputNoText( $parserOutput );
+ }
+ } else if ( $parserOutput ) {
$out->addParserOutput( $parserOutput );
}
}
@@ -556,6 +587,17 @@ class DifferenceEngine extends ContextSource {
wfProfileOut( __METHOD__ );
}
+ protected function getParserOutput( WikiPage $page, Revision $rev ) {
+ $parserOptions = $page->makeParserOptions( $this->getContext() );
+
+ if ( !$rev->isCurrent() || !$rev->getTitle()->quickUserCan( "edit" ) ) {
+ $parserOptions->setEditSection( false );
+ }
+
+ $parserOutput = $page->getParserOutput( $parserOptions, $rev->getId() );
+ return $parserOutput;
+ }
+
/**
* Get the diff text, send it to the OutputPage object
* Returns false if the diff could not be generated, otherwise returns true
@@ -584,9 +626,9 @@ class DifferenceEngine extends ContextSource {
/**
* Get complete diff table, including header
*
- * @param $otitle Title: old title
- * @param $ntitle Title: new title
- * @param $notice String: HTML between diff header and body
+ * @param string|bool $otitle Header for old text or false
+ * @param string|bool $ntitle Header for new text or false
+ * @param string $notice HTML between diff header and body
* @return mixed
*/
function getDiff( $otitle, $ntitle, $notice = '' ) {
@@ -652,7 +694,7 @@ class DifferenceEngine extends ContextSource {
return false;
}
- $difftext = $this->generateDiffBody( $this->mOldtext, $this->mNewtext );
+ $difftext = $this->generateContentDiffBody( $this->mOldContent, $this->mNewContent );
// Save to cache for 7 days
if ( !wfRunHooks( 'AbortDiffCache', array( &$this ) ) ) {
@@ -690,13 +732,64 @@ class DifferenceEngine extends ContextSource {
}
/**
+ * Generate a diff, no caching.
+ *
+ * This implementation uses generateTextDiffBody() to generate a diff based on the default
+ * serialization of the given Content objects. This will fail if $old or $new are not
+ * instances of TextContent.
+ *
+ * Subclasses may override this to provide a different rendering for the diff,
+ * perhaps taking advantage of the content's native form. This is required for all content
+ * models that are not text based.
+ *
+ * @param $old Content: old content
+ * @param $new Content: new content
+ *
+ * @return bool|string
+ * @since 1.21
+ * @throws MWException if $old or $new are not instances of TextContent.
+ */
+ function generateContentDiffBody( Content $old, Content $new ) {
+ if ( !( $old instanceof TextContent ) ) {
+ throw new MWException( "Diff not implemented for " . get_class( $old ) . "; "
+ . "override generateContentDiffBody to fix this." );
+ }
+
+ if ( !( $new instanceof TextContent ) ) {
+ throw new MWException( "Diff not implemented for " . get_class( $new ) . "; "
+ . "override generateContentDiffBody to fix this." );
+ }
+
+ $otext = $old->serialize();
+ $ntext = $new->serialize();
+
+ return $this->generateTextDiffBody( $otext, $ntext );
+ }
+
+ /**
* Generate a diff, no caching
*
- * @param $otext String: old text, must be already segmented
- * @param $ntext String: new text, must be already segmented
+ * @param string $otext old text, must be already segmented
+ * @param string $ntext new text, must be already segmented
* @return bool|string
+ * @deprecated since 1.21, use generateContentDiffBody() instead!
*/
function generateDiffBody( $otext, $ntext ) {
+ ContentHandler::deprecated( __METHOD__, "1.21" );
+
+ return $this->generateTextDiffBody( $otext, $ntext );
+ }
+
+ /**
+ * Generate a diff, no caching
+ *
+ * @todo move this to TextDifferenceEngine, make DifferenceEngine abstract. At some point.
+ *
+ * @param string $otext old text, must be already segmented
+ * @param string $ntext new text, must be already segmented
+ * @return bool|string
+ */
+ function generateTextDiffBody( $otext, $ntext ) {
global $wgExternalDiffEngine, $wgContLang;
wfProfileIn( __METHOD__ );
@@ -804,7 +897,6 @@ class DifferenceEngine extends ContextSource {
return $this->msg( 'lineno' )->numParams( $matches[1] )->escaped();
}
-
/**
* If there are revisions between the ones being compared, return a note saying so.
* @return string
@@ -855,11 +947,11 @@ class DifferenceEngine extends ContextSource {
* Get a header for a specified revision.
*
* @param $rev Revision
- * @param $complete String: 'complete' to get the header wrapped depending
+ * @param string $complete 'complete' to get the header wrapped depending
* the visibility of the revision and a link to edit the page.
* @return String HTML fragment
*/
- private function getRevisionHeader( Revision $rev, $complete = '' ) {
+ protected function getRevisionHeader( Revision $rev, $complete = '' ) {
$lang = $this->getLanguage();
$user = $this->getUser();
$revtimestamp = $rev->getTimestamp();
@@ -951,10 +1043,25 @@ class DifferenceEngine extends ContextSource {
/**
* Use specified text instead of loading from the database
+ * @deprecated since 1.21, use setContent() instead.
*/
function setText( $oldText, $newText ) {
- $this->mOldtext = $oldText;
- $this->mNewtext = $newText;
+ ContentHandler::deprecated( __METHOD__, "1.21" );
+
+ $oldContent = ContentHandler::makeContent( $oldText, $this->getTitle() );
+ $newContent = ContentHandler::makeContent( $newText, $this->getTitle() );
+
+ $this->setContent( $oldContent, $newContent );
+ }
+
+ /**
+ * Use specified text instead of loading from the database
+ * @since 1.21
+ */
+ function setContent( Content $oldContent, Content $newContent ) {
+ $this->mOldContent = $oldContent;
+ $this->mNewContent = $newContent;
+
$this->mTextLoaded = 2;
$this->mRevisionsLoaded = true;
}
@@ -1082,14 +1189,14 @@ class DifferenceEngine extends ContextSource {
return false;
}
if ( $this->mOldRev ) {
- $this->mOldtext = $this->mOldRev->getText( Revision::FOR_THIS_USER );
- if ( $this->mOldtext === false ) {
+ $this->mOldContent = $this->mOldRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
+ if ( $this->mOldContent === null ) {
return false;
}
}
if ( $this->mNewRev ) {
- $this->mNewtext = $this->mNewRev->getText( Revision::FOR_THIS_USER );
- if ( $this->mNewtext === false ) {
+ $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
+ if ( $this->mNewContent === null ) {
return false;
}
}
@@ -1110,7 +1217,7 @@ class DifferenceEngine extends ContextSource {
if ( !$this->loadRevisionData() ) {
return false;
}
- $this->mNewtext = $this->mNewRev->getText( Revision::FOR_THIS_USER );
+ $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
return true;
}
}
diff --git a/includes/diff/WikiDiff3.php b/includes/diff/WikiDiff3.php
index 66727445..ea6f6e5d 100644
--- a/includes/diff/WikiDiff3.php
+++ b/includes/diff/WikiDiff3.php
@@ -29,7 +29,7 @@
* (http://citeseer.ist.psu.edu/myers86ond.html) with range compression (see Wu et al.'s
* "An O(NP) Sequence Comparison Algorithm").
*
- * This implementation supports an upper bound on the excution time.
+ * This implementation supports an upper bound on the execution time.
*
* Complexity: O((M + N)D) worst case time, O(M + N + D^2) expected time, O(M + N) space
*
@@ -64,7 +64,7 @@ class WikiDiff3 {
public function diff( /*array*/ $from, /*array*/ $to ) {
// remember initial lengths
- $m = sizeof( $from );
+ $m = count( $from );
$n = count( $to );
$this->heuristicUsed = false;
@@ -490,7 +490,6 @@ class WikiDiff3 {
$temp = array( 0, 0, 0 );
-
$max_progress = array_fill( 0, ceil( max( $forward_end_diag - $forward_start_diag,
$backward_end_diag - $backward_start_diag ) / 2 ), $temp );
$num_progress = 0; // the 1st entry is current, it is initialized
diff --git a/includes/extauth/MediaWiki.php b/includes/extauth/MediaWiki.php
index 0a5efae6..c7f6a204 100644
--- a/includes/extauth/MediaWiki.php
+++ b/includes/extauth/MediaWiki.php
@@ -36,15 +36,15 @@
* 'DBprefix' => '',
* );
*
- * All fields must be present. These mean the same things as $wgDBtype,
- * $wgDBserver, etc. This implementation is quite crude; it could easily
- * support multiple database servers, for instance, and memcached, and it
- * probably has bugs. Kind of hard to reuse code when things might rely on who
+ * All fields must be present. These mean the same things as $wgDBtype,
+ * $wgDBserver, etc. This implementation is quite crude; it could easily
+ * support multiple database servers, for instance, and memcached, and it
+ * probably has bugs. Kind of hard to reuse code when things might rely on who
* knows what configuration globals.
*
- * If either wiki uses the UserComparePasswords hook, password authentication
- * might fail unexpectedly unless they both do the exact same validation.
- * There may be other corner cases like this where this will fail, but it
+ * If either wiki uses the UserComparePasswords hook, password authentication
+ * might fail unexpectedly unless they both do the exact same validation.
+ * There may be other corner cases like this where this will fail, but it
* should be unlikely.
*
* @ingroup ExternalUser
@@ -62,8 +62,8 @@ class ExternalUser_MediaWiki extends ExternalUser {
* @return bool
*/
protected function initFromName( $name ) {
- # We might not need the 'usable' bit, but let's be safe. Theoretically
- # this might return wrong results for old versions, but it's probably
+ # We might not need the 'usable' bit, but let's be safe. Theoretically
+ # this might return wrong results for old versions, but it's probably
# good enough.
$name = User::getCanonicalName( $name, 'usable' );
@@ -130,14 +130,14 @@ class ExternalUser_MediaWiki extends ExternalUser {
}
public function authenticate( $password ) {
- # This might be wrong if anyone actually uses the UserComparePasswords hook
+ # This might be wrong if anyone actually uses the UserComparePasswords hook
# (on either end), so don't use this if you those are incompatible.
return User::comparePasswords( $this->mRow->user_password, $password,
- $this->mRow->user_id );
+ $this->mRow->user_id );
}
public function getPref( $pref ) {
- # @todo FIXME: Return other prefs too. Lots of global-riddled code that does
+ # @todo FIXME: Return other prefs too. Lots of global-riddled code that does
# this normally.
if ( $pref === 'emailaddress'
&& $this->row->user_email_authenticated !== null ) {
diff --git a/includes/externalstore/ExternalStore.php b/includes/externalstore/ExternalStore.php
new file mode 100644
index 00000000..4ca193d4
--- /dev/null
+++ b/includes/externalstore/ExternalStore.php
@@ -0,0 +1,178 @@
+<?php
+/**
+ * @defgroup ExternalStorage ExternalStorage
+ */
+
+/**
+ * Interface for data storage in external repositories.
+ *
+ * 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
+ */
+
+/**
+ * Constructor class for key/value blob data kept in external repositories.
+ *
+ * Objects in external stores are defined by a special URL. The URL is of
+ * the form "<store protocol>://<location>/<object name>". The protocol is used
+ * to determine what ExternalStoreMedium class is used. The location identifies
+ * particular storage instances or database clusters for store class to use.
+ *
+ * When an object is inserted into a store, the calling code uses a partial URL of
+ * the form "<store protocol>://<location>" and receives the full object URL on success.
+ * This is useful since object names can be sequential IDs, UUIDs, or hashes.
+ * Callers are not responsible for unique name generation.
+ *
+ * External repositories might be populated by maintenance/async
+ * scripts, thus partial moving of data may be possible, as well
+ * as the possibility to have any storage format (i.e. for archives).
+ *
+ * @ingroup ExternalStorage
+ */
+class ExternalStore {
+ /**
+ * Get an external store object of the given type, with the given parameters
+ *
+ * @param string $proto Type of external storage, should be a value in $wgExternalStores
+ * @param array $params Associative array of ExternalStoreMedium parameters
+ * @return ExternalStoreMedium|bool The store class or false on error
+ */
+ public static function getStoreObject( $proto, array $params = array() ) {
+ global $wgExternalStores;
+
+ if ( !$wgExternalStores || !in_array( $proto, $wgExternalStores ) ) {
+ return false; // protocol not enabled
+ }
+
+ $class = 'ExternalStore' . ucfirst( $proto );
+ // Any custom modules should be added to $wgAutoLoadClasses for on-demand loading
+ return MWInit::classExists( $class ) ? new $class( $params ) : false;
+ }
+
+ /**
+ * Fetch data from given URL
+ *
+ * @param string $url The URL of the text to get
+ * @param array $params Associative array of ExternalStoreMedium parameters
+ * @return string|bool The text stored or false on error
+ * @throws MWException
+ */
+ public static function fetchFromURL( $url, array $params = array() ) {
+ $parts = explode( '://', $url, 2 );
+ if ( count( $parts ) != 2 ) {
+ return false; // invalid URL
+ }
+
+ list( $proto, $path ) = $parts;
+ if ( $path == '' ) { // bad URL
+ return false;
+ }
+
+ $store = self::getStoreObject( $proto, $params );
+ if ( $store === false ) {
+ return false;
+ }
+
+ return $store->fetchFromURL( $url );
+ }
+
+ /**
+ * Store a data item to an external store, identified by a partial URL
+ * The protocol part is used to identify the class, the rest is passed to the
+ * class itself as a parameter.
+ *
+ * @param string $url A partial external store URL ("<store type>://<location>")
+ * @param $data string
+ * @param array $params Associative array of ExternalStoreMedium parameters
+ * @return string|bool The URL of the stored data item, or false on error
+ * @throws MWException
+ */
+ public static function insert( $url, $data, array $params = array() ) {
+ $parts = explode( '://', $url, 2 );
+ if ( count( $parts ) != 2 ) {
+ return false; // invalid URL
+ }
+
+ list( $proto, $path ) = $parts;
+ if ( $path == '' ) { // bad URL
+ return false;
+ }
+
+ $store = self::getStoreObject( $proto, $params );
+ if ( $store === false ) {
+ return false;
+ } else {
+ return $store->store( $path, $data );
+ }
+ }
+
+ /**
+ * Like insert() above, but does more of the work for us.
+ * This function does not need a url param, it builds it by
+ * itself. It also fails-over to the next possible clusters.
+ *
+ * @param $data string
+ * @param array $params Associative array of ExternalStoreMedium parameters
+ * @return string|bool The URL of the stored data item, or false on error
+ * @throws MWException
+ */
+ public static function insertToDefault( $data, array $params = array() ) {
+ global $wgDefaultExternalStore;
+
+ $error = false;
+ $tryStores = (array)$wgDefaultExternalStore;
+ while ( count( $tryStores ) > 0 ) {
+ $index = mt_rand( 0, count( $tryStores ) - 1 );
+ $storeUrl = $tryStores[$index];
+ wfDebug( __METHOD__ . ": trying $storeUrl\n" );
+ list( $proto, $path ) = explode( '://', $storeUrl, 2 );
+ $store = self::getStoreObject( $proto, $params );
+ if ( $store === false ) {
+ throw new MWException( "Invalid external storage protocol - $storeUrl" );
+ }
+ try {
+ $url = $store->store( $path, $data ); // Try to save the object
+ } catch ( MWException $error ) {
+ $url = false;
+ }
+ if ( strlen( $url ) ) {
+ return $url; // Done!
+ } else {
+ unset( $tryStores[$index] ); // Don't try this one again!
+ $tryStores = array_values( $tryStores ); // Must have consecutive keys
+ wfDebugLog( 'ExternalStorage',
+ "Unable to store text to external storage $storeUrl" );
+ }
+ }
+ // All stores failed
+ if ( $error ) {
+ throw $error; // rethrow the last error
+ } else {
+ throw new MWException( "Unable to store text to external storage" );
+ }
+ }
+
+ /**
+ * @param $data string
+ * @param $wiki string
+ * @return string|bool The URL of the stored data item, or false on error
+ * @throws MWException
+ */
+ public static function insertToForeignDefault( $data, $wiki ) {
+ return self::insertToDefault( $data, array( 'wiki' => $wiki ) );
+ }
+}
diff --git a/includes/ExternalStoreDB.php b/includes/externalstore/ExternalStoreDB.php
index 6f2b33e1..196e7f2c 100644
--- a/includes/ExternalStoreDB.php
+++ b/includes/externalstore/ExternalStoreDB.php
@@ -21,23 +21,65 @@
*/
/**
- * DB accessable external objects
+ * DB accessable external objects.
+ *
+ * In this system, each store "location" maps to a database "cluster".
+ * The clusters must be defined in the normal LBFactory configuration.
+ *
* @ingroup ExternalStorage
*/
-class ExternalStoreDB {
+class ExternalStoreDB extends ExternalStoreMedium {
+ /**
+ * The URL returned is of the form of the form DB://cluster/id
+ * or DB://cluster/id/itemid for concatened storage.
+ *
+ * @see ExternalStoreMedium::fetchFromURL()
+ */
+ public function fetchFromURL( $url ) {
+ $path = explode( '/', $url );
+ $cluster = $path[2];
+ $id = $path[3];
+ if ( isset( $path[4] ) ) {
+ $itemID = $path[4];
+ } else {
+ $itemID = false;
+ }
- function __construct( $params = array() ) {
- $this->mParams = $params;
+ $ret =& $this->fetchBlob( $cluster, $id, $itemID );
+
+ if ( $itemID !== false && $ret !== false ) {
+ return $ret->getItem( $itemID );
+ }
+ return $ret;
+ }
+
+ /**
+ * @see ExternalStoreMedium::store()
+ */
+ public function store( $cluster, $data ) {
+ $dbw = $this->getMaster( $cluster );
+ $id = $dbw->nextSequenceValue( 'blob_blob_id_seq' );
+ $dbw->insert( $this->getTable( $dbw ),
+ array( 'blob_id' => $id, 'blob_text' => $data ),
+ __METHOD__ );
+ $id = $dbw->insertId();
+ if ( !$id ) {
+ throw new MWException( __METHOD__.': no insert ID' );
+ }
+ if ( $dbw->getFlag( DBO_TRX ) ) {
+ $dbw->commit( __METHOD__ );
+ }
+ return "DB://$cluster/$id";
}
/**
* Get a LoadBalancer for the specified cluster
*
- * @param $cluster String: cluster name
+ * @param string $cluster cluster name
* @return LoadBalancer object
*/
function &getLoadBalancer( $cluster ) {
- $wiki = isset($this->mParams['wiki']) ? $this->mParams['wiki'] : false;
+ $wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
return wfGetLBFactory()->getExternalLB( $cluster, $wiki );
}
@@ -45,18 +87,18 @@ class ExternalStoreDB {
/**
* Get a slave database connection for the specified cluster
*
- * @param $cluster String: cluster name
+ * @param string $cluster cluster name
* @return DatabaseBase object
*/
function &getSlave( $cluster ) {
global $wgDefaultExternalStore;
- $wiki = isset($this->mParams['wiki']) ? $this->mParams['wiki'] : false;
+ $wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
$lb =& $this->getLoadBalancer( $cluster );
if ( !in_array( "DB://" . $cluster, (array)$wgDefaultExternalStore ) ) {
wfDebug( "read only external store" );
- $lb->allowLagged(true);
+ $lb->allowLagged( true );
} else {
wfDebug( "writable external store" );
}
@@ -67,11 +109,11 @@ class ExternalStoreDB {
/**
* Get a master database connection for the specified cluster
*
- * @param $cluster String: cluster name
+ * @param string $cluster cluster name
* @return DatabaseBase object
*/
function &getMaster( $cluster ) {
- $wiki = isset($this->mParams['wiki']) ? $this->mParams['wiki'] : false;
+ $wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
$lb =& $this->getLoadBalancer( $cluster );
return $lb->getConnection( DB_MASTER, array(), $wiki );
}
@@ -91,29 +133,6 @@ class ExternalStoreDB {
}
/**
- * Fetch data from given URL
- * @param $url String: an url of the form DB://cluster/id or DB://cluster/id/itemid for concatened storage.
- * @return mixed
- */
- function fetchFromURL( $url ) {
- $path = explode( '/', $url );
- $cluster = $path[2];
- $id = $path[3];
- if ( isset( $path[4] ) ) {
- $itemID = $path[4];
- } else {
- $itemID = false;
- }
-
- $ret =& $this->fetchBlob( $cluster, $id, $itemID );
-
- if ( $itemID !== false && $ret !== false ) {
- return $ret->getItem( $itemID );
- }
- return $ret;
- }
-
- /**
* Fetch a blob item out of the database; a cache of the last-loaded
* blob will be kept so that multiple loads out of a multi-item blob
* can avoid redundant database access and decompression.
@@ -134,11 +153,11 @@ class ExternalStoreDB {
$cacheID = ( $itemID === false ) ? "$cluster/$id" : "$cluster/$id/";
if( isset( $externalBlobCache[$cacheID] ) ) {
- wfDebug( "ExternalStoreDB::fetchBlob cache hit on $cacheID\n" );
+ wfDebugLog( 'ExternalStoreDB-cache', "ExternalStoreDB::fetchBlob cache hit on $cacheID\n" );
return $externalBlobCache[$cacheID];
}
- wfDebug( "ExternalStoreDB::fetchBlob cache miss on $cacheID\n" );
+ wfDebugLog( 'ExternalStoreDB-cache', "ExternalStoreDB::fetchBlob cache miss on $cacheID\n" );
$dbr =& $this->getSlave( $cluster );
$ret = $dbr->selectField( $this->getTable( $dbr ), 'blob_text', array( 'blob_id' => $id ), __METHOD__ );
@@ -159,27 +178,4 @@ class ExternalStoreDB {
$externalBlobCache = array( $cacheID => &$ret );
return $ret;
}
-
- /**
- * Insert a data item into a given cluster
- *
- * @param $cluster String: the cluster name
- * @param $data String: the data item
- * @return string URL
- */
- function store( $cluster, $data ) {
- $dbw = $this->getMaster( $cluster );
- $id = $dbw->nextSequenceValue( 'blob_blob_id_seq' );
- $dbw->insert( $this->getTable( $dbw ),
- array( 'blob_id' => $id, 'blob_text' => $data ),
- __METHOD__ );
- $id = $dbw->insertId();
- if ( !$id ) {
- throw new MWException( __METHOD__.': no insert ID' );
- }
- if ( $dbw->getFlag( DBO_TRX ) ) {
- $dbw->commit( __METHOD__ );
- }
- return "DB://$cluster/$id";
- }
}
diff --git a/includes/ExternalStoreHttp.php b/includes/externalstore/ExternalStoreHttp.php
index 311e32b3..345c17be 100644
--- a/includes/ExternalStoreHttp.php
+++ b/includes/externalstore/ExternalStoreHttp.php
@@ -26,20 +26,18 @@
*
* @ingroup ExternalStorage
*/
-class ExternalStoreHttp {
-
+class ExternalStoreHttp extends ExternalStoreMedium {
/**
- * Fetch data from given URL
- *
- * @param $url String: the URL
- * @return String: the content at $url
+ * @see ExternalStoreMedium::fetchFromURL()
*/
- function fetchFromURL( $url ) {
- $ret = Http::get( $url );
- return $ret;
+ public function fetchFromURL( $url ) {
+ return Http::get( $url );
}
- /* XXX: may require other methods, for store, delete,
- * whatever, for initial ext storage
+ /**
+ * @see ExternalStoreMedium::store()
*/
+ public function store( $cluster, $data ) {
+ throw new MWException( "ExternalStoreHttp is read-only and does not support store()." );
+ }
}
diff --git a/includes/externalstore/ExternalStoreMedium.php b/includes/externalstore/ExternalStoreMedium.php
new file mode 100644
index 00000000..41af7d87
--- /dev/null
+++ b/includes/externalstore/ExternalStoreMedium.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * External storage in some particular medium.
+ *
+ * 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 ExternalStorage
+ * @author Aaron Schulz
+ */
+
+/**
+ * Accessable external objects in a particular storage medium
+ *
+ * @ingroup ExternalStorage
+ * @since 1.21
+ */
+abstract class ExternalStoreMedium {
+ /** @var Array */
+ protected $params = array();
+
+ /**
+ * @param array $params Options
+ */
+ public function __construct( array $params = array() ) {
+ $this->params = $params;
+ }
+
+ /**
+ * Fetch data from given external store URL
+ *
+ * @param string $url An external store URL
+ * @return string|bool The text stored or false on error
+ * @throws MWException
+ */
+ abstract public function fetchFromURL( $url );
+
+ /**
+ * Insert a data item into a given location
+ *
+ * @param string $location the location name
+ * @param string $data the data item
+ * @return string|bool The URL of the stored data item, or false on error
+ * @throws MWException
+ */
+ abstract public function store( $location, $data );
+}
diff --git a/includes/externalstore/ExternalStoreMwstore.php b/includes/externalstore/ExternalStoreMwstore.php
new file mode 100644
index 00000000..0911cca1
--- /dev/null
+++ b/includes/externalstore/ExternalStoreMwstore.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * External storage in a file backend.
+ *
+ * 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
+ */
+
+/**
+ * File backend accessable external objects.
+ *
+ * In this system, each store "location" maps to the name of a file backend.
+ * The file backends must be defined in $wgFileBackends and must be global
+ * and fully qualified with a global "wikiId" prefix in the configuration.
+ *
+ * @ingroup ExternalStorage
+ * @since 1.21
+ */
+class ExternalStoreMwstore extends ExternalStoreMedium {
+ /**
+ * The URL returned is of the form of the form mwstore://backend/container/wiki/id
+ *
+ * @see ExternalStoreMedium::fetchFromURL()
+ */
+ public function fetchFromURL( $url ) {
+ $be = FileBackendGroup::singleton()->backendFromPath( $url );
+ if ( $be instanceof FileBackend ) {
+ // We don't need "latest" since objects are immutable and
+ // backends should at least have "read-after-create" consistency.
+ return $be->getFileContents( array( 'src' => $url ) );
+ }
+ return false;
+ }
+
+ /**
+ * @see ExternalStoreMedium::store()
+ */
+ public function store( $backend, $data ) {
+ $be = FileBackendGroup::singleton()->get( $backend );
+ if ( $be instanceof FileBackend ) {
+ // Get three random base 36 characters to act as shard directories
+ $rand = wfBaseConvert( mt_rand( 0, 46655 ), 10, 36, 3 );
+ // Make sure ID is roughly lexicographically increasing for performance
+ $id = str_pad( UIDGenerator::newTimestampedUID128( 32 ), 26, '0', STR_PAD_LEFT );
+ // Segregate items by wiki ID for the sake of bookkeeping
+ $wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : wfWikiID();
+
+ $url = $be->getContainerStoragePath( 'data' ) . '/' .
+ rawurlencode( $wiki ) . "/{$rand[0]}/{$rand[1]}/{$rand[2]}/{$id}";
+
+ $be->prepare( array( 'dir' => dirname( $url ), 'noAccess' => 1, 'noListing' => 1 ) );
+ if ( $be->create( array( 'dst' => $url, 'content' => $data ) )->isOK() ) {
+ return $url;
+ }
+ }
+ return false;
+ }
+}
diff --git a/includes/filebackend/FSFile.php b/includes/filebackend/FSFile.php
index e07c99d4..7d0dbd52 100644
--- a/includes/filebackend/FSFile.php
+++ b/includes/filebackend/FSFile.php
@@ -28,11 +28,12 @@
*/
class FSFile {
protected $path; // path to file
+ private $sha1Base36 = null; // File Sha1Base36
/**
* Sets up the file object
*
- * @param $path string Path to temporary file on local disk
+ * @param string $path Path to temporary file on local disk
* @throws MWException
*/
public function __construct( $path ) {
@@ -86,8 +87,8 @@ class FSFile {
/**
* Guess the MIME type from the file contents alone
- *
- * @return string
+ *
+ * @return string
*/
public function getMimeType() {
return MimeMagic::singleton()->guessMimeType( $this->path, false );
@@ -104,7 +105,7 @@ class FSFile {
*/
public function getProps( $ext = true ) {
wfProfileIn( __METHOD__ );
- wfDebug( __METHOD__.": Getting file info for $this->path\n" );
+ wfDebug( __METHOD__ . ": Getting file info for $this->path\n" );
$info = self::placeholderProps();
$info['fileExists'] = $this->exists();
@@ -131,7 +132,7 @@ class FSFile {
# Height, width and metadata
$handler = MediaHandler::getHandler( $info['mime'] );
if ( $handler ) {
- $tempImage = (object)array();
+ $tempImage = (object)array(); // XXX (hack for File object)
$info['metadata'] = $handler->getMetadata( $tempImage, $this->path );
$gis = $handler->getImageSize( $tempImage, $this->path, $info['metadata'] );
if ( is_array( $gis ) ) {
@@ -140,9 +141,9 @@ class FSFile {
}
$info['sha1'] = $this->getSha1Base36();
- wfDebug(__METHOD__.": $this->path loaded, {$info['size']} bytes, {$info['mime']}.\n");
+ wfDebug( __METHOD__ . ": $this->path loaded, {$info['size']} bytes, {$info['mime']}.\n" );
} else {
- wfDebug(__METHOD__.": $this->path NOT FOUND!\n");
+ wfDebug( __METHOD__ . ": $this->path NOT FOUND!\n" );
}
wfProfileOut( __METHOD__ );
@@ -193,25 +194,32 @@ class FSFile {
* 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
* fairly neatly.
*
+ * @param $recache bool
* @return bool|string False on failure
*/
- public function getSha1Base36() {
+ public function getSha1Base36( $recache = false ) {
wfProfileIn( __METHOD__ );
+ if ( $this->sha1Base36 !== null && !$recache ) {
+ wfProfileOut( __METHOD__ );
+ return $this->sha1Base36;
+ }
+
wfSuppressWarnings();
- $hash = sha1_file( $this->path );
+ $this->sha1Base36 = sha1_file( $this->path );
wfRestoreWarnings();
- if ( $hash !== false ) {
- $hash = wfBaseConvert( $hash, 16, 36, 31 );
+
+ if ( $this->sha1Base36 !== false ) {
+ $this->sha1Base36 = wfBaseConvert( $this->sha1Base36, 16, 36, 31 );
}
wfProfileOut( __METHOD__ );
- return $hash;
+ return $this->sha1Base36;
}
/**
* Get the final file extension from a file system path
- *
+ *
* @param $path string
* @return string
*/
@@ -223,7 +231,7 @@ class FSFile {
/**
* Get an associative array containing information about a file in the local filesystem.
*
- * @param $path String: absolute local filesystem path
+ * @param string $path absolute local filesystem path
* @param $ext Mixed: the file extension, or true to extract it from the filename.
* Set it to false to ignore the extension.
*
@@ -242,11 +250,18 @@ class FSFile {
* fairly neatly.
*
* @param $path string
+ * @param $recache bool
*
* @return bool|string False on failure
*/
- public static function getSha1Base36FromPath( $path ) {
- $fsFile = new self( $path );
- return $fsFile->getSha1Base36();
+ public static function getSha1Base36FromPath( $path, $recache = false ) {
+ static $sha1Base36 = array();
+
+ if ( !isset( $sha1Base36[$path] ) || $recache ) {
+ $fsFile = new self( $path );
+ $sha1Base36[$path] = $fsFile->getSha1Base36();
+ }
+
+ return $sha1Base36[$path];
}
}
diff --git a/includes/filebackend/FSFileBackend.php b/includes/filebackend/FSFileBackend.php
index 93495340..c9769989 100644
--- a/includes/filebackend/FSFileBackend.php
+++ b/includes/filebackend/FSFileBackend.php
@@ -46,6 +46,7 @@ class FSFileBackend extends FileBackendStore {
protected $fileOwner; // string; required OS username to own files
protected $currentUser; // string; OS username running this script
+ /** @var Array */
protected $hadWarningErrors = array();
/**
@@ -69,7 +70,7 @@ class FSFileBackend extends FileBackendStore {
if ( isset( $config['containerPaths'] ) ) {
$this->containerPaths = (array)$config['containerPaths'];
foreach ( $this->containerPaths as &$path ) {
- $path = rtrim( $path, '/' ); // remove trailing slash
+ $path = rtrim( $path, '/' ); // remove trailing slash
}
}
@@ -101,7 +102,7 @@ class FSFileBackend extends FileBackendStore {
/**
* Sanity check a relative file system path for validity
*
- * @param $path string Normalized relative path
+ * @param string $path Normalized relative path
* @return bool
*/
protected function isLegalRelPath( $path ) {
@@ -136,7 +137,7 @@ class FSFileBackend extends FileBackendStore {
/**
* Get the absolute file system path for a storage path
*
- * @param $storagePath string Storage path
+ * @param string $storagePath Storage path
* @return string|null
*/
protected function resolveToFSPath( $storagePath ) {
@@ -144,7 +145,7 @@ class FSFileBackend extends FileBackendStore {
if ( $relPath === null ) {
return null; // invalid
}
- list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $storagePath );
+ list( , $shortCont, ) = FileBackend::splitStoragePath( $storagePath );
$fsPath = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
if ( $relPath != '' ) {
$fsPath .= "/{$relPath}";
@@ -178,10 +179,10 @@ class FSFileBackend extends FileBackendStore {
}
/**
- * @see FileBackendStore::doStoreInternal()
+ * @see FileBackendStore::doCreateInternal()
* @return Status
*/
- protected function doStoreInternal( array $params ) {
+ protected function doCreateInternal( array $params ) {
$status = Status::newGood();
$dest = $this->resolveToFSPath( $params['dst'] );
@@ -190,27 +191,74 @@ class FSFileBackend extends FileBackendStore {
return $status;
}
- if ( file_exists( $dest ) ) {
- if ( !empty( $params['overwrite'] ) ) {
- $ok = unlink( $dest );
- if ( !$ok ) {
- $status->fatal( 'backend-fail-delete', $params['dst'] );
- return $status;
- }
- } else {
- $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
+ if ( !empty( $params['async'] ) ) { // deferred
+ $tempFile = TempFSFile::factory( 'create_', 'tmp' );
+ if ( !$tempFile ) {
+ $status->fatal( 'backend-fail-create', $params['dst'] );
+ return $status;
+ }
+ $this->trapWarnings();
+ $bytes = file_put_contents( $tempFile->getPath(), $params['content'] );
+ $this->untrapWarnings();
+ if ( $bytes === false ) {
+ $status->fatal( 'backend-fail-create', $params['dst'] );
+ return $status;
+ }
+ $cmd = implode( ' ', array(
+ wfIsWindows() ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
+ wfEscapeShellArg( $this->cleanPathSlashes( $tempFile->getPath() ) ),
+ wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
+ ) );
+ $status->value = new FSFileOpHandle( $this, $params, 'Create', $cmd, $dest );
+ $tempFile->bind( $status->value );
+ } else { // immediate write
+ $this->trapWarnings();
+ $bytes = file_put_contents( $dest, $params['content'] );
+ $this->untrapWarnings();
+ if ( $bytes === false ) {
+ $status->fatal( 'backend-fail-create', $params['dst'] );
return $status;
}
+ $this->chmod( $dest );
+ }
+
+ return $status;
+ }
+
+ /**
+ * @see FSFileBackend::doExecuteOpHandlesInternal()
+ */
+ protected function _getResponseCreate( $errors, Status $status, array $params, $cmd ) {
+ if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
+ $status->fatal( 'backend-fail-create', $params['dst'] );
+ trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
+ }
+ }
+
+ /**
+ * @see FileBackendStore::doStoreInternal()
+ * @return Status
+ */
+ protected function doStoreInternal( array $params ) {
+ $status = Status::newGood();
+
+ $dest = $this->resolveToFSPath( $params['dst'] );
+ if ( $dest === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+ return $status;
}
if ( !empty( $params['async'] ) ) { // deferred
- $cmd = implode( ' ', array( wfIsWindows() ? 'COPY' : 'cp',
+ $cmd = implode( ' ', array(
+ wfIsWindows() ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
wfEscapeShellArg( $this->cleanPathSlashes( $params['src'] ) ),
wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
) );
$status->value = new FSFileOpHandle( $this, $params, 'Store', $cmd, $dest );
} else { // immediate write
+ $this->trapWarnings();
$ok = copy( $params['src'], $dest );
+ $this->untrapWarnings();
// In some cases (at least over NFS), copy() returns true when it fails
if ( !$ok || ( filesize( $params['src'] ) !== filesize( $dest ) ) ) {
if ( $ok ) { // PHP bug
@@ -255,31 +303,30 @@ class FSFileBackend extends FileBackendStore {
return $status;
}
- if ( file_exists( $dest ) ) {
- if ( !empty( $params['overwrite'] ) ) {
- $ok = unlink( $dest );
- if ( !$ok ) {
- $status->fatal( 'backend-fail-delete', $params['dst'] );
- return $status;
- }
- } else {
- $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
- return $status;
+ if ( !is_file( $source ) ) {
+ if ( empty( $params['ignoreMissingSource'] ) ) {
+ $status->fatal( 'backend-fail-copy', $params['src'] );
}
+ return $status; // do nothing; either OK or bad status
}
if ( !empty( $params['async'] ) ) { // deferred
- $cmd = implode( ' ', array( wfIsWindows() ? 'COPY' : 'cp',
+ $cmd = implode( ' ', array(
+ wfIsWindows() ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
wfEscapeShellArg( $this->cleanPathSlashes( $source ) ),
wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
) );
$status->value = new FSFileOpHandle( $this, $params, 'Copy', $cmd, $dest );
} else { // immediate write
+ $this->trapWarnings();
$ok = copy( $source, $dest );
+ $this->untrapWarnings();
// In some cases (at least over NFS), copy() returns true when it fails
if ( !$ok || ( filesize( $source ) !== filesize( $dest ) ) ) {
if ( $ok ) { // PHP bug
+ $this->trapWarnings();
unlink( $dest ); // remove broken file
+ $this->untrapWarnings();
trigger_error( __METHOD__ . ": copy() failed but returned true." );
}
$status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
@@ -320,30 +367,24 @@ class FSFileBackend extends FileBackendStore {
return $status;
}
- if ( file_exists( $dest ) ) {
- if ( !empty( $params['overwrite'] ) ) {
- // Windows does not support moving over existing files
- if ( wfIsWindows() ) {
- $ok = unlink( $dest );
- if ( !$ok ) {
- $status->fatal( 'backend-fail-delete', $params['dst'] );
- return $status;
- }
- }
- } else {
- $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
- return $status;
+ if ( !is_file( $source ) ) {
+ if ( empty( $params['ignoreMissingSource'] ) ) {
+ $status->fatal( 'backend-fail-move', $params['src'] );
}
+ return $status; // do nothing; either OK or bad status
}
if ( !empty( $params['async'] ) ) { // deferred
- $cmd = implode( ' ', array( wfIsWindows() ? 'MOVE' : 'mv',
+ $cmd = implode( ' ', array(
+ wfIsWindows() ? 'MOVE /Y' : 'mv', // (overwrite)
wfEscapeShellArg( $this->cleanPathSlashes( $source ) ),
wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
) );
$status->value = new FSFileOpHandle( $this, $params, 'Move', $cmd );
} else { // immediate write
+ $this->trapWarnings();
$ok = rename( $source, $dest );
+ $this->untrapWarnings();
clearstatcache(); // file no longer at source
if ( !$ok ) {
$status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
@@ -385,12 +426,15 @@ class FSFileBackend extends FileBackendStore {
}
if ( !empty( $params['async'] ) ) { // deferred
- $cmd = implode( ' ', array( wfIsWindows() ? 'DEL' : 'unlink',
+ $cmd = implode( ' ', array(
+ wfIsWindows() ? 'DEL' : 'unlink',
wfEscapeShellArg( $this->cleanPathSlashes( $source ) )
) );
$status->value = new FSFileOpHandle( $this, $params, 'Copy', $cmd );
} else { // immediate write
+ $this->trapWarnings();
$ok = unlink( $source );
+ $this->untrapWarnings();
if ( !$ok ) {
$status->fatal( 'backend-fail-delete', $params['src'] );
return $status;
@@ -411,89 +455,27 @@ class FSFileBackend extends FileBackendStore {
}
/**
- * @see FileBackendStore::doCreateInternal()
- * @return Status
- */
- protected function doCreateInternal( array $params ) {
- $status = Status::newGood();
-
- $dest = $this->resolveToFSPath( $params['dst'] );
- if ( $dest === null ) {
- $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
- return $status;
- }
-
- if ( file_exists( $dest ) ) {
- if ( !empty( $params['overwrite'] ) ) {
- $ok = unlink( $dest );
- if ( !$ok ) {
- $status->fatal( 'backend-fail-delete', $params['dst'] );
- return $status;
- }
- } else {
- $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
- return $status;
- }
- }
-
- if ( !empty( $params['async'] ) ) { // deferred
- $tempFile = TempFSFile::factory( 'create_', 'tmp' );
- if ( !$tempFile ) {
- $status->fatal( 'backend-fail-create', $params['dst'] );
- return $status;
- }
- $bytes = file_put_contents( $tempFile->getPath(), $params['content'] );
- if ( $bytes === false ) {
- $status->fatal( 'backend-fail-create', $params['dst'] );
- return $status;
- }
- $cmd = implode( ' ', array( wfIsWindows() ? 'COPY' : 'cp',
- wfEscapeShellArg( $this->cleanPathSlashes( $tempFile->getPath() ) ),
- wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
- ) );
- $status->value = new FSFileOpHandle( $this, $params, 'Create', $cmd, $dest );
- $tempFile->bind( $status->value );
- } else { // immediate write
- $bytes = file_put_contents( $dest, $params['content'] );
- if ( $bytes === false ) {
- $status->fatal( 'backend-fail-create', $params['dst'] );
- return $status;
- }
- $this->chmod( $dest );
- }
-
- return $status;
- }
-
- /**
- * @see FSFileBackend::doExecuteOpHandlesInternal()
- */
- protected function _getResponseCreate( $errors, Status $status, array $params, $cmd ) {
- if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
- $status->fatal( 'backend-fail-create', $params['dst'] );
- trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
- }
- }
-
- /**
* @see FileBackendStore::doPrepareInternal()
* @return Status
*/
protected function doPrepareInternal( $fullCont, $dirRel, array $params ) {
$status = Status::newGood();
- list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
+ list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
$contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
$dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
$existed = is_dir( $dir ); // already there?
- if ( !wfMkdirParents( $dir ) ) { // make directory and its parents
+ // Create the directory and its parents as needed...
+ $this->trapWarnings();
+ if ( !wfMkdirParents( $dir ) ) {
$status->fatal( 'directorycreateerror', $params['dir'] ); // fails on races
} elseif ( !is_writable( $dir ) ) {
$status->fatal( 'directoryreadonlyerror', $params['dir'] );
} elseif ( !is_readable( $dir ) ) {
$status->fatal( 'directorynotreadableerror', $params['dir'] );
}
+ $this->untrapWarnings();
+ // Respect any 'noAccess' or 'noListing' flags...
if ( is_dir( $dir ) && !$existed ) {
- // Respect any 'noAccess' or 'noListing' flags...
$status->merge( $this->doSecureInternal( $fullCont, $dirRel, $params ) );
}
return $status;
@@ -505,24 +487,26 @@ class FSFileBackend extends FileBackendStore {
*/
protected function doSecureInternal( $fullCont, $dirRel, array $params ) {
$status = Status::newGood();
- list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
+ list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
$contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
$dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
// Seed new directories with a blank index.html, to prevent crawling...
if ( !empty( $params['noListing'] ) && !file_exists( "{$dir}/index.html" ) ) {
+ $this->trapWarnings();
$bytes = file_put_contents( "{$dir}/index.html", $this->indexHtmlPrivate() );
+ $this->untrapWarnings();
if ( $bytes === false ) {
$status->fatal( 'backend-fail-create', $params['dir'] . '/index.html' );
- return $status;
}
}
// Add a .htaccess file to the root of the container...
if ( !empty( $params['noAccess'] ) && !file_exists( "{$contRoot}/.htaccess" ) ) {
+ $this->trapWarnings();
$bytes = file_put_contents( "{$contRoot}/.htaccess", $this->htaccessPrivate() );
+ $this->untrapWarnings();
if ( $bytes === false ) {
$storeDir = "mwstore://{$this->name}/{$shortCont}";
$status->fatal( 'backend-fail-create', "{$storeDir}/.htaccess" );
- return $status;
}
}
return $status;
@@ -534,25 +518,27 @@ class FSFileBackend extends FileBackendStore {
*/
protected function doPublishInternal( $fullCont, $dirRel, array $params ) {
$status = Status::newGood();
- list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
+ list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
$contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
$dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
// Unseed new directories with a blank index.html, to allow crawling...
if ( !empty( $params['listing'] ) && is_file( "{$dir}/index.html" ) ) {
$exists = ( file_get_contents( "{$dir}/index.html" ) === $this->indexHtmlPrivate() );
+ $this->trapWarnings();
if ( $exists && !unlink( "{$dir}/index.html" ) ) { // reverse secure()
$status->fatal( 'backend-fail-delete', $params['dir'] . '/index.html' );
- return $status;
}
+ $this->untrapWarnings();
}
// Remove the .htaccess file from the root of the container...
if ( !empty( $params['access'] ) && is_file( "{$contRoot}/.htaccess" ) ) {
$exists = ( file_get_contents( "{$contRoot}/.htaccess" ) === $this->htaccessPrivate() );
+ $this->trapWarnings();
if ( $exists && !unlink( "{$contRoot}/.htaccess" ) ) { // reverse secure()
$storeDir = "mwstore://{$this->name}/{$shortCont}";
$status->fatal( 'backend-fail-delete', "{$storeDir}/.htaccess" );
- return $status;
}
+ $this->untrapWarnings();
}
return $status;
}
@@ -563,14 +549,14 @@ class FSFileBackend extends FileBackendStore {
*/
protected function doCleanInternal( $fullCont, $dirRel, array $params ) {
$status = Status::newGood();
- list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
+ list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
$contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
$dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
- wfSuppressWarnings();
+ $this->trapWarnings();
if ( is_dir( $dir ) ) {
rmdir( $dir ); // remove directory if empty
}
- wfRestoreWarnings();
+ $this->untrapWarnings();
return $status;
}
@@ -612,7 +598,7 @@ class FSFileBackend extends FileBackendStore {
* @return bool|null
*/
protected function doDirectoryExists( $fullCont, $dirRel, array $params ) {
- list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
+ list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
$contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
$dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
@@ -628,7 +614,7 @@ class FSFileBackend extends FileBackendStore {
* @return Array|null
*/
public function getDirectoryListInternal( $fullCont, $dirRel, array $params ) {
- list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
+ list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
$contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
$dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
$exists = is_dir( $dir );
@@ -644,10 +630,10 @@ class FSFileBackend extends FileBackendStore {
/**
* @see FileBackendStore::getFileListInternal()
- * @return array|FSFileBackendFileList|null
+ * @return Array|FSFileBackendFileList|null
*/
public function getFileListInternal( $fullCont, $dirRel, array $params ) {
- list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
+ list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
$contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
$dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
$exists = is_dir( $dir );
@@ -662,44 +648,58 @@ class FSFileBackend extends FileBackendStore {
}
/**
- * @see FileBackendStore::getLocalReference()
- * @return FSFile|null
+ * @see FileBackendStore::doGetLocalReferenceMulti()
+ * @return Array
*/
- public function getLocalReference( array $params ) {
- $source = $this->resolveToFSPath( $params['src'] );
- if ( $source === null ) {
- return null;
+ protected function doGetLocalReferenceMulti( array $params ) {
+ $fsFiles = array(); // (path => FSFile)
+
+ foreach ( $params['srcs'] as $src ) {
+ $source = $this->resolveToFSPath( $src );
+ if ( $source === null || !is_file( $source ) ) {
+ $fsFiles[$src] = null; // invalid path or file does not exist
+ } else {
+ $fsFiles[$src] = new FSFile( $source );
+ }
}
- return new FSFile( $source );
+
+ return $fsFiles;
}
/**
- * @see FileBackendStore::getLocalCopy()
- * @return null|TempFSFile
+ * @see FileBackendStore::doGetLocalCopyMulti()
+ * @return Array
*/
- public function getLocalCopy( array $params ) {
- $source = $this->resolveToFSPath( $params['src'] );
- if ( $source === null ) {
- return null;
- }
-
- // Create a new temporary file with the same extension...
- $ext = FileBackend::extensionFromPath( $params['src'] );
- $tmpFile = TempFSFile::factory( 'localcopy_', $ext );
- if ( !$tmpFile ) {
- return null;
- }
- $tmpPath = $tmpFile->getPath();
+ protected function doGetLocalCopyMulti( array $params ) {
+ $tmpFiles = array(); // (path => TempFSFile)
- // Copy the source file over the temp file
- $ok = copy( $source, $tmpPath );
- if ( !$ok ) {
- return null;
+ foreach ( $params['srcs'] as $src ) {
+ $source = $this->resolveToFSPath( $src );
+ if ( $source === null ) {
+ $tmpFiles[$src] = null; // invalid path
+ } else {
+ // Create a new temporary file with the same extension...
+ $ext = FileBackend::extensionFromPath( $src );
+ $tmpFile = TempFSFile::factory( 'localcopy_', $ext );
+ if ( !$tmpFile ) {
+ $tmpFiles[$src] = null;
+ } else {
+ $tmpPath = $tmpFile->getPath();
+ // Copy the source file over the temp file
+ $this->trapWarnings();
+ $ok = copy( $source, $tmpPath );
+ $this->untrapWarnings();
+ if ( !$ok ) {
+ $tmpFiles[$src] = null;
+ } else {
+ $this->chmod( $tmpPath );
+ $tmpFiles[$src] = $tmpFile;
+ }
+ }
+ }
}
- $this->chmod( $tmpPath );
-
- return $tmpFile;
+ return $tmpFiles;
}
/**
@@ -747,13 +747,13 @@ class FSFileBackend extends FileBackendStore {
/**
* Chmod a file, suppressing the warnings
*
- * @param $path string Absolute file system path
+ * @param string $path Absolute file system path
* @return bool Success
*/
protected function chmod( $path ) {
- wfSuppressWarnings();
+ $this->trapWarnings();
$ok = chmod( $path, $this->fileMode );
- wfRestoreWarnings();
+ $this->untrapWarnings();
return $ok;
}
@@ -779,7 +779,7 @@ class FSFileBackend extends FileBackendStore {
/**
* Clean up directory separators for the given OS
*
- * @param $path string FS path
+ * @param string $path FS path
* @return string
*/
protected function cleanPathSlashes( $path ) {
@@ -789,12 +789,11 @@ class FSFileBackend extends FileBackendStore {
/**
* Listen for E_WARNING errors and track whether any happen
*
- * @return bool
+ * @return void
*/
protected function trapWarnings() {
$this->hadWarningErrors[] = false; // push to stack
set_error_handler( array( $this, 'handleWarning' ), E_WARNING );
- return false; // invoke normal PHP error handler
}
/**
@@ -808,9 +807,12 @@ class FSFileBackend extends FileBackendStore {
}
/**
+ * @param $errno integer
+ * @param $errstr string
* @return bool
*/
- private function handleWarning() {
+ private function handleWarning( $errno, $errstr ) {
+ wfDebugLog( 'FSFileBackend', $errstr ); // more detailed error logging
$this->hadWarningErrors[count( $this->hadWarningErrors ) - 1] = true;
return true; // suppress from PHP handler
}
@@ -855,16 +857,19 @@ abstract class FSFileBackendList implements Iterator {
protected $params = array();
/**
- * @param $dir string file system directory
+ * @param string $dir file system directory
* @param $params array
*/
public function __construct( $dir, array $params ) {
- $dir = realpath( $dir ); // normalize
- $this->suffixStart = strlen( $dir ) + 1; // size of "path/to/dir/"
+ $path = realpath( $dir ); // normalize
+ if( $path === false ) {
+ $path = $dir;
+ }
+ $this->suffixStart = strlen( $path ) + 1; // size of "path/to/dir/"
$this->params = $params;
try {
- $this->iter = $this->initIterator( $dir );
+ $this->iter = $this->initIterator( $path );
} catch ( UnexpectedValueException $e ) {
$this->iter = null; // bad permissions? deleted?
}
@@ -873,7 +878,7 @@ abstract class FSFileBackendList implements Iterator {
/**
* Return an appropriate iterator object to wrap
*
- * @param $dir string file system directory
+ * @param string $dir file system directory
* @return Iterator
*/
protected function initIterator( $dir ) {
@@ -956,8 +961,12 @@ abstract class FSFileBackendList implements Iterator {
* @param $path string
* @return string
*/
- protected function getRelPath( $path ) {
- return strtr( substr( realpath( $path ), $this->suffixStart ), '\\', '/' );
+ protected function getRelPath( $dir ) {
+ $path = realpath( $dir );
+ if( $path === false ) {
+ $path = $dir;
+ }
+ return strtr( substr( $path, $this->suffixStart ), '\\', '/' );
}
}
diff --git a/includes/filebackend/FileBackend.php b/includes/filebackend/FileBackend.php
index 76c761b0..f40b8c16 100644
--- a/includes/filebackend/FileBackend.php
+++ b/includes/filebackend/FileBackend.php
@@ -37,14 +37,18 @@
* Outside callers can assume that all backends will have these functions.
*
* All "storage paths" are of the format "mwstore://<backend>/<container>/<path>".
- * The "<path>" portion is a relative path that uses UNIX file system (FS)
- * notation, though any particular backend may not actually be using a local
- * filesystem. Therefore, the relative paths are only virtual.
+ * The "backend" portion is unique name for MediaWiki to refer to a backend, while
+ * the "container" portion is a top-level directory of the backend. The "path" portion
+ * is a relative path that uses UNIX file system (FS) notation, though any particular
+ * backend may not actually be using a local filesystem. Therefore, the relative paths
+ * are only virtual.
*
* Backend contents are stored under wiki-specific container names by default.
- * For legacy reasons, this has no effect for the FS backend class, and per-wiki
- * segregation must be done by setting the container paths appropriately.
+ * Global (qualified) backends are achieved by configuring the "wiki ID" to a constant.
+ * For legacy reasons, the FSFileBackend class allows manually setting the paths of
+ * containers to ones that do not respect the "wiki ID".
*
+ * In key/value stores, the container is the only hierarchy (the rest is emulated).
* FS-based backends are somewhat more restrictive due to the existence of real
* directory files; a regular file cannot have the same name as a directory. Other
* backends with virtual directories may not have this limitation. Callers should
@@ -75,9 +79,13 @@ abstract class FileBackend {
* $config includes:
* - name : The unique name of this backend.
* This should consist of alphanumberic, '-', and '_' characters.
- * This name should not be changed after use.
- * - wikiId : Prefix to container names that is unique to this wiki.
+ * This name should not be changed after use (e.g. with journaling).
+ * Note that the name is *not* used in actual container names.
+ * - wikiId : Prefix to container names that is unique to this backend.
+ * If not provided, this defaults to the current wiki ID.
* It should only consist of alphanumberic, '-', and '_' characters.
+ * This ID is what avoids collisions if multiple logical backends
+ * use the same storage system, so this should be set carefully.
* - lockManager : Registered name of a file lock manager to use.
* - fileJournal : File journal configuration; see FileJournal::factory().
* Journals simply log changes to files stored in the backend.
@@ -100,7 +108,7 @@ abstract class FileBackend {
: wfWikiID(); // e.g. "my_wiki-en_"
$this->lockManager = ( $config['lockManager'] instanceof LockManager )
? $config['lockManager']
- : LockManagerGroup::singleton()->get( $config['lockManager'] );
+ : LockManagerGroup::singleton( $this->wikiId )->get( $config['lockManager'] );
$this->fileJournal = isset( $config['fileJournal'] )
? ( ( $config['fileJournal'] instanceof FileJournal )
? $config['fileJournal']
@@ -129,7 +137,8 @@ abstract class FileBackend {
}
/**
- * Get the wiki identifier used for this backend (possibly empty)
+ * Get the wiki identifier used for this backend (possibly empty).
+ * Note that this might *not* be in the same format as wfWikiID().
*
* @return string
* @since 1.20
@@ -171,6 +180,7 @@ abstract class FileBackend {
* - copy
* - move
* - delete
+ * - describe (since 1.21)
* - null
*
* a) Create a new file in storage with the contents of a string
@@ -181,7 +191,8 @@ abstract class FileBackend {
* 'content' => <string of new file contents>,
* 'overwrite' => <boolean>,
* 'overwriteSame' => <boolean>,
- * 'disposition' => <Content-Disposition header value>
+ * 'disposition' => <Content-Disposition header value>,
+ * 'headers' => <HTTP header name/value map> # since 1.21
* );
* @endcode
*
@@ -193,7 +204,8 @@ abstract class FileBackend {
* 'dst' => <storage path>,
* 'overwrite' => <boolean>,
* 'overwriteSame' => <boolean>,
- * 'disposition' => <Content-Disposition header value>
+ * 'disposition' => <Content-Disposition header value>,
+ * 'headers' => <HTTP header name/value map> # since 1.21
* )
* @endcode
*
@@ -205,6 +217,7 @@ abstract class FileBackend {
* 'dst' => <storage path>,
* 'overwrite' => <boolean>,
* 'overwriteSame' => <boolean>,
+ * 'ignoreMissingSource' => <boolean>, # since 1.21
* 'disposition' => <Content-Disposition header value>
* )
* @endcode
@@ -217,6 +230,7 @@ abstract class FileBackend {
* 'dst' => <storage path>,
* 'overwrite' => <boolean>,
* 'overwriteSame' => <boolean>,
+ * 'ignoreMissingSource' => <boolean>, # since 1.21
* 'disposition' => <Content-Disposition header value>
* )
* @endcode
@@ -230,7 +244,17 @@ abstract class FileBackend {
* )
* @endcode
*
- * f) Do nothing (no-op)
+ * f) Update metadata for a file within storage
+ * @code
+ * array(
+ * 'op' => 'describe',
+ * 'src' => <storage path>,
+ * 'disposition' => <Content-Disposition header value>,
+ * 'headers' => <HTTP header name/value map>
+ * )
+ * @endcode
+ *
+ * g) Do nothing (no-op)
* @code
* array(
* 'op' => 'null',
@@ -244,28 +268,32 @@ abstract class FileBackend {
* - overwriteSame : An error will not be given if a file already
* exists at the destination that has the same
* contents as the new contents to be written there.
- * - disposition : When supplied, the backend will add a Content-Disposition
+ * - disposition : If supplied, the backend will return a Content-Disposition
* header when GETs/HEADs of the destination file are made.
- * Backends that don't support file metadata will ignore this.
- * See http://tools.ietf.org/html/rfc6266 (since 1.20).
+ * Backends that don't support metadata ignore this.
+ * See http://tools.ietf.org/html/rfc6266. (since 1.20)
+ * - headers : If supplied, the backend will return these headers when
+ * GETs/HEADs of the destination file are made. Header values
+ * should be smaller than 256 bytes, often options or numbers.
+ * Existing headers will remain, but these will replace any
+ * conflicting previous headers, and headers will be removed
+ * if they are set to an empty string.
+ * Backends that don't support metadata ignore this. (since 1.21)
*
* $opts is an associative of boolean flags, including:
* - force : Operation precondition errors no longer trigger an abort.
* Any remaining operations are still attempted. Unexpected
- * failures may still cause remaning operations to be aborted.
+ * failures may still cause remaining operations to be aborted.
* - nonLocking : No locks are acquired for the operations.
* This can increase performance for non-critical writes.
* This has no effect unless the 'force' flag is set.
- * - allowStale : Don't require the latest available data.
- * This can increase performance for non-critical writes.
- * This has no effect unless the 'force' flag is set.
* - nonJournaled : Don't log this operation batch in the file journal.
* This limits the ability of recovery scripts.
* - parallelize : Try to do operations in parallel when possible.
- * - bypassReadOnly : Allow writes in read-only mode (since 1.20).
+ * - bypassReadOnly : Allow writes in read-only mode. (since 1.20)
* - preserveCache : Don't clear the process cache before checking files.
* This should only be used if all entries in the process
- * cache were added after the files were already locked (since 1.20).
+ * cache were added after the files were already locked. (since 1.20)
*
* @remarks Remarks on locking:
* File system paths given to operations should refer to files that are
@@ -282,8 +310,8 @@ abstract class FileBackend {
* - a) unexpected operation errors occurred (network partitions, disk full...)
* - b) significant operation errors occurred and 'force' was not set
*
- * @param $ops Array List of operations to execute in order
- * @param $opts Array Batch operation options
+ * @param array $ops List of operations to execute in order
+ * @param array $opts Batch operation options
* @return Status
*/
final public function doOperations( array $ops, array $opts = array() ) {
@@ -292,18 +320,8 @@ abstract class FileBackend {
}
if ( empty( $opts['force'] ) ) { // sanity
unset( $opts['nonLocking'] );
- unset( $opts['allowStale'] );
- }
- $opts['concurrency'] = 1; // off
- if ( $this->parallelize === 'implicit' ) {
- if ( !isset( $opts['parallelize'] ) || $opts['parallelize'] ) {
- $opts['concurrency'] = $this->concurrency;
- }
- } elseif ( $this->parallelize === 'explicit' ) {
- if ( !empty( $opts['parallelize'] ) ) {
- $opts['concurrency'] = $this->concurrency;
- }
}
+ $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
return $this->doOperationsInternal( $ops, $opts );
}
@@ -319,8 +337,8 @@ abstract class FileBackend {
*
* @see FileBackend::doOperations()
*
- * @param $op Array Operation
- * @param $opts Array Operation options
+ * @param array $op Operation
+ * @param array $opts Operation options
* @return Status
*/
final public function doOperation( array $op, array $opts = array() ) {
@@ -333,8 +351,8 @@ abstract class FileBackend {
*
* @see FileBackend::doOperation()
*
- * @param $params Array Operation parameters
- * @param $opts Array Operation options
+ * @param array $params Operation parameters
+ * @param array $opts Operation options
* @return Status
*/
final public function create( array $params, array $opts = array() ) {
@@ -347,8 +365,8 @@ abstract class FileBackend {
*
* @see FileBackend::doOperation()
*
- * @param $params Array Operation parameters
- * @param $opts Array Operation options
+ * @param array $params Operation parameters
+ * @param array $opts Operation options
* @return Status
*/
final public function store( array $params, array $opts = array() ) {
@@ -361,8 +379,8 @@ abstract class FileBackend {
*
* @see FileBackend::doOperation()
*
- * @param $params Array Operation parameters
- * @param $opts Array Operation options
+ * @param array $params Operation parameters
+ * @param array $opts Operation options
* @return Status
*/
final public function copy( array $params, array $opts = array() ) {
@@ -375,8 +393,8 @@ abstract class FileBackend {
*
* @see FileBackend::doOperation()
*
- * @param $params Array Operation parameters
- * @param $opts Array Operation options
+ * @param array $params Operation parameters
+ * @param array $opts Operation options
* @return Status
*/
final public function move( array $params, array $opts = array() ) {
@@ -389,8 +407,8 @@ abstract class FileBackend {
*
* @see FileBackend::doOperation()
*
- * @param $params Array Operation parameters
- * @param $opts Array Operation options
+ * @param array $params Operation parameters
+ * @param array $opts Operation options
* @return Status
*/
final public function delete( array $params, array $opts = array() ) {
@@ -398,6 +416,21 @@ abstract class FileBackend {
}
/**
+ * Performs a single describe operation.
+ * This sets $params['op'] to 'describe' and passes it to doOperation().
+ *
+ * @see FileBackend::doOperation()
+ *
+ * @param array $params Operation parameters
+ * @param array $opts Operation options
+ * @return Status
+ * @since 1.21
+ */
+ final public function describe( array $params, array $opts = array() ) {
+ return $this->doOperation( array( 'op' => 'describe' ) + $params, $opts );
+ }
+
+ /**
* Perform a set of independent file operations on some files.
*
* This does no locking, nor journaling, and possibly no stat calls.
@@ -410,6 +443,7 @@ abstract class FileBackend {
* - copy
* - move
* - delete
+ * - describe (since 1.21)
* - null
*
* a) Create a new file in storage with the contents of a string
@@ -418,36 +452,44 @@ abstract class FileBackend {
* 'op' => 'create',
* 'dst' => <storage path>,
* 'content' => <string of new file contents>,
- * 'disposition' => <Content-Disposition header value>
+ * 'disposition' => <Content-Disposition header value>,
+ * 'headers' => <HTTP header name/value map> # since 1.21
* )
* @endcode
+ *
* b) Copy a file system file into storage
* @code
* array(
* 'op' => 'store',
* 'src' => <file system path>,
* 'dst' => <storage path>,
- * 'disposition' => <Content-Disposition header value>
+ * 'disposition' => <Content-Disposition header value>,
+ * 'headers' => <HTTP header name/value map> # since 1.21
* )
* @endcode
+ *
* c) Copy a file within storage
* @code
* array(
* 'op' => 'copy',
* 'src' => <storage path>,
* 'dst' => <storage path>,
+ * 'ignoreMissingSource' => <boolean>, # since 1.21
* 'disposition' => <Content-Disposition header value>
* )
* @endcode
+ *
* d) Move a file within storage
* @code
* array(
* 'op' => 'move',
* 'src' => <storage path>,
* 'dst' => <storage path>,
+ * 'ignoreMissingSource' => <boolean>, # since 1.21
* 'disposition' => <Content-Disposition header value>
* )
* @endcode
+ *
* e) Delete a file within storage
* @code
* array(
@@ -456,7 +498,18 @@ abstract class FileBackend {
* 'ignoreMissingSource' => <boolean>
* )
* @endcode
- * f) Do nothing (no-op)
+ *
+ * f) Update metadata for a file within storage
+ * @code
+ * array(
+ * 'op' => 'describe',
+ * 'src' => <storage path>,
+ * 'disposition' => <Content-Disposition header value>,
+ * 'headers' => <HTTP header name/value map>
+ * )
+ * @endcode
+ *
+ * g) Do nothing (no-op)
* @code
* array(
* 'op' => 'null',
@@ -470,6 +523,13 @@ abstract class FileBackend {
* header when GETs/HEADs of the destination file are made.
* Backends that don't support file metadata will ignore this.
* See http://tools.ietf.org/html/rfc6266 (since 1.20).
+ * - headers : If supplied with a header name/value map, the backend will
+ * reply with these headers when GETs/HEADs of the destination
+ * file are made. Header values should be smaller than 256 bytes.
+ * Existing headers will remain, but these will replace any
+ * conflicting previous headers, and headers will be removed
+ * if they are set to an empty string.
+ * Backends that don't support metadata ignore this. (since 1.21)
*
* $opts is an associative of boolean flags, including:
* - bypassReadOnly : Allow writes in read-only mode (since 1.20)
@@ -480,8 +540,8 @@ abstract class FileBackend {
* will reflect each operation attempted for the given files. The status will be
* considered "OK" as long as no fatal errors occurred.
*
- * @param $ops Array Set of operations to execute
- * @param $opts Array Batch operation options
+ * @param array $ops Set of operations to execute
+ * @param array $opts Batch operation options
* @return Status
* @since 1.20
*/
@@ -492,6 +552,7 @@ abstract class FileBackend {
foreach ( $ops as &$op ) {
$op['overwrite'] = true; // avoids RTTs in key/value stores
}
+ $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
return $this->doQuickOperationsInternal( $ops );
}
@@ -507,7 +568,7 @@ abstract class FileBackend {
*
* @see FileBackend::doQuickOperations()
*
- * @param $op Array Operation
+ * @param array $op Operation
* @return Status
* @since 1.20
*/
@@ -521,7 +582,7 @@ abstract class FileBackend {
*
* @see FileBackend::doQuickOperation()
*
- * @param $params Array Operation parameters
+ * @param array $params Operation parameters
* @return Status
* @since 1.20
*/
@@ -535,7 +596,7 @@ abstract class FileBackend {
*
* @see FileBackend::doQuickOperation()
*
- * @param $params Array Operation parameters
+ * @param array $params Operation parameters
* @return Status
* @since 1.20
*/
@@ -549,7 +610,7 @@ abstract class FileBackend {
*
* @see FileBackend::doQuickOperation()
*
- * @param $params Array Operation parameters
+ * @param array $params Operation parameters
* @return Status
* @since 1.20
*/
@@ -563,7 +624,7 @@ abstract class FileBackend {
*
* @see FileBackend::doQuickOperation()
*
- * @param $params Array Operation parameters
+ * @param array $params Operation parameters
* @return Status
* @since 1.20
*/
@@ -577,7 +638,7 @@ abstract class FileBackend {
*
* @see FileBackend::doQuickOperation()
*
- * @param $params Array Operation parameters
+ * @param array $params Operation parameters
* @return Status
* @since 1.20
*/
@@ -586,15 +647,30 @@ abstract class FileBackend {
}
/**
+ * Performs a single quick describe operation.
+ * This sets $params['op'] to 'describe' and passes it to doQuickOperation().
+ *
+ * @see FileBackend::doQuickOperation()
+ *
+ * @param array $params Operation parameters
+ * @return Status
+ * @since 1.21
+ */
+ final public function quickDescribe( array $params ) {
+ return $this->doQuickOperation( array( 'op' => 'describe' ) + $params );
+ }
+
+ /**
* Concatenate a list of storage files into a single file system file.
* The target path should refer to a file that is already locked or
* otherwise safe from modification from other processes. Normally,
* the file will be a new temp file, which should be adequate.
*
- * @param $params Array Operation parameters
+ * @param array $params Operation parameters
* $params include:
- * - srcs : ordered source storage paths (e.g. chunk1, chunk2, ...)
- * - dst : file system path to 0-byte temp file
+ * - srcs : ordered source storage paths (e.g. chunk1, chunk2, ...)
+ * - dst : file system path to 0-byte temp file
+ * - parallelize : try to do operations in parallel when possible
* @return Status
*/
abstract public function concatenate( array $params );
@@ -608,7 +684,7 @@ abstract class FileBackend {
* except they are only applied *if* the directory/container had to be created.
* These flags should always be set for directories that have private files.
*
- * @param $params Array
+ * @param array $params
* $params include:
* - dir : storage directory
* - noAccess : try to deny file access (since 1.20)
@@ -620,6 +696,7 @@ abstract class FileBackend {
if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
}
+ $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
return $this->doPrepare( $params );
}
@@ -635,7 +712,7 @@ abstract class FileBackend {
* access to the storage user representing end-users in web requests.
* This is not guaranteed to actually do anything.
*
- * @param $params Array
+ * @param array $params
* $params include:
* - dir : storage directory
* - noAccess : try to deny file access
@@ -647,6 +724,7 @@ abstract class FileBackend {
if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
}
+ $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
return $this->doSecure( $params );
}
@@ -662,7 +740,7 @@ abstract class FileBackend {
* access to the storage user representing end-users in web requests.
* This essentially can undo the result of secure() calls.
*
- * @param $params Array
+ * @param array $params
* $params include:
* - dir : storage directory
* - access : try to allow file access
@@ -675,6 +753,7 @@ abstract class FileBackend {
if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
}
+ $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
return $this->doPublish( $params );
}
@@ -688,7 +767,7 @@ abstract class FileBackend {
* Backends using key/value stores may do nothing unless the directory
* is that of an empty container, in which case it will be deleted.
*
- * @param $params Array
+ * @param array $params
* $params include:
* - dir : storage directory
* - recursive : recursively delete empty subdirectories first (since 1.20)
@@ -699,6 +778,7 @@ abstract class FileBackend {
if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
}
+ $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
return $this->doClean( $params );
}
@@ -708,10 +788,25 @@ abstract class FileBackend {
abstract protected function doClean( array $params );
/**
+ * Enter file operation scope.
+ * This just makes PHP ignore user aborts/disconnects until the return
+ * value leaves scope. This returns null and does nothing in CLI mode.
+ *
+ * @return ScopedCallback|null
+ */
+ final protected function getScopedPHPBehaviorForOps() {
+ if ( php_sapi_name() != 'cli' ) { // http://bugs.php.net/bug.php?id=47540
+ $old = ignore_user_abort( true ); // avoid half-finished operations
+ return new ScopedCallback( function() use ( $old ) { ignore_user_abort( $old ); } );
+ }
+ return null;
+ }
+
+ /**
* Check if a file exists at a storage path in the backend.
* This returns false if only a directory exists at the path.
*
- * @param $params Array
+ * @param array $params
* $params include:
* - src : source storage path
* - latest : use the latest available data
@@ -722,7 +817,7 @@ abstract class FileBackend {
/**
* Get the last-modified timestamp of the file at a storage path.
*
- * @param $params Array
+ * @param array $params
* $params include:
* - src : source storage path
* - latest : use the latest available data
@@ -734,18 +829,40 @@ abstract class FileBackend {
* Get the contents of a file at a storage path in the backend.
* This should be avoided for potentially large files.
*
- * @param $params Array
+ * @param array $params
* $params include:
* - src : source storage path
* - latest : use the latest available data
* @return string|bool Returns false on failure
*/
- abstract public function getFileContents( array $params );
+ final public function getFileContents( array $params ) {
+ $contents = $this->getFileContentsMulti(
+ array( 'srcs' => array( $params['src'] ) ) + $params );
+
+ return $contents[$params['src']];
+ }
+
+ /**
+ * Like getFileContents() except it takes an array of storage paths
+ * and returns a map of storage paths to strings (or null on failure).
+ * The map keys (paths) are in the same order as the provided list of paths.
+ *
+ * @see FileBackend::getFileContents()
+ *
+ * @param array $params
+ * $params include:
+ * - srcs : list of source storage paths
+ * - latest : use the latest available data
+ * - parallelize : try to do operations in parallel when possible
+ * @return Array Map of (path name => string or false on failure)
+ * @since 1.20
+ */
+ abstract public function getFileContentsMulti( array $params );
/**
* Get the size (bytes) of a file at a storage path in the backend.
*
- * @param $params Array
+ * @param array $params
* $params include:
* - src : source storage path
* - latest : use the latest available data
@@ -761,7 +878,7 @@ abstract class FileBackend {
* - size : the file size (bytes)
* Additional values may be included for internal use only.
*
- * @param $params Array
+ * @param array $params
* $params include:
* - src : source storage path
* - latest : use the latest available data
@@ -772,7 +889,7 @@ abstract class FileBackend {
/**
* Get a SHA-1 hash of the file at a storage path in the backend.
*
- * @param $params Array
+ * @param array $params
* $params include:
* - src : source storage path
* - latest : use the latest available data
@@ -782,13 +899,13 @@ abstract class FileBackend {
/**
* Get the properties of the file at a storage path in the backend.
- * Returns FSFile::placeholderProps() on failure.
+ * This gives the result of FSFile::getProps() on a local copy of the file.
*
- * @param $params Array
+ * @param array $params
* $params include:
* - src : source storage path
* - latest : use the latest available data
- * @return Array
+ * @return Array Returns FSFile::placeholderProps() on failure
*/
abstract public function getFileProps( array $params );
@@ -799,7 +916,7 @@ abstract class FileBackend {
* will be sent if streaming began, while none will be sent otherwise.
* Implementations should flush the output buffer before sending data.
*
- * @param $params Array
+ * @param array $params
* $params include:
* - src : source storage path
* - headers : list of additional HTTP headers to send on success
@@ -821,26 +938,89 @@ abstract class FileBackend {
* In that later case, there are copies of the file that must stay in sync.
* Additionally, further calls to this function may return the same file.
*
- * @param $params Array
+ * @param array $params
* $params include:
* - src : source storage path
* - latest : use the latest available data
* @return FSFile|null Returns null on failure
*/
- abstract public function getLocalReference( array $params );
+ final public function getLocalReference( array $params ) {
+ $fsFiles = $this->getLocalReferenceMulti(
+ array( 'srcs' => array( $params['src'] ) ) + $params );
+
+ return $fsFiles[$params['src']];
+ }
+
+ /**
+ * Like getLocalReference() except it takes an array of storage paths
+ * and returns a map of storage paths to FSFile objects (or null on failure).
+ * The map keys (paths) are in the same order as the provided list of paths.
+ *
+ * @see FileBackend::getLocalReference()
+ *
+ * @param array $params
+ * $params include:
+ * - srcs : list of source storage paths
+ * - latest : use the latest available data
+ * - parallelize : try to do operations in parallel when possible
+ * @return Array Map of (path name => FSFile or null on failure)
+ * @since 1.20
+ */
+ abstract public function getLocalReferenceMulti( array $params );
/**
* Get a local copy on disk of the file at a storage path in the backend.
* The temporary copy will have the same file extension as the source.
* Temporary files may be purged when the file object falls out of scope.
*
- * @param $params Array
+ * @param array $params
* $params include:
* - src : source storage path
* - latest : use the latest available data
* @return TempFSFile|null Returns null on failure
*/
- abstract public function getLocalCopy( array $params );
+ final public function getLocalCopy( array $params ) {
+ $tmpFiles = $this->getLocalCopyMulti(
+ array( 'srcs' => array( $params['src'] ) ) + $params );
+
+ return $tmpFiles[$params['src']];
+ }
+
+ /**
+ * Like getLocalCopy() except it takes an array of storage paths and
+ * returns a map of storage paths to TempFSFile objects (or null on failure).
+ * The map keys (paths) are in the same order as the provided list of paths.
+ *
+ * @see FileBackend::getLocalCopy()
+ *
+ * @param array $params
+ * $params include:
+ * - srcs : list of source storage paths
+ * - latest : use the latest available data
+ * - parallelize : try to do operations in parallel when possible
+ * @return Array Map of (path name => TempFSFile or null on failure)
+ * @since 1.20
+ */
+ abstract public function getLocalCopyMulti( array $params );
+
+ /**
+ * Return an HTTP URL to a given file that requires no authentication to use.
+ * The URL may be pre-authenticated (via some token in the URL) and temporary.
+ * This will return null if the backend cannot make an HTTP URL for the file.
+ *
+ * This is useful for key/value stores when using scripts that seek around
+ * large files and those scripts (and the backend) support HTTP Range headers.
+ * Otherwise, one would need to use getLocalReference(), which involves loading
+ * the entire file on to local disk.
+ *
+ * @param array $params
+ * $params include:
+ * - src : source storage path
+ * - ttl : lifetime (seconds) if pre-authenticated; default is 1 day
+ * @return string|null
+ * @since 1.21
+ */
+ abstract public function getFileHttpUrl( array $params );
/**
* Check if a directory exists at a given storage path.
@@ -930,7 +1110,7 @@ abstract class FileBackend {
* Preload persistent file stat and property cache into in-process cache.
* This should be used when stat calls will be made on a known list of a many files.
*
- * @param $paths Array Storage paths
+ * @param array $paths Storage paths
* @return void
*/
public function preloadCache( array $paths ) {}
@@ -939,7 +1119,7 @@ abstract class FileBackend {
* Invalidate any in-process file stat and property cache.
* If $paths is given, then only the cache for those files will be cleared.
*
- * @param $paths Array Storage paths (optional)
+ * @param array $paths Storage paths (optional)
* @return void
*/
public function clearCache( array $paths = null ) {}
@@ -950,7 +1130,7 @@ abstract class FileBackend {
*
* Callers should consider using getScopedFileLocks() instead.
*
- * @param $paths Array Storage paths
+ * @param array $paths Storage paths
* @param $type integer LockManager::LOCK_* constant
* @return Status
*/
@@ -961,7 +1141,7 @@ abstract class FileBackend {
/**
* Unlock the files at the given storage paths in the backend.
*
- * @param $paths Array Storage paths
+ * @param array $paths Storage paths
* @param $type integer LockManager::LOCK_* constant
* @return Status
*/
@@ -977,7 +1157,7 @@ abstract class FileBackend {
* Once the return value goes out scope, the locks will be released and
* the status updated. Unlock fatals will not change the status "OK" value.
*
- * @param $paths Array Storage paths
+ * @param array $paths Storage paths
* @param $type integer LockManager::LOCK_* constant
* @param $status Status Status to update on lock/unlock
* @return ScopedLock|null Returns null on failure
@@ -997,7 +1177,7 @@ abstract class FileBackend {
*
* @see FileBackend::doOperations()
*
- * @param $ops Array List of file operations to FileBackend::doOperations()
+ * @param array $ops List of file operations to FileBackend::doOperations()
* @param $status Status Status to update on lock/unlock
* @return Array List of ScopedFileLocks or null values
* @since 1.20
@@ -1016,6 +1196,17 @@ abstract class FileBackend {
}
/**
+ * Get the storage path for the given container for this backend
+ *
+ * @param string $container Container name
+ * @return string Storage path
+ * @since 1.21
+ */
+ final public function getContainerStoragePath( $container ) {
+ return $this->getRootStoragePath() . "/{$container}";
+ }
+
+ /**
* Get the file journal object for this backend
*
* @return FileJournal
@@ -1088,7 +1279,7 @@ abstract class FileBackend {
*/
final public static function parentStoragePath( $storagePath ) {
$storagePath = dirname( $storagePath );
- list( $b, $cont, $rel ) = self::splitStoragePath( $storagePath );
+ list( , , $rel ) = self::splitStoragePath( $storagePath );
return ( $rel === null ) ? null : $storagePath;
}
@@ -1117,8 +1308,9 @@ abstract class FileBackend {
/**
* Build a Content-Disposition header value per RFC 6266.
*
- * @param $type string One of (attachment, inline)
- * @param $filename string Suggested file name (should not contain slashes)
+ * @param string $type One of (attachment, inline)
+ * @param string $filename Suggested file name (should not contain slashes)
+ * @throws MWException
* @return string
* @since 1.20
*/
@@ -1145,7 +1337,7 @@ abstract class FileBackend {
*
* This uses the same traversal protection as Title::secureAndSplit().
*
- * @param $path string Storage path relative to a container
+ * @param string $path Storage path relative to a container
* @return string|null
*/
final protected static function normalizeContainerPath( $path ) {
diff --git a/includes/filebackend/FileBackendGroup.php b/includes/filebackend/FileBackendGroup.php
index 8bbc96d0..d790a996 100644
--- a/includes/filebackend/FileBackendGroup.php
+++ b/includes/filebackend/FileBackendGroup.php
@@ -87,6 +87,9 @@ class FileBackendGroup {
$thumbDir = isset( $info['thumbDir'] )
? $info['thumbDir']
: "{$directory}/thumb";
+ $transcodedDir = isset( $info['transcodedDir'] )
+ ? $info['transcodedDir']
+ : "{$directory}/transcoded";
$fileMode = isset( $info['fileMode'] )
? $info['fileMode']
: 0644;
@@ -98,6 +101,7 @@ class FileBackendGroup {
'containerPaths' => array(
"{$repoName}-public" => "{$directory}",
"{$repoName}-thumb" => $thumbDir,
+ "{$repoName}-transcoded" => $transcodedDir,
"{$repoName}-deleted" => $deletedDir,
"{$repoName}-temp" => "{$directory}/temp"
),
@@ -122,7 +126,9 @@ class FileBackendGroup {
throw new MWException( "Cannot register a backend with no name." );
}
$name = $config['name'];
- if ( !isset( $config['class'] ) ) {
+ if ( isset( $this->backends[$name] ) ) {
+ throw new MWException( "Backend with name `{$name}` already registered." );
+ } elseif ( !isset( $config['class'] ) ) {
throw new MWException( "Cannot register backend `{$name}` with no class." );
}
$class = $config['class'];
@@ -178,7 +184,7 @@ class FileBackendGroup {
* @return FileBackend|null Backend or null on failure
*/
public function backendFromPath( $storagePath ) {
- list( $backend, $c, $p ) = FileBackend::splitStoragePath( $storagePath );
+ list( $backend, , ) = FileBackend::splitStoragePath( $storagePath );
if ( $backend !== null && isset( $this->backends[$backend] ) ) {
return $this->get( $backend );
}
diff --git a/includes/filebackend/FileBackendMultiWrite.php b/includes/filebackend/FileBackendMultiWrite.php
index 4be03231..939315d1 100644
--- a/includes/filebackend/FileBackendMultiWrite.php
+++ b/includes/filebackend/FileBackendMultiWrite.php
@@ -62,7 +62,7 @@ class FileBackendMultiWrite extends FileBackend {
* Additional $config params include:
* - backends : Array of backend config and multi-backend settings.
* Each value is the config used in the constructor of a
- * FileBackendStore class, but with these additional settings:
+ * FileBackendStore class, but with these additional settings:
* - class : The name of the backend class
* - isMultiMaster : This must be set for one backend.
* - template: : If given a backend name, this will use
@@ -179,10 +179,11 @@ class FileBackendMultiWrite extends FileBackend {
// Actually attempt the operation batch on the master backend...
$masterStatus = $mbe->doOperations( $realOps, $opts );
$status->merge( $masterStatus );
- // Propagate the operations to the clone backends if there were no fatal errors.
- // If $ops only had one operation, this might avoid backend inconsistencies.
- // This also avoids inconsistency for expected errors (like "file already exists").
- if ( !count( $masterStatus->getErrorsArray() ) ) {
+ // Propagate the operations to the clone backends if there were no unexpected errors
+ // and if there were either no expected errors or if the 'force' option was used.
+ // However, if nothing succeeded at all, then don't replicate any of the operations.
+ // If $ops only had one operation, this might avoid backend sync inconsistencies.
+ if ( $masterStatus->isOK() && $masterStatus->successCount > 0 ) {
foreach ( $this->backends as $index => $backend ) {
if ( $index !== $this->masterIndex ) { // not done already
$realOps = $this->substOpBatchPaths( $ops, $backend );
@@ -203,7 +204,7 @@ class FileBackendMultiWrite extends FileBackend {
/**
* Check that a set of files are consistent across all internal backends
*
- * @param $paths Array List of storage paths
+ * @param array $paths List of storage paths
* @return Status
*/
public function consistencyCheck( array $paths ) {
@@ -269,7 +270,7 @@ class FileBackendMultiWrite extends FileBackend {
/**
* Check that a set of file paths are usable across all internal backends
*
- * @param $paths Array List of storage paths
+ * @param array $paths List of storage paths
* @return Status
*/
public function accessibilityCheck( array $paths ) {
@@ -294,7 +295,7 @@ class FileBackendMultiWrite extends FileBackend {
* Check that a set of files are consistent across all internal backends
* and re-synchronize those files againt the "multi master" if needed.
*
- * @param $paths Array List of storage paths
+ * @param array $paths List of storage paths
* @return Status
*/
public function resyncFiles( array $paths ) {
@@ -302,8 +303,8 @@ class FileBackendMultiWrite extends FileBackend {
$mBackend = $this->backends[$this->masterIndex];
foreach ( $paths as $path ) {
- $mPath = $this->substPaths( $path, $mBackend );
- $mSha1 = $mBackend->getFileSha1Base36( array( 'src' => $mPath ) );
+ $mPath = $this->substPaths( $path, $mBackend );
+ $mSha1 = $mBackend->getFileSha1Base36( array( 'src' => $mPath ) );
$mExist = $mBackend->fileExists( array( 'src' => $mPath ) );
// Check if the master backend is available...
if ( $mExist === null ) {
@@ -335,14 +336,20 @@ class FileBackendMultiWrite extends FileBackend {
/**
* Get a list of file storage paths to read or write for a list of operations
*
- * @param $ops Array Same format as doOperations()
+ * @param array $ops Same format as doOperations()
* @return Array List of storage paths to files (does not include directories)
*/
protected function fileStoragePathsForOps( array $ops ) {
$paths = array();
foreach ( $ops as $op ) {
if ( isset( $op['src'] ) ) {
- $paths[] = $op['src'];
+ // For things like copy/move/delete with "ignoreMissingSource" and there
+ // is no source file, nothing should happen and there should be no errors.
+ if ( empty( $op['ignoreMissingSource'] )
+ || $this->fileExists( array( 'src' => $op['src'] ) ) )
+ {
+ $paths[] = $op['src'];
+ }
}
if ( isset( $op['srcs'] ) ) {
$paths = array_merge( $paths, $op['srcs'] );
@@ -351,14 +358,14 @@ class FileBackendMultiWrite extends FileBackend {
$paths[] = $op['dst'];
}
}
- return array_unique( array_filter( $paths, 'FileBackend::isStoragePath' ) );
+ return array_values( array_unique( array_filter( $paths, 'FileBackend::isStoragePath' ) ) );
}
/**
* Substitute the backend name in storage path parameters
* for a set of operations with that of a given internal backend.
*
- * @param $ops Array List of file operation arrays
+ * @param array $ops List of file operation arrays
* @param $backend FileBackendStore
* @return Array
*/
@@ -379,7 +386,7 @@ class FileBackendMultiWrite extends FileBackend {
/**
* Same as substOpBatchPaths() but for a single operation
*
- * @param $ops array File operation array
+ * @param array $ops File operation array
* @param $backend FileBackendStore
* @return Array
*/
@@ -391,7 +398,7 @@ class FileBackendMultiWrite extends FileBackend {
/**
* Substitute the backend of storage paths with an internal backend's name
*
- * @param $paths Array|string List of paths or single string path
+ * @param array|string $paths List of paths or single string path
* @param $backend FileBackendStore
* @return Array|string
*/
@@ -406,7 +413,7 @@ class FileBackendMultiWrite extends FileBackend {
/**
* Substitute the backend of internal storage paths with the proxy backend's name
*
- * @param $paths Array|string List of paths or single string path
+ * @param array|string $paths List of paths or single string path
* @return Array|string
*/
protected function unsubstPaths( $paths ) {
@@ -446,11 +453,11 @@ class FileBackendMultiWrite extends FileBackend {
}
/**
- * @param $path string Storage path
+ * @param string $path Storage path
* @return bool Path container should have dir changes pushed to all backends
*/
protected function replicateContainerDirChanges( $path ) {
- list( $b, $shortCont, $r ) = self::splitStoragePath( $path );
+ list( , $shortCont, ) = self::splitStoragePath( $path );
return !in_array( $shortCont, $this->noPushDirConts );
}
@@ -535,6 +542,7 @@ class FileBackendMultiWrite extends FileBackend {
/**
* @see FileBackend::fileExists()
* @param $params array
+ * @return bool|null
*/
public function fileExists( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
@@ -572,13 +580,19 @@ class FileBackendMultiWrite extends FileBackend {
}
/**
- * @see FileBackend::getFileContents()
+ * @see FileBackend::getFileContentsMulti()
* @param $params array
* @return bool|string
*/
- public function getFileContents( array $params ) {
+ public function getFileContentsMulti( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
- return $this->backends[$this->masterIndex]->getFileContents( $realParams );
+ $contentsM = $this->backends[$this->masterIndex]->getFileContentsMulti( $realParams );
+
+ $contents = array(); // (path => FSFile) mapping using the proxy backend's name
+ foreach ( $contentsM as $path => $data ) {
+ $contents[$this->unsubstPaths( $path )] = $data;
+ }
+ return $contents;
}
/**
@@ -612,23 +626,44 @@ class FileBackendMultiWrite extends FileBackend {
}
/**
- * @see FileBackend::getLocalReference()
+ * @see FileBackend::getLocalReferenceMulti()
* @param $params array
* @return FSFile|null
*/
- public function getLocalReference( array $params ) {
+ public function getLocalReferenceMulti( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
- return $this->backends[$this->masterIndex]->getLocalReference( $realParams );
+ $fsFilesM = $this->backends[$this->masterIndex]->getLocalReferenceMulti( $realParams );
+
+ $fsFiles = array(); // (path => FSFile) mapping using the proxy backend's name
+ foreach ( $fsFilesM as $path => $fsFile ) {
+ $fsFiles[$this->unsubstPaths( $path )] = $fsFile;
+ }
+ return $fsFiles;
}
/**
- * @see FileBackend::getLocalCopy()
+ * @see FileBackend::getLocalCopyMulti()
* @param $params array
* @return null|TempFSFile
*/
- public function getLocalCopy( array $params ) {
+ public function getLocalCopyMulti( array $params ) {
+ $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+ $tempFilesM = $this->backends[$this->masterIndex]->getLocalCopyMulti( $realParams );
+
+ $tempFiles = array(); // (path => TempFSFile) mapping using the proxy backend's name
+ foreach ( $tempFilesM as $path => $tempFile ) {
+ $tempFiles[$this->unsubstPaths( $path )] = $tempFile;
+ }
+ return $tempFiles;
+ }
+
+ /**
+ * @see FileBackend::getFileHttpUrl()
+ * @return string|null
+ */
+ public function getFileHttpUrl( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
- return $this->backends[$this->masterIndex]->getLocalCopy( $realParams );
+ return $this->backends[$this->masterIndex]->getFileHttpUrl( $realParams );
}
/**
diff --git a/includes/filebackend/FileBackendStore.php b/includes/filebackend/FileBackendStore.php
index 083dfea9..3f1d1857 100644
--- a/includes/filebackend/FileBackendStore.php
+++ b/includes/filebackend/FileBackendStore.php
@@ -48,6 +48,8 @@ abstract class FileBackendStore extends FileBackend {
protected $maxFileSize = 4294967296; // integer bytes (4GiB)
+ const CACHE_TTL = 10; // integer; TTL in seconds for process cache entries
+
/**
* @see FileBackend::__construct()
*
@@ -55,8 +57,8 @@ abstract class FileBackendStore extends FileBackend {
*/
public function __construct( array $config ) {
parent::__construct( $config );
- $this->memCache = new EmptyBagOStuff(); // disabled by default
- $this->cheapCache = new ProcessCacheLRU( 300 );
+ $this->memCache = new EmptyBagOStuff(); // disabled by default
+ $this->cheapCache = new ProcessCacheLRU( 300 );
$this->expensiveCache = new ProcessCacheLRU( 5 );
}
@@ -72,8 +74,9 @@ abstract class FileBackendStore extends FileBackend {
}
/**
- * Check if a file can be created at a given storage path.
- * FS backends should check if the parent directory exists and the file is writable.
+ * Check if a file can be created or changed at a given storage path.
+ * FS backends should check if the parent directory exists, files can be
+ * written under it, and that any file already there is writable.
* Backends using key/value stores should check if the container exists.
*
* @param $storagePath string
@@ -83,18 +86,21 @@ abstract class FileBackendStore extends FileBackend {
/**
* Create a file in the backend with the given contents.
+ * This will overwrite any file that exists at the destination.
* Do not call this function from places outside FileBackend and FileOp.
*
* $params include:
- * - content : the raw file contents
- * - dst : destination storage path
- * - overwrite : overwrite any file that exists at the destination
- * - disposition : Content-Disposition header value for the destination
- * - async : Status will be returned immediately if supported.
- * If the status is OK, then its value field will be
- * set to a FileBackendStoreOpHandle object.
+ * - content : the raw file contents
+ * - dst : destination storage path
+ * - disposition : Content-Disposition header value for the destination
+ * - headers : HTTP header name/value map
+ * - async : Status will be returned immediately if supported.
+ * If the status is OK, then its value field will be
+ * set to a FileBackendStoreOpHandle object.
+ * - dstExists : Whether a file exists at the destination (optimization).
+ * Callers can use "false" if no existing file is being changed.
*
- * @param $params Array
+ * @param array $params
* @return Status
*/
final public function createInternal( array $params ) {
@@ -106,7 +112,7 @@ abstract class FileBackendStore extends FileBackend {
} else {
$status = $this->doCreateInternal( $params );
$this->clearCache( array( $params['dst'] ) );
- if ( !empty( $params['overwrite'] ) ) { // file possibly mutated
+ if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
$this->deleteFileCache( $params['dst'] ); // persistent cache
}
}
@@ -117,23 +123,27 @@ abstract class FileBackendStore extends FileBackend {
/**
* @see FileBackendStore::createInternal()
+ * @return Status
*/
abstract protected function doCreateInternal( array $params );
/**
* Store a file into the backend from a file on disk.
+ * This will overwrite any file that exists at the destination.
* Do not call this function from places outside FileBackend and FileOp.
*
* $params include:
- * - src : source path on disk
- * - dst : destination storage path
- * - overwrite : overwrite any file that exists at the destination
- * - disposition : Content-Disposition header value for the destination
- * - async : Status will be returned immediately if supported.
- * If the status is OK, then its value field will be
- * set to a FileBackendStoreOpHandle object.
+ * - src : source path on disk
+ * - dst : destination storage path
+ * - disposition : Content-Disposition header value for the destination
+ * - headers : HTTP header name/value map
+ * - async : Status will be returned immediately if supported.
+ * If the status is OK, then its value field will be
+ * set to a FileBackendStoreOpHandle object.
+ * - dstExists : Whether a file exists at the destination (optimization).
+ * Callers can use "false" if no existing file is being changed.
*
- * @param $params Array
+ * @param array $params
* @return Status
*/
final public function storeInternal( array $params ) {
@@ -145,7 +155,7 @@ abstract class FileBackendStore extends FileBackend {
} else {
$status = $this->doStoreInternal( $params );
$this->clearCache( array( $params['dst'] ) );
- if ( !empty( $params['overwrite'] ) ) { // file possibly mutated
+ if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
$this->deleteFileCache( $params['dst'] ); // persistent cache
}
}
@@ -156,23 +166,27 @@ abstract class FileBackendStore extends FileBackend {
/**
* @see FileBackendStore::storeInternal()
+ * @return Status
*/
abstract protected function doStoreInternal( array $params );
/**
* Copy a file from one storage path to another in the backend.
+ * This will overwrite any file that exists at the destination.
* Do not call this function from places outside FileBackend and FileOp.
*
* $params include:
- * - src : source storage path
- * - dst : destination storage path
- * - overwrite : overwrite any file that exists at the destination
- * - disposition : Content-Disposition header value for the destination
- * - async : Status will be returned immediately if supported.
- * If the status is OK, then its value field will be
- * set to a FileBackendStoreOpHandle object.
+ * - src : source storage path
+ * - dst : destination storage path
+ * - ignoreMissingSource : do nothing if the source file does not exist
+ * - disposition : Content-Disposition header value for the destination
+ * - async : Status will be returned immediately if supported.
+ * If the status is OK, then its value field will be
+ * set to a FileBackendStoreOpHandle object.
+ * - dstExists : Whether a file exists at the destination (optimization).
+ * Callers can use "false" if no existing file is being changed.
*
- * @param $params Array
+ * @param array $params
* @return Status
*/
final public function copyInternal( array $params ) {
@@ -180,7 +194,7 @@ abstract class FileBackendStore extends FileBackend {
wfProfileIn( __METHOD__ . '-' . $this->name );
$status = $this->doCopyInternal( $params );
$this->clearCache( array( $params['dst'] ) );
- if ( !empty( $params['overwrite'] ) ) { // file possibly mutated
+ if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
$this->deleteFileCache( $params['dst'] ); // persistent cache
}
wfProfileOut( __METHOD__ . '-' . $this->name );
@@ -190,6 +204,7 @@ abstract class FileBackendStore extends FileBackend {
/**
* @see FileBackendStore::copyInternal()
+ * @return Status
*/
abstract protected function doCopyInternal( array $params );
@@ -204,7 +219,7 @@ abstract class FileBackendStore extends FileBackend {
* If the status is OK, then its value field will be
* set to a FileBackendStoreOpHandle object.
*
- * @param $params Array
+ * @param array $params
* @return Status
*/
final public function deleteInternal( array $params ) {
@@ -220,23 +235,27 @@ abstract class FileBackendStore extends FileBackend {
/**
* @see FileBackendStore::deleteInternal()
+ * @return Status
*/
abstract protected function doDeleteInternal( array $params );
/**
* Move a file from one storage path to another in the backend.
+ * This will overwrite any file that exists at the destination.
* Do not call this function from places outside FileBackend and FileOp.
*
* $params include:
- * - src : source storage path
- * - dst : destination storage path
- * - overwrite : overwrite any file that exists at the destination
- * - disposition : Content-Disposition header value for the destination
- * - async : Status will be returned immediately if supported.
- * If the status is OK, then its value field will be
- * set to a FileBackendStoreOpHandle object.
+ * - src : source storage path
+ * - dst : destination storage path
+ * - ignoreMissingSource : do nothing if the source file does not exist
+ * - disposition : Content-Disposition header value for the destination
+ * - async : Status will be returned immediately if supported.
+ * If the status is OK, then its value field will be
+ * set to a FileBackendStoreOpHandle object.
+ * - dstExists : Whether a file exists at the destination (optimization).
+ * Callers can use "false" if no existing file is being changed.
*
- * @param $params Array
+ * @param array $params
* @return Status
*/
final public function moveInternal( array $params ) {
@@ -245,7 +264,7 @@ abstract class FileBackendStore extends FileBackend {
$status = $this->doMoveInternal( $params );
$this->clearCache( array( $params['src'], $params['dst'] ) );
$this->deleteFileCache( $params['src'] ); // persistent cache
- if ( !empty( $params['overwrite'] ) ) { // file possibly mutated
+ if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
$this->deleteFileCache( $params['dst'] ); // persistent cache
}
wfProfileOut( __METHOD__ . '-' . $this->name );
@@ -270,10 +289,44 @@ abstract class FileBackendStore extends FileBackend {
}
/**
+ * Alter metadata for a file at the storage path.
+ * Do not call this function from places outside FileBackend and FileOp.
+ *
+ * $params include:
+ * - src : source storage path
+ * - disposition : Content-Disposition header value for the destination
+ * - headers : HTTP header name/value map
+ * - async : Status will be returned immediately if supported.
+ * If the status is OK, then its value field will be
+ * set to a FileBackendStoreOpHandle object.
+ *
+ * @param array $params
+ * @return Status
+ */
+ final public function describeInternal( array $params ) {
+ wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
+ $status = $this->doDescribeInternal( $params );
+ $this->clearCache( array( $params['src'] ) );
+ $this->deleteFileCache( $params['src'] ); // persistent cache
+ wfProfileOut( __METHOD__ . '-' . $this->name );
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::describeInternal()
+ * @return Status
+ */
+ protected function doDescribeInternal( array $params ) {
+ return Status::newGood();
+ }
+
+ /**
* No-op file operation that does nothing.
* Do not call this function from places outside FileBackend and FileOp.
*
- * @param $params Array
+ * @param array $params
* @return Status
*/
final public function nullInternal( array $params ) {
@@ -314,31 +367,41 @@ abstract class FileBackendStore extends FileBackend {
protected function doConcatenate( array $params ) {
$status = Status::newGood();
$tmpPath = $params['dst']; // convenience
+ unset( $params['latest'] ); // sanity
// Check that the specified temp file is valid...
wfSuppressWarnings();
- $ok = ( is_file( $tmpPath ) && !filesize( $tmpPath ) );
+ $ok = ( is_file( $tmpPath ) && filesize( $tmpPath ) == 0 );
wfRestoreWarnings();
if ( !$ok ) { // not present or not empty
$status->fatal( 'backend-fail-opentemp', $tmpPath );
return $status;
}
- // Build up the temp file using the source chunks (in order)...
+ // Get local FS versions of the chunks needed for the concatenation...
+ $fsFiles = $this->getLocalReferenceMulti( $params );
+ foreach ( $fsFiles as $path => &$fsFile ) {
+ if ( !$fsFile ) { // chunk failed to download?
+ $fsFile = $this->getLocalReference( array( 'src' => $path ) );
+ if ( !$fsFile ) { // retry failed?
+ $status->fatal( 'backend-fail-read', $path );
+ return $status;
+ }
+ }
+ }
+ unset( $fsFile ); // unset reference so we can reuse $fsFile
+
+ // Get a handle for the destination temp file
$tmpHandle = fopen( $tmpPath, 'ab' );
if ( $tmpHandle === false ) {
$status->fatal( 'backend-fail-opentemp', $tmpPath );
return $status;
}
- foreach ( $params['srcs'] as $virtualSource ) {
- // Get a local FS version of the chunk
- $tmpFile = $this->getLocalReference( array( 'src' => $virtualSource ) );
- if ( !$tmpFile ) {
- $status->fatal( 'backend-fail-read', $virtualSource );
- return $status;
- }
+
+ // Build up the temp file using the source chunks (in order)...
+ foreach ( $fsFiles as $virtualSource => $fsFile ) {
// Get a handle to the local FS version
- $sourceHandle = fopen( $tmpFile->getPath(), 'r' );
+ $sourceHandle = fopen( $fsFile->getPath(), 'rb' );
if ( $sourceHandle === false ) {
fclose( $tmpHandle );
$status->fatal( 'backend-fail-read', $virtualSource );
@@ -384,7 +447,7 @@ abstract class FileBackendStore extends FileBackend {
$status->merge( $this->doPrepareInternal( $fullCont, $dir, $params ) );
} else { // directory is on several shards
wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
- list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
+ list( , $shortCont, ) = self::splitStoragePath( $params['dir'] );
foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
$status->merge( $this->doPrepareInternal( "{$fullCont}{$suffix}", $dir, $params ) );
}
@@ -424,7 +487,7 @@ abstract class FileBackendStore extends FileBackend {
$status->merge( $this->doSecureInternal( $fullCont, $dir, $params ) );
} else { // directory is on several shards
wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
- list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
+ list( , $shortCont, ) = self::splitStoragePath( $params['dir'] );
foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
$status->merge( $this->doSecureInternal( "{$fullCont}{$suffix}", $dir, $params ) );
}
@@ -464,7 +527,7 @@ abstract class FileBackendStore extends FileBackend {
$status->merge( $this->doPublishInternal( $fullCont, $dir, $params ) );
} else { // directory is on several shards
wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
- list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
+ list( , $shortCont, ) = self::splitStoragePath( $params['dir'] );
foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
$status->merge( $this->doPublishInternal( "{$fullCont}{$suffix}", $dir, $params ) );
}
@@ -500,6 +563,7 @@ abstract class FileBackendStore extends FileBackend {
$subDir = $params['dir'] . "/{$subDirRel}"; // full path
$status->merge( $this->doClean( array( 'dir' => $subDir ) + $params ) );
}
+ unset( $subDirsRel ); // free directory for rmdir() on Windows (for FS backends)
}
}
@@ -525,7 +589,7 @@ abstract class FileBackendStore extends FileBackend {
$this->deleteContainerCache( $fullCont ); // purge cache
} else { // directory is on several shards
wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
- list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
+ list( , $shortCont, ) = self::splitStoragePath( $params['dir'] );
foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
$status->merge( $this->doCleanInternal( "{$fullCont}{$suffix}", $dir, $params ) );
$this->deleteContainerCache( "{$fullCont}{$suffix}" ); // purge cache
@@ -596,17 +660,25 @@ abstract class FileBackendStore extends FileBackend {
wfProfileIn( __METHOD__ );
wfProfileIn( __METHOD__ . '-' . $this->name );
$latest = !empty( $params['latest'] ); // use latest data?
- if ( !$this->cheapCache->has( $path, 'stat' ) ) {
+ if ( !$this->cheapCache->has( $path, 'stat', self::CACHE_TTL ) ) {
$this->primeFileCache( array( $path ) ); // check persistent cache
}
- if ( $this->cheapCache->has( $path, 'stat' ) ) {
+ if ( $this->cheapCache->has( $path, 'stat', self::CACHE_TTL ) ) {
$stat = $this->cheapCache->get( $path, 'stat' );
// If we want the latest data, check that this cached
// value was in fact fetched with the latest available data.
- if ( !$latest || $stat['latest'] ) {
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
- return $stat;
+ if ( is_array( $stat ) ) {
+ if ( !$latest || $stat['latest'] ) {
+ wfProfileOut( __METHOD__ . '-' . $this->name );
+ wfProfileOut( __METHOD__ );
+ return $stat;
+ }
+ } elseif ( in_array( $stat, array( 'NOT_EXIST', 'NOT_EXIST_LATEST' ) ) ) {
+ if ( !$latest || $stat === 'NOT_EXIST_LATEST' ) {
+ wfProfileOut( __METHOD__ . '-' . $this->name );
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
}
}
wfProfileIn( __METHOD__ . '-miss' );
@@ -614,7 +686,7 @@ abstract class FileBackendStore extends FileBackend {
$stat = $this->doGetFileStat( $params );
wfProfileOut( __METHOD__ . '-miss-' . $this->name );
wfProfileOut( __METHOD__ . '-miss' );
- if ( is_array( $stat ) ) { // don't cache negatives
+ if ( is_array( $stat ) ) { // file exists
$stat['latest'] = $latest;
$this->cheapCache->set( $path, 'stat', $stat );
$this->setFileCache( $path, $stat ); // update persistent cache
@@ -622,8 +694,11 @@ abstract class FileBackendStore extends FileBackend {
$this->cheapCache->set( $path, 'sha1',
array( 'hash' => $stat['sha1'], 'latest' => $latest ) );
}
- } else {
+ } elseif ( $stat === false ) { // file does not exist
+ $this->cheapCache->set( $path, 'stat', $latest ? 'NOT_EXIST_LATEST' : 'NOT_EXIST' );
wfDebug( __METHOD__ . ": File $path does not exist.\n" );
+ } else { // an error occurred
+ wfDebug( __METHOD__ . ": Could not stat file $path.\n" );
}
wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
@@ -636,24 +711,33 @@ abstract class FileBackendStore extends FileBackend {
abstract protected function doGetFileStat( array $params );
/**
- * @see FileBackend::getFileContents()
- * @return bool|string
+ * @see FileBackend::getFileContentsMulti()
+ * @return Array
*/
- public function getFileContents( array $params ) {
+ public function getFileContentsMulti( array $params ) {
wfProfileIn( __METHOD__ );
wfProfileIn( __METHOD__ . '-' . $this->name );
- $tmpFile = $this->getLocalReference( $params );
- if ( !$tmpFile ) {
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
- return false;
- }
- wfSuppressWarnings();
- $data = file_get_contents( $tmpFile->getPath() );
- wfRestoreWarnings();
+
+ $params = $this->setConcurrencyFlags( $params );
+ $contents = $this->doGetFileContentsMulti( $params );
+
wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
- return $data;
+ return $contents;
+ }
+
+ /**
+ * @see FileBackendStore::getFileContentsMulti()
+ * @return Array
+ */
+ protected function doGetFileContentsMulti( array $params ) {
+ $contents = array();
+ foreach ( $this->doGetLocalReferenceMulti( $params ) as $path => $fsFile ) {
+ wfSuppressWarnings();
+ $contents[$path] = $fsFile ? file_get_contents( $fsFile->getPath() ) : false;
+ wfRestoreWarnings();
+ }
+ return $contents;
}
/**
@@ -668,7 +752,7 @@ abstract class FileBackendStore extends FileBackend {
wfProfileIn( __METHOD__ );
wfProfileIn( __METHOD__ . '-' . $this->name );
$latest = !empty( $params['latest'] ); // use latest data?
- if ( $this->cheapCache->has( $path, 'sha1' ) ) {
+ if ( $this->cheapCache->has( $path, 'sha1', self::CACHE_TTL ) ) {
$stat = $this->cheapCache->get( $path, 'sha1' );
// If we want the latest data, check that this cached
// value was in fact fetched with the latest available data.
@@ -683,10 +767,7 @@ abstract class FileBackendStore extends FileBackend {
$hash = $this->doGetFileSha1Base36( $params );
wfProfileOut( __METHOD__ . '-miss-' . $this->name );
wfProfileOut( __METHOD__ . '-miss' );
- if ( $hash ) { // don't cache negatives
- $this->cheapCache->set( $path, 'sha1',
- array( 'hash' => $hash, 'latest' => $latest ) );
- }
+ $this->cheapCache->set( $path, 'sha1', array( 'hash' => $hash, 'latest' => $latest ) );
wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $hash;
@@ -720,35 +801,82 @@ abstract class FileBackendStore extends FileBackend {
}
/**
- * @see FileBackend::getLocalReference()
- * @return TempFSFile|null
+ * @see FileBackend::getLocalReferenceMulti()
+ * @return Array
*/
- public function getLocalReference( array $params ) {
- $path = self::normalizeStoragePath( $params['src'] );
- if ( $path === null ) {
- return null; // invalid storage path
- }
+ final public function getLocalReferenceMulti( array $params ) {
wfProfileIn( __METHOD__ );
wfProfileIn( __METHOD__ . '-' . $this->name );
+
+ $params = $this->setConcurrencyFlags( $params );
+
+ $fsFiles = array(); // (path => FSFile)
$latest = !empty( $params['latest'] ); // use latest data?
- if ( $this->expensiveCache->has( $path, 'localRef' ) ) {
- $val = $this->expensiveCache->get( $path, 'localRef' );
- // If we want the latest data, check that this cached
- // value was in fact fetched with the latest available data.
- if ( !$latest || $val['latest'] ) {
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
- return $val['object'];
+ // Reuse any files already in process cache...
+ foreach ( $params['srcs'] as $src ) {
+ $path = self::normalizeStoragePath( $src );
+ if ( $path === null ) {
+ $fsFiles[$src] = null; // invalid storage path
+ } elseif ( $this->expensiveCache->has( $path, 'localRef' ) ) {
+ $val = $this->expensiveCache->get( $path, 'localRef' );
+ // If we want the latest data, check that this cached
+ // value was in fact fetched with the latest available data.
+ if ( !$latest || $val['latest'] ) {
+ $fsFiles[$src] = $val['object'];
+ }
}
}
- $tmpFile = $this->getLocalCopy( $params );
- if ( $tmpFile ) { // don't cache negatives
- $this->expensiveCache->set( $path, 'localRef',
- array( 'object' => $tmpFile, 'latest' => $latest ) );
+ // Fetch local references of any remaning files...
+ $params['srcs'] = array_diff( $params['srcs'], array_keys( $fsFiles ) );
+ foreach ( $this->doGetLocalReferenceMulti( $params ) as $path => $fsFile ) {
+ $fsFiles[$path] = $fsFile;
+ if ( $fsFile ) { // update the process cache...
+ $this->expensiveCache->set( $path, 'localRef',
+ array( 'object' => $fsFile, 'latest' => $latest ) );
+ }
}
+
+ wfProfileOut( __METHOD__ . '-' . $this->name );
+ wfProfileOut( __METHOD__ );
+ return $fsFiles;
+ }
+
+ /**
+ * @see FileBackendStore::getLocalReferenceMulti()
+ * @return Array
+ */
+ protected function doGetLocalReferenceMulti( array $params ) {
+ return $this->doGetLocalCopyMulti( $params );
+ }
+
+ /**
+ * @see FileBackend::getLocalCopyMulti()
+ * @return Array
+ */
+ final public function getLocalCopyMulti( array $params ) {
+ wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
+
+ $params = $this->setConcurrencyFlags( $params );
+ $tmpFiles = $this->doGetLocalCopyMulti( $params );
+
wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
- return $tmpFile;
+ return $tmpFiles;
+ }
+
+ /**
+ * @see FileBackendStore::getLocalCopyMulti()
+ * @return Array
+ */
+ abstract protected function doGetLocalCopyMulti( array $params );
+
+ /**
+ * @see FileBackend::getFileHttpUrl()
+ * @return string|null
+ */
+ public function getFileHttpUrl( array $params ) {
+ return null; // not supported
}
/**
@@ -776,6 +904,14 @@ abstract class FileBackendStore extends FileBackend {
$status = $this->doStreamFile( $params );
wfProfileOut( __METHOD__ . '-send-' . $this->name );
wfProfileOut( __METHOD__ . '-send' );
+ if ( !$status->isOK() ) {
+ // Per bug 41113, nasty things can happen if bad cache entries get
+ // stuck in cache. It's also possible that this error can come up
+ // with simple race conditions. Clear out the stat cache to be safe.
+ $this->clearCache( array( $params['src'] ) );
+ $this->deleteFileCache( $params['src'] );
+ trigger_error( "Bad stat cache or race condition for file {$params['src']}." );
+ }
} else {
$status->fatal( 'backend-fail-stream', $params['src'] );
}
@@ -815,7 +951,7 @@ abstract class FileBackendStore extends FileBackend {
return $this->doDirectoryExists( $fullCont, $dir, $params );
} else { // directory is on several shards
wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
- list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
+ list( , $shortCont, ) = self::splitStoragePath( $params['dir'] );
$res = false; // response
foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
$exists = $this->doDirectoryExists( "{$fullCont}{$suffix}", $dir, $params );
@@ -833,9 +969,9 @@ abstract class FileBackendStore extends FileBackend {
/**
* @see FileBackendStore::directoryExists()
*
- * @param $container string Resolved container name
- * @param $dir string Resolved path relative to container
- * @param $params Array
+ * @param string $container Resolved container name
+ * @param string $dir Resolved path relative to container
+ * @param array $params
* @return bool|null
*/
abstract protected function doDirectoryExists( $container, $dir, array $params );
@@ -855,7 +991,7 @@ abstract class FileBackendStore extends FileBackend {
} else {
wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
// File listing spans multiple containers/shards
- list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
+ list( , $shortCont, ) = self::splitStoragePath( $params['dir'] );
return new FileBackendStoreShardDirIterator( $this,
$fullCont, $dir, $this->getContainerSuffixes( $shortCont ), $params );
}
@@ -866,9 +1002,9 @@ abstract class FileBackendStore extends FileBackend {
*
* @see FileBackendStore::getDirectoryList()
*
- * @param $container string Resolved container name
- * @param $dir string Resolved path relative to container
- * @param $params Array
+ * @param string $container Resolved container name
+ * @param string $dir Resolved path relative to container
+ * @param array $params
* @return Traversable|Array|null Returns null on failure
*/
abstract public function getDirectoryListInternal( $container, $dir, array $params );
@@ -888,7 +1024,7 @@ abstract class FileBackendStore extends FileBackend {
} else {
wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
// File listing spans multiple containers/shards
- list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
+ list( , $shortCont, ) = self::splitStoragePath( $params['dir'] );
return new FileBackendStoreShardFileIterator( $this,
$fullCont, $dir, $this->getContainerSuffixes( $shortCont ), $params );
}
@@ -899,9 +1035,9 @@ abstract class FileBackendStore extends FileBackend {
*
* @see FileBackendStore::getFileList()
*
- * @param $container string Resolved container name
- * @param $dir string Resolved path relative to container
- * @param $params Array
+ * @param string $container Resolved container name
+ * @param string $dir Resolved path relative to container
+ * @param array $params
* @return Traversable|Array|null Returns null on failure
*/
abstract public function getFileListInternal( $container, $dir, array $params );
@@ -913,18 +1049,19 @@ abstract class FileBackendStore extends FileBackend {
* The result must have the same number of items as the input.
* An exception is thrown if an unsupported operation is requested.
*
- * @param $ops Array Same format as doOperations()
+ * @param array $ops Same format as doOperations()
* @return Array List of FileOp objects
* @throws MWException
*/
final public function getOperationsInternal( array $ops ) {
$supportedOps = array(
- 'store' => 'StoreFileOp',
- 'copy' => 'CopyFileOp',
- 'move' => 'MoveFileOp',
- 'delete' => 'DeleteFileOp',
- 'create' => 'CreateFileOp',
- 'null' => 'NullFileOp'
+ 'store' => 'StoreFileOp',
+ 'copy' => 'CopyFileOp',
+ 'move' => 'MoveFileOp',
+ 'delete' => 'DeleteFileOp',
+ 'create' => 'CreateFileOp',
+ 'describe' => 'DescribeFileOp',
+ 'null' => 'NullFileOp'
);
$performOps = array(); // array of FileOp objects
@@ -949,8 +1086,9 @@ abstract class FileBackendStore extends FileBackend {
* Get a list of storage paths to lock for a list of operations
* Returns an array with 'sh' (shared) and 'ex' (exclusive) keys,
* each corresponding to a list of storage paths to be locked.
+ * All returned paths are normalized.
*
- * @param $performOps Array List of FileOp objects
+ * @param array $performOps List of FileOp objects
* @return Array ('sh' => list of paths, 'ex' => list of paths)
*/
final public function getPathsToLockForOpsInternal( array $performOps ) {
@@ -989,6 +1127,9 @@ abstract class FileBackendStore extends FileBackend {
wfProfileIn( __METHOD__ . '-' . $this->name );
$status = Status::newGood();
+ // Fix up custom header name/value pairs...
+ $ops = array_map( array( $this, 'stripInvalidHeadersFromOp' ), $ops );
+
// Build up a list of FileOps...
$performOps = $this->getOperationsInternal( $ops );
@@ -1016,6 +1157,7 @@ abstract class FileBackendStore extends FileBackend {
$this->primeContainerCache( $performOps );
// Actually attempt the operation batch...
+ $opts = $this->setConcurrencyFlags( $opts );
$subStatus = FileOpBatch::attempt( $performOps, $opts, $this->fileJournal );
// Merge errors into status fields
@@ -1037,6 +1179,12 @@ abstract class FileBackendStore extends FileBackend {
wfProfileIn( __METHOD__ . '-' . $this->name );
$status = Status::newGood();
+ // Fix up custom header name/value pairs...
+ $ops = array_map( array( $this, 'stripInvalidHeadersFromOp' ), $ops );
+
+ // Clear any file cache entries
+ $this->clearCache();
+
$supportedOps = array( 'create', 'store', 'copy', 'move', 'delete', 'null' );
$async = ( $this->parallelize === 'implicit' );
$maxConcurrency = $this->concurrency; // throttle
@@ -1092,7 +1240,7 @@ abstract class FileBackendStore extends FileBackend {
* The resulting Status object fields will correspond
* to the order in which the handles where given.
*
- * @param $handles Array List of FileBackendStoreOpHandle objects
+ * @param array $handles List of FileBackendStoreOpHandle objects
* @return Array Map of Status objects
* @throws MWException
*/
@@ -1117,6 +1265,8 @@ abstract class FileBackendStore extends FileBackend {
/**
* @see FileBackendStore::executeOpHandlesInternal()
+ * @param array $fileOpHandles
+ * @throws MWException
* @return Array List of corresponding Status objects
*/
protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
@@ -1127,12 +1277,32 @@ abstract class FileBackendStore extends FileBackend {
}
/**
+ * Strip long HTTP headers from a file operation
+ *
+ * @param array $op Same format as doOperation()
+ * @return Array
+ */
+ protected function stripInvalidHeadersFromOp( array $op ) {
+ if ( isset( $op['headers'] ) ) {
+ foreach ( $op['headers'] as $name => $value ) {
+ if ( strlen( $name ) > 255 || strlen( $value ) > 255 ) {
+ trigger_error( "Header '$name: $value' is too long." );
+ unset( $op['headers'][$name] );
+ } elseif ( !strlen( $value ) ) {
+ $op['headers'][$name] = ''; // null/false => ""
+ }
+ }
+ }
+ return $op;
+ }
+
+ /**
* @see FileBackend::preloadCache()
*/
final public function preloadCache( array $paths ) {
$fullConts = array(); // full container names
foreach ( $paths as $path ) {
- list( $fullCont, $r, $s ) = $this->resolveStoragePath( $path );
+ list( $fullCont, , ) = $this->resolveStoragePath( $path );
$fullConts[] = $fullCont;
}
// Load from the persistent file and container caches
@@ -1165,7 +1335,7 @@ abstract class FileBackendStore extends FileBackend {
*
* @see FileBackend::clearCache()
*
- * @param $paths Array Storage paths (optional)
+ * @param array $paths Storage paths (optional)
* @return void
*/
protected function doClearCache( array $paths = null ) {}
@@ -1254,8 +1424,8 @@ abstract class FileBackendStore extends FileBackend {
* Get the container name shard suffix for a given path.
* Any empty suffix means the container is not sharded.
*
- * @param $container string Container name
- * @param $relPath string Storage path relative to the container
+ * @param string $container Container name
+ * @param string $relPath Storage path relative to the container
* @return string|null Returns null if shard could not be determined
*/
final protected function getContainerShard( $container, $relPath ) {
@@ -1291,11 +1461,11 @@ abstract class FileBackendStore extends FileBackend {
* Container dirs like "a", where the container shards on "x/xy",
* can reside on several shards. Such paths are tricky to handle.
*
- * @param $storagePath string Storage path
+ * @param string $storagePath Storage path
* @return bool
*/
final public function isSingleShardPathInternal( $storagePath ) {
- list( $c, $r, $shard ) = $this->resolveStoragePath( $storagePath );
+ list( , , $shard ) = $this->resolveStoragePath( $storagePath );
return ( $shard !== null );
}
@@ -1371,8 +1541,8 @@ abstract class FileBackendStore extends FileBackend {
* getting absolute paths (e.g. FS based backends). Note that the relative path
* may be the empty string (e.g. the path is simply to the container).
*
- * @param $container string Container name
- * @param $relStoragePath string Storage path relative to the container
+ * @param string $container Container name
+ * @param string $relStoragePath Storage path relative to the container
* @return string|null Path or null if not valid
*/
protected function resolveContainerPath( $container, $relStoragePath ) {
@@ -1382,7 +1552,7 @@ abstract class FileBackendStore extends FileBackend {
/**
* Get the cache key for a container
*
- * @param $container string Resolved container name
+ * @param string $container Resolved container name
* @return string
*/
private function containerCacheKey( $container ) {
@@ -1392,7 +1562,7 @@ abstract class FileBackendStore extends FileBackend {
/**
* Set the cached info for a container
*
- * @param $container string Resolved container name
+ * @param string $container Resolved container name
* @param $val mixed Information to cache
*/
final protected function setContainerCache( $container, $val ) {
@@ -1403,7 +1573,7 @@ abstract class FileBackendStore extends FileBackend {
* Delete the cached info for a container.
* The cache key is salted for a while to prevent race conditions.
*
- * @param $container string Resolved container name
+ * @param string $container Resolved container name
*/
final protected function deleteContainerCache( $container ) {
if ( !$this->memCache->set( $this->containerCacheKey( $container ), 'PURGED', 300 ) ) {
@@ -1414,6 +1584,7 @@ abstract class FileBackendStore extends FileBackend {
/**
* Do a batch lookup from cache for container stats for all containers
* used in a list of container names, storage paths, or FileOp objects.
+ * This loads the persistent cache values into the process cache.
*
* @param $items Array
* @return void
@@ -1437,7 +1608,7 @@ abstract class FileBackendStore extends FileBackend {
}
// Get all the corresponding cache keys for paths...
foreach ( $paths as $path ) {
- list( $fullCont, $r, $s ) = $this->resolveStoragePath( $path );
+ list( $fullCont, , ) = $this->resolveStoragePath( $path );
if ( $fullCont !== null ) { // valid path for this backend
$contNames[$this->containerCacheKey( $fullCont )] = $fullCont;
}
@@ -1462,7 +1633,7 @@ abstract class FileBackendStore extends FileBackend {
* resolved container names and their corresponding cached info.
* Only containers that actually exist should appear in the map.
*
- * @param $containerInfo Array Map of resolved container names to cached info
+ * @param array $containerInfo Map of resolved container names to cached info
* @return void
*/
protected function doPrimeContainerCache( array $containerInfo ) {}
@@ -1470,7 +1641,7 @@ abstract class FileBackendStore extends FileBackend {
/**
* Get the cache key for a file path
*
- * @param $path string Storage path
+ * @param string $path Normalized storage path
* @return string
*/
private function fileCacheKey( $path ) {
@@ -1482,20 +1653,30 @@ abstract class FileBackendStore extends FileBackend {
* Negatives (404s) are not cached. By not caching negatives, we can skip cache
* salting for the case when a file is created at a path were there was none before.
*
- * @param $path string Storage path
+ * @param string $path Storage path
* @param $val mixed Information to cache
*/
final protected function setFileCache( $path, $val ) {
+ $path = FileBackend::normalizeStoragePath( $path );
+ if ( $path === null ) {
+ return; // invalid storage path
+ }
$this->memCache->add( $this->fileCacheKey( $path ), $val, 7*86400 );
}
/**
* Delete the cached stat info for a file path.
* The cache key is salted for a while to prevent race conditions.
+ * Since negatives (404s) are not cached, this does not need to be called when
+ * a file is created at a path were there was none before.
*
- * @param $path string Storage path
+ * @param string $path Storage path
*/
final protected function deleteFileCache( $path ) {
+ $path = FileBackend::normalizeStoragePath( $path );
+ if ( $path === null ) {
+ return; // invalid storage path
+ }
if ( !$this->memCache->set( $this->fileCacheKey( $path ), 'PURGED', 300 ) ) {
trigger_error( "Unable to delete stat cache for file $path." );
}
@@ -1504,8 +1685,9 @@ abstract class FileBackendStore extends FileBackend {
/**
* Do a batch lookup from cache for file stats for all paths
* used in a list of storage paths or FileOp objects.
+ * This loads the persistent cache values into the process cache.
*
- * @param $items Array List of storage paths or FileOps
+ * @param array $items List of storage paths or FileOps
* @return void
*/
final protected function primeFileCache( array $items ) {
@@ -1520,12 +1702,14 @@ abstract class FileBackendStore extends FileBackend {
$paths = array_merge( $paths, $item->storagePathsRead() );
$paths = array_merge( $paths, $item->storagePathsChanged() );
} elseif ( self::isStoragePath( $item ) ) {
- $paths[] = $item;
+ $paths[] = FileBackend::normalizeStoragePath( $item );
}
}
+ // Get rid of any paths that failed normalization...
+ $paths = array_filter( $paths, 'strlen' ); // remove nulls
// Get all the corresponding cache keys for paths...
foreach ( $paths as $path ) {
- list( $cont, $rel, $s ) = $this->resolveStoragePath( $path );
+ list( , $rel, ) = $this->resolveStoragePath( $path );
if ( $rel !== null ) { // valid path for this backend
$pathNames[$this->fileCacheKey( $path )] = $path;
}
@@ -1546,6 +1730,26 @@ abstract class FileBackendStore extends FileBackend {
wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
}
+
+ /**
+ * Set the 'concurrency' option from a list of operation options
+ *
+ * @param array $opts Map of operation options
+ * @return Array
+ */
+ final protected function setConcurrencyFlags( array $opts ) {
+ $opts['concurrency'] = 1; // off
+ if ( $this->parallelize === 'implicit' ) {
+ if ( !isset( $opts['parallelize'] ) || $opts['parallelize'] ) {
+ $opts['concurrency'] = $this->concurrency;
+ }
+ } elseif ( $this->parallelize === 'explicit' ) {
+ if ( !empty( $opts['parallelize'] ) ) {
+ $opts['concurrency'] = $this->concurrency;
+ }
+ }
+ return $opts;
+ }
}
/**
@@ -1602,10 +1806,10 @@ abstract class FileBackendStoreShardListIterator implements Iterator {
/**
* @param $backend FileBackendStore
- * @param $container string Full storage container name
- * @param $dir string Storage directory relative to container
- * @param $suffixes Array List of container shard suffixes
- * @param $params Array
+ * @param string $container Full storage container name
+ * @param string $dir Storage directory relative to container
+ * @param array $suffixes List of container shard suffixes
+ * @param array $params
*/
public function __construct(
FileBackendStore $backend, $container, $dir, array $suffixes, array $params
@@ -1731,9 +1935,9 @@ abstract class FileBackendStoreShardListIterator implements Iterator {
/**
* Get the list for a given container shard
*
- * @param $container string Resolved container name
- * @param $dir string Resolved path relative to container
- * @param $params Array
+ * @param string $container Resolved container name
+ * @param string $dir Resolved path relative to container
+ * @param array $params
* @return Traversable|Array|null
*/
abstract protected function listFromShard( $container, $dir, array $params );
diff --git a/includes/filebackend/FileOp.php b/includes/filebackend/FileOp.php
index 7c43c489..bb0ab578 100644
--- a/includes/filebackend/FileOp.php
+++ b/includes/filebackend/FileOp.php
@@ -42,11 +42,12 @@ abstract class FileOp {
protected $state = self::STATE_NEW; // integer
protected $failed = false; // boolean
protected $async = false; // boolean
- protected $useLatest = true; // boolean
protected $batchId; // string
+ protected $doOperation = true; // boolean; operation is not a no-op
protected $sourceSha1; // string
protected $destSameAsSource; // boolean
+ protected $destExists; // boolean
/* Object life-cycle */
const STATE_NEW = 1;
@@ -65,37 +66,61 @@ abstract class FileOp {
list( $required, $optional ) = $this->allowedParams();
foreach ( $required as $name ) {
if ( isset( $params[$name] ) ) {
- $this->params[$name] = $params[$name];
+ $this->params[$name] = self::normalizeAnyStoragePaths( $params[$name] );
} else {
throw new MWException( "File operation missing parameter '$name'." );
}
}
foreach ( $optional as $name ) {
if ( isset( $params[$name] ) ) {
- $this->params[$name] = $params[$name];
+ $this->params[$name] = self::normalizeAnyStoragePaths( $params[$name] );
}
}
$this->params = $params;
}
/**
- * Set the batch UUID this operation belongs to
+ * Normalize $item or anything in $item that is a valid storage path
*
- * @param $batchId string
- * @return void
+ * @param $item string|array
+ * @return string|Array
+ */
+ protected function normalizeAnyStoragePaths( $item ) {
+ if ( is_array( $item ) ) {
+ $res = array();
+ foreach ( $item as $k => $v ) {
+ $k = self::normalizeIfValidStoragePath( $k );
+ $v = self::normalizeIfValidStoragePath( $v );
+ $res[$k] = $v;
+ }
+ return $res;
+ } else {
+ return self::normalizeIfValidStoragePath( $item );
+ }
+ }
+
+ /**
+ * Normalize a string if it is a valid storage path
+ *
+ * @param $path string
+ * @return string
*/
- final public function setBatchId( $batchId ) {
- $this->batchId = $batchId;
+ protected static function normalizeIfValidStoragePath( $path ) {
+ if ( FileBackend::isStoragePath( $path ) ) {
+ $res = FileBackend::normalizeStoragePath( $path );
+ return ( $res !== null ) ? $res : $path;
+ }
+ return $path;
}
/**
- * Whether to allow stale data for file reads and stat checks
+ * Set the batch UUID this operation belongs to
*
- * @param $allowStale bool
+ * @param $batchId string
* @return void
*/
- final public function allowStaleReads( $allowStale ) {
- $this->useLatest = !$allowStale;
+ final public function setBatchId( $batchId ) {
+ $this->batchId = $batchId;
}
/**
@@ -138,11 +163,11 @@ abstract class FileOp {
/**
* Update a dependency tracking array to account for this operation
*
- * @param $deps Array Prior path reads/writes; format of FileOp::newPredicates()
+ * @param array $deps Prior path reads/writes; format of FileOp::newPredicates()
* @return Array
*/
final public function applyDependencies( array $deps ) {
- $deps['read'] += array_fill_keys( $this->storagePathsRead(), 1 );
+ $deps['read'] += array_fill_keys( $this->storagePathsRead(), 1 );
$deps['write'] += array_fill_keys( $this->storagePathsChanged(), 1 );
return $deps;
}
@@ -150,7 +175,7 @@ abstract class FileOp {
/**
* Check if this operation changes files listed in $paths
*
- * @param $paths Array Prior path reads/writes; format of FileOp::newPredicates()
+ * @param array $paths Prior path reads/writes; format of FileOp::newPredicates()
* @return boolean
*/
final public function dependsOn( array $deps ) {
@@ -170,16 +195,19 @@ abstract class FileOp {
/**
* Get the file journal entries for this file operation
*
- * @param $oPredicates Array Pre-op info about files (format of FileOp::newPredicates)
- * @param $nPredicates Array Post-op info about files (format of FileOp::newPredicates)
+ * @param array $oPredicates Pre-op info about files (format of FileOp::newPredicates)
+ * @param array $nPredicates Post-op info about files (format of FileOp::newPredicates)
* @return Array
*/
final public function getJournalEntries( array $oPredicates, array $nPredicates ) {
+ if ( !$this->doOperation ) {
+ return array(); // this is a no-op
+ }
$nullEntries = array();
$updateEntries = array();
$deleteEntries = array();
$pathsUsed = array_merge( $this->storagePathsRead(), $this->storagePathsChanged() );
- foreach ( $pathsUsed as $path ) {
+ foreach ( array_unique( $pathsUsed ) as $path ) {
$nullEntries[] = array( // assertion for recovery
'op' => 'null',
'path' => $path,
@@ -205,7 +233,9 @@ abstract class FileOp {
}
/**
- * Check preconditions of the operation without writing anything
+ * Check preconditions of the operation without writing anything.
+ * This must update $predicates for each path that the op can change
+ * except when a failing status object is returned.
*
* @param $predicates Array
* @return Status
@@ -241,10 +271,14 @@ abstract class FileOp {
return Status::newFatal( 'fileop-fail-attempt-precheck' );
}
$this->state = self::STATE_ATTEMPTED;
- $status = $this->doAttempt();
- if ( !$status->isOK() ) {
- $this->failed = true;
- $this->logFailure( 'attempt' );
+ if ( $this->doOperation ) {
+ $status = $this->doAttempt();
+ if ( !$status->isOK() ) {
+ $this->failed = true;
+ $this->logFailure( 'attempt' );
+ }
+ } else { // no-op
+ $status = Status::newGood();
}
return $status;
}
@@ -292,15 +326,7 @@ abstract class FileOp {
*
* @return Array
*/
- final public function storagePathsRead() {
- return array_map( 'FileBackend::normalizeStoragePath', $this->doStoragePathsRead() );
- }
-
- /**
- * @see FileOp::storagePathsRead()
- * @return Array
- */
- protected function doStoragePathsRead() {
+ public function storagePathsRead() {
return array();
}
@@ -309,21 +335,13 @@ abstract class FileOp {
*
* @return Array
*/
- final public function storagePathsChanged() {
- return array_map( 'FileBackend::normalizeStoragePath', $this->doStoragePathsChanged() );
- }
-
- /**
- * @see FileOp::storagePathsChanged()
- * @return Array
- */
- protected function doStoragePathsChanged() {
+ public function storagePathsChanged() {
return array();
}
/**
* Check for errors with regards to the destination file already existing.
- * This also updates the destSameAsSource and sourceSha1 member variables.
+ * Also set the destExists, destSameAsSource and sourceSha1 member variables.
* A bad status will be returned if there is no chance it can be overwritten.
*
* @param $predicates Array
@@ -337,7 +355,8 @@ abstract class FileOp {
$this->sourceSha1 = $this->fileSha1( $this->params['src'], $predicates );
}
$this->destSameAsSource = false;
- if ( $this->fileExists( $this->params['dst'], $predicates ) ) {
+ $this->destExists = $this->fileExists( $this->params['dst'], $predicates );
+ if ( $this->destExists ) {
if ( $this->getParam( 'overwrite' ) ) {
return $status; // OK
} elseif ( $this->getParam( 'overwriteSame' ) ) {
@@ -373,7 +392,7 @@ abstract class FileOp {
/**
* Check if a file will exist in storage when this operation is attempted
*
- * @param $source string Storage path
+ * @param string $source Storage path
* @param $predicates Array
* @return bool
*/
@@ -381,7 +400,7 @@ abstract class FileOp {
if ( isset( $predicates['exists'][$source] ) ) {
return $predicates['exists'][$source]; // previous op assures this
} else {
- $params = array( 'src' => $source, 'latest' => $this->useLatest );
+ $params = array( 'src' => $source, 'latest' => true );
return $this->backend->fileExists( $params );
}
}
@@ -389,15 +408,17 @@ abstract class FileOp {
/**
* Get the SHA-1 of a file in storage when this operation is attempted
*
- * @param $source string Storage path
+ * @param string $source Storage path
* @param $predicates Array
* @return string|bool False on failure
*/
final protected function fileSha1( $source, array $predicates ) {
if ( isset( $predicates['sha1'][$source] ) ) {
return $predicates['sha1'][$source]; // previous op assures this
+ } elseif ( isset( $predicates['exists'][$source] ) && !$predicates['exists'][$source] ) {
+ return false; // previous op assures this
} else {
- $params = array( 'src' => $source, 'latest' => $this->useLatest );
+ $params = array( 'src' => $source, 'latest' => true );
return $this->backend->getFileSha1Base36( $params );
}
}
@@ -430,42 +451,32 @@ abstract class FileOp {
}
/**
- * Store a file into the backend from a file on the file system.
+ * Create a file in the backend with the given content.
* Parameters for this operation are outlined in FileBackend::doOperations().
*/
-class StoreFileOp extends FileOp {
- /**
- * @return array
- */
+class CreateFileOp extends FileOp {
protected function allowedParams() {
- return array( array( 'src', 'dst' ),
- array( 'overwrite', 'overwriteSame', 'disposition' ) );
+ return array( array( 'content', 'dst' ),
+ array( 'overwrite', 'overwriteSame', 'disposition', 'headers' ) );
}
- /**
- * @param $predicates array
- * @return Status
- */
protected function doPrecheck( array &$predicates ) {
$status = Status::newGood();
- // Check if the source file exists on the file system
- if ( !is_file( $this->params['src'] ) ) {
- $status->fatal( 'backend-fail-notexists', $this->params['src'] );
- return $status;
- // Check if the source file is too big
- } elseif ( filesize( $this->params['src'] ) > $this->backend->maxFileSizeInternal() ) {
+ // Check if the source data is too big
+ if ( strlen( $this->getParam( 'content' ) ) > $this->backend->maxFileSizeInternal() ) {
$status->fatal( 'backend-fail-maxsize',
$this->params['dst'], $this->backend->maxFileSizeInternal() );
- $status->fatal( 'backend-fail-store', $this->params['src'], $this->params['dst'] );
+ $status->fatal( 'backend-fail-create', $this->params['dst'] );
return $status;
- // Check if a file can be placed at the destination
+ // Check if a file can be placed/changed at the destination
} elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
$status->fatal( 'backend-fail-usable', $this->params['dst'] );
- $status->fatal( 'backend-fail-store', $this->params['src'], $this->params['dst'] );
+ $status->fatal( 'backend-fail-create', $this->params['dst'] );
return $status;
}
// Check if destination file exists
$status->merge( $this->precheckDestExistence( $predicates ) );
+ $this->params['dstExists'] = $this->destExists; // see FileBackendStore::setFileCache()
if ( $status->isOK() ) {
// Update file existence predicates
$predicates['exists'][$this->params['dst']] = true;
@@ -478,57 +489,66 @@ class StoreFileOp extends FileOp {
* @return Status
*/
protected function doAttempt() {
- // Store the file at the destination
if ( !$this->destSameAsSource ) {
- return $this->backend->storeInternal( $this->setFlags( $this->params ) );
+ // Create the file at the destination
+ return $this->backend->createInternal( $this->setFlags( $this->params ) );
}
return Status::newGood();
}
/**
- * @return bool|string
+ * @return bool|String
*/
protected function getSourceSha1Base36() {
- wfSuppressWarnings();
- $hash = sha1_file( $this->params['src'] );
- wfRestoreWarnings();
- if ( $hash !== false ) {
- $hash = wfBaseConvert( $hash, 16, 36, 31 );
- }
- return $hash;
+ return wfBaseConvert( sha1( $this->params['content'] ), 16, 36, 31 );
}
- protected function doStoragePathsChanged() {
+ /**
+ * @return array
+ */
+ public function storagePathsChanged() {
return array( $this->params['dst'] );
}
}
/**
- * Create a file in the backend with the given content.
+ * Store a file into the backend from a file on the file system.
* Parameters for this operation are outlined in FileBackend::doOperations().
*/
-class CreateFileOp extends FileOp {
+class StoreFileOp extends FileOp {
+ /**
+ * @return array
+ */
protected function allowedParams() {
- return array( array( 'content', 'dst' ),
- array( 'overwrite', 'overwriteSame', 'disposition' ) );
+ return array( array( 'src', 'dst' ),
+ array( 'overwrite', 'overwriteSame', 'disposition', 'headers' ) );
}
+ /**
+ * @param $predicates array
+ * @return Status
+ */
protected function doPrecheck( array &$predicates ) {
$status = Status::newGood();
- // Check if the source data is too big
- if ( strlen( $this->getParam( 'content' ) ) > $this->backend->maxFileSizeInternal() ) {
+ // Check if the source file exists on the file system
+ if ( !is_file( $this->params['src'] ) ) {
+ $status->fatal( 'backend-fail-notexists', $this->params['src'] );
+ return $status;
+ // Check if the source file is too big
+ } elseif ( filesize( $this->params['src'] ) > $this->backend->maxFileSizeInternal() ) {
$status->fatal( 'backend-fail-maxsize',
$this->params['dst'], $this->backend->maxFileSizeInternal() );
- $status->fatal( 'backend-fail-create', $this->params['dst'] );
+ $status->fatal( 'backend-fail-store', $this->params['src'], $this->params['dst'] );
return $status;
- // Check if a file can be placed at the destination
+ // Check if a file can be placed/changed at the destination
} elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
$status->fatal( 'backend-fail-usable', $this->params['dst'] );
- $status->fatal( 'backend-fail-create', $this->params['dst'] );
+ $status->fatal( 'backend-fail-store', $this->params['src'], $this->params['dst'] );
return $status;
}
// Check if destination file exists
$status->merge( $this->precheckDestExistence( $predicates ) );
+ $this->params['dstExists'] = $this->destExists; // see FileBackendStore::setFileCache()
if ( $status->isOK() ) {
// Update file existence predicates
$predicates['exists'][$this->params['dst']] = true;
@@ -541,24 +561,27 @@ class CreateFileOp extends FileOp {
* @return Status
*/
protected function doAttempt() {
+ // Store the file at the destination
if ( !$this->destSameAsSource ) {
- // Create the file at the destination
- return $this->backend->createInternal( $this->setFlags( $this->params ) );
+ return $this->backend->storeInternal( $this->setFlags( $this->params ) );
}
return Status::newGood();
}
/**
- * @return bool|String
+ * @return bool|string
*/
protected function getSourceSha1Base36() {
- return wfBaseConvert( sha1( $this->params['content'] ), 16, 36, 31 );
+ wfSuppressWarnings();
+ $hash = sha1_file( $this->params['src'] );
+ wfRestoreWarnings();
+ if ( $hash !== false ) {
+ $hash = wfBaseConvert( $hash, 16, 36, 31 );
+ }
+ return $hash;
}
- /**
- * @return array
- */
- protected function doStoragePathsChanged() {
+ public function storagePathsChanged() {
return array( $this->params['dst'] );
}
}
@@ -573,7 +596,7 @@ class CopyFileOp extends FileOp {
*/
protected function allowedParams() {
return array( array( 'src', 'dst' ),
- array( 'overwrite', 'overwriteSame', 'disposition' ) );
+ array( 'overwrite', 'overwriteSame', 'ignoreMissingSource', 'disposition' ) );
}
/**
@@ -584,9 +607,17 @@ class CopyFileOp extends FileOp {
$status = Status::newGood();
// Check if the source file exists
if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
- $status->fatal( 'backend-fail-notexists', $this->params['src'] );
- return $status;
- // Check if a file can be placed at the destination
+ if ( $this->getParam( 'ignoreMissingSource' ) ) {
+ $this->doOperation = false; // no-op
+ // Update file existence predicates (cache 404s)
+ $predicates['exists'][$this->params['src']] = false;
+ $predicates['sha1'][$this->params['src']] = false;
+ return $status; // nothing to do
+ } else {
+ $status->fatal( 'backend-fail-notexists', $this->params['src'] );
+ return $status;
+ }
+ // Check if a file can be placed/changed at the destination
} elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
$status->fatal( 'backend-fail-usable', $this->params['dst'] );
$status->fatal( 'backend-fail-copy', $this->params['src'], $this->params['dst'] );
@@ -594,6 +625,7 @@ class CopyFileOp extends FileOp {
}
// Check if destination file exists
$status->merge( $this->precheckDestExistence( $predicates ) );
+ $this->params['dstExists'] = $this->destExists; // see FileBackendStore::setFileCache()
if ( $status->isOK() ) {
// Update file existence predicates
$predicates['exists'][$this->params['dst']] = true;
@@ -619,14 +651,14 @@ class CopyFileOp extends FileOp {
/**
* @return array
*/
- protected function doStoragePathsRead() {
+ public function storagePathsRead() {
return array( $this->params['src'] );
}
/**
* @return array
*/
- protected function doStoragePathsChanged() {
+ public function storagePathsChanged() {
return array( $this->params['dst'] );
}
}
@@ -641,7 +673,7 @@ class MoveFileOp extends FileOp {
*/
protected function allowedParams() {
return array( array( 'src', 'dst' ),
- array( 'overwrite', 'overwriteSame', 'disposition' ) );
+ array( 'overwrite', 'overwriteSame', 'ignoreMissingSource', 'disposition' ) );
}
/**
@@ -652,9 +684,17 @@ class MoveFileOp extends FileOp {
$status = Status::newGood();
// Check if the source file exists
if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
- $status->fatal( 'backend-fail-notexists', $this->params['src'] );
- return $status;
- // Check if a file can be placed at the destination
+ if ( $this->getParam( 'ignoreMissingSource' ) ) {
+ $this->doOperation = false; // no-op
+ // Update file existence predicates (cache 404s)
+ $predicates['exists'][$this->params['src']] = false;
+ $predicates['sha1'][$this->params['src']] = false;
+ return $status; // nothing to do
+ } else {
+ $status->fatal( 'backend-fail-notexists', $this->params['src'] );
+ return $status;
+ }
+ // Check if a file can be placed/changed at the destination
} elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
$status->fatal( 'backend-fail-usable', $this->params['dst'] );
$status->fatal( 'backend-fail-move', $this->params['src'], $this->params['dst'] );
@@ -662,6 +702,7 @@ class MoveFileOp extends FileOp {
}
// Check if destination file exists
$status->merge( $this->precheckDestExistence( $predicates ) );
+ $this->params['dstExists'] = $this->destExists; // see FileBackendStore::setFileCache()
if ( $status->isOK() ) {
// Update file existence predicates
$predicates['exists'][$this->params['src']] = false;
@@ -693,14 +734,14 @@ class MoveFileOp extends FileOp {
/**
* @return array
*/
- protected function doStoragePathsRead() {
+ public function storagePathsRead() {
return array( $this->params['src'] );
}
/**
* @return array
*/
- protected function doStoragePathsChanged() {
+ public function storagePathsChanged() {
return array( $this->params['src'], $this->params['dst'] );
}
}
@@ -717,21 +758,29 @@ class DeleteFileOp extends FileOp {
return array( array( 'src' ), array( 'ignoreMissingSource' ) );
}
- protected $needsDelete = true;
-
/**
- * @param array $predicates
+ * @param $predicates array
* @return Status
*/
protected function doPrecheck( array &$predicates ) {
$status = Status::newGood();
// Check if the source file exists
if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
- if ( !$this->getParam( 'ignoreMissingSource' ) ) {
+ if ( $this->getParam( 'ignoreMissingSource' ) ) {
+ $this->doOperation = false; // no-op
+ // Update file existence predicates (cache 404s)
+ $predicates['exists'][$this->params['src']] = false;
+ $predicates['sha1'][$this->params['src']] = false;
+ return $status; // nothing to do
+ } else {
$status->fatal( 'backend-fail-notexists', $this->params['src'] );
return $status;
}
- $this->needsDelete = false;
+ // Check if a file can be placed/changed at the source
+ } elseif ( !$this->backend->isPathUsableInternal( $this->params['src'] ) ) {
+ $status->fatal( 'backend-fail-usable', $this->params['src'] );
+ $status->fatal( 'backend-fail-delete', $this->params['src'] );
+ return $status;
}
// Update file existence predicates
$predicates['exists'][$this->params['src']] = false;
@@ -743,17 +792,66 @@ class DeleteFileOp extends FileOp {
* @return Status
*/
protected function doAttempt() {
- if ( $this->needsDelete ) {
- // Delete the source file
- return $this->backend->deleteInternal( $this->setFlags( $this->params ) );
+ // Delete the source file
+ return $this->backend->deleteInternal( $this->setFlags( $this->params ) );
+ }
+
+ /**
+ * @return array
+ */
+ public function storagePathsChanged() {
+ return array( $this->params['src'] );
+ }
+}
+
+/**
+ * Change metadata for a file at the given storage path in the backend.
+ * Parameters for this operation are outlined in FileBackend::doOperations().
+ */
+class DescribeFileOp extends FileOp {
+ /**
+ * @return array
+ */
+ protected function allowedParams() {
+ return array( array( 'src' ), array( 'disposition', 'headers' ) );
+ }
+
+ /**
+ * @param $predicates array
+ * @return Status
+ */
+ protected function doPrecheck( array &$predicates ) {
+ $status = Status::newGood();
+ // Check if the source file exists
+ if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
+ $status->fatal( 'backend-fail-notexists', $this->params['src'] );
+ return $status;
+ // Check if a file can be placed/changed at the source
+ } elseif ( !$this->backend->isPathUsableInternal( $this->params['src'] ) ) {
+ $status->fatal( 'backend-fail-usable', $this->params['src'] );
+ $status->fatal( 'backend-fail-describe', $this->params['src'] );
+ return $status;
}
- return Status::newGood();
+ // Update file existence predicates
+ $predicates['exists'][$this->params['src']] =
+ $this->fileExists( $this->params['src'], $predicates );
+ $predicates['sha1'][$this->params['src']] =
+ $this->fileSha1( $this->params['src'], $predicates );
+ return $status; // safe to call attempt()
+ }
+
+ /**
+ * @return Status
+ */
+ protected function doAttempt() {
+ // Update the source file's metadata
+ return $this->backend->describeInternal( $this->setFlags( $this->params ) );
}
/**
* @return array
*/
- protected function doStoragePathsChanged() {
+ public function storagePathsChanged() {
return array( $this->params['src'] );
}
}
diff --git a/includes/filebackend/FileOpBatch.php b/includes/filebackend/FileOpBatch.php
index 33558725..fc51d78a 100644
--- a/includes/filebackend/FileOpBatch.php
+++ b/includes/filebackend/FileOpBatch.php
@@ -42,9 +42,6 @@ class FileOpBatch {
* $opts is an array of options, including:
* - force : Errors that would normally cause a rollback do not.
* The remaining operations are still attempted if any fail.
- * - allowStale : Don't require the latest available data.
- * This can increase performance for non-critical writes.
- * This has no effect unless the 'force' flag is set.
* - nonJournaled : Don't log this operation batch in the file journal.
* - concurrency : Try to do this many operations in parallel when possible.
*
@@ -52,8 +49,8 @@ class FileOpBatch {
* - a) unexpected operation errors occurred (network partitions, disk full...)
* - b) significant operation errors occurred and 'force' was not set
*
- * @param $performOps Array List of FileOp operations
- * @param $opts Array Batch operation options
+ * @param array $performOps List of FileOp operations
+ * @param array $opts Batch operation options
* @param $journal FileJournal Journal to log operations to
* @return Status
*/
@@ -69,7 +66,6 @@ class FileOpBatch {
}
$batchId = $journal->getTimestampedUUID();
- $allowStale = !empty( $opts['allowStale'] );
$ignoreErrors = !empty( $opts['force'] );
$journaled = empty( $opts['nonJournaled'] );
$maxConcurrency = isset( $opts['concurrency'] ) ? $opts['concurrency'] : 1;
@@ -84,7 +80,6 @@ class FileOpBatch {
foreach ( $performOps as $index => $fileOp ) {
$backendName = $fileOp->getBackend()->getName();
$fileOp->setBatchId( $batchId ); // transaction ID
- $fileOp->allowStaleReads( $allowStale ); // consistency level
// Decide if this op can be done concurrently within this sub-batch
// or if a new concurrent sub-batch must be started after this one...
if ( $fileOp->dependsOn( $curBatchDeps )
@@ -136,49 +131,13 @@ class FileOpBatch {
}
// Attempt each operation (in parallel if allowed and possible)...
- if ( count( $pPerformOps ) < count( $performOps ) ) {
- self::runBatchParallel( $pPerformOps, $status );
- } else {
- self::runBatchSeries( $performOps, $status );
- }
+ self::runParallelBatches( $pPerformOps, $status );
wfProfileOut( __METHOD__ );
return $status;
}
/**
- * Attempt a list of file operations in series.
- * This will abort remaining ops on failure.
- *
- * @param $performOps Array
- * @param $status Status
- * @return bool Success
- */
- protected static function runBatchSeries( array $performOps, Status $status ) {
- foreach ( $performOps as $index => $fileOp ) {
- if ( $fileOp->failed() ) {
- continue; // nothing to do
- }
- $subStatus = $fileOp->attempt();
- $status->merge( $subStatus );
- if ( $subStatus->isOK() ) {
- $status->success[$index] = true;
- ++$status->successCount;
- } else {
- $status->success[$index] = false;
- ++$status->failCount;
- // We can't continue (even with $ignoreErrors) as $predicates is wrong.
- // Log the remaining ops as failed for recovery...
- for ( $i = ($index + 1); $i < count( $performOps ); $i++ ) {
- $performOps[$i]->logFailure( 'attempt_aborted' );
- }
- return false; // bail out
- }
- }
- return true;
- }
-
- /**
* Attempt a list of file operations sub-batches in series.
*
* The operations *in* each sub-batch will be done in parallel.
@@ -190,8 +149,8 @@ class FileOpBatch {
* @param $status Status
* @return bool Success
*/
- protected static function runBatchParallel( array $pPerformOps, Status $status ) {
- $aborted = false;
+ protected static function runParallelBatches( array $pPerformOps, Status $status ) {
+ $aborted = false; // set to true on unexpected errors
foreach ( $pPerformOps as $performOpsBatch ) {
if ( $aborted ) { // check batch op abort flag...
// We can't continue (even with $ignoreErrors) as $predicates is wrong.
@@ -205,11 +164,16 @@ class FileOpBatch {
$opHandles = array();
// Get the backend; all sub-batch ops belong to a single backend
$backend = reset( $performOpsBatch )->getBackend();
- // If attemptAsync() returns synchronously, it was either an
- // error Status or the backend just doesn't support async ops.
+ // Get the operation handles or actually do it if there is just one.
+ // If attemptAsync() returns a Status, it was either due to an error
+ // or the backend does not support async ops and did it synchronously.
foreach ( $performOpsBatch as $i => $fileOp ) {
if ( !$fileOp->failed() ) { // failed => already has Status
- $subStatus = $fileOp->attemptAsync();
+ // If the batch is just one operation, it's faster to avoid
+ // pipelining as that can involve creating new TCP connections.
+ $subStatus = ( count( $performOpsBatch ) > 1 )
+ ? $fileOp->attemptAsync()
+ : $fileOp->attempt();
if ( $subStatus->value instanceof FileBackendStoreOpHandle ) {
$opHandles[$i] = $subStatus->value; // deferred
} else {
diff --git a/includes/filebackend/README b/includes/filebackend/README
new file mode 100644
index 00000000..6ab54810
--- /dev/null
+++ b/includes/filebackend/README
@@ -0,0 +1,208 @@
+/*!
+\ingroup FileBackend
+\page file_backend_design File backend design
+
+Some notes on the FileBackend architecture.
+
+\section intro Introduction
+
+To abstract away the differences among different types of storage media,
+MediaWiki is providing an interface known as FileBackend. Any MediaWiki
+interaction with stored files should thus use a FileBackend object.
+
+Different types of backing storage media are supported (ranging from local
+file system to distributed object stores). The types include:
+
+* FSFileBackend (used for mounted file systems)
+* SwiftFileBackend (used for Swift or Ceph Rados+RGW object stores)
+* FileBackendMultiWrite (useful for transitioning from one backend to another)
+
+Configuration documentation for each type of backend is to be found in their
+__construct() inline documentation.
+
+
+\section setup Setup
+
+File backends are registered in LocalSettings.php via the global variable
+$wgFileBackends. To access one of those defined backends, one would use
+FileBackendStore::get( <name> ) which will bring back a FileBackend object
+handle. Such handles are reused for any subsequent get() call (via singleton).
+The FileBackends objects are caching request calls such as file stats,
+SHA1 requests or TCP connection handles.
+
+\par Note:
+Some backends may require additional PHP extensions to be enabled or can rely on a
+MediaWiki extension. This is often the case when a FileBackend subclass makes use of an
+upstream client API for communicating with the backing store.
+
+
+\section fileoperations File operations
+
+The MediaWiki FileBackend API supports various operations on either files or
+directories. See FileBackend.php for full documentation for each function.
+
+
+\subsection reading Reading
+
+The following basic operations are supported for reading from a backend:
+
+On files:
+* state a file for basic information (timestamp, size)
+* read a file into a string or several files into a map of path names to strings
+* download a file or set of files to a temporary file (on a mounted file system)
+* get the SHA1 hash of a file
+* get various properties of a file (stat information, content time, mime information, ...)
+
+On directories:
+* get a list of files directly under a directory
+* get a recursive list of files under a directory
+* get a list of directories directly under a directory
+* get a recursive list of directories under a directory
+
+\par Note:
+Backend handles should return directory listings as iterators, all though in some cases
+they may just be simple arrays (which can still be iterated over). Iterators allow for
+callers to traverse a large number of file listings without consuming excessive RAM in
+the process. Either the memory consumed is flatly bounded (if the iterator does paging)
+or it is proportional to the depth of the portion of the directory tree being traversed
+(if the iterator works via recursion).
+
+
+\subsection writing Writing
+
+The following basic operations are supported for writing or changing in the backend:
+
+On files:
+* store (copying a mounted file system file into storage)
+* create (creating a file within storage from a string)
+* copy (within storage)
+* move (within storage)
+* delete (within storage)
+* lock/unlock (lock or unlock a file in storage)
+
+The following operations are supported for writing directories in the backend:
+* prepare (create parent container and directories for a path)
+* secure (try to lock-down access to a container)
+* publish (try to reverse the effects of secure)
+* clean (remove empty containers or directories)
+
+
+\subsection invokingoperation Invoking an operation
+
+Generally, callers should use doOperations() or doQuickOperations() when doing
+batches of changes, rather than making a suite of single operation calls. This
+makes the system tolerate high latency much better by pipelining operations
+when possible.
+
+doOperations() should be used for working on important original data, i.e. when
+consistency is important. The former will only pipeline operations that do not
+depend on each other. It is best if the operations that do not depend on each
+other occur in consecutive groups. This function can also log file changes to
+a journal (see FileJournal), which can be used to sync two backend instances.
+One might use this function for user uploads of file for example.
+
+doQuickOperations() is more geared toward ephemeral items that can be easily
+regenerated from original data. It will always pipeline without checking for
+dependencies within the operation batch. One might use this function for
+creating and purging generated thumbnails of original files for example.
+
+
+\section consistency Consistency
+
+Not all backing stores are sequentially consistent by default. Various FileBackend
+functions offer a "latest" option that can be passed in to assure (or try to assure)
+that the latest version of the file is read. Some backing stores are consistent by
+default, but callers should always assume that without this option, stale data may
+be read. This is actually true for stores that have eventual consistency.
+
+Note that file listing functions have no "latest" flag, and thus some systems may
+return stale data. Thus callers should avoid assuming that listings contain changes
+made my the current client or any other client from a very short time ago. For example,
+creating a file under a directory and then immediately doing a file listing operation
+on that directory may result in a listing that does not include that file.
+
+
+\section locking Locking
+
+Locking is effective if and only if a proper lock manager is registered and is
+actually being used by the backend. Lock managers can be registered in LocalSettings.php
+using the $wgLockManagers global configuration variable.
+
+For object stores, locking is not generally useful for avoiding partially
+written or read objects, since most stores use Multi Version Concurrency
+Control (MVCC) to avoid this. However, locking can be important when:
+* One or more operations must be done without objects changing in the meantime.
+* It can also be useful when a file read is used to determine a file write or DB change.
+ For example, doOperations() first checks that there will be no "file already exists"
+ or "file does not exist" type errors before attempting an operation batch. This works
+ by stating the files first, and is only safe if the files are locked in the meantime.
+
+When locking, callers should use the latest available file data for reads.
+Also, one should always lock the file *before* reading it, not after. If stale data is
+used to determine a write, there will be some data corruption, even when reads of the
+original file finally start returning the updated data without needing the "latest"
+option (eventual consistency). The "scoped" lock functions are preferable since
+there is not the problem of forgetting to unlock due to early returns or exceptions.
+
+Since acquiring locks can fail, and lock managers can be non-blocking, callers should:
+* Acquire all required locks up font
+* Be prepared for the case where locks fail to be acquired
+* Possible retry acquiring certain locks
+
+MVCC is also a useful pattern to use on top of the backend interface, because operations
+are not atomic, even with doOperations(), so doing complex batch file changes or changing
+files and updating a database row can result in partially written "transactions". Thus one
+should avoid changing files once they have been stored, except perhaps with ephemeral data
+that are tolerant of some degree of inconsistency.
+
+Callers can use their own locking (e.g. SELECT FOR UPDATE) if it is more convenient, but
+note that all callers that change any of the files should then go through functions that
+acquire these locks. For example, if a caller just directly uses the file backend store()
+function, it will ignore any custom "FOR UPDATE" locks, which can cause problems.
+
+\section objectstore Object stores
+
+Support for object stores (like Amazon S3/Swift) drive much of the API and design
+decisions of FileBackend, but using any POSIX compliant file systems works fine.
+The system essentially stores "files" in "containers". For a mounted file system
+as a backing store, "files" will just be files under directories. For an object store
+as a backing store, the "files" will be objects stored in actual containers.
+
+
+\section file_obj_diffs File system and Object store differences
+
+An advantage of object stores is the reduced Round-Trip Times. This is
+achieved by avoiding the need to create each parent directory before placing a
+file somewhere. It gets worse the deeper the directory hierarchy is. Another
+advantage of object stores is that object listings tend to use databases, which
+scale better than the linked list directories that file sytems sometimes use.
+File systems like btrfs and xfs use tree structures, which scale better.
+For both object stores and file systems, using "/" in filenames will allow for the
+intuitive use of directory functions. For example, creating a file in Swift
+called "container/a/b/file1" will mean that:
+- a "directory listing" of "container/a" will contain "b",
+- and a "file listing" of "b" will contain "file1"
+
+This means that switching from an object store to a file system and vise versa
+using the FileBackend interface will generally be harmless. However, one must be
+aware of some important differences:
+
+* In a file system, you cannot have a file and a directory within the same path
+ whereas it is possible in an object stores. Calling code should avoid any layouts
+ which allow files and directories at the same path.
+* Some file systems have file name length restrictions or overall path length
+ restrictions that others do not. The same goes with object stores which might
+ have a maximum object length or a limitation regarding the number of files
+ under a container or volume.
+* Latency varies among systems, certain access patterns may not be tolerable for
+ certain backends but may hold up for others. Some backend subclasses use
+ MediaWiki's object caching for serving stat requests, which can greatly
+ reduce latency. Making sure that the backend has pipelining (see the
+ "parallelize" and "concurrency" settings) enabled can also mask latency in
+ batch operation scenarios.
+* File systems may implement directories as linked-lists or other structures
+ with poor scalability, so calling code should use layouts that shard the data.
+ Instead of storing files like "container/file.txt", one can store files like
+ "container/<x>/<y>/file.txt". It is best if "sharding" optional or configurable.
+
+*/
diff --git a/includes/filebackend/SwiftFileBackend.php b/includes/filebackend/SwiftFileBackend.php
index b6f0aa60..0f3d97a3 100644
--- a/includes/filebackend/SwiftFileBackend.php
+++ b/includes/filebackend/SwiftFileBackend.php
@@ -40,11 +40,16 @@ class SwiftFileBackend extends FileBackendStore {
/** @var CF_Authentication */
protected $auth; // Swift authentication handler
protected $authTTL; // integer seconds
+ protected $swiftTempUrlKey; // string; shared secret value for making temp urls
protected $swiftAnonUser; // string; username to handle unauthenticated requests
protected $swiftUseCDN; // boolean; whether CloudFiles CDN is enabled
protected $swiftCDNExpiry; // integer; how long to cache things in the CDN
protected $swiftCDNPurgable; // boolean; whether object CDN purging is enabled
+ // Rados Gateway specific options
+ protected $rgwS3AccessKey; // string; S3 access key
+ protected $rgwS3SecretKey; // string; S3 authentication key
+
/** @var CF_Connection */
protected $conn; // Swift connection handle
protected $sessionStarted = 0; // integer UNIX timestamp
@@ -66,6 +71,8 @@ class SwiftFileBackend extends FileBackendStore {
* - swiftUser : Swift user used by MediaWiki (account:username)
* - swiftKey : Swift authentication key for the above user
* - swiftAuthTTL : Swift authentication TTL (seconds)
+ * - swiftTempUrlKey : Swift "X-Account-Meta-Temp-URL-Key" value on the account.
+ * Do not set this until it has been set in the backend.
* - swiftAnonUser : Swift user used for end-user requests (account:username).
* If set, then views of public containers are assumed to go
* through this user. If not set, then public containers are
@@ -84,6 +91,16 @@ class SwiftFileBackend extends FileBackendStore {
* - cacheAuthInfo : Whether to cache authentication tokens in APC, XCache, ect.
* If those are not available, then the main cache will be used.
* This is probably insecure in shared hosting environments.
+ * - rgwS3AccessKey : Ragos Gateway S3 "access key" value on the account.
+ * Do not set this until it has been set in the backend.
+ * This is used for generating expiring pre-authenticated URLs.
+ * Only use this when using rgw and to work around
+ * http://tracker.newdream.net/issues/3454.
+ * - rgwS3SecretKey : Ragos Gateway S3 "secret key" value on the account.
+ * Do not set this until it has been set in the backend.
+ * This is used for generating expiring pre-authenticated URLs.
+ * Only use this when using rgw and to work around
+ * http://tracker.newdream.net/issues/3454.
*/
public function __construct( array $config ) {
parent::__construct( $config );
@@ -104,6 +121,9 @@ class SwiftFileBackend extends FileBackendStore {
$this->swiftAnonUser = isset( $config['swiftAnonUser'] )
? $config['swiftAnonUser']
: '';
+ $this->swiftTempUrlKey = isset( $config['swiftTempUrlKey'] )
+ ? $config['swiftTempUrlKey']
+ : '';
$this->shardViaHashLevels = isset( $config['shardViaHashLevels'] )
? $config['shardViaHashLevels']
: '';
@@ -116,13 +136,19 @@ class SwiftFileBackend extends FileBackendStore {
$this->swiftCDNPurgable = isset( $config['swiftCDNPurgable'] )
? $config['swiftCDNPurgable']
: true;
+ $this->rgwS3AccessKey = isset( $config['rgwS3AccessKey'] )
+ ? $config['rgwS3AccessKey']
+ : '';
+ $this->rgwS3SecretKey = isset( $config['rgwS3SecretKey'] )
+ ? $config['rgwS3SecretKey']
+ : '';
// Cache container information to mask latency
$this->memCache = wfGetMainCache();
// Process cache for container info
$this->connContainerCache = new ProcessCacheLRU( 300 );
// Cache auth token information to avoid RTTs
if ( !empty( $config['cacheAuthInfo'] ) ) {
- if ( php_sapi_name() === 'cli' ) {
+ if ( PHP_SAPI === 'cli' ) {
$this->srvCache = wfGetMainCache(); // preferrably memcached
} else {
try { // look for APC, XCache, WinCache, ect...
@@ -168,14 +194,14 @@ class SwiftFileBackend extends FileBackendStore {
}
/**
- * @param $disposition string Content-Disposition header value
+ * @param string $disposition Content-Disposition header value
* @return string Truncated Content-Disposition header value to meet Swift limits
*/
protected function truncDisp( $disposition ) {
$res = '';
foreach ( explode( ';', $disposition ) as $part ) {
$part = trim( $part );
- $new = ( $res === '' ) ? $part : "{$res};{$part}";
+ $new = ( $res === '' ) ? $part : "{$res};{$part}";
if ( strlen( $new ) <= 255 ) {
$res = $new;
} else {
@@ -201,12 +227,6 @@ class SwiftFileBackend extends FileBackendStore {
// (a) Check the destination container and object
try {
$dContObj = $this->getContainer( $dstCont );
- if ( empty( $params['overwrite'] ) &&
- $this->fileExists( array( 'src' => $params['dst'], 'latest' => 1 ) ) )
- {
- $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
- return $status;
- }
} catch ( NoSuchContainerException $e ) {
$status->fatal( 'backend-fail-create', $params['dst'] );
return $status;
@@ -223,8 +243,7 @@ class SwiftFileBackend extends FileBackendStore {
// Create a fresh CF_Object with no fields preloaded.
// We don't want to preserve headers, metadata, and such.
$obj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD
- // Note: metadata keys stored as [Upper case char][[Lower case char]...]
- $obj->metadata = array( 'Sha1base36' => $sha1Hash );
+ $obj->setMetadataValues( array( 'Sha1base36' => $sha1Hash ) );
// Manually set the ETag (https://github.com/rackspace/php-cloudfiles/issues/59).
// The MD5 here will be checked within Swift against its own MD5.
$obj->set_etag( md5( $params['content'] ) );
@@ -237,17 +256,17 @@ class SwiftFileBackend extends FileBackendStore {
if ( isset( $params['disposition'] ) ) {
$obj->headers['Content-Disposition'] = $this->truncDisp( $params['disposition'] );
}
+ // Set any other custom headers if requested
+ if ( isset( $params['headers'] ) ) {
+ $obj->headers += $params['headers'];
+ }
if ( !empty( $params['async'] ) ) { // deferred
$op = $obj->write_async( $params['content'] );
$status->value = new SwiftFileOpHandle( $this, $params, 'Create', $op );
- if ( !empty( $params['overwrite'] ) ) { // file possibly mutated
- $status->value->affectedObjects[] = $obj;
- }
+ $status->value->affectedObjects[] = $obj;
} else { // actually write the object in Swift
$obj->write( $params['content'] );
- if ( !empty( $params['overwrite'] ) ) { // file possibly mutated
- $this->purgeCDNCache( array( $obj ) );
- }
+ $this->purgeCDNCache( array( $obj ) );
}
} catch ( CDNNotEnabledException $e ) {
// CDN not enabled; nothing to see here
@@ -287,12 +306,6 @@ class SwiftFileBackend extends FileBackendStore {
// (a) Check the destination container and object
try {
$dContObj = $this->getContainer( $dstCont );
- if ( empty( $params['overwrite'] ) &&
- $this->fileExists( array( 'src' => $params['dst'], 'latest' => 1 ) ) )
- {
- $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
- return $status;
- }
} catch ( NoSuchContainerException $e ) {
$status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
return $status;
@@ -302,7 +315,9 @@ class SwiftFileBackend extends FileBackendStore {
}
// (b) Get a SHA-1 hash of the object
+ wfSuppressWarnings();
$sha1Hash = sha1_file( $params['src'] );
+ wfRestoreWarnings();
if ( $sha1Hash === false ) { // source doesn't exist?
$status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
return $status;
@@ -314,8 +329,7 @@ class SwiftFileBackend extends FileBackendStore {
// Create a fresh CF_Object with no fields preloaded.
// We don't want to preserve headers, metadata, and such.
$obj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD
- // Note: metadata keys stored as [Upper case char][[Lower case char]...]
- $obj->metadata = array( 'Sha1base36' => $sha1Hash );
+ $obj->setMetadataValues( array( 'Sha1base36' => $sha1Hash ) );
// The MD5 here will be checked within Swift against its own MD5.
$obj->set_etag( md5_file( $params['src'] ) );
// Use the same content type as StreamFile for security
@@ -327,6 +341,10 @@ class SwiftFileBackend extends FileBackendStore {
if ( isset( $params['disposition'] ) ) {
$obj->headers['Content-Disposition'] = $this->truncDisp( $params['disposition'] );
}
+ // Set any other custom headers if requested
+ if ( isset( $params['headers'] ) ) {
+ $obj->headers += $params['headers'];
+ }
if ( !empty( $params['async'] ) ) { // deferred
wfSuppressWarnings();
$fp = fopen( $params['src'], 'rb' );
@@ -337,15 +355,11 @@ class SwiftFileBackend extends FileBackendStore {
$op = $obj->write_async( $fp, filesize( $params['src'] ), true );
$status->value = new SwiftFileOpHandle( $this, $params, 'Store', $op );
$status->value->resourcesToClose[] = $fp;
- if ( !empty( $params['overwrite'] ) ) { // file possibly mutated
- $status->value->affectedObjects[] = $obj;
- }
+ $status->value->affectedObjects[] = $obj;
}
} else { // actually write the object in Swift
$obj->load_from_filename( $params['src'], true ); // calls $obj->write()
- if ( !empty( $params['overwrite'] ) ) { // file possibly mutated
- $this->purgeCDNCache( array( $obj ) );
- }
+ $this->purgeCDNCache( array( $obj ) );
}
} catch ( CDNNotEnabledException $e ) {
// CDN not enabled; nothing to see here
@@ -396,14 +410,10 @@ class SwiftFileBackend extends FileBackendStore {
try {
$sContObj = $this->getContainer( $srcCont );
$dContObj = $this->getContainer( $dstCont );
- if ( empty( $params['overwrite'] ) &&
- $this->fileExists( array( 'src' => $params['dst'], 'latest' => 1 ) ) )
- {
- $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
- return $status;
- }
} catch ( NoSuchContainerException $e ) {
- $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+ if ( empty( $params['ignoreMissingSource'] ) || isset( $sContObj ) ) {
+ $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+ }
return $status;
} catch ( CloudFilesException $e ) { // some other exception?
$this->handleException( $e, $status, __METHOD__, $params );
@@ -420,19 +430,17 @@ class SwiftFileBackend extends FileBackendStore {
if ( !empty( $params['async'] ) ) { // deferred
$op = $sContObj->copy_object_to_async( $srcRel, $dContObj, $dstRel, null, $hdrs );
$status->value = new SwiftFileOpHandle( $this, $params, 'Copy', $op );
- if ( !empty( $params['overwrite'] ) ) { // file possibly mutated
- $status->value->affectedObjects[] = $dstObj;
- }
+ $status->value->affectedObjects[] = $dstObj;
} else { // actually write the object in Swift
$sContObj->copy_object_to( $srcRel, $dContObj, $dstRel, null, $hdrs );
- if ( !empty( $params['overwrite'] ) ) { // file possibly mutated
- $this->purgeCDNCache( array( $dstObj ) );
- }
+ $this->purgeCDNCache( array( $dstObj ) );
}
} catch ( CDNNotEnabledException $e ) {
// CDN not enabled; nothing to see here
} catch ( NoSuchObjectException $e ) { // source object does not exist
- $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+ if ( empty( $params['ignoreMissingSource'] ) ) {
+ $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+ }
} catch ( CloudFilesException $e ) { // some other exception?
$this->handleException( $e, $status, __METHOD__, $params );
}
@@ -474,14 +482,10 @@ class SwiftFileBackend extends FileBackendStore {
try {
$sContObj = $this->getContainer( $srcCont );
$dContObj = $this->getContainer( $dstCont );
- if ( empty( $params['overwrite'] ) &&
- $this->fileExists( array( 'src' => $params['dst'], 'latest' => 1 ) ) )
- {
- $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
- return $status;
- }
} catch ( NoSuchContainerException $e ) {
- $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
+ if ( empty( $params['ignoreMissingSource'] ) || isset( $sContObj ) ) {
+ $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
+ }
return $status;
} catch ( CloudFilesException $e ) { // some other exception?
$this->handleException( $e, $status, __METHOD__, $params );
@@ -500,20 +504,18 @@ class SwiftFileBackend extends FileBackendStore {
$op = $sContObj->move_object_to_async( $srcRel, $dContObj, $dstRel, null, $hdrs );
$status->value = new SwiftFileOpHandle( $this, $params, 'Move', $op );
$status->value->affectedObjects[] = $srcObj;
- if ( !empty( $params['overwrite'] ) ) { // file possibly mutated
- $status->value->affectedObjects[] = $dstObj;
- }
+ $status->value->affectedObjects[] = $dstObj;
} else { // actually write the object in Swift
$sContObj->move_object_to( $srcRel, $dContObj, $dstRel, null, $hdrs );
$this->purgeCDNCache( array( $srcObj ) );
- if ( !empty( $params['overwrite'] ) ) { // file possibly mutated
- $this->purgeCDNCache( array( $dstObj ) );
- }
+ $this->purgeCDNCache( array( $dstObj ) );
}
} catch ( CDNNotEnabledException $e ) {
// CDN not enabled; nothing to see here
} catch ( NoSuchObjectException $e ) { // source object does not exist
- $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
+ if ( empty( $params['ignoreMissingSource'] ) ) {
+ $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
+ }
} catch ( CloudFilesException $e ) { // some other exception?
$this->handleException( $e, $status, __METHOD__, $params );
}
@@ -559,7 +561,9 @@ class SwiftFileBackend extends FileBackendStore {
} catch ( CDNNotEnabledException $e ) {
// CDN not enabled; nothing to see here
} catch ( NoSuchContainerException $e ) {
- $status->fatal( 'backend-fail-delete', $params['src'] );
+ if ( empty( $params['ignoreMissingSource'] ) ) {
+ $status->fatal( 'backend-fail-delete', $params['src'] );
+ }
} catch ( NoSuchObjectException $e ) {
if ( empty( $params['ignoreMissingSource'] ) ) {
$status->fatal( 'backend-fail-delete', $params['src'] );
@@ -587,6 +591,47 @@ class SwiftFileBackend extends FileBackendStore {
}
/**
+ * @see FileBackendStore::doDescribeInternal()
+ * @return Status
+ */
+ protected function doDescribeInternal( array $params ) {
+ $status = Status::newGood();
+
+ list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
+ if ( $srcRel === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['src'] );
+ return $status;
+ }
+
+ $hdrs = isset( $params['headers'] ) ? $params['headers'] : array();
+ // Set the Content-Disposition header if requested
+ if ( isset( $params['disposition'] ) ) {
+ $hdrs['Content-Disposition'] = $this->truncDisp( $params['disposition'] );
+ }
+
+ try {
+ $sContObj = $this->getContainer( $srcCont );
+ // Get the latest version of the current metadata
+ $srcObj = $sContObj->get_object( $srcRel,
+ $this->headersFromParams( array( 'latest' => true ) ) );
+ // Merge in the metadata updates...
+ $srcObj->headers = $hdrs + $srcObj->headers;
+ $srcObj->sync_metadata(); // save to Swift
+ $this->purgeCDNCache( array( $srcObj ) );
+ } catch ( CDNNotEnabledException $e ) {
+ // CDN not enabled; nothing to see here
+ } catch ( NoSuchContainerException $e ) {
+ $status->fatal( 'backend-fail-describe', $params['src'] );
+ } catch ( NoSuchObjectException $e ) {
+ $status->fatal( 'backend-fail-describe', $params['src'] );
+ } catch ( CloudFilesException $e ) { // some other exception?
+ $this->handleException( $e, $status, __METHOD__, $params );
+ }
+
+ return $status;
+ }
+
+ /**
* @see FileBackendStore::doPrepareInternal()
* @return Status
*/
@@ -595,7 +640,7 @@ class SwiftFileBackend extends FileBackendStore {
// (a) Check if container already exists
try {
- $contObj = $this->getContainer( $fullCont );
+ $this->getContainer( $fullCont );
// NoSuchContainerException not thrown: container must exist
return $status; // already exists
} catch ( NoSuchContainerException $e ) {
@@ -761,7 +806,7 @@ class SwiftFileBackend extends FileBackendStore {
// Convert dates like "Tue, 03 Jan 2012 22:01:04 GMT" to TS_MW
'mtime' => wfTimestamp( TS_MW, $srcObj->last_modified ),
'size' => (int)$srcObj->content_length,
- 'sha1' => $srcObj->metadata['Sha1base36']
+ 'sha1' => $srcObj->getMetadataValue( 'Sha1base36' )
);
} catch ( NoSuchContainerException $e ) {
} catch ( NoSuchObjectException $e ) {
@@ -777,60 +822,106 @@ class SwiftFileBackend extends FileBackendStore {
* Fill in any missing object metadata and save it to Swift
*
* @param $obj CF_Object
- * @param $path string Storage path to object
+ * @param string $path Storage path to object
* @return bool Success
* @throws Exception cloudfiles exceptions
*/
protected function addMissingMetadata( CF_Object $obj, $path ) {
- if ( isset( $obj->metadata['Sha1base36'] ) ) {
+ if ( $obj->getMetadataValue( 'Sha1base36' ) !== null ) {
return true; // nothing to do
}
wfProfileIn( __METHOD__ );
+ trigger_error( "$path was not stored with SHA-1 metadata.", E_USER_WARNING );
$status = Status::newGood();
$scopeLockS = $this->getScopedFileLocks( array( $path ), LockManager::LOCK_UW, $status );
if ( $status->isOK() ) {
- # Do not stat the file in getLocalCopy() to avoid infinite loops
- $tmpFile = $this->getLocalCopy( array( 'src' => $path, 'latest' => 1, 'nostat' => 1 ) );
+ $tmpFile = $this->getLocalCopy( array( 'src' => $path, 'latest' => 1 ) );
if ( $tmpFile ) {
$hash = $tmpFile->getSha1Base36();
if ( $hash !== false ) {
- $obj->metadata['Sha1base36'] = $hash;
+ $obj->setMetadataValues( array( 'Sha1base36' => $hash ) );
$obj->sync_metadata(); // save to Swift
wfProfileOut( __METHOD__ );
return true; // success
}
}
}
- $obj->metadata['Sha1base36'] = false;
+ trigger_error( "Unable to set SHA-1 metadata for $path", E_USER_WARNING );
+ $obj->setMetadataValues( array( 'Sha1base36' => false ) );
wfProfileOut( __METHOD__ );
return false; // failed
}
/**
- * @see FileBackend::getFileContents()
- * @return bool|null|string
+ * @see FileBackendStore::doGetFileContentsMulti()
+ * @return Array
*/
- public function getFileContents( array $params ) {
- list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
- if ( $srcRel === null ) {
- return false; // invalid storage path
- }
-
- if ( !$this->fileExists( $params ) ) {
- return null;
- }
+ protected function doGetFileContentsMulti( array $params ) {
+ $contents = array();
+
+ $ep = array_diff_key( $params, array( 'srcs' => 1 ) ); // for error logging
+ // Blindly create tmp files and stream to them, catching any exception if the file does
+ // not exist. Doing stats here is useless and will loop infinitely in addMissingMetadata().
+ foreach ( array_chunk( $params['srcs'], $params['concurrency'] ) as $pathBatch ) {
+ $cfOps = array(); // (path => CF_Async_Op)
+
+ foreach ( $pathBatch as $path ) { // each path in this concurrent batch
+ list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path );
+ if ( $srcRel === null ) {
+ $contents[$path] = false;
+ continue;
+ }
+ $data = false;
+ try {
+ $sContObj = $this->getContainer( $srcCont );
+ $obj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD
+ // Create a new temporary memory file...
+ $handle = fopen( 'php://temp', 'wb' );
+ if ( $handle ) {
+ $headers = $this->headersFromParams( $params );
+ if ( count( $pathBatch ) > 1 ) {
+ $cfOps[$path] = $obj->stream_async( $handle, $headers );
+ $cfOps[$path]->_file_handle = $handle; // close this later
+ } else {
+ $obj->stream( $handle, $headers );
+ rewind( $handle ); // start from the beginning
+ $data = stream_get_contents( $handle );
+ fclose( $handle );
+ }
+ } else {
+ $data = false;
+ }
+ } catch ( NoSuchContainerException $e ) {
+ $data = false;
+ } catch ( NoSuchObjectException $e ) {
+ $data = false;
+ } catch ( CloudFilesException $e ) { // some other exception?
+ $data = false;
+ $this->handleException( $e, null, __METHOD__, array( 'src' => $path ) + $ep );
+ }
+ $contents[$path] = $data;
+ }
- $data = false;
- try {
- $sContObj = $this->getContainer( $srcCont );
- $obj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD
- $data = $obj->read( $this->headersFromParams( $params ) );
- } catch ( NoSuchContainerException $e ) {
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, null, __METHOD__, $params );
+ $batch = new CF_Async_Op_Batch( $cfOps );
+ $cfOps = $batch->execute();
+ foreach ( $cfOps as $path => $cfOp ) {
+ try {
+ $cfOp->getLastResponse();
+ rewind( $cfOp->_file_handle ); // start from the beginning
+ $contents[$path] = stream_get_contents( $cfOp->_file_handle );
+ } catch ( NoSuchContainerException $e ) {
+ $contents[$path] = false;
+ } catch ( NoSuchObjectException $e ) {
+ $contents[$path] = false;
+ } catch ( CloudFilesException $e ) { // some other exception?
+ $contents[$path] = false;
+ $this->handleException( $e, null, __METHOD__, array( 'src' => $path ) + $ep );
+ }
+ fclose( $cfOp->_file_handle ); // close open handle
+ }
}
- return $data;
+ return $contents;
}
/**
@@ -871,11 +962,11 @@ class SwiftFileBackend extends FileBackendStore {
/**
* Do not call this function outside of SwiftFileBackendFileList
*
- * @param $fullCont string Resolved container name
- * @param $dir string Resolved storage directory with no trailing slash
- * @param $after string|null Storage path of file to list items after
+ * @param string $fullCont Resolved container name
+ * @param string $dir Resolved storage directory with no trailing slash
+ * @param string|null $after Storage path of file to list items after
* @param $limit integer Max number of items to list
- * @param $params Array Includes flag for 'topOnly'
+ * @param array $params Includes flag for 'topOnly'
* @return Array List of relative paths of dirs directly under $dir
*/
public function getDirListPageInternal( $fullCont, $dir, &$after, $limit, array $params ) {
@@ -903,7 +994,7 @@ class SwiftFileBackend extends FileBackendStore {
$objects = $container->list_objects( $limit, $after, $prefix );
foreach ( $objects as $object ) { // files
$objectDir = $this->getParentDir( $object ); // directory of object
- if ( $objectDir !== false ) { // file has a parent dir
+ if ( $objectDir !== false && $objectDir !== $dir ) {
// Swift stores paths in UTF-8, using binary sorting.
// See function "create_container_table" in common/db.py.
// If a directory is not "greater" than the last one,
@@ -944,11 +1035,11 @@ class SwiftFileBackend extends FileBackendStore {
/**
* Do not call this function outside of SwiftFileBackendFileList
*
- * @param $fullCont string Resolved container name
- * @param $dir string Resolved storage directory with no trailing slash
- * @param $after string|null Storage path of file to list items after
+ * @param string $fullCont Resolved container name
+ * @param string $dir Resolved storage directory with no trailing slash
+ * @param string|null $after Storage path of file to list items after
* @param $limit integer Max number of items to list
- * @param $params Array Includes flag for 'topOnly'
+ * @param array $params Includes flag for 'topOnly'
* @return Array List of relative paths of files under $dir
*/
public function getFileListPageInternal( $fullCont, $dir, &$after, $limit, array $params ) {
@@ -1038,44 +1129,125 @@ class SwiftFileBackend extends FileBackendStore {
}
/**
- * @see FileBackendStore::getLocalCopy()
+ * @see FileBackendStore::doGetLocalCopyMulti()
* @return null|TempFSFile
*/
- public function getLocalCopy( array $params ) {
- list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
- if ( $srcRel === null ) {
- return null;
- }
+ protected function doGetLocalCopyMulti( array $params ) {
+ $tmpFiles = array();
+
+ $ep = array_diff_key( $params, array( 'srcs' => 1 ) ); // for error logging
+ // Blindly create tmp files and stream to them, catching any exception if the file does
+ // not exist. Doing a stat here is useless causes infinite loops in addMissingMetadata().
+ foreach ( array_chunk( $params['srcs'], $params['concurrency'] ) as $pathBatch ) {
+ $cfOps = array(); // (path => CF_Async_Op)
+
+ foreach ( $pathBatch as $path ) { // each path in this concurrent batch
+ list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path );
+ if ( $srcRel === null ) {
+ $tmpFiles[$path] = null;
+ continue;
+ }
+ $tmpFile = null;
+ try {
+ $sContObj = $this->getContainer( $srcCont );
+ $obj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD
+ // Get source file extension
+ $ext = FileBackend::extensionFromPath( $path );
+ // Create a new temporary file...
+ $tmpFile = TempFSFile::factory( 'localcopy_', $ext );
+ if ( $tmpFile ) {
+ $handle = fopen( $tmpFile->getPath(), 'wb' );
+ if ( $handle ) {
+ $headers = $this->headersFromParams( $params );
+ if ( count( $pathBatch ) > 1 ) {
+ $cfOps[$path] = $obj->stream_async( $handle, $headers );
+ $cfOps[$path]->_file_handle = $handle; // close this later
+ } else {
+ $obj->stream( $handle, $headers );
+ fclose( $handle );
+ }
+ } else {
+ $tmpFile = null;
+ }
+ }
+ } catch ( NoSuchContainerException $e ) {
+ $tmpFile = null;
+ } catch ( NoSuchObjectException $e ) {
+ $tmpFile = null;
+ } catch ( CloudFilesException $e ) { // some other exception?
+ $tmpFile = null;
+ $this->handleException( $e, null, __METHOD__, array( 'src' => $path ) + $ep );
+ }
+ $tmpFiles[$path] = $tmpFile;
+ }
- // Blindly create a tmp file and stream to it, catching any exception if the file does
- // not exist. Also, doing a stat here will cause infinite loops when filling metadata.
- $tmpFile = null;
- try {
- $sContObj = $this->getContainer( $srcCont );
- $obj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD
- // Get source file extension
- $ext = FileBackend::extensionFromPath( $srcRel );
- // Create a new temporary file...
- $tmpFile = TempFSFile::factory( 'localcopy_', $ext );
- if ( $tmpFile ) {
- $handle = fopen( $tmpFile->getPath(), 'wb' );
- if ( $handle ) {
- $obj->stream( $handle, $this->headersFromParams( $params ) );
- fclose( $handle );
- } else {
- $tmpFile = null; // couldn't open temp file
+ $batch = new CF_Async_Op_Batch( $cfOps );
+ $cfOps = $batch->execute();
+ foreach ( $cfOps as $path => $cfOp ) {
+ try {
+ $cfOp->getLastResponse();
+ } catch ( NoSuchContainerException $e ) {
+ $tmpFiles[$path] = null;
+ } catch ( NoSuchObjectException $e ) {
+ $tmpFiles[$path] = null;
+ } catch ( CloudFilesException $e ) { // some other exception?
+ $tmpFiles[$path] = null;
+ $this->handleException( $e, null, __METHOD__, array( 'src' => $path ) + $ep );
}
+ fclose( $cfOp->_file_handle ); // close open handle
}
- } catch ( NoSuchContainerException $e ) {
- $tmpFile = null;
- } catch ( NoSuchObjectException $e ) {
- $tmpFile = null;
- } catch ( CloudFilesException $e ) { // some other exception?
- $tmpFile = null;
- $this->handleException( $e, null, __METHOD__, $params );
}
- return $tmpFile;
+ return $tmpFiles;
+ }
+
+ /**
+ * @see FileBackendStore::getFileHttpUrl()
+ * @return string|null
+ */
+ public function getFileHttpUrl( array $params ) {
+ if ( $this->swiftTempUrlKey != '' ||
+ ( $this->rgwS3AccessKey != '' && $this->rgwS3SecretKey != '' ) )
+ {
+ list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
+ if ( $srcRel === null ) {
+ return null; // invalid path
+ }
+ try {
+ $ttl = isset( $params['ttl'] ) ? $params['ttl'] : 86400;
+ $sContObj = $this->getContainer( $srcCont );
+ $obj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD
+ if ( $this->swiftTempUrlKey != '' ) {
+ return $obj->get_temp_url( $this->swiftTempUrlKey, $ttl, "GET" );
+ } else { // give S3 API URL for rgw
+ $expires = time() + $ttl;
+ // Path for signature starts with the bucket
+ $spath = '/' . rawurlencode( $srcCont ) . '/' .
+ str_replace( '%2F', '/', rawurlencode( $srcRel ) );
+ // Calculate the hash
+ $signature = base64_encode( hash_hmac(
+ 'sha1',
+ "GET\n\n\n{$expires}\n{$spath}",
+ $this->rgwS3SecretKey,
+ true // raw
+ ) );
+ // See http://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html.
+ // Note: adding a newline for empty CanonicalizedAmzHeaders does not work.
+ return wfAppendQuery(
+ str_replace( '/swift/v1', '', // S3 API is the rgw default
+ $sContObj->cfs_http->getStorageUrl() . $spath ),
+ array(
+ 'Signature' => $signature,
+ 'Expires' => $expires,
+ 'AWSAccessKeyId' => $this->rgwS3AccessKey )
+ );
+ }
+ } catch ( NoSuchContainerException $e ) {
+ } catch ( CloudFilesException $e ) { // some other exception?
+ $this->handleException( $e, null, __METHOD__, $params );
+ }
+ }
+ return null;
}
/**
@@ -1091,7 +1263,7 @@ class SwiftFileBackend extends FileBackendStore {
* on a FileBackend params array, e.g. that of getLocalCopy().
* $params is currently only checked for a 'latest' flag.
*
- * @param $params Array
+ * @param array $params
* @return Array
*/
protected function headersFromParams( array $params ) {
@@ -1118,8 +1290,8 @@ class SwiftFileBackend extends FileBackendStore {
$cfOps = $batch->execute();
foreach ( $cfOps as $index => $cfOp ) {
$status = Status::newGood();
+ $function = '_getResponse' . $fileOpHandles[$index]->call;
try { // catch exceptions; update status
- $function = '_getResponse' . $fileOpHandles[$index]->call;
$this->$function( $cfOp, $status, $fileOpHandles[$index]->params );
$this->purgeCDNCache( $fileOpHandles[$index]->affectedObjects );
} catch ( CloudFilesException $e ) { // some other exception?
@@ -1137,12 +1309,12 @@ class SwiftFileBackend extends FileBackendStore {
*
* $readGrps is a list of the possible criteria for a request to have
* access to read a container. Each item is one of the following formats:
- * - account:user : Grants access if the request is by the given user
- * - .r:<regex> : Grants access if the request is from a referrer host that
- * matches the expression and the request is not for a listing.
- * Setting this to '*' effectively makes a container public.
- * - .rlistings:<regex> : Grants access if the request is from a referrer host that
- * matches the expression and the request for a listing.
+ * - account:user : Grants access if the request is by the given user
+ * - ".r:<regex>" : Grants access if the request is from a referrer host that
+ * matches the expression and the request is not for a listing.
+ * Setting this to '*' effectively makes a container public.
+ * -".rlistings:<regex>" : Grants access if the request is from a referrer host that
+ * matches the expression and the request for a listing.
*
* $writeGrps is a list of the possible criteria for a request to have
* access to write to a container. Each item is of the following format:
@@ -1154,8 +1326,8 @@ class SwiftFileBackend extends FileBackendStore {
* (lists are truncated to 10000 item with no way to page), and is just a performance risk.
*
* @param $contObj CF_Container Swift container
- * @param $readGrps Array List of read access routes
- * @param $writeGrps Array List of write access routes
+ * @param array $readGrps List of read access routes
+ * @param array $writeGrps List of write access routes
* @return Status
*/
protected function setContainerAccess(
@@ -1178,7 +1350,7 @@ class SwiftFileBackend extends FileBackendStore {
* Purge the CDN cache of affected objects if CDN caching is enabled.
* This is for Rackspace/Akamai CDNs.
*
- * @param $objects Array List of CF_Object items
+ * @param array $objects List of CF_Object items
* @return void
*/
public function purgeCDNCache( array $objects ) {
@@ -1199,8 +1371,9 @@ class SwiftFileBackend extends FileBackendStore {
/**
* Get an authenticated connection handle to the Swift proxy
*
- * @return CF_Connection|bool False on failure
* @throws CloudFilesException
+ * @throws CloudFilesException|Exception
+ * @return CF_Connection|bool False on failure
*/
protected function getConnection() {
if ( $this->connException instanceof CloudFilesException ) {
@@ -1251,6 +1424,7 @@ class SwiftFileBackend extends FileBackendStore {
protected function closeConnection() {
if ( $this->conn ) {
$this->conn->close(); // close active cURL handles in CF_Http object
+ $this->conn = null;
$this->sessionStarted = 0;
$this->connContainerCache->clear();
}
@@ -1267,18 +1441,11 @@ class SwiftFileBackend extends FileBackendStore {
}
/**
- * @see FileBackendStore::doClearCache()
- */
- protected function doClearCache( array $paths = null ) {
- $this->connContainerCache->clear(); // clear container object cache
- }
-
- /**
* Get a Swift container object, possibly from process cache.
* Use $reCache if the file count or byte count is needed.
*
- * @param $container string Container name
- * @param $bypassCache bool Bypass all caches and load from Swift
+ * @param string $container Container name
+ * @param bool $bypassCache Bypass all caches and load from Swift
* @return CF_Container
* @throws CloudFilesException
*/
@@ -1305,7 +1472,7 @@ class SwiftFileBackend extends FileBackendStore {
/**
* Create a Swift container
*
- * @param $container string Container name
+ * @param string $container Container name
* @return CF_Container
* @throws CloudFilesException
*/
@@ -1319,7 +1486,7 @@ class SwiftFileBackend extends FileBackendStore {
/**
* Delete a Swift container
*
- * @param $container string Container name
+ * @param string $container Container name
* @return void
* @throws CloudFilesException
*/
@@ -1353,7 +1520,7 @@ class SwiftFileBackend extends FileBackendStore {
* @param $e Exception
* @param $status Status|null
* @param $func string
- * @param $params Array
+ * @param array $params
* @return void
*/
protected function handleException( Exception $e, $status, $func, array $params ) {
@@ -1420,9 +1587,9 @@ abstract class SwiftFileBackendList implements Iterator {
/**
* @param $backend SwiftFileBackend
- * @param $fullCont string Resolved container name
- * @param $dir string Resolved directory relative to container
- * @param $params Array
+ * @param string $fullCont Resolved container name
+ * @param string $dir Resolved directory relative to container
+ * @param array $params
*/
public function __construct( SwiftFileBackend $backend, $fullCont, $dir, array $params ) {
$this->backend = $backend;
@@ -1491,11 +1658,11 @@ abstract class SwiftFileBackendList implements Iterator {
/**
* Get the given list portion (page)
*
- * @param $container string Resolved container name
- * @param $dir string Resolved path relative to container
+ * @param string $container Resolved container name
+ * @param string $dir Resolved path relative to container
* @param $after string|null
* @param $limit integer
- * @param $params Array
+ * @param array $params
* @return Traversable|Array|null Returns null on failure
*/
abstract protected function pageFromList( $container, $dir, &$after, $limit, array $params );
diff --git a/includes/filebackend/TempFSFile.php b/includes/filebackend/TempFSFile.php
index 5032bf68..11e125c1 100644
--- a/includes/filebackend/TempFSFile.php
+++ b/includes/filebackend/TempFSFile.php
@@ -82,30 +82,37 @@ class TempFSFile extends FSFile {
* Clean up the temporary file only after an object goes out of scope
*
* @param $object Object
- * @return void
+ * @return TempFSFile This object
*/
public function bind( $object ) {
if ( is_object( $object ) ) {
+ if ( !isset( $object->tempFSFileReferences ) ) {
+ // Init first since $object might use __get() and return only a copy variable
+ $object->tempFSFileReferences = array();
+ }
$object->tempFSFileReferences[] = $this;
}
+ return $this;
}
/**
* Set flag to not clean up after the temporary file
*
- * @return void
+ * @return TempFSFile This object
*/
public function preserve() {
$this->canDelete = false;
+ return $this;
}
/**
* Set flag clean up after the temporary file
*
- * @return void
+ * @return TempFSFile This object
*/
public function autocollect() {
$this->canDelete = true;
+ return $this;
}
/**
diff --git a/includes/filebackend/filejournal/DBFileJournal.php b/includes/filebackend/filejournal/DBFileJournal.php
index f6268c25..73f29a95 100644
--- a/includes/filebackend/filejournal/DBFileJournal.php
+++ b/includes/filebackend/filejournal/DBFileJournal.php
@@ -75,6 +75,9 @@ class DBFileJournal extends FileJournal {
try {
$dbw->insert( 'filejournal', $data, __METHOD__ );
+ if ( mt_rand( 0, 99 ) == 0 ) {
+ $this->purgeOldLogs(); // occasionally delete old logs
+ }
} catch ( DBError $e ) {
$status->fatal( 'filejournal-fail-dbquery', $this->backend );
return $status;
@@ -84,6 +87,35 @@ class DBFileJournal extends FileJournal {
}
/**
+ * @see FileJournal::doGetCurrentPosition()
+ * @return integer|false
+ */
+ protected function doGetCurrentPosition() {
+ $dbw = $this->getMasterDB();
+
+ return $dbw->selectField( 'filejournal', 'MAX(fj_id)',
+ array( 'fj_backend' => $this->backend ),
+ __METHOD__
+ );
+ }
+
+ /**
+ * @see FileJournal::doGetPositionAtTime()
+ * @param $time integer|string timestamp
+ * @return integer|false
+ */
+ protected function doGetPositionAtTime( $time ) {
+ $dbw = $this->getMasterDB();
+
+ $encTimestamp = $dbw->addQuotes( $dbw->timestamp( $time ) );
+ return $dbw->selectField( 'filejournal', 'fj_id',
+ array( 'fj_backend' => $this->backend, "fj_timestamp <= $encTimestamp" ),
+ __METHOD__,
+ array( 'ORDER BY' => 'fj_timestamp DESC' )
+ );
+ }
+
+ /**
* @see FileJournal::doGetChangeEntries()
* @return Array
* @throws DBError
diff --git a/includes/filebackend/filejournal/FileJournal.php b/includes/filebackend/filejournal/FileJournal.php
index ce029bbe..a1b7a459 100644
--- a/includes/filebackend/filejournal/FileJournal.php
+++ b/includes/filebackend/filejournal/FileJournal.php
@@ -54,7 +54,7 @@ abstract class FileJournal {
* Create an appropriate FileJournal object from config
*
* @param $config Array
- * @param $backend string A registered file backend name
+ * @param string $backend A registered file backend name
* @throws MWException
* @return FileJournal
*/
@@ -85,13 +85,13 @@ abstract class FileJournal {
/**
* Log changes made by a batch file operation.
* $entries is an array of log entries, each of which contains:
- * op : Basic operation name (create, store, copy, delete)
+ * op : Basic operation name (create, update, delete)
* path : The storage path of the file
* newSha1 : The final base 36 SHA-1 of the file
* Note that 'false' should be used as the SHA-1 for non-existing files.
*
- * @param $entries Array List of file operations (each an array of parameters)
- * @param $batchId string UUID string that identifies the operation batch
+ * @param array $entries List of file operations (each an array of parameters)
+ * @param string $batchId UUID string that identifies the operation batch
* @return Status
*/
final public function logChangeBatch( array $entries, $batchId ) {
@@ -104,13 +104,45 @@ abstract class FileJournal {
/**
* @see FileJournal::logChangeBatch()
*
- * @param $entries Array List of file operations (each an array of parameters)
- * @param $batchId string UUID string that identifies the operation batch
+ * @param array $entries List of file operations (each an array of parameters)
+ * @param string $batchId UUID string that identifies the operation batch
* @return Status
*/
abstract protected function doLogChangeBatch( array $entries, $batchId );
/**
+ * Get the position ID of the latest journal entry
+ *
+ * @return integer|false
+ */
+ final public function getCurrentPosition() {
+ return $this->doGetCurrentPosition();
+ }
+
+ /**
+ * @see FileJournal::getCurrentPosition()
+ * @return integer|false
+ */
+ abstract protected function doGetCurrentPosition();
+
+ /**
+ * Get the position ID of the latest journal entry at some point in time
+ *
+ * @param $time integer|string timestamp
+ * @return integer|false
+ */
+ final public function getPositionAtTime( $time ) {
+ return $this->doGetPositionAtTime( $time );
+ }
+
+ /**
+ * @see FileJournal::getPositionAtTime()
+ * @param $time integer|string timestamp
+ * @return integer|false
+ */
+ abstract protected function doGetPositionAtTime( $time );
+
+ /**
* Get an array of file change log entries.
* A starting change ID and/or limit can be specified.
*
@@ -169,7 +201,7 @@ abstract class FileJournal {
*/
class NullFileJournal extends FileJournal {
/**
- * @see FileJournal::logChangeBatch()
+ * @see FileJournal::doLogChangeBatch()
* @param $entries array
* @param $batchId string
* @return Status
@@ -179,6 +211,23 @@ class NullFileJournal extends FileJournal {
}
/**
+ * @see FileJournal::doGetCurrentPosition()
+ * @return integer|false
+ */
+ protected function doGetCurrentPosition() {
+ return false;
+ }
+
+ /**
+ * @see FileJournal::doGetPositionAtTime()
+ * @param $time integer|string timestamp
+ * @return integer|false
+ */
+ protected function doGetPositionAtTime( $time ) {
+ return false;
+ }
+
+ /**
* @see FileJournal::doGetChangeEntries()
* @return Array
*/
@@ -187,7 +236,7 @@ class NullFileJournal extends FileJournal {
}
/**
- * @see FileJournal::purgeOldLogs()
+ * @see FileJournal::doPurgeOldLogs()
* @return Status
*/
protected function doPurgeOldLogs() {
diff --git a/includes/filebackend/lockmanager/DBLockManager.php b/includes/filebackend/lockmanager/DBLockManager.php
index a8fe258b..f02387dc 100644
--- a/includes/filebackend/lockmanager/DBLockManager.php
+++ b/includes/filebackend/lockmanager/DBLockManager.php
@@ -22,10 +22,9 @@
*/
/**
- * Version of LockManager based on using DB table locks.
+ * Version of LockManager based on using named/row DB locks.
+ *
* This is meant for multi-wiki systems that may share files.
- * All locks are blocking, so it might be useful to set a small
- * lock-wait timeout via server config to curtail deadlocks.
*
* All lock requests for a resource, identified by a hash string, will map
* to one bucket. Each bucket maps to one or several peer DBs, each on their
@@ -37,7 +36,7 @@
* @ingroup LockManager
* @since 1.19
*/
-class DBLockManager extends QuorumLockManager {
+abstract class DBLockManager extends QuorumLockManager {
/** @var Array Map of DB names to server config */
protected $dbServers; // (DB name => server config array)
/** @var BagOStuff */
@@ -67,11 +66,12 @@ class DBLockManager extends QuorumLockManager {
* each having an odd-numbered list of DB names (peers) as values.
* Any DB named 'localDBMaster' will automatically use the DB master
* settings for this wiki (without the need for a dbServers entry).
+ * Only use 'localDBMaster' if the domain is a valid wiki ID.
* - lockExpiry : Lock timeout (seconds) for dropped connections. [optional]
* This tells the DB server how long to wait before assuming
* connection failure and releasing all the locks for a session.
*
- * @param Array $config
+ * @param array $config
*/
public function __construct( array $config ) {
parent::__construct( $config );
@@ -111,65 +111,6 @@ class DBLockManager extends QuorumLockManager {
}
/**
- * Get a connection to a lock DB and acquire locks on $paths.
- * This does not use GET_LOCK() per http://bugs.mysql.com/bug.php?id=1118.
- *
- * @see QuorumLockManager::getLocksOnServer()
- * @return Status
- */
- protected function getLocksOnServer( $lockSrv, array $paths, $type ) {
- $status = Status::newGood();
-
- if ( $type == self::LOCK_EX ) { // writer locks
- try {
- $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) );
- # Build up values for INSERT clause
- $data = array();
- foreach ( $keys as $key ) {
- $data[] = array( 'fle_key' => $key );
- }
- # Wait on any existing writers and block new ones if we get in
- $db = $this->getConnection( $lockSrv ); // checked in isServerUp()
- $db->insert( 'filelocks_exclusive', $data, __METHOD__ );
- } catch ( DBError $e ) {
- foreach ( $paths as $path ) {
- $status->fatal( 'lockmanager-fail-acquirelock', $path );
- }
- }
- }
-
- return $status;
- }
-
- /**
- * @see QuorumLockManager::freeLocksOnServer()
- * @return Status
- */
- protected function freeLocksOnServer( $lockSrv, array $paths, $type ) {
- return Status::newGood(); // not supported
- }
-
- /**
- * @see QuorumLockManager::releaseAllLocks()
- * @return Status
- */
- protected function releaseAllLocks() {
- $status = Status::newGood();
-
- foreach ( $this->conns as $lockDb => $db ) {
- if ( $db->trxLevel() ) { // in transaction
- try {
- $db->rollback( __METHOD__ ); // finish transaction and kill any rows
- } catch ( DBError $e ) {
- $status->fatal( 'lockmanager-fail-db-release', $lockDb );
- }
- }
- }
-
- return $status;
- }
-
- /**
* @see QuorumLockManager::isServerUp()
* @return bool
*/
@@ -197,8 +138,8 @@ class DBLockManager extends QuorumLockManager {
if ( !isset( $this->conns[$lockDb] ) ) {
$db = null;
if ( $lockDb === 'localDBMaster' ) {
- $lb = wfGetLBFactory()->newMainLB();
- $db = $lb->getConnection( DB_MASTER );
+ $lb = wfGetLBFactory()->getMainLB( $this->domain );
+ $db = $lb->getConnection( DB_MASTER, array(), $this->domain );
} elseif ( isset( $this->dbServers[$lockDb] ) ) {
$config = $this->dbServers[$lockDb];
$db = DatabaseBase::factory( $config['type'], $config );
@@ -274,14 +215,8 @@ class DBLockManager extends QuorumLockManager {
* Make sure remaining locks get cleared for sanity
*/
function __destruct() {
+ $this->releaseAllLocks();
foreach ( $this->conns as $db ) {
- if ( $db->trxLevel() ) { // in transaction
- try {
- $db->rollback( __METHOD__ ); // finish transaction and kill any rows
- } catch ( DBError $e ) {
- // oh well
- }
- }
$db->close();
}
}
@@ -321,27 +256,38 @@ class MySqlLockManager extends DBLockManager {
$status = Status::newGood();
$db = $this->getConnection( $lockSrv ); // checked in isServerUp()
- $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) );
+
+ $keys = array(); // list of hash keys for the paths
+ $data = array(); // list of rows to insert
+ $checkEXKeys = array(); // list of hash keys that this has no EX lock on
# Build up values for INSERT clause
- $data = array();
- foreach ( $keys as $key ) {
+ foreach ( $paths as $path ) {
+ $key = $this->sha1Base36Absolute( $path );
+ $keys[] = $key;
$data[] = array( 'fls_key' => $key, 'fls_session' => $this->session );
+ if ( !isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
+ $checkEXKeys[] = $key;
+ }
}
- # Block new writers...
+
+ # Block new writers (both EX and SH locks leave entries here)...
$db->insert( 'filelocks_shared', $data, __METHOD__, array( 'IGNORE' ) );
# Actually do the locking queries...
if ( $type == self::LOCK_SH ) { // reader locks
+ $blocked = false;
# Bail if there are any existing writers...
- $blocked = $db->selectField( 'filelocks_exclusive', '1',
- array( 'fle_key' => $keys ),
- __METHOD__
- );
- # Prospective writers that haven't yet updated filelocks_exclusive
- # will recheck filelocks_shared after doing so and bail due to our entry.
+ if ( count( $checkEXKeys ) ) {
+ $blocked = $db->selectField( 'filelocks_exclusive', '1',
+ array( 'fle_key' => $checkEXKeys ),
+ __METHOD__
+ );
+ }
+ # Other prospective writers that haven't yet updated filelocks_exclusive
+ # will recheck filelocks_shared after doing so and bail due to this entry.
} else { // writer locks
$encSession = $db->addQuotes( $this->session );
# Bail if there are any existing writers...
- # The may detect readers, but the safe check for them is below.
+ # This may detect readers, but the safe check for them is below.
# Note: if two writers come at the same time, both bail :)
$blocked = $db->selectField( 'filelocks_shared', '1',
array( 'fls_key' => $keys, "fls_session != $encSession" ),
@@ -371,4 +317,117 @@ class MySqlLockManager extends DBLockManager {
return $status;
}
+
+ /**
+ * @see QuorumLockManager::freeLocksOnServer()
+ * @return Status
+ */
+ protected function freeLocksOnServer( $lockSrv, array $paths, $type ) {
+ return Status::newGood(); // not supported
+ }
+
+ /**
+ * @see QuorumLockManager::releaseAllLocks()
+ * @return Status
+ */
+ protected function releaseAllLocks() {
+ $status = Status::newGood();
+
+ foreach ( $this->conns as $lockDb => $db ) {
+ if ( $db->trxLevel() ) { // in transaction
+ try {
+ $db->rollback( __METHOD__ ); // finish transaction and kill any rows
+ } catch ( DBError $e ) {
+ $status->fatal( 'lockmanager-fail-db-release', $lockDb );
+ }
+ }
+ }
+
+ return $status;
+ }
+}
+
+/**
+ * PostgreSQL version of DBLockManager that supports shared locks.
+ * All locks are non-blocking, which avoids deadlocks.
+ *
+ * @ingroup LockManager
+ */
+class PostgreSqlLockManager extends DBLockManager {
+ /** @var Array Mapping of lock types to the type actually used */
+ protected $lockTypeMap = array(
+ self::LOCK_SH => self::LOCK_SH,
+ self::LOCK_UW => self::LOCK_SH,
+ self::LOCK_EX => self::LOCK_EX
+ );
+
+ protected function getLocksOnServer( $lockSrv, array $paths, $type ) {
+ $status = Status::newGood();
+ if ( !count( $paths ) ) {
+ return $status; // nothing to lock
+ }
+
+ $db = $this->getConnection( $lockSrv ); // checked in isServerUp()
+ $bigints = array_unique( array_map(
+ function( $key ) { return wfBaseConvert( substr( $key, 0, 15 ), 16, 10 ); },
+ array_map( array( $this, 'sha1Base16Absolute' ), $paths )
+ ) );
+
+ // Try to acquire all the locks...
+ $fields = array();
+ foreach ( $bigints as $bigint ) {
+ $fields[] = ( $type == self::LOCK_SH )
+ ? "pg_try_advisory_lock_shared({$db->addQuotes( $bigint )}) AS K$bigint"
+ : "pg_try_advisory_lock({$db->addQuotes( $bigint )}) AS K$bigint";
+ }
+ $res = $db->query( 'SELECT ' . implode( ', ', $fields ), __METHOD__ );
+ $row = (array)$res->fetchObject();
+
+ if ( in_array( 'f', $row ) ) {
+ // Release any acquired locks if some could not be acquired...
+ $fields = array();
+ foreach ( $row as $kbigint => $ok ) {
+ if ( $ok === 't' ) { // locked
+ $bigint = substr( $kbigint, 1 ); // strip off the "K"
+ $fields[] = ( $type == self::LOCK_SH )
+ ? "pg_advisory_unlock_shared({$db->addQuotes( $bigint )})"
+ : "pg_advisory_unlock({$db->addQuotes( $bigint )})";
+ }
+ }
+ if ( count( $fields ) ) {
+ $db->query( 'SELECT ' . implode( ', ', $fields ), __METHOD__ );
+ }
+ foreach ( $paths as $path ) {
+ $status->fatal( 'lockmanager-fail-acquirelock', $path );
+ }
+ }
+
+ return $status;
+ }
+
+ /**
+ * @see QuorumLockManager::freeLocksOnServer()
+ * @return Status
+ */
+ protected function freeLocksOnServer( $lockSrv, array $paths, $type ) {
+ return Status::newGood(); // not supported
+ }
+
+ /**
+ * @see QuorumLockManager::releaseAllLocks()
+ * @return Status
+ */
+ protected function releaseAllLocks() {
+ $status = Status::newGood();
+
+ foreach ( $this->conns as $lockDb => $db ) {
+ try {
+ $db->query( "SELECT pg_advisory_unlock_all()", __METHOD__ );
+ } catch ( DBError $e ) {
+ $status->fatal( 'lockmanager-fail-db-release', $lockDb );
+ }
+ }
+
+ return $status;
+ }
}
diff --git a/includes/filebackend/lockmanager/FSLockManager.php b/includes/filebackend/lockmanager/FSLockManager.php
index 9a6206fd..eacba704 100644
--- a/includes/filebackend/lockmanager/FSLockManager.php
+++ b/includes/filebackend/lockmanager/FSLockManager.php
@@ -43,7 +43,7 @@ class FSLockManager extends LockManager {
protected $lockDir; // global dir for all servers
- /** @var Array Map of (locked key => lock type => lock file handle) */
+ /** @var Array Map of (locked key => lock file handle) */
protected $handles = array();
/**
@@ -115,12 +115,16 @@ class FSLockManager extends LockManager {
} elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
$this->locksHeld[$path][$type] = 1;
} else {
- wfSuppressWarnings();
- $handle = fopen( $this->getLockPath( $path ), 'a+' );
- wfRestoreWarnings();
- if ( !$handle ) { // lock dir missing?
- wfMkdirParents( $this->lockDir );
- $handle = fopen( $this->getLockPath( $path ), 'a+' ); // try again
+ if ( isset( $this->handles[$path] ) ) {
+ $handle = $this->handles[$path];
+ } else {
+ wfSuppressWarnings();
+ $handle = fopen( $this->getLockPath( $path ), 'a+' );
+ wfRestoreWarnings();
+ if ( !$handle ) { // lock dir missing?
+ wfMkdirParents( $this->lockDir );
+ $handle = fopen( $this->getLockPath( $path ), 'a+' ); // try again
+ }
}
if ( $handle ) {
// Either a shared or exclusive lock
@@ -128,7 +132,7 @@ class FSLockManager extends LockManager {
if ( flock( $handle, $lock | LOCK_NB ) ) {
// Record this lock as active
$this->locksHeld[$path][$type] = 1;
- $this->handles[$path][$type] = $handle;
+ $this->handles[$path] = $handle;
} else {
fclose( $handle );
$status->fatal( 'lockmanager-fail-acquirelock', $path );
@@ -160,24 +164,13 @@ class FSLockManager extends LockManager {
--$this->locksHeld[$path][$type];
if ( $this->locksHeld[$path][$type] <= 0 ) {
unset( $this->locksHeld[$path][$type] );
- // If a LOCK_SH comes in while we have a LOCK_EX, we don't
- // actually add a handler, so check for handler existence.
- if ( isset( $this->handles[$path][$type] ) ) {
- if ( $type === self::LOCK_EX
- && isset( $this->locksHeld[$path][self::LOCK_SH] )
- && !isset( $this->handles[$path][self::LOCK_SH] ) )
- {
- // EX lock came first: move this handle to the SH one
- $this->handles[$path][self::LOCK_SH] = $this->handles[$path][$type];
- } else {
- // Mark this handle to be unlocked and closed
- $handlesToClose[] = $this->handles[$path][$type];
- }
- unset( $this->handles[$path][$type] );
- }
}
if ( !count( $this->locksHeld[$path] ) ) {
unset( $this->locksHeld[$path] ); // no locks on this path
+ if ( isset( $this->handles[$path] ) ) {
+ $handlesToClose[] = $this->handles[$path];
+ unset( $this->handles[$path] );
+ }
}
// Unlock handles to release locks and delete
// any lock files that end up with no locks on them...
@@ -237,8 +230,7 @@ class FSLockManager extends LockManager {
* @return string
*/
protected function getLockPath( $path ) {
- $hash = self::sha1Base36( $path );
- return "{$this->lockDir}/{$hash}.lock";
+ return "{$this->lockDir}/{$this->sha1Base36Absolute( $path )}.lock";
}
/**
diff --git a/includes/filebackend/lockmanager/LSLockManager.php b/includes/filebackend/lockmanager/LSLockManager.php
index 89428182..97de8dca 100644
--- a/includes/filebackend/lockmanager/LSLockManager.php
+++ b/includes/filebackend/lockmanager/LSLockManager.php
@@ -66,7 +66,7 @@ class LSLockManager extends QuorumLockManager {
* each having an odd-numbered list of server names (peers) as values.
* - connTimeout : Lock server connection attempt timeout. [optional]
*
- * @param Array $config
+ * @param array $config
*/
public function __construct( array $config ) {
parent::__construct( $config );
@@ -94,7 +94,7 @@ class LSLockManager extends QuorumLockManager {
// Send out the command and get the response...
$type = ( $type == self::LOCK_SH ) ? 'SH' : 'EX';
- $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) );
+ $keys = array_unique( array_map( array( $this, 'sha1Base36Absolute' ), $paths ) );
$response = $this->sendCommand( $lockSrv, 'ACQUIRE', $type, $keys );
if ( $response !== 'ACQUIRED' ) {
@@ -115,7 +115,7 @@ class LSLockManager extends QuorumLockManager {
// Send out the command and get the response...
$type = ( $type == self::LOCK_SH ) ? 'SH' : 'EX';
- $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) );
+ $keys = array_unique( array_map( array( $this, 'sha1Base36Absolute' ), $paths ) );
$response = $this->sendCommand( $lockSrv, 'RELEASE', $type, $keys );
if ( $response !== 'RELEASED' ) {
@@ -169,7 +169,7 @@ class LSLockManager extends QuorumLockManager {
$authKey = $this->lockServers[$lockSrv]['authKey'];
// Build of the command as a flat string...
$values = implode( '|', $values );
- $key = sha1( $this->session . $action . $type . $values . $authKey );
+ $key = hash_hmac( 'sha1', "{$this->session}\n{$action}\n{$type}\n{$values}", $authKey );
// Send out the command...
if ( fwrite( $conn, "{$this->session}:$key:$action:$type:$values\n" ) === false ) {
return false;
diff --git a/includes/filebackend/lockmanager/LockManager.php b/includes/filebackend/lockmanager/LockManager.php
index 07853f87..0512a01b 100644
--- a/includes/filebackend/lockmanager/LockManager.php
+++ b/includes/filebackend/lockmanager/LockManager.php
@@ -53,6 +53,9 @@ abstract class LockManager {
/** @var Array Map of (resource path => lock type => count) */
protected $locksHeld = array();
+ protected $domain; // string; domain (usually wiki ID)
+ protected $lockTTL; // integer; maximum time locks can be held
+
/* Lock types; stronger locks have higher values */
const LOCK_SH = 1; // shared lock (for reads)
const LOCK_UW = 2; // shared lock (for reads used to write elsewhere)
@@ -61,14 +64,29 @@ abstract class LockManager {
/**
* Construct a new instance from configuration
*
+ * $config paramaters include:
+ * - domain : Domain (usually wiki ID) that all resources are relative to [optional]
+ * - lockTTL : Age (in seconds) at which resource locks should expire.
+ * This only applies if locks are not tied to a connection/process.
+ *
* @param $config Array
*/
- public function __construct( array $config ) {}
+ public function __construct( array $config ) {
+ $this->domain = isset( $config['domain'] ) ? $config['domain'] : wfWikiID();
+ if ( isset( $config['lockTTL'] ) ) {
+ $this->lockTTL = max( 1, $config['lockTTL'] );
+ } elseif ( PHP_SAPI === 'cli' ) {
+ $this->lockTTL = 2*3600;
+ } else {
+ $met = ini_get( 'max_execution_time' ); // this is 0 in CLI mode
+ $this->lockTTL = max( 5*60, 2*(int)$met );
+ }
+ }
/**
* Lock the resources at the given abstract paths
*
- * @param $paths Array List of resource names
+ * @param array $paths List of resource names
* @param $type integer LockManager::LOCK_* constant
* @return Status
*/
@@ -82,7 +100,7 @@ abstract class LockManager {
/**
* Unlock the resources at the given abstract paths
*
- * @param $paths Array List of storage paths
+ * @param array $paths List of storage paths
* @param $type integer LockManager::LOCK_* constant
* @return Status
*/
@@ -94,308 +112,46 @@ abstract class LockManager {
}
/**
- * Get the base 36 SHA-1 of a string, padded to 31 digits
+ * Get the base 36 SHA-1 of a string, padded to 31 digits.
+ * Before hashing, the path will be prefixed with the domain ID.
+ * This should be used interally for lock key or file names.
*
* @param $path string
* @return string
*/
- final protected static function sha1Base36( $path ) {
- return wfBaseConvert( sha1( $path ), 16, 36, 31 );
+ final protected function sha1Base36Absolute( $path ) {
+ return wfBaseConvert( sha1( "{$this->domain}:{$path}" ), 16, 36, 31 );
}
/**
- * Lock resources with the given keys and lock type
+ * Get the base 16 SHA-1 of a string, padded to 31 digits.
+ * Before hashing, the path will be prefixed with the domain ID.
+ * This should be used interally for lock key or file names.
*
- * @param $paths Array List of storage paths
- * @param $type integer LockManager::LOCK_* constant
+ * @param $path string
* @return string
*/
- abstract protected function doLock( array $paths, $type );
+ final protected function sha1Base16Absolute( $path ) {
+ return sha1( "{$this->domain}:{$path}" );
+ }
/**
- * Unlock resources with the given keys and lock type
+ * Lock resources with the given keys and lock type
*
- * @param $paths Array List of storage paths
+ * @param array $paths List of storage paths
* @param $type integer LockManager::LOCK_* constant
* @return string
*/
- abstract protected function doUnlock( array $paths, $type );
-}
-
-/**
- * Self-releasing locks
- *
- * LockManager helper class to handle scoped locks, which
- * release when an object is destroyed or goes out of scope.
- *
- * @ingroup LockManager
- * @since 1.19
- */
-class ScopedLock {
- /** @var LockManager */
- protected $manager;
- /** @var Status */
- protected $status;
- /** @var Array List of resource paths*/
- protected $paths;
-
- protected $type; // integer lock type
-
- /**
- * @param $manager LockManager
- * @param $paths Array List of storage paths
- * @param $type integer LockManager::LOCK_* constant
- * @param $status Status
- */
- protected function __construct(
- LockManager $manager, array $paths, $type, Status $status
- ) {
- $this->manager = $manager;
- $this->paths = $paths;
- $this->status = $status;
- $this->type = $type;
- }
+ abstract protected function doLock( array $paths, $type );
/**
- * Get a ScopedLock object representing a lock on resource paths.
- * Any locks are released once this object goes out of scope.
- * The status object is updated with any errors or warnings.
+ * Unlock resources with the given keys and lock type
*
- * @param $manager LockManager
- * @param $paths Array List of storage paths
+ * @param array $paths List of storage paths
* @param $type integer LockManager::LOCK_* constant
- * @param $status Status
- * @return ScopedLock|null Returns null on failure
- */
- public static function factory(
- LockManager $manager, array $paths, $type, Status $status
- ) {
- $lockStatus = $manager->lock( $paths, $type );
- $status->merge( $lockStatus );
- if ( $lockStatus->isOK() ) {
- return new self( $manager, $paths, $type, $status );
- }
- return null;
- }
-
- function __destruct() {
- $wasOk = $this->status->isOK();
- $this->status->merge( $this->manager->unlock( $this->paths, $this->type ) );
- if ( $wasOk ) {
- // Make sure status is OK, despite any unlockFiles() fatals
- $this->status->setResult( true, $this->status->value );
- }
- }
-}
-
-/**
- * Version of LockManager that uses a quorum from peer servers for locks.
- * The resource space can also be sharded into separate peer groups.
- *
- * @ingroup LockManager
- * @since 1.20
- */
-abstract class QuorumLockManager extends LockManager {
- /** @var Array Map of bucket indexes to peer server lists */
- protected $srvsByBucket = array(); // (bucket index => (lsrv1, lsrv2, ...))
-
- /**
- * @see LockManager::doLock()
- * @param $paths array
- * @param $type int
- * @return Status
- */
- final protected function doLock( array $paths, $type ) {
- $status = Status::newGood();
-
- $pathsToLock = array(); // (bucket => paths)
- // Get locks that need to be acquired (buckets => locks)...
- foreach ( $paths as $path ) {
- if ( isset( $this->locksHeld[$path][$type] ) ) {
- ++$this->locksHeld[$path][$type];
- } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
- $this->locksHeld[$path][$type] = 1;
- } else {
- $bucket = $this->getBucketFromKey( $path );
- $pathsToLock[$bucket][] = $path;
- }
- }
-
- $lockedPaths = array(); // files locked in this attempt
- // Attempt to acquire these locks...
- foreach ( $pathsToLock as $bucket => $paths ) {
- // Try to acquire the locks for this bucket
- $status->merge( $this->doLockingRequestBucket( $bucket, $paths, $type ) );
- if ( !$status->isOK() ) {
- $status->merge( $this->doUnlock( $lockedPaths, $type ) );
- return $status;
- }
- // Record these locks as active
- foreach ( $paths as $path ) {
- $this->locksHeld[$path][$type] = 1; // locked
- }
- // Keep track of what locks were made in this attempt
- $lockedPaths = array_merge( $lockedPaths, $paths );
- }
-
- return $status;
- }
-
- /**
- * @see LockManager::doUnlock()
- * @param $paths array
- * @param $type int
- * @return Status
- */
- final protected function doUnlock( array $paths, $type ) {
- $status = Status::newGood();
-
- $pathsToUnlock = array();
- foreach ( $paths as $path ) {
- if ( !isset( $this->locksHeld[$path][$type] ) ) {
- $status->warning( 'lockmanager-notlocked', $path );
- } else {
- --$this->locksHeld[$path][$type];
- // Reference count the locks held and release locks when zero
- if ( $this->locksHeld[$path][$type] <= 0 ) {
- unset( $this->locksHeld[$path][$type] );
- $bucket = $this->getBucketFromKey( $path );
- $pathsToUnlock[$bucket][] = $path;
- }
- if ( !count( $this->locksHeld[$path] ) ) {
- unset( $this->locksHeld[$path] ); // no SH or EX locks left for key
- }
- }
- }
-
- // Remove these specific locks if possible, or at least release
- // all locks once this process is currently not holding any locks.
- foreach ( $pathsToUnlock as $bucket => $paths ) {
- $status->merge( $this->doUnlockingRequestBucket( $bucket, $paths, $type ) );
- }
- if ( !count( $this->locksHeld ) ) {
- $status->merge( $this->releaseAllLocks() );
- }
-
- return $status;
- }
-
- /**
- * Attempt to acquire locks with the peers for a bucket.
- * This is all or nothing; if any key is locked then this totally fails.
- *
- * @param $bucket integer
- * @param $paths Array List of resource keys to lock
- * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
- * @return Status
- */
- final protected function doLockingRequestBucket( $bucket, array $paths, $type ) {
- $status = Status::newGood();
-
- $yesVotes = 0; // locks made on trustable servers
- $votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers
- $quorum = floor( $votesLeft/2 + 1 ); // simple majority
- // Get votes for each peer, in order, until we have enough...
- foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) {
- if ( !$this->isServerUp( $lockSrv ) ) {
- --$votesLeft;
- $status->warning( 'lockmanager-fail-svr-acquire', $lockSrv );
- continue; // server down?
- }
- // Attempt to acquire the lock on this peer
- $status->merge( $this->getLocksOnServer( $lockSrv, $paths, $type ) );
- if ( !$status->isOK() ) {
- return $status; // vetoed; resource locked
- }
- ++$yesVotes; // success for this peer
- if ( $yesVotes >= $quorum ) {
- return $status; // lock obtained
- }
- --$votesLeft;
- $votesNeeded = $quorum - $yesVotes;
- if ( $votesNeeded > $votesLeft ) {
- break; // short-circuit
- }
- }
- // At this point, we must not have met the quorum
- $status->setResult( false );
-
- return $status;
- }
-
- /**
- * Attempt to release locks with the peers for a bucket
- *
- * @param $bucket integer
- * @param $paths Array List of resource keys to lock
- * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
- * @return Status
- */
- final protected function doUnlockingRequestBucket( $bucket, array $paths, $type ) {
- $status = Status::newGood();
-
- foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) {
- if ( !$this->isServerUp( $lockSrv ) ) {
- $status->fatal( 'lockmanager-fail-svr-release', $lockSrv );
- // Attempt to release the lock on this peer
- } else {
- $status->merge( $this->freeLocksOnServer( $lockSrv, $paths, $type ) );
- }
- }
-
- return $status;
- }
-
- /**
- * Get the bucket for resource path.
- * This should avoid throwing any exceptions.
- *
- * @param $path string
- * @return integer
- */
- protected function getBucketFromKey( $path ) {
- $prefix = substr( sha1( $path ), 0, 2 ); // first 2 hex chars (8 bits)
- return (int)base_convert( $prefix, 16, 10 ) % count( $this->srvsByBucket );
- }
-
- /**
- * Check if a lock server is up
- *
- * @param $lockSrv string
- * @return bool
- */
- abstract protected function isServerUp( $lockSrv );
-
- /**
- * Get a connection to a lock server and acquire locks on $paths
- *
- * @param $lockSrv string
- * @param $paths array
- * @param $type integer
- * @return Status
- */
- abstract protected function getLocksOnServer( $lockSrv, array $paths, $type );
-
- /**
- * Get a connection to a lock server and release locks on $paths.
- *
- * Subclasses must effectively implement this or releaseAllLocks().
- *
- * @param $lockSrv string
- * @param $paths array
- * @param $type integer
- * @return Status
- */
- abstract protected function freeLocksOnServer( $lockSrv, array $paths, $type );
-
- /**
- * Release all locks that this session is holding.
- *
- * Subclasses must effectively implement this or freeLocksOnServer().
- *
- * @return Status
+ * @return string
*/
- abstract protected function releaseAllLocks();
+ abstract protected function doUnlock( array $paths, $type );
}
/**
diff --git a/includes/filebackend/lockmanager/LockManagerGroup.php b/includes/filebackend/lockmanager/LockManagerGroup.php
index 8c8c940a..ac0bd49b 100644
--- a/includes/filebackend/lockmanager/LockManagerGroup.php
+++ b/includes/filebackend/lockmanager/LockManagerGroup.php
@@ -29,33 +29,41 @@
* @since 1.19
*/
class LockManagerGroup {
- /**
- * @var LockManagerGroup
- */
- protected static $instance = null;
+ /** @var Array (domain => LockManager) */
+ protected static $instances = array();
+
+ protected $domain; // string; domain (usually wiki ID)
- /** @var Array of (name => ('class' =>, 'config' =>, 'instance' =>)) */
+ /** @var Array of (name => ('class' => ..., 'config' => ..., 'instance' => ...)) */
protected $managers = array();
- protected function __construct() {}
+ /**
+ * @param string $domain Domain (usually wiki ID)
+ */
+ protected function __construct( $domain ) {
+ $this->domain = $domain;
+ }
/**
+ * @param string $domain Domain (usually wiki ID)
* @return LockManagerGroup
*/
- public static function singleton() {
- if ( self::$instance == null ) {
- self::$instance = new self();
- self::$instance->initFromGlobals();
+ public static function singleton( $domain = false ) {
+ $domain = ( $domain === false ) ? wfWikiID() : $domain;
+ if ( !isset( self::$instances[$domain] ) ) {
+ self::$instances[$domain] = new self( $domain );
+ self::$instances[$domain]->initFromGlobals();
}
- return self::$instance;
+ return self::$instances[$domain];
}
/**
- * Destroy the singleton instance, so that a new one will be created next
- * time singleton() is called.
+ * Destroy the singleton instances
+ *
+ * @return void
*/
- public static function destroySingleton() {
- self::$instance = null;
+ public static function destroySingletons() {
+ self::$instances = array();
}
/**
@@ -78,6 +86,7 @@ class LockManagerGroup {
*/
protected function register( array $configs ) {
foreach ( $configs as $config ) {
+ $config['domain'] = $this->domain;
if ( !isset( $config['name'] ) ) {
throw new MWException( "Cannot register a lock manager with no name." );
}
@@ -116,6 +125,21 @@ class LockManagerGroup {
}
/**
+ * Get the config array for a lock manager object with a given name
+ *
+ * @param $name string
+ * @return Array
+ * @throws MWException
+ */
+ public function config( $name ) {
+ if ( !isset( $this->managers[$name] ) ) {
+ throw new MWException( "No lock manager defined with the name `$name`." );
+ }
+ $class = $this->managers[$name]['class'];
+ return array( 'class' => $class ) + $this->managers[$name]['config'];
+ }
+
+ /**
* Get the default lock manager configured for the site.
* Returns NullLockManager if no lock manager could be found.
*
diff --git a/includes/filebackend/lockmanager/MemcLockManager.php b/includes/filebackend/lockmanager/MemcLockManager.php
index 57c0463d..fafc588a 100644
--- a/includes/filebackend/lockmanager/MemcLockManager.php
+++ b/includes/filebackend/lockmanager/MemcLockManager.php
@@ -28,8 +28,8 @@
* This is meant for multi-wiki systems that may share files.
* All locks are non-blocking, which avoids deadlocks.
*
- * All lock requests for a resource, identified by a hash string, will map
- * to one bucket. Each bucket maps to one or several peer servers, each running memcached.
+ * All lock requests for a resource, identified by a hash string, will map to one
+ * bucket. Each bucket maps to one or several peer servers, each running memcached.
* A majority of peers must agree for a lock to be acquired.
*
* @ingroup LockManager
@@ -48,9 +48,7 @@ class MemcLockManager extends QuorumLockManager {
/** @var Array */
protected $serversUp = array(); // (server name => bool)
- protected $lockExpiry; // integer; maximum time locks can be held
- protected $session = ''; // string; random SHA-1 UUID
- protected $wikiId = ''; // string
+ protected $session = ''; // string; random UUID
/**
* Construct a new instance from configuration.
@@ -61,9 +59,9 @@ class MemcLockManager extends QuorumLockManager {
* each having an odd-numbered list of server names (peers) as values.
* - memcConfig : Configuration array for ObjectCache::newFromParams. [optional]
* If set, this must use one of the memcached classes.
- * - wikiId : Wiki ID string that all resources are relative to. [optional]
*
- * @param Array $config
+ * @param array $config
+ * @throws MWException
*/
public function __construct( array $config ) {
parent::__construct( $config );
@@ -87,11 +85,6 @@ class MemcLockManager extends QuorumLockManager {
}
}
- $this->wikiId = isset( $config['wikiId'] ) ? $config['wikiId'] : wfWikiID();
-
- $met = ini_get( 'max_execution_time' ); // this is 0 in CLI mode
- $this->lockExpiry = $met ? 2*(int)$met : 2*3600;
-
$this->session = wfRandomString( 32 );
}
@@ -110,7 +103,7 @@ class MemcLockManager extends QuorumLockManager {
foreach ( $paths as $path ) {
$status->fatal( 'lockmanager-fail-acquirelock', $path );
}
- return;
+ return $status;
}
// Fetch all the existing lock records...
@@ -121,8 +114,8 @@ class MemcLockManager extends QuorumLockManager {
foreach ( $paths as $path ) {
$locksKey = $this->recordKeyForPath( $path );
$locksHeld = isset( $lockRecords[$locksKey] )
- ? $lockRecords[$locksKey]
- : array( self::LOCK_SH => array(), self::LOCK_EX => array() ); // init
+ ? self::sanitizeLockArray( $lockRecords[$locksKey] )
+ : self::newLockArray(); // init
foreach ( $locksHeld[self::LOCK_EX] as $session => $expiry ) {
if ( $expiry < $now ) { // stale?
unset( $locksHeld[self::LOCK_EX][$session] );
@@ -141,7 +134,7 @@ class MemcLockManager extends QuorumLockManager {
}
if ( $status->isOK() ) {
// Register the session in the lock record array
- $locksHeld[$type][$this->session] = $now + $this->lockExpiry;
+ $locksHeld[$type][$this->session] = $now + $this->lockTTL;
// We will update this record if none of the other locks conflict
$lockRecords[$locksKey] = $locksHeld;
}
@@ -149,9 +142,15 @@ class MemcLockManager extends QuorumLockManager {
// If there were no lock conflicts, update all the lock records...
if ( $status->isOK() ) {
- foreach ( $lockRecords as $locksKey => $locksHeld ) {
- $memc->set( $locksKey, $locksHeld );
- wfDebug( __METHOD__ . ": acquired lock on key $locksKey.\n" );
+ foreach ( $paths as $path ) {
+ $locksKey = $this->recordKeyForPath( $path );
+ $locksHeld = $lockRecords[$locksKey];
+ $ok = $memc->set( $locksKey, $locksHeld, 7*86400 );
+ if ( !$ok ) {
+ $status->fatal( 'lockmanager-fail-acquirelock', $path );
+ } else {
+ wfDebug( __METHOD__ . ": acquired lock on key $locksKey.\n" );
+ }
}
}
@@ -186,17 +185,22 @@ class MemcLockManager extends QuorumLockManager {
foreach ( $paths as $path ) {
$locksKey = $this->recordKeyForPath( $path ); // lock record
if ( !isset( $lockRecords[$locksKey] ) ) {
+ $status->warning( 'lockmanager-fail-releaselock', $path );
continue; // nothing to do
}
- $locksHeld = $lockRecords[$locksKey];
- if ( is_array( $locksHeld ) && isset( $locksHeld[$type] ) ) {
- unset( $locksHeld[$type][$this->session] );
- $ok = $memc->set( $locksKey, $locksHeld );
+ $locksHeld = self::sanitizeLockArray( $lockRecords[$locksKey] );
+ if ( isset( $locksHeld[$type][$this->session] ) ) {
+ unset( $locksHeld[$type][$this->session] ); // unregister this session
+ if ( $locksHeld === self::newLockArray() ) {
+ $ok = $memc->delete( $locksKey );
+ } else {
+ $ok = $memc->set( $locksKey, $locksHeld );
+ }
+ if ( !$ok ) {
+ $status->fatal( 'lockmanager-fail-releaselock', $path );
+ }
} else {
- $ok = true;
- }
- if ( !$ok ) {
- $status->fatal( 'lockmanager-fail-releaselock', $path );
+ $status->warning( 'lockmanager-fail-releaselock', $path );
}
wfDebug( __METHOD__ . ": released lock on key $locksKey.\n" );
}
@@ -226,7 +230,7 @@ class MemcLockManager extends QuorumLockManager {
/**
* Get the MemcachedBagOStuff object for a $lockSrv
*
- * @param $lockSrv string Server name
+ * @param string $lockSrv Server name
* @return MemcachedBagOStuff|null
*/
protected function getCache( $lockSrv ) {
@@ -234,7 +238,7 @@ class MemcLockManager extends QuorumLockManager {
if ( isset( $this->bagOStuffs[$lockSrv] ) ) {
$memc = $this->bagOStuffs[$lockSrv];
if ( !isset( $this->serversUp[$lockSrv] ) ) {
- $this->serversUp[$lockSrv] = $memc->set( 'MemcLockManager:ping', 1, 1 );
+ $this->serversUp[$lockSrv] = $memc->set( __CLASS__ . ':ping', 1, 1 );
if ( !$this->serversUp[$lockSrv] ) {
trigger_error( __METHOD__ . ": Could not contact $lockSrv.", E_USER_WARNING );
}
@@ -251,14 +255,32 @@ class MemcLockManager extends QuorumLockManager {
* @return string
*/
protected function recordKeyForPath( $path ) {
- $hash = LockManager::sha1Base36( $path );
- list( $db, $prefix ) = wfSplitWikiID( $this->wikiId );
- return wfForeignMemcKey( $db, $prefix, __CLASS__, 'locks', $hash );
+ return implode( ':', array( __CLASS__, 'locks', $this->sha1Base36Absolute( $path ) ) );
+ }
+
+ /**
+ * @return Array An empty lock structure for a key
+ */
+ protected static function newLockArray() {
+ return array( self::LOCK_SH => array(), self::LOCK_EX => array() );
+ }
+
+ /**
+ * @param $a array
+ * @return Array An empty lock structure for a key
+ */
+ protected static function sanitizeLockArray( $a ) {
+ if ( is_array( $a ) && isset( $a[self::LOCK_EX] ) && isset( $a[self::LOCK_SH] ) ) {
+ return $a;
+ } else {
+ trigger_error( __METHOD__ . ": reset invalid lock array.", E_USER_WARNING );
+ return self::newLockArray();
+ }
}
/**
* @param $memc MemcachedBagOStuff
- * @param $keys Array List of keys to acquire
+ * @param array $keys List of keys to acquire
* @return bool
*/
protected function acquireMutexes( MemcachedBagOStuff $memc, array $keys ) {
@@ -284,10 +306,10 @@ class MemcLockManager extends QuorumLockManager {
continue; // acquire in order
}
}
- } while ( count( $lockedKeys ) < count( $keys ) && ( microtime( true ) - $start ) <= 6 );
+ } while ( count( $lockedKeys ) < count( $keys ) && ( microtime( true ) - $start ) <= 3 );
if ( count( $lockedKeys ) != count( $keys ) ) {
- $this->releaseMutexes( $lockedKeys ); // failed; release what was locked
+ $this->releaseMutexes( $memc, $lockedKeys ); // failed; release what was locked
return false;
}
@@ -296,7 +318,7 @@ class MemcLockManager extends QuorumLockManager {
/**
* @param $memc MemcachedBagOStuff
- * @param $keys Array List of acquired keys
+ * @param array $keys List of acquired keys
* @return void
*/
protected function releaseMutexes( MemcachedBagOStuff $memc, array $keys ) {
diff --git a/includes/filebackend/lockmanager/QuorumLockManager.php b/includes/filebackend/lockmanager/QuorumLockManager.php
new file mode 100644
index 00000000..b331b540
--- /dev/null
+++ b/includes/filebackend/lockmanager/QuorumLockManager.php
@@ -0,0 +1,230 @@
+<?php
+/**
+ * Version of LockManager that uses a quorum from peer servers for locks.
+ *
+ * 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 LockManager
+ */
+
+/**
+ * Version of LockManager that uses a quorum from peer servers for locks.
+ * The resource space can also be sharded into separate peer groups.
+ *
+ * @ingroup LockManager
+ * @since 1.20
+ */
+abstract class QuorumLockManager extends LockManager {
+ /** @var Array Map of bucket indexes to peer server lists */
+ protected $srvsByBucket = array(); // (bucket index => (lsrv1, lsrv2, ...))
+
+ /**
+ * @see LockManager::doLock()
+ * @param $paths array
+ * @param $type int
+ * @return Status
+ */
+ final protected function doLock( array $paths, $type ) {
+ $status = Status::newGood();
+
+ $pathsToLock = array(); // (bucket => paths)
+ // Get locks that need to be acquired (buckets => locks)...
+ foreach ( $paths as $path ) {
+ if ( isset( $this->locksHeld[$path][$type] ) ) {
+ ++$this->locksHeld[$path][$type];
+ } else {
+ $bucket = $this->getBucketFromPath( $path );
+ $pathsToLock[$bucket][] = $path;
+ }
+ }
+
+ $lockedPaths = array(); // files locked in this attempt
+ // Attempt to acquire these locks...
+ foreach ( $pathsToLock as $bucket => $paths ) {
+ // Try to acquire the locks for this bucket
+ $status->merge( $this->doLockingRequestBucket( $bucket, $paths, $type ) );
+ if ( !$status->isOK() ) {
+ $status->merge( $this->doUnlock( $lockedPaths, $type ) );
+ return $status;
+ }
+ // Record these locks as active
+ foreach ( $paths as $path ) {
+ $this->locksHeld[$path][$type] = 1; // locked
+ }
+ // Keep track of what locks were made in this attempt
+ $lockedPaths = array_merge( $lockedPaths, $paths );
+ }
+
+ return $status;
+ }
+
+ /**
+ * @see LockManager::doUnlock()
+ * @param $paths array
+ * @param $type int
+ * @return Status
+ */
+ final protected function doUnlock( array $paths, $type ) {
+ $status = Status::newGood();
+
+ $pathsToUnlock = array();
+ foreach ( $paths as $path ) {
+ if ( !isset( $this->locksHeld[$path][$type] ) ) {
+ $status->warning( 'lockmanager-notlocked', $path );
+ } else {
+ --$this->locksHeld[$path][$type];
+ // Reference count the locks held and release locks when zero
+ if ( $this->locksHeld[$path][$type] <= 0 ) {
+ unset( $this->locksHeld[$path][$type] );
+ $bucket = $this->getBucketFromPath( $path );
+ $pathsToUnlock[$bucket][] = $path;
+ }
+ if ( !count( $this->locksHeld[$path] ) ) {
+ unset( $this->locksHeld[$path] ); // no SH or EX locks left for key
+ }
+ }
+ }
+
+ // Remove these specific locks if possible, or at least release
+ // all locks once this process is currently not holding any locks.
+ foreach ( $pathsToUnlock as $bucket => $paths ) {
+ $status->merge( $this->doUnlockingRequestBucket( $bucket, $paths, $type ) );
+ }
+ if ( !count( $this->locksHeld ) ) {
+ $status->merge( $this->releaseAllLocks() );
+ }
+
+ return $status;
+ }
+
+ /**
+ * Attempt to acquire locks with the peers for a bucket.
+ * This is all or nothing; if any key is locked then this totally fails.
+ *
+ * @param $bucket integer
+ * @param array $paths List of resource keys to lock
+ * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
+ * @return Status
+ */
+ final protected function doLockingRequestBucket( $bucket, array $paths, $type ) {
+ $status = Status::newGood();
+
+ $yesVotes = 0; // locks made on trustable servers
+ $votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers
+ $quorum = floor( $votesLeft/2 + 1 ); // simple majority
+ // Get votes for each peer, in order, until we have enough...
+ foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) {
+ if ( !$this->isServerUp( $lockSrv ) ) {
+ --$votesLeft;
+ $status->warning( 'lockmanager-fail-svr-acquire', $lockSrv );
+ continue; // server down?
+ }
+ // Attempt to acquire the lock on this peer
+ $status->merge( $this->getLocksOnServer( $lockSrv, $paths, $type ) );
+ if ( !$status->isOK() ) {
+ return $status; // vetoed; resource locked
+ }
+ ++$yesVotes; // success for this peer
+ if ( $yesVotes >= $quorum ) {
+ return $status; // lock obtained
+ }
+ --$votesLeft;
+ $votesNeeded = $quorum - $yesVotes;
+ if ( $votesNeeded > $votesLeft ) {
+ break; // short-circuit
+ }
+ }
+ // At this point, we must not have met the quorum
+ $status->setResult( false );
+
+ return $status;
+ }
+
+ /**
+ * Attempt to release locks with the peers for a bucket
+ *
+ * @param $bucket integer
+ * @param array $paths List of resource keys to lock
+ * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
+ * @return Status
+ */
+ final protected function doUnlockingRequestBucket( $bucket, array $paths, $type ) {
+ $status = Status::newGood();
+
+ foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) {
+ if ( !$this->isServerUp( $lockSrv ) ) {
+ $status->fatal( 'lockmanager-fail-svr-release', $lockSrv );
+ // Attempt to release the lock on this peer
+ } else {
+ $status->merge( $this->freeLocksOnServer( $lockSrv, $paths, $type ) );
+ }
+ }
+
+ return $status;
+ }
+
+ /**
+ * Get the bucket for resource path.
+ * This should avoid throwing any exceptions.
+ *
+ * @param $path string
+ * @return integer
+ */
+ protected function getBucketFromPath( $path ) {
+ $prefix = substr( sha1( $path ), 0, 2 ); // first 2 hex chars (8 bits)
+ return (int)base_convert( $prefix, 16, 10 ) % count( $this->srvsByBucket );
+ }
+
+ /**
+ * Check if a lock server is up
+ *
+ * @param $lockSrv string
+ * @return bool
+ */
+ abstract protected function isServerUp( $lockSrv );
+
+ /**
+ * Get a connection to a lock server and acquire locks on $paths
+ *
+ * @param $lockSrv string
+ * @param $paths array
+ * @param $type integer
+ * @return Status
+ */
+ abstract protected function getLocksOnServer( $lockSrv, array $paths, $type );
+
+ /**
+ * Get a connection to a lock server and release locks on $paths.
+ *
+ * Subclasses must effectively implement this or releaseAllLocks().
+ *
+ * @param $lockSrv string
+ * @param $paths array
+ * @param $type integer
+ * @return Status
+ */
+ abstract protected function freeLocksOnServer( $lockSrv, array $paths, $type );
+
+ /**
+ * Release all locks that this session is holding.
+ *
+ * Subclasses must effectively implement this or freeLocksOnServer().
+ *
+ * @return Status
+ */
+ abstract protected function releaseAllLocks();
+}
diff --git a/includes/filebackend/lockmanager/ScopedLock.php b/includes/filebackend/lockmanager/ScopedLock.php
new file mode 100644
index 00000000..edcb1d65
--- /dev/null
+++ b/includes/filebackend/lockmanager/ScopedLock.php
@@ -0,0 +1,102 @@
+<?php
+/**
+ * Resource locking handling.
+ *
+ * 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 LockManager
+ * @author Aaron Schulz
+ */
+
+/**
+ * Self-releasing locks
+ *
+ * LockManager helper class to handle scoped locks, which
+ * release when an object is destroyed or goes out of scope.
+ *
+ * @ingroup LockManager
+ * @since 1.19
+ */
+class ScopedLock {
+ /** @var LockManager */
+ protected $manager;
+ /** @var Status */
+ protected $status;
+ /** @var Array List of resource paths*/
+ protected $paths;
+
+ protected $type; // integer lock type
+
+ /**
+ * @param $manager LockManager
+ * @param array $paths List of storage paths
+ * @param $type integer LockManager::LOCK_* constant
+ * @param $status Status
+ */
+ protected function __construct(
+ LockManager $manager, array $paths, $type, Status $status
+ ) {
+ $this->manager = $manager;
+ $this->paths = $paths;
+ $this->status = $status;
+ $this->type = $type;
+ }
+
+ /**
+ * Get a ScopedLock object representing a lock on resource paths.
+ * Any locks are released once this object goes out of scope.
+ * The status object is updated with any errors or warnings.
+ *
+ * @param $manager LockManager
+ * @param array $paths List of storage paths
+ * @param $type integer LockManager::LOCK_* constant
+ * @param $status Status
+ * @return ScopedLock|null Returns null on failure
+ */
+ public static function factory(
+ LockManager $manager, array $paths, $type, Status $status
+ ) {
+ $lockStatus = $manager->lock( $paths, $type );
+ $status->merge( $lockStatus );
+ if ( $lockStatus->isOK() ) {
+ return new self( $manager, $paths, $type, $status );
+ }
+ return null;
+ }
+
+ /**
+ * Release a scoped lock and set any errors in the attatched Status object.
+ * This is useful for early release of locks before function scope is destroyed.
+ * This is the same as setting the lock object to null.
+ *
+ * @param ScopedLock $lock
+ * @return void
+ * @since 1.21
+ */
+ public static function release( ScopedLock &$lock = null ) {
+ $lock = null;
+ }
+
+ function __destruct() {
+ $wasOk = $this->status->isOK();
+ $this->status->merge( $this->manager->unlock( $this->paths, $this->type ) );
+ if ( $wasOk ) {
+ // Make sure status is OK, despite any unlockFiles() fatals
+ $this->status->setResult( true, $this->status->value );
+ }
+ }
+}
diff --git a/includes/filerepo/FSRepo.php b/includes/filerepo/FSRepo.php
index 9c8d85dc..e49f37d2 100644
--- a/includes/filerepo/FSRepo.php
+++ b/includes/filerepo/FSRepo.php
@@ -24,9 +24,9 @@
/**
* A repository for files accessible via the local filesystem.
* Does not support database access or registration.
- *
+ *
* This is a mostly a legacy class. New uses should not be added.
- *
+ *
* @ingroup FileRepo
* @deprecated since 1.19
*/
@@ -46,6 +46,9 @@ class FSRepo extends FileRepo {
$thumbDir = isset( $info['thumbDir'] )
? $info['thumbDir']
: "{$directory}/thumb";
+ $transcodedDir = isset( $info['transcodedDir'] )
+ ? $info['transcodedDir']
+ : "{$directory}/transcoded";
$fileMode = isset( $info['fileMode'] )
? $info['fileMode']
: 0644;
@@ -59,6 +62,7 @@ class FSRepo extends FileRepo {
"{$repoName}-public" => "{$directory}",
"{$repoName}-temp" => "{$directory}/temp",
"{$repoName}-thumb" => $thumbDir,
+ "{$repoName}-transcoded" => $transcodedDir,
"{$repoName}-deleted" => $deletedDir
),
'fileMode' => $fileMode,
diff --git a/includes/filerepo/FileRepo.php b/includes/filerepo/FileRepo.php
index a31b148a..366dd8a5 100644
--- a/includes/filerepo/FileRepo.php
+++ b/includes/filerepo/FileRepo.php
@@ -120,13 +120,16 @@ class FileRepo {
$this->isPrivate = !empty( $info['isPrivate'] );
// Give defaults for the basic zones...
$this->zones = isset( $info['zones'] ) ? $info['zones'] : array();
- foreach ( array( 'public', 'thumb', 'temp', 'deleted' ) as $zone ) {
+ foreach ( array( 'public', 'thumb', 'transcoded', 'temp', 'deleted' ) as $zone ) {
if ( !isset( $this->zones[$zone]['container'] ) ) {
$this->zones[$zone]['container'] = "{$this->name}-{$zone}";
}
if ( !isset( $this->zones[$zone]['directory'] ) ) {
$this->zones[$zone]['directory'] = '';
}
+ if ( !isset( $this->zones[$zone]['urlsByExt'] ) ) {
+ $this->zones[$zone]['urlsByExt'] = array();
+ }
}
}
@@ -152,7 +155,7 @@ class FileRepo {
/**
* Check if a single zone or list of zones is defined for usage
*
- * @param $doZones Array Only do a particular zones
+ * @param array $doZones Only do a particular zones
* @throws MWException
* @return Status
*/
@@ -196,14 +199,17 @@ class FileRepo {
/**
* Get the URL corresponding to one of the four basic zones
*
- * @param $zone String: one of: public, deleted, temp, thumb
+ * @param string $zone One of: public, deleted, temp, thumb
+ * @param string|null $ext Optional file extension
* @return String or false
*/
- public function getZoneUrl( $zone ) {
- if ( isset( $this->zones[$zone]['url'] )
- && in_array( $zone, array( 'public', 'temp', 'thumb' ) ) )
- {
- return $this->zones[$zone]['url']; // custom URL
+ public function getZoneUrl( $zone, $ext = null ) {
+ if ( in_array( $zone, array( 'public', 'temp', 'thumb', 'transcoded' ) ) ) { // standard public zones
+ if ( $ext !== null && isset( $this->zones[$zone]['urlsByExt'][$ext] ) ) {
+ return $this->zones[$zone]['urlsByExt'][$ext]; // custom URL for extension/zone
+ } elseif ( isset( $this->zones[$zone]['url'] ) ) {
+ return $this->zones[$zone]['url']; // custom URL for zone
+ }
}
switch ( $zone ) {
case 'public':
@@ -214,6 +220,8 @@ class FileRepo {
return false; // no public URL
case 'thumb':
return $this->thumbUrl;
+ case 'transcoded':
+ return "{$this->url}/transcoded";
default:
return false;
}
@@ -229,12 +237,12 @@ class FileRepo {
* from the URL path, one can configure thumb_handler.php to recognize a special path on the
* same host name as the wiki that is used for viewing thumbnails.
*
- * @param $zone String: one of: public, deleted, temp, thumb
+ * @param string $zone one of: public, deleted, temp, thumb
* @return String or false
*/
public function getZoneHandlerUrl( $zone ) {
if ( isset( $this->zones[$zone]['handlerUrl'] )
- && in_array( $zone, array( 'public', 'temp', 'thumb' ) ) )
+ && in_array( $zone, array( 'public', 'temp', 'thumb', 'transcoded' ) ) )
{
return $this->zones[$zone]['handlerUrl'];
}
@@ -332,7 +340,7 @@ class FileRepo {
* version control should return false if the time is specified.
*
* @param $title Mixed: Title object or string
- * @param $options array Associative array of options:
+ * @param array $options Associative array of options:
* time: requested time for a specific file version, or false for the
* current version. An image object will be returned which was
* created at the specified time (which may be archived or current).
@@ -391,7 +399,7 @@ class FileRepo {
/**
* Find many files at once.
*
- * @param $items array An array of titles, or an array of findFile() options with
+ * @param array $items An array of titles, or an array of findFile() options with
* the "title" option giving the title. Example:
*
* $findItem = array( 'title' => $title, 'private' => true );
@@ -423,8 +431,8 @@ class FileRepo {
* Returns false if the file does not exist. Repositories not supporting
* version control should return false if the time is specified.
*
- * @param $sha1 String base 36 SHA-1 hash
- * @param $options array Option array, same as findFile().
+ * @param string $sha1 base 36 SHA-1 hash
+ * @param array $options Option array, same as findFile().
* @return File|bool False on failure
*/
public function findFileFromKey( $sha1, $options = array() ) {
@@ -468,7 +476,7 @@ class FileRepo {
* Get an array of arrays or iterators of file objects for files that
* have the given SHA-1 content hashes.
*
- * @param $hashes array An array of hashes
+ * @param array $hashes An array of hashes
* @return array An Array of arrays or iterators of file objects and the hash as key
*/
public function findBySha1s( array $hashes ) {
@@ -483,6 +491,18 @@ class FileRepo {
}
/**
+ * Return an array of files where the name starts with $prefix.
+ *
+ * STUB
+ * @param string $prefix The prefix to search for
+ * @param int $limit The maximum amount of files to return
+ * @return array
+ */
+ public function findFilesByPrefix( $prefix, $limit ) {
+ return array();
+ }
+
+ /**
* Get the public root URL of the repository
*
* @deprecated since 1.20
@@ -542,7 +562,7 @@ class FileRepo {
* Get a relative path including trailing slash, e.g. f/fa/
* If the repo is not hashed, returns an empty string
*
- * @param $name string Name of file
+ * @param string $name Name of file
* @return string
*/
public function getHashPath( $name ) {
@@ -553,7 +573,7 @@ class FileRepo {
* Get a relative path including trailing slash, e.g. f/fa/
* If the repo is not hashed, returns an empty string
*
- * @param $suffix string Basename of file from FileRepo::storeTemp()
+ * @param string $suffix Basename of file from FileRepo::storeTemp()
* @return string
*/
public function getTempHashPath( $suffix ) {
@@ -602,7 +622,7 @@ class FileRepo {
* Make an url to this repo
*
* @param $query mixed Query string to append
- * @param $entry string Entry point; defaults to index
+ * @param string $entry Entry point; defaults to index
* @return string|bool False on failure
*/
public function makeUrl( $query = '', $entry = 'index' ) {
@@ -656,8 +676,8 @@ class FileRepo {
* repository's file class, since it may return invalid results. User code
* should use File::getDescriptionText().
*
- * @param $name String: name of image to fetch
- * @param $lang String: language to fetch it in, if any.
+ * @param string $name name of image to fetch
+ * @param string $lang language to fetch it in, if any.
* @return string
*/
public function getDescriptionRenderUrl( $name, $lang = null ) {
@@ -688,7 +708,7 @@ class FileRepo {
public function getDescriptionStylesheetUrl() {
if ( isset( $this->scriptDirUrl ) ) {
return $this->makeUrl( 'title=MediaWiki:Filepage.css&' .
- wfArrayToCGI( Skin::getDynamicStylesheetQuery() ) );
+ wfArrayToCgi( Skin::getDynamicStylesheetQuery() ) );
}
return false;
}
@@ -696,9 +716,9 @@ class FileRepo {
/**
* Store a file to a given destination.
*
- * @param $srcPath String: source file system path, storage path, or virtual URL
- * @param $dstZone String: destination zone
- * @param $dstRel String: destination relative path
+ * @param string $srcPath source file system path, storage path, or virtual URL
+ * @param string $dstZone destination zone
+ * @param string $dstRel destination relative path
* @param $flags Integer: bitwise combination of the following flags:
* self::DELETE_SOURCE Delete the source file after upload
* self::OVERWRITE Overwrite an existing destination file instead of failing
@@ -721,7 +741,7 @@ class FileRepo {
/**
* Store a batch of files
*
- * @param $triplets Array: (src, dest zone, dest rel) triplets as per store()
+ * @param array $triplets (src, dest zone, dest rel) triplets as per store()
* @param $flags Integer: bitwise combination of the following flags:
* self::DELETE_SOURCE Delete the source file after upload
* self::OVERWRITE Overwrite an existing destination file instead of failing
@@ -755,7 +775,7 @@ class FileRepo {
throw new MWException( 'Validation error in $dstRel' );
}
$dstPath = "$root/$dstRel";
- $dstDir = dirname( $dstPath );
+ $dstDir = dirname( $dstPath );
// Create destination directories for this triplet
if ( !$this->initDirectory( $dstDir )->isOK() ) {
return $this->newFatal( 'directorycreateerror', $dstDir );
@@ -803,7 +823,7 @@ class FileRepo {
* Each file can be a (zone, rel) pair, virtual url, storage path.
* It will try to delete each file, but ignores any errors that may occur.
*
- * @param $files array List of files to delete
+ * @param array $files List of files to delete
* @param $flags Integer: bitwise combination of the following flags:
* self::SKIP_LOCKING Skip any file locking when doing the deletions
* @return FileRepoStatus
@@ -841,9 +861,9 @@ class FileRepo {
* This function can be used to write to otherwise read-only foreign repos.
* This is intended for copying generated thumbnails into the repo.
*
- * @param $src string Source file system path, storage path, or virtual URL
- * @param $dst string Virtual URL or storage path
- * @param $disposition string|null Content-Disposition if given and supported
+ * @param string $src Source file system path, storage path, or virtual URL
+ * @param string $dst Virtual URL or storage path
+ * @param string|null $disposition Content-Disposition if given and supported
* @return FileRepoStatus
*/
final public function quickImport( $src, $dst, $disposition = null ) {
@@ -855,7 +875,7 @@ class FileRepo {
* This function can be used to write to otherwise read-only foreign repos.
* This is intended for purging thumbnails.
*
- * @param $path string Virtual URL or storage path
+ * @param string $path Virtual URL or storage path
* @return FileRepoStatus
*/
final public function quickPurge( $path ) {
@@ -866,7 +886,7 @@ class FileRepo {
* Deletes a directory if empty.
* This function can be used to write to otherwise read-only foreign repos.
*
- * @param $dir string Virtual URL (or storage path) of directory to clean
+ * @param string $dir Virtual URL (or storage path) of directory to clean
* @return Status
*/
public function quickCleanDir( $dir ) {
@@ -886,7 +906,7 @@ class FileRepo {
* All path parameters may be a file system path, storage path, or virtual URL.
* When "dispositions" are given they are used as Content-Disposition if supported.
*
- * @param $triples Array List of (source path, destination path, disposition)
+ * @param array $triples List of (source path, destination path, disposition)
* @return FileRepoStatus
*/
public function quickImportBatch( array $triples ) {
@@ -914,7 +934,7 @@ class FileRepo {
* This function can be used to write to otherwise read-only foreign repos.
* This does no locking nor journaling and is intended for purging thumbnails.
*
- * @param $paths Array List of virtual URLs or storage paths
+ * @param array $paths List of virtual URLs or storage paths
* @return FileRepoStatus
*/
public function quickPurgeBatch( array $paths ) {
@@ -937,19 +957,18 @@ class FileRepo {
* Returns a FileRepoStatus object with the file Virtual URL in the value,
* file can later be disposed using FileRepo::freeTemp().
*
- * @param $originalName String: the base name of the file as specified
+ * @param string $originalName the base name of the file as specified
* by the user. The file extension will be maintained.
- * @param $srcPath String: the current location of the file.
+ * @param string $srcPath the current location of the file.
* @return FileRepoStatus object with the URL in the value.
*/
public function storeTemp( $originalName, $srcPath ) {
$this->assertWritableRepo(); // fail out if read-only
- $date = gmdate( "YmdHis" );
- $hashPath = $this->getHashPath( $originalName );
- $dstRel = "{$hashPath}{$date}!{$originalName}";
- $dstUrlRel = $hashPath . $date . '!' . rawurlencode( $originalName );
- $virtualUrl = $this->getVirtualUrl( 'temp' ) . '/' . $dstUrlRel;
+ $date = gmdate( "YmdHis" );
+ $hashPath = $this->getHashPath( $originalName );
+ $dstUrlRel = $hashPath . $date . '!' . rawurlencode( $originalName );
+ $virtualUrl = $this->getVirtualUrl( 'temp' ) . '/' . $dstUrlRel;
$result = $this->quickImport( $srcPath, $virtualUrl );
$result->value = $virtualUrl;
@@ -960,7 +979,7 @@ class FileRepo {
/**
* Remove a temporary file or mark it for garbage collection
*
- * @param $virtualUrl String: the virtual URL returned by FileRepo::storeTemp()
+ * @param string $virtualUrl the virtual URL returned by FileRepo::storeTemp()
* @return Boolean: true on success, false on failure
*/
public function freeTemp( $virtualUrl ) {
@@ -978,8 +997,8 @@ class FileRepo {
/**
* Concatenate a list of temporary files into a target file location.
*
- * @param $srcPaths Array Ordered list of source virtual URLs/storage paths
- * @param $dstPath String Target file system path
+ * @param array $srcPaths Ordered list of source virtual URLs/storage paths
+ * @param string $dstPath Target file system path
* @param $flags Integer: bitwise combination of the following flags:
* self::DELETE_SOURCE Delete the source files
* @return FileRepoStatus
@@ -1021,18 +1040,25 @@ class FileRepo {
* Returns a FileRepoStatus object. On success, the value contains "new" or
* "archived", to indicate whether the file was new with that name.
*
- * @param $srcPath String: the source file system path, storage path, or URL
- * @param $dstRel String: the destination relative path
- * @param $archiveRel String: the relative path where the existing file is to
+ * Options to $options include:
+ * - headers : name/value map of HTTP headers to use in response to GET/HEAD requests
+ *
+ * @param string $srcPath the source file system path, storage path, or URL
+ * @param string $dstRel the destination relative path
+ * @param string $archiveRel the relative path where the existing file is to
* be archived, if there is one. Relative to the public zone root.
* @param $flags Integer: bitfield, may be FileRepo::DELETE_SOURCE to indicate
* that the source file should be deleted if possible
+ * @param array $options Optional additional parameters
* @return FileRepoStatus
*/
- public function publish( $srcPath, $dstRel, $archiveRel, $flags = 0 ) {
+ public function publish(
+ $srcPath, $dstRel, $archiveRel, $flags = 0, array $options = array()
+ ) {
$this->assertWritableRepo(); // fail out if read-only
- $status = $this->publishBatch( array( array( $srcPath, $dstRel, $archiveRel ) ), $flags );
+ $status = $this->publishBatch(
+ array( array( $srcPath, $dstRel, $archiveRel, $options ) ), $flags );
if ( $status->successCount == 0 ) {
$status->ok = false;
}
@@ -1048,13 +1074,14 @@ class FileRepo {
/**
* Publish a batch of files
*
- * @param $triplets Array: (source, dest, archive) triplets as per publish()
+ * @param array $ntuples (source, dest, archive) triplets or
+ * (source, dest, archive, options) 4-tuples as per publish().
* @param $flags Integer: bitfield, may be FileRepo::DELETE_SOURCE to indicate
* that the source files should be deleted if possible
* @throws MWException
* @return FileRepoStatus
*/
- public function publishBatch( array $triplets, $flags = 0 ) {
+ public function publishBatch( array $ntuples, $flags = 0 ) {
$this->assertWritableRepo(); // fail out if read-only
$backend = $this->backend; // convenience
@@ -1069,8 +1096,9 @@ class FileRepo {
$operations = array();
$sourceFSFilesToDelete = array(); // cleanup for disk source files
// Validate each triplet and get the store operation...
- foreach ( $triplets as $i => $triplet ) {
- list( $srcPath, $dstRel, $archiveRel ) = $triplet;
+ foreach ( $ntuples as $ntuple ) {
+ list( $srcPath, $dstRel, $archiveRel ) = $ntuple;
+ $options = isset( $ntuple[3] ) ? $ntuple[3] : array();
// Resolve source to a storage path if virtual
$srcPath = $this->resolveToStoragePath( $srcPath );
if ( !$this->validateFilename( $dstRel ) ) {
@@ -1090,51 +1118,52 @@ class FileRepo {
if ( !$this->initDirectory( $dstDir )->isOK() ) {
return $this->newFatal( 'directorycreateerror', $dstDir );
}
- if ( !$this->initDirectory($archiveDir )->isOK() ) {
+ if ( !$this->initDirectory( $archiveDir )->isOK() ) {
return $this->newFatal( 'directorycreateerror', $archiveDir );
}
- // Archive destination file if it exists
- if ( $backend->fileExists( array( 'src' => $dstPath ) ) ) {
- // Check if the archive file exists
- // This is a sanity check to avoid data loss. In UNIX, the rename primitive
- // unlinks the destination file if it exists. DB-based synchronisation in
- // publishBatch's caller should prevent races. In Windows there's no
- // problem because the rename primitive fails if the destination exists.
- if ( $backend->fileExists( array( 'src' => $archivePath ) ) ) {
- $operations[] = array( 'op' => 'null' );
- continue;
- } else {
- $operations[] = array(
- 'op' => 'move',
- 'src' => $dstPath,
- 'dst' => $archivePath
- );
- }
- $status->value[$i] = 'archived';
- } else {
- $status->value[$i] = 'new';
- }
+ // Set any desired headers to be use in GET/HEAD responses
+ $headers = isset( $options['headers'] ) ? $options['headers'] : array();
+
+ // Archive destination file if it exists.
+ // This will check if the archive file also exists and fail if does.
+ // This is a sanity check to avoid data loss. On Windows and Linux,
+ // copy() will overwrite, so the existence check is vulnerable to
+ // race conditions unless an functioning LockManager is used.
+ // LocalFile also uses SELECT FOR UPDATE for synchronization.
+ $operations[] = array(
+ 'op' => 'copy',
+ 'src' => $dstPath,
+ 'dst' => $archivePath,
+ 'ignoreMissingSource' => true
+ );
+
// Copy (or move) the source file to the destination
if ( FileBackend::isStoragePath( $srcPath ) ) {
if ( $flags & self::DELETE_SOURCE ) {
$operations[] = array(
- 'op' => 'move',
- 'src' => $srcPath,
- 'dst' => $dstPath
+ 'op' => 'move',
+ 'src' => $srcPath,
+ 'dst' => $dstPath,
+ 'overwrite' => true, // replace current
+ 'headers' => $headers
);
} else {
$operations[] = array(
- 'op' => 'copy',
- 'src' => $srcPath,
- 'dst' => $dstPath
+ 'op' => 'copy',
+ 'src' => $srcPath,
+ 'dst' => $dstPath,
+ 'overwrite' => true, // replace current
+ 'headers' => $headers
);
}
} else { // FS source path
$operations[] = array(
- 'op' => 'store',
- 'src' => $srcPath,
- 'dst' => $dstPath
+ 'op' => 'store',
+ 'src' => $srcPath,
+ 'dst' => $dstPath,
+ 'overwrite' => true, // replace current
+ 'headers' => $headers
);
if ( $flags & self::DELETE_SOURCE ) {
$sourceFSFilesToDelete[] = $srcPath;
@@ -1143,8 +1172,17 @@ class FileRepo {
}
// Execute the operations for each triplet
- $opts = array( 'force' => true );
- $status->merge( $backend->doOperations( $operations, $opts ) );
+ $status->merge( $backend->doOperations( $operations ) );
+ // Find out which files were archived...
+ foreach ( $ntuples as $i => $ntuple ) {
+ list( , , $archiveRel ) = $ntuple;
+ $archivePath = $this->getZonePath( 'public' ) . "/$archiveRel";
+ if ( $this->fileExists( $archivePath ) ) {
+ $status->value[$i] = 'archived';
+ } else {
+ $status->value[$i] = 'new';
+ }
+ }
// Cleanup for disk source files...
foreach ( $sourceFSFilesToDelete as $file ) {
wfSuppressWarnings();
@@ -1159,12 +1197,12 @@ class FileRepo {
* Creates a directory with the appropriate zone permissions.
* Callers are responsible for doing read-only and "writable repo" checks.
*
- * @param $dir string Virtual URL (or storage path) of directory to clean
+ * @param string $dir Virtual URL (or storage path) of directory to clean
* @return Status
*/
protected function initDirectory( $dir ) {
$path = $this->resolveToStoragePath( $dir );
- list( $b, $container, $r ) = FileBackend::splitStoragePath( $path );
+ list( , $container, ) = FileBackend::splitStoragePath( $path );
$params = array( 'dir' => $path );
if ( $this->isPrivate || $container === $this->zones['deleted']['container'] ) {
@@ -1179,7 +1217,7 @@ class FileRepo {
/**
* Deletes a directory if empty.
*
- * @param $dir string Virtual URL (or storage path) of directory to clean
+ * @param string $dir Virtual URL (or storage path) of directory to clean
* @return Status
*/
public function cleanDir( $dir ) {
@@ -1195,7 +1233,7 @@ class FileRepo {
/**
* Checks existence of a a file
*
- * @param $file string Virtual URL (or storage path) of file to check
+ * @param string $file Virtual URL (or storage path) of file to check
* @return bool
*/
public function fileExists( $file ) {
@@ -1206,7 +1244,7 @@ class FileRepo {
/**
* Checks existence of an array of files.
*
- * @param $files Array: Virtual URLs (or storage paths) of files to check
+ * @param array $files Virtual URLs (or storage paths) of files to check
* @return array|bool Either array of files and existence flags, or false
*/
public function fileExistsBatch( array $files ) {
@@ -1244,7 +1282,7 @@ class FileRepo {
* assumes a naming scheme in the deleted zone based on content hash, as
* opposed to the public zone which is assumed to be unique.
*
- * @param $sourceDestPairs Array of source/destination pairs. Each element
+ * @param array $sourceDestPairs of source/destination pairs. Each element
* is a two-element array containing the source file path relative to the
* public root in the first element, and the archive file path relative
* to the deleted zone root in the second element.
@@ -1318,9 +1356,13 @@ class FileRepo {
* e.g. s/z/a/ for sza251lrxrc1jad41h5mgilp8nysje52.jpg
*
* @param $key string
+ * @throws MWException
* @return string
*/
public function getDeletedHashPath( $key ) {
+ if ( strlen( $key ) < 31 ) {
+ throw new MWException( "Invalid storage key '$key'." );
+ }
$path = '';
for ( $i = 0; $i < $this->deletedHashLevels; $i++ ) {
$path .= $key[$i] . '/';
@@ -1392,6 +1434,17 @@ class FileRepo {
}
/**
+ * Get the size of a file with a given virtual URL/storage path
+ *
+ * @param $virtualUrl string
+ * @return integer|bool False on failure
+ */
+ public function getFileSize( $virtualUrl ) {
+ $path = $this->resolveToStoragePath( $virtualUrl );
+ return $this->backend->getFileSize( array( 'src' => $path ) );
+ }
+
+ /**
* Get the sha1 (base 36) of a file with a given virtual URL/storage path
*
* @param $virtualUrl string
@@ -1406,7 +1459,7 @@ class FileRepo {
* Attempt to stream a file with the given virtual URL/storage path
*
* @param $virtualUrl string
- * @param $headers Array Additional HTTP headers to send on success
+ * @param array $headers Additional HTTP headers to send on success
* @return bool Success
*/
public function streamFile( $virtualUrl, $headers = array() ) {
@@ -1568,7 +1621,7 @@ class FileRepo {
*/
public function nameForThumb( $name ) {
if ( strlen( $name ) > $this->abbrvThreshold ) {
- $ext = FileBackend::extensionFromPath( $name );
+ $ext = FileBackend::extensionFromPath( $name );
$name = ( $ext == '' ) ? 'thumbnail' : "thumbnail.$ext";
}
return $name;
@@ -1630,10 +1683,17 @@ class FileRepo {
'directory' => ( $this->zones['thumb']['directory'] == '' )
? 'temp'
: $this->zones['thumb']['directory'] . '/temp'
+ ),
+ 'transcoded' => array(
+ 'container' => $this->zones['transcoded']['container'],
+ 'directory' => ( $this->zones['transcoded']['directory'] == '' )
+ ? 'temp'
+ : $this->zones['transcoded']['directory'] . '/temp'
)
),
'url' => $this->getZoneUrl( 'temp' ),
'thumbUrl' => $this->getZoneUrl( 'thumb' ) . '/temp',
+ 'transcodedUrl' => $this->getZoneUrl( 'transcoded' ) . '/temp',
'hashLevels' => $this->hashLevels // performance
) );
}
@@ -1641,10 +1701,11 @@ class FileRepo {
/**
* Get an UploadStash associated with this repo.
*
+ * @param $user User
* @return UploadStash
*/
- public function getUploadStash() {
- return new UploadStash( $this );
+ public function getUploadStash( User $user = null ) {
+ return new UploadStash( $this, $user );
}
/**
diff --git a/includes/filerepo/ForeignAPIRepo.php b/includes/filerepo/ForeignAPIRepo.php
index 13de9e6b..ba574da1 100644
--- a/includes/filerepo/ForeignAPIRepo.php
+++ b/includes/filerepo/ForeignAPIRepo.php
@@ -247,10 +247,10 @@ class ForeignAPIRepo extends FileRepo {
* If the url has been requested today, get it from cache
* Otherwise retrieve remote thumb url, check for local file.
*
- * @param $name String is a dbkey form of a title
+ * @param string $name is a dbkey form of a title
* @param $width
* @param $height
- * @param String $params Other rendering parameters (page number, etc) from handler's makeParamString.
+ * @param string $params Other rendering parameters (page number, etc) from handler's makeParamString.
* @return bool|string
*/
function getThumbUrlFromCache( $name, $width, $height, $params = "" ) {
@@ -267,14 +267,14 @@ class ForeignAPIRepo extends FileRepo {
$sizekey = "$width:$height:$params";
/* Get the array of urls that we already know */
- $knownThumbUrls = $wgMemc->get($key);
+ $knownThumbUrls = $wgMemc->get( $key );
if( !$knownThumbUrls ) {
/* No knownThumbUrls for this file */
$knownThumbUrls = array();
} else {
if( isset( $knownThumbUrls[$sizekey] ) ) {
wfDebug( __METHOD__ . ': Got thumburl from local cache: ' .
- "{$knownThumbUrls[$sizekey]} \n");
+ "{$knownThumbUrls[$sizekey]} \n" );
return $knownThumbUrls[$sizekey];
}
/* This size is not yet known */
@@ -294,9 +294,9 @@ class ForeignAPIRepo extends FileRepo {
wfDebug( __METHOD__ . " The deduced filename $fileName is not safe\n" );
return false;
}
- $localPath = $this->getZonePath( 'thumb' ) . "/" . $this->getHashPath( $name ) . $name;
+ $localPath = $this->getZonePath( 'thumb' ) . "/" . $this->getHashPath( $name ) . $name;
$localFilename = $localPath . "/" . $fileName;
- $localUrl = $this->getZoneUrl( 'thumb' ) . "/" . $this->getHashPath( $name ) . rawurlencode( $name ) . "/" . rawurlencode( $fileName );
+ $localUrl = $this->getZoneUrl( 'thumb' ) . "/" . $this->getHashPath( $name ) . rawurlencode( $name ) . "/" . rawurlencode( $fileName );
if( $backend->fileExists( array( 'src' => $localFilename ) )
&& isset( $metadata['timestamp'] ) )
@@ -320,7 +320,6 @@ class ForeignAPIRepo extends FileRepo {
return false;
}
-
# @todo FIXME: Delete old thumbs that aren't being used. Maintenance script?
$backend->prepare( array( 'dir' => dirname( $localFilename ) ) );
$params = array( 'dst' => $localFilename, 'content' => $thumb );
@@ -337,16 +336,17 @@ class ForeignAPIRepo extends FileRepo {
/**
* @see FileRepo::getZoneUrl()
* @param $zone String
+ * @param string|null $ext Optional file extension
* @return String
*/
- function getZoneUrl( $zone ) {
+ function getZoneUrl( $zone, $ext = null ) {
switch ( $zone ) {
case 'public':
return $this->url;
case 'thumb':
return $this->thumbUrl;
default:
- return parent::getZoneUrl( $zone );
+ return parent::getZoneUrl( $zone, $ext );
}
}
diff --git a/includes/filerepo/ForeignDBRepo.php b/includes/filerepo/ForeignDBRepo.php
index 4b206c3d..18659852 100644
--- a/includes/filerepo/ForeignDBRepo.php
+++ b/includes/filerepo/ForeignDBRepo.php
@@ -86,7 +86,7 @@ class ForeignDBRepo extends LocalRepo {
/**
* Get a key on the primary cache for this repository.
- * Returns false if the repository's cache is not accessible at this site.
+ * Returns false if the repository's cache is not accessible at this site.
* The parameters are the parts of the key, as for wfMemcKey().
* @return bool|mixed
*/
diff --git a/includes/filerepo/ForeignDBViaLBRepo.php b/includes/filerepo/ForeignDBViaLBRepo.php
index bd76fce7..7951fb13 100644
--- a/includes/filerepo/ForeignDBViaLBRepo.php
+++ b/includes/filerepo/ForeignDBViaLBRepo.php
@@ -61,7 +61,7 @@ class ForeignDBViaLBRepo extends LocalRepo {
/**
* Get a key on the primary cache for this repository.
- * Returns false if the repository's cache is not accessible at this site.
+ * Returns false if the repository's cache is not accessible at this site.
* The parameters are the parts of the key, as for wfMemcKey().
* @return bool|string
*/
diff --git a/includes/filerepo/LocalRepo.php b/includes/filerepo/LocalRepo.php
index 0954422d..be11b233 100644
--- a/includes/filerepo/LocalRepo.php
+++ b/includes/filerepo/LocalRepo.php
@@ -103,8 +103,8 @@ class LocalRepo extends FileRepo {
/**
* Check if a deleted (filearchive) file has this sha1 key
*
- * @param $key String File storage key (base-36 sha1 key with file extension)
- * @param $lock String|null Use "lock" to lock the row via FOR UPDATE
+ * @param string $key File storage key (base-36 sha1 key with file extension)
+ * @param string|null $lock Use "lock" to lock the row via FOR UPDATE
* @return bool File with this key is in use
*/
protected function deletedFileHasKey( $key, $lock = null ) {
@@ -120,8 +120,8 @@ class LocalRepo extends FileRepo {
/**
* Check if a hidden (revision delete) file has this sha1 key
*
- * @param $key String File storage key (base-36 sha1 key with file extension)
- * @param $lock String|null Use "lock" to lock the row via FOR UPDATE
+ * @param string $key File storage key (base-36 sha1 key with file extension)
+ * @param string|null $lock Use "lock" to lock the row via FOR UPDATE
* @return bool File with this key is in use
*/
protected function hiddenFileHasKey( $key, $lock = null ) {
@@ -168,7 +168,7 @@ class LocalRepo extends FileRepo {
$expiry = 86400; // has invalidation, 1 day
}
$cachedValue = $wgMemc->get( $memcKey );
- if ( $cachedValue === ' ' || $cachedValue === '' ) {
+ if ( $cachedValue === ' ' || $cachedValue === '' ) {
// Does not exist
return false;
} elseif ( strval( $cachedValue ) !== '' ) {
@@ -212,12 +212,12 @@ class LocalRepo extends FileRepo {
$dbr = $this->getSlaveDB();
$id = $dbr->selectField(
'page', // Table
- 'page_id', //Field
- array( //Conditions
+ 'page_id', //Field
+ array( //Conditions
'page_namespace' => $title->getNamespace(),
'page_title' => $title->getDBkey(),
),
- __METHOD__ //Function name
+ __METHOD__ //Function name
);
return $id;
}
@@ -226,7 +226,7 @@ class LocalRepo extends FileRepo {
* Get an array or iterator of file objects for files that have a given
* SHA-1 content hash.
*
- * @param $hash String a sha1 hash to look for
+ * @param string $hash a sha1 hash to look for
* @return Array
*/
function findBySha1( $hash ) {
@@ -238,7 +238,7 @@ class LocalRepo extends FileRepo {
__METHOD__,
array( 'ORDER BY' => 'img_name' )
);
-
+
$result = array();
foreach ( $res as $row ) {
$result[] = $this->newFileFromRow( $row );
@@ -254,7 +254,7 @@ class LocalRepo extends FileRepo {
*
* Overrides generic implementation in FileRepo for performance reason
*
- * @param $hashes array An array of hashes
+ * @param array $hashes An array of hashes
* @return array An Array of arrays or iterators of file objects and the hash as key
*/
function findBySha1s( array $hashes ) {
@@ -281,6 +281,34 @@ class LocalRepo extends FileRepo {
return $result;
}
+ /**
+ * Return an array of files where the name starts with $prefix.
+ *
+ * @param string $prefix The prefix to search for
+ * @param int $limit The maximum amount of files to return
+ * @return array
+ */
+ public function findFilesByPrefix( $prefix, $limit ) {
+ $selectOptions = array( 'ORDER BY' => 'img_name', 'LIMIT' => intval( $limit ) );
+
+ // Query database
+ $dbr = $this->getSlaveDB();
+ $res = $dbr->select(
+ 'image',
+ LocalFile::selectFields(),
+ 'img_name ' . $dbr->buildLike( $prefix, $dbr->anyString() ),
+ __METHOD__,
+ $selectOptions
+ );
+
+ // Build file objects
+ $files = array();
+ foreach ( $res as $row ) {
+ $files[] = $this->newFileFromRow( $row );
+ }
+ return $files;
+ }
+
/**
* Get a connection to the slave DB
* @return DatabaseBase
@@ -299,7 +327,7 @@ class LocalRepo extends FileRepo {
/**
* Get a key on the primary cache for this repository.
- * Returns false if the repository's cache is not accessible at this site.
+ * Returns false if the repository's cache is not accessible at this site.
* The parameters are the parts of the key, as for wfMemcKey().
*
* @return string
@@ -323,4 +351,3 @@ class LocalRepo extends FileRepo {
}
}
}
-
diff --git a/includes/filerepo/README b/includes/filerepo/README
index 885a1ded..1423d359 100644
--- a/includes/filerepo/README
+++ b/includes/filerepo/README
@@ -18,10 +18,10 @@ repository-specific configuration is needed, or in static members of File or
FileRepo, where no such configuration is needed.
File objects are generated by a factory function from the repository. The
-repository thus has full control over the behaviour of its subsidiary file
+repository thus has full control over the behavior of its subsidiary file
class, since it can subclass the file class and override functionality at its
whim. Thus there is no need for the File subclass to query its parent repository
-for information about repository-class-dependent behaviour -- the file subclass
+for information about repository-class-dependent behavior -- the file subclass
is generally fully aware of the static preferences of its repository. Limited
exceptions can be made to this rule to permit sharing of functions, or perhaps
even entire classes, between repositories.
@@ -39,22 +39,3 @@ LocalRepo.php. LocalRepo provides only file access, and LocalFile provides
database access and higher-level functions such as cache management.
Tim Starling, June 2007
-
-Structure:
-
-File defines an abstract class File.
- ForeignAPIFile extends File.
- LocalFile extends File.
- ForeignDBFile extends LocalFile
- Image extends LocalFile
- UnregisteredLocalFile extends File.
- UploadStashFile extends UnregisteredLocalFile.
-FileRepo defines an abstract class FileRepo.
- ForeignAPIRepo extends FileRepo
- FSRepo extends FileRepo
- LocalRepo extends FSRepo
- ForeignDBRepo extends LocalRepo
- ForeignDBViaLBRepo extends LocalRepo
- NullRepo extends FileRepo
-
-Russ Nelson, March 2011
diff --git a/includes/filerepo/RepoGroup.php b/includes/filerepo/RepoGroup.php
index f9e57599..02dfdad6 100644
--- a/includes/filerepo/RepoGroup.php
+++ b/includes/filerepo/RepoGroup.php
@@ -79,8 +79,8 @@ class RepoGroup {
/**
* Construct a group of file repositories.
*
- * @param $localInfo array Associative array for local repo's info
- * @param $foreignInfo Array of repository info arrays.
+ * @param array $localInfo Associative array for local repo's info
+ * @param array $foreignInfo of repository info arrays.
* Each info array is an associative array with the 'class' member
* giving the class name. The entire array is passed to the repository
* constructor as the first parameter.
@@ -96,7 +96,7 @@ class RepoGroup {
* You can also use wfFindFile() to do this.
*
* @param $title Title|string Title object or string
- * @param $options array Associative array of options:
+ * @param array $options Associative array of options:
* time: requested time for an archived image, or false for the
* current version. An image object will be returned which was
* created at the specified time.
@@ -131,7 +131,7 @@ class RepoGroup {
$time = isset( $options['time'] ) ? $options['time'] : '';
$dbkey = $title->getDBkey();
if ( isset( $this->cache[$dbkey][$time] ) ) {
- wfDebug( __METHOD__.": got File:$dbkey from process cache\n" );
+ wfDebug( __METHOD__ . ": got File:$dbkey from process cache\n" );
# Move it to the end of the list so that we can delete the LRU entry later
$this->pingCache( $dbkey );
# Return the entry
@@ -225,8 +225,8 @@ class RepoGroup {
* Find an instance of the file with this key, created at the specified time
* Returns false if the file does not exist.
*
- * @param $hash String base 36 SHA-1 hash
- * @param $options array Option array, same as findFile()
+ * @param string $hash base 36 SHA-1 hash
+ * @param array $options Option array, same as findFile()
* @return File object or false if it is not found
*/
function findFileFromKey( $hash, $options = array() ) {
@@ -247,7 +247,7 @@ class RepoGroup {
/**
* Find all instances of files with this key
*
- * @param $hash String base 36 SHA-1 hash
+ * @param string $hash base 36 SHA-1 hash
* @return Array of File objects
*/
function findBySha1( $hash ) {
@@ -266,7 +266,7 @@ class RepoGroup {
/**
* Find all instances of files with this keys
*
- * @param $hashes Array base 36 SHA-1 hashes
+ * @param array $hashes base 36 SHA-1 hashes
* @return Array of array of File objects
*/
function findBySha1s( array $hashes ) {
@@ -335,7 +335,7 @@ class RepoGroup {
* first parameter.
*
* @param $callback Callback: the function to call
- * @param $params Array: optional additional parameters to pass to the function
+ * @param array $params optional additional parameters to pass to the function
* @return bool
*/
function forEachForeignRepo( $callback, $params = array() ) {
@@ -388,12 +388,12 @@ class RepoGroup {
*/
function splitVirtualUrl( $url ) {
if ( substr( $url, 0, 9 ) != 'mwrepo://' ) {
- throw new MWException( __METHOD__.': unknown protocol' );
+ throw new MWException( __METHOD__ . ': unknown protocol' );
}
$bits = explode( '/', substr( $url, 9 ), 3 );
if ( count( $bits ) != 3 ) {
- throw new MWException( __METHOD__.": invalid mwrepo URL: $url" );
+ throw new MWException( __METHOD__ . ": invalid mwrepo URL: $url" );
}
return $bits;
}
@@ -433,7 +433,7 @@ class RepoGroup {
while ( count( $this->cache ) >= self::MAX_CACHE_SIZE ) {
reset( $this->cache );
$key = key( $this->cache );
- wfDebug( __METHOD__.": evicting $key\n" );
+ wfDebug( __METHOD__ . ": evicting $key\n" );
unset( $this->cache[$key] );
}
}
diff --git a/includes/filerepo/file/ArchivedFile.php b/includes/filerepo/file/ArchivedFile.php
index c5a0bd1b..3f786197 100644
--- a/includes/filerepo/file/ArchivedFile.php
+++ b/includes/filerepo/file/ArchivedFile.php
@@ -47,6 +47,7 @@ class ArchivedFile {
$timestamp, # time of upload
$dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx)
$deleted, # Bitfield akin to rev_deleted
+ $sha1, # sha1 hash of file content
$pageCount,
$archive_name;
@@ -67,7 +68,7 @@ class ArchivedFile {
* @param int $id
* @param string $key
*/
- function __construct( $title, $id=0, $key='' ) {
+ function __construct( $title, $id = 0, $key = '' ) {
$this->id = -1;
$this->title = false;
$this->name = false;
@@ -87,17 +88,18 @@ class ArchivedFile {
$this->deleted = 0;
$this->dataLoaded = false;
$this->exists = false;
+ $this->sha1 = '';
if( $title instanceof Title ) {
$this->title = File::normalizeTitle( $title, 'exception' );
$this->name = $title->getDBkey();
}
- if ($id) {
+ if ( $id ) {
$this->id = $id;
}
- if ($key) {
+ if ( $key ) {
$this->key = $key;
}
@@ -108,6 +110,7 @@ class ArchivedFile {
/**
* Loads a file object from the filearchive table
+ * @throws MWException
* @return bool|null True on success or null
*/
public function load() {
@@ -127,64 +130,30 @@ class ArchivedFile {
$conds['fa_name'] = $this->title->getDBkey();
}
- if( !count($conds)) {
+ if( !count( $conds ) ) {
throw new MWException( "No specific information for retrieving archived file" );
}
if( !$this->title || $this->title->getNamespace() == NS_FILE ) {
+ $this->dataLoaded = true; // set it here, to have also true on miss
$dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( 'filearchive',
- array(
- '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_description',
- 'fa_user',
- 'fa_user_text',
- 'fa_timestamp',
- 'fa_deleted' ),
+ $row = $dbr->selectRow(
+ 'filearchive',
+ self::selectFields(),
$conds,
__METHOD__,
- array( 'ORDER BY' => 'fa_timestamp DESC' ) );
- if ( $res == false || $dbr->numRows( $res ) == 0 ) {
- // this revision does not exist?
+ array( 'ORDER BY' => 'fa_timestamp DESC' )
+ );
+ if ( !$row ) {
+ // this revision does not exist?
return null;
}
- $ret = $dbr->resultObject( $res );
- $row = $ret->fetchObject();
// initialize fields for filestore image object
- $this->id = intval($row->fa_id);
- $this->name = $row->fa_name;
- $this->archive_name = $row->fa_archive_name;
- $this->group = $row->fa_storage_group;
- $this->key = $row->fa_storage_key;
- $this->size = $row->fa_size;
- $this->bits = $row->fa_bits;
- $this->width = $row->fa_width;
- $this->height = $row->fa_height;
- $this->metadata = $row->fa_metadata;
- $this->mime = "$row->fa_major_mime/$row->fa_minor_mime";
- $this->media_type = $row->fa_media_type;
- $this->description = $row->fa_description;
- $this->user = $row->fa_user;
- $this->user_text = $row->fa_user_text;
- $this->timestamp = $row->fa_timestamp;
- $this->deleted = $row->fa_deleted;
+ $this->loadFromRow( $row );
} else {
throw new MWException( 'This title does not correspond to an image page.' );
}
- $this->dataLoaded = true;
$this->exists = true;
return true;
@@ -199,26 +168,68 @@ class ArchivedFile {
*/
public static function newFromRow( $row ) {
$file = new ArchivedFile( Title::makeTitle( NS_FILE, $row->fa_name ) );
+ $file->loadFromRow( $row );
+ return $file;
+ }
- $file->id = intval($row->fa_id);
- $file->name = $row->fa_name;
- $file->archive_name = $row->fa_archive_name;
- $file->group = $row->fa_storage_group;
- $file->key = $row->fa_storage_key;
- $file->size = $row->fa_size;
- $file->bits = $row->fa_bits;
- $file->width = $row->fa_width;
- $file->height = $row->fa_height;
- $file->metadata = $row->fa_metadata;
- $file->mime = "$row->fa_major_mime/$row->fa_minor_mime";
- $file->media_type = $row->fa_media_type;
- $file->description = $row->fa_description;
- $file->user = $row->fa_user;
- $file->user_text = $row->fa_user_text;
- $file->timestamp = $row->fa_timestamp;
- $file->deleted = $row->fa_deleted;
+ /**
+ * Fields in the filearchive table
+ * @return array
+ */
+ static function selectFields() {
+ return array(
+ '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_description',
+ 'fa_user',
+ 'fa_user_text',
+ 'fa_timestamp',
+ 'fa_deleted',
+ 'fa_sha1',
+ );
+ }
- return $file;
+ /**
+ * Load ArchivedFile object fields from a DB row.
+ *
+ * @param $row Object database row
+ * @since 1.21
+ */
+ public function loadFromRow( $row ) {
+ $this->id = intval( $row->fa_id );
+ $this->name = $row->fa_name;
+ $this->archive_name = $row->fa_archive_name;
+ $this->group = $row->fa_storage_group;
+ $this->key = $row->fa_storage_key;
+ $this->size = $row->fa_size;
+ $this->bits = $row->fa_bits;
+ $this->width = $row->fa_width;
+ $this->height = $row->fa_height;
+ $this->metadata = $row->fa_metadata;
+ $this->mime = "$row->fa_major_mime/$row->fa_minor_mime";
+ $this->media_type = $row->fa_media_type;
+ $this->description = $row->fa_description;
+ $this->user = $row->fa_user;
+ $this->user_text = $row->fa_user_text;
+ $this->timestamp = $row->fa_timestamp;
+ $this->deleted = $row->fa_deleted;
+ if( isset( $row->fa_sha1 ) ) {
+ $this->sha1 = $row->fa_sha1;
+ } else {
+ // old row, populate from key
+ $this->sha1 = LocalRepo::getHashFromKey( $this->key );
+ }
}
/**
@@ -381,6 +392,17 @@ class ArchivedFile {
}
/**
+ * Get the SHA-1 base 36 hash of the file
+ *
+ * @return string
+ * @since 1.21
+ */
+ function getSha1() {
+ $this->load();
+ return $this->sha1;
+ }
+
+ /**
* Return the user ID of the uploader.
*
* @return int
diff --git a/includes/filerepo/file/File.php b/includes/filerepo/file/File.php
index 557609d4..cecd0aee 100644
--- a/includes/filerepo/file/File.php
+++ b/includes/filerepo/file/File.php
@@ -40,7 +40,7 @@
* never name a file class explictly outside of the repo class. Instead use the
* repo's factory functions to generate file objects, for example:
*
- * RepoGroup::singleton()->getLocalRepo()->newFile($title);
+ * RepoGroup::singleton()->getLocalRepo()->newFile( $title );
*
* The convenience functions wfLocalFile() and wfFindFile() should be sufficient
* in most cases.
@@ -54,7 +54,7 @@ abstract class File {
const DELETED_RESTRICTED = 8;
/** Force rendering in the current process */
- const RENDER_NOW = 1;
+ const RENDER_NOW = 1;
/**
* Force rendering even if thumbnail already exist and using RENDER_NOW
* I.e. you have to pass both flags: File::RENDER_NOW | File::RENDER_FORCE
@@ -152,7 +152,7 @@ abstract class File {
* valid Title object with namespace NS_FILE or null
*
* @param $title Title|string
- * @param $exception string|bool Use 'exception' to throw an error on bad titles
+ * @param string|bool $exception Use 'exception' to throw an error on bad titles
* @throws MWException
* @return Title|null
*/
@@ -190,7 +190,7 @@ abstract class File {
* Normalize a file extension to the common form, and ensure it's clean.
* Extensions with non-alphanumeric characters will be discarded.
*
- * @param $ext string (without the .)
+ * @param string $ext (without the .)
* @return string
*/
static function normalizeExtension( $ext ) {
@@ -214,7 +214,7 @@ abstract class File {
* Checks if file extensions are compatible
*
* @param $old File Old file
- * @param $new string New name
+ * @param string $new New name
*
* @return bool|null
*/
@@ -316,7 +316,8 @@ abstract class File {
public function getUrl() {
if ( !isset( $this->url ) ) {
$this->assertRepoDefined();
- $this->url = $this->repo->getZoneUrl( 'public' ) . '/' . $this->getUrlRel();
+ $ext = $this->getExtension();
+ $this->url = $this->repo->getZoneUrl( 'public', $ext ) . '/' . $this->getUrlRel();
}
return $this->url;
}
@@ -347,7 +348,7 @@ abstract class File {
if ( $this->canRender() ) {
return $this->createThumb( $this->getWidth() );
} else {
- wfDebug( __METHOD__.': supposed to render ' . $this->getName() .
+ wfDebug( __METHOD__ . ': supposed to render ' . $this->getName() .
' (' . $this->getMimeType() . "), but can't!\n" );
return $this->getURL(); #hm... return NULL?
}
@@ -368,7 +369,7 @@ abstract class File {
* returns false.
*
* @return string|bool ForeignAPIFile::getPath can return false
- */
+ */
public function getPath() {
if ( !isset( $this->path ) ) {
$this->assertRepoDefined();
@@ -431,7 +432,7 @@ abstract class File {
* Returns ID or name of user who uploaded the file
* STUB
*
- * @param $type string 'text' or 'id'
+ * @param string $type 'text' or 'id'
*
* @return string|int
*/
@@ -511,12 +512,12 @@ abstract class File {
}
/**
- * get versioned metadata
- *
- * @param $metadata Mixed Array or String of (serialized) metadata
- * @param $version integer version number.
- * @return Array containing metadata, or what was passed to it on fail (unserializing if not array)
- */
+ * get versioned metadata
+ *
+ * @param $metadata Mixed Array or String of (serialized) metadata
+ * @param $version integer version number.
+ * @return Array containing metadata, or what was passed to it on fail (unserializing if not array)
+ */
public function convertMetadataVersion($metadata, $version) {
$handler = $this->getHandler();
if ( !is_array( $metadata ) ) {
@@ -662,13 +663,13 @@ abstract class File {
if ( $this->allowInlineDisplay() ) {
return true;
}
- if ($this->isTrustedFile()) {
+ if ( $this->isTrustedFile() ) {
return true;
}
$type = $this->getMediaType();
$mime = $this->getMimeType();
- #wfDebug("LocalFile::isSafeFile: type= $type, mime= $mime\n");
+ #wfDebug( "LocalFile::isSafeFile: type= $type, mime= $mime\n" );
if ( !$type || $type === MEDIATYPE_UNKNOWN ) {
return false; #unknown type, not trusted
@@ -766,7 +767,7 @@ abstract class File {
* Use File::THUMB_FULL_NAME to always get a name like "<params>-<source>".
* Otherwise, the format may be "<params>-<source>" or "<params>-thumbnail.<ext>".
*
- * @param $params Array: handler-specific parameters
+ * @param array $params handler-specific parameters
* @param $flags integer Bitfield that supports THUMB_* constants
* @return string
*/
@@ -831,8 +832,8 @@ abstract class File {
/**
* Return either a MediaTransformError or placeholder thumbnail (if $wgIgnoreImageErrors)
*
- * @param $thumbPath string Thumbnail storage path
- * @param $thumbUrl string Thumbnail URL
+ * @param string $thumbPath Thumbnail storage path
+ * @param string $thumbUrl Thumbnail URL
* @param $params Array
* @param $flags integer
* @return MediaTransformOutput
@@ -841,7 +842,7 @@ abstract class File {
global $wgIgnoreImageErrors;
if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
- return $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+ return $this->getHandler()->getTransform( $this, $thumbPath, $thumbUrl, $params );
} else {
return new MediaTransformError( 'thumbnail_error',
$params['width'], 0, wfMessage( 'thumbnail-dest-create' )->text() );
@@ -851,7 +852,7 @@ abstract class File {
/**
* Transform a media file
*
- * @param $params Array: an associative array of handler-specific parameters.
+ * @param array $params an associative array of handler-specific parameters.
* Typical keys are width, height and page.
* @param $flags Integer: a bitfield, may contain self::RENDER_NOW to force rendering
* @return MediaTransformOutput|bool False on failure
@@ -872,17 +873,18 @@ abstract class File {
$params['descriptionUrl'] = wfExpandUrl( $descriptionUrl, PROTO_CANONICAL );
}
+ $handler = $this->getHandler();
$script = $this->getTransformScript();
if ( $script && !( $flags & self::RENDER_NOW ) ) {
// Use a script to transform on client request, if possible
- $thumb = $this->handler->getScriptedTransform( $this, $script, $params );
+ $thumb = $handler->getScriptedTransform( $this, $script, $params );
if ( $thumb ) {
break;
}
}
$normalisedParams = $params;
- $this->handler->normaliseParams( $this, $normalisedParams );
+ $handler->normaliseParams( $this, $normalisedParams );
$thumbName = $this->thumbName( $normalisedParams );
$thumbUrl = $this->getThumbUrl( $thumbName );
@@ -895,20 +897,21 @@ abstract class File {
// XXX: Pass in the storage path even though we are not rendering anything
// and the path is supposed to be an FS path. This is due to getScalerType()
// getting called on the path and clobbering $thumb->getUrl() if it's false.
- $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+ $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
break;
}
// Clean up broken thumbnails as needed
$this->migrateThumbFile( $thumbName );
// Check if an up-to-date thumbnail already exists...
- wfDebug( __METHOD__.": Doing stat for $thumbPath\n" );
- if ( $this->repo->fileExists( $thumbPath ) && !( $flags & self::RENDER_FORCE ) ) {
+ wfDebug( __METHOD__ . ": Doing stat for $thumbPath\n" );
+ if ( !( $flags & self::RENDER_FORCE ) && $this->repo->fileExists( $thumbPath ) ) {
$timestamp = $this->repo->getFileTimestamp( $thumbPath );
if ( $timestamp !== false && $timestamp >= $wgThumbnailEpoch ) {
// XXX: Pass in the storage path even though we are not rendering anything
// and the path is supposed to be an FS path. This is due to getScalerType()
// getting called on the path and clobbering $thumb->getUrl() if it's false.
- $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+ $thumb = $handler->getTransform(
+ $this, $thumbPath, $thumbUrl, $params );
$thumb->setStoragePath( $thumbPath );
break;
}
@@ -935,7 +938,7 @@ abstract class File {
// Actually render the thumbnail...
wfProfileIn( __METHOD__ . '-doTransform' );
- $thumb = $this->handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $params );
+ $thumb = $handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $params );
wfProfileOut( __METHOD__ . '-doTransform' );
$tmpFile->bind( $thumb ); // keep alive with $thumb
@@ -945,7 +948,7 @@ abstract class File {
$this->lastError = $thumb->toText();
// Ignore errors if requested
if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
- $thumb = $this->handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $params );
+ $thumb = $handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $params );
}
} elseif ( $this->repo && $thumb->hasFile() && !$thumb->fileIsSource() ) {
// Copy the thumbnail from the file system into storage...
@@ -975,7 +978,7 @@ abstract class File {
}
/**
- * @param $thumbName string Thumbnail name
+ * @param string $thumbName Thumbnail name
* @return string Content-Disposition header value
*/
function getThumbDisposition( $thumbName ) {
@@ -1048,7 +1051,7 @@ abstract class File {
* Purge shared caches such as thumbnails and DB data caching
* STUB
* Overridden by LocalFile
- * @param $options Array Options, which include:
+ * @param array $options Options, which include:
* 'forThumbRefresh' : The purging is only to refresh thumbnails
*/
function purgeCache( $options = array() ) {}
@@ -1088,9 +1091,9 @@ abstract class File {
*
* STUB
* @param $limit integer Limit of rows to return
- * @param $start string timestamp Only revisions older than $start will be returned
- * @param $end string timestamp Only revisions newer than $end will be returned
- * @param $inc bool Include the endpoints of the time range
+ * @param string $start timestamp Only revisions older than $start will be returned
+ * @param string $end timestamp Only revisions newer than $end will be returned
+ * @param bool $inc Include the endpoints of the time range
*
* @return array
*/
@@ -1147,7 +1150,7 @@ abstract class File {
/**
* Get the path of an archived file relative to the public zone root
*
- * @param $suffix bool|string if not false, the name of an archived thumbnail file
+ * @param bool|string $suffix if not false, the name of an archived thumbnail file
*
* @return string
*/
@@ -1165,7 +1168,7 @@ abstract class File {
* Get the path, relative to the thumbnail zone root, of the
* thumbnail directory or a particular file if $suffix is specified
*
- * @param $suffix bool|string if not false, the name of a thumbnail file
+ * @param bool|string $suffix if not false, the name of a thumbnail file
*
* @return string
*/
@@ -1191,8 +1194,8 @@ abstract class File {
* Get the path, relative to the thumbnail zone root, for an archived file's thumbs directory
* or a specific thumb if the $suffix is given.
*
- * @param $archiveName string the timestamped name of an archived image
- * @param $suffix bool|string if not false, the name of a thumbnail file
+ * @param string $archiveName the timestamped name of an archived image
+ * @param bool|string $suffix if not false, the name of a thumbnail file
*
* @return string
*/
@@ -1209,7 +1212,7 @@ abstract class File {
/**
* Get the path of the archived file.
*
- * @param $suffix bool|string if not false, the name of an archived file.
+ * @param bool|string $suffix if not false, the name of an archived file.
*
* @return string
*/
@@ -1221,8 +1224,8 @@ abstract class File {
/**
* Get the path of an archived file's thumbs, or a particular thumb if $suffix is specified
*
- * @param $archiveName string the timestamped name of an archived image
- * @param $suffix bool|string if not false, the name of a thumbnail file
+ * @param string $archiveName the timestamped name of an archived image
+ * @param bool|string $suffix if not false, the name of a thumbnail file
*
* @return string
*/
@@ -1235,7 +1238,7 @@ abstract class File {
/**
* Get the path of the thumbnail directory, or a particular file if $suffix is specified
*
- * @param $suffix bool|string if not false, the name of a thumbnail file
+ * @param bool|string $suffix if not false, the name of a thumbnail file
*
* @return string
*/
@@ -1245,15 +1248,28 @@ abstract class File {
}
/**
+ * Get the path of the transcoded directory, or a particular file if $suffix is specified
+ *
+ * @param bool|string $suffix if not false, the name of a media file
+ *
+ * @return string
+ */
+ function getTranscodedPath( $suffix = false ) {
+ $this->assertRepoDefined();
+ return $this->repo->getZonePath( 'transcoded' ) . '/' . $this->getThumbRel( $suffix );
+ }
+
+ /**
* Get the URL of the archive directory, or a particular file if $suffix is specified
*
- * @param $suffix bool|string if not false, the name of an archived file
+ * @param bool|string $suffix if not false, the name of an archived file
*
* @return string
*/
function getArchiveUrl( $suffix = false ) {
$this->assertRepoDefined();
- $path = $this->repo->getZoneUrl( 'public' ) . '/archive/' . $this->getHashPath();
+ $ext = $this->getExtension();
+ $path = $this->repo->getZoneUrl( 'public', $ext ) . '/archive/' . $this->getHashPath();
if ( $suffix === false ) {
$path = substr( $path, 0, -1 );
} else {
@@ -1265,14 +1281,15 @@ abstract class File {
/**
* Get the URL of the archived file's thumbs, or a particular thumb if $suffix is specified
*
- * @param $archiveName string the timestamped name of an archived image
- * @param $suffix bool|string if not false, the name of a thumbnail file
+ * @param string $archiveName the timestamped name of an archived image
+ * @param bool|string $suffix if not false, the name of a thumbnail file
*
* @return string
*/
function getArchiveThumbUrl( $archiveName, $suffix = false ) {
$this->assertRepoDefined();
- $path = $this->repo->getZoneUrl( 'thumb' ) . '/archive/' .
+ $ext = $this->getExtension();
+ $path = $this->repo->getZoneUrl( 'thumb', $ext ) . '/archive/' .
$this->getHashPath() . rawurlencode( $archiveName ) . "/";
if ( $suffix === false ) {
$path = substr( $path, 0, -1 );
@@ -1283,15 +1300,17 @@ abstract class File {
}
/**
- * Get the URL of the thumbnail directory, or a particular file if $suffix is specified
+ * Get the URL of the zone directory, or a particular file if $suffix is specified
*
- * @param $suffix bool|string if not false, the name of a thumbnail file
+ * @param string $zone name of requested zone
+ * @param bool|string $suffix if not false, the name of a file in zone
*
* @return string path
*/
- function getThumbUrl( $suffix = false ) {
+ function getZoneUrl( $zone, $suffix = false ) {
$this->assertRepoDefined();
- $path = $this->repo->getZoneUrl( 'thumb' ) . '/' . $this->getUrlRel();
+ $ext = $this->getExtension();
+ $path = $this->repo->getZoneUrl( $zone, $ext ) . '/' . $this->getUrlRel();
if ( $suffix !== false ) {
$path .= '/' . rawurlencode( $suffix );
}
@@ -1299,9 +1318,31 @@ abstract class File {
}
/**
+ * Get the URL of the thumbnail directory, or a particular file if $suffix is specified
+ *
+ * @param bool|string $suffix if not false, the name of a thumbnail file
+ *
+ * @return string path
+ */
+ function getThumbUrl( $suffix = false ) {
+ return $this->getZoneUrl( 'thumb', $suffix );
+ }
+
+ /**
+ * Get the URL of the transcoded directory, or a particular file if $suffix is specified
+ *
+ * @param bool|string $suffix if not false, the name of a media file
+ *
+ * @return string path
+ */
+ function getTranscodedUrl( $suffix = false ) {
+ return $this->getZoneUrl( 'transcoded', $suffix );
+ }
+
+ /**
* Get the public zone virtual URL for a current version source file
*
- * @param $suffix bool|string if not false, the name of a thumbnail file
+ * @param bool|string $suffix if not false, the name of a thumbnail file
*
* @return string
*/
@@ -1317,7 +1358,7 @@ abstract class File {
/**
* Get the public zone virtual URL for an archived version source file
*
- * @param $suffix bool|string if not false, the name of a thumbnail file
+ * @param bool|string $suffix if not false, the name of a thumbnail file
*
* @return string
*/
@@ -1335,7 +1376,7 @@ abstract class File {
/**
* Get the virtual URL for a thumbnail file or directory
*
- * @param $suffix bool|string if not false, the name of a thumbnail file
+ * @param bool|string $suffix if not false, the name of a thumbnail file
*
* @return string
*/
@@ -1360,7 +1401,7 @@ abstract class File {
* @throws MWException
*/
function readOnlyError() {
- throw new MWException( get_class($this) . ': write operations are not supported' );
+ throw new MWException( get_class( $this ) . ': write operations are not supported' );
}
/**
@@ -1373,8 +1414,12 @@ abstract class File {
* @param $copyStatus string
* @param $source string
* @param $watch bool
+ * @param $timestamp string|bool
+ * @param $user User object or null to use $wgUser
+ * @return bool
+ * @throws MWException
*/
- function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false ) {
+ function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false, $timestamp = false, User $user = null ) {
$this->readOnlyError();
}
@@ -1386,17 +1431,21 @@ abstract class File {
* The archive name should be passed through to recordUpload for database
* registration.
*
- * @param $srcPath String: local filesystem path to the source image
+ * Options to $options include:
+ * - headers : name/value map of HTTP headers to use in response to GET/HEAD requests
+ *
+ * @param string $srcPath local filesystem path to the source image
* @param $flags Integer: a bitwise combination of:
* File::DELETE_SOURCE Delete the source file, i.e. move
* rather than copy
+ * @param array $options Optional additional parameters
* @return FileRepoStatus object. On success, the value member contains the
* archive name, or an empty string if it was a new file.
*
* STUB
* Overridden by LocalFile
*/
- function publish( $srcPath, $flags = 0 ) {
+ function publish( $srcPath, $flags = 0, array $options = array() ) {
$this->readOnlyError();
}
@@ -1490,9 +1539,9 @@ abstract class File {
* @param $target Title New file name
* @return FileRepoStatus object.
*/
- function move( $target ) {
+ function move( $target ) {
$this->readOnlyError();
- }
+ }
/**
* Delete all versions of the file.
@@ -1518,9 +1567,9 @@ abstract class File {
*
* May throw database exceptions on error.
*
- * @param $versions array set of record ids of deleted items to restore,
+ * @param array $versions set of record ids of deleted items to restore,
* or empty to restore all revisions.
- * @param $unsuppress bool remove restrictions on content upon restoration?
+ * @param bool $unsuppress remove restrictions on content upon restoration?
* @return int|bool the number of file revisions restored if successful,
* or false on failure
* STUB
@@ -1580,7 +1629,7 @@ abstract class File {
* Get an image size array like that returned by getImageSize(), or false if it
* can't be determined.
*
- * @param $fileName String: The filename
+ * @param string $fileName The filename
* @return Array
*/
function getImageSize( $fileName ) {
@@ -1617,15 +1666,15 @@ abstract class File {
$renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgLang->getCode() );
if ( $renderUrl ) {
if ( $this->repo->descriptionCacheExpiry > 0 ) {
- wfDebug("Attempting to get the description from cache...");
+ wfDebug( "Attempting to get the description from cache..." );
$key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', $wgLang->getCode(),
$this->getName() );
- $obj = $wgMemc->get($key);
- if ($obj) {
- wfDebug("success!\n");
+ $obj = $wgMemc->get( $key );
+ if ( $obj ) {
+ wfDebug( "success!\n" );
return $obj;
}
- wfDebug("miss\n");
+ wfDebug( "miss\n" );
}
wfDebug( "Fetching shared description from $renderUrl\n" );
$res = Http::get( $renderUrl );
@@ -1704,14 +1753,15 @@ abstract class File {
/**
* Get an associative array containing information about a file in the local filesystem.
*
- * @param $path String: absolute local filesystem path
+ * @param string $path absolute local filesystem path
* @param $ext Mixed: the file extension, or true to extract it from the filename.
* Set it to false to ignore the extension.
*
* @return array
+ * @deprecated since 1.19
*/
static function getPropsFromPath( $path, $ext = true ) {
- wfDebug( __METHOD__.": Getting file info for $path\n" );
+ wfDebug( __METHOD__ . ": Getting file info for $path\n" );
wfDeprecated( __METHOD__, '1.19' );
$fsFile = new FSFile( $path );
@@ -1728,6 +1778,7 @@ abstract class File {
* @param $path string
*
* @return bool|string False on failure
+ * @deprecated since 1.19
*/
static function sha1Base36( $path ) {
wfDeprecated( __METHOD__, '1.19' );
@@ -1737,6 +1788,18 @@ abstract class File {
}
/**
+ * @return Array HTTP header name/value map to use for HEAD/GET request responses
+ */
+ function getStreamHeaders() {
+ $handler = $this->getHandler();
+ if ( $handler ) {
+ return $handler->getStreamHeaders( $this->getMetadata() );
+ } else {
+ return array();
+ }
+ }
+
+ /**
* @return string
*/
function getLongDesc() {
@@ -1780,7 +1843,7 @@ abstract class File {
}
/**
- * @return Title
+ * @return Title|null
*/
function getRedirectedTitle() {
if ( $this->redirected ) {
@@ -1789,6 +1852,7 @@ abstract class File {
}
return $this->redirectTitle;
}
+ return null;
}
/**
diff --git a/includes/filerepo/file/ForeignAPIFile.php b/includes/filerepo/file/ForeignAPIFile.php
index 56482611..a96c1f3f 100644
--- a/includes/filerepo/file/ForeignAPIFile.php
+++ b/includes/filerepo/file/ForeignAPIFile.php
@@ -106,7 +106,7 @@ class ForeignAPIFile extends File {
}
/**
- * @param Array $params
+ * @param array $params
* @param int $flags
* @return bool|MediaTransformOutput
*/
@@ -189,7 +189,7 @@ class ForeignAPIFile extends File {
* @param string $method
* @return int|null|string
*/
- public function getUser( $method='text' ) {
+ public function getUser( $method = 'text' ) {
return isset( $this->mInfo['user'] ) ? strval( $this->mInfo['user'] ) : null;
}
@@ -256,7 +256,7 @@ class ForeignAPIFile extends File {
*/
function getThumbPath( $suffix = '' ) {
if ( $this->repo->canCacheThumbs() ) {
- $path = $this->repo->getZonePath('thumb') . '/' . $this->getHashPath( $this->getName() );
+ $path = $this->repo->getZonePath( 'thumb' ) . '/' . $this->getHashPath( $this->getName() );
if ( $suffix ) {
$path = $path . $suffix . '/';
}
@@ -293,7 +293,7 @@ class ForeignAPIFile extends File {
global $wgMemc, $wgContLang;
$url = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgContLang->getCode() );
- $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', md5($url) );
+ $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', md5( $url ) );
$wgMemc->delete( $key );
}
diff --git a/includes/filerepo/file/ForeignDBFile.php b/includes/filerepo/file/ForeignDBFile.php
index 91f6cb62..ee5883c4 100644
--- a/includes/filerepo/file/ForeignDBFile.php
+++ b/includes/filerepo/file/ForeignDBFile.php
@@ -57,9 +57,11 @@ class ForeignDBFile extends LocalFile {
/**
* @param $srcPath String
* @param $flags int
+ * @param $options Array
+ * @return \FileRepoStatus
* @throws MWException
*/
- function publish( $srcPath, $flags = 0 ) {
+ function publish( $srcPath, $flags = 0, array $options = array() ) {
$this->readOnlyError();
}
@@ -71,16 +73,19 @@ class ForeignDBFile extends LocalFile {
* @param $source string
* @param $watch bool
* @param $timestamp bool|string
+ * @param $user User object or null to use $wgUser
+ * @return bool
* @throws MWException
*/
function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
- $watch = false, $timestamp = false ) {
+ $watch = false, $timestamp = false, User $user = null ) {
$this->readOnlyError();
}
/**
* @param $versions array
* @param $unsuppress bool
+ * @return \FileRepoStatus
* @throws MWException
*/
function restore( $versions = array(), $unsuppress = false ) {
@@ -90,6 +95,7 @@ class ForeignDBFile extends LocalFile {
/**
* @param $reason string
* @param $suppress bool
+ * @return \FileRepoStatus
* @throws MWException
*/
function delete( $reason, $suppress = false ) {
@@ -98,6 +104,7 @@ class ForeignDBFile extends LocalFile {
/**
* @param $target Title
+ * @return \FileRepoStatus
* @throws MWException
*/
function move( $target ) {
@@ -108,7 +115,7 @@ class ForeignDBFile extends LocalFile {
* @return string
*/
function getDescriptionUrl() {
- // Restore remote behaviour
+ // Restore remote behavior
return File::getDescriptionUrl();
}
@@ -116,7 +123,7 @@ class ForeignDBFile extends LocalFile {
* @return string
*/
function getDescriptionText() {
- // Restore remote behaviour
+ // Restore remote behavior
return File::getDescriptionText();
}
}
diff --git a/includes/filerepo/file/LocalFile.php b/includes/filerepo/file/LocalFile.php
index 695c4e9e..639228b9 100644
--- a/includes/filerepo/file/LocalFile.php
+++ b/includes/filerepo/file/LocalFile.php
@@ -24,7 +24,7 @@
/**
* Bump this number when serialized cache records may be incompatible.
*/
-define( 'MW_FILE_VERSION', 8 );
+define( 'MW_FILE_VERSION', 9 );
/**
* Class to represent a local file in the wiki's own database
@@ -36,7 +36,7 @@ define( 'MW_FILE_VERSION', 8 );
* never name a file class explictly outside of the repo class. Instead use the
* repo's factory functions to generate file objects, for example:
*
- * RepoGroup::singleton()->getLocalRepo()->newFile($title);
+ * RepoGroup::singleton()->getLocalRepo()->newFile( $title );
*
* The convenience functions wfLocalFile() and wfFindFile() should be sufficient
* in most cases.
@@ -67,9 +67,11 @@ class LocalFile extends File {
$sha1, # SHA-1 base 36 content hash
$user, $user_text, # User, who uploaded the file
$description, # Description of current revision of the file
- $dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx)
+ $dataLoaded, # Whether or not core data has been loaded from the database (loadFromXxx)
+ $extraDataLoaded, # Whether or not lazy-loaded data has been loaded from the database
$upgraded, # Whether the row was upgraded on load
$locked, # True if the image row is locked
+ $lockedOwnTrx, # True if the image row is locked with a lock initiated transaction
$missing, # True if file is not present in file system. Not to be cached in memcached
$deleted; # Bitfield akin to rev_deleted
@@ -82,6 +84,8 @@ class LocalFile extends File {
protected $repoClass = 'LocalRepo';
+ const LOAD_ALL = 1; // integer; load all the lazy fields too (like metadata)
+
/**
* Create a LocalFile from a title
* Do not call this except from inside a repo class.
@@ -119,7 +123,7 @@ class LocalFile extends File {
* Create a LocalFile from a SHA-1 key
* Do not call this except from inside a repo class.
*
- * @param $sha1 string base-36 SHA-1
+ * @param string $sha1 base-36 SHA-1
* @param $repo LocalRepo
* @param string|bool $timestamp MW_timestamp (optional)
*
@@ -175,6 +179,7 @@ class LocalFile extends File {
$this->historyLine = 0;
$this->historyRes = null;
$this->dataLoaded = false;
+ $this->extraDataLoaded = false;
$this->assertRepoDefined();
$this->assertTitleDefined();
@@ -200,6 +205,7 @@ class LocalFile extends File {
wfProfileIn( __METHOD__ );
$this->dataLoaded = false;
+ $this->extraDataLoaded = false;
$key = $this->getCacheKey();
if ( !$key ) {
@@ -210,13 +216,17 @@ class LocalFile extends File {
$cachedValues = $wgMemc->get( $key );
// Check if the key existed and belongs to this version of MediaWiki
- if ( isset( $cachedValues['version'] ) && ( $cachedValues['version'] == MW_FILE_VERSION ) ) {
+ if ( isset( $cachedValues['version'] ) && $cachedValues['version'] == MW_FILE_VERSION ) {
wfDebug( "Pulling file metadata from cache key $key\n" );
$this->fileExists = $cachedValues['fileExists'];
if ( $this->fileExists ) {
$this->setProps( $cachedValues );
}
$this->dataLoaded = true;
+ $this->extraDataLoaded = true;
+ foreach ( $this->getLazyCacheFields( '' ) as $field ) {
+ $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
+ }
}
if ( $this->dataLoaded ) {
@@ -252,7 +262,17 @@ class LocalFile extends File {
}
}
- $wgMemc->set( $key, $cache, 60 * 60 * 24 * 7 ); // A week
+ // Strip off excessive entries from the subset of fields that can become large.
+ // If the cache value gets to large it will not fit in memcached and nothing will
+ // get cached at all, causing master queries for any file access.
+ foreach ( $this->getLazyCacheFields( '' ) as $field ) {
+ if ( isset( $cache[$field] ) && strlen( $cache[$field] ) > 100 * 1024 ) {
+ unset( $cache[$field] ); // don't let the value get too big
+ }
+ }
+
+ // Cache presence for 1 week and negatives for 1 day
+ $wgMemc->set( $key, $cache, $this->fileExists ? 86400 * 7 : 86400 );
}
/**
@@ -288,6 +308,28 @@ class LocalFile extends File {
}
/**
+ * @return array
+ */
+ function getLazyCacheFields( $prefix = 'img_' ) {
+ static $fields = array( 'metadata' );
+ static $results = array();
+
+ if ( $prefix == '' ) {
+ return $fields;
+ }
+
+ if ( !isset( $results[$prefix] ) ) {
+ $prefixedFields = array();
+ foreach ( $fields as $field ) {
+ $prefixedFields[] = $prefix . $field;
+ }
+ $results[$prefix] = $prefixedFields;
+ }
+
+ return $results[$prefix];
+ }
+
+ /**
* Load file metadata from the DB
*/
function loadFromDB() {
@@ -297,9 +339,9 @@ class LocalFile extends File {
# Unconditionally set loaded=true, we don't want the accessors constantly rechecking
$this->dataLoaded = true;
+ $this->extraDataLoaded = true;
$dbr = $this->repo->getMasterDB();
-
$row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ),
array( 'img_name' => $this->getName() ), $fname );
@@ -313,27 +355,70 @@ class LocalFile extends File {
}
/**
- * Decode a row from the database (either object or array) to an array
- * with timestamps and MIME types decoded, and the field prefix removed.
- * @param $row
+ * Load lazy file metadata from the DB.
+ * This covers fields that are sometimes not cached.
+ */
+ protected function loadExtraFromDB() {
+ # Polymorphic function name to distinguish foreign and local fetches
+ $fname = get_class( $this ) . '::' . __FUNCTION__;
+ wfProfileIn( $fname );
+
+ # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
+ $this->extraDataLoaded = true;
+
+ $dbr = $this->repo->getSlaveDB();
+ // In theory the file could have just been renamed/deleted...oh well
+ $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ),
+ array( 'img_name' => $this->getName() ), $fname );
+
+ if ( !$row ) { // fallback to master
+ $dbr = $this->repo->getMasterDB();
+ $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ),
+ array( 'img_name' => $this->getName() ), $fname );
+ }
+
+ if ( $row ) {
+ foreach ( $this->unprefixRow( $row, 'img_' ) as $name => $value ) {
+ $this->$name = $value;
+ }
+ } else {
+ throw new MWException( "Could not find data for image '{$this->getName()}'." );
+ }
+
+ wfProfileOut( $fname );
+ }
+
+ /**
+ * @param Row $row
* @param $prefix string
- * @throws MWException
- * @return array
+ * @return Array
*/
- function decodeRow( $row, $prefix = 'img_' ) {
+ protected function unprefixRow( $row, $prefix = 'img_' ) {
$array = (array)$row;
$prefixLength = strlen( $prefix );
// Sanity check prefix once
if ( substr( key( $array ), 0, $prefixLength ) !== $prefix ) {
- throw new MWException( __METHOD__ . ': incorrect $prefix parameter' );
+ throw new MWException( __METHOD__ . ': incorrect $prefix parameter' );
}
$decoded = array();
-
foreach ( $array as $name => $value ) {
$decoded[substr( $name, $prefixLength )] = $value;
}
+ return $decoded;
+ }
+
+ /**
+ * Decode a row from the database (either object or array) to an array
+ * with timestamps and MIME types decoded, and the field prefix removed.
+ * @param $row
+ * @param $prefix string
+ * @throws MWException
+ * @return array
+ */
+ function decodeRow( $row, $prefix = 'img_' ) {
+ $decoded = $this->unprefixRow( $row, $prefix );
$decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] );
@@ -357,6 +442,8 @@ class LocalFile extends File {
*/
function loadFromRow( $row, $prefix = 'img_' ) {
$this->dataLoaded = true;
+ $this->extraDataLoaded = true;
+
$array = $this->decodeRow( $row, $prefix );
foreach ( $array as $name => $value ) {
@@ -369,8 +456,9 @@ class LocalFile extends File {
/**
* Load file metadata from cache or DB, unless already loaded
+ * @param integer $flags
*/
- function load() {
+ function load( $flags = 0 ) {
if ( !$this->dataLoaded ) {
if ( !$this->loadFromCache() ) {
$this->loadFromDB();
@@ -378,6 +466,9 @@ class LocalFile extends File {
}
$this->dataLoaded = true;
}
+ if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
+ $this->loadExtraFromDB();
+ }
}
/**
@@ -397,7 +488,7 @@ class LocalFile extends File {
} else {
$handler = $this->getHandler();
if ( $handler ) {
- $validity = $handler->isMetadataValid( $this, $this->metadata );
+ $validity = $handler->isMetadataValid( $this, $this->getMetadata() );
if ( $validity === MediaHandler::METADATA_BAD
|| ( $validity === MediaHandler::METADATA_COMPATIBLE && $wgUpdateCompatibleMetadata )
) {
@@ -464,6 +555,7 @@ class LocalFile extends File {
/**
* Set properties in this object to be equal to those given in the
* associative array $info. Only cacheable fields can be set.
+ * All fields *must* be set in $info except for getLazyCacheFields().
*
* If 'mime' is given, it will be split into major_mime/minor_mime.
* If major_mime/minor_mime are given, $this->mime will also be set.
@@ -552,7 +644,7 @@ class LocalFile extends File {
/**
* Returns ID or name of user who uploaded the file
*
- * @param $type string 'text' or 'id'
+ * @param string $type 'text' or 'id'
* @return int|string
*/
function getUser( $type = 'text' ) {
@@ -570,7 +662,7 @@ class LocalFile extends File {
* @return string
*/
function getMetadata() {
- $this->load();
+ $this->load( self::LOAD_ALL ); // large metadata is loaded in another step
return $this->metadata;
}
@@ -638,15 +730,14 @@ class LocalFile extends File {
* RTT regression for wikis without 404 handling.
*/
function migrateThumbFile( $thumbName ) {
- $thumbDir = $this->getThumbPath();
-
/* Old code for bug 2532
+ $thumbDir = $this->getThumbPath();
$thumbPath = "$thumbDir/$thumbName";
if ( is_dir( $thumbPath ) ) {
// Directory where file should be
// This happened occasionally due to broken migration code in 1.5
// Rename to broken-*
- for ( $i = 0; $i < 100 ; $i++ ) {
+ for ( $i = 0; $i < 100; $i++ ) {
$broken = $this->repo->getZonePath( 'public' ) . "/broken-$i-$thumbName";
if ( !file_exists( $broken ) ) {
rename( $thumbPath, $broken );
@@ -672,7 +763,7 @@ class LocalFile extends File {
/**
* Get all thumbnail names previously generated for this file
- * @param $archiveName string|bool Name of an archive file, default false
+ * @param string|bool $archiveName Name of an archive file, default false
* @return array first element is the base dir, then files in that base dir.
*/
function getThumbnails( $archiveName = false ) {
@@ -736,7 +827,7 @@ class LocalFile extends File {
/**
* Delete cached transformed files for an archived version only.
- * @param $archiveName string name of the archived file
+ * @param string $archiveName name of the archived file
*/
function purgeOldThumbnails( $archiveName ) {
global $wgUseSquid;
@@ -771,8 +862,16 @@ class LocalFile extends File {
// Delete thumbnails
$files = $this->getThumbnails();
+ // Always purge all files from squid regardless of handler filters
+ if ( $wgUseSquid ) {
+ $urls = array();
+ foreach( $files as $file ) {
+ $urls[] = $this->getThumbUrl( $file );
+ }
+ array_shift( $urls ); // don't purge directory
+ }
- // Give media handler a chance to filter the purge list
+ // Give media handler a chance to filter the file purge list
if ( !empty( $options['forThumbRefresh'] ) ) {
$handler = $this->getHandler();
if ( $handler ) {
@@ -788,10 +887,6 @@ class LocalFile extends File {
// Purge the squid
if ( $wgUseSquid ) {
- $urls = array();
- foreach( $files as $file ) {
- $urls[] = $this->getThumbUrl( $file );
- }
SquidUpdate::purge( $urls );
}
@@ -800,13 +895,13 @@ class LocalFile extends File {
/**
* Delete a list of thumbnails visible at urls
- * @param $dir string base dir of the files.
- * @param $files array of strings: relative filenames (to $dir)
+ * @param string $dir base dir of the files.
+ * @param array $files of strings: relative filenames (to $dir)
*/
protected function purgeThumbList( $dir, $files ) {
$fileListDebug = strtr(
var_export( $files, true ),
- array("\n"=>'')
+ array( "\n" => '' )
);
wfDebug( __METHOD__ . ": $fileListDebug\n" );
@@ -814,7 +909,9 @@ class LocalFile extends File {
foreach ( $files as $file ) {
# Check that the base file name is part of the thumb name
# This is a basic sanity check to avoid erasing unrelated directories
- if ( strpos( $file, $this->getName() ) !== false ) {
+ if ( strpos( $file, $this->getName() ) !== false
+ || strpos( $file, "-thumbnail" ) !== false // "short" thumb name
+ ) {
$purgeList[] = "{$dir}/{$file}";
}
}
@@ -949,15 +1046,15 @@ class LocalFile extends File {
/**
* Upload a file and record it in the DB
- * @param $srcPath String: source storage path or virtual URL
- * @param $comment String: upload description
- * @param $pageText String: text to use for the new description page,
+ * @param string $srcPath source storage path, virtual URL, or filesystem path
+ * @param string $comment upload description
+ * @param string $pageText text to use for the new description page,
* if a new description page is created
* @param $flags Integer|bool: flags for publish()
- * @param $props Array|bool: File properties, if known. This can be used to reduce the
+ * @param array|bool $props File properties, if known. This can be used to reduce the
* upload time when uploading virtual URLs for which the file info
* is already known
- * @param $timestamp String|bool: timestamp for img_timestamp, or false to use the current time
+ * @param string|bool $timestamp timestamp for img_timestamp, or false to use the current time
* @param $user User|null: User object or null to use $wgUser
*
* @return FileRepoStatus object. On success, the value member contains the
@@ -970,11 +1067,34 @@ class LocalFile extends File {
return $this->readOnlyFatalStatus();
}
+ if ( !$props ) {
+ wfProfileIn( __METHOD__ . '-getProps' );
+ if ( $this->repo->isVirtualUrl( $srcPath )
+ || FileBackend::isStoragePath( $srcPath ) )
+ {
+ $props = $this->repo->getFileProps( $srcPath );
+ } else {
+ $props = FSFile::getPropsFromPath( $srcPath );
+ }
+ wfProfileOut( __METHOD__ . '-getProps' );
+ }
+
+ $options = array();
+ $handler = MediaHandler::getHandler( $props['mime'] );
+ if ( $handler ) {
+ $options['headers'] = $handler->getStreamHeaders( $props['metadata'] );
+ } else {
+ $options['headers'] = array();
+ }
+
+ // Trim spaces on user supplied text
+ $comment = trim( $comment );
+
// truncate nicely or the DB will do it for us
// non-nicely (dangling multi-byte chars, non-truncated version in cache).
$comment = $wgContLang->truncate( $comment, 255 );
$this->lock(); // begin
- $status = $this->publish( $srcPath, $flags );
+ $status = $this->publish( $srcPath, $flags, $options );
if ( $status->successCount > 0 ) {
# Essentially we are displacing any existing current file and saving
@@ -999,20 +1119,25 @@ class LocalFile extends File {
* @param $source string
* @param $watch bool
* @param $timestamp string|bool
+ * @param $user User object or null to use $wgUser
* @return bool
*/
function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
- $watch = false, $timestamp = false )
+ $watch = false, $timestamp = false, User $user = null )
{
+ if ( !$user ) {
+ global $wgUser;
+ $user = $wgUser;
+ }
+
$pageText = SpecialUpload::getInitialPageText( $desc, $license, $copyStatus, $source );
- if ( !$this->recordUpload2( $oldver, $desc, $pageText ) ) {
+ if ( !$this->recordUpload2( $oldver, $desc, $pageText, false, $timestamp, $user ) ) {
return false;
}
if ( $watch ) {
- global $wgUser;
- $wgUser->addWatch( $this->getTitle() );
+ $user->addWatch( $this->getTitle() );
}
return true;
}
@@ -1165,7 +1290,7 @@ class LocalFile extends File {
$log->getRcComment(),
false
);
- if (!is_null($nullRevision)) {
+ if ( !is_null( $nullRevision ) ) {
$nullRevision->insertOn( $dbw );
wfRunHooks( 'NewRevisionFromEditComplete', array( $wikiPage, $nullRevision, $latest, $user ) );
@@ -1186,17 +1311,18 @@ class LocalFile extends File {
} else {
# New file; create the description page.
# There's already a log entry, so don't make a second RC entry
- # Squid and file cache for the description page are purged by doEdit.
- $status = $wikiPage->doEdit( $pageText, $comment, EDIT_NEW | EDIT_SUPPRESS_RC, false, $user );
+ # Squid and file cache for the description page are purged by doEditContent.
+ $content = ContentHandler::makeContent( $pageText, $descTitle );
+ $status = $wikiPage->doEditContent( $content, $comment, EDIT_NEW | EDIT_SUPPRESS_RC, false, $user );
if ( isset( $status->value['revision'] ) ) { // XXX; doEdit() uses a transaction
- $dbw->begin();
+ $dbw->begin( __METHOD__ );
$dbw->update( 'logging',
array( 'log_page' => $status->value['revision']->getPage() ),
array( 'log_id' => $logId ),
__METHOD__
);
- $dbw->commit(); // commit before anything bad can happen
+ $dbw->commit( __METHOD__ ); // commit before anything bad can happen
}
}
wfProfileOut( __METHOD__ . '-edit' );
@@ -1246,14 +1372,15 @@ class LocalFile extends File {
* The archive name should be passed through to recordUpload for database
* registration.
*
- * @param $srcPath String: local filesystem path to the source image
+ * @param string $srcPath local filesystem path to the source image
* @param $flags Integer: a bitwise combination of:
* File::DELETE_SOURCE Delete the source file, i.e. move rather than copy
+ * @param array $options Optional additional parameters
* @return FileRepoStatus object. On success, the value member contains the
* archive name, or an empty string if it was a new file.
*/
- function publish( $srcPath, $flags = 0 ) {
- return $this->publishTo( $srcPath, $this->getRel(), $flags );
+ function publish( $srcPath, $flags = 0, array $options = array() ) {
+ return $this->publishTo( $srcPath, $this->getRel(), $flags, $options );
}
/**
@@ -1263,14 +1390,15 @@ class LocalFile extends File {
* The archive name should be passed through to recordUpload for database
* registration.
*
- * @param $srcPath String: local filesystem path to the source image
- * @param $dstRel String: target relative path
+ * @param string $srcPath local filesystem path to the source image
+ * @param string $dstRel target relative path
* @param $flags Integer: a bitwise combination of:
* File::DELETE_SOURCE Delete the source file, i.e. move rather than copy
+ * @param array $options Optional additional parameters
* @return FileRepoStatus object. On success, the value member contains the
* archive name, or an empty string if it was a new file.
*/
- function publishTo( $srcPath, $dstRel, $flags = 0 ) {
+ function publishTo( $srcPath, $dstRel, $flags = 0, array $options = array() ) {
if ( $this->getRepo()->getReadOnlyReason() !== false ) {
return $this->readOnlyFatalStatus();
}
@@ -1280,7 +1408,7 @@ class LocalFile extends File {
$archiveName = wfTimestamp( TS_MW ) . '!'. $this->getName();
$archiveRel = 'archive/' . $this->getHashPath() . $archiveName;
$flags = $flags & File::DELETE_SOURCE ? LocalRepo::DELETE_SOURCE : 0;
- $status = $this->repo->publish( $srcPath, $dstRel, $archiveRel, $flags );
+ $status = $this->repo->publish( $srcPath, $dstRel, $archiveRel, $flags, $options );
if ( $status->value == 'new' ) {
$status->value = '';
@@ -1422,7 +1550,7 @@ class LocalFile extends File {
*
* May throw database exceptions on error.
*
- * @param $versions array set of record ids of deleted items to restore,
+ * @param array $versions set of record ids of deleted items to restore,
* or empty to restore all revisions.
* @param $unsuppress Boolean
* @return FileRepoStatus
@@ -1472,12 +1600,11 @@ class LocalFile extends File {
* @return bool|mixed
*/
function getDescriptionText() {
- global $wgParser;
$revision = Revision::newFromTitle( $this->title, false, Revision::READ_NORMAL );
if ( !$revision ) return false;
- $text = $revision->getText();
- if ( !$text ) return false;
- $pout = $wgParser->parse( $text, $this->title, new ParserOptions() );
+ $content = $revision->getContent();
+ if ( !$content ) return false;
+ $pout = $content->getParserOutput( $this->title, null, new ParserOptions() );
return $pout->getText();
}
@@ -1547,7 +1674,10 @@ class LocalFile extends File {
$dbw = $this->repo->getMasterDB();
if ( !$this->locked ) {
- $dbw->begin( __METHOD__ );
+ if ( !$dbw->trxLevel() ) {
+ $dbw->begin( __METHOD__ );
+ $this->lockedOwnTrx = true;
+ }
$this->locked++;
}
@@ -1562,9 +1692,10 @@ class LocalFile extends File {
function unlock() {
if ( $this->locked ) {
--$this->locked;
- if ( !$this->locked ) {
+ if ( !$this->locked && $this->lockedOwnTrx ) {
$dbw = $this->repo->getMasterDB();
$dbw->commit( __METHOD__ );
+ $this->lockedOwnTrx = false;
}
}
}
@@ -1576,6 +1707,7 @@ class LocalFile extends File {
$this->locked = false;
$dbw = $this->repo->getMasterDB();
$dbw->rollback( __METHOD__ );
+ $this->lockedOwnTrx = false;
}
/**
@@ -1758,7 +1890,7 @@ class LocalFileDeleteBatch {
'fa_deleted_user' => $encUserId,
'fa_deleted_timestamp' => $encTimestamp,
'fa_deleted_reason' => $encReason,
- 'fa_deleted' => $this->suppress ? $bitfield : 0,
+ 'fa_deleted' => $this->suppress ? $bitfield : 0,
'fa_name' => 'img_name',
'fa_archive_name' => 'NULL',
@@ -1773,7 +1905,8 @@ class LocalFileDeleteBatch {
'fa_description' => 'img_description',
'fa_user' => 'img_user',
'fa_user_text' => 'img_user_text',
- 'fa_timestamp' => 'img_timestamp'
+ 'fa_timestamp' => 'img_timestamp',
+ 'fa_sha1' => 'img_sha1',
), $where, __METHOD__ );
}
@@ -1805,6 +1938,7 @@ class LocalFileDeleteBatch {
'fa_user' => 'oi_user',
'fa_user_text' => 'oi_user_text',
'fa_timestamp' => 'oi_timestamp',
+ 'fa_sha1' => 'oi_sha1',
), $where, __METHOD__ );
}
}
@@ -1836,7 +1970,7 @@ class LocalFileDeleteBatch {
$this->file->lock();
// Leave private files alone
$privateFiles = array();
- list( $oldRels, $deleteCurrent ) = $this->getOldRels();
+ list( $oldRels, ) = $this->getOldRels();
$dbw = $this->file->repo->getMasterDB();
if ( !empty( $oldRels ) ) {
@@ -1914,7 +2048,7 @@ class LocalFileDeleteBatch {
$files = $newBatch = array();
foreach ( $batch as $batchItem ) {
- list( $src, $dest ) = $batchItem;
+ list( $src, ) = $batchItem;
$files[$src] = $this->file->repo->getVirtualUrl( 'public' ) . '/' . rawurlencode( $src );
}
@@ -2004,7 +2138,9 @@ class LocalFileRestoreBatch {
$conditions[] = 'fa_id IN (' . $dbw->makeList( $this->ids ) . ')';
}
- $result = $dbw->select( 'filearchive', '*',
+ $result = $dbw->select(
+ 'filearchive',
+ ArchivedFile::selectFields(),
$conditions,
__METHOD__,
array( 'ORDER BY' => 'fa_timestamp DESC' )
@@ -2037,7 +2173,12 @@ class LocalFileRestoreBatch {
$deletedRel = $this->file->repo->getDeletedHashPath( $row->fa_storage_key ) . $row->fa_storage_key;
$deletedUrl = $this->file->repo->getVirtualUrl() . '/deleted/' . $deletedRel;
- $sha1 = substr( $row->fa_storage_key, 0, strcspn( $row->fa_storage_key, '.' ) );
+ if( isset( $row->fa_sha1 ) ) {
+ $sha1 = $row->fa_sha1;
+ } else {
+ // old row, populate from key
+ $sha1 = LocalRepo::getHashFromKey( $row->fa_storage_key );
+ }
# Fix leading zero
if ( strlen( $sha1 ) == 32 && $sha1[0] == '0' ) {
@@ -2251,7 +2392,7 @@ class LocalFileRestoreBatch {
/**
* Delete unused files in the deleted zone.
* This should be called from outside the transaction in which execute() was called.
- * @return FileRepoStatus|void
+ * @return FileRepoStatus
*/
function cleanup() {
if ( !$this->cleanupBatch ) {
@@ -2492,7 +2633,7 @@ class LocalFileMoveBatch {
*/
function getMoveTriplets() {
$moves = array_merge( array( $this->cur ), $this->olds );
- $triplets = array(); // The format is: (srcUrl, destZone, destUrl)
+ $triplets = array(); // The format is: (srcUrl, destZone, destUrl)
foreach ( $moves as $move ) {
// $move: (oldRelativePath, newRelativePath)
diff --git a/includes/filerepo/file/OldLocalFile.php b/includes/filerepo/file/OldLocalFile.php
index 40d7dca7..5c505928 100644
--- a/includes/filerepo/file/OldLocalFile.php
+++ b/includes/filerepo/file/OldLocalFile.php
@@ -42,7 +42,7 @@ class OldLocalFile extends LocalFile {
static function newFromTitle( $title, $repo, $time = null ) {
# The null default value is only here to avoid an E_STRICT
if ( $time === null ) {
- throw new MWException( __METHOD__.' got null for $time parameter' );
+ throw new MWException( __METHOD__ . ' got null for $time parameter' );
}
return new self( $title, $repo, $time, null );
}
@@ -73,7 +73,7 @@ class OldLocalFile extends LocalFile {
* Create a OldLocalFile from a SHA-1 key
* Do not call this except from inside a repo class.
*
- * @param $sha1 string base-36 SHA-1
+ * @param string $sha1 base-36 SHA-1
* @param $repo LocalRepo
* @param string|bool $timestamp MW_timestamp (optional)
*
@@ -123,8 +123,8 @@ class OldLocalFile extends LocalFile {
/**
* @param $title Title
* @param $repo FileRepo
- * @param $time String: timestamp or null to load by archive name
- * @param $archiveName String: archive name or null to load by timestamp
+ * @param string $time timestamp or null to load by archive name
+ * @param string $archiveName archive name or null to load by timestamp
* @throws MWException
*/
function __construct( $title, $repo, $time, $archiveName ) {
@@ -132,7 +132,7 @@ class OldLocalFile extends LocalFile {
$this->requestedTime = $time;
$this->archive_name = $archiveName;
if ( is_null( $time ) && is_null( $archiveName ) ) {
- throw new MWException( __METHOD__.': must specify at least one of $time or $archiveName' );
+ throw new MWException( __METHOD__ . ': must specify at least one of $time or $archiveName' );
}
}
@@ -164,18 +164,19 @@ class OldLocalFile extends LocalFile {
* @return bool
*/
function isVisible() {
- return $this->exists() && !$this->isDeleted(File::DELETED_FILE);
+ return $this->exists() && !$this->isDeleted( File::DELETED_FILE );
}
function loadFromDB() {
wfProfileIn( __METHOD__ );
+
$this->dataLoaded = true;
$dbr = $this->repo->getSlaveDB();
$conds = array( 'oi_name' => $this->getName() );
if ( is_null( $this->requestedTime ) ) {
$conds['oi_archive_name'] = $this->archive_name;
} else {
- $conds[] = 'oi_timestamp = ' . $dbr->addQuotes( $dbr->timestamp( $this->requestedTime ) );
+ $conds['oi_timestamp'] = $dbr->timestamp( $this->requestedTime );
}
$row = $dbr->selectRow( 'oldimage', $this->getCacheFields( 'oi_' ),
$conds, __METHOD__, array( 'ORDER BY' => 'oi_timestamp DESC' ) );
@@ -184,6 +185,42 @@ class OldLocalFile extends LocalFile {
} else {
$this->fileExists = false;
}
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Load lazy file metadata from the DB
+ */
+ protected function loadExtraFromDB() {
+ wfProfileIn( __METHOD__ );
+
+ $this->extraDataLoaded = true;
+ $dbr = $this->repo->getSlaveDB();
+ $conds = array( 'oi_name' => $this->getName() );
+ if ( is_null( $this->requestedTime ) ) {
+ $conds['oi_archive_name'] = $this->archive_name;
+ } else {
+ $conds['oi_timestamp'] = $dbr->timestamp( $this->requestedTime );
+ }
+ // In theory the file could have just been renamed/deleted...oh well
+ $row = $dbr->selectRow( 'oldimage', $this->getLazyCacheFields( 'oi_' ),
+ $conds, __METHOD__, array( 'ORDER BY' => 'oi_timestamp DESC' ) );
+
+ if ( !$row ) { // fallback to master
+ $dbr = $this->repo->getMasterDB();
+ $row = $dbr->selectRow( 'oldimage', $this->getLazyCacheFields( 'oi_' ),
+ $conds, __METHOD__, array( 'ORDER BY' => 'oi_timestamp DESC' ) );
+ }
+
+ if ( $row ) {
+ foreach ( $this->unprefixRow( $row, 'oi_' ) as $name => $value ) {
+ $this->$name = $value;
+ }
+ } else {
+ throw new MWException( "Could not find data for image '{$this->archive_name}'." );
+ }
+
wfProfileOut( __METHOD__ );
}
@@ -218,7 +255,7 @@ class OldLocalFile extends LocalFile {
# Don't destroy file info of missing files
if ( !$this->fileExists ) {
- wfDebug( __METHOD__.": file does not exist, aborting\n" );
+ wfDebug( __METHOD__ . ": file does not exist, aborting\n" );
wfProfileOut( __METHOD__ );
return;
}
@@ -226,7 +263,7 @@ class OldLocalFile extends LocalFile {
$dbw = $this->repo->getMasterDB();
list( $major, $minor ) = self::splitMime( $this->mime );
- wfDebug(__METHOD__.': upgrading '.$this->archive_name." to the current schema\n");
+ wfDebug( __METHOD__ . ': upgrading ' . $this->archive_name . " to the current schema\n" );
$dbw->update( 'oldimage',
array(
'oi_size' => $this->size, // sanity
@@ -281,8 +318,8 @@ class OldLocalFile extends LocalFile {
/**
* Upload a file directly into archive. Generally for Special:Import.
*
- * @param $srcPath string File system path of the source file
- * @param $archiveName string Full archive name of the file, in the form
+ * @param string $srcPath File system path of the source file
+ * @param string $archiveName Full archive name of the file, in the form
* $timestamp!$filename, where $filename must match $this->getName()
*
* @param $timestamp string
@@ -313,10 +350,10 @@ class OldLocalFile extends LocalFile {
/**
* Record a file upload in the oldimage table, without adding log entries.
*
- * @param $srcPath string File system path to the source file
- * @param $archiveName string The archive name of the file
+ * @param string $srcPath File system path to the source file
+ * @param string $archiveName The archive name of the file
* @param $timestamp string
- * @param $comment string Upload comment
+ * @param string $comment Upload comment
* @param $user User User who did this upload
* @return bool
*/
diff --git a/includes/filerepo/file/UnregisteredLocalFile.php b/includes/filerepo/file/UnregisteredLocalFile.php
index 8d4a3f88..47ba6d6b 100644
--- a/includes/filerepo/file/UnregisteredLocalFile.php
+++ b/includes/filerepo/file/UnregisteredLocalFile.php
@@ -42,7 +42,7 @@ class UnregisteredLocalFile extends File {
var $handler;
/**
- * @param $path string Storage path
+ * @param string $path Storage path
* @param $mime string
* @return UnregisteredLocalFile
*/
@@ -71,7 +71,7 @@ class UnregisteredLocalFile extends File {
*/
function __construct( $title = false, $repo = false, $path = false, $mime = false ) {
if ( !( $title && $repo ) && !$path ) {
- throw new MWException( __METHOD__.': not enough parameters, must specify title and repo, or a full path' );
+ throw new MWException( __METHOD__ . ': not enough parameters, must specify title and repo, or a full path' );
}
if ( $title instanceof Title ) {
$this->title = File::normalizeTitle( $title, 'exception' );
@@ -179,10 +179,18 @@ class UnregisteredLocalFile extends File {
*/
function getSize() {
$this->assertRepoDefined();
- $props = $this->repo->getFileProps( $this->path );
- if ( isset( $props['size'] ) ) {
- return $props['size'];
- }
- return false; // doesn't exist
+ return $this->repo->getFileSize( $this->path );
+ }
+
+ /**
+ * Optimize getLocalRefPath() by using an existing local reference.
+ * The file at the path of $fsFile should not be deleted (or at least
+ * not until the end of the request). This is mostly a performance hack.
+ *
+ * @param $fsFile FSFile
+ * @return void
+ */
+ public function setLocalReference( FSFile $fsFile ) {
+ $this->fsFile = $fsFile;
}
}
diff --git a/includes/installer/CliInstaller.php b/includes/installer/CliInstaller.php
index 38b4a824..bb7e8776 100644
--- a/includes/installer/CliInstaller.php
+++ b/includes/installer/CliInstaller.php
@@ -129,7 +129,7 @@ class CliInstaller extends Installer {
/**
* Write LocalSettings.php to a given path
*
- * @param $path String Full path to write LocalSettings.php to
+ * @param string $path Full path to write LocalSettings.php to
*/
public function writeConfigurationFile( $path ) {
$ls = InstallerOverrides::getLocalSettingsGenerator( $this );
@@ -191,9 +191,9 @@ class CliInstaller extends Installer {
}
}
- public function envCheckPath( ) {
+ public function envCheckPath() {
if ( !$this->specifiedScriptPath ) {
- $this->showMessage( 'config-no-cli-uri', $this->getVar("wgScriptPath") );
+ $this->showMessage( 'config-no-cli-uri', $this->getVar( "wgScriptPath" ) );
}
return parent::envCheckPath();
}
diff --git a/includes/installer/DatabaseInstaller.php b/includes/installer/DatabaseInstaller.php
index de59b2d6..3472b7ff 100644
--- a/includes/installer/DatabaseInstaller.php
+++ b/includes/installer/DatabaseInstaller.php
@@ -62,12 +62,12 @@ abstract class DatabaseInstaller {
/**
* Return the internal name, e.g. 'mysql', or 'sqlite'.
*/
- public abstract function getName();
+ abstract public function getName();
/**
* @return bool Returns true if the client library is compiled in.
*/
- public abstract function isCompiled();
+ abstract public function isCompiled();
/**
* Checks for installation prerequisites other than those checked by isCompiled()
@@ -85,7 +85,7 @@ abstract class DatabaseInstaller {
*
* If this is called, $this->parent can be assumed to be a WebInstaller.
*/
- public abstract function getConnectForm();
+ abstract public function getConnectForm();
/**
* Set variables based on the request array, assuming it was submitted
@@ -96,7 +96,7 @@ abstract class DatabaseInstaller {
*
* @return Status
*/
- public abstract function submitConnectForm();
+ abstract public function submitConnectForm();
/**
* Get HTML for a web form that retrieves settings used for installation.
@@ -127,7 +127,7 @@ abstract class DatabaseInstaller {
*
* @return Status
*/
- public abstract function openConnection();
+ abstract public function openConnection();
/**
* Create the database and return a Status object indicating success or
@@ -135,7 +135,7 @@ abstract class DatabaseInstaller {
*
* @return Status
*/
- public abstract function setupDatabase();
+ abstract public function setupDatabase();
/**
* Connect to the database using the administrative user/password currently
@@ -218,7 +218,7 @@ abstract class DatabaseInstaller {
*
* @return String
*/
- public abstract function getLocalSettings();
+ abstract public function getLocalSettings();
/**
* Override this to provide DBMS-specific schema variables, to be
@@ -240,7 +240,7 @@ abstract class DatabaseInstaller {
if ( $status->isOK() ) {
$status->value->setSchemaVars( $this->getSchemaVars() );
} else {
- throw new MWException( __METHOD__.': unexpected DB connection error' );
+ throw new MWException( __METHOD__ . ': unexpected DB connection error' );
}
}
@@ -252,7 +252,7 @@ abstract class DatabaseInstaller {
public function enableLB() {
$status = $this->getConnection();
if ( !$status->isOK() ) {
- throw new MWException( __METHOD__.': unexpected DB connection error' );
+ throw new MWException( __METHOD__ . ': unexpected DB connection error' );
}
LBFactory::setInstance( new LBFactory_Single( array(
'connection' => $status->value ) ) );
@@ -269,14 +269,15 @@ abstract class DatabaseInstaller {
$ret = true;
ob_start( array( $this, 'outputHandler' ) );
+ $up = DatabaseUpdater::newForDB( $this->db );
try {
- $up = DatabaseUpdater::newForDB( $this->db );
$up->doUpdates();
} catch ( MWException $e ) {
echo "\nAn error occurred:\n";
echo $e->getText();
$ret = false;
}
+ $up->purgeCache();
ob_end_flush();
return $ret;
}
@@ -526,7 +527,7 @@ abstract class DatabaseInstaller {
/**
* Get a standard web-user fieldset
- * @param $noCreateMsg String: Message to display instead of the creation checkbox.
+ * @param string $noCreateMsg Message to display instead of the creation checkbox.
* Set this to false to show a creation checkbox.
*
* @return String
diff --git a/includes/installer/DatabaseUpdater.php b/includes/installer/DatabaseUpdater.php
index ff0a99e9..25f751c7 100644
--- a/includes/installer/DatabaseUpdater.php
+++ b/includes/installer/DatabaseUpdater.php
@@ -40,6 +40,13 @@ abstract class DatabaseUpdater {
protected $updates = array();
/**
+ * Array of updates that were skipped
+ *
+ * @var array
+ */
+ protected $updatesSkipped = array();
+
+ /**
* List of extension-provided database updates
* @var array
*/
@@ -54,19 +61,43 @@ abstract class DatabaseUpdater {
protected $shared = false;
+ /**
+ * Scripts to run after database update
+ * Should be a subclass of LoggedUpdateMaintenance
+ */
protected $postDatabaseUpdateMaintenance = array(
'DeleteDefaultMessages',
'PopulateRevisionLength',
'PopulateRevisionSha1',
'PopulateImageSha1',
'FixExtLinksProtocolRelative',
+ 'PopulateFilearchiveSha1',
);
/**
+ * File handle for SQL output.
+ *
+ * @var Filehandle
+ */
+ protected $fileHandle = null;
+
+ /**
+ * Flag specifying whether or not to skip schema (e.g. SQL-only) updates.
+ *
+ * @var bool
+ */
+ protected $skipSchema = false;
+
+ /**
+ * Hold the value of $wgContentHandlerUseDB during the upgrade.
+ */
+ protected $wgContentHandlerUseDB = true;
+
+ /**
* Constructor
*
* @param $db DatabaseBase object to perform updates on
- * @param $shared bool Whether to perform updates on shared tables
+ * @param bool $shared Whether to perform updates on shared tables
* @param $maintenance Maintenance Maintenance object which created us
*/
protected function __construct( DatabaseBase &$db, $shared, Maintenance $maintenance = null ) {
@@ -75,6 +106,7 @@ abstract class DatabaseUpdater {
$this->shared = $shared;
if ( $maintenance ) {
$this->maintenance = $maintenance;
+ $this->fileHandle = $maintenance->fileHandle;
} else {
$this->maintenance = new FakeMaintenance;
}
@@ -150,7 +182,7 @@ abstract class DatabaseUpdater {
/**
* Output some text. If we're running from web, escape the text first.
*
- * @param $str String: Text to output
+ * @param string $str Text to output
*/
public function output( $str ) {
if ( $this->maintenance->isQuiet() ) {
@@ -170,14 +202,14 @@ abstract class DatabaseUpdater {
*
* @since 1.17
*
- * @param $update Array: the update to run. Format is the following:
+ * @param array $update the update to run. Format is the following:
* first item is the callback function, it also can be a
* simple string with the name of a function in this class,
* following elements are parameters to the function.
* Note that callback functions will receive this object as
* first parameter.
*/
- public function addExtensionUpdate( Array $update ) {
+ public function addExtensionUpdate( array $update ) {
$this->extensionUpdates[] = $update;
}
@@ -187,8 +219,8 @@ abstract class DatabaseUpdater {
*
* @since 1.18
*
- * @param $tableName String Name of table to create
- * @param $sqlPath String Full path to the schema file
+ * @param string $tableName Name of table to create
+ * @param string $sqlPath Full path to the schema file
*/
public function addExtensionTable( $tableName, $sqlPath ) {
$this->extensionUpdates[] = array( 'addTable', $tableName, $sqlPath, true );
@@ -230,6 +262,19 @@ abstract class DatabaseUpdater {
}
/**
+ * Drop an index from an extension table
+ *
+ * @since 1.21
+ *
+ * @param string $tableName The table name
+ * @param string $indexName The index name
+ * @param string $sqlPath The path to the SQL change path
+ */
+ public function dropExtensionIndex( $tableName, $indexName, $sqlPath ) {
+ $this->extensionUpdates[] = array( 'dropIndex', $tableName, $indexName, $sqlPath, true );
+ }
+
+ /**
*
* @since 1.20
*
@@ -241,6 +286,32 @@ abstract class DatabaseUpdater {
}
/**
+ * Rename an index on an extension table
+ *
+ * @since 1.21
+ *
+ * @param string $tableName The table name
+ * @param string $oldIndexName The old index name
+ * @param string $newIndexName The new index name
+ * @param $skipBothIndexExistWarning Boolean: Whether to warn if both the old and the new indexes exist. [facultative; by default, false]
+ * @param string $sqlPath The path to the SQL change path
+ */
+ public function renameExtensionIndex( $tableName, $oldIndexName, $newIndexName, $sqlPath, $skipBothIndexExistWarning = false ) {
+ $this->extensionUpdates[] = array( 'renameIndex', $tableName, $oldIndexName, $newIndexName, $skipBothIndexExistWarning, $sqlPath, true );
+ }
+
+ /**
+ * @since 1.21
+ *
+ * @param string $tableName The table name
+ * @param string $fieldName The field to be modified
+ * @param string $sqlPath The path to the SQL change path
+ */
+ public function modifyExtensionField( $tableName, $fieldName, $sqlPath) {
+ $this->extensionUpdates[] = array( 'modifyField', $tableName, $fieldName, $sqlPath, true );
+ }
+
+ /**
*
* @since 1.20
*
@@ -254,9 +325,11 @@ abstract class DatabaseUpdater {
/**
* Add a maintenance script to be run after the database updates are complete.
*
+ * Script should subclass LoggedUpdateMaintenance
+ *
* @since 1.19
*
- * @param $class string Name of a Maintenance subclass
+ * @param string $class Name of a Maintenance subclass
*/
public function addPostDatabaseUpdateMaintenance( $class ) {
$this->postDatabaseUpdateMaintenance[] = $class;
@@ -281,15 +354,35 @@ abstract class DatabaseUpdater {
}
/**
+ * @since 1.21
+ *
+ * Writes the schema updates desired to a file for the DB Admin to run.
+ */
+ private function writeSchemaUpdateFile( $schemaUpdate = array() ) {
+ $updates = $this->updatesSkipped;
+ $this->updatesSkipped = array();
+
+ foreach( $updates as $funcList ) {
+ $func = $funcList[0];
+ $arg = $funcList[1];
+ $origParams = $funcList[2];
+ call_user_func_array( $func, $arg );
+ flush();
+ $this->updatesSkipped[] = $origParams;
+ }
+ }
+
+ /**
* Do all the updates
*
- * @param $what Array: what updates to perform
+ * @param array $what what updates to perform
*/
- public function doUpdates( $what = array( 'core', 'extensions', 'purge', 'stats' ) ) {
- global $wgLocalisationCacheConf, $wgVersion;
+ public function doUpdates( $what = array( 'core', 'extensions', 'stats' ) ) {
+ global $wgVersion, $wgLocalisationCacheConf;
$this->db->begin( __METHOD__ );
$what = array_flip( $what );
+ $this->skipSchema = isset( $what['noschema'] ) || $this->fileHandle !== null;
if ( isset( $what['core'] ) ) {
$this->runUpdates( $this->getCoreUpdateList(), false );
}
@@ -298,8 +391,6 @@ abstract class DatabaseUpdater {
$this->runUpdates( $this->getExtensionUpdates(), true );
}
- $this->setAppliedUpdates( $wgVersion, $this->updates );
-
if ( isset( $what['stats'] ) ) {
$this->checkStats();
}
@@ -311,28 +402,46 @@ abstract class DatabaseUpdater {
$this->rebuildLocalisationCache();
}
}
+
+ $this->setAppliedUpdates( $wgVersion, $this->updates );
+
+ if( $this->fileHandle ) {
+ $this->skipSchema = false;
+ $this->writeSchemaUpdateFile();
+ $this->setAppliedUpdates( "$wgVersion-schema", $this->updatesSkipped );
+ }
+
$this->db->commit( __METHOD__ );
}
/**
* Helper function for doUpdates()
*
- * @param $updates Array of updates to run
+ * @param array $updates of updates to run
* @param $passSelf Boolean: whether to pass this object we calling external
* functions
*/
private function runUpdates( array $updates, $passSelf ) {
+ $updatesDone = array();
+ $updatesSkipped = array();
foreach ( $updates as $params ) {
+ $origParams = $params;
$func = array_shift( $params );
if( !is_array( $func ) && method_exists( $this, $func ) ) {
$func = array( $this, $func );
} elseif ( $passSelf ) {
array_unshift( $params, $this );
}
- call_user_func_array( $func, $params );
+ $ret = call_user_func_array( $func, $params );
flush();
+ if( $ret !== false ) {
+ $updatesDone[] = $origParams;
+ } else {
+ $updatesSkipped[] = array( $func, $params, $origParams );
+ }
}
- $this->updates = array_merge( $this->updates, $updates );
+ $this->updatesSkipped = array_merge( $this->updatesSkipped, $updatesSkipped );
+ $this->updates = array_merge( $this->updates, $updatesDone );
}
/**
@@ -347,7 +456,7 @@ abstract class DatabaseUpdater {
$key = "updatelist-$version-" . time();
$this->db->insert( 'updatelog',
array( 'ul_key' => $key, 'ul_value' => serialize( $updates ) ),
- __METHOD__ );
+ __METHOD__ );
$this->db->setFlag( DBO_DDLMODE );
}
@@ -355,7 +464,7 @@ abstract class DatabaseUpdater {
* Helper function: check if the given key is present in the updatelog table.
* Obviously, only use this for updates that occur after the updatelog table was
* created!
- * @param $key String Name of the key to check for
+ * @param string $key Name of the key to check for
*
* @return bool
*/
@@ -373,8 +482,8 @@ abstract class DatabaseUpdater {
* Helper function: Add a key to the updatelog table
* Obviously, only use this for updates that occur after the updatelog table was
* created!
- * @param $key String Name of key to insert
- * @param $val String [optional] value to insert along with the key
+ * @param string $key Name of key to insert
+ * @param string $val [optional] value to insert along with the key
*/
public function insertUpdateRow( $key, $val = null ) {
$this->db->clearFlag( DBO_DDLMODE );
@@ -400,6 +509,26 @@ abstract class DatabaseUpdater {
}
/**
+ * Returns whether updates should be executed on the database table $name.
+ * Updates will be prevented if the table is a shared table and it is not
+ * specified to run updates on shared tables.
+ *
+ * @param string $name table name
+ * @return bool
+ */
+ protected function doTable( $name ) {
+ global $wgSharedDB, $wgSharedTables;
+
+ // Don't bother to check $wgSharedTables if there isn't a shared database
+ // or the user actually also wants to do updates on the shared database.
+ if ( $wgSharedDB === null || $this->shared ) {
+ return true;
+ }
+
+ return !in_array( $name, $wgSharedTables );
+ }
+
+ /**
* Before 1.17, we used to handle updates via stuff like
* $wgExtNewTables/Fields/Indexes. This is nasty :) We refactored a lot
* of this in 1.17 but we want to remain back-compatible for a while. So
@@ -409,11 +538,7 @@ abstract class DatabaseUpdater {
*/
protected function getOldGlobalUpdates() {
global $wgExtNewFields, $wgExtNewTables, $wgExtModifiedFields,
- $wgExtNewIndexes, $wgSharedDB, $wgSharedTables;
-
- $doUser = $this->shared ?
- $wgSharedDB && in_array( 'user', $wgSharedTables ) :
- !$wgSharedDB || !in_array( 'user', $wgSharedTables );
+ $wgExtNewIndexes;
$updates = array();
@@ -424,12 +549,10 @@ abstract class DatabaseUpdater {
}
foreach ( $wgExtNewFields as $fieldRecord ) {
- if ( $fieldRecord[0] != 'user' || $doUser ) {
- $updates[] = array(
- 'addField', $fieldRecord[0], $fieldRecord[1],
- $fieldRecord[2], true
- );
- }
+ $updates[] = array(
+ 'addField', $fieldRecord[0], $fieldRecord[1],
+ $fieldRecord[2], true
+ );
}
foreach ( $wgExtNewIndexes as $fieldRecord ) {
@@ -457,104 +580,221 @@ abstract class DatabaseUpdater {
*
* @return Array
*/
- protected abstract function getCoreUpdateList();
+ abstract protected function getCoreUpdateList();
+
+ /**
+ * Append an SQL fragment to the open file handle.
+ *
+ * @param string $filename File name to open
+ */
+ public function copyFile( $filename ) {
+ $this->db->sourceFile( $filename, false, false, false,
+ array( $this, 'appendLine' )
+ );
+ }
+
+ /**
+ * Append a line to the open filehandle. The line is assumed to
+ * be a complete SQL statement.
+ *
+ * This is used as a callback for for sourceLine().
+ *
+ * @param string $line text to append to the file
+ * @return Boolean false to skip actually executing the file
+ * @throws MWException
+ */
+ public function appendLine( $line ) {
+ $line = rtrim( $line ) . ";\n";
+ if( fwrite( $this->fileHandle, $line ) === false ) {
+ throw new MWException( "trouble writing file" );
+ }
+ return false;
+ }
/**
* Applies a SQL patch
- * @param $path String Path to the patch file
+ *
+ * @param string $path Path to the patch file
* @param $isFullPath Boolean Whether to treat $path as a relative or not
- * @param $msg String Description of the patch
+ * @param string $msg Description of the patch
+ * @return boolean false if patch is skipped.
*/
protected function applyPatch( $path, $isFullPath = false, $msg = null ) {
if ( $msg === null ) {
$msg = "Applying $path patch";
}
+ if ( $this->skipSchema ) {
+ $this->output( "...skipping schema change ($msg).\n" );
+ return false;
+ }
+
+ $this->output( "$msg ..." );
if ( !$isFullPath ) {
$path = $this->db->patchPath( $path );
}
-
- $this->output( "$msg ..." );
- $this->db->sourceFile( $path );
+ if( $this->fileHandle !== null ) {
+ $this->copyFile( $path );
+ } else {
+ $this->db->sourceFile( $path );
+ }
$this->output( "done.\n" );
+ return true;
}
/**
* Add a new table to the database
- * @param $name String Name of the new table
- * @param $patch String Path to the patch file
+ *
+ * @param string $name Name of the new table
+ * @param string $patch Path to the patch file
* @param $fullpath Boolean Whether to treat $patch path as a relative or not
+ * @return Boolean false if this was skipped because schema changes are skipped
*/
protected function addTable( $name, $patch, $fullpath = false ) {
+ if ( !$this->doTable( $name ) ) {
+ return true;
+ }
+
if ( $this->db->tableExists( $name, __METHOD__ ) ) {
$this->output( "...$name table already exists.\n" );
} else {
- $this->applyPatch( $patch, $fullpath, "Creating $name table" );
+ return $this->applyPatch( $patch, $fullpath, "Creating $name table" );
}
+ return true;
}
/**
* Add a new field to an existing table
- * @param $table String Name of the table to modify
- * @param $field String Name of the new field
- * @param $patch String Path to the patch file
+ *
+ * @param string $table Name of the table to modify
+ * @param string $field Name of the new field
+ * @param string $patch Path to the patch file
* @param $fullpath Boolean Whether to treat $patch path as a relative or not
+ * @return Boolean false if this was skipped because schema changes are skipped
*/
protected function addField( $table, $field, $patch, $fullpath = false ) {
+ if ( !$this->doTable( $table ) ) {
+ return true;
+ }
+
if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
$this->output( "...$table table does not exist, skipping new field patch.\n" );
} elseif ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
$this->output( "...have $field field in $table table.\n" );
} else {
- $this->applyPatch( $patch, $fullpath, "Adding $field field to table $table" );
+ return $this->applyPatch( $patch, $fullpath, "Adding $field field to table $table" );
}
+ return true;
}
/**
* Add a new index to an existing table
- * @param $table String Name of the table to modify
- * @param $index String Name of the new index
- * @param $patch String Path to the patch file
+ *
+ * @param string $table Name of the table to modify
+ * @param string $index Name of the new index
+ * @param string $patch Path to the patch file
* @param $fullpath Boolean Whether to treat $patch path as a relative or not
+ * @return Boolean false if this was skipped because schema changes are skipped
*/
protected function addIndex( $table, $index, $patch, $fullpath = false ) {
- if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
+ if ( !$this->doTable( $table ) ) {
+ return true;
+ }
+
+ if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
+ $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
+ } else if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
$this->output( "...index $index already set on $table table.\n" );
} else {
- $this->applyPatch( $patch, $fullpath, "Adding index $index to table $table" );
+ return $this->applyPatch( $patch, $fullpath, "Adding index $index to table $table" );
}
+ return true;
}
/**
* Drop a field from an existing table
*
- * @param $table String Name of the table to modify
- * @param $field String Name of the old field
- * @param $patch String Path to the patch file
+ * @param string $table Name of the table to modify
+ * @param string $field Name of the old field
+ * @param string $patch Path to the patch file
* @param $fullpath Boolean Whether to treat $patch path as a relative or not
+ * @return Boolean false if this was skipped because schema changes are skipped
*/
protected function dropField( $table, $field, $patch, $fullpath = false ) {
+ if ( !$this->doTable( $table ) ) {
+ return true;
+ }
+
if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
- $this->applyPatch( $patch, $fullpath, "Table $table contains $field field. Dropping" );
+ return $this->applyPatch( $patch, $fullpath, "Table $table contains $field field. Dropping" );
} else {
$this->output( "...$table table does not contain $field field.\n" );
}
+ return true;
}
/**
* Drop an index from an existing table
*
- * @param $table String: Name of the table to modify
- * @param $index String: Name of the old index
- * @param $patch String: Path to the patch file
+ * @param string $table Name of the table to modify
+ * @param string $index Name of the index
+ * @param string $patch Path to the patch file
* @param $fullpath Boolean: Whether to treat $patch path as a relative or not
+ * @return Boolean false if this was skipped because schema changes are skipped
*/
protected function dropIndex( $table, $index, $patch, $fullpath = false ) {
+ if ( !$this->doTable( $table ) ) {
+ return true;
+ }
+
if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
- $this->applyPatch( $patch, $fullpath, "Dropping $index index from table $table" );
+ return $this->applyPatch( $patch, $fullpath, "Dropping $index index from table $table" );
} else {
$this->output( "...$index key doesn't exist.\n" );
}
+ return true;
+ }
+
+ /**
+ * Rename an index from an existing table
+ *
+ * @param string $table Name of the table to modify
+ * @param string $oldIndex Old name of the index
+ * @param string $newIndex New name of the index
+ * @param $skipBothIndexExistWarning Boolean: Whether to warn if both the old and the new indexes exist.
+ * @param string $patch Path to the patch file
+ * @param $fullpath Boolean: Whether to treat $patch path as a relative or not
+ * @return Boolean false if this was skipped because schema changes are skipped
+ */
+ protected function renameIndex( $table, $oldIndex, $newIndex, $skipBothIndexExistWarning, $patch, $fullpath = false ) {
+ if ( !$this->doTable( $table ) ) {
+ return true;
+ }
+
+ // First requirement: the table must exist
+ if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
+ $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
+ return true;
+ }
+
+ // Second requirement: the new index must be missing
+ if ( $this->db->indexExists( $table, $newIndex, __METHOD__ ) ) {
+ $this->output( "...index $newIndex already set on $table table.\n" );
+ if ( !$skipBothIndexExistWarning && $this->db->indexExists( $table, $oldIndex, __METHOD__ ) ) {
+ $this->output( "...WARNING: $oldIndex still exists, despite it has been renamed into $newIndex (which also exists).\n" .
+ " $oldIndex should be manually removed if not needed anymore.\n" );
+ }
+ return true;
+ }
+
+ // Third requirement: the old index must exist
+ if ( !$this->db->indexExists( $table, $oldIndex, __METHOD__ ) ) {
+ $this->output( "...skipping: index $oldIndex doesn't exist.\n" );
+ return true;
+ }
+
+ // Requirements have been satisfied, patch can be applied
+ return $this->applyPatch( $patch, $fullpath, "Renaming index $oldIndex into $newIndex to table $table" );
}
/**
@@ -566,8 +806,13 @@ abstract class DatabaseUpdater {
* @param $table string
* @param $patch string|false
* @param $fullpath bool
+ * @return Boolean false if this was skipped because schema changes are skipped
*/
public function dropTable( $table, $patch = false, $fullpath = false ) {
+ if ( !$this->doTable( $table ) ) {
+ return true;
+ }
+
if ( $this->db->tableExists( $table, __METHOD__ ) ) {
$msg = "Dropping table $table";
@@ -577,23 +822,28 @@ abstract class DatabaseUpdater {
$this->output( "done.\n" );
}
else {
- $this->applyPatch( $patch, $fullpath, $msg );
+ return $this->applyPatch( $patch, $fullpath, $msg );
}
-
} else {
$this->output( "...$table doesn't exist.\n" );
}
+ return true;
}
/**
* Modify an existing field
*
- * @param $table String: name of the table to which the field belongs
- * @param $field String: name of the field to modify
- * @param $patch String: path to the patch file
+ * @param string $table name of the table to which the field belongs
+ * @param string $field name of the field to modify
+ * @param string $patch path to the patch file
* @param $fullpath Boolean: whether to treat $patch path as a relative or not
+ * @return Boolean false if this was skipped because schema changes are skipped
*/
public function modifyField( $table, $field, $patch, $fullpath = false ) {
+ if ( !$this->doTable( $table ) ) {
+ return true;
+ }
+
$updateKey = "$table-$field-$patch";
if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
$this->output( "...$table table does not exist, skipping modify field patch.\n" );
@@ -602,19 +852,25 @@ abstract class DatabaseUpdater {
} elseif( $this->updateRowExists( $updateKey ) ) {
$this->output( "...$field in table $table already modified by patch $patch.\n" );
} else {
- $this->applyPatch( $patch, $fullpath, "Modifying $field field of table $table" );
$this->insertUpdateRow( $updateKey );
+ return $this->applyPatch( $patch, $fullpath, "Modifying $field field of table $table" );
}
+ return true;
}
/**
* Purge the objectcache table
*/
- protected function purgeCache() {
+ public function purgeCache() {
+ global $wgLocalisationCacheConf;
# We can't guarantee that the user will be able to use TRUNCATE,
# but we know that DELETE is available to us
$this->output( "Purging caches..." );
$this->db->delete( 'objectcache', '*', __METHOD__ );
+ if ( $wgLocalisationCacheConf['manualRecache'] ) {
+ $this->rebuildLocalisationCache();
+ }
+ MessageBlobStore::clear();
$this->output( "done.\n" );
}
@@ -693,10 +949,11 @@ abstract class DatabaseUpdater {
protected function doUpdateTranscacheField() {
if ( $this->updateRowExists( 'convert transcache field' ) ) {
$this->output( "...transcache tc_time already converted.\n" );
- return;
+ return true;
}
- $this->applyPatch( 'patch-tc-timestamp.sql', false, "Converting tc_time from UNIX epoch to MediaWiki timestamp" );
+ return $this->applyPatch( 'patch-tc-timestamp.sql', false,
+ "Converting tc_time from UNIX epoch to MediaWiki timestamp" );
}
/**
@@ -704,29 +961,33 @@ abstract class DatabaseUpdater {
*/
protected function doCollationUpdate() {
global $wgCategoryCollation;
- if ( $this->db->selectField(
- 'categorylinks',
- 'COUNT(*)',
- 'cl_collation != ' . $this->db->addQuotes( $wgCategoryCollation ),
- __METHOD__
- ) == 0 ) {
- $this->output( "...collations up-to-date.\n" );
- return;
- }
+ if ( $this->db->fieldExists( 'categorylinks', 'cl_collation', __METHOD__ ) ) {
+ if ( $this->db->selectField(
+ 'categorylinks',
+ 'COUNT(*)',
+ 'cl_collation != ' . $this->db->addQuotes( $wgCategoryCollation ),
+ __METHOD__
+ ) == 0 ) {
+ $this->output( "...collations up-to-date.\n" );
+ return;
+ }
- $this->output( "Updating category collations..." );
- $task = $this->maintenance->runChild( 'UpdateCollation' );
- $task->execute();
- $this->output( "...done.\n" );
+ $this->output( "Updating category collations..." );
+ $task = $this->maintenance->runChild( 'UpdateCollation' );
+ $task->execute();
+ $this->output( "...done.\n" );
+ }
}
/**
* Migrates user options from the user table blob to user_properties
*/
protected function doMigrateUserOptions() {
- $cl = $this->maintenance->runChild( 'ConvertUserOptions', 'convertUserOptions.php' );
- $cl->execute();
- $this->output( "done.\n" );
+ if( $this->db->tableExists( 'user_properties' ) ) {
+ $cl = $this->maintenance->runChild( 'ConvertUserOptions', 'convertUserOptions.php' );
+ $cl->execute();
+ $this->output( "done.\n" );
+ }
}
/**
@@ -742,4 +1003,30 @@ abstract class DatabaseUpdater {
$cl->execute();
$this->output( "done.\n" );
}
+
+ /**
+ * Turns off content handler fields during parts of the upgrade
+ * where they aren't available.
+ */
+ protected function disableContentHandlerUseDB() {
+ global $wgContentHandlerUseDB;
+
+ if( $wgContentHandlerUseDB ) {
+ $this->output( "Turning off Content Handler DB fields for this part of upgrade.\n" );
+ $this->holdContentHandlerUseDB = $wgContentHandlerUseDB;
+ $wgContentHandlerUseDB = false;
+ }
+ }
+
+ /**
+ * Turns content handler fields back on.
+ */
+ protected function enableContentHandlerUseDB() {
+ global $wgContentHandlerUseDB;
+
+ if( $this->holdContentHandlerUseDB ) {
+ $this->output( "Content Handler DB fields should be usable now.\n" );
+ $wgContentHandlerUseDB = $this->holdContentHandlerUseDB;
+ }
+ }
}
diff --git a/includes/installer/Ibm_db2Installer.php b/includes/installer/Ibm_db2Installer.php
deleted file mode 100644
index ca9bdf4b..00000000
--- a/includes/installer/Ibm_db2Installer.php
+++ /dev/null
@@ -1,270 +0,0 @@
-<?php
-/**
- * IBM_DB2-specific installer.
- *
- * 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 Deployment
- */
-
-/**
- * Class for setting up the MediaWiki database using IBM_DB2.
- *
- * @ingroup Deployment
- * @since 1.17
- */
-class Ibm_db2Installer extends DatabaseInstaller {
-
-
- protected $globalNames = array(
- 'wgDBserver',
- 'wgDBport',
- 'wgDBname',
- 'wgDBuser',
- 'wgDBpassword',
- 'wgDBmwschema',
- );
-
- protected $internalDefaults = array(
- '_InstallUser' => 'db2admin'
- );
-
- /**
- * Get the DB2 database extension name
- * @return string
- */
- public function getName(){
- return 'ibm_db2';
- }
-
- /**
- * Determine whether the DB2 database extension is currently available in PHP
- * @return boolean
- */
- public function isCompiled() {
- return self::checkExtension( 'ibm_db2' );
- }
-
- /**
- * Generate a connection form for a DB2 database
- * @return string
- */
- public function getConnectForm() {
- return
- $this->getTextBox( 'wgDBserver', 'config-db-host', array(), $this->parent->getHelpBox( 'config-db-host-help' ) ) .
- $this->getTextBox( 'wgDBport', 'config-db-port', array(), $this->parent->getHelpBox( 'config-db-port' ) ) .
- Html::openElement( 'fieldset' ) .
- Html::element( 'legend', array(), wfMessage( 'config-db-wiki-settings' )->text() ) .
- $this->getTextBox( 'wgDBname', 'config-db-name', array(), $this->parent->getHelpBox( 'config-db-name-help' ) ) .
- $this->getTextBox( 'wgDBmwschema', 'config-db-schema', array(), $this->parent->getHelpBox( 'config-db-schema-help' ) ) .
- Html::closeElement( 'fieldset' ) .
- $this->getInstallUserBox();
- }
-
- /**
- * Validate and then execute the connection form for a DB2 database
- * @return Status
- */
- public function submitConnectForm() {
- // Get variables from the request
- $newValues = $this->setVarsFromRequest(
- array( 'wgDBserver', 'wgDBport', 'wgDBname',
- 'wgDBmwschema', 'wgDBuser', 'wgDBpassword' ) );
-
- // Validate them
- $status = Status::newGood();
- if ( !strlen( $newValues['wgDBname'] ) ) {
- $status->fatal( 'config-missing-db-name' );
- } elseif ( !preg_match( '/^[a-zA-Z0-9_]+$/', $newValues['wgDBname'] ) ) {
- $status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] );
- }
- if ( !strlen( $newValues['wgDBmwschema'] ) ) {
- $status->fatal( 'config-invalid-schema' );
- }
- elseif ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBmwschema'] ) ) {
- $status->fatal( 'config-invalid-schema', $newValues['wgDBmwschema'] );
- }
- if ( !strlen( $newValues['wgDBport'] ) ) {
- $status->fatal( 'config-invalid-port' );
- }
- elseif ( !preg_match( '/^[0-9_]*$/', $newValues['wgDBport'] ) ) {
- $status->fatal( 'config-invalid-port', $newValues['wgDBport'] );
- }
-
- // Submit user box
- if ( $status->isOK() ) {
- $status->merge( $this->submitInstallUserBox() );
- }
- if ( !$status->isOK() ) {
- return $status;
- }
-
- global $wgDBport;
- $wgDBport = $newValues['wgDBport'];
-
- // Try to connect
- $status->merge( $this->getConnection() );
- if ( !$status->isOK() ) {
- return $status;
- }
-
- $this->parent->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
- $this->parent->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
-
- return $status;
- }
-
- /**
- * Open a DB2 database connection
- * @return Status
- */
- public function openConnection() {
- $status = Status::newGood();
- try {
- $db = new DatabaseIbm_db2(
- $this->getVar( 'wgDBserver' ),
- $this->getVar( '_InstallUser' ),
- $this->getVar( '_InstallPassword' ),
- $this->getVar( 'wgDBname' ),
- 0,
- $this->getVar( 'wgDBmwschema' )
- );
- $status->value = $db;
- } catch ( DBConnectionError $e ) {
- $status->fatal( 'config-connection-error', $e->getMessage() );
- }
- return $status;
- }
-
- /**
- * Create a DB2 database for MediaWiki
- * @return Status
- */
- public function setupDatabase() {
- $status = $this->getConnection();
- if ( !$status->isOK() ) {
- return $status;
- }
- /**
- * @var $conn DatabaseBase
- */
- $conn = $status->value;
- $dbName = $this->getVar( 'wgDBname' );
- if( !$conn->selectDB( $dbName ) ) {
- $conn->query( "CREATE DATABASE "
- . $conn->addIdentifierQuotes( $dbName )
- . " AUTOMATIC STORAGE YES"
- . " USING CODESET UTF-8 TERRITORY US COLLATE USING SYSTEM"
- . " PAGESIZE 32768", __METHOD__ );
- $conn->selectDB( $dbName );
- }
- $this->setupSchemaVars();
- return $status;
- }
-
- /**
- * Create tables from scratch.
- * First check if pagesize >= 32k.
- *
- * @return Status
- */
- public function createTables() {
- $status = $this->getConnection();
- if ( !$status->isOK() ) {
- return $status;
- }
- $this->db->selectDB( $this->getVar( 'wgDBname' ) );
-
- if( $this->db->tableExists( 'user' ) ) {
- $status->warning( 'config-install-tables-exist' );
- return $status;
- }
-
- /* Check for pagesize */
- $status = $this->checkPageSize();
- if ( !$status->isOK() ) {
- return $status;
- }
-
- $this->db->setFlag( DBO_DDLMODE ); // For Oracle's handling of schema files
- $this->db->begin( __METHOD__ );
-
- $error = $this->db->sourceFile( $this->db->getSchemaPath() );
- if( $error !== true ) {
- $this->db->reportQueryError( $error, 0, '', __METHOD__ );
- $this->db->rollback( __METHOD__ );
- $status->fatal( 'config-install-tables-failed', $error );
- } else {
- $this->db->commit( __METHOD__ );
- }
- // Resume normal operations
- if( $status->isOk() ) {
- $this->enableLB();
- }
- return $status;
- }
-
- /**
- * Check if database has a tablspace with pagesize >= 32k.
- *
- * @return Status
- */
- public function checkPageSize() {
- $status = $this->getConnection();
- if ( !$status->isOK() ) {
- return $status;
- }
- $this->db->selectDB( $this->getVar( 'wgDBname' ) );
-
- try {
- $result = $this->db->query( 'SELECT PAGESIZE FROM SYSCAT.TABLESPACES FOR READ ONLY' );
- if( $result == false ) {
- $status->fatal( 'config-connection-error', '' );
- } else {
- $row = $this->db->fetchRow( $result );
- while ( $row ) {
- if( $row[0] >= 32768 ) {
- return $status;
- }
- $row = $this->db->fetchRow( $result );
- }
- $status->fatal( 'config-ibm_db2-low-db-pagesize', '' );
- }
- } catch ( DBUnexpectedError $e ) {
- $status->fatal( 'config-connection-error', $e->getMessage() );
- }
-
- return $status;
- }
-
- /**
- * Generate the code to store the DB2-specific settings defined by the configuration form
- * @return string
- */
- public function getLocalSettings() {
- $schema = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgDBmwschema' ) );
- $port = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgDBport' ) );
- return
-"# IBM_DB2 specific settings
-\$wgDBmwschema = \"{$schema}\";
-\$wgDBport = \"{$port}\";";
- }
-
- public function __construct( $parent ) {
- parent::__construct( $parent );
- }
-}
diff --git a/includes/installer/Ibm_db2Updater.php b/includes/installer/Ibm_db2Updater.php
deleted file mode 100644
index 9daba9c2..00000000
--- a/includes/installer/Ibm_db2Updater.php
+++ /dev/null
@@ -1,91 +0,0 @@
-<?php
-/**
- * IBM_DB2-specific updater.
- *
- * 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 Deployment
- */
-
-/**
- * Class for handling updates to IBM_DB2 databases.
- *
- * @ingroup Deployment
- * @since 1.17
- */
-class Ibm_db2Updater extends DatabaseUpdater {
-
- /**
- * Get the changes in the DB2 database scheme since MediaWiki 1.14
- * @return array
- */
- protected function getCoreUpdateList() {
- return array(
- // 1.14
- array( 'addField', 'site_stats', 'ss_active_users', 'patch-ss_active_users.sql' ),
- array( 'addField', 'ipblocks', 'ipb_allow_usertalk', 'patch-ipb_allow_usertalk.sql' ),
-
- // 1.15
- array( 'addTable', 'change_tag', 'patch-change_tag.sql' ),
- array( 'addTable', 'tag_summary', 'patch-change_tag_summary.sql' ),
- array( 'addTable', 'valid_tag', 'patch-change_valid_tag.sql' ),
-
- // 1.16
- array( 'addTable', 'user_properties', 'patch-user_properties.sql' ),
- array( 'addTable', 'log_search', 'patch-log_search.sql' ),
- array( 'addField', 'logging', 'log_user_text', 'patch-log_user_text.sql' ),
- array( 'addTable', 'l10n_cache', 'patch-l10n_cache.sql' ),
- array( 'addTable', 'external_user', 'patch-external_user.sql' ),
- array( 'addIndex', 'log_search', 'ls_field_val', 'patch-log_search-rename-index.sql' ),
- array( 'addIndex', 'change_tag', 'change_tag_rc_tag', 'patch-change_tag-indexes.sql' ),
- array( 'addField', 'redirect', 'rd_interwiki', 'patch-rd_interwiki.sql' ),
-
- // 1.17
- array( 'addTable', 'iwlinks', 'patch-iwlinks.sql' ),
- array( 'addField', 'updatelog', 'ul_value', 'patch-ul_value.sql' ),
- array( 'addField', 'interwiki', 'iw_api', 'patch-iw_api_and_wikiid.sql' ),
- array( 'addField', 'categorylinks', 'cl_collation', 'patch-categorylinks-better-collation.sql' ),
- array( 'addTable', 'msg_resource', 'patch-msg_resource.sql' ),
- array( 'addTable', 'msg_resource_links', 'patch-msg_resource_links.sql' ),
- array( 'addIndex', 'msg_resource_links', 'uq61_msg_resource_links', 'patch-uq_61_msg_resource_links.sql' ),
- array( 'addIndex', 'msg_resource', 'uq81_msg_resource', 'patch-uq_81_msg_resource.sql' ),
- array( 'addTable', 'module_deps', 'patch-module_deps.sql' ),
- array( 'addIndex', 'module_deps', 'uq96_module_deps', 'patch-uq_96_module_deps.sql' ),
- array( 'addField', 'interwiki', 'iw_api', 'patch-iw_api-field.sql' ),
- array( 'addField', 'interwiki', 'iw_wikiid', 'patch-iw_wikiid-field.sql' ),
- array( 'addField', 'categorylinks', 'cl_sortkey_prefix', 'patch-cl_sortkey_prefix-field.sql' ),
- array( 'addField', 'categorylinks', 'cl_collation', 'patch-cl_collation-field.sql' ),
- array( 'addField', 'categorylinks', 'cl_type', 'patch-cl_type-field.sql' ),
-
- //1.18
- array( 'doUserNewTalkTimestampNotNull' ),
- array( 'addIndex', 'user', 'user_email', 'patch-user_email_index.sql' ),
- array( 'modifyField', 'user_properties', 'up_property', 'patch-up_property.sql' ),
- array( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ),
- array( 'addTable', 'user_former_groups', 'patch-user_former_groups.sql'),
- array( 'doRebuildLocalisationCache' ),
-
- // 1.19
- array( 'addIndex', 'logging', 'type_action', 'patch-logging-type-action-index.sql'),
- array( 'dropField', 'user', 'user_options', 'patch-drop-user_options.sql' ),
- array( 'addField', 'revision', 'rev_sha1', 'patch-rev_sha1.sql' ),
- array( 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1.sql' ),
-
- // 1.20
- );
- }
-}
diff --git a/includes/installer/InstallDocFormatter.php b/includes/installer/InstallDocFormatter.php
index 9a389dd8..a508e24c 100644
--- a/includes/installer/InstallDocFormatter.php
+++ b/includes/installer/InstallDocFormatter.php
@@ -1,6 +1,6 @@
<?php
/**
- * Installer-specific wikitext formating.
+ * Installer-specific wikitext formatting.
*
* 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
@@ -44,9 +44,9 @@ class InstallDocFormatter {
$text = preg_replace( '/^\t\t/m', '::', $text );
$text = preg_replace( '/^\t/m', ':', $text );
// turn (bug nnnn) into links
- $text = preg_replace_callback('/bug (\d+)/', array( $this, 'replaceBugLinks' ), $text );
+ $text = preg_replace_callback( '/bug (\d+)/', array( $this, 'replaceBugLinks' ), $text );
// add links to manual to every global variable mentioned
- $text = preg_replace_callback('/(\$wg[a-z0-9_]+)/i', array( $this, 'replaceConfigLinks' ), $text );
+ $text = preg_replace_callback( '/(\$wg[a-z0-9_]+)/i', array( $this, 'replaceConfigLinks' ), $text );
return $text;
}
diff --git a/includes/installer/Installer.i18n.php b/includes/installer/Installer.i18n.php
index 4f1c4d0c..85b877a8 100644
--- a/includes/installer/Installer.i18n.php
+++ b/includes/installer/Installer.i18n.php
@@ -17,19 +17,19 @@ $messages['en'] = array(
'config-information' => 'Information',
'config-localsettings-upgrade' => "A <code>LocalSettings.php</code> file has been detected.
To upgrade this installation, please enter the value of <code>\$wgUpgradeKey</code> in the box below.
-You will find it in LocalSettings.php.",
- 'config-localsettings-cli-upgrade' => 'A LocalSettings.php file has been detected.
-To upgrade this installation, please run update.php instead',
+You will find it in <code>LocalSettings.php</code>.",
+ 'config-localsettings-cli-upgrade' => 'A <code>LocalSettings.php</code> file has been detected.
+To upgrade this installation, please run <code>update.php</code> instead',
'config-localsettings-key' => 'Upgrade key:',
'config-localsettings-badkey' => 'The key you provided is incorrect.',
'config-upgrade-key-missing' => 'An existing installation of MediaWiki has been detected.
-To upgrade this installation, please put the following line at the bottom of your LocalSettings.php:
+To upgrade this installation, please put the following line at the bottom of your <code>LocalSettings.php</code>:
$1',
- 'config-localsettings-incomplete' => 'The existing LocalSettings.php appears to be incomplete.
+ 'config-localsettings-incomplete' => 'The existing <code>LocalSettings.php</code> appears to be incomplete.
The $1 variable is not set.
-Please change LocalSettings.php so that this variable is set, and click "Continue".',
- 'config-localsettings-connection-error' => 'An error was encountered when connecting to the database using the settings specified in LocalSettings.php or AdminSettings.php. Please fix these settings and try again.
+Please change <code>LocalSettings.php</code> so that this variable is set, and click "{{int:Config-continue}}".',
+ 'config-localsettings-connection-error' => 'An error was encountered when connecting to the database using the settings specified in <code>LocalSettings.php</code> or <code>AdminSettings.php</code>. Please fix these settings and try again.
$1',
'config-session-error' => 'Error starting session: $1',
@@ -93,9 +93,9 @@ You cannot install MediaWiki.',
However, MediaWiki requires PHP $2 or higher.',
'config-unicode-using-utf8' => 'Using Brion Vibber\'s utf8_normalize.so for Unicode normalization.',
'config-unicode-using-intl' => 'Using the [http://pecl.php.net/intl intl PECL extension] for Unicode normalization.',
- 'config-unicode-pure-php-warning' => "'''Warning''': The [http://pecl.php.net/intl intl PECL extension] is not available to handle Unicode normalization, falling back to slow pure-PHP implementation.
+ 'config-unicode-pure-php-warning' => "'''Warning:''' The [http://pecl.php.net/intl intl PECL extension] is not available to handle Unicode normalization, falling back to slow pure-PHP implementation.
If you run a high-traffic site, you should read a little on [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode normalization].",
- 'config-unicode-update-warning' => "'''Warning''': The installed version of the Unicode normalization wrapper uses an older version of [http://site.icu-project.org/ the ICU project's] library.
+ 'config-unicode-update-warning' => "'''Warning:''' The installed version of the Unicode normalization wrapper uses an older version of [http://site.icu-project.org/ the ICU project's] library.
You should [//www.mediawiki.org/wiki/Unicode_normalization_considerations upgrade] if you are at all concerned about using Unicode.",
'config-no-db' => 'Could not find a suitable database driver! You need to install a database driver for PHP.
The following database types are supported: $1.
@@ -103,8 +103,8 @@ The following database types are supported: $1.
If you are on shared hosting, ask your hosting provider to install a suitable database driver.
If you compiled PHP yourself, reconfigure it with a database client enabled, for example using <code>./configure --with-mysql</code>.
If you installed PHP from a Debian or Ubuntu package, then you also need install the php5-mysql module.',
- 'config-outdated-sqlite' => "'''Warning''': you have SQLite $1, which is lower than minimum required version $2. SQLite will be unavailable.",
- 'config-no-fts3' => "'''Warning''': SQLite is compiled without the [//sqlite.org/fts3.html FTS3 module], search features will be unavailable on this backend.",
+ 'config-outdated-sqlite' => "'''Warning:''' you have SQLite $1, which is lower than minimum required version $2. SQLite will be unavailable.",
+ 'config-no-fts3' => "'''Warning:''' SQLite is compiled without the [//sqlite.org/fts3.html FTS3 module], search features will be unavailable on this backend.",
'config-register-globals' => "'''Warning: PHP's <code>[http://php.net/register_globals register_globals]</code> option is enabled.'''
'''Disable it if you can.'''
MediaWiki will work, but your server is exposed to potential security vulnerabilities.",
@@ -127,19 +127,19 @@ MediaWiki requires functions in this module and will not work in this configurat
If you're running Mandrake, install the php-xml package.",
'config-pcre' => 'The PCRE support module appears to be missing.
MediaWiki requires the Perl-compatible regular expression functions to work.',
- 'config-pcre-no-utf8' => "'''Fatal''': PHP's PCRE module seems to be compiled without PCRE_UTF8 support.
+ 'config-pcre-no-utf8' => "'''Fatal:''' PHP's PCRE module seems to be compiled without PCRE_UTF8 support.
MediaWiki requires UTF-8 support to function correctly.",
'config-memory-raised' => "PHP's <code>memory_limit</code> is $1, raised to $2.",
'config-memory-bad' => "'''Warning:''' PHP's <code>memory_limit</code> is $1.
This is probably too low.
The installation may fail!",
- 'config-ctype' => "'''Fatal''': PHP must be compiled with support for the [http://www.php.net/manual/en/ctype.installation.php Ctype extension].",
+ 'config-ctype' => "'''Fatal:''' PHP must be compiled with support for the [http://www.php.net/manual/en/ctype.installation.php Ctype extension].",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache] is installed',
'config-apc' => '[http://www.php.net/apc APC] is installed',
'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] is installed',
'config-no-cache' => "'''Warning:''' Could not find [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] or [http://www.iis.net/download/WinCacheForPhp WinCache].
Object caching is not enabled.",
- 'config-mod-security' => "'''Warning''': Your web server has [http://modsecurity.org/ mod_security] enabled. If misconfigured, it can cause problems for MediaWiki or other software that allows users to post arbitrary content.
+ 'config-mod-security' => "'''Warning:''' Your web server has [http://modsecurity.org/ mod_security] enabled. If misconfigured, it can cause problems for MediaWiki or other software that allows users to post arbitrary content.
Refer to [http://modsecurity.org/documentation/ mod_security documentation] or contact your host's support if you encounter random errors.",
'config-diff3-bad' => 'GNU diff3 not found.',
'config-imagemagick' => 'Found ImageMagick: <code>$1</code>.
@@ -150,7 +150,7 @@ Image thumbnailing will be enabled if you enable uploads.',
Image thumbnailing will be disabled.',
'config-no-uri' => "'''Error:''' Could not determine the current URI.
Installation aborted.",
- 'config-no-cli-uri' => "'''Warning''': No --scriptpath specified, using default: <code>$1</code>.",
+ 'config-no-cli-uri' => "'''Warning:''' No --scriptpath specified, using default: <code>$1</code>.",
'config-using-server' => 'Using server name "<nowiki>$1</nowiki>".',
'config-using-uri' => 'Using server URL "<nowiki>$1$2</nowiki>".',
'config-uploads-not-safe' => "'''Warning:''' Your default directory for uploads <code>$1</code> is vulnerable to arbitrary scripts execution.
@@ -163,7 +163,9 @@ Installation aborted.',
'config-using531' => 'MediaWiki cannot be used with PHP $1 due to a bug involving reference parameters to <code>__call()</code>.
Upgrade to PHP 5.3.2 or higher, or downgrade to PHP 5.3.0 to resolve this.
Installation aborted.',
- 'config-suhosin-max-value-length' => "Suhosin is installed and limits the GET parameter length to $1 bytes. MediaWiki's ResourceLoader component will work around this limit, but that will degrade performance. If at all possible, you should set suhosin.get.max_value_length to 1024 or higher in php.ini , and set \$wgResourceLoaderMaxQueryLength to the same value in LocalSettings.php .",
+ 'config-suhosin-max-value-length' => "Suhosin is installed and limits the GET parameter <code>length</code> to $1 bytes.
+MediaWiki's ResourceLoader component will work around this limit, but that will degrade performance.
+If at all possible, you should set <code>suhosin.get.max_value_length</code> to 1024 or higher in <code>php.ini</code>, and set <code>\$wgResourceLoaderMaxQueryLength</code> to the same value in <code>LocalSettings.php</code>.",
'config-db-type' => 'Database type:',
'config-db-host' => 'Database host:',
'config-db-host-help' => 'If your database server is on different server, enter the host name or IP address here.
@@ -230,7 +232,7 @@ The directory you provide must be writable by the webserver during installation.
It should '''not''' be accessible via the web, this is why we're not putting it where your PHP files are.
The installer will write a <code>.htaccess</code> file along with it, but if that fails someone can gain access to your raw database.
-That includes raw user data (e-mail addresses, hashed passwords) as well as deleted revisions and other restricted data on the wiki.
+That includes raw user data (email addresses, hashed passwords) as well as deleted revisions and other restricted data on the wiki.
Consider putting the database somewhere else altogether, for example in <code>/var/lib/mediawiki/yourwiki</code>.",
'config-oracle-def-ts' => 'Default tablespace:',
@@ -239,7 +241,6 @@ Consider putting the database somewhere else altogether, for example in <code>/v
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'MediaWiki supports the following database systems:
$1
@@ -249,12 +250,10 @@ If you do not see the database system you are trying to use listed below, then f
'config-support-postgres' => '* $1 is a popular open source database system as an alternative to MySQL ([http://www.php.net/manual/en/pgsql.installation.php how to compile PHP with PostgreSQL support]). There may be some minor outstanding bugs, and it is not recommended for use in a production environment.',
'config-support-sqlite' => '* $1 is a lightweight database system which is very well supported. ([http://www.php.net/manual/en/pdo.installation.php How to compile PHP with SQLite support], uses PDO)',
'config-support-oracle' => '* $1 is a commercial enterprise database. ([http://www.php.net/manual/en/oci8.installation.php How to compile PHP with OCI8 support])',
- 'config-support-ibm_db2' => '* $1 is a commercial enterprise database.',
'config-header-mysql' => 'MySQL settings',
'config-header-postgres' => 'PostgreSQL settings',
'config-header-sqlite' => 'SQLite settings',
'config-header-oracle' => 'Oracle settings',
- 'config-header-ibm_db2' => 'IBM DB2 settings',
'config-invalid-db-type' => 'Invalid database type',
'config-missing-db-name' => 'You must enter a value for "Database name"',
'config-missing-db-host' => 'You must enter a value for "Database host"',
@@ -316,9 +315,9 @@ This is '''not recommended''' unless you are having problems with your wiki.",
'config-upgrade-done-no-regenerate' => "Upgrade complete.
You can now [$1 start using your wiki].",
- 'config-regenerate' => 'Regenerate LocalSettings.php →',
- 'config-show-table-status' => 'SHOW TABLE STATUS query failed!',
- 'config-unknown-collation' => "'''Warning:''' Database is using unrecognised collation.",
+ 'config-regenerate' => 'Regenerate <code>LocalSettings.php</code> →',
+ 'config-show-table-status' => '<code>SHOW TABLE STATUS</code> query failed!',
+ 'config-unknown-collation' => "'''Warning:''' Database is using unrecognized collation.",
'config-db-web-account' => 'Database account for web access',
'config-db-web-help' => 'Select the username and password that the web server will use to connect to the database server, during ordinary operation of the wiki.',
'config-db-web-account-same' => 'Use the same account as for installation',
@@ -328,7 +327,7 @@ The account you specify here must already exist.',
'config-mysql-engine' => 'Storage engine:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''Warning''': You have selected MyISAM as storage engine for MySQL, which is not recommended for use with MediaWiki, because:
+ 'config-mysql-myisam-dep' => "'''Warning:''' You have selected MyISAM as storage engine for MySQL, which is not recommended for use with MediaWiki, because:
* it barely supports concurrency due to table locking
* it is more prone to corruption than other engines
* the MediaWiki codebase does not always handle MyISAM as it should
@@ -347,8 +346,6 @@ This is more efficient than MySQL's UTF-8 mode, and allows you to use the full r
In '''UTF-8 mode''', MySQL will know what character set your data is in, and can present and convert it appropriately, but it will not let you store characters above the [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
- 'config-ibm_db2-low-db-pagesize' => "Your DB2 database has a default tablespace with an insufficient pagesize. The pagesize has to be '''32K''' or greater.",
-
'config-site-name' => 'Name of wiki:',
'config-site-name-help' => "This will appear in the title bar of the browser and in various other places.",
'config-site-name-blank' => 'Enter a site name.',
@@ -359,7 +356,7 @@ In '''UTF-8 mode''', MySQL will know what character set your data is in, and can
'config-ns-other-default' => 'MyWiki',
'config-project-namespace-help' => 'Following Wikipedia\'s example, many wikis keep their policy pages separate from their content pages, in a "\'\'\'project namespace\'\'\'".
All page titles in this namespace start with a certain prefix, which you can specify here.
-Traditionally, this prefix is derived from the name of the wiki, but it cannot contain punctuation characters such as "#" or ":".',
+Usually, this prefix is derived from the name of the wiki, but it cannot contain punctuation characters such as "#" or ":".',
'config-ns-invalid' => 'The specified namespace "<nowiki>$1</nowiki>" is invalid.
Specify a different project namespace.',
'config-ns-conflict' => 'The specified namespace "<nowiki>$1</nowiki>" conflicts with a default MediaWiki namespace.
@@ -376,22 +373,22 @@ Specify a different username.',
'config-admin-password-blank' => 'Enter a password for the administrator account.',
'config-admin-password-same' => 'The password must not be the same as the username.',
'config-admin-password-mismatch' => 'The two passwords you entered do not match.',
- 'config-admin-email' => 'E-mail address:',
- 'config-admin-email-help' => 'Enter an e-mail address here to allow you to receive e-mail from other users on the wiki, reset your password, and be notified of changes to pages on your watchlist. You can leave this field empty.',
+ 'config-admin-email' => 'Email address:',
+ 'config-admin-email-help' => 'Enter an email address here to allow you to receive email from other users on the wiki, reset your password, and be notified of changes to pages on your watchlist. You can leave this field empty.',
'config-admin-error-user' => 'Internal error when creating an admin with the name "<nowiki>$1</nowiki>".',
'config-admin-error-password' => 'Internal error when setting a password for the admin "<nowiki>$1</nowiki>": <pre>$2</pre>',
- 'config-admin-error-bademail' => 'You have entered an invalid e-mail address.',
+ 'config-admin-error-bademail' => 'You have entered an invalid email address.',
'config-subscribe' => 'Subscribe to the [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce release announcements mailing list].',
'config-subscribe-help' => 'This is a low-volume mailing list used for release announcements, including important security announcements.
You should subscribe to it and update your MediaWiki installation when new versions come out.',
- 'config-subscribe-noemail' => 'You tried to subscribe to the release announcements mailing list without providing an e-mail address.
-Please provide an e-mail address if you wish to subscribe to the mailing list.',
+ 'config-subscribe-noemail' => 'You tried to subscribe to the release announcements mailing list without providing an email address.
+Please provide an email address if you wish to subscribe to the mailing list.',
'config-almost-done' => 'You are almost done!
You can now skip the remaining configuration and install the wiki right now.',
'config-optional-continue' => 'Ask me more questions.',
'config-optional-skip' => "I'm bored already, just install the wiki.",
'config-profile' => 'User rights profile:',
- 'config-profile-wiki' => 'Traditional wiki',
+ 'config-profile-wiki' => 'Open wiki',
'config-profile-no-anon' => 'Account creation required',
'config-profile-fishbowl' => 'Authorized editors only',
'config-profile-private' => 'Private wiki',
@@ -401,7 +398,7 @@ In MediaWiki, it is easy to review the recent changes, and to revert any damage
However, many have found MediaWiki to be useful in a wide variety of roles, and sometimes it is not easy to convince everyone of the benefits of the wiki way.
So you have the choice.
-A '''{{int:config-profile-wiki}}''' allows anyone to edit, without even logging in.
+The '''{{int:config-profile-wiki}}''' model allows anyone to edit, without even logging in.
A wiki with '''{{int:config-profile-no-anon}}''' provides extra accountability, but may deter casual contributors.
The '''{{int:config-profile-fishbowl}}''' scenario allows approved users to edit, but the public can view the pages, including history.
@@ -426,22 +423,22 @@ If you want to be able to use text from Wikipedia, and you want Wikipedia to be
Wikipedia previously used the GNU Free Documentation License.
The GFDL is a valid license, but it is difficult to understand.
It is also difficult to reuse content licensed under the GFDL.",
- 'config-email-settings' => 'E-mail settings',
- 'config-enable-email' => 'Enable outbound e-mail',
- 'config-enable-email-help' => "If you want e-mail to work, [http://www.php.net/manual/en/mail.configuration.php PHP's mail settings] need to be configured correctly.
-If you do not want any e-mail features, you can disable them here.",
- 'config-email-user' => 'Enable user-to-user e-mail',
- 'config-email-user-help' => 'Allow all users to send each other e-mail if they have enabled it in their preferences.',
+ 'config-email-settings' => 'Email settings',
+ 'config-enable-email' => 'Enable outbound email',
+ 'config-enable-email-help' => "If you want email to work, [http://www.php.net/manual/en/mail.configuration.php PHP's mail settings] need to be configured correctly.
+If you do not want any email features, you can disable them here.",
+ 'config-email-user' => 'Enable user-to-user email',
+ 'config-email-user-help' => 'Allow all users to send each other email if they have enabled it in their preferences.',
'config-email-usertalk' => 'Enable user talk page notification',
'config-email-usertalk-help' => 'Allow users to receive notifications on user talk page changes, if they have enabled it in their preferences.',
'config-email-watchlist' => 'Enable watchlist notification',
'config-email-watchlist-help' => 'Allow users to receive notifications about their watched pages if they have enabled it in their preferences.',
- 'config-email-auth' => 'Enable e-mail authentication',
- 'config-email-auth-help' => "If this option is enabled, users have to confirm their e-mail address using a link sent to them whenever they set or change it.
-Only authenticated e-mail addresses can receive e-mails from other users or change notification e-mails.
-Setting this option is '''recommended''' for public wikis because of potential abuse of the e-mail features.",
- 'config-email-sender' => 'Return e-mail address:',
- 'config-email-sender-help' => 'Enter the e-mail address to use as the return address on outbound e-mail.
+ 'config-email-auth' => 'Enable email authentication',
+ 'config-email-auth-help' => "If this option is enabled, users have to confirm their email address using a link sent to them whenever they set or change it.
+Only authenticated email addresses can receive emails from other users or change notification emails.
+Setting this option is '''recommended''' for public wikis because of potential abuse of the email features.",
+ 'config-email-sender' => 'Return email address:',
+ 'config-email-sender-help' => 'Enter the email address to use as the return address on outbound email.
This is where bounces will be sent.
Many mail servers require at least the domain name part to be valid.',
'config-upload-settings' => 'Images and file uploads',
@@ -458,6 +455,8 @@ Ideally, this should not be accessible from the web.',
'config-logo-help' => "MediaWiki's default skin includes space for a 135x160 pixel logo above the sidebar menu.
Upload an image of the appropriate size, and enter the URL here.
+You can use <code>\$wgStylePath</code> or <code>\$wgScriptPath</code> if your logo is relative to those paths.
+
If you do not want a logo, leave this box blank.",
'config-instantcommons' => 'Enable Instant Commons',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] is a feature that allows wikis to use images, sounds and other media found on the [//commons.wikimedia.org/ Wikimedia Commons] site.
@@ -492,7 +491,7 @@ They may require additional configuration, but you can enable them now',
'config-install-alreadydone' => "'''Warning:''' You seem to have already installed MediaWiki and are trying to install it again.
Please proceed to the next page.",
'config-install-begin' => 'By pressing "{{int:config-continue}}", you will begin the installation of MediaWiki.
-If you still want to make changes, press back.',
+If you still want to make changes, press "{{int:config-back}}".',
'config-install-step-done' => 'done',
'config-install-step-failed' => 'failed',
'config-install-extensions' => 'Including extensions',
@@ -517,12 +516,12 @@ MediaWiki currently requires that the tables be owned by the web user. Please sp
'config-install-user-missing-create' => 'The specified user "$1" does not exist.
Please click the "create account" checkbox below if you want to create it.',
'config-install-tables' => 'Creating tables',
- 'config-install-tables-exist' => "'''Warning''': MediaWiki tables seem to already exist.
+ 'config-install-tables-exist' => "'''Warning:''' MediaWiki tables seem to already exist.
Skipping creation.",
- 'config-install-tables-failed' => "'''Error''': Table creation failed with the following error: $1",
+ 'config-install-tables-failed' => "'''Error:''' Table creation failed with the following error: $1",
'config-install-interwiki' => 'Populating default interwiki table',
'config-install-interwiki-list' => 'Could not read file <code>interwiki.list</code>.',
- 'config-install-interwiki-exists' => "'''Warning''': The interwiki table seems to already have entries.
+ 'config-install-interwiki-exists' => "'''Warning:''' The interwiki table seems to already have entries.
Skipping default list.",
'config-install-stats' => 'Initializing statistics',
'config-install-keys' => 'Generating secret keys',
@@ -545,10 +544,10 @@ If the download was not offered, or if you cancelled it, you can restart the dow
$3
-'''Note''': If you do not do this now, this generated configuration file will not be available to you later if you exit the installation without downloading it.
+'''Note:''' If you do not do this now, this generated configuration file will not be available to you later if you exit the installation without downloading it.
When that has been done, you can '''[$2 enter your wiki]'''.",
- 'config-download-localsettings' => 'Download LocalSettings.php',
+ 'config-download-localsettings' => 'Download <code>LocalSettings.php</code>',
'config-help' => 'help',
'config-nofile' => 'File "$1" could not be found. Has it been deleted?',
'mainpagetext' => "'''MediaWiki has been successfully installed.'''",
@@ -557,7 +556,8 @@ When that has been done, you can '''[$2 enter your wiki]'''.",
== Getting started ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Localise MediaWiki for your language]",
);
/** Message documentation (Message documentation)
@@ -582,11 +582,22 @@ $messages['qqq'] = array(
'config-title' => 'Parameters:
* $1 is the version of MediaWiki that is being installed.',
'config-information' => '{{Identical|Information}}',
- 'config-localsettings-cli-upgrade' => 'Do not translate the <code>LocalSettings.php</code> and the <code>update.php</code> parts.',
+ 'config-localsettings-upgrade' => '{{doc-important|Do not translate <code>LocalSettings.php</code> and <code>$wgUpgradeKey</code>.}}',
+ 'config-localsettings-cli-upgrade' => '{{doc-important|Do not translate the <code>LocalSettings.php</code> and the <code>update.php</code> parts.}}',
+ 'config-upgrade-key-missing' => 'Used in info box. Parameters:
+* $1 - the upgrade key, enclosed in <code><nowiki><pre></nowikI></code> tag.',
+ 'config-localsettings-incomplete' => '{{doc-important|Do not translate <code>LocalSettings.php</code> and <code><nowiki>{{int:Config-continue}}</nowiki><code>.}}
+Parameters:
+* $1 - name of variable (any one of required variables or installer-specific global variables)',
+ 'config-localsettings-connection-error' => '{{doc-important|Do not translate <code>LocalSettings.php</code> and <code>AdminSettings.php</code>.}}
+Used as error message. Parameters:
+* $1 - (probably empty string)',
'config-session-error' => 'Parameters:
* $1 is the error that was encountered with the session.',
'config-session-expired' => 'Parameters:
* $1 is the configured session lifetime.',
+ 'config-no-session' => '{{doc-important|Do not translate <code>php.ini</code> and <code>session.save_path</code>.}}
+Used as error message.',
'config-back' => '{{Identical|Back}}',
'config-continue' => '{{Identical|Continue}}',
'config-page-language' => '{{Identical|Language}}',
@@ -600,15 +611,17 @@ $messages['qqq'] = array(
* $1 is the version of PHP that has been installed.',
'config-unicode-pure-php-warning' => 'PECL is the name of a group producing standard pieces of software for PHP, and intl is the name of their library handling some aspects of internationalization.',
'config-unicode-update-warning' => "ICU is a body producing standard software tools for support of Unicode and other internationalization aspects. This message warns the system administrator installing MediaWiki that the server's software is not up-to-date and MediaWiki will have problems handling some characters.",
- 'config-no-db' => 'Do not translate: <code>./configure --with-mysql</code>.
-<br />
-Do not translate: <code>php5-mysql</code>.
-
+ 'config-no-db' => '{{doc-important|Do not translate "<code>./configure --with-mysql</code>" and "<code>php5-mysql</code>".}}
Parameters:
* $1 is comma separated list of database types supported by MediaWiki.',
'config-no-fts3' => 'A "[[:wikipedia:Front and back ends|backend]]" is a system or component that ordinary users don\'t interact with directly and don\'t need to know about, and that is responsible for a distinct task or service - for example, a storage back-end is a generic system for storing data which other applications can use. Possible alternatives for back-end are "system" or "service", or (depending on context and language) even leave it untranslated.',
+ 'config-magic-quotes-runtime' => '{{Related|Config-fatal}}',
+ 'config-magic-quotes-sybase' => '{{Related|Config-fatal}}',
+ 'config-mbstring' => '{{Related|Config-fatal}}',
+ 'config-ze1' => '{{Related|Config-fatal}}',
'config-pcre' => 'PCRE is an initialism for "Perl-compatible regular expression". Perl is programming language whose [[:w:regular expression|regular expression]] syntax is popular and used in other languages using a library called PCRE.',
- 'config-pcre-no-utf8' => "PCRE is a name of a programmers' library for supporting regular expressions. It can probably be translated without change.",
+ 'config-pcre-no-utf8' => "PCRE is a name of a programmers' library for supporting regular expressions. It can probably be translated without change.
+{{Related|Config-fatal}}",
'config-memory-raised' => 'Parameters:
* $1 is the configured <code>memory_limit</code>.
* $2 is the value to which <code>memory_limit</code> was raised.',
@@ -624,11 +637,17 @@ Add dir="ltr" to the <nowiki><code></nowiki> for right-to-left languages.',
'config-no-cli-uri' => 'Parameters:
* $1 is the default value for scriptpath.',
'config-no-cli-uploads-check' => 'CLI = [[w:Command-line interface|command-line interface]] (i.e. the installer runs as a command-line script, not using HTML interface via an internet browser)',
- 'config-suhosin-max-value-length' => 'Message shown when PHP parameter suhosin.get.max_value_length is between 0 and 1023 (that max value is hard set in MediaWiki software)',
+ 'config-suhosin-max-value-length' => '{{doc-important|Do not translate "length", "suhosin.get.max_value_length", "php.ini", "$wgResourceLoaderMaxQueryLength" and "LocalSettings.php".}}
+Message shown when PHP parameter <code>suhosin.get.max_value_length</code> is between 0 and 1023 (that max value is hard set in MediaWiki software).',
+ 'config-db-host-help' => '{{doc-singularthey}}',
'config-db-host-oracle' => 'TNS = [[:wikipedia:Transparent Network Substrate|Transparent Network Substrate]] (<== wikipedia link)',
'config-db-wiki-settings' => 'This is more acurate: "Enter identifying or distinguishing data for this wiki" since a MySQL database can host tables of several wikis.',
'config-db-account-oracle-warn' => 'A "[[:wikipedia:Front and back ends|backend]]" is a system or component that ordinary users don\'t interact with directly and don\'t need to know about, and that is responsible for a distinct task or service - for example, a storage back-end is a generic system for storing data which other applications can use. Possible alternatives for back-end are "system" or "service", or (depending on context and language) even leave it untranslated.',
'config-db-account-lock' => "It might be easier to translate ''normal operation'' as \"also after the installation process\"",
+ 'config-pg-test-error' => '* $1 - database name
+* $2 - error message',
+ 'config-sqlite-dir-help' => '{{doc-important|Do not translate <code>.htaccess</code> and <code>/var/lib/mediawiki/yourwiki</code>.}}
+Used in help box.',
'config-type-mysql' => '{{optional}}',
'config-type-postgres' => '{{optional}}',
'config-type-sqlite' => '{{optional}}',
@@ -650,7 +669,7 @@ If you\'re translating this message to a right-to-left language, consider writin
'config-sqlite-dir-unwritable' => 'webserver refers to a software like Apache or Lighttpd.',
'config-can-upgrade' => 'Parameters:
* $1 - Version or Revision indicator.',
- 'config-show-table-status' => '{{doc-important|"SHOW TABLE STATUS" is a MySQL command. Do not translate this.}}',
+ 'config-show-table-status' => '{{doc-important|"<code>SHOW TABLE STATUS</code>" is a MySQL command. Do not translate this.}}',
'config-db-web-account-same' => 'checkbox label',
'config-db-web-create' => 'checkbox label',
'config-ns-generic' => '{{Identical|Project}}',
@@ -658,17 +677,25 @@ If you\'re translating this message to a right-to-left language, consider writin
'config-admin-password' => '{{Identical|Password}}',
'config-admin-email' => '{{Identical|E-mail address}}',
'config-subscribe' => 'Used as label for the installer checkbox',
+ 'config-subscribe-help' => '"Low-volume" in this context means that there will be few e-mails to that mailing list per time period.',
'config-profile-help' => 'Messages referenced:
* {{msg-mw|config-profile-wiki}}
* {{msg-mw|config-profile-no-anon}}
* {{msg-mw|config-profile-fishbowl}}
* {{msg-mw|config-profile-private}}',
+ 'config-email-settings' => '{{Identical|E-mail setting}}',
+ 'config-email-user' => '{{Identical|Enable user-to-user e-mail}}',
'config-upload-help' => 'The word "mode" here refers to the access rights given to various user groups when attempting to create and store files and/or subdiretories in the said directory on the server. It also refers to the <code>mode</code> command used to maipulate said right mask under Unix, Linux, and similar operating systems. A less operating-system-centric translation is fine.',
'config-logo-help' => '',
- 'config-cc-not-chosen' => 'Do not translate the <code>"proceed".</code> part.
-This message refers to a block of HTML being embedded into the installer page. It comes from the Creative Commons Web site. The block is in the English language. It is a scripted license chooser. When an individual license has been selected, it asks you to klick "proceed" so as to return to the MediaWiki installer page.',
+ 'config-cc-not-chosen' => '{{doc-important|Do not translate the "<code>proceed</code>" part.}}
+This message refers to a block of HTML being embedded into the installer page. It comes from the Creative Commons Web site. The block is in the English language. It is a scripted license chooser. When an individual license has been selected, it asks you to click "proceed" so as to return to the MediaWiki installer page.',
+ 'config-memcached-servers' => '{{doc-important|Do not translate "memcached".}}
+{{Identical|Memcached server}}',
'config-extensions' => '{{Identical|Extension}}',
+ 'config-extensions-help' => '{{doc-important|Do not translate <code>./extensions</code>.}}
+Used in help box.',
'config-install-step-done' => '{{Identical|Done}}',
+ 'config-install-step-failed' => '{{Identical|Failed}}',
'config-install-database' => '*{{msg-mw|Config-install-database}}
*{{msg-mw|Config-install-tables}}
*{{msg-mw|Config-install-schema}}
@@ -729,6 +756,8 @@ See also:
*{{msg-mw|Config-install-keys}}
*{{msg-mw|Config-install-sysop}}
*{{msg-mw|Config-install-mainpage}}',
+ 'config-install-interwiki-list' => '{{doc-important|Do not translate <code>interwiki.list</code>.}}
+Used as error message.',
'config-install-stats' => '*{{msg-mw|Config-install-database}}
*{{msg-mw|Config-install-tables}}
*{{msg-mw|Config-install-schema}}
@@ -779,8 +808,8 @@ See also:
'config-download-localsettings' => 'The link text used in the download link in config-install-done.',
'config-help' => 'This is used in help boxes.
{{Identical|Help}}',
- 'mainpagetext' => 'Along with {{msg|mainpagedocfooter}}, the text you will see on the Main Page when your wiki is installed.',
- 'mainpagedocfooter' => 'Along with {{msg|mainpagetext}}, the text you will see on the Main Page when your wiki is installed.
+ 'mainpagetext' => 'Along with {{msg-mw|mainpagedocfooter}}, the text you will see on the Main Page when your wiki is installed.',
+ 'mainpagedocfooter' => 'Along with {{msg-mw|mainpagetext}}, the text you will see on the Main Page when your wiki is installed.
This might be a good place to put information about <nowiki>{{GRAMMAR:}}</nowiki>. See [[{{NAMESPACE}}:{{BASEPAGENAME}}/fi]] for an example. For languages having grammatical distinctions and not having an appropriate <nowiki>{{GRAMMAR:}}</nowiki> software available, a suggestion to check and possibly amend the messages having <nowiki>{{SITENAME}}</nowiki> may be valuable. See [[{{NAMESPACE}}:{{BASEPAGENAME}}/ksh]] for an example.',
);
@@ -874,8 +903,8 @@ U gebruik tans $2.',
'config-upgrade-done-no-regenerate' => 'Opgradering is voltooi.
U kan nou [$1 u wiki gebruik].',
- 'config-regenerate' => 'Herskep LocalSettings.php →',
- 'config-show-table-status' => 'Die uitvoer van SHOW TABLE STATUS het gefaal!',
+ 'config-regenerate' => 'Herskep <code>LocalSettings.php</code> →',
+ 'config-show-table-status' => 'Die uitvoer van <code>SHOW TABLE STATUS</code> het gefaal!',
'config-db-web-account' => 'Databasisgebruiker vir toegang tot die web',
'config-mysql-engine' => 'Stoor-enjin:',
'config-mysql-innodb' => 'InnoDB',
@@ -900,7 +929,7 @@ U kan nou [$1 u wiki gebruik].',
'config-admin-email' => 'E-posadres:',
'config-optional-continue' => 'Vra my meer vrae.',
'config-optional-skip' => 'Ek is reeds verveeld, installeer maar net die wiki.',
- 'config-profile-wiki' => 'Tradisionele wiki',
+ 'config-profile-wiki' => 'Tradisionele wiki', # Fuzzy
'config-profile-no-anon' => 'Skep van gebruiker is verpligtend',
'config-profile-fishbowl' => 'Slegs vir gemagtigde redaksie',
'config-profile-private' => 'Privaat wiki',
@@ -957,7 +986,7 @@ U sal dit moet [$1 aflaai] en dit in die hoofgids van u wiki-installasie plaas;
'''Let wel''': As u dit nie nou doen nie, sal die gegenereerde konfigurasielêer nie later meer beskikbaar wees nadat u die installasie afgesluit het nie.
As dit gedoen is, kan u '''[u $2 wiki besoek]'''.", # Fuzzy
- 'config-download-localsettings' => 'Laai LocalSettings.php af',
+ 'config-download-localsettings' => 'Laai <code>LocalSettings.php</code> af',
'config-help' => 'hulp',
'mainpagetext' => "'''MediaWiki is suksesvol geïnstalleer.'''",
'mainpagedocfooter' => "Konsulteer '''[//meta.wikimedia.org/wiki/Help:Contents User's Guide]''' vir inligting oor hoe om die wikisagteware te gebruik.
@@ -965,7 +994,7 @@ As dit gedoen is, kan u '''[u $2 wiki besoek]'''.", # Fuzzy
== Hoe om te Begin ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]", # Fuzzy
);
/** Gheg Albanian (Gegë)
@@ -1025,12 +1054,13 @@ $messages['ang'] = array(
/** Arabic (العربية)
* @author Meno25
+ * @author Mido
* @author OsamaK
* @author روخو
*/
$messages['ar'] = array(
'config-desc' => 'مثبت لميدياويكي',
- 'config-title' => 'ميدياويكي 1$ التثبيت', # Fuzzy
+ 'config-title' => 'تثبيت ميدياويكي $1',
'config-information' => 'معلومات',
'config-back' => '→ ارجع',
'config-continue' => 'استمر ←',
@@ -1051,7 +1081,7 @@ $messages['ar'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings قائمة إعدادات الضبط]
* [//www.mediawiki.org/wiki/Manual:FAQ أسئلة متكررة حول ميدياويكي]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce القائمة البريدية الخاصة بإصدار ميدياويكي]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce القائمة البريدية الخاصة بإصدار ميدياويكي]', # Fuzzy
);
/** Aramaic (ܐܪܡܝܐ)
@@ -1116,16 +1146,63 @@ $messages['as'] = array(
);
/** Asturian (asturianu)
+ * @author Xuacu
*/
$messages['ast'] = array(
+ 'config-desc' => "L'instalador pa MediaWiki",
+ 'config-title' => 'Instalación de MediaWiki $1',
+ 'config-information' => 'Información',
+ 'config-localsettings-upgrade' => "Detectose un ficheru <code>LocalSettings.php</code>.
+P'anovar esta instalación, escriba'l valor de
+<code>\$wgUpgradeKey</code> nel cuadru d'abaxo.
+Alcontraralu en <code>LocalSettings.php</code>.",
+ 'config-localsettings-cli-upgrade' => "Deteutose un ficheru <code>LocalSettings.php</code>.
+P'anovar esta instalación, execute <code>update.php</code>",
+ 'config-localsettings-key' => "Clave d'anovamientu:",
+ 'config-localsettings-badkey' => 'La clave que dio ye incorreuta.',
+ 'config-upgrade-key-missing' => "Deteutose una instalación esistente de MediaWiki.
+P'anovar esta instalación, ponga la llinia siguiente al final del ficheru <code>LocalSettings.php</code>:
+
+$1",
+ 'config-localsettings-incomplete' => 'Paez que\'l ficheru <code>LocalSettings.php</code> esistente ta incompletu.
+La variable $1 nun ta definida.
+Camude\'l ficheru <code>LocalSettings.php</code> pa qu\'esta variable quede definida y calque "{{int:Config-continue}}".',
+ 'config-localsettings-connection-error' => 'Alcontrose un error al conectar cola base de datos usando la configuración especificada en <code>LocalSettings.php</code> o <code>AdminSettings.php</code>. Corrixa esta configuración y vuelva a intentalo.
+
+$1',
+ 'config-your-language' => 'La so llingua:',
+ 'config-your-language-help' => "Seleicione la llingua a emplegar nel procesu d'instalación.",
+ 'config-wiki-language' => 'Llingua de la wiki:',
+ 'config-wiki-language-help' => "Seleicione la llingua que s'usará preferentemente na wiki.",
+ 'config-back' => '← Atrás',
+ 'config-continue' => 'Siguir →',
+ 'config-page-language' => 'Llingua',
+ 'config-page-welcome' => '¡Bienveníu a MediaWiki!',
+ 'config-page-dbconnect' => 'Conectar cola base de datos',
+ 'config-page-upgrade' => 'Anovar instalación esistente',
+ 'config-page-dbsettings' => 'Configuración de la base de datos',
+ 'config-page-name' => 'Nome',
+ 'config-page-options' => 'Opciones',
+ 'config-page-install' => 'Instalar',
+ 'config-page-complete' => '¡Completo!',
+ 'config-page-restart' => 'Reaniciar la instalación',
+ 'config-page-readme' => 'Llei-me',
+ 'config-page-releasenotes' => 'Notes de la versión',
+ 'config-page-copying' => 'Copiar',
+ 'config-page-upgradedoc' => 'Anovando',
+ 'config-page-existingwiki' => 'Wiki esistente',
+ 'config-download-localsettings' => 'Descargar <code>LocalSettings.php</code>',
+ 'config-help' => 'Ayuda',
+ 'config-nofile' => 'Nun pudo atopase\'l ficheru "$1". ¿Desaniciose?',
'mainpagetext' => "'''MediaWiki instalóse correchamente.'''",
- 'mainpagedocfooter' => "Visita la [//meta.wikimedia.org/wiki/Help:Contents Guía d'usuariu] pa saber cómo usar esti software wiki.
+ 'mainpagedocfooter' => 'Visita la [//meta.wikimedia.org/wiki/Help:Contents Guía del usuariu] pa saber cómo usar esti software wiki.
== Empecipiando ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Llista de les opciones de configuración]
* [//www.mediawiki.org/wiki/Manual:FAQ FAQ de MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Llista de corréu de les ediciones de MediaWiki]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Llista de corréu de les ediciones de MediaWiki]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Llocaliza MediaWiki na to llingua]',
);
/** Kotava (Kotava)
@@ -1238,6 +1315,7 @@ $messages['be'] = array(
* @author Jim-by
* @author Wizardist
* @author Zedlik
+ * @author 아라
*/
$messages['be-tarask'] = array(
'config-desc' => 'Праграма ўсталяваньня MediaWiki',
@@ -1245,19 +1323,19 @@ $messages['be-tarask'] = array(
'config-information' => 'Інфармацыя',
'config-localsettings-upgrade' => 'Выяўлены файл <code>LocalSettings.php</code>.
Каб абнавіць гэтае усталяваньне, калі ласка, увядзіце значэньне <code>$wgUpgradeKey</code> у полі ніжэй.
-Яго можна знайсьці ў LocalSettings.php.',
- 'config-localsettings-cli-upgrade' => 'Быў знойдзены файл LocalSettings.php.
-Каб зьмяніць гэтае ўсталяваньне, калі ласка, запусьціце update.php',
+Яго можна знайсьці ў <code>LocalSettings.php</code>.',
+ 'config-localsettings-cli-upgrade' => 'Быў знойдзены файл <code>LocalSettings.php</code>.
+Каб зьмяніць гэтае ўсталяваньне, калі ласка, запусьціце <code>update.php</code>',
'config-localsettings-key' => 'Ключ паляпшэньня:',
'config-localsettings-badkey' => 'Пададзены Вамі ключ зьяўляецца няслушным',
'config-upgrade-key-missing' => 'Выяўленае існуючае ўсталяваньне MediaWiki.
-Каб абнавіць гэтае ўсталяваньне, калі ласка, устаўце наступны радок у канец Вашага LocalSettings.php:
+Каб абнавіць гэтае ўсталяваньне, калі ласка, устаўце наступны радок у канец Вашага <code>LocalSettings.php</code>:
$1',
- 'config-localsettings-incomplete' => 'Выглядае, што існуючы LocalSettings.php зьяўляецца няпоўным.
+ 'config-localsettings-incomplete' => 'Выглядае, што існуючы <code>LocalSettings.php</code> зьяўляецца няпоўным.
Не ўстаноўленая пераменная $1.
-Калі ласка, зьмяніце LocalSettings.php так, каб была ўстаноўленая гэтая пераменная, і націсьніце «Працягваць».',
- 'config-localsettings-connection-error' => 'Адбылася памылка падчас злучэньня з базай зьвестак з выкарыстаньнем наладаў, пазначаных у LocalSettings.php ці AdminSettings.php. Калі ласка, выпраўце гэтыя налады і паспрабуйце зноў.
+Калі ласка, зьмяніце <code>LocalSettings.php</code> так, каб была ўстаноўленая гэтая пераменная, і націсьніце «{{int:Config-continue}}».',
+ 'config-localsettings-connection-error' => 'Адбылася памылка падчас злучэньня з базай зьвестак з выкарыстаньнем наладаў, пазначаных у <code>LocalSettings.php</code> ці <code>AdminSettings.php</code>. Калі ласка, выпраўце гэтыя налады і паспрабуйце зноў.
$1',
'config-session-error' => 'Памылка стварэньня сэсіі: $1',
@@ -1391,7 +1469,9 @@ MediaWiki патрабуе падтрымкі UTF-8 для слушнай пра
'config-using531' => 'PHP $1 не сумяшчальнае з MediaWiki з-за памылкі ў перадачы парамэтраў па ўказальніку да <code>__call()</code>.
Абнавіце PHP да вэрсіі 5.3.2 ці болей позьняй, ці адкаціце да вэрсіі 5.3.0 каб гэта выправіць.
Усталяваньне перарванае.',
- 'config-suhosin-max-value-length' => 'Suhosin усталяваны і абмяжоўвае даўжыню парамэтра GET у $1 {{PLURAL:$1|байт|байты|байтаў}}. ResourceLoader для MediaWiki будзе абходзіць гэтае абмежаваньне, што, аднак, адаб’ецца на хуткадзеяньні. Калі магчыма, варта ўстанавіць suhosin.get.max_value_length роўным 1024 ці больш у php.ini, а таксама ўстанавіць тое ж значэньне для $wgResourceLoaderMaxQueryLength у LocalSettings.php.',
+ 'config-suhosin-max-value-length' => 'Suhosin усталяваны і абмяжоўвае <code>даўжыню</code> парамэтра GET да $1 {{PLURAL:$1|байта|байтаў}}.
+ResourceLoader, складнік MediaWiki, будзе абходзіць гэтае абмежаваньне, што, адаб’ецца на прадукцыйнасьці.
+Калі магчыма, варта ўсталяваць у <code>php.ini</code> <code>suhosin.get.max_value_length</code> роўным 1024 ці больш, а таксама вызначыць тое ж значэньне для <code>$wgResourceLoaderMaxQueryLength</code> у LocalSettings.php.',
'config-db-type' => 'Тып базы зьвестак:',
'config-db-host' => 'Хост базы зьвестак:',
'config-db-host-help' => 'Калі сэрвэр Вашай базы зьвестак знаходзіцца на іншым сэрвэры, увядзіце тут імя хоста ці IP-адрас.
@@ -1475,7 +1555,7 @@ $1
'config-support-postgres' => '* $1 — вядомая сыстэма базы зьвестак з адкрытым кодам, якая зьяўляецца альтэрнатывай MySQL ([http://www.php.net/manual/en/pgsql.installation.php як кампіляваць PHP з падтрымкай PostgreSQL]). Яна можа ўтрымліваць дробныя памылкі, і не рэкамэндуецца выкарыстоўваць яе для працуючых праектаў.',
'config-support-sqlite' => '* $1 — невялікая сыстэма базы зьвестак, якая мае вельмі добрую падтрымку. ([http://www.php.net/manual/en/pdo.installation.php як кампіляваць PHP з падтрымкай SQLite], выкарыстоўвае PDO)',
'config-support-oracle' => '* $1 зьяўляецца камэрцыйнай прафэсійнай базай зьвестак. ([http://www.php.net/manual/en/oci8.installation.php Як скампіляваць PHP з падтрымкай OCI8])',
- 'config-support-ibm_db2' => '* $1 — база зьвестак камэрцыйнага прадпрыемства.',
+ 'config-support-ibm_db2' => '* $1 — база зьвестак маштабу прадпрыемства. ([http://www.php.net/manual/en/ibm-db2.installation.php Як скампіляваць PHP з падтрымкай IBM DB2])',
'config-header-mysql' => 'Налады MySQL',
'config-header-postgres' => 'Налады PostgreSQL',
'config-header-sqlite' => 'Налады SQLite',
@@ -1542,8 +1622,8 @@ chmod a+w $3</pre>',
'config-upgrade-done-no-regenerate' => 'Абнаўленьне скончанае.
Цяпер Вы можаце [$1 пачаць працу з вікі].',
- 'config-regenerate' => 'Рэгенэраваць LocalSettings.php →',
- 'config-show-table-status' => "Запыт 'SHOW TABLE STATUS' не атрымаўся!",
+ 'config-regenerate' => 'Рэгенэраваць <code>LocalSettings.php</code> →',
+ 'config-show-table-status' => "Запыт '<code>SHOW TABLE STATUS</code>' не атрымаўся!",
'config-unknown-collation' => "'''Папярэджаньне:''' база зьвестак выкарыстоўвае нераспазнанае супастаўленьне.",
'config-db-web-account' => 'Рахунак базы зьвестак для вэб-доступу',
'config-db-web-help' => 'Выберыце імя карыстальніка і пароль, які выкарыстоўваецца вэб-сэрвэрам для злучэньня з сэрвэрам базы зьвестак, падчас звычайных апэрацыяў вікі.',
@@ -1615,7 +1695,7 @@ chmod a+w $3</pre>',
'config-optional-continue' => 'Задаць болей пытаньняў.',
'config-optional-skip' => 'Хопіць, проста ўсталяваць вікі.',
'config-profile' => 'Профіль правоў удзельніка:',
- 'config-profile-wiki' => 'Традыцыйная вікі',
+ 'config-profile-wiki' => 'Адкрытая вікі',
'config-profile-no-anon' => 'Патрэбнае стварэньне рахунку',
'config-profile-fishbowl' => 'Толькі для аўтарызаваных рэдактараў',
'config-profile-private' => 'Прыватная вікі',
@@ -1631,7 +1711,7 @@ chmod a+w $3</pre>',
Сцэнар '''{{int:config-profile-fishbowl}}''' дазваляе рэдагаваць зацьверджаным удзельнікам, але ўсе могуць праглядаць старонкі іх гісторыю.
'''{{int:config-profile-private}}''' дазваляе праглядаць і рэдагаваць старонкі толькі зацьверджаным удзельнікам.
-Больш складаныя правы ўдзельнікаў даступныя пасьля ўсталяваньня, глядзіце [//www.mediawiki.org/wiki/Manual:User_rights адпаведную старонку дакумэнтацыі].",
+Больш складаныя правы ўдзельнікаў даступныя пасьля ўсталяваньня, глядзіце [//www.mediawiki.org/wiki/Manual:User_rights адпаведную старонку дакумэнтацыі].", # Fuzzy
'config-license' => 'Аўтарскія правы і ліцэнзія:',
'config-license-none' => 'Без інфармацыі пра ліцэнзію',
'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
@@ -1716,7 +1796,7 @@ chmod a+w $3</pre>',
'config-install-alreadydone' => "'''Папярэджаньне:''' здаецца, што Вы ўжо ўсталёўвалі MediaWiki і спрабуеце зрабіць гэтай зноў.
Калі ласка, перайдзіце на наступную старонку.",
'config-install-begin' => 'Пасьля націску кнопкі «{{int:config-continue}}» пачнецца ўсталяваньне MediaWiki.
-Калі Вы жадаеце што-небудзь зьмяніць, націсьніце кнопку «Вярнуцца».',
+Калі Вы жадаеце што-небудзь зьмяніць, націсьніце кнопку «Вярнуцца».', # Fuzzy
'config-install-step-done' => 'зроблена',
'config-install-step-failed' => 'не атрымалася',
'config-install-extensions' => 'Уключаючы пашырэньні',
@@ -1772,7 +1852,7 @@ $3
'''Заўвага''': калі Вы гэтага ня зробіце зараз, то створаны файл ня будзе даступны Вам потым, калі Вы выйдзеце з праграмы ўсталяваньня без яго загрузкі.
Калі Вы гэта зробіце, Вы можаце '''[$2 ўвайсьці ў Вашую вікі]'''.",
- 'config-download-localsettings' => 'Загрузіць LocalSettings.php',
+ 'config-download-localsettings' => 'Загрузіць <code>LocalSettings.php</code>',
'config-help' => 'дапамога',
'config-nofile' => 'Файл «$1» ня знойдзены. Ці быў ён выдалены?',
'mainpagetext' => "'''MediaWiki пасьпяхова ўсталяваная.'''",
@@ -1781,11 +1861,12 @@ $3
== З чаго пачаць ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Сьпіс парамэтраў канфігурацыі]
* [//www.mediawiki.org/wiki/Manual:FAQ Частыя пытаньні MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Рассылка паведамленьняў пра зьяўленьне новых вэрсіяў MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Рассылка паведамленьняў пра зьяўленьне новых вэрсіяў MediaWiki]', # Fuzzy
);
/** Bulgarian (български)
* @author DCLXVI
+ * @author 아라
*/
$messages['bg'] = array(
'config-desc' => 'Инсталатор на МедияУики',
@@ -1793,19 +1874,19 @@ $messages['bg'] = array(
'config-information' => 'Информация',
'config-localsettings-upgrade' => 'Беше открит файл <code>LocalSettings.php</code>.
За надграждане на съществуващата инсталация, необходимо е в кутията по-долу да се въведе стойността на <code>$wgUpgradeKey</code>.
-Тази информация е налична в LocalSettings.php.',
- 'config-localsettings-cli-upgrade' => 'Беше открит файл LocalSettings.php.
-За надграждане на наличната инсталация, необходимо е да се стартира update.php',
+Тази информация е налична в <code>LocalSettings.php</code>.',
+ 'config-localsettings-cli-upgrade' => 'Беше открит файл <code>LocalSettings.php</code>.
+За надграждане на наличната инсталация, необходимо е да се стартира <code>update.php</code>',
'config-localsettings-key' => 'Ключ за надграждане:',
'config-localsettings-badkey' => 'Предоставеният ключ е неправилен.',
'config-upgrade-key-missing' => 'Беше открита съществуваща инсталация на МедияУики.
-За надграждане на съществуващата инсталация, необходимо е да се постави следният ред в края на файла LocalSettings.php:
+За надграждане на съществуващата инсталация, необходимо е да се постави следният ред в края на файла <code>LocalSettings.php</code>:
$1',
- 'config-localsettings-incomplete' => 'Съществуващият файл LocalSettings.php изглежда непълен.
+ 'config-localsettings-incomplete' => 'Съществуващият файл <code>LocalSettings.php</code> изглежда непълен.
Променливата $1 не е зададена.
-Необходимо е да се редактира файлът LocalSettings.php и да се зададе променливата, след което да се натисне "Продължаване".',
- 'config-localsettings-connection-error' => 'Възникна грешка при свързване с базата от данни чрез данните, посочени в LocalSettings.php или AdminSettings.php. Необходимо е да се коригират тези настройки преди повторен опит за свързване.
+Необходимо е да се редактира файлът <code>LocalSettings.php</code> и да се зададе променливата, след което да се натисне „{{int:Config-continue}}“.',
+ 'config-localsettings-connection-error' => 'Възникна грешка при свързване с базата от данни чрез данните, посочени в <code>LocalSettings.php</code> или <code>AdminSettings.php</code>. Необходимо е да се коригират тези настройки преди повторен опит за свързване.
$1',
'config-session-error' => 'Грешка при създаване на сесия: $1',
@@ -1933,7 +2014,7 @@ $1
'config-using531' => 'МедияУики не може да се използва с PHP $1 заради проблем с референтните параметри за <code>__call()</code>.
За разрешаване на този проблем е необходимо да се обнови до PHP 5.3.2 или по-нова версия или да се инсталира по-стара версия, напр. PHP 5.3.0.
Инсталацията беше прекратена.',
- 'config-suhosin-max-value-length' => 'Suhosin е инсталиран и ограничава дължината на параметъра GET на $1 байта. Компонентът на МедияУики ResourceLoader ще може да пренебрегне частично това ограничение, но това ще намали производителността. По възможност е препоръчително да се настрои suhosin.get.max_value_length на 1024 или по-голяма стойност в php.ini и в LocalSettings.php да се настрои $wgResourceLoaderMaxQueryLength със същата стойност.',
+ 'config-suhosin-max-value-length' => 'Suhosin е инсталиран и ограничава дължината на параметъра GET на $1 байта. Компонентът на МедияУики ResourceLoader ще може да пренебрегне частично това ограничение, но това ще намали производителността. По възможност е препоръчително да се настрои <code>suhosin.get.max_value_length</code> на 1024 или по-голяма стойност в <code>php.ini</code> и в LocalSettings.php да се настрои <code>$wgResourceLoaderMaxQueryLength</code> със същата стойност.', # Fuzzy
'config-db-type' => 'Тип на базата от данни:',
'config-db-host' => 'Хост на базата от данни:',
'config-db-host-help' => 'Ако базата от данни е на друг сървър, в кутията се въвежда името на хоста или IP адреса.
@@ -2000,7 +2081,7 @@ $1
'config-support-postgres' => '* $1 е популярна система за бази от данни с отворен изходен код, която е алтернатива на MySQL ([http://www.php.net/manual/en/pgsql.installation.php как се компилира PHP с поддръжка на PostgreSQL]). Възможно е все още да има грешки, затова не се препоръчва да се използва в общодостъпна среда.',
'config-support-sqlite' => '* $1 е лека система за база от данни, която е много добре поддържана. ([http://www.php.net/manual/en/pdo.installation.php Как се компилира PHP с поддръжка на SQLite], използва PDO)',
'config-support-oracle' => '* $1 е комерсиална корпоративна база от данни. ([http://www.php.net/manual/en/oci8.installation.php Как се компилира PHP с поддръжка на OCI8])',
- 'config-support-ibm_db2' => '* $1 е комерсиална фирмена база от данни.',
+ 'config-support-ibm_db2' => '* $1 е комерсиална фирмена база от данни. ([http://www.php.net/manual/en/ibm-db2.installation.php Как се компилира PHP с поддръжка на IBM DB2])',
'config-header-mysql' => 'Настройки за MySQL',
'config-header-postgres' => 'Настройки за PostgreSQL',
'config-header-sqlite' => 'Настройки за SQLite',
@@ -2067,8 +2148,8 @@ chmod a+w $3</pre>',
'config-upgrade-done-no-regenerate' => 'Обновяването приключи.
Вече е възможно [$1 да използвате уикито].',
- 'config-regenerate' => 'Създаване на LocalSettings.php →',
- 'config-show-table-status' => 'Заявката SHOW TABLE STATUS не сполучи!',
+ 'config-regenerate' => 'Създаване на <code>LocalSettings.php</code> →',
+ 'config-show-table-status' => 'Заявката <code>SHOW TABLE STATUS</code> не сполучи!',
'config-unknown-collation' => "'''Предупреждение:''' Базата от данни използва неразпозната колация.",
'config-db-web-account' => 'Сметка за уеб достъп до базата от данни',
'config-db-web-help' => 'Избиране на потребителско име и парола, които уеб сървърът ще използва да се свързва с базата от данни при обичайната работа на уикито.',
@@ -2139,7 +2220,7 @@ chmod a+w $3</pre>',
'config-optional-continue' => 'Задаване на допълнителни въпроси.',
'config-optional-skip' => 'Достатъчно, инсталиране на уикито.',
'config-profile' => 'Профил на потребителските права:',
- 'config-profile-wiki' => 'Традиционно уики',
+ 'config-profile-wiki' => 'Отворено уики',
'config-profile-no-anon' => 'Необходимо е създаване на сметка',
'config-profile-fishbowl' => 'Само одобрени редактори',
'config-profile-private' => 'Затворено уики',
@@ -2155,7 +2236,7 @@ chmod a+w $3</pre>',
Уики, което е '''{{int:config-profile-fishbowl}}''' позволява на всички да преглеждат страниците, но само предварително одобрени редактори могат да редактират съдържанието.
В '''{{int:config-profile-private}}''' само предварително одобрени потребители могат да четат и редактират съдържанието.
-Детайлно обяснение на конфигурациите на потребителските права е достъпно след инсталацията в [//www.mediawiki.org/wiki/Manual:User_rights Наръчника за потребителски права].",
+Детайлно обяснение на конфигурациите на потребителските права е достъпно след инсталацията в [//www.mediawiki.org/wiki/Manual:User_rights Наръчника за потребителски права].", # Fuzzy
'config-license' => 'Авторски права и лиценз:',
'config-license-none' => 'Без лиценз',
'config-license-cc-by-sa' => 'Криейтив Комънс Признание-Споделяне на споделеното',
@@ -2238,8 +2319,8 @@ chmod a+w $3</pre>',
Възможно е те да изискват допълнително конфигуриране, но сега могат да бъдат включени.',
'config-install-alreadydone' => "'''Предупреждение:''' Изглежда вече сте инсталирали МедияУики и се опитвате да го инсталирате отново.
Продължете към следващата страница.",
- 'config-install-begin' => 'Инсталацията на МедияУики ще започне след натискане на бутона "{{int:config-continue}}".
-Ако желаете да направите промени, натиснете Връщане.',
+ 'config-install-begin' => 'Инсталацията на МедияУики ще започне след натискане на бутона „{{int:config-continue}}“.
+В случай, че е необходимо да се направят промени, използва се бутона „{{int:config-back}}“.',
'config-install-step-done' => 'готово',
'config-install-step-failed' => 'неуспешно',
'config-install-extensions' => 'Добавяне на разширенията',
@@ -2294,15 +2375,17 @@ $3
'''Забележка''': Ако това не бъде извършено сега, генерираният конфигурационен файл няма да е достъпен на по-късен етап ако не бъде изтеглен сега или инсталацията приключи без изтеглянето му.
Когато файлът вече е в основната директория, '''[$2 уикито ще е достъпно на този адрес]'''.",
- 'config-download-localsettings' => 'Изтегляне на LocalSettings.php',
+ 'config-download-localsettings' => 'Изтегляне на <code>LocalSettings.php</code>',
'config-help' => 'помощ',
+ 'config-nofile' => 'Файлът „$1“ не може да бъде открит. Да не е бил изтрит?',
'mainpagetext' => "'''Уикито беше успешно инсталирано.'''",
- 'mainpagedocfooter' => 'Разгледайте [//meta.wikimedia.org/wiki/Help:Contents ръководството] за подробна информация относно използването на софтуера.
+ 'mainpagedocfooter' => 'Разгледайте [//meta.wikimedia.org/wiki/Help:Contents ръководството] за подробна информация относно използването на уики софтуера.
== Първи стъпки ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Конфигурационни настройки]
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Настройки за конфигуриране]
* [//www.mediawiki.org/wiki/Manual:FAQ ЧЗВ за МедияУики]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Пощенски списък относно нови версии на МедияУики]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Пощенски списък относно нови версии на МедияУики]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Локализиране на МедияУики]',
);
/** Banjar (Bahasa Banjar)
@@ -2452,6 +2535,7 @@ $messages['bpy'] = array(
* @author Fulup
* @author Gwendal
* @author Y-M D
+ * @author 아라
*/
$messages['br'] = array(
'config-desc' => 'Poellad staliañ MediaWIki',
@@ -2459,19 +2543,19 @@ $messages['br'] = array(
'config-information' => 'Titouroù',
'config-localsettings-upgrade' => 'Kavet ez eus bet ur restr <code>LocalSettings.php</code>.
Evit hizivaat ar staliadur-se, merkit an talvoud <code>$wgUpgradeKey</code> er voest dindan.
-E gavout a rit e LocalSettings.php.',
- 'config-localsettings-cli-upgrade' => 'Dinoet ez eus bet ur restr LocalSettings.php.
-Evit lakaat ar staliadur-mañ a-live, implijit update.php e plas',
+E gavout a rit e <code>LocalSettings.php</code>.',
+ 'config-localsettings-cli-upgrade' => 'Dinoet ez eus bet ur restr <code>LocalSettings.php</code>.
+Evit lakaat ar staliadur-mañ a-live, implijit <code>update.php</code> e plas',
'config-localsettings-key' => "Alc'hwez hizivaat :",
'config-localsettings-badkey' => "Direizh eo an alc'hwez merket ganeoc'h",
'config-upgrade-key-missing' => 'Kavet ez eus bet ur staliadur kent eus MediaWiki.
-Evit hizivaat ar staliadur-se, ouzhpennit al linenn da-heul e traoñ ho restr LocalSettings.php:
+Evit hizivaat ar staliadur-se, ouzhpennit al linenn da-heul e traoñ ho restr <code>LocalSettings.php</code>:
$1',
- 'config-localsettings-incomplete' => "Diglok e seblant bezañ ar restr LocalSettings.php zo anezhi dija.
+ 'config-localsettings-incomplete' => "Diglok e seblant bezañ ar restr <code>LocalSettings.php</code> zo anezhi dija.
An argemmenn $1 n'eo ket termenet.
-Kemmit LocalSettings.php evit ma vo termenet an argemmenn-se, ha klikit war « Kenderc'hel ».",
- 'config-localsettings-connection-error' => "C'hoarvezet ez eus ur fazi en ur gevreañ ouzh an diaz roadennoù oc'h implijout an arventennoù diferet e LocalSettings.php pe AdminSettings.php. Reizhit an arventennoù-se hag esaeit en-dro.
+Kemmit <code>LocalSettings.php</code> evit ma vo termenet an argemmenn-se, ha klikit war « {{int:Config-continue}} ».",
+ 'config-localsettings-connection-error' => "C'hoarvezet ez eus ur fazi en ur gevreañ ouzh an diaz roadennoù oc'h implijout an arventennoù diferet e <code>LocalSettings.php</code> pe <code>AdminSettings.php</code>. Reizhit an arventennoù-se hag esaeit en-dro.
$1",
'config-session-error' => "Fazi e-ser loc'hañ an dalc'h : $1",
@@ -2640,7 +2724,6 @@ Arabat cheñch anezho ma n'hoc'h eus ket ezhomm d'en ober.",
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => "Skoret eo ar reizhiadoù diaz titouroù da-heul gant MediaWiki :
$1
@@ -2650,12 +2733,10 @@ Ma ne welit ket amañ dindan ar reizhiad diaz titouroù a fell deoc'h ober ganti
'config-support-postgres' => "* Ur reizhiad diaz titouroù brudet ha digor eo $1. Gallout a ra ober evit MySQL ([http://www.php.net/manual/en/pgsql.installation.php Penaos kempunañ PHP gant skor PostgreSQL]). Gallout a ra bezañ un nebeud drein bihan enni ha n'eo ket erbedet he implijout en un endro produiñ.",
'config-support-sqlite' => "* $1 zo ur reizhiad diaz titouroù skañv skoret eus ar c'hentañ. ([http://www.php.net/manual/en/pdo.installation.php Penaos kempunañ PHP gant skor SQLite], implijout a ra PDO)",
'config-support-oracle' => '* $1 zo un diaz titouroù kenwerzhel. ([http://www.php.net/manual/en/oci8.installation.php Penaos kempunañ PHP gant skor OCI8])',
- 'config-support-ibm_db2' => '* Un diaz titouroù evit embregerezhioù kenwerzhel eo $1.',
'config-header-mysql' => 'Arventennoù MySQL',
'config-header-postgres' => 'Arventennoù PostgreSQL',
'config-header-sqlite' => 'Arventennoù SQLite',
'config-header-oracle' => 'Arventennoù Oracle',
- 'config-header-ibm_db2' => 'Arventennoù IBM DB2',
'config-invalid-db-type' => 'Direizh eo ar seurt diaz roadennoù',
'config-missing-db-name' => 'Ret eo deoc\'h merkañ un dalvoudenn evit "Anv an diaz titouroù"',
'config-missing-db-host' => 'Ret eo deoc\'h merkañ un dalvoudenn evit "Ostiz an diaz titouroù"',
@@ -2692,8 +2773,8 @@ Da hizivaat anezho da VediaWiki $1, klikañ war '''Kenderc'hel'''.",
'config-upgrade-done-no-regenerate' => 'Hizivadenn kaset da benn.
Gallout a rit [$1 kregiñ da implijout ho wiki].',
- 'config-regenerate' => 'Adgenel LocalSettings.php →',
- 'config-show-table-status' => "C'hwitet ar reked SHOW TABLE STATUS !",
+ 'config-regenerate' => 'Adgenel <code>LocalSettings.php</code> →',
+ 'config-show-table-status' => "C'hwitet ar reked <code>SHOW TABLE STATUS</code> !",
'config-unknown-collation' => "'''Diwallit :''' Emañ an diaz roadennoù o renkañ an traoù diouzh un urzh lizherennek dianav.",
'config-db-web-account' => 'Kont an diaz roadennoù evit ar voned Kenrouedad',
'config-db-web-help' => 'Diuzañ an anv implijer hag ar ger-tremen a vo implijet gant ar servijer web evit kevreañ ouzh ar servijer diaz roadennoù pa vez ar wiki o vont en-dro war ar pemdez.',
@@ -2740,7 +2821,7 @@ Gellout a rit tremen ar c'hefluniadur nevez ha staliañ ar wiki war-eeun.",
'config-optional-continue' => "Sevel muioc'h a goulennoù ouzhin.",
'config-optional-skip' => 'Aet on skuizh, staliañ ar wiki hepken.',
'config-profile' => 'Profil ar gwirioù implijer :',
- 'config-profile-wiki' => 'Wiki hengounel',
+ 'config-profile-wiki' => 'Wiki digor',
'config-profile-no-anon' => 'Krouidigezh ur gont ret',
'config-profile-fishbowl' => 'Embanner aotreet hepken',
'config-profile-private' => 'Wiki prevez',
@@ -2797,7 +2878,7 @@ Marteze e vo ezhomm kefluniañ pelloc'h met gallout a rit o gweredekaat bremañ.
'config-install-alreadydone' => "'''Diwallit''': Staliet hoc'h eus MediaWiki dija war a seblant hag emaoc'h o klask e staliañ c'hoazh.
Kit d'ar bajenn war-lerc'h, mar plij.",
'config-install-begin' => 'Pa vo bet pouezet ganeoc\'h war "{{int:config-continue}}" e krogo staliadur MediaWiki.
-Pouezit war Kent mar fell deoc\'h cheñch tra pe dra.',
+Pouezit war "{{int:config-back}}" mar fell deoc\'h cheñch tra pe dra.',
'config-install-step-done' => 'graet',
'config-install-step-failed' => "c'hwitet",
'config-install-extensions' => 'En ur gontañ an astennoù',
@@ -2825,16 +2906,18 @@ Gwiriit hag-eñ e c'hall an implijer « $1 » skrivañ er brastres « $2 ».",
'config-install-mainpage' => "O krouiñ ar bajenn bennañ gant un endalc'had dre ziouer",
'config-install-extension-tables' => 'O krouiñ taolennoù evit an astennoù gweredekaet',
'config-install-mainpage-failed' => "Ne c'haller ket ensoc'hañ ar bajenn bennañ: $1",
- 'config-download-localsettings' => 'Pellgargañ LocalSettings.php',
+ 'config-download-localsettings' => 'Pellgargañ <code>LocalSettings.php</code>',
'config-help' => 'skoazell',
+ 'config-nofile' => 'N\'eus ket bet gallet kavout ar restr "$1". Daoust ha dilamet eo bet ?',
'mainpagetext' => "'''Meziant MediaWiki staliet.'''",
'mainpagedocfooter' => "Sellit ouzh [//meta.wikimedia.org/wiki/Help:Contents Sturlevr an implijerien] evit gouzout hiroc'h war an doare da implijout ar meziant wiki.
== Kregiñ ganti ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Roll an arventennoù kefluniañ]
+* [//www.mediawiki.org/wiki/Manual:FAQ FAG MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Roll ar c'haozeadennoù diwar-benn dasparzhoù MediaWiki]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Lec'hiañ MediaWiki en ho yezh", # Fuzzy
);
/** Bosnian (bosanski)
@@ -2846,7 +2929,7 @@ $messages['bs'] = array(
'config-information' => 'Informacija',
'config-localsettings-upgrade' => 'Otkrivena je datoteka <code>LocalSettings.php</code>.
Da biste unaprijedili vaš softver, molimo vas upišite vrijednost od <code>$wgUpgradeKey</code> u okvir ispod.
-Naći ćete ga u LocalSettings.php.',
+Naći ćete ga u <code>LocalSettings.php</code>.',
'config-localsettings-key' => 'Ključ za nadgradnju:',
'config-session-error' => 'Greška pri pokretanju sesije: $1',
'config-no-session' => 'Vaši podaci sesije su izgubljeni!
@@ -2919,15 +3002,28 @@ Ovo '''nije preporučeno''' osim ako nemate problema s vašom wiki.",
== Početak ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista postavki]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki najčešće postavljana pitanja]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista E-Mail adresa MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista E-Mail adresa MediaWiki]', # Fuzzy
);
/** Catalan (català)
+ * @author Pitort
* @author පසිඳු කාවින්ද
*/
$messages['ca'] = array(
'config-page-language' => 'Llengua',
'config-page-name' => 'Nom',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-ns-generic' => 'Projecte',
+ 'config-admin-password' => 'Contrasenya:',
+ 'config-profile-wiki' => 'Wiki públic',
+ 'config-profile-private' => 'Wiki privat',
+ 'config-license-pd' => 'Domini públic',
+ 'config-upload-deleted' => 'Directori pels arxius suprimits:',
+ 'config-advanced-settings' => 'Configuració avançada',
+ 'config-extensions' => 'Extensions',
'mainpagetext' => "'''El programari del MediaWiki s'ha instaŀlat correctament.'''",
'mainpagedocfooter' => "Consulteu la [//meta.wikimedia.org/wiki/Help:Contents Guia d'Usuari] per a més informació sobre com utilitzar-lo.
@@ -2935,7 +3031,7 @@ $messages['ca'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Llista de característiques configurables]
* [//www.mediawiki.org/wiki/Manual:FAQ PMF del MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Llista de correu (''listserv'') per a anuncis del MediaWiki]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Llista de correu (''listserv'') per a anuncis del MediaWiki]", # Fuzzy
);
/** Chechen (нохчийн)
@@ -2966,15 +3062,20 @@ $messages['ceb'] = array(
/** Sorani Kurdish (کوردی)
* @author Asoxor
+ * @author Calak
*/
$messages['ckb'] = array(
+ 'config-wiki-language' => 'زمانی ویکی:',
+ 'config-page-language' => 'زمان',
+ 'config-page-name' => 'ناو',
'mainpagetext' => "'''میدیاویکی بە سەرکەوتوویی دامەزرا.'''",
- 'mainpagedocfooter' => 'پرس بکە بە [//meta.wikimedia.org/wiki/Help:Contents ڕێنوێنیی بەکارھێنەران] بۆ زانیاری سەبارەت بە بەکارھێنانی نەرمامێری ویکی.
+ 'mainpagedocfooter' => 'لە [//meta.wikimedia.org/wiki/Help:Contents ڕێنوێنیی بەکارھێنەران] بۆ زانیاری سەبارەت بە بەکارھێنانی نەرمامێری ویکی کەڵک وەربگرە.
== دەستپێکردن ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings لیستی ڕێکخستنەکان شێوەپێدان]
-* [//www.mediawiki.org/wiki/Manual:FAQ پرسیارە دوپاتکراوەکانی میدیاویکی (MediaWiki FAQ)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce لیستی ئیمەیلی وەشانەکانی میدیاویکی]',
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings پێرستی ڕێکخستنەکانی شێوەپێدان]
+* [//www.mediawiki.org/wiki/Manual:FAQ پرسیارە دووپاتکراوەکانی میدیاویکی (MediaWiki FAQ)]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce پێرستی ئیمەیلی وەشانەکانی میدیاویکی]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources خۆماڵیکردنی ویکیمیدیا بۆ زمانەکەت]',
);
/** Capiznon (Capiceño)
@@ -3018,25 +3119,26 @@ $messages['crh-latn'] = array(
/** Czech (česky)
* @author Danny B.
* @author Mormegil
+ * @author 아라
*/
$messages['cs'] = array(
'config-desc' => 'Instalační program pro MediaWiki',
'config-title' => 'Instalace MediaWiki $1',
'config-information' => 'Informace',
'config-localsettings-upgrade' => 'Byl nalezen soubor <code>LocalSettings.php</code>.
-Pokud chcete stávající instalaci aktualizovat, zadejte hodnotu <code>$wgUpgradeKey</code>, kterou naleznete v souboru LocalSettings.php, do následujícího rámečku.',
- 'config-localsettings-cli-upgrade' => 'Byl detekován soubor <code>LocalSettings.php</code>
+Pokud chcete stávající instalaci aktualizovat, zadejte hodnotu <code>$wgUpgradeKey</code>, kterou naleznete v souboru <code>LocalSettings.php</code>, do následujícího rámečku.',
+ 'config-localsettings-cli-upgrade' => 'Byl detekován soubor <code><code>LocalSettings.php</code></code>
Pro aktualizaci spusťte místo instalace skript <code>update.php</code>.',
'config-localsettings-key' => 'Klíč pro aktualizaci:',
'config-localsettings-badkey' => 'Zadaný klíč je nesprávný.',
'config-upgrade-key-missing' => 'Byla detekována existující instalace MediaWiki.
-Pokud ji chcete aktualizovat, přidejte následující řádku na konec souboru LocalSettings.php:
+Pokud ji chcete aktualizovat, přidejte následující řádku na konec souboru <code>LocalSettings.php</code>:
$1',
- 'config-localsettings-incomplete' => 'Existující soubor LocalSettings.php vypadá neúplný.
+ 'config-localsettings-incomplete' => 'Existující soubor <code>LocalSettings.php</code> vypadá neúplný.
Není nastavena proměnná $1.
-Upravte soubor LocalSettings.php tak, aby tuto proměnnou obsahoval, a klikněte na „Pokračovat“.',
- 'config-localsettings-connection-error' => 'Při připojování k databázi s využitím nastavení uvedených v LocalSettings.php nebo AdminSettings.php došlo k chybě. Opravte tato nastavení a zkuste to znovu.
+Upravte soubor <code>LocalSettings.php</code> tak, aby tuto proměnnou obsahoval, a klikněte na „{{int:Config-continue}}“.',
+ 'config-localsettings-connection-error' => 'Při připojování k databázi s využitím nastavení uvedených v <code>LocalSettings.php</code> nebo <code>AdminSettings.php</code> došlo k chybě. Opravte tato nastavení a zkuste to znovu.
$1',
'config-session-error' => 'Nepodařilo se inicializovat relaci: $1',
@@ -3168,7 +3270,9 @@ Instalace přerušena.',
'config-using531' => 'MediaWiki nelze používat na PHP $1 kvůli chybě při předávání parametrů odkazem do <code>__call()</code>.
Pro vyřešení upgradujte na PHP 5.3.2 nebo vyšší nebo downgradujte na PHP 5.3.0.
Instalace přerušena.',
- 'config-suhosin-max-value-length' => 'Je nainstalován Suhosin, který omezuje délku parametrů GET na $1 bajtů. Komponenta ResourceLoader z MediaWiki dokáže s tímto omezením pracovat, ale sníží to výkon. Pokud to je alespoň trochu možné, měli byste v php.ini nastavit suhosin.get.max_value_length na 1024 nebo vyšší a na stejnou hodnotu nastavit v LocalSettings.php proměnnou $wgResourceLoaderMaxQueryLength.',
+ 'config-suhosin-max-value-length' => 'Je nainstalován Suhosin, který omezuje délku parametrů GET na $1 bajtů.
+Komponenta ResourceLoader z MediaWiki dokáže s tímto omezením pracovat, ale sníží to výkon.
+Pokud to je alespoň trochu možné, měli byste v <code>php.ini</code> nastavit <code>suhosin.get.max_value_length</code> na 1024 nebo vyšší a na stejnou hodnotu nastavit v <code>LocalSettings.php</code> proměnnou <code>$wgResourceLoaderMaxQueryLength</code>.',
'config-db-type' => 'Typ databáze:',
'config-db-host' => 'Databázový server:',
'config-db-host-help' => 'Pokud je váš databázový server na jiném počítači, zadejte zde jméno stroje nebo IP adresu.
@@ -3254,7 +3358,7 @@ Pokud v nabídce níže nevidíte databázový systém, který chcete použít,
'config-support-postgres' => '* $1 je populární open-source databázový systém používaný jako alternativa k MySQL ([http://www.php.net/manual/en/pgsql.installation.php jak přeložit PHP s podporou PostgreSQL]). Mohou se vyskytnout ještě nějaké menší chyby, použití v produkčním prostředí se nedoporučuje.',
'config-support-sqlite' => '* $1 je velmi dobře podporovaný lehký databázový systém. ([http://www.php.net/manual/en/pdo.installation.php Jak přeložit PHP s podporou SQLite], používá PDO)',
'config-support-oracle' => '* $1 je komerční podniková databáze. ([http://www.php.net/manual/en/oci8.installation.php Jak přeložit PHP s podporou OCI8])',
- 'config-support-ibm_db2' => '* $1 je komerční podniková databáze.',
+ 'config-support-ibm_db2' => '* $1 je komerční podniková databáze. ([http://www.php.net/manual/en/ibm-db2.installation.php Jak přeložit PHP s podporou IBM DB2])',
'config-header-mysql' => 'Nastavení MySQL',
'config-header-postgres' => 'Nastavení PostgreSQL',
'config-header-sqlite' => 'Nastavení SQLite',
@@ -3321,8 +3425,8 @@ To se ale '''nedoporučuje''', pokud s wiki nemáte problémy.",
'config-upgrade-done-no-regenerate' => 'Aktualizace byla dokončena.
Svou wiki teď můžete [$1 začít používat].',
- 'config-regenerate' => 'Přegenerovat LocalSettings.php →',
- 'config-show-table-status' => 'Dotaz SHOW TABLE STATUS se nezdařil!',
+ 'config-regenerate' => 'Přegenerovat <code>LocalSettings.php</code> →',
+ 'config-show-table-status' => 'Dotaz <code>SHOW TABLE STATUS</code> se nezdařil!',
'config-unknown-collation' => "'''Upozornění:''' Databáze používá nerozpoznané řazení.",
'config-db-web-account' => 'Databázový účet pro webový přístup',
'config-db-web-help' => 'Zvolte uživatelské jméno a heslo, které bude webový server používat pro připojení k databázovému serveru při běžném provozu wiki.',
@@ -3394,7 +3498,7 @@ Zbývající konfiguraci už můžete přeskočit a nainstalovat wiki hned teď.
'config-optional-continue' => 'Ptejte se mě dál.',
'config-optional-skip' => 'Už mě to nudí, prostě nainstalujte wiki.',
'config-profile' => 'Profil uživatelských práv:',
- 'config-profile-wiki' => 'Tradiční wiki',
+ 'config-profile-wiki' => 'Otevřená wiki',
'config-profile-no-anon' => 'Vyžadována registrace uživatelů',
'config-profile-fishbowl' => 'Editace jen pro vybrané',
'config-profile-private' => 'Soukromá wiki',
@@ -3404,7 +3508,7 @@ V MediaWiki můžete snadno kontrolovat poslední změny a vracet zpět libovoln
Mnoho lidí však zjistilo, že je MediaWiki užitečné v širokém spektru rolí a někdy není snadné všechny přesvědčit o výhodách wikizvyklostí.
Takže si můžete vybrat.
-'''{{int:config-profile-wiki}}''' dovoluje editovat všem, aniž by se museli přihlašovat.
+Model '''{{int:config-profile-wiki}}''' dovoluje editovat všem, aniž by se museli přihlašovat.
Na wiki, kde je '''{{int:config-profile-no-anon}}''', se lépe řídí zodpovědnost, ale může to odradit náhodné přispěvatele.
Profil '''{{int:config-profile-fishbowl}}''' umožňuje schváleným uživatelům editovat, ale veřejnost si může stránky prohlížet včetně jejich historie.
@@ -3495,7 +3599,7 @@ Mohou vyžadovat dodatečnou konfiguraci, ale teď je můžete povolit.',
'config-install-alreadydone' => "'''Upozornění:''' Vypadá to, že jste MediaWiki již nainstalovali a teď se o to pokoušíte znovu.
Pokračujte na další stránku.",
'config-install-begin' => 'Stisknutím „{{int:config-continue}}“ spustíte instalaci MediaWiki.
-Pokud ještě chcete udělat nějaké změny, stiskněte tlačítko zpět.',
+Pokud ještě chcete udělat nějaké změny, stiskněte „{{int:config-back}}“.',
'config-install-step-done' => 'hotovo',
'config-install-step-failed' => 'selhaly',
'config-install-extensions' => 'Vkládají se rozšíření',
@@ -3551,7 +3655,7 @@ $3
'''Poznámka''': Pokud to neuděláte hned, tento vygenerovaný konfigurační soubor nebude později dostupný, pokud instalaci opustíte, aniž byste si ho stáhli.
Až to dokončíte, můžete '''[$2 vstoupit do své wiki]'''.",
- 'config-download-localsettings' => 'Stáhnout LocalSettings.php',
+ 'config-download-localsettings' => 'Stáhnout <code>LocalSettings.php</code>',
'config-help' => 'nápověda',
'config-nofile' => 'Soubor „$1“ nelze nalézt. Byl smazán?',
'mainpagetext' => "'''MediaWiki byla úspěšně nainstalována.'''",
@@ -3561,7 +3665,8 @@ Až to dokončíte, můžete '''[$2 vstoupit do své wiki]'''.",
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Nastavení konfigurace]
* [//www.mediawiki.org/wiki/Manual:FAQ Často kladené otázky o MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce E-mailová konference oznámení MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce E-mailová konference oznámení MediaWiki]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Překlad MediaWiki do vašeho jazyka]',
);
/** Kashubian (kaszëbsczi)
@@ -3614,8 +3719,11 @@ $messages['da'] = array(
* @author LWChris
* @author Metalhead64
* @author Purodha
+ * @author Rillke
* @author The Evil IP address
* @author Umherirrender
+ * @author Wikinaut
+ * @author 아라
*/
$messages['de'] = array(
'config-desc' => 'Das MediaWiki-Installationsprogramm',
@@ -3623,19 +3731,19 @@ $messages['de'] = array(
'config-information' => 'Informationen',
'config-localsettings-upgrade' => 'Eine Datei <code>LocalSettings.php</code> wurde gefunden.
Um die vorhandene Installation aktualisieren zu können, muss der Wert des Parameters <code>$wgUpgradeKey</code> im folgenden Eingabefeld angegeben werden.
-Der Parameterwert befindet sich in der Datei LocalSettings.php.',
- 'config-localsettings-cli-upgrade' => 'Eine Datei <code>LocalSettings.php</code> wurde gefunden.
+Der Parameterwert befindet sich in der Datei <code>LocalSettings.php</code>.',
+ 'config-localsettings-cli-upgrade' => 'Eine Datei <code><code>LocalSettings.php</code></code> wurde gefunden.
Um die vorhandene Installation zu aktualisieren, muss die Datei <code>update.php</code> ausgeführt werden.',
'config-localsettings-key' => 'Aktualisierungsschlüssel:',
'config-localsettings-badkey' => 'Der angegebene Aktualisierungsschlüssel ist falsch.',
'config-upgrade-key-missing' => 'Eine MediaWiki-Installation wurde gefunden.
-Um die vorhandene Installation aktualisieren zu können, muss die unten angegebene Codezeile in die Datei LocalSettings.php an deren Ende eingefügt werden:
+Um die vorhandene Installation aktualisieren zu können, muss die unten angegebene Codezeile in die Datei <code>LocalSettings.php</code> an deren Ende eingefügt werden:
$1',
- 'config-localsettings-incomplete' => 'Die vorhandene Datei LocalSettings.php scheint unvollständig zu sein.
+ 'config-localsettings-incomplete' => 'Die vorhandene Datei <code>LocalSettings.php</code> scheint unvollständig zu sein.
Die Variable <code>$1</code> wurde nicht definiert.
-Die Datei LocalSettings.php muss entsprechend geändert werden, so dass sie definiert ist. Klicke danach auf „Weiter“.',
- 'config-localsettings-connection-error' => 'Beim Verbindungsversuch zur Datenbank ist, unter Verwendung der in den Dateien LocalSettings.php oder AdminSettings.php hinterlegten Einstellungen, ein Fehler aufgetreten. Diese Einstellungen müssen korrigiert werden. Danach kann ein erneuter Versuch unternommen werden.
+Die Datei <code>LocalSettings.php</code> muss entsprechend geändert werden, so dass sie definiert ist. Klicke danach auf „{{int:Config-continue}}“.',
+ 'config-localsettings-connection-error' => 'Beim Verbindungsversuch zur Datenbank ist, unter Verwendung der in den Dateien <code>LocalSettings.php</code> oder <code>AdminSettings.php</code> hinterlegten Einstellungen, ein Fehler aufgetreten. Diese Einstellungen müssen korrigiert werden. Danach kann ein erneuter Versuch unternommen werden.
$1',
'config-session-error' => 'Fehler beim Starten der Sitzung: $1',
@@ -3666,7 +3774,7 @@ Die Datei <code>php.ini</code> muss geprüft und es muss dabei sichergestellt we
'config-page-copying' => 'Kopie der Lizenz',
'config-page-upgradedoc' => 'Aktualisiere',
'config-page-existingwiki' => 'Vorhandenes Wiki',
- 'config-help-restart' => 'Sollen alle bereits eingegebene Daten gelöscht und der Installationsvorgang erneut gestartet werden?',
+ 'config-help-restart' => 'Sollen alle bereits eingegebenen Daten gelöscht und der Installationsvorgang erneut gestartet werden?',
'config-restart' => 'Ja, erneut starten',
'config-welcome' => '=== Prüfung der Installationsumgebung ===
Die Basisprüfungen werden durchgeführt, um festzustellen, ob die Installationsumgebung für die Installation von MediaWiki geeignet ist.
@@ -3766,7 +3874,10 @@ PHP muss auf Version 5.2.9 oder später sowie libxml2 auf die Version 2.7.3 oder
'config-using531' => 'MediaWiki kann nicht zusammen mit PHP $1 verwendet werden. Grund hierfür ist ein Fehler im Zusammenhang mit den Verweisparametern zu <code>__call()</code>.
PHP muss auf Version 5.3.2 oder höher oder 5.3.0 oder niedriger aktualisiert werden, um das Problem zu beheben.
Die Installation wurde abgebrochen.',
- 'config-suhosin-max-value-length' => 'Suhosin ist installiert und beschränkt die Länge des GET-Parameters auf $1 Bytes. Der ResouceLoader von MediaWiki wird zwar unter diesen Bedingungen funktionieren, allerdings nur mit verminderter Leistungsfähigkeit. Sofern möglich sollte der Parameter <code>suhosin.get.max_value_length</code> in der Datei php.ini auf 1024 oder höher festgelegt werden. Gleichzeitig muss der Parameter <code>$wgResourceLoaderMaxQueryLength</code> in der Datei LocalSettings.php auf den selben Wert eingestellt werden.',
+ 'config-suhosin-max-value-length' => 'Suhosin ist installiert und beschränkt die Länge des GET-Parameters auf $1 Bytes.
+Der ResouceLoader von MediaWiki wird zwar unter diesen Bedingungen funktionieren, allerdings nur mit verminderter Leistungsfähigkeit.
+Sofern möglich sollte der Parameter <code>suhosin.get.max_value_length</code> in der Datei <code>php.ini</code> auf 1024 oder höher festgelegt werden.
+Gleichzeitig muss der Parameter <code>$wgResourceLoaderMaxQueryLength</code> in der Datei <code>LocalSettings.php</code> auf den selben Wert eingestellt werden.',
'config-db-type' => 'Datenbanksystem:',
'config-db-host' => 'Datenbankserver:',
'config-db-host-help' => 'Sofern sich die Datenbank auf einem anderen Server befindet, ist hier der Servername oder die entsprechende IP-Adresse anzugeben.
@@ -3850,7 +3961,7 @@ Sofern nicht das Datenbanksystem angezeigt wird, das verwendet werden soll, gibt
'config-support-postgres' => '* $1 ist ein beliebtes Open-Source-Datenbanksystem und eine Alternative zu MySQL ([http://www.php.net/manual/de/pgsql.installation.php Anleitung zur Kompilierung von PHP mit PostgreSQL-Unterstützung]). Es gibt allerdings einige kleinere Implementierungsfehler, so dass von der Nutzung in einer Produktivumgebung abgeraten wird.',
'config-support-sqlite' => '* $1 ist ein verschlanktes Datenbanksystem, das auch gut unterstützt wird ([http://www.php.net/manual/de/pdo.installation.php Anleitung zur Kompilierung von PHP mit SQLite-Unterstützung], verwendet PHP Data Objects (PDO))',
'config-support-oracle' => '* $1 ist eine kommerzielle Unternehmensdatenbank ([http://www.php.net/manual/en/oci8.installation.php Anleitung zur Kompilierung von PHP mit OCI8-Unterstützung (en)])',
- 'config-support-ibm_db2' => '* $1 ist eine kommerzielle Unternehmensdatenbank',
+ 'config-support-ibm_db2' => '* $1 ist eine kommerzielle Unternehmensdatenbank ([http://www.php.net/manual/en/ibm-db2.installation.php PHP mit IBM-DB2-Support kompilieren])',
'config-header-mysql' => 'MySQL-Einstellungen',
'config-header-postgres' => 'PostgreSQL-Einstellungen',
'config-header-sqlite' => 'SQLite-Einstellungen',
@@ -3918,8 +4029,8 @@ Dies wird '''nicht empfohlen''', es sei denn, es treten Probleme mit dem Wiki au
'config-upgrade-done-no-regenerate' => 'Die Aktualisierung ist abgeschlossen.
Das Wiki kann nun [$1 genutzt werden].',
- 'config-regenerate' => 'LocalSettings.php neu erstellen →',
- 'config-show-table-status' => 'Die Abfrage SHOW TABLE STATUS ist gescheitert!',
+ 'config-regenerate' => '<code>LocalSettings.php</code> neu erstellen →',
+ 'config-show-table-status' => 'Die Abfrage <code>SHOW TABLE STATUS</code> ist gescheitert!',
'config-unknown-collation' => "'''Warnung:''' Die Datenbank nutzt eine unbekannte Kollation.",
'config-db-web-account' => 'Datenbankkonto für den Webzugriff',
'config-db-web-help' => 'Bitte Benutzernamen und Passwort auswählen, die der Webserver während des Normalbetriebes dazu verwenden soll, eine Verbindung zum Datenbankserver herzustellen.',
@@ -3930,10 +4041,10 @@ Das hier angegebene Datenbankkonto muss daher bereits vorhanden sein.',
'config-mysql-engine' => 'Speicher-Engine:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''Warnung:''' Es wurde MyISAM als Speicher-Engine für MySQL ausgewählt, die aus folgenden Gründen nicht die für den Einsatz mit MediaWiki empfohlene ist:
-* sie unterstützt aufgrund von Tabellensperrungen kaum die nebenläufige Ausführung von Aktionen
-* sie ist anfälliger für Datenprobleme
-* sie wird von MediaWiki nicht immer adäquat unterstützt
+ 'config-mysql-myisam-dep' => "'''Warnung:''' Es wurde MyISAM als Speicher-Engine für MySQL ausgewählt, die aus folgenden Gründen nicht für den Einsatz mit MediaWiki empfohlen ist:
+* Sie unterstützt aufgrund von Tabellensperrungen kaum die nebenläufige Ausführung von Aktionen.
+* Sie ist anfälliger für Datenprobleme.
+* Sie wird von MediaWiki nicht immer adäquat unterstützt.
Sofern die vorhandene MySQL-Installation die Speicher-Engine InnoDB unterstützt, wird deren Verwendung eindringlich empfohlen.
Sofern sie sie nicht unterstützt, sollte eine entsprechende Aktualisierung nunmehr Erwägung gezogen werden.",
@@ -4001,7 +4112,7 @@ Mit MediaWiki ist es einfach die letzten Änderungen nachzuvollziehen und unbrau
Allerdings finden etliche Menschen Wikis auch mit anderen Bearbeitungskonzepten sinnvoll. Manchmal ist es zudem nicht einfach alle Beteiligten von den Vorteilen des „Wiki-Prinzips” zu überzeugen. Darum ist diese Auswahl möglich.
-Ein '''{{int:config-profile-wiki}}''' ermöglicht es jedermann, sogar ohne über ein Benutzerkonto zu verfügen, Bearbeitungen vorzunehmen.
+Das Modell „'''{{int:config-profile-wiki}}'''“ ermöglicht es jedermann, sogar ohne über ein Benutzerkonto zu verfügen, Bearbeitungen vorzunehmen.
Ein Wiki bei dem die '''{{int:config-profile-no-anon}}''' ist, fordert von den Benutzern eine höhere Verantwortung für ihre Bearbeitungen ein, könnte allerdings Personen abschrecken, die nur gelegentlich Bearbeitungen vornehmen wollen. Ein Wiki für '''{{int:config-profile-fishbowl}}''' gestattet es nur bestimmten Benutzern, Bearbeitungen vorzunehmen. Allerdings kann dabei die Allgemeinheit die Seiten immer noch betrachten und Änderungen nachvollziehen. Ein '''{{int:config-profile-private}}''' gestattet es nur ausgewählten Benutzern, Seiten zu betrachten sowie zu bearbeiten.
Komplexere Konzepte zur Zugriffssteuerung können erst nach abgeschlossenem Installationsvorgang eingerichtet werden. Hierzu gibt es weitere Informationen auf der Website mit der [//www.mediawiki.org/wiki/Manual:User_rights entsprechenden Anleitung].",
@@ -4089,7 +4200,7 @@ Es könnten zusätzliche Konfigurierungen zu einzelnen Erweiterungen erforderlic
'config-install-alreadydone' => "'''Warnung:''' Es wurde eine vorhandene MediaWiki-Installation gefunden.
Es muss daher mit den nächsten Seite weitergemacht werden.",
'config-install-begin' => 'Durch Drücken von „{{int:config-continue}}“ wird die Installation von MediaWiki gestartet.
-Sofern Änderungen vorgenommen werden sollen, kann man auf „← Zurück“ klicken.',
+Sofern Änderungen vorgenommen werden sollen, kann man auf „{{int:config-back}}“ klicken.',
'config-install-step-done' => 'erledigt',
'config-install-step-failed' => 'gescheitert',
'config-install-extensions' => 'Programmerweiterungen',
@@ -4145,7 +4256,7 @@ $3
'''Hinweis:''' Die Konfigurationsdatei sollte jetzt unbedingt heruntergeladen werden. Sie wird nach Beenden des Installationsprogramms, nicht mehr zur Verfügung stehen.
Sobald alles erledigt wurde, kann auf das '''[$2 Wiki zugegriffen werden]'''. Wir wünschen viel Spaß und Erfolg mit dem Wiki.",
- 'config-download-localsettings' => 'LocalSettings.php herunterladen',
+ 'config-download-localsettings' => '<code>LocalSettings.php</code> herunterladen',
'config-help' => 'Hilfe',
'config-nofile' => 'Die Datei „$1“ konnte nicht gefunden werden. Wurde sie gelöscht?',
'mainpagetext' => "'''MediaWiki wurde erfolgreich installiert.'''",
@@ -4155,7 +4266,8 @@ Sobald alles erledigt wurde, kann auf das '''[$2 Wiki zugegriffen werden]'''. Wi
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Liste der Konfigurationsvariablen]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki-FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailingliste neuer MediaWiki-Versionen]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailingliste neuer MediaWiki-Versionen]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Lokalisiere MediaWiki für deine Sprache]',
);
/** German (formal address) (Deutsch (Sie-Form)‎)
@@ -4175,6 +4287,8 @@ $messages['de-formal'] = array(
* @author Mirzali
*/
$messages['diq'] = array(
+ 'config-title' => 'MediaWiki $1 sazkerdış',
+ 'config-information' => 'Melumat',
'config-your-language' => 'Zıwanê şıma:',
'config-wiki-language' => 'Wiki zıwan:',
'config-back' => '← Peyser',
@@ -4185,7 +4299,10 @@ $messages['diq'] = array(
'config-page-name' => 'Name',
'config-page-options' => 'Weçinegi',
'config-page-install' => 'Barine',
+ 'config-page-complete' => 'Temamyayo',
'config-page-readme' => 'Mı bıwane',
+ 'config-page-copying' => 'Kopyayeno',
+ 'config-page-upgradedoc' => 'Berzkerdış',
'config-restart' => 'E, fına dest pekê',
'config-sidebar' => "* [//www.mediawiki.org MediaWiki keye]
* [//www.mediawiki.org/wiki/Help:Contents User's Şınasiye]
@@ -4214,11 +4331,14 @@ $messages['diq'] = array(
'config-admin-name' => 'Namey şıma:',
'config-admin-password' => 'Parola:',
'config-admin-password-confirm' => 'Fına parola:',
+ 'config-admin-email' => 'Adresa e-postey:',
+ 'config-profile-private' => 'Bexse wiki',
'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
'config-license-cc-by' => 'Creative Commons Attribution',
'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
'config-license-pd' => 'Malê Şari',
'config-extensions' => 'Olekeni',
+ 'config-help' => 'peşti',
'mainpagetext' => "'''MediaWiki vıst ra ser, vıraziya.'''",
'mainpagedocfooter' => 'Seba gurenayış u eyarkerdışê Wiki-Softwarey [//meta.wikimedia.org/wiki/Help:Contents İdarê karberi] de mıracaet ke.
@@ -4230,16 +4350,20 @@ $messages['diq'] = array(
);
/** Lower Sorbian (dolnoserbski)
+ * @author Michawiki
*/
$messages['dsb'] = array(
'mainpagetext' => "'''MediaWiki jo se wuspěšnje instalěrowało.'''",
'mainpagedocfooter' => "Pomoc pśi wužywanju softwary wiki namakajoš pód [//meta.wikimedia.org/wiki/Help:Contents User's Guide].
+Pomoc pśi wužywanju softwary wiki namakajoš pód [//meta.wikimedia.org/wiki/Help:Contents User's Guide].
+
== Na zachopjenje ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Konfiguracija lisćiny połoženjow]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ (pšašanja a wótegrona)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lisćina e-mailowych nakładow MediaWiki]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lisćina e-mailowych nakładow MediaWiki]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources MediaWiki za twóju rěc lokalizěrowaś]",
);
/** Central Dusun (Dusun Bundu-liwan)
@@ -4256,10 +4380,86 @@ $messages['dtp'] = array(
);
/** Greek (Ελληνικά)
+ * @author Glavkos
+ * @author Protnet
+ * @author ZaDiak
*/
$messages['el'] = array(
+ 'config-desc' => 'Το πρόγραμμα εγκατάστασης για το MediaWiki',
+ 'config-title' => 'Εγκατάσταση MediaWiki $1',
+ 'config-information' => 'Πληροφορίες',
+ 'config-your-language' => 'Η γλώσσα σας:',
+ 'config-wiki-language' => 'Γλώσσα του wiki:',
+ 'config-back' => '← Πίσω',
+ 'config-continue' => 'Συνέχεια →',
+ 'config-page-language' => 'Γλώσσα',
+ 'config-page-welcome' => 'Καλώς ήλθατε στο MediaWiki!',
+ 'config-page-name' => 'Όνομα',
+ 'config-page-options' => 'Επιλογές',
+ 'config-page-install' => 'Εγκατάσταση',
+ 'config-page-complete' => 'Ολοκληρώθηκε!',
+ 'config-page-restart' => 'Επανεκκίνηση εγκατάστασης',
+ 'config-page-copying' => 'Αντιγραφή',
+ 'config-page-upgradedoc' => 'Αναβάθμιση',
+ 'config-page-existingwiki' => 'Υπάρχον βίκι',
+ 'config-restart' => 'Ναι, κάντε επανεκκίνηση',
+ 'config-env-php' => 'H PHP $1 είναι εγκατεστημένη.',
+ 'config-db-type' => 'Τύπος βάσης δεδομένων:',
+ 'config-db-host' => 'Φιλοξενία βάσης δεδομένων:',
+ 'config-db-wiki-settings' => 'Αναγνώριση αυτού του βίκι',
+ 'config-db-name' => 'Όνομα βάσης δεδομένων:',
+ 'config-db-install-account' => 'Λογαριασμός χρήστη για την εγκατάσταση',
+ 'config-db-username' => 'Όνομα χρήστη βάσης δεδομένων:',
+ 'config-db-password' => 'Κωδικός πρόσβασης βάσης δεδομένων:',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 δυαδικό',
+ 'config-header-mysql' => 'Ρυθμίσεις MySQL',
+ 'config-header-postgres' => 'Ρυθμίσεις PostgreSQL',
+ 'config-header-sqlite' => 'Ρυθμίσεις SQLite',
+ 'config-header-oracle' => 'Ρυθμίσεις Oracle',
+ 'config-header-ibm_db2' => 'Ρυθμίσεις IBM DB2',
+ 'config-invalid-db-type' => 'Μη έγκυρος τύπος βάσης δεδομένων',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-site-name' => 'Όνομα του βίκι:',
+ 'config-site-name-blank' => 'Εισαγάγετε όνομα ιστοχώρου.',
+ 'config-project-namespace' => 'Ονοματοχώρος εγχειρήματος:',
+ 'config-ns-generic' => 'Εγχείρημα',
+ 'config-ns-site-name' => 'Ίδιο με το όνομα του wiki: $1',
+ 'config-ns-other' => 'Άλλο (προσδιορίστε)',
+ 'config-admin-box' => 'Λογαριασμός διαχειριστή',
+ 'config-admin-name' => 'Το όνομά σας:',
+ 'config-admin-password' => 'Κωδικός πρόσβασης:',
+ 'config-admin-password-confirm' => 'Επανάληψη κωδικού πρόσβασης:',
+ 'config-admin-email' => 'Διεύθυνση ηλεκτρονικού ταχυδρομείου:',
+ 'config-optional-continue' => 'Να ερωτηθώ περισσότερες ερωτήσεις.',
+ 'config-profile-wiki' => 'Παραδοσιακό wiki', # Fuzzy
+ 'config-profile-no-anon' => 'Απαιτείται η δημιουργία λογαριασμού',
+ 'config-profile-private' => 'Ιδιωτικό wiki',
+ 'config-email-settings' => 'Ρυθμίσεις ηλεκτρονικού ταχυδρομείου',
+ 'config-upload-settings' => 'Ανέβασμα εικόνων και άλλων αρχείων',
+ 'config-upload-enable' => 'Ενεργοποιήστε το ανέβασμα αρχείων',
+ 'config-logo' => 'Διεύθυνση URL λογότυπου:',
+ 'config-cc-again' => 'Επιλέξτε ξανά...',
+ 'config-extensions' => 'Επεκτάσεις',
+ 'config-install-step-done' => 'έγινε',
+ 'config-install-step-failed' => 'απέτυχε',
+ 'config-help' => 'βοήθεια',
'mainpagetext' => "'''To λογισμικό MediaWiki εγκαταστάθηκε με επιτυχία.'''",
- 'mainpagedocfooter' => 'Περισσότερες πληροφορίες σχετικά με τη χρήση και με τη ρύθμιση παραμέτρων θα βρείτε στους συνδέσμους: [//meta.wikimedia.org/wiki/MediaWiki_localisation Οδηγίες για τροποποίηση του περιβάλλοντος εργασίας] και [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Εγχειρίδιο χρήστη].',
+ 'mainpagedocfooter' => 'Περισσότερες πληροφορίες σχετικά με τη χρήση και με τη ρύθμιση παραμέτρων θα βρείτε στους συνδέσμους: [//meta.wikimedia.org/wiki/MediaWiki_localisation Οδηγίες για τροποποίηση του περιβάλλοντος εργασίας] και [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Εγχειρίδιο χρήστη].', # Fuzzy
+);
+
+/** British English (British English)
+ * @author Shirayuki
+ */
+$messages['en-gb'] = array(
+ 'config-unicode-using-utf8' => "Using Brion Vibber's utf8_normalize.so for Unicode normalisation.",
+ 'config-unicode-using-intl' => 'Using the [http://pecl.php.net/intl intl PECL extension] for Unicode normalisation.',
+ 'config-unicode-pure-php-warning' => "'''Warning:''' The [http://pecl.php.net/intl intl PECL extension] is not available to handle Unicode normalisation, falling back to slow pure-PHP implementation.
+If you run a high-traffic site, you should read a little on [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode normalisation].",
+ 'config-unicode-update-warning' => "'''Warning:''' The installed version of the Unicode normalisation wrapper uses an older version of [http://site.icu-project.org/ the ICU project's] library.
+You should [//www.mediawiki.org/wiki/Unicode_normalization_considerations upgrade] if you are at all concerned about using Unicode.",
+ 'config-unknown-collation' => "'''Warning:''' Database is using unrecognised collation.",
+ 'config-profile-fishbowl' => 'Authorised editors only',
+ 'config-install-stats' => 'Initialising statistics',
);
/** Esperanto (Esperanto)
@@ -4297,6 +4497,7 @@ $messages['eo'] = array(
* @author Sanbec
* @author Translationista
* @author Vivaelcelta
+ * @author 아라
*/
$messages['es'] = array(
'config-desc' => 'El instalador para MediaWiki',
@@ -4304,19 +4505,19 @@ $messages['es'] = array(
'config-information' => 'Información',
'config-localsettings-upgrade' => 'Se ha encontrado un archivo <code>LocalSettings.php</code>.
Para actualizar esta instalación, por favor ingresa el valor de <code>$wgUpgradeKey</code> en el cuadro de abajo.
-Lo encontrarás en LocalSettings.php.',
- 'config-localsettings-cli-upgrade' => 'Se ha detectado un archivo LocalSettings.php.
-Para actualizar la instalación, vuelva a ejecutar update.php',
+Lo encontrarás en <code>LocalSettings.php</code>.',
+ 'config-localsettings-cli-upgrade' => 'Se ha detectado un archivo <code>LocalSettings.php</code>.
+Para actualizar la instalación, vuelva a ejecutar <code>update.php</code>',
'config-localsettings-key' => 'Clave de actualización:',
'config-localsettings-badkey' => 'La clave proporcionada es incorrecta.',
'config-upgrade-key-missing' => 'Se ha detectado una instalación existente de MediaWiki.
-Para actualizar la instalación, por favor, ponga la siguiente línea al final de su archivo LocalSettings.php:
+Para actualizar la instalación, por favor, ponga la siguiente línea al final de su archivo <code>LocalSettings.php</code>:
$1',
- 'config-localsettings-incomplete' => 'El archivo LocalSettings.php existente parece estar incompleto.
+ 'config-localsettings-incomplete' => 'El archivo <code>LocalSettings.php</code> existente parece estar incompleto.
La variable $1 no está definida.
-Cambie el archivo LocalSettings.php para que esta variable quede establecida y haga clic en "Continuar".',
- 'config-localsettings-connection-error' => 'Se detectó un error al conectarse a la base de datos utilizando la configuración especificada en los archivos LocalSettings.php o AdminSettings.php. Corrija estas opciones y vuelva a intentarlo.
+Cambie el archivo <code>LocalSettings.php</code> para que esta variable quede establecida y haga clic en "{{int:Config-continue}}".',
+ 'config-localsettings-connection-error' => 'Se detectó un error al conectarse a la base de datos utilizando la configuración especificada en los archivos <code>LocalSettings.php</code> o <code>AdminSettings.php</code>. Corrija estas opciones y vuelva a intentarlo.
$1',
'config-session-error' => 'Error comenzando sesión: $1',
@@ -4449,7 +4650,9 @@ Instalación anulada.',
'config-using531' => 'MediaWiki no puede utilizarse con PHP $1 debido a un error con los parámetros de referencia para <code>__call()</code> .
Actualice el sistema a PHP 5.3.2 o superior, o vuelva a la versión PHP 5.3.0 para resolver este problema.
Instalación anulada.',
- 'config-suhosin-max-value-length' => 'Suhosin está instalado y limita la longitud del parámetro GET a $1 bytes. El componente ResourceLoader de MediaWiki trabajará en este límite, pero eso degradará el rendimiento. Si es posible, debe establecer el valor de suhosin.get.max_value_length en 1024 o superior en el archivo php.ini y establecer $wgResourceLoaderMaxQueryLength en el mismo valor en LocalSettings.php.',
+ 'config-suhosin-max-value-length' => 'Suhosin está instalado y limita el parámetro <code>length</code> GET a $1 bytes.
+El componente ResourceLoader (gestor de recursos) de MediaWiki trabajará en este límite, pero eso perjudicará el rendimiento.
+Si es posible, deberías establecer <code>suhosin.get.max_value_length</code> en el valor 1024 o superior en <code>php.ini</code> y establecer <code>$wgResourceLoaderMaxQueryLength</code> en el mismo valor en <code>php.ini</code>.',
'config-db-type' => 'Tipo de base de datos',
'config-db-host' => 'Servidor de la base de datos:',
'config-db-host-help' => 'Si su servidor de base de datos está en otro servidor, escriba el nombre del host o su dirección IP aquí.
@@ -4530,9 +4733,9 @@ $1
Si no encuentras en el listado el sistema de base de datos que estás intentando utilizar, sigue las instrucciones vinculadas arriba para habilitar la compatibilidad.',
'config-support-mysql' => '* $1 es la base de datos mayoritaria para MediaWiki y la que goza de mayor compatibilidad ([http://www.php.net/manual/es/mysql.installation.php cómo compilar PHP con compatibilidad MySQL])',
'config-support-postgres' => '$1 es un popular sistema de base de datos de código abierto, alternativa a MySQL. ([http://www.php.net/manual/es/pgsql.installation.php cómo compilar PHP con compatibilidad PostgreSQL]). Puede haber algunos defectos menores destacables, y no es recomendable para uso en un entorno de producción.',
- 'config-support-sqlite' => '* $1 es una base de datos ligera con gran compatibilidad con MediaWiki. ([http://www.php.net/manual/es/pdo.installation.php Cómo compilar PHP con compatibilidad SQLite], usa PDO)',
+ 'config-support-sqlite' => '* $1 es una base de datos ligera con gran compatibilidad con MediaWiki ([http://www.php.net/manual/es/pdo.installation.php cómo compilar PHP con compatibilidad SQLite usando PDO]).',
'config-support-oracle' => '* $1 es una base de datos comercial a nivel empresarial ([http://www.php.net/manual/es/oci8.installation.php cómo compilar PHP con compatibilidad con OCI8])',
- 'config-support-ibm_db2' => ' $1 es una base de datos de empresa comercial.',
+ 'config-support-ibm_db2' => '* $1 es una base de datos comercial a nivel empresarial ([http://www.php.net/manual/es/ibm-db2.installation.php cómo compilar PHP con compatibilidad con ibm_db2]).', # Fuzzy
'config-header-mysql' => 'Configuración de MySQL',
'config-header-postgres' => 'Configuración de PostgreSQL',
'config-header-sqlite' => 'Configuración de SQLite',
@@ -4598,8 +4801,8 @@ Esto '''no se recomienda''' a menos que esté teniendo problemas con su wiki.",
'config-upgrade-done-no-regenerate' => 'Actualización completa.
Usted puede ahora [$1 empezar a usar su wiki].',
- 'config-regenerate' => 'Regenerar LocalSettings.php →',
- 'config-show-table-status' => 'SHOW TABLE STATUS ha fallado!',
+ 'config-regenerate' => 'Regenerar <code>LocalSettings.php</code> →',
+ 'config-show-table-status' => '<code>SHOW TABLE STATUS</code> ha fallado!',
'config-unknown-collation' => "'''Advertencia:''' La base de datos está utilizando una intercalación no reconocida.",
'config-db-web-account' => 'Cuenta de base de datos para acceso Web',
'config-db-web-help' => 'Elige el usuario y contraseña que el servidor Web usará para conectarse al servidor de la base de datos durante el fincionamiento normal del wiki.',
@@ -4672,7 +4875,7 @@ Ahora puedes saltarte el resto de pasos e instalar el wiki con valores predeterm
'config-optional-continue' => 'Hazme más preguntas.',
'config-optional-skip' => 'Ya estoy aburrido, sólo instala el wiki.',
'config-profile' => 'Perfil de derechos de usuario:',
- 'config-profile-wiki' => 'Wiki tradicional',
+ 'config-profile-wiki' => 'Wiki tradicional', # Fuzzy
'config-profile-no-anon' => 'Creación de cuenta requerida',
'config-profile-fishbowl' => 'Sólo editores autorizados',
'config-profile-private' => 'Wiki privado',
@@ -4687,7 +4890,7 @@ Un wiki con '''{{int:config-profile-no-anon}}''' ofrece rendición de cuentas ad
El escenario '''{{int:config-profile-fishbowl}}''' permite editar a los usuarios autorizados, pero el público puede ver las páginas, incluyendo el historial.
Un '''{{int:config-profile-private}}''' sólo permite ver páginas a los usuarios autorizados, el mismo grupo al que le está permitido editar.
-Configuraciones más complejas de derechos de usuario están disponibles después de la instalación, consulte [//www.mediawiki.org/wiki/Manual:User_rights esta entrada en el manual].",
+Configuraciones más complejas de derechos de usuario están disponibles después de la instalación, consulte [//www.mediawiki.org/wiki/Manual:User_rights esta entrada en el manual].", # Fuzzy
'config-license' => 'Copyright and licencia:',
'config-license-none' => 'Pie sin licencia',
'config-license-cc-by-sa' => 'Creative Commons Reconocimiento Compartir Igual',
@@ -4772,7 +4975,7 @@ Puede que necesiten configuraciones adicionales, pero puedes habilitarlas ahora.
'config-install-alreadydone' => "'''Aviso:''' Parece que ya habías instalado MediaWiki y estás intentando instalarlo nuevamente.
Pasa a la próxima página, por favor.",
'config-install-begin' => 'Pulsando "{{int:config-continue}}", se iniciará la instalación de MediaWiki.
-Si todavía desea realizar algún cambio, pulse atrás.',
+Si todavía desea realizar algún cambio, pulse atrás.', # Fuzzy
'config-install-step-done' => 'hecho',
'config-install-step-failed' => 'falló',
'config-install-extensions' => 'Extensiones inclusive',
@@ -4826,16 +5029,17 @@ $3
'''Nota''': Si no haces esto ahora, este archivo de configuración generado no estará disponible para usted más tarde si sale de la instalación sin descargarlo.
Cuando lo haya hecho, usted puede '''[$2 entrar en su wiki]'''.",
- 'config-download-localsettings' => 'Descargar archivo LocalSettings.php',
+ 'config-download-localsettings' => 'Descargar archivo <code>LocalSettings.php</code>',
'config-help' => 'Ayuda',
'config-nofile' => 'El archivo "$1" no se pudo encontrar. ¿Se ha eliminado?',
'mainpagetext' => "'''MediaWiki ha sido instalado con éxito.'''",
- 'mainpagedocfooter' => 'Consulta la [//meta.wikimedia.org/wiki/Ayuda:Contenido Guía de usuario] para obtener información sobre el uso del software wiki.
+ 'mainpagedocfooter' => 'Consulta la [//meta.wikimedia.org/wiki/Ayuda:Guía del usuario de contenidos] para obtener información sobre el uso del software wiki.
== Empezando ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista de ajustes de configuración]
* [//www.mediawiki.org/wiki/Manual:FAQ/es FAQ de MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de correo de anuncios de distribución de MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de correo de anuncios de distribución de MediaWiki]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Regionalizar MediaWiki para tu idioma]',
);
/** español (formal) (español (formal))
@@ -4852,26 +5056,74 @@ $messages['es-formal'] = array(
/** Estonian (eesti)
* @author Avjoska
+ * @author Pikne
*/
$messages['et'] = array(
+ 'config-information' => 'Teave',
+ 'config-session-error' => 'Tõrge seansi alustamisel: $1',
+ 'config-your-language' => 'Oma keel:',
+ 'config-wiki-language' => 'Viki keel:',
'config-back' => '← Tagasi',
'config-continue' => 'Jätka →',
'config-page-language' => 'Keel',
'config-page-welcome' => 'Tere tulemast MediaWikisse!',
+ 'config-page-dbconnect' => 'Andmebaasiga ühendamine',
+ 'config-page-upgrade' => 'Olemasoleva installi uuendus',
+ 'config-page-dbsettings' => 'Andmebaasi sätted',
'config-page-name' => 'Nimi',
'config-page-options' => 'Seaded',
'config-page-install' => 'Paigaldamine',
'config-page-complete' => 'Valmis!',
+ 'config-page-restart' => 'Alusta installimist uuesti',
+ 'config-page-readme' => 'Loe mind',
+ 'config-page-copying' => 'Kopeerimine',
+ 'config-page-upgradedoc' => 'Uuendamine',
+ 'config-page-existingwiki' => 'Olemasolev viki',
+ 'config-restart' => 'Jah, tee taaskäivitus',
'config-db-name' => 'Andmebaasi nimi:',
'config-db-username' => 'Andmebaasi kasutajanimi:',
'config-db-password' => 'Andmebaasi parool:',
+ 'config-db-port' => 'Andmebaasi port:',
+ 'config-invalid-db-type' => 'Vigane andmebaasi tüüp',
+ 'config-site-name' => 'Viki nimi:',
+ 'config-site-name-blank' => 'Sisestage lehekülje nimi.',
+ 'config-project-namespace' => 'Projekti nimeruum:',
+ 'config-ns-generic' => 'Projekt',
+ 'config-admin-box' => 'Administraatorikonto',
+ 'config-admin-name' => 'Sinu nimi:',
+ 'config-admin-password' => 'Parool:',
+ 'config-admin-password-confirm' => 'Parool uuesti:',
+ 'config-admin-name-blank' => 'Sisesta administraatori kasutajanimi.',
+ 'config-admin-password-blank' => 'Sisesta administraatorikonto parool.',
+ 'config-admin-password-same' => 'Parool ei tohi kattuda kasutajanimega.',
+ 'config-admin-password-mismatch' => 'Sisestatud kaks parooli ei lange kokku.',
'config-admin-email' => 'E-posti aadress:',
+ 'config-admin-error-bademail' => 'Sisestasid vigase e-posti aadressi.',
'config-optional-continue' => 'Küsi minult veel küsimusi.',
+ 'config-profile-private' => 'Eraviki',
+ 'config-license' => 'Autoriõigus ja litsents:',
+ 'config-license-none' => 'Litsentsijaluseta',
+ 'config-license-cc-by-sa' => 'Creative Commonsi litsents "Autorile viitamine + jagamine samadel tingimustel"',
+ 'config-license-cc-by' => 'Creative Commonsi litsents "Autorile viitamine"',
+ 'config-license-cc-by-nc-sa' => 'Creative Commonsi litsents "Autorile viitamine + mitteäriline eesmärk + jagamine samadel tingimustel"',
+ 'config-email-settings' => 'E-posti sätted',
+ 'config-email-sender' => 'Saatja e-aadress:',
+ 'config-logo' => 'Logo internetiaadress:',
+ 'config-cc-again' => 'Vali uuesti...',
+ 'config-extensions' => 'Lisad',
'config-install-step-done' => 'valmis',
'config-install-step-failed' => 'ebaõnnestus',
+ 'config-install-user-alreadyexists' => 'Kasutaja "$1" on juba olemas',
+ 'config-install-tables' => 'Tabelite loomine',
+ 'config-help' => 'abi',
'mainpagetext' => "'''MediaWiki tarkvara on edukalt paigaldatud.'''",
- 'mainpagedocfooter' => 'Juhiste saamiseks kasutamise ning konfigureerimise kohta vaata palun inglisekeelset [//meta.wikimedia.org/wiki/MediaWiki_localisation dokumentatsiooni liidese kohaldamisest]
-ning [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide kasutusjuhendit].',
+ 'mainpagedocfooter' => 'Vikitarkvara kasutamise kohta leiad lisateavet [//meta.wikimedia.org/wiki/Help:Contents juhendist].
+
+== Alustamine ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Häälestussätete loend]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki KKK]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki versiooniuuenduste postiloend]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources MediaWiki lokaliseerimine]',
);
/** Basque (euskara)
@@ -4983,18 +5235,22 @@ $messages['fa'] = array(
'config-desc' => 'نصب کنندهٔ ویکی‌مدیا',
'config-title' => 'نصب ویکی‌مدیا $1',
'config-information' => 'اطلاعات',
+ 'config-localsettings-key' => 'کلید ارتقا:',
'config-your-language' => 'زبان شما:',
'config-wiki-language' => 'زبان ویکی:',
'config-back' => '→ بازگشت',
'config-continue' => 'ادامه ←',
'config-page-language' => 'زبان',
'config-page-welcome' => 'به مدیاویکی خوش آمدید!',
+ 'config-page-dbconnect' => 'اتصال به پایگاه داده',
'config-page-name' => 'نام',
'config-page-options' => 'گزینه‌ها',
'config-page-install' => 'نصب',
'config-page-complete' => 'کامل!',
'config-page-readme' => 'مرا بخوان',
'config-page-releasenotes' => 'یادداشت‌های انتشار',
+ 'config-page-copying' => 'تکثیر',
+ 'config-page-upgradedoc' => 'ارتقا',
'config-page-existingwiki' => 'ویکی موجود',
'config-restart' => 'بله ، آن دوباره راه اندازی کن',
'config-sidebar' => '* [//www.mediawiki.org صفحهٔ اصلی مدیاویکی]
@@ -5013,13 +5269,20 @@ $messages['fa'] = array(
'config-db-host' => 'میزبان پایگاه اطلاعات:',
'config-db-username' => 'نام کاربری پایگاه اطلاعات:',
'config-db-password' => 'کلمه عبور پایگاه اطلاعات:',
+ 'config-mysql-old' => 'مای‌اس‌کیو‌ال نسخهٔ $1 و یا بالاتر نیاز است، شما نسخهٔ $2 را دارید.',
+ 'config-db-port' => 'درگاه پایگاه‌داده:',
'config-header-mysql' => 'تنظیمات مای‌اس‌کیو‌ال',
'config-connection-error' => '$1.
میزبان، نام کاربری و گذرواژه را بررسی کنید و دوباره امتحان کنید.',
+ 'config-mysql-binary' => 'دودویی',
+ 'config-mysql-utf8' => 'UTF-8',
'config-site-name' => 'نام ویکی:',
'config-site-name-blank' => 'نام تارنما را وارد کنید.',
'config-project-namespace' => 'فضای نام پروژه:',
+ 'config-ns-generic' => 'پروژه',
+ 'config-ns-other-default' => 'ویکی‌من',
+ 'config-admin-box' => 'حساب مدیر سیستم',
'config-admin-name' => 'نام شما:',
'config-admin-password' => 'کلمه عبور:',
'config-admin-password-confirm' => 'دوباره کلمه عبور:',
@@ -5032,6 +5295,7 @@ $messages['fa'] = array(
'config-email-settings' => 'تنظیمات پست الکترونیکی',
'config-upload-enable' => 'فعال سازی بارگذاری پرونده',
'config-logo' => 'نشانی نامواره:',
+ 'config-extensions' => 'افزونه‌ها',
'config-install-step-done' => 'انجام شد',
'config-install-step-failed' => 'ناموفق بود',
'config-help' => 'راهنما',
@@ -5043,7 +5307,7 @@ $messages['fa'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings تنظیم پیکربندی]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki پرسش‌های متداول]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce فهرست پست الکترونیکی نسخه‌های مدیاویکی]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce فهرست پست الکترونیکی نسخه‌های مدیاویکی]', # Fuzzy
);
/** Finnish (suomi)
@@ -5053,6 +5317,8 @@ $messages['fa'] = array(
* @author Nike
* @author Olli
* @author Str4nd
+ * @author VezonThunder
+ * @author 아라
*/
$messages['fi'] = array(
'config-desc' => 'MediaWiki-asennin',
@@ -5060,19 +5326,28 @@ $messages['fi'] = array(
'config-information' => 'Tiedot',
'config-localsettings-upgrade' => '<code>LocalSettings.php</code>-tiedosto havaittiin.
Kirjoita muuttujan <code>$wgUpgradeKey</code> arvo alla olevaan kenttään päivittääksesi asennuksen.
-Löydät sen LocalSettings.php-tiedostosta.',
- 'config-localsettings-cli-upgrade' => 'LocalSettings.php-tiedosto havaittiin.
-Päivitä asennus suorittamalla update.php.',
+Löydät sen <code>LocalSettings.php</code>-tiedostosta.',
+ 'config-localsettings-cli-upgrade' => '<code>LocalSettings.php</code>-tiedosto havaittiin.
+Päivitä asennus suorittamalla <code>update.php</code>.',
'config-localsettings-key' => 'Päivitysavain',
'config-localsettings-badkey' => 'Antamasi avain on virheellinen.',
- 'config-localsettings-incomplete' => 'Nykyinen LocalSettings.php-tiedosto näyttää olevan puutteellinen.
+ 'config-upgrade-key-missing' => 'Havaittiin aiempi MediaWiki-asennus.
+Päivittääksesi tämän asennuksen lisää <code>LocalSettings.php</code>-tiedostosi loppuun seuraava rivi:
+
+$1',
+ 'config-localsettings-incomplete' => 'Nykyinen <code>LocalSettings.php</code>-tiedosto näyttää olevan puutteellinen.
Muuttujaa $1 ei ole asetettu.
-Muuta LocalSettings.php-tiedostoa siten, että muuttuja on asetettu ja napsauta »Jatka».',
+Muuta <code>LocalSettings.php</code>-tiedostoa siten, että muuttuja on asetettu ja napsauta »{{int:Config-continue}}».',
+ 'config-localsettings-connection-error' => 'Virhe yhdistettäessä tietokantaan käyttäen tiedostossa <code>LocalSettings.php</code> tai <code>AdminSettings.php</code> määritettyjä asetuksia. Korjaa asetukset ja yritä uudelleen.
+
+$1',
'config-session-error' => 'Istunnon aloittaminen epäonnistui: $1',
'config-session-expired' => 'Istuntotietosi näyttävät olevan vanhentuneita.
Istuntojen elinajaksi on määritelty $1.
Voit muuttaa tätä asetusta vaihtamalla kohtaa <code>session.gc_maxlifetime</code> php.ini-tiedostossa.
Käynnistä asennusprosessi uudelleen.',
+ 'config-no-session' => 'Istuntosi tiedot menetettiin!
+Tarkista php.ini-tiedostosi ja varmista, että <code>session.save_path</code> on asetettu sopivaan kansioon.',
'config-your-language' => 'Asennuksen kieli',
'config-your-language-help' => 'Valitse kieli, jota haluat käyttää asennuksen ajan.',
'config-wiki-language' => 'Wikin kieli',
@@ -5145,7 +5420,7 @@ Asennus saattaa epäonnistua!",
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
'config-type-ibm_db2' => 'IBM DB2',
- 'config-support-ibm_db2' => '* $1 on kaupallinen tietokanta yrityskäyttöön.',
+ 'config-support-ibm_db2' => '* $1 on kaupallinen tietokanta yrityskäyttöön.', # Fuzzy
'config-header-mysql' => 'MySQL-asetukset',
'config-header-postgres' => 'PostgreSQL-asetukset',
'config-header-sqlite' => 'SQLite-asetukset',
@@ -5174,8 +5449,8 @@ Tämä '''ei ole suositeltavaa''', jos wikissäsi ei ole ongelmia.",
'config-upgrade-done-no-regenerate' => 'Päivitys valmis.
Voit [$1 aloittaa wikin käytön].',
- 'config-regenerate' => 'Luo LocalSettings.php uudelleen →',
- 'config-show-table-status' => 'Kysely SHOW TABLE STATUS epäonnistui!',
+ 'config-regenerate' => 'Luo <code>LocalSettings.php</code> uudelleen →',
+ 'config-show-table-status' => 'Kysely <code>SHOW TABLE STATUS</code> epäonnistui!',
'config-mysql-engine' => 'Tallennusmoottori',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
@@ -5195,7 +5470,7 @@ Voit [$1 aloittaa wikin käytön].',
'config-admin-error-bademail' => 'Annoit virheellisen sähköpostiosoitteen.',
'config-almost-done' => 'Olet jo lähes valmis!
Voit ohittaa jäljellä olevat määritykset ja asentaa wikin juuri nyt.',
- 'config-profile-wiki' => 'Perinteinen wiki',
+ 'config-profile-wiki' => 'Avoin wiki',
'config-profile-no-anon' => 'Tunnuksen luonti vaaditaan',
'config-profile-private' => 'Yksityinen wiki',
'config-license' => 'Tekijänoikeus ja lisenssi:',
@@ -5208,7 +5483,7 @@ Voit ohittaa jäljellä olevat määritykset ja asentaa wikin juuri nyt.',
'config-install-step-failed' => 'epäonnistui',
'config-install-user-alreadyexists' => 'Käyttäjä $1 on jo olemassa',
'config-install-interwiki-list' => 'Tiedostoa <code>interwiki.list</code> ei voitu lukea.',
- 'config-download-localsettings' => 'Lataa LocalSettings.php',
+ 'config-download-localsettings' => 'Lataa <code>LocalSettings.php</code>',
'config-help' => 'ohje',
'mainpagetext' => "'''MediaWiki on onnistuneesti asennettu.'''",
'mainpagedocfooter' => "Lisätietoja käytöstä on sivulla [//meta.wikimedia.org/wiki/Help:Contents User's Guide].
@@ -5253,6 +5528,7 @@ $messages['fo'] = array(
* @author Verdy p
* @author Wyz
* @author Yumeki
+ * @author 아라
*/
$messages['fr'] = array(
'config-desc' => 'Le programme d’installation de MediaWiki',
@@ -5260,20 +5536,20 @@ $messages['fr'] = array(
'config-information' => 'Informations',
'config-localsettings-upgrade' => 'Un fichier <code>LocalSettings.php</code> a été détecté.
Pour mettre à jour cette installation, veuillez saisir la valeur de <code>$wgUpgradeKey</code> dans le champ ci-dessous.
-Vous la trouverez dans LocalSettings.php.',
- 'config-localsettings-cli-upgrade' => 'Un fichier LocalSettings.php a été détecté.
-Pour mettre à niveau cette installation, veuillez exécuter update.php',
+Vous la trouverez dans <code>LocalSettings.php</code>.',
+ 'config-localsettings-cli-upgrade' => 'Un fichier <code>LocalSettings.php</code> a été détecté.
+Pour mettre à niveau cette installation, veuillez exécuter <code>update.php</code>',
'config-localsettings-key' => 'Clé de mise à jour :',
'config-localsettings-badkey' => 'La clé que vous avez fournie est incorrecte',
'config-upgrade-key-missing' => 'Une installation existante de MediaWiki a été détectée.
-Pour mettre à jour cette installation, veuillez ajouter la ligne suivante à la fin de votre fichier LocalSettings.php
+Pour mettre à jour cette installation, veuillez ajouter la ligne suivante à la fin de votre fichier <code>LocalSettings.php</code>
$1',
- 'config-localsettings-incomplete' => 'Le fichier LocalSettings.php existant semble être incomplet.
+ 'config-localsettings-incomplete' => 'Le fichier <code>LocalSettings.php</code> existant semble être incomplet.
La variable $1 n’est pas définie.
-Veuillez modifier LocalSettings.php de sorte que cette variable soit définie, puis cliquer sur « Continuer ».',
- 'config-localsettings-connection-error' => 'Une erreur est survenue lors de la connexion à la base de données en utilisant la configuration spécifiée dans LocalSettings.php ou AdminSettings.php. Veuillez corriger cette configuration puis réessayer.
+Veuillez modifier <code>LocalSettings.php</code> de sorte que cette variable soit définie, puis cliquer sur « {{int:Config-continue}} ».',
+ 'config-localsettings-connection-error' => 'Une erreur est survenue lors de la connexion à la base de données en utilisant la configuration spécifiée dans <code>LocalSettings.php</code> ou <code>AdminSettings.php</code>. Veuillez corriger cette configuration puis réessayer.
$1',
'config-session-error' => 'Erreur lors du démarrage de la session : $1',
@@ -5405,7 +5681,8 @@ Installation interrompue.',
'config-using531' => 'MediaWiki ne peut pas être utilisé avec PHP $1 à cause d’un bogue affectant les paramètres passés par référence à <code>__call()</code>.
Veuillez mettre à jour votre système vers PHP 5.3.2 ou plus récent ou revenir à PHP 5.3.0 pour résoudre ce problème.
Installation interrompue.',
- 'config-suhosin-max-value-length' => 'Suhosin est installé et limite la longueur du paramètre GET à $1 octets. Le <code>ResourceLoader</code> de MediaWiki va répondre en respectant cette limite, mais ses performances seront dégradées. Si possible, vous devriez définir <code>suhosin.get.max_value_length</code> à 1024 ou plus dans le fichier <code>php.ini</code>, et fixer <code>$wgResourceLoaderMaxQueryLength</code> à la même valeur dans <code>LocalSettings.php</code>.',
+ 'config-suhosin-max-value-length' => 'Suhosin est installé et limite la <code>longueur</code> du paramètre GET à $1 octets.
+Le composant ResourceLoader de MediaWiki va répondre en respectant cette limite, mais ses performances seront dégradées. Si possible, vous devriez définir <code>suhosin.get.max_value_length</code> à 1024 ou plus dans le fichier <code>php.ini</code>, et fixer <code>$wgResourceLoaderMaxQueryLength</code> à la même valeur dans <code>LocalSettings.php</code>.',
'config-db-type' => 'Type de base de données :',
'config-db-host' => 'Nom d’hôte de la base de données :',
'config-db-host-help' => 'Si votre serveur de base de données est sur un serveur différent, saisissez ici son nom d’hôte ou son adresse IP.
@@ -5487,7 +5764,7 @@ Si vous ne voyez pas le système de base de données que vous essayez d'utiliser
'config-support-postgres' => "* $1 est un système de base de données populaire et ''open source'' qui peut être une alternative à MySQL ([http://www.php.net/manual/en/pgsql.installation.php how to compile PHP with PostgreSQL support]). Il peut contenir quelques bogues mineurs et n'est pas recommandé dans un environnement de production.",
'config-support-sqlite' => '* $1 est un système de base de données léger qui est bien supporté. ([http://www.php.net/manual/en/pdo.installation.php How to compile PHP with SQLite support], utilise PDO)',
'config-support-oracle' => '* $1 est un système commercial de gestion de base de données d’entreprise. ([http://www.php.net/manual/en/oci8.installation.php Comment compiler PHP avec le support OCI8])',
- 'config-support-ibm_db2' => "* $1 est une base de données d'entreprise commerciale.",
+ 'config-support-ibm_db2' => "* $1 est une base de données d'entreprise commerciale. ([http://www.php.net/manual/en/ibm-db2.installation.php Comment compiler PHP avec le support de DB2 d’IBM])",
'config-header-mysql' => 'Paramètres de MySQL',
'config-header-postgres' => 'Paramètres de PostgreSQL',
'config-header-sqlite' => 'Paramètres de SQLite',
@@ -5554,8 +5831,8 @@ Ce '''n'est pas recommandé''' sauf si vous rencontrez des problèmes avec votre
'config-upgrade-done-no-regenerate' => 'Mise à jour terminée.
Vous pouvez maintenant [$1 commencer à utiliser votre wiki].',
- 'config-regenerate' => 'Regénérer LocalSettings.php →',
- 'config-show-table-status' => 'Échec de la requête SHOW TABLE STATUS !',
+ 'config-regenerate' => 'Regénérer <code>LocalSettings.php</code> →',
+ 'config-show-table-status' => 'Échec de la requête <code>SHOW TABLE STATUS</code> !',
'config-unknown-collation' => "'''Attention:''' La base de données effectue un classement alphabétique (''collation'') inconnu.",
'config-db-web-account' => "Compte de la base de données pour l'accès Web",
'config-db-web-help' => "Sélectionnez le nom d'utilisateur et le mot de passe que le serveur web utilisera pour se connecter au serveur de base de données pendant le fonctionnement habituel du wiki.",
@@ -5623,7 +5900,7 @@ Vous pouvez passer la configuration restante et installer immédiatement le wiki
'config-optional-continue' => 'Me poser davantage de questions.',
'config-optional-skip' => 'J’en ai assez, installer simplement le wiki.',
'config-profile' => 'Profil des droits d’utilisateurs :',
- 'config-profile-wiki' => 'Wiki traditionnel',
+ 'config-profile-wiki' => 'Wiki ouvert',
'config-profile-no-anon' => 'Création de compte requise',
'config-profile-fishbowl' => 'Éditeurs autorisés seulement',
'config-profile-private' => 'Wiki privé',
@@ -5633,7 +5910,7 @@ Avec MediaWiki, il est facile de vérifier les modifications récentes et de ré
Cependant, de nombreuses autres utilisations ont été trouvées au logiciel et il n’est pas toujours facile de convaincre tout le monde des bénéfices de l’esprit wiki.
Vous avez donc le choix.
-'''{{int:config-profile-wiki}}''' autorise toute personne à modifier, y compris sans s’identifier.
+Le modèle '''{{int:config-profile-wiki}}''' autorise toute personne à modifier, y compris sans s’identifier.
'''{{int:config-profile-no-anon}}''' fournit plus de contrôle, par l’identification, mais peut rebuter les contributeurs occasionnels.
'''{{int:config-profile-fishbowl}}''' autorise la modification par les utilisateurs approuvés, mais le public peut toujours lire les pages et leur historique.
@@ -5722,8 +5999,8 @@ Si vous ne le connaissez pas, la valeur par défaut est 11211.",
Elles peuvent nécessiter une configuration supplémentaire, mais vous pouvez les activer maintenant',
'config-install-alreadydone' => "'''Attention''': Vous semblez avoir déjà installé MediaWiki et tentez de l'installer à nouveau.
S'il vous plaît, allez à la page suivante.",
- 'config-install-begin' => "En appuyant sur {{int:config-continue}}, vous commencerez l'installation de MediaWiki.
-Si vous voulez apporter des modifications, appuyez sur Retour.",
+ 'config-install-begin' => 'En appuyant sur {{int:config-continue}}, vous commencerez l\'installation de MediaWiki.
+Si vous voulez apporter des modifications, appuyez sur "{{int:config-back}}".',
'config-install-step-done' => 'fait',
'config-install-step-failed' => 'échec',
'config-install-extensions' => 'Inclusion des extensions',
@@ -5776,16 +6053,17 @@ $3
'''Note''': Si vous ne le faites pas maintenant, ce fichier de configuration généré ne sera pas disponible plus tard si vous quittez l'installation sans le télécharger.
Lorsque c'est fait, vous pouvez '''[$2 accéder à votre wiki]'''.",
- 'config-download-localsettings' => 'Télécharger LocalSettings.php',
+ 'config-download-localsettings' => 'Télécharger <code>LocalSettings.php</code>',
'config-help' => 'aide',
'config-nofile' => 'Le fichier « $1 » est introuvable. A-t-il été supprimé ?',
'mainpagetext' => "'''MediaWiki a été installé avec succès.'''",
- 'mainpagedocfooter' => 'Consultez le [//meta.wikimedia.org/wiki/Aide:Contenu Guide de l’utilisateur] pour plus d’informations sur l’utilisation de ce logiciel.
+ 'mainpagedocfooter' => 'Consultez le [//meta.wikimedia.org/wiki/Aide:Contenu Guide de l’utilisateur] pour plus d’informations sur l’utilisation de ce logiciel de wiki.
-== Démarrer avec MediaWiki ==
+== Pour démarrer ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Liste des paramètres de configuration]
* [//www.mediawiki.org/wiki/Manual:FAQ/fr FAQ sur MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Liste de discussion sur les distributions de MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Liste de discussion sur les distributions de MediaWiki]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Adaptez MediaWiki dans votre langue]',
);
/** Cajun French (français cadien)
@@ -5870,8 +6148,8 @@ Portant, MediaWiki at fôta de PHP $2 ou ben ples hôt.',
'config-missing-db-host' => 'Vos dête buchiér una valor por « Hôto de la bâsa de balyês »',
'config-missing-db-server-oracle' => 'Vos dête buchiér una valor por « TNS de la bâsa de balyês »',
'config-sqlite-readonly' => 'Lo fichiér <code>$1</code> est pas accèssiblo en ècritura.',
- 'config-regenerate' => 'Refâre LocalSettings.php →',
- 'config-show-table-status' => 'Falyita de la requéta SHOW TABLE STATUS !',
+ 'config-regenerate' => 'Refâre <code>LocalSettings.php</code> →',
+ 'config-show-table-status' => 'Falyita de la requéta <code>SHOW TABLE STATUS</code> !',
'config-db-web-account' => 'Compto de la bâsa de balyês por l’accès vouèbe',
'config-db-web-account-same' => 'Utilisâd lo mémo compto que por l’enstalacion',
'config-db-web-create' => 'Féte lo compto s’ègziste p’oncor',
@@ -5897,7 +6175,7 @@ Portant, MediaWiki at fôta de PHP $2 ou ben ples hôt.',
'config-admin-email' => 'Adrèce èlèctronica :',
'config-optional-continue' => 'Mè posar més de quèstions.',
'config-profile' => 'Profil des drêts d’usanciér :',
- 'config-profile-wiki' => 'Vouiqui tradicionâl',
+ 'config-profile-wiki' => 'Vouiqui tradicionâl', # Fuzzy
'config-profile-no-anon' => 'Crèacion de compto nècèssèra',
'config-profile-fishbowl' => 'Solament los èditors ôtorisâs',
'config-profile-private' => 'Vouiqui privâ',
@@ -5950,7 +6228,7 @@ Portant, MediaWiki at fôta de PHP $2 ou ben ples hôt.',
'config-install-mainpage' => 'Crèacion de la pâge principâla avouéc un contegnu per dèfôt',
'config-install-extension-tables' => 'Crèacion de trâbles por les èxtensions activâs',
'config-install-mainpage-failed' => 'Empossiblo d’entrebetar la pâge principâla : $1',
- 'config-download-localsettings' => 'Tèlèchargiér LocalSettings.php',
+ 'config-download-localsettings' => 'Tèlèchargiér <code>LocalSettings.php</code>',
'config-help' => 'éde',
'mainpagetext' => "'''MediaWiki at étâ enstalâ avouéc reusséta.'''",
'mainpagedocfooter' => 'Vêde lo [//meta.wikimedia.org/wiki/Aide:Contenu guido d’usanciér] por més d’enformacions sur l’usâjo de la programeria vouiqui.
@@ -5958,7 +6236,7 @@ Portant, MediaWiki at fôta de PHP $2 ou ben ples hôt.',
== Emmodar avouéc MediaWiki ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista des paramètres de configuracion]
* [//www.mediawiki.org/wiki/Manual:FAQ/fr FDQ sur MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discussion sur les distribucions de MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discussion sur les distribucions de MediaWiki]', # Fuzzy
);
/** Northern Frisian (Nordfriisk)
@@ -6057,12 +6335,14 @@ $messages['gd'] = array(
== Toiseach tòiseachaidh ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Liosta suidheachadh nan roghainnean]
* [//www.mediawiki.org/wiki/Manual:FAQ CÀBHA MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Liosta puist nan sgaoilidhean MediaWiki]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Liosta puist nan sgaoilidhean MediaWiki]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Cuir do chànan air MediaWiki]",
);
/** Galician (galego)
* @author Elisardojm
* @author Toliño
+ * @author 아라
*/
$messages['gl'] = array(
'config-desc' => 'O programa de instalación de MediaWiki',
@@ -6070,19 +6350,19 @@ $messages['gl'] = array(
'config-information' => 'Información',
'config-localsettings-upgrade' => 'Detectouse un ficheiro <code>LocalSettings.php</code>.
Para actualizar esta instalación, introduza o valor de <code>$wgUpgradeKey</code> na caixa.
-Pode atopalo en LocalSettings.php.',
- 'config-localsettings-cli-upgrade' => 'Detectouse un ficheiro LocalSettings.php.
-Para actualizar esta instalación, execute update.php',
+Pode atopalo en <code>LocalSettings.php</code>.',
+ 'config-localsettings-cli-upgrade' => 'Detectouse un ficheiro <code>LocalSettings.php</code>.
+Para actualizar esta instalación, execute <code>update.php</code>',
'config-localsettings-key' => 'Clave de actualización:',
'config-localsettings-badkey' => 'A clave dada é incorrecta',
'config-upgrade-key-missing' => 'Detectouse unha instalación existente de MediaWiki.
-Para actualizar esta instalación, inclúa esta liña ao final do ficheiro LocalSettings.php:
+Para actualizar esta instalación, inclúa esta liña ao final do ficheiro <code>LocalSettings.php</code>:
$1',
- 'config-localsettings-incomplete' => 'Semella que o ficheiro LocalSettings.php existente está incompleto.
+ 'config-localsettings-incomplete' => 'Semella que o ficheiro <code>LocalSettings.php</code> existente está incompleto.
A variable $1 non está establecida.
-Modifique o ficheiro LocalSettings.php de xeito que a variable quede establecida e prema en "Continuar".',
- 'config-localsettings-connection-error' => 'Atopouse un erro ao conectar coa base de datos empregando a configuración especificada no ficheiro LocalSettings.php ou no ficheiro AdminSettings.php. Corrixa esta configuración e inténteo de novo.
+Modifique o ficheiro <code>LocalSettings.php</code> de xeito que a variable quede establecida e prema en "{{int:Config-continue}}".',
+ 'config-localsettings-connection-error' => 'Atopouse un erro ao conectar coa base de datos empregando a configuración especificada no ficheiro <code>LocalSettings.php</code> ou no ficheiro <code>AdminSettings.php</code>. Corrixa esta configuración e inténteo de novo.
$1',
'config-session-error' => 'Erro ao iniciar a sesión: $1',
@@ -6215,7 +6495,9 @@ Instalación abortada.',
'config-using531' => 'O PHP $1 non é compatible con MediaWiki debido a un erro que afecta aos parámetros de referencia de <code>__call()</code>.
Actualice o sistema á versión 5.3.2 ou posterior do PHP ou volva á versión 5.3.0 do PHP para arranxar o problema.
Instalación abortada.',
- 'config-suhosin-max-value-length' => 'Suhosin está instalado e limita a lonxitude do parámetro GET a $1 bytes. O compoñente ResourceLoader (xestor de recursos) de MediaWiki traballa neste límite, pero este prexudica o rendemento. Se é posible, debería establecer suhosin.get.max_value_length no valor 1024 ou superior en php.ini e establecer $wgResourceLoaderMaxQueryLength no mesmo valor en LocalSettings.php.',
+ 'config-suhosin-max-value-length' => 'Suhosin está instalado e limita o parámetro GET <code>length</code> a $1 bytes.
+O compoñente ResourceLoader (xestor de recursos) de MediaWiki traballa neste límite, pero este prexudica o rendemento.
+Se é posible, debería establecer <code>suhosin.get.max_value_length</code> no valor 1024 ou superior en <code>php.ini</code> e establecer <code>$wgResourceLoaderMaxQueryLength</code> no mesmo valor en <code>LocalSettings.php</code>.',
'config-db-type' => 'Tipo de base de datos:',
'config-db-host' => 'Servidor da base de datos:',
'config-db-host-help' => 'Se o servidor da súa base de datos está nun servidor diferente, escriba o nome do servidor ou o enderezo IP aquí.
@@ -6298,8 +6580,8 @@ Se non ve listado a continuación o sistema de base de datos que intenta usar, s
'config-support-mysql' => '* $1 é o obxectivo principal para MediaWiki e está mellor soportado ([http://www.php.net/manual/en/mysql.installation.php como compilar o PHP con soporte MySQL])',
'config-support-postgres' => '* $1 é un sistema de base de datos popular e de código aberto como alternativa a MySQL ([http://www.php.net/manual/en/pgsql.installation.php como compilar o PHP con soporte PostgreSQL]). É posible que haxa algúns pequenos erros e non se recomenda o seu uso nunha contorna de produción.',
'config-support-sqlite' => '* $1 é un sistema de base de datos lixeiro moi ben soportado. ([http://www.php.net/manual/en/pdo.installation.php Como compilar o PHP con soporte SQLite], emprega PDO)',
- 'config-support-oracle' => '* $1 é un sistema comercial de xestión de base de datos de empresa. ([http://www.php.net/manual/en/oci8.installation.php Como compilar PHP con soporte OCI8])',
- 'config-support-ibm_db2' => '* $1 é unha base de datos de empresa comercial.',
+ 'config-support-oracle' => '* $1 é un sistema comercial de xestión de base de datos de empresa. ([http://www.php.net/manual/en/oci8.installation.php Como compilar o PHP con soporte OCI8])',
+ 'config-support-ibm_db2' => '* $1 é unha base de datos de empresa comercial. ([http://www.php.net/manual/en/ibm-db2.installation.php Como compilar o PHP con soporte IBM DB2])',
'config-header-mysql' => 'Configuración do MySQL',
'config-header-postgres' => 'Configuración do PostgreSQL',
'config-header-sqlite' => 'Configuración do SQLite',
@@ -6366,8 +6648,8 @@ Isto '''non é recomendable''' a menos que estea a ter problemas co seu wiki.",
'config-upgrade-done-no-regenerate' => 'Actualización completada.
Xa pode [$1 comezar a usar o seu wiki].',
- 'config-regenerate' => 'Rexenerar LocalSettings.php →',
- 'config-show-table-status' => 'A pescuda SHOW TABLE STATUS fallou!',
+ 'config-regenerate' => 'Rexenerar <code>LocalSettings.php</code> →',
+ 'config-show-table-status' => 'A pescuda <code>SHOW TABLE STATUS</code> fallou!',
'config-unknown-collation' => "'''Atención:''' A base de datos está a empregar unha clasificación alfabética irrecoñecible.",
'config-db-web-account' => 'Conta na base de datos para o acceso á internet',
'config-db-web-help' => 'Seleccione o nome de usuario e contrasinal que o servidor web empregará para se conectar ao servidor da base de datos durante o funcionamento normal do wiki.',
@@ -6440,7 +6722,7 @@ Neste paso pode saltar o resto da configuración e instalar o wiki agora mesmo.'
'config-optional-continue' => 'Facédeme máis preguntas.',
'config-optional-skip' => 'Xa estou canso. Instalade o wiki.',
'config-profile' => 'Perfil dos dereitos de usuario:',
- 'config-profile-wiki' => 'Wiki tradicional',
+ 'config-profile-wiki' => 'Wiki aberto',
'config-profile-no-anon' => 'Necesítase a creación dunha conta',
'config-profile-fishbowl' => 'Só os editores autorizados',
'config-profile-private' => 'Wiki privado',
@@ -6449,7 +6731,7 @@ En MediaWiki, é doado revisar os cambios recentes e reverter calquera dano feit
Porén, moita xente atopa MediaWiki útil nunha ampla variedade de papeis, e ás veces non é fácil convencer a todos dos beneficios que leva consigo o estilo wiki.
Vostede decide.
-O tipo '''{{int:config-profile-wiki}}''' permite a edición por parte de calquera, mesmo sen rexistro.
+O modelo '''{{int:config-profile-wiki}}''' permite a edición por parte de calquera, mesmo sen rexistro.
A opción '''{{int:config-profile-no-anon}}''' proporciona un control maior, pero pode desalentar os colaboradores casuais.
O escenario '''{{int:config-profile-fishbowl}}''' restrinxe a edición aos usuarios aprobados, pero o público pode ollar as páxinas, incluíndo os historiais.
@@ -6540,7 +6822,7 @@ Quizais necesite algunha configuración adicional, pero pode activalas agora',
'config-install-alreadydone' => "'''Atención:''' Semella que xa instalou MediaWiki e que o está a instalar de novo.
Vaia ata a seguinte páxina.",
'config-install-begin' => 'Ao premer en "{{int:config-continue}}", comezará a instalación de MediaWiki.
-Se aínda quere facer algún cambio, volva atrás.',
+Se aínda quere facer algún cambio, prema en "{{int:config-back}}".',
'config-install-step-done' => 'feito',
'config-install-step-failed' => 'erro',
'config-install-extensions' => 'Incluíndo as extensións',
@@ -6596,7 +6878,7 @@ $3
'''Nota:''' Se non fai iso agora, este ficheiro de configuración xerado non estará dispoñible máis adiante se sae da instalación sen descargalo.
Cando faga todo isto, xa poderá '''[$2 entrar no seu wiki]'''.",
- 'config-download-localsettings' => 'Descargar o LocalSettings.php',
+ 'config-download-localsettings' => 'Descargar o <code>LocalSettings.php</code>',
'config-help' => 'axuda',
'config-nofile' => 'Non se puido atopar o ficheiro "$1". Se cadra, foi borrado?',
'mainpagetext' => "'''MediaWiki instalouse correctamente.'''",
@@ -6605,7 +6887,8 @@ Cando faga todo isto, xa poderá '''[$2 entrar no seu wiki]'''.",
== Primeiros pasos ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista das opcións de configuración]
* [//www.mediawiki.org/wiki/Manual:FAQ Preguntas máis frecuentes sobre MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de correo dos lanzamentos de MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de correo dos lanzamentos de MediaWiki]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Localice MediaWiki á súa lingua]',
);
/** Goan Konkani (Latin script) (Konknni)
@@ -6639,7 +6922,7 @@ $messages['gsw'] = array(
'config-information' => 'Information',
'config-localsettings-upgrade' => "'''Warnig:''' E Datei <code>LocalSettings.php</code> isch gfunde wore.
Fir d Aktualisierig vu dr däre Inschtallation, gib bitte dr Wärt vum Parameter <code>\$wgUpgradeKey</code> im Fäld unten yy.
-Du findsch dr Wärt in dr Datei LocalSettings.php.",
+Du findsch dr Wärt in dr Datei <code>LocalSettings.php</code>.",
'config-localsettings-key' => 'Aktualisierigsschlissel:',
'config-localsettings-badkey' => 'Dr Aktualisierigsschlissel, wu du aagee hesch, isch falsch.',
'config-session-error' => 'Fähler bim Starte vu dr Sitzig: $1',
@@ -6738,7 +7021,7 @@ S Objäktcaching isch wäge däm nit aktiviert.",
Miniaturaasichte vu Bilder sin megli, sobald s Uffelade vu Dateie aktiviert isch.',
'config-help' => 'Hilf',
'mainpagetext' => "'''MediaWiki isch erfolgrich inschtalliert worre.'''",
- 'mainpagedocfooter' => 'Lueg uf d [//meta.wikimedia.org/wiki/MediaWiki_localisation Dokumentation fir d Aapassig vu dr Benutzeroberflächi] un s [//meta.wikimedia.org/wiki/Help:Contents Benutzerhandbuech] fir d Hilf iber d Benutzig un s Yystelle.',
+ 'mainpagedocfooter' => 'Lueg uf d [//meta.wikimedia.org/wiki/MediaWiki_localisation Dokumentation fir d Aapassig vu dr Benutzeroberflächi] un s [//meta.wikimedia.org/wiki/Help:Contents Benutzerhandbuech] fir d Hilf iber d Benutzig un s Yystelle.', # Fuzzy
);
/** Gujarati (ગુજરાતી)
@@ -6783,6 +7066,7 @@ $messages['haw'] = array(
* @author Amire80
* @author YaronSh
* @author ערן
+ * @author 아라
*/
$messages['he'] = array(
'config-desc' => 'תכנית ההתקנה של מדיה־ויקי',
@@ -6790,19 +7074,19 @@ $messages['he'] = array(
'config-information' => 'פרטים',
'config-localsettings-upgrade' => 'זוהה קובץ <code>LocalSettings.php</code>.
כדי לשדרג את ההתקנה הזאת, נא להקליד את הערך של <code>$wgUpgradeKey</code> בתיבה להלן.
-אפשר למצוא אותו בקובץ LocalSettings.php.',
- 'config-localsettings-cli-upgrade' => 'זוהה קובץ LocalSettings.php.
-כדי לשדרג את ההתקנה הזאת, הריצו את update.php ולא את הקובץ הזה.',
+אפשר למצוא אותו בקובץ <code>LocalSettings.php</code>.',
+ 'config-localsettings-cli-upgrade' => 'זוהה קובץ <code>LocalSettings.php</code>.
+כדי לשדרג את ההתקנה הזאת, הריצו את <code>update.php</code> ולא את הקובץ הזה.',
'config-localsettings-key' => 'מפתח השדרוג:',
'config-localsettings-badkey' => 'המפתח שהקלדתם שגוי',
'config-upgrade-key-missing' => 'זוהתה התקנה קיימת של מדיה־ויקי.
-כדי לשדרג את ההתקנה הזאת, אנא כתבו את השורה הבא בתחתית קובץ LocalSettings.php שלכם:
+כדי לשדרג את ההתקנה הזאת, אנא כתבו את השורה הבא בתחתית קובץ <code>LocalSettings.php</code> שלכם:
$1',
- 'config-localsettings-incomplete' => 'נראה שקובץ LocalSettings.php הקיים אינו שלם.
+ 'config-localsettings-incomplete' => 'נראה שקובץ <code>LocalSettings.php</code> הקיים אינו שלם.
המשתנה $1 אינו מוגדר.
-נו לשנות את קובץ LocalSettings.php שלכם כך שהמשתנה הזה יהיה מוגדר וללחוץ "המשך".',
- 'config-localsettings-connection-error' => 'אירעה שגיאה בעת חיבור למסד נתונים עם הגדרות ב־LocalSettings.php או ב־AdminSettings.php. נא לתקן את ההגדרות האלו ולנסות שוב.
+נו לשנות את קובץ <code>LocalSettings.php</code> שלכם כך שהמשתנה הזה יהיה מוגדר וללחוץ "{{int:Config-continue}}".',
+ 'config-localsettings-connection-error' => 'אירעה שגיאה בעת חיבור למסד נתונים עם הגדרות ב־<code>LocalSettings.php</code> או ב־<code>AdminSettings.php</code>. נא לתקן את ההגדרות האלו ולנסות שוב.
$1',
'config-session-error' => 'שגיאה באתחול שיחה: $1',
@@ -6934,7 +7218,7 @@ $1
'config-using531' => 'אי־אפשר להשתמש במדיה־ויקי עם <span dir="ltr">PHP $1</span> בגלל באג בפרמטרים של הפניות (reference parameters) ל־<code dir="ltr">__call()</code>.
שדרגו ל־PHP 5.3.2 או לגרסה גבוהה יותר כדי לתקן את זה ([//bugs.php.net/bug.php?id=50394 bug filed with PHP]) או שַנמכו ל־PHP 5.3.0 כדי לפתור את הבעיה הזאת.
ההתקנה בוטלה.',
- 'config-suhosin-max-value-length' => 'מותקן פה Suhosin והוא מגביל את אורך פרמטר GET ל־$1 בתים. רכיב ResourceLoader של מדיה־ויקי יעקוף את המגלבה הזאת, אבל זה יפגע בביצועים. אם זה בכלל אפשרי, כדי לתקן את הערך של suhosin.get.max_value_length ל־1024 בקובץ php.ini ולהגדיר את ‎$wgResourceLoaderMaxQueryLength לאותו הערך בקובץ LocalSettings.php.',
+ 'config-suhosin-max-value-length' => 'מותקן פה Suhosin והוא מגביל את אורך פרמטר GET ל־$1 בתים. רכיב ResourceLoader של מדיה־ויקי יעקוף את המגלבה הזאת, אבל זה יפגע בביצועים. אם זה בכלל אפשרי, כדאי לתקן את הערך של <code>suhosin.get.max_value_length</code> ל־1024 או יותר בקובץ <code>php.ini</code> ולהגדיר את ‎<code>$wgResourceLoaderMaxQueryLength</code> לאותו הערך בקובץ LocalSettings.php.',
'config-db-type' => 'סוג מסד הנתונים:',
'config-db-host' => 'שרת מסד הנתונים:',
'config-db-host-help' => 'אם שרת מסד הנתונים שלכם נמצא על שרת אחר, הקלידו את שם המחשב או את כתובת ה־IP כאן.
@@ -7015,7 +7299,7 @@ $1
'config-support-postgres' => '$1 הוא מסד נתונים נפוץ בקוד פתוח והוא נפוץ בתור חלופה ל־MySQL (ר׳ [http://www.php.net/manual/en/pgsql.installation.php how to compile PHP with PostgreSQL support]). ייתכן שיש בתצורה הזאת באגים מסוימים והיא לא מומלצת לסביבות מבצעיות.',
'config-support-sqlite' => '* $1 הוא מסד נתונים קליל עם תמיכה טובה מאוד. (ר׳ [http://www.php.net/manual/en/pdo.installation.php How to compile PHP with SQLite support], משתמש ב־PDO)',
'config-support-oracle' => '* $1 הוא מסד נתונים עסקי מסחרי. (ר׳ [http://www.php.net/manual/en/oci8.installation.php How to compile PHP with OCI8 support])',
- 'config-support-ibm_db2' => '* $1 הוא מסד נתונים מסחרי ארגוני.',
+ 'config-support-ibm_db2' => '* $1 הוא מסד נתונים מסחרי ארגוני. ([http://www.php.net/manual/en/ibm-db2.installation.php How to compile PHP with IBM DB2 support])',
'config-header-mysql' => 'הגדרות MySQL',
'config-header-postgres' => 'הגדרות PostgreSQL',
'config-header-sqlite' => 'הגדרות SQLite',
@@ -7082,8 +7366,8 @@ chmod a+w $3</pre></div>',
'config-upgrade-done-no-regenerate' => 'השדרוג הושלם.
עכשיו אפשר [$1 להתחיל להשתמש בוויקי שלכם].',
- 'config-regenerate' => 'לחולל מחדש את LocalSettings.php ←',
- 'config-show-table-status' => 'שאילתת SHOW TABLE STATUS נכשלה!',
+ 'config-regenerate' => 'לחולל מחדש את <code>LocalSettings.php</code> ←',
+ 'config-show-table-status' => 'שאילתת <code>SHOW TABLE STATUS</code> נכשלה!',
'config-unknown-collation' => "'''אזהרה:''' מסד הנתונים משתמש בשיטת מיון שאינה מוּכּרת.",
'config-db-web-account' => 'חשבון במסד הנתונים לגישה מהרשת',
'config-db-web-help' => 'לבחור את שם המשתמש ואת הססמה ששרת הווב ישתמש בו להתחברות לשרת מסד הנתונים בזמן פעילות רגילה של הוויקי.',
@@ -7155,7 +7439,7 @@ chmod a+w $3</pre></div>',
'config-optional-continue' => 'הצגת שאלות נוספות.',
'config-optional-skip' => 'משעמם לי, תתקינו לי כבר את הוויקי הזה.',
'config-profile' => 'תסריט הרשאות משתמשים:',
- 'config-profile-wiki' => 'ויקי מסורתי',
+ 'config-profile-wiki' => 'ויקי פיתוח',
'config-profile-no-anon' => 'נדרשת יצירת חשבון',
'config-profile-fishbowl' => 'עורכים מורשים בלבד',
'config-profile-private' => 'ויקי פרטי',
@@ -7164,7 +7448,7 @@ chmod a+w $3</pre></div>',
עם זאת, אנשים שונים מצאו למדיה־ויקי שימושים מגוּונים ולעתים לא קל לשכנע את כולם ביתרונות של \"דרך הוויקי\" המסורתית. ולכן יש לכם בררה.
-באתר '''{{int:config-profile-wiki}}''' – לכולם יש הרשאה לערוך, אפילו בלי להיכנס לחשבון.
+באתר מסוג '''{{int:config-profile-wiki}}''' – לכולם יש הרשאה לערוך, אפילו בלי להיכנס לחשבון.
באתר וויקי מסוג '''{{int:config-profile-no-anon}}''' יש ביטחון גדול יותר, אבל הגדרה כזאת יכולה להרתיע תורמים מזדמנים.
בתסריט '''{{int:config-profile-fishbowl}}''' רק משתמשים שקיבלו אישור יכולים לערוך, אבל כל הגולשים יכולים לקרוא את הדפים ואת גרסאותיהם הקודמות.
@@ -7255,7 +7539,7 @@ chmod a+w $3</pre></div>',
'config-install-alreadydone' => "'''אזהרה:''' נראה שכבר התקנתם את מדיה־ויקי ואתם מנסים להתקין אותה שוב.
אנה התקדמו לדף הבא.",
'config-install-begin' => 'כשתלחצו על "{{int:config-continue}}", תתחילו את ההתקנה של מדיה־ויקי.
-אם אתם עדיין רוצים לשנות משהו, לחצו על "הקודם".',
+אם אתם עדיין רוצים לשנות משהו, לחצו על "{{int:config-back}}"',
'config-install-step-done' => 'בוצע',
'config-install-step-failed' => 'נכשל',
'config-install-extensions' => 'כולל הרחבות',
@@ -7311,7 +7595,7 @@ $3
'''שימו לב''': אם לא תעשו זאת עכשיו, קובץ ההגדרות המחוּלל לא יהיה זמין לכם שוב.
אחרי שתעשו את זה, תוכלו '''[$2 להיכנס לוויקי שלכם]'''.",
- 'config-download-localsettings' => 'הורדת LocalSettings.php',
+ 'config-download-localsettings' => 'הורדת <code>LocalSettings.php</code>',
'config-help' => 'עזרה',
'config-nofile' => 'הקובץ "$1" לא נמצא. האם הוא נמחק?',
'mainpagetext' => "'''תוכנת מדיה־ויקי הותקנה בהצלחה.'''",
@@ -7320,7 +7604,8 @@ $3
== קישורים שימושיים ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings רשימת ההגדרות]
* [//www.mediawiki.org/wiki/Manual:FAQ שאלות ותשובות על מדיה־ויקי]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce רשימת התפוצה על השקת גרסאות]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce רשימת התפוצה על השקת גרסאות]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources תרגום מדיה־ויקי לשפה שלך]',
);
/** Hindi (हिन्दी)
@@ -7371,6 +7656,7 @@ i [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Vodič za suradnike] za po
/** Upper Sorbian (hornjoserbsce)
* @author Michawiki
+ * @author 아라
*/
$messages['hsb'] = array(
'config-desc' => 'Instalaciski program za MediaWiki',
@@ -7378,19 +7664,19 @@ $messages['hsb'] = array(
'config-information' => 'Informacije',
'config-localsettings-upgrade' => 'Dataja <code>LocalSettings.php</code> je so wotkryła.
Zo by tutu instalaciju aktualizował, zapodaj prošu hódnotu za parameter <code>$wgUpgradeKey</code> do slědowaceho pola.
-Namakaš tón parameter w dataji LocalSettings.php.',
- 'config-localsettings-cli-upgrade' => 'Dataja LocalSettings.php bu wotkryta.
-Zo by tutu instalaciju aktualizował, wuwjedźće update.php',
+Namakaš tón parameter w dataji <code>LocalSettings.php</code>.',
+ 'config-localsettings-cli-upgrade' => 'Dataja <code>LocalSettings.php</code> bu wotkryta.
+Zo by tutu instalaciju aktualizował, wuwjedźće <code>update.php</code>',
'config-localsettings-key' => 'Aktualizaciski kluč:',
'config-localsettings-badkey' => 'Kluč, kotryž sy podał, je wopak',
'config-upgrade-key-missing' => 'Eksistowaca instalacija MediaWiki je so wotkryła.
-Zo by tutu instalaciju aktualizował, staj prošu slědowacu linku deleka w dataji LocalSettings.php:
+Zo by tutu instalaciju aktualizował, staj prošu slědowacu linku deleka w dataji <code>LocalSettings.php</code>:
$1',
- 'config-localsettings-incomplete' => 'Zda so, zo eksistwoaca dataja LocalSettings.php je njedospołna.
+ 'config-localsettings-incomplete' => 'Zda so, zo eksistwoaca dataja <code>LocalSettings.php</code> je njedospołna.
Wariabla $1 njeje nastajena.
-Prošu změń dataju LocalSettings.php, zo by so tuta wariabla nastajiła a klikń na "Dale".',
- 'config-localsettings-connection-error' => 'Při zwjazowanju z datowej banku z pomocu nastajenjow podatych w LocalSettings.php abo AdminSettings.php je zmylk wustupił. Prošu skoriguj tute nastajenja a spytaj hišće raz.
+Prošu změń dataju <code>LocalSettings.php</code>, zo by so tuta wariabla nastajiła a klikń na "{{int:Config-continue}}".',
+ 'config-localsettings-connection-error' => 'Při zwjazowanju z datowej banku z pomocu nastajenjow podatych w <code>LocalSettings.php</code> abo <code>AdminSettings.php</code> je zmylk wustupił. Prošu skoriguj tute nastajenja a spytaj hišće raz.
$1',
'config-session-error' => 'Zmylk při startowanju posedźenja: $1',
@@ -7514,16 +7800,13 @@ Změń ju jenož, jeli su přeswědčiwe přičiny za to.',
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-mysql' => '* $1 je primarny cil za MediaWiki a podpěruje so najlěpje ([http://www.php.net/manual/en/mysql.installation.php Nawod ke kompilowanju PHP z MySQL-podpěru])',
'config-support-postgres' => '* $1 je popularny system datoweje banki zjawneho žórła jako alternatiwa k MySQL ([http://www.php.net/manual/en/pgsql.installation.php nawod za kompilowanje PHP z podpěru PostgreSQL]). Móhło hišće někotre zmylki eksistować, a njeporuča so jón w produktiwnej wokolinje wužiwać.',
'config-support-oracle' => '* $1 je komercielna předewzaćelska datowa banka. ([http://www.php.net/manual/en/oci8.installation.php Nawod za kompilowanje PHP z OCI8-podpěru])',
- 'config-support-ibm_db2' => '* $1 je komercielna předewzaćelska datowa banka.',
'config-header-mysql' => 'Nastajenja MySQL',
'config-header-postgres' => 'Nastajenja PostgreSQL',
'config-header-sqlite' => 'Nastajenja SQLite',
'config-header-oracle' => 'Nastajenja Oracle',
- 'config-header-ibm_db2' => 'Nastajenja IBM DB2',
'config-invalid-db-type' => 'Njepłaćiwy typ datoweje banki',
'config-missing-db-name' => 'Dyrbiš hódnotu za "Mjeno datoweje banki" zapodać',
'config-missing-db-host' => 'Dyrbiš hódnotu za "Database host" zapodać',
@@ -7560,8 +7843,8 @@ Zo by je na MediaWiki $1 aktualizował, klikń na '''Dale'''.",
'config-upgrade-done-no-regenerate' => 'Aktualizacija dokónčena.
Móžeš nětko [$1 swój wiki wužiwać].',
- 'config-regenerate' => 'LocalSettings.php znowa wutworić →',
- 'config-show-table-status' => 'Naprašowanje SHOW TABLE STATUS je so njeporadźiło!',
+ 'config-regenerate' => '<code>LocalSettings.php</code> znowa wutworić →',
+ 'config-show-table-status' => 'Naprašowanje <code>SHOW TABLE STATUS</code> je so njeporadźiło!',
'config-unknown-collation' => "'''Warnowanje:''' Datowa banka njeznatu kolaciju wužiwa.",
'config-db-web-account' => 'Konto datoweje banki za webpřistup',
'config-db-web-help' => 'wubjer wužiwarske mjeno a hesło, kotrejž webserwer budźe wužiwać, zo by z serwerom datoweje banki za wšědnu operaciju zwjazać',
@@ -7610,7 +7893,7 @@ Móžeš nětko zbytnu konfiguraciju přeskočić a wiki hnydom instalować.',
'config-optional-continue' => 'Dalše prašenja?',
'config-optional-skip' => 'Instaluj nětko wiki.',
'config-profile' => 'Profil wužiwarskich prawow:',
- 'config-profile-wiki' => 'Tradicionelny wiki',
+ 'config-profile-wiki' => 'Zjawny wiki',
'config-profile-no-anon' => 'Załoženje konto je trěbne',
'config-profile-fishbowl' => 'Jenož awtorizowani wobdźěłarjo',
'config-profile-private' => 'Priwatny wiki',
@@ -7668,7 +7951,7 @@ To móže sej přidatnu konfiguraciju wužadać, ale móžeš je nětko zmóžni
'config-install-alreadydone' => "'''Warnowanje:''' Zda so, zo sy hižo MediaWiki instalował a pospytuješ jón znowa instalować.
Prošu pokročuj z přichodnej stronu.",
'config-install-begin' => 'Přez kliknjenje na "{{int:config-continue}}" budźe so instalacija MediaWiki startować.
-Jeli hišće chceš něšto změnić, klikń na "Wróćo".',
+Jeli hišće chceš něšto změnić, klikń na "{{int:config-back}}".',
'config-install-step-done' => 'dokónčene',
'config-install-step-failed' => 'njeporadźiło',
'config-install-extensions' => 'Inkluziwnje rozšěrjenja',
@@ -7704,7 +7987,7 @@ Standardna lisćina sp přeskakuje.",
'config-install-mainpage' => 'Hłowna strona so ze standardnym wobsahom wutworja',
'config-install-extension-tables' => 'Tabele za zmóžnjene rozšěrjenja so tworja',
'config-install-mainpage-failed' => 'Powěsć njeda so zasunyć: $1',
- 'config-download-localsettings' => 'LocalSettings.php sćahnyć',
+ 'config-download-localsettings' => '<code>LocalSettings.php</code> sćahnyć',
'config-help' => 'pomoc',
'config-nofile' => 'Dataja "$1" njeje so namakała. Je so zhašała?',
'mainpagetext' => "'''MediaWiki bu wuspěšnje instalowany.'''",
@@ -7714,7 +7997,8 @@ Standardna lisćina sp přeskakuje.",
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Wo nastajenjach]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources MediaWiki za twoju rěč lokalizować]',
);
/** Haitian (Kreyòl ayisyen)
@@ -7734,25 +8018,26 @@ $messages['ht'] = array(
/** Hungarian (magyar)
* @author Dani
* @author Glanthor Reviol
+ * @author 아라
*/
$messages['hu'] = array(
'config-desc' => 'A MediaWiki telepítője',
'config-title' => 'A MediaWiki $1 telepítése',
'config-information' => 'Információ',
'config-localsettings-upgrade' => 'Már létezik a <code>LocalSettings.php</code> fájl.
-A telepített szoftver frissítéséhez írd be az alábbi mezőbe a <code>$wgUpgradeKey</code> beállítás értékét, melyet a LocalSettings.php nevű fájlban találhatsz meg.',
- 'config-localsettings-cli-upgrade' => 'A LocalSettings.php fájl megtalálható.
-A telepített rendszer frissítéséhez futtasd az update.php-t.',
+A telepített szoftver frissítéséhez írd be az alábbi mezőbe a <code>$wgUpgradeKey</code> beállítás értékét, melyet a <code>LocalSettings.php</code> nevű fájlban találhatsz meg.',
+ 'config-localsettings-cli-upgrade' => 'A <code>LocalSettings.php</code> fájl megtalálható.
+A telepített rendszer frissítéséhez futtasd az <code>update.php</code>-t.',
'config-localsettings-key' => 'Frissítési kulcs:',
'config-localsettings-badkey' => 'A megadott kulcs érvénytelen.',
'config-upgrade-key-missing' => 'A telepítő a MediaWiki meglévő példányát észlelte.
-A telepített rendszer frissítéséhez helyezd el az alábbi sort a LocalSettings.php végére:
+A telepített rendszer frissítéséhez helyezd el az alábbi sort a <code>LocalSettings.php</code> végére:
$1',
- 'config-localsettings-incomplete' => 'A meglévő LocalSettings.php hiányosnak tűnik.
+ 'config-localsettings-incomplete' => 'A meglévő <code>LocalSettings.php</code> hiányosnak tűnik.
A(z) $1 változó értéke nincs beállítva.
-Módosítsd a LocalSettings.php fájlt úgy, hogy ez a változó be legyen állítva, majd kattints a „Folytatás” gombra.',
- 'config-localsettings-connection-error' => 'Nem sikerült csatlakozni az adatbázishoz a LocalSettings.php-ben vagy az AdminSettings.php-ben megadott adatokkal. Ellenőrizd a beállításokat, majd próbáld újra.
+Módosítsd a <code>LocalSettings.php</code> fájlt úgy, hogy ez a változó be legyen állítva, majd kattints a „{{int:Config-continue}}” gombra.',
+ 'config-localsettings-connection-error' => 'Nem sikerült csatlakozni az adatbázishoz a <code>LocalSettings.php</code>-ben vagy az <code>AdminSettings.php</code>-ben megadott adatokkal. Ellenőrizd a beállításokat, majd próbáld újra.
$1',
'config-session-error' => 'Nem sikerült elindítani a munkamenetet: $1',
@@ -7877,7 +8162,7 @@ Telepítés megszakítva.',
'config-using531' => 'A MediaWiki nem használható a PHP $1-es verziójával, mert hiba van a <code>__call()</code> függvénynek átadott referenciaparaméterekkel.
A probléma kiküszöböléséhez frissíts a PHP 5.3.2-es verziójára, vagy használd a korábbi, 5.3.0-ásat.
Telepítés megszakítva.',
- 'config-suhosin-max-value-length' => 'A Suhosin telepítve van, és a GET paraméter hosszát $1 bájtra korlátozza. A MediaWiki erőforrásbetöltő összetevője megkerüli a problémát, de így csökkenni fog a teljesítmény. Ha lehetséges, állítsd be a suhosin.get.max_value_length értékét legalább 1024-re a php.iniben, és állítsd be a $wgResourceLoaderMaxQueryLength változót ugyanerre az értékre a LocalSettings.php-ben.',
+ 'config-suhosin-max-value-length' => 'A Suhosin telepítve van, és a GET paraméter hosszát $1 bájtra korlátozza. A MediaWiki erőforrásbetöltő összetevője megkerüli a problémát, de így csökkenni fog a teljesítmény. Ha lehetséges, állítsd be a <code>suhosin.get.max_value_length</code> értékét legalább 1024-re a <code>php.ini</code>ben, és állítsd be a <code>$wgResourceLoaderMaxQueryLength</code> változót ugyanerre az értékre a LocalSettings.php-ben.', # Fuzzy
'config-db-type' => 'Adatbázis típusa:',
'config-db-host' => 'Adatbázis hosztneve:',
'config-db-host-help' => 'Ha az adatbázisszerver másik szerveren található, add meg a hosztnevét vagy az IP-címét.
@@ -7955,7 +8240,7 @@ Ha az alábbi listán nem találod azt a rendszert, melyet használni szeretnél
'config-support-postgres' => '* A $1 népszerű, nyílt forráskódú adatbázisrendszer, a MySQL alternatívája ([http://www.php.net/manual/en/pgsql.installation.php Hogyan fordítható a PHP PostgreSQL-támogatással]). Több apró, javítatlan hiba is előfordulhat, így nem ajánlott éles környezetben használni.',
'config-support-sqlite' => '* Az $1 egy könnyű, nagyon jól támogatott adatbázisrendszer. ([http://www.php.net/manual/en/pdo.installation.php Hogyan fordítható a PHP SQLite-támogatással], PDO-t használ)',
'config-support-oracle' => '* Az $1 kereskedelmi, vállalati adatbázisrendszer. ([http://www.php.net/manual/en/oci8.installation.php Hogyan fordítható a PHP OCI8-támogatással])',
- 'config-support-ibm_db2' => '* Az $1 kereskedelmi vállalati adatbázisrendszer.',
+ 'config-support-ibm_db2' => '* Az $1 kereskedelmi vállalati adatbázisrendszer.', # Fuzzy
'config-header-mysql' => 'MySQL-beállítások',
'config-header-postgres' => 'PostgreSQL-beállítások',
'config-header-sqlite' => 'SQLite-beállítások',
@@ -8022,8 +8307,8 @@ Ez '''nem ajánlott''', csak akkor, ha problémák vannak a wikivel.",
'config-upgrade-done-no-regenerate' => "A frissítés befejeződött.
Most már '''[$1 beléphetsz a wikibe]'''.",
- 'config-regenerate' => 'LocalSettings.php elkészítése újra →',
- 'config-show-table-status' => 'A SHOW TABLE STATUS lekérdezés nem sikerült!',
+ 'config-regenerate' => '<code>LocalSettings.php</code> elkészítése újra →',
+ 'config-show-table-status' => 'A <code>SHOW TABLE STATUS</code> lekérdezés nem sikerült!',
'config-unknown-collation' => "'''Figyelmeztetés:''' az adatbázis ismeretlen egybevetést használ.",
'config-db-web-account' => 'A webes hozzáférésnél használt adatbázisfiók',
'config-db-web-help' => 'Add meg azt a felhasználónevet és jelszót, amit a webszerver a wiki általános működése során használ a csatlakozáshoz.',
@@ -8094,7 +8379,7 @@ A további konfigurációt kihagyhatod, és most azonnal elindíthatod a wiki te
'config-optional-continue' => 'További információk megadása.',
'config-optional-skip' => 'Épp elég volt, települjön a wiki!',
'config-profile' => 'Felhasználói jogosultságok profilja:',
- 'config-profile-wiki' => 'Hagyományos wiki',
+ 'config-profile-wiki' => 'Hagyományos wiki', # Fuzzy
'config-profile-no-anon' => 'Felhasználói fiók létrehozása szükséges',
'config-profile-fishbowl' => 'Csak engedélyezett szerkesztők',
'config-profile-private' => 'Privát wiki',
@@ -8108,7 +8393,7 @@ Választhatsz!
Lehetőség van arra is, hogy '''{{lc:{{int:config-profile-fishbowl}}}}''' módosíthassák a lapokat, de a nyilvánosság ekkor megtekintheti a lapokat és azok laptörténetét is. '''{{int:config-profile-private}}''' esetén csak az engedélyezett szerkesztők tekinthetik meg a lapokat, és ugyanez a csoport szerkeszthet.
-Telepítés után jóval összetettebb jogosultságrendszer állítható össze, további információ a [//www.mediawiki.org/wiki/Manual:User_rights kézikönyv kapcsolódó bejegyzésében].",
+Telepítés után jóval összetettebb jogosultságrendszer állítható össze, további információ a [//www.mediawiki.org/wiki/Manual:User_rights kézikönyv kapcsolódó bejegyzésében].", # Fuzzy
'config-license' => 'Szerzői jog és licenc:',
'config-license-none' => 'Nincs licencjelzés',
'config-license-cc-by-sa' => 'Creative Commons Nevezd meg! - Így add tovább!',
@@ -8192,7 +8477,7 @@ Lehetséges, hogy további beállításra lesz szükség hozzájuk, de már most
'config-install-alreadydone' => "'''Figyelmeztetés:''' Úgy tűnik, hogy a MediaWiki telepítve van, és te ismét megpróbálod telepíteni.
Folytasd a következő oldalon.",
'config-install-begin' => 'A „{{int:config-continue}}” gomb megnyomása elindítja a MediaWiki telepítését.
-Ha szeretnél módosítani a beállításokon, kattints a vissza gombra.',
+Ha szeretnél módosítani a beállításokon, kattints a vissza gombra.', # Fuzzy
'config-install-step-done' => 'kész',
'config-install-step-failed' => 'sikertelen',
'config-install-extensions' => 'Kiterjesztések beillesztése',
@@ -8243,7 +8528,7 @@ $3
'''Megjegyzés''': Ha ezt most nem teszed meg, és kilépsz a telepítésből, az elkészített konfigurációs fájlt nem tudod elérni a későbbiekben.
Ha végeztél a fájl elhelyezésével, '''[$2 beléphetsz a wikibe]'''.",
- 'config-download-localsettings' => 'LocalSettings.php letöltése',
+ 'config-download-localsettings' => '<code>LocalSettings.php</code> letöltése',
'config-help' => 'segítség',
'mainpagetext' => "'''A MediaWiki telepítése sikeresen befejeződött.'''",
'mainpagedocfooter' => "Ha segítségre van szükséged a wikiszoftver használatához, akkor keresd fel a [//meta.wikimedia.org/wiki/Help:Contents User's Guide] oldalt.
@@ -8251,7 +8536,7 @@ Ha végeztél a fájl elhelyezésével, '''[$2 beléphetsz a wikibe]'''.",
== Alapok (angol nyelven) ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Beállítások listája]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki GyIK]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-kiadások levelezőlistája]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-kiadások levelezőlistája]", # Fuzzy
);
/** Magyar (magázó) (Magyar (magázó))
@@ -8330,7 +8615,7 @@ Ha ezzel készen van, '''[$2 beléphet a wikibe]'''.", # Fuzzy
== Alapok (angol nyelven) ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Beállítások listája]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki GyIK]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-kiadások levelezőlistája]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-kiadások levelezőlistája]", # Fuzzy
);
/** Armenian (Հայերեն)
@@ -8348,6 +8633,7 @@ $messages['hy'] = array(
/** Interlingua (interlingua)
* @author McDutchie
+ * @author 아라
*/
$messages['ia'] = array(
'config-desc' => 'Le installator de MediaWiki',
@@ -8355,19 +8641,19 @@ $messages['ia'] = array(
'config-information' => 'Information',
'config-localsettings-upgrade' => 'Un file <code>LocalSettings.php</code> ha essite detegite.
Pro actualisar iste installation, per favor entra le valor de <code>$wgUpgradeKey</code> in le quadro hic infra.
-Iste se trova in LocalSettings.php.',
- 'config-localsettings-cli-upgrade' => 'Un file LocalSettings.php file ha essite detegite.
-Pro actualisar iste installation, per favor executa upgrade.php.',
+Iste se trova in <code>LocalSettings.php</code>.',
+ 'config-localsettings-cli-upgrade' => 'Un file <code>LocalSettings.php</code> file ha essite detegite.
+Pro actualisar iste installation, per favor executa <code>update.php</code>.',
'config-localsettings-key' => 'Clave de actualisation:',
'config-localsettings-badkey' => 'Le clave que tu forniva es incorrecte',
'config-upgrade-key-missing' => 'Un installation existente de MediaWiki ha essite detegite.
-Pro actualisar iste installation, es necessari adjunger le sequente linea al fin del file LocalSettings.php:
+Pro actualisar iste installation, es necessari adjunger le sequente linea al fin del file <code>LocalSettings.php</code>:
$1',
- 'config-localsettings-incomplete' => 'Le file LocalSettings.php existente pare esser incomplete.
+ 'config-localsettings-incomplete' => 'Le file <code>LocalSettings.php</code> existente pare esser incomplete.
Le variabile $1 non es definite.
-Per favor cambia LocalSettings.php de sorta que iste variabile es definite, e clicca "Continuar".',
- 'config-localsettings-connection-error' => 'Un error esseva incontrate durante le connexion al base de datos usante le configurationes specificate in LocalSettings.php o AdminSettings.php. Per favor repara iste configurationes e tenta lo de novo.
+Per favor cambia <code>LocalSettings.php</code> de sorta que iste variabile es definite, e clicca "{{int:Config-continue}}".',
+ 'config-localsettings-connection-error' => 'Un error esseva incontrate durante le connexion al base de datos usante le configurationes specificate in <code>LocalSettings.php</code> o <code>AdminSettings.php</code>. Per favor repara iste configurationes e tenta lo de novo.
$1',
'config-session-error' => 'Error al comenciamento del session: $1',
@@ -8501,7 +8787,7 @@ Installation abortate.',
'config-using531' => 'MediaWiki non pote esser usate con PHP $1 a causa de un defecto concernente parametros de referentia a <code>__call()</code>.
Actualisa a PHP 5.3.2 o plus recente, o retrograda a PHP 5.3.0 pro remediar isto.
Installation abortate.',
- 'config-suhosin-max-value-length' => 'Suhosin es installate e limita le longitude del parametro GET a $1 bytes. Le componente ResourceLoader de MediaWiki pote contornar iste limite, ma isto degradara le rendimento. Si possibile, tu deberea mitter suhosin.get.max_value_length a 1024 o plus in php.ini , e mitter $wgResourceLoaderMaxQueryLength al mesme valor in LocalSettings.php .',
+ 'config-suhosin-max-value-length' => 'Suhosin es installate e limita le longitude del parametro GET a $1 bytes. Le componente ResourceLoader de MediaWiki pote contornar iste limite, ma isto degradara le rendimento. Si possibile, tu deberea mitter <code>suhosin.get.max_value_length</code> a 1024 o plus in <code>php.ini</code> , e mitter <code>$wgResourceLoaderMaxQueryLength</code> al mesme valor in LocalSettings.php .', # Fuzzy
'config-db-type' => 'Typo de base de datos:',
'config-db-host' => 'Servitor de base de datos:',
'config-db-host-help' => 'Si tu servitor de base de datos es in un altere servitor, entra hic le nomine o adresse IP del servitor.
@@ -8585,7 +8871,7 @@ Si tu non vide hic infra le systema de base de datos que tu tenta usar, alora se
'config-support-postgres' => '* $1 es un systema de base de datos popular e open source, alternativa a MySQL ([http://www.php.net/manual/en/pgsql.installation.php como compilar PHP con supporto de PostgreSQL]). Es possibile que resta alcun minor defectos non resolvite, dunque illo non es recommendate pro uso in un ambiente de production.',
'config-support-sqlite' => '* $1 es un systema de base de datos legier que es multo ben supportate. ([http://www.php.net/manual/en/pdo.installation.php Como compilar PHP con supporto de SQLite], usa PDO)',
'config-support-oracle' => '* $1 es un banca de datos commercial pro interprisas. ([http://www.php.net/manual/en/oci8.installation.php Como compilar PHP con supporto de OCI8])',
- 'config-support-ibm_db2' => '* $1 es un systema commercial de base de datos pro interprisas.',
+ 'config-support-ibm_db2' => '* $1 es un systema commercial de base de datos pro interprisas.', # Fuzzy
'config-header-mysql' => 'Configuration de MySQL',
'config-header-postgres' => 'Configuration de PostgreSQL',
'config-header-sqlite' => 'Configuration de SQLite',
@@ -8652,8 +8938,8 @@ Isto '''non es recommendate''' si tu non ha problemas con tu wiki.",
'config-upgrade-done-no-regenerate' => 'Actualisation complete.
Tu pote ora [$1 comenciar a usar tu wiki].',
- 'config-regenerate' => 'Regenerar LocalSettings.php →',
- 'config-show-table-status' => 'Le consulta SHOW TABLE STATUS falleva!',
+ 'config-regenerate' => 'Regenerar <code>LocalSettings.php</code> →',
+ 'config-show-table-status' => 'Le consulta <code>SHOW TABLE STATUS</code> falleva!',
'config-unknown-collation' => "'''Aviso:''' Le base de datos usa un collation non recognoscite.",
'config-db-web-account' => 'Conto de base de datos pro accesso via web',
'config-db-web-help' => 'Selige le nomine de usator e contrasigno que le servitor web usara pro connecter al servitor de base de datos, durante le operation ordinari del wiki.',
@@ -8725,7 +9011,7 @@ Tu pote ora saltar le configuration remanente e installar le wiki immediatemente
'config-optional-continue' => 'Pone me plus questiones.',
'config-optional-skip' => 'Isto me es jam tediose. Simplemente installa le wiki.',
'config-profile' => 'Profilo de derectos de usator:',
- 'config-profile-wiki' => 'Wiki traditional',
+ 'config-profile-wiki' => 'Wiki traditional', # Fuzzy
'config-profile-no-anon' => 'Creation de conto obligatori',
'config-profile-fishbowl' => 'Modificatores autorisate solmente',
'config-profile-private' => 'Wiki private',
@@ -8741,7 +9027,7 @@ Un wiki con '''{{int:config-profile-no-anon}}''' attribue additional responsabil
Le scenario '''{{int:config-profile-fishbowl}}''' permitte al usatores approbate de modificar, ma le publico pote vider le paginas, includente lor historia.
Un '''{{int:config-profile-private}}''' permitte solmente al usatores approbate de vider le paginas e de modificar los.
-Configurationes de derectos de usator plus complexe es disponibile post installation, vide le [//www.mediawiki.org/wiki/Manual:User_rights pertinente section del manual].",
+Configurationes de derectos de usator plus complexe es disponibile post installation, vide le [//www.mediawiki.org/wiki/Manual:User_rights pertinente section del manual].", # Fuzzy
'config-license' => 'Copyright e licentia:',
'config-license-none' => 'Nulle licentia in pede de paginas',
'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
@@ -8826,7 +9112,7 @@ Istes pote requirer additional configuration, ma tu pote activar los ora.',
'config-install-alreadydone' => "'''Aviso:''' Il pare que tu ha jam installate MediaWiki e tenta installar lo de novo.
Per favor continua al proxime pagina.",
'config-install-begin' => 'Un clic sur "{{int:config-continue}}" comencia le installation de MediaWiki.
-Pro facer alterationes, clicca sur "Retro".',
+Pro facer alterationes, clicca sur "Retro".', # Fuzzy
'config-install-step-done' => 'finite',
'config-install-step-failed' => 'fallite',
'config-install-extensions' => 'Include le extensiones',
@@ -8883,7 +9169,7 @@ $3
'''Nota''': Si tu non discarga iste file de configuration ora, illo non essera disponibile plus tarde.
Post facer isto, tu pote '''[$2 entrar in tu wiki]'''.",
- 'config-download-localsettings' => 'Discargar LocalSettings.php',
+ 'config-download-localsettings' => 'Discargar <code>LocalSettings.php</code>',
'config-help' => 'adjuta',
'config-nofile' => 'Le file "$1" non poteva esser trovate. Ha illo essite delite?',
'mainpagetext' => "'''MediaWiki ha essite installate con successo.'''",
@@ -8892,7 +9178,7 @@ Post facer isto, tu pote '''[$2 entrar in tu wiki]'''.",
== Pro initiar ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista de configurationes]
* [//www.mediawiki.org/wiki/Manual:FAQ FAQ a proposito de MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de diffusion pro annuncios de nove versiones de MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de diffusion pro annuncios de nove versiones de MediaWiki]', # Fuzzy
);
/** Indonesian (Bahasa Indonesia)
@@ -8900,6 +9186,7 @@ Post facer isto, tu pote '''[$2 entrar in tu wiki]'''.",
* @author IvanLanin
* @author Kenrick95
* @author Reedy
+ * @author 아라
*/
$messages['id'] = array(
'config-desc' => 'Penginstal untuk MediaWiki',
@@ -8907,19 +9194,19 @@ $messages['id'] = array(
'config-information' => 'Informasi',
'config-localsettings-upgrade' => 'Berkas <code>LocalSettings.php</code> sudah ada.
Untuk memutakhirkan instalasi ini, masukkan nilai <code>$wgUpgradeKey</code> dalam kotak yang tersedia di bawah ini.
-Anda dapat menemukan nilai tersebut dalam LocalSettings.php.',
- 'config-localsettings-cli-upgrade' => 'Berkas LocalSettings.php terdeteksi.
-Untuk meningkatkan versi, harap jalankan update.php.',
+Anda dapat menemukan nilai tersebut dalam <code>LocalSettings.php</code>.',
+ 'config-localsettings-cli-upgrade' => 'Berkas <code>LocalSettings.php</code> terdeteksi.
+Untuk meningkatkan versi, harap jalankan <code>update.php</code>.',
'config-localsettings-key' => 'Kunci pemutakhiran:',
'config-localsettings-badkey' => 'Kunci yang Anda berikan tidak benar',
'config-upgrade-key-missing' => 'Suatu instalasi MediaWiki telah terdeteksi.
-Untuk memutakhirkan instalasi ini, silakan masukkan baris berikut di bagian bawah LocalSettings.php Anda:
+Untuk memutakhirkan instalasi ini, silakan masukkan baris berikut di bagian bawah <code>LocalSettings.php</code> Anda:
$1',
- 'config-localsettings-incomplete' => 'LocalSettings.php yang ada tampaknya tidak lengkap.
+ 'config-localsettings-incomplete' => '<code>LocalSettings.php</code> yang ada tampaknya tidak lengkap.
Variabel $1 tidak diatur.
-Silakan ubah LocalSettings.php untuk mengatur variabel ini dan klik "Lanjutkan".',
- 'config-localsettings-connection-error' => 'Timbul galat saat menghubungkan ke basis data dengan menggunakan setelan yang ditentukan di LocalSettings.php atau AdminSettings.php. Harap perbaiki setelan ini dan coba lagi.
+Silakan ubah <code>LocalSettings.php</code> untuk mengatur variabel ini dan klik "{{int:Config-continue}}".',
+ 'config-localsettings-connection-error' => 'Timbul galat saat menghubungkan ke basis data dengan menggunakan setelan yang ditentukan di <code>LocalSettings.php</code> atau <code>AdminSettings.php</code>. Harap perbaiki setelan ini dan coba lagi.
$1',
'config-session-error' => 'Kesalahan sesi mulai: $1',
@@ -9043,7 +9330,7 @@ Instalasi dibatalkan.',
'config-using531' => 'MediaWiki tidak dapat dijalankan dengan PHP $1 karena bug yang melibatkan parameter referensi untuk <code>__call()</code> .
Tingkatkan ke PHP 5.3.2 atau lebih baru, atau turunkan ke PHP versi 5.3.0 untuk menyelesaikan hal ini.
Instalasi dibatalkan.',
- 'config-suhosin-max-value-length' => 'Suhosin terpasang dan membatasi panjang parameter GET sebesar $1 bita. Komponen ResourceLoader MediaWiki akan mengatasi batasan ini, tapi penanganannya akan menurunkan kinerja. Jika memungkinkan, Anda sebaiknya menetapkan nilai suhosin.get.max_value_length menjadi 1024 atau lebih tinggi dalam php.ini dan menyetel $wgResourceLoaderMaxQueryLength dengan nilai yang sama dalam LocalSettings.php.',
+ 'config-suhosin-max-value-length' => 'Suhosin terpasang dan membatasi panjang parameter GET sebesar $1 bita. Komponen ResourceLoader MediaWiki akan mengatasi batasan ini, tapi penanganannya akan menurunkan kinerja. Jika memungkinkan, Anda sebaiknya menetapkan nilai <code>suhosin.get.max_value_length</code> menjadi 1024 atau lebih tinggi dalam <code>php.ini</code> dan menyetel <code>$wgResourceLoaderMaxQueryLength</code> dengan nilai yang sama dalam LocalSettings.php.', # Fuzzy
'config-db-type' => 'Jenis basis data:',
'config-db-host' => 'Inang basis data:',
'config-db-host-help' => 'Jika server basis data Anda berada di server yang berbeda, masukkan nama inang atau alamat IP di sini.
@@ -9115,7 +9402,6 @@ Pertimbangkan untuk menempatkan basis data di tempat lain, misalnya di <code>/va
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'MediaWiki mendukung sistem basis data berikut:
$1
@@ -9125,12 +9411,10 @@ Jika Anda tidak melihat sistem basis data yang Anda gunakan tercantum di bawah i
'config-support-postgres' => '* $1 adalah sistem basis data sumber terbuka populer sebagai alternatif untuk MySQL ([http://www.php.net/manual/en/pgsql.installation.php cara mengompilasi PHP dengan dukungan PostgreSQL]). Mungkin ada beberapa bug terbuka dan alternatif ini tidak direkomendasikan untuk dipakai dalam lingkungan produksi.',
'config-support-sqlite' => '* $1 adalah sistem basis data yang ringan yang sangat baik dukungannya. ([http://www.php.net/manual/en/pdo.installation.php cara mengompilasi PHP dengan dukungan SQLite], menggunakan PDO)',
'config-support-oracle' => '* $1 adalah basis data komersial untuka perusahaan. ([http://www.php.net/manual/en/oci8.installation.php cara mengompilasi PHP dengan dukungan OCI8])',
- 'config-support-ibm_db2' => '* $1 adalah basis data-perusahaan komersial.',
'config-header-mysql' => 'Pengaturan MySQL',
'config-header-postgres' => 'Pengaturan PostgreSQL',
'config-header-sqlite' => 'Pengaturan SQLite',
'config-header-oracle' => 'Pengaturan Oracle',
- 'config-header-ibm_db2' => 'Pengaturan IBM DB2',
'config-invalid-db-type' => 'Jenis basis data tidak sah',
'config-missing-db-name' => 'Anda harus memasukkan nilai untuk "Nama basis data"',
'config-missing-db-host' => 'Anda harus memasukkan nilai untuk "Inang basis data"',
@@ -9192,8 +9476,8 @@ Tindakan ini '''tidak dianjurkan''' kecuali jika Anda mengalami masalah dengan w
'config-upgrade-done-no-regenerate' => 'Pemutakhiran selesai.
Anda sekarang dapat [$1 mulai menggunakan wiki Anda].',
- 'config-regenerate' => 'Regenerasi LocalSettings.php →',
- 'config-show-table-status' => 'Kueri SHOW TABLE STATUS gagal!',
+ 'config-regenerate' => 'Regenerasi <code>LocalSettings.php</code> →',
+ 'config-show-table-status' => 'Kueri <code>SHOW TABLE STATUS</code> gagal!',
'config-unknown-collation' => "'''Peringatan:''' basis data menggunakan kolasi yang tidak dikenal.",
'config-db-web-account' => 'Akun basis data untuk akses web',
'config-db-web-help' => 'Masukkan nama pengguna dan sandi yang akan digunakan server web untuk terhubung ke server basis data saat operasi normal wiki.',
@@ -9215,7 +9499,6 @@ Basis data MyISAM cenderung lebih sering rusak daripada basis data InnoDB.",
Ini lebih efisien daripada modus UTF-8 MySQL dan memungkinkan Anda untuk menggunakan ragam penuh karakter Unicode.
Dalam '''modus UTF-8''', MySQL akan tahu apa set karakter data dan dapat menampilkan dan mengubahnya sesuai keperluan, tetapi tidak akan mengizinkan Anda menyimpan karakter di atas [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
- 'config-ibm_db2-low-db-pagesize' => "Basis data DB2 Anda tidak memiliki pagesize yang cukup untuk tablespace bawaan. Pagesize harus sama atau lebih dari '''32K'''.",
'config-site-name' => 'Nama wiki:',
'config-site-name-help' => 'Ini akan muncul di bilah judul peramban dan di berbagai tempat lainnya.',
'config-site-name-blank' => 'Masukkan nama situs.',
@@ -9256,7 +9539,7 @@ Anda sekarang dapat melewati sisa konfigurasi dan menginstal wiki sekarang.',
'config-optional-continue' => 'Berikan saya pertanyaan lagi.',
'config-optional-skip' => 'Saya sudah bosan, instal saja wikinya.',
'config-profile' => 'Profil hak pengguna:',
- 'config-profile-wiki' => 'Wiki tradisional',
+ 'config-profile-wiki' => 'Wiki tradisional', # Fuzzy
'config-profile-no-anon' => 'Pembuatan akun diperlukan',
'config-profile-fishbowl' => 'Khusus penyunting terdaftar',
'config-profile-private' => 'Wiki pribadi',
@@ -9270,7 +9553,7 @@ Namun, berbagai kegunaan lain dari MediaWiki telah ditemukan, dan kadang tidak m
'''{{int:config-profile-fishbowl}}''' memungkinkan pengguna yang disetujui untuk menyunting, tetapi publik dapat melihat halaman, termasuk riwayatnya.
'''{{int:config-profile-private}}''' hanya memungkinkan pengguna yang disetujui untuk melihat dan menyunting halaman.
-Konfigurasi hak pengguna yang lebih kompleks tersedia setelah instalasi. Lihat [//www.mediawiki.org/wiki/Manual:User_rights/id entri manual terkait].",
+Konfigurasi hak pengguna yang lebih kompleks tersedia setelah instalasi. Lihat [//www.mediawiki.org/wiki/Manual:User_rights/id entri manual terkait].", # Fuzzy
'config-license' => 'Hak cipta dan lisensi:',
'config-license-none' => 'Tidak ada lisensi',
'config-license-cc-by-sa' => 'Creative Commons Atribusi Berbagi Serupa',
@@ -9355,7 +9638,7 @@ Ekstensi tersebut mungkin memerlukan konfigurasi tambahan, tetapi Anda dapat men
'config-install-alreadydone' => "'''Peringatan:''' Anda tampaknya telah menginstal MediaWiki dan mencoba untuk menginstalnya lagi.
Lanjutkan ke halaman berikutnya.",
'config-install-begin' => 'Dengan menekan "{{int:config-continue}}", Anda akan memulai instalasi MediaWiki.
-Jika Anda masih ingin membuat perubahan, tekan "{{int:config-back}}".',
+Jika Anda masih ingin membuat perubahan, tekan "{{int:config-back}}".', # Fuzzy
'config-install-step-done' => 'selesai',
'config-install-step-failed' => 'gagal',
'config-install-extensions' => 'Termasuk ekstensi',
@@ -9383,7 +9666,8 @@ Mengabaikan daftar bawaan.",
'config-install-keys' => 'Membuat kunci rahasia',
'config-insecure-keys' => "'''Peringatan:''' {{PLURAL:$2|Suatu|Beberapa}} kunci aman ($1) yang dibuat selama instalasi {{PLURAL:$2|tidak|tidak}} benar-benar aman. Pertimbangkan untuk mengubah {{PLURAL:$2|kunci|kunci-kunci}} tersebut secara manual.",
'config-install-sysop' => 'Membuat akun pengguna pengurus',
- 'config-install-subscribe-fail' => 'Tidak dapat berlangganan mediawiki-announce', # Fuzzy
+ 'config-install-subscribe-fail' => 'Tidak dapat berlangganan mediawiki-announce: $1',
+ 'config-install-subscribe-notpossible' => 'cURL tidak diinstal dan allow_url_fopen tidak tersedia.',
'config-install-mainpage' => 'Membuat halaman utama dengan konten bawaan',
'config-install-extension-tables' => 'Pembuatan tabel untuk ekstensi yang diaktifkan',
'config-install-mainpage-failed' => 'Tidak dapat membuat halaman utama: $1',
@@ -9402,16 +9686,17 @@ $3
'''Catatan''': Jika Anda tidak melakukannya sekarang, berkas konfigurasi yang dihasilkan ini tidak akan tersedia lagi setelah Anda keluar dari proses instalasi tanpa mengunduhnya.
Setelah melakukannya, Anda dapat '''[$2 memasuki wiki Anda]'''.",
- 'config-download-localsettings' => 'Unduh LocalSettings.php',
+ 'config-download-localsettings' => 'Unduh <code>LocalSettings.php</code>',
'config-help' => 'bantuan',
+ 'config-nofile' => 'Berkas "$1" tidak dapat ditemukan. Mungkin sudah dihapus?',
'mainpagetext' => "'''MediaWiki telah terpasang dengan sukses'''.",
'mainpagedocfooter' => 'Silakan baca [//www.mediawiki.org/wiki/Help:Contents/id Panduan Pengguna] untuk cara penggunaan perangkat lunak wiki ini.
== Memulai penggunaan ==
-
* [//www.mediawiki.org/wiki/Manual:Configuration_settings/id Daftar pengaturan konfigurasi]
* [//www.mediawiki.org/wiki/Manual:FAQ/id Daftar pertanyaan yang sering diajukan mengenai MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Milis rilis MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Milis rilis MediaWiki]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Terjemahkan MediaWiki ke bahasa Anda]',
);
/** Interlingue (Interlingue)
@@ -9424,6 +9709,12 @@ $messages['ie'] = array(
* @author Ukabia
*/
$messages['ig'] = array(
+ 'config-back' => '← Laàzú',
+ 'config-continue' => 'Gawazie →',
+ 'config-page-language' => 'Ásụ̀sụ̀',
+ 'config-page-name' => 'Áhà',
+ 'config-page-install' => 'Sụ̀ímé',
+ 'config-restart' => 'Eeh, bìdówárí ya.',
'config-admin-password' => 'Okwúngáfè:',
'config-admin-password-confirm' => 'Okwúngáfè mgbe ozor:',
'mainpagetext' => "'''MediaWiki a banyélé nke oma.'''",
@@ -9432,7 +9723,7 @@ $messages['ig'] = array(
== I bídó ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Ndétu ndósé ihe]
* [//www.mediawiki.org/wiki/Manual:FAQ FAQ MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce wéfù ndétu nke ozi MediaWiki]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce wéfù ndétu nke ozi MediaWiki]", # Fuzzy
);
/** Iloko (Ilokano)
@@ -9471,7 +9762,9 @@ $messages['is'] = array(
* @author Beta16
* @author Darth Kule
* @author F. Cosoleto
+ * @author Gianfranco
* @author Karika
+ * @author 아라
*/
$messages['it'] = array(
'config-desc' => 'Il programma di installazione per MediaWiki',
@@ -9479,19 +9772,19 @@ $messages['it'] = array(
'config-information' => 'Informazioni',
'config-localsettings-upgrade' => 'È stato rilevato un file <code>LocalSettings.php</code>.
Per aggiornare questa installazione, si prega di inserire il valore di <code>$wgUpgradeKey</code> nella casella qui sotto.
-Lo potete trovare in LocalSettings.php.',
- 'config-localsettings-cli-upgrade' => 'È stato rilevato un file LocalSettings.php.
-Per aggiornare questa installazione, eseguire update.php',
+Lo potete trovare in <code>LocalSettings.php</code>.',
+ 'config-localsettings-cli-upgrade' => 'È stato rilevato un file <code>LocalSettings.php</code>.
+Per aggiornare questa installazione, eseguire <code>update.php</code>',
'config-localsettings-key' => 'Chiave di aggiornamento:',
'config-localsettings-badkey' => 'La chiave che hai fornito non è corretta.',
'config-upgrade-key-missing' => "È stata rilevata un'installazione esistente di MediaWiki.
-Per aggiornare questa installazione, si prega di inserire la seguente riga nella parte inferiore del tuo LocalSettings.php:
+Per aggiornare questa installazione, si prega di inserire la seguente riga nella parte inferiore del tuo <code>LocalSettings.php</code>:
$1",
- 'config-localsettings-incomplete' => 'Il file LocalSettings.php esistente sembra essere incompleto.
+ 'config-localsettings-incomplete' => 'Il file <code>LocalSettings.php</code> esistente sembra essere incompleto.
La variabile $1 non è impostata.
-Cambia LocalSettings.php in modo che questa variabile sia impostata e fai clic su "Continua".',
- 'config-localsettings-connection-error' => 'Si è verificato un errore durante la connessione al database utilizzando le impostazioni specificate in LocalSettings.php o AdminSettings.php. Si prega di correggere queste impostazioni e riprovare.
+Cambia <code>LocalSettings.php</code> in modo che questa variabile sia impostata e fai clic su "{{int:Config-continue}}".',
+ 'config-localsettings-connection-error' => 'Si è verificato un errore durante la connessione al database utilizzando le impostazioni specificate in <code>LocalSettings.php</code> o <code>AdminSettings.php</code>. Si prega di correggere queste impostazioni e riprovare.
$1',
'config-session-error' => "Errore nell'avvio della sessione: $1",
@@ -9526,6 +9819,15 @@ Controlla il tuo file php.ini ed assicurati che <code>session.save_path</code>
'config-welcome' => "=== Controllo dell'ambiente ===
Vengono eseguiti controlli di base per vedere se questo ambiente è adatto per l'installazione di MediaWiki.
Se hai bisogno di aiuto durante l'installazione, è necessario fornire i risultati di questi controlli.",
+ 'config-sidebar' => '* [//www.mediawiki.org Pagina principale MediaWiki]
+* [//www.mediawiki.org/wiki/Aiuto:Guida ai contenuti per utenti]
+* [//www.mediawiki.org/wiki/Manuale:Guida ai contenuti per admin]
+* [//www.mediawiki.org/wiki/Manuale:FAQ FAQ]
+----
+* <doclink href=Readme>Leggimi</doclink>
+* <doclink href=ReleaseNotes>Note di versione</doclink>
+* <doclink href=Copying>Copie</doclink>
+* <doclink href=UpgradeDoc>Aggiornamenti</doclink>',
'config-env-good' => "L'ambiente è stato controllato.
È possibile installare MediaWiki.",
'config-env-bad' => "L'ambiente è stato controllato.
@@ -9564,10 +9866,14 @@ Installazione interrotta.",
'config-using-server' => 'Nome server in uso "<nowiki>$1</nowiki>".',
'config-using-uri' => 'URL del server in uso "<nowiki>$1$2</nowiki>".',
'config-db-type' => 'Tipo di database:',
+ 'config-db-wiki-settings' => 'Identifica questo wiki',
'config-db-name' => 'Nome del database:',
+ 'config-db-name-oracle' => 'Schema del database:',
+ 'config-db-username' => 'Nome utente del database:',
'config-db-password-empty' => 'Inserire una password per il nuovo utente del database: $1.
Anche se può essere possibile creare utenti senza password, questo non è sicuro.',
'config-db-install-help' => "Inserire il nome utente e la password che verranno usate per la connessione al database durante il processo d'installazione.",
+ 'config-db-prefix' => 'Prefisso tabella del database:',
'config-db-charset' => 'Set di caratteri del database',
'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
'config-charset-mysql4' => 'MySQL 4.0 con compatibilità UTF-8',
@@ -9585,15 +9891,19 @@ Da cambiare solamente se si è sicuri di averne bisogno.',
'config-header-oracle' => 'Impostazioni Oracle',
'config-header-ibm_db2' => 'Impostazioni IBM DB2',
'config-invalid-db-type' => 'Tipo di database non valido',
+ 'config-missing-db-name' => 'È necessario immettere un valore per "Nome del database"',
+ 'config-db-web-account' => "Account del database per l'accesso web",
'config-db-web-create' => "Crea l'account se non esiste già",
'config-mysql-engine' => 'Storage engine:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
'config-mysql-charset' => 'Set di caratteri del database:',
+ 'config-mysql-binary' => 'Binario',
'config-mysql-utf8' => 'UTF-8',
'config-ibm_db2-low-db-pagesize' => "Il database DB2 in uso ha una tablespace predefinita con un insufficiente pagesize, che dovrebbe essere '''32K''' o maggiore.",
'config-ns-generic' => 'Progetto',
'config-ns-site-name' => 'Stesso nome wiki: $1',
+ 'config-ns-other-default' => 'MyWiki',
'config-admin-box' => 'Account amministratore',
'config-admin-name' => 'Tuo nome:',
'config-admin-password' => 'Password:',
@@ -9614,26 +9924,47 @@ Specificare un nome utente diverso.',
Inserire un indirizzo email se si desidera effettuare l'iscrizione alla mailing list.",
'config-almost-done' => 'Hai quasi finito!
Adesso puoi saltare la rimanente parte della configurazione e semplicemente installare la wiki.',
+ 'config-optional-continue' => 'Fammi altre domande.',
+ 'config-profile-wiki' => 'Wiki tradizionale', # Fuzzy
+ 'config-profile-no-anon' => 'Creazione utenza obbligatoria',
+ 'config-profile-fishbowl' => 'Solo editori autorizzati',
+ 'config-profile-private' => 'Wiki privata',
'config-license' => 'Copyright e licenza:',
+ 'config-license-none' => 'Nessun piè di pagina per la licenza',
'config-license-cc-by-sa' => 'Creative Commons Attribuzione-Condividi allo stesso modo',
'config-license-cc-by' => 'Creative Commons Attribuzione',
'config-license-cc-by-nc-sa' => 'Creative Commons Attribuzione-Non commerciale-Condividi allo stesso modo',
'config-license-cc-0' => 'Creative Commons Zero (pubblico dominio)',
'config-license-gfdl' => 'GNU Free Documentation License 1.3 o versioni successive',
'config-license-pd' => 'Pubblico dominio',
+ 'config-license-cc-choose' => 'Seleziona una delle licenze Creative Commons',
+ 'config-license-help' => "Molti wiki pubblici rilasciano i loro contributi con una [http://freedomdefined.org/Definition licenza libera]. Questo aiuta a creare un senso di proprietà condivisa nella comunità e incoraggia a contribuire a lungo termine. Non è generalmente necessario per un wiki privato o aziendale.
+
+Se vuoi usare testi da Wikipedia, o desideri che Wikipedia possa essere in grado di accettare testi copiati dal tuo wiki, dovresti scegliere '''Creative Commons Attribution Share Alike'''.
+
+In precedenza Wikipedia ha utilizzato la GNU Free Documentation License. La GFDL è una licenza valida, ma è di difficile comprensione e complica il riutilizzo dei contenuti.",
'config-email-settings' => 'Impostazioni email',
+ 'config-email-user' => 'Abilita invio email fra utenti',
'config-email-auth' => 'Abilita autenticazione via email',
+ 'config-upload-enable' => 'Consentire il caricamento di file',
'config-upload-deleted' => 'Directory per i file cancellati:',
'config-logo' => 'URL del logo:',
+ 'config-instantcommons' => 'Abilita Instant Commons',
'config-cc-again' => 'Seleziona di nuovo...',
'config-cc-not-chosen' => 'Scegliere quale licenza Creative Commons si desidera e cliccare su "procedi".',
'config-advanced-settings' => 'Configurazione avanzata',
+ 'config-memcache-needservers' => 'È stato selezionato il tipo di caching Memcached, ma non è stato impostato alcun server.',
'config-memcache-badip' => 'È stato inserito un indirizzo IP non valido per Memcached: $1.',
'config-extensions' => 'Estensioni',
+ 'config-install-step-done' => 'fatto',
+ 'config-install-step-failed' => 'non riuscito',
+ 'config-install-schema' => 'Creazione dello schema',
+ 'config-install-user' => 'Creazione di utente del database',
'config-install-user-alreadyexists' => 'L\'utente "$1" è già presente',
'config-install-user-create-failed' => 'Creazione dell\'utente "$1" non riuscita: $2',
'config-install-user-missing' => 'L\'utente indicato "$1" non esiste.',
'config-install-tables-failed' => "'''Errore''': La creazione della tabella non è riuscita: $1",
+ 'config-install-interwiki' => 'Riempimento della tabella interwiki predefinita',
'config-install-interwiki-list' => 'Impossibile leggere il file <code>interwiki.list</code>.',
'config-install-stats' => 'Inizializzazione delle statistiche',
'config-install-keys' => 'Generazione delle chiavi segrete',
@@ -9642,7 +9973,7 @@ Adesso puoi saltare la rimanente parte della configurazione e semplicemente inst
'config-install-subscribe-notpossible' => 'cURL non è installato e allow_url_fopen non è disponibile.',
'config-install-mainpage' => 'Creazione della pagina principale con contenuto predefinito',
'config-install-mainpage-failed' => 'Impossibile inserire la pagina principale: $1',
- 'config-download-localsettings' => 'Scarica LocalSettings.php',
+ 'config-download-localsettings' => 'Scarica <code>LocalSettings.php</code>',
'config-help' => 'aiuto',
'config-nofile' => 'Il file "$1" non può essere trovato. È stato eliminato?',
'mainpagetext' => "'''Installazione di MediaWiki completata correttamente.'''",
@@ -9653,7 +9984,8 @@ I seguenti collegamenti sono in lingua inglese:
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Impostazioni di configurazione]
* [//www.mediawiki.org/wiki/Manual:FAQ Domande frequenti su MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list annunci MediaWiki]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list annunci MediaWiki]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Localizza MediaWiki nella tua lingua]",
);
/** Japanese (日本語)
@@ -9673,25 +10005,29 @@ $messages['ja'] = array(
'config-information' => '情報',
'config-localsettings-upgrade' => 'ファイル <code>LocalSettings.php</code> を検出しました。
インストールされているものをアップグレードするには、<code>$wgUpgradeKey</code> の値を以下の欄に入力してください。
-この値は LocalSettings.php 内にあります。',
- 'config-localsettings-cli-upgrade' => 'ファイル LocalSettings.php を検出しました。
-インストールされているものをアップグレードするには、update.php を実行してください',
+この値は <code>LocalSettings.php</code> 内にあります。',
+ 'config-localsettings-cli-upgrade' => 'ファイル <code>LocalSettings.php</code> を検出しました。
+インストールされているものをアップグレードするには、<code>update.php</code> を実行してください',
'config-localsettings-key' => 'アップグレード キー:',
'config-localsettings-badkey' => '与えられたキーが間違っています',
'config-upgrade-key-missing' => 'MediaWiki が既にインストールされていることを検出しました。
-インストールされているものをアップグレードするために、以下の行を LocalSettings.php の末尾に挿入してください:
+インストールされているものをアップグレードするために、以下の行を <code>LocalSettings.php</code> の末尾に挿入してください:
$1',
- 'config-localsettings-incomplete' => '既存の LocalSettings.php の内容は不完全のようです。
+ 'config-localsettings-incomplete' => '既存の <code>LocalSettings.php</code> の内容は不完全のようです。
変数 $1 が設定されていません。
-LocalSettings.php 内でこの変数を設定して、「{{int:Config-continue}}」をクリックしてください。',
+<code>LocalSettings.php</code> 内でこの変数を設定して、「{{int:Config-continue}}」をクリックしてください。',
+ 'config-localsettings-connection-error' => '<code>LocalSettings.php</code> または <code>AdminSettings.php</code> で指定した設定を使用してデータベースに接続する際にエラーが発生しました。
+設定を修正してから再度試してください。
+
+$1',
'config-session-error' => 'セッションの開始エラー: $1',
'config-session-expired' => 'セッションの有効期限が切れたようです。
セッションの有効期間は$1に設定されています。
php.iniの<code>session.gc_maxlifetime</code>を設定することで、この問題を改善できます。
インストール作業を再起動させてください。',
- 'config-no-session' => 'セッションのデータが消失しました!
-php.iniを確認し、<code>session.save_path</code>が適切なディレクトリに設定されていることを確認してください。',
+ 'config-no-session' => 'セッションのデータが消失しました!
+php.ini 内で <code>session.save_path</code> が適切なディレクトリに設定されていることを確認してください。',
'config-your-language' => 'あなたの言語:',
'config-your-language-help' => 'インストール作業に使用する言語を選択してください。',
'config-wiki-language' => 'ウィキの言語:',
@@ -9713,7 +10049,7 @@ php.iniを確認し、<code>session.save_path</code>が適切なディレクト
'config-page-copying' => 'コピー',
'config-page-upgradedoc' => 'アップグレード',
'config-page-existingwiki' => '既存のウィキ',
- 'config-help-restart' => '入力した保存データをすべて消去して、インストール作業を再起動しますか?',
+ 'config-help-restart' => '入力した保存データをすべて消去して、インストール作業を再起動しますか?',
'config-restart' => 'はい、再起動します',
'config-welcome' => '=== 環境の確認 ===
基本的な確認では、現在の環境がMediaWikiのインストールに適しているかを確認します。
@@ -9721,12 +10057,12 @@ php.iniを確認し、<code>session.save_path</code>が適切なディレクト
'config-copyright' => '=== 著作権および規約 ===
$1
-この作品はフリーソフトウェアです。あなたは、フリーソフトウェア財団の発行するGNU一般公衆利用許諾書 (GNU General Public License)(バージョン2、またはそれ以降のライセンス)の規約に基づき、このライブラリを再配布および改変できます。
+この作品はフリーソフトウェアです。あなたは、フリーソフトウェア財団の発行するGNU一般公衆利用許諾書 (GNU General Public License) (バージョン2、またはそれ以降のライセンス) の規約に基づき、このライブラリを再配布および改変できます。
この作品は、有用であることを期待して配布されていますが、商用あるいは特定の目的に適するかどうかも含めて、暗黙的にも、一切保証されません。
詳しくは、GNU一般公衆利用許諾書をご覧ください。
-あなたはこのプログラムと共に、<doclink href=Copying>GNU一般公衆利用許諾契約書の複製</doclink>を一部受け取ったはずです。もし受け取っていなければ、フリーソフトウェア財団(宛先は the Free Software Foundation, Inc., 59Temple Place, Suite 330, Boston, MA 02111-1307 USA)まで請求してください。',
+あなたはこのプログラムと共に、<doclink href=Copying>GNU一般公衆利用許諾契約書の複製</doclink>を一部受け取ったはずです。受け取っていない場合は、フリーソフトウェア財団 (宛先は the Free Software Foundation, Inc., 59Temple Place, Suite 330, Boston, MA 02111-1307 USA) まで請求してください。',
'config-sidebar' => '* [//www.mediawiki.org MediaWikiのホーム]
* [//www.mediawiki.org/wiki/Help:Contents 利用者向け案内]
* [//www.mediawiki.org/wiki/Manual:Contents 管理者向け案内]
@@ -9745,62 +10081,63 @@ MediaWikiのインストールはできません。',
しかし、MediaWikiには PHP $2 以上が必要です。',
'config-unicode-using-utf8' => 'Unicode正規化に、Brion Vibberのutf8_normalize.soを使用。',
'config-unicode-using-intl' => 'Unicode正規化に[http://pecl.php.net/intl intl PECL 拡張機能]を使用。',
- 'config-unicode-pure-php-warning' => "'''警告''':Unicode正規化の処理に [http://pecl.php.net/intl intl PECL 拡張機能]が使用可能ではなく、処理の遅いピュア PHP の実装を代わりに用いています。
-高トラフィックのサイトを運営する場合は、[//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode正規化に関するページ]をお読みください。",
- 'config-unicode-update-warning' => "'''警告''':インストールされているバージョンのUnicode正規化ラッパーは、[http://site.icu-project.org/ ICUプロジェクト]のライブラリの古いバージョンを使用しています。
-Unicodeを少しでも利用する可能性があるなら、[//www.mediawiki.org/wiki/Unicode_normalization_considerations アップグレード]する必要があります。",
+ 'config-unicode-pure-php-warning' => "'''警告:''' Unicode 正規化の処理に [http://pecl.php.net/intl intl PECL 拡張機能]を利用できないため、処理が遅いピュア PHP の実装を代わりに使用しています。
+高トラフィックのサイトを運営する場合は、[//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode 正規化]をお読みください。",
+ 'config-unicode-update-warning' => "'''警告:''' インストールされているバージョンの Unicode 正規化ラッパーは、[http://site.icu-project.org/ ICU プロジェクト]のライブラリの古いバージョンを使用しています。
+Unicode を少しでも利用する可能性がある場合は、[//www.mediawiki.org/wiki/Unicode_normalization_considerations アップグレード]してください。",
'config-no-db' => '適切なデータベース ドライバーが見つかりませんでした! PHP にデータベース ドライバーをインストールする必要があります。
以下の種類のデータベースに対応しています: $1
共有サーバーを使用している場合は、適切なデータベース ドライバーのインストールを、サーバーの管理者に依頼してください。
PHP を自分でコンパイルした場合は、例えば <code>./configure --with-mysql</code> を実行して、データベース クライアントを使用できるように再設定してください。
Debian または Ubuntu のパッケージから PHP をインストールした場合は、php5-mysql モジュールもインストールする必要があります。',
- 'config-no-fts3' => "'''警告''':SQLiteは[//sqlite.org/fts3.html FTS3]モジュールなしでコンパイルされており、検索機能はこのバックエンドで利用不可能になります。",
- 'config-register-globals' => "'''警告:PHPの<code>[http://php.net/register_globals register_globals]</code>オプションが有効になっています。'''
+ 'config-no-fts3' => "'''警告:''' SQLite は [//sqlite.org/fts3.html FTS3] モジュールなしでコンパイルされており、このバックエンドでは検索機能は利用できなくなります。",
+ 'config-register-globals' => "'''警告: PHP の <code>[http://php.net/register_globals register_globals]</code> オプションが有効になっています。'''
'''可能なら無効化してください。'''
-MediaWikiは動作しますが、サーバーは、潜在的なセキュリティ脆弱性を露呈します。",
- 'config-magic-quotes-runtime' => "'''致命的エラー:[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]が動作しています!'''
+MediaWiki は動作しますが、サーバーの潜在的なセキュリティ脆弱性が露呈されます。",
+ 'config-magic-quotes-runtime' => "'''致命的エラー: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] が動作しています!'''
このオプションは、予期せずデータ入力を破壊します。
-このオプションが無効化されないかぎり、MediaWikiをインストールし利用することはできません。",
- 'config-magic-quotes-sybase' => "'''致命的エラー:[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]が動作しています!'''
+このオプションを無効化しない限り、MediaWiki のインストールや使用はできません。",
+ 'config-magic-quotes-sybase' => "'''致命的エラー: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] が動作しています!'''
このオプションは、予期せずデータ入力を破壊します。
-このオプションが無効化されないかぎり、MediaWikiをインストールし利用することはできません。",
- 'config-mbstring' => "'''致命的エラー:[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]が動作しています!'''
-このオプションは、エラーを引き起こし、予期せずデータ入力を破壊する可能性があります。
-このオプションが無効化されないかぎり、MediaWikiをインストールし利用することはできません。",
- 'config-ze1' => "'''致命的エラー:[http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]が動作しています!'''
-このオプションは、MediaWikiにおいて深刻なバグを引き起こします。
-このオプションが無効化されないかぎり、MediaWikiをインストールし利用することはできません。",
- 'config-safe-mode' => "'''警告:'''PHPの[http://www.php.net/features.safe-mode セーフモード]が有効です。
-特にファイルのアップロード<code>math</code>のサポートにおいて、問題が発生する可能性があります。",
+このオプションを無効化しない限り、MediaWiki のインストールや使用はできません。",
+ 'config-mbstring' => "'''致命的エラー: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] が動作しています!'''
+このオプションは、エラーを引き起こし、予期せずデータを破壊するおそれがあります。
+このオプションを無効化しない限り、MediaWiki のインストールや使用はできません。",
+ 'config-ze1' => "'''致命的エラー: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] が動作しています!'''
+このオプションは、MediaWiki において深刻なバグを引き起こします。
+このオプションを無効化しない限り、MediaWiki のインストールや使用はできません。",
+ 'config-safe-mode' => "'''警告:''' PHPの[http://www.php.net/features.safe-mode セーフモード]が有効になっています。
+特に、ファイルのアップロードや<code>math</code>機能で、問題が発生するおそれがあります。",
'config-xml-bad' => 'PHPのXMLモジュールが不足しています。
MediaWikiは、このモジュールの関数を必要としているため、この構成では動作しません。
Mandrakeを実行している場合、php-xmlパッケージをインストールしてください。',
'config-pcre' => 'PCREをサポートしているモジュールが不足しているようです。
MediaWikiは、Perl互換の正規表現関数の動作が必要です。',
- 'config-pcre-no-utf8' => "'''致命的エラー''': PHP の PCRE が PCRE_UTF8 対応なしでコンパイルされているようです。
+ 'config-pcre-no-utf8' => "'''致命的エラー:''' PHP の PCRE が PCRE_UTF8 対応なしでコンパイルされているようです。
MediaWiki を正しく動作させるには、UTF-8 対応が必要です。",
'config-memory-raised' => 'PHPの<code>memory_limit</code>は$1で、$2に引き上げられました。',
- 'config-memory-bad' => "'''警告:'''PHPの<code>memory_limit</code>は$1です。
-これは、非常に遅い可能性があります。
-インストールが失敗するかもしれません!",
+ 'config-memory-bad' => "'''警告:''' PHPの<code>memory_limit</code>に$1に設定されています。
+この値はおそらく小さすぎます。
+インストールが失敗するおそれがあります!",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache] がインストール済み',
'config-apc' => '[http://www.php.net/apc APC] がインストール済み',
'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] がインストール済み',
- 'config-no-cache' => "'''警告:'''[http://www.php.net/apc APC]、[http://xcache.lighttpd.net/ XCache]、[http://www.iis.net/download/WinCacheForPhp WinCache] のいずれも見つかりませんでした。
+ 'config-no-cache' => "'''警告:''' [http://www.php.net/apc APC]、[http://xcache.lighttpd.net/ XCache]、[http://www.iis.net/download/WinCacheForPhp WinCache] のいずれも見つかりませんでした。
オブジェクトのキャッシュは有効化されません。",
'config-diff3-bad' => 'GNU diff3 が見つかりません。',
- 'config-imagemagick' => 'ImageMagickが見つかりました:<code>$1</code>。
-アップロードが有効なら、画像のサムネイルが利用できます。',
+ 'config-imagemagick' => 'ImageMagickが見つかりました: <code>$1</code>。
+アップロードが有効であれば、画像のサムネイルを利用できます。',
'config-gd' => 'GD画像ライブラリが内蔵されていることが確認されました。
アップロードが有効なら、画像のサムネイルが利用できます。',
'config-no-scaling' => 'GDライブラリもImageMagickも見つかりませんでした。
画像のサムネイル生成は無効になります。',
- 'config-no-uri' => "'''エラー:'''現在のURIを決定できませんでした。
+ 'config-no-uri' => "'''エラー:''' 現在のURIを決定できませんでした。
インストールは中止されました。",
+ 'config-no-cli-uri' => "'''警告:''' --scriptpath が指定されていないため、既定値 <code>$1</code> を使用します。",
'config-using-server' => 'サーバー名「<nowiki>$1</nowiki>」を使用しています。',
'config-using-uri' => 'サーバー URL「<nowiki>$1$2</nowiki>」を使用しています。',
- 'config-uploads-not-safe' => "'''警告:'''アップロードの既定ディレクトリ <code>$1</code> に、任意のスクリプト実行に関する脆弱性があります。
+ 'config-uploads-not-safe' => "'''警告:''' アップロードの既定ディレクトリ <code>$1</code> に、任意のスクリプト実行に関する脆弱性があります。
MediaWiki はアップロードされたファイルのセキュリティ上の脅威を確認しますが、アップロードを有効化する前に、[//www.mediawiki.org/wiki/Manual:Security#Upload_security このセキュリティ上の脆弱性を解決する]ことを強く推奨します。",
'config-brokenlibxml' => 'このシステムで使われているPHPとlibxml2のバージョンのこの組み合わせにはバグがあります。具体的には、MediaWikiやその他のウェブアプリケーションでhiddenデータが破損する可能性があります。
PHPを5.2.9かそれ以降のバージョンに、libxml2を2.7.3かそれ以降のバージョンにアップグレードしてください([//bugs.php.net/bug.php?id=45996 PHPでのバグ情報])。
@@ -9808,7 +10145,9 @@ PHPを5.2.9かそれ以降のバージョンに、libxml2を2.7.3かそれ以降
'config-using531' => 'PHP$1は<code>__call()</code>の引数参照に関するバグのため、MediaWikiと互換性がありません。
PHP5.3.2以降に更新するか、この([//bugs.php.net/bug.php?id=50394 PHPに提出されたバグ])を修正するためにPHP5.3.0へ戻してください。
インストールは中止されました。',
- 'config-suhosin-max-value-length' => 'Suhosin がインストールされており、GETパラメータの長さを $1 バイトに制限しています。MediaWiki の ResourceLoader コンポーネントはこの制限を回避しますが、パフォーマンスは低下します。可能な限り、php.ini で suhosin.get.max_value_length を 1024 以上に設定し、同じ値を LocalSettings.php 中で $wgResourceLoaderMaxQueryLength に設定してください。',
+ 'config-suhosin-max-value-length' => 'Suhosin がインストールされており、GET パラメーターの <code>length</code> を $1 バイトに制限しています。
+MediaWiki の ResourceLoader コンポーネントはこの制限を回避しますが、パフォーマンスは低下します。
+可能な限り、<code>php.ini</code> で <code>suhosin.get.max_value_length</code> を 1024 以上に設定し、同じ値を <code>LocalSettings.php</code> 内で <code>$wgResourceLoaderMaxQueryLength</code> に設定してください。',
'config-db-type' => 'データベースの種類:',
'config-db-host' => 'データベースのホスト:',
'config-db-host-help' => '異なるサーバー上にデータベースサーバーがある場合、ホスト名またはIPアドレスをここに入力してください。
@@ -9831,9 +10170,11 @@ PostgreSQLを使用している場合、UNIXソケットで接続するにはこ
'config-db-username' => 'データベースのユーザー名:',
'config-db-password' => 'データベースのパスワード:',
'config-db-password-empty' => '新しいデータベースの利用者名 $1 のパスワードを入力してください。
-パスワードを設定しないでユーザを作ることもできるかもしれませんが、安全ではありません。',
- 'config-db-install-username' => 'インストール中にデータベースに接続するために使うユーザ名を入力してください。これは MediaWiki アカウントのユーザ名 (利用者名) のことではありません。あなたのデータベースでのユーザ名です。',
- 'config-db-install-password' => 'インストール中にデータベースに接続するために使うパスワードを入力してください。これは MediaWiki アカウントパスワードのことではありません。あなたのデータベースでのパスワードです。',
+パスワードを設定せずにユーザーを作成できる場合もありますが、安全ではありません。',
+ 'config-db-install-username' => 'インストール中にデータベースへの接続で使用するユーザー名を入力してください。
+これは MediaWiki アカウントの利用者名のことではありません。あなたのデータベースでのユーザー名です。',
+ 'config-db-install-password' => 'インストール中にデータベースへの接続で使用するパスワードを入力してください。
+これは MediaWiki アカウントのパスワードのことではありません。あなたのデータベースでのパスワードです。',
'config-db-install-help' => 'インストール作業中にデータベースに接続するための利用者名とパスワードを入力してください。',
'config-db-account-lock' => 'インストール作業終了後も同じ利用者名とパスワードを使用する',
'config-db-wiki-account' => 'インストール作業終了後の利用者アカウント',
@@ -9859,6 +10200,7 @@ PostgreSQLを使用している場合、UNIXソケットで接続するにはこ
'config-db-schema' => 'MediaWiki のスキーマ:',
'config-db-schema-help' => '通常はこのスキーマで問題ありません。
必要な場合のみ変更してください。',
+ 'config-pg-test-error' => "データベース '''$1''' に接続できません: $2",
'config-sqlite-dir' => 'SQLite データ ディレクトリ:',
'config-sqlite-dir-help' => "SQLite は単一のファイル内にすべてのデータを格納しています。
@@ -9876,22 +10218,19 @@ PostgreSQLを使用している場合、UNIXソケットで接続するにはこ
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'MediaWiki は以下のデータベース システムに対応しています:
$1
使用しようとしているデータベース システムが下記の一覧にない場合は、上記リンク先の手順に従ってインストールしてください。',
- 'config-support-mysql' => '* $1はMediaWikiの主要な対象で、もっともサポートされています([http://www.php.net/manual/en/mysql.installation.php MySQLのサポート下でPHPをコンパイルする方法])',
- 'config-support-postgres' => '* $1は、MySQLの代替として、人気のあるオープンソースデータベースシステムです([http://www.php.net/manual/en/pgsql.installation.php PostgreSQLのサポート下でPHPをコンパイルする方法])',
- 'config-support-sqlite' => '* $1は、良くサポートされている、軽量データベースシステムです。([http://www.php.net/manual/en/pdo.installation.php SQLiteのサポート下でPHPをコンパイルする方法]、PDOを使用)',
- 'config-support-oracle' => '* $1は商業企業のデータベースです。([http://www.php.net/manual/en/oci8.installation.php OCI8サポートなPHPをコンパイルする方法])',
- 'config-support-ibm_db2' => '* $1 は商業企業のデータベースです。',
+ 'config-support-mysql' => '* $1はMediaWikiの主要な対象であり、最もサポートされています ([http://www.php.net/manual/en/mysql.installation.php MySQLに対応したPHPをコンパイルする方法])',
+ 'config-support-postgres' => '* $1は、MySQLの代替として人気があるオープンソースのデータベースシステムです ([http://www.php.net/manual/en/pgsql.installation.php PostgreSQLに対応したPHPをコンパイルする方法])',
+ 'config-support-sqlite' => '* $1は、良くサポートされている、軽量データベースシステムです。([http://www.php.net/manual/ja/pdo.installation.php SQLiteに対応したPHPをコンパイルする方法]、PDOを使用)',
+ 'config-support-oracle' => '* $1は商業企業のデータベースです。([http://www.php.net/manual/en/oci8.installation.php OCI8サポートなPHPをコンパイルする方法])',
'config-header-mysql' => 'MySQL の設定',
'config-header-postgres' => 'PostgreSQL の設定',
'config-header-sqlite' => 'SQLite の設定',
'config-header-oracle' => 'Oracle の設定',
- 'config-header-ibm_db2' => 'IBM DB2 の設定',
'config-invalid-db-type' => '無効なデータベースの種類',
'config-missing-db-name' => '「データベース名」を入力してください',
'config-missing-db-host' => '「データベースのホスト」を入力してください',
@@ -9904,7 +10243,7 @@ $1
アスキー英字(a-z, A-Z)、数字(0-9)、下線(_)、ハイフン(-)のみを使用してください。',
'config-connection-error' => '$1。
-以下のホスト名、ユーザ名、パスワードをチェックして、再度試してみてください。',
+以下のホスト名、ユーザー名、パスワードを確認してから再度試してください。',
'config-invalid-schema' => '「$1」は MediaWiki のスキーマとして無効です。
ASCII の英数字 (a-z、A-Z、0-9)、下線 (_) のみを使用してください。',
'config-postgres-old' => 'PostgreSQL $1 以降が必要です。ご使用中の PostgreSQL は $2 です。',
@@ -9951,8 +10290,8 @@ chmod a+w $3</pre>',
'config-upgrade-done-no-regenerate' => 'アップグレードが完了しました。
[$1 ウィキの使用を開始]することができます。',
- 'config-regenerate' => 'LocalSettings.phpを再生成→',
- 'config-show-table-status' => 'SHOW TABLE STATUSクエリーが失敗しました!',
+ 'config-regenerate' => '<code>LocalSettings.php</code> を再生成→',
+ 'config-show-table-status' => '<code>SHOW TABLE STATUS</code> クエリが失敗しました!',
'config-unknown-collation' => "'''警告:''' データベースは認識されない照合を使用しています。",
'config-db-web-account' => 'ウェブアクセスのためのデータベースアカウント',
'config-db-web-help' => 'ウィキの通常の操作の際に、ウェブ サーバーがデータベース サーバーに接続できるように、ユーザー名とパスワードを指定してください。',
@@ -9967,7 +10306,7 @@ chmod a+w $3</pre>',
'''MyISAM'''は、利用者が1人の場合、あるいは読み込み専用でインストールする場合に、より処理が早くなるでしょう。
ただし、MyISAMのデータベースは、InnoDBより高頻度で破損する傾向があります。",
- 'config-mysql-charset' => 'データベースの文字セット:',
+ 'config-mysql-charset' => 'データベースの文字セット:',
'config-mysql-binary' => 'バイナリ',
'config-mysql-utf8' => 'UTF-8',
'config-mysql-charset-help' => "'''バイナリ モード'''では、MediaWiki は、UTF-8 テキストをデータベースのバイナリ フィールドに格納します。
@@ -9984,49 +10323,50 @@ chmod a+w $3</pre>',
'config-ns-other' => 'その他 (指定してください)',
'config-ns-other-default' => 'マイウィキ',
'config-project-namespace-help' => "ウィキペディアの例に従い、多くのウィキは、コンテンツのページとは分離したポリシーページを「'''プロジェクトの名前空間'''」に持っています。
-この名前空間のページのタイトルはすべて、ある接頭辞で始まります。それをここで指定することができます。
-この接頭辞はウィキの名前に由来するのが伝統的ですが、「#」や「:」のような区切り文字を含めることはできません。",
- 'config-ns-invalid' => '"<nowiki>$1</nowiki>"のように指定された名前空間は無効です。
-違うプロジェクト名前空間を指定してください。',
+この名前空間内のページのページ名はすべて特定の接頭辞で始まります。それをここで指定できます。
+通常、この接頭辞はウィキ名に基づきますが、「#」や「:」のような区切り文字を含めることはできません。",
+ 'config-ns-invalid' => '指定した名前空間「<nowiki>$1</nowiki>」は無効です。
+別のプロジェクト名前空間を指定してください。',
'config-admin-box' => '管理アカウント',
'config-admin-name' => '名前:',
'config-admin-password' => 'パスワード:',
'config-admin-password-confirm' => 'パスワードの再入力:',
- 'config-admin-help' => '希望するユーザー名をここに入力してください (例: "Joe Bloggs")。
+ 'config-admin-help' => '希望するユーザー名をここに入力してください (例:「Joe Bloggs」)。
この名前でこのウィキにログインすることになります。',
- 'config-admin-name-blank' => '管理者のユーザ名を入力してください。',
- 'config-admin-name-invalid' => '指定されたユーザ名 "<nowiki>$1</nowiki>" は無効です。
-別のユーザ名を指定してください。',
+ 'config-admin-name-blank' => '管理者のユーザー名を入力してください。',
+ 'config-admin-name-invalid' => '指定したユーザー名「<nowiki>$1</nowiki>」は無効です。
+別のユーザー名を指定してください。',
'config-admin-password-blank' => '管理者アカウントのパスワードを入力してください。',
- 'config-admin-password-same' => 'ユーザ名と同じパスワードは使えません。',
+ 'config-admin-password-same' => 'ユーザー名と同じパスワードは使用できません。',
'config-admin-password-mismatch' => '入力された2つのパスワードが一致しません。',
'config-admin-email' => 'メールアドレス:',
'config-admin-email-help' => 'メールアドレスを入力してください。他の利用者からのメールの受け取り、パスワードのリセット、ウォッチリストに登録したページの更新通知に使用します。空欄のままにすることもできます。',
- 'config-admin-error-user' => '"<nowiki>$1</nowiki>"という名前の管理者を作成する際に内部エラーが発生しました。',
- 'config-admin-error-password' => '管理者"<nowiki>$1</nowiki>"のパスワードを設定する際に内部エラーが発生しました: <pre>$2</pre>',
+ 'config-admin-error-user' => '「<nowiki>$1</nowiki>」という名前の管理者を作成する際に内部エラーが発生しました。',
+ 'config-admin-error-password' => '管理者「<nowiki>$1</nowiki>」のパスワードを設定する際に内部エラーが発生しました: <pre>$2</pre>',
+ 'config-admin-error-bademail' => '無効なメールアドレスを入力しました。',
'config-subscribe' => '[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce リリース告知のメーリングリスト]を購読する。',
- 'config-subscribe-help' => 'これは、リリースの告知(重要なセキュリティに関する案内を含む)に使われる、低容量のメーリングリストです。
+ 'config-subscribe-help' => 'これは、リリースの告知 (重要なセキュリティに関する案内を含む) に使用される、流量が少ないメーリングリストです。
このメーリングリストを購読して、新しいバージョンが出た場合にMediaWikiを更新してください。',
- 'config-almost-done' => 'これでほぼ終わりました!
-残りの設定を飛ばして、今すぐにウィキをインストールできます。',
+ 'config-almost-done' => 'これでほぼ終わりました!
+残りの設定を飛ばして、ウィキを今すぐインストールできます。',
'config-optional-continue' => '私にもっと質問してください。',
'config-optional-skip' => 'もう飽きてしまったので、とにかくウィキをインストールしてください。',
'config-profile' => '利用者権限のプロファイル:',
- 'config-profile-wiki' => '伝統的なウィキ',
+ 'config-profile-wiki' => '伝統的なウィキ', # Fuzzy
'config-profile-no-anon' => 'アカウントの作成が必要',
'config-profile-fishbowl' => '承認された編集者のみ',
'config-profile-private' => '非公開ウィキ',
- 'config-profile-help' => "ウィキは、たくさんの人が可能な限りそのウィキを編集できるとき、最も優れた働きをします。
-MediaWikiでは、最近の更新を確認し、神経質な、もしくは悪意を持った利用者からの損害を差し戻すことが、簡単にできます。
+ 'config-profile-help' => "ウィキは、できるだけ多くの人が編集できるようにすると最も優れた働きをします。
+MediaWikiでは、最近の更新を確認しやすく、神経質な、または悪意を持った利用者からの損害を簡単に差し戻せます。
しかし一方で、MediaWikiは、さらにさまざまな形態での利用も優れていると言われています。また、時には、すべての人にウィキ手法の利点を説得させるのは容易ではないかもしれません。
そこで、選択肢があります。
-'''{{int:config-profile-wiki}}'''は、ログインしなくても、誰でも編集できるものです。
-'''{{int:config-profile-no-anon}}'''なウィキは、各編集に対してより強い説明責任を付与しますが、気軽な投稿を阻害するかもしれません。
+「'''{{int:config-profile-wiki}}'''」モデルでは、ログインしなくても、誰でも編集できます。
+「'''{{int:config-profile-no-anon}}'''」なウィキでは、各編集に対してより強い説明責任を付与しますが、気軽な投稿を阻害するかもしれません。
-'''{{int:config-profile-fishbowl}}'''のウィキは、承認された利用者が編集でき、一方、一般の人はページ(とその履歴)を閲覧できます。
-'''{{int:config-profile-private}}'''は、承認された利用者のみがページを閲覧でき、そのグループが編集できます。
+「'''{{int:config-profile-fishbowl}}'''」シナリオでは、承認された利用者のみが編集でき、一般の人はページ (とその履歴) を閲覧できます。
+「'''{{int:config-profile-private}}'''」では、承認された利用者のみがページを閲覧でき、そのグループが編集できます。
より複雑な利用者権限の設定は、インストール後に設定できます。詳細は[//www.mediawiki.org/wiki/Manual:User_rights 関連するマニュアル]をご覧ください。",
'config-license' => '著作権とライセンス:',
@@ -10037,23 +10377,23 @@ MediaWikiでは、最近の更新を確認し、神経質な、もしくは悪
'config-license-gfdl' => 'GNU フリー文書利用許諾契約書 1.3 以降',
'config-license-pd' => 'パブリック・ドメイン',
'config-license-cc-choose' => 'その他のクリエイティブ・コモンズ・ライセンスを選択する',
- 'config-license-help' => "多くの公開ウィキでは、すべての寄稿物が[http://freedomdefined.org/Definition フリーライセンス]の元に置かれています。
+ 'config-license-help' => "多くの公開ウィキでは、すべての寄稿物が[http://freedomdefined.org/Definition フリーライセンス]のもとに置かれています。
こうすることにより、コミュニティによる共有の感覚が生まれ、長期的な寄稿が促されます。
私的ウィキや企業のウィキでは、通常、フリーライセンスにする必要はありません。
ウィキペディアにあるテキストをあなたのウィキで利用し、逆にあなたのウィキにあるテキストをウィキペディアに複製することを許可したい場合には、'''クリエイティブ・コモンズ 表示-継承'''を選択するべきです。
ウィキペディアは以前、GNUフリー文書利用許諾契約書(GFDL)を使用していました。
-GFDL は有効なライセンスですが、内容を理解するのは困難です。
-また、GFDL の元に置かれているコンテンツの再利用も困難です。",
+GFDLは有効なライセンスですが、内容を理解するのは困難です。
+また、GFDLのもとに置かれているコンテンツの再利用も困難です。",
'config-email-settings' => 'メールの設定',
'config-enable-email' => 'メール送信を有効にする',
'config-enable-email-help' => 'メールを使用したい場合は、[http://www.php.net/manual/en/mail.configuration.php PHP のメール設定]が正しく設定されている必要があります。
メールの機能を使用しない場合は、ここで無効にすることができます。',
'config-email-user' => '利用者間のメールを有効にする',
- 'config-email-user-help' => '設定において有効になっている場合、すべてのユーザがお互いにメールのやりとりを行うことを許可する。',
- 'config-email-usertalk' => 'ユーザのトークページにおける通知を有効にする',
- 'config-email-usertalk-help' => '設定で有効にしているならば、ユーザのトークページの変更の通知を受けることをユーザに許可する。',
+ 'config-email-user-help' => '設定で有効になっている場合、すべてのユーザーがお互いにメールのやりとりを行うことを許可する。',
+ 'config-email-usertalk' => 'ユーザーのトークページでの通知を有効にする',
+ 'config-email-usertalk-help' => '設定で有効にしている場合は、ユーザーのトークページの変更の通知を受けることをユーザーに許可する。',
'config-email-watchlist' => 'ウォッチリストの通知を有効にする',
'config-email-watchlist-help' => '利用者が設定で有効にしている場合、閲覧されたページに関する通知を受け取ることを許可する。',
'config-email-auth' => 'メールの認証を有効にする',
@@ -10094,8 +10434,8 @@ GFDL は有効なライセンスですが、内容を理解するのは困難で
中〜大規模サイトではこれを有効にすることを強くお勧めします。小規模サイトでも同様に効果があります。',
'config-cache-none' => 'キャッシングしない(機能は取り払われます、しかもより大きなウィキサイト上でスピードの問題が発生します)',
'config-cache-accel' => 'PHP オブジェクト キャッシュ (APC、XCache、WinCache のいずれか)',
- 'config-cache-memcached' => 'Memcachedを使用(追加の設定が必要です)',
- 'config-memcached-servers' => 'メモリをキャッシュされたサーバ:',
+ 'config-cache-memcached' => 'memcached を使用 (追加の設定が必要)',
+ 'config-memcached-servers' => 'memcached サーバー:',
'config-memcached-help' => 'Memcachedを使用するIPアドレスの一覧。
カンマ区切りで、利用する特定のポートの指定が必要です。例:
127.0.0.1:11211
@@ -10107,7 +10447,7 @@ GFDL は有効なライセンスですが、内容を理解するのは困難で
'config-install-alreadydone' => "'''警告:''' 既にMediaWikiがインストール済みで、再びインストールし直そうとしています。
次のページへ進んでください。",
'config-install-begin' => '「{{int:config-continue}}」を押すと、MediaWiki のインストールを開始できます。
-変更したい設定がある場合は、「{{int:Config-back}}」を押してください。',
+変更したい設定がある場合は、「{{int:config-back}}」を押してください。',
'config-install-step-done' => '実行',
'config-install-step-failed' => '失敗した',
'config-install-extensions' => '拡張機能を含む',
@@ -10125,42 +10465,44 @@ GFDL は有効なライセンスですが、内容を理解するのは困難で
'config-install-user-missing-create' => '指定したユーザー「$1」は存在しません。
アカウントを作成する場合は、下の「アカウント作成」をクリックしてください。',
'config-install-tables' => 'テーブルの作成',
- 'config-install-tables-exist' => "'''警告''':MediaWikiテーブルは既に存在するようです。
-作成を飛ばします。",
- 'config-install-tables-failed' => "'''エラー''': テーブルの作成が、以下のエラーにより失敗しました: $1",
+ 'config-install-tables-exist' => "'''警告:''' MediaWiki テーブルは既に存在するようです。
+作成を省略します。",
+ 'config-install-tables-failed' => "'''エラー:''' テーブルの作成が、以下のエラーにより失敗しました: $1",
'config-install-interwiki' => '既定のウィキ間テーブルの導入',
'config-install-interwiki-list' => 'ファイル <code>interwiki.list</code> から読み取れませんでした。',
- 'config-install-interwiki-exists' => "'''警告''':ウィキ間テーブルは既に登録されているようです。
+ 'config-install-interwiki-exists' => "'''警告:''' ウィキ間テーブルは既に登録されているようです。
既定のテーブルを無視します。",
'config-install-stats' => '統計情報の初期化',
'config-install-keys' => '秘密鍵の生成',
'config-install-sysop' => '管理者のアカウントの作成',
'config-install-mainpage' => 'メインページを既定の内容で作成',
'config-install-mainpage-failed' => 'メインページを挿入できませんでした: $1',
- 'config-install-done' => "'''おめでとうございます!'''
+ 'config-install-done' => "'''おめでとうございます!'''
MediaWikiのインストールに成功しました。
<code>LocalSettings.php</code>ファイルが生成されました。
-すべての設定がそのファイルに含まれています。
+このファイルはすべての設定を含んでいます。
-それをダウンロードし、ウィキをインストールした基準ディレクトリ(index.phpと同じディレクトリ)に設置する必要があります。ダウンロードは自動的に開始しているはずです。
+これをダウンロードして、ウィキをインストールした基準ディレクトリ (index.phpと同じディレクトリ) に設置する必要があります。ダウンロードは自動的に開始されるはずです。
-ダウンロードが開始していない場合、またダウンロードをキャンセルした場合は、下記のリンクからダウンロードを再開することができます:
+ダウンロードが開始されていない場合、またはダウンロードをキャンセルした場合は、下記のリンクをクリックしてダウンロードを再開できます:
$3
-'''注意''': もし、これを今しなければ、つまり、このファイルをダウンロードせずインストールを終了した場合、この生成された設定ファイルは利用されません。
+'''注意:''' この生成された設定ファイルをダウンロードせずにインストールを終了すると、このファイルは利用できなくなります。
-それを完了すれば、'''[$2 ウィキに入る]'''ことができます。",
- 'config-download-localsettings' => 'LocalSettings.php をダウンロード',
+上記の作業が完了すると、'''[$2 ウィキに入る]'''ことができます。",
+ 'config-download-localsettings' => '<code>LocalSettings.php</code> をダウンロード',
'config-help' => 'ヘルプ',
+ 'config-nofile' => 'ファイル「$1」が見つかりませんでした。削除された可能性があります。',
'mainpagetext' => "'''MediaWiki のインストールに成功しました。'''",
'mainpagedocfooter' => 'ウィキソフトウェアの使い方に関する情報は[//meta.wikimedia.org/wiki/Help:Contents 利用者案内]を参照してください。
== はじめましょう ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings/ja 設定の一覧]
* [//www.mediawiki.org/wiki/Manual:FAQ/ja MediaWiki よくある質問と回答]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWikiリリース情報メーリングリスト]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki リリース情報メーリングリスト]
+* [//www.mediawiki.org/wiki/Localisation/ja MediaWiki のあなたの言語へのローカライズ]',
);
/** Jamaican Creole English (Patois)
@@ -10258,7 +10600,8 @@ $messages['ka'] = array(
'config-admin-password-confirm' => 'პაროლი ხელმეორედ:',
'config-admin-name-blank' => 'შეიყვანეთ ადმინისტრატორის მომხმარებლის სახელი.',
'config-admin-email' => 'ელ. ფოსტის მისამართი:',
- 'config-profile-wiki' => 'ტრადიციული ვიკი',
+ 'config-profile' => 'მომხმარებელთა უფლებების პროფილი:',
+ 'config-profile-wiki' => 'ღია ვიკი',
'config-profile-private' => 'დახურული ვიკი',
'config-license' => 'საავტორო უფლები და ლიცენზია:',
'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
@@ -10277,7 +10620,8 @@ $messages['ka'] = array(
'config-install-step-done' => 'შესრულდა',
'config-install-step-failed' => 'ვერ მოხერხდა',
'config-install-tables' => 'ცხრილების შექმნა',
- 'config-download-localsettings' => 'LocalSettings.php-ის გადმოწერა',
+ 'config-install-interwiki-list' => 'ვერ მოიძებნა ფაილი <code>interwiki.list</code>.',
+ 'config-download-localsettings' => '<code>LocalSettings.php</code>-ის გადმოწერა',
'config-help' => 'დახმარება',
'mainpagetext' => "'''მედიავიკი წარმატებით ჩაიტვირთა.'''",
'mainpagedocfooter' => 'ვიკი პროგრამის გამოყენების ინფორმაციისთვის იხილეთ [//meta.wikimedia.org/wiki/Help:Contents მომხმარებლის მეგზური].
@@ -10286,7 +10630,8 @@ $messages['ka'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings კონფიგურაციის მაჩვენებლების სია]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce მედიავიკის გამოცემის დაგზავნის სია]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce მედიავიკის გამოცემის დაგზავნის სია]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources მედიავიკის ლოკალიზება თქვენ ენაზე]',
);
/** Kara-Kalpak (Qaraqalpaqsha)
@@ -10419,24 +10764,24 @@ $messages['kn'] = array(
* @author 아라
*/
$messages['ko'] = array(
- 'config-desc' => '미디어위키 설치 마법사',
+ 'config-desc' => '미디어위키 설치 프로그램',
'config-title' => 'MediaWiki $1 설치',
'config-information' => '정보',
- 'config-localsettings-upgrade' => '<code>LocalSettings.php</code> 파일이 감지되었습니다.
+ 'config-localsettings-upgrade' => '<code>LocalSettings.php</code> 파일을 감지했습니다.
이 설치를 업그레이드하려면 아래 상자에 <code>$wgUpgradeKey</code>의 값을 입력하세요.
-LocalSettings.php에 찾으세요.',
- 'config-localsettings-cli-upgrade' => 'LocalSettings.php 파일이 감지되었습니다.
-이 설치를 업그레이드하려면 update.php를 대신 실행하세요',
+<code>LocalSettings.php</code>에 찾을 수 있습니다.',
+ 'config-localsettings-cli-upgrade' => '<code>LocalSettings.php</code> 파일을 감지했습니다.
+이 설치를 업그레이드하려면 <code>update.php</code>를 대신 실행하세요',
'config-localsettings-key' => '업그레이드 키:',
'config-localsettings-badkey' => '제공한 키가 잘못되었습니다.',
'config-upgrade-key-missing' => '미디어위키의 기존 설치가 감지되었습니다.
-이 설치를 업그레이드하려면 LocalSettings.php의 아래에 다음 줄을 넣으세요:
+이 설치를 업그레이드하려면 <code>LocalSettings.php</code>의 아래에 다음 줄을 넣으세요:
$1',
- 'config-localsettings-incomplete' => '기존 LocalSettings.php가 완전하지 않은 것 같습니다.
+ 'config-localsettings-incomplete' => '기존 <code>LocalSettings.php</code>가 완전하지 않은 것 같습니다.
$1 변수가 설정되어 있지 않습니다.
-이 변수가 설정되도록 LocalSettings.php를 바꾸고 "계속"을 클릭하세요.',
- 'config-localsettings-connection-error' => 'LocalSettings.php 또는 AdminSettings.php에 지정한 설정을 사용하여 데이터베이스에 연결할 때 오류가 발생했습니다. 이러한 설정을 수정하고 다시 시도하세요.
+이 변수가 설정되도록 <code>LocalSettings.php</code>를 바꾸고 "{{int:Config-continue}}"을 클릭하세요.',
+ 'config-localsettings-connection-error' => '<code>LocalSettings.php</code> 또는 <code>AdminSettings.php</code>에 지정한 설정을 사용하여 데이터베이스에 연결할 때 오류가 발생했습니다. 이러한 설정을 수정하고 다시 시도하세요.
$1',
'config-session-error' => '세션 시작 오류: $1',
@@ -10444,12 +10789,12 @@ $1',
세션은 $1의 작동 시간 동안 구성됩니다.
php.ini에 있는 <code>session.gc_maxlifetime</code>에서 설정해 이를 증가시킬 수 있습니다.
설치 과정을 다시 시작합니다.',
- 'config-no-session' => '세션 데이터가 손실되었습니다!
+ 'config-no-session' => '세션 데이터가 없어졌습니다!
php.ini를 확인하고 <code>session.save_path</code>가 적절한 디렉토리로 설정되어 있는지 확인하세요.',
'config-your-language' => '설치 언어:',
'config-your-language-help' => '설치 과정에서 사용할 언어를 선택하세요.',
'config-wiki-language' => '위키 언어:',
- 'config-wiki-language-help' => '주로 작성될 위키에 대한 언어를 선택하세요.',
+ 'config-wiki-language-help' => '위키에 주로 작성될 언어를 선택하세요.',
'config-back' => '← 뒤로',
'config-continue' => '계속 →',
'config-page-language' => '언어',
@@ -10467,7 +10812,7 @@ php.ini를 확인하고 <code>session.save_path</code>가 적절한 디렉토리
'config-page-copying' => '전문',
'config-page-upgradedoc' => '업그레이드하기',
'config-page-existingwiki' => '기존 위키',
- 'config-help-restart' => '당신이 입력한 모든 저장된 데이터를 지우고 설치 과정을 다시 시작하겠습니까?',
+ 'config-help-restart' => '입력한 모든 저장된 데이터를 지우고 설치 과정을 다시 시작하겠습니까?',
'config-restart' => '예, 다시 시작합니다',
'config-welcome' => '=== 사용 환경 검사 ===
이 환경이 미디어위키 설치에 적합한지 기본 검사를 실행합니다.
@@ -10498,37 +10843,37 @@ $1
'config-env-php' => 'PHP $1(이)가 설치되었습니다.',
'config-env-php-toolow' => 'PHP $1(이)가 설치되었습니다.
하지만 미디어위키는 PHP $2 이상이 필요합니다.',
- 'config-unicode-using-utf8' => '유니코드 정규화에 대해 Brion Vibber의 utf8_normalize.so를 사용합니다.',
- 'config-unicode-using-intl' => '유니코드 정규화에 대해 [http://pecl.php.net/intl intl PECL 확장]을 사용합니다.',
- 'config-unicode-pure-php-warning' => "'''경고''': [http://pecl.php.net/intl intl PECL 확장]은 PHP만으로 구현하는 데에는 느려질 정도로 성능이 떨어지는 유니코드 정규화를 처리할 수 없습니다.
-높은 트래픽의 사이트에서 실행하려면 [//www.mediawiki.org/wiki/Unicode_normalization_considerations 유니코드 정규화]에 대해 약간 참고해야 합니다.",
+ 'config-unicode-using-utf8' => '유니코드 정규화에 Brion Vibber의 utf8_normalize.so를 사용합니다.',
+ 'config-unicode-using-intl' => '유니코드 정규화에 [http://pecl.php.net/intl intl PECL 확장 기능]을 사용합니다.',
+ 'config-unicode-pure-php-warning' => "'''경고''': 유니코드 정규화를 처리할 [http://pecl.php.net/intl intl PECL 확장 기능]을 사용할 수 없기 때문에 느린 순수한 PHP 구현을 대신 사용합니다.
+높은 트래픽 사이트에서 실행하려면 [//www.mediawiki.org/wiki/Unicode_normalization_considerations 유니코드 정규화]를 읽어보시기 바랍니다.",
'config-unicode-update-warning' => "'''경고''': 유니코드 정규화 래퍼의 설치된 버전은 [http://site.icu-project.org/ ICU 프로젝트]의 라이브러리의 이전 버전을 사용합니다.
만약 유니코드를 사용하는 것에 대해 우려가 된다면 [//www.mediawiki.org/wiki/Unicode_normalization_considerations 업그레이드]해야합니다.",
'config-no-db' => '적절한 데이터베이스 드라이버를 찾을 수 없습니다! PHP에 데이터베이스 드라이버를 설치해야 합니다.
다음 데이터베이스 유형을 지원합니다 : $1.
-호스팅을 공유하고 있다면 적절한 데이터베이스 드라이버를 설치하도록 호스팅 제공 업체에 문의하세요.
-PHP를 직접 컴파일할 경우 데이터베이스 클라이언트를 사용하여 활성화하도록 다시 설정하세요. 예들 들어 <code>./configure --with-mysql</code>을 사용하세요.
+공유하는 호스팅을 사용하고 있다면 적절한 데이터베이스 드라이버를 설치하도록 호스팅 제공 업체에 문의하세요.
+PHP를 직접 컴파일했다면 예를 들어 <code>./configure --with-mysql</code>을 사용하여 데이터베이스 클라이언트를 활성화하도록 다시 설정하세요.
데비안이나 우분트 패키지에서 PHP를 설치했다면 php-mysql 모듈도 설치해야 합니다.',
'config-outdated-sqlite' => "'''경고''': SQLite 필요한 최소 $2 버전보다 낮은 $1(이)가 있습니다. SQLite는 사용할 수 없습니다.",
'config-no-fts3' => "'''경고''': SQLite는 [//sqlite.org/fts3.html FTS3 모듈] 없이 컴파일되어, 검색 기능은 백엔드에 사용할 수 없습니다.",
'config-register-globals' => "'''경고: PHP의 <code>[http://php.net/register_globals register_globals]</code> 옵션이 활성화되어 있습니다.'''
'''가능하면 이를 비활성화하십시오.'''
-미디어위키는 작동하지만 서버에 잠재적인 보안 취약점에 노출됩니다.",
+미디어위키는 작동하지만 서버에 잠재적인 보안 취약점이 노출됩니다.",
'config-magic-quotes-runtime' => "'''치명: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]이 활성합니다!'''
이 옵션은 데이터를 입력하는 데 예기치 않는 손상이 일어납니다.
-설치할 수 없습니다. 또는 미디어위키가 사용하지 않는 이 옵션을 비활성화하십시오.",
+이 옵션을 비활성화하지 않는 한 미디어위키를 설치하고 사용할 수 없습니다.",
'config-magic-quotes-sybase' => "'''치명: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]이 활성합니다!'''
이 옵션은 데이터를 입력하는 데 예기치 않는 손상이 일어납니다.
-설치할 수 없습니다. 또는 미디어위키가 사용하지 않는 이 옵션을 비활성화하십시오.",
+이 옵션을 비활성화하지 않는 한 미디어위키를 설치하고 사용할 수 없습니다.",
'config-mbstring' => "'''치명: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]이 활성합니다!'''
이 옵션은 오류가 발생하고 데이터를 입력하는 데 예기치 않는 손상이 일어날 수 있습니다.
-설치할 수 없습니다. 또는 미디어위키가 사용하지 않는 이 옵션을 비활성화하십시오.",
+이 옵션을 비활성화하지 않는 한 미디어위키를 설치하고 사용할 수 없습니다.",
'config-ze1' => "'''치명: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]이 활성합니다!'''
-이 옵션은 미디어위키에 끔찍한 버그를 일으킵니다.
-설치할 수 없습니다. 또는 미디어위키가 사용하지 않는 이 옵션을 비활성화하십시오.",
+이 옵션은 미디어위키에 심간한 버그를 일으킵니다.
+이 옵션을 비활성화하지 않는 한 미디어위키를 설치하고 사용할 수 없습니다.",
'config-safe-mode' => "'''경고:''' [http://www.php.net/features.safe-mode 안전 모드]이 활성합니다!
-이는 특히 파일을 올리거나 <code>math</code>를 지원하는 데 문제가 발생할 수 있습니다.",
+특히 파일을 올리거나 <code>math</code>를 지원하는 데 문제가 발생할 수 있습니다.",
'config-xml-bad' => 'PHP의 XML 모듈이 없습니다.
미디어위키는 이 모듈의 기능이 필요하며 이 설정에서는 작동하지 않습니다.
Mandrake를 실행하고 있다면 php-xml 패키지를 설치하세요.',
@@ -10538,69 +10883,72 @@ Mandrake를 실행하고 있다면 php-xml 패키지를 설치하세요.',
미디어위키가 제대로 작동하려면 UTF-8 지원이 필요합니다.",
'config-memory-raised' => 'PHP의 <code>memory_limit</code>는 $1이며 $2(으)로 늘리세요.',
'config-memory-bad' => "'''경고:''' PHP의 <code>memory_limit</code>는 $1입니다.
-이는 아마도 너무 낮은 것 같습니다.
+아마도 너무 낮은 것 같습니다.
설치가 실패할 수 있습니다!",
'config-ctype' => "'''치명''': PHP는 [http://www.php.net/manual/en/ctype.installation.php Ctype 확장 기능]에 대해 지원하여 컴파일해야 합니다.",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache]가 설치되었습니다',
'config-apc' => '[http://www.php.net/apc APC]가 설치되었습니다',
'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache]가 설치되었습니다',
'config-no-cache' => "'''경고:''' [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] 또는 [http://www.iis.net/download/WinCacheForPhp WinCache]를 찾을 수 없습니다.
-개체 캐싱이 활성화되지 않습니다.",
+개체 캐싱을 활성화하지 않습니다.",
'config-mod-security' => "'''경고''': 웹 서버에 [http://modsecurity.org/ mod_security]가 허용되었습니다. 잘못 설정된 경우 미디어위키나 사용자가 임의의 콘텐츠를 게시할 수 있는 다른 소프트웨어에 대한 문제를 일으킬 수 있습니다.
[http://modsecurity.org/documentation/ mod_security] 문서를 참고하거나 임의의 오류가 발생할 경우 호스트의 지원 요청에 문의하십시오.",
'config-diff3-bad' => 'GNU diff3를 찾을 수 없습니다.',
'config-imagemagick' => 'ImageMagick를 찾았습니다: <code>$1</code>.
-올리기를 활성화할 경우 그림 섬네일이 활성화될 것입니다.',
+올리기를 활성화할 경우 그림 섬네일이 활성화됩니다.',
'config-gd' => '내장된 GD 그래픽 라이브러리를 찾았습니다.
-올리기를 활성화할 경우 그림 섬네일이 활성화될 것입니다.',
+올리기를 활성화할 경우 그림 섬네일이 활성화됩니다.',
'config-no-scaling' => 'GD 라이브러리나 ImageMagick를 찾을 수 없습니다.
-그림 섬네일이 비활성화될 것입니다.',
+그림 섬네일이 비활성화됩니다.',
'config-no-uri' => "'''오류:''' 현재 URI를 확인할 수 없습니다.
설치가 중단되었습니다.",
'config-no-cli-uri' => "'''경고''': 기본값을 사용하여 --scriptpath를 지정하지 않았습니다: <code>$1</code>.",
'config-using-server' => '"<nowiki>$1</nowiki>"(을)를 서버 이름으로 사용합니다.',
'config-using-uri' => '"<nowiki>$1$2</nowiki>"(을)를 서버 URL로 사용합니다.',
'config-uploads-not-safe' => "'''경고:''' 올리기에 대한 기본 디렉토리(<code>$1</code>)는 임의의 스크립트 실행에 취약합니다.
-미디어위키는 보안 위협에 대한 모든 올린 파일을 검사하지만, 이는 올리기를 활성화하기 전에 [//www.mediawiki.org/wiki/Manual:Security#Upload_security 이 보안 취약점을 해결할 것]을 매우 권장합니다.",
+미디어위키는 보안 위협에 대한 모든 올린 파일을 검사하지만, 올리기를 활성화하기 전에 [//www.mediawiki.org/wiki/Manual:Security#Upload_security 이 보안 취약점을 해결할 것]을 매우 권장합니다.",
'config-no-cli-uploads-check' => "'''경고:''' 올리기에 대한 기본 디렉토리(<code>$1</code>)는 CLI를 설치하는 동안 임의의 스크립트 실행에 대한 취약점에 대해 검사되지 않습니다.",
'config-brokenlibxml' => '시스템에 버그가 있는 PHP와 libxml2의 조합이 있으며 미디어위키나 다른 웹 어플리케이션에 숨겨진 데이터 손상을 일으킬 수 있습니다.
-PHP 5.2.9 이후와 libxml2 2.7.3 이후로 업그레이드하세요 ([//bugs.php.net/bug.php?id=45996 PHP에 제기한 버그]).
+PHP 5.2.9 이후와 libxml2 2.7.3 이후로 업그레이드하세요. ([//bugs.php.net/bug.php?id=45996 PHP에 제기한 버그])
설치가 중단되었습니다.',
'config-using531' => '미디어위키는 <code>__call()</code>을 참고로 매개 변수를 포함하는 버그로 인해 PHP $1(와)과 함께 사용할 수 없습니다.
문제를 해결하려면 PHP 5.3.2 이상로 업그레이드하거나 PHP 5.3.0으로 다운그레이드를 하세요.
설치가 중단되었습니다.',
- 'config-suhosin-max-value-length' => 'Suhosin(수호신)이 설치되었고 $1 바이트로 GET 매개 변수 길이를 제한하고 있습니다. 미디어위키의 ResourceLoader 구성 요소는 이 제한을 해결하지만 성능이 저하됩니다. 가능하면 php.ini의 suhosin.get.max_value_length에 1024 이상으로 설정하고 LocalSettings.php의 $wgResourceLoaderMaxQueryLength에 같은 값을 설정해야 합니다.',
+ 'config-suhosin-max-value-length' => 'Suhosin이 설치되었고 $1 바이트로 GET 매개 변수인 <code>length</code>를 제한하고 있습니다.
+미디어위키의 ResourceLoader 구성 요소는 이 제한을 해결하지만 성능이 저하됩니다.
+가능하면 <code>php.ini</code>의 <code>suhosin.get.max_value_length</code>에 1024 이상으로 설정하고 <code>LocalSettings.php</code>의 <code>$wgResourceLoaderMaxQueryLength</code>에 같은 값을 설정해야 합니다.',
'config-db-type' => '데이터베이스 종류:',
'config-db-host' => '데이터베이스 호스트:',
- 'config-db-host-help' => '데이터베이스 서버가 다른 서버에 있을 경우 여기에 호스트 이름이나 IP 주소를 입력하세요.
+ 'config-db-host-help' => '데이터베이스 서버가 다른 서버에 있으면 여기에 호스트 이름이나 IP 주소를 입력하세요.
-웹 호스팅을 공유하여 사용하는 경우 호스팅 공급자는 당신에게 이들 설명서의 올바른 호스트 이름을 표기해야 합니다.
+공유하는 웹 호스팅을 사용하고 있으면 호스팅 제공 업체는 호스트 이름을 설명하고 있을 것입니다.
-윈도 서버에 설치하고 MySQL을 사용할 경우 "localhost"는 서버 이름으로 작동하지 않을 수 있습니다. 그렇지 않으면 로컬 IP 주소로 "127.0.0.1"를 시도하세요.
+윈도 서버에 설치하고 MySQL을 사용하면 "localhost"는 서버 이름으로 작동하지 않을 수 있습니다. 그렇게 된다면 로컬 IP 주소로 "127.0.0.1"를 시도하세요.
-PostgreSQL을 사용할 경우 유닉스 소켓을 통해 연결되도록 입력란을 비워두세요.',
+PostgreSQL을 사용하면 유닉스 소켓을 통해 연결되도록 입력란을 비워두세요.',
'config-db-host-oracle' => '데이터베이스 TNS:',
'config-db-host-oracle-help' => '유효한 [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm 로컬 연결 이름]을 입력하세요. tnsnames.ora 파일이 이 설치에 보여야 합니다.<br />10g 이후의 클라이언트 라이브러리를 사용하는 경우 [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm 쉬운 연결] 네이밍 메소드도 사용할 수 있습니다.',
'config-db-wiki-settings' => '이 위키 식별',
'config-db-name' => '데이터베이스 이름:',
'config-db-name-help' => '위키를 식별하기 위한 이름을 선택하세요.
-이는 공백이 없어야 합니다.
+공백이 없어야 합니다.
-웹 호스팅을 공유해 사용하는 경우 호스팅 제공 업체도 당신에게 제어판을 통해 데이터베이스를 사용하거나 만들 수 있도록 특정 데이터베이스 이름을 제공합니다.',
+공유하는 웹 호스팅 사용하면 호스팅 제공 업체가 특정 데이터베이스 이름을 제공하거나 관리 패널에서 데이터베이스를 만들 수 있습니다.',
'config-db-name-oracle' => '데이터베이스 스키마:',
'config-db-account-oracle-warn' => '데이터베이스 백엔드로 오라클을 설치하기 위해 지원하는 세 가지 시나리오가 있습니다:
설치 과정의 일부로 데이터베이스 계정을 만들려면 설치를 위해 데이터베이스 계정으로 SYSDBA 역할을 가진 계정을 제공하고 웹 접근 계정에 대해 원하는 자격 증명을 지정하세요, 그렇지 않으면 수동으로 웹 접근 계정을 만들 수 있으며 (필요한 경우 권한 스키마 개체를 만들어야 합니다) 또는 다른 계정 두 개를 만들고 권한을 가진 하나의 웹 접근을 위한 제한된 하나를 제공할 수 있습니다.
-필요한 권한을 가진 계정을 만드는 스크립트는 이 설치의 "maintenance/oracle/" 디렉토리에서 찾을 수 있습니다. 제한된 계정을 사용하면 기본 계정으로 모든 관리 기능을 비활성화할 것을 염두해 두십시오.',
+필요한 권한을 가진 계정을 만드는 스크립트는 이 설치의 "maintenance/oracle/" 디렉토리에서 찾을 수 있습니다. 제한된 계정을 사용하면 기본 계정으로 모든 관리 기능을 비활성화할 것을 유의하십시오.',
'config-db-install-account' => '설치를 위한 사용자 계정',
'config-db-username' => '데이터베이스 사용자 이름:',
'config-db-password' => '데이터베이스 비밀번호:',
'config-db-password-empty' => '새 데이터베이스 사용자의 비밀번호를 입력하세요: $1.
-비밀번호 없이 사용자를 만들 수도 있지만 이는 안전하지 않습니다.',
+비밀번호 없이 사용자를 만들 수도 있지만 안전하지 않습니다.',
'config-db-install-username' => '설치 과정 중에 데이터베이스에 연결할 때 사용할 사용자 이름을 입력하세요.
-이는 미디어위키 계정의 사용자 이름이 아닌 데이터베이스에 대한 사용자 이름입니다.',
- 'config-db-install-password' => '설치 과정 중에 데이터베이스에 연결할 때 사용할 비밀번호을 입력하세요. 이는 미디어위키 계정의 비밀번호가 아닌 데이터베이스에 대한 비밀번호입니다.',
+미디어위키 계정의 사용자 이름이 아닌 데이터베이스에 대한 사용자 이름입니다.',
+ 'config-db-install-password' => '설치 과정 중에 데이터베이스에 연결할 때 사용할 비밀번호을 입력하세요.
+미디어위키 계정의 비밀번호가 아닌 데이터베이스에 대한 비밀번호입니다.',
'config-db-install-help' => '설치 과정 중에 데이터베이스에 연결할 때 사용할 사용자 이름과 비밀번호를 입력하세요.',
'config-db-account-lock' => '정상적으로 작동하는 동안 같은 사용자 이름과 비밀번호를 사용함',
'config-db-wiki-account' => '정상적인 작동을 위한 사용자 계정',
@@ -10615,29 +10963,29 @@ PostgreSQL을 사용할 경우 유닉스 소켓을 통해 연결되도록 입력
'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 바이너리',
'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
'config-charset-mysql4' => 'MySQL 4.0 UTF-8 하위 호환성',
- 'config-charset-help' => "'''경고:''' MySQL 4.1에서 '''UTF-8 하위 호환성'''을 사용하고 나서 <code>mysqldump</code>로 데이터베이스에 백업한다면 이는 모든 ASCII가 아닌 문자를 파괴하고 손상한 백업을 되돌릴 수 없습니다!
+ 'config-charset-help' => "'''경고:''' MySQL 4.1에서 '''UTF-8 하위 호환성'''을 사용하고 나서 <code>mysqldump</code>로 데이터베이스에 백업한다면 모든 ASCII가 아닌 문자를 파괴하고 손상한 백업을 되돌릴 수 없습니다!
'''바이너리 모드'''에서는 미디어위키는 바이너리 필드의 데이터베이스에 UTF-8 텍스트를 저장합니다.
-이는 MySQL의 UTF-8 모드를 보다 더 효율적이고 유니코드 문자의 전체 범위를 사용할 수 있습니다.
-'''UTF-8 모드'''에서는 MySQL은 데이터를 설정하는 어떤 문자열인지를 알 것이며, 표현하고 적절하게 그것을 변환할 수 있지만
-[//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes 기본 다국어 범위] 상의 문자를 저장하지 못하게 될 수 있습니다.",
+MySQL의 UTF-8 모드를 보다 더 효율적이고 유니코드 문자의 전체 범위를 사용할 수 있습니다.
+'''UTF-8 모드'''에서는 MySQL은 데이터를 설정하는 문자 집합을 알고 있기 때문에 적절하게 표현하고 변환할 수 있지만
+[//ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C_%ED%8F%89%EB%A9%B4#.EA.B8.B0.EB.B3.B8_.EB.8B.A4.EA.B5.AD.EC.96.B4_.ED.8F.89.EB.A9.B4 기본 다국어 평면] 밖의 문자를 저장할 수 없습니다.",
'config-mysql-old' => 'MySQL $1 이상이 필요하나 $2(이)가 있습니다.',
'config-db-port' => '데이터베이스 포트:',
'config-db-schema' => '미디어위키에 대한 스키마:',
- 'config-db-schema-help' => '이 스키마는 보통 괜찮습니다.
-필요로 알고 있을 경우에만 이를 바꾸세요.',
+ 'config-db-schema-help' => '보통 이 스키마는 문제가 없습니다.
+필요한 경우에만 바꾸세요.',
'config-pg-test-error' => "'''$1''' 데이터베이스에 연결할 수 없습니다: $2",
'config-sqlite-dir' => 'SQLite 데이터 디렉토리:',
- 'config-sqlite-dir-help' => 'SQLite는 하나의 파일에 모든 데이터를 저장합니다.
+ 'config-sqlite-dir-help' => "SQLite는 하나의 파일에 모든 데이터를 저장합니다.
-제공하는 디렉토리는 설치하는 동안 웹 서버에 의해 쓸 수 있어야 합니다.
+제공하는 디렉토리는 설치하는 동안 웹 서버가 쓸 수 있어야 합니다.
-PHP 파일이 있는 곳을 우리가 이를 맡길 수 없는 이유는 웹을 통해 접근할 수 없다는 것입니다.
+이 디렉토리는 웹을 통해 접근할 수 '''없어야''' 하는데 PHP 파일이 있는 곳에 넣을 수 없는 것은 이 때문입니다.
-설치 마법사가 이과 함께 .htaccess 파일을 만들지만 거기서 실패하면 누군가는 원본 데이터베이스에 접근하는 데 실패합니다.
-이는 원시 사용자 데이터(이메일 주소, 암호 해시) 뿐만 아니라 삭제된 개정판과 위키의 다른 제한된 데이터를 포함합니다.
+설치 프로그램은 <code>.htaccess</code> 파일을 작성하지만 이것이 실패하면 누군가가 원본 데이터베이스에 접근할 수 있습니다.
+데이터베이스는 원본 사용자 데이터(이메일 주소, 해시한 비밀번호) 뿐만 아니라 삭제된 판과 위키의 다른 제한된 데이터를 포함합니다.
-<code>/var/lib/mediawiki/yourwiki</code>와 같이 모두 다른 곳에서 데이터베이스를 넣어보도록 하세요.',
+예를 들어 <code>/var/lib/mediawiki/yourwiki</code>와 같이 다른 곳에 데이터베이스를 넣는 것이 좋습니다.",
'config-oracle-def-ts' => '기본 테이블공간:',
'config-oracle-temp-ts' => '임시 테이블공간:',
'config-type-oracle' => '오라클',
@@ -10646,12 +10994,12 @@ PHP 파일이 있는 곳을 우리가 이를 맡길 수 없는 이유는 웹을
$1
-데이터베이스 시스템이 표시되지 않을 때 아래에 나열된 다음 지원을 활성화하려면 당신은 위의 링크된 지시에 따라 사용해볼 수도 있습니다.',
+데이터베이스 시스템이 표시되지 않을 때 아래에 나열된 다음 지원을 활성화하려면 위의 링크된 지시에 따라 설치해볼 수 있습니다.',
'config-support-mysql' => '* $1은 미디어위키의 기본 대상으로 가장 잘 지원합니다. ([http://www.php.net/manual/en/mysql.installation.php MySQL을 지원하여 PHP를 컴파일하는 방법])',
- 'config-support-postgres' => '* $1은 MySQL의 대안으로 인기있는 오픈 소스 데이터베이스 시스템입니다. ([http://www.php.net/manual/en/pgsql.installation.php PostgreSQL을 지원하여 PHP를 컴파일하는 방법]) 몇가지 사소한 해결하지 못한 버그가 있을 수 있으며, 이를 제작 환경에서 사용하지 않는 것이 좋습니다.',
+ 'config-support-postgres' => '* $1은 MySQL의 대안으로 인기 있는 오픈 소스 데이터베이스 시스템입니다. ([http://www.php.net/manual/en/pgsql.installation.php PostgreSQL을 지원하여 PHP를 컴파일하는 방법]) 몇가지 사소한 해결하지 못한 버그가 있을 수 있으며, 이를 제작 환경에서 사용하지 않는 것이 좋습니다.',
'config-support-sqlite' => '* $1는 매우 잘 지원하는 가벼운 데이터베이스 시스템입니다. ([http://www.php.net/manual/en/pdo.installation.php SQLite를 지원하여 PHP를 컴파일하는 방법], PDO 사용)',
'config-support-oracle' => '* $1은 상용 엔터프라이스 데이터베이스입니다. ([http://www.php.net/manual/en/oci8.installation.php OCI8을 지원하여 PHP를 컴파일하는 방법])',
- 'config-support-ibm_db2' => '* $1는 상용 엔터프라이즈 데이터베이스입니다.',
+ 'config-support-ibm_db2' => '* $1는 상용 엔터프라이즈 데이터베이스입니다.([http://www.php.net/manual/en/ibm-db2.installation.php IBM DB2를 지원하여 PHP를 컴파일하는 방법])',
'config-header-mysql' => 'MySQL 설정',
'config-header-postgres' => 'PostgreSQL 설정',
'config-header-sqlite' => 'SQLite 설정',
@@ -10672,39 +11020,39 @@ ASCII 글자 (a-z, A-Z), 숫자 (0-9), 밑줄 (_)과 하이픈 (-)만 사용하
호스트, 계정 이름과 비밀번호를 확인하고 다시 시도하세요.',
'config-invalid-schema' => '미디어위키 "$1"에 대한 스키마가 잘못됐습니다.
ASCII 글자 (a-z, A-Z), 숫자 (0-9), 밑줄 (_)과 하이픈 (-)만 사용하세요.',
- 'config-db-sys-create-oracle' => '설치 마법사는 새 계정을 만들기 위한 SYSDBA 계정만을 지원합니다.',
+ 'config-db-sys-create-oracle' => '설치 프로그램은 새 계정을 만들기 위한 SYSDBA 계정만을 지원합니다.',
'config-db-sys-user-exists-oracle' => '"$1" 사용자 계정이 이미 존재합니다. SYSDBA는 새 계정을 만드는 데에만 사용할 수 있습니다!',
'config-postgres-old' => 'PostgreSQL $1 이상이 필요하나 $2(이)가 있습니다.',
'config-sqlite-name-help' => '위키를 식별하기 위한 이름을 선택하세요.
공백이나 하이픈을 사용하지 마십시오.
-이는 SQLite 데이터 파일 이름에 사용됩니다.',
- 'config-sqlite-parent-unwritable-group' => '<code><nowiki>$1</nowiki></code> 데이터 디렉토리를 만들 수 없으며 <code><nowiki>$2</nowiki></code> 상위 디렉토리에 웹 서버에 의해 쓸 수 없기 때문입니다.
+SQLite 데이터 파일 이름에 사용됩니다.',
+ 'config-sqlite-parent-unwritable-group' => '<code><nowiki>$1</nowiki></code> 데이터 디렉토리를 만들 수 없으며 웹 서버는 <code><nowiki>$2</nowiki></code> 상위 디렉토리에 쓸 수 없기 때문입니다.
-설치 마법사는 웹 서버로 실행중인 사용자를 결정할 수 없습니다.
-계속하려면 이를 쓸 수 있는 <code><nowiki>$3</nowiki></code> 디렉토리를 만드세요.
+설치 프로그램은 웹 서버로 실행중인 사용자를 지정할 수 없습니다.
+계속하려면 웹 서버가 쓸 수 있는 <code><nowiki>$3</nowiki></code> 디렉토리를 만드세요.
유닉스/리눅스 시스템에서의 수행:
<pre>cd $2
mkdir $3
chgrp $4 $3
chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => '<code><nowiki>$1</nowiki></code> 데이터 디렉토리를 만들 수 없으며 <code><nowiki>$2</nowiki></code> 상위 디렉토리에 웹 서버에 의해 쓸 수 없기 때문입니다.
+ 'config-sqlite-parent-unwritable-nogroup' => '<code><nowiki>$1</nowiki></code> 데이터 디렉토리를 만들 수 없으며 웹 서버는 <code><nowiki>$2</nowiki></code> 상위 디렉토리에 쓸 수 없기 때문입니다.
-설치 마법사는 웹 서버로 실행중인 사용자를 결정할 수 없습니다.
-계속하려면 이(와 기타!)를 전역으로 쓸 수 있는 <code><nowiki>$3</nowiki></code> 디렉토리를 만드세요.
+설치 프로그램은 웹 서버로 실행중인 사용자를 지정할 수 없습니다.
+계속하려면 웹 서버(와 기타!)가 전역으로 쓸 수 있는 <code><nowiki>$3</nowiki></code> 디렉토리를 만드세요.
유닉스/리눅스 시스템에서의 수행:
<pre>cd $2
mkdir $3
chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => '"$1" 데이터 디렉토리를 만드는 중 오류났습니다.
+ 'config-sqlite-mkdir-error' => '"$1" 데이터 디렉토리를 만드는 중 오류가 났습니다.
경로를 확인하고 다시 시도하세요.',
'config-sqlite-dir-unwritable' => '"$1" 디렉토리에 쓸 수 없습니다.
웹 서버를 쓸 수 있도록 권한을 바꾸고 다시 시도하세요.',
'config-sqlite-connection-error' => '$1.
호스트, 계정 이름과 비밀번호를 확인하고 다시 시도하세요.',
- 'config-sqlite-readonly' => '<code>$1</code> 파일은 쓰기가 불가능합니다.',
+ 'config-sqlite-readonly' => '<code>$1</code> 파일은 쓸 수 없습니다.',
'config-sqlite-cant-create-db' => '<code>$1</code> 데이터베이스 파일을 만들 수 없습니다.',
'config-sqlite-fts3-downgrade' => 'PHP가 FTS3 지원이 없어졌습니다. 테이블을 다운그레이드하세요.',
'config-can-upgrade' => "이 데이터베이스에 미디어위키 테이블이 있습니다.
@@ -10714,12 +11062,12 @@ chmod a+w $3</pre>',
이제 [$1 위키를 시작]할 수 있습니다.
만약 <code>LocalSettings.php</code> 파일을 다시 만들기를 원하면 아래의 버튼을 클릭하세요.
-이것은 위키에 문제가 있지 않는 한 '''권장하지 않습니다'''.",
+위키에 문제가 있지 않는 한 '''권장하지 않습니다'''.",
'config-upgrade-done-no-regenerate' => '업그레이드가 완료되었습니다.
이제 [$1 위키를 시작]할 수 있습니다.',
- 'config-regenerate' => 'LocalSettings.php 다시 만들기 →',
- 'config-show-table-status' => 'SHOW TABLE STATUS 쿼리 실패!',
+ 'config-regenerate' => '<code>LocalSettings.php</code> 다시 만들기 →',
+ 'config-show-table-status' => '<code>SHOW TABLE STATUS</code> 쿼리를 실패했습니다!',
'config-unknown-collation' => "'''경고:''' 데이터베이스가 인식하지 않는 정렬을 사용하고 있습니다.",
'config-db-web-account' => '웹 접근을 위한 데이터베이스 계정',
'config-db-web-help' => '위키의 일반적인 작업 중에 데이터베이스 서버에 연결하는 데 사용할 웹 서버에 대한 계정 이름과 비밀번호를 선택하세요.',
@@ -10731,13 +11079,13 @@ chmod a+w $3</pre>',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
'config-mysql-myisam-dep' => "'''경고''': 미디어위키와 함께 사용하도록 권장하지 않는 MySQL에 대한 스토리지 엔진으로 MyISAM을 선택하였습니다. 이유는:
-* 이는 테이블이 잠겨있어 동시성을 거의 지원하지 않습니다
-* 이는 다른 엔진보다 손상이 더 자주 발생합니다
+* 테이블이 잠겨있어 동시성을 거의 지원하지 않습니다
+* 다른 엔진보다 손상이 더 자주 발생합니다
* 미디어위키 바탕 코드가 항상 정상적으로 MyISAM을 처리하지 않습니다
MySQL 설치가 InnoDB를 지원한다면 그 선택 대신에 InnoDB를 선택할 것을 매우 권장합니다.
MySQL 설치가 InnoDB를 지원하지 않는다면 아마도 업그레이드를 해야 할 수도 있습니다.",
- 'config-mysql-engine-help' => "'''InnoDB'''는 동시적인 지원에 좋기 때문에 거의 항상 최고의 옵션입니다.
+ 'config-mysql-engine-help' => "'''InnoDB'''는 동시적인 지원에 좋기 때문에 대부분 최고의 옵션입니다.
'''MyISAM'''은 단일 사용자 또는 읽기 전용 설치에 빠를 수 있습니다.
MyISAM 데이터베이스는 InnoDB 데이터베이스보다 더 자주 손실될 수 있습니다.",
@@ -10745,12 +11093,12 @@ MyISAM 데이터베이스는 InnoDB 데이터베이스보다 더 자주 손실
'config-mysql-binary' => '바이너리',
'config-mysql-utf8' => 'UTF-8',
'config-mysql-charset-help' => "'''바이너리 모드'''에서는 미디어위키는 바이너리 필드의 데이터베이스에 UTF-8 텍스트를 저장합니다.
-이는 MySQL의 UTF-8 모드를 보다 더 효율적이고 유니코드 문자의 전체 범위를 사용할 수 있습니다.
-'''UTF-8 모드'''에서는 MySQL은 데이터를 설정하는 어떤 문자열인지를 알 것이며, 표현하고 적절하게 그것을 변환할 수 있지만
-[//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes 기본 다국어 범위] 상의 문자를 저장하지 못하게 될 수 있습니다.",
- 'config-ibm_db2-low-db-pagesize' => "당신의 DB2 데이터베이스에 부족한 페이지 크기가 기본 테이블 공간에 있습니다. 페이지 크기는 '''32K''' 이상이어야 합니다.",
+MySQL의 UTF-8 모드를 보다 더 효율적이고 유니코드 문자의 전체 범위를 사용할 수 있습니다.
+'''UTF-8 모드'''에서는 MySQL은 데이터를 설정하는 문자 집합을 알고 있기 때문에 적절하게 표현하고 변환할 수 있지만
+[//ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C_%ED%8F%89%EB%A9%B4#.EA.B8.B0.EB.B3.B8_.EB.8B.A4.EA.B5.AD.EC.96.B4_.ED.8F.89.EB.A9.B4 기본 다국어 평면] 밖의 문자를 저장할 수 없습니다.",
+ 'config-ibm_db2-low-db-pagesize' => "DB2 데이터베이스에 부족한 페이지 크기가 기본 테이블 공간에 있습니다. 페이지 크기는 '''32K''' 이상이어야 합니다.",
'config-site-name' => '위키 이름:',
- 'config-site-name-help' => '이는 브라우저 제목 표시줄과 다른 여러 곳에 나타날 것입니다.',
+ 'config-site-name-help' => '브라우저 제목 표시줄과 다른 여러 곳에 나타납니다.',
'config-site-name-blank' => '사이트 이름을 입력하세요.',
'config-project-namespace' => '프로젝트 이름공간:',
'config-ns-generic' => '프로젝트',
@@ -10759,7 +11107,7 @@ MyISAM 데이터베이스는 InnoDB 데이터베이스보다 더 자주 손실
'config-ns-other-default' => '내위키',
'config-project-namespace-help' => '위키백과의 예를 따라서, 많은 위키는 "\'\'\'프로젝트 이름공간\'\'\'"에 그들의 콘텐츠 페이지에서 그들의 정책 페이지는 별도로 보관합니다.
이 이름공간에 있는 모든 페이지의 제목은 여기서 지정할 수 있는 특정 접두어로 시작합니다.
-보통 이 접두어는 위키의 이름에서 파생되지만, 이는 "#" 또는 ":"와 같은 특수 문자를 포함할 수 없습니다.',
+보통 이 접두어는 위키의 이름에서 파생되지만, "#" 또는 ":"와 같은 특수 문자를 포함할 수 없습니다.',
'config-ns-invalid' => '특정 "<nowiki>$1</nowiki>" 이름공간이 잘못되었습니다.
다른 프로젝트 이름공간을 지정하세요.',
'config-ns-conflict' => '특정 "<nowiki>$1</nowiki>" 이름공간이 기본 미디어위키 이름공간과 충돌합니다.
@@ -10769,7 +11117,7 @@ MyISAM 데이터베이스는 InnoDB 데이터베이스보다 더 자주 손실
'config-admin-password' => '비밀번호:',
'config-admin-password-confirm' => '비밀번호 확인:',
'config-admin-help' => '"홍길동"과 같이 여기에 원하는 사용자 이름을 입력하세요.
-이는 위키에 로그인하는 데 사용되는 이름입니다.',
+위키에 로그인하는 데 사용되는 이름입니다.',
'config-admin-name-blank' => '관리자의 사용자 이름을 입력하세요.',
'config-admin-name-invalid' => '특정 "<nowiki>$1</nowiki>" 사용자 이름이 잘못되었습니다.
다른 사용자 이름을 지정하세요.',
@@ -10782,8 +11130,8 @@ MyISAM 데이터베이스는 InnoDB 데이터베이스보다 더 자주 손실
'config-admin-error-password' => '"<nowiki>$1</nowiki>" 관리자의 비밀번호를 설정하는 중 내부 오류가 발생했습니다: <pre>$2</pre>',
'config-admin-error-bademail' => '이메일 주소를 잘못 입력하였습니다.',
'config-subscribe' => '[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce 배포 발표 메일링 리스트]에 가입합니다.',
- 'config-subscribe-help' => '이는 중요한 보안 알림을 포함한 배포 알림에 대해 사용되는 로우 볼륨 메일링 리스트입니다.
-당신이 이를 구독하고 나서 새 버전이 나올 때 미디어위키 설치를 업데이트해야합니다.',
+ 'config-subscribe-help' => '중요한 보안 알림을 포함한 배포 알림에 대해 사용되는 로우 볼륨 메일링 리스트입니다.
+이 리스트를 구독하고 나서 새 버전이 나올 때 미디어위키 설치를 업데이트하십시오.',
'config-subscribe-noemail' => '이메일 주소를 제공하지 않고 배포 발표 메일링 리스트에 가입하려 합니다.
메일링 리스트에 가입하고자 할 경우 이메일 주소를 제공하세요.',
'config-almost-done' => '거의 다 완료했습니다!
@@ -10791,22 +11139,23 @@ MyISAM 데이터베이스는 InnoDB 데이터베이스보다 더 자주 손실
'config-optional-continue' => '더 많은 질문을 물어보세요.',
'config-optional-skip' => '지겨워요, 그냥 위키를 설치할래요.',
'config-profile' => '사용자 권한 프로필:',
- 'config-profile-wiki' => '평범한 위키',
+ 'config-profile-wiki' => '열린 위키',
'config-profile-no-anon' => '계정 만들기 필요',
- 'config-profile-fishbowl' => '승인된 편집자만 이용 가능',
+ 'config-profile-fishbowl' => '승인된 편집자만',
'config-profile-private' => '비공개 위키',
- 'config-profile-help' => "위키는 당신이 가능한 한 많은 사람들이 편집하도록 할 때 최고로 적합합니다.
-미디어위키에서는 최근 바뀜을 검토하고, 선하거나 악의적인 사용자에 의해 수행되는 모든 손실을 되돌리는 것이 쉽습니다.
+ 'config-profile-help' => "위키는 많은 사람들이 가능한 한 편집할 수 있도록 하면 가장 뛰어난 역할을 합니다.
+미디어위키에서는 최근 바뀜을 검토하기 쉽고, 선하거나 악의적인 사용자의 어떠한 손실을 되돌리는 것이 쉽습니다.
-그러나 많은 사람들이 미디어위키가 다양한 역할의 유용하지만, 때로는 그것이 위키 방식의 장점을 모두 설득하기 쉽지 않음을 발견했습니다.
+그러나 많은 사람이 미디어위키는 다양한 역할로 유용하지만, 때로는 모든 사람에게 위키 방식의 장점을 설득하기 쉽지 않을 지도 모릅니다.
그래서 선택할 수 있습니다.
-'''{{int:config-profile-wiki}}'''는 로그인하지 않고도 누구나 편집할 수 있습니다.
-'''{{int:config-profile-no-anon}}'''는 추가적으로 필요한 책임을 제공하지만, 기존의 기여자를 망칠 수도 있습니다.
+'''{{int:config-profile-wiki}}''' 모델은 로그인하지 않고도 누구나 편집할 수 있습니다.
+'''{{int:config-profile-no-anon}}'''인 위키는 각 편집에 추가적으로 강한 책임을 제공하지만, 부담 없는 기여를 저해할 수도 있습니다.
-'''{{int:config-profile-fishbowl}}''' 같은 경우는 승인된 사용자만 편집할 수 있지만, 대중은 역사를 포함하여 페이지를 볼 수 있습니다. '''{{int:config-profile-private}}'''는 승인된 사용자만 같은 그룹에서 편집할 수 있고 볼 수 있습니다.
+'''{{int:config-profile-fishbowl}}''' 시나리오는 승인된 사용자만 편집할 수 있지만, 대중은 역사를 포함하여 문서를 볼 수 있습니다.
+'''{{int:config-profile-private}}'''는 승인된 사용자만 문서를 볼 수 있으며 해당 그룹을 편집할 수 있습니다.
-더 복잡한 사용자 권한을 설정하여 설치한 후 사용할 수 있도록 하려면 [//www.mediawiki.org/wiki/Manual:User_rights 관련 매뉴얼 항목]을 참고하세요.",
+더 복잡한 사용자 권한을 설정은 설치한 후 사용할 수 있으며 [//www.mediawiki.org/wiki/Manual:User_rights 관련 설명서 항목]을 참고하세요.",
'config-license' => '저작권 및 라이선스:',
'config-license-none' => '라이선스 바닥글 없음',
'config-license-cc-by-sa' => '크리에이티브 커먼즈 저작자표시-동일조건변경허락',
@@ -10816,15 +11165,15 @@ MyISAM 데이터베이스는 InnoDB 데이터베이스보다 더 자주 손실
'config-license-gfdl' => 'GNU 자유 문서 사용 허가서 1.3 이상',
'config-license-pd' => '퍼블릭 도메인',
'config-license-cc-choose' => '다른 크리에이티브 커먼즈 라이선스 선택',
- 'config-license-help' => '많은 공개 위키는 모든 기여를 [http://freedomdefined.org/Definition 자유 라이선스] 하에 넣습니다.
-이럴 경우 커뮤니티 소유권의 이해를 할 수 있도록 하고 장기적인 기여를 장려합니다.
-이는 일반적으로 개인 또는 회사 위키에 대해서는 필요하지 않습니다.
+ 'config-license-help' => "많은 공개 위키는 모든 기여를 [http://freedomdefined.org/Definition 자유 라이선스] 하에 넣습니다.
+이렇게 하면 커뮤니티 소유권의 이해를 할 수 있도록 하고 장기적인 기여를 장려합니다.
+일반적으로 개인 또는 회사 위키에 대해서는 필요하지 않습니다.
-위키백과의 텍스트를 사용할 수 있도록 하고 위키백과가 위키에서 복사한 텍스트를 사용할 수 있도록 원한다면 크리에이티브 커먼즈 저작자표시-동일조건변경허락으로 선택해야 합니다.
+위키백과의 텍스트를 사용할 수 있도록 하고 위키백과가 위키에서 복사한 텍스트를 사용할 수 있도록 원한다면 '''크리에이티브 커먼즈 저작자표시-동일조건변경허락'''으로 선택해야 합니다.
위키백과는 이전에 GNU 자유 문서 사용 허가서를 사용했습니다.
-GFDL은 유효한 라이선스이지만 이는 이해하기 어렵습니다.
-이는 GFDL 하에 라이선스 내용을 재사용하는 것도 어렵습니다.',
+GFDL은 유효한 라이선스이지만 내용을 이해하기 어렵습니다.
+GFDL 하에 사용을 허가한 내용을 재사용하는 것도 어렵습니다.",
'config-email-settings' => '이메일 설정',
'config-enable-email' => '발신 이메일 활성화',
'config-enable-email-help' => '이메일을 작동하려면 [http://www.php.net/manual/en/mail.configuration.php PHP의 메일 설정]을 올바르게 설정해야 합니다.
@@ -10836,17 +11185,17 @@ GFDL은 유효한 라이선스이지만 이는 이해하기 어렵습니다.
'config-email-watchlist' => '주시문서 목록 알림 활성화',
'config-email-watchlist-help' => '환경 설정에서 활성화한 경우 사용자가 주시한 문서에 대한 알림을 받도록 활성화합니다.',
'config-email-auth' => '이메일 인증 활성화',
- 'config-email-auth-help' => "이 설정이 활성화되어 있으면 사용자는 이메일 주소를 설정하거나 바꿀 때마다 그들에게 보낸 링크를 사용하여 이메일 주소를 확인해야 합니다.
+ 'config-email-auth-help' => "이 설정이 활성화되어 있으면 사용자는 이메일 주소를 설정하거나 바꿀 때마다 링크를 사용하여 이메일 주소를 확인해야 합니다.
인증된 이메일 주소만 다른 사용자로부터의 이메일이나 바뀜 알림 이메일을 받을 수 있습니다.
이메일 기능의 남용 가능성이 있기 때문에 이 옵션을 설정하는 것은 공개 위키에서 '''권장'''합니다.",
'config-email-sender' => '반송 이메일 주소',
'config-email-sender-help' => '발신한 이메일에 대한 반송 주소로 사용할 이메일 주소를 입력하세요.
-이는 반송할 때 보내는 주소입니다.
+반송할 때 보내는 주소입니다.
대부분의 메일 서버는 적어도 도메인 이름 부분은 유효합니다.',
'config-upload-settings' => '그림과 파일 올리기',
'config-upload-enable' => '파일 올리기 활성화',
'config-upload-help' => '파일 올리기는 서버에 잠재적인 보안 위험에 쉽게 노출될 수 있습니다.
-자세한 내용은 매뉴얼의 [//www.mediawiki.org/wiki/Manual:Security 보안 문단]을 읽어보세요.
+자세한 내용은 매뉴얼의 [//www.mediawiki.org/wiki/Manual:Security 보안 문단]을 참고하세요.
파일 올리기를 활성화하려면 미디어위키의 루트 디렉토리에 있는 <code>images</code> 하위 디렉토리에서 웹 서버가 기록할 수 있도록 모드를 바꿉니다.
그 다음 이 옵션을 활성화합니다.',
@@ -10854,10 +11203,10 @@ GFDL은 유효한 라이선스이지만 이는 이해하기 어렵습니다.
'config-upload-deleted-help' => '삭제된 파일을 보관할 디렉토리를 선택하세요.
이상적으로 웹에서 접근할 수 없게 해야 합니다.',
'config-logo' => '로고 URL:',
- 'config-logo-help' => '미디어위키 기본 스킨은 사이드바 메뉴 위에 135×160픽셀의 로고를 포함하고 있습니다.
+ 'config-logo-help' => '미디어위키의 기본 스킨은 사이드바 메뉴 위에 135×160 픽셀의 로고를 포함하고 있습니다.
적당한 크기로 이미지를 올리고 URL을 여기에 입력하세요.
-로고 사용을 원하지 않으면 이 상자를 비워 두십시오.',
+로고 사용을 원하지 않으면 이 상자를 비우세요.',
'config-instantcommons' => '인스턴트 공용 활성화',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons 인스턴트 공용]은 [//commons.wikimedia.org/ 위키미디어 공용] 사이트에서 찾을 수 있는 그림, 소리 및 다른 미디어를 위키에서 사용할 수 있도록 하는 기능입니다.
이렇게 하려면 미디어위키가 인터넷에 접근해야합니다.
@@ -10887,11 +11236,11 @@ GFDL은 유효한 라이선스이지만 이는 이해하기 어렵습니다.
'config-extensions' => '확장 기능',
'config-extensions-help' => '위에 나열된 확장 기능이 <code>./extensions</code>에서 발견되었습니다.
-이는 추가적인 설정이 필요할 수 있습니다만 지금 활성화시킬 수 있습니다.',
- 'config-install-alreadydone' => "'''경고:''' 당신은 이미 미디어위키를 설치하였고 다시 설치하려고 합니다.
+추가적인 설정이 필요할 수 있습니다만 지금 활성화시킬 수 있습니다.',
+ 'config-install-alreadydone' => "'''경고:''' 이미 미디어위키를 설치했고 다시 설치하려고 합니다.
다음 페이지에서 진행하세요.",
'config-install-begin' => '"{{int:config-continue}}"을 누르면 미디어위키의 설치를 시작합니다.
-그래도 바꾸는 것을 원한다면 뒤로를 누릅니다.',
+그래도 바꾸는 것을 원한다면 "{{int:config-back}}"를 누르세요.',
'config-install-step-done' => '완료',
'config-install-step-failed' => '실패',
'config-install-extensions' => '확장 기능을 포함하는 중',
@@ -10909,7 +11258,7 @@ GFDL은 유효한 라이선스이지만 이는 이해하기 어렵습니다.
현재 미디어위키는 테이블을 웹 사용자가 소유해야 합니다. 다른 웹 계정 이름을 지정하거나 "뒤로"를 클릭하고 적절한 권한의 설치할 사용자를 지정하세요.',
'config-install-user' => '데이터베이스 사용자를 만드는 중',
- 'config-install-user-alreadyexists' => '"$1" 사용자가 이미 있음',
+ 'config-install-user-alreadyexists' => '"$1" 사용자가 이미 있습니다',
'config-install-user-create-failed' => '"$1" 사용자 만드는 중 실패: $2',
'config-install-user-grant-failed' => '"$1" 사용자에 대한 권한 부여 실패: $2',
'config-install-user-missing' => '지정한 "$1" 사용자가 존재하지 않습니다.',
@@ -10935,29 +11284,30 @@ GFDL은 유효한 라이선스이지만 이는 이해하기 어렵습니다.
'config-install-done' => "'''축하합니다!'''
미디어위키가 성공적으로 설치되었습니다.
-설치 마법사가 <code>LocalSettings.php</code> 파일을 만들었습니다.
-이는 모든 설정이 포함되어 있습니다.
+설치 프로그램이 <code>LocalSettings.php</code> 파일을 만들었습니다.
+모든 설정이 포함되어 있습니다.
-이를 다운로드하여 위키 설치의 거점에 넣어야 합니다 (index.php와 같은 디렉토리). 다운로드가 자동으로 시작됩니다.
+파일을 다운로드하여 위키 설치의 거점에 넣어야 합니다. (index.php와 같은 디렉토리) 다운로드가 자동으로 시작됩니다.
다운로드가 제공되지 않을 경우나 그것을 취소한 경우에는 아래의 링크를 클릭하여 다운로드를 다시 시작할 수 있습니다:
$3
-'''참고''': 지금 이렇게 하지 않으면, 이 설정 파일을 다운로드하지 않고 설치를 종료할 경우 만들어진 설정 파일은 나중에 사용할 수 없습니다.
+'''참고''': 이 생성한 설정 파일을 다운로드하지 않고 설치를 끝내면 이 파일은 나중에 사용할 수 없습니다.
완료되었으면 '''[$2 위키에 들어갈 수 있습니다]'''.",
- 'config-download-localsettings' => 'LocalSettings.php 다운로드',
+ 'config-download-localsettings' => '<code>LocalSettings.php</code> 다운로드',
'config-help' => '도움말',
'config-nofile' => '"$1" 파일을 찾을 수 없습니다. 이미 삭제되었나요?',
'mainpagetext' => "'''미디어위키가 성공적으로 설치되었습니다.'''",
- 'mainpagedocfooter' => '[//meta.wikimedia.org/wiki/Help:Contents 이곳]에서 위키 프로그램에 대한 정보를 얻을 수 있습니다.
+ 'mainpagedocfooter' => '[//meta.wikimedia.org/wiki/Help:Contents 이곳]에서 위키 소프트웨어에 대한 정보를 얻을 수 있습니다.
== 시작하기 ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings 설정하기]
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings 설정하기 목록]
* [//www.mediawiki.org/wiki/Manual:FAQ 미디어위키 FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce 미디어위키 발표 메일링 리스트]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce 미디어위키 발표 메일링 리스트]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources 내 언어로 미디어위키 지역화]',
);
/** Karachay-Balkar (къарачай-малкъар)
@@ -10982,21 +11332,21 @@ $messages['ksh'] = array(
'config-desc' => 'Et Projramm för Mediwiki opzesäze.',
'config-title' => 'MediaWiki $1 opsäze',
'config-information' => 'Enfomazjuhn',
- 'config-localsettings-upgrade' => 'De Dattei <code lang="en">LocalSettings.php</code> es ald doh.
+ 'config-localsettings-upgrade' => 'De Dattei <code lang="en"><code>LocalSettings.php</code></code> es ald doh.
De Projramme vum Wiki künne op der neußte Shtand jebraat wääde:
Donn doför dä Wäät vum <code lang="en">$wgUpgradeKey</code> en dat heh Feld enjävve.
-Do fenggs_et en dä Dattei <code lang="en">LocalSettings.php</code> om ẞööver.',
- 'config-localsettings-cli-upgrade' => 'En Dattei <code lang="en">LocalSettings.php</code> es jefonge woode.
+Do fenggs_et en dä Dattei <code lang="en"><code>LocalSettings.php</code></code> om ẞööver.',
+ 'config-localsettings-cli-upgrade' => 'En Dattei <code lang="en"><code>LocalSettings.php</code></code> es jefonge woode.
Öm et Wiki_Projramm op ene neue Shtand ze bränge, donn <code lang="en">update.php</code> oproofe.',
'config-localsettings-key' => 'Der Schlößel för et Projramm op ene neue Schtand ze bränge:',
'config-localsettings-badkey' => 'Dinge Schlößel paß nit.',
'config-upgrade-key-missing' => 'Mer han jefonge, dat MediaWiki ald enschtalleed es.
-Üm de Projramme un Daate o der neue Schtand bränge ze künne, dunn aan et Engk vun dä Dattei <code lang="en">LocalSettings.php</code> op dämm ẞööver:
+Üm de Projramme un Daate o der neue Schtand bränge ze künne, dunn aan et Engk vun dä Dattei <code lang="en"><code>LocalSettings.php</code></code> op dämm ẞööver:
$1
aanhange.',
- 'config-localsettings-incomplete' => 'Mer han en Dattei <code lang="en">LocalSettings.php:</code> jefonge, ävver di schingk nit kumplätt ze sin.
+ 'config-localsettings-incomplete' => 'Mer han en Dattei <code lang="en"><code>LocalSettings.php</code>:</code> jefonge, ävver di schingk nit kumplätt ze sin.
De Varijable <code lang="en">$1</code> es nit jesatz.
Bes esu joot, un donn di Dattei esu aanpaße, dat se jesaz ea, un dann donn op „{{int:config-continue}}“ klecke.',
'config-localsettings-connection-error' => 'Ene Fähler es opjetrodde wi mer en Verbendung noh de Datebangk opmaache wullte met dä Enshtellunge uß dä Dattei <code lang="en">LocalSettings</code> udder uß dä Dattei <code lang="en">LocalSettings</code> un et hät nit jeflupp. Bes esu joot un dat repareere un versöhg et dann norr_ens.
@@ -11134,7 +11484,7 @@ Heh jeihd et nit wigger.',
'config-using531' => 'MediaWiki läuf nit met PHP $1 zosamme wääje enem [//bugs.php.net/bug.php?id=50394 Fähler em Zosammehang met Parrameetere för <code lang="en">__call()</code>].
Jangk op de Version 5.3.2 vum <i lang="en">PHP</i> ov dohnoh, udder op de Version 5.3.0 udder dovöör, öm dat Problem ze ömjonn.
Heh jeiht et nit wigger.',
- 'config-suhosin-max-value-length' => '<i lang="en">Suhosin</i> es enschtalleet. Dröm kann ene <code lang="en">GET</code>-Parrameeter nit övver {{PLURAL:$1|ei Byte|$q Bytes|noll Byte}} lang wääde. En MediaWiki singe <i lang="en">ResourceLoader</i> kütt doh zwa drömeröm, ävver dat brems. Wann müjelesch, doht <code lang="en">suhosin.get.max_value_length</code> en dä Dattei <code lang="en">php.ini</code> op 1024 Bytes udder drövver enschtälle. un dann moß <code lang="en">$wgResourceLoaderMaxQueryLength</code> en dä Dattei <code lang="en">LocalSettings.php</code> op däsälve Wäät jesaz wääde.',
+ 'config-suhosin-max-value-length' => '<i lang="en">Suhosin</i> es enschtalleet. Dröm kann ene <code lang="en">GET</code>-Parrameeter nit övver {{PLURAL:$1|ei Byte|$q Bytes|noll Byte}} lang wääde. En MediaWiki singe <i lang="en">ResourceLoader</i> kütt doh zwa drömeröm, ävver dat brems. Wann müjelesch, doht <code lang="en">suhosin.get.max_value_length</code> en dä Dattei <code lang="en">php.ini</code> op 1024 Bytes udder drövver enschtälle. un dann moß <code lang="en">$wgResourceLoaderMaxQueryLength</code> en dä Dattei <code lang="en">LocalSettings.php</code> op däsälve Wäät jesaz wääde.', # Fuzzy
'config-db-type' => 'De Zoot Daatebangk:',
'config-db-host' => 'Dä Name vun däm Rääschner met dä Daatebangk:',
'config-db-host-help' => 'Wann Dinge ẞööver för de Daatebangk ob enem andere Rääschner es, donn heh dämm singe Name udder dämm sing <i lang="en">IP</i>-Addräß enjävve.
@@ -11226,7 +11576,7 @@ Wann dat Daatebangk_Süßteem, wat De nämme wells, onge nit dobei es, dann donn
'config-support-postgres' => '* <i lang="en">$1</i> es e bikannt Daatebangksüßteem met offe Quälltäxde, un en och en Wahl nävve <i lang="en">MySQL</i> ([http://www.php.net/manual/de/pgsql.installation.php Aanleidung för et Övversäze un Enreeschte von PHP met <i lang="en">PostgreSQL</i> dobei, op Deutsch]) Et sinn_er ävver paa klein Fählershe bekannt, um kunne dat em Momang för et reschtijje Werke nit emfähle.',
'config-support-sqlite' => '* <i lang="en">$1</i> es e eijfach Daatebangksüßteem, wat joot ongershtöz weed. ([http://www.php.net/manual/de/pdo.installation.php Aanleidong för et Övversäze un Enreeschte von PHP met <i lang="en">SQLite</i> dobei, op Deutsch])',
'config-support-oracle' => '* <i lang="en">$1</i> es e jeschäfflesch Daatebangksüßteem för Ferme. ([http://www.php.net/manual/de/oci8.installation.php Aanleidong för et Övversäze un Enreeschte von PHP met <i lang="en">OCI8</i> dobei, op Deutsch])',
- 'config-support-ibm_db2' => '* $1 es en Datebengk för et Jeschäff un fö Ongernehme.',
+ 'config-support-ibm_db2' => '* $1 es en Datebengk för et Jeschäff un fö Ongernehme.', # Fuzzy
'config-header-mysql' => 'De Enshtällunge för de <i lang="en">MySQL</i> Daatebangk',
'config-header-postgres' => 'De Enshtällunge för de <i lang="en">PostgreSQL</i> Daatebangk',
'config-header-sqlite' => 'De Enshtällunge för de <i lang="en">SQLite</i> Daatebangk',
@@ -11290,7 +11640,7 @@ Dat dom_mer ävver '''nit vörschlonn'''em Jääjedeil, ußer, wann et Probleme
Mer kann dat Wiki jäz [$1 bruche].',
'config-regenerate' => 'Donn de Dattei <code lang="en">LocalSettings.php</code> neu opsäze →',
- 'config-show-table-status' => 'Et Kommando <code lang="en">SHOW TABLE STATUS</code> aan de Daatebangk es donävve jejange!',
+ 'config-show-table-status' => 'Et Kommando <code lang="en"><code>SHOW TABLE STATUS</code></code> aan de Daatebangk es donävve jejange!',
'config-unknown-collation' => "'''Opjepaß:''' De Daatabangk deiht en onbikannte Reijefollsch bruche, för Booshtaabe un Zeishe ze verjliishe un ze zotteere.",
'config-db-web-account' => 'Dä Zohjang zor Daatebangk för et Wiki',
'config-db-web-help' => 'Donn ene Name un e Paßwoot för der Zohjang zor Daatebangk för et Wiki em nomaale Bedrief aanjävve.',
@@ -11363,7 +11713,7 @@ Do künnts jez der Räß vun de einzel Enshtellunge övverjonn, un et Wiki tirä
'config-optional-continue' => 'De wells noch mieh Frore jeshtallt krijje un noch mieh Enshtällunge maache?',
'config-optional-skip' => 'Nä, lohß dä Ömshtand, donn eifarr_et Wiki opsäze.',
'config-profile' => 'Enshtällunge för de Metmaacher ier Rääschte:',
- 'config-profile-wiki' => 'E tradizjonäll offe Wiki',
+ 'config-profile-wiki' => 'E tradizjonäll offe Wiki', # Fuzzy
'config-profile-no-anon' => 'Schriever möße enlogge',
'config-profile-fishbowl' => 'Bloß ußdröcklesch zohjelohße Schriever',
'config-profile-private' => 'E jeschloße Privat_Wiki',
@@ -11381,7 +11731,7 @@ Esu häß De de Wahl:
'''{{int:config-profile-private}}''' kann nur lässe, wäh en et Wiki zohjelohße es, un desellve Jropp kann uch schrieve.
-Noch ander un un opwändijere Enshtellunge för de Rääschte sin müjjelesch, wann et Wiki ens aam Loufe es. Loor Der doför de [//www.mediawiki.org/wiki/Manual:User_rights zopaß Hölp em Handbooch] aan.",
+Noch ander un un opwändijere Enshtellunge för de Rääschte sin müjjelesch, wann et Wiki ens aam Loufe es. Loor Der doför de [//www.mediawiki.org/wiki/Manual:User_rights zopaß Hölp em Handbooch] aan.", # Fuzzy
'config-license' => 'Urhävverrääsch un Lizänz:',
'config-license-none' => 'Kein Fooßreih övver de Lizänz',
'config-license-cc-by-sa' => '<i lang="en">Creative Commons</i> Der Name moß jenannt sin, et Wiggerjävve es zohjelohße onger dersellve Bedengunge',
@@ -11464,7 +11814,7 @@ Do kann se heh un jez aanschallde, ävver se künnte noch zohsäzlesch Enshtellu
Et sühd esu uß, wi wann De MediaWiki ald enshtalleet hätß, un wöhrs aam Versöhke, dat norr_ens ze donn.
Jang wigger op de näähßte Sigg.",
'config-install-begin' => 'Wann De op „{{int:config-continue}}“ klecks, jeiht de Enshtallazjuhn vum MediaWiki loßß.
-Wann De noch Änderonge maache wells, dann kleck op „{{int:config-back}}“.',
+Wann De noch Änderonge maache wells, dann kleck op „{{int:config-back}}“.', # Fuzzy
'config-install-step-done' => 'jedonn',
'config-install-step-failed' => 'donävve jejange',
'config-install-extensions' => 'Zohsazprojramme enjeschloße',
@@ -11531,7 +11881,7 @@ Wann De mem Ronger- un widder Huhlaade fäädesh bes, kanns De '''[\$2 en Ding W
Dat es och all op Änglesch:
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]', # Fuzzy
);
/** Kurdish (Latin script) (Kurdî (latînî)‎)
@@ -11571,6 +11921,7 @@ $messages['lad'] = array(
/** Luxembourgish (Lëtzebuergesch)
* @author Robby
+ * @author 아라
*/
$messages['lb'] = array(
'config-desc' => 'Den Installatiounsprogramm vu MediaWiki',
@@ -11578,12 +11929,12 @@ $messages['lb'] = array(
'config-information' => 'Informatioun',
'config-localsettings-upgrade' => "'''Opgepasst''': E Fichier <code>LocalSettings.php</code> gouf fonnt.
Är Software kann aktualiséiert ginn, setzt w.e.g. de Wäert vum <code>\$wgUpgradeKey</code> an d'Këscht.
-Dir fannt en am LocalSettings.php.",
+Dir fannt en am <code>LocalSettings.php</code>.",
'config-localsettings-key' => 'Aktualisatiounsschlëssel:',
'config-localsettings-badkey' => 'De Schlëssel deen Dir aginn hutt ass net korrekt',
- 'config-localsettings-incomplete' => 'De Fichier LocalSettings.php schéngt net komplett ze sinn.
+ 'config-localsettings-incomplete' => 'De Fichier <code>LocalSettings.php</code> schéngt net komplett ze sinn.
D\'Variabel $1 ass net definéiert.
-Ännert w.e.g. de Fichier LocalSettings.php esou datt déi Variabel definéiert ass a klickt op "Virufueren".',
+Ännert w.e.g. de Fichier <code>LocalSettings.php</code> esou datt déi Variabel definéiert ass a klickt op "{{int:Config-continue}}".',
'config-session-error' => 'Feeler beim Starte vun der Sessioun: $1',
'config-no-session' => "D'Donnéeë vun ärer Sessioun si verluergaangen!
Kuckt Är php.ini no a vergewëssert Iech datt <code>session.save_path</code> op adequate REpertoire agestallt ass.",
@@ -11676,7 +12027,7 @@ Wann et de Kont net gëtt, a wann den Installatiouns-Kont genuch Rechter huet, g
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
'config-type-ibm_db2' => 'IBM DB2',
- 'config-support-ibm_db2' => '* $1 ass eng kommerziell Firma fir Datebanken',
+ 'config-support-ibm_db2' => '* $1 ass eng kommerziell Firma fir Datebanken', # Fuzzy
'config-header-mysql' => 'MySQL-Astellungen',
'config-header-postgres' => 'PostgreSQL-Astellungen',
'config-header-sqlite' => 'SQLite-Astellungen',
@@ -11696,7 +12047,7 @@ E gëtt fir den Numm vum SQLite Date-Fichier benotzt.',
'config-upgrade-done-no-regenerate' => "D'Aktualisatioun ass ofgeschloss.
Dir kënnt elo [$1 ufänken Är Wiki ze benotzen]",
- 'config-regenerate' => 'LocalSettings.php regeneréieren →',
+ 'config-regenerate' => '<code>LocalSettings.php</code> regeneréieren →',
'config-db-web-account' => 'Datebankkont fir den Accès iwwer de Web',
'config-db-web-account-same' => 'Dee selwechte Kont wéi bei der Installatioun benotzen',
'config-db-web-create' => 'De Kont uleeë wann et e net scho gëtt',
@@ -11734,7 +12085,7 @@ Dir kënnt elo déi Astellungen déi nach iwwreg sinn iwwersprangen an d'Wiki el
'config-optional-continue' => 'Stellt mir méi Froen.',
'config-optional-skip' => "Ech hunn es genuch, installéier just d'Wiki.",
'config-profile' => 'Profil vun de Benotzerrechter:',
- 'config-profile-wiki' => 'Traditionell Wiki',
+ 'config-profile-wiki' => 'Traditionell Wiki', # Fuzzy
'config-profile-no-anon' => 'Uleeë vun engem Benotzerkont verlaangt',
'config-profile-fishbowl' => 'Nëmmen autoriséiert Editeuren',
'config-profile-private' => 'Privat Wiki',
@@ -11773,7 +12124,7 @@ Dir kënnt elo déi Astellungen déi nach iwwreg sinn iwwersprangen an d'Wiki el
'config-install-sysop' => 'Administrateur Benotzerkont gëtt ugeluecht',
'config-install-extension-tables' => "D'Tabelle fir déi aktivéiert Erweiderunge ginn ugeluecht",
'config-install-mainpage-failed' => "D'Haaptsäit konnt net dragesat ginn: $1",
- 'config-download-localsettings' => 'LocalSettings.php eroflueden',
+ 'config-download-localsettings' => '<code>LocalSettings.php</code> eroflueden',
'config-help' => 'Hëllef',
'config-nofile' => 'De Fichier "$1" gouf net fonnt. Gouf e geläscht?',
'mainpagetext' => "'''MediaWiki gouf installéiert.'''",
@@ -11782,7 +12133,7 @@ Dir kënnt elo déi Astellungen déi nach iwwreg sinn iwwersprangen an d'Wiki el
== Starthëllefen ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Hëllef bei der Konfiguratioun]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki-FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglëscht vun neie MediaWiki-Versiounen]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglëscht vun neie MediaWiki-Versiounen]", # Fuzzy
);
/** Lingua Franca Nova (Lingua Franca Nova)
@@ -11831,8 +12182,65 @@ $messages['lo'] = array(
);
/** Lithuanian (lietuvių)
+ * @author Eitvys200
*/
$messages['lt'] = array(
+ 'config-information' => 'Informacija',
+ 'config-your-language' => 'Jūsų kalba:',
+ 'config-wiki-language' => 'Viki kalba:',
+ 'config-back' => '← Atgal',
+ 'config-continue' => 'Toliau →',
+ 'config-page-language' => 'Kalba',
+ 'config-page-welcome' => 'Sveiki atvykę į MediaWiki!',
+ 'config-page-name' => 'Vardas',
+ 'config-page-options' => 'Parinktys',
+ 'config-page-install' => 'Įdiegti',
+ 'config-page-complete' => 'Baigta!',
+ 'config-page-restart' => 'Iš naujo paleiskite diegimą',
+ 'config-page-readme' => 'Perskaityk manę',
+ 'config-page-copying' => 'Kopijuojama',
+ 'config-page-upgradedoc' => 'Atnaujinama',
+ 'config-restart' => 'Taip, paleiskite jį iš naujo',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-type-ibm_db2' => 'IBM DB2',
+ 'config-header-mysql' => 'MySQL nustatymai',
+ 'config-header-postgres' => 'PostgreSQL nustatymai',
+ 'config-header-sqlite' => 'SQLite nustatymai',
+ 'config-header-oracle' => 'Oracle nustatymai',
+ 'config-header-ibm_db2' => 'IBM DB2 nustatymai',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-site-name' => 'Viki pavadinimas:',
+ 'config-site-name-blank' => 'Įveskite svetainės pavadinimą.',
+ 'config-project-namespace' => 'Projekto pavadinimas:',
+ 'config-ns-generic' => 'Projektas',
+ 'config-ns-site-name' => 'Toks pat kaip viki pavadinimas: $1',
+ 'config-admin-box' => 'Administratoriaus paskyra',
+ 'config-admin-name' => 'Jūsų vardas:',
+ 'config-admin-password' => 'Slaptažodis:',
+ 'config-admin-password-confirm' => 'Slaptažodis dar kartą:',
+ 'config-admin-name-blank' => 'Įveskite administratoriaus vartotojo vardą.',
+ 'config-admin-password-blank' => 'Įvesti administratoriaus paskyros slaptažodį.',
+ 'config-admin-password-same' => 'Slaptažodis turi būti ne toks pat, kaip vartotojo vardas.',
+ 'config-admin-password-mismatch' => 'Įvesti slaptažodžiai nesutampa.',
+ 'config-admin-email' => 'El. pašto adresas:',
+ 'config-optional-continue' => 'Paklausti daugiau klausimų.',
+ 'config-optional-skip' => 'Man jau nuobodu, tiesiog įdiekite viki.',
+ 'config-profile' => 'Vartotojo teisių paskyra:',
+ 'config-profile-wiki' => 'Tradicinė viki',
+ 'config-profile-private' => 'Privati viki',
+ 'config-license-pd' => 'Viešas Domenas',
+ 'config-email-settings' => 'El. pašto nustatymai',
+ 'config-upload-enable' => 'Įgalinti failų įkėlimus',
+ 'config-logo' => 'Logotipo URL:',
+ 'config-cc-again' => 'Pasirinkti dar kartą...',
+ 'config-extensions' => 'Plėtiniai',
+ 'config-install-step-done' => 'atlikta',
+ 'config-install-step-failed' => 'nepavyko',
+ 'config-install-schema' => 'Kuriama schema',
+ 'config-install-keys' => 'Generuojami slapti raktai',
+ 'config-help' => 'pagalba',
'mainpagetext' => "'''MediaWiki sėkmingai įdiegta.'''",
'mainpagedocfooter' => 'Informacijos apie wiki programinės įrangos naudojimą, ieškokite [//meta.wikimedia.org/wiki/Help:Contents žinyne].
@@ -11840,7 +12248,7 @@ $messages['lt'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Konfigūracijos nustatymų sąrašas]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki DUK]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki pranešimai paštu apie naujas versijas]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki pranešimai paštu apie naujas versijas]', # Fuzzy
);
/** Latvian (latviešu)
@@ -12002,6 +12410,7 @@ $messages['min'] = array(
/** Macedonian (македонски)
* @author Bjankuloski06
+ * @author 아라
*/
$messages['mk'] = array(
'config-desc' => 'Инсталатор на МедијаВики',
@@ -12009,19 +12418,19 @@ $messages['mk'] = array(
'config-information' => 'Информации',
'config-localsettings-upgrade' => 'Востановена е податотека <code>LocalSettings.php</code>.
За да ја надградите инсталцијава, внесете ја вредноста на <code>$wgUpgradeKey</code> во полето подолу.
-Тоа е го најдете во LocalSettings.php.',
- 'config-localsettings-cli-upgrade' => 'Утврдено е присуството на податотеката „LocalSettings.php“.
-За да ја надградите инсталацијата, пуштете ја „update.php“ наместо горенаведената.',
+Тоа е го најдете во <code>LocalSettings.php</code>.',
+ 'config-localsettings-cli-upgrade' => 'Утврдено е присуството на податотеката „<code>LocalSettings.php</code>“.
+За да ја надградите инсталацијата, пуштете ја „<code>update.php</code>“ наместо горенаведената.',
'config-localsettings-key' => 'Надградбен клуч:',
'config-localsettings-badkey' => 'Клучот што го наведовте е погрешен',
'config-upgrade-key-missing' => 'Востановена е постоечка инсталација на МедијаВики.
-За да ја надградите, вметнете го следниов ред на дното од вашата страница LocalSettings.php:
+За да ја надградите, вметнете го следниов ред на дното од вашата страница <code>LocalSettings.php</code>:
$1',
- 'config-localsettings-incomplete' => 'Постоечката страница LocalSettings.php е нецелосна.
+ 'config-localsettings-incomplete' => 'Постоечката страница <code>LocalSettings.php</code> е нецелосна.
Не е поставена променливата $1.
-Изменете ја страницата LocalSettings.php така што ќе ѝ зададете вредност на променливата, па стиснете на „Продолжи“.',
- 'config-localsettings-connection-error' => 'Се појави грешка при поврзувањето со базата користејќи ги поставките назначени во LocalSettings.php или AdminSettings.php. Исправете ги овие поставки и обидете се повторно.
+Изменете ја страницата <code>LocalSettings.php</code> така што ќе ѝ зададете вредност на променливата, па стиснете на „{{int:Config-continue}}“.',
+ 'config-localsettings-connection-error' => 'Се појави грешка при поврзувањето со базата користејќи ги поставките назначени во <code>LocalSettings.php</code> или <code>AdminSettings.php</code>. Исправете ги овие поставки и обидете се повторно.
$1',
'config-session-error' => 'Грешка при започнување на сесијата: $1',
@@ -12154,7 +12563,7 @@ $1
Надградете го на PHP 5.2.9 и libxml2 2.7.3 или нивни понови верзии! ПРЕКИНУВАМ ([//bugs.php.net/bug.php?id=45996 грешката е заведена во PHP]).',
'config-using531' => 'МедијаВики не може да се користи со PHP $1 поради грешка кај упатните параметри за <code>__call()</code>.
За да го решите проблемот, надградете го на PHP 5.3.2 или понова верзија, или пак користете го постариот PHP 5.3.0.',
- 'config-suhosin-max-value-length' => 'Suhosin е инсталиран и ја ограничува должината на параметарот GET на $1 bytes. Делот ResourceLoader на МедијаВики ќе ја заобиколува ова граница, но со тоа ќе се влоши делотворноста. Ако е воопшто можно, на suhosin.get.max_value_length треба да го наместите на 1024 или поевеќе во php.ini , и да му ја зададете истата вредност на $wgResourceLoaderMaxQueryLength во LocalSettings.php .',
+ 'config-suhosin-max-value-length' => 'Suhosin е инсталиран и ја ограничува должината на параметарот GET на $1 бајти. Делот ResourceLoader на МедијаВики ќе ја заобиколува ова граница, но со тоа ќе се влоши делотворноста. Ако е воопшто можно, на <code>suhosin.get.max_value_length</code> треба да го наместите на 1024 или повеќе во <code>php.ini</code>, и да му ја зададете истата вредност на <code>$wgResourceLoaderMaxQueryLength</code> во <code>LocalSettings.php</code>.',
'config-db-type' => 'Тип на база:',
'config-db-host' => 'Домаќин на базата:',
'config-db-host-help' => 'Ако вашата база е на друг опслужувач, тогаш тука внесете го името на домаќинот или IP-адресата.
@@ -12238,7 +12647,7 @@ $1
'config-support-postgres' => '* $1 е популарен систем на бази на податоци со отворен код кој претставува алтернатива на MySQL ([http://www.php.net/manual/en/pgsql.installation.php како да составите PHP со поддршка за PostgreSQL]). Може сè уште да има некои грешки. па затоа не се препорачува за употреба во производна средина.',
'config-support-sqlite' => '* $1 е лесен систем за бази на податоци кој е многу добро поддржан. ([http://www.php.net/manual/en/pdo.installation.php Како да составите PHP со поддршка за SQLite], користи PDO)',
'config-support-oracle' => '* $1 е база на податоци на комерцијално претпријатие. ([http://www.php.net/manual/en/oci8.installation.php Како да составите PHP со поддршка за OCI8])',
- 'config-support-ibm_db2' => '* $1 is комерцијална база на податоциза фирми.',
+ 'config-support-ibm_db2' => '* $1 е комерцијална база на податоциза фирми. ([http://www.php.net/manual/en/ibm-db2.installation.php Како да составите PHP со поддршка за IBM DB2])',
'config-header-mysql' => 'Нагодувања на MySQL',
'config-header-postgres' => 'Нагодувања на PostgreSQL',
'config-header-sqlite' => 'Нагодувања на SQLite',
@@ -12305,8 +12714,8 @@ chmod a+w $3</pre>',
'config-upgrade-done-no-regenerate' => 'Надградбата заврши.
Сега можете да [$1 почнете да го користите викито].',
- 'config-regenerate' => 'Пресоздај LocalSettings.php →',
- 'config-show-table-status' => 'Барањето SHOW TABLE STATUS не успеа!',
+ 'config-regenerate' => 'Пресоздај <code>LocalSettings.php</code> →',
+ 'config-show-table-status' => 'Барањето <code>SHOW TABLE STATUS</code> не успеа!',
'config-unknown-collation' => "'''Предупредување:''' Базата корисни непрепознаена упатна споредба.",
'config-db-web-account' => 'Сметка на базата за мрежен пристап',
'config-db-web-help' => 'Одберете корисничко име и лозинка што ќе ги користи мрежниот опслужувач за поврзување со опслужувачот на базта на податоци во текот на редовната работа со викито.',
@@ -12378,7 +12787,7 @@ chmod a+w $3</pre>',
'config-optional-continue' => 'Постави ми повеќе прашања.',
'config-optional-skip' => 'Веќе ми здосади, дај само инсталирај го викито.',
'config-profile' => 'Профил на кориснички права:',
- 'config-profile-wiki' => 'Традиционално вики',
+ 'config-profile-wiki' => 'Отворено вики',
'config-profile-no-anon' => 'Задолжително отворање сметка',
'config-profile-fishbowl' => 'Само овластени уредници',
'config-profile-private' => 'Приватно вики',
@@ -12388,7 +12797,7 @@ chmod a+w $3</pre>',
Многумина имаат најдено најразлични полезни примени за МедијаВики, но понекогаш не е лесно да убедите некого во предностите на вики-концептот.
Значи имате избор.
-'''{{int:config-profile-wiki}}''' — секој може да го уредува, дури и без најавување.
+'''{{int:config-profile-wiki}}''' — модел според кој секој може да уредува, дури и без најавување.
Ако имате вики со '''задолжително отворање на сметка''', тогаш добивате повеќе контрола, но ова може даги одврати спонтаните учесници.
'''{{int:config-profile-fishbowl}}''' — може да уредуваат само уредници што имаат добиено дозвола за тоа, но јавноста може да ги гледа страниците, вклучувајќи ја нивната историја.
@@ -12397,13 +12806,13 @@ chmod a+w $3</pre>',
По инсталацијата имате на избор и посложени кориснички права и поставки. Погледајте во [//www.mediawiki.org/wiki/Manual:User_rights прирачникот].",
'config-license' => 'Авторски права и лиценца:',
'config-license-none' => 'Без подножје за лиценца',
- 'config-license-cc-by-sa' => 'Creative Commons НаведиИзвор СподелиПодИстиУслови',
+ 'config-license-cc-by-sa' => 'Криејтив комонс НаведиИзвор СподелиПодИстиУслови',
'config-license-cc-by' => 'Криејтив комонс НаведиИзвор',
- 'config-license-cc-by-nc-sa' => 'Creative Commons НаведиИзвор-Некомерцијално-СподелиПодИстиУслови',
+ 'config-license-cc-by-nc-sa' => 'Криејтив комонс НаведиИзвор-Некомерцијално-СподелиПодИстиУслови',
'config-license-cc-0' => 'Криејтив комонс Нула (јавна сопственост)',
'config-license-gfdl' => 'ГНУ-ова лиценца за слободна документација 1.3 или понова',
'config-license-pd' => 'Јавна сопственост',
- 'config-license-cc-choose' => 'Одберете друга Creative Commons лиценца по ваш избор',
+ 'config-license-cc-choose' => 'Одберете друга лиценца на Криејтив комонс по ваш избор',
'config-license-help' => "Многу јавни викија ги ставаат сите придонеси под [http://freedomdefined.org/Definition слободна лиценца].
Со ова се создава атмосфера на општа сопственост и поттикнува долгорочно учество.
Ова не е неопходно за викија на поединечни физички или правни лица.
@@ -12451,10 +12860,10 @@ chmod a+w $3</pre>',
За да може ова да работи, МедијаВики бара пристап до интернет.
За повеќе информации за оваа функција и напатствија за нејзино поставување на вики (сите други освен Ризницата), коносултирајте го [//mediawiki.org/wiki/Manual:$wgForeignFileRepos прирачникот].',
- 'config-cc-error' => 'Изборникот на Creative Commons лиценца не даде резултати.
+ 'config-cc-error' => 'Изборникот на лиценци од Криејтив комонс не даде резултати.
Внесете го името на лиценцата рачно.',
'config-cc-again' => 'Одберете повторно...',
- 'config-cc-not-chosen' => 'Одберете ја саканата Creative Commons лиценца и кликнете на „продолжи“.',
+ 'config-cc-not-chosen' => 'Одберете ја саканата лиценца од Криејтив комонс и стиснете на „продолжи“.',
'config-advanced-settings' => 'Напредни нагодувања',
'config-cache-options' => 'Нагодувања за кеширање на објекти:',
'config-cache-help' => 'Кеширањето на објекти се користи за зголемување на брзината на МедијаВики со кеширање на често употребуваните податоци.
@@ -12479,7 +12888,7 @@ chmod a+w $3</pre>',
'config-install-alreadydone' => "'''Предупредување:''' Изгледа дека веќе го имате инсталирано МедијаВики и сега сакате да го инсталирате повторно.
Продолжете на следната страница.",
'config-install-begin' => 'Стискајќи на „{{int:config-continue}}“ ќе ја започнете инсталацијата на МедијаВики.
-Ако сакате да направите измени во досегашното, стиснете на „Назад“.',
+Ако сакате да направите измени во досегашното, стиснете на „{{int:config-back}}“.',
'config-install-step-done' => 'готово',
'config-install-step-failed' => 'не успеа',
'config-install-extensions' => 'Вклучувам додатоци',
@@ -12535,7 +12944,7 @@ $3
'''Напомена''': Ако ова не го направите сега, податотеката со поставки повеќе нема да биде на достапна.
Откога ќе завршите со тоа, можете да '''[$2 влезете на вашето вики]'''.",
- 'config-download-localsettings' => 'Преземи го LocalSettings.php',
+ 'config-download-localsettings' => 'Преземи го <code>LocalSettings.php</code>',
'config-help' => 'помош',
'config-nofile' => 'Податотеката „$1“ не е пронајдена. Да не е избришана?',
'mainpagetext' => "'''МедијаВики е успешно инсталиран.'''",
@@ -12544,7 +12953,8 @@ $3
==Од каде да почнете==
* [//meta.wikimedia.org/wiki/Manual:Configuration_settings Список на нагодувања]
* [//meta.wikimedia.org/wiki/Manual:FAQ ЧПП (често поставувани прашања) за МедијаВики].
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Поштенски список на МедијаВики за нови верзии]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Поштенски список на МедијаВики за нови верзии]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Локализирајте го МедијаВики на вашиот јазик]',
);
/** Malayalam (മലയാളം)
@@ -12614,7 +13024,7 @@ $1
'config-connection-error' => '$1.
താഴെ നൽകിയിരിക്കുന്ന ഹോസ്റ്റ്, ഉപയോക്തൃനാമം, രഹസ്യവാക്ക് എന്നിവ പരിശോധിച്ച് വീണ്ടും ശ്രമിക്കുക.',
- 'config-regenerate' => 'LocalSettings.php പുനഃസൃഷ്ടിക്കുക →',
+ 'config-regenerate' => '<code>LocalSettings.php</code> പുനഃസൃഷ്ടിക്കുക →',
'config-mysql-engine' => 'സ്റ്റോറേജ് എൻജിൻ:',
'config-site-name' => 'വിക്കിയുടെ പേര്:',
'config-site-name-help' => 'ഇത് ബ്രൗസറിന്റെ ടൈറ്റിൽ ബാറിലും മറ്റനേകം ഇടങ്ങളിലും പ്രദർശിപ്പിക്കപ്പെടും.',
@@ -12646,7 +13056,7 @@ $1
ബാക്കിയുള്ളവ അവഗണിച്ച് വിക്കി ഇൻസ്റ്റോൾ ചെയ്യാവുന്നതാണ്.',
'config-optional-continue' => 'കൂടുതൽ ചോദ്യങ്ങൾ ചോദിക്കൂ.',
'config-optional-skip' => 'എനിക്ക് മടുത്തു, ഒന്ന് ഇൻസ്റ്റോൾ ചെയ്ത് തീർക്ക്.',
- 'config-profile-wiki' => 'പരമ്പരാഗത വിക്കി',
+ 'config-profile-wiki' => 'പരമ്പരാഗത വിക്കി', # Fuzzy
'config-profile-no-anon' => 'അംഗത്വ സൃഷ്ടി ചെയ്യേണ്ടതുണ്ട്',
'config-profile-fishbowl' => 'അനുവാദമുള്ളവർ മാത്രം തിരുത്തുക',
'config-profile-private' => 'സ്വകാര്യ വിക്കി',
@@ -12700,7 +13110,7 @@ $3
== പ്രാരംഭസഹായികൾ ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings ക്രമീകരണങ്ങളുടെ പട്ടിക]
* [//www.mediawiki.org/wiki/Manual:FAQ മീഡിയവിക്കി പതിവുചോദ്യങ്ങൾ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce മീഡിയവിക്കി പ്രകാശന മെയിലിങ് ലിസ്റ്റ്]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce മീഡിയവിക്കി പ്രകാശന മെയിലിങ് ലിസ്റ്റ്]', # Fuzzy
);
/** Mongolian (монгол)
@@ -12732,8 +13142,19 @@ $messages['mr'] = array(
/** Malay (Bahasa Melayu)
* @author Anakmalaysia
+ * @author Pizza1016
*/
$messages['ms'] = array(
+ 'config-desc' => 'Pemasang MediaWiki',
+ 'config-title' => 'Pasangan MediaWiki $1',
+ 'config-information' => 'Maklumat',
+ 'config-localsettings-key' => 'Kunci naik taraf:',
+ 'config-localsettings-badkey' => 'Kunci yang anda berikan tidak betul.',
+ 'config-session-error' => 'Ralat ketika memulakan sesi: $1',
+ 'config-your-language' => 'Bahasa kamu:',
+ 'config-your-language-help' => 'Pilihkan bahasa untuk digunakan dalam proses pemasangan ini.',
+ 'config-wiki-language' => 'Bahasa wiki:',
+ 'config-wiki-language-help' => 'Pilih bahasa utama wiki yang bakal dicipta ini.',
'config-back' => '← Undur',
'config-continue' => 'Teruskan →',
'config-page-language' => 'Bahasa',
@@ -12744,6 +13165,13 @@ $messages['ms'] = array(
'config-page-name' => 'Nama',
'config-page-options' => 'Pilihan',
'config-page-install' => 'Pasang',
+ 'config-page-complete' => 'Selesai!',
+ 'config-page-restart' => 'Mulakan semula pemasangan',
+ 'config-page-readme' => 'Baca saya',
+ 'config-page-releasenotes' => 'Catatan keluaran',
+ 'config-page-copying' => 'Sedang menyalin',
+ 'config-page-upgradedoc' => 'Sedang menaik taraf',
+ 'config-page-existingwiki' => 'Wiki sedia ada',
'config-env-php' => 'PHP $1 dipasang.',
'config-env-php-toolow' => 'PHP $1 dipasang.
Bagaimanapun, MediaWiki memerlukan PHP $2 ke atas.',
@@ -12751,6 +13179,14 @@ Bagaimanapun, MediaWiki memerlukan PHP $2 ke atas.',
'config-unicode-using-intl' => '[http://pecl.php.net/intl Sambungan intl PECL] digunakan untuk penormalan Unicode.',
'config-db-charset' => 'Peranggu aksara pangkalan data',
'config-type-ibm_db2' => 'IBM DB2',
+ 'config-header-mysql' => 'Keutamaan MySQL',
+ 'config-header-postgres' => 'Keutamaan PostgreSQL',
+ 'config-header-sqlite' => 'Keutamaan SQLite',
+ 'config-header-oracle' => 'Keutamaan Oracle',
+ 'config-header-ibm_db2' => 'Keutamaan IBM DB2',
+ 'config-invalid-db-type' => 'Jenis pangkalan data tidak sah',
+ 'config-db-web-account-same' => 'Gunakan akaun yang sama seperti dalam pemasangan',
+ 'config-db-web-create' => 'Ciptakan akaun jika belum wujud',
'config-mysql-engine' => 'Enjin storan:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
@@ -12765,19 +13201,26 @@ Bagaimanapun, MediaWiki memerlukan PHP $2 ke atas.',
'config-ns-site-name' => 'Sama dengan nama wiki: $1',
'config-ns-other' => 'Lain-lain (nyatakan)',
'config-ns-other-default' => 'MyWiki',
+ 'config-admin-box' => 'Akaun penyelia',
+ 'config-admin-name' => 'Nama kamu:',
'config-admin-password' => 'Kata laluan:',
+ 'config-admin-password-confirm' => 'Kata laluan lagi:',
+ 'config-admin-password-mismatch' => 'Kata-kata laluan yang kamu berikan tidak sepadan.',
'config-admin-email' => 'Alamat e-mel:',
+ 'config-admin-error-bademail' => 'Kamu telah memberikan alamat e-mel yang tidak betul.',
+ 'config-optional-skip' => 'Saya sudah bosan, pasangkanlah wiki sahaja.',
'config-license' => 'Hak cipta dan lesen:',
'config-license-none' => 'Tiada pengaki lesen',
'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
'config-license-cc-by' => 'Creative Commons Attribution',
'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
'config-license-cc-0' => 'Creative Commons Zero (Domain Awam)',
- 'config-license-gfdl' => 'Lesen Dokumentasi Bebas GNU 1.3 ke atas',
+ 'config-license-gfdl' => 'Lesen Dokumentasi Bebas GNU 1.3 atau ke atas',
'config-license-pd' => 'Domain Awam',
'config-email-settings' => 'Tetapan e-mel',
'config-install-step-done' => 'siap',
'config-install-step-failed' => 'gagal',
+ 'config-install-user-alreadyexists' => 'Pengguna "$1" sudah wujud',
'config-help' => 'bantuan',
'mainpagetext' => "'''MediaWiki telah berjaya dipasang.'''",
'mainpagedocfooter' => 'Sila rujuk [//meta.wikimedia.org/wiki/Help:Contents Panduan Penggunaan] untuk maklumat mengenai penggunaan perisian wiki ini.
@@ -12786,13 +13229,23 @@ Bagaimanapun, MediaWiki memerlukan PHP $2 ke atas.',
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Senarai tetapan konfigurasi]
* [//www.mediawiki.org/wiki/Manual:FAQ Soalan Lazim MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Senarai mel bagi keluaran MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Senarai mel bagi keluaran MediaWiki]', # Fuzzy
);
/** Maltese (Malti)
* @author Chrisportelli
*/
$messages['mt'] = array(
+ 'config-title' => "Installazzjoni ta' MediaWiki $1",
+ 'config-information' => 'Informazzjoni',
+ 'config-localsettings-key' => 'Ċavetta tal-aġġornament:',
+ 'config-localsettings-badkey' => 'Iċ-ċavetta li tajt hija ħażina.',
+ 'config-your-language' => 'Il-lingwa tiegħek:',
+ 'config-your-language-help' => "Agħżel lingwa li tixtieq tuża' matul il-proċess ta' installazzjoni.",
+ 'config-wiki-language' => 'Lingwi tal-wiki:',
+ 'config-wiki-language-help' => 'Agħżel il-lingwa li l-wiki se tkun l-aktar użata fil-wiki.',
+ 'config-back' => '← Lura',
+ 'config-continue' => 'Kompli →',
'config-page-language' => 'Lingwa',
'config-page-welcome' => 'Merħba fuq MediaWiki!',
'config-page-dbconnect' => 'Aqbad mad-databażi',
@@ -12805,13 +13258,77 @@ $messages['mt'] = array(
'config-page-restart' => "Erġa' ibda l-installazzjoni",
'config-page-readme' => 'Aqrani',
'config-page-releasenotes' => 'Noti tal-verżjoni',
+ 'config-page-upgradedoc' => 'Aġġornament',
+ 'config-page-existingwiki' => 'Wiki eżistenti',
+ 'config-restart' => "Iva, erġa' ibda",
+ 'config-env-php' => 'PHP $1 huwa installat.',
+ 'config-db-wiki-settings' => 'Identifika din il-wiki',
+ 'config-db-name' => 'Isem tad-databażi:',
+ 'config-db-install-account' => 'Kont tal-utent għall-installazzjoni',
+ 'config-db-username' => 'Isem tal-utent tad-databażi:',
+ 'config-db-password' => 'Password tad-databażi:',
+ 'config-db-port' => 'Port tad-databażi:',
+ 'config-db-schema' => 'Skema għal MediaWiki:',
+ 'config-db-web-create' => 'Oħloq il-kont jekk għadu ma jeżistix',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-charset' => "Sett ta' karattri tad-databażi:",
+ 'config-mysql-binary' => 'Binarju',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-site-name' => 'Isem tal-wiki:',
+ 'config-site-name-help' => "Dan se jidher fil-barra tat-titlu tal-browżer u f'diversi postijiet oħra.",
+ 'config-site-name-blank' => 'Daħħal isem tas-sit.',
+ 'config-project-namespace' => 'Spazju tal-isem tal-proġett:',
+ 'config-ns-generic' => 'Proġett',
+ 'config-ns-site-name' => 'L-istess bħall-isem tal-wiki: $1',
+ 'config-ns-other' => 'Oħrajn (speċifika)',
+ 'config-ns-other-default' => 'MyWiki',
+ 'config-ns-invalid' => 'L-ispazju speċifikat "<nowiki>$1</nowiki>" huwa ħażin.
+Speċifika spazju tal-isem tal-proġett differenti.',
+ 'config-ns-conflict' => 'L-ispazju speċifikat "<nowiki>$1</nowiki>" joħloq kunflitt ma\' spazju tal-isem ieħor tal-MediaWiki.
+Speċifika spazju tal-isem tal-proġett differenti.',
+ 'config-admin-box' => 'Kont tal-amministratur',
+ 'config-admin-name' => 'Ismek:',
+ 'config-admin-password' => 'Password:',
+ 'config-admin-password-confirm' => "Erġa' daħħal il-password:",
+ 'config-admin-help' => 'Daħħal l-isem tal-utent preferit hawnhekk, per eżempju "Joe Borg".
+Dan huwa l-isem li se tuża\' kull darba li tidħol fil-wiki.',
+ 'config-admin-name-blank' => 'Daħħal isem tal-utent għall-amministratur.',
+ 'config-admin-name-invalid' => 'L-isem tal-utent speċifikat "<nowiki>$1</nowiki>" huwa ħażin.
+Speċifika isem tal-utent differenti.',
+ 'config-admin-password-blank' => 'Daħħal password għall-kont tal-amministratur.',
+ 'config-admin-password-same' => 'Il-password ma tistax tkun l-istess bħall-isem tal-utent.',
+ 'config-admin-password-mismatch' => 'Il-passwords li daħħalt ma jaqblux.',
+ 'config-admin-email' => 'Indirizz elettroniku:',
+ 'config-admin-error-bademail' => 'Daħħalt indirizz elettroniku ħażin.',
+ 'config-almost-done' => "Kważi lest!
+Jekk trid tista' taqbeż il-parti li jmiss tal-konfigurazzjoni u sempliċiment tinstalla l-wiki.",
+ 'config-optional-continue' => 'Staqsini aktar mistoqsijiet.',
+ 'config-optional-skip' => 'Xbajt diġà, installa l-wiki.',
+ 'config-profile-wiki' => 'Wiki tradizzjonali', # Fuzzy
+ 'config-profile-no-anon' => 'Huwa obbligatorju l-ħolqien tal-kont',
+ 'config-profile-fishbowl' => 'Edituri awtorizzati biss',
+ 'config-profile-private' => 'Wiki privata',
+ 'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
+ 'config-license-cc-by' => 'Creative Commons Attribution',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
+ 'config-license-cc-0' => 'Creative Commons Zero (dominju pubbliku)',
+ 'config-license-pd' => 'Dominju pubbliku',
+ 'config-license-cc-choose' => 'Agħżel waħda mil-liċenzji tal-Creative Commons',
+ 'config-upload-deleted' => 'Direttorju għall-fajls imħassra:',
+ 'config-upload-deleted-help' => "Agħżel direttorju fejn iżżomm fajls imħassra.
+Idealment, dan m'għandux ikun aċċessibbli mill-web.",
+ 'config-logo' => 'URL tal-logo:',
+ 'config-download-localsettings' => 'Niżżel <code>LocalSettings.php</code>',
+ 'config-help' => 'għajnuna',
+ 'config-nofile' => 'Il-fajl "$1" ma setax jinstab. Dan ġie mħassar?',
'mainpagetext' => "'''MediaWiki ġie installat b'suċċess.'''",
'mainpagedocfooter' => "Ikkonsulta l-[//meta.wikimedia.org/wiki/Help:Contents Gwida għall-utenti] sabiex tikseb iktar informazzjoni dwar kif tuża' s-softwer tal-wiki.
== Biex tibda ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista ta' preferenzi għall-konfigurazzjoni]
* [//www.mediawiki.org/wiki/Manual:FAQ Mistoqsijiet rikorrenti fuq il-MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Il-lista tal-posta tħabbar 'l MediaWiki]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Il-lista tal-posta tħabbar 'l MediaWiki]", # Fuzzy
);
/** Burmese (မြန်မာဘာသာ)
@@ -12865,6 +13382,7 @@ $messages['nan'] = array(
/** Norwegian Bokmål (norsk (bokmål)‎)
* @author Event
* @author Nghtwlkr
+ * @author 아라
*/
$messages['nb'] = array(
'config-desc' => 'Installasjonsprogrammet for MediaWiki',
@@ -12872,19 +13390,19 @@ $messages['nb'] = array(
'config-information' => 'Informasjon',
'config-localsettings-upgrade' => 'En <code>LocalSettings.php</code>-fil har blitt oppdaget.
For å oppgradere denne installasjonen, skriv inn verdien av <code>$wgUpgradeKey</code> i boksen nedenfor.
-Du finner denne i LocalSettings.php.',
- 'config-localsettings-cli-upgrade' => "Filen ''LocalSettings.php'' er funnet.
+Du finner denne i <code>LocalSettings.php</code>.',
+ 'config-localsettings-cli-upgrade' => "Filen ''<code>LocalSettings.php</code>'' er funnet.
For å oppgradere denne installasjonen, vennligst kjør ''update.php'' i stedet",
'config-localsettings-key' => 'Oppgraderingsnøkkel:',
'config-localsettings-badkey' => 'Nøkkelen du oppga er feil.',
'config-upgrade-key-missing' => "En eksisterende installasjon av MediaWiki er funnet.
-For å oppgradere denne installasjonen, vær vennlig å legge til følgende linje helt til slutt i din ''LocalSettings.php''-fil:
+For å oppgradere denne installasjonen, vær vennlig å legge til følgende linje helt til slutt i din ''<code>LocalSettings.php</code>''-fil:
$1",
- 'config-localsettings-incomplete' => "Den eksisterende ''LocalSettings.php'' ser ut til å være ufullstendig.
+ 'config-localsettings-incomplete' => "Den eksisterende ''<code>LocalSettings.php</code>'' ser ut til å være ufullstendig.
Variabelen $1 har ingen verdi.
-Vær vennlig å endre ''LocalSettings.php'' slik at variabelen får en verdi, og klikk ''Fortsett''.",
- 'config-localsettings-connection-error' => "Det ble funnet en feil ved tilknytning av databasen med innstillingene i ''LocalSettings.php'' eller ''AdminSettings.php''. Vær vennlig å rette opp disse innstillingene og prøv igjen.
+Vær vennlig å endre ''<code>LocalSettings.php</code>'' slik at variabelen får en verdi, og klikk ''{{int:Config-continue}}''.",
+ 'config-localsettings-connection-error' => "Det ble funnet en feil ved tilknytning av databasen med innstillingene i ''<code>LocalSettings.php</code>'' eller ''<code>AdminSettings.php</code>''. Vær vennlig å rette opp disse innstillingene og prøv igjen.
$1",
'config-session-error' => 'Feil under oppstart av økt: $1',
@@ -13017,7 +13535,7 @@ Installasjon abortert.',
'config-using531' => 'MediaWiki kan ikke brukes med PHP $1 på grunn av en feil med referanseparametere til <code>__call()</code>.
Oppgrader til PHP 5.3.2 eller høyere, eller nedgrader til PHP 5.3.0 for å løse dette.
Installasjonen avbrutt.',
- 'config-suhosin-max-value-length' => 'Suhosin er installert og begrenser GET-parameterlengder til $1 bytes. MediaWiki\'s ResourceLoader-komponent klarer å komme rundt denne begrensningen, med med redusert ytelse. På mulig bør du sette suhosin.get.max_value_length til minst 1024 i php.ini, og sette $wgResourceLoaderMaxQueryLength til samme verdi i LocalSettings.php.',
+ 'config-suhosin-max-value-length' => 'Suhosin er installert og begrenser GET-parameterlengder til $1 bytes. MediaWiki\'s ResourceLoader-komponent klarer å komme rundt denne begrensningen, med med redusert ytelse. På mulig bør du sette <code>suhosin.get.max_value_length</code> til minst 1024 i <code>php.ini</code>, og sette <code>$wgResourceLoaderMaxQueryLength</code> til samme verdi i LocalSettings.php.', # Fuzzy
'config-db-type' => 'Databasetype:',
'config-db-host' => 'Databasevert:',
'config-db-host-help' => 'Hvis databasen kjører på en annen tjenermaskin, skriv inn vertsnavnet eller IP-adressen her.
@@ -13102,7 +13620,7 @@ Hvis du ikke ser databasesystemet du prøver å bruke i listen nedenfor, følg i
'config-support-postgres' => '* $1 er et populært åpen kildekode-databasesystem som er et alternativ til MySQL ([http://www.php.net/manual/en/pgsql.installation.php hvordan kompilere PHP med PostgreSQL-støtte]). Det kan være noen små utestående feil og det anbefales ikke for bruk i et produksjonsmiljø.',
'config-support-sqlite' => '* $1 er et lettvekts-databasesystem som er veldig godt støttet. ([http://www.php.net/manual/en/pdo.installation.php hvordan kompilere PHP med SQLite-støtte], bruker PDO)',
'config-support-oracle' => '* $1 er en kommersiell bedriftsdatabase. ([http://www.php.net/manual/en/oci8.installation.php Hvordan kompilere PHP med OCI8-støtte])',
- 'config-support-ibm_db2' => '* $1 er en kommersiell bedriftsdatabase.',
+ 'config-support-ibm_db2' => '* $1 er en kommersiell bedriftsdatabase.', # Fuzzy
'config-header-mysql' => 'MySQL-innstillinger',
'config-header-postgres' => 'PostgreSQL-innstillinger',
'config-header-sqlite' => 'SQLite-innstillinger',
@@ -13169,8 +13687,8 @@ Dette er '''ikke anbefalt''' med mindre du har problemer med wikien din.",
'config-upgrade-done-no-regenerate' => 'Oppgradering fullført.
Du kan nå [$1 begynne å bruke wikien din].',
- 'config-regenerate' => 'Regenerer LocalSettings.php →',
- 'config-show-table-status' => 'SHOW TABLE STATUS etterspørselen mislyktes!',
+ 'config-regenerate' => 'Regenerer <code>LocalSettings.php</code> →',
+ 'config-show-table-status' => '<code>SHOW TABLE STATUS</code> etterspørselen mislyktes!',
'config-unknown-collation' => "'''Advarsel:''' Databasen bruker en ukjent sortering.",
'config-db-web-account' => 'Databasekonto for nettilgang',
'config-db-web-help' => 'Velg brukernavnet og passordet som nettjeneren skal bruke for å koble til databasetjeneren under ordinær drift av wikien.',
@@ -13234,7 +13752,7 @@ Du kan hoppe over de resterende konfigurasjonene og installere wikien nå.',
'config-optional-continue' => 'Spør meg flere spørsmål.',
'config-optional-skip' => 'Jeg er lei, bare installer wikien.',
'config-profile' => 'Brukerrettighetsprofil:',
- 'config-profile-wiki' => 'Tradisjonell wiki',
+ 'config-profile-wiki' => 'Tradisjonell wiki', # Fuzzy
'config-profile-no-anon' => 'Kontoopprettelse påkrevd',
'config-profile-fishbowl' => 'Kun autoriserte bidragsytere',
'config-profile-private' => 'Privat wiki',
@@ -13250,7 +13768,7 @@ En wiki med '''{{int:config-profile-no-anon}}''' tilbyr ekstra ansvarlighet, men
'''{{int:config-profile-fishbowl}}'''-scenariet tillater godkjente brukere å redigere, mens publikum kan se sider, og også historikken.
En '''{{int:config-profile-private}}''' tillater kun godkjente brukere å se sider, den samme gruppen som får lov til å redigere dem.
-Mer komplekse konfigurasjoner av brukerrettigheter er tilgjengelig etter installasjon, se det [//www.mediawiki.org/wiki/Manual:User_rights relevante manualavsnittet].",
+Mer komplekse konfigurasjoner av brukerrettigheter er tilgjengelig etter installasjon, se det [//www.mediawiki.org/wiki/Manual:User_rights relevante manualavsnittet].", # Fuzzy
'config-license' => 'Opphavsrett og lisens:',
'config-license-none' => 'Ingen lisensbunntekst',
'config-license-cc-by-sa' => 'Creative Commons Navngivelse Del på samme vilkår',
@@ -13311,7 +13829,7 @@ For mer informasjon om denne funksjonen, inklusive instruksjoner om hvordan man
'config-install-user-grant-failed' => 'Å gi tillatelse til brukeren «$1» mislyktes: $2',
'config-install-tables' => 'Oppretter tabeller',
'config-install-mainpage-failed' => 'Kunne ikke sette inn hovedside: $1',
- 'config-download-localsettings' => 'Last ned LocalSettings.php',
+ 'config-download-localsettings' => 'Last ned <code>LocalSettings.php</code>',
'config-help' => 'hjelp',
'config-nofile' => 'Filen "$1" ble ikke funnet. Kan den være blitt slettet?',
'mainpagetext' => "'''MediaWiki-programvaren er nå installert.'''",
@@ -13320,7 +13838,7 @@ For mer informasjon om denne funksjonen, inklusive instruksjoner om hvordan man
==Å starte==
*[//www.mediawiki.org/wiki/Manual:Configuration_settings Oppsettsliste]
*[//www.mediawiki.org/wiki/Manual:FAQ Ofte stilte spørsmål]
-*[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-postliste]',
+*[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-postliste]', # Fuzzy
);
/** Low German (Plattdüütsch)
@@ -13365,6 +13883,7 @@ $messages['ne'] = array(
* @author SPQRobin
* @author Siebrand
* @author Tjcool007
+ * @author 아라
*/
$messages['nl'] = array(
'config-desc' => 'Het installatieprogramma voor MediaWiki',
@@ -13372,19 +13891,19 @@ $messages['nl'] = array(
'config-information' => 'Gegevens',
'config-localsettings-upgrade' => 'Er is een bestaand instellingenbestand <code>LocalSettings.php</code> gevonden.
Voer de waarde van <code>$wgUpgradeKey</code> in in onderstaande invoerveld om deze installatie bij te werken.
-De instelling is terug te vinden in LocalSettings.php.',
- 'config-localsettings-cli-upgrade' => 'Het bestand LocalSettings.php is al aanwezig.
-Voer update.php uit om deze installatie bij te werken.',
+De instelling is terug te vinden in <code>LocalSettings.php</code>.',
+ 'config-localsettings-cli-upgrade' => 'Het bestand <code>LocalSettings.php</code> is al aanwezig.
+Voer <code>update.php</code> uit om deze installatie bij te werken.',
'config-localsettings-key' => 'Upgradesleutel:',
'config-localsettings-badkey' => 'De sleutel die u hebt opgegeven is onjuist',
'config-upgrade-key-missing' => 'Er is een bestaande installatie van MediaWiki aangetroffen.
-Plaats de volgende regel onderaan uw LocalSettings.php om deze installatie bij te werken:
+Plaats de volgende regel onderaan uw <code>LocalSettings.php</code> om deze installatie bij te werken:
$1',
- 'config-localsettings-incomplete' => 'De bestaande inhoud van LocalSettings.php lijkt incompleet.
+ 'config-localsettings-incomplete' => 'De bestaande inhoud van <code>LocalSettings.php</code> lijkt incompleet.
De variabele $1 is niet ingesteld.
-Wijzig LocalSettings.php zodat deze variabele is ingesteld en klik op "Doorgaan".',
- 'config-localsettings-connection-error' => 'Er is een fout opgetreden tijdens het verbinden van de database met de instellingen uit LocalSettings.php of AdminSettings.php. Los het probleem met de instellingen op en probeer het daarna opnieuw.
+Wijzig <code>LocalSettings.php</code> zodat deze variabele is ingesteld en klik op "{{int:Config-continue}}".',
+ 'config-localsettings-connection-error' => 'Er is een fout opgetreden tijdens het verbinden van de database met de instellingen uit <code>LocalSettings.php</code> of <code>AdminSettings.php</code>. Los het probleem met de instellingen op en probeer het daarna opnieuw.
$1',
'config-session-error' => 'Fout bij het begin van de sessie: $1',
@@ -13448,9 +13967,9 @@ U kunt MediaWiki niet installeren.',
MediaWiki heeft PHP $2 of hoger nodig om correct te kunnen werken.',
'config-unicode-using-utf8' => 'Voor Unicode-normalisatie wordt utf8_normalize.so van Brion Vibber gebruikt.',
'config-unicode-using-intl' => 'Voor Unicode-normalisatie wordt de [http://pecl.php.net/intl PECL-extensie intl] gebruikt.',
- 'config-unicode-pure-php-warning' => "'''Waarschuwing''': De [http://pecl.php.net/intl PECL-extensie intl] is niet beschikbaar om de Unicode-normalisatie af te handelen en daarom wordt de langzame PHP-implementatie gebruikt.
-Als u MediaWiki voor een website met veel verkeer installeert, lees u dan in over [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode-normalisatie].",
- 'config-unicode-update-warning' => "'''Waarschuwing''': De geïnstalleerde versie van de Unicode-normalisatiewrapper maakt gebruik van een oudere versie van [http://site.icu-project.org/ de bibliotheek van het ICU-project].
+ 'config-unicode-pure-php-warning' => "'''Waarschuwing''': de [http://pecl.php.net/intl PECL-extensie intl] is niet beschikbaar om de Unicodenormalisatie af te handelen en daarom wordt de langzame PHP-implementatie gebruikt.
+Als u MediaWiki voor een website met veel verkeer installeert, lees u dan in over [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicodenormalisatie].",
+ 'config-unicode-update-warning' => "'''Waarschuwing''': de geïnstalleerde versie van de Unicodenormalisatiewrapper maakt gebruik van een oudere versie van [http://site.icu-project.org/ de bibliotheek van het ICU-project].
U moet [//www.mediawiki.org/wiki/Unicode_normalization_considerations bijwerken] als Unicode voor u van belang is.",
'config-no-db' => 'Het was niet mogelijk een geschikte databasedriver te vinden voor PHP.
U moet een databasedriver installeren voor PHP.
@@ -13461,7 +13980,7 @@ Als u PHP zelf hebt gecompileerd, wijzig dan uw instellingen zodat een databased
Als u PHP hebt geïnstalleerd via een Debian- of Ubuntu-package, installeer dan ook de module php5-mysql.',
'config-outdated-sqlite' => "''' Waarschuwing:''' u gebruikt SQLite $1. SQLite is niet beschikbaar omdat de minimaal vereiste versie $2 is.",
'config-no-fts3' => "'''Waarschuwing''': SQLite is gecompileerd zonder de module [//sqlite.org/fts3.html FTS3]; er zijn geen zoekfuncties niet beschikbaar.",
- 'config-register-globals' => "'''Waarschuwing: De PHP-optie <code>[http://php.net/register_globals register_globals]</code> is ingeschakeld.'''
+ 'config-register-globals' => "'''Waarschuwing: de PHP-optie <code>[http://php.net/register_globals register_globals]</code> is ingeschakeld.'''
'''Schakel deze uit als dat mogelijk is.'''
MediaWiki kan ermee werken, maar uw server is dan meer kwetsbaar voor beveiligingslekken.",
'config-magic-quotes-runtime' => "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] is actief!'''
@@ -13494,7 +14013,7 @@ De installatie kan mislukken!",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache] is op dit moment geïnstalleerd',
'config-apc' => '[http://www.php.net/apc APC] is op dit moment geïnstalleerd',
'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] is op dit moment geïnstalleerd',
- 'config-no-cache' => "'''Waarschuwing:''' [http://www.php.net/apc APC] of [http://trac.lighttpd.net/ xcache / XCache] is niet aangetroffen.
+ 'config-no-cache' => "'''Waarschuwing:''' [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] of [http://www.iis.net/download/WinCacheForPhp WinCache] is niet aangetroffen.
Het cachen van objecten is niet ingeschakeld.",
'config-mod-security' => "'''Waarschuwing:''' uw webserver heeft de module [http://modsecurity.org/ mod_security] ingeschakeld. Als deze onjuist is ingesteld, kan dit problemen geven in combinatie met MediaWiki of andere software die gebruikers in staat stelt willekeurige inhoud te posten.
Lees de [http://modsecurity.org/documentation/ documentatie over mod_security] of neem contact op met de helpdesk van uw provider als u tegen problemen aanloopt.",
@@ -13508,17 +14027,20 @@ Het maken van miniaturen van afbeeldingen wordt uitgeschakeld.',
'config-no-uri' => "'''Fout:''' de huidige URI kon niet vastgesteld worden.
De installatie is afgebroken.",
'config-no-cli-uri' => "'''Waarschuwing:''' de parameter ==scriptpath is niet opgegeven. De standaardwaarde wordt gebruikt: <code>$1</code>.",
- 'config-using-server' => 'Servernaam "<nowiki>$1</nowiki>" wordt gebruikt.',
+ 'config-using-server' => 'De servernaam "<nowiki>$1</nowiki>" wordt gebruikt.',
'config-using-uri' => 'De server-URL "<nowiki>$1$2</nowiki>" wordt gebruikt.',
'config-uploads-not-safe' => "'''Waarschuwing:''' uw uploadmap <code>$1</code> kan gebruikt worden voor het arbitrair uitvoeren van scripts.
-Hoewel MediaWiki alle toegevoegde bestanden controleert op bedreigingen, is het zeer aan te bevelen het [//www.mediawiki.org/wiki/Manual:Security#Upload_security beveiligingslek te verhelpen] alvorens uploads in te schakelen.",
+Hoewel MediaWiki alle toegevoegde bestanden controleert op bedreigingen, is het zeer aan te bevelen het [//www.mediawiki.org/wiki/Manual:Security#Upload_security beveiligingslek te verhelpen] alvorens uploads in te schakelen.",
'config-no-cli-uploads-check' => "''Waarschuwing:'' uw standaardmap voor uploads (<code>$1</code>) wordt niet gecontroleerd op kwetsbaarheden voor het uitvoeren van willekeurige scripts gedurende de CLI-installatie.",
'config-brokenlibxml' => 'Uw systeem heeft een combinatie van PHP- en libxml2-versies geïnstalleerd die is foutgevoelig is en kan leiden tot onzichtbare beschadiging van gegevens in MediaWiki en andere webapplicaties.
-Upgrade naar PHP 5.2.9 of hoger en libxml2 2.7.3 of hoger! De installatie wordt afgebroken ([//bugs.php.net/bug.php?id=45996 bij PHP gerapporteerde fout]).',
+Upgrade naar PHP 5.2.9 of hoger en libxml2 2.7.3 of hoger([//bugs.php.net/bug.php?id=45996 bij PHP gerapporteerde fout]).
+De installatie wordt afgebroken.',
'config-using531' => 'PHP $1 is niet compatibel met MediaWiki vanwege een fout met betrekking tot referentieparameters met <code>__call()</code>.
Werk uw PHP bij naar PHP 5.3.2 of hoger of werk bij naar de lagere versie PHP 5.3.0 om dit op te lossen.
De installatie wordt afgebroken.',
- 'config-suhosin-max-value-length' => 'Suhosin is geïnstalleerd en beperkt de lengte van de GET-parameter tot $1 bytes. De ResourceLoader van MediaWiki omzeilt deze beperking, maar dat is slecht voor de prestaties. Als het mogelijk is, moet u de waarde "suhosin.get.max_value_length" in php.ini instellen op 1024 of hoger en $wgResourceLoaderMaxQueryLength in LocalSettings.php op dezelfde waarde instellen.',
+ 'config-suhosin-max-value-length' => 'Suhosin is geïnstalleerd en beperkt de GET-parameter <code>length</code> tot $1 bytes.
+De ResourceLoader van MediaWiki omzeilt deze beperking, maar dat is slecht voor de prestaties.
+Als het mogelijk is, moet u de waarde "<code>suhosin.get.max_value_length</code>" in <code>php.ini</code> instellen op 1024 of hoger en <code>$wgResourceLoaderMaxQueryLength</code> in LocalSettings.php op dezelfde waarde instellen.',
'config-db-type' => 'Databasetype:',
'config-db-host' => 'Databasehost:',
'config-db-host-help' => 'Als uw databaseserver een andere server is, voer dan de hostnaam of het IP-adres hier in.
@@ -13552,7 +14074,7 @@ Hoewel het wellicht mogelijk is gebruikers aan te maken zonder wachtwoord, is di
'config-db-install-help' => 'Voer de gebruikersnaam en het wachtwoord in die worden gebruikt voor de databaseverbinding tijdens het installatieproces.',
'config-db-account-lock' => 'Dezelfde gebruiker en wachwoord gebruiken na de installatie',
'config-db-wiki-account' => 'Gebruiker voor na de installatie',
- 'config-db-wiki-help' => 'Selecteer de gebruikersnaam en het wachtwoord die gebruikt worden om verbinding te maken met de database na de installatie.
+ 'config-db-wiki-help' => 'Voer de gebruikersnaam en het wachtwoord in die gebruikt worden om verbinding te maken met de database na de installatie.
Als de gebruiker niet bestaat en de gebruiker die tijdens de installatie gebruikt wordt voldoende rechten heeft, wordt deze gebruiker aangemaakt met de minimaal benodigde rechten voor het laten werken van de wiki.',
'config-db-prefix' => 'Databasetabelvoorvoegsel:',
'config-db-prefix-help' => "Als u een database moet gebruiken voor meerdere wiki's, of voor MediaWiki en een andere applicatie, dan kunt u ervoor kiezen om een voorvoegsel toe te voegen aan de tabelnamen om conflicten te voorkomen.
@@ -13579,12 +14101,12 @@ Wijzig het alleen als u weet dat dit nodig is.',
'config-sqlite-dir' => 'Gegevensmap voor SQLite:',
'config-sqlite-dir-help' => "SQLite slaat alle gegevens op in een enkel bestand.
-De map die u opgeeft moet schrijfbaar zijn voor de webserver tijdens de installatie.
+De map die u opgeeft moet beschrijfbaar zijn voor de webserver tijdens de installatie.
Deze mag '''niet toegankelijk''' zijn via het web en het bestand mag dus niet tussen de PHP-bestanden staan.
Het installatieprogramma schrijft het bestand <code>.htaccess</code> weg met het databasebestand, maar als dat niet werkt kan iemand zich toegang tot het ruwe databasebestand verschaffen.
-Ook de gebruikersgegevens (e-mailsadressen, wachtwoordhashes) en verwijderde versies en overige gegevens met beperkte toegang via MediaWiki zijn dan onbeschermd.
+Ook de gebruikersgegevens (e-mailadressen, wachtwoordhashes) en verwijderde versies en overige gegevens met beperkte toegang via MediaWiki zijn dan onbeschermd.
Overweeg om de database op een totaal andere plaats neer te zetten, bijvoorbeeld in <code>/var/lib/mediawiki/yourwiki</code>.",
'config-oracle-def-ts' => 'Standaard tablespace:',
@@ -13603,7 +14125,7 @@ Als u het databasesysteem dat u wilt gebruiken niet in de lijst terugvindt, volg
'config-support-postgres' => '* $1 is een populair open source databasesysteem als alternatief voor MySQL ([http://www.php.net/manual/en/pgsql.installation.php hoe PHP gecompileerd moet zijn met ondersteuning voor PostgreSQL]). Het is mogelijk dat er een aantal bekende problemen zijn met MediaWiki in combinatie met deze database en daarom wordt PostgreSQL niet aanbevolen voor een productieomgeving.',
'config-support-sqlite' => '* $1 is een zeer goed ondersteund lichtgewicht databasesysteem ([http://www.php.net/manual/en/pdo.installation.php hoe PHP gecompileerd zijn met ondersteuning voor SQLite]; gebruikt PDO)',
'config-support-oracle' => '* $1 is een commerciële data voor grote bedrijven ([http://www.php.net/manual/en/oci8.installation.php PHP compileren met ondersteuning voor OCI8]).',
- 'config-support-ibm_db2' => '* $1 is een commerciële enterprisedatabase.',
+ 'config-support-ibm_db2' => '* $1 is een commerciële enterprisedatabase. ([http://www.php.net/manual/en/ibm-db2.installation.php Hoe PHP compolieren met ondersteuning voor IBM DB2])',
'config-header-mysql' => 'MySQL-instellingen',
'config-header-postgres' => 'PostgreSQL-instellingen',
'config-header-sqlite' => 'SQLite-instellingen',
@@ -13621,7 +14143,7 @@ Gebruik alleen letters (a-z, A-Z), cijfers (0-9) en liggende streepjes (_) en st
Gebruik alleen letters (a-z, A-Z), cijfers (0-9) en liggende streepjes (_) en streepjes (-).',
'config-connection-error' => '$1.
-Controleer de host, gebruikersnaam en wachtwoord hieronder in en probeer het opnieuw.',
+Controleer de host, gebruikersnaam en wachtwoord en probeer het opnieuw.',
'config-invalid-schema' => 'Ongeldig schema voor MediaWiki "$1".
Gebruik alleen letters (a-z, A-Z), cijfers (0-9) en liggende streepjes (_).',
'config-db-sys-create-oracle' => 'Het installatieprogramma biedt alleen de mogelijkheid een nieuwe gebruiker aan te maken met de SYSDBA-gebruiker.',
@@ -13630,7 +14152,7 @@ Gebruik alleen letters (a-z, A-Z), cijfers (0-9) en liggende streepjes (_).',
U gebruikt $2.',
'config-sqlite-name-help' => 'Kies een naam die uw wiki identificeert.
Gebruik geen spaties of koppeltekens.
-Deze naam wordt gebruikt voor het gegevensbestands van SQLite.',
+Deze naam wordt gebruikt voor het gegevensbestand van SQLite.',
'config-sqlite-parent-unwritable-group' => 'Het was niet mogelijk de gegevensmap <code><nowiki>$1</nowiki></code> te maken omdat in de bovenliggende map <code><nowiki>$2</nowiki></code> niet geschreven mag worden door de webserver.
Het installatieprogramma heeft vast kunnen stellen onder welke gebruiker de webserver draait.
@@ -13650,14 +14172,14 @@ Voer op een Linux-systeem de volgende opdrachten uit:
<pre>cd $2
mkdir $3
chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Er is een fout opgetreden bij het aanmaken van de gegevensmap "$1".
+ 'config-sqlite-mkdir-error' => 'Er is een fout opgetreden tijdens het aanmaken van de gegevensmap "$1".
Controleer de locatie en probeer het opnieuw.',
'config-sqlite-dir-unwritable' => 'Het was niet mogelijk in de map "$1" te schrijven.
Wijzig de rechten zodat de webserver erin kan schrijven en probeer het opnieuw.',
'config-sqlite-connection-error' => '$1.
Controleer de map voor gegevens en de databasenaam hieronder en probeer het opnieuw.',
- 'config-sqlite-readonly' => 'Het bestand <code>$1</code> kan niet geschreven worden.',
+ 'config-sqlite-readonly' => 'Er kan niet naar bestand <code>$1</code> worden geschreven.',
'config-sqlite-cant-create-db' => 'Het was niet mogelijk het databasebestand <code>$1</code> aan te maken.',
'config-sqlite-fts3-downgrade' => 'PHP heeft geen ondersteuning voor FTS3.
De tabellen worden gedowngrade.',
@@ -13671,9 +14193,9 @@ Als u uw <code>LocalSettings.php</code> opnieuw wilt aanmaken, klik dan op de kn
Dit is '''niet aan te raden''' tenzij u problemen hebt met uw wiki.",
'config-upgrade-done-no-regenerate' => 'Het bijwerken is afgerond.
-U kunt u [$1 uw wiki gebruiken].',
- 'config-regenerate' => 'LocalSettings.php opnieuw aanmaken →',
- 'config-show-table-status' => 'Het uitvoeren van SHOW TABLE STATUS is mislukt!',
+U kunt nu [$1 uw wiki gebruiken].',
+ 'config-regenerate' => '<code>LocalSettings.php</code> opnieuw aanmaken →',
+ 'config-show-table-status' => 'Het uitvoeren van <code>SHOW TABLE STATUS</code> is mislukt!',
'config-unknown-collation' => "'''Waarschuwing:''' de database gebruikt een collatie die niet wordt herkend.",
'config-db-web-account' => 'Databasegebruiker voor webtoegang',
'config-db-web-help' => 'Selecteer de gebruikersnaam en het wachtwoord die de webserver gebruikt om verbinding te maken met de databaseserver na de installatie.',
@@ -13684,7 +14206,7 @@ De gebruiker die u hier opgeeft moet al bestaan.',
'config-mysql-engine' => 'Opslagmethode:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''Waarschuwing''': U hebt MyISAM geselecteerd als opslagengine voor MySQL. Dit is niet aan te raden voor MediaWiki omdat:
+ 'config-mysql-myisam-dep' => "'''Waarschuwing''': u hebt MyISAM geselecteerd als opslagengine voor MySQL. Dit is niet aan te raden voor MediaWiki omdat:
* het nauwelijks ondersteuning biedt voor gebruik door meerdere gebruikers tegelijkertijd door het locken van tabellen;
* het meer vatbaar is voor corruptie dan andere engines;
* de code van MediaWiki niet alstijd omgaat met MyISAM zoals dat zou moeten.
@@ -13699,7 +14221,7 @@ MyISAM-databases raken vaker corrupt dan InnoDB-databases.",
'config-mysql-binary' => 'Binair',
'config-mysql-utf8' => 'UTF-8',
'config-mysql-charset-help' => "In '''binaire modus''' slaat MediaWiki tekst in UTF-8 op in binaire databasevelden.
-Dit is efficiënter dan de UTF-8-modus van MySQL en stelt u in staat de volledige reeks Unicode-tekens te gebruiken.
+Dit is efficiënter dan de UTF-8-modus van MySQL en stelt u in staat de volledige reeks Unicodetekens te gebruiken.
In '''UTF-8-modus''' kent MySQL de tekenset van uw gegevens en kan de databaseserver ze juist weergeven en converteren.
Het is dat niet mogelijk tekens op te slaan die de \"[//nl.wikipedia.org/wiki/Lijst_van_Unicode-subbereiken#Basic_Multilingual_Plane Basic Multilingual Plane]\" te boven gaan.",
@@ -13740,13 +14262,13 @@ Kies een andere gebruikersnaam.',
'config-subscribe-help' => 'Dit is een mailinglijst met een laag volume voor aankondigingen van nieuwe versies, inclusief belangrijke aankondigingen met betrekking tot beveiliging.
Abonneer uzelf erop en werk uw MediaWiki-installatie bij als er nieuwe versies uitkomen.',
'config-subscribe-noemail' => 'U hebt geprobeerd zich te abonneren op de mailinglijst voor release-aankondigingen zonder een e-mailadres op te geven.
-Geef een e-mailadres op als u zich wil abonneren op de mailinglijst.',
+Geef een e-mailadres op als u zich wilt abonneren op de mailinglijst.',
'config-almost-done' => 'U bent bijna klaar!
Als u wilt kunt u de overige instellingen overslaan en de wiki nu installeren.',
'config-optional-continue' => 'Stel me meer vragen.',
'config-optional-skip' => 'Laat dat maar, installeer gewoon de wiki.',
'config-profile' => 'Gebruikersrechtenprofiel:',
- 'config-profile-wiki' => 'Traditionele wiki',
+ 'config-profile-wiki' => 'Open wiki',
'config-profile-no-anon' => 'Gebruiker aanmaken verplicht',
'config-profile-fishbowl' => 'Alleen voor geautoriseerde bewerkers',
'config-profile-private' => 'Privéwiki',
@@ -13756,7 +14278,7 @@ In MediaWiki is het eenvoudig om de recente wijzigingen te controleren en eventu
Daarnaast vinden velen MediaWiki goed inzetbaar in vele andere rollen, en soms is het niet handig om helemaal \"op de wikimanier\" te werken.
Daarom biedt dit installatieprogramma u de volgende keuzes voor de basisinstelling van gebruikersvrijheden:
-Een '''{{int:config-profile-wiki}}''' staat iedereen toe te bewerken, zonder zelfs aan te melden.
+Het profiel '''{{int:config-profile-wiki}}''' staat iedereen toe te bewerken, zonder zelfs aan te melden.
Een wiki met '''{{int:config-profile-no-anon}}\" biedt extra verantwoordelijkheid, maar kan afschrikken toevallige gebruikers afschrikken.
Het scenario '''{{int:config-profile-fishbowl}}''' laat gebruikers waarvoor dat is ingesteld bewerkt, maar andere gebruikers kunnen alleen pagina's bekijken, inclusief de bewerkingsgeschiedenis.
@@ -13783,25 +14305,25 @@ Dit is nog steeds een geldige licentie, maar deze licentie is lastig te begrijpe
Het is ook lastig inhoud te hergebruiken onder de GFDL.",
'config-email-settings' => 'E-mailinstellingen',
'config-enable-email' => 'Uitgaande e-mail inschakelen',
- 'config-enable-email-help' => "Als u wilt dat e-mailen mogelijk is, dan moeten [http://www.php.net/manual/en/mail.configuration.php PHP's e-mailinstellingen] correct zijn.
-Als u niet wilt dat e-mailen mogelijk is, dan kunt u de instellingen hier uitschakelen.",
+ 'config-enable-email-help' => 'Als u wilt dat e-mailen mogelijk is, dan moeten de [http://www.php.net/manual/en/mail.configuration.php e-mailinstellingen van PHP] correct zijn.
+Als u niet wilt dat e-mailen mogelijk is, dan kunt u de instellingen hier uitschakelen.',
'config-email-user' => 'E-mail tussen gebruikers inschakelen',
'config-email-user-help' => 'Gebruikers toestaan e-mail aan elkaar te verzenden als dit in de voorkeuren is ingesteld.',
- 'config-email-usertalk' => 'Gebruikersoverlegnotificatie inschakelen',
- 'config-email-usertalk-help' => 'Gebruikers toestaan notificaties te ontvangen bij wijzigingen op de eigen overlegpagina als dit in de voorkeuren is ingesteld',
- 'config-email-watchlist' => 'Volglijstnotificatie inschakelen',
- 'config-email-watchlist-help' => "Gebruikers toestaan notificaties te ontvangen bij wijzigingen van pagina's op hun volglijst als dit in de voorkeuren is ingesteld",
+ 'config-email-usertalk' => 'Gebruikersoverlegmeldingen inschakelen',
+ 'config-email-usertalk-help' => 'Gebruikers toestaan meldingen te ontvangen bij wijzigingen op de eigen overlegpagina, als dit in de voorkeuren is ingesteld.',
+ 'config-email-watchlist' => 'Volglijstmeldingen inschakelen',
+ 'config-email-watchlist-help' => "Gebruikers toestaan meldingen te ontvangen bij wijzigingen van pagina's op hun volglijst, als dit in de voorkeuren is ingesteld.",
'config-email-auth' => 'E-mailbevestiging inschakelen',
'config-email-auth-help' => "Als deze instelling actief is, moeten gebruikers hun e-mailadres bevestigen via een verwijziging die ze per e-mail wordt toegezonden.
Alleen bevestigde e-mailadressen kunnen e-mail ontvangen van andere gebruikers of wijzigingsnotificaties ontvangen.
-Het inschakelen van deze instelling is '''aan te raden''' voor openbare wiki's vanwege de mogelijkheden voor misbruik van e-mailmogelijkheden.",
+Het inschakelen van deze instelling wordt '''aangeraden''' voor openbare wiki's vanwege de mogelijkheden voor misbruik van e-mailmogelijkheden.",
'config-email-sender' => 'E-mailadres voor antwoorden:',
'config-email-sender-help' => 'Voer het e-mailadres in dat u wilt gebruiken als antwoordadres voor uitgaande e-mail.
Als een e-mail niet bezorgd kan worden, wordt dat op dit e-mailadres gemeld.
Veel mailservers vereisen dat tenminste het domein bestaat.',
'config-upload-settings' => 'Afbeeldingen en bestanden uploaden',
'config-upload-enable' => 'Uploaden van bestanden inschakelen',
- 'config-upload-help' => "Het uploaden van bestanden stelt uw server mogelijk bloot aan beveiligingsrisico's.
+ 'config-upload-help' => "Het toestaan van het uploaden van bestanden stelt uw server mogelijk bloot aan beveiligingsrisico's.
Er is meer [//www.mediawiki.org/wiki/Manual:Security informatie over beveiliging] beschikbaar in de handleiding.
Om het bestandsuploads mogelijk te maken kunt u de rechten op de submap <code>images</code> onder de hoofdmap van MediaWiki aanpassen, zodat de webserver erin kan schrijven.
@@ -13822,13 +14344,13 @@ Meer informatie over deze functie en hoe deze in te stellen voor andere wiki\'s
'config-cc-error' => 'De licentiekiezer van Creative Commons heeft geen resultaat opgeleverd.
Voer de licentie handmatig in.',
'config-cc-again' => 'Opnieuw kiezen...',
- 'config-cc-not-chosen' => 'Kies alstublieft de Creative Commons-licentie die u wilt gebruiken en klik op "doorgaan".',
+ 'config-cc-not-chosen' => 'Kies alstublieft de Creative Commonslicentie die u wilt gebruiken en klik op "doorgaan".',
'config-advanced-settings' => 'Gevorderde instellingen',
'config-cache-options' => 'Instellingen voor het cachen van objecten:',
'config-cache-help' => 'Het cachen van objecten wordt gebruikt om de snelheid van MediaWiki te verbeteren door vaak gebruikte gegevens te bewaren.
Middelgrote tot grote websites wordt geadviseerd dit in te schakelen en ook kleine sites merken de voordelen.',
'config-cache-none' => 'Niets cachen.
-Er gaat geen functionaliteit verloren, maar dit kan invloed hebben op de snelheid.',
+Er gaat geen functionaliteit verloren, maar dit kan invloed hebben op de prestaties.',
'config-cache-accel' => 'Cachen van objecten via PHP (APC, XCache of WinCache)',
'config-cache-memcached' => 'Memcached gebruiken (dit vereist aanvullende instellingen)',
'config-memcached-servers' => 'Memcachedservers:',
@@ -13849,9 +14371,9 @@ Mogelijk moet u aanvullende instellingen maken, maar u kunt deze uitbreidingen n
'config-install-alreadydone' => "'''Waarschuwing:''' het lijkt alsof u MediaWiki al hebt geïnstalleerd en probeert het programma opnieuw te installeren.
Ga alstublieft door naar de volgende pagina.",
'config-install-begin' => 'Als u nu op "{{int:config-continue}}" klikt, begint de installatie van MediaWiki.
-Als u nog wijzigingen wilt maken, klik dan op "Terug".',
- 'config-install-step-done' => 'Afgerond',
- 'config-install-step-failed' => 'Mislukt',
+Als u nog wijzigingen wilt maken, klik dan op "{{int:config-back}}".',
+ 'config-install-step-done' => 'afgerond',
+ 'config-install-step-failed' => 'mislukt',
'config-install-extensions' => 'Inclusief uitbreidingen',
'config-install-database' => 'Database inrichten',
'config-install-schema' => 'Het schema wordt aangemaakt',
@@ -13872,9 +14394,9 @@ MediaWiki vereist momenteel dat de tabellen van de webgebruiker zijn. Geef een a
'config-install-user-grant-failed' => 'Het geven van rechten aan gebruiker "$1" is mislukt: $2',
'config-install-user-missing' => 'De opgegeven gebruiker "$1" bestaat niet.',
'config-install-user-missing-create' => 'De opgegeven gebruiker "$1" bestaat niet.
-Klik op "registreren" onderaan als u het wilt aanmaken.',
+Klik op "registreren" onderaan als u de gebruiker wilt aanmaken.',
'config-install-tables' => 'Tabellen aanmaken',
- 'config-install-tables-exist' => "'''Waarschuwing''': de MediaWiki-tabellen lijken al te bestaan.
+ 'config-install-tables-exist' => "'''Waarschuwing''': de MediaWikitabellen lijken al te bestaan.
Het aanmaken wordt overgeslagen.",
'config-install-tables-failed' => "'''Fout''': het aanmaken van een tabel is mislukt met de volgende foutmelding: $1",
'config-install-interwiki' => 'Bezig met het vullen van de interwikitabel',
@@ -13899,14 +14421,14 @@ Dit bevat al uw instellingen.
U moet het bestand downloaden en in de hoofdmap van uw wiki-installatie plaatsten; in dezelfde map als index.php.
De download moet u automatisch zijn aangeboden.
-Als de download niet is aangeboden of als u de download hebt geannuleerd, dan kunt u de download opnieuw starten door op de onderstaande verwijzing te klikken:
+Als de download niet is aangeboden of als u de download hebt geannuleerd, dan kunt u de download opnieuw starten door op de onderstaande koppeling te klikken:
$3
'''Let op''': als u dit niet nu doet, dan het is bestand als u later de installatieprocedure afsluit zonder het bestand te downloaden niet meer beschikbaar.
Na het plaatsen van het bestand met instellingen kunt u '''[$2 uw wiki betreden]'''.",
- 'config-download-localsettings' => 'LocalSettings.php downloaden',
+ 'config-download-localsettings' => '<code>LocalSettings.php</code> downloaden',
'config-help' => 'hulp',
'config-nofile' => 'Het bestand "$1" is niet gevonden. Is het verwijderd?',
'mainpagetext' => "'''De installatie van MediaWiki is geslaagd.'''",
@@ -13916,7 +14438,254 @@ Na het plaatsen van het bestand met instellingen kunt u '''[$2 uw wiki betreden]
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lijst met instellingen]
* [//www.mediawiki.org/wiki/Manual:FAQ Veelgestelde vragen (FAQ)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglijst voor aankondigingen van nieuwe versies]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglijst voor aankondigingen van nieuwe versies]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Maak MediaWiki beschikbaar in uw taal]',
+);
+
+/** Nederlands (informeel)‎ (Nederlands (informeel)‎)
+ * @author Siebrand
+ */
+$messages['nl-informal'] = array(
+ 'config-localsettings-badkey' => 'De sleutel die je hebt opgegeven is onjuist',
+ 'config-upgrade-key-missing' => 'Er is een bestaande installatie van MediaWiki aangetroffen.
+Plaats de volgende regel onderaan je <code>LocalSettings.php</code> om deze installatie bij te werken:
+
+$1',
+ 'config-session-expired' => 'Je sessiegegevens zijn verlopen.
+Sessies zijn ingesteld om een levensduur van $1 te hebben.
+Je kunt deze wijzigen via de instelling <code>session.gc_maxlifetime</code> in php.ini.
+Begin het installatieproces opnieuw.',
+ 'config-no-session' => 'Je sessiegegevens zijn verloren gegaan.
+Controleer je php.ini en zorg dat er een juiste map is ingesteld voor <code>session.save_path</code>.',
+ 'config-your-language' => 'Jouw taal:',
+ 'config-help-restart' => 'Wil je alle opgeslagen gegevens die je hebt ingevoerd wissen en het installatieproces opnieuw starten?',
+ 'config-welcome' => '=== Controle omgeving ===
+Er worden een aantal basiscontroles uitgevoerd met als doel vast te stellen of deze omgeving geschikt is voor een installatie van MediaWiki.
+Als je hulp nodig hebt bij de installatie, lever deze gegevens dan ook aan.',
+ 'config-copyright' => "=== Auteursrechten en voorwaarden ===
+
+$1
+
+Dit programma is vrije software. Je mag het verder verspreiden en/of aanpassen in overeenstemming met de voorwaarden van de GNU General Public License zoals uitgegeven door de Free Software Foundation; ofwel versie 2 van de Licentie of - naar eigen keuze - enige latere versie.
+
+Dit programma wordt verspreid in de hoop dat het nuttig is, maar '''zonder enige garantie''', zelfs zonder de impliciete garantie van '''verkoopbaarheid''' of '''geschiktheid voor een bepaald doel'''.
+Zie de GNU General Public License voor meer informatie.
+
+Samen met dit programma hoor je een <doclink href=Copying>exemplaar van de GNU General Public License</doclink> ontvangen te hebben; zo niet, schrijf dan aan de Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, Verenigde Staten. Of [http://www.gnu.org/copyleft/gpl.html lees de licentie online].",
+ 'config-env-good' => 'De omgeving is gecontroleerd.
+Je kunt MediaWiki installeren.',
+ 'config-env-bad' => 'De omgeving is gecontroleerd.
+Je kunt MediaWiki niet installeren.',
+ 'config-unicode-pure-php-warning' => "'''Waarschuwing''': de [http://pecl.php.net/intl PECL-extensie intl] is niet beschikbaar om de Unicodenormalisatie af te handelen en daarom wordt de langzame PHP-implementatie gebruikt.
+Als je MediaWiki voor een website met veel verkeer installeert, lees je dan in over [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicodenormalisatie].",
+ 'config-unicode-update-warning' => "'''Waarschuwing''': de geïnstalleerde versie van de Unicodenormalisatiewrapper maakt gebruik van een oudere versie van [http://site.icu-project.org/ de bibliotheek van het ICU-project].
+Je moet [//www.mediawiki.org/wiki/Unicode_normalization_considerations bijwerken] als Unicode voor jou van belang is.",
+ 'config-no-db' => 'Het was niet mogelijk een geschikte databasedriver te vinden voor PHP.
+Je moet een databasedriver installeren voor PHP.
+De volgende databases worden ondersteund: $1.
+
+Als je op een gedeelde omgeving zit, vraag dan aan je hostingprovider een geschikte databasedriver te installeren.
+Als je PHP zelf hebt gecompileerd, wijzig dan je instellingen zodat een databasedriver wordt geactiveerd, bijvoorbeeld via <code>./configure --with-mysql</code>.
+Als je PHP hebt geïnstalleerd via een Debian- of Ubuntu-package, installeer dan ook de module php5-mysql.',
+ 'config-outdated-sqlite' => "''' Waarschuwing:''' je gebruikt SQLite $1. SQLite is niet beschikbaar omdat de minimaal vereiste versie $2 is.",
+ 'config-register-globals' => "'''Waarschuwing: de PHP-optie <code>[http://php.net/register_globals register_globals]</code> is ingeschakeld.'''
+'''Schakel deze uit als dat mogelijk is.'''
+MediaWiki kan ermee werken, maar je server is dan meer kwetsbaar voor beveiligingslekken.",
+ 'config-magic-quotes-runtime' => "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] is actief!'''
+Deze instelling zorgt voor gegevenscorruptie.
+Je kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
+ 'config-magic-quotes-sybase' => "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_sybase] is actief!'''
+Deze instelling zorgt voor gegevenscorruptie.
+Je kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
+ 'config-mbstring' => "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] is actief!'''
+Deze instelling zorgt voor gegevenscorruptie.
+Je kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
+ 'config-ze1' => "'''Onherstelbare fout: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] is actief!'''
+Deze instelling zorgt voor grote problemen in MediaWiki.
+Je kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
+ 'config-xml-bad' => 'De XML-module van PHP ontbreekt.
+MediaWiki heeft de functies van deze module nodig en werkt niet zonder deze module.
+Als je gebruik maakt van Mandrake, installeer dan het package php-xml.',
+ 'config-mod-security' => "'''Waarschuwing:''' je webserver heeft de module [http://modsecurity.org/ mod_security] ingeschakeld. Als deze onjuist is ingesteld, kan dit problemen geven in combinatie met MediaWiki of andere software die gebruikers in staat stelt willekeurige inhoud te posten.
+Lees de [http://modsecurity.org/documentation/ documentatie over mod_security] of neem contact op met de helpdesk van je provider als je tegen problemen aanloopt.",
+ 'config-imagemagick' => 'ImageMagick aangetroffen: <code>$1</code>.
+Het aanmaken van miniaturen van afbeeldingen wordt ingeschakeld als je uploaden inschakelt.',
+ 'config-gd' => 'Ingebouwde GD grafische bibliotheek aangetroffen.
+Het aanmaken van miniaturen van afbeeldingen wordt ingeschakeld als je uploaden inschakelt.',
+ 'config-uploads-not-safe' => "'''Waarschuwing:''' je uploadmap <code>$1</code> kan gebruikt worden voor het arbitrair uitvoeren van scripts.
+Hoewel MediaWiki alle toegevoegde bestanden controleert op bedreigingen, is het zeer aan te bevelen het [//www.mediawiki.org/wiki/Manual:Security#Upload_security beveiligingslek te verhelpen] alvorens uploads in te schakelen.",
+ 'config-no-cli-uploads-check' => "''Waarschuwing:'' je standaardmap voor uploads (<code>$1</code>) wordt niet gecontroleerd op kwetsbaarheden voor het uitvoeren van willekeurige scripts gedurende de CLI-installatie.",
+ 'config-brokenlibxml' => 'Je systeem heeft een combinatie van PHP- en libxml2-versies geïnstalleerd die is foutgevoelig is en kan leiden tot onzichtbare beschadiging van gegevens in MediaWiki en andere webapplicaties.
+Upgrade naar PHP 5.2.9 of hoger en libxml2 2.7.3 of hoger([//bugs.php.net/bug.php?id=45996 bij PHP gerapporteerde fout]).
+De installatie wordt afgebroken.',
+ 'config-db-host-help' => 'Als je databaseserver een andere server is, voer dan de hostnaam of het IP-adres hier in.
+
+Als je gebruik maakt van gedeelde webhosting, hoort je provider je de juiste hostnaam te hebben verstrekt.
+
+Als je MediaWiki op een Windowsserver installeert en MySQL gebruikt, dan werkt "localhost" mogelijk niet als servernaam.
+Als het inderdaad niet werkt, probeer dan "127.0.0.1" te gebruiken als lokaal IP-adres.
+
+Als je PostgreSQL gebruikt, laat dit veld dan leeg om via een Unix-socket te verbinden.',
+ 'config-db-host-oracle-help' => 'Voer een geldige [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name] in; een tnsnames.ora-bestand moet zichtbaar zijn voor deze installatie.<br />Als je gebruik maakt van clientlibraries 10g of een latere versie, kan je ook gebruik maken van de naamgevingsmethode [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
+ 'config-db-name-help' => 'Kies een naam die je wiki identificeert.
+Er mogen geen spaties gebruikt worden.
+Als je gebruik maakt van gedeelde webhosting, dan hoort je provider ofwel jou een te gebruiken databasenaam gegeven te hebben, of je aangegeven te hebben hoe je databases kunt aanmaken.',
+ 'config-db-account-oracle-warn' => 'Er zijn drie ondersteunde scenario\'s voor het installeren van Oracle als databasebackend:
+
+Als je een databasegebruiker wilt aanmaken als onderdeel van het installatieproces, geef dan de gegevens op van een databasegebruiker in met de rol SYSDBA voor de installatie en voer de gewenste aanmeldgegevens in voor de gebruiker met webtoegang. Je kunt ook de gebruiker met webtoegang handmatig aanmaken en alleen van die gebruiker de aanmeldgegevens opgeven als deze de vereiste rechten heeft om schemaobjecten aan te maken. Als laatste is het mogelijk om aanmeldgegevens van twee verschillende gebruikers op te geven; een met de rechten om schemaobjecten aan te maken, en een met alleen webtoegang.
+
+Een script voor het aanmaken van een gebruiker met de vereiste rechten is te vinden in de map "maintenance/oracle/" van deze installatie. Onthoud dat het gebruiken van een gebruiker met beperkte rechten alle mogelijkheden om beheerscripts uit te voeren met de standaard gebruiker onmogelijk maakt.',
+ 'config-db-prefix-help' => "Als je een database moet gebruiken voor meerdere wiki's, of voor MediaWiki en een andere applicatie, dan kan je ervoor kiezen om een voorvoegsel toe te voegen aan de tabelnamen om conflicten te voorkomen.
+Gebruik geen spaties.
+
+Dit veld wordt meestal leeg gelaten.",
+ 'config-charset-help' => "'''Waarschuwing:''' als je '''achterwaarts compatibel met UTF-8''' gebruikt met MySQL 4.1+ en een back-up van de database maakt met <code>mysqldump</code>, dan kunnen alle niet-ASCII-tekens in je back-ups onherstelbaar beschadigd raken.
+
+In '''binaire modus''' slaat MediaWiki tekst in UTF-8 op in binaire databasevelden.
+Dit is efficiënter dan de UTF-8-modus van MySQL en stelt je in staat de volledige reeks Unicode-tekens te gebruiken.
+In '''UTF-8-modus''' kent MySQL de tekenset van je gegevens en kan de databaseserver ze juist weergeven en converteren.
+Het is dan niet mogelijk tekens op te slaan die de \"[//nl.wikipedia.org/wiki/Lijst_van_Unicode-subbereiken#Basic_Multilingual_Plane Basic Multilingual Plane]\" te boven gaan.",
+ 'config-mysql-old' => 'Je moet MySQL $1 of later gebruiken.
+Jij gebruikt $2.',
+ 'config-db-schema-help' => 'Dit schema klopt meestal.
+Wijzig het alleen als je weet dat dit nodig is.',
+ 'config-sqlite-dir-help' => "SQLite slaat alle gegevens op in een enkel bestand.
+
+De map die je opgeeft moet beschrijfbaar zijn voor de webserver tijdens de installatie.
+
+Deze mag '''niet toegankelijk''' zijn via het web en het bestand mag dus niet tussen de PHP-bestanden staan.
+
+Het installatieprogramma schrijft het bestand <code>.htaccess</code> weg met het databasebestand, maar als dat niet werkt kan iemand zich toegang tot het ruwe databasebestand verschaffen.
+Ook de gebruikersgegevens (e-mailadressen, wachtwoordhashes) en verwijderde versies en overige gegevens met beperkte toegang via MediaWiki zijn dan onbeschermd.
+
+Overweeg om de database op een totaal andere plaats neer te zetten, bijvoorbeeld in <code>/var/lib/mediawiki/yourwiki</code>.",
+ 'config-support-info' => 'MediaWiki ondersteunt de volgende databasesystemen:
+
+$1
+
+Als je het databasesysteem dat je wilt gebruiken niet in de lijst terugvindt, volg dan de handleiding waarnaar hierboven wordt verwezen om ondersteuning toe te voegen.',
+ 'config-missing-db-name' => 'Je moet een waarde ingeven voor "Databasenaam"',
+ 'config-missing-db-host' => 'Je moet een waarde invoeren voor "Databaseserver"',
+ 'config-missing-db-server-oracle' => 'Je moet een waarde voor "Database-TNS" ingeven',
+ 'config-postgres-old' => 'PostgreSQL $1 of hoger is vereist.
+Jij gebruikt $2.',
+ 'config-sqlite-name-help' => 'Kies een naam die je wiki identificeert.
+Gebruik geen spaties of koppeltekens.
+Deze naam wordt gebruikt voor het gegevensbestand van SQLite.',
+ 'config-upgrade-done' => "Het bijwerken is afgerond.
+
+Je kunt [$1 je wiki nu gebruiken].
+
+Als je je <code>LocalSettings.php</code> opnieuw wilt aanmaken, klik dan op de knop hieronder.
+Dit is '''niet aan te raden''' tenzij je problemen hebt met je wiki.",
+ 'config-upgrade-done-no-regenerate' => 'Het bijwerken is afgerond.
+
+Je kunt nu [$1 je wiki gebruiken].',
+ 'config-db-web-no-create-privs' => 'De gebruiker die je hebt opgegeven voor de installatie heeft niet voldoende rechten om een gebruiker aan te maken.
+De gebruiker die je hier opgeeft moet al bestaan.',
+ 'config-mysql-myisam-dep' => "'''Waarschuwing''': je hebt MyISAM geselecteerd als opslagengine voor MySQL. Dit is niet aan te raden voor MediaWiki omdat:
+* het nauwelijks ondersteuning biedt voor gebruik door meerdere gebruikers tegelijkertijd door het locken van tabellen;
+* het meer vatbaar is voor corruptie dan andere engines;
+* de code van MediaWiki niet alstijd omgaat met MyISAM zoals dat zou moeten.
+
+Als je installatie van MySQL InnoDB ondersteunt, gebruik dat dan vooral.
+Als je installatie van MySQL geen ondersteuning heeft voor InnoDB, denk dan na over upgraden.",
+ 'config-mysql-charset-help' => "In '''binaire modus''' slaat MediaWiki tekst in UTF-8 op in binaire databasevelden.
+Dit is efficiënter dan de UTF-8-modus van MySQL en stelt je in staat de volledige reeks Unicodetekens te gebruiken.
+
+In '''UTF-8-modus''' kent MySQL de tekenset van je gegevens en kan de databaseserver ze juist weergeven en converteren.
+Het is dat niet mogelijk tekens op te slaan die de \"[//nl.wikipedia.org/wiki/Lijst_van_Unicode-subbereiken#Basic_Multilingual_Plane Basic Multilingual Plane]\" te boven gaan.",
+ 'config-ibm_db2-low-db-pagesize' => "Je DB2-database heeft een standaard tablespace met een onvoldoende grote pagesize. De pagesize moet tenminste '''32K''' zijn.",
+ 'config-project-namespace-help' => "In het kielzog van Wikipedia beheren veel wiki's hun beleidspagina's apart van hun inhoudelijke pagina's in een \"'''projectnaamruimte'''\".
+Alle paginanamen in deze naamruimte beginnen met een bepaald voorvoegsel dat je hier kunt aangeven.
+Dit voorvoegsel wordt meestal afgeleid van de naam van de wiki, maar het kan geen bijzondere tekens bevatten als \"#\" of \":\".",
+ 'config-admin-name' => 'Je naam:',
+ 'config-admin-password-mismatch' => 'De twee door jou ingevoerde wachtwoorden komen niet overeen.',
+ 'config-admin-email-help' => "Voer hier een e-mailadres in om e-mail te kunnen ontvangen van andere gebruikers op de wiki, je wachtwoord opnieuw in te kunnen stellen en op de hoogte te worden gehouden van wijzigingen van pagina's op uw volglijst. Je kunt het veld leeg laten.",
+ 'config-admin-error-bademail' => 'Je hebt een ongeldig e-mailadres opgegeven',
+ 'config-subscribe-help' => 'Dit is een mailinglijst met een laag volume voor aankondigingen van nieuwe versies, inclusief belangrijke aankondigingen met betrekking tot beveiliging.
+Abonneer jezelf erop en werk je MediaWiki-installatie bij als er nieuwe versies uitkomen.',
+ 'config-subscribe-noemail' => 'Je hebt geprobeerd je te abonneren op de mailinglijst voor release-aankondigingen zonder een e-mailadres op te geven.
+Geef een e-mailadres op als je je wilt abonneren op de mailinglijst.',
+ 'config-almost-done' => 'Je bent bijna klaar!
+Als je wilt kan je de overige instellingen overslaan en de wiki nu installeren.',
+ 'config-profile-help' => "Wiki's werken het beste als ze door zoveel mogelijk gebruikers worden bewerkt.
+In MediaWiki is het eenvoudig om de recente wijzigingen te controleren en eventuele foutieve of kwaadwillende bewerkingen terug te draaien.
+
+Daarnaast vinden velen MediaWiki goed inzetbaar in vele andere rollen, en soms is het niet handig om helemaal \"op de wikimanier\" te werken.
+Daarom biedt dit installatieprogramma je de volgende keuzes voor de basisinstelling van gebruikersvrijheden:
+
+Een '''{{int:config-profile-wiki}}''' staat iedereen toe te bewerken, zonder zelfs aan te melden.
+Een wiki met '''{{int:config-profile-no-anon}}\" biedt extra verantwoordelijkheid, maar kan afschrikken toevallige gebruikers afschrikken.
+
+Het scenario '''{{int:config-profile-fishbowl}}''' laat gebruikers waarvoor dat is ingesteld bewerkt, maar andere gebruikers kunnen alleen pagina's bekijken, inclusief de bewerkingsgeschiedenis.
+In een '''{{int:config-profile-private}}''' kunnen alleen goedgekeurde gebruikers pagina's bekijken en bewerken.
+
+Meer complexe instellingen voor gebruikersrechten zijn te maken na de installatie; hierover is meer te lezen in de [//www.mediawiki.org/wiki/Manual:User_rights handleiding].", # Fuzzy
+ 'config-license-help' => "In veel openbare wiki's zijn alle bijdragen beschikbaar onder een [http://freedomdefined.org/Definition vrije licentie].
+Dit helpt bij het creëren van een gevoel van gemeenschappelijk eigendom en stimuleert bijdragen op lange termijn.
+Dit is over het algemeen niet nodig is voor een particuliere of zakelijke wiki.
+
+Als je teksten uit Wikipedia wilt kunnen gebruiken en je wilt het mogelijk maken teksten uit je wiki naar Wikipedia te kopiëren, kies dan de licentie '''Creative Commons Naamsvermelding-Gelijk delen'''.
+
+De GNU Free Documentation License is de oude licentie voor inhoud uit Wikipedia.
+Dit is nog steeds een geldige licentie, maar deze licentie is lastig te begrijpen.
+Het is ook lastig inhoud te hergebruiken onder de GFDL.",
+ 'config-enable-email-help' => 'Als je wilt dat e-mailen mogelijk is, dan moeten de [http://www.php.net/manual/en/mail.configuration.php e-mailinstellingen van PHP] correct zijn.
+Als je niet wilt dat e-mailen mogelijk is, dan kan je de instellingen hier uitschakelen.',
+ 'config-upload-help' => "Het toestaan van het uploaden van bestanden stelt je server mogelijk bloot aan beveiligingsrisico's.
+Er is meer [//www.mediawiki.org/wiki/Manual:Security informatie over beveiliging] beschikbaar in de handleiding.
+
+Om het bestandsuploads mogelijk te maken kan je de rechten op de submap <code>images</code> onder de hoofdmap van MediaWiki aanpassen, zodat de webserver erin kan schrijven.
+Daarmee wordt deze functie ingeschakeld.",
+ 'config-logo-help' => 'Het standaarduiterlijk van MediaWiki bevat ruimte voor een logo van 135x160 pixels boven het menu.
+Upload een afbeelding met de juiste afmetingen en voer de URL hier in.
+
+Als je geen logo wilt gebruiken, kan je dit veld leeg laten.',
+ 'config-cc-not-chosen' => 'Kies alsjeblieft de Creative Commonslicentie die je wilt gebruiken en klik op "doorgaan".',
+ 'config-memcache-needservers' => 'Je hebt Memcached geselecteerd als je cache, maar je hebt geen servers opgegeven.',
+ 'config-memcache-badip' => 'Je hebt een ongeldig IP-adres ingevoerd voor Memcached: $1.',
+ 'config-memcache-noport' => 'Je hebt geen poort opgegeven voor de Memcachedserver: $1.
+De standaardpoort is 11211.',
+ 'config-extensions-help' => 'De bovenstaande uitbreidingen zijn aangetroffen in de map <code>./extensions</code>.
+
+Mogelijk moet je aanvullende instellingen maken, maar je kunt deze uitbreidingen nu inschakelen.',
+ 'config-install-alreadydone' => "'''Waarschuwing:''' het lijkt alsof je MediaWiki al hebt geïnstalleerd en probeert het programma opnieuw te installeren.
+Ga alsjeblieft door naar de volgende pagina.",
+ 'config-install-begin' => 'Als je nu op "{{int:config-continue}}" klikt, begint de installatie van MediaWiki.
+Als je nog wijzigingen wilt maken, klik dan op "Terug".', # Fuzzy
+ 'config-pg-no-plpgsql' => 'Je moet de taal PL/pgSQL installeren in de database $1',
+ 'config-pg-no-create-privs' => 'De gebruiker die je hebt opgegeven door de installatie heeft niet voldoende rechten om een gebruiker aan te maken.',
+ 'config-pg-not-in-role' => 'De gebruiker die je hebt opgegeven voor de webgebruiker bestaat al.
+De gebruiker die je hebt opgegeven voor installatie is geen superuser en geen lid van de rol van de webgebruiker, en kan het dus geen objecten aanmaken die van de webgebruiker zijn.
+
+MediaWiki vereist momenteel dat de tabellen van de webgebruiker zijn. Geef een andere webgebruikersnaam op, of klik op "terug" en geef een gebruiker op die voldoende installatierechten heeft.',
+ 'config-install-user-missing-create' => 'De opgegeven gebruiker "$1" bestaat niet.
+Klik op "registreren" onderaan als je de gebruiker wilt aanmaken.',
+ 'config-install-done' => "'''Gefeliciteerd!'''
+Je hebt MediaWiki met geïnstalleerd.
+
+Het installatieprogramma heeft het bestand <code>LocalSettings.php</code> aangemaakt.
+Dit bevat al je instellingen.
+
+Je moet het bestand downloaden en in de hoofdmap van uw wikiinstallatie plaatsten; in dezelfde map als index.php.
+De download moet je automatisch zijn aangeboden.
+
+Als de download niet is aangeboden of als je de download hebt geannuleerd, dan kan je de download opnieuw starten door op de onderstaande koppeling te klikken:
+
+$3
+
+'''Let op''': als je dit niet nu doet, dan het is bestand als u later de installatieprocedure afsluit zonder het bestand te downloaden niet meer beschikbaar.
+
+Na het plaatsen van het bestand met instellingen kan je '''[$2 je wiki betreden]'''.",
+ 'mainpagedocfooter' => 'Raadpleeg de [//meta.wikimedia.org/wiki/NL_Help:Inhoudsopgave handleiding] voor informatie over het gebruik van de wikisoftware.
+
+== Meer hulp over MediaWiki ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lijst met instellingen]
+* [//www.mediawiki.org/wiki/Manual:FAQ Veelgestelde vragen (FAQ)]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglijst voor aankondigingen van nieuwe versies]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Maak MediaWiki beschikbaar in jouw taal]',
);
/** Norwegian Nynorsk (norsk (nynorsk)‎)
@@ -14056,6 +14825,7 @@ $messages['pdc'] = array(
* @author Saper
* @author Sp5uhe
* @author Woytecr
+ * @author 아라
*/
$messages['pl'] = array(
'config-desc' => 'Instalator MediaWiki',
@@ -14063,19 +14833,19 @@ $messages['pl'] = array(
'config-information' => 'Informacja',
'config-localsettings-upgrade' => 'Plik <code>LocalSettings.php</code> istnieje.
Aby oprogramowanie zostało zaktualizowane musisz wstawić wartość <code>$wgUpgradeKey</code> w poniższe pole.
-Odnajdziesz ją w LocalSettings.php.',
- 'config-localsettings-cli-upgrade' => 'Wykryto obecność pliku LocalSettings.php.
-Aktualizację należy wykonać poprzez uruchomienie update.php',
+Odnajdziesz ją w <code>LocalSettings.php</code>.',
+ 'config-localsettings-cli-upgrade' => 'Wykryto obecność pliku <code>LocalSettings.php</code>.
+Aktualizację należy wykonać poprzez uruchomienie <code>update.php</code>',
'config-localsettings-key' => 'Klucz aktualizacji',
'config-localsettings-badkey' => 'Podany klucz jest nieprawidłowy',
'config-upgrade-key-missing' => 'Wykryto zainstalowane wcześniej MediaWiki.
-Jeśli chcesz je zaktualizować dodaj na koniec pliku LocalSettings.php poniższą linię tekstu.
+Jeśli chcesz je zaktualizować dodaj na koniec pliku <code>LocalSettings.php</code> poniższą linię tekstu.
$1',
- 'config-localsettings-incomplete' => 'Istniejący plik LocalSettings.php wygląda na niekompletny.
+ 'config-localsettings-incomplete' => 'Istniejący plik <code>LocalSettings.php</code> wygląda na niekompletny.
Brak wartości zmiennej $1.
-Zmień plik LocalSettings.php, tak by zawierał deklarację wartości tej zmiennej, a następnie kliknij „Dalej”.',
- 'config-localsettings-connection-error' => 'Wystąpił błąd podczas łączenia z bazą danych z wykorzystaniem danych z LocalSettings.php lub AdminSettings.php.
+Zmień plik <code>LocalSettings.php</code>, tak by zawierał deklarację wartości tej zmiennej, a następnie kliknij „{{int:Config-continue}}”.',
+ 'config-localsettings-connection-error' => 'Wystąpił błąd podczas łączenia z bazą danych z wykorzystaniem danych z <code>LocalSettings.php</code> lub <code>AdminSettings.php</code>.
Popraw ustawienia i spróbuj ponownie.
$1',
@@ -14206,7 +14976,7 @@ Instalacja została przerwana.',
'config-using531' => 'MediaWiki nie może być używane z PHP $1 z powodu błędu dotyczącego referencyjnych argumentów funkcji <code>__call()</code>.
Uaktualnij do PHP 5.3.2 lub nowszego. Możesz również cofnąć wersję do PHP 5.3.0, aby naprawić ten błąd.
Instalacja została przerwana.',
- 'config-suhosin-max-value-length' => 'Jest zainstalowany Suhosin i ogranicza długość parametru GET do $1 bajtów. Komponent ResourceLoader w MediaWiki wykona obejście tego ograniczenia, ale kosztem wydajności. Jeśli to możliwe należy ustawić suhosin.get.max_value_length na 1024 lub wyższej w php.ini oraz ustawić $wgResourceLoaderMaxQueryLength w LocalSettings.php na tę samą wartość.',
+ 'config-suhosin-max-value-length' => 'Jest zainstalowany Suhosin i ogranicza długość parametru GET do $1 bajtów. Komponent ResourceLoader w MediaWiki wykona obejście tego ograniczenia, ale kosztem wydajności. Jeśli to możliwe należy ustawić <code>suhosin.get.max_value_length</code> na 1024 lub wyższej w <code>php.ini</code> oraz ustawić <code>$wgResourceLoaderMaxQueryLength</code> w LocalSettings.php na tę samą wartość.', # Fuzzy
'config-db-type' => 'Typ bazy danych',
'config-db-host' => 'Adres serwera bazy danych',
'config-db-host-help' => 'Jeśli serwer bazy danych jest na innej maszynie, wprowadź jej nazwę domenową lub adres IP.
@@ -14288,7 +15058,7 @@ Poniżej wyświetlone są systemy baz danych gotowe do użycia. Jeżeli poniżej
'config-support-postgres' => '* $1 jest popularnym systemem baz danych, często stosowanym zamiast MySQL ([http://www.php.net/manual/en/pgsql.installation.php Zobacz, jak skompilować PHP ze wsparciem dla PostgreSQL]). Z powodu możliwości wystąpienia drobnych błędów, nie jest zalecana do wymagających wdrożeń.',
'config-support-sqlite' => '* $1 jest niewielkim systemem bazy danych, z którym MediaWiki bardzo dobrze współpracuje. ([http://www.php.net/manual/en/pdo.installation.php Jak skompilować PHP ze wsparciem dla SQLite], korzystając z PDO)',
'config-support-oracle' => '* $1 jest komercyjną profesjonalną bazą danych. ([http://www.php.net/manual/en/oci8.installation.php Jak skompilować PHP ze wsparciem dla OCI8])',
- 'config-support-ibm_db2' => '* $1 jest komercyjną zaawansowaną bazą danych.',
+ 'config-support-ibm_db2' => '* $1 jest komercyjną zaawansowaną bazą danych.', # Fuzzy
'config-header-mysql' => 'Ustawienia MySQL',
'config-header-postgres' => 'Ustawienia PostgreSQL',
'config-header-sqlite' => 'Ustawienia SQLite',
@@ -14355,8 +15125,8 @@ Jest to '''nie zalecane''', chyba że występują problemy z twoją wiki.",
'config-upgrade-done-no-regenerate' => 'Aktualizacja zakończona.
Możesz wreszcie [$1 zacząć korzystać ze swojej wiki].',
- 'config-regenerate' => 'Ponowne generowanie LocalSettings.php →',
- 'config-show-table-status' => 'Zapytanie „SHOW TABLE STATUS” nie powiodło się!',
+ 'config-regenerate' => 'Ponowne generowanie <code>LocalSettings.php</code> →',
+ 'config-show-table-status' => 'Zapytanie „<code>SHOW TABLE STATUS</code>” nie powiodło się!',
'config-unknown-collation' => "'''Uwaga''' – bazy danych używa nierozpoznanej metody porównywania.",
'config-db-web-account' => 'Konto bazy danych dla dostępu przez WWW',
'config-db-web-help' => 'Wybierz nazwę użytkownika i hasło, z których korzystać będzie serwer WWW do łączenia się z serwerem baz danych, podczas zwykłej pracy z wiki.',
@@ -14428,7 +15198,7 @@ Możesz pominąć pozostałe czynności konfiguracyjne i zainstalować wiki.',
'config-optional-continue' => 'Zadaj mi więcej pytań.',
'config-optional-skip' => 'Jestem już znudzony, po prostu zainstaluj wiki.',
'config-profile' => 'Profil uprawnień użytkowników',
- 'config-profile-wiki' => 'Tradycyjne wiki',
+ 'config-profile-wiki' => 'Tradycyjne wiki', # Fuzzy
'config-profile-no-anon' => 'Wymagane utworzenie konta',
'config-profile-fishbowl' => 'Wyłącznie zatwierdzeni edytorzy',
'config-profile-private' => 'Prywatna wiki',
@@ -14443,7 +15213,7 @@ Wiki z '''{{int:config-profile-no-anon}}''' zawiera dodatkowe funkcje rozliczani
Scenariusz '''{{int:config-profile-fishbowl}}''' umożliwia zatwierdzonym użytkownikom edycję, ale wyświetlanie stron jest powszechnie dostępne, włącznie z historią.
Ustawienie '''{{int:config-profile-private}}'' ' pozwala na wyświetlanie stron tylko zatwierdzonym użytkownikom, ta sama grupa może edytować.
-Bardziej skomplikowane konfiguracje uprawnień użytkowników są dostępne po zakończeniu instalacji, zobacz [//www.mediawiki.org/wiki/Manual:User_rights odpowiednią część podręcznika].",
+Bardziej skomplikowane konfiguracje uprawnień użytkowników są dostępne po zakończeniu instalacji, zobacz [//www.mediawiki.org/wiki/Manual:User_rights odpowiednią część podręcznika].", # Fuzzy
'config-license' => 'Prawa autorskie i licencja',
'config-license-none' => 'Brak stopki z licencją',
'config-license-cc-by-sa' => 'Creative Commons – za uznaniem autora, na tych samych zasadach',
@@ -14528,7 +15298,7 @@ Mogą one wymagać dodatkowych czynności konfiguracyjnych, ale można je teraz
'config-install-alreadydone' => "'''Uwaga''' – wydaje się, że MediaWiki jest już zainstalowane, a obecnie próbujesz zainstalować je ponownie.
Przejdź do następnej strony.",
'config-install-begin' => 'Po naciśnięciu "{{int:config-continue}}", rozpocznie się instalacji MediaWiki.
-Jeśli nadal chcesz dokonać zmian, naciśnij wstecz.',
+Jeśli nadal chcesz dokonać zmian, naciśnij wstecz.', # Fuzzy
'config-install-step-done' => 'gotowe',
'config-install-step-failed' => 'nieudane',
'config-install-extensions' => 'Włącznie z rozszerzeniami',
@@ -14583,7 +15353,7 @@ $3
'''Uwaga''': Jeśli tego nie zrobisz tego teraz, wygenerowany plik konfiguracyjny nie będzie już dostępny po zakończeniu instalacji.
Po załadowaniu pliku konfiguracyjnego możesz '''[ $2 wejść na wiki]'''.",
- 'config-download-localsettings' => 'Pobierz LocalSettings.php',
+ 'config-download-localsettings' => 'Pobierz <code>LocalSettings.php</code>',
'config-help' => 'pomoc',
'config-nofile' => 'Nie udało się odnaleźć pliku "$1". Czy nie został usunięty?',
'mainpagetext' => "'''Instalacja MediaWiki powiodła się.'''",
@@ -14592,13 +15362,14 @@ Po załadowaniu pliku konfiguracyjnego możesz '''[ $2 wejść na wiki]'''.",
== Na początek ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista ustawień konfiguracyjnych]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Komunikaty o nowych wersjach MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Komunikaty o nowych wersjach MediaWiki]', # Fuzzy
);
/** Piedmontese (Piemontèis)
* @author Borichèt
* @author Dragonòt
* @author Krinkle
+ * @author 아라
*/
$messages['pms'] = array(
'config-desc' => "L'instalador për mediaWiki",
@@ -14607,10 +15378,20 @@ $messages['pms'] = array(
'config-localsettings-upgrade' => "A l'é stàit trovà n'archivi <code>LocalSettings.php</code>.
Për agiorné cost'anstalassion, ch'a anserissa ël valor ëd <code>\$wgUpgradeKey</code> ant la casela sì-sota.
A la trovrà an LocalSetting.php.",
- 'config-localsettings-cli-upgrade' => "N'archivi LocalSettings.php a l'é stàit trovà.
-Për agiorné sta instalassion, për piasì fà anvece giré update.php",
+ 'config-localsettings-cli-upgrade' => "N'archivi <code>LocalSettings.php</code> a l'é stàit trovà.
+Për agiorné sta instalassion, për piasì fà anvece giré <code>update.php</code>",
'config-localsettings-key' => "Ciav d'agiornament:",
'config-localsettings-badkey' => "La ciav ch'it l'has dàit a l'é pa giusta.",
+ 'config-upgrade-key-missing' => "A l'é stàita trovà n'istalassion esistenta ëd MediaWiki.
+Për agiorné soa istalassion, për piasì ch'a buta la linia sì-sota al fond ëd sò <code>LocalSettings.php</code>:
+
+$1",
+ 'config-localsettings-incomplete' => "L'esistent <code>LocalSettings.php</code> a smija esse ancomplet.
+La variàbil $1 a l'é nen ampostà.
+Për piasì, ch'a modìfica <code>LocalSettings.php</code> ëd fasson che costa variàbil a sia ampostà, e ch'a sgnaca «{{int:Config-continue}}».",
+ 'config-localsettings-connection-error' => "A l'é ancapitaje n'eror an colegand-se a la base ëd dàit an dovrand j'ampostassion specificà an <code>LocalSettings.php</code> o <code>AdminSettings.php</code>. Për piasì, ch'a coregia cost'ampostassion e ch'a preuva torna.
+
+$1",
'config-session-error' => 'Eror an fasend parte la session: $1',
'config-session-expired' => "Ij sò dat ëd session a smijo scadù.
Le session a son configurà për na durà ëd $1.
@@ -14638,6 +15419,7 @@ Ch'a contròla sò php.ini e ch'as sigura che <code>session.save_path</code> a s
'config-page-releasenotes' => 'Nòte ëd publicassion',
'config-page-copying' => 'Copié',
'config-page-upgradedoc' => 'Agiorné',
+ 'config-page-existingwiki' => 'Wiki esistenta',
'config-help-restart' => "Veul-lo scancelé tùit ij dat salvà ch'a l'ha anserì e anandié torna ël process d'instalassion?",
'config-restart' => 'É!, felo torna parte',
'config-welcome' => "=== Contròj d'ambient ===
@@ -14666,13 +15448,21 @@ It peule instalé MediaWiki.",
'config-env-bad' => "L'ambient a l'é stàit controlà.
It peule pa instalé MediaWiki.",
'config-env-php' => "PHP $1 a l'é instalà.",
+ 'config-env-php-toolow' => "PHP $1 a l'é instalà.
+Ant tùit ij cas, MediaWiki a ciama PHP $2 o pi neuv.",
'config-unicode-using-utf8' => 'As deuvra utf8_normalize.so ëd Brion Vibber për la normalisassion Unicode.',
'config-unicode-using-intl' => "As deuvra l'[http://pecl.php.net/intl estension intl PECL] për la normalisassion Unicode.",
'config-unicode-pure-php-warning' => "'''Avis:''' L'[http://pecl.php.net/intl estension intl PECL] a l'é pa disponìbil për gestì la normalisassion Unicode, da già che l'implementassion an PHP pur a faliss për lentëssa.
S'a gestiss un sit a àut tràfich, a dovrìa lese cheicòs an sla [//www.mediawiki.org/wiki/Unicode_normalization_considerations normalisassion Unicode].",
'config-unicode-update-warning' => "'''Avis:''' La version instalà dlë spassiador ëd normalisassion Unicode a deuvra na version veja ëd la librarìa dël [http://site.icu-project.org/ proget ICU].
A dovrìa fé n'[//www.mediawiki.org/wiki/Unicode_normalization_considerations agiornament] s'a l'é anteressà a dovré Unicode.",
- 'config-no-db' => 'Impossìbil tové un pilòta ëd base ëd dàit bon!', # Fuzzy
+ 'config-no-db' => "Impossìbil trové un pilòta ëd base ëd dàit bon! A dev instalé un pilòta ëd base ëd dàit për PHP.
+Le sòrt ëd database ch'a ven-o a son apogià: $1.
+
+S'a l'é ansima a 'n servissi partagià, ch'a ciama a sò fornidor ëd servissi d'instalé un pilòta ëd base ëd dàit compatìbil.
+S'a l'é compilasse PHP chiel-midem, ch'a lo configura torna con un client ëd base ëd dàit abilità, për esempi an dovrand <code>./configure --with-mysql</code>.
+S'a l'ha instalà PHP dai pachèt Debian o Ubuntu, antlora a dev ëdcò istalé ël mòdul php5-mysql.",
+ 'config-outdated-sqlite' => "'''Avis''': chiel a l'ha SQLite $1, che a l'é pi vej che la version mìnima dont a-i é damanca $2. SQLite a sarà pa disponìbil.",
'config-no-fts3' => "'''Avis''': SQLite a l'é compilà sensa ël mòdul [//sqlite.org/fts3.html FTS3], le funsion d'arserca a saran pa disponìbij su cost motor.",
'config-register-globals' => "'''Avis: L'opsion <code>[http://php.net/register_globals register_globals]</code> ëd PHP a l'é abilità.'''
'''Ch'a la disabìlita s'a peul.'''
@@ -14696,14 +15486,19 @@ MediaWiki a l'ha da manca dle funsion an sto mòdul e a travajërà pa an costa
S'a fa giré mandrake, ch'a instala ël pachet php-xml.",
'config-pcre' => "A smija che ël mòdul d'apògg PCRE a sia mancant.
MediaWiki a l'ha da manca dle funsion d'espression regolar Perl-compatìbij për marcé.",
+ 'config-pcre-no-utf8' => "'''Fatal''': ël mòdul PCRE ëd PHP a smija esse compilà sensa l'apògg PCRE_UTF8.
+MediaWiki a ciama l'apògg d'UTF8 për marcé për da bin.",
'config-memory-raised' => "<code>memory_limit</code> ëd PHP a l'é $1, aussà a $2.",
'config-memory-bad' => "'''Avis:''' <code>memory_limit</code> ëd PHP a l'é $1.
Sossì a l'é probabilment tròp bass.
L'instalassion a peul falì!",
+ 'config-ctype' => "'''Fatal''': PHP a dev esse compilà con l'apògg për l'[http://www.php.net/manual/en/ctype.installation.php extension Ctype].",
'config-xcache' => "[http://xcache.lighttpd.net/ XCache] a l'é instalà",
'config-apc' => "[http://www.php.net/apc APC] a l'é instalà",
'config-wincache' => "[http://www.iis.net/download/WinCacheForPhp WinCache] a l'é instalà",
'config-no-cache' => "'''Avis:''' As treuva pa [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache]. Ël buté d'oget an memòria local a l'é pa abilità.",
+ 'config-mod-security' => "'''Avis''': Sò servent për l'aragnà a l'ha [http://modsecurity.org/ mod_security] abilità. Se mal configurà, a peul causé dij problema për MediaWiki o d'àutri programa ch'a përmëtto a j'utent dë spedì un contnù qualsëssìa.
+Ch'a fasa arferiment a la [http://modsecurity.org/documentation/ mod_security documentassion] o ch'a contata l'echip ëd sò servissi s'a-j rivo dj'eror casuaj.",
'config-diff3-bad' => 'GNU diff3 pa trovà.',
'config-imagemagick' => "Trovà ImageMagick: <code>$1</code>.
La miniaturisassion ëd figure a sarà abilità s'it abìlite le carie.",
@@ -14713,27 +15508,52 @@ La miniaturisassion ëd figure a sarà abilità s'a abìlita ij cariament.",
La miniaturisassion ëd figure a sarà disabilità.',
'config-no-uri' => "'''Eror:''' As peul pa determiné l'URI corenta.
Instalassion abortìa.",
+ 'config-no-cli-uri' => "'''Avis''': pa gnun --scriptpath specificà, a sarà dovrà ël predefinì: <code>$1</code>.",
+ 'config-using-server' => 'Utilisassion dël nòm ëd servent "<nowiki>$1</nowiki>".',
+ 'config-using-uri' => "Utilisassion ëd l'anliura ëd servent «<nowiki>$1$2</nowiki>».",
'config-uploads-not-safe' => "'''Avis:''' Sò dossié stàndard për carié <code>$1</code> a l'é vulneràbil a l'esecussion ëd qualsëssìa senari.
Bele che MediaWiki a contròla j'aspet ëd sicurëssa ëd tùit j'archivi carià, a l'é motobin arcomandà ëd [//www.mediawiki.org/wiki/Manual:Security#Upload_security saré ës përtus ëd sicurëssa] prima d'abilité ij cariament.",
+ 'config-no-cli-uploads-check' => "'''Avis:''' Toa cartela predefinìa për j-amportassion (<code>$1</code>) a l'é nen controlà a propòsit ëd la vulnerabilità
+d'esecussion ëd senari arbitrari durant l'istalassion CLI.",
+ 'config-brokenlibxml' => "Sò sistema a l'ha na combinassion ëd version PHP e libxml2 che a l'ha dij bigat e a peul provoché la corussion ëd dat ëstërmà an MediaWiki e d'àutre aplicassion për l'aragnà.
+Ch'a agiorna a PHP 5.2.9 o pi neuv e libxml2 2.7.3 o pi neuv ([//bugs.php.net/bug.php?id=45996 bug filed with PHP]).
+Istalassion abortìa.",
+ 'config-using531' => "MediaWiki a peul pa esse dovrà con PHP $1 a motiv d'un bigat ch'a ìmplica ij paràmetr d'arferiment a <code>__call()</code>.
+Ch'a agiorna a PHP 5.3.2 o pi neuv, o ch'a torna andré a PHP 5.3.0 për arzòlve ës problema.
+Istalassion abortìa.",
+ 'config-suhosin-max-value-length' => 'Suhosin a l\'é instalà e a lìmita la longheur dël paràmetr GET a $1 byte. Ël component ResourceLoader ëd MediaWiki a travajerà an rispetand ës lìmit, ma sòn a degraderà le prestassion. Se possìbil, a dovrìa amposté suhosin.get.max_value_lenght a 1024 o pi àut an <code>php.ini</code>, e amposté <code>$wgResourceLoaderMaxQueryLength</code> al midem valor an LocalSettings.php .', # Fuzzy
'config-db-type' => 'Sòrt ëd base ëd dàit:',
'config-db-host' => 'Ospitant ëd la base ëd dàit:',
- 'config-db-host-help' => "Se sò servent ëd base ëd dàit a l'é su un servent diferent, ch'a anseriss ambelessì ël nòm dl'ospitant o l'adrëssa IP.
+ 'config-db-host-help' => "Se sò servent ëd base ëd dàit a l'é su un servent diferent, ch'a anserissa ambelessì ël nòm dl'ospitant o l'adrëssa IP.
S'a deuvra n'ospitalità partagià, sò fornidor d'ospitalità a dovrìa deje ël nòm dl'ospitant giust ant soa documentassion.
-Se a anstala su un servent Windows e a deuvra MySQL, dovré \"localhost\" a podrìa funsioné nen com nòm dël servent. S'a marcia nen, ch'a preuva \"127.0.0.1\" com adrëssa IP local.", # Fuzzy
+Se a anstala su un servent Windows e a deuvra MySQL, dovré «localhost» a podrìa funsioné nen com nòm dël servent. S'a marcia nen, ch'a preuva «127.0.0.1» com adrëssa IP local.
+
+S'a deuvra PostgresSQL, ch'a lassa sto camp bianch për coleghesse a travers un socket UNIX.",
'config-db-host-oracle' => 'TNS dla base ëd dàit:',
'config-db-host-oracle-help' => "Anserì un [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nòm ëd conession local] bon; n'archivi tnsnames.ora a dev esse visìbil da costa anstalassion..<br />S'a deuvra le librarìe cliente 10g o pi neuve a peul ëdcò dovré ël métod ëd nominassion [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].",
'config-db-wiki-settings' => 'Identìfica sta wiki',
'config-db-name' => 'Nòm dla base ëd dàit:',
'config-db-name-help' => "Ch'a serna un nòm ch'a identìfica soa wiki.
-A dovrìa conten-e gnun ëspassi o tratin.
+A dovrìa conten-e gnun ëspassi.
-S'a deuvra n'ospitalità partagià, sò fornidor ëd l'ospitalità a-j darà un nòm ëd base ëd dàit specìfich da dovré, o a lassrà ch'a lo crea via un panel ëd contròl.", # Fuzzy
+S'a deuvra n'ospitalità partagià, sò fornidor ëd l'ospitalità a-j darà un nòm ëd base ëd dàit specìfich da dovré o a lassrà ch'a lo crea via un panel ëd contròl.",
'config-db-name-oracle' => 'Schema dla base ëd dàit:',
+ 'config-db-account-oracle-warn' => "A-i é tre possibilità mantnùe për istalé Oracle tanme terminal ëd base ëd dàit:
+
+S'a veul creé un cont ëd base ëd dàit com part dël process d'istalassion, për piasì ch'a fornissa un cont con ël ròl SYSDBA com cont ëd base ëd dàit për l'istalassion e ch'a specìfica le credensiaj vorsùe për ël cont d'acess an sl'aragnà, dësnò a peul ëdcò creé ël cont d'acess an sl'aragnà manualment e mach fornì col cont (se a l'ha ij përmess necessari për creé j'oget dë schema) o fornì doi cont diferent, un con ij privilegi ëd creé e un limità për l'acess an sla Ragnà.
+
+Ij senari për creé un cont con ij privilegi necessari a peul esse trovà ant la cartela «manutension/oracol/» ëd costa istalassion. Ch'a ten-a da ment che dovrand un cont limità a disabiliterà tute le funsion ëd manutension con ël cont predefinì.",
'config-db-install-account' => "Cont d'utent për l'instalassion.",
'config-db-username' => "Nòm d'utent dla base ëd dàit:",
'config-db-password' => 'Ciav dla base ëd dàit:',
+ 'config-db-password-empty' => "Për piasì, ch'a anserissa na ciav për ël neuv utent ëd base ëd dàit: $1.
+Con tut ch'a sia possìbil creé d'utent sensa ciav, a l'é pa na ròba sigura.",
+ 'config-db-install-username' => "Ch'a nserissa lë stranòm che a sarà dovrà për coleghesse a la base ëd dàit durant ël process d'istalassion.
+Cost-sì a l'é nen lë stranòm dël cont MediaWiki; a l'é lë stranòm për soa base ëd dàit.",
+ 'config-db-install-password' => "Ch'a anserissa la ciav che a sarà dovrà për coleghesse a la base ëd dàit durant ël process d'istalassion.
+Costa-sì a l'é nen la ciav dël cont MediaWiki; a l'é la ciav për soa base ëd dàit.",
'config-db-install-help' => "Ch'a anserissa lë stranòm d'utent e la ciav che a saran dovrà për coleghesse a la base ëd dàit durant ël process d'instalassion.",
'config-db-account-lock' => "Dovré ij midem stranòm d'utent e ciav durant j'operassion normaj",
'config-db-wiki-account' => "Cont d'utent për j'operassion normaj",
@@ -14741,9 +15561,9 @@ S'a deuvra n'ospitalità partagià, sò fornidor ëd l'ospitalità a-j darà un
S'ël cont a esist pa, e ël cont d'instalassion a l'ha ij privilegi ch'a-i van, sto cont utent a sarà creà con ij privilegi mìnin për fé marcé la wiki.",
'config-db-prefix' => 'Prefiss dle tàule dla base ëd dàit:',
'config-db-prefix-help' => "S'a l'ha dabzògn ëd partagé na base ëd dàit an tra vàire wiki, o tra MediaWiki e n'àutra aplicassion dl'aragnà, a peul serne ëd gionté un prefiss a tùit ij nòm ëd le tàule për evité ëd conflit.
-Ch'a deuvra ni dë spassi ni ëd tratin.
+Ch'a deuvra pa dë spassi.
-Cost camp a l'é lassà normalment veuid.", # Fuzzy
+Cost camp a l'é lassà normalment veuid.",
'config-db-charset' => 'Ansema dij caràter dla base ëd dàit',
'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binari',
'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
@@ -14756,8 +15576,9 @@ An '''manera UTF-8''', MySQL a arconòss an che ansema ëd caràter a son ij sò
'config-mysql-old' => "A-i é da manca ëd MySQL $1 o pi recent, chiel a l'ha $2.",
'config-db-port' => 'Porta dla base ëd dàit:',
'config-db-schema' => 'Schema për MediaWiki',
- 'config-db-schema-help' => "Jë schema sì-dzora a son normalment giust.
-Ch'a-j cangia mach s'a sa ch'a n'ha da manca.", # Fuzzy
+ 'config-db-schema-help' => "Lë schema sì-sota a l'é ëd sòlit giust.
+Ch'a lo cangia mach s'a sa ch'a n'ha da manca.",
+ 'config-pg-test-error' => "Impossìbil coleghesse a la base ëd dàit '''$1'''; $2",
'config-sqlite-dir' => 'Dossié dij dat SQLite:',
'config-sqlite-dir-help' => "SQLite a memorisa tùit ij dat ant n'archivi ùnich.
@@ -14771,33 +15592,39 @@ Lòn a comprend ij dat brut ëd l'utent (adrëssa ëd pòsta eletrònica, ciav t
Ch'a consìdera ëd buté la base ëd dàit tuta antrega da n'àutra part, për esempi an <code>/var/lib/mediawiki/yourwiki</code>.",
'config-oracle-def-ts' => 'Spassi dla tàula dë stàndard:',
'config-oracle-temp-ts' => 'Spassi dla tàula temporani:',
+ 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => "MediaWiki a manten ij sistema ëd base ëd dàit sì-dapress:
$1
S'a vëd pa listà sì-sota ël sistema ëd base ëd dàit ch'a preuva a dovré, antlora va andaré a j'istrussion dl'anliura sì-dzora për abilité ël manteniment.",
'config-support-mysql' => "* $1 e l'é l'obietiv primari për MediaWiki e a l'é mej mantnù ([http://www.php.net/manual/en/mysql.installation.php com compilé PHP con ël manteniment MySQL])",
- 'config-support-postgres' => "* $1 e l'é un sistema ëd base ëd dàit popolar a sorgiss duverta com alternativa a MySQL ([http://www.php.net/manual/en/pgsql.installation.php com compilé PHP con ël manteniment ëd PostgreSQL])", # Fuzzy
+ 'config-support-postgres' => "* $1 e l'é un sistema ëd base ëd dàit popolar a sorgiss duverta com alternativa a MySQL ([http://www.php.net/manual/en/pgsql.installation.php com compilé PHP con ël manteniment ëd PostgreSQL]). A peulo ess-ie chèich cit bigat, e a l'é nen arcomandà ëd dovrelo an n'ambient ëd produssion.",
'config-support-sqlite' => "* $1 e l'é un sistema ëd base ëd dàit leger che a l'é motobin bin mantnù ([http://www.php.net/manual/en/pdo.installation.php com compilé PHP con ël manteniment ëd SQLite], a deuvra PDO)",
'config-support-oracle' => "* $1 a l'é na base ëd dàit comersial për j'amprèise. ([http://www.php.net/manual/en/oci8.installation.php Com compilé PHP con ël manteniment OCI8])",
+ 'config-support-ibm_db2' => "* $1 a l'é na base ëd dàit d'asiendal comersial.", # Fuzzy
'config-header-mysql' => 'Ampostassion MySQL',
'config-header-postgres' => 'Ampostassion PostgreSQL',
'config-header-sqlite' => 'Ampostassion SQLite',
'config-header-oracle' => 'Ampostassion Oracle',
+ 'config-header-ibm_db2' => "Ampostassion d'IBM DB2",
'config-invalid-db-type' => 'Sòrt ëd ëd base ëd dàit pa bon-a',
'config-missing-db-name' => 'A dev buteje un valor për "Nòm ëd la base ëd dàit"',
+ 'config-missing-db-host' => 'A dev buteje un valor për "l\'òspit ëd la base ëd dàit"',
'config-missing-db-server-oracle' => 'A dev buteje un valor për "TNS ëd la base ëd dat"',
'config-invalid-db-server-oracle' => 'TNS ëd la base ëd dat pa bon "$1".
Dovré mach dle litre ASCII (a-z, A-Z), nùmer (0-9), sotlignadure (_) e pontin (.).',
'config-invalid-db-name' => 'Nòm ëd la base ëd dàit pa bon "$1".
-Dovré mach litre ASCII (a-z, A-Z), nùmer (0-9) e sotlignadure (_).', # Fuzzy
+Dovré mach litre ASCII (a-z, A-Z), nùmer (0-9), sotlignadure (_) e tratin (-).',
'config-invalid-db-prefix' => 'Prefiss dla base ëd dàit pa bon "$1".
-Dovré mach litre ASCII (a-z, A-Z), nùmer (0-9) e sotlignadure (_).', # Fuzzy
+Dovré mach litre ASCII (a-z, A-Z), nùmer (0-9), sotlignadure (_) e tratin (-).',
'config-connection-error' => "$1.
Controla l'ospitant, lë stranòm d'utent e la ciav sì-sota e prové torna.",
'config-invalid-schema' => 'Schema pa bon për MediaWiki "$1".
Dovré mach litre ASCII (a-z, A-Z), nùmer (0-9) e sotlignadure (_).',
+ 'config-db-sys-create-oracle' => "L'istalador a arconòss mach ij cont SYSDBA durant la creassion d'un cont neuv.",
+ 'config-db-sys-user-exists-oracle' => 'Ël cont utent "$1" a esist già. SYSDBA a peul mach esse dovrà për creé un cont neuv!',
'config-postgres-old' => "A-i é da manca ëd PostgreSQL $1 o pi recent, chiel a l'ha $2.",
'config-sqlite-name-help' => "Serne un nòm ch'a identìfica soa wiki.
Dovré nì dë spassi nì ëd tratin.
@@ -14837,8 +15664,11 @@ Adess a peule [$1 ancaminé a dovré soa wiki].
S'a veul generé torna sò archivi <code>LocalSettings.php</code>, ch'a sgnaca ël boton sì-sota.
Sòn a l'è '''pa arcomandà''' gavà ch'a rancontra dij problema con soa wiki.",
- 'config-regenerate' => 'Generé torna LocalSettings.php →',
- 'config-show-table-status' => 'Arcesta SHOW TABLE STATUS falìa!',
+ 'config-upgrade-done-no-regenerate' => 'Agiornament complet.
+
+It peule adess [$1 ancaminé a dovré toa wiki].',
+ 'config-regenerate' => 'Generé torna <code>LocalSettings.php</code> →',
+ 'config-show-table-status' => 'Arcesta <code>SHOW TABLE STATUS</code> falìa!',
'config-unknown-collation' => "'''Avis:''' La base ëd dàit a deuvra na classificassion pa arconossùa.",
'config-db-web-account' => "Cont dla base ëd dàit për l'acess a l'aragnà",
'config-db-web-help' => "Ch'a selession-a lë stranòm d'utent e la ciav che ël servent ëd l'aragnà a dovrërà për coleghesse al servent dle base ëd dàit, durant j'operassion ordinarie dla wiki.",
@@ -14849,6 +15679,13 @@ Sòn a l'è '''pa arcomandà''' gavà ch'a rancontra dij problema con soa wiki."
'config-mysql-engine' => 'Motor ëd memorisassion:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-myisam-dep' => "'''Avis''': A l'ha selessionà MyISAM com motor ëd memorisassion për MySQL, che a l'é pa arcomandà da dovré con MediaWiki, përchè:
+* a sopòrta a pen-a la contemporanità për via ëd saradure ëd tàula
+* a l'é pi soget a la corussion che j'àutri motor
+* ël còdes bas ëd MediaWiki pa sempe a gestiss MyISAM com a dovrìa
+
+Se soa istalassion MySQL a manten InnoDB, a l'é fortement arcomandà ch'a serna pitòst col-lì.
+Se soa istalassion MySQL a manten nen InnoDB, a peul esse ch'a sia ël moment ëd n'agiornament.",
'config-mysql-engine-help' => "'''InnoDB''' a l'é scasi sempe la mej opsion, da già ch'a l'ha un bon manteniment dla concorensa.
'''MyISAM''' a peul esse pi lest an instalassion për n'utent sol o mach an letura.
@@ -14860,6 +15697,7 @@ La base ëd dàit MyISAM a tira a corompse pi 'd soens che la base ëd dàit Inn
Sòn a l'é pi eficient che la manera UTF-8 ëd MySQL, e a-j përmët ëd dovré l'ansema antregh ëd caràter Unicode.
An '''manera UTF-8''', MySQL a conossrà an che ansem ëd caràter a son ij sò dat, e a peul presenteje e convertije apropriatament, ma a-j lassa pa memorisé ij caràter ëdzora al [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes pian multilenghìstich ëd base].",
+ 'config-ibm_db2-low-db-pagesize' => "Soa base ëd dàit DB2 a l'ha në spassi d'ambaronament predefinì con na dimension ëd pàgina insuficent. La dimension ëd pàgina a dev esse '''32K''' o pi gròssa.",
'config-site-name' => 'Nòm ëd la wiki:',
'config-site-name-help' => "Sòn a comparirà ant la bara dël tìtol dël navigador e an vàire d'àutri pòst.",
'config-site-name-blank' => "Ch'a buta un nòm ëd sit.",
@@ -14873,6 +15711,8 @@ Tùit ij tìtoj ëd pàgina ant cost ëspassi nominal a parto con un sert prefis
Tradissionalment, sto prefiss a l'é derivà dal nòm ëd la wiki, ma a peul pa conten-e caràter ëd pontegiatura coma \"#\" o \":\".",
'config-ns-invalid' => 'Lë spassi nominal specificà "<nowiki>$1</nowiki>" a l\'é pa bon.
Specìfica në spassi nominal ëd proget diferent.',
+ 'config-ns-conflict' => 'Lë spassi nominal specificà "<nowiki>$1</nowiki>" a và contra në spassi nominal predefinì ëd MediaWiki.
+Specìfica në spassi nominal ëd proget diferent.',
'config-admin-box' => "Cont ëd l'Aministrator",
'config-admin-name' => 'Tò nòm:',
'config-admin-password' => 'Ciav:',
@@ -14886,18 +15726,21 @@ Specìfica un nòm utent diferent.',
'config-admin-password-same' => "La ciav a dev nen esse l'istessa ëd lë stranòm d'utent.",
'config-admin-password-mismatch' => "Le doe ciav che a l'ha scrivù a son diferente antra 'd lor.",
'config-admin-email' => 'Adrëssa ëd pòsta eletrònica:',
- 'config-admin-email-help' => "Ch'a anserissa ambelessì n'adrëssa ëd pòsta eletrònica për përmëtt-je d'arsèive ëd mëssagi da d'àutri utent an sla wiki, riamposté soa ciav, e esse anformà ëd camgiament a le pàgine ch'a ten sot-euj.", # Fuzzy
+ 'config-admin-email-help' => "Ch'a anserissa ambelessì n'adrëssa ëd pòsta eletrònica për përmëtt-je d'arsèive ëd mëssagi da d'àutri utent an sla wiki, riamposté soa ciav, e esse anformà dle modìfiche a le pàgine ch'a ten sot-euj. A peule lassé ës camp veuid.",
'config-admin-error-user' => 'Eror antern an creand n\'aministrator con lë stranòm "<nowiki>$1</nowiki>".',
'config-admin-error-password' => 'Eror antern an ampostand na ciav për l\'admin "<nowiki>$1</nowiki>": <pre>$2</pre>',
+ 'config-admin-error-bademail' => "A l'ha butà n'adrëssa ëd pòsta eletrònica pa bon-a.",
'config-subscribe' => "Ch'a sot-scriva la [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce lista ëd discussion ëd j'anonsi ëd publicassion].",
'config-subscribe-help' => "Costa a l'é na lista ëd discussion a bass tràfich dovrà për j'anonsi ëd publicassion, comprèis d'amportant anonsi ëd sicurëssa.
A dovrìa sot-ëscrivla e agiorné soa instalassion mediaWiki quand che ëd version neuve a rivo.",
+ 'config-subscribe-noemail' => "A l'ha provà a abonesse a la lista ëd difusion dij comunicà sensa dé n'adrëssa ëd pòsta eletrònica.
+Për piasì, ch'a fornissa n'adrëssa ëd pòsta eletrònica s'a veul abonesse a la lista ëd pòsta.",
'config-almost-done' => "A l'ha bele che fàit!
A peul adess sauté la configurassion rimanenta e instalé dlongh la wiki.",
'config-optional-continue' => "Ciameme d'àutre chestion.",
'config-optional-skip' => 'I son già anojà, instala mach la wiki.',
'config-profile' => "Profil dij drit d'utent:",
- 'config-profile-wiki' => 'Wiki tradissional',
+ 'config-profile-wiki' => 'Deurb wiki',
'config-profile-no-anon' => 'A venta creé un cont',
'config-profile-fishbowl' => 'Mach editor autorisà',
'config-profile-private' => 'Wiki privà',
@@ -14907,7 +15750,7 @@ An MediaWiki, a l'é bel fé revisioné ij cambi recent, e buté andré minca da
An tùit ij cas, an tanti a l'han trovà che MediaWiki a sia ùtil ant na gran varietà ëd manere, e dle vire a l'é pa bel fé convince cheidun dij vantagi dla wiki.
Parèj a l'ha doe possibilità.
-Un '''{{int:config-profile-wiki}}''' a përmët a chicassìa ëd modifiché, bele sensa intré ant ël sistema.
+Ël model '''{{int:config-profile-wiki}}''' a përmët a chicassìa ëd modifiché, bele sensa intré ant ël sistema.
Na wiki con '''{{int:config-profile-no-anon}}''' a dà pì 'd contròl, ma a peul slontané dij contribudor casuaj.
Ël senari '''{{int:config-profile-fishbowl}}''' a përmët a j'utent aprovà ëd modifiché, ma ël pùblich a peul vëdde le pàgine, comprèisa la stòria.
@@ -14917,7 +15760,10 @@ Configurassion ëd drit d'utent pi complicà a son disponìbij apress l'instalas
'config-license' => "Drit d'autor e licensa",
'config-license-none' => 'Gnun-a licensa an nòta an bass',
'config-license-cc-by-sa' => 'Creative Commons atribussion an part uguaj',
+ 'config-license-cc-by' => 'Creative Commons Attribution',
'config-license-cc-by-nc-sa' => 'Creative Commons atribussion nen comersial an part uguaj',
+ 'config-license-cc-0' => 'Creative Commons Zero (domini pùblich)',
+ 'config-license-gfdl' => 'Licensa GNU Free Documentation 1.3 o pi neuva',
'config-license-pd' => 'Domini Pùblich',
'config-license-cc-choose' => 'Selessioné na licensa Creative Commons përsonalisà',
'config-license-help' => "Vàire wiki pùbliche a buto tute le contribussion sota na [http://freedomdefined.org/Definition licensa lìbera]. Sòn a giuta a creé un sens d'apartenensa a la comunità e a ancoragia ëd contribussion ëd longa durà.
@@ -14925,8 +15771,9 @@ A l'é generalment nen necessari për na wiki privà o d'asienda.
S'a veul podèj dovré dij test da Wikipedia, e a veul che Wikipedia a aceta dij test copià da soa wiki, a dovrìa serne '''Creative Commons Attribution Share Alike'''.
-La GNU Free Documentation License a l'era la veja licensa dont sota a-i era Wikipedia.
-A l'é anco' na licensa bon-a, an tùit ij cas, sta licensa a l'ha chèich funsion ch'a rendo difìcij l'utilisassion e l'antërpretassion.", # Fuzzy
+Wikipedia prima a dovrava la GNU Free Documentation License.
+La GDFL a l'é anco' na licensa bon-a, ma a l'é malfé da capila.
+A l'é ëdcò mal fé riutilisé dël contnù licensià sota la GDFL.",
'config-email-settings' => 'Ampostassion ëd pòsta eletrònica',
'config-enable-email' => 'Abilité ij mëssagi ëd pòsta eletrònica an surtìa',
'config-enable-email-help' => "S'a veul che la pòsta eletrònica a marcia, j'[http://www.php.net/manual/en/mail.configuration.php ampostassion ëd pòsta eletrònica PHP] a devo esse configurà për da bin.
@@ -14956,10 +15803,10 @@ Peui ch'a abìlita costa opsion.",
'config-upload-deleted-help' => "ch'a serna un dossié andova goerné j'archivi scancelà.
Idealment, sòn a dovrìa pa esse acessìbil an sl'aragnà.",
'config-logo' => 'Anliura dla marca:',
- 'config-logo-help' => "La pel dë stàndard ëd MediaWiki a comprend lë spassi për na marca ëd 135x160 pontin ant ël canton an àut a snista.
+ 'config-logo-help' => "La pel dë stàndard ëd MediaWiki a comprend lë spassi për na marca ëd 135x160 pontin dzora la lista dla bara lateral.
Ch'a dëscaria na figura ëd la dimension aproprià, e ch'a anserissa l'anliura ambelessì.
-S'a veul gnun-e marche, ch'a lassa ës camp bianch.", # Fuzzy
+S'a veul gnun-e marche, ch'a lassa ës camp bianch.",
'config-instantcommons' => 'Abìlita Instant Commons',
'config-instantcommons-help' => "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] a l'é na funsion ch'a përmët a le wiki ëd dovré dle figure, dij son e d'àutri mojen trovà an sël sit [//commons.wikimedia.org/ Wikimedia Commons].
Për dovré sossì, MediaWiki a l'ha da manca dl'acess a la ragnà.
@@ -14978,21 +15825,45 @@ Ij sit da mesan a gròss a son motobin ancoragià a abilité sòn, e ij sit cit
'config-cache-memcached' => "Dovré Memcached (a ciama n'ampostassion e na configurassion adissionaj)",
'config-memcached-servers' => 'Servent Memcached:',
'config-memcached-help' => "Lista d'adrësse IP da dovré për Memcached.
-A dovrìa esse separà con dle vìrgole e specifiché la pòrta da dovré (për esempi: 127.0.0.1:11211, 192.168.1.25:11211).", # Fuzzy
+A dovrìa specifichene un-a për linia e specifiché la pòrta da dovré. Për esempi:
+127.0.0.1:11211
+192.168.1.25:11211",
+ 'config-memcache-needservers' => "A l'ha selessionà Memcached com soa sòrt ëd memorisassion local ma a l'ha specificà gnun servent.",
+ 'config-memcache-badip' => "It l'ha anserì n'adrëssa IP pa bon-a për Memcached: $1.",
+ 'config-memcache-noport' => "A l'ha pa specificà na pòrta da dovré për ël servent Memcached: $1.
+S'a conòsse nen la pòrta, cola predefinìa a l'é 11211.",
+ 'config-memcache-badport' => 'Ij nùmer ëd pòrta ëd Memcached a dovrìo esse tra $1 e $2.',
'config-extensions' => 'Estension',
'config-extensions-help' => "J'estension listà dì-sota a son ëstàite trovà ant sò dossié <code>./extensions</code>.
A peulo avèj da manca ëd configurassion adissionaj, ma a peul abiliteje adess",
'config-install-alreadydone' => "'''Avis''' A smija ch'a l'abie già instalà MediaWiki e ch'a preuva a instalelo torna.
Për piasì, ch'a vada a la pàgina ch'a-i ven.",
+ 'config-install-begin' => 'An sgnacand "{{int:config-continue}}", a anandiërà l\'istalassion ëd MediaWiki.
+S\'a veul anco\' fé dle modìfiche, ch\'a sgnaca su "{{int:config-back}}".',
'config-install-step-done' => 'fàit',
'config-install-step-failed' => 'falì',
'config-install-extensions' => "Comprende j'estension",
'config-install-database' => 'Creassion ëd la base ëd dàit',
+ 'config-install-schema' => 'Creassion dë schema',
+ 'config-install-pg-schema-not-exist' => 'Lë schema postgreSQL a esist pa.',
'config-install-pg-schema-failed' => 'Creassion dle tàule falìa.
Sigurte che l\'utent "$1" a peussa scrive lë schema "$2".',
+ 'config-install-pg-commit' => 'Salvé ij cambi.',
+ 'config-install-pg-plpgsql' => 'Contròl dël langagi PL/pgSQL',
+ 'config-pg-no-plpgsql' => 'A dev istalé ël langage PL/pgSQL ant la base ëd dàit $1',
+ 'config-pg-no-create-privs' => "Ël cont ch'a l'ha specificà për l'istalassion a l'ha pa basta 'd privilegi për creé un cont.",
+ 'config-pg-not-in-role' => "Ël cont ch'a l'ha specificà për l'utent ëd la ragnà a esist già.
+Ël cont ch'a l'has specificà për l'istalassion a l'é pa un superutent e a l'é pa un mémber dla partìa dj'utent dla Ragnà, parèj a peul pa creé dj'oget ch'a apartenent a l'utent dla Ragnà.
+
+MediaWiki al moment a ciama che le tàule a sia possedùe da n'utent dla Ragnà. Për piasì, ch'a specìfica n'àutr nòm ëd cont dla Ragnà, o ch'a sgnaca ansima a \"andré\" e ch'a specìfica n'utent ch'a l'ha ij privilegi ch'a basto për l'anstalassion.",
'config-install-user' => "Creassion ëd n'utent ëd la base ëd dàit",
+ 'config-install-user-alreadyexists' => 'L\'utent "$1" a esist già',
+ 'config-install-user-create-failed' => "Faliment ant la creassion ëd l'utent «$1»: $2",
'config-install-user-grant-failed' => 'Falì a dé ij përmess a l\'utent "$1": $2',
+ 'config-install-user-missing' => 'L\'utent specificà "$1" a esist pa.',
+ 'config-install-user-missing-create' => "L'utent specificà «$1» a esist pa.
+Për piasì, ch'a selession-a la casela «cont da creé» sì-sota s'a veul creelo.",
'config-install-tables' => 'Creassion dle tàule',
'config-install-tables-exist' => "'''Avis''': A smija che le tàule ëd mediaWiki a esisto già.
Sauté la creassion.",
@@ -15001,26 +15872,41 @@ Sauté la creassion.",
'config-install-interwiki-list' => "As peul pa trovesse l'archivi <code>interwiki.list</code>.",
'config-install-interwiki-exists' => "'''Avis''': La tàula interwiki a smija ch'a l'abia già dj'element.
Për stàndard, la lista a sarà sautà.",
- 'config-install-keys' => 'Generassion ëd la ciav segreta', # Fuzzy
+ 'config-install-stats' => 'Inissialisassion dle statìstiche',
+ 'config-install-keys' => 'Generassion ëd le ciav segrete',
+ 'config-insecure-keys' => "'''Avis:''' {{PLURAL:$2|Na ciav sigura|Dle ciav sigure}} ($1) generà durant l'istalassion {{PLURAL:$2|a l'é|a son}} pa completament sigure. Ch'a consìdera ëd modifiche{{PLURAL:$2|la|je}} manualment.",
'config-install-sysop' => "Creassion dël cont ëd l'utent aministrator",
+ 'config-install-subscribe-fail' => 'As peul pa sot-scrivse mediawiki-announce: $1',
+ 'config-install-subscribe-notpossible' => "cURL a l'é pa istalà e allow_url_fopen a l'é pa disponìbil.",
+ 'config-install-mainpage' => 'Creassion ëd la pàgina prinsipal con un contnù predefinì',
+ 'config-install-extension-tables' => "Creassion ëd tàule për j'estension abilità",
+ 'config-install-mainpage-failed' => 'As peul pa inserisse la pàgina prinsipal: $1',
'config-install-done' => "'''Congratulassion!'''
A l'ha instalà për da bin mediaWiki.
L'instalador a l'ha generà n'archivi <code>LocalSettings.php</code>.
A conten tuta soa configurassion.
-A dovrà [$1 dëscarielo] e butelo ant la bas ëd l'instalassion ëd soa wiki (ël midem dossié d'index.php).
+A dovrà dëscarielo e butelo ant la bas ëd l'instalassion ëd soa wiki (ël midem dossié d'index.php). La dëscaria a dovrìa esse ancaminà automaticament.
+
+Se la dëscaria a l'é pa disponìbil, o s'a l'ha scancelala, a peul torna ancaminé la dëscaria an sgnacand an sla liura sì-sota:
+
+$3
+
'''Nòta''': S'a lo fa nen adess, cost archivi ëd configurassion generà a sarà pa disponìbil për chiel pi tard s'a chita l'instalassion sensa dëscarielo.
-Quand che a l'é stàit fàit, a peul '''[$2 intré an soa wiki]'''.", # Fuzzy
+Quand che a l'é stàit fàit, a peul '''[$2 intré an soa wiki]'''.",
+ 'config-download-localsettings' => 'Dëscarié <code>LocalSettings.php</code>',
'config-help' => 'agiut',
+ 'config-nofile' => "L'archivi «$1» as treuva nen. A l'é stàit ëscancelà?",
'mainpagetext' => "'''MediaWiki a l'é staita anstalà a la përfession.'''",
'mainpagedocfooter' => "Che a varda la [//meta.wikimedia.org/wiki/Help:Contents User's Guide] për avèj dj'anformassion ant sël coma dovré ël programa dla wiki.
== Për anandiesse a travajé ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista dij paràmeter ëd configurassion]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki Chestion frequente]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista ëd discussion an sla distribussion ëd MediaWiki]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista ëd discussion an sla distribussion ëd MediaWiki]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Localisa MediaWiki për toa lenga]",
);
/** Pontic (Ποντιακά)
@@ -15079,7 +15965,7 @@ $messages['ps'] = array(
'config-admin-password' => 'پټنوم:',
'config-admin-password-confirm' => 'پټنوم يو ځل بيا:',
'config-admin-email' => 'برېښليک پته:',
- 'config-profile-wiki' => 'دوديزه ويکي',
+ 'config-profile-wiki' => 'دوديزه ويکي', # Fuzzy
'config-license-pd' => 'ټولګړی شپول',
'config-email-settings' => 'د برېښليک امستنې',
'config-install-step-done' => 'ترسره شو',
@@ -15091,7 +15977,8 @@ $messages['ps'] = array(
== پيلول ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings د امستنو د سازونې لړليک]
* [//www.mediawiki.org/wiki/Manual:FAQ د ميډياويکي ډېرځليزې پوښتنې]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce د مېډياويکي د برېښليکونو لړليک]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce د مېډياويکي د برېښليکونو لړليک]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources خپلې ژبې لپاره MediaWiki ځايتابول]',
);
/** Portuguese (português)
@@ -15101,6 +15988,7 @@ $messages['ps'] = array(
* @author Platonides
* @author SandroHc
* @author Waldir
+ * @author 아라
*/
$messages['pt'] = array(
'config-desc' => 'O instalador do MediaWiki',
@@ -15108,19 +15996,19 @@ $messages['pt'] = array(
'config-information' => 'Informação',
'config-localsettings-upgrade' => 'Foi detectado um ficheiro <code>LocalSettings.php</code>.
Para actualizar esta instalação, por favor introduza o valor de <code>$wgUpgradeKey</code> na caixa abaixo.
-Encontra este valor no LocalSettings.php.',
- 'config-localsettings-cli-upgrade' => 'Foi detectada a existência de um ficheiro LocalSettings.php.
-Para actualizar esta instalação execute o update.php, por favor.',
+Encontra este valor no <code>LocalSettings.php</code>.',
+ 'config-localsettings-cli-upgrade' => 'Foi detectada a existência de um ficheiro <code>LocalSettings.php</code>.
+Para actualizar esta instalação execute o <code>update.php</code>, por favor.',
'config-localsettings-key' => 'Chave de actualização:',
'config-localsettings-badkey' => 'A chave que forneceu está incorreta.',
'config-upgrade-key-missing' => 'Foi detectada uma instalação existente do MediaWiki.
-Para actualizar esta instalação, por favor coloque a seguinte linha no final do seu LocalSettings.php:
+Para actualizar esta instalação, por favor coloque a seguinte linha no final do seu <code>LocalSettings.php</code>:
$1',
- 'config-localsettings-incomplete' => 'O ficheiro LocalSettings.php existente parece estar incompleto.
+ 'config-localsettings-incomplete' => 'O ficheiro <code>LocalSettings.php</code> existente parece estar incompleto.
A variável $1 não está definida.
-Por favor defina esta variável no LocalSettings.php e clique "Continuar".',
- 'config-localsettings-connection-error' => 'Ocorreu um erro ao ligar à base de dados usando as configurações especificadas no LocalSettings.php ou AdminSettings.php. Por favor corrija essas configurações e tente novamente.
+Por favor defina esta variável no <code>LocalSettings.php</code> e clique "{{int:Config-continue}}".',
+ 'config-localsettings-connection-error' => 'Ocorreu um erro ao ligar à base de dados usando as configurações especificadas no <code>LocalSettings.php</code> ou <code>AdminSettings.php</code>. Por favor corrija essas configurações e tente novamente.
$1',
'config-session-error' => 'Erro ao iniciar a sessão: $1',
@@ -15252,7 +16140,7 @@ Instalação interrompida.',
'config-using531' => 'O MediaWiki não pode ser usado com o PHP $1 devido a um problema que envolve parâmetros de referência para <code>__call()</code>.
Para resolver este problema, actualize o PHP para a versão 5.3.2 ou posterior, ou reverta-o para a 5.3.0.
Instalação interrompida.',
- 'config-suhosin-max-value-length' => 'O Suhosin está instalado e limita a $1 bytes o comprimento do parâmetro GET. O componente ResourceLoader do MediaWiki pode tornear este limite, mas prejudicando o desempenho. Se lhe for possível, deve atribuir o valor 1024 ou maior ao parâmetro suhosin.get.max_value_length no ficheiro php.ini, e definir o mesmo valor para $wgResourceLoaderMaxQueryLength no ficheiro LocalSettings.php.',
+ 'config-suhosin-max-value-length' => 'O Suhosin está instalado e limita a $1 bytes o comprimento do parâmetro GET. O componente ResourceLoader do MediaWiki pode tornear este limite, mas prejudicando o desempenho. Se lhe for possível, deve atribuir o valor 1024 ou maior ao parâmetro <code>suhosin.get.max_value_length</code> no ficheiro <code>php.ini</code>, e definir o mesmo valor para <code>$wgResourceLoaderMaxQueryLength</code> no ficheiro LocalSettings.php.', # Fuzzy
'config-db-type' => 'Tipo da base de dados:',
'config-db-host' => 'Servidor da base de dados:',
'config-db-host-help' => 'Se a base de dados estiver num servidor separado, introduza aqui o nome ou o endereço IP desse servidor.
@@ -15336,7 +16224,7 @@ Se a plataforma que pretende usar não está listada abaixo, siga as instruçõe
'config-support-postgres' => '* $1 é uma plataforma de base de dados comum, de fonte aberta, alternativa ao MySQL ([http://www.php.net/manual/en/pgsql.installation.php como compilar PHP com suporte PostgreSQL]). Poderão existir alguns pequenos problemas e não é recomendado o seu uso em ambientes de exploração/produção.',
'config-support-sqlite' => '* $1 é uma plataforma de base de dados ligeira muito bem suportada. ([http://www.php.net/manual/en/pdo.installation.php Como compilar PHP com suporte SQLite], usa PDO)',
'config-support-oracle' => '* $1 é uma base de dados de uma empresa comercial. ([http://www.php.net/manual/en/oci8.installation.php How to compile PHP with OCI8 support])',
- 'config-support-ibm_db2' => '* $1 é uma base de dados empresarial.',
+ 'config-support-ibm_db2' => '* $1 é uma base de dados empresarial.', # Fuzzy
'config-header-mysql' => 'Definições MySQL',
'config-header-postgres' => 'Definições PostgreSQL',
'config-header-sqlite' => 'Definições SQLite',
@@ -15403,8 +16291,8 @@ Esta operação '''não é recomendada''' a menos que esteja a ter problemas com
'config-upgrade-done-no-regenerate' => 'Actualização terminada.
Agora pode [$1 começar a usar a sua wiki].',
- 'config-regenerate' => 'Regenerar o LocalSettings.php →',
- 'config-show-table-status' => 'A consulta SHOW TABLE STATUS falhou!',
+ 'config-regenerate' => 'Regenerar o <code>LocalSettings.php</code> →',
+ 'config-show-table-status' => 'A consulta <code>SHOW TABLE STATUS</code> falhou!',
'config-unknown-collation' => "'''Aviso:''' A base de dados está a utilizar uma colação ''(collation)'' desconhecida.",
'config-db-web-account' => 'Conta na base de dados para acesso pela internet',
'config-db-web-help' => 'Seleccione o nome de utilizador e a palavra-chave que o servidor de internet irá utilizar para aceder ao servidor da base de dados, durante a operação normal da wiki.',
@@ -15477,7 +16365,7 @@ Agora pode saltar as configurações restantes e instalar já a wiki.',
'config-optional-continue' => 'Faz-me mais perguntas.',
'config-optional-skip' => 'Já estou aborrecido, instala lá a wiki.',
'config-profile' => 'Perfil de permissões:',
- 'config-profile-wiki' => 'Wiki tradicional',
+ 'config-profile-wiki' => 'Wiki tradicional', # Fuzzy
'config-profile-no-anon' => 'Criação de conta exigida',
'config-profile-fishbowl' => 'Somente utilizadores autorizados',
'config-profile-private' => 'Wiki privada',
@@ -15493,7 +16381,7 @@ Uma wiki com '''{{int:config-profile-no-anon}}''' atribui mais responsabilidade,
Um cenário '''{{int:config-profile-fishbowl}}''' permite que os utilizadores aprovados editem, mas que o público visione as páginas, incluindo o historial das mesmas.
Uma '''{{int:config-profile-private}}''' só permite que os utilizadores aprovados visionem as páginas e as editem.
-Após a instalação, estarão disponíveis mais configurações de privilégios. Consulte [//www.mediawiki.org/wiki/Manual:User_rights a entrada relevante no Manual].",
+Após a instalação, estarão disponíveis mais configurações de privilégios. Consulte [//www.mediawiki.org/wiki/Manual:User_rights a entrada relevante no Manual].", # Fuzzy
'config-license' => 'Direitos de autor e licença:',
'config-license-none' => 'Sem rodapé com a licença',
'config-license-cc-by-sa' => 'Creative Commons - Atribuição - Partilha nos Mesmos Termos',
@@ -15578,7 +16466,7 @@ Estas talvez necessitem de configurações adicionais, mas pode activá-las agor
'config-install-alreadydone' => "'''Aviso:''' Parece que já instalou o MediaWiki e está a tentar instalá-lo novamente.
Passe para a próxima página, por favor.",
'config-install-begin' => 'Ao clicar "{{int:config-continue}}", vai iniciar a instalação do MediaWiki.
-Se quiser fazer mais alterações, clique Voltar.',
+Se quiser fazer mais alterações, clique Voltar.', # Fuzzy
'config-install-step-done' => 'terminado',
'config-install-step-failed' => 'falhou',
'config-install-extensions' => 'A incluir as extensões',
@@ -15634,7 +16522,7 @@ $3
'''Nota''': Se não fizer isto agora, o ficheiro que foi gerado deixará de estar disponível quando sair do processo de instalação.
Depois de terminar o passo anterior, pode '''[$2 entrar na wiki]'''.",
- 'config-download-localsettings' => 'Download do LocalSettings.php',
+ 'config-download-localsettings' => 'Download do <code>LocalSettings.php</code>',
'config-help' => 'ajuda',
'config-nofile' => 'Não foi possível encontrar o ficheiro "$1". Terá sido apagado?',
'mainpagetext' => "'''MediaWiki instalado com sucesso.'''",
@@ -15644,7 +16532,7 @@ Depois de terminar o passo anterior, pode '''[$2 entrar na wiki]'''.",
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista de opções de configuração]
* [//www.mediawiki.org/wiki/Manual:FAQ Perguntas e respostas frequentes sobre o MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Subscreva a lista de divulgação de novas versões do MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Subscreva a lista de divulgação de novas versões do MediaWiki]', # Fuzzy
);
/** Brazilian Portuguese (português do Brasil)
@@ -15659,13 +16547,13 @@ $messages['pt-br'] = array(
'config-information' => 'Informações',
'config-localsettings-upgrade' => 'Foi detectada a existência do arquivo <code>LocalSettings.php</code>.
Para atualizar esta instalação, insira no box abaixo o valor de <code>$wgUpgradeKey</code>.
-Essa informação pode ser encontrada no arquivo LocalSettings.php',
- 'config-localsettings-cli-upgrade' => 'Foi detectada a existência do arquivo <code>LocalSettings.php</code>.
+Essa informação pode ser encontrada no arquivo <code>LocalSettings.php</code>',
+ 'config-localsettings-cli-upgrade' => 'Foi detectada a existência do arquivo <code><code>LocalSettings.php</code></code>.
Esta instalação deverá ser atualizada através do <code>update.php</code>',
'config-localsettings-key' => 'Chave de atualização:',
'config-localsettings-badkey' => 'A chave fornecida está incorreta.',
'config-upgrade-key-missing' => 'Foi detectada uma instalação existente do MediaWiki.
-Para atualizar esta instalação, por favor, coloque a seguinte linha na parte inferior do seu LocalSettings.php:
+Para atualizar esta instalação, por favor, coloque a seguinte linha na parte inferior do seu <code>LocalSettings.php</code>:
$ 1', # Fuzzy
'config-session-error' => 'Erro ao iniciar a sessão: $1',
@@ -15739,20 +16627,33 @@ Se você não pretende usar um logotipo, deixe este campo em branco.', # Fuzzy
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista de opções de configuração]
* [//www.mediawiki.org/wiki/Manual:FAQ FAQ do MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discussão com avisos de novas versões do MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discussão com avisos de novas versões do MediaWiki]', # Fuzzy
);
/** Quechua (Runa Simi)
+ * @author AlimanRuna
*/
$messages['qu'] = array(
+ 'config-desc' => 'MediaWiki tiyachiq',
+ 'config-title' => 'MediaWiki $1 tiyachiy',
+ 'config-information' => 'Willay',
+ 'config-your-language' => 'Rimayniyki:',
+ 'config-wiki-language' => 'Wiki rimay:',
+ 'config-back' => '← Ñawpaqman',
+ 'config-extensions' => "Mast'ariykuna",
+ 'config-install-step-done' => 'rurasqañam',
+ 'config-install-step-failed' => 'manam aypasqachu',
+ 'config-help' => 'yanapay',
+ 'config-nofile' => '"$1" sutiyuq willañiqiqa manam tarisqachu. Qullusqachu?',
'mainpagetext' => "'''MediaWiki nisqa llamp'u kaqqa aypaylla takyachisqañam.'''",
'mainpagedocfooter' => "Wiki llamp'u kaqmanta willasunaykipaqqa [//meta.wikimedia.org/wiki/Help:Contents Ruraqpaq yanapana] ''(User's Guide)'' sutiyuq p'anqata qhaway.
== Qallarichkaspa ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Kunphigurasyun churanamanta sutisuyu]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki nisqamanta pasaq tapuykuna]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki kachaykuy e-chaski sutisuyu]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources MediaWiki nisqata qampa rimaykiman t'ikray]",
);
/** Romagnol (Rumagnôl)
@@ -15775,10 +16676,16 @@ $messages['rm'] = array(
);
/** Romanian (română)
+ * @author Firilacroco
* @author Minisarm
* @author Stelistcristi
*/
$messages['ro'] = array(
+ 'config-desc' => 'Programul de instalare pentru MediaWiki',
+ 'config-title' => 'Instalarea MediaWiki $1',
+ 'config-information' => 'Informații',
+ 'config-localsettings-key' => 'Cheie de actualizare:',
+ 'config-localsettings-badkey' => 'Cheia furnizată este incorectă.',
'config-session-error' => 'Eroare la pornirea sesiunii: $1',
'config-your-language' => 'Limba ta:',
'config-your-language-help' => 'Alege o limbă pentru a o utiliza în timpul procesului de instalare.',
@@ -15794,46 +16701,153 @@ $messages['ro'] = array(
'config-page-name' => 'Nume',
'config-page-options' => 'Opţiuni',
'config-page-install' => 'Instalare',
+ 'config-page-complete' => 'Finalizat!',
'config-page-restart' => 'Reporneşte instalarea',
'config-page-readme' => 'Citeşte-mă',
'config-page-releasenotes' => 'Note de lansare',
+ 'config-page-copying' => 'Copiere',
+ 'config-page-upgradedoc' => 'Actualizare',
+ 'config-page-existingwiki' => 'Wiki existent',
+ 'config-restart' => 'Da, repornește.',
+ 'config-env-php' => 'PHP $1 este instalat.',
+ 'config-env-php-toolow' => 'PHP $1 este instalat.
+Totuși, MediaWiki necesită PHP $2 sau mai nou.',
'config-db-type' => 'Tipul bazei de date:',
'config-db-host' => 'Gazdă bază de date:',
+ 'config-db-host-oracle' => 'Baza de date TNS:',
+ 'config-db-wiki-settings' => 'Identificați acest wiki',
+ 'config-db-name' => 'Numele bazei de date:',
+ 'config-db-name-oracle' => 'Schema bazei de date:',
+ 'config-db-username' => 'Nume de utilizator pentru baza de date:',
+ 'config-db-password' => 'Parola bazei de date:',
+ 'config-db-prefix' => 'Prefixul tabelelor din baza de date:',
+ 'config-db-charset' => 'Setul de caractere al bazei de date',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binar',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-db-port' => 'Portul bazei de date:',
+ 'config-db-schema' => 'Schema pentru MediaWiki:',
+ 'config-sqlite-dir' => 'Director de date SQLite:',
+ 'config-oracle-def-ts' => 'Spațiu de stocare („tablespace”) implicit:',
+ 'config-oracle-temp-ts' => 'Spațiu de stocare („tablespace”) temporar:',
+ 'config-type-ibm_db2' => 'IBM DB2',
'config-header-mysql' => 'Setările MySQL',
+ 'config-header-postgres' => 'Setări PostgreSQL',
'config-header-sqlite' => 'Setări SQLite',
'config-header-oracle' => 'Setări Oracle',
+ 'config-header-ibm_db2' => 'Setări IBM DB2',
+ 'config-invalid-db-type' => 'Tip de bază de date incorect',
'config-missing-db-name' => 'Trebuie să introduci o valoare pentru „Numele bazei de date”',
+ 'config-connection-error' => '$1.
+
+Verificați gazda, numele de utilizator și parola și reîncercați.',
+ 'config-upgrade-done-no-regenerate' => 'Actualizare completă.
+
+Acum puteți [$1 începe să vă folosiți wikiul].',
+ 'config-regenerate' => 'Regenerare <code>LocalSettings.php</code> →',
+ 'config-unknown-collation' => 'AVERTISMENT: Baza de date folosește o colaționare nerecunoscută.',
+ 'config-db-web-account' => 'Contul bazei de date pentru accesul web.',
+ 'config-db-web-create' => 'Creați contul dacă nu există deja',
'config-mysql-engine' => 'Motor de stocare:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-charset' => 'Setul de caractere al bazei de date:',
+ 'config-mysql-binary' => 'Binar',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-site-name' => 'Numele wikiului:',
'config-site-name-blank' => 'Introduceți un nume pentru sit.',
+ 'config-project-namespace' => 'Spațiul de nume al proiectului:',
'config-ns-generic' => 'Proiect',
+ 'config-ns-site-name' => 'Același nume ca al wikiului: $1',
+ 'config-ns-other' => 'Altul (specificați)',
+ 'config-ns-other-default' => 'MyWiki',
+ 'config-admin-box' => 'Cont de administrator',
+ 'config-admin-name' => 'Numele dumneavoastră:',
'config-admin-password' => 'Parolă:',
+ 'config-admin-password-confirm' => 'Parola, din nou:',
+ 'config-admin-password-blank' => 'Introduceți o parolă pentru contul de administrator.',
+ 'config-admin-password-same' => 'Parola trebuie să difere de numele de utilizator.',
+ 'config-admin-password-mismatch' => 'Cele două parole introduse nu corespund.',
+ 'config-admin-email' => 'Adresa de e-mail:',
+ 'config-admin-error-bademail' => 'Ați introdus o adresă de e-mail incorectă.',
+ 'config-almost-done' => 'Sunteți aproape gata!
+Puteți sări peste configurarea rămasă și să instalați wikiul chiar acum.',
'config-optional-continue' => 'Adresează-mi mai multe întrebări.',
'config-optional-skip' => 'Sunt deja plictisit, doar instalează wikiul.',
- 'config-profile-wiki' => 'Wiki tradițional',
+ 'config-profile' => 'Profilul drepturilor de utilizator:',
+ 'config-profile-wiki' => 'Wiki tradițional', # Fuzzy
+ 'config-profile-no-anon' => 'Crearea de cont este necesară',
+ 'config-profile-fishbowl' => 'Doar editorii autorizați',
'config-profile-private' => 'Wiki privat',
+ 'config-license' => 'Drepturi de autor și licență:',
+ 'config-license-none' => 'Fără licență în subsolul paginii',
+ 'config-license-cc-by-sa' => 'Creative Commons Atribuire și distribuire în condiții identice',
+ 'config-license-cc-by' => 'Creative Commons Atribuire',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons Atribuire, necomercial și distribuire în condiții identice',
+ 'config-license-cc-0' => 'Creative Commons Zero (domeniu public)',
+ 'config-license-gfdl' => 'Licența GNU pentru Documentare Liberă 1.3 sau ulterioară',
+ 'config-license-pd' => 'Domeniu public',
+ 'config-license-cc-choose' => 'Alegeți o licență Creative Commons personalizată',
'config-email-settings' => 'Setări pentru e-mail',
+ 'config-email-usertalk' => 'Activați notificările pentru pagina de discuții a utilizatorului',
+ 'config-upload-deleted' => 'Director pentru fișierele șterse:',
+ 'config-logo' => 'Adresa URL a siglei:',
+ 'config-cc-again' => 'Alegeți din nou...',
+ 'config-advanced-settings' => 'Configurare avansată',
+ 'config-cache-options' => 'Parametrii pentru stocarea temporară a obiectelor:',
+ 'config-extensions' => 'Extensii',
'config-install-step-done' => 'realizat',
+ 'config-install-step-failed' => 'eșuat',
+ 'config-install-extensions' => 'Se includ extensiile',
+ 'config-install-database' => 'Se creează baza de date',
+ 'config-install-schema' => 'Se creează schema',
+ 'config-install-pg-schema-not-exist' => 'Schema PostgreSQL nu există.',
+ 'config-install-pg-commit' => 'Se validează modificările',
+ 'config-install-user' => 'Se creează utilizatorul pentru baza de date',
+ 'config-install-user-alreadyexists' => 'Utilizatorul „$1” există deja',
+ 'config-install-user-create-failed' => 'Crearea utilizatorului „$1” a eșuat: $2',
+ 'config-install-tables' => 'Se creează tabelele',
+ 'config-install-stats' => 'Se inițializează statisticile',
+ 'config-install-keys' => 'Se generează cheile secrete',
+ 'config-install-sysop' => 'Se creează contul de administrator',
+ 'config-install-mainpage-failed' => 'Nu s-a putut insera pagina principală: $1',
+ 'config-download-localsettings' => 'Descarcă <code>LocalSettings.php</code>',
+ 'config-help' => 'ajutor',
'mainpagetext' => "'''Programul Wiki a fost instalat cu succes.'''",
'mainpagedocfooter' => 'Consultați [//meta.wikimedia.org/wiki/Help:Contents Ghidul utilizatorului (en)] pentru informații despre utilizarea software-ului wiki.
== Primii pași ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista parametrilor configurabili (en)]
* [//www.mediawiki.org/wiki/Manual:FAQ Întrebări frecvente despre MediaWiki (en)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discuții a MediaWiki (en)]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discuții a MediaWiki (en)]', # Fuzzy
);
/** tarandíne (tarandíne)
* @author Joetaras
*/
$messages['roa-tara'] = array(
+ 'config-desc' => "'U 'nstallatore de MediaUicchi",
+ 'config-title' => 'Installazzione de MediaUicchi $1',
+ 'config-information' => "'Mbormaziune",
+ 'config-localsettings-key' => 'Chiave de aggiornamende:',
+ 'config-page-language' => 'Lènghe',
+ 'config-page-name' => 'Nome',
'config-db-charset' => "'Nzieme de carattere d'u database",
'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binary',
'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
'config-charset-mysql4' => 'MySQL 4.0 backwards-compatible UTF-8',
+ 'config-db-port' => "Porte d'u database:",
+ 'config-db-schema' => 'Scheme pe MediaUicchi:',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'config-admin-email' => 'Indirizze e-mail:',
'config-install-step-done' => 'fatte',
'config-install-step-failed' => 'fallite',
+ 'config-install-extensions' => "'Ngludenne le estenziune",
+ 'config-install-database' => "Stoche a 'mboste l'archivije",
+ 'config-install-schema' => "Stoche a creje 'u scheme",
+ 'config-install-pg-schema-not-exist' => "'U scheme PostgreSQL non g'esiste.",
'config-help' => 'ajute',
'mainpagetext' => "'''MediaUicchi ha state 'nstallete.'''",
'mainpagedocfooter' => "Vè vide [//meta.wikimedia.org/wiki/Help:Contents User's Guide] pe l'mbormaziune sus a cumme s'ause 'u softuer wiki.
@@ -15841,7 +16855,8 @@ $messages['roa-tara'] = array(
== Pe accumenzà ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Liste pe le configuraziune]
* [//www.mediawiki.org/wiki/Manual:FAQ FAQ de MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Liste d'a poste de MediaWiki]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Elenghe d'a poste de MediaUicchi]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Localizzazzione de MediaUicchi pa lènga toje]",
);
/** Russian (русский)
@@ -15856,6 +16871,7 @@ $messages['roa-tara'] = array(
* @author Yuriy Apostol
* @author Александр Сигачёв
* @author Сrower
+ * @author 아라
*/
$messages['ru'] = array(
'config-desc' => 'Инсталлятор MediaWiki',
@@ -15863,19 +16879,19 @@ $messages['ru'] = array(
'config-information' => 'Информация',
'config-localsettings-upgrade' => 'Обнаружен файл <code>LocalSettings.php</code>.
Для обновления этой установки, пожалуйста, введите значение <code>$wgUpgradeKey</code>.
-Его можно найти в файле LocalSettings.php.',
- 'config-localsettings-cli-upgrade' => 'Обнаружен файл LocalSettings.php.
-Для обновления этой установки, пожалуйста, запустите update.php',
+Его можно найти в файле <code>LocalSettings.php</code>.',
+ 'config-localsettings-cli-upgrade' => 'Обнаружен файл <code>LocalSettings.php</code>.
+Для обновления этой установки, пожалуйста, запустите <code>update.php</code>',
'config-localsettings-key' => 'Ключ обновления:',
'config-localsettings-badkey' => 'Вы указали неправильный ключ',
'config-upgrade-key-missing' => 'Обнаружена существующая установленная копия MediaWiki.
-Чтобы обновить обнаруженную установку, пожалуйста, добавьте следующую строку в конец вашего файла LocalSettings.php:
+Чтобы обновить обнаруженную установку, пожалуйста, добавьте следующую строку в конец вашего файла <code>LocalSettings.php</code>:
$1',
- 'config-localsettings-incomplete' => 'Похоже, что существующий файл LocalSettings.php не является полными.
+ 'config-localsettings-incomplete' => 'Похоже, что существующий файл <code>LocalSettings.php</code> не является полными.
Не установлена переменная $1.
-Пожалуйста, измените LocalSettings.php так, чтобы значение этой переменной было задано, затем нажмите «Продолжить».',
- 'config-localsettings-connection-error' => 'Произошла ошибка при подключении к базе данных с помощью настроек, указанных в LocalSettings.php или AdminSettings.php. Пожалуйста, исправьте эти настройки и повторите попытку.
+Пожалуйста, измените <code>LocalSettings.php</code> так, чтобы значение этой переменной было задано, затем нажмите «{{int:Config-continue}}».',
+ 'config-localsettings-connection-error' => 'Произошла ошибка при подключении к базе данных с помощью настроек, указанных в <code>LocalSettings.php</code> или <code>AdminSettings.php</code>. Пожалуйста, исправьте эти настройки и повторите попытку.
$1',
'config-session-error' => 'Ошибка при запуске сессии: $1',
@@ -16006,7 +17022,7 @@ MediaWiki требует поддержки UTF-8 для корректной р
'config-using531' => 'PHP $1 не совместим с MediaWiki из-за ошибки с параметрами-ссылками при вызовах <code>__call()</code>.
Обновитесь до PHP 5.3.2 и выше, или откатитесь до PHP 5.3.0, чтобы избежать этой проблемы.
Установка прервана.',
- 'config-suhosin-max-value-length' => 'Suhosin установлен и ограничивает длину параметра GET до $1 байт. Компонент MediaWiki ResourceLoader будет обходить это ограничение, но это снизит производительность. Если это возможно, следует установить suhosin.get.max_value_length 1024 или выше в php.ini, а также установить для $wgResourceLoaderMaxQueryLength такое же значение в LocalSettings.php.',
+ 'config-suhosin-max-value-length' => 'Suhosin установлен и ограничивает длину параметра GET до $1 байт. Компонент MediaWiki ResourceLoader будет обходить это ограничение, но это снизит производительность. Если это возможно, следует установить <code>suhosin.get.max_value_length</code> 1024 или выше в <code>php.ini</code>, а также установить для <code>$wgResourceLoaderMaxQueryLength</code> такое же значение в LocalSettings.php.', # Fuzzy
'config-db-type' => 'Тип базы данных:',
'config-db-host' => 'Хост базы данных:',
'config-db-host-help' => 'Если сервер базы данных находится на другом сервере, введите здесь его имя хоста или IP-адрес.
@@ -16092,7 +17108,7 @@ $1
'config-support-postgres' => '* $1 — популярная открытая СУБД, альтернатива MySQL ([http://www.php.net/manual/en/pgsql.installation.php инструкция, как собрать PHP с поддержкой PostgreSQL]). Могут встречаться небольшие неисправленные ошибки, не рекомендуется для использования в рабочей системе.',
'config-support-sqlite' => '* $1 — это легковесная система баз данных, имеющая очень хорошую поддержку. ([http://www.php.net/manual/en/pdo.installation.php инструкция, как собрать PHP с поддержкой SQLite], работающей посредством PDO)',
'config-support-oracle' => '* $1 — это коммерческая база данных масштаба предприятия. ([http://www.php.net/manual/en/oci8.installation.php Как собрать PHP с поддержкой OCI8])',
- 'config-support-ibm_db2' => '$1 — коммерческая база данных масштаба предприятия.',
+ 'config-support-ibm_db2' => '$1 — коммерческая база данных масштаба предприятия.', # Fuzzy
'config-header-mysql' => 'Настройки MySQL',
'config-header-postgres' => 'Настройки PostgreSQL',
'config-header-sqlite' => 'Настройки SQLite',
@@ -16159,8 +17175,8 @@ chmod a+w $3</pre>',
'config-upgrade-done-no-regenerate' => 'Обновление завершено.
Теперь вы можете [$1 начать работу с вики].',
- 'config-regenerate' => 'Создать LocalSettings.php заново →',
- 'config-show-table-status' => 'Запрос «SHOW TABLE STATUS» не выполнен!',
+ 'config-regenerate' => 'Создать <code>LocalSettings.php</code> заново →',
+ 'config-show-table-status' => 'Запрос «<code>SHOW TABLE STATUS</code>» не выполнен!',
'config-unknown-collation' => "'''Внимание:''' База данных использует нераспознанные правила сортировки.",
'config-db-web-account' => 'Учётная запись для доступа к базе данных из веб-сервера',
'config-db-web-help' => 'Выберите имя пользователя и пароль, которые веб-сервер будет использовать для подключения к серверу базы данных при обычной работе вики.',
@@ -16231,7 +17247,7 @@ chmod a+w $3</pre>',
'config-optional-continue' => 'Произвести тонкую настройку',
'config-optional-skip' => 'Хватит, установить вики',
'config-profile' => 'Профиль прав прользователей:',
- 'config-profile-wiki' => 'Традиционная вики',
+ 'config-profile-wiki' => 'Открытая вики',
'config-profile-no-anon' => 'Требуется создание учётной записи',
'config-profile-fishbowl' => 'Только для авторизованных редакторов',
'config-profile-private' => 'Закрытая вики',
@@ -16241,7 +17257,7 @@ chmod a+w $3</pre>',
Однако, движок MediaWiki можно использовать и иными способами, и не далеко не всех удаётся убедить в преимуществах открытой вики-работы.
Так что в вас есть выбор.
-Конфигурация '''«{{int:config-profile-wiki}}»''' позволяет всем править страницы даже не регистрируясь на сайте. Конфигурация '''{{int:config-profile-no-anon}}''' обеспечивает дополнительный учёт, но может отсечь случайных участников.
+Модель '''«{{int:config-profile-wiki}}»''' позволяет всем править страницы даже не регистрируясь на сайте. Конфигурация '''{{int:config-profile-no-anon}}''' обеспечивает дополнительный учёт, но может отсечь случайных участников.
Сценарий '''«{{int:config-profile-fishbowl}}»''' разрешает редактирование только определённым участникам, но общедоступным остаётся просмотр страниц, в том числе просмотр истории изменения. В режиме '''«{{int:config-profile-private}}»''' просмотр страниц разрешён только определённым пользователям, какая-то их часть может иметь также права на редактирование.
@@ -16329,7 +17345,7 @@ GFDL может быть использована, но она сложна дл
'config-install-alreadydone' => "'''Предупреждение:''' Вы, кажется, уже устанавливали MediaWiki и пытаетесь произвести повторную установку.
Пожалуйста, перейдите на следующую страницу.",
'config-install-begin' => 'Нажав «{{int:config-continue}}», вы начнёте установку MediaWiki.
-Если вы хотите внести изменения, нажмите «Назад».',
+Если вы хотите внести изменения, нажмите «{{int:config-back}}».',
'config-install-step-done' => 'выполнено',
'config-install-step-failed' => 'не удалось',
'config-install-extensions' => 'В том числе расширения',
@@ -16385,7 +17401,7 @@ $3
'''Примечание''': Если вы не сделаете этого сейчас, то сгенерированный файл конфигурации не будет доступен вам в дальнейшем, если вы выйдете из установки, не скачивая его.
По окончании действий, описанных выше, вы сможете '''[$2 войти в вашу вики]'''.",
- 'config-download-localsettings' => 'Загрузить LocalSettings.php',
+ 'config-download-localsettings' => 'Загрузить <code>LocalSettings.php</code>',
'config-help' => 'справка',
'config-nofile' => 'Файл "$1" не удается найти. Он был удален?',
'mainpagetext' => "'''Вики-движок «MediaWiki» успешно установлен.'''",
@@ -16394,7 +17410,8 @@ $3
== Некоторые полезные ресурсы ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Список возможных настроек];
* [//www.mediawiki.org/wiki/Manual:FAQ Часто задаваемые вопросы и ответы по MediaWiki];
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Рассылка уведомлений о выходе новых версий MediaWiki].',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Рассылка уведомлений о выходе новых версий MediaWiki].
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Перевод MediaWiki на свой язык]',
);
/** Rusyn (русиньскый)
@@ -16514,62 +17531,146 @@ $messages['shi'] = array(
* @author පසිඳු කාවින්ද
*/
$messages['si'] = array(
+ 'config-desc' => 'මාධ්‍යවිකි සඳහා වූ ස්ථාපකය',
+ 'config-title' => 'මාධ්‍යවිකි $1 ස්ථාපනය',
'config-information' => 'තොරතුරු',
+ 'config-localsettings-key' => 'උසස්කිරීම් යතුර:',
+ 'config-localsettings-badkey' => 'ඔබ ඉදිරිපත් කෙරූ යතුර වැරදිය.',
+ 'config-session-error' => 'සැසිය ඇරඹීමේ දෝෂය: $1',
'config-your-language' => 'ඔබේ භාෂාව:',
'config-wiki-language' => 'විකි භාෂාව:',
'config-back' => '← ආපසු',
'config-continue' => 'ඉදිරියට →',
'config-page-language' => 'භාෂාව',
'config-page-welcome' => 'මාධ්‍යවිකි වෙත පිළිගනිමු!',
+ 'config-page-dbconnect' => 'දත්ත සංචිතයට සම්බන්ධ කරන්න',
+ 'config-page-upgrade' => 'පවත්නා ස්ථාපනය උසස් කරන්න',
'config-page-dbsettings' => 'දත්ත සංචිත සැකසුම්',
'config-page-name' => 'නම',
'config-page-options' => 'විකල්ප',
'config-page-install' => 'ස්ථාපනය',
'config-page-complete' => 'සම්පූර්ණයි!',
+ 'config-page-restart' => 'ස්ථාපනය යළි අරඹන්න',
'config-page-readme' => 'මාව කියවන්න',
'config-page-releasenotes' => 'නිකුතු සටහන්',
'config-page-copying' => 'පිටපත් කරමින්',
+ 'config-page-upgradedoc' => 'උසස් කරමින්',
+ 'config-page-existingwiki' => 'පවත්නා විකිය',
+ 'config-env-php' => 'PHP $1 ස්ථාපිතයි.',
+ 'config-db-type' => 'දත්ත සංචිත වර්ගය:',
+ 'config-db-host' => 'දත්ත සංචිත ධාරක:',
+ 'config-db-wiki-settings' => 'මෙම විකිය හඳුනා ගන්න',
'config-db-name' => 'දත්ත සංචිතයේ නම:',
+ 'config-db-name-oracle' => 'දත්ත සංචිත සංක්ෂිප්ත නිරූපණය:',
+ 'config-db-install-account' => 'ස්ථාපනය සඳහා පරිශීලක ගිණුම',
+ 'config-db-username' => 'දත්ත සංචිතයේ පරිශීලක නාමය:',
+ 'config-db-password' => 'දත්ත සංචිතයේ මුරපදය:',
+ 'config-db-wiki-account' => 'සාමාන්‍ය ක්‍රියාකාරිත්වය සඳහා පරිශීලක ගිණුම',
+ 'config-db-prefix' => 'දත්ත සංචිත වගු උපසර්ගය:',
+ 'config-db-charset' => 'දත්ත සංචිත අක්ෂර කට්ටලය',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 ද්විමය',
'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 ආපස්සට-ගැළපෙන UTF-8',
'config-db-port' => 'දත්ත සංචිතයේ කවුළුව:',
+ 'config-db-schema' => 'මාධ්‍යවිකි සඳහා සංක්ෂිප්ත නිරූපණය:',
+ 'config-pg-test-error' => "'''$1''' දත්ත සංචිතය වෙත සම්බන්ධ විය නොහැක: $2",
+ 'config-sqlite-dir' => 'SQLite දත්ත නාමවලිය:',
+ 'config-oracle-def-ts' => 'සාමාන්‍ය වගු අවකාශය:',
+ 'config-oracle-temp-ts' => 'තාවකාලික වගු අවකාශය:',
'config-type-ibm_db2' => 'IBM DB2',
'config-header-mysql' => 'MySQL සැකසුම්',
'config-header-postgres' => 'PostgreSQL සැකසුම්',
'config-header-sqlite' => 'SQLite සැකසුම්',
'config-header-oracle' => 'ඔරකල් සැකසුම්',
'config-header-ibm_db2' => 'IBM DB2 සැකසුම්',
+ 'config-invalid-db-type' => 'වලංගු නොවන දත්ත සංචිත වර්ගය',
+ 'config-missing-db-name' => '"දත්ත සංචිත නාමය" සඳහා ඔබ විසින් අගයක් දිය යුතු වේ',
+ 'config-missing-db-host' => '"දත්ත සංචිත ධාරකය" සඳහා ඔබ විසින් අගයක් දිය යුතු වේ',
+ 'config-missing-db-server-oracle' => '"දත්ත සංචිත TNS" සඳහා ඔබ විසින් අගයක් දිය යුතු වේ',
+ 'config-regenerate' => 'නැවත ජනිත කරන්න <code>LocalSettings.php</code> →',
+ 'config-db-web-account' => 'ජාල ප්‍රවේශනය සඳහා දත්ත සංචිත ගිණුම',
+ 'config-mysql-engine' => 'ආචයන එන්ජිම:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
'config-mysql-binary' => 'ද්විමය',
'config-mysql-utf8' => 'UTF-8',
+ 'config-site-name' => 'විකියෙහි නම:',
'config-site-name-blank' => 'අඩවි නාමයක් යොදන්න.',
'config-project-namespace' => 'ව්‍යාපෘතියේ නාමඅවකාශය:',
'config-ns-generic' => 'ව්‍යාපෘතිය',
+ 'config-ns-site-name' => 'විකියෙහි නම ලෙසම: $1',
'config-ns-other' => 'වෙනත් (විශේෂණය කරන්න)',
'config-ns-other-default' => 'මගේවිකිය',
'config-admin-box' => 'පරිපාලක ගිණුම',
'config-admin-name' => 'ඔබේ නම:',
'config-admin-password' => 'මුරපදය:',
'config-admin-password-confirm' => 'මුරපදය නැවතත්:',
+ 'config-admin-name-blank' => 'පරිපාලක පරිශීලක නාමය යොදන්න.',
+ 'config-admin-password-blank' => 'පරිපාලක ගිණුම සඳහා මුරපදය යොදන්න.',
+ 'config-admin-password-same' => 'මුරපදය හා පරිශීලක නාමය එක සමාන නොවිය යුතුය.',
+ 'config-admin-password-mismatch' => 'ඔබ ඇතුළු කල මුරපද දෙක නොගැලපේ.',
'config-admin-email' => 'විද්‍යුත්-තැපැල් ලිපිනය:',
- 'config-profile-wiki' => 'සාම්ප්‍රදායික විකිය',
+ 'config-admin-error-bademail' => 'ඔබ විසින් වලංගු නොවන විද්‍යුත්-ලිපිනයක් යොදා ඇත.',
+ 'config-optional-continue' => 'මගෙන් තව ප්‍රශ්ණ අහන්න.',
+ 'config-optional-skip' => 'මම දැනටමත් කම්මැලි වී ඇත, විකිය ස්ථාපනය කරන්න.',
+ 'config-profile' => 'පරිශීලක හිමිකම් පැතිකඩ:',
+ 'config-profile-wiki' => 'සාම්ප්‍රදායික විකිය', # Fuzzy
'config-profile-no-anon' => 'ගිණුම් තැනීම අවශ්‍යයි',
+ 'config-profile-fishbowl' => 'අවසරලත් සංස්කාරකවරුන් පමණි',
'config-profile-private' => 'පුද්ගලික විකිය',
+ 'config-license' => 'කතුහිමිකම සහ බලපත්‍රය:',
+ 'config-license-none' => 'බලපත්‍ර පාද තලයක් නොමැත',
+ 'config-license-cc-by-sa' => 'නිර්මාණාත්මක පොදුජන ආරෝපණය හුවමාරුවට සමානව',
+ 'config-license-cc-by' => 'නිර්මාණාත්මක පොදුජන ආරෝපණය',
+ 'config-license-cc-by-nc-sa' => 'නිර්මාණාත්මක පොදුජන ආරෝපණය වාණිජ්‍ය-නොවන හුවමාරුවට සමානව',
'config-license-pd' => 'පොදු වසම',
'config-email-settings' => 'විද්‍යුත්-තැපැල් සැකසුම්',
+ 'config-enable-email' => 'පිටතට යොමු වූ විද්‍යුත්-තැපෑල සක්‍රිය කරන්න',
+ 'config-email-user' => 'පරිශීලක-වෙත-පරිශීලක විද්‍යුත්-තැපෑල සක්‍රිය කරන්න',
+ 'config-email-usertalk' => 'පරිශීලක කතාබහ පිටු නිවේදනය සක්‍රිය කරන්න',
+ 'config-email-watchlist' => 'මුරලැයිස්තු නිවේදනය සක්‍රිය කරන්න',
+ 'config-email-auth' => 'විද්‍යුත්-තැපැල් සහතික කිරීම සක්‍රිය කරන්න',
+ 'config-email-sender' => 'ප්‍රත්‍යාගමන විද්‍යුත්-තැපැල් ලිපිනය:',
+ 'config-upload-settings' => 'පින්තූර සහ ගොනු උඩුගත කිරීම්',
+ 'config-upload-enable' => 'ගොනු උඩුගත කිරීම් සක්‍රිය කරන්න',
'config-upload-deleted' => 'මැකූ ගොනු සඳහා නාමාවලිය:',
+ 'config-logo' => 'ලාංඡනයේ URL:',
+ 'config-instantcommons' => 'ක්ෂණික කොමන්ස් සක්‍රිය කරන්න',
+ 'config-cc-again' => 'නැවත ඇහිඳගන්න...',
+ 'config-advanced-settings' => 'උසස් වින්‍යාසගතකෙරුම',
+ 'config-cache-options' => 'වස්තු කෑෂය සඳහා සැකසුම්:',
+ 'config-memcached-servers' => 'මතකකෑෂිත සර්වරයන්:',
'config-extensions' => 'විස්තීර්ණ',
'config-install-step-done' => 'සිදුකලා',
'config-install-step-failed' => 'අසාර්ථකයි',
+ 'config-install-extensions' => 'විස්තීර්ණ අඩංගු කරමින්',
+ 'config-install-database' => 'දත්ත සංචිතය සකසමින්',
+ 'config-install-schema' => 'සංක්ෂිප්ත නිරූපණය තනමින්',
+ 'config-install-pg-schema-not-exist' => 'PostgreSQL සංක්ෂිප්ත නිරූපණය නොපවතියි.',
+ 'config-install-pg-commit' => 'වෙනස්කම් ප්‍රයාපනය කරමින්',
+ 'config-install-pg-plpgsql' => 'PL/pgSQL භාෂාව සඳහා පරික්ෂා කරමින්',
+ 'config-install-user' => 'දත්ත සංචිත පරිශීලක තනමින්',
+ 'config-install-user-alreadyexists' => '"$1" පරිශීලක දැනටමත් පවතී',
+ 'config-install-user-create-failed' => '"$1" පරිශීලක තැනීම අසාර්ථකයි: $2',
+ 'config-install-user-missing' => 'විශේෂණය කෙරූ "$1" පරිශීලකයා නොපවතියි.',
'config-install-tables' => 'වගු තනමින්',
+ 'config-install-interwiki' => 'සාමාන්‍ය අන්තර්විකි වගුව ගහනය කරමින්',
+ 'config-install-interwiki-list' => '<code>interwiki.list</code> ගොනුව කියවිය නොහැක.',
+ 'config-install-stats' => 'සංඛ්‍යානය අරඹමින්',
+ 'config-install-keys' => 'රහස් යතුරු ජනිත කරමින්',
+ 'config-install-sysop' => 'පරිපාලක පරිශීලක ගිණුම තනමින්',
+ 'config-install-mainpage' => 'සාමාන්‍ය අන්තර්ගතය සමඟින් ප්‍රධාන පිටුව තනමින්',
+ 'config-install-mainpage-failed' => 'ප්‍රධාන පිටුව ඇතුල් කල නොහැක: $1',
+ 'config-download-localsettings' => '<code>LocalSettings.php</code> බාගන්න',
'config-help' => 'උදව්',
+ 'config-nofile' => '"$1" ගොනුව සොයාගත නොහැක. එක මැකිලා ගියාවත්ද?',
'mainpagetext' => "'''මීඩියාවිකි සාර්ථක ලෙස ස්ථාපනය කරන ලදි.'''",
'mainpagedocfooter' => 'විකි මෘදුකාංග භාවිතා කිරීම පිළිබඳ තොරතුරු සඳහා [//meta.wikimedia.org/wiki/Help:Contents පරිශීලකයන් සඳහා නියමුව] හදාරන්න.
== ඇරඹුම ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings වින්‍යාස සැකසුම්]
* [//www.mediawiki.org/wiki/Manual:FAQ මීඩියාවිකි නිති-විමසන-පැන]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce මීඩියාවිකි නිකුතුව තැපැල් ලැයිස්තුව]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce මීඩියාවිකි නිකුතුව තැපැල් ලැයිස්තුව]', # Fuzzy
);
/** Slovak (slovenčina)
@@ -16581,7 +17682,7 @@ $messages['sk'] = array(
'config-back' => '← Späť',
'config-continue' => 'Pokračovať →',
'config-page-language' => 'Jazyk',
- 'config-download-localsettings' => 'Stiahnuť LocalSettings.php',
+ 'config-download-localsettings' => 'Stiahnuť <code>LocalSettings.php</code>',
'config-nofile' => 'Súbor "$1" sa nenašiel. Bol zmazaný?',
'mainpagetext' => "'''Softvér MediaWiki bol úspešne nainštalovaný.'''",
'mainpagedocfooter' => 'Informácie ako používať wiki softvér nájdete v [//meta.wikimedia.org/wiki/Help:Contents Používateľskej príručke].
@@ -16590,7 +17691,7 @@ $messages['sk'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Zoznam konfiguračných nastavení]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce mailing list nových verzií MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce mailing list nových verzií MediaWiki]', # Fuzzy
);
/** Slovenian (slovenščina)
@@ -16601,12 +17702,12 @@ $messages['sl'] = array(
'config-desc' => 'Namestitveni program za MediaWiki',
'config-title' => 'Namestitev MediaWiki $1',
'config-information' => 'Informacije',
- 'config-localsettings-cli-upgrade' => 'Zaznana je bila datoteka LocalSettings.php.
-Za nadgradnjo te namestitve zaženite update.php',
+ 'config-localsettings-cli-upgrade' => 'Zaznana je bila datoteka <code>LocalSettings.php</code>.
+Za nadgradnjo te namestitve zaženite <code>update.php</code>',
'config-localsettings-key' => 'Nadgraditveni ključ:',
'config-localsettings-badkey' => 'Naveden ključ je napačen.',
'config-upgrade-key-missing' => 'Zaznana je bila obstoječa namestitev MediaWiki.
-Za nadgradnjo te namestitve vstavite naslednjo vrstico na dno vaše LocalSettings.php:
+Za nadgradnjo te namestitve vstavite naslednjo vrstico na dno vaše <code>LocalSettings.php</code>:
$1',
'config-session-error' => 'Napaka pri začenjanju seje: $1',
@@ -16717,8 +17818,8 @@ Preverite mapo podatkov in ime zbirke podatkov spodaj ter poskusite znova.',
'config-upgrade-done-no-regenerate' => 'Nadgradnja je končana.
Sedaj lahko [$1 začnete uporabljati vaš wiki].',
- 'config-regenerate' => 'Ponovno ustvari LocalSettings.php →',
- 'config-show-table-status' => 'Poizvedba SHOW TABLE STATUS ni uspela!',
+ 'config-regenerate' => 'Ponovno ustvari <code>LocalSettings.php</code> →',
+ 'config-show-table-status' => 'Poizvedba <code>SHOW TABLE STATUS</code> ni uspela!',
'config-unknown-collation' => "'''Opozorilo:''' Zbirke podatkov uporablja neprepoznano razvrščanje znakov.",
'config-db-web-account' => 'Račun zbirke podatkov za spletni dostop',
'config-db-web-account-same' => 'Uporabi enak račun kot za namestitev',
@@ -16763,7 +17864,7 @@ Sedaj lahko preskočite preostalo konfiguriranje in zdaj namestite wiki.',
'config-optional-continue' => 'Zastavi mi več vprašanj.',
'config-optional-skip' => 'Se že dolgočasim; samo namesti wiki.',
'config-profile' => 'Profil uporabniških pravic:',
- 'config-profile-wiki' => 'Klasičen wiki',
+ 'config-profile-wiki' => 'Odprti wiki',
'config-profile-no-anon' => 'Zahtevano je ustvarjanje računa',
'config-profile-fishbowl' => 'Samo pooblaščeni urejevalci',
'config-profile-private' => 'Zasebni wiki',
@@ -16802,11 +17903,11 @@ Vnesite ime dovoljenja ročno.',
'config-install-pg-schema-not-exist' => 'Shema PostgreSQL ne obstaja.',
'config-install-user-alreadyexists' => 'Uporabnik »$1« že obstaja',
'config-install-tables' => 'Ustvarjanje tabel',
- 'config-download-localsettings' => 'Prenesi LocalSettings.php',
+ 'config-download-localsettings' => 'Prenesi <code>LocalSettings.php</code>',
'config-help' => 'pomoč',
'mainpagetext' => "'''Programje MediaWiki je bilo uspešno nameščeno.'''",
'mainpagedocfooter' => 'Za uporabo in pomoč pri nastavitvi, prosimo, preglejte [//meta.wikimedia.org/wiki/MediaWiki_localisation dokumentacijo za prilagajanje vmesnika]
-in [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Uporabniški priročnik].',
+in [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Uporabniški priročnik].', # Fuzzy
);
/** Lower Silesian (Schläsch)
@@ -17027,12 +18128,14 @@ MediaWiki kräver PHP $2 eller högre.',
'config-diff3-bad' => 'GNU diff3 hittades inte.',
'config-using-server' => 'Använder servernamn "<nowiki>$1</nowiki>".',
'config-using-uri' => 'Använder server-URL "<nowiki>$1$2</nowiki>".',
+ 'config-db-type' => 'Databastyp:',
'config-db-wiki-settings' => 'Identifiera denna wiki',
'config-db-name' => 'Databasnamn:',
'config-db-name-oracle' => 'Databasschema:',
'config-db-install-account' => 'Användarkonto för installation',
'config-db-username' => 'Databas-användarnamn:',
'config-db-password' => 'Databas-lösenord:',
+ 'config-db-port' => 'Databasport:',
'config-db-schema' => 'Schema för MediaWiki',
'config-header-mysql' => 'MySQL-inställningar',
'config-header-postgres' => 'PostgreSQL-inställningar',
@@ -17061,10 +18164,19 @@ Detta '''rekommenderas inte''' om du har problem med din wiki.",
'config-upgrade-done-no-regenerate' => 'Uppgraderingen slutfördes.
Du kan nu [$1 börja använda din wiki].',
+ 'config-db-web-account-same' => 'Använd samma konto som för installation',
+ 'config-db-web-create' => 'Skapa kontot om det inte redan finns',
+ 'config-mysql-engine' => 'Lagringsmotor:',
+ 'config-mysql-binary' => 'Binär',
'config-mysql-utf8' => 'UTF-8',
'config-site-name' => 'Namnet på wikin:',
'config-site-name-blank' => 'Ange ett sidnamn.',
'config-ns-generic' => 'Projekt',
+ 'config-ns-invalid' => 'Den angivna namnrymden "<nowiki>$1</nowiki>" är ogiltig.
+Ange ett annat namnrymd för projektet.',
+ 'config-ns-conflict' => 'Den angivna namnrymden "<nowiki>$1</nowiki>" står i konflikt med en standardnamnrymd för MediaWiki.
+Ange ett annat namnrymd för projektet.',
+ 'config-admin-box' => 'Administratörskonto',
'config-admin-name' => 'Ditt namn:',
'config-admin-password' => 'Lösenord:',
'config-admin-password-confirm' => 'Lösenord igen:',
@@ -17074,26 +18186,78 @@ Detta är namnet du kommer att använda för att logga in på wikin.',
'config-admin-name-invalid' => 'Det angivna användarnamnet "<nowiki>$1</nowiki>" är ogiltigt.
Ange ett annat användarnamn.',
'config-admin-password-blank' => 'Ange ett lösenord för administratörskontot.',
+ 'config-admin-password-same' => 'Lösenordet får inte vara samma som användarnamnet.',
+ 'config-admin-password-mismatch' => 'De två lösenord du uppgett överensstämmer inte med varandra.',
'config-admin-email' => 'E-postadress:',
'config-admin-error-bademail' => 'Du har angivit en felaktigt e-postadress.',
+ 'config-almost-done' => 'Du är nästan färdig!
+Du kan nu hoppa över återstående konfigurationer och installera wikin nu.',
'config-optional-continue' => 'Ställ fler frågor till mig.',
+ 'config-optional-skip' => 'Jag är redan uttråkad, bara installera wiki.',
+ 'config-profile-wiki' => 'Öppen wiki',
+ 'config-profile-fishbowl' => 'Endast auktoriserade redigerare',
'config-profile-private' => 'Privat wiki',
'config-license' => 'Upphovsrätt och licens:',
+ 'config-license-none' => 'Ingen licenssidfot',
+ 'config-license-cc-by-sa' => 'Creative Commons Erkännande Dela Lika',
+ 'config-license-cc-by' => 'Creative Commons Erkännande',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons Erkännande Icke-Kommersiell Dela Lika',
+ 'config-license-cc-0' => 'Creative Commons Zero (allmän egendom)',
+ 'config-license-gfdl' => 'GNU Free Documentation License 1.3 eller senare',
'config-license-pd' => 'Allmän egendom',
+ 'config-license-cc-choose' => 'Välj en anpassad Creative Commons-licens',
'config-email-settings' => 'E-postinställningar',
+ 'config-email-watchlist' => 'Aktivera meddelanden för bevakningslistan',
+ 'config-upload-settings' => 'Bild- och filuppladdningar',
+ 'config-upload-enable' => 'Aktivera filöverföringar',
+ 'config-upload-deleted' => 'Mapp för raderade filer:',
+ 'config-logo' => 'Logotyp-URL:',
+ 'config-cc-again' => 'Välj igen...',
+ 'config-advanced-settings' => 'Avancerad konfiguration',
+ 'config-extensions' => 'Tillägg',
'config-install-step-done' => 'klar',
'config-install-step-failed' => 'misslyckades',
+ 'config-install-database' => 'Konfigurerar databas',
+ 'config-install-schema' => 'Skapar schema',
+ 'config-install-user' => 'Skapar databasanvändare',
+ 'config-install-user-alreadyexists' => 'Användaren "$1" finns redan',
+ 'config-install-user-create-failed' => 'Misslyckades att skapa användare "$1": $2',
+ 'config-install-user-missing' => 'Den angivna användaren "$1" existerar inte.',
+ 'config-install-tables' => 'Skapar tabeller',
+ 'config-install-interwiki' => 'Lägger till standardtabell för interwiki',
+ 'config-install-interwiki-list' => 'Kunde inte läsa filen <code>interwiki.list</code>.',
+ 'config-install-stats' => 'Initierar statistik',
+ 'config-install-keys' => 'Genererar hemliga nycklar',
'config-insecure-keys' => "'''Varning:''' {{PLURAL:$2|En säkerhetsnyckel|Säkerhetsnycklar}} ($1) som generades under installationen är inte helt {{PLURAL:$2|säker|säkra}} . Överväg att ändra {{PLURAL:$2|den|dem}} manuellt.",
- 'config-download-localsettings' => 'Ladda ned LocalSettings.php',
+ 'config-install-sysop' => 'Skapar administratörskonto',
+ 'config-install-mainpage' => 'Skapa huvudsida med standardinnehåll',
+ 'config-install-extension-tables' => 'Skapar tabeller för aktiverade tillägg',
+ 'config-install-done' => "'''Grattis!'''
+Du har installerat MediaWiki.
+
+Installationsprogrammet har genererat filen <code>LocalSettings.php</code>.
+Det innehåller alla dina konfigurationer.
+
+Du kommer att behöva ladda ned den och placera den i botten av din wiki-installation (samma mapp som index.php). Nedladdningen borde ha startats automatiskt.
+
+Om ingen nedladdning erbjöds, eller om du har avbrutit det kan du starta om nedladdningen genom att klicka på länken nedan:
+
+$3
+
+'''OBS''': Om du inte gör detta nu, kommer denna genererade konfigurationsfil inte vara tillgänglig för dig senare om du avslutar installationen utan att ladda ned den.
+
+När det är klart, kan du '''[$2 gå in på din wiki]'''.",
+ 'config-download-localsettings' => 'Ladda ned <code>LocalSettings.php</code>',
'config-help' => 'hjälp',
+ 'config-nofile' => 'Filen "$1" kunde inte hittas. Har den tagits bort?',
'mainpagetext' => "'''MediaWiki har installerats utan problem.'''",
'mainpagedocfooter' => 'Information om hur wiki-programvaran används finns i [//meta.wikimedia.org/wiki/Help:Contents användarguiden].
== Att komma igång ==
-
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista över konfigurationsinställningar]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mail list]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mail list]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Lokalisera MediaWiki för ditt språk]',
);
/** Swahili (Kiswahili)
@@ -17193,7 +18357,7 @@ $messages['ta'] = array(
'config-optional-continue' => 'என்னை இன்னும் அதிகமாக வினவு.',
'config-optional-skip' => 'நான் ஏற்கனவே சோர்வடைந்துள்ளேன், விக்கியை மட்டும் உருவாக்கு.',
'config-profile' => 'பயனர் உரிமைகள் சுயவிவரம்:',
- 'config-profile-wiki' => 'பாரம்பரிய விக்கி',
+ 'config-profile-wiki' => 'பாரம்பரிய விக்கி', # Fuzzy
'config-profile-no-anon' => 'கணக்கு உருவாக்குதல் அவசியம்',
'config-profile-private' => 'தனியார் விக்கி',
'config-license' => 'பதிப்புரிமை மற்றும் உரிமம்:',
@@ -17214,7 +18378,7 @@ $messages['ta'] = array(
'config-install-tables' => 'வரிசைப் பட்டியல்களை உருவாக்குகிறது',
'config-install-mainpage' => 'இயல்புநிலை உள்ளடக்கத்துடன் முதற்பக்கத்தை உருவாக்குகிறது',
'config-install-extension-tables' => 'செயற்படுத்தப்பட்ட நீட்சிகளுக்கு வரிசைப் பட்டியல்களை உருவாக்குகிறது',
- 'config-download-localsettings' => 'LocalSettings.phpஐத் தரவிறக்கவும்',
+ 'config-download-localsettings' => '<code>LocalSettings.php</code>ஐத் தரவிறக்கவும்',
'config-help' => 'உதவி',
'mainpagetext' => "'''விக்கி மென்பொருள் வெற்றிகரமாக உள்ளிடப்பட்டது.'''",
'mainpagedocfooter' => 'விக்கி மென்பொருளைப் பயன்படுத்துவது தொடர்பாக [//meta.wikimedia.org/wiki/Help:Contents பயனர் வழிகாட்டியைப்] பார்க்க.
@@ -17223,7 +18387,7 @@ $messages['ta'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings அமைப்புக்களை மாற்றம் செய்தல்]
* [//www.mediawiki.org/wiki/Manual:FAQ மிடியாவிக்கி பொதுவான கேள்விகள்]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce மீடியாவிக்கி வெளியீடு மின்னஞ்சல் பட்டியல்]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce மீடியாவிக்கி வெளியீடு மின்னஞ்சல் பட்டியல்]', # Fuzzy
);
/** Tulu (ತುಳು)
@@ -17368,6 +18532,7 @@ $messages['tk'] = array(
/** Tagalog (Tagalog)
* @author AnakngAraw
* @author Sky Harbor
+ * @author 아라
*/
$messages['tl'] = array(
'config-desc' => 'Ang tagapagluklok para sa MediaWiki',
@@ -17375,20 +18540,20 @@ $messages['tl'] = array(
'config-information' => 'Kabatiran',
'config-localsettings-upgrade' => 'Napansin ang isang talaksang <code>LocalSettings.php</code>.
Upang maitaas ang uri ng pagluluklok na ito, paki ipasok ang halaga ng <code>$wgUpgradeKey</code> sa loob ng kahong nasa ibaba.
-Matatagpuan mo ito sa loob ng LocalSettings.php.',
- 'config-localsettings-cli-upgrade' => 'Napansin ang isang talaksan ng LocalSettings.php.
-Upang isapanahon ang pagtatalagang ito, mangyaring patakbuhin sa halip ang update.php',
+Matatagpuan mo ito sa loob ng <code>LocalSettings.php</code>.',
+ 'config-localsettings-cli-upgrade' => 'Napansin ang isang talaksan ng <code>LocalSettings.php</code>.
+Upang isapanahon ang pagtatalagang ito, mangyaring patakbuhin sa halip ang <code>update.php</code>',
'config-localsettings-key' => 'Susi ng pagsasapanahon:',
'config-localsettings-badkey' => 'Hindi tama ang susing ibinigay mo.',
'config-upgrade-key-missing' => 'Napansin ang isang umiiral na pagtatalaga ng MediaWiki.
-Upang isapanahon ang katalagahang ito, mangyaring ilagay ang sumusunod na guhit sa ilalim ng iyong LocalSettings.php:
+Upang isapanahon ang katalagahang ito, mangyaring ilagay ang sumusunod na guhit sa ilalim ng iyong <code>LocalSettings.php</code>:
$1',
- 'config-localsettings-incomplete' => 'Lumilitaw na hindi pa buo ang umiiral na LocalSettings.php.
+ 'config-localsettings-incomplete' => 'Lumilitaw na hindi pa buo ang umiiral na <code>LocalSettings.php</code>.
Ang pabagu-bagong $1 ay hindi nakatakda.
-Mangyaring baguhin ang LocalSettings.php upang ang maitakda ang pagpapabagu-bagong ito, at pindutin ang "Magpatuloy".',
- 'config-localsettings-connection-error' => 'Isang kamalian ang nakatagpo noong kumakabit sa kalipunan ng dato na ginagamit ang tinukoy na mga katakdaan sa loob ng LocalSettings.php o
-AdminSettings.php. Paki kumpunihin ang mga katakdaang ito at subukang muli.
+Mangyaring baguhin ang <code>LocalSettings.php</code> upang ang maitakda ang pagpapabagu-bagong ito, at pindutin ang "{{int:Config-continue}}".',
+ 'config-localsettings-connection-error' => 'Isang kamalian ang nakatagpo noong kumakabit sa kalipunan ng dato na ginagamit ang tinukoy na mga katakdaan sa loob ng <code>LocalSettings.php</code> o
+<code>AdminSettings.php</code>. Paki kumpunihin ang mga katakdaang ito at subukang muli.
$1',
'config-session-error' => 'Kamalian sa pagsisimula ng sesyon: $1',
@@ -17517,7 +18682,7 @@ Pinigilan ang pag-iinstala.",
'config-brokenlibxml' => "Ang sistema mo ay mayroong isang pagsasama ng mga bersiyon ng PHP at libxml2 na maaaring masurot at maaaring makapagsanhi ng pagkasira ng datong nakakubli sa loob ng MediaWiki at iba pang mga aplikasyon ng sangkasaputan.
Magtaas ng uri upang maging PHP 5.2.9 o mas lalong huli at libxml2 2.7.3 o mas lalong huli ([//bugs.php.net/bug.php?id=45996 isinalansan ang surot o ''bug'' na mayroong PHP]). Binigo ang pagluluklok.",
'config-using531' => 'Hindi maaaring gamitin ang MediaWiki na kapiling ang PHP na $1 dahil sa isang surot na kinasasangkutan ng mga parametrong pangsangguni sa <code>__call()</code>. Magtaas ng uri upang maging PHP 5.3.2 o mas mataas, o magbaba ng uri upang maging PHP 5.3.0 upang malutas ito. Binigo ang pagluluklok.',
- 'config-suhosin-max-value-length' => 'Nakaluklok ang Suhosin at hinahanggahan ang haba ng parametro ng GET sa $1 mga byte. Ang sangkap na ResourceLoader ng MediaWiki ay gagana sa paligid ng hangganang ito, subalit pasasamain nito ang pagganap. Kung talagang maaari, dapat mong itakda ang suhosin.get.max_value_length upang maging 1024 o mas mataas sa loob ng php.ini, at itakda ang $wgResourceLoaderMaxQueryLength sa katulad na halaga sa loob ng LocalSettings.php.',
+ 'config-suhosin-max-value-length' => 'Nakaluklok ang Suhosin at hinahanggahan ang haba ng parametro ng GET sa $1 mga byte. Ang sangkap na ResourceLoader ng MediaWiki ay gagana sa paligid ng hangganang ito, subalit pasasamain nito ang pagganap. Kung talagang maaari, dapat mong itakda ang <code>suhosin.get.max_value_length</code> upang maging 1024 o mas mataas sa loob ng <code>php.ini</code>, at itakda ang <code>$wgResourceLoaderMaxQueryLength</code> sa katulad na halaga sa loob ng LocalSettings.php.', # Fuzzy
'config-db-type' => 'Uri ng kalipunan ng datos:',
'config-db-host' => 'Tagapagpasinaya ng kalipunan ng datos:',
'config-db-host-help' => 'Kung ang iyong tagapaghain ng kalipunan ng dato ay nasa ibabaw ng isang ibang tagapaghain, ipasok ang pangalan ng tagapagpasinaya o tirahan ng IP dito.
@@ -17602,7 +18767,7 @@ Kung hindi mo makita ang sistema ng kalipunan ng dato na sinusubukan mong gamiti
'config-support-postgres' => '* Ang $1 ay isang bantog na sistema ng kalipunan ng dato na bukas ang pinagmulan na panghalili sa MySQL ([http://www.php.net/manual/en/pgsql.installation.php paano magtipon ng PHP na mayroong suporta ng PostgreSQL]). Maaaring mayroong ilang hindi pangunahing mga surot na natitira pa, at hindi iminumungkahi para gamitin sa loob ng isang kapaligiran ng produksiyon.',
'config-support-sqlite' => 'Ang $1 ay isang magaan ang timbang na sistema ng kalipunan ng dato na sinusuportahan nang napaka mainam. ([http://www.php.net/manual/en/pdo.installation.php Paano magtipon ng PHP na mayroong suporta ng SQLite], gumagamit ng PDO)',
'config-support-oracle' => '* Ang $1 ay isang kalipunan ng dato ng kasigasigang pangkalakal. ([http://www.php.net/manual/en/oci8.installation.php Paano magtipunan ng PHP na mayroong suporta ng OCI8])',
- 'config-support-ibm_db2' => '* Ang $1 ay isang kalipunan ng dato ng kasigasigang pangkalakal.',
+ 'config-support-ibm_db2' => '* Ang $1 ay isang kalipunan ng dato ng kasigasigang pangkalakal.', # Fuzzy
'config-header-mysql' => 'Mga katakdaan ng MySQL',
'config-header-postgres' => 'Mga katakdaan ng PostgreSQL',
'config-header-sqlite' => 'Mga katakdaan ng SQLite',
@@ -17669,8 +18834,8 @@ Kung nais mong muling likhain ang iyong talaksang <code>LocalSettings.php</code>
'config-upgrade-done-no-regenerate' => 'Buo na ang pagsasapanahon.
Maaari ka na ngayong [$1 magsimula sa paggamit ng wiki mo].',
- 'config-regenerate' => 'Muling likhain ang LocalSettings.php →',
- 'config-show-table-status' => 'Nabigo ang pagtatanong na IPAKITA ANG KALAGAYAN NG TALAHANAYAN!',
+ 'config-regenerate' => 'Muling likhain ang <code>LocalSettings.php</code> →',
+ 'config-show-table-status' => 'Nabigo ang pagtatanong na IPAKITA ANG KALAGAYAN NG TALAHANAYAN!', # Fuzzy
'config-unknown-collation' => "'''Babala:''' Ang kalipunan ng dato ay gumagagamit ng hindi nakikilalang pag-iipon.",
'config-db-web-account' => 'Akawnt ng kalipunan ng dato para sa pagpunta sa web',
'config-db-web-help' => 'Piliin ang pangalan ng tagagamit at hudyat na gagamitin ng tagapaghain ng web upang umugnay sa tagapaghain ng kalipunan ng dato, habang nasa pangkaraniwang pagtakbo ng wiki.',
@@ -17740,7 +18905,7 @@ Maaari mo ngayong laktawan ang natitira pang pag-aayos at iluklok na ang wiki ng
'config-optional-continue' => 'Magtanong sa akin ng marami pang mga tanong.',
'config-optional-skip' => 'Naiinip na ako, basta iluklok na lang ang wiki.',
'config-profile' => 'Balangkas ng mga karapatan ng tagagamit:',
- 'config-profile-wiki' => 'Tradisyonal na wiki',
+ 'config-profile-wiki' => 'Tradisyonal na wiki', # Fuzzy
'config-profile-no-anon' => 'Kailangan ang paglikha ng akawnt',
'config-profile-fishbowl' => 'Pinahintulutang mga patnugot lamang',
'config-profile-private' => 'Pribadong wiki',
@@ -17756,7 +18921,7 @@ Ang isang wiki na mayroong '''{{int:config-profile-no-anon}}''' ay nagbibigay ng
Ang tagpo na '''{{int:config-profile-fishbowl}}''' ay nagpapahintulot lamang sa pinayagang mga tagagamit na makatingin ng mga pahina, na kapiling ang pangkat na pinayagang makapamatnugot.
Ang isang '''{{int:config-profile-private}}''' ay nagpapahintulot lamang sa pinayagang mga tagagamit na makatingin ng mga pahina, na kapiling ang pangkat na pinayagang makapamatnugot.
-Ang mas masasalimuot na mga kaayusan ng mga karapatan ng tagagamit ay makukuha pagkaraan ng pagluluklok, tingnan ang [//www.mediawiki.org/wiki/Manual:User_rights may kaugnayang kinamay na lahok].",
+Ang mas masasalimuot na mga kaayusan ng mga karapatan ng tagagamit ay makukuha pagkaraan ng pagluluklok, tingnan ang [//www.mediawiki.org/wiki/Manual:User_rights may kaugnayang kinamay na lahok].", # Fuzzy
'config-license' => 'Karapatang-ari at lisensiya:',
'config-license-none' => 'Walang talababa ng lisensiya',
'config-license-cc-by-sa' => 'Malikhaing Pangkaraniwang Pagtukoy Pamamahaging Magkatulad',
@@ -17841,7 +19006,7 @@ Maaaring mangailangan ang mga ito ng karagdagang kaayusan, subalit mapapagana mo
'config-install-alreadydone' => "'''Babala:''' Tila nailuklok mo na ang MediaWiki at tinatangka mong iluklok ito ulit.
Paki magpatuloy sa susunod na pahina.",
'config-install-begin' => 'Sa pamamagitan ng pagpindot sa "{{int:config-continue}}", sisimulan mo ang pagluluklok ng MediaWiki.
-Kung nais mo paring gumawa ng mga pagbabago, paki pindutin ang bumalik.',
+Kung nais mo paring gumawa ng mga pagbabago, paki pindutin ang bumalik.', # Fuzzy
'config-install-step-done' => 'nagawa na',
'config-install-step-failed' => 'nabigo',
'config-install-extensions' => 'Isinasama ang mga karugtong',
@@ -17897,7 +19062,7 @@ $3
'''Paunawa''': Kapag hindi mo ito ginawa ngayon, ang nagawang talaksang ito ng pagkakaayos ay hindi mo na makukuha mamaya kapag lumabas ka mula sa pagluluklok na hindi ikinakarga itong paibaba.
Kapag nagawa na iyan, maaari ka nang '''[$2 pumasok sa wiki mo]'''.",
- 'config-download-localsettings' => 'Ikargang paibaba ang LocalSettings.php',
+ 'config-download-localsettings' => 'Ikargang paibaba ang <code>LocalSettings.php</code>',
'config-help' => 'saklolo',
'config-nofile' => 'Hindi matagpuan ang talaksang "$1". Binura na ba ito?',
'mainpagetext' => "'''Matagumpay na ininstala ang MediaWiki.'''",
@@ -17907,7 +19072,7 @@ Kapag nagawa na iyan, maaari ka nang '''[$2 pumasok sa wiki mo]'''.",
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Tala ng mga nakatakdang kumpigurasyon]
* [//www.mediawiki.org/wiki/Manual:FAQ Mga malimit itanong sa MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Tala ng mga pinadadalhan ng liham ng MediaWiki]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Tala ng mga pinadadalhan ng liham ng MediaWiki]", # Fuzzy
);
/** толышә зывон (толышә зывон)
@@ -17979,8 +19144,11 @@ $messages['ug-arab'] = array(
* @author AS
* @author Ahonc
* @author Alex Khimich
+ * @author Base
* @author Diemon.ukr
+ * @author Ата
* @author Тест
+ * @author 아라
*/
$messages['uk'] = array(
'config-desc' => 'Інсталятор MediaWiki',
@@ -17989,14 +19157,26 @@ $messages['uk'] = array(
'config-localsettings-upgrade' => "'''Увага''': було виявлено файл <code>LocalSettings.php</code>.
Ваше програмне забезпечення може бути оновлено.
Будь-ласка, перемістіть файл <code>LocalSettings.php</code> в іншу безпечну директорію, а потім знову запустіть програму установки.",
- 'config-localsettings-cli-upgrade' => 'Виявлено файл LocalSettings.php.
-Щоб оновити наявну установку, запустіть update.php',
+ 'config-localsettings-cli-upgrade' => 'Виявлено файл <code>LocalSettings.php</code>.
+Щоб оновити наявну установку, запустіть <code>update.php</code>',
'config-localsettings-key' => 'Ключ оновлення:',
'config-localsettings-badkey' => 'Ви вказали неправильний ключ.',
'config-upgrade-key-missing' => 'Виявлено наявну установку MediaWiki.
-Для оновлення цієї установки, будь ласка, вставте такий рядок в кінець вашого LocalSettings.php:
+Для оновлення цієї установки, будь ласка, вставте такий рядок в кінець вашого <code>LocalSettings.php</code>:
+$1',
+ 'config-localsettings-incomplete' => 'Існуючий файл <code>LocalSettings.php</code> виявився неповним.
+Не вказано змінну $1.
+Будь ласка, змініть <code>LocalSettings.php</code> так, щоб цю змінну було задано, і натисніть "{{int:Config-continue}}".',
+ 'config-localsettings-connection-error' => 'Сталася помилка при підключення до бази даних з допомогою налаштувань на сторінці <code>LocalSettings.php</code> чи <code>AdminSettings.php</code>. Будь ласка, виплавте ці налаштування і спробуйте знову.
+
$1',
'config-session-error' => 'Помилка початку сесії: $1',
+ 'config-session-expired' => 'Час Вашої сесії минув.
+Задана тривалість сесії — $1.
+Ви можете збільшити її, змінивши <code>session.gc_maxlifetime</code> у php.ini.
+Перезапустіть процес встановлення.',
+ 'config-no-session' => 'Дані сесії було втрачено!
+Перевірте Ваш php.ini і переконайтесь, що <code>session.save_path</code> встановлено у відповідну папку.',
'config-your-language' => 'Ваша мова:',
'config-your-language-help' => 'Оберіть мову для використання в процесі установки.',
'config-wiki-language' => 'Мова для вікі:',
@@ -18017,73 +19197,491 @@ $1',
'config-page-releasenotes' => 'Інформація про версію',
'config-page-copying' => 'Копіювання',
'config-page-upgradedoc' => 'Оновлення',
+ 'config-page-existingwiki' => 'Існуюча вікі',
'config-help-restart' => 'Ви бажаєте видалити всі введені та збережені вами дані і запустити процес установки спочатку?',
'config-restart' => 'Так, перезапустити установку',
'config-welcome' => '=== Перевірка оточення ===
Проводяться базові перевірки, щоб виявити, чи можлива установка MediaWiki у даній системі.
Вкажіть результати цих перевірок при зверненні за допомогою під час установки.',
+ 'config-copyright' => "=== Авторське право і умови ===
+
+$1
+
+Ця програма є вільним програмним забезпеченням; Ви можете розповсюджувати та/або змінювати її під ліцензією GNU General Public License, опублікованою Фондом вільного програмного забезпечення; версією 2 цієї ліцензії або будь-якою пізнішою на Ваш вибір.
+
+Ця програма поширюється з надією на те, що вона буде корисною, однак '''без жодних гарантій'''; навіть без неявної гарантії '''комерційної цінності''' або '''придатності для певних цілей'''.
+Див. GNU General Public License для детальної інформації.
+
+Ви повинні були отримати <doclink href=Copying>копію GNU General Public License</doclink> разом із цією програмою; якщо ж ні, зверніться до Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. або [http://www.gnu.org/copyleft/gpl.html ознайомтесь з нею онлайн].",
'config-sidebar' => '* [//www.mediawiki.org Сайт MediaWiki]
-* [//www.mediawiki.org/wiki/Help:Contents/uk Керівництво користувача]
-* [//www.mediawiki.org/wiki/Manual:Contents/uk Керівництво адміністратора]
-* [//www.mediawiki.org/wiki/Manual:FAQ/uk FAQ]', # Fuzzy
+* [//www.mediawiki.org/wiki/Help:Contents Посібник користувача]
+* [//www.mediawiki.org/wiki/Manual:Contents Посібник адміністратора]
+* [//www.mediawiki.org/wiki/Manual:FAQ FAQ]
+----
+* <doclink href=Readme>Read me</doclink>
+* <doclink href=ReleaseNotes>Інформація про випуск</doclink>
+* <doclink href=Copying>Ліцензія</doclink>
+* <doclink href=UpgradeDoc>Оновлення</doclink>',
'config-env-good' => 'Перевірку середовища успішно завершено.
Ви можете встановити MediaWiki.',
'config-env-bad' => 'Було проведено перевірку середовища. Ви не можете встановити MediaWiki.',
'config-env-php' => 'Встановлено версію PHP: $1.',
+ 'config-env-php-toolow' => 'Встановлено PHP $1.
+Натомість MediaWiki вимагає PHP $2 і вище.',
'config-unicode-using-utf8' => 'Використовувати utf8_normalize.so Брайона Віббера для нормалізації Юнікоду.',
'config-unicode-using-intl' => 'Використовувати [http://pecl.php.net/intl міжнародне розширення PECL] для нормалізації Юнікоду.',
'config-unicode-pure-php-warning' => "'''Увага''': [http://pecl.php.net/intl міжнародне розширення PECL] не може провести нормалізацію Юнікоду.
Якщо ваш сайт має високий трафік, вам варто почитати про [//www.mediawiki.org/wiki/Unicode_normalization_considerations нормалізацію Юнікоду].",
+ 'config-unicode-update-warning' => "'''Увага''': Встановлена версія обгортки нормалізації Юнікоду використовує стару версію бібліотеки [http://site.icu-project.org/ проекту ICU].
+Ви маєте [//www.mediawiki.org/wiki/Unicode_normalization_considerations оновити версію], якщо плануєте повноцінно використовувати Юнікод.",
'config-no-db' => 'Не вдалося знайти відповідний драйвер бази даних! Вам необхідно встановити драйвер бази даних для PHP. Підтримуються такі типи баз даних: $1.
Якщо ви користуєтесь віртуальним хостингом, попросіть вашого хостинг-провайдера інсталювати відповідний драйвер бази даних.
Якщо ви скомпілювали PHP самостійно, переналаштуйте його з включенням клієнта бази даних, наприклад за допомогою <code>./configure --with-mysql</code>.
Якщо установлено PHP з пакетів Debian або Ubuntu, тоді ви також повинні встановити php5-mysql модуль.',
+ 'config-outdated-sqlite' => "'''Увага''': у Вас встановлена версія SQLite $1, а це нижче, ніж мінімально необхідна версія $2. SQLite буде недоступним.",
+ 'config-no-fts3' => "'''Увага''': SQLite зібраний без [//sqlite.org/fts3.html модуля FTS3], функції пошуку не будуть працювати у цій системі.",
+ 'config-register-globals' => "'''Увага: Опція PHP <code>[http://php.net/register_globals register_globals]</code> увімкнена.'''
+'''Вимкніть її, якщо це можливо.'''
+MediaWiki буде працювати, але Ваш сервер буде більш вразливим до потенційного проникнення зовні.",
+ 'config-magic-quotes-runtime' => "'''Проблема: Опція PHP [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] увімкнена!'''
+Ця опція призводить до непередбачуваного пошкодження даних.
+Ви не можете встановити і використовувати MediaWiki, поки не буде вимкнено цю опцію.",
+ 'config-magic-quotes-sybase' => "'''Проблема: Опція PHP [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] увімкнена!'''
+Ця опція призводить до непередбачуваного пошкодження даних.
+Ви не можете встановити і використовувати MediaWiki, поки не буде вимкнено цю опцію.",
+ 'config-mbstring' => "'''Проблема: Опція PHP [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] увімкнена!'''
+Ця опція призводить до непередбачуваного пошкодження даних.
+Ви не можете встановити і використовувати MediaWiki, поки не буде вимкнено цю опцію.",
+ 'config-ze1' => "'''Проблема: Опція PHP [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] увімкнена!'''
+Ця опція призводить до непередбачуваного пошкодження даних.
+Ви не можете встановити і використовувати MediaWiki, поки не буде вимкнено цю опцію.",
+ 'config-safe-mode' => "'''Увага:''' Опція PHP [http://www.php.net/features.safe-mode «безпечний режим»] увімкнена.
+Це може спричинити проблеми, зокрема із завантаженням файлів та вставкою математичних формул.",
+ 'config-xml-bad' => 'XML-модуть PHP відсутній.
+MediaWiki необхідні його функції, без цього модуля вона працювати не буде.
+Якщо Ви використовуєте Mandrake, встановіть php-xml пакет.',
+ 'config-pcre' => 'Модуть підтримку PCRE не знайдено.
+Для роботи MediaWiki необхідна підтримка Perl-сумісних регулярних виразів.',
+ 'config-pcre-no-utf8' => "'''Помилка''': PCRE-модуть PHP, вочевидь, було зібрано без підтримки PCRE_UTF8.
+MediaWiki вимагає підтримку UTF-8 для коректної роботи.",
+ 'config-memory-raised' => "Обмеження пам'яті PHP (<code>memory_limit</code>) $1, піднято до $2.",
+ 'config-memory-bad' => "'''Увага:''' Розмір пам'яті PHP (<code>memory_limit</code>) становить $1.
+Імовірно, це замало.
+Встановлення може не вдатись!",
+ 'config-ctype' => "'''Помилка''': PHP має бути зібраним з підтримкою [http://www.php.net/manual/en/ctype.installation.php розширення Ctype].",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache] встановлено',
'config-apc' => '[http://www.php.net/apc APC] встановлено',
'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] встановлено',
+ 'config-no-cache' => "'''Увага:''' Не вдалося знайти [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] чи [http://www.iis.net/download/WinCacheForPhp WinCache].
+Кешування об'єктів не ввімкнено.",
+ 'config-mod-security' => "'''Увага''': на Вашому веб-сервері увімкнено [http://modsecurity.org/ mod_security]. У разі неправильних налаштувать, він може викликати проблеми MediaWiki або іншого ПЗ, яке дозволяє користувачам надсилати довільний вміст.
+Зверніться до [http://modsecurity.org/documentation/ документації mod_security] або підтримки Вашого хостера, якщо під час роботи виникають незрозумілі помилки.",
+ 'config-diff3-bad' => 'GNU diff3 не знайдено.',
+ 'config-imagemagick' => 'Виявлено ImageMagick: <code>$1</code>.
+Буде ввімкнуто відображення мініатюр, якщо ви дозволите завантаження файлів.',
+ 'config-gd' => 'Виявлено вбудовано графічну бібліотеку GD.
+Буде ввімкнуто відображення мініатюр, якщо ви дозволите завантаження файлів.',
+ 'config-no-scaling' => 'Не вдалося виявити бібліотеку GD чи ImageMagick.
+Відображення мініатюр буде вимкнено.',
+ 'config-no-uri' => "'''Помилка:''' Не вдалося визначити поточний URI.
+Встановлення перервано.",
+ 'config-no-cli-uri' => "'''Увага''': Не задано параметр --scriptpath, використовується за замовчуванням: <code>$1</code>.",
+ 'config-using-server' => 'Використовується ім\'я сервера "<nowiki>$1</nowiki>".',
+ 'config-using-uri' => 'Використовується URL сервера "<nowiki>$1$2</nowiki>".',
+ 'config-uploads-not-safe' => "'''Увага:''' Ваша типова папка для завантажень <code>$1</code> вразлива до виконання довільних скриптів.
+Хоча MediaWiki перевіряє усі завантажені файли на наявність загроз, наполегливо рекомендується [//www.mediawiki.org/wiki/Manual:Security#Upload_security закрити дану вразливість] перед тим, як дозволяти завантаження файлів.",
+ 'config-no-cli-uploads-check' => "'''Увага:''' Ваша типова папка для завантажень (<code>$1</code>) не перевірялась на вразливість до виконання довільних скриптів під час встановлення CLI.",
+ 'config-brokenlibxml' => 'У Вашій системі невдале поєднання версій PHP і libxml2, яке може спричинити пошкодження прихованих даних у MediaWiki та інших веб-застосунках.
+Оновіть PHP до версії 5.2.9 або пізнішої і libxml2 до 2.7.3 або пізнішої ([//bugs.php.net/bug.php?id=45996 відомості про помилку]).
+Встановлення перервано.',
+ 'config-using531' => 'MediaWiki не можна використовувати разом з PHP $1 через помилку з параметрами-посиланнями <code>__call()</code>.
+Оновіть PHP до версії 5.3.2 і вище або відкотіть до PHP 5.3.0 щоб уникнути цієї проблеми.
+Встановлення скасовано.',
+ 'config-suhosin-max-value-length' => 'Suhosin встановлено і обмежує довжину параметра GET до $1 байтів. Компонент MediaWiki ResourceLoader буде обходити це обмеження, однак це зменшить продуктивність. Якщо це можливо, Вам варто встановити значення <code>suhosin.get.max_value_length</code> 1024 і більше у <code>php.ini</code> і встановити таке ж значення <code>$wgResourceLoaderMaxQueryLength</code> у LocalSettings.php .', # Fuzzy
'config-db-type' => 'Тип бази даних:',
'config-db-host' => 'Хост бази даних:',
+ 'config-db-host-help' => 'Якщо сервер бази даних знаходиться на іншому сервері, введіть тут ім\'я хосту і IP адресу.
+
+Якщо Ви використовуєте віртуальний хостинг, Ваш хостинг-провайдер має надати Вам правильне ім\'я хосту у його документації.
+
+Якщо у Вас сервер із Windows Ви використовуєте MySQL, параметр "localhost" може не працювати для імені сервера. Якщо не працює, використайте "127.0.0.1" як локальну IP-адресу.
+
+Якщо Ви використовуєте PostgreSQL, залиште це поле пустим, щоб під\'єднатись через сокет Unix.',
+ 'config-db-host-oracle' => 'TNS бази даних:',
+ 'config-db-host-oracle-help' => 'Введіть допустиме [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; файл tnsnames.ora має бути видимим для цієї інсталяції. <br />Якщо Ви використовуєте бібліотеки 10g чи новіші, можна також використовувати метод іменування [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
+ 'config-db-wiki-settings' => 'Ідентифікувати цю вікі',
'config-db-name' => 'Назва бази даних:',
+ 'config-db-name-help' => 'Виберіть назву, що ідентифікує Вашу вікі.
+Вона не повинна містити пробілів.
+
+Якщо Ви використовуєте віртуальний хостинг, Ваш хостинг-провайдер або надасть Вам конкретну назву бази даних, або дозволить створювати бази даних з допомогою панелі управління.',
+ 'config-db-name-oracle' => 'Схема бази даних:',
+ 'config-db-account-oracle-warn' => 'Є три підтримувані сценарії установки Oracle:
+
+Якщо Ви хочете створити обліковий запис бази даних у процесі встановлення, будь ласка, вкажіть обліковий запис ролі SYSDBA для установки і бажані повноваження для облікового запису з веб-доступом. В протилежному випадку Ви можете або створити обліковий запис з веб-доступом вручну і вказати тільки цей обліковий запис (якщо він має необхідні дозволи на створення об\'єктів-схем), або вказати два різні облікові записи, з яких в одного будуть права на створення, а в другого, обмеженого — права веб-доступу.
+
+Скрипт для створення облікового запису з необхідними повноваженнями можна знайти у папці "maintenance/oracle/" цієї інсталяції. Майте на увазі, що використання обмеженого облікового запису вимкне можливість використання технічного обслуговування з облікового запису за замовчуванням.',
+ 'config-db-install-account' => 'Обліковий запис користувача для встановлення',
+ 'config-db-username' => "Ім'я користувача бази даних:",
'config-db-password' => 'Пароль бази даних:',
+ 'config-db-password-empty' => 'Будь ласка, введіть пароль для нового користувача бази даних: $1.
+Хоча можна створювати користувачів без паролів, це не є безпечним.',
+ 'config-db-install-username' => "Введіть ім'я користувача, яке буде використано для підключення до бази даних під час процесу встановлення.
+Це не ім'я користувача облікового запису MediaWiki; це ім'я користувача для Вашої бази даних.",
+ 'config-db-install-password' => 'Введіть пароль, який буде використано для підключення до бази даних під час процесу встановлення.
+Це не пароль облікового запису MediaWiki; це пароль для Вашої бази даних.',
+ 'config-db-install-help' => "Введіть ім'я користувача і пароль, які буде використано для підключення до бази даних у процесі встановлення.",
+ 'config-db-account-lock' => "Використовувати ті ж ім'я користувача і пароль і для звичайної роботи",
+ 'config-db-wiki-account' => 'Обліковий запис користувача для звичайної роботи',
+ 'config-db-wiki-help' => "Введіть ім'я користувача і пароль, які будуть використовуватись для з'єднання з базою даних під час звичайної роботи.
+Якщо обліковий запис не існує, а в облікового запису інсталяції є достатні повноваження, цей обліковий запис користувача буде створено з мінімальними правами, що необхідні для роботи з вікі.",
+ 'config-db-prefix' => 'Префікс таблиць бази даних:',
+ 'config-db-prefix-help' => 'Якщо треба ділити одну базу даних між декількома вікі або між MediaWiki та іншим веб-застосунком, Ви можете додати префікс до усіх назв таблиць для уникнення конфліктів.
+Не використовуйте пробіли.
+
+Це поле зазвичай залишають пустим.',
'config-db-charset' => 'Кодування бази даних',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binary',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 зворотно сумісна з UTF-8',
+ 'config-charset-help' => "'''Увага:''' Якщо Ви використовуєте '''зворотно сумісну UTF-8''' на MySQL 4.1+ і створюєте резервні копії бази даних з допомогою <code>mysqldump</code>, це може викривити усі не-ASCII символи, незворотно пошкодивши резервні копії!
+
+У '''бінарному режимі''' MediaWiki зберігає текст UTF-8 у базі даних з бінарними полями.
+Це більш ефективно, ніж UTF-8 режим MySQL, і дозволяє використовувати увесь набір символів Юнікоду.
+У '''режимі UTF-8''' MySQL буде знати, якого символу стосуються Ваші дані, і могтиме відображати та конвертувати їх належним чином,
+але не дозволятиме зберігати символи, що виходять за межі [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ 'config-mysql-old' => 'Необхідна MySQL $1 або пізніша, а у Вас $2.',
'config-db-port' => 'Порт бази даних:',
+ 'config-db-schema' => 'Схема для MediaWiki',
+ 'config-db-schema-help' => 'Ця схема зазвичай працює добре.
+Змінюйте її тільки якщо знаєте, що Вам це потрібно.',
+ 'config-pg-test-error' => "Не вдається підключитися до бази даних '''$1''': $2",
+ 'config-sqlite-dir' => 'Папка даних SQLite:',
+ 'config-sqlite-dir-help' => "SQLite зберігає усі дані в єдиному файлі.
+
+Папка, яку Ви вказуєте, має бути доступна серверу для запису під час встановлення.
+
+Вона '''не''' повинна бути доступна через інтернет, тому ми і не поміщуємо її туди, де Ваші файли PHP.
+
+Інсталятор пропише у неї файл <code>.htaccess</code>, але якщо це не спрацює, хтось може отримати доступ до Вашої вихідної бази даних, яка містить вихідні дані користувача (адреси електронної пошти, хеші паролів), а також видалені версії та інші обмежені дані на вікі.
+
+За можливості розташуйте базу даних десь окремо, наприклад в <code>/var/lib/mediawiki/yourwiki</code>.",
+ 'config-oracle-def-ts' => 'Простір таблиць за замовчуванням:',
+ 'config-oracle-temp-ts' => 'Тимчасовий простір таблиць:',
+ 'config-type-ibm_db2' => 'IBM DB2',
+ 'config-support-info' => 'MediaWiki підтримує таки системи баз даних:
+
+$1
+
+Якщо Ви не бачите серед перерахованих систему баз даних, яку використовуєте, виконайте вказівки, вказані вище, щоб увімкнути підтримку.',
+ 'config-support-mysql' => '* $1 є основною для MediaWiki і найкраще підтримується ([http://www.php.net/manual/en/mysql.installation.php як зібрати PHP з допомогою MySQL])',
+ 'config-support-postgres' => '* $1 — популярна відкрита СУБД, альтернатива MySQL ([http://www.php.net/manual/en/pgsql.installation.php як зібрати PHP з допомогою PostgreSQL]). Можуть зустрічатись деякі невеликі невиправлені помилки, не рекомендується використовувати у робочій системі.',
+ 'config-support-sqlite' => '* $1 — легка система баз даних, яка дуже добре підтримується. ([http://www.php.net/manual/en/pdo.installation.php Як зібрати PHP з допомогою SQLite], що використовує PDO)',
+ 'config-support-oracle' => '* $1 — комерційна база даних масштабу підприємства. ([http://www.php.net/manual/en/oci8.installation.php Як зібрати PHP з підтримкою OCI8])',
+ 'config-support-ibm_db2' => '* $1 — комерційна база даних масштабу підприємства.', # Fuzzy
+ 'config-header-mysql' => 'Налаштування MySQL',
+ 'config-header-postgres' => 'Налаштування PostgreSQL',
+ 'config-header-sqlite' => 'Налаштування SQLite',
+ 'config-header-oracle' => 'Налаштування Oracle',
+ 'config-header-ibm_db2' => 'Налаштування IBM DB2',
'config-invalid-db-type' => 'Невірний тип бази даних',
+ 'config-missing-db-name' => "Ви повинні ввести значення параметру «Ім'я бази даних»",
+ 'config-missing-db-host' => 'Ви повинні ввести значення параметру «Хост бази даних»',
+ 'config-missing-db-server-oracle' => 'Ви повинні ввести значення параметру «TNS бази даних»',
+ 'config-invalid-db-server-oracle' => 'Неприпустиме TNS бази даних "$1".
+Використовуйте тільки ASCII букви (a-z, A-Z), цифри (0-9), знаки підкреслення (_) і крапки (.).',
'config-invalid-db-name' => 'Неприпустима назва бази даних "$1".
Використовуйте тільки ASCII букви (a-z, A-Z), цифри (0-9), знаки підкреслення (_) і дефіси (-).',
'config-invalid-db-prefix' => 'Неприпустимий префікс бази даних "$1".
Використовуйте тільки ASCII букви (a-z, A-Z), цифри (0-9), знаки підкреслення (_) і дефіси (-).',
+ 'config-connection-error' => "$1.
+
+Перевірте хост, ім'я користувача та пароль і спробуйте ще раз.",
+ 'config-invalid-schema' => 'Неприпустима схема для MediaWiki "$1".
+Використовуйте тільки ASCII букви (a-z, A-Z), цифри (0-9) і знаки підкреслення(_).',
+ 'config-db-sys-create-oracle' => 'Інсталятор підтримує лише використання облікового запису SYSDBA для створення нового облікового запису.',
+ 'config-db-sys-user-exists-oracle' => 'Обліковий запис користувача "$1" уже існує. SYSDBA використовується лише для створення новий облікових записів!',
+ 'config-postgres-old' => 'Необхідна PostgreSQL $1 або пізніша, а у Вас $2.',
+ 'config-sqlite-name-help' => 'Виберіть назву, що ідентифікує Вашу вікі.
+Не використовуйте пробіли і дефіси.
+Це буде використовуватись у назві файлу даних SQLite.',
+ 'config-sqlite-parent-unwritable-group' => 'Не можна створити папку даних <code><nowiki>$1</nowiki></code>, оскільки батьківська папка <code><nowiki>$2</nowiki></code> не доступна веб-серверу для запису.
+
+Інсталятор виявив, під яким користувачем працює Ваш сервер.
+Зробіть папку <code><nowiki>$3</nowiki></code> доступною для запису, щоб продовжити.
+В ОС Unix/Linux виконайте:
+
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>',
+ 'config-sqlite-parent-unwritable-nogroup' => 'Не можна створити папку даних <code><nowiki>$1</nowiki></code>, оскільки батьківська папка <code><nowiki>$2</nowiki></code> не доступна веб-серверу для запису.
+
+Інсталятор не зміг виявити, під яким користувачем працює Ваш сервер.
+Зробіть папку <code><nowiki>$3</nowiki></code> доступною для запису серверу (і всім!) глобально, щоб продовжити.
+В ОС Unix/Linux виконайте:
+
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
+ 'config-sqlite-mkdir-error' => 'Помилка при створенні папки даних "$1".
+Перевірте розташування і спробуйте знову.',
+ 'config-sqlite-dir-unwritable' => 'Не можливо записати до папки "$1".
+Змініть налаштування доступу так, щоб веб-сервер міг писати до неї, і спробуйте ще раз.',
+ 'config-sqlite-connection-error' => '$1.
+
+Перевірте папку даних і назву бази даних нижче та спробуйте знову.',
+ 'config-sqlite-readonly' => 'Файл <code>$1</code> недоступний для запису.',
'config-sqlite-cant-create-db' => 'Не вдалося створити файл бази даних <code>$1</code>.',
+ 'config-sqlite-fts3-downgrade' => 'У PHP немає підтримки FTS3, скидаю таблиці',
+ 'config-can-upgrade' => "У цій базі даних є таблиці MediaWiki.
+Щоб оновити їх до MediaWiki $1, натисніть '''Продовжити'''.",
+ 'config-upgrade-done' => "Оновлення завершено.
+
+Ви можете зараз [$1 починати використовувати свою вікі].
+
+Якщо Ви хочете повторно згенерувати файл <code>LocalSettings.php</code>, натисніть на кнопку нижче.
+Це '''не рекомендується''', якщо тільки у Вас не виникли проблеми з Вашою вікі.",
+ 'config-upgrade-done-no-regenerate' => 'Оновлення завершено.
+
+Ви можете зараз [$1 починати використовувати свою вікі].',
+ 'config-regenerate' => 'Повторно згенерувати <code>LocalSettings.php</code> →',
+ 'config-show-table-status' => 'Запит <code>SHOW TABLE STATUS</code> не виконано!',
+ 'config-unknown-collation' => "'''Увага:''' База даних використовує нерозпізнане сортування.",
+ 'config-db-web-account' => 'Обліковий запис бази даних для інтернет-доступу',
+ 'config-db-web-help' => "Оберіть ім'я користувача і пароль, які веб-сервер буде використовувати для з'єднання із сервером бази даних під час звичайної роботи вікі.",
+ 'config-db-web-account-same' => 'Використати той же обліковий запис для встановлення',
'config-db-web-create' => 'Створити обліковий запис, якщо його ще не існує',
+ 'config-db-web-no-create-privs' => 'Обліковий запис, вказаний Вами для встановлення, не має достатніх повноважень для створення облікового запису.
+Обліковий запис, який Ви вказуєте тут, уже повинен існувати.',
+ 'config-mysql-engine' => 'Двигун бази даних:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-myisam-dep' => "'''Увага''': Ви обрали MyISAM для зберігання даних MySQL, що не рекомендовано для роботи з MediaWiki, оскільки:
+* він слабко підтримує паралелізм через блокування таблиць
+* він більш схильний до ушкоджень, ніж інші двигуни
+* база коду MediaWiki не завжди працює з MyISAM так, як мала б.
+
+Якщо Ваша інсталяція MySQL підтримує InnoDB, дуже рекомендується вибрати цей двигун.
+Якщо Ваша інсталяція MySQL не підтримує InnoDB, можливо настав час її оновити.",
+ 'config-mysql-engine-help' => "'''InnoDB''' є завжди кращим вибором, оскільки краще підтримує паралельний доступ.
+
+'''MyISAM''' може бути швидшим для одного користувача або в інсталяціях read-only.
+Бази даних MyISAM схильні псуватись частіше, ніж бази InnoDB.",
'config-mysql-charset' => 'Кодування бази даних:',
'config-mysql-binary' => 'Двійкове',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "У '''бінарному режимі''' MediaWiki зберігає текст UTF-8 у базі даних з бінарними полями.
+Це більш ефективно, ніж UTF-8 режим MySQL, і дозволяє використовувати увесь набір символів Юнікоду.
+
+У '''режимі UTF-8''' MySQL буде знати, якого символу стосуються Ваші дані, і могтиме відображати та конвертувати їх належним чином, але не дозволятиме зберігати символи, що виходять за межі [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ 'config-ibm_db2-low-db-pagesize' => "У Вашій базі даних DB2 за замовчуванням заданий табличний простір з недостатнім розміром сторінки. Розмір сторінки має бути '''32K''' і більше.",
'config-site-name' => 'Назва вікі:',
+ 'config-site-name-help' => 'Це буде відображатись у заголовку вікна браузера та у деяких інших місцях.',
'config-site-name-blank' => 'Введіть назву сайту.',
'config-project-namespace' => 'Простір назв проекту:',
'config-ns-generic' => 'Проект',
+ 'config-ns-site-name' => 'Те ж саме, що й назва вікі: $1',
+ 'config-ns-other' => 'Інше (вкажіть)',
+ 'config-ns-other-default' => 'MyWiki',
+ 'config-project-namespace-help' => 'За прикладом Вікіпедії, чимало вікі тримають свої сторінки правил окремо від сторінок основного вмісту, у "\'\'\'просторі імен проекту\'\'\'".
+Усі назви сторінок у цьому просторі імен починаються з певного префікса, який Ви можете вказати тут.
+Традиційно цей префікс виводиться з назви вікі, але не може містити знаки пунктуація, як-то "#" чи ":".',
+ 'config-ns-invalid' => 'Вказаний простір імен "<nowiki>$1</nowiki>" не припустимий.
+Вкажіть інший простір імен проекту.',
+ 'config-ns-conflict' => 'Вказаний простір імен "<nowiki>$1</nowiki>" конфліктує зі стандартним простором імен MediaWiki.
+Вкажіть інший простір імен проекту.',
+ 'config-admin-box' => 'Обліковий запис адміністратора',
'config-admin-name' => "Ваше ім'я:",
'config-admin-password' => 'Пароль:',
'config-admin-password-confirm' => 'Пароль ще раз:',
+ 'config-admin-help' => 'Введіть бажане ім\'я користувача тут, наприклад "Павло НЛО".
+Це ім\'я ви будете використовувати про вході у вікі.',
+ 'config-admin-name-blank' => "Введіть ім'я користувача адміністратора.",
+ 'config-admin-name-invalid' => 'Вказане ім\'я користувача "<nowiki>$1</nowiki>" не припустиме.
+Вкажіть інше ім\'я користувача.',
+ 'config-admin-password-blank' => 'Введіть пароль до облікового запису адміністратора.',
+ 'config-admin-password-same' => "Пароль не може бути таким же, як ім'я користувача.",
'config-admin-password-mismatch' => 'Два введені вами паролі не збігаються.',
'config-admin-email' => 'Адреса електронної пошти:',
+ 'config-admin-email-help' => 'Введіть адресу електронної пошти, щоб мати змогу отримувати електронну пошту від інших користувачів у вікі, відновити пароль і отримувати повідомлення про зміни, внесені до сторінок у Вашому списку спостереження. Ви можете залишити це поле пустим.',
+ 'config-admin-error-user' => 'Внутрішня помилка під час створення адміністратора з ім\'ям "<nowiki>$1</nowiki>".',
+ 'config-admin-error-password' => 'Внутрішня помилка під час встановлення пароля для адміністратора "<nowiki>$1</nowiki>":<pre>$2</pre>',
+ 'config-admin-error-bademail' => 'Ви ввели недопустиму адресу електронної пошти.',
+ 'config-subscribe' => 'Підписатися на [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce розсилку анонсів нових версій MediaWiki].',
+ 'config-subscribe-help' => "Це список розсилки з малим обсягом повідомлень, що використовується для анонсування релізів, а також важливих повідомлень про безпеку.
+Вам варто підписати і оновлювати інсталяцію MediaWiki, коли з'являтимуться нові версії.",
+ 'config-subscribe-noemail' => 'Ви намагались підписатись на розсилку анонсів релізів, не вказавши адреси електронної пошти.
+Будь ласка, вкажіть адресу електронної пошти, якщо хочете підписатись на розсилку.',
+ 'config-almost-done' => 'Майже готово!
+Ви можете зараз пропустити налаштування, що залишилось, і встановити вікі прямо зараз.',
+ 'config-optional-continue' => 'Запитуйте ще.',
+ 'config-optional-skip' => 'Це вже втомлює, просто встановити вікі.',
+ 'config-profile' => 'Профіль прав користувача:',
+ 'config-profile-wiki' => 'Традиційна вікі', # Fuzzy
+ 'config-profile-no-anon' => 'Необхідно створити обліковий запис',
+ 'config-profile-fishbowl' => 'Тільки для авторизованих редакторів',
+ 'config-profile-private' => 'Приватна вікі',
+ 'config-profile-help' => "Вікі краще працюють, коли Ви дозволяєте їх редагувати якомога ширшому колу людей.
+У MediaWiki легко переглядати останні зміни і відкочувати будь-яку шкоду, спричинену недосвідченими або зловмисними користувачами.
+
+Одначе, MediaWiki може бути корисна по-різному, й інколи важко переконати у вигідності відкритої вікі-роботи.
+Тож у Вас є вибір.
+
+'''{{int:config-profile-wiki}}''' дозволяє редагувати будь-кому, навіть без входження в систему.
+Вікі з вимогою \"'''{{int:config-profile-no-anon}}'''\" дає певний облік, але може відвернути випадкових дописувачів.
+Спосіб \"'''{{int:config-profile-fishbowl}}'''\" дозволяє редагувати підтвердженим користувачам, а переглядати сторінки і історію можуть усі.
+'''{{int:config-profile-private}}''' дозволяє переглядати сторінки і редагувати лише підтвердженим користувачам.
+
+Детальніші конфігурації прав користувачів доступні після встановлення, див. [//www.mediawiki.org/wiki/Manual:User_rights відповідний розділ посібника].", # Fuzzy
'config-license' => 'Авторські права і ліцензія:',
+ 'config-license-none' => 'Без ліцензії у нижньому колонтитулі',
+ 'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
+ 'config-license-cc-by' => 'Creative Commons Attribution',
'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
+ 'config-license-cc-0' => 'Creative Commons Zero (Суспільне надбання)',
+ 'config-license-gfdl' => 'GNU Free Documentation License 1.3 або пізніша',
+ 'config-license-pd' => 'Суспільне надбання (Public Domain)',
+ 'config-license-cc-choose' => 'Виберіть одну з ліцензій Creative Commons',
+ 'config-license-help' => "Чимало загальнодоступних вікі публікують увесь свій вміст під [http://freedomdefined.org/Definition вільною ліцензією]. Це розвиває відчуття спільної власності і заохочує довготривалу участь. У загальному випадку для приватної чи корпоративної вікі у цьому немає необхідності.
+
+Якщо Ви хочете мати змогу використовувати текст з Вікіпедії і дати Вікіпедії змогу використовувати текст, скопійований з Вашої вікі, вам необхідно обрати '''Creative Commons Attribution Share Alike'''.
+
+Раніше Вікіпедія використовувала GNU Free Documentation License.
+GFDL — допустима ліцензія, але у ній важко розібратися, а контент під GFDL важко використовувати повторно.",
'config-email-settings' => 'Налаштування електронної пошти',
+ 'config-enable-email' => 'Увімкнути вихідну електронну пошту',
+ 'config-enable-email-help' => 'Якщо Ви хочете, що електронна пошта працювала, необхідно виставити коректні [http://www.php.net/manual/en/mail.configuration.php налаштування пошти у PHP].
+Якщо Вам не потрібні жодні можливості електронної пошти у вікі, можете тут їх відключити.',
+ 'config-email-user' => 'Увімкнути електронну пошту користувач-користувачеві',
+ 'config-email-user-help' => 'Дозволити усім користувачам надсилати один одному електронну пошту, якщо вони увімкнули цю можливість у своїх налаштуваннях.',
+ 'config-email-usertalk' => 'Увімкнути сповіщення про повідомлення на сторінці обговорення користувача',
+ 'config-email-usertalk-help' => 'Дозволити користувачам отримувати сповіщення про зміни на своїй сторінці обговорення, якщо вони увімкнули цю можливість у своїх налаштуваннях.',
+ 'config-email-watchlist' => 'Увімкнути сповіщення про зміни у списку спостереження',
+ 'config-email-watchlist-help' => 'Дозволити користувачам отримувати сповіщення про сторінки з їхнього списку спостереження, якщо вони увімкнули цю можливість у своїх налаштуваннях.',
+ 'config-email-auth' => 'Увімкнути автентифікацію через електронну пошту',
+ 'config-email-auth-help' => "Якщо ця опція увімкнена, користувачам треба підтвердити свою адресу електронної пошти з допомогою надісланого їм посилання, коли вони встановлюють чи змінюють її.
+Тільки автентифіковані адреси електронної пошти отримують листи від інших користувачів або змінювати поштові сповіщення.
+Увімкнення цієї опції '''рекомендується''' загальнодоступним вікі через можливі зловживання функціями електронної пошти.",
+ 'config-email-sender' => 'Зворотна адреса електронної пошти:',
+ 'config-email-sender-help' => "Введіть адресу електронної пошти, що буде використовуватись як зворотна адреса для вихідної пошти.
+На неї будуть надсилатись відмови.
+Чимало поштових серверів вимагають, щоб принаймні доменне ім'я було допустимим.",
+ 'config-upload-settings' => 'Завантаження зображень і файлів',
'config-upload-enable' => 'Дозволити завантаження файлів',
+ 'config-upload-help' => 'Завантаження файлів підставляє Ваш сервер під потенційні загрози.
+Детальнішу інформацію можна почитати у посібнику, [//www.mediawiki.org/wiki/Manual:Security розділ про безпеку].
+
+Щоб дозволити завантаження файлів, змініть режим підпапки <code>images</code> у кореневій папці MediaWiki так, щоб сервер міг у неї записувати.
+Потім увімкніть цю опцію.',
'config-upload-deleted' => 'Каталог для вилучених файлів:',
+ 'config-upload-deleted-help' => 'Оберіть папку для архівації видалених файлів.
+В ідеалі, вона не має бути доступною через інтернет.',
+ 'config-logo' => 'URL логотипу:',
+ 'config-logo-help' => 'Стандартна схема оформлення MediaWiki містить вільне для логотипу місце над бічною панеллю розміром 135x160 пікселів.
+Завантажте зображення відповідного розміру і введіть тут його URL.
+
+Якщо Вам не потрібен логотип, залиште це поле пустим.',
+ 'config-instantcommons' => 'Увімкнути Instant Commons',
+ 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] це функція, що дозволяє вікі використовувати зображення, звуки та інші медіа, розміщені на [//commons.wikimedia.org/ Вікісховищі].
+Для цього MediaWiki необхідний доступ до інтернету.
+
+Додаткову інформацію стосовно цієї функції, включаючи інструкції, як її увімкнути у вікі, відмінних від Вікісховища, дивіться у [//mediawiki.org/wiki/Manual:$wgForeignFileRepos посібнику].',
+ 'config-cc-error' => 'Механізм вибору ліцензії Creative Commons не дав результатів.
+Введіть назву ліцензії вручну.',
'config-cc-again' => 'Виберіть знову ...',
+ 'config-cc-not-chosen' => 'Оберіть, яку ліцензію Creative Commons Ви хочете використовувати, і натисніть "продовжити".',
+ 'config-advanced-settings' => 'Розширені налаштування',
+ 'config-cache-options' => "Налаштування кешування об'єктів:",
+ 'config-cache-help' => "Кешування об'єктів використовується для покращення швидкодії MediaWiki методом кешування часто використовуваних даних.
+Заохочується увімкнення цієї можливості для середніх і великих сайтів, малі сайти також можуть відчути її перевагу.",
+ 'config-cache-none' => 'Без кешування (жодні функції не втрачаються, але впливає на швидкодію великих вікі-сайтів)',
+ 'config-cache-accel' => "PHP кешування об'єктів (APC, XCache чи WinCache)",
+ 'config-cache-memcached' => 'Використовувати Memcached (вимагає додаткової установки і налаштування)',
+ 'config-memcached-servers' => 'Сервери Memcached:',
+ 'config-memcached-help' => 'Список IP-адрес, що викоритовує Memcached.
+Вкажіть по одному в рядку, разом з портами. Наприклад:
+ 127.0.0.1:11211
+ 192.168.1.25:1234',
+ 'config-memcache-needservers' => 'Ви обрали тип кешування Memcached, але не вказали ніяких серверів.',
+ 'config-memcache-badip' => 'Ви ввели недопустиму IP-адресу для Memcached: $1.',
+ 'config-memcache-noport' => 'Ви не вказали порт для сервера Memcached: $1.
+Якщо Ви його не знаєте, за замовчуванням використовується 11211.',
+ 'config-memcache-badport' => 'Номери портів Memcached повинні лежати в межах від $1 до $2.',
'config-extensions' => 'Розширення',
+ 'config-extensions-help' => 'Розширення, перераховані вище, були знайдені у папці <code>./extensions</code>.
+
+Вони можуть потребувати додаткових налаштувань, але Ви можете увімкнути їх зараз.',
+ 'config-install-alreadydone' => "'''Увага:''' Здається, Ви вже встановлювали MediaWiki і зараз намагаєтесь встановити її знову.
+Будь ласка, перейдіть на наступну сторінку.",
+ 'config-install-begin' => 'Натискаючи "{{int:config-continue}}", Ви розпочинаєте встановлення MediaWiki.
+Якщо Ви все ще хочете внести зміни, натисніть "Назад".', # Fuzzy
'config-install-step-done' => 'виконано',
'config-install-step-failed' => 'не вдалося',
+ 'config-install-extensions' => 'У тому числі розширення',
+ 'config-install-database' => 'Налаштування бази даних',
+ 'config-install-schema' => 'Створення схеми',
+ 'config-install-pg-schema-not-exist' => 'Схеми PostgreSQL не існує.',
+ 'config-install-pg-schema-failed' => 'Не вдалось створити таблиці.
+Переконайтесь, що користувач "$1" може писати до схеми "$2".',
+ 'config-install-pg-commit' => 'Внесення змін',
+ 'config-install-pg-plpgsql' => 'Перевірка мови PL/pgSQL',
+ 'config-pg-no-plpgsql' => 'Вам необхідно встановити мову PL/pgSQL у базі даних $1',
+ 'config-pg-no-create-privs' => 'Обліковий запис, вказаний для встановлення, має недостатньо прав для створення облікового запису.',
+ 'config-pg-not-in-role' => 'Обліковий запис, який Ви вказали для веб-користувача, уже існує.
+Обліковий запис, який Ви вказали для встановлення не є суперюзером і не відноситься до ролі веб-користувача, тому неможливо створити об\'єкти, що належать веб-користувачеві.
+
+У даний час MediaWiki вимагає, щоб усі таблиці належали веб-користувачу. Будь ласка, вкажіть інше ім\'я облікового запису або натисніть "Назад" та вкажіть користувача з достатніми правами.',
+ 'config-install-user' => 'Створення користувача бази даних',
+ 'config-install-user-alreadyexists' => 'Користувач "$1" уже існує',
+ 'config-install-user-create-failed' => 'Не вдалося створити користувача "$1": $2',
+ 'config-install-user-grant-failed' => 'Не вдалося надати права користувачеві "$1": $2',
+ 'config-install-user-missing' => 'Зазначеного користувача "$1" не існує.',
+ 'config-install-user-missing-create' => 'Зазначеного користувача "$1" не існує.
+Будь ласка, поставте галочку "Створити обліковий запис", якщо хочете його створити.',
+ 'config-install-tables' => 'Створення таблиць',
+ 'config-install-tables-exist' => "'''Увага''': Таблиці MediaWiki уже, здається, існують.
+Пропуск створення.",
+ 'config-install-tables-failed' => "'''Помилка''': Не вдалося створити таблицю внаслідок такої помилки: $1",
+ 'config-install-interwiki' => 'Заповнення таблиці інтервікі значеннями за замовчуванням',
'config-install-interwiki-list' => 'Не вдалося знайти файл <code>interwiki.list</code>.',
+ 'config-install-interwiki-exists' => "'''Увага''': Таблиця інтервікі уже, здається, має записи.
+Створення стандартного списку пропускається.",
+ 'config-install-stats' => 'Ініціалізація статистики',
+ 'config-install-keys' => 'Генерація секретних ключів',
+ 'config-insecure-keys' => "'''Увага:''' {{PLURAL:$2|Секретний ключ|Секретні ключі}} ($1), {{PLURAL:$2|згенерований в процесі встановлення, недостатньо надійний|згенеровані в процесі встановлення, недостатньо надійні}}. Розгляньте можливість {{PLURAL:$2|його|їх}} заміни вручку.",
+ 'config-install-sysop' => 'Створення облікового запису адміністратора',
+ 'config-install-subscribe-fail' => 'Не можливо підписатись на mediawiki-announce: $1',
+ 'config-install-subscribe-notpossible' => 'cURL не встановлено і опція allow_url_fopen не доступна.',
+ 'config-install-mainpage' => 'Створення головної сторінки із вмістом за замовчуванням',
+ 'config-install-extension-tables' => 'Створення таблиць для увімкнених розширень',
+ 'config-install-mainpage-failed' => 'Не вдається вставити головну сторінку: $1',
+ 'config-install-done' => "'''Вітаємо!'''
+Ви успішно встановили MediaWiki.
+
+Інсталятор згенерував файл <code>LocalSettings.php</code>, який містить усі Ваші налаштування.
+
+Вам необхідно завантажити його і помістити у кореневу папку Вашої вікі (туди ж, де index.php). Завантаження мало початись автоматично.
+
+Якщо завантаження не почалось або Ви його скасували, можете заново його почати, натиснувши на посилання внизу:
+
+$3
+
+'''Примітка''': Якщо Ви не зробите цього зараз, цей файл не буде доступним пізніше, коли Ви вийдете з встановлення, не скачавши його.
+
+Після виконання дій, описаних вище, Ви зможете '''[$2 увійти у свою вікі]'''.",
+ 'config-download-localsettings' => 'Завантажити <code>LocalSettings.php</code>',
'config-help' => 'допомога',
+ 'config-nofile' => 'Файл "$1" не знайдено. Його видалено?',
'mainpagetext' => 'Програмне забезпечення «MediaWiki» успішно встановлене.',
- 'mainpagedocfooter' => 'Інформацію про роботу з цією вікі можна знайти в [//meta.wikimedia.org/wiki/%D0%9F%D0%BE%D0%BC%D0%BE%D1%89%D1%8C:%D0%A1%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B0%D0%BD%D0%B8%D0%B5 посібнику користувача].
+ 'mainpagedocfooter' => 'Інформацію про роботу з цією вікі можна знайти в [//meta.wikimedia.org/wiki/Help:Contents посібнику користувача].
== Деякі корисні ресурси ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Список налаштувань];
* [//www.mediawiki.org/wiki/Manual:FAQ Часті питання з приводу MediaWiki];
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Розсилка повідомлень про появу нових версій MediaWiki].',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Розсилка повідомлень про появу нових версій MediaWiki];
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Локалізуйте MediaWiki своєю мовою]',
);
/** Urdu (اردو)
@@ -18267,21 +19865,36 @@ $messages['xal'] = array(
* @author පසිඳු කාවින්ද
*/
$messages['yi'] = array(
+ 'config-desc' => 'דער אינסטאלירער פאר מעדיעוויקי',
+ 'config-title' => 'מעדיעוויקי $1 אינסטאלירונג',
+ 'config-information' => 'אינפֿארמאציע',
+ 'config-wiki-language' => 'ווקי שפראך:',
'config-back' => '→ צוריק',
+ 'config-continue' => 'פֿארזעצן ←',
'config-page-language' => 'שפראַך',
'config-page-name' => 'נאָמען',
'config-page-options' => 'ברירות',
+ 'config-db-type' => 'דאטנבאזע טיפ:',
+ 'config-db-name' => 'דאטנבאזע נאָמען:',
+ 'config-project-namespace' => 'פראיעקט נאָמענטייל:',
+ 'config-ns-generic' => 'פראיעקט',
'config-admin-name' => 'אײַער נאָמען:',
'config-admin-password' => 'פאַסווארט:',
+ 'config-admin-password-mismatch' => 'די צוויי פאסוועטרט איר האט איינגעגעבן שטימען נישט.',
'config-admin-email' => 'בליצפּאָסט אַדרעס:',
+ 'config-install-tables' => 'שאפן טאבעלעס',
+ 'config-install-tables-exist' => "'''ווארענונג''': זעט אויס אז די מעדיעוויקי טאבעלעס עקזיסטירן שוין.
+איבערהיפן שאפֿן.",
+ 'config-download-localsettings' => 'אראפלאדן <code>LocalSettings.php</code>',
'config-help' => 'הילף',
+ 'config-nofile' => 'מ\'האט נישט געקענט טרעפן די טעקע "$1". צי האט מען זי אויסגעמעקט?',
'mainpagetext' => "'''מעדיעוויקי אינסטאלירט מיט דערפאלג.'''",
'mainpagedocfooter' => "גיט זיך אן עצה מיט [//meta.wikimedia.org/wiki/Help:Contents באניצער'ס וועגווײַזער] פֿאר אינפֿארמאציע וויאזוי זיך באנוצן מיט וויקי ווייכוואַרג.
== נוצליכע וועבלינקען פֿאַר אנהייבערס ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings רשימה פון קאנפֿיגוראציעס]
* [//www.mediawiki.org/wiki/Manual:FAQ אפֿט געפֿרעגטע שאלות]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce מעדיעוויקי באפֿרײַאונג פאסטליסטע]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce מעדיעוויקי באפֿרײַאונג פאסטליסטע]* [//www.mediawiki.org/wiki/Localisation#Translation_resources איבערזעצן מעדיעוויקי אין אײַער שפראך]",
);
/** Yoruba (Yorùbá)
@@ -18323,26 +19936,30 @@ $messages['zea'] = array(
);
/** Simplified Chinese (中文(简体)‎)
+ * @author Anthony Fok
* @author Hydra
* @author Hzy980512
* @author Liangent
* @author PhiLiP
* @author Xiaomingyan
+ * @author Yfdyh000
+ * @author 乌拉跨氪
* @author 阿pp
+ * @author 아라
*/
$messages['zh-hans'] = array(
'config-desc' => 'MediaWiki安装程序',
'config-title' => 'MediaWiki $1配置',
'config-information' => '信息',
- 'config-localsettings-upgrade' => '已检测到<code>LocalSettings.php</code>文件。要升级该配置,请在下面的框中输入<code>$wgUpgradeKey</code>的值。您可以在LocalSettings.php中找到它。',
- 'config-localsettings-cli-upgrade' => '已检测到LocalSettings.php文件。要升级该配置,请直接运行update.php。',
+ 'config-localsettings-upgrade' => '已检测到<code>LocalSettings.php</code>文件。要升级该配置,请在下面的框中输入<code>$wgUpgradeKey</code>的值。您可以在<code>LocalSettings.php</code>中找到它。',
+ 'config-localsettings-cli-upgrade' => '已检测到<code>LocalSettings.php</code>文件。要升级该配置,请直接运行<code>update.php</code>。',
'config-localsettings-key' => '升级密钥:',
'config-localsettings-badkey' => '您提供的密钥不正确。',
- 'config-upgrade-key-missing' => '检测到MediaWiki的配置已经存在。若要升级该配置,请将下面一行文本添加到LocalSettings.php的底部:
+ 'config-upgrade-key-missing' => '检测到MediaWiki的配置已经存在。若要升级该配置,请将下面一行文本添加到<code>LocalSettings.php</code>的底部:
$1',
- 'config-localsettings-incomplete' => '当前的LocalSettings.php可能并不完整,因为变量$1没有设置。请在LocalSettings.php设置该变量,并单击“继续”。',
- 'config-localsettings-connection-error' => '在使用LocalSettings.php或AdminSettings.php中指定的设置连接数据库时发生错误。请修复相应设置并重试。
+ 'config-localsettings-incomplete' => '当前的<code>LocalSettings.php</code>可能并不完整,因为变量$1没有设置。请在<code>LocalSettings.php</code>设置该变量,并单击“{{int:Config-continue}}”。',
+ 'config-localsettings-connection-error' => '在使用<code>LocalSettings.php</code>或<code>AdminSettings.php</code>中指定的设置连接数据库时发生错误。请修复相应设置并重试。
$1',
'config-session-error' => '启动会话出错:$1',
@@ -18397,7 +20014,8 @@ $1',
'config-env-php-toolow' => '已安装PHP $1;但是,MediaWiki需要PHP $2或更高版本。',
'config-unicode-using-utf8' => '使用Brion Vibber的utf8_normalize.so实现Unicode正常化。',
'config-unicode-using-intl' => '使用[http://pecl.php.net/intl intl PECL扩展]实现Unicode正常化。',
- 'config-unicode-pure-php-warning' => "'''警告''':[http://pecl.php.net/intl intl PECL扩展]无法处理Unicode正常化,故只能退而采用运行较慢的纯PHP实现的方法。如果您运行着一个高流量的站点,请参阅[//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode正常化]一文。",
+ 'config-unicode-pure-php-warning' => "'''警告:'''因为尚未安装 [http://pecl.php.net/intl intl PECL 扩展]以处理 Unicode 正常化,故只能退而采用运行较慢的纯 PHP 实现的方法。
+如果您运行着一个高流量的站点,请参阅 [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode 正常化]一文。",
'config-unicode-update-warning' => "'''警告''':Unicode正常化封装器的已安装版本使用了旧版本的[http://site.icu-project.org/ ICU项目]库。如果您需要使用Unicode,请将其[//www.mediawiki.org/wiki/Unicode_normalization_considerations 升级]。",
'config-no-db' => '找不到合适的数据库驱动!您需要为PHP安装数据库驱动。目前支持以下数据库:$1。
@@ -18438,7 +20056,7 @@ Object caching is not enabled.",
'config-no-cli-uploads-check' => "'''警告''':在CLI安装过程中,没有对您的默认上传目录(<code>$1</code>)进行执行任意脚本的漏洞检查。",
'config-brokenlibxml' => '您的系统安装的PHP和libxml2版本组合存在故障,并可能在MediaWiki和其他web应用程序中造成隐藏的数据损坏。请将PHP升级到5.2.9或以上,libxml2升级到2.7.3或以上([//bugs.php.net/bug.php?id=45996 PHP的故障报告])。安装已中断。',
'config-using531' => '由于函数<code>__call()</code>的引用参数存在故障,PHP $1和MediaWiki无法兼容。请升级到PHP 5.3.2或更高版本,或降级到PHP 5.3.0以修复该问题。安装已中断。',
- 'config-suhosin-max-value-length' => 'Suhosin已经安装并将GET请求的参数长度限制在$1字节。MediaWiki的ResourceLoader部件可以在此限制下正常工作,但其性能会被降低。如果可能,请在php.ini中将suhosin.get.max_value_length设为1024或更高值,并在LocalSettings.php中将$wgResourceLoaderMaxQueryLength设为同一值。',
+ 'config-suhosin-max-value-length' => 'Suhosin已经安装并将GET请求的参数长度限制在$1字节。MediaWiki的ResourceLoader部件可以在此限制下正常工作,但其性能会被降低。如果可能,请在<code>php.ini</code>中将<code>suhosin.get.max_value_length</code>设为1024或更高值,并在LocalSettings.php中将<code>$wgResourceLoaderMaxQueryLength</code>设为同一值。', # Fuzzy
'config-db-type' => '数据库类型:',
'config-db-host' => '数据库主机:',
'config-db-host-help' => '如果您的数据库在别的服务器上,请在这里输入它的域名或IP地址。
@@ -18501,7 +20119,6 @@ Object caching is not enabled.",
请考虑将数据库统一放置在某处,如<code>/var/lib/mediawiki/yourwiki</code>下。",
'config-oracle-def-ts' => '默认表空间:',
'config-oracle-temp-ts' => '临时表空间:',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'MediaWiki支持以下数据库系统:
$1
@@ -18511,12 +20128,10 @@ $1
'config-support-postgres' => '* $1是一种流行的开源数据库系统,可作为MySQL的替代([http://www.php.net/manual/en/pgsql.installation.php 如何将对PostgreSQL的支持编译进PHP中])。本程序中可能依然存在一些小而明显的错误,因此并不建议在生产环境中使用该数据库系统。',
'config-support-sqlite' => '* $1是一种轻量级的数据库系统,能被良好地支持。([http://www.php.net/manual/en/pdo.installation.php 如何将对SQLite的支持编译进PHP中],须使用PDO)',
'config-support-oracle' => '* $1是一种商用企业级的数据库。([http://www.php.net/manual/en/oci8.installation.php 如何将对OCI8的支持编译进PHP中])',
- 'config-support-ibm_db2' => '* $1是一种商用企业级数据库。',
'config-header-mysql' => 'MySQL设置',
'config-header-postgres' => 'PostgreSQL设置',
'config-header-sqlite' => 'SQLite设置',
'config-header-oracle' => 'Oracle设置',
- 'config-header-ibm_db2' => 'IBM DB2设置',
'config-invalid-db-type' => '无效的数据库类型',
'config-missing-db-name' => '您必须为“数据库名称”输入内容',
'config-missing-db-host' => '您必须为“数据库主机”输入内容',
@@ -18564,8 +20179,8 @@ chmod a+w $3</pre>',
'config-upgrade-done-no-regenerate' => '升级完成。
现在您可以[$1 开始使用您的wiki]了。',
- 'config-regenerate' => '重新生成LocalSettings.php →',
- 'config-show-table-status' => 'SHOW TABLE STATUS语句执行失败!',
+ 'config-regenerate' => '重新生成<code>LocalSettings.php</code> →',
+ 'config-show-table-status' => '<code>SHOW TABLE STATUS</code>语句执行失败!',
'config-unknown-collation' => "'''警告:'''数据库使用了无法识别的整理。",
'config-db-web-account' => '供网页访问使用的数据库帐号',
'config-db-web-help' => '请指定在wiki执行普通操作时,网页服务器用于连接数据库服务器的用户名和密码。',
@@ -18591,7 +20206,6 @@ chmod a+w $3</pre>',
'config-mysql-charset-help' => "在'''二进制模式'''下,MediaWiki会将UTF-8编码的文本存于数据库的二进制字段中。相对于MySQL的UTF-8模式,这种方法效率更高,并允许您使用全范围的Unicode字符。
在'''UTF-8模式'''下,MySQL将知道您数据使用的字符集,并能适当地提供和转换内容。但这样做您将无法在数据库中存储[//zh.wikipedia.org/wiki/基本多文种平面 基本多文种平面]以外的字符。",
- 'config-ibm_db2-low-db-pagesize' => "您的DB2数据库默认表空间的页长(pagesize)不足。至少需要'''32K'''或更大的页长。",
'config-site-name' => 'Wiki的名称:',
'config-site-name-help' => '填入的内容会出现在浏览器的标题栏以及其他多处位置中。',
'config-site-name-blank' => '输入网站的名称。',
@@ -18625,7 +20239,7 @@ chmod a+w $3</pre>',
'config-optional-continue' => '多问我一些问题吧。',
'config-optional-skip' => '我已经不耐烦了,赶紧安装我的wiki。',
'config-profile' => '用户权限配置:',
- 'config-profile-wiki' => '传统wiki',
+ 'config-profile-wiki' => '开放的wiki',
'config-profile-no-anon' => '需要注册帐号',
'config-profile-fishbowl' => '编辑受限',
'config-profile-private' => '非公开wiki',
@@ -18702,7 +20316,7 @@ GNU自由文档许可证是维基百科曾经使用过的许可证,并迄今
您可能要对它们进行额外的配置,但您现在可以启用它们。',
'config-install-alreadydone' => "'''警告:'''您似乎已经安装了MediaWiki,并试图重新安装它。请前往下一个页面。",
- 'config-install-begin' => '点击“{{int:config-continue}}”后,您将开始安装MediaWiki。如果您还想对配置作一些修改,请点击后退。',
+ 'config-install-begin' => '点击“{{int:config-continue}}”后,您将开始安装MediaWiki。如果您还想对配置作一些修改,请点击“{{int:config-back}}”。',
'config-install-step-done' => '完成',
'config-install-step-failed' => '失败',
'config-install-extensions' => '正在启用扩展',
@@ -18753,35 +20367,40 @@ $3
'''注意''':如果您现在不完成本步骤,而是没有下载便退出了安装过程,此后您将无法获得自动生成的配置文件。
当本步骤完成后,您可以 '''[$2 进入您的wiki]'''。",
- 'config-download-localsettings' => '下载LocalSettings.php',
+ 'config-download-localsettings' => '下载<code>LocalSettings.php</code>',
'config-help' => '帮助',
+ 'config-nofile' => '找不到文件“$1”。它是否已被删除?',
'mainpagetext' => "'''已成功安装MediaWiki。'''",
'mainpagedocfooter' => '请查阅[//meta.wikimedia.org/wiki/Help:Contents 用户指南]以获取使用本wiki软件的信息!
== 入门 ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki配置设置列表]
* [//www.mediawiki.org/wiki/Manual:FAQ/zh-hans MediaWiki常见问题]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki发布邮件列表]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki发布邮件列表]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources 本地化MediaWiki到您的语言]',
);
/** Traditional Chinese (中文(繁體)‎)
+ * @author Anthony Fok
* @author Hzy980512
* @author Liangent
* @author Mark85296341
+ * @author Simon Shek
+ * @author 아라
*/
$messages['zh-hant'] = array(
'config-desc' => 'MediaWiki安裝程序',
'config-title' => 'MediaWiki $1配置',
'config-information' => '資訊',
- 'config-localsettings-upgrade' => '已檢測到<code>LocalSettings.php</code>文件。要升級該配置,請在下面的框中輸入<code>$wgUpgradeKey</code>的值。您可以在LocalSettings.php中找到它。',
- 'config-localsettings-cli-upgrade' => '已檢測到LocalSettings.php文件。要升級該配置,請直接執行update.php。',
+ 'config-localsettings-upgrade' => '已檢測到<code>LocalSettings.php</code>文件。要升級該配置,請在下面的框中輸入<code>$wgUpgradeKey</code>的值。您可以在<code>LocalSettings.php</code>中找到它。',
+ 'config-localsettings-cli-upgrade' => '已檢測到<code>LocalSettings.php</code>文件。要升級該配置,請直接執行<code>update.php</code>。',
'config-localsettings-key' => '升級密鑰:',
'config-localsettings-badkey' => '您提供的密鑰不正確。',
- 'config-upgrade-key-missing' => '檢測到MediaWiki的配置已經存在。若要升級該配置,請將下面一行文本添加到LocalSettings.php的底部:
+ 'config-upgrade-key-missing' => '檢測到MediaWiki的配置已經存在。若要升級該配置,請將下面一行文本添加到<code>LocalSettings.php</code>的底部:
$1',
- 'config-localsettings-incomplete' => '當前的LocalSettings.php可能並不完整,因為變量$1沒有設置。請在LocalSettings.php設置該變量,並單擊“繼續”。',
- 'config-localsettings-connection-error' => '在使用LocalSettings.php或AdminSettings.php中指定的設置連接數據庫時發生錯誤。請修復相應設置並重試。
+ 'config-localsettings-incomplete' => '當前的<code>LocalSettings.php</code>可能並不完整,因為變量$1沒有設置。請在<code>LocalSettings.php</code>設置該變量,並單擊“{{int:Config-continue}}”。',
+ 'config-localsettings-connection-error' => '在使用<code>LocalSettings.php</code>或<code>AdminSettings.php</code>中指定的設置連接數據庫時發生錯誤。請修復相應設置並重試。
$1',
'config-session-error' => '啟動會話出錯:$1',
@@ -18832,11 +20451,11 @@ $1',
* <doclink href=UpgradeDoc>升級</doclink>',
'config-env-good' => '環境檢查已經完成。您可以安裝MediaWiki。',
'config-env-bad' => '環境檢查已經完成。您不能安裝MediaWiki。',
- 'config-env-php' => 'PHP $1已安裝。',
- 'config-env-php-toolow' => '已安裝PHP $1;但是,MediaWiki需要PHP $2或更高版本。',
- 'config-unicode-using-utf8' => '使用Brion Vibber的utf8_normalize.so實現Unicode正常化。',
- 'config-unicode-using-intl' => '使用[http://pecl.php.net/intl intl PECL擴展]實現Unicode正常化。',
- 'config-unicode-pure-php-warning' => "'''警告''':[http://pecl.php.net/intl intl PECL擴展]無法處理Unicode正常化,故只能退而採用運行較慢的純PHP實現的方法。如果您運行着一個高流量的站點,請參閱[//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode正常化]一文。",
+ 'config-env-php' => 'PHP $1 已安裝。',
+ 'config-env-php-toolow' => '已安裝 PHP $1;但是,MediaWiki 需要 PHP $2 或更高版本。',
+ 'config-unicode-using-utf8' => '將使用 Brion Vibber 的 utf8_normalize.so 以實作 Unicode 正規化。',
+ 'config-unicode-using-intl' => '將使用 [http://pecl.php.net/intl intl PECL 延伸函式庫]以實作 Unicode 正規化。',
+ 'config-unicode-pure-php-warning' => "'''警告:'''因為尚未安裝 [http://pecl.php.net/intl intl PECL 延伸函式庫]以處理 Unicode 正規化,故只能退而採用較慢的純 PHP 實作。如果您運行着一個高流量的網站,請參閱 [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode 正規化]一文。",
'config-unicode-update-warning' => "'''警告''':Unicode正常化封裝器的已安裝版本使用了舊版本的[http://site.icu-project.org/ ICU項目]庫。如果您需要使用Unicode,請將其[//www.mediawiki.org/wiki/Unicode_normalization_considerations 升級]。",
'config-no-db' => '找不到合適的數據庫驅動!您需要為PHP安裝數據庫驅動。目前支持以下數據庫:$1。
@@ -18877,7 +20496,7 @@ Object caching is not enabled.",
'config-no-cli-uploads-check' => "'''警告''':在CLI安裝過程中,沒有對您的默認上傳目錄(<code>$1</code>)進行執行任意腳本的漏洞檢查。",
'config-brokenlibxml' => '您的系統安裝的PHP和libxml2版本組合存在故障,並可能在MediaWiki和其他web應用程序中造成隱藏的數據損壞。請將PHP升級到5.2.9或以上,libxml2升級到2.7.3或以上([//bugs.php.net/bug.php?id=45996 PHP的故障報告])。安裝已中斷。',
'config-using531' => '由於函數<code>__call()</code>的引用參數存在故障,PHP $1和MediaWiki無法兼容。請升級到PHP 5.3.2或更高版本,或降級到PHP 5.3.0以修復該問題。安裝已中斷。',
- 'config-suhosin-max-value-length' => 'Suhosin已經安裝並將GET請求的參數長度限制在$1字節。MediaWiki的ResourceLoader部件可以在此限制下正常工作,但其性能會被降低。如果可能,請在php.ini中將suhosin.get.max_value_length設為1024或更高值,並在LocalSettings.php中將$wgResourceLoaderMaxQueryLength設為同一值。',
+ 'config-suhosin-max-value-length' => 'Suhosin已經安裝並將GET請求的參數長度限制在$1字節。MediaWiki的ResourceLoader部件可以在此限制下正常工作,但其性能會被降低。如果可能,請在<code>php.ini</code>中將<code>suhosin.get.max_value_length</code>設為1024或更高值,並在LocalSettings.php中將<code>$wgResourceLoaderMaxQueryLength</code>設為同一值。', # Fuzzy
'config-db-type' => '資料庫類型:',
'config-db-host' => '資料庫主機:',
'config-db-host-help' => '如果您的數據庫在別的服務器上,請在這裡輸入它的域名或IP地址。
@@ -18940,7 +20559,6 @@ Object caching is not enabled.",
請考慮將數據庫統一放置在某處,如<code>/var/lib/mediawiki/yourwiki</code>下。",
'config-oracle-def-ts' => '默認表空間:',
'config-oracle-temp-ts' => '臨時表空間:',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'MediaWiki支持以下數據庫系統:
$1
@@ -18950,12 +20568,10 @@ $1
'config-support-postgres' => '* $1是一種流行的開源數據庫系統,可作為MySQL的替代([http://www.php.net/manual/en/pgsql.installation.php 如何將對PostgreSQL的支持編譯進PHP中])。本程序中可能依然存在一些小而明顯的錯誤,因此並不建議在生產環境中使用該數據庫系統。',
'config-support-sqlite' => '* $1是一種輕量級的數據庫系統,能被良好地支持。([http://www.php.net/manual/en/pdo.installation.php 如何將對SQLite的支持編譯進PHP中],須使用PDO)',
'config-support-oracle' => '* $1是一種商用企業級的數據庫。([http://www.php.net/manual/en/oci8.installation.php 如何將對OCI8的支持編譯進PHP中])',
- 'config-support-ibm_db2' => '* $1是一種商用企業級數據庫。',
'config-header-mysql' => 'MySQL 的設定',
'config-header-postgres' => 'PostgreSQL設置',
'config-header-sqlite' => 'SQLite 的設定',
'config-header-oracle' => '甲骨文設定',
- 'config-header-ibm_db2' => 'IBM DB2設置',
'config-invalid-db-type' => '無效的資料庫類型',
'config-missing-db-name' => '您必須為“數據庫名稱”輸入內容',
'config-missing-db-host' => '您必須為“數據庫主機”輸入內容',
@@ -19003,8 +20619,8 @@ chmod a+w $3</pre>',
'config-upgrade-done-no-regenerate' => '升級完成。
現在您可以[$1 開始使用您的wiki]了。',
- 'config-regenerate' => '重新生成LocalSettings.php →',
- 'config-show-table-status' => '查詢SHOW TABLE STATUS失敗!',
+ 'config-regenerate' => '重新生成<code>LocalSettings.php</code> →',
+ 'config-show-table-status' => '查詢<code>SHOW TABLE STATUS</code>失敗!',
'config-unknown-collation' => "'''警告:'''數據庫使用了無法識別的整理。",
'config-db-web-account' => '供網頁訪問使用的數據庫帳號',
'config-db-web-help' => '請指定在wiki執行普通操作時,網頁服務器用於連接數據庫服務器的用戶名和密碼。',
@@ -19030,7 +20646,6 @@ chmod a+w $3</pre>',
'config-mysql-charset-help' => "在'''二進制模式'''下,MediaWiki會將UTF-8編碼的文本存於數據庫的二進制字段中。相對於MySQL的UTF-8模式,這種方法效率更高,並允許您使用全範圍的Unicode字符。
在'''UTF-8模式'''下,MySQL將知道您數據使用的字符集,並能適當地提供和轉換內容。但這樣做您將無法在數據庫中存儲[//zh.wikipedia.org/wiki/基本多文種平面 基本多文種平面]以外的字符。",
- 'config-ibm_db2-low-db-pagesize' => "您的DB2數據庫默認表空間的頁長(pagesize)不足。至少需要'''32K'''或更大的頁長。",
'config-site-name' => 'Wiki的名稱:',
'config-site-name-help' => '填入的內容會出現在瀏覽器的標題欄以及其他多處位置中。',
'config-site-name-blank' => '輸入站點名稱。',
@@ -19052,7 +20667,7 @@ chmod a+w $3</pre>',
'config-admin-password-blank' => '輸入管理員帳號密碼。',
'config-admin-password-same' => '密碼不能與使用者名稱相同。',
'config-admin-password-mismatch' => '兩次輸入的密碼並不相同。',
- 'config-admin-email' => 'E-mail 地址:',
+ 'config-admin-email' => '電郵地址:',
'config-admin-email-help' => '輸入電子郵件地址後,您可以收到此wiki上其他用戶發來的電子郵件,並能重置您的密碼,還可在監視列表中頁面被更改時收到郵件通知。您可以將此字段留空。',
'config-admin-error-user' => '在創建用戶名為“<nowiki>$1</nowiki>”的管理員帳號時發生內部錯誤。',
'config-admin-error-password' => '在為管理員“<nowiki>$1</nowiki>”設置密碼時發生內部錯誤:<pre>$2</pre>',
@@ -19064,7 +20679,7 @@ chmod a+w $3</pre>',
'config-optional-continue' => '多問我一些問題吧。',
'config-optional-skip' => '我已經不耐煩了,趕緊安裝我的wiki。',
'config-profile' => '用戶權限配置:',
- 'config-profile-wiki' => '傳統wiki',
+ 'config-profile-wiki' => '傳統wiki', # Fuzzy
'config-profile-no-anon' => '需要註冊帳號',
'config-profile-fishbowl' => '編輯受限',
'config-profile-private' => '非公開wiki',
@@ -19076,7 +20691,7 @@ chmod a+w $3</pre>',
'''{{int:config-profile-fishbowl}}'''模式只允許獲批准的用戶編輯,但對公眾開放頁面瀏覽(包括歷史記錄)。'''{{int:config-profile-private}}'''則只允許獲批准的用戶瀏覽、編輯頁面。
-安裝完成後,您還可以對用戶權限進行更多、更複雜的配置,參見[//www.mediawiki.org/wiki/Manual:User_rights 相關的使用手冊]。",
+安裝完成後,您還可以對用戶權限進行更多、更複雜的配置,參見[//www.mediawiki.org/wiki/Manual:User_rights 相關的使用手冊]。", # Fuzzy
'config-license' => '版權和許可證:',
'config-license-none' => '頁腳無許可證',
'config-license-cc-by-sa' => '知識共享署名-相同方式分享',
@@ -19141,7 +20756,7 @@ GNU自由文檔許可證是維基百科曾經使用過的許可證,並迄今
您可能要對它們進行額外的配置,但您現在可以啟用它們。',
'config-install-alreadydone' => "'''警告:'''您似乎已經安裝了MediaWiki,並試圖重新安裝它。請前往下一個頁面。",
- 'config-install-begin' => '點擊“{{int:config-continue}}”後,您將開始安裝MediaWiki。如果您還想對配置作一些修改,請點擊後退。',
+ 'config-install-begin' => '點擊“{{int:config-continue}}”後,您將開始安裝MediaWiki。如果您還想對配置作一些修改,請點擊後退。', # Fuzzy
'config-install-step-done' => '完成',
'config-install-step-failed' => '失敗',
'config-install-extensions' => '正在啟用擴展',
@@ -19192,7 +20807,7 @@ $3
'''注意''':如果您現在不完成本步驟,而是沒有下載便退出了安裝過程,此後您將無法獲得自動生成的配置文件。
當本步驟完成後,您可以 '''[$2 進入您的wiki]'''。",
- 'config-download-localsettings' => '下載LocalSettings.php',
+ 'config-download-localsettings' => '下載<code>LocalSettings.php</code>',
'config-help' => '說明',
'mainpagetext' => "'''已成功安裝MediaWiki。'''",
'mainpagedocfooter' => '請參閱[//meta.wikimedia.org/wiki/Help:Contents 用戶手冊]以獲得使用此wiki軟體的訊息!
@@ -19200,7 +20815,8 @@ $3
== 入門 ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki配置設定清單]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki常見問題解答]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki發佈郵件清單]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki發佈郵件清單]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources MediaWiki界面本地化]',
);
/** Chinese (Hong Kong) (‪中文(香港)‬)
diff --git a/includes/installer/Installer.php b/includes/installer/Installer.php
index ac5dbd74..4d8e5f0d 100644
--- a/includes/installer/Installer.php
+++ b/includes/installer/Installer.php
@@ -88,7 +88,6 @@ abstract class Installer {
'postgres',
'oracle',
'sqlite',
- 'ibm_db2',
);
/**
@@ -313,19 +312,19 @@ abstract class Installer {
* output format such as HTML or text before being sent to the user.
* @param $msg
*/
- public abstract function showMessage( $msg /*, ... */ );
+ abstract public function showMessage( $msg /*, ... */ );
/**
* Same as showMessage(), but for displaying errors
* @param $msg
*/
- public abstract function showError( $msg /*, ... */ );
+ abstract public function showError( $msg /*, ... */ );
/**
* Show a message to the installing user by using a Status object
* @param $status Status
*/
- public abstract function showStatusMessage( Status $status );
+ abstract public function showStatusMessage( Status $status );
/**
* Constructor, always call this from child classes.
@@ -488,7 +487,7 @@ abstract class Installer {
if( !$_lsExists ) {
return false;
}
- unset($_lsExists);
+ unset( $_lsExists );
require( "$IP/includes/DefaultSettings.php" );
require( "$IP/LocalSettings.php" );
@@ -927,7 +926,7 @@ abstract class Installer {
* Helper function to be called from envCheckServer()
* @return String
*/
- protected abstract function envGetDefaultServer();
+ abstract protected function envGetDefaultServer();
/**
* Environment check for setting $IP and $wgScriptPath.
@@ -989,7 +988,7 @@ abstract class Installer {
continue;
}
- list( $all, $lang, $territory, $charset, $modifier ) = $m;
+ list( , $lang, , , ) = $m;
$candidatesByLocale[$m[0]] = $m;
$candidatesByLang[$lang][] = $m;
@@ -1073,24 +1072,23 @@ abstract class Installer {
* @return string
*/
protected function unicodeChar( $c ) {
- $c = hexdec($c);
- if ($c <= 0x7F) {
- return chr($c);
- } elseif ($c <= 0x7FF) {
- return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
- } elseif ($c <= 0xFFFF) {
- return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F)
- . chr(0x80 | $c & 0x3F);
- } elseif ($c <= 0x10FFFF) {
- return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F)
- . chr(0x80 | $c >> 6 & 0x3F)
- . chr(0x80 | $c & 0x3F);
+ $c = hexdec( $c );
+ if ( $c <= 0x7F ) {
+ return chr( $c );
+ } elseif ( $c <= 0x7FF ) {
+ return chr( 0xC0 | $c >> 6 ) . chr( 0x80 | $c & 0x3F );
+ } elseif ( $c <= 0xFFFF ) {
+ return chr( 0xE0 | $c >> 12 ) . chr( 0x80 | $c >> 6 & 0x3F )
+ . chr( 0x80 | $c & 0x3F );
+ } elseif ( $c <= 0x10FFFF ) {
+ return chr( 0xF0 | $c >> 18 ) . chr( 0x80 | $c >> 12 & 0x3F )
+ . chr( 0x80 | $c >> 6 & 0x3F )
+ . chr( 0x80 | $c & 0x3F );
} else {
return false;
}
}
-
/**
* Check the libicu version
*/
@@ -1105,8 +1103,8 @@ abstract class Installer {
* Note that we use the hex representation to create the code
* points in order to avoid any Unicode-destroying during transit.
*/
- $not_normal_c = $this->unicodeChar("FA6C");
- $normal_c = $this->unicodeChar("242EE");
+ $not_normal_c = $this->unicodeChar( "FA6C" );
+ $normal_c = $this->unicodeChar( "242EE" );
$useNormalizer = 'php';
$needsUpdate = false;
@@ -1174,8 +1172,8 @@ abstract class Installer {
*
* Used only by environment checks.
*
- * @param $path String: path to search
- * @param $names Array of executable names
+ * @param string $path path to search
+ * @param array $names of executable names
* @param $versionInfo Boolean false or array with two members:
* 0 => Command to run for version check, with $1 for the full executable name
* 1 => String to compare the output with
@@ -1279,7 +1277,7 @@ abstract class Installer {
/**
* Checks for presence of an Apache module. Works only if PHP is running as an Apache module, too.
*
- * @param $moduleName String: Name of module to check.
+ * @param string $moduleName Name of module to check.
* @return bool
*/
public static function apacheModulePresent( $moduleName ) {
@@ -1436,8 +1434,8 @@ abstract class Installer {
/**
* Actually perform the installation.
*
- * @param $startCB Array A callback array for the beginning of each step
- * @param $endCB Array A callback array for the end of each step
+ * @param array $startCB A callback array for the beginning of each step
+ * @param array $endCB A callback array for the end of each step
*
* @return Array of Status objects
*/
@@ -1594,13 +1592,16 @@ abstract class Installer {
$status = Status::newGood();
try {
$page = WikiPage::factory( Title::newMainPage() );
- $page->doEdit( wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" .
- wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text(),
+ $content = new WikitextContent (
+ wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" .
+ wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text()
+ );
+
+ $page->doEditContent( $content,
'',
EDIT_NEW,
false,
- User::newFromName( 'MediaWiki default' )
- );
+ User::newFromName( 'MediaWiki default' ) );
} catch (MWException $e) {
//using raw, because $wgShowExceptionDetails can not be set yet
$status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
@@ -1641,9 +1642,9 @@ abstract class Installer {
/**
* Add an installation step following the given step.
*
- * @param $callback Array A valid installation callback array, in this form:
+ * @param array $callback A valid installation callback array, in this form:
* array( 'name' => 'some-unique-name', 'callback' => array( $obj, 'function' ) );
- * @param $findStep String the step to find. Omit to put the step at the beginning
+ * @param string $findStep the step to find. Omit to put the step at the beginning
*/
public function addInstallStep( $callback, $findStep = 'BEGINNING' ) {
$this->extraInstallSteps[$findStep][] = $callback;
diff --git a/includes/installer/LocalSettingsGenerator.php b/includes/installer/LocalSettingsGenerator.php
index bbc6b64e..72ea3db7 100644
--- a/includes/installer/LocalSettingsGenerator.php
+++ b/includes/installer/LocalSettingsGenerator.php
@@ -62,12 +62,12 @@ class LocalSettingsGenerator {
'wgRightsText', 'wgMainCacheType', 'wgEnableUploads',
'wgMainCacheType', '_MemCachedServers', 'wgDBserver', 'wgDBuser',
'wgDBpassword', 'wgUseInstantCommons', 'wgUpgradeKey', 'wgDefaultSkin',
- 'wgMetaNamespace', 'wgResourceLoaderMaxQueryLength'
+ 'wgMetaNamespace', 'wgResourceLoaderMaxQueryLength', 'wgLogo',
),
$db->getGlobalNames()
);
- $unescaped = array( 'wgRightsIcon' );
+ $unescaped = array( 'wgRightsIcon', 'wgLogo' );
$boolItems = array(
'wgEnableEmail', 'wgEnableUserEmail', 'wgEnotifUserTalk',
'wgEnotifWatchlist', 'wgEmailAuthentication', 'wgEnableUploads', 'wgUseInstantCommons'
@@ -94,8 +94,8 @@ class LocalSettingsGenerator {
/**
* For $wgGroupPermissions, set a given ['group']['permission'] value.
- * @param $group String Group name
- * @param $rightsArr Array An array of permissions, in the form of:
+ * @param string $group Group name
+ * @param array $rightsArr An array of permissions, in the form of:
* array( 'right' => true, 'right2' => false )
*/
public function setGroupRights( $group, $rightsArr ) {
@@ -157,7 +157,7 @@ class LocalSettingsGenerator {
/**
* Write the generated LocalSettings to a file
*
- * @param $fileName String Full path to filename to write to
+ * @param string $fileName Full path to filename to write to
*/
public function writeFile( $fileName ) {
file_put_contents( $fileName, $this->getText() );
@@ -255,59 +255,59 @@ if ( !defined( 'MEDIAWIKI' ) ) {
## Uncomment this to disable output compression
# \$wgDisableOutputCompression = true;
-\$wgSitename = \"{$this->values['wgSitename']}\";
+\$wgSitename = \"{$this->values['wgSitename']}\";
{$metaNamespace}
## The URL base path to the directory containing the wiki;
## defaults for all runtime URL paths are based off of this.
## For more information on customizing the URLs
## (like /w/index.php/Page_title to /wiki/Page_title) please see:
## http://www.mediawiki.org/wiki/Manual:Short_URL
-\$wgScriptPath = \"{$this->values['wgScriptPath']}\";
-\$wgScriptExtension = \"{$this->values['wgScriptExtension']}\";
+\$wgScriptPath = \"{$this->values['wgScriptPath']}\";
+\$wgScriptExtension = \"{$this->values['wgScriptExtension']}\";
## The protocol and server name to use in fully-qualified URLs
-\$wgServer = \"{$this->values['wgServer']}\";
+\$wgServer = \"{$this->values['wgServer']}\";
## The relative URL path to the skins directory
-\$wgStylePath = \"\$wgScriptPath/skins\";
+\$wgStylePath = \"\$wgScriptPath/skins\";
## The relative URL path to the logo. Make sure you change this from the default,
## or else you'll overwrite your logo when you upgrade!
-\$wgLogo = \"\$wgStylePath/common/images/wiki.png\";
+\$wgLogo = \"{$this->values['wgLogo']}\";
## UPO means: this is also a user preference option
-\$wgEnableEmail = {$this->values['wgEnableEmail']};
-\$wgEnableUserEmail = {$this->values['wgEnableUserEmail']}; # UPO
+\$wgEnableEmail = {$this->values['wgEnableEmail']};
+\$wgEnableUserEmail = {$this->values['wgEnableUserEmail']}; # UPO
\$wgEmergencyContact = \"{$this->values['wgEmergencyContact']}\";
-\$wgPasswordSender = \"{$this->values['wgPasswordSender']}\";
+\$wgPasswordSender = \"{$this->values['wgPasswordSender']}\";
-\$wgEnotifUserTalk = {$this->values['wgEnotifUserTalk']}; # UPO
-\$wgEnotifWatchlist = {$this->values['wgEnotifWatchlist']}; # UPO
+\$wgEnotifUserTalk = {$this->values['wgEnotifUserTalk']}; # UPO
+\$wgEnotifWatchlist = {$this->values['wgEnotifWatchlist']}; # UPO
\$wgEmailAuthentication = {$this->values['wgEmailAuthentication']};
## Database settings
-\$wgDBtype = \"{$this->values['wgDBtype']}\";
-\$wgDBserver = \"{$this->values['wgDBserver']}\";
-\$wgDBname = \"{$this->values['wgDBname']}\";
-\$wgDBuser = \"{$this->values['wgDBuser']}\";
-\$wgDBpassword = \"{$this->values['wgDBpassword']}\";
+\$wgDBtype = \"{$this->values['wgDBtype']}\";
+\$wgDBserver = \"{$this->values['wgDBserver']}\";
+\$wgDBname = \"{$this->values['wgDBname']}\";
+\$wgDBuser = \"{$this->values['wgDBuser']}\";
+\$wgDBpassword = \"{$this->values['wgDBpassword']}\";
{$this->dbSettings}
## Shared memory settings
-\$wgMainCacheType = $cacheType;
+\$wgMainCacheType = $cacheType;
\$wgMemCachedServers = $mcservers;
## To enable image uploads, make sure the 'images' directory
## is writable, then set this to true:
-\$wgEnableUploads = {$this->values['wgEnableUploads']};
+\$wgEnableUploads = {$this->values['wgEnableUploads']};
{$magic}\$wgUseImageMagick = true;
{$magic}\$wgImageMagickConvertCommand = \"{$this->values['wgImageMagickConvertCommand']}\";
# InstantCommons allows wiki to use images from http://commons.wikimedia.org
-\$wgUseInstantCommons = {$this->values['wgUseInstantCommons']};
+\$wgUseInstantCommons = {$this->values['wgUseInstantCommons']};
## If you use ImageMagick (or any other shell command) on a
## Linux server, this will need to be set to the name of an
@@ -342,7 +342,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
## appropriate copyright notice / icon. GNU Free Documentation
## License and Creative Commons licenses are supported so far.
\$wgRightsPage = \"\"; # Set to the title of a wiki page that describes your license/copyright
-\$wgRightsUrl = \"{$this->values['wgRightsUrl']}\";
+\$wgRightsUrl = \"{$this->values['wgRightsUrl']}\";
\$wgRightsText = \"{$this->values['wgRightsText']}\";
\$wgRightsIcon = \"{$this->values['wgRightsIcon']}\";
diff --git a/includes/installer/MysqlInstaller.php b/includes/installer/MysqlInstaller.php
index f66f15f2..f9a8ce75 100644
--- a/includes/installer/MysqlInstaller.php
+++ b/includes/installer/MysqlInstaller.php
@@ -107,7 +107,7 @@ class MysqlInstaller extends DatabaseInstaller {
}
if ( !strlen( $newValues['wgDBname'] ) ) {
$status->fatal( 'config-missing-db-name' );
- } elseif ( !preg_match( '/^[a-z0-9_-]+$/i', $newValues['wgDBname'] ) ) {
+ } elseif ( !preg_match( '/^[a-z0-9+_-]+$/i', $newValues['wgDBname'] ) ) {
$status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] );
}
if ( !preg_match( '/^[a-z0-9_-]*$/i', $newValues['wgDBprefix'] ) ) {
@@ -516,7 +516,8 @@ class MysqlInstaller extends DatabaseInstaller {
}
if( $tryToCreate ) {
- $createHostList = array($server,
+ $createHostList = array(
+ $server,
'localhost',
'localhost.localdomain',
'%'
@@ -573,8 +574,8 @@ class MysqlInstaller extends DatabaseInstaller {
/**
* Return a formal 'User'@'Host' username for use in queries
- * @param $name String Username, quotes will be added
- * @param $host String Hostname, quotes will be added
+ * @param string $name Username, quotes will be added
+ * @param string $host Hostname, quotes will be added
* @return String
*/
private function buildFullUserName( $name, $host ) {
@@ -584,8 +585,8 @@ class MysqlInstaller extends DatabaseInstaller {
/**
* Try to see if the user account exists. Our "superuser" may not have
* access to mysql.user, so false means "no" or "maybe"
- * @param $host String Hostname to check
- * @param $user String Username to check
+ * @param string $host Hostname to check
+ * @param string $user Username to check
* @return boolean
*/
private function userDefinitelyExists( $host, $user ) {
@@ -636,10 +637,10 @@ class MysqlInstaller extends DatabaseInstaller {
$tblOpts = LocalSettingsGenerator::escapePhpString( $this->getTableOptions() );
return
"# MySQL specific settings
-\$wgDBprefix = \"{$prefix}\";
+\$wgDBprefix = \"{$prefix}\";
# MySQL table options to use during installation or update
-\$wgDBTableOptions = \"{$tblOpts}\";
+\$wgDBTableOptions = \"{$tblOpts}\";
# Experimental charset support for MySQL 5.0.
\$wgDBmysql5 = {$dbmysql5};";
diff --git a/includes/installer/MysqlUpdater.php b/includes/installer/MysqlUpdater.php
index 49dff805..9d73e629 100644
--- a/includes/installer/MysqlUpdater.php
+++ b/includes/installer/MysqlUpdater.php
@@ -31,6 +31,8 @@ class MysqlUpdater extends DatabaseUpdater {
protected function getCoreUpdateList() {
return array(
+ array( 'disableContentHandlerUseDB' ),
+
// 1.2
array( 'addField', 'ipblocks', 'ipb_id', 'patch-ipblocks.sql' ),
array( 'addField', 'ipblocks', 'ipb_expiry', 'patch-ipb_expiry.sql' ),
@@ -155,8 +157,8 @@ class MysqlUpdater extends DatabaseUpdater {
// 1.15
array( 'doUniquePlTlIl' ),
array( 'addTable', 'change_tag', 'patch-change_tag.sql' ),
- array( 'addTable', 'tag_summary', 'patch-change_tag.sql' ),
- array( 'addTable', 'valid_tag', 'patch-change_tag.sql' ),
+ /* array( 'addTable', 'tag_summary', 'patch-change_tag.sql' ), */
+ /* array( 'addTable', 'valid_tag', 'patch-change_tag.sql' ), */
// 1.16
array( 'addTable', 'user_properties', 'patch-user_properties.sql' ),
@@ -203,16 +205,35 @@ class MysqlUpdater extends DatabaseUpdater {
array( 'addField', 'revision', 'rev_sha1', 'patch-rev_sha1.sql' ),
array( 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1.sql' ),
array( 'addIndex', 'page', 'page_redirect_namespace_len', 'patch-page_redirect_namespace_len.sql' ),
- array( 'modifyField', 'user_groups', 'ug_group', 'patch-ug_group-length-increase.sql' ),
array( 'addField', 'uploadstash', 'us_chunk_inx', 'patch-uploadstash_chunk.sql' ),
array( 'addfield', 'job', 'job_timestamp', 'patch-jobs-add-timestamp.sql' ),
- array( 'modifyField', 'user_former_groups', 'ufg_group', 'patch-ufg_group-length-increase.sql' ),
// 1.20
array( 'addIndex', 'revision', 'page_user_timestamp', 'patch-revision-user-page-index.sql' ),
array( 'addField', 'ipblocks', 'ipb_parent_block_id', 'patch-ipb-parent-block-id.sql' ),
array( 'addIndex', 'ipblocks', 'ipb_parent_block_id', 'patch-ipb-parent-block-id-index.sql' ),
array( 'dropField', 'category', 'cat_hidden', 'patch-cat_hidden.sql' ),
+
+ // 1.21
+ array( 'addField', 'revision', 'rev_content_format', 'patch-revision-rev_content_format.sql' ),
+ array( 'addField', 'revision', 'rev_content_model', 'patch-revision-rev_content_model.sql' ),
+ array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ),
+ array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ),
+ array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ),
+ array( 'enableContentHandlerUseDB' ),
+
+ array( 'dropField', 'site_stats', 'ss_admins', 'patch-drop-ss_admins.sql' ),
+ array( 'dropField', 'recentchanges', 'rc_moved_to_title', 'patch-rc_moved.sql' ),
+ array( 'addTable', 'sites', 'patch-sites.sql' ),
+ array( 'addField', 'filearchive', 'fa_sha1', 'patch-fa_sha1.sql' ),
+ array( 'addField', 'job', 'job_token', 'patch-job_token.sql' ),
+ array( 'addField', 'job', 'job_attempts', 'patch-job_attempts.sql' ),
+ array( 'doEnableProfiling' ),
+ array( 'addField', 'uploadstash', 'us_props', 'patch-uploadstash-us_props.sql' ),
+ array( 'modifyField', 'user_groups', 'ug_group', 'patch-ug_group-length-increase-255.sql' ),
+ array( 'modifyField', 'user_former_groups', 'ufg_group', 'patch-ufg_group-length-increase-255.sql' ),
+ array( 'addIndex', 'page_props', 'pp_propname_page', 'patch-page_props-propname-page-index.sql' ),
+ array( 'addIndex', 'image', 'img_media_mime', 'patch-img_media_mime-index.sql' ),
);
}
@@ -220,11 +241,15 @@ class MysqlUpdater extends DatabaseUpdater {
* 1.4 betas were missing the 'binary' marker from logging.log_title,
* which causes a collation mismatch error on joins in MySQL 4.1.
*
- * @param $table String: table name
- * @param $field String: field name to check
- * @param $patchFile String: path to the patch to correct the field
+ * @param string $table table name
+ * @param string $field field name to check
+ * @param string $patchFile path to the patch to correct the field
*/
protected function checkBin( $table, $field, $patchFile ) {
+ if ( !$this->doTable( $table ) ) {
+ return true;
+ }
+
$tableName = $this->db->tableName( $table );
$res = $this->db->query( "SELECT $field FROM $tableName LIMIT 0", __METHOD__ );
$flags = explode( ' ', mysql_field_flags( $res->result, 0 ) );
@@ -239,12 +264,16 @@ class MysqlUpdater extends DatabaseUpdater {
/**
* Check whether an index contain a field
*
- * @param $table String: table name
- * @param $index String: index name to check
- * @param $field String: field that should be in the index
+ * @param string $table table name
+ * @param string $index index name to check
+ * @param string $field field that should be in the index
* @return Boolean
*/
protected function indexHasField( $table, $index, $field ) {
+ if ( !$this->doTable( $table ) ) {
+ return true;
+ }
+
$info = $this->db->indexInfo( $table, $index, __METHOD__ );
if ( $info ) {
foreach ( $info as $row ) {
@@ -264,6 +293,10 @@ class MysqlUpdater extends DatabaseUpdater {
protected function doInterwikiUpdate() {
global $IP;
+ if ( !$this->doTable( 'interwiki' ) ) {
+ return true;
+ }
+
if ( $this->db->tableExists( "interwiki", __METHOD__ ) ) {
$this->output( "...already have interwiki table\n" );
return;
@@ -302,7 +335,7 @@ class MysqlUpdater extends DatabaseUpdater {
}
if( $this->applyPatch( 'patch-fix-il_from.sql', false, "Fixing ancient broken imagelinks table." ) ) {
- $this->output("NOTE: you will have to run maintenance/refreshLinks.php after this." );
+ $this->output( "NOTE: you will have to run maintenance/refreshLinks.php after this." );
}
}
@@ -388,7 +421,7 @@ class MysqlUpdater extends DatabaseUpdater {
if ( $prev_title == $row->cur_title && $prev_namespace == $row->cur_namespace ) {
$deleteId[] = $row->cur_id;
}
- $prev_title = $row->cur_title;
+ $prev_title = $row->cur_title;
$prev_namespace = $row->cur_namespace;
}
$sql = "DELETE FROM $cur WHERE cur_id IN ( " . join( ',', $deleteId ) . ')';
@@ -552,6 +585,10 @@ class MysqlUpdater extends DatabaseUpdater {
}
protected function doUserUniqueUpdate() {
+ if ( !$this->doTable( 'user' ) ) {
+ return true;
+ }
+
$duper = new UserDupes( $this->db, array( $this, 'output' ) );
if ( $duper->hasUniqueIndex() ) {
$this->output( "...already have unique user_name index.\n" );
@@ -565,6 +602,10 @@ class MysqlUpdater extends DatabaseUpdater {
}
protected function doUserGroupsUpdate() {
+ if ( !$this->doTable( 'user_groups' ) ) {
+ return true;
+ }
+
if ( $this->db->tableExists( 'user_groups', __METHOD__ ) ) {
$info = $this->db->fieldInfo( 'user_groups', 'ug_group' );
if ( $info->type() == 'int' ) {
@@ -627,6 +668,9 @@ class MysqlUpdater extends DatabaseUpdater {
*/
protected function doWatchlistNull() {
$info = $this->db->fieldInfo( 'watchlist', 'wl_notificationtimestamp' );
+ if ( !$info ) {
+ return;
+ }
if ( $info->isNullable() ) {
$this->output( "...wl_notificationtimestamp is already nullable.\n" );
return;
@@ -759,14 +803,30 @@ class MysqlUpdater extends DatabaseUpdater {
}
}
+ protected function doEnableProfiling() {
+ global $wgProfileToDatabase;
+
+ if ( !$this->doTable( 'profiling' ) ) {
+ return true;
+ }
+
+ if ( $wgProfileToDatabase === true && ! $this->db->tableExists( 'profiling', __METHOD__ ) ) {
+ $this->applyPatch( 'patch-profiling.sql', false, 'Add profiling table' );
+ }
+ }
+
protected function doMaybeProfilingMemoryUpdate() {
+ if ( !$this->doTable( 'profiling' ) ) {
+ return true;
+ }
+
if ( !$this->db->tableExists( 'profiling', __METHOD__ ) ) {
- // Simply ignore
+ return true;
} elseif ( $this->db->fieldExists( 'profiling', 'pf_memory', __METHOD__ ) ) {
$this->output( "...profiling table has pf_memory field.\n" );
- } else {
- $this->applyPatch( 'patch-profiling-memory.sql', false, "Adding pf_memory field to table profiling" );
+ return true;
}
+ return $this->applyPatch( 'patch-profiling-memory.sql', false, "Adding pf_memory field to table profiling" );
}
protected function doFilearchiveIndicesUpdate() {
@@ -774,16 +834,21 @@ class MysqlUpdater extends DatabaseUpdater {
if ( !$info ) {
$this->applyPatch( 'patch-filearchive-user-index.sql', false, "Updating filearchive indices" );
}
+ return true;
}
protected function doUniquePlTlIl() {
$info = $this->db->indexInfo( 'pagelinks', 'pl_namespace' );
if ( is_array( $info ) && !$info[0]->Non_unique ) {
$this->output( "...pl_namespace, tl_namespace, il_to indices are already UNIQUE.\n" );
- return;
+ return true;
+ }
+ if ( $this->skipSchema ) {
+ $this->output( "...skipping schema change (making pl_namespace, tl_namespace and il_to indices UNIQUE).\n" );
+ return false;
}
- $this->applyPatch( 'patch-pl-tl-il-unique.sql', false, "Making pl_namespace, tl_namespace and il_to indices UNIQUE" );
+ return $this->applyPatch( 'patch-pl-tl-il-unique.sql', false, "Making pl_namespace, tl_namespace and il_to indices UNIQUE" );
}
protected function renameEuWikiId() {
@@ -826,7 +891,14 @@ class MysqlUpdater extends DatabaseUpdater {
}
protected function doUserNewTalkTimestampNotNull() {
+ if ( !$this->doTable( 'user_newtalk' ) ) {
+ return true;
+ }
+
$info = $this->db->fieldInfo( 'user_newtalk', 'user_last_timestamp' );
+ if ( $info === false ) {
+ return;
+ }
if ( $info->isNullable() ) {
$this->output( "...user_last_timestamp is already nullable.\n" );
return;
diff --git a/includes/installer/OracleInstaller.php b/includes/installer/OracleInstaller.php
index 72ec800d..e8538890 100644
--- a/includes/installer/OracleInstaller.php
+++ b/includes/installer/OracleInstaller.php
@@ -40,7 +40,7 @@ class OracleInstaller extends DatabaseInstaller {
protected $internalDefaults = array(
'_OracleDefTS' => 'USERS',
'_OracleTempTS' => 'TEMP',
- '_InstallUser' => 'SYSDBA',
+ '_InstallUser' => 'SYSTEM',
);
public $minimumVersion = '9.0.1'; // 9iR1
@@ -119,7 +119,7 @@ class OracleInstaller extends DatabaseInstaller {
return $status;
}
if ( !$this->getVar( '_CreateDBAccount' ) ) {
- $status->fatal('config-db-sys-create-oracle');
+ $status->fatal( 'config-db-sys-create-oracle' );
}
} else {
return $status;
@@ -202,7 +202,6 @@ class OracleInstaller extends DatabaseInstaller {
$this->parent->addInstallStep( $callback, 'database' );
}
-
public function setupDatabase() {
$status = Status::newGood();
return $status;
@@ -241,7 +240,7 @@ class OracleInstaller extends DatabaseInstaller {
$status->fatal( 'config-db-sys-user-exists-oracle', $this->getVar( 'wgDBuser' ) );
}
- if ($status->isOK()) {
+ if ( $status->isOK() ) {
// user created or already existing, switching back to a normal connection
// as the new user has all needed privileges to setup the rest of the schema
// i will be using that user as _InstallUser from this point on
@@ -294,7 +293,7 @@ class OracleInstaller extends DatabaseInstaller {
$prefix = $this->getVar( 'wgDBprefix' );
return
"# Oracle specific settings
-\$wgDBprefix = \"{$prefix}\";
+\$wgDBprefix = \"{$prefix}\";
";
}
diff --git a/includes/installer/OracleUpdater.php b/includes/installer/OracleUpdater.php
index e71c26fe..b416f4b6 100644
--- a/includes/installer/OracleUpdater.php
+++ b/includes/installer/OracleUpdater.php
@@ -38,6 +38,8 @@ class OracleUpdater extends DatabaseUpdater {
protected function getCoreUpdateList() {
return array(
+ array( 'disableContentHandlerUseDB' ),
+
// 1.17
array( 'doNamespaceDefaults' ),
array( 'doFKRenameDeferr' ),
@@ -59,16 +61,33 @@ class OracleUpdater extends DatabaseUpdater {
array( 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1_field.sql' ),
array( 'doRemoveNotNullEmptyDefaults2' ),
array( 'addIndex', 'page', 'i03', 'patch-page_redirect_namespace_len.sql' ),
- array( 'modifyField', 'user_groups', 'ug_group', 'patch-ug_group-length-increase.sql' ),
array( 'addField', 'uploadstash', 'us_chunk_inx', 'patch-us_chunk_inx_field.sql' ),
array( 'addField', 'job', 'job_timestamp', 'patch-job_timestamp_field.sql' ),
array( 'addIndex', 'job', 'i02', 'patch-job_timestamp_index.sql' ),
array( 'doPageRestrictionsPKUKFix' ),
- array( 'modifyField', 'user_former_groups', 'ufg_group', 'patch-ufg_group-length-increase.sql' ),
//1.20
array( 'addIndex', 'ipblocks', 'i05', 'patch-ipblocks_i05_index.sql' ),
array( 'addIndex', 'revision', 'i05', 'patch-revision_i05_index.sql' ),
+ array( 'dropField', 'category', 'cat_hidden', 'patch-cat_hidden.sql' ),
+
+ //1.21
+ array( 'addField', 'revision', 'rev_content_format', 'patch-revision-rev_content_format.sql' ),
+ array( 'addField', 'revision', 'rev_content_model', 'patch-revision-rev_content_model.sql' ),
+ array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ),
+ array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ),
+ array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ),
+ array( 'enableContentHandlerUseDB' ),
+
+ array( 'dropField', 'site_stats', 'ss_admins', 'patch-ss_admins.sql' ),
+ array( 'dropField', 'recentchanges', 'rc_moved_to_title', 'patch-rc_moved.sql' ),
+ array( 'addTable', 'sites', 'patch-sites.sql' ),
+ array( 'addField', 'filearchive', 'fa_sha1', 'patch-fa_sha1.sql' ),
+ array( 'addField', 'job', 'job_token', 'patch-job_token.sql' ),
+ array( 'addField', 'job', 'job_attempts', 'patch-job_attempts.sql' ),
+ array( 'addField', 'uploadstash', 'us_props', 'patch-uploadstash-us_props.sql' ),
+ array( 'modifyField', 'user_groups', 'ug_group', 'patch-ug_group-length-increase-255.sql' ),
+ array( 'modifyField', 'user_former_groups', 'ufg_group', 'patch-ufg_group-length-increase-255.sql' ),
// KEEP THIS AT THE BOTTOM!!
array( 'doRebuildDuplicateFunction' ),
@@ -148,7 +167,7 @@ class OracleUpdater extends DatabaseUpdater {
* converted to NULL in Oracle
*/
protected function doRemoveNotNullEmptyDefaults() {
- $meta = $this->db->fieldInfo( 'categorylinks' , 'cl_sortkey_prefix' );
+ $meta = $this->db->fieldInfo( 'categorylinks', 'cl_sortkey_prefix' );
if ( $meta->isNullable() ) {
return;
}
@@ -156,7 +175,7 @@ class OracleUpdater extends DatabaseUpdater {
}
protected function doRemoveNotNullEmptyDefaults2() {
- $meta = $this->db->fieldInfo( 'ipblocks' , 'ipb_by_text' );
+ $meta = $this->db->fieldInfo( 'ipblocks', 'ipb_by_text' );
if ( $meta->isNullable() ) {
return;
}
@@ -215,7 +234,7 @@ class OracleUpdater extends DatabaseUpdater {
/**
* Overload: because of the DDL_MODE tablename escaping is a bit dodgy
*/
- protected function purgeCache() {
+ public function purgeCache() {
# We can't guarantee that the user will be able to use TRUNCATE,
# but we know that DELETE is available to us
$this->output( "Purging caches..." );
diff --git a/includes/installer/PostgresInstaller.php b/includes/installer/PostgresInstaller.php
index 3ac2b3a8..4e5ae8cf 100644
--- a/includes/installer/PostgresInstaller.php
+++ b/includes/installer/PostgresInstaller.php
@@ -125,9 +125,9 @@ class PostgresInstaller extends DatabaseInstaller {
/**
* Open a PG connection with given parameters
- * @param $user string User name
- * @param $password string Password
- * @param $dbName string Database name
+ * @param string $user User name
+ * @param string $password Password
+ * @param string $dbName Database name
* @return Status
*/
protected function openConnectionWithParams( $user, $password, $dbName ) {
@@ -147,7 +147,7 @@ class PostgresInstaller extends DatabaseInstaller {
/**
* Get a special type of connection
- * @param $type string See openPgConnection() for details.
+ * @param string $type See openPgConnection() for details.
* @return Status
*/
protected function getPgConnection( $type ) {
@@ -183,13 +183,14 @@ class PostgresInstaller extends DatabaseInstaller {
* separate connection for this allows us to avoid accidental cross-module
* dependencies.
*
- * @param $type string The type of connection to get:
+ * @param string $type The type of connection to get:
* - create-db: A connection for creating DBs, suitable for pre-
* installation.
* - create-schema: A connection to the new DB, for creating schemas and
* other similar objects in the new DB.
* - create-tables: A connection with a role suitable for creating tables.
*
+ * @throws MWException
* @return Status object. On success, a connection object will be in the
* value member.
*/
@@ -382,9 +383,9 @@ class PostgresInstaller extends DatabaseInstaller {
/**
* Recursive helper for canCreateObjectsForWebUser().
* @param $conn DatabaseBase object
- * @param $targetMember int Role ID of the member to look for
- * @param $group int Role ID of the group to look for
- * @param $maxDepth int Maximum recursive search depth
+ * @param int $targetMember Role ID of the member to look for
+ * @param int $group Role ID of the group to look for
+ * @param int $maxDepth Maximum recursive search depth
* @return bool
*/
protected function isRoleMember( $conn, $targetMember, $group, $maxDepth ) {
@@ -530,8 +531,8 @@ class PostgresInstaller extends DatabaseInstaller {
$schema = $this->getVar( 'wgDBmwschema' );
return
"# Postgres specific settings
-\$wgDBport = \"{$port}\";
-\$wgDBmwschema = \"{$schema}\";";
+\$wgDBport = \"{$port}\";
+\$wgDBmwschema = \"{$schema}\";";
}
public function preUpgrade() {
diff --git a/includes/installer/PostgresUpdater.php b/includes/installer/PostgresUpdater.php
index 6cffe84a..0a4b5e65 100644
--- a/includes/installer/PostgresUpdater.php
+++ b/includes/installer/PostgresUpdater.php
@@ -90,6 +90,7 @@ class PostgresUpdater extends DatabaseUpdater {
array( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ),
array( 'addTable', 'user_former_groups','patch-user_former_groups.sql' ),
array( 'addTable', 'external_user', 'patch-external_user.sql' ),
+ array( 'addTable', 'sites', 'patch-sites.sql' ),
# Needed before new field
array( 'convertArchive2' ),
@@ -100,6 +101,8 @@ class PostgresUpdater extends DatabaseUpdater {
array( 'addPgField', 'archive', 'ar_len', 'INTEGER' ),
array( 'addPgField', 'archive', 'ar_page_id', 'INTEGER' ),
array( 'addPgField', 'archive', 'ar_parent_id', 'INTEGER' ),
+ array( 'addPgField', 'archive', 'ar_content_model', 'TEXT' ),
+ array( 'addPgField', 'archive', 'ar_content_format', 'TEXT' ),
array( 'addPgField', 'categorylinks', 'cl_sortkey_prefix', "TEXT NOT NULL DEFAULT ''"),
array( 'addPgField', 'categorylinks', 'cl_collation', "TEXT NOT NULL DEFAULT 0"),
array( 'addPgField', 'categorylinks', 'cl_type', "TEXT NOT NULL DEFAULT 'page'"),
@@ -113,6 +116,7 @@ class PostgresUpdater extends DatabaseUpdater {
array( 'addPgField', 'ipblocks', 'ipb_enable_autoblock', 'SMALLINT NOT NULL DEFAULT 1' ),
array( 'addPgField', 'ipblocks', 'ipb_parent_block_id', 'INTEGER DEFAULT NULL REFERENCES ipblocks(ipb_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED' ),
array( 'addPgField', 'filearchive', 'fa_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
+ array( 'addPgField', 'filearchive', 'fa_sha1', "TEXT NOT NULL DEFAULT ''" ),
array( 'addPgField', 'logging', 'log_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
array( 'addPgField', 'logging', 'log_id', "INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('logging_log_id_seq')" ),
array( 'addPgField', 'logging', 'log_params', 'TEXT' ),
@@ -124,6 +128,7 @@ class PostgresUpdater extends DatabaseUpdater {
array( 'addPgField', 'oldimage', 'oi_metadata', "BYTEA NOT NULL DEFAULT ''" ),
array( 'addPgField', 'oldimage', 'oi_minor_mime', "TEXT NOT NULL DEFAULT 'unknown'" ),
array( 'addPgField', 'oldimage', 'oi_sha1', "TEXT NOT NULL DEFAULT ''" ),
+ array( 'addPgField', 'page', 'page_content_model', 'TEXT' ),
array( 'addPgField', 'page_restrictions', 'pr_id', "INTEGER NOT NULL UNIQUE DEFAULT nextval('page_restrictions_pr_id_seq')" ),
array( 'addPgField', 'profiling', 'pf_memory', 'NUMERIC(18,10) NOT NULL DEFAULT 0' ),
array( 'addPgField', 'recentchanges', 'rc_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
@@ -138,6 +143,8 @@ class PostgresUpdater extends DatabaseUpdater {
array( 'addPgField', 'revision', 'rev_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
array( 'addPgField', 'revision', 'rev_len', 'INTEGER' ),
array( 'addPgField', 'revision', 'rev_parent_id', 'INTEGER DEFAULT NULL' ),
+ array( 'addPgField', 'revision', 'rev_content_model', 'TEXT' ),
+ array( 'addPgField', 'revision', 'rev_content_format', 'TEXT' ),
array( 'addPgField', 'site_stats', 'ss_active_users', "INTEGER DEFAULT '-1'" ),
array( 'addPgField', 'user_newtalk', 'user_last_timestamp', 'TIMESTAMPTZ' ),
array( 'addPgField', 'logging', 'log_user_text', "TEXT NOT NULL DEFAULT ''" ),
@@ -148,6 +155,11 @@ class PostgresUpdater extends DatabaseUpdater {
array( 'addPgField', 'archive', 'ar_sha1', "TEXT NOT NULL DEFAULT ''" ),
array( 'addPgField', 'uploadstash', 'us_chunk_inx', "INTEGER NULL" ),
array( 'addPgField', 'job', 'job_timestamp', "TIMESTAMPTZ" ),
+ array( 'addPgField', 'job', 'job_random', "INTEGER NOT NULL DEFAULT 0" ),
+ array( 'addPgField', 'job', 'job_attempts', "INTEGER NOT NULL DEFAULT 0" ),
+ array( 'addPgField', 'job', 'job_token', "TEXT NOT NULL DEFAULT ''" ),
+ array( 'addPgField', 'job', 'job_token_timestamp', "TIMESTAMPTZ" ),
+ array( 'addPgField', 'job', 'job_sha1', "TEXT NOT NULL DEFAULT ''" ),
# type changes
array( 'changeField', 'archive', 'ar_deleted', 'smallint', '' ),
@@ -213,6 +225,7 @@ class PostgresUpdater extends DatabaseUpdater {
array( 'addPgIndex', 'oldimage', 'oi_sha1', '(oi_sha1)' ),
array( 'addPgIndex', 'page', 'page_mediawiki_title', '(page_title) WHERE page_namespace = 8' ),
array( 'addPgIndex', 'pagelinks', 'pagelinks_title', '(pl_title)' ),
+ array( 'addPgIndex', 'page_props', 'pp_propname_page', '(pp_propname, pp_page)' ),
array( 'addPgIndex', 'revision', 'rev_text_id_idx', '(rev_text_id)' ),
array( 'addPgIndex', 'recentchanges', 'rc_timestamp_bot', '(rc_timestamp) WHERE rc_bot = 0' ),
array( 'addPgIndex', 'templatelinks', 'templatelinks_from', '(tl_from)' ),
@@ -221,64 +234,68 @@ class PostgresUpdater extends DatabaseUpdater {
array( 'addPgIndex', 'logging', 'logging_page_id_time', '(log_page,log_timestamp)' ),
array( 'addPgIndex', 'iwlinks', 'iwl_prefix_title_from', '(iwl_prefix, iwl_title, iwl_from)' ),
array( 'addPgIndex', 'job', 'job_timestamp_idx', '(job_timestamp)' ),
+ array( 'addPgIndex', 'job', 'job_sha1', '(job_sha1)' ),
+ array( 'addPgIndex', 'job', 'job_cmd_token', '(job_cmd, job_token, job_random)' ),
+ array( 'addPgIndex', 'job', 'job_cmd_token_id', '(job_cmd, job_token, job_id)' ),
+ array( 'addPgIndex', 'filearchive', 'fa_sha1', '(fa_sha1)' ),
array( 'checkIndex', 'pagelink_unique', array(
- array('pl_from', 'int4_ops', 'btree', 0),
- array('pl_namespace', 'int2_ops', 'btree', 0),
- array('pl_title', 'text_ops', 'btree', 0),
+ array( 'pl_from', 'int4_ops', 'btree', 0 ),
+ array( 'pl_namespace', 'int2_ops', 'btree', 0 ),
+ array( 'pl_title', 'text_ops', 'btree', 0 ),
),
'CREATE UNIQUE INDEX pagelink_unique ON pagelinks (pl_from,pl_namespace,pl_title)' ),
array( 'checkIndex', 'cl_sortkey', array(
- array('cl_to', 'text_ops', 'btree', 0),
- array('cl_sortkey', 'text_ops', 'btree', 0),
- array('cl_from', 'int4_ops', 'btree', 0),
+ array( 'cl_to', 'text_ops', 'btree', 0 ),
+ array( 'cl_sortkey', 'text_ops', 'btree', 0 ),
+ array( 'cl_from', 'int4_ops', 'btree', 0 ),
),
'CREATE INDEX cl_sortkey ON "categorylinks" USING "btree" ("cl_to", "cl_sortkey", "cl_from")' ),
array( 'checkIndex', 'logging_times', array(
- array('log_timestamp', 'timestamptz_ops', 'btree', 0),
+ array( 'log_timestamp', 'timestamptz_ops', 'btree', 0 ),
),
'CREATE INDEX "logging_times" ON "logging" USING "btree" ("log_timestamp")' ),
array( 'dropIndex', 'oldimage', 'oi_name' ),
array( 'checkIndex', 'oi_name_archive_name', array(
- array('oi_name', 'text_ops', 'btree', 0),
- array('oi_archive_name', 'text_ops', 'btree', 0),
+ array( 'oi_name', 'text_ops', 'btree', 0 ),
+ array( 'oi_archive_name', 'text_ops', 'btree', 0 ),
),
'CREATE INDEX "oi_name_archive_name" ON "oldimage" USING "btree" ("oi_name", "oi_archive_name")' ),
array( 'checkIndex', 'oi_name_timestamp', array(
- array('oi_name', 'text_ops', 'btree', 0),
- array('oi_timestamp', 'timestamptz_ops', 'btree', 0),
+ array( 'oi_name', 'text_ops', 'btree', 0 ),
+ array( 'oi_timestamp', 'timestamptz_ops', 'btree', 0 ),
),
'CREATE INDEX "oi_name_timestamp" ON "oldimage" USING "btree" ("oi_name", "oi_timestamp")' ),
array( 'checkIndex', 'page_main_title', array(
- array('page_title', 'text_pattern_ops', 'btree', 0),
+ array( 'page_title', 'text_pattern_ops', 'btree', 0 ),
),
'CREATE INDEX "page_main_title" ON "page" USING "btree" ("page_title" "text_pattern_ops") WHERE ("page_namespace" = 0)' ),
array( 'checkIndex', 'page_mediawiki_title', array(
- array('page_title', 'text_pattern_ops', 'btree', 0),
+ array( 'page_title', 'text_pattern_ops', 'btree', 0 ),
),
'CREATE INDEX "page_mediawiki_title" ON "page" USING "btree" ("page_title" "text_pattern_ops") WHERE ("page_namespace" = 8)' ),
array( 'checkIndex', 'page_project_title', array(
- array('page_title', 'text_pattern_ops', 'btree', 0),
+ array( 'page_title', 'text_pattern_ops', 'btree', 0 ),
),
'CREATE INDEX "page_project_title" ON "page" USING "btree" ("page_title" "text_pattern_ops") WHERE ("page_namespace" = 4)' ),
array( 'checkIndex', 'page_talk_title', array(
- array('page_title', 'text_pattern_ops', 'btree', 0),
+ array( 'page_title', 'text_pattern_ops', 'btree', 0 ),
),
'CREATE INDEX "page_talk_title" ON "page" USING "btree" ("page_title" "text_pattern_ops") WHERE ("page_namespace" = 1)' ),
array( 'checkIndex', 'page_user_title', array(
- array('page_title', 'text_pattern_ops', 'btree', 0),
+ array( 'page_title', 'text_pattern_ops', 'btree', 0 ),
),
'CREATE INDEX "page_user_title" ON "page" USING "btree" ("page_title" "text_pattern_ops") WHERE ("page_namespace" = 2)' ),
array( 'checkIndex', 'page_utalk_title', array(
- array('page_title', 'text_pattern_ops', 'btree', 0),
+ array( 'page_title', 'text_pattern_ops', 'btree', 0 ),
),
'CREATE INDEX "page_utalk_title" ON "page" USING "btree" ("page_title" "text_pattern_ops") WHERE ("page_namespace" = 3)' ),
array( 'checkIndex', 'ts2_page_text', array(
- array('textvector', 'tsvector_ops', 'gist', 0),
+ array( 'textvector', 'tsvector_ops', 'gist', 0 ),
),
'CREATE INDEX "ts2_page_text" ON "pagecontent" USING "gist" ("textvector")' ),
array( 'checkIndex', 'ts2_page_title', array(
- array('titlevector', 'tsvector_ops', 'gist', 0),
+ array( 'titlevector', 'tsvector_ops', 'gist', 0 ),
),
'CREATE INDEX "ts2_page_title" ON "page" USING "gist" ("titlevector")' ),
@@ -287,10 +304,10 @@ class PostgresUpdater extends DatabaseUpdater {
array( 'checkRevUserFkey' ),
array( 'dropIndex', 'ipblocks', 'ipb_address'),
array( 'checkIndex', 'ipb_address_unique', array(
- array('ipb_address', 'text_ops', 'btree', 0),
- array('ipb_user', 'int4_ops', 'btree', 0),
- array('ipb_auto', 'int2_ops', 'btree', 0),
- array('ipb_anon_only', 'int2_ops', 'btree', 0),
+ array( 'ipb_address', 'text_ops', 'btree', 0 ),
+ array( 'ipb_user', 'int4_ops', 'btree', 0 ),
+ array( 'ipb_auto', 'int2_ops', 'btree', 0 ),
+ array( 'ipb_anon_only', 'int2_ops', 'btree', 0 ),
),
'CREATE UNIQUE INDEX ipb_address_unique ON ipblocks (ipb_address,ipb_user,ipb_auto,ipb_anon_only)' ),
@@ -493,7 +510,7 @@ END;
$this->output( "Creating sequence $ns\n" );
$this->db->query( "CREATE SEQUENCE $ns" );
if( $pkey !== false ) {
- $this->setDefault( $table, $pkey, '"nextval"(\'"' . $ns . '"\'::"regclass")' );
+ $this->setDefault( $table, $pkey, '"nextval"(\'"' . $ns . '"\'::"regclass")' );
}
}
}
@@ -521,11 +538,34 @@ END;
}
}
- protected function renameIndex( $table, $old, $new ) {
- if ( $this->db->indexExists( $table, $old ) ) {
- $this->output( "Renaming index $old to $new\n" );
- $this->db->query( "ALTER INDEX $old RENAME TO $new" );
+ protected function renameIndex(
+ $table, $old, $new, $skipBothIndexExistWarning = false, $a = false, $b = false
+ ) {
+ // First requirement: the table must exist
+ if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
+ $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
+ return;
+ }
+
+ // Second requirement: the new index must be missing
+ if ( $this->db->indexExists( $table, $new, __METHOD__ ) ) {
+ $this->output( "...index $new already set on $table table.\n" );
+ if ( !$skipBothIndexExistWarning
+ && $this->db->indexExists( $table, $old, __METHOD__ ) )
+ {
+ $this->output( "...WARNING: $old still exists, despite it has been renamed into $new (which also exists).\n" .
+ " $old should be manually removed if not needed anymore.\n" );
+ }
+ return;
}
+
+ // Third requirement: the old index must exist
+ if ( !$this->db->indexExists( $table, $old, __METHOD__ ) ) {
+ $this->output( "...skipping: index $old doesn't exist.\n" );
+ return;
+ }
+
+ $this->db->query( "ALTER INDEX $old RENAME TO $new" );
}
protected function addPgField( $table, $field, $type ) {
diff --git a/includes/installer/SqliteInstaller.php b/includes/installer/SqliteInstaller.php
index 6e1a74f6..68df6ab2 100644
--- a/includes/installer/SqliteInstaller.php
+++ b/includes/installer/SqliteInstaller.php
@@ -113,6 +113,8 @@ class SqliteInstaller extends DatabaseInstaller {
$dir = self::realpath( $dir );
$this->setVar( 'wgSQLiteDataDir', $dir );
}
+ # Table prefix is not used on SQLite, keep it empty
+ $this->setVar( 'wgDBprefix', '' );
return $result;
}
@@ -238,7 +240,7 @@ class SqliteInstaller extends DatabaseInstaller {
$module = DatabaseSqlite::getFulltextSearchModule();
$fts3tTable = $this->db->checkForEnabledSearch();
- if ( $fts3tTable && !$module ) {
+ if ( $fts3tTable && !$module ) {
$status->warning( 'config-sqlite-fts3-downgrade' );
$this->db->sourceFile( "$IP/maintenance/sqlite/archives/searchindex-no-fts.sql" );
} elseif ( !$fts3tTable && $module == 'FTS3' ) {
@@ -254,6 +256,6 @@ class SqliteInstaller extends DatabaseInstaller {
$dir = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgSQLiteDataDir' ) );
return
"# SQLite-specific settings
-\$wgSQLiteDataDir = \"{$dir}\";";
+\$wgSQLiteDataDir = \"{$dir}\";";
}
}
diff --git a/includes/installer/SqliteUpdater.php b/includes/installer/SqliteUpdater.php
index 12a310af..2064842a 100644
--- a/includes/installer/SqliteUpdater.php
+++ b/includes/installer/SqliteUpdater.php
@@ -31,6 +31,8 @@ class SqliteUpdater extends DatabaseUpdater {
protected function getCoreUpdateList() {
return array(
+ array( 'disableContentHandlerUseDB' ),
+
// 1.14
array( 'addField', 'site_stats', 'ss_active_users', 'patch-ss_active_users.sql' ),
array( 'doActiveUsersInit' ),
@@ -82,16 +84,35 @@ class SqliteUpdater extends DatabaseUpdater {
array( 'addField', 'revision', 'rev_sha1', 'patch-rev_sha1.sql' ),
array( 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1.sql' ),
array( 'addIndex', 'page', 'page_redirect_namespace_len', 'patch-page_redirect_namespace_len.sql' ),
- array( 'modifyField', 'user_groups', 'ug_group', 'patch-ug_group-length-increase.sql' ),
array( 'addField', 'uploadstash', 'us_chunk_inx', 'patch-uploadstash_chunk.sql' ),
array( 'addfield', 'job', 'job_timestamp', 'patch-jobs-add-timestamp.sql' ),
- array( 'modifyField', 'user_former_groups', 'ufg_group', 'patch-ug_group-length-increase.sql' ),
// 1.20
array( 'addIndex', 'revision', 'page_user_timestamp', 'patch-revision-user-page-index.sql' ),
array( 'addField', 'ipblocks', 'ipb_parent_block_id', 'patch-ipb-parent-block-id.sql' ),
array( 'addIndex', 'ipblocks', 'ipb_parent_block_id', 'patch-ipb-parent-block-id-index.sql' ),
array( 'dropField', 'category', 'cat_hidden', 'patch-cat_hidden.sql' ),
+
+ // 1.21
+ array( 'addField', 'revision', 'rev_content_format', 'patch-revision-rev_content_format.sql' ),
+ array( 'addField', 'revision', 'rev_content_model', 'patch-revision-rev_content_model.sql' ),
+ array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ),
+ array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ),
+ array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ),
+ array( 'enableContentHandlerUseDB' ),
+
+ array( 'dropField', 'site_stats', 'ss_admins', 'patch-drop-ss_admins.sql' ),
+ array( 'dropField', 'recentchanges', 'rc_moved_to_title', 'patch-rc_moved.sql' ),
+ array( 'addTable', 'sites', 'patch-sites.sql' ),
+ array( 'addField', 'filearchive', 'fa_sha1', 'patch-fa_sha1.sql' ),
+ array( 'addField', 'job', 'job_token', 'patch-job_token.sql' ),
+ array( 'addField', 'job', 'job_attempts', 'patch-job_attempts.sql' ),
+ array( 'doEnableProfiling' ),
+ array( 'addField', 'uploadstash', 'us_props', 'patch-uploadstash-us_props.sql' ),
+ array( 'modifyField', 'user_groups', 'ug_group', 'patch-ug_group-length-increase-255.sql' ),
+ array( 'modifyField', 'user_former_groups', 'ufg_group', 'patch-ufg_group-length-increase-255.sql' ),
+ array( 'addIndex', 'page_props', 'pp_propname_page', 'patch-page_props-propname-page-index.sql' ),
+ array( 'addIndex', 'image', 'img_media_mime', 'patch-img_media_mime-index.sql' ),
);
}
@@ -107,7 +128,7 @@ class SqliteUpdater extends DatabaseUpdater {
protected function sqliteSetupSearchindex() {
$module = DatabaseSqlite::getFulltextSearchModule();
$fts3tTable = $this->updateRowExists( 'fts3' );
- if ( $fts3tTable && !$module ) {
+ if ( $fts3tTable && !$module ) {
$this->applyPatch( 'searchindex-no-fts.sql', false, 'PHP is missing FTS3 support, downgrading tables' );
} elseif ( !$fts3tTable && $module == 'FTS3' ) {
$this->applyPatch( 'searchindex-fts3.sql', false, "Adding FTS3 search capabilities" );
@@ -115,4 +136,11 @@ class SqliteUpdater extends DatabaseUpdater {
$this->output( "...fulltext search table appears to be in order.\n" );
}
}
+
+ protected function doEnableProfiling() {
+ global $wgProfileToDatabase;
+ if ( $wgProfileToDatabase === true && ! $this->db->tableExists( 'profiling', __METHOD__ ) ) {
+ $this->applyPatch( 'patch-profiling.sql', false, 'Add profiling table' );
+ }
+ }
}
diff --git a/includes/installer/WebInstaller.php b/includes/installer/WebInstaller.php
index 2f46ff0b..35d649b2 100644
--- a/includes/installer/WebInstaller.php
+++ b/includes/installer/WebInstaller.php
@@ -56,10 +56,11 @@ class WebInstaller extends Installer {
/**
* The main sequence of page names. These will be displayed in turn.
- * To add one:
- * * Add it here
- * * Add a config-page-<name> message
- * * Add a WebInstaller_<name> class
+ *
+ * To add a new installer page:
+ * * Add it to this WebInstaller::$pageSequence property
+ * * Add a "config-page-<name>" message
+ * * Add a "WebInstaller_<name>" class
* @var array
*/
public $pageSequence = array(
@@ -139,7 +140,7 @@ class WebInstaller extends Installer {
/**
* Main entry point.
*
- * @param $session Array: initial session array
+ * @param array $session initial session array
*
* @return Array: new session array
*/
@@ -425,7 +426,7 @@ class WebInstaller extends Installer {
$url = preg_replace( '/\?.*$/', '', $url );
if ( $query ) {
- $url .= '?' . wfArrayToCGI( $query );
+ $url .= '?' . wfArrayToCgi( $query );
}
return $url;
@@ -460,7 +461,7 @@ class WebInstaller extends Installer {
/**
* Set a session variable.
- * @param $name String key for the variable
+ * @param string $name key for the variable
* @param $value Mixed
*/
public function setSession( $name, $value ) {
@@ -609,7 +610,7 @@ class WebInstaller extends Installer {
/**
* Get HTML for an error box with an icon.
*
- * @param $text String: wikitext, get this with wfMessage()->plain()
+ * @param string $text wikitext, get this with wfMessage()->plain()
*
* @return string
*/
@@ -620,7 +621,7 @@ class WebInstaller extends Installer {
/**
* Get HTML for a warning box with an icon.
*
- * @param $text String: wikitext, get this with wfMessage()->plain()
+ * @param string $text wikitext, get this with wfMessage()->plain()
*
* @return string
*/
@@ -631,9 +632,9 @@ class WebInstaller extends Installer {
/**
* Get HTML for an info box with an icon.
*
- * @param $text String: wikitext, get this with wfMessage()->plain()
- * @param $icon String: icon name, file in skins/common/images
- * @param $class String: additional class name to add to the wrapper div
+ * @param string $text wikitext, get this with wfMessage()->plain()
+ * @param string $icon icon name, file in skins/common/images
+ * @param string $class additional class name to add to the wrapper div
*
* @return string
*/
@@ -667,7 +668,7 @@ class WebInstaller extends Installer {
/**
* Output a help box.
- * @param $msg String key for wfMessage()
+ * @param string $msg key for wfMessage()
*/
public function showHelpBox( $msg /*, ... */ ) {
$args = func_get_args();
@@ -932,7 +933,7 @@ class WebInstaller extends Installer {
* @return string
*/
public function getRadioSet( $params ) {
- if ( !isset( $params['controlName'] ) ) {
+ if ( !isset( $params['controlName'] ) ) {
$params['controlName'] = 'config_' . $params['var'];
}
@@ -1005,7 +1006,7 @@ class WebInstaller extends Installer {
* fake) passwords.
*
* @param $varNames Array
- * @param $prefix String: the prefix added to variables to obtain form names
+ * @param string $prefix the prefix added to variables to obtain form names
*
* @return array
*/
@@ -1078,14 +1079,14 @@ class WebInstaller extends Installer {
) );
$anchor = Html::rawElement( 'a',
array( 'href' => $this->getURL( array( 'localsettings' => 1 ) ) ),
- $img . ' ' . wfMessage( 'config-download-localsettings' )->escaped() );
+ $img . ' ' . wfMessage( 'config-download-localsettings' )->parse() );
return Html::rawElement( 'div', array( 'class' => 'config-download-link' ), $anchor );
}
/**
* @return bool
*/
- public function envCheckPath( ) {
+ public function envCheckPath() {
// PHP_SELF isn't available sometimes, such as when PHP is CGI but
// cgi.fix_pathinfo is disabled. In that case, fall back to SCRIPT_NAME
// to get the path to the current script... hopefully it's reliable. SIGH
@@ -1095,7 +1096,7 @@ class WebInstaller extends Installer {
} elseif ( !empty( $_SERVER['SCRIPT_NAME'] ) ) {
$path = $_SERVER['SCRIPT_NAME'];
}
- if ($path !== false) {
+ if ( $path !== false ) {
$uri = preg_replace( '{^(.*)/(mw-)?config.*$}', '$1', $path );
$this->setVar( 'wgScriptPath', $uri );
} else {
diff --git a/includes/installer/WebInstallerOutput.php b/includes/installer/WebInstallerOutput.php
index f3166c25..d61d843f 100644
--- a/includes/installer/WebInstallerOutput.php
+++ b/includes/installer/WebInstallerOutput.php
@@ -104,7 +104,7 @@ class WebInstallerOutput {
/**
* Get the raw vector CSS, flipping if needed
- * @param $dir String 'ltr' or 'rtl'
+ * @param string $dir 'ltr' or 'rtl'
* @return String
*/
public function getCSS( $dir ) {
@@ -157,7 +157,7 @@ class WebInstallerOutput {
* "<link>" to index.php?css=foobar for the "<head>"
* @return String
*/
- private function getCssUrl( ) {
+ private function getCssUrl() {
return Html::linkedStyle( $_SERVER['PHP_SELF'] . '?css=' . $this->getDir() );
}
@@ -219,7 +219,7 @@ class WebInstallerOutput {
$dbTypes = $this->parent->getDBTypes();
$this->parent->request->response()->header( 'Content-Type: text/html; charset=utf-8' );
- if (!$this->allowFrames) {
+ if ( !$this->allowFrames ) {
$this->parent->request->response()->header( 'X-Frame-Options: DENY' );
}
if ( $this->redirectTarget ) {
@@ -239,7 +239,7 @@ class WebInstallerOutput {
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title><?php $this->outputTitle(); ?></title>
<?php echo $this->getCssUrl() . "\n"; ?>
- <?php echo Html::inlineScript( "var dbTypes = " . Xml::encodeJsVar( $dbTypes ) ) . "\n"; ?>
+ <?php echo Html::inlineScript( "var dbTypes = " . Xml::encodeJsVar( $dbTypes ) ) . "\n"; ?>
<?php echo $this->getJQuery() . "\n"; ?>
<?php echo Html::linkedScript( '../skins/common/config.js' ) . "\n"; ?>
</head>
diff --git a/includes/installer/WebInstallerPage.php b/includes/installer/WebInstallerPage.php
index a193afb7..78830293 100644
--- a/includes/installer/WebInstallerPage.php
+++ b/includes/installer/WebInstallerPage.php
@@ -36,7 +36,7 @@ abstract class WebInstallerPage {
*/
public $parent;
- public abstract function execute();
+ abstract public function execute();
/**
* Constructor.
@@ -128,7 +128,7 @@ abstract class WebInstallerPage {
/**
* Get the starting tags of a fieldset.
*
- * @param $legend String: message name
+ * @param string $legend message name
*
* @return string
*/
@@ -357,7 +357,7 @@ class WebInstaller_ExistingWiki extends WebInstallerPage {
/**
* Initiate an upgrade of the existing database
- * @param $vars array Variables from LocalSettings.php and AdminSettings.php
+ * @param array $vars Variables from LocalSettings.php and AdminSettings.php
* @return Status
*/
protected function handleExistingUpgrade( $vars ) {
@@ -369,7 +369,7 @@ class WebInstaller_ExistingWiki extends WebInstallerPage {
// Set the relevant variables from LocalSettings.php
$requiredVars = array( 'wgDBtype' );
- $status = $this->importVariables( $requiredVars , $vars );
+ $status = $this->importVariables( $requiredVars, $vars );
$installer = $this->parent->getDBInstaller();
$status->merge( $this->importVariables( $installer->getGlobalNames(), $vars ) );
if ( !$status->isOK() ) {
@@ -422,6 +422,7 @@ class WebInstaller_Welcome extends WebInstallerPage {
} else {
$this->parent->showStatusMessage( $status );
}
+ return '';
}
}
@@ -459,7 +460,14 @@ class WebInstaller_DBConnect extends WebInstallerPage {
$this->addHTML( $this->parent->getInfoBox(
wfMessage( 'config-support-info', trim( $dbSupport ) )->text() ) );
- foreach ( $this->parent->getVar( '_CompiledDBs' ) as $type ) {
+ // It's possible that the library for the default DB type is not compiled in.
+ // In that case, instead select the first supported DB type in the list.
+ $compiledDBs = $this->parent->getVar( '_CompiledDBs' );
+ if ( !in_array( $defaultType, $compiledDBs ) ) {
+ $defaultType = $compiledDBs[0];
+ }
+
+ foreach ( $compiledDBs as $type ) {
$installer = $this->parent->getDBInstaller( $type );
$types .=
'<li>' .
@@ -493,6 +501,9 @@ class WebInstaller_DBConnect extends WebInstallerPage {
public function submit() {
$r = $this->parent->request;
$type = $r->getVal( 'DBType' );
+ if ( !$type ) {
+ return Status::newFatal( 'config-invalid-db-type' );
+ }
$this->setVar( 'wgDBtype', $type );
$installer = $this->parent->getDBInstaller( $type );
if ( !$installer ) {
@@ -644,7 +655,7 @@ class WebInstaller_Name extends WebInstallerPage {
$this->parent->getTextBox( array(
'var' => 'wgSitename',
'label' => 'config-site-name',
- 'help' => $this->parent->getHelpBox( 'config-site-name-help' )
+ 'help' => $this->parent->getHelpBox( 'config-site-name-help' )
) ) .
$this->parent->getRadioSet( array(
'var' => '_NamespaceType',
@@ -911,6 +922,10 @@ class WebInstaller_Options extends WebInstallerPage {
$this->getVar( 'wgDeletedDirectory' )
)
);
+ // If we're using the default, let the user set it relative to $wgScriptPath
+ $curLogo = $this->getVar( 'wgLogo' );
+ $logoString = ( $curLogo == "/wiki/skins/common/images/wiki.png" ) ?
+ '$wgStylePath/common/images/wiki.png' : $curLogo;
$uploadwrapperStyle = $this->getVar( 'wgEnableUploads' ) ? '' : 'display: none';
$this->addHTML(
@@ -932,6 +947,7 @@ class WebInstaller_Options extends WebInstallerPage {
'</div>' .
$this->parent->getTextBox( array(
'var' => 'wgLogo',
+ 'value' => $logoString,
'label' => 'config-logo',
'attribs' => array( 'dir' => 'ltr' ),
'help' => $this->parent->getHelpBox( 'config-logo-help' )
@@ -954,7 +970,7 @@ class WebInstaller_Options extends WebInstallerPage {
// We'll hide/show this on demand when the value changes, see config.js.
$cacheval = $this->getVar( 'wgMainCacheType' );
- if (!$cacheval) {
+ if ( !$cacheval ) {
// We need to set a default here; but don't hardcode it
// or we lose it every time we reload the page for validation
// or going back!
@@ -1001,7 +1017,7 @@ class WebInstaller_Options extends WebInstallerPage {
$styleUrl = $server . dirname( dirname( $this->parent->getUrl() ) ) .
'/skins/common/config-cc.css';
$iframeUrl = 'http://creativecommons.org/license/?' .
- wfArrayToCGI( array(
+ wfArrayToCgi( array(
'partner' => 'MediaWiki',
'exit_url' => $exitUrl,
'lang' => $this->getVar( '_UserLang' ),
@@ -1024,7 +1040,7 @@ class WebInstaller_Options extends WebInstallerPage {
} else {
$iframeAttribs['src'] = $this->getCCPartnerUrl();
}
- $wrapperStyle = ($this->getVar('_LicenseCode') == 'cc-choose') ? '' : 'display: none';
+ $wrapperStyle = ($this->getVar( '_LicenseCode' ) == 'cc-choose') ? '' : 'display: none';
return
"<div class=\"config-cc-wrapper\" id=\"config-cc-wrapper\" style=\"$wrapperStyle\">\n" .
@@ -1154,12 +1170,12 @@ class WebInstaller_Install extends WebInstallerPage {
return 'continue';
} elseif( $this->parent->request->wasPosted() ) {
$this->startForm();
- $this->addHTML("<ul>");
+ $this->addHTML( "<ul>" );
$results = $this->parent->performInstallation(
- array( $this, 'startStage'),
+ array( $this, 'startStage' ),
array( $this, 'endStage' )
);
- $this->addHTML("</ul>");
+ $this->addHTML( "</ul>" );
// PerformInstallation bails on a fatal, so make sure the last item
// completed before giving 'next.' Likewise, only provide back on failure
$lastStep = end( $results );
@@ -1175,7 +1191,7 @@ class WebInstaller_Install extends WebInstallerPage {
}
public function startStage( $step ) {
- $this->addHTML( "<li>" . wfMessage( "config-install-$step" )->escaped() . wfMessage( 'ellipsis')->escaped() );
+ $this->addHTML( "<li>" . wfMessage( "config-install-$step" )->escaped() . wfMessage( 'ellipsis' )->escaped() );
if ( $step == 'extension-tables' ) {
$this->startLiveBox();
}
@@ -1258,7 +1274,7 @@ class WebInstaller_Restart extends WebInstallerPage {
abstract class WebInstaller_Document extends WebInstallerPage {
- protected abstract function getFileName();
+ abstract protected function getFileName();
public function execute() {
$text = $this->getFileContents();
@@ -1286,8 +1302,8 @@ class WebInstaller_ReleaseNotes extends WebInstaller_Document {
protected function getFileName() {
global $wgVersion;
- if(! preg_match( '/^(\d+)\.(\d+).*/i', $wgVersion, $result ) ) {
- throw new MWException('Variable $wgVersion has an invalid value.');
+ if( !preg_match( '/^(\d+)\.(\d+).*/i', $wgVersion, $result ) ) {
+ throw new MWException( 'Variable $wgVersion has an invalid value.' );
}
return 'RELEASE-NOTES-' . $result[1] . '.' . $result[2];
@@ -1301,4 +1317,3 @@ class WebInstaller_UpgradeDoc extends WebInstaller_Document {
class WebInstaller_Copying extends WebInstaller_Document {
protected function getFileName() { return 'COPYING'; }
}
-
diff --git a/includes/interwiki/Interwiki.php b/includes/interwiki/Interwiki.php
index eacf9a87..4003fa88 100644
--- a/includes/interwiki/Interwiki.php
+++ b/includes/interwiki/Interwiki.php
@@ -27,14 +27,15 @@
* schema updates etc, which aren't wiki-related)
*/
class Interwiki {
-
// Cache - removes oldest entry when it hits limit
protected static $smCache = array();
const CACHE_LIMIT = 100; // 0 means unlimited, any other value is max number of entries.
protected $mPrefix, $mURL, $mAPI, $mWikiID, $mLocal, $mTrans;
- public function __construct( $prefix = null, $url = '', $api = '', $wikiId = '', $local = 0, $trans = 0 ) {
+ public function __construct( $prefix = null, $url = '', $api = '', $wikiId = '', $local = 0,
+ $trans = 0
+ ) {
$this->mPrefix = $prefix;
$this->mURL = $url;
$this->mAPI = $api;
@@ -46,10 +47,10 @@ class Interwiki {
/**
* Check whether an interwiki prefix exists
*
- * @param $prefix String: interwiki prefix to use
- * @return Boolean: whether it exists
+ * @param string $prefix Interwiki prefix to use
+ * @return bool Whether it exists
*/
- static public function isValidInterwiki( $prefix ) {
+ public static function isValidInterwiki( $prefix ) {
$result = self::fetch( $prefix );
return (bool)$result;
}
@@ -57,28 +58,28 @@ class Interwiki {
/**
* Fetch an Interwiki object
*
- * @param $prefix String: interwiki prefix to use
+ * @param string $prefix Interwiki prefix to use
* @return Interwiki|null|bool
*/
- static public function fetch( $prefix ) {
+ public static function fetch( $prefix ) {
global $wgContLang;
- if( $prefix == '' ) {
+ if ( $prefix == '' ) {
return null;
}
$prefix = $wgContLang->lc( $prefix );
- if( isset( self::$smCache[$prefix] ) ) {
+ if ( isset( self::$smCache[$prefix] ) ) {
return self::$smCache[$prefix];
}
global $wgInterwikiCache;
- if( $wgInterwikiCache ) {
+ if ( $wgInterwikiCache ) {
$iw = Interwiki::getInterwikiCached( $prefix );
} else {
$iw = Interwiki::load( $prefix );
- if( !$iw ) {
+ if ( !$iw ) {
$iw = false;
}
}
- if( self::CACHE_LIMIT && count( self::$smCache ) >= self::CACHE_LIMIT ) {
+ if ( self::CACHE_LIMIT && count( self::$smCache ) >= self::CACHE_LIMIT ) {
reset( self::$smCache );
unset( self::$smCache[key( self::$smCache )] );
}
@@ -91,7 +92,7 @@ class Interwiki {
*
* @note More logic is explained in DefaultSettings.
*
- * @param $prefix String: interwiki prefix
+ * @param string $prefix Interwiki prefix
* @return Interwiki object
*/
protected static function getInterwikiCached( $prefix ) {
@@ -114,19 +115,19 @@ class Interwiki {
*
* @note More logic is explained in DefaultSettings.
*
- * @param $prefix String: database key
- * @return String: the entry
+ * @param string $prefix Database key
+ * @return string The interwiki entry
*/
protected static function getInterwikiCacheEntry( $prefix ) {
global $wgInterwikiCache, $wgInterwikiScopes, $wgInterwikiFallbackSite;
static $db, $site;
wfDebug( __METHOD__ . "( $prefix )\n" );
- if( !$db ) {
+ if ( !$db ) {
$db = CdbReader::open( $wgInterwikiCache );
}
/* Resolve site name */
- if( $wgInterwikiScopes >= 3 && !$site ) {
+ if ( $wgInterwikiScopes >= 3 && !$site ) {
$site = $db->get( '__sites:' . wfWikiID() );
if ( $site == '' ) {
$site = $wgInterwikiFallbackSite;
@@ -146,15 +147,14 @@ class Interwiki {
$value = '';
}
-
return $value;
}
/**
* Load the interwiki, trying first memcached then the DB
*
- * @param $prefix string The interwiki prefix
- * @return Boolean: the prefix is valid
+ * @param string $prefix The interwiki prefix
+ * @return bool If $prefix is valid
*/
protected static function load( $prefix ) {
global $wgMemc, $wgInterwikiExpiry;
@@ -172,9 +172,9 @@ class Interwiki {
}
}
- if( $iwData && is_array( $iwData ) ) { // is_array is hack for old keys
+ if ( $iwData && is_array( $iwData ) ) { // is_array is hack for old keys
$iw = Interwiki::loadFromArray( $iwData );
- if( $iw ) {
+ if ( $iw ) {
return $iw;
}
}
@@ -203,11 +203,11 @@ class Interwiki {
/**
* Fill in member variables from an array (e.g. memcached result, Database::fetchRow, etc)
*
- * @param $mc array Associative array: row from the interwiki table
- * @return Boolean|Interwiki whether everything was there
+ * @param array $mc Associative array: row from the interwiki table
+ * @return Interwiki|bool Interwiki object or false if $mc['iw_url'] is not set
*/
protected static function loadFromArray( $mc ) {
- if( isset( $mc['iw_url'] ) ) {
+ if ( isset( $mc['iw_url'] ) ) {
$iw = new Interwiki();
$iw->mURL = $mc['iw_url'];
$iw->mLocal = isset( $mc['iw_local'] ) ? $mc['iw_local'] : 0;
@@ -223,8 +223,8 @@ class Interwiki {
/**
* Fetch all interwiki prefixes from interwiki cache
*
- * @param $local null|string If not null, limits output to local/non-local interwikis
- * @return Array List of prefixes
+ * @param null|string $local If not null, limits output to local/non-local interwikis
+ * @return array List of prefixes
* @since 1.19
*/
protected static function getAllPrefixesCached( $local ) {
@@ -232,11 +232,11 @@ class Interwiki {
static $db, $site;
wfDebug( __METHOD__ . "()\n" );
- if( !$db ) {
+ if ( !$db ) {
$db = CdbReader::open( $wgInterwikiCache );
}
/* Resolve site name */
- if( $wgInterwikiScopes >= 3 && !$site ) {
+ if ( $wgInterwikiScopes >= 3 && !$site ) {
$site = $db->get( '__sites:' . wfWikiID() );
if ( $site == '' ) {
$site = $wgInterwikiFallbackSite;
@@ -257,11 +257,11 @@ class Interwiki {
$data = array();
- foreach( $sources as $source ) {
+ foreach ( $sources as $source ) {
$list = $db->get( "__list:{$source}" );
foreach ( explode( ' ', $list ) as $iw_prefix ) {
$row = $db->get( "{$source}:{$iw_prefix}" );
- if( !$row ) {
+ if ( !$row ) {
continue;
}
@@ -273,8 +273,8 @@ class Interwiki {
$data[$iw_prefix] = array(
'iw_prefix' => $iw_prefix,
- 'iw_url' => $iw_url,
- 'iw_local' => $iw_local,
+ 'iw_url' => $iw_url,
+ 'iw_local' => $iw_local,
);
}
}
@@ -287,8 +287,8 @@ class Interwiki {
/**
* Fetch all interwiki prefixes from DB
*
- * @param $local string|null If not null, limits output to local/non-local interwikis
- * @return Array List of prefixes
+ * @param string|null $local If not null, limits output to local/non-local interwikis
+ * @return array List of prefixes
* @since 1.19
*/
protected static function getAllPrefixesDB( $local ) {
@@ -318,8 +318,8 @@ class Interwiki {
/**
* Returns all interwiki prefixes
*
- * @param $local string|null If set, limits output to local/non-local interwikis
- * @return Array List of prefixes
+ * @param string|null $local If set, limits output to local/non-local interwikis
+ * @return array List of prefixes
* @since 1.19
*/
public static function getAllPrefixes( $local = null ) {
@@ -335,15 +335,15 @@ class Interwiki {
/**
* Get the URL for a particular title (or with $1 if no title given)
*
- * @param $title String: what text to put for the article name
- * @return String: the URL
+ * @param string $title What text to put for the article name
+ * @return string The URL
* @note Prior to 1.19 The getURL with an argument was broken.
* If you if you use this arg in an extension that supports MW earlier
* than 1.19 please wfUrlencode and substitute $1 on your own.
*/
public function getURL( $title = null ) {
$url = $this->mURL;
- if( $title !== null ) {
+ if ( $title !== null ) {
$url = str_replace( "$1", wfUrlencode( $title ), $url );
}
return $url;
@@ -352,7 +352,7 @@ class Interwiki {
/**
* Get the API URL for this wiki
*
- * @return String: the URL
+ * @return string The URL
*/
public function getAPI() {
return $this->mAPI;
@@ -361,7 +361,7 @@ class Interwiki {
/**
* Get the DB name for this wiki
*
- * @return String: the DB name
+ * @return string The DB name
*/
public function getWikiID() {
return $this->mWikiID;
@@ -371,7 +371,7 @@ class Interwiki {
* Is this a local link from a sister project, or is
* it something outside, like Google
*
- * @return Boolean
+ * @return bool
*/
public function isLocal() {
return $this->mLocal;
@@ -381,7 +381,7 @@ class Interwiki {
* Can pages from this wiki be transcluded?
* Still requires $wgEnableScaryTransclusion
*
- * @return Boolean
+ * @return bool
*/
public function isTranscludable() {
return $this->mTrans;
@@ -390,7 +390,7 @@ class Interwiki {
/**
* Get the name for the interwiki site
*
- * @return String
+ * @return string
*/
public function getName() {
$msg = wfMessage( 'interwiki-name-' . $this->mPrefix )->inContentLanguage();
@@ -400,7 +400,7 @@ class Interwiki {
/**
* Get a description for this interwiki
*
- * @return String
+ * @return string
*/
public function getDescription() {
$msg = wfMessage( 'interwiki-desc-' . $this->mPrefix )->inContentLanguage();
@@ -409,8 +409,8 @@ class Interwiki {
/**
* Return the list of interwiki fields that should be selected to create
- * a new interwiki object.
- * @return array
+ * a new Interwiki object.
+ * @return string[]
*/
public static function selectFields() {
return array(
diff --git a/includes/job/Job.php b/includes/job/Job.php
index d777a5d4..bcf582e7 100644
--- a/includes/job/Job.php
+++ b/includes/job/Job.php
@@ -23,11 +23,11 @@
/**
* Class to both describe a background job and handle jobs.
+ * The queue aspects of this class are now deprecated.
*
* @ingroup JobQueue
*/
abstract class Job {
-
/**
* @var Title
*/
@@ -39,6 +39,9 @@ abstract class Job {
$removeDuplicates,
$error;
+ /** @var Array Additional queue metadata */
+ public $metadata = array();
+
/*-------------------------------------------------------------------------
* Abstract functions
*------------------------------------------------------------------------*/
@@ -47,176 +50,23 @@ abstract class Job {
* Run the job
* @return boolean success
*/
- abstract function run();
+ abstract public function run();
/*-------------------------------------------------------------------------
* Static functions
*------------------------------------------------------------------------*/
/**
- * Pop a job of a certain type. This tries less hard than pop() to
- * actually find a job; it may be adversely affected by concurrent job
- * runners.
- *
- * @param $type string
- *
- * @return Job
- */
- static function pop_type( $type ) {
- wfProfilein( __METHOD__ );
-
- $dbw = wfGetDB( DB_MASTER );
-
- $dbw->begin( __METHOD__ );
-
- $row = $dbw->selectRow(
- 'job',
- '*',
- array( 'job_cmd' => $type ),
- __METHOD__,
- array( 'LIMIT' => 1, 'FOR UPDATE' )
- );
-
- if ( $row === false ) {
- $dbw->commit( __METHOD__ );
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- /* Ensure we "own" this row */
- $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
- $affected = $dbw->affectedRows();
- $dbw->commit( __METHOD__ );
-
- if ( $affected == 0 ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- wfIncrStats( 'job-pop' );
- $namespace = $row->job_namespace;
- $dbkey = $row->job_title;
- $title = Title::makeTitleSafe( $namespace, $dbkey );
- $job = Job::factory( $row->job_cmd, $title, Job::extractBlob( $row->job_params ),
- $row->job_id );
-
- $job->removeDuplicates();
-
- wfProfileOut( __METHOD__ );
- return $job;
- }
-
- /**
- * Pop a job off the front of the queue
- *
- * @param $offset Integer: Number of jobs to skip
- * @return Job or false if there's no jobs
- */
- static function pop( $offset = 0 ) {
- wfProfileIn( __METHOD__ );
-
- $dbr = wfGetDB( DB_SLAVE );
-
- /* Get a job from the slave, start with an offset,
- scan full set afterwards, avoid hitting purged rows
-
- NB: If random fetch previously was used, offset
- will always be ahead of few entries
- */
-
- $conditions = self::defaultQueueConditions();
-
- $offset = intval( $offset );
- $options = array( 'ORDER BY' => 'job_id', 'USE INDEX' => 'PRIMARY' );
-
- $row = $dbr->selectRow( 'job', '*',
- array_merge( $conditions, array( "job_id >= $offset" ) ),
- __METHOD__,
- $options
- );
-
- // Refetching without offset is needed as some of job IDs could have had delayed commits
- // and have lower IDs than jobs already executed, blame concurrency :)
- //
- if ( $row === false ) {
- if ( $offset != 0 ) {
- $row = $dbr->selectRow( 'job', '*', $conditions, __METHOD__, $options );
- }
-
- if ( $row === false ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
- }
-
- // Try to delete it from the master
- $dbw = wfGetDB( DB_MASTER );
- $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
- $affected = $dbw->affectedRows();
-
- if ( !$affected ) {
- // Failed, someone else beat us to it
- // Try getting a random row
- $row = $dbw->selectRow( 'job', array( 'minjob' => 'MIN(job_id)',
- 'maxjob' => 'MAX(job_id)' ), '1=1', __METHOD__ );
- if ( $row === false || is_null( $row->minjob ) || is_null( $row->maxjob ) ) {
- // No jobs to get
- wfProfileOut( __METHOD__ );
- return false;
- }
- // Get the random row
- $row = $dbw->selectRow( 'job', '*',
- 'job_id >= ' . mt_rand( $row->minjob, $row->maxjob ), __METHOD__ );
- if ( $row === false ) {
- // Random job gone before we got the chance to select it
- // Give up
- wfProfileOut( __METHOD__ );
- return false;
- }
- // Delete the random row
- $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
- $affected = $dbw->affectedRows();
-
- if ( !$affected ) {
- // Random job gone before we exclusively deleted it
- // Give up
- wfProfileOut( __METHOD__ );
- return false;
- }
- }
-
- // If execution got to here, there's a row in $row that has been deleted from the database
- // by this thread. Hence the concurrent pop was successful.
- wfIncrStats( 'job-pop' );
- $namespace = $row->job_namespace;
- $dbkey = $row->job_title;
- $title = Title::makeTitleSafe( $namespace, $dbkey );
-
- if ( is_null( $title ) ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- $job = Job::factory( $row->job_cmd, $title, Job::extractBlob( $row->job_params ), $row->job_id );
-
- // Remove any duplicates it may have later in the queue
- $job->removeDuplicates();
-
- wfProfileOut( __METHOD__ );
- return $job;
- }
-
- /**
* Create the appropriate object to handle a specific job
*
- * @param $command String: Job command
+ * @param string $command Job command
* @param $title Title: Associated title
- * @param $params Array|bool: Job parameters
- * @param $id Int: Job identifier
+ * @param array|bool $params Job parameters
+ * @param int $id Job identifier
* @throws MWException
* @return Job
*/
- static function factory( $command, Title $title, $params = false, $id = 0 ) {
+ public static function factory( $command, Title $title, $params = false, $id = 0 ) {
global $wgJobClasses;
if( isset( $wgJobClasses[$command] ) ) {
$class = $wgJobClasses[$command];
@@ -226,64 +76,18 @@ abstract class Job {
}
/**
- * @param $params
- * @return string
- */
- static function makeBlob( $params ) {
- if ( $params !== false ) {
- return serialize( $params );
- } else {
- return '';
- }
- }
-
- /**
- * @param $blob
- * @return bool|mixed
- */
- static function extractBlob( $blob ) {
- if ( (string)$blob !== '' ) {
- return unserialize( $blob );
- } else {
- return false;
- }
- }
-
- /**
* Batch-insert a group of jobs into the queue.
* This will be wrapped in a transaction with a forced commit.
*
* This may add duplicate at insert time, but they will be
* removed later on, when the first one is popped.
*
- * @param $jobs array of Job objects
+ * @param array $jobs of Job objects
+ * @return bool
+ * @deprecated 1.21
*/
- static function batchInsert( $jobs ) {
- if ( !count( $jobs ) ) {
- return;
- }
- $dbw = wfGetDB( DB_MASTER );
- $rows = array();
-
- /**
- * @var $job Job
- */
- foreach ( $jobs as $job ) {
- $rows[] = $job->insertFields();
- if ( count( $rows ) >= 50 ) {
- # Do a small transaction to avoid slave lag
- $dbw->begin( __METHOD__ );
- $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
- $dbw->commit( __METHOD__ );
- $rows = array();
- }
- }
- if ( $rows ) { // last chunk
- $dbw->begin( __METHOD__ );
- $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
- $dbw->commit( __METHOD__ );
- }
- wfIncrStats( 'job-insert', count( $jobs ) );
+ public static function batchInsert( $jobs ) {
+ return JobQueueGroup::singleton()->push( $jobs );
}
/**
@@ -293,46 +97,36 @@ abstract class Job {
* be rolled-back as part of a larger transaction. However,
* large batches of jobs can cause slave lag.
*
- * @param $jobs array of Job objects
+ * @param array $jobs of Job objects
+ * @return bool
+ * @deprecated 1.21
*/
- static function safeBatchInsert( $jobs ) {
- if ( !count( $jobs ) ) {
- return;
- }
- $dbw = wfGetDB( DB_MASTER );
- $rows = array();
- foreach ( $jobs as $job ) {
- $rows[] = $job->insertFields();
- if ( count( $rows ) >= 500 ) {
- $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
- $rows = array();
- }
- }
- if ( $rows ) { // last chunk
- $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
- }
- wfIncrStats( 'job-insert', count( $jobs ) );
+ public static function safeBatchInsert( $jobs ) {
+ return JobQueueGroup::singleton()->push( $jobs, JobQueue::QoS_Atomic );
}
-
/**
- * SQL conditions to apply on most JobQueue queries
+ * Pop a job of a certain type. This tries less hard than pop() to
+ * actually find a job; it may be adversely affected by concurrent job
+ * runners.
*
- * Whenever we exclude jobs types from the default queue, we want to make
- * sure that queries to the job queue actually ignore them.
+ * @param $type string
+ * @return Job|bool Returns false if there are no jobs
+ * @deprecated 1.21
+ */
+ public static function pop_type( $type ) {
+ return JobQueueGroup::singleton()->get( $type )->pop();
+ }
+
+ /**
+ * Pop a job off the front of the queue.
+ * This is subject to $wgJobTypesExcludedFromDefaultQueue.
*
- * @return array SQL conditions suitable for Database:: methods
+ * @return Job or false if there's no jobs
+ * @deprecated 1.21
*/
- static function defaultQueueConditions( ) {
- global $wgJobTypesExcludedFromDefaultQueue;
- $conditions = array();
- if ( count( $wgJobTypesExcludedFromDefaultQueue ) > 0 ) {
- $dbr = wfGetDB( DB_SLAVE );
- foreach ( $wgJobTypesExcludedFromDefaultQueue as $cmdType ) {
- $conditions[] = "job_cmd != " . $dbr->addQuotes( $cmdType );
- }
- }
- return $conditions;
+ public static function pop() {
+ return JobQueueGroup::singleton()->pop();
}
/*-------------------------------------------------------------------------
@@ -345,83 +139,131 @@ abstract class Job {
* @param $params array|bool
* @param $id int
*/
- function __construct( $command, $title, $params = false, $id = 0 ) {
+ public function __construct( $command, $title, $params = false, $id = 0 ) {
$this->command = $command;
$this->title = $title;
$this->params = $params;
$this->id = $id;
- // A bit of premature generalisation
- // Oh well, the whole class is premature generalisation really
- $this->removeDuplicates = true;
+ $this->removeDuplicates = false; // expensive jobs may set this to true
}
/**
- * Insert a single job into the queue.
- * @return bool true on success
+ * @return integer May be 0 for jobs stored outside the DB
*/
- function insert() {
- $fields = $this->insertFields();
+ public function getId() {
+ return $this->id;
+ }
- $dbw = wfGetDB( DB_MASTER );
+ /**
+ * @return string
+ */
+ public function getType() {
+ return $this->command;
+ }
- if ( $this->removeDuplicates ) {
- $res = $dbw->select( 'job', array( '1' ), $fields, __METHOD__ );
- if ( $dbw->numRows( $res ) ) {
- return true;
- }
- }
- wfIncrStats( 'job-insert' );
- return $dbw->insert( 'job', $fields, __METHOD__ );
+ /**
+ * @return Title
+ */
+ public function getTitle() {
+ return $this->title;
}
/**
* @return array
*/
- protected function insertFields() {
- $dbw = wfGetDB( DB_MASTER );
+ public function getParams() {
+ return $this->params;
+ }
+
+ /**
+ * @return bool Whether only one of each identical set of jobs should be run
+ */
+ public function ignoreDuplicates() {
+ return $this->removeDuplicates;
+ }
+
+ /**
+ * @return bool Whether this job can be retried on failure by job runners
+ */
+ public function allowRetries() {
+ return true;
+ }
+
+ /**
+ * Subclasses may need to override this to make duplication detection work
+ *
+ * @return Array Map of key/values
+ */
+ public function getDeduplicationInfo() {
+ $info = array(
+ 'type' => $this->getType(),
+ 'namespace' => $this->getTitle()->getNamespace(),
+ 'title' => $this->getTitle()->getDBkey(),
+ 'params' => $this->getParams()
+ );
+ // Identical jobs with different "root" jobs should count as duplicates
+ if ( is_array( $info['params'] ) ) {
+ unset( $info['params']['rootJobSignature'] );
+ unset( $info['params']['rootJobTimestamp'] );
+ }
+ return $info;
+ }
+
+ /**
+ * @param string $key A key that identifies the task
+ * @return Array
+ */
+ public static function newRootJobParams( $key ) {
return array(
- 'job_id' => $dbw->nextSequenceValue( 'job_job_id_seq' ),
- 'job_cmd' => $this->command,
- 'job_namespace' => $this->title->getNamespace(),
- 'job_title' => $this->title->getDBkey(),
- 'job_timestamp' => $dbw->timestamp(),
- 'job_params' => Job::makeBlob( $this->params )
+ 'rootJobSignature' => sha1( $key ),
+ 'rootJobTimestamp' => wfTimestampNow()
);
}
/**
- * Remove jobs in the job queue which are duplicates of this job.
- * This is deadlock-prone and so starts its own transaction.
+ * @return Array
*/
- function removeDuplicates() {
- if ( !$this->removeDuplicates ) {
- return;
- }
+ public function getRootJobParams() {
+ return array(
+ 'rootJobSignature' => isset( $this->params['rootJobSignature'] )
+ ? $this->params['rootJobSignature']
+ : null,
+ 'rootJobTimestamp' => isset( $this->params['rootJobTimestamp'] )
+ ? $this->params['rootJobTimestamp']
+ : null
+ );
+ }
- $fields = $this->insertFields();
- unset( $fields['job_id'] );
- unset( $fields['job_timestamp'] );
- $dbw = wfGetDB( DB_MASTER );
- $dbw->begin( __METHOD__ );
- $dbw->delete( 'job', $fields, __METHOD__ );
- $affected = $dbw->affectedRows();
- $dbw->commit( __METHOD__ );
- if ( $affected ) {
- wfIncrStats( 'job-dup-delete', $affected );
- }
+ /**
+ * Insert a single job into the queue.
+ * @return bool true on success
+ * @deprecated 1.21
+ */
+ public function insert() {
+ return JobQueueGroup::singleton()->push( $this );
}
/**
* @return string
*/
- function toString() {
+ public function toString() {
$paramString = '';
if ( $this->params ) {
foreach ( $this->params as $key => $value ) {
if ( $paramString != '' ) {
$paramString .= ' ';
}
+ if ( is_array( $value ) ) {
+ $value = "array(" . count( $value ) . ")";
+ } elseif ( is_object( $value ) && !method_exists( $value, '__toString' ) ) {
+ $value = "object(" . get_class( $value ) . ")";
+ }
+ $value = (string)$value;
+ if ( mb_strlen( $value ) > 1024 ) {
+ $value = "string(" . mb_strlen( $value ) . ")";
+ }
+
$paramString .= "$key=$value";
}
}
@@ -441,7 +283,7 @@ abstract class Job {
$this->error = $error;
}
- function getLastError() {
+ public function getLastError() {
return $this->error;
}
}
diff --git a/includes/job/JobQueue.php b/includes/job/JobQueue.php
new file mode 100644
index 00000000..b0dd9258
--- /dev/null
+++ b/includes/job/JobQueue.php
@@ -0,0 +1,435 @@
+<?php
+/**
+ * Job queue base code.
+ *
+ * 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
+ * @defgroup JobQueue JobQueue
+ * @author Aaron Schulz
+ */
+
+/**
+ * Class to handle enqueueing and running of background jobs
+ *
+ * @ingroup JobQueue
+ * @since 1.21
+ */
+abstract class JobQueue {
+ protected $wiki; // string; wiki ID
+ protected $type; // string; job type
+ protected $order; // string; job priority for pop()
+ protected $claimTTL; // integer; seconds
+ protected $maxTries; // integer; maximum number of times to try a job
+
+ const QoS_Atomic = 1; // integer; "all-or-nothing" job insertions
+
+ /**
+ * @param $params array
+ */
+ protected function __construct( array $params ) {
+ $this->wiki = $params['wiki'];
+ $this->type = $params['type'];
+ $this->claimTTL = isset( $params['claimTTL'] ) ? $params['claimTTL'] : 0;
+ $this->maxTries = isset( $params['maxTries'] ) ? $params['maxTries'] : 3;
+ if ( isset( $params['order'] ) && $params['order'] !== 'any' ) {
+ $this->order = $params['order'];
+ } else {
+ $this->order = $this->optimalOrder();
+ }
+ if ( !in_array( $this->order, $this->supportedOrders() ) ) {
+ throw new MWException( __CLASS__ . " does not support '{$this->order}' order." );
+ }
+ }
+
+ /**
+ * Get a job queue object of the specified type.
+ * $params includes:
+ * - class : What job class to use (determines job type)
+ * - wiki : wiki ID of the wiki the jobs are for (defaults to current wiki)
+ * - type : The name of the job types this queue handles
+ * - order : Order that pop() selects jobs, one of "fifo", "timestamp" or "random".
+ * If "fifo" is used, the queue will effectively be FIFO. Note that
+ * job completion will not appear to be exactly FIFO if there are multiple
+ * job runners since jobs can take different times to finish once popped.
+ * If "timestamp" is used, the queue will at least be loosely ordered
+ * by timestamp, allowing for some jobs to be popped off out of order.
+ * If "random" is used, pop() will pick jobs in random order.
+ * Note that it may only be weakly random (e.g. a lottery of the oldest X).
+ * If "any" is choosen, the queue will use whatever order is the fastest.
+ * This might be useful for improving concurrency for job acquisition.
+ * - claimTTL : If supported, the queue will recycle jobs that have been popped
+ * but not acknowledged as completed after this many seconds. Recycling
+ * of jobs simple means re-inserting them into the queue. Jobs can be
+ * attempted up to three times before being discarded.
+ *
+ * Queue classes should throw an exception if they do not support the options given.
+ *
+ * @param $params array
+ * @return JobQueue
+ * @throws MWException
+ */
+ final public static function factory( array $params ) {
+ $class = $params['class'];
+ if ( !MWInit::classExists( $class ) ) {
+ throw new MWException( "Invalid job queue class '$class'." );
+ }
+ $obj = new $class( $params );
+ if ( !( $obj instanceof self ) ) {
+ throw new MWException( "Class '$class' is not a " . __CLASS__ . " class." );
+ }
+ return $obj;
+ }
+
+ /**
+ * @return string Wiki ID
+ */
+ final public function getWiki() {
+ return $this->wiki;
+ }
+
+ /**
+ * @return string Job type that this queue handles
+ */
+ final public function getType() {
+ return $this->type;
+ }
+
+ /**
+ * @return string One of (random, timestamp, fifo)
+ */
+ final public function getOrder() {
+ return $this->order;
+ }
+
+ /**
+ * @return Array Subset of (random, timestamp, fifo)
+ */
+ abstract protected function supportedOrders();
+
+ /**
+ * @return string One of (random, timestamp, fifo)
+ */
+ abstract protected function optimalOrder();
+
+ /**
+ * Quickly check if the queue is empty (has no available jobs).
+ * Queue classes should use caching if they are any slower without memcached.
+ *
+ * If caching is used, this might return false when there are actually no jobs.
+ * If pop() is called and returns false then it should correct the cache. Also,
+ * calling flushCaches() first prevents this. However, this affect is typically
+ * not distinguishable from the race condition between isEmpty() and pop().
+ *
+ * @return bool
+ * @throws MWException
+ */
+ final public function isEmpty() {
+ wfProfileIn( __METHOD__ );
+ $res = $this->doIsEmpty();
+ wfProfileOut( __METHOD__ );
+ return $res;
+ }
+
+ /**
+ * @see JobQueue::isEmpty()
+ * @return bool
+ */
+ abstract protected function doIsEmpty();
+
+ /**
+ * Get the number of available (unacquired) jobs in the queue.
+ * Queue classes should use caching if they are any slower without memcached.
+ *
+ * If caching is used, this number might be out of date for a minute.
+ *
+ * @return integer
+ * @throws MWException
+ */
+ final public function getSize() {
+ wfProfileIn( __METHOD__ );
+ $res = $this->doGetSize();
+ wfProfileOut( __METHOD__ );
+ return $res;
+ }
+
+ /**
+ * @see JobQueue::getSize()
+ * @return integer
+ */
+ abstract protected function doGetSize();
+
+ /**
+ * Get the number of acquired jobs (these are temporarily out of the queue).
+ * Queue classes should use caching if they are any slower without memcached.
+ *
+ * If caching is used, this number might be out of date for a minute.
+ *
+ * @return integer
+ * @throws MWException
+ */
+ final public function getAcquiredCount() {
+ wfProfileIn( __METHOD__ );
+ $res = $this->doGetAcquiredCount();
+ wfProfileOut( __METHOD__ );
+ return $res;
+ }
+
+ /**
+ * @see JobQueue::getAcquiredCount()
+ * @return integer
+ */
+ abstract protected function doGetAcquiredCount();
+
+ /**
+ * Push a single jobs into the queue.
+ * This does not require $wgJobClasses to be set for the given job type.
+ * Outside callers should use JobQueueGroup::push() instead of this function.
+ *
+ * @param $jobs Job|Array
+ * @param $flags integer Bitfield (supports JobQueue::QoS_Atomic)
+ * @return bool Returns false on failure
+ * @throws MWException
+ */
+ final public function push( $jobs, $flags = 0 ) {
+ return $this->batchPush( is_array( $jobs ) ? $jobs : array( $jobs ), $flags );
+ }
+
+ /**
+ * Push a batch of jobs into the queue.
+ * This does not require $wgJobClasses to be set for the given job type.
+ * Outside callers should use JobQueueGroup::push() instead of this function.
+ *
+ * @param array $jobs List of Jobs
+ * @param $flags integer Bitfield (supports JobQueue::QoS_Atomic)
+ * @return bool Returns false on failure
+ * @throws MWException
+ */
+ final public function batchPush( array $jobs, $flags = 0 ) {
+ if ( !count( $jobs ) ) {
+ return true; // nothing to do
+ }
+
+ foreach ( $jobs as $job ) {
+ if ( $job->getType() !== $this->type ) {
+ throw new MWException( "Got '{$job->getType()}' job; expected '{$this->type}'." );
+ }
+ }
+
+ wfProfileIn( __METHOD__ );
+ $ok = $this->doBatchPush( $jobs, $flags );
+ wfProfileOut( __METHOD__ );
+ return $ok;
+ }
+
+ /**
+ * @see JobQueue::batchPush()
+ * @return bool
+ */
+ abstract protected function doBatchPush( array $jobs, $flags );
+
+ /**
+ * Pop a job off of the queue.
+ * This requires $wgJobClasses to be set for the given job type.
+ * Outside callers should use JobQueueGroup::pop() instead of this function.
+ *
+ * @return Job|bool Returns false if there are no jobs
+ * @throws MWException
+ */
+ final public function pop() {
+ global $wgJobClasses;
+
+ if ( $this->wiki !== wfWikiID() ) {
+ throw new MWException( "Cannot pop '{$this->type}' job off foreign wiki queue." );
+ } elseif ( !isset( $wgJobClasses[$this->type] ) ) {
+ // Do not pop jobs if there is no class for the queue type
+ throw new MWException( "Unrecognized job type '{$this->type}'." );
+ }
+
+ wfProfileIn( __METHOD__ );
+ $job = $this->doPop();
+ wfProfileOut( __METHOD__ );
+ return $job;
+ }
+
+ /**
+ * @see JobQueue::pop()
+ * @return Job
+ */
+ abstract protected function doPop();
+
+ /**
+ * Acknowledge that a job was completed.
+ *
+ * This does nothing for certain queue classes or if "claimTTL" is not set.
+ * Outside callers should use JobQueueGroup::ack() instead of this function.
+ *
+ * @param $job Job
+ * @return bool
+ * @throws MWException
+ */
+ final public function ack( Job $job ) {
+ if ( $job->getType() !== $this->type ) {
+ throw new MWException( "Got '{$job->getType()}' job; expected '{$this->type}'." );
+ }
+ wfProfileIn( __METHOD__ );
+ $ok = $this->doAck( $job );
+ wfProfileOut( __METHOD__ );
+ return $ok;
+ }
+
+ /**
+ * @see JobQueue::ack()
+ * @return bool
+ */
+ abstract protected function doAck( Job $job );
+
+ /**
+ * Register the "root job" of a given job into the queue for de-duplication.
+ * This should only be called right *after* all the new jobs have been inserted.
+ * This is used to turn older, duplicate, job entries into no-ops. The root job
+ * information will remain in the registry until it simply falls out of cache.
+ *
+ * This requires that $job has two special fields in the "params" array:
+ * - rootJobSignature : hash (e.g. SHA1) that identifies the task
+ * - rootJobTimestamp : TS_MW timestamp of this instance of the task
+ *
+ * A "root job" is a conceptual job that consist of potentially many smaller jobs
+ * that are actually inserted into the queue. For example, "refreshLinks" jobs are
+ * spawned when a template is edited. One can think of the task as "update links
+ * of pages that use template X" and an instance of that task as a "root job".
+ * However, what actually goes into the queue are potentially many refreshLinks2 jobs.
+ * Since these jobs include things like page ID ranges and DB master positions, and morph
+ * into smaller refreshLinks2 jobs recursively, simple duplicate detection (like job_sha1)
+ * for individual jobs being identical is not useful.
+ *
+ * In the case of "refreshLinks", if these jobs are still in the queue when the template
+ * is edited again, we want all of these old refreshLinks jobs for that template to become
+ * no-ops. This can greatly reduce server load, since refreshLinks jobs involves parsing.
+ * Essentially, the new batch of jobs belong to a new "root job" and the older ones to a
+ * previous "root job" for the same task of "update links of pages that use template X".
+ *
+ * This does nothing for certain queue classes.
+ *
+ * @param $job Job
+ * @return bool
+ * @throws MWException
+ */
+ final public function deduplicateRootJob( Job $job ) {
+ if ( $job->getType() !== $this->type ) {
+ throw new MWException( "Got '{$job->getType()}' job; expected '{$this->type}'." );
+ }
+ wfProfileIn( __METHOD__ );
+ $ok = $this->doDeduplicateRootJob( $job );
+ wfProfileOut( __METHOD__ );
+ return $ok;
+ }
+
+ /**
+ * @see JobQueue::deduplicateRootJob()
+ * @param $job Job
+ * @return bool
+ */
+ protected function doDeduplicateRootJob( Job $job ) {
+ return true;
+ }
+
+ /**
+ * Wait for any slaves or backup servers to catch up.
+ *
+ * This does nothing for certain queue classes.
+ *
+ * @return void
+ * @throws MWException
+ */
+ final public function waitForBackups() {
+ wfProfileIn( __METHOD__ );
+ $this->doWaitForBackups();
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * @see JobQueue::waitForBackups()
+ * @return void
+ */
+ protected function doWaitForBackups() {}
+
+ /**
+ * Return a map of task names to task definition maps.
+ * A "task" is a fast periodic queue maintenance action.
+ * Mutually exclusive tasks must implement their own locking in the callback.
+ *
+ * Each task value is an associative array with:
+ * - name : the name of the task
+ * - callback : a PHP callable that performs the task
+ * - period : the period in seconds corresponding to the task frequency
+ *
+ * @return Array
+ */
+ final public function getPeriodicTasks() {
+ $tasks = $this->doGetPeriodicTasks();
+ foreach ( $tasks as $name => &$def ) {
+ $def['name'] = $name;
+ }
+ return $tasks;
+ }
+
+ /**
+ * @see JobQueue::getPeriodicTasks()
+ * @return Array
+ */
+ protected function doGetPeriodicTasks() {
+ return array();
+ }
+
+ /**
+ * Clear any process and persistent caches
+ *
+ * @return void
+ */
+ final public function flushCaches() {
+ wfProfileIn( __METHOD__ );
+ $this->doFlushCaches();
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * @see JobQueue::flushCaches()
+ * @return void
+ */
+ protected function doFlushCaches() {}
+
+ /**
+ * Get an iterator to traverse over all of the jobs in this queue.
+ * This does not include jobs that are current acquired. In general,
+ * this should only be called on a queue that is no longer being popped.
+ *
+ * @return Iterator|Traversable|Array
+ * @throws MWException
+ */
+ abstract public function getAllQueuedJobs();
+
+ /**
+ * Namespace the queue with a key to isolate it for testing
+ *
+ * @param $key string
+ * @return void
+ * @throws MWException
+ */
+ public function setTestingPrefix( $key ) {
+ throw new MWException( "Queue namespacing not supported for this queue type." );
+ }
+}
diff --git a/includes/job/JobQueueAggregator.php b/includes/job/JobQueueAggregator.php
new file mode 100644
index 00000000..3dba3c53
--- /dev/null
+++ b/includes/job/JobQueueAggregator.php
@@ -0,0 +1,139 @@
+<?php
+/**
+ * Job queue aggregator code.
+ *
+ * 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
+ * @author Aaron Schulz
+ */
+
+/**
+ * Class to handle tracking information about all queues
+ *
+ * @ingroup JobQueue
+ * @since 1.21
+ */
+abstract class JobQueueAggregator {
+ /** @var JobQueueAggregator */
+ protected static $instance = null;
+
+ /**
+ * @param array $params
+ */
+ protected function __construct( array $params ) {}
+
+ /**
+ * @return JobQueueAggregator
+ */
+ final public static function singleton() {
+ global $wgJobQueueAggregator;
+
+ if ( !isset( self::$instance ) ) {
+ $class = $wgJobQueueAggregator['class'];
+ $obj = new $class( $wgJobQueueAggregator );
+ if ( !( $obj instanceof JobQueueAggregator ) ) {
+ throw new MWException( "Class '$class' is not a JobQueueAggregator class." );
+ }
+ self::$instance = $obj;
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Destroy the singleton instance
+ *
+ * @return void
+ */
+ final public static function destroySingleton() {
+ self::$instance = null;
+ }
+
+ /**
+ * Mark a queue as being empty
+ *
+ * @param string $wiki
+ * @param string $type
+ * @return bool Success
+ */
+ final public function notifyQueueEmpty( $wiki, $type ) {
+ wfProfileIn( __METHOD__ );
+ $ok = $this->doNotifyQueueEmpty( $wiki, $type );
+ wfProfileOut( __METHOD__ );
+ return $ok;
+ }
+
+ /**
+ * @see JobQueueAggregator::notifyQueueEmpty()
+ */
+ abstract protected function doNotifyQueueEmpty( $wiki, $type );
+
+ /**
+ * Mark a queue as being non-empty
+ *
+ * @param string $wiki
+ * @param string $type
+ * @return bool Success
+ */
+ final public function notifyQueueNonEmpty( $wiki, $type ) {
+ wfProfileIn( __METHOD__ );
+ $ok = $this->doNotifyQueueNonEmpty( $wiki, $type );
+ wfProfileOut( __METHOD__ );
+ return $ok;
+ }
+
+ /**
+ * @see JobQueueAggregator::notifyQueueNonEmpty()
+ */
+ abstract protected function doNotifyQueueNonEmpty( $wiki, $type );
+
+ /**
+ * Get the list of all of the queues with jobs
+ *
+ * @return Array (job type => (list of wiki IDs))
+ */
+ final public function getAllReadyWikiQueues() {
+ wfProfileIn( __METHOD__ );
+ $res = $this->doGetAllReadyWikiQueues();
+ wfProfileOut( __METHOD__ );
+ return $res;
+ }
+
+ /**
+ * @see JobQueueAggregator::getAllReadyWikiQueues()
+ */
+ abstract protected function doGetAllReadyWikiQueues();
+
+ /**
+ * Get all databases that have a pending job.
+ * This poll all the queues and is this expensive.
+ *
+ * @return Array (job type => (list of wiki IDs))
+ */
+ protected function findPendingWikiQueues() {
+ global $wgLocalDatabases;
+
+ $pendingDBs = array(); // (job type => (db list))
+ foreach ( $wgLocalDatabases as $db ) {
+ foreach ( JobQueueGroup::singleton( $db )->getQueuesWithJobs() as $type ) {
+ $pendingDBs[$type][] = $db;
+ }
+ }
+
+ return $pendingDBs;
+ }
+}
diff --git a/includes/job/JobQueueAggregatorMemc.php b/includes/job/JobQueueAggregatorMemc.php
new file mode 100644
index 00000000..4b82cf92
--- /dev/null
+++ b/includes/job/JobQueueAggregatorMemc.php
@@ -0,0 +1,117 @@
+<?php
+/**
+ * Job queue aggregator code that uses BagOStuff.
+ *
+ * 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
+ * @author Aaron Schulz
+ */
+
+/**
+ * Class to handle tracking information about all queues using BagOStuff
+ *
+ * @ingroup JobQueue
+ * @since 1.21
+ */
+class JobQueueAggregatorMemc extends JobQueueAggregator {
+ /** @var BagOStuff */
+ protected $cache;
+
+ protected $cacheTTL; // integer; seconds
+
+ /**
+ * @params include:
+ * - objectCache : Name of an object cache registered in $wgObjectCaches.
+ * This defaults to the one specified by $wgMainCacheType.
+ * - cacheTTL : Seconds to cache the aggregate data before regenerating.
+ * @param array $params
+ */
+ protected function __construct( array $params ) {
+ parent::__construct( $params );
+ $this->cache = isset( $params['objectCache'] )
+ ? wfGetCache( $params['objectCache'] )
+ : wfGetMainCache();
+ $this->cacheTTL = isset( $params['cacheTTL'] ) ? $params['cacheTTL'] : 180; // 3 min
+ }
+
+ /**
+ * @see JobQueueAggregator::doNotifyQueueEmpty()
+ */
+ protected function doNotifyQueueEmpty( $wiki, $type ) {
+ $key = $this->getReadyQueueCacheKey();
+ // Delist the queue from the "ready queue" list
+ if ( $this->cache->add( "$key:lock", 1, 60 ) ) { // lock
+ $curInfo = $this->cache->get( $key );
+ if ( is_array( $curInfo ) && isset( $curInfo['pendingDBs'][$type] ) ) {
+ if ( in_array( $wiki, $curInfo['pendingDBs'][$type] ) ) {
+ $curInfo['pendingDBs'][$type] = array_diff(
+ $curInfo['pendingDBs'][$type], array( $wiki ) );
+ $this->cache->set( $key, $curInfo );
+ }
+ }
+ $this->cache->delete( "$key:lock" ); // unlock
+ }
+ return true;
+ }
+
+ /**
+ * @see JobQueueAggregator::doNotifyQueueNonEmpty()
+ */
+ protected function doNotifyQueueNonEmpty( $wiki, $type ) {
+ return true; // updated periodically
+ }
+
+ /**
+ * @see JobQueueAggregator::doAllGetReadyWikiQueues()
+ */
+ protected function doGetAllReadyWikiQueues() {
+ $key = $this->getReadyQueueCacheKey();
+ // If the cache entry wasn't present, is stale, or in .1% of cases otherwise,
+ // regenerate the cache. Use any available stale cache if another process is
+ // currently regenerating the pending DB information.
+ $pendingDbInfo = $this->cache->get( $key );
+ if ( !is_array( $pendingDbInfo )
+ || ( time() - $pendingDbInfo['timestamp'] ) > $this->cacheTTL
+ || mt_rand( 0, 999 ) == 0
+ ) {
+ if ( $this->cache->add( "$key:rebuild", 1, 1800 ) ) { // lock
+ $pendingDbInfo = array(
+ 'pendingDBs' => $this->findPendingWikiQueues(),
+ 'timestamp' => time()
+ );
+ for ( $attempts=1; $attempts <= 25; ++$attempts ) {
+ if ( $this->cache->add( "$key:lock", 1, 60 ) ) { // lock
+ $this->cache->set( $key, $pendingDbInfo );
+ $this->cache->delete( "$key:lock" ); // unlock
+ break;
+ }
+ }
+ $this->cache->delete( "$key:rebuild" ); // unlock
+ }
+ }
+ return is_array( $pendingDbInfo )
+ ? $pendingDbInfo['pendingDBs']
+ : array(); // cache is both empty and locked
+ }
+
+ /**
+ * @return string
+ */
+ private function getReadyQueueCacheKey() {
+ return "jobqueue:aggregator:ready-queues:v1"; // global
+ }
+}
diff --git a/includes/job/JobQueueAggregatorRedis.php b/includes/job/JobQueueAggregatorRedis.php
new file mode 100644
index 00000000..74e9171c
--- /dev/null
+++ b/includes/job/JobQueueAggregatorRedis.php
@@ -0,0 +1,165 @@
+<?php
+/**
+ * Job queue aggregator code that uses PhpRedis.
+ *
+ * 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
+ * @author Aaron Schulz
+ */
+
+/**
+ * Class to handle tracking information about all queues using PhpRedis
+ *
+ * @ingroup JobQueue
+ * @since 1.21
+ */
+class JobQueueAggregatorRedis extends JobQueueAggregator {
+ /** @var RedisConnectionPool */
+ protected $redisPool;
+
+ /**
+ * @params include:
+ * - redisConfig : An array of parameters to RedisConnectionPool::__construct().
+ * - redisServer : A hostname/port combination or the absolute path of a UNIX socket.
+ * If a hostname is specified but no port, the standard port number
+ * 6379 will be used. Required.
+ * @param array $params
+ */
+ protected function __construct( array $params ) {
+ parent::__construct( $params );
+ $this->server = $params['redisServer'];
+ $this->redisPool = RedisConnectionPool::singleton( $params['redisConfig'] );
+ }
+
+ /**
+ * @see JobQueueAggregator::doNotifyQueueEmpty()
+ */
+ protected function doNotifyQueueEmpty( $wiki, $type ) {
+ $conn = $this->getConnection();
+ if ( !$conn ) {
+ return false;
+ }
+ try {
+ $conn->hDel( $this->getReadyQueueKey(), $this->encQueueName( $type, $wiki ) );
+ return true;
+ } catch ( RedisException $e ) {
+ $this->handleException( $conn, $e );
+ return false;
+ }
+ }
+
+ /**
+ * @see JobQueueAggregator::doNotifyQueueNonEmpty()
+ */
+ protected function doNotifyQueueNonEmpty( $wiki, $type ) {
+ $conn = $this->getConnection();
+ if ( !$conn ) {
+ return false;
+ }
+ try {
+ $conn->hSet( $this->getReadyQueueKey(), $this->encQueueName( $type, $wiki ), time() );
+ return true;
+ } catch ( RedisException $e ) {
+ $this->handleException( $conn, $e );
+ return false;
+ }
+ }
+
+ /**
+ * @see JobQueueAggregator::doAllGetReadyWikiQueues()
+ */
+ protected function doGetAllReadyWikiQueues() {
+ $conn = $this->getConnection();
+ if ( !$conn ) {
+ return array();
+ }
+ try {
+ $conn->multi( Redis::PIPELINE );
+ $conn->exists( $this->getReadyQueueKey() );
+ $conn->hGetAll( $this->getReadyQueueKey() );
+ list( $exists, $map ) = $conn->exec();
+
+ if ( $exists ) { // cache hit
+ $pendingDBs = array(); // (type => list of wikis)
+ foreach ( $map as $key => $time ) {
+ list( $type, $wiki ) = $this->dencQueueName( $key );
+ $pendingDBs[$type][] = $wiki;
+ }
+ } else { // cache miss
+ $pendingDBs = $this->findPendingWikiQueues(); // (type => list of wikis)
+
+ $now = time();
+ $map = array();
+ foreach ( $pendingDBs as $type => $wikis ) {
+ foreach ( $wikis as $wiki ) {
+ $map[$this->encQueueName( $type, $wiki )] = $now;
+ }
+ }
+ $conn->hMSet( $this->getReadyQueueKey(), $map );
+ }
+
+ return $pendingDBs;
+ } catch ( RedisException $e ) {
+ $this->handleException( $conn, $e );
+ return array();
+ }
+ }
+
+ /**
+ * Get a connection to the server that handles all sub-queues for this queue
+ *
+ * @return Array (server name, Redis instance)
+ * @throws MWException
+ */
+ protected function getConnection() {
+ return $this->redisPool->getConnection( $this->server );
+ }
+
+ /**
+ * @param RedisConnRef $conn
+ * @param RedisException $e
+ * @return void
+ */
+ protected function handleException( RedisConnRef $conn, $e ) {
+ $this->redisPool->handleException( $this->server, $conn, $e );
+ }
+
+ /**
+ * @return string
+ */
+ private function getReadyQueueKey() {
+ return "jobqueue:aggregator:h-ready-queues:v1"; // global
+ }
+
+ /**
+ * @param string $type
+ * @param string $wiki
+ * @return string
+ */
+ private function encQueueName( $type, $wiki ) {
+ return rawurlencode( $type ) . '/' . rawurlencode( $wiki );
+ }
+
+ /**
+ * @param string $name
+ * @return string
+ */
+ private function dencQueueName( $name ) {
+ list( $type, $wiki ) = explode( '/', $name, 2 );
+ return array( rawurldecode( $type ), rawurldecode( $wiki ) );
+ }
+}
diff --git a/includes/job/JobQueueDB.php b/includes/job/JobQueueDB.php
new file mode 100644
index 00000000..ff7f7abc
--- /dev/null
+++ b/includes/job/JobQueueDB.php
@@ -0,0 +1,716 @@
+<?php
+/**
+ * Database-backed job queue code.
+ *
+ * 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
+ * @author Aaron Schulz
+ */
+
+/**
+ * Class to handle job queues stored in the DB
+ *
+ * @ingroup JobQueue
+ * @since 1.21
+ */
+class JobQueueDB extends JobQueue {
+ const ROOTJOB_TTL = 1209600; // integer; seconds to remember root jobs (14 days)
+ const CACHE_TTL_SHORT = 30; // integer; seconds to cache info without re-validating
+ const CACHE_TTL_LONG = 300; // integer; seconds to cache info that is kept up to date
+ const MAX_AGE_PRUNE = 604800; // integer; seconds a job can live once claimed
+ const MAX_JOB_RANDOM = 2147483647; // integer; 2^31 - 1, used for job_random
+ const MAX_OFFSET = 255; // integer; maximum number of rows to skip
+
+ protected $cluster = false; // string; name of an external DB cluster
+
+ /**
+ * Additional parameters include:
+ * - cluster : The name of an external cluster registered via LBFactory.
+ * If not specified, the primary DB cluster for the wiki will be used.
+ * This can be overridden with a custom cluster so that DB handles will
+ * be retrieved via LBFactory::getExternalLB() and getConnection().
+ * @param $params array
+ */
+ protected function __construct( array $params ) {
+ parent::__construct( $params );
+ $this->cluster = isset( $params['cluster'] ) ? $params['cluster'] : false;
+ }
+
+ protected function supportedOrders() {
+ return array( 'random', 'timestamp', 'fifo' );
+ }
+
+ protected function optimalOrder() {
+ return 'random';
+ }
+
+ /**
+ * @see JobQueue::doIsEmpty()
+ * @return bool
+ */
+ protected function doIsEmpty() {
+ global $wgMemc;
+
+ $key = $this->getCacheKey( 'empty' );
+
+ $isEmpty = $wgMemc->get( $key );
+ if ( $isEmpty === 'true' ) {
+ return true;
+ } elseif ( $isEmpty === 'false' ) {
+ return false;
+ }
+
+ list( $dbr, $scope ) = $this->getSlaveDB();
+ $found = $dbr->selectField( // unclaimed job
+ 'job', '1', array( 'job_cmd' => $this->type, 'job_token' => '' ), __METHOD__
+ );
+ $wgMemc->add( $key, $found ? 'false' : 'true', self::CACHE_TTL_LONG );
+
+ return !$found;
+ }
+
+ /**
+ * @see JobQueue::doGetSize()
+ * @return integer
+ */
+ protected function doGetSize() {
+ global $wgMemc;
+
+ $key = $this->getCacheKey( 'size' );
+
+ $size = $wgMemc->get( $key );
+ if ( is_int( $size ) ) {
+ return $size;
+ }
+
+ list( $dbr, $scope ) = $this->getSlaveDB();
+ $size = (int)$dbr->selectField( 'job', 'COUNT(*)',
+ array( 'job_cmd' => $this->type, 'job_token' => '' ),
+ __METHOD__
+ );
+ $wgMemc->set( $key, $size, self::CACHE_TTL_SHORT );
+
+ return $size;
+ }
+
+ /**
+ * @see JobQueue::doGetAcquiredCount()
+ * @return integer
+ */
+ protected function doGetAcquiredCount() {
+ global $wgMemc;
+
+ if ( $this->claimTTL <= 0 ) {
+ return 0; // no acknowledgements
+ }
+
+ $key = $this->getCacheKey( 'acquiredcount' );
+
+ $count = $wgMemc->get( $key );
+ if ( is_int( $count ) ) {
+ return $count;
+ }
+
+ list( $dbr, $scope ) = $this->getSlaveDB();
+ $count = (int)$dbr->selectField( 'job', 'COUNT(*)',
+ array( 'job_cmd' => $this->type, "job_token != {$dbr->addQuotes( '' )}" ),
+ __METHOD__
+ );
+ $wgMemc->set( $key, $count, self::CACHE_TTL_SHORT );
+
+ return $count;
+ }
+
+ /**
+ * @see JobQueue::doBatchPush()
+ * @param array $jobs
+ * @param $flags
+ * @throws DBError|Exception
+ * @return bool
+ */
+ protected function doBatchPush( array $jobs, $flags ) {
+ if ( count( $jobs ) ) {
+ list( $dbw, $scope ) = $this->getMasterDB();
+
+ $rowSet = array(); // (sha1 => job) map for jobs that are de-duplicated
+ $rowList = array(); // list of jobs for jobs that are are not de-duplicated
+
+ foreach ( $jobs as $job ) {
+ $row = $this->insertFields( $job );
+ if ( $job->ignoreDuplicates() ) {
+ $rowSet[$row['job_sha1']] = $row;
+ } else {
+ $rowList[] = $row;
+ }
+ }
+
+ $key = $this->getCacheKey( 'empty' );
+ $atomic = ( $flags & self::QoS_Atomic );
+
+ $dbw->onTransactionIdle(
+ function() use ( $dbw, $rowSet, $rowList, $atomic, $key, $scope
+ ) {
+ global $wgMemc;
+
+ if ( $atomic ) {
+ $dbw->begin( __METHOD__ ); // wrap all the job additions in one transaction
+ }
+ try {
+ // Strip out any duplicate jobs that are already in the queue...
+ if ( count( $rowSet ) ) {
+ $res = $dbw->select( 'job', 'job_sha1',
+ array(
+ // No job_type condition since it's part of the job_sha1 hash
+ 'job_sha1' => array_keys( $rowSet ),
+ 'job_token' => '' // unclaimed
+ ),
+ __METHOD__
+ );
+ foreach ( $res as $row ) {
+ wfDebug( "Job with hash '{$row->job_sha1}' is a duplicate." );
+ unset( $rowSet[$row->job_sha1] ); // already enqueued
+ }
+ }
+ // Build the full list of job rows to insert
+ $rows = array_merge( $rowList, array_values( $rowSet ) );
+ // Insert the job rows in chunks to avoid slave lag...
+ foreach ( array_chunk( $rows, 50 ) as $rowBatch ) {
+ $dbw->insert( 'job', $rowBatch, __METHOD__ );
+ }
+ wfIncrStats( 'job-insert', count( $rows ) );
+ wfIncrStats( 'job-insert-duplicate',
+ count( $rowSet ) + count( $rowList ) - count( $rows ) );
+ } catch ( DBError $e ) {
+ if ( $atomic ) {
+ $dbw->rollback( __METHOD__ );
+ }
+ throw $e;
+ }
+ if ( $atomic ) {
+ $dbw->commit( __METHOD__ );
+ }
+
+ $wgMemc->set( $key, 'false', JobQueueDB::CACHE_TTL_LONG );
+ } );
+ }
+
+ return true;
+ }
+
+ /**
+ * @see JobQueue::doPop()
+ * @return Job|bool
+ */
+ protected function doPop() {
+ global $wgMemc;
+
+ if ( $wgMemc->get( $this->getCacheKey( 'empty' ) ) === 'true' ) {
+ return false; // queue is empty
+ }
+
+ list( $dbw, $scope ) = $this->getMasterDB();
+ $dbw->commit( __METHOD__, 'flush' ); // flush existing transaction
+ $autoTrx = $dbw->getFlag( DBO_TRX ); // get current setting
+ $dbw->clearFlag( DBO_TRX ); // make each query its own transaction
+ $scopedReset = new ScopedCallback( function() use ( $dbw, $autoTrx ) {
+ $dbw->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore old setting
+ } );
+
+ $uuid = wfRandomString( 32 ); // pop attempt
+ $job = false; // job popped off
+ do { // retry when our row is invalid or deleted as a duplicate
+ // Try to reserve a row in the DB...
+ if ( in_array( $this->order, array( 'fifo', 'timestamp' ) ) ) {
+ $row = $this->claimOldest( $uuid );
+ } else { // random first
+ $rand = mt_rand( 0, self::MAX_JOB_RANDOM ); // encourage concurrent UPDATEs
+ $gte = (bool)mt_rand( 0, 1 ); // find rows with rand before/after $rand
+ $row = $this->claimRandom( $uuid, $rand, $gte );
+ }
+ // Check if we found a row to reserve...
+ if ( !$row ) {
+ $wgMemc->set( $this->getCacheKey( 'empty' ), 'true', self::CACHE_TTL_LONG );
+ break; // nothing to do
+ }
+ wfIncrStats( 'job-pop' );
+ // Get the job object from the row...
+ $title = Title::makeTitleSafe( $row->job_namespace, $row->job_title );
+ if ( !$title ) {
+ $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
+ wfDebugLog( 'JobQueueDB', "Row has invalid title '{$row->job_title}'." );
+ continue; // try again
+ }
+ $job = Job::factory( $row->job_cmd, $title,
+ self::extractBlob( $row->job_params ), $row->job_id );
+ $job->id = $row->job_id; // XXX: work around broken subclasses
+ // Flag this job as an old duplicate based on its "root" job...
+ if ( $this->isRootJobOldDuplicate( $job ) ) {
+ wfIncrStats( 'job-pop-duplicate' );
+ $job = DuplicateJob::newFromJob( $job ); // convert to a no-op
+ }
+ break; // done
+ } while( true );
+
+ return $job;
+ }
+
+ /**
+ * Reserve a row with a single UPDATE without holding row locks over RTTs...
+ *
+ * @param string $uuid 32 char hex string
+ * @param $rand integer Random unsigned integer (31 bits)
+ * @param bool $gte Search for job_random >= $random (otherwise job_random <= $random)
+ * @return Row|false
+ */
+ protected function claimRandom( $uuid, $rand, $gte ) {
+ global $wgMemc;
+
+ list( $dbw, $scope ) = $this->getMasterDB();
+ // Check cache to see if the queue has <= OFFSET items
+ $tinyQueue = $wgMemc->get( $this->getCacheKey( 'small' ) );
+
+ $row = false; // the row acquired
+ $invertedDirection = false; // whether one job_random direction was already scanned
+ // This uses a replication safe method for acquiring jobs. One could use UPDATE+LIMIT
+ // instead, but that either uses ORDER BY (in which case it deadlocks in MySQL) or is
+ // not replication safe. Due to http://bugs.mysql.com/bug.php?id=6980, subqueries cannot
+ // be used here with MySQL.
+ do {
+ if ( $tinyQueue ) { // queue has <= MAX_OFFSET rows
+ // For small queues, using OFFSET will overshoot and return no rows more often.
+ // Instead, this uses job_random to pick a row (possibly checking both directions).
+ $ineq = $gte ? '>=' : '<=';
+ $dir = $gte ? 'ASC' : 'DESC';
+ $row = $dbw->selectRow( 'job', '*', // find a random job
+ array(
+ 'job_cmd' => $this->type,
+ 'job_token' => '', // unclaimed
+ "job_random {$ineq} {$dbw->addQuotes( $rand )}" ),
+ __METHOD__,
+ array( 'ORDER BY' => "job_random {$dir}" )
+ );
+ if ( !$row && !$invertedDirection ) {
+ $gte = !$gte;
+ $invertedDirection = true;
+ continue; // try the other direction
+ }
+ } else { // table *may* have >= MAX_OFFSET rows
+ // Bug 42614: "ORDER BY job_random" with a job_random inequality causes high CPU
+ // in MySQL if there are many rows for some reason. This uses a small OFFSET
+ // instead of job_random for reducing excess claim retries.
+ $row = $dbw->selectRow( 'job', '*', // find a random job
+ array(
+ 'job_cmd' => $this->type,
+ 'job_token' => '', // unclaimed
+ ),
+ __METHOD__,
+ array( 'OFFSET' => mt_rand( 0, self::MAX_OFFSET ) )
+ );
+ if ( !$row ) {
+ $tinyQueue = true; // we know the queue must have <= MAX_OFFSET rows
+ $wgMemc->set( $this->getCacheKey( 'small' ), 1, 30 );
+ continue; // use job_random
+ }
+ }
+ if ( $row ) { // claim the job
+ $dbw->update( 'job', // update by PK
+ array(
+ 'job_token' => $uuid,
+ 'job_token_timestamp' => $dbw->timestamp(),
+ 'job_attempts = job_attempts+1' ),
+ array( 'job_cmd' => $this->type, 'job_id' => $row->job_id, 'job_token' => '' ),
+ __METHOD__
+ );
+ // This might get raced out by another runner when claiming the previously
+ // selected row. The use of job_random should minimize this problem, however.
+ if ( !$dbw->affectedRows() ) {
+ $row = false; // raced out
+ }
+ } else {
+ break; // nothing to do
+ }
+ } while ( !$row );
+
+ return $row;
+ }
+
+ /**
+ * Reserve a row with a single UPDATE without holding row locks over RTTs...
+ *
+ * @param string $uuid 32 char hex string
+ * @return Row|false
+ */
+ protected function claimOldest( $uuid ) {
+ list( $dbw, $scope ) = $this->getMasterDB();
+
+ $row = false; // the row acquired
+ do {
+ if ( $dbw->getType() === 'mysql' ) {
+ // Per http://bugs.mysql.com/bug.php?id=6980, we can't use subqueries on the
+ // same table being changed in an UPDATE query in MySQL (gives Error: 1093).
+ // Oracle and Postgre have no such limitation. However, MySQL offers an
+ // alternative here by supporting ORDER BY + LIMIT for UPDATE queries.
+ $dbw->query( "UPDATE {$dbw->tableName( 'job' )} " .
+ "SET " .
+ "job_token = {$dbw->addQuotes( $uuid ) }, " .
+ "job_token_timestamp = {$dbw->addQuotes( $dbw->timestamp() )}, " .
+ "job_attempts = job_attempts+1 " .
+ "WHERE ( " .
+ "job_cmd = {$dbw->addQuotes( $this->type )} " .
+ "AND job_token = {$dbw->addQuotes( '' )} " .
+ ") ORDER BY job_id ASC LIMIT 1",
+ __METHOD__
+ );
+ } else {
+ // Use a subquery to find the job, within an UPDATE to claim it.
+ // This uses as much of the DB wrapper functions as possible.
+ $dbw->update( 'job',
+ array(
+ 'job_token' => $uuid,
+ 'job_token_timestamp' => $dbw->timestamp(),
+ 'job_attempts = job_attempts+1' ),
+ array( 'job_id = (' .
+ $dbw->selectSQLText( 'job', 'job_id',
+ array( 'job_cmd' => $this->type, 'job_token' => '' ),
+ __METHOD__,
+ array( 'ORDER BY' => 'job_id ASC', 'LIMIT' => 1 ) ) .
+ ')'
+ ),
+ __METHOD__
+ );
+ }
+ // Fetch any row that we just reserved...
+ if ( $dbw->affectedRows() ) {
+ $row = $dbw->selectRow( 'job', '*',
+ array( 'job_cmd' => $this->type, 'job_token' => $uuid ), __METHOD__
+ );
+ if ( !$row ) { // raced out by duplicate job removal
+ wfDebugLog( 'JobQueueDB', "Row deleted as duplicate by another process." );
+ }
+ } else {
+ break; // nothing to do
+ }
+ } while ( !$row );
+
+ return $row;
+ }
+
+ /**
+ * Recycle or destroy any jobs that have been claimed for too long
+ *
+ * @return integer Number of jobs recycled/deleted
+ */
+ public function recycleAndDeleteStaleJobs() {
+ global $wgMemc;
+
+ $now = time();
+ list( $dbw, $scope ) = $this->getMasterDB();
+ $count = 0; // affected rows
+
+ if ( !$dbw->lock( "jobqueue-recycle-{$this->type}", __METHOD__, 1 ) ) {
+ return $count; // already in progress
+ }
+
+ // Remove claims on jobs acquired for too long if enabled...
+ if ( $this->claimTTL > 0 ) {
+ $claimCutoff = $dbw->timestamp( $now - $this->claimTTL );
+ // Get the IDs of jobs that have be claimed but not finished after too long.
+ // These jobs can be recycled into the queue by expiring the claim. Selecting
+ // the IDs first means that the UPDATE can be done by primary key (less deadlocks).
+ $res = $dbw->select( 'job', 'job_id',
+ array(
+ 'job_cmd' => $this->type,
+ "job_token != {$dbw->addQuotes( '' )}", // was acquired
+ "job_token_timestamp < {$dbw->addQuotes( $claimCutoff )}", // stale
+ "job_attempts < {$dbw->addQuotes( $this->maxTries )}" ), // retries left
+ __METHOD__
+ );
+ $ids = array_map( function( $o ) { return $o->job_id; }, iterator_to_array( $res ) );
+ if ( count( $ids ) ) {
+ // Reset job_token for these jobs so that other runners will pick them up.
+ // Set the timestamp to the current time, as it is useful to now that the job
+ // was already tried before (the timestamp becomes the "released" time).
+ $dbw->update( 'job',
+ array(
+ 'job_token' => '',
+ 'job_token_timestamp' => $dbw->timestamp( $now ) ), // time of release
+ array(
+ 'job_id' => $ids ),
+ __METHOD__
+ );
+ $count += $dbw->affectedRows();
+ wfIncrStats( 'job-recycle', $dbw->affectedRows() );
+ $wgMemc->set( $this->getCacheKey( 'empty' ), 'false', self::CACHE_TTL_LONG );
+ }
+ }
+
+ // Just destroy any stale jobs...
+ $pruneCutoff = $dbw->timestamp( $now - self::MAX_AGE_PRUNE );
+ $conds = array(
+ 'job_cmd' => $this->type,
+ "job_token != {$dbw->addQuotes( '' )}", // was acquired
+ "job_token_timestamp < {$dbw->addQuotes( $pruneCutoff )}" // stale
+ );
+ if ( $this->claimTTL > 0 ) { // only prune jobs attempted too many times...
+ $conds[] = "job_attempts >= {$dbw->addQuotes( $this->maxTries )}";
+ }
+ // Get the IDs of jobs that are considered stale and should be removed. Selecting
+ // the IDs first means that the UPDATE can be done by primary key (less deadlocks).
+ $res = $dbw->select( 'job', 'job_id', $conds, __METHOD__ );
+ $ids = array_map( function( $o ) { return $o->job_id; }, iterator_to_array( $res ) );
+ if ( count( $ids ) ) {
+ $dbw->delete( 'job', array( 'job_id' => $ids ), __METHOD__ );
+ $count += $dbw->affectedRows();
+ }
+
+ $dbw->unlock( "jobqueue-recycle-{$this->type}", __METHOD__ );
+
+ return $count;
+ }
+
+ /**
+ * @see JobQueue::doAck()
+ * @param Job $job
+ * @throws MWException
+ * @return Job|bool
+ */
+ protected function doAck( Job $job ) {
+ if ( !$job->getId() ) {
+ throw new MWException( "Job of type '{$job->getType()}' has no ID." );
+ }
+
+ list( $dbw, $scope ) = $this->getMasterDB();
+ $dbw->commit( __METHOD__, 'flush' ); // flush existing transaction
+ $autoTrx = $dbw->getFlag( DBO_TRX ); // get current setting
+ $dbw->clearFlag( DBO_TRX ); // make each query its own transaction
+ $scopedReset = new ScopedCallback( function() use ( $dbw, $autoTrx ) {
+ $dbw->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore old setting
+ } );
+
+ // Delete a row with a single DELETE without holding row locks over RTTs...
+ $dbw->delete( 'job',
+ array( 'job_cmd' => $this->type, 'job_id' => $job->getId() ), __METHOD__ );
+
+ return true;
+ }
+
+ /**
+ * @see JobQueue::doDeduplicateRootJob()
+ * @param Job $job
+ * @throws MWException
+ * @return bool
+ */
+ protected function doDeduplicateRootJob( Job $job ) {
+ $params = $job->getParams();
+ if ( !isset( $params['rootJobSignature'] ) ) {
+ throw new MWException( "Cannot register root job; missing 'rootJobSignature'." );
+ } elseif ( !isset( $params['rootJobTimestamp'] ) ) {
+ throw new MWException( "Cannot register root job; missing 'rootJobTimestamp'." );
+ }
+ $key = $this->getRootJobCacheKey( $params['rootJobSignature'] );
+ // Callers should call batchInsert() and then this function so that if the insert
+ // fails, the de-duplication registration will be aborted. Since the insert is
+ // deferred till "transaction idle", do the same here, so that the ordering is
+ // maintained. Having only the de-duplication registration succeed would cause
+ // jobs to become no-ops without any actual jobs that made them redundant.
+ list( $dbw, $scope ) = $this->getMasterDB();
+ $dbw->onTransactionIdle( function() use ( $params, $key, $scope ) {
+ global $wgMemc;
+
+ $timestamp = $wgMemc->get( $key ); // current last timestamp of this job
+ if ( $timestamp && $timestamp >= $params['rootJobTimestamp'] ) {
+ return true; // a newer version of this root job was enqueued
+ }
+
+ // Update the timestamp of the last root job started at the location...
+ return $wgMemc->set( $key, $params['rootJobTimestamp'], JobQueueDB::ROOTJOB_TTL );
+ } );
+
+ return true;
+ }
+
+ /**
+ * Check if the "root" job of a given job has been superseded by a newer one
+ *
+ * @param $job Job
+ * @return bool
+ */
+ protected function isRootJobOldDuplicate( Job $job ) {
+ global $wgMemc;
+
+ $params = $job->getParams();
+ if ( !isset( $params['rootJobSignature'] ) ) {
+ return false; // job has no de-deplication info
+ } elseif ( !isset( $params['rootJobTimestamp'] ) ) {
+ trigger_error( "Cannot check root job; missing 'rootJobTimestamp'." );
+ return false;
+ }
+
+ // Get the last time this root job was enqueued
+ $timestamp = $wgMemc->get( $this->getRootJobCacheKey( $params['rootJobSignature'] ) );
+
+ // Check if a new root job was started at the location after this one's...
+ return ( $timestamp && $timestamp > $params['rootJobTimestamp'] );
+ }
+
+ /**
+ * @see JobQueue::doWaitForBackups()
+ * @return void
+ */
+ protected function doWaitForBackups() {
+ wfWaitForSlaves();
+ }
+
+ /**
+ * @return Array
+ */
+ protected function doGetPeriodicTasks() {
+ return array(
+ 'recycleAndDeleteStaleJobs' => array(
+ 'callback' => array( $this, 'recycleAndDeleteStaleJobs' ),
+ 'period' => ceil( $this->claimTTL / 2 )
+ )
+ );
+ }
+
+ /**
+ * @return void
+ */
+ protected function doFlushCaches() {
+ global $wgMemc;
+
+ foreach ( array( 'empty', 'size', 'acquiredcount' ) as $type ) {
+ $wgMemc->delete( $this->getCacheKey( $type ) );
+ }
+ }
+
+ /**
+ * @see JobQueue::getAllQueuedJobs()
+ * @return Iterator
+ */
+ public function getAllQueuedJobs() {
+ list( $dbr, $scope ) = $this->getSlaveDB();
+ return new MappedIterator(
+ $dbr->select( 'job', '*', array( 'job_cmd' => $this->getType(), 'job_token' => '' ) ),
+ function( $row ) use ( $scope ) {
+ $job = Job::factory(
+ $row->job_cmd,
+ Title::makeTitle( $row->job_namespace, $row->job_title ),
+ strlen( $row->job_params ) ? unserialize( $row->job_params ) : false,
+ $row->job_id
+ );
+ $job->id = $row->job_id; // XXX: work around broken subclasses
+ return $job;
+ }
+ );
+ }
+
+ /**
+ * @return Array (DatabaseBase, ScopedCallback)
+ */
+ protected function getSlaveDB() {
+ return $this->getDB( DB_SLAVE );
+ }
+
+ /**
+ * @return Array (DatabaseBase, ScopedCallback)
+ */
+ protected function getMasterDB() {
+ return $this->getDB( DB_MASTER );
+ }
+
+ /**
+ * @param $index integer (DB_SLAVE/DB_MASTER)
+ * @return Array (DatabaseBase, ScopedCallback)
+ */
+ protected function getDB( $index ) {
+ $lb = ( $this->cluster !== false )
+ ? wfGetLBFactory()->getExternalLB( $this->cluster, $this->wiki )
+ : wfGetLB( $this->wiki );
+ $conn = $lb->getConnection( $index, array(), $this->wiki );
+ return array(
+ $conn,
+ new ScopedCallback( function() use ( $lb, $conn ) {
+ $lb->reuseConnection( $conn );
+ } )
+ );
+ }
+
+ /**
+ * @param $job Job
+ * @return array
+ */
+ protected function insertFields( Job $job ) {
+ list( $dbw, $scope ) = $this->getMasterDB();
+ return array(
+ // Fields that describe the nature of the job
+ 'job_cmd' => $job->getType(),
+ 'job_namespace' => $job->getTitle()->getNamespace(),
+ 'job_title' => $job->getTitle()->getDBkey(),
+ 'job_params' => self::makeBlob( $job->getParams() ),
+ // Additional job metadata
+ 'job_id' => $dbw->nextSequenceValue( 'job_job_id_seq' ),
+ 'job_timestamp' => $dbw->timestamp(),
+ 'job_sha1' => wfBaseConvert(
+ sha1( serialize( $job->getDeduplicationInfo() ) ),
+ 16, 36, 31
+ ),
+ 'job_random' => mt_rand( 0, self::MAX_JOB_RANDOM )
+ );
+ }
+
+ /**
+ * @return string
+ */
+ private function getCacheKey( $property ) {
+ list( $db, $prefix ) = wfSplitWikiID( $this->wiki );
+ return wfForeignMemcKey( $db, $prefix, 'jobqueue', $this->type, $property );
+ }
+
+ /**
+ * @param string $signature Hash identifier of the root job
+ * @return string
+ */
+ private function getRootJobCacheKey( $signature ) {
+ list( $db, $prefix ) = wfSplitWikiID( $this->wiki );
+ return wfForeignMemcKey( $db, $prefix, 'jobqueue', $this->type, 'rootjob', $signature );
+ }
+
+ /**
+ * @param $params
+ * @return string
+ */
+ protected static function makeBlob( $params ) {
+ if ( $params !== false ) {
+ return serialize( $params );
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * @param $blob
+ * @return bool|mixed
+ */
+ protected static function extractBlob( $blob ) {
+ if ( (string)$blob !== '' ) {
+ return unserialize( $blob );
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/includes/job/JobQueueGroup.php b/includes/job/JobQueueGroup.php
new file mode 100644
index 00000000..351c71a3
--- /dev/null
+++ b/includes/job/JobQueueGroup.php
@@ -0,0 +1,351 @@
+<?php
+/**
+ * Job queue base code.
+ *
+ * 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
+ * @author Aaron Schulz
+ */
+
+/**
+ * Class to handle enqueueing of background jobs
+ *
+ * @ingroup JobQueue
+ * @since 1.21
+ */
+class JobQueueGroup {
+ /** @var Array */
+ protected static $instances = array();
+
+ /** @var ProcessCacheLRU */
+ protected $cache;
+
+ protected $wiki; // string; wiki ID
+
+ const TYPE_DEFAULT = 1; // integer; jobs popped by default
+ const TYPE_ANY = 2; // integer; any job
+
+ const USE_CACHE = 1; // integer; use process or persistent cache
+
+ const PROC_CACHE_TTL = 15; // integer; seconds
+
+ const CACHE_VERSION = 1; // integer; cache version
+
+ /**
+ * @param string $wiki Wiki ID
+ */
+ protected function __construct( $wiki ) {
+ $this->wiki = $wiki;
+ $this->cache = new ProcessCacheLRU( 10 );
+ }
+
+ /**
+ * @param string $wiki Wiki ID
+ * @return JobQueueGroup
+ */
+ public static function singleton( $wiki = false ) {
+ $wiki = ( $wiki === false ) ? wfWikiID() : $wiki;
+ if ( !isset( self::$instances[$wiki] ) ) {
+ self::$instances[$wiki] = new self( $wiki );
+ }
+ return self::$instances[$wiki];
+ }
+
+ /**
+ * Destroy the singleton instances
+ *
+ * @return void
+ */
+ public static function destroySingletons() {
+ self::$instances = array();
+ }
+
+ /**
+ * Get the job queue object for a given queue type
+ *
+ * @param $type string
+ * @return JobQueue
+ */
+ public function get( $type ) {
+ global $wgJobTypeConf;
+
+ $conf = array( 'wiki' => $this->wiki, 'type' => $type );
+ if ( isset( $wgJobTypeConf[$type] ) ) {
+ $conf = $conf + $wgJobTypeConf[$type];
+ } else {
+ $conf = $conf + $wgJobTypeConf['default'];
+ }
+
+ return JobQueue::factory( $conf );
+ }
+
+ /**
+ * Insert jobs into the respective queues of with the belong.
+ *
+ * This inserts the jobs into the queue specified by $wgJobTypeConf
+ * and updates the aggregate job queue information cache as needed.
+ *
+ * @param $jobs Job|array A single Job or a list of Jobs
+ * @throws MWException
+ * @return bool
+ */
+ public function push( $jobs ) {
+ $jobs = is_array( $jobs ) ? $jobs : array( $jobs );
+
+ $jobsByType = array(); // (job type => list of jobs)
+ foreach ( $jobs as $job ) {
+ if ( $job instanceof Job ) {
+ $jobsByType[$job->getType()][] = $job;
+ } else {
+ throw new MWException( "Attempted to push a non-Job object into a queue." );
+ }
+ }
+
+ $ok = true;
+ foreach ( $jobsByType as $type => $jobs ) {
+ if ( $this->get( $type )->push( $jobs ) ) {
+ JobQueueAggregator::singleton()->notifyQueueNonEmpty( $this->wiki, $type );
+ } else {
+ $ok = false;
+ }
+ }
+
+ if ( $this->cache->has( 'queues-ready', 'list' ) ) {
+ $list = $this->cache->get( 'queues-ready', 'list' );
+ if ( count( array_diff( array_keys( $jobsByType ), $list ) ) ) {
+ $this->cache->clear( 'queues-ready' );
+ }
+ }
+
+ return $ok;
+ }
+
+ /**
+ * Pop a job off one of the job queues
+ *
+ * This pops a job off a queue as specified by $wgJobTypeConf and
+ * updates the aggregate job queue information cache as needed.
+ *
+ * @param $qtype integer|string JobQueueGroup::TYPE_DEFAULT or type string
+ * @param $flags integer Bitfield of JobQueueGroup::USE_* constants
+ * @return Job|bool Returns false on failure
+ */
+ public function pop( $qtype = self::TYPE_DEFAULT, $flags = 0 ) {
+ if ( is_string( $qtype ) ) { // specific job type
+ $job = $this->get( $qtype )->pop();
+ if ( !$job ) {
+ JobQueueAggregator::singleton()->notifyQueueEmpty( $this->wiki, $qtype );
+ }
+ return $job;
+ } else { // any job in the "default" jobs types
+ if ( $flags & self::USE_CACHE ) {
+ if ( !$this->cache->has( 'queues-ready', 'list', self::PROC_CACHE_TTL ) ) {
+ $this->cache->set( 'queues-ready', 'list', $this->getQueuesWithJobs() );
+ }
+ $types = $this->cache->get( 'queues-ready', 'list' );
+ } else {
+ $types = $this->getQueuesWithJobs();
+ }
+
+ if ( $qtype == self::TYPE_DEFAULT ) {
+ $types = array_intersect( $types, $this->getDefaultQueueTypes() );
+ }
+ shuffle( $types ); // avoid starvation
+
+ foreach ( $types as $type ) { // for each queue...
+ $job = $this->get( $type )->pop();
+ if ( $job ) { // found
+ return $job;
+ } else { // not found
+ JobQueueAggregator::singleton()->notifyQueueEmpty( $this->wiki, $type );
+ $this->cache->clear( 'queues-ready' );
+ }
+ }
+
+ return false; // no jobs found
+ }
+ }
+
+ /**
+ * Acknowledge that a job was completed
+ *
+ * @param $job Job
+ * @return bool
+ */
+ public function ack( Job $job ) {
+ return $this->get( $job->getType() )->ack( $job );
+ }
+
+ /**
+ * Register the "root job" of a given job into the queue for de-duplication.
+ * This should only be called right *after* all the new jobs have been inserted.
+ *
+ * @param $job Job
+ * @return bool
+ */
+ public function deduplicateRootJob( Job $job ) {
+ return $this->get( $job->getType() )->deduplicateRootJob( $job );
+ }
+
+ /**
+ * Wait for any slaves or backup queue servers to catch up.
+ *
+ * This does nothing for certain queue classes.
+ *
+ * @return void
+ * @throws MWException
+ */
+ public function waitForBackups() {
+ global $wgJobTypeConf;
+
+ wfProfileIn( __METHOD__ );
+ // Try to avoid doing this more than once per queue storage medium
+ foreach ( $wgJobTypeConf as $type => $conf ) {
+ $this->get( $type )->waitForBackups();
+ }
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Get the list of queue types
+ *
+ * @return array List of strings
+ */
+ public function getQueueTypes() {
+ return array_keys( $this->getCachedConfigVar( 'wgJobClasses' ) );
+ }
+
+ /**
+ * Get the list of default queue types
+ *
+ * @return array List of strings
+ */
+ public function getDefaultQueueTypes() {
+ global $wgJobTypesExcludedFromDefaultQueue;
+
+ return array_diff( $this->getQueueTypes(), $wgJobTypesExcludedFromDefaultQueue );
+ }
+
+ /**
+ * Get the list of job types that have non-empty queues
+ *
+ * @return Array List of job types that have non-empty queues
+ */
+ public function getQueuesWithJobs() {
+ $types = array();
+ foreach ( $this->getQueueTypes() as $type ) {
+ if ( !$this->get( $type )->isEmpty() ) {
+ $types[] = $type;
+ }
+ }
+ return $types;
+ }
+
+ /**
+ * Check if jobs should not be popped of a queue right now.
+ * This is only used for performance, such as to avoid spamming
+ * the queue with many sub-jobs before they actually get run.
+ *
+ * @param $type string
+ * @return bool
+ */
+ public function isQueueDeprioritized( $type ) {
+ if ( $type === 'refreshLinks2' ) {
+ // Don't keep converting refreshLinks2 => refreshLinks jobs if the
+ // later jobs have not been done yet. This helps throttle queue spam.
+ return !$this->get( 'refreshLinks' )->isEmpty();
+ }
+ return false;
+ }
+
+ /**
+ * Execute any due periodic queue maintenance tasks for all queues.
+ *
+ * A task is "due" if the time ellapsed since the last run is greater than
+ * the defined run period. Concurrent calls to this function will cause tasks
+ * to be attempted twice, so they may need their own methods of mutual exclusion.
+ *
+ * @return integer Number of tasks run
+ */
+ public function executeReadyPeriodicTasks() {
+ global $wgMemc;
+
+ list( $db, $prefix ) = wfSplitWikiID( $this->wiki );
+ $key = wfForeignMemcKey( $db, $prefix, 'jobqueuegroup', 'taskruns', 'v1' );
+ $lastRuns = $wgMemc->get( $key ); // (queue => task => UNIX timestamp)
+
+ $count = 0;
+ $tasksRun = array(); // (queue => task => UNIX timestamp)
+ foreach ( $this->getQueueTypes() as $type ) {
+ $queue = $this->get( $type );
+ foreach ( $queue->getPeriodicTasks() as $task => $definition ) {
+ if ( $definition['period'] <= 0 ) {
+ continue; // disabled
+ } elseif ( !isset( $lastRuns[$type][$task] )
+ || $lastRuns[$type][$task] < ( time() - $definition['period'] ) )
+ {
+ if ( call_user_func( $definition['callback'] ) !== null ) {
+ $tasksRun[$type][$task] = time();
+ ++$count;
+ }
+ }
+ }
+ }
+
+ $wgMemc->merge( $key, function( $cache, $key, $lastRuns ) use ( $tasksRun ) {
+ if ( is_array( $lastRuns ) ) {
+ foreach ( $tasksRun as $type => $tasks ) {
+ foreach ( $tasks as $task => $timestamp ) {
+ if ( !isset( $lastRuns[$type][$task] )
+ || $timestamp > $lastRuns[$type][$task] )
+ {
+ $lastRuns[$type][$task] = $timestamp;
+ }
+ }
+ }
+ } else {
+ $lastRuns = $tasksRun;
+ }
+ return $lastRuns;
+ } );
+
+ return $count;
+ }
+
+ /**
+ * @param $name string
+ * @return mixed
+ */
+ private function getCachedConfigVar( $name ) {
+ global $wgConf, $wgMemc;
+
+ if ( $this->wiki === wfWikiID() ) {
+ return $GLOBALS[$name]; // common case
+ } else {
+ list( $db, $prefix ) = wfSplitWikiID( $this->wiki );
+ $key = wfForeignMemcKey( $db, $prefix, 'configvalue', $name );
+ $value = $wgMemc->get( $key ); // ('v' => ...) or false
+ if ( is_array( $value ) ) {
+ return $value['v'];
+ } else {
+ $value = $wgConf->getConfig( $this->wiki, $name );
+ $wgMemc->set( $key, array( 'v' => $value ), 86400 + mt_rand( 0, 86400 ) );
+ return $value;
+ }
+ }
+ }
+}
diff --git a/includes/job/README b/includes/job/README
new file mode 100644
index 00000000..c11d5a78
--- /dev/null
+++ b/includes/job/README
@@ -0,0 +1,81 @@
+/*!
+\ingroup JobQueue
+\page jobqueue_design Job queue design
+
+Notes on the Job queuing system architecture.
+
+\section intro Introduction
+
+The data model consist of the following main components:
+* The Job object represents a particular deferred task that happens in the
+ background. All jobs subclass the Job object and put the main logic in the
+ function called run().
+* The JobQueue object represents a particular queue of jobs of a certain type.
+ For example there may be a queue for email jobs and a queue for squid purge
+ jobs.
+
+\section jobqueue Job queues
+
+Each job type has its own queue and is associated to a storage medium. One
+queue might save its jobs in redis while another one uses would use a database.
+
+Storage medium are defined in a queue class. Before using it, you must
+define in $wgJobTypeConf a mapping of the job type to a queue class.
+
+The factory class JobQueueGroup provides helper functions:
+- getting the queue for a given job
+- route new job insertions to the proper queue
+
+The following queue classes are available:
+* JobQueueDB (stores jobs in the `job` table in a database)
+* JobQueueRedis (stores jobs in a redis server)
+
+All queue classes support some basic operations (though some may be no-ops):
+* enqueueing a batch of jobs
+* dequeueing a single job
+* acknowledging a job is completed
+* checking if the queue is empty
+
+Some queue classes (like JobQueueDB) may dequeue jobs in random order while other
+queues might dequeue jobs in exact FIFO order. Callers should thus not assume jobs
+are executed in FIFO order.
+
+Also note that not all queue classes will have the same reliability guarantees.
+In-memory queues may lose data when restarted depending on snapshot and journal
+settings (including journal fsync() frequency). Some queue types may totally remove
+jobs when dequeued while leaving the ack() function as a no-op; if a job is
+dequeued by a job runner, which crashes before completion, the job will be
+lost. Some jobs, like purging squid caches after a template change, may not
+require durable queues, whereas other jobs might be more important.
+
+\section aggregator Job queue aggregator
+
+The aggregators are used by nextJobDB.php, which is a script that will return a
+random ready queue (on any wiki in the farm) that can be used with runJobs.php.
+This can be used in conjunction with any scripts that handle wiki farm job queues.
+Note that $wgLocalDatabases defines what wikis are in the wiki farm.
+
+Since each job type has its own queue, and wiki-farms may have many wikis,
+there might be a large number of queues to keep track of. To avoid wasting
+large amounts of time polling empty queues, aggregators exists to keep track
+of which queues are ready.
+
+The following queue aggregator classes are available:
+* JobQueueAggregatorMemc (uses $wgMemc to track ready queues)
+* JobQueueAggregatorRedis (uses a redis server to track ready queues)
+
+Some aggregators cache data for a few minutes while others may be always up to date.
+This can be an important factor for jobs that need a low pickup time (or latency).
+
+\section jobs Jobs
+
+Callers should also try to make jobs maintain correctness when executed twice.
+This is useful for queues that actually implement ack(), since they may recycle
+dequeued but un-acknowledged jobs back into the queue to be attempted again. If
+a runner dequeues a job, runs it, but then crashes before calling ack(), the
+job may be returned to the queue and run a second time. Jobs like cache purging can
+happen several times without any correctness problems. However, a pathological case
+would be if a bug causes the problem to systematically keep repeating. For example,
+a job may always throw a DB error at the end of run(). This problem is trickier to
+solve and more obnoxious for things like email jobs, for example. For such jobs,
+it might be useful to use a queue that does not retry jobs.
diff --git a/includes/job/RefreshLinksJob.php b/includes/job/RefreshLinksJob.php
deleted file mode 100644
index b23951c6..00000000
--- a/includes/job/RefreshLinksJob.php
+++ /dev/null
@@ -1,202 +0,0 @@
-<?php
-/**
- * Job to update links for a given title.
- *
- * 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 JobQueue
- */
-
-/**
- * Background job to update links for a given title.
- *
- * @ingroup JobQueue
- */
-class RefreshLinksJob extends Job {
-
- function __construct( $title, $params = '', $id = 0 ) {
- parent::__construct( 'refreshLinks', $title, $params, $id );
- }
-
- /**
- * Run a refreshLinks job
- * @return boolean success
- */
- function run() {
- wfProfileIn( __METHOD__ );
-
- $linkCache = LinkCache::singleton();
- $linkCache->clear();
-
- if ( is_null( $this->title ) ) {
- $this->error = "refreshLinks: Invalid title";
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- # Wait for the DB of the current/next slave DB handle to catch up to the master.
- # This way, we get the correct page_latest for templates or files that just changed
- # milliseconds ago, having triggered this job to begin with.
- if ( isset( $this->params['masterPos'] ) ) {
- wfGetLB()->waitFor( $this->params['masterPos'] );
- }
-
- $revision = Revision::newFromTitle( $this->title, false, Revision::READ_NORMAL );
- if ( !$revision ) {
- $this->error = 'refreshLinks: Article not found "' .
- $this->title->getPrefixedDBkey() . '"';
- wfProfileOut( __METHOD__ );
- return false; // XXX: what if it was just deleted?
- }
-
- self::runForTitleInternal( $this->title, $revision, __METHOD__ );
-
- wfProfileOut( __METHOD__ );
- return true;
- }
-
- public static function runForTitleInternal( Title $title, Revision $revision, $fname ) {
- global $wgParser, $wgContLang;
-
- wfProfileIn( $fname . '-parse' );
- $options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
- $parserOutput = $wgParser->parse(
- $revision->getText(), $title, $options, true, true, $revision->getId() );
- wfProfileOut( $fname . '-parse' );
-
- wfProfileIn( $fname . '-update' );
- $updates = $parserOutput->getSecondaryDataUpdates( $title, false );
- DataUpdate::runUpdates( $updates );
- wfProfileOut( $fname . '-update' );
- }
-}
-
-/**
- * Background job to update links for a given title.
- * Newer version for high use templates.
- *
- * @ingroup JobQueue
- */
-class RefreshLinksJob2 extends Job {
- const MAX_TITLES_RUN = 10;
-
- function __construct( $title, $params, $id = 0 ) {
- parent::__construct( 'refreshLinks2', $title, $params, $id );
- }
-
- /**
- * Run a refreshLinks2 job
- * @return boolean success
- */
- function run() {
- wfProfileIn( __METHOD__ );
-
- $linkCache = LinkCache::singleton();
- $linkCache->clear();
-
- if ( is_null( $this->title ) ) {
- $this->error = "refreshLinks2: Invalid title";
- wfProfileOut( __METHOD__ );
- return false;
- } elseif ( !isset( $this->params['start'] ) || !isset( $this->params['end'] ) ) {
- $this->error = "refreshLinks2: Invalid params";
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- // Back compat for pre-r94435 jobs
- $table = isset( $this->params['table'] ) ? $this->params['table'] : 'templatelinks';
-
- // Avoid slave lag when fetching templates
- if ( isset( $this->params['masterPos'] ) ) {
- $masterPos = $this->params['masterPos'];
- } elseif ( wfGetLB()->getServerCount() > 1 ) {
- $masterPos = wfGetLB()->getMasterPos();
- } else {
- $masterPos = false;
- }
-
- $titles = $this->title->getBacklinkCache()->getLinks(
- $table, $this->params['start'], $this->params['end'] );
-
- if ( $titles->count() > self::MAX_TITLES_RUN ) {
- # We don't want to parse too many pages per job as it can starve other jobs.
- # If there are too many pages to parse, break this up into smaller jobs. By passing
- # in the master position here we can cut down on the time spent waiting for slaves to
- # catch up by the runners handling these jobs since time will have passed between now
- # and when they pop these jobs off the queue.
- $start = 0; // batch start
- $end = 0; // batch end
- $bsize = 0; // batch size
- $first = true; // first of batch
- $jobs = array();
- foreach ( $titles as $title ) {
- $start = $first ? $title->getArticleId() : $start;
- $end = $title->getArticleId();
- $first = false;
- if ( ++$bsize >= self::MAX_TITLES_RUN ) {
- $jobs[] = new RefreshLinksJob2( $this->title, array(
- 'table' => $table,
- 'start' => $start,
- 'end' => $end,
- 'masterPos' => $masterPos
- ) );
- $first = true;
- $start = $end = $bsize = 0;
- }
- }
- if ( $bsize > 0 ) { // group remaining pages into a job
- $jobs[] = new RefreshLinksJob2( $this->title, array(
- 'table' => $table,
- 'start' => $start,
- 'end' => $end,
- 'masterPos' => $masterPos
- ) );
- }
- Job::batchInsert( $jobs );
- } elseif ( php_sapi_name() != 'cli' ) {
- # Not suitable for page load triggered job running!
- # Gracefully switch to refreshLinks jobs if this happens.
- $jobs = array();
- foreach ( $titles as $title ) {
- $jobs[] = new RefreshLinksJob( $title, array( 'masterPos' => $masterPos ) );
- }
- Job::batchInsert( $jobs );
- } else {
- # Wait for the DB of the current/next slave DB handle to catch up to the master.
- # This way, we get the correct page_latest for templates or files that just changed
- # milliseconds ago, having triggered this job to begin with.
- if ( $masterPos ) {
- wfGetLB()->waitFor( $masterPos );
- }
- # Re-parse each page that transcludes this page and update their tracking links...
- foreach ( $titles as $title ) {
- $revision = Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
- if ( !$revision ) {
- $this->error = 'refreshLinks: Article not found "' .
- $title->getPrefixedDBkey() . '"';
- continue; // skip this page
- }
- RefreshLinksJob::runForTitleInternal( $title, $revision, __METHOD__ );
- wfWaitForSlaves();
- }
- }
-
- wfProfileOut( __METHOD__ );
- return true;
- }
-}
diff --git a/includes/job/jobs/AssembleUploadChunksJob.php b/includes/job/jobs/AssembleUploadChunksJob.php
new file mode 100644
index 00000000..c5dd9eaa
--- /dev/null
+++ b/includes/job/jobs/AssembleUploadChunksJob.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Assemble the segments of a chunked upload.
+ *
+ * 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 Upload
+ */
+
+/**
+ * Assemble the segments of a chunked upload.
+ *
+ * @ingroup Upload
+ */
+class AssembleUploadChunksJob extends Job {
+ public function __construct( $title, $params, $id = 0 ) {
+ parent::__construct( 'AssembleUploadChunks', $title, $params, $id );
+ $this->removeDuplicates = true;
+ }
+
+ public function run() {
+ $scope = RequestContext::importScopedSession( $this->params['session'] );
+ $context = RequestContext::getMain();
+ try {
+ $user = $context->getUser();
+ if ( !$user->isLoggedIn() ) {
+ $this->setLastError( "Could not load the author user from session." );
+ return false;
+ }
+
+ UploadBase::setSessionStatus(
+ $this->params['filekey'],
+ array( 'result' => 'Poll', 'stage' => 'assembling', 'status' => Status::newGood() )
+ );
+
+ $upload = new UploadFromChunks( $user );
+ $upload->continueChunks(
+ $this->params['filename'],
+ $this->params['filekey'],
+ $context->getRequest()
+ );
+
+ // Combine all of the chunks into a local file and upload that to a new stash file
+ $status = $upload->concatenateChunks();
+ if ( !$status->isGood() ) {
+ UploadBase::setSessionStatus(
+ $this->params['filekey'],
+ array( 'result' => 'Failure', 'stage' => 'assembling', 'status' => $status )
+ );
+ $this->setLastError( $status->getWikiText() );
+ return false;
+ }
+
+ // We have a new filekey for the fully concatenated file
+ $newFileKey = $upload->getLocalFile()->getFileKey();
+
+ // Remove the old stash file row and first chunk file
+ $upload->stash->removeFileNoAuth( $this->params['filekey'] );
+
+ // Build the image info array while we have the local reference handy
+ $apiMain = new ApiMain(); // dummy object (XXX)
+ $imageInfo = $upload->getImageInfo( $apiMain->getResult() );
+
+ // Cleanup any temporary local file
+ $upload->cleanupTempFile();
+
+ // Cache the info so the user doesn't have to wait forever to get the final info
+ UploadBase::setSessionStatus(
+ $this->params['filekey'],
+ array(
+ 'result' => 'Success',
+ 'stage' => 'assembling',
+ 'filekey' => $newFileKey,
+ 'imageinfo' => $imageInfo,
+ 'status' => Status::newGood()
+ )
+ );
+ } catch ( MWException $e ) {
+ UploadBase::setSessionStatus(
+ $this->params['filekey'],
+ array(
+ 'result' => 'Failure',
+ 'stage' => 'assembling',
+ 'status' => Status::newFatal( 'api-error-stashfailed' )
+ )
+ );
+ $this->setLastError( get_class( $e ) . ": " . $e->getText() );
+ return false;
+ }
+ return true;
+ }
+
+ public function getDeduplicationInfo() {
+ $info = parent::getDeduplicationInfo();
+ if ( is_array( $info['params'] ) ) {
+ $info['params'] = array( 'filekey' => $info['params']['filekey'] );
+ }
+ return $info;
+ }
+
+ public function allowRetries() {
+ return false;
+ }
+}
diff --git a/includes/job/DoubleRedirectJob.php b/includes/job/jobs/DoubleRedirectJob.php
index 08af9975..05abeeef 100644
--- a/includes/job/DoubleRedirectJob.php
+++ b/includes/job/jobs/DoubleRedirectJob.php
@@ -27,7 +27,7 @@
* @ingroup JobQueue
*/
class DoubleRedirectJob extends Job {
- var $reason, $redirTitle, $destTitleText;
+ var $reason, $redirTitle;
/**
* @var User
@@ -36,9 +36,9 @@ class DoubleRedirectJob extends Job {
/**
* Insert jobs into the job queue to fix redirects to the given title
- * @param $reason String: the reason for the fix, see message "double-redirect-fixed-<reason>"
+ * @param string $reason the reason for the fix, see message "double-redirect-fixed-<reason>"
* @param $redirTitle Title: the title which has changed, redirects pointing to this title are fixed
- * @param $destTitle bool Not used
+ * @param bool $destTitle Not used
*/
public static function fixRedirects( $reason, $redirTitle, $destTitle = false ) {
# Need to use the master to get the redirect table updated in the same transaction
@@ -66,18 +66,17 @@ class DoubleRedirectJob extends Job {
'redirTitle' => $redirTitle->getPrefixedDBkey() ) );
# Avoid excessive memory usage
if ( count( $jobs ) > 10000 ) {
- Job::batchInsert( $jobs );
+ JobQueueGroup::singleton()->push( $jobs );
$jobs = array();
}
}
- Job::batchInsert( $jobs );
+ JobQueueGroup::singleton()->push( $jobs );
}
function __construct( $title, $params = false, $id = 0 ) {
parent::__construct( 'fixDoubleRedirect', $title, $params, $id );
$this->reason = $params['reason'];
$this->redirTitle = Title::newFromText( $params['redirTitle'] );
- $this->destTitleText = !empty( $params['destTitle'] ) ? $params['destTitle'] : '';
}
/**
@@ -94,8 +93,8 @@ class DoubleRedirectJob extends Job {
wfDebug( __METHOD__.": target redirect already deleted, ignoring\n" );
return true;
}
- $text = $targetRev->getText();
- $currentDest = Title::newFromRedirect( $text );
+ $content = $targetRev->getContent();
+ $currentDest = $content ? $content->getRedirectTarget() : null;
if ( !$currentDest || !$currentDest->equals( $this->redirTitle ) ) {
wfDebug( __METHOD__.": Redirect has changed since the job was queued\n" );
return true;
@@ -103,7 +102,7 @@ class DoubleRedirectJob extends Job {
# Check for a suppression tag (used e.g. in periodically archived discussions)
$mw = MagicWord::get( 'staticredirect' );
- if ( $mw->match( $text ) ) {
+ if ( $content->matchMagicWord( $mw ) ) {
wfDebug( __METHOD__.": skipping: suppressed with __STATICREDIRECT__\n" );
return true;
}
@@ -122,29 +121,31 @@ class DoubleRedirectJob extends Job {
# Preserve fragment (bug 14904)
$newTitle = Title::makeTitle( $newTitle->getNamespace(), $newTitle->getDBkey(),
- $currentDest->getFragment() );
+ $currentDest->getFragment(), $newTitle->getInterwiki() );
# Fix the text
- # Remember that redirect pages can have categories, templates, etc.,
- # so the regex has to be fairly general
- $newText = preg_replace( '/ \[ \[ [^\]]* \] \] /x',
- '[[' . $newTitle->getFullText() . ']]',
- $text, 1 );
-
- if ( $newText === $text ) {
- $this->setLastError( 'Text unchanged???' );
+ $newContent = $content->updateRedirect( $newTitle );
+
+ if ( $newContent->equals( $content ) ) {
+ $this->setLastError( 'Content unchanged???' );
+ return false;
+ }
+
+ $user = $this->getUser();
+ if ( !$user ) {
+ $this->setLastError( 'Invalid user' );
return false;
}
# Save it
global $wgUser;
$oldUser = $wgUser;
- $wgUser = $this->getUser();
+ $wgUser = $user;
$article = WikiPage::factory( $this->title );
$reason = wfMessage( 'double-redirect-fixed-' . $this->reason,
$this->redirTitle->getPrefixedText(), $newTitle->getPrefixedText()
)->inContentLanguage()->text();
- $article->doEdit( $newText, $reason, EDIT_UPDATE | EDIT_SUPPRESS_RC, false, $this->getUser() );
+ $article->doEditContent( $newContent, $reason, EDIT_UPDATE | EDIT_SUPPRESS_RC, false, $user );
$wgUser = $oldUser;
return true;
@@ -171,9 +172,17 @@ class DoubleRedirectJob extends Job {
}
$seenTitles[$titleText] = true;
+ if ( $title->getInterwiki() ) {
+ // If the target is interwiki, we have to break early (bug 40352).
+ // Otherwise it will look up a row in the local page table
+ // with the namespace/page of the interwiki target which can cause
+ // unexpected results (e.g. X -> foo:Bar -> Bar -> .. )
+ break;
+ }
+
$row = $dbw->selectRow(
array( 'redirect', 'page' ),
- array( 'rd_namespace', 'rd_title' ),
+ array( 'rd_namespace', 'rd_title', 'rd_interwiki' ),
array(
'rd_from=page_id',
'page_namespace' => $title->getNamespace(),
@@ -183,7 +192,7 @@ class DoubleRedirectJob extends Job {
# No redirect from here, chain terminates
break;
} else {
- $dest = $title = Title::makeTitle( $row->rd_namespace, $row->rd_title );
+ $dest = $title = Title::makeTitle( $row->rd_namespace, $row->rd_title, '', $row->rd_interwiki );
}
}
return $dest;
@@ -191,17 +200,19 @@ class DoubleRedirectJob extends Job {
/**
* Get a user object for doing edits, from a request-lifetime cache
- * @return User
+ * False will be returned if the user name specified in the
+ * 'double-redirect-fixer' message is invalid.
+ *
+ * @return User|bool
*/
function getUser() {
if ( !self::$user ) {
- self::$user = User::newFromName( wfMessage( 'double-redirect-fixer' )->inContentLanguage()->text(), false );
- # FIXME: newFromName could return false on a badly configured wiki.
- if ( !self::$user->isLoggedIn() ) {
+ self::$user = User::newFromName( wfMessage( 'double-redirect-fixer' )->inContentLanguage()->text() );
+ # User::newFromName() can return false on a badly configured wiki.
+ if ( self::$user && !self::$user->isLoggedIn() ) {
self::$user->addToDatabase();
}
}
return self::$user;
}
}
-
diff --git a/includes/job/jobs/DuplicateJob.php b/includes/job/jobs/DuplicateJob.php
new file mode 100644
index 00000000..524983b8
--- /dev/null
+++ b/includes/job/jobs/DuplicateJob.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * No-op job that does nothing.
+ *
+ * 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 Cache
+ */
+
+/**
+ * No-op job that does nothing. Used to represent duplicates.
+ *
+ * @ingroup JobQueue
+ */
+final class DuplicateJob extends Job {
+ /**
+ * Callers should use DuplicateJob::newFromJob() instead
+ *
+ * @param $title Title
+ * @param array $params job parameters
+ * @param $id Integer: job id
+ */
+ function __construct( $title, $params, $id = 0 ) {
+ parent::__construct( 'duplicate', $title, $params, $id );
+ }
+
+ /**
+ * Get a duplicate no-op version of a job
+ *
+ * @param Job $job
+ * @return Job
+ */
+ public static function newFromJob( Job $job ) {
+ $djob = new self( $job->getTitle(), $job->getParams(), $job->getId() );
+ $djob->command = $job->getType();
+ $djob->params = is_array( $djob->params ) ? $djob->params : array();
+ $djob->params = array( 'isDuplicate' => true ) + $djob->params;
+ $djob->metadata = $job->metadata;
+ return $djob;
+ }
+
+ public function run() {
+ return true;
+ }
+}
diff --git a/includes/job/EmaillingJob.php b/includes/job/jobs/EmaillingJob.php
index d3599882..9fbf3124 100644
--- a/includes/job/EmaillingJob.php
+++ b/includes/job/jobs/EmaillingJob.php
@@ -33,14 +33,15 @@ class EmaillingJob extends Job {
}
function run() {
- UserMailer::send(
+ $status = UserMailer::send(
$this->params['to'],
$this->params['from'],
$this->params['subj'],
$this->params['body'],
$this->params['replyto']
);
- return true;
+
+ return $status->isOK();
}
}
diff --git a/includes/job/EnotifNotifyJob.php b/includes/job/jobs/EnotifNotifyJob.php
index b4c925e9..2be05b63 100644
--- a/includes/job/EnotifNotifyJob.php
+++ b/includes/job/jobs/EnotifNotifyJob.php
@@ -49,7 +49,8 @@ class EnotifNotifyJob extends Job {
$this->params['summary'],
$this->params['minorEdit'],
$this->params['oldid'],
- $this->params['watchers']
+ $this->params['watchers'],
+ $this->params['pageStatus']
);
return true;
}
diff --git a/includes/job/jobs/HTMLCacheUpdateJob.php b/includes/job/jobs/HTMLCacheUpdateJob.php
new file mode 100644
index 00000000..818c6abf
--- /dev/null
+++ b/includes/job/jobs/HTMLCacheUpdateJob.php
@@ -0,0 +1,254 @@
+<?php
+/**
+ * HTML cache invalidation of all pages linking to a given title.
+ *
+ * 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 Cache
+ */
+
+/**
+ * Job wrapper for HTMLCacheUpdate. Gets run whenever a related
+ * job gets called from the queue.
+ *
+ * This class is designed to work efficiently with small numbers of links, and
+ * to work reasonably well with up to ~10^5 links. Above ~10^6 links, the memory
+ * and time requirements of loading all backlinked IDs in doUpdate() might become
+ * prohibitive. The requirements measured at Wikimedia are approximately:
+ *
+ * memory: 48 bytes per row
+ * time: 16us per row for the query plus processing
+ *
+ * The reason this query is done is to support partitioning of the job
+ * by backlinked ID. The memory issue could be allieviated by doing this query in
+ * batches, but of course LIMIT with an offset is inefficient on the DB side.
+ *
+ * The class is nevertheless a vast improvement on the previous method of using
+ * File::getLinksTo() and Title::touchArray(), which uses about 2KB of memory per
+ * link.
+ *
+ * @ingroup JobQueue
+ */
+class HTMLCacheUpdateJob extends Job {
+ /** @var BacklinkCache */
+ protected $blCache;
+
+ protected $rowsPerJob, $rowsPerQuery;
+
+ /**
+ * Construct a job
+ * @param $title Title: the title linked to
+ * @param array $params job parameters (table, start and end page_ids)
+ * @param $id Integer: job id
+ */
+ function __construct( $title, $params, $id = 0 ) {
+ global $wgUpdateRowsPerJob, $wgUpdateRowsPerQuery;
+
+ parent::__construct( 'htmlCacheUpdate', $title, $params, $id );
+
+ $this->rowsPerJob = $wgUpdateRowsPerJob;
+ $this->rowsPerQuery = $wgUpdateRowsPerQuery;
+ $this->blCache = $title->getBacklinkCache();
+ }
+
+ public function run() {
+ if ( isset( $this->params['start'] ) && isset( $this->params['end'] ) ) {
+ # This is hit when a job is actually performed
+ return $this->doPartialUpdate();
+ } else {
+ # This is hit when the jobs have to be inserted
+ return $this->doFullUpdate();
+ }
+ }
+
+ /**
+ * Update all of the backlinks
+ */
+ protected function doFullUpdate() {
+ # Get an estimate of the number of rows from the BacklinkCache
+ $numRows = $this->blCache->getNumLinks( $this->params['table'] );
+ if ( $numRows > $this->rowsPerJob * 2 ) {
+ # Do fast cached partition
+ $this->insertPartitionJobs();
+ } else {
+ # Get the links from the DB
+ $titleArray = $this->blCache->getLinks( $this->params['table'] );
+ # Check if the row count estimate was correct
+ if ( $titleArray->count() > $this->rowsPerJob * 2 ) {
+ # Not correct, do accurate partition
+ wfDebug( __METHOD__.": row count estimate was incorrect, repartitioning\n" );
+ $this->insertJobsFromTitles( $titleArray );
+ } else {
+ $this->invalidateTitles( $titleArray ); // just do the query
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Update some of the backlinks, defined by a page ID range
+ */
+ protected function doPartialUpdate() {
+ $titleArray = $this->blCache->getLinks(
+ $this->params['table'], $this->params['start'], $this->params['end'] );
+ if ( $titleArray->count() <= $this->rowsPerJob * 2 ) {
+ # This partition is small enough, do the update
+ $this->invalidateTitles( $titleArray );
+ } else {
+ # Partitioning was excessively inaccurate. Divide the job further.
+ # This can occur when a large number of links are added in a short
+ # period of time, say by updating a heavily-used template.
+ $this->insertJobsFromTitles( $titleArray );
+ }
+ return true;
+ }
+
+ /**
+ * Partition the current range given by $this->params['start'] and $this->params['end'],
+ * using a pre-calculated title array which gives the links in that range.
+ * Queue the resulting jobs.
+ *
+ * @param $titleArray array
+ * @param $rootJobParams array
+ * @return void
+ */
+ protected function insertJobsFromTitles( $titleArray, $rootJobParams = array() ) {
+ // Carry over any "root job" information
+ $rootJobParams = $this->getRootJobParams();
+ # We make subpartitions in the sense that the start of the first job
+ # will be the start of the parent partition, and the end of the last
+ # job will be the end of the parent partition.
+ $jobs = array();
+ $start = $this->params['start']; # start of the current job
+ $numTitles = 0;
+ foreach ( $titleArray as $title ) {
+ $id = $title->getArticleID();
+ # $numTitles is now the number of titles in the current job not
+ # including the current ID
+ if ( $numTitles >= $this->rowsPerJob ) {
+ # Add a job up to but not including the current ID
+ $jobs[] = new HTMLCacheUpdateJob( $this->title,
+ array(
+ 'table' => $this->params['table'],
+ 'start' => $start,
+ 'end' => $id - 1
+ ) + $rootJobParams // carry over information for de-duplication
+ );
+ $start = $id;
+ $numTitles = 0;
+ }
+ $numTitles++;
+ }
+ # Last job
+ $jobs[] = new HTMLCacheUpdateJob( $this->title,
+ array(
+ 'table' => $this->params['table'],
+ 'start' => $start,
+ 'end' => $this->params['end']
+ ) + $rootJobParams // carry over information for de-duplication
+ );
+ wfDebug( __METHOD__.": repartitioning into " . count( $jobs ) . " jobs\n" );
+
+ if ( count( $jobs ) < 2 ) {
+ # I don't think this is possible at present, but handling this case
+ # makes the code a bit more robust against future code updates and
+ # avoids a potential infinite loop of repartitioning
+ wfDebug( __METHOD__.": repartitioning failed!\n" );
+ $this->invalidateTitles( $titleArray );
+ } else {
+ JobQueueGroup::singleton()->push( $jobs );
+ }
+ }
+
+ /**
+ * @param $rootJobParams array
+ * @return void
+ */
+ protected function insertPartitionJobs( $rootJobParams = array() ) {
+ // Carry over any "root job" information
+ $rootJobParams = $this->getRootJobParams();
+
+ $batches = $this->blCache->partition( $this->params['table'], $this->rowsPerJob );
+ if ( !count( $batches ) ) {
+ return; // no jobs to insert
+ }
+
+ $jobs = array();
+ foreach ( $batches as $batch ) {
+ list( $start, $end ) = $batch;
+ $jobs[] = new HTMLCacheUpdateJob( $this->title,
+ array(
+ 'table' => $this->params['table'],
+ 'start' => $start,
+ 'end' => $end,
+ ) + $rootJobParams // carry over information for de-duplication
+ );
+ }
+
+ JobQueueGroup::singleton()->push( $jobs );
+ }
+
+ /**
+ * Invalidate an array (or iterator) of Title objects, right now
+ * @param $titleArray array
+ */
+ protected function invalidateTitles( $titleArray ) {
+ global $wgUseFileCache, $wgUseSquid;
+
+ $dbw = wfGetDB( DB_MASTER );
+ $timestamp = $dbw->timestamp();
+
+ # Get all IDs in this query into an array
+ $ids = array();
+ foreach ( $titleArray as $title ) {
+ $ids[] = $title->getArticleID();
+ }
+
+ if ( !$ids ) {
+ return;
+ }
+
+ # Don't invalidated pages that were already invalidated
+ $touchedCond = isset( $this->params['rootJobTimestamp'] )
+ ? array( "page_touched < " .
+ $dbw->addQuotes( $dbw->timestamp( $this->params['rootJobTimestamp'] ) ) )
+ : array();
+
+ # Update page_touched
+ $batches = array_chunk( $ids, $this->rowsPerQuery );
+ foreach ( $batches as $batch ) {
+ $dbw->update( 'page',
+ array( 'page_touched' => $timestamp ),
+ array( 'page_id' => $batch ) + $touchedCond,
+ __METHOD__
+ );
+ }
+
+ # Update squid
+ if ( $wgUseSquid ) {
+ $u = SquidUpdate::newFromTitles( $titleArray );
+ $u->doUpdate();
+ }
+
+ # Update file cache
+ if ( $wgUseFileCache ) {
+ foreach ( $titleArray as $title ) {
+ HTMLFileCache::clearFileCache( $title );
+ }
+ }
+ }
+}
diff --git a/includes/job/jobs/NullJob.php b/includes/job/jobs/NullJob.php
new file mode 100644
index 00000000..d282a8e6
--- /dev/null
+++ b/includes/job/jobs/NullJob.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Degenerate job that does nothing.
+ *
+ * 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 Cache
+ */
+
+/**
+ * Degenerate job that does nothing, but can optionally replace itself
+ * in the queue and/or sleep for a brief time period. These can be used
+ * to represent "no-op" jobs or test lock contention and performance.
+ *
+ * @ingroup JobQueue
+ */
+class NullJob extends Job {
+ /**
+ * @param $title Title (can be anything)
+ * @param array $params job parameters (lives, usleep)
+ * @param $id Integer: job id
+ */
+ function __construct( $title, $params, $id = 0 ) {
+ parent::__construct( 'null', $title, $params, $id );
+ if ( !isset( $this->params['lives'] ) ) {
+ $this->params['lives'] = 1;
+ }
+ if ( !isset( $this->params['usleep'] ) ) {
+ $this->params['usleep'] = 0;
+ }
+ $this->removeDuplicates = !empty( $this->params['removeDuplicates'] );
+ }
+
+ public function run() {
+ if ( $this->params['usleep'] > 0 ) {
+ usleep( $this->params['usleep'] );
+ }
+ if ( $this->params['lives'] > 1 ) {
+ $params = $this->params;
+ $params['lives']--;
+ $job = new self( $this->title, $params );
+ JobQueueGroup::singleton()->push( $job );
+ }
+ return true;
+ }
+}
diff --git a/includes/job/jobs/PublishStashedFileJob.php b/includes/job/jobs/PublishStashedFileJob.php
new file mode 100644
index 00000000..d3feda28
--- /dev/null
+++ b/includes/job/jobs/PublishStashedFileJob.php
@@ -0,0 +1,130 @@
+<?php
+/**
+ * Upload a file from the upload stash into the local file repo.
+ *
+ * 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 Upload
+ */
+
+/**
+ * Upload a file from the upload stash into the local file repo.
+ *
+ * @ingroup Upload
+ */
+class PublishStashedFileJob extends Job {
+ public function __construct( $title, $params, $id = 0 ) {
+ parent::__construct( 'PublishStashedFile', $title, $params, $id );
+ $this->removeDuplicates = true;
+ }
+
+ public function run() {
+ $scope = RequestContext::importScopedSession( $this->params['session'] );
+ $context = RequestContext::getMain();
+ try {
+ $user = $context->getUser();
+ if ( !$user->isLoggedIn() ) {
+ $this->setLastError( "Could not load the author user from session." );
+ return false;
+ }
+
+ UploadBase::setSessionStatus(
+ $this->params['filekey'],
+ array( 'result' => 'Poll', 'stage' => 'publish', 'status' => Status::newGood() )
+ );
+
+ $upload = new UploadFromStash( $user );
+ // @TODO: initialize() causes a GET, ideally we could frontload the antivirus
+ // checks and anything else to the stash stage (which includes concatenation and
+ // the local file is thus already there). That way, instead of GET+PUT, there could
+ // just be a COPY operation from the stash to the public zone.
+ $upload->initialize( $this->params['filekey'], $this->params['filename'] );
+
+ // Check if the local file checks out (this is generally a no-op)
+ $verification = $upload->verifyUpload();
+ if ( $verification['status'] !== UploadBase::OK ) {
+ $status = Status::newFatal( 'verification-error' );
+ $status->value = array( 'verification' => $verification );
+ UploadBase::setSessionStatus(
+ $this->params['filekey'],
+ array( 'result' => 'Failure', 'stage' => 'publish', 'status' => $status )
+ );
+ $this->setLastError( "Could not verify upload." );
+ return false;
+ }
+
+ // Upload the stashed file to a permanent location
+ $status = $upload->performUpload(
+ $this->params['comment'],
+ $this->params['text'],
+ $this->params['watch'],
+ $user
+ );
+ if ( !$status->isGood() ) {
+ UploadBase::setSessionStatus(
+ $this->params['filekey'],
+ array( 'result' => 'Failure', 'stage' => 'publish', 'status' => $status )
+ );
+ $this->setLastError( $status->getWikiText() );
+ return false;
+ }
+
+ // Build the image info array while we have the local reference handy
+ $apiMain = new ApiMain(); // dummy object (XXX)
+ $imageInfo = $upload->getImageInfo( $apiMain->getResult() );
+
+ // Cleanup any temporary local file
+ $upload->cleanupTempFile();
+
+ // Cache the info so the user doesn't have to wait forever to get the final info
+ UploadBase::setSessionStatus(
+ $this->params['filekey'],
+ array(
+ 'result' => 'Success',
+ 'stage' => 'publish',
+ 'filename' => $upload->getLocalFile()->getName(),
+ 'imageinfo' => $imageInfo,
+ 'status' => Status::newGood()
+ )
+ );
+ } catch ( MWException $e ) {
+ UploadBase::setSessionStatus(
+ $this->params['filekey'],
+ array(
+ 'result' => 'Failure',
+ 'stage' => 'publish',
+ 'status' => Status::newFatal( 'api-error-publishfailed' )
+ )
+ );
+ $this->setLastError( get_class( $e ) . ": " . $e->getText() );
+ return false;
+ }
+ return true;
+ }
+
+ public function getDeduplicationInfo() {
+ $info = parent::getDeduplicationInfo();
+ if ( is_array( $info['params'] ) ) {
+ $info['params'] = array( 'filekey' => $info['params']['filekey'] );
+ }
+ return $info;
+ }
+
+ public function allowRetries() {
+ return false;
+ }
+}
diff --git a/includes/job/jobs/RefreshLinksJob.php b/includes/job/jobs/RefreshLinksJob.php
new file mode 100644
index 00000000..9dbe8278
--- /dev/null
+++ b/includes/job/jobs/RefreshLinksJob.php
@@ -0,0 +1,226 @@
+<?php
+/**
+ * Job to update links for a given title.
+ *
+ * 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 JobQueue
+ */
+
+/**
+ * Background job to update links for a given title.
+ *
+ * @ingroup JobQueue
+ */
+class RefreshLinksJob extends Job {
+ function __construct( $title, $params = '', $id = 0 ) {
+ parent::__construct( 'refreshLinks', $title, $params, $id );
+ $this->removeDuplicates = true; // job is expensive
+ }
+
+ /**
+ * Run a refreshLinks job
+ * @return boolean success
+ */
+ function run() {
+ wfProfileIn( __METHOD__ );
+
+ $linkCache = LinkCache::singleton();
+ $linkCache->clear();
+
+ if ( is_null( $this->title ) ) {
+ $this->error = "refreshLinks: Invalid title";
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ # Wait for the DB of the current/next slave DB handle to catch up to the master.
+ # This way, we get the correct page_latest for templates or files that just changed
+ # milliseconds ago, having triggered this job to begin with.
+ if ( isset( $this->params['masterPos'] ) ) {
+ wfGetLB()->waitFor( $this->params['masterPos'] );
+ }
+
+ $revision = Revision::newFromTitle( $this->title, false, Revision::READ_NORMAL );
+ if ( !$revision ) {
+ $this->error = 'refreshLinks: Article not found "' .
+ $this->title->getPrefixedDBkey() . '"';
+ wfProfileOut( __METHOD__ );
+ return false; // XXX: what if it was just deleted?
+ }
+
+ self::runForTitleInternal( $this->title, $revision, __METHOD__ );
+
+ wfProfileOut( __METHOD__ );
+ return true;
+ }
+
+ /**
+ * @return Array
+ */
+ public function getDeduplicationInfo() {
+ $info = parent::getDeduplicationInfo();
+ // Don't let highly unique "masterPos" values ruin duplicate detection
+ if ( is_array( $info['params'] ) ) {
+ unset( $info['params']['masterPos'] );
+ }
+ return $info;
+ }
+
+ /**
+ * @param $title Title
+ * @param $revision Revision
+ * @param $fname string
+ * @return void
+ */
+ public static function runForTitleInternal( Title $title, Revision $revision, $fname ) {
+ wfProfileIn( $fname );
+ $content = $revision->getContent( Revision::RAW );
+
+ if ( !$content ) {
+ // if there is no content, pretend the content is empty
+ $content = $revision->getContentHandler()->makeEmptyContent();
+ }
+
+ // Revision ID must be passed to the parser output to get revision variables correct
+ $parserOutput = $content->getParserOutput( $title, $revision->getId(), null, false );
+
+ $updates = $content->getSecondaryDataUpdates( $title, null, false, $parserOutput );
+ DataUpdate::runUpdates( $updates );
+ wfProfileOut( $fname );
+ }
+}
+
+/**
+ * Background job to update links for a given title.
+ * Newer version for high use templates.
+ *
+ * @ingroup JobQueue
+ */
+class RefreshLinksJob2 extends Job {
+ function __construct( $title, $params, $id = 0 ) {
+ parent::__construct( 'refreshLinks2', $title, $params, $id );
+ }
+
+ /**
+ * Run a refreshLinks2 job
+ * @return boolean success
+ */
+ function run() {
+ global $wgUpdateRowsPerJob;
+
+ wfProfileIn( __METHOD__ );
+
+ $linkCache = LinkCache::singleton();
+ $linkCache->clear();
+
+ if ( is_null( $this->title ) ) {
+ $this->error = "refreshLinks2: Invalid title";
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ // Back compat for pre-r94435 jobs
+ $table = isset( $this->params['table'] ) ? $this->params['table'] : 'templatelinks';
+
+ // Avoid slave lag when fetching templates.
+ // When the outermost job is run, we know that the caller that enqueued it must have
+ // committed the relevant changes to the DB by now. At that point, record the master
+ // position and pass it along as the job recursively breaks into smaller range jobs.
+ // Hopefully, when leaf jobs are popped, the slaves will have reached that position.
+ if ( isset( $this->params['masterPos'] ) ) {
+ $masterPos = $this->params['masterPos'];
+ } elseif ( wfGetLB()->getServerCount() > 1 ) {
+ $masterPos = wfGetLB()->getMasterPos();
+ } else {
+ $masterPos = false;
+ }
+
+ $tbc = $this->title->getBacklinkCache();
+
+ $jobs = array(); // jobs to insert
+ if ( isset( $this->params['start'] ) && isset( $this->params['end'] ) ) {
+ # This is a partition job to trigger the insertion of leaf jobs...
+ $jobs = array_merge( $jobs, $this->getSingleTitleJobs( $table, $masterPos ) );
+ } else {
+ # This is a base job to trigger the insertion of partitioned jobs...
+ if ( $tbc->getNumLinks( $table ) <= $wgUpdateRowsPerJob ) {
+ # Just directly insert the single per-title jobs
+ $jobs = array_merge( $jobs, $this->getSingleTitleJobs( $table, $masterPos ) );
+ } else {
+ # Insert the partition jobs to make per-title jobs
+ foreach ( $tbc->partition( $table, $wgUpdateRowsPerJob ) as $batch ) {
+ list( $start, $end ) = $batch;
+ $jobs[] = new RefreshLinksJob2( $this->title,
+ array(
+ 'table' => $table,
+ 'start' => $start,
+ 'end' => $end,
+ 'masterPos' => $masterPos,
+ ) + $this->getRootJobParams() // carry over information for de-duplication
+ );
+ }
+ }
+ }
+
+ if ( count( $jobs ) ) {
+ JobQueueGroup::singleton()->push( $jobs );
+ }
+
+ wfProfileOut( __METHOD__ );
+ return true;
+ }
+
+ /**
+ * @param $table string
+ * @param $masterPos mixed
+ * @return Array
+ */
+ protected function getSingleTitleJobs( $table, $masterPos ) {
+ # The "start"/"end" fields are not set for the base jobs
+ $start = isset( $this->params['start'] ) ? $this->params['start'] : false;
+ $end = isset( $this->params['end'] ) ? $this->params['end'] : false;
+ $titles = $this->title->getBacklinkCache()->getLinks( $table, $start, $end );
+ # Convert into single page refresh links jobs.
+ # This handles well when in sapi mode and is useful in any case for job
+ # de-duplication. If many pages use template A, and that template itself
+ # uses template B, then an edit to both will create many duplicate jobs.
+ # Roughly speaking, for each page, one of the "RefreshLinksJob" jobs will
+ # get run first, and when it does, it will remove the duplicates. Of course,
+ # one page could have its job popped when the other page's job is still
+ # buried within the logic of a refreshLinks2 job.
+ $jobs = array();
+ foreach ( $titles as $title ) {
+ $jobs[] = new RefreshLinksJob( $title,
+ array( 'masterPos' => $masterPos ) + $this->getRootJobParams()
+ ); // carry over information for de-duplication
+ }
+ return $jobs;
+ }
+
+ /**
+ * @return Array
+ */
+ public function getDeduplicationInfo() {
+ $info = parent::getDeduplicationInfo();
+ // Don't let highly unique "masterPos" values ruin duplicate detection
+ if ( is_array( $info['params'] ) ) {
+ unset( $info['params']['masterPos'] );
+ }
+ return $info;
+ }
+}
diff --git a/includes/job/UploadFromUrlJob.php b/includes/job/jobs/UploadFromUrlJob.php
index e06f68e4..87549140 100644
--- a/includes/job/UploadFromUrlJob.php
+++ b/includes/job/jobs/UploadFromUrlJob.php
@@ -148,8 +148,8 @@ class UploadFromUrlJob extends Job {
* Store a result in the session data. Note that the caller is responsible
* for appropriate session_start and session_write_close calls.
*
- * @param $result String: the result (Success|Warning|Failure)
- * @param $dataKey String: the key of the extra data
+ * @param string $result the result (Success|Warning|Failure)
+ * @param string $dataKey the key of the extra data
* @param $dataValue Mixed: the extra data itself
*/
protected function storeResultInSession( $result, $dataKey, $dataValue ) {
diff --git a/includes/json/FormatJson.php b/includes/json/FormatJson.php
index f67700c9..eececcba 100644
--- a/includes/json/FormatJson.php
+++ b/includes/json/FormatJson.php
@@ -1,6 +1,6 @@
<?php
/**
- * Simple wrapper for json_econde and json_decode that falls back on Services_JSON class.
+ * Simple wrapper for json_encode and json_decode that falls back on Services_JSON class.
*
* 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
@@ -31,28 +31,23 @@ class FormatJson {
* Returns the JSON representation of a value.
*
* @param $value Mixed: the value being encoded. Can be any type except a resource.
- * @param $isHtml Boolean
- *
- * @todo FIXME: "$isHtml" parameter's purpose is not documented. It appears to
- * map to a parameter labeled "pretty-print output with indents and
- * newlines" in Services_JSON::encode(), which has no string relation
- * to HTML output.
+ * @param $pretty Boolean: If true, adds non-significant whitespace to improve readability.
*
* @return string
*/
- public static function encode( $value, $isHtml = false ) {
- if ( !function_exists( 'json_encode' ) || ( $isHtml && version_compare( PHP_VERSION, '5.4.0', '<' ) ) ) {
+ public static function encode( $value, $pretty = false ) {
+ if ( !function_exists( 'json_encode' ) || ( $pretty && version_compare( PHP_VERSION, '5.4.0', '<' ) ) ) {
$json = new Services_JSON();
- return $json->encode( $value, $isHtml );
+ return $json->encode( $value, $pretty );
} else {
- return json_encode( $value, $isHtml ? JSON_PRETTY_PRINT : 0 );
+ return json_encode( $value, $pretty ? JSON_PRETTY_PRINT : 0 );
}
}
/**
* Decodes a JSON string.
*
- * @param $value String: the json string being decoded.
+ * @param string $value the json string being decoded.
* @param $assoc Boolean: when true, returned objects will be converted into associative arrays.
*
* @return Mixed: the value encoded in json in appropriate PHP type.
diff --git a/includes/json/Services_JSON.php b/includes/json/Services_JSON.php
index 398ed6a2..b7c101a1 100644
--- a/includes/json/Services_JSON.php
+++ b/includes/json/Services_JSON.php
@@ -100,7 +100,7 @@ define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
* // create a new instance of Services_JSON
* $json = new Services_JSON();
*
- * // convert a complexe value to JSON notation, and send it to the browser
+ * // convert a complex value to JSON notation, and send it to the browser
* $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
* $output = $json->encode($value);
*
@@ -155,9 +155,9 @@ class Services_JSON
*
* Normally should be handled by mb_convert_encoding, but
* provides a slower PHP-only method for installations
- * that lack the multibye string extension.
+ * that lack the multibyte string extension.
*
- * @param $utf16 String: UTF-16 character
+ * @param string $utf16 UTF-16 character
* @return String: UTF-8 character
* @access private
*/
@@ -210,9 +210,9 @@ class Services_JSON
*
* Normally should be handled by mb_convert_encoding, but
* provides a slower PHP-only method for installations
- * that lack the multibye string extension.
+ * that lack the multibyte string extension.
*
- * @param $utf8 String: UTF-8 character
+ * @param string $utf8 UTF-8 character
* @return String: UTF-16 character
* @access private
*/
@@ -268,7 +268,7 @@ class Services_JSON
*
* @param $var Mixed: any number, boolean, string, array, or object to be encoded.
* see argument 1 to Services_JSON() above for array-parsing behavior.
- * if var is a strng, note that encode() always expects it
+ * if var is a string, note that encode() always expects it
* to be in ASCII or UTF-8 format!
* @param $pretty Boolean: pretty-print output with indents and newlines
*
@@ -283,12 +283,12 @@ class Services_JSON
return $this->encode2($var);
}
- /**
+ /**
* encodes an arbitrary variable into JSON format
*
* @param $var Mixed: any number, boolean, string, array, or object to be encoded.
* see argument 1 to Services_JSON() above for array-parsing behavior.
- * if var is a strng, note that encode() always expects it
+ * if var is a string, note that encode() always expects it
* to be in ASCII or UTF-8 format!
*
* @return mixed JSON string representation of input var or an error if a problem occurs
@@ -424,7 +424,7 @@ class Services_JSON
*/
// treat as a JSON object
- if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
+ if (is_array($var) && count($var) && (array_keys($var) !== range(0, count($var) - 1))) {
$this->indent++;
$properties = array_map(array($this, 'name_value'),
array_keys($var),
@@ -480,7 +480,7 @@ class Services_JSON
/**
* array-walking function for use in generating JSON-formatted name-value pairs
*
- * @param $name String: name of key to use
+ * @param string $name name of key to use
* @param $value Mixed: reference to an array element to be encoded
*
* @return String: JSON-formatted name-value pair, like '"name":value'
@@ -500,7 +500,7 @@ class Services_JSON
/**
* reduce a string by removing leading and trailing comments and whitespace
*
- * @param $str String: string value to strip of comments and whitespace
+ * @param string $str string value to strip of comments and whitespace
*
* @return String: string value stripped of comments and whitespace
* @access private
@@ -527,7 +527,7 @@ class Services_JSON
/**
* decodes a JSON string into appropriate variable
*
- * @param $str String: JSON-formatted string
+ * @param string $str JSON-formatted string
*
* @return mixed number, boolean, string, array, or object
* corresponding to given JSON input string.
diff --git a/includes/libs/CSSJanus.php b/includes/libs/CSSJanus.php
index 4ebbc497..fb1e9a4c 100644
--- a/includes/libs/CSSJanus.php
+++ b/includes/libs/CSSJanus.php
@@ -57,6 +57,7 @@ class CSSJanus {
'lookahead_not_open_brace' => null,
'lookahead_not_closing_paren' => null,
'lookahead_for_closing_paren' => null,
+ 'lookahead_not_letter' => '(?![a-zA-Z])',
'lookbehind_not_letter' => '(?<![a-zA-Z])',
'chars_within_selector' => '[^\}]*?',
'noflip_annotation' => '\/\*\s*@noflip\s*\*\/',
@@ -104,8 +105,8 @@ class CSSJanus {
$patterns['noflip_class'] = "/({$patterns['noflip_annotation']}{$patterns['chars_within_selector']}})/i";
$patterns['direction_ltr'] = "/({$patterns['direction']})ltr/i";
$patterns['direction_rtl'] = "/({$patterns['direction']})rtl/i";
- $patterns['left'] = "/{$patterns['lookbehind_not_letter']}(left){$patterns['lookahead_not_closing_paren']}{$patterns['lookahead_not_open_brace']}/i";
- $patterns['right'] = "/{$patterns['lookbehind_not_letter']}(right){$patterns['lookahead_not_closing_paren']}{$patterns['lookahead_not_open_brace']}/i";
+ $patterns['left'] = "/{$patterns['lookbehind_not_letter']}(left){$patterns['lookahead_not_letter']}{$patterns['lookahead_not_closing_paren']}{$patterns['lookahead_not_open_brace']}/i";
+ $patterns['right'] = "/{$patterns['lookbehind_not_letter']}(right){$patterns['lookahead_not_letter']}{$patterns['lookahead_not_closing_paren']}{$patterns['lookahead_not_open_brace']}/i";
$patterns['left_in_url'] = "/{$patterns['lookbehind_not_letter']}(left){$patterns['lookahead_for_closing_paren']}/i";
$patterns['right_in_url'] = "/{$patterns['lookbehind_not_letter']}(right){$patterns['lookahead_for_closing_paren']}/i";
$patterns['ltr_in_url'] = "/{$patterns['lookbehind_not_letter']}(ltr){$patterns['lookahead_for_closing_paren']}/i";
@@ -122,7 +123,7 @@ class CSSJanus {
/**
* Transform an LTR stylesheet to RTL
- * @param $css String: stylesheet to transform
+ * @param string $css stylesheet to transform
* @param $swapLtrRtlInURL Boolean: If true, swap 'ltr' and 'rtl' in URLs
* @param $swapLeftRightInURL Boolean: If true, swap 'left' and 'right' in URLs
* @return string Transformed stylesheet
@@ -304,8 +305,8 @@ class CSSJanus_Tokenizer {
/**
* Constructor
- * @param $regex string Regular expression whose matches to replace by a token.
- * @param $token string Token
+ * @param string $regex Regular expression whose matches to replace by a token.
+ * @param string $token Token
*/
public function __construct( $regex, $token ) {
$this->regex = $regex;
@@ -316,7 +317,7 @@ class CSSJanus_Tokenizer {
/**
* Replace all occurrences of $regex in $str with a token and remember
* the original strings.
- * @param $str String to tokenize
+ * @param string $str to tokenize
* @return string Tokenized string
*/
public function tokenize( $str ) {
@@ -335,7 +336,7 @@ class CSSJanus_Tokenizer {
/**
* Replace tokens with their originals. If multiple strings were tokenized, it's important they be
* detokenized in exactly the SAME ORDER.
- * @param $str String: previously run through tokenize()
+ * @param string $str previously run through tokenize()
* @return string Original string
*/
public function detokenize( $str ) {
diff --git a/includes/libs/CSSMin.php b/includes/libs/CSSMin.php
index fc75cdcc..8b0e2873 100644
--- a/includes/libs/CSSMin.php
+++ b/includes/libs/CSSMin.php
@@ -59,8 +59,8 @@ class CSSMin {
/**
* Gets a list of local file paths which are referenced in a CSS style sheet
*
- * @param $source string CSS data to remap
- * @param $path string File path where the source was read from (optional)
+ * @param string $source CSS data to remap
+ * @param string $path File path where the source was read from (optional)
* @return array List of local file references
*/
public static function getLocalFileReferences( $source, $path = null ) {
@@ -115,10 +115,10 @@ class CSSMin {
* Remaps CSS URL paths and automatically embeds data URIs for URL rules
* preceded by an /* @embed * / comment
*
- * @param $source string CSS data to remap
- * @param $local string File path where the source was read from
- * @param $remote string URL path to the file
- * @param $embedData bool If false, never do any data URI embedding, even if / * @embed * / is found
+ * @param string $source CSS data to remap
+ * @param string $local File path where the source was read from
+ * @param string $remote URL path to the file
+ * @param bool $embedData If false, never do any data URI embedding, even if / * @embed * / is found
* @return string Remapped CSS data
*/
public static function remap( $source, $local, $remote, $embedData = true ) {
@@ -219,7 +219,7 @@ class CSSMin {
/**
* Removes whitespace from CSS data
*
- * @param $css string CSS data to minify
+ * @param string $css CSS data to minify
* @return string Minified CSS data
*/
public static function minify( $css ) {
diff --git a/includes/libs/GenericArrayObject.php b/includes/libs/GenericArrayObject.php
index b4b9d610..d77d8ad6 100644
--- a/includes/libs/GenericArrayObject.php
+++ b/includes/libs/GenericArrayObject.php
@@ -28,9 +28,8 @@
* @since 1.20
*
* @file
- * @ingroup Diff
*
- * @licence GNU GPL v2+
+ * @license GNU GPL v2+
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
abstract class GenericArrayObject extends ArrayObject {
@@ -42,7 +41,7 @@ abstract class GenericArrayObject extends ArrayObject {
*
* @return string
*/
- public abstract function getObjectType();
+ abstract public function getObjectType();
/**
* @see SiteList::getNewOffset()
@@ -61,13 +60,11 @@ abstract class GenericArrayObject extends ArrayObject {
* @return integer
*/
protected function getNewOffset() {
- while ( true ) {
- if ( !$this->offsetExists( $this->indexOffset ) ) {
- return $this->indexOffset;
- }
-
+ while ( $this->offsetExists( $this->indexOffset ) ) {
$this->indexOffset++;
}
+
+ return $this->indexOffset;
}
/**
@@ -194,7 +191,7 @@ abstract class GenericArrayObject extends ArrayObject {
/**
* Returns an array holding all the data that should go into serialization calls.
* This is intended to allow overloading without having to reimplement the
- * behaviour of this base class.
+ * behavior of this base class.
*
* @since 1.20
*
diff --git a/includes/libs/IEContentAnalyzer.php b/includes/libs/IEContentAnalyzer.php
index cfc7f536..7f461a03 100644
--- a/includes/libs/IEContentAnalyzer.php
+++ b/includes/libs/IEContentAnalyzer.php
@@ -329,9 +329,9 @@ class IEContentAnalyzer {
* Get the MIME types from getMimesFromData(), but convert the result from IE's
* idiosyncratic private types into something other apps will understand.
*
- * @param $fileName String: the file name (unused at present)
- * @param $chunk String: the first 256 bytes of the file
- * @param $proposed String: the MIME type proposed by the server
+ * @param string $fileName the file name (unused at present)
+ * @param string $chunk the first 256 bytes of the file
+ * @param string $proposed the MIME type proposed by the server
*
* @return Array: map of IE version to detected mime type
*/
@@ -367,9 +367,9 @@ class IEContentAnalyzer {
/**
* Get the untranslated MIME types for all known versions
*
- * @param $fileName String: the file name (unused at present)
- * @param $chunk String: the first 256 bytes of the file
- * @param $proposed String: the MIME type proposed by the server
+ * @param string $fileName the file name (unused at present)
+ * @param string $chunk the first 256 bytes of the file
+ * @param string $proposed the MIME type proposed by the server
*
* @return Array: map of IE version to detected mime type
*/
@@ -848,4 +848,3 @@ class IEContentAnalyzer {
return 'unknown';
}
}
-
diff --git a/includes/libs/IEUrlExtension.php b/includes/libs/IEUrlExtension.php
index e9cfa997..79387e63 100644
--- a/includes/libs/IEUrlExtension.php
+++ b/includes/libs/IEUrlExtension.php
@@ -55,8 +55,8 @@ class IEUrlExtension {
*
* If the a variable is unset in $_SERVER, it should be unset in $vars.
*
- * @param $vars array A subset of $_SERVER.
- * @param $extWhitelist array Extensions which are allowed, assumed harmless.
+ * @param array $vars A subset of $_SERVER.
+ * @param array $extWhitelist Extensions which are allowed, assumed harmless.
* @return bool
*/
public static function areServerVarsBad( $vars, $extWhitelist = array() ) {
@@ -92,8 +92,8 @@ class IEUrlExtension {
* Given a right-hand portion of a URL, determine whether IE would detect
* a potentially harmful file extension.
*
- * @param $urlPart string The right-hand portion of a URL
- * @param $extWhitelist array An array of file extensions which may occur in this
+ * @param string $urlPart The right-hand portion of a URL
+ * @param array $extWhitelist An array of file extensions which may occur in this
* URL, and which should be allowed.
* @return bool
*/
@@ -187,7 +187,7 @@ class IEUrlExtension {
* - if we find a possible extension followed by a dot or another illegal
* character, we ignore it and continue searching
*
- * @param $url string URL
+ * @param string $url URL
* @return mixed Detected extension (string), or false if none found
*/
public static function findIE6Extension( $url ) {
@@ -245,7 +245,7 @@ class IEUrlExtension {
* whether the script filename has been obscured.
*
* The function returns false if the server is not known to have this
- * behaviour. Microsoft IIS in particular is known to decode escaped script
+ * behavior. Microsoft IIS in particular is known to decode escaped script
* filenames.
*
* SERVER_SOFTWARE typically contains either a plain string such as "Zeus",
diff --git a/includes/libs/JavaScriptMinifier.php b/includes/libs/JavaScriptMinifier.php
index 0b4be9ae..998805ae 100644
--- a/includes/libs/JavaScriptMinifier.php
+++ b/includes/libs/JavaScriptMinifier.php
@@ -59,7 +59,7 @@ class JavaScriptMinifier {
const TYPE_DO = 15; // keywords: case, var, finally, else, do, try
const TYPE_FUNC = 16; // keywords: function
const TYPE_LITERAL = 17; // all literals, identifiers and unrecognised tokens
-
+
// Sanity limit to avoid excessive memory usage
const STACK_LIMIT = 1000;
@@ -72,9 +72,9 @@ class JavaScriptMinifier {
* literals (e.g. quoted strings) longer than $maxLineLength are encountered
* or when required to guard against semicolon insertion.
*
- * @param $s String JavaScript code to minify
- * @param $statementsOnOwnLine Bool Whether to put each statement on its own line
- * @param $maxLineLength Int Maximum length of a single line, or -1 for no maximum.
+ * @param string $s JavaScript code to minify
+ * @param bool $statementsOnOwnLine Whether to put each statement on its own line
+ * @param int $maxLineLength Maximum length of a single line, or -1 for no maximum.
* @return String Minified code
*/
public static function minify( $s, $statementsOnOwnLine = false, $maxLineLength = 1000 ) {
@@ -385,7 +385,7 @@ class JavaScriptMinifier {
self::TYPE_LITERAL => true
)
);
-
+
// Rules for when newlines should be inserted if
// $statementsOnOwnLine is enabled.
// $newlineBefore is checked before switching state,
@@ -514,7 +514,7 @@ class JavaScriptMinifier {
return self::parseError($s, $end, 'Number with several E' );
}
$end++;
-
+
// + sign is optional; - sign is required.
$end += strspn( $s, '-+', $end );
$len = strspn( $s, '0123456789', $end );
@@ -564,13 +564,13 @@ class JavaScriptMinifier {
$out .= ' ';
$lineLength++;
}
-
+
$out .= $token;
$lineLength += $end - $pos; // += strlen( $token )
$last = $s[$end - 1];
$pos = $end;
$newlineFound = false;
-
+
// Output a newline after the token if required
// This is checked before AND after switching state
$newlineAdded = false;
@@ -589,7 +589,7 @@ class JavaScriptMinifier {
} elseif( isset( $goto[$state][$type] ) ) {
$state = $goto[$state][$type];
}
-
+
// Check for newline insertion again
if ( $statementsOnOwnLine && !$newlineAdded && isset( $newlineAfter[$state][$type] ) ) {
$out .= "\n";
@@ -598,7 +598,7 @@ class JavaScriptMinifier {
}
return $out;
}
-
+
static function parseError($fullJavascript, $position, $errorMsg) {
// TODO: Handle the error: trigger_error, throw exception, return false...
return false;
diff --git a/includes/libs/jsminplus.php b/includes/libs/jsminplus.php
index 7c4e32bd..f250217f 100644
--- a/includes/libs/jsminplus.php
+++ b/includes/libs/jsminplus.php
@@ -256,7 +256,7 @@ class JSMinPlus
}
elseif ($type == KEYWORD_VAR && $type == $lastType)
{
- // mutiple var-statements can go into one
+ // multiple var-statements can go into one
$t = ',' . substr($t, 4);
}
else
@@ -298,7 +298,7 @@ class JSMinPlus
if ($elsePart)
{
- // be carefull and always make a block out of the thenPart; could be more optimized but is a lot of trouble
+ // be careful and always make a block out of the thenPart; could be more optimized but is a lot of trouble
if ($thenPart != ';' && $thenPart[0] != '{')
$thenPart = '{' . $thenPart . '}';
@@ -521,7 +521,7 @@ class JSMinPlus
break;
case TOKEN_STRING:
- //combine concatted strings with same quotestyle
+ //combine concatenated strings with same quote style
if ($n->type == OP_PLUS && substr($left, -1) == $right[0])
{
$s = substr($left, 0, -1) . substr($right, 1);
diff --git a/includes/limit.sh b/includes/limit.sh
new file mode 100644
index 00000000..bc1988de
--- /dev/null
+++ b/includes/limit.sh
@@ -0,0 +1,102 @@
+#!/bin/bash
+#
+# Resource limiting wrapper for command execution
+#
+# Why is this in shell script? Because bash has a setrlimit() wrapper
+# and is available on most Linux systems. If Perl was distributed with
+# BSD::Resource included, we would happily use that instead, but it isn't.
+
+MW_CPU_LIMIT=0
+MW_CGROUP=
+MW_MEM_LIMIT=0
+MW_FILE_SIZE_LIMIT=0
+MW_WALL_CLOCK_LIMIT=0
+
+# Override settings
+eval "$2"
+
+if [ "$MW_CPU_LIMIT" -gt 0 ]; then
+ ulimit -t "$MW_CPU_LIMIT"
+fi
+if [ "$MW_MEM_LIMIT" -gt 0 ]; then
+ if [ -n "$MW_CGROUP" ]; then
+ # Create cgroup
+ if ! mkdir -m 0700 "$MW_CGROUP"/$$; then
+ echo "limit.sh: failed to create the cgroup." 1>&2
+ exit 1
+ fi
+ echo $$ > "$MW_CGROUP"/$$/tasks
+ if [ -n "$MW_CGROUP_NOTIFY" ]; then
+ echo "1" > "$MW_CGROUP"/$$/notify_on_release
+ fi
+ # Memory
+ echo $(($MW_MEM_LIMIT*1024)) > "$MW_CGROUP"/$$/memory.limit_in_bytes
+ # Memory+swap
+ echo $(($MW_MEM_LIMIT*1024)) > "$MW_CGROUP"/$$/memory.memsw.limit_in_bytes
+ else
+ ulimit -v "$MW_MEM_LIMIT"
+ fi
+else
+ MW_CGROUP=""
+fi
+if [ "$MW_FILE_SIZE_LIMIT" -gt 0 ]; then
+ ulimit -f "$MW_FILE_SIZE_LIMIT"
+fi
+if [ "$MW_WALL_CLOCK_LIMIT" -gt 0 -a -x "/usr/bin/timeout" ]; then
+ /usr/bin/timeout $MW_WALL_CLOCK_LIMIT /bin/bash -c "$1"
+ STATUS="$?"
+ if [ "$STATUS" == 124 ]; then
+ echo "limit.sh: timed out." 1>&2
+ fi
+else
+ eval "$1"
+ STATUS="$?"
+fi
+
+# Clean up cgroup
+cleanup() {
+ # First we have to move the current task into a "garbage" group, otherwise
+ # the cgroup will not be empty, and attempting to remove it will fail with
+ # "Device or resource busy"
+ if [ -w "$MW_CGROUP"/tasks ]; then
+ GARBAGE="$MW_CGROUP"
+ else
+ GARBAGE="$MW_CGROUP"/garbage-"$USER"
+ if [ ! -e "$GARBAGE" ]; then
+ mkdir -m 0700 "$GARBAGE"
+ fi
+ fi
+ echo $BASHPID > "$GARBAGE"/tasks
+
+ # Suppress errors in case the cgroup has disappeared due to a release script
+ rmdir "$MW_CGROUP"/$$ 2>/dev/null
+}
+
+updateTaskCount() {
+ # There are lots of ways to count lines in a file in shell script, but this
+ # is one of the few that doesn't create another process, which would
+ # increase the returned number of tasks.
+ readarray < "$MW_CGROUP"/$$/tasks
+ NUM_TASKS=${#MAPFILE[*]}
+}
+
+if [ -n "$MW_CGROUP" ]; then
+ updateTaskCount
+
+ if [ $NUM_TASKS -gt 1 ]; then
+ # Spawn a monitor process which will continue to poll for completion
+ # of all processes in the cgroup after termination of the parent shell
+ (
+ while [ $NUM_TASKS -gt 1 ]; do
+ sleep 10
+ updateTaskCount
+ done
+ cleanup
+ ) >&/dev/null < /dev/null &
+ disown -a
+ else
+ cleanup
+ fi
+fi
+exit "$STATUS"
+
diff --git a/includes/logging/LogEntry.php b/includes/logging/LogEntry.php
index 37560d80..0f20ed16 100644
--- a/includes/logging/LogEntry.php
+++ b/includes/logging/LogEntry.php
@@ -175,6 +175,7 @@ class DatabaseLogEntry extends LogEntryBase {
/// Database result row.
protected $row;
+ protected $performer;
protected function __construct( $row ) {
$this->row = $row;
@@ -232,17 +233,20 @@ class DatabaseLogEntry extends LogEntryBase {
}
public function getPerformer() {
- $userId = (int) $this->row->log_user;
- if ( $userId !== 0 ) { // logged-in users
- if ( isset( $this->row->user_name ) ) {
- return User::newFromRow( $this->row );
- } else {
- return User::newFromId( $userId );
+ if( !$this->performer ) {
+ $userId = (int) $this->row->log_user;
+ if ( $userId !== 0 ) { // logged-in users
+ if ( isset( $this->row->user_name ) ) {
+ $this->performer = User::newFromRow( $this->row );
+ } else {
+ $this->performer = User::newFromId( $userId );
+ }
+ } else { // IP users
+ $userText = $this->row->log_user_text;
+ $this->performer = User::newFromName( $userText, false );
}
- } else { // IP users
- $userText = $this->row->log_user_text;
- return User::newFromName( $userText, false );
}
+ return $this->performer;
}
public function getTarget() {
@@ -287,14 +291,17 @@ class RCDatabaseLogEntry extends DatabaseLogEntry {
}
public function getPerformer() {
- $userId = (int) $this->row->rc_user;
- if ( $userId !== 0 ) {
- return User::newFromId( $userId );
- } else {
- $userText = $this->row->rc_user_text;
- // Might be an IP, don't validate the username
- return User::newFromName( $userText, false );
+ if( !$this->performer ) {
+ $userId = (int) $this->row->rc_user;
+ if ( $userId !== 0 ) {
+ $this->performer = User::newFromId( $userId );
+ } else {
+ $userText = $this->row->rc_user_text;
+ // Might be an IP, don't validate the username
+ $this->performer = User::newFromName( $userText, false );
+ }
}
+ return $this->performer;
}
public function getTarget() {
@@ -335,9 +342,9 @@ class ManualLogEntry extends LogEntryBase {
/**
* Constructor.
- *
+ *
* @since 1.19
- *
+ *
* @param string $type
* @param string $subtype
*/
@@ -357,10 +364,10 @@ class ManualLogEntry extends LogEntryBase {
* '4:color' => 'blue',
* 'animal' => 'dog'
* );
- *
+ *
* @since 1.19
- *
- * @param $parameters array Associative array
+ *
+ * @param array $parameters Associative array
*/
public function setParameters( $parameters ) {
$this->parameters = $parameters;
@@ -368,9 +375,9 @@ class ManualLogEntry extends LogEntryBase {
/**
* Set the user that performed the action being logged.
- *
+ *
* @since 1.19
- *
+ *
* @param User $performer
*/
public function setPerformer( User $performer ) {
@@ -379,9 +386,9 @@ class ManualLogEntry extends LogEntryBase {
/**
* Set the title of the object changed.
- *
+ *
* @since 1.19
- *
+ *
* @param Title $target
*/
public function setTarget( Title $target ) {
@@ -390,9 +397,9 @@ class ManualLogEntry extends LogEntryBase {
/**
* Set the timestamp of when the logged action took place.
- *
+ *
* @since 1.19
- *
+ *
* @param string $timestamp
*/
public function setTimestamp( $timestamp ) {
@@ -401,9 +408,9 @@ class ManualLogEntry extends LogEntryBase {
/**
* Set a comment associated with the action being logged.
- *
+ *
* @since 1.19
- *
+ *
* @param string $comment
*/
public function setComment( $comment ) {
@@ -412,9 +419,9 @@ class ManualLogEntry extends LogEntryBase {
/**
* TODO: document
- *
+ *
* @since 1.19
- *
+ *
* @param integer $deleted
*/
public function setDeleted( $deleted ) {
@@ -435,8 +442,11 @@ class ManualLogEntry extends LogEntryBase {
$this->timestamp = wfTimestampNow();
}
+ # Trim spaces on user supplied text
+ $comment = trim( $this->getComment() );
+
# Truncate for whole multibyte characters.
- $comment = $wgContLang->truncate( $this->getComment(), 255 );
+ $comment = $wgContLang->truncate( $comment, 255 );
$data = array(
'log_id' => $id,
@@ -458,8 +468,8 @@ class ManualLogEntry extends LogEntryBase {
/**
* Publishes the log entry.
- * @param $newId int id of the log entry.
- * @param $to string: rcandudp (default), rc, udp
+ * @param int $newId id of the log entry.
+ * @param string $to rcandudp (default), rc, udp
*/
public function publish( $newId, $to = 'rcandudp' ) {
$log = new LogPage( $this->getType() );
diff --git a/includes/logging/LogEventsList.php b/includes/logging/LogEventsList.php
index 4de1a974..501af7d6 100644
--- a/includes/logging/LogEventsList.php
+++ b/includes/logging/LogEventsList.php
@@ -42,7 +42,7 @@ class LogEventsList extends ContextSource {
*
* @param $context IContextSource Context to use; formerly it was Skin object.
* @param $unused void Unused; used to be an OutputPage object.
- * @param $flags int flags; can be a combinaison of self::NO_ACTION_LINK,
+ * @param int $flags flags; can be a combinaison of self::NO_ACTION_LINK,
* self::NO_EXTRA_USER_LINKS or self::USE_REVDEL_CHECKBOXES.
*/
public function __construct( $context, $unused = null, $flags = 0 ) {
@@ -74,7 +74,7 @@ class LogEventsList extends ContextSource {
public function showHeader( $type ) {
wfDeprecated( __METHOD__, '1.19' );
// If only one log type is used, then show a special message...
- $headerType = (count($type) == 1) ? $type[0] : '';
+ $headerType = (count( $type ) == 1) ? $type[0] : '';
$out = $this->getOutput();
if( LogPage::isLogType( $headerType ) ) {
$page = new LogPage( $headerType );
@@ -88,7 +88,7 @@ class LogEventsList extends ContextSource {
/**
* Show options for the log list
*
- * @param $types string or Array
+ * @param string $types or Array
* @param $user String
* @param $page String
* @param $pattern String
@@ -97,8 +97,8 @@ class LogEventsList extends ContextSource {
* @param $filter: array
* @param $tagFilter: array?
*/
- public function showOptions( $types=array(), $user='', $page='', $pattern='', $year='',
- $month = '', $filter = null, $tagFilter='' ) {
+ public function showOptions( $types=array(), $user = '', $page = '', $pattern = '', $year = '',
+ $month = '', $filter = null, $tagFilter = '' ) {
global $wgScript, $wgMiserMode;
$title = SpecialPage::getTitleFor( 'Log' );
@@ -117,7 +117,7 @@ class LogEventsList extends ContextSource {
$html .= $this->getExtraInputs( $types ) . "\n";
// Title pattern, if allowed
- if (!$wgMiserMode) {
+ if ( !$wgMiserMode ) {
$html .= $this->getTitlePattern( $pattern ) . "\n";
}
@@ -125,12 +125,12 @@ class LogEventsList extends ContextSource {
$html .= Xml::tags( 'p', null, Xml::dateMenu( $year, $month ) );
// Tag filter
- if ($tagSelector) {
+ if ( $tagSelector ) {
$html .= Xml::tags( 'p', null, implode( '&#160;', $tagSelector ) );
}
// Filter links
- if ($filter) {
+ if ( $filter ) {
$html .= Xml::tags( 'p', null, $this->getFilterLinks( $filter ) );
}
@@ -162,7 +162,7 @@ class LogEventsList extends ContextSource {
$query = $this->getDefaultQuery();
$queryKey = "hide_{$type}_log";
- $hideVal = 1 - intval($val);
+ $hideVal = 1 - intval( $val );
$query[$queryKey] = $hideVal;
$link = Linker::linkKnown(
@@ -176,7 +176,7 @@ class LogEventsList extends ContextSource {
$hiddens .= Html::hidden( "hide_{$type}_log", $val ) . "\n";
}
// Build links
- return '<small>'.$this->getLanguage()->pipeList( $links ) . '</small>' . $hiddens;
+ return '<small>' . $this->getLanguage()->pipeList( $links ) . '</small>' . $hiddens;
}
private function getDefaultQuery() {
@@ -198,7 +198,7 @@ class LogEventsList extends ContextSource {
* @return String: Formatted HTML
*/
private function getTypeMenu( $queryTypes ) {
- $queryType = count($queryTypes) == 1 ? $queryTypes[0] : '';
+ $queryType = count( $queryTypes ) == 1 ? $queryTypes[0] : '';
$selector = $this->getTypeSelector();
$selector->setDefault( $queryType );
return $selector->getHtml();
@@ -212,7 +212,7 @@ class LogEventsList extends ContextSource {
public function getTypeSelector() {
$typesByName = array(); // Temporary array
// First pass to load the log names
- foreach( LogPage::validTypes() as $type ) {
+ foreach( LogPage::validTypes() as $type ) {
$page = new LogPage( $type );
$restriction = $page->getRestriction();
if ( $this->getUser()->isAllowed( $restriction ) ) {
@@ -221,7 +221,7 @@ class LogEventsList extends ContextSource {
}
// Second pass to sort by name
- asort($typesByName);
+ asort( $typesByName );
// Always put "All public logs" on top
$public = $typesByName[''];
@@ -273,10 +273,10 @@ class LogEventsList extends ContextSource {
private function getExtraInputs( $types ) {
$offender = $this->getRequest()->getVal( 'offender' );
$user = User::newFromName( $offender, false );
- if( !$user || ($user->getId() == 0 && !IP::isIPAddress($offender) ) ) {
+ if( !$user || ( $user->getId() == 0 && !IP::isIPAddress( $offender ) ) ) {
$offender = ''; // Blank field if invalid
}
- if( count($types) == 1 && $types[0] == 'suppress' ) {
+ if( count( $types ) == 1 && $types[0] == 'suppress' ) {
return Xml::inputLabel( $this->msg( 'revdelete-offender' )->text(), 'offender',
'mw-log-offender', 20, $offender );
}
@@ -307,7 +307,6 @@ class LogEventsList extends ContextSource {
$formatter->setContext( $this->getContext() );
$formatter->setShowUserToolLinks( !( $this->flags & self::NO_EXTRA_USER_LINKS ) );
- $title = $entry->getTarget();
$time = htmlspecialchars( $this->getLanguage()->userTimeAndDate(
$entry->getTimestamp(), $this->getUser() ) );
@@ -351,9 +350,9 @@ class LogEventsList extends ContextSource {
$user = $this->getUser();
// Don't show useless checkbox to people who cannot hide log entries
if( $user->isAllowed( 'deletedhistory' ) ) {
- if( $row->log_deleted || $user->isAllowed( 'deletelogentry' ) ) {
- $canHide = $user->isAllowed( 'deletelogentry' );
- if ( $this->flags & self::USE_REVDEL_CHECKBOXES ) { // Show checkboxes instead of links.
+ $canHide = $user->isAllowed( 'deletelogentry' );
+ if( $row->log_deleted || $canHide ) {
+ if ( $canHide && $this->flags & self::USE_REVDEL_CHECKBOXES ) { // Show checkboxes instead of links.
if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $user ) ) { // If event was hidden from sysops
$del = Xml::check( 'deleterevisions', false, array( 'disabled' => 'disabled' ) );
} else {
@@ -383,8 +382,8 @@ class LogEventsList extends ContextSource {
* @param $right string
* @return Boolean
*/
- public static function typeAction( $row, $type, $action, $right='' ) {
- $match = is_array($type) ?
+ public static function typeAction( $row, $type, $action, $right = '' ) {
+ $match = is_array( $type ) ?
in_array( $row->log_type, $type ) : $row->log_type == $type;
if( $match ) {
$match = is_array( $action ) ?
@@ -450,10 +449,10 @@ class LogEventsList extends ContextSource {
* Show log extract. Either with text and a box (set $msgKey) or without (don't set $msgKey)
*
* @param $out OutputPage|String-by-reference
- * @param $types String|Array Log types to show
- * @param $page String|Title The page title to show log entries for
- * @param $user String The user who made the log entries
- * @param $param array Associative Array with the following additional options:
+ * @param string|array $types Log types to show
+ * @param string|Title $page The page title to show log entries for
+ * @param string $user The user who made the log entries
+ * @param array $param Associative Array with the following additional options:
* - lim Integer Limit of items to show, default is 50
* - conds Array Extra conditions for the query (e.g. "log_action != 'revision'")
* - showIfEmpty boolean Set to false if you don't want any output in case the loglist is empty
@@ -468,13 +467,13 @@ class LogEventsList extends ContextSource {
* @return Integer Number of total log items (not limited by $lim)
*/
public static function showLogExtract(
- &$out, $types=array(), $page='', $user='', $param = array()
+ &$out, $types=array(), $page = '', $user = '', $param = array()
) {
$defaultParameters = array(
'lim' => 25,
'conds' => array(),
'showIfEmpty' => true,
- 'msgKey' => array(''),
+ 'msgKey' => array( '' ),
'wrap' => "$1",
'flags' => 0
);
@@ -520,8 +519,8 @@ class LogEventsList extends ContextSource {
}
}
$s .= $loglist->beginLogEventsList() .
- $logBody .
- $loglist->endLogEventsList();
+ $logBody .
+ $loglist->endLogEventsList();
} else {
if ( $showIfEmpty ) {
$s = Html::rawElement( 'div', array( 'class' => 'mw-warning-logempty' ),
@@ -535,7 +534,7 @@ class LogEventsList extends ContextSource {
} elseif ( $page != '' ) {
$urlParam['page'] = $page;
}
- if ( $user != '')
+ if ( $user != '' )
$urlParam['user'] = $user;
if ( !is_array( $types ) ) # Make it an array, if it isn't
$types = array( $types );
@@ -560,7 +559,7 @@ class LogEventsList extends ContextSource {
/* hook can return false, if we don't want the message to be emitted (Wikia BugId:7093) */
if ( wfRunHooks( 'LogEventsListShowLogExtract', array( &$s, $types, $page, $user, $param ) ) ) {
// $out can be either an OutputPage object or a String-by-reference
- if ( $out instanceof OutputPage ){
+ if ( $out instanceof OutputPage ) {
$out->addHTML( $s );
} else {
$out = $s;
@@ -575,24 +574,31 @@ class LogEventsList extends ContextSource {
*
* @param $db DatabaseBase
* @param $audience string, public/user
+ * @param $user User object to check, or null to use $wgUser
* @return Mixed: string or false
*/
- public static function getExcludeClause( $db, $audience = 'public' ) {
- global $wgLogRestrictions, $wgUser;
+ public static function getExcludeClause( $db, $audience = 'public', User $user = null ) {
+ global $wgLogRestrictions;
+
+ if ( $audience != 'public' && $user === null ) {
+ global $wgUser;
+ $user = $wgUser;
+ }
+
// Reset the array, clears extra "where" clauses when $par is used
$hiddenLogs = array();
+
// Don't show private logs to unprivileged users
foreach( $wgLogRestrictions as $logType => $right ) {
- if( $audience == 'public' || !$wgUser->isAllowed($right) ) {
- $safeType = $db->strencode( $logType );
- $hiddenLogs[] = $safeType;
+ if( $audience == 'public' || !$user->isAllowed( $right ) ) {
+ $hiddenLogs[] = $logType;
}
}
- if( count($hiddenLogs) == 1 ) {
+ if( count( $hiddenLogs ) == 1 ) {
return 'log_type != ' . $db->addQuotes( $hiddenLogs[0] );
} elseif( $hiddenLogs ) {
- return 'log_type NOT IN (' . $db->makeList($hiddenLogs) . ')';
+ return 'log_type NOT IN (' . $db->makeList( $hiddenLogs ) . ')';
}
return false;
}
- }
+}
diff --git a/includes/logging/LogFormatter.php b/includes/logging/LogFormatter.php
index 7586bb65..ace26bbe 100644
--- a/includes/logging/LogFormatter.php
+++ b/includes/logging/LogFormatter.php
@@ -192,18 +192,18 @@ class LogFormatter {
$parameters = $entry->getParameters();
// @see LogPage::actionText()
// Text of title the action is aimed at.
- $target = $entry->getTarget()->getPrefixedText() ;
+ $target = $entry->getTarget()->getPrefixedText();
$text = null;
switch( $entry->getType() ) {
case 'move':
switch( $entry->getSubtype() ) {
case 'move':
- $movesource = $parameters['4::target'];
+ $movesource = $parameters['4::target'];
$text = wfMessage( '1movedto2' )
->rawParams( $target, $movesource )->inContentLanguage()->escaped();
break;
case 'move_redir':
- $movesource = $parameters['4::target'];
+ $movesource = $parameters['4::target'];
$text = wfMessage( '1movedto2_redir' )
->rawParams( $target, $movesource )->inContentLanguage()->escaped();
break;
@@ -270,6 +270,7 @@ class LogFormatter {
->inContentLanguage()->escaped();
break;
case 'create2':
+ case 'byemail':
$text = wfMessage( 'newuserlog-create2-entry' )
->rawParams( $target )->inContentLanguage()->escaped();
break;
@@ -293,6 +294,28 @@ class LogFormatter {
}
break;
+ case 'rights':
+ if ( count( $parameters['4::oldgroups'] ) ) {
+ $oldgroups = implode( ', ', $parameters['4::oldgroups'] );
+ } else {
+ $oldgroups = wfMessage( 'rightsnone' )->inContentLanguage()->escaped();
+ }
+ if ( count( $parameters['5::newgroups'] ) ) {
+ $newgroups = implode( ', ', $parameters['5::newgroups'] );
+ } else {
+ $newgroups = wfMessage( 'rightsnone' )->inContentLanguage()->escaped();
+ }
+ switch( $entry->getSubtype() ) {
+ case 'rights':
+ $text = wfMessage( 'rightslogentry' )
+ ->rawParams( $target, $oldgroups, $newgroups )->inContentLanguage()->escaped();
+ break;
+ case 'autopromote':
+ $text = wfMessage( 'rightslogentry-autopromote' )
+ ->rawParams( $target, $oldgroups, $newgroups )->inContentLanguage()->escaped();
+ break;
+ }
+ break;
// case 'suppress' --private log -- aaron (sign your messages so we know who to blame in a few years :-D)
// default:
@@ -379,9 +402,11 @@ class LogFormatter {
// Filter out parameters which are not in format #:foo
foreach ( $entry->getParameters() as $key => $value ) {
- if ( strpos( $key, ':' ) === false ) continue;
- list( $index, $type, $name ) = explode( ':', $key, 3 );
- $params[$index - 1] = $value;
+ if ( strpos( $key, ':' ) === false ) {
+ continue;
+ }
+ list( $index, $type, ) = explode( ':', $key, 3 );
+ $params[$index - 1] = $this->formatParameterValue( $type, $value );
}
/* Message class doesn't like non consecutive numbering.
@@ -416,7 +441,7 @@ class LogFormatter {
$entry = $this->entry;
$params = $this->extractParameters();
$params[0] = Message::rawParam( $this->getPerformerElement() );
- $params[1] = $entry->getPerformer()->getName();
+ $params[1] = $this->canView( LogPage::DELETED_USER ) ? $entry->getPerformer()->getName() : '';
$params[2] = Message::rawParam( $this->makePageLink( $entry->getTarget() ) );
// Bad things happens if the numbers are not in correct order
@@ -425,10 +450,83 @@ class LogFormatter {
}
/**
+ * Formats parameters values dependent to their type
+ * @param string $type The type of the value.
+ * Valid are currently:
+ * * - (empty) or plain: The value is returned as-is
+ * * raw: The value will be added to the log message
+ * as raw parameter (e.g. no escaping)
+ * Use this only if there is no other working
+ * type like user-link or title-link
+ * * msg: The value is a message-key, the output is
+ * the message in user language
+ * * msg-content: The value is a message-key, the output
+ * is the message in content language
+ * * user: The value is a user name, e.g. for GENDER
+ * * user-link: The value is a user name, returns a
+ * link for the user
+ * * title: The value is a page title,
+ * returns name of page
+ * * title-link: The value is a page title,
+ * returns link to this page
+ * * number: Format value as number
+ * @param string $value The parameter value that should
+ * be formated
+ * @return string or Message::numParam or Message::rawParam
+ * Formated value
+ * @since 1.21
+ */
+ protected function formatParameterValue( $type, $value ) {
+ $saveLinkFlood = $this->linkFlood;
+
+ switch( strtolower( trim( $type ) ) ) {
+ case 'raw':
+ $value = Message::rawParam( $value );
+ break;
+ case 'msg':
+ $value = $this->msg( $value )->text();
+ break;
+ case 'msg-content':
+ $value = $this->msg( $value )->inContentLanguage()->text();
+ break;
+ case 'number':
+ $value = Message::numParam( $value );
+ break;
+ case 'user':
+ $user = User::newFromName( $value );
+ $value = $user->getName();
+ break;
+ case 'user-link':
+ $this->setShowUserToolLinks( false );
+
+ $user = User::newFromName( $value );
+ $value = Message::rawParam( $this->makeUserLink( $user ) );
+
+ $this->setShowUserToolLinks( $saveLinkFlood );
+ break;
+ case 'title':
+ $title = Title::newFromText( $value );
+ $value = $title->getPrefixedText();
+ break;
+ case 'title-link':
+ $title = Title::newFromText( $value );
+ $value = Message::rawParam( $this->makePageLink( $title ) );
+ break;
+ case 'plain':
+ // Plain text, nothing to do
+ default:
+ // Catch other types and use the old behavior (return as-is)
+ }
+
+ return $value;
+ }
+
+ /**
* Helper to make a link to the page, taking the plaintext
* value in consideration.
* @param $title Title the page
- * @param $parameters array query parameters
+ * @param array $parameters query parameters
+ * @throws MWException
* @return String
*/
protected function makePageLink( Title $title = null, $parameters = array() ) {
@@ -492,7 +590,7 @@ class LogFormatter {
return $this->msg( $message )->text();
}
- $content = $this->msg( $message )->escaped();
+ $content = $this->msg( $message )->escaped();
$attribs = array( 'class' => 'history-deleted' );
return Html::rawElement( 'span', $attribs, $content );
}
@@ -547,6 +645,16 @@ class LogFormatter {
return array();
}
+ /**
+ * @return Output of getMessageParameters() for testing
+ */
+ public function getMessageParametersForTesting() {
+ // This function was added because getMessageParameters() is
+ // protected and a change from protected to public caused
+ // problems with extensions
+ return $this->getMessageParameters();
+ }
+
}
/**
@@ -607,7 +715,7 @@ class LegacyLogFormatter extends LogFormatter {
$performer = $this->getPerformerElement();
if ( !$this->irctext ) {
- $action = $performer . $this->msg( 'word-separator' )->text() . $action;
+ $action = $performer . $this->msg( 'word-separator' )->text() . $action;
}
return $action;
@@ -786,9 +894,11 @@ class DeleteLogFormatter extends LogFormatter {
$params = parent::getMessageParameters();
$subtype = $this->entry->getSubtype();
if ( in_array( $subtype, array( 'event', 'revision' ) ) ) {
+ // $params[3] here is 'revision' for page revisions, 'oldimage' for file versions, or a comma-separated list of log_ids for log entries.
+ // $subtype here is 'revision' for page revisions and file versions, or 'event' for log entries.
if (
- ($subtype === 'event' && count( $params ) === 6 ) ||
- ($subtype === 'revision' && isset( $params[3] ) && $params[3] === 'revision' )
+ ( $subtype === 'event' && count( $params ) === 6 ) ||
+ ( $subtype === 'revision' && isset( $params[3] ) && ( $params[3] === 'revision' || $params[3] === 'oldimage' ) )
) {
$paramStart = $subtype === 'revision' ? 4 : 3;
@@ -805,8 +915,7 @@ class DeleteLogFormatter extends LogFormatter {
foreach ( $extra as $v ) {
$changes[] = $this->msg( $v )->plain();
}
- $changeText = $this->context->getLanguage()->listToText( $changes );
-
+ $changeText = $this->context->getLanguage()->listToText( $changes );
$newParams = array_slice( $params, 0, 3 );
$newParams[3] = $changeText;
@@ -849,7 +958,7 @@ class DeleteLogFormatter extends LogFormatter {
$this->msg( $message )->escaped(),
array(),
array( 'target' => $this->entry->getTarget()->getPrefixedDBkey() )
- );
+ );
return $this->msg( 'parentheses' )->rawParams( $revert )->escaped();
case 'revision': // If an edit was hidden from a page give a review link to the history
@@ -885,7 +994,7 @@ class DeleteLogFormatter extends LogFormatter {
$this->msg( 'diff' )->escaped(),
array(),
array(
- 'target' => $this->entry->getTarget()->getPrefixedDBKey(),
+ 'target' => $this->entry->getTarget()->getPrefixedDBkey(),
'diff' => 'prev',
'timestamp' => $ids[0]
)
@@ -978,7 +1087,8 @@ class PatrolLogFormatter extends LogFormatter {
class NewUsersLogFormatter extends LogFormatter {
protected function getMessageParameters() {
$params = parent::getMessageParameters();
- if ( $this->entry->getSubtype() === 'create2' ) {
+ $subtype = $this->entry->getSubtype();
+ if ( $subtype === 'create2' || $subtype === 'byemail' ) {
if ( isset( $params[3] ) ) {
$target = User::newFromId( $params[3] );
} else {
@@ -1001,10 +1111,98 @@ class NewUsersLogFormatter extends LogFormatter {
}
public function getPreloadTitles() {
- if ( $this->entry->getSubtype() === 'create2' ) {
+ $subtype = $this->entry->getSubtype();
+ if ( $subtype === 'create2' || $subtype === 'byemail' ) {
//add the user talk to LinkBatch for the userLink
return array( Title::makeTitle( NS_USER_TALK, $this->entry->getTarget()->getText() ) );
}
return array();
}
}
+
+/**
+ * This class formats rights log entries.
+ * @since 1.21
+ */
+class RightsLogFormatter extends LogFormatter {
+ protected function makePageLink( Title $title = null, $parameters = array() ) {
+ global $wgContLang, $wgUserrightsInterwikiDelimiter;
+
+ if ( !$this->plaintext ) {
+ $text = $wgContLang->ucfirst( $title->getText() );
+ $parts = explode( $wgUserrightsInterwikiDelimiter, $text, 2 );
+
+ if ( count( $parts ) === 2 ) {
+ $titleLink = WikiMap::foreignUserLink( $parts[1], $parts[0],
+ htmlspecialchars( $title->getPrefixedText() ) );
+
+ if ( $titleLink !== false ) {
+ return $titleLink;
+ }
+ }
+ }
+
+ return parent::makePageLink( $title, $parameters );
+ }
+
+ protected function getMessageKey() {
+ $key = parent::getMessageKey();
+ $params = $this->getMessageParameters();
+ if ( !isset( $params[3] ) && !isset( $params[4] ) ) {
+ $key .= '-legacy';
+ }
+ return $key;
+ }
+
+ protected function getMessageParameters() {
+ $params = parent::getMessageParameters();
+
+ // Really old entries
+ if ( !isset( $params[3] ) && !isset( $params[4] ) ) {
+ return $params;
+ }
+
+ $oldGroups = $params[3];
+ $newGroups = $params[4];
+
+ // Less old entries
+ if ( $oldGroups === '' ) {
+ $oldGroups = array();
+ } elseif ( is_string( $oldGroups ) ) {
+ $oldGroups = array_map( 'trim', explode( ',', $oldGroups ) );
+ }
+ if ( $newGroups === '' ) {
+ $newGroups = array();
+ } elseif ( is_string( $newGroups ) ) {
+ $newGroups = array_map( 'trim', explode( ',', $newGroups ) );
+ }
+
+ $userName = $this->entry->getTarget()->getText();
+ if ( !$this->plaintext && count( $oldGroups ) ) {
+ foreach ( $oldGroups as &$group ) {
+ $group = User::getGroupMember( $group, $userName );
+ }
+ }
+ if ( !$this->plaintext && count( $newGroups ) ) {
+ foreach ( $newGroups as &$group ) {
+ $group = User::getGroupMember( $group, $userName );
+ }
+ }
+
+ $lang = $this->context->getLanguage();
+ if ( count( $oldGroups ) ) {
+ $params[3] = $lang->listToText( $oldGroups );
+ } else {
+ $params[3] = $this->msg( 'rightsnone' )->text();
+ }
+ if ( count( $newGroups ) ) {
+ // Array_values is used here because of bug 42211
+ // see use of array_unique in UserrightsPage::doSaveUserGroups on $newGroups.
+ $params[4] = $lang->listToText( array_values( $newGroups ) );
+ } else {
+ $params[4] = $this->msg( 'rightsnone' )->text();
+ }
+
+ return $params;
+ }
+}
diff --git a/includes/logging/LogPage.php b/includes/logging/LogPage.php
index d96a5ea5..4191c577 100644
--- a/includes/logging/LogPage.php
+++ b/includes/logging/LogPage.php
@@ -50,16 +50,16 @@ class LogPage {
*/
var $target;
- /* @acess public */
+ /* @access public */
var $updateRecentChanges, $sendToUDP;
/**
* Constructor
*
- * @param $type String: one of '', 'block', 'protect', 'rights', 'delete',
+ * @param string $type one of '', 'block', 'protect', 'rights', 'delete',
* 'upload', 'move'
* @param $rc Boolean: whether to update recent changes as well as the logging table
- * @param $udp String: pass 'UDP' to send to the UDP feed if NOT sent to RC
+ * @param string $udp pass 'UDP' to send to the UDP feed if NOT sent to RC
*/
public function __construct( $type, $rc = true, $udp = 'skipUDP' ) {
$this->type = $type;
@@ -181,7 +181,7 @@ class LogPage {
/**
* Is $type a valid log type
*
- * @param $type String: log type to check
+ * @param string $type log type to check
* @return Boolean
*/
public static function isLogType( $type ) {
@@ -191,7 +191,7 @@ class LogPage {
/**
* Get the name for the given log type
*
- * @param $type String: logtype
+ * @param string $type logtype
* @return String: log name
* @deprecated in 1.19, warnings in 1.21. Use getName()
*/
@@ -210,7 +210,7 @@ class LogPage {
* Get the log header for the given log type
*
* @todo handle missing log types
- * @param $type String: logtype
+ * @param string $type logtype
* @return String: headertext of this logtype
* @deprecated in 1.19, warnings in 1.21. Use getDescription()
*/
@@ -220,15 +220,15 @@ class LogPage {
}
/**
- * Generate text for a log entry.
+ * Generate text for a log entry.
* Only LogFormatter should call this function.
*
- * @param $type String: log type
- * @param $action String: log action
+ * @param string $type log type
+ * @param string $action log action
* @param $title Mixed: Title object or null
* @param $skin Mixed: Skin object or null. If null, we want to use the wiki
* content language, since that will go to the IRC feed.
- * @param $params Array: parameters
+ * @param array $params parameters
* @param $filterWikilinks Boolean: whether to filter wiki links
* @return HTML string
*/
@@ -253,29 +253,6 @@ class LogPage {
} else {
$titleLink = self::getTitleLink( $type, $langObjOrNull, $title, $params );
- if( preg_match( '/^rights\/(rights|autopromote)/', $key ) ) {
- $rightsnone = wfMessage( 'rightsnone' )->inLanguage( $langObj )->text();
-
- if( $skin ) {
- $username = $title->getText();
- foreach ( $params as &$param ) {
- $groupArray = array_map( 'trim', explode( ',', $param ) );
- foreach( $groupArray as &$group ) {
- $group = User::getGroupMember( $group, $username );
- }
- $param = $wgLang->listToText( $groupArray );
- }
- }
-
- if( !isset( $params[0] ) || trim( $params[0] ) == '' ) {
- $params[0] = $rightsnone;
- }
-
- if( !isset( $params[1] ) || trim( $params[1] ) == '' ) {
- $params[1] = $rightsnone;
- }
- }
-
if( count( $params ) == 0 ) {
$rv = wfMessage( $wgLogActions[$key] )->rawParams( $titleLink )->inLanguage( $langObj )->escaped();
} else {
@@ -294,7 +271,7 @@ class LogPage {
$params[2] = isset( $params[2] ) ?
self::formatBlockFlags( $params[2], $langObj ) : '';
// Page protections
- } elseif ( $type == 'protect' && count($params) == 3 ) {
+ } elseif ( $type == 'protect' && count( $params ) == 3 ) {
// Restrictions and expiries
if( $skin ) {
$details .= $wgLang->getDirMark() . htmlspecialchars( " {$params[1]}" );
@@ -350,8 +327,6 @@ class LogPage {
* @return String
*/
protected static function getTitleLink( $type, $lang, $title, &$params ) {
- global $wgContLang, $wgUserrightsInterwikiDelimiter;
-
if( !$lang ) {
return $title->getPrefixedText();
}
@@ -388,20 +363,6 @@ class LogPage {
. Linker::userToolLinks( $id, $title->getText(), false, Linker::TOOL_LINKS_NOBLOCK );
}
break;
- case 'rights':
- $text = $wgContLang->ucfirst( $title->getText() );
- $parts = explode( $wgUserrightsInterwikiDelimiter, $text, 2 );
-
- if ( count( $parts ) == 2 ) {
- $titleLink = WikiMap::foreignUserLink( $parts[1], $parts[0],
- htmlspecialchars( $title->getPrefixedText() ) );
-
- if ( $titleLink !== false ) {
- break;
- }
- }
- $titleLink = Linker::link( Title::makeTitle( NS_USER, $text ) );
- break;
case 'merge':
$titleLink = Linker::link(
$title,
@@ -441,10 +402,10 @@ class LogPage {
/**
* Add a log entry
*
- * @param $action String: one of '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'move_redir'
+ * @param string $action one of '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'move_redir'
* @param $target Title object
- * @param $comment String: description associated
- * @param $params Array: parameters passed later to wfMessage function
+ * @param string $comment description associated
+ * @param array $params parameters passed later to wfMessage function
* @param $doer User object: the user doing the action
*
* @return int log_id of the inserted log entry
@@ -460,6 +421,9 @@ class LogPage {
$comment = '';
}
+ # Trim spaces on user supplied text
+ $comment = trim( $comment );
+
# Truncate for whole multibyte characters.
$comment = $wgContLang->truncate( $comment, 255 );
@@ -549,7 +513,7 @@ class LogPage {
* Convert a comma-delimited list of block log flags
* into a more readable (and translated) form
*
- * @param $flags string Flags to format
+ * @param string $flags Flags to format
* @param $lang Language object to use
* @return String
*/
@@ -570,7 +534,7 @@ class LogPage {
/**
* Translate a block log flag if possible
*
- * @param $flag int Flag to translate
+ * @param int $flag Flag to translate
* @param $lang Language object to use
* @return String
*/
@@ -598,7 +562,6 @@ class LogPage {
return $messages[$flag];
}
-
/**
* Name of the log.
* @return Message
diff --git a/includes/logging/LogPager.php b/includes/logging/LogPager.php
index ea1be8e0..908755ed 100644
--- a/includes/logging/LogPager.php
+++ b/includes/logging/LogPager.php
@@ -35,14 +35,14 @@ class LogPager extends ReverseChronologicalPager {
* Constructor
*
* @param $list LogEventsList
- * @param $types String or Array: log types to show
- * @param $performer String: the user who made the log entries
- * @param $title String|Title: the page title the log entries are for
- * @param $pattern String: do a prefix search rather than an exact title match
- * @param $conds Array: extra conditions for the query
+ * @param string $types or Array: log types to show
+ * @param string $performer the user who made the log entries
+ * @param string|Title $title the page title the log entries are for
+ * @param string $pattern do a prefix search rather than an exact title match
+ * @param array $conds extra conditions for the query
* @param $year Integer: the year to start from
* @param $month Integer: the month to start from
- * @param $tagFilter String: tag
+ * @param string $tagFilter tag
*/
public function __construct( $list, $types = array(), $performer = '', $title = '', $pattern = '',
$conds = array(), $year = false, $month = false, $tagFilter = '' ) {
@@ -71,7 +71,7 @@ class LogPager extends ReverseChronologicalPager {
public function getFilterParams() {
global $wgFilterLogTypes;
$filters = array();
- if( count($this->types) ) {
+ if( count( $this->types ) ) {
return $filters;
}
foreach( $wgFilterLogTypes as $type => $default ) {
@@ -90,18 +90,20 @@ class LogPager extends ReverseChronologicalPager {
* Set the log reader to return only entries of the given type.
* Type restrictions enforced here
*
- * @param $types String or array: Log types ('upload', 'delete', etc);
+ * @param string $types or array: Log types ('upload', 'delete', etc);
* empty string means no restriction
*/
private function limitType( $types ) {
global $wgLogRestrictions;
+
+ $user = $this->getUser();
// If $types is not an array, make it an array
$types = ($types === '') ? array() : (array)$types;
// Don't even show header for private logs; don't recognize it...
$needReindex = false;
foreach ( $types as $type ) {
if( isset( $wgLogRestrictions[$type] )
- && !$this->getUser()->isAllowed($wgLogRestrictions[$type])
+ && !$user->isAllowed( $wgLogRestrictions[$type] )
) {
$needReindex = true;
$types = array_diff( $types, array( $type ) );
@@ -116,21 +118,21 @@ class LogPager extends ReverseChronologicalPager {
// Don't show private logs to unprivileged users.
// Also, only show them upon specific request to avoid suprises.
$audience = $types ? 'user' : 'public';
- $hideLogs = LogEventsList::getExcludeClause( $this->mDb, $audience );
+ $hideLogs = LogEventsList::getExcludeClause( $this->mDb, $audience, $user );
if( $hideLogs !== false ) {
$this->mConds[] = $hideLogs;
}
- if( count($types) ) {
+ if( count( $types ) ) {
$this->mConds['log_type'] = $types;
// Set typeCGI; used in url param for paging
- if( count($types) == 1 ) $this->typeCGI = $types[0];
+ if( count( $types ) == 1 ) $this->typeCGI = $types[0];
}
}
/**
* Set the log reader to return only entries by the given user.
*
- * @param $name String: (In)valid user name
+ * @param string $name (In)valid user name
* @return bool
*/
private function limitPerformer( $name ) {
@@ -138,7 +140,7 @@ class LogPager extends ReverseChronologicalPager {
return false;
}
$usertitle = Title::makeTitleSafe( NS_USER, $name );
- if( is_null($usertitle) ) {
+ if( is_null( $usertitle ) ) {
return false;
}
/* Fetch userid at first, if known, provides awesome query plan afterwards */
@@ -152,9 +154,9 @@ class LogPager extends ReverseChronologicalPager {
// Paranoia: avoid brute force searches (bug 17342)
$user = $this->getUser();
if( !$user->isAllowed( 'deletedhistory' ) ) {
- $this->mConds[] = $this->mDb->bitAnd('log_deleted', LogPage::DELETED_USER) . ' = 0';
+ $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::DELETED_USER ) . ' = 0';
} elseif( !$user->isAllowed( 'suppressrevision' ) ) {
- $this->mConds[] = $this->mDb->bitAnd('log_deleted', LogPage::SUPPRESSED_USER) .
+ $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::SUPPRESSED_USER ) .
' != ' . LogPage::SUPPRESSED_USER;
}
$this->performer = $usertitle->getText();
@@ -165,7 +167,7 @@ class LogPager extends ReverseChronologicalPager {
* Set the log reader to return only entries affecting the given page.
* (For the block and rights logs, this is a user page.)
*
- * @param $page String or Title object: Title name
+ * @param string $page or Title object: Title name
* @param $pattern String
* @return bool
*/
@@ -207,9 +209,9 @@ class LogPager extends ReverseChronologicalPager {
// Paranoia: avoid brute force searches (bug 17342)
$user = $this->getUser();
if( !$user->isAllowed( 'deletedhistory' ) ) {
- $this->mConds[] = $db->bitAnd('log_deleted', LogPage::DELETED_ACTION) . ' = 0';
+ $this->mConds[] = $db->bitAnd( 'log_deleted', LogPage::DELETED_ACTION) . ' = 0';
} elseif( !$user->isAllowed( 'suppressrevision' ) ) {
- $this->mConds[] = $db->bitAnd('log_deleted', LogPage::SUPPRESSED_ACTION) .
+ $this->mConds[] = $db->bitAnd( 'log_deleted', LogPage::SUPPRESSED_ACTION) .
' != ' . LogPage::SUPPRESSED_ACTION;
}
}
@@ -249,10 +251,10 @@ class LogPager extends ReverseChronologicalPager {
# avoids site-breaking filesorts.
} elseif( $this->title || $this->pattern || $this->performer ) {
$index['logging'] = array( 'page_time', 'user_time' );
- if( count($this->types) == 1 ) {
+ if( count( $this->types ) == 1 ) {
$index['logging'][] = 'log_user_type_time';
}
- } elseif( count($this->types) == 1 ) {
+ } elseif( count( $this->types ) == 1 ) {
$index['logging'] = 'type_time';
} else {
$index['logging'] = 'times';
diff --git a/includes/media/BMP.php b/includes/media/BMP.php
index a515c635..46d1b95b 100644
--- a/includes/media/BMP.php
+++ b/includes/media/BMP.php
@@ -62,7 +62,7 @@ class BmpHandler extends BitmapHandler {
return false;
}
$header = fread( $f, 54 );
- fclose($f);
+ fclose( $f );
// Extract binary form of width and height from the header
$w = substr( $header, 18, 4);
diff --git a/includes/media/Bitmap.php b/includes/media/Bitmap.php
index 99ac854b..e2dc68b2 100644
--- a/includes/media/Bitmap.php
+++ b/includes/media/Bitmap.php
@@ -29,7 +29,7 @@
class BitmapHandler extends ImageHandler {
/**
* @param $image File
- * @param $params array Transform parameters. Entries with the keys 'width'
+ * @param array $params Transform parameters. Entries with the keys 'width'
* and 'height' are the respective screen width and height, while the keys
* 'physicalWidth' and 'physicalHeight' indicate the thumbnail dimensions.
* @return bool
@@ -75,7 +75,6 @@ class BitmapHandler extends ImageHandler {
return true;
}
-
/**
* Extracts the width/height if the image will be scaled before rotating
*
@@ -84,8 +83,8 @@ class BitmapHandler extends ImageHandler {
* stored as raw landscape with 90-degress rotation, the resulting size
* will be wider than it is tall.
*
- * @param $params array Parameters as returned by normaliseParams
- * @param $rotation int The rotation angle that will be applied
+ * @param array $params Parameters as returned by normaliseParams
+ * @param int $rotation The rotation angle that will be applied
* @return array ($width, $height) array
*/
public function extractPreRotationDimensions( $params, $rotation ) {
@@ -100,7 +99,6 @@ class BitmapHandler extends ImageHandler {
return array( $width, $height );
}
-
/**
* Function that returns the number of pixels to be thumbnailed.
* Intended for animated GIFs to multiply by the number of frames.
@@ -158,7 +156,6 @@ class BitmapHandler extends ImageHandler {
return $this->getClientScalingThumbnailImage( $image, $scalerParams );
}
-
if ( $scaler == 'client' ) {
# Client-side image scaling, use the source URL
# Using the destination URL in a TRANSFORM_LATER request would be incorrect
@@ -264,7 +261,7 @@ class BitmapHandler extends ImageHandler {
* client side
*
* @param $image File File associated with this thumbnail
- * @param $scalerParams array Array with scaler params
+ * @param array $scalerParams Array with scaler params
* @return ThumbnailImage
*
* @todo fixme: no rotation support
@@ -281,7 +278,7 @@ class BitmapHandler extends ImageHandler {
* Transform an image using ImageMagick
*
* @param $image File File associated with this thumbnail
- * @param $params array Array with scaler params
+ * @param array $params Array with scaler params
*
* @return MediaTransformError Error object if error occurred, false (=no error) otherwise
*/
@@ -341,7 +338,7 @@ class BitmapHandler extends ImageHandler {
$rotation = $this->getRotation( $image );
list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
- $cmd =
+ $cmd =
wfEscapeShellArg( $wgImageMagickConvertCommand ) .
// Specify white background color, will be used for transparent images
// in Internet Explorer/Windows instead of default black.
@@ -380,7 +377,7 @@ class BitmapHandler extends ImageHandler {
* Transform an image using the Imagick PHP extension
*
* @param $image File File associated with this thumbnail
- * @param $params array Array with scaler params
+ * @param array $params Array with scaler params
*
* @return MediaTransformError Error object if error occurred, false (=no error) otherwise
*/
@@ -457,7 +454,7 @@ class BitmapHandler extends ImageHandler {
* Transform an image using a custom command
*
* @param $image File File associated with this thumbnail
- * @param $params array Array with scaler params
+ * @param array $params Array with scaler params
*
* @return MediaTransformError Error object if error occurred, false (=no error) otherwise
*/
@@ -500,8 +497,8 @@ class BitmapHandler extends ImageHandler {
/**
* Get a MediaTransformError with error 'thumbnail_error'
*
- * @param $params array Parameter array as passed to the transform* functions
- * @param $errMsg string Error message
+ * @param array $params Parameter array as passed to the transform* functions
+ * @param string $errMsg Error message
* @return MediaTransformError
*/
public function getMediaTransformError( $params, $errMsg ) {
@@ -513,7 +510,7 @@ class BitmapHandler extends ImageHandler {
* Transform an image using the built in GD library
*
* @param $image File File associated with this thumbnail
- * @param $params array Array with scaler params
+ * @param array $params Array with scaler params
*
* @return MediaTransformError Error object if error occurred, false (=no error) otherwise
*/
@@ -622,8 +619,9 @@ class BitmapHandler extends ImageHandler {
* in a directory, so we're better off escaping and waiting for the bugfix
* to filter down to users.
*
- * @param $path string The file path
- * @param $scene string The scene specification, or false if there is none
+ * @param string $path The file path
+ * @param bool|string $scene The scene specification, or false if there is none
+ * @throws MWException
* @return string
*/
function escapeMagickInput( $path, $scene = false ) {
@@ -653,8 +651,9 @@ class BitmapHandler extends ImageHandler {
* Armour a string against ImageMagick's GetPathComponent(). This is a
* helper function for escapeMagickInput() and escapeMagickOutput().
*
- * @param $path string The file path
- * @param $scene string The scene specification, or false if there is none
+ * @param string $path The file path
+ * @param bool|string $scene The scene specification, or false if there is none
+ * @throws MWException
* @return string
*/
protected function escapeMagickPath( $path, $scene = false ) {
@@ -757,6 +756,55 @@ class BitmapHandler extends ImageHandler {
}
/**
+ * @param $file File
+ * @param array $params Rotate parameters.
+ * 'rotation' clockwise rotation in degrees, allowed are multiples of 90
+ * @since 1.21
+ * @return bool
+ */
+ public function rotate( $file, $params ) {
+ global $wgImageMagickConvertCommand;
+
+ $rotation = ( $params[ 'rotation' ] + $this->getRotation( $file ) ) % 360;
+ $scene = false;
+
+ $scaler = self::getScalerType( null, false );
+ switch ( $scaler ) {
+ case 'im':
+ $cmd = wfEscapeShellArg( $wgImageMagickConvertCommand ) . " " .
+ wfEscapeShellArg( $this->escapeMagickInput( $params[ 'srcPath' ], $scene ) ) .
+ " -rotate -$rotation " .
+ wfEscapeShellArg( $this->escapeMagickOutput( $params[ 'dstPath' ] ) ) . " 2>&1";
+ wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" );
+ wfProfileIn( 'convert' );
+ $retval = 0;
+ $err = wfShellExec( $cmd, $retval, $env );
+ wfProfileOut( 'convert' );
+ if ( $retval !== 0 ) {
+ $this->logErrorForExternalProcess( $retval, $err, $cmd );
+ return new MediaTransformError( 'thumbnail_error', 0, 0, $err );
+ }
+ return false;
+ case 'imext':
+ $im = new Imagick();
+ $im->readImage( $params['srcPath'] );
+ if ( !$im->rotateImage( new ImagickPixel( 'white' ), 360 - $rotation ) ) {
+ return new MediaTransformError( 'thumbnail_error', 0, 0,
+ "Error rotating $rotation degrees" );
+ }
+ $result = $im->writeImage( $params['dstPath'] );
+ if ( !$result ) {
+ return new MediaTransformError( 'thumbnail_error', 0, 0,
+ "Unable to write image to {$params['dstPath']}" );
+ }
+ return false;
+ default:
+ return new MediaTransformError( 'thumbnail_error', 0, 0,
+ "$scaler rotation not implemented" );
+ }
+ }
+
+ /**
* Rerurns whether the file needs to be rendered. Returns true if the
* file requires rotation and we are able to rotate it.
*
diff --git a/includes/media/BitmapMetadataHandler.php b/includes/media/BitmapMetadataHandler.php
index 0a195547..345e7869 100644
--- a/includes/media/BitmapMetadataHandler.php
+++ b/includes/media/BitmapMetadataHandler.php
@@ -47,13 +47,13 @@ class BitmapMetadataHandler {
private $iptcType = 'iptc-no-hash';
/**
- * This does the photoshop image resource app13 block
- * of interest, IPTC-IIM metadata is stored here.
- *
- * Mostly just calls doPSIR and doIPTC
- *
- * @param String $app13 String containing app13 block from jpeg file
- */
+ * This does the photoshop image resource app13 block
+ * of interest, IPTC-IIM metadata is stored here.
+ *
+ * Mostly just calls doPSIR and doIPTC
+ *
+ * @param string $app13 String containing app13 block from jpeg file
+ */
private function doApp13 ( $app13 ) {
try {
$this->iptcType = JpegMetadataExtractor::doPSIR( $app13 );
@@ -69,7 +69,6 @@ class BitmapMetadataHandler {
$this->addMetadata( $iptc, $this->iptcType );
}
-
/**
* Get exif info using exif class.
* Basically what used to be in BitmapHandler::getMetadata().
@@ -91,11 +90,11 @@ class BitmapMetadataHandler {
}
}
/** Add misc metadata. Warning: atm if the metadata category
- * doesn't have a priority, it will be silently discarded.
- *
- * @param Array $metaArray array of metadata values
- * @param string $type type. defaults to other. if two things have the same type they're merged
- */
+ * doesn't have a priority, it will be silently discarded.
+ *
+ * @param array $metaArray array of metadata values
+ * @param string $type type. defaults to other. if two things have the same type they're merged
+ */
function addMetadata ( $metaArray, $type = 'other' ) {
if ( isset( $this->metadata[$type] ) ) {
/* merge with old data */
@@ -106,14 +105,14 @@ class BitmapMetadataHandler {
}
/**
- * Merge together the various types of metadata
- * the different types have different priorites,
- * and are merged in order.
- *
- * This function is generally called by the media handlers' getMetadata()
- *
- * @return Array metadata array
- */
+ * Merge together the various types of metadata
+ * the different types have different priorites,
+ * and are merged in order.
+ *
+ * This function is generally called by the media handlers' getMetadata()
+ *
+ * @return Array metadata array
+ */
function getMetadataArray () {
// this seems a bit ugly... This is all so its merged in right order
// based on the MWG recomendation.
@@ -144,7 +143,7 @@ class BitmapMetadataHandler {
/** Main entry point for jpeg's.
*
- * @param $filename string filename (with full path)
+ * @param string $filename filename (with full path)
* @return array metadata result array.
* @throws MWException on invalid file.
*/
@@ -187,10 +186,10 @@ class BitmapMetadataHandler {
* merge the png various tEXt chunks to that
* are interesting, but for now it only does XMP
*
- * @param $filename String full path to file
+ * @param string $filename full path to file
* @return Array Array for storage in img_metadata.
*/
- static public function PNG ( $filename ) {
+ public static function PNG ( $filename ) {
$showXMP = function_exists( 'xml_parser_create_ns' );
$meta = new self();
@@ -216,10 +215,10 @@ class BitmapMetadataHandler {
* They don't really have native metadata, so just merges together
* XMP and image comment.
*
- * @param $filename string full path to file
+ * @param string $filename full path to file
* @return Array metadata array
*/
- static public function GIF ( $filename ) {
+ public static function GIF ( $filename ) {
$meta = new self();
$baseArray = GIFMetadataExtractor::getMetadata( $filename );
@@ -240,7 +239,7 @@ class BitmapMetadataHandler {
unset( $baseArray['comment'] );
unset( $baseArray['xmp'] );
-
+
$baseArray['metadata'] = $meta->getMetadataArray();
$baseArray['metadata']['_MW_GIF_VERSION'] = GIFMetadataExtractor::VERSION;
return $baseArray;
@@ -257,9 +256,10 @@ class BitmapMetadataHandler {
*
* The various exceptions this throws are caught later.
* @param $filename String
+ * @throws MWException
* @return Array The metadata.
*/
- static public function Tiff ( $filename ) {
+ public static function Tiff ( $filename ) {
if ( file_exists( $filename ) ) {
$byteOrder = self::getTiffByteOrder( $filename );
if ( !$byteOrder ) {
@@ -281,7 +281,7 @@ class BitmapMetadataHandler {
* Read the first 2 bytes of a tiff file to figure out
* Little Endian or Big Endian. Needed for exif stuff.
*
- * @param $filename String The filename
+ * @param string $filename The filename
* @return String 'BE' or 'LE' or false
*/
static function getTiffByteOrder( $filename ) {
@@ -300,6 +300,4 @@ class BitmapMetadataHandler {
}
}
-
-
}
diff --git a/includes/media/DjVu.php b/includes/media/DjVu.php
index 84672e05..0a39a2cf 100644
--- a/includes/media/DjVu.php
+++ b/includes/media/DjVu.php
@@ -183,7 +183,7 @@ class DjVuHandler extends ImageHandler {
if ( $wgDjvuPostProcessor ) {
$cmd .= " | {$wgDjvuPostProcessor}";
}
- $cmd .= ' > ' . wfEscapeShellArg($dstPath) . ') 2>&1';
+ $cmd .= ' > ' . wfEscapeShellArg( $dstPath ) . ') 2>&1';
wfProfileIn( 'ddjvu' );
wfDebug( __METHOD__.": $cmd\n" );
$retval = '';
@@ -194,7 +194,7 @@ class DjVuHandler extends ImageHandler {
if ( $retval != 0 || $removed ) {
wfDebugLog( 'thumbnail',
sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
- wfHostname(), $retval, trim($err), $cmd ) );
+ wfHostname(), $retval, trim( $err ), $cmd ) );
return new MediaTransformError( 'thumbnail_error', $width, $height, $err );
} else {
$params = array(
@@ -228,7 +228,7 @@ class DjVuHandler extends ImageHandler {
* @param $gettext Boolean: DOCUMENT (Default: false)
* @return bool
*/
- function getMetaTree( $image , $gettext = false ) {
+ function getMetaTree( $image, $gettext = false ) {
if ( isset( $image->dejaMetaTree ) ) {
return $image->dejaMetaTree;
}
@@ -247,7 +247,7 @@ class DjVuHandler extends ImageHandler {
$image->djvuTextTree = false;
$tree = new SimpleXMLElement( $metadata );
if( $tree->getName() == 'mw-djvu' ) {
- foreach($tree->children() as $b){
+ foreach( $tree->children() as $b ) {
if( $b->getName() == 'DjVuTxt' ) {
$image->djvuTextTree = $b;
}
@@ -322,7 +322,7 @@ class DjVuHandler extends ImageHandler {
}
}
- function getPageText( $image, $page ){
+ function getPageText( $image, $page ) {
$tree = $this->getMetaTree( $image, true );
if ( !$tree ) {
return false;
diff --git a/includes/media/DjVuImage.php b/includes/media/DjVuImage.php
index 6aef562b..46989668 100644
--- a/includes/media/DjVuImage.php
+++ b/includes/media/DjVuImage.php
@@ -34,11 +34,21 @@
* @ingroup Media
*/
class DjVuImage {
+ /**
+ * Constructor
+ *
+ * @param string $filename The DjVu file name.
+ */
function __construct( $filename ) {
$this->mFilename = $filename;
}
/**
+ * @const DJVUTXT_MEMORY_LIMIT Memory limit for the DjVu description software
+ */
+ const DJVUTXT_MEMORY_LIMIT = 300000;
+
+ /**
* Check if the given file is indeed a valid DjVu image file
* @return bool
*/
@@ -47,7 +57,6 @@ class DjVuImage {
return $info !== false;
}
-
/**
* Return data in the style of getimagesize()
* @return array or false on failure
@@ -56,7 +65,7 @@ class DjVuImage {
$data = $this->getInfo();
if( $data !== false ) {
- $width = $data['width'];
+ $width = $data['width'];
$height = $data['height'];
return array( $width, $height, 'DjVu',
@@ -228,7 +237,7 @@ class DjVuImage {
function retrieveMetaData() {
global $wgDjvuToXML, $wgDjvuDump, $wgDjvuTxt;
wfProfileIn( __METHOD__ );
-
+
if ( isset( $wgDjvuDump ) ) {
# djvudump is faster as of version 3.5
# http://sourceforge.net/tracker/index.php?func=detail&aid=1704049&group_id=32953&atid=406583
@@ -247,12 +256,12 @@ class DjVuImage {
$xml = null;
}
# Text layer
- if ( isset( $wgDjvuTxt ) ) {
+ if ( isset( $wgDjvuTxt ) ) {
wfProfileIn( 'djvutxt' );
- $cmd = wfEscapeShellArg( $wgDjvuTxt ) . ' --detail=page ' . wfEscapeShellArg( $this->mFilename ) ;
- wfDebug( __METHOD__.": $cmd\n" );
+ $cmd = wfEscapeShellArg( $wgDjvuTxt ) . ' --detail=page ' . wfEscapeShellArg( $this->mFilename );
+ wfDebug( __METHOD__ . ": $cmd\n" );
$retval = '';
- $txt = wfShellExec( $cmd, $retval );
+ $txt = wfShellExec( $cmd, $retval, array(), array( 'memory' => self::DJVUTXT_MEMORY_LIMIT ) );
wfProfileOut( 'djvutxt' );
if( $retval == 0) {
# Strip some control characters
@@ -260,7 +269,7 @@ class DjVuImage {
$reg = <<<EOR
/\(page\s[\d-]*\s[\d-]*\s[\d-]*\s[\d-]*\s*"
((?> # Text to match is composed of atoms of either:
- \\\\. # - any escaped character
+ \\\\. # - any escaped character
| # - any character different from " and \
[^"\\\\]+
)*?)
@@ -271,7 +280,7 @@ EOR;
$txt = preg_replace_callback( $reg, array( $this, 'pageTextCallback' ), $txt );
$txt = "<DjVuTxt>\n<HEAD></HEAD>\n<BODY>\n" . $txt . "</BODY>\n</DjVuTxt>\n";
$xml = preg_replace( "/<DjVuXML>/", "<mw-djvu><DjVuXML>", $xml, 1 );
- $xml = $xml . $txt. '</mw-djvu>' ;
+ $xml = $xml . $txt. '</mw-djvu>';
}
}
wfProfileOut( __METHOD__ );
diff --git a/includes/media/Exif.php b/includes/media/Exif.php
index 784a6018..17671808 100644
--- a/includes/media/Exif.php
+++ b/includes/media/Exif.php
@@ -31,15 +31,15 @@
*/
class Exif {
- const BYTE = 1; //!< An 8-bit (1-byte) unsigned integer.
- const ASCII = 2; //!< An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL.
- const SHORT = 3; //!< A 16-bit (2-byte) unsigned integer.
- const LONG = 4; //!< A 32-bit (4-byte) unsigned integer.
- const RATIONAL = 5; //!< Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator
- const UNDEFINED = 7; //!< An 8-bit byte that can take any value depending on the field definition
- const SLONG = 9; //!< A 32-bit (4-byte) signed integer (2's complement notation),
- const SRATIONAL = 10; //!< Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator.
- const IGNORE = -1; // A fake value for things we don't want or don't support.
+ const BYTE = 1; //!< An 8-bit (1-byte) unsigned integer.
+ const ASCII = 2; //!< An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL.
+ const SHORT = 3; //!< A 16-bit (2-byte) unsigned integer.
+ const LONG = 4; //!< A 32-bit (4-byte) unsigned integer.
+ const RATIONAL = 5; //!< Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator
+ const UNDEFINED = 7; //!< An 8-bit byte that can take any value depending on the field definition
+ const SLONG = 9; //!< A 32-bit (4-byte) signed integer (2's complement notation),
+ const SRATIONAL = 10; //!< Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator.
+ const IGNORE = -1; // A fake value for things we don't want or don't support.
//@{
/* @var array
@@ -102,8 +102,9 @@ class Exif {
/**
* Constructor
*
- * @param $file String: filename.
- * @param $byteOrder String Type of byte ordering either 'BE' (Big Endian) or 'LE' (Little Endian). Default ''.
+ * @param string $file filename.
+ * @param string $byteOrder Type of byte ordering either 'BE' (Big Endian) or 'LE' (Little Endian). Default ''.
+ * @throws MWException
* @todo FIXME: The following are broke:
* SubjectArea. Need to test the more obscure tags.
*
@@ -289,8 +290,8 @@ class Exif {
// Only give a warning for b/c, since originally we didn't
// require this. The number of things affected by this is
// rather small.
- wfWarn( 'Exif class did not have byte order specified. '
- . 'Some properties may be decoded incorrectly.' );
+ wfWarn( 'Exif class did not have byte order specified. ' .
+ 'Some properties may be decoded incorrectly.' );
$this->byteOrder = 'BE'; // BE seems about twice as popular as LE in jpg's.
}
@@ -321,7 +322,7 @@ class Exif {
foreach ( array_keys( $this->mRawExifData ) as $section ) {
if ( !in_array( $section, array_keys( $this->mExifTags ) ) ) {
- $this->debug( $section , __FUNCTION__, "'$section' is not a valid Exif section" );
+ $this->debug( $section, __FUNCTION__, "'$section' is not a valid Exif section" );
continue;
}
@@ -345,24 +346,24 @@ class Exif {
}
/**
- * Collapse some fields together.
- * This converts some fields from exif form, to a more friendly form.
- * For example GPS latitude to a single number.
- *
- * The rationale behind this is that we're storing data, not presenting to the user
- * For example a longitude is a single number describing how far away you are from
- * the prime meridian. Well it might be nice to split it up into minutes and seconds
- * for the user, it doesn't really make sense to split a single number into 4 parts
- * for storage. (degrees, minutes, second, direction vs single floating point number).
- *
- * Other things this might do (not really sure if they make sense or not):
- * Dates -> mediawiki date format.
- * convert values that can be in different units to be in one standardized unit.
- *
- * As an alternative approach, some of this could be done in the validate phase
- * if we make up our own types like Exif::DATE.
- */
- function collapseData( ) {
+ * Collapse some fields together.
+ * This converts some fields from exif form, to a more friendly form.
+ * For example GPS latitude to a single number.
+ *
+ * The rationale behind this is that we're storing data, not presenting to the user
+ * For example a longitude is a single number describing how far away you are from
+ * the prime meridian. Well it might be nice to split it up into minutes and seconds
+ * for the user, it doesn't really make sense to split a single number into 4 parts
+ * for storage. (degrees, minutes, second, direction vs single floating point number).
+ *
+ * Other things this might do (not really sure if they make sense or not):
+ * Dates -> mediawiki date format.
+ * convert values that can be in different units to be in one standardized unit.
+ *
+ * As an alternative approach, some of this could be done in the validate phase
+ * if we make up our own types like Exif::DATE.
+ */
+ function collapseData() {
$this->exifGPStoNumber( 'GPSLatitude' );
$this->exifGPStoNumber( 'GPSDestLatitude' );
@@ -386,22 +387,22 @@ class Exif {
$this->exifPropToOrd( 'SceneType' );
$this->charCodeString( 'UserComment' );
- $this->charCodeString( 'GPSProcessingMethod');
+ $this->charCodeString( 'GPSProcessingMethod' );
$this->charCodeString( 'GPSAreaInformation' );
-
+
//ComponentsConfiguration should really be an array instead of a string...
//This turns a string of binary numbers into an array of numbers.
if ( isset ( $this->mFilteredExifData['ComponentsConfiguration'] ) ) {
$val = $this->mFilteredExifData['ComponentsConfiguration'];
$ccVals = array();
- for ($i = 0; $i < strlen($val); $i++) {
- $ccVals[$i] = ord( substr($val, $i, 1) );
+ for ( $i = 0; $i < strlen( $val ); $i++ ) {
+ $ccVals[$i] = ord( substr( $val, $i, 1 ) );
}
$ccVals['_type'] = 'ol'; //this is for formatting later.
$this->mFilteredExifData['ComponentsConfiguration'] = $ccVals;
}
-
+
//GPSVersion(ID) is treated as the wrong type by php exif support.
//Go through each byte turning it into a version string.
//For example: "\x02\x02\x00\x00" -> "2.2.0.0"
@@ -412,11 +413,11 @@ class Exif {
if ( isset ( $this->mFilteredExifData['GPSVersion'] ) ) {
$val = $this->mFilteredExifData['GPSVersion'];
$newVal = '';
- for ($i = 0; $i < strlen($val); $i++) {
+ for ( $i = 0; $i < strlen( $val ); $i++ ) {
if ( $i !== 0 ) {
$newVal .= '.';
}
- $newVal .= ord( substr($val, $i, 1) );
+ $newVal .= ord( substr( $val, $i, 1 ) );
}
if ( $this->byteOrder === 'LE' ) {
// Need to reverse the string
@@ -433,26 +434,25 @@ class Exif {
}
/**
- * Do userComment tags and similar. See pg. 34 of exif standard.
- * basically first 8 bytes is charset, rest is value.
- * This has not been tested on any shift-JIS strings.
- * @param $prop String prop name.
- */
+ * Do userComment tags and similar. See pg. 34 of exif standard.
+ * basically first 8 bytes is charset, rest is value.
+ * This has not been tested on any shift-JIS strings.
+ * @param string $prop prop name.
+ */
private function charCodeString ( $prop ) {
if ( isset( $this->mFilteredExifData[$prop] ) ) {
- if ( strlen($this->mFilteredExifData[$prop]) <= 8 ) {
+ if ( strlen( $this->mFilteredExifData[$prop] ) <= 8 ) {
//invalid. Must be at least 9 bytes long.
- $this->debug( $this->mFilteredExifData[$prop] , __FUNCTION__, false );
- unset($this->mFilteredExifData[$prop]);
+ $this->debug( $this->mFilteredExifData[$prop], __FUNCTION__, false );
+ unset( $this->mFilteredExifData[$prop] );
return;
}
- $charCode = substr( $this->mFilteredExifData[$prop], 0, 8);
- $val = substr( $this->mFilteredExifData[$prop], 8);
-
-
- switch ($charCode) {
+ $charCode = substr( $this->mFilteredExifData[$prop], 0, 8 );
+ $val = substr( $this->mFilteredExifData[$prop], 8 );
+
+ switch ( $charCode ) {
case "\x4A\x49\x53\x00\x00\x00\x00\x00":
//JIS
$charset = "Shift-JIS";
@@ -466,9 +466,9 @@ class Exif {
}
// This could possibly check to see if iconv is really installed
// or if we're using the compatibility wrapper in globalFunctions.php
- if ($charset) {
+ if ( $charset ) {
wfSuppressWarnings();
- $val = iconv($charset, 'UTF-8//IGNORE', $val);
+ $val = iconv( $charset, 'UTF-8//IGNORE', $val );
wfRestoreWarnings();
} else {
// if valid utf-8, assume that, otherwise assume windows-1252
@@ -476,17 +476,17 @@ class Exif {
UtfNormal::quickIsNFCVerify( $valCopy ); //validates $valCopy.
if ( $valCopy !== $val ) {
wfSuppressWarnings();
- $val = iconv('Windows-1252', 'UTF-8//IGNORE', $val);
+ $val = iconv( 'Windows-1252', 'UTF-8//IGNORE', $val );
wfRestoreWarnings();
}
}
-
+
//trim and check to make sure not only whitespace.
- $val = trim($val);
+ $val = trim( $val );
if ( strlen( $val ) === 0 ) {
//only whitespace.
- $this->debug( $this->mFilteredExifData[$prop] , __FUNCTION__, "$prop: Is only whitespace" );
- unset($this->mFilteredExifData[$prop]);
+ $this->debug( $this->mFilteredExifData[$prop], __FUNCTION__, "$prop: Is only whitespace" );
+ unset( $this->mFilteredExifData[$prop] );
return;
}
@@ -495,21 +495,21 @@ class Exif {
}
}
/**
- * Convert an Exif::UNDEFINED from a raw binary string
- * to its value. This is sometimes needed depending on
- * the type of UNDEFINED field
- * @param $prop String name of property
- */
+ * Convert an Exif::UNDEFINED from a raw binary string
+ * to its value. This is sometimes needed depending on
+ * the type of UNDEFINED field
+ * @param string $prop name of property
+ */
private function exifPropToOrd ( $prop ) {
if ( isset( $this->mFilteredExifData[$prop] ) ) {
$this->mFilteredExifData[$prop] = ord( $this->mFilteredExifData[$prop] );
}
}
/**
- * Convert gps in exif form to a single floating point number
- * for example 10 degress 20`40`` S -> -10.34444
- * @param String $prop a gps coordinate exif tag name (like GPSLongitude)
- */
+ * Convert gps in exif form to a single floating point number
+ * for example 10 degress 20`40`` S -> -10.34444
+ * @param string $prop a gps coordinate exif tag name (like GPSLongitude)
+ */
private function exifGPStoNumber ( $prop ) {
$loc =& $this->mFilteredExifData[$prop];
$dir =& $this->mFilteredExifData[$prop . 'Ref'];
@@ -545,7 +545,7 @@ class Exif {
*
* @deprecated since 1.18
*/
- function makeFormattedData( ) {
+ function makeFormattedData() {
wfDeprecated( __METHOD__, '1.18' );
$this->mFormattedExifData = FormatMetadata::getFormattedData(
$this->mFilteredExifData );
@@ -580,7 +580,7 @@ class Exif {
*/
function getFormattedData() {
wfDeprecated( __METHOD__, '1.18' );
- if (!$this->mFormattedExifData) {
+ if ( !$this->mFormattedExifData ) {
$this->makeFormattedData();
}
return $this->mFormattedExifData;
@@ -612,7 +612,7 @@ class Exif {
* @return bool
*/
private function isByte( $in ) {
- if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 255 ) {
+ if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 255 ) {
$this->debug( $in, __FUNCTION__, true );
return true;
} else {
@@ -648,7 +648,7 @@ class Exif {
* @return bool
*/
private function isShort( $in ) {
- if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 65536 ) {
+ if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 65536 ) {
$this->debug( $in, __FUNCTION__, true );
return true;
} else {
@@ -662,7 +662,7 @@ class Exif {
* @return bool
*/
private function isLong( $in ) {
- if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 4294967296 ) {
+ if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 4294967296 ) {
$this->debug( $in, __FUNCTION__, true );
return true;
} else {
@@ -677,7 +677,7 @@ class Exif {
*/
private function isRational( $in ) {
$m = array();
- if ( !is_array( $in ) && @preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
+ if ( !is_array( $in ) && preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
return $this->isLong( $m[1] ) && $this->isLong( $m[2] );
} else {
$this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
@@ -727,8 +727,8 @@ class Exif {
* Validates if a tag has a legal value according to the Exif spec
*
* @private
- * @param $section String: section where tag is located.
- * @param $tag String: the tag to check.
+ * @param string $section section where tag is located.
+ * @param string $tag the tag to check.
* @param $val Mixed: the value of the tag.
* @param $recursive Boolean: true if called recursively for array types.
* @return bool
@@ -748,10 +748,10 @@ class Exif {
return false;
}
if( $count > 1 ) {
- foreach( $val as $v ) {
+ foreach( $val as $v ) {
if( !$this->validate( $section, $tag, $v, true ) ) {
- return false;
- }
+ return false;
+ }
}
return true;
}
@@ -813,13 +813,13 @@ class Exif {
}
if ( $action === true ) {
- wfDebugLog( $this->log, "$class::$fname: accepted: '$in' (type: $type)\n");
+ wfDebugLog( $this->log, "$class::$fname: accepted: '$in' (type: $type)\n" );
} elseif ( $action === false ) {
- wfDebugLog( $this->log, "$class::$fname: rejected: '$in' (type: $type)\n");
+ wfDebugLog( $this->log, "$class::$fname: rejected: '$in' (type: $type)\n" );
} elseif ( $action === null ) {
- wfDebugLog( $this->log, "$class::$fname: input was: '$in' (type: $type)\n");
+ wfDebugLog( $this->log, "$class::$fname: input was: '$in' (type: $type)\n" );
} else {
- wfDebugLog( $this->log, "$class::$fname: $action (type: $type; content: '$in')\n");
+ wfDebugLog( $this->log, "$class::$fname: $action (type: $type; content: '$in')\n" );
}
}
@@ -828,7 +828,7 @@ class Exif {
*
* @private
*
- * @param $fname String: the name of the function calling this function
+ * @param string $fname the name of the function calling this function
* @param $io Boolean: Specify whether we're beginning or ending
*/
private function debugFile( $fname, $io ) {
@@ -843,4 +843,3 @@ class Exif {
}
}
}
-
diff --git a/includes/media/ExifBitmap.php b/includes/media/ExifBitmap.php
index 34a1f511..1671ab25 100644
--- a/includes/media/ExifBitmap.php
+++ b/includes/media/ExifBitmap.php
@@ -34,8 +34,8 @@ class ExifBitmapHandler extends BitmapHandler {
function convertMetadataVersion( $metadata, $version = 1 ) {
// basically flattens arrays.
- $version = explode(';', $version, 2);
- $version = intval($version[0]);
+ $version = explode( ';', $version, 2 );
+ $version = intval( $version[0] );
if ( $version < 1 || $version >= 2 ) {
return $metadata;
}
@@ -56,7 +56,7 @@ class ExifBitmapHandler extends BitmapHandler {
&& is_array( $metadata['Software'][0])
&& isset( $metadata['Software'][0][0] )
&& isset( $metadata['Software'][0][1])
- ) {
+ ) {
$metadata['Software'] = $metadata['Software'][0][0] . ' (Version '
. $metadata['Software'][0][1] . ')';
}
@@ -86,7 +86,7 @@ class ExifBitmapHandler extends BitmapHandler {
if ( $metadata === self::OLD_BROKEN_FILE ) {
# Old special value indicating that there is no EXIF data in the file.
# or that there was an error well extracting the metadata.
- wfDebug( __METHOD__ . ": back-compat version\n");
+ wfDebug( __METHOD__ . ": back-compat version\n" );
return self::METADATA_COMPATIBLE;
}
if ( $metadata === self::BROKEN_FILE ) {
@@ -102,11 +102,11 @@ class ExifBitmapHandler extends BitmapHandler {
$exif['MEDIAWIKI_EXIF_VERSION'] == 1 )
{
//back-compatible but old
- wfDebug( __METHOD__.": back-compat version\n" );
+ wfDebug( __METHOD__ . ": back-compat version\n" );
return self::METADATA_COMPATIBLE;
}
# Wrong (non-compatible) version
- wfDebug( __METHOD__.": wrong version\n" );
+ wfDebug( __METHOD__ . ": wrong version\n" );
return self::METADATA_BAD;
}
return self::METADATA_GOOD;
@@ -163,7 +163,7 @@ class ExifBitmapHandler extends BitmapHandler {
$rotation = 0;
}
- if ($rotation == 90 || $rotation == 270) {
+ if ( $rotation == 90 || $rotation == 270 ) {
$width = $gis[0];
$gis[0] = $gis[1];
$gis[1] = $width;
@@ -225,4 +225,3 @@ class ExifBitmapHandler extends BitmapHandler {
return 0;
}
}
-
diff --git a/includes/media/FormatMetadata.php b/includes/media/FormatMetadata.php
index 843c1fa2..1a7d7723 100644
--- a/includes/media/FormatMetadata.php
+++ b/includes/media/FormatMetadata.php
@@ -1,6 +1,6 @@
<?php
/**
- * Formating of image metadata values into human readable form.
+ * Formatting of image metadata values into human readable form.
*
* 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
@@ -25,7 +25,6 @@
* @file
*/
-
/**
* Format Image metadata values into a human readable form.
*
@@ -53,7 +52,7 @@ class FormatMetadata {
* value which most of the time are plain integers. This function
* formats Exif (and other metadata) values into human readable form.
*
- * @param $tags Array: the Exif data to format ( as returned by
+ * @param array $tags the Exif data to format ( as returned by
* Exif::getFilteredData() or BitmapMetadataHandler )
* @return array
*/
@@ -80,20 +79,20 @@ class FormatMetadata {
}
//This is done differently as the tag is an array.
- if ($tag == 'GPSTimeStamp' && count($vals) === 3) {
+ if ( $tag == 'GPSTimeStamp' && count( $vals ) === 3) {
//hour min sec array
- $h = explode('/', $vals[0]);
- $m = explode('/', $vals[1]);
- $s = explode('/', $vals[2]);
+ $h = explode( '/', $vals[0] );
+ $m = explode( '/', $vals[1] );
+ $s = explode( '/', $vals[2] );
// this should already be validated
// when loaded from file, but it could
// come from a foreign repo, so be
// paranoid.
- if ( !isset($h[1])
- || !isset($m[1])
- || !isset($s[1])
+ if ( !isset( $h[1] )
+ || !isset( $m[1] )
+ || !isset( $s[1] )
|| $h[1] == 0
|| $m[1] == 0
|| $s[1] == 0
@@ -322,7 +321,7 @@ class FormatMetadata {
if ( $subTag != 'fired' && $subValue == 0 ) {
continue;
}
- $fullTag = $tag . '-' . $subTag ;
+ $fullTag = $tag . '-' . $subTag;
$flashMsgs[] = self::msg( $fullTag, $subValue );
}
$val = $wgLang->commaList( $flashMsgs );
@@ -526,7 +525,6 @@ class FormatMetadata {
}
break;
-
case 'GPSTrackRef':
case 'GPSImgDirectionRef':
case 'GPSDestBearingRef':
@@ -631,7 +629,7 @@ class FormatMetadata {
case 'MaxApertureValue':
if ( strpos( $val, '/' ) !== false ) {
// need to expand this earlier to calculate fNumber
- list($n, $d) = explode('/', $val);
+ list( $n, $d ) = explode( '/', $val );
if ( is_numeric( $n ) && is_numeric( $d ) ) {
$val = $n / $d;
}
@@ -809,7 +807,7 @@ class FormatMetadata {
case 'LanguageCode':
$lang = Language::fetchLanguageName( strtolower( $val ), $wgLang->getCode() );
- if ($lang) {
+ if ( $lang ) {
$val = htmlspecialchars( $lang );
} else {
$val = htmlspecialchars( $val );
@@ -829,20 +827,20 @@ class FormatMetadata {
}
/**
- * A function to collapse multivalued tags into a single value.
- * This turns an array of (for example) authors into a bulleted list.
- *
- * This is public on the basis it might be useful outside of this class.
- *
- * @param $vals Array array of values
- * @param $type String Type of array (either lang, ul, ol).
- * lang = language assoc array with keys being the lang code
- * ul = unordered list, ol = ordered list
- * type can also come from the '_type' member of $vals.
- * @param $noHtml Boolean If to avoid returning anything resembling
- * html. (Ugly hack for backwards compatibility with old mediawiki).
- * @return String single value (in wiki-syntax).
- */
+ * A function to collapse multivalued tags into a single value.
+ * This turns an array of (for example) authors into a bulleted list.
+ *
+ * This is public on the basis it might be useful outside of this class.
+ *
+ * @param array $vals array of values
+ * @param string $type Type of array (either lang, ul, ol).
+ * lang = language assoc array with keys being the lang code
+ * ul = unordered list, ol = ordered list
+ * type can also come from the '_type' member of $vals.
+ * @param $noHtml Boolean If to avoid returning anything resembling
+ * html. (Ugly hack for backwards compatibility with old mediawiki).
+ * @return String single value (in wiki-syntax).
+ */
public static function flattenArray( $vals, $type = 'ul', $noHtml = false ) {
if ( isset( $vals['_type'] ) ) {
$type = $vals['_type'];
@@ -850,7 +848,7 @@ class FormatMetadata {
}
if ( !is_array( $vals ) ) {
- return $vals; // do nothing if not an array;
+ return $vals; // do nothing if not an array;
}
elseif ( count( $vals ) === 1 && $type !== 'lang' ) {
return $vals[0];
@@ -899,7 +897,7 @@ class FormatMetadata {
}
$content .= self::langItem(
$vals[$cLang], $cLang,
- $isDefault, $noHtml );
+ $isDefault, $noHtml );
unset( $vals[$cLang] );
}
@@ -915,8 +913,8 @@ class FormatMetadata {
}
if ( $defaultItem !== false ) {
$content = self::langItem( $defaultItem,
- $defaultLang, true, $noHtml )
- . $content;
+ $defaultLang, true, $noHtml ) .
+ $content;
}
if ( $noHtml ) {
return $content;
@@ -941,8 +939,8 @@ class FormatMetadata {
/** Helper function for creating lists of translations.
*
- * @param $value String value (this is not escaped)
- * @param $lang String lang code of item or false
+ * @param string $value value (this is not escaped)
+ * @param string $lang lang code of item or false
* @param $default Boolean if it is default value.
* @param $noHtml Boolean If to avoid html (for back-compat)
* @throws MWException
@@ -951,8 +949,8 @@ class FormatMetadata {
*/
private static function langItem( $value, $lang, $default = false, $noHtml = false ) {
if ( $lang === false && $default === false) {
- throw new MWException('$lang and $default cannot both '
- . 'be false.');
+ throw new MWException( '$lang and $default cannot both '
+ . 'be false.' );
}
if ( $noHtml ) {
@@ -1008,16 +1006,16 @@ class FormatMetadata {
*
* @private
*
- * @param $tag String: the tag name to pass on
- * @param $val String: the value of the tag
- * @param $arg String: an argument to pass ($1)
- * @param $arg2 String: a 2nd argument to pass ($2)
+ * @param string $tag the tag name to pass on
+ * @param string $val the value of the tag
+ * @param string $arg an argument to pass ($1)
+ * @param string $arg2 a 2nd argument to pass ($2)
* @return string A wfMessage of "exif-$tag-$val" in lower case
*/
static function msg( $tag, $val, $arg = null, $arg2 = null ) {
global $wgContLang;
- if ($val === '')
+ if ( $val === '' )
$val = 'value';
return wfMessage( $wgContLang->lc( "exif-$tag-$val" ), $arg, $arg2 )->text();
}
@@ -1033,10 +1031,10 @@ class FormatMetadata {
static function formatNum( $num, $round = false ) {
global $wgLang;
$m = array();
- if( is_array($num) ) {
+ if( is_array( $num ) ) {
$out = array();
foreach( $num as $number ) {
- $out[] = self::formatNum($number);
+ $out[] = self::formatNum( $number );
}
return $wgLang->commaList( $out );
}
@@ -1117,16 +1115,16 @@ class FormatMetadata {
* Note, leading 0's are significant, so this is
* a string, not an int.
*
- * @param $val String: The 8 digit news code.
+ * @param string $val The 8 digit news code.
* @return string The human readable form
*/
- static private function convertNewsCode( $val ) {
+ private static function convertNewsCode( $val ) {
if ( !preg_match( '/^\d{8}$/D', $val ) ) {
// Not a valid news code.
return $val;
}
$cat = '';
- switch( substr( $val , 0, 2 ) ) {
+ switch( substr( $val, 0, 2 ) ) {
case '01':
$cat = 'ace';
break;
@@ -1190,8 +1188,8 @@ class FormatMetadata {
* Format a coordinate value, convert numbers from floating point
* into degree minute second representation.
*
- * @param $coord int degrees, minutes and seconds
- * @param $type String: latitude or longitude (for if its a NWS or E)
+ * @param int $coord degrees, minutes and seconds
+ * @param string $type latitude or longitude (for if its a NWS or E)
* @return mixed A floating point number or whatever we were fed
*/
static function formatCoords( $coord, $type ) {
@@ -1226,7 +1224,7 @@ class FormatMetadata {
/**
* Format the contact info field into a single value.
*
- * @param $vals Array array with fields of the ContactInfo
+ * @param array $vals array with fields of the ContactInfo
* struct defined in the IPTC4XMP spec. Or potentially
* an array with one element that is a free form text
* value from the older iptc iim 1:118 prop.
@@ -1238,7 +1236,7 @@ class FormatMetadata {
* @return String of html-ish looking wikitext
*/
public static function collapseContactInfo( $vals ) {
- if( ! ( isset( $vals['CiAdrExtadr'] )
+ if( !( isset( $vals['CiAdrExtadr'] )
|| isset( $vals['CiAdrCity'] )
|| isset( $vals['CiAdrCtry'] )
|| isset( $vals['CiEmailWork'] )
@@ -1353,7 +1351,7 @@ class FormatMetadata {
*
* @deprecated since 1.18
*
-**/
+ */
class FormatExif {
var $meta;
@@ -1361,7 +1359,7 @@ class FormatExif {
* @param $meta array
*/
function FormatExif( $meta ) {
- wfDeprecated(__METHOD__);
+ wfDeprecated( __METHOD__, '1.18' );
$this->meta = $meta;
}
diff --git a/includes/media/GIF.php b/includes/media/GIF.php
index 84b9b8ca..2e532feb 100644
--- a/includes/media/GIF.php
+++ b/includes/media/GIF.php
@@ -29,7 +29,7 @@
class GIFHandler extends BitmapHandler {
const BROKEN_FILE = '0'; // value to store in img_metadata if error extracting metadata.
-
+
function getMetadata( $image, $filename ) {
try {
$parsedGIFMetadata = BitmapMetadataHandler::GIF( $filename );
@@ -39,7 +39,7 @@ class GIFHandler extends BitmapHandler {
return self::BROKEN_FILE;
}
- return serialize($parsedGIFMetadata);
+ return serialize( $parsedGIFMetadata );
}
/**
@@ -53,7 +53,7 @@ class GIFHandler extends BitmapHandler {
return false;
}
$meta = unserialize( $meta );
- if ( !isset( $meta['metadata'] ) || count( $meta['metadata'] ) <= 1 ) {
+ if ( !isset( $meta['metadata'] ) || count( $meta['metadata'] ) <= 1 ) {
return false;
}
@@ -85,7 +85,7 @@ class GIFHandler extends BitmapHandler {
function isAnimatedImage( $image ) {
$ser = $image->getMetadata();
if ( $ser ) {
- $metadata = unserialize($ser);
+ $metadata = unserialize( $ser );
if( $metadata['frameCount'] > 1 ) {
return true;
}
@@ -119,13 +119,13 @@ class GIFHandler extends BitmapHandler {
wfRestoreWarnings();
if ( !$data || !is_array( $data ) ) {
- wfDebug(__METHOD__ . ' invalid GIF metadata' );
+ wfDebug( __METHOD__ . ' invalid GIF metadata' );
return self::METADATA_BAD;
}
if ( !isset( $data['metadata']['_MW_GIF_VERSION'] )
|| $data['metadata']['_MW_GIF_VERSION'] != GIFMetadataExtractor::VERSION ) {
- wfDebug(__METHOD__ . ' old but compatible GIF metadata' );
+ wfDebug( __METHOD__ . ' old but compatible GIF metadata' );
return self::METADATA_COMPATIBLE;
}
return self::METADATA_GOOD;
@@ -141,29 +141,29 @@ class GIFHandler extends BitmapHandler {
$original = parent::getLongDesc( $image );
wfSuppressWarnings();
- $metadata = unserialize($image->getMetadata());
+ $metadata = unserialize( $image->getMetadata() );
wfRestoreWarnings();
-
- if (!$metadata || $metadata['frameCount'] <= 1) {
+
+ if ( !$metadata || $metadata['frameCount'] <= 1 ) {
return $original;
}
/* Preserve original image info string, but strip the last char ')' so we can add even more */
$info = array();
$info[] = $original;
-
+
if ( $metadata['looped'] ) {
$info[] = wfMessage( 'file-info-gif-looped' )->parse();
}
-
+
if ( $metadata['frameCount'] > 1 ) {
$info[] = wfMessage( 'file-info-gif-frames' )->numParams( $metadata['frameCount'] )->parse();
}
-
+
if ( $metadata['duration'] ) {
$info[] = $wgLang->formatTimePeriod( $metadata['duration'] );
}
-
+
return $wgLang->commaList( $info );
}
}
diff --git a/includes/media/GIFMetadataExtractor.php b/includes/media/GIFMetadataExtractor.php
index 5fc5c1a7..6a4e753d 100644
--- a/includes/media/GIFMetadataExtractor.php
+++ b/includes/media/GIFMetadataExtractor.php
@@ -49,16 +49,16 @@ class GIFMetadataExtractor {
* @return array
*/
static function getMetadata( $filename ) {
- self::$gif_frame_sep = pack( "C", ord("," ) );
- self::$gif_extension_sep = pack( "C", ord("!" ) );
- self::$gif_term = pack( "C", ord(";" ) );
+ self::$gif_frame_sep = pack( "C", ord( "," ) );
+ self::$gif_extension_sep = pack( "C", ord( "!" ) );
+ self::$gif_term = pack( "C", ord( ";" ) );
$frameCount = 0;
$duration = 0.0;
$isLooped = false;
$xmp = "";
$comment = array();
-
+
if ( !$filename ) {
throw new Exception( "No file name specified" );
} elseif ( !file_exists( $filename ) || is_dir( $filename ) ) {
@@ -73,7 +73,7 @@ class GIFMetadataExtractor {
// Check for the GIF header
$buf = fread( $fh, 6 );
- if ( !($buf == 'GIF87a' || $buf == 'GIF89a') ) {
+ if ( !( $buf == 'GIF87a' || $buf == 'GIF89a' ) ) {
throw new Exception( "Not a valid GIF file; header: $buf" );
}
@@ -93,7 +93,7 @@ class GIFMetadataExtractor {
while( !feof( $fh ) ) {
$buf = fread( $fh, 1 );
- if ($buf == self::$gif_frame_sep) {
+ if ( $buf == self::$gif_frame_sep ) {
// Found a frame
$frameCount++;
@@ -107,14 +107,14 @@ class GIFMetadataExtractor {
## Read GCT
self::readGCT( $fh, $bpp );
fread( $fh, 1 );
- self::skipBlock( $fh );
+ self::skipBlock( $fh );
} elseif ( $buf == self::$gif_extension_sep ) {
$buf = fread( $fh, 1 );
if ( strlen( $buf ) < 1 ) throw new Exception( "Ran out of input" );
$extension_code = unpack( 'C', $buf );
$extension_code = $extension_code[1];
- if ($extension_code == 0xF9) {
+ if ( $extension_code == 0xF9 ) {
// Graphics Control Extension.
fread( $fh, 1 ); // Block size
@@ -132,10 +132,10 @@ class GIFMetadataExtractor {
if ( strlen( $term ) < 1 ) throw new Exception( "Ran out of input" );
$term = unpack( 'C', $term );
$term = $term[1];
- if ($term != 0 ) {
+ if ( $term != 0 ) {
throw new Exception( "Malformed Graphics Control Extension block" );
}
- } elseif ($extension_code == 0xFE) {
+ } elseif ( $extension_code == 0xFE ) {
// Comment block(s).
$data = self::readBlock( $fh );
if ( $data === "" ) {
@@ -164,7 +164,7 @@ class GIFMetadataExtractor {
// is identical to the last, only extract once.
$comment[] = $data;
}
- } elseif ($extension_code == 0xFF) {
+ } elseif ( $extension_code == 0xFF ) {
// Application extension (Netscape info about the animated gif)
// or XMP (or theoretically any other type of extension block)
$blockLength = fread( $fh, 1 );
@@ -173,7 +173,7 @@ class GIFMetadataExtractor {
$blockLength = $blockLength[1];
$data = fread( $fh, $blockLength );
- if ($blockLength != 11 ) {
+ if ( $blockLength != 11 ) {
wfDebug( __METHOD__ . ' GIF application block with wrong length' );
fseek( $fh, -($blockLength + 1), SEEK_CUR );
self::skipBlock( $fh );
@@ -182,23 +182,22 @@ class GIFMetadataExtractor {
// NETSCAPE2.0 (application name for animated gif)
if ( $data == 'NETSCAPE2.0' ) {
-
$data = fread( $fh, 2 ); // Block length and introduction, should be 03 01
- if ($data != "\x03\x01") {
+ if ( $data != "\x03\x01" ) {
throw new Exception( "Expected \x03\x01, got $data" );
}
-
+
// Unsigned little-endian integer, loop count or zero for "forever"
$loopData = fread( $fh, 2 );
if ( strlen( $loopData ) < 2 ) throw new Exception( "Ran out of input" );
$loopData = unpack( 'v', $loopData );
$loopCount = $loopData[1];
-
- if ($loopCount != 1) {
+
+ if ( $loopCount != 1 ) {
$isLooped = true;
}
-
+
// Read out terminator byte
fread( $fh, 1 );
} elseif ( $data == 'XMP DataXMP' ) {
@@ -232,7 +231,7 @@ class GIFMetadataExtractor {
if ( strlen( $buf ) < 1 ) throw new Exception( "Ran out of input" );
$byte = unpack( 'C', $buf );
$byte = $byte[1];
- throw new Exception( "At position: ".ftell($fh). ", Unknown byte ".$byte );
+ throw new Exception( "At position: " . ftell( $fh ) . ", Unknown byte " . $byte );
}
}
@@ -252,7 +251,7 @@ class GIFMetadataExtractor {
*/
static function readGCT( $fh, $bpp ) {
if ( $bpp > 0 ) {
- for( $i=1; $i<=pow( 2, $bpp ); ++$i ) {
+ for( $i = 1; $i <= pow( 2, $bpp ); ++$i ) {
fread( $fh, 3 );
}
}
@@ -260,6 +259,7 @@ class GIFMetadataExtractor {
/**
* @param $data
+ * @throws Exception
* @return int
*/
static function decodeBPP( $data ) {
@@ -276,7 +276,7 @@ class GIFMetadataExtractor {
/**
* @param $fh
- * @return
+ * @throws Exception
*/
static function skipBlock( $fh ) {
while ( !feof( $fh ) ) {
@@ -284,12 +284,13 @@ class GIFMetadataExtractor {
if ( strlen( $buf ) < 1 ) throw new Exception( "Ran out of input" );
$block_len = unpack( 'C', $buf );
$block_len = $block_len[1];
- if ($block_len == 0) {
+ if ( $block_len == 0 ) {
return;
}
fread( $fh, $block_len );
}
}
+
/**
* Read a block. In the GIF format, a block is made up of
* several sub-blocks. Each sub block starts with one byte
@@ -301,6 +302,7 @@ class GIFMetadataExtractor {
* sub-blocks in the returned value. Normally this is false,
* except XMP is weird and does a hack where you need to keep
* these length bytes.
+ * @throws Exception
* @return string The data.
*/
static function readBlock( $fh, $includeLengths = false ) {
diff --git a/includes/media/IPTC.php b/includes/media/IPTC.php
index 8fd3552f..4191cde0 100644
--- a/includes/media/IPTC.php
+++ b/includes/media/IPTC.php
@@ -29,27 +29,27 @@
class IPTC {
/**
- * This takes the results of iptcparse() and puts it into a
- * form that can be handled by mediawiki. Generally called from
- * BitmapMetadataHandler::doApp13.
- *
- * @see http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf
- *
- * @param $rawData String app13 block from jpeg containing iptc/iim data
- * @return Array iptc metadata array
- */
+ * This takes the results of iptcparse() and puts it into a
+ * form that can be handled by mediawiki. Generally called from
+ * BitmapMetadataHandler::doApp13.
+ *
+ * @see http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf
+ *
+ * @param string $rawData app13 block from jpeg containing iptc/iim data
+ * @return Array iptc metadata array
+ */
static function parse( $rawData ) {
$parsed = iptcparse( $rawData );
$data = Array();
- if (!is_array($parsed)) {
+ if ( !is_array( $parsed ) ) {
return $data;
}
$c = '';
//charset info contained in tag 1:90.
- if (isset($parsed['1#090']) && isset($parsed['1#090'][0])) {
- $c = self::getCharset($parsed['1#090'][0]);
- if ($c === false) {
+ if ( isset( $parsed['1#090'] ) && isset( $parsed['1#090'][0] ) ) {
+ $c = self::getCharset( $parsed['1#090'][0] );
+ if ( $c === false ) {
//Unknown charset. refuse to parse.
//note: There is a different between
//unknown and no charset specified.
@@ -59,8 +59,8 @@ class IPTC {
}
foreach ( $parsed as $tag => $val ) {
- if ( isset( $val[0] ) && trim($val[0]) == '' ) {
- wfDebugLog('iptc', "IPTC tag $tag had only whitespace as its value.");
+ if ( isset( $val[0] ) && trim( $val[0] ) == '' ) {
+ wfDebugLog( 'iptc', "IPTC tag $tag had only whitespace as its value." );
continue;
}
switch( $tag ) {
@@ -175,7 +175,7 @@ class IPTC {
if ( isset( $parsed['2#070'] ) ) {
//if a version is set for the software.
$softwareVersion = self::convIPTC( $parsed['2#070'], $c );
- unset($parsed['2#070']);
+ unset( $parsed['2#070'] );
$data['Software'] = array( array( $software[0], $softwareVersion[0] ) );
} else {
$data['Software'] = $software;
@@ -198,7 +198,7 @@ class IPTC {
/* original transmission ref.
* "A code representing the location of original transmission ac-
* cording to practises of the provider."
- */
+ */
$data['OriginalTransmissionRef'] = self::convIPTC( $val, $c );
break;
case '2#118': /*contact*/
@@ -227,8 +227,8 @@ class IPTC {
} else {
$time = Array();
}
- $timestamp = self::timeHelper( $val, $time, $c );
- if ($timestamp) {
+ $timestamp = self::timeHelper( $val, $time, $c );
+ if ( $timestamp ) {
$data['DateTimeOriginal'] = $timestamp;
}
break;
@@ -241,8 +241,8 @@ class IPTC {
} else {
$time = Array();
}
- $timestamp = self::timeHelper( $val, $time, $c );
- if ($timestamp) {
+ $timestamp = self::timeHelper( $val, $time, $c );
+ if ( $timestamp ) {
$data['DateTimeDigitized'] = $timestamp;
}
break;
@@ -254,8 +254,8 @@ class IPTC {
} else {
$time = Array();
}
- $timestamp = self::timeHelper( $val, $time, $c );
- if ($timestamp) {
+ $timestamp = self::timeHelper( $val, $time, $c );
+ if ( $timestamp ) {
$data['DateTimeReleased'] = $timestamp;
}
break;
@@ -267,8 +267,8 @@ class IPTC {
} else {
$time = Array();
}
- $timestamp = self::timeHelper( $val, $time, $c );
- if ($timestamp) {
+ $timestamp = self::timeHelper( $val, $time, $c );
+ if ( $timestamp ) {
$data['DateTimeExpires'] = $timestamp;
}
break;
@@ -313,7 +313,7 @@ class IPTC {
// describing the subject matter of the content.
$codes = self::convIPTC( $val, $c );
foreach ( $codes as $ic ) {
- $fields = explode(':', $ic, 3 );
+ $fields = explode( ':', $ic, 3 );
if ( count( $fields ) < 2 ||
$fields[0] !== 'IPTC' )
@@ -350,43 +350,43 @@ class IPTC {
}
/**
- * Convert an iptc date and time tags into the exif format
- *
- * @todo Potentially this should also capture the timezone offset.
- * @param Array $date The date tag
- * @param Array $time The time tag
- * @param $c
- * @return String Date in exif format.
- */
+ * Convert an iptc date and time tags into the exif format
+ *
+ * @todo Potentially this should also capture the timezone offset.
+ * @param array $date The date tag
+ * @param array $time The time tag
+ * @param $c
+ * @return String Date in exif format.
+ */
private static function timeHelper( $date, $time, $c ) {
if ( count( $date ) === 1 ) {
//the standard says this should always be 1
//just double checking.
- list($date) = self::convIPTC( $date, $c );
+ list( $date ) = self::convIPTC( $date, $c );
} else {
return null;
}
if ( count( $time ) === 1 ) {
- list($time) = self::convIPTC( $time, $c );
+ list( $time ) = self::convIPTC( $time, $c );
$dateOnly = false;
} else {
$time = '000000+0000'; //placeholder
$dateOnly = true;
}
- if ( ! ( preg_match('/\d\d\d\d\d\d[-+]\d\d\d\d/', $time)
- && preg_match('/\d\d\d\d\d\d\d\d/', $date)
- && substr($date, 0, 4) !== '0000'
- && substr($date, 4, 2) !== '00'
- && substr($date, 6, 2) !== '00'
- ) ) {
+ if ( !( preg_match( '/\d\d\d\d\d\d[-+]\d\d\d\d/', $time )
+ && preg_match( '/\d\d\d\d\d\d\d\d/', $date )
+ && substr( $date, 0, 4 ) !== '0000'
+ && substr( $date, 4, 2 ) !== '00'
+ && substr( $date, 6, 2 ) !== '00'
+ ) ) {
//something wrong.
// Note, this rejects some valid dates according to iptc spec
// for example: the date 00000400 means the photo was taken in
// April, but the year and day is unknown. We don't process these
// types of incomplete dates atm.
- wfDebugLog( 'iptc', "IPTC: invalid time ( $time ) or date ( $date )");
+ wfDebugLog( 'iptc', "IPTC: invalid time ( $time ) or date ( $date )" );
return null;
}
@@ -417,15 +417,15 @@ class IPTC {
}
/**
- * Helper function to convert charset for iptc values.
- * @param $data string|array The iptc string
- * @param $charset String: The charset
+ * Helper function to convert charset for iptc values.
+ * @param string|array $data The iptc string
+ * @param string $charset The charset
*
* @return string|array
- */
+ */
private static function convIPTC ( $data, $charset ) {
if ( is_array( $data ) ) {
- foreach ($data as &$val) {
+ foreach ( $data as &$val ) {
$val = self::convIPTCHelper( $val, $charset );
}
} else {
@@ -435,27 +435,27 @@ class IPTC {
return $data;
}
/**
- * Helper function of a helper function to convert charset for iptc values.
- * @param $data Mixed String or Array: The iptc string
- * @param $charset String: The charset
- *
- * @return string
- */
+ * Helper function of a helper function to convert charset for iptc values.
+ * @param $data Mixed String or Array: The iptc string
+ * @param string $charset The charset
+ *
+ * @return string
+ */
private static function convIPTCHelper ( $data, $charset ) {
if ( $charset ) {
wfSuppressWarnings();
- $data = iconv($charset, "UTF-8//IGNORE", $data);
+ $data = iconv( $charset, "UTF-8//IGNORE", $data );
wfRestoreWarnings();
- if ($data === false) {
+ if ( $data === false ) {
$data = "";
- wfDebugLog('iptc', __METHOD__ . " Error converting iptc data charset $charset to utf-8");
+ wfDebugLog( 'iptc', __METHOD__ . " Error converting iptc data charset $charset to utf-8" );
}
} else {
//treat as utf-8 if is valid utf-8. otherwise pretend its windows-1252
// most of the time if there is no 1:90 tag, it is either ascii, latin1, or utf-8
$oldData = $data;
UtfNormal::quickIsNFCVerify( $data ); //make $data valid utf-8
- if ($data === $oldData) {
+ if ( $data === $oldData ) {
return $data; //if validation didn't change $data
} else {
return self::convIPTCHelper( $oldData, 'Windows-1252' );
@@ -465,14 +465,14 @@ class IPTC {
}
/**
- * take the value of 1:90 tag and returns a charset
- * @param String $tag 1:90 tag.
- * @return string charset name or "?"
- * Warning, this function does not (and is not intended to) detect
- * all iso 2022 escape codes. In practise, the code for utf-8 is the
- * only code that seems to have wide use. It does detect that code.
- */
- static function getCharset($tag) {
+ * take the value of 1:90 tag and returns a charset
+ * @param string $tag 1:90 tag.
+ * @return string charset name or "?"
+ * Warning, this function does not (and is not intended to) detect
+ * all iso 2022 escape codes. In practise, the code for utf-8 is the
+ * only code that seems to have wide use. It does detect that code.
+ */
+ static function getCharset( $tag ) {
//According to iim standard, charset is defined by the tag 1:90.
//in which there are iso 2022 escape sequences to specify the character set.
@@ -530,7 +530,7 @@ class IPTC {
case "\x1b(K":
$c = "ISO646-DE";
break;
- case "\x1b(N": //crylic
+ case "\x1b(N": //crylic
$c = "ISO_5427";
break;
case "\x1b(`": //iso646-NO
@@ -590,7 +590,7 @@ class IPTC {
$c = 'CSN_369103';
break;
default:
- wfDebugLog('iptc', __METHOD__ . 'Unknown charset in iptc 1:90: ' . bin2hex( $tag ) );
+ wfDebugLog( 'iptc', __METHOD__ . 'Unknown charset in iptc 1:90: ' . bin2hex( $tag ) );
//at this point just give up and refuse to parse iptc?
$c = false;
}
diff --git a/includes/media/ImageHandler.php b/includes/media/ImageHandler.php
index 61759074..419afeef 100644
--- a/includes/media/ImageHandler.php
+++ b/includes/media/ImageHandler.php
@@ -139,7 +139,6 @@ abstract class ImageHandler extends MediaHandler {
$params['height'] = $params['physicalHeight'];
}
-
if ( !$this->validateThumbParams( $params['physicalWidth'],
$params['physicalHeight'], $srcWidth, $srcHeight, $mimeType ) ) {
return false;
@@ -162,11 +161,11 @@ abstract class ImageHandler extends MediaHandler {
# Sanity check $width
if( $width <= 0) {
- wfDebug( __METHOD__.": Invalid destination width: $width\n" );
+ wfDebug( __METHOD__ . ": Invalid destination width: $width\n" );
return false;
}
if ( $srcWidth <= 0 ) {
- wfDebug( __METHOD__.": Invalid source width: $srcWidth\n" );
+ wfDebug( __METHOD__ . ": Invalid source width: $srcWidth\n" );
return false;
}
@@ -188,7 +187,7 @@ abstract class ImageHandler extends MediaHandler {
if ( !$this->normaliseParams( $image, $params ) ) {
return false;
}
- $url = $script . '&' . wfArrayToCGI( $this->getScriptParams( $params ) );
+ $url = $script . '&' . wfArrayToCgi( $this->getScriptParams( $params ) );
if( $image->mustRender() || $params['width'] < $image->getWidth() ) {
return new ThumbnailImage( $image, $url, false, $params );
diff --git a/includes/media/Jpeg.php b/includes/media/Jpeg.php
index a15b6524..8b5d6513 100644
--- a/includes/media/Jpeg.php
+++ b/includes/media/Jpeg.php
@@ -37,7 +37,7 @@ class JpegHandler extends ExifBitmapHandler {
$meta = BitmapMetadataHandler::Jpeg( $filename );
if ( !is_array( $meta ) ) {
// This should never happen, but doesn't hurt to be paranoid.
- throw new MWException('Metadata array is not an array');
+ throw new MWException( 'Metadata array is not an array' );
}
$meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
return serialize( $meta );
@@ -59,5 +59,36 @@ class JpegHandler extends ExifBitmapHandler {
}
}
-}
+ /**
+ * @param $file File
+ * @param array $params Rotate parameters.
+ * 'rotation' clockwise rotation in degrees, allowed are multiples of 90
+ * @since 1.21
+ * @return bool
+ */
+ public function rotate( $file, $params ) {
+ global $wgJpegTran;
+
+ $rotation = ( $params[ 'rotation' ] + $this->getRotation( $file ) ) % 360;
+ if( $wgJpegTran && is_file( $wgJpegTran ) ){
+ $cmd = wfEscapeShellArg( $wgJpegTran ) .
+ " -rotate " . wfEscapeShellArg( $rotation ) .
+ " -outfile " . wfEscapeShellArg( $params[ 'dstPath' ] ) .
+ " " . wfEscapeShellArg( $params[ 'srcPath' ] ) . " 2>&1";
+ wfDebug( __METHOD__ . ": running jpgtran: $cmd\n" );
+ wfProfileIn( 'jpegtran' );
+ $retval = 0;
+ $err = wfShellExec( $cmd, $retval, $env );
+ wfProfileOut( 'jpegtran' );
+ if ( $retval !== 0 ) {
+ $this->logErrorForExternalProcess( $retval, $err, $cmd );
+ return new MediaTransformError( 'thumbnail_error', 0, 0, $err );
+ }
+ return false;
+ } else {
+ return parent::rotate( $file, $params );
+ }
+ }
+
+}
diff --git a/includes/media/JpegMetadataExtractor.php b/includes/media/JpegMetadataExtractor.php
index 8d7e43b9..6ff07ed2 100644
--- a/includes/media/JpegMetadataExtractor.php
+++ b/includes/media/JpegMetadataExtractor.php
@@ -25,7 +25,7 @@
* Class for reading jpegs and extracting metadata.
* see also BitmapMetadataHandler.
*
- * Based somewhat on GIFMetadataExtrator.
+ * Based somewhat on GIFMetadataExtractor.
*
* @ingroup Media
*/
@@ -37,16 +37,16 @@ class JpegMetadataExtractor {
// that many segments. Your average file has about 10.
/** Function to extract metadata segments of interest from jpeg files
- * based on GIFMetadataExtractor.
- *
- * we can almost use getimagesize to do this
- * but gis doesn't support having multiple app1 segments
- * and those can't extract xmp on files containing both exif and xmp data
- *
- * @param String $filename name of jpeg file
- * @return Array of interesting segments.
- * @throws MWException if given invalid file.
- */
+ * based on GIFMetadataExtractor.
+ *
+ * we can almost use getimagesize to do this
+ * but gis doesn't support having multiple app1 segments
+ * and those can't extract xmp on files containing both exif and xmp data
+ *
+ * @param string $filename name of jpeg file
+ * @return Array of interesting segments.
+ * @throws MWException if given invalid file.
+ */
static function segmentSplitter ( $filename ) {
$showXMP = function_exists( 'xml_parser_create_ns' );
@@ -129,7 +129,7 @@ class JpegMetadataExtractor {
// whatever...
$segments["XMP"] = substr( $temp, 29 );
wfDebug( __METHOD__ . ' Found XMP section with wrong app identifier '
- . "Using anyways.\n" );
+ . "Using anyways.\n" );
} elseif ( substr( $temp, 0, 6 ) === "Exif\0\0" ) {
// Just need to find out what the byte order is.
// because php's exif plugin sucks...
@@ -165,10 +165,11 @@ class JpegMetadataExtractor {
}
/**
- * Helper function for jpegSegmentSplitter
- * @param &$fh FileHandle for jpeg file
- * @return string data content of segment.
- */
+ * Helper function for jpegSegmentSplitter
+ * @param &$fh FileHandle for jpeg file
+ * @throws MWException
+ * @return string data content of segment.
+ */
private static function jpegExtractMarker( &$fh ) {
$size = wfUnpack( "nint", fread( $fh, 2 ), 2 );
if ( $size['int'] <= 2 ) {
@@ -182,18 +183,18 @@ class JpegMetadataExtractor {
}
/**
- * This reads the photoshop image resource.
- * Currently it only compares the iptc/iim hash
- * with the stored hash, which is used to determine the precedence
- * of the iptc data. In future it may extract some other info, like
- * url of copyright license.
- *
- * This should generally be called by BitmapMetadataHandler::doApp13()
- *
- * @param String $app13 photoshop psir app13 block from jpg.
- * @throws MWException (It gets caught next level up though)
- * @return String if the iptc hash is good or not.
- */
+ * This reads the photoshop image resource.
+ * Currently it only compares the iptc/iim hash
+ * with the stored hash, which is used to determine the precedence
+ * of the iptc data. In future it may extract some other info, like
+ * url of copyright license.
+ *
+ * This should generally be called by BitmapMetadataHandler::doApp13()
+ *
+ * @param string $app13 photoshop psir app13 block from jpg.
+ * @throws MWException (It gets caught next level up though)
+ * @return String if the iptc hash is good or not.
+ */
public static function doPSIR ( $app13 ) {
if ( !$app13 ) {
throw new MWException( "No App13 segment given" );
diff --git a/includes/media/MediaHandler.php b/includes/media/MediaHandler.php
index 965099fd..9a3f645b 100644
--- a/includes/media/MediaHandler.php
+++ b/includes/media/MediaHandler.php
@@ -46,7 +46,7 @@ abstract class MediaHandler {
static function getHandler( $type ) {
global $wgMediaHandlers;
if ( !isset( $wgMediaHandlers[$type] ) ) {
- wfDebug( __METHOD__ . ": no handler found for $type.\n");
+ wfDebug( __METHOD__ . ": no handler found for $type.\n" );
return false;
}
$class = $wgMediaHandlers[$type];
@@ -103,7 +103,7 @@ abstract class MediaHandler {
* can't be determined.
*
* @param $image File: the image object, or false if there isn't one
- * @param $path String: the filename
+ * @param string $path the filename
* @return Array Follow the format of PHP getimagesize() internal function. See http://www.php.net/getimagesize
*/
abstract function getImageSize( $image, $path );
@@ -113,42 +113,42 @@ abstract class MediaHandler {
*
* @param $image File: the image object, or false if there isn't one.
* Warning, FSFile::getPropsFromPath might pass an (object)array() instead (!)
- * @param $path String: the filename
+ * @param string $path the filename
* @return String
*/
function getMetadata( $image, $path ) { return ''; }
/**
- * Get metadata version.
- *
- * This is not used for validating metadata, this is used for the api when returning
- * metadata, since api content formats should stay the same over time, and so things
- * using ForiegnApiRepo can keep backwards compatibility
- *
- * All core media handlers share a common version number, and extensions can
- * use the GetMetadataVersion hook to append to the array (they should append a unique
- * string so not to get confusing). If there was a media handler named 'foo' with metadata
- * version 3 it might add to the end of the array the element 'foo=3'. if the core metadata
- * version is 2, the end version string would look like '2;foo=3'.
- *
- * @return string version string
- */
+ * Get metadata version.
+ *
+ * This is not used for validating metadata, this is used for the api when returning
+ * metadata, since api content formats should stay the same over time, and so things
+ * using ForiegnApiRepo can keep backwards compatibility
+ *
+ * All core media handlers share a common version number, and extensions can
+ * use the GetMetadataVersion hook to append to the array (they should append a unique
+ * string so not to get confusing). If there was a media handler named 'foo' with metadata
+ * version 3 it might add to the end of the array the element 'foo=3'. if the core metadata
+ * version is 2, the end version string would look like '2;foo=3'.
+ *
+ * @return string version string
+ */
static function getMetadataVersion () {
$version = Array( '2' ); // core metadata version
- wfRunHooks('GetMetadataVersion', Array(&$version));
+ wfRunHooks( 'GetMetadataVersion', Array( &$version ) );
return implode( ';', $version);
- }
-
- /**
- * Convert metadata version.
- *
- * By default just returns $metadata, but can be used to allow
- * media handlers to convert between metadata versions.
- *
- * @param $metadata Mixed String or Array metadata array (serialized if string)
- * @param $version Integer target version
- * @return Array serialized metadata in specified version, or $metadata on fail.
- */
+ }
+
+ /**
+ * Convert metadata version.
+ *
+ * By default just returns $metadata, but can be used to allow
+ * media handlers to convert between metadata versions.
+ *
+ * @param $metadata Mixed String or Array metadata array (serialized if string)
+ * @param $version Integer target version
+ * @return Array serialized metadata in specified version, or $metadata on fail.
+ */
function convertMetadataVersion( $metadata, $version = 1 ) {
if ( !is_array( $metadata ) ) {
@@ -181,7 +181,6 @@ abstract class MediaHandler {
return self::METADATA_GOOD;
}
-
/**
* Get a MediaTransformOutput object representing an alternate of the transformed
* output which will call an intermediary thumbnail assist script.
@@ -200,9 +199,9 @@ abstract class MediaHandler {
* actually do the transform.
*
* @param $image File: the image object
- * @param $dstPath String: filesystem destination path
- * @param $dstUrl String: Destination URL to use in output HTML
- * @param $params Array: Arbitrary set of parameters validated by $this->validateParam()
+ * @param string $dstPath filesystem destination path
+ * @param string $dstUrl Destination URL to use in output HTML
+ * @param array $params Arbitrary set of parameters validated by $this->validateParam()
* @return MediaTransformOutput
*/
final function getTransform( $image, $dstPath, $dstUrl, $params ) {
@@ -214,9 +213,9 @@ abstract class MediaHandler {
* transform unless $flags contains self::TRANSFORM_LATER.
*
* @param $image File: the image object
- * @param $dstPath String: filesystem destination path
- * @param $dstUrl String: destination URL to use in output HTML
- * @param $params Array: arbitrary set of parameters validated by $this->validateParam()
+ * @param string $dstPath filesystem destination path
+ * @param string $dstUrl destination URL to use in output HTML
+ * @param array $params arbitrary set of parameters validated by $this->validateParam()
* @param $flags Integer: a bitfield, may contain self::TRANSFORM_LATER
*
* @return MediaTransformOutput
@@ -244,6 +243,15 @@ abstract class MediaHandler {
}
/**
+ * Get useful response headers for GET/HEAD requests for a file with the given metadata
+ * @param $metadata mixed Result of the getMetadata() function of this handler for a file
+ * @return Array
+ */
+ public function getStreamHeaders( $metadata ) {
+ return array();
+ }
+
+ /**
* True if the handled types can be transformed
* @return bool
*/
@@ -352,11 +360,11 @@ abstract class MediaHandler {
*
* This is used by the media handlers that use the FormatMetadata class
*
- * @param $metadataArray Array metadata array
+ * @param array $metadataArray metadata array
* @return array for use displaying metadata.
*/
function formatMetadataHelper( $metadataArray ) {
- $result = array(
+ $result = array(
'visible' => array(),
'collapsed' => array()
);
@@ -396,7 +404,6 @@ abstract class MediaHandler {
return $fields;
}
-
/**
* This is used to generate an array element for each metadata value
* That array is then used to generate the table of metadata values
@@ -405,17 +412,17 @@ abstract class MediaHandler {
* @param &$array Array An array containing elements for each type of visibility
* and each of those elements being an array of metadata items. This function adds
* a value to that array.
- * @param $visibility string ('visible' or 'collapsed') if this value is hidden
+ * @param string $visibility ('visible' or 'collapsed') if this value is hidden
* by default.
- * @param $type String type of metadata tag (currently always 'exif')
- * @param $id String the name of the metadata tag (like 'artist' for example).
+ * @param string $type type of metadata tag (currently always 'exif')
+ * @param string $id the name of the metadata tag (like 'artist' for example).
* its name in the table displayed is the message "$type-$id" (Ex exif-artist ).
- * @param $value String thingy goes into a wikitext table; it used to be escaped but
+ * @param string $value thingy goes into a wikitext table; it used to be escaped but
* that was incompatible with previous practise of customized display
* with wikitext formatting via messages such as 'exif-model-value'.
* So the escaping is taken back out, but generally this seems a confusing
* interface.
- * @param $param String value to pass to the message for the name of the field
+ * @param string $param value to pass to the message for the name of the field
* as $1. Currently this parameter doesn't seem to ever be used.
*
* Note, everything here is passed through the parser later on (!)
@@ -512,7 +519,7 @@ abstract class MediaHandler {
* match the handler class, a Status object should be returned containing
* relevant errors.
*
- * @param $fileName string The local path to the file.
+ * @param string $fileName The local path to the file.
* @return Status object
*/
function verifyUpload( $fileName ) {
@@ -523,8 +530,8 @@ abstract class MediaHandler {
* Check for zero-sized thumbnails. These can be generated when
* no disk space is available or some other error occurs
*
- * @param $dstPath string The location of the suspect file
- * @param $retval int Return value of some shell process, file will be deleted if this is non-zero
+ * @param string $dstPath The location of the suspect file
+ * @param int $retval Return value of some shell process, file will be deleted if this is non-zero
* @return bool True if removed, false otherwise
*/
function removeBadFile( $dstPath, $retval = 0 ) {
@@ -557,4 +564,13 @@ abstract class MediaHandler {
public function filterThumbnailPurgeList( &$files, $options ) {
// Do nothing
}
+
+ /*
+ * True if the handler can rotate the media
+ * @since 1.21
+ * @return bool
+ */
+ public static function canRotate() {
+ return false;
+ }
}
diff --git a/includes/media/MediaTransformOutput.php b/includes/media/MediaTransformOutput.php
index 773824cb..1f95bc3b 100644
--- a/includes/media/MediaTransformOutput.php
+++ b/includes/media/MediaTransformOutput.php
@@ -33,6 +33,13 @@ abstract class MediaTransformOutput {
var $file;
var $width, $height, $url, $page, $path;
+
+ /**
+ * @var array Associative array mapping optional supplementary image files
+ * from pixel density (eg 1.5 or 2) to additional URLs.
+ */
+ public $responsiveUrls = array();
+
protected $storagePath = false;
/**
@@ -73,7 +80,7 @@ abstract class MediaTransformOutput {
}
/**
- * @param $storagePath string The permanent storage path
+ * @param string $storagePath The permanent storage path
* @return void
*/
public function setStoragePath( $storagePath ) {
@@ -83,7 +90,7 @@ abstract class MediaTransformOutput {
/**
* Fetch HTML for this transform output
*
- * @param $options array Associative array of options. Boolean options
+ * @param array $options Associative array of options. Boolean options
* should be indicated with a value of true for true, and false or
* absent for false.
*
@@ -153,7 +160,7 @@ abstract class MediaTransformOutput {
/**
* Stream the file if there were no errors
*
- * @param $headers Array Additional HTTP headers to send on success
+ * @param array $headers Additional HTTP headers to send on success
* @return Bool success
*/
public function streamFile( $headers = array() ) {
@@ -189,9 +196,12 @@ abstract class MediaTransformOutput {
* @return array
*/
public function getDescLinkAttribs( $title = null, $params = '' ) {
- $query = $this->page ? ( 'page=' . urlencode( $this->page ) ) : '';
+ $query = '';
+ if ( $this->page && $this->page !== 1 ) {
+ $query = 'page=' . urlencode( $this->page );
+ }
if( $params ) {
- $query .= $query ? '&'.$params : $params;
+ $query .= $query ? '&' . $params : $params;
}
$attribs = array(
'href' => $this->file->getTitle()->getLocalURL( $query ),
@@ -218,16 +228,16 @@ class ThumbnailImage extends MediaTransformOutput {
* It may also include a 'page' parameter for multipage files.
*
* @param $file File object
- * @param $url String: URL path to the thumb
+ * @param string $url URL path to the thumb
* @param $path String|bool|null: filesystem path to the thumb
- * @param $parameters Array: Associative array of parameters
+ * @param array $parameters Associative array of parameters
* @private
*/
function __construct( $file, $url, $path = false, $parameters = array() ) {
# Previous parameters:
# $file, $url, $width, $height, $path = false, $page = false
- if( is_array( $parameters ) ){
+ if( is_array( $parameters ) ) {
$defaults = array(
'page' => false
);
@@ -260,7 +270,7 @@ class ThumbnailImage extends MediaTransformOutput {
* Return HTML <img ... /> tag for the thumbnail, will include
* width and height attributes and a blank alt text (as required).
*
- * @param $options array Associative array of options. Boolean options
+ * @param array $options Associative array of options. Boolean options
* should be indicated with a value of true for true, and false or
* absent for false.
*
@@ -281,6 +291,7 @@ class ThumbnailImage extends MediaTransformOutput {
* For images, desc-link and file-link are implemented as a click-through. For
* sounds and videos, they may be displayed in other ways.
*
+ * @throws MWException
* @return string
*/
function toHtml( $options = array() ) {
@@ -290,7 +301,7 @@ class ThumbnailImage extends MediaTransformOutput {
$alt = empty( $options['alt'] ) ? '' : $options['alt'];
- $query = empty( $options['desc-query'] ) ? '' : $options['desc-query'];
+ $query = empty( $options['desc-query'] ) ? '' : $options['desc-query'];
if ( !empty( $options['custom-url-link'] ) ) {
$linkAttribs = array( 'href' => $options['custom-url-link'] );
@@ -323,7 +334,7 @@ class ThumbnailImage extends MediaTransformOutput {
'alt' => $alt,
'src' => $this->url,
'width' => $this->width,
- 'height' => $this->height,
+ 'height' => $this->height
);
if ( !empty( $options['valign'] ) ) {
$attribs['style'] = "vertical-align: {$options['valign']}";
@@ -331,6 +342,14 @@ class ThumbnailImage extends MediaTransformOutput {
if ( !empty( $options['img-class'] ) ) {
$attribs['class'] = $options['img-class'];
}
+
+ // Additional densities for responsive images, if specified.
+ if ( !empty( $this->responsiveUrls ) ) {
+ $attribs['srcset'] = Html::srcSet( $this->responsiveUrls );
+ }
+
+ wfRunHooks( 'ThumbnailBeforeProduceHTML', array( $this, &$attribs, &$linkAttribs ) );
+
return $this->linkWrap( $linkAttribs, Xml::element( 'img', $attribs ) );
}
@@ -385,7 +404,7 @@ class MediaTransformError extends MediaTransformOutput {
class TransformParameterError extends MediaTransformError {
function __construct( $params ) {
parent::__construct( 'thumbnail_error',
- max( isset( $params['width'] ) ? $params['width'] : 0, 120 ),
+ max( isset( $params['width'] ) ? $params['width'] : 0, 120 ),
max( isset( $params['height'] ) ? $params['height'] : 0, 120 ),
wfMessage( 'thumbnail_invalid_params' )->text() );
}
diff --git a/includes/media/PNG.php b/includes/media/PNG.php
index 1b329e57..b8a5b40b 100644
--- a/includes/media/PNG.php
+++ b/includes/media/PNG.php
@@ -44,7 +44,7 @@ class PNGHandler extends BitmapHandler {
return self::BROKEN_FILE;
}
- return serialize($metadata);
+ return serialize( $metadata );
}
/**
@@ -74,8 +74,8 @@ class PNGHandler extends BitmapHandler {
*/
function isAnimatedImage( $image ) {
$ser = $image->getMetadata();
- if ($ser) {
- $metadata = unserialize($ser);
+ if ( $ser ) {
+ $metadata = unserialize( $ser );
if( $metadata['frameCount'] > 1 ) return true;
}
return false;
@@ -88,11 +88,11 @@ class PNGHandler extends BitmapHandler {
function canAnimateThumbnail( $image ) {
return false;
}
-
+
function getMetadataType( $image ) {
return 'parsed-png';
}
-
+
function isMetadataValid( $image, $metadata ) {
if ( $metadata === self::BROKEN_FILE ) {
@@ -105,13 +105,13 @@ class PNGHandler extends BitmapHandler {
wfRestoreWarnings();
if ( !$data || !is_array( $data ) ) {
- wfDebug(__METHOD__ . ' invalid png metadata' );
+ wfDebug( __METHOD__ . ' invalid png metadata' );
return self::METADATA_BAD;
}
if ( !isset( $data['metadata']['_MW_PNG_VERSION'] )
|| $data['metadata']['_MW_PNG_VERSION'] != PNGMetadataExtractor::VERSION ) {
- wfDebug(__METHOD__ . ' old but compatible png metadata' );
+ wfDebug( __METHOD__ . ' old but compatible png metadata' );
return self::METADATA_COMPATIBLE;
}
return self::METADATA_GOOD;
@@ -126,7 +126,7 @@ class PNGHandler extends BitmapHandler {
$original = parent::getLongDesc( $image );
wfSuppressWarnings();
- $metadata = unserialize($image->getMetadata());
+ $metadata = unserialize( $image->getMetadata() );
wfRestoreWarnings();
if( !$metadata || $metadata['frameCount'] <= 0 )
@@ -134,21 +134,21 @@ class PNGHandler extends BitmapHandler {
$info = array();
$info[] = $original;
-
+
if ( $metadata['loopCount'] == 0 ) {
$info[] = wfMessage( 'file-info-png-looped' )->parse();
} elseif ( $metadata['loopCount'] > 1 ) {
$info[] = wfMessage( 'file-info-png-repeat' )->numParams( $metadata['loopCount'] )->parse();
}
-
+
if ( $metadata['frameCount'] > 0 ) {
$info[] = wfMessage( 'file-info-png-frames' )->numParams( $metadata['frameCount'] )->parse();
}
-
+
if ( $metadata['duration'] ) {
$info[] = $wgLang->formatTimePeriod( $metadata['duration'] );
}
-
+
return $wgLang->commaList( $info );
}
diff --git a/includes/media/PNGMetadataExtractor.php b/includes/media/PNGMetadataExtractor.php
index 9dcde406..87f705ca 100644
--- a/includes/media/PNGMetadataExtractor.php
+++ b/includes/media/PNGMetadataExtractor.php
@@ -124,7 +124,7 @@ class PNGMetadataExtractor {
case 0:
$colorType = 'greyscale';
break;
- case 2:
+ case 2:
$colorType = 'truecolour';
break;
case 3:
@@ -417,7 +417,7 @@ class PNGMetadataExtractor {
* @throws Exception if too big.
* @return String The chunk.
*/
- static private function read( $fh, $size ) {
+ private static function read( $fh, $size ) {
if ( $size > self::MAX_CHUNK_SIZE ) {
throw new Exception( __METHOD__ . ': Chunk size of ' . $size .
' too big. Max size is: ' . self::MAX_CHUNK_SIZE );
diff --git a/includes/media/SVG.php b/includes/media/SVG.php
index 55fa5547..cddab51d 100644
--- a/includes/media/SVG.php
+++ b/includes/media/SVG.php
@@ -120,6 +120,12 @@ class SvgHandler extends ImageHandler {
return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
}
+ $metadata = $this->unpackMetadata( $image->getMetadata() );
+ if ( isset( $metadata['error'] ) ) { // sanity check
+ $err = wfMessage( 'svg-long-error', $metadata['error']['message'] )->text();
+ return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
+ }
+
if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight,
wfMessage( 'thumbnail_dest_directory' )->text() );
@@ -127,7 +133,7 @@ class SvgHandler extends ImageHandler {
$srcPath = $image->getLocalRefPath();
$status = $this->rasterize( $srcPath, $dstPath, $physicalWidth, $physicalHeight );
- if( $status === true ) {
+ if ( $status === true ) {
return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
} else {
return $status; // MediaTransformError
@@ -135,14 +141,15 @@ class SvgHandler extends ImageHandler {
}
/**
- * Transform an SVG file to PNG
- * This function can be called outside of thumbnail contexts
- * @param string $srcPath
- * @param string $dstPath
- * @param string $width
- * @param string $height
- * @return bool|MediaTransformError
- */
+ * Transform an SVG file to PNG
+ * This function can be called outside of thumbnail contexts
+ * @param string $srcPath
+ * @param string $dstPath
+ * @param string $width
+ * @param string $height
+ * @throws MWException
+ * @return bool|MediaTransformError
+ */
public function rasterize( $srcPath, $dstPath, $width, $height ) {
global $wgSVGConverters, $wgSVGConverter, $wgSVGConverterPath;
$err = false;
@@ -163,14 +170,14 @@ class SvgHandler extends ImageHandler {
$cmd = str_replace(
array( '$path/', '$width', '$height', '$input', '$output' ),
array( $wgSVGConverterPath ? wfEscapeShellArg( "$wgSVGConverterPath/" ) : "",
- intval( $width ),
- intval( $height ),
- wfEscapeShellArg( $srcPath ),
- wfEscapeShellArg( $dstPath ) ),
+ intval( $width ),
+ intval( $height ),
+ wfEscapeShellArg( $srcPath ),
+ wfEscapeShellArg( $dstPath ) ),
$wgSVGConverters[$wgSVGConverter]
) . " 2>&1";
wfProfileIn( 'rsvg' );
- wfDebug( __METHOD__.": $cmd\n" );
+ wfDebug( __METHOD__ . ": $cmd\n" );
$err = wfShellExec( $cmd, $retval );
wfProfileOut( 'rsvg' );
}
@@ -178,7 +185,7 @@ class SvgHandler extends ImageHandler {
$removed = $this->removeBadFile( $dstPath, $retval );
if ( $retval != 0 || $removed ) {
wfDebugLog( 'thumbnail', sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
- wfHostname(), $retval, trim($err), $cmd ) );
+ wfHostname(), $retval, trim( $err ), $cmd ) );
return new MediaTransformError( 'thumbnail_error', $width, $height, $err );
}
return true;
@@ -213,6 +220,8 @@ class SvgHandler extends ImageHandler {
if ( isset( $metadata['width'] ) && isset( $metadata['height'] ) ) {
return array( $metadata['width'], $metadata['height'], 'SVG',
"width=\"{$metadata['width']}\" height=\"{$metadata['height']}\"" );
+ } else { // error
+ return array( 0, 0, 'SVG', "width=\"0\" height=\"0\"" );
}
}
@@ -231,6 +240,12 @@ class SvgHandler extends ImageHandler {
*/
function getLongDesc( $file ) {
global $wgLang;
+
+ $metadata = $this->unpackMetadata( $file->getMetadata() );
+ if ( isset( $metadata['error'] ) ) {
+ return wfMessage( 'svg-long-error', $metadata['error']['message'] )->text();
+ }
+
$size = $wgLang->formatSize( $file->getSize() );
if ( $this->isAnimatedImage( $file ) ) {
@@ -239,23 +254,23 @@ class SvgHandler extends ImageHandler {
$msg = wfMessage( 'svg-long-desc' );
}
- $msg->numParams(
- $file->getWidth(),
- $file->getHeight()
- );
- $msg->Params( $size );
+ $msg->numParams( $file->getWidth(), $file->getHeight() )->params( $size );
+
return $msg->parse();
}
function getMetadata( $file, $filename ) {
+ $metadata = array( 'version' => self::SVG_METADATA_VERSION );
try {
- $metadata = SVGMetadataExtractor::getMetadata( $filename );
- } catch( Exception $e ) {
- // Broken file?
+ $metadata += SVGMetadataExtractor::getMetadata( $filename );
+ } catch( MWException $e ) { // @TODO: SVG specific exceptions
+ // File not found, broken, etc.
+ $metadata['error'] = array(
+ 'message' => $e->getMessage(),
+ 'code' => $e->getCode()
+ );
wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
- return '0';
}
- $metadata['version'] = self::SVG_METADATA_VERSION;
return serialize( $metadata );
}
@@ -305,7 +320,7 @@ class SvgHandler extends ImageHandler {
return false;
}
$metadata = $this->unpackMetadata( $metadata );
- if ( !$metadata ) {
+ if ( !$metadata || isset( $metadata['error'] ) ) {
return false;
}
diff --git a/includes/media/SVGMetadataExtractor.php b/includes/media/SVGMetadataExtractor.php
index c6f63fd4..0de212b9 100644
--- a/includes/media/SVGMetadataExtractor.php
+++ b/includes/media/SVGMetadataExtractor.php
@@ -51,7 +51,8 @@ class SVGReader {
* Constructor
*
* Creates an SVGReader drawing from the source provided
- * @param $source String: URI from which to read
+ * @param string $source URI from which to read
+ * @throws MWException|Exception
*/
function __construct( $source ) {
global $wgSVGMetadataCutoff;
@@ -66,7 +67,7 @@ class SVGReader {
if ( $size > $wgSVGMetadataCutoff ) {
$this->debug( "SVG is $size bytes, which is bigger than $wgSVGMetadataCutoff. Truncating." );
$contents = file_get_contents( $source, false, null, -1, $wgSVGMetadataCutoff );
- if ($contents === false) {
+ if ( $contents === false ) {
throw new MWException( 'Error reading SVG file.' );
}
$this->reader->XML( $contents, null, LIBXML_NOERROR | LIBXML_NOWARNING );
@@ -120,6 +121,7 @@ class SVGReader {
/**
* Read the SVG
+ * @throws MWException
* @return bool
*/
protected function read() {
@@ -137,7 +139,7 @@ class SVGReader {
$this->debug( "<svg> tag is correct." );
$this->handleSVGAttribs();
- $exitDepth = $this->reader->depth;
+ $exitDepth = $this->reader->depth;
$keepReading = $this->reader->read();
while ( $keepReading ) {
$tag = $this->reader->localName;
@@ -180,8 +182,8 @@ class SVGReader {
/**
* Read a textelement from an element
*
- * @param String $name of the element that we are reading from
- * @param String $metafield that we will fill with the result
+ * @param string $name of the element that we are reading from
+ * @param string $metafield that we will fill with the result
*/
private function readField( $name, $metafield=null ) {
$this->debug ( "Read field $metafield" );
@@ -192,7 +194,7 @@ class SVGReader {
while( $keepReading ) {
if( $this->reader->localName == $name && $this->reader->namespaceURI == self::NS_SVG && $this->reader->nodeType == XmlReader::END_ELEMENT ) {
break;
- } elseif( $this->reader->nodeType == XmlReader::TEXT ){
+ } elseif( $this->reader->nodeType == XmlReader::TEXT ) {
$this->metadata[$metafield] = trim( $this->reader->value );
}
$keepReading = $this->reader->read();
@@ -202,7 +204,8 @@ class SVGReader {
/**
* Read an XML snippet from an element
*
- * @param String $metafield that we will fill with the result
+ * @param string $metafield that we will fill with the result
+ * @throws MWException
*/
private function readXml( $metafield=null ) {
$this->debug ( "Read top level metadata" );
@@ -221,7 +224,7 @@ class SVGReader {
/**
* Filter all children, looking for animate elements
*
- * @param String $name of the element that we are reading from
+ * @param string $name of the element that we are reading from
*/
private function animateFilter( $name ) {
$this->debug ( "animate filter for tag $name" );
@@ -231,7 +234,7 @@ class SVGReader {
if ( $this->reader->isEmptyElement ) {
return;
}
- $exitDepth = $this->reader->depth;
+ $exitDepth = $this->reader->depth;
$keepReading = $this->reader->read();
while( $keepReading ) {
if( $this->reader->localName == $name && $this->reader->depth <= $exitDepth
@@ -282,16 +285,16 @@ class SVGReader {
*
* The parser has to be in the start element of "<svg>"
*/
- private function handleSVGAttribs( ) {
+ private function handleSVGAttribs() {
$defaultWidth = self::DEFAULT_WIDTH;
$defaultHeight = self::DEFAULT_HEIGHT;
$aspect = 1.0;
$width = null;
$height = null;
- if( $this->reader->getAttribute('viewBox') ) {
+ if( $this->reader->getAttribute( 'viewBox' ) ) {
// min-x min-y width height
- $viewBox = preg_split( '/\s+/', trim( $this->reader->getAttribute('viewBox') ) );
+ $viewBox = preg_split( '/\s+/', trim( $this->reader->getAttribute( 'viewBox' ) ) );
if( count( $viewBox ) == 4 ) {
$viewWidth = $this->scaleSVGUnit( $viewBox[2] );
$viewHeight = $this->scaleSVGUnit( $viewBox[3] );
@@ -301,12 +304,12 @@ class SVGReader {
}
}
}
- if( $this->reader->getAttribute('width') ) {
- $width = $this->scaleSVGUnit( $this->reader->getAttribute('width'), $defaultWidth );
+ if( $this->reader->getAttribute( 'width' ) ) {
+ $width = $this->scaleSVGUnit( $this->reader->getAttribute( 'width' ), $defaultWidth );
$this->metadata['originalWidth'] = $this->reader->getAttribute( 'width' );
}
- if( $this->reader->getAttribute('height') ) {
- $height = $this->scaleSVGUnit( $this->reader->getAttribute('height'), $defaultHeight );
+ if( $this->reader->getAttribute( 'height' ) ) {
+ $height = $this->scaleSVGUnit( $this->reader->getAttribute( 'height' ), $defaultHeight );
$this->metadata['originalHeight'] = $this->reader->getAttribute( 'height' );
}
@@ -329,11 +332,11 @@ class SVGReader {
* Return a rounded pixel equivalent for a labeled CSS/SVG length.
* http://www.w3.org/TR/SVG11/coords.html#UnitIdentifiers
*
- * @param $length String: CSS/SVG length.
+ * @param string $length CSS/SVG length.
* @param $viewportSize: Float optional scale for percentage units...
* @return float: length in pixels
*/
- static function scaleSVGUnit( $length, $viewportSize=512 ) {
+ static function scaleSVGUnit( $length, $viewportSize = 512 ) {
static $unitLength = array(
'px' => 1.0,
'pt' => 1.25,
diff --git a/includes/media/Tiff.php b/includes/media/Tiff.php
index d95c9074..0042208b 100644
--- a/includes/media/Tiff.php
+++ b/includes/media/Tiff.php
@@ -70,8 +70,9 @@ class TiffHandler extends ExifBitmapHandler {
}
/**
- * @param $image
- * @param $filename
+ * @param File $image
+ * @param string $filename
+ * @throws MWException
* @return string
*/
function getMetadata( $image, $filename ) {
@@ -81,7 +82,7 @@ class TiffHandler extends ExifBitmapHandler {
$meta = BitmapMetadataHandler::Tiff( $filename );
if ( !is_array( $meta ) ) {
// This should never happen, but doesn't hurt to be paranoid.
- throw new MWException('Metadata array is not an array');
+ throw new MWException( 'Metadata array is not an array' );
}
$meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
return serialize( $meta );
diff --git a/includes/media/XCF.php b/includes/media/XCF.php
index 555fa1fb..ba38d158 100644
--- a/includes/media/XCF.php
+++ b/includes/media/XCF.php
@@ -72,7 +72,7 @@ class XCFHandler extends BitmapHandler {
* @author Hexmode
* @author Hashar
*
- * @param $filename String Full path to a XCF file
+ * @param string $filename Full path to a XCF file
* @return bool|array metadata array just like PHP getimagesize()
*/
static function getXCFMetaData( $filename ) {
@@ -84,7 +84,7 @@ class XCFHandler extends BitmapHandler {
# The image structure always starts at offset 0 in the XCF file.
# So we just read it :-)
$binaryHeader = fread( $f, 26 );
- fclose($f);
+ fclose( $f );
# Master image structure:
#
diff --git a/includes/media/XMP.php b/includes/media/XMP.php
index 36660b3d..62738a00 100644
--- a/includes/media/XMP.php
+++ b/includes/media/XMP.php
@@ -22,30 +22,30 @@
*/
/**
-* Class for reading xmp data containing properties relevant to
-* images, and spitting out an array that FormatExif accepts.
-*
-* Note, this is not meant to recognize every possible thing you can
-* encode in XMP. It should recognize all the properties we want.
-* For example it doesn't have support for structures with multiple
-* nesting levels, as none of the properties we're supporting use that
-* feature. If it comes across properties it doesn't recognize, it should
-* ignore them.
-*
-* The public methods one would call in this class are
-* - parse( $content )
-* Reads in xmp content.
-* Can potentially be called multiple times with partial data each time.
-* - parseExtended( $content )
-* Reads XMPExtended blocks (jpeg files only).
-* - getResults
-* Outputs a results array.
-*
-* Note XMP kind of looks like rdf. They are not the same thing - XMP is
-* encoded as a specific subset of rdf. This class can read XMP. It cannot
-* read rdf.
-*
-*/
+ * Class for reading xmp data containing properties relevant to
+ * images, and spitting out an array that FormatExif accepts.
+ *
+ * Note, this is not meant to recognize every possible thing you can
+ * encode in XMP. It should recognize all the properties we want.
+ * For example it doesn't have support for structures with multiple
+ * nesting levels, as none of the properties we're supporting use that
+ * feature. If it comes across properties it doesn't recognize, it should
+ * ignore them.
+ *
+ * The public methods one would call in this class are
+ * - parse( $content )
+ * Reads in xmp content.
+ * Can potentially be called multiple times with partial data each time.
+ * - parseExtended( $content )
+ * Reads XMPExtended blocks (jpeg files only).
+ * - getResults
+ * Outputs a results array.
+ *
+ * Note XMP kind of looks like rdf. They are not the same thing - XMP is
+ * encoded as a specific subset of rdf. This class can read XMP. It cannot
+ * read rdf.
+ *
+ */
class XMPReader {
private $curItem = array(); // array to hold the current element (and previous element, and so on)
@@ -63,39 +63,38 @@ class XMPReader {
protected $items;
/**
- * These are various mode constants.
- * they are used to figure out what to do
- * with an element when its encountered.
- *
- * For example, MODE_IGNORE is used when processing
- * a property we're not interested in. So if a new
- * element pops up when we're in that mode, we ignore it.
- */
+ * These are various mode constants.
+ * they are used to figure out what to do
+ * with an element when its encountered.
+ *
+ * For example, MODE_IGNORE is used when processing
+ * a property we're not interested in. So if a new
+ * element pops up when we're in that mode, we ignore it.
+ */
const MODE_INITIAL = 0;
- const MODE_IGNORE = 1;
- const MODE_LI = 2;
+ const MODE_IGNORE = 1;
+ const MODE_LI = 2;
const MODE_LI_LANG = 3;
- const MODE_QDESC = 4;
+ const MODE_QDESC = 4;
// The following MODE constants are also used in the
// $items array to denote what type of property the item is.
- const MODE_SIMPLE = 10;
- const MODE_STRUCT = 11; // structure (associative array)
- const MODE_SEQ = 12; // ordered list
- const MODE_BAG = 13; // unordered list
- const MODE_LANG = 14;
- const MODE_ALT = 15; // non-language alt. Currently not implemented, and not needed atm.
+ const MODE_SIMPLE = 10;
+ const MODE_STRUCT = 11; // structure (associative array)
+ const MODE_SEQ = 12; // ordered list
+ const MODE_BAG = 13; // unordered list
+ const MODE_LANG = 14;
+ const MODE_ALT = 15; // non-language alt. Currently not implemented, and not needed atm.
const MODE_BAGSTRUCT = 16; // A BAG of Structs.
const NS_RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
const NS_XML = 'http://www.w3.org/XML/1998/namespace';
-
/**
- * Constructor.
- *
- * Primary job is to initialize the XMLParser
- */
+ * Constructor.
+ *
+ * Primary job is to initialize the XMLParser
+ */
function __construct() {
if ( !function_exists( 'xml_parser_create_ns' ) ) {
@@ -109,12 +108,12 @@ class XMPReader {
}
/**
- * Main use is if a single item has multiple xmp documents describing it.
- * For example in jpeg's with extendedXMP
- */
+ * Main use is if a single item has multiple xmp documents describing it.
+ * For example in jpeg's with extendedXMP
+ */
private function resetXMLParser() {
- if ($this->xmlParser) {
+ if ( $this->xmlParser ) {
//is this needed?
xml_parser_free( $this->xmlParser );
}
@@ -131,20 +130,20 @@ class XMPReader {
}
/** Destroy the xml parser
- *
- * Not sure if this is actually needed.
- */
+ *
+ * Not sure if this is actually needed.
+ */
function __destruct() {
// not sure if this is needed.
xml_parser_free( $this->xmlParser );
}
/** Get the result array. Do some post-processing before returning
- * the array, and transform any metadata that is special-cased.
- *
- * @return Array array of results as an array of arrays suitable for
- * FormatMetadata::getFormattedData().
- */
+ * the array, and transform any metadata that is special-cased.
+ *
+ * @return Array array of results as an array of arrays suitable for
+ * FormatMetadata::getFormattedData().
+ */
public function getResults() {
// xmp-special is for metadata that affects how stuff
// is extracted. For example xmpNote:HasExtendedXMP.
@@ -156,7 +155,7 @@ class XMPReader {
$data = $this->results;
- wfRunHooks('XMPGetResults', Array(&$data));
+ wfRunHooks( 'XMPGetResults', Array( &$data ) );
if ( isset( $data['xmp-special']['AuthorsPosition'] )
&& is_string( $data['xmp-special']['AuthorsPosition'] )
@@ -201,13 +200,12 @@ class XMPReader {
// To avoid copying over the _type meta-fields.
continue;
}
- foreach( $loc as $field => $val ) {
+ foreach( $loc as $field => $val ) {
$data['xmp-general'][$field . 'Created'][] = $val;
}
}
}
-
// We don't want to return the special values, since they're
// special and not info to be stored about the file.
unset( $data['xmp-special'] );
@@ -232,17 +230,18 @@ class XMPReader {
}
/**
- * Main function to call to parse XMP. Use getResults to
- * get results.
- *
- * Also catches any errors during processing, writes them to
- * debug log, blanks result array and returns false.
- *
- * @param $content String: XMP data
- * @param $allOfIt Boolean: If this is all the data (true) or if its split up (false). Default true
- * @param $reset Boolean: does xml parser need to be reset. Default false
- * @return Boolean success.
- */
+ * Main function to call to parse XMP. Use getResults to
+ * get results.
+ *
+ * Also catches any errors during processing, writes them to
+ * debug log, blanks result array and returns false.
+ *
+ * @param string $content XMP data
+ * @param $allOfIt Boolean: If this is all the data (true) or if its split up (false). Default true
+ * @param $reset Boolean: does xml parser need to be reset. Default false
+ * @throws MWException
+ * @return Boolean success.
+ */
public function parse( $content, $allOfIt = true, $reset = false ) {
if ( $reset ) {
$this->resetXMLParser();
@@ -254,7 +253,7 @@ class XMPReader {
if ( !$this->charset ) {
$bom = array();
if ( preg_match( '/\xEF\xBB\xBF|\xFE\xFF|\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\xFF\xFE/',
- $content, $bom )
+ $content, $bom )
) {
switch ( $bom[0] ) {
case "\xFE\xFF":
@@ -274,11 +273,8 @@ class XMPReader {
break;
default:
//this should be impossible to get to
- throw new MWException("Invalid BOM");
- break;
-
+ throw new MWException( "Invalid BOM" );
}
-
} else {
// standard specifically says, if no bom assume utf-8
$this->charset = 'UTF-8';
@@ -314,7 +310,7 @@ class XMPReader {
*
* @todo In serious need of testing
* @see http://www.adobe.ge/devnet/xmp/pdfs/XMPSpecificationPart3.pdf XMP spec part 3 page 20
- * @param String $content XMPExtended block minus the namespace signature
+ * @param string $content XMPExtended block minus the namespace signature
* @return Boolean If it succeeded.
*/
public function parseExtended( $content ) {
@@ -323,17 +319,16 @@ class XMPReader {
$guid = substr( $content, 0, 32 );
if ( !isset( $this->results['xmp-special']['HasExtendedXMP'] )
|| $this->results['xmp-special']['HasExtendedXMP'] !== $guid ) {
- wfDebugLog('XMP', __METHOD__ . " Ignoring XMPExtended block due to wrong guid (guid= '$guid' )");
+ wfDebugLog( 'XMP', __METHOD__ . " Ignoring XMPExtended block due to wrong guid (guid= '$guid')" );
return false;
}
- $len = unpack( 'Nlength/Noffset', substr( $content, 32, 8 ) );
+ $len = unpack( 'Nlength/Noffset', substr( $content, 32, 8 ) );
- if (!$len || $len['length'] < 4 || $len['offset'] < 0 || $len['offset'] > $len['length'] ) {
- wfDebugLog('XMP', __METHOD__ . 'Error reading extended XMP block, invalid length or offset.');
+ if ( !$len || $len['length'] < 4 || $len['offset'] < 0 || $len['offset'] > $len['length'] ) {
+ wfDebugLog( 'XMP', __METHOD__ . 'Error reading extended XMP block, invalid length or offset.' );
return false;
}
-
// we're not very robust here. we should accept it in the wrong order. To quote
// the xmp standard:
// "A JPEG writer should write the ExtendedXMP marker segments in order, immediately following the
@@ -344,8 +339,8 @@ class XMPReader {
// so the probability that it will have > 128k, and be in the wrong order is very low...
if ( $len['offset'] !== $this->extendedXMPOffset ) {
- wfDebugLog('XMP', __METHOD__ . 'Ignoring XMPExtended block due to wrong order. (Offset was '
- . $len['offset'] . ' but expected ' . $this->extendedXMPOffset . ')');
+ wfDebugLog( 'XMP', __METHOD__ . 'Ignoring XMPExtended block due to wrong order. (Offset was '
+ . $len['offset'] . ' but expected ' . $this->extendedXMPOffset . ')' );
return false;
}
@@ -365,26 +360,26 @@ class XMPReader {
$atEnd = false;
}
- wfDebugLog('XMP', __METHOD__ . 'Parsing a XMPExtended block');
+ wfDebugLog( 'XMP', __METHOD__ . 'Parsing a XMPExtended block' );
return $this->parse( $actualContent, $atEnd );
}
/**
- * Character data handler
- * Called whenever character data is found in the xmp document.
- *
- * does nothing if we're in MODE_IGNORE or if the data is whitespace
- * throws an error if we're not in MODE_SIMPLE (as we're not allowed to have character
- * data in the other modes).
- *
- * As an example, this happens when we encounter XMP like:
- * <exif:DigitalZoomRatio>0/10</exif:DigitalZoomRatio>
- * and are processing the 0/10 bit.
- *
- * @param $parser XMLParser reference to the xml parser
- * @param $data String Character data
- * @throws MWException on invalid data
- */
+ * Character data handler
+ * Called whenever character data is found in the xmp document.
+ *
+ * does nothing if we're in MODE_IGNORE or if the data is whitespace
+ * throws an error if we're not in MODE_SIMPLE (as we're not allowed to have character
+ * data in the other modes).
+ *
+ * As an example, this happens when we encounter XMP like:
+ * <exif:DigitalZoomRatio>0/10</exif:DigitalZoomRatio>
+ * and are processing the 0/10 bit.
+ *
+ * @param $parser XMLParser reference to the xml parser
+ * @param string $data Character data
+ * @throws MWException on invalid data
+ */
function char( $parser, $data ) {
$data = trim( $data );
@@ -414,36 +409,33 @@ class XMPReader {
}
/** When we hit a closing element in MODE_IGNORE
- * Check to see if this is the element we started to ignore,
- * in which case we get out of MODE_IGNORE
- *
- * @param $elm String Namespace of element followed by a space and then tag name of element.
- */
+ * Check to see if this is the element we started to ignore,
+ * in which case we get out of MODE_IGNORE
+ *
+ * @param string $elm Namespace of element followed by a space and then tag name of element.
+ */
private function endElementModeIgnore ( $elm ) {
-
if ( $this->curItem[0] === $elm ) {
array_shift( $this->curItem );
array_shift( $this->mode );
}
- return;
-
}
/**
- * Hit a closing element when in MODE_SIMPLE.
- * This generally means that we finished processing a
- * property value, and now have to save the result to the
- * results array
- *
- * For example, when processing:
- * <exif:DigitalZoomRatio>0/10</exif:DigitalZoomRatio>
- * this deals with when we hit </exif:DigitalZoomRatio>.
- *
- * Or it could be if we hit the end element of a property
- * of a compound data structure (like a member of an array).
- *
- * @param $elm String namespace, space, and tag name.
- */
+ * Hit a closing element when in MODE_SIMPLE.
+ * This generally means that we finished processing a
+ * property value, and now have to save the result to the
+ * results array
+ *
+ * For example, when processing:
+ * <exif:DigitalZoomRatio>0/10</exif:DigitalZoomRatio>
+ * this deals with when we hit </exif:DigitalZoomRatio>.
+ *
+ * Or it could be if we hit the end element of a property
+ * of a compound data structure (like a member of an array).
+ *
+ * @param string $elm namespace, space, and tag name.
+ */
private function endElementModeSimple ( $elm ) {
if ( $this->charContent !== false ) {
if ( $this->processingArray ) {
@@ -463,22 +455,23 @@ class XMPReader {
}
/**
- * Hit a closing element in MODE_STRUCT, MODE_SEQ, MODE_BAG
- * generally means we've finished processing a nested structure.
- * resets some internal variables to indicate that.
- *
- * Note this means we hit the closing element not the "</rdf:Seq>".
- *
- * @par For example, when processing:
- * @code{,xml}
- * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
- * </rdf:Seq> </exif:ISOSpeedRatings>
- * @endcode
- *
- * This method is called when we hit the "</exif:ISOSpeedRatings>" tag.
- *
- * @param $elm String namespace . space . tag name.
- */
+ * Hit a closing element in MODE_STRUCT, MODE_SEQ, MODE_BAG
+ * generally means we've finished processing a nested structure.
+ * resets some internal variables to indicate that.
+ *
+ * Note this means we hit the closing element not the "</rdf:Seq>".
+ *
+ * @par For example, when processing:
+ * @code{,xml}
+ * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
+ * </rdf:Seq> </exif:ISOSpeedRatings>
+ * @endcode
+ *
+ * This method is called when we hit the "</exif:ISOSpeedRatings>" tag.
+ *
+ * @param string $elm namespace . space . tag name.
+ * @throws MWException
+ */
private function endElementNested( $elm ) {
/* cur item must be the same as $elm, unless if in MODE_STRUCT
@@ -486,7 +479,7 @@ class XMPReader {
if ( $this->curItem[0] !== $elm
&& !( $elm === self::NS_RDF . ' Description'
&& $this->mode[0] === self::MODE_STRUCT )
- ) {
+ ) {
throw new MWException( "nesting mismatch. got a </$elm> but expected a </" . $this->curItem[0] . '>' );
}
@@ -528,23 +521,24 @@ class XMPReader {
}
/**
- * Hit a closing element in MODE_LI (either rdf:Seq, or rdf:Bag )
- * Add information about what type of element this is.
- *
- * Note we still have to hit the outer "</property>"
- *
- * @par For example, when processing:
- * @code{,xml}
- * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
- * </rdf:Seq> </exif:ISOSpeedRatings>
- * @endcode
- *
- * This method is called when we hit the "</rdf:Seq>".
- * (For comparison, we call endElementModeSimple when we
- * hit the "</rdf:li>")
- *
- * @param $elm String namespace . ' ' . element name
- */
+ * Hit a closing element in MODE_LI (either rdf:Seq, or rdf:Bag )
+ * Add information about what type of element this is.
+ *
+ * Note we still have to hit the outer "</property>"
+ *
+ * @par For example, when processing:
+ * @code{,xml}
+ * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
+ * </rdf:Seq> </exif:ISOSpeedRatings>
+ * @endcode
+ *
+ * This method is called when we hit the "</rdf:Seq>".
+ * (For comparison, we call endElementModeSimple when we
+ * hit the "</rdf:li>")
+ *
+ * @param string $elm namespace . ' ' . element name
+ * @throws MWException
+ */
private function endElementModeLi( $elm ) {
list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
@@ -575,15 +569,15 @@ class XMPReader {
}
/**
- * End element while in MODE_QDESC
- * mostly when ending an element when we have a simple value
- * that has qualifiers.
- *
- * Qualifiers aren't all that common, and we don't do anything
- * with them.
- *
- * @param $elm String namespace and element
- */
+ * End element while in MODE_QDESC
+ * mostly when ending an element when we have a simple value
+ * that has qualifiers.
+ *
+ * Qualifiers aren't all that common, and we don't do anything
+ * with them.
+ *
+ * @param string $elm namespace and element
+ */
private function endElementModeQDesc( $elm ) {
if ( $elm === self::NS_RDF . ' value' ) {
@@ -594,22 +588,21 @@ class XMPReader {
array_shift( $this->mode );
array_shift( $this->curItem );
}
-
-
}
/**
- * Handler for hitting a closing element.
- *
- * generally just calls a helper function depending on what
- * mode we're in.
- *
- * Ignores the outer wrapping elements that are optional in
- * xmp and have no meaning.
- *
- * @param $parser XMLParser
- * @param $elm String namespace . ' ' . element name
- */
+ * Handler for hitting a closing element.
+ *
+ * generally just calls a helper function depending on what
+ * mode we're in.
+ *
+ * Ignores the outer wrapping elements that are optional in
+ * xmp and have no meaning.
+ *
+ * @param $parser XMLParser
+ * @param string $elm namespace . ' ' . element name
+ * @throws MWException
+ */
function endElement( $parser, $elm ) {
if ( $elm === ( self::NS_RDF . ' RDF' )
|| $elm === 'adobe:ns:meta/ xmpmeta'
@@ -681,16 +674,16 @@ class XMPReader {
}
/**
- * Hit an opening element while in MODE_IGNORE
- *
- * XMP is extensible, so ignore any tag we don't understand.
- *
- * Mostly ignores, unless we encounter the element that we are ignoring.
- * in which case we add it to the item stack, so we can ignore things
- * that are nested, correctly.
- *
- * @param $elm String namespace . ' ' . tag name
- */
+ * Hit an opening element while in MODE_IGNORE
+ *
+ * XMP is extensible, so ignore any tag we don't understand.
+ *
+ * Mostly ignores, unless we encounter the element that we are ignoring.
+ * in which case we add it to the item stack, so we can ignore things
+ * that are nested, correctly.
+ *
+ * @param string $elm namespace . ' ' . tag name
+ */
private function startElementModeIgnore( $elm ) {
if ( $elm === $this->curItem[0] ) {
array_unshift( $this->curItem, $elm );
@@ -699,12 +692,12 @@ class XMPReader {
}
/**
- * Start element in MODE_BAG (unordered array)
- * this should always be <rdf:Bag>
- *
- * @param $elm String namespace . ' ' . tag
- * @throws MWException if we have an element that's not <rdf:Bag>
- */
+ * Start element in MODE_BAG (unordered array)
+ * this should always be <rdf:Bag>
+ *
+ * @param string $elm namespace . ' ' . tag
+ * @throws MWException if we have an element that's not <rdf:Bag>
+ */
private function startElementModeBag( $elm ) {
if ( $elm === self::NS_RDF . ' Bag' ) {
array_unshift( $this->mode, self::MODE_LI );
@@ -715,12 +708,12 @@ class XMPReader {
}
/**
- * Start element in MODE_SEQ (ordered array)
- * this should always be <rdf:Seq>
- *
- * @param $elm String namespace . ' ' . tag
- * @throws MWException if we have an element that's not <rdf:Seq>
- */
+ * Start element in MODE_SEQ (ordered array)
+ * this should always be <rdf:Seq>
+ *
+ * @param string $elm namespace . ' ' . tag
+ * @throws MWException if we have an element that's not <rdf:Seq>
+ */
private function startElementModeSeq( $elm ) {
if ( $elm === self::NS_RDF . ' Seq' ) {
array_unshift( $this->mode, self::MODE_LI );
@@ -736,19 +729,19 @@ class XMPReader {
}
/**
- * Start element in MODE_LANG (language alternative)
- * this should always be <rdf:Alt>
- *
- * This tag tends to be used for metadata like describe this
- * picture, which can be translated into multiple languages.
- *
- * XMP supports non-linguistic alternative selections,
- * which are really only used for thumbnails, which
- * we don't care about.
- *
- * @param $elm String namespace . ' ' . tag
- * @throws MWException if we have an element that's not <rdf:Alt>
- */
+ * Start element in MODE_LANG (language alternative)
+ * this should always be <rdf:Alt>
+ *
+ * This tag tends to be used for metadata like describe this
+ * picture, which can be translated into multiple languages.
+ *
+ * XMP supports non-linguistic alternative selections,
+ * which are really only used for thumbnails, which
+ * we don't care about.
+ *
+ * @param string $elm namespace . ' ' . tag
+ * @throws MWException if we have an element that's not <rdf:Alt>
+ */
private function startElementModeLang( $elm ) {
if ( $elm === self::NS_RDF . ' Alt' ) {
array_unshift( $this->mode, self::MODE_LI_LANG );
@@ -759,22 +752,23 @@ class XMPReader {
}
/**
- * Handle an opening element when in MODE_SIMPLE
- *
- * This should not happen often. This is for if a simple element
- * already opened has a child element. Could happen for a
- * qualified element.
- *
- * For example:
- * <exif:DigitalZoomRatio><rdf:Description><rdf:value>0/10</rdf:value>
- * <foo:someQualifier>Bar</foo:someQualifier> </rdf:Description>
- * </exif:DigitalZoomRatio>
- *
- * This method is called when processing the <rdf:Description> element
- *
- * @param $elm String namespace and tag names separated by space.
- * @param $attribs Array Attributes of the element.
- */
+ * Handle an opening element when in MODE_SIMPLE
+ *
+ * This should not happen often. This is for if a simple element
+ * already opened has a child element. Could happen for a
+ * qualified element.
+ *
+ * For example:
+ * <exif:DigitalZoomRatio><rdf:Description><rdf:value>0/10</rdf:value>
+ * <foo:someQualifier>Bar</foo:someQualifier> </rdf:Description>
+ * </exif:DigitalZoomRatio>
+ *
+ * This method is called when processing the <rdf:Description> element
+ *
+ * @param string $elm namespace and tag names separated by space.
+ * @param array $attribs Attributes of the element.
+ * @throws MWException
+ */
private function startElementModeSimple( $elm, $attribs ) {
if ( $elm === self::NS_RDF . ' Description' ) {
// If this value has qualifiers
@@ -800,19 +794,19 @@ class XMPReader {
}
/**
- * Start an element when in MODE_QDESC.
- * This generally happens when a simple element has an inner
- * rdf:Description to hold qualifier elements.
- *
- * For example in:
- * <exif:DigitalZoomRatio><rdf:Description><rdf:value>0/10</rdf:value>
- * <foo:someQualifier>Bar</foo:someQualifier> </rdf:Description>
- * </exif:DigitalZoomRatio>
- * Called when processing the <rdf:value> or <foo:someQualifier>.
- *
- * @param $elm String namespace and tag name separated by a space.
- *
- */
+ * Start an element when in MODE_QDESC.
+ * This generally happens when a simple element has an inner
+ * rdf:Description to hold qualifier elements.
+ *
+ * For example in:
+ * <exif:DigitalZoomRatio><rdf:Description><rdf:value>0/10</rdf:value>
+ * <foo:someQualifier>Bar</foo:someQualifier> </rdf:Description>
+ * </exif:DigitalZoomRatio>
+ * Called when processing the <rdf:value> or <foo:someQualifier>.
+ *
+ * @param string $elm namespace and tag name separated by a space.
+ *
+ */
private function startElementModeQDesc( $elm ) {
if ( $elm === self::NS_RDF . ' value' ) {
return; // do nothing
@@ -824,16 +818,17 @@ class XMPReader {
}
/**
- * Starting an element when in MODE_INITIAL
- * This usually happens when we hit an element inside
- * the outer rdf:Description
- *
- * This is generally where most properties start.
- *
- * @param $ns String Namespace
- * @param $tag String tag name (without namespace prefix)
- * @param $attribs Array array of attributes
- */
+ * Starting an element when in MODE_INITIAL
+ * This usually happens when we hit an element inside
+ * the outer rdf:Description
+ *
+ * This is generally where most properties start.
+ *
+ * @param string $ns Namespace
+ * @param string $tag tag name (without namespace prefix)
+ * @param array $attribs array of attributes
+ * @throws MWException
+ */
private function startElementModeInitial( $ns, $tag, $attribs ) {
if ( $ns !== self::NS_RDF ) {
@@ -877,23 +872,24 @@ class XMPReader {
}
/**
- * Hit an opening element when in a Struct (MODE_STRUCT)
- * This is generally for fields of a compound property.
- *
- * Example of a struct (abbreviated; flash has more properties):
- *
- * <exif:Flash> <rdf:Description> <exif:Fired>True</exif:Fired>
- * <exif:Mode>1</exif:Mode></rdf:Description></exif:Flash>
- *
- * or:
- *
- * <exif:Flash rdf:parseType='Resource'> <exif:Fired>True</exif:Fired>
- * <exif:Mode>1</exif:Mode></exif:Flash>
- *
- * @param $ns String namespace
- * @param $tag String tag name (no ns)
- * @param $attribs Array array of attribs w/ values.
- */
+ * Hit an opening element when in a Struct (MODE_STRUCT)
+ * This is generally for fields of a compound property.
+ *
+ * Example of a struct (abbreviated; flash has more properties):
+ *
+ * <exif:Flash> <rdf:Description> <exif:Fired>True</exif:Fired>
+ * <exif:Mode>1</exif:Mode></rdf:Description></exif:Flash>
+ *
+ * or:
+ *
+ * <exif:Flash rdf:parseType='Resource'> <exif:Fired>True</exif:Fired>
+ * <exif:Mode>1</exif:Mode></exif:Flash>
+ *
+ * @param string $ns namespace
+ * @param string $tag tag name (no ns)
+ * @param array $attribs array of attribs w/ values.
+ * @throws MWException
+ */
private function startElementModeStruct( $ns, $tag, $attribs ) {
if ( $ns !== self::NS_RDF ) {
@@ -929,18 +925,18 @@ class XMPReader {
}
/**
- * opening element in MODE_LI
- * process elements of arrays.
- *
- * Example:
- * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
- * </rdf:Seq> </exif:ISOSpeedRatings>
- * This method is called when we hit the <rdf:li> element.
- *
- * @param $elm String: namespace . ' ' . tagname
- * @param $attribs Array: Attributes. (needed for BAGSTRUCTS)
- * @throws MWException if gets a tag other than <rdf:li>
- */
+ * opening element in MODE_LI
+ * process elements of arrays.
+ *
+ * Example:
+ * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
+ * </rdf:Seq> </exif:ISOSpeedRatings>
+ * This method is called when we hit the <rdf:li> element.
+ *
+ * @param string $elm namespace . ' ' . tagname
+ * @param array $attribs Attributes. (needed for BAGSTRUCTS)
+ * @throws MWException if gets a tag other than <rdf:li>
+ */
private function startElementModeLi( $elm, $attribs ) {
if ( ( $elm ) !== self::NS_RDF . ' li' ) {
throw new MWException( "<rdf:li> expected but got $elm." );
@@ -980,19 +976,19 @@ class XMPReader {
}
/**
- * Opening element in MODE_LI_LANG.
- * process elements of language alternatives
- *
- * Example:
- * <dc:title> <rdf:Alt> <rdf:li xml:lang="x-default">My house
- * </rdf:li> </rdf:Alt> </dc:title>
- *
- * This method is called when we hit the <rdf:li> element.
- *
- * @param $elm String namespace . ' ' . tag
- * @param $attribs array array of elements (most importantly xml:lang)
- * @throws MWException if gets a tag other than <rdf:li> or if no xml:lang
- */
+ * Opening element in MODE_LI_LANG.
+ * process elements of language alternatives
+ *
+ * Example:
+ * <dc:title> <rdf:Alt> <rdf:li xml:lang="x-default">My house
+ * </rdf:li> </rdf:Alt> </dc:title>
+ *
+ * This method is called when we hit the <rdf:li> element.
+ *
+ * @param string $elm namespace . ' ' . tag
+ * @param array $attribs array of elements (most importantly xml:lang)
+ * @throws MWException if gets a tag other than <rdf:li> or if no xml:lang
+ */
private function startElementModeLiLang( $elm, $attribs ) {
if ( $elm !== self::NS_RDF . ' li' ) {
throw new MWException( __METHOD__ . " <rdf:li> expected but got $elm." );
@@ -1015,19 +1011,20 @@ class XMPReader {
}
/**
- * Hits an opening element.
- * Generally just calls a helper based on what MODE we're in.
- * Also does some initial set up for the wrapper element
- *
- * @param $parser XMLParser
- * @param $elm String namespace "<space>" element
- * @param $attribs Array attribute name => value
- */
+ * Hits an opening element.
+ * Generally just calls a helper based on what MODE we're in.
+ * Also does some initial set up for the wrapper element
+ *
+ * @param $parser XMLParser
+ * @param string $elm namespace "<space>" element
+ * @param array $attribs attribute name => value
+ * @throws MWException
+ */
function startElement( $parser, $elm, $attribs ) {
if ( $elm === self::NS_RDF . ' RDF'
|| $elm === 'adobe:ns:meta/ xmpmeta'
- || $elm === 'adobe:ns:meta/ xapmeta')
+ || $elm === 'adobe:ns:meta/ xapmeta' )
{
/* ignore. */
return;
@@ -1057,7 +1054,7 @@ class XMPReader {
if ( count( $this->mode ) === 0 ) {
// This should not happen.
- throw new MWException('Error extracting XMP, '
+ throw new MWException( 'Error extracting XMP, '
. "encountered <$elm> with no mode" );
}
@@ -1095,24 +1092,24 @@ class XMPReader {
break;
default:
throw new MWException( 'StartElement in unknown mode: ' . $this->mode[0] );
- break;
}
}
/**
- * Process attributes.
- * Simple values can be stored as either a tag or attribute
- *
- * Often the initial "<rdf:Description>" tag just has all the simple
- * properties as attributes.
- *
- * @par Example:
- * @code
- * <rdf:Description rdf:about="" xmlns:exif="http://ns.adobe.com/exif/1.0/" exif:DigitalZoomRatio="0/10">
- * @endcode
- *
- * @param $attribs Array attribute=>value array.
- */
+ * Process attributes.
+ * Simple values can be stored as either a tag or attribute
+ *
+ * Often the initial "<rdf:Description>" tag just has all the simple
+ * properties as attributes.
+ *
+ * @par Example:
+ * @code
+ * <rdf:Description rdf:about="" xmlns:exif="http://ns.adobe.com/exif/1.0/" exif:DigitalZoomRatio="0/10">
+ * @endcode
+ *
+ * @param array $attribs attribute=>value array.
+ * @throws MWException
+ */
private function doAttribs( $attribs ) {
// first check for rdf:parseType attribute, as that can change
@@ -1126,8 +1123,6 @@ class XMPReader {
$this->mode[0] = self::MODE_QDESC;
}
foreach ( $attribs as $name => $val ) {
-
-
if ( strpos( $name, ' ' ) === false ) {
// This shouldn't happen, but so far some old software forgets namespace
// on rdf:about.
@@ -1155,16 +1150,16 @@ class XMPReader {
}
/**
- * Given an extracted value, save it to results array
- *
- * note also uses $this->ancestorStruct and
- * $this->processingArray to determine what name to
- * save the value under. (in addition to $tag).
- *
- * @param $ns String namespace of tag this is for
- * @param $tag String tag name
- * @param $val String value to save
- */
+ * Given an extracted value, save it to results array
+ *
+ * note also uses $this->ancestorStruct and
+ * $this->processingArray to determine what name to
+ * save the value under. (in addition to $tag).
+ *
+ * @param string $ns namespace of tag this is for
+ * @param string $tag tag name
+ * @param string $val value to save
+ */
private function saveValue( $ns, $tag, $val ) {
$info =& $this->items[$ns][$tag];
diff --git a/includes/media/XMPInfo.php b/includes/media/XMPInfo.php
index 83b8a102..102547ff 100644
--- a/includes/media/XMPInfo.php
+++ b/includes/media/XMPInfo.php
@@ -22,20 +22,20 @@
*/
/**
-* This class is just a container for a big array
-* used by XMPReader to determine which XMP items to
-* extract.
-*/
+ * This class is just a container for a big array
+ * used by XMPReader to determine which XMP items to
+ * extract.
+ */
class XMPInfo {
/** get the items array
* @return Array XMP item configuration array.
- */
- public static function getItems ( ) {
+ */
+ public static function getItems () {
if( !self::$ranHooks ) {
// This is for if someone makes a custom metadata extension.
// For example, a medical wiki might want to decode DICOM xmp properties.
- wfRunHooks('XMPGetInfo', Array(&self::$items));
+ wfRunHooks( 'XMPGetInfo', Array( &self::$items ) );
self::$ranHooks = true; // Only want to do this once.
}
return self::$items;
@@ -44,26 +44,25 @@ class XMPInfo {
static private $ranHooks = false;
/**
- * XMPInfo::$items keeps a list of all the items
- * we are interested to extract, as well as
- * information about the item like what type
- * it is.
- *
- * Format is an array of namespaces,
- * each containing an array of tags
- * each tag is an array of information about the
- * tag, including:
- * * map_group - what group (used for precedence during conflicts)
- * * mode - What type of item (self::MODE_SIMPLE usually, see above for all values)
- * * validate - method to validate input. Could also post-process the input. A string value is assumed to be a static method of XMPValidate. Can also take a array( 'className', 'methodName' ).
- * * choices - array of potential values (format of 'value' => true ). Only used with validateClosed
- * * rangeLow and rangeHigh - alternative to choices for numeric ranges. Again for validateClosed only.
- * * children - for MODE_STRUCT items, allowed children.
- * * structPart - Indicates that this element can only appear as a member of a structure.
- *
- * currently this just has a bunch of exif values as this class is only half-done
- */
-
+ * XMPInfo::$items keeps a list of all the items
+ * we are interested to extract, as well as
+ * information about the item like what type
+ * it is.
+ *
+ * Format is an array of namespaces,
+ * each containing an array of tags
+ * each tag is an array of information about the
+ * tag, including:
+ * * map_group - what group (used for precedence during conflicts)
+ * * mode - What type of item (self::MODE_SIMPLE usually, see above for all values)
+ * * validate - method to validate input. Could also post-process the input. A string value is assumed to be a static method of XMPValidate. Can also take a array( 'className', 'methodName' ).
+ * * choices - array of potential values (format of 'value' => true ). Only used with validateClosed
+ * * rangeLow and rangeHigh - alternative to choices for numeric ranges. Again for validateClosed only.
+ * * children - for MODE_STRUCT items, allowed children.
+ * * structPart - Indicates that this element can only appear as a member of a structure.
+ *
+ * currently this just has a bunch of exif values as this class is only half-done
+ */
static private $items = array(
'http://ns.adobe.com/exif/1.0/' => array(
'ApertureValue' => array(
@@ -260,7 +259,7 @@ class XMPInfo {
'mode' => XMPReader::MODE_SIMPLE,
'validate' => 'validateDate',
),
- 'DateTimeDigitized' => array( /* xmp:CreateDate */
+ 'DateTimeDigitized' => array( /* xmp:CreateDate */
'map_group' => 'exif',
'mode' => XMPReader::MODE_SIMPLE,
'validate' => 'validateDate',
@@ -552,12 +551,12 @@ class XMPInfo {
'map_group' => 'exif',
'mode' => XMPReader::MODE_LANG,
),
- 'DateTime' => array( /* proper prop is xmp:ModifyDate */
+ 'DateTime' => array( /* proper prop is xmp:ModifyDate */
'map_group' => 'exif',
'mode' => XMPReader::MODE_SIMPLE,
'validate' => 'validateDate',
),
- 'ImageDescription' => array( /* proper one is dc:description */
+ 'ImageDescription' => array( /* proper one is dc:description */
'map_group' => 'exif',
'mode' => XMPReader::MODE_LANG,
),
@@ -622,7 +621,7 @@ class XMPInfo {
'mode' => XMPReader::MODE_SIMPLE,
'validate' => 'validateInteger',
),
- 'Software' => array( /* see xmp:CreatorTool */
+ 'Software' => array( /* see xmp:CreatorTool */
'map_group' => 'exif',
'mode' => XMPReader::MODE_SIMPLE,
),
@@ -669,7 +668,7 @@ class XMPInfo {
* 'validate' => 'validateClosed',
* 'choices' => array( '1' => true, '2' => true ),
* ),
- */
+ */
),
'http://ns.adobe.com/exif/1.0/aux/' => array(
'Lens' => array(
diff --git a/includes/media/XMPValidate.php b/includes/media/XMPValidate.php
index 5ce3c00b..f98f0b57 100644
--- a/includes/media/XMPValidate.php
+++ b/includes/media/XMPValidate.php
@@ -22,32 +22,32 @@
*/
/**
-* This contains some static methods for
-* validating XMP properties. See XMPInfo and XMPReader classes.
-*
-* Each of these functions take the same parameters
-* * an info array which is a subset of the XMPInfo::items array
-* * A value (passed as reference) to validate. This can be either a
-* simple value or an array
-* * A boolean to determine if this is validating a simple or complex values
-*
-* It should be noted that when an array is being validated, typically the validation
-* function is called once for each value, and then once at the end for the entire array.
-*
-* These validation functions can also be used to modify the data. See the gps and flash one's
-* for example.
-*
-* @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart1.pdf starting at pg 28
-* @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart2.pdf starting at pg 11
-*/
+ * This contains some static methods for
+ * validating XMP properties. See XMPInfo and XMPReader classes.
+ *
+ * Each of these functions take the same parameters
+ * * an info array which is a subset of the XMPInfo::items array
+ * * A value (passed as reference) to validate. This can be either a
+ * simple value or an array
+ * * A boolean to determine if this is validating a simple or complex values
+ *
+ * It should be noted that when an array is being validated, typically the validation
+ * function is called once for each value, and then once at the end for the entire array.
+ *
+ * These validation functions can also be used to modify the data. See the gps and flash one's
+ * for example.
+ *
+ * @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart1.pdf starting at pg 28
+ * @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart2.pdf starting at pg 11
+ */
class XMPValidate {
/**
- * function to validate boolean properties ( True or False )
- *
- * @param $info Array information about current property
- * @param &$val Mixed current value to validate
- * @param $standalone Boolean if this is a simple property or array
- */
+ * function to validate boolean properties ( True or False )
+ *
+ * @param array $info information about current property
+ * @param &$val Mixed current value to validate
+ * @param $standalone Boolean if this is a simple property or array
+ */
public static function validateBoolean( $info, &$val, $standalone ) {
if ( !$standalone ) {
// this only validates standalone properties, not arrays, etc
@@ -61,12 +61,12 @@ class XMPValidate {
}
/**
- * function to validate rational properties ( 12/10 )
- *
- * @param $info Array information about current property
- * @param &$val Mixed current value to validate
- * @param $standalone Boolean if this is a simple property or array
- */
+ * function to validate rational properties ( 12/10 )
+ *
+ * @param array $info information about current property
+ * @param &$val Mixed current value to validate
+ * @param $standalone Boolean if this is a simple property or array
+ */
public static function validateRational( $info, &$val, $standalone ) {
if ( !$standalone ) {
// this only validates standalone properties, not arrays, etc
@@ -80,23 +80,23 @@ class XMPValidate {
}
/**
- * function to validate rating properties -1, 0-5
- *
- * if its outside of range put it into range.
- *
- * @see MWG spec
- * @param $info Array information about current property
- * @param &$val Mixed current value to validate
- * @param $standalone Boolean if this is a simple property or array
- */
+ * function to validate rating properties -1, 0-5
+ *
+ * if its outside of range put it into range.
+ *
+ * @see MWG spec
+ * @param array $info information about current property
+ * @param &$val Mixed current value to validate
+ * @param $standalone Boolean if this is a simple property or array
+ */
public static function validateRating( $info, &$val, $standalone ) {
if ( !$standalone ) {
// this only validates standalone properties, not arrays, etc
return;
}
if ( !preg_match( '/^[-+]?\d*(?:\.?\d*)$/D', $val )
- || !is_numeric($val)
- ) {
+ || !is_numeric( $val )
+ ) {
wfDebugLog( 'XMP', __METHOD__ . " Expected rating but got $val" );
$val = null;
return;
@@ -106,12 +106,12 @@ class XMPValidate {
// We do < 0 here instead of < -1 here, since
// the values between 0 and -1 are also illegal
// as -1 is meant as a special reject rating.
- wfDebugLog( 'XMP', __METHOD__ . " Rating too low, setting to -1 (Rejected)");
+ wfDebugLog( 'XMP', __METHOD__ . " Rating too low, setting to -1 (Rejected)" );
$val = '-1';
return;
}
if ( $nVal > 5 ) {
- wfDebugLog( 'XMP', __METHOD__ . " Rating too high, setting to 5");
+ wfDebugLog( 'XMP', __METHOD__ . " Rating too high, setting to 5" );
$val = '5';
return;
}
@@ -119,12 +119,12 @@ class XMPValidate {
}
/**
- * function to validate integers
- *
- * @param $info Array information about current property
- * @param &$val Mixed current value to validate
- * @param $standalone Boolean if this is a simple property or array
- */
+ * function to validate integers
+ *
+ * @param array $info information about current property
+ * @param &$val Mixed current value to validate
+ * @param $standalone Boolean if this is a simple property or array
+ */
public static function validateInteger( $info, &$val, $standalone ) {
if ( !$standalone ) {
// this only validates standalone properties, not arrays, etc
@@ -138,13 +138,13 @@ class XMPValidate {
}
/**
- * function to validate properties with a fixed number of allowed
- * choices. (closed choice)
- *
- * @param $info Array information about current property
- * @param &$val Mixed current value to validate
- * @param $standalone Boolean if this is a simple property or array
- */
+ * function to validate properties with a fixed number of allowed
+ * choices. (closed choice)
+ *
+ * @param array $info information about current property
+ * @param &$val Mixed current value to validate
+ * @param $standalone Boolean if this is a simple property or array
+ */
public static function validateClosed( $info, &$val, $standalone ) {
if ( !$standalone ) {
// this only validates standalone properties, not arrays, etc
@@ -153,7 +153,7 @@ class XMPValidate {
//check if its in a numeric range
$inRange = false;
- if ( isset( $info['rangeLow'] )
+ if ( isset( $info['rangeLow'] )
&& isset( $info['rangeHigh'] )
&& is_numeric( $val )
&& ( intval( $val ) <= $info['rangeHigh'] )
@@ -169,12 +169,12 @@ class XMPValidate {
}
/**
- * function to validate and modify flash structure
- *
- * @param $info Array information about current property
- * @param &$val Mixed current value to validate
- * @param $standalone Boolean if this is a simple property or array
- */
+ * function to validate and modify flash structure
+ *
+ * @param array $info information about current property
+ * @param &$val Mixed current value to validate
+ * @param $standalone Boolean if this is a simple property or array
+ */
public static function validateFlash( $info, &$val, $standalone ) {
if ( $standalone ) {
// this only validates flash structs, not individual properties
@@ -198,17 +198,17 @@ class XMPValidate {
}
/**
- * function to validate LangCode properties ( en-GB, etc )
- *
- * This is just a naive check to make sure it somewhat looks like a lang code.
- *
- * @see rfc 3066
- * @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart1.pdf page 30 (section 8.2.2.5)
- *
- * @param $info Array information about current property
- * @param &$val Mixed current value to validate
- * @param $standalone Boolean if this is a simple property or array
- */
+ * function to validate LangCode properties ( en-GB, etc )
+ *
+ * This is just a naive check to make sure it somewhat looks like a lang code.
+ *
+ * @see rfc 3066
+ * @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart1.pdf page 30 (section 8.2.2.5)
+ *
+ * @param array $info information about current property
+ * @param &$val Mixed current value to validate
+ * @param $standalone Boolean if this is a simple property or array
+ */
public static function validateLangCode( $info, &$val, $standalone ) {
if ( !$standalone ) {
// this only validates standalone properties, not arrays, etc
@@ -223,22 +223,22 @@ class XMPValidate {
}
/**
- * function to validate date properties, and convert to (partial) Exif format.
- *
- * Dates can be one of the following formats:
- * YYYY
- * YYYY-MM
- * YYYY-MM-DD
- * YYYY-MM-DDThh:mmTZD
- * YYYY-MM-DDThh:mm:ssTZD
- * YYYY-MM-DDThh:mm:ss.sTZD
- *
- * @param $info Array information about current property
- * @param &$val Mixed current value to validate. Converts to TS_EXIF as a side-effect.
- * in cases where there's only a partial date, it will give things like
- * 2011:04.
- * @param $standalone Boolean if this is a simple property or array
- */
+ * function to validate date properties, and convert to (partial) Exif format.
+ *
+ * Dates can be one of the following formats:
+ * YYYY
+ * YYYY-MM
+ * YYYY-MM-DD
+ * YYYY-MM-DDThh:mmTZD
+ * YYYY-MM-DDThh:mm:ssTZD
+ * YYYY-MM-DDThh:mm:ss.sTZD
+ *
+ * @param array $info information about current property
+ * @param &$val Mixed current value to validate. Converts to TS_EXIF as a side-effect.
+ * in cases where there's only a partial date, it will give things like
+ * 2011:04.
+ * @param $standalone Boolean if this is a simple property or array
+ */
public static function validateDate( $info, &$val, $standalone ) {
if ( !$standalone ) {
// this only validates standalone properties, not arrays, etc
@@ -247,8 +247,8 @@ class XMPValidate {
$res = array();
if ( !preg_match(
/* ahh! scary regex... */
- '/^([0-3]\d{3})(?:-([01]\d)(?:-([0-3]\d)(?:T([0-2]\d):([0-6]\d)(?::([0-6]\d)(?:\.\d+)?)?([-+]\d{2}:\d{2}|Z)?)?)?)?$/D'
- , $val, $res)
+ '/^([0-3]\d{3})(?:-([01]\d)(?:-([0-3]\d)(?:T([0-2]\d):([0-6]\d)(?::([0-6]\d)(?:\.\d+)?)?([-+]\d{2}:\d{2}|Z)?)?)?)?$/D',
+ $val, $res )
) {
wfDebugLog( 'XMP', __METHOD__ . " Expected date but got $val" );
$val = null;
@@ -295,7 +295,6 @@ class XMPValidate {
return;
}
-
// Extra check for empty string necessary due to TZ but no second case.
$stripSeconds = false;
if ( !isset( $res[6] ) || $res[6] === '' ) {
@@ -331,7 +330,7 @@ class XMPValidate {
* @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart2.pdf
* section 1.2.7.4 on page 23
*
- * @param $info Array unused (info about prop)
+ * @param array $info unused (info about prop)
* @param &$val String GPS string in either DDD,MM,SSk or
* or DDD,MM.mmk form
* @param $standalone Boolean if its a simple prop (should always be true)
@@ -342,7 +341,7 @@ class XMPValidate {
}
$m = array();
- if ( preg_match(
+ if ( preg_match(
'/(\d{1,3}),(\d{1,2}),(\d{1,2})([NWSE])/D',
$val, $m )
) {
@@ -354,7 +353,7 @@ class XMPValidate {
}
$val = $coord;
return;
- } elseif ( preg_match(
+ } elseif ( preg_match(
'/(\d{1,3}),(\d{1,2}(?:.\d*)?)([NWSE])/D',
$val, $m )
) {
@@ -367,7 +366,7 @@ class XMPValidate {
return;
} else {
- wfDebugLog( 'XMP', __METHOD__
+ wfDebugLog( 'XMP', __METHOD__
. " Expected GPSCoordinate, but got $val." );
$val = null;
return;
diff --git a/includes/mime.types b/includes/mime.types
index 5b64201f..a89d229a 100644
--- a/includes/mime.types
+++ b/includes/mime.types
@@ -65,6 +65,7 @@ audio/basic au snd
audio/midi mid midi kar
audio/mpeg mpga mp2 mp3
audio/ogg oga ogg spx
+video/webm webm
audio/webm webm
audio/x-aiff aif aiff aifc
audio/x-matroska mka mkv
@@ -120,7 +121,6 @@ video/mpeg mpeg mpg mpe
video/ogg ogv ogm ogg
video/quicktime qt mov
video/vnd.mpegurl mxu
-video/webm webm
video/x-flv flv
video/x-matroska mkv mka
video/x-msvideo avi
diff --git a/includes/mobile/DeviceDetection.php b/includes/mobile/DeviceDetection.php
deleted file mode 100644
index 262665be..00000000
--- a/includes/mobile/DeviceDetection.php
+++ /dev/null
@@ -1,459 +0,0 @@
-<?php
-/**
- * Mobile device detection code
- *
- * Copyright © 2011 Patrick Reilly
- * http://www.mediawiki.org/
- *
- * 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
- */
-
-/**
- * Base for classes describing devices and their capabilities
- * @since 1.20
- */
-interface IDeviceProperties {
- /**
- * @return string: 'html' or 'wml'
- */
- function format();
-
- /**
- * @return bool
- */
- function supportsJavaScript();
-
- /**
- * @return bool
- */
- function supportsJQuery();
-
- /**
- * @return bool
- */
- function disableZoom();
-}
-
-/**
- * @since 1.20
- */
-interface IDeviceDetector {
- /**
- * @param $userAgent
- * @param string $acceptHeader
- * @return IDeviceProperties
- */
- function detectDeviceProperties( $userAgent, $acceptHeader = '' );
-
- /**
- * @param $deviceName
- * @return IDeviceProperties
- */
- function getDeviceProperties( $deviceName );
-
- /**
- * @param $userAgent string
- * @param $acceptHeader string
- * @return string
- */
- function detectDeviceName( $userAgent, $acceptHeader = '' );
-}
-
-/**
- * MediaWiki's default IDeviceProperties implementation
- */
-final class DeviceProperties implements IDeviceProperties {
- private $device;
-
- public function __construct( array $deviceCapabilities ) {
- $this->device = $deviceCapabilities;
- }
-
- /**
- * @return string
- */
- function format() {
- return $this->device['view_format'];
- }
-
- /**
- * @return bool
- */
- function supportsJavaScript() {
- return $this->device['supports_javascript'];
- }
-
- /**
- * @return bool
- */
- function supportsJQuery() {
- return $this->device['supports_jquery'];
- }
-
- /**
- * @return bool
- */
- function disableZoom() {
- return $this->device['disable_zoom'];
- }
-}
-
-/**
- * Provides abstraction for a device.
- * A device can select which format a request should receive and
- * may be extended to provide access to particular device functionality.
- * @since 1.20
- */
-class DeviceDetection implements IDeviceDetector {
-
- private static $formats = array (
- 'html' => array (
- 'view_format' => 'html',
- 'css_file_name' => 'default',
- 'supports_javascript' => false,
- 'supports_jquery' => false,
- 'disable_zoom' => true,
- ),
- 'capable' => array (
- 'view_format' => 'html',
- 'css_file_name' => 'default',
- 'supports_javascript' => true,
- 'supports_jquery' => true,
- 'disable_zoom' => true,
- ),
- 'webkit' => array (
- 'view_format' => 'html',
- 'css_file_name' => 'webkit',
- 'supports_javascript' => true,
- 'supports_jquery' => true,
- 'disable_zoom' => false,
- ),
- 'ie' => array (
- 'view_format' => 'html',
- 'css_file_name' => 'default',
- 'supports_javascript' => true,
- 'supports_jquery' => true,
- 'disable_zoom' => false,
- ),
- 'android' => array (
- 'view_format' => 'html',
- 'css_file_name' => 'android',
- 'supports_javascript' => true,
- 'supports_jquery' => true,
- 'disable_zoom' => false,
- ),
- 'iphone' => array (
- 'view_format' => 'html',
- 'css_file_name' => 'iphone',
- 'supports_javascript' => true,
- 'supports_jquery' => true,
- 'disable_zoom' => false,
- ),
- 'iphone2' => array (
- 'view_format' => 'html',
- 'css_file_name' => 'iphone2',
- 'supports_javascript' => true,
- 'supports_jquery' => true,
- 'disable_zoom' => true,
- ),
- 'native_iphone' => array (
- 'view_format' => 'html',
- 'css_file_name' => 'default',
- 'supports_javascript' => true,
- 'supports_jquery' => true,
- 'disable_zoom' => false,
- ),
- 'palm_pre' => array (
- 'view_format' => 'html',
- 'css_file_name' => 'palm_pre',
- 'supports_javascript' => true,
- 'supports_jquery' => false,
- 'disable_zoom' => true,
- ),
- 'kindle' => array (
- 'view_format' => 'html',
- 'css_file_name' => 'kindle',
- 'supports_javascript' => false,
- 'supports_jquery' => false,
- 'disable_zoom' => true,
- ),
- 'kindle2' => array (
- 'view_format' => 'html',
- 'css_file_name' => 'kindle',
- 'supports_javascript' => false,
- 'supports_jquery' => false,
- 'disable_zoom' => true,
- ),
- 'blackberry' => array (
- 'view_format' => 'html',
- 'css_file_name' => 'blackberry',
- 'supports_javascript' => true,
- 'supports_jquery' => false,
- 'disable_zoom' => true,
- ),
- 'blackberry-lt5' => array (
- 'view_format' => 'html',
- 'css_file_name' => 'blackberry',
- 'supports_javascript' => false,
- 'supports_jquery' => false,
- 'disable_zoom' => true,
- ),
- 'netfront' => array (
- 'view_format' => 'html',
- 'css_file_name' => 'simple',
- 'supports_javascript' => false,
- 'supports_jquery' => false,
- 'disable_zoom' => true,
- ),
- 'wap2' => array (
- 'view_format' => 'html',
- 'css_file_name' => 'simple',
- 'supports_javascript' => false,
- 'supports_jquery' => false,
- 'disable_zoom' => true,
- ),
- 'psp' => array (
- 'view_format' => 'html',
- 'css_file_name' => 'psp',
- 'supports_javascript' => false,
- 'supports_jquery' => false,
- 'disable_zoom' => true,
- ),
- 'ps3' => array (
- 'view_format' => 'html',
- 'css_file_name' => 'simple',
- 'supports_javascript' => false,
- 'supports_jquery' => false,
- 'disable_zoom' => true,
- ),
- 'wii' => array (
- 'view_format' => 'html',
- 'css_file_name' => 'wii',
- 'supports_javascript' => true,
- 'supports_jquery' => true,
- 'disable_zoom' => true,
- ),
- 'operamini' => array (
- 'view_format' => 'html',
- 'css_file_name' => 'operamini',
- 'supports_javascript' => false,
- 'supports_jquery' => false,
- 'disable_zoom' => true,
- ),
- 'operamobile' => array (
- 'view_format' => 'html',
- 'css_file_name' => 'operamobile',
- 'supports_javascript' => true,
- 'supports_jquery' => true,
- 'disable_zoom' => true,
- ),
- 'nokia' => array (
- 'view_format' => 'html',
- 'css_file_name' => 'nokia',
- 'supports_javascript' => true,
- 'supports_jquery' => false,
- 'disable_zoom' => true,
- ),
- 'wml' => array (
- 'view_format' => 'wml',
- 'css_file_name' => null,
- 'supports_javascript' => false,
- 'supports_jquery' => false,
- 'disable_zoom' => true,
- ),
- );
-
- /**
- * Returns an instance of detection class, overridable by extensions
- * @return IDeviceDetector
- */
- public static function factory() {
- global $wgDeviceDetectionClass;
-
- static $instance = null;
- if ( !$instance ) {
- $instance = new $wgDeviceDetectionClass();
- }
- return $instance;
- }
-
- /**
- * @deprecated: Deprecated, will be removed once detectDeviceProperties() will be deployed everywhere on WMF
- * @param $userAgent
- * @param string $acceptHeader
- * @return array
- */
- public function detectDevice( $userAgent, $acceptHeader = '' ) {
- $formatName = $this->detectFormatName( $userAgent, $acceptHeader );
- return $this->getDevice( $formatName );
- }
-
- /**
- * @param $userAgent
- * @param string $acceptHeader
- * @return IDeviceProperties
- */
- public function detectDeviceProperties( $userAgent, $acceptHeader = '' ) {
- $deviceName = $this->detectDeviceName( $userAgent, $acceptHeader );
- return $this->getDeviceProperties( $deviceName );
- }
-
- /**
- * @deprecated: Deprecated, will be removed once detectDeviceProperties() will be deployed everywhere on WMF
- * @param $formatName
- * @return array
- */
- public function getDevice( $formatName ) {
- return ( isset( self::$formats[$formatName] ) ) ? self::$formats[$formatName] : array();
- }
-
- /**
- * @param $deviceName
- * @return IDeviceProperties
- */
- public function getDeviceProperties( $deviceName ) {
- if ( isset( self::$formats[$deviceName] ) ) {
- return new DeviceProperties( self::$formats[$deviceName] );
- } else {
- return new DeviceProperties( array(
- 'view_format' => 'html',
- 'css_file_name' => 'default',
- 'supports_javascript' => true,
- 'supports_jquery' => true,
- 'disable_zoom' => true,
- ) );
- }
- }
-
- /**
- * @deprecated: Renamed to detectDeviceName()
- * @param $userAgent string
- * @param $acceptHeader string
- * @return string
- */
- public function detectFormatName( $userAgent, $acceptHeader = '' ) {
- return $this->detectDeviceName( $userAgent, $acceptHeader );
- }
-
- /**
- * @param $userAgent string
- * @param $acceptHeader string
- * @return string
- */
- public function detectDeviceName( $userAgent, $acceptHeader = '' ) {
- wfProfileIn( __METHOD__ );
-
- $deviceName = '';
- if ( preg_match( '/Android/', $userAgent ) ) {
- $deviceName = 'android';
- if ( strpos( $userAgent, 'Opera Mini' ) !== false ) {
- $deviceName = 'operamini';
- } elseif ( strpos( $userAgent, 'Opera Mobi' ) !== false ) {
- $deviceName = 'operamobile';
- }
- } elseif ( preg_match( '/MSIE 9.0/', $userAgent ) ||
- preg_match( '/MSIE 8.0/', $userAgent ) ) {
- $deviceName = 'ie';
- } elseif( preg_match( '/MSIE/', $userAgent ) ) {
- $deviceName = 'html';
- } elseif ( strpos( $userAgent, 'Opera Mobi' ) !== false ) {
- $deviceName = 'operamobile';
- } elseif ( preg_match( '/iPad.* Safari/', $userAgent ) ) {
- $deviceName = 'iphone';
- } elseif ( preg_match( '/iPhone.* Safari/', $userAgent ) ) {
- if ( strpos( $userAgent, 'iPhone OS 2' ) !== false ) {
- $deviceName = 'iphone2';
- } else {
- $deviceName = 'iphone';
- }
- } elseif ( preg_match( '/iPhone/', $userAgent ) ) {
- if ( strpos( $userAgent, 'Opera' ) !== false ) {
- $deviceName = 'operamini';
- } else {
- $deviceName = 'native_iphone';
- }
- } elseif ( preg_match( '/WebKit/', $userAgent ) ) {
- if ( preg_match( '/Series60/', $userAgent ) ) {
- $deviceName = 'nokia';
- } elseif ( preg_match( '/webOS/', $userAgent ) ) {
- $deviceName = 'palm_pre';
- } else {
- $deviceName = 'webkit';
- }
- } elseif ( preg_match( '/Opera/', $userAgent ) ) {
- if ( strpos( $userAgent, 'Nintendo Wii' ) !== false ) {
- $deviceName = 'wii';
- } elseif ( strpos( $userAgent, 'Opera Mini' ) !== false ) {
- $deviceName = 'operamini';
- } else {
- $deviceName = 'operamobile';
- }
- } elseif ( preg_match( '/Kindle\/1.0/', $userAgent ) ) {
- $deviceName = 'kindle';
- } elseif ( preg_match( '/Kindle\/2.0/', $userAgent ) ) {
- $deviceName = 'kindle2';
- } elseif ( preg_match( '/Firefox/', $userAgent ) ) {
- $deviceName = 'capable';
- } elseif ( preg_match( '/NetFront/', $userAgent ) ) {
- $deviceName = 'netfront';
- } elseif ( preg_match( '/SEMC-Browser/', $userAgent ) ) {
- $deviceName = 'wap2';
- } elseif ( preg_match( '/Series60/', $userAgent ) ) {
- $deviceName = 'wap2';
- } elseif ( preg_match( '/PlayStation Portable/', $userAgent ) ) {
- $deviceName = 'psp';
- } elseif ( preg_match( '/PLAYSTATION 3/', $userAgent ) ) {
- $deviceName = 'ps3';
- } elseif ( preg_match( '/SAMSUNG/', $userAgent ) ) {
- $deviceName = 'capable';
- } elseif ( preg_match( '/BlackBerry/', $userAgent ) ) {
- if( preg_match( '/BlackBerry[^\/]*\/[1-4]\./', $userAgent ) ) {
- $deviceName = 'blackberry-lt5';
- } else {
- $deviceName = 'blackberry';
- }
- }
-
- if ( $deviceName === '' ) {
- if ( strpos( $acceptHeader, 'application/vnd.wap.xhtml+xml' ) !== false ) {
- // Should be wap2
- $deviceName = 'html';
- } elseif ( strpos( $acceptHeader, 'vnd.wap.wml' ) !== false ) {
- $deviceName = 'wml';
- } else {
- $deviceName = 'html';
- }
- }
- wfProfileOut( __METHOD__ );
- return $deviceName;
- }
-
- /**
- * @return array: List of all device-specific stylesheets
- */
- public function getCssFiles() {
- $files = array();
-
- foreach ( self::$formats as $dev ) {
- if ( isset( $dev['css_file_name'] ) ) {
- $files[] = $dev['css_file_name'];
- }
- }
- return array_unique( $files );
- }
-}
diff --git a/includes/normal/Makefile b/includes/normal/Makefile
index f0c340f6..66348ee3 100644
--- a/includes/normal/Makefile
+++ b/includes/normal/Makefile
@@ -8,7 +8,7 @@
# Explicitly using Unicode 6.0
BASE=http://www.unicode.org/Public/6.0.0/ucd
-# Can override to php-cli or php5 or whatevah
+# Can override to php-cli or php5 or whatever
PHP=php
#PHP=php-cli
diff --git a/includes/normal/RandomTest.php b/includes/normal/RandomTest.php
index 23471e94..9dc1c861 100644
--- a/includes/normal/RandomTest.php
+++ b/includes/normal/RandomTest.php
@@ -26,7 +26,7 @@
* @ingroup UtfNormal
*/
-if( php_sapi_name() != 'cli' ) {
+if( PHP_SAPI != 'cli' ) {
die( "Run me from the command line please.\n" );
}
diff --git a/includes/normal/Utf8CaseGenerate.php b/includes/normal/Utf8CaseGenerate.php
index 368d0bcd..adc3ef22 100644
--- a/includes/normal/Utf8CaseGenerate.php
+++ b/includes/normal/Utf8CaseGenerate.php
@@ -25,7 +25,7 @@
* @ingroup UtfNormal
*/
-if( php_sapi_name() != 'cli' ) {
+if( PHP_SAPI != 'cli' ) {
die( "Run me from the command line please.\n" );
}
@@ -49,7 +49,7 @@ while( false !== ($line = fgets( $in ) ) ) {
$name = $columns[1];
$simpleUpper = $columns[12];
$simpleLower = $columns[13];
-
+
$source = codepointToUtf8( hexdec( $codepoint ) );
if( $simpleUpper ) {
$wikiUpperChars[$source] = codepointToUtf8( hexdec( $simpleUpper ) );
@@ -60,7 +60,7 @@ while( false !== ($line = fgets( $in ) ) ) {
}
fclose( $in );
-$out = fopen("Utf8Case.php", "wt");
+$out = fopen( "Utf8Case.php", "wt" );
if( $out ) {
$outUpperChars = escapeArray( $wikiUpperChars );
$outLowerChars = escapeArray( $wikiLowerChars );
diff --git a/includes/normal/Utf8Test.php b/includes/normal/Utf8Test.php
index 6eae6e72..c5c1be59 100644
--- a/includes/normal/Utf8Test.php
+++ b/includes/normal/Utf8Test.php
@@ -27,6 +27,10 @@
/** */
+if ( PHP_SAPI != 'cli' ) {
+ die( "Run me from the command line please.\n" );
+}
+
require_once 'UtfNormalDefines.php';
require_once 'UtfNormalUtil.php';
require_once 'UtfNormal.php';
@@ -34,9 +38,6 @@ mb_internal_encoding( "utf-8" );
$verbose = false;
#$verbose = true;
-if( php_sapi_name() != 'cli' ) {
- die( "Run me from the command line please.\n" );
-}
$in = fopen( "UTF-8-test.txt", "rt" );
if( !$in ) {
diff --git a/includes/normal/UtfNormal.php b/includes/normal/UtfNormal.php
index 08f85bd3..77ddb79b 100644
--- a/includes/normal/UtfNormal.php
+++ b/includes/normal/UtfNormal.php
@@ -37,7 +37,7 @@ define( 'NORMALIZE_INTL', function_exists( 'normalizer_normalize' ) );
*
* Not as fast as I'd like, but should be usable for most purposes.
* UtfNormal::toNFC() will bail early if given ASCII text or text
- * it can quickly deterimine is already normalized.
+ * it can quickly determine is already normalized.
*
* All functions can be called static.
*
@@ -73,7 +73,7 @@ class UtfNormal {
* Fast return for pure ASCII strings; some lesser optimizations for
* strings containing only known-good characters. Not as fast as toNFC().
*
- * @param $string String: a UTF-8 string
+ * @param string $string a UTF-8 string
* @return string a clean, shiny, normalized UTF-8 string
*/
static function cleanUp( $string ) {
@@ -114,7 +114,7 @@ class UtfNormal {
* Fast return for pure ASCII strings; some lesser optimizations for
* strings containing only known-good characters.
*
- * @param $string String: a valid UTF-8 string. Input is not validated.
+ * @param string $string a valid UTF-8 string. Input is not validated.
* @return string a UTF-8 string in normal form C
*/
static function toNFC( $string ) {
@@ -132,7 +132,7 @@ class UtfNormal {
* Convert a UTF-8 string to normal form D, canonical decomposition.
* Fast return for pure ASCII strings.
*
- * @param $string String: a valid UTF-8 string. Input is not validated.
+ * @param string $string a valid UTF-8 string. Input is not validated.
* @return string a UTF-8 string in normal form D
*/
static function toNFD( $string ) {
@@ -151,7 +151,7 @@ class UtfNormal {
* This may cause irreversible information loss, use judiciously.
* Fast return for pure ASCII strings.
*
- * @param $string String: a valid UTF-8 string. Input is not validated.
+ * @param string $string a valid UTF-8 string. Input is not validated.
* @return string a UTF-8 string in normal form KC
*/
static function toNFKC( $string ) {
@@ -170,7 +170,7 @@ class UtfNormal {
* This may cause irreversible information loss, use judiciously.
* Fast return for pure ASCII strings.
*
- * @param $string String: a valid UTF-8 string. Input is not validated.
+ * @param string $string a valid UTF-8 string. Input is not validated.
* @return string a UTF-8 string in normal form KD
*/
static function toNFKD( $string ) {
@@ -197,7 +197,7 @@ class UtfNormal {
/**
* Returns true if the string is _definitely_ in NFC.
* Returns false if not or uncertain.
- * @param $string String: a valid UTF-8 string. Input is not validated.
+ * @param string $string a valid UTF-8 string. Input is not validated.
* @return bool
*/
static function quickIsNFC( $string ) {
@@ -237,7 +237,7 @@ class UtfNormal {
/**
* Returns true if the string is _definitely_ in NFC.
* Returns false if not or uncertain.
- * @param $string String: a UTF-8 string, altered on output to be valid UTF-8 safe for XML.
+ * @param string $string a UTF-8 string, altered on output to be valid UTF-8 safe for XML.
* @return bool
*/
static function quickIsNFCVerify( &$string ) {
@@ -503,8 +503,8 @@ class UtfNormal {
* (depending on which decomposition map is passed to us).
* Input is assumed to be *valid* UTF-8. Invalid code will break.
* @private
- * @param $string String: valid UTF-8 string
- * @param $map Array: hash of expanded decomposition map
+ * @param string $string valid UTF-8 string
+ * @param array $map hash of expanded decomposition map
* @return string a UTF-8 string decomposed, not yet normalized (needs sorting)
*/
static function fastDecompose( $string, $map ) {
@@ -564,7 +564,7 @@ class UtfNormal {
* Sorts combining characters into canonical order. This is the
* final step in creating decomposed normal forms D and KD.
* @private
- * @param $string String: a valid, decomposed UTF-8 string. Input is not validated.
+ * @param string $string a valid, decomposed UTF-8 string. Input is not validated.
* @return string a UTF-8 string with combining characters sorted in canonical order
*/
static function fastCombiningSort( $string ) {
@@ -616,7 +616,7 @@ class UtfNormal {
* Produces canonically composed sequences, i.e. normal form C or KC.
*
* @private
- * @param $string String: a valid UTF-8 string in sorted normal form D or KD. Input is not validated.
+ * @param string $string a valid UTF-8 string in sorted normal form D or KD. Input is not validated.
* @return string a UTF-8 string with canonical precomposed characters used where possible
*/
static function fastCompose( $string ) {
@@ -627,8 +627,8 @@ class UtfNormal {
$lastHangul = 0;
$startChar = '';
$combining = '';
- $x1 = ord(substr(UTF8_HANGUL_VBASE,0,1));
- $x2 = ord(substr(UTF8_HANGUL_TEND,0,1));
+ $x1 = ord(substr(UTF8_HANGUL_VBASE, 0, 1));
+ $x2 = ord(substr(UTF8_HANGUL_TEND, 0, 1));
for( $i = 0; $i < $len; $i++ ) {
$c = $string[$i];
$n = ord( $c );
@@ -762,10 +762,10 @@ class UtfNormal {
* Function to replace some characters that we don't want
* but most of the native normalize functions keep.
*
- * @param $string String The string
+ * @param string $string The string
* @return String String with the character codes replaced.
*/
- private static function replaceForNativeNormalize( $string ) {
+ private static function replaceForNativeNormalize( $string ) {
$string = preg_replace(
'/[\x00-\x08\x0b\x0c\x0e-\x1f]/',
UTF8_REPLACEMENT,
diff --git a/includes/normal/UtfNormalBench.php b/includes/normal/UtfNormalBench.php
index 944c4435..89de9290 100644
--- a/includes/normal/UtfNormalBench.php
+++ b/includes/normal/UtfNormalBench.php
@@ -19,11 +19,15 @@
* 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 UtfNormal
*/
+if( PHP_SAPI != 'cli' ) {
+ die( "Run me from the command line please.\n" );
+}
+
if( isset( $_SERVER['argv'] ) && in_array( '--icu', $_SERVER['argv'] ) ) {
dl( 'php_utfnormal.so' );
}
@@ -34,10 +38,6 @@ require_once 'UtfNormal.php';
define( 'BENCH_CYCLES', 5 );
-if( php_sapi_name() != 'cli' ) {
- die( "Run me from the command line please.\n" );
-}
-
$testfiles = array(
'testdata/washington.txt' => 'English text',
'testdata/berlin.txt' => 'German text',
@@ -80,7 +80,7 @@ function benchmarkTest( &$u, $filename, $desc ) {
}
}
-function benchTime(){
+function benchTime() {
$st = explode( ' ', microtime() );
return (float)$st[0] + (float)$st[1];
}
diff --git a/includes/normal/UtfNormalDefines.php b/includes/normal/UtfNormalDefines.php
index 5142a414..b07e3399 100644
--- a/includes/normal/UtfNormalDefines.php
+++ b/includes/normal/UtfNormalDefines.php
@@ -2,7 +2,7 @@
/**
* Some constant definitions for the unicode normalization module.
*
- * Note: these constants must all be resolvable at compile time by HipHop,
+ * Note: these constants must all be resolvable at compile time by HipHop,
* since this file will not be executed during request startup for a compiled
* MediaWiki.
*
@@ -26,7 +26,7 @@
*/
define( 'UNICODE_HANGUL_FIRST', 0xac00 );
-define( 'UNICODE_HANGUL_LAST', 0xd7a3 );
+define( 'UNICODE_HANGUL_LAST', 0xd7a3 );
define( 'UNICODE_HANGUL_LBASE', 0x1100 );
define( 'UNICODE_HANGUL_VBASE', 0x1161 );
diff --git a/includes/normal/UtfNormalGenerate.php b/includes/normal/UtfNormalGenerate.php
index e4c1138e..f392df52 100644
--- a/includes/normal/UtfNormalGenerate.php
+++ b/includes/normal/UtfNormalGenerate.php
@@ -25,7 +25,7 @@
* @ingroup UtfNormal
*/
-if( php_sapi_name() != 'cli' ) {
+if( PHP_SAPI != 'cli' ) {
die( "Run me from the command line please.\n" );
}
@@ -177,7 +177,7 @@ if( $out ) {
*
* @file
*/
-
+
UtfNormal::\$utfCombiningClass = unserialize( '$serCombining' );
UtfNormal::\$utfCanonicalComp = unserialize( '$serComp' );
UtfNormal::\$utfCanonicalDecomp = unserialize( '$serCanon' );
diff --git a/includes/normal/UtfNormalMemStress.php b/includes/normal/UtfNormalMemStress.php
index 1277dc20..9732d762 100644
--- a/includes/normal/UtfNormalMemStress.php
+++ b/includes/normal/UtfNormalMemStress.php
@@ -26,6 +26,10 @@
* @ingroup UtfNormal
*/
+if( PHP_SAPI != 'cli' ) {
+ die( "Run me from the command line please.\n" );
+}
+
if( isset( $_SERVER['argv'] ) && in_array( '--icu', $_SERVER['argv'] ) ) {
dl( 'php_utfnormal.so' );
}
@@ -38,10 +42,6 @@ define( 'BENCH_CYCLES', 1 );
define( 'BIGSIZE', 1024 * 1024 * 10); // 10m
ini_set('memory_limit', BIGSIZE + 120 * 1024 * 1024);
-if( php_sapi_name() != 'cli' ) {
- die( "Run me from the command line please.\n" );
-}
-
$testfiles = array(
'testdata/washington.txt' => 'English text',
'testdata/berlin.txt' => 'German text',
@@ -82,7 +82,7 @@ function benchmarkTest( &$u, $filename, $desc ) {
}
}
-function benchTime(){
+function benchTime() {
$st = explode( ' ', microtime() );
return (float)$st[0] + (float)$st[1];
}
diff --git a/includes/normal/UtfNormalTest.php b/includes/normal/UtfNormalTest.php
index 5872ec34..661e53fd 100644
--- a/includes/normal/UtfNormalTest.php
+++ b/includes/normal/UtfNormalTest.php
@@ -25,6 +25,10 @@
* @ingroup UtfNormal
*/
+if( PHP_SAPI != 'cli' ) {
+ die( "Run me from the command line please.\n" );
+}
+
$verbose = true;
#define( 'PRETTY_UTF8', true );
@@ -54,10 +58,6 @@ require_once 'UtfNormalDefines.php';
require_once 'UtfNormalUtil.php';
require_once 'UtfNormal.php';
-if( php_sapi_name() != 'cli' ) {
- die( "Run me from the command line please.\n" );
-}
-
$in = fopen("NormalizationTest.txt", "rt");
if( !$in ) {
print "Couldn't open NormalizationTest.txt -- can't run tests.\n";
diff --git a/includes/normal/UtfNormalTest2.php b/includes/normal/UtfNormalTest2.php
index 691bfaa7..2266696e 100644
--- a/includes/normal/UtfNormalTest2.php
+++ b/includes/normal/UtfNormalTest2.php
@@ -1,4 +1,4 @@
-#!/usr/bin/php
+#!/usr/bin/env php
<?php
/**
* Other tests for the unicode normalization module.
@@ -22,7 +22,7 @@
* @ingroup UtfNormal
*/
-if( php_sapi_name() != 'cli' ) {
+if( PHP_SAPI != 'cli' ) {
die( "Run me from the command line please.\n" );
}
diff --git a/includes/normal/UtfNormalUtil.php b/includes/normal/UtfNormalUtil.php
index bfad7095..9b96a073 100644
--- a/includes/normal/UtfNormalUtil.php
+++ b/includes/normal/UtfNormalUtil.php
@@ -71,7 +71,7 @@ function hexSequenceToUtf8( $sequence ) {
* Take a UTF-8 string and return a space-separated series of hex
* numbers representing Unicode code points. For debugging.
*
- * @param $str String: UTF-8 string.
+ * @param string $str UTF-8 string.
* @return string
* @private
*/
@@ -114,7 +114,7 @@ function utf8ToCodepoint( $char ) {
$z >>= $length;
# Add in the free bits from subsequent bytes
- for ( $i=1; $i<$length; $i++ ) {
+ for ( $i=1; $i < $length; $i++ ) {
$z <<= 6;
$z |= ord( $char[$i] ) & 0x3f;
}
@@ -125,7 +125,7 @@ function utf8ToCodepoint( $char ) {
/**
* Escape a string for inclusion in a PHP single-quoted string literal.
*
- * @param $string String: string to be escaped.
+ * @param string $string string to be escaped.
* @return String: escaped string.
* @public
*/
diff --git a/includes/objectcache/APCBagOStuff.php b/includes/objectcache/APCBagOStuff.php
index 1a0de218..3fb80835 100644
--- a/includes/objectcache/APCBagOStuff.php
+++ b/includes/objectcache/APCBagOStuff.php
@@ -29,11 +29,14 @@
class APCBagOStuff extends BagOStuff {
/**
* @param $key string
+ * @param $casToken[optional] int
* @return mixed
*/
- public function get( $key ) {
+ public function get( $key, &$casToken = null ) {
$val = apc_fetch( $key );
+ $casToken = $val;
+
if ( is_string( $val ) ) {
if ( $this->isInteger( $val ) ) {
$val = intval( $val );
@@ -62,6 +65,18 @@ class APCBagOStuff extends BagOStuff {
}
/**
+ * @param $casToken mixed
+ * @param $key string
+ * @param $value mixed
+ * @param $exptime int
+ * @return bool
+ */
+ public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ // APC's CAS functions only work on integers
+ throw new MWException( "CAS is not implemented in " . __CLASS__ );
+ }
+
+ /**
* @param $key string
* @param $time int
* @return bool
@@ -72,6 +87,17 @@ class APCBagOStuff extends BagOStuff {
return true;
}
+ /**
+ * @param $key string
+ * @param $callback closure Callback method to be executed
+ * @param int $exptime Either an interval in seconds or a unix timestamp for expiry
+ * @param int $attempts The amount of times to attempt a merge in case of failure
+ * @return bool success
+ */
+ public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ return $this->mergeViaLock( $key, $callback, $exptime, $attempts );
+ }
+
public function incr( $key, $value = 1 ) {
return apc_inc( $key, $value );
}
@@ -79,19 +105,4 @@ class APCBagOStuff extends BagOStuff {
public function decr( $key, $value = 1 ) {
return apc_dec( $key, $value );
}
-
- /**
- * @return Array
- */
- public function keys() {
- $info = apc_cache_info( 'user' );
- $list = $info['cache_list'];
- $keys = array();
-
- foreach ( $list as $entry ) {
- $keys[] = $entry['info'];
- }
-
- return $keys;
- }
}
diff --git a/includes/objectcache/BagOStuff.php b/includes/objectcache/BagOStuff.php
index 7bbaff93..dd744672 100644
--- a/includes/objectcache/BagOStuff.php
+++ b/includes/objectcache/BagOStuff.php
@@ -56,58 +56,153 @@ abstract class BagOStuff {
/**
* Get an item with the given key. Returns false if it does not exist.
* @param $key string
+ * @param $casToken[optional] mixed
* @return mixed Returns false on failure
*/
- abstract public function get( $key );
+ abstract public function get( $key, &$casToken = null );
/**
* Set an item.
* @param $key string
* @param $value mixed
- * @param $exptime int Either an interval in seconds or a unix timestamp for expiry
+ * @param int $exptime Either an interval in seconds or a unix timestamp for expiry
* @return bool success
*/
abstract public function set( $key, $value, $exptime = 0 );
/**
+ * Check and set an item.
+ * @param $casToken mixed
+ * @param $key string
+ * @param $value mixed
+ * @param int $exptime Either an interval in seconds or a unix timestamp for expiry
+ * @return bool success
+ */
+ abstract public function cas( $casToken, $key, $value, $exptime = 0 );
+
+ /**
* Delete an item.
* @param $key string
- * @param $time int Amount of time to delay the operation (mostly memcached-specific)
+ * @param int $time Amount of time to delay the operation (mostly memcached-specific)
* @return bool True if the item was deleted or not found, false on failure
*/
abstract public function delete( $key, $time = 0 );
/**
+ * Merge changes into the existing cache value (possibly creating a new one).
+ * The callback function returns the new value given the current value (possibly false),
+ * and takes the arguments: (this BagOStuff object, cache key, current value).
+ *
* @param $key string
- * @param $timeout integer
+ * @param $callback closure Callback method to be executed
+ * @param int $exptime Either an interval in seconds or a unix timestamp for expiry
+ * @param int $attempts The amount of times to attempt a merge in case of failure
* @return bool success
*/
- public function lock( $key, $timeout = 0 ) {
- /* stub */
- return true;
+ public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ return $this->mergeViaCas( $key, $callback, $exptime, $attempts );
}
/**
+ * @see BagOStuff::merge()
+ *
* @param $key string
+ * @param $callback closure Callback method to be executed
+ * @param int $exptime Either an interval in seconds or a unix timestamp for expiry
+ * @param int $attempts The amount of times to attempt a merge in case of failure
* @return bool success
*/
- public function unlock( $key ) {
- /* stub */
- return true;
+ protected function mergeViaCas( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ do {
+ $casToken = null; // passed by reference
+ $currentValue = $this->get( $key, $casToken ); // get the old value
+ $value = $callback( $this, $key, $currentValue ); // derive the new value
+
+ if ( $value === false ) {
+ $success = true; // do nothing
+ } elseif ( $currentValue === false ) {
+ // Try to create the key, failing if it gets created in the meantime
+ $success = $this->add( $key, $value, $exptime );
+ } else {
+ // Try to update the key, failing if it gets changed in the meantime
+ $success = $this->cas( $casToken, $key, $value, $exptime );
+ }
+ } while ( !$success && --$attempts );
+
+ return $success;
}
/**
- * @todo: what is this?
- * @return Array
+ * @see BagOStuff::merge()
+ *
+ * @param $key string
+ * @param $callback closure Callback method to be executed
+ * @param int $exptime Either an interval in seconds or a unix timestamp for expiry
+ * @param int $attempts The amount of times to attempt a merge in case of failure
+ * @return bool success
*/
- public function keys() {
- /* stub */
- return array();
+ protected function mergeViaLock( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ if ( !$this->lock( $key, 60 ) ) {
+ return false;
+ }
+
+ $currentValue = $this->get( $key ); // get the old value
+ $value = $callback( $this, $key, $currentValue ); // derive the new value
+
+ if ( $value === false ) {
+ $success = true; // do nothing
+ } else {
+ $success = $this->set( $key, $value, $exptime ); // set the new value
+ }
+
+ if ( !$this->unlock( $key ) ) {
+ // this should never happen
+ trigger_error( "Could not release lock for key '$key'." );
+ }
+
+ return $success;
+ }
+
+ /**
+ * @param $key string
+ * @param $timeout integer [optional]
+ * @return bool success
+ */
+ public function lock( $key, $timeout = 60 ) {
+ $timestamp = microtime( true ); // starting UNIX timestamp
+ if ( $this->add( "{$key}:lock", $timeout ) ) {
+ return true;
+ }
+
+ $uRTT = ceil( 1e6 * ( microtime( true ) - $timestamp ) ); // estimate RTT (us)
+ $sleep = 2*$uRTT; // rough time to do get()+set()
+
+ $locked = false; // lock acquired
+ $attempts = 0; // failed attempts
+ do {
+ if ( ++$attempts >= 3 && $sleep <= 1e6 ) {
+ // Exponentially back off after failed attempts to avoid network spam.
+ // About 2*$uRTT*(2^n-1) us of "sleep" happen for the next n attempts.
+ $sleep *= 2;
+ }
+ usleep( $sleep ); // back off
+ $locked = $this->add( "{$key}:lock", $timeout );
+ } while( !$locked );
+
+ return $locked;
+ }
+
+ /**
+ * @param $key string
+ * @return bool success
+ */
+ public function unlock( $key ) {
+ return $this->delete( "{$key}:lock" );
}
/**
* Delete all objects expiring before a certain date.
- * @param $date string The reference date in MW format
+ * @param string $date The reference date in MW format
* @param $progressCallback callback|bool Optional, a function which will be called
* regularly during long-running operations with the percentage progress
* as the first parameter.
@@ -123,7 +218,7 @@ abstract class BagOStuff {
/**
* Get an associative array containing the item for each of the keys that have items.
- * @param $keys Array List of strings
+ * @param array $keys List of strings
* @return Array
*/
public function getMulti( array $keys ) {
@@ -165,7 +260,7 @@ abstract class BagOStuff {
/**
* Increase stored value of $key by $value while preserving its TTL
- * @param $key String: Key to increase
+ * @param string $key Key to increase
* @param $value Integer: Value to add to $key (Default 1)
* @return integer|bool New value or false on failure
*/
diff --git a/includes/objectcache/DBABagOStuff.php b/includes/objectcache/DBABagOStuff.php
index 36ced496..c82b3aa4 100644
--- a/includes/objectcache/DBABagOStuff.php
+++ b/includes/objectcache/DBABagOStuff.php
@@ -111,9 +111,10 @@ class DBABagOStuff extends BagOStuff {
/**
* @param $key string
+ * @param $casToken[optional] mixed
* @return mixed
*/
- public function get( $key ) {
+ public function get( $key, &$casToken = null ) {
wfProfileIn( __METHOD__ );
wfDebug( __METHOD__ . "($key)\n" );
@@ -138,7 +139,10 @@ class DBABagOStuff extends BagOStuff {
$val = false;
}
+ $casToken = $val;
+
wfProfileOut( __METHOD__ );
+
return $val;
}
@@ -168,6 +172,42 @@ class DBABagOStuff extends BagOStuff {
}
/**
+ * @param $casToken mixed
+ * @param $key string
+ * @param $value mixed
+ * @param $exptime int
+ * @return bool
+ */
+ public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ wfProfileIn( __METHOD__ );
+ wfDebug( __METHOD__ . "($key)\n" );
+
+ $blob = $this->encode( $value, $exptime );
+
+ $handle = $this->getWriter();
+ if ( !$handle ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ // DBA is locked to any other write connection, so we can safely
+ // compare the current & previous value before saving new value
+ $val = dba_fetch( $key, $handle );
+ list( $val, $exptime ) = $this->decode( $val );
+ if ( $casToken !== $val ) {
+ dba_close( $handle );
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ $ret = dba_replace( $key, $blob, $handle );
+ dba_close( $handle );
+
+ wfProfileOut( __METHOD__ );
+ return $ret;
+ }
+
+ /**
* @param $key string
* @param $time int
* @return bool
@@ -211,7 +251,7 @@ class DBABagOStuff extends BagOStuff {
# Insert failed, check to see if it failed due to an expired key
if ( !$ret ) {
- list( $value, $expiry ) = $this->decode( dba_fetch( $key, $handle ) );
+ list( , $expiry ) = $this->decode( dba_fetch( $key, $handle ) );
if ( $expiry && $expiry < time() ) {
# Yes expired, delete and try again
@@ -264,23 +304,4 @@ class DBABagOStuff extends BagOStuff {
return ( $value === false ) ? false : (int)$value;
}
-
- function keys() {
- $reader = $this->getReader();
- $k1 = dba_firstkey( $reader );
-
- if ( !$k1 ) {
- return array();
- }
-
- $result[] = $k1;
-
- $key = dba_nextkey( $reader );
- while ( $key ) {
- $result[] = $key;
- $key = dba_nextkey( $reader );
- }
-
- return $result;
- }
}
diff --git a/includes/objectcache/EhcacheBagOStuff.php b/includes/objectcache/EhcacheBagOStuff.php
index f86cf157..960668f5 100644
--- a/includes/objectcache/EhcacheBagOStuff.php
+++ b/includes/objectcache/EhcacheBagOStuff.php
@@ -28,27 +28,28 @@
* @ingroup Cache
*/
class EhcacheBagOStuff extends BagOStuff {
- var $servers, $cacheName, $connectTimeout, $timeout, $curlOptions,
+ var $servers, $cacheName, $connectTimeout, $timeout, $curlOptions,
$requestData, $requestDataPos;
-
+
var $curls = array();
/**
* @param $params array
+ * @throws MWException
*/
function __construct( $params ) {
if ( !defined( 'CURLOPT_TIMEOUT_MS' ) ) {
- throw new MWException( __CLASS__.' requires curl version 7.16.2 or later.' );
+ throw new MWException( __CLASS__ . ' requires curl version 7.16.2 or later.' );
}
if ( !extension_loaded( 'zlib' ) ) {
- throw new MWException( __CLASS__.' requires the zlib extension' );
+ throw new MWException( __CLASS__ . ' requires the zlib extension' );
}
if ( !isset( $params['servers'] ) ) {
- throw new MWException( __METHOD__.': servers parameter is required' );
+ throw new MWException( __METHOD__ . ': servers parameter is required' );
}
$this->servers = $params['servers'];
$this->cacheName = isset( $params['cache'] ) ? $params['cache'] : 'mw';
- $this->connectTimeout = isset( $params['connectTimeout'] )
+ $this->connectTimeout = isset( $params['connectTimeout'] )
? $params['connectTimeout'] : 1;
$this->timeout = isset( $params['timeout'] ) ? $params['timeout'] : 1;
$this->curlOptions = array(
@@ -64,9 +65,10 @@ class EhcacheBagOStuff extends BagOStuff {
/**
* @param $key string
+ * @param $casToken[optional] mixed
* @return bool|mixed
*/
- public function get( $key ) {
+ public function get( $key, &$casToken = null ) {
wfProfileIn( __METHOD__ );
$response = $this->doItemRequest( $key );
if ( !$response || $response['http_code'] == 404 ) {
@@ -74,16 +76,16 @@ class EhcacheBagOStuff extends BagOStuff {
return false;
}
if ( $response['http_code'] >= 300 ) {
- wfDebug( __METHOD__.": GET failure, got HTTP {$response['http_code']}\n" );
+ wfDebug( __METHOD__ . ": GET failure, got HTTP {$response['http_code']}\n" );
wfProfileOut( __METHOD__ );
- return false;
+ return false;
}
$body = $response['body'];
$type = $response['content_type'];
if ( $type == 'application/vnd.php.serialized+deflate' ) {
$body = gzinflate( $body );
if ( !$body ) {
- wfDebug( __METHOD__.": error inflating $key\n" );
+ wfDebug( __METHOD__ . ": error inflating $key\n" );
wfProfileOut( __METHOD__ );
return false;
}
@@ -91,11 +93,13 @@ class EhcacheBagOStuff extends BagOStuff {
} elseif ( $type == 'application/vnd.php.serialized' ) {
$data = unserialize( $body );
} else {
- wfDebug( __METHOD__.": unknown content type \"$type\"\n" );
+ wfDebug( __METHOD__ . ": unknown content type \"$type\"\n" );
wfProfileOut( __METHOD__ );
return false;
}
+ $casToken = $body;
+
wfProfileOut( __METHOD__ );
return $data;
}
@@ -123,7 +127,7 @@ class EhcacheBagOStuff extends BagOStuff {
if ( $code == 404 ) {
// Maybe the cache does not exist yet, let's try creating it
if ( !$this->createCache( $key ) ) {
- wfDebug( __METHOD__.": cache creation failed\n" );
+ wfDebug( __METHOD__ . ": cache creation failed\n" );
wfProfileOut( __METHOD__ );
return false;
}
@@ -132,9 +136,9 @@ class EhcacheBagOStuff extends BagOStuff {
$result = false;
if ( !$code ) {
- wfDebug( __METHOD__.": PUT failure for key $key\n" );
+ wfDebug( __METHOD__ . ": PUT failure for key $key\n" );
} elseif ( $code >= 300 ) {
- wfDebug( __METHOD__.": PUT failure for key $key: HTTP $code\n" );
+ wfDebug( __METHOD__ . ": PUT failure for key $key: HTTP $code\n" );
} else {
$result = true;
}
@@ -144,6 +148,20 @@ class EhcacheBagOStuff extends BagOStuff {
}
/**
+ * @param $casToken mixed
+ * @param $key string
+ * @param $value mixed
+ * @param $exptime int
+ * @return bool
+ */
+ public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ // Not sure if we can implement CAS for ehcache. There appears to be CAS-support per
+ // http://ehcache.org/documentation/get-started/consistency-options#cas-cache-operations,
+ // but I can't find any docs for our current implementation.
+ throw new MWException( "CAS is not implemented in " . __CLASS__ );
+ }
+
+ /**
* @param $key string
* @param $time int
* @return bool
@@ -154,7 +172,7 @@ class EhcacheBagOStuff extends BagOStuff {
array( CURLOPT_CUSTOMREQUEST => 'DELETE' ) );
$code = isset( $response['http_code'] ) ? $response['http_code'] : 0;
if ( !$response || ( $code != 404 && $code >= 300 ) ) {
- wfDebug( __METHOD__.": DELETE failure for key $key\n" );
+ wfDebug( __METHOD__ . ": DELETE failure for key $key\n" );
$result = false;
} else {
$result = true;
@@ -164,6 +182,14 @@ class EhcacheBagOStuff extends BagOStuff {
}
/**
+ * @see BagOStuff::merge()
+ * @return bool success
+ */
+ public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ return $this->mergeViaLock( $key, $callback, $exptime, $attempts );
+ }
+
+ /**
* @param $key string
* @return string
*/
@@ -202,9 +228,9 @@ class EhcacheBagOStuff extends BagOStuff {
* @return int
*/
protected function attemptPut( $key, $data, $type, $ttl ) {
- // In initial benchmarking, it was 30 times faster to use CURLOPT_POST
+ // In initial benchmarking, it was 30 times faster to use CURLOPT_POST
// than CURLOPT_UPLOAD with CURLOPT_READFUNCTION. This was because
- // CURLOPT_UPLOAD was pushing the request headers first, then waiting
+ // CURLOPT_UPLOAD was pushing the request headers first, then waiting
// for an ACK packet, then sending the data, whereas CURLOPT_POST just
// sends the headers and the data in a single send().
$response = $this->doItemRequest( $key,
@@ -230,15 +256,15 @@ class EhcacheBagOStuff extends BagOStuff {
* @return bool
*/
protected function createCache( $key ) {
- wfDebug( __METHOD__.": creating cache for $key\n" );
- $response = $this->doCacheRequest( $key,
+ wfDebug( __METHOD__ . ": creating cache for $key\n" );
+ $response = $this->doCacheRequest( $key,
array(
CURLOPT_POST => 1,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_POSTFIELDS => '',
) );
if ( !$response ) {
- wfDebug( __CLASS__.": failed to create cache for $key\n" );
+ wfDebug( __CLASS__ . ": failed to create cache for $key\n" );
return false;
}
return ( $response['http_code'] == 201 /* created */
@@ -278,8 +304,8 @@ class EhcacheBagOStuff extends BagOStuff {
protected function doRequest( $curl, $url, $curlOptions = array() ) {
if ( array_diff_key( $curlOptions, $this->curlOptions ) ) {
// var_dump( array_diff_key( $curlOptions, $this->curlOptions ) );
- throw new MWException( __METHOD__.": to prevent options set in one doRequest() " .
- "call from affecting subsequent doRequest() calls, only options listed " .
+ throw new MWException( __METHOD__ . ": to prevent options set in one doRequest() " .
+ "call from affecting subsequent doRequest() calls, only options listed " .
"in \$this->curlOptions may be specified in the \$curlOptions parameter." );
}
$curlOptions += $this->curlOptions;
@@ -288,7 +314,7 @@ class EhcacheBagOStuff extends BagOStuff {
curl_setopt_array( $curl, $curlOptions );
$result = curl_exec( $curl );
if ( $result === false ) {
- wfDebug( __CLASS__.": curl error: " . curl_error( $curl ) . "\n" );
+ wfDebug( __CLASS__ . ": curl error: " . curl_error( $curl ) . "\n" );
return false;
}
$info = curl_getinfo( $curl );
diff --git a/includes/objectcache/EmptyBagOStuff.php b/includes/objectcache/EmptyBagOStuff.php
index bd28b241..62060579 100644
--- a/includes/objectcache/EmptyBagOStuff.php
+++ b/includes/objectcache/EmptyBagOStuff.php
@@ -30,9 +30,10 @@ class EmptyBagOStuff extends BagOStuff {
/**
* @param $key string
+ * @param $casToken[optional] mixed
* @return bool
*/
- function get( $key ) {
+ function get( $key, &$casToken = null ) {
return false;
}
@@ -47,6 +48,17 @@ class EmptyBagOStuff extends BagOStuff {
}
/**
+ * @param $casToken mixed
+ * @param $key string
+ * @param $value mixed
+ * @param $exp int
+ * @return bool
+ */
+ function cas( $casToken, $key, $value, $exp = 0 ) {
+ return true;
+ }
+
+ /**
* @param $key string
* @param $time int
* @return bool
@@ -54,6 +66,17 @@ class EmptyBagOStuff extends BagOStuff {
function delete( $key, $time = 0 ) {
return true;
}
+
+ /**
+ * @param $key string
+ * @param $callback closure Callback method to be executed
+ * @param int $exptime Either an interval in seconds or a unix timestamp for expiry
+ * @param int $attempts The amount of times to attempt a merge in case of failure
+ * @return bool success
+ */
+ public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ return true;
+ }
}
/**
diff --git a/includes/objectcache/HashBagOStuff.php b/includes/objectcache/HashBagOStuff.php
index 799f26a3..d061eff0 100644
--- a/includes/objectcache/HashBagOStuff.php
+++ b/includes/objectcache/HashBagOStuff.php
@@ -52,9 +52,10 @@ class HashBagOStuff extends BagOStuff {
/**
* @param $key string
+ * @param $casToken[optional] mixed
* @return bool|mixed
*/
- function get( $key ) {
+ function get( $key, &$casToken = null ) {
if ( !isset( $this->bag[$key] ) ) {
return false;
}
@@ -63,6 +64,8 @@ class HashBagOStuff extends BagOStuff {
return false;
}
+ $casToken = $this->bag[$key][0];
+
return $this->bag[$key][0];
}
@@ -78,6 +81,21 @@ class HashBagOStuff extends BagOStuff {
}
/**
+ * @param $casToken mixed
+ * @param $key string
+ * @param $value mixed
+ * @param $exptime int
+ * @return bool
+ */
+ function cas( $casToken, $key, $value, $exptime = 0 ) {
+ if ( $this->get( $key ) === $casToken ) {
+ return $this->set( $key, $value, $exptime );
+ }
+
+ return false;
+ }
+
+ /**
* @param $key string
* @param $time int
* @return bool
@@ -91,12 +109,4 @@ class HashBagOStuff extends BagOStuff {
return true;
}
-
- /**
- * @return array
- */
- function keys() {
- return array_keys( $this->bag );
- }
}
-
diff --git a/includes/objectcache/MemcachedBagOStuff.php b/includes/objectcache/MemcachedBagOStuff.php
index 813c2727..3f1fa3a0 100644
--- a/includes/objectcache/MemcachedBagOStuff.php
+++ b/includes/objectcache/MemcachedBagOStuff.php
@@ -57,10 +57,11 @@ class MemcachedBagOStuff extends BagOStuff {
/**
* @param $key string
+ * @param $casToken[optional] mixed
* @return Mixed
*/
- public function get( $key ) {
- return $this->client->get( $this->encodeKey( $key ) );
+ public function get( $key, &$casToken = null ) {
+ return $this->client->get( $this->encodeKey( $key ), $casToken );
}
/**
@@ -76,6 +77,18 @@ class MemcachedBagOStuff extends BagOStuff {
/**
* @param $key string
+ * @param $casToken mixed
+ * @param $value
+ * @param $exptime int
+ * @return bool
+ */
+ public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ return $this->client->cas( $casToken, $this->encodeKey( $key ),
+ $value, $this->fixExpiry( $exptime ) );
+ }
+
+ /**
+ * @param $key string
* @param $time int
* @return bool
*/
@@ -86,7 +99,7 @@ class MemcachedBagOStuff extends BagOStuff {
/**
* @param $key string
* @param $value int
- * @param $exptime int (default 0)
+ * @param int $exptime (default 0)
* @return Mixed
*/
public function add( $key, $value, $exptime = 0 ) {
@@ -101,7 +114,7 @@ class MemcachedBagOStuff extends BagOStuff {
* @return Mixed
*/
public function replace( $key, $value, $exptime = 0 ) {
- return $this->client->replace( $this->encodeKey( $key ), $value,
+ return $this->client->replace( $this->encodeKey( $key ), $value,
$this->fixExpiry( $exptime ) );
}
@@ -166,15 +179,9 @@ class MemcachedBagOStuff extends BagOStuff {
* Send a debug message to the log
*/
protected function debugLog( $text ) {
- global $wgDebugLogGroups;
- if( !isset( $wgDebugLogGroups['memcached'] ) ) {
- # Prefix message since it will end up in main debug log file
- $text = "memcached: $text";
- }
if ( substr( $text, -1 ) !== "\n" ) {
$text .= "\n";
}
wfDebugLog( 'memcached', $text );
}
}
-
diff --git a/includes/objectcache/MemcachedClient.php b/includes/objectcache/MemcachedClient.php
index 536ba6ea..0d96ed6c 100644
--- a/includes/objectcache/MemcachedClient.php
+++ b/includes/objectcache/MemcachedClient.php
@@ -99,7 +99,6 @@ class MWMemcached {
// }}}
-
/**
* Command statistics
*
@@ -242,7 +241,7 @@ class MWMemcached {
/**
* Memcache initializer
*
- * @param $args Array Associative array of settings
+ * @param array $args Associative array of settings
*
* @return mixed
*/
@@ -272,12 +271,12 @@ class MWMemcached {
* Adds a key/value to the memcache server if one isn't already set with
* that key
*
- * @param $key String: key to set with data
+ * @param string $key key to set with data
* @param $val Mixed: value to store
* @param $exp Integer: (optional) Expiration time. This can be a number of seconds
* to cache for (up to 30 days inclusive). Any timespans of 30 days + 1 second or
* longer must be the timestamp of the time at which the mapping should expire. It
- * is safe to use timestamps in all cases, regardless of exipration
+ * is safe to use timestamps in all cases, regardless of expiration
* eg: strtotime("+3 hour")
*
* @return Boolean
@@ -292,7 +291,7 @@ class MWMemcached {
/**
* Decrease a value stored on the memcache server
*
- * @param $key String: key to decrease
+ * @param string $key key to decrease
* @param $amt Integer: (optional) amount to decrease
*
* @return Mixed: FALSE on failure, value on success
@@ -307,7 +306,7 @@ class MWMemcached {
/**
* Deletes a key from the server, optionally after $time
*
- * @param $key String: key to delete
+ * @param string $key key to delete
* @param $time Integer: (optional) how long to wait before deleting
*
* @return Boolean: TRUE on success, FALSE on failure
@@ -407,11 +406,12 @@ class MWMemcached {
/**
* Retrieves the value associated with the key from the memcache server
*
- * @param $key array|string key to retrieve
+ * @param array|string $key key to retrieve
+ * @param $casToken[optional] Float
*
* @return Mixed
*/
- public function get( $key ) {
+ public function get( $key, &$casToken = null ) {
wfProfileIn( __METHOD__ );
if ( $this->_debug ) {
@@ -437,14 +437,14 @@ class MWMemcached {
$this->stats['get'] = 1;
}
- $cmd = "get $key\r\n";
+ $cmd = "gets $key\r\n";
if ( !$this->_fwrite( $sock, $cmd ) ) {
wfProfileOut( __METHOD__ );
return false;
}
$val = array();
- $this->_load_items( $sock, $val );
+ $this->_load_items( $sock, $val, $casToken );
if ( $this->_debug ) {
foreach ( $val as $k => $v ) {
@@ -466,7 +466,7 @@ class MWMemcached {
/**
* Get multiple keys from the server(s)
*
- * @param $keys Array: keys to retrieve
+ * @param array $keys keys to retrieve
*
* @return Array
*/
@@ -498,7 +498,7 @@ class MWMemcached {
$gather = array();
// Send out the requests
foreach ( $socks as $sock ) {
- $cmd = 'get';
+ $cmd = 'gets';
foreach ( $sock_keys[ intval( $sock ) ] as $key ) {
$cmd .= ' ' . $key;
}
@@ -512,7 +512,7 @@ class MWMemcached {
// Parse responses
$val = array();
foreach ( $gather as $sock ) {
- $this->_load_items( $sock, $val );
+ $this->_load_items( $sock, $val, $casToken );
}
if ( $this->_debug ) {
@@ -530,7 +530,7 @@ class MWMemcached {
/**
* Increments $key (optionally) by $amt
*
- * @param $key String: key to increment
+ * @param string $key key to increment
* @param $amt Integer: (optional) amount to increment
*
* @return Integer: null if the key does not exist yet (this does NOT
@@ -547,7 +547,7 @@ class MWMemcached {
/**
* Overwrites an existing value for key; only works if key is already set
*
- * @param $key String: key to set value as
+ * @param string $key key to set value as
* @param $value Mixed: value to store
* @param $exp Integer: (optional) Expiration time. This can be a number of seconds
* to cache for (up to 30 days inclusive). Any timespans of 30 days + 1 second or
@@ -569,7 +569,7 @@ class MWMemcached {
* output as an array (null array if no output)
*
* @param $sock Resource: socket to send command on
- * @param $cmd String: command to run
+ * @param string $cmd command to run
*
* @return Array: output array
*/
@@ -603,7 +603,7 @@ class MWMemcached {
* Unconditionally sets a key to a given value in the memcache. Returns true
* if set successfully.
*
- * @param $key String: key to set value as
+ * @param string $key key to set value as
* @param $value Mixed: value to set
* @param $exp Integer: (optional) Expiration time. This can be a number of seconds
* to cache for (up to 30 days inclusive). Any timespans of 30 days + 1 second or
@@ -618,6 +618,28 @@ class MWMemcached {
}
// }}}
+ // {{{ cas()
+
+ /**
+ * Sets a key to a given value in the memcache if the current value still corresponds
+ * to a known, given value. Returns true if set successfully.
+ *
+ * @param $casToken Float: current known value
+ * @param string $key key to set value as
+ * @param $value Mixed: value to set
+ * @param $exp Integer: (optional) Expiration time. This can be a number of seconds
+ * to cache for (up to 30 days inclusive). Any timespans of 30 days + 1 second or
+ * longer must be the timestamp of the time at which the mapping should expire. It
+ * is safe to use timestamps in all cases, regardless of exipration
+ * eg: strtotime("+3 hour")
+ *
+ * @return Boolean: TRUE on success
+ */
+ public function cas( $casToken, $key, $value, $exp = 0 ) {
+ return $this->_set( 'cas', $key, $value, $exp, $casToken );
+ }
+
+ // }}}
// {{{ set_compress_threshold()
/**
@@ -649,7 +671,7 @@ class MWMemcached {
/**
* Sets the server list to distribute key gets and puts between
*
- * @param $list Array of servers to connect to
+ * @param array $list of servers to connect to
*
* @see MWMemcached::__construct()
*/
@@ -684,7 +706,7 @@ class MWMemcached {
/**
* Close the specified socket
*
- * @param $sock String: socket to close
+ * @param string $sock socket to close
*
* @access private
*/
@@ -701,7 +723,7 @@ class MWMemcached {
* Connects $sock to $host, timing out after $timeout
*
* @param $sock Integer: socket to connect
- * @param $host String: Host:IP to connect to
+ * @param string $host Host:IP to connect to
*
* @return boolean
* @access private
@@ -743,7 +765,7 @@ class MWMemcached {
/**
* Marks a host as dead until 30-40 seconds in the future
*
- * @param $sock String: socket to mark as dead
+ * @param string $sock socket to mark as dead
*
* @access private
*/
@@ -769,7 +791,7 @@ class MWMemcached {
/**
* get_sock
*
- * @param $key String: key to retrieve value for;
+ * @param string $key key to retrieve value for;
*
* @return Mixed: resource on success, false on failure
* @access private
@@ -818,7 +840,7 @@ class MWMemcached {
/**
* Creates a hash integer based on the $key
*
- * @param $key String: key to hash
+ * @param string $key key to hash
*
* @return Integer: hash value
* @access private
@@ -836,8 +858,8 @@ class MWMemcached {
/**
* Perform increment/decriment on $key
*
- * @param $cmd String command to perform
- * @param $key String|array key to perform it on
+ * @param string $cmd command to perform
+ * @param string|array $key key to perform it on
* @param $amt Integer amount to adjust
*
* @return Integer: new value of $key
@@ -878,40 +900,78 @@ class MWMemcached {
* Load items into $ret from $sock
*
* @param $sock Resource: socket to read from
- * @param $ret Array: returned values
+ * @param array $ret returned values
+ * @param $casToken[optional] Float
* @return boolean True for success, false for failure
*
* @access private
*/
- function _load_items( $sock, &$ret ) {
+ function _load_items( $sock, &$ret, &$casToken = null ) {
+ $results = array();
+
while ( 1 ) {
$decl = $this->_fgets( $sock );
+
if( $decl === false ) {
+ /*
+ * If nothing can be read, something is wrong because we know exactly when
+ * to stop reading (right after "END") and we return right after that.
+ */
return false;
+ } elseif ( preg_match( '/^VALUE (\S+) (\d+) (\d+) (\d+)$/', $decl, $match ) ) {
+ /*
+ * Read all data returned. This can be either one or multiple values.
+ * Save all that data (in an array) to be processed later: we'll first
+ * want to continue reading until "END" before doing anything else,
+ * to make sure that we don't leave our client in a state where it's
+ * output is not yet fully read.
+ */
+ $results[] = array(
+ $match[1], // rkey
+ $match[2], // flags
+ $match[3], // len
+ $match[4], // casToken
+ $this->_fread( $sock, $match[3] + 2 ), // data
+ );
} elseif ( $decl == "END" ) {
- return true;
- } elseif ( preg_match( '/^VALUE (\S+) (\d+) (\d+)$/', $decl, $match ) ) {
- list( $rkey, $flags, $len ) = array( $match[1], $match[2], $match[3] );
- $data = $this->_fread( $sock, $len + 2 );
- if ( $data === false ) {
- return false;
- }
- if ( substr( $data, -2 ) !== "\r\n" ) {
- $this->_handle_error( $sock,
- 'line ending missing from data block from $1' );
+ if ( count( $results ) == 0 ) {
return false;
}
- $data = substr( $data, 0, -2 );
- $ret[$rkey] = $data;
- if ( $this->_have_zlib && $flags & self::COMPRESSED ) {
- $ret[$rkey] = gzuncompress( $ret[$rkey] );
- }
+ /**
+ * All data has been read, time to process the data and build
+ * meaningful return values.
+ */
+ foreach ( $results as $vars ) {
+ list( $rkey, $flags, $len, $casToken, $data ) = $vars;
+
+ if ( $data === false || substr( $data, -2 ) !== "\r\n" ) {
+ $this->_handle_error( $sock,
+ 'line ending missing from data block from $1' );
+ return false;
+ }
+ $data = substr( $data, 0, -2 );
+ $ret[$rkey] = $data;
+
+ if ( $this->_have_zlib && $flags & self::COMPRESSED ) {
+ $ret[$rkey] = gzuncompress( $ret[$rkey] );
+ }
- if ( $flags & self::SERIALIZED ) {
- $ret[$rkey] = unserialize( $ret[$rkey] );
+ /*
+ * This unserialize is the exact reason that we only want to
+ * process data after having read until "END" (instead of doing
+ * this right away): "unserialize" can trigger outside code:
+ * in the event that $ret[$rkey] is a serialized object,
+ * unserializing it will trigger __wakeup() if present. If that
+ * function attempted to read from memcached (while we did not
+ * yet read "END"), these 2 calls would collide.
+ */
+ if ( $flags & self::SERIALIZED ) {
+ $ret[$rkey] = unserialize( $ret[$rkey] );
+ }
}
+ return true;
} else {
$this->_handle_error( $sock, 'Error parsing response from $1' );
return false;
@@ -925,19 +985,20 @@ class MWMemcached {
/**
* Performs the requested storage operation to the memcache server
*
- * @param $cmd String: command to perform
- * @param $key String: key to act on
+ * @param string $cmd command to perform
+ * @param string $key key to act on
* @param $val Mixed: what we need to store
* @param $exp Integer: (optional) Expiration time. This can be a number of seconds
* to cache for (up to 30 days inclusive). Any timespans of 30 days + 1 second or
* longer must be the timestamp of the time at which the mapping should expire. It
* is safe to use timestamps in all cases, regardless of exipration
* eg: strtotime("+3 hour")
+ * @param $casToken[optional] Float
*
* @return Boolean
* @access private
*/
- function _set( $cmd, $key, $val, $exp ) {
+ function _set( $cmd, $key, $val, $exp, $casToken = null ) {
if ( !$this->_active ) {
return false;
}
@@ -966,7 +1027,7 @@ class MWMemcached {
$len = strlen( $val );
if ( $this->_have_zlib && $this->_compress_enable &&
- $this->_compress_threshold && $len >= $this->_compress_threshold )
+ $this->_compress_threshold && $len >= $this->_compress_threshold )
{
$c_val = gzcompress( $val, 9 );
$c_len = strlen( $c_val );
@@ -980,7 +1041,13 @@ class MWMemcached {
$flags |= self::COMPRESSED;
}
}
- if ( !$this->_fwrite( $sock, "$cmd $key $flags $exp $len\r\n$val\r\n" ) ) {
+
+ $command = "$cmd $key $flags $exp $len";
+ if ( $casToken ) {
+ $command .= " $casToken";
+ }
+
+ if ( !$this->_fwrite( $sock, "$command\r\n$val\r\n" ) ) {
return false;
}
@@ -1001,7 +1068,7 @@ class MWMemcached {
/**
* Returns the socket for the host
*
- * @param $host String: Host:IP to get socket for
+ * @param string $host Host:IP to get socket for
*
* @return Mixed: IO Stream or false
* @access private
@@ -1036,11 +1103,6 @@ class MWMemcached {
* @param $text string
*/
function _debugprint( $text ) {
- global $wgDebugLogGroups;
- if( !isset( $wgDebugLogGroups['memcached'] ) ) {
- # Prefix message since it will end up in main debug log file
- $text = "memcached: $text";
- }
wfDebugLog( 'memcached', $text );
}
@@ -1096,7 +1158,7 @@ class MWMemcached {
}
/**
- * Read the specified number of bytes from a stream. If there is an error,
+ * Read the specified number of bytes from a stream. If there is an error,
* mark the socket dead.
*
* @param $sock The socket
@@ -1137,7 +1199,7 @@ class MWMemcached {
function _fgets( $sock ) {
$result = fgets( $sock );
// fgets() may return a partial line if there is a select timeout after
- // a successful recv(), so we have to check for a timeout even if we
+ // a successful recv(), so we have to check for a timeout even if we
// got a string response.
$data = stream_get_meta_data( $sock );
if ( $data['timed_out'] ) {
@@ -1167,10 +1229,16 @@ class MWMemcached {
if ( !is_resource( $f ) ) {
return;
}
- $n = stream_select( $r = array( $f ), $w = null, $e = null, 0, 0 );
+ $r = array( $f );
+ $w = null;
+ $e = null;
+ $n = stream_select( $r, $w, $e, 0, 0 );
while ( $n == 1 && !feof( $f ) ) {
fread( $f, 1024 );
- $n = stream_select( $r = array( $f ), $w = null, $e = null, 0, 0 );
+ $r = array( $f );
+ $w = null;
+ $e = null;
+ $n = stream_select( $r, $w, $e, 0, 0 );
}
}
@@ -1179,7 +1247,6 @@ class MWMemcached {
// }}}
}
-
// }}}
class MemCachedClientforWiki extends MWMemcached {
diff --git a/includes/objectcache/MemcachedPeclBagOStuff.php b/includes/objectcache/MemcachedPeclBagOStuff.php
index 76886ebb..31924293 100644
--- a/includes/objectcache/MemcachedPeclBagOStuff.php
+++ b/includes/objectcache/MemcachedPeclBagOStuff.php
@@ -47,7 +47,7 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
if ( $params['persistent'] ) {
// The pool ID must be unique to the server/option combination.
// The Memcached object is essentially shared for each pool ID.
- // We can only resuse a pool ID if we keep the config consistent.
+ // We can only reuse a pool ID if we keep the config consistent.
$this->client = new Memcached( md5( serialize( $params ) ) );
if ( count( $this->client->getServerList() ) ) {
wfDebug( __METHOD__ . ": persistent Memcached object already loaded.\n" );
@@ -104,11 +104,16 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
/**
* @param $key string
+ * @param $casToken[optional] float
* @return Mixed
*/
- public function get( $key ) {
+ public function get( $key, &$casToken = null ) {
+ wfProfileIn( __METHOD__ );
$this->debugLog( "get($key)" );
- return $this->checkResult( $key, parent::get( $key ) );
+ $result = $this->client->get( $this->encodeKey( $key ), null, $casToken );
+ $result = $this->checkResult( $key, $result );
+ wfProfileOut( __METHOD__ );
+ return $result;
}
/**
@@ -123,6 +128,18 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
}
/**
+ * @param $casToken float
+ * @param $key string
+ * @param $value
+ * @param $exptime int
+ * @return bool
+ */
+ public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ $this->debugLog( "cas($key)" );
+ return $this->checkResult( $key, parent::cas( $casToken, $key, $value, $exptime ) );
+ }
+
+ /**
* @param $key string
* @param $time int
* @return bool
@@ -189,7 +206,7 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
* the client, but some day we might find a case where it should be
* different.
*
- * @param $key string The key used by the caller, or false if there wasn't one.
+ * @param string $key The key used by the caller, or false if there wasn't one.
* @param $result Mixed The return value
* @return Mixed
*/
@@ -224,9 +241,11 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
* @return Array
*/
public function getMulti( array $keys ) {
+ wfProfileIn( __METHOD__ );
$this->debugLog( 'getMulti(' . implode( ', ', $keys ) . ')' );
$callback = array( $this, 'encodeKey' );
$result = $this->client->getMulti( array_map( $callback, $keys ) );
+ wfProfileOut( __METHOD__ );
return $this->checkResult( false, $result );
}
diff --git a/includes/objectcache/MemcachedPhpBagOStuff.php b/includes/objectcache/MemcachedPhpBagOStuff.php
index a46dc716..33a134c7 100644
--- a/includes/objectcache/MemcachedPhpBagOStuff.php
+++ b/includes/objectcache/MemcachedPhpBagOStuff.php
@@ -81,7 +81,7 @@ class MemcachedPhpBagOStuff extends MemcachedBagOStuff {
public function unlock( $key ) {
return $this->client->unlock( $this->encodeKey( $key ) );
}
-
+
/**
* @param $key string
* @param $value int
@@ -100,4 +100,3 @@ class MemcachedPhpBagOStuff extends MemcachedBagOStuff {
return $this->client->decr( $this->encodeKey( $key ), $value );
}
}
-
diff --git a/includes/objectcache/MultiWriteBagOStuff.php b/includes/objectcache/MultiWriteBagOStuff.php
index e496ddd8..92afaacd 100644
--- a/includes/objectcache/MultiWriteBagOStuff.php
+++ b/includes/objectcache/MultiWriteBagOStuff.php
@@ -22,8 +22,8 @@
*/
/**
- * A cache class that replicates all writes to multiple child caches. Reads
- * are implemented by reading from the caches in the order they are given in
+ * A cache class that replicates all writes to multiple child caches. Reads
+ * are implemented by reading from the caches in the order they are given in
* the configuration until a cache gives a positive result.
*
* @ingroup Cache
@@ -61,9 +61,10 @@ class MultiWriteBagOStuff extends BagOStuff {
/**
* @param $key string
+ * @param $casToken[optional] mixed
* @return bool|mixed
*/
- public function get( $key ) {
+ public function get( $key, &$casToken = null ) {
foreach ( $this->caches as $cache ) {
$value = $cache->get( $key );
if ( $value !== false ) {
@@ -74,6 +75,17 @@ class MultiWriteBagOStuff extends BagOStuff {
}
/**
+ * @param $casToken mixed
+ * @param $key string
+ * @param $value mixed
+ * @param $exptime int
+ * @return bool
+ */
+ public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ throw new MWException( "CAS is not implemented in " . __CLASS__ );
+ }
+
+ /**
* @param $key string
* @param $value mixed
* @param $exptime int
@@ -157,6 +169,17 @@ class MultiWriteBagOStuff extends BagOStuff {
}
/**
+ * @param $key string
+ * @param $callback closure Callback method to be executed
+ * @param int $exptime Either an interval in seconds or a unix timestamp for expiry
+ * @param int $attempts The amount of times to attempt a merge in case of failure
+ * @return bool success
+ */
+ public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ return $this->doWrite( 'merge', $key, $callback, $exptime );
+ }
+
+ /**
* @param $method string
* @return bool
*/
diff --git a/includes/objectcache/ObjectCache.php b/includes/objectcache/ObjectCache.php
index 9b360f32..eafa836a 100644
--- a/includes/objectcache/ObjectCache.php
+++ b/includes/objectcache/ObjectCache.php
@@ -123,7 +123,7 @@ class ObjectCache {
* @return ObjectCache
*/
static function newAccelerator( $params ) {
- if ( function_exists( 'apc_fetch') ) {
+ if ( function_exists( 'apc_fetch' ) ) {
$id = 'apc';
} elseif( function_exists( 'xcache_get' ) && wfIniGetBool( 'xcache.var_size' ) ) {
$id = 'xcache';
@@ -139,9 +139,9 @@ class ObjectCache {
/**
* Factory function that creates a memcached client object.
*
- * This always uses the PHP client, since the PECL client has a different
- * hashing scheme and a different interpretation of the flags bitfield, so
- * switching between the two clients randomly would be disasterous.
+ * This always uses the PHP client, since the PECL client has a different
+ * hashing scheme and a different interpretation of the flags bitfield, so
+ * switching between the two clients randomly would be disastrous.
*
* @param $params array
*
diff --git a/includes/objectcache/ObjectCacheSessionHandler.php b/includes/objectcache/ObjectCacheSessionHandler.php
index f55da94d..bc76294a 100644
--- a/includes/objectcache/ObjectCacheSessionHandler.php
+++ b/includes/objectcache/ObjectCacheSessionHandler.php
@@ -58,7 +58,7 @@ class ObjectCacheSessionHandler {
/**
* Get a cache key for the given session id.
*
- * @param $id String: session id
+ * @param string $id session id
* @return String: cache key
*/
static function getKey( $id ) {
@@ -89,7 +89,7 @@ class ObjectCacheSessionHandler {
/**
* Callback when reading session data.
*
- * @param $id String: session id
+ * @param string $id session id
* @return Mixed: session data
*/
static function read( $id ) {
@@ -103,7 +103,7 @@ class ObjectCacheSessionHandler {
/**
* Callback when writing session data.
*
- * @param $id String: session id
+ * @param string $id session id
* @param $data Mixed: session data
* @return Boolean: success
*/
@@ -116,7 +116,7 @@ class ObjectCacheSessionHandler {
/**
* Callback to destroy a session when calling session_destroy().
*
- * @param $id String: session id
+ * @param string $id session id
* @return Boolean: success
*/
static function destroy( $id ) {
diff --git a/includes/objectcache/RedisBagOStuff.php b/includes/objectcache/RedisBagOStuff.php
index c5966cdb..f9feaf9d 100644
--- a/includes/objectcache/RedisBagOStuff.php
+++ b/includes/objectcache/RedisBagOStuff.php
@@ -20,29 +20,13 @@
* @file
*/
-
class RedisBagOStuff extends BagOStuff {
- protected $connectTimeout, $persistent, $password, $automaticFailover;
-
- /**
- * A list of server names, from $params['servers']
- */
+ /** @var RedisConnectionPool */
+ protected $redisPool;
+ /** @var Array List of server names */
protected $servers;
-
- /**
- * A cache of Redis objects, representing connections to Redis servers.
- * The key is the server name.
- */
- protected $conns = array();
-
- /**
- * An array listing "dead" servers which have had a connection error in
- * the past. Servers are marked dead for a limited period of time, to
- * avoid excessive overhead from repeated connection timeouts. The key in
- * the array is the server name, the value is the UNIX timestamp at which
- * the server is resurrected.
- */
- protected $deadServers = array();
+ /** @var bool */
+ protected $automaticFailover;
/**
* Construct a RedisBagOStuff object. Parameters are:
@@ -71,18 +55,15 @@ class RedisBagOStuff extends BagOStuff {
* flap, for example if it is in swap death.
*/
function __construct( $params ) {
- if ( !extension_loaded( 'redis' ) ) {
- throw new MWException( __CLASS__. ' requires the phpredis extension: ' .
- 'https://github.com/nicolasff/phpredis' );
+ $redisConf = array( 'serializer' => 'php' );
+ foreach ( array( 'connectTimeout', 'persistent', 'password' ) as $opt ) {
+ if ( isset( $params[$opt] ) ) {
+ $redisConf[$opt] = $params[$opt];
+ }
}
+ $this->redisPool = RedisConnectionPool::singleton( $redisConf );
$this->servers = $params['servers'];
- $this->connectTimeout = isset( $params['connectTimeout'] )
- ? $params['connectTimeout'] : 1;
- $this->persistent = !empty( $params['persistent'] );
- if ( isset( $params['password'] ) ) {
- $this->password = $params['password'];
- }
if ( isset( $params['automaticFailover'] ) ) {
$this->automaticFailover = $params['automaticFailover'];
} else {
@@ -90,7 +71,7 @@ class RedisBagOStuff extends BagOStuff {
}
}
- public function get( $key ) {
+ public function get( $key, &$casToken = null ) {
wfProfileIn( __METHOD__ );
list( $server, $conn ) = $this->getConnection( $key );
if ( !$conn ) {
@@ -101,8 +82,9 @@ class RedisBagOStuff extends BagOStuff {
$result = $conn->get( $key );
} catch ( RedisException $e ) {
$result = false;
- $this->handleException( $server, $e );
+ $this->handleException( $server, $conn, $e );
}
+ $casToken = $result;
$this->logRequest( 'get', $key, $server, $result );
wfProfileOut( __METHOD__ );
return $result;
@@ -125,7 +107,7 @@ class RedisBagOStuff extends BagOStuff {
}
} catch ( RedisException $e ) {
$result = false;
- $this->handleException( $server, $e );
+ $this->handleException( $server, $conn, $e );
}
$this->logRequest( 'set', $key, $server, $result );
@@ -133,6 +115,42 @@ class RedisBagOStuff extends BagOStuff {
return $result;
}
+ public function cas( $casToken, $key, $value, $expiry = 0 ) {
+ wfProfileIn( __METHOD__ );
+ list( $server, $conn ) = $this->getConnection( $key );
+ if ( !$conn ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ $expiry = $this->convertToRelative( $expiry );
+ try {
+ $conn->watch( $key );
+
+ if ( $this->get( $key ) !== $casToken ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ $conn->multi();
+
+ if ( !$expiry ) {
+ // No expiry, that is very different from zero expiry in Redis
+ $conn->set( $key, $value );
+ } else {
+ $conn->setex( $key, $expiry, $value );
+ }
+
+ $result = $conn->exec();
+ } catch ( RedisException $e ) {
+ $result = false;
+ $this->handleException( $server, $conn, $e );
+ }
+
+ $this->logRequest( 'cas', $key, $server, $result );
+ wfProfileOut( __METHOD__ );
+ return $result;
+ }
+
public function delete( $key, $time = 0 ) {
wfProfileIn( __METHOD__ );
list( $server, $conn ) = $this->getConnection( $key );
@@ -146,7 +164,7 @@ class RedisBagOStuff extends BagOStuff {
$result = true;
} catch ( RedisException $e ) {
$result = false;
- $this->handleException( $server, $e );
+ $this->handleException( $server, $conn, $e );
}
$this->logRequest( 'delete', $key, $server, $result );
wfProfileOut( __METHOD__ );
@@ -184,7 +202,7 @@ class RedisBagOStuff extends BagOStuff {
}
}
} catch ( RedisException $e ) {
- $this->handleException( $server, $e );
+ $this->handleException( $server, $conn, $e );
}
}
@@ -209,7 +227,7 @@ class RedisBagOStuff extends BagOStuff {
}
} catch ( RedisException $e ) {
$result = false;
- $this->handleException( $server, $e );
+ $this->handleException( $server, $conn, $e );
}
$this->logRequest( 'add', $key, $server, $result );
wfProfileOut( __METHOD__ );
@@ -241,7 +259,7 @@ class RedisBagOStuff extends BagOStuff {
}
} catch ( RedisException $e ) {
$result = false;
- $this->handleException( $server, $e );
+ $this->handleException( $server, $conn, $e );
}
$this->logRequest( 'replace', $key, $server, $result );
@@ -273,7 +291,7 @@ class RedisBagOStuff extends BagOStuff {
$result = $conn->incrBy( $key, $value );
} catch ( RedisException $e ) {
$result = false;
- $this->handleException( $server, $e );
+ $this->handleException( $server, $conn, $e );
}
$this->logRequest( 'incr', $key, $server, $result );
@@ -283,27 +301,21 @@ class RedisBagOStuff extends BagOStuff {
/**
* Get a Redis object with a connection suitable for fetching the specified key
+ * @return Array (server, RedisConnRef) or (false, false)
*/
protected function getConnection( $key ) {
if ( count( $this->servers ) === 1 ) {
$candidates = $this->servers;
} else {
- // Use consistent hashing
- $hashes = array();
- foreach ( $this->servers as $server ) {
- $hashes[$server] = md5( $server . '/' . $key );
- }
- asort( $hashes );
+ $candidates = $this->servers;
+ ArrayUtils::consistentHashSort( $candidates, $key, '/' );
if ( !$this->automaticFailover ) {
- reset( $hashes );
- $candidates = array( key( $hashes ) );
- } else {
- $candidates = array_keys( $hashes );
+ $candidates = array_slice( $candidates, 0, 1 );
}
}
foreach ( $candidates as $server ) {
- $conn = $this->getConnectionToServer( $server );
+ $conn = $this->redisPool->getConnection( $server );
if ( $conn ) {
return array( $server, $conn );
}
@@ -312,79 +324,6 @@ class RedisBagOStuff extends BagOStuff {
}
/**
- * Get a connection to the server with the specified name. Connections
- * are cached, and failures are persistent to avoid multiple timeouts.
- *
- * @return Redis object, or false on failure
- */
- protected function getConnectionToServer( $server ) {
- if ( isset( $this->deadServers[$server] ) ) {
- $now = time();
- if ( $now > $this->deadServers[$server] ) {
- // Dead time expired
- unset( $this->deadServers[$server] );
- } else {
- // Server is dead
- $this->debug( "server $server is marked down for another " .
- ($this->deadServers[$server] - $now ) .
- " seconds, can't get connection" );
- return false;
- }
- }
-
- if ( isset( $this->conns[$server] ) ) {
- return $this->conns[$server];
- }
-
- if ( substr( $server, 0, 1 ) === '/' ) {
- // UNIX domain socket
- // These are required by the redis extension to start with a slash, but
- // we still need to set the port to a special value to make it work.
- $host = $server;
- $port = 0;
- } else {
- // TCP connection
- $hostPort = IP::splitHostAndPort( $server );
- if ( !$hostPort ) {
- throw new MWException( __CLASS__.": invalid configured server \"$server\"" );
- }
- list( $host, $port ) = $hostPort;
- if ( $port === false ) {
- $port = 6379;
- }
- }
- $conn = new Redis;
- try {
- if ( $this->persistent ) {
- $this->debug( "opening persistent connection to $host:$port" );
- $result = $conn->pconnect( $host, $port, $this->connectTimeout );
- } else {
- $this->debug( "opening non-persistent connection to $host:$port" );
- $result = $conn->connect( $host, $port, $this->connectTimeout );
- }
- if ( !$result ) {
- $this->logError( "could not connect to server $server" );
- // Mark server down for 30s to avoid further timeouts
- $this->deadServers[$server] = time() + 30;
- return false;
- }
- if ( $this->password !== null ) {
- if ( !$conn->auth( $this->password ) ) {
- $this->logError( "authentication error connecting to $server" );
- }
- }
- } catch ( RedisException $e ) {
- $this->deadServers[$server] = time() + 30;
- wfDebugLog( 'redis', "Redis exception: " . $e->getMessage() . "\n" );
- return false;
- }
-
- $conn->setOption( Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP );
- $this->conns[$server] = $conn;
- return $conn;
- }
-
- /**
* Log a fatal error
*/
protected function logError( $msg ) {
@@ -397,9 +336,8 @@ class RedisBagOStuff extends BagOStuff {
* not. The safest response for us is to explicitly destroy the connection
* object and let it be reopened during the next request.
*/
- protected function handleException( $server, $e ) {
- wfDebugLog( 'redis', "Redis exception on server $server: " . $e->getMessage() . "\n" );
- unset( $this->conns[$server] );
+ protected function handleException( $server, RedisConnRef $conn, $e ) {
+ $this->redisPool->handleException( $server, $conn, $e );
}
/**
@@ -410,4 +348,3 @@ class RedisBagOStuff extends BagOStuff {
( $result === false ? "failure" : "success" ) );
}
}
-
diff --git a/includes/objectcache/SqlBagOStuff.php b/includes/objectcache/SqlBagOStuff.php
index 54051dc1..87f787d8 100644
--- a/includes/objectcache/SqlBagOStuff.php
+++ b/includes/objectcache/SqlBagOStuff.php
@@ -32,23 +32,26 @@ class SqlBagOStuff extends BagOStuff {
*/
var $lb;
- /**
- * @var DatabaseBase
- */
- var $db;
- var $serverInfo;
+ var $serverInfos;
+ var $serverNames;
+ var $numServers;
+ var $conns;
var $lastExpireAll = 0;
var $purgePeriod = 100;
var $shards = 1;
var $tableName = 'objectcache';
- protected $connFailureTime = 0; // UNIX timestamp
- protected $connFailureError; // exception
+ protected $connFailureTimes = array(); // UNIX timestamps
+ protected $connFailureErrors = array(); // exceptions
/**
* Constructor. Parameters are:
- * - server: A server info structure in the format required by each
- * element in $wgDBServers.
+ * - server: A server info structure in the format required by each
+ * element in $wgDBServers.
+ *
+ * - servers: An array of server info structures describing a set of
+ * database servers to distribute keys to. If this is
+ * specified, the "server" option will be ignored.
*
* - purgePeriod: The average number of object cache requests in between
* garbage collection operations, where expired entries
@@ -59,8 +62,8 @@ class SqlBagOStuff extends BagOStuff {
*
* - tableName: The table name to use, default is "objectcache".
*
- * - shards: The number of tables to use for data storage. If this is
- * more than 1, table names will be formed in the style
+ * - shards: The number of tables to use for data storage on each server.
+ * If this is more than 1, table names will be formed in the style
* objectcacheNNN where NNN is the shard index, between 0 and
* shards-1. The number of digits will be the minimum number
* required to hold the largest shard index. Data will be
@@ -70,9 +73,19 @@ class SqlBagOStuff extends BagOStuff {
* @param $params array
*/
public function __construct( $params ) {
- if ( isset( $params['server'] ) ) {
- $this->serverInfo = $params['server'];
- $this->serverInfo['load'] = 1;
+ if ( isset( $params['servers'] ) ) {
+ $this->serverInfos = $params['servers'];
+ $this->numServers = count( $this->serverInfos );
+ $this->serverNames = array();
+ foreach ( $this->serverInfos as $i => $info ) {
+ $this->serverNames[$i] = isset( $info['host'] ) ? $info['host'] : "#$i";
+ }
+ } elseif ( isset( $params['server'] ) ) {
+ $this->serverInfos = array( $params['server'] );
+ $this->numServers = count( $this->serverInfos );
+ } else {
+ $this->serverInfos = false;
+ $this->numServers = 1;
}
if ( isset( $params['purgePeriod'] ) ) {
$this->purgePeriod = intval( $params['purgePeriod'] );
@@ -86,60 +99,81 @@ class SqlBagOStuff extends BagOStuff {
}
/**
+ * Get a connection to the specified database
+ *
+ * @param $serverIndex integer
* @return DatabaseBase
*/
- protected function getDB() {
+ protected function getDB( $serverIndex ) {
global $wgDebugDBTransactions;
- # Don't keep timing out trying to connect for each call if the DB is down
- if ( $this->connFailureError && ( time() - $this->connFailureTime ) < 60 ) {
- throw $this->connFailureError;
- }
+ if ( !isset( $this->conns[$serverIndex] ) ) {
+ if ( $serverIndex >= $this->numServers ) {
+ throw new MWException( __METHOD__ . ": Invalid server index \"$serverIndex\"" );
+ }
+
+ # Don't keep timing out trying to connect for each call if the DB is down
+ if ( isset( $this->connFailureErrors[$serverIndex] )
+ && ( time() - $this->connFailureTimes[$serverIndex] ) < 60 )
+ {
+ throw $this->connFailureErrors[$serverIndex];
+ }
- if ( !isset( $this->db ) ) {
# If server connection info was given, use that
- if ( $this->serverInfo ) {
+ if ( $this->serverInfos ) {
if ( $wgDebugDBTransactions ) {
- wfDebug( sprintf( "Using provided serverInfo for SqlBagOStuff\n" ) );
+ wfDebug( "Using provided serverInfo for SqlBagOStuff\n" );
}
- $this->lb = new LoadBalancer( array(
- 'servers' => array( $this->serverInfo ) ) );
- $this->db = $this->lb->getConnection( DB_MASTER );
- $this->db->clearFlag( DBO_TRX );
+ $info = $this->serverInfos[$serverIndex];
+ $type = isset( $info['type'] ) ? $info['type'] : 'mysql';
+ $host = isset( $info['host'] ) ? $info['host'] : '[unknown]';
+ wfDebug( __CLASS__ . ": connecting to $host\n" );
+ $db = DatabaseBase::factory( $type, $info );
+ $db->clearFlag( DBO_TRX );
} else {
/*
* We must keep a separate connection to MySQL in order to avoid deadlocks
- * However, SQLite has an opposite behaviour. And PostgreSQL needs to know
+ * However, SQLite has an opposite behavior. And PostgreSQL needs to know
* if we are in transaction or no
*/
if ( wfGetDB( DB_MASTER )->getType() == 'mysql' ) {
$this->lb = wfGetLBFactory()->newMainLB();
- $this->db = $this->lb->getConnection( DB_MASTER );
- $this->db->clearFlag( DBO_TRX ); // auto-commit mode
+ $db = $this->lb->getConnection( DB_MASTER );
+ $db->clearFlag( DBO_TRX ); // auto-commit mode
} else {
- $this->db = wfGetDB( DB_MASTER );
+ $db = wfGetDB( DB_MASTER );
}
}
if ( $wgDebugDBTransactions ) {
- wfDebug( sprintf( "Connection %s will be used for SqlBagOStuff\n", $this->db ) );
+ wfDebug( sprintf( "Connection %s will be used for SqlBagOStuff\n", $db ) );
}
+ $this->conns[$serverIndex] = $db;
}
- return $this->db;
+ return $this->conns[$serverIndex];
}
/**
- * Get the table name for a given key
+ * Get the server index and table name for a given key
* @param $key string
- * @return string
+ * @return Array: server index and table name
*/
protected function getTableByKey( $key ) {
if ( $this->shards > 1 ) {
$hash = hexdec( substr( md5( $key ), 0, 8 ) ) & 0x7fffffff;
- return $this->getTableByShard( $hash % $this->shards );
+ $tableIndex = $hash % $this->shards;
} else {
- return $this->tableName;
+ $tableIndex = 0;
+ }
+ if ( $this->numServers > 1 ) {
+ $sortedServers = $this->serverNames;
+ ArrayUtils::consistentHashSort( $sortedServers, $key );
+ reset( $sortedServers );
+ $serverIndex = key( $sortedServers );
+ } else {
+ $serverIndex = 0;
}
+ return array( $serverIndex, $this->getTableNameByShard( $tableIndex ) );
}
/**
@@ -147,7 +181,7 @@ class SqlBagOStuff extends BagOStuff {
* @param $index int
* @return string
*/
- protected function getTableByShard( $index ) {
+ protected function getTableNameByShard( $index ) {
if ( $this->shards > 1 ) {
$decimals = strlen( $this->shards - 1 );
return $this->tableName .
@@ -159,11 +193,16 @@ class SqlBagOStuff extends BagOStuff {
/**
* @param $key string
+ * @param $casToken[optional] mixed
* @return mixed
*/
- public function get( $key ) {
+ public function get( $key, &$casToken = null ) {
$values = $this->getMulti( array( $key ) );
- return array_key_exists( $key, $values ) ? $values[$key] : false;
+ if ( array_key_exists( $key, $values ) ) {
+ $casToken = $values[$key];
+ return $values[$key];
+ }
+ return false;
}
/**
@@ -173,59 +212,61 @@ class SqlBagOStuff extends BagOStuff {
public function getMulti( array $keys ) {
$values = array(); // array of (key => value)
- try {
- $db = $this->getDB();
- $keysByTableName = array();
- foreach ( $keys as $key ) {
- $tableName = $this->getTableByKey( $key );
- if ( !isset( $keysByTableName[$tableName] ) ) {
- $keysByTableName[$tableName] = array();
- }
- $keysByTableName[$tableName][] = $key;
- }
+ $keysByTable = array();
+ foreach ( $keys as $key ) {
+ list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
+ $keysByTable[$serverIndex][$tableName][] = $key;
+ }
- $this->garbageCollect(); // expire old entries if any
+ $this->garbageCollect(); // expire old entries if any
- $dataRows = array();
- foreach ( $keysByTableName as $tableName => $tableKeys ) {
- $res = $db->select( $tableName,
- array( 'keyname', 'value', 'exptime' ),
- array( 'keyname' => $tableKeys ),
- __METHOD__ );
- foreach ( $res as $row ) {
- $dataRows[$row->keyname] = $row;
+ $dataRows = array();
+ foreach ( $keysByTable as $serverIndex => $serverKeys ) {
+ $db = $this->getDB( $serverIndex );
+ try {
+ foreach ( $serverKeys as $tableName => $tableKeys ) {
+ $res = $db->select( $tableName,
+ array( 'keyname', 'value', 'exptime' ),
+ array( 'keyname' => $tableKeys ),
+ __METHOD__ );
+ foreach ( $res as $row ) {
+ $row->serverIndex = $serverIndex;
+ $row->tableName = $tableName;
+ $dataRows[$row->keyname] = $row;
+ }
}
+ } catch ( DBError $e ) {
+ $this->handleReadError( $e, $serverIndex );
}
+ }
- foreach ( $keys as $key ) {
- if ( isset( $dataRows[$key] ) ) { // HIT?
- $row = $dataRows[$key];
- $this->debug( "get: retrieved data; expiry time is " . $row->exptime );
- if ( $this->isExpired( $row->exptime ) ) { // MISS
- $this->debug( "get: key has expired, deleting" );
- try {
- $db->begin( __METHOD__ );
- # Put the expiry time in the WHERE condition to avoid deleting a
- # newly-inserted value
- $db->delete( $this->getTableByKey( $key ),
- array( 'keyname' => $key, 'exptime' => $row->exptime ),
- __METHOD__ );
- $db->commit( __METHOD__ );
- } catch ( DBQueryError $e ) {
- $this->handleWriteError( $e );
- }
- $values[$key] = false;
- } else { // HIT
- $values[$key] = $this->unserialize( $db->decodeBlob( $row->value ) );
+ foreach ( $keys as $key ) {
+ if ( isset( $dataRows[$key] ) ) { // HIT?
+ $row = $dataRows[$key];
+ $this->debug( "get: retrieved data; expiry time is " . $row->exptime );
+ $db = $this->getDB( $row->serverIndex );
+ if ( $this->isExpired( $db, $row->exptime ) ) { // MISS
+ $this->debug( "get: key has expired, deleting" );
+ try {
+ $db->begin( __METHOD__ );
+ # Put the expiry time in the WHERE condition to avoid deleting a
+ # newly-inserted value
+ $db->delete( $row->tableName,
+ array( 'keyname' => $key, 'exptime' => $row->exptime ),
+ __METHOD__ );
+ $db->commit( __METHOD__ );
+ } catch ( DBQueryError $e ) {
+ $this->handleWriteError( $e, $row->serverIndex );
}
- } else { // MISS
$values[$key] = false;
- $this->debug( 'get: no matching rows' );
+ } else { // HIT
+ $values[$key] = $this->unserialize( $db->decodeBlob( $row->value ) );
}
+ } else { // MISS
+ $values[$key] = false;
+ $this->debug( 'get: no matching rows' );
}
- } catch ( DBError $e ) {
- $this->handleReadError( $e );
- };
+ }
return $values;
}
@@ -237,8 +278,9 @@ class SqlBagOStuff extends BagOStuff {
* @return bool
*/
public function set( $key, $value, $exptime = 0 ) {
+ list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
try {
- $db = $this->getDB();
+ $db = $this->getDB( $serverIndex );
$exptime = intval( $exptime );
if ( $exptime < 0 ) {
@@ -246,7 +288,7 @@ class SqlBagOStuff extends BagOStuff {
}
if ( $exptime == 0 ) {
- $encExpiry = $this->getMaxDateTime();
+ $encExpiry = $this->getMaxDateTime( $db );
} else {
if ( $exptime < 3.16e8 ) { # ~10 years
$exptime += time();
@@ -258,7 +300,7 @@ class SqlBagOStuff extends BagOStuff {
// (bug 24425) use a replace if the db supports it instead of
// delete/insert to avoid clashes with conflicting keynames
$db->replace(
- $this->getTableByKey( $key ),
+ $tableName,
array( 'keyname' ),
array(
'keyname' => $key,
@@ -267,7 +309,7 @@ class SqlBagOStuff extends BagOStuff {
), __METHOD__ );
$db->commit( __METHOD__ );
} catch ( DBError $e ) {
- $this->handleWriteError( $e );
+ $this->handleWriteError( $e, $serverIndex );
return false;
}
@@ -275,21 +317,73 @@ class SqlBagOStuff extends BagOStuff {
}
/**
+ * @param $casToken mixed
+ * @param $key string
+ * @param $value mixed
+ * @param $exptime int
+ * @return bool
+ */
+ public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
+ try {
+ $db = $this->getDB( $serverIndex );
+ $exptime = intval( $exptime );
+
+ if ( $exptime < 0 ) {
+ $exptime = 0;
+ }
+
+ if ( $exptime == 0 ) {
+ $encExpiry = $this->getMaxDateTime( $db );
+ } else {
+ if ( $exptime < 3.16e8 ) { # ~10 years
+ $exptime += time();
+ }
+ $encExpiry = $db->timestamp( $exptime );
+ }
+ $db->begin( __METHOD__ );
+ // (bug 24425) use a replace if the db supports it instead of
+ // delete/insert to avoid clashes with conflicting keynames
+ $db->update(
+ $tableName,
+ array(
+ 'keyname' => $key,
+ 'value' => $db->encodeBlob( $this->serialize( $value ) ),
+ 'exptime' => $encExpiry
+ ),
+ array(
+ 'keyname' => $key,
+ 'value' => $db->encodeBlob( $this->serialize( $casToken ) )
+ ),
+ __METHOD__
+ );
+ $db->commit( __METHOD__ );
+ } catch ( DBQueryError $e ) {
+ $this->handleWriteError( $e, $serverIndex );
+
+ return false;
+ }
+
+ return (bool) $db->affectedRows();
+ }
+
+ /**
* @param $key string
* @param $time int
* @return bool
*/
public function delete( $key, $time = 0 ) {
+ list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
try {
- $db = $this->getDB();
+ $db = $this->getDB( $serverIndex );
$db->begin( __METHOD__ );
$db->delete(
- $this->getTableByKey( $key ),
+ $tableName,
array( 'keyname' => $key ),
__METHOD__ );
$db->commit( __METHOD__ );
} catch ( DBError $e ) {
- $this->handleWriteError( $e );
+ $this->handleWriteError( $e, $serverIndex );
return false;
}
@@ -302,9 +396,9 @@ class SqlBagOStuff extends BagOStuff {
* @return int|null
*/
public function incr( $key, $step = 1 ) {
+ list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
try {
- $db = $this->getDB();
- $tableName = $this->getTableByKey( $key );
+ $db = $this->getDB( $serverIndex );
$step = intval( $step );
$db->begin( __METHOD__ );
$row = $db->selectRow(
@@ -320,7 +414,7 @@ class SqlBagOStuff extends BagOStuff {
return null;
}
$db->delete( $tableName, array( 'keyname' => $key ), __METHOD__ );
- if ( $this->isExpired( $row->exptime ) ) {
+ if ( $this->isExpired( $db, $row->exptime ) ) {
// Expired, do not reinsert
$db->commit( __METHOD__ );
@@ -342,7 +436,7 @@ class SqlBagOStuff extends BagOStuff {
}
$db->commit( __METHOD__ );
} catch ( DBError $e ) {
- $this->handleWriteError( $e );
+ $this->handleWriteError( $e, $serverIndex );
return null;
}
@@ -350,43 +444,21 @@ class SqlBagOStuff extends BagOStuff {
}
/**
- * @return Array
- */
- public function keys() {
- $result = array();
-
- try {
- $db = $this->getDB();
- for ( $i = 0; $i < $this->shards; $i++ ) {
- $res = $db->select( $this->getTableByShard( $i ),
- array( 'keyname' ), false, __METHOD__ );
- foreach ( $res as $row ) {
- $result[] = $row->keyname;
- }
- }
- } catch ( DBError $e ) {
- $this->handleReadError( $e );
- }
-
- return $result;
- }
-
- /**
* @param $exptime string
* @return bool
*/
- protected function isExpired( $exptime ) {
- return $exptime != $this->getMaxDateTime() && wfTimestamp( TS_UNIX, $exptime ) < time();
+ protected function isExpired( $db, $exptime ) {
+ return $exptime != $this->getMaxDateTime( $db ) && wfTimestamp( TS_UNIX, $exptime ) < time();
}
/**
* @return string
*/
- protected function getMaxDateTime() {
+ protected function getMaxDateTime( $db ) {
if ( time() > 0x7fffffff ) {
- return $this->getDB()->timestamp( 1 << 62 );
+ return $db->timestamp( 1 << 62 );
} else {
- return $this->getDB()->timestamp( 0x7fffffff );
+ return $db->timestamp( 0x7fffffff );
}
}
@@ -418,87 +490,91 @@ class SqlBagOStuff extends BagOStuff {
* @return bool
*/
public function deleteObjectsExpiringBefore( $timestamp, $progressCallback = false ) {
- try {
- $db = $this->getDB();
- $dbTimestamp = $db->timestamp( $timestamp );
- $totalSeconds = false;
- $baseConds = array( 'exptime < ' . $db->addQuotes( $dbTimestamp ) );
- for ( $i = 0; $i < $this->shards; $i++ ) {
- $maxExpTime = false;
- while ( true ) {
- $conds = $baseConds;
- if ( $maxExpTime !== false ) {
- $conds[] = 'exptime > ' . $db->addQuotes( $maxExpTime );
- }
- $rows = $db->select(
- $this->getTableByShard( $i ),
- array( 'keyname', 'exptime' ),
- $conds,
- __METHOD__,
- array( 'LIMIT' => 100, 'ORDER BY' => 'exptime' ) );
- if ( !$rows->numRows() ) {
- break;
- }
- $keys = array();
- $row = $rows->current();
- $minExpTime = $row->exptime;
- if ( $totalSeconds === false ) {
- $totalSeconds = wfTimestamp( TS_UNIX, $timestamp )
- - wfTimestamp( TS_UNIX, $minExpTime );
- }
- foreach ( $rows as $row ) {
- $keys[] = $row->keyname;
- $maxExpTime = $row->exptime;
- }
-
- $db->begin( __METHOD__ );
- $db->delete(
- $this->getTableByShard( $i ),
- array(
- 'exptime >= ' . $db->addQuotes( $minExpTime ),
- 'exptime < ' . $db->addQuotes( $dbTimestamp ),
- 'keyname' => $keys
- ),
- __METHOD__ );
- $db->commit( __METHOD__ );
+ for ( $serverIndex = 0; $serverIndex < $this->numServers; $serverIndex++ ) {
+ try {
+ $db = $this->getDB( $serverIndex );
+ $dbTimestamp = $db->timestamp( $timestamp );
+ $totalSeconds = false;
+ $baseConds = array( 'exptime < ' . $db->addQuotes( $dbTimestamp ) );
+ for ( $i = 0; $i < $this->shards; $i++ ) {
+ $maxExpTime = false;
+ while ( true ) {
+ $conds = $baseConds;
+ if ( $maxExpTime !== false ) {
+ $conds[] = 'exptime > ' . $db->addQuotes( $maxExpTime );
+ }
+ $rows = $db->select(
+ $this->getTableNameByShard( $i ),
+ array( 'keyname', 'exptime' ),
+ $conds,
+ __METHOD__,
+ array( 'LIMIT' => 100, 'ORDER BY' => 'exptime' ) );
+ if ( !$rows->numRows() ) {
+ break;
+ }
+ $keys = array();
+ $row = $rows->current();
+ $minExpTime = $row->exptime;
+ if ( $totalSeconds === false ) {
+ $totalSeconds = wfTimestamp( TS_UNIX, $timestamp )
+ - wfTimestamp( TS_UNIX, $minExpTime );
+ }
+ foreach ( $rows as $row ) {
+ $keys[] = $row->keyname;
+ $maxExpTime = $row->exptime;
+ }
- if ( $progressCallback ) {
- if ( intval( $totalSeconds ) === 0 ) {
- $percent = 0;
- } else {
- $remainingSeconds = wfTimestamp( TS_UNIX, $timestamp )
- - wfTimestamp( TS_UNIX, $maxExpTime );
- if ( $remainingSeconds > $totalSeconds ) {
- $totalSeconds = $remainingSeconds;
+ $db->begin( __METHOD__ );
+ $db->delete(
+ $this->getTableNameByShard( $i ),
+ array(
+ 'exptime >= ' . $db->addQuotes( $minExpTime ),
+ 'exptime < ' . $db->addQuotes( $dbTimestamp ),
+ 'keyname' => $keys
+ ),
+ __METHOD__ );
+ $db->commit( __METHOD__ );
+
+ if ( $progressCallback ) {
+ if ( intval( $totalSeconds ) === 0 ) {
+ $percent = 0;
+ } else {
+ $remainingSeconds = wfTimestamp( TS_UNIX, $timestamp )
+ - wfTimestamp( TS_UNIX, $maxExpTime );
+ if ( $remainingSeconds > $totalSeconds ) {
+ $totalSeconds = $remainingSeconds;
+ }
+ $percent = ( $i + $remainingSeconds / $totalSeconds )
+ / $this->shards * 100;
}
- $percent = ( $i + $remainingSeconds / $totalSeconds )
- / $this->shards * 100;
+ $percent = ( $percent / $this->numServers )
+ + ( $serverIndex / $this->numServers * 100 );
+ call_user_func( $progressCallback, $percent );
}
- call_user_func( $progressCallback, $percent );
}
}
+ } catch ( DBError $e ) {
+ $this->handleWriteError( $e, $serverIndex );
+ return false;
}
- } catch ( DBError $e ) {
- $this->handleWriteError( $e );
- return false;
}
-
return true;
}
public function deleteAll() {
- try {
- $db = $this->getDB();
- for ( $i = 0; $i < $this->shards; $i++ ) {
- $db->begin( __METHOD__ );
- $db->delete( $this->getTableByShard( $i ), '*', __METHOD__ );
- $db->commit( __METHOD__ );
+ for ( $serverIndex = 0; $serverIndex < $this->numServers; $serverIndex++ ) {
+ try {
+ $db = $this->getDB( $serverIndex );
+ for ( $i = 0; $i < $this->shards; $i++ ) {
+ $db->begin( __METHOD__ );
+ $db->delete( $this->getTableNameByShard( $i ), '*', __METHOD__ );
+ $db->commit( __METHOD__ );
+ }
+ } catch ( DBError $e ) {
+ $this->handleWriteError( $e, $serverIndex );
+ return false;
}
- } catch ( DBError $e ) {
- $this->handleWriteError( $e );
- return false;
}
-
return true;
}
@@ -544,58 +620,77 @@ class SqlBagOStuff extends BagOStuff {
/**
* Handle a DBError which occurred during a read operation.
*/
- protected function handleReadError( DBError $exception ) {
+ protected function handleReadError( DBError $exception, $serverIndex ) {
if ( $exception instanceof DBConnectionError ) {
- $this->connFailureTime = time();
- $this->connFailureError = $exception;
+ $this->markServerDown( $exception, $serverIndex );
}
wfDebugLog( 'SQLBagOStuff', "DBError: {$exception->getMessage()}" );
- if ( $this->db ) {
- wfDebug( __METHOD__ . ": ignoring query error\n" );
- } else {
+ if ( $exception instanceof DBConnectionError ) {
wfDebug( __METHOD__ . ": ignoring connection error\n" );
+ } else {
+ wfDebug( __METHOD__ . ": ignoring query error\n" );
}
}
/**
* Handle a DBQueryError which occurred during a write operation.
*/
- protected function handleWriteError( DBError $exception ) {
+ protected function handleWriteError( DBError $exception, $serverIndex ) {
if ( $exception instanceof DBConnectionError ) {
- $this->connFailureTime = time();
- $this->connFailureError = $exception;
+ $this->markServerDown( $exception, $serverIndex );
}
- if ( $this->db && $this->db->wasReadOnlyError() ) {
+ if ( $exception->db && $exception->db->wasReadOnlyError() ) {
try {
- $this->db->rollback( __METHOD__ );
+ $exception->db->rollback( __METHOD__ );
} catch ( DBError $e ) {}
}
wfDebugLog( 'SQLBagOStuff', "DBError: {$exception->getMessage()}" );
- if ( $this->db ) {
- wfDebug( __METHOD__ . ": ignoring query error\n" );
- } else {
+ if ( $exception instanceof DBConnectionError ) {
wfDebug( __METHOD__ . ": ignoring connection error\n" );
+ } else {
+ wfDebug( __METHOD__ . ": ignoring query error\n" );
+ }
+ }
+
+ /**
+ * Mark a server down due to a DBConnectionError exception
+ */
+ protected function markServerDown( $exception, $serverIndex ) {
+ if ( isset( $this->connFailureTimes[$serverIndex] ) ) {
+ if ( time() - $this->connFailureTimes[$serverIndex] >= 60 ) {
+ unset( $this->connFailureTimes[$serverIndex] );
+ unset( $this->connFailureErrors[$serverIndex] );
+ } else {
+ wfDebug( __METHOD__ . ": Server #$serverIndex already down\n" );
+ return;
+ }
}
+ $now = time();
+ wfDebug( __METHOD__ . ": Server #$serverIndex down until " . ( $now + 60 ) . "\n" );
+ $this->connFailureTimes[$serverIndex] = $now;
+ $this->connFailureErrors[$serverIndex] = $exception;
}
/**
* Create shard tables. For use from eval.php.
*/
public function createTables() {
- $db = $this->getDB();
- if ( $db->getType() !== 'mysql'
- || version_compare( $db->getServerVersion(), '4.1.0', '<' ) )
- {
- throw new MWException( __METHOD__ . ' is not supported on this DB server' );
- }
+ for ( $serverIndex = 0; $serverIndex < $this->numServers; $serverIndex++ ) {
+ $db = $this->getDB( $serverIndex );
+ if ( $db->getType() !== 'mysql'
+ || version_compare( $db->getServerVersion(), '4.1.0', '<' ) )
+ {
+ throw new MWException( __METHOD__ . ' is not supported on this DB server' );
+ }
- for ( $i = 0; $i < $this->shards; $i++ ) {
- $db->begin( __METHOD__ );
- $db->query(
- 'CREATE TABLE ' . $db->tableName( $this->getTableByShard( $i ) ) .
- ' LIKE ' . $db->tableName( 'objectcache' ),
- __METHOD__ );
- $db->commit( __METHOD__ );
+ for ( $i = 0; $i < $this->shards; $i++ ) {
+ $db->begin( __METHOD__ );
+ $db->query(
+ 'CREATE TABLE ' . $db->tableName( $this->getTableNameByShard( $i ) ) .
+ ' LIKE ' . $db->tableName( 'objectcache' ),
+ __METHOD__ );
+ $db->commit( __METHOD__ );
+ }
}
}
}
@@ -604,4 +699,3 @@ class SqlBagOStuff extends BagOStuff {
* Backwards compatibility alias
*/
class MediaWikiBagOStuff extends SqlBagOStuff { }
-
diff --git a/includes/objectcache/WinCacheBagOStuff.php b/includes/objectcache/WinCacheBagOStuff.php
index 21aa39e7..6d9b47ad 100644
--- a/includes/objectcache/WinCacheBagOStuff.php
+++ b/includes/objectcache/WinCacheBagOStuff.php
@@ -32,12 +32,15 @@ class WinCacheBagOStuff extends BagOStuff {
/**
* Get a value from the WinCache object cache
*
- * @param $key String: cache key
+ * @param string $key cache key
+ * @param $casToken[optional] int: cas token
* @return mixed
*/
- public function get( $key ) {
+ public function get( $key, &$casToken = null ) {
$val = wincache_ucache_get( $key );
+ $casToken = $val;
+
if ( is_string( $val ) ) {
$val = unserialize( $val );
}
@@ -48,9 +51,9 @@ class WinCacheBagOStuff extends BagOStuff {
/**
* Store a value in the WinCache object cache
*
- * @param $key String: cache key
+ * @param string $key cache key
* @param $value Mixed: object to store
- * @param $expire Int: expiration time
+ * @param int $expire expiration time
* @return bool
*/
public function set( $key, $value, $expire = 0 ) {
@@ -62,34 +65,28 @@ class WinCacheBagOStuff extends BagOStuff {
}
/**
- * Remove a value from the WinCache object cache
+ * Store a value in the WinCache object cache, race condition-safe
*
- * @param $key String: cache key
- * @param $time Int: not used in this implementation
+ * @param int $casToken cas token
+ * @param string $key cache key
+ * @param int $value object to store
+ * @param int $exptime expiration time
* @return bool
*/
- public function delete( $key, $time = 0 ) {
- wincache_ucache_delete( $key );
-
- return true;
+ public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ return wincache_ucache_cas( $key, $casToken, serialize( $value ) );
}
/**
- * @return Array
+ * Remove a value from the WinCache object cache
+ *
+ * @param string $key cache key
+ * @param int $time not used in this implementation
+ * @return bool
*/
- public function keys() {
- $info = wincache_ucache_info();
- $list = $info['ucache_entries'];
- $keys = array();
-
- if ( is_null( $list ) ) {
- return array();
- }
-
- foreach ( $list as $entry ) {
- $keys[] = $entry['key_name'];
- }
+ public function delete( $key, $time = 0 ) {
+ wincache_ucache_delete( $key );
- return $keys;
+ return true;
}
}
diff --git a/includes/objectcache/XCacheBagOStuff.php b/includes/objectcache/XCacheBagOStuff.php
index bc68b596..0f45db73 100644
--- a/includes/objectcache/XCacheBagOStuff.php
+++ b/includes/objectcache/XCacheBagOStuff.php
@@ -31,10 +31,11 @@ class XCacheBagOStuff extends BagOStuff {
/**
* Get a value from the XCache object cache
*
- * @param $key String: cache key
+ * @param string $key cache key
+ * @param $casToken mixed: cas token
* @return mixed
*/
- public function get( $key ) {
+ public function get( $key, &$casToken = null ) {
$val = xcache_get( $key );
if ( is_string( $val ) ) {
@@ -53,9 +54,9 @@ class XCacheBagOStuff extends BagOStuff {
/**
* Store a value in the XCache object cache
*
- * @param $key String: cache key
+ * @param string $key cache key
* @param $value Mixed: object to store
- * @param $expire Int: expiration time
+ * @param int $expire expiration time
* @return bool
*/
public function set( $key, $value, $expire = 0 ) {
@@ -68,10 +69,22 @@ class XCacheBagOStuff extends BagOStuff {
}
/**
+ * @param $casToken mixed
+ * @param $key string
+ * @param $value mixed
+ * @param $exptime int
+ * @return bool
+ */
+ public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ // Can't find any documentation on xcache cas
+ throw new MWException( "CAS is not implemented in " . __CLASS__ );
+ }
+
+ /**
* Remove a value from the XCache object cache
*
- * @param $key String: cache key
- * @param $time Int: not used in this implementation
+ * @param string $key cache key
+ * @param int $time not used in this implementation
* @return bool
*/
public function delete( $key, $time = 0 ) {
@@ -79,6 +92,21 @@ class XCacheBagOStuff extends BagOStuff {
return true;
}
+ /**
+ * Merge an item.
+ * XCache does not seem to support any way of performing CAS - this however will
+ * provide a way to perform CAS-like functionality.
+ *
+ * @param $key string
+ * @param $callback closure Callback method to be executed
+ * @param int $exptime Either an interval in seconds or a unix timestamp for expiry
+ * @param int $attempts The amount of times to attempt a merge in case of failure
+ * @return bool success
+ */
+ public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ return $this->mergeViaLock( $key, $callback, $exptime, $attempts );
+ }
+
public function incr( $key, $value = 1 ) {
return xcache_inc( $key, $value );
}
diff --git a/includes/parser/CacheTime.php b/includes/parser/CacheTime.php
index 881dded7..6b70e1da 100644
--- a/includes/parser/CacheTime.php
+++ b/includes/parser/CacheTime.php
@@ -116,7 +116,7 @@ class CacheTime {
* per-article cache invalidation timestamps, or if it comes from
* an incompatible older version.
*
- * @param $touched String: the affected article's last touched timestamp
+ * @param string $touched the affected article's last touched timestamp
* @return Boolean
*/
public function expired( $touched ) {
diff --git a/includes/parser/CoreLinkFunctions.php b/includes/parser/CoreLinkFunctions.php
index 4bfa9d35..1cabf766 100644
--- a/includes/parser/CoreLinkFunctions.php
+++ b/includes/parser/CoreLinkFunctions.php
@@ -47,15 +47,15 @@ class CoreLinkFunctions {
*/
static function defaultLinkHook( $parser, $holders, $markers,
Title $title, $titleText, &$displayText = null, &$leadingColon = false ) {
- if( isset($displayText) && $markers->findMarker( $displayText ) ) {
+ if( isset( $displayText ) && $markers->findMarker( $displayText ) ) {
# There are links inside of the displayText
# For backwards compatibility the deepest links are dominant so this
# link should not be handled
- $displayText = $markers->expand($displayText);
+ $displayText = $markers->expand( $displayText );
# Return false so that this link is reverted back to WikiText
return false;
}
- return $holders->makeHolder( $title, isset($displayText) ? $displayText : $titleText, array(), '', '' );
+ return $holders->makeHolder( $title, isset( $displayText ) ? $displayText : $titleText, array(), '', '' );
}
/**
@@ -73,15 +73,15 @@ class CoreLinkFunctions {
global $wgContLang;
# When a category link starts with a : treat it as a normal link
if( $leadingColon ) return true;
- if( isset($sortText) && $markers->findMarker( $sortText ) ) {
+ if( isset( $sortText ) && $markers->findMarker( $sortText ) ) {
# There are links inside of the sortText
# For backwards compatibility the deepest links are dominant so this
# link should not be handled
- $sortText = $markers->expand($sortText);
+ $sortText = $markers->expand( $sortText );
# Return false so that this link is reverted back to WikiText
return false;
}
- if( !isset($sortText) ) $sortText = $parser->getDefaultSort();
+ if( !isset( $sortText ) ) $sortText = $parser->getDefaultSort();
$sortText = Sanitizer::decodeCharReferences( $sortText );
$sortText = str_replace( "\n", '', $sortText );
$sortText = $wgContLang->convertCategoryKey( $sortText );
diff --git a/includes/parser/CoreParserFunctions.php b/includes/parser/CoreParserFunctions.php
index 8917b6d0..cdd03aa4 100644
--- a/includes/parser/CoreParserFunctions.php
+++ b/includes/parser/CoreParserFunctions.php
@@ -175,8 +175,8 @@ class CoreParserFunctions {
* For links to "wiki"s, or similar software, spaces are encoded as '_',
*
* @param $parser Parser object
- * @param $s String: The text to encode.
- * @param $arg String (optional): The type of encoding.
+ * @param string $s The text to encode.
+ * @param string $arg (optional): The type of encoding.
* @return string
*/
static function urlencode( $parser, $s = '', $arg = null ) {
@@ -269,12 +269,14 @@ class CoreParserFunctions {
/**
* @param $parser Parser
* @param string $num
- * @param null $raw
- * @return
+ * @param string $arg
+ * @return string
*/
- static function formatnum( $parser, $num = '', $raw = null) {
- if ( self::isRaw( $raw ) ) {
+ static function formatnum( $parser, $num = '', $arg = null ) {
+ if ( self::matchAgainstMagicword( 'rawsuffix', $arg ) ) {
$func = array( $parser->getFunctionLang(), 'parseFormattedNumber' );
+ } elseif ( self::matchAgainstMagicword( 'nocommafysuffix', $arg ) ) {
+ $func = array( $parser->getFunctionLang(), 'formatNumNoSeparators' );
} else {
$func = array( $parser->getFunctionLang(), 'formatNum' );
}
@@ -351,7 +353,7 @@ class CoreParserFunctions {
* title which will normalise to the canonical title
*
* @param $parser Parser: parent parser
- * @param $text String: desired title text
+ * @param string $text desired title text
* @return String
*/
static function displaytitle( $parser, $text = '' ) {
@@ -386,20 +388,23 @@ class CoreParserFunctions {
return '';
}
- static function isRaw( $param ) {
- static $mwRaw;
- if ( !$mwRaw ) {
- $mwRaw =& MagicWord::get( 'rawsuffix' );
- }
- if ( is_null( $param ) ) {
+ /**
+ * Matches the given value against the value of given magic word
+ *
+ * @param string $magicword magic word key
+ * @param mixed $value value to match
+ * @return boolean true on successful match
+ */
+ static private function matchAgainstMagicword( $magicword, $value ) {
+ if ( strval( $value ) === '' ) {
return false;
- } else {
- return $mwRaw->match( $param );
}
+ $mwObject = MagicWord::get( $magicword );
+ return $mwObject->match( $value );
}
static function formatRaw( $num, $raw ) {
- if( self::isRaw( $raw ) ) {
+ if( self::matchAgainstMagicword( 'rawsuffix', $raw ) ) {
return $num;
} else {
global $wgContLang;
@@ -422,7 +427,7 @@ class CoreParserFunctions {
return self::formatRaw( SiteStats::images(), $raw );
}
static function numberofadmins( $parser, $raw = null ) {
- return self::formatRaw( SiteStats::numberingroup('sysop'), $raw );
+ return self::formatRaw( SiteStats::numberingroup( 'sysop' ), $raw );
}
static function numberofedits( $parser, $raw = null ) {
return self::formatRaw( SiteStats::edits(), $raw );
@@ -437,7 +442,6 @@ class CoreParserFunctions {
return self::formatRaw( SiteStats::numberingroup( strtolower( $name ) ), $raw );
}
-
/**
* Given a title, return the namespace name that would be given by the
* corresponding magic word
@@ -585,7 +589,7 @@ class CoreParserFunctions {
static $cache = array();
// split the given option to its variable
- if( self::isRaw( $arg1 ) ) {
+ if( self::matchAgainstMagicword( 'rawsuffix', $arg1 ) ) {
//{{pagesincategory:|raw[|type]}}
$raw = $arg1;
$type = $magicWords->matchStartToEnd( $arg2 );
@@ -641,7 +645,7 @@ class CoreParserFunctions {
* @todo Document parameters
*
* @param $parser Parser
- * @param $page String TODO DOCUMENT (Default: empty string)
+ * @param string $page TODO DOCUMENT (Default: empty string)
* @param $raw TODO DOCUMENT (Default: null)
* @return string
*/
@@ -662,21 +666,31 @@ class CoreParserFunctions {
$length = $cache[$page];
} elseif( $parser->incrementExpensiveFunctionCount() ) {
$rev = Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
- $id = $rev ? $rev->getPage() : 0;
+ $pageID = $rev ? $rev->getPage() : 0;
+ $revID = $rev ? $rev->getId() : 0;
$length = $cache[$page] = $rev ? $rev->getSize() : 0;
// Register dependency in templatelinks
- $parser->mOutput->addTemplate( $title, $id, $rev ? $rev->getId() : 0 );
+ $parser->mOutput->addTemplate( $title, $pageID, $revID );
}
return self::formatRaw( $length, $raw );
}
/**
- * Returns the requested protection level for the current page
+ * Returns the requested protection level for the current page
+ *
+ * @param Parser $parser
+ * @param string $type
+ * @param string $title
+ *
* @return string
*/
- static function protectionlevel( $parser, $type = '' ) {
- $restrictions = $parser->mTitle->getRestrictions( strtolower( $type ) );
+ static function protectionlevel( $parser, $type = '', $title = '' ) {
+ $titleObject = Title::newFromText( $title );
+ if ( !( $titleObject instanceof Title ) ) {
+ $titleObject = $parser->mTitle;
+ }
+ $restrictions = $titleObject->getRestrictions( strtolower( $type ) );
# Title::getRestrictions returns an array, its possible it may have
# multiple values in the future
return implode( $restrictions, ',' );
@@ -685,8 +699,8 @@ class CoreParserFunctions {
/**
* Gives language names.
* @param $parser Parser
- * @param $code String Language code (of which to get name)
- * @param $inLanguage String Language code (in which to get name)
+ * @param string $code Language code (of which to get name)
+ * @param string $inLanguage Language code (in which to get name)
* @return String
*/
static function language( $parser, $code = '', $inLanguage = '' ) {
@@ -739,7 +753,7 @@ class CoreParserFunctions {
*/
static function anchorencode( $parser, $text ) {
$text = $parser->killMarkers( $text );
- return substr( $parser->guessSectionNameFromWikiText( $text ), 1);
+ return (string)substr( $parser->guessSectionNameFromWikiText( $text ), 1 );
}
static function special( $parser, $text ) {
@@ -758,8 +772,8 @@ class CoreParserFunctions {
/**
* @param $parser Parser
- * @param $text String The sortkey to use
- * @param $uarg String Either "noreplace" or "noerror" (in en)
+ * @param string $text The sortkey to use
+ * @param string $uarg Either "noreplace" or "noerror" (in en)
* both suppress errors, and noreplace does nothing if
* a default sortkey already exists.
* @return string
@@ -790,7 +804,7 @@ class CoreParserFunctions {
// Usage {{filepath|300}}, {{filepath|nowiki}}, {{filepath|nowiki|300}} or {{filepath|300|nowiki}}
// or {{filepath|300px}}, {{filepath|200x300px}}, {{filepath|nowiki|200x300px}}, {{filepath|200x300px|nowiki}}
- public static function filepath( $parser, $name='', $argA='', $argB='' ) {
+ public static function filepath( $parser, $name = '', $argA = '', $argB = '' ) {
$file = wfFindFile( $name );
if( $argA == 'nowiki' ) {
diff --git a/includes/parser/CoreTagHooks.php b/includes/parser/CoreTagHooks.php
index 296be66f..65051839 100644
--- a/includes/parser/CoreTagHooks.php
+++ b/includes/parser/CoreTagHooks.php
@@ -72,6 +72,7 @@ class CoreTagHooks {
* @param $content string
* @param $attributes array
* @param $parser Parser
+ * @throws MWException
* @return array
*/
static function html( $content, $attributes, $parser ) {
diff --git a/includes/parser/DateFormatter.php b/includes/parser/DateFormatter.php
index 2917b4a7..a2da3074 100644
--- a/includes/parser/DateFormatter.php
+++ b/includes/parser/DateFormatter.php
@@ -22,7 +22,7 @@
*/
/**
- * Date formatter, recognises dates in plain text and formats them accoding to user preferences.
+ * Date formatter, recognises dates in plain text and formats them according to user preferences.
* @todo preferences, OutputPage
* @ingroup Parser
*/
@@ -55,7 +55,7 @@ class DateFormatter {
$this->lang = $lang;
$this->monthNames = $this->getMonthRegex();
- for ( $i=1; $i<=12; $i++ ) {
+ for ( $i = 1; $i <= 12; $i++ ) {
$this->xMonths[$this->lang->lc( $this->lang->getMonthName( $i ) )] = $i;
$this->xMonths[$this->lang->lc( $this->lang->getMonthAbbreviation( $i ) )] = $i;
}
@@ -102,11 +102,11 @@ class DateFormatter {
# Rules
# pref source target
- $this->rules[self::DMY][self::MD] = self::DM;
- $this->rules[self::ALL][self::MD] = self::MD;
- $this->rules[self::MDY][self::DM] = self::MD;
- $this->rules[self::ALL][self::DM] = self::DM;
- $this->rules[self::NONE][self::ISO2] = self::ISO1;
+ $this->rules[self::DMY][self::MD] = self::DM;
+ $this->rules[self::ALL][self::MD] = self::MD;
+ $this->rules[self::MDY][self::DM] = self::MD;
+ $this->rules[self::ALL][self::DM] = self::DM;
+ $this->rules[self::NONE][self::ISO2] = self::ISO1;
$this->preferences = array(
'default' => self::NONE,
@@ -140,12 +140,12 @@ class DateFormatter {
}
/**
- * @param $preference String: User preference
- * @param $text String: Text to reformat
- * @param $options Array: can contain 'linked' and/or 'match-whole'
+ * @param string $preference User preference
+ * @param string $text Text to reformat
+ * @param array $options can contain 'linked' and/or 'match-whole'
* @return mixed|String
*/
- function reformat( $preference, $text, $options = array('linked') ) {
+ function reformat( $preference, $text, $options = array( 'linked' ) ) {
$linked = in_array( 'linked', $options );
$match_whole = in_array( 'match-whole', $options );
@@ -154,7 +154,7 @@ class DateFormatter {
} else {
$preference = self::NONE;
}
- for ( $i=1; $i<=self::LAST; $i++ ) {
+ for ( $i = 1; $i <= self::LAST; $i++ ) {
$this->mSource = $i;
if ( isset ( $this->rules[$preference][$i] ) ) {
# Specific rules
@@ -172,21 +172,21 @@ class DateFormatter {
$regex = $this->regexes[$i];
// Horrible hack
- if (!$linked) {
+ if ( !$linked ) {
$regex = str_replace( array( '\[\[', '\]\]' ), '', $regex );
}
- if ($match_whole) {
+ if ( $match_whole ) {
// Let's hope this works
$regex = preg_replace( '!^/!', '/^', $regex );
$regex = str_replace( $this->regexTrail,
- '$'.$this->regexTrail, $regex );
+ '$' . $this->regexTrail, $regex );
}
// Another horrible hack
$this->mLinked = $linked;
$text = preg_replace_callback( $regex, array( &$this, 'replace' ), $text );
- unset($this->mLinked);
+ unset( $this->mLinked );
}
return $text;
}
@@ -200,10 +200,10 @@ class DateFormatter {
$linked = true;
if ( isset( $this->mLinked ) )
$linked = $this->mLinked;
-
+
$bits = array();
$key = $this->keys[$this->mSource];
- for ( $p=0; $p < strlen($key); $p++ ) {
+ for ( $p = 0; $p < strlen( $key ); $p++ ) {
if ( $key[$p] != ' ' ) {
$bits[$key[$p]] = $matches[$p+1];
}
@@ -219,8 +219,8 @@ class DateFormatter {
*/
function formatDate( $bits, $link = true ) {
$format = $this->targets[$this->mTarget];
-
- if (!$link) {
+
+ if ( !$link ) {
// strip piped links
$format = preg_replace( '/\[\[[^|]+\|([^\]]+)\]\]/', '$1', $format );
// strip remaining links
@@ -246,11 +246,11 @@ class DateFormatter {
}
}
- if ( !isset($bits['d']) ) {
+ if ( !isset( $bits['d'] ) ) {
$bits['d'] = sprintf( '%02d', $bits['j'] );
}
- for ( $p=0; $p < strlen( $format ); $p++ ) {
+ for ( $p = 0; $p < strlen( $format ); $p++ ) {
$char = $format[$p];
switch ( $char ) {
case 'd': # ISO day of month
@@ -263,7 +263,7 @@ class DateFormatter {
$text .= $bits['y'];
break;
case 'j': # ordinary day of month
- if ( !isset($bits['j']) ) {
+ if ( !isset( $bits['j'] ) ) {
$text .= intval( $bits['d'] );
} else {
$text .= $bits['j'];
@@ -271,7 +271,7 @@ class DateFormatter {
break;
case 'F': # long month
if ( !isset( $bits['F'] ) ) {
- $m = intval($bits['m']);
+ $m = intval( $bits['m'] );
if ( $m > 12 || $m < 1 ) {
$fail = true;
} else {
@@ -293,7 +293,7 @@ class DateFormatter {
}
$isoBits = array();
- if ( isset($bits['y']) )
+ if ( isset( $bits['y'] ) )
$isoBits[] = $bits['y'];
$isoBits[] = $bits['m'];
$isoBits[] = $bits['d'];
@@ -321,7 +321,7 @@ class DateFormatter {
/**
* Makes an ISO month, e.g. 02, from a month name
- * @param $monthName String: month name
+ * @param string $monthName month name
* @return string ISO month name
*/
function makeIsoMonth( $monthName ) {
@@ -331,7 +331,7 @@ class DateFormatter {
/**
* @todo document
- * @param $year String: Year name
+ * @param string $year Year name
* @return string ISO year name
*/
function makeIsoYear( $year ) {
diff --git a/includes/parser/LinkHolderArray.php b/includes/parser/LinkHolderArray.php
index d9356b48..49b2d333 100644
--- a/includes/parser/LinkHolderArray.php
+++ b/includes/parser/LinkHolderArray.php
@@ -43,9 +43,9 @@ class LinkHolderArray {
}
}
- /**
+ /**
* Don't serialize the parent object, it is big, and not needed when it is
- * a parameter to mergeForeign(), which is the only application of
+ * a parameter to mergeForeign(), which is the only application of
* serializing at present.
*
* Compact the titles, only serialize the text form.
@@ -103,15 +103,15 @@ class LinkHolderArray {
}
/**
- * Merge a LinkHolderArray from another parser instance into this one. The
- * keys will not be preserved. Any text which went with the old
- * LinkHolderArray and needs to work with the new one should be passed in
+ * Merge a LinkHolderArray from another parser instance into this one. The
+ * keys will not be preserved. Any text which went with the old
+ * LinkHolderArray and needs to work with the new one should be passed in
* the $texts array. The strings in this array will have their link holders
* converted for use in the destination link holder. The resulting array of
* strings will be returned.
*
* @param $other LinkHolderArray
- * @param $texts Array of strings
+ * @param array $texts of strings
* @return Array
*/
function mergeForeign( $other, $texts ) {
@@ -126,7 +126,7 @@ class LinkHolderArray {
$maxId = $newKey > $maxId ? $newKey : $maxId;
}
}
- $texts = preg_replace_callback( '/(<!--LINK \d+:)(\d+)(-->)/',
+ $texts = preg_replace_callback( '/(<!--LINK \d+:)(\d+)(-->)/',
array( $this, 'mergeForeignCallback' ), $texts );
# Renumber interwiki links
@@ -135,7 +135,7 @@ class LinkHolderArray {
$this->interwikis[$newKey] = $entry;
$maxId = $newKey > $maxId ? $newKey : $maxId;
}
- $texts = preg_replace_callback( '/(<!--IWLINK )(\d+)(-->)/',
+ $texts = preg_replace_callback( '/(<!--IWLINK )(\d+)(-->)/',
array( $this, 'mergeForeignCallback' ), $texts );
# Set the parent link ID to be beyond the highest used ID
@@ -159,8 +159,8 @@ class LinkHolderArray {
# Internal links
$pos = 0;
while ( $pos < strlen( $text ) ) {
- if ( !preg_match( '/<!--LINK (\d+):(\d+)-->/',
- $text, $m, PREG_OFFSET_CAPTURE, $pos ) )
+ if ( !preg_match( '/<!--LINK (\d+):(\d+)-->/',
+ $text, $m, PREG_OFFSET_CAPTURE, $pos ) )
{
break;
}
@@ -210,14 +210,14 @@ class LinkHolderArray {
*
* @param $nt Title
* @param $text String
- * @param $query Array [optional]
- * @param $trail String [optional]
- * @param $prefix String [optional]
+ * @param array $query [optional]
+ * @param string $trail [optional]
+ * @param string $prefix [optional]
* @return string
*/
- function makeHolder( $nt, $text = '', $query = array(), $trail = '', $prefix = '' ) {
+ function makeHolder( $nt, $text = '', $query = array(), $trail = '', $prefix = '' ) {
wfProfileIn( __METHOD__ );
- if ( ! is_object($nt) ) {
+ if ( !is_object( $nt ) ) {
# Fail gracefully
$retVal = "<!-- ERROR -->{$prefix}{$text}{$trail}";
} else {
@@ -226,7 +226,7 @@ class LinkHolderArray {
$entry = array(
'title' => $nt,
- 'text' => $prefix.$text.$inside,
+ 'text' => $prefix . $text . $inside,
'pdbk' => $nt->getPrefixedDBkey(),
);
if ( $query !== array() ) {
@@ -254,12 +254,12 @@ class LinkHolderArray {
* @todo FIXME: Update documentation. makeLinkObj() is deprecated.
* Replace <!--LINK--> link placeholders with actual links, in the buffer
* Placeholders created in Skin::makeLinkObj()
- * Returns an array of link CSS classes, indexed by PDBK.
+ * @return array of link CSS classes, indexed by PDBK.
*/
function replace( &$text ) {
wfProfileIn( __METHOD__ );
- $colours = $this->replaceInternal( $text );
+ $colours = $this->replaceInternal( $text ); // FIXME: replaceInternal doesn't return a value
$this->replaceInterwiki( $text );
wfProfileOut( __METHOD__ );
@@ -281,7 +281,7 @@ class LinkHolderArray {
$linkCache = LinkCache::singleton();
$output = $this->parent->getOutput();
- wfProfileIn( __METHOD__.'-check' );
+ wfProfileIn( __METHOD__ . '-check' );
$dbr = wfGetDB( DB_SLAVE );
$threshold = $this->parent->getOptions()->getStubThreshold();
@@ -322,7 +322,7 @@ class LinkHolderArray {
}
if ( $queries ) {
$where = array();
- foreach( $queries as $ns => $pages ){
+ foreach( $queries as $ns => $pages ) {
$where[] = $dbr->makeList(
array(
'page_namespace' => $ns,
@@ -355,19 +355,19 @@ class LinkHolderArray {
}
unset( $res );
}
- if ( count($linkcolour_ids) ) {
+ if ( count( $linkcolour_ids ) ) {
//pass an array of page_ids to an extension
wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
}
- wfProfileOut( __METHOD__.'-check' );
+ wfProfileOut( __METHOD__ . '-check' );
# Do a second query for different language variants of links and categories
- if($wgContLang->hasVariants()) {
+ if( $wgContLang->hasVariants() ) {
$this->doVariants( $colours );
}
# Construct search and replace arrays
- wfProfileIn( __METHOD__.'-construct' );
+ wfProfileIn( __METHOD__ . '-construct' );
$replacePairs = array();
foreach ( $this->internals as $ns => $entries ) {
foreach ( $entries as $index => $entry ) {
@@ -399,16 +399,16 @@ class LinkHolderArray {
}
}
$replacer = new HashtableReplacer( $replacePairs, 1 );
- wfProfileOut( __METHOD__.'-construct' );
+ wfProfileOut( __METHOD__ . '-construct' );
# Do the thing
- wfProfileIn( __METHOD__.'-replace' );
+ wfProfileIn( __METHOD__ . '-replace' );
$text = preg_replace_callback(
'/(<!--LINK .*?-->)/',
$replacer->cb(),
$text);
- wfProfileOut( __METHOD__.'-replace' );
+ wfProfileOut( __METHOD__ . '-replace' );
wfProfileOut( __METHOD__ );
}
@@ -497,20 +497,23 @@ class LinkHolderArray {
// process categories, check if a category exists in some variant
$categoryMap = array(); // maps $category_variant => $category (dbkeys)
$varCategories = array(); // category replacements oldDBkey => newDBkey
- foreach( $output->getCategoryLinks() as $category ){
+ foreach ( $output->getCategoryLinks() as $category ) {
+ $categoryTitle = Title::makeTitleSafe( NS_CATEGORY, $category );
+ $linkBatch->addObj( $categoryTitle );
$variants = $wgContLang->autoConvertToAllVariants( $category );
- foreach($variants as $variant){
- if($variant != $category){
- $variantTitle = Title::newFromDBkey( Title::makeName(NS_CATEGORY,$variant) );
- if(is_null($variantTitle)) continue;
+ foreach ( $variants as $variant ) {
+ if ( $variant !== $category ) {
+ $variantTitle = Title::makeTitleSafe( NS_CATEGORY, $variant );
+ if ( is_null( $variantTitle ) ) {
+ continue;
+ }
$linkBatch->addObj( $variantTitle );
- $categoryMap[$variant] = $category;
+ $categoryMap[$variant] = array( $category, $categoryTitle );
}
}
}
-
- if(!$linkBatch->isEmpty()){
+ if( !$linkBatch->isEmpty() ) {
// construct query
$dbr = wfGetDB( DB_SLAVE );
$varRes = $dbr->select( 'page',
@@ -556,25 +559,28 @@ class LinkHolderArray {
}
// check if the object is a variant of a category
- if(isset($categoryMap[$vardbk])){
- $oldkey = $categoryMap[$vardbk];
- if($oldkey != $vardbk)
- $varCategories[$oldkey]=$vardbk;
+ if ( isset( $categoryMap[$vardbk] ) ) {
+ list( $oldkey, $oldtitle ) = $categoryMap[$vardbk];
+ if ( !isset( $varCategories[$oldkey] ) && !$oldtitle->exists() ) {
+ $varCategories[$oldkey] = $vardbk;
+ }
}
}
wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
// rebuild the categories in original order (if there are replacements)
- if(count($varCategories)>0){
+ if( count( $varCategories ) > 0 ) {
$newCats = array();
$originalCats = $output->getCategories();
- foreach($originalCats as $cat => $sortkey){
+ foreach( $originalCats as $cat => $sortkey ) {
// make the replacement
- if( array_key_exists($cat,$varCategories) )
+ if( array_key_exists( $cat, $varCategories ) ) {
$newCats[$varCategories[$cat]] = $sortkey;
- else $newCats[$cat] = $sortkey;
+ } else {
+ $newCats[$cat] = $sortkey;
+ }
}
- $output->setCategoryLinks($newCats);
+ $output->setCategoryLinks( $newCats );
}
}
}
@@ -607,7 +613,7 @@ class LinkHolderArray {
*/
function replaceTextCallback( $matches ) {
$type = $matches[1];
- $key = $matches[2];
+ $key = $matches[2];
if( $type == 'LINK' ) {
list( $ns, $index ) = explode( ':', $key, 2 );
if( isset( $this->internals[$ns][$index]['text'] ) ) {
diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php
index 10765de2..5ef0bc71 100644
--- a/includes/parser/Parser.php
+++ b/includes/parser/Parser.php
@@ -62,7 +62,6 @@
* $wgAllowSpecialInclusion
* $wgInterwikiMagic
* $wgMaxArticleSize
- * $wgUseDynamicDates
*
* @ingroup Parser
*/
@@ -123,8 +122,8 @@ class Parser {
var $mFunctionHooks = array();
var $mFunctionSynonyms = array( 0 => array(), 1 => array() );
var $mFunctionTagHooks = array();
- var $mStripList = array();
- var $mDefaultStripList = array();
+ var $mStripList = array();
+ var $mDefaultStripList = array();
var $mVarCache = array();
var $mImageParams = array();
var $mImageParamsMagicArray = array();
@@ -201,6 +200,13 @@ class Parser {
var $mUniqPrefix;
/**
+ * @var Array with the language name of each language link (i.e. the
+ * interwiki prefix) in the key, value arbitrary. Used to avoid sending
+ * duplicate language links to the ParserOutput.
+ */
+ var $mLangLinkLanguages;
+
+ /**
* Constructor
*
* @param $conf array
@@ -208,8 +214,8 @@ class Parser {
public function __construct( $conf = array() ) {
$this->mConf = $conf;
$this->mUrlProtocols = wfUrlProtocols();
- $this->mExtLinkBracketedRegex = '/\[(((?i)' . $this->mUrlProtocols . ')'.
- self::EXT_LINK_URL_CLASS.'+)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F]*?)\]/Su';
+ $this->mExtLinkBracketedRegex = '/\[(((?i)' . $this->mUrlProtocols . ')' .
+ self::EXT_LINK_URL_CLASS . '+)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F]*?)\]/Su';
if ( isset( $conf['preprocessorClass'] ) ) {
$this->mPreprocessorClass = $conf['preprocessorClass'];
} elseif ( defined( 'MW_COMPILED' ) ) {
@@ -240,6 +246,13 @@ class Parser {
}
/**
+ * Allow extensions to clean up when the parser is cloned
+ */
+ function __clone() {
+ wfRunHooks( 'ParserCloned', array( $this ) );
+ }
+
+ /**
* Do various kinds of initialisation on the first call of the parser
*/
function firstCallInit() {
@@ -282,6 +295,7 @@ class Parser {
$this->mRevisionId = $this->mRevisionUser = null;
$this->mVarCache = array();
$this->mUser = null;
+ $this->mLangLinkLanguages = array();
/**
* Prefix for temporary replacement strings for the multipass parser.
@@ -291,12 +305,11 @@ class Parser {
* string constructs.
*
* Must not consist of all title characters, or else it will change
- * the behaviour of <nowiki> in a link.
+ * the behavior of <nowiki> in a link.
*/
$this->mUniqPrefix = "\x7fUNIQ" . self::getRandomString();
$this->mStripState = new StripState( $this->mUniqPrefix );
-
# Clear these on every parse, bug 4549
$this->mTplExpandCache = $this->mTplRedirCache = $this->mTplDomCache = array();
@@ -327,12 +340,12 @@ class Parser {
* Convert wikitext to HTML
* Do not call this function recursively.
*
- * @param $text String: text we want to parse
+ * @param string $text text we want to parse
* @param $title Title object
* @param $options ParserOptions
* @param $linestart boolean
* @param $clearState boolean
- * @param $revid Int: number to pass in {{REVISIONID}}
+ * @param int $revid number to pass in {{REVISIONID}}
* @return ParserOutput a ParserOutput
*/
public function parse( $text, Title $title, ParserOptions $options, $linestart = true, $clearState = true, $revid = null ) {
@@ -342,7 +355,7 @@ class Parser {
*/
global $wgUseTidy, $wgAlwaysUseTidy;
- $fname = __METHOD__.'-' . wfGetCaller();
+ $fname = __METHOD__ . '-' . wfGetCaller();
wfProfileIn( __METHOD__ );
wfProfileIn( $fname );
@@ -397,9 +410,7 @@ class Parser {
if ( !( $options->getDisableContentConversion()
|| isset( $this->mDoubleUnderscores['nocontentconvert'] ) ) )
{
- # Run convert unconditionally in 1.18-compatible mode
- global $wgBug34832TransitionalRollback;
- if ( $wgBug34832TransitionalRollback || !$this->mOptions->getInterfaceMessage() ) {
+ if ( !$this->mOptions->getInterfaceMessage() ) {
# The position of the convert() call should not be changed. it
# assumes that the links are all replaced and the only thing left
# is the <nowiki> mark.
@@ -486,8 +497,8 @@ class Parser {
"Preprocessor generated node count: " .
"{$this->mGeneratedPPNodeCount}/{$this->mOptions->getMaxGeneratedPPNodeCount()}\n" .
"Post-expand include size: {$this->mIncludeSizes['post-expand']}/$max bytes\n" .
- "Template argument size: {$this->mIncludeSizes['arg']}/$max bytes\n".
- "Highest expansion depth: {$this->mHighestExpansionDepth}/{$this->mOptions->getMaxPPExpandDepth()}\n".
+ "Template argument size: {$this->mIncludeSizes['arg']}/$max bytes\n" .
+ "Highest expansion depth: {$this->mHighestExpansionDepth}/{$this->mOptions->getMaxPPExpandDepth()}\n" .
$PFreport;
wfRunHooks( 'ParserLimitReport', array( $this, &$limitReport ) );
@@ -496,6 +507,11 @@ class Parser {
$limitReport = str_replace( array( '-', '&' ), array( '‐', '&amp;' ), $limitReport );
$text .= "\n<!-- \n$limitReport-->\n";
+
+ if ( $this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10 ) {
+ wfDebugLog( 'generated-pp-node-count', $this->mGeneratedPPNodeCount . ' ' .
+ $this->mTitle->getPrefixedDBkey() );
+ }
}
$this->mOutput->setText( $text );
@@ -515,7 +531,7 @@ class Parser {
*
* If $frame is not provided, then template variables (e.g., {{{1}}}) within $text are not expanded
*
- * @param $text String: text extension wants to have parsed
+ * @param string $text text extension wants to have parsed
* @param $frame PPFrame: The frame to use for expanding any template variables
*
* @return string
@@ -534,7 +550,7 @@ class Parser {
* Also removes comments.
* @return mixed|string
*/
- function preprocess( $text, Title $title, ParserOptions $options, $revid = null ) {
+ function preprocess( $text, Title $title = null, ParserOptions $options, $revid = null ) {
wfProfileIn( __METHOD__ );
$this->startParse( $title, $options, self::OT_PREPROCESS, true );
if ( $revid !== null ) {
@@ -552,7 +568,7 @@ class Parser {
* Recursive parser entry point that can be called from an extension tag
* hook.
*
- * @param $text String: text to be expanded
+ * @param string $text text to be expanded
* @param $frame PPFrame: The frame to use for expanding any template variables
* @return String
* @since 1.19
@@ -593,7 +609,7 @@ class Parser {
*
* @return string
*/
- static public function getRandomString() {
+ public static function getRandomString() {
return wfRandomString( 16 );
}
@@ -682,7 +698,7 @@ class Parser {
/**
* Accessor/mutator for the output type
*
- * @param $x int|null New value or null to just get the current one
+ * @param int|null $x New value or null to just get the current one
* @return Integer
*/
function OutputType( $x = null ) {
@@ -745,6 +761,7 @@ class Parser {
*
* @since 1.19
*
+ * @throws MWException
* @return Language|null
*/
public function getTargetLanguage() {
@@ -765,12 +782,7 @@ class Parser {
* Get the language object for language conversion
*/
function getConverterLanguage() {
- global $wgBug34832TransitionalRollback, $wgContLang;
- if ( $wgBug34832TransitionalRollback ) {
- return $wgContLang;
- } else {
- return $this->getTargetLanguage();
- }
+ return $this->getTargetLanguage();
}
/**
@@ -813,9 +825,9 @@ class Parser {
* '<element param="x">tag content</element>' ) )
* @endcode
*
- * @param $elements array list of element names. Comments are always extracted.
- * @param $text string Source text string.
- * @param $matches array Out parameter, Array: extracted tags
+ * @param array $elements list of element names. Comments are always extracted.
+ * @param string $text Source text string.
+ * @param array $matches Out parameter, Array: extracted tags
* @param $uniq_prefix string
* @return String: stripped text
*/
@@ -835,16 +847,16 @@ class Parser {
}
if ( count( $p ) > 5 ) {
# comment
- $element = $p[4];
+ $element = $p[4];
$attributes = '';
- $close = '';
- $inside = $p[5];
+ $close = '';
+ $inside = $p[5];
} else {
# tag
- $element = $p[1];
+ $element = $p[1];
$attributes = $p[2];
- $close = $p[3];
- $inside = $p[4];
+ $close = $p[3];
+ $inside = $p[4];
}
$marker = "$uniq_prefix-$element-" . sprintf( '%08X', $n++ ) . self::MARKER_SUFFIX;
@@ -928,33 +940,33 @@ class Parser {
$line = trim( $outLine );
if ( $line === '' ) { # empty line, go to next line
- $out .= $outLine."\n";
+ $out .= $outLine . "\n";
continue;
}
$first_character = $line[0];
$matches = array();
- if ( preg_match( '/^(:*)\{\|(.*)$/', $line , $matches ) ) {
+ if ( preg_match( '/^(:*)\{\|(.*)$/', $line, $matches ) ) {
# First check if we are starting a new table
$indent_level = strlen( $matches[1] );
$attributes = $this->mStripState->unstripBoth( $matches[2] );
- $attributes = Sanitizer::fixTagAttributes( $attributes , 'table' );
-
- $outLine = str_repeat( '<dl><dd>' , $indent_level ) . "<table{$attributes}>";
- array_push( $td_history , false );
- array_push( $last_tag_history , '' );
- array_push( $tr_history , false );
- array_push( $tr_attributes , '' );
- array_push( $has_opened_tr , false );
+ $attributes = Sanitizer::fixTagAttributes( $attributes, 'table' );
+
+ $outLine = str_repeat( '<dl><dd>', $indent_level ) . "<table{$attributes}>";
+ array_push( $td_history, false );
+ array_push( $last_tag_history, '' );
+ array_push( $tr_history, false );
+ array_push( $tr_attributes, '' );
+ array_push( $has_opened_tr, false );
} elseif ( count( $td_history ) == 0 ) {
# Don't do any of the following
- $out .= $outLine."\n";
+ $out .= $outLine . "\n";
continue;
- } elseif ( substr( $line , 0 , 2 ) === '|}' ) {
+ } elseif ( substr( $line, 0, 2 ) === '|}' ) {
# We are ending a table
- $line = '</table>' . substr( $line , 2 );
+ $line = '</table>' . substr( $line, 2 );
$last_tag = array_pop( $last_tag_history );
if ( !array_pop( $has_opened_tr ) ) {
@@ -969,8 +981,8 @@ class Parser {
$line = "</{$last_tag}>{$line}";
}
array_pop( $tr_attributes );
- $outLine = $line . str_repeat( '</dd></dl>' , $indent_level );
- } elseif ( substr( $line , 0 , 2 ) === '|-' ) {
+ $outLine = $line . str_repeat( '</dd></dl>', $indent_level );
+ } elseif ( substr( $line, 0, 2 ) === '|-' ) {
# Now we have a table row
$line = preg_replace( '#^\|-+#', '', $line );
@@ -983,7 +995,7 @@ class Parser {
$line = '';
$last_tag = array_pop( $last_tag_history );
array_pop( $has_opened_tr );
- array_push( $has_opened_tr , true );
+ array_push( $has_opened_tr, true );
if ( array_pop( $tr_history ) ) {
$line = '</tr>';
@@ -994,27 +1006,27 @@ class Parser {
}
$outLine = $line;
- array_push( $tr_history , false );
- array_push( $td_history , false );
- array_push( $last_tag_history , '' );
- } elseif ( $first_character === '|' || $first_character === '!' || substr( $line , 0 , 2 ) === '|+' ) {
+ array_push( $tr_history, false );
+ array_push( $td_history, false );
+ array_push( $last_tag_history, '' );
+ } elseif ( $first_character === '|' || $first_character === '!' || substr( $line, 0, 2 ) === '|+' ) {
# This might be cell elements, td, th or captions
- if ( substr( $line , 0 , 2 ) === '|+' ) {
+ if ( substr( $line, 0, 2 ) === '|+' ) {
$first_character = '+';
- $line = substr( $line , 1 );
+ $line = substr( $line, 1 );
}
- $line = substr( $line , 1 );
+ $line = substr( $line, 1 );
if ( $first_character === '!' ) {
- $line = str_replace( '!!' , '||' , $line );
+ $line = str_replace( '!!', '||', $line );
}
# Split up multiple cells on the same line.
# FIXME : This can result in improper nesting of tags processed
# by earlier parser steps, but should avoid splitting up eg
# attribute values containing literal "||".
- $cells = StringUtils::explodeMarkup( '||' , $line );
+ $cells = StringUtils::explodeMarkup( '||', $line );
$outLine = '';
@@ -1026,10 +1038,10 @@ class Parser {
if ( !array_pop( $tr_history ) ) {
$previous = "<tr{$tr_after}>\n";
}
- array_push( $tr_history , true );
- array_push( $tr_attributes , '' );
+ array_push( $tr_history, true );
+ array_push( $tr_attributes, '' );
array_pop( $has_opened_tr );
- array_push( $has_opened_tr , true );
+ array_push( $has_opened_tr, true );
}
$last_tag = array_pop( $last_tag_history );
@@ -1048,10 +1060,10 @@ class Parser {
$last_tag = '';
}
- array_push( $last_tag_history , $last_tag );
+ array_push( $last_tag_history, $last_tag );
# A cell could contain both parameters and data
- $cell_data = explode( '|' , $cell , 2 );
+ $cell_data = explode( '|', $cell, 2 );
# Bug 553: Note that a '|' inside an invalid link should not
# be mistaken as delimiting cell parameters
@@ -1061,12 +1073,12 @@ class Parser {
$cell = "{$previous}<{$last_tag}>{$cell_data[0]}";
} else {
$attributes = $this->mStripState->unstripBoth( $cell_data[0] );
- $attributes = Sanitizer::fixTagAttributes( $attributes , $last_tag );
+ $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
$cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}";
}
$outLine .= $cell;
- array_push( $td_history , true );
+ array_push( $td_history, true );
}
}
$out .= $outLine . "\n";
@@ -1081,7 +1093,7 @@ class Parser {
$out .= "</tr>\n";
}
if ( !array_pop( $has_opened_tr ) ) {
- $out .= "<tr><td></td></tr>\n" ;
+ $out .= "<tr><td></td></tr>\n";
}
$out .= "</table>\n";
@@ -1122,7 +1134,7 @@ class Parser {
# Hook to suspend the parser in this state
if ( !wfRunHooks( 'ParserBeforeInternalParse', array( &$this, &$text, &$this->mStripState ) ) ) {
wfProfileOut( __METHOD__ );
- return $text ;
+ return $text;
}
# if $frame is provided, then use $frame for replacing any variables
@@ -1156,17 +1168,13 @@ class Parser {
$text = $this->doDoubleUnderscore( $text );
$text = $this->doHeadings( $text );
- if ( $this->mOptions->getUseDynamicDates() ) {
- $df = DateFormatter::getInstance();
- $text = $df->reformat( $this->mOptions->getDateFormat(), $text );
- }
$text = $this->replaceInternalLinks( $text );
$text = $this->doAllQuotes( $text );
$text = $this->replaceExternalLinks( $text );
# replaceInternalLinks may sometimes leave behind
# absolute URLs, which have to be masked to hide them from replaceExternalLinks
- $text = str_replace( $this->mUniqPrefix.'NOPARSE', '', $text );
+ $text = str_replace( $this->mUniqPrefix . 'NOPARSE', '', $text );
$text = $this->doMagicLinks( $text );
$text = $this->formatHeadings( $text, $origText, $isMain );
@@ -1234,7 +1242,7 @@ class Parser {
$CssClass = 'mw-magiclink-pmid';
$id = $m[4];
} else {
- throw new MWException( __METHOD__.': unrecognised match type "' .
+ throw new MWException( __METHOD__ . ': unrecognised match type "' .
substr( $m[0], 0, 20 ) . '"' );
}
$url = wfMessage( $urlmsg, $id )->inContentLanguage()->text();
@@ -1298,7 +1306,8 @@ class Parser {
if ( $text === false ) {
# Not an image, make a link
$text = Linker::makeExternalLink( $url,
- $this->getConverterLanguage()->markNoConversion($url), true, 'free',
+ $this->getConverterLanguage()->markNoConversion( $url, true ),
+ true, 'free',
$this->getExternalLinkAttribs( $url ) );
# Register it in the output object...
# Replace unnecessary URL escape codes with their equivalent characters
@@ -1309,7 +1318,6 @@ class Parser {
return $text . $trail;
}
-
/**
* Parse headers and return html
*
@@ -1323,8 +1331,7 @@ class Parser {
wfProfileIn( __METHOD__ );
for ( $i = 6; $i >= 1; --$i ) {
$h = str_repeat( '=', $i );
- $text = preg_replace( "/^$h(.+)$h\\s*$/m",
- "<h$i>\\1</h$i>", $text );
+ $text = preg_replace( "/^$h(.+)$h\\s*$/m", "<h$i>\\1</h$i>", $text );
}
wfProfileOut( __METHOD__ );
return $text;
@@ -1345,7 +1352,7 @@ class Parser {
foreach ( $lines as $line ) {
$outtext .= $this->doQuotes( $line ) . "\n";
}
- $outtext = substr( $outtext, 0,-1 );
+ $outtext = substr( $outtext, 0, -1 );
wfProfileOut( __METHOD__ );
return $outtext;
}
@@ -1410,7 +1417,7 @@ class Parser {
if ( $firstspace == -1 ) {
$firstspace = $i;
}
- } elseif ( $x2 === ' ') {
+ } elseif ( $x2 === ' ' ) {
if ( $firstsingleletterword == -1 ) {
$firstsingleletterword = $i;
}
@@ -1461,7 +1468,7 @@ class Parser {
} elseif ( $state === 'ib' ) {
$output .= '</b></i><b>'; $state = 'b';
} elseif ( $state === 'both' ) {
- $output .= '<b><i>'.$buffer.'</i>'; $state = 'b';
+ $output .= '<b><i>' . $buffer . '</i>'; $state = 'b';
} else { # $state can be 'b' or ''
$output .= '<i>'; $state .= 'i';
}
@@ -1473,7 +1480,7 @@ class Parser {
} elseif ( $state === 'ib' ) {
$output .= '</b>'; $state = 'i';
} elseif ( $state === 'both' ) {
- $output .= '<i><b>'.$buffer.'</b>'; $state = 'i';
+ $output .= '<i><b>' . $buffer . '</b>'; $state = 'i';
} else { # $state can be 'i' or ''
$output .= '<b>'; $state .= 'b';
}
@@ -1487,7 +1494,7 @@ class Parser {
} elseif ( $state === 'ib' ) {
$output .= '</b></i>'; $state = '';
} elseif ( $state === 'both' ) {
- $output .= '<i><b>'.$buffer.'</b></i>'; $state = '';
+ $output .= '<i><b>' . $buffer . '</b></i>'; $state = '';
} else { # ($state == '')
$buffer = ''; $state = 'both';
}
@@ -1507,7 +1514,7 @@ class Parser {
}
# There might be lonely ''''', so make sure we have a buffer
if ( $state === 'both' && $buffer ) {
- $output .= '<b><i>'.$buffer.'</i></b>';
+ $output .= '<b><i>' . $buffer . '</i></b>';
}
return $output;
}
@@ -1523,6 +1530,7 @@ class Parser {
*
* @param $text string
*
+ * @throws MWException
* @return string
*/
function replaceExternalLinks( $text ) {
@@ -1537,7 +1545,7 @@ class Parser {
$i = 0;
while ( $i<count( $bits ) ) {
$url = $bits[$i++];
- $protocol = $bits[$i++];
+ $i++; // protocol
$text = $bits[$i++];
$trail = $bits[$i++];
@@ -1595,26 +1603,39 @@ class Parser {
wfProfileOut( __METHOD__ );
return $s;
}
-
+ /**
+ * Get the rel attribute for a particular external link.
+ *
+ * @since 1.21
+ * @param string|bool $url optional URL, to extract the domain from for rel =>
+ * nofollow if appropriate
+ * @param $title Title optional Title, for wgNoFollowNsExceptions lookups
+ * @return string|null rel attribute for $url
+ */
+ public static function getExternalLinkRel( $url = false, $title = null ) {
+ global $wgNoFollowLinks, $wgNoFollowNsExceptions, $wgNoFollowDomainExceptions;
+ $ns = $title ? $title->getNamespace() : false;
+ if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions ) &&
+ !wfMatchesDomainList( $url, $wgNoFollowDomainExceptions ) )
+ {
+ return 'nofollow';
+ }
+ return null;
+ }
/**
* Get an associative array of additional HTML attributes appropriate for a
* particular external link. This currently may include rel => nofollow
* (depending on configuration, namespace, and the URL's domain) and/or a
* target attribute (depending on configuration).
*
- * @param $url String|bool optional URL, to extract the domain from for rel =>
+ * @param string|bool $url optional URL, to extract the domain from for rel =>
* nofollow if appropriate
* @return Array associative array of HTML attributes
*/
function getExternalLinkAttribs( $url = false ) {
$attribs = array();
- global $wgNoFollowLinks, $wgNoFollowNsExceptions, $wgNoFollowDomainExceptions;
- $ns = $this->mTitle->getNamespace();
- if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions ) &&
- !wfMatchesDomainList( $url, $wgNoFollowDomainExceptions ) )
- {
- $attribs['rel'] = 'nofollow';
- }
+ $attribs['rel'] = self::getExternalLinkRel( $url, $this->mTitle );
+
if ( $this->mOptions->getExternalLinkTarget() ) {
$attribs['target'] = $this->mOptions->getExternalLinkTarget();
}
@@ -1726,6 +1747,8 @@ class Parser {
/**
* Process [[ ]] wikilinks (RIL)
+ * @param $s
+ * @throws MWException
* @return LinkHolderArray
*
* @private
@@ -1733,8 +1756,8 @@ class Parser {
function replaceInternalLinks2( &$s ) {
wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__.'-setup' );
- static $tc = FALSE, $e1, $e1_img;
+ wfProfileIn( __METHOD__ . '-setup' );
+ static $tc = false, $e1, $e1_img;
# the % is needed to support urlencoded titles as well
if ( !$tc ) {
$tc = Title::legalChars() . '#%';
@@ -1763,9 +1786,9 @@ class Parser {
}
if ( is_null( $this->mTitle ) ) {
- wfProfileOut( __METHOD__.'-setup' );
+ wfProfileOut( __METHOD__ . '-setup' );
wfProfileOut( __METHOD__ );
- throw new MWException( __METHOD__.": \$this->mTitle is null\n" );
+ throw new MWException( __METHOD__ . ": \$this->mTitle is null\n" );
}
$nottalk = !$this->mTitle->isTalkPage();
@@ -1780,17 +1803,11 @@ class Parser {
$prefix = '';
}
- if ( $this->getConverterLanguage()->hasVariants() ) {
- $selflink = $this->getConverterLanguage()->autoConvertToAllVariants(
- $this->mTitle->getPrefixedText() );
- } else {
- $selflink = array( $this->mTitle->getPrefixedText() );
- }
$useSubpages = $this->areSubpagesAllowed();
- wfProfileOut( __METHOD__.'-setup' );
+ wfProfileOut( __METHOD__ . '-setup' );
# Loop for each link
- for ( ; $line !== false && $line !== null ; $a->next(), $line = $a->current() ) {
+ for ( ; $line !== false && $line !== null; $a->next(), $line = $a->current() ) {
# Check for excessive memory usage
if ( $holders->isBig() ) {
# Too big
@@ -1800,24 +1817,24 @@ class Parser {
}
if ( $useLinkPrefixExtension ) {
- wfProfileIn( __METHOD__.'-prefixhandling' );
+ wfProfileIn( __METHOD__ . '-prefixhandling' );
if ( preg_match( $e2, $s, $m ) ) {
$prefix = $m[2];
$s = $m[1];
} else {
- $prefix='';
+ $prefix = '';
}
# first link
if ( $first_prefix ) {
$prefix = $first_prefix;
$first_prefix = false;
}
- wfProfileOut( __METHOD__.'-prefixhandling' );
+ wfProfileOut( __METHOD__ . '-prefixhandling' );
}
$might_be_img = false;
- wfProfileIn( __METHOD__."-e1" );
+ wfProfileIn( __METHOD__ . "-e1" );
if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
$text = $m[2];
# If we get a ] at the beginning of $m[3] that means we have a link that's something like:
@@ -1839,7 +1856,7 @@ class Parser {
# fix up urlencoded title texts
if ( strpos( $m[1], '%' ) !== false ) {
# Should anchors '#' also be rejected?
- $m[1] = str_replace( array('<', '>'), array('&lt;', '&gt;'), rawurldecode( $m[1] ) );
+ $m[1] = str_replace( array( '<', '>' ), array( '&lt;', '&gt;' ), rawurldecode( $m[1] ) );
}
$trail = $m[3];
} elseif ( preg_match( $e1_img, $line, $m ) ) { # Invalid, but might be an image with a link in its caption
@@ -1850,19 +1867,19 @@ class Parser {
}
$trail = "";
} else { # Invalid form; output directly
- $s .= $prefix . '[[' . $line ;
- wfProfileOut( __METHOD__."-e1" );
+ $s .= $prefix . '[[' . $line;
+ wfProfileOut( __METHOD__ . "-e1" );
continue;
}
- wfProfileOut( __METHOD__."-e1" );
- wfProfileIn( __METHOD__."-misc" );
+ wfProfileOut( __METHOD__ . "-e1" );
+ wfProfileIn( __METHOD__ . "-misc" );
# Don't allow internal links to pages containing
# PROTO: where PROTO is a valid URL protocol; these
# should be external links.
if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $m[1] ) ) {
- $s .= $prefix . '[[' . $line ;
- wfProfileOut( __METHOD__."-misc" );
+ $s .= $prefix . '[[' . $line;
+ wfProfileOut( __METHOD__ . "-misc" );
continue;
}
@@ -1879,21 +1896,21 @@ class Parser {
$link = substr( $link, 1 );
}
- wfProfileOut( __METHOD__."-misc" );
- wfProfileIn( __METHOD__."-title" );
+ wfProfileOut( __METHOD__ . "-misc" );
+ wfProfileIn( __METHOD__ . "-title" );
$nt = Title::newFromText( $this->mStripState->unstripNoWiki( $link ) );
if ( $nt === null ) {
$s .= $prefix . '[[' . $line;
- wfProfileOut( __METHOD__."-title" );
+ wfProfileOut( __METHOD__ . "-title" );
continue;
}
$ns = $nt->getNamespace();
$iw = $nt->getInterWiki();
- wfProfileOut( __METHOD__."-title" );
+ wfProfileOut( __METHOD__ . "-title" );
if ( $might_be_img ) { # if this is actually an invalid link
- wfProfileIn( __METHOD__."-might_be_img" );
+ wfProfileIn( __METHOD__ . "-might_be_img" );
if ( $ns == NS_FILE && $noforce ) { # but might be an image
$found = false;
while ( true ) {
@@ -1925,19 +1942,19 @@ class Parser {
$holders->merge( $this->replaceInternalLinks2( $text ) );
$s .= "{$prefix}[[$link|$text";
# note: no $trail, because without an end, there *is* no trail
- wfProfileOut( __METHOD__."-might_be_img" );
+ wfProfileOut( __METHOD__ . "-might_be_img" );
continue;
}
} else { # it's not an image, so output it raw
$s .= "{$prefix}[[$link|$text";
# note: no $trail, because without an end, there *is* no trail
- wfProfileOut( __METHOD__."-might_be_img" );
+ wfProfileOut( __METHOD__ . "-might_be_img" );
continue;
}
- wfProfileOut( __METHOD__."-might_be_img" );
+ wfProfileOut( __METHOD__ . "-might_be_img" );
}
- $wasblank = ( $text == '' );
+ $wasblank = ( $text == '' );
if ( $wasblank ) {
$text = $link;
} else {
@@ -1951,18 +1968,25 @@ class Parser {
# Link not escaped by : , create the various objects
if ( $noforce ) {
# Interwikis
- wfProfileIn( __METHOD__."-interwiki" );
+ wfProfileIn( __METHOD__ . "-interwiki" );
if ( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && Language::fetchLanguageName( $iw, null, 'mw' ) ) {
- $this->mOutput->addLanguageLink( $nt->getFullText() );
+ // XXX: the above check prevents links to sites with identifiers that are not language codes
+
+ # Bug 24502: filter duplicates
+ if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
+ $this->mLangLinkLanguages[$iw] = true;
+ $this->mOutput->addLanguageLink( $nt->getFullText() );
+ }
+
$s = rtrim( $s . $prefix );
$s .= trim( $trail, "\n" ) == '' ? '': $prefix . $trail;
- wfProfileOut( __METHOD__."-interwiki" );
+ wfProfileOut( __METHOD__ . "-interwiki" );
continue;
}
- wfProfileOut( __METHOD__."-interwiki" );
+ wfProfileOut( __METHOD__ . "-interwiki" );
if ( $ns == NS_FILE ) {
- wfProfileIn( __METHOD__."-image" );
+ wfProfileIn( __METHOD__ . "-image" );
if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
if ( $wasblank ) {
# if no parameters were passed, $text
@@ -1983,12 +2007,12 @@ class Parser {
} else {
$s .= $prefix . $trail;
}
- wfProfileOut( __METHOD__."-image" );
+ wfProfileOut( __METHOD__ . "-image" );
continue;
}
if ( $ns == NS_CATEGORY ) {
- wfProfileIn( __METHOD__."-category" );
+ wfProfileIn( __METHOD__ . "-category" );
$s = rtrim( $s . "\n" ); # bug 87
if ( $wasblank ) {
@@ -2007,14 +2031,18 @@ class Parser {
*/
$s .= trim( $prefix . $trail, "\n" ) == '' ? '' : $prefix . $trail;
- wfProfileOut( __METHOD__."-category" );
+ wfProfileOut( __METHOD__ . "-category" );
continue;
}
}
# Self-link checking
if ( $nt->getFragment() === '' && $ns != NS_SPECIAL ) {
- if ( in_array( $nt->getPrefixedText(), $selflink, true ) ) {
+ if ( $nt->equals( $this->mTitle ) || ( !$nt->isKnown() && in_array(
+ $this->mTitle->getPrefixedText(),
+ $this->getConverterLanguage()->autoConvertToAllVariants( $nt->getPrefixedText() ),
+ true
+ ) ) ) {
$s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail );
continue;
}
@@ -2023,7 +2051,7 @@ class Parser {
# NS_MEDIA is a pseudo-namespace for linking directly to a file
# @todo FIXME: Should do batch file existence checks, see comment below
if ( $ns == NS_MEDIA ) {
- wfProfileIn( __METHOD__."-media" );
+ wfProfileIn( __METHOD__ . "-media" );
# Give extensions a chance to select the file revision for us
$options = array();
$descQuery = false;
@@ -2034,11 +2062,11 @@ class Parser {
# Cloak with NOPARSE to avoid replacement in replaceExternalLinks
$s .= $prefix . $this->armorLinks(
Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
- wfProfileOut( __METHOD__."-media" );
+ wfProfileOut( __METHOD__ . "-media" );
continue;
}
- wfProfileIn( __METHOD__."-always_known" );
+ wfProfileIn( __METHOD__ . "-always_known" );
# Some titles, such as valid special pages or files in foreign repos, should
# be shown as bluelinks even though they're not included in the page table
#
@@ -2051,7 +2079,7 @@ class Parser {
# Links will be added to the output link list after checking
$s .= $holders->makeHolder( $nt, $text, array(), $trail, $prefix );
}
- wfProfileOut( __METHOD__."-always_known" );
+ wfProfileOut( __METHOD__ . "-always_known" );
}
wfProfileOut( __METHOD__ );
return $holders;
@@ -2066,7 +2094,7 @@ class Parser {
*
* @param $nt Title
* @param $text String
- * @param $query Array or String
+ * @param array $query or String
* @param $trail String
* @param $prefix String
* @return String: HTML-wikitext mix oh yuck
@@ -2093,7 +2121,7 @@ class Parser {
* Not needed quite as much as it used to be since free links are a bit
* more sensible these days. But bracketed links are still an issue.
*
- * @param $text String: more-or-less HTML
+ * @param string $text more-or-less HTML
* @return String: less-or-more HTML with NOPARSE bits
*/
function armorLinks( $text ) {
@@ -2113,7 +2141,7 @@ class Parser {
/**
* Handle link to subpage if necessary
*
- * @param $target String: the source of the link
+ * @param string $target the source of the link
* @param &$text String: the link text, modified as necessary
* @return string the full name of the link
* @private
@@ -2239,7 +2267,7 @@ class Parser {
} else {
return '<!-- ERR 3 -->';
}
- return $text."\n";
+ return $text . "\n";
}
/**#@-*/
@@ -2306,7 +2334,7 @@ class Parser {
$output .= $this->nextItem( substr( $prefix, -1 ) );
$paragraphStack = false;
- if ( substr( $prefix, -1 ) === ';') {
+ if ( substr( $prefix, -1 ) === ';' ) {
# The one nasty exception: definition lists work like this:
# ; title : definition text
# So we check for : in the remainder text to split up the
@@ -2354,13 +2382,13 @@ class Parser {
# If we have no prefixes, go to paragraph mode.
if ( 0 == $prefixLength ) {
- wfProfileIn( __METHOD__."-paragraph" );
+ wfProfileIn( __METHOD__ . "-paragraph" );
# No prefix (not in list)--go to paragraph mode
# XXX: use a stack for nestable elements like span, table and div
- $openmatch = preg_match('/(?:<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<li|<\\/tr|<\\/td|<\\/th)/iS', $t );
+ $openmatch = preg_match( '/(?:<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<dl|<li|<\\/tr|<\\/td|<\\/th)/iS', $t );
$closematch = preg_match(
'/(?:<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|'.
- '<td|<th|<\\/?div|<hr|<\\/pre|<\\/p|'.$this->mUniqPrefix.'-pre|<\\/li|<\\/ul|<\\/ol|<\\/?center)/iS', $t );
+ '<td|<th|<\\/?div|<hr|<\\/pre|<\\/p|'.$this->mUniqPrefix . '-pre|<\\/li|<\\/ul|<\\/ol|<\\/dl|<\\/?center)/iS', $t );
if ( $openmatch or $closematch ) {
$paragraphStack = false;
# TODO bug 5718: paragraph closed
@@ -2374,7 +2402,7 @@ class Parser {
# pre
if ( $this->mLastSection !== 'pre' ) {
$paragraphStack = false;
- $output .= $this->closeParagraph().'<pre>';
+ $output .= $this->closeParagraph() . '<pre>';
$this->mLastSection = 'pre';
}
$t = substr( $t, 1 );
@@ -2382,7 +2410,7 @@ class Parser {
# paragraph
if ( trim( $t ) === '' ) {
if ( $paragraphStack ) {
- $output .= $paragraphStack.'<br />';
+ $output .= $paragraphStack . '<br />';
$paragraphStack = false;
$this->mLastSection = 'p';
} else {
@@ -2400,20 +2428,20 @@ class Parser {
$paragraphStack = false;
$this->mLastSection = 'p';
} elseif ( $this->mLastSection !== 'p' ) {
- $output .= $this->closeParagraph().'<p>';
+ $output .= $this->closeParagraph() . '<p>';
$this->mLastSection = 'p';
}
}
}
}
- wfProfileOut( __METHOD__."-paragraph" );
+ wfProfileOut( __METHOD__ . "-paragraph" );
}
# somewhere above we forget to get out of pre block (bug 785)
if ( $preCloseMatch && $this->mInPre ) {
$this->mInPre = false;
}
if ( $paragraphStack === false ) {
- $output .= $t."\n";
+ $output .= $t . "\n";
}
}
while ( $prefixLength ) {
@@ -2433,9 +2461,10 @@ class Parser {
* Split up a string on ':', ignoring any occurrences inside tags
* to prevent illegal overlapping.
*
- * @param $str String the string to split
+ * @param string $str the string to split
* @param &$before String set to everything before the ':'
* @param &$after String set to everything after the ':'
+ * @throws MWException
* @return String the position of the ':', or false if none found
*/
function findColonNoLinks( $str, &$before, &$after ) {
@@ -2546,7 +2575,7 @@ class Parser {
if ( $c === ">" ) {
$stack--;
if ( $stack < 0 ) {
- wfDebug( __METHOD__.": Invalid input; too many close tags\n" );
+ wfDebug( __METHOD__ . ": Invalid input; too many close tags\n" );
wfProfileOut( __METHOD__ );
return false;
}
@@ -2586,7 +2615,7 @@ class Parser {
}
}
if ( $stack > 0 ) {
- wfDebug( __METHOD__.": Invalid input; not enough close tags (stack $stack, state $state)\n" );
+ wfDebug( __METHOD__ . ": Invalid input; not enough close tags (stack $stack, state $state)\n" );
wfProfileOut( __METHOD__ );
return false;
}
@@ -2600,8 +2629,9 @@ class Parser {
* @private
*
* @param $index integer
- * @param $frame PPFrame
+ * @param bool|\PPFrame $frame
*
+ * @throws MWException
* @return string
*/
function getVariableValue( $index, $frame = false ) {
@@ -2813,7 +2843,7 @@ class Parser {
$value = $this->getRevisionUser();
break;
case 'namespace':
- $value = str_replace( '_',' ',$wgContLang->getNsText( $this->mTitle->getNamespace() ) );
+ $value = str_replace( '_', ' ', $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
break;
case 'namespacee':
$value = wfUrlencode( $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
@@ -2822,7 +2852,7 @@ class Parser {
$value = $this->mTitle->getNamespace();
break;
case 'talkspace':
- $value = $this->mTitle->canTalk() ? str_replace( '_',' ',$this->mTitle->getTalkNsText() ) : '';
+ $value = $this->mTitle->canTalk() ? str_replace( '_', ' ', $this->mTitle->getTalkNsText() ) : '';
break;
case 'talkspacee':
$value = $this->mTitle->canTalk() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
@@ -2960,7 +2990,7 @@ class Parser {
* Preprocess some wikitext and return the document tree.
* This is the ghost of replace_variables().
*
- * @param $text String: The text to parse
+ * @param string $text The text to parse
* @param $flags Integer: bitwise combination of:
* self::PTD_FOR_INCLUSION Handle "<noinclude>" and "<includeonly>" as if the text is being
* included. Default is to assume a direct page view.
@@ -3015,7 +3045,7 @@ class Parser {
* self::OT_PREPROCESS: templates but not extension tags
* self::OT_HTML: all templates and extension tags
*
- * @param $text String the text to transform
+ * @param string $text the text to transform
* @param $frame PPFrame Object describing the arguments passed to the template.
* Arguments may also be provided as an associative array, as was the usual case before MW1.12.
* Providing arguments this way may be useful for extensions wishing to perform variable replacement explicitly.
@@ -3034,7 +3064,7 @@ class Parser {
if ( $frame === false ) {
$frame = $this->getPreprocessor()->newFrame();
} elseif ( !( $frame instanceof PPFrame ) ) {
- wfDebug( __METHOD__." called using plain parameters instead of a PPFrame instance. Creating custom frame.\n" );
+ wfDebug( __METHOD__ . " called using plain parameters instead of a PPFrame instance. Creating custom frame.\n" );
$frame = $this->getPreprocessor()->newCustomFrame( $frame );
}
@@ -3079,7 +3109,7 @@ class Parser {
* Warn the user when a parser limitation is reached
* Will warn at most once the user per limitation type
*
- * @param $limitationType String: should be one of:
+ * @param string $limitationType should be one of:
* 'expensive-parserfunction' (corresponding messages:
* 'expensive-parserfunction-warning',
* 'expensive-parserfunction-category')
@@ -3089,8 +3119,8 @@ class Parser {
* 'post-expand-template-inclusion' (corresponding messages:
* 'post-expand-template-inclusion-warning',
* 'post-expand-template-inclusion-category')
- * @param $current int|null Current value
- * @param $max int|null Maximum allowed, when an explicit limit has been
+ * @param int|null $current Current value
+ * @param int|null $max Maximum allowed, when an explicit limit has been
* exceeded, provide the values (optional)
*/
function limitationWarn( $limitationType, $current = '', $max = '' ) {
@@ -3105,18 +3135,19 @@ class Parser {
* Return the text of a template, after recursively
* replacing any variables or templates within the template.
*
- * @param $piece Array: the parts of the template
+ * @param array $piece the parts of the template
* $piece['title']: the title, i.e. the part before the |
* $piece['parts']: the parameter array
* $piece['lineStart']: whether the brace was at the start of a line
* @param $frame PPFrame The current frame, contains template arguments
+ * @throws MWException
* @return String: the text of the template
* @private
*/
function braceSubstitution( $piece, $frame ) {
global $wgContLang;
wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__.'-setup' );
+ wfProfileIn( __METHOD__ . '-setup' );
# Flags
$found = false; # $text has been filled
@@ -3141,12 +3172,12 @@ class Parser {
# $args is a list of argument nodes, starting from index 0, not including $part1
# @todo FIXME: If piece['parts'] is null then the call to getLength() below won't work b/c this $args isn't an object
$args = ( null == $piece['parts'] ) ? array() : $piece['parts'];
- wfProfileOut( __METHOD__.'-setup' );
+ wfProfileOut( __METHOD__ . '-setup' );
$titleProfileIn = null; // profile templates
# SUBST
- wfProfileIn( __METHOD__.'-modifiers' );
+ wfProfileIn( __METHOD__ . '-modifiers' );
if ( !$found ) {
$substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
@@ -3203,7 +3234,7 @@ class Parser {
$forceRawInterwiki = true;
}
}
- wfProfileOut( __METHOD__.'-modifiers' );
+ wfProfileOut( __METHOD__ . '-modifiers' );
# Parser functions
if ( !$found ) {
@@ -3211,70 +3242,22 @@ class Parser {
$colonPos = strpos( $part1, ':' );
if ( $colonPos !== false ) {
- # Case sensitive functions
- $function = substr( $part1, 0, $colonPos );
- if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
- $function = $this->mFunctionSynonyms[1][$function];
- } else {
- # Case insensitive functions
- $function = $wgContLang->lc( $function );
- if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
- $function = $this->mFunctionSynonyms[0][$function];
- } else {
- $function = false;
- }
+ $func = substr( $part1, 0, $colonPos );
+ $funcArgs = array( trim( substr( $part1, $colonPos + 1 ) ) );
+ for ( $i = 0; $i < $args->getLength(); $i++ ) {
+ $funcArgs[] = $args->item( $i );
}
- if ( $function ) {
- wfProfileIn( __METHOD__ . '-pfunc-' . $function );
- list( $callback, $flags ) = $this->mFunctionHooks[$function];
- $initialArgs = array( &$this );
- $funcArgs = array( trim( substr( $part1, $colonPos + 1 ) ) );
- if ( $flags & SFH_OBJECT_ARGS ) {
- # Add a frame parameter, and pass the arguments as an array
- $allArgs = $initialArgs;
- $allArgs[] = $frame;
- for ( $i = 0; $i < $args->getLength(); $i++ ) {
- $funcArgs[] = $args->item( $i );
- }
- $allArgs[] = $funcArgs;
- } else {
- # Convert arguments to plain text
- for ( $i = 0; $i < $args->getLength(); $i++ ) {
- $funcArgs[] = trim( $frame->expand( $args->item( $i ) ) );
- }
- $allArgs = array_merge( $initialArgs, $funcArgs );
- }
-
- # Workaround for PHP bug 35229 and similar
- if ( !is_callable( $callback ) ) {
- wfProfileOut( __METHOD__ . '-pfunc-' . $function );
- wfProfileOut( __METHOD__ . '-pfunc' );
- wfProfileOut( __METHOD__ );
- throw new MWException( "Tag hook for $function is not callable\n" );
- }
- $result = call_user_func_array( $callback, $allArgs );
- $found = true;
- $noparse = true;
- $preprocessFlags = 0;
-
- if ( is_array( $result ) ) {
- if ( isset( $result[0] ) ) {
- $text = $result[0];
- unset( $result[0] );
- }
-
- # Extract flags into the local scope
- # This allows callers to set flags such as nowiki, found, etc.
- extract( $result );
- } else {
- $text = $result;
- }
- if ( !$noparse ) {
- $text = $this->preprocessToDom( $text, $preprocessFlags );
- $isChildObj = true;
- }
- wfProfileOut( __METHOD__ . '-pfunc-' . $function );
+ try {
+ $result = $this->callParserFunction( $frame, $func, $funcArgs );
+ } catch ( Exception $ex ) {
+ wfProfileOut( __METHOD__ . '-pfunc' );
+ throw $ex;
}
+
+ # The interface for parser functions allows for extracting
+ # flags into the local scope. Extract any forwarded flags
+ # here.
+ extract( $result );
}
wfProfileOut( __METHOD__ . '-pfunc' );
}
@@ -3350,7 +3333,7 @@ class Parser {
}
} elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
$found = false; # access denied
- wfDebug( __METHOD__.": template inclusion denied for " . $title->getPrefixedDBkey() );
+ wfDebug( __METHOD__ . ": template inclusion denied for " . $title->getPrefixedDBkey() );
} else {
list( $text, $title ) = $this->getTemplateDom( $title );
if ( $text !== false ) {
@@ -3385,7 +3368,7 @@ class Parser {
$text = '<span class="error">'
. wfMessage( 'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
. '</span>';
- wfDebug( __METHOD__.": template loop broken at '$titleText'\n" );
+ wfDebug( __METHOD__ . ": template loop broken at '$titleText'\n" );
}
wfProfileOut( __METHOD__ . '-loadtpl' );
}
@@ -3442,7 +3425,7 @@ class Parser {
{
# Bug 529: if the template begins with a table or block-level
# element, it should be treated as beginning a new line.
- # This behaviour is somewhat controversial.
+ # This behavior is somewhat controversial.
$text = "\n" . $text;
}
@@ -3472,6 +3455,120 @@ class Parser {
}
/**
+ * Call a parser function and return an array with text and flags.
+ *
+ * The returned array will always contain a boolean 'found', indicating
+ * whether the parser function was found or not. It may also contain the
+ * following:
+ * text: string|object, resulting wikitext or PP DOM object
+ * isHTML: bool, $text is HTML, armour it against wikitext transformation
+ * isChildObj: bool, $text is a DOM node needing expansion in a child frame
+ * isLocalObj: bool, $text is a DOM node needing expansion in the current frame
+ * nowiki: bool, wiki markup in $text should be escaped
+ *
+ * @since 1.21
+ * @param $frame PPFrame The current frame, contains template arguments
+ * @param $function string Function name
+ * @param $args array Arguments to the function
+ * @return array
+ */
+ public function callParserFunction( $frame, $function, array $args = array() ) {
+ global $wgContLang;
+
+ wfProfileIn( __METHOD__ );
+
+ # Case sensitive functions
+ if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
+ $function = $this->mFunctionSynonyms[1][$function];
+ } else {
+ # Case insensitive functions
+ $function = $wgContLang->lc( $function );
+ if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
+ $function = $this->mFunctionSynonyms[0][$function];
+ } else {
+ wfProfileOut( __METHOD__ );
+ return array( 'found' => false );
+ }
+ }
+
+ wfProfileIn( __METHOD__ . '-pfunc-' . $function );
+ list( $callback, $flags ) = $this->mFunctionHooks[$function];
+
+ # Workaround for PHP bug 35229 and similar
+ if ( !is_callable( $callback ) ) {
+ wfProfileOut( __METHOD__ . '-pfunc-' . $function );
+ wfProfileOut( __METHOD__ );
+ throw new MWException( "Tag hook for $function is not callable\n" );
+ }
+
+ $allArgs = array( &$this );
+ if ( $flags & SFH_OBJECT_ARGS ) {
+ # Convert arguments to PPNodes and collect for appending to $allArgs
+ $funcArgs = array();
+ foreach ( $args as $k => $v ) {
+ if ( $v instanceof PPNode || $k === 0 ) {
+ $funcArgs[] = $v;
+ } else {
+ $funcArgs[] = $this->mPreprocessor->newPartNodeArray( array( $k => $v ) )->item( 0 );
+ }
+ }
+
+ # Add a frame parameter, and pass the arguments as an array
+ $allArgs[] = $frame;
+ $allArgs[] = $funcArgs;
+ } else {
+ # Convert arguments to plain text and append to $allArgs
+ foreach ( $args as $k => $v ) {
+ if ( $v instanceof PPNode ) {
+ $allArgs[] = trim( $frame->expand( $v ) );
+ } elseif ( is_int( $k ) && $k >= 0 ) {
+ $allArgs[] = trim( $v );
+ } else {
+ $allArgs[] = trim( "$k=$v" );
+ }
+ }
+ }
+
+ $result = call_user_func_array( $callback, $allArgs );
+
+ # The interface for function hooks allows them to return a wikitext
+ # string or an array containing the string and any flags. This mungs
+ # things around to match what this method should return.
+ if ( !is_array( $result ) ) {
+ $result = array(
+ 'found' => true,
+ 'text' => $result,
+ );
+ } else {
+ if ( isset( $result[0] ) && !isset( $result['text'] ) ) {
+ $result['text'] = $result[0];
+ }
+ unset( $result[0] );
+ $result += array(
+ 'found' => true,
+ );
+ }
+
+ $noparse = true;
+ $preprocessFlags = 0;
+ if ( isset( $result['noparse'] ) ) {
+ $noparse = $result['noparse'];
+ }
+ if ( isset( $result['preprocessFlags'] ) ) {
+ $preprocessFlags = $result['preprocessFlags'];
+ }
+
+ if ( !$noparse ) {
+ $result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
+ $result['isChildObj'] = true;
+ }
+ wfProfileOut( __METHOD__ . '-pfunc-' . $function );
+ wfProfileOut( __METHOD__ );
+
+ return $result;
+ }
+
+ /**
* Get the semi-parsed DOM representation of a template with a given title,
* and its redirect destination title. Cached.
*
@@ -3593,7 +3690,13 @@ class Parser {
}
if ( $rev ) {
- $text = $rev->getText();
+ $content = $rev->getContent();
+ $text = $content ? $content->getWikitextForTransclusion() : null;
+
+ if ( $text === false || $text === null ) {
+ $text = false;
+ break;
+ }
} elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
global $wgContLang;
$message = wfMessage( $wgContLang->lcfirst( $title->getText() ) )->inContentLanguage();
@@ -3601,16 +3704,17 @@ class Parser {
$text = false;
break;
}
+ $content = $message->content();
$text = $message->plain();
} else {
break;
}
- if ( $text === false ) {
+ if ( !$content ) {
break;
}
# Redirect?
$finalTitle = $title;
- $title = Title::newFromRedirect( $text );
+ $title = $content->getRedirectTarget();
}
return array(
'text' => $text,
@@ -3622,7 +3726,7 @@ class Parser {
* Fetch a file and its title and register a reference to it.
* If 'broken' is a key in $options then the file will appear as a broken thumbnail.
* @param Title $title
- * @param Array $options Array of options to RepoGroup::findFile
+ * @param array $options Array of options to RepoGroup::findFile
* @return File|bool
*/
function fetchFile( $title, $options = array() ) {
@@ -3634,7 +3738,7 @@ class Parser {
* Fetch a file and its title and register a reference to it.
* If 'broken' is a key in $options then the file will appear as a broken thumbnail.
* @param Title $title
- * @param Array $options Array of options to RepoGroup::findFile
+ * @param array $options Array of options to RepoGroup::findFile
* @return Array ( File or false, Title of file )
*/
function fetchFileAndTitle( $title, $options = array() ) {
@@ -3674,7 +3778,7 @@ class Parser {
global $wgEnableScaryTranscluding;
if ( !$wgEnableScaryTranscluding ) {
- return wfMessage('scarytranscludedisabled')->inContentLanguage()->text();
+ return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
}
$url = $title->getFullUrl( "action=$action" );
@@ -3693,19 +3797,24 @@ class Parser {
global $wgTranscludeCacheExpiry;
$dbr = wfGetDB( DB_SLAVE );
$tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
- $obj = $dbr->selectRow( 'transcache', array('tc_time', 'tc_contents' ),
+ $obj = $dbr->selectRow( 'transcache', array( 'tc_time', 'tc_contents' ),
array( 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ) );
if ( $obj ) {
return $obj->tc_contents;
}
- $text = Http::get( $url );
- if ( !$text ) {
+ $req = MWHttpRequest::factory( $url );
+ $status = $req->execute(); // Status object
+ if ( $status->isOK() ) {
+ $text = $req->getContent();
+ } elseif ( $req->getStatus() != 200 ) { // Though we failed to fetch the content, this status is useless.
+ return wfMessage( 'scarytranscludefailed-httpstatus', $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text();
+ } else {
return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
}
$dbw = wfGetDB( DB_MASTER );
- $dbw->replace( 'transcache', array('tc_url'), array(
+ $dbw->replace( 'transcache', array( 'tc_url' ), array(
'tc_url' => $url,
'tc_time' => $dbw->timestamp( time() ),
'tc_contents' => $text)
@@ -3731,7 +3840,7 @@ class Parser {
$argName = trim( $nameWithSpaces );
$object = false;
$text = $frame->getArgument( $argName );
- if ( $text === false && $parts->getLength() > 0
+ if ( $text === false && $parts->getLength() > 0
&& (
$this->ot['html']
|| $this->ot['pre']
@@ -3767,7 +3876,7 @@ class Parser {
* Return the text to be used for a given extension tag.
* This is the ghost of strip().
*
- * @param $params array Associative array of parameters:
+ * @param array $params Associative array of parameters:
* name PPNode for the tag name
* attr PPNode for unparsed text where tag attributes are thought to be
* attributes Optional associative array of parsed attributes
@@ -3775,6 +3884,7 @@ class Parser {
* noClose Original text did not have a close tag
* @param $frame PPFrame
*
+ * @throws MWException
* @return string
*/
function extensionSubstitution( $params, $frame ) {
@@ -3783,7 +3893,7 @@ class Parser {
$content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
$marker = "{$this->mUniqPrefix}-$name-" . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
- $isFunctionTag = isset( $this->mFunctionTagHooks[strtolower($name)] ) &&
+ $isFunctionTag = isset( $this->mFunctionTagHooks[strtolower( $name )] ) &&
( $this->ot['html'] || $this->ot['pre'] );
if ( $isFunctionTag ) {
$markerType = 'none';
@@ -3805,7 +3915,7 @@ class Parser {
$output = call_user_func_array( $this->mTagHooks[$name],
array( $content, $attributes, $this, $frame ) );
} elseif ( isset( $this->mFunctionTagHooks[$name] ) ) {
- list( $callback, $flags ) = $this->mFunctionTagHooks[$name];
+ list( $callback, ) = $this->mFunctionTagHooks[$name];
if ( !is_callable( $callback ) ) {
throw new MWException( "Tag hook for $name is not callable\n" );
}
@@ -3848,7 +3958,7 @@ class Parser {
} elseif ( $markerType === 'general' ) {
$this->mStripState->addGeneral( $marker, $output );
} else {
- throw new MWException( __METHOD__.': invalid marker type' );
+ throw new MWException( __METHOD__ . ': invalid marker type' );
}
return $marker;
}
@@ -3856,7 +3966,7 @@ class Parser {
/**
* Increment an include size counter
*
- * @param $type String: the type of expansion
+ * @param string $type the type of expansion
* @param $size Integer: the size of the text
* @return Boolean: false if this inclusion would take it over the maximum, true otherwise
*/
@@ -3942,12 +4052,12 @@ class Parser {
* Add a tracking category, getting the title from a system message,
* or print a debug message if the title is invalid.
*
- * @param $msg String: message key
+ * @param string $msg message key
* @return Boolean: whether the addition was successful
*/
public function addTrackingCategory( $msg ) {
if ( $this->mTitle->getNamespace() === NS_SPECIAL ) {
- wfDebug( __METHOD__.": Not adding tracking category $msg to special page!\n" );
+ wfDebug( __METHOD__ . ": Not adding tracking category $msg to special page!\n" );
return false;
}
// Important to parse with correct title (bug 31469)
@@ -3966,7 +4076,7 @@ class Parser {
$this->mOutput->addCategory( $containerCategory->getDBkey(), $this->getDefaultSort() );
return true;
} else {
- wfDebug( __METHOD__.": [[MediaWiki:$msg]] is not a valid title!\n" );
+ wfDebug( __METHOD__ . ": [[MediaWiki:$msg]] is not a valid title!\n" );
return false;
}
}
@@ -3982,7 +4092,7 @@ class Parser {
* string and re-inserts the newly formatted headlines.
*
* @param $text String
- * @param $origText String: original, untouched wikitext
+ * @param string $origText original, untouched wikitext
* @param $isMain Boolean
* @return mixed|string
* @private
@@ -4062,7 +4172,7 @@ class Parser {
$sectionIndex = false;
$numbering = '';
$markerMatches = array();
- if ( preg_match("/^$markerRegex/", $headline, $markerMatches ) ) {
+ if ( preg_match( "/^$markerRegex/", $headline, $markerMatches ) ) {
$serial = $markerMatches[1];
list( $titleText, $sectionIndex ) = $this->mHeadings[$serial];
$isTemplate = ( $titleText != $baseTitleText );
@@ -4078,7 +4188,7 @@ class Parser {
# Increase TOC level
$toclevel++;
$sublevelCount[$toclevel] = 0;
- if ( $toclevel<$wgMaxTocLevel ) {
+ if ( $toclevel < $wgMaxTocLevel ) {
$prevtoclevel = $toclevel;
$toc .= Linker::tocIndent();
$numVisible++;
@@ -4100,7 +4210,7 @@ class Parser {
if ( $i == 0 ) {
$toclevel = 1;
}
- if ( $toclevel<$wgMaxTocLevel ) {
+ if ( $toclevel < $wgMaxTocLevel ) {
if ( $prevtoclevel < $wgMaxTocLevel ) {
# Unindent only if the previous toc level was shown :p
$toc .= Linker::tocUnindent( $prevtoclevel - $toclevel );
@@ -4111,7 +4221,7 @@ class Parser {
}
} else {
# No change in level, end TOC line
- if ( $toclevel<$wgMaxTocLevel ) {
+ if ( $toclevel < $wgMaxTocLevel ) {
$toc .= Linker::tocLineEnd();
}
}
@@ -4119,7 +4229,7 @@ class Parser {
$levelCount[$toclevel] = $level;
# count number of headlines for each level
- @$sublevelCount[$toclevel]++;
+ $sublevelCount[$toclevel]++;
$dot = 0;
for( $i = 1; $i <= $toclevel; $i++ ) {
if ( !empty( $sublevelCount[$i] ) ) {
@@ -4144,11 +4254,17 @@ class Parser {
$safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
# Strip out HTML (first regex removes any tag not allowed)
- # Allowed tags are <sup> and <sub> (bug 8393), <i> (bug 26375) and <b> (r105284)
- # We strip any parameter from accepted tags (second regex)
+ # Allowed tags are:
+ # * <sup> and <sub> (bug 8393)
+ # * <i> (bug 26375)
+ # * <b> (r105284)
+ # * <span dir="rtl"> and <span dir="ltr"> (bug 35167)
+ #
+ # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
+ # to allow setting directionality in toc items.
$tocline = preg_replace(
- array( '#<(?!/?(sup|sub|i|b)(?: [^>]*)?>).*?'.'>#', '#<(/?(sup|sub|i|b))(?: .*?)?'.'>#' ),
- array( '', '<$1>' ),
+ array( '#<(?!/?(span|sup|sub|i|b)(?: [^>]*)?>).*?'.'>#', '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|i|b))(?: .*?)?'.'>#' ),
+ array( '', '<$1>' ),
$safeHeadline
);
$tocline = trim( $tocline );
@@ -4269,9 +4385,9 @@ class Parser {
// We use a page and section attribute to stop the language converter from converting these important bits
// of data, but put the headline hint inside a content block because the language converter is supposed to
// be able to convert that piece of data.
- $editlink = '<mw:editsection page="' . htmlspecialchars($editlinkArgs[0]);
- $editlink .= '" section="' . htmlspecialchars($editlinkArgs[1]) .'"';
- if ( isset($editlinkArgs[2]) ) {
+ $editlink = '<mw:editsection page="' . htmlspecialchars( $editlinkArgs[0] );
+ $editlink .= '" section="' . htmlspecialchars( $editlinkArgs[1] ) . '"';
+ if ( isset( $editlinkArgs[2] ) ) {
$editlink .= '>' . $editlinkArgs[2] . '</mw:editsection>';
} else {
$editlink .= '/>';
@@ -4353,7 +4469,7 @@ class Parser {
* Transform wiki markup when saving a page by doing "\r\n" -> "\n"
* conversion, substitting signatures, {{subst:}} templates, etc.
*
- * @param $text String: the text to transform
+ * @param string $text the text to transform
* @param $title Title: the Title object for the current article
* @param $user User: the User object describing the current user
* @param $options ParserOptions: parsing options
@@ -4436,14 +4552,14 @@ class Parser {
'~~~' => $sigText
) );
- # Context links: [[|name]] and [[name (context)|]]
+ # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
$tc = '[' . Title::legalChars() . ']';
$nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
- $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/"; # [[ns:page (context)|]]
- $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/"; # [[ns:page(context)|]]
- $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/"; # [[ns:page (context), context|]]
- $p2 = "/\[\[\\|($tc+)]]/"; # [[|page]]
+ $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/"; # [[ns:page (context)|]]
+ $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/"; # [[ns:page(context)|]] (double-width brackets, added in r40257)
+ $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/"; # [[ns:page (context), context|]] (using either single or double-width comma)
+ $p2 = "/\[\[\\|($tc+)]]/"; # [[|page]] (reverse pipe trick: add context from page title)
# try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
$text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
@@ -4476,7 +4592,7 @@ class Parser {
* as it may have changed if it's the $wgParser.
*
* @param $user User
- * @param $nickname String|bool nickname to use or false to use user's default nickname
+ * @param string|bool $nickname nickname to use or false to use user's default nickname
* @param $fancySig Boolean|null whether the nicknname is the complete signature
* or null to use default value
* @return string
@@ -4507,7 +4623,7 @@ class Parser {
} else {
# Failed to validate; fall back to the default
$nickname = $username;
- wfDebug( __METHOD__.": $username has bad XML tags in signature.\n" );
+ wfDebug( __METHOD__ . ": $username has bad XML tags in signature.\n" );
}
}
@@ -4539,7 +4655,7 @@ class Parser {
* 2) Substitute all transclusions
*
* @param $text String
- * @param $parsing bool Whether we're cleaning (preferences save) or parsing
+ * @param bool $parsing Whether we're cleaning (preferences save) or parsing
* @return String: signature text
*/
public function cleanSig( $text, $parsing = false ) {
@@ -4614,7 +4730,7 @@ class Parser {
/**
* Wrapper for preprocess()
*
- * @param $text String: the text to preprocess
+ * @param string $text the text to preprocess
* @param $options ParserOptions: options
* @param $title Title object or null to use $wgTitle
* @return String
@@ -4633,11 +4749,7 @@ class Parser {
global $wgTitle;
$title = $wgTitle;
}
- if ( !$title ) {
- # It's not uncommon having a null $wgTitle in scripts. See r80898
- # Create a ghost title in such case
- $title = Title::newFromText( 'Dwimmerlaik' );
- }
+
$text = $this->preprocess( $text, $title, $options );
$executing = false;
@@ -4666,6 +4778,7 @@ class Parser {
*
* @param $tag Mixed: the tag to use, e.g. 'hook' for "<hook>"
* @param $callback Mixed: the callback function (and object) to use for the tag
+ * @throws MWException
* @return Mixed|null The old value of the mTagHooks array associated with the hook
*/
public function setHook( $tag, $callback ) {
@@ -4696,6 +4809,7 @@ class Parser {
*
* @param $tag Mixed: the tag to use, e.g. 'hook' for "<hook>"
* @param $callback Mixed: the callback function (and object) to use for the tag
+ * @throws MWException
* @return Mixed|null The old value of the mTagHooks array associated with the hook
*/
function setTransparentTagHook( $tag, $callback ) {
@@ -4734,7 +4848,7 @@ class Parser {
* nowiki Wiki markup in the return value should be escaped
* isHTML The returned text is HTML, armour it against wikitext transformation
*
- * @param $id String: The magic word ID
+ * @param string $id The magic word ID
* @param $callback Mixed: the callback function (and object) to use
* @param $flags Integer: a combination of the following flags:
* SFH_NO_HASH No leading hash, i.e. {{plural:...}} instead of {{#if:...}}
@@ -4758,6 +4872,7 @@ class Parser {
* Please read the documentation in includes/parser/Preprocessor.php for more information
* about the methods available in PPFrame and PPNode.
*
+ * @throws MWException
* @return string|callback The old callback function for this name, if any
*/
public function setFunctionHook( $id, $callback, $flags = 0 ) {
@@ -4769,7 +4884,7 @@ class Parser {
# Add to function cache
$mw = MagicWord::get( $id );
if ( !$mw )
- throw new MWException( __METHOD__.'() expecting a magic word identifier.' );
+ throw new MWException( __METHOD__ . '() expecting a magic word identifier.' );
$synonyms = $mw->getSynonyms();
$sensitive = intval( $mw->isCaseSensitive() );
@@ -4805,6 +4920,10 @@ class Parser {
* Create a tag function, e.g. "<test>some stuff</test>".
* Unlike tag hooks, tag functions are parsed at preprocessor level.
* Unlike parser functions, their content is not preprocessed.
+ * @param $tag
+ * @param $callback
+ * @param $flags
+ * @throws MWException
* @return null
*/
function setFunctionTagHook( $tag, $callback, $flags ) {
@@ -4920,7 +5039,7 @@ class Parser {
// is defined for images in galleries
$matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
- $parameterMatches = StringUtils::explode('|', $matches[3]);
+ $parameterMatches = StringUtils::explode( '|', $matches[3] );
$magicWordAlt = MagicWord::get( 'img_alt' );
$magicWordLink = MagicWord::get( 'img_link' );
@@ -4928,14 +5047,18 @@ class Parser {
if ( $match = $magicWordAlt->matchVariableStartToEnd( $parameterMatch ) ) {
$alt = $this->stripAltText( $match, false );
}
- elseif( $match = $magicWordLink->matchVariableStartToEnd( $parameterMatch ) ){
- $link = strip_tags($this->replaceLinkHoldersText($match));
+ elseif( $match = $magicWordLink->matchVariableStartToEnd( $parameterMatch ) ) {
+ $linkValue = strip_tags( $this->replaceLinkHoldersText( $match ) );
$chars = self::EXT_LINK_URL_CLASS;
$prots = $this->mUrlProtocols;
//check to see if link matches an absolute url, if not then it must be a wiki link.
- if(!preg_match( "/^($prots)$chars+$/u", $link)){
- $localLinkTitle = Title::newFromText($link);
- $link = $localLinkTitle->getLocalURL();
+ if ( preg_match( "/^($prots)$chars+$/u", $linkValue ) ) {
+ $link = $linkValue;
+ } else {
+ $localLinkTitle = Title::newFromText( $linkValue );
+ if ( $localLinkTitle !== null ) {
+ $link = $localLinkTitle->getLocalURL();
+ }
}
}
else {
@@ -4947,7 +5070,7 @@ class Parser {
$label = substr( $label, 1 );
}
- $ig->add( $title, $label, $alt ,$link);
+ $ig->add( $title, $label, $alt, $link );
}
return $ig->toHTML();
}
@@ -4962,7 +5085,7 @@ class Parser {
} else {
$handlerClass = '';
}
- if ( !isset( $this->mImageParams[$handlerClass] ) ) {
+ if ( !isset( $this->mImageParams[$handlerClass] ) ) {
# Initialise static lists
static $internalParamNames = array(
'horizAlign' => array( 'left', 'right', 'center', 'none' ),
@@ -5180,7 +5303,7 @@ class Parser {
} else { # Inline image
if ( !isset( $params['frame']['alt'] ) ) {
# No alt text, use the "caption" for the alt text
- if ( $caption !== '') {
+ if ( $caption !== '' ) {
$params['frame']['alt'] = $this->stripAltText( $caption, $holders );
} else {
# No caption, fall back to using the filename for the
@@ -5303,8 +5426,8 @@ class Parser {
*
* External callers should use the getSection and replaceSection methods.
*
- * @param $text String: Page wikitext
- * @param $section String: a section identifier string of the form:
+ * @param string $text Page wikitext
+ * @param string $section a section identifier string of the form:
* "<flag1> - <flag2> - ... - <section number>"
*
* Currently the only recognised flag is "T", which means the target section number
@@ -5321,12 +5444,12 @@ class Parser {
* string. If $text is the empty string and section 0 is replaced, $newText is
* returned.
*
- * @param $mode String: one of "get" or "replace"
- * @param $newText String: replacement text for section data.
+ * @param string $mode one of "get" or "replace"
+ * @param string $newText replacement text for section data.
* @return String: for "get", the extracted section text.
* for "replace", the whole page with the section replaced.
*/
- private function extractSections( $text, $section, $mode, $newText='' ) {
+ private function extractSections( $text, $section, $mode, $newText = '' ) {
global $wgTitle; # not generally used but removes an ugly failure mode
$this->startParse( $wgTitle, new ParserOptions, self::OT_PLAIN, true );
$outText = '';
@@ -5442,12 +5565,12 @@ class Parser {
*
* If a section contains subsections, these are also returned.
*
- * @param $text String: text to look in
- * @param $section String: section identifier
- * @param $deftext String: default to return if section is not found
+ * @param string $text text to look in
+ * @param string $section section identifier
+ * @param string $deftext default to return if section is not found
* @return string text of the requested section
*/
- public function getSection( $text, $section, $deftext='' ) {
+ public function getSection( $text, $section, $deftext = '' ) {
return $this->extractSections( $text, $section, "get", $deftext );
}
@@ -5456,9 +5579,9 @@ class Parser {
* specified by $section has been replaced with $text. If the target
* section does not exist, $oldtext is returned unchanged.
*
- * @param $oldtext String: former text of the article
- * @param $section int section identifier
- * @param $text String: replacing text
+ * @param string $oldtext former text of the article
+ * @param int $section section identifier
+ * @param string $text replacing text
* @return String: modified text
*/
public function replaceSection( $oldtext, $section, $text ) {
@@ -5540,7 +5663,7 @@ class Parser {
/**
* Mutator for $mDefaultSort
*
- * @param $sort string New value
+ * @param string $sort New value
*/
public function setDefaultSort( $sort ) {
$this->mDefaultSort = $sort;
@@ -5596,7 +5719,7 @@ class Parser {
* instead. For use in redirects, since IE6 interprets Redirect: headers
* as something other than UTF-8 (apparently?), resulting in breakage.
*
- * @param $text String: The section name
+ * @param string $text The section name
* @return string An anchor
*/
public function guessLegacySectionNameFromWikiText( $text ) {
@@ -5616,7 +5739,7 @@ class Parser {
* to create valid section anchors by mimicing the output of the
* parser when headings are parsed.
*
- * @param $text String: text string to be stripped of wikitext
+ * @param string $text text string to be stripped of wikitext
* for use in a Section anchor
* @return string Filtered text string
*/
@@ -5767,12 +5890,13 @@ class Parser {
* If the $data array has been stored persistently, the caller should first
* check whether it is still valid, by calling isValidHalfParsedText().
*
- * @param $data array Serialized data
+ * @param array $data Serialized data
+ * @throws MWException
* @return String
*/
function unserializeHalfParsedText( $data ) {
if ( !isset( $data['version'] ) || $data['version'] != self::HALF_PARSED_VERSION ) {
- throw new MWException( __METHOD__.': invalid version' );
+ throw new MWException( __METHOD__ . ': invalid version' );
}
# First, extract the strip state.
diff --git a/includes/parser/ParserCache.php b/includes/parser/ParserCache.php
index 6a4ef0c5..0faa40a8 100644
--- a/includes/parser/ParserCache.php
+++ b/includes/parser/ParserCache.php
@@ -48,6 +48,7 @@ class ParserCache {
* May be a memcached client or a BagOStuff derivative.
*
* @param $memCached Object
+ * @throws MWException
*/
protected function __construct( $memCached ) {
if ( !$memCached ) {
@@ -66,7 +67,7 @@ class ParserCache {
// idhash seem to mean 'page id' + 'rendering hash' (r3710)
$pageid = $article->getID();
- $renderkey = (int)($wgRequest->getVal('action') == 'render');
+ $renderkey = (int)($wgRequest->getVal( 'action' ) == 'render');
$key = wfMemcKey( 'pcache', 'idhash', "{$pageid}-{$renderkey}!{$hash}" );
return $key;
@@ -200,8 +201,8 @@ class ParserCache {
wfDebug( "ParserOutput cache found.\n" );
- // The edit section preference may not be the appropiate one in
- // the ParserOutput, as we are not storing it in the parsercache
+ // The edit section preference may not be the appropiate one in
+ // the ParserOutput, as we are not storing it in the parsercache
// key. Force it here. See bug 31445.
$value->setEditSectionTokens( $popts->getEditSection() );
diff --git a/includes/parser/ParserOptions.php b/includes/parser/ParserOptions.php
index 009b18a1..3eb83e36 100644
--- a/includes/parser/ParserOptions.php
+++ b/includes/parser/ParserOptions.php
@@ -29,67 +29,62 @@
* @ingroup Parser
*/
class ParserOptions {
-
- /**
- * Use DateFormatter to format dates
- */
- var $mUseDynamicDates;
-
+
/**
* Interlanguage links are removed and returned in an array
*/
var $mInterwikiMagic;
-
+
/**
* Allow external images inline?
*/
var $mAllowExternalImages;
-
+
/**
* If not, any exception?
*/
var $mAllowExternalImagesFrom;
-
+
/**
* If not or it doesn't match, should we check an on-wiki whitelist?
*/
var $mEnableImageWhitelist;
-
+
/**
* Date format index
*/
var $mDateFormat = null;
-
+
/**
* Create "edit section" links?
*/
var $mEditSection = true;
-
+
/**
* Allow inclusion of special pages?
*/
var $mAllowSpecialInclusion;
-
+
/**
* Use tidy to cleanup output HTML?
*/
var $mTidy = false;
-
+
/**
* Which lang to call for PLURAL and GRAMMAR
*/
var $mInterfaceMessage = false;
-
+
/**
* Overrides $mInterfaceMessage with arbitrary language
*/
var $mTargetLanguage = null;
-
+
/**
* Maximum size of template expansions, in bytes
*/
var $mMaxIncludeSize;
-
+
/**
* Maximum number of nodes touched by PPFrame::expand()
*/
@@ -99,56 +94,56 @@ class ParserOptions {
* Maximum number of nodes generated by Preprocessor::preprocessToObj()
*/
var $mMaxGeneratedPPNodeCount;
-
+
/**
* Maximum recursion depth in PPFrame::expand()
*/
var $mMaxPPExpandDepth;
-
+
/**
* Maximum recursion depth for templates within templates
*/
var $mMaxTemplateDepth;
-
+
/**
* Maximum number of calls per parse to expensive parser functions
*/
var $mExpensiveParserFunctionLimit;
-
+
/**
* Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS
*/
var $mRemoveComments = true;
-
+
/**
* Callback for template fetching. Used as first argument to call_user_func().
*/
var $mTemplateCallback =
array( 'Parser', 'statelessFetchTemplate' );
-
+
/**
* Enable limit report in an HTML comment on output
*/
var $mEnableLimitReport = false;
-
+
/**
* Timestamp used for {{CURRENTDAY}} etc.
*/
var $mTimestamp;
-
+
/**
* Target attribute for external links
*/
var $mExternalLinkTarget;
-
+
/**
- * Clean up signature texts?
+ * Clean up signature texts?
*
* 1) Strip ~~~, ~~~~ and ~~~~~ out of signatures
* 2) Substitute all transclusions
*/
var $mCleanSignatures;
-
+
/**
* Transform wiki markup when saving the page?
*/
@@ -168,43 +163,43 @@ class ParserOptions {
* Automatically number headings?
*/
var $mNumberHeadings;
-
+
/**
* User math preference (as integer). Not used (1.19)
*/
var $mMath;
-
+
/**
* Thumb size preferred by the user.
*/
var $mThumbSize;
-
+
/**
* Maximum article size of an article to be marked as "stub"
*/
private $mStubThreshold;
-
+
/**
* Language object of the User language.
*/
var $mUserLang;
/**
- * @var User
+ * @var User
* Stored user object
*/
var $mUser;
-
+
/**
* Parsing the page for a "preview" operation?
*/
var $mIsPreview = false;
-
+
/**
* Parsing the page for a "preview" operation on a single section?
*/
var $mIsSectionPreview = false;
-
+
/**
* Parsing the printable version of the page?
*/
@@ -220,7 +215,6 @@ class ParserOptions {
*/
protected $onAccessCallback = null;
- function getUseDynamicDates() { return $this->mUseDynamicDates; }
function getInterwikiMagic() { return $this->mInterwikiMagic; }
function getAllowExternalImages() { return $this->mAllowExternalImages; }
function getAllowExternalImagesFrom() { return $this->mAllowExternalImagesFrom; }
@@ -308,7 +302,6 @@ class ParserOptions {
return $this->getUserLangObj()->getCode();
}
- function setUseDynamicDates( $x ) { return wfSetVar( $this->mUseDynamicDates, $x ); }
function setInterwikiMagic( $x ) { return wfSetVar( $this->mInterwikiMagic, $x ); }
function setAllowExternalImages( $x ) { return wfSetVar( $this->mAllowExternalImages, $x ); }
function setAllowExternalImagesFrom( $x ) { return wfSetVar( $this->mAllowExternalImagesFrom, $x ); }
@@ -415,14 +408,14 @@ class ParserOptions {
return new ParserOptions( $context->getUser(), $context->getLanguage() );
}
- /**
- * Get user options
+ /**
+ * Get user options
*
* @param $user User object
* @param $lang Language object
*/
private function initialiseFromUser( $user, $lang ) {
- global $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages,
+ global $wgInterwikiMagic, $wgAllowExternalImages,
$wgAllowExternalImagesFrom, $wgEnableImageWhitelist, $wgAllowSpecialInclusion,
$wgMaxArticleSize, $wgMaxPPNodeCount, $wgMaxTemplateDepth, $wgMaxPPExpandDepth,
$wgCleanSignatures, $wgExternalLinkTarget, $wgExpensiveParserFunctionLimit,
@@ -430,7 +423,6 @@ class ParserOptions {
wfProfileIn( __METHOD__ );
- $this->mUseDynamicDates = $wgUseDynamicDates;
$this->mInterwikiMagic = $wgInterwikiMagic;
$this->mAllowExternalImages = $wgAllowExternalImages;
$this->mAllowExternalImagesFrom = $wgAllowExternalImagesFrom;
@@ -481,12 +473,7 @@ class ParserOptions {
* @return array
*/
public static function legacyOptions() {
- global $wgUseDynamicDates;
- $legacyOpts = array( 'math', 'stubthreshold', 'numberheadings', 'userlang', 'thumbsize', 'editsection', 'printable' );
- if ( $wgUseDynamicDates ) {
- $legacyOpts[] = 'dateformat';
- }
- return $legacyOpts;
+ return array( 'math', 'stubthreshold', 'numberheadings', 'userlang', 'thumbsize', 'editsection', 'printable' );
}
/**
@@ -518,14 +505,13 @@ class ParserOptions {
$confstr .= '*';
}
-
// Space assigned for the stubthreshold but unused
// since it disables the parser cache, its value will always
// be 0 when this function is called by parsercache.
if ( in_array( 'stubthreshold', $forOptions ) ) {
$confstr .= '!' . $this->mStubThreshold;
} else {
- $confstr .= '!*' ;
+ $confstr .= '!*';
}
if ( in_array( 'dateformat', $forOptions ) ) {
diff --git a/includes/parser/ParserOutput.php b/includes/parser/ParserOutput.php
index 41b4a385..db649f11 100644
--- a/includes/parser/ParserOutput.php
+++ b/includes/parser/ParserOutput.php
@@ -50,7 +50,8 @@ class ParserOutput extends CacheTime {
$mTimestamp; # Timestamp of the revision
private $mIndexPolicy = ''; # 'index' or 'noindex'? Any other value will result in no change.
private $mAccessedOptions = array(); # List of ParserOptions (stored in the keys)
- private $mSecondaryDataUpdates = array(); # List of instances of SecondaryDataObject(), used to cause some information extracted from the page in a custom place.
+ private $mSecondaryDataUpdates = array(); # List of DataUpdate, used to save info from the page somewhere else.
+ private $mExtensionData = array(); # extra data used by extensions
const EDITSECTION_REGEX = '#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)(</(?:mw:)?editsection>))#';
@@ -75,18 +76,20 @@ class ParserOutput extends CacheTime {
/**
* callback used by getText to replace editsection tokens
* @private
+ * @param $m
+ * @throws MWException
* @return mixed
*/
function replaceEditSectionLinksCallback( $m ) {
global $wgOut, $wgLang;
$args = array(
- htmlspecialchars_decode($m[1]),
- htmlspecialchars_decode($m[2]),
- isset($m[4]) ? $m[3] : null,
+ htmlspecialchars_decode( $m[1] ),
+ htmlspecialchars_decode( $m[2] ),
+ isset( $m[4] ) ? $m[3] : null,
);
$args[0] = Title::newFromText( $args[0] );
- if ( !is_object($args[0]) ) {
- throw new MWException("Bad parser output text.");
+ if ( !is_object( $args[0] ) ) {
+ throw new MWException( "Bad parser output text." );
}
$args[] = $wgLang->getCode();
$skin = $wgOut->getSkin();
@@ -150,11 +153,35 @@ class ParserOutput extends CacheTime {
return (bool)$this->mNewSection;
}
+ /**
+ * Checks, if a url is pointing to the own server
+ *
+ * @param string $internal the server to check against
+ * @param string $url the url to check
+ * @return bool
+ */
+ static function isLinkInternal( $internal, $url ) {
+ return (bool)preg_match( '/^' .
+ # If server is proto relative, check also for http/https links
+ ( substr( $internal, 0, 2 ) === '//' ? '(?:https?:)?' : '' ) .
+ preg_quote( $internal, '/' ) .
+ # check for query/path/anchor or end of link in each case
+ '(?:[\?\/\#]|$)/i',
+ $url
+ );
+ }
+
function addExternalLink( $url ) {
# We don't register links pointing to our own server, unless... :-)
global $wgServer, $wgRegisterInternalExternals;
- if( $wgRegisterInternalExternals or stripos($url,$wgServer.'/')!==0)
+
+ $registerExternalLink = true;
+ if( !$wgRegisterInternalExternals ) {
+ $registerExternalLink = !self::isLinkInternal( $wgServer, $url );
+ }
+ if( $registerExternalLink ) {
$this->mExternalLinks[$url] = 1;
+ }
}
/**
@@ -163,7 +190,7 @@ class ParserOutput extends CacheTime {
* @param $title Title object
* @param $id Mixed: optional known page_id so we can skip the lookup
*/
- function addLink( $title, $id = null ) {
+ function addLink( Title $title, $id = null ) {
if ( $title->isExternal() ) {
// Don't record interwikis in pagelinks
$this->addInterwikiLink( $title );
@@ -193,9 +220,9 @@ class ParserOutput extends CacheTime {
/**
* Register a file dependency for this output
- * @param $name string Title dbKey
- * @param $timestamp string MW timestamp of file creation (or false if non-existing)
- * @param $sha1 string base 36 SHA-1 of file (or false if non-existing)
+ * @param string $name Title dbKey
+ * @param string $timestamp MW timestamp of file creation (or false if non-existing)
+ * @param string $sha1 base 36 SHA-1 of file (or false if non-existing)
* @return void
*/
function addImage( $name, $timestamp = null, $sha1 = null ) {
@@ -234,7 +261,7 @@ class ParserOutput extends CacheTime {
if( $prefix == '' ) {
throw new MWException( 'Non-interwiki link passed, internal parser error.' );
}
- if (!isset($this->mInterwikiLinks[$prefix])) {
+ if ( !isset( $this->mInterwikiLinks[$prefix] ) ) {
$this->mInterwikiLinks[$prefix] = array();
}
$this->mInterwikiLinks[$prefix][$title->getDBkey()] = 1;
@@ -288,7 +315,7 @@ class ParserOutput extends CacheTime {
* -- this is assumed to have been validated
* (check equal normalisation, etc.)
*
- * @param $text String: desired title text
+ * @param string $text desired title text
*/
public function setDisplayTitle( $text ) {
$this->setTitleText( $text );
@@ -320,13 +347,67 @@ class ParserOutput extends CacheTime {
}
/**
- * Set a property to be cached in the DB
+ * Set a property to be stored in the page_props database table.
+ *
+ * page_props is a key value store indexed by the page ID. This allows
+ * the parser to set a property on a page which can then be quickly
+ * retrieved given the page ID or via a DB join when given the page
+ * title.
+ *
+ * setProperty() is thus used to propagate properties from the parsed
+ * page to request contexts other than a page view of the currently parsed
+ * article.
+ *
+ * Some applications examples:
+ *
+ * * To implement hidden categories, hiding pages from category listings
+ * by storing a property.
+ *
+ * * Overriding the displayed article title.
+ * @see ParserOutput::setDisplayTitle()
+ *
+ * * To implement image tagging, for example displaying an icon on an
+ * image thumbnail to indicate that it is listed for deletion on
+ * Wikimedia Commons.
+ * This is not actually implemented, yet but would be pretty cool.
+ *
+ * @note: Do not use setProperty() to set a property which is only used
+ * in a context where the ParserOutput object itself is already available,
+ * for example a normal page view. There is no need to save such a property
+ * in the database since it the text is already parsed. You can just hook
+ * OutputPageParserOutput and get your data out of the ParserOutput object.
+ *
+ * If you are writing an extension where you want to set a property in the
+ * parser which is used by an OutputPageParserOutput hook, you have to
+ * associate the extension data directly with the ParserOutput object.
+ * Since MediaWiki 1.21, you can use setExtensionData() to do this:
+ *
+ * @par Example:
+ * @code
+ * $parser->getOutput()->setExtensionData( 'my_ext_foo', '...' );
+ * @endcode
+ *
+ * And then later, in OutputPageParserOutput or similar:
+ *
+ * @par Example:
+ * @code
+ * $output->getExtensionData( 'my_ext_foo' );
+ * @endcode
+ *
+ * In MediaWiki 1.20 and older, you have to use a custom member variable
+ * within the ParserOutput object:
+ *
+ * @par Example:
+ * @code
+ * $parser->getOutput()->my_ext_foo = '...';
+ * @endcode
+ *
*/
public function setProperty( $name, $value ) {
$this->mProperties[$name] = $value;
}
- public function getProperty( $name ){
+ public function getProperty( $name ) {
return isset( $this->mProperties[$name] ) ? $this->mProperties[$name] : false;
}
@@ -337,26 +418,25 @@ class ParserOutput extends CacheTime {
return $this->mProperties;
}
-
/**
* Returns the options from its ParserOptions which have been taken
* into account to produce this output or false if not available.
* @return mixed Array
*/
- public function getUsedOptions() {
+ public function getUsedOptions() {
if ( !isset( $this->mAccessedOptions ) ) {
return array();
}
return array_keys( $this->mAccessedOptions );
- }
+ }
- /**
- * Callback passed by the Parser to the ParserOptions to keep track of which options are used.
- * @access private
- */
- function recordOption( $option ) {
- $this->mAccessedOptions[$option] = true;
- }
+ /**
+ * Callback passed by the Parser to the ParserOptions to keep track of which options are used.
+ * @access private
+ */
+ function recordOption( $option ) {
+ $this->mAccessedOptions[$option] = true;
+ }
/**
* Adds an update job to the output. Any update jobs added to the output will eventually bexecuted in order to
@@ -375,9 +455,13 @@ class ParserOutput extends CacheTime {
* extracted from the page's content, including a LinksUpdate object for all links stored in
* this ParserOutput object.
*
+ * @note: Avoid using this method directly, use ContentHandler::getSecondaryDataUpdates() instead! The content
+ * handler may provide additional update objects.
+ *
* @since 1.20
*
- * @param $title Title of the page we're updating. If not given, a title object will be created based on $this->getTitleText()
+ * @param $title Title The title of the page we're updating. If not given, a title object will be created
+ * based on $this->getTitleText()
* @param $recursive Boolean: queue jobs for recursive updates?
*
* @return Array. An array of instances of DataUpdate
@@ -389,13 +473,75 @@ class ParserOutput extends CacheTime {
$linksUpdate = new LinksUpdate( $title, $this, $recursive );
- if ( $this->mSecondaryDataUpdates === array() ) {
- return array( $linksUpdate );
+ return array_merge( $this->mSecondaryDataUpdates, array( $linksUpdate ) );
+ }
+
+ /**
+ * Attaches arbitrary data to this ParserObject. This can be used to store some information in
+ * the ParserOutput object for later use during page output. The data will be cached along with
+ * the ParserOutput object, but unlike data set using setProperty(), it is not recorded in the
+ * database.
+ *
+ * This method is provided to overcome the unsafe practice of attaching extra information to a
+ * ParserObject by directly assigning member variables.
+ *
+ * To use setExtensionData() to pass extension information from a hook inside the parser to a
+ * hook in the page output, use this in the parser hook:
+ *
+ * @par Example:
+ * @code
+ * $parser->getOutput()->setExtensionData( 'my_ext_foo', '...' );
+ * @endcode
+ *
+ * And then later, in OutputPageParserOutput or similar:
+ *
+ * @par Example:
+ * @code
+ * $output->getExtensionData( 'my_ext_foo' );
+ * @endcode
+ *
+ * In MediaWiki 1.20 and older, you have to use a custom member variable
+ * within the ParserOutput object:
+ *
+ * @par Example:
+ * @code
+ * $parser->getOutput()->my_ext_foo = '...';
+ * @endcode
+ *
+ * @since 1.21
+ *
+ * @param string $key The key for accessing the data. Extensions should take care to avoid
+ * conflicts in naming keys. It is suggested to use the extension's name as a
+ * prefix.
+ *
+ * @param mixed $value The value to set. Setting a value to null is equivalent to removing
+ * the value.
+ */
+ public function setExtensionData( $key, $value ) {
+ if ( $value === null ) {
+ unset( $this->mExtensionData[$key] );
} else {
- $updates = array_merge( $this->mSecondaryDataUpdates, array( $linksUpdate ) );
+ $this->mExtensionData[$key] = $value;
}
+ }
- return $updates;
- }
+ /**
+ * Gets extensions data previously attached to this ParserOutput using setExtensionData().
+ * Typically, such data would be set while parsing the page, e.g. by a parser function.
+ *
+ * @since 1.21
+ *
+ * @param string $key The key to look up.
+ *
+ * @return mixed The value previously set for the given key using setExtensionData( $key ),
+ * or null if no value was set for this key.
+ */
+ public function getExtensionData( $key ) {
+ if ( isset( $this->mExtensionData[$key] ) ) {
+ return $this->mExtensionData[$key];
+ }
+
+ return null;
+ }
}
diff --git a/includes/parser/Parser_LinkHooks.php b/includes/parser/Parser_LinkHooks.php
index 6bcc324d..b2cdc41a 100644
--- a/includes/parser/Parser_LinkHooks.php
+++ b/includes/parser/Parser_LinkHooks.php
@@ -32,7 +32,7 @@ class Parser_LinkHooks extends Parser {
* can automatically discard old data.
*/
const VERSION = '1.6.4';
-
+
# Flags for Parser::setLinkHook
# Also available as global constants from Defines.php
const SLH_PATTERN = 1;
@@ -84,11 +84,11 @@ class Parser_LinkHooks extends Parser {
* Create a link hook, e.g. [[Namepsace:...|display}}
* The callback function should have the form:
* function myLinkCallback( $parser, $holders, $markers,
- * Title $title, $titleText, &$sortText = null, &$leadingColon = false ) { ... }
+ * Title $title, $titleText, &$sortText = null, &$leadingColon = false ) { ... }
*
* Or with SLH_PATTERN:
* function myLinkCallback( $parser, $holders, $markers, )
- * &$titleText, &$sortText = null, &$leadingColon = false ) { ... }
+ * &$titleText, &$sortText = null, &$leadingColon = false ) { ... }
*
* The callback may either return a number of different possible values:
* String) Text result of the link
@@ -100,18 +100,19 @@ class Parser_LinkHooks extends Parser {
* @param $flags Integer: a combination of the following flags:
* SLH_PATTERN Use a regex link pattern rather than a namespace
*
+ * @throws MWException
* @return callback|null The old callback function for this name, if any
*/
public function setLinkHook( $ns, $callback, $flags = 0 ) {
if( $flags & SLH_PATTERN && !is_string($ns) )
- throw new MWException( __METHOD__.'() expecting a regex string pattern.' );
- elseif( $flags | ~SLH_PATTERN && !is_int($ns) )
- throw new MWException( __METHOD__.'() expecting a namespace index.' );
+ throw new MWException( __METHOD__ . '() expecting a regex string pattern.' );
+ elseif( $flags | ~SLH_PATTERN && !is_int( $ns ) )
+ throw new MWException( __METHOD__ . '() expecting a namespace index.' );
$oldVal = isset( $this->mLinkHooks[$ns] ) ? $this->mLinkHooks[$ns][0] : null;
$this->mLinkHooks[$ns] = array( $callback, $flags );
return $oldVal;
}
-
+
/**
* Get all registered link hook identifiers
*
@@ -120,9 +121,11 @@ class Parser_LinkHooks extends Parser {
function getLinkHooks() {
return array_keys( $this->mLinkHooks );
}
-
+
/**
* Process [[ ]] wikilinks
+ * @param $s
+ * @throws MWException
* @return LinkHolderArray
*
* @private
@@ -130,8 +133,8 @@ class Parser_LinkHooks extends Parser {
function replaceInternalLinks2( &$s ) {
wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__.'-setup' );
- static $tc = FALSE, $titleRegex;//$e1, $e1_img;
+ wfProfileIn( __METHOD__ . '-setup' );
+ static $tc = false, $titleRegex; //$e1, $e1_img;
if( !$tc ) {
# the % is needed to support urlencoded titles as well
$tc = Title::legalChars() . '#%';
@@ -144,15 +147,15 @@ class Parser_LinkHooks extends Parser {
}
$holders = new LinkHolderArray( $this );
-
+
if( is_null( $this->mTitle ) ) {
+ wfProfileOut( __METHOD__ . '-setup' );
wfProfileOut( __METHOD__ );
- wfProfileOut( __METHOD__.'-setup' );
- throw new MWException( __METHOD__.": \$this->mTitle is null\n" );
+ throw new MWException( __METHOD__ . ": \$this->mTitle is null\n" );
}
- wfProfileOut( __METHOD__.'-setup' );
-
+ wfProfileOut( __METHOD__ . '-setup' );
+
$offset = 0;
$offsetStack = array();
$markers = new LinkMarkerReplacer( $this, $holders, array( &$this, 'replaceInternalLinksCallback' ) );
@@ -164,7 +167,7 @@ class Parser_LinkHooks extends Parser {
# Determine if the bracket is a starting or ending bracket
# When we find both, use the first one
elseif( $startBracketOffset !== false && $endBracketOffset !== false )
- $isStart = $startBracketOffset <= $endBracketOffset;
+ $isStart = $startBracketOffset <= $endBracketOffset;
# When we only found one, check which it is
else $isStart = $startBracketOffset !== false;
$bracketOffset = $isStart ? $startBracketOffset : $endBracketOffset;
@@ -175,26 +178,26 @@ class Parser_LinkHooks extends Parser {
} else {
/** Closing bracket **/
# Pop the start pos for our current link zone off the stack
- $startBracketOffset = array_pop($offsetStack);
+ $startBracketOffset = array_pop( $offsetStack );
# Just to clean up the code, lets place offsets on the outer ends
$endBracketOffset += 2;
-
+
# Only do logic if we actually have a opening bracket for this
- if( isset($startBracketOffset) ) {
+ if( isset( $startBracketOffset ) ) {
# Extract text inside the link
- @list( $titleText, $paramText ) = explode('|',
- substr($s, $startBracketOffset+2, $endBracketOffset-$startBracketOffset-4), 2);
+ @list( $titleText, $paramText ) = explode( '|',
+ substr( $s, $startBracketOffset + 2, $endBracketOffset - $startBracketOffset - 4 ), 2 );
# Create markers only for valid links
if( preg_match( $titleRegex, $titleText ) ) {
# Store the text for the marker
- $marker = $markers->addMarker($titleText, $paramText);
+ $marker = $markers->addMarker( $titleText, $paramText );
# Replace the current link with the marker
- $s = substr($s,0,$startBracketOffset).
- $marker.
- substr($s, $endBracketOffset);
+ $s = substr( $s, 0, $startBracketOffset ) .
+ $marker .
+ substr( $s, $endBracketOffset );
# We have modified $s, because of this we need to set the
# offset manually since the end position is different now
- $offset = $startBracketOffset+strlen($marker);
+ $offset = $startBracketOffset+strlen( $marker );
continue;
}
# ToDo: Some LinkHooks may allow recursive links inside of
@@ -203,55 +206,55 @@ class Parser_LinkHooks extends Parser {
# ToDO: Some LinkHooks use patterns rather than namespaces
# these need to be tested at this point here
}
-
}
# Bump our offset to after our current bracket
$offset = $bracketOffset+2;
}
-
-
+
# Now expand our tree
- wfProfileIn( __METHOD__.'-expand' );
+ wfProfileIn( __METHOD__ . '-expand' );
$s = $markers->expand( $s );
- wfProfileOut( __METHOD__.'-expand' );
-
+ wfProfileOut( __METHOD__ . '-expand' );
+
wfProfileOut( __METHOD__ );
return $holders;
}
-
+
function replaceInternalLinksCallback( $parser, $holders, $markers, $titleText, $paramText ) {
wfProfileIn( __METHOD__ );
- $wt = isset($paramText) ? "[[$titleText|$paramText]]" : "[[$titleText]]";
- wfProfileIn( __METHOD__."-misc" );
+ $wt = isset( $paramText ) ? "[[$titleText|$paramText]]" : "[[$titleText]]";
+ wfProfileIn( __METHOD__ . "-misc" );
+
# Don't allow internal links to pages containing
# PROTO: where PROTO is a valid URL protocol; these
# should be external links.
- if( preg_match('/^\b(?i:' . wfUrlProtocols() . ')/', $titleText) ) {
+ if( preg_match( '/^\b(?i:' . wfUrlProtocols() . ')/', $titleText ) ) {
+ wfProfileOut( __METHOD__ . "-misc" );
wfProfileOut( __METHOD__ );
return $wt;
}
-
+
# Make subpage if necessary
if( $this->areSubpagesAllowed() ) {
$titleText = $this->maybeDoSubpageLink( $titleText, $paramText );
}
-
+
# Check for a leading colon and strip it if it is there
$leadingColon = $titleText[0] == ':';
if( $leadingColon ) $titleText = substr( $titleText, 1 );
-
- wfProfileOut( __METHOD__."-misc" );
+
+ wfProfileOut( __METHOD__ . "-misc" );
# Make title object
- wfProfileIn( __METHOD__."-title" );
+ wfProfileIn( __METHOD__ . "-title" );
$title = Title::newFromText( $this->mStripState->unstripNoWiki( $titleText ) );
if( !$title ) {
- wfProfileOut( __METHOD__."-title" );
+ wfProfileOut( __METHOD__ . "-title" );
wfProfileOut( __METHOD__ );
return $wt;
}
$ns = $title->getNamespace();
- wfProfileOut( __METHOD__."-title" );
-
+ wfProfileOut( __METHOD__ . "-title" );
+
# Default for Namespaces is a default link
# ToDo: Default for patterns is plain wikitext
$return = true;
@@ -270,25 +273,25 @@ class Parser_LinkHooks extends Parser {
}
if( $return === true ) {
# True (treat as plain link) was returned, call the defaultLinkHook
- $return = CoreLinkFunctions::defaultLinkHook( $parser, $holders, $markers, $title,
+ $return = CoreLinkFunctions::defaultLinkHook( $parser, $holders, $markers, $title,
$titleText, $paramText, $leadingColon );
}
if( $return === false ) {
# False (no link) was returned, output plain wikitext
# Build it again as the hook is allowed to modify $paramText
- $return = isset($paramText) ? "[[$titleText|$paramText]]" : "[[$titleText]]";
+ $return = isset( $paramText ) ? "[[$titleText|$paramText]]" : "[[$titleText]]";
}
# Content was returned, return it
wfProfileOut( __METHOD__ );
return $return;
}
-
+
}
class LinkMarkerReplacer {
-
+
protected $markers, $nextId, $parser, $holders, $callback;
-
+
function __construct( $parser, $holders, $callback ) {
$this->nextId = 0;
$this->markers = array();
@@ -296,29 +299,28 @@ class LinkMarkerReplacer {
$this->holders = $holders;
$this->callback = $callback;
}
-
- function addMarker($titleText, $paramText) {
+
+ function addMarker( $titleText, $paramText ) {
$id = $this->nextId++;
$this->markers[$id] = array( $titleText, $paramText );
return "<!-- LINKMARKER $id -->";
}
-
+
function findMarker( $string ) {
- return (bool) preg_match('/<!-- LINKMARKER [0-9]+ -->/', $string );
+ return (bool) preg_match( '/<!-- LINKMARKER [0-9]+ -->/', $string );
}
-
+
function expand( $string ) {
return StringUtils::delimiterReplaceCallback( "<!-- LINKMARKER ", " -->", array( &$this, 'callback' ), $string );
}
-
+
function callback( $m ) {
- $id = intval($m[1]);
- if( !array_key_exists($id, $this->markers) ) return $m[0];
+ $id = intval( $m[1] );
+ if( !array_key_exists( $id, $this->markers ) ) return $m[0];
$args = $this->markers[$id];
array_unshift( $args, $this );
array_unshift( $args, $this->holders );
array_unshift( $args, $this->parser );
return call_user_func_array( $this->callback, $args );
}
-
}
diff --git a/includes/parser/Preprocessor.php b/includes/parser/Preprocessor.php
index bd13f9ae..aeacd2e1 100644
--- a/includes/parser/Preprocessor.php
+++ b/includes/parser/Preprocessor.php
@@ -84,9 +84,9 @@ interface PPFrame {
/**
* Create a child frame
*
- * @param $args array
- * @param $title Title
- * @param $indexOffset A number subtracted from the index attributes of the arguments
+ * @param array $args
+ * @param Title $title
+ * @param int $indexOffset A number subtracted from the index attributes of the arguments
*
* @return PPFrame
*/
@@ -205,7 +205,6 @@ interface PPNode {
*/
function getChildrenOfType( $type );
-
/**
* Returns the length of the array, or false if this is not an array-type node
*/
diff --git a/includes/parser/Preprocessor_DOM.php b/includes/parser/Preprocessor_DOM.php
index 34de0ba5..d0c57ab5 100644
--- a/includes/parser/Preprocessor_DOM.php
+++ b/includes/parser/Preprocessor_DOM.php
@@ -110,7 +110,7 @@ class Preprocessor_DOM implements Preprocessor {
* Preprocess some wikitext and return the document tree.
* This is the ghost of Parser::replace_variables().
*
- * @param $text String: the text to parse
+ * @param string $text the text to parse
* @param $flags Integer: bitwise combination of:
* Parser::PTD_FOR_INCLUSION Handle "<noinclude>" and "<includeonly>" as if the text is being
* included. Default is to assume a direct page view.
@@ -126,6 +126,7 @@ class Preprocessor_DOM implements Preprocessor {
* cache may be implemented at a later date which takes further advantage of these strict
* dependency requirements.
*
+ * @throws MWException
* @return PPNode_DOM
*/
function preprocessToObj( $text, $flags = 0 ) {
@@ -136,9 +137,9 @@ class Preprocessor_DOM implements Preprocessor {
$cacheable = ( $wgPreprocessorCacheThreshold !== false
&& strlen( $text ) > $wgPreprocessorCacheThreshold );
if ( $cacheable ) {
- wfProfileIn( __METHOD__.'-cacheable' );
+ wfProfileIn( __METHOD__ . '-cacheable' );
- $cacheKey = wfMemcKey( 'preprocess-xml', md5($text), $flags );
+ $cacheKey = wfMemcKey( 'preprocess-xml', md5( $text ), $flags );
$cacheValue = $wgMemc->get( $cacheKey );
if ( $cacheValue ) {
$version = substr( $cacheValue, 0, 8 );
@@ -151,11 +152,11 @@ class Preprocessor_DOM implements Preprocessor {
}
if ( $xml === false ) {
if ( $cacheable ) {
- wfProfileIn( __METHOD__.'-cache-miss' );
+ wfProfileIn( __METHOD__ . '-cache-miss' );
$xml = $this->preprocessToXml( $text, $flags );
$cacheValue = sprintf( "%08d", self::CACHE_VERSION ) . $xml;
$wgMemc->set( $cacheKey, $cacheValue, 86400 );
- wfProfileOut( __METHOD__.'-cache-miss' );
+ wfProfileOut( __METHOD__ . '-cache-miss' );
wfDebugLog( "Preprocessor", "Saved preprocessor XML to memcached (key $cacheKey)" );
} else {
$xml = $this->preprocessToXml( $text, $flags );
@@ -164,14 +165,14 @@ class Preprocessor_DOM implements Preprocessor {
}
// Fail if the number of elements exceeds acceptable limits
- // Do not attempt to generate the DOM
+ // Do not attempt to generate the DOM
$this->parser->mGeneratedPPNodeCount += substr_count( $xml, '<' );
$max = $this->parser->mOptions->getMaxGeneratedPPNodeCount();
if ( $this->parser->mGeneratedPPNodeCount > $max ) {
- throw new MWException( __METHOD__.': generated node count limit exceeded' );
+ throw new MWException( __METHOD__ . ': generated node count limit exceeded' );
}
- wfProfileIn( __METHOD__.'-loadXML' );
+ wfProfileIn( __METHOD__ . '-loadXML' );
$dom = new DOMDocument;
wfSuppressWarnings();
$result = $dom->loadXML( $xml );
@@ -182,13 +183,13 @@ class Preprocessor_DOM implements Preprocessor {
// 1 << 19 == XML_PARSE_HUGE, needed so newer versions of libxml2 don't barf when the XML is >256 levels deep
$result = $dom->loadXML( $xml, 1 << 19 );
if ( !$result ) {
- throw new MWException( __METHOD__.' generated invalid XML' );
+ throw new MWException( __METHOD__ . ' generated invalid XML' );
}
}
$obj = new PPNode_DOM( $dom->documentElement );
- wfProfileOut( __METHOD__.'-loadXML' );
+ wfProfileOut( __METHOD__ . '-loadXML' );
if ( $cacheable ) {
- wfProfileOut( __METHOD__.'-cacheable' );
+ wfProfileOut( __METHOD__ . '-cacheable' );
}
wfProfileOut( __METHOD__ );
return $obj;
@@ -396,7 +397,7 @@ class Preprocessor_DOM implements Preprocessor {
if ( $stack->top ) {
$part = $stack->top->getCurrentPart();
- if ( ! (isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 )) {
+ if ( !(isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 )) {
$part->visualEnd = $wsStart;
}
// Else comments abutting, no change in visual end
@@ -521,7 +522,7 @@ class Preprocessor_DOM implements Preprocessor {
if ( $equalsLength > 0 ) {
if ( $searchStart - $equalsLength == $piece->startPos ) {
// This is just a single string of equals signs on its own line
- // Replicate the doHeadings behaviour /={count}(.+)={count}/
+ // Replicate the doHeadings behavior /={count}(.+)={count}/
// First find out how many equals signs there really are (don't stop at 6)
$count = $equalsLength;
if ( $count < 3 ) {
@@ -657,19 +658,13 @@ class Preprocessor_DOM implements Preprocessor {
$piece->parts = array( new PPDPart );
$piece->count -= $matchingCount;
# do we still qualify for any callback with remaining count?
- $names = $rules[$piece->open]['names'];
- $skippedBraces = 0;
- $enclosingAccum =& $accum;
- while ( $piece->count ) {
- if ( array_key_exists( $piece->count, $names ) ) {
- $stack->push( $piece );
- $accum =& $stack->getAccum();
- break;
- }
- --$piece->count;
- $skippedBraces ++;
+ $min = $rules[$piece->open]['min'];
+ if ( $piece->count >= $min ) {
+ $stack->push( $piece );
+ $accum =& $stack->getAccum();
+ } else {
+ $accum .= str_repeat( $piece->open, $piece->count );
}
- $enclosingAccum .= str_repeat( $piece->open, $skippedBraces );
}
$flags = $stack->getFlags();
extract( $flags );
@@ -757,7 +752,7 @@ class PPDStack {
function pop() {
if ( !count( $this->stack ) ) {
- throw new MWException( __METHOD__.': no elements remaining' );
+ throw new MWException( __METHOD__ . ': no elements remaining' );
}
$temp = array_pop( $this->stack );
@@ -796,8 +791,8 @@ class PPDStack {
* @ingroup Parser
*/
class PPDStackElement {
- var $open, // Opening character (\n for heading)
- $close, // Matching closing character
+ var $open, // Opening character (\n for heading)
+ $close, // Matching closing character
$count, // Number of opening characters found (number of "=" for heading)
$parts, // Array of PPDPart objects describing pipe-separated parts.
$lineStart; // True if the open char appeared at the start of the input line. Not set for headings.
@@ -814,7 +809,7 @@ class PPDStackElement {
}
function &getAccum() {
- return $this->parts[count($this->parts) - 1]->out;
+ return $this->parts[count( $this->parts ) - 1]->out;
}
function addPart( $s = '' ) {
@@ -823,7 +818,7 @@ class PPDStackElement {
}
function getCurrentPart() {
- return $this->parts[count($this->parts) - 1];
+ return $this->parts[count( $this->parts ) - 1];
}
/**
@@ -916,7 +911,6 @@ class PPFrame_DOM implements PPFrame {
*/
var $depth;
-
/**
* Construct a new preprocessor frame.
* @param $preprocessor Preprocessor The parent preprocessor
@@ -1117,7 +1111,7 @@ class PPFrame_DOM implements PPFrame {
}
# Add a strip marker in PST mode so that pstPass2() can run some old-fashioned regexes on the result
# Not in RECOVER_COMMENTS mode (extractSections) though
- elseif ( $this->parser->ot['wiki'] && ! ( $flags & PPFrame::RECOVER_COMMENTS ) ) {
+ elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) {
$out .= $this->parser->insertStripItem( $contextNode->textContent );
}
# Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
@@ -1174,7 +1168,7 @@ class PPFrame_DOM implements PPFrame {
}
} else {
wfProfileOut( __METHOD__ );
- throw new MWException( __METHOD__.': Invalid parameter type' );
+ throw new MWException( __METHOD__ . ': Invalid parameter type' );
}
if ( $newIterator !== false ) {
@@ -1458,25 +1452,25 @@ class PPTemplateFrame_DOM extends PPFrame_DOM {
function getArguments() {
$arguments = array();
foreach ( array_merge(
- array_keys($this->numberedArgs),
- array_keys($this->namedArgs)) as $key ) {
- $arguments[$key] = $this->getArgument($key);
+ array_keys( $this->numberedArgs ),
+ array_keys( $this->namedArgs ) ) as $key ) {
+ $arguments[$key] = $this->getArgument( $key );
}
return $arguments;
}
function getNumberedArguments() {
$arguments = array();
- foreach ( array_keys($this->numberedArgs) as $key ) {
- $arguments[$key] = $this->getArgument($key);
+ foreach ( array_keys( $this->numberedArgs ) as $key ) {
+ $arguments[$key] = $this->getArgument( $key );
}
return $arguments;
}
function getNamedArguments() {
$arguments = array();
- foreach ( array_keys($this->namedArgs) as $key ) {
- $arguments[$key] = $this->getArgument($key);
+ foreach ( array_keys( $this->namedArgs ) as $key ) {
+ $arguments[$key] = $this->getArgument( $key );
}
return $arguments;
}
@@ -1673,6 +1667,7 @@ class PPNode_DOM implements PPNode {
* - index String index
* - value PPNode value
*
+ * @throws MWException
* @return array
*/
function splitArg() {
@@ -1694,6 +1689,7 @@ class PPNode_DOM implements PPNode {
* Split an "<ext>" node into an associative array containing name, attr, inner and close
* All values in the resulting array are PPNodes. Inner and close are optional.
*
+ * @throws MWException
* @return array
*/
function splitExt() {
@@ -1719,6 +1715,7 @@ class PPNode_DOM implements PPNode {
/**
* Split a "<h>" node
+ * @throws MWException
* @return array
*/
function splitHeading() {
diff --git a/includes/parser/Preprocessor_Hash.php b/includes/parser/Preprocessor_Hash.php
index 4f04c865..fad1adbb 100644
--- a/includes/parser/Preprocessor_Hash.php
+++ b/includes/parser/Preprocessor_Hash.php
@@ -89,7 +89,7 @@ class Preprocessor_Hash implements Preprocessor {
* Preprocess some wikitext and return the document tree.
* This is the ghost of Parser::replace_variables().
*
- * @param $text String: the text to parse
+ * @param string $text the text to parse
* @param $flags Integer: bitwise combination of:
* Parser::PTD_FOR_INCLUSION Handle "<noinclude>" and "<includeonly>" as if the text is being
* included. Default is to assume a direct page view.
@@ -105,6 +105,7 @@ class Preprocessor_Hash implements Preprocessor {
* cache may be implemented at a later date which takes further advantage of these strict
* dependency requirements.
*
+ * @throws MWException
* @return PPNode_Hash_Tree
*/
function preprocessToObj( $text, $flags = 0 ) {
@@ -115,9 +116,9 @@ class Preprocessor_Hash implements Preprocessor {
$cacheable = $wgPreprocessorCacheThreshold !== false && strlen( $text ) > $wgPreprocessorCacheThreshold;
if ( $cacheable ) {
- wfProfileIn( __METHOD__.'-cacheable' );
+ wfProfileIn( __METHOD__ . '-cacheable' );
- $cacheKey = wfMemcKey( 'preprocess-hash', md5($text), $flags );
+ $cacheKey = wfMemcKey( 'preprocess-hash', md5( $text ), $flags );
$cacheValue = $wgMemc->get( $cacheKey );
if ( $cacheValue ) {
$version = substr( $cacheValue, 0, 8 );
@@ -126,12 +127,12 @@ class Preprocessor_Hash implements Preprocessor {
// From the cache
wfDebugLog( "Preprocessor",
"Loaded preprocessor hash from memcached (key $cacheKey)" );
- wfProfileOut( __METHOD__.'-cacheable' );
+ wfProfileOut( __METHOD__ . '-cacheable' );
wfProfileOut( __METHOD__ );
return $hash;
}
}
- wfProfileIn( __METHOD__.'-cache-miss' );
+ wfProfileIn( __METHOD__ . '-cache-miss' );
}
$rules = array(
@@ -331,7 +332,7 @@ class Preprocessor_Hash implements Preprocessor {
if ( $stack->top ) {
$part = $stack->top->getCurrentPart();
- if ( ! (isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 )) {
+ if ( !(isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 )) {
$part->visualEnd = $wsStart;
}
// Else comments abutting, no change in visual end
@@ -390,7 +391,7 @@ class Preprocessor_Hash implements Preprocessor {
}
// <includeonly> and <noinclude> just become <ignore> tags
if ( in_array( $lowerName, $ignoredElements ) ) {
- $accum->addNodeWithText( 'ignore', substr( $text, $tagStartPos, $i - $tagStartPos ) );
+ $accum->addNodeWithText( 'ignore', substr( $text, $tagStartPos, $i - $tagStartPos ) );
continue;
}
@@ -461,7 +462,7 @@ class Preprocessor_Hash implements Preprocessor {
if ( $equalsLength > 0 ) {
if ( $searchStart - $equalsLength == $piece->startPos ) {
// This is just a single string of equals signs on its own line
- // Replicate the doHeadings behaviour /={count}(.+)={count}/
+ // Replicate the doHeadings behavior /={count}(.+)={count}/
// First find out how many equals signs there really are (don't stop at 6)
$count = $equalsLength;
if ( $count < 3 ) {
@@ -548,7 +549,7 @@ class Preprocessor_Hash implements Preprocessor {
}
}
- if ($matchingCount <= 0) {
+ if ( $matchingCount <= 0 ) {
# No matching element found in callback array
# Output a literal closing brace and continue
$accum->addLiteral( str_repeat( $curChar, $count ) );
@@ -590,10 +591,10 @@ class Preprocessor_Hash implements Preprocessor {
$lastNode = $node;
}
if ( !$node ) {
- throw new MWException( __METHOD__. ': eqpos not found' );
+ throw new MWException( __METHOD__ . ': eqpos not found' );
}
if ( $node->name !== 'equals' ) {
- throw new MWException( __METHOD__ .': eqpos is not equals' );
+ throw new MWException( __METHOD__ . ': eqpos is not equals' );
}
$equalsNode = $node;
@@ -638,23 +639,17 @@ class Preprocessor_Hash implements Preprocessor {
$accum =& $stack->getAccum();
# Re-add the old stack element if it still has unmatched opening characters remaining
- if ($matchingCount < $piece->count) {
+ if ( $matchingCount < $piece->count ) {
$piece->parts = array( new PPDPart_Hash );
$piece->count -= $matchingCount;
# do we still qualify for any callback with remaining count?
- $names = $rules[$piece->open]['names'];
- $skippedBraces = 0;
- $enclosingAccum =& $accum;
- while ( $piece->count ) {
- if ( array_key_exists( $piece->count, $names ) ) {
- $stack->push( $piece );
- $accum =& $stack->getAccum();
- break;
- }
- --$piece->count;
- $skippedBraces ++;
+ $min = $rules[$piece->open]['min'];
+ if ( $piece->count >= $min ) {
+ $stack->push( $piece );
+ $accum =& $stack->getAccum();
+ } else {
+ $accum->addLiteral( str_repeat( $piece->open, $piece->count ) );
}
- $enclosingAccum->addLiteral( str_repeat( $piece->open, $skippedBraces ) );
}
extract( $stack->getFlags() );
@@ -695,11 +690,11 @@ class Preprocessor_Hash implements Preprocessor {
$rootNode->lastChild = $stack->rootAccum->lastNode;
// Cache
- if ($cacheable) {
+ if ( $cacheable ) {
$cacheValue = sprintf( "%08d", self::CACHE_VERSION ) . serialize( $rootNode );
$wgMemc->set( $cacheKey, $cacheValue, 86400 );
- wfProfileOut( __METHOD__.'-cache-miss' );
- wfProfileOut( __METHOD__.'-cacheable' );
+ wfProfileOut( __METHOD__ . '-cache-miss' );
+ wfProfileOut( __METHOD__ . '-cacheable' );
wfDebugLog( "Preprocessor", "Saved preprocessor Hash to memcached (key $cacheKey)" );
}
@@ -866,7 +861,6 @@ class PPFrame_Hash implements PPFrame {
*/
var $depth;
-
/**
* Construct a new preprocessor frame.
* @param $preprocessor Preprocessor: the parent preprocessor
@@ -884,9 +878,11 @@ class PPFrame_Hash implements PPFrame {
* Create a new child frame
* $args is optionally a multi-root PPNode or array containing the template arguments
*
- * @param $args PPNode_Hash_Array|array
+ * @param array|bool|\PPNode_Hash_Array $args PPNode_Hash_Array|array
* @param $title Title|bool
*
+ * @param int $indexOffset
+ * @throws MWException
* @return PPTemplateFrame_Hash
*/
function newChild( $args = false, $title = false, $indexOffset = 0 ) {
@@ -1035,7 +1031,7 @@ class PPFrame_Hash implements PPFrame {
}
# Add a strip marker in PST mode so that pstPass2() can run some old-fashioned regexes on the result
# Not in RECOVER_COMMENTS mode (extractSections) though
- elseif ( $this->parser->ot['wiki'] && ! ( $flags & PPFrame::RECOVER_COMMENTS ) ) {
+ elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) {
$out .= $this->parser->insertStripItem( $contextNode->firstChild->value );
}
# Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
@@ -1082,7 +1078,7 @@ class PPFrame_Hash implements PPFrame {
$newIterator = $contextNode->getChildren();
}
} else {
- throw new MWException( __METHOD__.': Invalid parameter type' );
+ throw new MWException( __METHOD__ . ': Invalid parameter type' );
}
if ( $newIterator !== false ) {
@@ -1371,9 +1367,9 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
function getArguments() {
$arguments = array();
foreach ( array_merge(
- array_keys($this->numberedArgs),
- array_keys($this->namedArgs)) as $key ) {
- $arguments[$key] = $this->getArgument($key);
+ array_keys( $this->numberedArgs ),
+ array_keys( $this->namedArgs ) ) as $key ) {
+ $arguments[$key] = $this->getArgument( $key );
}
return $arguments;
}
@@ -1383,8 +1379,8 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
*/
function getNumberedArguments() {
$arguments = array();
- foreach ( array_keys($this->numberedArgs) as $key ) {
- $arguments[$key] = $this->getArgument($key);
+ foreach ( array_keys( $this->numberedArgs ) as $key ) {
+ $arguments[$key] = $this->getArgument( $key );
}
return $arguments;
}
@@ -1394,8 +1390,8 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
*/
function getNamedArguments() {
$arguments = array();
- foreach ( array_keys($this->namedArgs) as $key ) {
- $arguments[$key] = $this->getArgument($key);
+ foreach ( array_keys( $this->namedArgs ) as $key ) {
+ $arguments[$key] = $this->getArgument( $key );
}
return $arguments;
}
@@ -1609,6 +1605,7 @@ class PPNode_Hash_Tree implements PPNode {
* - index String index
* - value PPNode value
*
+ * @throws MWException
* @return array
*/
function splitArg() {
@@ -1642,6 +1639,7 @@ class PPNode_Hash_Tree implements PPNode {
* Split an "<ext>" node into an associative array containing name, attr, inner and close
* All values in the resulting array are PPNodes. Inner and close are optional.
*
+ * @throws MWException
* @return array
*/
function splitExt() {
@@ -1669,6 +1667,7 @@ class PPNode_Hash_Tree implements PPNode {
/**
* Split an "<h>" node
*
+ * @throws MWException
* @return array
*/
function splitHeading() {
@@ -1695,6 +1694,7 @@ class PPNode_Hash_Tree implements PPNode {
/**
* Split a "<template>" or "<tplarg>" node
*
+ * @throws MWException
* @return array
*/
function splitTemplate() {
diff --git a/includes/parser/Preprocessor_HipHop.hphp b/includes/parser/Preprocessor_HipHop.hphp
deleted file mode 100644
index 8b71a1b5..00000000
--- a/includes/parser/Preprocessor_HipHop.hphp
+++ /dev/null
@@ -1,2013 +0,0 @@
-<?php
-/**
- * A preprocessor optimised for HipHop, using HipHop-specific syntax.
- * vim: ft=php
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Parser
- */
-
-/**
- * @ingroup Parser
- */
-class Preprocessor_HipHop implements Preprocessor {
- /**
- * @var Parser
- */
- var $parser;
-
- const CACHE_VERSION = 1;
-
- /**
- * @param $parser Parser
- */
- function __construct( $parser ) {
- $this->parser = $parser;
- }
-
- /**
- * @return PPFrame_HipHop
- */
- function newFrame() {
- return new PPFrame_HipHop( $this );
- }
-
- /**
- * @param $args array
- * @return PPCustomFrame_HipHop
- */
- function newCustomFrame( $args ) {
- return new PPCustomFrame_HipHop( $this, $args );
- }
-
- /**
- * @param $values array
- * @return PPNode_HipHop_Array
- */
- function newPartNodeArray( $values ) {
- $list = array();
-
- foreach ( $values as $k => $val ) {
- $partNode = new PPNode_HipHop_Tree( 'part' );
- $nameNode = new PPNode_HipHop_Tree( 'name' );
-
- if ( is_int( $k ) ) {
- $nameNode->addChild( new PPNode_HipHop_Attr( 'index', $k ) );
- $partNode->addChild( $nameNode );
- } else {
- $nameNode->addChild( new PPNode_HipHop_Text( $k ) );
- $partNode->addChild( $nameNode );
- $partNode->addChild( new PPNode_HipHop_Text( '=' ) );
- }
-
- $valueNode = new PPNode_HipHop_Tree( 'value' );
- $valueNode->addChild( new PPNode_HipHop_Text( $val ) );
- $partNode->addChild( $valueNode );
-
- $list[] = $partNode;
- }
-
- $node = new PPNode_HipHop_Array( $list );
- return $node;
- }
-
- /**
- * Preprocess some wikitext and return the document tree.
- * This is the ghost of Parser::replace_variables().
- *
- * @param $text String: the text to parse
- * @param $flags Integer: bitwise combination of:
- * Parser::PTD_FOR_INCLUSION Handle <noinclude>/<includeonly> as if the text is being
- * included. Default is to assume a direct page view.
- *
- * The generated DOM tree must depend only on the input text and the flags.
- * The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of bug 4899.
- *
- * Any flag added to the $flags parameter here, or any other parameter liable to cause a
- * change in the DOM tree for a given text, must be passed through the section identifier
- * in the section edit link and thus back to extractSections().
- *
- * The output of this function is currently only cached in process memory, but a persistent
- * cache may be implemented at a later date which takes further advantage of these strict
- * dependency requirements.
- *
- * @throws MWException
- * @return PPNode_HipHop_Tree
- */
- function preprocessToObj( $text, $flags = 0 ) {
- wfProfileIn( __METHOD__ );
-
- // Check cache.
- global $wgMemc, $wgPreprocessorCacheThreshold;
-
- $lengthText = strlen( $text );
-
- $cacheable = ($wgPreprocessorCacheThreshold !== false && $lengthText > $wgPreprocessorCacheThreshold);
- if ( $cacheable ) {
- wfProfileIn( __METHOD__.'-cacheable' );
-
- $cacheKey = strval( wfMemcKey( 'preprocess-hash', md5($text), $flags ) );
- $cacheValue = strval( $wgMemc->get( $cacheKey ) );
- if ( $cacheValue !== '' ) {
- $version = substr( $cacheValue, 0, 8 );
- if ( intval( $version ) == self::CACHE_VERSION ) {
- $hash = unserialize( substr( $cacheValue, 8 ) );
- // From the cache
- wfDebugLog( "Preprocessor",
- "Loaded preprocessor hash from memcached (key $cacheKey)" );
- wfProfileOut( __METHOD__.'-cacheable' );
- wfProfileOut( __METHOD__ );
- return $hash;
- }
- }
- wfProfileIn( __METHOD__.'-cache-miss' );
- }
-
- $rules = array(
- '{' => array(
- 'end' => '}',
- 'names' => array(
- 2 => 'template',
- 3 => 'tplarg',
- ),
- 'min' => 2,
- 'max' => 3,
- ),
- '[' => array(
- 'end' => ']',
- 'names' => array( 2 => 'LITERAL' ),
- 'min' => 2,
- 'max' => 2,
- )
- );
-
- $forInclusion = (bool)( $flags & Parser::PTD_FOR_INCLUSION );
-
- $xmlishElements = (array)$this->parser->getStripList();
- $enableOnlyinclude = false;
- if ( $forInclusion ) {
- $ignoredTags = array( 'includeonly', '/includeonly' );
- $ignoredElements = array( 'noinclude' );
- $xmlishElements[] = 'noinclude';
- if ( strpos( $text, '<onlyinclude>' ) !== false && strpos( $text, '</onlyinclude>' ) !== false ) {
- $enableOnlyinclude = true;
- }
- } else if ( $this->parser->ot['wiki'] ) {
- $ignoredTags = array( 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude', 'includeonly', '/includeonly' );
- $ignoredElements = array();
- } else {
- $ignoredTags = array( 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude' );
- $ignoredElements = array( 'includeonly' );
- $xmlishElements[] = 'includeonly';
- }
- $xmlishRegex = implode( '|', array_merge( $xmlishElements, $ignoredTags ) );
-
- // Use "A" modifier (anchored) instead of "^", because ^ doesn't work with an offset
- $elementsRegex = "~($xmlishRegex)(?:\s|\/>|>)|(!--)~iA";
-
- $stack = new PPDStack_HipHop;
-
- $searchBase = "[{<\n";
- $revText = strrev( $text ); // For fast reverse searches
-
- $i = 0; # Input pointer, starts out pointing to a pseudo-newline before the start
- $accum = $stack->getAccum(); # Current accumulator
- $headingIndex = 1;
- $stackFlags = array(
- 'findPipe' => false, # True to take notice of pipe characters
- 'findEquals' => false, # True to find equals signs in arguments
- 'inHeading' => false, # True if $i is inside a possible heading
- );
- $noMoreGT = false; # True if there are no more greater-than (>) signs right of $i
- $findOnlyinclude = $enableOnlyinclude; # True to ignore all input up to the next <onlyinclude>
- $fakeLineStart = true; # Do a line-start run without outputting an LF character
-
- while ( true ) {
- //$this->memCheck();
-
- if ( $findOnlyinclude ) {
- // Ignore all input up to the next <onlyinclude>
- $variantStartPos = strpos( $text, '<onlyinclude>', $i );
- if ( $variantStartPos === false ) {
- // Ignored section runs to the end
- $accum->addNodeWithText( 'ignore', strval( substr( $text, $i ) ) );
- break;
- }
- $startPos1 = intval( $variantStartPos );
- $tagEndPos = $startPos1 + strlen( '<onlyinclude>' ); // past-the-end
- $accum->addNodeWithText( 'ignore', strval( substr( $text, $i, $tagEndPos - $i ) ) );
- $i = $tagEndPos;
- $findOnlyinclude = false;
- }
-
- if ( $fakeLineStart ) {
- $found = 'line-start';
- $curChar = '';
- } else {
- # Find next opening brace, closing brace or pipe
- $search = $searchBase;
- if ( $stack->top === false ) {
- $currentClosing = '';
- } else {
- $currentClosing = strval( $stack->getTop()->close );
- $search .= $currentClosing;
- }
- if ( $stackFlags['findPipe'] ) {
- $search .= '|';
- }
- if ( $stackFlags['findEquals'] ) {
- // First equals will be for the template
- $search .= '=';
- }
- $rule = null;
- # Output literal section, advance input counter
- $literalLength = intval( strcspn( $text, $search, $i ) );
- if ( $literalLength > 0 ) {
- $accum->addLiteral( strval( substr( $text, $i, $literalLength ) ) );
- $i += $literalLength;
- }
- if ( $i >= $lengthText ) {
- if ( $currentClosing === "\n" ) {
- // Do a past-the-end run to finish off the heading
- $curChar = '';
- $found = 'line-end';
- } else {
- # All done
- break;
- }
- } else {
- $curChar = $text[$i];
- if ( $curChar === '|' ) {
- $found = 'pipe';
- } elseif ( $curChar === '=' ) {
- $found = 'equals';
- } elseif ( $curChar === '<' ) {
- $found = 'angle';
- } elseif ( $curChar === "\n" ) {
- if ( $stackFlags['inHeading'] ) {
- $found = 'line-end';
- } else {
- $found = 'line-start';
- }
- } elseif ( $curChar === $currentClosing ) {
- $found = 'close';
- } elseif ( isset( $rules[$curChar] ) ) {
- $found = 'open';
- $rule = $rules[$curChar];
- } else {
- # Some versions of PHP have a strcspn which stops on null characters
- # Ignore and continue
- ++$i;
- continue;
- }
- }
- }
-
- if ( $found === 'angle' ) {
- $matches = false;
- // Handle </onlyinclude>
- if ( $enableOnlyinclude
- && substr( $text, $i, strlen( '</onlyinclude>' ) ) === '</onlyinclude>' )
- {
- $findOnlyinclude = true;
- continue;
- }
-
- // Determine element name
- if ( !preg_match( $elementsRegex, $text, $matches, 0, $i + 1 ) ) {
- // Element name missing or not listed
- $accum->addLiteral( '<' );
- ++$i;
- continue;
- }
- // Handle comments
- if ( isset( $matches[2] ) && $matches[2] === '!--' ) {
- // To avoid leaving blank lines, when a comment is both preceded
- // and followed by a newline (ignoring spaces), trim leading and
- // trailing spaces and one of the newlines.
-
- // Find the end
- $variantEndPos = strpos( $text, '-->', $i + 4 );
- if ( $variantEndPos === false ) {
- // Unclosed comment in input, runs to end
- $inner = strval( substr( $text, $i ) );
- $accum->addNodeWithText( 'comment', $inner );
- $i = $lengthText;
- } else {
- $endPos = intval( $variantEndPos );
- // Search backwards for leading whitespace
- if ( $i ) {
- $wsStart = $i - intval( strspn( $revText, ' ', $lengthText - $i ) );
- } else {
- $wsStart = 0;
- }
- // Search forwards for trailing whitespace
- // $wsEnd will be the position of the last space (or the '>' if there's none)
- $wsEnd = $endPos + 2 + intval( strspn( $text, ' ', $endPos + 3 ) );
- // Eat the line if possible
- // TODO: This could theoretically be done if $wsStart == 0, i.e. for comments at
- // the overall start. That's not how Sanitizer::removeHTMLcomments() did it, but
- // it's a possible beneficial b/c break.
- if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) === "\n"
- && substr( $text, $wsEnd + 1, 1 ) === "\n" )
- {
- $startPos2 = $wsStart;
- $endPos = $wsEnd + 1;
- // Remove leading whitespace from the end of the accumulator
- // Sanity check first though
- $wsLength = $i - $wsStart;
- if ( $wsLength > 0
- && $accum->lastNode instanceof PPNode_HipHop_Text
- && substr( $accum->lastNode->value, -$wsLength ) === str_repeat( ' ', $wsLength ) )
- {
- $accum->lastNode->value = strval( substr( $accum->lastNode->value, 0, -$wsLength ) );
- }
- // Do a line-start run next time to look for headings after the comment
- $fakeLineStart = true;
- } else {
- // No line to eat, just take the comment itself
- $startPos2 = $i;
- $endPos += 2;
- }
-
- if ( $stack->top ) {
- $part = $stack->getTop()->getCurrentPart();
- if ( ! (isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 )) {
- $part->visualEnd = $wsStart;
- }
- // Else comments abutting, no change in visual end
- $part->commentEnd = $endPos;
- }
- $i = $endPos + 1;
- $inner = strval( substr( $text, $startPos2, $endPos - $startPos2 + 1 ) );
- $accum->addNodeWithText( 'comment', $inner );
- }
- continue;
- }
- $name = strval( $matches[1] );
- $lowerName = strtolower( $name );
- $attrStart = $i + strlen( $name ) + 1;
-
- // Find end of tag
- $variantTagEndPos = $noMoreGT ? false : strpos( $text, '>', $attrStart );
- if ( $variantTagEndPos === false ) {
- // Infinite backtrack
- // Disable tag search to prevent worst-case O(N^2) performance
- $noMoreGT = true;
- $accum->addLiteral( '<' );
- ++$i;
- continue;
- }
- $tagEndPos = intval( $variantTagEndPos );
-
- // Handle ignored tags
- if ( in_array( $lowerName, $ignoredTags ) ) {
- $accum->addNodeWithText( 'ignore', strval( substr( $text, $i, $tagEndPos - $i + 1 ) ) );
- $i = $tagEndPos + 1;
- continue;
- }
-
- $tagStartPos = $i;
- $close = '';
- if ( $text[$tagEndPos-1] === '/' ) {
- // Short end tag
- $attrEnd = $tagEndPos - 1;
- $shortEnd = true;
- $inner = '';
- $i = $tagEndPos + 1;
- $haveClose = false;
- } else {
- $attrEnd = $tagEndPos;
- $shortEnd = false;
- // Find closing tag
- if ( preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i",
- $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) )
- {
- $inner = strval( substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 ) );
- $i = intval( $matches[0][1] ) + strlen( $matches[0][0] );
- $close = strval( $matches[0][0] );
- $haveClose = true;
- } else {
- // No end tag -- let it run out to the end of the text.
- $inner = strval( substr( $text, $tagEndPos + 1 ) );
- $i = $lengthText;
- $haveClose = false;
- }
- }
- // <includeonly> and <noinclude> just become <ignore> tags
- if ( in_array( $lowerName, $ignoredElements ) ) {
- $accum->addNodeWithText( 'ignore', strval( substr( $text, $tagStartPos, $i - $tagStartPos ) ) );
- continue;
- }
-
- if ( $attrEnd <= $attrStart ) {
- $attr = '';
- } else {
- // Note that the attr element contains the whitespace between name and attribute,
- // this is necessary for precise reconstruction during pre-save transform.
- $attr = strval( substr( $text, $attrStart, $attrEnd - $attrStart ) );
- }
-
- $extNode = new PPNode_HipHop_Tree( 'ext' );
- $extNode->addChild( PPNode_HipHop_Tree::newWithText( 'name', $name ) );
- $extNode->addChild( PPNode_HipHop_Tree::newWithText( 'attr', $attr ) );
- if ( !$shortEnd ) {
- $extNode->addChild( PPNode_HipHop_Tree::newWithText( 'inner', $inner ) );
- }
- if ( $haveClose ) {
- $extNode->addChild( PPNode_HipHop_Tree::newWithText( 'close', $close ) );
- }
- $accum->addNode( $extNode );
- }
-
- elseif ( $found === 'line-start' ) {
- // Is this the start of a heading?
- // Line break belongs before the heading element in any case
- if ( $fakeLineStart ) {
- $fakeLineStart = false;
- } else {
- $accum->addLiteral( $curChar );
- $i++;
- }
-
- $count = intval( strspn( $text, '=', $i, 6 ) );
- if ( $count == 1 && $stackFlags['findEquals'] ) {
- // DWIM: This looks kind of like a name/value separator
- // Let's let the equals handler have it and break the potential heading
- // This is heuristic, but AFAICT the methods for completely correct disambiguation are very complex.
- } elseif ( $count > 0 ) {
- $partData = array(
- 'open' => "\n",
- 'close' => "\n",
- 'parts' => array( new PPDPart_HipHop( str_repeat( '=', $count ) ) ),
- 'startPos' => $i,
- 'count' => $count );
- $stack->push( $partData );
- $accum = $stack->getAccum();
- $stackFlags = $stack->getFlags();
- $i += $count;
- }
- } elseif ( $found === 'line-end' ) {
- $piece = $stack->getTop();
- // A heading must be open, otherwise \n wouldn't have been in the search list
- assert( $piece->open === "\n" ); // Passing the assert condition directly instead of string, as
- // HPHP /compiler/ chokes on strings when ASSERT_ACTIVE != 0.
- $part = $piece->getCurrentPart();
- // Search back through the input to see if it has a proper close
- // Do this using the reversed string since the other solutions (end anchor, etc.) are inefficient
- $wsLength = intval( strspn( $revText, " \t", $lengthText - $i ) );
- $searchStart = $i - $wsLength;
- if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) {
- // Comment found at line end
- // Search for equals signs before the comment
- $searchStart = intval( $part->visualEnd );
- $searchStart -= intval( strspn( $revText, " \t", $lengthText - $searchStart ) );
- }
- $count = intval( $piece->count );
- $equalsLength = intval( strspn( $revText, '=', $lengthText - $searchStart ) );
- $isTreeNode = false;
- $resultAccum = $accum;
- if ( $equalsLength > 0 ) {
- if ( $searchStart - $equalsLength == $piece->startPos ) {
- // This is just a single string of equals signs on its own line
- // Replicate the doHeadings behaviour /={count}(.+)={count}/
- // First find out how many equals signs there really are (don't stop at 6)
- $count = $equalsLength;
- if ( $count < 3 ) {
- $count = 0;
- } else {
- $count = intval( ( $count - 1 ) / 2 );
- if ( $count > 6 ) {
- $count = 6;
- }
- }
- } else {
- if ( $count > $equalsLength ) {
- $count = $equalsLength;
- }
- }
- if ( $count > 0 ) {
- // Normal match, output <h>
- $tree = new PPNode_HipHop_Tree( 'possible-h' );
- $tree->addChild( new PPNode_HipHop_Attr( 'level', $count ) );
- $tree->addChild( new PPNode_HipHop_Attr( 'i', $headingIndex++ ) );
- $tree->lastChild->nextSibling = $accum->firstNode;
- $tree->lastChild = $accum->lastNode;
- $isTreeNode = true;
- } else {
- // Single equals sign on its own line, count=0
- // Output $resultAccum
- }
- } else {
- // No match, no <h>, just pass down the inner text
- // Output $resultAccum
- }
- // Unwind the stack
- $stack->pop();
- $accum = $stack->getAccum();
- $stackFlags = $stack->getFlags();
-
- // Append the result to the enclosing accumulator
- if ( $isTreeNode ) {
- $accum->addNode( $tree );
- } else {
- $accum->addAccum( $resultAccum );
- }
- // Note that we do NOT increment the input pointer.
- // This is because the closing linebreak could be the opening linebreak of
- // another heading. Infinite loops are avoided because the next iteration MUST
- // hit the heading open case above, which unconditionally increments the
- // input pointer.
- } elseif ( $found === 'open' ) {
- # count opening brace characters
- $count = intval( strspn( $text, $curChar, $i ) );
-
- # we need to add to stack only if opening brace count is enough for one of the rules
- if ( $count >= $rule['min'] ) {
- # Add it to the stack
- $partData = array(
- 'open' => $curChar,
- 'close' => $rule['end'],
- 'count' => $count,
- 'lineStart' => ($i == 0 || $text[$i-1] === "\n"),
- );
-
- $stack->push( $partData );
- $accum = $stack->getAccum();
- $stackFlags = $stack->getFlags();
- } else {
- # Add literal brace(s)
- $accum->addLiteral( str_repeat( $curChar, $count ) );
- }
- $i += $count;
- } elseif ( $found === 'close' ) {
- $piece = $stack->getTop();
- # lets check if there are enough characters for closing brace
- $maxCount = intval( $piece->count );
- $count = intval( strspn( $text, $curChar, $i, $maxCount ) );
-
- # check for maximum matching characters (if there are 5 closing
- # characters, we will probably need only 3 - depending on the rules)
- $rule = $rules[$piece->open];
- if ( $count > $rule['max'] ) {
- # The specified maximum exists in the callback array, unless the caller
- # has made an error
- $matchingCount = intval( $rule['max'] );
- } else {
- # Count is less than the maximum
- # Skip any gaps in the callback array to find the true largest match
- # Need to use array_key_exists not isset because the callback can be null
- $matchingCount = $count;
- while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $rule['names'] ) ) {
- --$matchingCount;
- }
- }
-
- if ($matchingCount <= 0) {
- # No matching element found in callback array
- # Output a literal closing brace and continue
- $accum->addLiteral( str_repeat( $curChar, $count ) );
- $i += $count;
- continue;
- }
- $name = strval( $rule['names'][$matchingCount] );
- $isTreeNode = false;
- if ( $name === 'LITERAL' ) {
- // No element, just literal text
- $resultAccum = $piece->breakSyntax( $matchingCount );
- $resultAccum->addLiteral( str_repeat( $rule['end'], $matchingCount ) );
- } else {
- # Create XML element
- # Note: $parts is already XML, does not need to be encoded further
- $isTreeNode = true;
- $parts = $piece->parts;
- $titleAccum = PPDAccum_HipHop::cast( $parts[0]->out );
- unset( $parts[0] );
-
- $tree = new PPNode_HipHop_Tree( $name );
-
- # The invocation is at the start of the line if lineStart is set in
- # the stack, and all opening brackets are used up.
- if ( $maxCount == $matchingCount && !empty( $piece->lineStart ) ) {
- $tree->addChild( new PPNode_HipHop_Attr( 'lineStart', 1 ) );
- }
- $titleNode = new PPNode_HipHop_Tree( 'title' );
- $titleNode->firstChild = $titleAccum->firstNode;
- $titleNode->lastChild = $titleAccum->lastNode;
- $tree->addChild( $titleNode );
- $argIndex = 1;
- foreach ( $parts as $variantPart ) {
- $part = PPDPart_HipHop::cast( $variantPart );
- if ( isset( $part->eqpos ) ) {
- // Find equals
- $lastNode = false;
- for ( $node = $part->out->firstNode; $node; $node = $node->nextSibling ) {
- if ( $node === $part->eqpos ) {
- break;
- }
- $lastNode = $node;
- }
- if ( !$node ) {
- throw new MWException( __METHOD__. ': eqpos not found' );
- }
- if ( $node->name !== 'equals' ) {
- throw new MWException( __METHOD__ .': eqpos is not equals' );
- }
- $equalsNode = $node;
-
- // Construct name node
- $nameNode = new PPNode_HipHop_Tree( 'name' );
- if ( $lastNode !== false ) {
- $lastNode->nextSibling = false;
- $nameNode->firstChild = $part->out->firstNode;
- $nameNode->lastChild = $lastNode;
- }
-
- // Construct value node
- $valueNode = new PPNode_HipHop_Tree( 'value' );
- if ( $equalsNode->nextSibling !== false ) {
- $valueNode->firstChild = $equalsNode->nextSibling;
- $valueNode->lastChild = $part->out->lastNode;
- }
- $partNode = new PPNode_HipHop_Tree( 'part' );
- $partNode->addChild( $nameNode );
- $partNode->addChild( $equalsNode->firstChild );
- $partNode->addChild( $valueNode );
- $tree->addChild( $partNode );
- } else {
- $partNode = new PPNode_HipHop_Tree( 'part' );
- $nameNode = new PPNode_HipHop_Tree( 'name' );
- $nameNode->addChild( new PPNode_HipHop_Attr( 'index', $argIndex++ ) );
- $valueNode = new PPNode_HipHop_Tree( 'value' );
- $valueNode->firstChild = $part->out->firstNode;
- $valueNode->lastChild = $part->out->lastNode;
- $partNode->addChild( $nameNode );
- $partNode->addChild( $valueNode );
- $tree->addChild( $partNode );
- }
- }
- }
-
- # Advance input pointer
- $i += $matchingCount;
-
- # Unwind the stack
- $stack->pop();
- $accum = $stack->getAccum();
-
- # Re-add the old stack element if it still has unmatched opening characters remaining
- if ($matchingCount < $piece->count) {
- $piece->parts = array( new PPDPart_HipHop );
- $piece->count -= $matchingCount;
- # do we still qualify for any callback with remaining count?
- $names = $rules[$piece->open]['names'];
- $skippedBraces = 0;
- $enclosingAccum = $accum;
- while ( $piece->count ) {
- if ( array_key_exists( $piece->count, $names ) ) {
- $stack->push( $piece );
- $accum = $stack->getAccum();
- break;
- }
- --$piece->count;
- $skippedBraces ++;
- }
- $enclosingAccum->addLiteral( str_repeat( $piece->open, $skippedBraces ) );
- }
-
- $stackFlags = $stack->getFlags();
-
- # Add XML element to the enclosing accumulator
- if ( $isTreeNode ) {
- $accum->addNode( $tree );
- } else {
- $accum->addAccum( $resultAccum );
- }
- } elseif ( $found === 'pipe' ) {
- $stackFlags['findEquals'] = true; // shortcut for getFlags()
- $stack->addPart();
- $accum = $stack->getAccum();
- ++$i;
- } elseif ( $found === 'equals' ) {
- $stackFlags['findEquals'] = false; // shortcut for getFlags()
- $accum->addNodeWithText( 'equals', '=' );
- $stack->getCurrentPart()->eqpos = $accum->lastNode;
- ++$i;
- }
- }
-
- # Output any remaining unclosed brackets
- foreach ( $stack->stack as $variantPiece ) {
- $piece = PPDStackElement_HipHop::cast( $variantPiece );
- $stack->rootAccum->addAccum( $piece->breakSyntax() );
- }
-
- # Enable top-level headings
- for ( $node = $stack->rootAccum->firstNode; $node; $node = $node->nextSibling ) {
- if ( isset( $node->name ) && $node->name === 'possible-h' ) {
- $node->name = 'h';
- }
- }
-
- $rootNode = new PPNode_HipHop_Tree( 'root' );
- $rootNode->firstChild = $stack->rootAccum->firstNode;
- $rootNode->lastChild = $stack->rootAccum->lastNode;
-
- // Cache
- if ($cacheable) {
- $cacheValue = sprintf( "%08d", self::CACHE_VERSION ) . serialize( $rootNode );
- $wgMemc->set( $cacheKey, $cacheValue, 86400 );
- wfProfileOut( __METHOD__.'-cache-miss' );
- wfProfileOut( __METHOD__.'-cacheable' );
- wfDebugLog( "Preprocessor", "Saved preprocessor Hash to memcached (key $cacheKey)" );
- }
-
- wfProfileOut( __METHOD__ );
- return $rootNode;
- }
-}
-
-
-
-/**
- * Stack class to help Preprocessor::preprocessToObj()
- * @ingroup Parser
- */
-class PPDStack_HipHop {
- var $stack, $rootAccum;
-
- /**
- * @var PPDStack
- */
- var $top;
- var $out;
-
- static $false = false;
-
- function __construct() {
- $this->stack = array();
- $this->top = false;
- $this->rootAccum = new PPDAccum_HipHop;
- $this->accum = $this->rootAccum;
- }
-
- /**
- * @return int
- */
- function count() {
- return count( $this->stack );
- }
-
- function getAccum() {
- return PPDAccum_HipHop::cast( $this->accum );
- }
-
- function getCurrentPart() {
- return $this->getTop()->getCurrentPart();
- }
-
- function getTop() {
- return PPDStackElement_HipHop::cast( $this->top );
- }
-
- function push( $data ) {
- if ( $data instanceof PPDStackElement_HipHop ) {
- $this->stack[] = $data;
- } else {
- $this->stack[] = new PPDStackElement_HipHop( $data );
- }
- $this->top = $this->stack[ count( $this->stack ) - 1 ];
- $this->accum = $this->top->getAccum();
- }
-
- function pop() {
- if ( !count( $this->stack ) ) {
- throw new MWException( __METHOD__.': no elements remaining' );
- }
- $temp = array_pop( $this->stack );
-
- if ( count( $this->stack ) ) {
- $this->top = $this->stack[ count( $this->stack ) - 1 ];
- $this->accum = $this->top->getAccum();
- } else {
- $this->top = self::$false;
- $this->accum = $this->rootAccum;
- }
- return $temp;
- }
-
- function addPart( $s = '' ) {
- $this->top->addPart( $s );
- $this->accum = $this->top->getAccum();
- }
-
- /**
- * @return array
- */
- function getFlags() {
- if ( !count( $this->stack ) ) {
- return array(
- 'findEquals' => false,
- 'findPipe' => false,
- 'inHeading' => false,
- );
- } else {
- return $this->top->getFlags();
- }
- }
-}
-
-/**
- * @ingroup Parser
- */
-class PPDStackElement_HipHop {
- var $open, // Opening character (\n for heading)
- $close, // Matching closing character
- $count, // Number of opening characters found (number of "=" for heading)
- $parts, // Array of PPDPart objects describing pipe-separated parts.
- $lineStart; // True if the open char appeared at the start of the input line. Not set for headings.
-
- /**
- * @param $obj PPDStackElement_HipHop
- * @return PPDStackElement_HipHop
- */
- static function cast( PPDStackElement_HipHop $obj ) {
- return $obj;
- }
-
- /**
- * @param $data array
- */
- function __construct( $data = array() ) {
- $this->parts = array( new PPDPart_HipHop );
-
- foreach ( $data as $name => $value ) {
- $this->$name = $value;
- }
- }
-
- /**
- * @return PPDAccum_HipHop
- */
- function getAccum() {
- return PPDAccum_HipHop::cast( $this->parts[count($this->parts) - 1]->out );
- }
-
- /**
- * @param $s string
- */
- function addPart( $s = '' ) {
- $this->parts[] = new PPDPart_HipHop( $s );
- }
-
- /**
- * @return PPDPart_HipHop
- */
- function getCurrentPart() {
- return PPDPart_HipHop::cast( $this->parts[count($this->parts) - 1] );
- }
-
- /**
- * @return array
- */
- function getFlags() {
- $partCount = count( $this->parts );
- $findPipe = $this->open !== "\n" && $this->open !== '[';
- return array(
- 'findPipe' => $findPipe,
- 'findEquals' => $findPipe && $partCount > 1 && !isset( $this->parts[$partCount - 1]->eqpos ),
- 'inHeading' => $this->open === "\n",
- );
- }
-
- /**
- * Get the accumulator that would result if the close is not found.
- *
- * @param $openingCount bool
- * @return PPDAccum_HipHop
- */
- function breakSyntax( $openingCount = false ) {
- if ( $this->open === "\n" ) {
- $accum = PPDAccum_HipHop::cast( $this->parts[0]->out );
- } else {
- if ( $openingCount === false ) {
- $openingCount = $this->count;
- }
- $accum = new PPDAccum_HipHop;
- $accum->addLiteral( str_repeat( $this->open, $openingCount ) );
- $first = true;
- foreach ( $this->parts as $part ) {
- if ( $first ) {
- $first = false;
- } else {
- $accum->addLiteral( '|' );
- }
- $accum->addAccum( $part->out );
- }
- }
- return $accum;
- }
-}
-
-/**
- * @ingroup Parser
- */
-class PPDPart_HipHop {
- var $out; // Output accumulator object
-
- // Optional member variables:
- // eqpos Position of equals sign in output accumulator
- // commentEnd Past-the-end input pointer for the last comment encountered
- // visualEnd Past-the-end input pointer for the end of the accumulator minus comments
-
- function __construct( $out = '' ) {
- $this->out = new PPDAccum_HipHop;
- if ( $out !== '' ) {
- $this->out->addLiteral( $out );
- }
- }
-
- static function cast( PPDPart_HipHop $obj ) {
- return $obj;
- }
-}
-
-/**
- * @ingroup Parser
- */
-class PPDAccum_HipHop {
- var $firstNode, $lastNode;
-
- function __construct() {
- $this->firstNode = $this->lastNode = false;
- }
-
- static function cast( PPDAccum_HipHop $obj ) {
- return $obj;
- }
-
- /**
- * Append a string literal
- */
- function addLiteral( string $s ) {
- if ( $this->lastNode === false ) {
- $this->firstNode = $this->lastNode = new PPNode_HipHop_Text( $s );
- } elseif ( $this->lastNode instanceof PPNode_HipHop_Text ) {
- $this->lastNode->value .= $s;
- } else {
- $this->lastNode->nextSibling = new PPNode_HipHop_Text( $s );
- $this->lastNode = $this->lastNode->nextSibling;
- }
- }
-
- /**
- * Append a PPNode
- */
- function addNode( PPNode $node ) {
- if ( $this->lastNode === false ) {
- $this->firstNode = $this->lastNode = $node;
- } else {
- $this->lastNode->nextSibling = $node;
- $this->lastNode = $node;
- }
- }
-
- /**
- * Append a tree node with text contents
- */
- function addNodeWithText( string $name, string $value ) {
- $node = PPNode_HipHop_Tree::newWithText( $name, $value );
- $this->addNode( $node );
- }
-
- /**
- * Append a PPDAccum_HipHop
- * Takes over ownership of the nodes in the source argument. These nodes may
- * subsequently be modified, especially nextSibling.
- */
- function addAccum( PPDAccum_HipHop $accum ) {
- if ( $accum->lastNode === false ) {
- // nothing to add
- } elseif ( $this->lastNode === false ) {
- $this->firstNode = $accum->firstNode;
- $this->lastNode = $accum->lastNode;
- } else {
- $this->lastNode->nextSibling = $accum->firstNode;
- $this->lastNode = $accum->lastNode;
- }
- }
-}
-
-/**
- * An expansion frame, used as a context to expand the result of preprocessToObj()
- * @ingroup Parser
- */
-class PPFrame_HipHop implements PPFrame {
-
- /**
- * @var Parser
- */
- var $parser;
-
- /**
- * @var Preprocessor
- */
- var $preprocessor;
-
- /**
- * @var Title
- */
- var $title;
- var $titleCache;
-
- /**
- * Hashtable listing templates which are disallowed for expansion in this frame,
- * having been encountered previously in parent frames.
- */
- var $loopCheckHash;
-
- /**
- * Recursion depth of this frame, top = 0
- * Note that this is NOT the same as expansion depth in expand()
- */
- var $depth;
-
- /**
- * Construct a new preprocessor frame.
- * @param $preprocessor Preprocessor: the parent preprocessor
- */
- function __construct( $preprocessor ) {
- $this->preprocessor = $preprocessor;
- $this->parser = $preprocessor->parser;
- $this->title = $this->parser->mTitle;
- $this->titleCache = array( $this->title ? $this->title->getPrefixedDBkey() : false );
- $this->loopCheckHash = array();
- $this->depth = 0;
- }
-
- /**
- * Create a new child frame
- * $args is optionally a multi-root PPNode or array containing the template arguments
- *
- * @param $args PPNode_HipHop_Array|array|bool
- * @param $title Title|bool
- * @param $indexOffset A number subtracted from the index attributes of the arguments
- *
- * @throws MWException
- * @return PPTemplateFrame_HipHop
- */
- function newChild( $args = false, $title = false, $indexOffset = 0 ) {
- $namedArgs = array();
- $numberedArgs = array();
- if ( $title === false ) {
- $title = $this->title;
- }
- if ( $args !== false ) {
- if ( $args instanceof PPNode_HipHop_Array ) {
- $args = $args->value;
- } elseif ( !is_array( $args ) ) {
- throw new MWException( __METHOD__ . ': $args must be array or PPNode_HipHop_Array' );
- }
- foreach ( $args as $arg ) {
- $bits = $arg->splitArg();
- if ( $bits['index'] !== '' ) {
- // Numbered parameter
- $numberedArgs[$bits['index']] = $bits['value'];
- unset( $namedArgs[$bits['index']] );
- } else {
- // Named parameter
- $name = trim( $this->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
- $namedArgs[$name] = $bits['value'];
- unset( $numberedArgs[$name] );
- }
- }
- }
- return new PPTemplateFrame_HipHop( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
- }
-
- /**
- * @throws MWException
- * @param $root
- * @param $flags int
- * @return string
- */
- function expand( $root, $flags = 0 ) {
- static $expansionDepth = 0;
- if ( is_string( $root ) ) {
- return $root;
- }
-
- if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) {
- $this->parser->limitationWarn( 'node-count-exceeded',
- $this->parser->mPPNodeCount,
- $this->parser->mOptions->getMaxPPNodeCount()
- );
- return '<span class="error">Node-count limit exceeded</span>';
- }
- if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) {
- $this->parser->limitationWarn( 'expansion-depth-exceeded',
- $expansionDepth,
- $this->parser->mOptions->getMaxPPExpandDepth()
- );
- return '<span class="error">Expansion depth limit exceeded</span>';
- }
- ++$expansionDepth;
- if ( $expansionDepth > $this->parser->mHighestExpansionDepth ) {
- $this->parser->mHighestExpansionDepth = $expansionDepth;
- }
-
- $outStack = array( '', '' );
- $iteratorStack = array( false, $root );
- $indexStack = array( 0, 0 );
-
- while ( count( $iteratorStack ) > 1 ) {
- $level = count( $outStack ) - 1;
- $iteratorNode =& $iteratorStack[ $level ];
- $out =& $outStack[$level];
- $index =& $indexStack[$level];
-
- if ( is_array( $iteratorNode ) ) {
- if ( $index >= count( $iteratorNode ) ) {
- // All done with this iterator
- $iteratorStack[$level] = false;
- $contextNode = false;
- } else {
- $contextNode = $iteratorNode[$index];
- $index++;
- }
- } elseif ( $iteratorNode instanceof PPNode_HipHop_Array ) {
- if ( $index >= $iteratorNode->getLength() ) {
- // All done with this iterator
- $iteratorStack[$level] = false;
- $contextNode = false;
- } else {
- $contextNode = $iteratorNode->item( $index );
- $index++;
- }
- } else {
- // Copy to $contextNode and then delete from iterator stack,
- // because this is not an iterator but we do have to execute it once
- $contextNode = $iteratorStack[$level];
- $iteratorStack[$level] = false;
- }
-
- $newIterator = false;
-
- if ( $contextNode === false ) {
- // nothing to do
- } elseif ( is_string( $contextNode ) ) {
- $out .= $contextNode;
- } elseif ( is_array( $contextNode ) || $contextNode instanceof PPNode_HipHop_Array ) {
- $newIterator = $contextNode;
- } elseif ( $contextNode instanceof PPNode_HipHop_Attr ) {
- // No output
- } elseif ( $contextNode instanceof PPNode_HipHop_Text ) {
- $out .= $contextNode->value;
- } elseif ( $contextNode instanceof PPNode_HipHop_Tree ) {
- if ( $contextNode->name === 'template' ) {
- # Double-brace expansion
- $bits = $contextNode->splitTemplate();
- if ( $flags & PPFrame::NO_TEMPLATES ) {
- $newIterator = $this->virtualBracketedImplode( '{{', '|', '}}', $bits['title'], $bits['parts'] );
- } else {
- $ret = $this->parser->braceSubstitution( $bits, $this );
- if ( isset( $ret['object'] ) ) {
- $newIterator = $ret['object'];
- } else {
- $out .= $ret['text'];
- }
- }
- } elseif ( $contextNode->name === 'tplarg' ) {
- # Triple-brace expansion
- $bits = $contextNode->splitTemplate();
- if ( $flags & PPFrame::NO_ARGS ) {
- $newIterator = $this->virtualBracketedImplode( '{{{', '|', '}}}', $bits['title'], $bits['parts'] );
- } else {
- $ret = $this->parser->argSubstitution( $bits, $this );
- if ( isset( $ret['object'] ) ) {
- $newIterator = $ret['object'];
- } else {
- $out .= $ret['text'];
- }
- }
- } elseif ( $contextNode->name === 'comment' ) {
- # HTML-style comment
- # Remove it in HTML, pre+remove and STRIP_COMMENTS modes
- if ( $this->parser->ot['html']
- || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
- || ( $flags & PPFrame::STRIP_COMMENTS ) )
- {
- $out .= '';
- }
- # Add a strip marker in PST mode so that pstPass2() can run some old-fashioned regexes on the result
- # Not in RECOVER_COMMENTS mode (extractSections) though
- elseif ( $this->parser->ot['wiki'] && ! ( $flags & PPFrame::RECOVER_COMMENTS ) ) {
- $out .= $this->parser->insertStripItem( $contextNode->firstChild->value );
- }
- # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
- else {
- $out .= $contextNode->firstChild->value;
- }
- } elseif ( $contextNode->name === 'ignore' ) {
- # Output suppression used by <includeonly> etc.
- # OT_WIKI will only respect <ignore> in substed templates.
- # The other output types respect it unless NO_IGNORE is set.
- # extractSections() sets NO_IGNORE and so never respects it.
- if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) || ( $flags & PPFrame::NO_IGNORE ) ) {
- $out .= $contextNode->firstChild->value;
- } else {
- //$out .= '';
- }
- } elseif ( $contextNode->name === 'ext' ) {
- # Extension tag
- $bits = $contextNode->splitExt() + array( 'attr' => null, 'inner' => null, 'close' => null );
- $out .= $this->parser->extensionSubstitution( $bits, $this );
- } elseif ( $contextNode->name === 'h' ) {
- # Heading
- if ( $this->parser->ot['html'] ) {
- # Expand immediately and insert heading index marker
- $s = '';
- for ( $node = $contextNode->firstChild; $node; $node = $node->nextSibling ) {
- $s .= $this->expand( $node, $flags );
- }
-
- $bits = $contextNode->splitHeading();
- $titleText = $this->title->getPrefixedDBkey();
- $this->parser->mHeadings[] = array( $titleText, $bits['i'] );
- $serial = count( $this->parser->mHeadings ) - 1;
- $marker = "{$this->parser->mUniqPrefix}-h-$serial-" . Parser::MARKER_SUFFIX;
- $s = substr( $s, 0, $bits['level'] ) . $marker . substr( $s, $bits['level'] );
- $this->parser->mStripState->addGeneral( $marker, '' );
- $out .= $s;
- } else {
- # Expand in virtual stack
- $newIterator = $contextNode->getChildren();
- }
- } else {
- # Generic recursive expansion
- $newIterator = $contextNode->getChildren();
- }
- } else {
- throw new MWException( __METHOD__.': Invalid parameter type' );
- }
-
- if ( $newIterator !== false ) {
- $outStack[] = '';
- $iteratorStack[] = $newIterator;
- $indexStack[] = 0;
- } elseif ( $iteratorStack[$level] === false ) {
- // Return accumulated value to parent
- // With tail recursion
- while ( $iteratorStack[$level] === false && $level > 0 ) {
- $outStack[$level - 1] .= $out;
- array_pop( $outStack );
- array_pop( $iteratorStack );
- array_pop( $indexStack );
- $level--;
- }
- }
- }
- --$expansionDepth;
- return $outStack[0];
- }
-
- /**
- * @param $sep
- * @param $flags
- * @return string
- */
- function implodeWithFlags( $sep, $flags /*, ... */ ) {
- $args = array_slice( func_get_args(), 2 );
-
- $first = true;
- $s = '';
- foreach ( $args as $root ) {
- if ( $root instanceof PPNode_HipHop_Array ) {
- $root = $root->value;
- }
- if ( !is_array( $root ) ) {
- $root = array( $root );
- }
- foreach ( $root as $node ) {
- if ( $first ) {
- $first = false;
- } else {
- $s .= $sep;
- }
- $s .= $this->expand( $node, $flags );
- }
- }
- return $s;
- }
-
- /**
- * Implode with no flags specified
- * This previously called implodeWithFlags but has now been inlined to reduce stack depth
- * @param $sep
- * @return string
- */
- function implode( $sep /*, ... */ ) {
- $args = array_slice( func_get_args(), 1 );
-
- $first = true;
- $s = '';
- foreach ( $args as $root ) {
- if ( $root instanceof PPNode_HipHop_Array ) {
- $root = $root->value;
- }
- if ( !is_array( $root ) ) {
- $root = array( $root );
- }
- foreach ( $root as $node ) {
- if ( $first ) {
- $first = false;
- } else {
- $s .= $sep;
- }
- $s .= $this->expand( $node );
- }
- }
- return $s;
- }
-
- /**
- * Makes an object that, when expand()ed, will be the same as one obtained
- * with implode()
- *
- * @param $sep
- * @return PPNode_HipHop_Array
- */
- function virtualImplode( $sep /*, ... */ ) {
- $args = array_slice( func_get_args(), 1 );
- $out = array();
- $first = true;
-
- foreach ( $args as $root ) {
- if ( $root instanceof PPNode_HipHop_Array ) {
- $root = $root->value;
- }
- if ( !is_array( $root ) ) {
- $root = array( $root );
- }
- foreach ( $root as $node ) {
- if ( $first ) {
- $first = false;
- } else {
- $out[] = $sep;
- }
- $out[] = $node;
- }
- }
- return new PPNode_HipHop_Array( $out );
- }
-
- /**
- * Virtual implode with brackets
- *
- * @param $start
- * @param $sep
- * @param $end
- * @return PPNode_HipHop_Array
- */
- function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) {
- $args = array_slice( func_get_args(), 3 );
- $out = array( $start );
- $first = true;
-
- foreach ( $args as $root ) {
- if ( $root instanceof PPNode_HipHop_Array ) {
- $root = $root->value;
- }
- if ( !is_array( $root ) ) {
- $root = array( $root );
- }
- foreach ( $root as $node ) {
- if ( $first ) {
- $first = false;
- } else {
- $out[] = $sep;
- }
- $out[] = $node;
- }
- }
- $out[] = $end;
- return new PPNode_HipHop_Array( $out );
- }
-
- function __toString() {
- return 'frame{}';
- }
-
- /**
- * @param $level bool
- * @return array|bool|String
- */
- function getPDBK( $level = false ) {
- if ( $level === false ) {
- return $this->title->getPrefixedDBkey();
- } else {
- return isset( $this->titleCache[$level] ) ? $this->titleCache[$level] : false;
- }
- }
-
- /**
- * @return array
- */
- function getArguments() {
- return array();
- }
-
- /**
- * @return array
- */
- function getNumberedArguments() {
- return array();
- }
-
- /**
- * @return array
- */
- function getNamedArguments() {
- return array();
- }
-
- /**
- * Returns true if there are no arguments in this frame
- *
- * @return bool
- */
- function isEmpty() {
- return true;
- }
-
- /**
- * @param $name
- * @return bool
- */
- function getArgument( $name ) {
- return false;
- }
-
- /**
- * Returns true if the infinite loop check is OK, false if a loop is detected
- *
- * @param $title Title
- *
- * @return bool
- */
- function loopCheck( $title ) {
- return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
- }
-
- /**
- * Return true if the frame is a template frame
- *
- * @return bool
- */
- function isTemplate() {
- return false;
- }
-
- /**
- * Get a title of frame
- *
- * @return Title
- */
- function getTitle() {
- return $this->title;
- }
-}
-
-/**
- * Expansion frame with template arguments
- * @ingroup Parser
- */
-class PPTemplateFrame_HipHop extends PPFrame_HipHop {
- var $numberedArgs, $namedArgs, $parent;
- var $numberedExpansionCache, $namedExpansionCache;
-
- /**
- * @param $preprocessor Preprocessor_HipHop
- * @param $parent bool
- * @param $numberedArgs array
- * @param $namedArgs array
- * @param $title Title|bool
- */
- function __construct( $preprocessor, $parent = false, $numberedArgs = array(), $namedArgs = array(), $title = false ) {
- parent::__construct( $preprocessor );
-
- $this->parent = $parent;
- $this->numberedArgs = $numberedArgs;
- $this->namedArgs = $namedArgs;
- $this->title = $title;
- $pdbk = $title ? $title->getPrefixedDBkey() : false;
- $this->titleCache = $parent->titleCache;
- $this->titleCache[] = $pdbk;
- $this->loopCheckHash = /*clone*/ $parent->loopCheckHash;
- if ( $pdbk !== false ) {
- $this->loopCheckHash[$pdbk] = true;
- }
- $this->depth = $parent->depth + 1;
- $this->numberedExpansionCache = $this->namedExpansionCache = array();
- }
-
- function __toString() {
- $s = 'tplframe{';
- $first = true;
- $args = $this->numberedArgs + $this->namedArgs;
- foreach ( $args as $name => $value ) {
- if ( $first ) {
- $first = false;
- } else {
- $s .= ', ';
- }
- $s .= "\"$name\":\"" .
- str_replace( '"', '\\"', $value->__toString() ) . '"';
- }
- $s .= '}';
- return $s;
- }
- /**
- * Returns true if there are no arguments in this frame
- *
- * @return bool
- */
- function isEmpty() {
- return !count( $this->numberedArgs ) && !count( $this->namedArgs );
- }
-
- /**
- * @return array
- */
- function getArguments() {
- $arguments = array();
- foreach ( array_merge(
- array_keys($this->numberedArgs),
- array_keys($this->namedArgs)) as $key ) {
- $arguments[$key] = $this->getArgument($key);
- }
- return $arguments;
- }
-
- /**
- * @return array
- */
- function getNumberedArguments() {
- $arguments = array();
- foreach ( array_keys($this->numberedArgs) as $key ) {
- $arguments[$key] = $this->getArgument($key);
- }
- return $arguments;
- }
-
- /**
- * @return array
- */
- function getNamedArguments() {
- $arguments = array();
- foreach ( array_keys($this->namedArgs) as $key ) {
- $arguments[$key] = $this->getArgument($key);
- }
- return $arguments;
- }
-
- /**
- * @param $index
- * @return array|bool
- */
- function getNumberedArgument( $index ) {
- if ( !isset( $this->numberedArgs[$index] ) ) {
- return false;
- }
- if ( !isset( $this->numberedExpansionCache[$index] ) ) {
- # No trimming for unnamed arguments
- $this->numberedExpansionCache[$index] = $this->parent->expand( $this->numberedArgs[$index], PPFrame::STRIP_COMMENTS );
- }
- return $this->numberedExpansionCache[$index];
- }
-
- /**
- * @param $name
- * @return bool
- */
- function getNamedArgument( $name ) {
- if ( !isset( $this->namedArgs[$name] ) ) {
- return false;
- }
- if ( !isset( $this->namedExpansionCache[$name] ) ) {
- # Trim named arguments post-expand, for backwards compatibility
- $this->namedExpansionCache[$name] = trim(
- $this->parent->expand( $this->namedArgs[$name], PPFrame::STRIP_COMMENTS ) );
- }
- return $this->namedExpansionCache[$name];
- }
-
- /**
- * @param $name
- * @return array|bool
- */
- function getArgument( $name ) {
- $text = $this->getNumberedArgument( $name );
- if ( $text === false ) {
- $text = $this->getNamedArgument( $name );
- }
- return $text;
- }
-
- /**
- * Return true if the frame is a template frame
- *
- * @return bool
- */
- function isTemplate() {
- return true;
- }
-}
-
-/**
- * Expansion frame with custom arguments
- * @ingroup Parser
- */
-class PPCustomFrame_HipHop extends PPFrame_HipHop {
- var $args;
-
- function __construct( $preprocessor, $args ) {
- parent::__construct( $preprocessor );
- $this->args = $args;
- }
-
- function __toString() {
- $s = 'cstmframe{';
- $first = true;
- foreach ( $this->args as $name => $value ) {
- if ( $first ) {
- $first = false;
- } else {
- $s .= ', ';
- }
- $s .= "\"$name\":\"" .
- str_replace( '"', '\\"', $value->__toString() ) . '"';
- }
- $s .= '}';
- return $s;
- }
-
- /**
- * @return bool
- */
- function isEmpty() {
- return !count( $this->args );
- }
-
- /**
- * @param $index
- * @return bool
- */
- function getArgument( $index ) {
- if ( !isset( $this->args[$index] ) ) {
- return false;
- }
- return $this->args[$index];
- }
-}
-
-/**
- * @ingroup Parser
- */
-class PPNode_HipHop_Tree implements PPNode {
- var $name, $firstChild, $lastChild, $nextSibling;
-
- function __construct( $name ) {
- $this->name = $name;
- $this->firstChild = $this->lastChild = $this->nextSibling = false;
- }
-
- function __toString() {
- $inner = '';
- $attribs = '';
- for ( $node = $this->firstChild; $node; $node = $node->nextSibling ) {
- if ( $node instanceof PPNode_HipHop_Attr ) {
- $attribs .= ' ' . $node->name . '="' . htmlspecialchars( $node->value ) . '"';
- } else {
- $inner .= $node->__toString();
- }
- }
- if ( $inner === '' ) {
- return "<{$this->name}$attribs/>";
- } else {
- return "<{$this->name}$attribs>$inner</{$this->name}>";
- }
- }
-
- /**
- * @param $name
- * @param $text
- * @return PPNode_HipHop_Tree
- */
- static function newWithText( $name, $text ) {
- $obj = new self( $name );
- $obj->addChild( new PPNode_HipHop_Text( $text ) );
- return $obj;
- }
-
- function addChild( $node ) {
- if ( $this->lastChild === false ) {
- $this->firstChild = $this->lastChild = $node;
- } else {
- $this->lastChild->nextSibling = $node;
- $this->lastChild = $node;
- }
- }
-
- /**
- * @return PPNode_HipHop_Array
- */
- function getChildren() {
- $children = array();
- for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
- $children[] = $child;
- }
- return new PPNode_HipHop_Array( $children );
- }
-
- function getFirstChild() {
- return $this->firstChild;
- }
-
- function getNextSibling() {
- return $this->nextSibling;
- }
-
- /**
- * @param $name string
- * @return array
- */
- function getChildrenOfType( $name ) {
- $children = array();
- for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
- if ( isset( $child->name ) && $child->name === $name ) {
- $children[] = $child;
- }
- }
- return $children;
- }
-
- /**
- * @return bool
- */
- function getLength() {
- return false;
- }
-
- /**
- * @param $i
- * @return bool
- */
- function item( $i ) {
- return false;
- }
-
- /**
- * @return string
- */
- function getName() {
- return $this->name;
- }
-
- /**
- * Split a <part> node into an associative array containing:
- * name PPNode name
- * index String index
- * value PPNode value
- *
- * @throws MWException
- * @return array
- */
- function splitArg() {
- $bits = array();
- for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
- if ( !isset( $child->name ) ) {
- continue;
- }
- if ( $child->name === 'name' ) {
- $bits['name'] = $child;
- if ( $child->firstChild instanceof PPNode_HipHop_Attr
- && $child->firstChild->name === 'index' )
- {
- $bits['index'] = $child->firstChild->value;
- }
- } elseif ( $child->name === 'value' ) {
- $bits['value'] = $child;
- }
- }
-
- if ( !isset( $bits['name'] ) ) {
- throw new MWException( 'Invalid brace node passed to ' . __METHOD__ );
- }
- if ( !isset( $bits['index'] ) ) {
- $bits['index'] = '';
- }
- return $bits;
- }
-
- /**
- * Split an <ext> node into an associative array containing name, attr, inner and close
- * All values in the resulting array are PPNodes. Inner and close are optional.
- *
- * @throws MWException
- * @return array
- */
- function splitExt() {
- $bits = array();
- for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
- if ( !isset( $child->name ) ) {
- continue;
- }
- if ( $child->name === 'name' ) {
- $bits['name'] = $child;
- } elseif ( $child->name === 'attr' ) {
- $bits['attr'] = $child;
- } elseif ( $child->name === 'inner' ) {
- $bits['inner'] = $child;
- } elseif ( $child->name === 'close' ) {
- $bits['close'] = $child;
- }
- }
- if ( !isset( $bits['name'] ) ) {
- throw new MWException( 'Invalid ext node passed to ' . __METHOD__ );
- }
- return $bits;
- }
-
- /**
- * Split an <h> node
- *
- * @throws MWException
- * @return array
- */
- function splitHeading() {
- if ( $this->name !== 'h' ) {
- throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
- }
- $bits = array();
- for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
- if ( !isset( $child->name ) ) {
- continue;
- }
- if ( $child->name === 'i' ) {
- $bits['i'] = $child->value;
- } elseif ( $child->name === 'level' ) {
- $bits['level'] = $child->value;
- }
- }
- if ( !isset( $bits['i'] ) ) {
- throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
- }
- return $bits;
- }
-
- /**
- * Split a <template> or <tplarg> node
- *
- * @return array
- */
- function splitTemplate() {
- $parts = array();
- $bits = array( 'lineStart' => '' );
- for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
- if ( !isset( $child->name ) ) {
- continue;
- }
- if ( $child->name === 'title' ) {
- $bits['title'] = $child;
- }
- if ( $child->name === 'part' ) {
- $parts[] = $child;
- }
- if ( $child->name === 'lineStart' ) {
- $bits['lineStart'] = '1';
- }
- }
- if ( !isset( $bits['title'] ) ) {
- throw new MWException( 'Invalid node passed to ' . __METHOD__ );
- }
- $bits['parts'] = new PPNode_HipHop_Array( $parts );
- return $bits;
- }
-}
-
-/**
- * @ingroup Parser
- */
-class PPNode_HipHop_Text implements PPNode {
- var $value, $nextSibling;
-
- function __construct( $value ) {
- if ( is_object( $value ) ) {
- throw new MWException( __CLASS__ . ' given object instead of string' );
- }
- $this->value = $value;
- }
-
- function __toString() {
- return htmlspecialchars( $this->value );
- }
-
- function getNextSibling() {
- return $this->nextSibling;
- }
-
- function getChildren() { return false; }
- function getFirstChild() { return false; }
- function getChildrenOfType( $name ) { return false; }
- function getLength() { return false; }
- function item( $i ) { return false; }
- function getName() { return '#text'; }
- function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); }
- function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); }
- function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); }
-}
-
-/**
- * @ingroup Parser
- */
-class PPNode_HipHop_Array implements PPNode {
- var $value, $nextSibling;
-
- function __construct( $value ) {
- $this->value = $value;
- }
-
- function __toString() {
- return var_export( $this, true );
- }
-
- function getLength() {
- return count( $this->value );
- }
-
- function item( $i ) {
- return $this->value[$i];
- }
-
- function getName() { return '#nodelist'; }
-
- function getNextSibling() {
- return $this->nextSibling;
- }
-
- function getChildren() { return false; }
- function getFirstChild() { return false; }
- function getChildrenOfType( $name ) { return false; }
- function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); }
- function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); }
- function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); }
-}
-
-/**
- * @ingroup Parser
- */
-class PPNode_HipHop_Attr implements PPNode {
- var $name, $value, $nextSibling;
-
- function __construct( $name, $value ) {
- $this->name = $name;
- $this->value = $value;
- }
-
- function __toString() {
- return "<@{$this->name}>" . htmlspecialchars( $this->value ) . "</@{$this->name}>";
- }
-
- function getName() {
- return $this->name;
- }
-
- function getNextSibling() {
- return $this->nextSibling;
- }
-
- function getChildren() { return false; }
- function getFirstChild() { return false; }
- function getChildrenOfType( $name ) { return false; }
- function getLength() { return false; }
- function item( $i ) { return false; }
- function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); }
- function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); }
- function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); }
-}
diff --git a/includes/parser/StripState.php b/includes/parser/StripState.php
index ad95d5f7..5f3f18ea 100644
--- a/includes/parser/StripState.php
+++ b/includes/parser/StripState.php
@@ -112,7 +112,7 @@ class StripState {
* @return mixed
*/
protected function unstripType( $type, $text ) {
- // Shortcut
+ // Shortcut
if ( !count( $this->data[$type] ) ) {
return $text;
}
@@ -139,7 +139,7 @@ class StripState {
. '</span>';
}
if ( $this->recursionLevel >= self::UNSTRIP_RECURSION_LIMIT ) {
- return '<span class="error">' .
+ return '<span class="error">' .
wfMessage( 'parser-unstrip-recursion-limit' )
->numParams( self::UNSTRIP_RECURSION_LIMIT )->inContentLanguage()->text() .
'</span>';
@@ -156,7 +156,7 @@ class StripState {
}
/**
- * Get a StripState object which is sufficient to unstrip the given text.
+ * Get a StripState object which is sufficient to unstrip the given text.
* It will contain the minimum subset of strip items necessary.
*
* @param $text string
@@ -233,4 +233,3 @@ class StripState {
return preg_replace( $this->regex, '', $text );
}
}
-
diff --git a/includes/parser/Tidy.php b/includes/parser/Tidy.php
index ed2d436d..0f7e0d31 100644
--- a/includes/parser/Tidy.php
+++ b/includes/parser/Tidy.php
@@ -59,12 +59,18 @@ class MWTidyWrapper {
dechex( mt_rand( 0, 0x7fffffff ) ) . dechex( mt_rand( 0, 0x7fffffff ) );
$this->mMarkerIndex = 0;
+ // Replace <mw:editsection> elements with placeholders
$wrappedtext = preg_replace_callback( ParserOutput::EDITSECTION_REGEX,
array( &$this, 'replaceEditSectionLinksCallback' ), $text );
- $wrappedtext = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'.
- ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html>'.
- '<head><title>test</title></head><body>'.$wrappedtext.'</body></html>';
+ // Modify inline Microdata <link> and <meta> elements so they say <html-link> and <html-meta> so
+ // we can trick Tidy into not stripping them out by including them in tidy's new-empty-tags config
+ $wrappedtext = preg_replace( '!<(link|meta)([^>]*?)(/{0,1}>)!', '<html-$1$2$3', $wrappedtext );
+
+ // Wrap the whole thing in a doctype and body for Tidy.
+ $wrappedtext = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"' .
+ ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html>' .
+ '<head><title>test</title></head><body>' . $wrappedtext . '</body></html>';
return $wrappedtext;
}
@@ -86,7 +92,13 @@ class MWTidyWrapper {
* @return string
*/
public function postprocess( $text ) {
- return $this->mTokens->replace( $text );
+ // Revert <html-{link,meta}> back to <{link,meta}>
+ $text = preg_replace( '!<html-(link|meta)([^>]*?)(/{0,1}>)!', '<$1$2$3', $text );
+
+ // Restore the contents of placeholder tokens
+ $text = $this->mTokens->replace( $text );
+
+ return $text;
}
}
@@ -106,7 +118,7 @@ class MWTidy {
* If tidy isn't able to correct the markup, the original will be
* returned in all its glory with a warning comment appended.
*
- * @param $text String: hideous HTML input
+ * @param string $text hideous HTML input
* @return String: corrected HTML output
*/
public static function tidy( $text ) {
@@ -159,7 +171,7 @@ class MWTidy {
* Spawn an external HTML tidy process and get corrected markup back from it.
* Also called in OutputHandler.php for full page validation
*
- * @param $text String: HTML to check
+ * @param string $text HTML to check
* @param $stderr Boolean: Whether to read result from STDERR rather than STDOUT
* @param &$retval int Exit code (-1 on internal error)
* @return mixed String or null
@@ -223,7 +235,7 @@ class MWTidy {
* Use the HTML tidy extension to use the tidy library in-process,
* saving the overhead of spawning a new process.
*
- * @param $text String: HTML to check
+ * @param string $text HTML to check
* @param $stderr Boolean: Whether to read result from error status instead of output
* @param &$retval int Exit code (-1 on internal error)
* @return mixed String or null
@@ -248,24 +260,24 @@ class MWTidy {
wfProfileOut( __METHOD__ );
return $tidy->errorBuffer;
+ }
+
+ $tidy->cleanRepair();
+ $retval = $tidy->getStatus();
+ if ( $retval == 2 ) {
+ // 2 is magic number for fatal error
+ // http://www.php.net/manual/en/function.tidy-get-status.php
+ $cleansource = null;
} else {
- $tidy->cleanRepair();
- $retval = $tidy->getStatus();
- if ( $retval == 2 ) {
- // 2 is magic number for fatal error
- // http://www.php.net/manual/en/function.tidy-get-status.php
- $cleansource = null;
- } else {
- $cleansource = tidy_get_output( $tidy );
- if ( $wgDebugTidy && $retval > 0 ) {
- $cleansource .= "<!--\nTidy reports:\n" .
- str_replace( '-->', '--&gt;', $tidy->errorBuffer ) .
- "\n-->";
- }
+ $cleansource = tidy_get_output( $tidy );
+ if ( $wgDebugTidy && $retval > 0 ) {
+ $cleansource .= "<!--\nTidy reports:\n" .
+ str_replace( '-->', '--&gt;', $tidy->errorBuffer ) .
+ "\n-->";
}
-
- wfProfileOut( __METHOD__ );
- return $cleansource;
}
+
+ wfProfileOut( __METHOD__ );
+ return $cleansource;
}
}
diff --git a/includes/profiler/Profiler.php b/includes/profiler/Profiler.php
index 62be39e4..5ecdc4f0 100644
--- a/includes/profiler/Profiler.php
+++ b/includes/profiler/Profiler.php
@@ -28,7 +28,7 @@
/**
* Begin profiling of a function
- * @param $functionname String: name of the function we will profile
+ * @param string $functionname name of the function we will profile
*/
function wfProfileIn( $functionname ) {
global $wgProfiler;
@@ -39,7 +39,7 @@ function wfProfileIn( $functionname ) {
/**
* Stop profiling of a function
- * @param $functionname String: name of the function we have profiled
+ * @param string $functionname name of the function we have profiled
*/
function wfProfileOut( $functionname = 'missing' ) {
global $wgProfiler;
@@ -157,7 +157,7 @@ class Profiler {
*/
public function profileIn( $functionname ) {
global $wgDebugFunctionEntry;
- if( $wgDebugFunctionEntry ){
+ if( $wgDebugFunctionEntry ) {
$this->debug( str_repeat( ' ', count( $this->mWorkStack ) ) . 'Entering ' . $functionname . "\n" );
}
@@ -174,22 +174,22 @@ class Profiler {
$memory = memory_get_usage();
$time = $this->getTime();
- if( $wgDebugFunctionEntry ){
+ if( $wgDebugFunctionEntry ) {
$this->debug( str_repeat( ' ', count( $this->mWorkStack ) - 1 ) . 'Exiting ' . $functionname . "\n" );
}
- $bit = array_pop($this->mWorkStack);
+ $bit = array_pop( $this->mWorkStack );
- if (!$bit) {
- $this->debug("Profiling error, !\$bit: $functionname\n");
+ if ( !$bit ) {
+ $this->debug( "Profiling error, !\$bit: $functionname\n" );
} else {
- //if( $wgDebugProfiling ){
- if( $functionname == 'close' ){
+ //if( $wgDebugProfiling ) {
+ if( $functionname == 'close' ) {
$message = "Profile section ended by close(): {$bit[0]}";
$this->debug( "$message\n" );
$this->mStack[] = array( $message, 0, 0.0, 0, 0.0, 0 );
}
- elseif( $bit[0] != $functionname ){
+ elseif( $bit[0] != $functionname ) {
$message = "Profiling error: in({$bit[0]}), out($functionname)";
$this->debug( "$message\n" );
$this->mStack[] = array( $message, 0, 0.0, 0, 0.0, 0 );
@@ -205,7 +205,7 @@ class Profiler {
* Close opened profiling sections
*/
public function close() {
- while( count( $this->mWorkStack ) ){
+ while( count( $this->mWorkStack ) ) {
$this->profileOut( 'close' );
}
}
@@ -228,7 +228,7 @@ class Profiler {
global $wgDebugFunctionEntry, $wgProfileCallTree;
$wgDebugFunctionEntry = false;
- if( !count( $this->mStack ) && !count( $this->mCollated ) ){
+ if( !count( $this->mStack ) && !count( $this->mCollated ) ) {
return "No profiling output\n";
}
@@ -250,20 +250,20 @@ class Profiler {
/**
* Recursive function the format the current profiling array into a tree
*
- * @param $stack array profiling array
+ * @param array $stack profiling array
* @return array
*/
function remapCallTree( $stack ) {
- if( count( $stack ) < 2 ){
+ if( count( $stack ) < 2 ) {
return $stack;
}
$outputs = array ();
- for( $max = count( $stack ) - 1; $max > 0; ){
+ for( $max = count( $stack ) - 1; $max > 0; ) {
/* Find all items under this entry */
$level = $stack[$max][1];
$working = array ();
- for( $i = $max -1; $i >= 0; $i-- ){
- if( $stack[$i][1] > $level ){
+ for( $i = $max -1; $i >= 0; $i-- ) {
+ if( $stack[$i][1] > $level ) {
$working[] = $stack[$i];
} else {
break;
@@ -271,7 +271,7 @@ class Profiler {
}
$working = $this->remapCallTree( array_reverse( $working ) );
$output = array();
- foreach( $working as $item ){
+ foreach( $working as $item ) {
array_push( $output, $item );
}
array_unshift( $output, $stack[$max] );
@@ -280,8 +280,8 @@ class Profiler {
array_unshift( $outputs, $output );
}
$final = array();
- foreach( $outputs as $output ){
- foreach( $output as $item ){
+ foreach( $outputs as $output ) {
+ foreach( $output as $item ) {
$final[] = $item;
}
}
@@ -293,9 +293,9 @@ class Profiler {
* @return string
*/
function getCallTreeLine( $entry ) {
- list( $fname, $level, $start, /* $x */, $end) = $entry;
+ list( $fname, $level, $start, /* $x */, $end ) = $entry;
$delta = $end - $start;
- $space = str_repeat(' ', $level);
+ $space = str_repeat( ' ', $level );
# The ugly double sprintf is to work around a PHP bug,
# which has been fixed in recent releases.
return sprintf( "%10s %s %s\n", trim( sprintf( "%7.3f", $delta * 1000.0 ) ), $space, $fname );
@@ -305,7 +305,7 @@ class Profiler {
* Get the initial time of the request, based either on $wgRequestTime or
* $wgRUstart. Will return null if not able to find data.
*
- * @param $metric string|false: metric to use, with the following possibilities:
+ * @param string|false $metric metric to use, with the following possibilities:
* - user: User CPU time (without system calls)
* - cpu: Total CPU time (user and system calls)
* - wall (or any other string): elapsed time
@@ -338,7 +338,7 @@ class Profiler {
* Get the initial time of the request, based either on $wgRequestTime or
* $wgRUstart. Will return null if not able to find data.
*
- * @param $metric string|false: metric to use, with the following possibilities:
+ * @param string|false $metric metric to use, with the following possibilities:
* - user: User CPU time (without system calls)
* - cpu: Total CPU time (user and system calls)
* - wall (or any other string): elapsed time
@@ -386,23 +386,23 @@ class Profiler {
$this->mMemory = array();
# Estimate profiling overhead
- $profileCount = count($this->mStack);
+ $profileCount = count( $this->mStack );
self::calculateOverhead( $profileCount );
# First, subtract the overhead!
$overheadTotal = $overheadMemory = $overheadInternal = array();
- foreach( $this->mStack as $entry ){
+ foreach( $this->mStack as $entry ) {
$fname = $entry[0];
$start = $entry[2];
$end = $entry[4];
$elapsed = $end - $start;
$memory = $entry[5] - $entry[3];
- if( $fname == '-overhead-total' ){
+ if( $fname == '-overhead-total' ) {
$overheadTotal[] = $elapsed;
$overheadMemory[] = $memory;
}
- elseif( $fname == '-overhead-internal' ){
+ elseif( $fname == '-overhead-internal' ) {
$overheadInternal[] = $elapsed;
}
}
@@ -411,7 +411,7 @@ class Profiler {
$overheadInternal = $overheadInternal ? array_sum( $overheadInternal ) / count( $overheadInternal ) : 0;
# Collate
- foreach( $this->mStack as $index => $entry ){
+ foreach( $this->mStack as $index => $entry ) {
$fname = $entry[0];
$start = $entry[2];
$end = $entry[4];
@@ -420,7 +420,7 @@ class Profiler {
$memory = $entry[5] - $entry[3];
$subcalls = $this->calltreeCount( $this->mStack, $index );
- if( !preg_match( '/^-overhead/', $fname ) ){
+ if( !preg_match( '/^-overhead/', $fname ) ) {
# Adjust for profiling overhead (except special values with elapsed=0
if( $elapsed ) {
$elapsed -= $overheadInternal;
@@ -429,7 +429,7 @@ class Profiler {
}
}
- if( !array_key_exists( $fname, $this->mCollated ) ){
+ if( !array_key_exists( $fname, $this->mCollated ) ) {
$this->mCollated[$fname] = 0;
$this->mCalls[$fname] = 0;
$this->mMemory[$fname] = 0;
@@ -441,8 +441,8 @@ class Profiler {
$this->mCollated[$fname] += $elapsed;
$this->mCalls[$fname]++;
$this->mMemory[$fname] += $memory;
- $this->mMin[$fname] = min($this->mMin[$fname], $elapsed);
- $this->mMax[$fname] = max($this->mMax[$fname], $elapsed);
+ $this->mMin[$fname] = min( $this->mMin[$fname], $elapsed );
+ $this->mMax[$fname] = max( $this->mMax[$fname], $elapsed );
$this->mOverhead[$fname] += $subcalls;
}
@@ -460,18 +460,18 @@ class Profiler {
$width = 140;
$nameWidth = $width - 65;
- $format = "%-{$nameWidth}s %6d %13.3f %13.3f %13.3f%% %9d (%13.3f -%13.3f) [%d]\n";
+ $format = "%-{$nameWidth}s %6d %13.3f %13.3f %13.3f%% %9d (%13.3f -%13.3f) [%d]\n";
$titleFormat = "%-{$nameWidth}s %6s %13s %13s %13s %9s\n";
$prof = "\nProfiling data\n";
$prof .= sprintf( $titleFormat, 'Name', 'Calls', 'Total', 'Each', '%', 'Mem' );
$total = isset( $this->mCollated['-total'] ) ? $this->mCollated['-total'] : 0;
- foreach( $this->mCollated as $fname => $elapsed ){
+ foreach( $this->mCollated as $fname => $elapsed ) {
$calls = $this->mCalls[$fname];
$percent = $total ? 100. * $elapsed / $total : 0;
$memory = $this->mMemory[$fname];
- $prof .= sprintf($format, substr($fname, 0, $nameWidth), $calls, (float) ($elapsed * 1000), (float) ($elapsed * 1000) / $calls, $percent, $memory, ($this->mMin[$fname] * 1000.0), ($this->mMax[$fname] * 1000.0), $this->mOverhead[$fname]);
+ $prof .= sprintf( $format, substr( $fname, 0, $nameWidth ), $calls, (float) ($elapsed * 1000), (float) ($elapsed * 1000) / $calls, $percent, $memory, ( $this->mMin[$fname] * 1000.0 ), ( $this->mMax[$fname] * 1000.0 ), $this->mOverhead[$fname] );
}
$prof .= "\nTotal: $total\n\n";
@@ -483,7 +483,7 @@ class Profiler {
*/
protected static function calculateOverhead( $profileCount ) {
wfProfileIn( '-overhead-total' );
- for( $i = 0; $i < $profileCount; $i++ ){
+ for( $i = 0; $i < $profileCount; $i++ ) {
wfProfileIn( '-overhead-internal' );
wfProfileOut( '-overhead-internal' );
}
@@ -499,10 +499,10 @@ class Profiler {
* @return Integer
* @private
*/
- function calltreeCount($stack, $start) {
+ function calltreeCount( $stack, $start ) {
$level = $stack[$start][1];
$count = 0;
- for ($i = $start -1; $i >= 0 && $stack[$i][1] > $level; $i --) {
+ for ( $i = $start -1; $i >= 0 && $stack[$i][1] > $level; $i-- ) {
$count ++;
}
return $count;
@@ -511,7 +511,7 @@ class Profiler {
/**
* Log the whole profiling data into the database.
*/
- public function logData(){
+ public function logData() {
global $wgProfilePerHost, $wgProfileToDatabase;
# Do not log anything if database is readonly (bug 5375)
@@ -524,52 +524,50 @@ class Profiler {
return;
}
- $errorState = $dbw->ignoreErrors( true );
-
- if( $wgProfilePerHost ){
+ if( $wgProfilePerHost ) {
$pfhost = wfHostname();
} else {
$pfhost = '';
}
- $this->collateData();
-
- foreach( $this->mCollated as $name => $elapsed ){
- $eventCount = $this->mCalls[$name];
- $timeSum = (float) ($elapsed * 1000);
- $memorySum = (float)$this->mMemory[$name];
- $name = substr($name, 0, 255);
-
- // Kludge
- $timeSum = ($timeSum >= 0) ? $timeSum : 0;
- $memorySum = ($memorySum >= 0) ? $memorySum : 0;
-
- $dbw->update( 'profiling',
- array(
- "pf_count=pf_count+{$eventCount}",
- "pf_time=pf_time+{$timeSum}",
- "pf_memory=pf_memory+{$memorySum}",
- ),
- array(
- 'pf_name' => $name,
- 'pf_server' => $pfhost,
- ),
- __METHOD__ );
-
- $rc = $dbw->affectedRows();
- if ( $rc == 0 ) {
- $dbw->insert('profiling', array ('pf_name' => $name, 'pf_count' => $eventCount,
- 'pf_time' => $timeSum, 'pf_memory' => $memorySum, 'pf_server' => $pfhost ),
- __METHOD__, array ('IGNORE'));
+ try {
+ $this->collateData();
+
+ foreach( $this->mCollated as $name => $elapsed ) {
+ $eventCount = $this->mCalls[$name];
+ $timeSum = (float) ($elapsed * 1000);
+ $memorySum = (float)$this->mMemory[$name];
+ $name = substr($name, 0, 255);
+
+ // Kludge
+ $timeSum = ($timeSum >= 0) ? $timeSum : 0;
+ $memorySum = ($memorySum >= 0) ? $memorySum : 0;
+
+ $dbw->update( 'profiling',
+ array(
+ "pf_count=pf_count+{$eventCount}",
+ "pf_time=pf_time+{$timeSum}",
+ "pf_memory=pf_memory+{$memorySum}",
+ ),
+ array(
+ 'pf_name' => $name,
+ 'pf_server' => $pfhost,
+ ),
+ __METHOD__ );
+
+ $rc = $dbw->affectedRows();
+ if ( $rc == 0 ) {
+ $dbw->insert( 'profiling', array ( 'pf_name' => $name, 'pf_count' => $eventCount,
+ 'pf_time' => $timeSum, 'pf_memory' => $memorySum, 'pf_server' => $pfhost ),
+ __METHOD__, array ( 'IGNORE' ) );
+ }
+ // When we upgrade to mysql 4.1, the insert+update
+ // can be merged into just a insert with this construct added:
+ // "ON DUPLICATE KEY UPDATE ".
+ // "pf_count=pf_count + VALUES(pf_count), ".
+ // "pf_time=pf_time + VALUES(pf_time)";
}
- // When we upgrade to mysql 4.1, the insert+update
- // can be merged into just a insert with this construct added:
- // "ON DUPLICATE KEY UPDATE ".
- // "pf_count=pf_count + VALUES(pf_count), ".
- // "pf_time=pf_time + VALUES(pf_time)";
- }
-
- $dbw->ignoreErrors( $errorState );
+ } catch ( DBError $e ) {}
}
/**
@@ -584,11 +582,25 @@ class Profiler {
/**
* Add an entry in the debug log file
*
- * @param $s String to output
+ * @param string $s to output
*/
function debug( $s ) {
if( defined( 'MW_COMPILED' ) || function_exists( 'wfDebug' ) ) {
wfDebug( $s );
}
}
+
+ /**
+ * Get the content type sent out to the client.
+ * Used for profilers that output instead of store data.
+ * @return string
+ */
+ protected function getContentType() {
+ foreach ( headers_list() as $header ) {
+ if ( preg_match( '#^content-type: (\w+/\w+);?#i', $header, $m ) ) {
+ return $m[1];
+ }
+ }
+ return null;
+ }
}
diff --git a/includes/profiler/ProfilerSimple.php b/includes/profiler/ProfilerSimple.php
index d1d1c5d9..1d4873c6 100644
--- a/includes/profiler/ProfilerSimple.php
+++ b/includes/profiler/ProfilerSimple.php
@@ -29,7 +29,7 @@
class ProfilerSimple extends Profiler {
var $mMinimumTime = 0;
- var $zeroEntry = array('cpu'=> 0.0, 'cpu_sq' => 0.0, 'real' => 0.0, 'real_sq' => 0.0, 'count' => 0);
+ var $zeroEntry = array( 'cpu' => 0.0, 'cpu_sq' => 0.0, 'real' => 0.0, 'real_sq' => 0.0, 'count' => 0 );
var $errorEntry;
public function isPersistent() {
@@ -57,33 +57,33 @@ class ProfilerSimple extends Profiler {
$this->mMinimumTime = $min;
}
- function profileIn($functionname) {
+ function profileIn( $functionname ) {
global $wgDebugFunctionEntry;
- if ($wgDebugFunctionEntry) {
- $this->debug(str_repeat(' ', count($this->mWorkStack)).'Entering '.$functionname."\n");
+ if ( $wgDebugFunctionEntry ) {
+ $this->debug( str_repeat( ' ', count( $this->mWorkStack ) ) . 'Entering ' . $functionname . "\n" );
}
$this->mWorkStack[] = array( $functionname, count( $this->mWorkStack ), $this->getTime(), $this->getTime( 'cpu' ) );
}
- function profileOut($functionname) {
+ function profileOut( $functionname ) {
global $wgDebugFunctionEntry;
- if ($wgDebugFunctionEntry) {
- $this->debug(str_repeat(' ', count($this->mWorkStack) - 1).'Exiting '.$functionname."\n");
+ if ( $wgDebugFunctionEntry ) {
+ $this->debug( str_repeat( ' ', count( $this->mWorkStack ) - 1 ) . 'Exiting ' . $functionname . "\n" );
}
- list($ofname, /* $ocount */ ,$ortime,$octime) = array_pop($this->mWorkStack);
+ list( $ofname, /* $ocount */, $ortime, $octime ) = array_pop( $this->mWorkStack );
- if (!$ofname) {
- $this->debug("Profiling error: $functionname\n");
+ if ( !$ofname ) {
+ $this->debug( "Profiling error: $functionname\n" );
} else {
- if ($functionname == 'close') {
+ if ( $functionname == 'close' ) {
$message = "Profile section ended by close(): {$ofname}";
$functionname = $ofname;
$this->debug( "$message\n" );
$this->mCollated[$message] = $this->errorEntry;
}
- elseif ($ofname != $functionname) {
+ elseif ( $ofname != $functionname ) {
$message = "Profiling error: in({$ofname}), out($functionname)";
$this->debug( "$message\n" );
$this->mCollated[$message] = $this->errorEntry;
@@ -91,7 +91,7 @@ class ProfilerSimple extends Profiler {
$entry =& $this->mCollated[$functionname];
$elapsedcpu = $this->getTime( 'cpu' ) - $octime;
$elapsedreal = $this->getTime() - $ortime;
- if (!is_array($entry)) {
+ if ( !is_array( $entry ) ) {
$entry = $this->zeroEntry;
$this->mCollated[$functionname] =& $entry;
}
diff --git a/includes/profiler/ProfilerSimpleText.php b/includes/profiler/ProfilerSimpleText.php
index 3e7d6fa4..37350bf3 100644
--- a/includes/profiler/ProfilerSimpleText.php
+++ b/includes/profiler/ProfilerSimpleText.php
@@ -48,12 +48,20 @@ class ProfilerSimpleText extends ProfilerSimple {
$totalReal = isset( $this->mCollated['-total'] )
? $this->mCollated['-total']['real']
: 0; // profiling mismatch error?
- uasort( $this->mCollated, array('self','sort') );
- array_walk( $this->mCollated, array('self','format'), $totalReal );
- if ( $this->visible ) {
- print '<pre>'.self::$out.'</pre>';
- } else {
+ uasort( $this->mCollated, array( 'self', 'sort' ) );
+ array_walk( $this->mCollated, array( 'self', 'format' ), $totalReal );
+ if ( PHP_SAPI === 'cli' ) {
print "<!--\n".self::$out."\n-->\n";
+ } elseif ( $this->getContentType() === 'text/html' ) {
+ if ( $this->visible ) {
+ print '<pre>'.self::$out.'</pre>';
+ } else {
+ print "<!--\n".self::$out."\n-->\n";
+ }
+ } elseif ( $this->getContentType() === 'text/javascript' ) {
+ print "\n/*\n".self::$out."*/\n";
+ } elseif ( $this->getContentType() === 'text/css' ) {
+ print "\n/*\n".self::$out."*/\n";
}
}
}
diff --git a/includes/profiler/ProfilerSimpleTrace.php b/includes/profiler/ProfilerSimpleTrace.php
index 822e9fe4..d44dfe1b 100644
--- a/includes/profiler/ProfilerSimpleTrace.php
+++ b/includes/profiler/ProfilerSimpleTrace.php
@@ -32,18 +32,18 @@ class ProfilerSimpleTrace extends ProfilerSimple {
function profileIn( $functionname ) {
parent::profileIn( $functionname );
- $this->trace .= " " . sprintf("%6.1f",$this->memoryDiff()) .
- str_repeat( " ", count($this->mWorkStack)) . " > " . $functionname . "\n";
+ $this->trace .= " " . sprintf( "%6.1f", $this->memoryDiff() ) .
+ str_repeat( " ", count( $this->mWorkStack ) ) . " > " . $functionname . "\n";
}
- function profileOut($functionname) {
+ function profileOut( $functionname ) {
global $wgDebugFunctionEntry;
if ( $wgDebugFunctionEntry ) {
- $this->debug(str_repeat(' ', count($this->mWorkStack) - 1).'Exiting '.$functionname."\n");
+ $this->debug( str_repeat( ' ', count( $this->mWorkStack ) - 1 ) . 'Exiting ' . $functionname . "\n" );
}
- list( $ofname, /* $ocount */ , $ortime ) = array_pop( $this->mWorkStack );
+ list( $ofname, /* $ocount */, $ortime ) = array_pop( $this->mWorkStack );
if ( !$ofname ) {
$this->trace .= "Profiling error: $functionname\n";
@@ -58,7 +58,7 @@ class ProfilerSimpleTrace extends ProfilerSimple {
}
$elapsedreal = $this->getTime() - $ortime;
$this->trace .= sprintf( "%03.6f %6.1f", $elapsedreal, $this->memoryDiff() ) .
- str_repeat(" ", count( $this->mWorkStack ) + 1 ) . " < " . $functionname . "\n";
+ str_repeat( " ", count( $this->mWorkStack ) + 1 ) . " < " . $functionname . "\n";
}
}
@@ -69,6 +69,14 @@ class ProfilerSimpleTrace extends ProfilerSimple {
}
function logData() {
- print "<!-- \n {$this->trace} \n -->";
+ if ( PHP_SAPI === 'cli' ) {
+ print "<!-- \n {$this->trace} \n -->";
+ } elseif ( $this->getContentType() === 'text/html' ) {
+ print "<!-- \n {$this->trace} \n -->";
+ } elseif ( $this->getContentType() === 'text/javascript' ) {
+ print "\n/*\n {$this->trace}\n*/";
+ } elseif ( $this->getContentType() === 'text/css' ) {
+ print "\n/*\n {$this->trace}\n*/";
+ }
}
}
diff --git a/includes/profiler/ProfilerSimpleUDP.php b/includes/profiler/ProfilerSimpleUDP.php
index a95ccb0d..abefa811 100644
--- a/includes/profiler/ProfilerSimpleUDP.php
+++ b/includes/profiler/ProfilerSimpleUDP.php
@@ -50,7 +50,7 @@ class ProfilerSimpleUDP extends ProfilerSimple {
$plength = 0;
$packet = "";
foreach ( $this->mCollated as $entry => $pfdata ) {
- if( !isset($pfdata['count'])
+ if( !isset( $pfdata['count'] )
|| !isset( $pfdata['cpu'] )
|| !isset( $pfdata['cpu_sq'] )
|| !isset( $pfdata['real'] )
diff --git a/includes/resourceloader/ResourceLoader.php b/includes/resourceloader/ResourceLoader.php
index 7b87f9d4..27f682c2 100644
--- a/includes/resourceloader/ResourceLoader.php
+++ b/includes/resourceloader/ResourceLoader.php
@@ -39,7 +39,7 @@ class ResourceLoader {
/** Associative array mapping module name to info associative array */
protected $moduleInfos = array();
-
+
/** Associative array mapping framework ids to a list of names of test suite modules */
/** like array( 'qunit' => array( 'mediawiki.tests.qunit.suites', 'ext.foo.tests', .. ), .. ) */
protected $testModuleNames = array();
@@ -60,7 +60,7 @@ class ResourceLoader {
* requests its own information. This sacrifice of modularity yields a substantial
* performance improvement.
*
- * @param $modules Array: List of module names to preload information for
+ * @param array $modules List of module names to preload information for
* @param $context ResourceLoaderContext: Context to load the information within
*/
public function preloadModuleInfo( array $modules, ResourceLoaderContext $context ) {
@@ -127,8 +127,8 @@ class ResourceLoader {
* If $data is empty, only contains whitespace or the filter was unknown,
* $data is returned unmodified.
*
- * @param $filter String: Name of filter to run
- * @param $data String: Text to filter, such as JavaScript or CSS text
+ * @param string $filter Name of filter to run
+ * @param string $data Text to filter, such as JavaScript or CSS text
* @return String: Filtered data, or a comment containing an error message
*/
protected function filter( $filter, $data ) {
@@ -210,7 +210,6 @@ class ResourceLoader {
$this->registerTestModules();
}
-
wfProfileOut( __METHOD__ );
}
@@ -218,7 +217,7 @@ class ResourceLoader {
* Registers a module with the ResourceLoader system.
*
* @param $name Mixed: Name of module as a string or List of name/object pairs as an array
- * @param $info array Module info array. For backwards compatibility with 1.17alpha,
+ * @param array $info Module info array. For backwards compatibility with 1.17alpha,
* this may also be a ResourceLoaderModule object. Optional when using
* multiple-registration calling style.
* @throws MWException: If a duplicate module registration is attempted
@@ -309,7 +308,8 @@ class ResourceLoader {
* 'loadScript': URL (either fully-qualified or protocol-relative) of load.php for this source
*
* @param $id Mixed: source ID (string), or array( id1 => props1, id2 => props2, ... )
- * @param $properties Array: source properties
+ * @param array $properties source properties
+ * @throws MWException
*/
public function addSource( $id, $properties = null) {
// Allow multiple sources to be registered in one call
@@ -346,13 +346,13 @@ class ResourceLoader {
public function getModuleNames() {
return array_keys( $this->moduleInfos );
}
-
- /**
+
+ /**
* Get a list of test module names for one (or all) frameworks.
* If the given framework id is unknkown, or if the in-object variable is not an array,
* then it will return an empty array.
*
- * @param $framework String: Optional. Get only the test module names for one
+ * @param string $framework Optional. Get only the test module names for one
* particular framework.
* @return Array
*/
@@ -370,7 +370,7 @@ class ResourceLoader {
/**
* Get the ResourceLoaderModule object for a given module name.
*
- * @param $name String: Module name
+ * @param string $name Module name
* @return ResourceLoaderModule if module has been registered, null otherwise
*/
public function getModule( $name ) {
@@ -450,7 +450,7 @@ class ResourceLoader {
$this->hasErrors = true;
continue;
}
- $modules[$name] = $this->getModule( $name );
+ $modules[$name] = $module;
} else {
$missing[] = $name;
}
@@ -530,8 +530,8 @@ class ResourceLoader {
/**
* Send content type and last modified headers to the client.
* @param $context ResourceLoaderContext
- * @param $mtime string TS_MW timestamp to use for last-modified
- * @param $error bool Whether there are commented-out errors in the response
+ * @param string $mtime TS_MW timestamp to use for last-modified
+ * @param bool $error Whether there are commented-out errors in the response
* @return void
*/
protected function sendResponseHeaders( ResourceLoaderContext $context, $mtime, $errors ) {
@@ -540,12 +540,12 @@ class ResourceLoader {
// to propagate to clients quickly
// If there were errors, we also need a shorter expiry time so we can recover quickly
if ( is_null( $context->getVersion() ) || $errors ) {
- $maxage = $wgResourceLoaderMaxage['unversioned']['client'];
+ $maxage = $wgResourceLoaderMaxage['unversioned']['client'];
$smaxage = $wgResourceLoaderMaxage['unversioned']['server'];
// If a version was specified we can use a longer expiry time since changing
// version numbers causes cache misses
} else {
- $maxage = $wgResourceLoaderMaxage['versioned']['client'];
+ $maxage = $wgResourceLoaderMaxage['versioned']['client'];
$smaxage = $wgResourceLoaderMaxage['versioned']['server'];
}
if ( $context->getOnly() === 'styles' ) {
@@ -569,7 +569,7 @@ class ResourceLoader {
* If there's an If-Modified-Since header, respond with a 304 appropriately
* and clear out the output buffer. If the client cache is too old then do nothing.
* @param $context ResourceLoaderContext
- * @param $mtime string The TS_MW timestamp to check the header against
+ * @param string $mtime The TS_MW timestamp to check the header against
* @return bool True iff 304 header sent and output handled
*/
protected function tryRespondLastModified( ResourceLoaderContext $context, $mtime ) {
@@ -666,8 +666,8 @@ class ResourceLoader {
* Generates code for a response
*
* @param $context ResourceLoaderContext: Context in which to generate a response
- * @param $modules Array: List of module objects keyed by module name
- * @param $missing Array: List of unavailable modules (optional)
+ * @param array $modules List of module objects keyed by module name
+ * @param array $missing List of unavailable modules (optional)
* @return String: Response data
*/
public function makeModuleResponse( ResourceLoaderContext $context,
@@ -834,7 +834,7 @@ class ResourceLoader {
* Returns JS code to call to mw.loader.implement for a module with
* given properties.
*
- * @param $name string Module name
+ * @param string $name Module name
* @param $scripts Mixed: List of URLs to JavaScript files or String of JavaScript code
* @param $styles Mixed: Array of CSS strings keyed by media type, or an array of lists of URLs to
* CSS files keyed by media type
@@ -842,6 +842,7 @@ class ResourceLoader {
* associative array mapping message key to value, or a JSON-encoded message blob containing
* the same data, wrapped in an XmlJsCode object.
*
+ * @throws MWException
* @return string
*/
public static function makeLoaderImplementScript( $name, $scripts, $styles, $messages ) {
@@ -881,7 +882,7 @@ class ResourceLoader {
* Combines an associative array mapping media type to CSS into a
* single stylesheet with "@media" blocks.
*
- * @param $styles Array: Array keyed by media type containing (arrays of) CSS strings.
+ * @param array $stylePairs Array keyed by media type containing (arrays of) CSS strings.
*
* @return Array
*/
@@ -941,12 +942,12 @@ class ResourceLoader {
* which will have values corresponding to $name, $version, $dependencies
* and $group as supplied.
*
- * @param $name String: Module name
+ * @param string $name Module name
* @param $version Integer: Module version number as a timestamp
- * @param $dependencies Array: List of module names on which this module depends
- * @param $group String: Group which the module is in.
- * @param $source String: Source of the module, or 'local' if not foreign.
- * @param $script String: JavaScript code
+ * @param array $dependencies List of module names on which this module depends
+ * @param string $group Group which the module is in.
+ * @param string $source Source of the module, or 'local' if not foreign.
+ * @param string $script JavaScript code
*
* @return string
*/
@@ -974,11 +975,11 @@ class ResourceLoader {
* ) ):
* Registers modules with the given names and parameters.
*
- * @param $name String: Module name
+ * @param string $name Module name
* @param $version Integer: Module version number as a timestamp
- * @param $dependencies Array: List of module names on which this module depends
- * @param $group String: group which the module is in.
- * @param $source String: source of the module, or 'local' if not foreign
+ * @param array $dependencies List of module names on which this module depends
+ * @param string $group group which the module is in.
+ * @param string $source source of the module, or 'local' if not foreign
*
* @return string
*/
@@ -1004,8 +1005,8 @@ class ResourceLoader {
* - ResourceLoader::makeLoaderSourcesScript( array( $id1 => $props1, $id2 => $props2, ... ) );
* Register sources with the given IDs and properties.
*
- * @param $id String: source ID
- * @param $properties Array: source properties (see addSource())
+ * @param string $id source ID
+ * @param array $properties source properties (see addSource())
*
* @return string
*/
@@ -1021,7 +1022,7 @@ class ResourceLoader {
* Returns JS code which runs given JS code if the client-side framework is
* present.
*
- * @param $script String: JavaScript code
+ * @param string $script JavaScript code
*
* @return string
*/
@@ -1033,7 +1034,7 @@ class ResourceLoader {
* Returns JS code which will set the MediaWiki configuration array to
* the given value.
*
- * @param $configuration Array: List of configuration values keyed by variable name
+ * @param array $configuration List of configuration values keyed by variable name
*
* @return string
*/
@@ -1046,7 +1047,7 @@ class ResourceLoader {
*
* For example, array( 'foo.bar', 'foo.baz', 'bar.baz', 'bar.quux' )
* becomes 'foo.bar,baz|bar.baz,quux'
- * @param $modules array of module names (strings)
+ * @param array $modules of module names (strings)
* @return string Packed query string
*/
public static function makePackedModulesString( $modules ) {
@@ -1084,16 +1085,16 @@ class ResourceLoader {
/**
* Build a load.php URL
- * @param $modules array of module names (strings)
- * @param $lang string Language code
- * @param $skin string Skin name
- * @param $user string|null User name. If null, the &user= parameter is omitted
- * @param $version string|null Versioning timestamp
- * @param $debug bool Whether the request should be in debug mode
- * @param $only string|null &only= parameter
- * @param $printable bool Printable mode
- * @param $handheld bool Handheld mode
- * @param $extraQuery array Extra query parameters to add
+ * @param array $modules of module names (strings)
+ * @param string $lang Language code
+ * @param string $skin Skin name
+ * @param string|null $user User name. If null, the &user= parameter is omitted
+ * @param string|null $version Versioning timestamp
+ * @param bool $debug Whether the request should be in debug mode
+ * @param string|null $only &only= parameter
+ * @param bool $printable Printable mode
+ * @param bool $handheld Handheld mode
+ * @param array $extraQuery Extra query parameters to add
* @return string URL to load.php. May be protocol-relative (if $wgLoadScript is procol-relative)
*/
public static function makeLoaderURL( $modules, $lang, $skin, $user = null, $version = null, $debug = false, $only = null,
@@ -1149,7 +1150,7 @@ class ResourceLoader {
* Module names may not contain pipes (|), commas (,) or exclamation marks (!) and can be
* at most 255 bytes.
*
- * @param $moduleName string Module name to check
+ * @param string $moduleName Module name to check
* @return bool Whether $moduleName is a valid module name
*/
public static function isValidModuleName( $moduleName ) {
diff --git a/includes/resourceloader/ResourceLoaderContext.php b/includes/resourceloader/ResourceLoaderContext.php
index 0e96c6c8..4588015f 100644
--- a/includes/resourceloader/ResourceLoaderContext.php
+++ b/includes/resourceloader/ResourceLoaderContext.php
@@ -58,14 +58,14 @@ class ResourceLoaderContext {
// Interpret request
// List of modules
$modules = $request->getVal( 'modules' );
- $this->modules = $modules ? self::expandModuleNames( $modules ) : array();
+ $this->modules = $modules ? self::expandModuleNames( $modules ) : array();
// Various parameters
- $this->skin = $request->getVal( 'skin' );
- $this->user = $request->getVal( 'user' );
- $this->debug = $request->getFuzzyBool( 'debug', $wgResourceLoaderDebug );
- $this->only = $request->getVal( 'only' );
- $this->version = $request->getVal( 'version' );
- $this->raw = $request->getFuzzyBool( 'raw' );
+ $this->skin = $request->getVal( 'skin' );
+ $this->user = $request->getVal( 'user' );
+ $this->debug = $request->getFuzzyBool( 'debug', $wgResourceLoaderDebug );
+ $this->only = $request->getVal( 'only' );
+ $this->version = $request->getVal( 'version' );
+ $this->raw = $request->getFuzzyBool( 'raw' );
$skinnames = Skin::getSkinNames();
// If no skin is specified, or we don't recognize the skin, use the default skin
@@ -78,7 +78,7 @@ class ResourceLoaderContext {
* Expand a string of the form jquery.foo,bar|jquery.ui.baz,quux to
* an array of module names like array( 'jquery.foo', 'jquery.bar',
* 'jquery.ui.baz', 'jquery.ui.quux' )
- * @param $modules String Packed module name list
+ * @param string $modules Packed module name list
* @return array of module names
*/
public static function expandModuleNames( $modules ) {
@@ -145,7 +145,7 @@ class ResourceLoaderContext {
public function getLanguage() {
if ( $this->language === null ) {
global $wgLang;
- $this->language = $this->request->getVal( 'lang' );
+ $this->language = $this->request->getVal( 'lang' );
if ( !$this->language ) {
$this->language = $wgLang->getCode();
}
diff --git a/includes/resourceloader/ResourceLoaderFileModule.php b/includes/resourceloader/ResourceLoaderFileModule.php
index 8b9b7277..cedb5dcc 100644
--- a/includes/resourceloader/ResourceLoaderFileModule.php
+++ b/includes/resourceloader/ResourceLoaderFileModule.php
@@ -113,6 +113,8 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
protected $debugRaw = true;
/** Boolean: Whether mw.loader.state() call should be omitted */
protected $raw = false;
+ protected $targets = array( 'desktop' );
+
/**
* Array: Cache for mtime
* @par Usage:
@@ -135,52 +137,53 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Constructs a new module from an options array.
*
- * @param $options Array: List of options; if not given or empty, an empty module will be
+ * @param array $options List of options; if not given or empty, an empty module will be
* constructed
- * @param $localBasePath String: Base path to prepend to all local paths in $options. Defaults
+ * @param string $localBasePath Base path to prepend to all local paths in $options. Defaults
* to $IP
- * @param $remoteBasePath String: Base path to prepend to all remote paths in $options. Defaults
+ * @param string $remoteBasePath Base path to prepend to all remote paths in $options. Defaults
* to $wgScriptPath
*
* Below is a description for the $options array:
+ * @throws MWException
* @par Construction options:
* @code
- * array(
- * // Base path to prepend to all local paths in $options. Defaults to $IP
- * 'localBasePath' => [base path],
- * // Base path to prepend to all remote paths in $options. Defaults to $wgScriptPath
- * 'remoteBasePath' => [base path],
- * // Equivalent of remoteBasePath, but relative to $wgExtensionAssetsPath
- * 'remoteExtPath' => [base path],
- * // Scripts to always include
- * 'scripts' => [file path string or array of file path strings],
- * // Scripts to include in specific language contexts
- * 'languageScripts' => array(
- * [language code] => [file path string or array of file path strings],
- * ),
- * // Scripts to include in specific skin contexts
- * 'skinScripts' => array(
- * [skin name] => [file path string or array of file path strings],
- * ),
- * // Scripts to include in debug contexts
- * 'debugScripts' => [file path string or array of file path strings],
- * // Scripts to include in the startup module
- * 'loaderScripts' => [file path string or array of file path strings],
- * // Modules which must be loaded before this module
- * 'dependencies' => [modile name string or array of module name strings],
- * // Styles to always load
- * 'styles' => [file path string or array of file path strings],
- * // Styles to include in specific skin contexts
- * 'skinStyles' => array(
- * [skin name] => [file path string or array of file path strings],
- * ),
- * // Messages to always load
- * 'messages' => [array of message key strings],
- * // Group which this module should be loaded together with
- * 'group' => [group name string],
- * // Position on the page to load this module at
- * 'position' => ['bottom' (default) or 'top']
- * )
+ * array(
+ * // Base path to prepend to all local paths in $options. Defaults to $IP
+ * 'localBasePath' => [base path],
+ * // Base path to prepend to all remote paths in $options. Defaults to $wgScriptPath
+ * 'remoteBasePath' => [base path],
+ * // Equivalent of remoteBasePath, but relative to $wgExtensionAssetsPath
+ * 'remoteExtPath' => [base path],
+ * // Scripts to always include
+ * 'scripts' => [file path string or array of file path strings],
+ * // Scripts to include in specific language contexts
+ * 'languageScripts' => array(
+ * [language code] => [file path string or array of file path strings],
+ * ),
+ * // Scripts to include in specific skin contexts
+ * 'skinScripts' => array(
+ * [skin name] => [file path string or array of file path strings],
+ * ),
+ * // Scripts to include in debug contexts
+ * 'debugScripts' => [file path string or array of file path strings],
+ * // Scripts to include in the startup module
+ * 'loaderScripts' => [file path string or array of file path strings],
+ * // Modules which must be loaded before this module
+ * 'dependencies' => [module name string or array of module name strings],
+ * // Styles to always load
+ * 'styles' => [file path string or array of file path strings],
+ * // Styles to include in specific skin contexts
+ * 'skinStyles' => array(
+ * [skin name] => [file path string or array of file path strings],
+ * ),
+ * // Messages to always load
+ * 'messages' => [array of message key strings],
+ * // Group which this module should be loaded together with
+ * 'group' => [group name string],
+ * // Position on the page to load this module at
+ * 'position' => ['bottom' (default) or 'top']
+ * )
* @endcode
*/
public function __construct( $options = array(), $localBasePath = null,
@@ -231,6 +234,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
// Lists of strings
case 'dependencies':
case 'messages':
+ case 'targets':
$this->{$member} = (array) $option;
break;
// Single strings
@@ -437,9 +441,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
return $this->modifiedTime[$context->getHash()] = 1;
}
- wfProfileIn( __METHOD__.'-filemtime' );
+ wfProfileIn( __METHOD__ . '-filemtime' );
$filesMtime = max( array_map( array( __CLASS__, 'safeFilemtime' ), $files ) );
- wfProfileOut( __METHOD__.'-filemtime' );
+ wfProfileOut( __METHOD__ . '-filemtime' );
$this->modifiedTime[$context->getHash()] = max(
$filesMtime,
$this->getMsgBlobMtime( $context->getLanguage() ) );
@@ -469,9 +473,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Collates file paths by option (where provided).
*
- * @param $list Array: List of file paths in any combination of index/path
+ * @param array $list List of file paths in any combination of index/path
* or path/options pairs
- * @param $option String: option name
+ * @param string $option option name
* @param $default Mixed: default value if the option isn't set
* @return Array: List of file paths, collated by $option
*/
@@ -499,9 +503,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets a list of element that match a key, optionally using a fallback key.
*
- * @param $list Array: List of lists to select from
- * @param $key String: Key to look for in $map
- * @param $fallback String: Key to look for in $list if $key doesn't exist
+ * @param array $list List of lists to select from
+ * @param string $key Key to look for in $map
+ * @param string $fallback Key to look for in $list if $key doesn't exist
* @return Array: List of elements from $map which matched $key or $fallback,
* or an empty list in case of no match
*/
@@ -532,7 +536,8 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
if ( $context->getDebug() ) {
$files = array_merge( $files, $this->debugScripts );
}
- return $files;
+
+ return array_unique( $files );
}
/**
@@ -553,7 +558,8 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets the contents of a list of JavaScript files.
*
- * @param $scripts Array: List of file paths to scripts to read, remap and concetenate
+ * @param array $scripts List of file paths to scripts to read, remap and concetenate
+ * @throws MWException
* @return String: Concatenated and remapped JavaScript data from $scripts
*/
protected function readScriptFiles( array $scripts ) {
@@ -565,7 +571,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
foreach ( array_unique( $scripts ) as $fileName ) {
$localPath = $this->getLocalPath( $fileName );
if ( !file_exists( $localPath ) ) {
- throw new MWException( __METHOD__.": script file not found: \"$localPath\"" );
+ throw new MWException( __METHOD__ . ": script file not found: \"$localPath\"" );
}
$contents = file_get_contents( $localPath );
if ( $wgResourceLoaderValidateStaticJS ) {
@@ -582,7 +588,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets the contents of a list of CSS files.
*
- * @param $styles Array: List of media type/list of file paths pairs, to read, remap and
+ * @param array $styles List of media type/list of file paths pairs, to read, remap and
* concetenate
*
* @param $flip bool
@@ -613,7 +619,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
*
* This method can be used as a callback for array_map()
*
- * @param $path String: File path of style file to read
+ * @param string $path File path of style file to read
* @param $flip bool
*
* @return String: CSS data in script file
@@ -622,7 +628,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
protected function readStyleFile( $path, $flip ) {
$localPath = $this->getLocalPath( $path );
if ( !file_exists( $localPath ) ) {
- throw new MWException( __METHOD__.": style file not found: \"$localPath\"" );
+ $msg = __METHOD__ . ": style file not found: \"$localPath\"";
+ wfDebugLog( 'resourceloader', $msg );
+ throw new MWException( $msg );
}
$style = file_get_contents( $localPath );
if ( $flip ) {
@@ -646,23 +654,6 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
}
/**
- * Safe version of filemtime(), which doesn't throw a PHP warning if the file doesn't exist
- * but returns 1 instead.
- * @param $filename string File name
- * @return int UNIX timestamp, or 1 if the file doesn't exist
- */
- protected static function safeFilemtime( $filename ) {
- if ( file_exists( $filename ) ) {
- return filemtime( $filename );
- } else {
- // We only ever map this function on an array if we're gonna call max() after,
- // so return our standard minimum timestamps here. This is 1, not 0, because
- // wfTimestamp(0) == NOW
- return 1;
- }
- }
-
- /**
* Get whether CSS for this module should be flipped
* @param $context ResourceLoaderContext
* @return bool
@@ -670,4 +661,14 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
public function getFlip( $context ) {
return $context->getDirection() === 'rtl';
}
+
+ /**
+ * Get target(s) for the module, eg ['desktop'] or ['desktop', 'mobile']
+ *
+ * @return array of strings
+ */
+ public function getTargets() {
+ return $this->targets;
+ }
+
}
diff --git a/includes/resourceloader/ResourceLoaderLanguageDataModule.php b/includes/resourceloader/ResourceLoaderLanguageDataModule.php
index c916c4a5..0f8e54ce 100644
--- a/includes/resourceloader/ResourceLoaderLanguageDataModule.php
+++ b/includes/resourceloader/ResourceLoaderLanguageDataModule.php
@@ -28,6 +28,7 @@
class ResourceLoaderLanguageDataModule extends ResourceLoaderModule {
protected $language;
+ protected $targets = array( 'desktop', 'mobile' );
/**
* Get the grammar forms for the site content language.
*
@@ -47,24 +48,35 @@ class ResourceLoaderLanguageDataModule extends ResourceLoaderModule {
}
/**
+ * Get the digit groupin Pattern for the site content language.
+ *
+ * @return array
+ */
+ protected function getDigitGroupingPattern() {
+ return $this->language->digitGroupingPattern();
+ }
+
+ /**
* Get the digit transform table for the content language
- * Seperator transform table also required here to convert
- * the . and , sign to appropriate forms in content language.
*
* @return array
*/
protected function getDigitTransformTable() {
- $digitTransformTable = $this->language->digitTransformTable();
- $separatorTransformTable = $this->language->separatorTransformTable();
- if ( $digitTransformTable ) {
- array_merge( $digitTransformTable, (array)$separatorTransformTable );
- } else {
- return $separatorTransformTable;
- }
- return $digitTransformTable;
+ return $this->language->digitTransformTable();
}
/**
+ * Get seperator transform table required for converting
+ * the . and , sign to appropriate forms in site content language.
+ *
+ * @return array
+ */
+ protected function getSeparatorTransformTable() {
+ return $this->language->separatorTransformTable();
+ }
+
+
+ /**
* Get all the dynamic data for the content language to an array
*
* @return array
@@ -72,8 +84,10 @@ class ResourceLoaderLanguageDataModule extends ResourceLoaderModule {
protected function getData() {
return array(
'digitTransformTable' => $this->getDigitTransformTable(),
+ 'separatorTransformTable' => $this->getSeparatorTransformTable(),
'grammarForms' => $this->getSiteLangGrammarForms(),
'pluralRules' => $this->getPluralRules(),
+ 'digitGroupingPattern' => $this->getDigitGroupingPattern(),
);
}
diff --git a/includes/resourceloader/ResourceLoaderModule.php b/includes/resourceloader/ResourceLoaderModule.php
index 9c49c45f..03f3cc37 100644
--- a/includes/resourceloader/ResourceLoaderModule.php
+++ b/includes/resourceloader/ResourceLoaderModule.php
@@ -58,6 +58,7 @@ abstract class ResourceLoaderModule {
/* Protected Members */
protected $name = null;
+ protected $targets = array( 'desktop' );
// In-object cache for file dependencies
protected $fileDeps = array();
@@ -77,10 +78,10 @@ abstract class ResourceLoaderModule {
}
/**
- * Set this module's name. This is called by ResourceLodaer::register()
+ * Set this module's name. This is called by ResourceLoader::register()
* when registering the module. Other code should not call this.
*
- * @param $name String: Name
+ * @param string $name Name
*/
public function setName( $name ) {
$this->name = $name;
@@ -91,17 +92,17 @@ abstract class ResourceLoaderModule {
* with ResourceLoader::register()
*
* @return Int ResourceLoaderModule class constant, the subclass default
- * if not set manuall
+ * if not set manually
*/
public function getOrigin() {
return $this->origin;
}
/**
- * Set this module's origin. This is called by ResourceLodaer::register()
+ * Set this module's origin. This is called by ResourceLoader::register()
* when registering the module. Other code should not call this.
*
- * @param $origin Int origin
+ * @param int $origin origin
*/
public function setOrigin( $origin ) {
$this->origin = $origin;
@@ -290,10 +291,19 @@ abstract class ResourceLoaderModule {
}
/**
+ * Get target(s) for the module, eg ['desktop'] or ['desktop', 'mobile']
+ *
+ * @return array of strings
+ */
+ public function getTargets() {
+ return $this->targets;
+ }
+
+ /**
* Get the files this module depends on indirectly for a given skin.
* Currently these are only image files referenced by the module's CSS.
*
- * @param $skin String: Skin name
+ * @param string $skin Skin name
* @return Array: List of files
*/
public function getFileDependencies( $skin ) {
@@ -319,8 +329,8 @@ abstract class ResourceLoaderModule {
/**
* Set preloaded file dependency information. Used so we can load this
* information for all modules at once.
- * @param $skin String: Skin name
- * @param $deps Array: Array of file names
+ * @param string $skin Skin name
+ * @param array $deps Array of file names
*/
public function setFileDependencies( $skin, $deps ) {
$this->fileDeps[$skin] = $deps;
@@ -329,13 +339,14 @@ abstract class ResourceLoaderModule {
/**
* Get the last modification timestamp of the message blob for this
* module in a given language.
- * @param $lang String: Language code
+ * @param string $lang Language code
* @return Integer: UNIX timestamp, or 0 if the module doesn't have messages
*/
public function getMsgBlobMtime( $lang ) {
if ( !isset( $this->msgBlobMtime[$lang] ) ) {
- if ( !count( $this->getMessages() ) )
+ if ( !count( $this->getMessages() ) ) {
return 0;
+ }
$dbr = wfGetDB( DB_SLAVE );
$msgBlobMtime = $dbr->selectField( 'msg_resource', 'mr_timestamp', array(
@@ -356,7 +367,7 @@ abstract class ResourceLoaderModule {
/**
* Set a preloaded message blob last modification timestamp. Used so we
* can load this information for all modules at once.
- * @param $lang String: Language code
+ * @param string $lang Language code
* @param $mtime Integer: UNIX timestamp or 0 if there is no such blob
*/
public function setMsgBlobMtime( $lang, $mtime ) {
@@ -375,7 +386,7 @@ abstract class ResourceLoaderModule {
* NOTE: The mtime of the module's messages is NOT automatically included.
* If you want this to happen, you'll need to call getMsgBlobMtime()
* yourself and take its result into consideration.
- *
+ *
* @param $context ResourceLoaderContext: Context object
* @return Integer: UNIX timestamp
*/
@@ -397,7 +408,6 @@ abstract class ResourceLoaderModule {
return false;
}
-
/** @var JSParser lazy-initialized; use self::javaScriptParser() */
private static $jsParser;
private static $parseCacheVersion = 1;
@@ -426,10 +436,10 @@ abstract class ResourceLoaderModule {
try {
$parser->parse( $contents, $fileName, 1 );
$result = $contents;
- } catch (Exception $e) {
+ } catch ( Exception $e ) {
// We'll save this to cache to avoid having to validate broken JS over and over...
$err = $e->getMessage();
- $result = "throw new Error(" . Xml::encodeJsVar("JavaScript parse error: $err") . ");";
+ $result = "throw new Error(" . Xml::encodeJsVar( "JavaScript parse error: $err" ) . ");";
}
$cache->set( $key, $result );
@@ -449,4 +459,20 @@ abstract class ResourceLoaderModule {
return self::$jsParser;
}
+ /**
+ * Safe version of filemtime(), which doesn't throw a PHP warning if the file doesn't exist
+ * but returns 1 instead.
+ * @param string $filename File name
+ * @return int UNIX timestamp, or 1 if the file doesn't exist
+ */
+ protected static function safeFilemtime( $filename ) {
+ if ( file_exists( $filename ) ) {
+ return filemtime( $filename );
+ } else {
+ // We only ever map this function on an array if we're gonna call max() after,
+ // so return our standard minimum timestamps here. This is 1, not 0, because
+ // wfTimestamp(0) == NOW
+ return 1;
+ }
+ }
}
diff --git a/includes/resourceloader/ResourceLoaderNoscriptModule.php b/includes/resourceloader/ResourceLoaderNoscriptModule.php
index 8e81c8d9..bd026f3f 100644
--- a/includes/resourceloader/ResourceLoaderNoscriptModule.php
+++ b/includes/resourceloader/ResourceLoaderNoscriptModule.php
@@ -45,7 +45,7 @@ class ResourceLoaderNoscriptModule extends ResourceLoaderWikiModule {
/**
* Gets group name
- *
+ *
* @return String: Name of group
*/
public function getGroup() {
diff --git a/includes/resourceloader/ResourceLoaderSiteModule.php b/includes/resourceloader/ResourceLoaderSiteModule.php
index 03fe1fe5..1cc5c1a9 100644
--- a/includes/resourceloader/ResourceLoaderSiteModule.php
+++ b/includes/resourceloader/ResourceLoaderSiteModule.php
@@ -47,8 +47,8 @@ class ResourceLoaderSiteModule extends ResourceLoaderWikiModule {
'MediaWiki:Print.css' => array( 'type' => 'style', 'media' => 'print' ),
);
if ( $wgHandheldStyle ) {
- $pages['MediaWiki:Handheld.css'] = array(
- 'type' => 'style',
+ $pages['MediaWiki:Handheld.css'] = array(
+ 'type' => 'style',
'media' => 'handheld' );
}
return $pages;
@@ -58,7 +58,7 @@ class ResourceLoaderSiteModule extends ResourceLoaderWikiModule {
/**
* Gets group name
- *
+ *
* @return String: Name of group
*/
public function getGroup() {
diff --git a/includes/resourceloader/ResourceLoaderStartUpModule.php b/includes/resourceloader/ResourceLoaderStartUpModule.php
index 20ee83f9..32cf6b26 100644
--- a/includes/resourceloader/ResourceLoaderStartUpModule.php
+++ b/includes/resourceloader/ResourceLoaderStartUpModule.php
@@ -114,6 +114,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
$out = '';
$registrations = array();
$resourceLoader = $context->getResourceLoader();
+ $target = $context->getRequest()->getVal( 'target', 'desktop' );
// Register sources
$out .= ResourceLoader::makeLoaderSourcesScript( $resourceLoader->getSources() );
@@ -121,6 +122,10 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
// Register modules
foreach ( $resourceLoader->getModuleNames() as $name ) {
$module = $resourceLoader->getModule( $name );
+ $moduleTargets = $module->getTargets();
+ if ( !in_array( $target, $moduleTargets ) ) {
+ continue;
+ }
$deps = $module->getDependencies();
$group = $module->getGroup();
$source = $module->getSource();
@@ -130,33 +135,33 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
$version = wfTimestamp( TS_ISO_8601_BASIC,
$module->getModifiedTime( $context ) );
$out .= ResourceLoader::makeCustomLoaderScript( $name, $version, $deps, $group, $source, $loader );
+ continue;
}
+
// Automatically register module
+ // getModifiedTime() is supposed to return a UNIX timestamp, but it doesn't always
+ // seem to do that, and custom implementations might forget. Coerce it to TS_UNIX
+ $moduleMtime = wfTimestamp( TS_UNIX, $module->getModifiedTime( $context ) );
+ $mtime = max( $moduleMtime, wfTimestamp( TS_UNIX, $wgCacheEpoch ) );
+ // Modules without dependencies, a group or a foreign source pass two arguments (name, timestamp) to
+ // mw.loader.register()
+ if ( !count( $deps ) && $group === null && $source === 'local' ) {
+ $registrations[] = array( $name, $mtime );
+ }
+ // Modules with dependencies but no group or foreign source pass three arguments
+ // (name, timestamp, dependencies) to mw.loader.register()
+ elseif ( $group === null && $source === 'local' ) {
+ $registrations[] = array( $name, $mtime, $deps );
+ }
+ // Modules with a group but no foreign source pass four arguments (name, timestamp, dependencies, group)
+ // to mw.loader.register()
+ elseif ( $source === 'local' ) {
+ $registrations[] = array( $name, $mtime, $deps, $group );
+ }
+ // Modules with a foreign source pass five arguments (name, timestamp, dependencies, group, source)
+ // to mw.loader.register()
else {
- // getModifiedTime() is supposed to return a UNIX timestamp, but it doesn't always
- // seem to do that, and custom implementations might forget. Coerce it to TS_UNIX
- $moduleMtime = wfTimestamp( TS_UNIX, $module->getModifiedTime( $context ) );
- $mtime = max( $moduleMtime, wfTimestamp( TS_UNIX, $wgCacheEpoch ) );
- // Modules without dependencies, a group or a foreign source pass two arguments (name, timestamp) to
- // mw.loader.register()
- if ( !count( $deps ) && $group === null && $source === 'local' ) {
- $registrations[] = array( $name, $mtime );
- }
- // Modules with dependencies but no group or foreign source pass three arguments
- // (name, timestamp, dependencies) to mw.loader.register()
- elseif ( $group === null && $source === 'local' ) {
- $registrations[] = array( $name, $mtime, $deps );
- }
- // Modules with a group but no foreign source pass four arguments (name, timestamp, dependencies, group)
- // to mw.loader.register()
- elseif ( $source === 'local' ) {
- $registrations[] = array( $name, $mtime, $deps, $group );
- }
- // Modules with a foreign source pass five arguments (name, timestamp, dependencies, group, source)
- // to mw.loader.register()
- else {
- $registrations[] = array( $name, $mtime, $deps, $group, $source );
- }
+ $registrations[] = array( $name, $mtime, $deps, $group, $source );
}
}
$out .= ResourceLoader::makeLoaderRegisterScript( $registrations );
@@ -219,7 +224,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
"};\n";
// Conditional script injection
- $scriptTag = Html::linkedScript( $wgLoadScript . '?' . wfArrayToCGI( $query ) );
+ $scriptTag = Html::linkedScript( $wgLoadScript . '?' . wfArrayToCgi( $query ) );
$out .= "if ( isCompatible() ) {\n" .
"\t" . Xml::encodeJsCall( 'document.write', array( $scriptTag ) ) .
"}\n" .
diff --git a/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php b/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php
index 0e95d964..bdb240e0 100644
--- a/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php
+++ b/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php
@@ -48,7 +48,7 @@ class ResourceLoaderUserCSSPrefsModule extends ResourceLoaderModule {
global $wgUser;
return $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() );
}
-
+
/**
* @param $context ResourceLoaderContext
* @return array
diff --git a/includes/resourceloader/ResourceLoaderUserTokensModule.php b/includes/resourceloader/ResourceLoaderUserTokensModule.php
index 62d096a6..6d787c50 100644
--- a/includes/resourceloader/ResourceLoaderUserTokensModule.php
+++ b/includes/resourceloader/ResourceLoaderUserTokensModule.php
@@ -43,7 +43,8 @@ class ResourceLoaderUserTokensModule extends ResourceLoaderModule {
return array(
'editToken' => $wgUser->getEditToken(),
- 'watchToken' => ApiQueryInfo::getWatchToken(null, null),
+ 'patrolToken' => ApiQueryRecentChanges::getPatrolToken( null, null ),
+ 'watchToken' => ApiQueryInfo::getWatchToken( null, null ),
);
}
diff --git a/includes/resourceloader/ResourceLoaderWikiModule.php b/includes/resourceloader/ResourceLoaderWikiModule.php
index ee8dd1e5..6c60d474 100644
--- a/includes/resourceloader/ResourceLoaderWikiModule.php
+++ b/includes/resourceloader/ResourceLoaderWikiModule.php
@@ -42,7 +42,20 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
/* Abstract Protected Methods */
/**
+ * Subclasses should return an associative array of resources in the module.
+ * Keys should be the title of a page in the MediaWiki or User namespace.
+ *
+ * Values should be a nested array of options. The supported keys are 'type' and
+ * (CSS only) 'media'.
+ *
+ * For scripts, 'type' should be 'script'.
+ *
+ * For stylesheets, 'type' should be 'style'.
+ * There is an optional media key, the value of which can be the
+ * medium ('screen', 'print', etc.) of the stylesheet.
+ *
* @param $context ResourceLoaderContext
+ * @return array
*/
abstract protected function getPages( ResourceLoaderContext $context );
@@ -75,7 +88,22 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
if ( !$revision ) {
return null;
}
- return $revision->getRawText();
+
+ $content = $revision->getContent( Revision::RAW );
+
+ if ( !$content ) {
+ wfDebug( __METHOD__ . "failed to load content of JS/CSS page!\n" );
+ return null;
+ }
+
+ $model = $content->getModel();
+
+ if ( $model !== CONTENT_MODEL_CSS && $model !== CONTENT_MODEL_JAVASCRIPT ) {
+ wfDebug( __METHOD__ . "bad content model $model for JS/CSS page!\n" );
+ return null;
+ }
+
+ return $content->getNativeData(); //NOTE: this is safe, we know it's JS or CSS
}
/* Methods */
@@ -98,7 +126,7 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
if ( strval( $script ) !== '' ) {
$script = $this->validateScriptFile( $titleText, $script );
if ( strpos( $titleText, '*/' ) === false ) {
- $scripts .= "/* $titleText */\n";
+ $scripts .= "/* $titleText */\n";
}
$scripts .= $script . "\n";
}
@@ -119,7 +147,7 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
continue;
}
$title = Title::newFromText( $titleText );
- if ( !$title || $title->isRedirect() ) {
+ if ( !$title || $title->isRedirect() ) {
continue;
}
$media = isset( $options['media'] ) ? $options['media'] : 'all';
@@ -135,7 +163,7 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
$styles[$media] = array();
}
if ( strpos( $titleText, '*/' ) === false ) {
- $style = "/* $titleText */\n" . $style;
+ $style = "/* $titleText */\n" . $style;
}
$styles[$media][] = $style;
}
diff --git a/includes/revisiondelete/RevisionDelete.php b/includes/revisiondelete/RevisionDelete.php
index 6ceadff4..1ace3836 100644
--- a/includes/revisiondelete/RevisionDelete.php
+++ b/includes/revisiondelete/RevisionDelete.php
@@ -398,7 +398,6 @@ class RevDel_ArchiveItem extends RevDel_RevisionItem {
}
}
-
/**
* Item class for a archive table row by ar_rev_id -- actually
* used via RevDel_RevisionList.
@@ -424,7 +423,7 @@ class RevDel_ArchivedRevisionItem extends RevDel_ArchiveItem {
$dbw->update( 'archive',
array( 'ar_deleted' => $bits ),
array( 'ar_rev_id' => $this->row->ar_rev_id,
- 'ar_deleted' => $this->getBits()
+ 'ar_deleted' => $this->getBits()
),
__METHOD__ );
return (bool)$dbw->affectedRows();
@@ -454,7 +453,9 @@ class RevDel_FileList extends RevDel_List {
foreach( $this->ids as $timestamp ) {
$archiveNames[] = $timestamp . '!' . $this->title->getDBkey();
}
- return $db->select( 'oldimage', '*',
+ return $db->select(
+ 'oldimage',
+ OldLocalFile::selectFields(),
array(
'oi_name' => $this->title->getDBkey(),
'oi_archive_name' => $archiveNames
@@ -695,7 +696,9 @@ class RevDel_ArchivedFileList extends RevDel_FileList {
*/
public function doQuery( $db ) {
$ids = array_map( 'intval', $this->ids );
- return $db->select( 'filearchive', '*',
+ return $db->select(
+ 'filearchive',
+ ArchivedFile::selectFields(),
array(
'fa_name' => $this->title->getDBkey(),
'fa_id' => $ids
@@ -899,7 +902,7 @@ class RevDel_LogItem extends RevDel_Item {
$action = $formatter->getActionText();
// Comment
$comment = $this->list->getLanguage()->getDirMark() . Linker::commentBlock( $this->row->log_comment );
- if( LogEventsList::isDeleted($this->row,LogPage::DELETED_COMMENT) ) {
+ if( LogEventsList::isDeleted( $this->row, LogPage::DELETED_COMMENT ) ) {
$comment = '<span class="history-deleted">' . $comment . '</span>';
}
diff --git a/includes/revisiondelete/RevisionDeleteAbstracts.php b/includes/revisiondelete/RevisionDeleteAbstracts.php
index 4f58099f..b2108de6 100644
--- a/includes/revisiondelete/RevisionDeleteAbstracts.php
+++ b/includes/revisiondelete/RevisionDeleteAbstracts.php
@@ -47,7 +47,7 @@ abstract class RevDel_List extends RevisionListBase {
* Set the visibility for the revisions in this list. Logging and
* transactions are done here.
*
- * @param $params array Associative array of parameters. Members are:
+ * @param array $params Associative array of parameters. Members are:
* value: The integer value to set the visibility to
* comment: The log comment.
* @return Status
@@ -176,7 +176,7 @@ abstract class RevDel_List extends RevisionListBase {
/**
* Record a log entry on the action
- * @param $params array Associative array of parameters:
+ * @param array $params Associative array of parameters:
* newBits: The new value of the *_deleted bitfield
* oldBits: The old value of the *_deleted bitfield.
* title: The target title
@@ -184,6 +184,7 @@ abstract class RevDel_List extends RevisionListBase {
* comment: The log comment
* authorsIds: The array of the user IDs of the offenders
* authorsIPs: The array of the IP/anon user offenders
+ * @throws MWException
*/
protected function updateLog( $params ) {
// Get the URL param's corresponding DB field
@@ -219,7 +220,7 @@ abstract class RevDel_List extends RevisionListBase {
/**
* Get log parameter array.
- * @param $params array Associative array of log parameters, same as updateLog()
+ * @param array $params Associative array of log parameters, same as updateLog()
* @return array
*/
public function getLogParams( $params ) {
diff --git a/includes/revisiondelete/RevisionDeleter.php b/includes/revisiondelete/RevisionDeleter.php
index c59edc2a..fe351c51 100644
--- a/includes/revisiondelete/RevisionDeleter.php
+++ b/includes/revisiondelete/RevisionDeleter.php
@@ -31,12 +31,12 @@ class RevisionDeleter {
* Checks for a change in the bitfield for a certain option and updates the
* provided array accordingly.
*
- * @param $desc String: description to add to the array if the option was
+ * @param string $desc description to add to the array if the option was
* enabled / disabled.
* @param $field Integer: the bitmask describing the single option.
* @param $diff Integer: the xor of the old and new bitfields.
* @param $new Integer: the new bitfield
- * @param $arr Array: the array to update.
+ * @param array $arr the array to update.
*/
protected static function checkItem( $desc, $field, $diff, $new, &$arr ) {
if( $diff & $field ) {
diff --git a/includes/search/SearchEngine.php b/includes/search/SearchEngine.php
index 27a321ac..6b3e62b1 100644
--- a/includes/search/SearchEngine.php
+++ b/includes/search/SearchEngine.php
@@ -45,7 +45,7 @@ class SearchEngine {
*/
protected $db;
- function __construct($db = null) {
+ function __construct( $db = null ) {
if ( $db ) {
$this->db = $db;
} else {
@@ -58,7 +58,7 @@ class SearchEngine {
* If title searches are not supported or disabled, return null.
* STUB
*
- * @param $term String: raw search term
+ * @param string $term raw search term
* @return SearchResultSet
*/
function searchText( $term ) {
@@ -70,7 +70,7 @@ class SearchEngine {
* If title searches are not supported or disabled, return null.
* STUB
*
- * @param $term String: raw search term
+ * @param string $term raw search term
* @return SearchResultSet
*/
function searchTitle( $term ) {
@@ -118,7 +118,7 @@ class SearchEngine {
* on text to be used for searching or updating search index.
* Default implementation does nothing (simply returns $string).
*
- * @param $string string: String to process
+ * @param string $string String to process
* @return string
*/
public function normalizeText( $string ) {
@@ -163,7 +163,7 @@ class SearchEngine {
/**
* Really find the title match.
- * @return null|\Title
+ * @return null|Title
*/
private static function getNearMatchInternal( $searchterm ) {
global $wgContLang, $wgEnableSearchContributorsByIP;
@@ -183,10 +183,15 @@ class SearchEngine {
# Exact match? No need to look further.
$title = Title::newFromText( $term );
- if ( is_null( $title ) ){
+ if ( is_null( $title ) ) {
return null;
}
+ # Try files if searching in the Media: namespace
+ if ( $title->getNamespace() == NS_MEDIA ) {
+ $title = Title::makeTitle( NS_FILE, $title->getText() );
+ }
+
if ( $title->isSpecialPage() || $title->isExternal() || $title->exists() ) {
return $title;
}
@@ -197,22 +202,23 @@ class SearchEngine {
return $title;
}
+ if ( !wfRunHooks( 'SearchAfterNoDirectMatch', array( $term, &$title ) ) ) {
+ return $title;
+ }
+
# Now try all lower case (i.e. first letter capitalized)
- #
$title = Title::newFromText( $wgContLang->lc( $term ) );
if ( $title && $title->exists() ) {
return $title;
}
# Now try capitalized string
- #
$title = Title::newFromText( $wgContLang->ucwords( $term ) );
if ( $title && $title->exists() ) {
return $title;
}
# Now try all upper case
- #
$title = Title::newFromText( $wgContLang->uc( $term ) );
if ( $title && $title->exists() ) {
return $title;
@@ -233,7 +239,6 @@ class SearchEngine {
$title = Title::newFromText( $searchterm );
-
# Entering an IP address goes to the contributions page
if ( $wgEnableSearchContributorsByIP ) {
if ( ( $title->getNamespace() == NS_USER && User::isIP( $title->getText() ) )
@@ -242,7 +247,6 @@ class SearchEngine {
}
}
-
# Entering a user goes to the user page whether it's there or not
if ( $title->getNamespace() == NS_USER ) {
return $title;
@@ -669,7 +673,6 @@ class SearchResultTooMany {
# # Some search engines may bail out if too many matches are found
}
-
/**
* @todo FIXME: This class is horribly factored. It would probably be better to
* have a useful base class to which you pass some standard information, then
@@ -791,21 +794,26 @@ class SearchResult {
*/
protected function initText() {
if ( !isset( $this->mText ) ) {
- if ( $this->mRevision != null )
- $this->mText = $this->mRevision->getText();
- else // TODO: can we fetch raw wikitext for commons images?
+ if ( $this->mRevision != null ) {
+ //TODO: if we could plug in some code that knows about special content models *and* about
+ // special features of the search engine, the search could benefit.
+ $content = $this->mRevision->getContent();
+ $this->mText = $content ? $content->getTextForSearchIndex() : '';
+ } else { // TODO: can we fetch raw wikitext for commons images?
$this->mText = '';
-
+ }
}
}
/**
- * @param $terms Array: terms to highlight
+ * @param array $terms terms to highlight
* @return String: highlighted text snippet, null (and not '') if not supported
*/
function getTextSnippet( $terms ) {
global $wgUser, $wgAdvancedSearchHighlighting;
$this->initText();
+
+ // TODO: make highliter take a content object. Make ContentHandler a factory for SearchHighliter.
list( $contextlines, $contextchars ) = SearchEngine::userHighlightPrefs( $wgUser );
$h = new SearchHighlighter();
if ( $wgAdvancedSearchHighlighting )
@@ -815,7 +823,7 @@ class SearchResult {
}
/**
- * @param $terms Array: terms to highlight
+ * @param array $terms terms to highlight
* @return String: highlighted title, '' if not supported
*/
function getTitleSnippet( $terms ) {
@@ -823,7 +831,7 @@ class SearchResult {
}
/**
- * @param $terms Array: terms to highlight
+ * @param array $terms terms to highlight
* @return String: highlighted redirect name (redirect to this page), '' if none or not supported
*/
function getRedirectSnippet( $terms ) {
@@ -934,7 +942,7 @@ class SearchHighlighter {
* Default implementation of wikitext highlighting
*
* @param $text String
- * @param $terms Array: terms to highlight (unescaped)
+ * @param array $terms terms to highlight (unescaped)
* @param $contextlines Integer
* @param $contextchars Integer
* @return String
@@ -962,7 +970,7 @@ class SearchHighlighter {
}
$spat .= '/';
$textExt = array(); // text extracts
- $otherExt = array(); // other extracts
+ $otherExt = array(); // other extracts
wfProfileIn( "$fname-split" );
$start = 0;
$textLen = strlen( $text );
@@ -1131,8 +1139,8 @@ class SearchHighlighter {
// add more lines
$add = $index + 1;
while ( $len < $targetchars - 20
- && array_key_exists( $add, $all )
- && !array_key_exists( $add, $snippets ) ) {
+ && array_key_exists( $add, $all )
+ && !array_key_exists( $add, $snippets ) ) {
$offsets[$add] = 0;
$tt = "\n" . $this->extract( $all[$add], 0, $targetchars - $len, $offsets[$add] );
$extended[$add] = $tt;
@@ -1142,7 +1150,7 @@ class SearchHighlighter {
}
}
- // $snippets = array_map('htmlspecialchars', $extended);
+ // $snippets = array_map( 'htmlspecialchars', $extended );
$snippets = $extended;
$last = - 1;
$extract = '';
@@ -1177,7 +1185,7 @@ class SearchHighlighter {
/**
* Split text into lines and add it to extracts array
*
- * @param $extracts Array: index -> $line
+ * @param array $extracts index -> $line
* @param $count Integer
* @param $text String
*/
@@ -1232,7 +1240,7 @@ class SearchHighlighter {
$posEnd = $end;
}
- if ( $end > $start ) {
+ if ( $end > $start ) {
return substr( $text, $start, $end - $start );
} else {
return '';
@@ -1272,12 +1280,12 @@ class SearchHighlighter {
/**
* Search extracts for a pattern, and return snippets
*
- * @param $pattern String: regexp for matching lines
- * @param $extracts Array: extracts to search
+ * @param string $pattern regexp for matching lines
+ * @param array $extracts extracts to search
* @param $linesleft Integer: number of extracts to make
* @param $contextchars Integer: length of snippet
- * @param $out Array: map for highlighted snippets
- * @param $offsets Array: map of starting points of snippets
+ * @param array $out map for highlighted snippets
+ * @param array $offsets map of starting points of snippets
* @protected
*/
function process( $pattern, $extracts, &$linesleft, &$contextchars, &$out, &$offsets ) {
@@ -1321,12 +1329,12 @@ class SearchHighlighter {
$fname = __METHOD__;
wfProfileIn( $fname );
- // $text = preg_replace("/'{2,5}/", "", $text);
- // $text = preg_replace("/\[[a-z]+:\/\/[^ ]+ ([^]]+)\]/", "\\2", $text);
- // $text = preg_replace("/\[\[([^]|]+)\]\]/", "\\1", $text);
- // $text = preg_replace("/\[\[([^]]+\|)?([^|]]+)\]\]/", "\\2", $text);
- // $text = preg_replace("/\\{\\|(.*?)\\|\\}/", "", $text);
- // $text = preg_replace("/\\[\\[[A-Za-z_-]+:([^|]+?)\\]\\]/", "", $text);
+ // $text = preg_replace( "/'{2,5}/", "", $text );
+ // $text = preg_replace( "/\[[a-z]+:\/\/[^ ]+ ([^]]+)\]/", "\\2", $text );
+ // $text = preg_replace( "/\[\[([^]|]+)\]\]/", "\\1", $text );
+ // $text = preg_replace( "/\[\[([^]]+\|)?([^|]]+)\]\]/", "\\2", $text );
+ // $text = preg_replace( "/\\{\\|(.*?)\\|\\}/", "", $text );
+ // $text = preg_replace( "/\\[\\[[A-Za-z_-]+:([^|]+?)\\]\\]/", "", $text );
$text = preg_replace( "/\\{\\{([^|]+?)\\}\\}/", "", $text );
$text = preg_replace( "/\\{\\{([^|]+\\|)(.*?)\\}\\}/", "\\2", $text );
$text = preg_replace( "/\\[\\[([^|]+?)\\]\\]/", "\\1", $text );
@@ -1408,8 +1416,7 @@ class SearchHighlighter {
$line = htmlspecialchars( $pre . $found . $post );
$pat2 = '/(' . $terms . ")/i";
- $line = preg_replace( $pat2,
- "<span class='searchmatch'>\\1</span>", $line );
+ $line = preg_replace( $pat2, "<span class='searchmatch'>\\1</span>", $line );
$extract .= "${line}\n";
}
diff --git a/includes/search/SearchIBM_DB2.php b/includes/search/SearchIBM_DB2.php
deleted file mode 100644
index 51ed000f..00000000
--- a/includes/search/SearchIBM_DB2.php
+++ /dev/null
@@ -1,234 +0,0 @@
-<?php
-/**
- * IBM DB2 search engine
- *
- * Copyright © 2004 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
- *
- * 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 Search
- */
-
-/**
- * Search engine hook base class for IBM DB2
- * @ingroup Search
- */
-class SearchIBM_DB2 extends SearchEngine {
-
- /**
- * Creates an instance of this class
- * @param $db DatabaseIbm_db2: database object
- */
- function __construct($db) {
- parent::__construct( $db );
- }
-
- /**
- * Perform a full text search query and return a result set.
- *
- * @param $term String: raw search term
- * @return SqlSearchResultSet
- */
- function searchText( $term ) {
- $resultSet = $this->db->resultObject($this->db->query($this->getQuery($this->filter($term), true)));
- return new SqlSearchResultSet($resultSet, $this->searchTerms);
- }
-
- /**
- * Perform a title-only search query and return a result set.
- *
- * @param $term String: taw search term
- * @return SqlSearchResultSet
- */
- function searchTitle($term) {
- $resultSet = $this->db->resultObject($this->db->query($this->getQuery($this->filter($term), false)));
- return new SqlSearchResultSet($resultSet, $this->searchTerms);
- }
-
-
- /**
- * Return a partial WHERE clause to exclude redirects, if so set
- * @return String
- */
- function queryRedirect() {
- if ($this->showRedirects) {
- return '';
- } else {
- return 'AND page_is_redirect=0';
- }
- }
-
- /**
- * Return a partial WHERE clause to limit the search to the given namespaces
- * @return String
- */
- function queryNamespaces() {
- if( is_null($this->namespaces) )
- return '';
- $namespaces = implode(',', $this->namespaces);
- if ($namespaces == '') {
- $namespaces = '0';
- }
- return 'AND page_namespace IN (' . $namespaces . ')';
- }
-
- /**
- * Return a LIMIT clause to limit results on the query.
- * @return String
- */
- function queryLimit( $sql ) {
- return $this->db->limitResult($sql, $this->limit, $this->offset);
- }
-
- /**
- * Does not do anything for generic search engine
- * subclasses may define this though
- * @return String
- */
- function queryRanking($filteredTerm, $fulltext) {
- // requires Net Search Extender or equivalent
- // return ' ORDER BY score(1)';
- return '';
- }
-
- /**
- * Construct the full SQL query to do the search.
- * The guts shoulds be constructed in queryMain()
- * @param $filteredTerm String
- * @param $fulltext Boolean
- * @return String
- */
- function getQuery( $filteredTerm, $fulltext ) {
- return $this->queryLimit($this->queryMain($filteredTerm, $fulltext) . ' ' .
- $this->queryRedirect() . ' ' .
- $this->queryNamespaces() . ' ' .
- $this->queryRanking( $filteredTerm, $fulltext ) . ' ');
- }
-
-
- /**
- * Picks which field to index on, depending on what type of query.
- * @param $fulltext Boolean
- * @return String
- */
- function getIndexField($fulltext) {
- return $fulltext ? 'si_text' : 'si_title';
- }
-
- /**
- * Get the base part of the search query.
- *
- * @param $filteredTerm String
- * @param $fulltext Boolean
- * @return String
- */
- function queryMain( $filteredTerm, $fulltext ) {
- $match = $this->parseQuery($filteredTerm, $fulltext);
- $page = $this->db->tableName('page');
- $searchindex = $this->db->tableName('searchindex');
- return 'SELECT page_id, page_namespace, page_title ' .
- "FROM $page,$searchindex " .
- 'WHERE page_id=si_page AND ' . $match;
- }
-
- /** @todo document
- * @return string
- */
- function parseQuery($filteredText, $fulltext) {
- global $wgContLang;
- $lc = SearchEngine::legalSearchChars();
- $this->searchTerms = array();
-
- # @todo FIXME: This doesn't handle parenthetical expressions.
- $m = array();
- $q = array();
-
- if (preg_match_all('/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
- $filteredText, $m, PREG_SET_ORDER)) {
- foreach($m as $terms) {
-
- // Search terms in all variant forms, only
- // apply on wiki with LanguageConverter
- $temp_terms = $wgContLang->autoConvertToAllVariants( $terms[2] );
- if( is_array( $temp_terms )) {
- $temp_terms = array_unique( array_values( $temp_terms ));
- foreach( $temp_terms as $t )
- $q[] = $terms[1] . $wgContLang->normalizeForSearch( $t );
- }
- else
- $q[] = $terms[1] . $wgContLang->normalizeForSearch( $terms[2] );
-
- if (!empty($terms[3])) {
- $regexp = preg_quote( $terms[3], '/' );
- if ($terms[4])
- $regexp .= "[0-9A-Za-z_]+";
- } else {
- $regexp = preg_quote(str_replace('"', '', $terms[2]), '/');
- }
- $this->searchTerms[] = $regexp;
- }
- }
-
- $searchon = $this->db->strencode(join(',', $q));
- $field = $this->getIndexField($fulltext);
-
- // requires Net Search Extender or equivalent
- //return " CONTAINS($field, '$searchon') > 0 ";
-
- return " lcase($field) LIKE lcase('%$searchon%')";
- }
-
- /**
- * Create or update the search index record for the given page.
- * Title and text should be pre-processed.
- *
- * @param $id Integer
- * @param $title String
- * @param $text String
- */
- function update($id, $title, $text) {
- $dbw = wfGetDB(DB_MASTER);
- $dbw->replace('searchindex',
- array('si_page'),
- array(
- 'si_page' => $id,
- 'si_title' => $title,
- 'si_text' => $text
- ), 'SearchIBM_DB2::update' );
- // ?
- //$dbw->query("CALL ctx_ddl.sync_index('si_text_idx')");
- //$dbw->query("CALL ctx_ddl.sync_index('si_title_idx')");
- }
-
- /**
- * Update a search index record's title only.
- * Title should be pre-processed.
- *
- * @param $id Integer
- * @param $title String
- */
- function updateTitle($id, $title) {
- $dbw = wfGetDB(DB_MASTER);
-
- $dbw->update('searchindex',
- array('si_title' => $title),
- array('si_page' => $id),
- 'SearchIBM_DB2::updateTitle',
- array());
- }
-}
diff --git a/includes/search/SearchMssql.php b/includes/search/SearchMssql.php
index 69c92ba3..163d9dc3 100644
--- a/includes/search/SearchMssql.php
+++ b/includes/search/SearchMssql.php
@@ -38,7 +38,7 @@ class SearchMssql extends SearchEngine {
/**
* Perform a full text search query and return a result set.
*
- * @param $term String: raw search term
+ * @param string $term raw search term
* @return MssqlSearchResultSet
* @access public
*/
@@ -50,7 +50,7 @@ class SearchMssql extends SearchEngine {
/**
* Perform a title-only search query and return a result set.
*
- * @param $term String: raw search term
+ * @param string $term raw search term
* @return MssqlSearchResultSet
* @access public
*/
@@ -59,7 +59,6 @@ class SearchMssql extends SearchEngine {
return new MssqlSearchResultSet( $resultSet, $this->searchTerms );
}
-
/**
* Return a partial WHERE clause to exclude redirects, if so set
*
@@ -78,7 +77,7 @@ class SearchMssql extends SearchEngine {
* Return a partial WHERE clause to limit the search to the given namespaces
*
* @return String
- * @private
+ * @private
*/
function queryNamespaces() {
$namespaces = implode( ',', $this->namespaces );
@@ -144,9 +143,9 @@ class SearchMssql extends SearchEngine {
*/
function queryMain( $filteredTerm, $fulltext ) {
$match = $this->parseQuery( $filteredTerm, $fulltext );
- $page = $this->db->tableName( 'page' );
+ $page = $this->db->tableName( 'page' );
$searchindex = $this->db->tableName( 'searchindex' );
-
+
return 'SELECT page_id, page_namespace, page_title, ftindex.[RANK]' .
"FROM $page,FREETEXTTABLE($searchindex , $match, LANGUAGE 'English') as ftindex " .
'WHERE page_id=ftindex.[KEY] ';
@@ -192,11 +191,11 @@ class SearchMssql extends SearchEngine {
* @param $id Integer
* @param $title String
* @param $text String
- * @return bool|\ResultWrapper
+ * @return bool|ResultWrapper
*/
function update( $id, $title, $text ) {
// We store the column data as UTF-8 byte order marked binary stream
- // because we are invoking the plain text IFilter on it so that, and we want it
+ // because we are invoking the plain text IFilter on it so that, and we want it
// to properly decode the stream as UTF-8. SQL doesn't support UTF8 as a data type
// but the indexer will correctly handle it by this method. Since all we are doing
// is passing this data to the indexer and never retrieving it via PHP, this will save space
@@ -215,7 +214,7 @@ class SearchMssql extends SearchEngine {
*
* @param $id Integer
* @param $title String
- * @return bool|\ResultWrapper
+ * @return bool|ResultWrapper
*/
function updateTitle( $id, $title ) {
$table = $this->db->tableName( 'searchindex' );
@@ -253,5 +252,3 @@ class MssqlSearchResultSet extends SearchResultSet {
return new SearchResult( $row );
}
}
-
-
diff --git a/includes/search/SearchMySQL.php b/includes/search/SearchMySQL.php
index 5cee03e0..4a501fd0 100644
--- a/includes/search/SearchMySQL.php
+++ b/includes/search/SearchMySQL.php
@@ -58,7 +58,7 @@ class SearchMySQL extends SearchEngine {
# @todo FIXME: This doesn't handle parenthetical expressions.
$m = array();
if( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
- $filteredText, $m, PREG_SET_ORDER ) ) {
+ $filteredText, $m, PREG_SET_ORDER ) ) {
foreach( $m as $bits ) {
@list( /* all */, $modifier, $term, $nonQuoted, $wildcard ) = $bits;
@@ -156,7 +156,7 @@ class SearchMySQL extends SearchEngine {
/**
* Perform a full text search query and return a result set.
*
- * @param $term String: raw search term
+ * @param string $term raw search term
* @return MySQLSearchResultSet
*/
function searchText( $term ) {
@@ -166,7 +166,7 @@ class SearchMySQL extends SearchEngine {
/**
* Perform a title-only search query and return a result set.
*
- * @param $term String: raw search term
+ * @param string $term raw search term
* @return MySQLSearchResultSet
*/
function searchTitle( $term ) {
@@ -221,7 +221,7 @@ class SearchMySQL extends SearchEngine {
*/
protected function queryFeatures( &$query ) {
foreach ( $this->features as $feature => $value ) {
- if ( $feature === 'list-redirects' && !$value ) {
+ if ( $feature === 'list-redirects' && !$value ) {
$query['conds']['page_is_redirect'] = 0;
} elseif( $feature === 'title-suffix-filter' && $value ) {
$query['conds'][] = 'page_title' . $this->db->buildLike( $this->db->anyString(), $value );
diff --git a/includes/search/SearchOracle.php b/includes/search/SearchOracle.php
index a2db52f3..b0ea97fe 100644
--- a/includes/search/SearchOracle.php
+++ b/includes/search/SearchOracle.php
@@ -29,77 +29,76 @@
* @ingroup Search
*/
class SearchOracle extends SearchEngine {
-
- private $reservedWords = array ('ABOUT' => 1,
- 'ACCUM' => 1,
- 'AND' => 1,
- 'BT' => 1,
- 'BTG' => 1,
- 'BTI' => 1,
+
+ private $reservedWords = array ('ABOUT' => 1,
+ 'ACCUM' => 1,
+ 'AND' => 1,
+ 'BT' => 1,
+ 'BTG' => 1,
+ 'BTI' => 1,
'BTP' => 1,
- 'FUZZY' => 1,
- 'HASPATH' => 1,
- 'INPATH' => 1,
- 'MINUS' => 1,
- 'NEAR' => 1,
+ 'FUZZY' => 1,
+ 'HASPATH' => 1,
+ 'INPATH' => 1,
+ 'MINUS' => 1,
+ 'NEAR' => 1,
'NOT' => 1,
- 'NT' => 1,
- 'NTG' => 1,
- 'NTI' => 1,
- 'NTP' => 1,
- 'OR' => 1,
- 'PT' => 1,
- 'RT' => 1,
+ 'NT' => 1,
+ 'NTG' => 1,
+ 'NTI' => 1,
+ 'NTP' => 1,
+ 'OR' => 1,
+ 'PT' => 1,
+ 'RT' => 1,
'SQE' => 1,
- 'SYN' => 1,
- 'TR' => 1,
- 'TRSYN' => 1,
- 'TT' => 1,
+ 'SYN' => 1,
+ 'TR' => 1,
+ 'TRSYN' => 1,
+ 'TT' => 1,
'WITHIN' => 1);
/**
* Creates an instance of this class
* @param $db DatabasePostgres: database object
*/
- function __construct($db) {
+ function __construct( $db ) {
parent::__construct( $db );
}
/**
* Perform a full text search query and return a result set.
*
- * @param $term String: raw search term
+ * @param string $term raw search term
* @return SqlSearchResultSet
*/
function searchText( $term ) {
- if ($term == '')
- return new SqlSearchResultSet(false, '');
+ if ( $term == '' )
+ return new SqlSearchResultSet( false, '' );
- $resultSet = $this->db->resultObject($this->db->query($this->getQuery($this->filter($term), true)));
- return new SqlSearchResultSet($resultSet, $this->searchTerms);
+ $resultSet = $this->db->resultObject( $this->db->query( $this->getQuery( $this->filter( $term ), true ) ) );
+ return new SqlSearchResultSet( $resultSet, $this->searchTerms );
}
/**
* Perform a title-only search query and return a result set.
*
- * @param $term String: raw search term
+ * @param string $term raw search term
* @return SqlSearchResultSet
*/
- function searchTitle($term) {
- if ($term == '')
- return new SqlSearchResultSet(false, '');
+ function searchTitle( $term ) {
+ if ( $term == '' )
+ return new SqlSearchResultSet( false, '' );
- $resultSet = $this->db->resultObject($this->db->query($this->getQuery($this->filter($term), false)));
- return new MySQLSearchResultSet($resultSet, $this->searchTerms);
+ $resultSet = $this->db->resultObject( $this->db->query( $this->getQuery( $this->filter( $term ), false ) ) );
+ return new MySQLSearchResultSet( $resultSet, $this->searchTerms );
}
-
/**
* Return a partial WHERE clause to exclude redirects, if so set
* @return String
*/
function queryRedirect() {
- if ($this->showRedirects) {
+ if ( $this->showRedirects ) {
return '';
} else {
return 'AND page_is_redirect=0';
@@ -111,7 +110,7 @@ class SearchOracle extends SearchEngine {
* @return String
*/
function queryNamespaces() {
- if( is_null($this->namespaces) )
+ if( is_null( $this->namespaces ) )
return '';
if ( !count( $this->namespaces ) ) {
$namespaces = '0';
@@ -129,7 +128,7 @@ class SearchOracle extends SearchEngine {
* @return String
*/
function queryLimit( $sql ) {
- return $this->db->limitResult($sql, $this->limit, $this->offset);
+ return $this->db->limitResult( $sql, $this->limit, $this->offset );
}
/**
@@ -150,19 +149,18 @@ class SearchOracle extends SearchEngine {
* @return String
*/
function getQuery( $filteredTerm, $fulltext ) {
- return $this->queryLimit($this->queryMain($filteredTerm, $fulltext) . ' ' .
+ return $this->queryLimit( $this->queryMain( $filteredTerm, $fulltext ) . ' ' .
$this->queryRedirect() . ' ' .
$this->queryNamespaces() . ' ' .
- $this->queryRanking( $filteredTerm, $fulltext ) . ' ');
+ $this->queryRanking( $filteredTerm, $fulltext ) . ' ' );
}
-
/**
* Picks which field to index on, depending on what type of query.
* @param $fulltext Boolean
* @return String
*/
- function getIndexField($fulltext) {
+ function getIndexField( $fulltext ) {
return $fulltext ? 'si_text' : 'si_title';
}
@@ -174,9 +172,9 @@ class SearchOracle extends SearchEngine {
* @return String
*/
function queryMain( $filteredTerm, $fulltext ) {
- $match = $this->parseQuery($filteredTerm, $fulltext);
- $page = $this->db->tableName('page');
- $searchindex = $this->db->tableName('searchindex');
+ $match = $this->parseQuery( $filteredTerm, $fulltext );
+ $page = $this->db->tableName( 'page' );
+ $searchindex = $this->db->tableName( 'searchindex' );
return 'SELECT page_id, page_namespace, page_title ' .
"FROM $page,$searchindex " .
'WHERE page_id=si_page AND ' . $match;
@@ -187,7 +185,7 @@ class SearchOracle extends SearchEngine {
* as part of a WHERE clause
* @return string
*/
- function parseQuery($filteredText, $fulltext) {
+ function parseQuery( $filteredText, $fulltext ) {
global $wgContLang;
$lc = SearchEngine::legalSearchChars();
$this->searchTerms = array();
@@ -195,9 +193,9 @@ class SearchOracle extends SearchEngine {
# @todo FIXME: This doesn't handle parenthetical expressions.
$m = array();
$searchon = '';
- if (preg_match_all('/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
- $filteredText, $m, PREG_SET_ORDER)) {
- foreach($m as $terms) {
+ if ( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
+ $filteredText, $m, PREG_SET_ORDER ) ) {
+ foreach( $m as $terms ) {
// Search terms in all variant forms, only
// apply on wiki with LanguageConverter
$temp_terms = $wgContLang->autoConvertToAllVariants( $terms[2] );
@@ -210,27 +208,26 @@ class SearchOracle extends SearchEngine {
else {
$searchon .= ($terms[1] == '-' ? ' ~' : ' & ') . $this->escapeTerm( $terms[2] );
}
- if (!empty($terms[3])) {
+ if ( !empty( $terms[3] ) ) {
$regexp = preg_quote( $terms[3], '/' );
- if ($terms[4])
+ if ( $terms[4] )
$regexp .= "[0-9A-Za-z_]+";
} else {
- $regexp = preg_quote(str_replace('"', '', $terms[2]), '/');
+ $regexp = preg_quote( str_replace( '"', '', $terms[2] ), '/' );
}
$this->searchTerms[] = $regexp;
}
}
-
- $searchon = $this->db->addQuotes(ltrim($searchon, ' &'));
- $field = $this->getIndexField($fulltext);
+ $searchon = $this->db->addQuotes( ltrim( $searchon, ' &' ) );
+ $field = $this->getIndexField( $fulltext );
return " CONTAINS($field, $searchon, 1) > 0 ";
}
- private function escapeTerm($t) {
+ private function escapeTerm( $t ) {
global $wgContLang;
- $t = $wgContLang->normalizeForSearch($t);
- $t = isset($this->reservedWords[strtoupper($t)]) ? '{'.$t.'}' : $t;
+ $t = $wgContLang->normalizeForSearch( $t );
+ $t = isset( $this->reservedWords[strtoupper( $t )] ) ? '{'.$t.'}' : $t;
$t = preg_replace('/^"(.*)"$/', '($1)', $t);
$t = preg_replace('/([-&|])/', '\\\\$1', $t);
return $t;
@@ -243,10 +240,10 @@ class SearchOracle extends SearchEngine {
* @param $title String
* @param $text String
*/
- function update($id, $title, $text) {
- $dbw = wfGetDB(DB_MASTER);
- $dbw->replace('searchindex',
- array('si_page'),
+ function update( $id, $title, $text ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->replace( 'searchindex',
+ array( 'si_page' ),
array(
'si_page' => $id,
'si_title' => $title,
@@ -254,13 +251,13 @@ class SearchOracle extends SearchEngine {
), 'SearchOracle::update' );
// Sync the index
- // We need to specify the DB name (i.e. user/schema) here so that
+ // We need to specify the DB name (i.e. user/schema) here so that
// it can work from the installer, where
// ALTER SESSION SET CURRENT_SCHEMA = ...
// was used.
- $dbw->query( "CALL ctx_ddl.sync_index(" .
+ $dbw->query( "CALL ctx_ddl.sync_index(" .
$dbw->addQuotes( $dbw->getDBname() . '.' . $dbw->tableName( 'si_text_idx', 'raw' ) ) . ")" );
- $dbw->query( "CALL ctx_ddl.sync_index(" .
+ $dbw->query( "CALL ctx_ddl.sync_index(" .
$dbw->addQuotes( $dbw->getDBname() . '.' . $dbw->tableName( 'si_title_idx', 'raw' ) ) . ")" );
}
@@ -271,17 +268,16 @@ class SearchOracle extends SearchEngine {
* @param $id Integer
* @param $title String
*/
- function updateTitle($id, $title) {
- $dbw = wfGetDB(DB_MASTER);
+ function updateTitle( $id, $title ) {
+ $dbw = wfGetDB( DB_MASTER );
- $dbw->update('searchindex',
- array('si_title' => $title),
- array('si_page' => $id),
+ $dbw->update( 'searchindex',
+ array( 'si_title' => $title ),
+ array( 'si_page' => $id ),
'SearchOracle::updateTitle',
- array());
+ array() );
}
-
public static function legalSearchChars() {
return "\"" . parent::legalSearchChars();
}
diff --git a/includes/search/SearchPostgres.php b/includes/search/SearchPostgres.php
index 68648894..56464e98 100644
--- a/includes/search/SearchPostgres.php
+++ b/includes/search/SearchPostgres.php
@@ -47,15 +47,15 @@ class SearchPostgres extends SearchEngine {
* Currently searches a page's current title (page.page_title) and
* latest revision article text (pagecontent.old_text)
*
- * @param $term String: raw search term
+ * @param string $term raw search term
* @return PostgresSearchResultSet
*/
function searchTitle( $term ) {
- $q = $this->searchQuery( $term , 'titlevector', 'page_title' );
- $olderror = error_reporting(E_ERROR);
+ $q = $this->searchQuery( $term, 'titlevector', 'page_title' );
+ $olderror = error_reporting( E_ERROR );
$resultSet = $this->db->resultObject( $this->db->query( $q, 'SearchPostgres', true ) );
- error_reporting($olderror);
- if (!$resultSet) {
+ error_reporting( $olderror );
+ if ( !$resultSet ) {
// Needed for "Query requires full scan, GIN doesn't support it"
return new SearchResultTooMany();
}
@@ -66,8 +66,8 @@ class SearchPostgres extends SearchEngine {
$q = $this->searchQuery( $term, 'textvector', 'old_text' );
$olderror = error_reporting(E_ERROR);
$resultSet = $this->db->resultObject( $this->db->query( $q, 'SearchPostgres', true ) );
- error_reporting($olderror);
- if (!$resultSet) {
+ error_reporting( $olderror );
+ if ( !$resultSet ) {
return new SearchResultTooMany();
}
return new PostgresSearchResultSet( $resultSet, $this->searchTerms );
@@ -99,16 +99,16 @@ class SearchPostgres extends SearchEngine {
$m = array();
if( preg_match_all('/([-!]?)(\S+)\s*/', $term, $m, PREG_SET_ORDER ) ) {
foreach( $m as $terms ) {
- if (strlen($terms[1])) {
+ if ( strlen( $terms[1] ) ) {
$searchstring .= ' & !';
}
- if (strtolower($terms[2]) === 'and') {
+ if ( strtolower( $terms[2] ) === 'and' ) {
$searchstring .= ' & ';
}
- elseif (strtolower($terms[2]) === 'or' or $terms[2] === '|') {
+ elseif ( strtolower( $terms[2] ) === 'or' or $terms[2] === '|' ) {
$searchstring .= ' | ';
}
- elseif (strtolower($terms[2]) === 'not') {
+ elseif ( strtolower( $terms[2] ) === 'not' ) {
$searchstring .= ' & !';
}
else {
@@ -133,7 +133,7 @@ class SearchPostgres extends SearchEngine {
$searchstring = preg_replace('/^[\'"](.*)[\'"]$/', "$1", $searchstring);
## Quote the whole thing
- $searchstring = $this->db->addQuotes($searchstring);
+ $searchstring = $this->db->addQuotes( $searchstring );
wfDebug( "parseQuery returned: $searchstring \n" );
@@ -154,15 +154,15 @@ class SearchPostgres extends SearchEngine {
## We need a separate query here so gin does not complain about empty searches
$SQL = "SELECT to_tsquery($searchstring)";
- $res = $this->db->query($SQL);
- if (!$res) {
+ $res = $this->db->query( $SQL );
+ if ( !$res ) {
## TODO: Better output (example to catch: one 'two)
- die ("Sorry, that was not a valid search string. Please go back and try again");
+ die( "Sorry, that was not a valid search string. Please go back and try again" );
}
$top = $res->fetchRow();
$top = $top[0];
- if ($top === "") { ## e.g. if only stopwords are used XXX return something better
+ if ( $top === "" ) { ## e.g. if only stopwords are used XXX return something better
$query = "SELECT page_id, page_namespace, page_title, 0 AS score ".
"FROM page p, revision r, pagecontent c WHERE p.page_latest = r.rev_id " .
"AND r.rev_text_id = c.old_id AND 1=0";
@@ -182,14 +182,14 @@ class SearchPostgres extends SearchEngine {
}
## Redirects
- if (! $this->showRedirects)
+ if ( !$this->showRedirects )
$query .= ' AND page_is_redirect = 0';
## Namespaces - defaults to 0
- if( !is_null($this->namespaces) ){ // null -> search all
- if ( count($this->namespaces) < 1)
+ if( !is_null( $this->namespaces ) ) { // null -> search all
+ if ( count( $this->namespaces ) < 1 ) {
$query .= ' AND page_namespace = 0';
- else {
+ } else {
$namespaces = $this->db->makeList( $this->namespaces );
$query .= " AND page_namespace IN ($namespaces)";
}
@@ -211,7 +211,7 @@ class SearchPostgres extends SearchEngine {
$SQL = "UPDATE pagecontent SET textvector = NULL WHERE old_id IN ".
"(SELECT rev_text_id FROM revision WHERE rev_page = " . intval( $pageid ) .
" ORDER BY rev_text_id DESC OFFSET 1)";
- $this->db->query($SQL);
+ $this->db->query( $SQL );
return true;
}
@@ -226,7 +226,7 @@ class SearchPostgres extends SearchEngine {
*/
class PostgresSearchResult extends SearchResult {
function __construct( $row ) {
- parent::__construct($row);
+ parent::__construct( $row );
$this->score = $row->score;
}
function getScore() {
diff --git a/includes/search/SearchSqlite.php b/includes/search/SearchSqlite.php
index e52e4fe3..f3f4788c 100644
--- a/includes/search/SearchSqlite.php
+++ b/includes/search/SearchSqlite.php
@@ -62,7 +62,7 @@ class SearchSqlite extends SearchEngine {
$m = array();
if( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
- $filteredText, $m, PREG_SET_ORDER ) ) {
+ $filteredText, $m, PREG_SET_ORDER ) ) {
foreach( $m as $bits ) {
@list( /* all */, $modifier, $term, $nonQuoted, $wildcard ) = $bits;
@@ -156,7 +156,7 @@ class SearchSqlite extends SearchEngine {
/**
* Perform a full text search query and return a result set.
*
- * @param $term String: raw search term
+ * @param string $term raw search term
* @return SqliteSearchResultSet
*/
function searchText( $term ) {
@@ -166,7 +166,7 @@ class SearchSqlite extends SearchEngine {
/**
* Perform a title-only search query and return a result set.
*
- * @param $term String: raw search term
+ * @param string $term raw search term
* @return SqliteSearchResultSet
*/
function searchTitle( $term ) {
@@ -196,7 +196,6 @@ class SearchSqlite extends SearchEngine {
return new SqliteSearchResultSet( $resultSet, $this->searchTerms, $total );
}
-
/**
* Return a partial WHERE clause to exclude redirects, if so set
* @return String
@@ -214,7 +213,7 @@ class SearchSqlite extends SearchEngine {
* @return String
*/
function queryNamespaces() {
- if( is_null($this->namespaces) )
+ if( is_null( $this->namespaces ) )
return ''; # search all
if ( !count( $this->namespaces ) ) {
$namespaces = '0';
@@ -266,7 +265,7 @@ class SearchSqlite extends SearchEngine {
*/
function queryMain( $filteredTerm, $fulltext ) {
$match = $this->parseQuery( $filteredTerm, $fulltext );
- $page = $this->db->tableName( 'page' );
+ $page = $this->db->tableName( 'page' );
$searchindex = $this->db->tableName( 'searchindex' );
return "SELECT $searchindex.rowid, page_namespace, page_title " .
"FROM $page,$searchindex " .
@@ -275,7 +274,7 @@ class SearchSqlite extends SearchEngine {
function getCountQuery( $filteredTerm, $fulltext ) {
$match = $this->parseQuery( $filteredTerm, $fulltext );
- $page = $this->db->tableName( 'page' );
+ $page = $this->db->tableName( 'page' );
$searchindex = $this->db->tableName( 'searchindex' );
return "SELECT COUNT(*) AS c " .
"FROM $page,$searchindex " .
@@ -317,7 +316,7 @@ class SearchSqlite extends SearchEngine {
* @param $id Integer
* @param $title String
*/
- function updateTitle( $id, $title ) {
+ function updateTitle( $id, $title ) {
if ( !$this->fulltextSearchSupported() ) {
return;
}
diff --git a/includes/search/SearchUpdate.php b/includes/search/SearchUpdate.php
index 40dd36c2..eabcda3e 100644
--- a/includes/search/SearchUpdate.php
+++ b/includes/search/SearchUpdate.php
@@ -34,7 +34,12 @@ class SearchUpdate implements DeferrableUpdate {
private $mTitleWords;
function __construct( $id, $title, $text = false ) {
- $nt = Title::newFromText( $title );
+ if ( is_string( $title ) ) {
+ $nt = Title::newFromText( $title );
+ } else {
+ $nt = $title;
+ }
+
if( $nt ) {
$this->mId = $id;
$this->mText = $text;
@@ -74,7 +79,7 @@ class SearchUpdate implements DeferrableUpdate {
$text = preg_replace( "/<\\/?\\s*[A-Za-z][^>]*?>/",
' ', $wgContLang->lc( " " . $text . " " ) ); # Strip HTML markup
$text = preg_replace( "/(^|\\n)==\\s*([^\\n]+)\\s*==(\\s)/sD",
- "\\1\\2 \\2 \\2\\3", $text ); # Emphasize headings
+ "\\1\\2 \\2 \\2\\3", $text ); # Emphasize headings
# Strip external URLs
$uc = "A-Za-z0-9_\\/:.,~%\\-+&;#?!=()@\\x80-\\xFF";
@@ -92,7 +97,7 @@ class SearchUpdate implements DeferrableUpdate {
$text = preg_replace( $pat2, " \\1 \\3", $text );
$text = preg_replace( "/([^{$lc}])([{$lc}]+)]]([a-z]+)/",
- "\\1\\2 \\2\\3", $text ); # Handle [[game]]s
+ "\\1\\2 \\2\\3", $text ); # Handle [[game]]s
# Strip all remaining non-search characters
$text = preg_replace( "/[^{$lc}]+/", " ", $text );
@@ -122,7 +127,7 @@ class SearchUpdate implements DeferrableUpdate {
wfRunHooks( 'SearchUpdate', array( $this->mId, $this->mNamespace, $this->mTitle, &$text ) );
# Perform the actual update
- $search->update($this->mId, $search->normalizeText( Title::indexTitle( $this->mNamespace, $this->mTitle ) ),
+ $search->update( $this->mId, $search->normalizeText( Title::indexTitle( $this->mNamespace, $this->mTitle ) ),
$search->normalizeText( $text ) );
wfProfileOut( __METHOD__ );
diff --git a/includes/site/MediaWikiSite.php b/includes/site/MediaWikiSite.php
new file mode 100644
index 00000000..05092723
--- /dev/null
+++ b/includes/site/MediaWikiSite.php
@@ -0,0 +1,352 @@
+<?php
+/**
+ * Class representing a MediaWiki site.
+ *
+ * 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 Site
+ * @license GNU GPL v2+
+ * @author John Erling Blad < jeblad@gmail.com >
+ * @author Daniel Kinzler
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+
+/**
+ * Class representing a MediaWiki site.
+ *
+ * @since 1.21
+ *
+ * @ingroup Site
+ */
+class MediaWikiSite extends Site {
+
+ const PATH_FILE = 'file_path';
+ const PATH_PAGE = 'page_path';
+
+ /**
+ * @since 1.21
+ * @deprecated Just use the constructor or the factory Site::newForType
+ *
+ * @param integer $globalId
+ *
+ * @return MediaWikiSite
+ */
+ public static function newFromGlobalId( $globalId ) {
+ $site = new static();
+ $site->setGlobalId( $globalId );
+ return $site;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @since 1.21
+ *
+ * @param string $type
+ */
+ public function __construct( $type = self::TYPE_MEDIAWIKI ) {
+ parent::__construct( $type );
+ }
+
+ /**
+ * Returns the database form of the given title.
+ *
+ * @since 1.21
+ *
+ * @param string $title the target page's title, in normalized form.
+ *
+ * @return String
+ */
+ public function toDBKey( $title ) {
+ return str_replace( ' ', '_', $title );
+ }
+
+ /**
+ * Returns the normalized form of the given page title, using the normalization rules of the given site.
+ * If the given title is a redirect, the redirect weill be resolved and the redirect target is returned.
+ *
+ * @note : This actually makes an API request to the remote site, so beware that this function is slow and depends
+ * on an external service.
+ *
+ * @note : If MW_PHPUNIT_TEST is defined, the call to the external site is skipped, and the title
+ * is normalized using the local normalization rules as implemented by the Title class.
+ *
+ * @see Site::normalizePageName
+ *
+ * @since 1.21
+ *
+ * @param string $pageName
+ *
+ * @return string
+ * @throws MWException
+ */
+ public function normalizePageName( $pageName ) {
+
+ // Check if we have strings as arguments.
+ if ( !is_string( $pageName ) ) {
+ throw new MWException( '$pageName must be a string' );
+ }
+
+ // Go on call the external site
+ if ( defined( 'MW_PHPUNIT_TEST' ) ) {
+ // If the code is under test, don't call out to other sites, just normalize locally.
+ // Note: this may cause results to be inconsistent with the actual normalization used by the respective remote site!
+
+ $t = Title::newFromText( $pageName );
+ return $t->getPrefixedText();
+ } else {
+
+ // Make sure the string is normalized into NFC (due to the bug 40017)
+ // but do nothing to the whitespaces, that should work appropriately.
+ // @see https://bugzilla.wikimedia.org/show_bug.cgi?id=40017
+ $pageName = UtfNormal::cleanUp( $pageName );
+
+ // Build the args for the specific call
+ $args = array(
+ 'action' => 'query',
+ 'prop' => 'info',
+ 'redirects' => true,
+ 'converttitles' => true,
+ 'format' => 'json',
+ 'titles' => $pageName,
+ //@todo: options for maxlag and maxage
+ // Note that maxlag will lead to a long delay before a reply is made,
+ // but that maxage can avoid the extreme delay. On the other hand
+ // maxage could be nice to use anyhow as it stops unnecessary requests.
+ // Also consider smaxage if maxage is used.
+ );
+
+ $url = $this->getFileUrl( 'api.php' ) . '?' . wfArrayToCgi( $args );
+
+ // Go on call the external site
+ //@todo: we need a good way to specify a timeout here.
+ $ret = Http::get( $url );
+ }
+
+ if ( $ret === false ) {
+ wfDebugLog( "MediaWikiSite", "call to external site failed: $url" );
+ return false;
+ }
+
+ $data = FormatJson::decode( $ret, true );
+
+ if ( !is_array( $data ) ) {
+ wfDebugLog( "MediaWikiSite", "call to <$url> returned bad json: " . $ret );
+ return false;
+ }
+
+ $page = static::extractPageRecord( $data, $pageName );
+
+ if ( isset( $page['missing'] ) ) {
+ wfDebugLog( "MediaWikiSite", "call to <$url> returned a marker for a missing page title! " . $ret );
+ return false;
+ }
+
+ if ( isset( $page['invalid'] ) ) {
+ wfDebugLog( "MediaWikiSite", "call to <$url> returned a marker for an invalid page title! " . $ret );
+ return false;
+ }
+
+ if ( !isset( $page['title'] ) ) {
+ wfDebugLog( "MediaWikiSite", "call to <$url> did not return a page title! " . $ret );
+ return false;
+ }
+
+ return $page['title'];
+ }
+
+ /**
+ * Get normalization record for a given page title from an API response.
+ *
+ * @since 1.21
+ *
+ * @param array $externalData A reply from the API on a external server.
+ * @param string $pageTitle Identifies the page at the external site, needing normalization.
+ *
+ * @return array|boolean a 'page' structure representing the page identified by $pageTitle.
+ */
+ private static function extractPageRecord( $externalData, $pageTitle ) {
+ // If there is a special case with only one returned page
+ // we can cheat, and only return
+ // the single page in the "pages" substructure.
+ if ( isset( $externalData['query']['pages'] ) ) {
+ $pages = array_values( $externalData['query']['pages'] );
+ if ( count( $pages) === 1 ) {
+ return $pages[0];
+ }
+ }
+ // This is only used during internal testing, as it is assumed
+ // a more optimal (and lossfree) storage.
+ // Make initial checks and return if prerequisites are not meet.
+ if ( !is_array( $externalData ) || !isset( $externalData['query'] ) ) {
+ return false;
+ }
+ // Loop over the tree different named structures, that otherwise are similar
+ $structs = array(
+ 'normalized' => 'from',
+ 'converted' => 'from',
+ 'redirects' => 'from',
+ 'pages' => 'title'
+ );
+ foreach ( $structs as $listId => $fieldId ) {
+ // Check if the substructure exist at all.
+ if ( !isset( $externalData['query'][$listId] ) ) {
+ continue;
+ }
+ // Filter the substructure down to what we actually are using.
+ $collectedHits = array_filter(
+ array_values( $externalData['query'][$listId] ),
+ function( $a ) use ( $fieldId, $pageTitle ) {
+ return $a[$fieldId] === $pageTitle;
+ }
+ );
+ // If still looping over normalization, conversion or redirects,
+ // then we need to keep the new page title for later rounds.
+ if ( $fieldId === 'from' && is_array( $collectedHits ) ) {
+ switch ( count( $collectedHits ) ) {
+ case 0:
+ break;
+ case 1:
+ $pageTitle = $collectedHits[0]['to'];
+ break;
+ default:
+ return false;
+ }
+ }
+ // If on the pages structure we should prepare for returning.
+ elseif ( $fieldId === 'title' && is_array( $collectedHits ) ) {
+ switch ( count( $collectedHits ) ) {
+ case 0:
+ return false;
+ case 1:
+ return array_shift( $collectedHits );
+ default:
+ return false;
+ }
+ }
+ }
+ // should never be here
+ return false;
+ }
+
+ /**
+ * @see Site::getLinkPathType
+ * Returns Site::PATH_PAGE
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getLinkPathType() {
+ return self::PATH_PAGE;
+ }
+
+ /**
+ * Returns the relative page path.
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getRelativePagePath() {
+ return parse_url( $this->getPath( self::PATH_PAGE ), PHP_URL_PATH );
+ }
+
+ /**
+ * Returns the relative file path.
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getRelativeFilePath() {
+ return parse_url( $this->getPath( self::PATH_FILE ), PHP_URL_PATH );
+ }
+
+ /**
+ * Sets the relative page path.
+ *
+ * @since 1.21
+ *
+ * @param string $path
+ */
+ public function setPagePath( $path ) {
+ $this->setPath( self::PATH_PAGE, $path );
+ }
+
+ /**
+ * Sets the relative file path.
+ *
+ * @since 1.21
+ *
+ * @param string $path
+ */
+ public function setFilePath( $path ) {
+ $this->setPath( self::PATH_FILE, $path );
+ }
+
+ /**
+ * @see Site::getPageUrl
+ *
+ * This implementation returns a URL constructed using the path returned by getLinkPath().
+ * In addition to the default behavior implemented by Site::getPageUrl(), this
+ * method converts the $pageName to DBKey-format by replacing spaces with underscores
+ * before using it in the URL.
+ *
+ * @since 1.21
+ *
+ * @param string|boolean $pageName Page name or false (default: false)
+ *
+ * @return string
+ */
+ public function getPageUrl( $pageName = false ) {
+ $url = $this->getLinkPath();
+
+ if ( $url === false ) {
+ return false;
+ }
+
+ if ( $pageName !== false ) {
+ $pageName = $this->toDBKey( trim( $pageName ) );
+ $url = str_replace( '$1', wfUrlencode( $pageName ), $url );
+ }
+
+ return $url;
+ }
+
+ /**
+ * Returns the full file path (ie site url + relative file path).
+ * The path should go at the $1 marker. If the $path
+ * argument is provided, the marker will be replaced by it's value.
+ *
+ * @since 1.21
+ *
+ * @param string|boolean $path
+ *
+ * @return string
+ */
+ public function getFileUrl( $path = false ) {
+ $filePath = $this->getPath( self::PATH_FILE );
+
+ if ( $filePath !== false ) {
+ $filePath = str_replace( '$1', $path, $filePath );
+ }
+
+ return $filePath;
+ }
+
+}
diff --git a/includes/site/Site.php b/includes/site/Site.php
new file mode 100644
index 00000000..076dc88c
--- /dev/null
+++ b/includes/site/Site.php
@@ -0,0 +1,702 @@
+<?php
+
+/**
+ * Represents a single site.
+ *
+ * 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
+ *
+ * @since 1.21
+ *
+ * @file
+ * @ingroup Site
+ *
+ * @license GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class Site implements Serializable {
+
+ const TYPE_UNKNOWN = 'unknown';
+ const TYPE_MEDIAWIKI = 'mediawiki';
+
+ const GROUP_NONE = 'none';
+
+ const ID_INTERWIKI = 'interwiki';
+ const ID_EQUIVALENT = 'equivalent';
+
+ const SOURCE_LOCAL = 'local';
+
+ const PATH_LINK = 'link';
+
+ /**
+ * A version ID that identifies the serialization structure used by getSerializationData()
+ * and unserialize(). This is useful for constructing cache keys in cases where the cache relies
+ * on serialization for storing the SiteList.
+ *
+ * @var string A string uniquely identifying the version of the serialization structure.
+ */
+ const SERIAL_VERSION_ID = '2013-01-23';
+
+ /**
+ * @since 1.21
+ *
+ * @var string|null
+ */
+ protected $globalId = null;
+
+ /**
+ * @since 1.21
+ *
+ * @var string
+ */
+ protected $type = self::TYPE_UNKNOWN;
+
+ /**
+ * @since 1.21
+ *
+ * @var string
+ */
+ protected $group = self::GROUP_NONE;
+
+ /**
+ * @since 1.21
+ *
+ * @var string
+ */
+ protected $source = self::SOURCE_LOCAL;
+
+ /**
+ * @since 1.21
+ *
+ * @var string|null
+ */
+ protected $languageCode = null;
+
+ /**
+ * Holds the local ids for this site.
+ * local id type => [ ids for this type (strings) ]
+ *
+ * @since 1.21
+ *
+ * @var array[]
+ */
+ protected $localIds = array();
+
+ /**
+ * @since 1.21
+ *
+ * @var array
+ */
+ protected $extraData = array();
+
+ /**
+ * @since 1.21
+ *
+ * @var array
+ */
+ protected $extraConfig = array();
+
+ /**
+ * @since 1.21
+ *
+ * @var bool
+ */
+ protected $forward = false;
+
+ /**
+ * @since 1.21
+ *
+ * @var int|null
+ */
+ protected $internalId = null;
+
+ /**
+ * Constructor.
+ *
+ * @since 1.21
+ *
+ * @param string $type
+ */
+ public function __construct( $type = self::TYPE_UNKNOWN ) {
+ $this->type = $type;
+ }
+
+ /**
+ * Returns the global site identifier (ie enwiktionary).
+ *
+ * @since 1.21
+ *
+ * @return string|null
+ */
+ public function getGlobalId() {
+ return $this->globalId;
+ }
+
+ /**
+ * Sets the global site identifier (ie enwiktionary).
+ *
+ * @since 1.21
+ *
+ * @param string|null $globalId
+ *
+ * @throws MWException
+ */
+ public function setGlobalId( $globalId ) {
+ if ( $globalId !== null && !is_string( $globalId ) ) {
+ throw new MWException( '$globalId needs to be string or null' );
+ }
+
+ $this->globalId = $globalId;
+ }
+
+ /**
+ * Returns the type of the site (ie mediawiki).
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getType() {
+ return $this->type;
+ }
+
+ /**
+ * Gets the type of the site (ie wikipedia).
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getGroup() {
+ return $this->group;
+ }
+
+ /**
+ * Sets the type of the site (ie wikipedia).
+ *
+ * @since 1.21
+ *
+ * @param string $group
+ *
+ * @throws MWException
+ */
+ public function setGroup( $group ) {
+ if ( !is_string( $group ) ) {
+ throw new MWException( '$group needs to be a string' );
+ }
+
+ $this->group = $group;
+ }
+
+ /**
+ * Returns the source of the site data (ie 'local', 'wikidata', 'my-magical-repo').
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getSource() {
+ return $this->source;
+ }
+
+ /**
+ * Sets the source of the site data (ie 'local', 'wikidata', 'my-magical-repo').
+ *
+ * @since 1.21
+ *
+ * @param string $source
+ *
+ * @throws MWException
+ */
+ public function setSource( $source ) {
+ if ( !is_string( $source ) ) {
+ throw new MWException( '$source needs to be a string' );
+ }
+
+ $this->source = $source;
+ }
+
+ /**
+ * Gets if site.tld/path/key:pageTitle should forward users to the page on
+ * the actual site, where "key" is the local identifier.
+ *
+ * @since 1.21
+ *
+ * @return boolean
+ */
+ public function shouldForward() {
+ return $this->forward;
+ }
+
+ /**
+ * Sets if site.tld/path/key:pageTitle should forward users to the page on
+ * the actual site, where "key" is the local identifier.
+ *
+ * @since 1.21
+ *
+ * @param boolean $shouldForward
+ *
+ * @throws MWException
+ */
+ public function setForward( $shouldForward ) {
+ if ( !is_bool( $shouldForward ) ) {
+ throw new MWException( '$shouldForward needs to be a boolean' );
+ }
+
+ $this->forward = $shouldForward;
+ }
+
+ /**
+ * Returns the domain of the site, ie en.wikipedia.org
+ * Or false if it's not known.
+ *
+ * @since 1.21
+ *
+ * @return string|null
+ */
+ public function getDomain() {
+ $path = $this->getLinkPath();
+
+ if ( $path === null ) {
+ return null;
+ }
+
+ return parse_url( $path, PHP_URL_HOST );
+ }
+
+ /**
+ * Returns the protocol of the site.
+ *
+ * @since 1.21
+ *
+ * @throws MWException
+ * @return string
+ */
+ public function getProtocol() {
+ $path = $this->getLinkPath();
+
+ if ( $path === null ) {
+ return '';
+ }
+
+ $protocol = parse_url( $path, PHP_URL_SCHEME );
+
+ // Malformed URL
+ if ( $protocol === false ) {
+ throw new MWException( "failed to parse URL '$path'" );
+ }
+
+ // No schema
+ if ( $protocol === null ) {
+ // Used for protocol relative URLs
+ $protocol = '';
+ }
+
+ return $protocol;
+ }
+
+ /**
+ * Sets the path used to construct links with.
+ * Shall be equivalent to setPath( getLinkPathType(), $fullUrl ).
+ *
+ * @param string $fullUrl
+ *
+ * @since 1.21
+ *
+ * @throws MWException
+ */
+ public function setLinkPath( $fullUrl ) {
+ $type = $this->getLinkPathType();
+
+ if ( $type === null ) {
+ throw new MWException( "This Site does not support link paths." );
+ }
+
+ $this->setPath( $type, $fullUrl );
+ }
+
+ /**
+ * Returns the path used to construct links with or false if there is no such path.
+ *
+ * Shall be equivalent to getPath( getLinkPathType() ).
+ *
+ * @return string|null
+ */
+ public function getLinkPath() {
+ $type = $this->getLinkPathType();
+ return $type === null ? null: $this->getPath( $type );
+ }
+
+ /**
+ * Returns the main path type, that is the type of the path that should generally be used to construct links
+ * to the target site.
+ *
+ * This default implementation returns Site::PATH_LINK as the default path type. Subclasses can override this
+ * to define a different default path type, or return false to disable site links.
+ *
+ * @since 1.21
+ *
+ * @return string|null
+ */
+ public function getLinkPathType() {
+ return self::PATH_LINK;
+ }
+
+ /**
+ * Returns the full URL for the given page on the site.
+ * Or false if the needed information is not known.
+ *
+ * This generated URL is usually based upon the path returned by getLinkPath(),
+ * but this is not a requirement.
+ *
+ * This implementation returns a URL constructed using the path returned by getLinkPath().
+ *
+ * @since 1.21
+ *
+ * @param bool|String $pageName
+ *
+ * @return string|boolean false
+ */
+ public function getPageUrl( $pageName = false ) {
+ $url = $this->getLinkPath();
+
+ if ( $url === false ) {
+ return false;
+ }
+
+ if ( $pageName !== false ) {
+ $url = str_replace( '$1', rawurlencode( $pageName ), $url );
+ }
+
+ return $url;
+ }
+
+ /**
+ * Returns $pageName without changes.
+ * Subclasses may override this to apply some kind of normalization.
+ *
+ * @see Site::normalizePageName
+ *
+ * @since 1.21
+ *
+ * @param string $pageName
+ *
+ * @return string
+ */
+ public function normalizePageName( $pageName ) {
+ return $pageName;
+ }
+
+ /**
+ * Returns the type specific fields.
+ *
+ * @since 1.21
+ *
+ * @return array
+ */
+ public function getExtraData() {
+ return $this->extraData;
+ }
+
+ /**
+ * Sets the type specific fields.
+ *
+ * @since 1.21
+ *
+ * @param array $extraData
+ */
+ public function setExtraData( array $extraData ) {
+ $this->extraData = $extraData;
+ }
+
+ /**
+ * Returns the type specific config.
+ *
+ * @since 1.21
+ *
+ * @return array
+ */
+ public function getExtraConfig() {
+ return $this->extraConfig;
+ }
+
+ /**
+ * Sets the type specific config.
+ *
+ * @since 1.21
+ *
+ * @param array $extraConfig
+ */
+ public function setExtraConfig( array $extraConfig ) {
+ $this->extraConfig = $extraConfig;
+ }
+
+ /**
+ * Returns language code of the sites primary language.
+ * Or null if it's not known.
+ *
+ * @since 1.21
+ *
+ * @return string|null
+ */
+ public function getLanguageCode() {
+ return $this->languageCode;
+ }
+
+ /**
+ * Sets language code of the sites primary language.
+ *
+ * @since 1.21
+ *
+ * @param string $languageCode
+ */
+ public function setLanguageCode( $languageCode ) {
+ $this->languageCode = $languageCode;
+ }
+
+ /**
+ * Returns the set internal identifier for the site.
+ *
+ * @since 1.21
+ *
+ * @return string|null
+ */
+ public function getInternalId() {
+ return $this->internalId;
+ }
+
+ /**
+ * Sets the internal identifier for the site.
+ * This typically is a primary key in a db table.
+ *
+ * @since 1.21
+ *
+ * @param int|null $internalId
+ */
+ public function setInternalId( $internalId = null ) {
+ $this->internalId = $internalId;
+ }
+
+ /**
+ * Adds a local identifier.
+ *
+ * @since 1.21
+ *
+ * @param string $type
+ * @param string $identifier
+ */
+ public function addLocalId( $type, $identifier ) {
+ if ( $this->localIds === false ) {
+ $this->localIds = array();
+ }
+
+ if ( !array_key_exists( $type, $this->localIds ) ) {
+ $this->localIds[$type] = array();
+ }
+
+ if ( !in_array( $identifier, $this->localIds[$type] ) ) {
+ $this->localIds[$type][] = $identifier;
+ }
+ }
+
+ /**
+ * Adds an interwiki id to the site.
+ *
+ * @since 1.21
+ *
+ * @param string $identifier
+ */
+ public function addInterwikiId( $identifier ) {
+ $this->addLocalId( self::ID_INTERWIKI, $identifier );
+ }
+
+ /**
+ * Adds a navigation id to the site.
+ *
+ * @since 1.21
+ *
+ * @param string $identifier
+ */
+ public function addNavigationId( $identifier ) {
+ $this->addLocalId( self::ID_EQUIVALENT, $identifier );
+ }
+
+ /**
+ * Returns the interwiki link identifiers that can be used for this site.
+ *
+ * @since 1.21
+ *
+ * @return string[]
+ */
+ public function getInterwikiIds() {
+ return array_key_exists( self::ID_INTERWIKI, $this->localIds ) ? $this->localIds[self::ID_INTERWIKI] : array();
+ }
+
+ /**
+ * Returns the equivalent link identifiers that can be used to make
+ * the site show up in interfaces such as the "language links" section.
+ *
+ * @since 1.21
+ *
+ * @return string[]
+ */
+ public function getNavigationIds() {
+ return array_key_exists( self::ID_EQUIVALENT, $this->localIds ) ? $this->localIds[self::ID_EQUIVALENT] : array();
+ }
+
+ /**
+ * Returns all local ids
+ *
+ * @since 1.21
+ *
+ * @return array[]
+ */
+ public function getLocalIds() {
+ return $this->localIds;
+ }
+
+ /**
+ * Sets the path used to construct links with.
+ * Shall be equivalent to setPath( getLinkPathType(), $fullUrl ).
+ *
+ * @since 1.21
+ *
+ * @param string $pathType
+ * @param string $fullUrl
+ *
+ * @throws MWException
+ */
+ public function setPath( $pathType, $fullUrl ) {
+ if ( !is_string( $fullUrl ) ) {
+ throw new MWException( '$fullUrl needs to be a string' );
+ }
+
+ if ( !array_key_exists( 'paths', $this->extraData ) ) {
+ $this->extraData['paths'] = array();
+ }
+
+ $this->extraData['paths'][$pathType] = $fullUrl;
+ }
+
+ /**
+ * Returns the path of the provided type or false if there is no such path.
+ *
+ * @since 1.21
+ *
+ * @param string $pathType
+ *
+ * @return string|null
+ */
+ public function getPath( $pathType ) {
+ $paths = $this->getAllPaths();
+ return array_key_exists( $pathType, $paths ) ? $paths[$pathType] : null;
+ }
+
+ /**
+ * Returns the paths as associative array.
+ * The keys are path types, the values are the path urls.
+ *
+ * @since 1.21
+ *
+ * @return string[]
+ */
+ public function getAllPaths() {
+ return array_key_exists( 'paths', $this->extraData ) ? $this->extraData['paths'] : array();
+ }
+
+ /**
+ * Removes the path of the provided type if it's set.
+ *
+ * @since 1.21
+ *
+ * @param string $pathType
+ */
+ public function removePath( $pathType ) {
+ if ( array_key_exists( 'paths', $this->extraData ) ) {
+ unset( $this->extraData['paths'][$pathType] );
+ }
+ }
+
+ /**
+ * @since 1.21
+ *
+ * @param string $siteType
+ *
+ * @return Site
+ */
+ public static function newForType( $siteType ) {
+ global $wgSiteTypes;
+
+ if ( array_key_exists( $siteType, $wgSiteTypes ) ) {
+ return new $wgSiteTypes[$siteType]();
+ }
+
+ return new Site();
+ }
+
+ /**
+ * @see Serializable::serialize
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function serialize() {
+ $fields = array(
+ 'globalid' => $this->globalId,
+ 'type' => $this->type,
+ 'group' => $this->group,
+ 'source' => $this->source,
+ 'language' => $this->languageCode,
+ 'localids' => $this->localIds,
+ 'config' => $this->extraConfig,
+ 'data' => $this->extraData,
+ 'forward' => $this->forward,
+ 'internalid' => $this->internalId,
+
+ );
+
+ return serialize( $fields );
+ }
+
+ /**
+ * @see Serializable::unserialize
+ *
+ * @since 1.21
+ *
+ * @param string $serialized
+ */
+ public function unserialize( $serialized ) {
+ $fields = unserialize( $serialized );
+
+ $this->__construct( $fields['type'] );
+
+ $this->setGlobalId( $fields['globalid'] );
+ $this->setGroup( $fields['group'] );
+ $this->setSource( $fields['source'] );
+ $this->setLanguageCode( $fields['language'] );
+ $this->localIds = $fields['localids'];
+ $this->setExtraConfig( $fields['config'] );
+ $this->setExtraData( $fields['data'] );
+ $this->setForward( $fields['forward'] );
+ $this->setInternalId( $fields['internalid'] );
+ }
+
+}
+
+/**
+ * @deprecated
+ */
+class SiteObject extends Site {}
diff --git a/includes/site/SiteList.php b/includes/site/SiteList.php
new file mode 100644
index 00000000..b0d1f95b
--- /dev/null
+++ b/includes/site/SiteList.php
@@ -0,0 +1,300 @@
+<?php
+
+/**
+ * Collection of Site objects.
+ *
+ * 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
+ *
+ * @since 1.21
+ *
+ * @file
+ * @ingroup Site
+ *
+ * @license GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class SiteList extends GenericArrayObject {
+
+ /**
+ * Internal site identifiers pointing to their sites offset value.
+ *
+ * @since 1.21
+ *
+ * @var array of integer
+ */
+ protected $byInternalId = array();
+
+ /**
+ * Global site identifiers pointing to their sites offset value.
+ *
+ * @since 1.21
+ *
+ * @var array of string
+ */
+ protected $byGlobalId = array();
+
+ /**
+ * @see GenericArrayObject::getObjectType
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getObjectType() {
+ return 'Site';
+ }
+
+ /**
+ * @see GenericArrayObject::preSetElement
+ *
+ * @since 1.21
+ *
+ * @param int|string $index
+ * @param Site $site
+ *
+ * @return boolean
+ */
+ protected function preSetElement( $index, $site ) {
+ if ( $this->hasSite( $site->getGlobalId() ) ) {
+ $this->removeSite( $site->getGlobalId() );
+ }
+
+ $this->byGlobalId[$site->getGlobalId()] = $index;
+ $this->byInternalId[$site->getInternalId()] = $index;
+
+ return true;
+ }
+
+ /**
+ * @see ArrayObject::offsetUnset()
+ *
+ * @since 1.21
+ *
+ * @param mixed $index
+ */
+ public function offsetUnset( $index ) {
+ if ( $this->offsetExists( $index ) ) {
+ /**
+ * @var Site $site
+ */
+ $site = $this->offsetGet( $index );
+
+ unset( $this->byGlobalId[$site->getGlobalId()] );
+ unset( $this->byInternalId[$site->getInternalId()] );
+ }
+
+ parent::offsetUnset( $index );
+ }
+
+ /**
+ * Returns all the global site identifiers.
+ * Optionally only those belonging to the specified group.
+ *
+ * @since 1.21
+ *
+ * @return array
+ */
+ public function getGlobalIdentifiers() {
+ return array_keys( $this->byGlobalId );
+ }
+
+ /**
+ * Returns if the list contains the site with the provided global site identifier.
+ *
+ * @param string $globalSiteId
+ *
+ * @return boolean
+ */
+ public function hasSite( $globalSiteId ) {
+ return array_key_exists( $globalSiteId, $this->byGlobalId );
+ }
+
+ /**
+ * Returns the Site with the provided global site identifier.
+ * The site needs to exist, so if not sure, call hasGlobalId first.
+ *
+ * @since 1.21
+ *
+ * @param string $globalSiteId
+ *
+ * @return Site
+ */
+ public function getSite( $globalSiteId ) {
+ return $this->offsetGet( $this->byGlobalId[$globalSiteId] );
+ }
+
+ /**
+ * Removes the site with the specified global site identifier.
+ * The site needs to exist, so if not sure, call hasGlobalId first.
+ *
+ * @since 1.21
+ *
+ * @param string $globalSiteId
+ */
+ public function removeSite( $globalSiteId ) {
+ $this->offsetUnset( $this->byGlobalId[$globalSiteId] );
+ }
+
+ /**
+ * Returns if the list contains no sites.
+ *
+ * @since 1.21
+ *
+ * @return boolean
+ */
+ public function isEmpty() {
+ return $this->byGlobalId === array();
+ }
+
+ /**
+ * Returns if the list contains the site with the provided site id.
+ *
+ * @param integer $id
+ *
+ * @return boolean
+ */
+ public function hasInternalId( $id ) {
+ return array_key_exists( $id, $this->byInternalId );
+ }
+
+ /**
+ * Returns the Site with the provided site id.
+ * The site needs to exist, so if not sure, call has first.
+ *
+ * @since 1.21
+ *
+ * @param integer $id
+ *
+ * @return Site
+ */
+ public function getSiteByInternalId( $id ) {
+ return $this->offsetGet( $this->byInternalId[$id] );
+ }
+
+ /**
+ * Removes the site with the specified site id.
+ * The site needs to exist, so if not sure, call has first.
+ *
+ * @since 1.21
+ *
+ * @param integer $id
+ */
+ public function removeSiteByInternalId( $id ) {
+ $this->offsetUnset( $this->byInternalId[$id] );
+ }
+
+ /**
+ * Sets a site in the list. If the site was not there,
+ * it will be added. If it was, it will be updated.
+ *
+ * @since 1.21
+ *
+ * @param Site $site
+ */
+ public function setSite( Site $site ) {
+ $this[] = $site;
+ }
+
+ /**
+ * Returns the sites that are in the provided group.
+ *
+ * @since 1.21
+ *
+ * @param string $groupName
+ *
+ * @return SiteList
+ */
+ public function getGroup( $groupName ) {
+ $group = new self();
+
+ /**
+ * @var \Site $site
+ */
+ foreach ( $this as $site ) {
+ if ( $site->getGroup() === $groupName ) {
+ $group[] = $site;
+ }
+ }
+
+ return $group;
+ }
+
+ /**
+ * A version ID that identifies the serialization structure used by getSerializationData()
+ * and unserialize(). This is useful for constructing cache keys in cases where the cache relies
+ * on serialization for storing the SiteList.
+ *
+ * @var string A string uniquely identifying the version of the serialization structure,
+ * not including any sub-structures.
+ */
+ const SERIAL_VERSION_ID = '2013-02-07';
+
+ /**
+ * Returns the version ID that identifies the serialization structure used by
+ * getSerializationData() and unserialize(), including the structure of any nested structures.
+ * This is useful for constructing cache keys in cases where the cache relies
+ * on serialization for storing the SiteList.
+ *
+ * @return string A string uniquely identifying the version of the serialization structure,
+ * including any sub-structures.
+ */
+ public static function getSerialVersionId() {
+ return self::SERIAL_VERSION_ID . '+Site:' . Site::SERIAL_VERSION_ID;
+ }
+
+ /**
+ * @see GenericArrayObject::getSerializationData
+ *
+ * @since 1.21
+ *
+ * @return array
+ */
+ protected function getSerializationData() {
+ //NOTE: When changing the structure, either implement unserialize() to handle the
+ // old structure too, or update SERIAL_VERSION_ID to kill any caches.
+ return array_merge(
+ parent::getSerializationData(),
+ array(
+ 'internalIds' => $this->byInternalId,
+ 'globalIds' => $this->byGlobalId,
+ )
+ );
+ }
+
+ /**
+ * @see GenericArrayObject::unserialize
+ *
+ * @since 1.21
+ *
+ * @param string $serialization
+ *
+ * @return array
+ */
+ public function unserialize( $serialization ) {
+ $serializationData = parent::unserialize( $serialization );
+
+ $this->byInternalId = $serializationData['internalIds'];
+ $this->byGlobalId = $serializationData['globalIds'];
+
+ return $serializationData;
+ }
+
+}
+
+/**
+ * @deprecated
+ */
+class SiteArray extends SiteList {}
diff --git a/includes/site/SiteSQLStore.php b/includes/site/SiteSQLStore.php
new file mode 100644
index 00000000..41238055
--- /dev/null
+++ b/includes/site/SiteSQLStore.php
@@ -0,0 +1,491 @@
+<?php
+
+/**
+ * Represents the site configuration of a wiki.
+ * Holds a list of sites (ie SiteList) and takes care
+ * of retrieving and caching site information when appropriate.
+ *
+ * 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
+ *
+ * @since 1.21
+ *
+ * @file
+ * @ingroup Site
+ *
+ * @license GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class SiteSQLStore implements SiteStore {
+
+ /**
+ * @since 1.21
+ *
+ * @var SiteList|null
+ */
+ protected $sites = null;
+
+ /**
+ * @var ORMTable
+ */
+ protected $sitesTable;
+
+ /**
+ * @var string|null
+ */
+ private $cacheKey = null;
+
+ /**
+ * @var int
+ */
+ private $cacheTimeout = 3600;
+
+ /**
+ * @since 1.21
+ *
+ * @param ORMTable|null $sitesTable
+ *
+ * @return SiteStore
+ */
+ public static function newInstance( ORMTable $sitesTable = null ) {
+ return new static( $sitesTable );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @since 1.21
+ *
+ * @param ORMTable|null $sitesTable
+ */
+ protected function __construct( ORMTable $sitesTable = null ) {
+ if ( $sitesTable === null ) {
+ $sitesTable = $this->newSitesTable();
+ }
+
+ $this->sitesTable = $sitesTable;
+ }
+
+ /**
+ * Constructs a cache key to use for caching the list of sites.
+ *
+ * This includes the concrete class name of the site list as well as a version identifier
+ * for the list's serialization, to avoid problems when unserializing site lists serialized
+ * by an older version, e.g. when reading from a cache.
+ *
+ * The cache key also includes information about where the sites were loaded from, e.g.
+ * the name of a database table.
+ *
+ * @see SiteList::getSerialVersionId
+ *
+ * @return String The cache key.
+ */
+ protected function getCacheKey() {
+ wfProfileIn( __METHOD__ );
+
+ if ( $this->cacheKey === null ) {
+ $type = 'SiteList#' . SiteList::getSerialVersionId();
+ $source = $this->sitesTable->getName();
+
+ if ( $this->sitesTable->getTargetWiki() !== false ) {
+ $source = $this->sitesTable->getTargetWiki() . '.' . $source;
+ }
+
+ $this->cacheKey = wfMemcKey( "$source/$type" );
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $this->cacheKey;
+ }
+
+ /**
+ * @see SiteStore::getSites
+ *
+ * @since 1.21
+ *
+ * @param string $source either 'cache' or 'recache'
+ *
+ * @return SiteList
+ */
+ public function getSites( $source = 'cache' ) {
+ wfProfileIn( __METHOD__ );
+
+ if ( $source === 'cache' ) {
+ if ( $this->sites === null ) {
+ $cache = wfGetMainCache();
+ $sites = $cache->get( $this->getCacheKey() );
+
+ if ( is_object( $sites ) ) {
+ $this->sites = $sites;
+ } else {
+ $this->loadSites();
+ }
+ }
+ }
+ else {
+ $this->loadSites();
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $this->sites;
+ }
+
+ /**
+ * Returns a new Site object constructed from the provided ORMRow.
+ *
+ * @since 1.21
+ *
+ * @param ORMRow $siteRow
+ *
+ * @return Site
+ */
+ protected function siteFromRow( ORMRow $siteRow ) {
+ wfProfileIn( __METHOD__ );
+
+ $site = Site::newForType( $siteRow->getField( 'type', Site::TYPE_UNKNOWN ) );
+
+ $site->setGlobalId( $siteRow->getField( 'global_key' ) );
+
+ $site->setInternalId( $siteRow->getField( 'id' ) );
+
+ if ( $siteRow->hasField( 'forward' ) ) {
+ $site->setForward( $siteRow->getField( 'forward' ) );
+ }
+
+ if ( $siteRow->hasField( 'group' ) ) {
+ $site->setGroup( $siteRow->getField( 'group' ) );
+ }
+
+ if ( $siteRow->hasField( 'language' ) ) {
+ $site->setLanguageCode( $siteRow->getField( 'language' ) === '' ? null : $siteRow->getField( 'language' ) );
+ }
+
+ if ( $siteRow->hasField( 'source' ) ) {
+ $site->setSource( $siteRow->getField( 'source' ) );
+ }
+
+ if ( $siteRow->hasField( 'data' ) ) {
+ $site->setExtraData( $siteRow->getField( 'data' ) );
+ }
+
+ if ( $siteRow->hasField( 'config' ) ) {
+ $site->setExtraConfig( $siteRow->getField( 'config' ) );
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $site;
+ }
+
+ /**
+ * Fetches the site from the database and loads them into the sites field.
+ *
+ * @since 1.21
+ */
+ protected function loadSites() {
+ wfProfileIn( __METHOD__ );
+
+ $this->sites = new SiteList();
+
+ foreach ( $this->sitesTable->select() as $siteRow ) {
+ $this->sites[] = $this->siteFromRow( $siteRow );
+ }
+
+ // Batch load the local site identifiers.
+ $ids = wfGetDB( $this->sitesTable->getReadDb() )->select(
+ 'site_identifiers',
+ array(
+ 'si_site',
+ 'si_type',
+ 'si_key',
+ ),
+ array(),
+ __METHOD__
+ );
+
+ foreach ( $ids as $id ) {
+ if ( $this->sites->hasInternalId( $id->si_site ) ) {
+ $site = $this->sites->getSiteByInternalId( $id->si_site );
+ $site->addLocalId( $id->si_type, $id->si_key );
+ $this->sites->setSite( $site );
+ }
+ }
+
+ $cache = wfGetMainCache();
+ $cache->set( $this->getCacheKey(), $this->sites, $this->cacheTimeout );
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * @see SiteStore::getSite
+ *
+ * @since 1.21
+ *
+ * @param string $globalId
+ * @param string $source
+ *
+ * @return Site|null
+ */
+ public function getSite( $globalId, $source = 'cache' ) {
+ wfProfileIn( __METHOD__ );
+
+ $sites = $this->getSites( $source );
+
+ wfProfileOut( __METHOD__ );
+ return $sites->hasSite( $globalId ) ? $sites->getSite( $globalId ) : null;
+ }
+
+ /**
+ * @see SiteStore::saveSite
+ *
+ * @since 1.21
+ *
+ * @param Site $site
+ *
+ * @return boolean Success indicator
+ */
+ public function saveSite( Site $site ) {
+ return $this->saveSites( array( $site ) );
+ }
+
+ /**
+ * @see SiteStore::saveSites
+ *
+ * @since 1.21
+ *
+ * @param Site[] $sites
+ *
+ * @return boolean Success indicator
+ */
+ public function saveSites( array $sites ) {
+ wfProfileIn( __METHOD__ );
+
+ if ( empty( $sites ) ) {
+ wfProfileOut( __METHOD__ );
+ return true;
+ }
+
+ $dbw = $this->sitesTable->getWriteDbConnection();
+
+ $trx = $dbw->trxLevel();
+
+ if ( $trx == 0 ) {
+ $dbw->begin( __METHOD__ );
+ }
+
+ $success = true;
+
+ $internalIds = array();
+ $localIds = array();
+
+ foreach ( $sites as $site ) {
+ $fields = array(
+ // Site data
+ 'global_key' => $site->getGlobalId(), // TODO: check not null
+ 'type' => $site->getType(),
+ 'group' => $site->getGroup(),
+ 'source' => $site->getSource(),
+ 'language' => $site->getLanguageCode() === null ? '' : $site->getLanguageCode(),
+ 'protocol' => $site->getProtocol(),
+ 'domain' => strrev( $site->getDomain() ) . '.',
+ 'data' => $site->getExtraData(),
+
+ // Site config
+ 'forward' => $site->shouldForward(),
+ 'config' => $site->getExtraConfig(),
+ );
+
+ if ( $site->getInternalId() !== null ) {
+ $fields['id'] = $site->getInternalId();
+ $internalIds[] = $site->getInternalId();
+ }
+
+ $siteRow = new ORMRow( $this->sitesTable, $fields );
+ $success = $siteRow->save( __METHOD__ ) && $success;
+
+ foreach ( $site->getLocalIds() as $idType => $ids ) {
+ foreach ( $ids as $id ) {
+ $localIds[] = array( $siteRow->getId(), $idType, $id );
+ }
+ }
+ }
+
+ if ( $internalIds !== array() ) {
+ $dbw->delete(
+ 'site_identifiers',
+ array( 'si_site' => $internalIds ),
+ __METHOD__
+ );
+ }
+
+ foreach ( $localIds as $localId ) {
+ $dbw->insert(
+ 'site_identifiers',
+ array(
+ 'si_site' => $localId[0],
+ 'si_type' => $localId[1],
+ 'si_key' => $localId[2],
+ ),
+ __METHOD__
+ );
+ }
+
+ if ( $trx == 0 ) {
+ $dbw->commit( __METHOD__ );
+ }
+
+ // purge cache
+ $this->reset();
+
+ wfProfileOut( __METHOD__ );
+ return $success;
+ }
+
+ /**
+ * Purges the internal and external cache of the site list, forcing the list
+ * of sites to be re-read from the database.
+ *
+ * @since 1.21
+ */
+ public function reset() {
+ wfProfileIn( __METHOD__ );
+ // purge cache
+ $cache = wfGetMainCache();
+ $cache->delete( $this->getCacheKey() );
+ $this->sites = null;
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Clears the list of sites stored in the database.
+ *
+ * @see SiteStore::clear()
+ *
+ * @return bool success
+ */
+ public function clear() {
+ wfProfileIn( __METHOD__ );
+ $dbw = $this->sitesTable->getWriteDbConnection();
+
+ $trx = $dbw->trxLevel();
+
+ if ( $trx == 0 ) {
+ $dbw->begin( __METHOD__ );
+ }
+
+ $ok = $dbw->delete( 'sites', '*', __METHOD__ );
+ $ok = $dbw->delete( 'site_identifiers', '*', __METHOD__ ) && $ok;
+
+ if ( $trx == 0 ) {
+ $dbw->commit( __METHOD__ );
+ }
+
+ $this->reset();
+
+ wfProfileOut( __METHOD__ );
+ return $ok;
+ }
+
+ /**
+ * @since 1.21
+ *
+ * @return ORMTable
+ */
+ protected function newSitesTable() {
+ return new ORMTable(
+ 'sites',
+ array(
+ 'id' => 'id',
+
+ // Site data
+ 'global_key' => 'str',
+ 'type' => 'str',
+ 'group' => 'str',
+ 'source' => 'str',
+ 'language' => 'str',
+ 'protocol' => 'str',
+ 'domain' => 'str',
+ 'data' => 'array',
+
+ // Site config
+ 'forward' => 'bool',
+ 'config' => 'array',
+ ),
+ array(
+ 'type' => Site::TYPE_UNKNOWN,
+ 'group' => Site::GROUP_NONE,
+ 'source' => Site::SOURCE_LOCAL,
+ 'data' => array(),
+
+ 'forward' => false,
+ 'config' => array(),
+ 'language' => '',
+ ),
+ 'ORMRow',
+ 'site_'
+ );
+ }
+
+}
+
+/**
+ * @deprecated
+ */
+class Sites extends SiteSQLStore {
+
+ /**
+ * Factory for creating new site objects.
+ *
+ * @since 1.21
+ * @deprecated
+ *
+ * @param string|boolean false $globalId
+ *
+ * @return Site
+ */
+ public static function newSite( $globalId = false ) {
+ $site = new Site();
+
+ if ( $globalId !== false ) {
+ $site->setGlobalId( $globalId );
+ }
+
+ return $site;
+ }
+
+ /**
+ * @deprecated
+ * @return SiteStore
+ */
+ public static function singleton() {
+ static $singleton;
+
+ if ( $singleton === null ) {
+ $singleton = new static();
+ }
+
+ return $singleton;
+ }
+
+ /**
+ * @deprecated
+ * @return SiteList
+ */
+ public function getSiteGroup( $group ) {
+ return $this->getSites()->getGroup( $group );
+ }
+
+}
diff --git a/includes/site/SiteStore.php b/includes/site/SiteStore.php
new file mode 100644
index 00000000..52ba8fbf
--- /dev/null
+++ b/includes/site/SiteStore.php
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * Interface for service objects providing a storage interface for Site objects.
+ *
+ * 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
+ *
+ * @since 1.21
+ *
+ * @file
+ * @ingroup Site
+ *
+ * @license GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+interface SiteStore {
+
+ /**
+ * Saves the provided site.
+ *
+ * @since 1.21
+ *
+ * @param Site $site
+ *
+ * @return boolean Success indicator
+ */
+ public function saveSite( Site $site );
+
+ /**
+ * Saves the provided sites.
+ *
+ * @since 1.21
+ *
+ * @param Site[] $sites
+ *
+ * @return boolean Success indicator
+ */
+ public function saveSites( array $sites );
+
+ /**
+ * Returns the site with provided global id, or null if there is no such site.
+ *
+ * @since 1.21
+ *
+ * @param string $globalId
+ * @param string $source either 'cache' or 'recache'.
+ * If 'cache', the values are allowed (but not obliged) to come from a cache.
+ *
+ * @return Site|null
+ */
+ public function getSite( $globalId, $source = 'cache' );
+
+ /**
+ * Returns a list of all sites. By default this site is
+ * fetched from the cache, which can be changed to loading
+ * the list from the database using the $useCache parameter.
+ *
+ * @since 1.21
+ *
+ * @param string $source either 'cache' or 'recache'.
+ * If 'cache', the values are allowed (but not obliged) to come from a cache.
+ *
+ * @return SiteList
+ */
+ public function getSites( $source = 'cache' );
+
+ /**
+ * Deletes all sites from the database. After calling clear(), getSites() will return an empty
+ * list and getSite() will return null until saveSite() or saveSites() is called.
+ */
+ public function clear();
+}
diff --git a/includes/specials/SpecialActiveusers.php b/includes/specials/SpecialActiveusers.php
index c5aa2389..c9c82ada 100644
--- a/includes/specials/SpecialActiveusers.php
+++ b/includes/specials/SpecialActiveusers.php
@@ -50,7 +50,7 @@ class ActiveUsersPager extends UsersPager {
/**
* @param $context IContextSource
* @param $group null Unused
- * @param $par string Parameter passed to the page
+ * @param string $par Parameter passed to the page
*/
function __construct( IContextSource $context = null, $group = null, $par = null ) {
global $wgActiveUserDays;
@@ -93,37 +93,38 @@ class ActiveUsersPager extends UsersPager {
function getQueryInfo() {
$dbr = wfGetDB( DB_SLAVE );
$conds = array( 'rc_user > 0' ); // Users - no anons
- $conds[] = 'ipb_deleted IS NULL'; // don't show hidden names
+ if( !$this->getUser()->isAllowed( 'hideuser' ) ) {
+ $conds[] = 'ipb_deleted IS NULL OR ipb_deleted = 0'; // don't show hidden names
+ }
$conds[] = 'rc_log_type IS NULL OR rc_log_type != ' . $dbr->addQuotes( 'newusers' );
- $conds[] = 'rc_timestamp >= ' . $dbr->addQuotes( $dbr->timestamp( wfTimestamp( TS_UNIX ) - $this->RCMaxAge*24*3600 ) );
+ $conds[] = 'rc_timestamp >= ' . $dbr->addQuotes(
+ $dbr->timestamp( wfTimestamp( TS_UNIX ) - $this->RCMaxAge*24*3600 ) );
if( $this->requestedUser != '' ) {
$conds[] = 'rc_user_text >= ' . $dbr->addQuotes( $this->requestedUser );
}
- $query = array(
- 'tables' => array( 'recentchanges', 'user', 'ipblocks' ),
- 'fields' => array( 'user_name' => 'rc_user_text', // inheritance
+ return array(
+ 'tables' => array( 'recentchanges', 'ipblocks' ),
+ 'fields' => array(
+ 'user_name' => 'rc_user_text', // for Pager inheritance
'rc_user_text', // for Pager
- 'user_id',
+ 'user_id' => 'rc_user',
'recentedits' => 'COUNT(*)',
- 'blocked' => 'MAX(ipb_user)'
+ 'ipb_deleted' => 'MAX(ipb_deleted)'
),
'options' => array(
'GROUP BY' => array( 'rc_user_text', 'user_id' ),
'USE INDEX' => array( 'recentchanges' => 'rc_user_text' )
),
- 'join_conds' => array(
- 'user' => array( 'INNER JOIN', 'rc_user_text=user_name' ),
+ 'join_conds' => array( // check for suppression blocks
'ipblocks' => array( 'LEFT JOIN', array(
- 'user_id=ipb_user',
- 'ipb_auto' => 0,
- 'ipb_deleted' => 1
+ 'rc_user=ipb_user',
+ 'ipb_auto' => 0 # avoid duplicate blocks
)),
),
'conds' => $conds
);
- return $query;
}
function formatRow( $row ) {
@@ -162,9 +163,12 @@ class ActiveUsersPager extends UsersPager {
$groups = $lang->commaList( $list );
$item = $lang->specialList( $ulinks, $groups );
+ if( $row->ipb_deleted ) {
+ $item = "<span class=\"deleted\">$item</span>";
+ }
$count = $this->msg( 'activeusers-count' )->numParams( $row->recentedits )
->params( $userName )->numParams( $this->RCMaxAge )->escaped();
- $blocked = $row->blocked ? ' ' . $this->msg( 'listusers-blocked', $userName )->escaped() : '';
+ $blocked = !is_null( $row->ipb_deleted ) ? ' ' . $this->msg( 'listusers-blocked', $userName )->escaped() : '';
return Html::rawElement( 'li', array(), "{$item} [{$count}]{$blocked}" );
}
@@ -240,4 +244,7 @@ class SpecialActiveUsers extends SpecialPage {
}
}
+ protected function getGroupName() {
+ return 'users';
+ }
}
diff --git a/includes/specials/SpecialAllmessages.php b/includes/specials/SpecialAllmessages.php
index fe9d41e5..a60c8efe 100644
--- a/includes/specials/SpecialAllmessages.php
+++ b/includes/specials/SpecialAllmessages.php
@@ -77,6 +77,9 @@ class SpecialAllmessages extends SpecialPage {
}
+ protected function getGroupName() {
+ return 'wiki';
+ }
}
/**
@@ -113,12 +116,12 @@ class AllmessagesTablePager extends TablePager {
$this->lang = ( $langObj ? $langObj : $wgContLang );
$this->langcode = $this->lang->getCode();
- $this->foreign = $this->langcode != $wgContLang->getCode();
+ $this->foreign = $this->langcode != $wgContLang->getCode();
$request = $this->getRequest();
$this->filter = $request->getVal( 'filter', 'all' );
- if( $this->filter === 'all' ){
+ if( $this->filter === 'all' ) {
$this->custom = null; // So won't match in either case
} else {
$this->custom = ($this->filter == 'unmodified');
@@ -126,7 +129,7 @@ class AllmessagesTablePager extends TablePager {
$prefix = $this->getLanguage()->ucfirst( $request->getVal( 'prefix', '' ) );
$prefix = $prefix != '' ? Title::makeTitleSafe( NS_MEDIAWIKI, $request->getVal( 'prefix', null ) ) : null;
- if( $prefix !== null ){
+ if( $prefix !== null ) {
$this->displayPrefix = $prefix->getDBkey();
$this->prefix = '/^' . preg_quote( $this->displayPrefix ) . '/i';
} else {
@@ -150,7 +153,7 @@ class AllmessagesTablePager extends TablePager {
$msg = wfMessage( 'allmessages-language' );
$langSelect = Xml::languageSelector( $this->langcode, false, null, $attrs, $msg );
- $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-allmessages-form' ) ) .
+ $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-allmessages-form' ) ) .
Xml::fieldset( $this->msg( 'allmessages-filter-legend' )->text() ) .
Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
Xml::openElement( 'table', array( 'class' => 'mw-allmessages-table' ) ) . "\n" .
@@ -216,7 +219,7 @@ class AllmessagesTablePager extends TablePager {
function getAllMessages( $descending ) {
wfProfileIn( __METHOD__ );
$messageNames = Language::getLocalisationCache()->getSubitemList( 'en', 'messages' );
- if( $descending ){
+ if( $descending ) {
rsort( $messageNames );
} else {
asort( $messageNames );
@@ -331,15 +334,13 @@ class AllmessagesTablePager extends TablePager {
</tr></thead><tbody>\n";
}
- function formatValue( $field, $value ){
- switch( $field ){
-
+ function formatValue( $field, $value ) {
+ switch( $field ) {
case 'am_title' :
-
$title = Title::makeTitle( NS_MEDIAWIKI, $value . $this->suffix );
- $talk = Title::makeTitle( NS_MEDIAWIKI_TALK, $value . $this->suffix );
+ $talk = Title::makeTitle( NS_MEDIAWIKI_TALK, $value . $this->suffix );
- if( $this->mCurrentRow->am_customised ){
+ if( $this->mCurrentRow->am_customised ) {
$title = Linker::linkKnown( $title, $this->getLanguage()->lcfirst( $value ) );
} else {
$title = Linker::link(
@@ -351,7 +352,7 @@ class AllmessagesTablePager extends TablePager {
);
}
if ( $this->mCurrentRow->am_talk_exists ) {
- $talk = Linker::linkKnown( $talk , $this->talk );
+ $talk = Linker::linkKnown( $talk, $this->talk );
} else {
$talk = Linker::link(
$talk,
@@ -370,12 +371,12 @@ class AllmessagesTablePager extends TablePager {
return '';
}
- function formatRow( $row ){
+ function formatRow( $row ) {
// Do all the normal stuff
$s = parent::formatRow( $row );
// But if there's a customised message, add that too.
- if( $row->am_customised ){
+ if( $row->am_customised ) {
$s .= Xml::openElement( 'tr', $this->getRowAttrs( $row, true ) );
$formatted = strval( $this->formatValue( 'am_actual', $row->am_actual ) );
if ( $formatted == '' ) {
@@ -387,19 +388,19 @@ class AllmessagesTablePager extends TablePager {
return $s;
}
- function getRowAttrs( $row, $isSecond = false ){
+ function getRowAttrs( $row, $isSecond = false ) {
$arr = array();
- if( $row->am_customised ){
+ if( $row->am_customised ) {
$arr['class'] = 'allmessages-customised';
}
- if( !$isSecond ){
+ if( !$isSecond ) {
$arr['id'] = Sanitizer::escapeId( 'msg_' . $this->getLanguage()->lcfirst( $row->am_title ) );
}
return $arr;
}
- function getCellAttrs( $field, $value ){
- if( $this->mCurrentRow->am_customised && $field == 'am_title' ){
+ function getCellAttrs( $field, $value ) {
+ if( $this->mCurrentRow->am_customised && $field == 'am_title' ) {
return array( 'rowspan' => '2', 'class' => $field );
} elseif( $field == 'am_title' ) {
return array( 'class' => $field );
@@ -420,16 +421,15 @@ class AllmessagesTablePager extends TablePager {
return SpecialPage::getTitleFor( 'Allmessages', false );
}
- function isFieldSortable( $x ){
+ function isFieldSortable( $x ) {
return false;
}
- function getDefaultSort(){
+ function getDefaultSort() {
return '';
}
- function getQueryInfo(){
+ function getQueryInfo() {
return '';
}
}
-
diff --git a/includes/specials/SpecialAllpages.php b/includes/specials/SpecialAllpages.php
index 0f8b2557..f9cb5cd8 100644
--- a/includes/specials/SpecialAllpages.php
+++ b/includes/specials/SpecialAllpages.php
@@ -59,16 +59,16 @@ class SpecialAllpages extends IncludableSpecialPage {
/**
* Constructor
*
- * @param $name string: name of the special page, as seen in links and URLs (default: 'Allpages')
+ * @param string $name name of the special page, as seen in links and URLs (default: 'Allpages')
*/
- function __construct( $name = 'Allpages' ){
+ function __construct( $name = 'Allpages' ) {
parent::__construct( $name );
}
/**
* Entry point : initialise variables and call subfunctions.
*
- * @param $par String: becomes "FOO" when called like Special:Allpages/FOO (default NULL)
+ * @param string $par becomes "FOO" when called like Special:Allpages/FOO (default NULL)
*/
function execute( $par ) {
global $wgContLang;
@@ -107,16 +107,16 @@ class SpecialAllpages extends IncludableSpecialPage {
* HTML for the top form
*
* @param $namespace Integer: a namespace constant (default NS_MAIN).
- * @param $from String: dbKey we are starting listing at.
- * @param $to String: dbKey we are ending listing at.
- * @param $hideredirects Bool: dont show redirects (default FALSE)
+ * @param string $from dbKey we are starting listing at.
+ * @param string $to dbKey we are ending listing at.
+ * @param bool $hideredirects dont show redirects (default FALSE)
* @return string
*/
function namespaceForm( $namespace = NS_MAIN, $from = '', $to = '', $hideredirects = false ) {
global $wgScript;
$t = $this->getTitle();
- $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) );
+ $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) );
$out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
$out .= Html::hidden( 'title', $t->getPrefixedText() );
$out .= Xml::openElement( 'fieldset' );
@@ -127,7 +127,7 @@ class SpecialAllpages extends IncludableSpecialPage {
Xml::label( $this->msg( 'allpagesfrom' )->text(), 'nsfrom' ) .
" </td>
<td class='mw-input'>" .
- Xml::input( 'from', 30, str_replace('_',' ',$from), array( 'id' => 'nsfrom' ) ) .
+ Xml::input( 'from', 30, str_replace( '_', ' ', $from ), array( 'id' => 'nsfrom' ) ) .
" </td>
</tr>
<tr>
@@ -135,7 +135,7 @@ class SpecialAllpages extends IncludableSpecialPage {
Xml::label( $this->msg( 'allpagesto' )->text(), 'nsto' ) .
" </td>
<td class='mw-input'>" .
- Xml::input( 'to', 30, str_replace('_',' ',$to), array( 'id' => 'nsto' ) ) .
+ Xml::input( 'to', 30, str_replace( '_', ' ', $to ), array( 'id' => 'nsto' ) ) .
" </td>
</tr>
<tr>
@@ -165,9 +165,9 @@ class SpecialAllpages extends IncludableSpecialPage {
/**
* @param $namespace Integer (default NS_MAIN)
- * @param $from String: list all pages from this name
- * @param $to String: list all pages to this name
- * @param $hideredirects Bool: dont show redirects (default FALSE)
+ * @param string $from list all pages from this name
+ * @param string $to list all pages to this name
+ * @param bool $hideredirects dont show redirects (default FALSE)
*/
function showToplevel( $namespace = NS_MAIN, $from = '', $to = '', $hideredirects = false ) {
$output = $this->getOutput();
@@ -180,7 +180,7 @@ class SpecialAllpages extends IncludableSpecialPage {
$where = array( 'page_namespace' => $namespace );
if ( $hideredirects ) {
- $where[ 'page_is_redirect' ] = 0;
+ $where['page_is_redirect'] = 0;
}
$from = Title::makeTitleSafe( $namespace, $from );
@@ -188,18 +188,18 @@ class SpecialAllpages extends IncludableSpecialPage {
$from = ( $from && $from->isLocal() ) ? $from->getDBkey() : null;
$to = ( $to && $to->isLocal() ) ? $to->getDBkey() : null;
- if( isset($from) )
- $where[] = 'page_title >= '.$dbr->addQuotes( $from );
- if( isset($to) )
- $where[] = 'page_title <= '.$dbr->addQuotes( $to );
+ if( isset( $from ) )
+ $where[] = 'page_title >= ' . $dbr->addQuotes( $from );
+ if( isset( $to ) )
+ $where[] = 'page_title <= ' . $dbr->addQuotes( $to );
global $wgMemc;
- $key = wfMemcKey( 'allpages', 'ns', $namespace, $from, $to );
+ $key = wfMemcKey( 'allpages', 'ns', $namespace, sha1( $from ), sha1( $to ) );
$lines = $wgMemc->get( $key );
$count = $dbr->estimateRowCount( 'page', '*', $where, __METHOD__ );
- $maxPerSubpage = intval($count/$this->maxLineCount);
- $maxPerSubpage = max($maxPerSubpage,$this->maxPerPage);
+ $maxPerSubpage = intval( $count / $this->maxLineCount );
+ $maxPerSubpage = max( $maxPerSubpage, $this->maxPerPage );
if( !is_array( $lines ) ) {
$options = array( 'LIMIT' => 1 );
@@ -217,9 +217,9 @@ class SpecialAllpages extends IncludableSpecialPage {
: array( 'page_title >= ' . $dbr->addQuotes( $lastTitle ) );
$res = $dbr->select( 'page', /* FROM */
'page_title', /* WHAT */
- array_merge($where,$chunk),
+ array_merge( $where, $chunk ),
__METHOD__,
- array ('LIMIT' => 2, 'OFFSET' => $maxPerSubpage - 1, 'ORDER BY' => 'page_title ASC')
+ array( 'LIMIT' => 2, 'OFFSET' => $maxPerSubpage - 1, 'ORDER BY' => 'page_title ASC' )
);
$s = $dbr->fetchObject( $res );
@@ -228,7 +228,7 @@ class SpecialAllpages extends IncludableSpecialPage {
} else {
// Final chunk, but ended prematurely. Go back and find the end.
$endTitle = $dbr->selectField( 'page', 'MAX(page_title)',
- array_merge($where,$chunk),
+ array_merge( $where, $chunk ),
__METHOD__ );
array_push( $lines, $endTitle );
$done = true;
@@ -250,7 +250,7 @@ class SpecialAllpages extends IncludableSpecialPage {
// If there are only two or less sections, don't even display them.
// Instead, display the first section directly.
if( count( $lines ) <= 2 ) {
- if( !empty($lines) ) {
+ if( !empty( $lines ) ) {
$this->showChunk( $namespace, $from, $to, $hideredirects );
} else {
$output->addHTML( $this->namespaceForm( $namespace, $from, $to, $hideredirects ) );
@@ -272,7 +272,7 @@ class SpecialAllpages extends IncludableSpecialPage {
if( $this->including() ) {
$out2 = '';
} else {
- if( isset($from) || isset($to) ) {
+ if( isset( $from ) || isset( $to ) ) {
$out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ).
'<tr>
<td>' .
@@ -294,10 +294,10 @@ class SpecialAllpages extends IncludableSpecialPage {
/**
* Show a line of "ABC to DEF" ranges of articles
*
- * @param $inpoint String: lower limit of pagenames
- * @param $outpoint String: upper limit of pagenames
+ * @param string $inpoint lower limit of pagenames
+ * @param string $outpoint upper limit of pagenames
* @param $namespace Integer (Default NS_MAIN)
- * @param $hideredirects Bool: dont show redirects (default FALSE)
+ * @param bool $hideredirects dont show redirects (default FALSE)
* @return string
*/
function showline( $inpoint, $outpoint, $namespace = NS_MAIN, $hideredirects ) {
@@ -311,12 +311,12 @@ class SpecialAllpages extends IncludableSpecialPage {
$queryparams = $namespace ? "namespace=$namespace&" : '';
$queryhideredirects = array();
- if ($hideredirects) {
- $queryhideredirects[ 'hideredirects' ] = 1;
+ if ( $hideredirects ) {
+ $queryhideredirects['hideredirects'] = 1;
}
$special = $this->getTitle();
- $link = htmlspecialchars( $special->getLocalUrl( $queryparams . 'from=' . urlencode($inpoint) . '&to=' . urlencode($outpoint), $queryhideredirects ) );
+ $link = htmlspecialchars( $special->getLocalUrl( $queryparams . 'from=' . urlencode( $inpoint ) . '&to=' . urlencode( $outpoint ), $queryhideredirects ) );
$out = $this->msg( 'alphaindexline' )->rawParams(
"<a href=\"$link\">$inpointf</a></td><td>",
@@ -327,15 +327,15 @@ class SpecialAllpages extends IncludableSpecialPage {
/**
* @param $namespace Integer (Default NS_MAIN)
- * @param $from String: list all pages from this name (default FALSE)
- * @param $to String: list all pages to this name (default FALSE)
- * @param $hideredirects Bool: dont show redirects (default FALSE)
+ * @param string $from list all pages from this name (default FALSE)
+ * @param string $to list all pages to this name (default FALSE)
+ * @param bool $hideredirects dont show redirects (default FALSE)
*/
function showChunk( $namespace = NS_MAIN, $from = false, $to = false, $hideredirects = false ) {
global $wgContLang;
$output = $this->getOutput();
- $fromList = $this->getNamespaceKeyAndText($namespace, $from);
+ $fromList = $this->getNamespaceKeyAndText( $namespace, $from );
$toList = $this->getNamespaceKeyAndText( $namespace, $to );
$namespaces = $wgContLang->getNamespaces();
$n = 0;
@@ -357,7 +357,7 @@ class SpecialAllpages extends IncludableSpecialPage {
);
if ( $hideredirects ) {
- $conds[ 'page_is_redirect' ] = 0;
+ $conds['page_is_redirect'] = 0;
}
if( $toKey !== "" ) {
@@ -416,10 +416,10 @@ class SpecialAllpages extends IncludableSpecialPage {
$res_prev = $dbr->select(
'page',
'page_title',
- array( 'page_namespace' => $namespace, 'page_title < '.$dbr->addQuotes($from) ),
+ array( 'page_namespace' => $namespace, 'page_title < ' . $dbr->addQuotes( $from ) ),
__METHOD__,
array( 'ORDER BY' => 'page_title DESC',
- 'LIMIT' => $this->maxPerPage, 'OFFSET' => ($this->maxPerPage - 1 )
+ 'LIMIT' => $this->maxPerPage, 'OFFSET' => ( $this->maxPerPage - 1 )
)
);
@@ -438,7 +438,7 @@ class SpecialAllpages extends IncludableSpecialPage {
array( 'page_namespace' => $namespace ), __METHOD__, $options );
# Show the previous link if it s not the current requested chunk
if( $from != $reallyFirstPage_title ) {
- $prevTitle = Title::makeTitle( $namespace, $reallyFirstPage_title );
+ $prevTitle = Title::makeTitle( $namespace, $reallyFirstPage_title );
} else {
$prevTitle = null;
}
@@ -457,7 +457,7 @@ class SpecialAllpages extends IncludableSpecialPage {
Linker::link( $self, $this->msg( 'allpages' )->escaped() );
# Do we put a previous link ?
- if( isset( $prevTitle ) && $pt = $prevTitle->getText() ) {
+ if( isset( $prevTitle ) && $pt = $prevTitle->getText() ) {
$query = array( 'from' => $prevTitle->getText() );
if( $namespace )
@@ -477,7 +477,7 @@ class SpecialAllpages extends IncludableSpecialPage {
if( $n == $this->maxPerPage && $s = $res->fetchObject() ) {
# $s is the first link of the next chunk
- $t = Title::makeTitle($namespace, $s->page_title);
+ $t = Title::makeTitle( $namespace, $s->page_title );
$query = array( 'from' => $t->getText() );
if( $namespace )
@@ -515,14 +515,14 @@ class SpecialAllpages extends IncludableSpecialPage {
/**
* @param $ns Integer: the namespace of the article
- * @param $text String: the name of the article
+ * @param string $text the name of the article
* @return array( int namespace, string dbkey, string pagename ) or NULL on error
*/
- protected function getNamespaceKeyAndText($ns, $text) {
+ protected function getNamespaceKeyAndText( $ns, $text ) {
if ( $text == '' )
return array( $ns, '', '' ); # shortcut for common case
- $t = Title::makeTitleSafe($ns, $text);
+ $t = Title::makeTitleSafe( $ns, $text );
if ( $t && $t->isLocal() ) {
return array( $t->getNamespace(), $t->getDBkey(), $t->getText() );
} elseif ( $t ) {
@@ -530,12 +530,16 @@ class SpecialAllpages extends IncludableSpecialPage {
}
# try again, in case the problem was an empty pagename
- $text = preg_replace('/(#|$)/', 'X$1', $text);
- $t = Title::makeTitleSafe($ns, $text);
+ $text = preg_replace( '/(#|$)/', 'X$1', $text );
+ $t = Title::makeTitleSafe( $ns, $text );
if ( $t && $t->isLocal() ) {
return array( $t->getNamespace(), '', '' );
} else {
return null;
}
}
+
+ protected function getGroupName() {
+ return 'pages';
+ }
}
diff --git a/includes/specials/SpecialAncientpages.php b/includes/specials/SpecialAncientpages.php
index 6e3d49bd..b0f333c4 100644
--- a/includes/specials/SpecialAncientpages.php
+++ b/includes/specials/SpecialAncientpages.php
@@ -36,7 +36,9 @@ class AncientPagesPage extends QueryPage {
return true;
}
- function isSyndicated() { return false; }
+ function isSyndicated() {
+ return false;
+ }
function getQueryInfo() {
return array(
@@ -69,4 +71,8 @@ class AncientPagesPage extends QueryPage {
);
return $this->getLanguage()->specialList( $link, htmlspecialchars( $d ) );
}
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
}
diff --git a/includes/specials/SpecialBlankpage.php b/includes/specials/SpecialBlankpage.php
index 42d33779..bfa2f951 100644
--- a/includes/specials/SpecialBlankpage.php
+++ b/includes/specials/SpecialBlankpage.php
@@ -33,6 +33,6 @@ class SpecialBlankpage extends UnlistedSpecialPage {
}
public function execute( $par ) {
$this->setHeaders();
- $this->getOutput()->addWikiMsg('intentionallyblankpage');
+ $this->getOutput()->addWikiMsg( 'intentionallyblankpage' );
}
}
diff --git a/includes/specials/SpecialBlock.php b/includes/specials/SpecialBlock.php
index 1d6656ab..50fdbc26 100644
--- a/includes/specials/SpecialBlock.php
+++ b/includes/specials/SpecialBlock.php
@@ -62,7 +62,7 @@ class SpecialBlock extends FormSpecialPage {
* @throws ErrorPageError
*/
protected function checkExecutePermissions( User $user ) {
- parent::checkExecutePermissions( $user );
+ parent::checkExecutePermissions( $user );
# bug 15810: blocked admins should have limited access here
$status = self::checkUnblockSelf( $this->target, $user );
@@ -134,6 +134,7 @@ class SpecialBlock extends FormSpecialPage {
'tabindex' => '1',
'id' => 'mw-bi-target',
'size' => '45',
+ 'autofocus' => true,
'required' => true,
'validation-callback' => array( __CLASS__, 'validateTargetField' ),
),
@@ -224,7 +225,7 @@ class SpecialBlock extends FormSpecialPage {
/**
* If the user has already been blocked with similar settings, load that block
* and change the defaults for the form fields to match the existing settings.
- * @param $fields Array HTMLForm descriptor array
+ * @param array $fields HTMLForm descriptor array
* @return Bool whether fields were altered (that is, whether the target is
* already blocked)
*/
@@ -239,7 +240,7 @@ class SpecialBlock extends FormSpecialPage {
if ( $block instanceof Block && !$block->mAuto # The block exists and isn't an autoblock
&& ( $this->type != Block::TYPE_RANGE # The block isn't a rangeblock
- || $block->getTarget() == $this->target ) # or if it is, the range is what we're about to block
+ || $block->getTarget() == $this->target ) # or if it is, the range is what we're about to block
)
{
$fields['HardBlock']['default'] = $block->isHardblock();
@@ -386,7 +387,7 @@ class SpecialBlock extends FormSpecialPage {
);
}
- $text = Html::rawElement(
+ $text = Html::rawElement(
'p',
array( 'class' => 'mw-ipb-conveniencelinks' ),
$this->getLanguage()->pipeList( $links )
@@ -450,7 +451,7 @@ class SpecialBlock extends FormSpecialPage {
/**
* Determine the target of the block, and the type of target
* TODO: should be in Block.php?
- * @param $par String subpage parameter passed to setup, or data value from
+ * @param string $par subpage parameter passed to setup, or data value from
* the HTMLForm
* @param $request WebRequest optionally try and get data from a request too
* @return array( User|string|null, Block::TYPE_ constant|null )
@@ -507,53 +508,74 @@ class SpecialBlock extends FormSpecialPage {
* @return Message
*/
public static function validateTargetField( $value, $alldata, $form ) {
+ $status = self::validateTarget( $value, $form->getUser() );
+ if ( !$status->isOK() ) {
+ $errors = $status->getErrorsArray();
+ return call_user_func_array( array( $form, 'msg' ), $errors[0] );
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Validate a block target.
+ *
+ * @since 1.21
+ * @param string $value Block target to check
+ * @param User $user Performer of the block
+ * @return Status
+ */
+ public static function validateTarget( $value, User $user ) {
global $wgBlockCIDRLimit;
list( $target, $type ) = self::getTargetAndType( $value );
+ $status = Status::newGood( $target );
if ( $type == Block::TYPE_USER ) {
- # TODO: why do we not have a User->exists() method?
- if ( !$target->getId() ) {
- return $form->msg( 'nosuchusershort',
- wfEscapeWikiText( $target->getName() ) );
+ if ( $target->isAnon() ) {
+ $status->fatal(
+ 'nosuchusershort',
+ wfEscapeWikiText( $target->getName() )
+ );
}
- $status = self::checkUnblockSelf( $target, $form->getUser() );
- if ( $status !== true ) {
- return $form->msg( 'badaccess', $status );
+ $unblockStatus = self::checkUnblockSelf( $target, $user );
+ if ( $unblockStatus !== true ) {
+ $status->fatal( 'badaccess', $unblockStatus );
}
-
} elseif ( $type == Block::TYPE_RANGE ) {
list( $ip, $range ) = explode( '/', $target, 2 );
- if ( ( IP::isIPv4( $ip ) && $wgBlockCIDRLimit['IPv4'] == 32 )
- || ( IP::isIPv6( $ip ) && $wgBlockCIDRLimit['IPv6'] == 128 ) )
- {
- # Range block effectively disabled
- return $form->msg( 'range_block_disabled' );
+ if (
+ ( IP::isIPv4( $ip ) && $wgBlockCIDRLimit['IPv4'] == 32 ) ||
+ ( IP::isIPv6( $ip ) && $wgBlockCIDRLimit['IPv6'] == 128 )
+ ) {
+ // Range block effectively disabled
+ $status->fatal( 'range_block_disabled' );
}
- if ( ( IP::isIPv4( $ip ) && $range > 32 )
- || ( IP::isIPv6( $ip ) && $range > 128 ) )
- {
- # Dodgy range
- return $form->msg( 'ip_range_invalid' );
+ if (
+ ( IP::isIPv4( $ip ) && $range > 32 ) ||
+ ( IP::isIPv6( $ip ) && $range > 128 )
+ ) {
+ // Dodgy range
+ $status->fatal( 'ip_range_invalid' );
}
if ( IP::isIPv4( $ip ) && $range < $wgBlockCIDRLimit['IPv4'] ) {
- return $form->msg( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv4'] );
+ $status->fatal( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv4'] );
}
if ( IP::isIPv6( $ip ) && $range < $wgBlockCIDRLimit['IPv6'] ) {
- return $form->msg( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv6'] );
+ $status->fatal( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv6'] );
}
} elseif ( $type == Block::TYPE_IP ) {
# All is well
} else {
- return $form->msg( 'badipaddress' );
+ $status->fatal( 'badipaddress' );
}
- return true;
+ return $status;
}
/**
@@ -629,7 +651,7 @@ class SpecialBlock extends FormSpecialPage {
}
if ( $data['HideUser'] ) {
- if ( !$performer->isAllowed('hideuser') ) {
+ if ( !$performer->isAllowed( 'hideuser' ) ) {
# this codepath is unreachable except by a malicious user spoofing forms,
# or by race conditions (user has oversight and sysop, loads block form,
# and is de-oversighted before submission); so need to fail completely
@@ -672,10 +694,16 @@ class SpecialBlock extends FormSpecialPage {
# Try to insert block. Is there a conflicting block?
$status = $block->insert();
if ( !$status ) {
+ # Indicates whether the user is confirming the block and is aware of
+ # the conflict (did not change the block target in the meantime)
+ $blockNotConfirmed = !$data['Confirm'] || ( array_key_exists( 'PreviousTarget', $data )
+ && $data['PreviousTarget'] !== $target );
+
+ # Special case for API - bug 32434
+ $reblockNotAllowed = ( array_key_exists( 'Reblock', $data ) && !$data['Reblock'] );
+
# Show form unless the user is already aware of this...
- if ( !$data['Confirm'] || ( array_key_exists( 'PreviousTarget', $data )
- && $data['PreviousTarget'] !== $target ) )
- {
+ if( $blockNotConfirmed || $reblockNotAllowed ) {
return array( array( 'ipb_already_blocked', $block->getTarget() ) );
# Otherwise, try to update the block...
} else {
@@ -742,7 +770,7 @@ class SpecialBlock extends FormSpecialPage {
$logParams
);
# Relate log ID to block IDs (bug 25763)
- $blockIds = array_merge( array( $status['id'] ), $status['autoIds'] );
+ $blockIds = array_merge( array( $status['id'] ), $status['autoIds'] );
$log->addRelations( 'ipb_id', $blockIds, $log_id );
# Report to the user
@@ -782,7 +810,7 @@ class SpecialBlock extends FormSpecialPage {
/**
* Convert a submitted expiry time, which may be relative ("2 weeks", etc) or absolute
* ("24 May 2034", etc), into an absolute timestamp we can put into the database.
- * @param $expiry String: whatever was typed into the form
+ * @param string $expiry whatever was typed into the form
* @return String: timestamp or "infinity" string for the DB implementation
*/
public static function parseExpiryInput( $expiry ) {
@@ -855,7 +883,7 @@ class SpecialBlock extends FormSpecialPage {
/**
* Return a comma-delimited list of "flags" to be passed to the log
* reader for this block, to provide more information in the logs
- * @param $data Array from HTMLForm data
+ * @param array $data from HTMLForm data
* @param $type Block::TYPE_ constant (USER, RANGE, or IP)
* @return string
*/
@@ -918,6 +946,10 @@ class SpecialBlock extends FormSpecialPage {
$out->setPageTitle( $this->msg( 'blockipsuccesssub' ) );
$out->addWikiMsg( 'blockipsuccesstext', wfEscapeWikiText( $this->target ) );
}
+
+ protected function getGroupName() {
+ return 'users';
+ }
}
# BC @since 1.18
diff --git a/includes/specials/SpecialBlockList.php b/includes/specials/SpecialBlockList.php
index 7143d5bc..e10df4fe 100644
--- a/includes/specials/SpecialBlockList.php
+++ b/includes/specials/SpecialBlockList.php
@@ -37,7 +37,7 @@ class SpecialBlockList extends SpecialPage {
/**
* Main execution point
*
- * @param $par String title fragment
+ * @param string $par title fragment
*/
public function execute( $par ) {
$this->setHeaders();
@@ -113,14 +113,14 @@ class SpecialBlockList extends SpecialPage {
$conds = array();
# Is the user allowed to see hidden blocks?
- if ( !$this->getUser()->isAllowed( 'hideuser' ) ){
+ if ( !$this->getUser()->isAllowed( 'hideuser' ) ) {
$conds['ipb_deleted'] = 0;
}
- if ( $this->target !== '' ){
+ if ( $this->target !== '' ) {
list( $target, $type ) = Block::parseTarget( $this->target );
- switch( $type ){
+ switch( $type ) {
case Block::TYPE_ID:
case Block::TYPE_AUTO:
$conds['ipb_id'] = $target;
@@ -205,6 +205,10 @@ class SpecialBlockList extends SpecialPage {
$out->addHTML( Html::rawElement( 'ul', array( 'class' => 'mw-ipblocklist-otherblocks' ), $list ) . "\n" );
}
}
+
+ protected function getGroupName() {
+ return 'users';
+ }
}
class BlockListPager extends TablePager {
@@ -269,11 +273,11 @@ class BlockListPager extends TablePager {
break;
case 'ipb_target':
- if( $row->ipb_auto ){
+ if( $row->ipb_auto ) {
$formatted = $this->msg( 'autoblockid', $row->ipb_id )->parse();
} else {
list( $target, $type ) = Block::parseTarget( $row->ipb_address );
- switch( $type ){
+ switch( $type ) {
case Block::TYPE_USER:
case Block::TYPE_IP:
$formatted = Linker::userLink( $target->getId(), $target );
@@ -292,8 +296,8 @@ class BlockListPager extends TablePager {
case 'ipb_expiry':
$formatted = $this->getLanguage()->formatExpiry( $value, /* User preference timezone */ true );
- if( $this->getUser()->isAllowed( 'block' ) ){
- if( $row->ipb_auto ){
+ if( $this->getUser()->isAllowed( 'block' ) ) {
+ if( $row->ipb_auto ) {
$links[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Unblock' ),
$msg['unblocklink'],
@@ -329,7 +333,7 @@ class BlockListPager extends TablePager {
break;
case 'ipb_reason':
- $formatted = Linker::commentBlock( $value );
+ $formatted = Linker::formatComment( $value );
break;
case 'ipb_params':
@@ -391,14 +395,14 @@ class BlockListPager extends TablePager {
);
# Is the user allowed to see hidden blocks?
- if ( !$this->getUser()->isAllowed( 'hideuser' ) ){
+ if ( !$this->getUser()->isAllowed( 'hideuser' ) ) {
$info['conds']['ipb_deleted'] = 0;
}
return $info;
}
- public function getTableClass(){
+ public function getTableClass() {
return 'TablePager mw-blocklist';
}
@@ -418,7 +422,7 @@ class BlockListPager extends TablePager {
* Do a LinkBatch query to minimise database load when generating all these links
* @param $result
*/
- function preprocessResults( $result ){
+ function preprocessResults( $result ) {
wfProfileIn( __METHOD__ );
# Do a link batch query
$lb = new LinkBatch;
@@ -437,11 +441,11 @@ class BlockListPager extends TablePager {
}
$ua = UserArray::newFromIDs( $userids );
- foreach( $ua as $user ){
+ foreach( $ua as $user ) {
$name = str_replace( ' ', '_', $user->getName() );
$lb->add( NS_USER, $name );
$lb->add( NS_USER_TALK, $name );
- }
+ }
$lb->execute();
wfProfileOut( __METHOD__ );
@@ -472,7 +476,7 @@ class HTMLBlockedUsersItemSelect extends HTMLSelectField {
// This adds the explicitly requested limit value to the drop-down,
// then makes sure it's sorted correctly so when we output the list
// later, the custom option doesn't just show up last.
- $this->mParams['options'][ $this->mParent->getLanguage()->formatNum( $value ) ] = intval($value);
+ $this->mParams['options'][$this->mParent->getLanguage()->formatNum( $value )] = intval( $value );
asort( $this->mParams['options'] );
}
diff --git a/includes/specials/SpecialBlockme.php b/includes/specials/SpecialBlockme.php
index 3840b2ff..85a3019e 100644
--- a/includes/specials/SpecialBlockme.php
+++ b/includes/specials/SpecialBlockme.php
@@ -22,7 +22,7 @@
*/
/**
- * A special page called by proxy_check.php to block open proxies
+ * A special page called by proxyCheck.php to block open proxies
*
* @ingroup SpecialPage
*/
@@ -59,4 +59,8 @@ class SpecialBlockme extends UnlistedSpecialPage {
$this->getOutput()->addWikiMsg( 'proxyblocksuccess' );
}
+
+ protected function getGroupName() {
+ return 'other';
+ }
}
diff --git a/includes/specials/SpecialBooksources.php b/includes/specials/SpecialBooksources.php
index bf7de3f5..bdbd77b8 100644
--- a/includes/specials/SpecialBooksources.php
+++ b/includes/specials/SpecialBooksources.php
@@ -46,7 +46,7 @@ class SpecialBookSources extends SpecialPage {
/**
* Show the special page
*
- * @param $isbn string ISBN passed as a subpage parameter
+ * @param string $isbn ISBN passed as a subpage parameter
*/
public function execute( $isbn ) {
$this->setHeaders();
@@ -62,8 +62,8 @@ class SpecialBookSources extends SpecialPage {
}
/**
- * Returns whether a given ISBN (10 or 13) is valid. True indicates validity.
- * @param isbn string ISBN passed for check
+ * Returns whether a given ISBN (10 or 13) is valid. True indicates validity.
+ * @param string $isbn ISBN passed for check
* @return bool
*/
public static function isValidISBN( $isbn ) {
@@ -71,7 +71,7 @@ class SpecialBookSources extends SpecialPage {
$sum = 0;
if( strlen( $isbn ) == 13 ) {
for( $i = 0; $i < 12; $i++ ) {
- if($i % 2 == 0) {
+ if( $i % 2 == 0 ) {
$sum += $isbn[$i];
} else {
$sum += 3 * $isbn[$i];
@@ -79,19 +79,19 @@ class SpecialBookSources extends SpecialPage {
}
$check = (10 - ($sum % 10)) % 10;
- if ($check == $isbn[12]) {
+ if ( $check == $isbn[12] ) {
return true;
}
} elseif( strlen( $isbn ) == 10 ) {
- for($i = 0; $i < 9; $i++) {
+ for( $i = 0; $i < 9; $i++ ) {
$sum += $isbn[$i] * ($i + 1);
}
$check = $sum % 11;
- if($check == 10) {
+ if( $check == 10 ) {
$check = "X";
}
- if($check == $isbn[9]) {
+ if( $check == $isbn[9] ) {
return true;
}
}
@@ -101,7 +101,7 @@ class SpecialBookSources extends SpecialPage {
/**
* Trim ISBN and remove characters which aren't required
*
- * @param $isbn string Unclean ISBN
+ * @param string $isbn Unclean ISBN
* @return string
*/
private static function cleanIsbn( $isbn ) {
@@ -116,7 +116,7 @@ class SpecialBookSources extends SpecialPage {
private function makeForm() {
global $wgScript;
- $form = '<fieldset><legend>' . $this->msg( 'booksources-search-legend' )->escaped() . '</legend>';
+ $form = '<fieldset><legend>' . $this->msg( 'booksources-search-legend' )->escaped() . '</legend>';
$form .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
$form .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() );
$form .= '<p>' . Xml::inputLabel( $this->msg( 'booksources-isbn' )->text(), 'isbn', 'isbn', 20, $this->isbn );
@@ -130,6 +130,7 @@ class SpecialBookSources extends SpecialPage {
* Determine where to get the list of book sources from,
* format and output them
*
+ * @throws MWException
* @return string
*/
private function showList() {
@@ -144,8 +145,17 @@ class SpecialBookSources extends SpecialPage {
$title = Title::makeTitleSafe( NS_PROJECT, $page ); # Show list in content language
if( is_object( $title ) && $title->exists() ) {
$rev = Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
- $this->getOutput()->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $rev->getText() ) );
- return true;
+ $content = $rev->getContent();
+
+ if ( $content instanceof TextContent ) {
+ //XXX: in the future, this could be stored as structured data, defining a list of book sources
+
+ $text = $content->getNativeData();
+ $this->getOutput()->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $text ) );
+ return true;
+ } else {
+ throw new MWException( "Unexpected content type for book sources: " . $content->getModel() );
+ }
}
# Fall back to the defaults given in the language file
@@ -161,12 +171,16 @@ class SpecialBookSources extends SpecialPage {
/**
* Format a book source list item
*
- * @param $label string Book source label
- * @param $url string Book source URL
+ * @param string $label Book source label
+ * @param string $url Book source URL
* @return string
*/
private function makeListItem( $label, $url ) {
$url = str_replace( '$1', $this->isbn, $url );
return '<li><a href="' . htmlspecialchars( $url ) . '" class="external">' . htmlspecialchars( $label ) . '</a></li>';
}
+
+ protected function getGroupName() {
+ return 'other';
+ }
}
diff --git a/includes/specials/SpecialBrokenRedirects.php b/includes/specials/SpecialBrokenRedirects.php
index 8119e6d1..fac41236 100644
--- a/includes/specials/SpecialBrokenRedirects.php
+++ b/includes/specials/SpecialBrokenRedirects.php
@@ -33,35 +33,54 @@ class BrokenRedirectsPage extends QueryPage {
parent::__construct( $name );
}
- function isExpensive() { return true; }
- function isSyndicated() { return false; }
- function sortDescending() { return false; }
+ function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ function sortDescending() {
+ return false;
+ }
function getPageHeader() {
return $this->msg( 'brokenredirectstext' )->parseAsBlock();
}
function getQueryInfo() {
+ $dbr = wfGetDB( DB_SLAVE );
return array(
- 'tables' => array( 'redirect', 'p1' => 'page',
- 'p2' => 'page' ),
- 'fields' => array( 'namespace' => 'p1.page_namespace',
- 'title' => 'p1.page_title',
- 'value' => 'p1.page_title',
- 'rd_namespace',
- 'rd_title'
+ 'tables' => array(
+ 'redirect',
+ 'p1' => 'page',
+ 'p2' => 'page',
),
- 'conds' => array( 'rd_namespace >= 0',
- 'p2.page_namespace IS NULL'
+ 'fields' => array(
+ 'namespace' => 'p1.page_namespace',
+ 'title' => 'p1.page_title',
+ 'value' => 'p1.page_title',
+ 'rd_namespace',
+ 'rd_title',
+ ),
+ 'conds' => array(
+ // Exclude pages that don't exist locally as wiki pages,
+ // but aren't "broken" either.
+ // Special pages and interwiki links
+ 'rd_namespace >= 0',
+ 'rd_interwiki IS NULL OR rd_interwiki = ' . $dbr->addQuotes( '' ),
+ 'p2.page_namespace IS NULL',
+ ),
+ 'join_conds' => array(
+ 'p1' => array( 'JOIN', array(
+ 'rd_from=p1.page_id',
+ ) ),
+ 'p2' => array( 'LEFT JOIN', array(
+ 'rd_namespace=p2.page_namespace',
+ 'rd_title=p2.page_title'
+ ) ),
),
- 'join_conds' => array( 'p1' => array( 'JOIN', array(
- 'rd_from=p1.page_id',
- ) ),
- 'p2' => array( 'LEFT JOIN', array(
- 'rd_namespace=p2.page_namespace',
- 'rd_title=p2.page_title'
- ) )
- )
);
}
@@ -132,4 +151,8 @@ class BrokenRedirectsPage extends QueryPage {
$out .= " {$arr} {$to}";
return $out;
}
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
}
diff --git a/includes/specials/SpecialCachedPage.php b/includes/specials/SpecialCachedPage.php
index b3f6c720..ddd11ad0 100644
--- a/includes/specials/SpecialCachedPage.php
+++ b/includes/specials/SpecialCachedPage.php
@@ -119,7 +119,7 @@ abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper {
*
* @since 1.20
*
- * @param {function} $computeFunction
+ * @param callable $computeFunction
* @param array|mixed $args
* @param string|null $key
*
@@ -137,7 +137,7 @@ abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper {
*
* @since 1.20
*
- * @param {function} $computeFunction
+ * @param callable $computeFunction
* @param array $args
* @param string|null $key
*/
diff --git a/includes/specials/SpecialCategories.php b/includes/specials/SpecialCategories.php
index 1232e3fa..9040c640 100644
--- a/includes/specials/SpecialCategories.php
+++ b/includes/specials/SpecialCategories.php
@@ -50,6 +50,10 @@ class SpecialCategories extends SpecialPage {
Html::closeElement( 'div' )
);
}
+
+ protected function getGroupName() {
+ return 'pages';
+ }
}
/**
@@ -72,7 +76,7 @@ class CategoryPager extends AlphabeticPager {
function getQueryInfo() {
return array(
'tables' => array( 'category' ),
- 'fields' => array( 'cat_title','cat_pages' ),
+ 'fields' => array( 'cat_title', 'cat_pages' ),
'conds' => array( 'cat_pages > 0' ),
'options' => array( 'USE INDEX' => 'cat_title' ),
);
@@ -112,7 +116,7 @@ class CategoryPager extends AlphabeticPager {
return parent::getBody();
}
- function formatRow($result) {
+ function formatRow( $result ) {
$title = Title::makeTitle( NS_CATEGORY, $result->cat_title );
$titleText = Linker::link( $title, htmlspecialchars( $title->getText() ) );
$count = $this->msg( 'nmembers' )->numParams( $result->cat_pages )->escaped();
diff --git a/includes/specials/SpecialChangeEmail.php b/includes/specials/SpecialChangeEmail.php
index fc726106..59a02578 100644
--- a/includes/specials/SpecialChangeEmail.php
+++ b/includes/specials/SpecialChangeEmail.php
@@ -151,10 +151,10 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
$items = array(
array( 'wpName', 'username', 'text', $user->getName() ),
array( 'wpOldEmail', 'changeemail-oldemail', 'text', $oldEmailText ),
- array( 'wpNewEmail', 'changeemail-newemail', 'input', $this->mNewEmail ),
+ array( 'wpNewEmail', 'changeemail-newemail', 'email', $this->mNewEmail ),
);
if ( $wgRequirePasswordforEmailChange ) {
- $items[] = array( 'wpPassword', 'yourpassword', 'password', $this->mPassword );
+ $items[] = array( 'wpPassword', 'changeemail-password', 'password', $this->mPassword );
}
$this->getOutput()->addHTML(
@@ -195,7 +195,7 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
if ( $type != 'text' ) {
$out .= Xml::label( $this->msg( $label )->text(), $name );
} else {
- $out .= $this->msg( $label )->escaped();
+ $out .= $this->msg( $label )->escaped();
}
$out .= "</td>\n";
$out .= "\t<td class='mw-input'>";
@@ -213,6 +213,8 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
* @return bool|string true or string on success, false on failure
*/
protected function attemptChange( User $user, $pass, $newaddr ) {
+ global $wgAuth;
+
if ( $newaddr != '' && !Sanitizer::validateEmail( $newaddr ) ) {
$this->error( 'invalidemailaddress' );
return false;
@@ -248,6 +250,12 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
$user->saveSettings();
+ $wgAuth->updateExternalDB( $user );
+
return $status->value;
}
+
+ protected function getGroupName() {
+ return 'users';
+ }
}
diff --git a/includes/specials/SpecialChangePassword.php b/includes/specials/SpecialChangePassword.php
index ba728ac2..e538caca 100644
--- a/includes/specials/SpecialChangePassword.php
+++ b/includes/specials/SpecialChangePassword.php
@@ -27,6 +27,9 @@
* @ingroup SpecialPage
*/
class SpecialChangePassword extends UnlistedSpecialPage {
+
+ protected $mUserName, $mOldpass, $mNewpass, $mRetype, $mDomain;
+
public function __construct() {
parent::__construct( 'ChangePassword' );
}
@@ -70,8 +73,10 @@ class SpecialChangePassword extends UnlistedSpecialPage {
}
$this->attemptReset( $this->mNewpass, $this->mRetype );
- $this->getOutput()->addWikiMsg( 'resetpass_success' );
- if( !$user->isLoggedIn() ) {
+
+ if( $user->isLoggedIn() ) {
+ $this->doReturnTo();
+ } else {
LoginForm::setLoginToken();
$token = LoginForm::getLoginToken();
$data = array(
@@ -79,17 +84,13 @@ class SpecialChangePassword extends UnlistedSpecialPage {
'wpName' => $this->mUserName,
'wpDomain' => $this->mDomain,
'wpLoginToken' => $token,
- 'wpPassword' => $this->mNewpass,
- 'returnto' => $request->getVal( 'returnto' ),
- );
- if( $request->getCheck( 'wpRemember' ) ) {
- $data['wpRemember'] = 1;
- }
+ 'wpPassword' => $request->getVal( 'wpNewPassword' ),
+ ) + $request->getValues( 'wpRemember', 'returnto', 'returntoquery' );
$login = new LoginForm( new FauxRequest( $data, true ) );
$login->setContext( $this->getContext() );
$login->execute( null );
}
- $this->doReturnTo();
+ return;
} catch( PasswordError $e ) {
$this->error( $e->getMessage() );
}
@@ -98,15 +99,20 @@ class SpecialChangePassword extends UnlistedSpecialPage {
}
function doReturnTo() {
- $titleObj = Title::newFromText( $this->getRequest()->getVal( 'returnto' ) );
+ $request = $this->getRequest();
+ $titleObj = Title::newFromText( $request->getVal( 'returnto' ) );
if ( !$titleObj instanceof Title ) {
$titleObj = Title::newMainPage();
}
- $this->getOutput()->redirect( $titleObj->getFullURL() );
+ $query = $request->getVal( 'returntoquery' );
+ $this->getOutput()->redirect( $titleObj->getFullURL( $query ) );
}
+ /**
+ * @param $msg string
+ */
function error( $msg ) {
- $this->getOutput()->addHTML( Xml::element('p', array( 'class' => 'error' ), $msg ) );
+ $this->getOutput()->addHTML( Xml::element( 'p', array( 'class' => 'error' ), $msg ) );
}
function showForm() {
@@ -142,6 +148,15 @@ class SpecialChangePassword extends UnlistedSpecialPage {
array( 'wpRetype', 'retypenew', 'password', null ),
);
$prettyFields = array_merge( $prettyFields, $extraFields );
+ $hiddenFields = array(
+ 'token' => $user->getEditToken(),
+ 'wpName' => $this->mUserName,
+ 'wpDomain' => $this->mDomain,
+ ) + $this->getRequest()->getValues( 'returnto', 'returntoquery' );
+ $hiddenFieldsStr = '';
+ foreach( $hiddenFields as $fieldname => $fieldvalue ) {
+ $hiddenFieldsStr .= Html::hidden( $fieldname, $fieldvalue ) . "\n";
+ }
$this->getOutput()->addHTML(
Xml::fieldset( $this->msg( 'resetpass_header' )->text() ) .
Xml::openElement( 'form',
@@ -149,10 +164,7 @@ class SpecialChangePassword extends UnlistedSpecialPage {
'method' => 'post',
'action' => $this->getTitle()->getLocalUrl(),
'id' => 'mw-resetpass-form' ) ) . "\n" .
- Html::hidden( 'token', $user->getEditToken() ) . "\n" .
- Html::hidden( 'wpName', $this->mUserName ) . "\n" .
- Html::hidden( 'wpDomain', $this->mDomain ) . "\n" .
- Html::hidden( 'returnto', $this->getRequest()->getVal( 'returnto' ) ) . "\n" .
+ $hiddenFieldsStr .
$this->msg( 'resetpass_text' )->parseAsBlock() . "\n" .
Xml::openElement( 'table', array( 'id' => 'mw-resetpass-table' ) ) . "\n" .
$this->pretty( $prettyFields ) . "\n" .
@@ -170,6 +182,10 @@ class SpecialChangePassword extends UnlistedSpecialPage {
);
}
+ /**
+ * @param $fields array
+ * @return string
+ */
function pretty( $fields ) {
$out = '';
foreach ( $fields as $list ) {
@@ -192,7 +208,7 @@ class SpecialChangePassword extends UnlistedSpecialPage {
if ( $type != 'text' )
$out .= Xml::label( $this->msg( $label )->text(), $name );
else
- $out .= $this->msg( $label )->escaped();
+ $out .= $this->msg( $label )->escaped();
$out .= "</td>\n";
$out .= "\t<td class='mw-input'>";
$out .= $field;
@@ -206,7 +222,13 @@ class SpecialChangePassword extends UnlistedSpecialPage {
* @throws PasswordError when cannot set the new password because requirements not met.
*/
protected function attemptReset( $newpass, $retype ) {
- $user = User::newFromName( $this->mUserName );
+ $isSelf = ( $this->mUserName === $this->getUser()->getName() );
+ if ( $isSelf ) {
+ $user = $this->getUser();
+ } else {
+ $user = User::newFromName( $this->mUserName );
+ }
+
if( !$user || $user->isAnon() ) {
throw new PasswordError( $this->msg( 'nosuchusershort', $this->mUserName )->text() );
}
@@ -227,7 +249,7 @@ class SpecialChangePassword extends UnlistedSpecialPage {
throw new PasswordError( $this->msg( $abortMsg )->text() );
}
- if( !$user->checkTemporaryPassword($this->mOldpass) && !$user->checkPassword($this->mOldpass) ) {
+ if( !$user->checkTemporaryPassword( $this->mOldpass ) && !$user->checkPassword( $this->mOldpass ) ) {
wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'wrongpassword' ) );
throw new PasswordError( $this->msg( 'resetpass-wrong-oldpass' )->text() );
}
@@ -240,13 +262,22 @@ class SpecialChangePassword extends UnlistedSpecialPage {
try {
$user->setPassword( $this->mNewpass );
wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'success' ) );
- $this->mNewpass = $this->mOldpass = $this->mRetypePass = '';
+ $this->mNewpass = $this->mOldpass = $this->mRetype = '';
} catch( PasswordError $e ) {
wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'error' ) );
throw new PasswordError( $e->getMessage() );
}
- $user->setCookies();
+ if ( $isSelf ) {
+ // This is needed to keep the user connected since
+ // changing the password also modifies the user's token.
+ $user->setCookies();
+ }
+
$user->saveSettings();
}
+
+ protected function getGroupName() {
+ return 'users';
+ }
}
diff --git a/includes/specials/SpecialComparePages.php b/includes/specials/SpecialComparePages.php
index 9e3c52b9..c3bd3fec 100644
--- a/includes/specials/SpecialComparePages.php
+++ b/includes/specials/SpecialComparePages.php
@@ -106,28 +106,33 @@ class SpecialComparePages extends SpecialPage {
$form->trySubmit();
}
- public static function showDiff( $data, HTMLForm $form ){
+ public static function showDiff( $data, HTMLForm $form ) {
$rev1 = self::revOrTitle( $data['Revision1'], $data['Page1'] );
$rev2 = self::revOrTitle( $data['Revision2'], $data['Page2'] );
if( $rev1 && $rev2 ) {
- $de = new DifferenceEngine( $form->getContext(),
- $rev1,
- $rev2,
- null, // rcid
- ( $data['Action'] == 'purge' ),
- ( $data['Unhide'] == '1' )
- );
- $de->showDiffPage( true );
+ $revision = Revision::newFromId( $rev1 );
+
+ if ( $revision ) { // NOTE: $rev1 was already checked, should exist.
+ $contentHandler = $revision->getContentHandler();
+ $de = $contentHandler->createDifferenceEngine( $form->getContext(),
+ $rev1,
+ $rev2,
+ null, // rcid
+ ( $data['Action'] == 'purge' ),
+ ( $data['Unhide'] == '1' )
+ );
+ $de->showDiffPage( true );
+ }
}
}
public static function revOrTitle( $revision, $title ) {
- if( $revision ){
+ if( $revision ) {
return $revision;
} elseif( $title ) {
$title = Title::newFromText( $title );
- if( $title instanceof Title ){
+ if( $title instanceof Title ) {
return $title->getLatestRevID();
}
}
@@ -158,4 +163,8 @@ class SpecialComparePages extends SpecialPage {
}
return true;
}
+
+ protected function getGroupName() {
+ return 'pagetools';
+ }
}
diff --git a/includes/specials/SpecialConfirmemail.php b/includes/specials/SpecialConfirmemail.php
index 3e9ce128..078c3865 100644
--- a/includes/specials/SpecialConfirmemail.php
+++ b/includes/specials/SpecialConfirmemail.php
@@ -98,7 +98,7 @@ class EmailConfirmation extends UnlistedSpecialPage {
$out->wrapWikiMsg( "<div class=\"error mw-confirmemail-pending\">\n$1\n</div>", 'confirmemail_pending' );
}
$out->addWikiMsg( 'confirmemail_text' );
- $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalUrl() ) );
+ $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalUrl() ) );
$form .= Html::hidden( 'token', $user->getEditToken() );
$form .= Xml::submitButton( $this->msg( 'confirmemail_send' )->text() );
$form .= Xml::closeElement( 'form' );
@@ -110,7 +110,7 @@ class EmailConfirmation extends UnlistedSpecialPage {
* Attempt to confirm the user's email address and show success or failure
* as needed; if successful, take the user to log in
*
- * @param $code string Confirmation code
+ * @param string $code Confirmation code
*/
function attemptConfirm( $code ) {
$user = User::newFromConfirmationCode( $code );
@@ -145,9 +145,7 @@ class EmailInvalidation extends UnlistedSpecialPage {
function execute( $code ) {
$this->setHeaders();
- if ( wfReadOnly() ) {
- throw new ReadOnlyError;
- }
+ $this->checkReadOnly();
$this->attemptInvalidate( $code );
}
@@ -156,7 +154,7 @@ class EmailInvalidation extends UnlistedSpecialPage {
* Attempt to invalidate the user's email address and show success or failure
* as needed; if successful, link to main page
*
- * @param $code string Confirmation code
+ * @param string $code Confirmation code
*/
function attemptInvalidate( $code ) {
$user = User::newFromConfirmationCode( $code );
diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php
index 54f8e261..b118059c 100644
--- a/includes/specials/SpecialContributions.php
+++ b/includes/specials/SpecialContributions.php
@@ -152,7 +152,7 @@ class SpecialContributions extends SpecialPage {
$apiParams['month'] = $this->opts['month'];
}
- $url = wfScript( 'api' ) . '?' . wfArrayToCGI( $apiParams );
+ $url = wfScript( 'api' ) . '?' . wfArrayToCgi( $apiParams );
$out->redirect( $url, '301' );
return;
@@ -192,7 +192,6 @@ class SpecialContributions extends SpecialPage {
}
$out->preventClickjacking( $pager->getPreventClickjacking() );
-
# Show the appropriate "footer" message - WHOIS tools, etc.
if ( $this->opts['contribs'] == 'newbie' ) {
$message = 'sp-contributions-footer-newbies';
@@ -360,7 +359,7 @@ class SpecialContributions extends SpecialPage {
if ( !isset( $this->opts['target'] ) ) {
$this->opts['target'] = '';
} else {
- $this->opts['target'] = str_replace( '_' , ' ' , $this->opts['target'] );
+ $this->opts['target'] = str_replace( '_', ' ', $this->opts['target'] );
}
if ( !isset( $this->opts['namespace'] ) ) {
@@ -399,7 +398,7 @@ class SpecialContributions extends SpecialPage {
$this->opts['topOnly'] = false;
}
- $form = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'class' => 'mw-contributions-form' ) );
+ $form = Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'class' => 'mw-contributions-form' ) );
# Add hidden params for tracking except for parameters in $skipParameters
$skipParameters = array( 'namespace', 'nsInvert', 'deletedOnly', 'target', 'contribs', 'year', 'month', 'topOnly', 'associated' );
@@ -414,17 +413,17 @@ class SpecialContributions extends SpecialPage {
if ( $tagFilter ) {
$filterSelection =
- Xml::tags( 'td', array( 'class' => 'mw-label' ), array_shift( $tagFilter ) ) .
- Xml::tags( 'td', array( 'class' => 'mw-input' ), implode( '&#160', $tagFilter ) );
+ Html::rawElement( 'td', array( 'class' => 'mw-label' ), array_shift( $tagFilter ) ) .
+ Html::rawElement( 'td', array( 'class' => 'mw-input' ), implode( '&#160', $tagFilter ) );
} else {
- $filterSelection = Xml::tags( 'td', array( 'colspan' => 2 ), '' );
+ $filterSelection = Html::rawElement( 'td', array( 'colspan' => 2 ), '' );
}
- $targetSelection = Xml::tags( 'td', array( 'colspan' => 2 ),
+ $targetSelection = Html::rawElement( 'td', array( 'colspan' => 2 ),
Xml::radioLabel(
$this->msg( 'sp-contributions-newbies' )->text(),
'contribs',
- 'newbie' ,
+ 'newbie',
'newbie',
$this->opts['contribs'] == 'newbie',
array( 'class' => 'mw-input' )
@@ -445,7 +444,7 @@ class SpecialContributions extends SpecialPage {
( $this->opts['target'] ? array() : array( 'autofocus' )
)
) . ' '
- ) ;
+ );
$namespaceSelection =
Xml::tags( 'td', array( 'class' => 'mw-label' ),
@@ -455,7 +454,7 @@ class SpecialContributions extends SpecialPage {
''
)
) .
- Xml::tags( 'td', null,
+ Html::rawElement( 'td', null,
Html::namespaceSelector( array(
'selected' => $this->opts['namespace'],
'all' => '',
@@ -483,10 +482,10 @@ class SpecialContributions extends SpecialPage {
array( 'title' => $this->msg( 'tooltip-namespace_association' )->text(), 'class' => 'mw-input' )
) . '&#160;'
)
- ) ;
+ );
- $extraOptions = Xml::tags( 'td', array( 'colspan' => 2 ),
- Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ),
+ if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) {
+ $deletedOnlyCheck = Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ),
Xml::checkLabel(
$this->msg( 'history-show-deleted' )->text(),
'deletedOnly',
@@ -494,7 +493,13 @@ class SpecialContributions extends SpecialPage {
$this->opts['deletedOnly'],
array( 'class' => 'mw-input' )
)
- ) .
+ );
+ } else {
+ $deletedOnlyCheck = '';
+ }
+
+ $extraOptions = Html::rawElement( 'td', array( 'colspan' => 2 ),
+ $deletedOnlyCheck .
Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ),
Xml::checkLabel(
$this->msg( 'sp-contributions-toponly' )->text(),
@@ -504,7 +509,7 @@ class SpecialContributions extends SpecialPage {
array( 'class' => 'mw-input' )
)
)
- ) ;
+ );
$dateSelectionAndSubmit = Xml::tags( 'td', array( 'colspan' => 2 ),
Xml::dateMenu(
@@ -515,36 +520,30 @@ class SpecialContributions extends SpecialPage {
$this->msg( 'sp-contributions-submit' )->text(),
array( 'class' => 'mw-submit' )
)
- ) ;
+ );
$form .=
Xml::fieldset( $this->msg( 'sp-contributions-search' )->text() ) .
- Xml::openElement( 'table', array( 'class' => 'mw-contributions-table' ) ) .
- Xml::openElement( 'tr' ) .
- $targetSelection .
- Xml::closeElement( 'tr' ) .
- Xml::openElement( 'tr' ) .
- $namespaceSelection .
- Xml::closeElement( 'tr' ) .
- Xml::openElement( 'tr' ) .
- $filterSelection .
- Xml::closeElement( 'tr' ) .
- Xml::openElement( 'tr' ) .
- $extraOptions .
- Xml::closeElement( 'tr' ) .
- Xml::openElement( 'tr' ) .
- $dateSelectionAndSubmit .
- Xml::closeElement( 'tr' ) .
- Xml::closeElement( 'table' );
+ Html::rawElement( 'table', array( 'class' => 'mw-contributions-table' ), "\n" .
+ Html::rawElement( 'tr', array(), $targetSelection ) . "\n" .
+ Html::rawElement( 'tr', array(), $namespaceSelection ) . "\n" .
+ Html::rawElement( 'tr', array(), $filterSelection ) . "\n" .
+ Html::rawElement( 'tr', array(), $extraOptions ) . "\n" .
+ Html::rawElement( 'tr', array(), $dateSelectionAndSubmit ) . "\n"
+ );
$explain = $this->msg( 'sp-contributions-explain' );
- if ( $explain->exists() ) {
- $form .= "<p id='mw-sp-contributions-explain'>{$explain}</p>";
+ if ( !$explain->isBlank() ) {
+ $form .= "<p id='mw-sp-contributions-explain'>{$explain->parse()}</p>";
}
$form .= Xml::closeElement( 'fieldset' ) .
Xml::closeElement( 'form' );
return $form;
}
+
+ protected function getGroupName() {
+ return 'users';
+ }
}
/**
@@ -598,7 +597,7 @@ class ContribsPager extends ReverseChronologicalPager {
* This method basically executes the exact same code as the parent class, though with
* a hook added, to allow extentions to add additional queries.
*
- * @param $offset String: index offset, inclusive
+ * @param string $offset index offset, inclusive
* @param $limit Integer: exact query limit
* @param $descending Boolean: query direction, false for ascending, true for descending
* @return ResultWrapper
@@ -631,7 +630,7 @@ class ContribsPager extends ReverseChronologicalPager {
$result = array();
// loop all results and collect them in an array
- foreach ( $data as $j => $query ) {
+ foreach ( $data as $query ) {
foreach ( $query as $i => $row ) {
// use index column as key, allowing us to easily sort in PHP
$result[$row->{$this->getIndexField()} . "-$i"] = $row;
@@ -731,10 +730,10 @@ class ContribsPager extends ReverseChronologicalPager {
}
}
if ( $this->deletedOnly ) {
- $condition[] = "rev_deleted != '0'";
+ $condition[] = 'rev_deleted != 0';
}
if ( $this->topOnly ) {
- $condition[] = "rev_id = page_latest";
+ $condition[] = 'rev_id = page_latest';
}
return array( $tables, $index, $condition, $join_conds );
}
@@ -831,7 +830,7 @@ class ContribsPager extends ReverseChronologicalPager {
*/
wfSuppressWarnings();
$rev = new Revision( $row );
- $validRevision = $rev->getParentId() !== null;
+ $validRevision = (bool) $rev->getId();
wfRestoreWarnings();
if ( $validRevision ) {
@@ -950,8 +949,12 @@ class ContribsPager extends ReverseChronologicalPager {
// Let extensions add data
wfRunHooks( 'ContributionsLineEnding', array( $this, &$ret, $row, &$classes ) );
- $classes = implode( ' ', $classes );
- $ret = "<li class=\"$classes\">$ret</li>\n";
+ if ( $classes === array() && $ret === '' ) {
+ wfDebug( 'Dropping Special:Contribution row that could not be formatted' );
+ $ret = "<!-- Could not format Special:Contribution row. -->\n";
+ } else {
+ $ret = Html::rawElement( 'li', array( 'class' => $classes ), $ret ) . "\n";
+ }
wfProfileOut( __METHOD__ );
return $ret;
diff --git a/includes/specials/SpecialDeadendpages.php b/includes/specials/SpecialDeadendpages.php
index f4904a50..6978d6bb 100644
--- a/includes/specials/SpecialDeadendpages.php
+++ b/includes/specials/SpecialDeadendpages.php
@@ -82,4 +82,8 @@ class DeadendPagesPage extends PageQueryPage {
return array( 'page_title' );
}
}
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
}
diff --git a/includes/specials/SpecialDeletedContributions.php b/includes/specials/SpecialDeletedContributions.php
index c880b617..e374979e 100644
--- a/includes/specials/SpecialDeletedContributions.php
+++ b/includes/specials/SpecialDeletedContributions.php
@@ -135,7 +135,10 @@ class DeletedContribsPager extends IndexPager {
function formatRow( $row ) {
wfProfileIn( __METHOD__ );
+ $page = Title::makeTitle( $row->ar_namespace, $row->ar_title );
+
$rev = new Revision( array(
+ 'title' => $page,
'id' => $row->ar_rev_id,
'comment' => $row->ar_comment,
'user' => $row->ar_user,
@@ -145,8 +148,6 @@ class DeletedContribsPager extends IndexPager {
'deleted' => $row->ar_deleted,
) );
- $page = Title::makeTitle( $row->ar_namespace, $row->ar_title );
-
$undelete = SpecialPage::getTitleFor( 'Undelete' );
$logs = SpecialPage::getTitleFor( 'Log' );
@@ -167,7 +168,7 @@ class DeletedContribsPager extends IndexPager {
$user = $this->getUser();
- if( $user->isAllowed('deletedtext') ) {
+ if( $user->isAllowed( 'deletedtext' ) ) {
$last = Linker::linkKnown(
$undelete,
$this->messages['diff'],
@@ -260,7 +261,7 @@ class DeletedContributionsPage extends SpecialPage {
* Special page "deleted user contributions".
* Shows a list of the deleted contributions of a user.
*
- * @param $par String: (optional) user name of the user for which to show the contributions
+ * @param string $par (optional) user name of the user for which to show the contributions
*/
function execute( $par ) {
global $wgQueryPageDefaultLimit;
@@ -464,7 +465,7 @@ class DeletedContributionsPage extends SpecialPage {
/**
* Generates the namespace selector form with hidden attributes.
- * @param $options Array: the options to be included.
+ * @param array $options the options to be included.
* @return string
*/
function getForm( $options ) {
@@ -474,7 +475,7 @@ class DeletedContributionsPage extends SpecialPage {
if ( !isset( $options['target'] ) ) {
$options['target'] = '';
} else {
- $options['target'] = str_replace( '_' , ' ' , $options['target'] );
+ $options['target'] = str_replace( '_', ' ', $options['target'] );
}
if ( !isset( $options['namespace'] ) ) {
@@ -498,7 +499,7 @@ class DeletedContributionsPage extends SpecialPage {
$f .= "\t" . Html::hidden( $name, $value ) . "\n";
}
- $f .= Xml::openElement( 'fieldset' ) .
+ $f .= Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', array(), $this->msg( 'sp-contributions-search' )->text() ) .
Xml::tags( 'label', array( 'for' => 'target' ), $this->msg( 'sp-contributions-username' )->parse() ) . ' ' .
Html::input( 'target', $options['target'], 'text', array(
@@ -521,4 +522,8 @@ class DeletedContributionsPage extends SpecialPage {
Xml::closeElement( 'form' );
return $f;
}
+
+ protected function getGroupName() {
+ return 'users';
+ }
}
diff --git a/includes/specials/SpecialDisambiguations.php b/includes/specials/SpecialDisambiguations.php
index 48180a77..2126ca52 100644
--- a/includes/specials/SpecialDisambiguations.php
+++ b/includes/specials/SpecialDisambiguations.php
@@ -59,20 +59,20 @@ class DisambiguationsPage extends QueryPage {
if( $dp->getNamespace() != NS_TEMPLATE ) {
# @todo FIXME: We assume the disambiguation message is a template but
# the page can potentially be from another namespace :/
- wfDebug("Mediawiki:disambiguationspage message does not refer to a template!\n");
+ wfDebug( "Mediawiki:disambiguationspage message does not refer to a template!\n" );
}
$linkBatch->addObj( $dp );
} else {
# Get all the templates linked from the Mediawiki:Disambiguationspage
$disPageObj = Title::makeTitleSafe( NS_MEDIAWIKI, 'disambiguationspage' );
$res = $dbr->select(
- array('pagelinks', 'page'),
+ array( 'pagelinks', 'page' ),
'pl_title',
- array('page_id = pl_from',
+ array( 'page_id = pl_from',
'pl_namespace' => NS_TEMPLATE,
'page_namespace' => $disPageObj->getNamespace(),
- 'page_title' => $disPageObj->getDBkey()),
- __METHOD__ );
+ 'page_title' => $disPageObj->getDBkey()
+ ), __METHOD__ );
foreach ( $res as $row ) {
$linkBatch->addObj( Title::makeTitle( NS_TEMPLATE, $row->pl_title ));
@@ -158,4 +158,8 @@ class DisambiguationsPage extends QueryPage {
return "$from $edit $arr $to";
}
+
+ protected function getGroupName() {
+ return 'pages';
+ }
}
diff --git a/includes/specials/SpecialDoubleRedirects.php b/includes/specials/SpecialDoubleRedirects.php
index 5864ca9f..5a5d749c 100644
--- a/includes/specials/SpecialDoubleRedirects.php
+++ b/includes/specials/SpecialDoubleRedirects.php
@@ -33,9 +33,17 @@ class DoubleRedirectsPage extends QueryPage {
parent::__construct( $name );
}
- function isExpensive() { return true; }
- function isSyndicated() { return false; }
- function sortDescending() { return false; }
+ function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ function sortDescending() {
+ return false;
+ }
function getPageHeader() {
return $this->msg( 'doubleredirectstext' )->parseAsBlock();
@@ -43,23 +51,45 @@ class DoubleRedirectsPage extends QueryPage {
function reallyGetQueryInfo( $namespace = null, $title = null ) {
$limitToTitle = !( $namespace === null && $title === null );
+ $dbr = wfGetDB( DB_SLAVE );
$retval = array (
- 'tables' => array ( 'ra' => 'redirect',
- 'rb' => 'redirect', 'pa' => 'page',
- 'pb' => 'page', 'pc' => 'page' ),
- 'fields' => array ( 'namespace' => 'pa.page_namespace',
- 'title' => 'pa.page_title',
- 'value' => 'pa.page_title',
- 'nsb' => 'pb.page_namespace',
- 'tb' => 'pb.page_title',
- 'nsc' => 'pc.page_namespace',
- 'tc' => 'pc.page_title' ),
- 'conds' => array ( 'ra.rd_from = pa.page_id',
- 'pb.page_namespace = ra.rd_namespace',
- 'pb.page_title = ra.rd_title',
- 'rb.rd_from = pb.page_id',
- 'pc.page_namespace = rb.rd_namespace',
- 'pc.page_title = rb.rd_title' )
+ 'tables' => array (
+ 'ra' => 'redirect',
+ 'rb' => 'redirect',
+ 'pa' => 'page',
+ 'pb' => 'page'
+ ),
+ 'fields' => array(
+ 'namespace' => 'pa.page_namespace',
+ 'title' => 'pa.page_title',
+ 'value' => 'pa.page_title',
+
+ 'nsb' => 'pb.page_namespace',
+ 'tb' => 'pb.page_title',
+
+ // Select fields from redirect instead of page. Because there may
+ // not actually be a page table row for this target (e.g. for interwiki redirects)
+ 'nsc' => 'rb.rd_namespace',
+ 'tc' => 'rb.rd_title',
+ 'iwc' => 'rb.rd_interwiki',
+ ),
+ 'conds' => array(
+ 'ra.rd_from = pa.page_id',
+
+ // Filter out redirects where the target goes interwiki (bug 40353).
+ // This isn't an optimization, it is required for correct results,
+ // otherwise a non-double redirect like Bar -> w:Foo will show up
+ // like "Bar -> Foo -> w:Foo".
+
+ // Need to check both NULL and "" for some reason,
+ // apparently either can be stored for non-iw entries.
+ 'ra.rd_interwiki IS NULL OR ra.rd_interwiki = ' . $dbr->addQuotes( '' ),
+
+ 'pb.page_namespace = ra.rd_namespace',
+ 'pb.page_title = ra.rd_title',
+
+ 'rb.rd_from = pb.page_id',
+ )
);
if ( $limitToTitle ) {
$retval['conds']['pa.page_namespace'] = $namespace;
@@ -79,11 +109,16 @@ class DoubleRedirectsPage extends QueryPage {
function formatResult( $skin, $result ) {
$titleA = Title::makeTitle( $result->namespace, $result->title );
+ // If only titleA is in the query, it means this came from
+ // querycache (which only saves 3 columns).
+ // That does save the bulk of the query cost, but now we need to
+ // get a little more detail about each individual entry quickly
+ // using the filter of reallyGetQueryInfo.
if ( $result && !isset( $result->nsb ) ) {
$dbr = wfGetDB( DB_SLAVE );
$qi = $this->reallyGetQueryInfo( $result->namespace,
$result->title );
- $res = $dbr->select($qi['tables'], $qi['fields'],
+ $res = $dbr->select( $qi['tables'], $qi['fields'],
$qi['conds'], __METHOD__ );
if ( $res ) {
$result = $dbr->fetchObject( $res );
@@ -94,7 +129,7 @@ class DoubleRedirectsPage extends QueryPage {
}
$titleB = Title::makeTitle( $result->nsb, $result->tb );
- $titleC = Title::makeTitle( $result->nsc, $result->tc );
+ $titleC = Title::makeTitle( $result->nsc, $result->tc, '', $result->iwc );
$linkA = Linker::linkKnown(
$titleA,
@@ -127,4 +162,8 @@ class DoubleRedirectsPage extends QueryPage {
return( "{$linkA} {$edit} {$arr} {$linkB} {$arr} {$linkC}" );
}
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
}
diff --git a/includes/specials/SpecialEditWatchlist.php b/includes/specials/SpecialEditWatchlist.php
index 23cd9aa6..d2838e01 100644
--- a/includes/specials/SpecialEditWatchlist.php
+++ b/includes/specials/SpecialEditWatchlist.php
@@ -49,7 +49,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
private $badItems = array();
- public function __construct(){
+ public function __construct() {
parent::__construct( 'EditWatchlist' );
}
@@ -77,6 +77,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
}
$this->checkPermissions();
+ $this->checkReadOnly();
$this->outputHeader();
@@ -85,9 +86,9 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
# B/C: $mode used to be waaay down the parameter list, and the first parameter
# was $wgUser
- if( $mode instanceof User ){
+ if( $mode instanceof User ) {
$args = func_get_args();
- if( count( $args >= 4 ) ){
+ if( count( $args >= 4 ) ) {
$mode = $args[3];
}
}
@@ -101,7 +102,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
case self::EDIT_RAW:
$out->setPageTitle( $this->msg( 'watchlistedit-raw-title' ) );
$form = $this->getRawForm();
- if( $form->show() ){
+ if( $form->show() ) {
$out->addHTML( $this->successMessage );
$out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) );
}
@@ -111,7 +112,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
default:
$out->setPageTitle( $this->msg( 'watchlistedit-normal-title' ) );
$form = $this->getNormalForm();
- if( $form->show() ){
+ if( $form->show() ) {
$out->addHTML( $this->successMessage );
$out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) );
} elseif ( $this->toc !== false ) {
@@ -153,7 +154,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
return array_unique( $list );
}
- public function submitRaw( $data ){
+ public function submitRaw( $data ) {
$wanted = $this->extractTitles( $data['Titles'] );
$current = $this->getWatchlist();
@@ -164,7 +165,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$this->unwatchTitles( $toUnwatch );
$this->getUser()->invalidateCache();
- if( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 ){
+ if( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 ) {
$this->successMessage = $this->msg( 'watchlistedit-raw-done' )->parse();
} else {
return false;
@@ -185,7 +186,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$this->clearWatchlist();
$this->getUser()->invalidateCache();
- if( count( $current ) > 0 ){
+ if( count( $current ) > 0 ) {
$this->successMessage = $this->msg( 'watchlistedit-raw-done' )->parse();
} else {
return false;
@@ -204,7 +205,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
* $titles can be an array of strings or Title objects; the former
* is preferred, since Titles are very memory-heavy
*
- * @param $titles array of strings, or Title objects
+ * @param array $titles of strings, or Title objects
* @param $output String
*/
private function showTitles( $titles, &$output ) {
@@ -289,7 +290,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$res = $dbr->select(
array( 'watchlist' ),
- array( 'wl_namespace', 'wl_title' ),
+ array( 'wl_namespace', 'wl_title' ),
array( 'wl_user' => $this->getUser()->getId() ),
__METHOD__,
array( 'ORDER BY' => array( 'wl_namespace', 'wl_title' ) )
@@ -312,7 +313,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
*
* @param Title $title
* @param int $namespace
- * @param String $dbKey
+ * @param string $dbKey
* @return bool: Whether this item is valid
*/
private function checkTitle( $title, $namespace, $dbKey ) {
@@ -381,7 +382,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
* $titles can be an array of strings or Title objects; the former
* is preferred, since Titles are very memory-heavy
*
- * @param $titles Array of strings, or Title objects
+ * @param array $titles of strings, or Title objects
*/
private function watchTitles( $titles ) {
$dbw = wfGetDB( DB_MASTER );
@@ -393,13 +394,13 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
if( $title instanceof Title ) {
$rows[] = array(
'wl_user' => $this->getUser()->getId(),
- 'wl_namespace' => ( $title->getNamespace() & ~1 ),
+ 'wl_namespace' => MWNamespace::getSubject( $title->getNamespace() ),
'wl_title' => $title->getDBkey(),
'wl_notificationtimestamp' => null,
);
$rows[] = array(
'wl_user' => $this->getUser()->getId(),
- 'wl_namespace' => ( $title->getNamespace() | 1 ),
+ 'wl_namespace' => MWNamespace::getTalk( $title->getNamespace() ),
'wl_title' => $title->getDBkey(),
'wl_notificationtimestamp' => null,
);
@@ -414,7 +415,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
* $titles can be an array of strings or Title objects; the former
* is preferred, since Titles are very memory-heavy
*
- * @param $titles Array of strings, or Title objects
+ * @param array $titles of strings, or Title objects
*/
private function unwatchTitles( $titles ) {
$dbw = wfGetDB( DB_MASTER );
@@ -427,7 +428,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
'watchlist',
array(
'wl_user' => $this->getUser()->getId(),
- 'wl_namespace' => ( $title->getNamespace() & ~1 ),
+ 'wl_namespace' => MWNamespace::getSubject( $title->getNamespace() ),
'wl_title' => $title->getDBkey(),
),
__METHOD__
@@ -436,7 +437,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
'watchlist',
array(
'wl_user' => $this->getUser()->getId(),
- 'wl_namespace' => ( $title->getNamespace() | 1 ),
+ 'wl_namespace' => MWNamespace::getTalk( $title->getNamespace() ),
'wl_title' => $title->getDBkey(),
),
__METHOD__
@@ -470,26 +471,26 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
*
* @return HTMLForm
*/
- protected function getNormalForm(){
+ protected function getNormalForm() {
global $wgContLang;
$fields = array();
$count = 0;
- foreach( $this->getWatchlistInfo() as $namespace => $pages ){
+ foreach( $this->getWatchlistInfo() as $namespace => $pages ) {
if ( $namespace >= 0 ) {
- $fields['TitlesNs'.$namespace] = array(
+ $fields['TitlesNs' . $namespace] = array(
'class' => 'EditWatchlistCheckboxSeriesField',
'options' => array(),
'section' => "ns$namespace",
);
}
- foreach( array_keys( $pages ) as $dbkey ){
+ foreach( array_keys( $pages ) as $dbkey ) {
$title = Title::makeTitleSafe( $namespace, $dbkey );
if ( $this->checkTitle( $title, $namespace, $dbkey ) ) {
$text = $this->buildRemoveLine( $title );
- $fields['TitlesNs'.$namespace]['options'][$text] = htmlspecialchars( $title->getPrefixedText() );
+ $fields['TitlesNs' . $namespace]['options'][$text] = $title->getPrefixedText();
$count++;
}
}
@@ -519,7 +520,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$form->setTitle( $this->getTitle() );
$form->setSubmitTextMsg( 'watchlistedit-normal-submit' );
# Used message keys: 'accesskey-watchlistedit-normal-submit', 'tooltip-watchlistedit-normal-submit'
- $form->setSubmitTooltip('watchlistedit-normal-submit');
+ $form->setSubmitTooltip( 'watchlistedit-normal-submit' );
$form->setWrapperLegendMsg( 'watchlistedit-normal-legend' );
$form->addHeaderText( $this->msg( 'watchlistedit-normal-explain' )->parse() );
$form->setSubmitCallback( array( $this, 'submitNormal' ) );
@@ -564,7 +565,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
*
* @return HTMLForm
*/
- protected function getRawForm(){
+ protected function getRawForm() {
$titles = implode( $this->getWatchlist(), "\n" );
$fields = array(
'Titles' => array(
@@ -577,7 +578,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$form->setTitle( $this->getTitle( 'raw' ) );
$form->setSubmitTextMsg( 'watchlistedit-raw-submit' );
# Used message keys: 'accesskey-watchlistedit-raw-submit', 'tooltip-watchlistedit-raw-submit'
- $form->setSubmitTooltip('watchlistedit-raw-submit');
+ $form->setSubmitTooltip( 'watchlistedit-raw-submit' );
$form->setWrapperLegendMsg( 'watchlistedit-raw-legend' );
$form->addHeaderText( $this->msg( 'watchlistedit-raw-explain' )->parse() );
$form->setSubmitCallback( array( $this, 'submitRaw' ) );
@@ -648,7 +649,7 @@ class WatchlistEditor extends SpecialEditWatchlist {}
* Extend HTMLForm purely so we can have a more sane way of getting the section headers
*/
class EditWatchlistNormalHTMLForm extends HTMLForm {
- public function getLegend( $namespace ){
+ public function getLegend( $namespace ) {
$namespace = substr( $namespace, 2 );
return $namespace == NS_MAIN
? $this->msg( 'blanknamespace' )->escaped()
@@ -667,8 +668,8 @@ class EditWatchlistCheckboxSeriesField extends HTMLMultiSelectField {
* form is open (bug 32126), but we know that invalid items will
* be harmless so we can override it here.
*
- * @param $value String the value the field was submitted with
- * @param $alldata Array the data collected from the form
+ * @param string $value the value the field was submitted with
+ * @param array $alldata the data collected from the form
* @return Mixed Bool true on success, or String error to display.
*/
function validate( $value, $alldata ) {
diff --git a/includes/specials/SpecialEmailuser.php b/includes/specials/SpecialEmailuser.php
index 4d875e6e..b5ad589e 100644
--- a/includes/specials/SpecialEmailuser.php
+++ b/includes/specials/SpecialEmailuser.php
@@ -139,7 +139,8 @@ class SpecialEmailUser extends UnlistedSpecialPage {
$this->mTargetObj = $ret;
$form = new HTMLForm( $this->getFormFields(), $this->getContext() );
- $form->addPreText( $this->msg( 'emailpagetext' )->parse() );
+ // By now we are supposed to be sure that $this->mTarget is a user name
+ $form->addPreText( $this->msg( 'emailpagetext', $this->mTarget )->parse() );
$form->setSubmitTextMsg( 'emailsend' );
$form->setTitle( $this->getTitle() );
$form->setSubmitCallback( array( __CLASS__, 'uiSubmit' ) );
@@ -162,7 +163,7 @@ class SpecialEmailUser extends UnlistedSpecialPage {
/**
* Validate target User
*
- * @param $target String: target user name
+ * @param string $target target user name
* @return User object on success or a string on error
*/
public static function getTarget( $target ) {
@@ -190,7 +191,7 @@ class SpecialEmailUser extends UnlistedSpecialPage {
* Check whether a user is allowed to send email
*
* @param $user User object
- * @param $editToken String: edit token
+ * @param string $editToken edit token
* @return null on success or string on error
*/
public static function getPermissionsError( $user, $editToken ) {
@@ -230,7 +231,7 @@ class SpecialEmailUser extends UnlistedSpecialPage {
/**
* Form to ask for target user name.
*
- * @param $name String: user name submitted.
+ * @param string $name user name submitted.
* @return String: form asking for user name.
*/
protected function userForm( $name ) {
@@ -336,4 +337,8 @@ class SpecialEmailUser extends UnlistedSpecialPage {
return $status;
}
}
+
+ protected function getGroupName() {
+ return 'users';
+ }
}
diff --git a/includes/specials/SpecialExport.php b/includes/specials/SpecialExport.php
index b4294b32..7abfefe2 100644
--- a/includes/specials/SpecialExport.php
+++ b/includes/specials/SpecialExport.php
@@ -80,7 +80,7 @@ class SpecialExport extends SpecialPage {
$page = $request->getText( 'pages' );
$nsindex = $request->getText( 'nsindex', '' );
- if ( strval( $nsindex ) !== '' ) {
+ if ( strval( $nsindex ) !== '' ) {
/**
* Same implementation as above, so same @todo
*/
@@ -124,7 +124,7 @@ class SpecialExport extends SpecialPage {
if ( $this->curonly ) {
$history = WikiExporter::CURRENT;
} elseif ( !$historyCheck ) {
- if ( $limit > 0 && ($wgExportMaxHistory == 0 || $limit < $wgExportMaxHistory ) ) {
+ if ( $limit > 0 && ( $wgExportMaxHistory == 0 || $limit < $wgExportMaxHistory ) ) {
$history['limit'] = $limit;
}
if ( !is_null( $offset ) ) {
@@ -161,7 +161,7 @@ class SpecialExport extends SpecialPage {
$list_authors = $request->getCheck( 'listauthors' );
if ( !$this->curonly || !$wgExportAllowListContributors ) {
- $list_authors = false ;
+ $list_authors = false;
}
if ( $this->doExport ) {
@@ -272,7 +272,7 @@ class SpecialExport extends SpecialPage {
/**
* Do the actual page exporting
*
- * @param $page String: user input on what page(s) to export
+ * @param string $page user input on what page(s) to export
* @param $history Mixed: one of the WikiExporter history export constants
* @param $list_authors Boolean: Whether to add distinct author list (when
* not returning full history)
@@ -286,7 +286,7 @@ class SpecialExport extends SpecialPage {
} else {
$pageSet = array(); // Inverted index of all pages to look up
-
+
// Split up and normalize input
foreach( explode( "\n", $page ) as $pageName ) {
$pageName = trim( $pageName );
@@ -339,7 +339,7 @@ class SpecialExport extends SpecialPage {
// This might take a while... :D
wfSuppressWarnings();
- set_time_limit(0);
+ set_time_limit( 0 );
wfRestoreWarnings();
}
@@ -405,7 +405,7 @@ class SpecialExport extends SpecialPage {
foreach ( $res as $row ) {
$n = $row->page_title;
- if ($row->page_namespace) {
+ if ( $row->page_namespace ) {
$ns = $wgContLang->getNsText( $row->page_namespace );
$n = $ns . ':' . $n;
}
@@ -561,4 +561,7 @@ class SpecialExport extends SpecialPage {
return $pageSet;
}
+ protected function getGroupName() {
+ return 'pagetools';
+ }
}
diff --git a/includes/specials/SpecialFewestrevisions.php b/includes/specials/SpecialFewestrevisions.php
index 7e4bc9ce..5b7f353d 100644
--- a/includes/specials/SpecialFewestrevisions.php
+++ b/includes/specials/SpecialFewestrevisions.php
@@ -60,7 +60,6 @@ class FewestrevisionsPage extends QueryPage {
);
}
-
function sortDescending() {
return false;
}
@@ -94,4 +93,8 @@ class FewestrevisionsPage extends QueryPage {
return $this->getLanguage()->specialList( $plink, $nlink );
}
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
}
diff --git a/includes/specials/SpecialFileDuplicateSearch.php b/includes/specials/SpecialFileDuplicateSearch.php
index ccf8ba17..3fe64e6f 100644
--- a/includes/specials/SpecialFileDuplicateSearch.php
+++ b/includes/specials/SpecialFileDuplicateSearch.php
@@ -40,9 +40,17 @@ class FileDuplicateSearchPage extends QueryPage {
parent::__construct( $name );
}
- function isSyndicated() { return false; }
- function isCacheable() { return false; }
- function isCached() { return false; }
+ function isSyndicated() {
+ return false;
+ }
+
+ function isCacheable() {
+ return false;
+ }
+
+ function isCached() {
+ return false;
+ }
function linkParameters() {
return array( 'filename' => $this->filename );
@@ -59,7 +67,7 @@ class FileDuplicateSearchPage extends QueryPage {
/**
*
- * @param $dupes Array of File objects
+ * @param array $dupes of File objects
*/
function showList( $dupes ) {
$html = array();
@@ -93,7 +101,7 @@ class FileDuplicateSearchPage extends QueryPage {
$this->setHeaders();
$this->outputHeader();
- $this->filename = isset( $par ) ? $par : $this->getRequest()->getText( 'filename' );
+ $this->filename = isset( $par ) ? $par : $this->getRequest()->getText( 'filename' );
$this->file = null;
$this->hash = '';
$title = Title::newFromText( $this->filename, NS_FILE );
@@ -106,7 +114,7 @@ class FileDuplicateSearchPage extends QueryPage {
# Create the input form
$out->addHTML(
Xml::openElement( 'form', array( 'id' => 'fileduplicatesearch', 'method' => 'get', 'action' => $wgScript ) ) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) .
+ Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', null, $this->msg( 'fileduplicatesearch-legend' )->text() ) .
Xml::inputLabel( $this->msg( 'fileduplicatesearch-filename' )->text(), 'filename', 'filename', 50, $this->filename ) . ' ' .
@@ -207,4 +215,8 @@ class FileDuplicateSearchPage extends QueryPage {
return "$plink . . $user . . $time";
}
+
+ protected function getGroupName() {
+ return 'media';
+ }
}
diff --git a/includes/specials/SpecialFilepath.php b/includes/specials/SpecialFilepath.php
index e0866504..bbcced26 100644
--- a/includes/specials/SpecialFilepath.php
+++ b/includes/specials/SpecialFilepath.php
@@ -47,7 +47,7 @@ class SpecialFilepath extends SpecialPage {
$file = wfFindFile( $title );
if ( $file && $file->exists() ) {
- // Default behaviour: Use the direct link to the file.
+ // Default behavior: Use the direct link to the file.
$url = $file->getURL();
$width = $request->getInt( 'width', -1 );
$height = $request->getInt( 'height', -1 );
@@ -86,4 +86,8 @@ class SpecialFilepath extends SpecialPage {
Html::closeElement( 'form' )
);
}
+
+ protected function getGroupName() {
+ return 'media';
+ }
}
diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php
index 362fc5cf..aa56041b 100644
--- a/includes/specials/SpecialImport.php
+++ b/includes/specials/SpecialImport.php
@@ -101,7 +101,7 @@ class SpecialImport extends SpecialPage {
$this->logcomment = $request->getText( 'log-comment' );
$this->pageLinkDepth = $wgExportMaxLinkDepth == 0 ? 0 : $request->getIntOrNull( 'pagelink-depth' );
- $this->rootpage = $request->getText( 'rootpage' );
+ $this->rootpage = $request->getText( 'rootpage' );
$user = $this->getUser();
if ( !$user->matchEditToken( $request->getVal( 'editToken' ) ) ) {
@@ -114,7 +114,7 @@ class SpecialImport extends SpecialPage {
throw new PermissionsError( 'importupload' );
}
} elseif ( $sourceName == "interwiki" ) {
- if( !$user->isAllowed( 'import' ) ){
+ if( !$user->isAllowed( 'import' ) ) {
throw new PermissionsError( 'import' );
}
$this->interwiki = $request->getVal( 'interwiki' );
@@ -153,7 +153,7 @@ class SpecialImport extends SpecialPage {
$out->addWikiMsg( "importstart" );
- $reporter = new ImportReporter( $importer, $isUpload, $this->interwiki , $this->logcomment);
+ $reporter = new ImportReporter( $importer, $isUpload, $this->interwiki, $this->logcomment );
$reporter->setContext( $this->getContext() );
$exception = false;
@@ -194,14 +194,14 @@ class SpecialImport extends SpecialPage {
$this->msg( 'importtext' )->parseAsBlock() .
Html::hidden( 'action', 'submit' ) .
Html::hidden( 'source', 'upload' ) .
- Xml::openElement( 'table', array( 'id' => 'mw-import-table' ) ) .
+ Xml::openElement( 'table', array( 'id' => 'mw-import-table-upload' ) ) .
"<tr>
<td class='mw-label'>" .
Xml::label( $this->msg( 'import-upload-filename' )->text(), 'xmlimport' ) .
"</td>
<td class='mw-input'>" .
- Xml::input( 'xmlimport', 50, '', array( 'type' => 'file' ) ) . ' ' .
+ Html::input( 'xmlimport', '', 'file', array( 'id' => 'xmlimport' ) ) . ' ' .
"</td>
</tr>
<tr>
@@ -215,11 +215,11 @@ class SpecialImport extends SpecialPage {
</tr>
<tr>
<td class='mw-label'>" .
- Xml::label( $this->msg( 'import-interwiki-rootpage' )->text(), 'mw-interwiki-rootpage' ) .
+ Xml::label( $this->msg( 'import-interwiki-rootpage' )->text(), 'mw-interwiki-rootpage-upload' ) .
"</td>
<td class='mw-input'>" .
Xml::input( 'rootpage', 50, $this->rootpage,
- array( 'id' => 'mw-interwiki-rootpage', 'type' => 'text' ) ) . ' ' .
+ array( 'id' => 'mw-interwiki-rootpage-upload', 'type' => 'text' ) ) . ' ' .
"</td>
</tr>
<tr>
@@ -260,13 +260,13 @@ class SpecialImport extends SpecialPage {
Html::hidden( 'action', 'submit' ) .
Html::hidden( 'source', 'interwiki' ) .
Html::hidden( 'editToken', $user->getEditToken() ) .
- Xml::openElement( 'table', array( 'id' => 'mw-import-table' ) ) .
+ Xml::openElement( 'table', array( 'id' => 'mw-import-table-interwiki' ) ) .
"<tr>
<td class='mw-label'>" .
Xml::label( $this->msg( 'import-interwiki-source' )->text(), 'interwiki' ) .
"</td>
<td class='mw-input'>" .
- Xml::openElement( 'select', array( 'name' => 'interwiki' ) )
+ Xml::openElement( 'select', array( 'name' => 'interwiki', 'id' => 'interwiki' ) )
);
foreach( $wgImportSources as $prefix ) {
$selected = ( $this->interwiki === $prefix ) ? ' selected="selected"' : '';
@@ -275,7 +275,7 @@ class SpecialImport extends SpecialPage {
$out->addHTML(
Xml::closeElement( 'select' ) .
- Xml::input( 'frompage', 50, $this->frompage ) .
+ Xml::input( 'frompage', 50, $this->frompage, array( 'id' => 'frompage' ) ) .
"</td>
</tr>
<tr>
@@ -321,11 +321,11 @@ class SpecialImport extends SpecialPage {
</tr>
<tr>
<td class='mw-label'>" .
- Xml::label( $this->msg( 'import-interwiki-rootpage' )->text(), 'mw-interwiki-rootpage' ) .
+ Xml::label( $this->msg( 'import-interwiki-rootpage' )->text(), 'mw-interwiki-rootpage-interwiki' ) .
"</td>
<td class='mw-input'>" .
Xml::input( 'rootpage', 50, $this->rootpage,
- array( 'id' => 'mw-interwiki-rootpage', 'type' => 'text' ) ) . ' ' .
+ array( 'id' => 'mw-interwiki-rootpage-interwiki', 'type' => 'text' ) ) . ' ' .
"</td>
</tr>
<tr>
@@ -341,6 +341,10 @@ class SpecialImport extends SpecialPage {
);
}
}
+
+ protected function getGroupName() {
+ return 'pagetools';
+ }
}
/**
@@ -353,7 +357,7 @@ class ImportReporter extends ContextSource {
private $mOriginalPageOutCallback = null;
private $mLogItemCount = 0;
- function __construct( $importer, $upload, $interwiki , $reason=false ) {
+ function __construct( $importer, $upload, $interwiki, $reason = false ) {
$this->mOriginalPageOutCallback =
$importer->setPageOutCallback( array( $this, 'reportPage' ) );
$this->mOriginalLogCallback =
@@ -410,7 +414,7 @@ class ImportReporter extends ContextSource {
$detail = $this->msg( 'import-logentry-upload-detail' )->numParams(
$successCount )->inContentLanguage()->text();
if ( $this->reason ) {
- $detail .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . $this->reason;
+ $detail .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . $this->reason;
}
$log->addEntry( 'upload', $title, $detail );
} else {
@@ -419,7 +423,7 @@ class ImportReporter extends ContextSource {
$detail = $this->msg( 'import-logentry-interwiki-detail' )->numParams(
$successCount )->params( $interwiki )->inContentLanguage()->text();
if ( $this->reason ) {
- $detail .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . $this->reason;
+ $detail .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . $this->reason;
}
$log->addEntry( 'interwiki', $title, $detail );
}
@@ -428,7 +432,7 @@ class ImportReporter extends ContextSource {
$dbw = wfGetDB( DB_MASTER );
$latest = $title->getLatestRevID();
$nullRevision = Revision::newNullRevision( $dbw, $title->getArticleID(), $comment, true );
- if (!is_null($nullRevision)) {
+ if ( !is_null( $nullRevision ) ) {
$nullRevision->insertOn( $dbw );
$page = WikiPage::factory( $title );
# Update page record
diff --git a/includes/specials/SpecialJavaScriptTest.php b/includes/specials/SpecialJavaScriptTest.php
index c217eccb..d204d50c 100644
--- a/includes/specials/SpecialJavaScriptTest.php
+++ b/includes/specials/SpecialJavaScriptTest.php
@@ -40,19 +40,11 @@ class SpecialJavaScriptTest extends SpecialPage {
}
public function execute( $par ) {
- global $wgEnableJavaScriptTest;
-
$out = $this->getOutput();
$this->setHeaders();
$out->disallowUserJs();
- // Abort early if we're disabled
- if ( $wgEnableJavaScriptTest !== true ) {
- $out->addWikiMsg( 'javascripttest-disabled' );
- return;
- }
-
$out->addModules( 'mediawiki.special.javaScriptTest' );
// Determine framework
@@ -110,8 +102,9 @@ class SpecialJavaScriptTest extends SpecialPage {
* Function to wrap the summary.
* It must be given a valid state as a second parameter or an exception will
* be thrown.
- * @param $html String: The raw HTML.
- * @param $state String: State, one of 'noframework', 'unknownframework' or 'frameworkfound'
+ * @param string $html The raw HTML.
+ * @param string $state State, one of 'noframework', 'unknownframework' or 'frameworkfound'
+ * @throws MWException
* @return string
*/
private function wrapSummaryHtml( $html, $state ) {
@@ -165,9 +158,7 @@ HTML;
$out->addJsConfigVars( 'QUnitTestSwarmInjectJSPath', $wgJavaScriptTestConfig['qunit']['testswarm-injectjs'] );
}
- public function isListed(){
- global $wgEnableJavaScriptTest;
- return $wgEnableJavaScriptTest === true;
+ protected function getGroupName() {
+ return 'other';
}
-
}
diff --git a/includes/specials/SpecialLinkSearch.php b/includes/specials/SpecialLinkSearch.php
index 0810ee77..030416fb 100644
--- a/includes/specials/SpecialLinkSearch.php
+++ b/includes/specials/SpecialLinkSearch.php
@@ -22,7 +22,6 @@
* @author Brion Vibber
*/
-
/**
* Special:LinkSearch to search the external-links table.
* @ingroup SpecialPage
@@ -43,7 +42,7 @@ class LinkSearchPage extends QueryPage {
}
function execute( $par ) {
- global $wgUrlProtocols, $wgMiserMode;
+ global $wgUrlProtocols, $wgMiserMode, $wgScript;
$this->setHeaders();
$this->outputHeader();
@@ -64,15 +63,15 @@ class LinkSearchPage extends QueryPage {
$target2 = $target;
$protocol = '';
- $pr_sl = strpos($target2, '//' );
- $pr_cl = strpos($target2, ':' );
+ $pr_sl = strpos( $target2, '//' );
+ $pr_cl = strpos( $target2, ':' );
if ( $pr_sl ) {
// For protocols with '//'
- $protocol = substr( $target2, 0 , $pr_sl+2 );
- $target2 = substr( $target2, $pr_sl+2 );
+ $protocol = substr( $target2, 0, $pr_sl + 2 );
+ $target2 = substr( $target2, $pr_sl + 2 );
} elseif ( !$pr_sl && $pr_cl ) {
// For protocols without '//' like 'mailto:'
- $protocol = substr( $target2, 0 , $pr_cl+1 );
+ $protocol = substr( $target2, 0, $pr_cl + 1 );
$target2 = substr( $target2, $pr_cl+1 );
} elseif ( $protocol == '' && $target2 != '' ) {
// default
@@ -84,12 +83,16 @@ class LinkSearchPage extends QueryPage {
$protocol = '';
}
- $out->addWikiMsg( 'linksearch-text', '<nowiki>' . $this->getLanguage()->commaList( $protocols_list ) . '</nowiki>' );
- $s = Xml::openElement( 'form', array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => $GLOBALS['wgScript'] ) ) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) .
- '<fieldset>' .
- Xml::element( 'legend', array(), $this->msg( 'linksearch' )->text() ) .
- Xml::inputLabel( $this->msg( 'linksearch-pat' )->text(), 'target', 'target', 50, $target ) . ' ';
+ $out->addWikiMsg(
+ 'linksearch-text',
+ '<nowiki>' . $this->getLanguage()->commaList( $protocols_list ) . '</nowiki>',
+ count( $protocols_list )
+ );
+ $s = Html::openElement( 'form', array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => $wgScript ) ) . "\n" .
+ Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . "\n" .
+ Html::openElement( 'fieldset' ) . "\n" .
+ Html::element( 'legend', array(), $this->msg( 'linksearch' )->text() ) . "\n" .
+ Xml::inputLabel( $this->msg( 'linksearch-pat' )->text(), 'target', 'target', 50, $target ) . "\n";
if ( !$wgMiserMode ) {
$s .= Html::namespaceSelector(
array(
@@ -103,9 +106,9 @@ class LinkSearchPage extends QueryPage {
)
);
}
- $s .= Xml::submitButton( $this->msg( 'linksearch-ok' )->text() ) .
- '</fieldset>' .
- Xml::closeElement( 'form' );
+ $s .= Xml::submitButton( $this->msg( 'linksearch-ok' )->text() ) . "\n" .
+ Html::closeElement( 'fieldset' ) . "\n" .
+ Html::closeElement( 'form' ) . "\n";
$out->addHTML( $s );
if( $target != '' ) {
@@ -134,10 +137,10 @@ class LinkSearchPage extends QueryPage {
*/
static function mungeQuery( $query, $prot ) {
$field = 'el_index';
- $rv = LinkFilter::makeLikeArray( $query , $prot );
+ $rv = LinkFilter::makeLikeArray( $query, $prot );
if ( $rv === false ) {
// LinkFilter doesn't handle wildcard in IP, so we'll have to munge here.
- if (preg_match('/^(:?[0-9]{1,3}\.)+\*\s*$|^(:?[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]*\*\s*$/', $query)) {
+ if ( preg_match( '/^(:?[0-9]{1,3}\.)+\*\s*$|^(:?[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]*\*\s*$/', $query ) ) {
$dbr = wfGetDB( DB_SLAVE );
$rv = array( $prot . rtrim( $query, " \t*" ), $dbr->anyString() );
$field = 'el_to';
@@ -197,7 +200,7 @@ class LinkSearchPage extends QueryPage {
* Override to check query validity.
*/
function doQuery( $offset = false, $limit = false ) {
- list( $this->mMungedQuery, ) = LinkSearchPage::mungeQuery( $this->mQuery, $this->mProt );
+ list( $this->mMungedQuery, ) = LinkSearchPage::mungeQuery( $this->mQuery, $this->mProt );
if( $this->mMungedQuery === false ) {
$this->getOutput()->addWikiMsg( 'linksearch-error' );
} else {
@@ -218,4 +221,8 @@ class LinkSearchPage extends QueryPage {
function getOrderFields() {
return array();
}
+
+ protected function getGroupName() {
+ return 'redirects';
+ }
}
diff --git a/includes/specials/SpecialListfiles.php b/includes/specials/SpecialListfiles.php
index cc055221..c864ae2a 100644
--- a/includes/specials/SpecialListfiles.php
+++ b/includes/specials/SpecialListfiles.php
@@ -23,11 +23,11 @@
class SpecialListFiles extends IncludableSpecialPage {
- public function __construct(){
+ public function __construct() {
parent::__construct( 'Listfiles' );
}
- public function execute( $par ){
+ public function execute( $par ) {
$this->setHeaders();
$this->outputHeader();
@@ -51,6 +51,10 @@ class SpecialListFiles extends IncludableSpecialPage {
}
$this->getOutput()->addHTML( $html );
}
+
+ protected function getGroupName() {
+ return 'media';
+ }
}
/**
@@ -137,7 +141,7 @@ class ImageListPager extends TablePager {
$tables = array( 'image' );
$fields = array_keys( $this->getFieldNames() );
$fields[] = 'img_user';
- $fields[array_search('thumb', $fields)] = 'img_name AS thumb';
+ $fields[array_search( 'thumb', $fields )] = 'img_name AS thumb';
$options = $join_conds = array();
# Depends on $wgMiserMode
@@ -223,7 +227,7 @@ class ImageListPager extends TablePager {
case 'img_size':
return htmlspecialchars( $this->getLanguage()->formatSize( $value ) );
case 'img_description':
- return Linker::commentBlock( $value );
+ return Linker::formatComment( $value );
case 'count':
return intval( $value ) + 1;
}
diff --git a/includes/specials/SpecialListgrouprights.php b/includes/specials/SpecialListgrouprights.php
index 1f95c225..7cccf887 100644
--- a/includes/specials/SpecialListgrouprights.php
+++ b/includes/specials/SpecialListgrouprights.php
@@ -139,15 +139,15 @@ class SpecialListGroupRights extends SpecialPage {
/**
* Create a user-readable list of permissions from the given array.
*
- * @param $permissions Array of permission => bool (from $wgGroupPermissions items)
- * @param $revoke Array of permission => bool (from $wgRevokePermissions items)
- * @param $add Array of groups this group is allowed to add or true
- * @param $remove Array of groups this group is allowed to remove or true
- * @param $addSelf Array of groups this group is allowed to add to self or true
- * @param $removeSelf Array of group this group is allowed to remove from self or true
+ * @param array $permissions of permission => bool (from $wgGroupPermissions items)
+ * @param array $revoke of permission => bool (from $wgRevokePermissions items)
+ * @param array $add of groups this group is allowed to add or true
+ * @param array $remove of groups this group is allowed to remove or true
+ * @param array $addSelf of groups this group is allowed to add to self or true
+ * @param array $removeSelf of group this group is allowed to remove from self or true
* @return string List of all granted permissions, separated by comma separator
*/
- private function formatPermissions( $permissions, $revoke, $add, $remove, $addSelf, $removeSelf ) {
+ private function formatPermissions( $permissions, $revoke, $add, $remove, $addSelf, $removeSelf ) {
$r = array();
foreach( $permissions as $permission => $granted ) {
//show as granted only if it isn't revoked to prevent duplicate display of permissions
@@ -170,7 +170,7 @@ class SpecialListGroupRights extends SpecialPage {
}
sort( $r );
$lang = $this->getLanguage();
- if( $add === true ){
+ if( $add === true ) {
$r[] = $this->msg( 'listgrouprights-addgroup-all' )->escaped();
} elseif( is_array( $add ) && count( $add ) ) {
$add = array_values( array_unique( $add ) );
@@ -179,7 +179,7 @@ class SpecialListGroupRights extends SpecialPage {
count( $add )
)->parse();
}
- if( $remove === true ){
+ if( $remove === true ) {
$r[] = $this->msg( 'listgrouprights-removegroup-all' )->escaped();
} elseif( is_array( $remove ) && count( $remove ) ) {
$remove = array_values( array_unique( $remove ) );
@@ -188,7 +188,7 @@ class SpecialListGroupRights extends SpecialPage {
count( $remove )
)->parse();
}
- if( $addSelf === true ){
+ if( $addSelf === true ) {
$r[] = $this->msg( 'listgrouprights-addgroup-self-all' )->escaped();
} elseif( is_array( $addSelf ) && count( $addSelf ) ) {
$addSelf = array_values( array_unique( $addSelf ) );
@@ -197,7 +197,7 @@ class SpecialListGroupRights extends SpecialPage {
count( $addSelf )
)->parse();
}
- if( $removeSelf === true ){
+ if( $removeSelf === true ) {
$r[] = $this->msg( 'listgrouprights-removegroup-self-all' )->parse();
} elseif( is_array( $removeSelf ) && count( $removeSelf ) ) {
$removeSelf = array_values( array_unique( $removeSelf ) );
@@ -212,4 +212,8 @@ class SpecialListGroupRights extends SpecialPage {
return '<ul><li>' . implode( "</li>\n<li>", $r ) . '</li></ul>';
}
}
+
+ protected function getGroupName() {
+ return 'users';
+ }
}
diff --git a/includes/specials/SpecialListredirects.php b/includes/specials/SpecialListredirects.php
index fe338a08..0283767a 100644
--- a/includes/specials/SpecialListredirects.php
+++ b/includes/specials/SpecialListredirects.php
@@ -34,9 +34,17 @@ class ListredirectsPage extends QueryPage {
parent::__construct( $name );
}
- function isExpensive() { return true; }
- function isSyndicated() { return false; }
- function sortDescending() { return false; }
+ function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ function sortDescending() {
+ return false;
+ }
function getQueryInfo() {
return array(
@@ -77,7 +85,7 @@ class ListredirectsPage extends QueryPage {
$batch->execute();
// Back to start for display
- if ( $db->numRows( $res ) > 0 ) {
+ if ( $res->numRows() > 0 ) {
// If there are no rows we get an error seeking.
$db->dataSeek( $res, 0 );
}
@@ -118,4 +126,8 @@ class ListredirectsPage extends QueryPage {
return "<del>$rd_link</del>";
}
}
+
+ protected function getGroupName() {
+ return 'pages';
+ }
}
diff --git a/includes/specials/SpecialListusers.php b/includes/specials/SpecialListusers.php
index 1089fbbe..d253a4d3 100644
--- a/includes/specials/SpecialListusers.php
+++ b/includes/specials/SpecialListusers.php
@@ -36,7 +36,9 @@ class UsersPager extends AlphabeticPager {
/**
* @param $context IContextSource
- * @param $par null|array
+ * @param array $par (Default null)
+ * @param $including boolean Whether this page is being transcluded in
+ * another page
*/
function __construct( IContextSource $context = null, $par = null, $including = null ) {
if ( $context ) {
@@ -89,7 +91,7 @@ class UsersPager extends AlphabeticPager {
$conds = array();
// Don't show hidden names
if( !$this->getUser()->isAllowed( 'hideuser' ) ) {
- $conds[] = 'ipb_deleted IS NULL';
+ $conds[] = 'ipb_deleted IS NULL OR ipb_deleted = 0';
}
$options = array();
@@ -114,7 +116,7 @@ class UsersPager extends AlphabeticPager {
$options['GROUP BY'] = $this->creationSort ? 'user_id' : 'user_name';
$query = array(
- 'tables' => array( 'user', 'user_groups', 'ipblocks'),
+ 'tables' => array( 'user', 'user_groups', 'ipblocks' ),
'fields' => array(
'user_name' => $this->creationSort ? 'MAX(user_name)' : 'user_name',
'user_id' => $this->creationSort ? 'user_id' : 'MAX(user_id)',
@@ -129,7 +131,6 @@ class UsersPager extends AlphabeticPager {
'user_groups' => array( 'LEFT JOIN', 'user_id=ug_user' ),
'ipblocks' => array( 'LEFT JOIN', array(
'user_id=ipb_user',
- 'ipb_deleted' => 1,
'ipb_auto' => 0
)),
),
@@ -152,7 +153,7 @@ class UsersPager extends AlphabeticPager {
$userName = $row->user_name;
$ulinks = Linker::userLink( $row->user_id, $userName );
- $ulinks .= Linker::userToolLinks( $row->user_id, $userName );
+ $ulinks .= Linker::userToolLinksRedContribs( $row->user_id, $userName, intval( $row->edits ) );
$lang = $this->getLanguage();
@@ -185,9 +186,10 @@ class UsersPager extends AlphabeticPager {
$created = $this->msg( 'usercreated', $d, $t, $row->user_name )->escaped();
$created = ' ' . $this->msg( 'parentheses' )->rawParams( $created )->escaped();
}
+ $blocked = !is_null( $row->ipb_deleted ) ? ' ' . $this->msg( 'listusers-blocked', $userName )->escaped() : '';
wfRunHooks( 'SpecialListusersFormatRow', array( &$item, $row ) );
- return Html::rawElement( 'li', array(), "{$item}{$edits}{$created}" );
+ return Html::rawElement( 'li', array(), "{$item}{$edits}{$created}{$blocked}" );
}
function doBatchLookups() {
@@ -204,23 +206,32 @@ class UsersPager extends AlphabeticPager {
/**
* @return string
*/
- function getPageHeader( ) {
+ function getPageHeader() {
global $wgScript;
list( $self ) = explode( '/', $this->getTitle()->getPrefixedDBkey() );
# Form tag
- $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listusers-form' ) ) .
+ $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listusers-form' ) ) .
Xml::fieldset( $this->msg( 'listusers' )->text() ) .
Html::hidden( 'title', $self );
# Username field
$out .= Xml::label( $this->msg( 'listusersfrom' )->text(), 'offset' ) . ' ' .
- Xml::input( 'username', 20, $this->requestedUser, array( 'id' => 'offset' ) ) . ' ';
+ Html::input(
+ 'username',
+ $this->requestedUser,
+ 'text',
+ array(
+ 'id' => 'offset',
+ 'size' => 20,
+ 'autofocus' => $this->requestedUser === ''
+ )
+ ) . ' ';
# Group drop-down list
$out .= Xml::label( $this->msg( 'group' )->text(), 'group' ) . ' ' .
- Xml::openElement('select', array( 'name' => 'group', 'id' => 'group' ) ) .
+ Xml::openElement( 'select', array( 'name' => 'group', 'id' => 'group' ) ) .
Xml::option( $this->msg( 'group-all' )->text(), '' );
foreach( $this->getAllGroups() as $group => $groupText )
$out .= Xml::option( $groupText, $group, $group == $this->requestedGroup );
@@ -286,8 +297,8 @@ class UsersPager extends AlphabeticPager {
/**
* Format a link to a group description page
*
- * @param $group String: group name
- * @param $username String Username
+ * @param string $group group name
+ * @param string $username Username
* @return string
*/
protected static function buildGroupLink( $group, $username ) {
@@ -298,20 +309,19 @@ class UsersPager extends AlphabeticPager {
/**
* @ingroup SpecialPage
*/
-class SpecialListUsers extends SpecialPage {
+class SpecialListUsers extends IncludableSpecialPage {
/**
* Constructor
*/
public function __construct() {
parent::__construct( 'Listusers' );
- $this->mIncludable = true;
}
/**
* Show the special page
*
- * @param $par string (optional) A group to list users from
+ * @param string $par (optional) A group to list users from
*/
public function execute( $par ) {
$this->setHeaders();
@@ -337,4 +347,8 @@ class SpecialListUsers extends SpecialPage {
$this->getOutput()->addHTML( $s );
}
+
+ protected function getGroupName() {
+ return 'users';
+ }
}
diff --git a/includes/specials/SpecialLockdb.php b/includes/specials/SpecialLockdb.php
index d71ac6e1..95ef9510 100644
--- a/includes/specials/SpecialLockdb.php
+++ b/includes/specials/SpecialLockdb.php
@@ -102,4 +102,8 @@ class SpecialLockdb extends FormSpecialPage {
$out->addSubtitle( $this->msg( 'lockdbsuccesssub' ) );
$out->addWikiMsg( 'lockdbsuccesstext' );
}
+
+ protected function getGroupName() {
+ return 'wiki';
+ }
}
diff --git a/includes/specials/SpecialLog.php b/includes/specials/SpecialLog.php
index 7800e566..4fc0f6e8 100644
--- a/includes/specials/SpecialLog.php
+++ b/includes/specials/SpecialLog.php
@@ -182,7 +182,6 @@ class SpecialLog extends SpecialPage {
return $s;
}
-
/**
* Set page title and show header for this log type
* @param $type string
@@ -194,4 +193,7 @@ class SpecialLog extends SpecialPage {
$this->getOutput()->addHTML( $page->getDescription()->parseAsBlock() );
}
+ protected function getGroupName() {
+ return 'changes';
+ }
}
diff --git a/includes/specials/SpecialLonelypages.php b/includes/specials/SpecialLonelypages.php
index 763bbdb1..8c6a88ac 100644
--- a/includes/specials/SpecialLonelypages.php
+++ b/includes/specials/SpecialLonelypages.php
@@ -44,7 +44,10 @@ class LonelyPagesPage extends PageQueryPage {
function isExpensive() {
return true;
}
- function isSyndicated() { return false; }
+
+ function isSyndicated() {
+ return false;
+ }
function getQueryInfo() {
return array (
@@ -78,4 +81,8 @@ class LonelyPagesPage extends PageQueryPage {
return array( 'page_title' );
}
}
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
}
diff --git a/includes/specials/SpecialLongpages.php b/includes/specials/SpecialLongpages.php
index dd60e37d..c045f9e9 100644
--- a/includes/specials/SpecialLongpages.php
+++ b/includes/specials/SpecialLongpages.php
@@ -34,4 +34,8 @@ class LongPagesPage extends ShortPagesPage {
function sortDescending() {
return true;
}
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
}
diff --git a/includes/specials/SpecialMIMEsearch.php b/includes/specials/SpecialMIMEsearch.php
index 104c653f..c5a109d4 100644
--- a/includes/specials/SpecialMIMEsearch.php
+++ b/includes/specials/SpecialMIMEsearch.php
@@ -34,9 +34,17 @@ class MIMEsearchPage extends QueryPage {
parent::__construct( $name );
}
- function isExpensive() { return true; }
- function isSyndicated() { return false; }
- function isCacheable() { return false; }
+ function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ function isCacheable() {
+ return false;
+ }
function linkParameters() {
return array( 'mime' => "{$this->major}/{$this->minor}" );
@@ -84,7 +92,6 @@ class MIMEsearchPage extends QueryPage {
parent::execute( $par );
}
-
function formatResult( $skin, $result ) {
global $wgContLang;
@@ -126,4 +133,8 @@ class MIMEsearchPage extends QueryPage {
);
return in_array( $type, $types );
}
+
+ protected function getGroupName() {
+ return 'media';
+ }
}
diff --git a/includes/specials/SpecialMergeHistory.php b/includes/specials/SpecialMergeHistory.php
index 1f057499..1476e156 100644
--- a/includes/specials/SpecialMergeHistory.php
+++ b/includes/specials/SpecialMergeHistory.php
@@ -141,7 +141,7 @@ class SpecialMergeHistory extends SpecialPage {
'<fieldset>' .
Xml::element( 'legend', array(),
$this->msg( 'mergehistory-box' )->text() ) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) .
+ Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
Html::hidden( 'submitted', '1' ) .
Html::hidden( 'mergepoint', $this->mTimestamp ) .
Xml::openElement( 'table' ) .
@@ -373,26 +373,33 @@ class SpecialMergeHistory extends SpecialPage {
$destTitle->getPrefixedText()
)->inContentLanguage()->text();
}
- $mwRedir = MagicWord::get( 'redirect' );
- $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $destTitle->getPrefixedText() . "]]\n";
- $redirectPage = WikiPage::factory( $targetTitle );
- $redirectRevision = new Revision( array(
- 'page' => $this->mTargetID,
- 'comment' => $comment,
- 'text' => $redirectText ) );
- $redirectRevision->insertOn( $dbw );
- $redirectPage->updateRevisionOn( $dbw, $redirectRevision );
-
- # Now, we record the link from the redirect to the new title.
- # It should have no other outgoing links...
- $dbw->delete( 'pagelinks', array( 'pl_from' => $this->mDestID ), __METHOD__ );
- $dbw->insert( 'pagelinks',
- array(
- 'pl_from' => $this->mDestID,
- 'pl_namespace' => $destTitle->getNamespace(),
- 'pl_title' => $destTitle->getDBkey() ),
- __METHOD__
- );
+
+ $contentHandler = ContentHandler::getForTitle( $targetTitle );
+ $redirectContent = $contentHandler->makeRedirectContent( $destTitle );
+
+ if ( $redirectContent ) {
+ $redirectPage = WikiPage::factory( $targetTitle );
+ $redirectRevision = new Revision( array(
+ 'title' => $targetTitle,
+ 'page' => $this->mTargetID,
+ 'comment' => $comment,
+ 'content' => $redirectContent ) );
+ $redirectRevision->insertOn( $dbw );
+ $redirectPage->updateRevisionOn( $dbw, $redirectRevision );
+
+ # Now, we record the link from the redirect to the new title.
+ # It should have no other outgoing links...
+ $dbw->delete( 'pagelinks', array( 'pl_from' => $this->mDestID ), __METHOD__ );
+ $dbw->insert( 'pagelinks',
+ array(
+ 'pl_from' => $this->mDestID,
+ 'pl_namespace' => $destTitle->getNamespace(),
+ 'pl_title' => $destTitle->getDBkey() ),
+ __METHOD__
+ );
+ } else {
+ // would be nice to show a warning if we couldn't create a redirect
+ }
} else {
$targetTitle->invalidateCache(); // update histories
}
@@ -416,6 +423,10 @@ class SpecialMergeHistory extends SpecialPage {
return true;
}
+
+ protected function getGroupName() {
+ return 'pagetools';
+ }
}
class MergeHistoryPager extends ReverseChronologicalPager {
diff --git a/includes/specials/SpecialMostcategories.php b/includes/specials/SpecialMostcategories.php
index 3f0bafa3..11f26bd7 100644
--- a/includes/specials/SpecialMostcategories.php
+++ b/includes/specials/SpecialMostcategories.php
@@ -35,8 +35,13 @@ class MostcategoriesPage extends QueryPage {
parent::__construct( $name );
}
- function isExpensive() { return true; }
- function isSyndicated() { return false; }
+ function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
function getQueryInfo() {
return array (
@@ -94,4 +99,8 @@ class MostcategoriesPage extends QueryPage {
return $this->getLanguage()->specialList( $link, $count );
}
+
+ protected function getGroupName() {
+ return 'highuse';
+ }
}
diff --git a/includes/specials/SpecialMostimages.php b/includes/specials/SpecialMostimages.php
index 3d797908..78b2d911 100644
--- a/includes/specials/SpecialMostimages.php
+++ b/includes/specials/SpecialMostimages.php
@@ -35,8 +35,13 @@ class MostimagesPage extends ImageQueryPage {
parent::__construct( $name );
}
- function isExpensive() { return true; }
- function isSyndicated() { return false; }
+ function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
function getQueryInfo() {
return array (
@@ -53,4 +58,7 @@ class MostimagesPage extends ImageQueryPage {
return $this->msg( 'nimagelinks' )->numParams( $row->value )->escaped() . '<br />';
}
+ protected function getGroupName() {
+ return 'highuse';
+ }
}
diff --git a/includes/specials/SpecialMostinterwikis.php b/includes/specials/SpecialMostinterwikis.php
index 894d697b..574a9afb 100644
--- a/includes/specials/SpecialMostinterwikis.php
+++ b/includes/specials/SpecialMostinterwikis.php
@@ -35,8 +35,13 @@ class MostinterwikisPage extends QueryPage {
parent::__construct( $name );
}
- function isExpensive() { return true; }
- function isSyndicated() { return false; }
+ function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
function getQueryInfo() {
return array (
@@ -109,4 +114,8 @@ class MostinterwikisPage extends QueryPage {
return $this->getLanguage()->specialList( $link, $count );
}
+
+ protected function getGroupName() {
+ return 'highuse';
+ }
}
diff --git a/includes/specials/SpecialMostlinked.php b/includes/specials/SpecialMostlinked.php
index 89c43509..4b6e5670 100644
--- a/includes/specials/SpecialMostlinked.php
+++ b/includes/specials/SpecialMostlinked.php
@@ -36,8 +36,13 @@ class MostlinkedPage extends QueryPage {
parent::__construct( $name );
}
- function isExpensive() { return true; }
- function isSyndicated() { return false; }
+ function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
function getQueryInfo() {
return array (
@@ -76,7 +81,7 @@ class MostlinkedPage extends QueryPage {
* Make a link to "what links here" for the specified title
*
* @param $title Title being queried
- * @param $caption String: text to display on the link
+ * @param string $caption text to display on the link
* @return String
*/
function makeWlhLink( $title, $caption ) {
@@ -102,4 +107,8 @@ class MostlinkedPage extends QueryPage {
$this->msg( 'nlinks' )->numParams( $result->value )->escaped() );
return $this->getLanguage()->specialList( $link, $wlh );
}
+
+ protected function getGroupName() {
+ return 'highuse';
+ }
}
diff --git a/includes/specials/SpecialMostlinkedcategories.php b/includes/specials/SpecialMostlinkedcategories.php
index dadef8bf..a1bce45d 100644
--- a/includes/specials/SpecialMostlinkedcategories.php
+++ b/includes/specials/SpecialMostlinkedcategories.php
@@ -35,7 +35,9 @@ class MostlinkedCategoriesPage extends QueryPage {
parent::__construct( $name );
}
- function isSyndicated() { return false; }
+ function isSyndicated() {
+ return false;
+ }
function getQueryInfo() {
return array (
@@ -46,7 +48,9 @@ class MostlinkedCategoriesPage extends QueryPage {
);
}
- function sortDescending() { return true; }
+ function sortDescending() {
+ return true;
+ }
/**
* Fetch user page links and cache their existence
@@ -90,4 +94,8 @@ class MostlinkedCategoriesPage extends QueryPage {
$nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
return $this->getLanguage()->specialList( $plink, $nlinks );
}
+
+ protected function getGroupName() {
+ return 'highuse';
+ }
}
diff --git a/includes/specials/SpecialMostlinkedtemplates.php b/includes/specials/SpecialMostlinkedtemplates.php
index 22932e5c..506e6b22 100644
--- a/includes/specials/SpecialMostlinkedtemplates.php
+++ b/includes/specials/SpecialMostlinkedtemplates.php
@@ -124,5 +124,8 @@ class MostlinkedTemplatesPage extends QueryPage {
$label = $this->msg( 'ntransclusions' )->numParams( $result->value )->escaped();
return Linker::link( $wlh, $label );
}
-}
+ protected function getGroupName() {
+ return 'highuse';
+ }
+}
diff --git a/includes/specials/SpecialMostrevisions.php b/includes/specials/SpecialMostrevisions.php
index b0253316..ad6b788d 100644
--- a/includes/specials/SpecialMostrevisions.php
+++ b/includes/specials/SpecialMostrevisions.php
@@ -31,4 +31,8 @@ class MostrevisionsPage extends FewestrevisionsPage {
function sortDescending() {
return true;
}
+
+ protected function getGroupName() {
+ return 'highuse';
+ }
}
diff --git a/includes/specials/SpecialMovepage.php b/includes/specials/SpecialMovepage.php
index af3dbf3e..4adb0371 100644
--- a/includes/specials/SpecialMovepage.php
+++ b/includes/specials/SpecialMovepage.php
@@ -71,7 +71,6 @@ class MovePageForm extends UnlistedSpecialPage {
? Title::newFromText( $newTitleText_bc )
: Title::makeTitleSafe( $newTitleTextNs, $newTitleTextMain );
-
$user = $this->getUser();
# Check rights
@@ -104,7 +103,7 @@ class MovePageForm extends UnlistedSpecialPage {
/**
* Show the form
*
- * @param $err Array: error messages. Each item is an error message.
+ * @param array $err error messages. Each item is an error message.
* It may either be a string message name or array message name and
* parameters, like the second argument to OutputPage::wrapWikiMsg().
*/
@@ -152,7 +151,7 @@ class MovePageForm extends UnlistedSpecialPage {
</tr>";
$err = array();
} else {
- if ($this->oldTitle->getNamespace() == NS_USER && !$this->oldTitle->isSubpage() ) {
+ if ( $this->oldTitle->getNamespace() == NS_USER && !$this->oldTitle->isSubpage() ) {
$out->wrapWikiMsg( "<div class=\"error mw-moveuserpage-warning\">\n$1\n</div>", 'moveuserpage-warning' );
}
$out->addWikiMsg( $wgFixDoubleRedirects ? 'movepagetext' :
@@ -189,7 +188,7 @@ class MovePageForm extends UnlistedSpecialPage {
array(
'rd_namespace' => $this->oldTitle->getNamespace(),
'rd_title' => $this->oldTitle->getDBkey(),
- ) , __METHOD__ );
+ ), __METHOD__ );
} else {
$hasRedirects = false;
}
@@ -254,12 +253,14 @@ class MovePageForm extends UnlistedSpecialPage {
}
}
+ $handler = ContentHandler::getForTitle( $this->oldTitle );
+
$out->addHTML(
- Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL( 'action=submit' ), 'id' => 'movepage' ) ) .
- Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', null, $this->msg( 'move-page-legend' )->text() ) .
- Xml::openElement( 'table', array( 'id' => 'mw-movepage-table' ) ) .
- "<tr>
+ Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL( 'action=submit' ), 'id' => 'movepage' ) ) .
+ Xml::openElement( 'fieldset' ) .
+ Xml::element( 'legend', null, $this->msg( 'move-page-legend' )->text() ) .
+ Xml::openElement( 'table', array( 'id' => 'mw-movepage-table' ) ) .
+ "<tr>
<td class='mw-label'>" .
$this->msg( 'movearticle' )->escaped() .
"</td>
@@ -309,7 +310,7 @@ class MovePageForm extends UnlistedSpecialPage {
);
}
- if ( $user->isAllowed( 'suppressredirect' ) ) {
+ if ( $user->isAllowed( 'suppressredirect' ) && $handler->supportsRedirects() ) {
$out->addHTML( "
<tr>
<td></td>
@@ -342,7 +343,7 @@ class MovePageForm extends UnlistedSpecialPage {
'wpMovesubpages',
# Don't check the box if we only have talk subpages to
# move and we aren't moving the talk page.
- $this->moveSubpages && ($this->oldTitle->hasSubpages() || $this->moveTalk),
+ $this->moveSubpages && ( $this->oldTitle->hasSubpages() || $this->moveTalk ),
array( 'id' => 'wpMovesubpages' )
) . '&#160;' .
Xml::tags( 'label', array( 'for' => 'wpMovesubpages' ),
@@ -357,7 +358,7 @@ class MovePageForm extends UnlistedSpecialPage {
);
}
- $watchChecked = $user->isLoggedIn() && ($this->watch || $user->getBoolOption( 'watchmoves' )
+ $watchChecked = $user->isLoggedIn() && ( $this->watch || $user->getBoolOption( 'watchmoves' )
|| $user->isWatched( $this->oldTitle ) );
# Don't allow watching if user is not logged in
if( $user->isLoggedIn() ) {
@@ -447,7 +448,11 @@ class MovePageForm extends UnlistedSpecialPage {
}
}
- if ( $user->isAllowed( 'suppressredirect' ) ) {
+ $handler = ContentHandler::getForTitle( $ot );
+
+ if ( !$handler->supportsRedirects() ) {
+ $createRedirect = false;
+ } elseif ( $user->isAllowed( 'suppressredirect' ) ) {
$createRedirect = $this->leaveRedirect;
} else {
$createRedirect = true;
@@ -464,8 +469,6 @@ class MovePageForm extends UnlistedSpecialPage {
DoubleRedirectJob::fixRedirects( 'move', $ot, $nt );
}
- wfRunHooks( 'SpecialMovepageAfterMove', array( &$this, &$ot, &$nt ) );
-
$out = $this->getOutput();
$out->setPageTitle( $this->msg( 'pagemovedsub' ) );
@@ -479,11 +482,23 @@ class MovePageForm extends UnlistedSpecialPage {
$oldText = $ot->getPrefixedText();
$newText = $nt->getPrefixedText();
- $msgName = $createRedirect ? 'movepage-moved-redirect' : 'movepage-moved-noredirect';
+ if ( $ot->exists() ) {
+ //NOTE: we assume that if the old title exists, it's because it was re-created as
+ // a redirect to the new title. This is not safe, but what we did before was
+ // even worse: we just determined whether a redirect should have been created,
+ // and reported that it was created if it should have, without any checks.
+ // Also note that isRedirect() is unreliable because of bug 37209.
+ $msgName = 'movepage-moved-redirect';
+ } else {
+ $msgName = 'movepage-moved-noredirect';
+ }
+
$out->addHTML( $this->msg( 'movepage-moved' )->rawParams( $oldLink,
$newLink )->params( $oldText, $newText )->parseAsBlock() );
$out->addWikiMsg( $msgName );
+ wfRunHooks( 'SpecialMovepageAfterMove', array( &$this, &$ot, &$nt ) );
+
# Now we move extra pages we've been asked to move: subpages and talk
# pages. First, if the old page or the new page is a talk page, we
# can't move any talk pages: cancel that.
@@ -521,10 +536,10 @@ class MovePageForm extends UnlistedSpecialPage {
);
$conds['page_namespace'] = array();
if( MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
- $conds['page_namespace'] []= $ot->getNamespace();
+ $conds['page_namespace'][] = $ot->getNamespace();
}
if( $this->moveTalk && MWNamespace::hasSubpages( $nt->getTalkPage()->getNamespace() ) ) {
- $conds['page_namespace'] []= $ot->getTalkPage()->getNamespace();
+ $conds['page_namespace'][] = $ot->getTalkPage()->getNamespace();
}
} elseif( $this->moveTalk ) {
$conds = array(
@@ -570,7 +585,7 @@ class MovePageForm extends UnlistedSpecialPage {
$newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
if( !$newSubpage ) {
$oldLink = Linker::linkKnown( $oldSubpage );
- $extraOutput []= $this->msg( 'movepage-page-unmoved' )->rawParams( $oldLink
+ $extraOutput[] = $this->msg( 'movepage-page-unmoved' )->rawParams( $oldLink
)->params( Title::makeName( $newNs, $newPageName ) )->escaped();
continue;
}
@@ -578,7 +593,7 @@ class MovePageForm extends UnlistedSpecialPage {
# This was copy-pasted from Renameuser, bleh.
if ( $newSubpage->exists() && !$oldSubpage->isValidMoveTarget( $newSubpage ) ) {
$link = Linker::linkKnown( $newSubpage );
- $extraOutput []= $this->msg( 'movepage-page-exists' )->rawParams( $link )->escaped();
+ $extraOutput[] = $this->msg( 'movepage-page-exists' )->rawParams( $link )->escaped();
} else {
$success = $oldSubpage->moveTo( $newSubpage, true, $this->reason, $createRedirect );
if( $success === true ) {
@@ -592,16 +607,16 @@ class MovePageForm extends UnlistedSpecialPage {
array( 'redirect' => 'no' )
);
$newLink = Linker::linkKnown( $newSubpage );
- $extraOutput []= $this->msg( 'movepage-page-moved' )->rawParams( $oldLink, $newLink )->escaped();
+ $extraOutput[] = $this->msg( 'movepage-page-moved' )->rawParams( $oldLink, $newLink )->escaped();
++$count;
if( $count >= $wgMaximumMovedPages ) {
- $extraOutput []= $this->msg( 'movepage-max-pages' )->numParams( $wgMaximumMovedPages )->escaped();
+ $extraOutput[] = $this->msg( 'movepage-max-pages' )->numParams( $wgMaximumMovedPages )->escaped();
break;
}
} else {
$oldLink = Linker::linkKnown( $oldSubpage );
$newLink = Linker::link( $newSubpage );
- $extraOutput []= $this->msg( 'movepage-page-unmoved' )->rawParams( $oldLink, $newLink )->escaped();
+ $extraOutput[] = $this->msg( 'movepage-page-unmoved' )->rawParams( $oldLink, $newLink )->escaped();
}
}
@@ -660,4 +675,8 @@ class MovePageForm extends UnlistedSpecialPage {
}
$out->addHTML( "</ul>\n" );
}
+
+ protected function getGroupName() {
+ return 'pagetools';
+ }
}
diff --git a/includes/specials/SpecialNewimages.php b/includes/specials/SpecialNewimages.php
index 350aac63..52cbc3aa 100644
--- a/includes/specials/SpecialNewimages.php
+++ b/includes/specials/SpecialNewimages.php
@@ -22,11 +22,11 @@
*/
class SpecialNewFiles extends IncludableSpecialPage {
- public function __construct(){
+ public function __construct() {
parent::__construct( 'Newimages' );
}
- public function execute( $par ){
+ public function execute( $par ) {
$this->setHeaders();
$this->outputHeader();
@@ -42,8 +42,11 @@ class SpecialNewFiles extends IncludableSpecialPage {
$this->getOutput()->addHTML( $pager->getNavigationBar() );
}
}
-}
+ protected function getGroupName() {
+ return 'changes';
+ }
+}
/**
* @ingroup SpecialPage Pager
@@ -57,7 +60,7 @@ class NewFilesPager extends ReverseChronologicalPager {
function __construct( IContextSource $context, $par = null ) {
$this->like = $context->getRequest()->getText( 'like' );
- $this->showbots = $context->getRequest()->getBool( 'showbots' , 0 );
+ $this->showbots = $context->getRequest()->getBool( 'showbots', 0 );
if ( is_numeric( $par ) ) {
$this->setLimit( $par );
}
@@ -85,10 +88,10 @@ class NewFilesPager extends ReverseChronologicalPager {
}
}
- if( !$wgMiserMode && $this->like !== null ){
+ if( !$wgMiserMode && $this->like !== null ) {
$dbr = wfGetDB( DB_SLAVE );
$likeObj = Title::newFromURL( $this->like );
- if( $likeObj instanceof Title ){
+ if( $likeObj instanceof Title ) {
$like = $dbr->buildLike( $dbr->anyString(), strtolower( $likeObj->getDBkey() ), $dbr->anyString() );
$conds[] = "LOWER(img_name) $like";
}
@@ -104,18 +107,18 @@ class NewFilesPager extends ReverseChronologicalPager {
return $query;
}
- function getIndexField(){
+ function getIndexField() {
return 'img_timestamp';
}
- function getStartBody(){
+ function getStartBody() {
if ( !$this->gallery ) {
$this->gallery = new ImageGallery();
}
return '';
}
- function getEndBody(){
+ function getEndBody() {
return $this->gallery->toHTML();
}
@@ -161,7 +164,7 @@ class NewFilesPager extends ReverseChronologicalPager {
),
);
- if( $wgMiserMode ){
+ if( $wgMiserMode ) {
unset( $fields['like'] );
}
diff --git a/includes/specials/SpecialNewpages.php b/includes/specials/SpecialNewpages.php
index 8e15d554..ebb3021d 100644
--- a/includes/specials/SpecialNewpages.php
+++ b/includes/specials/SpecialNewpages.php
@@ -53,12 +53,13 @@ class SpecialNewpages extends IncludableSpecialPage {
$opts->add( 'hidepatrolled', $this->getUser()->getBoolOption( 'newpageshidepatrolled' ) );
$opts->add( 'hidebots', false );
$opts->add( 'hideredirs', true );
- $opts->add( 'limit', (int)$this->getUser()->getOption( 'rclimit' ) );
+ $opts->add( 'limit', $this->getUser()->getIntOption( 'rclimit' ) );
$opts->add( 'offset', '' );
$opts->add( 'namespace', '0' );
$opts->add( 'username', '' );
$opts->add( 'feed', '' );
$opts->add( 'tagfilter', '' );
+ $opts->add( 'invert', false );
$this->customFilters = array();
wfRunHooks( 'SpecialNewPagesFilters', array( $this, &$this->customFilters ) );
@@ -105,7 +106,7 @@ class SpecialNewpages extends IncludableSpecialPage {
}
// PG offsets not just digits!
if ( preg_match( '/^offset=([^=]+)$/', $bit, $m ) ) {
- $this->opts->setValue( 'offset', intval( $m[1] ) );
+ $this->opts->setValue( 'offset', intval( $m[1] ) );
}
if ( preg_match( '/^username=(.*)$/', $bit, $m ) ) {
$this->opts->setValue( 'username', $m[1] );
@@ -113,7 +114,7 @@ class SpecialNewpages extends IncludableSpecialPage {
if ( preg_match( '/^namespace=(.*)$/', $bit, $m ) ) {
$ns = $this->getLanguage()->getNsIndex( $m[1] );
if( $ns !== false ) {
- $this->opts->setValue( 'namespace', $ns );
+ $this->opts->setValue( 'namespace', $ns );
}
}
}
@@ -140,12 +141,13 @@ class SpecialNewpages extends IncludableSpecialPage {
$feedType = $this->opts->getValue( 'feed' );
if( $feedType ) {
- return $this->feed( $feedType );
+ $this->feed( $feedType );
+ return;
}
$allValues = $this->opts->getAllValues();
unset( $allValues['feed'] );
- $out->setFeedAppendQuery( wfArrayToCGI( $allValues ) );
+ $out->setFeedAppendQuery( wfArrayToCgi( $allValues ) );
}
$pager = new NewPagesPager( $this, $this->opts );
@@ -164,8 +166,6 @@ class SpecialNewpages extends IncludableSpecialPage {
}
protected function filterLinks() {
- global $wgGroupPermissions;
-
// show/hide links
$showhide = array( $this->msg( 'show' )->escaped(), $this->msg( 'hide' )->escaped() );
@@ -181,8 +181,7 @@ class SpecialNewpages extends IncludableSpecialPage {
}
// Disable some if needed
- # @todo FIXME: Throws E_NOTICEs if not set; and doesn't obey hooks etc.
- if ( $wgGroupPermissions['*']['createpage'] !== true ) {
+ if ( !User::groupHasPermission( '*', 'createpage' ) ) {
unset( $filters['hideliu'] );
}
if ( !$this->getUser()->useNPPatrol() ) {
@@ -213,6 +212,7 @@ class SpecialNewpages extends IncludableSpecialPage {
$namespace = $this->opts->consumeValue( 'namespace' );
$username = $this->opts->consumeValue( 'username' );
$tagFilterVal = $this->opts->consumeValue( 'tagfilter' );
+ $nsinvert = $this->opts->consumeValue( 'invert' );
// Check username input validity
$ut = Title::makeTitleSafe( NS_USER, $username );
@@ -248,6 +248,13 @@ class SpecialNewpages extends IncludableSpecialPage {
'id' => 'namespace',
'class' => 'namespaceselector',
)
+ ) . '&#160;' .
+ Xml::checkLabel(
+ $this->msg( 'invert' )->text(),
+ 'invert',
+ 'nsinvert',
+ $nsinvert,
+ array( 'title' => $this->msg( 'tooltip-invert' )->text() )
) .
'</td>
</tr>' . ( $tagFilter ? (
@@ -298,11 +305,11 @@ class SpecialNewpages extends IncludableSpecialPage {
# Revision deletion works on revisions, so we should cast one
$row = array(
- 'comment' => $result->rc_comment,
- 'deleted' => $result->rc_deleted,
- 'user_text' => $result->rc_user_text,
- 'user' => $result->rc_user,
- );
+ 'comment' => $result->rc_comment,
+ 'deleted' => $result->rc_deleted,
+ 'user_text' => $result->rc_user_text,
+ 'user' => $result->rc_user,
+ );
$rev = new Revision( $row );
$rev->setTitle( $title );
@@ -328,12 +335,13 @@ class SpecialNewpages extends IncludableSpecialPage {
$query['rcid'] = $result->rc_id;
}
- $plink = Linker::linkKnown(
+ // Linker::linkKnown() uses 'known' and 'noclasses' options. This breaks the colouration for stubs.
+ $plink = Linker::link(
$title,
null,
array( 'class' => 'mw-newpages-pagename' ),
$query,
- array( 'known' ) // Set explicitly to avoid the default of 'known','noclasses'. This breaks the colouration for stubs
+ array( 'known' )
);
$histLink = Linker::linkKnown(
$title,
@@ -459,14 +467,19 @@ class SpecialNewpages extends IncludableSpecialPage {
protected function feedItemDesc( $row ) {
$revision = Revision::newFromId( $row->rev_id );
if( $revision ) {
+ //XXX: include content model/type in feed item?
return '<p>' . htmlspecialchars( $revision->getUserText() ) .
$this->msg( 'colon-separator' )->inContentLanguage()->escaped() .
htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) .
"</p>\n<hr />\n<div>" .
- nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>";
+ nl2br( htmlspecialchars( $revision->getContent()->serialize() ) ) . "</div>";
}
return '';
}
+
+ protected function getGroupName() {
+ return 'changes';
+ }
}
/**
@@ -488,7 +501,7 @@ class NewPagesPager extends ReverseChronologicalPager {
}
function getQueryInfo() {
- global $wgEnableNewpagesUserFilter, $wgGroupPermissions;
+ global $wgEnableNewpagesUserFilter;
$conds = array();
$conds['rc_new'] = 1;
@@ -499,7 +512,11 @@ class NewPagesPager extends ReverseChronologicalPager {
$user = Title::makeTitleSafe( NS_USER, $username );
if( $namespace !== false ) {
- $conds['rc_namespace'] = $namespace;
+ if ( $this->opts->getValue( 'invert' ) ) {
+ $conds[] = 'rc_namespace != ' . $this->mDb->addQuotes( $namespace );
+ } else {
+ $conds['rc_namespace'] = $namespace;
+ }
$rcIndexes = array( 'new_name_timestamp' );
} else {
$rcIndexes = array( 'rc_timestamp' );
@@ -510,7 +527,7 @@ class NewPagesPager extends ReverseChronologicalPager {
$conds['rc_user_text'] = $user->getText();
$rcIndexes = 'rc_user_text';
# If anons cannot make new pages, don't "exclude logged in users"!
- } elseif( $wgGroupPermissions['*']['createpage'] && $this->opts->getValue( 'hideliu' ) ) {
+ } elseif( User::groupHasPermission( '*', 'createpage' ) && $this->opts->getValue( 'hideliu' ) ) {
$conds['rc_user'] = 0;
}
# If this user cannot see patrolled edits or they are off, don't do dumb queries!
diff --git a/includes/specials/SpecialPagesWithProp.php b/includes/specials/SpecialPagesWithProp.php
new file mode 100644
index 00000000..8f8c981e
--- /dev/null
+++ b/includes/specials/SpecialPagesWithProp.php
@@ -0,0 +1,138 @@
+<?php
+/**
+ * Implements Special:PagesWithProp
+ *
+ * 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
+ *
+ * @since 1.21
+ * @file
+ * @ingroup SpecialPage
+ * @author Brad Jorsch
+ */
+
+
+/**
+ * Special:PagesWithProp to search the page_props table
+ * @ingroup SpecialPage
+ * @since 1.21
+ */
+class SpecialPagesWithProp extends QueryPage {
+ private $propName = null;
+
+ function __construct( $name = 'PagesWithProp' ) {
+ parent::__construct( $name );
+ }
+
+ function isCacheable() {
+ return false;
+ }
+
+ function execute( $par ) {
+ $this->setHeaders();
+ $this->outputHeader();
+
+ $request = $this->getRequest();
+ $propname = $request->getVal( 'propname', $par );
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select(
+ 'page_props',
+ 'pp_propname',
+ '',
+ __METHOD__,
+ array( 'DISTINCT', 'ORDER BY' => 'pp_propname' )
+ );
+ foreach ( $res as $row ) {
+ $propnames[$row->pp_propname] = $row->pp_propname;
+ }
+
+ $form = new HTMLForm( array(
+ 'propname' => array(
+ 'type' => 'selectorother',
+ 'name' => 'propname',
+ 'options' => $propnames,
+ 'default' => $propname,
+ 'label-message' => 'pageswithprop-prop',
+ 'required' => true,
+ ),
+ ), $this->getContext() );
+ $form->setMethod( 'get' );
+ $form->setAction( $this->getTitle()->getFullUrl() );
+ $form->setSubmitCallback( array( $this, 'onSubmit' ) );
+ $form->setWrapperLegend( $this->msg( 'pageswithprop-legend' ) );
+ $form->addHeaderText( $this->msg( 'pageswithprop-text' )->parseAsBlock() );
+ $form->setSubmitTextMsg( 'pageswithprop-submit' );
+
+ $form->prepareForm();
+ $form->displayForm( false );
+ if ( $propname !== '' && $propname !== null ) {
+ $form->trySubmit();
+ }
+ }
+
+ public function onSubmit( $data, $form ) {
+ $this->propName = $data['propname'];
+ parent::execute( $data['propname'] );
+ }
+
+ /**
+ * Disable RSS/Atom feeds
+ * @return bool
+ */
+ function isSyndicated() {
+ return false;
+ }
+
+ function getQueryInfo() {
+ return array(
+ 'tables' => array( 'page_props', 'page' ),
+ 'fields' => array(
+ 'page_id' => 'pp_page',
+ 'page_namespace',
+ 'page_title',
+ 'page_len',
+ 'page_is_redirect',
+ 'page_latest',
+ 'pp_value',
+ ),
+ 'conds' => array(
+ 'page_id = pp_page',
+ 'pp_propname' => $this->propName,
+ ),
+ 'options' => array()
+ );
+ }
+
+ function getOrderFields() {
+ return array( 'page_id' );
+ }
+
+ function formatResult( $skin, $result ) {
+ $title = Title::newFromRow( $result );
+ $ret = Linker::link( $title, null, array(), array(), array( 'known' ) );
+ if ( $result->pp_value !== '' ) {
+ $value = $this->msg( 'parentheses' )
+ ->rawParams( Xml::span( $result->pp_value, 'prop-value' ) )
+ ->escaped();
+ $ret .= " $value";
+ }
+ return $ret;
+ }
+
+ protected function getGroupName() {
+ return 'pages';
+ }
+}
diff --git a/includes/specials/SpecialPasswordReset.php b/includes/specials/SpecialPasswordReset.php
index efb57657..90b0ac80 100644
--- a/includes/specials/SpecialPasswordReset.php
+++ b/includes/specials/SpecialPasswordReset.php
@@ -86,7 +86,7 @@ class SpecialPasswordReset extends FormSpecialPage {
);
}
- if( $this->getUser()->isAllowed( 'passwordreset' ) ){
+ if( $this->getUser()->isAllowed( 'passwordreset' ) ) {
$a['Capture'] = array(
'type' => 'check',
'label-message' => 'passwordreset-capture',
@@ -121,6 +121,8 @@ class SpecialPasswordReset extends FormSpecialPage {
* userCanExecute(), and if the data array contains 'Username', etc, then Username
* resets are allowed.
* @param $data array
+ * @throws MWException
+ * @throws ThrottledError|PermissionsError
* @return Bool|Array
*/
public function onSubmit( array $data ) {
@@ -134,7 +136,7 @@ class SpecialPasswordReset extends FormSpecialPage {
}
}
- if( isset( $data['Capture'] ) && !$this->getUser()->isAllowed( 'passwordreset' ) ){
+ if( isset( $data['Capture'] ) && !$this->getUser()->isAllowed( 'passwordreset' ) ) {
// The user knows they don't have the passwordreset permission, but they tried to spoof the form. That's naughty
throw new PermissionsError( 'passwordreset' );
}
@@ -160,7 +162,7 @@ class SpecialPasswordReset extends FormSpecialPage {
);
if ( $res ) {
$users = array();
- foreach( $res as $row ){
+ foreach( $res as $row ) {
$users[] = User::newFromRow( $row );
}
} else {
@@ -178,8 +180,8 @@ class SpecialPasswordReset extends FormSpecialPage {
return array( $error );
}
- if( count( $users ) == 0 ){
- if( $method == 'email' ){
+ if( count( $users ) == 0 ) {
+ if( $method == 'email' ) {
// Don't reveal whether or not an email address is in use
return true;
} else {
@@ -247,13 +249,13 @@ class SpecialPasswordReset extends FormSpecialPage {
$username,
$passwordBlock,
count( $passwords ),
- Title::newMainPage()->getCanonicalUrl(),
+ '<' . Title::newMainPage()->getCanonicalUrl() . '>',
round( $wgNewPasswordExpiry / 86400 )
);
$title = $this->msg( 'passwordreset-emailtitle' );
- $this->result = $firstUser->sendMail( $title->escaped(), $this->email->escaped() );
+ $this->result = $firstUser->sendMail( $title->escaped(), $this->email->text() );
// Blank the email if the user is not supposed to see it
if( !isset( $data['Capture'] ) || !$data['Capture'] ) {
@@ -262,7 +264,7 @@ class SpecialPasswordReset extends FormSpecialPage {
if ( $this->result->isGood() ) {
return true;
- } elseif( isset( $data['Capture'] ) && $data['Capture'] ){
+ } elseif( isset( $data['Capture'] ) && $data['Capture'] ) {
// The email didn't send, but maybe they knew that and that's why they captured it
return true;
} else {
@@ -273,10 +275,10 @@ class SpecialPasswordReset extends FormSpecialPage {
}
public function onSuccess() {
- if( $this->getUser()->isAllowed( 'passwordreset' ) && $this->email != null ){
+ if( $this->getUser()->isAllowed( 'passwordreset' ) && $this->email != null ) {
// @todo: Logging
- if( $this->result->isGood() ){
+ if( $this->result->isGood() ) {
$this->getOutput()->addWikiMsg( 'passwordreset-emailsent-capture' );
} else {
$this->getOutput()->addWikiMsg( 'passwordreset-emailerror-capture', $this->result->getMessage() );
@@ -324,4 +326,8 @@ class SpecialPasswordReset extends FormSpecialPage {
return false;
}
+
+ protected function getGroupName() {
+ return 'users';
+ }
}
diff --git a/includes/specials/SpecialPopularpages.php b/includes/specials/SpecialPopularpages.php
index 448d1799..7ce8c13f 100644
--- a/includes/specials/SpecialPopularpages.php
+++ b/includes/specials/SpecialPopularpages.php
@@ -37,14 +37,16 @@ class PopularPagesPage extends QueryPage {
return true;
}
- function isSyndicated() { return false; }
+ function isSyndicated() {
+ return false;
+ }
function getQueryInfo() {
return array (
'tables' => array( 'page' ),
'fields' => array( 'namespace' => 'page_namespace',
'title' => 'page_title',
- 'value' => 'page_counter'),
+ 'value' => 'page_counter' ),
'conds' => array( 'page_is_redirect' => 0,
'page_namespace' => MWNamespace::getContentNamespaces() ) );
}
@@ -70,4 +72,8 @@ class PopularPagesPage extends QueryPage {
$nv = $this->msg( 'nviews' )->numParams( $result->value )->escaped();
return $this->getLanguage()->specialList( $link, $nv );
}
+
+ protected function getGroupName() {
+ return 'wiki';
+ }
}
diff --git a/includes/specials/SpecialPreferences.php b/includes/specials/SpecialPreferences.php
index c6b2bb6b..a50e7c18 100644
--- a/includes/specials/SpecialPreferences.php
+++ b/includes/specials/SpecialPreferences.php
@@ -78,7 +78,7 @@ class SpecialPreferences extends SpecialPage {
public function submitReset( $formData ) {
$user = $this->getUser();
- $user->resetOptions();
+ $user->resetOptions( 'all' );
$user->saveSettings();
$url = $this->getTitle()->getFullURL( 'success' );
@@ -87,4 +87,8 @@ class SpecialPreferences extends SpecialPage {
return true;
}
+
+ protected function getGroupName() {
+ return 'users';
+ }
}
diff --git a/includes/specials/SpecialPrefixindex.php b/includes/specials/SpecialPrefixindex.php
index 7740b320..6affa735 100644
--- a/includes/specials/SpecialPrefixindex.php
+++ b/includes/specials/SpecialPrefixindex.php
@@ -29,13 +29,13 @@
class SpecialPrefixindex extends SpecialAllpages {
// Inherit $maxPerPage
- function __construct(){
+ function __construct() {
parent::__construct( 'Prefixindex' );
}
/**
* Entry point : initialise variables and call subfunctions.
- * @param $par String: becomes "FOO" when called like Special:Prefixindex/FOO (default null)
+ * @param string $par becomes "FOO" when called like Special:Prefixindex/FOO (default null)
*/
function execute( $par ) {
global $wgContLang;
@@ -83,14 +83,14 @@ class SpecialPrefixindex extends SpecialAllpages {
/**
* HTML for the top form
* @param $namespace Integer: a namespace constant (default NS_MAIN).
- * @param $from String: dbKey we are starting listing at.
- * @param $hideredirects Bool: hide redirects (default FALSE)
+ * @param string $from dbKey we are starting listing at.
+ * @param bool $hideredirects hide redirects (default FALSE)
* @return string
*/
function namespacePrefixForm( $namespace = NS_MAIN, $from = '', $hideredirects = false ) {
global $wgScript;
- $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) );
+ $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) );
$out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
$out .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() );
$out .= Xml::openElement( 'fieldset' );
@@ -101,7 +101,7 @@ class SpecialPrefixindex extends SpecialAllpages {
Xml::label( $this->msg( 'allpagesprefix' )->text(), 'nsfrom' ) .
"</td>
<td class='mw-input'>" .
- Xml::input( 'prefix', 30, str_replace('_',' ',$from), array( 'id' => 'nsfrom' ) ) .
+ Xml::input( 'prefix', 30, str_replace( '_', ' ', $from ), array( 'id' => 'nsfrom' ) ) .
"</td>
</tr>
<tr>
@@ -135,8 +135,8 @@ class SpecialPrefixindex extends SpecialAllpages {
/**
* @param $namespace Integer, default NS_MAIN
* @param $prefix String
- * @param $from String: list all pages from this name (default FALSE)
- * @param $hideredirects Bool: hide redirects (default FALSE)
+ * @param string $from list all pages from this name (default FALSE)
+ * @param bool $hideredirects hide redirects (default FALSE)
*/
function showPrefixChunk( $namespace = NS_MAIN, $prefix, $from = null, $hideredirects = false ) {
global $wgContLang;
@@ -145,8 +145,8 @@ class SpecialPrefixindex extends SpecialAllpages {
$from = $prefix;
}
- $fromList = $this->getNamespaceKeyAndText($namespace, $from);
- $prefixList = $this->getNamespaceKeyAndText($namespace, $prefix);
+ $fromList = $this->getNamespaceKeyAndText( $namespace, $from );
+ $prefixList = $this->getNamespaceKeyAndText( $namespace, $prefix );
$namespaces = $wgContLang->getNamespaces();
if ( !$prefixList || !$fromList ) {
@@ -227,7 +227,7 @@ class SpecialPrefixindex extends SpecialAllpages {
} else {
$nsForm = $this->namespacePrefixForm( $namespace, $prefix, $hideredirects );
$self = $this->getTitle();
- $out2 = Xml::openElement( 'table', array( 'id' => 'mw-prefixindex-nav-table' ) ) .
+ $out2 = Xml::openElement( 'table', array( 'id' => 'mw-prefixindex-nav-table' ) ) .
'<tr>
<td>' .
$nsForm .
@@ -241,14 +241,14 @@ class SpecialPrefixindex extends SpecialAllpages {
'hideredirects' => $hideredirects,
);
- if( $namespace || ($prefix == '')) {
+ if( $namespace || $prefix == '' ) {
// Keep the namespace even if it's 0 for empty prefixes.
// This tells us we're not just a holdover from old links.
$query['namespace'] = $namespace;
}
$nextLink = Linker::linkKnown(
$self,
- $this->msg( 'nextpage', str_replace( '_',' ', $s->page_title ) )->escaped(),
+ $this->msg( 'nextpage', str_replace( '_', ' ', $s->page_title ) )->escaped(),
array(),
$query
);
@@ -263,4 +263,8 @@ class SpecialPrefixindex extends SpecialAllpages {
$this->getOutput()->addHTML( $out2 . $out . $footer );
}
+
+ protected function getGroupName() {
+ return 'pages';
+ }
}
diff --git a/includes/specials/SpecialProtectedpages.php b/includes/specials/SpecialProtectedpages.php
index 74ed5378..cdf053ec 100644
--- a/includes/specials/SpecialProtectedpages.php
+++ b/includes/specials/SpecialProtectedpages.php
@@ -29,7 +29,7 @@
class SpecialProtectedpages extends SpecialPage {
protected $IdLevel = 'level';
- protected $IdType = 'type';
+ protected $IdType = 'type';
public function __construct() {
parent::__construct( 'Protectedpages' );
@@ -51,7 +51,7 @@ class SpecialProtectedpages extends SpecialPage {
$size = $request->getIntOrNull( 'size' );
$NS = $request->getIntOrNull( 'namespace' );
$indefOnly = $request->getBool( 'indefonly' ) ? 1 : 0;
- $cascadeOnly = $request->getBool('cascadeonly') ? 1 : 0;
+ $cascadeOnly = $request->getBool( 'cascadeonly' ) ? 1 : 0;
$pager = new ProtectedPagesPager( $this, array(), $type, $level, $NS, $sizetype, $size, $indefOnly, $cascadeOnly );
@@ -78,11 +78,17 @@ class SpecialProtectedpages extends SpecialPage {
static $infinity = null;
- if( is_null( $infinity ) ){
+ if( is_null( $infinity ) ) {
$infinity = wfGetDB( DB_SLAVE )->getInfinity();
}
$title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
+ if( !$title ) {
+ return Html::rawElement( 'li', array(),
+ Html::element( 'span', array( 'class' => 'mw-invalidtitle' ),
+ Linker::getInvalidTitleDescription( $this->getContext(), $row->page_namespace, $row->page_title ) ) ) . "\n";
+ }
+
$link = Linker::link( $title );
$description_items = array ();
@@ -109,7 +115,7 @@ class SpecialProtectedpages extends SpecialPage {
)->escaped();
}
- if(!is_null($size = $row->page_len)) {
+ if( !is_null( $size = $row->page_len ) ) {
$stxt = $lang->getDirMark() . ' ' . Linker::formatRevisionSize( $size );
}
@@ -146,15 +152,15 @@ class SpecialProtectedpages extends SpecialPage {
/**
* @param $namespace Integer
- * @param $type String: restriction type
- * @param $level String: restriction level
- * @param $sizetype String: "min" or "max"
+ * @param string $type restriction type
+ * @param string $level restriction level
+ * @param string $sizetype "min" or "max"
* @param $size Integer
* @param $indefOnly Boolean: only indefinie protection
* @param $cascadeOnly Boolean: only cascading protection
* @return String: input form
*/
- protected function showOptions( $namespace, $type='edit', $level, $sizetype, $size, $indefOnly, $cascadeOnly ) {
+ protected function showOptions( $namespace, $type = 'edit', $level, $sizetype, $size, $indefOnly, $cascadeOnly ) {
global $wgScript;
$title = $this->getTitle();
return Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) .
@@ -272,7 +278,7 @@ class SpecialProtectedpages extends SpecialPage {
// First pass to load the log names
foreach( $wgRestrictionLevels as $type ) {
// Messages used can be 'restriction-level-sysop' and 'restriction-level-autoconfirmed'
- if( $type !='' && $type !='*') {
+ if( $type != '' && $type != '*' ) {
$text = $this->msg( "restriction-level-$type" )->text();
$m[$text] = $type;
}
@@ -290,6 +296,10 @@ class SpecialProtectedpages extends SpecialPage {
array( 'id' => $this->IdLevel, 'name' => $this->IdLevel ),
implode( "\n", $options ) ) . "</span>";
}
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
}
/**
@@ -300,7 +310,7 @@ class ProtectedPagesPager extends AlphabeticPager {
public $mForm, $mConds;
private $type, $level, $namespace, $sizetype, $size, $indefonly;
- function __construct( $form, $conds = array(), $type, $level, $namespace, $sizetype='', $size=0,
+ function __construct( $form, $conds = array(), $type, $level, $namespace, $sizetype = '', $size = 0,
$indefonly = false, $cascadeonly = false )
{
$this->mForm = $form;
@@ -309,7 +319,7 @@ class ProtectedPagesPager extends AlphabeticPager {
$this->level = $level;
$this->namespace = $namespace;
$this->sizetype = $sizetype;
- $this->size = intval($size);
+ $this->size = intval( $size );
$this->indefonly = (bool)$indefonly;
$this->cascadeonly = (bool)$cascadeonly;
parent::__construct( $form->getContext() );
@@ -336,27 +346,27 @@ class ProtectedPagesPager extends AlphabeticPager {
$conds[] = 'page_id=pr_page';
$conds[] = 'pr_type=' . $this->mDb->addQuotes( $this->type );
- if( $this->sizetype=='min' ) {
+ if( $this->sizetype == 'min' ) {
$conds[] = 'page_len>=' . $this->size;
- } elseif( $this->sizetype=='max' ) {
+ } elseif( $this->sizetype == 'max' ) {
$conds[] = 'page_len<=' . $this->size;
}
if( $this->indefonly ) {
- $db = wfGetDB( DB_SLAVE );
- $conds[] = "pr_expiry = {$db->addQuotes( $db->getInfinity() )} OR pr_expiry IS NULL";
+ $conds[] = "pr_expiry = {$this->mDb->addQuotes( $this->mDb->getInfinity() )} OR pr_expiry IS NULL";
}
if( $this->cascadeonly ) {
- $conds[] = "pr_cascade = '1'";
+ $conds[] = 'pr_cascade = 1';
}
if( $this->level )
$conds[] = 'pr_level=' . $this->mDb->addQuotes( $this->level );
- if( !is_null($this->namespace) )
+ if( !is_null( $this->namespace ) )
$conds[] = 'page_namespace=' . $this->mDb->addQuotes( $this->namespace );
return array(
'tables' => array( 'page_restrictions', 'page' ),
- 'fields' => 'pr_id,page_namespace,page_title,page_len,pr_type,pr_level,pr_expiry,pr_cascade',
+ 'fields' => array( 'pr_id', 'page_namespace', 'page_title', 'page_len',
+ 'pr_type', 'pr_level', 'pr_expiry', 'pr_cascade' ),
'conds' => $conds
);
}
diff --git a/includes/specials/SpecialProtectedtitles.php b/includes/specials/SpecialProtectedtitles.php
index a80f0d0a..0cba5ebd 100644
--- a/includes/specials/SpecialProtectedtitles.php
+++ b/includes/specials/SpecialProtectedtitles.php
@@ -29,7 +29,7 @@
class SpecialProtectedtitles extends SpecialPage {
protected $IdLevel = 'level';
- protected $IdType = 'type';
+ protected $IdType = 'type';
public function __construct() {
parent::__construct( 'Protectedtitles' );
@@ -76,11 +76,17 @@ class SpecialProtectedtitles extends SpecialPage {
static $infinity = null;
- if( is_null( $infinity ) ){
+ if( is_null( $infinity ) ) {
$infinity = wfGetDB( DB_SLAVE )->getInfinity();
}
$title = Title::makeTitleSafe( $row->pt_namespace, $row->pt_title );
+ if( !$title ) {
+ return Html::rawElement( 'li', array(),
+ Html::element( 'span', array( 'class' => 'mw-invalidtitle' ),
+ Linker::getInvalidTitleDescription( $this->getContext(), $row->pt_namespace, $row->pt_title ) ) ) . "\n";
+ }
+
$link = Linker::link( $title );
$description_items = array ();
@@ -113,7 +119,7 @@ class SpecialProtectedtitles extends SpecialPage {
* @return string
* @private
*/
- function showOptions( $namespace, $type='edit', $level ) {
+ function showOptions( $namespace, $type = 'edit', $level ) {
global $wgScript;
$action = htmlspecialchars( $wgScript );
$title = $this->getTitle();
@@ -161,13 +167,13 @@ class SpecialProtectedtitles extends SpecialPage {
// First pass to load the log names
foreach( $wgRestrictionLevels as $type ) {
- if ( $type !='' && $type !='*') {
+ if ( $type != '' && $type != '*' ) {
$text = $this->msg( "restriction-level-$type" )->text();
$m[$text] = $type;
}
}
// Is there only one level (aside from "all")?
- if( count($m) <= 2 ) {
+ if( count( $m ) <= 2 ) {
return '';
}
// Third pass generates sorted XHTML content
@@ -182,6 +188,10 @@ class SpecialProtectedtitles extends SpecialPage {
array( 'id' => $this->IdLevel, 'name' => $this->IdLevel ),
implode( "\n", $options ) );
}
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
}
/**
@@ -191,7 +201,7 @@ class SpecialProtectedtitles extends SpecialPage {
class ProtectedTitlesPager extends AlphabeticPager {
public $mForm, $mConds;
- function __construct( $form, $conds = array(), $type, $level, $namespace, $sizetype='', $size=0 ) {
+ function __construct( $form, $conds = array(), $type, $level, $namespace, $sizetype = '', $size = 0 ) {
$this->mForm = $form;
$this->mConds = $conds;
$this->level = $level;
@@ -234,11 +244,12 @@ class ProtectedTitlesPager extends AlphabeticPager {
$conds[] = 'pt_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() );
if( $this->level )
$conds['pt_create_perm'] = $this->level;
- if( !is_null($this->namespace) )
+ if( !is_null( $this->namespace ) )
$conds[] = 'pt_namespace=' . $this->mDb->addQuotes( $this->namespace );
return array(
'tables' => 'protected_titles',
- 'fields' => 'pt_namespace,pt_title,pt_create_perm,pt_expiry,pt_timestamp',
+ 'fields' => array( 'pt_namespace', 'pt_title', 'pt_create_perm',
+ 'pt_expiry', 'pt_timestamp' ),
'conds' => $conds
);
}
@@ -247,4 +258,3 @@ class ProtectedTitlesPager extends AlphabeticPager {
return 'pt_timestamp';
}
}
-
diff --git a/includes/specials/SpecialRandompage.php b/includes/specials/SpecialRandompage.php
index 307088ed..b59f8349 100644
--- a/includes/specials/SpecialRandompage.php
+++ b/includes/specials/SpecialRandompage.php
@@ -28,11 +28,11 @@
* @ingroup SpecialPage
*/
class RandomPage extends SpecialPage {
- private $namespaces; // namespaces to select pages from
+ private $namespaces; // namespaces to select pages from
protected $isRedir = false; // should the result be a redirect?
protected $extra = array(); // Extra SQL statements
- public function __construct( $name = 'Randompage' ){
+ public function __construct( $name = 'Randompage' ) {
$this->namespaces = MWNamespace::getContentNamespaces();
parent::__construct( $name );
}
@@ -49,7 +49,7 @@ class RandomPage extends SpecialPage {
}
// select redirects instead of normal pages?
- public function isRedirect(){
+ public function isRedirect() {
return $this->isRedir;
}
@@ -159,4 +159,8 @@ class RandomPage extends SpecialPage {
return $dbr->fetchObject( $res );
}
+
+ protected function getGroupName() {
+ return 'redirects';
+ }
}
diff --git a/includes/specials/SpecialRandomredirect.php b/includes/specials/SpecialRandomredirect.php
index 88c81b31..51783a2f 100644
--- a/includes/specials/SpecialRandomredirect.php
+++ b/includes/specials/SpecialRandomredirect.php
@@ -28,7 +28,7 @@
* @ingroup SpecialPage
*/
class SpecialRandomredirect extends RandomPage {
- function __construct(){
+ function __construct() {
parent::__construct( 'Randomredirect' );
$this->isRedir = true;
}
diff --git a/includes/specials/SpecialRecentchanges.php b/includes/specials/SpecialRecentchanges.php
index 2bd8b0a9..008678f7 100644
--- a/includes/specials/SpecialRecentchanges.php
+++ b/includes/specials/SpecialRecentchanges.php
@@ -42,16 +42,16 @@ class SpecialRecentChanges extends IncludableSpecialPage {
public function getDefaultOptions() {
$opts = new FormOptions();
- $opts->add( 'days', (int)$this->getUser()->getOption( 'rcdays' ) );
- $opts->add( 'limit', (int)$this->getUser()->getOption( 'rclimit' ) );
+ $opts->add( 'days', $this->getUser()->getIntOption( 'rcdays' ) );
+ $opts->add( 'limit', $this->getUser()->getIntOption( 'rclimit' ) );
$opts->add( 'from', '' );
- $opts->add( 'hideminor', $this->getUser()->getBoolOption( 'hideminor' ) );
- $opts->add( 'hidebots', true );
- $opts->add( 'hideanons', false );
- $opts->add( 'hideliu', false );
+ $opts->add( 'hideminor', $this->getUser()->getBoolOption( 'hideminor' ) );
+ $opts->add( 'hidebots', true );
+ $opts->add( 'hideanons', false );
+ $opts->add( 'hideliu', false );
$opts->add( 'hidepatrolled', $this->getUser()->getBoolOption( 'hidepatrolled' ) );
- $opts->add( 'hidemyself', false );
+ $opts->add( 'hidemyself', false );
$opts->add( 'namespace', '', FormOptions::INTNULL );
$opts->add( 'invert', false );
@@ -109,8 +109,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
public function feedSetup() {
global $wgFeedLimit;
$opts = $this->getDefaultOptions();
- # Feed is cached on limit,hideminor,namespace; other params would randomly not work
- $opts->fetchValuesFromRequest( $this->getRequest(), array( 'limit', 'hideminor', 'namespace' ) );
+ $opts->fetchValuesFromRequest( $this->getRequest() );
$opts->validateIntBounds( 'limit', 0, $wgFeedLimit );
return $opts;
}
@@ -130,7 +129,6 @@ class SpecialRecentChanges extends IncludableSpecialPage {
return $this->rcOptions;
}
-
/**
* Main execution point
*
@@ -156,7 +154,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
// Fetch results, prepare a batch link existence check query
$conds = $this->buildMainQueryConds( $opts );
$rows = $this->doMainQuery( $conds, $opts );
- if( $rows === false ){
+ if( $rows === false ) {
if( !$this->including() ) {
$this->doHeader( $opts );
}
@@ -187,7 +185,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
*
* @return Array
*/
- public function getFeedObject( $feedFormat ){
+ public function getFeedObject( $feedFormat ) {
$changesFeed = new ChangesFeed( $feedFormat, 'rcfeed' );
$formatter = $changesFeed->getFeedObject(
$this->msg( 'recentchanges' )->inContentLanguage()->text(),
@@ -233,7 +231,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
if( is_numeric( $bit ) ) {
- $opts['limit'] = $bit;
+ $opts['limit'] = $bit;
}
$m = array();
@@ -282,9 +280,9 @@ class SpecialRecentChanges extends IncludableSpecialPage {
# It makes no sense to hide both anons and logged-in users
# Where this occurs, force anons to be shown
$forcebot = false;
- if( $opts['hideanons'] && $opts['hideliu'] ){
+ if( $opts['hideanons'] && $opts['hideliu'] ) {
# Check if the user wants to show bots only
- if( $opts['hidebots'] ){
+ if( $opts['hidebots'] ) {
$opts['hideanons'] = false;
} else {
$forcebot = true;
@@ -297,9 +295,9 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$cutoff_unixtime = $cutoff_unixtime - ($cutoff_unixtime % 86400);
$cutoff = $dbr->timestamp( $cutoff_unixtime );
- $fromValid = preg_match('/^[0-9]{14}$/', $opts['from']);
- if( $fromValid && $opts['from'] > wfTimestamp(TS_MW,$cutoff) ) {
- $cutoff = $dbr->timestamp($opts['from']);
+ $fromValid = preg_match( '/^[0-9]{14}$/', $opts['from'] );
+ if( $fromValid && $opts['from'] > wfTimestamp( TS_MW, $cutoff ) ) {
+ $cutoff = $dbr->timestamp( $opts['from'] );
} else {
$opts->reset( 'from' );
}
@@ -341,7 +339,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
if( $opts['namespace'] !== '' ) {
$selectedNS = $dbr->addQuotes( $opts['namespace'] );
$operator = $opts['invert'] ? '!=' : '=';
- $boolean = $opts['invert'] ? 'AND' : 'OR';
+ $boolean = $opts['invert'] ? 'AND' : 'OR';
# namespace association (bug 2429)
if( !$opts['associated'] ) {
@@ -352,8 +350,8 @@ class SpecialRecentChanges extends IncludableSpecialPage {
MWNamespace::getAssociated( $opts['namespace'] )
);
$condition = "(rc_namespace $operator $selectedNS "
- . $boolean
- . " rc_namespace $operator $associatedNS)";
+ . $boolean
+ . " rc_namespace $operator $associatedNS)";
}
$conds[] = $condition;
@@ -382,19 +380,22 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$invert = $opts['invert'];
$associated = $opts['associated'];
- $fields = array( $dbr->tableName( 'recentchanges' ) . '.*' ); // all rc columns
+ $fields = RecentChange::selectFields();
// JOIN on watchlist for users
if ( $uid ) {
$tables[] = 'watchlist';
$fields[] = 'wl_user';
$fields[] = 'wl_notificationtimestamp';
- $join_conds['watchlist'] = array('LEFT JOIN',
- "wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace");
+ $join_conds['watchlist'] = array( 'LEFT JOIN', array(
+ 'wl_user' => $uid,
+ 'wl_title=rc_title',
+ 'wl_namespace=rc_namespace'
+ ));
}
if ( $this->getUser()->isAllowed( 'rollback' ) ) {
$tables[] = 'page';
$fields[] = 'page_latest';
- $join_conds['page'] = array('LEFT JOIN', 'rc_cur_id=page_id');
+ $join_conds['page'] = array( 'LEFT JOIN', 'rc_cur_id=page_id' );
}
// Tag stuff.
ChangeTags::modifyDisplayQuery(
@@ -467,7 +468,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
/**
* Send output to the OutputPage object, only called if not used feeds
*
- * @param $rows Array of database rows
+ * @param array $rows of database rows
* @param $opts FormOptions
*/
public function webOutput( $rows, $opts ) {
@@ -481,7 +482,12 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
// And now for the content
- $this->getOutput()->setFeedAppendQuery( $this->getFeedQuery() );
+ $feedQuery = $this->getFeedQuery();
+ if ( $feedQuery !== '' ) {
+ $this->getOutput()->setFeedAppendQuery( $feedQuery );
+ } else {
+ $this->getOutput()->setFeedAppendQuery( false );
+ }
if( $wgAllowCategorizedRecentChanges ) {
$this->filterByCategories( $rows, $opts );
@@ -525,8 +531,12 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
$rc->numberofWatchingusers = $watcherCache[$obj->rc_namespace][$obj->rc_title];
}
- $s .= $list->recentChangesLine( $rc, !empty( $obj->wl_user ), $counter );
- --$limit;
+
+ $changeLine = $list->recentChangesLine( $rc, !empty( $obj->wl_user ), $counter );
+ if ( $changeLine !== false ) {
+ $s .= $changeLine;
+ --$limit;
+ }
}
$s .= $list->endRecentChangesList();
$this->getOutput()->addHTML( $s );
@@ -534,11 +544,24 @@ class SpecialRecentChanges extends IncludableSpecialPage {
/**
* Get the query string to append to feed link URLs.
- * This is overridden by RCL to add the target parameter
- * @return bool
+ *
+ * @return string
*/
public function getFeedQuery() {
- return false;
+ global $wgFeedLimit;
+
+ $this->getOptions()->validateIntBounds( 'limit', 0, $wgFeedLimit );
+ $options = $this->getOptions()->getChangedValues();
+
+ // wfArrayToCgi() omits options set to null or false
+ foreach ( $options as &$value ) {
+ if ( $value === false ) {
+ $value = '0';
+ }
+ }
+ unset( $value );
+
+ return wfArrayToCgi( $options );
}
/**
@@ -701,11 +724,11 @@ class SpecialRecentChanges extends IncludableSpecialPage {
/**
* Filter $rows by categories set in $opts
*
- * @param $rows Array of database rows
+ * @param array $rows of database rows
* @param $opts FormOptions
*/
function filterByCategories( &$rows, FormOptions $opts ) {
- $categories = array_map( 'trim', explode( '|' , $opts['categories'] ) );
+ $categories = array_map( 'trim', explode( '|', $opts['categories'] ) );
if( !count( $categories ) ) {
return;
@@ -766,8 +789,8 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* Makes change an option link which carries all the other options
*
* @param $title Title
- * @param $override Array: options to override
- * @param $options Array: current options
+ * @param array $override options to override
+ * @param array $options current options
* @param $active Boolean: whether to show the link in bold
* @return string
*/
@@ -839,7 +862,6 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
$dl = $lang->pipeList( $dl );
-
// show/hide links
$showhide = array( $this->msg( 'show' )->text(), $this->msg( 'hide' )->text() );
$filters = array(
@@ -885,4 +907,8 @@ class SpecialRecentChanges extends IncludableSpecialPage {
'mediawiki.special.recentchanges',
) );
}
+
+ protected function getGroupName() {
+ return 'changes';
+ }
}
diff --git a/includes/specials/SpecialRecentchangeslinked.php b/includes/specials/SpecialRecentchangeslinked.php
index 862736d3..391c4a7f 100644
--- a/includes/specials/SpecialRecentchangeslinked.php
+++ b/includes/specials/SpecialRecentchangeslinked.php
@@ -29,7 +29,7 @@
class SpecialRecentchangeslinked extends SpecialRecentChanges {
var $rclTargetTitle;
- function __construct(){
+ function __construct() {
parent::__construct( 'Recentchangeslinked' );
}
@@ -37,7 +37,6 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$opts = parent::getDefaultOptions();
$opts->add( 'target', '' );
$opts->add( 'showlinkedto', false );
- $opts->add( 'tagfilter', '' );
return $opts;
}
@@ -51,7 +50,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
return $opts;
}
- public function getFeedObject( $feedFormat ){
+ public function getFeedObject( $feedFormat ) {
$feed = new ChangesFeed( $feedFormat, false );
$feedObj = $feed->getFeedObject(
$this->msg( 'recentchangeslinked-title', $this->getTargetTitle()->getPrefixedText() )
@@ -72,7 +71,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
}
$outputPage = $this->getOutput();
$title = Title::newFromURL( $target );
- if( !$title || $title->getInterwiki() != '' ){
+ if( !$title || $title->getInterwiki() != '' ) {
$outputPage->wrapWikiMsg( "<div class=\"errorbox\">\n$1\n</div><br style=\"clear: both\" />", 'allpagesbadtitle' );
return false;
}
@@ -94,7 +93,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$dbkey = $title->getDBkey();
$tables = array( 'recentchanges' );
- $select = array( $dbr->tableName( 'recentchanges' ) . '.*' );
+ $select = RecentChange::selectFields();
$join_conds = array();
$query_options = array();
@@ -103,11 +102,15 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
if( $uid ) {
$tables[] = 'watchlist';
$select[] = 'wl_user';
- $join_conds['watchlist'] = array( 'LEFT JOIN', "wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace" );
+ $join_conds['watchlist'] = array( 'LEFT JOIN', array(
+ 'wl_user' => $uid,
+ 'wl_title=rc_title',
+ 'wl_namespace=rc_namespace'
+ ));
}
if ( $this->getUser()->isAllowed( 'rollback' ) ) {
$tables[] = 'page';
- $join_conds['page'] = array('LEFT JOIN', 'rc_cur_id=page_id');
+ $join_conds['page'] = array( 'LEFT JOIN', 'rc_cur_id=page_id' );
$select[] = 'page_latest';
}
ChangeTags::modifyDisplayQuery(
@@ -125,7 +128,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
if( $ns == NS_CATEGORY && !$showlinkedto ) {
// special handling for categories
- // XXX: should try to make this less klugy
+ // XXX: should try to make this less kludgy
$link_tables = array( 'categorylinks' );
$showlinkedto = true;
} else {
@@ -176,7 +179,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$subconds["rc_namespace"] = $link_ns;
$subjoin = "rc_title = {$pfx}_to";
} else {
- $subjoin = "rc_namespace = {$pfx}_namespace AND rc_title = {$pfx}_title";
+ $subjoin = array( "rc_namespace = {$pfx}_namespace", "rc_title = {$pfx}_title" );
}
}
@@ -201,15 +204,15 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$subsql[] = $query;
}
- if( count($subsql) == 0 ) {
+ if( count( $subsql ) == 0 ) {
return false; // should never happen
}
- if( count($subsql) == 1 && $dbr->unionSupportsOrderAndLimit() ) {
+ if( count( $subsql ) == 1 && $dbr->unionSupportsOrderAndLimit() ) {
$sql = $subsql[0];
} else {
// need to resort and relimit after union
- $sql = $dbr->unionQueries($subsql, false).' ORDER BY rc_timestamp DESC';
- $sql = $dbr->limitResult($sql, $limit, false);
+ $sql = $dbr->unionQueries( $subsql, false ) . ' ORDER BY rc_timestamp DESC';
+ $sql = $dbr->limitResult( $sql, $limit, false );
}
$res = $dbr->query( $sql, __METHOD__ );
@@ -225,16 +228,16 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
* @param $opts FormOptions
* @return array
*/
- function getExtraOptions( $opts ){
+ function getExtraOptions( $opts ) {
$opts->consumeValues( array( 'showlinkedto', 'target', 'tagfilter' ) );
$extraOpts = array();
$extraOpts['namespace'] = $this->namespaceFilterForm( $opts );
$extraOpts['target'] = array( $this->msg( 'recentchangeslinked-page' )->escaped(),
- Xml::input( 'target', 40, str_replace('_',' ',$opts['target']) ) .
- Xml::check( 'showlinkedto', $opts['showlinkedto'], array('id' => 'showlinkedto') ) . ' ' .
+ Xml::input( 'target', 40, str_replace( '_', ' ', $opts['target'] ) ) .
+ Xml::check( 'showlinkedto', $opts['showlinkedto'], array( 'id' => 'showlinkedto' ) ) . ' ' .
Xml::label( $this->msg( 'recentchangeslinked-to' )->text(), 'showlinkedto' ) );
$tagFilter = ChangeTags::buildTagFilterSelector( $opts['tagfilter'] );
- if ($tagFilter) {
+ if ( $tagFilter ) {
$extraOpts['tagfilter'] = $tagFilter;
}
return $extraOpts;
@@ -262,15 +265,6 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
}
}
- public function getFeedQuery() {
- $target = $this->getTargetTitle();
- if( $target ) {
- return "target=" . urlencode( $target->getPrefixedDBkey() );
- } else {
- return false;
- }
- }
-
function setBottomText( FormOptions $opts ) {
if( isset( $this->mResultEmpty ) && $this->mResultEmpty ) {
$this->getOutput()->addWikiMsg( 'recentchangeslinked-noresult' );
diff --git a/includes/specials/SpecialRevisiondelete.php b/includes/specials/SpecialRevisiondelete.php
index aba90cf8..5a5f8ffb 100644
--- a/includes/specials/SpecialRevisiondelete.php
+++ b/includes/specials/SpecialRevisiondelete.php
@@ -133,7 +133,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$this->ids = explode( ',', $ids );
} else {
# Array input
- $this->ids = array_keys( $request->getArray('ids',array()) );
+ $this->ids = array_keys( $request->getArray( 'ids', array() ) );
}
// $this->ids = array_map( 'intval', $this->ids );
$this->ids = array_unique( array_filter( $this->ids ) );
@@ -147,7 +147,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
} else {
$this->typeName = $request->getVal( 'type' );
$this->targetObj = Title::newFromText( $request->getText( 'target' ) );
- if ( $this->targetObj->isSpecial( 'Log' ) ) {
+ if ( $this->targetObj && $this->targetObj->isSpecial( 'Log' ) && count( $this->ids ) !== 0 ) {
$result = wfGetDB( DB_SLAVE )->select( 'logging',
'log_type',
array( 'log_id' => $this->ids ),
@@ -155,14 +155,9 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
array( 'DISTINCT' )
);
- $logTypes = array();
- foreach ( $result as $row ) {
- $logTypes[] = $row->log_type;
- }
-
- if ( count( $logTypes ) == 1 ) {
+ if ( $result->numRows() == 1 ) {
// If there's only one type, the target can be set to include it.
- $this->targetObj = SpecialPage::getTitleFor( 'Log', $logTypes[0] );
+ $this->targetObj = SpecialPage::getTitleFor( 'Log', $result->current()->log_type );
}
}
}
@@ -196,7 +191,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$this->otherReason = $request->getVal( 'wpReason' );
# We need a target page!
- if( is_null($this->targetObj) ) {
+ if( is_null( $this->targetObj ) ) {
$output->addWikiMsg( 'undelete-header' );
return;
}
@@ -209,7 +204,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
array( 'revdelete-hide-comment', 'wpHideComment', Revision::DELETED_COMMENT ),
array( 'revdelete-hide-user', 'wpHideUser', Revision::DELETED_USER )
);
- if( $user->isAllowed('suppressrevision') ) {
+ if( $user->isAllowed( 'suppressrevision' ) ) {
$this->checks[] = array( 'revdelete-hide-restricted',
'wpHideRestricted', Revision::DELETED_RESTRICTED );
}
@@ -230,7 +225,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
# Show relevant lines from the suppression log
if( $user->isAllowed( 'suppressionlog' ) ) {
$suppressLogPage = new LogPage( 'suppress' );
- $output->addHTML( "<h2>" . $suppressLogPage->getName()->escaped() . "</h2>\n" );
+ $output->addHTML( "<h2>" . $suppressLogPage->getName()->escaped() . "</h2>\n" );
LogEventsList::showLogExtract( $output, 'suppress',
$this->targetObj, '', array( 'lim' => 25, 'conds' => $qc ) );
}
@@ -258,7 +253,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
array( 'action' => 'history' )
);
# Link to deleted edits
- if( $this->getUser()->isAllowed('undelete') ) {
+ if( $this->getUser()->isAllowed( 'undelete' ) ) {
$undelete = SpecialPage::getTitleFor( 'Undelete' );
$links[] = Linker::linkKnown(
$undelete,
@@ -361,7 +356,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$UserAllowed = true;
if ( $this->typeName == 'logging' ) {
- $this->getOutput()->addWikiMsg( 'logdelete-selected', $this->getLanguage()->formatNum( count($this->ids) ) );
+ $this->getOutput()->addWikiMsg( 'logdelete-selected', $this->getLanguage()->formatNum( count( $this->ids ) ) );
} else {
$this->getOutput()->addWikiMsg( 'revdelete-selected',
$this->targetObj->getPrefixedText(), count( $this->ids ) );
@@ -469,8 +464,8 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
}
/**
- * @return String: HTML
- */
+ * @return String: HTML
+ */
protected function buildCheckBoxes() {
$html = '<table>';
// If there is just one item, use checkboxes
@@ -522,11 +517,12 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
/**
* UI entry point for form submission.
+ * @throws PermissionsError
* @return bool
*/
protected function submit() {
# Check edit token on submission
- $token = $this->getRequest()->getVal('wpEditToken');
+ $token = $this->getRequest()->getVal( 'wpEditToken' );
if( $this->submitClicked && !$this->getUser()->matchEditToken( $token ) ) {
$this->getOutput()->addWikiMsg( 'sessionfailure' );
return false;
@@ -541,7 +537,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$comment = $this->otherReason;
}
# Can the user set this field?
- if( $bitParams[Revision::DELETED_RESTRICTED]==1 && !$this->getUser()->isAllowed('suppressrevision') ) {
+ if( $bitParams[Revision::DELETED_RESTRICTED] == 1 && !$this->getUser()->isAllowed( 'suppressrevision' ) ) {
throw new PermissionsError( 'suppressrevision' );
}
# If the save went through, go to success message...
@@ -583,14 +579,14 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
protected function extractBitParams() {
$bitfield = array();
foreach( $this->checks as $item ) {
- list( /* message */ , $name, $field ) = $item;
+ list( /* message */, $name, $field ) = $item;
$val = $this->getRequest()->getInt( $name, 0 /* unchecked */ );
if( $val < -1 || $val > 1) {
$val = -1; // -1 for existing value
}
$bitfield[$field] = $val;
}
- if( !isset($bitfield[Revision::DELETED_RESTRICTED]) ) {
+ if( !isset( $bitfield[Revision::DELETED_RESTRICTED] ) ) {
$bitfield[Revision::DELETED_RESTRICTED] = 0;
}
return $bitfield;
@@ -598,8 +594,8 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
/**
* Put together a rev_deleted bitfield
- * @param $bitPars array extractBitParams() params
- * @param $oldfield int current bitfield
+ * @param array $bitPars extractBitParams() params
+ * @param int $oldfield current bitfield
* @return array
*/
public static function extractBitfield( $bitPars, $oldfield ) {
@@ -627,5 +623,8 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
array( 'value' => $bitfield, 'comment' => $reason )
);
}
-}
+ protected function getGroupName() {
+ return 'pagetools';
+ }
+}
diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php
index 5f5b6b4d..6c401486 100644
--- a/includes/specials/SpecialSearch.php
+++ b/includes/specials/SpecialSearch.php
@@ -78,7 +78,7 @@ class SpecialSearch extends SpecialPage {
/**
* Entry point
*
- * @param $par String or null
+ * @param string $par or null
*/
public function execute( $par ) {
$this->setHeaders();
@@ -138,7 +138,7 @@ class SpecialSearch extends SpecialPage {
// BC with old request format
$profile = 'advanced';
foreach( $profiles as $key => $data ) {
- if ( $nslist === $data['namespaces'] && $key !== 'advanced') {
+ if ( $nslist === $data['namespaces'] && $key !== 'advanced' ) {
$profile = $key;
}
}
@@ -159,7 +159,7 @@ class SpecialSearch extends SpecialPage {
$default = $request->getBool( 'profile' ) ? 0 : 1;
$this->searchRedirects = $request->getBool( 'redirs', $default ) ? 1 : 0;
$this->didYouMeanHtml = ''; # html of did you mean... link
- $this->fulltext = $request->getVal('fulltext');
+ $this->fulltext = $request->getVal( 'fulltext' );
$this->profile = $profile;
}
@@ -218,7 +218,7 @@ class SpecialSearch extends SpecialPage {
$search->showRedirects = $this->searchRedirects; // BC
$search->setFeatureData( 'list-redirects', $this->searchRedirects );
$search->prefix = $this->mPrefix;
- $term = $search->transformSearchTerm($term);
+ $term = $search->transformSearchTerm( $term );
wfRunHooks( 'SpecialSearchSetupEngine', array( $this, $this->profile, $search ) );
@@ -250,7 +250,7 @@ class SpecialSearch extends SpecialPage {
$t = Title::newFromText( $term );
// fetch search results
- $rewritten = $search->replacePrefixes($term);
+ $rewritten = $search->replacePrefixes( $term );
$titleMatches = $search->searchTitle( $rewritten );
if( !( $titleMatches instanceof SearchResultTooMany ) ) {
@@ -261,7 +261,7 @@ class SpecialSearch extends SpecialPage {
if( $textMatches && $textMatches->hasSuggestion() ) {
$st = SpecialPage::getTitleFor( 'Search' );
- # mirror Go/Search behaviour of original request ..
+ # mirror Go/Search behavior of original request ..
$didYouMeanParams = array( 'search' => $textMatches->getSuggestionQuery() );
if( $this->fulltext != null ) {
@@ -288,6 +288,13 @@ class SpecialSearch extends SpecialPage {
$this->didYouMeanHtml = '<div class="searchdidyoumean">' . $this->msg( 'search-suggest' )->rawParams( $suggestLink )->text() . '</div>';
}
+
+ if ( !wfRunHooks( 'SpecialSearchResultsPrepend', array( $this, $out, $term ) ) ) {
+ # Hook requested termination
+ wfProfileOut( __METHOD__ );
+ return;
+ }
+
// start rendering the page
$out->addHtml(
Xml::openElement(
@@ -304,9 +311,9 @@ class SpecialSearch extends SpecialPage {
Xml::openElement( 'tr' ) .
Xml::openElement( 'td' ) . "\n" .
$this->shortDialog( $term ) .
- Xml::closeElement('td') .
- Xml::closeElement('tr') .
- Xml::closeElement('table')
+ Xml::closeElement( 'td' ) .
+ Xml::closeElement( 'tr' ) .
+ Xml::closeElement( 'table' )
);
// Sometimes the search engine knows there are too many hits
@@ -316,7 +323,7 @@ class SpecialSearch extends SpecialPage {
return;
}
- $filePrefix = $wgContLang->getFormattedNsText(NS_FILE).':';
+ $filePrefix = $wgContLang->getFormattedNsText( NS_FILE ) . ':';
if( trim( $term ) === '' || $filePrefix === trim( $term ) ) {
$out->addHTML( $this->formHeader( $term, 0, 0 ) );
$out->addHtml( $this->getProfileForm( $this->profile, $term ) );
@@ -340,16 +347,15 @@ class SpecialSearch extends SpecialPage {
// get total number of results if backend can calculate it
$totalRes = 0;
- if($titleMatches && !is_null( $titleMatches->getTotalHits() ) )
+ if( $titleMatches && !is_null( $titleMatches->getTotalHits() ) )
$totalRes += $titleMatches->getTotalHits();
- if($textMatches && !is_null( $textMatches->getTotalHits() ))
+ if( $textMatches && !is_null( $textMatches->getTotalHits() ) )
$totalRes += $textMatches->getTotalHits();
// show number of results and current offset
$out->addHTML( $this->formHeader( $term, $num, $totalRes ) );
$out->addHtml( $this->getProfileForm( $this->profile, $term ) );
-
$out->addHtml( Xml::closeElement( 'form' ) );
$out->addHtml( "<div class='searchresults'>" );
@@ -404,6 +410,7 @@ class SpecialSearch extends SpecialPage {
if( $num || $this->offset ) {
$out->addHTML( "<p class='mw-search-pager-bottom'>{$prevnext}</p>\n" );
}
+ wfRunHooks( 'SpecialSearchResultsAppend', array( $this, $out, $term ) );
wfProfileOut( __METHOD__ );
}
@@ -423,7 +430,7 @@ class SpecialSearch extends SpecialPage {
if( $t->isKnown() ) {
$messageName = 'searchmenu-exists';
- } elseif( $t->userCan( 'create' ) ) {
+ } elseif( $t->userCan( 'create', $this->getUser() ) ) {
$messageName = 'searchmenu-new';
} else {
$messageName = 'searchmenu-new-nocreate';
@@ -447,9 +454,11 @@ class SpecialSearch extends SpecialPage {
# Should advanced UI be used?
$this->searchAdvanced = ($this->profile === 'advanced');
$out = $this->getOutput();
- if( strval( $term ) !== '' ) {
+ if( strval( $term ) !== '' ) {
$out->setPageTitle( $this->msg( 'searchresults' ) );
- $out->setHTMLTitle( $this->msg( 'pagetitle', $this->msg( 'searchresults-title', $term )->plain() ) );
+ $out->setHTMLTitle( $this->msg( 'pagetitle' )->rawParams(
+ $this->msg( 'searchresults-title' )->rawParams( $term )->text()
+ ) );
}
// add javascript specific to special:search
$out->addModules( 'mediawiki.special.search' );
@@ -506,7 +515,7 @@ class SpecialSearch extends SpecialPage {
$out = "";
$infoLine = $matches->getInfo();
- if( !is_null($infoLine) ) {
+ if( !is_null( $infoLine ) ) {
$out .= "\n<!-- {$infoLine} -->\n";
}
$out .= "<ul class='mw-search-results'>\n";
@@ -527,7 +536,7 @@ class SpecialSearch extends SpecialPage {
* Format a single hit result
*
* @param $result SearchResult
- * @param $terms Array: terms to highlight
+ * @param array $terms terms to highlight
*
* @return string
*/
@@ -541,7 +550,7 @@ class SpecialSearch extends SpecialPage {
$t = $result->getTitle();
- $titleSnippet = $result->getTitleSnippet($terms);
+ $titleSnippet = $result->getTitleSnippet( $terms );
if( $titleSnippet == '' )
$titleSnippet = null;
@@ -559,7 +568,7 @@ class SpecialSearch extends SpecialPage {
//If page content is not readable, just return the title.
//This is not quite safe, but better than showing excerpts from non-readable pages
//Note that hiding the entry entirely would screw up paging.
- if( !$t->userCan( 'read' ) ) {
+ if( !$t->userCan( 'read', $this->getUser() ) ) {
wfProfileOut( __METHOD__ );
return "<li>{$link}</li>\n";
}
@@ -574,12 +583,12 @@ class SpecialSearch extends SpecialPage {
// format redirects / relevant sections
$redirectTitle = $result->getRedirectTitle();
- $redirectText = $result->getRedirectSnippet($terms);
+ $redirectText = $result->getRedirectSnippet( $terms );
$sectionTitle = $result->getSectionTitle();
- $sectionText = $result->getSectionSnippet($terms);
+ $sectionText = $result->getSectionSnippet( $terms );
$redirect = '';
- if( !is_null($redirectTitle) ) {
+ if( !is_null( $redirectTitle ) ) {
if( $redirectText == '' )
$redirectText = null;
@@ -591,7 +600,7 @@ class SpecialSearch extends SpecialPage {
$section = '';
- if( !is_null($sectionTitle) ) {
+ if( !is_null( $sectionTitle ) ) {
if( $sectionText == '' )
$sectionText = null;
@@ -602,7 +611,7 @@ class SpecialSearch extends SpecialPage {
}
// format text extract
- $extract = "<div class='searchresult'>".$result->getTextSnippet($terms)."</div>";
+ $extract = "<div class='searchresult'>" . $result->getTextSnippet( $terms ) . "</div>";
$lang = $this->getLanguage();
@@ -667,7 +676,7 @@ class SpecialSearch extends SpecialPage {
return "<li>" .
'<table class="searchResultImage">' .
'<tr>' .
- '<td width="120" style="text-align: center; vertical-align: top;">' .
+ '<td style="width: 120px; text-align: center; vertical-align: top;">' .
$thumb->toHtml( array( 'desc-link' => true ) ) .
'</td>' .
'<td style="vertical-align: top;">' .
@@ -682,11 +691,21 @@ class SpecialSearch extends SpecialPage {
}
}
- wfProfileOut( __METHOD__ );
- return "<li><div class='mw-search-result-heading'>{$link} {$redirect} {$section}</div> {$extract}\n" .
- "<div class='mw-search-result-data'>{$score}{$size} - {$date}{$related}</div>" .
- "</li>\n";
+ $html = null;
+
+ if ( wfRunHooks( 'ShowSearchHit', array (
+ $this, $result, $terms,
+ &$link, &$redirect, &$section, &$extract,
+ &$score, &$size, &$date, &$related,
+ &$html
+ ) ) ) {
+ $html = "<li><div class='mw-search-result-heading'>{$link} {$redirect} {$section}</div> {$extract}\n" .
+ "<div class='mw-search-result-data'>{$score}{$size} - {$date}{$related}</div>" .
+ "</li>\n";
+ }
+ wfProfileOut( __METHOD__ );
+ return $html;
}
/**
@@ -703,16 +722,17 @@ class SpecialSearch extends SpecialPage {
$terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
$out = "<div id='mw-search-interwiki'><div id='mw-search-interwiki-caption'>".
- $this->msg( 'search-interwiki-caption' )->text() . "</div>\n";
+ $this->msg( 'search-interwiki-caption' )->text() . "</div>\n";
$out .= "<ul class='mw-search-iwresults'>\n";
// work out custom project captions
$customCaptions = array();
$customLines = explode( "\n", $this->msg( 'search-interwiki-custom' )->text() ); // format per line <iwprefix>:<caption>
- foreach($customLines as $line) {
- $parts = explode(":",$line,2);
- if(count($parts) == 2) // validate line
+ foreach( $customLines as $line ) {
+ $parts = explode( ":", $line, 2 );
+ if( count( $parts ) == 2 ) { // validate line
$customCaptions[$parts[0]] = $parts[1];
+ }
}
$prev = null;
@@ -738,7 +758,7 @@ class SpecialSearch extends SpecialPage {
* @param $lastInterwiki String
* @param $terms Array
* @param $query String
- * @param $customCaptions Array: iw prefix -> caption
+ * @param array $customCaptions iw prefix -> caption
*
* @return string
*/
@@ -752,7 +772,7 @@ class SpecialSearch extends SpecialPage {
$t = $result->getTitle();
- $titleSnippet = $result->getTitleSnippet($terms);
+ $titleSnippet = $result->getTitleSnippet( $terms );
if( $titleSnippet == '' )
$titleSnippet = null;
@@ -764,9 +784,9 @@ class SpecialSearch extends SpecialPage {
// format redirect if any
$redirectTitle = $result->getRedirectTitle();
- $redirectText = $result->getRedirectSnippet($terms);
+ $redirectText = $result->getRedirectSnippet( $terms );
$redirect = '';
- if( !is_null($redirectTitle) ) {
+ if( !is_null( $redirectTitle ) ) {
if( $redirectText == '' )
$redirectText = null;
@@ -778,8 +798,8 @@ class SpecialSearch extends SpecialPage {
$out = "";
// display project name
- if(is_null($lastInterwiki) || $lastInterwiki != $t->getInterwiki()) {
- if( array_key_exists($t->getInterwiki(),$customCaptions) ) {
+ if( is_null( $lastInterwiki ) || $lastInterwiki != $t->getInterwiki() ) {
+ if( array_key_exists( $t->getInterwiki(), $customCaptions ) ) {
// captions from 'search-interwiki-custom'
$caption = $customCaptions[$t->getInterwiki()];
} else {
@@ -789,7 +809,7 @@ class SpecialSearch extends SpecialPage {
$caption = $this->msg( 'search-interwiki-default', $parsed['host'] )->text();
}
// "more results" link (special page stuff could be localized, but we might not know target lang)
- $searchTitle = Title::newFromText($t->getInterwiki().":Special:Search");
+ $searchTitle = Title::newFromText( $t->getInterwiki() . ":Special:Search" );
$searchLink = Linker::linkKnown(
$searchTitle,
$this->msg( 'search-interwiki-more' )->text(),
@@ -831,7 +851,7 @@ class SpecialSearch extends SpecialPage {
/**
* Generates the power search box at [[Special:Search]]
*
- * @param $term String: search term
+ * @param string $term search term
* @param $opts array
* @return String: HTML form
*/
@@ -897,7 +917,7 @@ class SpecialSearch extends SpecialPage {
'fieldset',
array( 'id' => 'mw-searchoptions', 'style' => 'margin:0em;' )
) .
- Xml::element( 'legend', null, $this->msg('powersearch-legend' )->text() ) .
+ Xml::element( 'legend', null, $this->msg( 'powersearch-legend' )->text() ) .
Xml::tags( 'h4', null, $this->msg( 'powersearch-ns' )->parse() ) .
Html::element( 'div', array( 'id' => 'mw-search-togglebox' ) ) .
Xml::element( 'div', array( 'class' => 'divider' ), '', false ) .
@@ -964,7 +984,7 @@ class SpecialSearch extends SpecialPage {
* @return string
*/
protected function formHeader( $term, $resultsShown, $totalNum ) {
- $out = Xml::openElement('div', array( 'class' => 'mw-search-formheader' ) );
+ $out = Xml::openElement( 'div', array( 'class' => 'mw-search-formheader' ) );
$bareterm = $term;
if( $this->startsWithImage( $term ) ) {
@@ -1001,11 +1021,11 @@ class SpecialSearch extends SpecialPage {
);
}
$out .= Xml::closeElement( 'ul' );
- $out .= Xml::closeElement('div') ;
+ $out .= Xml::closeElement( 'div' );
// Results-info
if ( $resultsShown > 0 ) {
- if ( $totalNum > 0 ){
+ if ( $totalNum > 0 ) {
$top = $this->msg( 'showingresultsheader' )
->numParams( $this->offset + 1, $this->offset + $resultsShown, $totalNum )
->params( wfEscapeWikiText( $term ) )
@@ -1026,7 +1046,7 @@ class SpecialSearch extends SpecialPage {
}
$out .= Xml::element( 'div', array( 'style' => 'clear:both' ), '', false );
- $out .= Xml::closeElement('div');
+ $out .= Xml::closeElement( 'div' );
return $out;
}
@@ -1053,10 +1073,10 @@ class SpecialSearch extends SpecialPage {
* Make a search link with some target namespaces
*
* @param $term String
- * @param $namespaces Array ignored
- * @param $label String: link's text
- * @param $tooltip String: link's tooltip
- * @param $params Array: query string parameters
+ * @param array $namespaces ignored
+ * @param string $label link's text
+ * @param string $tooltip link's tooltip
+ * @param array $params query string parameters
* @return String: HTML fragment
*/
protected function makeSearchLink( $term, $namespaces, $label, $tooltip, $params = array() ) {
@@ -1086,7 +1106,7 @@ class SpecialSearch extends SpecialPage {
/**
* Check if query starts with image: prefix
*
- * @param $term String: the string to check
+ * @param string $term the string to check
* @return Boolean
*/
protected function startsWithImage( $term ) {
@@ -1102,7 +1122,7 @@ class SpecialSearch extends SpecialPage {
/**
* Check if query starts with all: prefix
*
- * @param $term String: the string to check
+ * @param string $term the string to check
* @return Boolean
*/
protected function startsWithAll( $term ) {
@@ -1111,7 +1131,7 @@ class SpecialSearch extends SpecialPage {
$p = explode( ':', $term );
if( count( $p ) > 1 ) {
- return $p[0] == $allkeyword;
+ return $p[0] == $allkeyword;
}
return false;
}
@@ -1141,4 +1161,7 @@ class SpecialSearch extends SpecialPage {
$this->extraParams[$key] = $value;
}
+ protected function getGroupName() {
+ return 'redirects';
+ }
}
diff --git a/includes/specials/SpecialShortpages.php b/includes/specials/SpecialShortpages.php
index 5a4e8f03..1be7fbed 100644
--- a/includes/specials/SpecialShortpages.php
+++ b/includes/specials/SpecialShortpages.php
@@ -110,4 +110,8 @@ class ShortPagesPage extends QueryPage {
? "${hlinkInParentheses} {$dm}{$plink} {$dm}[{$size}]"
: "<del>${hlinkInParentheses} {$dm}{$plink} {$dm}[{$size}]</del>";
}
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
}
diff --git a/includes/specials/SpecialSpecialpages.php b/includes/specials/SpecialSpecialpages.php
index e973ddc8..57fffb84 100644
--- a/includes/specials/SpecialSpecialpages.php
+++ b/includes/specials/SpecialSpecialpages.php
@@ -62,11 +62,15 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
$groups = array();
foreach ( $pages as $page ) {
if ( $page->isListed() ) {
- $group = SpecialPageFactory::getGroup( $page );
+ $group = $page->getFinalGroupName();
if( !isset( $groups[$group] ) ) {
$groups[$group] = array();
}
- $groups[$group][$page->getDescription()] = array( $page->getTitle(), $page->isRestricted(), $page->isExpensive() );
+ $groups[$group][$page->getDescription()] = array(
+ $page->getTitle(),
+ $page->isRestricted(),
+ $page->isCached()
+ );
}
}
@@ -88,15 +92,14 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
}
private function outputPageList( $groups ) {
- global $wgMiserMode;
$out = $this->getOutput();
$includesRestrictedPages = false;
$includesCachedPages = false;
foreach ( $groups as $group => $sortedPages ) {
- $middle = ceil( count( $sortedPages )/2 );
$total = count( $sortedPages );
+ $middle = ceil( $total / 2 );
$count = 0;
$out->wrapWikiMsg( "<h2 class=\"mw-specialpagesgroup\" id=\"mw-specialpagesgroup-$group\">$1</h2>\n", "specialpages-group-$group" );
@@ -107,10 +110,10 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
Html::openElement( 'ul' ) . "\n"
);
foreach( $sortedPages as $desc => $specialpage ) {
- list( $title, $restricted, $expensive) = $specialpage;
+ list( $title, $restricted, $cached ) = $specialpage;
$pageClasses = array();
- if ( $expensive && $wgMiserMode ){
+ if ( $cached ) {
$includesCachedPages = true;
$pageClasses[] = 'mw-specialpagecached';
}
@@ -119,7 +122,7 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
$pageClasses[] = 'mw-specialpagerestricted';
}
- $link = Linker::linkKnown( $title , htmlspecialchars( $desc ) );
+ $link = Linker::linkKnown( $title, htmlspecialchars( $desc ) );
$out->addHTML( Html::rawElement( 'li', array( 'class' => implode( ' ', $pageClasses ) ), $link ) . "\n" );
# Split up the larger groups
diff --git a/includes/specials/SpecialStatistics.php b/includes/specials/SpecialStatistics.php
index 46881ec4..ee768263 100644
--- a/includes/specials/SpecialStatistics.php
+++ b/includes/specials/SpecialStatistics.php
@@ -61,7 +61,7 @@ class SpecialStatistics extends SpecialPage {
if( !$wgMiserMode ) {
$key = wfMemcKey( 'sitestats', 'activeusers-updated' );
// Re-calculate the count if the last tally is old...
- if( !$wgMemc->get($key) ) {
+ if( !$wgMemc->get( $key ) ) {
$dbw = wfGetDB( DB_MASTER );
SiteStatsUpdate::cacheUpdate( $dbw );
$wgMemc->set( $key, '1', 24*3600 ); // don't update for 1 day
@@ -222,7 +222,7 @@ class SpecialStatistics extends SpecialPage {
}
$text .= $this->formatRow( $grouppage . ' ' . $grouplink,
$this->getLanguage()->formatNum( $countUsers ),
- array( 'class' => 'statistics-group-' . Sanitizer::escapeClass( $group ) . $classZero ) );
+ array( 'class' => 'statistics-group-' . Sanitizer::escapeClass( $group ) . $classZero ) );
}
return $text;
}
@@ -277,21 +277,60 @@ class SpecialStatistics extends SpecialPage {
return $text;
}
- private function getOtherStats( $stats ) {
- if ( !count( $stats ) )
- return '';
+ /**
+ * Conversion of external statistics into an internal representation
+ * Following a ([<header-message>][<item-message>] = number) pattern
+ *
+ * @param array $stats
+ * @return string
+ */
+ private function getOtherStats( array $stats ) {
+ $return = '';
- $return = Xml::openElement( 'tr' ) .
- Xml::tags( 'th', array( 'colspan' => '2' ), $this->msg( 'statistics-header-hooks' )->parse() ) .
- Xml::closeElement( 'tr' );
+ foreach( $stats as $header => $items ) {
+
+ // Identify the structure used
+ if ( is_array( $items ) ) {
- foreach( $stats as $name => $number ) {
- $name = htmlspecialchars( $name );
- $number = htmlspecialchars( $number );
+ // Ignore headers that are recursively set as legacy header
+ if ( $header !== 'statistics-header-hooks' ) {
+ $return .= $this->formatRowHeader( $header );
+ }
+
+ // Collect all items that belong to the same header
+ foreach( $items as $key => $value ) {
+ $name = $this->msg( $key )->parse();
+ $number = htmlspecialchars( $value );
+
+ $return .= $this->formatRow( $name, $this->getLanguage()->formatNum( $number ), array( 'class' => 'mw-statistics-hook' ) );
+ }
+ } else {
+ // Create the legacy header only once
+ if ( $return === '' ) {
+ $return .= $this->formatRowHeader( 'statistics-header-hooks' );
+ }
- $return .= $this->formatRow( $name, $this->getLanguage()->formatNum( $number ), array( 'class' => 'mw-statistics-hook' ) );
+ // Recursively remap the legacy structure
+ $return .= $this->getOtherStats( array( 'statistics-header-hooks' => array( $header => $items ) ) );
+ }
}
return $return;
}
+
+ /**
+ * Format row header
+ *
+ * @param string $header
+ * @return string
+ */
+ private function formatRowHeader( $header ) {
+ return Xml::openElement( 'tr' ) .
+ Xml::tags( 'th', array( 'colspan' => '2' ), $this->msg( $header )->parse() ) .
+ Xml::closeElement( 'tr' );
+ }
+
+ protected function getGroupName() {
+ return 'wiki';
+ }
}
diff --git a/includes/specials/SpecialTags.php b/includes/specials/SpecialTags.php
index 4036ebb2..6d161031 100644
--- a/includes/specials/SpecialTags.php
+++ b/includes/specials/SpecialTags.php
@@ -92,4 +92,8 @@ class SpecialTags extends SpecialPage {
return Xml::tags( 'tr', null, $newRow ) . "\n";
}
+
+ protected function getGroupName() {
+ return 'changes';
+ }
}
diff --git a/includes/specials/SpecialUnblock.php b/includes/specials/SpecialUnblock.php
index fb2005b5..c4a53cf0 100644
--- a/includes/specials/SpecialUnblock.php
+++ b/includes/specials/SpecialUnblock.php
@@ -32,11 +32,11 @@ class SpecialUnblock extends SpecialPage {
protected $type;
protected $block;
- public function __construct(){
+ public function __construct() {
parent::__construct( 'Unblock', 'block' );
}
- public function execute( $par ){
+ public function execute( $par ) {
$this->checkPermissions();
$this->checkReadOnly();
@@ -56,8 +56,8 @@ class SpecialUnblock extends SpecialPage {
$form->setSubmitTextMsg( 'ipusubmit' );
$form->addPreText( $this->msg( 'unblockiptext' )->parseAsBlock() );
- if( $form->show() ){
- switch( $this->type ){
+ if( $form->show() ) {
+ switch( $this->type ) {
case Block::TYPE_USER:
case Block::TYPE_IP:
$out->addWikiMsg( 'unblocked', wfEscapeWikiText( $this->target ) );
@@ -73,7 +73,7 @@ class SpecialUnblock extends SpecialPage {
}
}
- protected function getFields(){
+ protected function getFields() {
$fields = array(
'Target' => array(
'type' => 'text',
@@ -92,21 +92,21 @@ class SpecialUnblock extends SpecialPage {
)
);
- if( $this->block instanceof Block ){
+ if( $this->block instanceof Block ) {
list( $target, $type ) = $this->block->getTargetAndType();
# Autoblocks are logged as "autoblock #123 because the IP was recently used by
# User:Foo, and we've just got any block, auto or not, that applies to a target
# the user has specified. Someone could be fishing to connect IPs to autoblocks,
# so don't show any distinction between unblocked IPs and autoblocked IPs
- if( $type == Block::TYPE_AUTO && $this->type == Block::TYPE_IP ){
+ if( $type == Block::TYPE_AUTO && $this->type == Block::TYPE_IP ) {
$fields['Target']['default'] = $this->target;
unset( $fields['Name'] );
} else {
$fields['Target']['default'] = $target;
$fields['Target']['type'] = 'hidden';
- switch( $type ){
+ switch( $type ) {
case Block::TYPE_USER:
case Block::TYPE_IP:
$fields['Name']['default'] = Linker::link(
@@ -149,14 +149,15 @@ class SpecialUnblock extends SpecialPage {
*
* @param $data Array
* @param $context IContextSource
+ * @throws ErrorPageError
* @return Array( Array(message key, parameters) ) on failure, True on success
*/
- public static function processUnblock( array $data, IContextSource $context ){
+ public static function processUnblock( array $data, IContextSource $context ) {
$performer = $context->getUser();
$target = $data['Target'];
$block = Block::newFromTarget( $data['Target'] );
- if( !$block instanceof Block ){
+ if( !$block instanceof Block ) {
return array( array( 'ipb_cant_unblock', $target ) );
}
@@ -172,8 +173,8 @@ class SpecialUnblock extends SpecialPage {
# unblock the whole range.
list( $target, $type ) = SpecialBlock::getTargetAndType( $target );
if( $block->getType() == Block::TYPE_RANGE && $type == Block::TYPE_IP ) {
- $range = $block->getTarget();
- return array( array( 'ipb_blocked_as_range', $target, $range ) );
+ $range = $block->getTarget();
+ return array( array( 'ipb_blocked_as_range', $target, $range ) );
}
# If the name was hidden and the blocking user cannot hide
@@ -212,4 +213,8 @@ class SpecialUnblock extends SpecialPage {
return true;
}
+
+ protected function getGroupName() {
+ return 'users';
+ }
}
diff --git a/includes/specials/SpecialUncategorizedcategories.php b/includes/specials/SpecialUncategorizedcategories.php
index 70d98df9..54b20dde 100644
--- a/includes/specials/SpecialUncategorizedcategories.php
+++ b/includes/specials/SpecialUncategorizedcategories.php
@@ -31,4 +31,17 @@ class UncategorizedCategoriesPage extends UncategorizedPagesPage {
parent::__construct( $name );
$this->requestedNamespace = NS_CATEGORY;
}
+
+ /**
+ * Formats the result
+ * @param $skin The current skin
+ * @param $result The query result
+ * @return string The category link
+ */
+ function formatResult ( $skin, $result ) {
+ $title = Title::makeTitle( NS_CATEGORY, $result->title );
+ $text = $title->getText();
+
+ return Linker::linkKnown( $title, htmlspecialchars( $text ) );
+ }
}
diff --git a/includes/specials/SpecialUncategorizedimages.php b/includes/specials/SpecialUncategorizedimages.php
index 5865bf62..53aa3f34 100644
--- a/includes/specials/SpecialUncategorizedimages.php
+++ b/includes/specials/SpecialUncategorizedimages.php
@@ -60,4 +60,7 @@ class UncategorizedImagesPage extends ImageQueryPage {
);
}
+ protected function getGroupName() {
+ return 'maintenance';
+ }
}
diff --git a/includes/specials/SpecialUncategorizedpages.php b/includes/specials/SpecialUncategorizedpages.php
index 1226a6ca..b518e6fb 100644
--- a/includes/specials/SpecialUncategorizedpages.php
+++ b/includes/specials/SpecialUncategorizedpages.php
@@ -41,7 +41,10 @@ class UncategorizedPagesPage extends PageQueryPage {
function isExpensive() {
return true;
}
- function isSyndicated() { return false; }
+
+ function isSyndicated() {
+ return false;
+ }
function getQueryInfo() {
return array (
@@ -52,7 +55,7 @@ class UncategorizedPagesPage extends PageQueryPage {
// default for page_namespace is all content namespaces (if requestedNamespace is false)
// otherwise, page_namespace is requestedNamespace
'conds' => array ( 'cl_from IS NULL',
- 'page_namespace' => ( $this->requestedNamespace!==false ? $this->requestedNamespace : MWNamespace::getContentNamespaces() ),
+ 'page_namespace' => ( $this->requestedNamespace !== false ? $this->requestedNamespace : MWNamespace::getContentNamespaces() ),
'page_is_redirect' => 0 ),
'join_conds' => array ( 'categorylinks' => array (
'LEFT JOIN', 'cl_from = page_id' ) )
@@ -66,4 +69,8 @@ class UncategorizedPagesPage extends PageQueryPage {
return array( 'page_namespace', 'page_title' );
return array( 'page_title' );
}
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
}
diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php
index d8e0b97c..e0363481 100644
--- a/includes/specials/SpecialUndelete.php
+++ b/includes/specials/SpecialUndelete.php
@@ -32,7 +32,16 @@ class PageArchive {
* @var Title
*/
protected $title;
- var $fileStatus;
+
+ /**
+ * @var Status
+ */
+ protected $fileStatus;
+
+ /**
+ * @var Status
+ */
+ protected $revisionStatus;
function __construct( $title ) {
if( is_null( $title ) ) {
@@ -58,7 +67,7 @@ class PageArchive {
* given title prefix.
* Returns result wrapper with (ar_namespace, ar_title, count) fields.
*
- * @param $prefix String: title prefix
+ * @param string $prefix title prefix
* @return ResultWrapper
*/
public static function listPagesByPrefix( $prefix ) {
@@ -112,14 +121,24 @@ class PageArchive {
* @return ResultWrapper
*/
function listRevisions() {
+ global $wgContentHandlerUseDB;
+
$dbr = wfGetDB( DB_SLAVE );
+
+ $fields = array(
+ 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
+ 'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1',
+ );
+
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'ar_content_format';
+ $fields[] = 'ar_content_model';
+ }
+
$res = $dbr->select( 'archive',
- array(
- 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
- 'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1'
- ),
+ $fields,
array( 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey() ),
+ 'ar_title' => $this->title->getDBkey() ),
__METHOD__,
array( 'ORDER BY' => 'ar_timestamp DESC' ) );
$ret = $dbr->resultObject( $res );
@@ -137,26 +156,9 @@ class PageArchive {
function listFiles() {
if( $this->title->getNamespace() == NS_FILE ) {
$dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( 'filearchive',
- array(
- 'fa_id',
- 'fa_name',
- 'fa_archive_name',
- 'fa_storage_key',
- 'fa_storage_group',
- 'fa_size',
- 'fa_width',
- 'fa_height',
- 'fa_bits',
- 'fa_metadata',
- 'fa_media_type',
- 'fa_major_mime',
- 'fa_minor_mime',
- 'fa_description',
- 'fa_user',
- 'fa_user_text',
- 'fa_timestamp',
- 'fa_deleted' ),
+ $res = $dbr->select(
+ 'filearchive',
+ ArchivedFile::selectFields(),
array( 'fa_name' => $this->title->getDBkey() ),
__METHOD__,
array( 'ORDER BY' => 'fa_timestamp DESC' ) );
@@ -174,28 +176,38 @@ class PageArchive {
* @return Revision
*/
function getRevision( $timestamp ) {
+ global $wgContentHandlerUseDB;
+
$dbr = wfGetDB( DB_SLAVE );
+
+ $fields = array(
+ 'ar_rev_id',
+ 'ar_text',
+ 'ar_comment',
+ 'ar_user',
+ 'ar_user_text',
+ 'ar_timestamp',
+ 'ar_minor_edit',
+ 'ar_flags',
+ 'ar_text_id',
+ 'ar_deleted',
+ 'ar_len',
+ 'ar_sha1',
+ );
+
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'ar_content_format';
+ $fields[] = 'ar_content_model';
+ }
+
$row = $dbr->selectRow( 'archive',
- array(
- 'ar_rev_id',
- 'ar_text',
- 'ar_comment',
- 'ar_user',
- 'ar_user_text',
- 'ar_timestamp',
- 'ar_minor_edit',
- 'ar_flags',
- 'ar_text_id',
- 'ar_deleted',
- 'ar_len',
- 'ar_sha1',
- ),
+ $fields,
array( 'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDBkey(),
'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
__METHOD__ );
if( $row ) {
- return Revision::newFromArchiveRow( $row, array( 'page' => $this->title->getArticleID() ) );
+ return Revision::newFromArchiveRow( $row, array( 'title' => $this->title ) );
} else {
return null;
}
@@ -218,9 +230,9 @@ class PageArchive {
$row = $dbr->selectRow( 'archive',
'ar_timestamp',
array( 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey(),
- 'ar_timestamp < ' .
- $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ),
+ 'ar_title' => $this->title->getDBkey(),
+ 'ar_timestamp < ' .
+ $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ),
__METHOD__,
array(
'ORDER BY' => 'ar_timestamp DESC',
@@ -289,7 +301,7 @@ class PageArchive {
$row = $dbr->selectRow( 'archive',
array( 'ar_text', 'ar_flags', 'ar_text_id' ),
array( 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey() ),
+ 'ar_title' => $this->title->getDBkey() ),
__METHOD__,
array( 'ORDER BY' => 'ar_timestamp DESC' ) );
if( $row ) {
@@ -308,7 +320,7 @@ class PageArchive {
$dbr = wfGetDB( DB_SLAVE );
$n = $dbr->selectField( 'archive', 'COUNT(ar_title)',
array( 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey() ),
+ 'ar_title' => $this->title->getDBkey() ),
__METHOD__
);
return ( $n > 0 );
@@ -319,7 +331,7 @@ class PageArchive {
* Once restored, the items will be removed from the archive tables.
* The deletion log will be updated with an undeletion notice.
*
- * @param $timestamps Array: pass an empty array to restore all revisions, otherwise list the ones to undelete.
+ * @param array $timestamps pass an empty array to restore all revisions, otherwise list the ones to undelete.
* @param $comment String
* @param $fileVersions Array
* @param $unsuppress Boolean
@@ -329,8 +341,6 @@ class PageArchive {
* on success, false on failure
*/
function undelete( $timestamps, $comment = '', $fileVersions = array(), $unsuppress = false, User $user = null ) {
- global $wgUser;
-
// If both the set of text revisions and file revisions are empty,
// restore everything. Otherwise, just restore the requested items.
$restoreAll = empty( $timestamps ) && empty( $fileVersions );
@@ -341,7 +351,7 @@ class PageArchive {
if( $restoreFiles && $this->title->getNamespace() == NS_FILE ) {
$img = wfLocalFile( $this->title );
$this->fileStatus = $img->restore( $fileVersions, $unsuppress );
- if ( !$this->fileStatus->isOk() ) {
+ if ( !$this->fileStatus->isOK() ) {
return false;
}
$filesRestored = $this->fileStatus->successCount;
@@ -350,10 +360,12 @@ class PageArchive {
}
if( $restoreText ) {
- $textRestored = $this->undeleteRevisions( $timestamps, $unsuppress, $comment );
- if( $textRestored === false ) { // It must be one of UNDELETE_*
+ $this->revisionStatus = $this->undeleteRevisions( $timestamps, $unsuppress, $comment );
+ if( !$this->revisionStatus->isOK() ) {
return false;
}
+
+ $textRestored = $this->revisionStatus->getValue();
} else {
$textRestored = 0;
}
@@ -379,6 +391,7 @@ class PageArchive {
}
if ( $user === null ) {
+ global $wgUser;
$user = $wgUser;
}
@@ -386,6 +399,9 @@ class PageArchive {
$logEntry->setPerformer( $user );
$logEntry->setTarget( $this->title );
$logEntry->setComment( $reason );
+
+ wfRunHooks( 'ArticleUndeleteLogEntry', array( $this, &$logEntry, $user ) );
+
$logid = $logEntry->insert();
$logEntry->publish( $logid );
@@ -397,15 +413,18 @@ class PageArchive {
* to the cur/old tables. If the page currently exists, all revisions will
* be stuffed into old, otherwise the most recent will go into cur.
*
- * @param $timestamps Array: pass an empty array to restore all revisions, otherwise list the ones to undelete.
- * @param $comment String
+ * @param array $timestamps pass an empty array to restore all revisions, otherwise list the ones to undelete.
* @param $unsuppress Boolean: remove all ar_deleted/fa_deleted restrictions of seletected revs
*
- * @return Mixed: number of revisions restored or false on failure
+ * @param $comment String
+ * @throws ReadOnlyError
+ * @return Status, containing the number of revisions restored on success
*/
private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) {
+ global $wgContentHandlerUseDB;
+
if ( wfReadOnly() ) {
- return false;
+ throw new ReadOnlyError();
}
$restoreAll = empty( $timestamps );
@@ -420,7 +439,7 @@ class PageArchive {
$page = $dbw->selectRow( 'page',
array( 'page_id', 'page_latest' ),
array( 'page_namespace' => $this->title->getNamespace(),
- 'page_title' => $this->title->getDBkey() ),
+ 'page_title' => $this->title->getDBkey() ),
__METHOD__,
array( 'FOR UPDATE' ) // lock page
);
@@ -428,16 +447,21 @@ class PageArchive {
$makepage = false;
# Page already exists. Import the history, and if necessary
# we'll update the latest revision field in the record.
- $newid = 0;
- $pageId = $page->page_id;
- $previousRevId = $page->page_latest;
+
+ $previousRevId = $page->page_latest;
+
# Get the time span of this page
$previousTimestamp = $dbw->selectField( 'revision', 'rev_timestamp',
array( 'rev_id' => $previousRevId ),
__METHOD__ );
+
if( $previousTimestamp === false ) {
- wfDebug( __METHOD__.": existing page refers to a page_latest that does not exist\n" );
- return 0;
+ wfDebug( __METHOD__ . ": existing page refers to a page_latest that does not exist\n" );
+
+ $status = Status::newGood( 0 );
+ $status->warning( 'undeleterevision-missing' );
+
+ return $status;
}
} else {
# Have to create a new article...
@@ -457,24 +481,32 @@ class PageArchive {
$oldones = "ar_timestamp IN ( {$oldts} )";
}
+ $fields = array(
+ 'ar_rev_id',
+ 'ar_text',
+ 'ar_comment',
+ 'ar_user',
+ 'ar_user_text',
+ 'ar_timestamp',
+ 'ar_minor_edit',
+ 'ar_flags',
+ 'ar_text_id',
+ 'ar_deleted',
+ 'ar_page_id',
+ 'ar_len',
+ 'ar_sha1'
+ );
+
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'ar_content_format';
+ $fields[] = 'ar_content_model';
+ }
+
/**
* Select each archived revision...
*/
$result = $dbw->select( 'archive',
- /* fields */ array(
- 'ar_rev_id',
- 'ar_text',
- 'ar_comment',
- 'ar_user',
- 'ar_user_text',
- 'ar_timestamp',
- 'ar_minor_edit',
- 'ar_flags',
- 'ar_text_id',
- 'ar_deleted',
- 'ar_page_id',
- 'ar_len',
- 'ar_sha1' ),
+ $fields,
/* WHERE */ array(
'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDBkey(),
@@ -486,29 +518,51 @@ class PageArchive {
$rev_count = $dbw->numRows( $result );
if( !$rev_count ) {
wfDebug( __METHOD__ . ": no revisions to restore\n" );
- return false; // ???
+
+ $status = Status::newGood( 0 );
+ $status->warning( "undelete-no-results" );
+ return $status;
}
$ret->seek( $rev_count - 1 ); // move to last
$row = $ret->fetchObject(); // get newest archived rev
$ret->seek( 0 ); // move back
+ // grab the content to check consistency with global state before restoring the page.
+ $revision = Revision::newFromArchiveRow( $row,
+ array(
+ 'title' => $article->getTitle(), // used to derive default content model
+ )
+ );
+ $user = User::newFromName( $revision->getRawUserText(), false );
+ $content = $revision->getContent( Revision::RAW );
+
+ //NOTE: article ID may not be known yet. prepareSave() should not modify the database.
+ $status = $content->prepareSave( $article, 0, -1, $user );
+
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
if( $makepage ) {
// Check the state of the newest to-be version...
if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
- return false; // we can't leave the current revision like this!
+ return Status::newFatal( "undeleterevdel" );
}
// Safe to insert now...
- $newid = $article->insertOn( $dbw );
+ $newid = $article->insertOn( $dbw );
$pageId = $newid;
} else {
// Check if a deleted revision will become the current revision...
if( $row->ar_timestamp > $previousTimestamp ) {
// Check the state of the newest to-be version...
if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
- return false; // we can't leave the current revision like this!
+ return Status::newFatal( "undeleterevdel" );
}
}
+
+ $newid = false;
+ $pageId = $article->getId();
}
$revision = null;
@@ -528,6 +582,7 @@ class PageArchive {
$revision = Revision::newFromArchiveRow( $row,
array(
'page' => $pageId,
+ 'title' => $this->title,
'deleted' => $unsuppress ? 0 : $row->ar_deleted
) );
@@ -546,7 +601,7 @@ class PageArchive {
// Was anything restored at all?
if ( $restored == 0 ) {
- return 0;
+ return Status::newGood( 0 );
}
$created = (bool)$newid;
@@ -566,13 +621,18 @@ class PageArchive {
$update->doUpdate();
}
- return $restored;
+ return Status::newGood( $restored );
}
/**
* @return Status
*/
function getFileStatus() { return $this->fileStatus; }
+
+ /**
+ * @return Status
+ */
+ function getRevisionStatus() { return $this->revisionStatus; }
}
/**
@@ -721,7 +781,7 @@ class SpecialUndelete extends SpecialPage {
'action' => $wgScript ) ) .
Xml::fieldset( $this->msg( 'undelete-search-box' )->text() ) .
Html::hidden( 'title',
- $this->getTitle()->getPrefixedDbKey() ) .
+ $this->getTitle()->getPrefixedDBkey() ) .
Xml::inputLabel( $this->msg( 'undelete-search-prefix' )->text(),
'prefix', 'prefix', 20,
$this->mSearchPrefix ) . ' ' .
@@ -748,7 +808,7 @@ class SpecialUndelete extends SpecialPage {
if( $result->numRows() == 0 ) {
$out->addWikiMsg( 'undelete-no-results' );
- return;
+ return false;
}
$out->addWikiMsg( 'undeletepagetext', $this->getLanguage()->formatNum( $result->numRows() ) );
@@ -780,11 +840,13 @@ class SpecialUndelete extends SpecialPage {
private function showRevision( $timestamp ) {
if( !preg_match( '/[0-9]{14}/', $timestamp ) ) {
- return 0;
+ return;
}
$archive = new PageArchive( $this->mTargetObj );
- wfRunHooks( 'UndeleteForm::showRevision', array( &$archive, $this->mTargetObj ) );
+ if ( !wfRunHooks( 'UndeleteForm::showRevision', array( &$archive, $this->mTargetObj ) ) ) {
+ return;
+ }
$rev = $archive->getRevision( $timestamp );
$out = $this->getOutput();
@@ -834,7 +896,11 @@ class SpecialUndelete extends SpecialPage {
$t = $lang->userTime( $timestamp, $user );
$userLink = Linker::revUserTools( $rev );
- if( $this->mPreview ) {
+ $content = $rev->getContent( Revision::FOR_THIS_USER, $user );
+
+ $isText = ( $content instanceof TextContent );
+
+ if( $this->mPreview || $isText ) {
$openDiv = '<div id="mw-undelete-revision" class="mw-warning">';
} else {
$openDiv = '<div id="mw-undelete-revision">';
@@ -851,30 +917,55 @@ class SpecialUndelete extends SpecialPage {
$out->addHTML( $this->msg( 'undelete-revision' )->rawParams( $link )->params(
$time )->rawParams( $userLink )->params( $d, $t )->parse() . '</div>' );
- wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) );
- if( $this->mPreview ) {
+ if ( !wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) ) ) {
+ return;
+ }
+
+ if( $this->mPreview || !$isText ) {
+ // NOTE: non-text content has no source view, so always use rendered preview
+
// Hide [edit]s
$popts = $out->parserOptions();
$popts->setEditSection( false );
- $out->parserOptions( $popts );
- $out->addWikiTextTitleTidy( $rev->getText( Revision::FOR_THIS_USER, $user ), $this->mTargetObj, true );
+
+ $pout = $content->getParserOutput( $this->mTargetObj, $rev->getId(), $popts, true );
+ $out->addParserOutput( $pout );
}
+ if ( $isText ) {
+ // source view for textual content
+ $sourceView = Xml::element( 'textarea', array(
+ 'readonly' => 'readonly',
+ 'cols' => $user->getIntOption( 'cols' ),
+ 'rows' => $user->getIntOption( 'rows' ) ),
+ $content->getNativeData() . "\n" );
+
+ $previewButton = Xml::element( 'input', array(
+ 'type' => 'submit',
+ 'name' => 'preview',
+ 'value' => $this->msg( 'showpreview' )->text() ) );
+ } else {
+ $sourceView = '';
+ $previewButton = '';
+ }
+
+ $diffButton = Xml::element( 'input', array(
+ 'name' => 'diff',
+ 'type' => 'submit',
+ 'value' => $this->msg( 'showdiff' )->text() ) );
+
$out->addHTML(
- Xml::element( 'textarea', array(
- 'readonly' => 'readonly',
- 'cols' => intval( $user->getOption( 'cols' ) ),
- 'rows' => intval( $user->getOption( 'rows' ) ) ),
- $rev->getText( Revision::FOR_THIS_USER, $user ) . "\n" ) .
- Xml::openElement( 'div' ) .
+ $sourceView .
+ Xml::openElement( 'div', array(
+ 'style' => 'clear: both' ) ) .
Xml::openElement( 'form', array(
'method' => 'post',
'action' => $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) ) ) ) .
Xml::element( 'input', array(
'type' => 'hidden',
'name' => 'target',
- 'value' => $this->mTargetObj->getPrefixedDbKey() ) ) .
+ 'value' => $this->mTargetObj->getPrefixedDBkey() ) ) .
Xml::element( 'input', array(
'type' => 'hidden',
'name' => 'timestamp',
@@ -883,14 +974,8 @@ class SpecialUndelete extends SpecialPage {
'type' => 'hidden',
'name' => 'wpEditToken',
'value' => $user->getEditToken() ) ) .
- Xml::element( 'input', array(
- 'type' => 'submit',
- 'name' => 'preview',
- 'value' => $this->msg( 'showpreview' )->text() ) ) .
- Xml::element( 'input', array(
- 'name' => 'diff',
- 'type' => 'submit',
- 'value' => $this->msg( 'showdiff' )->text() ) ) .
+ $previewButton .
+ $diffButton .
Xml::closeElement( 'form' ) .
Xml::closeElement( 'div' ) );
}
@@ -904,26 +989,30 @@ class SpecialUndelete extends SpecialPage {
* @return String: HTML
*/
function showDiff( $previousRev, $currentRev ) {
- $diffEngine = new DifferenceEngine( $this->getContext() );
+ $diffContext = clone $this->getContext();
+ $diffContext->setTitle( $currentRev->getTitle() );
+ $diffContext->setWikiPage( WikiPage::factory( $currentRev->getTitle() ) );
+
+ $diffEngine = $currentRev->getContentHandler()->createDifferenceEngine( $diffContext );
$diffEngine->showDiffStyle();
$this->getOutput()->addHTML(
"<div>" .
- "<table width='98%' cellpadding='0' cellspacing='4' class='diff'>" .
+ "<table style='width: 98%;' cellpadding='0' cellspacing='4' class='diff'>" .
"<col class='diff-marker' />" .
"<col class='diff-content' />" .
"<col class='diff-marker' />" .
"<col class='diff-content' />" .
"<tr>" .
- "<td colspan='2' width='50%' style='text-align: center' class='diff-otitle'>" .
+ "<td colspan='2' style='width: 50%; text-align: center' class='diff-otitle'>" .
$this->diffHeader( $previousRev, 'o' ) .
"</td>\n" .
- "<td colspan='2' width='50%' style='text-align: center' class='diff-ntitle'>" .
+ "<td colspan='2' style='width: 50%; text-align: center' class='diff-ntitle'>" .
$this->diffHeader( $currentRev, 'n' ) .
"</td>\n" .
"</tr>" .
- $diffEngine->generateDiffBody(
- $previousRev->getText( Revision::FOR_THIS_USER, $this->getUser() ),
- $currentRev->getText( Revision::FOR_THIS_USER, $this->getUser() ) ) .
+ $diffEngine->generateContentDiffBody(
+ $previousRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ),
+ $currentRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ) ) .
"</table>" .
"</div>\n"
);
@@ -967,10 +1056,10 @@ class SpecialUndelete extends SpecialPage {
$targetQuery
) .
'</strong></div>' .
- '<div id="mw-diff-'.$prefix.'title2">' .
+ '<div id="mw-diff-' . $prefix . 'title2">' .
Linker::revUserTools( $rev ) . '<br />' .
'</div>' .
- '<div id="mw-diff-'.$prefix.'title3">' .
+ '<div id="mw-diff-' . $prefix . 'title3">' .
Linker::revComment( $rev ) . $rdel . '<br />' .
'</div>';
}
@@ -1122,7 +1211,7 @@ class SpecialUndelete extends SpecialPage {
Xml::label( $this->msg( 'undeletecomment' )->text(), 'wpComment' ) .
"</td>
<td class='mw-input'>" .
- Xml::input( 'wpComment', 50, $this->mComment, array( 'id' => 'wpComment' ) ) .
+ Xml::input( 'wpComment', 50, $this->mComment, array( 'id' => 'wpComment' ) ) .
"</td>
</tr>
<tr>
@@ -1169,7 +1258,7 @@ class SpecialUndelete extends SpecialPage {
if ( $this->mAllowed ) {
# Slip in the hidden controls here
- $misc = Html::hidden( 'target', $this->mTarget );
+ $misc = Html::hidden( 'target', $this->mTarget );
$misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() );
$misc .= Xml::closeElement( 'form' );
$out->addHTML( $misc );
@@ -1180,7 +1269,10 @@ class SpecialUndelete extends SpecialPage {
private function formatRevisionRow( $row, $earliestLiveTime, $remaining ) {
$rev = Revision::newFromArchiveRow( $row,
- array( 'page' => $this->mTargetObj->getArticleID() ) );
+ array(
+ 'title' => $this->mTargetObj
+ ) );
+
$revTextSize = '';
$ts = wfTimestamp( TS_MW, $row->ar_timestamp );
// Build checkboxen...
@@ -1237,7 +1329,7 @@ class SpecialUndelete extends SpecialPage {
// Revision delete links
$revdlink = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj );
- $revisionRow = $this->msg( 'undelete-revisionrow' )->rawParams( $checkBox, $revdlink, $last, $pageLink , $userLink, $revTextSize, $comment )->escaped();
+ $revisionRow = $this->msg( 'undelete-revisionrow' )->rawParams( $checkBox, $revdlink, $last, $pageLink, $userLink, $revTextSize, $comment )->escaped();
return "<li>$revisionRow</li>";
}
@@ -1286,7 +1378,7 @@ class SpecialUndelete extends SpecialPage {
*
* @param $rev Revision
* @param $titleObj Title
- * @param $ts string Timestamp
+ * @param string $ts Timestamp
* @return string
*/
function getPageLink( $rev, $titleObj, $ts ) {
@@ -1317,8 +1409,8 @@ class SpecialUndelete extends SpecialPage {
*
* @param $file File
* @param $titleObj Title
- * @param $ts string A timestamp
- * @param $key String: a storage key
+ * @param string $ts A timestamp
+ * @param string $key a storage key
*
* @return String: HTML fragment
*/
@@ -1417,14 +1509,22 @@ class SpecialUndelete extends SpecialPage {
$out->addHTML( $this->msg( 'undeletedpage' )->rawParams( $link )->parse() );
} else {
$out->setPageTitle( $this->msg( 'undelete-error' ) );
- $out->addWikiMsg( 'cannotundelete' );
- $out->addWikiMsg( 'undeleterevdel' );
}
- // Show file deletion warnings and errors
+ // Show revision undeletion warnings and errors
+ $status = $archive->getRevisionStatus();
+ if( $status && !$status->isGood() ) {
+ $out->addWikiText( '<div class="error">' . $status->getWikiText( 'cannotundelete', 'cannotundelete' ) . '</div>' );
+ }
+
+ // Show file undeletion warnings and errors
$status = $archive->getFileStatus();
if( $status && !$status->isGood() ) {
$out->addWikiText( '<div class="error">' . $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) . '</div>' );
}
}
+
+ protected function getGroupName() {
+ return 'pagetools';
+ }
}
diff --git a/includes/specials/SpecialUnlockdb.php b/includes/specials/SpecialUnlockdb.php
index 2e772540..35141d80 100644
--- a/includes/specials/SpecialUnlockdb.php
+++ b/includes/specials/SpecialUnlockdb.php
@@ -84,4 +84,8 @@ class SpecialUnlockdb extends FormSpecialPage {
$out->addSubtitle( $this->msg( 'unlockdbsuccesssub' ) );
$out->addWikiMsg( 'unlockdbsuccesstext' );
}
+
+ protected function getGroupName() {
+ return 'wiki';
+ }
}
diff --git a/includes/specials/SpecialUnusedcategories.php b/includes/specials/SpecialUnusedcategories.php
index 1bd38e17..6b91dd39 100644
--- a/includes/specials/SpecialUnusedcategories.php
+++ b/includes/specials/SpecialUnusedcategories.php
@@ -26,7 +26,9 @@
*/
class UnusedCategoriesPage extends QueryPage {
- function isExpensive() { return true; }
+ function isExpensive() {
+ return true;
+ }
function __construct( $name = 'Unusedcategories' ) {
parent::__construct( $name );
@@ -62,4 +64,8 @@ class UnusedCategoriesPage extends QueryPage {
$title = Title::makeTitle( NS_CATEGORY, $result->title );
return Linker::link( $title, htmlspecialchars( $title->getText() ) );
}
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
}
diff --git a/includes/specials/SpecialUnusedimages.php b/includes/specials/SpecialUnusedimages.php
index cdab557e..69553282 100644
--- a/includes/specials/SpecialUnusedimages.php
+++ b/includes/specials/SpecialUnusedimages.php
@@ -80,4 +80,7 @@ class UnusedimagesPage extends ImageQueryPage {
return $this->msg( 'unusedimagestext' )->parseAsBlock();
}
+ protected function getGroupName() {
+ return 'maintenance';
+ }
}
diff --git a/includes/specials/SpecialUnusedtemplates.php b/includes/specials/SpecialUnusedtemplates.php
index 06077d1f..493e936a 100644
--- a/includes/specials/SpecialUnusedtemplates.php
+++ b/includes/specials/SpecialUnusedtemplates.php
@@ -35,9 +35,17 @@ class UnusedtemplatesPage extends QueryPage {
parent::__construct( $name );
}
- function isExpensive() { return true; }
- function isSyndicated() { return false; }
- function sortDescending() { return false; }
+ function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ function sortDescending() {
+ return false;
+ }
function getQueryInfo() {
return array (
@@ -77,4 +85,8 @@ class UnusedtemplatesPage extends QueryPage {
function getPageHeader() {
return $this->msg( 'unusedtemplatestext' )->parseAsBlock();
}
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
}
diff --git a/includes/specials/SpecialUnwatchedpages.php b/includes/specials/SpecialUnwatchedpages.php
index e5a79413..05ec6b02 100644
--- a/includes/specials/SpecialUnwatchedpages.php
+++ b/includes/specials/SpecialUnwatchedpages.php
@@ -35,8 +35,13 @@ class UnwatchedpagesPage extends QueryPage {
parent::__construct( $name, 'unwatchedpages' );
}
- function isExpensive() { return true; }
- function isSyndicated() { return false; }
+ function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
function getQueryInfo() {
return array (
@@ -54,7 +59,9 @@ class UnwatchedpagesPage extends QueryPage {
);
}
- function sortDescending() { return false; }
+ function sortDescending() {
+ return false;
+ }
function getOrderFields() {
return array( 'page_namespace', 'page_title' );
@@ -87,4 +94,8 @@ class UnwatchedpagesPage extends QueryPage {
return $this->getLanguage()->specialList( $plink, $wlink );
}
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
}
diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php
index 43ea345b..89c06b2a 100644
--- a/includes/specials/SpecialUpload.php
+++ b/includes/specials/SpecialUpload.php
@@ -39,7 +39,7 @@ class SpecialUpload extends SpecialPage {
}
/** Misc variables **/
- public $mRequest; // The WebRequest or FauxRequest this form is supposed to handle
+ public $mRequest; // The WebRequest or FauxRequest this form is supposed to handle
public $mSourceType;
/**
@@ -54,7 +54,7 @@ class SpecialUpload extends SpecialPage {
public $mUploadClicked;
/** User input variables from the "description" section **/
- public $mDesiredDestName; // The requested target file name
+ public $mDesiredDestName; // The requested target file name
public $mComment;
public $mLicense;
@@ -66,10 +66,10 @@ class SpecialUpload extends SpecialPage {
/** Hidden variables **/
public $mDestWarningAck;
- public $mForReUpload; // The user followed an "overwrite this file" link
- public $mCancelUpload; // The user clicked "Cancel and return to upload form" button
+ public $mForReUpload; // The user followed an "overwrite this file" link
+ public $mCancelUpload; // The user clicked "Cancel and return to upload form" button
public $mTokenOk;
- public $mUploadSuccessful = false; // Subclasses can use this to determine whether a file was uploaded
+ public $mUploadSuccessful = false; // Subclasses can use this to determine whether a file was uploaded
/** Text injection points for hooks not using HTMLForm **/
public $uploadFormTextTop;
@@ -82,32 +82,30 @@ class SpecialUpload extends SpecialPage {
*/
protected function loadRequest() {
$this->mRequest = $request = $this->getRequest();
- $this->mSourceType = $request->getVal( 'wpSourceType', 'file' );
- $this->mUpload = UploadBase::createFromRequest( $request );
- $this->mUploadClicked = $request->wasPosted()
+ $this->mSourceType = $request->getVal( 'wpSourceType', 'file' );
+ $this->mUpload = UploadBase::createFromRequest( $request );
+ $this->mUploadClicked = $request->wasPosted()
&& ( $request->getCheck( 'wpUpload' )
|| $request->getCheck( 'wpUploadIgnoreWarning' ) );
// Guess the desired name from the filename if not provided
- $this->mDesiredDestName = $request->getText( 'wpDestFile' );
+ $this->mDesiredDestName = $request->getText( 'wpDestFile' );
if( !$this->mDesiredDestName && $request->getFileName( 'wpUploadFile' ) !== null ) {
$this->mDesiredDestName = $request->getFileName( 'wpUploadFile' );
}
- $this->mComment = $request->getText( 'wpUploadDescription' );
- $this->mLicense = $request->getText( 'wpLicense' );
+ $this->mComment = $request->getText( 'wpUploadDescription' );
+ $this->mLicense = $request->getText( 'wpLicense' );
-
- $this->mDestWarningAck = $request->getText( 'wpDestFileWarningAck' );
- $this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' )
+ $this->mDestWarningAck = $request->getText( 'wpDestFileWarningAck' );
+ $this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' )
|| $request->getCheck( 'wpUploadIgnoreWarning' );
- $this->mWatchthis = $request->getBool( 'wpWatchthis' ) && $this->getUser()->isLoggedIn();
- $this->mCopyrightStatus = $request->getText( 'wpUploadCopyStatus' );
- $this->mCopyrightSource = $request->getText( 'wpUploadSource' );
-
+ $this->mWatchthis = $request->getBool( 'wpWatchthis' ) && $this->getUser()->isLoggedIn();
+ $this->mCopyrightStatus = $request->getText( 'wpUploadCopyStatus' );
+ $this->mCopyrightSource = $request->getText( 'wpUploadSource' );
- $this->mForReUpload = $request->getBool( 'wpForReUpload' ); // updating a file
- $this->mCancelUpload = $request->getCheck( 'wpCancelUpload' )
- || $request->getCheck( 'wpReUpload' ); // b/w compat
+ $this->mForReUpload = $request->getBool( 'wpForReUpload' ); // updating a file
+ $this->mCancelUpload = $request->getCheck( 'wpCancelUpload' )
+ || $request->getCheck( 'wpReUpload' ); // b/w compat
// If it was posted check for the token (no remote POST'ing with user credentials)
$token = $request->getVal( 'wpEditToken' );
@@ -209,8 +207,8 @@ class SpecialUpload extends SpecialPage {
/**
* Get an UploadForm instance with title and text properly set.
*
- * @param $message String: HTML string to add to the form
- * @param $sessionKey String: session key in case this is a stashed upload
+ * @param string $message HTML string to add to the form
+ * @param string $sessionKey session key in case this is a stashed upload
* @param $hideIgnoreWarning Boolean: whether to hide "ignore warning" check box
* @return UploadForm
*/
@@ -246,9 +244,9 @@ class SpecialUpload extends SpecialPage {
LogEventsList::showLogExtract( $delNotice, array( 'delete', 'move' ),
$desiredTitleObj,
'', array( 'lim' => 10,
- 'conds' => array( "log_action != 'revision'" ),
- 'showIfEmpty' => false,
- 'msgKey' => array( 'upload-recreate-warning' ) )
+ 'conds' => array( "log_action != 'revision'" ),
+ 'showIfEmpty' => false,
+ 'msgKey' => array( 'upload-recreate-warning' ) )
);
}
$form->addPreText( $delNotice );
@@ -300,7 +298,7 @@ class SpecialUpload extends SpecialPage {
* essentially means that UploadBase::VERIFICATION_ERROR and
* UploadBase::EMPTY_FILE should not be passed here.
*
- * @param $message String: HTML message to be passed to mainUploadForm
+ * @param string $message HTML message to be passed to mainUploadForm
*/
protected function showRecoverableUploadError( $message ) {
$sessionKey = $this->mUpload->stashSession();
@@ -312,12 +310,12 @@ class SpecialUpload extends SpecialPage {
$this->showUploadForm( $form );
}
/**
- * Stashes the upload, shows the main form, but adds an "continue anyway button".
+ * Stashes the upload, shows the main form, but adds a "continue anyway button".
* Also checks whether there are actually warnings to display.
*
* @param $warnings Array
* @return boolean true if warnings were displayed, false if there are no
- * warnings and the should continue processing like there was no warning
+ * warnings and it should continue processing
*/
protected function showUploadWarning( $warnings ) {
# If there are no warnings, or warnings we can ignore, return early.
@@ -336,6 +334,9 @@ class SpecialUpload extends SpecialPage {
$warningHtml = '<h2>' . $this->msg( 'uploadwarning' )->escaped() . "</h2>\n"
. '<ul class="warning">';
foreach( $warnings as $warning => $args ) {
+ if( $warning == 'badfilename' ) {
+ $this->mDesiredDestName = Title::makeTitle( NS_FILE, $args )->getText();
+ }
if( $warning == 'exists' ) {
$msg = "\t<li>" . self::getExistsWarning( $args ) . "</li>\n";
} elseif( $warning == 'duplicate' ) {
@@ -371,7 +372,7 @@ class SpecialUpload extends SpecialPage {
/**
* Show the upload form with error message, but do not stash the file.
*
- * @param $message string HTML string
+ * @param string $message HTML string
*/
protected function showUploadError( $message ) {
$message = '<h2>' . $this->msg( 'uploadwarning' )->escaped() . "</h2>\n" .
@@ -473,17 +474,17 @@ class SpecialUpload extends SpecialPage {
if ( $wgUseCopyrightUpload ) {
$licensetxt = '';
if ( $license != '' ) {
- $licensetxt = '== ' . $msg[ 'license-header' ] . " ==\n" . '{{' . $license . '}}' . "\n";
+ $licensetxt = '== ' . $msg['license-header'] . " ==\n" . '{{' . $license . '}}' . "\n";
}
- $pageText = '== ' . $msg[ 'filedesc' ] . " ==\n" . $comment . "\n" .
- '== ' . $msg[ 'filestatus' ] . " ==\n" . $copyStatus . "\n" .
+ $pageText = '== ' . $msg['filedesc'] . " ==\n" . $comment . "\n" .
+ '== ' . $msg['filestatus'] . " ==\n" . $copyStatus . "\n" .
"$licensetxt" .
- '== ' . $msg[ 'filesource' ] . " ==\n" . $source;
+ '== ' . $msg['filesource'] . " ==\n" . $source;
} else {
if ( $license != '' ) {
- $filedesc = $comment == '' ? '' : '== ' . $msg[ 'filedesc' ] . " ==\n" . $comment . "\n";
+ $filedesc = $comment == '' ? '' : '== ' . $msg['filedesc'] . " ==\n" . $comment . "\n";
$pageText = $filedesc .
- '== ' . $msg[ 'license-header' ] . " ==\n" . '{{' . $license . '}}' . "\n";
+ '== ' . $msg['license-header'] . " ==\n" . '{{' . $license . '}}' . "\n";
} else {
$pageText = $comment;
}
@@ -520,11 +521,11 @@ class SpecialUpload extends SpecialPage {
}
}
-
/**
* Provides output to the user for a result of UploadBase::verifyUpload
*
- * @param $details Array: result of UploadBase::verifyUpload
+ * @param array $details result of UploadBase::verifyUpload
+ * @throws MWException
*/
protected function processVerificationError( $details ) {
global $wgFileExtensions;
@@ -622,7 +623,7 @@ class SpecialUpload extends SpecialPage {
* Formats a result of UploadBase::getExistsWarning as HTML
* This check is static and can be done pre-upload via AJAX
*
- * @param $exists Array: the result of UploadBase::getExistsWarning
+ * @param array $exists the result of UploadBase::getExistsWarning
* @return String: empty string if there is no warning or an HTML fragment
*/
public static function getExistsWarning( $exists ) {
@@ -645,7 +646,7 @@ class SpecialUpload extends SpecialPage {
$exists['normalizedFile']->getTitle()->getPrefixedText() )->parse();
} elseif ( $exists['warning'] == 'thumb' ) {
// Swapped argument order compared with other messages for backwards compatibility
- $warning = wfMessage( 'fileexists-thumbnail-yes',
+ $warning = wfMessage( 'fileexists-thumbnail-yes',
$exists['thumbFile']->getTitle()->getPrefixedText(), $filename )->parse();
} elseif ( $exists['warning'] == 'thumb-name' ) {
// Image w/o '180px-' does not exists, but we do not like these filenames
@@ -675,7 +676,7 @@ class SpecialUpload extends SpecialPage {
/**
* Get a list of warnings
*
- * @param $filename String: local filename, e.g. 'file exists', 'non-descriptive filename'
+ * @param string $filename local filename, e.g. 'file exists', 'non-descriptive filename'
* @return Array: list of warning messages
*/
public static function ajaxGetExistsWarning( $filename ) {
@@ -716,6 +717,9 @@ class SpecialUpload extends SpecialPage {
$gallery->toHtml() . "</li>\n";
}
+ protected function getGroupName() {
+ return 'media';
+ }
}
/**
@@ -789,6 +793,8 @@ class UploadForm extends HTMLForm {
* @return Array: descriptor array
*/
protected function getSourceSection() {
+ global $wgCopyUploadsFromSpecialUpload;
+
if ( $this->mSessionKey ) {
return array(
'SessionKey' => array(
@@ -802,7 +808,9 @@ class UploadForm extends HTMLForm {
);
}
- $canUploadByUrl = UploadFromUrl::isEnabled() && UploadFromUrl::isAllowed( $this->getUser() );
+ $canUploadByUrl = UploadFromUrl::isEnabled()
+ && UploadFromUrl::isAllowed( $this->getUser() )
+ && $wgCopyUploadsFromSpecialUpload;
$radio = $canUploadByUrl;
$selectedSourceType = strtolower( $this->getRequest()->getText( 'wpSourceType', 'File' ) );
@@ -946,7 +954,7 @@ class UploadForm extends HTMLForm {
? 'filereuploadsummary'
: 'fileuploadsummary',
'default' => $this->mComment,
- 'cols' => intval( $this->getUser()->getOption( 'cols' ) ),
+ 'cols' => $this->getUser()->getIntOption( 'cols' ),
'rows' => 8,
)
);
@@ -1077,7 +1085,6 @@ class UploadForm extends HTMLForm {
$out = $this->getOutput();
$out->addJsConfigVars( $scriptVars );
-
$out->addModules( array(
'mediawiki.action.edit', // For <charinsert> support
'mediawiki.legacy.upload', // Old form stuff...
@@ -1106,7 +1113,7 @@ class UploadSourceField extends HTMLTextField {
* @return string
*/
function getLabelHtml( $cellAttributes = array() ) {
- $id = "wpSourceType{$this->mParams['upload-type']}";
+ $id = $this->mParams['id'];
$label = Html::rawElement( 'label', array( 'for' => $id ), $this->mLabel );
if ( !empty( $this->mParams['radio'] ) ) {
@@ -1134,4 +1141,3 @@ class UploadSourceField extends HTMLTextField {
: 60;
}
}
-
diff --git a/includes/specials/SpecialUploadStash.php b/includes/specials/SpecialUploadStash.php
index 1a00d731..ddf0c6da 100644
--- a/includes/specials/SpecialUploadStash.php
+++ b/includes/specials/SpecialUploadStash.php
@@ -57,7 +57,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
/**
* Execute page -- can output a file directly or show a listing of them.
*
- * @param $subPage String: subpage, e.g. in http://example.com/wiki/Special:UploadStash/foo.jpg, the "foo.jpg" part
+ * @param string $subPage subpage, e.g. in http://example.com/wiki/Special:UploadStash/foo.jpg, the "foo.jpg" part
* @return Boolean: success
*/
public function execute( $subPage ) {
@@ -73,7 +73,8 @@ class SpecialUploadStash extends UnlistedSpecialPage {
* If file available in stash, cats it out to the client as a simple HTTP response.
* n.b. Most sanity checking done in UploadStashLocalFile, so this is straightforward.
*
- * @param $key String: the key of a particular requested file
+ * @param string $key the key of a particular requested file
+ * @throws HttpError
* @return bool
*/
public function showUpload( $key ) {
@@ -113,6 +114,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
* application the transform parameters
*
* @param string $key
+ * @throws UploadStashBadPathException
* @return array
*/
private function parseKey( $key ) {
@@ -164,10 +166,11 @@ class SpecialUploadStash extends UnlistedSpecialPage {
/**
* Scale a file (probably with a locally installed imagemagick, or similar) and output it to STDOUT.
- * @param $file: File object
- * @param $params: scaling parameters ( e.g. array( width => '50' ) );
- * @param $flags: scaling flags ( see File:: constants )
+ * @param $file File
+ * @param array $params Scaling parameters ( e.g. array( width => '50' ) );
+ * @param int $flags Scaling flags ( see File:: constants )
* @throws MWException
+ * @throws UploadStashFileNotFoundException
* @return boolean success
*/
private function outputLocallyScaledThumb( $file, $params, $flags ) {
@@ -189,7 +192,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
// now we should construct a File, so we can get mime and other such info in a standard way
// n.b. mimetype may be different from original (ogx original -> jpeg thumb)
- $thumbFile = new UnregisteredLocalFile( false,
+ $thumbFile = new UnregisteredLocalFile( false,
$this->stash->repo, $thumbnailImage->getStoragePath(), false );
if ( !$thumbFile ) {
throw new UploadStashFileNotFoundException( "couldn't create local file object for thumbnail" );
@@ -258,6 +261,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
* Side effect: writes HTTP response to STDOUT.
*
* @param $file File object with a local path (e.g. UnregisteredLocalFile, LocalFile. Oddly these don't share an ancestor!)
+ * @throws SpecialUploadStashTooLargeException
* @return bool
*/
private function outputLocalFile( File $file ) {
@@ -273,8 +277,9 @@ class SpecialUploadStash extends UnlistedSpecialPage {
/**
* Output HTTP response of raw content
* Side effect: writes HTTP response to STDOUT.
- * @param $content String content
- * @param $contentType String mime type
+ * @param string $content content
+ * @param string $contentType mime type
+ * @throws SpecialUploadStashTooLargeException
* @return bool
*/
private function outputContents( $content, $contentType ) {
@@ -291,8 +296,8 @@ class SpecialUploadStash extends UnlistedSpecialPage {
* Output headers for streaming
* XXX unsure about encoding as binary; if we received from HTTP perhaps we should use that encoding, concatted with semicolon to mimeType as it usually is.
* Side effect: preps PHP to write headers to STDOUT.
- * @param String $contentType : string suitable for content-type header
- * @param String $size: length in bytes
+ * @param string $contentType : string suitable for content-type header
+ * @param string $size: length in bytes
*/
private static function outputFileHeaders( $contentType, $size ) {
header( "Content-Type: $contentType", true );
@@ -322,14 +327,9 @@ class SpecialUploadStash extends UnlistedSpecialPage {
/**
* Default action when we don't have a subpage -- just show links to the uploads we have,
* Also show a button to clear stashed files
- * @param $status [optional] Status: the result of processRequest
* @return bool
*/
- private function showUploads( $status = null ) {
- if ( $status === null ) {
- $status = Status::newGood();
- }
-
+ private function showUploads() {
// sets the title, etc.
$this->setHeaders();
$this->outputHeader();
@@ -344,7 +344,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
'name' => 'clear',
)
), $this->getContext(), 'clearStashedUploads' );
- $form->setSubmitCallback( array( __CLASS__ , 'tryClearStashedUploads' ) );
+ $form->setSubmitCallback( array( __CLASS__, 'tryClearStashedUploads' ) );
$form->setTitle( $this->getTitle() );
$form->setSubmitTextMsg( 'uploadstash-clear' );
diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php
index 58da77da..eef66914 100644
--- a/includes/specials/SpecialUserlogin.php
+++ b/includes/specials/SpecialUserlogin.php
@@ -95,9 +95,9 @@ class LoginForm extends SpecialPage {
$this->mReason = $request->getText( 'wpReason' );
$this->mCookieCheck = $request->getVal( 'wpCookieCheck' );
$this->mPosted = $request->wasPosted();
- $this->mCreateaccount = $request->getCheck( 'wpCreateaccount' );
$this->mCreateaccountMail = $request->getCheck( 'wpCreateaccountMail' )
&& $wgEnableEmail;
+ $this->mCreateaccount = $request->getCheck( 'wpCreateaccount' ) && !$this->mCreateaccountMail;
$this->mLoginattempt = $request->getCheck( 'wpLoginattempt' );
$this->mAction = $request->getVal( 'action' );
$this->mRemember = $request->getCheck( 'wpRemember' );
@@ -149,6 +149,23 @@ class LoginForm extends SpecialPage {
$this->load();
$this->setHeaders();
+ global $wgSecureLogin;
+ if (
+ $this->mType !== 'signup' &&
+ $wgSecureLogin &&
+ WebRequest::detectProtocol() !== 'https'
+ ) {
+ $title = $this->getFullTitle();
+ $query = array(
+ 'returnto' => $this->mReturnTo,
+ 'returntoquery' => $this->mReturnToQuery,
+ 'wpStickHTTPS' => $this->mStickHTTPS
+ );
+ $url = $title->getFullURL( $query, false, PROTO_HTTPS );
+ $this->getOutput()->redirect( $url );
+ return;
+ }
+
if ( $par == 'signup' ) { # Check for [[Special:Userlogin/signup]]
$this->mType = 'signup';
}
@@ -180,19 +197,22 @@ class LoginForm extends SpecialPage {
return;
}
- $u = $this->addNewaccountInternal();
-
- if ( $u == null ) {
+ $status = $this->addNewaccountInternal();
+ if( !$status->isGood() ) {
+ $error = $this->getOutput()->parse( $status->getWikiText() );
+ $this->mainLoginForm( $error );
return;
}
+ $u = $status->getValue();
+
// Wipe the initial password and mail a temporary one
$u->setPassword( null );
$u->saveSettings();
$result = $this->mailPasswordInternal( $u, false, 'createaccount-title', 'createaccount-text' );
wfRunHooks( 'AddNewAccount', array( $u, true ) );
- $u->addNewUserLogEntry( true, $this->mReason );
+ $u->addNewUserLogEntry( 'byemail', $this->mReason );
$out = $this->getOutput();
$out->setPageTitle( $this->msg( 'accmailtitle' ) );
@@ -210,18 +230,34 @@ class LoginForm extends SpecialPage {
* @return bool
*/
function addNewAccount() {
- global $wgUser, $wgEmailAuthentication, $wgLoginLanguageSelector;
+ global $wgContLang, $wgUser, $wgEmailAuthentication, $wgLoginLanguageSelector;
# Create the account and abort if there's a problem doing so
- $u = $this->addNewAccountInternal();
- if( $u == null ) {
+ $status = $this->addNewAccountInternal();
+ if( !$status->isGood() ) {
+ $error = $this->getOutput()->parse( $status->getWikiText() );
+ $this->mainLoginForm( $error );
return false;
}
- # If we showed up language selection links, and one was in use, be
- # smart (and sensible) and save that language as the user's preference
- if( $wgLoginLanguageSelector && $this->mLanguage ) {
- $u->setOption( 'language', $this->mLanguage );
+ $u = $status->getValue();
+
+ # Only save preferences if the user is not creating an account for someone else.
+ if ( $this->getUser()->isAnon() ) {
+ # If we showed up language selection links, and one was in use, be
+ # smart (and sensible) and save that language as the user's preference
+ if( $wgLoginLanguageSelector && $this->mLanguage ) {
+ $u->setOption( 'language', $this->mLanguage );
+ } else {
+
+ # Otherwise the user's language preference defaults to $wgContLang,
+ # but it may be better to set it to their preferred $wgContLang variant,
+ # based on browser preferences or URL parameters.
+ $u->setOption( 'language', $wgContLang->getPreferredVariant() );
+ }
+ if ( $wgContLang->hasVariants() ) {
+ $u->setOption( 'variant', $wgContLang->getPreferredVariant() );
+ }
}
$out = $this->getOutput();
@@ -250,7 +286,7 @@ class LoginForm extends SpecialPage {
// wrong.
$this->getContext()->setUser( $u );
wfRunHooks( 'AddNewAccount', array( $u, false ) );
- $u->addNewUserLogEntry();
+ $u->addNewUserLogEntry( 'create' );
if( $this->hasSessionCookie() ) {
$this->successfulCreation();
} else {
@@ -262,23 +298,24 @@ class LoginForm extends SpecialPage {
$out->addWikiMsg( 'accountcreatedtext', $u->getName() );
$out->addReturnTo( $this->getTitle() );
wfRunHooks( 'AddNewAccount', array( $u, false ) );
- $u->addNewUserLogEntry( false, $this->mReason );
+ $u->addNewUserLogEntry( 'create2', $this->mReason );
}
return true;
}
/**
+ * Make a new user account using the loaded data.
* @private
- * @return bool|User
+ * @throws PermissionsError|ReadOnlyError
+ * @return Status
*/
- function addNewAccountInternal() {
+ public function addNewAccountInternal() {
global $wgAuth, $wgMemc, $wgAccountCreationThrottle,
$wgMinimalPasswordLength, $wgEmailConfirmToEdit;
// If the user passes an invalid domain, something is fishy
if( !$wgAuth->validDomain( $this->mDomain ) ) {
- $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() );
- return false;
+ return Status::newFatal( 'wrongpassword' );
}
// If we are not allowing users to login locally, we should be checking
@@ -287,10 +324,14 @@ class LoginForm extends SpecialPage {
// create a local account and login as any domain user). We only need
// to check this for domains that aren't local.
if( 'local' != $this->mDomain && $this->mDomain != '' ) {
- if( !$wgAuth->canCreateAccounts() && ( !$wgAuth->userExists( $this->mUsername )
- || !$wgAuth->authenticate( $this->mUsername, $this->mPassword ) ) ) {
- $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() );
- return false;
+ if(
+ !$wgAuth->canCreateAccounts() &&
+ (
+ !$wgAuth->userExists( $this->mUsername ) ||
+ !$wgAuth->authenticate( $this->mUsername, $this->mPassword )
+ )
+ ) {
+ return Status::newFatal( 'wrongpassword' );
}
}
@@ -301,28 +342,28 @@ class LoginForm extends SpecialPage {
# Request forgery checks.
if ( !self::getCreateaccountToken() ) {
self::setCreateaccountToken();
- $this->mainLoginForm( $this->msg( 'nocookiesfornew' )->parse() );
- return false;
+ return Status::newFatal( 'nocookiesfornew' );
}
# The user didn't pass a createaccount token
if ( !$this->mToken ) {
- $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() );
- return false;
+ return Status::newFatal( 'sessionfailure' );
}
# Validate the createaccount token
if ( $this->mToken !== self::getCreateaccountToken() ) {
- $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() );
- return false;
+ return Status::newFatal( 'sessionfailure' );
}
# Check permissions
$currentUser = $this->getUser();
+ $creationBlock = $currentUser->isBlockedFromCreateAccount();
if ( !$currentUser->isAllowed( 'createaccount' ) ) {
throw new PermissionsError( 'createaccount' );
- } elseif ( $currentUser->isBlockedFromCreateAccount() ) {
- $this->userBlockedMessage( $currentUser->isBlockedFromCreateAccount() );
+ } elseif ( $creationBlock instanceof Block ) {
+ // Throws an ErrorPageError.
+ $this->userBlockedMessage( $creationBlock );
+ // This should never be reached.
return false;
}
@@ -334,58 +375,45 @@ class LoginForm extends SpecialPage {
$ip = $this->getRequest()->getIP();
if ( $currentUser->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) {
- $this->mainLoginForm( $this->msg( 'sorbs_create_account_reason' )->text() . ' ' . $this->msg( 'parentheses', $ip )->escaped() );
- return false;
+ return Status::newFatal( 'sorbs_create_account_reason' );
}
# Now create a dummy user ($u) and check if it is valid
$name = trim( $this->mUsername );
$u = User::newFromName( $name, 'creatable' );
if ( !is_object( $u ) ) {
- $this->mainLoginForm( $this->msg( 'noname' )->text() );
- return false;
- }
-
- if ( 0 != $u->idForName() ) {
- $this->mainLoginForm( $this->msg( 'userexists' )->text() );
- return false;
+ return Status::newFatal( 'noname' );
+ } elseif ( 0 != $u->idForName() ) {
+ return Status::newFatal( 'userexists' );
}
- if ( 0 != strcmp( $this->mPassword, $this->mRetype ) ) {
- $this->mainLoginForm( $this->msg( 'badretype' )->text() );
- return false;
- }
+ if ( $this->mCreateaccountMail ) {
+ # do not force a password for account creation by email
+ # set invalid password, it will be replaced later by a random generated password
+ $this->mPassword = null;
+ } else {
+ if ( $this->mPassword !== $this->mRetype ) {
+ return Status::newFatal( 'badretype' );
+ }
- # check for minimal password length
- $valid = $u->getPasswordValidity( $this->mPassword );
- if ( $valid !== true ) {
- if ( !$this->mCreateaccountMail ) {
- if ( is_array( $valid ) ) {
- $message = array_shift( $valid );
- $params = $valid;
- } else {
- $message = $valid;
- $params = array( $wgMinimalPasswordLength );
+ # check for minimal password length
+ $valid = $u->getPasswordValidity( $this->mPassword );
+ if ( $valid !== true ) {
+ if ( !is_array( $valid ) ) {
+ $valid = array( $valid, $wgMinimalPasswordLength );
}
- $this->mainLoginForm( $this->msg( $message, $params )->text() );
- return false;
- } else {
- # do not force a password for account creation by email
- # set invalid password, it will be replaced later by a random generated password
- $this->mPassword = null;
+ return call_user_func_array( 'Status::newFatal', $valid );
}
}
# if you need a confirmed email address to edit, then obviously you
# need an email address.
- if ( $wgEmailConfirmToEdit && empty( $this->mEmail ) ) {
- $this->mainLoginForm( $this->msg( 'noemailtitle' )->text() );
- return false;
+ if ( $wgEmailConfirmToEdit && strval( $this->mEmail ) === '' ) {
+ return Status::newFatal( 'noemailtitle' );
}
- if( !empty( $this->mEmail ) && !Sanitizer::validateEmail( $this->mEmail ) ) {
- $this->mainLoginForm( $this->msg( 'invalidemailaddress' )->text() );
- return false;
+ if ( strval( $this->mEmail ) !== '' && !Sanitizer::validateEmail( $this->mEmail ) ) {
+ return Status::newFatal( 'invalidemailaddress' );
}
# Set some additional data so the AbortNewAccount hook can be used for
@@ -397,8 +425,7 @@ class LoginForm extends SpecialPage {
if( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError ) ) ) {
// Hook point to add extra creation throttles and blocks
wfDebug( "LoginForm::addNewAccountInternal: a hook blocked creation\n" );
- $this->mainLoginForm( $abortError );
- return false;
+ return Status::newFatal( new RawMessage( $abortError ) );
}
// Hook point to check for exempt from account creation throttle
@@ -412,16 +439,14 @@ class LoginForm extends SpecialPage {
$wgMemc->set( $key, 0, 86400 );
}
if ( $value >= $wgAccountCreationThrottle ) {
- $this->throttleHit( $wgAccountCreationThrottle );
- return false;
+ return Status::newFatal( 'acct_creation_throttle_hit', $wgAccountCreationThrottle );
}
$wgMemc->incr( $key );
}
}
if( !$wgAuth->addUser( $u, $this->mPassword, $this->mEmail, $this->mRealName ) ) {
- $this->mainLoginForm( $this->msg( 'externaldberror' )->text() );
- return false;
+ return Status::newFatal( 'externaldberror' );
}
self::clearCreateaccountToken();
@@ -434,13 +459,16 @@ class LoginForm extends SpecialPage {
*
* @param $u User object.
* @param $autocreate boolean -- true if this is an autocreation via auth plugin
- * @return User object.
+ * @return Status object, with the User object in the value member on success
* @private
*/
function initUser( $u, $autocreate ) {
global $wgAuth;
- $u->addToDatabase();
+ $status = $u->addToDatabase();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
if ( $wgAuth->allowPasswordChange() ) {
$u->setPassword( $this->mPassword );
@@ -464,10 +492,9 @@ class LoginForm extends SpecialPage {
$u->saveSettings();
# Update user count
- $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 );
- $ssUpdate->doUpdate();
+ DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 0, 0, 0, 1 ) );
- return $u;
+ return Status::newGood( $u );
}
/**
@@ -533,7 +560,7 @@ class LoginForm extends SpecialPage {
}
$isAutoCreated = false;
- if ( 0 == $u->getID() ) {
+ if ( $u->getID() == 0 ) {
$status = $this->attemptAutoCreate( $u );
if ( $status !== self::SUCCESS ) {
return $status;
@@ -543,8 +570,9 @@ class LoginForm extends SpecialPage {
} else {
global $wgExternalAuthType, $wgAutocreatePolicy;
if ( $wgExternalAuthType && $wgAutocreatePolicy != 'never'
- && is_object( $this->mExtUser )
- && $this->mExtUser->authenticate( $this->mPassword ) ) {
+ && is_object( $this->mExtUser )
+ && $this->mExtUser->authenticate( $this->mPassword )
+ ) {
# The external user and local user have the same name and
# password, so we assume they're the same.
$this->mExtUser->linkToLocal( $u->getID() );
@@ -588,7 +616,7 @@ class LoginForm extends SpecialPage {
// faces etc will probably just fail cleanly here.
$retval = self::RESET_PASS;
} else {
- $retval = ( $this->mPassword == '' ) ? self::EMPTY_PASS : self::WRONG_PASS;
+ $retval = ( $this->mPassword == '' ) ? self::EMPTY_PASS : self::WRONG_PASS;
}
} elseif ( $wgBlockDisablesLogin && $u->isBlocked() ) {
// If we've enabled it, make it so that a blocked user cannot login
@@ -620,7 +648,7 @@ class LoginForm extends SpecialPage {
/**
* Increment the login attempt throttle hit count for the (username,current IP)
* tuple unless the throttle was already reached.
- * @param $username string The user name
+ * @param string $username The user name
* @return Bool|Integer The integer hit count or True if it is already at the limit
*/
public static function incLoginThrottle( $username ) {
@@ -648,7 +676,7 @@ class LoginForm extends SpecialPage {
/**
* Clear the login attempt throttle hit count for the (username,current IP) tuple.
- * @param $username string The user name
+ * @param string $username The user name
* @return void
*/
public static function clearLoginThrottle( $username ) {
@@ -713,24 +741,36 @@ class LoginForm extends SpecialPage {
}
wfDebug( __METHOD__ . ": creating account\n" );
- $this->initUser( $user, true );
+ $status = $this->initUser( $user, true );
+
+ if ( !$status->isOK() ) {
+ $errors = $status->getErrorsByType( 'error' );
+ $this->mAbortLoginErrorMsg = $errors[0]['message'];
+ return self::ABORTED;
+ }
+
return self::SUCCESS;
}
function processLogin() {
- global $wgMemc, $wgLang;
+ global $wgMemc, $wgLang, $wgSecureLogin;
switch ( $this->authenticateUserData() ) {
case self::SUCCESS:
# We've verified now, update the real record
$user = $this->getUser();
- if( (bool)$this->mRemember != (bool)$user->getOption( 'rememberpassword' ) ) {
+ if( (bool)$this->mRemember != $user->getBoolOption( 'rememberpassword' ) ) {
$user->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 );
$user->saveSettings();
} else {
$user->invalidateCache();
}
- $user->setCookies();
+
+ if( $wgSecureLogin && !$this->mStickHTTPS ) {
+ $user->setCookies( null, false );
+ } else {
+ $user->setCookies();
+ }
self::clearLoginToken();
// Reset the throttle
@@ -803,8 +843,11 @@ class LoginForm extends SpecialPage {
}
}
+ /**
+ * @param $error string
+ */
function resetLoginForm( $error ) {
- $this->getOutput()->addHTML( Xml::element('p', array( 'class' => 'error' ), $error ) );
+ $this->getOutput()->addHTML( Xml::element( 'p', array( 'class' => 'error' ), $error ) );
$reset = new SpecialChangePassword();
$reset->setContext( $this->getContext() );
$reset->execute( null );
@@ -813,12 +856,12 @@ class LoginForm extends SpecialPage {
/**
* @param $u User object
* @param $throttle Boolean
- * @param $emailTitle String: message name of email title
- * @param $emailText String: message name of email text
+ * @param string $emailTitle message name of email title
+ * @param string $emailText message name of email text
* @return Status object
*/
function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle', $emailText = 'passwordremindertext' ) {
- global $wgServer, $wgScript, $wgNewPasswordExpiry;
+ global $wgCanonicalServer, $wgScript, $wgNewPasswordExpiry;
if ( $u->getEmail() == '' ) {
return Status::newFatal( 'noemail', $u->getName() );
@@ -835,14 +878,13 @@ class LoginForm extends SpecialPage {
$u->setNewpassword( $np, $throttle );
$u->saveSettings();
$userLanguage = $u->getOption( 'language' );
- $m = $this->msg( $emailText, $ip, $u->getName(), $np, $wgServer . $wgScript,
+ $m = $this->msg( $emailText, $ip, $u->getName(), $np, '<' . $wgCanonicalServer . $wgScript . '>',
round( $wgNewPasswordExpiry / 86400 ) )->inLanguage( $userLanguage )->text();
$result = $u->sendMail( $this->msg( $emailTitle )->inLanguage( $userLanguage )->text(), $m );
return $result;
}
-
/**
* Run any hooks registered for logins, then HTTP redirect to
* $this->mReturnTo (or Main Page if that's undefined). Formerly we had a
@@ -860,7 +902,8 @@ class LoginForm extends SpecialPage {
wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) );
if( $injected_html !== '' ) {
- $this->displaySuccessfulLogin( 'loginsuccess', $injected_html );
+ $this->displaySuccessfulAction( $this->msg( 'loginsuccesstitle' ),
+ 'loginsuccess', $injected_html );
} else {
$this->executeReturnTo( 'successredirect' );
}
@@ -876,7 +919,7 @@ class LoginForm extends SpecialPage {
# Run any hooks; display injected HTML
$currentUser = $this->getUser();
$injected_html = '';
- $welcome_creation_msg = 'welcomecreation';
+ $welcome_creation_msg = 'welcomecreation-msg';
wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) );
@@ -887,18 +930,21 @@ class LoginForm extends SpecialPage {
*/
wfRunHooks( 'BeforeWelcomeCreation', array( &$welcome_creation_msg, &$injected_html ) );
- $this->displaySuccessfulLogin( $welcome_creation_msg, $injected_html );
+ $this->displaySuccessfulAction( $this->msg( 'welcomeuser', $this->getUser()->getName() ),
+ $welcome_creation_msg, $injected_html );
}
/**
- * Display a "login successful" page.
+ * Display an "successful action" page.
+ *
+ * @param string|Message $title page's title
* @param $msgname string
* @param $injected_html string
*/
- private function displaySuccessfulLogin( $msgname, $injected_html ) {
+ private function displaySuccessfulAction( $title, $msgname, $injected_html ) {
$out = $this->getOutput();
- $out->setPageTitle( $this->msg( 'loginsuccesstitle' ) );
- if( $msgname ){
+ $out->setPageTitle( $title );
+ if ( $msgname ) {
$out->addWikiMsg( $msgname, wfEscapeWikiText( $this->getUser()->getName() ) );
}
@@ -913,6 +959,7 @@ class LoginForm extends SpecialPage {
* User::isBlockedFromCreateAccount(), which gets this block, ignores the 'hardblock'
* setting on blocks (bug 13611).
* @param $block Block the block causing this error
+ * @throws ErrorPageError
*/
function userBlockedMessage( Block $block ) {
# Let's be nice about this, it's likely that this feature will be used
@@ -922,23 +969,15 @@ class LoginForm extends SpecialPage {
# haven't bothered to log out before trying to create an account to
# evade it, but we'll leave that to their guilty conscience to figure
# out.
-
- $out = $this->getOutput();
- $out->setPageTitle( $this->msg( 'cantcreateaccounttitle' ) );
-
- $block_reason = $block->mReason;
- if ( strval( $block_reason ) === '' ) {
- $block_reason = $this->msg( 'blockednoreason' )->text();
- }
-
- $out->addWikiMsg(
+ throw new ErrorPageError(
+ 'cantcreateaccounttitle',
'cantcreateaccount-text',
- $block->getTarget(),
- $block_reason,
- $block->getByName()
+ array(
+ $block->getTarget(),
+ $block->mReason ? $block->mReason : $this->msg( 'blockednoreason' )->text(),
+ $block->getByName()
+ )
);
-
- $this->executeReturnTo( 'error' );
}
/**
@@ -965,14 +1004,22 @@ class LoginForm extends SpecialPage {
$returnToTitle = Title::newMainPage();
}
+ if ( $wgSecureLogin && !$this->mStickHTTPS ) {
+ $options = array( 'http' );
+ $proto = PROTO_HTTP;
+ } elseif( $wgSecureLogin ) {
+ $options = array( 'https' );
+ $proto = PROTO_HTTPS;
+ } else {
+ $options = array();
+ $proto = PROTO_RELATIVE;
+ }
+
if ( $type == 'successredirect' ) {
- $redirectUrl = $returnToTitle->getFullURL( $returnToQuery );
- if( $wgSecureLogin && !$this->mStickHTTPS ) {
- $redirectUrl = preg_replace( '/^https:/', 'http:', $redirectUrl );
- }
+ $redirectUrl = $returnToTitle->getFullURL( $returnToQuery, false, $proto );
$this->getOutput()->redirect( $redirectUrl );
} else {
- $this->getOutput()->addReturnTo( $returnToTitle, $returnToQuery );
+ $this->getOutput()->addReturnTo( $returnToTitle, $returnToQuery, null, $options );
}
}
@@ -983,7 +1030,7 @@ class LoginForm extends SpecialPage {
global $wgEnableEmail, $wgEnableUserEmail;
global $wgHiddenPrefs, $wgLoginLanguageSelector;
global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration;
- global $wgSecureLogin, $wgPasswordResetRoutes;
+ global $wgSecureLogin, $wgSecureLoginDefaultHTTPS, $wgPasswordResetRoutes;
$titleObj = $this->getTitle();
$user = $this->getUser();
@@ -1003,7 +1050,8 @@ class LoginForm extends SpecialPage {
}
}
- if ( $this->mUsername == '' ) {
+ // Pre-fill username (if not creating an account, bug 44775).
+ if ( $this->mUsername == '' && $this->mType != 'signup' ) {
if ( $user->isLoggedIn() ) {
$this->mUsername = $user->getName();
} else {
@@ -1016,6 +1064,7 @@ class LoginForm extends SpecialPage {
$q = 'action=submitlogin&type=signup';
$linkq = 'type=login';
$linkmsg = 'gotaccount';
+ $this->getOutput()->addModules( 'mediawiki.special.userlogin.signup' );
} else {
$template = new UserloginTemplate();
$q = 'action=submitlogin&type=login';
@@ -1047,6 +1096,11 @@ class LoginForm extends SpecialPage {
$template->set( 'link', '' );
}
+ // Decide if we default stickHTTPS on
+ if ( $wgSecureLoginDefaultHTTPS && $this->mAction != 'submitlogin' && !$this->mLoginattempt ) {
+ $this->mStickHTTPS = true;
+ }
+
$resetLink = $this->mType == 'signup'
? null
: is_array( $wgPasswordResetRoutes ) && in_array( true, array_values( $wgPasswordResetRoutes ) );
@@ -1055,6 +1109,7 @@ class LoginForm extends SpecialPage {
$template->set( 'name', $this->mUsername );
$template->set( 'password', $this->mPassword );
$template->set( 'retype', $this->mRetype );
+ $template->set( 'createemailset', $this->mCreateaccountMail );
$template->set( 'email', $this->mEmail );
$template->set( 'realname', $this->mRealName );
$template->set( 'domain', $this->mDomain );
@@ -1213,15 +1268,21 @@ class LoginForm extends SpecialPage {
* Renew the user's session id, using strong entropy
*/
private function renewSessionId() {
- if ( wfCheckEntropy() ) {
+ global $wgSecureLogin, $wgCookieSecure;
+ if( $wgSecureLogin && !$this->mStickHTTPS ) {
+ $wgCookieSecure = false;
+ }
+
+ // If either we don't trust PHP's entropy, or if we need
+ // to change cookie settings when logging in because of
+ // wpStickHTTPS, then change the session ID manually.
+ $cookieParams = session_get_cookie_params();
+ if ( wfCheckEntropy() && $wgCookieSecure == $cookieParams['secure'] ) {
session_regenerate_id( false );
} else {
- //If we don't trust PHP's entropy, we have to replace the session manually
$tmp = $_SESSION;
- session_unset();
- session_write_close();
- session_id( MWCryptRand::generateHex( 32 ) );
- session_start();
+ session_destroy();
+ wfSetupSession( MWCryptRand::generateHex( 32 ) );
$_SESSION = $tmp;
}
}
@@ -1260,13 +1321,6 @@ class LoginForm extends SpecialPage {
}
/**
- * @private
- */
- function throttleHit( $limit ) {
- $this->mainLoginForm( $this->msg( 'acct_creation_throttle_hit' )->numParams( $limit )->parse() );
- }
-
- /**
* Produce a bar of links which allow the user to select another language
* during login/registration but retain "returnto"
*
@@ -1295,8 +1349,8 @@ class LoginForm extends SpecialPage {
* Create a language selector link for a particular language
* Links back to this page preserving type and returnto
*
- * @param $text Link text
- * @param $lang Language code
+ * @param string $text Link text
+ * @param string $lang Language code
* @return string
*/
function makeLanguageSelectorLink( $text, $lang ) {
@@ -1324,4 +1378,8 @@ class LoginForm extends SpecialPage {
$query
);
}
+
+ protected function getGroupName() {
+ return 'login';
+ }
}
diff --git a/includes/specials/SpecialUserlogout.php b/includes/specials/SpecialUserlogout.php
index ab2bf0ac..d957e875 100644
--- a/includes/specials/SpecialUserlogout.php
+++ b/includes/specials/SpecialUserlogout.php
@@ -49,8 +49,11 @@ class SpecialUserlogout extends UnlistedSpecialPage {
$oldName = $user->getName();
$user->logout();
+ $loginURL = SpecialPage::getTitleFor( 'Userlogin' )->getFullURL(
+ $this->getRequest()->getValues( 'returnto', 'returntoquery' ) );
+
$out = $this->getOutput();
- $out->addWikiMsg( 'logouttext' );
+ $out->addWikiMsg( 'logouttext', $loginURL );
// Hook.
$injected_html = '';
@@ -59,4 +62,8 @@ class SpecialUserlogout extends UnlistedSpecialPage {
$out->returnToMain();
}
+
+ protected function getGroupName() {
+ return 'login';
+ }
}
diff --git a/includes/specials/SpecialUserrights.php b/includes/specials/SpecialUserrights.php
index 9f5a48a5..d4baae28 100644
--- a/includes/specials/SpecialUserrights.php
+++ b/includes/specials/SpecialUserrights.php
@@ -54,7 +54,7 @@ class UserrightsPage extends SpecialPage {
|| !empty( $available['remove'] )
|| ( ( $this->isself || !$checkIfSelf ) &&
( !empty( $available['add-self'] )
- || !empty( $available['remove-self'] ) ) );
+ || !empty( $available['remove-self'] ) ) );
}
/**
@@ -62,6 +62,7 @@ class UserrightsPage extends SpecialPage {
* Depending on the submit button used, call a form or a save function.
*
* @param $par Mixed: string if any subpage provided, else null
+ * @throws UserBlockedError|PermissionsError
*/
public function execute( $par ) {
// If the visitor doesn't have permissions to assign or remove
@@ -152,8 +153,8 @@ class UserrightsPage extends SpecialPage {
* Save user groups changes in the database.
* Data comes from the editUserGroupsForm() form function
*
- * @param $username String: username to apply changes to.
- * @param $reason String: reason for group change
+ * @param string $username username to apply changes to.
+ * @param string $reason reason for group change
* @return null
*/
function saveUserGroups( $username, $reason = '' ) {
@@ -188,9 +189,9 @@ class UserrightsPage extends SpecialPage {
* Save user groups changes in the database.
*
* @param $user User object
- * @param $add Array of groups to add
- * @param $remove Array of groups to remove
- * @param $reason String: reason for group change
+ * @param array $add of groups to add
+ * @param array $remove of groups to remove
+ * @param string $reason reason for group change
* @return Array: Tuple of added, then removed groups
*/
function doSaveUserGroups( $user, $add, $remove, $reason = '' ) {
@@ -239,26 +240,25 @@ class UserrightsPage extends SpecialPage {
return array( $add, $remove );
}
-
/**
* Add a rights log entry for an action.
*/
function addLogEntry( $user, $oldGroups, $newGroups, $reason ) {
- $log = new LogPage( 'rights' );
-
- $log->addEntry( 'rights',
- $user->getUserPage(),
- $reason,
- array(
- $this->makeGroupNameListForLog( $oldGroups ),
- $this->makeGroupNameListForLog( $newGroups )
- )
- );
+ $logEntry = new ManualLogEntry( 'rights', 'rights' );
+ $logEntry->setPerformer( $this->getUser() );
+ $logEntry->setTarget( $user->getUserPage() );
+ $logEntry->setComment( $reason );
+ $logEntry->setParameters( array(
+ '4::oldgroups' => $oldGroups,
+ '5::newgroups' => $newGroups,
+ ) );
+ $logid = $logEntry->insert();
+ $logEntry->publish( $logid );
}
/**
* Edit user groups membership
- * @param $username String: name of the user.
+ * @param string $username name of the user.
*/
function editUserGroupsForm( $username ) {
$status = $this->fetchUser( $username );
@@ -354,7 +354,16 @@ class UserrightsPage extends SpecialPage {
}
}
+ /**
+ * Make a list of group names to be stored as parameter for log entries
+ *
+ * @deprecated in 1.21; use LogFormatter instead.
+ * @param $ids array
+ * @return string
+ */
function makeGroupNameListForLog( $ids ) {
+ wfDeprecated( __METHOD__, '1.21' );
+
if( empty( $ids ) ) {
return '';
} else {
@@ -369,7 +378,7 @@ class UserrightsPage extends SpecialPage {
global $wgScript;
$this->getOutput()->addHTML(
Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'name' => 'uluser', 'id' => 'mw-userrights-form1' ) ) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
+ Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
Xml::fieldset( $this->msg( 'userrights-lookup-user' )->text() ) .
Xml::inputLabel( $this->msg( 'userrights-user-editname' )->text(), 'user', 'username', 30, str_replace( '_', ' ', $this->mTarget ) ) . ' ' .
Xml::submitButton( $this->msg( 'editusergroup' )->text() ) .
@@ -383,7 +392,7 @@ class UserrightsPage extends SpecialPage {
* form will be able to manipulate based on the current user's system
* permissions.
*
- * @param $groups Array: list of groups the given user is in
+ * @param array $groups list of groups the given user is in
* @return Array: Tuple of addable, then removable groups
*/
protected function splitGroups( $groups ) {
@@ -409,27 +418,41 @@ class UserrightsPage extends SpecialPage {
*/
protected function showEditUserGroupsForm( $user, $groups ) {
$list = array();
+ $membersList = array();
foreach( $groups as $group ) {
$list[] = self::buildGroupLink( $group );
+ $membersList[] = self::buildGroupMemberLink( $group );
}
- $autolist = array();
+ $autoList = array();
+ $autoMembersList = array();
if ( $user instanceof User ) {
foreach( Autopromote::getAutopromoteGroups( $user ) as $group ) {
- $autolist[] = self::buildGroupLink( $group );
+ $autoList[] = self::buildGroupLink( $group );
+ $autoMembersList[] = self::buildGroupMemberLink( $group );
}
}
+ $language = $this->getLanguage();
+ $displayedList = $this->msg( 'userrights-groupsmember-type',
+ $language->listToText( $list ),
+ $language->listToText( $membersList )
+ )->plain();
+ $displayedAutolist = $this->msg( 'userrights-groupsmember-type',
+ $language->listToText( $autoList ),
+ $language->listToText( $autoMembersList )
+ )->plain();
+
$grouplist = '';
$count = count( $list );
- if( $count > 0 ) {
+ if ( $count > 0 ) {
$grouplist = $this->msg( 'userrights-groupsmember', $count, $user->getName() )->parse();
- $grouplist = '<p>' . $grouplist . ' ' . $this->getLanguage()->listToText( $list ) . "</p>\n";
+ $grouplist = '<p>' . $grouplist . ' ' . $displayedList . "</p>\n";
}
- $count = count( $autolist );
- if( $count > 0 ) {
+ $count = count( $autoList );
+ if ( $count > 0 ) {
$autogrouplistintro = $this->msg( 'userrights-groupsmember-auto', $count, $user->getName() )->parse();
- $grouplist .= '<p>' . $autogrouplistintro . ' ' . $this->getLanguage()->listToText( $autolist ) . "</p>\n";
+ $grouplist .= '<p>' . $autogrouplistintro . ' ' . $displayedAutolist . "</p>\n";
}
$userToolLinks = Linker::userToolLinks(
@@ -479,10 +502,17 @@ class UserrightsPage extends SpecialPage {
* @return string
*/
private static function buildGroupLink( $group ) {
- static $cache = array();
- if( !isset( $cache[$group] ) )
- $cache[$group] = User::makeGroupLinkHtml( $group, htmlspecialchars( User::getGroupName( $group ) ) );
- return $cache[$group];
+ return User::makeGroupLinkHtml( $group, User::getGroupName( $group ) );
+ }
+
+ /**
+ * Format a link to a group member description page
+ *
+ * @param $group string
+ * @return string
+ */
+ private static function buildGroupMemberLink( $group ) {
+ return User::makeGroupLinkHtml( $group, User::getGroupMember( $group ) );
}
/**
@@ -497,7 +527,7 @@ class UserrightsPage extends SpecialPage {
* Adds a table with checkboxes where you can select what groups to add/remove
*
* @todo Just pass the username string?
- * @param $usergroups Array: groups the user belongs to
+ * @param array $usergroups groups the user belongs to
* @param $user User a user object
* @return string XHTML table element with checkboxes
*/
@@ -534,14 +564,14 @@ class UserrightsPage extends SpecialPage {
}
# Build the HTML table
- $ret .= Xml::openElement( 'table', array( 'class' => 'mw-userrights-groups' ) ) .
+ $ret .= Xml::openElement( 'table', array( 'class' => 'mw-userrights-groups' ) ) .
"<tr>\n";
foreach( $columns as $name => $column ) {
if( $column === array() )
continue;
$ret .= Xml::element( 'th', null, $this->msg( 'userrights-' . $name . '-col', count( $column ) )->text() );
}
- $ret.= "</tr>\n<tr>\n";
+ $ret .= "</tr>\n<tr>\n";
foreach( $columns as $column ) {
if( $column === array() )
continue;
@@ -581,7 +611,7 @@ class UserrightsPage extends SpecialPage {
}
/**
- * @param $group string: the name of the group to check
+ * @param string $group the name of the group to check
* @return bool Can we add the group?
*/
private function canAdd( $group ) {
@@ -592,7 +622,7 @@ class UserrightsPage extends SpecialPage {
/**
* Returns $this->getUser()->changeableGroups()
*
- * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ) , 'add-self' => array( addablegroups to self), 'remove-self' => array( removable groups from self) )
+ * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ), 'add-self' => array( addablegroups to self ), 'remove-self' => array( removable groups from self ) )
*/
function changeableGroups() {
return $this->getUser()->changeableGroups();
@@ -609,4 +639,8 @@ class UserrightsPage extends SpecialPage {
$output->addHTML( Xml::element( 'h2', null, $rightsLogPage->getName()->text() ) );
LogEventsList::showLogExtract( $output, 'rights', $user->getUserPage() );
}
+
+ protected function getGroupName() {
+ return 'users';
+ }
}
diff --git a/includes/specials/SpecialVersion.php b/includes/specials/SpecialVersion.php
index 4e5b6bf5..81d17817 100644
--- a/includes/specials/SpecialVersion.php
+++ b/includes/specials/SpecialVersion.php
@@ -40,7 +40,7 @@ class SpecialVersion extends SpecialPage {
'https://svn.wikimedia.org/svnroot/mediawiki' => 'https://svn.wikimedia.org/viewvc/mediawiki',
);
- public function __construct(){
+ public function __construct() {
parent::__construct( 'Version' );
}
@@ -48,29 +48,43 @@ class SpecialVersion extends SpecialPage {
* main()
*/
public function execute( $par ) {
- global $wgSpecialVersionShowHooks;
+ global $wgSpecialVersionShowHooks, $IP;
$this->setHeaders();
$this->outputHeader();
$out = $this->getOutput();
$out->allowClickjacking();
- $text =
- $this->getMediaWikiCredits() .
- $this->softwareInformation() .
- $this->getEntryPointInfo() .
- $this->getExtensionCredits();
- if ( $wgSpecialVersionShowHooks ) {
- $text .= $this->getWgHooks();
- }
+ if( $par !== 'Credits' ) {
+ $text =
+ $this->getMediaWikiCredits() .
+ $this->softwareInformation() .
+ $this->getEntryPointInfo() .
+ $this->getExtensionCredits();
+ if ( $wgSpecialVersionShowHooks ) {
+ $text .= $this->getWgHooks();
+ }
- $out->addWikiText( $text );
- $out->addHTML( $this->IPInfo() );
+ $out->addWikiText( $text );
+ $out->addHTML( $this->IPInfo() );
- if ( $this->getRequest()->getVal( 'easteregg' ) ) {
- if ( $this->showEasterEgg() ) {
- // TODO: put something interesting here
+ if ( $this->getRequest()->getVal( 'easteregg' ) ) {
+ if ( $this->showEasterEgg() ) {
+ // TODO: put something interesting here
+ }
}
+ } else {
+ // Credits sub page
+
+ // Header
+ $out->addHTML( wfMessage( 'version-credits-summary' )->parseAsBlock() );
+
+ $wikiText = file_get_contents( $IP . '/CREDITS' );
+
+ // Take everything from the first section onwards, to remove the (not localized) header
+ $wikiText = substr( $wikiText, strpos( $wikiText, '==' ) );
+
+ $out->addWikiText( $wikiText );
}
}
@@ -100,6 +114,12 @@ class SpecialVersion extends SpecialPage {
public static function getCopyrightAndAuthorList() {
global $wgLang;
+ if ( defined( 'MEDIAWIKI_INSTALL' ) ) {
+ $othersLink = '[http://www.mediawiki.org/wiki/Special:Version/Credits ' . wfMessage( 'version-poweredby-others' )->text() . ']';
+ } else {
+ $othersLink = '[[Special:Version/Credits|' . wfMessage( 'version-poweredby-others' )->text() . ']]';
+ }
+
$authorList = array(
'Magnus Manske', 'Brion Vibber', 'Lee Daniel Crocker',
'Tim Starling', 'Erik Möller', 'Gabriel Wicke', 'Ævar Arnfjörð Bjarmason',
@@ -108,10 +128,7 @@ class SpecialVersion extends SpecialPage {
'Alexandre Emsenhuber', 'Siebrand Mazeland', 'Chad Horohoe',
'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed',
'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Antoine Musso',
- 'Timo Tijhof',
- '[{{SERVER}}{{SCRIPTPATH}}/CREDITS ' .
- wfMessage( 'version-poweredby-others' )->text() .
- ']'
+ 'Timo Tijhof', 'Daniel Kinzler', 'Jeroen De Dauw', $othersLink
);
return wfMessage( 'version-poweredby-credits', date( 'Y' ),
@@ -131,14 +148,14 @@ class SpecialVersion extends SpecialPage {
// wikimarkup can be used.
$software = array();
$software['[https://www.mediawiki.org/ MediaWiki]'] = self::getVersionLinked();
- $software['[http://www.php.net/ PHP]'] = phpversion() . " (" . php_sapi_name() . ")";
+ $software['[http://www.php.net/ PHP]'] = phpversion() . " (" . PHP_SAPI . ")";
$software[$dbr->getSoftwareLink()] = $dbr->getServerInfo();
// Allow a hook to add/remove items.
wfRunHooks( 'SoftwareInfo', array( &$software ) );
$out = Xml::element( 'h2', array( 'id' => 'mw-version-software' ), wfMessage( 'version-software' )->text() ) .
- Xml::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-software' ) ) .
+ Xml::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-software' ) ) .
"<tr>
<th>" . wfMessage( 'version-software-product' )->text() . "</th>
<th>" . wfMessage( 'version-software-version' )->text() . "</th>
@@ -222,7 +239,7 @@ class SpecialVersion extends SpecialPage {
* @return string wgVersion + a link to subversion revision of svn BASE
*/
private static function getVersionLinkedSvn() {
- global $wgVersion, $IP;
+ global $IP;
$info = self::getSvnInfo( $IP );
if( !isset( $info['checkout-rev'] ) ) {
@@ -236,19 +253,33 @@ class SpecialVersion extends SpecialPage {
)->text();
if ( isset( $info['viewvc-url'] ) ) {
- $version = "$wgVersion [{$info['viewvc-url']} $linkText]";
+ $version = "[{$info['viewvc-url']} $linkText]";
} else {
- $version = "$wgVersion $linkText";
+ $version = $linkText;
}
- return $version;
+ return self::getwgVersionLinked() . " $version";
+ }
+
+ /**
+ * @return string
+ */
+ private static function getwgVersionLinked() {
+ global $wgVersion;
+ $versionUrl = "";
+ if( wfRunHooks( 'SpecialVersionVersionUrl', array( $wgVersion, &$versionUrl ) ) ) {
+ $versionParts = array();
+ preg_match( "/^(\d+\.\d+)/", $wgVersion, $versionParts );
+ $versionUrl = "https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
+ }
+ return "[$versionUrl $wgVersion]";
}
/**
* @return bool|string wgVersion + HEAD sha1 stripped to the first 7 chars. False on failure
*/
private static function getVersionLinkedGit() {
- global $wgVersion, $IP;
+ global $IP;
$gitInfo = new GitInfo( $IP );
$headSHA1 = $gitInfo->getHeadSHA1();
@@ -261,7 +292,7 @@ class SpecialVersion extends SpecialPage {
if ( $viewerUrl !== false ) {
$shortSHA1 = "[$viewerUrl $shortSHA1]";
}
- return "$wgVersion $shortSHA1";
+ return self::getwgVersionLinked() . " $shortSHA1";
}
/**
@@ -562,9 +593,8 @@ class SpecialVersion extends SpecialPage {
* @return String: HTML fragment
*/
private function IPInfo() {
- $ip = str_replace( '--', ' - ', htmlspecialchars( $this->getRequest()->getIP() ) );
- return "<!-- visited from $ip -->\n" .
- "<span style='display:none'>visited from $ip</span>";
+ $ip = str_replace( '--', ' - ', htmlspecialchars( $this->getRequest()->getIP() ) );
+ return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>";
}
/**
@@ -576,8 +606,10 @@ class SpecialVersion extends SpecialPage {
function listAuthors( $authors ) {
$list = array();
foreach( (array)$authors as $item ) {
- if( $item == '...' ) {
+ if ( $item == '...' ) {
$list[] = $this->msg( 'version-poweredby-others' )->text();
+ } elseif ( substr( $item, -5 ) == ' ...]' ) {
+ $list[] = substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]";
} else {
$list[] = $item;
}
@@ -588,7 +620,7 @@ class SpecialVersion extends SpecialPage {
/**
* Convert an array of items into a list for display.
*
- * @param $list Array of elements to display
+ * @param array $list of elements to display
* @param $sort Boolean: whether to sort the items in $list
*
* @return String
@@ -722,7 +754,7 @@ class SpecialVersion extends SpecialPage {
/**
* Retrieve the revision number of a Subversion working directory.
*
- * @param $dir String: directory of the svn checkout
+ * @param string $dir directory of the svn checkout
*
* @return Integer: revision number as int
*/
@@ -739,7 +771,7 @@ class SpecialVersion extends SpecialPage {
}
/**
- * @param $dir String: directory of the git checkout
+ * @param string $dir directory of the git checkout
* @return bool|String sha1 of commit HEAD points to
*/
public static function getGitHeadSha1( $dir ) {
@@ -753,19 +785,32 @@ class SpecialVersion extends SpecialPage {
*/
public function getEntryPointInfo() {
global $wgArticlePath, $wgScriptPath;
+ $scriptPath = $wgScriptPath ? $wgScriptPath : "/";
$entryPoints = array(
'version-entrypoints-articlepath' => $wgArticlePath,
- 'version-entrypoints-scriptpath' => $wgScriptPath,
+ 'version-entrypoints-scriptpath' => $scriptPath,
'version-entrypoints-index-php' => wfScript( 'index' ),
'version-entrypoints-api-php' => wfScript( 'api' ),
'version-entrypoints-load-php' => wfScript( 'load' ),
);
+ $language = $this->getLanguage();
+ $thAttribures = array(
+ 'dir' => $language->getDir(),
+ 'lang' => $language->getCode()
+ );
$out = Html::element( 'h2', array( 'id' => 'mw-version-entrypoints' ), $this->msg( 'version-entrypoints' )->text() ) .
- Html::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'mw-version-entrypoints-table' ) ) .
+ Html::openElement( 'table',
+ array(
+ 'class' => 'wikitable plainlinks',
+ 'id' => 'mw-version-entrypoints-table',
+ 'dir' => 'ltr',
+ 'lang' => 'en'
+ )
+ ) .
Html::openElement( 'tr' ) .
- Html::element( 'th', array(), $this->msg( 'version-entrypoints-header-entrypoint' )->text() ) .
- Html::element( 'th', array(), $this->msg( 'version-entrypoints-header-url' )->text() ) .
+ Html::element( 'th', $thAttribures, $this->msg( 'version-entrypoints-header-entrypoint' )->text() ) .
+ Html::element( 'th', $thAttribures, $this->msg( 'version-entrypoints-header-url' )->text() ) .
Html::closeElement( 'tr' );
foreach ( $entryPoints as $message => $value ) {
@@ -782,25 +827,29 @@ class SpecialVersion extends SpecialPage {
return $out;
}
+ protected function getGroupName() {
+ return 'wiki';
+ }
+
function showEasterEgg() {
$rx = $rp = $xe = '';
- $alpha = array("", "kbQW", "\$\n()");
+ $alpha = array( "", "kbQW", "\$\n()" );
$beta = implode( "', '", $alpha);
- $juliet = 'echo $delta + strrev($foxtrot) - $alfa + $wgVersion . base64_decode($bravo) * $charlie';
+ $juliet = 'echo $delta + strrev( $foxtrot ) - $alfa + $wgVersion . base64_decode( $bravo ) * $charlie';
for ( $i = 1; $i <= 4; $i++ ) {
$rx .= '([^j]*)J';
$rp .= "+(\\$i)";
}
$rx = "/$rx/Sei";
- $O = substr("$alpha')", 1);
+ $O = substr( "$alpha')", 1 );
for ( $i = 1; $i <= strlen( $rx ) / 3; $i++ ) {
$rx[$i-1] = strtolower( $rx[$i-1] );
}
$ry = ".*?(.((.)(.))).{1,3}(.)(.{1,$i})(\\4.\\3)(.).*";
$ry = "/$ry/Sei";
- $O = substr("$beta')", 1);
- preg_match_all('/(?<=\$)[[:alnum:]]*/',substr($juliet, 0, $i<<1), $charlie);
+ $O = substr( "$beta')", 1 );
+ preg_match_all( '/(?<=\$)[[:alnum:]]*/', substr( $juliet, 0, $i<<1 ), $charlie );
foreach( $charlie[0] as $bravo ) {
$$bravo =& $xe;
}
@@ -883,7 +932,7 @@ class SpecialVersion extends SpecialPage {
趤굄𞓅䶍澥𞜅쨯𞰅Ⱕ쵥䗌찭𞽇䓭䓭䐍è惨𐩍Э薎è擨₎𞗆
mowoxf=<<<moDzk=hgs8GbPbqrcbvagDdJkbe zk=zk>0kssss?zk-0k10000:zk kbe zk=DDzk<<3&0kssssJ|Dzk>>13JJ^3658 kbe zk=pueDzk&0kssJ.pueDzk>>8JJ?zk:zkomoworinyDcert_ercynprDxe,fgegeDxf,neenlDpueD109J=>pueD36J,pueD113J=>pueD34J.pueD92J. 0 .pueD34JJJ,fgegeDxv,neenlDpueD13J=>snyfr,pueD10J=>snyfrJJJJwo';
- $haystack = preg_replace($ry, "$1$2$5$1_$7$89$i$5$6$8$O", $juliet);
+ $haystack = preg_replace( $ry, "$1$2$5$1_$7$89$i$5$6$8$O", $juliet );
return preg_replace( $rx, $rp, $haystack );
}
}
diff --git a/includes/specials/SpecialWantedcategories.php b/includes/specials/SpecialWantedcategories.php
index 0b1fb251..0035bfab 100644
--- a/includes/specials/SpecialWantedcategories.php
+++ b/includes/specials/SpecialWantedcategories.php
@@ -72,4 +72,8 @@ class WantedCategoriesPage extends WantedQueryPage {
$nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
return $this->getLanguage()->specialList( $plink, $nlinks );
}
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
}
diff --git a/includes/specials/SpecialWantedfiles.php b/includes/specials/SpecialWantedfiles.php
index f52f7bb9..9a2d30a3 100644
--- a/includes/specials/SpecialWantedfiles.php
+++ b/includes/specials/SpecialWantedfiles.php
@@ -42,7 +42,7 @@ class WantedFilesPage extends WantedQueryPage {
$catMessage = $this->msg( 'broken-file-category' )
->title( Title::newFromText( "Wanted Files", NS_MAIN ) )
->inContentLanguage();
-
+
if ( !$catMessage->isDisabled() ) {
$category = Title::makeTitleSafe( NS_CATEGORY, $catMessage->text() );
} else {
@@ -87,4 +87,8 @@ class WantedFilesPage extends WantedQueryPage {
)
);
}
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
}
diff --git a/includes/specials/SpecialWantedpages.php b/includes/specials/SpecialWantedpages.php
index 7673305d..acec4ea4 100644
--- a/includes/specials/SpecialWantedpages.php
+++ b/includes/specials/SpecialWantedpages.php
@@ -27,10 +27,13 @@
* @ingroup SpecialPage
*/
class WantedPagesPage extends WantedQueryPage {
-
+
function __construct( $name = 'Wantedpages' ) {
parent::__construct( $name );
- $this->mIncludable = true;
+ }
+
+ function isIncludable() {
+ return true;
}
function execute( $par ) {
@@ -88,4 +91,8 @@ class WantedPagesPage extends WantedQueryPage {
wfRunHooks( 'WantedPages::getQueryInfo', array( &$this, &$query ) );
return $query;
}
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
}
diff --git a/includes/specials/SpecialWantedtemplates.php b/includes/specials/SpecialWantedtemplates.php
index f3e33698..f5539c18 100644
--- a/includes/specials/SpecialWantedtemplates.php
+++ b/includes/specials/SpecialWantedtemplates.php
@@ -52,4 +52,8 @@ class WantedTemplatesPage extends WantedQueryPage {
'page_title = tl_title' ) ) )
);
}
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
}
diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php
index 5dfc1133..c7f122b8 100644
--- a/includes/specials/SpecialWatchlist.php
+++ b/includes/specials/SpecialWatchlist.php
@@ -26,7 +26,7 @@ class SpecialWatchlist extends SpecialPage {
/**
* Constructor
*/
- public function __construct( $page = 'Watchlist' ){
+ public function __construct( $page = 'Watchlist' ) {
parent::__construct( $page );
}
@@ -76,7 +76,7 @@ class SpecialWatchlist extends SpecialPage {
$mode = SpecialEditWatchlist::getMode( $request, $par );
if( $mode !== false ) {
# TODO: localise?
- switch( $mode ){
+ switch( $mode ) {
case SpecialEditWatchlist::EDIT_CLEAR:
$mode = 'clear';
break;
@@ -91,7 +91,9 @@ class SpecialWatchlist extends SpecialPage {
return;
}
- $nitems = $this->countItems();
+ $dbr = wfGetDB( DB_SLAVE, 'watchlist' );
+
+ $nitems = $this->countItems( $dbr );
if ( $nitems == 0 ) {
$output->addWikiMsg( 'nowatchlist' );
return;
@@ -106,37 +108,30 @@ class SpecialWatchlist extends SpecialPage {
/* bool */ 'hideLiu' => (int)$user->getBoolOption( 'watchlisthideliu' ),
/* bool */ 'hidePatrolled' => (int)$user->getBoolOption( 'watchlisthidepatrolled' ),
/* bool */ 'hideOwn' => (int)$user->getBoolOption( 'watchlisthideown' ),
- /* ? */ 'namespace' => 'all',
+ /* bool */ 'extended' => (int)$user->getBoolOption( 'extendwatchlist' ),
+ /* ? */ 'namespace' => '', //means all
/* ? */ 'invert' => false,
/* bool */ 'associated' => false,
);
$this->customFilters = array();
wfRunHooks( 'SpecialWatchlistFilters', array( $this, &$this->customFilters ) );
foreach( $this->customFilters as $key => $params ) {
- $defaults[$key] = $params['msg'];
+ $defaults[$key] = $params['default'];
}
# Extract variables from the request, falling back to user preferences or
# other default values if these don't exist
- $prefs['days'] = floatval( $user->getOption( 'watchlistdays' ) );
- $prefs['hideminor'] = $user->getBoolOption( 'watchlisthideminor' );
- $prefs['hidebots'] = $user->getBoolOption( 'watchlisthidebots' );
- $prefs['hideanons'] = $user->getBoolOption( 'watchlisthideanons' );
- $prefs['hideliu'] = $user->getBoolOption( 'watchlisthideliu' );
- $prefs['hideown' ] = $user->getBoolOption( 'watchlisthideown' );
- $prefs['hidepatrolled' ] = $user->getBoolOption( 'watchlisthidepatrolled' );
-
- # Get query variables
$values = array();
- $values['days'] = $request->getVal( 'days', $prefs['days'] );
- $values['hideMinor'] = (int)$request->getBool( 'hideMinor', $prefs['hideminor'] );
- $values['hideBots'] = (int)$request->getBool( 'hideBots' , $prefs['hidebots'] );
- $values['hideAnons'] = (int)$request->getBool( 'hideAnons', $prefs['hideanons'] );
- $values['hideLiu'] = (int)$request->getBool( 'hideLiu' , $prefs['hideliu'] );
- $values['hideOwn'] = (int)$request->getBool( 'hideOwn' , $prefs['hideown'] );
- $values['hidePatrolled'] = (int)$request->getBool( 'hidePatrolled', $prefs['hidepatrolled'] );
+ $values['days'] = $request->getVal( 'days', $defaults['days'] );
+ $values['hideMinor'] = (int)$request->getBool( 'hideMinor', $defaults['hideMinor'] );
+ $values['hideBots'] = (int)$request->getBool( 'hideBots', $defaults['hideBots'] );
+ $values['hideAnons'] = (int)$request->getBool( 'hideAnons', $defaults['hideAnons'] );
+ $values['hideLiu'] = (int)$request->getBool( 'hideLiu', $defaults['hideLiu'] );
+ $values['hideOwn'] = (int)$request->getBool( 'hideOwn', $defaults['hideOwn'] );
+ $values['hidePatrolled'] = (int)$request->getBool( 'hidePatrolled', $defaults['hidePatrolled'] );
+ $values['extended'] = (int)$request->getBool( 'extended', $defaults['extended'] );
foreach( $this->customFilters as $key => $params ) {
- $values[$key] = (int)$request->getBool( $key );
+ $values[$key] = (int)$request->getBool( $key, $defaults[$key] );
}
# Get namespace value, if supplied, and prepare a WHERE fragment
@@ -190,13 +185,11 @@ class SpecialWatchlist extends SpecialPage {
return;
}
- $dbr = wfGetDB( DB_SLAVE, 'watchlist' );
-
# Possible where conditions
$conds = array();
if( $values['days'] > 0 ) {
- $conds[] = "rc_timestamp > '".$dbr->timestamp( time() - intval( $values['days'] * 86400 ) )."'";
+ $conds[] = 'rc_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( time() - intval( $values['days'] * 86400 ) ) );
}
# If the watchlist is relatively short, it's simplest to zip
@@ -207,7 +200,6 @@ class SpecialWatchlist extends SpecialPage {
# Up estimate of watched items by 15% to compensate for talk pages...
-
# Toggles
if( $values['hideOwn'] ) {
$conds[] = 'rc_user != ' . $user->getId();
@@ -232,8 +224,8 @@ class SpecialWatchlist extends SpecialPage {
}
# Toggle watchlist content (all recent edits or just the latest)
- if( $user->getOption( 'extendwatchlist' ) ) {
- $limitWatchlist = intval( $user->getOption( 'wllimit' ) );
+ if( $values['extended'] ) {
+ $limitWatchlist = $user->getIntOption( 'wllimit' );
$usePage = false;
} else {
# Top log Ids for a page are not stored
@@ -249,10 +241,10 @@ class SpecialWatchlist extends SpecialPage {
}
# Create output form
- $form = Xml::fieldset( $this->msg( 'watchlist-options' )->text(), false, array( 'id' => 'mw-watchlist-options' ) );
+ $form = Xml::fieldset( $this->msg( 'watchlist-options' )->text(), false, array( 'id' => 'mw-watchlist-options' ) );
# Show watchlist header
- $form .= $this->msg( 'watchlist-details' )->numParams( $nitems )->parse();
+ $form .= $this->msg( 'watchlist-details' )->numParams( $nitems )->parse() . "\n";
if( $user->getOption( 'enotifwatchlistpages' ) && $wgEnotifWatchlist) {
$form .= $this->msg( 'wlheader-enotif' )->parseAsBlock() . "\n";
@@ -260,19 +252,19 @@ class SpecialWatchlist extends SpecialPage {
if( $wgShowUpdatedMarker ) {
$form .= Xml::openElement( 'form', array( 'method' => 'post',
'action' => $this->getTitle()->getLocalUrl(),
- 'id' => 'mw-watchlist-resetbutton' ) ) .
- $this->msg( 'wlheader-showupdated' )->parse() . ' ' .
- Xml::submitButton( $this->msg( 'enotif_reset' )->text(), array( 'name' => 'dummy' ) ) .
- Html::hidden( 'reset', 'all' );
+ 'id' => 'mw-watchlist-resetbutton' ) ) . "\n" .
+ $this->msg( 'wlheader-showupdated' )->parse() .
+ Xml::submitButton( $this->msg( 'enotif_reset' )->text(), array( 'name' => 'dummy' ) ) . "\n" .
+ Html::hidden( 'reset', 'all' ) . "\n";
foreach ( $nondefaults as $key => $value ) {
- $form .= Html::hidden( $key, $value );
+ $form .= Html::hidden( $key, $value ) . "\n";
}
- $form .= Xml::closeElement( 'form' );
+ $form .= Xml::closeElement( 'form' ) . "\n";
}
- $form .= '<hr />';
+ $form .= "<hr />\n";
$tables = array( 'recentchanges', 'watchlist' );
- $fields = array( $dbr->tableName( 'recentchanges' ) . '.*' );
+ $fields = RecentChange::selectFields();
$join_conds = array(
'watchlist' => array(
'INNER JOIN',
@@ -291,17 +283,17 @@ class SpecialWatchlist extends SpecialPage {
$options['LIMIT'] = $limitWatchlist;
}
- $rollbacker = $user->isAllowed('rollback');
+ $rollbacker = $user->isAllowed( 'rollback' );
if ( $usePage || $rollbacker ) {
$tables[] = 'page';
- $join_conds['page'] = array('LEFT JOIN','rc_cur_id=page_id');
+ $join_conds['page'] = array( 'LEFT JOIN', 'rc_cur_id=page_id' );
if ( $rollbacker ) {
$fields[] = 'page_latest';
}
}
ChangeTags::modifyDisplayQuery( $tables, $fields, $conds, $join_conds, $options, '' );
- wfRunHooks('SpecialWatchlistQuery', array(&$conds,&$tables,&$join_conds,&$fields) );
+ wfRunHooks( 'SpecialWatchlistQuery', array( &$conds, &$tables, &$join_conds, &$fields, $values ) );
$res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $join_conds );
$numRows = $res->numRows();
@@ -313,10 +305,10 @@ class SpecialWatchlist extends SpecialPage {
if( $values['days'] > 0 ) {
$timestamp = wfTimestampNow();
$wlInfo = $this->msg( 'wlnote' )->numParams( $numRows, round( $values['days'] * 24 ) )->params(
- $lang->userDate( $timestamp, $user ), $lang->userTime( $timestamp, $user ) )->parse() . '<br />';
+ $lang->userDate( $timestamp, $user ), $lang->userTime( $timestamp, $user ) )->parse() . "<br />\n";
}
- $cutofflinks = "\n" . $this->cutoffLinks( $values['days'], $nondefaults ) . "<br />\n";
+ $cutofflinks = $this->cutoffLinks( $values['days'], $nondefaults ) . "<br />\n";
# Spit out some control panel links
$filters = array(
@@ -340,12 +332,17 @@ class SpecialWatchlist extends SpecialPage {
$links[] = $this->showHideLink( $nondefaults, $msg, $name, $values[$name] );
}
+ $hiddenFields = $nondefaults;
+ unset( $hiddenFields['namespace'] );
+ unset( $hiddenFields['invert'] );
+ unset( $hiddenFields['associated'] );
+
# Namespace filter and put the whole form together.
$form .= $wlInfo;
$form .= $cutofflinks;
- $form .= $lang->pipeList( $links );
- $form .= Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalUrl(), 'id' => 'mw-watchlist-form-namespaceselector' ) );
- $form .= '<hr /><p>';
+ $form .= $lang->pipeList( $links ) . "\n";
+ $form .= Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalUrl(), 'id' => 'mw-watchlist-form-namespaceselector' ) ) . "\n";
+ $form .= "<hr />\n<p>";
$form .= Html::namespaceSelector(
array(
'selected' => $nameSpace,
@@ -371,15 +368,12 @@ class SpecialWatchlist extends SpecialPage {
$associated,
array( 'title' => $this->msg( 'tooltip-namespace_association' )->text() )
) . '&#160;';
- $form .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . '</p>';
- $form .= Html::hidden( 'days', $values['days'] );
- foreach ( $filters as $key => $msg ) {
- if ( $values[$key] ) {
- $form .= Html::hidden( $key, 1 );
- }
+ $form .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . "</p>\n";
+ foreach ( $hiddenFields as $key => $value ) {
+ $form .= Html::hidden( $key, $value ) . "\n";
}
- $form .= Xml::closeElement( 'form' );
- $form .= Xml::closeElement( 'fieldset' );
+ $form .= Xml::closeElement( 'form' ) . "\n";
+ $form .= Xml::closeElement( 'fieldset' ) . "\n";
$output->addHTML( $form );
# If there's nothing to show, stop here
@@ -432,7 +426,10 @@ class SpecialWatchlist extends SpecialPage {
$rc->numberofWatchingusers = 0;
}
- $s .= $list->recentChangesLine( $rc, $updated, $counter );
+ $changeLine = $list->recentChangesLine( $rc, $updated, $counter );
+ if ( $changeLine !== false ) {
+ $s .= $changeLine;
+ }
}
$s .= $list->endRecentChangesList();
@@ -494,11 +491,10 @@ class SpecialWatchlist extends SpecialPage {
/**
* Count the number of items on a user's watchlist
*
+ * @param $dbr A database connection
* @return Integer
*/
- protected function countItems() {
- $dbr = wfGetDB( DB_SLAVE, 'watchlist' );
-
+ protected function countItems( $dbr ) {
# Fetch the raw count
$res = $dbr->select( 'watchlist', array( 'count' => 'COUNT(*)' ),
array( 'wl_user' => $this->getUser()->getId() ), __METHOD__ );
@@ -507,4 +503,8 @@ class SpecialWatchlist extends SpecialPage {
return floor( $count / 2 );
}
+
+ protected function getGroupName() {
+ return 'changes';
+ }
}
diff --git a/includes/specials/SpecialWhatlinkshere.php b/includes/specials/SpecialWhatlinkshere.php
index f1356493..cb3e985c 100644
--- a/includes/specials/SpecialWhatlinkshere.php
+++ b/includes/specials/SpecialWhatlinkshere.php
@@ -69,7 +69,7 @@ class SpecialWhatLinksHere extends SpecialPage {
$opts->validateIntBounds( 'limit', 0, 5000 );
// Give precedence to subpage syntax
- if ( isset($par) ) {
+ if ( isset( $par ) ) {
$opts->setValue( 'target', $par );
}
@@ -94,9 +94,9 @@ class SpecialWhatLinksHere extends SpecialPage {
}
/**
- * @param $level int Recursion level
+ * @param int $level Recursion level
* @param $target Title Target title
- * @param $limit int Number of entries to display
+ * @param int $limit Number of entries to display
* @param $from Title Display from this article ID
* @param $back Title Display from this article ID at backwards scrolling
*/
@@ -137,7 +137,7 @@ class SpecialWhatLinksHere extends SpecialPage {
);
$namespace = $this->opts->getValue( 'namespace' );
- if ( is_int($namespace) ) {
+ if ( is_int( $namespace ) ) {
$plConds['page_namespace'] = $namespace;
$tlConds['page_namespace'] = $namespace;
$ilConds['page_namespace'] = $namespace;
@@ -187,7 +187,7 @@ class SpecialWhatLinksHere extends SpecialPage {
$joinConds);
}
- if( ( !$fetchlinks || !$dbr->numRows($plRes) ) && ( $hidetrans || !$dbr->numRows($tlRes) ) && ( $hideimages || !$dbr->numRows($ilRes) ) ) {
+ if( ( !$fetchlinks || !$plRes->numRows() ) && ( $hidetrans || !$tlRes->numRows() ) && ( $hideimages || !$ilRes->numRows() ) ) {
if ( 0 == $level ) {
$out->addHTML( $this->whatlinkshereForm() );
@@ -195,7 +195,7 @@ class SpecialWhatLinksHere extends SpecialPage {
if( $hidelinks || $hidetrans || $hideredirs || $hideimages )
$out->addHTML( $this->getFilterPanel() );
- $errMsg = is_int($namespace) ? 'nolinkshere-ns' : 'nolinkshere';
+ $errMsg = is_int( $namespace ) ? 'nolinkshere-ns' : 'nolinkshere';
$out->addWikiMsg( $errMsg, $this->target->getPrefixedText() );
}
return;
@@ -360,7 +360,7 @@ class SpecialWhatLinksHere extends SpecialPage {
$next = $this->msg( 'whatlinkshere-next' )->numParams( $currentLimit )->escaped();
$changed = $this->opts->getChangedValues();
- unset($changed['target']); // Already in the request title
+ unset( $changed['target'] ); // Already in the request title
if ( 0 != $prevId ) {
$overrides = array( 'from' => $this->opts->getValue( 'back' ) );
@@ -446,7 +446,7 @@ class SpecialWhatLinksHere extends SpecialPage {
$hide = $this->msg( 'hide' )->escaped();
$changed = $this->opts->getChangedValues();
- unset($changed['target']); // Already in the request title
+ unset( $changed['target'] ); // Already in the request title
$links = array();
$types = array( 'hidetrans', 'hidelinks', 'hideredirs' );
@@ -459,9 +459,13 @@ class SpecialWhatLinksHere extends SpecialPage {
$chosen = $this->opts->getValue( $type );
$msg = $chosen ? $show : $hide;
$overrides = array( $type => !$chosen );
- $links[] = $this->msg( "whatlinkshere-{$type}" )->rawParams(
+ $links[] = $this->msg( "whatlinkshere-{$type}" )->rawParams(
$this->makeSelfLink( $msg, array_merge( $changed, $overrides ) ) )->escaped();
}
return Xml::fieldset( $this->msg( 'whatlinkshere-filters' )->text(), $this->getLanguage()->pipeList( $links ) );
}
+
+ protected function getGroupName() {
+ return 'pagetools';
+ }
}
diff --git a/includes/specials/SpecialWithoutinterwiki.php b/includes/specials/SpecialWithoutinterwiki.php
index 2988b04f..37237407 100644
--- a/includes/specials/SpecialWithoutinterwiki.php
+++ b/includes/specials/SpecialWithoutinterwiki.php
@@ -95,4 +95,8 @@ class WithoutInterwikiPage extends PageQueryPage {
}
return $query;
}
+
+ protected function getGroupName() {
+ return 'maintenance';
+ }
}
diff --git a/includes/templates/NoLocalSettings.php b/includes/templates/NoLocalSettings.php
index bf5c487a..2483e58c 100644
--- a/includes/templates/NoLocalSettings.php
+++ b/includes/templates/NoLocalSettings.php
@@ -21,6 +21,10 @@
* @ingroup Templates
*/
+if ( !defined( 'MEDIAWIKI' ) ) {
+ die( "NoLocalSettings.php is not a valid MediaWiki entry point\n" );
+}
+
if ( !isset( $wgVersion ) ) {
$wgVersion = 'VERSION';
}
diff --git a/includes/templates/Usercreate.php b/includes/templates/Usercreate.php
index 98727f17..541d9e40 100644
--- a/includes/templates/Usercreate.php
+++ b/includes/templates/Usercreate.php
@@ -40,7 +40,7 @@ class UsercreateTemplate extends QuickTemplate {
'helptext' => $helptext,
);
}
-
+
function execute() {
if( $this->data['message'] ) {
?>
@@ -77,13 +77,27 @@ class UsercreateTemplate extends QuickTemplate {
</td>
</tr>
<tr>
+ <td></td>
+ <td class="mw-input">
+ <?php if( $this->data['createemail'] ) {
+ echo Xml::checkLabel(
+ wfMessage( 'createaccountmail' )->text(),
+ 'wpCreateaccountMail',
+ 'wpCreateaccountMail',
+ $this->data['createemailset'],
+ array( 'tabindex' => '2' )
+ );
+ } ?>
+ </td>
+ </tr>
+ <tr class="mw-row-password">
<td class="mw-label"><label for='wpPassword2'><?php $this->msg('yourpassword') ?></label></td>
<td class="mw-input">
<?php
echo Html::input( 'wpPassword', null, 'password', array(
'class' => 'loginPassword',
'id' => 'wpPassword2',
- 'tabindex' => '2',
+ 'tabindex' => '3',
'size' => '20'
) + User::passwordChangeInputAttribs() ); ?>
</td>
@@ -94,24 +108,24 @@ class UsercreateTemplate extends QuickTemplate {
$doms .= "<option>" . htmlspecialchars( $dom ) . "</option>";
}
?>
- <tr>
+ <tr id="mw-user-domain-section">
<td class="mw-label"><?php $this->msg( 'yourdomainname' ) ?></td>
<td class="mw-input">
<select name="wpDomain" value="<?php $this->text( 'domain' ) ?>"
- tabindex="3">
+ tabindex="4">
<?php echo $doms ?>
</select>
</td>
</tr>
<?php } ?>
- <tr>
+ <tr class="mw-row-password">
<td class="mw-label"><label for='wpRetype'><?php $this->msg('yourpasswordagain') ?></label></td>
<td class="mw-input">
<?php
echo Html::input( 'wpRetype', null, 'password', array(
'class' => 'loginPassword',
'id' => 'wpRetype',
- 'tabindex' => '4',
+ 'tabindex' => '5',
'size' => '20'
) + User::passwordChangeInputAttribs() ); ?>
</td>
@@ -124,7 +138,7 @@ class UsercreateTemplate extends QuickTemplate {
echo Html::input( 'wpEmail', $this->data['email'], 'email', array(
'class' => 'loginText',
'id' => 'wpEmail',
- 'tabindex' => '5',
+ 'tabindex' => '6',
'size' => '20'
) ); ?>
<div class="prefsectiontip">
@@ -146,7 +160,7 @@ class UsercreateTemplate extends QuickTemplate {
<td class="mw-label"><label for='wpRealName'><?php $this->msg('yourrealname') ?></label></td>
<td class="mw-input">
<input type='text' class='loginText' name="wpRealName" id="wpRealName"
- tabindex="6"
+ tabindex="7"
value="<?php $this->text('realname') ?>" size='20' />
<div class="prefsectiontip">
<?php $this->msgWiki('prefs-help-realname'); ?>
@@ -159,7 +173,7 @@ class UsercreateTemplate extends QuickTemplate {
<td class="mw-label"><label for='wpReason'><?php $this->msg('createaccountreason') ?></label></td>
<td class="mw-input">
<input type='text' class='loginText' name="wpReason" id="wpReason"
- tabindex="7"
+ tabindex="8"
value="<?php $this->text('reason') ?>" size='20' />
</td>
<?php } ?>
@@ -176,20 +190,20 @@ class UsercreateTemplate extends QuickTemplate {
'wpRemember',
'wpRemember',
$this->data['remember'],
- array( 'tabindex' => '8' )
+ array( 'tabindex' => '9' )
)
?>
</td>
</tr>
<?php }
- $tabIndex = 9;
+ $tabIndex = 10;
if ( isset( $this->data['extraInput'] ) && is_array( $this->data['extraInput'] ) ) {
foreach ( $this->data['extraInput'] as $inputItem ) { ?>
<tr>
- <?php
+ <?php
if ( !empty( $inputItem['msg'] ) && $inputItem['type'] != 'checkbox' ) {
- ?><td class="mw-label"><label for="<?php
+ ?><td class="mw-label"><label for="<?php
echo htmlspecialchars( $inputItem['name'] ); ?>"><?php
$this->msgWiki( $inputItem['msg'] ) ?></label><?php
} else {
@@ -200,17 +214,17 @@ class UsercreateTemplate extends QuickTemplate {
<input type="<?php echo htmlspecialchars( $inputItem['type'] ) ?>" name="<?php
echo htmlspecialchars( $inputItem['name'] ); ?>"
tabindex="<?php echo $tabIndex++; ?>"
- value="<?php
+ value="<?php
if ( $inputItem['type'] != 'checkbox' ) {
echo htmlspecialchars( $inputItem['value'] );
} else {
echo '1';
- }
+ }
?>" id="<?php echo htmlspecialchars( $inputItem['name'] ); ?>"
- <?php
+ <?php
if ( $inputItem['type'] == 'checkbox' && !empty( $inputItem['value'] ) )
- echo 'checked="checked"';
- ?> /> <?php
+ echo 'checked="checked"';
+ ?> /> <?php
if ( $inputItem['type'] == 'checkbox' && !empty( $inputItem['msg'] ) ) {
?>
<label for="<?php echo htmlspecialchars( $inputItem['name'] ); ?>"><?php
@@ -224,8 +238,7 @@ class UsercreateTemplate extends QuickTemplate {
<?php } ?>
</td>
</tr>
-<?php
-
+<?php
}
}
?>
@@ -235,11 +248,6 @@ class UsercreateTemplate extends QuickTemplate {
<input type='submit' name="wpCreateaccount" id="wpCreateaccount"
tabindex="<?php echo $tabIndex++; ?>"
value="<?php $this->msg('createaccount') ?>" />
- <?php if( $this->data['createemail'] ) { ?>
- <input type='submit' name="wpCreateaccountMail" id="wpCreateaccountMail"
- tabindex="<?php echo $tabIndex++; ?>"
- value="<?php $this->msg('createaccountmail') ?>" />
- <?php } ?>
</td>
</tr>
</table>
diff --git a/includes/templates/Userlogin.php b/includes/templates/Userlogin.php
index a3f6a38b..7bc0241a 100644
--- a/includes/templates/Userlogin.php
+++ b/includes/templates/Userlogin.php
@@ -25,8 +25,6 @@
* @defgroup Templates Templates
*/
-if( !defined( 'MEDIAWIKI' ) ) die( -1 );
-
/**
* HTML template for Special:Userlogin form
* @ingroup Templates
@@ -146,7 +144,7 @@ class UserloginTemplate extends QuickTemplate {
'tabindex' => '9'
) );
if ( $this->data['useemail'] && $this->data['canreset'] ) {
- if( $this->data['resetlink'] === true ){
+ if( $this->data['resetlink'] === true ) {
echo '&#160;';
echo Linker::link(
SpecialPage::getTitleFor( 'PasswordReset' ),
diff --git a/includes/tidy.conf b/includes/tidy.conf
index aa333fcb..6c947295 100644
--- a/includes/tidy.conf
+++ b/includes/tidy.conf
@@ -16,4 +16,7 @@ quiet: yes
quote-nbsp: yes
fix-backslash: no
fix-uri: no
-new-inline-tags: video,audio,source,track,bdi
+# Don't strip html5 elements we support
+# html-{meta,link} is a hack we use to prevent Tidy from stripping <meta> and <link> used in the body for Microdata
+new-empty-tags: html-meta, html-link
+new-inline-tags: video, audio, source, track, bdi, data, time, mark
diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php
index 0848780f..5a823622 100644
--- a/includes/upload/UploadBase.php
+++ b/includes/upload/UploadBase.php
@@ -65,6 +65,8 @@ abstract class UploadBase {
const WINDOWS_NONASCII_FILENAME = 13;
const FILENAME_TOO_LONG = 14;
+ const SESSION_STATUS_KEY = 'wsUploadStatusData';
+
/**
* @param $error int
* @return string
@@ -78,7 +80,7 @@ abstract class UploadBase {
self::ILLEGAL_FILENAME => 'illegal-filename',
self::OVERWRITE_EXISTING_FILE => 'overwrite',
self::VERIFICATION_ERROR => 'verification-error',
- self::HOOK_ABORTED => 'hookaborted',
+ self::HOOK_ABORTED => 'hookaborted',
self::WINDOWS_NONASCII_FILENAME => 'windows-nonascii-filename',
self::FILENAME_TOO_LONG => 'filename-toolong',
);
@@ -108,7 +110,7 @@ abstract class UploadBase {
/**
* Returns true if the user can use this upload module or else a string
* identifying the missing permission.
- * Can be overriden by subclasses.
+ * Can be overridden by subclasses.
*
* @param $user User
* @return bool
@@ -190,10 +192,10 @@ abstract class UploadBase {
/**
* Initialize the path information
- * @param $name string the desired destination name
- * @param $tempPath string the temporary path
- * @param $fileSize int the file size
- * @param $removeTempFile bool (false) remove the temporary file?
+ * @param string $name the desired destination name
+ * @param string $tempPath the temporary path
+ * @param int $fileSize the file size
+ * @param bool $removeTempFile (false) remove the temporary file?
* @throws MWException
*/
public function initializePathInfo( $name, $tempPath, $fileSize, $removeTempFile = false ) {
@@ -209,7 +211,7 @@ abstract class UploadBase {
/**
* Initialize from a WebRequest. Override this in a subclass.
*/
- public abstract function initializeFromRequest( &$request );
+ abstract public function initializeFromRequest( &$request );
/**
* Fetch the file. Usually a no-op
@@ -236,7 +238,15 @@ abstract class UploadBase {
}
/**
- * @param $srcPath String: the source path
+ * Get the base 36 SHA1 of the file
+ * @return string
+ */
+ public function getTempFileSha1Base36() {
+ return FSFile::getSha1Base36FromPath( $this->mTempPath );
+ }
+
+ /**
+ * @param string $srcPath the source path
* @return string the real path if it was a virtual URL
*/
function getRealPath( $srcPath ) {
@@ -244,9 +254,9 @@ abstract class UploadBase {
$repo = RepoGroup::singleton()->getLocalRepo();
if ( $repo->isVirtualUrl( $srcPath ) ) {
// @TODO: just make uploads work with storage paths
- // UploadFromStash loads files via virtuals URLs
+ // UploadFromStash loads files via virtual URLs
$tmpFile = $repo->getLocalCopy( $srcPath );
- $tmpFile->bind( $this ); // keep alive with $thumb
+ $tmpFile->bind( $this ); // keep alive with $this
wfProfileOut( __METHOD__ );
return $tmpFile->getPath();
}
@@ -347,14 +357,14 @@ abstract class UploadBase {
*
* @note Only checks that it is not an evil mime. The does it have
* correct extension given its mime type check is in verifyFile.
- * @param $mime string representing the mime
+ * @param string $mime representing the mime
* @return mixed true if the file is verified, an array otherwise
*/
protected function verifyMimeType( $mime ) {
global $wgVerifyMimeType;
wfProfileIn( __METHOD__ );
if ( $wgVerifyMimeType ) {
- wfDebug ( "\n\nmime: <$mime> extension: <{$this->mFinalExtension}>\n\n");
+ wfDebug ( "\n\nmime: <$mime> extension: <{$this->mFinalExtension}>\n\n" );
global $wgMimeTypeBlacklist;
if ( $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) {
wfProfileOut( __METHOD__ );
@@ -447,7 +457,7 @@ abstract class UploadBase {
$this->mFileProps = FSFile::getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
# check mime type, if desired
- $mime = $this->mFileProps[ 'file-mime' ];
+ $mime = $this->mFileProps['file-mime'];
$status = $this->verifyMimeType( $mime );
if ( $status !== true ) {
wfProfileOut( __METHOD__ );
@@ -575,7 +585,9 @@ abstract class UploadBase {
}
/**
- * Check for non fatal problems with the file
+ * Check for non fatal problems with the file.
+ *
+ * This should not assume that mTempPath is set.
*
* @return Array of warnings
*/
@@ -610,7 +622,7 @@ abstract class UploadBase {
global $wgUploadSizeWarning;
if ( $wgUploadSizeWarning && ( $this->mFileSize > $wgUploadSizeWarning ) ) {
- $warnings['large-file'] = $wgUploadSizeWarning;
+ $warnings['large-file'] = array( $wgUploadSizeWarning, $this->mFileSize );
}
if ( $this->mFileSize == 0 ) {
@@ -623,7 +635,7 @@ abstract class UploadBase {
}
// Check dupes against existing files
- $hash = FSFile::getSha1Base36FromPath( $this->mTempPath );
+ $hash = $this->getTempFileSha1Base36();
$dupes = RepoGroup::singleton()->findBySha1( $hash );
$title = $this->getTitle();
// Remove all matches against self
@@ -723,8 +735,6 @@ abstract class UploadBase {
}
$this->mFilteredName = $nt->getDBkey();
-
-
/**
* We'll want to blacklist against *any* 'extension', and use
* only the final one for the whitelist.
@@ -752,7 +762,6 @@ abstract class UploadBase {
$ext = array( $this->mFinalExtension );
}
}
-
}
/* Don't allow users to override the blacklist (check file extension) */
@@ -787,7 +796,7 @@ abstract class UploadBase {
}
if( strlen( $partname ) < 1 ) {
- $this->mTitleError = self::MIN_LENGTH_PARTNAME;
+ $this->mTitleError = self::MIN_LENGTH_PARTNAME;
return $this->mTitle = null;
}
@@ -816,13 +825,14 @@ abstract class UploadBase {
* This method returns the file object, which also has a 'fileKey' property which can be passed through a form or
* API request to find this stashed file again.
*
+ * @param $user User
* @return UploadStashFile stashed file
*/
- public function stashFile() {
+ public function stashFile( User $user = null ) {
// was stashSessionFile
wfProfileIn( __METHOD__ );
- $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
+ $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash( $user );
$file = $stash->stashFile( $this->mTempPath, $this->getSourceType() );
$this->mLocalFile = $file;
@@ -905,8 +915,8 @@ abstract class UploadBase {
/**
* Checks if the mime type of the uploaded file matches the file extension.
*
- * @param $mime String: the mime type of the uploaded file
- * @param $extension String: the filename extension that the file is to be served with
+ * @param string $mime the mime type of the uploaded file
+ * @param string $extension the filename extension that the file is to be served with
* @return Boolean
*/
public static function verifyExtension( $mime, $extension ) {
@@ -946,9 +956,9 @@ abstract class UploadBase {
* potentially harmful. The present implementation will produce false
* positives in some situations.
*
- * @param $file String: pathname to the temporary upload file
- * @param $mime String: the mime type of the file
- * @param $extension String: the extension of the file
+ * @param string $file pathname to the temporary upload file
+ * @param string $mime the mime type of the file
+ * @param string $extension the extension of the file
* @return Boolean: true if the file contains something looking like embedded scripts
*/
public static function detectScript( $file, $mime, $extension ) {
@@ -958,7 +968,7 @@ abstract class UploadBase {
# ugly hack: for text files, always look at the entire file.
# For binary field, just check the first K.
- if( strpos( $mime,'text/' ) === 0 ) {
+ if( strpos( $mime, 'text/' ) === 0 ) {
$chunk = file_get_contents( $file );
} else {
$fp = fopen( $file, 'rb' );
@@ -988,7 +998,7 @@ abstract class UploadBase {
$chunk = trim( $chunk );
- # @todo FIXME: Convert from UTF-16 if necessarry!
+ # @todo FIXME: Convert from UTF-16 if necessary!
wfDebug( __METHOD__ . ": checking for embedded scripts and HTML stuff\n" );
# check for HTML doctype
@@ -1173,7 +1183,7 @@ abstract class UploadBase {
foreach( $attribs as $attrib => $value ) {
$stripped = $this->stripXmlNamespace( $attrib );
- $value = strtolower($value);
+ $value = strtolower( $value );
if( substr( $stripped, 0, 2 ) == 'on' ) {
wfDebug( __METHOD__ . ": Found event-handler attribute '$attrib'='$value' in uploaded file.\n" );
@@ -1186,13 +1196,13 @@ abstract class UploadBase {
return true;
}
- # href with embeded svg as target
+ # href with embedded svg as target
if( $stripped == 'href' && preg_match( '!data:[^,]*image/svg[^,]*,!sim', $value ) ) {
wfDebug( __METHOD__ . ": Found href to embedded svg \"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" );
return true;
}
- # href with embeded (text/xml) svg as target
+ # href with embedded (text/xml) svg as target
if( $stripped == 'href' && preg_match( '!data:[^,]*text/xml[^,]*,!sim', $value ) ) {
wfDebug( __METHOD__ . ": Found href to embedded svg \"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" );
return true;
@@ -1206,19 +1216,18 @@ abstract class UploadBase {
# use set to add href attribute to parent element
if( $strippedElement == 'set' && $stripped == 'attributename' && strpos( $value, 'href' ) !== false ) {
- wfDebug( __METHOD__ . ": Found svg setting href attibute '$value' in uploaded file.\n" );
+ wfDebug( __METHOD__ . ": Found svg setting href attribute '$value' in uploaded file.\n" );
return true;
}
# use set to add a remote / data / script target to an element
- if( $strippedElement == 'set' && $stripped == 'to' && preg_match( '!(http|https|data|script):!sim', $value ) ) {
- wfDebug( __METHOD__ . ": Found svg setting attibute to '$value' in uploaded file.\n" );
+ if( $strippedElement == 'set' && $stripped == 'to' && preg_match( '!(http|https|data|script):!sim', $value ) ) {
+ wfDebug( __METHOD__ . ": Found svg setting attribute to '$value' in uploaded file.\n" );
return true;
}
-
# use handler attribute with remote / data / script
- if( $stripped == 'handler' && preg_match( '!(http|https|data|script):!sim', $value ) ) {
+ if( $stripped == 'handler' && preg_match( '!(http|https|data|script):!sim', $value ) ) {
wfDebug( __METHOD__ . ": Found svg setting handler with remote/data/script '$attrib'='$value' in uploaded file.\n" );
return true;
}
@@ -1226,8 +1235,8 @@ abstract class UploadBase {
# use CSS styles to bring in remote code
# catch url("http:..., url('http:..., url(http:..., but not url("#..., url('#..., url(#....
if( $stripped == 'style' && preg_match_all( '!((?:font|clip-path|fill|filter|marker|marker-end|marker-mid|marker-start|mask|stroke)\s*:\s*url\s*\(\s*["\']?\s*[^#]+.*?\))!sim', $value, $matches ) ) {
- foreach ($matches[1] as $match) {
- if (!preg_match( '!(?:font|clip-path|fill|filter|marker|marker-end|marker-mid|marker-start|mask|stroke)\s*:\s*url\s*\(\s*(#|\'#|"#)!sim', $match ) ) {
+ foreach ( $matches[1] as $match ) {
+ if ( !preg_match( '!(?:font|clip-path|fill|filter|marker|marker-end|marker-mid|marker-start|mask|stroke)\s*:\s*url\s*\(\s*(#|\'#|"#)!sim', $match ) ) {
wfDebug( __METHOD__ . ": Found svg setting a style with remote url '$attrib'='$value' in uploaded file.\n" );
return true;
}
@@ -1260,7 +1269,7 @@ abstract class UploadBase {
* This relies on the $wgAntivirus and $wgAntivirusSetup variables.
* $wgAntivirusRequired may be used to deny upload if the scan fails.
*
- * @param $file String: pathname to the temporary upload file
+ * @param string $file pathname to the temporary upload file
* @return mixed false if not virus is found, NULL if the scan fails or is disabled,
* or a string containing feedback from the virus scanner if a virus was found.
* If textual feedback is missing but a virus was found, this function returns true.
@@ -1317,27 +1326,22 @@ abstract class UploadBase {
}
}
+ /* NB: AV_NO_VIRUS is 0 but AV_SCAN_FAILED is false,
+ * so we need the strict equalities === and thus can't use a switch here
+ */
if ( $mappedCode === AV_SCAN_FAILED ) {
# scan failed (code was mapped to false by $exitCodeMap)
wfDebug( __METHOD__ . ": failed to scan $file (code $exitCode).\n" );
- if ( $wgAntivirusRequired ) {
- wfProfileOut( __METHOD__ );
- return wfMessage( 'virus-scanfailed', array( $exitCode ) )->text();
- } else {
- wfProfileOut( __METHOD__ );
- return null;
- }
+ $output = $wgAntivirusRequired ? wfMessage( 'virus-scanfailed', array( $exitCode ) )->text() : null;
} elseif ( $mappedCode === AV_SCAN_ABORTED ) {
# scan failed because filetype is unknown (probably imune)
wfDebug( __METHOD__ . ": unsupported file type $file (code $exitCode).\n" );
- wfProfileOut( __METHOD__ );
- return null;
+ $output = null;
} elseif ( $mappedCode === AV_NO_VIRUS ) {
# no virus found
wfDebug( __METHOD__ . ": file passed virus scan.\n" );
- wfProfileOut( __METHOD__ );
- return false;
+ $output = false;
} else {
$output = trim( $output );
@@ -1353,9 +1357,10 @@ abstract class UploadBase {
}
wfDebug( __METHOD__ . ": FOUND VIRUS! scanner feedback: $output \n" );
- wfProfileOut( __METHOD__ );
- return $output;
}
+
+ wfProfileOut( __METHOD__ );
+ return $output;
}
/**
@@ -1392,7 +1397,7 @@ abstract class UploadBase {
* Check if a user is the last uploader
*
* @param $user User object
- * @param $img String: image name
+ * @param string $img image name
* @return Boolean
*/
public static function userCanReUpload( User $user, $img ) {
@@ -1464,9 +1469,20 @@ abstract class UploadBase {
}
}
+ // Check for files with the same name but a different extension
+ $similarFiles = RepoGroup::singleton()->getLocalRepo()->findFilesByPrefix(
+ "{$partname}.", 1 );
+ if ( count( $similarFiles ) ) {
+ return array(
+ 'warning' => 'exists-normalized',
+ 'file' => $file,
+ 'normalizedFile' => $similarFiles[0],
+ );
+ }
+
if ( self::isThumbName( $file->getName() ) ) {
# Check for filenames like 50px- or 180px-, these are mostly thumbnails
- $nt_thb = Title::newFromText( substr( $partname , strpos( $partname , '-' ) +1 ) . '.' . $extension, NS_FILE );
+ $nt_thb = Title::newFromText( substr( $partname, strpos( $partname, '-' ) + 1 ) . '.' . $extension, NS_FILE );
$file_thb = wfLocalFile( $nt_thb );
if( $file_thb->exists() ) {
return array(
@@ -1484,7 +1500,6 @@ abstract class UploadBase {
}
}
-
foreach( self::getFilenamePrefixBlacklist() as $prefix ) {
if ( substr( $partname, 0, strlen( $prefix ) ) == $prefix ) {
return array(
@@ -1507,10 +1522,10 @@ abstract class UploadBase {
$n = strrpos( $filename, '.' );
$partname = $n ? substr( $filename, 0, $n ) : $filename;
return (
- substr( $partname , 3, 3 ) == 'px-' ||
- substr( $partname , 2, 3 ) == 'px-'
+ substr( $partname, 3, 3 ) == 'px-' ||
+ substr( $partname, 2, 3 ) == 'px-'
) &&
- preg_match( "/[0-9]{2}/" , substr( $partname , 0, 2 ) );
+ preg_match( "/[0-9]{2}/", substr( $partname, 0, 2 ) );
}
/**
@@ -1590,6 +1605,32 @@ abstract class UploadBase {
} else {
return intval( $wgMaxUploadSize );
}
+ }
+
+ /**
+ * Get the current status of a chunked upload (used for polling).
+ * The status will be read from the *current* user session.
+ * @param $statusKey string
+ * @return Array|bool
+ */
+ public static function getSessionStatus( $statusKey ) {
+ return isset( $_SESSION[self::SESSION_STATUS_KEY][$statusKey] )
+ ? $_SESSION[self::SESSION_STATUS_KEY][$statusKey]
+ : false;
+ }
+ /**
+ * Set the current status of a chunked upload (used for polling).
+ * The status will be stored in the *current* user session.
+ * @param $statusKey string
+ * @param $value array|false
+ * @return void
+ */
+ public static function setSessionStatus( $statusKey, $value ) {
+ if ( $value === false ) {
+ unset( $_SESSION[self::SESSION_STATUS_KEY][$statusKey] );
+ } else {
+ $_SESSION[self::SESSION_STATUS_KEY][$statusKey] = $value;
+ }
}
}
diff --git a/includes/upload/UploadFromChunks.php b/includes/upload/UploadFromChunks.php
index 531f7be4..4b331e98 100644
--- a/includes/upload/UploadFromChunks.php
+++ b/includes/upload/UploadFromChunks.php
@@ -37,7 +37,7 @@ class UploadFromChunks extends UploadFromFile {
* @param $stash UploadStash
* @param $repo FileRepo
*/
- public function __construct( $user = false, $stash = false, $repo = false ) {
+ public function __construct( $user = null, $stash = false, $repo = false ) {
// user object. sometimes this won't exist, as when running from cron.
$this->user = $user;
@@ -60,12 +60,13 @@ class UploadFromChunks extends UploadFromFile {
return true;
}
+
/**
* Calls the parent stashFile and updates the uploadsession table to handle "chunks"
*
* @return UploadStashFile stashed file
*/
- public function stashFile() {
+ public function stashFile( User $user = null ) {
// Stash file is the called on creating a new chunk session:
$this->mChunkIndex = 0;
$this->mOffset = 0;
@@ -78,7 +79,7 @@ class UploadFromChunks extends UploadFromFile {
$this->mFileKey = $this->mLocalFile->getFileKey();
// Output a copy of this first to chunk 0 location:
- $status = $this->outputChunk( $this->mLocalFile->getPath() );
+ $this->outputChunk( $this->mLocalFile->getPath() );
// Update db table to reflect initial "chunk" state
$this->updateChunkStatus();
@@ -113,7 +114,7 @@ class UploadFromChunks extends UploadFromFile {
// Concatenate all the chunks to mVirtualTempPath
$fileList = Array();
// The first chunk is stored at the mVirtualTempPath path so we start on "chunk 1"
- for( $i = 0; $i <= $this->getChunkIndex(); $i++ ){
+ for( $i = 0; $i <= $this->getChunkIndex(); $i++ ) {
$fileList[] = $this->getVirtualChunkLocation( $i );
}
@@ -122,13 +123,16 @@ class UploadFromChunks extends UploadFromFile {
// Get a 0-byte temp file to perform the concatenation at
$tmpFile = TempFSFile::factory( 'chunkedupload_', $ext );
$tmpPath = $tmpFile
- ? $tmpFile->getPath()
+ ? $tmpFile->bind( $this )->getPath() // keep alive with $this
: false; // fail in concatenate()
// Concatenate the chunks at the temp file
+ $tStart = microtime( true );
$status = $this->repo->concatenate( $fileList, $tmpPath, FileRepo::DELETE_SOURCE );
- if( !$status->isOk() ){
+ $tAmount = microtime( true ) - $tStart;
+ if( !$status->isOk() ) {
return $status;
}
+ wfDebugLog( 'fileconcatenate', "Combined $i chunks in $tAmount seconds.\n" );
$this->mTempPath = $tmpPath; // file system path
$this->mFileSize = filesize( $this->mTempPath ); //Since this was set for the last chunk previously
@@ -141,7 +145,11 @@ class UploadFromChunks extends UploadFromFile {
// Update the mTempPath and mLocalFile
// ( for FileUpload or normal Stash to take over )
- $this->mLocalFile = parent::stashFile();
+ $tStart = microtime( true );
+ $this->mLocalFile = parent::stashFile( $this->user );
+ $tAmount = microtime( true ) - $tStart;
+ $this->mLocalFile->setLocalReference( $tmpFile ); // reuse (e.g. for getImageInfo())
+ wfDebugLog( 'fileconcatenate', "Stashed combined file ($i chunks) in $tAmount seconds.\n" );
return $status;
}
@@ -164,7 +172,7 @@ class UploadFromChunks extends UploadFromFile {
* @param $index
* @return string
*/
- function getVirtualChunkLocation( $index ){
+ function getVirtualChunkLocation( $index ) {
return $this->repo->getVirtualUrl( 'temp' ) .
'/' .
$this->repo->getHashPath(
@@ -176,9 +184,9 @@ class UploadFromChunks extends UploadFromFile {
/**
* Add a chunk to the temporary directory
*
- * @param $chunkPath string path to temporary chunk file
- * @param $chunkSize int size of the current chunk
- * @param $offset int offset of current chunk ( mutch match database chunk offset )
+ * @param string $chunkPath path to temporary chunk file
+ * @param int $chunkSize size of the current chunk
+ * @param int $offset offset of current chunk ( mutch match database chunk offset )
* @return Status
*/
public function addChunk( $chunkPath, $chunkSize, $offset ) {
@@ -202,7 +210,7 @@ class UploadFromChunks extends UploadFromFile {
return Status::newFatal( $e->getMessage() );
}
$status = $this->outputChunk( $chunkPath );
- if( $status->isGood() ){
+ if( $status->isGood() ) {
// Update local offset:
$this->mOffset = $preAppendOffset + $chunkSize;
// Update chunk table status db
@@ -218,11 +226,14 @@ class UploadFromChunks extends UploadFromFile {
/**
* Update the chunk db table with the current status:
*/
- private function updateChunkStatus(){
+ private function updateChunkStatus() {
wfDebug( __METHOD__ . " update chunk status for {$this->mFileKey} offset:" .
$this->getOffset() . ' inx:' . $this->getChunkIndex() . "\n" );
$dbw = $this->repo->getMasterDb();
+ // Use a quick transaction since we will upload the full temp file into shared
+ // storage, which takes time for large files. We don't want to hold locks then.
+ $dbw->begin( __METHOD__ );
$dbw->update(
'uploadstash',
array(
@@ -233,12 +244,13 @@ class UploadFromChunks extends UploadFromFile {
array( 'us_key' => $this->mFileKey ),
__METHOD__
);
+ $dbw->commit( __METHOD__ );
}
/**
* Get the chunk db state and populate update relevant local values
*/
- private function getChunkStatus(){
+ private function getChunkStatus() {
// get Master db to avoid race conditions.
// Otherwise, if chunk upload time < replag there will be spurious errors
$dbw = $this->repo->getMasterDb();
@@ -264,8 +276,8 @@ class UploadFromChunks extends UploadFromFile {
* Get the current Chunk index
* @return Integer index of the current chunk
*/
- private function getChunkIndex(){
- if( $this->mChunkIndex !== null ){
+ private function getChunkIndex() {
+ if( $this->mChunkIndex !== null ) {
return $this->mChunkIndex;
}
return 0;
@@ -275,8 +287,8 @@ class UploadFromChunks extends UploadFromFile {
* Gets the current offset in fromt the stashedupload table
* @return Integer current byte offset of the chunk file set
*/
- private function getOffset(){
- if ( $this->mOffset !== null ){
+ private function getOffset() {
+ if ( $this->mOffset !== null ) {
return $this->mOffset;
}
return 0;
@@ -289,7 +301,7 @@ class UploadFromChunks extends UploadFromFile {
* @throws UploadChunkFileException
* @return FileRepoStatus
*/
- private function outputChunk( $chunkPath ){
+ private function outputChunk( $chunkPath ) {
// Key is fileKey + chunk index
$fileKey = $this->getChunkFileKey();
@@ -314,11 +326,11 @@ class UploadFromChunks extends UploadFromFile {
return $storeStatus;
}
- private function getChunkFileKey( $index = null ){
- if( $index === null ){
+ private function getChunkFileKey( $index = null ) {
+ if( $index === null ) {
$index = $this->getChunkIndex();
}
- return $this->mFileKey . '.' . $index ;
+ return $this->mFileKey . '.' . $index;
}
/**
diff --git a/includes/upload/UploadFromFile.php b/includes/upload/UploadFromFile.php
index aa0cc77b..ab2a7a39 100644
--- a/includes/upload/UploadFromFile.php
+++ b/includes/upload/UploadFromFile.php
@@ -79,21 +79,21 @@ class UploadFromFile extends UploadBase {
* @return array
*/
public function verifyUpload() {
- # Check for a post_max_size or upload_max_size overflow, so that a
+ # Check for a post_max_size or upload_max_size overflow, so that a
# proper error can be shown to the user
if ( is_null( $this->mTempPath ) || $this->isEmptyFile() ) {
if ( $this->mUpload->isIniSizeOverflow() ) {
- return array(
+ return array(
'status' => UploadBase::FILE_TOO_LARGE,
- 'max' => min(
- self::getMaxUploadSize( $this->getSourceType() ),
- wfShorthandToInteger( ini_get( 'upload_max_filesize' ) ),
+ 'max' => min(
+ self::getMaxUploadSize( $this->getSourceType() ),
+ wfShorthandToInteger( ini_get( 'upload_max_filesize' ) ),
wfShorthandToInteger( ini_get( 'post_max_size' ) )
),
);
}
}
-
+
return parent::verifyUpload();
}
}
diff --git a/includes/upload/UploadFromStash.php b/includes/upload/UploadFromStash.php
index d79641ce..c82103cb 100644
--- a/includes/upload/UploadFromStash.php
+++ b/includes/upload/UploadFromStash.php
@@ -89,7 +89,7 @@ class UploadFromStash extends UploadBase {
* @param $key string
* @param $name string
*/
- public function initialize( $key, $name = 'upload_file' ) {
+ public function initialize( $key, $name = 'upload_file', $initTempFile = true ) {
/**
* Confirming a temporarily stashed upload.
* We don't want path names to be forged, so we keep
@@ -98,7 +98,7 @@ class UploadFromStash extends UploadBase {
*/
$metadata = $this->stash->getMetadata( $key );
$this->initializePathInfo( $name,
- $this->getRealPath ( $metadata['us_path'] ),
+ $initTempFile ? $this->getRealPath( $metadata['us_path'] ) : false,
$metadata['us_size'],
false
);
@@ -129,6 +129,14 @@ class UploadFromStash extends UploadBase {
return $this->mSourceType;
}
+ /**
+ * Get the base 36 SHA1 of the file
+ * @return string
+ */
+ public function getTempFileSha1Base36() {
+ return $this->mFileProps['sha1'];
+ }
+
/*
* protected function verifyFile() inherited
*/
@@ -136,12 +144,13 @@ class UploadFromStash extends UploadBase {
/**
* Stash the file.
*
+ * @param $user User
* @return UploadStashFile
*/
- public function stashFile() {
+ public function stashFile( User $user = null ) {
// replace mLocalFile with an instance of UploadStashFile, which adds some methods
// that are useful for stashed files.
- $this->mLocalFile = parent::stashFile();
+ $this->mLocalFile = parent::stashFile( $user );
return $this->mLocalFile;
}
diff --git a/includes/upload/UploadFromUrl.php b/includes/upload/UploadFromUrl.php
index 927c3cd9..70b69034 100644
--- a/includes/upload/UploadFromUrl.php
+++ b/includes/upload/UploadFromUrl.php
@@ -61,6 +61,8 @@ class UploadFromUrl extends UploadBase {
/**
* Checks whether the URL is for an allowed host
+ * The domains in the whitelist can include wildcard characters (*) in place
+ * of any of the domain levels, e.g. '*.flickr.com' or 'upload.*.gov.uk'.
*
* @param $url string
* @return bool
@@ -76,10 +78,28 @@ class UploadFromUrl extends UploadBase {
}
$valid = false;
foreach( $wgCopyUploadsDomains as $domain ) {
+ // See if the domain for the upload matches this whitelisted domain
+ $whitelistedDomainPieces = explode( '.', $domain );
+ $uploadDomainPieces = explode( '.', $parsedUrl['host'] );
+ if ( count( $whitelistedDomainPieces ) === count( $uploadDomainPieces ) ) {
+ $valid = true;
+ // See if all the pieces match or not (excluding wildcards)
+ foreach ( $whitelistedDomainPieces as $index => $piece ) {
+ if ( $piece !== '*' && $piece !== $uploadDomainPieces[$index] ) {
+ $valid = false;
+ }
+ }
+ if ( $valid ) {
+ // We found a match, so quit comparing against the list
+ break;
+ }
+ }
+ /* Non-wildcard test
if ( $parsedUrl['host'] === $domain ) {
$valid = true;
break;
}
+ */
}
return $valid;
}
@@ -312,7 +332,7 @@ class UploadFromUrl extends UploadBase {
'sessionKey' => $sessionKey,
) );
$job->initializeSessionData();
- $job->insert();
+ JobQueueGroup::singleton()->push( $job );
return $sessionKey;
}
diff --git a/includes/upload/UploadStash.php b/includes/upload/UploadStash.php
index 53a90582..089bd8b7 100644
--- a/includes/upload/UploadStash.php
+++ b/includes/upload/UploadStash.php
@@ -97,7 +97,7 @@ class UploadStash {
* Get a file and its metadata from the stash.
* The noAuth param is a bit janky but is required for automated scripts which clean out the stash.
*
- * @param $key String: key under which file information is stored
+ * @param string $key key under which file information is stored
* @param $noAuth Boolean (optional) Don't check authentication. Used by maintenance scripts.
* @throws UploadStashFileNotFoundException
* @throws UploadStashNotLoggedInException
@@ -106,15 +106,13 @@ class UploadStash {
* @return UploadStashFile
*/
public function getFile( $key, $noAuth = false ) {
-
if ( ! preg_match( self::KEY_FORMAT_REGEX, $key ) ) {
throw new UploadStashBadPathException( "key '$key' is not in a proper format" );
}
- if ( !$noAuth ) {
- if ( !$this->isLoggedIn ) {
- throw new UploadStashNotLoggedInException( __METHOD__ . ' No user is logged in, files must belong to users' );
- }
+ if ( !$noAuth && !$this->isLoggedIn ) {
+ throw new UploadStashNotLoggedInException( __METHOD__ .
+ ' No user is logged in, files must belong to users' );
}
if ( !isset( $this->fileMetadata[$key] ) ) {
@@ -131,8 +129,13 @@ class UploadStash {
$this->initFile( $key );
// fetch fileprops
- $path = $this->fileMetadata[$key]['us_path'];
- $this->fileProps[$key] = $this->repo->getFileProps( $path );
+ if ( strlen( $this->fileMetadata[$key]['us_props'] ) ) {
+ $this->fileProps[$key] = unserialize( $this->fileMetadata[$key]['us_props'] );
+ } else { // b/c for rows with no us_props
+ wfDebug( __METHOD__ . " fetched props for $key from file\n" );
+ $path = $this->fileMetadata[$key]['us_path'];
+ $this->fileProps[$key] = $this->repo->getFileProps( $path );
+ }
}
if ( ! $this->files[$key]->exists() ) {
@@ -152,7 +155,7 @@ class UploadStash {
/**
* Getter for file metadata.
*
- * @param key String: key under which file information is stored
+ * @param string $key key under which file information is stored
* @return Array
*/
public function getMetadata ( $key ) {
@@ -163,7 +166,7 @@ class UploadStash {
/**
* Getter for fileProps
*
- * @param key String: key under which file information is stored
+ * @param string $key key under which file information is stored
* @return Array
*/
public function getFileProps ( $key ) {
@@ -174,15 +177,15 @@ class UploadStash {
/**
* Stash a file in a temp directory and record that we did this in the database, along with other metadata.
*
- * @param $path String: path to file you want stashed
- * @param $sourceType String: the type of upload that generated this file (currently, I believe, 'file' or null)
+ * @param string $path path to file you want stashed
+ * @param string $sourceType the type of upload that generated this file (currently, I believe, 'file' or null)
* @throws UploadStashBadPathException
* @throws UploadStashFileException
* @throws UploadStashNotLoggedInException
* @return UploadStashFile: file, or null on failure
*/
public function stashFile( $path, $sourceType = null ) {
- if ( ! file_exists( $path ) ) {
+ if ( !is_file( $path ) ) {
wfDebug( __METHOD__ . " tried to stash file at '$path', but it doesn't exist\n" );
throw new UploadStashBadPathException( "path doesn't exist" );
}
@@ -192,12 +195,10 @@ class UploadStash {
// we will be initializing from some tmpnam files that don't have extensions.
// most of MediaWiki assumes all uploaded files have good extensions. So, we fix this.
$extension = self::getExtensionForPath( $path );
- if ( ! preg_match( "/\\.\\Q$extension\\E$/", $path ) ) {
+ if ( !preg_match( "/\\.\\Q$extension\\E$/", $path ) ) {
$pathWithGoodExtension = "$path.$extension";
- if ( ! rename( $path, $pathWithGoodExtension ) ) {
- throw new UploadStashFileException( "couldn't rename $path to have a better extension at $pathWithGoodExtension" );
- }
- $path = $pathWithGoodExtension;
+ } else {
+ $pathWithGoodExtension = $path;
}
// If no key was supplied, make one. a mysql insertid would be totally reasonable here, except
@@ -205,8 +206,8 @@ class UploadStash {
//
// some things that when combined will make a suitably unique key.
// see: http://www.jwz.org/doc/mid.html
- list ($usec, $sec) = explode( ' ', microtime() );
- $usec = substr($usec, 2);
+ list( $usec, $sec ) = explode( ' ', microtime() );
+ $usec = substr( $usec, 2 );
$key = wfBaseConvert( $sec . $usec, 10, 36 ) . '.' .
wfBaseConvert( mt_rand(), 10, 36 ) . '.'.
$this->userId . '.' .
@@ -221,7 +222,7 @@ class UploadStash {
wfDebug( __METHOD__ . " key for '$path': $key\n" );
// if not already in a temporary area, put it there
- $storeStatus = $this->repo->storeTemp( basename( $path ), $path );
+ $storeStatus = $this->repo->storeTemp( basename( $pathWithGoodExtension ), $path );
if ( ! $storeStatus->isOK() ) {
// It is a convention in MediaWiki to only return one error per API exception, even if multiple errors
@@ -244,9 +245,6 @@ class UploadStash {
}
$stashPath = $storeStatus->value;
- // we have renamed the file so we have to cleanup once done
- unlink($path);
-
// fetch the current user ID
if ( !$this->isLoggedIn ) {
throw new UploadStashNotLoggedInException( __METHOD__ . ' No user is logged in, files must belong to users' );
@@ -262,6 +260,7 @@ class UploadStash {
'us_key' => $key,
'us_orig_path' => $path,
'us_path' => $stashPath, // virtual URL
+ 'us_props' => serialize( $fileProps ),
'us_size' => $fileProps['size'],
'us_sha1' => $fileProps['sha1'],
'us_mime' => $fileProps['mime'],
@@ -319,8 +318,8 @@ class UploadStash {
/**
* Remove a particular file from the stash. Also removes it from the repo.
*
- * @throws UploadStashNotLoggedInException
- * @throws UploadStashWrongOwnerException
+ * @param $key
+ * @throws UploadStashNoSuchKeyException|UploadStashNotLoggedInException|UploadStashWrongOwnerException
* @return boolean: success
*/
public function removeFile( $key ) {
@@ -350,7 +349,6 @@ class UploadStash {
return $this->removeFileNoAuth( $key );
}
-
/**
* Remove a file (see removeFile), but doesn't check ownership first.
*
@@ -359,16 +357,16 @@ class UploadStash {
public function removeFileNoAuth( $key ) {
wfDebug( __METHOD__ . " clearing row $key\n" );
+ // Ensure we have the UploadStashFile loaded for this key
+ $this->getFile( $key );
+
$dbw = $this->repo->getMasterDb();
- // this gets its own transaction since it's called serially by the cleanupUploadStash maintenance script
- $dbw->begin( __METHOD__ );
$dbw->delete(
'uploadstash',
array( 'us_key' => $key ),
__METHOD__
);
- $dbw->commit( __METHOD__ );
// TODO: look into UnregisteredLocalFile and find out why the rv here is sometimes wrong (false when file was removed)
// for now, ignore.
@@ -419,6 +417,8 @@ class UploadStash {
* with an extension.
* XXX this is somewhat redundant with the checks that ApiUpload.php does with incoming
* uploads versus the desired filename. Maybe we can get that passed to us...
+ * @param $path
+ * @throws UploadStashFileException
* @return string
*/
public static function getExtensionForPath( $path ) {
@@ -456,7 +456,7 @@ class UploadStash {
/**
* Helper function: do the actual database query to fetch file metadata.
*
- * @param $key String: key
+ * @param string $key key
* @param $readFromDB: constant (default: DB_SLAVE)
* @return boolean
*/
@@ -490,7 +490,7 @@ class UploadStash {
/**
* Helper function: Initialize the UploadStashFile for a given file.
*
- * @param $key String: key under which to store the object
+ * @param string $key key under which to store the object
* @throws UploadStashZeroLengthFileException
* @return bool
*/
@@ -514,8 +514,8 @@ class UploadStashFile extends UnregisteredLocalFile {
* Arguably UnregisteredLocalFile should be handling its own file repo but that class is a bit retarded currently
*
* @param $repo FileRepo: repository where we should find the path
- * @param $path String: path to file
- * @param $key String: key to store the path and any stashed data under
+ * @param string $path path to file
+ * @param string $key key to store the path and any stashed data under
* @throws UploadStashBadPathException
* @throws UploadStashFileNotFoundException
*/
@@ -564,7 +564,7 @@ class UploadStashFile extends UnregisteredLocalFile {
* The actual argument is the result of thumbName although we seem to have
* buggy code elsewhere that expects a boolean 'suffix'
*
- * @param $thumbName String: name of thumbnail (e.g. "120px-123456.jpg" ), or false to just get the path
+ * @param string $thumbName name of thumbnail (e.g. "120px-123456.jpg" ), or false to just get the path
* @return String: path thumbnail should take on filesystem, or containing directory if thumbname is false
*/
public function getThumbPath( $thumbName = false ) {
@@ -580,7 +580,7 @@ class UploadStashFile extends UnregisteredLocalFile {
* We override this because we want to use the pretty url name instead of the
* ugly file name.
*
- * @param $params Array: handler-specific parameters
+ * @param array $params handler-specific parameters
* @param $flags integer Bitfield that supports THUMB_* constants
* @return String: base name for URL, like '120px-12345.jpg', or null if there is no handler
*/
@@ -603,7 +603,7 @@ class UploadStashFile extends UnregisteredLocalFile {
* the thumbnail urls be predictable. However, in our model the URL is not based on the filename
* (that's hidden in the db)
*
- * @param $thumbName String: basename of thumbnail file -- however, we don't want to use the file exactly
+ * @param string $thumbName basename of thumbnail file -- however, we don't want to use the file exactly
* @return String: URL to access thumbnail, or URL with partial path
*/
public function getThumbUrl( $thumbName = false ) {
diff --git a/includes/zhtable/Makefile b/includes/zhtable/Makefile
deleted file mode 100644
index 5dd88d38..00000000
--- a/includes/zhtable/Makefile
+++ /dev/null
@@ -1,336 +0,0 @@
-#
-# Creating the file ZhConversion.php used for Simplified/Traditional
-# Chinese conversion. It gets the basic conversion table from the Unihan
-# database, and construct the phrase tables using phrase libraries in
-# the SCIM packages and the libtabe package. There are also special
-# tables used to for adjustment.
-#
-
-GREP = LANG=zh_CN.UTF8 grep
-SED = LANG=zh_CN.UTF8 sed
-DIFF = LANG=zh_CN.UTF8 diff
-CC ?= gcc
-
-SF_MIRROR = easynews
-SCIM_TABLES_VER = 0.5.9
-SCIM_PINYIN_VER = 0.5.91
-LIBTABE_VER = 0.2.3
-
-# Installation directory
-INSTDIR = /usr/local/share/zhdaemons/
-
-all: ZhConversion.php tradphrases.notsure simpphrases.notsure wordlist toHans.dict toHant.dict toCN.dict toTW.dict toHK.dict toSG.dict
-
-# Download Unihan database and Traditional Chinese / Simplified Chinese phrases files
-Unihan.zip:
- wget -nc http://www.unicode.org/Public/UNIDATA/Unihan.zip
-
-scim-tables-$(SCIM_TABLES_VER).tar.gz:
- wget -nc http://$(SF_MIRROR).dl.sourceforge.net/sourceforge/scim/scim-tables-$(SCIM_TABLES_VER).tar.gz
-
-scim-pinyin-$(SCIM_PINYIN_VER).tar.gz:
- wget -nc http://$(SF_MIRROR).dl.sourceforge.net/sourceforge/scim/scim-pinyin-$(SCIM_PINYIN_VER).tar.gz
-
-libtabe-$(LIBTABE_VER).tgz:
- wget -nc http://$(SF_MIRROR).dl.sourceforge.net/sourceforge/libtabe/libtabe-$(LIBTABE_VER).tgz
-
-# Extract the file from a comressed files
-Unihan.txt: Unihan.zip
- unzip -oq Unihan.zip
-
-EZ.txt.in: scim-tables-$(SCIM_TABLES_VER).tar.gz
- tar -xzf scim-tables-$(SCIM_TABLES_VER).tar.gz -O scim-tables-$(SCIM_TABLES_VER)/tables/zh/EZ-Big.txt.in > EZ.txt.in
-
-Wubi.txt.in: scim-tables-$(SCIM_TABLES_VER).tar.gz
- tar -xzf scim-tables-$(SCIM_TABLES_VER).tar.gz -O scim-tables-$(SCIM_TABLES_VER)/tables/zh/Wubi.txt.in > Wubi.txt.in
-
-Ziranma.txt.in: scim-tables-$(SCIM_TABLES_VER).tar.gz
- tar -xzf scim-tables-$(SCIM_TABLES_VER).tar.gz -O scim-tables-$(SCIM_TABLES_VER)/tables/zh/Ziranma.txt.in > Ziranma.txt.in
-
-
-phrase_lib.txt: scim-pinyin-$(SCIM_PINYIN_VER).tar.gz
- tar -xzf scim-pinyin-$(SCIM_PINYIN_VER).tar.gz -O scim-pinyin-$(SCIM_PINYIN_VER)/data/phrase_lib.txt > phrase_lib.txt
-
-tsi.src: libtabe-$(LIBTABE_VER).tgz
- tar -xzf libtabe-$(LIBTABE_VER).tgz -O libtabe/tsi-src/tsi.src > tsi.src
-
-# Make a word list
-wordlist: phrase_lib.txt EZ.txt.in tsi.src
- iconv -c -f big5 -t utf8 tsi.src | $(SED) 's/# //g' | $(SED) 's/[ ][0-9].*//' > wordlist
- $(SED) 's/\(.*\)\t[0-9][0-9]*.*/\1/' phrase_lib.txt | $(SED) '1,5d' >> wordlist
- $(SED) '1,/BEGIN_TABLE/d' EZ.txt.in | colrm 1 8 | $(SED) 's/\t.*//' | $(GREP) "^...*" >> wordlist
- sort wordlist | uniq | $(SED) 's/ //g' > t
- mv t wordlist
-
-printutf8: printutf8.c
- $(CC) -o printutf8 printutf8.c
-
-unihan.t2s.t: Unihan.txt printutf8
- $(GREP) kSimplifiedVariant Unihan.txt | $(SED) '/#/d' | $(SED) 's/kSimplifiedVariant//' | ./printutf8 > unihan.t2s.t
-
-trad2simp.t: trad2simp.manual unihan.t2s.t
- cp unihan.t2s.t tmp1
- for I in `colrm 11 < trad2simp.manual` ; do $(SED) "/^$$I/d" tmp1 > tmp2; mv tmp2 tmp1; done
- cat trad2simp.manual tmp1 > trad2simp.t
-
-unihan.s2t.t: Unihan.txt printutf8
- $(GREP) kTraditionalVariant Unihan.txt | $(SED) '/#/d' | $(SED) 's/kTraditionalVariant//' | ./printutf8 > unihan.s2t.t
-
-simp2trad.t: unihan.s2t.t simp2trad.manual
- cp unihan.s2t.t tmp1
- for I in `colrm 11 < simp2trad.manual` ; do $(SED) "/^$$I/d" tmp1 > tmp2; mv tmp2 tmp1; done
- cat simp2trad.manual tmp1 > simp2trad.t
-
-t2s_1tomany.t: trad2simp.t
- $(GREP) -s ".\{19,\}" trad2simp.t | $(SED) 's/U+...../"/' | $(SED) 's/|U+...../"=>"/' | $(SED) 's/|U+.....//g' | $(SED) 's/|/",/' > t2s_1tomany.t
-
-t2s_1to1.t: trad2simp.t s2t_1tomany.t
- $(SED) "/.*|.*|.*|.*/d" trad2simp.t | $(SED) 's/U+[0-9a-z][0-9a-z]*/"/' | $(SED) 's/|U+[0-9a-z][0-9a-z]*/"=>"/' | $(SED) 's/|/",/' > t2s_1to1.t
- $(GREP) '"."=>"..",' s2t_1tomany.t | $(SED) 's/\("."\)=>".\(.\)",/"\2"=>\1,/' >> t2s_1to1.t
- $(GREP) '"."=>"...",' s2t_1tomany.t | $(SED) 's/\("."\)=>".\(.\).",/"\2"=>\1,/' >> t2s_1to1.t
- $(GREP) '"."=>"...",' s2t_1tomany.t | $(SED) 's/\("."\)=>"..\(.\)",/"\2"=>\1,/' >> t2s_1to1.t
- $(GREP) '"."=>"....",' s2t_1tomany.t | $(SED) 's/\("."\)=>".\(.\)..",/"\2"=>\1,/' >> t2s_1to1.t
- $(GREP) '"."=>"....",' s2t_1tomany.t | $(SED) 's/\("."\)=>"..\(.\).",/"\2"=>\1,/' >> t2s_1to1.t
- $(GREP) '"."=>"....",' s2t_1tomany.t | $(SED) 's/\("."\)=>"...\(.\)",/"\2"=>\1,/' >> t2s_1to1.t
- sort t2s_1to1.t | uniq > t
- mv t t2s_1to1.t
-
-
-s2t_1tomany.t: simp2trad.t
- $(GREP) -s ".\{19,\}" simp2trad.t | $(SED) 's/U+...../"/' | $(SED) 's/|U+...../"=>"/' | $(SED) 's/|U+.....//g' | $(SED) 's/|/",/' > s2t_1tomany.t
-
-s2t_1to1.t: simp2trad.t t2s_1tomany.t
- $(SED) "/.*|.*|.*|.*/d" simp2trad.t | $(SED) 's/U+[0-9a-z][0-9a-z]*/"/' | $(SED) 's/|U+[0-9a-z][0-9a-z]*/"=>"/' | $(SED) 's/|/",/' > s2t_1to1.t
- $(GREP) '"."=>"..",' t2s_1tomany.t | $(SED) 's/\("."\)=>".\(.\)",/"\2"=>\1,/' >> s2t_1to1.t
- $(GREP) '"."=>"...",' t2s_1tomany.t | $(SED) 's/\("."\)=>".\(.\).",/"\2"=>\1,/' >> s2t_1to1.t
- $(GREP) '"."=>"...",' t2s_1tomany.t | $(SED) 's/\("."\)=>"..\(.\)",/"\2"=>\1,/' >> s2t_1to1.t
- $(GREP) '"."=>"....",' t2s_1tomany.t | $(SED) 's/\("."\)=>".\(.\)..",/"\2"=>\1,/' >> s2t_1to1.t
- $(GREP) '"."=>"....",' t2s_1tomany.t | $(SED) 's/\("."\)=>"..\(.\).",/"\2"=>\1,/' >> s2t_1to1.t
- $(GREP) '"."=>"....",' t2s_1tomany.t | $(SED) 's/\("."\)=>"...\(.\)",/"\2"=>\1,/' >> s2t_1to1.t
- sort s2t_1to1.t | uniq > t
- mv t s2t_1to1.t
-
-tphrase.t: EZ.txt.in tsi.src
- colrm 1 8 < EZ.txt.in | $(SED) 's/\t//g' | $(GREP) "^.\{2,4\}[0-9]" | $(SED) 's/[0-9]//g' > t
- iconv -c -f big5 -t utf8 tsi.src | $(SED) 's/ [0-9].*//g' | $(SED) 's/[# ]//g'| $(GREP) "^.\{2,4\}" >> t
- sort t | uniq > tphrase.t
-
-alltradphrases.t: tphrase.t s2t_1tomany.t tradphrases_exclude.manual
- for i in `cat s2t_1tomany.t | $(SED) 's/.*=>".//' | $(SED) 's/"//g' |$(SED) 's/,/\n/' | $(SED) 's/\(.\)/\1\n/g' |sort | uniq`; do $(GREP) -s $$i tphrase.t ; done > alltradphrases.t || true
- cat alltradphrases.t | $(GREP) -vf tradphrases_exclude.manual > alltradphrases.tt ; mv alltradphrases.tt alltradphrases.t
-
-
-tradphrases_2.t: alltradphrases.t
- cat alltradphrases.t | $(GREP) "^..$$" | sort | uniq > tradphrases_2.t
-
-tradphrases_3.t: alltradphrases.t
- cat alltradphrases.t | $(GREP) "^...$$" | sort | uniq > tradphrases_3.t
- for i in `cat tradphrases_2.t`; do $(GREP) $$i tradphrases_3.t ; done | sort | uniq > t3 || true
- $(DIFF) t3 tradphrases_3.t | $(GREP) ">" | $(SED) 's/> //' > t
- mv t tradphrases_3.t
-
-
-tradphrases_4.t: alltradphrases.t
- cat alltradphrases.t | $(GREP) "^....$$" | sort | uniq > tradphrases_4.t
- for i in `cat tradphrases_2.t`; do $(GREP) $$i tradphrases_4.t ; done | sort | uniq > t3 || true
- $(DIFF) t3 tradphrases_4.t | $(GREP) ">" | $(SED) 's/> //' > t
- mv t tradphrases_4.t
- for i in `cat tradphrases_3.t`; do $(GREP) $$i tradphrases_4.t ; done | sort | uniq > t3 || true
- $(DIFF) t3 tradphrases_4.t | $(GREP) ">" | $(SED) 's/> //' > t
- mv t tradphrases_4.t
-
-tradphrases.t: tradphrases.manual tradphrases_2.t tradphrases_3.t tradphrases_4.t t2s_1tomany.t
- cat tradphrases.manual tradphrases_2.t tradphrases_3.t tradphrases_4.t |sort | uniq > tradphrases.t
- for i in `$(SED) 's/"\(.\).*/\1/' t2s_1tomany.t ` ; do $(GREP) $$i tradphrases.t ; done | $(DIFF) tradphrases.t - | $(GREP) '<' | $(SED) 's/< //' > t
- for i in `$(SED) 's/"\(..\)..*/\1/' t2s_1tomany.t ` ; do $(GREP) $$i tradphrases.t ; done | $(DIFF) tradphrases.t - | $(GREP) '<' | $(SED) 's/< //' >> t
- mv t tradphrases.t
- cat tradphrases.t | sort | uniq > t
- mv t tradphrases.t
-
-tradphrases.notsure: tradphrases_2.t tradphrases_3.t tradphrases_4.t t2s_1tomany.t
- cat tradphrases_2.t tradphrases_3.t tradphrases_4.t |sort | uniq > t
- for i in `$(SED) 's/"\(.\).*/\1/' t2s_1tomany.t ` ; do $(GREP) $$i t; done | $(DIFF) t - | $(GREP) '>' | $(SED) 's/> //' > tradphrases.notsure
-
-
-ph.t: phrase_lib.txt
- $(SED) 's/[\t0-9a-zA-Z]//g' phrase_lib.txt | $(GREP) "^.\{2,4\}$$" > ph.t
-
-Wubi.t: Wubi.txt.in
- $(SED) '1,/BEGIN_TABLE/d' Wubi.txt.in | colrm 1 8 | $(SED) 's/\t.*//' | $(GREP) "^...*" > Wubi.t
-
-Ziranma.t: Ziranma.txt.in
- $(SED) '1,/BEGIN_TABLE/d' Ziranma.txt.in | colrm 1 8 | $(SED) 's/\t.*//' | $(GREP) "^...*" > Ziranma.t
-
-
-allsimpphrases.t: t2s_1tomany.t ph.t Wubi.t Ziranma.t simpphrases_exclude.manual
- rm -f allsimpphrases.t
- for i in `cat t2s_1tomany.t | $(SED) 's/.*=>".//' | $(SED) 's/"//g' | $(SED) 's/,/\n/' | $(SED) 's/\(.\)/\1\n/g' | sort | uniq `; do $(GREP) $$i Wubi.t >> allsimpphrases.t; done
- for i in `cat t2s_1tomany.t | $(SED) 's/.*=>".//' | $(SED) 's/"//g' | $(SED) 's/,/\n/' | $(SED) 's/\(.\)/\1\n/g' | sort | uniq `; do $(GREP) $$i Ziranma.t >> allsimpphrases.t; done
- for i in `cat t2s_1tomany.t | $(SED) 's/.*=>".//' | $(SED) 's/"//g' | $(SED) 's/,/\n/' | $(SED) 's/\(.\)/\1\n/g' | sort | uniq `; do $(GREP) $$i ph.t >> allsimpphrases.t; done
- cat allsimpphrases.t | $(GREP) -vf simpphrases_exclude.manual > allsimpphrases.tt ; mv allsimpphrases.tt allsimpphrases.t
-
-simpphrases_2.t: allsimpphrases.t
- cat allsimpphrases.t | $(GREP) "^..$$" | sort | uniq > simpphrases_2.t
-
-simpphrases_3.t: allsimpphrases.t
- cat allsimpphrases.t | $(GREP) "^...$$" | sort | uniq > simpphrases_3.t
- for i in `cat simpphrases_2.t`; do $(GREP) $$i simpphrases_3.t ; done | sort | uniq > t3 || true
- $(DIFF) t3 simpphrases_3.t | $(GREP) ">" | $(SED) 's/> //' > t
- mv t simpphrases_3.t
-
-simpphrases_4.t: allsimpphrases.t
- cat allsimpphrases.t | $(GREP) "^....$$" | sort | uniq > simpphrases_4.t
- rm -f t
- for i in `cat simpphrases_2.t`; do $(GREP) $$i simpphrases_4.t >> t; done || true
- sort t | uniq > t3
- $(DIFF) t3 simpphrases_4.t | $(GREP) ">" | $(SED) 's/> //' > t
- mv t simpphrases_4.t
- for i in `cat simpphrases_3.t`; do $(GREP) $$i simpphrases_4.t; done | sort | uniq > t3 || true
- $(DIFF) t3 simpphrases_4.t | $(GREP) ">" | $(SED) 's/> //' > t
- mv t simpphrases_4.t
-
-simpphrases.t: simpphrases.manual simpphrases_2.t simpphrases_3.t simpphrases_4.t t2s_1tomany.t
- cat simpphrases.manual simpphrases_2.t simpphrases_3.t simpphrases_4.t > simpphrases.t
- for i in `$(SED) 's/"\(.\).*/\1/' t2s_1tomany.t ` ; do $(GREP) $$i simpphrases.t ; done | $(DIFF) simpphrases.t - | $(GREP) '<' | $(SED) 's/< //' > t
- for i in `$(SED) 's/"\(..\)..*/\1/' t2s_1tomany.t ` ; do $(GREP) $$i simpphrases.t ; done | $(DIFF) simpphrases.t - | $(GREP) '<' | $(SED) 's/< //' >> t
- mv t simpphrases.t
- cat simpphrases.t | sort | uniq > t
- mv t simpphrases.t
-
-simpphrases.notsure: simpphrases_2.t simpphrases_3.t simpphrases_4.t t2s_1tomany.t
- cat simpphrases_2.t simpphrases_3.t simpphrases_4.t > t
- for i in `$(SED) 's/"\(.\).*/\1/' t2s_1tomany.t ` ; do $(GREP) $$i t ; done | $(DIFF) t - | $(GREP) '>' | $(SED) 's/> //' > simpphrases.notsure
-
-trad2simp1to1.t: t2s_1tomany.t t2s_1to1.t trad2simp_noconvert.manual
- $(SED) 's/\(.......\).*/\1",/' t2s_1tomany.t > tt
- colrm 1 7 < trad2simp.manual | colrm 3 > trad2simpcharsrc.t
- colrm 1 17 < trad2simp.manual | colrm 3 > trad2simpchardest.t
- cat trad2simpcharsrc.t | $(GREP) -f trad2simpchardest.t > trad2simprepeatedchar.t
- cat tt | $(GREP) -vf trad2simprepeatedchar.t > trad2simp1to1.t
- cat t2s_1to1.t >> trad2simp1to1.t
- cat trad2simp1to1.t | $(GREP) -vf trad2simp_noconvert.manual > tt
- mv tt trad2simp1to1.t
-
-simp2trad1to1.t: s2t_1tomany.t s2t_1to1.t simp2trad.manual simp2trad_noconvert.manual
- $(SED) 's/\(.......\).*/\1",/' s2t_1tomany.t > tt
- colrm 1 7 < simp2trad.manual | colrm 3 > simp2tradcharsrc.t
- colrm 1 17 < simp2trad.manual | colrm 3 > simp2tradchardest.t
- cat simp2tradcharsrc.t | $(GREP) -f simp2tradchardest.t > simp2tradrepeatedchar.t
- cat tt | $(GREP) -vf simp2tradrepeatedchar.t > simp2trad1to1.t
- cat s2t_1to1.t >> simp2trad1to1.t
- cat simp2trad1to1.t | $(GREP) -vf simp2trad_noconvert.manual > tt
- mv tt simp2trad1to1.t
-
-trad2simp.php: trad2simp1to1.t tradphrases.t trad2simp_supp_unset.manual trad2simp_supp_set.manual
- printf '<?php\n$$trad2simp=array(' > trad2simp.php
- cat trad2simp1to1.t >> trad2simp.php
- $(SED) 's/\(.*\)\t\(.*\)/"\1" => "\2",/' trad2simp_supp_set.manual >> trad2simp.php
- printf ');\n$$str=\n"' >> trad2simp.php
- cat tradphrases.t >> trad2simp.php
- printf '";\n$$t=strtr($$str, $$trad2simp);\necho $$t;\n?>' >> trad2simp.php
- cat trad2simp1to1.t | $(GREP) -vf trad2simp_supp_unset.manual > tt
- mv tt trad2simp1to1.t
-
-simp2trad.php: simp2trad1to1.t simpphrases.t simp2trad_supp_set.manual
- printf '<?php\n$$simp2trad=array(' > simp2trad.php
- cat simp2trad1to1.t >> simp2trad.php
- $(SED) 's/\(.*\)\t\(.*\)/"\1" => "\2",/' simp2trad_supp_set.manual >> simp2trad.php
- printf ');\n$$str=\n"' >> simp2trad.php
- cat simpphrases.t >> simp2trad.php
- printf '";\n$$t=strtr($$str, $$simp2trad);\necho $$t;\n?>' >> simp2trad.php
-
-simp2trad.phrases.t: trad2simp.php tradphrases.t simp2trad_supp_set.manual
- php -f trad2simp.php | $(SED) 's/\(.*\)/"\1" => /' > tmp1
- cat tradphrases.t | $(SED) 's/\(.*\)/"\1",/' > tmp2
- paste tmp1 tmp2 > simp2trad.phrases.t
- colrm 3 < simp2trad_supp_set.manual > simp2trad_supp_noconvert.t
- cat trad2simp.php | $(GREP) -vf simp2trad_supp_noconvert.t > trad2simp.tt
- mv trad2simp.tt trad2simp.php
-
-trad2simp.phrases.t: simp2trad.php simpphrases.t trad2simp_supp_set.manual
- php -f simp2trad.php | $(SED) 's/\(.*\)/"\1" => /' > tmp1
- cat simpphrases.t | $(SED) 's/\(.*\)/"\1",/' > tmp2
- paste tmp1 tmp2 > trad2simp.phrases.t
- colrm 3 < trad2simp_supp_set.manual > trad2simp_supp_noconvert.t
- cat simp2trad.php | $(GREP) -vf trad2simp_supp_noconvert.t > simp2trad.tt
- mv simp2trad.tt simp2trad.php
-
-toHans.dict: trad2simp1to1.t trad2simp.phrases.t toSimp.manual
- cat trad2simp1to1.t | $(SED) 's/[, \t]//g' | $(SED) 's/=>/\t/' > toHans.dict
- cat trad2simp.phrases.t | $(SED) 's/[, \t]//g' | $(SED) 's/=>/\t/' >> toHans.dict
- cat toSimp.manual | $(SED) 's/ //g' | $(SED) 's/\(^.*\)\t\(.*\)/"\1"\t"\2"/' >> toHans.dict
-
-toHant.dict: simp2trad1to1.t simp2trad.phrases.t toTrad.manual
- cat simp2trad1to1.t | $(SED) 's/[, \t]//g' | $(SED) 's/=>/\t/' > toHant.dict
- cat simp2trad.phrases.t | $(SED) 's/[, \t]//g' | $(SED) 's/=>/\t/' >> toHant.dict
- cat toTrad.manual | $(SED) 's/ //g' | $(SED) 's/\(^.*\)\t\(.*\)/"\1"\t"\2"/' >> toHant.dict
-
-toTW.dict: toTW.manual
- cat toTW.manual | $(SED) 's/ //g' | $(SED) 's/\(^.*\)\t\(.*\)/"\1"\t"\2"/' > toTW.dict
-
-toHK.dict: toHK.manual
- cat toHK.manual | $(SED) 's/ //g' | $(SED) 's/\(^.*\)\t\(.*\)/"\1"\t"\2"/' > toHK.dict
-
-toCN.dict: toCN.manual
- cat toCN.manual | $(SED) 's/ //g' | $(SED) 's/\(^.*\)\t\(.*\)/"\1"\t"\2"/' > toCN.dict
-
-toSG.dict: toSG.manual
- cat toSG.manual | $(SED) 's/ //g' | $(SED) 's/\(^.*\)\t\(.*\)/"\1"\t"\2"/' > toSG.dict
-
-ZhConversion.php: simp2trad1to1.t simp2trad.phrases.t trad2simp1to1.t trad2simp.phrases.t toSimp.manual toTrad.manual toCN.manual toHK.manual toSG.manual toTW.manual
- printf '<?php\n/**\n * Simplified / Traditional Chinese conversion tables\n' > ZhConversion.php
- printf ' *\n * Automatically generated using code and data in includes/zhtable/\n' >> ZhConversion.php
- printf ' * Do not modify directly!\n */\n\n' >> ZhConversion.php
- printf '$$zh2Hant = array(\n' >> ZhConversion.php
- cat simp2trad1to1.t >> ZhConversion.php
- echo >> ZhConversion.php
- cat simp2trad.phrases.t >> ZhConversion.php
- $(SED) 's/\(.*\)\t\(.*\)/"\1" => "\2",/' toTrad.manual >> ZhConversion.php
- echo ');' >> ZhConversion.php
- echo >> ZhConversion.php
- printf '$$zh2Hans = array(\n' >> ZhConversion.php
- cat trad2simp1to1.t >> ZhConversion.php
- echo >> ZhConversion.php
- cat trad2simp.phrases.t >> ZhConversion.php
- $(SED) 's/\(.*\)\t\(.*\)/"\1" => "\2",/' toSimp.manual >> ZhConversion.php
- echo ');' >> ZhConversion.php
- echo >> ZhConversion.php
- printf '$$zh2TW = array(\n' >> ZhConversion.php
- $(SED) 's/\(.*\)\t\(.*\)/"\1" => "\2",/' toTW.manual >> ZhConversion.php
- echo ');' >> ZhConversion.php
- echo >> ZhConversion.php
- printf '$$zh2HK = array(\n' >> ZhConversion.php
- $(SED) 's/\(.*\)\t\(.*\)/"\1" => "\2",/' toHK.manual >> ZhConversion.php
- echo ');' >> ZhConversion.php
- echo >> ZhConversion.php
- printf '$$zh2CN = array(\n' >> ZhConversion.php
- $(SED) 's/\(.*\)\t\(.*\)/"\1" => "\2",/' toCN.manual >> ZhConversion.php
- echo ');' >> ZhConversion.php
- echo >> ZhConversion.php
- printf '$$zh2SG = array(\n' >> ZhConversion.php
- $(SED) 's/\(.*\)\t\(.*\)/"\1" => "\2",/' toSG.manual >> ZhConversion.php
- echo >> ZhConversion.php
- printf ');' >> ZhConversion.php
-
-clean: cleantmp cleandl
-
-cleantmp:
- # Stuff unpacked from the files fetched by wget
- rm -f \
- Unihan.txt \
- EZ.txt.in \
- Wubi.txt.in \
- Ziranma.txt.in \
- phrase_lib.txt \
- tsi.src
- # Temporary files and other trash
- rm -f ZhConversion.php tmp1 tmp2 tmp3 t3 *.t trad2simp.php simp2trad.php *.dict printutf8 *~ \
- simpphrases.notsure tradphrases.notsure wordlist
-
-cleandl:
- rm -f \
- Unihan.zip \
- scim-tables-$(SCIM_TABLES_VER).tar.gz \
- scim-pinyin-$(SCIM_PINYIN_VER).tar.gz \
- libtabe-$(LIBTABE_VER).tgz
-
diff --git a/includes/zhtable/Makefile.py b/includes/zhtable/Makefile.py
deleted file mode 100644
index fd603ce4..00000000
--- a/includes/zhtable/Makefile.py
+++ /dev/null
@@ -1,391 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# @author Philip
-import tarfile as tf
-import zipfile as zf
-import os, re, shutil, sys, platform
-
-pyversion = platform.python_version()
-islinux = platform.system().lower() == 'linux'
-
-if pyversion[:3] in ['2.6', '2.7']:
- import urllib as urllib_request
- import codecs
- open = codecs.open
- _unichr = unichr
- if sys.maxunicode < 0x10000:
- def unichr(i):
- if i < 0x10000:
- return _unichr(i)
- else:
- return _unichr( 0xD7C0 + ( i>>10 ) ) + _unichr( 0xDC00 + ( i & 0x3FF ) )
-elif pyversion[:2] == '3.':
- import urllib.request as urllib_request
- unichr = chr
-
-def unichr2( *args ):
- return [unichr( int( i.split('<')[0][2:], 16 ) ) for i in args]
-
-def unichr3( *args ):
- return [unichr( int( i[2:7], 16 ) ) for i in args if i[2:7]]
-
-# DEFINE
-UNIHAN_VER = '5.2.0'
-SF_MIRROR = 'dfn'
-SCIM_TABLES_VER = '0.5.10'
-SCIM_PINYIN_VER = '0.5.91'
-LIBTABE_VER = '0.2.3'
-# END OF DEFINE
-
-def download( url, dest ):
- if os.path.isfile( dest ):
- print( 'File %s is up to date.' % dest )
- return
- global islinux
- if islinux:
- # we use wget instead urlretrieve under Linux,
- # because wget could display details like download progress
- os.system( 'wget %s -O %s' % ( url, dest ) )
- else:
- print( 'Downloading from [%s] ...' % url )
- urllib_request.urlretrieve( url, dest )
- print( 'Download complete.\n' )
- return
-
-def uncompress( fp, member, encoding = 'U8' ):
- name = member.rsplit( '/', 1 )[-1]
- print( 'Extracting %s ...' % name )
- fp.extract( member )
- shutil.move( member, name )
- if '/' in member:
- shutil.rmtree( member.split( '/', 1 )[0] )
- return open( name, 'rb', encoding, 'ignore' )
-
-unzip = lambda path, member, encoding = 'U8': \
- uncompress( zf.ZipFile( path ), member, encoding )
-
-untargz = lambda path, member, encoding = 'U8': \
- uncompress( tf.open( path, 'r:gz' ), member, encoding )
-
-def parserCore( fp, pos, beginmark = None, endmark = None ):
- if beginmark and endmark:
- start = False
- else: start = True
- mlist = set()
- for line in fp:
- if beginmark and line.startswith( beginmark ):
- start = True
- continue
- elif endmark and line.startswith( endmark ):
- break
- if start and not line.startswith( '#' ):
- elems = line.split()
- if len( elems ) < 2:
- continue
- elif len( elems[0] ) > 1 and \
- len( elems[pos] ) > 1: # words only
- mlist.add( elems[pos] )
- return mlist
-
-def tablesParser( path, name ):
- """ Read file from scim-tables and parse it. """
- global SCIM_TABLES_VER
- src = 'scim-tables-%s/tables/zh/%s' % ( SCIM_TABLES_VER, name )
- fp = untargz( path, src, 'U8' )
- return parserCore( fp, 1, 'BEGIN_TABLE', 'END_TABLE' )
-
-ezbigParser = lambda path: tablesParser( path, 'EZ-Big.txt.in' )
-wubiParser = lambda path: tablesParser( path, 'Wubi.txt.in' )
-zrmParser = lambda path: tablesParser( path, 'Ziranma.txt.in' )
-
-def phraseParser( path ):
- """ Read phrase_lib.txt and parse it. """
- global SCIM_PINYIN_VER
- src = 'scim-pinyin-%s/data/phrase_lib.txt' % SCIM_PINYIN_VER
- dst = 'phrase_lib.txt'
- fp = untargz( path, src, 'U8' )
- return parserCore( fp, 0 )
-
-def tsiParser( path ):
- """ Read tsi.src and parse it. """
- src = 'libtabe/tsi-src/tsi.src'
- dst = 'tsi.src'
- fp = untargz( path, src, 'big5hkscs' )
- return parserCore( fp, 0 )
-
-def unihanParser( path ):
- """ Read Unihan_Variants.txt and parse it. """
- fp = unzip( path, 'Unihan_Variants.txt', 'U8' )
- t2s = dict()
- s2t = dict()
- for line in fp:
- if line.startswith( '#' ):
- continue
- else:
- elems = line.split()
- if len( elems ) < 3:
- continue
- type = elems.pop( 1 )
- elems = unichr2( *elems )
- if type == 'kTraditionalVariant':
- s2t[elems[0]] = elems[1:]
- elif type == 'kSimplifiedVariant':
- t2s[elems[0]] = elems[1:]
- fp.close()
- return ( t2s, s2t )
-
-def applyExcludes( mlist, path ):
- """ Apply exclude rules from path to mlist. """
- excludes = open( path, 'rb', 'U8' ).read().split()
- excludes = [word.split( '#' )[0].strip() for word in excludes]
- excludes = '|'.join( excludes )
- excptn = re.compile( '.*(?:%s).*' % excludes )
- diff = [mword for mword in mlist if excptn.search( mword )]
- mlist.difference_update( diff )
- return mlist
-
-def charManualTable( path ):
- fp = open( path, 'rb', 'U8' )
- ret = {}
- for line in fp:
- elems = line.split( '#' )[0].split( '|' )
- elems = unichr3( *elems )
- if len( elems ) > 1:
- ret[elems[0]] = elems[1:]
- return ret
-
-def toManyRules( src_table ):
- tomany = set()
- for ( f, t ) in src_table.iteritems():
- for i in range( 1, len( t ) ):
- tomany.add( t[i] )
- return tomany
-
-def removeRules( path, table ):
- fp = open( path, 'rb', 'U8' )
- texc = list()
- for line in fp:
- elems = line.split( '=>' )
- f = t = elems[0].strip()
- if len( elems ) == 2:
- t = elems[1].strip()
- f = f.strip('"').strip("'")
- t = t.strip('"').strip("'")
- if f:
- try:
- table.pop( f )
- except:
- pass
- if t:
- texc.append( t )
- texcptn = re.compile( '^(?:%s)$' % '|'.join( texc ) )
- for (tmp_f, tmp_t) in table.copy().iteritems():
- if texcptn.match( tmp_t ):
- table.pop( tmp_f )
- return table
-
-def customRules( path ):
- fp = open( path, 'rb', 'U8' )
- ret = dict()
- for line in fp:
- elems = line.split( '#' )[0].split()
- if len( elems ) > 1:
- ret[elems[0]] = elems[1]
- return ret
-
-def dictToSortedList( src_table, pos ):
- return sorted( src_table.items(), key = lambda m: m[pos] )
-
-def translate( text, conv_table ):
- i = 0
- while i < len( text ):
- for j in range( len( text ) - i, 0, -1 ):
- f = text[i:][:j]
- t = conv_table.get( f )
- if t:
- text = text[:i] + t + text[i:][j:]
- i += len(t) - 1
- break
- i += 1
- return text
-
-def manualWordsTable( path, conv_table, reconv_table ):
- fp = open( path, 'rb', 'U8' )
- reconv_table = {}
- wordlist = [line.split( '#' )[0].strip() for line in fp]
- wordlist = list( set( wordlist ) )
- wordlist.sort( key = len, reverse = True )
- while wordlist:
- word = wordlist.pop()
- new_word = translate( word, conv_table )
- rcv_word = translate( word, reconv_table )
- if word != rcv_word:
- reconv_table[word] = word
- reconv_table[new_word] = word
- return reconv_table
-
-def defaultWordsTable( src_wordlist, src_tomany, char_conv_table, char_reconv_table ):
- wordlist = list( src_wordlist )
- wordlist.sort( key = len, reverse = True )
- word_conv_table = {}
- word_reconv_table = {}
- conv_table = char_conv_table.copy()
- reconv_table = char_reconv_table.copy()
- tomanyptn = re.compile( '(?:%s)' % '|'.join( src_tomany ) )
- while wordlist:
- conv_table.update( word_conv_table )
- reconv_table.update( word_reconv_table )
- word = wordlist.pop()
- new_word_len = word_len = len( word )
- while new_word_len == word_len:
- add = False
- test_word = translate( word, reconv_table )
- new_word = translate( word, conv_table )
- if not reconv_table.get( new_word ) \
- and ( test_word != word \
- or ( tomanyptn.search( word ) \
- and word != translate( new_word, reconv_table ) ) ):
- word_conv_table[word] = new_word
- word_reconv_table[new_word] = word
- try:
- word = wordlist.pop()
- except IndexError:
- break
- new_word_len = len(word)
- return word_reconv_table
-
-def PHPArray( table ):
- lines = ['\'%s\' => \'%s\',' % (f, t) for (f, t) in table if f and t]
- return '\n'.join(lines)
-
-def main():
- #Get Unihan.zip:
- url = 'http://www.unicode.org/Public/%s/ucd/Unihan.zip' % UNIHAN_VER
- han_dest = 'Unihan.zip'
- download( url, han_dest )
-
- # Get scim-tables-$(SCIM_TABLES_VER).tar.gz:
- url = 'http://%s.dl.sourceforge.net/sourceforge/scim/scim-tables-%s.tar.gz' % ( SF_MIRROR, SCIM_TABLES_VER )
- tbe_dest = 'scim-tables-%s.tar.gz' % SCIM_TABLES_VER
- download( url, tbe_dest )
-
- # Get scim-pinyin-$(SCIM_PINYIN_VER).tar.gz:
- url = 'http://%s.dl.sourceforge.net/sourceforge/scim/scim-pinyin-%s.tar.gz' % ( SF_MIRROR, SCIM_PINYIN_VER )
- pyn_dest = 'scim-pinyin-%s.tar.gz' % SCIM_PINYIN_VER
- download( url, pyn_dest )
-
- # Get libtabe-$(LIBTABE_VER).tgz:
- url = 'http://%s.dl.sourceforge.net/sourceforge/libtabe/libtabe-%s.tgz' % ( SF_MIRROR, LIBTABE_VER )
- lbt_dest = 'libtabe-%s.tgz' % LIBTABE_VER
- download( url, lbt_dest )
-
- # Unihan.txt
- ( t2s_1tomany, s2t_1tomany ) = unihanParser( han_dest )
-
- t2s_1tomany.update( charManualTable( 'trad2simp.manual' ) )
- s2t_1tomany.update( charManualTable( 'simp2trad.manual' ) )
-
- t2s_1to1 = dict( [( f, t[0] ) for ( f, t ) in t2s_1tomany.iteritems()] )
- s2t_1to1 = dict( [( f, t[0] ) for ( f, t ) in s2t_1tomany.iteritems()] )
-
- s_tomany = toManyRules( t2s_1tomany )
- t_tomany = toManyRules( s2t_1tomany )
-
- # noconvert rules
- t2s_1to1 = removeRules( 'trad2simp_noconvert.manual', t2s_1to1 )
- s2t_1to1 = removeRules( 'simp2trad_noconvert.manual', s2t_1to1 )
-
- # the supper set for word to word conversion
- t2s_1to1_supp = t2s_1to1.copy()
- s2t_1to1_supp = s2t_1to1.copy()
- t2s_1to1_supp.update( customRules( 'trad2simp_supp_set.manual' ) )
- s2t_1to1_supp.update( customRules( 'simp2trad_supp_set.manual' ) )
-
- # word to word manual rules
- t2s_word2word_manual = manualWordsTable( 'simpphrases.manual', s2t_1to1_supp, t2s_1to1_supp )
- t2s_word2word_manual.update( customRules( 'toSimp.manual' ) )
- s2t_word2word_manual = manualWordsTable( 'tradphrases.manual', t2s_1to1_supp, s2t_1to1_supp )
- s2t_word2word_manual.update( customRules( 'toTrad.manual' ) )
-
- # word to word rules from input methods
- t_wordlist = set()
- s_wordlist = set()
- t_wordlist.update( ezbigParser( tbe_dest ),
- tsiParser( lbt_dest ) )
- s_wordlist.update( wubiParser( tbe_dest ),
- zrmParser( tbe_dest ),
- phraseParser( pyn_dest ) )
-
- # exclude
- s_wordlist = applyExcludes( s_wordlist, 'simpphrases_exclude.manual' )
- t_wordlist = applyExcludes( t_wordlist, 'tradphrases_exclude.manual' )
-
- s2t_supp = s2t_1to1_supp.copy()
- s2t_supp.update( s2t_word2word_manual )
- t2s_supp = t2s_1to1_supp.copy()
- t2s_supp.update( t2s_word2word_manual )
-
- # parse list to dict
- t2s_word2word = defaultWordsTable( s_wordlist, s_tomany, s2t_1to1_supp, t2s_supp )
- t2s_word2word.update( t2s_word2word_manual )
- s2t_word2word = defaultWordsTable( t_wordlist, t_tomany, t2s_1to1_supp, s2t_supp )
- s2t_word2word.update( s2t_word2word_manual )
-
- # Final tables
- # sorted list toHans
- t2s_1to1 = dict( [( f, t ) for ( f, t ) in t2s_1to1.iteritems() if f != t] )
- toHans = dictToSortedList( t2s_1to1, 0 ) + dictToSortedList( t2s_word2word, 1 )
- # sorted list toHant
- s2t_1to1 = dict( [( f, t ) for ( f, t ) in s2t_1to1.iteritems() if f != t] )
- toHant = dictToSortedList( s2t_1to1, 0 ) + dictToSortedList( s2t_word2word, 1 )
- # sorted list toCN
- toCN = dictToSortedList( customRules( 'toCN.manual' ), 1 )
- # sorted list toHK
- toHK = dictToSortedList( customRules( 'toHK.manual' ), 1 )
- # sorted list toSG
- toSG = dictToSortedList( customRules( 'toSG.manual' ), 1 )
- # sorted list toTW
- toTW = dictToSortedList( customRules( 'toTW.manual' ), 1 )
-
- # Get PHP Array
- php = '''<?php
-/**
- * Simplified / Traditional Chinese conversion tables
- *
- * Automatically generated using code and data in includes/zhtable/
- * Do not modify directly!
- *
- * @file
- */
-
-$zh2Hant = array(\n'''
- php += PHPArray( toHant ) \
- + '\n);\n\n$zh2Hans = array(\n' \
- + PHPArray( toHans ) \
- + '\n);\n\n$zh2TW = array(\n' \
- + PHPArray( toTW ) \
- + '\n);\n\n$zh2HK = array(\n' \
- + PHPArray( toHK ) \
- + '\n);\n\n$zh2CN = array(\n' \
- + PHPArray( toCN ) \
- + '\n);\n\n$zh2SG = array(\n' \
- + PHPArray( toSG ) \
- + '\n);\n'
-
- f = open( os.path.join( '..', 'ZhConversion.php' ), 'wb', encoding = 'utf8' )
- print ('Writing ZhConversion.php ... ')
- f.write( php )
- f.close()
-
- # Remove temporary files
- print ('Deleting temporary files ... ')
- os.remove('EZ-Big.txt.in')
- os.remove('phrase_lib.txt')
- os.remove('tsi.src')
- os.remove('Unihan_Variants.txt')
- os.remove('Wubi.txt.in')
- os.remove('Ziranma.txt.in')
-
-
-if __name__ == '__main__':
- main()
diff --git a/includes/zhtable/README b/includes/zhtable/README
deleted file mode 100644
index 7e3f87e2..00000000
--- a/includes/zhtable/README
+++ /dev/null
@@ -1,33 +0,0 @@
-The various .manual files contains special mappings not included in the
-unihan database, and phrases not included in the SCIM package.
-
-- simp2trad.manual: Simplified to Traditional character mapping. Most
- data adapted from
-
- 冯寿忠,“非对称繁简字”对照表, 《语文建设通讯》1997-9第53期.
- /http://www.yywzw.com/jt/feng/fengb01.htm
-
-- trad2simp.manual: Traditional to Simplified character mapping.
-
-- simp2trad_noconvert.manual: Do not convert the chars as inapporiate.
-
-- trad2simp_noconvert.manual: Do not convert the chars as inapporiate.
-
-- tradphrases.manual: Phrases in Traditional Chinese. A portition is obtained
- from the TongWen package (http://tongwen.mozdev.org/)
-
-- simpphrases.manual: Phrases in Simplified Chinese.
-
-- tradphrases_exclude.manual: Excluding several phrases from
- the SCIM phrases as inappoiated.
-
-- simpphrases_exclude.manual: Excluding several phrases from
- the SCIM phrases as inapporated.
-
-- toTrad.manual, toSimp.manual: Special phrase mappings that
- tradphrases.manual or simphrases.manual cannot be handled.
-
-- toTW.manual, toCN.manual, toSG.manual and toHK.manual: Special phrase
- mappings.
-
-zhengzhu at gmail dot com & shinjiman at gmail dot com
diff --git a/includes/zhtable/printutf8.c b/includes/zhtable/printutf8.c
deleted file mode 100644
index b6ccf17c..00000000
--- a/includes/zhtable/printutf8.c
+++ /dev/null
@@ -1,99 +0,0 @@
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-/*
- Unicode UTF8
-0x00000000 - 0x0000007F: 0xxxxxxx
-0x00000080 - 0x000007FF: 110xxx xx 10xx xxxx
-0x00000800 - 0x0000FFFF: 1110xxxx 10xxxx xx 10xx xxxx
-0x00010000 - 0x001FFFFF: 11110x xx 10xx xxxx 10xxxx xx 10xx xxxx
-0x00200000 - 0x03FFFFFF: 111110xx 10xxxx xx 10xx xxxx 10xxxx xx 10xx xxxx
-0x04000000 - 0x7FFFFFFF: 1111110x 10xx xxxx 10xxxx xx 10xx xxxx 10xxxx xx 10xx xxxx
-
-0000 0 1001 9
-0001 1 1010 A
-0010 2 1011 B
-0011 3 1100 C
-0100 4 1101 D
-0101 5 1110 E
-0110 6 1111 F
-0111 7
-1000 8
-*/
-void printUTF8(long long u) {
- long long m;
- if(u<0x80) {
- printf("%c", (unsigned char)u);
- }
- else if(u<0x800) {
- m = ((u&0x7c0)>>6) | 0xc0;
- printf("%c", (unsigned char)m);
- m = (u&0x3f) | 0x80;
- printf("%c", (unsigned char)m);
- }
- else if(u<0x10000) {
- m = ((u&0xf000)>>12) | 0xe0;
- printf("%c",(unsigned char)m);
- m = ((u&0xfc0)>>6) | 0x80;
- printf("%c",(unsigned char)m);
- m = (u & 0x3f) | 0x80;
- printf("%c",(unsigned char)m);
- }
- else if(u<0x200000) {
- m = ((u&0x1c0000)>>18) | 0xf0;
- printf("%c", (unsigned char)m);
- m = ((u& 0x3f000)>>12) | 0x80;
- printf("%c", (unsigned char)m);
- m = ((u& 0xfc0)>>6) | 0x80;
- printf("%c", (unsigned char)m);
- m = (u&0x3f) | 0x80;
- printf("%c", (unsigned char)m);
- }
- else if(u<0x4000000){
- m = ((u&0x3000000)>>24) | 0xf8;
- printf("%c", (unsigned char)m);
- m = ((u&0xfc0000)>>18) | 0x80;
- printf("%c", (unsigned char)m);
- m = ((u&0x3f000)>>12) | 0x80;
- printf("%c", (unsigned char)m);
- m = ((u&0xfc00)>>6) | 0x80;
- printf("%c", (unsigned char)m);
- m = (u&0x3f) | 0x80;
- printf("%c", (unsigned char)m);
- }
- else {
- m = ((u&0x40000000)>>30) | 0xfc;
- printf("%c", (unsigned char)m);
- m = ((u&0x3f000000)>>24) | 0x80;
- printf("%c", (unsigned char)m);
- m = ((u&0xfc0000)>>18) | 0x80;
- printf("%c", (unsigned char)m);
- m = ((u&0x3f000)>>12) | 0x80;
- printf("%c", (unsigned char)m);
- m = ((u&0xfc0)>>6) | 0x80;
- printf("%c", (unsigned char)m);
- m = (u&0x3f)| 0x80;
- printf("%c", (unsigned char)m);
- }
-}
-
-int main() {
- int i,j;
- long long n1, n2;
- unsigned char b1[15], b2[15];
- unsigned char buf[1024];
- i=0;
- while(fgets(buf, 1024, stdin)) {
- // printf("read %s\n", buf);
- for(i=0;i<strlen(buf); i++)
- if(buf[i]=='U') {
- if(buf[i+1] == '+') {
- n1 = strtoll(buf+i+2,0,16);
- printf("U+%05x", n1);
- printUTF8(n1);printf("|");
- }
- }
- printf("\n");
- }
-}
-
diff --git a/includes/zhtable/simp2trad.manual b/includes/zhtable/simp2trad.manual
deleted file mode 100644
index 1b84f8e7..00000000
--- a/includes/zhtable/simp2trad.manual
+++ /dev/null
@@ -1,372 +0,0 @@
-U+03CE0㳠|U+06FBE澾|
-U+0447D䑽|U+26A99𦪙|
-U+0497A䥺|U+091FE釾|
-U+0497D䥽|U+093FA鏺|
-U+04983䦃|U+0942F鐯|
-U+04985䦅|U+09425鐥|
-U+04B6A䭪|U+297AF𩞯|
-U+04C9F䲟|U+09BA3鮣|
-U+04CA0䲠|U+09C06鰆|
-U+04CA1䲡|U+09C0C鰌|
-U+04CA2䲢|U+09C27鰧|
-U+04CA3䲣|U+04C77䱷|
-U+04DAE䶮|U+09F91龑|
-U+04E07万|U+0842C萬|U+04E07万|
-U+04E0E与|U+08207與|U+04E0E与|
-U+04E11丑|U+04E11丑|U+0919C醜|
-U+04E2A个|U+0500B個|U+07B87箇|
-U+04E30丰|U+08C50豐|U+04E30丰|
-U+04E3A为|U+070BA為|U+07232爲|
-U+04E48么|U+04E48么|U+09EBD麽|U+05E7A幺|U+09EBC麼|
-U+04E86了|U+04E86了|U+077AD瞭|
-U+04E8E于|U+065BC於|U+04E8E于|
-U+04E91云|U+096F2雲|U+04E91云|
-U+04EA7产|U+07522產|U+07523産|
-U+04EC6仆|U+04EC6仆|U+050D5僕|
-U+04EC7仇|U+04EC7仇|U+08B8E讎|
-U+04ED1仑|U+04F96侖|U+05D19崙|
-U+04EF7价|U+050F9價|U+04EF7价|
-U+04F17众|U+0773E眾|U+08846衆|
-U+04F19伙|U+04F19伙|U+05925夥|
-U+04F2A伪|U+0507D偽|U+050DE僞|
-U+04F53体|U+09AD4體|U+04F53体|
-U+04F59余|U+04F59余|U+09918餘|
-U+04F63佣|U+04F63佣|U+050AD傭|
-U+0501F借|U+0501F借|U+085C9藉|
-U+0513F儿|U+05152兒|U+0513F儿|
-U+0514B克|U+0514B克|U+0524B剋|
-U+0515A党|U+09EE8黨|U+0515A党|
-U+051AC冬|U+051AC冬|U+09F15鼕|
-U+051B2冲|U+06C96沖|U+0885D衝|
-U+051C6准|U+051C6准|U+06E96準|
-U+051E0几|U+05E7E幾|U+051E0几|
-U+051EB凫|U+09CE7鳧|U+09CEC鳬|
-U+051FA出|U+051FA出|U+09F63齣|
-U+05212划|U+05283劃|U+05212划|
-U+0522B别|U+05225別|U+05F46彆|
-U+0522E刮|U+0522E刮|U+098B3颳|
-U+05236制|U+05236制|U+088FD製|
-U+05343千|U+05343千|U+097C6韆|
-U+05347升|U+05347升|U+06607昇|U+0965E陞|
-U+0535C卜|U+0535C卜|U+08514蔔|
-U+05360占|U+05360占|U+04F54佔|
-U+05364卤|U+09E75鹵|U+06EF7滷|
-U+05377卷|U+05377卷|U+06372捲|
-U+0537A卺|U+05DF9巹|
-U+05382厂|U+05EE0廠|U+05382厂|
-U+05386历|U+06B77歷|U+066C6曆|U+053A4厤|
-U+05395厕|U+05EC1廁|U+053A0厠|
-U+05398厘|U+05398厘|U+091D0釐|
-U+053D1发|U+0767C發|U+09AEE髮|
-U+053EA只|U+053EA只|U+096BB隻|
-U+053F0台|U+053F0台|U+081FA臺|U+06AAF檯|U+098B1颱|
-U+053F6叶|U+08449葉|U+053F6叶|
-U+05401吁|U+05401吁|U+07C72籲|
-U+05408合|U+05408合|U+095A4閤|
-U+0540A吊|U+0540A吊|U+05F14弔|
-U+0540C同|U+0540C同|U+08855衕|
-U+0540E后|U+05F8C後|U+0540E后|
-U+05411向|U+05411向|U+056AE嚮|U+066CF曏|
-U+0542F启|U+0555F啟|U+05553啓|
-U+05446呆|U+05446呆|U+07343獃|
-U+054B8咸|U+054B8咸|U+09E79鹹|
-U+054C4哄|U+054C4哄|U+09B28鬨|
-U+05582喂|U+05582喂|U+09935餵|
-U+056DE回|U+056DE回|U+08FF4迴|
-U+056E2团|U+05718團|U+07CF0糰|
-U+056F0困|U+056F0困|U+0774F睏|
-U+05742坂|U+05742坂|U+0962A阪|
-U+0574F坏|U+058DE壞|U+0574F坏|
-U+0575B坛|U+058C7壇|U+07F48罈|
-U+057FC埼|U+057FC埼|U+07895碕|
-U+05899墙|U+07246牆|U+058BB墻|
-U+058F3壳|U+06BBC殼|U+06BBB殻|
-U+0590D复|U+05FA9復|U+08907複|
-U+05956奖|U+0734E獎|U+0596C奬|
-U+05978奸|U+05978奸|U+059E6姦|
-U+059AB妫|U+05AAF媯|U+05B00嬀|
-U+059DC姜|U+059DC姜|U+08591薑|
-U+05B81宁|U+05BE7寧|U+05B81宁|
-U+05BB6家|U+05BB6家|U+050A2傢|
-U+05C3D尽|U+076E1盡|U+05118儘|
-U+05CB3岳|U+05CB3岳|U+05DBD嶽|
-U+05E03布|U+05E03布|U+04F48佈|
-U+05E18帘|U+07C3E簾|U+05E18帘|
-U+05E5E幞|U+08946襆|
-U+05E72干|U+05E72干|U+04E7E乾|U+05E79幹|U+069A6榦|
-U+05E76并|U+04E26並|U+04F75併|
-U+05E78幸|U+05E78幸|U+05016倖|
-U+05E7F广|U+05EE3廣|U+05E7F广|
-U+05E84庄|U+05E84庄|U+0838A莊|
-U+05EB5庵|U+05EB5庵|U+083F4菴|
-U+05F25弥|U+05F4C彌|U+07030瀰|
-U+05F53当|U+07576當|U+05679噹|
-U+05F55录|U+09304錄|U+09332録|
-U+05F69彩|U+05F69彩|U+07DB5綵|
-U+05F81征|U+05F81征|U+05FB5徵|
-U+05FA1御|U+05FA1御|U+079A6禦|
-U+05FD7志|U+05FD7志|U+08A8C誌|
-U+06076恶|U+060E1惡|U+05641噁|
-U+060AB悫|U+06128愨|U+06164慤|
-U+0613F愿|U+09858願|U+0613F愿|
-U+0621A戚|U+0621A戚|U+0617C慼|U+093DA鏚|
-U+0624D才|U+0624D才|U+07E94纔|
-U+0624E扎|U+0624E扎|U+07D2E紮|
-U+06258托|U+06258托|U+08A17託|
-U+06298折|U+06298折|U+0647A摺|
-U+062C5担|U+064D4擔|U+062C5担|
-U+062FC拼|U+062FC拼|U+062DA拚|
-U+06328挨|U+06328挨|U+06371捱|
-U+0633D挽|U+0633D挽|U+08F13輓|
-U+0636E据|U+064DA據|U+0636E据|
-U+06597斗|U+06597斗|U+09B25鬥|
-U+065CB旋|U+065CB旋|U+0955F镟|
-U+065D7旗|U+065D7旗|U+065C2旂|
-U+066F2曲|U+066F2曲|U+09EAF麯|U+09EB4麯|
-U+0672F术|U+08853術|U+0672E朮|
-U+06731朱|U+06731朱|U+07843硃|
-U+06734朴|U+06734朴|U+06A38樸|
-U+0676F杯|U+0676F杯|U+076C3盃|
-U+0677E松|U+0677E松|U+09B06鬆|
-U+0677F板|U+0677F板|U+095C6闆|
-U+06781极|U+06975極|U+06781极|
-U+067DC柜|U+06AC3櫃|U+067DC柜|
-U+06817栗|U+06817栗|U+06144慄|
-U+06881梁|U+06881梁|U+06A11樑|
-U+068F1棱|U+068F1棱|U+07A1C稜|
-U+06B32欲|U+06B32欲|U+0617E慾|
-U+06C47汇|U+0532F匯|U+06ED9滙|U+05F59彙|
-U+06C84沄|U+06C84沄|U+06F90澐|
-U+06C88沈|U+06C88沈|U+0700B瀋|
-U+06CA9沩|U+06E88溈|U+06F59潙|
-U+06CE8注|U+06CE8注|U+08A3B註|
-U+06D82涂|U+05857塗|U+06D82涂|
-U+06D8C涌|U+06D8C涌|U+06E67湧|
-U+06DC0淀|U+06DC0淀|U+06FB1澱|
-U+06E38游|U+06E38游|U+0904A遊|
-U+06EAF溯|U+06EAF溯|U+06CDD泝|
-U+06F13漓|U+06F13漓|U+07055灕|
-U+070BC炼|U+07149煉|U+0934A鍊|
-U+0753B画|U+0756B畫|U+07575畵|
-U+075C7症|U+075C7症|U+07665癥|
-U+07618瘘|U+0763A瘺|U+0763B瘻|
-U+0786E确|U+078BA確|U+0786E确|
-U+07877硷|U+07906礆|U+09E7C鹼|
-U+079CB秋|U+079CB秋|U+097A6鞦|
-U+079CD种|U+07A2E種|U+079CD种|
-U+07A57穗|U+07A57穗|U+07E50繐|
-U+07AD6竖|U+08C4E豎|U+07AEA竪|
-U+07B51筑|U+07BC9築|U+07B51筑|
-U+07B7E签|U+07C3D簽|U+07C64籤|
-U+07CFB系|U+07CFB系|U+07E6B繫|U+04FC2係|
-U+07D2F累|U+07D2F累|U+07E8D纍|
-U+07EA4纤|U+07E96纖|U+07E34縴|
-U+07EBF线|U+07DDA線|U+07DAB綫|
-U+07EDD绝|U+07D55絕|U+07D76絶|
-U+07EE3绣|U+07D89綉|U+07E61繡|
-U+07EE6绦|U+07D5B絛|U+07E27縧|
-U+07EF1绱|U+07DD4緔|U+0979D鞝|
-U+07EF7绷|U+07DB3綳|U+07E43繃|
-U+07EFF绿|U+07DA0綠|U+07DD1緑|
-U+07F30缰|U+097C1韁|U+07E6E繮|
-U+07FA1羡|U+07FA8羨|
-U+080DC胜|U+052DD勝|U+080DC胜|
-U+080E1胡|U+080E1胡|U+09B0D鬍|U+0885A衚|
-U+0810F脏|U+081DF臟|U+09AD2髒|
-U+0814A腊|U+081D8臘|U+0814A腊|
-U+081F4致|U+081F4致|U+07DFB緻|
-U+0820D舍|U+0820D舍|U+06368捨|
-U+082B8芸|U+082B8芸|U+08553蕓|
-U+082CE苎|U+082E7苧|
-U+082CF苏|U+08607蘇|U+056CC囌|U+07C64甦|
-U+082E7苧|U+085B4薴|
-U+082F9苹|U+0860B蘋|U+082F9苹|
-U+08303范|U+08303范|U+07BC4範|
-U+0836F药|U+0846F葯|U+085E5藥|
-U+083B7获|U+07372獲|U+07A6B穫|
-U+083BC莼|U+08493蒓|U+084F4蓴|
-U+08499蒙|U+08499蒙|U+077C7矇|U+06FDB濛|U+061DE懞|
-U+084D1蓑|U+084D1蓑|U+07C11簑|
-U+08511蔑|U+08511蔑|U+0884A衊|
-U+08574蕴|U+0860A蘊|U+085F4藴|
-U+0866B虫|U+087F2蟲|U+0866B虫|
-U+08721蜡|U+0881F蠟|U+08721蜡|
-U+0874E蝎|U+0880D蠍|
-U+08868表|U+08868表|U+09336錶|
-U+08BF4说|U+08AAA說|U+08AAC説|
-U+08C23谣|U+08B20謠|U+08B21謡|
-U+08C2B谫|U+08B7E譾|U+08B2D謭|
-U+08C37谷|U+08C37谷|U+07A40穀|
-U+08D43赃|U+08D13贓|U+08D1C贜|
-U+08D4D赍|U+09F4E齎|U+08CEB賫|
-U+08D5D赝|U+08D17贗|U+08D0B贋|
-U+08D5E赞|U+08D0A贊|U+08B9A讚|
-U+08F9F辟|U+08F9F辟|U+095E2闢|
-U+09002适|U+09069適|U+09002适|
-U+090C1郁|U+090C1郁|U+09B31鬱|
-U+0915D酝|U+0919E醞|U+09196醖|
-U+09170酰|U+09170酰|U+091AF醯|
-U+09178酸|U+09178酸|U+075E0痠|
-U+091C7采|U+091C7采|U+063A1採|U+057F0埰|
-U+091CC里|U+091CC里|U+088E1裡|U+088CF裏|
-U+093AD鎭|U+093AE鎮|
-U+0949F钟|U+0937E鍾|U+09418鐘|
-U+094A9钩|U+09264鉤|U+0920E鈎|
-U+094B5钵|U+07F3D缽|U+09262鉢|
-U+094F2铲|U+093DF鏟|U+05277剷|
-U+09508锈|U+092B9銹|U+093FD鏽|
-U+09510锐|U+092B3銳|U+092ED鋭|
-U+09528锨|U+06774杴|U+09341鍁|
-U+0954C镌|U+0942B鐫|U+093B8鎸|
-U+09562镢|U+09481钁|U+0941D鐝|
-U+09605阅|U+095B1閱|U+095B2閲|
-U+096C7雇|U+096C7雇|U+050F1僱|
-U+096D5雕|U+096D5雕|U+09D70鵰|
-U+09709霉|U+09709霉|U+09EF4黴|
-U+09762面|U+09762面|U+09EB5麵|U+09EAA麪|U+09EAB麫|
-U+097B2鞲|U+097DD韝|
-U+0987B须|U+09808須|U+09B1A鬚|
-U+09893颓|U+09839頹|U+0983D頽|
-U+0989C颜|U+0984F顏|U+09854顔|
-U+09965饥|U+098E2飢|U+09951饑|
-U+09980馀|U+09918餘|
-U+09986馆|U+09928館|U+08218舘|
-U+09A82骂|U+07F75罵|U+099E1駡|
-U+09C87鲇|U+09BF0鯰|U+09B8E鮎|
-U+09C9E鲞|U+09BD7鯗|U+09B9D鮝|
-U+09CC4鳄|U+09C77鱷|U+09C10鰐|
-U+09E21鸡|U+096DE雞|U+09DC4鷄|
-U+09E5A鹚|U+09DBF鶿|U+09DC0鷀|
-U+09E6E鹮|U+04D09䴉|
-U+09F44齄|U+09F47齇|
-U+20BB6𠮶|U+055F0嗰|
-U+26216𦈖|U+04308䌈|
-U+28C3E𨰾|U+093B7鎷|
-U+28C3F𨰿|U+091F3釳|
-U+28C40𨱀|U+2895B𨥛|
-U+28C41𨱁|U+09220鈠|
-U+28C42𨱂|U+0920B鈋|
-U+28C43𨱃|U+09232鈲|
-U+28C44𨱄|U+0922F鈯|
-U+28C45𨱅|U+09241鉁|
-U+28C47𨱇|U+092B6銶|
-U+28C48𨱈|U+092C9鋉|
-U+28C49𨱉|U+09344鍄|
-U+28C4A𨱊|U+289F1𨧱|
-U+28C4B𨱋|U+09302錂|
-U+28C4C𨱌|U+093C6鏆|
-U+28C4D𨱍|U+093AF鎯|
-U+28C4E𨱎|U+0936E鍮|
-U+28C4F𨱏|U+0939D鎝|
-U+28C50𨱐|U+28AD2𨫒|
-U+28C52𨱒|U+093C9鏉|
-U+28C53𨱓|U+0940E鐎|
-U+28C54𨱔|U+0940F鐏|
-U+28C55𨱕|U+28B82𨮂|
-U+28E02𨸂|U+0958D閍|
-U+28E03𨸃|U+09590閐|
-U+293FC𩏼|U+04A8F䪏|
-U+293FD𩏽|U+293EA𩏪|
-U+293FE𩏾|U+293A2𩎢|
-U+293FF𩏿|U+04A98䪘|
-U+29400𩐀|U+04A97䪗|
-U+29595𩖕|U+294E3𩓣|
-U+29596𩖖|U+09843顃|
-U+29597𩖗|U+04AF4䫴|
-U+29665𩙥|U+098B0颰|
-U+29666𩙦|U+295C0𩗀|
-U+29667𩙧|U+295E1𩗡|
-U+29668𩙨|U+29639𩘹|
-U+29669𩙩|U+29600𩘀|
-U+2966A𩙪|U+098B7颷|
-U+2966B𩙫|U+098BE颾|
-U+2966C𩙬|U+2963A𩘺|
-U+2966D𩙭|U+2961D𩘝|
-U+2966E𩙮|U+04B18䬘|
-U+2966F𩙯|U+04B1D䬝|
-U+29670𩙰|U+29648𩙈|
-U+29805𩠅|U+297D0𩟐|
-U+29806𩠆|U+29726𩜦|
-U+29807𩠇|U+04B40䭀|
-U+29808𩠈|U+04B43䭃|
-U+2980B𩠋|U+29754𩝔|
-U+2980C𩠌|U+09938餸|
-U+299E6𩧦|U+2987A𩡺|
-U+299E8𩧨|U+099CE駎|
-U+299E9𩧩|U+2990A𩤊|
-U+299EA𩧪|U+04BBE䮾|
-U+299EB𩧫|U+099DA駚|
-U+299EC𩧬|U+298A1𩢡|
-U+299ED𩧭|U+04B7F䭿|
-U+299EE𩧮|U+298BE𩢾|
-U+299EF𩧯|U+09A4B驋|
-U+299F0𩧰|U+04B9D䮝|
-U+299F1𩧱|U+29949𩥉|
-U+299F2𩧲|U+099E7駧|
-U+299F3𩧳|U+298B8𩢸|
-U+299F4𩧴|U+099E9駩|
-U+299F5𩧵|U+298B4𩢴|
-U+299F6𩧶|U+298CF𩣏|
-U+299FA𩧺|U+099F6駶|
-U+299FB𩧻|U+298F5𩣵|
-U+299FC𩧼|U+298FA𩣺|
-U+299FF𩧿|U+04BA0䮠|
-U+29A00𩨀|U+09A14騔|
-U+29A01𩨁|U+04B9E䮞|
-U+29A03𩨃|U+09A1D騝|
-U+29A04𩨄|U+09A2A騪|
-U+29A05𩨅|U+29938𩤸|
-U+29A06𩨆|U+29919𩤙|
-U+29A08𩨈|U+09A1F騟|
-U+29A09𩨉|U+29932𩤲|
-U+29A0A𩨊|U+09A1A騚|
-U+29A0B𩨋|U+29944𩥄|
-U+29A0C𩨌|U+29951𩥑|
-U+29A0D𩨍|U+29947𩥇|
-U+29A0F𩨏|U+04BB3䮳|
-U+29A10𩨐|U+299C6𩧆|
-U+29F79𩽹|U+09B65魥|
-U+29F7A𩽺|U+29D69𩵩|
-U+29F7B𩽻|U+29D79𩵹|
-U+29F7C𩽼|U+09BF6鯶|
-U+29F7D𩽽|U+29DB1𩶱|
-U+29F7E𩽾|U+09B9F鮟|
-U+29F7F𩽿|U+29DB0𩶰|
-U+29F80𩾀|U+09B95鮕|
-U+29F81𩾁|U+09BC4鯄|
-U+29F83𩾃|U+09BB8鮸|
-U+29F84𩾄|U+29DF0𩷰|
-U+29F85𩾅|U+29E03𩸃|
-U+29F86𩾆|U+29E26𩸦|
-U+29F87𩾇|U+09BF1鯱|
-U+29F88𩾈|U+04C59䱙|
-U+29F8A𩾊|U+04C6C䱬|
-U+29F8B𩾋|U+04C70䱰|
-U+29F8C𩾌|U+09C47鱇|
-U+29F8C𩾌|U+09C47鱇|
-U+29F8E𩾎|U+29F47𩽇|
-U+2A242𪉂|U+04CB0䲰|
-U+2A243𪉃|U+09CFC鳼|
-U+2A244𪉄|U+29FEA𩿪|
-U+2A245𪉅|U+2A026𪀦|
-U+2A246𪉆|U+09D32鴲|
-U+2A248𪉈|U+09D1C鴜|
-U+2A249𪉉|U+2A048𪁈|
-U+2A24A𪉊|U+09DE8鷨|
-U+2A24B𪉋|U+2A03E𪀾|
-U+2A24C𪉌|U+2A056𪁖|
-U+2A24D𪉍|U+09D5A鵚|
-U+2A24E𪉎|U+2A086𪂆|
-U+2A24F𪉏|U+2A0CF𪃏|
-U+2A250𪉐|U+2A0CD𪃍|
-U+2A251𪉑|U+09DD4鷔|
-U+2A252𪉒|U+2A115𪄕|
-U+2A254𪉔|U+2A106𪄆|
-U+2A255𪉕|U+2A1F3𪇳|
-U+2A388𪎈|U+04D2C䴬|
-U+2A389𪎉|U+09EB2麲|
-U+2A38A𪎊|U+09EA8麨|
-U+2A38B𪎋|U+04D34䴴|
-U+2A38C𪎌|U+09EB3麳|
-U+2A68F𪚏|U+2A600𪘀|
-U+2A690𪚐|U+2A62F𪘯|
diff --git a/includes/zhtable/simp2trad_noconvert.manual b/includes/zhtable/simp2trad_noconvert.manual
deleted file mode 100644
index a46560a7..00000000
--- a/includes/zhtable/simp2trad_noconvert.manual
+++ /dev/null
@@ -1,139 +0,0 @@
-著
-竈
-彞
-=>"余"
-=>"𫗭"
-=>"𪨧"
-=>"𫚭"
-=>"𫔀"
-=>"𫊻"
-=>"𫋌"
-=>"蚃"
-=>"𩾂"
-=>"𫚜"
-=>"𫚢"
-=>"𧉰"
-=>"䙌"
-=>"𫊮"
-=>"𫋇"
-=>"𫉄"
-=>"𫘛"
-=>"𫘜"
-=>"𫘝"
-=>"𫘟"
-=>"𩧨"
-=>"𩧫"
-=>"𫘞"
-=>"𫘠"
-=>"𩧲"
-=>"𩧴"
-=>"𫘡"
-=>"𩧺"
-=>"𫘣"
-=>"𫘤"
-=>"𫘧"
-=>"𫘥"
-=>"𫘦"
-=>"𩨀"
-=>"𩨊"
-=>"𫘩"
-=>"𩨃"
-=>"𫘪"
-=>"𫘪"
-=>"𫘫"
-=>"𫘬"
-=>"𩨈"
-=>"𫘨"
-=>"𩨄"
-=>"𫘭"
-=>"𩧯"
-=>"𫘯"
-=>"𫘰"
-=>"𫘱"
-=>"𫘽"
-=>"𫚉"
-=>"𩽹"
-=>"𫚌"
-=>"𫚍"
-=>"𫚒"
-=>"𫚑"
-=>"𫚖"
-=>"𩽾"
-=>"䲟"
-=>"𫚓"
-=>"𫚗"
-=>"𫚔"
-=>"𫚛"
-=>"𩾃"
-=>"𫚚"
-=>"𩾁"
-=>"𫚙"
-=>"𫚡"
-=>"𫚞"
-=>"𩾇"
-=>"𩽼"
-=>"𫚣"
-=>"䲠"
-=>"䲡"
-=>"𫚊"
-=>"𫚥"
-=>"𫚕"
-=>"𫚤"
-=>"䲢"
-=>"𫚦"
-=>"𫚧"
-=>"𫚋"
-=>"𩾌"
-=>"𫚪"
-=>"𫚫"
-=>"𫚈"
-=>"𫚭"
-=>"𫛛"
-=>"𪉃"
-=>"𫛚"
-=>"𫛜"
-=>"𫛞"
-=>"𫛝"
-=>"𫛤"
-=>"𫛡"
-=>"𫁡"
-=>"𪉈"
-=>"𫛣"
-=>"𫛦"
-=>"𪉆"
-=>"𫛩"
-=>"𫛪"
-=>"𫛥"
-=>"𪉍"
-=>"𫛭"
-=>"𫛨"
-=>"𫛳"
-=>"𫛱"
-=>"𫛲"
-=>"𫛵"
-=>"𫛶"
-=>"𫛸"
-=>"𫛷"
-=>"𫛯"
-=>"𫛫"
-=>"𫛽"
-=>"𫜀"
-=>"𪉑"
-=>"𫜃"
-=>"𫛴"
-=>"𪉊"
-=>"𫜁"
-=>"𫜄"
-=>"𫛢"
-=>"𫛟"
-=>"𪎊"
-=>"𤿲"
-=>"𪎉"
-=>"𪎌"
-=>"𫜑"
-=>"𫜩"
-=>"𫜪"
-=>"𫜭"
-=>"𫜬"
-=>"𫜮"
-=>"𫜰"
diff --git a/includes/zhtable/simp2trad_supp_set.manual b/includes/zhtable/simp2trad_supp_set.manual
deleted file mode 100644
index a5038a5d..00000000
--- a/includes/zhtable/simp2trad_supp_set.manual
+++ /dev/null
@@ -1,2 +0,0 @@
-余 餘
-着 著 \ No newline at end of file
diff --git a/includes/zhtable/simpphrases.manual b/includes/zhtable/simpphrases.manual
deleted file mode 100644
index d8602fec..00000000
--- a/includes/zhtable/simpphrases.manual
+++ /dev/null
@@ -1,2239 +0,0 @@
-乾上乾下
-乾为天
-乾为阳
-乾九
-乾乾
-乾亨
-乾仪
-乾位
-乾健
-乾元
-乾光
-乾兴
-乾冈
-乾刘
-乾刚
-乾化
-乾卦
-乾县
-乾台
-乾吉
-乾启
-乾命
-乾和
-乾嘉
-乾图
-乾坤
-乾城
-乾基
-乾始
-乾姓
-乾宁
-乾宅
-乾宇
-乾安
-乾定
-乾封
-乾居
-乾岗
-乾巛
-乾州
-乾式
-乾录
-乾律
-乾德
-乾心
-乾文
-乾断
-乾方
-乾施
-乾旦
-乾明
-乾昧
-乾晖
-乾景
-乾晷
-乾曜
-乾构
-乾枢
-乾栋
-乾步
-乾氏
-乾泉
-乾清宫
-乾渥
-乾灵
-乾男
-乾皋
-乾盛世
-乾矢
-乾祐
-乾穹
-乾窦
-乾竺
-乾笃
-乾符
-乾策
-乾精
-乾红
-乾纲
-乾纽
-乾络
-乾统
-乾维
-乾罗
-乾花
-乾荫
-乾行
-乾衡
-乾覆
-乾象
-乾象历
-乾贞
-乾贶
-乾车
-乾轴
-乾造
-乾道
-乾鉴
-乾钧
-乾闼
-乾陀
-乾陵
-乾隆
-乾音
-乾顾
-乾风
-乾首
-乾马
-乾鹄
-乾鹊
-乾龙
-乾,健也
-乾,天也
-乾健也
-乾天也
-坤乾
-天道为乾
-尼乾陀
-康乾
-张法乾
-旋乾转坤
-易·乾
-《易乾
-周易乾
-易经·乾
-易经乾
-李乾德
-萧乾
-郭子乾
-雍乾
-乾务
-乾沓和
-乾沓婆
-乾通
-乾忠
-乾淳
-李乾顺
-黄润乾
-男性为乾
-男为乾
-阳为乾
-乾一组
-乾一坛
-陈乾生
-陈公乾生
-字乾生
-不着痕迹
-不着边际
-与着
-与著书
-与著作
-与著名
-与著录
-与著称
-与著者
-与著述
-丑着
-丑著书
-丑著作
-丑著名
-丑著录
-丑著称
-丑著者
-丑著述
-临着
-临著书
-临著作
-临著名
-临著录
-临著称
-临著者
-临著述
-丽着
-丽著书
-丽著作
-丽著名
-丽著录
-丽著称
-丽著者
-丽著述
-乐着
-乐著书
-乐著作
-乐著名
-乐著录
-乐著称
-乐著者
-乐著述
-乘着
-乘著书
-乘著作
-乘著名
-乘著录
-乘著称
-乘著者
-乘著述
-争着
-争著书
-争著作
-争著名
-争著录
-争著称
-争著者
-争著述
-亮着
-亮著书
-亮著作
-亮著名
-亮著录
-亮著称
-亮著者
-亮著述
-仗着
-仗著书
-仗著作
-仗著名
-仗著录
-仗著称
-仗著者
-仗著述
-代表着
-代表著书
-代表著作
-代表著名
-代表著录
-代表著称
-代表著者
-代表著述
-伴着
-伴著书
-伴著作
-伴著名
-伴著录
-伴著称
-伴著者
-伴著述
-低着
-低著书
-低著作
-低著名
-低著录
-低著称
-低著者
-低著述
-住着
-住著书
-住著作
-住著名
-住著录
-住著称
-住著者
-住著述
-侧着
-侧著书
-侧著作
-侧著名
-侧著录
-侧著称
-侧著者
-侧著述
-保障着
-保障著书
-保障著作
-保障著名
-保障著录
-保障著称
-保障著者
-保障著述
-信着
-信著书
-信著作
-信著名
-信著录
-信著称
-信著者
-信著述
-候着
-候著书
-候著作
-候著名
-候著录
-候著称
-候著者
-候著述
-借着
-借著书
-借著作
-借著名
-借著录
-借著称
-借著者
-借著述
-做着
-做著书
-做著作
-做著名
-做著录
-做著称
-做著者
-做著述
-偷着
-偷著书
-偷著作
-偷著名
-偷著录
-偷著称
-偷著者
-偷著述
-光着
-光著书
-光著作
-光著名
-光著录
-光著称
-光著者
-光著述
-关着
-关著书
-关著作
-关著名
-关著录
-关著称
-关著者
-关著述
-冀着
-冀著书
-冀著作
-冀著名
-冀著录
-冀著称
-冀著者
-冀著述
-冒着
-冒著书
-冒著作
-冒著名
-冒著录
-冒著称
-冒著者
-冒著述
-写着
-写著书
-写著作
-写著名
-写著录
-写著称
-写著者
-写著述
-凉着
-凉著书
-凉著作
-凉著名
-凉著录
-凉著称
-凉著者
-凉著述
-制着
-制著书
-制著作
-制著名
-制著录
-制著称
-制著者
-制著述
-刻着
-刻著书
-刻著作
-刻著名
-刻著录
-刻著称
-刻著者
-刻著述
-办着
-办著书
-办著作
-办著名
-办著录
-办著称
-办著者
-办著述
-动着
-动著书
-动著作
-动著名
-动著录
-动著称
-动著者
-动著述
-努力着
-努力著书
-努力著作
-努力著名
-努力著录
-努力著称
-努力著者
-努力著述
-努着
-努著书
-努著作
-努著名
-努著录
-努著称
-努著者
-努著述
-印着
-印著书
-印著作
-印著名
-印著录
-印著称
-印著者
-印著述
-压着
-压著书
-压著作
-压著名
-压著录
-压著称
-压著者
-压著述
-去着
-去著书
-去著作
-去著名
-去著录
-去著称
-去著者
-去著述
-受着
-受著书
-受著作
-受著名
-受著录
-受著称
-受著者
-受著述
-变着
-变著书
-变著作
-变著名
-变著录
-变著称
-变著者
-变著述
-叫着
-叫著书
-叫著作
-叫著名
-叫著录
-叫著称
-叫著者
-叫著述
-向着
-向著书
-向著作
-向著名
-向著录
-向著称
-向著者
-向著述
-含着
-含著书
-含著作
-含著名
-含著录
-含著称
-含著者
-含著述
-听得着
-听不着
-听着
-听著书
-听著作
-听著名
-听著录
-听著称
-听著者
-听著述
-吹着
-吹著书
-吹著作
-吹著名
-吹著录
-吹著称
-吹著者
-吹著述
-味着
-味著书
-味著作
-味著名
-味著录
-味著称
-味著者
-味著述
-响着
-响著书
-响著作
-响著名
-响著录
-响著称
-响著者
-响著述
-哭着
-哭著书
-哭著作
-哭著名
-哭著录
-哭著称
-哭著者
-哭著述
-唱着
-唱著书
-唱著作
-唱著名
-唱著录
-唱著称
-唱著者
-唱著述
-喝着
-喝著书
-喝著作
-喝著名
-喝著录
-喝著称
-喝著者
-喝著述
-嚷着
-嚷著书
-嚷著作
-嚷著名
-嚷著录
-嚷著称
-嚷著者
-嚷著述
-因着
-因著书
-因著作
-因著名
-因著录
-因著称
-因著者
-因著述
-困着
-困著书
-困著作
-困著名
-困著录
-困著称
-困著者
-困著述
-围着
-围著书
-围著作
-围著名
-围著录
-围著称
-围著者
-围著述
-在着
-在著书
-在著作
-在著名
-在著录
-在著称
-在著者
-在著述
-坐着
-坐著书
-坐著作
-坐著名
-坐著录
-坐著称
-坐著者
-坐著述
-备着
-备著书
-备著作
-备著名
-备著录
-备著称
-备著者
-备著述
-夹着
-夹著书
-夹著作
-夹著名
-夹著录
-夹著称
-夹著者
-夹著述
-孤着
-孤著书
-孤著作
-孤著名
-孤著录
-孤著称
-孤著者
-孤著述
-学着
-学著书
-学著作
-学著名
-学著录
-学著称
-学著者
-学著述
-守着
-守著书
-守著作
-守著名
-守著录
-守著称
-守著者
-守著述
-定着
-定著书
-定著作
-定著名
-定著录
-定著称
-定著者
-定著述
-对着
-对著书
-对著作
-对著名
-对著录
-对著称
-对著者
-对著述
-寻着
-寻著书
-寻著作
-寻著名
-寻著录
-寻著称
-寻著者
-寻著述
-展着
-展著书
-展著作
-展著名
-展著录
-展著称
-展著者
-展著述
-带着
-带著书
-带著作
-带著名
-带著录
-带著称
-带著者
-带著述
-帮着
-帮著书
-帮著作
-帮著名
-帮著录
-帮著称
-帮著者
-帮著述
-应着
-应著书
-应著作
-应著名
-应著录
-应著称
-应著者
-应著述
-康着
-康著书
-康著作
-康著名
-康著录
-康著称
-康著者
-康著述
-开着
-开著书
-开著作
-开著名
-开著录
-开著称
-开著者
-开著述
-当着
-当著书
-当著作
-当著名
-当著录
-当著称
-当著者
-当著述
-待着
-待著书
-待著作
-待著名
-待著录
-待著称
-待著者
-待著述
-得着
-得著书
-得著作
-得著名
-得著录
-得著称
-得著者
-得著述
-循着
-循著书
-循著作
-循著名
-循著录
-循著称
-循著者
-循著述
-心着
-心著书
-心著作
-心著名
-心著录
-心著称
-心著者
-心著述
-忍着
-忍著书
-忍著作
-忍著名
-忍著录
-忍著称
-忍著者
-忍著述
-志着
-志著书
-志著作
-志著名
-志著录
-志著称
-志著者
-志著述
-忙着
-忙著书
-忙著作
-忙著名
-忙著录
-忙著称
-忙著者
-忙著述
-怀着
-怀著书
-怀著作
-怀著名
-怀著录
-怀著称
-怀著者
-怀著述
-急着
-急著书
-急著作
-急著名
-急著录
-急著称
-急著者
-急著述
-性着
-性著书
-性著作
-性著名
-性著录
-性著称
-性著者
-性著述
-恋着
-恋著书
-恋著作
-恋著名
-恋著录
-恋著称
-恋著者
-恋著述
-悠着
-悠著书
-悠著作
-悠著名
-悠著录
-悠著称
-悠著者
-悠著述
-惯着
-惯著书
-惯著作
-惯著名
-惯著录
-惯著称
-惯著者
-惯著述
-想着
-想著书
-想著作
-想著名
-想著录
-想著称
-想著者
-想著述
-战着
-战著书
-战著作
-战著名
-战著录
-战著称
-战著者
-战著述
-戴着
-戴著书
-戴著作
-戴著名
-戴著录
-戴著称
-戴著者
-戴著述
-扎着
-扎著书
-扎著作
-扎著名
-扎著录
-扎著称
-扎著者
-扎著述
-打着
-打著书
-打著作
-打著名
-打著录
-打著称
-打著者
-打著述
-扛着
-扛著书
-扛著作
-扛著名
-扛著录
-扛著称
-扛著者
-扛著述
-找得着
-找不着
-抓着
-抓著作
-抓著名
-抓著录
-抓著称
-抓著者
-抓著述
-披着
-披著书
-披著作
-披著名
-披著录
-披著称
-披著者
-披著述
-抬着
-抬著作
-抬著名
-抬著录
-抬著称
-抬著者
-抬著述
-抱着
-抱著作
-抱著名
-抱著录
-抱著称
-抱著者
-抱著述
-拉着
-拉著书
-拉著作
-拉著名
-拉著录
-拉著称
-拉著者
-拉著述
-拎着
-拎著作
-拎著名
-拎著录
-拎著称
-拎著者
-拎著述
-拖着
-拖著作
-拖著名
-拖著录
-拖著称
-拖著者
-拖著述
-拼着
-拼著作
-拼著名
-拼著录
-拼著称
-拼著者
-拼著述
-拿着
-拿著作
-拿著名
-拿著录
-拿著称
-拿著者
-拿著述
-持着
-持著作
-持著名
-持著录
-持著称
-持著者
-持著述
-挑着
-挑著作
-挑著名
-挑著录
-挑著称
-挑著者
-挑著述
-挡着
-挡著作
-挡著名
-挡著录
-挡著称
-挡著者
-挡著述
-挣着
-挣著书
-挣著作
-挣著名
-挣著录
-挣著称
-挣著者
-挣著述
-挥着
-挥著作
-挥著名
-挥著录
-挥著称
-挥著者
-挥著述
-挨着
-挨著作
-挨著名
-挨著录
-挨著称
-挨著者
-挨著述
-捆着
-捆著作
-捆著名
-捆著录
-捆著称
-捆著者
-捆著述
-据着
-据著书
-据著作
-据著名
-据著录
-据著称
-据著者
-据著述
-掖着
-掖著作
-掖著名
-掖著录
-掖著称
-掖著者
-掖著述
-接着
-接著作
-接著名
-接著录
-接著称
-接著者
-接著述
-揉着
-揉著书
-揉著作
-揉著名
-揉著录
-揉著称
-揉著者
-揉著述
-提着
-提著作
-提著名
-提著录
-提著称
-提著者
-提著述
-搂着
-搂著作
-搂著名
-搂著录
-搂著称
-搂著者
-搂著述
-摆着
-摆著作
-摆著名
-摆著录
-摆著称
-摆著者
-摆著述
-撼着
-撼著书
-撼著作
-撼著名
-撼著录
-撼著称
-撼著者
-撼著述
-敞着
-敞著作
-敞著名
-敞著录
-敞著称
-敞著者
-敞著述
-数着
-数著作
-数著名
-数著录
-数著称
-数著者
-数著述
-斗着
-斗著书
-斗著作
-斗著名
-斗著录
-斗著称
-斗著者
-斗著述
-斥着
-斥著书
-斥著作
-斥著名
-斥著录
-斥著称
-斥著者
-斥著述
-昂着
-昂著书
-昂著作
-昂著名
-昂著录
-昂著称
-昂著者
-昂著述
-映着
-映著书
-映著作
-映著名
-映著录
-映著称
-映著者
-映著述
-晃着
-晃著作
-晃著名
-晃著录
-晃著称
-晃著者
-晃著述
-暗着
-暗著书
-暗著作
-暗著名
-暗著录
-暗著称
-暗著者
-暗著述
-有着
-有著书
-有著作
-有著名
-有著录
-有著称
-有著者
-有著述
-望着
-望著作
-望著名
-望著录
-望著称
-望著者
-望著述
-朝着
-朝著作
-朝著名
-朝著录
-朝著称
-朝著者
-朝著述
-本着
-本著书
-本著作
-本著名
-本著录
-本著称
-本著者
-本著述
-杀着
-杀著书
-杀著作
-杀著名
-杀著录
-杀著称
-杀著者
-杀著述
-杂着
-杂著书
-杂著作
-杂著名
-杂著录
-杂著称
-杂著者
-杂著述
-来着
-来著书
-来著作
-来著名
-来著录
-来著称
-来著者
-来著述
-枕着
-枕著作
-枕著名
-枕著录
-枕著称
-枕著者
-枕著述
-梦着
-梦著书
-梦著作
-梦著名
-梦著录
-梦著称
-梦著者
-梦著述
-梳着
-梳著作
-梳著名
-梳著录
-梳著称
-梳著者
-梳著述
-求着
-求著书
-求著作
-求著名
-求著录
-求著称
-求著者
-求著述
-沉着
-沉著书
-沉著作
-沉著名
-沉著录
-沉著称
-沉著者
-沉著述
-沿着
-沿著书
-沿著作
-沿著名
-沿著录
-沿著称
-沿著者
-沿著述
-活着
-活著书
-活著作
-活著名
-活著录
-活著称
-活著者
-活著述
-流着
-流著书
-流著作
-流著名
-流著录
-流著称
-流著者
-流著述
-浮着
-浮著书
-浮著作
-浮著名
-浮著录
-浮著称
-浮著者
-浮著述
-润着
-润著书
-润著作
-润著名
-润著录
-润著称
-润著者
-润著述
-涵着
-涵著书
-涵著作
-涵著名
-涵著录
-涵著称
-涵著者
-涵著述
-渴着
-渴著书
-渴著作
-渴著名
-渴著录
-渴著称
-渴著者
-渴著述
-溢着
-溢著书
-溢著作
-溢著名
-溢著录
-溢著称
-溢著者
-溢著述
-演着
-演著书
-演著作
-演著名
-演著录
-演著称
-演著者
-演著述
-漫着
-漫著书
-漫著作
-漫著名
-漫著录
-漫著称
-漫著者
-漫著述
-点着
-点著作
-点著名
-点著录
-点著称
-点著者
-点著述
-烧着
-烧著作
-烧著名
-烧著录
-烧著称
-烧著者
-烧著述
-照着
-照著书
-照著作
-照著名
-照著录
-照著称
-照著者
-照著述
-爱着
-爱著书
-爱著作
-爱著名
-爱著录
-爱著称
-爱著者
-爱著述
-牵着
-牵著书
-牵著作
-牵著名
-牵著录
-牵著称
-牵著者
-牵著述
-犯得着
-犯不着
-独着
-独著书
-独著作
-独著名
-独著录
-独著称
-独著者
-独著述
-猜着
-猜着书
-猜著作
-猜著名
-猜著录
-猜著称
-猜著者
-猜著述
-甜着
-甜著书
-甜著作
-甜著名
-甜著录
-甜著称
-甜著者
-甜著述
-用得着
-用不着
-用着
-用著书
-用著作
-用著名
-用著录
-用著称
-用著者
-用著述
-留着
-留着书
-留著作
-留著名
-留著录
-留著称
-留著者
-留著述
-疑着
-疑著书
-疑著作
-疑著名
-疑著录
-疑著称
-疑著者
-疑著述
-皱着
-皱著书
-皱著作
-皱著名
-皱著录
-皱著称
-皱著者
-皱著述
-盛着
-盛著书
-盛著作
-盛著名
-盛著录
-盛著称
-盛著者
-盛著述
-盯着
-盯着书
-盯著作
-盯著名
-盯著录
-盯著称
-盯著者
-盯著述
-盾着
-盾著书
-盾著作
-盾著名
-盾著录
-盾著称
-盾著者
-盾著述
-看得着
-看不着
-看着
-看着书
-看著作
-看著名
-看著录
-看著称
-看著者
-看著述
-瞧着
-瞧着书
-瞧著作
-瞧著名
-瞧著录
-瞧著称
-瞧著者
-瞧著述
-着业
-着丝
-着么
-着人
-着什么急
-着他
-着令
-着位
-着体
-着你
-着便
-着凉
-着力
-着劲
-着号
-着呢
-着哩
-着地
-着墨
-着声
-着处
-着她
-着妳
-着姓
-着它
-着定
-着实
-着己
-着帐
-着床
-着庸
-着式
-着录
-着心
-着志
-着忙
-着急
-着恼
-着惊
-着想
-着意
-着慌
-着我
-着手
-着抹
-着摸
-着撰
-着数
-着明
-着末
-着极
-着格
-着棋
-着槁
-着气
-着法
-着浅
-着火
-着然
-着甚
-着生
-着疑
-着白
-着相
-着眼
-着着
-着祂
-着积
-着稿
-着笔
-着籍
-着紧
-着緑
-着绊
-着绩
-着绯
-着绿
-着肉
-着脚
-着舰
-着色
-着节
-着花
-着莫
-着落
-着藁
-着衣
-着装
-着要
-着警
-着趣
-着边
-着迷
-着迹
-着重
-着録
-着闻
-着陆
-着雝
-着鞭
-着题
-着魔
-睡得着
-睡不着
-睡着
-睡著书
-睡著作
-睡著名
-睡著录
-睡著称
-睡著者
-睡著述
-瞒着
-瞒著书
-瞒著作
-瞒著名
-瞒著录
-瞒著称
-瞒著者
-瞒著述
-瞪着
-瞪著书
-瞪著作
-瞪著名
-瞪著录
-瞪著称
-瞪著者
-瞪著述
-福着
-福著书
-福著作
-福著名
-福著录
-福著称
-福著者
-福著述
-空着
-空著书
-空著作
-空著名
-空著录
-空著称
-空著者
-空著述
-穿着
-穿著书
-穿著作
-穿著名
-穿著录
-穿著称
-穿著者
-穿著述
-竖着
-竖著书
-竖著作
-竖著名
-竖著录
-竖著称
-竖著者
-竖著述
-站着
-站著书
-站著作
-站著名
-站著录
-站著称
-站著者
-站著述
-笑着
-笑著书
-笑著作
-笑著名
-笑著录
-笑著称
-笑著者
-笑著述
-管着
-管著书
-管著作
-管著名
-管著录
-管著称
-管著者
-管著述
-绑着
-绑著书
-绑著作
-绑著名
-绑著录
-绑著称
-绑著者
-绑著述
-绕着
-绕著书
-绕著作
-绕著名
-绕著录
-绕著称
-绕著者
-绕著述
-缠着
-缠著书
-缠著作
-缠著名
-缠著录
-缠著称
-缠著者
-缠著述
-罩着
-罩著书
-罩著作
-罩著名
-罩著录
-罩著称
-罩著者
-罩著述
-美着
-美著书
-美著作
-美著名
-美著录
-美著称
-美著者
-美著述
-耀着
-耀著书
-耀著作
-耀著名
-耀著录
-耀著称
-耀著者
-耀著述
-考着
-考著书
-考著作
-考著名
-考著录
-考著称
-考著者
-考著述
-背着
-背著书
-背著作
-背著名
-背著录
-背著称
-背著者
-背著述
-胶着
-胶著书
-胶著作
-胶著名
-胶著录
-胶著称
-胶著者
-胶著述
-艺着
-艺著书
-艺著作
-艺著名
-艺著录
-艺著称
-艺著者
-艺著述
-苦着
-苦著书
-苦著作
-苦著名
-苦著录
-苦著称
-苦著者
-苦著述
-获着
-获著书
-获著作
-获著名
-获著录
-获著称
-获著者
-获著述
-落着
-落著书
-落著作
-落著名
-落著录
-落著称
-落著者
-落著述
-蒙着
-蒙著书
-蒙著作
-蒙著名
-蒙著录
-蒙著称
-蒙著者
-蒙著述
-藏着
-藏著书
-藏著作
-藏著名
-藏著录
-藏著称
-藏著者
-藏著述
-蘸着
-蘸著书
-蘸著作
-蘸著名
-蘸著录
-蘸著称
-蘸著者
-蘸著述
-行着
-行著书
-行著作
-行著名
-行著录
-行著称
-行著者
-行著述
-衣着
-衣著书
-衣著作
-衣著名
-衣著录
-衣著称
-衣著者
-衣著述
-装着
-装著书
-装著作
-装著名
-装著录
-装著称
-装著者
-装著述
-裹着
-裹著书
-裹著作
-裹著名
-裹著录
-裹著称
-裹著者
-裹著述
-见着
-见著书
-见著作
-见著名
-见著录
-见著称
-见著者
-见著述
-记着
-记著书
-记著作
-记著名
-记著录
-记著称
-记著者
-记著述
-试着
-试著书
-试著作
-试著名
-试著录
-试著称
-试著者
-试著述
-语着
-语著书
-语著作
-语著名
-语著录
-语著称
-语著者
-语著述
-豫着
-豫著书
-豫著作
-豫著名
-豫著录
-豫著称
-豫著者
-豫著述
-贞着
-贞著书
-贞著作
-贞著名
-贞著录
-贞著称
-贞著者
-贞著述
-走着
-走著书
-走著作
-走著名
-走著录
-走著称
-走著者
-走著述
-赶着
-赶著书
-赶著作
-赶著名
-赶著录
-赶著称
-赶著者
-赶著述
-趴着
-趴著书
-趴著作
-趴著名
-趴著录
-趴著称
-趴著者
-趴著述
-跃着
-跃著书
-跃著作
-跃著名
-跃著录
-跃著称
-跃著者
-跃著述
-跑着
-跑著书
-跑著作
-跑著名
-跑著录
-跑著称
-跑著者
-跑著述
-跟着
-跟著书
-跟著作
-跟著名
-跟著录
-跟著称
-跟著者
-跟著述
-跪着
-跪著书
-跪著作
-跪著名
-跪著录
-跪著称
-跪著者
-跪著述
-跳着
-跳著书
-跳著作
-跳著名
-跳著录
-跳著称
-跳著者
-跳著述
-踏着
-踏著书
-踏著作
-踏著名
-踏著录
-踏著称
-踏著者
-踏著述
-踩着
-踩著书
-踩著作
-踩著名
-踩著录
-踩著称
-踩著者
-踩著述
-身着
-身著书
-身著作
-身著名
-身著录
-身著称
-身著者
-身著述
-躺着
-躺著书
-躺著作
-躺著名
-躺著录
-躺著称
-躺著者
-躺著述
-转着
-转著书
-转著作
-转著名
-转著录
-转著称
-转著者
-转著述
-载着
-载著书
-载著作
-载著名
-载著录
-载著称
-载著者
-载著述
-达着
-达著书
-达著作
-达著名
-达著录
-达著称
-达著者
-达著述
-远着
-远著书
-远著作
-远著名
-远著录
-远著称
-远著者
-远著述
-连着
-连著书
-连著作
-连著名
-连著录
-连著称
-连著者
-连著述
-追着
-追著书
-追著作
-追著名
-追著录
-追著称
-追著者
-追著述
-逆着
-逆著书
-逆著作
-逆著名
-逆著录
-逆著称
-逆著者
-逆著述
-逼着
-逼著书
-逼著作
-逼著名
-逼著录
-逼著称
-逼著者
-逼著述
-遇着
-遇著书
-遇著作
-遇著名
-遇著录
-遇著称
-遇著者
-遇著述
-配着
-配著书
-配著作
-配著名
-配著录
-配著称
-配著者
-配著述
-酿着
-酿著书
-酿著作
-酿著名
-酿著录
-酿著称
-酿著者
-酿著述
-铺着
-铺著书
-铺著作
-铺著名
-铺著录
-铺著称
-铺著者
-铺著述
-闭着
-闭著书
-闭著作
-闭著名
-闭著录
-闭著称
-闭著者
-闭著述
-闲着
-闲著书
-闲著作
-闲著名
-闲著录
-闲著称
-闲著者
-闲著述
-附着
-附著书
-附著作
-附著名
-附著录
-附著称
-附著者
-附著述
-陋着
-陋著书
-陋著作
-陋著名
-陋著录
-陋著称
-陋著者
-陋著述
-陪着
-陪著书
-陪著作
-陪著名
-陪著录
-陪著称
-陪著者
-陪著述
-随着
-随著书
-随著作
-随著名
-随著录
-随著称
-随著者
-随著述
-隔着
-隔著书
-隔著作
-隔著名
-隔著录
-隔著称
-隔著者
-隔著述
-雅着
-雅著书
-雅著作
-雅著名
-雅著录
-雅著称
-雅著者
-雅著述
-顶着
-顶著书
-顶著作
-顶著名
-顶著录
-顶著称
-顶著者
-顶著述
-顺着
-顺著书
-顺著作
-顺著名
-顺著录
-顺著称
-顺著者
-顺著述
-领着
-领著书
-领著作
-领著名
-领著录
-领著称
-领著者
-领著述
-飘着
-飘著书
-飘著作
-飘著名
-飘著录
-飘著称
-飘著者
-飘著述
-驾着
-驾著书
-驾著作
-驾著名
-驾著录
-驾著称
-驾著者
-驾著述
-骂着
-骂著书
-骂著作
-骂著名
-骂著录
-骂著称
-骂著者
-骂著述
-骑着
-骑著书
-骑著作
-骑著名
-骑著录
-骑著称
-骑著者
-骑著述
-骗着
-骗著书
-骗著作
-骗著名
-骗著录
-骗著称
-骗著者
-骗著述
-高着
-高著书
-高著作
-高著名
-高著录
-高著称
-高著者
-高著述
-髭着
-髭著书
-髭著作
-髭著名
-髭著录
-髭著称
-髭著者
-髭著述
-黏着
-黏著书
-黏著作
-黏著名
-黏著录
-黏著称
-黏著者
-黏著述
-新著龙虎门
-护着
-护著书
-护著作
-护著名
-护著录
-护著称
-护著者
-护著述
-保护着
-爱护着
-庇护着
-传着
-传著书
-传著作
-传著名
-传著录
-传著称
-传著者
-传著述
-标志着
-流露着
-靠着
-靠著作
-靠著名
-靠著录
-靠著称
-靠著者
-靠著述
-玩着
-迫着
-吃得着
-吃不着
-吃着
-闻得着
-闻不着
-闻着
-嗅得着
-嗅不着
-嗅着
-警戒着
-於乎
-於戏
-魏徵
-柳诒徵
-於姓
-於氏
-於夫罗
-於梨华
-卷舌
-樊於期
-於菟
-於潜县
-石碁镇
-因著《
-因著〈
-李泽钜
-於祥玉
-於崇文
-於世成
-於乙宇同
-於宇同
-朴於宇同
-於哲
-於除鞬
-於志贺
-覆蓋
-五箇山
-麽麽
-幺厮
-幺半群
-幺元
-幺爹
-幺叔
-幺舅
-幺爸
-幺妈
-幺姨
-幺娘
-幺妹
-幺小
-幺姓
-姓幺
-幺氏
-麽氏
-幺蛾子
-幺麽
-幺麽小丑
-幺凤
-幺二三
-幺篇
-幺谦
-麴义
-麴英
-麯崇裕
-阿部正瞭
-醯酱
-醯鸡
-醯醋
-醯醢
-醯壶
-苧烯
-近角聪信
-米泽瑠美
-峯岸南
-僧伽吒
-王道乾
-後姓
diff --git a/includes/zhtable/simpphrases_exclude.manual b/includes/zhtable/simpphrases_exclude.manual
deleted file mode 100644
index 3e9d3ecc..00000000
--- a/includes/zhtable/simpphrases_exclude.manual
+++ /dev/null
@@ -1,21 +0,0 @@
-整飭
-後
-谘
-彷佛
-三番四复
-三复
-藉
-关於
-对於
-属於
-至於
-夥计
-薹
-嚇
-醣
-捱
-簑
-樑
-摺叠
-餗
-安甯 \ No newline at end of file
diff --git a/includes/zhtable/toCN.manual b/includes/zhtable/toCN.manual
deleted file mode 100644
index 243f61b0..00000000
--- a/includes/zhtable/toCN.manual
+++ /dev/null
@@ -1,275 +0,0 @@
-」 ”
-「 “
-『 ‘
-』 ’
-記憶體 内存
-預設 默认
-串列 串行
-串列加速器 串列加速器
-乙太網 以太网
-點陣圖 位图
-常式 例程
-光碟 光盘
-光碟機 光驱
-全形 全角
-載入 加载
-半形 半角
-變數 变量
-雜訊 噪声
-因數 因子
-功能變數名稱 域名
-音效卡 声卡
-字型大小 字号
-字型檔 字库
-欄位 字段
-字元 字符
-字元济 字元济
-字元濟 字元济
-字元会 字元会
-字元會 字元会
-存檔 存盘
-定址 寻址
-章節附註 尾注
-非同步 异步
-匯流排 总线
-括弧 括号
-介面 接口
-控制項 控件
-許可權 权限
-碟片 盘片
-矽片 硅片
-矽谷 硅谷
-硬碟 硬盘
-磁碟 磁盘
-磁軌 磁道
-程式控制 程控
-遠程控制 远程控制
-远程控制 远程控制
-運算元 算子
-演算法 算法
-晶片 芯片
-晶元 芯片
-片語 词组
-軟碟機 软驱
-快閃記憶體 快闪存储器
-滑鼠 鼠标
-滑鼠蛇 滑鼠蛇
-二進位 二进制
-滿二進位 满二进位
-六進位 六进制
-滿六進位 满六进位
-滿十六進位 满十六进位
-八進位 八进制
-滿八進位 满八进位
-十進位 十进制
-滿十進位 满十进位
-16進位 16进位
-滿16進位 满16进位
-二進位制 二进位制
-六進位制 六进位制
-八進位制 八进位制
-十進位制 十进位制
-16進位制 16进位制
-互動式 交互式
-優先順序 优先级
-感測 传感
-攜帶型 便携式
-資訊理論 信息论
-迴圈 循环
-防寫 写保护
-解析度 分辨率
-伺服器 服务器
-等於 等于
-區域網 局域网
-巨集 宏
-掃瞄器 扫瞄仪
-寬頻 宽带
-資料庫 数据库
-萬曆 万历
-永曆 永历
-辭彙 词汇
-母音 元音
-字母 字母
-頭槌 头球
-進球 入球
-顆進球 粒入球
-射門 打门
-蓋火鍋 火锅盖帽
-印表機 打印机
-打印機 打印机
-位元組 字节
-字節 字节
-列印 打印
-打印 打印
-硬體 硬件
-二極體 二极管
-二極管 二极管
-三極體 三极管
-三極管 三极管
-軟體 软件
-軟件 软件
-網路 网络
-網絡 网络
-人工智慧 人工智能
-太空梭 航天飞机
-穿梭機 航天飞机
-網際網路 互联网
-互聯網 互联网
-機械人 机器人
-機器人 机器人
-行動電話 移动电话
-流動電話 移动电话
-調制解調器 调制解调器
-數據機 调制解调器
-短訊 短信
-簡訊 短信
-烏茲別克 乌兹别克斯坦
-葉門 也门
-伯利茲 伯利兹
-貝里斯 伯利兹
-維德角 佛得角
-克羅埃西亞 克罗地亚
-甘比亞 冈比亚
-幾內亞比索 几内亚比绍
-列支敦斯登 列支敦士登
-賴比瑞亞 利比里亚
-迦納 加纳
-加彭 加蓬
-波札那 博茨瓦纳
-盧安達 卢旺达
-瓜地馬拉 危地马拉
-厄瓜多爾 厄瓜多尔
-厄瓜多尔 厄瓜多尔
-厄瓜多 厄瓜多尔
-厄利垂亞 厄立特里亚
-吉布地 吉布提
-哈薩克 哈萨克斯坦
-哥斯大黎加 哥斯达黎加
-吐瓦魯 图瓦卢
-土庫曼 土库曼斯坦
-聖露西亞 圣卢西亚
-聖吉斯納域斯 圣基茨和尼维斯
-聖克里斯多福及尼維斯 圣基茨和尼维斯
-聖文森及格瑞那丁 圣文森特和格林纳丁斯
-聖馬利諾 圣马力诺
-蓋亞那 圭亚那
-坦尚尼亞 坦桑尼亚
-衣索匹亞 埃塞俄比亚
-衣索比亞 埃塞俄比亚
-吉里巴斯 基里巴斯
-塔吉克 塔吉克斯坦
-塞拉利昂 塞拉利昂
-塞普勒斯 塞浦路斯
-塞席爾 塞舌尔
-多米尼克 多米尼加国
-安地卡及巴布達 安提瓜和巴布达
-尼日利亞 尼日利亚
-尼日利亚 尼日利亚
-奈及利亞 尼日利亚
-尼日爾 尼日尔
-尼日尔 尼日尔
-巴貝多 巴巴多斯
-巴布亞紐幾內亞 巴布亚新几内亚
-布基納法索 布基纳法索
-布吉納法索 布基纳法索
-蒲隆地 布隆迪
-帛琉 帕劳
-義大利 意大利
-索羅門群島 所罗门群岛
-汶萊 文莱
-史瓦濟蘭 斯威士兰
-斯洛維尼亞 斯洛文尼亚
-紐西蘭 新西兰
-格瑞那達 格林纳达
-茅利塔尼亞 毛里塔尼亚
-毛里裘斯 毛里求斯
-模里西斯 毛里求斯
-沙地阿拉伯 沙特阿拉伯
-沙烏地阿拉伯 沙特阿拉伯
-波士尼亞赫塞哥維納 波斯尼亚和黑塞哥维那
-辛巴威 津巴布韦
-宏都拉斯 洪都拉斯
-千里達托貝哥 特立尼达和托巴哥
-諾魯 瑙鲁
-萬那杜 瓦努阿图
-溫納圖 瓦努阿图
-葛摩 科摩罗
-象牙海岸 科特迪瓦
-突尼西亞 突尼斯
-索馬利亞 索马里
-寮國 老挝
-肯雅 肯尼亚
-肯亞 肯尼亚
-蘇利南 苏里南
-莫三比克 莫桑比克
-賴索托 莱索托
-貝南 贝宁
-尚比亞 赞比亚
-亞塞拜然 阿塞拜疆
-阿拉伯聯合大公國 阿拉伯联合酋长国
-南韓 韩国
-馬爾地夫 马尔代夫
-馬爾他 马耳他
-馬利共和國 马里共和国
-即食麵 方便面
-快速面 方便面
-速食麵 方便面
-泡麵 方便面
-笨豬跳 蹦极跳
-绑紧跳 蹦极跳
-冷盤 凉菜
-冷菜 凉菜
-散钱 零钱
-谐星 笑星
-夜学 夜校
-华乐 民乐
-中樂 民乐
-軍中樂園 军中乐园
-华乐街 华乐街
-屋价 房价
-計程車 出租车
-單車 自行车
-節慶 节日
-芝士 乾酪
-狗隻 犬只
-士多啤梨 草莓
-忌廉 奶油
-桌球 台球
-撞球 台球
-衞生 卫生
-衛生 卫生
-賓士 奔驰
-平治 奔驰
-平治之亂 平治之乱
-平治之乱 平治之乱
-積架 捷豹
-福斯 大众
-福士 大众
-萬事得 马自达
-寶獅 标志
-拿破崙 拿破仑
-布殊 布什
-布希 布什
-布希亞 布希亚
-布希亚 布希亚
-柯林頓 克林顿
-海珊 侯赛因
-梵谷 凡高
-大衛碧咸 大卫·贝克汉姆
-米高奧雲 迈克尔·欧文
-卡佩雅蒂 珍妮弗·卡普里亚蒂
-沙芬 马拉特·萨芬
-舒麥加 迈克尔·舒马赫
-希特拉 希特勒
-黛安娜 戴安娜
-榴槤 榴莲
-榴梿 榴莲
-矽 硅
-矽肺 矽肺
-矽塵 矽尘
-矽尘 矽尘
-矽鋼 矽钢
-矽钢 矽钢
-侏儸紀 侏罗纪
-甚麽 什么
-甚麼 什么
diff --git a/includes/zhtable/toHK.manual b/includes/zhtable/toHK.manual
deleted file mode 100644
index 1f7fe7d0..00000000
--- a/includes/zhtable/toHK.manual
+++ /dev/null
@@ -1,2300 +0,0 @@
-” 」
-“ 「
-‘ 『
-’ 』
-鉤 鈎
-衛 衞
-凶殺 兇殺
-凶殘 兇殘
-緝凶 緝兇
-買凶 買兇
-印表機 打印機
-字节 位元組
-字節 位元組
-列印 打印
-硬件 硬件
-硬體 硬件
-二極體 二極管
-三極體 三極管
-軟體 軟件
-網路 網絡
-人工智慧 人工智能
-航天飞机 穿梭機
-太空梭 穿梭機
-因特网 互聯網
-網際網路 互聯網
-机器人 機械人
-機器人 機械人
-移动电话 流動電話
-行動電話 流動電話
-數據機 調制解調器
-短信 短訊
-簡訊 短訊
-查德 乍得
-葉門 也門
-貝里斯 伯利茲
-維德角 佛得角
-克羅埃西亞 克羅地亞
-甘比亞 岡比亞
-幾內亞比索 幾內亞比紹
-列支敦斯登 列支敦士登
-賴比瑞亞 利比里亞
-迦納 加納
-加彭 加蓬
-波札那 博茨瓦納
-盧安達 盧旺達
-瓜地馬拉 危地馬拉
-厄瓜多尔 厄瓜多爾
-厄瓜多爾 厄瓜多爾
-厄瓜多 厄瓜多爾
-厄利垂亞 厄立特里亞
-吉布地 吉布堤
-哥斯大黎加 哥斯達黎加
-吐瓦魯 圖瓦盧
-聖露西亞 聖盧西亞
-圣基茨和尼维斯 聖吉斯納域斯
-聖克里斯多福及尼維斯 聖吉斯納域斯
-聖文森及格瑞那丁 聖文森特和格林納丁斯
-聖馬利諾 聖馬力諾
-蓋亞那 圭亞那
-坦尚尼亞 坦桑尼亞
-衣索匹亞 埃塞俄比亞
-衣索比亞 埃塞俄比亞
-吉里巴斯 基里巴斯
-塞普勒斯 塞浦路斯
-塞席爾 塞舌爾
-安地卡及巴布達 安提瓜和巴布達
-尼日利亚 尼日利亞
-尼日利亞 尼日利亞
-奈及利亞 尼日利亞
-尼日尔 尼日爾
-尼日爾 尼日爾
-尼日 尼日爾
-巴貝多 巴巴多斯
-巴布亞紐幾內亞 巴布亞新畿內亞
-布吉納法索 布基納法索
-蒲隆地 布隆迪
-帕劳 帛琉
-義大利 意大利
-索羅門群島 所羅門群島
-文莱 汶萊
-史瓦濟蘭 斯威士蘭
-斯洛維尼亞 斯洛文尼亞
-紐西蘭 新西蘭
-格瑞那達 格林納達
-茅利塔尼亞 毛里塔尼亞
-毛里求斯 毛里裘斯
-模里西斯 毛里裘斯
-沙地阿拉伯 沙特阿拉伯
-沙烏地阿拉伯 沙特阿拉伯
-波士尼亞赫塞哥維納 波斯尼亞黑塞哥維那
-辛巴威 津巴布韋
-宏都拉斯 洪都拉斯
-千里達托貝哥 特立尼達和多巴哥
-諾魯 瑙魯
-萬那杜 瓦努阿圖
-葛摩 科摩羅
-索馬利亞 索馬里
-寮國 老撾
-肯尼亚 肯雅
-肯亞 肯雅
-莫三比克 莫桑比克
-賴索托 萊索托
-貝南 貝寧
-尚比亞 贊比亞
-亞塞拜然 阿塞拜疆
-阿拉伯聯合大公國 阿拉伯聯合酋長國
-馬爾地夫 馬爾代夫
-馬利共和國 馬里共和國
-方便面 即食麵
-快速面 即食麵
-速食麵 即食麵
-泡麵 即食麵
-土豆 馬鈴薯
-土豆网 土豆網
-土豆網 土豆網
-华乐 中樂
-民乐 中樂
-計程車 的士
-出租车 的士
-公車 巴士
-公車上書 公車上書
-自行车 單車
-犬只 狗隻
-台球 桌球
-撞球 桌球
-冰淇淋 雪糕
-賓士 平治
-捷豹 積架
-福斯 福士
-雪铁龙 先進
-雪鐵龍 先進
-沃尓沃 富豪
-马自达 萬事得
-馬自達 萬事得
-寶獅 標致
-布什 布殊
-布希 布殊
-布希亞 布希亞
-布希亚 布希亞
-柯林頓 克林頓
-萨达姆 薩達姆
-海珊 侯賽因
-大卫·贝克汉姆 大衛碧咸
-迈克尔·欧文 米高奧雲
-珍妮弗·卡普里亚蒂 卡佩雅蒂
-马拉特·萨芬 沙芬
-迈克尔·舒马赫 舒麥加
-希特勒 希特拉
-狄安娜 戴安娜
-黛安娜 戴安娜
-颁布 頒佈
-頒布 頒佈
-挨著 挨着
-愛著 愛着
-暗著 暗着
-昂著 昂着
-擺著 擺着
-伴著 伴着
-辦著 辦着
-幫著 幫着
-綁著 綁着
-抱著 抱着
-背著 背着
-備著 備着
-本著 本着
-逼著 逼着
-閉著 閉着
-變著 變着
-猜著 猜着
-踩著 踩着
-藏著 藏着
-側著 側着
-纏著 纏着
-敞著 敞着
-唱著 唱着
-朝著 朝着
-沉著 沉着
-乘著 乘着
-持著 持着
-斥著 斥着
-醜著 醜着
-穿著 穿着
-吹著 吹着
-達著 達着
-打著 打着
-待著 待着
-帶著 帶着
-戴著 戴着
-當著 當着
-擋著 擋着
-得著 得着
-瞪著 瞪着
-低著 低着
-點著 點着
-盯著 盯着
-頂著 頂着
-定著 定着
-動著 動着
-鬥著 鬥着
-獨著 獨着
-對著 對着
-盾著 盾着
-犯得著 犯得着
-犯不著 犯不着
-福著 福着
-趕著 趕着
-高著 高着
-隔著 隔着
-跟著 跟着
-孤著 孤着
-關著 關着
-管著 管着
-慣著 慣着
-光著 光着
-跪著 跪着
-裹著 裹着
-撼著 撼着
-喝著 喝着
-候著 候着
-懷著 懷着
-晃著 晃着
-揮著 揮着
-活著 活着
-獲著 獲着
-獲著 獲着
-急著 急着
-記著 記着
-冀著 冀着
-夾著 夾着
-駕著 駕着
-見著 見着
-閑著 閑着
-叫著 叫着
-接著 接着
-借著 借着
-借著 借着
-據著 據着
-開著 開着
-看得著 看得着
-看不著 看不着
-看著 看着
-康著 康着
-扛著 扛着
-考著 考着
-渴著 渴着
-刻著 刻着
-空著 空着
-哭著 哭着
-苦著 苦着
-捆著 捆着
-困著 困着
-拉著 拉着
-來著 來着
-樂著 樂着
-努力著 努力着
-麗著 麗着
-連著 連着
-戀著 戀着
-涼著 涼着
-亮著 亮着
-臨著 臨着
-拎著 拎着
-領著 領着
-流著 流着
-留著 留着
-摟著 摟着
-陋著 陋着
-落著 落着
-罵著 罵着
-瞞著 瞞着
-漫著 漫着
-忙著 忙着
-冒著 冒着
-美著 美着
-夢著 夢着
-蒙著 蒙着
-拿著 拿着
-逆著 逆着
-釀著 釀着
-努著 努着
-趴著 趴着
-跑著 跑着
-陪著 陪着
-配著 配着
-披著 披着
-騙著 騙着
-飄著 飄着
-拼著 拼着
-鋪著 鋪着
-騎著 騎着
-牽著 牽着
-求著 求着
-去著 去着
-嚷著 嚷着
-繞著 繞着
-忍著 忍着
-揉著 揉着
-潤著 潤着
-燒著 燒着
-身著 身着
-沉著 沉着
-盛著 盛着
-試著 試着
-守著 守着
-受著 受着
-梳著 梳着
-豎著 豎着
-數著 數着
-睡得著 睡得着
-睡不著 睡不着
-睡著 睡着
-順著 順着
-隨著 隨着
-踏著 踏着
-抬著 抬着
-躺著 躺着
-提著 提着
-甜著 甜着
-挑著 挑着
-跳著 跳着
-聽得著 聽得着
-聽不著 聽不着
-聽著 聽着
-偷著 偷着
-拖著 拖着
-望著 望着
-圍著 圍着
-味著 味着
-想著 想着
-響著 響着
-向著 向着
-笑著 笑着
-心著 心着
-信著 信着
-行著 行着
-性著 性着
-學著 學着
-尋著 尋着
-循著 循着
-壓著 壓着
-雅著 雅着
-沿著 沿着
-耀著 耀着
-掖著 掖着
-衣著 衣着
-疑著 疑着
-溢著 溢着
-藝著 藝着
-因著 因着
-印著 印着
-應著 應着
-映著 映着
-用得著 用得着
-用不著 用不着
-用著 用着
-悠著 悠着
-有著 有着
-與著 與着
-語著 語着
-豫著 豫着
-遠著 遠着
-躍著 躍着
-雜著 雜着
-載著 載着
-在著 在着
-紮著 紮着
-展著 展着
-站著 站着
-戰著 戰着
-蘸著 蘸着
-仗著 仗着
-找得著 找得着
-找不著 找不着
-照著 照着
-罩著 罩着
-貞著 貞着
-枕著 枕着
-爭著 爭着
-掙著 掙着
-制著 制着
-志著 志着
-皺著 皺着
-住著 住着
-抓著 抓着
-轉著 轉着
-裝著 裝着
-追著 追着
-髭著 髭着
-走著 走着
-坐著 坐着
-做著 做着
-含著 含着
-涵著 涵着
-演著 演着
-保障著 保障着
-黏著 黏着
-膠著 膠着
-附著 附着
-代表著 代表着
-浮著 浮着
-寫著 寫着
-遇著 遇着
-殺著 殺着
-著筆 着筆
-著鞭 着鞭
-著法 着法
-著火 着火
-著急 着急
-著艦 着艦
-著腳 着腳
-著她 着她
-著緊 着緊
-著力 着力
-著涼 着涼
-著陸 着陸
-著錄 着錄
-著落 着落
-著忙 着忙
-著迷 着迷
-著墨 着墨
-著妳 着妳
-著你 着你
-著色 着色
-著什麼急 着什麼急
-著實 着實
-著手 着手
-著數 着數
-著絲 着絲
-著他 着他
-著它 着它
-著祂 着祂
-著我 着我
-著想 着想
-著眼 着眼
-著衣 着衣
-著意 着意
-著重 着重
-著重 着重
-著裝 着裝
-著地 着地
-不著邊際 不着邊際
-不著痕跡 不着痕跡
-挨著作 挨著作
-挨著者 挨著者
-挨著名 挨著名
-挨著述 挨著述
-挨著稱 挨著稱
-挨著錄 挨著錄
-愛著作 愛著作
-愛著者 愛著者
-愛著名 愛著名
-愛著述 愛著述
-愛著稱 愛著稱
-愛著錄 愛著錄
-愛著書 愛著書
-暗著作 暗著作
-暗著者 暗著者
-暗著名 暗著名
-暗著述 暗著述
-暗著稱 暗著稱
-暗著錄 暗著錄
-暗著書 暗著書
-昂著作 昂著作
-昂著者 昂著者
-昂著名 昂著名
-昂著述 昂著述
-昂著稱 昂著稱
-昂著錄 昂著錄
-昂著書 昂著書
-擺著作 擺著作
-擺著者 擺著者
-擺著名 擺著名
-擺著述 擺著述
-擺著稱 擺著稱
-擺著錄 擺著錄
-伴著作 伴著作
-伴著者 伴著者
-伴著名 伴著名
-伴著述 伴著述
-伴著稱 伴著稱
-伴著錄 伴著錄
-伴著書 伴著書
-辦著作 辦著作
-辦著者 辦著者
-辦著名 辦著名
-辦著述 辦著述
-辦著稱 辦著稱
-辦著錄 辦著錄
-辦著書 辦著書
-幫著作 幫著作
-幫著者 幫著者
-幫著名 幫著名
-幫著述 幫著述
-幫著稱 幫著稱
-幫著錄 幫著錄
-幫著書 幫著書
-綁著作 綁著作
-綁著者 綁著者
-綁著名 綁著名
-綁著述 綁著述
-綁著稱 綁著稱
-綁著錄 綁著錄
-綁著書 綁著書
-抱著作 抱著作
-抱著者 抱著者
-抱著名 抱著名
-抱著述 抱著述
-抱著稱 抱著稱
-抱著錄 抱著錄
-背著作 背著作
-背著者 背著者
-背著名 背著名
-背著述 背著述
-背著稱 背著稱
-背著錄 背著錄
-背著書 背著書
-備著作 備著作
-備著者 備著者
-備著名 備著名
-備著述 備著述
-備著稱 備著稱
-備著錄 備著錄
-備著書 備著書
-本著作 本著作
-本著者 本著者
-本著名 本著名
-本著述 本著述
-本著稱 本著稱
-本著錄 本著錄
-本著書 本著書
-逼著作 逼著作
-逼著者 逼著者
-逼著名 逼著名
-逼著述 逼著述
-逼著稱 逼著稱
-逼著錄 逼著錄
-逼著書 逼著書
-閉著作 閉著作
-閉著者 閉著者
-閉著名 閉著名
-閉著述 閉著述
-閉著稱 閉著稱
-閉著錄 閉著錄
-閉著書 閉著書
-變著作 變著作
-變著者 變著者
-變著名 變著名
-變著述 變著述
-變著稱 變著稱
-變著錄 變著錄
-變著書 變著書
-猜著作 猜著作
-猜著者 猜著者
-猜著名 猜著名
-猜著述 猜著述
-猜著稱 猜著稱
-猜著錄 猜著錄
-猜著書 猜著書
-踩著作 踩著作
-踩著者 踩著者
-踩著名 踩著名
-踩著述 踩著述
-踩著稱 踩著稱
-踩著錄 踩著錄
-踩著書 踩著書
-藏著作 藏著作
-藏著者 藏著者
-藏著名 藏著名
-藏著述 藏著述
-藏著稱 藏著稱
-藏著錄 藏著錄
-藏著書 藏著書
-側著作 側著作
-側著者 側著者
-側著名 側著名
-側著述 側著述
-側著稱 側著稱
-側著錄 側著錄
-側著書 側著書
-纏著作 纏著作
-纏著者 纏著者
-纏著名 纏著名
-纏著述 纏著述
-纏著稱 纏著稱
-纏著錄 纏著錄
-纏著書 纏著書
-敞著作 敞著作
-敞著者 敞著者
-敞著名 敞著名
-敞著述 敞著述
-敞著稱 敞著稱
-敞著錄 敞著錄
-唱著作 唱著作
-唱著者 唱著者
-唱著名 唱著名
-唱著述 唱著述
-唱著稱 唱著稱
-唱著錄 唱著錄
-唱著書 唱著書
-朝著作 朝著作
-朝著者 朝著者
-朝著名 朝著名
-朝著述 朝著述
-朝著稱 朝著稱
-朝著錄 朝著錄
-沉著作 沉著作
-沉著者 沉著者
-沉著名 沉著名
-沉著述 沉著述
-沉著稱 沉著稱
-沉著錄 沉著錄
-沉著書 沉著書
-乘著作 乘著作
-乘著者 乘著者
-乘著名 乘著名
-乘著述 乘著述
-乘著稱 乘著稱
-乘著錄 乘著錄
-乘著書 乘著書
-持著作 持著作
-持著者 持著者
-持著名 持著名
-持著述 持著述
-持著稱 持著稱
-持著錄 持著錄
-斥著作 斥著作
-斥著者 斥著者
-斥著名 斥著名
-斥著述 斥著述
-斥著稱 斥著稱
-斥著錄 斥著錄
-斥著書 斥著書
-醜著作 醜著作
-醜著者 醜著者
-醜著名 醜著名
-醜著述 醜著述
-醜著稱 醜著稱
-醜著錄 醜著錄
-醜著書 醜著書
-穿著作 穿著作
-穿著者 穿著者
-穿著名 穿著名
-穿著述 穿著述
-穿著稱 穿著稱
-穿著錄 穿著錄
-穿著書 穿著書
-吹著作 吹著作
-吹著者 吹著者
-吹著名 吹著名
-吹著述 吹著述
-吹著稱 吹著稱
-吹著錄 吹著錄
-吹著書 吹著書
-達著作 達著作
-達著者 達著者
-達著名 達著名
-達著述 達著述
-達著稱 達著稱
-達著錄 達著錄
-達著書 達著書
-打著作 打著作
-打著者 打著者
-打著名 打著名
-打著述 打著述
-打著稱 打著稱
-打著錄 打著錄
-打著書 打著書
-待著作 待著作
-待著者 待著者
-待著名 待著名
-待著述 待著述
-待著稱 待著稱
-待著錄 待著錄
-待著書 待著書
-帶著作 帶著作
-帶著者 帶著者
-帶著名 帶著名
-帶著述 帶著述
-帶著稱 帶著稱
-帶著錄 帶著錄
-帶著書 帶著書
-戴著作 戴著作
-戴著者 戴著者
-戴著名 戴著名
-戴著述 戴著述
-戴著稱 戴著稱
-戴著錄 戴著錄
-戴著書 戴著書
-當著作 當著作
-當著者 當著者
-當著名 當著名
-當著述 當著述
-當著稱 當著稱
-當著錄 當著錄
-當著書 當著書
-擋著作 擋著作
-擋著者 擋著者
-擋著名 擋著名
-擋著述 擋著述
-擋著稱 擋著稱
-擋著錄 擋著錄
-得著作 得著作
-得著者 得著者
-得著名 得著名
-得著述 得著述
-得著稱 得著稱
-得著錄 得著錄
-得著書 得著書
-瞪著作 瞪著作
-瞪著者 瞪著者
-瞪著名 瞪著名
-瞪著述 瞪著述
-瞪著稱 瞪著稱
-瞪著錄 瞪著錄
-瞪著書 瞪著書
-低著作 低著作
-低著者 低著者
-低著名 低著名
-低著述 低著述
-低著稱 低著稱
-低著錄 低著錄
-低著書 低著書
-點著作 點著作
-點著者 點著者
-點著名 點著名
-點著述 點著述
-點著稱 點著稱
-點著錄 點著錄
-點著書 點著書
-盯著作 盯著作
-盯著者 盯著者
-盯著名 盯著名
-盯著述 盯著述
-盯著稱 盯著稱
-盯著錄 盯著錄
-盯著書 盯著書
-頂著作 頂著作
-頂著者 頂著者
-頂著名 頂著名
-頂著述 頂著述
-頂著稱 頂著稱
-頂著錄 頂著錄
-頂著書 頂著書
-定著作 定著作
-定著者 定著者
-定著名 定著名
-定著述 定著述
-定著稱 定著稱
-定著錄 定著錄
-定著書 定著書
-動著作 動著作
-動著者 動著者
-動著名 動著名
-動著述 動著述
-動著稱 動著稱
-動著錄 動著錄
-動著書 動著書
-鬥著作 鬥著作
-鬥著者 鬥著者
-鬥著名 鬥著名
-鬥著述 鬥著述
-鬥著稱 鬥著稱
-鬥著錄 鬥著錄
-鬥著書 鬥著書
-獨著作 獨著作
-獨著者 獨著者
-獨著名 獨著名
-獨著述 獨著述
-獨著稱 獨著稱
-獨著錄 獨著錄
-獨著書 獨著書
-對著作 對著作
-對著者 對著者
-對著名 對著名
-對著述 對著述
-對著稱 對著稱
-對著錄 對著錄
-對著書 對著書
-盾著作 盾著作
-盾著者 盾著者
-盾著名 盾著名
-盾著述 盾著述
-盾著稱 盾著稱
-盾著錄 盾著錄
-盾著書 盾著書
-犯不著作 犯不著作
-犯不著者 犯不著者
-犯不著名 犯不著名
-犯不著述 犯不著述
-犯不著稱 犯不著稱
-犯不著錄 犯不著錄
-犯不著書 犯不著書
-福著作 福著作
-福著者 福著者
-福著名 福著名
-福著述 福著述
-福著稱 福著稱
-福著錄 福著錄
-福著書 福著書
-趕著作 趕著作
-趕著者 趕著者
-趕著名 趕著名
-趕著述 趕著述
-趕著稱 趕著稱
-趕著錄 趕著錄
-趕著書 趕著書
-高著作 高著作
-高著者 高著者
-高著名 高著名
-高著述 高著述
-高著稱 高著稱
-高著錄 高著錄
-高著書 高著書
-隔著作 隔著作
-隔著者 隔著者
-隔著名 隔著名
-隔著述 隔著述
-隔著稱 隔著稱
-隔著錄 隔著錄
-隔著書 隔著書
-跟著作 跟著作
-跟著者 跟著者
-跟著名 跟著名
-跟著述 跟著述
-跟著稱 跟著稱
-跟著錄 跟著錄
-跟著書 跟著書
-孤著作 孤著作
-孤著者 孤著者
-孤著名 孤著名
-孤著述 孤著述
-孤著稱 孤著稱
-孤著錄 孤著錄
-孤著書 孤著書
-關著作 關著作
-關著者 關著者
-關著名 關著名
-關著述 關著述
-關著稱 關著稱
-關著錄 關著錄
-關著書 關著書
-管著作 管著作
-管著者 管著者
-管著名 管著名
-管著述 管著述
-管著稱 管著稱
-管著錄 管著錄
-管著書 管著書
-慣著作 慣著作
-慣著者 慣著者
-慣著名 慣著名
-慣著述 慣著述
-慣著稱 慣著稱
-慣著錄 慣著錄
-慣著書 慣著書
-光著作 光著作
-光著者 光著者
-光著名 光著名
-光著述 光著述
-光著稱 光著稱
-光著錄 光著錄
-光著書 光著書
-跪著作 跪著作
-跪著者 跪著者
-跪著名 跪著名
-跪著述 跪著述
-跪著稱 跪著稱
-跪著錄 跪著錄
-跪著書 跪著書
-裹著作 裹著作
-裹著者 裹著者
-裹著名 裹著名
-裹著述 裹著述
-裹著稱 裹著稱
-裹著錄 裹著錄
-裹著書 裹著書
-撼著作 撼著作
-撼著者 撼著者
-撼著名 撼著名
-撼著述 撼著述
-撼著稱 撼著稱
-撼著錄 撼著錄
-撼著書 撼著書
-喝著作 喝著作
-喝著者 喝著者
-喝著名 喝著名
-喝著述 喝著述
-喝著稱 喝著稱
-喝著錄 喝著錄
-喝著書 喝著書
-候著作 候著作
-候著者 候著者
-候著名 候著名
-候著述 候著述
-候著稱 候著稱
-候著錄 候著錄
-候著書 候著書
-懷著作 懷著作
-懷著者 懷著者
-懷著名 懷著名
-懷著述 懷著述
-懷著稱 懷著稱
-懷著錄 懷著錄
-懷著書 懷著書
-晃著作 晃著作
-晃著者 晃著者
-晃著名 晃著名
-晃著述 晃著述
-晃著稱 晃著稱
-晃著錄 晃著錄
-揮著作 揮著作
-揮著者 揮著者
-揮著名 揮著名
-揮著述 揮著述
-揮著稱 揮著稱
-揮著錄 揮著錄
-活著作 活著作
-活著者 活著者
-活著名 活著名
-活著述 活著述
-活著稱 活著稱
-活著錄 活著錄
-活著書 活著書
-獲著作 獲著作
-獲著者 獲著者
-獲著名 獲著名
-獲著述 獲著述
-獲著稱 獲著稱
-獲著錄 獲著錄
-獲著書 獲著書
-獲著作 獲著作
-獲著者 獲著者
-獲著名 獲著名
-獲著述 獲著述
-獲著稱 獲著稱
-獲著錄 獲著錄
-獲著書 獲著書
-急著作 急著作
-急著者 急著者
-急著名 急著名
-急著述 急著述
-急著稱 急著稱
-急著錄 急著錄
-急著書 急著書
-記著作 記著作
-記著者 記著者
-記著名 記著名
-記著述 記著述
-記著稱 記著稱
-記著錄 記著錄
-記著書 記著書
-冀著作 冀著作
-冀著者 冀著者
-冀著名 冀著名
-冀著述 冀著述
-冀著稱 冀著稱
-冀著錄 冀著錄
-冀著書 冀著書
-夾著作 夾著作
-夾著者 夾著者
-夾著名 夾著名
-夾著述 夾著述
-夾著稱 夾著稱
-夾著錄 夾著錄
-夾著書 夾著書
-駕著作 駕著作
-駕著者 駕著者
-駕著名 駕著名
-駕著述 駕著述
-駕著稱 駕著稱
-駕著錄 駕著錄
-駕著書 駕著書
-見著作 見著作
-見著者 見著者
-見著名 見著名
-見著述 見著述
-見著稱 見著稱
-見著錄 見著錄
-見著書 見著書
-閑著作 閑著作
-閑著者 閑著者
-閑著名 閑著名
-閑著述 閑著述
-閑著稱 閑著稱
-閑著錄 閑著錄
-閑著書 閑著書
-叫著作 叫著作
-叫著者 叫著者
-叫著名 叫著名
-叫著述 叫著述
-叫著稱 叫著稱
-叫著錄 叫著錄
-叫著書 叫著書
-接著作 接著作
-接著者 接著者
-接著名 接著名
-接著述 接著述
-接著稱 接著稱
-接著錄 接著錄
-借著作 借著作
-借著者 借著者
-借著名 借著名
-借著述 借著述
-借著稱 借著稱
-借著錄 借著錄
-借著書 借著書
-借著作 借著作
-借著者 借著者
-借著名 借著名
-借著述 借著述
-借著稱 借著稱
-借著錄 借著錄
-借著書 借著書
-據著作 據著作
-據著者 據著者
-據著名 據著名
-據著述 據著述
-據著稱 據著稱
-據著錄 據著錄
-據著書 據著書
-開著作 開著作
-開著者 開著者
-開著名 開著名
-開著述 開著述
-開著稱 開著稱
-開著錄 開著錄
-開著書 開著書
-看著作 看著作
-看著者 看著者
-看著名 看著名
-看著述 看著述
-看著稱 看著稱
-看著錄 看著錄
-看著書 看著書
-康著作 康著作
-康著者 康著者
-康著名 康著名
-康著述 康著述
-康著稱 康著稱
-康著錄 康著錄
-康著書 康著書
-扛著作 扛著作
-扛著者 扛著者
-扛著名 扛著名
-扛著述 扛著述
-扛著稱 扛著稱
-扛著錄 扛著錄
-扛著書 扛著書
-考著作 考著作
-考著者 考著者
-考著名 考著名
-考著述 考著述
-考著稱 考著稱
-考著錄 考著錄
-考著書 考著書
-渴著作 渴著作
-渴著者 渴著者
-渴著名 渴著名
-渴著述 渴著述
-渴著稱 渴著稱
-渴著錄 渴著錄
-渴著書 渴著書
-刻著作 刻著作
-刻著者 刻著者
-刻著名 刻著名
-刻著述 刻著述
-刻著稱 刻著稱
-刻著錄 刻著錄
-刻著書 刻著書
-空著作 空著作
-空著者 空著者
-空著名 空著名
-空著述 空著述
-空著稱 空著稱
-空著錄 空著錄
-空著書 空著書
-哭著作 哭著作
-哭著者 哭著者
-哭著名 哭著名
-哭著述 哭著述
-哭著稱 哭著稱
-哭著錄 哭著錄
-哭著書 哭著書
-苦著作 苦著作
-苦著者 苦著者
-苦著名 苦著名
-苦著述 苦著述
-苦著稱 苦著稱
-苦著錄 苦著錄
-苦著書 苦著書
-捆著作 捆著作
-捆著者 捆著者
-捆著名 捆著名
-捆著述 捆著述
-捆著稱 捆著稱
-捆著錄 捆著錄
-困著作 困著作
-困著者 困著者
-困著名 困著名
-困著述 困著述
-困著稱 困著稱
-困著錄 困著錄
-困著書 困著書
-拉著作 拉著作
-拉著者 拉著者
-拉著名 拉著名
-拉著述 拉著述
-拉著稱 拉著稱
-拉著錄 拉著錄
-拉著書 拉著書
-來著作 來著作
-來著者 來著者
-來著名 來著名
-來著述 來著述
-來著稱 來著稱
-來著錄 來著錄
-來著書 來著書
-樂著作 樂著作
-樂著者 樂著者
-樂著名 樂著名
-樂著述 樂著述
-樂著稱 樂著稱
-樂著錄 樂著錄
-樂著書 樂著書
-努力著作 努力著作
-努力著者 努力著者
-努力著名 努力著名
-努力著述 努力著述
-努力著稱 努力著稱
-努力著錄 努力著錄
-努力著書 努力著書
-麗著作 麗著作
-麗著者 麗著者
-麗著名 麗著名
-麗著述 麗著述
-麗著稱 麗著稱
-麗著錄 麗著錄
-麗著書 麗著書
-連著作 連著作
-連著者 連著者
-連著名 連著名
-連著述 連著述
-連著稱 連著稱
-連著錄 連著錄
-連著書 連著書
-戀著作 戀著作
-戀著者 戀著者
-戀著名 戀著名
-戀著述 戀著述
-戀著稱 戀著稱
-戀著錄 戀著錄
-戀著書 戀著書
-涼著作 涼著作
-涼著者 涼著者
-涼著名 涼著名
-涼著述 涼著述
-涼著稱 涼著稱
-涼著錄 涼著錄
-涼著書 涼著書
-亮著作 亮著作
-亮著者 亮著者
-亮著名 亮著名
-亮著述 亮著述
-亮著稱 亮著稱
-亮著錄 亮著錄
-亮著書 亮著書
-臨著作 臨著作
-臨著者 臨著者
-臨著名 臨著名
-臨著述 臨著述
-臨著稱 臨著稱
-臨著錄 臨著錄
-臨著書 臨著書
-拎著作 拎著作
-拎著者 拎著者
-拎著名 拎著名
-拎著述 拎著述
-拎著稱 拎著稱
-拎著錄 拎著錄
-領著作 領著作
-領著者 領著者
-領著名 領著名
-領著述 領著述
-領著稱 領著稱
-領著錄 領著錄
-領著書 領著書
-流著作 流著作
-流著者 流著者
-流著名 流著名
-流著述 流著述
-流著稱 流著稱
-流著錄 流著錄
-流著書 流著書
-留著作 留著作
-留著者 留著者
-留著名 留著名
-留著述 留著述
-留著稱 留著稱
-留著錄 留著錄
-留著書 留著書
-摟著作 摟著作
-摟著者 摟著者
-摟著名 摟著名
-摟著述 摟著述
-摟著稱 摟著稱
-摟著錄 摟著錄
-陋著作 陋著作
-陋著者 陋著者
-陋著名 陋著名
-陋著述 陋著述
-陋著稱 陋著稱
-陋著錄 陋著錄
-陋著書 陋著書
-落著作 落著作
-落著者 落著者
-落著名 落著名
-落著述 落著述
-落著稱 落著稱
-落著錄 落著錄
-落著書 落著書
-罵著作 罵著作
-罵著者 罵著者
-罵著名 罵著名
-罵著述 罵著述
-罵著稱 罵著稱
-罵著錄 罵著錄
-罵著書 罵著書
-瞞著作 瞞著作
-瞞著者 瞞著者
-瞞著名 瞞著名
-瞞著述 瞞著述
-瞞著稱 瞞著稱
-瞞著錄 瞞著錄
-瞞著書 瞞著書
-漫著作 漫著作
-漫著者 漫著者
-漫著名 漫著名
-漫著述 漫著述
-漫著稱 漫著稱
-漫著錄 漫著錄
-漫著書 漫著書
-忙著作 忙著作
-忙著者 忙著者
-忙著名 忙著名
-忙著述 忙著述
-忙著稱 忙著稱
-忙著錄 忙著錄
-忙著書 忙著書
-冒著作 冒著作
-冒著者 冒著者
-冒著名 冒著名
-冒著述 冒著述
-冒著稱 冒著稱
-冒著錄 冒著錄
-冒著書 冒著書
-美著作 美著作
-美著者 美著者
-美著名 美著名
-美著述 美著述
-美著稱 美著稱
-美著錄 美著錄
-美著書 美著書
-夢著作 夢著作
-夢著者 夢著者
-夢著名 夢著名
-夢著述 夢著述
-夢著稱 夢著稱
-夢著錄 夢著錄
-夢著書 夢著書
-蒙著作 蒙著作
-蒙著者 蒙著者
-蒙著名 蒙著名
-蒙著述 蒙著述
-蒙著稱 蒙著稱
-蒙著錄 蒙著錄
-蒙著書 蒙著書
-拿著作 拿著作
-拿著者 拿著者
-拿著名 拿著名
-拿著述 拿著述
-拿著稱 拿著稱
-拿著錄 拿著錄
-逆著作 逆著作
-逆著者 逆著者
-逆著名 逆著名
-逆著述 逆著述
-逆著稱 逆著稱
-逆著錄 逆著錄
-逆著書 逆著書
-釀著作 釀著作
-釀著者 釀著者
-釀著名 釀著名
-釀著述 釀著述
-釀著稱 釀著稱
-釀著錄 釀著錄
-釀著書 釀著書
-努著作 努著作
-努著者 努著者
-努著名 努著名
-努著述 努著述
-努著稱 努著稱
-努著錄 努著錄
-努著書 努著書
-趴著作 趴著作
-趴著者 趴著者
-趴著名 趴著名
-趴著述 趴著述
-趴著稱 趴著稱
-趴著錄 趴著錄
-趴著書 趴著書
-跑著作 跑著作
-跑著者 跑著者
-跑著名 跑著名
-跑著述 跑著述
-跑著稱 跑著稱
-跑著錄 跑著錄
-跑著書 跑著書
-陪著作 陪著作
-陪著者 陪著者
-陪著名 陪著名
-陪著述 陪著述
-陪著稱 陪著稱
-陪著錄 陪著錄
-陪著書 陪著書
-配著作 配著作
-配著者 配著者
-配著名 配著名
-配著述 配著述
-配著稱 配著稱
-配著錄 配著錄
-配著書 配著書
-披著作 披著作
-披著者 披著者
-披著名 披著名
-披著述 披著述
-披著稱 披著稱
-披著錄 披著錄
-披著書 披著書
-騙著作 騙著作
-騙著者 騙著者
-騙著名 騙著名
-騙著述 騙著述
-騙著稱 騙著稱
-騙著錄 騙著錄
-騙著書 騙著書
-飄著作 飄著作
-飄著者 飄著者
-飄著名 飄著名
-飄著述 飄著述
-飄著稱 飄著稱
-飄著錄 飄著錄
-飄著書 飄著書
-拼著作 拼著作
-拼著者 拼著者
-拼著名 拼著名
-拼著述 拼著述
-拼著稱 拼著稱
-拼著錄 拼著錄
-鋪著作 鋪著作
-鋪著者 鋪著者
-鋪著名 鋪著名
-鋪著述 鋪著述
-鋪著稱 鋪著稱
-鋪著錄 鋪著錄
-鋪著書 鋪著書
-騎著作 騎著作
-騎著者 騎著者
-騎著名 騎著名
-騎著述 騎著述
-騎著稱 騎著稱
-騎著錄 騎著錄
-騎著書 騎著書
-牽著作 牽著作
-牽著者 牽著者
-牽著名 牽著名
-牽著述 牽著述
-牽著稱 牽著稱
-牽著錄 牽著錄
-牽著書 牽著書
-求著作 求著作
-求著者 求著者
-求著名 求著名
-求著述 求著述
-求著稱 求著稱
-求著錄 求著錄
-求著書 求著書
-去著作 去著作
-去著者 去著者
-去著名 去著名
-去著述 去著述
-去著稱 去著稱
-去著錄 去著錄
-去著書 去著書
-嚷著作 嚷著作
-嚷著者 嚷著者
-嚷著名 嚷著名
-嚷著述 嚷著述
-嚷著稱 嚷著稱
-嚷著錄 嚷著錄
-嚷著書 嚷著書
-繞著作 繞著作
-繞著者 繞著者
-繞著名 繞著名
-繞著述 繞著述
-繞著稱 繞著稱
-繞著錄 繞著錄
-繞著書 繞著書
-忍著作 忍著作
-忍著者 忍著者
-忍著名 忍著名
-忍著述 忍著述
-忍著稱 忍著稱
-忍著錄 忍著錄
-忍著書 忍著書
-揉著作 揉著作
-揉著者 揉著者
-揉著名 揉著名
-揉著述 揉著述
-揉著稱 揉著稱
-揉著錄 揉著錄
-揉著書 揉著書
-潤著作 潤著作
-潤著者 潤著者
-潤著名 潤著名
-潤著述 潤著述
-潤著稱 潤著稱
-潤著錄 潤著錄
-潤著書 潤著書
-燒著作 燒著作
-燒著者 燒著者
-燒著名 燒著名
-燒著述 燒著述
-燒著稱 燒著稱
-燒著錄 燒著錄
-燒著書 燒著書
-身著作 身著作
-身著者 身著者
-身著名 身著名
-身著述 身著述
-身著稱 身著稱
-身著錄 身著錄
-身著書 身著書
-沉著作 沉著作
-沉著者 沉著者
-沉著名 沉著名
-沉著述 沉著述
-沉著稱 沉著稱
-沉著錄 沉著錄
-沉著書 沉著書
-盛著作 盛著作
-盛著者 盛著者
-盛著名 盛著名
-盛著述 盛著述
-盛著稱 盛著稱
-盛著錄 盛著錄
-盛著書 盛著書
-試著作 試著作
-試著者 試著者
-試著名 試著名
-試著述 試著述
-試著稱 試著稱
-試著錄 試著錄
-試著書 試著書
-守著作 守著作
-守著者 守著者
-守著名 守著名
-守著述 守著述
-守著稱 守著稱
-守著錄 守著錄
-守著書 守著書
-受著作 受著作
-受著者 受著者
-受著名 受著名
-受著述 受著述
-受著稱 受著稱
-受著錄 受著錄
-受著書 受著書
-梳著作 梳著作
-梳著者 梳著者
-梳著名 梳著名
-梳著述 梳著述
-梳著稱 梳著稱
-梳著錄 梳著錄
-豎著作 豎著作
-豎著者 豎著者
-豎著名 豎著名
-豎著述 豎著述
-豎著稱 豎著稱
-豎著錄 豎著錄
-豎著書 豎著書
-數著作 數著作
-數著者 數著者
-數著名 數著名
-數著述 數著述
-數著稱 數著稱
-數著錄 數著錄
-睡著作 睡著作
-睡著者 睡著者
-睡著名 睡著名
-睡著述 睡著述
-睡著稱 睡著稱
-睡著錄 睡著錄
-睡著書 睡著書
-順著作 順著作
-順著者 順著者
-順著名 順著名
-順著述 順著述
-順著稱 順著稱
-順著錄 順著錄
-順著書 順著書
-隨著作 隨著作
-隨著者 隨著者
-隨著名 隨著名
-隨著述 隨著述
-隨著稱 隨著稱
-隨著錄 隨著錄
-隨著書 隨著書
-踏著作 踏著作
-踏著者 踏著者
-踏著名 踏著名
-踏著述 踏著述
-踏著稱 踏著稱
-踏著錄 踏著錄
-抬著作 抬著作
-抬著者 抬著者
-抬著名 抬著名
-抬著述 抬著述
-抬著稱 抬著稱
-抬著錄 抬著錄
-躺著作 躺著作
-躺著者 躺著者
-躺著名 躺著名
-躺著述 躺著述
-躺著稱 躺著稱
-躺著錄 躺著錄
-躺著書 躺著書
-提著作 提著作
-提著者 提著者
-提著名 提著名
-提著述 提著述
-提著稱 提著稱
-提著錄 提著錄
-甜著作 甜著作
-甜著者 甜著者
-甜著名 甜著名
-甜著述 甜著述
-甜著稱 甜著稱
-甜著錄 甜著錄
-甜著書 甜著書
-挑著作 挑著作
-挑著者 挑著者
-挑著名 挑著名
-挑著述 挑著述
-挑著稱 挑著稱
-挑著錄 挑著錄
-跳著作 跳著作
-跳著者 跳著者
-跳著名 跳著名
-跳著述 跳著述
-跳著稱 跳著稱
-跳著錄 跳著錄
-跳著書 跳著書
-聽著作 聽著作
-聽著者 聽著者
-聽著名 聽著名
-聽著述 聽著述
-聽著稱 聽著稱
-聽著錄 聽著錄
-聽著書 聽著書
-偷著作 偷著作
-偷著者 偷著者
-偷著名 偷著名
-偷著述 偷著述
-偷著稱 偷著稱
-偷著錄 偷著錄
-偷著書 偷著書
-拖著作 拖著作
-拖著者 拖著者
-拖著名 拖著名
-拖著述 拖著述
-拖著稱 拖著稱
-拖著錄 拖著錄
-望著作 望著作
-望著者 望著者
-望著名 望著名
-望著述 望著述
-望著稱 望著稱
-望著錄 望著錄
-望著書 望著書
-圍著作 圍著作
-圍著者 圍著者
-圍著名 圍著名
-圍著述 圍著述
-圍著稱 圍著稱
-圍著錄 圍著錄
-圍著書 圍著書
-味著作 味著作
-味著者 味著者
-味著名 味著名
-味著述 味著述
-味著稱 味著稱
-味著錄 味著錄
-味著書 味著書
-想著作 想著作
-想著者 想著者
-想著名 想著名
-想著述 想著述
-想著稱 想著稱
-想著錄 想著錄
-想著書 想著書
-響著作 響著作
-響著者 響著者
-響著名 響著名
-響著述 響著述
-響著稱 響著稱
-響著錄 響著錄
-響著書 響著書
-向著作 向著作
-向著者 向著者
-向著名 向著名
-向著述 向著述
-向著稱 向著稱
-向著錄 向著錄
-向著書 向著書
-笑著作 笑著作
-笑著者 笑著者
-笑著名 笑著名
-笑著述 笑著述
-笑著稱 笑著稱
-笑著錄 笑著錄
-笑著書 笑著書
-心著作 心著作
-心著者 心著者
-心著名 心著名
-心著述 心著述
-心著稱 心著稱
-心著錄 心著錄
-心著書 心著書
-信著作 信著作
-信著者 信著者
-信著名 信著名
-信著述 信著述
-信著稱 信著稱
-信著錄 信著錄
-信著書 信著書
-行著作 行著作
-行著者 行著者
-行著名 行著名
-行著述 行著述
-行著稱 行著稱
-行著錄 行著錄
-行著書 行著書
-性著作 性著作
-性著者 性著者
-性著名 性著名
-性著述 性著述
-性著稱 性著稱
-性著錄 性著錄
-性著書 性著書
-學著作 學著作
-學著者 學著者
-學著名 學著名
-學著述 學著述
-學著稱 學著稱
-學著錄 學著錄
-學著書 學著書
-尋著作 尋著作
-尋著者 尋著者
-尋著名 尋著名
-尋著述 尋著述
-尋著稱 尋著稱
-尋著錄 尋著錄
-尋著書 尋著書
-循著作 循著作
-循著者 循著者
-循著名 循著名
-循著述 循著述
-循著稱 循著稱
-循著錄 循著錄
-循著書 循著書
-壓著作 壓著作
-壓著者 壓著者
-壓著名 壓著名
-壓著述 壓著述
-壓著稱 壓著稱
-壓著錄 壓著錄
-壓著書 壓著書
-雅著作 雅著作
-雅著者 雅著者
-雅著名 雅著名
-雅著述 雅著述
-雅著稱 雅著稱
-雅著錄 雅著錄
-雅著書 雅著書
-沿著作 沿著作
-沿著者 沿著者
-沿著名 沿著名
-沿著述 沿著述
-沿著稱 沿著稱
-沿著錄 沿著錄
-沿著書 沿著書
-耀著作 耀著作
-耀著者 耀著者
-耀著名 耀著名
-耀著述 耀著述
-耀著稱 耀著稱
-耀著錄 耀著錄
-耀著書 耀著書
-掖著作 掖著作
-掖著者 掖著者
-掖著名 掖著名
-掖著述 掖著述
-掖著稱 掖著稱
-掖著錄 掖著錄
-衣著作 衣著作
-衣著者 衣著者
-衣著名 衣著名
-衣著述 衣著述
-衣著稱 衣著稱
-衣著錄 衣著錄
-衣著書 衣著書
-疑著作 疑著作
-疑著者 疑著者
-疑著名 疑著名
-疑著述 疑著述
-疑著稱 疑著稱
-疑著錄 疑著錄
-疑著書 疑著書
-溢著作 溢著作
-溢著者 溢著者
-溢著名 溢著名
-溢著述 溢著述
-溢著稱 溢著稱
-溢著錄 溢著錄
-溢著書 溢著書
-藝著作 藝著作
-藝著者 藝著者
-藝著名 藝著名
-藝著述 藝著述
-藝著稱 藝著稱
-藝著錄 藝著錄
-藝著書 藝著書
-因著作 因著作
-因著者 因著者
-因著名 因著名
-因著述 因著述
-因著稱 因著稱
-因著錄 因著錄
-因著書 因著書
-印著作 印著作
-印著者 印著者
-印著名 印著名
-印著述 印著述
-印著稱 印著稱
-印著錄 印著錄
-印著書 印著書
-應著作 應著作
-應著者 應著者
-應著名 應著名
-應著述 應著述
-應著稱 應著稱
-應著錄 應著錄
-應著書 應著書
-映著作 映著作
-映著者 映著者
-映著名 映著名
-映著述 映著述
-映著稱 映著稱
-映著錄 映著錄
-映著書 映著書
-用著作 用著作
-用著者 用著者
-用著名 用著名
-用著述 用著述
-用著稱 用著稱
-用著錄 用著錄
-用著書 用著書
-悠著作 悠著作
-悠著者 悠著者
-悠著名 悠著名
-悠著述 悠著述
-悠著稱 悠著稱
-悠著錄 悠著錄
-悠著書 悠著書
-有著作 有著作
-有著者 有著者
-有著名 有著名
-有著述 有著述
-有著稱 有著稱
-有著錄 有著錄
-有著書 有著書
-與著作 與著作
-與著者 與著者
-與著名 與著名
-與著述 與著述
-與著稱 與著稱
-與著錄 與著錄
-與著書 與著書
-語著作 語著作
-語著者 語著者
-語著名 語著名
-語著述 語著述
-語著稱 語著稱
-語著錄 語著錄
-語著書 語著書
-豫著作 豫著作
-豫著者 豫著者
-豫著名 豫著名
-豫著述 豫著述
-豫著稱 豫著稱
-豫著錄 豫著錄
-豫著書 豫著書
-遠著作 遠著作
-遠著者 遠著者
-遠著名 遠著名
-遠著述 遠著述
-遠著稱 遠著稱
-遠著錄 遠著錄
-遠著書 遠著書
-躍著作 躍著作
-躍著者 躍著者
-躍著名 躍著名
-躍著述 躍著述
-躍著稱 躍著稱
-躍著錄 躍著錄
-躍著書 躍著書
-雜著作 雜著作
-雜著者 雜著者
-雜著名 雜著名
-雜著述 雜著述
-雜著稱 雜著稱
-雜著錄 雜著錄
-雜著書 雜著書
-載著作 載著作
-載著者 載著者
-載著名 載著名
-載著述 載著述
-載著稱 載著稱
-載著錄 載著錄
-載著書 載著書
-在著作 在著作
-在著者 在著者
-在著名 在著名
-在著述 在著述
-在著稱 在著稱
-在著錄 在著錄
-在著書 在著書
-紮著作 紮著作
-紮著者 紮著者
-紮著名 紮著名
-紮著述 紮著述
-紮著稱 紮著稱
-紮著錄 紮著錄
-紮著書 紮著書
-展著作 展著作
-展著者 展著者
-展著名 展著名
-展著述 展著述
-展著稱 展著稱
-展著錄 展著錄
-展著書 展著書
-站著作 站著作
-站著者 站著者
-站著名 站著名
-站著述 站著述
-站著稱 站著稱
-站著錄 站著錄
-站著書 站著書
-戰著作 戰著作
-戰著者 戰著者
-戰著名 戰著名
-戰著述 戰著述
-戰著稱 戰著稱
-戰著錄 戰著錄
-戰著書 戰著書
-蘸著作 蘸著作
-蘸著者 蘸著者
-蘸著名 蘸著名
-蘸著述 蘸著述
-蘸著稱 蘸著稱
-蘸著錄 蘸著錄
-蘸著書 蘸著書
-仗著作 仗著作
-仗著者 仗著者
-仗著名 仗著名
-仗著述 仗著述
-仗著稱 仗著稱
-仗著錄 仗著錄
-仗著書 仗著書
-照著作 照著作
-照著者 照著者
-照著名 照著名
-照著述 照著述
-照著稱 照著稱
-照著錄 照著錄
-照著書 照著書
-罩著作 罩著作
-罩著者 罩著者
-罩著名 罩著名
-罩著述 罩著述
-罩著稱 罩著稱
-罩著錄 罩著錄
-罩著書 罩著書
-貞著作 貞著作
-貞著者 貞著者
-貞著名 貞著名
-貞著述 貞著述
-貞著稱 貞著稱
-貞著錄 貞著錄
-貞著書 貞著書
-枕著作 枕著作
-枕著者 枕著者
-枕著名 枕著名
-枕著述 枕著述
-枕著稱 枕著稱
-枕著錄 枕著錄
-爭著作 爭著作
-爭著者 爭著者
-爭著名 爭著名
-爭著述 爭著述
-爭著稱 爭著稱
-爭著錄 爭著錄
-爭著書 爭著書
-掙著作 掙著作
-掙著者 掙著者
-掙著名 掙著名
-掙著述 掙著述
-掙著稱 掙著稱
-掙著錄 掙著錄
-掙著書 掙著書
-制著作 制著作
-制著者 制著者
-制著名 制著名
-制著述 制著述
-制著稱 制著稱
-制著錄 制著錄
-制著書 制著書
-志著作 志著作
-志著者 志著者
-志著名 志著名
-志著述 志著述
-志著稱 志著稱
-志著錄 志著錄
-志著書 志著書
-皺著作 皺著作
-皺著者 皺著者
-皺著名 皺著名
-皺著述 皺著述
-皺著稱 皺著稱
-皺著錄 皺著錄
-皺著書 皺著書
-住著作 住著作
-住著者 住著者
-住著名 住著名
-住著述 住著述
-住著稱 住著稱
-住著錄 住著錄
-住著書 住著書
-抓著作 抓著作
-抓著者 抓著者
-抓著名 抓著名
-抓著述 抓著述
-抓著稱 抓著稱
-抓著錄 抓著錄
-轉著作 轉著作
-轉著者 轉著者
-轉著名 轉著名
-轉著述 轉著述
-轉著稱 轉著稱
-轉著錄 轉著錄
-轉著書 轉著書
-裝著作 裝著作
-裝著者 裝著者
-裝著名 裝著名
-裝著述 裝著述
-裝著稱 裝著稱
-裝著錄 裝著錄
-裝著書 裝著書
-追著作 追著作
-追著者 追著者
-追著名 追著名
-追著述 追著述
-追著稱 追著稱
-追著錄 追著錄
-追著書 追著書
-髭著作 髭著作
-髭著者 髭著者
-髭著名 髭著名
-髭著述 髭著述
-髭著稱 髭著稱
-髭著錄 髭著錄
-髭著書 髭著書
-走著作 走著作
-走著者 走著者
-走著名 走著名
-走著述 走著述
-走著稱 走著稱
-走著錄 走著錄
-走著書 走著書
-坐著作 坐著作
-坐著者 坐著者
-坐著名 坐著名
-坐著述 坐著述
-坐著稱 坐著稱
-坐著錄 坐著錄
-坐著書 坐著書
-做著作 做著作
-做著者 做著者
-做著名 做著名
-做著述 做著述
-做著稱 做著稱
-做著錄 做著錄
-做著書 做著書
-含著作 含著作
-含著者 含著者
-含著名 含著名
-含著述 含著述
-含著稱 含著稱
-含著錄 含著錄
-含著書 含著書
-涵著作 涵著作
-涵著者 涵著者
-涵著名 涵著名
-涵著述 涵著述
-涵著稱 涵著稱
-涵著錄 涵著錄
-涵著書 涵著書
-演著作 演著作
-演著者 演著者
-演著名 演著名
-演著述 演著述
-演著稱 演著稱
-演著錄 演著錄
-演著書 演著書
-保障著作 保障著作
-保障著者 保障著者
-保障著名 保障著名
-保障著述 保障著述
-保障著稱 保障著稱
-保障著錄 保障著錄
-保障著書 保障著書
-黏著作 黏著作
-黏著者 黏著者
-黏著名 黏著名
-黏著述 黏著述
-黏著稱 黏著稱
-黏著錄 黏著錄
-黏著書 黏著書
-膠著作 膠著作
-膠著者 膠著者
-膠著名 膠著名
-膠著述 膠著述
-膠著稱 膠著稱
-膠著錄 膠著錄
-膠著書 膠著書
-附著作 附著作
-附著者 附著者
-附著名 附著名
-附著述 附著述
-附著稱 附著稱
-附著錄 附著錄
-附著書 附著書
-代表著作 代表著作
-代表著者 代表著者
-代表著名 代表著名
-代表著述 代表著述
-代表著稱 代表著稱
-代表著錄 代表著錄
-代表著書 代表著書
-浮著作 浮著作
-浮著者 浮著者
-浮著名 浮著名
-浮著述 浮著述
-浮著稱 浮著稱
-浮著錄 浮著錄
-浮著書 浮著書
-寫著作 寫著作
-寫著者 寫著者
-寫著名 寫著名
-寫著述 寫著述
-寫著稱 寫著稱
-寫著錄 寫著錄
-寫著書 寫著書
-遇著作 遇著作
-遇著者 遇著者
-遇著名 遇著名
-遇著述 遇著述
-遇著稱 遇著稱
-遇著錄 遇著錄
-遇著書 遇著書
-殺著作 殺著作
-殺著者 殺著者
-殺著名 殺著名
-殺著述 殺著述
-殺著稱 殺著稱
-殺著錄 殺著錄
-殺著書 殺著書
-標誌著 標誌着
-幹著 幹着
-干着 幹着
-干着急 干着急
-流露著 流露着
-靠著 靠着
-靠著作 靠著作
-靠著名 靠著名
-靠著錄 靠著錄
-靠著录 靠著錄
-靠著稱 靠著稱
-靠著称 靠著稱
-靠著者 靠著者
-靠著述 靠著述
-新著龍虎門 新著龍虎門
-迫著 迫着
-心繫著 心繫着
-藉著 藉着
-吃得著 吃得着
-吃不著 吃不着
-吃著 吃着
-聞得著 闻得着
-聞不著 闻不着
-聞著 闻着
-嗅得著 嗅得着
-嗅不著 嗅不着
-嗅著 嗅着
-警戒著 警戒着
-榴莲 榴槤
-榴蓮 榴槤
-发布 發佈
-發布 發佈
-掛鉤 掛鈎
-鉤心鬥角 鈎心鬥角
-咤 咤
-叱吒 叱咤
-叱咤 叱咤
-醯 酰
-醯醬 醯醬
-醯雞 醯雞
-醯酱 醯醬
-醯鸡 醯雞
-醯醋 醯醋
-醯醢 醯醢
-醯壶 醯壺
-醯壺 醯壺
-菸 煙
-雪裡紅 雪裏紅
-雪裡蕻 雪裏蕻
-雪里蕻 雪裏蕻
-雪里红 雪裏紅
-森林裡 森林裏
-森林里 森林裏
-日子裡 日子裏
-日子里 日子裏
-故事裡 故事裏
-故事里 故事裏
-領域裡 領域裏
-领域里 領域裏
-時間裡 時間裏
-时间里 時間裏
-深淵裡 深淵裏
-深渊里 深渊裏
-醫院裡 醫院裏
-医院里 医院裏
-春假裡 春假裏
-春假里 春假裏
-暑假裡 暑假裏
-暑假里 暑假裏
-秋假裡 秋假裏
-秋假里 秋假裏
-寒假裡 寒假裏
-寒假里 寒假裏
-春天裡 春天裏
-春天里 春天裏
-夏天裡 夏天裏
-夏天里 夏天裏
-秋天裡 秋天裏
-秋天里 秋天裏
-冬天裡 冬天裏
-冬天里 冬天裏
-春日裡 春日裏
-夏日裡 夏日裏
-秋日裡 秋日裏
-冬日裡 冬日裏
-春日里 春日裏
-夏日里 夏日裏
-秋日里 秋日裏
-冬日里 冬日裏
-嘴裡 嘴裏
-嘴里 嘴裏
-心裡 心裏
-心里 心裏
-皮裡陽秋 皮裏陽秋
-皮里阳秋 皮裏陽秋
-肚裡 肚裏
-肚里 肚裏
-苦裡 苦裏
-苦里 苦裏
-裡勾外連 裏勾外連
-里勾外连 裏勾外連
-裡面 裏面
-里面 裏面
-這裡 這裏
-這里 這裏
-點裡 點裏
-点里 點裏
-中文裡 中文裏
-中文里 中文裏
-山洞里 山洞裏
-山洞裡 山洞裏
-近角聪信 近角聰信
-近角聰信 近角聰信
-世界里 世界裏
-世界裡 世界裏
-眼睛里 眼睛裏
-眼睛裡 眼睛裏
-百科裡 百科裏
-百科里 百科裏
-歷史裡 歷史裏
-历史里 歷史裏
-戲裡 戲裏
-戏里 戲裏
-作品裡 作品裏
-作品里 作品裏
-專輯裡 專輯裏
-专辑里 專輯裏
-年代裡 年代裏
-年代里 年代裏
-棺材裡 棺材裏
-棺材里 棺材裏
-學裡 學裏
-学里 學裏
-獄裡 獄裏
-狱里 獄裏
-館裡 館裏
-馆里 館裏
-系列裡 系列裏
-系列里 系列裏
-村子裡 村子裏
-村子里 村子裏
-分布 分佈
-分布于 分佈於
-分布於 分佈於
-想象 想像
-無線電視 無綫電視
-无线电视 無綫電視
-無線收費 無綫收費
-无线收费 無綫收費
-無線節目 無綫節目
-无线节目 無綫節目
-無線劇集 無綫劇集
-无线剧集 無綫劇集
-東鐵線 東鐵綫
-东铁线 東鐵綫
-觀塘線 觀塘綫
-观塘线 觀塘綫
-荃灣線 荃灣綫
-荃湾线 荃灣綫
-港島線 港島綫
-港岛线 港島綫
-東涌線 東涌綫
-东涌线 東涌綫
-將軍澳線 將軍澳綫
-将军澳线 將軍澳綫
-西鐵線 西鐵綫
-西铁线 西鐵綫
-馬鞍山線 馬鞍山綫
-马鞍山线 馬鞍山綫
-迪士尼線 迪士尼綫
-迪士尼线 迪士尼綫
-沙田至中環線 沙田至中環綫
-沙田至中环线 沙田至中環綫
-沙中線 沙中綫
-沙中线 沙中綫
-北環線 北環綫
-北环线 北環綫
-機場快線 機場快綫
-机场快线 機場快綫
-505線 505綫
-505线 505綫
-507線 507綫
-507线 507綫
-610線 610綫
-610线 610綫
-614線 614綫
-614线 614綫
-614P線 614P綫
-614P线 614P綫
-615線 615綫
-615线 615綫
-615P線 615P綫
-615P线 615P綫
-705線 705綫
-705线 705綫
-706線 706綫
-706线 706綫
-751線 751綫
-751线 751綫
-751P線 751P綫
-751P线 751P綫
-761P線 761P綫
-761P线 761P綫
diff --git a/includes/zhtable/toSG.manual b/includes/zhtable/toSG.manual
deleted file mode 100644
index 2d39aa35..00000000
--- a/includes/zhtable/toSG.manual
+++ /dev/null
@@ -1,21 +0,0 @@
-」 ”
-「 “
-『 ‘
-』 ’
-方便面 快速面
-速食麵 快速面
-即食麵 快速面
-泡麵 快速面
-蹦极跳 绑紧跳
-笨豬跳 绑紧跳
-凉菜 冷菜
-冷盤 冷菜
-零钱 散钱
-散紙 散钱
-笑星 谐星
-夜校 夜学
-民乐 华乐
-住房 住屋
-房价 屋价
-榴莲 榴梿
-榴蓮 榴梿 \ No newline at end of file
diff --git a/includes/zhtable/toSimp.manual b/includes/zhtable/toSimp.manual
deleted file mode 100644
index 739d04c3..00000000
--- a/includes/zhtable/toSimp.manual
+++ /dev/null
@@ -1,165 +0,0 @@
-乾县 乾县
-萧乾 萧乾
-乾断 乾断
-乾图 乾图
-乾纲 乾纲
-乾红 乾红
-乾清宫 乾清宫
-乾仪 乾仪
-乾兴 乾兴
-乾冈 乾冈
-乾刘 乾刘
-乾刚 乾刚
-乾启 乾启
-乾宁 乾宁
-乾岗 乾岗
-乾录 乾录
-乾晖 乾晖
-乾构 乾构
-乾枢 乾枢
-乾栋 乾栋
-乾灵 乾灵
-乾窦 乾窦
-乾笃 乾笃
-乾纽 乾纽
-乾络 乾络
-乾统 乾统
-乾维 乾维
-乾罗 乾罗
-乾荫 乾荫
-乾象历 乾象历
-乾贞 乾贞
-乾贶 乾贶
-乾车 乾车
-乾轴 乾轴
-乾鉴 乾鉴
-乾钧 乾钧
-乾闼 乾闼
-乾顾 乾顾
-乾风 乾风
-乾马 乾马
-乾鹄 乾鹄
-乾鹊 乾鹊
-乾龙 乾龙
-张法乾 张法乾
-旋乾转坤 旋乾转坤
-天道为乾 天道为乾
-易经·乾 易经·乾
-易经乾 易经乾
-乾务 乾务
-黄润乾 黄润乾
-男性为乾 男性为乾
-男为乾 男为乾
-阳为乾 阳为乾
-男性为乾 男性为乾
-男性爲乾 男性为乾
-男为乾 男为乾
-男爲乾 男为乾
-阳为乾 阳为乾
-陽爲乾 阳为乾
-乾一组 乾一组
-乾一坛 乾一坛
-陈乾生 陈乾生
-陈公乾生 陈公乾生
-柳诒徵 柳诒徵
-於夫罗 於夫罗
-於梨华 於梨华
-於潜县 於潜县
-於志贺 於志贺
-憑藉 凭借
-藉端 借端
-藉故 借故
-藉口 借口
-藉助 借助
-藉手 借手
-藉詞 借词
-藉機 借机
-藉此 借此
-藉由 借由
-藉著 借着
-藉着 借着
-沈積 沉积
-沈船 沉船
-沈默 沉默
-沈沒 沉没
-彷彿 仿佛
-項鍊 项链
-肘手鍊足 肘手链足
-鍊子 链子
-鍊條 链条
-拉鍊 拉链
-鉸鍊 铰链
-鍊鎖 链锁
-鎖鍊 锁链
-鐵鍊 铁链
-金鍊 金链
-銀鍊 银链
-鍊錘 链锤
-洗鍊 洗练
-石碁镇 石碁镇
-反覆 反复
-回覆 回复
-答覆 答复
-反反覆覆 反反复复
-重覆 重复
-覆核 复核
-覆查 复查
-鬱姓 鬱姓
-鬱氏 鬱氏
-侏儸紀 侏罗纪
-夥計 伙计
-吳其濬 吴其濬
-吴其濬 吴其濬
-乾泉水 干泉水
-么半群 幺半群
-么元 幺元
-么爹 幺爹
-么叔 幺叔
-么舅 幺舅
-么爸 幺爸
-么媽 幺妈
-么姨 幺姨
-么娘 幺娘
-么孃 幺娘
-幺孃 幺娘
-么妹 幺妹
-么小 幺小
-么姓 幺姓
-么氏 幺氏
-么蛾子 幺蛾子
-幺厮 幺厮
-睪丸 睾丸
-附睪 附睾
-隱睪 隱睾
-麼麼 麽麽
-么麼 幺麽
-么麼小丑 幺麽小丑
-么鳳 幺凤
-么二三 幺二三
-么篇 幺篇
-么謙 幺谦
-麴义 麴义
-乾乾淨淨 干干净净
-乾乾脆脆 干干脆脆
-肉乾乾 肉干干
-魚乾乾 鱼干干
-於于同 於于同
-於乙于同 於乙于同
-閻懷禮 闫怀礼
-醯酱 醯酱
-醯鸡 醯鸡
-醯壶 醯壶
-苧烯 苧烯
-李乾顺 李乾顺
-幹著 干着
-氾濫 泛滥
-显著 显著
-顯著 显著
-標誌著 标志着
-近角聪信 近角聪信
-修鍊 修炼
-米泽瑠美 米泽瑠美
-太閤 太阁
-候覆 候复
-待覆 待复
-批覆 批复
diff --git a/includes/zhtable/toTW.manual b/includes/zhtable/toTW.manual
deleted file mode 100644
index 1a14e99a..00000000
--- a/includes/zhtable/toTW.manual
+++ /dev/null
@@ -1,411 +0,0 @@
-” 」
-“ 「
-‘ 『
-’ 』
-着 著
-鈎 鉤
-钩 鉤
-衞 衛
-元凶 元凶
-元兇 元凶
-凶器 凶器
-兇器 凶器
-凶徒 凶徒
-兇徒 凶徒
-凶手 凶手
-兇手 凶手
-凶案 凶案
-兇案 凶案
-凶残 凶殘
-凶殘 凶殘
-兇殘 凶殘
-凶杀 凶殺
-凶殺 凶殺
-兇殺 凶殺
-疑凶 疑凶
-疑兇 疑凶
-真凶 真凶
-真兇 真凶
-缉凶 緝凶
-緝凶 緝凶
-緝兇 緝凶
-行凶 行凶
-行兇 行凶
-行凶后 行凶後
-行凶後 行凶後
-行兇後 行凶後
-买凶 買凶
-買凶 買凶
-買兇 買凶
-追凶 追凶
-追兇 追凶
-逞凶斗狠 逞凶鬥狠
-逞凶鬥狠 逞凶鬥狠
-逞兇鬥狠 逞凶鬥狠
-复苏 復甦
-復蘇 復甦
-缺省 預設
-串行 串列
-串列加速器 串列加速器
-以太网 乙太網
-位图 點陣圖
-例程 常式
-光标 游標
-光盘 光碟
-光驱 光碟機
-全角 全形
-加载 載入
-半角 半形
-变量 變數
-噪声 雜訊
-脱机 離線
-声卡 音效卡
-老字号 老字號
-连字号 連字號
-字号 字型大小
-字库 字型檔
-字段 欄位
-字符 字元
-字符集 字符集
-存盘 存檔
-寻址 定址
-尾注 章節附註
-异步 非同步
-总线 匯流排
-括号 括弧
-接口 介面
-控件 控制項
-权限 許可權
-盘片 碟片
-硅片 矽片
-硅谷 矽谷
-硬盘 硬碟
-磁盘 磁碟
-磁道 磁軌
-程控 程式控制
-远程控制 遠程控制
-遠程控制 遠程控制
-行程控制 行程控制
-流程控制 流程控制
-端口 埠
-算子 運算元
-算法 演算法
-芯片 晶片
-芯片 晶元
-词组 片語
-译码 解碼
-软驱 軟碟機
-快闪存储器 快閃記憶體
-闪存 快閃記憶體
-鼠标 滑鼠
-进制 進位
-交互式 互動式
-仿真 模擬
-优先级 優先順序
-传感 感測
-便携式 攜帶型
-信息论 資訊理論
-写保护 防寫
-分辨率 解析度
-服务器 伺服器
-等于 等於
-局域网 區域網
-扫瞄仪 掃瞄器
-宽带 寬頻
-数据库 資料庫
-奶酪 乳酪
-手电 手電筒
-手电筒 手電筒
-万历 萬曆
-永历 永曆
-词汇 辭彙
-习用 慣用
-元音 母音
-新纪元 新紀元
-新紀元 新紀元
-宋元 宋元
-头球 頭槌
-入球 進球
-粒入球 顆進球
-打门 射門
-火锅盖帽 蓋火鍋
-打印机 印表機
-打印機 印表機
-字节 位元組
-字節 位元組
-打印 列印
-打印 列印
-硬件 硬體
-硬件 硬體
-二极管 二極體
-二極管 二極體
-三极管 三極體
-三極管 三極體
-软件 軟體
-軟件 軟體
-网络 網路
-網絡 網路
-人工智能 人工智慧
-航天飞机 太空梭
-航天大学 航天大學
-穿梭機 太空梭
-因特网 網際網路
-互聯網 網際網路
-机器人 機器人
-機械人 機器人
-移动电话 行動電話
-流動電話 行動電話
-调制解调器 數據機
-調制解調器 數據機
-短信 簡訊
-短訊 簡訊
-乌兹别克斯坦 烏茲別克
-乍得 查德
-乍得 查德
-也门 葉門
-也門 葉門
-伯利兹 貝里斯
-伯利茲 貝里斯
-佛得角 維德角
-克罗地亚 克羅埃西亞
-克羅地亞 克羅埃西亞
-冈比亚 甘比亞
-岡比亞 甘比亞
-几内亚比绍 幾內亞比索
-幾內亞比紹 幾內亞比索
-列支敦士登 列支敦斯登
-列支敦士登 列支敦斯登
-利比里亚 賴比瑞亞
-利比里亞 賴比瑞亞
-加纳 迦納
-加納 迦納
-加蓬 加彭
-加蓬 加彭
-博茨瓦纳 波札那
-博茨瓦納 波札那
-卡塔尔 卡達
-卡塔爾 卡達
-卢旺达 盧安達
-盧旺達 盧安達
-危地马拉 瓜地馬拉
-危地馬拉 瓜地馬拉
-厄瓜多尔 厄瓜多
-厄瓜多爾 厄瓜多
-厄立特里亚 厄利垂亞
-厄立特里亞 厄利垂亞
-吉布提 吉布地
-吉布堤 吉布地
-哈萨克斯坦 哈薩克
-哥斯达黎加 哥斯大黎加
-哥斯達黎加 哥斯大黎加
-图瓦卢 吐瓦魯
-圖瓦盧 吐瓦魯
-土库曼斯坦 土庫曼
-圣卢西亚 聖露西亞
-聖盧西亞 聖露西亞
-圣基茨和尼维斯 聖克里斯多福及尼維斯
-聖吉斯納域斯 聖克里斯多福及尼維斯
-圣文森特和格林纳丁斯 聖文森及格瑞那丁
-聖文森特和格林納丁斯 聖文森及格瑞那丁
-圣马力诺 聖馬利諾
-聖馬力諾 聖馬利諾
-圭亚那 蓋亞那
-圭亞那 蓋亞那
-坦桑尼亚 坦尚尼亞
-坦桑尼亞 坦尚尼亞
-埃塞俄比亚 衣索比亞
-埃塞俄比亞 衣索比亞
-基里巴斯 吉里巴斯
-基里巴斯 吉里巴斯
-塔吉克斯坦 塔吉克
-塞拉利昂 獅子山
-塞拉利昂 獅子山
-塞浦路斯 塞普勒斯
-塞浦路斯 塞普勒斯
-塞舌尔 塞席爾
-塞舌爾 塞席爾
-多米尼加共和国 多明尼加
-多米尼加共和國 多明尼加
-多明尼加共和國 多明尼加
-多米尼加国 多米尼克
-多明尼加國 多米尼克
-安提瓜和巴布达 安地卡及巴布達
-安提瓜和巴布達 安地卡及巴布達
-尼日利亚 奈及利亞
-尼日利亞 奈及利亞
-尼日尔 尼日
-尼日爾 尼日
-巴巴多斯 巴貝多
-巴布亚新几内亚 巴布亞紐幾內亞
-巴布亞新畿內亞 巴布亞紐幾內亞
-布基纳法索 布吉納法索
-布基納法索 布吉納法索
-布隆迪 蒲隆地
-布隆迪 蒲隆地
-帕劳 帛琉
-意大利 義大利
-所罗门群岛 索羅門群島
-所羅門群島 索羅門群島
-文莱 汶萊
-斯威士兰 史瓦濟蘭
-斯威士蘭 史瓦濟蘭
-斯洛文尼亚 斯洛維尼亞
-斯洛文尼亞 斯洛維尼亞
-新西兰 紐西蘭
-新西蘭 紐西蘭
-格林纳达 格瑞那達
-格林納達 格瑞那達
-格鲁吉亚 喬治亞
-格魯吉亞 喬治亞
-佐治亚 喬治亞
-佐治亞 喬治亞
-毛里塔尼亚 茅利塔尼亞
-毛里塔尼亞 茅利塔尼亞
-毛里求斯 模里西斯
-毛里裘斯 模里西斯
-沙特阿拉伯 沙烏地阿拉伯
-沙地阿拉伯 沙烏地阿拉伯
-波斯尼亚和黑塞哥维那 波士尼亞赫塞哥維納
-波斯尼亞黑塞哥維那 波士尼亞赫塞哥維納
-津巴布韦 辛巴威
-津巴布韋 辛巴威
-洪都拉斯 宏都拉斯
-洪都拉斯 宏都拉斯
-特立尼达和托巴哥 千里達托貝哥
-特立尼達和多巴哥 千里達托貝哥
-瑙鲁 諾魯
-瑙魯 諾魯
-瓦努阿图 萬那杜
-瓦努阿圖 萬那杜
-溫納圖萬 那杜
-科摩罗 葛摩
-科摩羅 葛摩
-科特迪瓦 象牙海岸
-突尼斯 突尼西亞
-索马里 索馬利亞
-索馬里 索馬利亞
-老挝 寮國
-老撾 寮國
-肯尼亚 肯亞
-肯雅 肯亞
-苏里南 蘇利南
-莫桑比克 莫三比克
-莱索托 賴索托
-萊索托 賴索托
-贝宁 貝南
-貝寧 貝南
-赞比亚 尚比亞
-贊比亞 尚比亞
-阿塞拜疆 亞塞拜然
-阿拉伯联合酋长国 阿拉伯聯合大公國
-阿拉伯聯合酋長國 阿拉伯聯合大公國
-马尔代夫 馬爾地夫
-馬爾代夫 馬爾地夫
-马耳他 馬爾他
-马里共和国 馬利共和國
-馬里共和國 馬利共和國
-方便面 速食麵
-快速面 速食麵
-即食麵 速食麵
-薯仔 土豆
-土豆网 土豆網
-土豆網 土豆網
-蹦极跳 笨豬跳
-绑紧跳 笨豬跳
-冷菜 冷盤
-凉菜 冷盤
-出租车 計程車
-台球 撞球
-桌球 撞球
-卫生 衛生
-衞生 衛生
-平治之亂 平治之亂
-平治之乱 平治之亂
-平治 賓士
-奔驰 賓士
-積架 捷豹
-雪铁龙 雪鐵龍
-萬事得 馬自達
-拿破仑 拿破崙
-拿破侖 拿破崙
-布什 布希
-布殊 布希
-克林顿 柯林頓
-克林頓 柯林頓
-侯赛因 海珊
-侯賽因 海珊
-凡高 梵谷
-狄安娜 黛安娜
-戴安娜 黛安娜
-颁布 頒布
-頒佈 頒布
-彩带 彩帶
-彩排 彩排
-彩楼 彩樓
-彩牌楼 彩牌樓
-彩球 綵球
-彩绸 綵綢
-彩线 綵線
-彩船 綵船
-彩衣 綵衣
-结彩 結綵
-戏彩娱亲 戲綵娛親
-剪彩 剪綵
-榴莲 榴槤
-榴蓮 榴槤
-掛鈎 掛鉤
-挂钩 掛鉤
-鈎心鬥角 鉤心鬥角
-钩心斗角 鉤心鬥角
-酰 醯
-雪裏紅 雪裡紅
-雪裏蕻 雪裡蕻
-森林裏 森林裡
-日子裏 日子裡
-故事裏 故事裡
-領域裏 領域裡
-時間裏 時間裡
-深淵裏 深淵裡
-醫院裏 醫院裡
-春假裏 春假裡
-暑假裏 暑假裡
-秋假裏 秋假裡
-寒假裏 寒假裡
-春天裏 春天裡
-夏天裏 夏天裡
-秋天裏 秋天裡
-冬天裏 冬天裡
-春日裏 春日裡
-夏日裏 夏日裡
-秋日裏 秋日裡
-冬日裏 冬日裡
-百科裏 百科裡
-歷史裏 歷史裡
-戲裏 戲裡
-作品裏 作品裡
-專輯裏 專輯裡
-年代裏 年代裡
-棺材裏 棺材裡
-嘴裏 嘴裡
-心裏 心裡
-皮裏陽秋 皮裡陽秋
-肚裏 肚裡
-苦裏 苦裡
-裏勾外連 裡勾外連
-裏面 裡面
-這裏 這裡
-點裏 點裡
-中文裏 中文裡
-山洞裏 山洞裡
-世界裏 世界裡
-眼睛裏 眼睛裡
-學裏 學裡
-獄裏 獄裡
-館裏 館裡
-系列裏 系列裡
-村子裏 村子裡
-青霉素 青黴素
-想象 想像
-锎 鉲
-信道 信道
-綫 線
diff --git a/includes/zhtable/toTrad.manual b/includes/zhtable/toTrad.manual
deleted file mode 100644
index b0efd28e..00000000
--- a/includes/zhtable/toTrad.manual
+++ /dev/null
@@ -1,186 +0,0 @@
-手塚治虫 手塚治虫
-校仇 校讎
-仇校 讎校
-仇夷 讎夷
-仇問 讎問
-無言不仇 無言不讎
-視如寇仇 視如寇讎
-往日無仇 往日無讎
-近日無仇 近日無讎
-李連杰 李連杰
-周杰倫 周杰倫
-寶曆 寶曆
-涂謹申 涂謹申
-涂鴻欽 涂鴻欽
-涂壯勳 涂壯勳
-於姓 於姓
-於氏 於氏
-於夫羅 於夫羅
-於梨華 於梨華
-鄭凱云 鄭凱云
-筑陽 筑陽
-筑後 筑後
-采石磯 采石磯
-采石之戰 采石之戰
-張三丰 張三丰
-丰韻 丰韻
-丰儀 丰儀
-丰標不凡 丰標不凡
-干細胞 幹細胞
-干熱 乾熱
-二里頭 二里頭
-水里鄉 水里鄉
-蒙胧 朦朧
-酒曲 酒麴
-呆里呆气 呆裡呆氣
-拜托 拜託
-委托书 委託書
-委托 委託
-挽詞 輓詞
-挽聯 輓聯
-挽詩 輓詩
-於夫罗 於夫羅
-府干預 府干預
-府干擾 府干擾
-分布圖 分布圖
-頁面 頁面
-面條目 面條目
-黃鈺筑 黃鈺筑
-仿佛 彷彿
-凶殘 兇殘
-凶殺 兇殺
-緝凶 緝兇
-行凶後 行兇後
-買凶 買兇
-逞凶鬥狠 逞兇鬥狠
-合著者 合著者
-答复 答覆
-反复 反覆
-索馬里 索馬里
-洗练 洗鍊
-朝乾夕惕 朝乾夕惕
-乾象曆 乾象曆
-乾象历 乾象曆
-不好干預 不好干預
-不干預 不干預
-不干擾 不干擾
-不干牠 不干牠
-矽谷 矽谷
-范文瀾 范文瀾
-發表 發表
-機械系 機械系
-頂多 頂多
-馬占山 馬占山
-叱咤樂壇 叱咤樂壇
-闫怀礼 閆懷禮
-变髒 變髒
-薴烯 薴烯
-后豐 后豐
-于謙 于謙
-詩云 詩云
-鄭凱云 鄭凱云
-云為 云為
-古書云 古書云
-古語云 古語云
-經有云 經有云
-語有云 語有云
-显著标志 顯著標志
-占領 佔領
-采納 採納
-風采 風采
-于樂 于樂
-于軍 于軍
-于堅 于堅
-于帥 于帥
-于濤 于濤
-于贈 于贈
-于會泳 于會泳
-于偉國 于偉國
-于光遠 于光遠
-于鳳至 于鳳至
-于台煙 于台煙
-于國楨 于國楨
-于大寶 于大寶
-于學忠 于學忠
-于小偉 于小偉
-于山國 于山國
-于幼軍 于幼軍
-于廣洲 于廣洲
-于從濂 于從濂
-于志寧 于志寧
-于成龍 于成龍
-于明濤 于明濤
-于根偉 于根偉
-于樹潔 于樹潔
-于正昇 于正昇
-于漢超 于漢超
-于洪區 于洪區
-于湘蘭 于湘蘭
-于蔭霖 于蔭霖
-于遠偉 于遠偉
-于都縣 于都縣
-于震寰 于震寰
-于震環 于震環
-于非闇 于非闇
-于風政 于風政
-于鳳桐 于鳳桐
-于默奧 于默奧
-于爾岑 于爾岑
-于默奧 于默奧
-于貝爾 于貝爾
-于爾根 于爾根
-于雙戈 于雙戈
-于澤爾 于澤爾
-于斯達爾 于斯達爾
-于爾里克 于爾里克
-于奇庫杜克 于奇庫杜克
-于韋斯屈萊 于韋斯屈萊
-于克-蘭多縣 于克-蘭多縣
-于斯納爾斯貝里 于斯納爾斯貝里
-夏于喬 夏于喬
-涂澤民 涂澤民
-涂長望 涂長望
-涂敏恆 涂敏恆
-台历 枱曆
-艷后 艷后
-廢后 廢后
-后髮座 后髮座
-后髮星系團 后髮星系團
-后髮FK型星 后髮FK型星
-后海灣 后海灣
-賈后 賈后
-賢后 賢后
-呂后 呂后
-蟻后 蟻后
-馬格里布 馬格里布
-佳里鎮 佳里鎮
-埔裡社撫墾局 埔裏社撫墾局
-埔裏社撫墾局 埔裏社撫墾局
-有只採 有只採
-任何表達 任何表達
-會干擾 會干擾
-党項 党項
-余三勝 余三勝
-簡筑翎 簡筑翎
-楊雅筑 楊雅筑
-杰威爾音樂 杰威爾音樂
-尸羅精舍 尸羅精舍
-索馬里 索馬里
-騰格里 騰格里
-村里長 村里長
-進制 進制
-模范三軍 模范三軍
-黃詩杰 黃詩杰
-陳冲 陳冲
-劉佳怜 劉佳怜
-范賢惠 范賢惠
-于國治 于國治
-于楓 于楓
-黎吉雲 黎吉雲
-于飛島 于飛島
-鄉愿 鄉愿
-奇迹 奇蹟
-候复 候覆
-待复 待覆
-批复 批覆
-划槳 划槳
diff --git a/includes/zhtable/trad2simp.manual b/includes/zhtable/trad2simp.manual
deleted file mode 100644
index 7c3ce10d..00000000
--- a/includes/zhtable/trad2simp.manual
+++ /dev/null
@@ -1,150 +0,0 @@
-U+04E99亙|U+04E98亘|
-U+04F48佈|U+05E03布|
-U+04F48佈|U+05E03布|
-U+04F54佔|U+05360占|
-U+05016倖|U+05E78幸|
-U+050A2傢|U+05BB6家|
-U+050F1僱|U+096C7雇|
-U+05138儸|U+03469㑩|U+07F57罗|
-U+05147兇|U+051F6凶|
-U+05277剷|U+094F2铲|
-U+052F3勳|U+052CB勋|
-U+0537D卽|U+05373即|
-U+053A4厤|U+05386历|
-U+055AB喫|U+05403吃|
-U+05641噁|U+06076恶|
-U+05690嚐|U+05C1D尝|
-U+056A5嚥|U+054BD咽|
-U+056AE嚮|U+05411向|
-U+056CC囌|U+082CF苏|
-U+0585A塚|U+051A2冢|
-U+058B0墰|U+0575B坛|
-U+058DC壜|U+0575B坛|
-U+05925夥|U+04F19伙|
-U+05BC0寀|U+091C7采|
-U+05D11崑|U+06606昆|
-U+05D19崙|U+04ED1仑|
-U+05D57嵗|U+05C81岁|
-U+05DBD嶽|U+05CB3岳|
-U+05DD6巖|U+05CA9岩|
-U+05DF9巹|U+0537A卺|
-U+05F14弔|U+0540A吊|
-U+05F46彆|U+0522B别|
-U+0617C慼|U+0621A戚|
-U+0617E慾|U+06B32欲|
-U+061DE懞|U+08499蒙|
-U+062DA拚|U+062FC拼|
-U+06331挱|U+06332挲|
-U+06371捱|U+06328挨|
-U+06372捲|U+05377卷|
-U+0647A摺|U+06298折|
-U+065C2旂|U+065D7旗|
-U+065E3旣|U+065E2既|
-U+06607昇|U+05347升|
-U+0672E朮|U+0672F术|
-U+068CA棊|U+068CB棋|
-U+069A6榦|U+05E72干|
-U+069D3槓|U+06760杠|
-U+06A11樑|U+06881梁|
-U+06B05欅|U+06989榉|
-U+06B4E歎|U+053F9叹|
-U+06BAD殭|U+050F5僵|
-U+06C59汙|U+06C61污|
-U+06CDD泝|U+06EAF溯|
-U+06D29洩|U+06CC4泄|
-U+06DD2淒|U+051C4凄|
-U+06DE8淨|U+051C0净|
-U+06DE9淩|U+051CC凌|
-U+06E67湧|U+06D8C涌|
-U+06ED9滙|U+06C47汇|
-U+06F90澐|U+06C84沄|
-U+06FBE澾|U+03CE0㳠|
-U+06FDB濛|U+06FDB濛|U+08499蒙|
-U+07030瀰|U+05F25弥|
-U+071EC燬|U+06BC1毁|
-U+07343獃|U+05446呆|
-U+07515甕|U+074EE瓮|
-U+07526甦|U+082CF苏|
-U+0752F甯|U+05B81宁|
-U+0756B畫|U+0753B画|U+05212划|
-U+07575畵|U+0753B画|U+05212划|
-U+075E0痠|U+09178酸|
-U+07652癒|U+06108愈|
-U+07661癡|U+075F4痴|
-U+076C3盃|U+0676F杯|
-U+0771E眞|U+0771F真|
-U+077AD瞭|U+04E86了|
-U+077C7矇|U+08499蒙|
-U+07843硃|U+06731朱|
-U+07895碕|U+057FC埼|
-U+07958祘|U+07B97算|
-U+07A1C稜|U+068F1棱|
-U+07B87箇|U+04E2A个|
-U+07C11簑|U+084D1蓑|
-U+07C64籤|U+07B7E签|
-U+07C72籲|U+05401吁|
-U+07CF0糰|U+056E2团|
-U+07D2E紮|U+0624E扎|
-U+07DB5綵|U+05F69彩|U+0433D䌽|
-U+07E34縴|U+07EA4纤|
-U+07E50繐|U+07A57穗|
-U+07E94纔|U+0624D才|
-U+07F4E罎|U+0575B坛|
-U+07FA8羨|U+07FA1羡|
-U+08123脣|U+05507唇|
-U+081E5臥|U+05367卧|
-U+08218舘|U+09986馆|
-U+083F4菴|U+05EB5庵|
-U+08457著|U+08457著|U+07740着|
-U+08518蔘|U+053C2参|
-U+08591薑|U+059DC姜|
-U+085C9藉|U+085C9藉|U+0501F借|
-U+0880D蠍|U+0874E蝎|
-U+0884A衊|U+08511蔑|
-U+08946襆|U+05E5E幞|
-U+08986覆|U+08986覆|U+0590D复|
-U+08A17託|U+06258托|U+08BAC讬|
-U+08AEE諮|U+054A8咨|U+08C18谘|
-U+08B6D譭|U+06BC1毁|
-U+08B8E讎|U+04EC7仇|
-U+08B9A讚|U+08D5E赞|
-U+08C54豔|U+08273艳|
-U+08FF4迴|U+056DE回|
-U+09031週|U+05468周|
-U+0904A遊|U+06E38游|
-U+09061遡|U+06EAF溯|
-U+091A3醣|U+07CD6糖|
-U+091AF醯|U+09170酰|
-U+0934A鍊|U+070BC炼|U+094FE链|
-U+0938C鎌|U+09570镰|
-U+093AD鎭|U+093AE镇|
-U+093DA鏚|U+0621A戚|
-U+09451鑑|U+09274鉴|
-U+0955F镟|U+065CB旋|
-U+09592閒|U+095F2闲|
-U+095A4閤|U+05408合|
-U+095E2闢|U+08F9F辟|
-U+0962A阪|U+0962A阪|U+05742坂|
-U+0965E陞|U+05347升|
-U+097A6鞦|U+079CB秋|U+097A7鞧|
-U+097C6韆|U+05343千|
-U+097DD韝|U+097B2鞲|
-U+09858願|U+0613F愿|
-U+098F1飱|U+098E7飧|
-U+09918餘|U+04F59余|U+09980馀|
-U+09931餱|U+07CC7糇|
-U+09935餵|U+05582喂|
-U+09B28鬨|U+054C4哄|
-U+09D70鵰|U+096D5雕|U+05F6B彫|
-U+09E7C鹼|U+078B1碱|U+07877硷|
-U+09EAA麪|U+09762面|
-U+09EAB麫|U+09762面|
-U+09EAF麯|U+066F2曲|
-U+09EB4麴|U+066F2曲|U+09EB4麴|
-U+09EF4黴|U+09709霉|
-U+09F15鼕|U+051AC冬|
-U+09F47齇|U+09F44齄|
-U+09F63齣|U+051FA出|
-U+09F91龑|U+04DAE䶮|
-U+21ED5𡻕|U+05C81岁|
-U+298F5𩣵|U+299FB𩧻|
diff --git a/includes/zhtable/trad2simp_noconvert.manual b/includes/zhtable/trad2simp_noconvert.manual
deleted file mode 100644
index 052bab69..00000000
--- a/includes/zhtable/trad2simp_noconvert.manual
+++ /dev/null
@@ -1,5 +0,0 @@
-"余"=>
-碁
-藉
-=>"獃"
-𫚭
diff --git a/includes/zhtable/trad2simp_supp_set.manual b/includes/zhtable/trad2simp_supp_set.manual
deleted file mode 100644
index d1728f0a..00000000
--- a/includes/zhtable/trad2simp_supp_set.manual
+++ /dev/null
@@ -1,3 +0,0 @@
-著 着
-藉 借
-濛 蒙 \ No newline at end of file
diff --git a/includes/zhtable/tradphrases.manual b/includes/zhtable/tradphrases.manual
deleted file mode 100644
index 9a9534f8..00000000
--- a/includes/zhtable/tradphrases.manual
+++ /dev/null
@@ -1,4310 +0,0 @@
-零隻
-〇隻
-一隻
-二隻
-兩隻
-三隻
-四隻
-五隻
-六隻
-七隻
-八隻
-九隻
-0隻
-1隻
-2隻
-3隻
-4隻
-5隻
-6隻
-7隻
-8隻
-9隻
-0隻
-1隻
-2隻
-3隻
-4隻
-5隻
-6隻
-7隻
-8隻
-9隻
-0只支援
-1只支援
-2只支援
-3只支援
-4只支援
-5只支援
-6只支援
-7只支援
-8只支援
-9只支援
-0只支持
-1只支持
-2只支持
-3只支持
-4只支持
-5只支持
-6只支持
-7只支持
-8只支持
-9只支持
-百隻
-千隻
-萬隻
-億隻
-最多
-至多
-頂多
-多隻
-0多隻
-0多隻
-零多隻
-十多隻
-百多隻
-千多隻
-萬多隻
-億多隻
-這只能
-這只可
-這只在
-這只是
-這只需
-這只會
-這只用
-那只能
-那只可
-那只在
-那只是
-那只需
-那只會
-那只用
-多只能
-多只可
-多只在
-多只有
-多只是
-多只需
-多只會
-多只用
-大只能
-大只可
-大只在
-大只有
-大只是
-大只需
-大只會
-小只能
-小只可
-小只在
-小只有
-小只是
-小只需
-小只會
-隻身
-形單影隻
-首隻
-數天後
-幾天後
-多天後
-零天後
-一天後
-二天後
-兩天後
-三天後
-四天後
-五天後
-六天後
-七天後
-八天後
-九天後
-十天後
-百天後
-千天後
-萬天後
-億天後
-0天後
-1天後
-2天後
-3天後
-4天後
-5天後
-6天後
-7天後
-8天後
-9天後
-0天後
-1天後
-2天後
-3天後
-4天後
-5天後
-6天後
-7天後
-8天後
-9天後
-天後來
-天後天
-天後半
-後印
-萬象
-並存著
-乾絲
-乾著急
-乾魚
-魚乾
-乾梅
-糕乾
-黃乾黑瘦
-馬乾
-香乾
-趲幹
-謀幹
-詞幹
-蟶乾
-薄幹
-腦幹
-營幹
-老乾
-老幹部
-管幹
-盲幹
-煨乾
-海乾
-乾漆
-淚乾
-沒幹
-沒乾沒淨
-枝不得大於榦
-杯乾
-打幹
-打乾噦
-徐幹
-府幹
-乾館
-乾顙
-幹革命
-乾霍亂
-乾雷
-乾阿奶
-乾量
-乾醋
-乾逼
-乾貨
-乾衣
-幹蠱
-乾虔
-乾落
-幹營生
-乾茶錢
-乾茨臘
-乾苔
-乾花
-乾肥
-乾耗
-幹缺
-乾繃
-乾結
-乾餱
-乾篾片
-乾稿
-乾禮
-乾瞪眼
-乾白兒
-乾疥
-乾生子
-乾生受
-幹父之蠱
-乾熬
-乾燈盞
-乾濕
-乾澀
-幹濟
-乾沒
-乾死
-乾村沙
-乾暖
-乾料
-乾敲梆子不賣油
-乾支支
-乾支剌
-乾擦
-乾撇下
-乾撂台
-乾折
-乾急
-幹當
-乾式
-乾屎橛
-幹家
-乾奴才
-幹頭
-乾塢
-乾圓潔淨
-乾回付
-乾啼
-乾哭
-乾噦
-乾咽
-乾和
-幹吏
-乾吊著下巴
-乾號
-乾颱
-乾卦
-乾剝剝
-乾刻版
-乾芻
-幹人
-乾產
-乾喬
-夯幹
-大目乾連
-國之楨榦
-唇乾
-單幹
-勾幹
-豆乾
-果乾
-如果幹
-乾麵
-乾柴
-枯乾
-晒乾
-顛乾倒坤
-強幹
-乾著
-乾眼
-幹的停當
-乾巴
-偎乾
-眼乾
-偷雞不著
-几絲
-划著
-划著走
-別著
-刮著
-千絲萬縷
-參合
-參考價值
-參與
-參與人員
-參與制
-參與感
-參與者
-參觀團
-參觀團體
-參閱
-吃著不盡
-合著
-吊帶褲
-吊掛著
-吊著
-吊褲
-吊褲帶
-向著
-嚴絲合縫
-回絲
-回著
-塗著
-壟斷價格
-壟斷資產
-壟斷集團
-姜絲
-帶團參加
-干著急
-幾絲
-彆著
-怎麼著
-憑藉著
-憑藉
-接著說
-擔著
-擔負著
-敘說著
-斗轉參橫
-旋繞著
-板著臉
-正當著
-沈著
-沖著
-派團參加
-涂著
-湊合著
-瀰漫著
-為著
-煙斗絲
-率團參加
-畫著
-當著
-發著
-直接參与
-睡著了
-秋褲
-積极參与
-積极參加
-簽著
-系著
-絕對參照
-絲來線去
-絲布
-絲板
-絲瓜布
-絲絨布
-絲線
-絲織廠
-絲蟲
-緊繃著
-繃著
-繃著臉
-繃著臉兒
-繫著
-罵著
-肉絲麵
-背向著
-菌絲體
-著兒
-著書立說
-著色軟體
-著重指出
-著錄
-著錄規則
-薑絲
-藉著
-蘊含著
-蘊涵著
-衝著
-被覆著
-覆著
-覆蓋著
-反覆
-訴說著
-說著
-請參閱
-謝絕參觀
-豎著
-豐濱
-豐濱鄉
-豐度
-象徵著
-這麼著
-那麼著
-配合著
-醞釀著
-錄著
-鍛鍊出
-關係著
-雞絲
-雞絲麵
-面朝著
-面臨著
-颳著
-髮絲
-斷髮
-不斷發
-判斷發
-評斷發
-買斷發
-賣斷發
-打斷發
-披頭散髮
-髮禁
-鬥著
-鬧著玩兒
-鯰魚
-世界盃
-其次辟地
-開闢
-闢地
-精闢
-別闢
-另闢
-闢佛
-闢田
-闢築
-闢謠
-闢辟
-透闢
-墾闢
-翕闢
-軒闢
-闢建
-闢室
-各闢
-增闢
-闢邪以律
-錶盤
-錶板
-錶帶
-錶針
-錶蒙子
-袋錶
-腕錶
-碼錶
-錶冠
-魔錶
-彆口氣
-彆強
-皺彆
-一彆頭
-并州
-併兼
-併產
-併骨
-併網
-併線
-併流
-逼併
-併名
-併當
-併火
-併肩子
-併除
-併疊
-忙併
-打併
-簡併
-並發表
-並發現
-並發展
-並發動
-並發布
-火並非
-舉手表
-揮手表
-併一不二
-連三併四
-相併
-撤併
-數罪併罰
-催併
-狂併潮
-薝蔔
-提摩太後書
-當家纔知柴米價
-剛纔一載
-裏海
-骨頭裡掙出來的錢纔做得肉
-恰纔
-遠縣纔至
-別日南鴻纔北去
-然身死纔數月耳
-纔得兩年
-纔則
-纔此
-你纔子發昏
-纔可容顏十五餘
-不採
-披榛採蘭
-謬採虛聲
-採樵人
-回採
-觀採
-開採
-揪採
-樵採
-採訪
-採辦
-採補
-採買
-採風問俗
-採納
-採獵
-採蓮
-採錄
-採購
-採光
-採礦
-採花
-採集
-採擷
-採掘
-採芹人
-採取
-採選
-採摭
-採摘
-採珠
-採種
-採茶
-採石
-採拾
-採收
-採生折割
-採樹種
-採擇
-採藥
-採薇
-採用
-盜採
-採信
-採行
-採證
-採菊
-博採
-採空採穗
-採挖
-採鐵
-採金
-採氣
-採油
-採煤
-採鹽
-採區
-採運
-採風
-官地為寀
-寮寀
-蔘綏
-個人# “個人參數”不是“個人蔘數”
-人蔘
-蕭蔘
-人參與
-人參選
-人參觀
-人參考
-人參展
-人參加
-人參議
-人參謀
-人參酌
-人參照
-人參政
-人參戰
-人參拜
-人參閱
-人參禪
-人參贊
-人參見
-人參透
-人參看
-東衝西突
-天克地衝
-六衝
-撞陣衝軍
-衝波
-衝風
-衝頭陣
-衝堅陷陣
-衝陷
-衝心
-衝州撞府
-衝殺
-衝然
-衝盹
-左衝右突
-虫部
-手塚治虫
-群醜
-百拙千醜
-大醜
-地醜德齊
-丟醜
-亮醜
-揭醜
-倛醜
-嫌好道醜
-醜巴怪
-醜末
-醜婦
-醜地
-醜頭怪臉
-醜女效顰
-醜剌剌
-醜話
-醜媳
-醜吒
-醜聲遠播
-醜夷
-弄醜
-露醜
-摧堅獲醜
-謷醜
-不嫌母醜
-一爭兩醜
-惡直醜正
-很醜
-醜男
-醜斃了
-醜奴兒
-醜言
-醜徒
-醜雜
-醜儕
-醜沮
-醜辭
-醜比
-醜辱
-醜逆
-醜史
-醜賊生
-醜婆子
-出乖弄醜
-出乖露醜
-獲匪其醜
-乙丑
-丁丑
-己丑
-辛丑
-癸丑
-丑時
-丑日
-丑月
-丑年
-文丑
-武丑
-女丑
-小丑
-大丑
-丑婆子
-丑旦
-丑角
-丑三
-丑表功
-公孫丑
-么麼小丑
-齣電影
-齣電視
-齣動畫
-齣節目
-齣卡通
-齣戲
-齣劇
-平平當當
-滿滿當當
-當當丁丁
-丁丁當當
-停停當當
-快快當當
-咯噹
-啷噹
-党參
-党進
-党太尉
-党項
-撲鼕
-洗髮
-牽一髮
-白發其事
-后髮座
-后髮星系團
-后髮FK型星
-波髮藻
-辮髮
-逋髮
-抿髮
-髮漂
-髮匪
-髮腳
-髮癬
-髮釵
-髮飾
-髮紗
-髮上指冠
-髮上沖冠
-髮乳
-髮引千鈞
-髮踴沖冠
-董氏封髮
-胎髮
-禿妃之髮
-捉髮
-綠髮
-括髮
-髡髮
-鵠髮
-截髮
-解髮佯狂
-淨髮
-秋髮
-噙齒戴髮
-青山一髮
-晞髮
-細不容髮
-心細如髮
-祝髮
-擢髮
-齒髮
-齒危髮秀
-沖冠髮怒
-甩髮
-絲髮
-絲恩髮怨
-蒜髮
-算髮
-有髮頭陀寺
-髮箋
-髮屋
-櫛髮工
-鬒髮
-模范棒棒堂
-模范三軍
-模范七棒
-模范14棒
-模范21棒
-顏範
-儀範
-典範
-坤範
-壼範
-容範
-懿範
-明範
-格範
-模範
-樣範
-母範
-洪範
-淑範
-遺範
-科範
-立範
-貽範
-道範
-閨範
-閫範
-雅範
-霽範
-鴻範
-沒樣範
-錢範
-銅範
-金範
-範金
-垂範
-範性形變
-範字
-有事之無範
-置言成範
-吾爲之範我馳驅
-天地為範
-範數
-丰采
-丰標不凡
-丰神
-丰茸
-丰儀
-丰度
-丰情
-丰韵
-子之丰兮
-艸木丰丰
-張三丰
-復始
-複分析
-複輔音
-複元音
-複平面
-複函數
-複流
-反複製
-複對數
-顛覆
-答覆
-覆沒
-覆亡
-覆水難收
-翻雲覆雨
-覆雨翻雲
-覆轍
-覆巢之下無完卵
-覆蓋
-覆命
-天翻地覆
-天覆地載
-撥穀
-扁擬穀盜蟲
-不穀
-辟穀
-米穀
-田穀
-脫穀機
-年穀
-礱穀機
-孤寡不穀
-穀米
-穀旦
-穀圭
-穀貴餓農
-穀食
-穀日
-館穀
-禾穀
-積穀
-嘉穀
-嚼穀
-九穀
-戩穀
-錢穀
-息穀
-殖穀
-川穀
-曬穀
-臧穀亡羊
-種穀
-颳雪
-刮風下雪倒便宜
-广部
-亂鬨不過來
-斗鬨
-亂鬨
-開鬨
-花鬨
-鬨動
-交鬨
-喧鬨
-起鬨
-內鬨
-於後
-猜三划五
-划龍舟
-南迴線
-南迴鐵路
-北迴線
-北迴鐵路
-文匯報
-河流匯集
-品彙
-博彙
-滙豐
-伙頭
-方几
-伏几
-高几
-雪窗螢几
-燕几
-隱几
-饑饉
-乾薑
-毛薑
-薑母
-薑湯
-薑桂
-薑是老的辣
-吃薑
-薑老辣
-野薑
-咬薑呷醋
-薑蓉
-薑黃
-狐藉虎威
-滑藉
-藉寇兵
-藉箸代籌
-藉手
-藉此
-龍捲
-捲舌
-夸父
-夸克
-夸特
-夸毗
-夸麗
-夸姣
-夸人
-夸容
-大言非夸
-言大而夸
-睏覺
-愛睏
-纍堆
-纍紲
-纍臣
-纍瓦結繩
-湘纍
-印纍綬若
-灕湘
-灕然
-澤滲灕而下降
-裏勾外連
-裏手
-水里鄉
-水里溪
-水里濁水溪
-二里頭
-年歷史
-西歷史
-國歷史
-國歷代
-國歷任
-國歷屆
-國歷經
-國歷來
-新歷史
-夏歷史
-百花曆
-寶曆
-穆罕默德曆
-大明曆
-大曆
-台曆
-太初曆
-通曆
-曆本
-曆命
-曆紀
-曆始
-曆室
-曆日
-曆尾
-曆元
-律曆志
-官曆
-回曆
-巧曆
-慶曆
-朱理安曆
-長曆
-藏曆
-四分曆
-三統曆
-額我略曆
-埃及曆
-伊斯蘭教曆
-合曆
-玉曆
-農民曆
-桌曆
-商曆
-周曆
-大衍曆
-皇極曆
-儒略改革曆
-希伯來曆
-格里曆
-格里高利曆
-共和曆
-掛曆
-曆獄
-天文曆表
-日心曆表
-地心曆表
-復活節曆表
-月球曆表
-伊爾汗曆表
-延曆
-共和歷史
-厤物之意
-爰定祥厤
-白黴
-黴黧
-黴黑
-麴黴
-蒙霧露
-懞懞懂懂
-懞直
-老懞
-放懞掙
-矇著
-矇聵
-矇瞍
-矇事
-矇頭轉
-矇松雨
-藏矇歌兒
-矇著鍋兒
-朦朧
-濛濛細雨
-濛汜
-冥濛
-溟濛
-淡濛濛
-凌濛初
-涳濛
-灰濛濛
-澒濛
-瀰山遍野
-瀰瀰
-冷麵
-撈麵
-煮麵
-炆麵
-煎麵
-泡麵
-食麵
-公仔麵
-方便麵
-白粉麵
-棒子麵
-麵缸
-麵坯兒
-麵碼兒
-麵坊
-麵湯
-麵疙瘩
-麵館
-麵漿
-甜水麵
-麵人兒
-麵塑
-捏麵人
-趕麵棍
-擀麵
-過水麵
-蕎麥麵
-巧婦做不得無麵餺飥
-削麵
-小米麵
-壯麵
-吃板刀麵
-吃辣麵
-扯麵
-搋麵
-重羅麵
-雜麵
-雜合麵兒
-溲麵
-索麵
-一鍋麵
-伊府麵
-藥麵兒
-意大利麵
-湯下麵
-茶麵
-麵糰
-冷面相
-糞穢衊面
-湟潦生苹
-食野之苹
-苹縈
-青苹
-青蘋果
-僕僕
-有僕
-冉有僕
-屢顧爾僕
-僕少
-僕雖罷駑
-僕夫
-僕僮
-僕吏
-僕姑
-僕固懷恩
-僕程
-僕使
-僕憎
-僕歐
-僕射
-太僕
-僮僕
-金僕姑
-僕婢
-樸實
-樸訥
-樸念仁
-白樸
-抱素懷樸
-抱朴而長吟兮
-樸鄙
-樸馬
-樸父
-樸陋
-樸魯
-樸厚
-樸學
-樸質
-樸拙
-樸重
-樸素
-樸樕
-樸野
-反樸
-古樸
-胡樸安
-返樸
-渾樸
-儉樸
-簡樸
-拙樸
-斫雕為樸
-斲雕為樸
-質樸
-誠樸
-純樸
-曾樸
-郁樸
-棫樸
-敦樸
-樸鈍
-樸直
-見素抱樸
-掣籤
-標籤
-書籤
-發籤
-粉籤子
-路籤
-更籤
-好籤
-火籤
-籤幐
-籤押
-照入籤
-制籤
-抽公籤
-瑤籤
-藥籤
-萬籤插架
-雲笈七籤
-上簽名
-上簽字
-上簽收
-上簽寫
-下簽名
-下簽字
-下簽收
-下簽寫
-犖确
-磽确
-确瘠
-言辯而确
-數與虜确
-關弓與我确
-拚捨
-廣捨
-齊王捨牛
-捨墮
-捨實
-棄捨
-捨安就危
-施舍之道
-瀋河
-瀋水
-瀋州
-瀋山線
-瀋吉線
-墨沈
-瀋海鐵路
-遼瀋
-胜肽
-胜鍵
-雙胜類
-兀朮
-白朮
-蒼朮
-赤朮
-朮赤
-髼鬆
-皮鬆
-濛鬆雨
-發鬆
-翻鬆
-浮鬆
-弄鬆
-精鬆
-懈鬆
-鬆蛋
-鬆寬
-鬆氣
-鬆一口氣
-鬆元音
-鬆喉
-囉囉囌囌
-囉囌
-骨罈
-罈騞
-餵驢
-剪牡丹喂牛
-鹹粥
-鹹食
-鹹潟
-鹹嘴淡舌
-鹽打怎麼鹹
-鹹派
-鹹批
-錦綉花園
-籲天
-勃鬱
-怫鬱
-氣鬱
-沉鬱
-神荼鬱壘
-躁鬱
-蒼鬱
-漚鬱
-伊鬱
-壹鬱
-悒鬱
-氤鬱
-湮鬱
-陰鬱
-泱鬱
-坱鬱
-滃鬱
-蓊鬱
-紆鬱
-鬱勃
-鬱陶
-鬱律
-鬱壘
-鬱火
-鬱積
-鬱金
-鬱江
-鬱血
-鬱蒸
-鬱症
-鬱沉沉
-鬱熱
-鬱塞
-鬱伊
-鬱邑
-鬱挹
-鬱堙不偶
-鬱泱
-鬱蓊
-鬱紆
-鬱燠
-肝鬱
-鬱卒
-鬱鬱不平
-鬱鬱不樂
-鬱鬱寡歡
-鬱鬱蔥蔥
-鬱鬱而終
-愿樸
-愿而恭
-許愿起經
-北嶽
-嶽麓
-但云
-胡云
-詩云
-注云
-鄭凱云
-云乎
-云然
-云為
-對摺
-網誌
-標標致致
-澄澹精致
-呆緻緻
-光緻緻
-工緻
-功緻
-縝緻
-堅緻
-种放
-种師道
-种師中
-後庄
-舊庄
-正官庄
-龜山庄
-寶山庄
-冬山庄
-員山庄
-松山庄
-厂部
-閤府
-佈道
-剪綵
-衝量
-衝車
-書獃子
-相干
-府干預
-府干涉
-府干政
-府干擾
-府干犯
-府干卿
-一干人
-未乾
-未干涉
-抹乾
-餅乾
-拭乾
-擦乾
-晾乾
-烘乾
-肉乾
-菜乾
-腐乾
-乾脆
-乾淨
-乾燥
-乾旱
-乾涸
-乾洗
-乾女
-乾等
-乾糧
-乾枯
-乾薪
-乾爹
-乾粉
-乾爽
-乾兒
-乾子
-乾渴
-乾股
-乾果
-乾草
-乾菜
-乾笑
-乾餾
-乾電
-乾飯
-乾冰
-乾嘔
-乾材
-乾媽
-乾季
-葡萄乾
-提子乾
-蘿蔔乾
-蘋果乾
-芒果乾
-菠蘿乾
-鳳梨乾
-豆腐乾
-果子乾
-龍眼乾
-乾乾淨淨
-乾柴烈火
-乾乾兒的
-桑乾
-撈乾
-搭乾鋪
-揩乾
-敢幹
-幹探
-幹事
-幹什麼
-幹細胞
-悶著頭兒幹
-配水幹管
-繐幃飄井幹
-站乾岸兒
-秋陰入井幹
-沒梢幹
-楨幹
-據榦而窺井底
-井榦摧敗
-杰特
-李連杰
-周杰倫
-杰倫
-姜文杰
-稜鏡
-稜角
-稜台
-稜錐
-觚稜
-稜子
-稜層
-稜柱
-盧稜伽
-波稜菜
-菠稜菜
-稜縫
-稜等登
-稜稜
-嶒稜
-蹭稜子
-稜體
-二不稜登
-有稜有角
-威稜
-負債纍纍
-傷痕纍纍
-儒略曆
-伊斯蘭曆
-酒麴
-昇平
-爾冬陞
-澹臺
-拜託
-委託
-輓曲
-敬輓
-万俟
-万旗
-鬚鯨
-鬚鯊
-兇手
-兇徒
-兇案
-兇器
-兇殺
-兇殘
-行兇
-緝兇
-追兇
-真兇
-疑兇
-買兇
-元兇
-叶韻
-叶音
-叶恭弘
-叶 恭弘
-叶 恭弘
-於1
-於2
-於3
-於4
-於5
-於6
-於7
-於8
-於9
-於0
-於1
-於2
-於3
-於4
-於5
-於6
-於7
-於8
-於9
-於0
-於一
-於二
-於三
-於四
-於五
-於六
-於七
-於八
-於九
-於十
-於半
-於夫羅
-於梨華
-置於
-佈於
-散於
-播於
-國於
-敗於
-於一役
-畢於
-畢業於
-寒於
-任於
-拘於
-插於
-中於
-於市
-於野
-敏於
-聽於
-短於
-成於
-樊於期
-淡於
-於陸
-於密
-於盡
-禍於
-格於
-猛於
-施於
-於牆
-於物
-於己
-於你
-於我
-於他
-於她
-於它
-於祂
-拒人於
-拒於
-潰於
-窮於
-相於
-形於
-半於
-於始
-於終
-詢於
-美於
-醜於
-好於
-坏於
-強於
-弱於
-差於
-劣於
-於美
-於醜
-於好
-於坏
-於強
-於弱
-於差
-於劣
-於垂
-染指於
-於火
-存十一於千百
-存於
-於勤
-隱於
-藏於
-嚴於
-寬於
-於幕
-給於
-於穆
-於呼哀哉
-於時
-於該
-危於
-於伏
-於何
-於家
-於國
-於潛縣
-於焉
-於徵
-離於
-於畢
-麗於
-下於
-亞於
-同於
-屑於
-絕於
-致於
-於行
-遜於
-任教於
-教於
-自於
-來於
-附於
-於人
-於世
-阻於
-於民
-於盲
-於色
-囿於
-直於
-建於
-都於
-於農
-於樂
-於前
-役於
-於心
-於法
-於事
-助於
-害於
-損於
-益於
-從於
-隨於
-順於
-汲於
-溺於
-迷於
-醉於
-行於
-泥於
-身於
-足於
-溢於
-於衷
-畏於
-視於
-衷於
-狃於
-疲於
-通於
-於途
-老於
-耿於
-於懷
-服於
-臻於
-匿於
-因於
-似於
-遷於
-怒於
-心於
-集於
-容於
-髒詞
-髒心
-新紮
-紙紮
-紮鐵
-紮寨
-一紮
-兩紮
-三紮
-四紮
-五紮
-六紮
-七紮
-八紮
-九紮
-十紮
-百紮
-千紮
-萬紮
-佔1
-佔2
-佔3
-佔4
-佔5
-佔6
-佔7
-佔8
-佔9
-佔0
-佔1
-佔2
-佔3
-佔4
-佔5
-佔6
-佔7
-佔8
-佔9
-佔0
-佔零
-佔〇
-佔一
-佔二
-佔兩
-佔三
-佔四
-佔五
-佔六
-佔七
-佔八
-佔九
-佔十
-佔百
-佔千
-佔万
-佔億
-佔超過
-佔不足
-佔至少
-佔少
-佔至多
-佔半
-佔多
-佔大
-佔小
-佔中
-佔東
-佔西
-佔南
-佔北
-佔平均
-佔總
-獨佔鰲頭
-所佔
-市佔
-佔率
-市佔率
-佔市場
-佔世界
-佔全
-佔國內
-佔美
-佔台
-佔香
-佔澳
-佔加
-佔新
-佔馬
-佔印
-佔英
-佔法
-佔德
-佔葡
-佔俄
-佔蘇
-佔缺
-佔A
-佔B
-佔C
-佔D
-佔E
-佔F
-佔G
-佔H
-佔I
-佔J
-佔K
-佔L
-佔M
-佔N
-佔O
-佔P
-佔Q
-佔R
-佔S
-佔T
-佔U
-佔V
-佔W
-佔X
-佔Y
-佔Z
-佔a
-佔b
-佔c
-佔d
-佔e
-佔f
-佔g
-佔h
-佔i
-佔j
-佔k
-佔l
-佔m
-佔n
-佔o
-佔p
-佔q
-佔r
-佔s
-佔t
-佔u
-佔v
-佔w
-佔x
-佔y
-佔z
-佔A
-佔B
-佔C
-佔D
-佔E
-佔F
-佔G
-佔H
-佔I
-佔J
-佔K
-佔L
-佔M
-佔N
-佔O
-佔P
-佔Q
-佔R
-佔S
-佔T
-佔U
-佔V
-佔W
-佔X
-佔Y
-佔Z
-佔a
-佔b
-佔c
-佔d
-佔e
-佔f
-佔g
-佔h
-佔i
-佔j
-佔k
-佔l
-佔m
-佔n
-佔o
-佔p
-佔q
-佔r
-佔s
-佔t
-佔u
-佔v
-佔w
-佔x
-佔y
-佔z
-佔不佔
-不佔
-佔了
-佔穩
-佔資源
-佔人便宜
-佔頭
-佔道
-佔屋
-佔網
-佔床
-佔座
-佔分
-佔飯
-佔個位
-佔後
-佔著
-佔山
-馬占山
-佔比
-佔停車
-佔哺乳
-佔下風
-少佔
-多佔
-費佔
-佔查
-佔壓
-佔優
-佔劣
-穩佔
-佔整體
-佔局部
-日佔
-美佔
-英佔
-德佔
-法佔
-俄佔
-葡佔
-西佔
-奧佔
-意佔
-義佔
-地佔
-佔場
-佔耕
-狂佔
-徵佔
-圈佔
-已佔
-佔囁
-佔主
-佔次
-寡佔
-佔去
-將佔
-將占卜
-要佔
-要占卜
-會佔
-會占卜
-占卜
-夢有五不占
-占有五不驗
-誌異
-筑前
-筑後
-筑紫
-筑波
-筑州
-筑肥
-筑西
-筑北
-肥筑方言
-筑邦
-筑陽
-南筑
-批准的
-核准的
-為準
-準直
-擺鐘
-編鐘
-碰鐘
-鳴鐘
-晨鐘
-鐘體
-飯後鐘
-盜鐘
-一天鐘
-撞鐘
-殿鐘自鳴
-天文鐘
-天文學鐘
-洛鐘東應
-亮鐘
-郘鐘
-歌鐘
-鐘不撞不鳴
-毀鐘為鐸
-洪鐘
-擊鐘
-警世鐘
-竊鐘掩耳
-琴鐘
-見鐘不打
-釁鐘
-朝鐘
-木鐘
-鐘不扣不鳴
-鐘鳴
-鐘塔
-鐘漏
-鐘琴
-鐘磬
-鐘形蟲
-鐘乳洞
-鐘乳石
-鐘在寺裡
-詩鐘
-懸鐘
-山崩鐘應
-坐鐘
-宗周鐘
-塞耳盜鐘
-二缶鐘惑
-口鐘
-鐘的
-的鐘
-這鐘
-叩鐘
-音聲如鐘
-應鐘
-原子鐘
-泳氣鐘
-電子鐘
-電子鐘錶
-石英鐘錶
-石英鐘
-鐘錶王
-鐘律
-看鐘
-看錶
-看表面
-鐵鐘
-看下鐘
-看下錶
-瞅下鐘
-瞅下錶
-拿下鐘
-拿下錶
-鐘不敲不響
-對準鐘
-對準鐘錶
-對準錶
-鐘錶快
-鐘快
-錶快
-鐘錶慢
-鐘慢
-錶慢
-響鐘
-鐘敲
-大本鐘敲
-大笨鐘敲
-世紀鐘錶
-世紀鐘
-錶王
-鐘王
-鐘錶
-古鐘
-古鐘錶
-鐘面
-鐘表面
-南京鐘
-南京鐘錶
-造鐘錶
-造鐘
-九龍表行
-鐘錶行
-鐘行
-錶行
-小型鐘表面
-小型鐘面
-小型鐘錶
-小型鐘
-中型鐘表面
-中型鐘面
-中型鐘錶
-中型鐘
-大型鐘表面
-大型鐘面
-大型鐘錶
-大型鐘
-鐘匠
-深山何處鐘
-下課鐘
-上課鐘
-老爺鐘
-萬年曆錶
-個鐘
-個鐘錶
-喜歡鐘
-喜歡鐘錶
-喜歡錶
-大鐘
-佛鐘
-鐘壁
-鐘腰
-鐘口
-鐘身
-鐘模
-鐘頂
-鐘紐
-鐘座
-他鐘
-寺鐘
-座鐘
-盜鐘
-大笨鐘
-大本鐘
-鐘錶歷史
-錶的歷史
-鐘錶的歷史
-點多鐘
-點半鐘
-分多鐘
-刻多鐘
-分半鐘
-刻半鐘
-教學鐘
-操作鐘
-南屏晚鐘
-敲鐘
-瞧著鐘
-瞧著鐘錶
-瞧著錶
-警報鐘
-猶如鐘
-猶如鐘錶
-猶如錶
-舊鐘錶
-繁鐘
-四面鐘
-更鐘
-警示鐘
-鐘差
-任何鐘錶
-任何鐘
-任何錶
-任何表示
-任何表達
-任何表演
-選手表現
-選手表達
-選手表示
-選手表明
-選手表決
-分子鐘
-飛行鐘
-鐘罩
-主鐘差
-花鐘
-磬鐘
-主鐘曲線
-鐘速
-紅鐘
-各類鐘
-打著鐘
-鐘意
-衛星鐘
-該鐘
-錶轉
-鐘調
-調鐘錶
-調錶
-原鐘
-鐘錶速
-件鐘
-鐘發音
-逆鐘
-拂鐘無聲
-鐘不空則啞
-看著鐘錶
-看著鐘
-看著錶
-晚鐘
-潛水鐘錶
-潛水鐘
-潛水錶
-樂器鐘
-鐘左右
-埋頭尋鐘錶
-埋頭尋鐘
-埋頭尋錶
-鐘陳列
-驚鐘
-望著鐘錶
-望著鐘
-望著錶
-鐘錶停
-鐘停
-銫鐘
-數字鐘錶
-數字鐘
-顯示鐘錶
-顯示鐘
-顯示錶
-坐如鐘
-錶停
-西周鐘
-東周鐘
-錶速
-機械鐘錶
-機械鐘
-機械錶
-之鐘
-鐘形
-架鐘
-順鐘向
-逆鐘向
-遺傳鐘
-鬧錶
-華嚴鐘
-懷鐘
-生物鐘
-鐘錶的
-錶的嘀嗒
-的鐘錶
-嘀嗒的錶
-鐘好
-鐘太
-鐘不
-鐘有
-鐘盤
-鐘錶盤
-鐘沒
-鐘被
-制鐘
-布穀鳥鐘
-咕咕鐘
-拉克施爾德鐘
-鐘上
-鐘下
-摸鐘
-舊鐘
-舊錶
-台鐘
-鐘響
-叩鐘
-計時錶
-防水錶
-射鵰
-神鵰
-神雕像
-采石磯
-采石之戰
-采石之役
-聊齋志異
-部落發
-角落發
-村落發
-蛇髮女妖
-畢生發展
-對華發動
-中美發表
-尸魂界
-樹樑
-屋樑
-樑柱
-柱樑
-下樑
-上梁山
-昇陽
-僥倖
-夏遊
-秋遊
-冬遊
-黑奴籲天錄
-林郁方
-讚歌
-編餘
-餘墨
-唾餘
-餘韻
-歸餘
-公餘
-寬餘
-餘糧
-餘慶
-餘殃
-餘燼
-劫餘
-結餘
-燼餘
-淨餘
-餕餘
-餘暉
-餘輝
-羨餘
-餘悸
-心餘
-刑餘
-緒餘
-血餘
-朱慶餘
-諸餘
-餘論
-茶餘
-廚餘
-餘裕
-餘氣
-詩餘
-詞餘
-餘僇
-餘辜
-餘責
-餘罪
-無餘
-耳餘
-餘烈
-餘思
-鹽餘
-嬴餘
-贏餘
-王餘魚
-紆餘
-餘波
-餘杯
-餘步
-餘妙
-餘音
-餘聲
-餘明
-餘風
-餘黨
-餘毒
-餘桃
-餘桶
-餘利
-餘瀝
-餘膏
-餘光
-餘杭
-餘竅
-餘缺
-餘暇
-餘閒
-餘羨
-餘響
-餘興
-餘蓄
-餘緒
-餘珍
-餘眾
-餘酲
-餘喘
-餘食
-餘熱
-餘刃
-餘閏
-餘存
-餘業
-餘姚
-餘蔭
-餘映
-餘外
-餘威
-餘味
-餘溫
-餘勇
-多餘
-剩餘
-餘生
-餘歡
-有餘
-一餘
-二餘
-兩餘
-三餘
-四餘
-五餘
-六餘
-七餘
-八餘
-九餘
-十餘
-百餘
-千餘
-萬餘
-億餘
-兆餘
-0餘
-1餘
-2餘
-3餘
-4餘
-5餘
-6餘
-7餘
-8餘
-9餘
-0餘
-1餘
-2餘
-3餘
-4餘
-5餘
-6餘
-7餘
-8餘
-9餘
-余姓
-余光生
-余光中
-余思敏
-余威德
-余子明
-余三勝
-崑山
-崑曲
-崑腔
-崑調
-崑劇
-崑蘇
-蘇崑
-分布圖
-一干家中
-星期後
-不准你
-不准我
-不准他
-不准她
-不准它
-不准誰
-不准許
-准不准你
-准不准我
-准不准他
-准不准她
-准不准它
-准不准誰
-准不准許
-依依不捨
-戀戀不捨
-窮追不捨
-緊追不捨
-鍥而不捨
-稜登
-前言不答後語
-繃扒弔拷
-不弔
-不通弔慶
-陪弔
-盆弔
-屁股大弔了心
-撇弔
-憑弔
-門弔兒
-伐罪弔民
-打出弔入
-搗鬼弔白
-弔膀子
-弔民
-弔民伐罪
-弔奠
-弔頭
-弔古
-弔古尋幽
-弔詭
-弔詭矜奇
-弔客
-弔拷
-弔拷繃扒
-弔扣
-弔賀迎送
-弔鶴
-弔喉
-弔謊
-弔祭
-弔腳兒事
-弔頸
-弔橋
-弔取
-弔孝
-弔紙
-弔者大悅
-弔場
-弔書
-弔詞
-弔死問孤
-弔死問疾
-弔撒
-弔喪
-弔喪問疾
-弔腰撒跨
-弔唁
-弔宴
-弔喭
-弔影
-弔慰
-弔文
-弔問
-頭巾弔在水裡
-提心弔膽
-弄鬼弔猴
-管人弔腳兒事
-開弔
-鶴弔
-昊天不弔
-花馬弔嘴
-會弔
-吉凶慶弔
-蟣蝨相弔
-祭弔
-祭弔文
-青蠅弔客
-慶弔
-形影相弔
-哀弔
-一弔
-唁弔
-於水
-安於
-迫於
-罷於
-蹪於
-於敝
-於過
-甚於
-等於
-定於
-利於
-對於
-推舟於陸
-退藏於密
-歸於
-難於
-移禍於
-生於
-立於
-多於
-勝於
-傳於
-流於
-過於
-關於
-毀於
-基於
-急於
-嫁禍於
-借聽於聾
-見於
-鑒於
-謹於心
-求道於盲
-始於
-於藍
-出於
-輕於
-行百里者半於九十
-幸於
-怠於
-詢於芻蕘
-止於
-至於
-拙於
-忠於
-終於
-重於
-垂於
-善於
-死於
-屬於
-浮於
-在於
-厝薪於火
-易於
-精於
-由於
-於此
-燕巢於幕
-於菟
-於乎
-於戲
-於邑
-補於
-位於
-於今
-於是
-於是乎
-於斯
-寓於
-月離於畢
-月麗於箕
-源於
-且於
-長於
-短於
-現於
-較於
-於之
-分布於
-分散於
-優於
-早於
-晚於
-感於
-鬼谷子
-于美人
-緊緻
-冗餘
-曰云
-若干
-徵婚
-鬥鬨
-事有鬥巧
-歹鬥
-鬥茶
-鬥鴨
-爭奇鬥妍
-誇能鬥智
-春香鬥學
-鬥引
-鬥彩
-鬥武
-鬥悶
-鬥牙拌齒
-鬥幌子
-鬥腳
-雞吵鵝鬥
-辯鬥
-廝鬥
-誇多鬥靡
-臨潼鬥寶
-鬥趣
-撩鬥
-傲霜鬥雪
-賭鬥
-搬鬥
-鬥爭鬥合
-鬥疊
-鬥文
-耍鬥
-鬥巧
-油鬥
-蚊動牛鬥
-卵與石鬥
-挑鬥
-爭奇鬥異
-鬥葉子
-鬥分子
-爭妍鬥奇
-不鬥
-鬥心眼
-鬥頭
-挌鬥
-好鬥
-鬥合
-拚鬥
-兩虎共鬥
-兩鼠鬥穴
-鬥犀臺
-鬥牙鬥齒
-惡鬥
-鬥勝
-鬥富
-鬥艦
-鬥葉兒
-鬥彆氣
-鬥話
-鬥牌
-鬥百草
-鬥打
-鬥犬
-鬥風
-鬥雪紅
-鬥暴
-鬥閑氣
-龍鬥虎傷
-殷師牛鬥
-二虎相鬥
-鬥力
-爭紅鬥紫
-鬥麗
-鬥狠
-鬥飣
-虎鬥
-引鬥
-爭妍鬥豔
-轉鬥千里
-鬥而鑄兵
-困鬥
-好勇鬥狠
-爭奇鬥豔
-使其鬥
-鬥地主
-石樑
-木樑
-藏歷史
-頁面
-方面
-表面
-面條目
-課餘
-節餘
-盈餘
-病餘
-餘地
-餘力
-餘子
-餘事
-扶餘國
-腐餘
-富餘
-之餘
-餘澤
-流風餘俗
-流風餘韻
-淋餘土
-餘一
-餘二
-餘三
-餘四
-餘五
-餘六
-餘七
-餘八
-餘九
-餘十
-零餘
-〇餘
-餘零
-餘〇
-餘1
-餘2
-餘3
-餘4
-餘5
-餘6
-餘7
-餘8
-餘9
-餘0
-餘1
-餘2
-餘3
-餘4
-餘5
-餘6
-餘7
-餘8
-餘9
-餘0
-餘數
-其餘
-尸居餘氣
-賸餘
-餘孽
-殘餘
-業餘
-餘割
-餘款
-餘角
-餘切
-餘霞
-餘下
-餘弦
-餘震
-餘貾
-餘額
-禹餘糧
-餘人
-編余
-病余
-餘俗
-餘倍
-同餘
-大讚
-唄讚
-褒讚
-謬讚
-誄讚
-祝讚
-詩讚
-賞讚
-讚唄
-飛紮
-紮裹
-紮腳
-紮詐
-紮囮
-住紮
-佔畢
-佔頭籌
-佔高枝兒
-隱佔
-憑摺
-沒摺至
-大摺兒
-大週摺
-火摺子
-裝摺
-變徵
-談徵
-納徵
-流徵
-柳詒徵
-固徵
-貴徵
-考徵
-咎徵
-杞宋無徵
-休徵
-徵辟
-徵名責實
-徵發
-徵風召雨
-徵答
-徵啟
-徵選
-徵招
-徵士
-徵庸
-之徵
-瑞徵
-三徵七辟
-額徵
-有徵
-有征服
-有征戰
-有征伐
-有征討
-無徵不信
-文徵明
-徵跡
-徵車
-徵效
-徵怪
-徵聖
-徵咎
-徵吏
-徵令
-本徵
-船鐘
-黃鈺筑
-齊莊
-鴻案相莊
-項莊
-韋莊
-鍋莊
-鄭莊公
-通莊
-蒙莊
-端莊
-票莊
-矜莊
-楚莊問鼎
-楚莊絕纓
-整莊
-打路莊板
-莊騷
-莊語
-莊舄越吟
-莊房
-莊客
-莊農
-平泉莊
-布莊
-香山庄
-寶莊
-坐莊
-周莊王
-發莊
-卞莊
-包莊
-剔莊貨
-劉克莊
-冷莊子
-石家莊
-卞莊子
-新莊市
-當準
-憑準
-沒準
-蜂準
-推情準理
-寇準
-合準
-準保
-準譜
-準分子
-準點
-一個準
-準擬
-準貨幣
-準式
-認準
-三準
-鵝準
-有準
-崑崙
-鎌倉
-請君入甕
-甕安
-痊癒
-治癒
-病癒
-大病初癒
-癒合
-槓桿
-宣洩
-圖鑑
-諮詢
-勳章
-張勳
-趙治勳
-殭屍
-有栖川
-兇惡
-兇狠
-兇猛
-兇橫
-兇悍
-兇險
-兇相
-兇犯
-嫌兇
-兇嫌
-兇疑
-兇刀
-兇槍
-很兇
-兇巴巴
-行兇前
-凝鍊
-鍊貧
-鍊度
-鍊形
-鍊師
-鍊石
-鍊字
-鍊冶
-細鍊
-陳鍊
-闖鍊
-鍊汞
-淬鍊
-鋼之鍊金術師
-索馬里
-范登堡
-世田谷
-製漿
-三統歷史
-伊斯蘭教歷史
-伊斯蘭歷史
-儒略改革歷史
-儒略歷史
-公歷史
-台歷史
-合歷史
-周歷史
-商歷史
-四分歷史
-回歷史
-埃及歷史
-大明歷史
-大歷史
-大衍歷史
-太初歷史
-官歷史
-寶歷史
-巧歷史
-希伯來歷史
-弘歷史
-慶歷史
-日歷史
-星歷史
-月歷史
-朱理安歷史
-桌歷史
-永歷史
-玉歷史
-百花歷史
-皇歷史
-皇極歷史
-穆罕默德歷史
-算歷史
-紀歷史
-舊歷史
-航海歷史
-萬歷史
-行事歷史
-農歷史
-農民歷史
-通歷史
-長歷史
-陰歷史
-陽歷史
-額我略歷史
-黃歷史
-天曆
-天歷史
-美醜
-獻醜
-出醜
-家醜
-遮醜
-醜八怪
-醜名
-醜詆
-醜態
-醜女
-醜類
-醜陋
-醜虜
-醜化
-醜劇
-醜媳婦
-醜小鴨
-醜行
-醜事
-醜聲
-醜人
-醜惡
-醜丫頭
-醜聞
-醜語
-母醜
-一齣子
-齣兒
-賣獃
-發獃
-大獃
-獃獃
-獃等
-獃頭
-獃腦
-獃根
-獃磕
-獃憨獃
-獃話
-獃氣
-獃想
-獃性
-獃滯
-獃著
-獃痴
-獃串了皮
-獃事
-獃人
-獃子
-好獃
-占便宜的是獃
-阿獃
-丰標
-丰姿
-丰韻
-鵰翎
-鵰心雁爪
-鵰鶚
-雙鵰
-撲鼕鼕
-普鼕鼕
-鼕鼕鼓
-令人髮指
-爆發指數
-開發
-剪其髮
-吐哺捉髮
-吐哺握髮
-含齒戴髮
-大金髮苔
-寸髮千金
-心長髮短
-戴髮含齒
-拔髮
-拔鬚
-揪髮
-揪鬚
-整髮用品
-斷髮文身
-滿頭洋髮
-燙一個髮
-燙一次髮
-燙個髮
-燙完髮
-燙次髮
-理一個髮
-理一次髮
-理個髮
-理完髮
-理次髮
-細如髮
-繫於一髮
-膚髮
-皮膚
-生華髮
-蒼髮
-被髮佯狂
-被髮入山
-被髮左衽
-被髮纓冠
-被髮陽狂
-身體髮膚
-髒髮
-髮光可鑑
-髮已霜白
-髮油
-髮為血之本
-髮網
-髮踊沖冠
-髮際
-黃髮
-齒落髮白
-剷頭
-剷刈
-口燥唇乾
-舌乾唇焦
-花菴詞選
-渾箇
-箇中原因
-箇中理由
-箇中高手
-箇中好手
-箇中強手
-箇中滋味
-箇中奧秘
-箇中奧妙
-箇中玄機
-箇中消息
-箇中資訊
-箇中訊息
-對表達
-對表現
-對表演
-對表揚
-對表中
-對表明
-不準確
-並不準確
-一伙頭
-一伙食
-一半只
-一干弟兄
-一干弟子
-一干部下
-一斗斗
-一面食
-萬一只
-上面糊
-不克自制
-不准沒
-不加自制
-不占凶吉
-不占卜
-不占吉凶
-不占算
-不好干涉
-不好干預
-不干預
-不干涉
-不干休
-不干犯
-不干擾
-不干你
-不干我
-不干他
-不干她
-不干它
-不干事
-不斗膽
-不每只
-不采聲
-專向往
-丰容
-之一只
-之二只
-之八九只
-也斗了膽
-事情干脆
-事都干脆
-二只得
-亦云
-人云
-以自制
-們斗了膽
-你斗了膽
-其一只
-其二只
-其八九只
-內面包
-內面包的
-准保護
-准保釋
-几上
-几淨窗明
-几凳
-几子
-几旁
-几椅
-几榻
-几面上
-出征收
-擊扑
-划一槳
-划了一會
-划到岸
-划到江心
-前面店
-千只可
-千只夠
-千只怕
-千只能
-千只足夠
-半只可
-半只夠
-占了卜
-口干冒
-口干政
-口干涉
-口干犯
-口干預
-古書云
-古語云
-只占卜
-只占吉
-只占神問卜
-只占算
-只身上已
-只身上無
-只身上有
-只身上沒
-只身上的
-只身世
-只身為
-只身份
-只身體
-只身前
-只身受
-只身後
-只身子
-只身形
-只身影
-只身心
-只身旁
-只身材
-只身段
-只身邊
-只身首
-只身高
-只采聲
-可自制
-台子女
-台子孫
-台布景
-台面前
-合府上
-後面店
-向往常
-向往日
-向往時
-向往來
-唯一只
-喂了一聲
-喜向往
-四出徵收
-四面包
-多半只
-好斗大
-好斗室
-好斗笠
-好斗篷
-好斗膽
-好斗蓬
-家具體
-家具備
-家具有
-小几
-尸利
-尸祿
-尸臣
-尸鳩
-已占卜
-已占算
-并迭
-所云
-所云云
-所占卜
-所占星
-所占算
-手表決
-手表態
-手表明
-手表演
-手表現
-手表示
-手表達
-手表露
-手表面
-才干休
-才干戈
-才干擾
-才干政
-才干涉
-才干預
-扎好底子
-扎好根
-扑撻
-打吨
-折向往
-拉面上
-拉面具
-拉面前
-拉面巾
-拉面無
-拉面皮
-拉面罩
-拉面色
-拉面部
-捉奸黨
-捉奸徒
-捉奸細
-捉奸賊
-敢情欲
-敢斗了膽
-敲扑
-方向往
-望了望
-桌几
-每每只
-法自制
-洒滌
-洒淅
-洒濯
-洒然
-灘涂
-特制住
-特制定
-特制止
-特制訂
-百只可
-百只夠
-百只怕
-百只足夠
-皮制服
-相克制
-相克服
-短几
-石几
-秒表明
-秒表示
-窗明几亮
-竹几
-精制伏
-精制住
-精制服
-經有云
-給我干脆
-編制法
-能干休
-能干戈
-能干擾
-能干政
-能干涉
-能干預
-能自制
-自制一下
-自制下來
-自制不
-自制之力
-自制之能
-自制他
-自制伏
-自制你
-自制地
-自制她
-自制情
-自制我
-自制服
-自制的能
-自制能力
-船只得
-船只有
-船只能
-草荐
-荐居
-荐臻
-荐饑
-要自制
-語有云
-跌扑
-轉向往
-酒帘
-裡面包
-金表態
-金表情
-金表揚
-金表明
-金表演
-金表現
-金表示
-金表達
-金表露
-金表面
-長几
-隆准許
-雄斗斗
-面包住
-面包辦
-面包廂
-面包含
-面包圍
-面包容
-面包庇
-面包紮
-面包抄
-面包括
-面包攬
-面包涵
-面包管
-面包羅
-面包著
-面包藏
-面包裝
-面包裹
-面包起
-面店舖
-面粉碎
-面粉紅
-面食麵
-面食飯
-顛顛仆仆
-高干擾
-高干預
-高度自制
-黃金表
-天后宮
-一吊錢
-不食乾腊
-傳位于四太子
-儉确之教
-党懷英
-八蜡
-憑几
-南宮适
-大蜡
-子云
-分子雲
-小价
-歲聿云暮
-崖广
-恕乏价催
-悲筑
-折子戲
-揮杆
-搤肮拊背
-文采郁郁
-木杆
-洪适
-球杆
-腊之以為餌
-腊毒
-蜡月
-蜡祭
-言云
-宜云
-貴价
-郁郁菲菲
-馬杆
-造麯
-麴生
-麴秀才
-麴塵
-麴櫱
-大麴
-黃麴毒素
-酒醴麴櫱
-麴道士
-麴錢
-麴車
-麴院
-鼠麴草
-不乾不淨
-生發生
-必須
-須根據
-·范
-、剋制
-,剋制
-。剋制
-!剋制
-?剋制
-;剋制
-:剋制
-不剋制
-也剋制
-了剋制
-他剋制
-們剋制
-剋制不了
-剋制不住
-力剋制
-力求剋制
-可以剋制
-和剋制
-在剋制
-地剋制
-夠剋制
-她剋制
-你剋制
-您剋制
-就剋制
-彼此剋制
-得剋制
-快剋制
-想剋制
-意剋制
-應剋制
-我剋制
-才剋制
-於剋制
-易剋制
-無法剋制
-的剋制
-盡量剋制
-而剋制
-能剋制
-與剋制
-著剋制
-要剋制
-軍隊剋制
-空投佈雷
-火箭佈雷
-海灣佈雷
-空中佈雷
-海上佈雷
-佈雷的
-佈雷,
-佈雷、
-佈雷。
-佈雷;
-佈雷艦
-佈雷艇
-佈雷速度
-佈雷封鎖
-滿拚自盡
-拚生盡死
-拚卻
-拚老命
-拚絕
-成於思
-單單於
-積澱
-澱積
-澱北片
-澱解物
-澱謂之滓
-淺澱
-堙澱
-茂都澱
-並曰入澱
-澱乃不耕之地
-藍澱
-皆可作澱
-澱山
-海淀山後
-澱澱
-掛鈎
-薴悴
-絡腮鬍
-落腮鬍
-山羊鬍
-幸運鬍
-刮鬍
-剃鬍
-吹鬍
-蓄鬍
-白鬍
-長鬍
-鬍髯
-髯鬍
-髭鬍
-鬚鬍
-范文瀾
-范文同
-范文正公
-范文程
-范文芳
-范文藤
-范文虎
-范文照
-發表
-乾重
-若干
-鈎心鬥角
-若干
-乾重
-全面包圍
-全面包裹
-機械系
-體系
-心理
-複分解
-鹰鵰
-叱咤903
-叱咤MY903
-叱咤My903
-叱咤樂壇
-叱咤咤
-叱咤叱咤叱咤咤
-叱咤叱叱咤
-正在叱咤
-空餘
-變髒
-天地志狼
-薴烯
-阿斯圖里亞斯
-雙折射
-心繫家
-心繫國
-心繫祖
-心繫北
-心繫京
-心繫南
-心繫西
-心繫東
-心繫四
-心繫川
-心繫浙
-心繫汶
-心繫廣
-心繫湖
-心繫山
-心繫台
-心繫江
-心繫昌
-心繫香
-心繫澳
-心繫港
-心繫泰
-心繫健
-心繫天
-心繫地
-心繫大
-心繫小
-心繫全
-心繫眾
-心繫奧
-心繫世
-心繫中
-心繫高
-心繫災
-心繫非
-心繫群
-心繫新
-心繫沈
-心繫唐
-心繫黃
-心繫乔
-心繫阮
-心繫父
-心繫母
-心繫病
-心繫故
-心繫哪
-心繫中
-心繫英
-心繫美
-心繫日
-心繫德
-心繫功
-心繫曉
-心繫神
-心繫萬
-心繫的
-心繫在
-心繫兩
-心繫社
-心繫曼
-心繫彼
-心繫風
-心繫募
-心繫一
-心繫何
-心繫困
-心繫輸
-心繫人
-心繫民
-心繫十
-心繫百
-心繫千
-心繫和
-心繫選
-心繫囑
-心繫我
-心繫你
-心繫您
-心繫他
-心繫她
-心繫它
-心繫伊
-心繫長
-心繫舞
-心繫蘭
-心繫五
-心繫生
-心繫婦
-心繫幼
-心繫茶
-心繫動
-心繫沙
-心繫林
-心繫摩
-心繫农
-心繫慈
-心繫麥
-心繫貧
-心繫富
-心繫遠
-心繫近
-心繫宣
-心繫傳
-心繫紅
-心繫老
-心繫重
-心繫震
-心繫妻
-心繫夫
-心繫女
-心繫子
-心繫著
-重回
-挑大樑
-扛大樑
-后豐
-製得
-限制
-控制
-製取
-第四出局
-心臟
-肝臟
-脾臟
-肺臟
-腎臟
-參與
-浮誇
-星巴克
-于謙
-于寘
-淳于
-于禁
-于敏中
-註:# 不作“注:”
-呆呆獸
-劃為# 不作“划為”
-併為一體
-併為一家
-一個# 避免“個裡”的錯誤
-兩個
-二個
-三個
-四個
-五個
-六個
-七個
-八個
-九個
-十個
-百個
-千個
-萬個
-億個
-兆個
-零個
-云:# 不作“雲:”
-電子表格
-雪裡紅
-雪裡蕻
-森林裡
-日子裡
-故事裡
-領域裡
-時間裡
-深淵裡
-醫院裡
-春假裡
-暑假裡
-秋假裡
-寒假裡
-春天裡
-夏天裡
-秋天裡
-冬天裡
-春日裡
-夏日裡
-秋日裡
-冬日裡
-嘴裡
-心裡
-皮裡陽秋
-肚裡
-苦裡
-裡勾外連
-裡面
-這裡
-中文裡
-山洞裡
-世界裡
-眼睛裡
-首發
-夸脫
-誰幹的
-鐘螺
-風采
-代碼表
-編碼表
-字碼表
-電碼表
-科斗
-佔領
-灕水
-點裡
-這只是
-這只不
-這只容
-這只允
-這只採
-這只用
-有只是
-有只不
-有只容
-有只允
-有只採
-有只用
-葉叶琹
-胡子昂
-包括
-特别致
-分别致
-會上簽訂
-會上簽署
-周一 # (及以下)避免“周一齣版”的錯誤
-周二
-周三
-周四
-周五
-周六
-韶山沖
-總裁制
-于丹
-于樂
-于冕
-于軍
-于吉
-于堅
-于姓
-于氏
-于娜
-于娟
-于山
-于帥
-于慧
-于振
-于敏
-于斌
-于晴
-于波
-于濤
-于衡
-于贈
-于越
-于靖
-于勒
-于格
-于仁泰
-于會泳
-于偉國
-于佳卉
-于光遠
-于克勒
-于凌奎
-于鳳至
-于化虎
-于占元
-于台煙
-于品海
-于國楨
-于大寶
-于天仁
-于子千
-于孔兼
-于學忠
-于家堡
-于小偉
-于小彤
-于山國
-于幼軍
-于廣洲
-于康震
-于式枚
-于從濂
-于德海
-于志寧
-于慎行
-于成龍
-于振武
-于明濤
-于是之
-于晨楠
-于根偉
-于樹潔
-于欣源
-于正昇
-于正昌
-于永波
-于漢超
-于江震
-于洪區
-于浩威
-于海洋
-于湘蘭
-于特森
-于玉立
-于秀敏
-于素秋
-于若木
-于蔭霖
-于西翰
-于遠偉
-于道泉
-于都縣
-于震寰
-于震環
-于非闇
-于風政
-于鳳桐
-于默奧
-于家堡
-于爾岑
-于默奧
-于貝爾
-于爾根
-于雙戈
-于里察
-于澤爾
-于斯塔德
-于斯達爾
-于爾里克
-于奇庫杜克
-于韋斯屈萊
-于克-蘭多縣
-于斯納爾斯貝里
-夏于喬
-涂姓
-涂坤
-涂天相
-涂序瑄
-涂澤民
-涂紹煃
-涂羽卿
-涂逢年
-涂長望
-涂謹申
-涂鴻欽
-涂壯勳
-涂醒哲
-涂善妮
-涂敏恆
-總裁制
-故云
-強制作用
-鬱南
-西米谷
-一出生
-二出生
-三出生
-四出生
-五出生
-六出生
-七出生
-八出生
-九出生
-十出生
-一出版
-二出版
-三出版
-四出版
-五出版
-六出版
-七出版
-八出版
-九出版
-十出版
-一出刊
-二出刊
-三出刊
-四出刊
-五出刊
-六出刊
-七出刊
-八出刊
-九出刊
-十出刊
-一出逃
-二出逃
-三出逃
-四出逃
-五出逃
-六出逃
-七出逃
-八出逃
-九出逃
-十出逃
-一出口
-二出口
-三出口
-四出口
-五出口
-六出口
-七出口
-八出口
-九出口
-十出口
-一出祁山
-二出祁山
-三出祁山
-四出祁山
-五出祁山
-六出祁山
-七出祁山
-八出祁山
-九出祁山
-十出祁山
-鬱林
-饑荒
-免徵
-亞美尼亞曆
-百科裡
-歷史裡
-戲裡
-作品裡
-專輯裡
-年代裡
-棺材裡
-注釋
-月面
-路面
-修杰楷
-修杰麟
-學裡
-獄裡
-館裡
-系列裡
-村子裡
-艷后
-廢后
-妖后
-后海灣
-仙后
-賈后
-賢后
-蜂后
-皇后
-王后
-王侯后
-母后
-武后
-歌后
-影后
-封后
-太后
-天后
-呂后
-后里
-后街
-后羿
-后稷
-后座
-后平路
-后安路
-后土
-后北街
-后冠
-望后石
-后角
-蟻后
-后妃
-大周后
-小周后
-染殿后
-准三后
-風后
-后母戊
-風後,
-人如風後入江雲
-中風後
-屏風後
-颱風後
-颳風後
-整風後
-打風後
-遇風後
-聞風後
-逆風後
-順風後
-大風後
-馬格里布
-伊里布
-劃入
-中庄子
-埔裏社撫墾局
-懸掛
-僱傭
-四捨六入
-宿舍
-會干擾
-代表
-高清愿
-瓷製
-竹製
-絲製
-莜麵
-劃入
-簡筑翎
-楊雅筑
-魔杰座
-杰威爾音樂
-彭于晏
-尸羅精舍
-索馬里 # (及以下)避免里海=>裏海的轉換
-西西里
-騰格里
-阿里
-村里長
-進制
-黃詩杰
-陳冲
-何杰
-劉佳怜
-于小惠
-于品海
-于耘婕
-于洋
-于澄
-于光新
-范賢惠
-于國治
-于楓
-于熙珍
-涂善妮
-邱于庭
-熊杰
-卜云吉
-黎吉雲
-于飛島
-代表
-水無怜奈
-傲遊 # 浏览器名
-夏于喬
-賭后
-后海灣
-立后綜
-甲后路
-劉芸后
-謝華后
-趙惠后
-趙威后
-聖后
-陳有后
-許虬
-網遊
-狄志杰
-伊適杰
-于冠華
-于台煙
-于雲鶴
-于忠肅集
-于友澤
-于和偉
-于來山
-于樂
-于天龍
-于謹
-于榮光
-電波鐘
-余三勝
-掛名
-啟發式
-舞后
-甄后
-郭后
-0年 # 協助分詞
-1年
-2年
-3年
-4年
-5年
-6年
-7年
-8年
-9年
-0年
-1年
-2年
-3年
-4年
-5年
-6年
-7年
-8年
-9年
-〇年
-零年
-一年
-兩年
-二年
-三年
-四年
-五年
-六年
-七年
-八年
-九年
-十年
-百年
-千年
-萬年
-億年
-周后
-0周後
-1周後
-2周後
-3周後
-4周後
-5周後
-6周後
-7周後
-8周後
-9周後
-0周後
-1周後
-2周後
-3周後
-4周後
-5周後
-6周後
-7周後
-8周後
-9周後
-零周後
-〇周後
-一周後
-二周後
-兩周後
-三周後
-四周後
-五周後
-六周後
-七周後
-八周後
-九周後
-十周後
-百周後
-千周後
-萬周後
-億周後
-幾周後
-多周後
-前往
-后瑞站
-帝后臺
-新井里美
-樗里子
-伊達里子
-濱田里佳子
-尊后
-叶志穗
-叶不二子
-于立成
-山谷道
-李志喜
-于欣
-于少保
-于海
-於海邊
-於海上
-于凌辰
-于魁智
-于鬯
-于仲文
-于再清
-于震
-於震前
-於震后
-於震中
-固定制
-毗婆尸佛
-尸棄佛
-划船
-划不來
-划拳
-划槳
-划動
-划艇
-划行
-划算
-總裁制
-恒生
-嚴云農
-手裏劍
-秦莊襄王
-伊東怜
-衛後莊公
-餘量
-並行
-郁郁青青
-協防
-對表格
-對表示
-對表達
-對表演
-對表明
-了然後
-戴表元
-張樂于張徐
-余力為
-葉叶琴
-万俟
-幾個
-澀谷區
-協調
-選手
-併發症
-併發重症
-併發模式
-併發型模式
-金色長髮
-紅色長髮
-一頭長髮
-的長髮
-黑色長髮
-前天
-昨天
-今天
-明天
-後天
-數學家
-科學家
-物理學家
-化學家
-生物學家
-天文學家
-游離
-子晳
-紅后假說
-書面
-不只
-高涌泉
-請求
-考試
-測試
-筆試
-口試
-冰冷
-王田里
-后姓
-台州
-田庄英雄
-計劃
-抑制劑
diff --git a/includes/zhtable/tradphrases_exclude.manual b/includes/zhtable/tradphrases_exclude.manual
deleted file mode 100644
index e6abb4e1..00000000
--- a/includes/zhtable/tradphrases_exclude.manual
+++ /dev/null
@@ -1,330 +0,0 @@
-三國誌
-聊齋誌異
-北迴
-南迴
-併排
-併進
-併在
-併成
-衝衝
-臺
-著
-佈
-纔
-采
-着
-借
-甦
-荐
-担
-可憐虫
-一齣
-上弔
-弔車
-弔橋
-弔嗓子
-弔床
-弔架
-弔桶
-弔桿
-弔橋
-弔燈
-弔環
-弔籃
-弔胃口
-弔臂
-弔銷
-形影相弔
-被髮
-散髮
-長髮
-髮毛
-髮端
-周而複始
-答複
-複興
-複舊
-顛複
-修複
-報複
-複活
-反複
-迴首
-彙總
-饑餓
-饑不擇食
-饑荒
-憑藉
-藉故
-藉口
-藉端
-藉詞
-藉酒
-蛋捲
-行李捲
-克裡
-纍纍
-華裡
-裡海
-瞭解
-明瞭
-發黴
-矇蔽
-矇住
-濛濛
-矇矇
-下麵
-白麵
-切麵
-和麵
-過水麵
-復甦
-複蘇
-甦醒
-体
-繫數
-遊擊
-馥鬱
-鬱鬱
-改製
-獃住
-獃氣
-獃子
-獃頭獃腦
-儘量
-希腊
-腊肉
-瞭如
-昇
-武鬆
-赤鬆
-黑鬆
-鬆林
-鬆科
-鬆濤
-鬆毛蟲
-鬆節油
-濕地鬆
-尼克鬆
-紮伊爾
-阿布紮比
-阿紮尼亞
-利比裡亞
-斯裡蘭卡
-烏蘇裡江
-加裡寧
-歐幾裡得
-格裡
-巴裡
-居裡
-卡裡
-墨索裡尼
-底裡
-裡人
-裡加
-裡裡
-馬裡
-裡拉
-阿裡
-裡斯
-鄰裡
-鄉裡
-百裡
-特裡
-海裡
-三元裡
-漏鬥
-春捲
-採邑
-嚮日
-佔城
-水錶
-名錶
-錶面
-彆腳
-併力
-併列
-併為
-豐富多採
-採採
-尼採
-小醜
-辛醜
-整齣
-嚴複
-枯幹
-干著急
-單於
-攻剋
-剋服
-闢邪
-釐米
-後樑
-石樑
-木樑
-舊莊
-介係詞
-介繫詞
-餘年
-大阪
-阪田
-豪杰
-七拚八湊
-一捲
-十捲
-上捲
-下捲
-加捲
-不捨
-不識檯舉
-稜登
-半弔子
-分布圖
-星鬥
-筋鬥
-斗鬨
-料鬥
-煙鬥
-熨鬥
-笆鬥
-箕鬥
-金鬥
-門鬥
-風鬥
-鬥子
-鬥笠
-老板娘
-剋制
-洋麵
-病癥
-製裁
-台製
-石家庄
-酒盃
-積极
-殭尸
-上梁不正
-項鍊
-鍊子
-鍊條
-拉鍊
-鉸鍊
-鍊鎖
-鐵鍊
-鍛鍊
-鍊乳
-鍊丹
-至于
-浮于
-附于
-次于
-于人
-助于
-行于
-于衷
-于事
-低于
-大于
-高于
-等于
-位于
-用于
-答覆
-複蓋
-反覆
-藉藉
-蘊藉
-蹈藉
-醞藉
-氆氌
-慰藉
-文藉
-枕藉
-狼藉
-別隻
-鼕鼕
-矇松雨
-佈雷
-丰度
-剪彩
-脣
-菴
-公裡
-箇中
-樑子
-樑書
-讚成
-讚同
-鐘表店
-精採
-鞭尸
-尸身
-尸首
-行尸走肉
-裹尸
-慼慼
-痠
-簑
-捱
-朝乾夕惕
-大曲酒
-神麴
-便于
-偏于
-勇于
-居于
-常見于
-強加于
-從事于
-忙于
-敢于
-服務于
-服從于
-樂于
-歸罪于
-歸諸于
-活動于
-瀕于
-苦于
-莫過于
-處于
-適于
-乾和
-鉤
-高陞
-大胆
-託福
-繫系
-酰
-醯
-大樑
-光採
-鍾錶
-複原
-參与
-浮夸
-剋日
-羡
-旅游
-穀風
-復讎
-避暑山庄
-遊牧
-烟草
-征
-占領
-入夥
-懸挂
-註釋
-浮遊
-冶鍊
-裡子
-裡外
-單隻
-聯係
-那裏
-殺虫藥
-好家伙
-姦污
-併發
-衚衕